@dnax/ui 0.0.72 → 0.0.75

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/lib/iconSet.ts ADDED
@@ -0,0 +1,91 @@
1
+ // ⚠️ FICHIER GÉNÉRÉ — ne pas éditer à la main.
2
+ // Commande : bun scripts/gen-icon-set.ts --out ./lib/iconSet.ts --src ./components --src ./lib
3
+ //
4
+ // Sous-ensemble de la collection `lucide` : 68 icônes + 4 alias, relevées dans
5
+ // `./components`, `./lib`. Embarqué dans le bundle, il est enregistré auprès d'Iconify par
6
+ // `registerIcons()` (ou par le module Nuxt `@dnax/ui`) via `addCollection()` :
7
+ // ces icônes ne déclenchent **aucune** requête vers l'API Iconify et sont rendues
8
+ // dès le SSR. Les noms absents de ce fichier continuent de passer par l'API.
9
+ import type { IconSetJson } from "./icons"
10
+
11
+ export const lucideIconSet: IconSetJson = {
12
+ prefix: "lucide",
13
+ width: 24,
14
+ height: 24,
15
+ icons: {
16
+ "align-left": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 12H3m14 6H3M21 6H3\"/>" },
17
+ "arrow-down": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 5v14m7-7l-7 7l-7-7\"/>" },
18
+ "arrow-down-a-z": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m3 16l4 4l4-4m-4 4V4m13 4h-5m0 2V6.5a2.5 2.5 0 0 1 5 0V10m-5 4h5l-5 6h5\"/>" },
19
+ "arrow-down-wide-narrow": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m3 16l4 4l4-4m-4 4V4m4 0h10M11 8h7m-7 4h4\"/>" },
20
+ "arrow-up": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m5 12l7-7l7 7m-7 7V5\"/>" },
21
+ "arrow-up-a-z": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m3 8l4-4l4 4M7 4v16M20 8h-5m0 2V6.5a2.5 2.5 0 0 1 5 0V10m-5 4h5l-5 6h5\"/>" },
22
+ "arrow-up-down": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m21 16l-4 4l-4-4m4 4V4M3 8l4-4l4 4M7 4v16\"/>" },
23
+ "arrow-up-narrow-wide": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m3 8l4-4l4 4M7 4v16m4-8h4m-4 4h7m-7 4h10\"/>" },
24
+ "calendar-days": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M8 2v3m8-3v3\"/><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M3 9h18M8 13h.01M12 13h.01M16 13h.01M8 17h.01M12 17h.01M16 17h.01\"/></g>" },
25
+ "check": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M20 6L9 17l-5-5\"/>" },
26
+ "chevron-down": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m6 9l6 6l6-6\"/>" },
27
+ "chevron-left": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m15 18l-6-6l6-6\"/>" },
28
+ "chevron-right": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m9 18l6-6l-6-6\"/>" },
29
+ "chevron-up": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m18 15l-6-6l-6 6\"/>" },
30
+ "chevrons-left": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m11 17l-5-5l5-5m7 10l-5-5l5-5\"/>" },
31
+ "chevrons-right": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m6 17l5-5l-5-5m7 10l5-5l-5-5\"/>" },
32
+ "clipboard-paste": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M11 14h10M16 4h2a2 2 0 0 1 2 2v1.344M17 18l4-4l-4-4\"/><path d=\"M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 1.793-1.113\"/><rect width=\"8\" height=\"4\" x=\"8\" y=\"2\" rx=\"1\"/></g>" },
33
+ "clock": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 6v6l4 2\"/></g>" },
34
+ "columns-3": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M9 3v18m6-18v18\"/></g>" },
35
+ "copy": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></g>" },
36
+ "delete": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M10 5a2 2 0 0 0-1.344.519l-6.328 5.74a1 1 0 0 0 0 1.481l6.328 5.741A2 2 0 0 0 10 19h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2zm2 4l6 6m0-6l-6 6\"/>" },
37
+ "download": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M12 15V3m9 12v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><path d=\"m7 10l5 5l5-5\"/></g>" },
38
+ "ellipsis": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"1\"/><circle cx=\"19\" cy=\"12\" r=\"1\"/><circle cx=\"5\" cy=\"12\" r=\"1\"/></g>" },
39
+ "ellipsis-vertical": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"1\"/><circle cx=\"12\" cy=\"5\" r=\"1\"/><circle cx=\"12\" cy=\"19\" r=\"1\"/></g>" },
40
+ "eraser": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 21H8a2 2 0 0 1-1.42-.587l-3.994-3.999a2 2 0 0 1 0-2.828l10-10a2 2 0 0 1 2.829 0l5.999 6a2 2 0 0 1 0 2.828L12.834 21m-7.752-9.91l8.828 8.828\"/>" },
41
+ "eye": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M2.062 12.348a1 1 0 0 1 0-.696a10.75 10.75 0 0 1 19.876 0a1 1 0 0 1 0 .696a10.75 10.75 0 0 1-19.876 0\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></g>" },
42
+ "eye-off": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575a1 1 0 0 1 0 .696a10.8 10.8 0 0 1-1.444 2.49m-6.41-.679a3 3 0 0 1-4.242-4.242\"/><path d=\"M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151a1 1 0 0 1 0-.696a10.75 10.75 0 0 1 4.446-5.143M2 2l20 20\"/></g>" },
43
+ "file": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z\"/><path d=\"M14 2v5a1 1 0 0 0 1 1h5\"/></g>" },
44
+ "file-archive": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M13.659 22H18a2 2 0 0 0 2-2V8a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 14 2H6a2 2 0 0 0-2 2v11.5\"/><path d=\"M14 2v5a1 1 0 0 0 1 1h5M8 12v-1m0 7v-2m0-9V6\"/><circle cx=\"8\" cy=\"20\" r=\"2\"/></g>" },
45
+ "file-audio": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M17.5 22h.5a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v3\"/><path d=\"M14 2v4a2 2 0 0 0 2 2h4M2 19a2 2 0 1 1 4 0v1a2 2 0 1 1-4 0v-4a6 6 0 0 1 12 0v4a2 2 0 1 1-4 0v-1a2 2 0 1 1 4 0\"/></g>" },
46
+ "file-code": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z\"/><path d=\"M14 2v5a1 1 0 0 0 1 1h5m-10 4.5L8 15l2 2.5m4-5l2 2.5l-2 2.5\"/></g>" },
47
+ "file-image": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z\"/><path d=\"M14 2v5a1 1 0 0 0 1 1h5\"/><circle cx=\"10\" cy=\"12\" r=\"2\"/><path d=\"m20 17l-1.296-1.296a2.41 2.41 0 0 0-3.408 0L9 22\"/></g>" },
48
+ "file-play": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z\"/><path d=\"M14 2v5a1 1 0 0 0 1 1h5m-4.967 5.44a.647.647 0 0 1 0 1.12l-4.065 2.352a.645.645 0 0 1-.968-.56v-4.704a.645.645 0 0 1 .967-.56z\"/></g>" },
49
+ "file-plus": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z\"/><path d=\"M14 2v5a1 1 0 0 0 1 1h5M9 15h6m-3 3v-6\"/></g>" },
50
+ "file-spreadsheet": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z\"/><path d=\"M14 2v5a1 1 0 0 0 1 1h5M8 13h2m4 0h2m-8 4h2m4 0h2\"/></g>" },
51
+ "file-text": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z\"/><path d=\"M14 2v5a1 1 0 0 0 1 1h5M10 9H8m8 4H8m8 4H8\"/></g>" },
52
+ "filter": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M22 3H2l8 9.46V19l4 2v-8.54z\"/>" },
53
+ "globe": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 2a14.5 14.5 0 0 0 0 20a14.5 14.5 0 0 0 0-20M2 12h20\"/></g>" },
54
+ "grip-vertical": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><circle cx=\"9\" cy=\"12\" r=\"1\"/><circle cx=\"9\" cy=\"5\" r=\"1\"/><circle cx=\"9\" cy=\"19\" r=\"1\"/><circle cx=\"15\" cy=\"12\" r=\"1\"/><circle cx=\"15\" cy=\"5\" r=\"1\"/><circle cx=\"15\" cy=\"19\" r=\"1\"/></g>" },
55
+ "highlighter": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"m9 11l-6 6v3h9l3-3\"/><path d=\"m22 12l-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4\"/></g>" },
56
+ "house": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8\"/><path d=\"M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z\"/></g>" },
57
+ "image": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\"/><circle cx=\"9\" cy=\"9\" r=\"2\"/><path d=\"m21 15l-3.086-3.086a2 2 0 0 0-2.828 0L6 21\"/></g>" },
58
+ "image-plus": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M16 5h6m-3-3v6m2 3.5V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7.5\"/><path d=\"m21 15l-3.086-3.086a2 2 0 0 0-2.828 0L6 21\"/><circle cx=\"9\" cy=\"9\" r=\"2\"/></g>" },
59
+ "info": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4m0-4h.01\"/></g>" },
60
+ "italic": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 4h-9m4 16H5M15 4L9 20\"/>" },
61
+ "letter-b": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8\"/>" },
62
+ "loader-circle": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 12a9 9 0 1 1-6.219-8.56\"/>" },
63
+ "menu": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 5h16M4 12h16M4 19h16\"/>" },
64
+ "minus": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M5 12h14\"/>" },
65
+ "paperclip": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m16 6l-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551\"/>" },
66
+ "pencil": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497zM15 5l4 4\"/>" },
67
+ "plus": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M5 12h14m-7-7v14\"/>" },
68
+ "redo-2": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"m15 14l5-5l-5-5\"/><path d=\"M20 9H9.5A5.5 5.5 0 0 0 4 14.5A5.5 5.5 0 0 0 9.5 20H13\"/></g>" },
69
+ "refresh-cw": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M3 12a9 9 0 0 1 9-9a9.75 9.75 0 0 1 6.74 2.74L21 8\"/><path d=\"M21 3v5h-5m5 4a9 9 0 0 1-9 9a9.75 9.75 0 0 1-6.74-2.74L3 16\"/><path d=\"M8 16H3v5\"/></g>" },
70
+ "rows-3": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M21 9H3m18 6H3\"/></g>" },
71
+ "scissors": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><circle cx=\"6\" cy=\"6\" r=\"3\"/><path d=\"M8.12 8.12L12 12m8-8L8.12 15.88\"/><circle cx=\"6\" cy=\"18\" r=\"3\"/><path d=\"M14.8 14.8L20 20\"/></g>" },
72
+ "search": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"m21 21l-4.34-4.34\"/><circle cx=\"11\" cy=\"11\" r=\"8\"/></g>" },
73
+ "send": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11zm7.318-19.539l-10.94 10.939\"/>" },
74
+ "settings": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M9.671 4.136a2.34 2.34 0 0 1 4.659 0a2.34 2.34 0 0 0 3.319 1.915a2.34 2.34 0 0 1 2.33 4.033a2.34 2.34 0 0 0 0 3.831a2.34 2.34 0 0 1-2.33 4.033a2.34 2.34 0 0 0-3.319 1.915a2.34 2.34 0 0 1-4.659 0a2.34 2.34 0 0 0-3.32-1.915a2.34 2.34 0 0 1-2.33-4.033a2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></g>" },
75
+ "share": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 2v13m4-9l-4-4l-4 4m-4 6v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8\"/>" },
76
+ "square-minus": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M8 12h8\"/></g>" },
77
+ "square-plus": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M8 12h8m-4-4v8\"/></g>" },
78
+ "star": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.12 2.12 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.12 2.12 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.12 2.12 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.12 2.12 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.12 2.12 0 0 0 1.597-1.16z\"/>" },
79
+ "table-cells-merge": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M12 21v-6m0-6V3M3 15h18M3 9h18\"/><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/></g>" },
80
+ "trash": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M10 11v6m4-6v6m5-11v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/>" },
81
+ "undo-2": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M9 14L4 9l5-5\"/><path d=\"M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11\"/></g>" },
82
+ "user": { body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/></g>" },
83
+ "x": { body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M18 6L6 18M6 6l12 12\"/>" },
84
+ },
85
+ aliases: {
86
+ "bold": { parent: "letter-b" },
87
+ "file-video": { parent: "file-play" },
88
+ "home": { parent: "house" },
89
+ "trash-2": { parent: "trash" },
90
+ },
91
+ }
@@ -0,0 +1,139 @@
1
+ // Extraction d'icônes pour l'embarquement — **Node uniquement (build)**.
2
+ //
3
+ // Utilisé par :
4
+ // - `module.ts` (module Nuxt) : scanne les sources de l'app hôte au build ;
5
+ // - `scripts/gen-icon-set.ts` : produit `lib/iconSet.ts` (sous-ensemble figé) ;
6
+ // - `lib/iconSet.test.ts` : garde-fou contre la dérive du fichier généré.
7
+ //
8
+ // ⚠️ Ne pas réexporter depuis `index.ts` : ce fichier importe `node:fs`, il n'a rien à
9
+ // faire dans un bundle client.
10
+ import { readdirSync, readFileSync } from "node:fs"
11
+ import { join } from "node:path"
12
+ import type { IconSetJson } from "./icons"
13
+
14
+ /** `lucide:arrow-down`, `lucide:table-cells-merge`… */
15
+ export const ICON_NAME_RE = /\blucide:[a-z0-9]+(?:-[a-z0-9]+)*/g
16
+
17
+ const PREFIX = "lucide:"
18
+
19
+ const TEXT_FILE_RE = /\.(vue|ts|js|mjs|md|mdx)$/
20
+
21
+ /** Dossiers techniques jamais scannés (le reste de l'arbre est parcouru). */
22
+ export const SKIP_DIRS = new Set([
23
+ "node_modules",
24
+ ".nuxt",
25
+ ".output",
26
+ ".data",
27
+ ".git",
28
+ "dist",
29
+ "coverage",
30
+ "public",
31
+ ])
32
+
33
+ /** Noms d'icônes cités dans un texte, **sans** le préfixe (`arrow-down`). */
34
+ export const iconNamesIn = (text: string): string[] =>
35
+ [...text.matchAll(ICON_NAME_RE)].map((match) => match[0].slice(PREFIX.length))
36
+
37
+ /** Sous-ensemble de `IconifyJSON` (collection officielle) dont on a besoin. */
38
+ export interface IconifySourceCollection {
39
+ prefix: string
40
+ width?: number
41
+ height?: number
42
+ icons: Record<string, { body: string; [key: string]: unknown }>
43
+ aliases?: Record<string, { parent: string; [key: string]: unknown }>
44
+ }
45
+
46
+ export interface ScrapeOptions {
47
+ /** Dossiers supplémentaires à ignorer (noms de base, pas des chemins) */
48
+ skipDirs?: Iterable<string>
49
+ }
50
+
51
+ /** Fichiers texte (`.vue`, `.ts`, `.js`, `.mjs`, `.md`…) sous `dir`, récursivement. */
52
+ export const textFilesUnder = (dir: string, options: ScrapeOptions = {}): string[] => {
53
+ const skip = new Set([...SKIP_DIRS, ...(options.skipDirs ?? [])])
54
+ const found: string[] = []
55
+
56
+ const walk = (current: string): void => {
57
+ let entries
58
+ try {
59
+ entries = readdirSync(current, { withFileTypes: true })
60
+ } catch {
61
+ return // dossier absent ou illisible : on ignore
62
+ }
63
+ for (const entry of entries) {
64
+ const path = join(current, entry.name)
65
+ if (entry.isDirectory()) {
66
+ if (!skip.has(entry.name)) walk(path)
67
+ continue
68
+ }
69
+ // Les tests citent des noms d'icônes pour vérifier : rien à embarquer.
70
+ if (!TEXT_FILE_RE.test(entry.name) || entry.name.endsWith(".test.ts")) continue
71
+ found.push(path)
72
+ }
73
+ }
74
+
75
+ walk(dir)
76
+ return found
77
+ }
78
+
79
+ /** Noms d'icônes relevés dans un ensemble de dossiers (dédupliqués, triés). */
80
+ export const scrapeIconNames = (dirs: string[], options: ScrapeOptions = {}): string[] => {
81
+ const names = new Set<string>()
82
+ for (const dir of dirs) {
83
+ for (const file of textFilesUnder(dir, options)) {
84
+ for (const name of iconNamesIn(readFileSync(file, "utf8"))) names.add(name)
85
+ }
86
+ }
87
+ return [...names].sort()
88
+ }
89
+
90
+ /** Construit une collection (alias résolus, parente incluse) à partir de noms nus. */
91
+ export const buildIconSet = (
92
+ source: IconifySourceCollection,
93
+ names: Iterable<string>,
94
+ ): { set: IconSetJson; missing: string[] } => {
95
+ const icons: IconSetJson["icons"] = {}
96
+ const aliases: NonNullable<IconSetJson["aliases"]> = {}
97
+ const missing: string[] = []
98
+
99
+ const add = (name: string): void => {
100
+ if (name in icons || name in aliases) return
101
+ const icon = source.icons[name]
102
+ if (icon) {
103
+ icons[name] = { body: icon.body }
104
+ return
105
+ }
106
+ const alias = source.aliases?.[name]
107
+ if (alias) {
108
+ aliases[name] = { parent: alias.parent }
109
+ // Un alias ne rend rien sans sa parente : on l'embarque aussi.
110
+ add(alias.parent)
111
+ return
112
+ }
113
+ missing.push(name)
114
+ }
115
+
116
+ for (const name of [...names].sort()) add(name)
117
+
118
+ const set: IconSetJson = {
119
+ prefix: source.prefix,
120
+ width: source.width ?? 24,
121
+ height: source.height ?? 24,
122
+ icons,
123
+ }
124
+ if (Object.keys(aliases).length) set.aliases = aliases
125
+ return { set, missing }
126
+ }
127
+
128
+ /** Fusionne deux collections (les entrées de `extra` complètent `base`). */
129
+ export const mergeIconSets = (base: IconSetJson, extra: IconSetJson): IconSetJson => {
130
+ const aliases = { ...base.aliases, ...extra.aliases }
131
+ const merged: IconSetJson = {
132
+ prefix: base.prefix,
133
+ width: base.width ?? extra.width,
134
+ height: base.height ?? extra.height,
135
+ icons: { ...base.icons, ...extra.icons },
136
+ }
137
+ if (Object.keys(aliases).length) merged.aliases = aliases
138
+ return merged
139
+ }
package/lib/icons.ts CHANGED
@@ -64,3 +64,19 @@ export const icons = {
64
64
  highlighter: "lucide:highlighter",
65
65
  tableCellsMerge: "lucide:table-cells-merge",
66
66
  } as const
