@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/components/QBtnDropdown.vue +8 -1
- package/components/QCard.vue +3 -3
- package/components/QDialog.vue +2 -2
- package/components/QGridItem.vue +1 -1
- package/components/QImg.vue +3 -1
- package/components/QInput.vue +60 -21
- package/components/QInputOtp.vue +2 -0
- package/components/QInputTextarea.vue +211 -0
- package/components/QSyntax.vue +2 -2
- package/components/QTable.vue +2 -2
- package/index.ts +9 -0
- package/lib/iconSet.test.ts +61 -0
- package/lib/iconSet.ts +91 -0
- package/lib/iconSetScrape.ts +139 -0
- package/lib/icons.ts +16 -0
- package/lib/registerIcons.ts +37 -0
- package/lib/textarea.test.ts +80 -0
- package/lib/textarea.ts +68 -0
- package/lib/useComponentProps.test.ts +55 -1
- package/lib/useComponentProps.ts +15 -0
- package/lib/useTextareaAutosize.ts +83 -0
- package/module.ts +124 -6
- package/package.json +4 -2
- package/runtime.ts +5 -0
- package/scripts/gen-icon-set.ts +110 -0
- package/scripts/generate-exports.ts +46 -69
- package/styles/main.css +68 -0
package/module.ts
CHANGED
|
@@ -4,12 +4,39 @@
|
|
|
4
4
|
*
|
|
5
5
|
* - Auto-importe les composants Q* (balises kebab-case : <q-btn>, <q-header>…)
|
|
6
6
|
* - Ajoute styles/main.css (safe-area, tokens, styles de tous les composants)
|
|
7
|
+
* - **Embarque les icônes** : enregistre un sous-ensemble `lucide` auprès d'Iconify
|
|
8
|
+
* (`addCollection`) — design system + (par défaut) les icônes `lucide:…` trouvées
|
|
9
|
+
* dans les sources de l'app. Aucune requête vers l'API Iconify pour ces noms, et les
|
|
10
|
+
* `<path>` sont présents dès le SSR.
|
|
7
11
|
*
|
|
8
12
|
* Options (clé `dnaxUi`) :
|
|
9
|
-
* components: false
|
|
10
|
-
* css: false
|
|
13
|
+
* components: false — désactive l'auto-import des composants
|
|
14
|
+
* css: false — désactive l'ajout de styles/main.css
|
|
15
|
+
* icons: false — n'embarque aucune icône (API Iconify à la demande)
|
|
16
|
+
* icons: { scan: false } — n'embarque que le design system
|
|
17
|
+
* icons: { dirs: ["app"] } — limite le scan (chemins relatifs à rootDir)
|
|
11
18
|
*/
|
|
12
|
-
import { addComponentsDir, addPluginTemplate, createResolver, defineNuxtModule } from "@nuxt/kit"
|
|
19
|
+
import { addComponentsDir, addPluginTemplate, createResolver, defineNuxtModule, updateTemplates } from "@nuxt/kit"
|
|
20
|
+
import { readFileSync } from "node:fs"
|
|
21
|
+
import { createRequire } from "node:module"
|
|
22
|
+
import { join, resolve } from "node:path"
|
|
23
|
+
import { lucideIconSet } from "./lib/iconSet"
|
|
24
|
+
import {
|
|
25
|
+
buildIconSet,
|
|
26
|
+
mergeIconSets,
|
|
27
|
+
scrapeIconNames,
|
|
28
|
+
type IconifySourceCollection,
|
|
29
|
+
} from "./lib/iconSetScrape"
|
|
30
|
+
|
|
31
|
+
export interface DnaxUiIconsOptions {
|
|
32
|
+
/**
|
|
33
|
+
* Scanne les sources de l'app au build pour embarquer aussi **ses** icônes
|
|
34
|
+
* `lucide:` (défaut : true)
|
|
35
|
+
*/
|
|
36
|
+
scan?: boolean
|
|
37
|
+
/** Dossiers à scanner, résolus depuis `rootDir` (défaut : tout `rootDir`) */
|
|
38
|
+
dirs?: string[]
|
|
39
|
+
}
|
|
13
40
|
|
|
14
41
|
export interface DnaxUiModuleOptions {
|
|
15
42
|
/** Auto-importe les composants Q* (défaut : true) */
|
|
@@ -18,6 +45,33 @@ export interface DnaxUiModuleOptions {
|
|
|
18
45
|
css?: boolean
|
|
19
46
|
/** Installe le handler « retour » : ferme l'overlay ouvert au lieu de naviguer (défaut : true) */
|
|
20
47
|
overlayBack?: boolean
|
|
48
|
+
/**
|
|
49
|
+
* Icônes : `false` = rien d'embarqué (API Iconify à la demande) ; objet = réglages
|
|
50
|
+
* du scan. Par défaut : sous-ensemble du design system **+** scan des sources.
|
|
51
|
+
*/
|
|
52
|
+
icons?: boolean | DnaxUiIconsOptions
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Collection Iconify lue pour résoudre les noms (`@iconify-json/lucide`). */
|
|
56
|
+
const LUCIDE_COLLECTION = "@iconify-json/lucide/icons.json"
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Charge la collection `lucide`. Elle vit dans les dépendances de `@dnax/ui` ; on tente
|
|
60
|
+
* la résolution depuis ce module, puis depuis l'app hôte (hoisting).
|
|
61
|
+
*/
|
|
62
|
+
const loadLucideCollection = (rootDir: string): IconifySourceCollection | null => {
|
|
63
|
+
const resolvers = [
|
|
64
|
+
() => createRequire(import.meta.url).resolve(LUCIDE_COLLECTION),
|
|
65
|
+
() => createRequire(join(rootDir, "package.json")).resolve(LUCIDE_COLLECTION),
|
|
66
|
+
]
|
|
67
|
+
for (const resolveSpec of resolvers) {
|
|
68
|
+
try {
|
|
69
|
+
return JSON.parse(readFileSync(resolveSpec(), "utf8")) as IconifySourceCollection
|
|
70
|
+
} catch {
|
|
71
|
+
// on passe au résolveur suivant
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return null
|
|
21
75
|
}
|
|
22
76
|
|
|
23
77
|
export default defineNuxtModule<DnaxUiModuleOptions>({
|
|
@@ -30,12 +84,13 @@ export default defineNuxtModule<DnaxUiModuleOptions>({
|
|
|
30
84
|
components: true,
|
|
31
85
|
css: true,
|
|
32
86
|
overlayBack: true,
|
|
87
|
+
icons: { scan: true },
|
|
33
88
|
},
|
|
34
89
|
setup(options, nuxt) {
|
|
35
|
-
const { resolve } = createResolver(import.meta.url)
|
|
90
|
+
const { resolve: resolvePath } = createResolver(import.meta.url)
|
|
36
91
|
|
|
37
92
|
if (options.css) {
|
|
38
|
-
const cssPath =
|
|
93
|
+
const cssPath = resolvePath("./styles/main.css")
|
|
39
94
|
if (!nuxt.options.css.includes(cssPath)) {
|
|
40
95
|
nuxt.options.css.push(cssPath)
|
|
41
96
|
}
|
|
@@ -43,13 +98,76 @@ export default defineNuxtModule<DnaxUiModuleOptions>({
|
|
|
43
98
|
|
|
44
99
|
if (options.components) {
|
|
45
100
|
addComponentsDir({
|
|
46
|
-
path:
|
|
101
|
+
path: resolvePath("./components"),
|
|
47
102
|
prefix: "", // pas de préfixe : QBtn.vue → <q-btn>
|
|
48
103
|
pathPrefix: false, // pas de préfixe par sous-dossier
|
|
49
104
|
ignore: ["**/internal/**"], // composants internes : non auto-importés
|
|
50
105
|
})
|
|
51
106
|
}
|
|
52
107
|
|
|
108
|
+
// — Icônes embarquées ---------------------------------------------------------
|
|
109
|
+
// Plugin **universel** : côté serveur aussi, sinon le SSR ne rend que des coquilles
|
|
110
|
+
// `<svg class="iconify">`, les `<path>` n'arrivant qu'après le fetch client (et
|
|
111
|
+
// jamais sans réseau). `addCollection` est purement local et **fusionne**.
|
|
112
|
+
if (options.icons !== false) {
|
|
113
|
+
const iconsOptions: DnaxUiIconsOptions =
|
|
114
|
+
typeof options.icons === "object" ? options.icons : {}
|
|
115
|
+
const scan = iconsOptions.scan !== false
|
|
116
|
+
|
|
117
|
+
const dirs = iconsOptions.dirs?.length
|
|
118
|
+
? iconsOptions.dirs.map((dir) => resolve(nuxt.options.rootDir, dir))
|
|
119
|
+
: [nuxt.options.rootDir]
|
|
120
|
+
|
|
121
|
+
const collection = scan ? loadLucideCollection(nuxt.options.rootDir) : null
|
|
122
|
+
if (scan && !collection) {
|
|
123
|
+
console.warn(
|
|
124
|
+
"[@dnax/ui] `@iconify-json/lucide` introuvable : seules les icônes du design system sont embarquées. " +
|
|
125
|
+
"Installez-le (`bun add -D @iconify-json/lucide`) pour scanner votre app.",
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
addPluginTemplate({
|
|
130
|
+
filename: "dnax-ui-icons.mjs",
|
|
131
|
+
getContents: () => {
|
|
132
|
+
let set = lucideIconSet
|
|
133
|
+
let scanned = false
|
|
134
|
+
if (collection) {
|
|
135
|
+
const { set: appSet, missing } = buildIconSet(collection, scrapeIconNames(dirs))
|
|
136
|
+
set = mergeIconSets(lucideIconSet, appSet)
|
|
137
|
+
scanned = true
|
|
138
|
+
if (nuxt.options.dev && missing.length) {
|
|
139
|
+
console.warn(
|
|
140
|
+
`[@dnax/ui] icône(s) \`lucide:\` absente(s) de la collection (faute de frappe ?) : ${missing.join(", ")}`,
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return `
|
|
145
|
+
import { defineNuxtPlugin } from "#imports"
|
|
146
|
+
import { registerIconSet } from "@dnax/ui/runtime"
|
|
147
|
+
|
|
148
|
+
// Sous-ensemble \`lucide\` embarqué (${Object.keys(set.icons).length} icônes) :
|
|
149
|
+
// design system${scanned ? " + sources de l'app" : ""}. Aucune requête vers l'API Iconify
|
|
150
|
+
// pour ces noms ; les autres retombent dessus (progressive enhancement).
|
|
151
|
+
const ICON_SET = ${JSON.stringify(set)}
|
|
152
|
+
|
|
153
|
+
export default defineNuxtPlugin(() => {
|
|
154
|
+
registerIconSet(ICON_SET)
|
|
155
|
+
})
|
|
156
|
+
`
|
|
157
|
+
},
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
// Dev : une icône ajoutée dans les sources régénère le module virtuel — sans ça
|
|
161
|
+
// il faudrait redémarrer le serveur pour l'embarquer (en attendant, l'icône
|
|
162
|
+
// s'affiche via l'API : dégradation, pas rupture).
|
|
163
|
+
if (scan) {
|
|
164
|
+
nuxt.hook("builder:watch", async (_event, watchedPath) => {
|
|
165
|
+
if (!/\.(vue|ts|js|mjs|md|mdx)$/.test(String(watchedPath).replace(/\\/g, "/"))) return
|
|
166
|
+
await updateTemplates({ filter: (template) => template.filename === "dnax-ui-icons.mjs" })
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
53
171
|
// Dépendances CJS sans export ESM `default` explicite : esbuild doit les pré-bundler
|
|
54
172
|
// (interop) sinon, servies brutes, le navigateur échoue sur « does not provide an
|
|
55
173
|
// export named 'default' ». `leaflet` (lib/mapLeaflet.ts) est dans ce cas ; le code
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dnax/ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.75",
|
|
4
4
|
"module": "index.ts",
|
|
5
5
|
"description": "UI design System",
|
|
6
6
|
"type": "module",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"./styles/main.css": "./styles/main.css"
|
|
13
13
|
},
|
|
14
14
|
"scripts": {
|
|
15
|
-
"generate": "bun scripts/generate-exports.ts"
|
|
15
|
+
"generate": "bun scripts/generate-exports.ts",
|
|
16
|
+
"gen:icons": "bun scripts/gen-icon-set.ts --out ./lib/iconSet.ts --src ./components --src ./lib"
|
|
16
17
|
},
|
|
17
18
|
"private": false,
|
|
18
19
|
"devDependencies": {
|
|
@@ -23,6 +24,7 @@
|
|
|
23
24
|
"vue": "^3.5.41"
|
|
24
25
|
},
|
|
25
26
|
"dependencies": {
|
|
27
|
+
"@iconify-json/lucide": "^1.2.150",
|
|
26
28
|
"@iconify/vue": "^5.0.1",
|
|
27
29
|
"@maptiler/sdk": "^4.1.0",
|
|
28
30
|
"@nuxt/kit": "4.5.2",
|
package/runtime.ts
CHANGED
|
@@ -46,6 +46,11 @@ export type { QNetwork, NetworkOptions } from "./lib/network"
|
|
|
46
46
|
// `$q.wakeLock` (écran allumé) — Screen Wake Lock API.
|
|
47
47
|
export { wakeLock, createWakeLock } from "./lib/wakeLock"
|
|
48
48
|
export type { QWakeLock, WakeLockOptions } from "./lib/wakeLock"
|
|
49
|
+
// Icônes du design system embarquées dans le bundle (`lib/iconSet.ts`, généré) :
|
|
50
|
+
// appelé par le module Nuxt, à appeler soi-même sans module. Évite l'appel runtime
|
|
51
|
+
// à l'API Iconify et rend les icônes dès le SSR.
|
|
52
|
+
export { registerIcons, iconsRegistered } from "./lib/registerIcons"
|
|
53
|
+
export { registerIconSet } from "./lib/registerIcons"
|
|
49
54
|
|
|
50
55
|
// — Providers de `$q` (montés au boot par l'app hôte) —
|
|
51
56
|
export { default as QDialogProvider } from "./components/QDialogProvider.vue"
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Génère un **sous-ensemble** de la collection `lucide` contenant uniquement les
|
|
3
|
+
* icônes réellement référencées dans le code, sous forme d'un module TS prêt à être
|
|
4
|
+
* embarqué dans le bundle.
|
|
5
|
+
*
|
|
6
|
+
* C'est ce qui produit `lib/iconSet.ts` (icônes du design system, figées et testées).
|
|
7
|
+
* Le module Nuxt fait la même chose **à la volée** pour les sources de l'app hôte
|
|
8
|
+
* (option `icons.scan`) ; ce script sert quand on veut un fichier committé, ou sans Nuxt.
|
|
9
|
+
*
|
|
10
|
+
* Usage :
|
|
11
|
+
* # design system (composants + lib) → packages/ui/lib/iconSet.ts
|
|
12
|
+
* bun scripts/gen-icon-set.ts --out ./lib/iconSet.ts --src ./components --src ./lib
|
|
13
|
+
*
|
|
14
|
+
* # app / site de docs → ./app/iconSet.ts
|
|
15
|
+
* bun scripts/gen-icon-set.ts --out ./app/iconSet.ts --src ./app --src ./content \
|
|
16
|
+
* --type-import @dnax/ui
|
|
17
|
+
*
|
|
18
|
+
* `--src` est récursif (`.vue`, `.ts`, `.js`, `.mjs`, `.md`), les dossiers
|
|
19
|
+
* `node_modules` / `.nuxt` / `.output` / `dist` / `public` sont ignorés. Un nom absent
|
|
20
|
+
* de la collection est signalé (faute de frappe le plus souvent).
|
|
21
|
+
*/
|
|
22
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises"
|
|
23
|
+
import { dirname, resolve } from "node:path"
|
|
24
|
+
import {
|
|
25
|
+
buildIconSet,
|
|
26
|
+
scrapeIconNames,
|
|
27
|
+
type IconifySourceCollection,
|
|
28
|
+
} from "../lib/iconSetScrape"
|
|
29
|
+
|
|
30
|
+
// — Arguments ------------------------------------------------------------------
|
|
31
|
+
let outFile = ""
|
|
32
|
+
let typeImport = "./icons"
|
|
33
|
+
const sources: string[] = []
|
|
34
|
+
|
|
35
|
+
const args = process.argv.slice(2)
|
|
36
|
+
for (let i = 0; i < args.length; i++) {
|
|
37
|
+
const arg = args[i]!
|
|
38
|
+
if (arg === "--out") outFile = args[++i] ?? ""
|
|
39
|
+
else if (arg === "--src") sources.push(args[++i] ?? "")
|
|
40
|
+
else if (arg === "--type-import") typeImport = args[++i] ?? "./icons"
|
|
41
|
+
else {
|
|
42
|
+
console.error(`Argument inconnu : ${arg}`)
|
|
43
|
+
process.exit(1)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!outFile || sources.length === 0) {
|
|
48
|
+
console.error(
|
|
49
|
+
"Usage : bun scripts/gen-icon-set.ts --out <fichier.ts> --src <dossier> [--src <dossier>…] [--type-import <specifier>]",
|
|
50
|
+
)
|
|
51
|
+
process.exit(1)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// — Relevé + résolution --------------------------------------------------------
|
|
55
|
+
const resolvedSources = sources.map((src) => resolve(process.cwd(), src))
|
|
56
|
+
const names = scrapeIconNames(resolvedSources)
|
|
57
|
+
|
|
58
|
+
if (names.length === 0) {
|
|
59
|
+
console.error("Aucune icône `lucide:` trouvée dans les sources fournies.")
|
|
60
|
+
process.exit(1)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const collectionUrl = import.meta.resolve("@iconify-json/lucide/icons.json")
|
|
64
|
+
const collection = JSON.parse(await readFile(new URL(collectionUrl), "utf8")) as IconifySourceCollection
|
|
65
|
+
const { set, missing } = buildIconSet(collection, names)
|
|
66
|
+
|
|
67
|
+
// — Écriture -------------------------------------------------------------------
|
|
68
|
+
const iconNames = Object.keys(set.icons).sort()
|
|
69
|
+
const aliasNames = Object.keys(set.aliases ?? {}).sort()
|
|
70
|
+
|
|
71
|
+
const iconLines = iconNames
|
|
72
|
+
.map((name) => ` ${JSON.stringify(name)}: { body: ${JSON.stringify(set.icons[name]!.body)} },`)
|
|
73
|
+
.join("\n")
|
|
74
|
+
const aliasLines = aliasNames
|
|
75
|
+
.map(
|
|
76
|
+
(name) =>
|
|
77
|
+
` ${JSON.stringify(name)}: { parent: ${JSON.stringify(set.aliases![name]!.parent)} },`,
|
|
78
|
+
)
|
|
79
|
+
.join("\n")
|
|
80
|
+
|
|
81
|
+
const sourceLabel = sources.map((src) => `\`${src}\``).join(", ")
|
|
82
|
+
const header = `// ⚠️ FICHIER GÉNÉRÉ — ne pas éditer à la main.
|
|
83
|
+
// Commande : bun scripts/gen-icon-set.ts --out ${outFile} --src ${sources.join(" --src ")}${typeImport === "./icons" ? "" : ` --type-import ${typeImport}`}
|
|
84
|
+
//
|
|
85
|
+
// Sous-ensemble de la collection \`lucide\` : ${iconNames.length} icônes${aliasNames.length ? ` + ${aliasNames.length} alias` : ""}, relevées dans
|
|
86
|
+
// ${sourceLabel}. Embarqué dans le bundle, il est enregistré auprès d'Iconify par
|
|
87
|
+
// \`registerIcons()\` (ou par le module Nuxt \`@dnax/ui\`) via \`addCollection()\` :
|
|
88
|
+
// ces icônes ne déclenchent **aucune** requête vers l'API Iconify et sont rendues
|
|
89
|
+
// dès le SSR. Les noms absents de ce fichier continuent de passer par l'API.
|
|
90
|
+
import type { IconSetJson } from "${typeImport}"
|
|
91
|
+
|
|
92
|
+
export const lucideIconSet: IconSetJson = {
|
|
93
|
+
prefix: "lucide",
|
|
94
|
+
width: ${set.width ?? 24},
|
|
95
|
+
height: ${set.height ?? 24},
|
|
96
|
+
icons: {
|
|
97
|
+
${iconLines}
|
|
98
|
+
},
|
|
99
|
+
${aliasLines ? ` aliases: {\n${aliasLines}\n },\n` : ""}}
|
|
100
|
+
`
|
|
101
|
+
|
|
102
|
+
const outPath = resolve(process.cwd(), outFile)
|
|
103
|
+
await mkdir(dirname(outPath), { recursive: true })
|
|
104
|
+
await writeFile(outPath, header)
|
|
105
|
+
|
|
106
|
+
const kb = (Buffer.byteLength(header) / 1024).toFixed(1)
|
|
107
|
+
console.log(`✓ ${iconNames.length} icône(s) + ${aliasNames.length} alias → ${outFile} (${kb} Ko)`)
|
|
108
|
+
if (missing.length) {
|
|
109
|
+
console.warn(`⚠️ ${missing.length} nom(s) introuvable(s) dans lucide : ${missing.join(", ")}`)
|
|
110
|
+
}
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Regénère `index.ts` : une ligne d'export par composant, puis le **bloc manuel**
|
|
3
|
+
* (composables, helpers, types de `lib/`) recopié **tel quel**.
|
|
3
4
|
* Run with: bun scripts/generate-exports.ts
|
|
5
|
+
*
|
|
6
|
+
* ⚠️ Ne jamais figer le bloc manuel dans ce script. Une version précédente le
|
|
7
|
+
* contenait en dur ; il avait pris du retard sur `index.ts` et une exécution a
|
|
8
|
+
* silencieusement supprimé `network`, `wakeLock`, `chartFormat`, `chartStats`,
|
|
9
|
+
* `chartTable`… (cf. `.memory/warnings.md`). Ici le bloc manuel est **relu** depuis
|
|
10
|
+
* `index.ts` : tout ce qui suit le dernier export de composant est conservé, donc le
|
|
11
|
+
* script est idempotent et ne peut plus rien perdre.
|
|
4
12
|
*/
|
|
5
|
-
import { readdir } from "node:fs/promises"
|
|
13
|
+
import { readdir, readFile } from "node:fs/promises"
|
|
6
14
|
|
|
7
15
|
const componentsDir = new URL("../components/", import.meta.url)
|
|
8
16
|
const indexFile = new URL("../index.ts", import.meta.url)
|
|
9
17
|
|
|
18
|
+
/** Ligne produite pour un composant : `export { default as QX } from "./components/QX.vue"` */
|
|
19
|
+
const COMPONENT_EXPORT_LINE = /^export \{ default as [\w$]+ \} from "\.\/components\/[\w$]+\.vue"$/
|
|
20
|
+
|
|
10
21
|
const toPascalCase = (name: string) =>
|
|
11
22
|
name
|
|
12
23
|
.split(/[-_]/)
|
|
@@ -17,78 +28,44 @@ const files = (await readdir(componentsDir))
|
|
|
17
28
|
.filter((file) => file.endsWith(".vue") && !file.startsWith("_"))
|
|
18
29
|
.sort()
|
|
19
30
|
|
|
20
|
-
const
|
|
31
|
+
const componentExports = files
|
|
21
32
|
.map(
|
|
22
33
|
(file) =>
|
|
23
34
|
`export { default as ${toPascalCase(file.replace(/\.vue$/, ""))} } from "./components/${file}"`,
|
|
24
35
|
)
|
|
25
36
|
.join("\n")
|
|
26
37
|
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
export
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
export { placePopover } from "./lib/datePicker"
|
|
58
|
-
export type {
|
|
59
|
-
PlacePopoverOptions,
|
|
60
|
-
PopoverAnchor,
|
|
61
|
-
PopoverPlacement,
|
|
62
|
-
PopoverViewport,
|
|
63
|
-
} from "./lib/datePicker"
|
|
64
|
-
export { DEFAULT_LAYOUT_VIEW, parseView, qLayoutKey } from "./lib/layout"
|
|
65
|
-
export type { LayoutView, LayoutZone, LayoutZonePlacement, QLayoutContext } from "./lib/layout"
|
|
66
|
-
export { vRipple } from "./lib/ripple"
|
|
67
|
-
export type { RippleOptions, RippleValue } from "./lib/ripple"
|
|
68
|
-
export { chartToECharts, channelValue, pickFromEvent, tableModel, textValue } from "./lib/chart"
|
|
69
|
-
export type {
|
|
70
|
-
QChartAxis,
|
|
71
|
-
QChartChannel,
|
|
72
|
-
QChartColumn,
|
|
73
|
-
QChartConfig,
|
|
74
|
-
QChartLegend,
|
|
75
|
-
QChartMark,
|
|
76
|
-
QChartMarkType,
|
|
77
|
-
QChartOrientation,
|
|
78
|
-
QChartTable,
|
|
79
|
-
QChartPick,
|
|
80
|
-
} from "./lib/chart"
|
|
81
|
-
export { vTouchPan } from "./lib/touchPan"
|
|
82
|
-
export type { PanDetails, TouchPanHandler, PanDirection } from "./lib/touchPan"
|
|
83
|
-
export { vTouchHold } from "./lib/touchHold"
|
|
84
|
-
export type { TouchHoldDetails, TouchHoldHandler } from "./lib/touchHold"
|
|
85
|
-
export { vTouchSwipe } from "./lib/touchSwipe"
|
|
86
|
-
export type { TouchSwipeDetails, TouchSwipeHandler, SwipeDirection } from "./lib/touchSwipe"
|
|
87
|
-
export { vTouchRepeat } from "./lib/touchRepeat"
|
|
88
|
-
export type { TouchRepeatDetails, TouchRepeatHandler } from "./lib/touchRepeat"
|
|
89
|
-
export { vIntersection } from "./lib/intersection"
|
|
90
|
-
export type { IntersectionValue } from "./lib/intersection"
|
|
91
|
-
`
|
|
38
|
+
// — Bloc manuel : relu dans l'`index.ts` courant -------------------------------
|
|
39
|
+
let current = ""
|
|
40
|
+
try {
|
|
41
|
+
current = await readFile(indexFile, "utf8")
|
|
42
|
+
} catch {
|
|
43
|
+
throw new Error(
|
|
44
|
+
"packages/ui/index.ts est introuvable : le bloc manuel (exports de `lib/`) ne peut pas être conservé. " +
|
|
45
|
+
"Recréez le fichier avec ses exports manuels avant de lancer ce script.",
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const lines = current.split("\n")
|
|
50
|
+
let lastComponentLine = -1
|
|
51
|
+
lines.forEach((line, i) => {
|
|
52
|
+
if (COMPONENT_EXPORT_LINE.test(line)) lastComponentLine = i
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// Fichier sans aucun export de composant (création manuelle) : tout est conservé.
|
|
56
|
+
const manualExports = (lastComponentLine === -1 ? current : lines.slice(lastComponentLine + 1).join("\n"))
|
|
57
|
+
.trim()
|
|
58
|
+
|
|
59
|
+
if (lastComponentLine === -1 && /\bexport\b/.test(manualExports)) {
|
|
60
|
+
console.warn(
|
|
61
|
+
"⚠️ Aucun export de composant reconnu dans index.ts : tout son contenu est traité comme bloc manuel.",
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const output = manualExports ? `${componentExports}\n\n${manualExports}\n` : `${componentExports}\n`
|
|
66
|
+
|
|
67
|
+
await Bun.write(indexFile, output)
|
|
92
68
|
|
|
93
|
-
|
|
69
|
+
const kept = manualExports ? manualExports.split("\n").length : 0
|
|
94
70
|
console.log(`✓ ${files.length} composant(s) exporté(s) dans index.ts`)
|
|
71
|
+
console.log(`✓ ${kept} ligne(s) d'exports manuels conservées (bloc relu depuis index.ts)`)
|
package/styles/main.css
CHANGED
|
@@ -439,6 +439,7 @@ html.dark body {
|
|
|
439
439
|
}
|
|
440
440
|
|
|
441
441
|
.dark .q-input .q-field__native::placeholder,
|
|
442
|
+
.dark .q-input-textarea .q-field__native::placeholder,
|
|
442
443
|
.dark .q-input-tag__native::placeholder,
|
|
443
444
|
.dark .q-input-otp__box::placeholder,
|
|
444
445
|
.dark .q-select__search-input::placeholder,
|
|
@@ -2358,6 +2359,61 @@ html.dark body {
|
|
|
2358
2359
|
margin-left: auto;
|
|
2359
2360
|
}
|
|
2360
2361
|
|
|
2362
|
+
/* ===== QInputTextarea (champ multiligne) ===== */
|
|
2363
|
+
.q-input-textarea {
|
|
2364
|
+
display: flex;
|
|
2365
|
+
flex-direction: column;
|
|
2366
|
+
min-width: 0;
|
|
2367
|
+
font-size: 14px;
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
/* Le contenu est ancré **en haut** du contrôle (un champ simple centre sa valeur) :
|
|
2371
|
+
`rows` doit correspondre à ce qu'on voit, et les icônes / affixes restent sur la
|
|
2372
|
+
première ligne quand le champ grandit. 10px de padding = 40px de contrôle pour
|
|
2373
|
+
1 ligne de 20px, soit la hauteur exacte du champ simple. */
|
|
2374
|
+
.q-input-textarea .q-field__control {
|
|
2375
|
+
align-items: flex-start;
|
|
2376
|
+
padding: 10px 12px;
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
.q-input-textarea .q-field__native {
|
|
2380
|
+
flex: 1;
|
|
2381
|
+
width: 100%;
|
|
2382
|
+
min-width: 0;
|
|
2383
|
+
height: auto;
|
|
2384
|
+
padding: 0;
|
|
2385
|
+
border: none;
|
|
2386
|
+
outline: none;
|
|
2387
|
+
background: transparent;
|
|
2388
|
+
color: inherit;
|
|
2389
|
+
font: inherit;
|
|
2390
|
+
/* `rows` × line-height = hauteur visible ; l'auto-extension mesure la même valeur */
|
|
2391
|
+
line-height: 20px;
|
|
2392
|
+
resize: none;
|
|
2393
|
+
overflow-y: hidden;
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2396
|
+
.q-input-textarea .q-field__native::placeholder {
|
|
2397
|
+
color: rgb(0 0 0 / 0.45);
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
/* Affixes, icônes et bouton d'effacement : sur la première ligne */
|
|
2401
|
+
.q-input-textarea .q-field__icon,
|
|
2402
|
+
.q-input-textarea .q-field__prefix,
|
|
2403
|
+
.q-input-textarea .q-field__suffix,
|
|
2404
|
+
.q-input-textarea .q-field__clear {
|
|
2405
|
+
align-self: flex-start;
|
|
2406
|
+
line-height: 20px;
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
.q-field--dense.q-input-textarea .q-field__control {
|
|
2410
|
+
padding: 7px 10px;
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
.q-field--dense.q-input-textarea .q-field__native {
|
|
2414
|
+
line-height: 18px;
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2361
2417
|
/* ===== QInputPassword (toggle œil) ===== */
|
|
2362
2418
|
.q-input-password__toggle {
|
|
2363
2419
|
display: inline-flex;
|
|
@@ -2916,6 +2972,11 @@ html.dark body {
|
|
|
2916
2972
|
border-radius: var(--q-radius);
|
|
2917
2973
|
}
|
|
2918
2974
|
|
|
2975
|
+
/* `radius` (booléen) : forme pilule, cf. JSDoc de la prop. */
|
|
2976
|
+
.q-img--rounded {
|
|
2977
|
+
border-radius: 9999px;
|
|
2978
|
+
}
|
|
2979
|
+
|
|
2919
2980
|
.q-img__image {
|
|
2920
2981
|
position: absolute;
|
|
2921
2982
|
inset: 0;
|
|
@@ -9198,6 +9259,13 @@ a.q-breadcrumbs__el:hover {
|
|
|
9198
9259
|
.q-input-otp__box--error {
|
|
9199
9260
|
border-color: var(--negative);
|
|
9200
9261
|
}
|
|
9262
|
+
|
|
9263
|
+
/* `radius` (booléen) : forme pilule — même intention que `.q-field--rounded` (20px).
|
|
9264
|
+
Les cases font 48px : 20px les rend nettement arrondies sans les transformer en
|
|
9265
|
+
cercles, ce qui laisserait des trous entre les caractères. */
|
|
9266
|
+
.q-input-otp--rounded .q-input-otp__box {
|
|
9267
|
+
border-radius: 20px;
|
|
9268
|
+
}
|
|
9201
9269
|
.q-input-otp__box--error:focus {
|
|
9202
9270
|
border-color: var(--negative);
|
|
9203
9271
|
box-shadow: 0 0 0 1px var(--negative);
|