@dnax/ui 0.0.52 → 0.0.54
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/QConfigProvider.vue +40 -29
- package/components/QSelect.vue +10 -4
- package/lib/config.ts +8 -0
- package/lib/datePicker.ts +1 -1
- package/lib/qrcode.ts +26 -9
- package/lib/select.test.ts +42 -0
- package/lib/select.ts +29 -0
- package/lib/themeVars.test.ts +42 -0
- package/lib/themeVars.ts +32 -14
- package/module.ts +11 -0
- package/package.json +4 -3
- package/runtime.ts +48 -0
- package/shims.d.ts +6 -12
- package/styles/main.css +19 -8
|
@@ -13,7 +13,7 @@ import QBottomSheetProvider from "./QBottomSheetProvider.vue"
|
|
|
13
13
|
import QImagePreviewProvider from "./QImagePreviewProvider.vue"
|
|
14
14
|
import { qConfigKey, qProvidersKey } from "../lib/config"
|
|
15
15
|
import type { QAppLang, QConfigContext, QTheme, ThemeMode } from "../lib/config"
|
|
16
|
-
import {
|
|
16
|
+
import { themeVars } from "../lib/themeVars"
|
|
17
17
|
|
|
18
18
|
interface Props {
|
|
19
19
|
/**
|
|
@@ -119,40 +119,51 @@ watch(
|
|
|
119
119
|
|
|
120
120
|
provide<QConfigContext>(qConfigKey, { theme: mergedTheme, isDark, lang })
|
|
121
121
|
|
|
122
|
+
// — Dev : `componentProps` n'est PAS un spread de props — une clé non lue est ignorée
|
|
123
|
+
// silencieusement (cf. `.memory/knowledges.md`). On le signale, une fois par clé.
|
|
124
|
+
/** Clés de `componentProps.<Nom>` réellement lues, en plus du `radius` générique. */
|
|
125
|
+
const EXTRA_COMPONENT_PROP_KEYS: Record<string, readonly string[]> = { QMap: ["apiKey"] }
|
|
126
|
+
|
|
127
|
+
const IS_DEV = !!((import.meta as any).env?.DEV ?? (import.meta as any).dev)
|
|
128
|
+
if (IS_DEV) {
|
|
129
|
+
const warned = new Set<string>()
|
|
130
|
+
watch(
|
|
131
|
+
mergedTheme,
|
|
132
|
+
(theme) => {
|
|
133
|
+
if (typeof document === "undefined") return // pas de bruit au SSR/build
|
|
134
|
+
for (const [name, entry] of Object.entries(theme.componentProps ?? {})) {
|
|
135
|
+
if (!entry || typeof entry !== "object") continue
|
|
136
|
+
const allowed = new Set<string>(["radius", ...(EXTRA_COMPONENT_PROP_KEYS[name] ?? [])])
|
|
137
|
+
const ignored = Object.keys(entry).filter((key) => !allowed.has(key))
|
|
138
|
+
if (!ignored.length) continue
|
|
139
|
+
const signature = `${name}:${ignored.join(",")}`
|
|
140
|
+
if (warned.has(signature)) continue
|
|
141
|
+
warned.add(signature)
|
|
142
|
+
console.warn(
|
|
143
|
+
`[dnax/ui] componentProps.${name} : clé(s) ignorée(s) → ${ignored.map((k) => `"${k}"`).join(", ")}.\n` +
|
|
144
|
+
` Ces clés sont lues pour ${name} : ${[...allowed].map((k) => `"${k}"`).join(", ")}.\n` +
|
|
145
|
+
` Pour une couleur/un fond, passez par theme.vars (ex. { "--q-field-bg": "#eef7ee" }) ou du CSS.`,
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
{ immediate: true, deep: true },
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
122
153
|
// Providers intégrés ($q.dialog + $q.notify) : rendus UNE fois par le
|
|
123
154
|
// QConfigProvider le plus externe (les imbriqués ne re-rendent pas → pas de doublons)
|
|
124
155
|
const hasProviders = inject(qProvidersKey, false)
|
|
125
156
|
provide(qProvidersKey, true)
|
|
126
157
|
const isProvidersRoot = computed(() => !hasProviders)
|
|
127
158
|
|
|
128
|
-
/**
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const luminance = 0.299 * r + 0.587 * g + 0.114 * b
|
|
137
|
-
return luminance > 150 ? "#1d1d1d" : "#ffffff"
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const themeStyle = computed<Record<string, string>>(() => {
|
|
141
|
-
const style: Record<string, string> = {
|
|
142
|
-
"color-scheme": isDark.value ? "dark" : "light",
|
|
143
|
-
}
|
|
144
|
-
for (const [token, value] of Object.entries(mergedTheme.value.colors ?? {})) {
|
|
145
|
-
if (!value) continue
|
|
146
|
-
style[`--${token}`] = value
|
|
147
|
-
style[`--${token}-foreground`] = textColorFor(value)
|
|
148
|
-
}
|
|
149
|
-
// Arrondi global : composantProps.default.radius → --q-radius hérité par TOUS
|
|
150
|
-
// les composants dont le CSS utilise var(--q-radius). Une prop/override
|
|
151
|
-
// spécifique (useRadius) pose son propre --q-radius inline et prime.
|
|
152
|
-
const global = mergedTheme.value.componentProps?.default?.radius
|
|
153
|
-
if (isRadiusScale(global)) style["--q-radius"] = RADIUS_VALUES[global]
|
|
154
|
-
return style
|
|
155
|
-
})
|
|
159
|
+
/**
|
|
160
|
+
* Variables CSS du thème : `color-scheme` + couleurs, `--q-radius` et `vars` libres
|
|
161
|
+
* (logique partagée avec les providers d'overlays téléportés, cf. `themeVars`).
|
|
162
|
+
*/
|
|
163
|
+
const themeStyle = computed<Record<string, string>>(() => ({
|
|
164
|
+
"color-scheme": isDark.value ? "dark" : "light",
|
|
165
|
+
...themeVars(mergedTheme.value),
|
|
166
|
+
}))
|
|
156
167
|
|
|
157
168
|
// Thème GLOBAL (provider racine) : pose aussi les variables sur <html> — les
|
|
158
169
|
// overlays téléportés au body (dialogs $q.dialog, …) perdent l'héritage du div
|
package/components/QSelect.vue
CHANGED
|
@@ -60,6 +60,7 @@ import { radiusStyle, radiusValue, useRadius } from "../lib/useComponentProps"
|
|
|
60
60
|
import type { RadiusProp } from "../lib/useComponentProps"
|
|
61
61
|
import { useOverlayBack } from "../lib/overlayBack"
|
|
62
62
|
import { createSearcher } from "../lib/search"
|
|
63
|
+
import { optionsOf, selectionOf } from "../lib/select"
|
|
63
64
|
|
|
64
65
|
interface Props {
|
|
65
66
|
/** Valeur sélectionnée : option, valeur (emit-value), ou tableau (multiple) */
|
|
@@ -337,7 +338,9 @@ watch([popupPlacement, popupOffset], onPopupViewportChange)
|
|
|
337
338
|
const isObjectOption = (o: any): boolean => o !== null && typeof o === "object"
|
|
338
339
|
|
|
339
340
|
const normalizedOptions = computed<any[]>(() =>
|
|
340
|
-
|
|
341
|
+
// `options` peut arriver `false` (v-if/&&, config non chargée) : sans garde,
|
|
342
|
+
// `props.options.map` jetait et l'option par défaut ne s'affichait plus.
|
|
343
|
+
optionsOf(props.options).map((o) => {
|
|
341
344
|
if (isObjectOption(o)) return o
|
|
342
345
|
// Objet normalisé : garde la valeur d'origine (non-énumérable → invisible
|
|
343
346
|
// pour l'affichage, la recherche fuse et les clés de rendu)
|
|
@@ -361,8 +364,9 @@ const getOptionLabel = (opt: any): string => {
|
|
|
361
364
|
|
|
362
365
|
// — Sélection (dérivée du modèle) —
|
|
363
366
|
const selectedOptions = computed<any[]>(() => {
|
|
364
|
-
|
|
365
|
-
|
|
367
|
+
// En `multiple`, un modèle scalaire (ex. `false`) ne doit PAS faire jeter `list.map`
|
|
368
|
+
// (tout le popup cessait de se rendre) : `selectionOf` le traite comme vide.
|
|
369
|
+
const list = selectionOf(props.modelValue, props.multiple)
|
|
366
370
|
return list
|
|
367
371
|
.map((v) => {
|
|
368
372
|
if (props.emitValue) return normalizedOptions.value.find((o) => getOptionValue(o) === v)
|
|
@@ -417,7 +421,9 @@ const clear = () => {
|
|
|
417
421
|
}
|
|
418
422
|
|
|
419
423
|
const hasValue = computed(() =>
|
|
420
|
-
props.multiple
|
|
424
|
+
props.multiple
|
|
425
|
+
? Array.isArray(props.modelValue) && props.modelValue.length > 0
|
|
426
|
+
: props.modelValue !== undefined && props.modelValue !== null,
|
|
421
427
|
)
|
|
422
428
|
|
|
423
429
|
// — Recherche floue (fuse.js) —
|
package/lib/config.ts
CHANGED
|
@@ -24,6 +24,14 @@ export interface QTheme {
|
|
|
24
24
|
lang?: QAppLang
|
|
25
25
|
/** Props par défaut par composant (clé = nom du composant, ex. "QBtn") */
|
|
26
26
|
componentProps?: Record<string, Record<string, unknown>>
|
|
27
|
+
/**
|
|
28
|
+
* Variables CSS libres posées avec le thème, puis héritées par tout le sous-arbre
|
|
29
|
+
* (overlays téléportés compris : dialogs, bottom sheets). Clé avec ou sans `--`
|
|
30
|
+
* (ex. `{ "q-field-bg": "#eef7ee" }`). Appliquées **en dernier**, elles peuvent donc
|
|
31
|
+
* surcharger les tokens de `colors`. Utile pour ce qu'aucun token ne couvre
|
|
32
|
+
* (ex. le fond des champs, cf. `--q-field-bg`).
|
|
33
|
+
*/
|
|
34
|
+
vars?: Record<string, string>
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
export interface QConfigContext {
|
package/lib/datePicker.ts
CHANGED
|
@@ -16,7 +16,7 @@ export const POPOVER_GAP = 8
|
|
|
16
16
|
export const POPOVER_MIN_SPACE = 220
|
|
17
17
|
|
|
18
18
|
/** Largeur de repli tant que le panneau n'a pas été mesuré (1er rendu) */
|
|
19
|
-
export const POPOVER_FALLBACK_WIDTH =
|
|
19
|
+
export const POPOVER_FALLBACK_WIDTH = 300
|
|
20
20
|
|
|
21
21
|
/** Marge minimale de la flèche avec les coins du panneau */
|
|
22
22
|
export const POPOVER_CARET_INSET = 16
|
package/lib/qrcode.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
// qrcode — encodage QR et tracé SVG. Le moteur d'encodage est
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
1
|
+
// qrcode — encodage QR et tracé SVG. Le moteur d'encodage est `uqr` (ESM pur, sans
|
|
2
|
+
// dépendance) : il fournit la matrice de modules, ce module la traduit en **chemin SVG**
|
|
3
|
+
// (une suite de rectangles horizontaux, bien plus compact qu'un carré par module) — d'où
|
|
4
|
+
// un rendu net à toutes les tailles, thémable et rendable en SSR.
|
|
5
|
+
//
|
|
6
|
+
// `uqr` est **ESM** (contrairement à l'ancien `qrcode`, CJS sans `default`, qui cassait le
|
|
7
|
+
// bundle client — cf. `.memory/warnings.md`) : aucun interop esbuild, aucun `optimizeDeps`.
|
|
6
8
|
//
|
|
7
9
|
// Pur (aucun DOM) → testable hors navigateur, comme `lib/pagePadding.ts`.
|
|
8
|
-
import
|
|
10
|
+
import { encode } from "uqr"
|
|
9
11
|
|
|
10
12
|
/** Niveau de correction d'erreur : L (~7 %) → H (~30 %) */
|
|
11
13
|
export type QrEcc = "L" | "M" | "Q" | "H"
|
|
@@ -39,10 +41,25 @@ export function encodeQr(value: string, options: QrEncodeOptions = {}): QrMatrix
|
|
|
39
41
|
if (typeof value !== "string" || value === "") return undefined
|
|
40
42
|
|
|
41
43
|
try {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
+
const { size, data } = encode(value, {
|
|
45
|
+
ecc: options.ecc ?? "M",
|
|
46
|
+
// La zone de silence est gérée par dnax.ui (`margin`) : on demande la matrice nue.
|
|
47
|
+
border: 0,
|
|
48
|
+
// `uqr` raisonne en bornes de version, pas en version forcée.
|
|
49
|
+
...(typeof options.version === "number"
|
|
50
|
+
? { minVersion: options.version, maxVersion: options.version }
|
|
51
|
+
: {}),
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
// `uqr` renvoie un `boolean[][]` ; on l'aplatit au contrat `data[y * size + x]`
|
|
55
|
+
// (1 = module sombre) attendu par `isDark` et le tracé.
|
|
56
|
+
const flat = new Uint8Array(size * size)
|
|
57
|
+
let i = 0
|
|
58
|
+
for (const row of data) {
|
|
59
|
+
for (let x = 0; x < size; x++) flat[i++] = row[x] ? 1 : 0
|
|
60
|
+
}
|
|
44
61
|
|
|
45
|
-
return
|
|
62
|
+
return { size, data: flat }
|
|
46
63
|
} catch {
|
|
47
64
|
return undefined
|
|
48
65
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Tests unitaires des coercitions défensives de QSelect (`optionsOf` / `selectionOf`).
|
|
2
|
+
// Régression : `multiple` + modèle scalaire (`false`) faisait jeter `list.map` et
|
|
3
|
+
// empêchait le popup entier de se rendre.
|
|
4
|
+
import { describe, expect, it } from "bun:test"
|
|
5
|
+
import { optionsOf, selectionOf } from "./select"
|
|
6
|
+
|
|
7
|
+
describe("optionsOf", () => {
|
|
8
|
+
it("laisse passer un tableau (même vide)", () => {
|
|
9
|
+
expect(optionsOf(["a", "b"])).toEqual(["a", "b"])
|
|
10
|
+
expect(optionsOf([])).toEqual([])
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it("traite toute valeur non-tableau comme vide (au lieu de planter)", () => {
|
|
14
|
+
expect(optionsOf(false)).toEqual([])
|
|
15
|
+
expect(optionsOf(undefined)).toEqual([])
|
|
16
|
+
expect(optionsOf(null)).toEqual([])
|
|
17
|
+
expect(optionsOf("oops")).toEqual([])
|
|
18
|
+
expect(optionsOf({ value: 1 })).toEqual([])
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
describe("selectionOf", () => {
|
|
23
|
+
it("multiple : accepte un tableau", () => {
|
|
24
|
+
expect(selectionOf(["a", "b"], true)).toEqual(["a", "b"])
|
|
25
|
+
expect(selectionOf([], true)).toEqual([])
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it("multiple + scalaire → rien de sélectionné (le bug d'origine : `false`)", () => {
|
|
29
|
+
expect(selectionOf(false, true)).toEqual([])
|
|
30
|
+
expect(selectionOf(true, true)).toEqual([])
|
|
31
|
+
expect(selectionOf("a", true)).toEqual([])
|
|
32
|
+
expect(selectionOf(0, true)).toEqual([])
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it("simple : emballe la valeur, sauf null/undefined", () => {
|
|
36
|
+
expect(selectionOf("a", false)).toEqual(["a"])
|
|
37
|
+
expect(selectionOf(0, false)).toEqual([0])
|
|
38
|
+
expect(selectionOf(false, false)).toEqual([false])
|
|
39
|
+
expect(selectionOf(null, false)).toEqual([])
|
|
40
|
+
expect(selectionOf(undefined, false)).toEqual([])
|
|
41
|
+
})
|
|
42
|
+
})
|
package/lib/select.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Coercitions défensives de QSelect (`lib/select.ts`).
|
|
2
|
+
//
|
|
3
|
+
// Un `v-model` ou des `options` mal formés ne doivent **jamais** casser le rendu : un
|
|
4
|
+
// `list.map is not a function` dans un `computed` du template tue tout le popup, sans
|
|
5
|
+
// message utile. Cas réel : `<q-select multiple v-model="x">` avec `x` initialisé à
|
|
6
|
+
// `false` (défaut d'un `ref` booléen, retour d'un `&&`, config non chargée) — le garde
|
|
7
|
+
// `=== null`/`=== undefined` ne l'attrape pas, puis `false.map(...)` jette.
|
|
8
|
+
//
|
|
9
|
+
// Pur (aucun DOM) → testable hors navigateur, comme `lib/pagePadding.ts`.
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Tableau des options. `options` doit être un tableau : toute autre valeur (ex. `false`,
|
|
13
|
+
* `0`, un objet isolé) est traitée comme **vide** plutôt que de faire planter
|
|
14
|
+
* `normalizedOptions`.
|
|
15
|
+
*/
|
|
16
|
+
export const optionsOf = (value: unknown): any[] => (Array.isArray(value) ? value : [])
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Tableau de sélection dérivé du `v-model` :
|
|
20
|
+
* - `null` / `undefined` → rien ;
|
|
21
|
+
* - `multiple` → le modèle **doit** être un tableau ; une valeur scalaire (ex. `false`)
|
|
22
|
+
* vaut « rien de sélectionné » (et non une sélection de cette valeur) ;
|
|
23
|
+
* - mode simple → on emballe la valeur.
|
|
24
|
+
*/
|
|
25
|
+
export const selectionOf = (value: unknown, multiple: boolean): any[] => {
|
|
26
|
+
if (value === undefined || value === null) return []
|
|
27
|
+
if (multiple) return Array.isArray(value) ? value : []
|
|
28
|
+
return [value]
|
|
29
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Tests unitaires des variables CSS du thème (`themeVars`) — partagées par
|
|
2
|
+
// QConfigProvider et les providers d'overlays téléportés.
|
|
3
|
+
import { describe, expect, it } from "bun:test"
|
|
4
|
+
import { themeVars } from "./themeVars"
|
|
5
|
+
|
|
6
|
+
describe("themeVars", () => {
|
|
7
|
+
it("expose les couleurs en `--token` + `--token-foreground`", () => {
|
|
8
|
+
const style = themeVars({ colors: { primary: "#1976d2" } })
|
|
9
|
+
expect(style["--primary"]).toBe("#1976d2")
|
|
10
|
+
// foreground lisible calculé (clair sur fond sombre)
|
|
11
|
+
expect(style["--primary-foreground"]).toBe("#ffffff")
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it("traduit `componentProps.default.radius` en `--q-radius`", () => {
|
|
15
|
+
expect(themeVars({ componentProps: { default: { radius: "md" } } })["--q-radius"]).toBe("8px")
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it("ignore un `default.radius` non-échelle (`true` = forme native)", () => {
|
|
19
|
+
expect(themeVars({ componentProps: { default: { radius: true } } })["--q-radius"]).toBeUndefined()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it("pose les `vars` libres, avec ou sans préfixe `--`", () => {
|
|
23
|
+
const style = themeVars({ vars: { "--q-field-bg": "#eef7ee", "q-field-bg-filled": "red" } })
|
|
24
|
+
expect(style["--q-field-bg"]).toBe("#eef7ee")
|
|
25
|
+
expect(style["--q-field-bg-filled"]).toBe("red")
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it("donne le dernier mot aux `vars` sur les tokens de `colors`", () => {
|
|
29
|
+
const style = themeVars({ colors: { primary: "#111" }, vars: { "--primary": "#222" } })
|
|
30
|
+
expect(style["--primary"]).toBe("#222")
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it("ignore les valeurs vides", () => {
|
|
34
|
+
const style = themeVars({ colors: { primary: "" }, vars: { "--x": "" } })
|
|
35
|
+
expect(style["--primary"]).toBeUndefined()
|
|
36
|
+
expect(style["--x"]).toBeUndefined()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it("renvoie un objet vide pour un thème vide", () => {
|
|
40
|
+
expect(themeVars({})).toEqual({})
|
|
41
|
+
})
|
|
42
|
+
})
|
package/lib/themeVars.ts
CHANGED
|
@@ -1,22 +1,40 @@
|
|
|
1
|
-
//
|
|
2
|
-
// téléportés (dialogs, bottom sheets) qui sortent du div .q-config-provider.
|
|
1
|
+
// themeVars — variables CSS du thème (couleurs, radius global, `vars` libres) pour les
|
|
2
|
+
// contenus téléportés (dialogs, bottom sheets) qui sortent du div .q-config-provider.
|
|
3
3
|
import { computed } from "vue"
|
|
4
4
|
import type { ComputedRef } from "vue"
|
|
5
5
|
import { contrastText } from "./colors"
|
|
6
6
|
import { isRadiusScale, RADIUS_VALUES } from "./useComponentProps"
|
|
7
7
|
import type { QTheme } from "./config"
|
|
8
8
|
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Variables CSS d'un thème : `--<token>` (+ `--<token>-foreground` calculé) pour
|
|
11
|
+
* `colors`, `--q-radius` pour `componentProps.default.radius`, puis les `vars` libres
|
|
12
|
+
* **en dernier** (elles ont le dernier mot). Pur → testable hors navigateur.
|
|
13
|
+
*/
|
|
14
|
+
export function themeVars(theme: QTheme): Record<string, string> {
|
|
15
|
+
const style: Record<string, string> = {}
|
|
16
|
+
|
|
17
|
+
for (const [token, value] of Object.entries(theme.colors ?? {})) {
|
|
18
|
+
if (!value) continue
|
|
19
|
+
style[`--${token}`] = value
|
|
20
|
+
style[`--${token}-foreground`] = contrastText(value)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Arrondi global : hérité par TOUS les composants dont le CSS lit var(--q-radius).
|
|
24
|
+
// Une prop/override spécifique (`useRadius`) pose son propre --q-radius inline et prime.
|
|
25
|
+
const global = theme.componentProps?.default?.radius
|
|
26
|
+
if (isRadiusScale(global)) style["--q-radius"] = RADIUS_VALUES[global]
|
|
27
|
+
|
|
28
|
+
// Échappatoire : toute variable CSS, avec ou sans préfixe `--`.
|
|
29
|
+
for (const [name, value] of Object.entries(theme.vars ?? {})) {
|
|
30
|
+
if (!value) continue
|
|
31
|
+
style[name.startsWith("--") ? name : `--${name}`] = value
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return style
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Computed des variables du thème (cf. `themeVars`). */
|
|
10
38
|
export function themeVarsStyle(theme: ComputedRef<QTheme>): ComputedRef<Record<string, string>> {
|
|
11
|
-
return computed(() =>
|
|
12
|
-
const style: Record<string, string> = {}
|
|
13
|
-
for (const [token, value] of Object.entries(theme.value.colors ?? {})) {
|
|
14
|
-
if (!value) continue
|
|
15
|
-
style[`--${token}`] = value
|
|
16
|
-
style[`--${token}-foreground`] = contrastText(value)
|
|
17
|
-
}
|
|
18
|
-
const global = theme.value.componentProps?.default?.radius
|
|
19
|
-
if (isRadiusScale(global)) style["--q-radius"] = RADIUS_VALUES[global]
|
|
20
|
-
return style
|
|
21
|
-
})
|
|
39
|
+
return computed(() => themeVars(theme.value))
|
|
22
40
|
}
|
package/module.ts
CHANGED
|
@@ -50,6 +50,17 @@ export default defineNuxtModule<DnaxUiModuleOptions>({
|
|
|
50
50
|
})
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
// Dépendances CJS sans export ESM `default` explicite : esbuild doit les pré-bundler
|
|
54
|
+
// (interop) sinon, servies brutes, le navigateur échoue sur « does not provide an
|
|
55
|
+
// export named 'default' ». `leaflet` (lib/mapLeaflet.ts) est dans ce cas ; le code
|
|
56
|
+
// tolère déjà les deux formes (`default ?? module`), ceci est une ceinture-bretelles
|
|
57
|
+
// côté hôte. Ne pas pointer une sous-entrée sur `index.ts` (cf. warnings en mémoire).
|
|
58
|
+
nuxt.hook("vite:extendConfig", (config) => {
|
|
59
|
+
const optimizeDeps = ((config as any).optimizeDeps ||= {})
|
|
60
|
+
const include: string[] = (optimizeDeps.include ||= [])
|
|
61
|
+
if (!include.includes("leaflet")) include.push("leaflet")
|
|
62
|
+
})
|
|
63
|
+
|
|
53
64
|
// « Retour » navigateur → ferme l'overlay ouvert (dialog, sheet, sidebar…)
|
|
54
65
|
// addPluginTemplate : le template est compilé dans l'app (où #imports existe),
|
|
55
66
|
// évitant une dépendance `nuxt` dans ce package pour les types du plugin.
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dnax/ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.54",
|
|
4
4
|
"module": "index.ts",
|
|
5
5
|
"description": "UI design System",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./index.ts",
|
|
9
|
-
"./
|
|
9
|
+
"./registry": "./index.ts",
|
|
10
|
+
"./runtime": "./runtime.ts",
|
|
10
11
|
"./module": "./module.ts",
|
|
11
12
|
"./styles/main.css": "./styles/main.css"
|
|
12
13
|
},
|
|
@@ -36,11 +37,11 @@
|
|
|
36
37
|
"leaflet": "^1.9.4",
|
|
37
38
|
"maplibre-gl": "^5.24.0",
|
|
38
39
|
"moment": "^2.30.1",
|
|
39
|
-
"qrcode": "^1.5.4",
|
|
40
40
|
"shiki": "^3.0.0",
|
|
41
41
|
"swiper": "^14.2.0",
|
|
42
42
|
"tailwind-merge": "^3.6.0",
|
|
43
43
|
"tw-animate-css": "^1.4.0",
|
|
44
|
+
"uqr": "^0.1.3",
|
|
44
45
|
"uuid": "^14.0.2",
|
|
45
46
|
"vue-router": "^5.3.0",
|
|
46
47
|
"vue-sonner": "^2.0.9"
|
package/runtime.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// runtime.ts — **surface runtime** consommée par les plugins (module Nuxt + plugins de
|
|
2
|
+
// l'app hôte). Elle contient, et rien de plus :
|
|
3
|
+
// - l'API directive / overlay (`installOverlayBackHandler`, `v-close`, `v-ripple`,
|
|
4
|
+
// `v-touch-*`, `v-intersection`) ;
|
|
5
|
+
// - l'API impérative `$q` (composables `usePlugin` / `useDialogPluginComponent`…) ;
|
|
6
|
+
// - les **providers** de `$q` (dialog, bottom sheet, notify, loading, image preview),
|
|
7
|
+
// montés au boot par l'app hôte.
|
|
8
|
+
//
|
|
9
|
+
// ⚠️ Ne JAMAIS réexporter `index.ts` ici. Le barrel importe **tous** les composants et,
|
|
10
|
+
// avec eux, leurs dépendances lourdes (echarts, @maptiler/sdk, swiper, shiki, uqr…) : une
|
|
11
|
+
// sous-entrée qui pointe sur `index.ts` les évalue au boot, même si l'app n'utilise aucun
|
|
12
|
+
// de ces composants. C'est tout l'intérêt de ce fichier — voir `.memory/warnings.md`.
|
|
13
|
+
//
|
|
14
|
+
// Pour *résoudre un composant par son nom* (tableaux d'API, docs), importer le barrel
|
|
15
|
+
// `@dnax/ui` — pas cette entrée.
|
|
16
|
+
export {
|
|
17
|
+
installOverlayBackHandler,
|
|
18
|
+
registerOverlay,
|
|
19
|
+
unregisterOverlay,
|
|
20
|
+
hasOpenOverlays,
|
|
21
|
+
closeTopmostOverlay,
|
|
22
|
+
useOverlayBack,
|
|
23
|
+
} from "./lib/overlayBack"
|
|
24
|
+
export type { OverlayHandle } from "./lib/overlayBack"
|
|
25
|
+
export { markOverlayClose, closeParentOverlay, vClose } from "./lib/closeOverlay"
|
|
26
|
+
export { vRipple } from "./lib/ripple"
|
|
27
|
+
export { vTouchPan } from "./lib/touchPan"
|
|
28
|
+
export { vTouchHold } from "./lib/touchHold"
|
|
29
|
+
export { vTouchSwipe } from "./lib/touchSwipe"
|
|
30
|
+
export { vTouchRepeat } from "./lib/touchRepeat"
|
|
31
|
+
export { vIntersection } from "./lib/intersection"
|
|
32
|
+
|
|
33
|
+
// — API impérative `$q` (composables) —
|
|
34
|
+
export {
|
|
35
|
+
$q,
|
|
36
|
+
usePlugin,
|
|
37
|
+
useQ,
|
|
38
|
+
QPlugin,
|
|
39
|
+
useDialogPluginComponent,
|
|
40
|
+
useBottomSheetPluginComponent,
|
|
41
|
+
} from "./lib/q"
|
|
42
|
+
|
|
43
|
+
// — Providers de `$q` (montés au boot par l'app hôte) —
|
|
44
|
+
export { default as QDialogProvider } from "./components/QDialogProvider.vue"
|
|
45
|
+
export { default as QBottomSheetProvider } from "./components/QBottomSheetProvider.vue"
|
|
46
|
+
export { default as QNotifyProvider } from "./components/QNotifyProvider.vue"
|
|
47
|
+
export { default as QLoadingProvider } from "./components/QLoadingProvider.vue"
|
|
48
|
+
export { default as QImagePreviewProvider } from "./components/QImagePreviewProvider.vue"
|
package/shims.d.ts
CHANGED
|
@@ -7,19 +7,13 @@ declare module "*.css?raw" {
|
|
|
7
7
|
|
|
8
8
|
// Leaflet ne publie pas de types (ils vivent dans `@types/leaflet`, un paquet séparé) :
|
|
9
9
|
// le moteur `lib/mapLeaflet.ts` le manipule donc en `any`, comme le SDK MapTiler.
|
|
10
|
+
//
|
|
11
|
+
// ⚠️ Ce `default` est une commodité de **typage** : au runtime `leaflet` est un UMD, sans
|
|
12
|
+
// export ESM `default` garanti (selon la pré-bundlisation Vite). C'est pourquoi
|
|
13
|
+
// `lib/mapLeaflet.ts` importe en **dynamique** puis prend `leaflet.default ?? leaflet` —
|
|
14
|
+
// jamais un `import L from "leaflet"` statique (même piège que l'ancien `qrcode`, cf.
|
|
15
|
+
// `.memory/warnings.md`).
|
|
10
16
|
declare module "leaflet" {
|
|
11
17
|
const leaflet: any
|
|
12
18
|
export default leaflet
|
|
13
19
|
}
|
|
14
|
-
|
|
15
|
-
// Même situation pour `qrcode` (=`@types/qrcode` séparé) : seul `create()` est utilisé
|
|
16
|
-
// (matrice de modules) — le rendu est fait par dnax.ui (`lib/qrcode.ts`).
|
|
17
|
-
declare module "qrcode" {
|
|
18
|
-
const qrcode: {
|
|
19
|
-
create: (
|
|
20
|
-
text: string,
|
|
21
|
-
options?: { errorCorrectionLevel?: "L" | "M" | "Q" | "H"; version?: number },
|
|
22
|
-
) => { modules: { size: number; data: ArrayLike<number> } }
|
|
23
|
-
}
|
|
24
|
-
export default qrcode
|
|
25
|
-
}
|
package/styles/main.css
CHANGED
|
@@ -400,7 +400,7 @@ html.dark body {
|
|
|
400
400
|
}
|
|
401
401
|
|
|
402
402
|
.dark .q-field__control {
|
|
403
|
-
background-color: var(--muted);
|
|
403
|
+
background-color: var(--q-field-bg, var(--muted));
|
|
404
404
|
border-color: var(--border);
|
|
405
405
|
color: var(--foreground);
|
|
406
406
|
}
|
|
@@ -2021,7 +2021,11 @@ html.dark body {
|
|
|
2021
2021
|
gap: 6px;
|
|
2022
2022
|
min-height: 40px;
|
|
2023
2023
|
padding: 0 12px;
|
|
2024
|
-
|
|
2024
|
+
/* Fond du champ, partagé par TOUS les champs (QInput, QSelect, QAutocomplete,
|
|
2025
|
+
QDatePicker, QCountryPicker, QInputTag, QInputOtp…). Surchargeable globalement via
|
|
2026
|
+
`--q-field-bg` (héritée par le sous-arbre) — `theme.vars: { "--q-field-bg": "…" }` —
|
|
2027
|
+
ou localement par CSS. La variante `filled` lit `--q-field-bg-filled`. */
|
|
2028
|
+
background-color: var(--q-field-bg, #fff);
|
|
2025
2029
|
border: 1px solid rgb(0 0 0 / 0.22);
|
|
2026
2030
|
border-radius: var(--q-radius);
|
|
2027
2031
|
transition:
|
|
@@ -2031,18 +2035,18 @@ html.dark body {
|
|
|
2031
2035
|
}
|
|
2032
2036
|
|
|
2033
2037
|
.q-field--outlined .q-field__control {
|
|
2034
|
-
background-color: transparent;
|
|
2038
|
+
background-color: var(--q-field-bg, transparent);
|
|
2035
2039
|
}
|
|
2036
2040
|
|
|
2037
2041
|
.q-field--filled .q-field__control {
|
|
2038
|
-
background-color: rgb(0 0 0 / 0.05);
|
|
2042
|
+
background-color: var(--q-field-bg-filled, rgb(0 0 0 / 0.05));
|
|
2039
2043
|
border-color: transparent;
|
|
2040
2044
|
border-bottom: 1px solid rgb(0 0 0 / 0.3);
|
|
2041
2045
|
border-radius: var(--q-radius) var(--q-radius) 0 0;
|
|
2042
2046
|
}
|
|
2043
2047
|
|
|
2044
2048
|
.q-field--borderless .q-field__control {
|
|
2045
|
-
background-color: transparent;
|
|
2049
|
+
background-color: var(--q-field-bg, transparent);
|
|
2046
2050
|
border-color: transparent;
|
|
2047
2051
|
}
|
|
2048
2052
|
|
|
@@ -5790,7 +5794,7 @@ a.q-breadcrumbs__el:hover {
|
|
|
5790
5794
|
}
|
|
5791
5795
|
|
|
5792
5796
|
.q-date-picker__sheet--popover {
|
|
5793
|
-
width:
|
|
5797
|
+
width: 300px;
|
|
5794
5798
|
max-width: calc(100vw - 16px);
|
|
5795
5799
|
border: 1px solid rgb(0 0 0 / 0.08);
|
|
5796
5800
|
box-shadow: 0 10px 32px rgb(0 0 0 / 0.16);
|
|
@@ -5809,8 +5813,8 @@ a.q-breadcrumbs__el:hover {
|
|
|
5809
5813
|
.q-date-calendar {
|
|
5810
5814
|
display: flex;
|
|
5811
5815
|
flex-direction: column;
|
|
5812
|
-
gap:
|
|
5813
|
-
padding:
|
|
5816
|
+
gap: 6px;
|
|
5817
|
+
padding: 10px;
|
|
5814
5818
|
user-select: none;
|
|
5815
5819
|
}
|
|
5816
5820
|
|
|
@@ -5885,6 +5889,8 @@ a.q-breadcrumbs__el:hover {
|
|
|
5885
5889
|
.q-date-calendar__weekdays {
|
|
5886
5890
|
display: grid;
|
|
5887
5891
|
grid-template-columns: repeat(7, 1fr);
|
|
5892
|
+
/* Même gap que les semaines : les libellés s'alignent sur les colonnes des jours */
|
|
5893
|
+
gap: 2px;
|
|
5888
5894
|
}
|
|
5889
5895
|
|
|
5890
5896
|
.q-date-calendar__weekday {
|
|
@@ -5912,7 +5918,12 @@ a.q-breadcrumbs__el:hover {
|
|
|
5912
5918
|
align-items: center;
|
|
5913
5919
|
justify-content: center;
|
|
5914
5920
|
width: 100%;
|
|
5921
|
+
/* La colonne est en `1fr` : sans plafond le cercle grandit avec la largeur du
|
|
5922
|
+
panneau (popover 300px → cercles ~38px, inline large → bien plus). On borne
|
|
5923
|
+
la pastille et on la centre. */
|
|
5924
|
+
max-width: 34px;
|
|
5915
5925
|
aspect-ratio: 1;
|
|
5926
|
+
justify-self: center;
|
|
5916
5927
|
padding: 0;
|
|
5917
5928
|
border: none;
|
|
5918
5929
|
border-radius: 50%;
|