@dnax/ui 0.0.45 → 0.0.48
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/QBtn.vue +3 -4
- package/components/QContainer.vue +2 -2
- package/components/QSelect.vue +91 -56
- package/components/QSidebar.vue +8 -0
- package/components/QTiptap.vue +1 -0
- package/lib/useComponentProps.test.ts +36 -0
- package/lib/useComponentProps.ts +7 -1
- package/package.json +1 -1
- package/styles/main.css +54 -25
package/components/QBtn.vue
CHANGED
|
@@ -39,9 +39,9 @@ interface Props {
|
|
|
39
39
|
type?: "button" | "submit" | "reset"
|
|
40
40
|
/** Rend un lien natif <a> */
|
|
41
41
|
href?: string
|
|
42
|
-
/** Fond transparent, texte
|
|
42
|
+
/** Fond transparent, texte coloré. Gagne sur `outline` (designs exclusifs) */
|
|
43
43
|
flat?: boolean
|
|
44
|
-
/** Fond transparent + bordure */
|
|
44
|
+
/** Fond transparent + bordure (ignoré si `flat`, cf. Quasar : le design `flat` retire aussi la bordure) */
|
|
45
45
|
outline?: boolean
|
|
46
46
|
/** Supprime l'ombre portée */
|
|
47
47
|
unelevated?: boolean
|
|
@@ -110,8 +110,7 @@ const btnClasses = computed(() =>
|
|
|
110
110
|
btnVariants({ size: sizeVariant.value }),
|
|
111
111
|
isTokenColor(props.color) && `q-btn--${props.color}`,
|
|
112
112
|
isTokenColor(props.textColor) && `q-btn--text-${props.textColor}`,
|
|
113
|
-
props.flat
|
|
114
|
-
props.outline && "q-btn--outline",
|
|
113
|
+
props.flat ? "q-btn--flat" : props.outline && "q-btn--outline",
|
|
115
114
|
props.unelevated && "q-btn--unelevated",
|
|
116
115
|
props.dense && "q-btn--dense",
|
|
117
116
|
effectiveRadius.value === true && "q-btn--rounded",
|
|
@@ -9,7 +9,7 @@ interface Props {
|
|
|
9
9
|
maxWidth?: string | number
|
|
10
10
|
/** Supprime la largeur max (pleine largeur, padding conservé) */
|
|
11
11
|
fluid?: boolean
|
|
12
|
-
/** Padding horizontal (défaut
|
|
12
|
+
/** Padding horizontal (défaut 12px) */
|
|
13
13
|
padding?: string
|
|
14
14
|
/** Effet d'arrière-plan : "grid" | "aurora" | "star" | "falling-stars" | "flickering-grid" | "interactive-grid" | "wave" (vagues qui glissent) */
|
|
15
15
|
backgroundEffect?:
|
|
@@ -57,7 +57,7 @@ interface Props {
|
|
|
57
57
|
const props = withDefaults(defineProps<Props>(), {
|
|
58
58
|
maxWidth: "1200px",
|
|
59
59
|
fluid: false,
|
|
60
|
-
padding: "
|
|
60
|
+
padding: "12px",
|
|
61
61
|
})
|
|
62
62
|
|
|
63
63
|
const IMG_FIT = new Set(["cover", "contain", "fill", "none", "scale-down"])
|
package/components/QSelect.vue
CHANGED
|
@@ -56,7 +56,7 @@ import { computed, getCurrentInstance, nextTick, onBeforeUnmount, onMounted, ref
|
|
|
56
56
|
import { Icon } from "@iconify/vue"
|
|
57
57
|
import { icons } from "../lib/icons"
|
|
58
58
|
import { cn } from "../lib/utils"
|
|
59
|
-
import { radiusStyle, useRadius } from "../lib/useComponentProps"
|
|
59
|
+
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"
|
|
@@ -184,6 +184,11 @@ defineSlots<{
|
|
|
184
184
|
hint?: () => any
|
|
185
185
|
}>()
|
|
186
186
|
|
|
187
|
+
// Multi-racines (le champ + les <Teleport> du popup et du panneau) : Vue ne peut pas
|
|
188
|
+
// hériter les attributs tout seul (avertissement « fragment or text or teleport root
|
|
189
|
+
// nodes »). Ils sont posés explicitement sur le champ (racine principale).
|
|
190
|
+
defineOptions({ inheritAttrs: false })
|
|
191
|
+
|
|
187
192
|
const instance = getCurrentInstance()
|
|
188
193
|
const hasFilter = computed(() => !!instance?.vnode.props?.onFilter)
|
|
189
194
|
|
|
@@ -223,9 +228,11 @@ const VIEWPORT_MARGIN = 8
|
|
|
223
228
|
|
|
224
229
|
const popupDirection = ref<"down" | "up">("down")
|
|
225
230
|
const popupMaxHeight = ref(POPUP_MAX_HEIGHT)
|
|
226
|
-
/**
|
|
231
|
+
/** Coordonnées **fenêtre** : le popup est téléporté dans <body> (`position: fixed`) — null avant mesure */
|
|
227
232
|
const popupTop = ref<number | null>(null)
|
|
228
233
|
const popupBottom = ref<number | null>(null)
|
|
234
|
+
/** Rect du champ (coordonnées fenêtre) : ancre horizontale du popup */
|
|
235
|
+
const popupAnchor = ref<{ left: number; right: number; width: number } | null>(null)
|
|
229
236
|
|
|
230
237
|
/** Écart champ ↔ popup : `inline-options.offset` prioritaire sur la prop `offset` */
|
|
231
238
|
const popupOffset = computed(() => modeOptions.value?.offset ?? props.offset)
|
|
@@ -265,17 +272,32 @@ const positionPopup = () => {
|
|
|
265
272
|
|
|
266
273
|
popupDirection.value = down ? "down" : "up"
|
|
267
274
|
popupMaxHeight.value = Math.max(0, Math.min(POPUP_MAX_HEIGHT, available - gap))
|
|
268
|
-
|
|
269
|
-
|
|
275
|
+
// Tout en coordonnées fenêtre : le popup est `fixed` dans <body>, il n'est donc
|
|
276
|
+
// plus positionné par rapport à la racine du composant. Horizontalement on garde la
|
|
277
|
+
// boîte du CHAMP (c'était `left: 0; right: 0` sur la racine), verticalement l'ancre.
|
|
278
|
+
popupAnchor.value = { left: rect.left, right: rect.right, width: rect.width }
|
|
279
|
+
popupTop.value = Math.round(anchor.bottom + gap)
|
|
280
|
+
popupBottom.value = Math.round(window.innerHeight - anchor.top + gap)
|
|
270
281
|
}
|
|
271
282
|
|
|
272
283
|
const popupStyle = computed<Record<string, string>>(() => {
|
|
273
284
|
const up = popupDirection.value === "up"
|
|
274
|
-
const
|
|
285
|
+
const anchor = popupAnchor.value
|
|
275
286
|
const style: Record<string, string> = {
|
|
276
|
-
top: up ? "auto" : popupTop.value === null ? fallback : `${popupTop.value}px`,
|
|
277
|
-
bottom: up ? (popupBottom.value === null ? fallback : `${popupBottom.value}px`) : "auto",
|
|
278
287
|
maxHeight: `${Math.round(popupMaxHeight.value)}px`,
|
|
288
|
+
// Arrondi : l'échelle de radius du composant (composantProps / prop), sinon la
|
|
289
|
+
// variable globale. Le popup est TÉLÉPORTÉ dans <body> : il n'hérite plus de
|
|
290
|
+
// `--q-radius` posé sur le champ, d'où la valeur matérialisée ici.
|
|
291
|
+
borderRadius: radiusValue(effectiveFieldRadius.value) ?? "var(--q-radius, 4px)",
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Vertical : sous le champ, ou au-dessus (bascule / placement forcé)
|
|
295
|
+
if (up) {
|
|
296
|
+
style.bottom = popupBottom.value === null ? "0px" : `${popupBottom.value}px`
|
|
297
|
+
style.top = "auto"
|
|
298
|
+
} else {
|
|
299
|
+
style.top = popupTop.value === null ? "0px" : `${popupTop.value}px`
|
|
300
|
+
style.bottom = "auto"
|
|
279
301
|
}
|
|
280
302
|
|
|
281
303
|
// Ancre horizontale : par défaut le popup couvre la largeur du champ. Les suffixes
|
|
@@ -284,14 +306,19 @@ const popupStyle = computed<Record<string, string>>(() => {
|
|
|
284
306
|
const placement = popupPlacement.value
|
|
285
307
|
const width = modeOptions.value?.width
|
|
286
308
|
const anchored = placement !== "auto" && placement.includes("-")
|
|
309
|
+
const left = anchor?.left ?? 0
|
|
310
|
+
const right =
|
|
311
|
+
anchor && typeof window !== "undefined" ? window.innerWidth - anchor.right : 0
|
|
312
|
+
|
|
287
313
|
if (anchored || width) {
|
|
288
314
|
const alignEnd = placement.endsWith("-end")
|
|
289
|
-
style.left = alignEnd ? "auto" :
|
|
290
|
-
style.right = alignEnd ?
|
|
291
|
-
style.width = width ?? "
|
|
315
|
+
style.left = alignEnd ? "auto" : `${left}px`
|
|
316
|
+
style.right = alignEnd ? `${right}px` : "auto"
|
|
317
|
+
style.width = width ?? "auto"
|
|
292
318
|
} else {
|
|
293
|
-
style.left =
|
|
294
|
-
style.right = "
|
|
319
|
+
style.left = `${left}px`
|
|
320
|
+
style.right = "auto"
|
|
321
|
+
style.width = anchor ? `${anchor.width}px` : "auto"
|
|
295
322
|
}
|
|
296
323
|
|
|
297
324
|
return style
|
|
@@ -575,6 +602,8 @@ const onDocMousedown = (e: MouseEvent) => {
|
|
|
575
602
|
const target = e.target as Node
|
|
576
603
|
if (rootEl.value?.contains(target)) return
|
|
577
604
|
if (sheetRef.value?.contains(target)) return
|
|
605
|
+
// Popup inline : téléporté dans <body>, donc HORS de la racine du composant
|
|
606
|
+
if (popupRef.value?.contains(target)) return
|
|
578
607
|
closePopup()
|
|
579
608
|
}
|
|
580
609
|
|
|
@@ -643,7 +672,10 @@ const sheetRadiusStyle = computed<Record<string, string> | undefined>(() => {
|
|
|
643
672
|
const rounded = modeOptions.value?.rounded ?? props.rounded
|
|
644
673
|
if (rounded === false) return { borderRadius: "0" }
|
|
645
674
|
|
|
646
|
-
|
|
675
|
+
// Défaut : l'échelle de radius du composant (composantProps), sinon le défaut
|
|
676
|
+
// historique du mode — le panneau est téléporté, il ne peut pas lire `--q-radius`.
|
|
677
|
+
const defaultRadius =
|
|
678
|
+
radiusValue(effectiveFieldRadius.value) ?? (props.mode === "modal" ? "20px" : "12px")
|
|
647
679
|
const value = typeof rounded === "string" ? rounded : defaultRadius
|
|
648
680
|
const asPx = (v: string | number) => (typeof v === "number" ? `${v}px` : v)
|
|
649
681
|
|
|
@@ -672,7 +704,7 @@ const sheetClass = computed(() => [`q-select__sheet--${props.mode}`, modeOptions
|
|
|
672
704
|
</script>
|
|
673
705
|
|
|
674
706
|
<template>
|
|
675
|
-
<div ref="rootEl" class="q-select" :class="fieldClasses" :style="fieldRadiusStyle">
|
|
707
|
+
<div ref="rootEl" class="q-select" :class="fieldClasses" :style="fieldRadiusStyle" v-bind="$attrs">
|
|
676
708
|
<label v-if="label" class="q-field__label-stack">{{ label }}</label>
|
|
677
709
|
<div class="q-field__control" @click="onControlClick">
|
|
678
710
|
<slot name="prepend">
|
|
@@ -751,50 +783,53 @@ const sheetClass = computed(() => [`q-select__sheet--${props.mode}`, modeOptions
|
|
|
751
783
|
</div>
|
|
752
784
|
</div>
|
|
753
785
|
|
|
754
|
-
<!-- Popup inline (mode inline)
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
<
|
|
767
|
-
class="q-select__search-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
786
|
+
<!-- Popup inline (mode inline) : téléporté dans <body> + `position: fixed`,
|
|
787
|
+
pour échapper au `overflow: hidden` d'un q-card (cf. positionPopup) -->
|
|
788
|
+
<Teleport to="body">
|
|
789
|
+
<div
|
|
790
|
+
v-if="open && mode === 'inline'"
|
|
791
|
+
ref="popupRef"
|
|
792
|
+
class="q-select__popup"
|
|
793
|
+
:class="[inlineOptions?.class, popupDirection === 'up' && 'q-select__popup--up']"
|
|
794
|
+
:style="[popupStyle, inlineOptions?.style]"
|
|
795
|
+
role="listbox"
|
|
796
|
+
@mousedown.prevent
|
|
797
|
+
>
|
|
798
|
+
<div v-if="searchEnabled && !useInput" class="q-select__search" @mousedown.stop>
|
|
799
|
+
<Icon :icon="icons.search" class="q-select__search-icon" aria-hidden="true" />
|
|
800
|
+
<input
|
|
801
|
+
class="q-select__search-input"
|
|
802
|
+
:value="query"
|
|
803
|
+
:placeholder="effectiveSearchPlaceholder"
|
|
804
|
+
@input="onInput"
|
|
805
|
+
@keydown="onKeydown"
|
|
806
|
+
/>
|
|
807
|
+
</div>
|
|
808
|
+
<template v-if="displayOptions.length">
|
|
809
|
+
<div
|
|
810
|
+
v-for="(opt, i) in displayOptions"
|
|
811
|
+
:key="String(getOptionValue(opt))"
|
|
812
|
+
role="option"
|
|
813
|
+
:aria-selected="isSelected(opt) ? 'true' : 'false'"
|
|
814
|
+
class="q-select__option"
|
|
815
|
+
:class="{
|
|
816
|
+
'q-select__option--active': i === activeIndex,
|
|
817
|
+
'q-select__option--selected': isSelected(opt),
|
|
818
|
+
}"
|
|
819
|
+
@mouseenter="activeIndex = i"
|
|
820
|
+
@mousedown.prevent="select(opt)"
|
|
821
|
+
>
|
|
822
|
+
<slot :opt="opt" :index="i" :selected="isSelected(opt)">
|
|
823
|
+
<span class="q-select__option-label">{{ getOptionLabel(opt) }}</span>
|
|
824
|
+
</slot>
|
|
825
|
+
<Icon :icon="icons.check" v-if="multiple && isSelected(opt)" class="q-select__check" aria-hidden="true" />
|
|
826
|
+
</div>
|
|
827
|
+
</template>
|
|
828
|
+
<div v-else class="q-select__empty">
|
|
829
|
+
<slot name="noOption">{{ noOptionsLabel }}</slot>
|
|
792
830
|
</div>
|
|
793
|
-
</template>
|
|
794
|
-
<div v-else class="q-select__empty">
|
|
795
|
-
<slot name="noOption">{{ noOptionsLabel }}</slot>
|
|
796
831
|
</div>
|
|
797
|
-
</
|
|
832
|
+
</Teleport>
|
|
798
833
|
|
|
799
834
|
<!-- Mode modal / sheet / dialog : panneau téléporté -->
|
|
800
835
|
<Teleport to="body">
|
package/components/QSidebar.vue
CHANGED
|
@@ -69,6 +69,13 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
69
69
|
|
|
70
70
|
const emit = defineEmits<{ "update:modelValue": [value: boolean] }>()
|
|
71
71
|
|
|
72
|
+
// Multi-racines (<aside> + backdrop) : Vue ne peut pas hériter les attributs tout seul
|
|
73
|
+
// (avertissement « could not be automatically inherited because component renders
|
|
74
|
+
// fragment … »). On les applique explicitement sur le panneau, la seule racine qui
|
|
75
|
+
// compte : `id`, `data-*`, `aria-label`… arrivent donc sur l'<aside> (l'accessibilité
|
|
76
|
+
// et les sélecteurs de test sont préservés). `class` / `style` restent des props.
|
|
77
|
+
defineOptions({ inheritAttrs: false })
|
|
78
|
+
|
|
72
79
|
// — Mode statique (viewport >= breakpoint && show-if-above) —
|
|
73
80
|
const isAbove = ref(false)
|
|
74
81
|
let mql: MediaQueryList | null = null
|
|
@@ -238,6 +245,7 @@ watch(open, (v) => {
|
|
|
238
245
|
class="q-sidebar"
|
|
239
246
|
:class="rootClasses"
|
|
240
247
|
:style="[rootStyle, props.style]"
|
|
248
|
+
v-bind="$attrs"
|
|
241
249
|
:aria-hidden="!isStatic && !open ? 'true' : undefined"
|
|
242
250
|
@pointerdown="onPointerDown"
|
|
243
251
|
@pointermove="onPointerMove"
|
package/components/QTiptap.vue
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Tests unitaires de la résolution du radius (échelle + style CSS).
|
|
2
|
+
import { describe, expect, it } from "bun:test"
|
|
3
|
+
import { RADIUS_VALUES, radiusStyle, radiusValue } from "./useComponentProps"
|
|
4
|
+
|
|
5
|
+
describe("radiusValue", () => {
|
|
6
|
+
it("traduit chaque barreau de l'échelle", () => {
|
|
7
|
+
expect(radiusValue("none")).toBe("0px")
|
|
8
|
+
expect(radiusValue("xs")).toBe("2px")
|
|
9
|
+
expect(radiusValue("sm")).toBe("4px")
|
|
10
|
+
expect(radiusValue("md")).toBe("8px")
|
|
11
|
+
expect(radiusValue("lg")).toBe("16px")
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it("ne traduit ni la forme native (true) ni l'absence de valeur", () => {
|
|
15
|
+
expect(radiusValue(true)).toBeUndefined()
|
|
16
|
+
expect(radiusValue(false)).toBeUndefined()
|
|
17
|
+
expect(radiusValue(undefined)).toBeUndefined()
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it("reste aligné sur RADIUS_VALUES", () => {
|
|
21
|
+
for (const [scale, value] of Object.entries(RADIUS_VALUES)) {
|
|
22
|
+
expect(radiusValue(scale as keyof typeof RADIUS_VALUES)).toBe(value)
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe("radiusStyle", () => {
|
|
28
|
+
it("pose --q-radius pour une valeur d'échelle", () => {
|
|
29
|
+
expect(radiusStyle("lg")).toEqual({ "--q-radius": "16px" })
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it("ne pose rien pour true (forme native) ou undefined", () => {
|
|
33
|
+
expect(radiusStyle(true)).toBeUndefined()
|
|
34
|
+
expect(radiusStyle(undefined)).toBeUndefined()
|
|
35
|
+
})
|
|
36
|
+
})
|
package/lib/useComponentProps.ts
CHANGED
|
@@ -55,7 +55,13 @@ export function useRadius(
|
|
|
55
55
|
return computed(() => toValue(own) ?? (defaults.value.radius as RadiusProp | undefined))
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
/** Valeur CSS concrète d'une échelle de radius (`undefined` si `true` (forme native) ou absent) */
|
|
59
|
+
export function radiusValue(radius: RadiusProp | undefined): string | undefined {
|
|
60
|
+
return isRadiusScale(radius) ? RADIUS_VALUES[radius] : undefined
|
|
61
|
+
}
|
|
62
|
+
|
|
58
63
|
/** Style `--q-radius` à poser sur la racine du composant pour une valeur d'échelle. */
|
|
59
64
|
export function radiusStyle(radius: RadiusProp | undefined): Record<string, string> | undefined {
|
|
60
|
-
|
|
65
|
+
const value = radiusValue(radius)
|
|
66
|
+
return value ? { "--q-radius": value } : undefined
|
|
61
67
|
}
|
package/package.json
CHANGED
package/styles/main.css
CHANGED
|
@@ -32,18 +32,22 @@ body {
|
|
|
32
32
|
/* ===== Échelle des z-index =====
|
|
33
33
|
Règle : un menu superposé ne doit JAMAIS passer sous un overlay. Les menus
|
|
34
34
|
téléportés dans <body> sont donc au-dessus de la couche modale.
|
|
35
|
-
1000 popups en flux (`position: absolute` dans leur champ :
|
|
36
|
-
|
|
35
|
+
1000 popups en flux (`position: absolute` dans leur champ : autocomplete,
|
|
36
|
+
nav-menu…) → pas concernés par les overlays
|
|
37
37
|
1999/2000 sidebar offcanvas + backdrop
|
|
38
38
|
3000 overlays modaux : dialog, bottom sheet, action sheet,
|
|
39
39
|
date-picker, country-picker, select sheet
|
|
40
40
|
3200 menus téléportés dans <body> : panneau QBtnActions, menu
|
|
41
41
|
contextuel / filtre / suggestions du tableur → --q-z-menu
|
|
42
|
+
3500 popup inline de QSelect (téléporté dans <body> : il doit passer
|
|
43
|
+
au-dessus d'un overlay quand le champ est dans un dialog/sheet)
|
|
44
|
+
→ --q-z-popup
|
|
42
45
|
4000 palettes tiptap · 7000 loading global · 9999 tooltip ·
|
|
43
46
|
10000 image preview
|
|
44
|
-
Surchargez --q-z-menu si votre application a un overlay plus haut
|
|
47
|
+
Surchargez --q-z-menu ou --q-z-popup si votre application a un overlay plus haut. */
|
|
45
48
|
:root {
|
|
46
49
|
--q-z-menu: 3200;
|
|
50
|
+
--q-z-popup: 3500;
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
/* ===== Safe-area insets (encoche & barre d'accueil) =====
|
|
@@ -316,7 +320,6 @@ body {
|
|
|
316
320
|
--muted: #1c2128;
|
|
317
321
|
--border: rgb(255 255 255 / 0.12);
|
|
318
322
|
--q-table-border: rgb(255 255 255 / 0.12);
|
|
319
|
-
--q-table-header-bg: rgb(255 255 255 / 0.04);
|
|
320
323
|
--q-table-hover-bg: rgb(255 255 255 / 0.06);
|
|
321
324
|
--q-sidebar-border: rgb(255 255 255 / 0.12);
|
|
322
325
|
--q-translucent-bg: #0f1115;
|
|
@@ -410,7 +413,9 @@ html.dark body {
|
|
|
410
413
|
}
|
|
411
414
|
.dark .q-data-grid {
|
|
412
415
|
--q-data-grid-border: rgb(255 255 255 / 0.12);
|
|
413
|
-
|
|
416
|
+
/* Opaque (cf. QTable : en-tête sticky sur surface composée) */
|
|
417
|
+
--q-data-grid-header-bg: #1f242b;
|
|
418
|
+
--q-data-grid-header-bg: color-mix(in srgb, #fff 4%, var(--card, #161b22));
|
|
414
419
|
}
|
|
415
420
|
|
|
416
421
|
/* Footer de table (pagination) en dark : range + sélecteur lignes/page */
|
|
@@ -3209,7 +3214,10 @@ html.dark body {
|
|
|
3209
3214
|
/* ===== QTable ===== */
|
|
3210
3215
|
.q-table {
|
|
3211
3216
|
--q-table-border: rgb(0 0 0 / 0.1);
|
|
3212
|
-
|
|
3217
|
+
/* En-tête : la MÊME surface que le tableau (blanc en clair, carte en sombre) — il se
|
|
3218
|
+
distingue par sa bordure basse, pas par un fond grisé. Opaque par construction,
|
|
3219
|
+
donc un en-tête collant ne laisse pas voir les lignes qui défilent dessous. */
|
|
3220
|
+
--q-table-header-bg: var(--card, #fff);
|
|
3213
3221
|
--q-table-hover-bg: rgb(0 0 0 / 0.04);
|
|
3214
3222
|
|
|
3215
3223
|
position: relative;
|
|
@@ -3233,7 +3241,8 @@ html.dark body {
|
|
|
3233
3241
|
|
|
3234
3242
|
.q-table--dark {
|
|
3235
3243
|
--q-table-border: rgb(255 255 255 / 0.15);
|
|
3236
|
-
|
|
3244
|
+
/* = la surface du tableau dans cette variante (cf. `background-color` ci-dessous) */
|
|
3245
|
+
--q-table-header-bg: #1d1d1d;
|
|
3237
3246
|
--q-table-hover-bg: rgb(255 255 255 / 0.06);
|
|
3238
3247
|
background-color: #1d1d1d;
|
|
3239
3248
|
color: #fff;
|
|
@@ -4436,11 +4445,15 @@ a.q-breadcrumbs__el:hover {
|
|
|
4436
4445
|
transition: background-color 0.15s ease;
|
|
4437
4446
|
}
|
|
4438
4447
|
|
|
4439
|
-
|
|
4448
|
+
/* Hover : uniquement sur un item NON actif — sans le `:not(...)`, la règle `:hover`
|
|
4449
|
+
(spécificité 0,2,0) écrasait l'état actif `.q-sidebar__menu-button--active`
|
|
4450
|
+
(0,1,0) : au survol, le fond passait au gris et le texte blanc du primaire devenait
|
|
4451
|
+
illisible. Un item actif garde donc son apparence au survol. */
|
|
4452
|
+
.q-sidebar__menu-button:hover:not(.q-sidebar__menu-button--active) {
|
|
4440
4453
|
background-color: rgb(0 0 0 / 0.05);
|
|
4441
4454
|
}
|
|
4442
4455
|
|
|
4443
|
-
.q-sidebar--dark .q-sidebar__menu-button:hover {
|
|
4456
|
+
.q-sidebar--dark .q-sidebar__menu-button:hover:not(.q-sidebar__menu-button--active) {
|
|
4444
4457
|
background-color: rgb(255 255 255 / 0.08);
|
|
4445
4458
|
}
|
|
4446
4459
|
|
|
@@ -4835,12 +4848,16 @@ a.q-breadcrumbs__el:hover {
|
|
|
4835
4848
|
transform: rotate(180deg);
|
|
4836
4849
|
}
|
|
4837
4850
|
|
|
4851
|
+
/* Popup inline : téléporté dans <body> et en `position: fixed` — en `position:
|
|
4852
|
+
absolute` il restait **rogné** par le premier ancêtre en `overflow: hidden` (un
|
|
4853
|
+
q-card, par exemple), et un `z-index` ne franchit JAMAIS un clip. Les coordonnées
|
|
4854
|
+
fenêtre (top/bottom/left/width) sont posées en inline par `positionPopup()`, qui
|
|
4855
|
+
le repositionne aussi au scroll et au resize. */
|
|
4838
4856
|
.q-select__popup {
|
|
4839
|
-
position:
|
|
4840
|
-
top:
|
|
4857
|
+
position: fixed;
|
|
4858
|
+
top: 0;
|
|
4841
4859
|
left: 0;
|
|
4842
|
-
|
|
4843
|
-
z-index: 1000;
|
|
4860
|
+
z-index: var(--q-z-popup, 3500);
|
|
4844
4861
|
max-height: 240px;
|
|
4845
4862
|
overflow-y: auto;
|
|
4846
4863
|
padding: 4px 0;
|
|
@@ -7294,7 +7311,7 @@ a.q-breadcrumbs__el:hover {
|
|
|
7294
7311
|
width: 100%;
|
|
7295
7312
|
max-width: var(--q-container-max, 1200px);
|
|
7296
7313
|
margin-inline: auto;
|
|
7297
|
-
padding-inline: var(--q-container-pad,
|
|
7314
|
+
padding-inline: var(--q-container-pad, 12px);
|
|
7298
7315
|
}
|
|
7299
7316
|
|
|
7300
7317
|
.q-container--fluid {
|
|
@@ -8648,7 +8665,9 @@ a.q-breadcrumbs__el:hover {
|
|
|
8648
8665
|
/* ===== QDataGrid — grille type tableur (formules, édition, sélection clavier) ===== */
|
|
8649
8666
|
.q-data-grid {
|
|
8650
8667
|
--q-data-grid-border: rgb(0 0 0 / 0.1);
|
|
8651
|
-
|
|
8668
|
+
/* Opaque (cf. QTable : en-tête sticky sur surface composée) */
|
|
8669
|
+
--q-data-grid-header-bg: #f7f7f7;
|
|
8670
|
+
--q-data-grid-header-bg: color-mix(in srgb, #000 3%, #fff);
|
|
8652
8671
|
|
|
8653
8672
|
position: relative;
|
|
8654
8673
|
display: flex;
|
|
@@ -8667,7 +8686,8 @@ a.q-breadcrumbs__el:hover {
|
|
|
8667
8686
|
}
|
|
8668
8687
|
.q-data-grid--dark {
|
|
8669
8688
|
--q-data-grid-border: rgb(255 255 255 / 0.15);
|
|
8670
|
-
--q-data-grid-header-bg:
|
|
8689
|
+
--q-data-grid-header-bg: #2b2b2b;
|
|
8690
|
+
--q-data-grid-header-bg: color-mix(in srgb, #fff 6%, #1d1d1d);
|
|
8671
8691
|
background: #1d1d1d;
|
|
8672
8692
|
color: #fff;
|
|
8673
8693
|
}
|
|
@@ -9126,11 +9146,14 @@ a.q-breadcrumbs__el:hover {
|
|
|
9126
9146
|
width: 17px;
|
|
9127
9147
|
height: 17px;
|
|
9128
9148
|
}
|
|
9129
|
-
|
|
9149
|
+
/* Hover : jamais sur un bouton actif (même règle que QSidebarMenuButton — sans le
|
|
9150
|
+
`:not(...)`, `:hover` (0,2,0) écrasait `.q-tiptap__tool--active` (0,1,0) : le bouton
|
|
9151
|
+
actif perdait son fond et sa couleur primaire au survol). */
|
|
9152
|
+
.q-tiptap__tool:hover:not(:disabled):not(.q-tiptap__tool--active) {
|
|
9130
9153
|
background-color: rgb(0 0 0 / 0.06);
|
|
9131
9154
|
color: var(--q-tiptap-text);
|
|
9132
9155
|
}
|
|
9133
|
-
.dark .q-tiptap__tool:hover:not(:disabled) {
|
|
9156
|
+
.dark .q-tiptap__tool:hover:not(:disabled):not(.q-tiptap__tool--active) {
|
|
9134
9157
|
background-color: rgb(255 255 255 / 0.08);
|
|
9135
9158
|
}
|
|
9136
9159
|
.q-tiptap__tool--active {
|
|
@@ -9485,11 +9508,12 @@ a.q-breadcrumbs__el:hover {
|
|
|
9485
9508
|
width: 15px;
|
|
9486
9509
|
height: 15px;
|
|
9487
9510
|
}
|
|
9488
|
-
|
|
9511
|
+
/* Idem : un bouton de bulle actif garde son apparence au survol */
|
|
9512
|
+
.q-tiptap__bubble-btn:hover:not(:disabled):not(.q-tiptap__bubble-btn--active) {
|
|
9489
9513
|
background-color: rgb(0 0 0 / 0.06);
|
|
9490
9514
|
color: var(--q-tiptap-text);
|
|
9491
9515
|
}
|
|
9492
|
-
.dark .q-tiptap__bubble-btn:hover:not(:disabled) {
|
|
9516
|
+
.dark .q-tiptap__bubble-btn:hover:not(:disabled):not(.q-tiptap__bubble-btn--active) {
|
|
9493
9517
|
background-color: rgb(255 255 255 / 0.08);
|
|
9494
9518
|
}
|
|
9495
9519
|
.q-tiptap__bubble-btn--active {
|
|
@@ -9583,11 +9607,13 @@ a.q-breadcrumbs__el:hover {
|
|
|
9583
9607
|
width: 14px;
|
|
9584
9608
|
height: 14px;
|
|
9585
9609
|
}
|
|
9586
|
-
|
|
9587
|
-
|
|
9610
|
+
/* Popup inline téléporté dans <body> : impossible de le styler depuis le parent — on
|
|
9611
|
+
passe par la classe du popup (`inline-options.class`) ; la largeur, elle, vient de
|
|
9612
|
+
`inline-options.width` (l'inline posé par positionPopup() prime sur ce CSS). */
|
|
9613
|
+
.q-tiptap__font-size-popup {
|
|
9588
9614
|
min-width: 100px;
|
|
9589
9615
|
}
|
|
9590
|
-
.q-tiptap__font-size .q-select__option-label {
|
|
9616
|
+
.q-tiptap__font-size-popup .q-select__option-label {
|
|
9591
9617
|
font-size: 12px;
|
|
9592
9618
|
}
|
|
9593
9619
|
|
|
@@ -9777,7 +9803,9 @@ a.q-breadcrumbs__el:hover {
|
|
|
9777
9803
|
/* ===== QSpreadsheet (tableur type Excel) ===== */
|
|
9778
9804
|
.q-spreadsheet {
|
|
9779
9805
|
--q-spreadsheet-border: rgb(0 0 0 / 0.1);
|
|
9780
|
-
|
|
9806
|
+
/* Opaque (cf. QTable : entêtes sticky des colonnes/lignes) */
|
|
9807
|
+
--q-spreadsheet-header-bg: #f7f7f7;
|
|
9808
|
+
--q-spreadsheet-header-bg: color-mix(in srgb, #000 3%, #fff);
|
|
9781
9809
|
--q-spreadsheet-row-alt: rgb(0 0 0 / 0.015);
|
|
9782
9810
|
--q-spreadsheet-range: rgb(25 118 210 / 0.1);
|
|
9783
9811
|
--q-spreadsheet-range-brd: var(--primary, #1976d2);
|
|
@@ -9806,7 +9834,8 @@ a.q-breadcrumbs__el:hover {
|
|
|
9806
9834
|
.q-spreadsheet--dark,
|
|
9807
9835
|
.dark .q-spreadsheet {
|
|
9808
9836
|
--q-spreadsheet-border: rgb(255 255 255 / 0.12);
|
|
9809
|
-
--q-spreadsheet-header-bg:
|
|
9837
|
+
--q-spreadsheet-header-bg: #22262d;
|
|
9838
|
+
--q-spreadsheet-header-bg: color-mix(in srgb, #fff 5%, var(--card, #1d1d1d));
|
|
9810
9839
|
--q-spreadsheet-row-alt: rgb(255 255 255 / 0.02);
|
|
9811
9840
|
background: var(--card, #1d1d1d);
|
|
9812
9841
|
color: var(--foreground, #ececef);
|