@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.
@@ -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
- * Regenerates `index.ts` with one explicit export per component.
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 exports = files
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
- // Exports manuels (composables / helpers) — conservés à chaque régénération
28
- const manualExports = `
29
- export { $q, usePlugin, useQ, QPlugin, dialogStack, closeDialog, bottomSheetStack, closeBottomSheet, useDialogPluginComponent, useBottomSheetPluginComponent } from "./lib/q"
30
- export type {
31
- DialogOptions,
32
- DialogController,
33
- BottomSheetOptions,
34
- BottomSheetController,
35
- BottomSheetPluginContext,
36
- NotifyOptions,
37
- NotifyController,
38
- } from "./lib/q"
39
- export { platform, qBreakpoints } from "./lib/platform"
40
- export type { QPlatform } from "./lib/platform"
41
- export { screen } from "./lib/screen"
42
- export type { QScreen } from "./lib/screen"
43
- export { loading } from "./lib/loading"
44
- export type { LoadingOptions, QLoadingPlugin } from "./lib/loading"
45
- export { localStorage, sessionStorage } from "./lib/storage"
46
- export type { QWebStorage } from "./lib/storage"
47
- export {
48
- registerOverlay,
49
- unregisterOverlay,
50
- hasOpenOverlays,
51
- closeTopmostOverlay,
52
- useOverlayBack,
53
- installOverlayBackHandler,
54
- } from "./lib/overlayBack"
55
- export type { OverlayHandle } from "./lib/overlayBack"
56
- export { markOverlayClose, closeParentOverlay, vClose } from "./lib/closeOverlay"
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
- await Bun.write(indexFile, `${exports}\n${manualExports}`)
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
@@ -2972,6 +2972,11 @@ html.dark body {
2972
2972
  border-radius: var(--q-radius);
2973
2973
  }
2974
2974
 
2975
+ /* `radius` (booléen) : forme pilule, cf. JSDoc de la prop. */
2976
+ .q-img--rounded {
2977
+ border-radius: 9999px;
2978
+ }
2979
+
2975
2980
  .q-img__image {
2976
2981
  position: absolute;
2977
2982
  inset: 0;
@@ -6877,6 +6882,11 @@ a.q-breadcrumbs__el:hover {
6877
6882
  background-color: rgb(0 0 0 / 0.6);
6878
6883
  color: #fff;
6879
6884
  }
6885
+ /* Thème sombre : le voile blanc codé en dur éblouit (même convention que les champs) */
6886
+ .dark .q-inner-loading {
6887
+ background-color: rgb(0 0 0 / 0.55);
6888
+ color: var(--foreground);
6889
+ }
6880
6890
 
6881
6891
  .q-inner-loading__icon {
6882
6892
  animation: q-btn-spin 0.7s linear infinite;
@@ -9254,6 +9264,13 @@ a.q-breadcrumbs__el:hover {
9254
9264
  .q-input-otp__box--error {
9255
9265
  border-color: var(--negative);
9256
9266
  }
9267
+
9268
+ /* `radius` (booléen) : forme pilule — même intention que `.q-field--rounded` (20px).
9269
+ Les cases font 48px : 20px les rend nettement arrondies sans les transformer en
9270
+ cercles, ce qui laisserait des trous entre les caractères. */
9271
+ .q-input-otp--rounded .q-input-otp__box {
9272
+ border-radius: 20px;
9273
+ }
9257
9274
  .q-input-otp__box--error:focus {
9258
9275
  border-color: var(--negative);
9259
9276
  box-shadow: 0 0 0 1px var(--negative);
@@ -9625,6 +9642,20 @@ a.q-breadcrumbs__el:hover {
9625
9642
  overflow: auto;
9626
9643
  outline: none;
9627
9644
  }
9645
+
9646
+ /* Chargement distant : spinner du design system, teinté par le thème */
9647
+ .q-spreadsheet__loader {
9648
+ width: 30px;
9649
+ height: 30px;
9650
+ border-width: 3px;
9651
+ color: var(--primary, #1976d2);
9652
+ }
9653
+
9654
+ /* Le voile passe au-dessus de l'éditeur (z-index 20) et de la poignée de remplissage :
9655
+ on ne doit pas éditer une page en cours de remplacement. */
9656
+ .q-spreadsheet > .q-inner-loading {
9657
+ z-index: 40;
9658
+ }
9628
9659
  .q-spreadsheet__table {
9629
9660
  /* PAS de `min-width: 100%` : avec `table-layout: fixed`, un tableau étiré au-delà
9630
9661
  de la somme de ses colonnes RÉPARTIT l'espace libre sur toutes les colonnes
@@ -9636,7 +9667,8 @@ a.q-breadcrumbs__el:hover {
9636
9667
  border-collapse: separate;
9637
9668
  border-spacing: 0;
9638
9669
  table-layout: fixed;
9639
- font-size: 13px;
9670
+ /* Suit `sizeCell` (la police grandit avec la cellule) ; 13px = taille historique */
9671
+ font-size: var(--q-spreadsheet-font, 13px);
9640
9672
  }
9641
9673
 
9642
9674
  /* Grille sans aucune colonne : un seul message → il doit occuper toute la largeur */
@@ -9856,6 +9888,132 @@ a.q-breadcrumbs__el:hover {
9856
9888
  vertical-align: middle;
9857
9889
  }
9858
9890
 
9891
+ /* ─── Fichiers joints (type `attachment`) ─── */
9892
+ /* Une « puce » par fichier : vignette (image) ou icône typée, nom tronqué, bouton de retrait.
9893
+ Les puces passent à la ligne dans une cellule étroite, comme les badges. */
9894
+ .q-spreadsheet__attachments {
9895
+ display: inline-flex;
9896
+ flex-wrap: wrap;
9897
+ align-items: center;
9898
+ gap: 4px;
9899
+ max-width: 100%;
9900
+ vertical-align: middle;
9901
+ }
9902
+
9903
+ .q-spreadsheet__attachment {
9904
+ display: inline-flex;
9905
+ align-items: center;
9906
+ gap: 4px;
9907
+ max-width: 100%;
9908
+ padding: 1px 4px 1px 2px;
9909
+ border: 1px solid rgb(0 0 0 / 0.12);
9910
+ border-radius: 999px;
9911
+ background: rgb(0 0 0 / 0.04);
9912
+ font-size: 11.5px;
9913
+ line-height: 1.6;
9914
+ white-space: nowrap;
9915
+ /* La puce est cliquable : elle émet `attachment-click` (aperçu côté app) */
9916
+ cursor: pointer;
9917
+ }
9918
+ .q-spreadsheet__attachment:hover {
9919
+ border-color: rgb(0 0 0 / 0.24);
9920
+ background: rgb(0 0 0 / 0.08);
9921
+ }
9922
+
9923
+ .q-spreadsheet__attachment-thumb {
9924
+ width: 18px;
9925
+ height: 18px;
9926
+ flex: 0 0 auto;
9927
+ border-radius: 4px;
9928
+ object-fit: cover;
9929
+ }
9930
+
9931
+ .q-spreadsheet__attachment-icon {
9932
+ width: 14px;
9933
+ height: 14px;
9934
+ flex: 0 0 auto;
9935
+ color: rgb(0 0 0 / 0.55);
9936
+ }
9937
+
9938
+ .q-spreadsheet__attachment-name {
9939
+ max-width: 140px;
9940
+ overflow: hidden;
9941
+ text-overflow: ellipsis;
9942
+ }
9943
+
9944
+ .q-spreadsheet__attachment-x {
9945
+ display: inline-flex;
9946
+ align-items: center;
9947
+ justify-content: center;
9948
+ width: 14px;
9949
+ height: 14px;
9950
+ padding: 0;
9951
+ border: none;
9952
+ border-radius: 50%;
9953
+ background: transparent;
9954
+ color: rgb(0 0 0 / 0.45);
9955
+ cursor: pointer;
9956
+ flex: 0 0 auto;
9957
+ }
9958
+ .q-spreadsheet__attachment-x:hover {
9959
+ background: rgb(0 0 0 / 0.12);
9960
+ color: var(--negative, #c10015);
9961
+ }
9962
+ .q-spreadsheet__attachment-x svg {
9963
+ width: 10px;
9964
+ height: 10px;
9965
+ }
9966
+
9967
+ /* Bouton d'ajout : discret, visible dès qu'on survole / sélectionne la cellule */
9968
+ .q-spreadsheet__attachment-add {
9969
+ display: inline-flex;
9970
+ align-items: center;
9971
+ justify-content: center;
9972
+ width: 18px;
9973
+ height: 18px;
9974
+ padding: 0;
9975
+ border: 1px dashed rgb(0 0 0 / 0.25);
9976
+ border-radius: 50%;
9977
+ background: transparent;
9978
+ color: rgb(0 0 0 / 0.45);
9979
+ cursor: pointer;
9980
+ flex: 0 0 auto;
9981
+ }
9982
+ .q-spreadsheet__attachment-add:hover {
9983
+ border-color: var(--primary, #1976d2);
9984
+ color: var(--primary, #1976d2);
9985
+ }
9986
+ .q-spreadsheet__attachment-add svg {
9987
+ width: 10px;
9988
+ height: 10px;
9989
+ }
9990
+
9991
+ /* Input `type=file` masqué (piloté par `openAttachments`) */
9992
+ .q-spreadsheet__attach-input {
9993
+ position: absolute;
9994
+ width: 0;
9995
+ height: 0;
9996
+ opacity: 0;
9997
+ pointer-events: none;
9998
+ }
9999
+
10000
+ .dark .q-spreadsheet__attachment {
10001
+ border-color: rgb(255 255 255 / 0.16);
10002
+ background: rgb(255 255 255 / 0.06);
10003
+ }
10004
+ .dark .q-spreadsheet__attachment:hover {
10005
+ border-color: rgb(255 255 255 / 0.3);
10006
+ background: rgb(255 255 255 / 0.11);
10007
+ }
10008
+ .dark .q-spreadsheet__attachment-icon,
10009
+ .dark .q-spreadsheet__attachment-x,
10010
+ .dark .q-spreadsheet__attachment-add {
10011
+ color: rgb(255 255 255 / 0.6);
10012
+ }
10013
+ .dark .q-spreadsheet__attachment-add {
10014
+ border-color: rgb(255 255 255 / 0.24);
10015
+ }
10016
+
9859
10017
  /* ─── Éditeur overlay ─── */
9860
10018
  .q-spreadsheet__editor {
9861
10019
  position: absolute;
@@ -10746,6 +10904,38 @@ a.q-breadcrumbs__el:hover {
10746
10904
  .q-spreadsheet__status-spacer {
10747
10905
  flex: 1;
10748
10906
  }
10907
+
10908
+ /* Pagination distante : ‹ lignes 401–800 sur 12 000 · 2 / 30 › */
10909
+ .q-spreadsheet__pager {
10910
+ display: inline-flex;
10911
+ align-items: center;
10912
+ gap: 4px;
10913
+ padding-right: 8px;
10914
+ border-right: 1px solid rgb(0 0 0 / 0.1);
10915
+ margin-right: 2px;
10916
+ }
10917
+ .q-spreadsheet__pager .q-spreadsheet__tool {
10918
+ width: 22px;
10919
+ height: 20px;
10920
+ }
10921
+ .q-spreadsheet__pager-range {
10922
+ font-variant-numeric: tabular-nums;
10923
+ white-space: nowrap;
10924
+ }
10925
+ .q-spreadsheet__pager-page {
10926
+ padding: 0 6px;
10927
+ border-radius: 999px;
10928
+ background: rgb(0 0 0 / 0.06);
10929
+ font-weight: 600;
10930
+ font-variant-numeric: tabular-nums;
10931
+ white-space: nowrap;
10932
+ }
10933
+ .dark .q-spreadsheet__pager {
10934
+ border-right-color: rgb(255 255 255 / 0.14);
10935
+ }
10936
+ .dark .q-spreadsheet__pager-page {
10937
+ background: rgb(255 255 255 / 0.12);
10938
+ }
10749
10939
  .q-spreadsheet__status-zoom {
10750
10940
  display: inline-flex;
10751
10941
  align-items: center;