67
+
68
+ /**
69
+ * Collection Iconify minimale : le sous-ensemble d'une collection officielle
70
+ * (`lucide` ici) que l'on peut **embarquer dans le bundle** pour éviter l'appel
71
+ * réseau de `@iconify/vue`. Voir `lib/iconSet.ts` (généré) et `lib/registerIcons.ts`.
72
+ */
73
+ export interface IconSetJson {
74
+ /** Préfixe de collection, sans `:` — ex. `"lucide"` */
75
+ prefix: string
76
+ width?: number
77
+ height?: number
78
+ /** Icônes : nom (sans préfixe) → corps SVG */
79
+ icons: Record<string, { body: string }>
80
+ /** Alias (lucide en a quelques-uns : `activity-square` → `square-activity`) */
81
+ aliases?: Record<string, { parent: string }>
82
+ }
@@ -0,0 +1,37 @@
1
+ // registerIcons — embarque les icônes du design system dans le bundle.
2
+ //
3
+ // Par défaut, `@iconify/vue` va chercher chaque icône **à la demande** sur
4
+ // `api.iconify.design`. En SSR cela ne rend que la coquille
5
+ // `<svg class="iconify iconify--lucide">` (les `<path>` arrivent après le fetch
6
+ // client) et, sans réseau, **aucune** icône n'apparaît. Enregistrer une collection
7
+ // locale (`addCollection`) supprime la requête pour toutes les icônes de
8
+ // `lib/iconSet.ts` (fichier généré par `scripts/gen-icon-set.ts`).
9
+ //
10
+ // - Appelé automatiquement par le module Nuxt `@dnax/ui` (option `icons`, défaut `true`).
11
+ // - À appeler soi-même quand le module n'est pas utilisé (Vite/Nuxt manuel).
12
+ // - Idempotent, et `addCollection` **fusionne** avec une collection déjà enregistrée
13
+ // sous le même préfixe : plusieurs appels (design system + extras d'une app) sont
14
+ // sans danger.
15
+ import { addCollection } from "@iconify/vue"
16
+ import { lucideIconSet } from "./iconSet"
17
+ import type { IconSetJson } from "./icons"
18
+
19
+ let registered = false
20
+
21
+ /** Enregistre les icônes du design system auprès d'Iconify (idempotent). */
22
+ export const registerIcons = (): void => {
23
+ if (registered) return
24
+ registered = true
25
+ addCollection(lucideIconSet)
26
+ }
27
+
28
+ /** `true` si `registerIcons()` a déjà été appelé. */
29
+ export const iconsRegistered = (): boolean => registered
30
+
31
+ /**
32
+ * Enregistre une collection Iconify **supplémentaire** (icônes propres à une app, à un
33
+ * site de docs…). Fusionne avec la collection déjà chargée — même préfixe compris.
34
+ */
35
+ export const registerIconSet = (set: IconSetJson): void => {
36
+ addCollection(set)
37
+ }
@@ -0,0 +1,80 @@
1
+ // Auto-extension d'un textarea : le plafond `maxRows` doit être **respecté au pixel**
2
+ // (hauteur = lignes × line-height + paddings) et le défilement ne s'activer que
3
+ // lorsque le contenu dépasse — sinon un champ collé à son plafond clignote.
4
+ import { describe, expect, it } from "bun:test"
5
+ import {
6
+ DEFAULT_LINE_HEIGHT_RATIO,
7
+ textareaCap,
8
+ textareaMetrics,
9
+ textareaSizing,
10
+ } from "./textarea"
11
+
12
+ const metrics = { fontSize: 14, lineHeight: 20, paddingTop: 0, paddingBottom: 0 }
13
+
14
+ describe("textareaMetrics", () => {
15
+ it("résout des valeurs px", () => {
16
+ expect(
17
+ textareaMetrics({
18
+ fontSize: "14px",
19
+ lineHeight: "20px",
20
+ paddingTop: "8px",
21
+ paddingBottom: "12px",
22
+ }),
23
+ ).toEqual({ fontSize: 14, lineHeight: 20, paddingTop: 8, paddingBottom: 12 })
24
+ })
25
+
26
+ it("replie `line-height: normal` sur fontSize × 1.4", () => {
27
+ const m = textareaMetrics({ fontSize: "16px", lineHeight: "normal" })
28
+ expect(m.lineHeight).toBeCloseTo(16 * DEFAULT_LINE_HEIGHT_RATIO)
29
+ })
30
+
31
+ it("replie les valeurs absentes ou invalides", () => {
32
+ const m = textareaMetrics({})
33
+ expect(m).toEqual({ fontSize: 14, lineHeight: 14 * DEFAULT_LINE_HEIGHT_RATIO, paddingTop: 0, paddingBottom: 0 })
34
+ })
35
+
36
+ it("ignore les paddings négatifs ou non numériques", () => {
37
+ expect(textareaMetrics({ paddingTop: "-4px", paddingBottom: "auto" })).toEqual({
38
+ fontSize: 14,
39
+ lineHeight: 14 * DEFAULT_LINE_HEIGHT_RATIO,
40
+ paddingTop: 0,
41
+ paddingBottom: 0,
42
+ })
43
+ })
44
+ })
45
+
46
+ describe("textareaCap", () => {
47
+ it("vaut lignes × line-height + paddings", () => {
48
+ expect(textareaCap({ ...metrics, paddingTop: 10, paddingBottom: 6 }, 3)).toBe(76)
49
+ })
50
+
51
+ it("vaut Infinity sans plafond", () => {
52
+ expect(textareaCap(metrics, 0)).toBe(Number.POSITIVE_INFINITY)
53
+ expect(textareaCap(metrics, -2)).toBe(Number.POSITIVE_INFINITY)
54
+ })
55
+ })
56
+
57
+ describe("textareaSizing", () => {
58
+ it("prend la hauteur du contenu quand il tient sous le plafond", () => {
59
+ expect(textareaSizing(metrics, 40, 5)).toEqual({ height: 40, overflowY: "hidden", capped: false })
60
+ })
61
+
62
+ it("plafonne et active le défilement au-delà de maxRows", () => {
63
+ expect(textareaSizing(metrics, 300, 5)).toEqual({ height: 100, overflowY: "auto", capped: true })
64
+ })
65
+
66
+ it("ne plafonne pas quand maxRows vaut 0", () => {
67
+ expect(textareaSizing(metrics, 300, 0)).toEqual({ height: 300, overflowY: "hidden", capped: false })
68
+ })
69
+
70
+ it("défile dès que le contenu dépasse, sans marge", () => {
71
+ // 100 px pile = plafond : pas encore de défilement (évite le clignotement)
72
+ expect(textareaSizing(metrics, 100, 5).overflowY).toBe("hidden")
73
+ expect(textareaSizing(metrics, 101, 5).overflowY).toBe("auto")
74
+ })
75
+
76
+ it("normalise un scrollHeight invalide ou négatif", () => {
77
+ expect(textareaSizing(metrics, -10, 5).height).toBe(0)
78
+ expect(textareaSizing(metrics, Number.NaN, 5).height).toBe(0)
79
+ })
80
+ })
@@ -0,0 +1,68 @@
1
+ // Auto-extension d'un textarea — calculs **purs** (aucun DOM).
2
+ //
3
+ // Le composant mesure (`getComputedStyle` + `scrollHeight`) puis délègue ici la
4
+ // décision de hauteur : plafond de `maxRows` lignes, défilement au-delà. Tester ces
5
+ // règles sans navigateur évite de dépendre d'une vraie mise en page.
6
+
7
+ /** Métriques d'un textarea, en px (résolues depuis les styles calculés). */
8
+ export interface TextareaMetrics {
9
+ fontSize: number
10
+ lineHeight: number
11
+ paddingTop: number
12
+ paddingBottom: number
13
+ }
14
+
15
+ /** Sous-ensemble de `CSSStyleDeclaration` utile (valeurs CSS en texte). */
16
+ export interface TextareaComputedStyle {
17
+ fontSize?: string
18
+ lineHeight?: string
19
+ paddingTop?: string
20
+ paddingBottom?: string
21
+ }
22
+
23
+ /** Ratio de repli quand `line-height: normal` n'expose aucune valeur exploitable. */
24
+ export const DEFAULT_LINE_HEIGHT_RATIO = 1.4
25
+
26
+ const toPx = (raw: string | undefined, fallback: number): number => {
27
+ const n = Number.parseFloat(String(raw ?? ""))
28
+ return Number.isFinite(n) && n > 0 ? n : fallback
29
+ }
30
+
31
+ /** Résout des styles calculés en métriques px (`normal` → `fontSize × 1.4`). */
32
+ export const textareaMetrics = (style: TextareaComputedStyle): TextareaMetrics => {
33
+ const fontSize = toPx(style.fontSize, 14)
34
+ const lineHeight = toPx(style.lineHeight, fontSize * DEFAULT_LINE_HEIGHT_RATIO)
35
+ return {
36
+ fontSize,
37
+ lineHeight,
38
+ paddingTop: toPx(style.paddingTop, 0),
39
+ paddingBottom: toPx(style.paddingBottom, 0),
40
+ }
41
+ }
42
+
43
+ /** Hauteur maximale (px) de `maxRows` lignes — `Infinity` si `maxRows <= 0`. */
44
+ export const textareaCap = (metrics: TextareaMetrics, maxRows: number): number =>
45
+ maxRows > 0
46
+ ? metrics.lineHeight * maxRows + metrics.paddingTop + metrics.paddingBottom
47
+ : Number.POSITIVE_INFINITY
48
+
49
+ export interface TextareaSizing {
50
+ /** Hauteur à appliquer, en px */
51
+ height: number
52
+ /** Débordement vertical à appliquer */
53
+ overflowY: "auto" | "hidden"
54
+ /** Le contenu dépasse le plafond `maxRows` : le champ défile */
55
+ capped: boolean
56
+ }
57
+
58
+ /** Hauteur à appliquer pour un `scrollHeight` mesuré, plafonnée à `maxRows` lignes. */
59
+ export const textareaSizing = (
60
+ metrics: TextareaMetrics,
61
+ scrollHeight: number,
62
+ maxRows = 0,
63
+ ): TextareaSizing => {
64
+ const full = Number.isFinite(scrollHeight) ? Math.max(scrollHeight, 0) : 0
65
+ const cap = textareaCap(metrics, maxRows)
66
+ const capped = full > cap
67
+ return { height: Math.min(full, cap), overflowY: capped ? "auto" : "hidden", capped }
68
+ }
@@ -1,6 +1,18 @@
1
1
  // Tests unitaires de la résolution du radius (échelle + style CSS).
