@dnax/ui 0.0.45 → 0.0.46

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.
@@ -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
- /** Décalages en px par rapport à la RACINE (bloc conteneur du popup) — null avant mesure */
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
- popupTop.value = Math.round(anchor.bottom - rect.top + gap)
269
- popupBottom.value = Math.round(rect.bottom - anchor.top + gap)
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 fallback = `calc(100% + ${popupOffset.value}px)`
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" : "0"
290
- style.right = alignEnd ? "0" : "auto"
291
- style.width = width ?? "100%"
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 = "0"
294
- style.right = "0"
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
- const defaultRadius = props.mode === "modal" ? "20px" : "12px"
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
- <div
756
- v-if="open && mode === 'inline'"
757
- ref="popupRef"
758
- class="q-select__popup"
759
- :class="[inlineOptions?.class, popupDirection === 'up' && 'q-select__popup--up']"
760
- :style="[popupStyle, inlineOptions?.style]"
761
- role="listbox"
762
- @mousedown.prevent
763
- >
764
- <div v-if="searchEnabled && !useInput" class="q-select__search" @mousedown.stop>
765
- <Icon :icon="icons.search" class="q-select__search-icon" aria-hidden="true" />
766
- <input
767
- class="q-select__search-input"
768
- :value="query"
769
- :placeholder="effectiveSearchPlaceholder"
770
- @input="onInput"
771
- @keydown="onKeydown"
772
- />
773
- </div>
774
- <template v-if="displayOptions.length">
775
- <div
776
- v-for="(opt, i) in displayOptions"
777
- :key="String(getOptionValue(opt))"
778
- role="option"
779
- :aria-selected="isSelected(opt) ? 'true' : 'false'"
780
- class="q-select__option"
781
- :class="{
782
- 'q-select__option--active': i === activeIndex,
783
- 'q-select__option--selected': isSelected(opt),
784
- }"
785
- @mouseenter="activeIndex = i"
786
- @mousedown.prevent="select(opt)"
787
- >
788
- <slot :opt="opt" :index="i" :selected="isSelected(opt)">
789
- <span class="q-select__option-label">{{ getOptionLabel(opt) }}</span>
790
- </slot>
791
- <Icon :icon="icons.check" v-if="multiple && isSelected(opt)" class="q-select__check" aria-hidden="true" />
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
- </div>
832
+ </Teleport>
798
833
 
799
834
  <!-- Mode modal / sheet / dialog : panneau téléporté -->
800
835
  <Teleport to="body">
@@ -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"
@@ -816,6 +816,7 @@ defineExpose({
816
816
  :disable="!editor.isEditable"
817
817
  aria-label="Font size"
818
818
  class="q-tiptap__font-size"
819
+ :inline-options="{ class: 'q-tiptap__font-size-popup', width: '100px' }"
819
820
  @update:model-value="onFontSizeUpdate"
820
821
  />
821
822
  </div>
@@ -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
+ })
@@ -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
- return isRadiusScale(radius) ? { "--q-radius": RADIUS_VALUES[radius] } : undefined
65
+ const value = radiusValue(radius)
66
+ return value ? { "--q-radius": value } : undefined
61
67
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dnax/ui",
3
- "version": "0.0.45",
3
+ "version": "0.0.46",
4
4
  "module": "index.ts",
5
5
  "description": "UI design System",
