fusion #70 : retirer le bouton trois points, Console Spark au menu marque #28
@@ -56,7 +56,6 @@
|
|||||||
<span class="draco-spark-ico">✦</span><span class="draco-spark-txt">Spark</span>
|
<span class="draco-spark-ico">✦</span><span class="draco-spark-txt">Spark</span>
|
||||||
<span class="draco-badge" data-badge="spark" hidden>0</span>
|
<span class="draco-badge" data-badge="spark" hidden>0</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="draco-icon-btn" data-act="menu" aria-label="Plus d'actions" aria-haspopup="menu" aria-expanded="false">•••</button>
|
|
||||||
<button type="button" class="draco-avatar" data-act="compte" aria-label="Compte" aria-haspopup="menu" aria-expanded="false" title="{{ user.get('full_name') if user and user.get('full_name') else '' }}">{{ user.full_name[:1].upper() if user and user.get('full_name') else 'F' }}</button>
|
<button type="button" class="draco-avatar" data-act="compte" aria-label="Compte" aria-haspopup="menu" aria-expanded="false" title="{{ user.get('full_name') if user and user.get('full_name') else '' }}">{{ user.full_name[:1].upper() if user and user.get('full_name') else 'F' }}</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -87,13 +86,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (act === "palette") { dire("Recherche fédérée ⌘K : arrive avec la fédération (programme Fusion)."); return; }
|
if (act === "palette") { dire("Recherche fédérée ⌘K : arrive avec la fédération (programme Fusion)."); return; }
|
||||||
/* v0.1.6 : le toast placeholder "fédération" est remplacé
|
/* Correctif fusion #70 : action "menu" supprimée avec le bouton trois points (console agent dans le menu de la marque). Nonce CSP du script inchangé. */
|
||||||
par le menu par défaut du nouveau JS — retourner false le
|
|
||||||
demande explicitement (un onAction existant garde sinon la
|
|
||||||
main). Contenu : Console agent + applications, URL résolues
|
|
||||||
donc dérivées -dev sur un hôte -dev. Nonce CSP du script
|
|
||||||
inchangé. */
|
|
||||||
if (act === "menu") { return false; }
|
|
||||||
if (act === "spark") {
|
if (act === "spark") {
|
||||||
if (window.DRACO_PANNEAU) { window.DRACO_PANNEAU.toggle(); }
|
if (window.DRACO_PANNEAU) { window.DRACO_PANNEAU.toggle(); }
|
||||||
else { dire("Spark : le panneau n'a pas pu se charger."); }
|
else { dire("Spark : le panneau n'a pas pu se charger."); }
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
v0.1.3 : la marque ✦ (plus grande) ouvre le menu des applications —
|
v0.1.3 : la marque ✦ (plus grande) ouvre le menu des applications —
|
||||||
c'est LE changement d'app en mobile (choix de Bruno, 2026-10-21) ;
|
c'est LE changement d'app en mobile (choix de Bruno, 2026-10-21) ;
|
||||||
v0.1.4 : palier mobile élargi de 700 à 900 px (zone morte comblée) ;
|
v0.1.4 : palier mobile élargi de 700 à 900 px (zone morte comblée) ;
|
||||||
v0.1.6 : menus « ••• » et compte (construits par le JS, mêmes jetons
|
v0.1.6 : menu compte (construit par le JS, mêmes jetons que le
|
||||||
que le menu des applications) ; l'avatar est un vrai <button>.
|
menu des applications) ; l'avatar est un vrai <button>.
|
||||||
|
Correctif fusion #70 (SANS bump) : bouton trois points et menu par
|
||||||
|
défaut supprimés ; le menu de la marque gagne « Console Spark ».
|
||||||
Jetons repris du prototype validé Console v7.2.
|
Jetons repris du prototype validé Console v7.2.
|
||||||
Thème : clair par défaut, sombre via [data-theme="dark"] sur <html>
|
Thème : clair par défaut, sombre via [data-theme="dark"] sur <html>
|
||||||
ou prefers-color-scheme si l'app intégratrice ne force rien. */
|
ou prefers-color-scheme si l'app intégratrice ne force rien. */
|
||||||
@@ -34,6 +36,7 @@
|
|||||||
/* Marque — bouton du menu des applications (v0.1.3) */
|
/* Marque — bouton du menu des applications (v0.1.3) */
|
||||||
.draco-brand { display: flex; align-items: center; gap: 7px; color: var(--ink); text-decoration: none; font-weight: 800; cursor: pointer; border-radius: 10px; padding: 4px 6px; margin: -4px -6px; }
|
.draco-brand { display: flex; align-items: center; gap: 7px; color: var(--ink); text-decoration: none; font-weight: 800; cursor: pointer; border-radius: 10px; padding: 4px 6px; margin: -4px -6px; }
|
||||||
.draco-brand:hover { background: var(--surface-2); }
|
.draco-brand:hover { background: var(--surface-2); }
|
||||||
|
.draco-brand:focus-visible { outline: 2px solid var(--agent); outline-offset: 2px; }
|
||||||
.draco-brand-mark { color: var(--agent); font-size: 24px; line-height: 1; }
|
.draco-brand-mark { color: var(--agent); font-size: 24px; line-height: 1; }
|
||||||
|
|
||||||
/* Menu des applications — construit par le JS depuis les onglets */
|
/* Menu des applications — construit par le JS depuis les onglets */
|
||||||
@@ -45,15 +48,15 @@
|
|||||||
}
|
}
|
||||||
.draco-apps-menu[hidden] { display: none; }
|
.draco-apps-menu[hidden] { display: none; }
|
||||||
|
|
||||||
/* Menus « ••• » et compte (v0.1.6) — même boîte que le menu des apps,
|
/* Menu compte (v0.1.6) — même boîte que le menu des apps, ancré à
|
||||||
* ancrés à droite ; leurs items réutilisent .draco-apps-item. */
|
* droite ; ses items réutilisent .draco-apps-item. */
|
||||||
.draco-more-menu, .draco-compte-menu {
|
.draco-compte-menu {
|
||||||
position: absolute; top: calc(100% + 6px); right: 10px; z-index: 400;
|
position: absolute; top: calc(100% + 6px); right: 10px; z-index: 400;
|
||||||
min-width: 248px; padding: 6px;
|
min-width: 248px; padding: 6px;
|
||||||
background: var(--surface); border: 1px solid var(--line); border-radius: 14px;
|
background: var(--surface); border: 1px solid var(--line); border-radius: 14px;
|
||||||
box-shadow: 0 16px 44px rgb(0 0 0 / .28);
|
box-shadow: 0 16px 44px rgb(0 0 0 / .28);
|
||||||
}
|
}
|
||||||
.draco-more-menu[hidden], .draco-compte-menu[hidden] { display: none; }
|
.draco-compte-menu[hidden] { display: none; }
|
||||||
.draco-menu-item {
|
.draco-menu-item {
|
||||||
display: flex; align-items: center; gap: 10px; width: 100%; box-sizing: border-box;
|
display: flex; align-items: center; gap: 10px; width: 100%; box-sizing: border-box;
|
||||||
padding: 8px 10px; border: 0; border-radius: 10px;
|
padding: 8px 10px; border: 0; border-radius: 10px;
|
||||||
@@ -174,8 +177,8 @@
|
|||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
/* Mobile (spec des barres, § Mobile) : la barre se resserre sur
|
/* Mobile (spec des barres, § Mobile) : la barre se resserre sur
|
||||||
l'essentiel — marque, onglet de l'app courante, alertes, Spark,
|
l'essentiel — marque, onglet de l'app courante, alertes, Spark,
|
||||||
avatar. Le rail, la recherche, les domaines et le menu « ••• »
|
avatar. Le rail, la recherche et les domaines disparaissent :
|
||||||
disparaissent : chaque app garde ses contrôles natifs (barre
|
chaque app garde ses contrôles natifs (barre
|
||||||
basse, en-tête) — un seul hamburger visible, celui de l'app.
|
basse, en-tête) — un seul hamburger visible, celui de l'app.
|
||||||
Le changement d'app passe par le menu du ✦ (v0.1.3).
|
Le changement d'app passe par le menu du ✦ (v0.1.3).
|
||||||
(v0.1.2 — avant, les onglets entiers et les pastilles domaines
|
(v0.1.2 — avant, les onglets entiers et les pastilles domaines
|
||||||
|
|||||||
Vendored
+139
-94
@@ -4,7 +4,11 @@
|
|||||||
* environnement particulier n'est codé en dur, voir « URL » ci-dessous ;
|
* environnement particulier n'est codé en dur, voir « URL » ci-dessous ;
|
||||||
* v0.1.6 (prochaine publication — VERSION non bumpée dans cette PR,
|
* v0.1.6 (prochaine publication — VERSION non bumpée dans cette PR,
|
||||||
* la publication est séparée) : dérivation -dev par l'hôte courant,
|
* la publication est séparée) : dérivation -dev par l'hôte courant,
|
||||||
* menu « ••• » par défaut et menu compte fonctionnels, voir ci-dessous).
|
* menu trois points par défaut et menu compte fonctionnels, voir ci-dessous ;
|
||||||
|
* correctif fusion #70 (SANS bump de version, publication séparée) :
|
||||||
|
* bouton trois points et son menu par défaut SUPPRIMÉS, « Console Spark »
|
||||||
|
* ajoutée au menu de la marque, fermeture/focus corrigés (voir
|
||||||
|
* « Menu des applications », « Focus » et « Écouteurs globaux »).
|
||||||
*
|
*
|
||||||
* Aucun gestionnaire inline : un seul écouteur délégué sur le document
|
* Aucun gestionnaire inline : un seul écouteur délégué sur le document
|
||||||
* (patron validé par le prototype — les onclick du HTML généré sont
|
* (patron validé par le prototype — les onclick du HTML généré sont
|
||||||
@@ -54,22 +58,64 @@
|
|||||||
* alertes, spark, rappel-entete. L'app intégratrice décide de chacune
|
* alertes, spark, rappel-entete. L'app intégratrice décide de chacune
|
||||||
* (ouvrir la palette, le panneau Spark, changer de domaine…).
|
* (ouvrir la palette, le panneau Spark, changer de domaine…).
|
||||||
* Actions INTERNES (jamais relayées comme avant) : apps — la marque ✦
|
* Actions INTERNES (jamais relayées comme avant) : apps — la marque ✦
|
||||||
* ouvre le menu des applications, construit depuis les onglets ;
|
* ouvre le menu des applications, construit depuis les onglets, plus
|
||||||
|
* (depuis le correctif #70) une entrée « Console Spark » vers la
|
||||||
|
* console de l'agent commun (URL agent résolue, voir ci-dessous) ;
|
||||||
* c'est le moyen de changer d'app en mobile, où les onglets sont
|
* c'est le moyen de changer d'app en mobile, où les onglets sont
|
||||||
* repliés (v0.1.2). Menu « ••• » et compte : voir ci-dessous.
|
* repliés (v0.1.2). Menu compte : voir ci-dessous.
|
||||||
|
* L'action « menu » n'est PLUS émise nulle part : le bouton trois points
|
||||||
|
* a été supprimé (#70) ; les branches onAction("menu") des hôtes
|
||||||
|
* (TikTak/Kanbante/Flowdeck : retour false pour le menu par défaut ;
|
||||||
|
* ObsiGate : clic #header-menu-btn) sont donc mortes et ont été
|
||||||
|
* retirées des gabarits hôtes — le menu natif d'ObsiGate
|
||||||
|
* (#header-menu-btn) lui-même n'est pas touché.
|
||||||
*
|
*
|
||||||
* Menu « ••• » (v0.1.6) — audit des hôtes : ObsiGate est le seul hôte
|
* Retrait du bouton trois points (correctif fusion #70) — le bouton et
|
||||||
* connu à gérer l'action "menu" via onAction (il déclenche son propre
|
* son menu par défaut (console agent + applications) sont supprimés
|
||||||
* bouton d'en-tête #header-menu-btn) ; les autres hôtes ne la gèrent
|
* du fragment, du JS et du CSS. La console agent reste joignable par
|
||||||
* pas et le bouton restait mort (relais vers un onAction absent, puis
|
* l'entrée « Console Spark » du menu de la marque (ci-dessous) et
|
||||||
* événement draco:bar sans écouteur). Comportement désormais :
|
* par le bouton Spark (panneau), donc aucun accès n'est perdu.
|
||||||
* - sans onAction : un menu par défaut FONCTIONNEL s'ouvre — accès à
|
* Menu des applications (v0.1.3, étendu #70) — construit depuis les
|
||||||
* la console agent (URL agent résolue, donc dérivée -dev sur un
|
* onglets présents dans la barre, puis une entrée « Console Spark »
|
||||||
* hôte -dev) et aux quatre applications (URL résolues) ; fermeture
|
* (logo ✦, rôle « Agent commun ») APRÈS Kanbante, vers l'URL agent
|
||||||
* par clic extérieur, Échap ou choix d'un lien ;
|
* résolue : 1) config explicite DRACO_BAR_CONFIG.urls.agent,
|
||||||
* - avec onAction : l'hôte garde la main (comportement historique,
|
* 2) dérivation -dev agent-dev sur hôte -dev, 3) défaut officiel
|
||||||
* zéro régression pour ObsiGate) ; il peut demander explicitement
|
* prod https://agent.dracodev.net/ (il n'y a pas d'onglet agent, donc
|
||||||
* le menu par défaut en retournant false pour l'action "menu".
|
* pas d'étape « href rendu »). AUCUN hôte spark-dev n'est inventé :
|
||||||
|
* la clé spark, elle, suit toujours son propre contrat (explicite >
|
||||||
|
* href rendu > défaut prod) et ne sert qu'au panneau Spark.
|
||||||
|
* Focus (correctif #70) — ouverture au clavier : Entrée sur la marque
|
||||||
|
* (clic natif du lien) ; Espace sur la marque (keydown dédié, car un
|
||||||
|
* lien ne s'active pas à l'Espace) : ouvre le menu et met le focus
|
||||||
|
* sur son premier item. Fermeture : Échap (focus rendu au déclencheur
|
||||||
|
* qui avait ouvert le menu : la marque pour le menu apps, l'avatar
|
||||||
|
* pour le menu compte), nouvel appui sur le déclencheur (le focus y
|
||||||
|
* est déjà), choix d'un item, ou clic extérieur — ce dernier cas ne
|
||||||
|
* déplace VOLONTAIREMENT pas le focus (l'utilisateur vient de cliquer
|
||||||
|
* ailleurs : recherche, tâche… ; lui voler le focus casserait son
|
||||||
|
* geste). aria-expanded est tenu à jour sur chaque déclencheur.
|
||||||
|
* Écouteurs globaux (correctif #70, bug tiktak-dev) — les écouteurs
|
||||||
|
* clic/Échap de la barre sont posés via EventTarget.prototype (voir
|
||||||
|
* addGlobalListener), PAS via document.addEventListener. Cause
|
||||||
|
* prouvée sur TikTak (app/static/js/app.js, navigation #89) : app.js
|
||||||
|
* remplace document/window.addEventListener par un registre
|
||||||
|
* (__tcDocWin) et chaque navigation douce appelle tcInit() puis
|
||||||
|
* __tcTeardown(), qui SUPPRIME tous les écouteurs du registre — y
|
||||||
|
* compris ceux de draco-barre.js (script defer, enregistré après le
|
||||||
|
* tcInit() initial). La barre survit à la navigation (hors .layout
|
||||||
|
* remplacé) mais ses gestionnaires sont morts : un menu ouvert avant
|
||||||
|
* la navigation reste ouvert (Échap et clic extérieur sans effet) et
|
||||||
|
* la marque, lien href="/", est interceptée par l'intercepteur SPA
|
||||||
|
* brut de TikTak (__tcDocAdd, hors registre, donc survivant) comme
|
||||||
|
* une navigation interne au lieu de basculer le menu. Les écouteurs
|
||||||
|
* posés sur le prototype ne figurent pas dans le registre et ne sont
|
||||||
|
* donc jamais supprimés par le teardown ; en complément, le gabarit
|
||||||
|
* TikTak exclut désormais .draco-bar de l'intercepteur SPA, pour que
|
||||||
|
* la marque ne déclenche plus de navigation douce. Les autres hôtes
|
||||||
|
* (SSR, rechargement complet) ne sont pas concernés par le teardown
|
||||||
|
* mais bénéficient du même code ; repli : si EventTarget.prototype
|
||||||
|
* est indisponible (mini-DOM de tests), document.addEventListener
|
||||||
|
* classique est utilisé.
|
||||||
* Menu compte (v0.1.6) — l'avatar, auparavant un <span> mort affichant
|
* Menu compte (v0.1.6) — l'avatar, auparavant un <span> mort affichant
|
||||||
* un « B » codé en dur dans le fragment, est un vrai <button>
|
* un « B » codé en dur dans le fragment, est un vrai <button>
|
||||||
* (data-act="compte"). Son initiale vient de cfg.user ou, à défaut, de
|
* (data-act="compte"). Son initiale vient de cfg.user ou, à défaut, de
|
||||||
@@ -223,7 +269,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Menus déroulants (apps, •••, compte) ──────────────────────────
|
/* ── Menus déroulants (apps, compte) ───────────────────────────────
|
||||||
* Un seul menu ouvert à la fois ; fermeture par clic extérieur,
|
* Un seul menu ouvert à la fois ; fermeture par clic extérieur,
|
||||||
* Échap, ou choix d'un item. aria-expanded tenu à jour sur chaque
|
* Échap, ou choix d'un item. aria-expanded tenu à jour sur chaque
|
||||||
* déclencheur. Les href des menus sont (re)synchronisés avec
|
* déclencheur. Les href des menus sont (re)synchronisés avec
|
||||||
@@ -231,9 +277,11 @@
|
|||||||
* -dev s'y reflètent même si le menu a été construit avant. */
|
* -dev s'y reflètent même si le menu a été construit avant. */
|
||||||
var bar = document.querySelector(".draco-bar");
|
var bar = document.querySelector(".draco-bar");
|
||||||
var brand = bar ? bar.querySelector(".draco-brand") : null;
|
var brand = bar ? bar.querySelector(".draco-brand") : null;
|
||||||
var moreBtn = bar ? bar.querySelector('[data-act="menu"]') : null;
|
|
||||||
var avatarBtn = document.querySelector(".draco-avatar");
|
var avatarBtn = document.querySelector(".draco-avatar");
|
||||||
var appsMenu = null, moreMenu = null, compteMenu = null;
|
var appsMenu = null, compteMenu = null;
|
||||||
|
/* Déclencheur du menu ouvert en dernier (marque ou avatar) : Échap
|
||||||
|
* lui rend le focus à la fermeture (correctif #70). */
|
||||||
|
var lastOpener = null;
|
||||||
|
|
||||||
function setExpanded(btn, open) {
|
function setExpanded(btn, open) {
|
||||||
if (btn) btn.setAttribute("aria-expanded", open ? "true" : "false");
|
if (btn) btn.setAttribute("aria-expanded", open ? "true" : "false");
|
||||||
@@ -250,10 +298,13 @@
|
|||||||
menu.hidden = !open;
|
menu.hidden = !open;
|
||||||
setExpanded(btn, open);
|
setExpanded(btn, open);
|
||||||
}
|
}
|
||||||
function closeAllPopups() {
|
function closeAllPopups(returnFocus) {
|
||||||
|
var anyOpen = (appsMenu && !appsMenu.hidden) || (compteMenu && !compteMenu.hidden);
|
||||||
if (appsMenu) togglePopup(appsMenu, brand, false);
|
if (appsMenu) togglePopup(appsMenu, brand, false);
|
||||||
if (moreMenu) togglePopup(moreMenu, moreBtn, false);
|
|
||||||
if (compteMenu) togglePopup(compteMenu, avatarBtn, false);
|
if (compteMenu) togglePopup(compteMenu, avatarBtn, false);
|
||||||
|
/* Échap seulement : le clic extérieur ne déplace pas le focus
|
||||||
|
* (voir l'en-tête, « Focus »). */
|
||||||
|
if (returnFocus && anyOpen && lastOpener && typeof lastOpener.focus === "function") lastOpener.focus();
|
||||||
}
|
}
|
||||||
function tabLabel(app) {
|
function tabLabel(app) {
|
||||||
var tab = document.querySelector('.draco-apptab[data-app="' + app + '"]');
|
var tab = document.querySelector('.draco-apptab[data-app="' + app + '"]');
|
||||||
@@ -265,28 +316,6 @@
|
|||||||
var petit = tab ? tab.querySelector(".draco-apptab-txt small") : null;
|
var petit = tab ? tab.querySelector(".draco-apptab-txt small") : null;
|
||||||
return petit ? petit.textContent : "";
|
return petit ? petit.textContent : "";
|
||||||
}
|
}
|
||||||
function menuLink(app, nom, role) {
|
|
||||||
var a = document.createElement("a");
|
|
||||||
a.className = "draco-apps-item draco-menu-item";
|
|
||||||
a.href = resolvedUrls[app] || "#";
|
|
||||||
a.dataset.app = app;
|
|
||||||
a.setAttribute("role", "menuitem");
|
|
||||||
var txt = document.createElement("span");
|
|
||||||
txt.className = "draco-apps-item-txt";
|
|
||||||
var n = document.createElement("span");
|
|
||||||
n.className = "draco-apps-item-nom";
|
|
||||||
n.textContent = nom;
|
|
||||||
txt.appendChild(n);
|
|
||||||
if (role) {
|
|
||||||
var r = document.createElement("span");
|
|
||||||
r.className = "draco-apps-item-role";
|
|
||||||
r.textContent = role;
|
|
||||||
txt.appendChild(r);
|
|
||||||
}
|
|
||||||
a.appendChild(txt);
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Menu des applications (v0.1.3) — construit depuis les onglets
|
/* Menu des applications (v0.1.3) — construit depuis les onglets
|
||||||
* présents dans la barre : mêmes liens, mêmes icônes, mêmes badges.
|
* présents dans la barre : mêmes liens, mêmes icônes, mêmes badges.
|
||||||
* Ouvert par la marque ✦ (data-act="apps"), fermé par un clic
|
* Ouvert par la marque ✦ (data-act="apps"), fermé par un clic
|
||||||
@@ -326,6 +355,37 @@
|
|||||||
if (badge) a.appendChild(badge.cloneNode(true));
|
if (badge) a.appendChild(badge.cloneNode(true));
|
||||||
appsMenu.appendChild(a);
|
appsMenu.appendChild(a);
|
||||||
});
|
});
|
||||||
|
/* Entrée « Console Spark » (#70) : après Kanbante, vers la console
|
||||||
|
* de l'agent commun (URL agent résolue — explicite > -dev >
|
||||||
|
* défaut prod ; aucun hôte spark-dev inventé). data-app="agent"
|
||||||
|
* l'intègre à refreshMenuHrefs/applyUrls/setCurrent comme les
|
||||||
|
* autres items ; l'icône ✦ reprend la couleur agent du CSS. */
|
||||||
|
var sep = document.createElement("div");
|
||||||
|
sep.className = "draco-menu-sep";
|
||||||
|
sep.setAttribute("aria-hidden", "true");
|
||||||
|
appsMenu.appendChild(sep);
|
||||||
|
var sparkItem = document.createElement("a");
|
||||||
|
sparkItem.className = "draco-apps-item";
|
||||||
|
sparkItem.href = resolvedUrls.agent || "#";
|
||||||
|
sparkItem.dataset.app = "agent";
|
||||||
|
sparkItem.setAttribute("role", "menuitem");
|
||||||
|
var sparkIco = document.createElement("span");
|
||||||
|
sparkIco.className = "draco-apptab-ico";
|
||||||
|
sparkIco.setAttribute("style", "--app:#c2437e");
|
||||||
|
sparkIco.textContent = "✦";
|
||||||
|
sparkItem.appendChild(sparkIco);
|
||||||
|
var sparkTxt = document.createElement("span");
|
||||||
|
sparkTxt.className = "draco-apps-item-txt";
|
||||||
|
var sparkNom = document.createElement("span");
|
||||||
|
sparkNom.className = "draco-apps-item-nom";
|
||||||
|
sparkNom.textContent = "Console Spark";
|
||||||
|
sparkTxt.appendChild(sparkNom);
|
||||||
|
var sparkRole = document.createElement("span");
|
||||||
|
sparkRole.className = "draco-apps-item-role";
|
||||||
|
sparkRole.textContent = "Agent commun";
|
||||||
|
sparkTxt.appendChild(sparkRole);
|
||||||
|
sparkItem.appendChild(sparkTxt);
|
||||||
|
appsMenu.appendChild(sparkItem);
|
||||||
bar.appendChild(appsMenu);
|
bar.appendChild(appsMenu);
|
||||||
}
|
}
|
||||||
function toggleAppsMenu(force) {
|
function toggleAppsMenu(force) {
|
||||||
@@ -333,34 +393,7 @@
|
|||||||
if (!appsMenu) return;
|
if (!appsMenu) return;
|
||||||
if (force !== false) refreshMenuHrefs(appsMenu);
|
if (force !== false) refreshMenuHrefs(appsMenu);
|
||||||
togglePopup(appsMenu, brand, force);
|
togglePopup(appsMenu, brand, force);
|
||||||
}
|
if (!appsMenu.hidden) lastOpener = brand;
|
||||||
|
|
||||||
/* Menu « ••• » par défaut (v0.1.6) — console agent + applications.
|
|
||||||
* Construit depuis les URL résolues (donc dérivées -dev sur un hôte
|
|
||||||
* -dev) ; l'hôte qui gère "menu" via onAction garde la main (voir
|
|
||||||
* l'en-tête) et ne voit jamais ce menu, sauf s'il retourne false. */
|
|
||||||
function buildMoreMenu() {
|
|
||||||
if (!bar || !moreBtn || moreMenu) return;
|
|
||||||
moreMenu = document.createElement("div");
|
|
||||||
moreMenu.className = "draco-more-menu";
|
|
||||||
moreMenu.hidden = true;
|
|
||||||
moreMenu.setAttribute("role", "menu");
|
|
||||||
moreMenu.setAttribute("aria-label", "Plus d'actions");
|
|
||||||
moreMenu.appendChild(menuLink("agent", "Console agent", "Agent commun"));
|
|
||||||
var sep = document.createElement("div");
|
|
||||||
sep.className = "draco-menu-sep";
|
|
||||||
sep.setAttribute("aria-hidden", "true");
|
|
||||||
moreMenu.appendChild(sep);
|
|
||||||
["obsigate", "flowdeck", "tiktak", "kanbante"].forEach(function (app) {
|
|
||||||
moreMenu.appendChild(menuLink(app, tabLabel(app), tabRole(app)));
|
|
||||||
});
|
|
||||||
bar.appendChild(moreMenu);
|
|
||||||
}
|
|
||||||
function toggleMoreMenu(force) {
|
|
||||||
buildMoreMenu();
|
|
||||||
if (!moreMenu) return;
|
|
||||||
if (force !== false) refreshMenuHrefs(moreMenu);
|
|
||||||
togglePopup(moreMenu, moreBtn, force);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Menu compte (v0.1.6) — en-tête d'identité + Profil / Déconnexion
|
/* Menu compte (v0.1.6) — en-tête d'identité + Profil / Déconnexion
|
||||||
@@ -399,6 +432,7 @@
|
|||||||
if (!compteMenu) return;
|
if (!compteMenu) return;
|
||||||
if (force !== false) { syncAvatar(); refreshCompteMenu(); }
|
if (force !== false) { syncAvatar(); refreshCompteMenu(); }
|
||||||
togglePopup(compteMenu, avatarBtn, force);
|
togglePopup(compteMenu, avatarBtn, force);
|
||||||
|
if (!compteMenu.hidden) lastOpener = avatarBtn;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Relais d'une action vers l'hôte : onAction si présent, sinon
|
/* Relais d'une action vers l'hôte : onAction si présent, sinon
|
||||||
@@ -408,7 +442,21 @@
|
|||||||
else document.dispatchEvent(new CustomEvent("draco:bar", { detail: { act: act, el: el } }));
|
else document.dispatchEvent(new CustomEvent("draco:bar", { detail: { act: act, el: el } }));
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("click", function (ev) {
|
/* Écouteurs globaux posés sur le prototype EventTarget quand il
|
||||||
|
* existe, pour survivre au registre/teardown d'un hôte SPA (TikTak,
|
||||||
|
* voir l'en-tête) ; sinon document.addEventListener classique. */
|
||||||
|
function addGlobalListener(type, fn) {
|
||||||
|
try {
|
||||||
|
if (typeof EventTarget !== "undefined" && EventTarget.prototype &&
|
||||||
|
EventTarget.prototype.addEventListener) {
|
||||||
|
EventTarget.prototype.addEventListener.call(document, type, fn, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) { /* repli ci-dessous */ }
|
||||||
|
document.addEventListener(type, fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
addGlobalListener("click", function (ev) {
|
||||||
/* Items du menu compte : relais + fermeture, jamais de navigation. */
|
/* Items du menu compte : relais + fermeture, jamais de navigation. */
|
||||||
var compteItem = ev.target.closest ? ev.target.closest("[data-compte-act]") : null;
|
var compteItem = ev.target.closest ? ev.target.closest("[data-compte-act]") : null;
|
||||||
if (compteItem) {
|
if (compteItem) {
|
||||||
@@ -419,38 +467,21 @@
|
|||||||
var el = ev.target.closest ? ev.target.closest("[data-act]") : null;
|
var el = ev.target.closest ? ev.target.closest("[data-act]") : null;
|
||||||
if (el && el.dataset.act === "apps") {
|
if (el && el.dataset.act === "apps") {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
if (moreMenu) toggleMoreMenu(false);
|
|
||||||
if (compteMenu) toggleCompteMenu(false);
|
if (compteMenu) toggleCompteMenu(false);
|
||||||
toggleAppsMenu();
|
toggleAppsMenu();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (el && el.dataset.act === "menu") {
|
|
||||||
if (appsMenu) toggleAppsMenu(false);
|
|
||||||
if (compteMenu) toggleCompteMenu(false);
|
|
||||||
/* Hôte prioritaire : il garde la main, sauf s'il demande
|
|
||||||
* explicitement le menu par défaut en retournant false.
|
|
||||||
* Sans onAction, le menu par défaut s'ouvre (il ne restait
|
|
||||||
* auparavant qu'un événement draco:bar sans écouteur). */
|
|
||||||
if (typeof cfg.onAction === "function") {
|
|
||||||
var handled = cfg.onAction("menu", el, ev);
|
|
||||||
if (handled === false) toggleMoreMenu();
|
|
||||||
} else {
|
|
||||||
toggleMoreMenu();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (el && el.dataset.act === "compte") {
|
if (el && el.dataset.act === "compte") {
|
||||||
if (appsMenu) toggleAppsMenu(false);
|
if (appsMenu) toggleAppsMenu(false);
|
||||||
if (moreMenu) toggleMoreMenu(false);
|
|
||||||
toggleCompteMenu();
|
toggleCompteMenu();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/* Clic extérieur : ferme tout menu ouvert (clic dans un menu ou
|
/* Clic extérieur : ferme tout menu ouvert (clic dans un menu ou
|
||||||
* sur son déclencheur déjà traité ci-dessus). */
|
* sur son déclencheur déjà traité ci-dessus). */
|
||||||
var inPopup = ev.target.closest ? (ev.target.closest(".draco-apps-menu") ||
|
var inPopup = ev.target.closest ? (ev.target.closest(".draco-apps-menu") ||
|
||||||
ev.target.closest(".draco-more-menu") || ev.target.closest(".draco-compte-menu")) : null;
|
ev.target.closest(".draco-compte-menu")) : null;
|
||||||
if (!inPopup && ((appsMenu && !appsMenu.hidden) || (moreMenu && !moreMenu.hidden) || (compteMenu && !compteMenu.hidden))) {
|
if (!inPopup && ((appsMenu && !appsMenu.hidden) || (compteMenu && !compteMenu.hidden))) {
|
||||||
closeAllPopups();
|
closeAllPopups(false);
|
||||||
}
|
}
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
var act = el.dataset.act;
|
var act = el.dataset.act;
|
||||||
@@ -458,8 +489,23 @@
|
|||||||
relay(act, el, ev);
|
relay(act, el, ev);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("keydown", function (ev) {
|
addGlobalListener("keydown", function (ev) {
|
||||||
if (ev.key === "Escape") closeAllPopups();
|
if (ev.key === "Escape") { closeAllPopups(true); return; }
|
||||||
|
/* Espace sur la marque : un lien ne s'active pas à l'Espace, donc
|
||||||
|
* ouverture explicite + focus sur le premier item du menu (#70).
|
||||||
|
* Entrée : clic natif du lien, déjà géré par le gestionnaire clic
|
||||||
|
* ci-dessus (pas de double bascule ici). */
|
||||||
|
if ((ev.key === " " || ev.key === "Spacebar") && ev.target && ev.target.closest) {
|
||||||
|
var brandEl = ev.target.closest('[data-act="apps"]');
|
||||||
|
if (brandEl) {
|
||||||
|
if (ev.preventDefault) ev.preventDefault();
|
||||||
|
toggleAppsMenu();
|
||||||
|
if (appsMenu && !appsMenu.hidden) {
|
||||||
|
var first = appsMenu.querySelector(".draco-apps-item");
|
||||||
|
if (first && typeof first.focus === "function") first.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/* API publique : l'app met les compteurs à jour sans recharger.
|
/* API publique : l'app met les compteurs à jour sans recharger.
|
||||||
@@ -481,7 +527,6 @@
|
|||||||
|
|
||||||
applyUrls(resolvedUrls);
|
applyUrls(resolvedUrls);
|
||||||
buildAppsMenu();
|
buildAppsMenu();
|
||||||
buildMoreMenu();
|
|
||||||
buildCompteMenu();
|
buildCompteMenu();
|
||||||
syncAvatar();
|
syncAvatar();
|
||||||
refreshCompteMenu();
|
refreshCompteMenu();
|
||||||
|
|||||||
Reference in New Issue
Block a user