@dnax/ui 0.0.73 → 0.0.76

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.
@@ -8,7 +8,7 @@ import { createHighlighter, type Highlighter } from "shiki"
8
8
  import { createJavaScriptRegexEngine } from "shiki/engine/javascript"
9
9
  import { Icon } from "@iconify/vue"
10
10
  import { icons } from "../lib/icons"
11
- import { radiusStyle, useRadius } from "../lib/useComponentProps"
11
+ import { radiusStyleNative, useRadius } from "../lib/useComponentProps"
12
12
  import type { RadiusProp } from "../lib/useComponentProps"
13
13
 
14
14
  const DEFAULT_THEME = "github-dark-default"
@@ -44,7 +44,7 @@ const props = withDefaults(defineProps<Props>(), {
44
44
 
45
45
  // radius : prop explicite > composantProps.QSyntax.radius > composantProps.default.radius
46
46
  const effectiveRadius = useRadius("QSyntax", () => props.radius)
47
- const roundedStyle = computed(() => radiusStyle(effectiveRadius.value))
47
+ const roundedStyle = computed(() => radiusStyleNative(effectiveRadius.value))
48
48
 
49
49
  const slots = useSlots()
50
50
 
@@ -33,7 +33,7 @@ import { computed, getCurrentInstance, onBeforeUnmount, onMounted, ref, watch }
33
33
  import { Icon } from "@iconify/vue"
34
34
  import { icons } from "../lib/icons"
35
35
  import { cn } from "../lib/utils"
36
- import { radiusStyle, useRadius } from "../lib/useComponentProps"
36
+ import { radiusStyleNative, useRadius } from "../lib/useComponentProps"
37
37
  import type { RadiusProp } from "../lib/useComponentProps"
38
38
  import QSelect from "./QSelect.vue"
39
39
 
@@ -437,7 +437,7 @@ const tableClasses = computed(() =>
437
437
 
438
438
  // radius : prop explicite > composantProps.QTable.radius > composantProps.default.radius
439
439
  const effectiveRadius = useRadius("QTable", () => props.radius)
440
- const roundedStyle = computed(() => radiusStyle(effectiveRadius.value))
440
+ const roundedStyle = computed(() => radiusStyleNative(effectiveRadius.value))
441
441
 
442
442
  const sortThClass = computed(() => "q-table__sort")
443
443
 
package/index.ts CHANGED
@@ -208,6 +208,24 @@ export { textareaCap, textareaMetrics, textareaSizing } from "./lib/textarea"
208
208
  export type { TextareaMetrics, TextareaSizing, TextareaComputedStyle } from "./lib/textarea"
209
209
  export { useTextareaAutosize } from "./lib/useTextareaAutosize"
210
210
  export type { TextareaAutosize, UseTextareaAutosizeOptions } from "./lib/useTextareaAutosize"
211
+ export { iconsRegistered, registerIconSet, registerIcons } from "./lib/registerIcons"
212
+ export { pageInfo } from "./lib/pagination"
213
+ export type { PageInfo, PageInfoOptions } from "./lib/pagination"
214
+ export { CELL_SIZE_MAX, CELL_SIZE_MIN, CELL_SIZE_PX, cellFontFor, cellSizePx, cellWidthFor } from "./lib/cellSize"
215
+ export type { CellSizePreset } from "./lib/cellSize"
216
+ export { acceptsFile, attachmentsOf, fileIconKind, formatFileSize, isAttachment, selectFiles } from "./lib/fileList"
217
+ export type {
218
+ AttachmentLike,
219
+ FileDescriptor,
220
+ FileIconKind,
221
+ FileRejection,
222
+ FileRejectionReason,
223
+ FileSelection,
224
+ FileSelectionOptions,
225
+ } from "./lib/fileList"
226
+ export { lucideIconSet } from "./lib/iconSet"
227
+ export { icons } from "./lib/icons"
228
+ export type { IconSetJson } from "./lib/icons"
211
229
  export { chartToECharts, channelValue, pickFromEvent, tableCsv, tableModel, textValue, tokenCss } from "./lib/chart"
212
230
  export type {
213
231
  QChartAxis,
@@ -0,0 +1,52 @@
1
+ // Taille de cellule : presets, bornes, dérivés (largeur, police).
2
+ // Le point sensible est la **non-régression** : `md` doit reproduire exactement les valeurs
3
+ // historiques (32 px de haut, 160 px de large, 13 px de police).
4
+ import { describe, expect, it } from "bun:test"
5
+ import { CELL_SIZE_MAX, CELL_SIZE_MIN, CELL_SIZE_PX, cellFontFor, cellSizePx, cellWidthFor } from "./cellSize"
6
+
7
+ describe("cellSizePx", () => {
8
+ it("md est le défaut historique (32 px)", () => {
9
+ expect(cellSizePx()).toBe(32)
10
+ expect(cellSizePx(null)).toBe(32)
11
+ expect(cellSizePx("md")).toBe(32)
12
+ expect(CELL_SIZE_PX.md).toBe(32)
13
+ })
14
+
15
+ it("résout les presets", () => {
16
+ expect(cellSizePx("sm")).toBe(28)
17
+ expect(cellSizePx("lg")).toBe(40)
18
+ })
19
+
20
+ it("accepte une valeur en px, arrondie et bornée", () => {
21
+ expect(cellSizePx(48)).toBe(48)
22
+ expect(cellSizePx(66)).toBe(66)
23
+ expect(cellSizePx(47.6)).toBe(48)
24
+ expect(cellSizePx(2)).toBe(CELL_SIZE_MIN)
25
+ expect(cellSizePx(999)).toBe(CELL_SIZE_MAX)
26
+ expect(cellSizePx(Number.NaN)).toBe(32)
27
+ })
28
+ })
29
+
30
+ describe("cellWidthFor", () => {
31
+ it("garde le ratio historique 5:1", () => {
32
+ expect(cellWidthFor(32)).toBe(160)
33
+ expect(cellWidthFor(28)).toBe(140)
34
+ expect(cellWidthFor(40)).toBe(200)
35
+ expect(cellWidthFor(48)).toBe(240)
36
+ expect(cellWidthFor(66)).toBe(330)
37
+ })
38
+ })
39
+
40
+ describe("cellFontFor", () => {
41
+ it("md garde la police historique (13 px)", () => {
42
+ expect(cellFontFor(32)).toBe(13)
43
+ })
44
+
45
+ it("grandit avec la cellule puis plafonne", () => {
46
+ expect(cellFontFor(28)).toBe(11)
47
+ expect(cellFontFor(40)).toBe(16)
48
+ expect(cellFontFor(48)).toBe(19)
49
+ expect(cellFontFor(66)).toBe(20)
50
+ expect(cellFontFor(120)).toBe(20)
51
+ })
52
+ })
@@ -0,0 +1,43 @@
1
+ // Taille de cellule du tableur — calculs **purs** (aucun DOM).
2
+ //
3
+ // Une seule échelle pilote la hauteur de ligne, la largeur de colonne par défaut **et** la
4
+ // taille de police des cellules, pour qu'une grille « large » reste proportionnée (une
5
+ // cellule de 66px avec une police de 13px paraît vide).
6
+
7
+ /** Presets de hauteur de cellule, en px. */
8
+ export type CellSizePreset = "sm" | "md" | "lg"
9
+
10
+ export const CELL_SIZE_PX: Record<CellSizePreset, number> = {
11
+ sm: 28,
12
+ md: 32,
13
+ lg: 40,
14
+ }
15
+
16
+ /** Bornes d'une hauteur passée en nombre (px). */
17
+ export const CELL_SIZE_MIN = 20
18
+ export const CELL_SIZE_MAX = 120
19
+
20
+ /**
21
+ * Hauteur de cellule en px : preset (`sm` 28, `md` 32, `lg` 40), nombre borné
22
+ * `[20, 120]`, ou `md` par défaut / pour une valeur invalide.
23
+ */
24
+ export const cellSizePx = (size?: CellSizePreset | number | null): number => {
25
+ if (typeof size === "number" && Number.isFinite(size)) {
26
+ return Math.min(CELL_SIZE_MAX, Math.max(CELL_SIZE_MIN, Math.round(size)))
27
+ }
28
+ if (size === "sm" || size === "md" || size === "lg") return CELL_SIZE_PX[size]
29
+ return CELL_SIZE_PX.md
30
+ }
31
+
32
+ /**
33
+ * Largeur de colonne par défaut dérivée de la hauteur — ratio **5:1**, cohérent avec le
34
+ * couple historique 32 px / 160 px. Une cellule haute est aussi plus large.
35
+ */
36
+ export const cellWidthFor = (heightPx: number): number => Math.round(heightPx * 5)
37
+
38
+ /**
39
+ * Taille de police des cellules : suit la hauteur (~40 %), bornée pour rester lisible.
40
+ * `32 px → 13` (la valeur historique), `40 → 16`, `48 → 20`, `66 → 20` (plafond).
41
+ */
42
+ export const cellFontFor = (heightPx: number): number =>
43
+ Math.min(20, Math.max(11, Math.round(heightPx * 0.406)))
@@ -0,0 +1,119 @@
1
+ // Fichiers joints : règles d'`accept`, quota, icônes — testées sans navigateur ni DOM.
2
+ // Ces helpers sont la seule source de vérité (QFilePicker et QSpreadsheet `attachment`).
3
+ import { describe, expect, it } from "bun:test"
4
+ import {
5
+ acceptsFile,
6
+ attachmentsOf,
7
+ fileIconKind,
8
+ formatFileSize,
9
+ selectFiles,
10
+ type FileDescriptor,
11
+ } from "./fileList"
12
+
13
+ const file = (name: string, type = "", size = 1000): File =>
14
+ new File([new Uint8Array(size)], name, { type })
15
+
16
+ const desc = (name: string, type = ""): FileDescriptor => ({ name, type, size: 0 })
17
+
18
+ describe("formatFileSize", () => {
19
+ it("choisit l'unité lisible", () => {
20
+ expect(formatFileSize(0)).toBe("0 o")
21
+ expect(formatFileSize(512)).toBe("512 o")
22
+ expect(formatFileSize(2048)).toBe("2 Ko")
23
+ expect(formatFileSize(5 * 1024 * 1024)).toBe("5 Mo")
24
+ expect(formatFileSize(3 * 1024 * 1024 * 1024)).toBe("3.0 Go")
25
+ })
26
+
27
+ it("neutralise les valeurs invalides", () => {
28
+ expect(formatFileSize(Number.NaN)).toBe("0 o")
29
+ expect(formatFileSize(-12)).toBe("0 o")
30
+ })
31
+ })
32
+
33
+ describe("acceptsFile", () => {
34
+ it("accepte tout sans motif", () => {
35
+ expect(acceptsFile(desc("a.bin"), "")).toBe(true)
36
+ expect(acceptsFile(desc("a.bin"), undefined)).toBe(true)
37
+ expect(acceptsFile(desc("a.bin"), " , ")).toBe(true)
38
+ })
39
+
40
+ it("gère les jokers de type MIME", () => {
41
+ expect(acceptsFile(desc("photo.png", "image/png"), "image/*")).toBe(true)
42
+ expect(acceptsFile(desc("doc.pdf", "application/pdf"), "image/*")).toBe(false)
43
+ expect(acceptsFile(desc("clip.mp4", "video/mp4"), "image/*, video/*")).toBe(true)
44
+ })
45
+
46
+ it("gère les extensions et les types complets", () => {
47
+ expect(acceptsFile(desc("rapport.PDF"), ".pdf")).toBe(true)
48
+ expect(acceptsFile(desc("rapport.txt"), ".pdf")).toBe(false)
49
+ expect(acceptsFile(desc("a.bin", "Application/PDF"), "application/pdf")).toBe(true)
50
+ })
51
+
52
+ it("ignore un motif invalide (ni MIME, ni extension)", () => {
53
+ expect(acceptsFile(desc("a.png", "image/png"), "image")).toBe(false)
54
+ })
55
+ })
56
+
57
+ describe("fileIconKind", () => {
58
+ it("classe par type MIME puis par extension", () => {
59
+ expect(fileIconKind(desc("a.png", "image/png"))).toBe("image")
60
+ expect(fileIconKind(desc("a.mp4", "video/mp4"))).toBe("video")
61
+ expect(fileIconKind(desc("a.mp3", "audio/mpeg"))).toBe("audio")
62
+ expect(fileIconKind(desc("a.zip", "application/zip"))).toBe("archive")
63
+ expect(fileIconKind(desc("a.xlsx"))).toBe("spreadsheet")
64
+ expect(fileIconKind(desc("a.ts"))).toBe("code")
65
+ expect(fileIconKind(desc("a.bin"))).toBe("file")
66
+ })
67
+ })
68
+
69
+ describe("selectFiles", () => {
70
+ it("rejette par type, taille puis quota (dans cet ordre)", () => {
71
+ const result = selectFiles(
72
+ [file("a.png", "image/png"), file("b.pdf", "application/pdf"), file("c.png", "image/png", 5000)],
73
+ { accept: "image/*", maxSize: 2000, maxFiles: 1 },
74
+ )
75
+ expect(result.accepted.map((f) => f.name)).toEqual(["a.png"])
76
+ expect(result.rejected.map((r) => r.reason)).toEqual(["type", "size"])
77
+ })
78
+
79
+ it("tient compte des fichiers déjà présents dans le quota", () => {
80
+ const result = selectFiles([file("a.png"), file("b.png")], { maxFiles: 3, current: 2 })
81
+ expect(result.accepted.map((f) => f.name)).toEqual(["a.png"])
82
+ expect(result.rejected.map((r) => r.reason)).toEqual(["count"])
83
+ })
84
+
85
+ it("sans quota, tout passe (ordre conservé)", () => {
86
+ const result = selectFiles([file("a.png"), file("b.png")])
87
+ expect(result.accepted.map((f) => f.name)).toEqual(["a.png", "b.png"])
88
+ expect(result.rejected).toEqual([])
89
+ })
90
+
91
+ it("quota déjà atteint → tout est rejeté en `count`", () => {
92
+ const result = selectFiles([file("a.png")], { maxFiles: 2, current: 2 })
93
+ expect(result.accepted).toEqual([])
94
+ expect(result.rejected.map((r) => r.reason)).toEqual(["count"])
95
+ })
96
+ })
97
+
98
+ describe("attachmentsOf", () => {
99
+ it("normalise un tableau et ignore les entrées invalides", () => {
100
+ const list = attachmentsOf([
101
+ { id: "a", name: "a.png", size: 1, type: "image/png" },
102
+ { body: "pas un fichier" },
103
+ null,
104
+ { name: "b.pdf" },
105
+ ])
106
+ expect(list.map((att) => att.name)).toEqual(["a.png", "b.pdf"])
107
+ })
108
+
109
+ it("accepte un fichier seul (donnée posée à la main)", () => {
110
+ expect(attachmentsOf({ name: "seul.pdf" }).map((att) => att.name)).toEqual(["seul.pdf"])
111
+ })
112
+
113
+ it("renvoie un tableau vide pour le vide ou une chaîne", () => {
114
+ expect(attachmentsOf(null)).toEqual([])
115
+ expect(attachmentsOf("")).toEqual([])
116
+ expect(attachmentsOf([])).toEqual([])
117
+ expect(attachmentsOf("a.png")).toEqual([])
118
+ })
119
+ })
@@ -0,0 +1,159 @@
1
+ // Fichiers joints — helpers **purs** partagés par `QFilePicker` et `QSpreadsheet`
2
+ // (type de cellule `attachment`). Aucun DOM : les règles `accept` sont exactement celles
3
+ // de l'attribut HTML (`image/*`, `application/pdf`, `.pdf`…) pour que le filtre du
4
+ // sélecteur et la validation programmatique disent la même chose.
5
+
6
+ /** Raison d'un rejet de sélection. */
7
+ export type FileRejectionReason = "type" | "size" | "count"
8
+
9
+ export interface FileRejection {
10
+ file: File
11
+ reason: FileRejectionReason
12
+ }
13
+
14
+ /** Ce dont les helpers ont besoin — un vrai `File` y satisfait. */
15
+ export interface FileDescriptor {
16
+ name: string
17
+ type: string
18
+ size: number
19
+ }
20
+
21
+ const ARCHIVE_EXT = /\.(zip|rar|7z|tar|gz|bz2|xz|tgz)$/
22
+ const SPREADSHEET_EXT = /\.(xlsx|xls|csv|ods|tsv)$/
23
+ const CODE_EXT =
24
+ /\.(json|js|mjs|cjs|ts|jsx|tsx|html?|xml|css|scss|sass|less|vue|py|java|c|cpp|h|hpp|sh|bash|yml|yaml|toml|ini|go|rs|php|rb|swift|kt)$/
25
+
26
+ /** Famille d'icône d'un fichier (le composant la mappe sur `icons.*`). */
27
+ export type FileIconKind =
28
+ | "image"
29
+ | "video"
30
+ | "audio"
31
+ | "archive"
32
+ | "spreadsheet"
33
+ | "code"
34
+ | "file"
35
+
36
+ export const fileIconKind = (file: Pick<FileDescriptor, "name" | "type">): FileIconKind => {
37
+ const type = (file.type ?? "").toLowerCase()
38
+ const name = (file.name ?? "").toLowerCase()
39
+ if (type.startsWith("video/")) return "video"
40
+ if (type.startsWith("audio/")) return "audio"
41
+ if (ARCHIVE_EXT.test(name) || /zip|compressed/.test(type)) return "archive"
42
+ if (SPREADSHEET_EXT.test(name) || type.includes("spreadsheet") || type.includes("excel")) {
43
+ return "spreadsheet"
44
+ }
45
+ if (CODE_EXT.test(name)) return "code"
46
+ if (type.startsWith("image/")) return "image"
47
+ return "file"
48
+ }
49
+
50
+ /** Taille lisible : `o` / `Ko` / `Mo` / `Go`. */
51
+ export const formatFileSize = (bytes: number): string => {
52
+ if (!Number.isFinite(bytes) || bytes < 0) return "0 o"
53
+ if (bytes >= 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} Go`
54
+ if (bytes >= 1024 * 1024) return `${Math.round(bytes / (1024 * 1024))} Mo`
55
+ if (bytes >= 1024) return `${Math.round(bytes / 1024)} Ko`
56
+ return `${bytes} o`
57
+ }
58
+
59
+ /**
60
+ * Le fichier correspond-il à `accept` ? Mêmes règles que l'attribut HTML : liste
61
+ * séparée par des virgules, `type/*`, type MIME complet ou extension (`.pdf`).
62
+ * `accept` vide/absent → tout passe.
63
+ */
64
+ export const acceptsFile = (
65
+ file: Pick<FileDescriptor, "name" | "type">,
66
+ accept?: string,
67
+ ): boolean => {
68
+ const patterns = (accept ?? "")
69
+ .split(",")
70
+ .map((pattern) => pattern.trim().toLowerCase())
71
+ .filter(Boolean)
72
+ if (patterns.length === 0) return true
73
+
74
+ const name = (file.name ?? "").toLowerCase()
75
+ const type = (file.type ?? "").toLowerCase()
76
+
77
+ return patterns.some((pattern) => {
78
+ // `image/*`, `video/*`… (et `application/*` par généralisation)
79
+ if (pattern.endsWith("/*")) return type.startsWith(pattern.slice(0, -1))
80
+ if (pattern.startsWith(".")) return name.endsWith(pattern)
81
+ if (pattern.includes("/")) return type === pattern
82
+ return false
83
+ })
84
+ }
85
+
86
+ export interface FileSelectionOptions {
87
+ /** Motif `accept` de la colonne (voir `acceptsFile`) */
88
+ accept?: string
89
+ /** Taille maximale par fichier, en octets (0/absent = illimité) */
90
+ maxSize?: number
91
+ /** Nombre maximum de fichiers au total (0/absent = illimité) */
92
+ maxFiles?: number
93
+ /** Fichiers déjà présents (réduit le quota restant) */
94
+ current?: number
95
+ }
96
+
97
+ export interface FileSelection {
98
+ accepted: File[]
99
+ rejected: FileRejection[]
100
+ }
101
+
102
+ /**
103
+ * Filtre une sélection : par type, par taille, puis par quota — dans cet ordre, pour que
104
+ * le motif de rejet soit le plus précis possible. L'ordre des fichiers acceptés est celui
105
+ * de `incoming`.
106
+ */
107
+ export const selectFiles = (
108
+ incoming: File[],
109
+ options: FileSelectionOptions = {},
110
+ ): FileSelection => {
111
+ const accepted: File[] = []
112
+ const rejected: FileRejection[] = []
113
+
114
+ const valid: File[] = []
115
+ for (const file of incoming) {
116
+ if (options.maxSize && options.maxSize > 0 && file.size > options.maxSize) {
117
+ rejected.push({ file, reason: "size" })
118
+ continue
119
+ }
120
+ if (!acceptsFile(file, options.accept)) {
121
+ rejected.push({ file, reason: "type" })
122
+ continue
123
+ }
124
+ valid.push(file)
125
+ }
126
+
127
+ const quota = options.maxFiles && options.maxFiles > 0 ? options.maxFiles : Number.POSITIVE_INFINITY
128
+ const remaining = Math.max(0, quota - Math.max(0, options.current ?? 0))
129
+ for (const [index, file] of valid.entries()) {
130
+ if (index < remaining) accepted.push(file)
131
+ else rejected.push({ file, reason: "count" })
132
+ }
133
+
134
+ return { accepted, rejected }
135
+ }
136
+
137
+ // ─── Fichiers stockés dans une cellule (`attachment`) ───
138
+
139
+ /** Forme minimale d'un fichier stocké (ce qu'une cellule `attachment` contient). */
140
+ export interface AttachmentLike {
141
+ id?: string
142
+ name: string
143
+ size?: number
144
+ type?: string
145
+ url?: string
146
+ }
147
+
148
+ /** L'entrée ressemble-t-elle à un fichier stocké ? (tolérance des données importées) */
149
+ export const isAttachment = (value: unknown): value is AttachmentLike =>
150
+ !!value && typeof value === "object" && typeof (value as { name?: unknown }).name === "string"
151
+
152
+ /**
153
+ * Valeur d'une cellule `attachment` normalisée en tableau : un fichier **seul** est accepté
154
+ * (donnée posée à la main ou importée), tout le reste est ignoré.
155
+ */
156
+ export const attachmentsOf = (value: unknown): AttachmentLike[] => {
157
+ if (Array.isArray(value)) return value.filter(isAttachment)
158
+ return isAttachment(value) ? [value] : []
159
+ }
@@ -0,0 +1,61 @@
1
+ // Icônes embarquées — garde-fou contre la dérive du fichier généré.
2
+ //
3
+ // `lib/iconSet.ts` est produit par `bun scripts/gen-icon-set.ts`. Si quelqu'un ajoute
4
+ // un `lucide:…` dans un composant sans relancer le script, l'icône repasse
5
+ // silencieusement par l'API Iconify (donc : absente du SSR et du mode hors ligne).
6
+ // Ces tests échouent dans ce cas, sans navigateur ni réseau.
7
+ import { readdirSync, readFileSync } from "node:fs"
8
+ import { join } from "node:path"
9
+ import { describe, expect, it } from "bun:test"
10
+ import { icons } from "./icons"
11
+ import { lucideIconSet } from "./iconSet"
12
+
13
+ const ICON_NAME_RE = /\blucide:[a-z0-9]+(?:-[a-z0-9]+)*/g
14
+ const componentsDir = join(import.meta.dir, "..", "components")
15
+
16
+ const has = (name: string): boolean =>
17
+ name in lucideIconSet.icons || Boolean(lucideIconSet.aliases && name in lucideIconSet.aliases)
18
+
19
+ const strip = (value: string): string => value.slice("lucide:".length)
20
+
21
+ describe("lucideIconSet (généré)", () => {
22
+ it("est une collection lucide valide", () => {
23
+ expect(lucideIconSet.prefix).toBe("lucide")
24
+ expect(lucideIconSet.width).toBe(24)
25
+ expect(lucideIconSet.height).toBe(24)
26
+ expect(Object.keys(lucideIconSet.icons).length).toBeGreaterThan(0)
27
+ for (const [name, icon] of Object.entries(lucideIconSet.icons)) {
28
+ expect(name).not.toContain(":")
29
+ expect(icon.body.length).toBeGreaterThan(0)
30
+ }
31
+ })
32
+
33
+ it("contient toutes les icônes de `lib/icons.ts`", () => {
34
+ const missing = Object.values(icons)
35
+ .map(strip)
36
+ .filter((name) => !has(name))
37
+ expect(missing).toEqual([])
38
+ })
39
+
40
+ it("contient tous les `lucide:…` référencés dans les composants", () => {
41
+ const files = readdirSync(componentsDir, { recursive: true })
42
+ .map(String)
43
+ .filter((path) => path.endsWith(".vue"))
44
+ .map((path) => join(componentsDir, path))
45
+
46
+ const missing = new Set<string>()
47
+ for (const file of files) {
48
+ for (const match of readFileSync(file, "utf8").matchAll(ICON_NAME_RE)) {
49
+ const name = strip(match[0])
50
+ if (!has(name)) missing.add(name)
51
+ }
52
+ }
53
+ expect([...missing]).toEqual([])
54
+ })
55
+
56
+ it("embarque la parente de chaque alias", () => {
57
+ for (const alias of Object.values(lucideIconSet.aliases ?? {})) {
58
+ expect(alias.parent in lucideIconSet.icons).toBe(true)
59
+ }
60
+ })
61
+ })
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
+ }