Files
ObsiGate/docs/features/xlsx-sheetbar-180.md
T
bruno 634ba8a272
CI / lint (push) Successful in 2m43s
CI / security (push) Successful in 1m33s
CI / test (push) Canceled after 0s
CI / build (push) Canceled after 0s
CI / e2e (push) Canceled after 0s
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.
2026-10-08 12:00:15 -04:00

6.0 KiB

#180 — Éditeur tableur : redimensionnement au curseur, grille thémée, menu Fichier & barre épinglée

Statut : ✅ livré le 2026-10-08 (v2.54.0) — ouvert le 2026-10-05 | Effort : 1-2 jours | Impact : 🟡 Références : Roadmap · Changelog · #154 · #155 · #179 · #156

1. Constat

Retours utilisateur après #179, quatre défauts d'ergonomie et un défaut de thème :

  1. Le curseur de redimensionnement n'apparaît pas. Le redimensionnement par les marges existe depuis #156-A8 (wireResize), mais l'indicateur est invisible : .xlsx-table td[contenteditable] impose cursor: cell et les en-têtes reçoivent cursor: pointer en ligne (paintHeaders()). Les deux beat la règle table.style.cursor posée par le mousemove, y compris pendant le glissement. L'utilisateur n'a aucun retour sur la zone cliquable.
  2. La grille ne suit pas le thème. --grid-* n'est déclaré que pour le thème par défaut (dark + light) dans style.css. Les 13 autres thèmes et les modes générés contraste élevé / sépia n'en fournissent pas : la grille y reste en couleurs GitHub. themes.js applique pourtant bien les variables via setProperty, sans les jetons de grille.
  3. Les sorties de fichier sont dispersées. Télécharger, CSV et Exporter occupent trois commandes du ruban, à gauche du bouton Enregistrer.
  4. La barre n'est pas épinglée et l'éditeur ne prend pas toute la largeur du contenu quand la sidebar est masquée (contrairement aux viewers PDF / images / Excalidraw, cf. la règle :has(...) de .content-area).
  5. La pastille « Formules calculées localement » occupe une ligne entière pour annoncer un comportement normal, pas une limite.

2. Livré (A1-A4)

A1 — Curseur de redimensionnement sur les marges

wireResize() (viewer.js) remplace le table.style.cursor = … isolé par paintResizeCursor(kind, th) :

  • la table reçoit .xlsx-resize-col / .xlsx-resize-row, repint par le CSS sur la table et ses cellules (surplus des deux curseurs concurrents) ;
  • l'en-tête de bord est miroité en ligne (th.style.cursor), seul moyen de passer devant le cursor: pointer inline — l'ancienne valeur est mémorisée puis restaurée quand le pointeur quitte le bord (mouseleave) ;
  • l'état est remis à zéro au mouseup global : le curseur ne reste pas « coincé » après un glissement.

A2 — Grille compatible tous les thèmes

  • themes.js : applyTheme() dérive les jetons --grid-bg, --grid-header-bg, --grid-header-text, --grid-border et --grid-zebra des surfaces du thème actif. Le zébré passe par color-mix(… 4%, transparent) pour rester subtil sur fond clair et sombre. Un thème importé sans ces jetons est donc couvert automatiquement.
  • style.css : jetons de repli (var(--grid-bg, var(--surface)), …) pour que la grille reste lisible même sans applyTheme(), plus les deux modes contraste élevé et sépia déclarés explicitement.
  • Le fond du menu Export passe de var(--bg, #fff) (repli clair cassé en thème sombre) à var(--surface).

A3 — Menu Fichier + Enregistrer au niveau des onglets

  • buildCommandBar() renvoie un fragment fileHtml supplémentaire : menu Fichier (download, file-spreadsheet, file-text, code, printer) + bouton Enregistrer primary. Les trois commandes de sortie sortent du ruban, qui ne garde que l'édition.
  • La coquille (viewer.js) rend une barre de feuilles .xlsx-sheetbar : onglets à gauche, .xlsx-sheetbar-end (menu + Enregistrer) à droite.
  • Câblage : trackDismissable() (même contrat que #179), navigation clavier, fermeture au clic sur Télécharger / CSV, aria-haspopup / aria-expanded.
  • Identifiants stables : #xlsx-download-btn, #xlsx-csv-btn, #xlsx-save-btn conservés — seuls le conteneur (#xlsx-file-btn / #xlsx-file-menu) et la classe (.xlsx-file-menu) sont nouveaux.

A4 — Retrait de la pastille + barre épinglée pleine largeur

  • La pastille « Formules calculées localement » est retirée ; la barre de statut ne subsiste que pour la pastille lecture seule (format .xls / .ods), et disparaît entièrement quand elle n'a rien à afficher.
  • .xlsx-toolbar devient position: sticky; top: 0 avec des marges négatives qui neutralizing le padding de .content-area : la barre se colle au bord haut de la page et l'éditeur occupe la largeur totale du contenu.
  • .content-area:has(.xlsx-viewer) rejoint les viewers plein cadre.

3. Tests

  • tests/frontend/xlsx-viewer.test.mjs — 136/136 :
    • barre de feuilles (onglets / menu / Enregistrer à droite, onglets sortis du ruban), contenu exact du menu Fichier (5 icônes dans l'ordre), toggle ;
    • curseur col-resize / row-resize sur les deux types de bord, classes de repaint, restauration du curseur d'en-tête à la sortie ;
    • jetons --grid-* pour 4 thèmes (Spotify dark, Notion light, GitHub contrasté élevé, GitHub sépia) + zébré color-mix ;
    • absence de pastille formules (xlsx et csv).
  • tests/frontend/xlsx-menus.test.mjs — 12/12 : icônes de fileHtml.
  • tests/e2e/xlsx-viewer.spec.js : le menu Fichier s'ouvre, expose les 5 entrées, se ferme au clic extérieur ; Enregistrer présent dans .xlsx-sheetbar-end.
  • 8 tests préexistants réalignés sur la sémantique « écriture verrouillée puis application locale » (le viewer n'est plus re-lu après une écriture structure / mise en forme) : le second appel PUT n'a plus lieu, les assertions portent sur la grille peinte en place.

4. Points d'attention

  • La copie des cellules vers le presse-papiers système et l'export sont inchangés : seul le point d'entrée des sorties de fichier a bougé.
  • viewer.js reste le propriétaire du câblage ; command-bar.js reste un constructeur de chaînes pur, donc testable sans DOM.