2
+ //
3
+ // Ajout (2026-11-01) : `radius: true` était documenté « true = md » sur QCard / QDialog /
4
+ // QSyntax / QTable sans poser le moindre style → surface carrée, silencieusement.
5
+ // `radiusStyleNative` comble ce trou ; `radiusStyle` reste volontairement inchangé
6
+ // (`true` → undefined) pour ne pas injecter un `--q-radius` global qui fuirait aux
7
+ // enfants des champs/boutons (cf. `.memory/decisions.md`).
2
8
  import { describe, expect, it } from "bun:test"
3
- import { RADIUS_VALUES, radiusStyle, radiusValue } from "./useComponentProps"
9
+ import {
10
+ RADIUS_VALUES,
11
+ isRadiusScale,
12
+ radiusStyle,
13
+ radiusStyleNative,
14
+ radiusValue,
15
+ } from "./useComponentProps"
4
16
 
5
17
  describe("radiusValue", () => {
6
18
  it("traduit chaque barreau de l'échelle", () => {
@@ -33,4 +45,46 @@ describe("radiusStyle", () => {
33
45
  expect(radiusStyle(true)).toBeUndefined()
34
46
  expect(radiusStyle(undefined)).toBeUndefined()
35
47
  })
48
+
49
+ it("gère aussi none (carre) et l'échelle basse", () => {
50
+ expect(radiusStyle("none")).toEqual({ "--q-radius": "0px" })
51
+ expect(radiusStyle("sm")).toEqual({ "--q-radius": "4px" })
52
+ })
53
+ })
54
+
55
+ describe("isRadiusScale", () => {
56
+ it("reconnaît les échelles et rejette le reste", () => {
57
+ expect(isRadiusScale("md")).toBe(true)
58
+ expect(isRadiusScale("none")).toBe(true)
59
+ expect(isRadiusScale("12px")).toBe(false)
60
+ expect(isRadiusScale(true)).toBe(false)
61
+ expect(isRadiusScale(undefined)).toBe(false)
62
+ })
63
+ })
64
+
65
+ describe("RADIUS_VALUES", () => {
66
+ it("couvre toute l'échelle, croissante", () => {
67
+ expect(Object.keys(RADIUS_VALUES)).toEqual(["none", "xs", "sm", "md", "lg"])
68
+ expect(RADIUS_VALUES.none).toBe("0px")
69
+ expect(RADIUS_VALUES.lg).toBe("16px")
70
+ })
71
+ })
72
+
73
+ describe("radiusStyleNative (composants « true = échelle »)", () => {
74
+ it("convertit `true` en md par défaut", () => {
75
+ expect(radiusStyleNative(true)).toEqual({ "--q-radius": "8px" })
76
+ })
77
+
78
+ it("accepte une autre échelle par défaut", () => {
79
+ expect(radiusStyleNative(true, "lg")).toEqual({ "--q-radius": "16px" })
80
+ })
81
+
82
+ it("transmet une échelle telle quelle", () => {
83
+ expect(radiusStyleNative("sm")).toEqual({ "--q-radius": "4px" })
84
+ expect(radiusStyleNative("none")).toEqual({ "--q-radius": "0px" })
85
+ })
86
+
87
+ it("laisse undefined inchangé", () => {
88
+ expect(radiusStyleNative(undefined)).toBeUndefined()
89
+ })
36
90
  })
@@ -65,3 +65,18 @@ export function radiusStyle(radius: RadiusProp | undefined): Record<string, stri
65
65
  const value = radiusValue(radius)
66
66
  return value ? { "--q-radius": value } : undefined
67
67
  }
68
+
69
+ /**
70
+ * Style `--q-radius` des composants dont le booléen `radius` vaut une **échelle fixe**
71
+ * (documenté « true = md ») : `true` → `scale`.
72
+ *
73
+ * Les composants à forme native « pilule » (champs, boutons) ne passent **pas** par ici :
74
+ * ils posent leur propre classe (`.q-field--rounded`, `.q-btn--rounded`) — pour eux,
75
+ * `true` ne doit surtout pas devenir un `--q-radius` global qui fuirait aux enfants.
76
+ */
77
+ export function radiusStyleNative(
78
+ radius: RadiusProp | undefined,
79
+ scale: RadiusScale = "md",
80
+ ): Record<string, string> | undefined {
81
+ return radiusStyle(radius === true ? scale : radius)
82
+ }
@@ -0,0 +1,83 @@
1
+ // useTextareaAutosize — branchement DOM de l'auto-extension d'un <textarea>.
2
+ //
3
+ // Le calcul de hauteur est pur (`lib/textarea.ts`, testé) ; ici on ne fait que
4
+ // mesurer et gérer le cycle de vie : montage, redimensionnement de la fenêtre,
5
+ // changement de **largeur** du champ (le texte se replie → la hauteur change).
6
+ // Rien n'est mesuré si `enabled()` est faux : la hauteur reste celle du CSS (`rows`).
7
+ import { nextTick, onBeforeUnmount, onMounted, watch } from "vue"
8
+ import type { Ref } from "vue"
9
+ import { textareaMetrics, textareaSizing } from "./textarea"
10
+
11
+ export interface UseTextareaAutosizeOptions {
12
+ /** Auto-extension active (sinon la hauteur du CSS s'applique) */
13
+ enabled: () => boolean
14
+ /** Plafond en lignes, 0 = illimité */
15
+ maxRows: () => number
16
+ /** États dont le changement impose une re-mesure (valeur, rows, maxRows, mode…) */
17
+ sources?: () => unknown
18
+ /**
19
+ * Re-mesure aussi quand la **largeur** du champ change. À laisser à `false` pour
20
+ * un champ qui bascule `input`/`textarea` : l'élément observé y est remplacé.
21
+ */
22
+ observeWidth?: boolean
23
+ }
24
+
25
+ export interface TextareaAutosize {
26
+ /** Re-mesure immédiatement (après un changement programmatique de valeur) */
27
+ resize: () => Promise<void>
28
+ }
29
+
30
+ /** Élément mesuré : textarea, mais aussi l'`<input>` d'un champ à mode mixte. */
31
+ export type AutosizedElement = HTMLTextAreaElement | HTMLInputElement
32
+
33
+ export const useTextareaAutosize = <T extends AutosizedElement>(
34
+ el: Ref<T | null>,
35
+ options: UseTextareaAutosizeOptions,
36
+ ): TextareaAutosize => {
37
+ const resize = async () => {
38
+ if (!el.value || !options.enabled()) return
39
+ await nextTick()
40
+ const node = el.value
41
+ if (!node || typeof getComputedStyle !== "function") return
42
+ // Hauteur remise à `auto` avant la mesure : sinon `scrollHeight` inclut déjà
43
+ // la hauteur appliquée précédemment et le champ ne rétrécit jamais.
44
+ node.style.height = "auto"
45
+ const metrics = textareaMetrics(getComputedStyle(node))
46
+ const sizing = textareaSizing(metrics, node.scrollHeight, options.maxRows())
47
+ node.style.height = `${sizing.height}px`
48
+ node.style.overflowY = sizing.overflowY
49
+ }
50
+
51
+ if (options.sources) watch(options.sources, () => void resize())
52
+
53
+ const onWindowResize = () => void resize()
54
+
55
+ let observer: ResizeObserver | null = null
56
+ let observedWidth = 0
57
+
58
+ onMounted(() => {
59
+ void resize()
60
+ if (typeof window === "undefined") return
61
+ window.addEventListener("resize", onWindowResize)
62
+ if (options.observeWidth === false || typeof ResizeObserver === "undefined") return
63
+ const node = el.value
64
+ if (!node) return
65
+ // Seule la **largeur** nous intéresse : la hauteur est la conséquence de notre
66
+ // propre écriture de `style.height`, l'observer rebouclerait dessus.
67
+ observer = new ResizeObserver((entries) => {
68
+ const width = entries[0]?.contentRect.width ?? 0
69
+ if (Math.abs(width - observedWidth) < 0.5) return
70
+ observedWidth = width
71
+ void resize()
72
+ })
73
+ observer.observe(node)
74
+ })
75
+
76
+ onBeforeUnmount(() => {
77
+ if (typeof window !== "undefined") window.removeEventListener("resize", onWindowResize)
78
+ observer?.disconnect()
79
+ observer = null
80
+ })
81
+
82
+ return { resize }
83
+ }