feat: éditeur tableur — grille A → Z par blocs, barre de menus Sheets, panneau de couleurs, création .xlsx (#180 → #186)
Restaure et termine le lot tableur resté non committé. Il n'existait que dans un `git stash` (24 fichiers suivis) et en fichiers non suivis (menus.js, formula.js, color-picker.js, tests, fiches) : la 2.53.3 livrée ne le contenait donc pas. Le stash, créé avant le commit BUG-108, n'a jamais été restauré. #180 Redimensionnement au curseur (en-tête de bord miroité + classe de repaint), grille thémée sur les 15 thèmes et les modes contraste élevé / sépia, menu Fichier au niveau des onglets, barre épinglée pleine largeur. #181 Modèle de saisie Google Sheets : sélection ≠ édition (caret masqué sans quitter contenteditable, double-clic / F2 / première frappe qui remplace, Entrée contextuelle, Échap qui restaure) + inventaire priorisé des écarts. #182 Désélection fiable (clic simple sans dépendre du focus), contours de plage non empilés (sélecteur de classes sans point), couleurs texte/fond sur une plage, clic droit qui préserve la sélection multiple. #183 Panneau de couleurs façon Google Sheets : palette 8 × 10, STANDARD, PERSONNALISÉ, coche selon la luminance, sortie `#rrggbb` (une valeur HSL était rejetée par normHex). #184 Peinture de format complète (toutes propriétés, source sans format = réinitialisation), sélection multi-lignes/colonnes depuis les marges, grille étendue : colonnes A → Z d'emblée, lignes ajoutées PAR BLOCS DE 100 au défilement jusqu'à 1000 — matérialiser 1000 lignes d'un coup = ~26 000 cellules câblées par feuille, ce qui épuisait le tas de la suite JSDOM ; index de cellules `ref → td`, court-circuits formule/styles, marqueur data-wired. #185 Barre de menus : les 10 menus Google Sheets (161 entrées, 125 câblées, 36 annoncées indisponibles), ruban façon Sheets, grille unie 1 px dérivée du thème. #186 « Créer un fichier » propose .xlsx et construit un vrai classeur OPC (openpyxl) au lieu d'une charge utile texte illisible. Corrections trouvées en restaurant et en exerçant le lot : - fuite mémoire : écouteur `click` anonyme posé sur #content-area à chaque rendu, jamais retiré — sa fermeture retenait la grille précédente en entier ; - sorties Markdown / HTML / Imprimer de la barre de menus inertes (`data-xlsx-export` jamais réparti, seul `data-xlsx-action` l'était) ; - collision de classe `.xlsx-structure-menu` entre la barre de menus et le menu Structure de la barre d'outils (toute requête tombait sur un nœud masqué) ; - curseur col/row-resize absent quand le pointeur est sur la table elle-même ; - l'export emportait les lignes et colonnes vides du quadrillage (CSV, Markdown, HTML, impression) ; - clic extérieur avalé par la grâce de 250 ms du menu contextuel (destinée au seul appui long tactile) ; - une entrée indisponible laissait la barre de menus ouverte. Tests : pytest 1595 passés / 2 ignorés ; ruff et mypy 0 erreur ; bandit 0 ; 33 suites frontend vertes (xlsx-viewer 163/163, xlsx-menus 19/19, xlsx-formula 14/14) ; E2E complet 132 passés / 12 ignorés ; E2E xlsx-viewer 19/19.
This commit is contained in:
+732
-52
@@ -62,7 +62,7 @@
|
||||
--grid-bg: #161b22;
|
||||
--grid-header-bg: #1f2430;
|
||||
--grid-header-text: #e6edf3;
|
||||
--grid-border: #30363d;
|
||||
--grid-border: #3d444d;
|
||||
--grid-zebra: rgba(255, 255, 255, 0.025);
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
--grid-bg: #161b22;
|
||||
--grid-header-bg: #1f2430;
|
||||
--grid-header-text: #e6edf3;
|
||||
--grid-border: #30363d;
|
||||
--grid-border: #3d444d;
|
||||
--grid-zebra: rgba(255, 255, 255, 0.025);
|
||||
}
|
||||
|
||||
@@ -160,10 +160,28 @@
|
||||
--grid-bg: #ffffff;
|
||||
--grid-header-bg: #eaeef2;
|
||||
--grid-header-text: #1f2328;
|
||||
--grid-border: #d0d7de;
|
||||
--grid-border: #b6bec9;
|
||||
--grid-zebra: rgba(0, 0, 0, 0.025);
|
||||
}
|
||||
|
||||
/* ===== THEME — HIGH-CONTRAST (generated mode, see themes.js) ===== */
|
||||
:root[data-theme="high-contrast"] {
|
||||
--grid-bg: #000000;
|
||||
--grid-header-bg: #141414;
|
||||
--grid-header-text: #ffffff;
|
||||
--grid-border: #6b6b6b;
|
||||
--grid-zebra: rgba(255, 255, 255, 0.045);
|
||||
}
|
||||
|
||||
/* ===== THEME — SEPIA (generated mode, see themes.js) ===== */
|
||||
:root[data-theme="sepia"] {
|
||||
--grid-bg: #f5ecd5;
|
||||
--grid-header-bg: #e8dcc0;
|
||||
--grid-header-text: #3d2e1a;
|
||||
--grid-border: #b09a6d;
|
||||
--grid-zebra: rgba(61, 46, 26, 0.04);
|
||||
}
|
||||
|
||||
/* ===== BASE ===== */
|
||||
html {
|
||||
font-size: 16px;
|
||||
@@ -1491,10 +1509,12 @@ select {
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
/* Full-bleed viewers (PDF, images, Excalidraw) must use the whole width when
|
||||
the navigation sidebar is hidden instead of the centered reading column. */
|
||||
/* Full-bleed viewers (PDF, images, Excalidraw, spreadsheets) must use the
|
||||
whole width when the navigation sidebar is hidden instead of the centered
|
||||
reading column. */
|
||||
.sidebar.hidden ~ .content-wrapper .content-area:has(.pdf-viewer-container),
|
||||
.sidebar.hidden ~ .content-wrapper .content-area:has(.image-viewer-container),
|
||||
.sidebar.hidden ~ .content-wrapper .content-area:has(.xlsx-viewer),
|
||||
.sidebar.hidden ~ .content-wrapper .content-area:has(iframe[src*="excalidraw-editor.html"]) {
|
||||
margin: 0;
|
||||
max-width: none;
|
||||
@@ -10998,24 +11018,33 @@ body.desktop-mode .editor-container {
|
||||
padding: 6px 12px;
|
||||
border-bottom: 1px solid var(--border-light, var(--border));
|
||||
}
|
||||
.csv-table tr:hover td {
|
||||
background: var(--surface);
|
||||
}
|
||||
/* Google Sheets : le survol d'une ligne ne teinte rien (jamais de highlight
|
||||
de ligne entière au survol). */
|
||||
|
||||
/* ── XLSX Viewer ── */
|
||||
/* #154-A1 — the viewer shell is a grouped command bar: sheet tabs on the left,
|
||||
action groups (Formules · Insertion · Vue · Fichier) on the right, then a
|
||||
status row stating the viewer's limits (read-only, formulas not recalculated). */
|
||||
/* #154-A1 — the viewer shell is a grouped command bar (edition commands),
|
||||
then a sheet strip (tabs on the left, File menu + Save on the right), then
|
||||
a status row stating the viewer's limits (read-only). #180 pins the bar to
|
||||
the top edge of the page, full content width. */
|
||||
.xlsx-toolbar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
padding: 8px 10px;
|
||||
position: sticky; /* #180 — stays on the top edge while the grid scrolls */
|
||||
/* La marge haute négative (pleine largeur) tirait la barre 40 px sous sa
|
||||
position statique : `top: 0` la replaquait et elle recouvrait le contenu
|
||||
(en-tête A–Z, ligne 1) de 32 px. La contrainte reprend la même expression
|
||||
que la marge : aucun déplacement au repos, épinglage intact au scroll. */
|
||||
top: calc(-1 * clamp(16px, 3vw, 40px));
|
||||
z-index: 30;
|
||||
margin: calc(-1 * clamp(16px, 3vw, 40px)) calc(-1 * clamp(16px, 4vw, 40px)) 8px;
|
||||
padding: 8px clamp(16px, 4vw, 40px);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-top: none;
|
||||
border-radius: 0;
|
||||
background: var(--surface2);
|
||||
position: relative; /* anchors the A14 structure menu */
|
||||
}
|
||||
.xlsx-cmdbar {
|
||||
display: flex;
|
||||
@@ -11023,6 +11052,277 @@ body.desktop-mode .editor-container {
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
/* #185 — the Google Sheets menu bar: a flat row of text buttons above the
|
||||
ribbon. No chrome, no borders: only the open menu gets a filled pill. */
|
||||
.xlsx-menubar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
flex-wrap: wrap;
|
||||
margin: -2px 0 -2px;
|
||||
}
|
||||
/* No `position: relative` here on purpose: the dropdown is positioned by
|
||||
anchorUnder() (viewer.js) relative to the sticky `.xlsx-toolbar`, exactly like
|
||||
the Structure / Mise en forme / ribbon menus. Making the wrapper a containing
|
||||
block would shift every dropdown by the button's offset. */
|
||||
.xlsx-menuwrap {
|
||||
display: inline-flex;
|
||||
}
|
||||
.xlsx-menubtn {
|
||||
appearance: none;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
font-family: inherit;
|
||||
font-size: 0.82rem;
|
||||
line-height: 1.4;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.xlsx-menubtn:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.xlsx-menubtn[aria-expanded="true"] {
|
||||
background: var(--accent-bg, var(--bg-hover));
|
||||
color: var(--accent-text, var(--accent, #4a90d9));
|
||||
}
|
||||
/* The Gemini entry keeps its own accent so it reads as the AI corner of the
|
||||
bar, like the themed icon in Google Sheets. */
|
||||
.xlsx-menuwrap-gemini .xlsx-menubtn {
|
||||
color: var(--accent, #4a90d9);
|
||||
font-weight: 600;
|
||||
}
|
||||
.xlsx-menudrop {
|
||||
min-width: 260px;
|
||||
max-height: min(70vh, 560px);
|
||||
overflow-y: auto;
|
||||
z-index: 40;
|
||||
}
|
||||
/* Accordion section (Nombre, Thème, Zoom, Alignement, …). */
|
||||
.xlsx-menu-sub {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
padding-left: 18px;
|
||||
border-left: 2px solid var(--border);
|
||||
margin: 2px 0 4px 8px;
|
||||
}
|
||||
.xlsx-menu-sub[hidden] {
|
||||
display: none;
|
||||
}
|
||||
.xlsx-menu-item {
|
||||
width: 100%;
|
||||
}
|
||||
.xlsx-menu-item-d2 {
|
||||
padding-left: 18px;
|
||||
}
|
||||
.xlsx-menu-caret {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
flex: 0 0 auto;
|
||||
margin-left: auto;
|
||||
color: var(--text-muted);
|
||||
transition: transform 0.12s ease;
|
||||
}
|
||||
.xlsx-menu-item[aria-expanded="true"] > .xlsx-menu-caret {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
/* An entry ObsiGate does not implement: dimmed but still clickable, so it
|
||||
answers instead of pretending. */
|
||||
.xlsx-menu-item-na {
|
||||
opacity: 0.55;
|
||||
}
|
||||
.xlsx-menu-item-na:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.xlsx-menu-empty {
|
||||
padding: 6px 10px;
|
||||
font-size: 0.78rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
/* #185 — Google Sheets' ribbon look: flat square icon buttons, no border and
|
||||
no resting background, grouped by thin vertical rules. The old rounded /
|
||||
bordered chrome fought with the theme of every one of the 15 themes. */
|
||||
.xlsx-toolbar .btn-action {
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
min-width: 30px;
|
||||
height: 30px;
|
||||
padding: 0 6px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.xlsx-toolbar .btn-action:hover:not(:disabled) {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.xlsx-toolbar .btn-action.active {
|
||||
background: var(--accent-bg, var(--bg-hover));
|
||||
color: var(--accent-text, var(--accent, #4a90d9));
|
||||
}
|
||||
.xlsx-toolbar .btn-action:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
}
|
||||
.xlsx-toolbar .xlsx-cmd-sep {
|
||||
width: 1px;
|
||||
height: 22px;
|
||||
background: var(--border);
|
||||
}
|
||||
.xlsx-toolbar .xlsx-select {
|
||||
height: 30px;
|
||||
border-color: transparent;
|
||||
background: transparent;
|
||||
}
|
||||
.xlsx-toolbar .xlsx-select:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
/* Save stays the one solid pill: it is the only command with a consequence
|
||||
outside the browser. */
|
||||
.xlsx-toolbar .xlsx-save-primary {
|
||||
border-color: var(--accent);
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.xlsx-toolbar .xlsx-save-primary:hover:not(:disabled) {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
filter: brightness(1.08);
|
||||
}
|
||||
.xlsx-formula-bar[hidden] {
|
||||
display: none;
|
||||
}
|
||||
/* Wrapped command groups: every control stays visible, no scrollbar. */
|
||||
.xlsx-cmdbar > .xlsx-toolbar-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
overflow: visible;
|
||||
}
|
||||
.xlsx-cmdbar .xlsx-cmd-group,
|
||||
.xlsx-cmdbar .xlsx-cmd-sep {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
/* Native selects (zoom, number format): no custom dropdown to dismiss. */
|
||||
.xlsx-select {
|
||||
font-family: inherit;
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
background: transparent;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 4px 6px;
|
||||
cursor: pointer;
|
||||
max-width: 130px;
|
||||
}
|
||||
.xlsx-select:hover {
|
||||
color: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
/* B/I/S/U letter buttons + decimal shorthands. */
|
||||
.xlsx-fontstyle {
|
||||
font-family: Georgia, "Times New Roman", serif;
|
||||
min-width: 28px;
|
||||
}
|
||||
.xlsx-dec {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
}
|
||||
.xlsx-fontsize {
|
||||
min-width: 26px;
|
||||
text-align: center;
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
/* Colour pickers: a letter/swatch label wrapping the native input. */
|
||||
.xlsx-colorpick {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.xlsx-colorpick input[type="color"] {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
/* #182 — colour swatch under the picker: mirrors the chosen colour so applying
|
||||
one to a selection is visible on the button itself. Empty (transparent)
|
||||
until a colour is actually picked, so the default ribbon stays clean. */
|
||||
.xlsx-color-swatch {
|
||||
width: 14px;
|
||||
height: 4px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--border);
|
||||
background: transparent;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.xlsx-colorletter {
|
||||
font-family: Georgia, serif;
|
||||
font-weight: 700;
|
||||
border-bottom: 3px solid var(--accent);
|
||||
line-height: 1;
|
||||
padding: 0 2px;
|
||||
}
|
||||
/* Ribbon dropdown menus share the structure-menu look. */
|
||||
.xlsx-ribbon-menu {
|
||||
min-width: 220px;
|
||||
}
|
||||
/* Floating contextual mini-bar (multi-cell selections). */
|
||||
.xlsx-minibar {
|
||||
position: fixed;
|
||||
z-index: 10060;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
padding: 4px 6px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 8px 24px var(--shadow, rgba(0, 0, 0, 0.25));
|
||||
}
|
||||
.xlsx-minibar[hidden] {
|
||||
display: none;
|
||||
}
|
||||
.xlsx-minibar button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
}
|
||||
.xlsx-minibar button:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--accent);
|
||||
}
|
||||
.xlsx-minibar button svg,
|
||||
.xlsx-minibar button i[data-lucide] {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
@media (pointer: coarse) {
|
||||
.xlsx-minibar button {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
}
|
||||
.xlsx-toolbar-actions {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
@@ -11082,6 +11382,31 @@ body.desktop-mode .editor-container {
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
/* #180 — sheet strip: tabs on the left, File menu + Save pinned right. */
|
||||
.xlsx-sheetbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.xlsx-sheetbar .xlsx-tabs {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
.xlsx-sheetbar-end {
|
||||
margin-left: auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.xlsx-file-dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
.xlsx-file-item {
|
||||
text-align: left;
|
||||
}
|
||||
.xlsx-status-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -11162,28 +11487,45 @@ body.desktop-mode .editor-container {
|
||||
white-space: pre-line;
|
||||
}
|
||||
.xlsx-table {
|
||||
background: var(--grid-bg);
|
||||
background: var(--grid-bg, var(--surface));
|
||||
}
|
||||
/* #185 — one uniform 1px grid line on EVERY side of every cell (header rows
|
||||
included), the way Sheets draws it. Previously only the bottom edge existed
|
||||
and its colour came from `--border`, which is far too close to the cell
|
||||
background on most themes: the grid read as a smear instead of a grid.
|
||||
`--grid-border` is now derived from the theme by themes.js (same token for
|
||||
every line, so the mesh stays perfectly regular). */
|
||||
.xlsx-table td,
|
||||
.xlsx-table th {
|
||||
border: 1px solid var(--grid-border, var(--border));
|
||||
}
|
||||
.xlsx-table td {
|
||||
border-bottom: 1px solid var(--grid-border);
|
||||
border-bottom: 1px solid var(--grid-border, var(--border));
|
||||
/* Cellules de données : une seule et même couleur (fond de grille). Pas de
|
||||
variante une-ligne-sur-deux ici : sa spécificité écraserait la teinte de
|
||||
sélection (une ligne sur deux non teintée). */
|
||||
background: var(--grid-bg, var(--surface));
|
||||
}
|
||||
/* #154-A1 — zebra + hover make rows scannable; headers use dedicated tokens so
|
||||
they are visually distinct from the cells. */
|
||||
.xlsx-table tbody tr:nth-child(even) td {
|
||||
background: var(--grid-zebra);
|
||||
}
|
||||
.xlsx-table tbody tr:hover td {
|
||||
background: var(--bg-hover);
|
||||
/* Cellules fusionnées : couleur d'en-tête, sauf en sélection (la teinte de
|
||||
sélection gagne, uniforme sur toutes les cellules). */
|
||||
.xlsx-table tbody td[rowspan]:not([rowspan="1"]):not(.xlsx-selected),
|
||||
.xlsx-table tbody td[colspan]:not([colspan="1"]):not(.xlsx-selected) {
|
||||
background: var(--grid-header-bg, var(--bg-hover));
|
||||
color: var(--grid-header-text, var(--text-primary));
|
||||
}
|
||||
/* Google Sheets : aucun survol teinté — ni la ligne entière, ni la cellule.
|
||||
Rien ne se met en couleur tant que l'utilisateur n'a pas sélectionné au
|
||||
moins 2 cellules (voir `.xlsx-range-multi`). */
|
||||
.xlsx-table th.xlsx-corner,
|
||||
.xlsx-table th.xlsx-rownum {
|
||||
background: var(--grid-header-bg);
|
||||
color: var(--grid-header-text);
|
||||
background: var(--grid-header-bg, var(--bg-hover));
|
||||
color: var(--grid-header-text, var(--text-primary));
|
||||
font-weight: 500;
|
||||
text-align: right;
|
||||
padding: 6px 8px;
|
||||
border-bottom: 2px solid var(--grid-border);
|
||||
border-right: 1px solid var(--grid-border);
|
||||
/* #185 — the same 1px line as the cells: the header is told apart by its
|
||||
fill, not by a heavier rule that broke the grid's regularity. */
|
||||
border: 1px solid var(--grid-border, var(--border));
|
||||
position: sticky;
|
||||
left: 0;
|
||||
/* #153 A8 — `top: auto` is load-bearing: `.csv-table th` pins EVERY `th`
|
||||
@@ -11194,7 +11536,9 @@ body.desktop-mode .editor-container {
|
||||
}
|
||||
.xlsx-table th.xlsx-corner {
|
||||
left: 0;
|
||||
top: 0;
|
||||
/* Suit `--xlsx-stick` comme l'en-tête : mesuré en JS sur le recouvrement
|
||||
réel de la toolbar sticky, sinon la ligne A–Z glisse dessous. */
|
||||
top: var(--xlsx-stick, 0px);
|
||||
z-index: 4;
|
||||
}
|
||||
/* #153 A8 — the column headers stay visible while the sheet scrolls down.
|
||||
@@ -11202,11 +11546,43 @@ body.desktop-mode .editor-container {
|
||||
order is intentional: thead (3) < row numbers (2) < corner (4). */
|
||||
.xlsx-table thead th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
top: var(--xlsx-stick, 0px);
|
||||
z-index: 3;
|
||||
background: var(--grid-header-bg);
|
||||
color: var(--grid-header-text);
|
||||
border-bottom: 2px solid var(--grid-border);
|
||||
background: var(--grid-header-bg, var(--bg-hover));
|
||||
color: var(--grid-header-text, var(--text-primary));
|
||||
border-bottom: 1px solid var(--grid-border, var(--border));
|
||||
}
|
||||
/* #185 — Affichage ▸ Afficher ▸ Grille / En-têtes, and Outils ▸ Accessibilité:
|
||||
display concerns only, never a change to the file. */
|
||||
.xlsx-viewer.xlsx-no-grid .xlsx-table td,
|
||||
.xlsx-viewer.xlsx-no-grid .xlsx-table th {
|
||||
border-width: 0;
|
||||
}
|
||||
.xlsx-viewer.xlsx-no-headers thead,
|
||||
.xlsx-viewer.xlsx-no-headers th.xlsx-rownum,
|
||||
.xlsx-viewer.xlsx-no-headers th.xlsx-corner {
|
||||
display: none;
|
||||
}
|
||||
.xlsx-viewer.xlsx-a11y .xlsx-table td {
|
||||
padding: 8px 10px;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.xlsx-viewer.xlsx-a11y {
|
||||
--grid-border: color-mix(in srgb, var(--text-primary, #888) 42%, var(--surface, #000));
|
||||
}
|
||||
/* #180 — while the pointer rides a header edge, the representative resize
|
||||
icon must win over the cell (`cell`) and header (`pointer`) cursors. The
|
||||
edge header itself is mirrored inline by the viewer (its inline
|
||||
`cursor:pointer` beats any stylesheet rule). */
|
||||
.xlsx-table.xlsx-resize-col,
|
||||
.xlsx-table.xlsx-resize-col th,
|
||||
.xlsx-table.xlsx-resize-col td {
|
||||
cursor: col-resize;
|
||||
}
|
||||
.xlsx-table.xlsx-resize-row,
|
||||
.xlsx-table.xlsx-resize-row th,
|
||||
.xlsx-table.xlsx-resize-row td {
|
||||
cursor: row-resize;
|
||||
}
|
||||
.xlsx-table td[contenteditable] {
|
||||
/* #155 — Excel-style cross cursor over the cells. */
|
||||
@@ -11218,6 +11594,57 @@ body.desktop-mode .editor-container {
|
||||
outline: 2px solid var(--accent, #4a90d9);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
/* #181-A1 — SELECTION vs EDITION, like Google Sheets / Excel.
|
||||
A selected cell is outlined but must NOT show a text caret; the caret is
|
||||
only revealed in edit mode (double-click, F2 or the first keystroke).
|
||||
`caret-color: transparent` hides it without leaving `contenteditable`, so the
|
||||
click-to-select / type-to-replace flow stays native. */
|
||||
.xlsx-table td.xlsx-cell {
|
||||
caret-color: transparent;
|
||||
}
|
||||
.xlsx-table td.xlsx-editing {
|
||||
caret-color: auto;
|
||||
cursor: text;
|
||||
}
|
||||
/* The editing cell also reads as "open": a solid accent frame plus a raised
|
||||
shadow, so the state is visible even on a row that is part of a range. */
|
||||
.xlsx-table td.xlsx-editing {
|
||||
outline: 2px solid var(--accent, #4a90d9);
|
||||
outline-offset: -2px;
|
||||
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent);
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* #181-A2 — range indicator: the four edges of the selected block trace it
|
||||
exactly (Excel/Sheets draw a rectangle, not a flat wash), and the covered
|
||||
row/column headers are highlighted too. `outline` on inner edges would be
|
||||
overdrawn by the next cell, so the four borders are declared separately and
|
||||
win over `.xlsx-selected`'s background. */
|
||||
.xlsx-table tbody td.xlsx-sel-top {
|
||||
border-top: 2px solid var(--accent, #4a90d9);
|
||||
}
|
||||
.xlsx-table tbody td.xlsx-sel-bottom {
|
||||
border-bottom: 2px solid var(--accent, #4a90d9);
|
||||
}
|
||||
.xlsx-table tbody td.xlsx-sel-left {
|
||||
border-left: 2px solid var(--accent, #4a90d9);
|
||||
}
|
||||
.xlsx-table tbody td.xlsx-sel-right {
|
||||
border-right: 2px solid var(--accent, #4a90d9);
|
||||
}
|
||||
/* A one-cell selection keeps its four accent edges (a framed box) but NO fill:
|
||||
`.xlsx-range-multi` is toggled on the viewer by applySelection() and gates
|
||||
the tint, which starts at 2 cells — Google Sheets behaviour. */
|
||||
.xlsx-viewer.xlsx-range-multi .xlsx-table tbody td.xlsx-selected {
|
||||
background: var(--accent-bg);
|
||||
}
|
||||
.xlsx-table thead th.xlsx-hdr-selected,
|
||||
.xlsx-table th.xlsx-rownum.xlsx-hdr-selected {
|
||||
background: var(--accent-bg);
|
||||
color: var(--accent-text, var(--accent, #4a90d9));
|
||||
font-weight: 700;
|
||||
}
|
||||
/* #153 A7 — the active cell keeps its outline even when focus moves to the
|
||||
formula bar, so the user never loses track of what the bar edits.
|
||||
#154-A1 — solid (not dashed) for a stronger active-cell affordance. */
|
||||
@@ -11226,25 +11653,47 @@ body.desktop-mode .editor-container {
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
/* #153 A7 — formula bar under the toolbar: [ A1 | > | input ] */
|
||||
/* Barre d'édition de la cellule, juste au-dessus de la grille :
|
||||
[ position | champ d'édition ] + recherche. Bandeau distinct du fond pour
|
||||
rester identifiable dans tous les thèmes (le fond transparent la rendait
|
||||
invisible). Elle vit DANS la barre d'outils sticky (dernier rang) : la
|
||||
toolbar restait sinon peinte par-dessus au défilement. */
|
||||
.xlsx-formula-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
padding: 6px 8px;
|
||||
background: var(--surface2, var(--surface));
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
}
|
||||
/* Dernier rang de la toolbar sticky : l'écart est déjà géré par son `gap`. */
|
||||
.xlsx-toolbar .xlsx-formula-bar {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
/* Zone d'édition : position de la cellule | champ d'édition. */
|
||||
.xlsx-editbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
/* #156-A7 — the Name Box is editable (“Atteindre”): a plain input styled like
|
||||
the read-only badge it replaced. */
|
||||
.xlsx-active-cell {
|
||||
width: 84px;
|
||||
flex: 0 0 auto;
|
||||
box-sizing: border-box;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid var(--border);
|
||||
border: 1px solid var(--grid-border, var(--border));
|
||||
border-radius: 4px;
|
||||
background: var(--surface);
|
||||
color: var(--text-primary);
|
||||
font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
@@ -11252,17 +11701,22 @@ body.desktop-mode .editor-container {
|
||||
outline: 2px solid var(--accent, #4a90d9);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
.xlsx-formula-sep {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
/* Pastille « fx » façon tableur : repère visuel du champ d'édition. */
|
||||
.xlsx-edit-fx {
|
||||
flex: 0 0 auto;
|
||||
font-family: Georgia, 'Times New Roman', serif;
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-muted);
|
||||
padding: 0 2px;
|
||||
user-select: none;
|
||||
}
|
||||
.xlsx-formula-input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 5px 10px;
|
||||
border: 1px solid var(--border);
|
||||
border: 1px solid var(--grid-border, var(--border));
|
||||
border-radius: 4px;
|
||||
background: var(--surface);
|
||||
color: var(--text-primary);
|
||||
@@ -11334,7 +11788,7 @@ body.desktop-mode .editor-container {
|
||||
min-width: 200px;
|
||||
max-width: 86vw;
|
||||
padding: 4px;
|
||||
background: var(--bg, #fff);
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.18);
|
||||
@@ -11393,18 +11847,23 @@ body.desktop-mode .editor-container {
|
||||
.xlsx-table tr.xlsx-frozen-row td,
|
||||
.xlsx-table tr.xlsx-frozen-row th {
|
||||
position: sticky;
|
||||
top: 33px; /* thead height — keeps the frozen row below the header */
|
||||
/* Hauteur de l'en-tête + décalage toolbar sticky (`--xlsx-stick`). */
|
||||
top: calc(var(--xlsx-stick, 0px) + 33px); /* thead height — keeps the frozen row below the header */
|
||||
z-index: 2;
|
||||
background: var(--grid-header-bg);
|
||||
background: var(--grid-header-bg, var(--bg-hover));
|
||||
}
|
||||
.xlsx-table td.xlsx-frozen-col {
|
||||
position: sticky;
|
||||
left: 44px; /* the row-number column width */
|
||||
background: var(--grid-bg);
|
||||
background: var(--grid-bg, var(--surface));
|
||||
}
|
||||
|
||||
/* #153 A14 — structure menu (sheets / rows / columns) */
|
||||
.xlsx-structure-menu {
|
||||
/* #185 — the menu-bar dropdowns share the toolbar menus' look; the toolbar
|
||||
menus keep the `.xlsx-structure-menu` class to themselves so `.xlsx-structure-menu`
|
||||
queries (viewer, tests, E2E) only ever match the open toolbar menu. */
|
||||
.xlsx-structure-menu,
|
||||
.xlsx-menudrop {
|
||||
position: absolute;
|
||||
z-index: 30;
|
||||
display: flex;
|
||||
@@ -11412,7 +11871,8 @@ body.desktop-mode .editor-container {
|
||||
gap: 2px;
|
||||
min-width: 240px;
|
||||
max-width: min(320px, 92vw);
|
||||
margin-top: 4px;
|
||||
/* Positioned under its toggle by anchorUnder() (viewer.js), which already
|
||||
adds the 4px gap. */
|
||||
padding: 6px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
@@ -11610,6 +12070,29 @@ body.desktop-mode .editor-container {
|
||||
background: var(--border);
|
||||
}
|
||||
|
||||
/* Resize ghost: fixed guide line following the pointer while dragging a
|
||||
header edge (column widths / row heights). */
|
||||
.xlsx-resize-ghost {
|
||||
position: fixed;
|
||||
z-index: 10060;
|
||||
margin: 0;
|
||||
background: var(--accent, #4a90d9);
|
||||
pointer-events: none;
|
||||
}
|
||||
.xlsx-resize-ghost[data-kind="col"] { width: 2px; }
|
||||
.xlsx-resize-ghost[data-kind="row"] { height: 2px; }
|
||||
|
||||
/* Comment marker: content-free so copy/export/save text stays clean. */
|
||||
.xlsx-comment-dot::after {
|
||||
content: "\1F4AC";
|
||||
font-size: 0.68em;
|
||||
margin-left: 3px;
|
||||
vertical-align: super;
|
||||
cursor: help;
|
||||
}
|
||||
/* Computed formula links behave like spreadsheet links. */
|
||||
.xlsx-formula-link { color: var(--accent, #4a90d9); text-decoration: underline; }
|
||||
|
||||
/* #179 — shared menu-entry icon (grid context menu, structure, format, export). */
|
||||
.xlsx-menu-icon {
|
||||
width: 14px;
|
||||
@@ -11636,10 +12119,9 @@ body.desktop-mode .editor-container {
|
||||
outline: 2px solid var(--accent, #4a90d9);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
/* #155 — Excel-like range selection + right-click / long-press context menu. */
|
||||
.xlsx-table tbody td.xlsx-selected {
|
||||
background: var(--accent-bg);
|
||||
}
|
||||
/* #155 — Excel-like range selection + right-click / long-press context menu.
|
||||
Le remplissage de la sélection est déclaré plus haut et conditionné par
|
||||
`.xlsx-range-multi` : pas de fond teinté pour une sélection d'une cellule. */
|
||||
.xlsx-viewer.xlsx-selecting,
|
||||
.xlsx-viewer.xlsx-selecting td {
|
||||
user-select: none;
|
||||
@@ -11673,7 +12155,8 @@ body.desktop-mode .editor-container {
|
||||
.xlsx-context-menu {
|
||||
min-width: min(260px, 86vw);
|
||||
}
|
||||
.xlsx-structure-menu {
|
||||
.xlsx-structure-menu,
|
||||
.xlsx-menudrop {
|
||||
max-width: 92vw;
|
||||
}
|
||||
}
|
||||
@@ -11703,6 +12186,203 @@ body.desktop-mode .editor-container {
|
||||
background: var(--border);
|
||||
}
|
||||
|
||||
/* #183 — Sheets-like colour panel (text + fill). The chrome follows the
|
||||
active theme (variables only); the swatches carry their own colour inline,
|
||||
which is data, not decoration. */
|
||||
.xlsx-colorpick-wrap {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
}
|
||||
.xlsx-color-native {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
.xlsx-colour-panel {
|
||||
position: absolute;
|
||||
z-index: 40;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 236px;
|
||||
padding: 10px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 6px 18px var(--shadow, rgba(0, 0, 0, 0.22));
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.xlsx-colour-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.xlsx-colour-reset {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.8rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
.xlsx-colour-reset:hover {
|
||||
color: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.xlsx-colour-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
/* 8 columns x 10 rows = the 80 curated swatches. */
|
||||
.xlsx-colour-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, 1fr);
|
||||
gap: 4px;
|
||||
}
|
||||
.xlsx-colour-swatch-btn {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
aspect-ratio: 1 / 1;
|
||||
min-width: 20px;
|
||||
padding: 0;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.xlsx-colour-swatch-btn:hover {
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 55%, transparent);
|
||||
}
|
||||
.xlsx-colour-swatch-btn.selected {
|
||||
box-shadow: 0 0 0 2px var(--accent);
|
||||
}
|
||||
.xlsx-colour-check {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.xlsx-colour-section {
|
||||
margin-top: 10px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
.xlsx-colour-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 6px;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.68rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.xlsx-colour-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.xlsx-colour-row .xlsx-colour-swatch-btn {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
.xlsx-colour-add {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
padding: 0;
|
||||
border: 1px dashed var(--border);
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
}
|
||||
.xlsx-colour-add:hover {
|
||||
color: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.xlsx-colour-edit {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
}
|
||||
.xlsx-colour-edit:hover {
|
||||
color: var(--accent);
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
.xlsx-colour-foot {
|
||||
margin-top: 10px;
|
||||
padding-top: 6px;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
.xlsx-colour-foot-item {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 6px 4px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background: none;
|
||||
color: var(--text-primary);
|
||||
font-size: 0.8rem;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
.xlsx-colour-foot-item:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
/* Mise en forme menu: the colour rows now carry a trigger + a live dot. */
|
||||
.xlsx-format-colour {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 10px;
|
||||
color: var(--text-primary);
|
||||
font-size: 13px;
|
||||
}
|
||||
.xlsx-format-colour-btn {
|
||||
margin-left: auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 2px 4px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
background: var(--surface);
|
||||
cursor: pointer;
|
||||
}
|
||||
.xlsx-format-colour-btn:hover {
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
.xlsx-format-colour-dot {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--border);
|
||||
background: linear-gradient(135deg, var(--accent) 0%, var(--accent-green, var(--accent)) 100%);
|
||||
}
|
||||
.xlsx-colour-panel {
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
/* JSDOM shims for the tests that click anchors */
|
||||
mark {
|
||||
font: inherit;
|
||||
@@ -11785,7 +12465,7 @@ mark {
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
padding: 8px 10px;
|
||||
margin-bottom: 8px;
|
||||
margin: 8px 0;
|
||||
border: 1px solid var(--border);
|
||||
border-left: 3px solid var(--accent, #4a90d9);
|
||||
border-radius: 4px;
|
||||
|
||||
Reference in New Issue
Block a user