@enigmax/primitives 0.17.0 → 0.19.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/dist/button-CaXaqG_K.d.ts +63 -0
- package/dist/chunk-2QFTRNAZ.js +88 -0
- package/dist/chunk-6BGBYUSZ.js +114 -0
- package/dist/chunk-AU3H5WIY.js +107 -0
- package/dist/{chunk-UZFEEFMF.js → chunk-D5A2ZMAG.js} +34 -1
- package/dist/chunk-DTWZDONY.js +99 -0
- package/dist/chunk-F25CGNQC.js +19 -0
- package/dist/chunk-FWVWX67R.js +462 -0
- package/dist/chunk-HC2ME5PU.js +168 -0
- package/dist/chunk-HS3X3XCW.js +43 -0
- package/dist/chunk-IXVMRVD4.js +96 -0
- package/dist/chunk-KJINGUQN.js +188 -0
- package/dist/chunk-MMQPZGSU.js +161 -0
- package/dist/chunk-OCMI7R6H.js +79 -0
- package/dist/chunk-QQFNAKMY.js +47 -0
- package/dist/chunk-QYMUIW5I.js +28 -0
- package/dist/chunk-R4ZAEE7V.js +249 -0
- package/dist/chunk-S653GLSF.js +17 -0
- package/dist/chunk-SNYUBXWQ.js +149 -0
- package/dist/chunk-U3V4EHOB.js +41 -0
- package/dist/chunk-UOSSNUSC.js +309 -0
- package/dist/chunk-XNNQRA35.js +31 -0
- package/dist/chunk-XQHCZAPJ.js +102 -0
- package/dist/chunk-ZCUFYBPB.js +154 -0
- package/dist/chunk-ZWR2EXHQ.js +55 -0
- package/dist/flags-BBJc9unY.d.ts +133 -0
- package/dist/index-dTdAbOWl.d.ts +144 -0
- package/dist/index.d.ts +18 -603
- package/dist/index.js +11 -2
- package/dist/input-BwXjFenq.d.ts +77 -0
- package/dist/marquee-CJ3Uwy3E.d.ts +81 -0
- package/dist/network-D2LsBG_k.d.ts +39 -0
- package/dist/next/index.d.ts +22 -4
- package/dist/next/index.js +25 -4
- package/dist/notifications-BpVV6sel.d.ts +70 -0
- package/dist/palette-D7iuQh_T.d.ts +86 -0
- package/dist/password-3DRQYAYQ.js +2 -0
- package/dist/password-C8lG4Zm9.d.ts +71 -0
- package/dist/password-FB2CUEKJ.js +1 -0
- package/dist/react/button.d.ts +75 -0
- package/dist/react/button.js +4 -0
- package/dist/react/flag.d.ts +37 -0
- package/dist/react/flag.js +3 -0
- package/dist/react/index.d.ts +29 -312
- package/dist/react/index.js +24 -3
- package/dist/react/input.d.ts +4 -0
- package/dist/react/input.js +3 -0
- package/dist/react/marquee.d.ts +44 -0
- package/dist/react/marquee.js +3 -0
- package/dist/react/network.d.ts +20 -0
- package/dist/react/network.js +3 -0
- package/dist/react/notifications.d.ts +17 -0
- package/dist/react/notifications.js +3 -0
- package/dist/react/palette.d.ts +231 -0
- package/dist/react/palette.js +5 -0
- package/dist/react/relative-time.d.ts +21 -0
- package/dist/react/relative-time.js +3 -0
- package/dist/react/search.d.ts +30 -0
- package/dist/react/search.js +3 -0
- package/dist/react/slot.d.ts +47 -0
- package/dist/react/slot.js +2 -0
- package/dist/react/toast.d.ts +40 -0
- package/dist/react/toast.js +4 -0
- package/dist/react-router/index.d.ts +22 -4
- package/dist/react-router/index.js +25 -4
- package/dist/relative-time-YpRTG7YH.d.ts +106 -0
- package/dist/search/index.d.ts +2 -2
- package/dist/search/index.js +1 -1
- package/dist/{search-CsO3L1Lw.d.ts → search-DXxY8SEH.d.ts} +15 -1
- package/dist/search-UQEXAPQB.js +50 -0
- package/package.json +54 -3
- package/recipes/input/styles.css +60 -20
- package/recipes/palette/styles.css +243 -0
- package/registry.json +186 -18
- package/src/core/flags.ts +88 -42
- package/src/core/input-icons.ts +34 -0
- package/src/core/input.ts +5 -21
- package/src/core/palette.ts +0 -0
- package/src/core/search.ts +60 -0
- package/src/index.ts +19 -3
- package/src/react/button.tsx +70 -4
- package/src/react/flag.tsx +18 -6
- package/src/react/index.ts +50 -5
- package/src/react/input/icon.tsx +17 -0
- package/src/react/input/index.tsx +307 -0
- package/src/react/input/password.tsx +174 -0
- package/src/react/input/search.tsx +82 -0
- package/src/react/input/types.ts +146 -0
- package/src/react/input/write-value.ts +18 -0
- package/src/react/palette/context.ts +60 -0
- package/src/react/palette/index.tsx +66 -0
- package/src/react/palette/root.tsx +661 -0
- package/src/react/slot.tsx +91 -0
- package/src/react/use-button.ts +3 -1
- package/dist/chunk-4GLYRF4B.js +0 -1259
- package/dist/chunk-JFNND6P4.js +0 -749
- package/src/react/input.tsx +0 -429
package/registry.json
CHANGED
|
@@ -155,8 +155,8 @@
|
|
|
155
155
|
},
|
|
156
156
|
{
|
|
157
157
|
"name": "input",
|
|
158
|
-
"title": "Input,
|
|
159
|
-
"description": "A field you pass props to,
|
|
158
|
+
"title": "Input, one field for every type",
|
|
159
|
+
"description": "A field you pass props to, typed by its `type`: password gets the reveal toggle, a generator, a strength meter and a breach check; search gets debouncing, ranking and a clear button; everything else is a plain field with native props. What each type needs is loaded when that type is used, so a form of text fields ships none of it.",
|
|
160
160
|
"targets": [
|
|
161
161
|
"vanilla",
|
|
162
162
|
"astro",
|
|
@@ -165,7 +165,7 @@
|
|
|
165
165
|
"entry": {
|
|
166
166
|
"vanilla": "@enigmax/primitives",
|
|
167
167
|
"astro": "@enigmax/primitives",
|
|
168
|
-
"react": "@enigmax/primitives/react"
|
|
168
|
+
"react": "@enigmax/primitives/react/input"
|
|
169
169
|
},
|
|
170
170
|
"exports": {
|
|
171
171
|
"vanilla": [
|
|
@@ -181,17 +181,28 @@
|
|
|
181
181
|
},
|
|
182
182
|
"files": [
|
|
183
183
|
{
|
|
184
|
-
"path": "src/core/input.ts",
|
|
185
|
-
"dest": "input/input.ts",
|
|
184
|
+
"path": "src/core/input-icons.ts",
|
|
185
|
+
"dest": "input/input-icons.ts",
|
|
186
186
|
"targets": [
|
|
187
187
|
"vanilla",
|
|
188
188
|
"astro",
|
|
189
189
|
"react"
|
|
190
190
|
]
|
|
191
191
|
},
|
|
192
|
+
{
|
|
193
|
+
"path": "src/core/input.ts",
|
|
194
|
+
"dest": "input/input.ts",
|
|
195
|
+
"targets": [
|
|
196
|
+
"vanilla",
|
|
197
|
+
"astro"
|
|
198
|
+
],
|
|
199
|
+
"rewrite": {
|
|
200
|
+
"@/core/input-icons": "./input-icons"
|
|
201
|
+
}
|
|
202
|
+
},
|
|
192
203
|
{
|
|
193
204
|
"path": "src/core/password.ts",
|
|
194
|
-
"dest": "input/password.ts",
|
|
205
|
+
"dest": "input/password-core.ts",
|
|
195
206
|
"targets": [
|
|
196
207
|
"vanilla",
|
|
197
208
|
"astro",
|
|
@@ -199,13 +210,56 @@
|
|
|
199
210
|
]
|
|
200
211
|
},
|
|
201
212
|
{
|
|
202
|
-
"path": "src/
|
|
203
|
-
"dest": "input/
|
|
213
|
+
"path": "src/core/search.ts",
|
|
214
|
+
"dest": "input/search-core.ts",
|
|
215
|
+
"targets": [
|
|
216
|
+
"react"
|
|
217
|
+
]
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"path": "src/react/input/types.ts",
|
|
221
|
+
"dest": "input/types.ts",
|
|
222
|
+
"targets": [
|
|
223
|
+
"react"
|
|
224
|
+
],
|
|
225
|
+
"rewrite": {
|
|
226
|
+
"@/core/search": "./search-core",
|
|
227
|
+
"@/core/password": "./password-core"
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"path": "src/react/input/icon.tsx",
|
|
232
|
+
"dest": "input/icon.tsx",
|
|
233
|
+
"targets": [
|
|
234
|
+
"react"
|
|
235
|
+
]
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
"path": "src/react/input/write-value.ts",
|
|
239
|
+
"dest": "input/write-value.ts",
|
|
240
|
+
"targets": [
|
|
241
|
+
"react"
|
|
242
|
+
]
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"path": "src/react/input/password.tsx",
|
|
246
|
+
"dest": "input/password.tsx",
|
|
204
247
|
"targets": [
|
|
205
248
|
"react"
|
|
206
249
|
],
|
|
207
250
|
"rewrite": {
|
|
208
|
-
"@/core/
|
|
251
|
+
"@/core/password": "./password-core",
|
|
252
|
+
"@/react/input/types": "./types"
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"path": "src/react/input/search.tsx",
|
|
257
|
+
"dest": "input/search.tsx",
|
|
258
|
+
"targets": [
|
|
259
|
+
"react"
|
|
260
|
+
],
|
|
261
|
+
"rewrite": {
|
|
262
|
+
"@/core/search": "./search-core"
|
|
209
263
|
}
|
|
210
264
|
},
|
|
211
265
|
{
|
|
@@ -219,15 +273,20 @@
|
|
|
219
273
|
"style": "css"
|
|
220
274
|
},
|
|
221
275
|
{
|
|
222
|
-
"path": "src/react/input.tsx",
|
|
276
|
+
"path": "src/react/input/index.tsx",
|
|
223
277
|
"dest": "input/index.tsx",
|
|
224
278
|
"main": true,
|
|
225
279
|
"targets": [
|
|
226
280
|
"react"
|
|
227
281
|
],
|
|
228
282
|
"rewrite": {
|
|
229
|
-
"@/
|
|
230
|
-
"@/
|
|
283
|
+
"@/react/input/icon": "./icon",
|
|
284
|
+
"@/react/input/write-value": "./write-value",
|
|
285
|
+
"@/core/input-icons": "./input-icons",
|
|
286
|
+
"@/react/input/types": "./types",
|
|
287
|
+
"@/react/input/password": "./password",
|
|
288
|
+
"@/react/input/search": "./search",
|
|
289
|
+
"@/core/password": "./password-core"
|
|
231
290
|
}
|
|
232
291
|
},
|
|
233
292
|
{
|
|
@@ -262,7 +321,8 @@
|
|
|
262
321
|
"[data-enigma-password-strength-segment]",
|
|
263
322
|
"[data-enigma-password-strength-segment][data-filled]",
|
|
264
323
|
"[data-enigma-password-strength-label]",
|
|
265
|
-
"[data-enigma-password-strength-warning]"
|
|
324
|
+
"[data-enigma-password-strength-warning]",
|
|
325
|
+
"[data-enigma-input-action=clear]"
|
|
266
326
|
],
|
|
267
327
|
"docs": "docs/notes/primitives.md#input",
|
|
268
328
|
"recipes": [
|
|
@@ -273,7 +333,7 @@
|
|
|
273
333
|
{
|
|
274
334
|
"name": "search",
|
|
275
335
|
"title": "Search as you type",
|
|
276
|
-
"description": "
|
|
336
|
+
"description": "The engine behind `<Input type=\"search\">` and the palette: debouncing, ranking and cancellation, fuzzy with Fuse.js or a built-in accent-insensitive substring matcher with no dependency at all.",
|
|
277
337
|
"targets": [
|
|
278
338
|
"vanilla",
|
|
279
339
|
"astro",
|
|
@@ -282,7 +342,7 @@
|
|
|
282
342
|
"entry": {
|
|
283
343
|
"vanilla": "@enigmax/primitives/search",
|
|
284
344
|
"astro": "@enigmax/primitives/search",
|
|
285
|
-
"react": "@enigmax/primitives/react"
|
|
345
|
+
"react": "@enigmax/primitives/react/search"
|
|
286
346
|
},
|
|
287
347
|
"exports": {
|
|
288
348
|
"vanilla": [
|
|
@@ -413,14 +473,122 @@
|
|
|
413
473
|
"[data-loading]",
|
|
414
474
|
"[data-cooldown]",
|
|
415
475
|
"[aria-disabled=true]",
|
|
416
|
-
"[aria-busy=true]"
|
|
476
|
+
"[aria-busy=true]",
|
|
477
|
+
"[data-enigma-button-key]"
|
|
417
478
|
],
|
|
418
479
|
"docs": "docs/notes/primitives.md#button"
|
|
419
480
|
},
|
|
481
|
+
{
|
|
482
|
+
"name": "palette",
|
|
483
|
+
"title": "Command palette",
|
|
484
|
+
"description": "Ctrl/Cmd+K, search as you type, what was searched before, groups, and every key a palette is expected to answer to. A dialog made of parts you can compose or take whole, with the focus trap, the scroll lock and the listbox wiring already right.",
|
|
485
|
+
"targets": [
|
|
486
|
+
"react"
|
|
487
|
+
],
|
|
488
|
+
"entry": {
|
|
489
|
+
"react": "@enigmax/primitives/react/palette"
|
|
490
|
+
},
|
|
491
|
+
"exports": {
|
|
492
|
+
"react": [
|
|
493
|
+
"SearchPalette"
|
|
494
|
+
]
|
|
495
|
+
},
|
|
496
|
+
"files": [
|
|
497
|
+
{
|
|
498
|
+
"path": "src/core/palette.ts",
|
|
499
|
+
"dest": "palette/palette.ts",
|
|
500
|
+
"targets": [
|
|
501
|
+
"react"
|
|
502
|
+
]
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
"path": "src/core/search.ts",
|
|
506
|
+
"dest": "palette/search-core.ts",
|
|
507
|
+
"targets": [
|
|
508
|
+
"react"
|
|
509
|
+
]
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
"path": "src/react/slot.tsx",
|
|
513
|
+
"dest": "palette/slot.tsx",
|
|
514
|
+
"targets": [
|
|
515
|
+
"react"
|
|
516
|
+
]
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
"path": "recipes/palette/styles.css",
|
|
520
|
+
"dest": "palette/styles.css",
|
|
521
|
+
"targets": [
|
|
522
|
+
"react"
|
|
523
|
+
],
|
|
524
|
+
"style": "css"
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
"path": "src/react/palette/context.ts",
|
|
528
|
+
"dest": "palette/context.ts",
|
|
529
|
+
"targets": [
|
|
530
|
+
"react"
|
|
531
|
+
],
|
|
532
|
+
"rewrite": {
|
|
533
|
+
"@/core/palette": "./palette",
|
|
534
|
+
"@/core/search": "./search-core"
|
|
535
|
+
}
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
"path": "src/react/palette/root.tsx",
|
|
539
|
+
"dest": "palette/root.tsx",
|
|
540
|
+
"targets": [
|
|
541
|
+
"react"
|
|
542
|
+
],
|
|
543
|
+
"rewrite": {
|
|
544
|
+
"@/core/palette": "./palette",
|
|
545
|
+
"@/core/search": "./search-core",
|
|
546
|
+
"@/react/slot": "./slot",
|
|
547
|
+
"@/react/palette/context": "./context"
|
|
548
|
+
}
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
"path": "src/react/palette/index.tsx",
|
|
552
|
+
"dest": "palette/index.tsx",
|
|
553
|
+
"main": true,
|
|
554
|
+
"targets": [
|
|
555
|
+
"react"
|
|
556
|
+
],
|
|
557
|
+
"rewrite": {
|
|
558
|
+
"@/react/palette/root": "./root",
|
|
559
|
+
"@/react/palette/context": "./context"
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
],
|
|
563
|
+
"styles": false,
|
|
564
|
+
"themeHooks": [
|
|
565
|
+
"[data-enigma-palette-trigger]",
|
|
566
|
+
"[data-enigma-palette-trigger-key]",
|
|
567
|
+
"[data-enigma-palette-overlay]",
|
|
568
|
+
"[data-enigma-palette-content]",
|
|
569
|
+
"[data-enigma-palette-title]",
|
|
570
|
+
"[data-enigma-palette-field]",
|
|
571
|
+
"[data-enigma-palette-list]",
|
|
572
|
+
"[data-enigma-palette-group]",
|
|
573
|
+
"[data-enigma-palette-group-label]",
|
|
574
|
+
"[data-enigma-palette-item]",
|
|
575
|
+
"[data-enigma-palette-item][data-active=true]",
|
|
576
|
+
"[data-enigma-palette-item][data-kind=recent]",
|
|
577
|
+
"[data-enigma-palette-item-label]",
|
|
578
|
+
"[data-enigma-palette-item-description]",
|
|
579
|
+
"[data-enigma-palette-empty]",
|
|
580
|
+
"[data-enigma-palette-footer]",
|
|
581
|
+
"[data-enigma-palette-hint]"
|
|
582
|
+
],
|
|
583
|
+
"docs": "docs/notes/primitives.md#palette",
|
|
584
|
+
"recipes": [
|
|
585
|
+
"css"
|
|
586
|
+
]
|
|
587
|
+
},
|
|
420
588
|
{
|
|
421
589
|
"name": "flags",
|
|
422
590
|
"title": "Country flags",
|
|
423
|
-
"description": "A flag as an image,
|
|
591
|
+
"description": "A flag as an image, served by enigma or from files in your project, rectangular, square or round. It names itself in the reader's language, and accepts the emoji flag it replaces as its code - Windows ships no glyph for those, so a picker built out of emoji renders as bare letters for most desktop readers.",
|
|
424
592
|
"targets": [
|
|
425
593
|
"vanilla",
|
|
426
594
|
"astro",
|
|
@@ -429,7 +597,7 @@
|
|
|
429
597
|
"entry": {
|
|
430
598
|
"vanilla": "@enigmax/primitives",
|
|
431
599
|
"astro": "@enigmax/primitives",
|
|
432
|
-
"react": "@enigmax/primitives/react"
|
|
600
|
+
"react": "@enigmax/primitives/react/flag"
|
|
433
601
|
},
|
|
434
602
|
"exports": {
|
|
435
603
|
"vanilla": [
|
package/src/core/flags.ts
CHANGED
|
@@ -8,12 +8,14 @@
|
|
|
8
8
|
* out of them looks broken to those readers, with nothing in the code to explain why.
|
|
9
9
|
* There is no font stack that fixes it, so the fix is not a font: it is an image.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
11
|
+
* Three sets, all SVG, all served by enigma itself:
|
|
12
12
|
*
|
|
13
|
-
* - `rect`
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* - `rect` (4:3) and `square` (1:1) - the classic rectangular artwork.
|
|
14
|
+
* - `circle` - round, which is what a flag next to an avatar or inside a chip wants.
|
|
15
|
+
*
|
|
16
|
+
* They are served from enigma's own tree rather than from anyone else's, because a
|
|
17
|
+
* component whose default source is a third party is a component that breaks when that
|
|
18
|
+
* third party moves, renames a branch, or goes away.
|
|
17
19
|
*
|
|
18
20
|
* Nothing here touches the DOM, so it renders on a server.
|
|
19
21
|
*/
|
|
@@ -21,26 +23,24 @@
|
|
|
21
23
|
export type FlagShape = "rect" | "square" | "circle";
|
|
22
24
|
|
|
23
25
|
/**
|
|
24
|
-
* `svg` is the
|
|
25
|
-
*
|
|
26
|
-
*
|
|
26
|
+
* `svg` is what the sets are stored as, so it is what a remote source can serve. `png` and
|
|
27
|
+
* `webp` exist for a LOCAL set (`enigma add flags --flags local --flag-formats webp`), which
|
|
28
|
+
* rasterises what it downloads, and for a mirror of your own.
|
|
27
29
|
*/
|
|
28
30
|
export type FlagFormat = "svg" | "png" | "webp";
|
|
29
31
|
|
|
30
32
|
/**
|
|
31
33
|
* Where the files come from.
|
|
32
34
|
*
|
|
33
|
-
* - `"cdn"` -
|
|
35
|
+
* - `"cdn"` - enigma's own artwork, over a CDN. Nothing to install.
|
|
34
36
|
* - `"local"` - files under `basePath` in your own public directory.
|
|
35
|
-
* - any URL - a mirror of your own with the same layout
|
|
37
|
+
* - any URL - a mirror of your own with the same layout
|
|
36
38
|
* (`<base>/<shape>/<code>.<format>`), which is exactly the tree the downloader writes.
|
|
37
39
|
*/
|
|
38
40
|
export type FlagSource = "cdn" | "local" | (string & {});
|
|
39
41
|
|
|
40
|
-
/**
|
|
41
|
-
export const
|
|
42
|
-
/** circle-flags publishes from a branch, not a tag; gh-pages is its default branch. */
|
|
43
|
-
export const CIRCLE_FLAGS_REF = "gh-pages";
|
|
42
|
+
/** enigma's own artwork, over a CDN. Every source shares this layout. */
|
|
43
|
+
export const FLAG_CDN = "https://cdn.jsdelivr.net/gh/FJRG2007/enigma@main/assets/flags";
|
|
44
44
|
|
|
45
45
|
export interface FlagOptions {
|
|
46
46
|
shape?: FlagShape;
|
|
@@ -50,10 +50,16 @@ export interface FlagOptions {
|
|
|
50
50
|
basePath?: string;
|
|
51
51
|
/** Rendered HEIGHT in px. The width follows the shape's ratio. Default 16. */
|
|
52
52
|
size?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Language the automatic name is written in. Left out, it follows the document's own
|
|
55
|
+
* `lang`, which is what a translated page wants.
|
|
56
|
+
*/
|
|
57
|
+
locale?: string;
|
|
53
58
|
}
|
|
54
59
|
|
|
55
|
-
export interface FlagConfig extends Required<Omit<FlagOptions, "size">> {
|
|
60
|
+
export interface FlagConfig extends Required<Omit<FlagOptions, "size" | "locale">> {
|
|
56
61
|
size: number;
|
|
62
|
+
locale?: string;
|
|
57
63
|
}
|
|
58
64
|
|
|
59
65
|
const DEFAULTS: FlagConfig = {
|
|
@@ -96,12 +102,14 @@ const EMOJI_FLAG = /^[\u{1F1E6}-\u{1F1FF}]{2}$/u;
|
|
|
96
102
|
* A BCP 47 tag whose region is spelled the way the standard spells it: UPPERCASE. That
|
|
97
103
|
* casing is the whole discriminator, and it has to be read before the value is lowercased -
|
|
98
104
|
* `es-ES` is Spanish-in-Spain and means the flag `es`, while `es-ct` is a subdivision code
|
|
99
|
-
*
|
|
105
|
+
* the sets publish (Catalonia) and means the file `es-ct`. Lowercasing first makes those
|
|
100
106
|
* two the same string, and then one of them is always wrong.
|
|
101
107
|
*/
|
|
102
108
|
const LOCALE_TAG = /^([A-Za-z]{2,3})[-_]([A-Z]{2})$/;
|
|
103
|
-
/** What the
|
|
109
|
+
/** What the sets actually name their files: `es`, `gb-eng`, `au-nsw`, `easter_island`. */
|
|
104
110
|
const FILE_CODE = /^[a-z]{2,}(?:[-_][a-z0-9]+)*$/;
|
|
111
|
+
/** A plain country: exactly the shape `Intl.DisplayNames` can put a name to. */
|
|
112
|
+
const COUNTRY_CODE = /^[a-z]{2}$/;
|
|
105
113
|
|
|
106
114
|
/**
|
|
107
115
|
* Anything a codebase calls a country into the file name the sets use, or null.
|
|
@@ -128,21 +136,48 @@ export function normalizeFlagCode(value: string | null | undefined): string | nu
|
|
|
128
136
|
return FILE_CODE.test(code) ? code : null;
|
|
129
137
|
}
|
|
130
138
|
|
|
139
|
+
/**
|
|
140
|
+
* The country's name, in the reader's language, or null.
|
|
141
|
+
*
|
|
142
|
+
* `Intl.DisplayNames` is the whole implementation: the names are in the runtime already,
|
|
143
|
+
* translated, and maintained by whoever maintains the platform - so this ships no table of
|
|
144
|
+
* 250 country names to go stale, and a Spanish page says "España" without being told.
|
|
145
|
+
*
|
|
146
|
+
* Null for anything it cannot name, and that is deliberate for SUBDIVISIONS: `gb-eng` is
|
|
147
|
+
* England, and falling back to the region (`gb`, "United Kingdom") would put a confidently
|
|
148
|
+
* wrong name on the flag. A name nobody can source is worse than no name.
|
|
149
|
+
*/
|
|
150
|
+
export function flagName(code: string | null | undefined, locale?: string): string | null {
|
|
151
|
+
const normalized = normalizeFlagCode(code);
|
|
152
|
+
if (!normalized || !COUNTRY_CODE.test(normalized)) return null;
|
|
153
|
+
const region = normalized.toUpperCase();
|
|
154
|
+
try {
|
|
155
|
+
const names = new Intl.DisplayNames([locale ?? documentLocale() ?? "en"], { type: "region" });
|
|
156
|
+
const name = names.of(region);
|
|
157
|
+
// It echoes the input back when it has no name for the code.
|
|
158
|
+
return !name || name === region ? null : name;
|
|
159
|
+
} catch {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** The page's own language, which is what a translated page wants the name written in. */
|
|
165
|
+
function documentLocale(): string | undefined {
|
|
166
|
+
if (typeof document === "undefined") return undefined;
|
|
167
|
+
return document.documentElement.lang || undefined;
|
|
168
|
+
}
|
|
169
|
+
|
|
131
170
|
/** Everything a renderer needs, computed in one pass. Null when the code is unusable. */
|
|
132
171
|
export interface FlagView {
|
|
133
|
-
/** The
|
|
172
|
+
/** The file name, e.g. `es`, `gb-eng`. */
|
|
134
173
|
code: string;
|
|
135
174
|
src: string;
|
|
136
175
|
shape: FlagShape;
|
|
137
176
|
format: FlagFormat;
|
|
138
177
|
width: number;
|
|
139
178
|
height: number;
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
function cdnUrl(code: string, shape: FlagShape): string {
|
|
143
|
-
if (shape === "circle") return `https://cdn.jsdelivr.net/gh/HatScripts/circle-flags@${CIRCLE_FLAGS_REF}/flags/${code}.svg`;
|
|
144
|
-
const size = shape === "square" ? "1x1" : "4x3";
|
|
145
|
-
return `https://cdn.jsdelivr.net/npm/flag-icons@${FLAG_ICONS_VERSION}/flags/${size}/${code}.svg`;
|
|
179
|
+
/** The automatic name, when there is one. */
|
|
180
|
+
name: string | null;
|
|
146
181
|
}
|
|
147
182
|
|
|
148
183
|
/** One warning per process, not one per flag: a list of 200 would print 200 times. */
|
|
@@ -151,32 +186,32 @@ let warnedAboutFormat = false;
|
|
|
151
186
|
/**
|
|
152
187
|
* The URL for one flag. Null when the code cannot be resolved.
|
|
153
188
|
*
|
|
154
|
-
* A raster format asked of the CDN falls back to SVG rather than producing a 404:
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* with what the call site asked for.
|
|
189
|
+
* A raster format asked of the CDN falls back to SVG rather than producing a 404: the sets
|
|
190
|
+
* are stored as SVG, so `format: "webp"` is only meaningful against a local set or a mirror.
|
|
191
|
+
* It says so once, in development, instead of silently disagreeing with the call site.
|
|
158
192
|
*/
|
|
159
193
|
export function flagSrc(code: string | null | undefined, options: FlagOptions = {}): string | null {
|
|
160
194
|
const settings = { ...config, ...options };
|
|
161
195
|
const normalized = normalizeFlagCode(code);
|
|
162
196
|
if (!normalized) return null;
|
|
163
197
|
|
|
164
|
-
const
|
|
198
|
+
const remote = settings.source === "cdn";
|
|
165
199
|
let format = settings.format;
|
|
166
|
-
if (
|
|
200
|
+
if (remote && format !== "svg") {
|
|
167
201
|
if (!warnedAboutFormat && typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
|
|
168
202
|
warnedAboutFormat = true;
|
|
169
|
-
console.warn(`[enigma/flags] The
|
|
203
|
+
console.warn(`[enigma/flags] The flags are served as SVG, so "${format}" was served as SVG. Download a local set for raster formats: enigma add flags --flags local --flag-formats ${format}`);
|
|
170
204
|
}
|
|
171
205
|
format = "svg";
|
|
172
206
|
}
|
|
173
|
-
if (remoteCdn) return cdnUrl(normalized, settings.shape);
|
|
174
207
|
|
|
175
|
-
|
|
208
|
+
// One layout everywhere - `<base>/<shape>/<code>.<format>` - so a mirror is the same
|
|
209
|
+
// string with a different host, and the downloader writes exactly what this reads.
|
|
210
|
+
const base = remote ? FLAG_CDN : (settings.source === "local" ? settings.basePath : settings.source).replace(/\/+$/, "");
|
|
176
211
|
return `${base}/${settings.shape}/${normalized}.${format}`;
|
|
177
212
|
}
|
|
178
213
|
|
|
179
|
-
/** `flagSrc` plus the resolved geometry, for a renderer that has to size the box. */
|
|
214
|
+
/** `flagSrc` plus the resolved geometry and name, for a renderer that has to size the box. */
|
|
180
215
|
export function flagView(code: string | null | undefined, options: FlagOptions = {}): FlagView | null {
|
|
181
216
|
const settings = { ...config, ...options };
|
|
182
217
|
const normalized = normalizeFlagCode(code);
|
|
@@ -189,25 +224,36 @@ export function flagView(code: string | null | undefined, options: FlagOptions =
|
|
|
189
224
|
shape: settings.shape,
|
|
190
225
|
format: settings.source === "cdn" ? "svg" : settings.format,
|
|
191
226
|
width: Math.round(height * FLAG_RATIO[settings.shape]),
|
|
192
|
-
height
|
|
227
|
+
height,
|
|
228
|
+
name: flagName(normalized, settings.locale)
|
|
193
229
|
};
|
|
194
230
|
}
|
|
195
231
|
|
|
232
|
+
export interface FlagAttributeOptions extends FlagOptions {
|
|
233
|
+
/** Your own accessible name, which wins over the automatic one. */
|
|
234
|
+
label?: string;
|
|
235
|
+
/**
|
|
236
|
+
* Beside a country name that is already on screen, the flag is decoration and repeating
|
|
237
|
+
* the name only makes a screen reader say it twice.
|
|
238
|
+
*/
|
|
239
|
+
decorative?: boolean;
|
|
240
|
+
}
|
|
241
|
+
|
|
196
242
|
/**
|
|
197
243
|
* Attributes for a plain `<img>`, for vanilla, Astro and any template language.
|
|
198
244
|
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
245
|
+
* The accessible name is AUTOMATIC: the country's own name, in the reader's language, from
|
|
246
|
+
* `Intl.DisplayNames`. `label` replaces it and `decorative` drops it. Nothing is ever
|
|
247
|
+
* invented - a subdivision the platform cannot name, or a runtime without `Intl`, renders
|
|
248
|
+
* as decoration rather than putting a bare "ES" into a screen reader.
|
|
203
249
|
*/
|
|
204
|
-
export function flagAttributes(code: string | null | undefined, options:
|
|
250
|
+
export function flagAttributes(code: string | null | undefined, options: FlagAttributeOptions = {}): Record<string, string | number> | null {
|
|
205
251
|
const view = flagView(code, options);
|
|
206
252
|
if (!view) return null;
|
|
207
|
-
const label = options.label?.trim();
|
|
253
|
+
const label = options.decorative ? "" : options.label?.trim() || view.name || "";
|
|
208
254
|
return {
|
|
209
255
|
src: view.src,
|
|
210
|
-
alt: label
|
|
256
|
+
alt: label,
|
|
211
257
|
width: view.width,
|
|
212
258
|
height: view.height,
|
|
213
259
|
loading: "lazy",
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The icon shapes both renderers draw.
|
|
3
|
+
*
|
|
4
|
+
* Their own module because the React field imports NOTHING else from the vanilla adapter:
|
|
5
|
+
* pulling `createInput` in for two arrays of path data put the whole imperative renderer
|
|
6
|
+
* into every React bundle, for a component that never calls it.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The built-in glyphs, as path data.
|
|
11
|
+
*
|
|
12
|
+
* Path data rather than markup because there are two renderers: this file writes an SVG
|
|
13
|
+
* string into a button it created, and the React component builds elements. Keeping the
|
|
14
|
+
* shapes here means one definition, and a theme that replaces an icon replaces it in both.
|
|
15
|
+
* Everything is stroked with `currentColor` at 1em, so an icon inherits the field's text.
|
|
16
|
+
*/
|
|
17
|
+
export const INPUT_ICON_PATHS = {
|
|
18
|
+
eye: ["M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z", "M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"],
|
|
19
|
+
eyeOff: [
|
|
20
|
+
"M10.6 5.2A9.8 9.8 0 0 1 12 5c6.5 0 10 7 10 7a17.6 17.6 0 0 1-3.2 4.2M6.2 6.2A17.7 17.7 0 0 0 2 12s3.5 7 10 7a9.6 9.6 0 0 0 4.2-.9",
|
|
21
|
+
"m2 2 20 20",
|
|
22
|
+
"M9.9 9.9a3 3 0 0 0 4.2 4.2"
|
|
23
|
+
],
|
|
24
|
+
generate: ["m12 3 1.9 4.6L18.5 9.5 13.9 11.4 12 16l-1.9-4.6L5.5 9.5l4.6-1.9Z", "M19 15l.8 2.2 2.2.8-2.2.8-.8 2.2-.8-2.2-2.2-.8 2.2-.8Z"],
|
|
25
|
+
/** Clears a search field. The platform draws one only in WebKit, and not on mobile. */
|
|
26
|
+
clear: ["m6 6 12 12", "m18 6-12 12"],
|
|
27
|
+
search: ["M11 4a7 7 0 1 0 0 14 7 7 0 0 0 0-14Z", "m20 20-4.2-4.2"]
|
|
28
|
+
} as const;
|
|
29
|
+
|
|
30
|
+
/** The same shapes as a standalone SVG string, for the DOM renderer below. */
|
|
31
|
+
export function iconMarkup(paths: readonly string[]): string {
|
|
32
|
+
const body = paths.map((path) => `<path d="${path}"/>`).join("");
|
|
33
|
+
return `<svg viewBox="0 0 24 24" width="1em" height="1em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${body}</svg>`;
|
|
34
|
+
}
|
package/src/core/input.ts
CHANGED
|
@@ -73,28 +73,12 @@ export interface InputInstance {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
|
-
* The
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* string into a button it created, and the React component builds elements. Keeping the
|
|
80
|
-
* shapes here means one definition, and a theme that replaces an icon replaces it in both.
|
|
81
|
-
* Everything is stroked with `currentColor` at 1em, so an icon inherits the field's text.
|
|
76
|
+
* The icons live in their own module so the React field can take them without taking this
|
|
77
|
+
* whole adapter with it. Re-exported here because they were part of this module's API
|
|
78
|
+
* before the split, and moving an export is a breaking change for no gain.
|
|
82
79
|
*/
|
|
83
|
-
export
|
|
84
|
-
|
|
85
|
-
eyeOff: [
|
|
86
|
-
"M10.6 5.2A9.8 9.8 0 0 1 12 5c6.5 0 10 7 10 7a17.6 17.6 0 0 1-3.2 4.2M6.2 6.2A17.7 17.7 0 0 0 2 12s3.5 7 10 7a9.6 9.6 0 0 0 4.2-.9",
|
|
87
|
-
"m2 2 20 20",
|
|
88
|
-
"M9.9 9.9a3 3 0 0 0 4.2 4.2"
|
|
89
|
-
],
|
|
90
|
-
generate: ["m12 3 1.9 4.6L18.5 9.5 13.9 11.4 12 16l-1.9-4.6L5.5 9.5l4.6-1.9Z", "M19 15l.8 2.2 2.2.8-2.2.8-.8 2.2-.8-2.2-2.2-.8 2.2-.8Z"]
|
|
91
|
-
} as const;
|
|
92
|
-
|
|
93
|
-
/** The same shapes as a standalone SVG string, for the DOM renderer below. */
|
|
94
|
-
export function iconMarkup(paths: readonly string[]): string {
|
|
95
|
-
const body = paths.map((path) => `<path d="${path}"/>`).join("");
|
|
96
|
-
return `<svg viewBox="0 0 24 24" width="1em" height="1em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${body}</svg>`;
|
|
97
|
-
}
|
|
80
|
+
export { INPUT_ICON_PATHS, iconMarkup } from "@/core/input-icons";
|
|
81
|
+
import { INPUT_ICON_PATHS, iconMarkup } from "@/core/input-icons";
|
|
98
82
|
|
|
99
83
|
const EYE = iconMarkup(INPUT_ICON_PATHS.eye);
|
|
100
84
|
const EYE_OFF = iconMarkup(INPUT_ICON_PATHS.eyeOff);
|
|
Binary file
|