6
6
  "type": "module",
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 : select,
36
- autocomplete, nav-menu…) → pas concernés par les overlays
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 que 3200. */
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,10 @@ 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);
323
+ /* Opaque (4 % de blanc composé sur la surface) : un en-tête sticky ne doit pas
324
+ laisser voir les lignes qui défilent dessous. Fallback avant color-mix. */
325
+ --q-table-header-bg: #1f242b;
326
+ --q-table-header-bg: color-mix(in srgb, #fff 4%, var(--card, #161b22));
320
327
  --q-table-hover-bg: rgb(255 255 255 / 0.06);
321
328
  --q-sidebar-border: rgb(255 255 255 / 0.12);
322
329
  --q-translucent-bg: #0f1115;
@@ -410,7 +417,9 @@ html.dark body {
410
417
  }
411
418
  .dark .q-data-grid {
412
419
  --q-data-grid-border: rgb(255 255 255 / 0.12);
413
- --q-data-grid-header-bg: rgb(255 255 255 / 0.04);
420
+ /* Opaque (cf. QTable : en-tête sticky sur surface composée) */
421
+ --q-data-grid-header-bg: #1f242b;
422
+ --q-data-grid-header-bg: color-mix(in srgb, #fff 4%, var(--card, #161b22));
414
423
  }
415
424
 
416
425
  /* Footer de table (pagination) en dark : range + sélecteur lignes/page */
@@ -3209,7 +3218,10 @@ html.dark body {
3209
3218
  /* ===== QTable ===== */
3210
3219
  .q-table {
3211
3220
  --q-table-border: rgb(0 0 0 / 0.1);
3212
- --q-table-header-bg: rgb(0 0 0 / 0.03);
3221
+ /* Opaque : 3 % de noir **composé sur la surface** (un en-tête sticky laissait
3222
+ sinon voir les lignes qui défilent dessous). Fallback avant color-mix. */
3223
+ --q-table-header-bg: #f7f7f7;
3224
+ --q-table-header-bg: color-mix(in srgb, #000 3%, #fff);
3213
3225
  --q-table-hover-bg: rgb(0 0 0 / 0.04);
3214
3226
 
3215
3227
  position: relative;
@@ -3233,7 +3245,8 @@ html.dark body {
3233
3245
 
3234
3246
  .q-table--dark {
3235
3247
  --q-table-border: rgb(255 255 255 / 0.15);
3236
- --q-table-header-bg: rgb(255 255 255 / 0.06);
3248
+ --q-table-header-bg: #2b2b2b;
3249
+ --q-table-header-bg: color-mix(in srgb, #fff 6%, #1d1d1d);
3237
3250
  --q-table-hover-bg: rgb(255 255 255 / 0.06);
3238
3251
  background-color: #1d1d1d;
3239
3252
  color: #fff;
@@ -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: absolute;
4840
- top: calc(100% + 8px);
4857
+ position: fixed;
4858
+ top: 0;
4841
4859
  left: 0;
4842
- right: 0;
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;
@@ -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
- --q-data-grid-header-bg: rgb(0 0 0 / 0.03);
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: rgb(255 255 255 / 0.06);
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
  }
@@ -9583,11 +9603,13 @@ a.q-breadcrumbs__el:hover {
9583
9603
  width: 14px;
9584
9604
  height: 14px;
9585
9605
  }
9586
- .q-tiptap__font-size .q-select__popup {
9587
- width: 100px;
9606
+ /* Popup inline téléporté dans <body> : impossible de le styler depuis le parent — on
9607
+ passe par la classe du popup (`inline-options.class`) ; la largeur, elle, vient de
9608
+ `inline-options.width` (l'inline posé par positionPopup() prime sur ce CSS). */
9609
+ .q-tiptap__font-size-popup {
9588
9610
  min-width: 100px;
9589
9611
  }
9590
- .q-tiptap__font-size .q-select__option-label {
9612
+ .q-tiptap__font-size-popup .q-select__option-label {
9591
9613
  font-size: 12px;
9592
9614
  }
9593
9615
 
@@ -9777,7 +9799,9 @@ a.q-breadcrumbs__el:hover {
9777
9799
  /* ===== QSpreadsheet (tableur type Excel) ===== */
9778
9800
  .q-spreadsheet {
9779
9801
  --q-spreadsheet-border: rgb(0 0 0 / 0.1);
9780
- --q-spreadsheet-header-bg: rgb(0 0 0 / 0.03);
9802
+ /* Opaque (cf. QTable : entêtes sticky des colonnes/lignes) */
9803
+ --q-spreadsheet-header-bg: #f7f7f7;
9804
+ --q-spreadsheet-header-bg: color-mix(in srgb, #000 3%, #fff);
9781
9805
  --q-spreadsheet-row-alt: rgb(0 0 0 / 0.015);
9782
9806
  --q-spreadsheet-range: rgb(25 118 210 / 0.1);
9783
9807
  --q-spreadsheet-range-brd: var(--primary, #1976d2);
@@ -9806,7 +9830,8 @@ a.q-breadcrumbs__el:hover {
9806
9830
  .q-spreadsheet--dark,
9807
9831
  .dark .q-spreadsheet {
9808
9832
  --q-spreadsheet-border: rgb(255 255 255 / 0.12);
9809
- --q-spreadsheet-header-bg: rgb(255 255 255 / 0.05);
9833
+ --q-spreadsheet-header-bg: #22262d;
9834
+ --q-spreadsheet-header-bg: color-mix(in srgb, #fff 5%, var(--card, #1d1d1d));
9810
9835
  --q-spreadsheet-row-alt: rgb(255 255 255 / 0.02);
9811
9836
  background: var(--card, #1d1d1d);
9812
9837
  color: var(--foreground, #ececef);