@tekkare/romulus 0.1.52 → 0.1.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.16.0"
6
6
  },
7
- "version": "0.1.51",
7
+ "version": "0.1.53",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
@@ -1 +1 @@
1
- .grid-2{grid-template-columns:repeat(2,1fr)}.grid-2,.grid-3{display:grid;gap:20px}.grid-3{grid-template-columns:repeat(3,1fr)}.grid-4{display:grid;gap:16px;grid-template-columns:repeat(4,1fr)}.grid-2-1{display:grid;gap:20px;grid-template-columns:2fr 1fr}.pane-title{color:var(--text-primary);font-family:var(--font-serif);font-size:32px;font-weight:700;letter-spacing:-.02em;line-height:1.1;margin:0 0 16px}.pane-sub{color:var(--text-muted);font-size:13px;line-height:1.55;margin:0 0 24px;max-width:78ch}.chart-section{margin-inline:auto;max-width:var(--content-max-w,1440px);padding:0 0 40px;width:100%}.section-label{align-items:center;color:var(--text-secondary);display:flex;font-size:11px;font-weight:700;gap:8px;letter-spacing:.12em;margin:40px 0 18px;text-transform:uppercase}body.text-large .pane-title{font-size:38px}body.text-xl .pane-title{font-size:46px}body.text-large .section-label{font-size:13px}body.text-xl .section-label{font-size:14px}.chart-card{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:10px;box-shadow:var(--shadow-sm);display:flex;flex-direction:column;transition:box-shadow .18s,border-color .18s}.chart-card:hover{border-color:var(--color-violet-pale);box-shadow:0 6px 16px rgba(107,63,255,.1),0 1px 3px rgba(30,41,59,.04)}.chart-card-head{padding:22px 64px 14px 24px;position:relative}.chart-card-title{color:var(--text-primary);font-size:15px;font-weight:700;letter-spacing:-.01em;line-height:1.3;margin:0 0 5px}.chart-card-context{color:var(--text-muted);font-size:12px;line-height:1.55;margin:0}.rm-card-actions{position:relative}.chart-card-head>.chart-card-actions{position:absolute;right:12px;top:12px}.cc-export{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:6px;color:var(--text-secondary);cursor:pointer;display:flex;font-size:14px;height:28px;justify-content:center;transition:background .15s,color .15s,border-color .15s;width:28px}.cc-export:hover{color:var(--color-secondary)}.cc-export:hover,.rm-card-actions.open .cc-export{background:var(--bg-hover);border-color:var(--color-violet-pale)}.rm-card-actions.open .cc-export{color:var(--color-primary)}.cc-menu{animation:cc-menu-in .12s cubic-bezier(.2,0,0,1);background:var(--bg-surface);border:1px solid var(--border-default);border-radius:8px;box-shadow:var(--shadow-lg);display:none;min-width:200px;padding:6px 0;position:absolute;right:0;top:34px;z-index:var(--z-modal)}.rm-card-actions.open .cc-menu{display:block}.cc-menu-section{color:var(--text-muted);font-size:10px;font-weight:700;letter-spacing:.08em;padding:8px 14px 4px;text-transform:uppercase}.cc-menu-item{align-items:center;background:none;border:0;color:var(--text-secondary);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:13px;gap:10px;padding:7px 14px;text-align:left;width:100%}.cc-menu-item i{color:var(--text-muted);font-size:14px}.cc-menu-item:hover{background:var(--bg-surface-3);color:var(--text-primary)}.cc-menu-item:hover i{color:var(--text-secondary)}.cc-menu-divider{background:var(--bg-surface-3);height:1px;margin:4px 0}.cc-menu-note{border-top:1px solid var(--bg-surface-3);color:var(--text-secondary);font-size:12px;margin:4px 0 0;padding:8px 14px 4px}.cc-menu-item[disabled]{cursor:default;opacity:.55}.cc-menu-item[disabled]:hover{background:none;color:var(--text-secondary)}.chart-card:fullscreen,.rm-rcard:fullscreen,.rm-table-card:fullscreen{background:var(--bg-surface);border-radius:0;box-sizing:border-box;height:100vh;overflow:auto;padding:16px;width:100vw}.chart-card:fullscreen .chart-card-body{display:flex;flex-direction:column}.chart-card:fullscreen .rm-chart{flex:1 1 auto}.chart-card:fullscreen .echart{height:72vh!important}@keyframes cc-menu-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.chart-card-body{flex:1;padding:4px 12px 8px}.chart-card-body>.rm-empty{box-sizing:border-box;gap:6px;justify-content:center;max-width:none;min-height:240px;padding:36px 24px}.chart-card-foot{align-items:center;background:var(--bg-surface-2);border-radius:0 0 10px 10px;border-top:1px solid var(--border-soft);color:var(--text-muted);display:flex;flex-wrap:wrap;font-size:11px;gap:10px;padding:12px 24px}.chart-card-foot .src{align-items:center;display:flex;gap:4px}.chart-card-foot .src b{color:var(--text-secondary);font-weight:600}.chart-card-foot .dot{background:var(--border-default);border-radius:50%;height:3px;width:3px}.chart-card-foot .updated{color:var(--text-secondary);font-weight:500}.kpi-card{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:8px;box-shadow:0 2px 8px rgba(30,41,59,.06);padding:16px 18px}.kpi-label{color:var(--text-muted);font-size:11px;font-weight:600;letter-spacing:.1em;margin:0 0 6px;text-transform:uppercase}.kpi-value{color:var(--color-primary);font-family:var(--font-serif);font-size:28px;font-weight:700;line-height:1;margin:0}.kpi-value.healthcare{color:var(--color-healthcare)}.kpi-value.discovery{color:var(--color-discovery)}.kpi-value.pharma{color:var(--color-pharma)}.kpi-value.databank{color:var(--color-databank)}.kpi-trend{align-items:center;display:inline-flex;font-size:12px;font-weight:600;gap:4px;margin-top:6px}.kpi-trend i{font-size:11px}.kpi-trend--up{color:var(--color-success)}.kpi-trend--down{color:var(--color-danger)}.kpi-foot,.kpi-trend--flat{color:var(--text-muted)}.kpi-foot{font-size:11px;margin-top:4px}.kpi-extra{margin-top:12px}.badge{border-radius:4px;display:inline-block;font-size:10px;font-weight:600;padding:2px 8px}.badge-active{background:var(--color-success-bg);color:var(--color-success)}.badge-pending{background:var(--color-warning-bg);color:var(--color-warning)}.badge-error{background:var(--color-danger-bg);color:var(--color-danger)}.badge-info{background:var(--bg-hover);color:var(--color-primary)}.badge-grey{background:var(--bg-surface-3);color:var(--text-secondary)}table.tk{border-collapse:collapse;font-size:13px;width:100%}.sr-only,table.tk>caption.sr-only{height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;clip:rect(0,0,0,0);border:0;white-space:nowrap}table.tk caption{caption-side:top;color:var(--text-primary);font-size:var(--font-13);font-weight:var(--weight-bold);padding:0 0 var(--space-8);text-align:left}table.tk th{background:var(--bg-surface-2);border-bottom:1px solid var(--border-default);color:var(--text-secondary);font-size:11px;font-weight:700;letter-spacing:.08em;padding:12px 14px;text-align:left;text-transform:uppercase}table.tk tbody th[scope=row]{background:transparent;font-size:var(--font-13);font-weight:var(--weight-bold);letter-spacing:0;text-transform:none}table.tk tbody th[scope=row],table.tk td{border-bottom:1px solid var(--border-soft);color:var(--text-primary)}table.tk td{padding:12px 14px}table.tk tbody tr:nth-child(2n) td,table.tk tbody tr:nth-child(2n) th[scope=row]{background:var(--bg-surface-2)}table.tk tbody tr:hover td,table.tk tbody tr:hover th[scope=row]{background:var(--bg-hover)}table.tk th[aria-sort]{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}table.tk th[aria-sort]:hover{color:var(--text-primary)}.tk-wrap{overflow-x:auto}.tk-num{font-variant-numeric:tabular-nums;text-align:right}.tk-skeleton{height:14px;width:80%}.tk-empty{color:var(--text-muted);text-align:center}.nav-group{display:flex;flex-direction:column}.nav-group-header{align-items:center;background:none;border:0;color:var(--text-muted);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:11px;font-weight:700;gap:8px;letter-spacing:.08em;padding:8px 12px;text-align:left;text-transform:uppercase;transition:color var(--duration-fast) var(--ease-out);width:100%}.nav-group-header:hover{color:var(--text-secondary)}.nav-group-header .caret{display:inline-flex;margin-left:auto;transition:transform var(--duration-fast) var(--ease-out)}.nav-group[data-open=true]>.nav-group-header{color:var(--color-primary)}.nav-group[data-open=true]>.nav-group-header .caret{transform:rotate(180deg)}.nav-group-items{display:flex;flex-direction:column;gap:2px;padding:2px 0 8px}.cc-export:focus-visible,.cc-menu-item:focus-visible,.chart-card :focus-visible,.nav-group-header:focus-visible,table.tk th[aria-sort]:focus-visible{box-shadow:var(--shadow-focus);outline:2px solid var(--color-secondary);outline-offset:2px}.cc-menu-item,.nav-group-header{min-height:40px}body.text-large .kpi-label{font-size:13px}body.text-large .kpi-value{font-size:34px}body.text-large .kpi-trend{font-size:14px}body.text-large .kpi-foot{font-size:13px}body.text-large .chart-card-title{font-size:17px}body.text-large .chart-card-context{font-size:14px}body.text-large .chart-card-foot{font-size:12px}body.text-large .badge{font-size:12px;padding:3px 9px}body.text-large table.tk{font-size:15px}body.text-large table.tk th{font-size:13px}body.text-xl .kpi-label{font-size:14px}body.text-xl .kpi-value{font-size:40px}body.text-xl .kpi-trend{font-size:16px}body.text-xl .kpi-foot{font-size:14px}body.text-xl .chart-card-title{font-size:20px}body.text-xl .chart-card-context{font-size:16px}body.text-xl .chart-card-foot{font-size:14px}body.text-xl .badge{font-size:14px;padding:3px 10px}body.text-xl table.tk{font-size:16px}body.text-xl table.tk th{font-size:14px}body.high-contrast .cc-export,body.high-contrast .cc-menu,body.high-contrast .chart-card,body.high-contrast .kpi-card{border-width:2px}body.high-contrast .chart-card:hover{border-color:var(--border-default)}body.rm-switching,body.rm-switching *,body.rm-switching :after,body.rm-switching :before,body.theme-switching,body.theme-switching *,body.theme-switching :after,body.theme-switching :before{transition:none!important}body.high-contrast *{box-shadow:none!important}.rm-skeleton{animation:rm-skeleton-shimmer 1.5s ease-in-out infinite;background:linear-gradient(90deg,var(--bg-hover) 0,var(--bg-surface-2) 50%,var(--bg-hover) 100%);background-size:200% 100%;border-radius:var(--radius-md)}@keyframes rm-skeleton-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}@media (prefers-reduced-motion:reduce){.rm-skeleton{animation:none;background:var(--bg-hover)}}.rm-state-loading{display:flex;flex-direction:column;gap:10px;justify-content:center;min-height:240px;padding:8px 4px}.rm-spinner{animation:rm-spinner-turn .8s linear infinite;border:3px solid var(--border-default);border-radius:50%;border-top-color:var(--color-secondary);height:32px;width:32px}@keyframes rm-spinner-turn{to{transform:rotate(1turn)}}@media (prefers-reduced-motion:reduce){.rm-spinner{animation-duration:3s}}.rm-state{color:var(--text-secondary);flex-direction:column;font-family:var(--font-sans);gap:6px;min-height:240px;padding:36px 24px;text-align:center}.rm-state,.rm-state__icon{align-items:center;display:flex;justify-content:center}.rm-state__icon{background:var(--bg-surface-3);border-radius:50%;color:var(--text-muted);height:56px;margin-bottom:6px;width:56px}.rm-state__title{color:var(--text-primary);font-size:var(--font-14);font-weight:var(--weight-semibold);margin:0}.rm-state__text{color:var(--text-muted);font-size:var(--font-12);line-height:1.5;margin:0;max-width:280px}.rm-state__action{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-md);color:var(--text-secondary);cursor:pointer;display:inline-flex;font-family:inherit;font-size:var(--font-12);font-weight:var(--weight-semibold);gap:6px;margin-top:10px;padding:6px 14px}.rm-state__action:hover{background:var(--bg-surface-3);color:var(--text-primary)}.rm-state--error .rm-state__icon{background:var(--color-danger-bg);color:var(--color-danger)}.rm-state--error .rm-state__action{border-color:var(--color-danger);color:var(--color-danger)}.rm-state--error .rm-state__action:hover{background:var(--color-danger-bg)}
1
+ .grid-2{grid-template-columns:repeat(2,1fr)}.grid-2,.grid-3{display:grid;gap:20px}.grid-3{grid-template-columns:repeat(3,1fr)}.grid-4{display:grid;gap:16px;grid-template-columns:repeat(4,1fr)}.grid-2-1{display:grid;gap:20px;grid-template-columns:2fr 1fr}.pane-title{color:var(--text-primary);font-family:var(--font-serif);font-size:32px;font-weight:700;letter-spacing:-.02em;line-height:1.1;margin:0 0 16px}.pane-sub{color:var(--text-muted);font-size:13px;line-height:1.55;margin:0 0 24px;max-width:78ch}.chart-section{margin-inline:auto;max-width:var(--content-max-w,1440px);padding:0 0 40px;width:100%}.section-label{align-items:center;color:var(--text-secondary);display:flex;font-size:11px;font-weight:700;gap:8px;letter-spacing:.12em;margin:40px 0 18px;text-transform:uppercase}body.text-large .pane-title{font-size:38px}body.text-xl .pane-title{font-size:46px}body.text-large .section-label{font-size:13px}body.text-xl .section-label{font-size:14px}.chart-card{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:10px;box-shadow:var(--shadow-sm);display:flex;flex-direction:column;transition:box-shadow .18s,border-color .18s}.chart-card:hover{border-color:var(--color-violet-pale);box-shadow:0 6px 16px rgba(107,63,255,.1),0 1px 3px rgba(30,41,59,.04)}.chart-card-head{padding:22px 64px 14px 24px;position:relative}.chart-card-title{color:var(--text-primary);font-size:15px;font-weight:700;letter-spacing:-.01em;line-height:1.3;margin:0 0 5px}.chart-card-context{color:var(--text-muted);font-size:12px;line-height:1.55;margin:0}.rm-card-actions{position:relative}.chart-card-head>.chart-card-actions{position:absolute;right:12px;top:12px}.cc-export{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:6px;color:var(--text-secondary);cursor:pointer;display:flex;font-size:14px;height:28px;justify-content:center;transition:background .15s,color .15s,border-color .15s;width:28px}.cc-export:hover{color:var(--color-secondary)}.cc-export:hover,.rm-card-actions.open .cc-export{background:var(--bg-hover);border-color:var(--color-violet-pale)}.rm-card-actions.open .cc-export{color:var(--color-primary)}.cc-menu{animation:cc-menu-in .12s cubic-bezier(.2,0,0,1);background:var(--bg-surface);border:1px solid var(--border-default);border-radius:8px;box-shadow:var(--shadow-lg);display:none;min-width:200px;padding:6px 0;position:absolute;right:0;top:34px;z-index:500}.rm-card-actions.open .cc-menu{display:block}.cc-menu-section{color:var(--text-muted);font-size:10px;font-weight:700;letter-spacing:.08em;padding:8px 14px 4px;text-transform:uppercase}.cc-menu-item{align-items:center;background:none;border:0;color:var(--text-secondary);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:13px;gap:10px;padding:7px 14px;text-align:left;width:100%}.cc-menu-item i{color:var(--text-muted);font-size:14px}.cc-menu-item:hover{background:var(--bg-surface-3);color:var(--text-primary)}.cc-menu-item:hover i{color:var(--text-secondary)}.cc-menu-divider{background:var(--bg-surface-3);height:1px;margin:4px 0}.cc-menu-note{border-top:1px solid var(--bg-surface-3);color:var(--text-secondary);font-size:12px;margin:4px 0 0;padding:8px 14px 4px}.cc-menu-item[disabled]{cursor:default;opacity:.55}.cc-menu-item[disabled]:hover{background:none;color:var(--text-secondary)}.chart-card:fullscreen,.rm-card-zoom,.rm-rcard:fullscreen,.rm-table-card:fullscreen{background:var(--bg-surface);border-radius:0;box-sizing:border-box;height:100vh;overflow:auto;padding:16px;width:100vw}.rm-card-zoom{border:0;inset:0;margin:0;position:fixed;z-index:500}body.rm-card-zoom-open{overflow:hidden}.chart-card.rm-card-zoom .chart-card-body,.chart-card:fullscreen .chart-card-body{display:flex;flex-direction:column}.chart-card.rm-card-zoom .rm-chart,.chart-card:fullscreen .rm-chart{flex:1 1 auto}.chart-card.rm-card-zoom .echart,.chart-card:fullscreen .echart{height:72vh!important}@keyframes cc-menu-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.chart-card-body{flex:1;padding:4px 12px 8px}.chart-card-body>.rm-empty{box-sizing:border-box;gap:6px;justify-content:center;max-width:none;min-height:240px;padding:36px 24px}.chart-card-foot{align-items:center;background:var(--bg-surface-2);border-radius:0 0 10px 10px;border-top:1px solid var(--border-soft);color:var(--text-muted);display:flex;flex-wrap:wrap;font-size:11px;gap:10px;padding:12px 24px}.chart-card-foot .src{align-items:center;display:flex;gap:4px}.chart-card-foot .src b{color:var(--text-secondary);font-weight:600}.chart-card-foot .dot{background:var(--border-default);border-radius:50%;height:3px;width:3px}.chart-card-foot .updated{color:var(--text-secondary);font-weight:500}.kpi-card{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:8px;box-shadow:0 2px 8px rgba(30,41,59,.06);padding:16px 18px}.kpi-label{color:var(--text-muted);font-size:11px;font-weight:600;letter-spacing:.1em;margin:0 0 6px;text-transform:uppercase}.kpi-value{color:var(--color-primary);font-family:var(--font-serif);font-size:28px;font-weight:700;line-height:1;margin:0}.kpi-value.healthcare{color:var(--color-healthcare)}.kpi-value.discovery{color:var(--color-discovery)}.kpi-value.pharma{color:var(--color-pharma)}.kpi-value.databank{color:var(--color-databank)}.kpi-trend{align-items:center;display:inline-flex;font-size:12px;font-weight:600;gap:4px;margin-top:6px}.kpi-trend i{font-size:11px}.kpi-trend--up{color:var(--color-success)}.kpi-trend--down{color:var(--color-danger)}.kpi-foot,.kpi-trend--flat{color:var(--text-muted)}.kpi-foot{font-size:11px;margin-top:4px}.kpi-extra{margin-top:12px}.badge{border-radius:4px;display:inline-block;font-size:10px;font-weight:600;padding:2px 8px}.badge-active{background:var(--color-success-bg);color:var(--color-success)}.badge-pending{background:var(--color-warning-bg);color:var(--color-warning)}.badge-error{background:var(--color-danger-bg);color:var(--color-danger)}.badge-info{background:var(--bg-hover);color:var(--color-primary)}.badge-grey{background:var(--bg-surface-3);color:var(--text-secondary)}table.tk{border-collapse:collapse;font-size:13px;width:100%}.sr-only,table.tk>caption.sr-only{height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;clip:rect(0,0,0,0);border:0;white-space:nowrap}table.tk caption{caption-side:top;color:var(--text-primary);font-size:var(--font-13);font-weight:var(--weight-bold);padding:0 0 var(--space-8);text-align:left}table.tk th{background:var(--bg-surface-2);border-bottom:1px solid var(--border-default);color:var(--text-secondary);font-size:11px;font-weight:700;letter-spacing:.08em;padding:12px 14px;text-align:left;text-transform:uppercase}table.tk tbody th[scope=row]{background:transparent;font-size:var(--font-13);font-weight:var(--weight-bold);letter-spacing:0;text-transform:none}table.tk tbody th[scope=row],table.tk td{border-bottom:1px solid var(--border-soft);color:var(--text-primary)}table.tk td{padding:12px 14px}table.tk tbody tr:nth-child(2n) td,table.tk tbody tr:nth-child(2n) th[scope=row]{background:var(--bg-surface-2)}table.tk tbody tr:hover td,table.tk tbody tr:hover th[scope=row]{background:var(--bg-hover)}table.tk th[aria-sort]{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}table.tk th[aria-sort]:hover{color:var(--text-primary)}.tk-wrap{overflow-x:auto}.tk-num{font-variant-numeric:tabular-nums;text-align:right}.tk-skeleton{height:14px;width:80%}.tk-empty{color:var(--text-muted);text-align:center}.nav-group{display:flex;flex-direction:column}.nav-group-header{align-items:center;background:none;border:0;color:var(--text-muted);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:11px;font-weight:700;gap:8px;letter-spacing:.08em;padding:8px 12px;text-align:left;text-transform:uppercase;transition:color var(--duration-fast) var(--ease-out);width:100%}.nav-group-header:hover{color:var(--text-secondary)}.nav-group-header .caret{display:inline-flex;margin-left:auto;transition:transform var(--duration-fast) var(--ease-out)}.nav-group[data-open=true]>.nav-group-header{color:var(--color-primary)}.nav-group[data-open=true]>.nav-group-header .caret{transform:rotate(180deg)}.nav-group-items{display:flex;flex-direction:column;gap:2px;padding:2px 0 8px}.cc-export:focus-visible,.cc-menu-item:focus-visible,.chart-card :focus-visible,.nav-group-header:focus-visible,table.tk th[aria-sort]:focus-visible{box-shadow:var(--shadow-focus);outline:2px solid var(--color-secondary);outline-offset:2px}.cc-menu-item,.nav-group-header{min-height:40px}body.text-large .kpi-label{font-size:13px}body.text-large .kpi-value{font-size:34px}body.text-large .kpi-trend{font-size:14px}body.text-large .kpi-foot{font-size:13px}body.text-large .chart-card-title{font-size:17px}body.text-large .chart-card-context{font-size:14px}body.text-large .chart-card-foot{font-size:12px}body.text-large .badge{font-size:12px;padding:3px 9px}body.text-large table.tk{font-size:15px}body.text-large table.tk th{font-size:13px}body.text-xl .kpi-label{font-size:14px}body.text-xl .kpi-value{font-size:40px}body.text-xl .kpi-trend{font-size:16px}body.text-xl .kpi-foot{font-size:14px}body.text-xl .chart-card-title{font-size:20px}body.text-xl .chart-card-context{font-size:16px}body.text-xl .chart-card-foot{font-size:14px}body.text-xl .badge{font-size:14px;padding:3px 10px}body.text-xl table.tk{font-size:16px}body.text-xl table.tk th{font-size:14px}body.high-contrast .cc-export,body.high-contrast .cc-menu,body.high-contrast .chart-card,body.high-contrast .kpi-card{border-width:2px}body.high-contrast .chart-card:hover{border-color:var(--border-default)}body.rm-switching,body.rm-switching *,body.rm-switching :after,body.rm-switching :before,body.theme-switching,body.theme-switching *,body.theme-switching :after,body.theme-switching :before{transition:none!important}body.high-contrast *{box-shadow:none!important}.rm-skeleton{animation:rm-skeleton-shimmer 1.5s ease-in-out infinite;background:linear-gradient(90deg,var(--bg-hover) 0,var(--bg-surface-2) 50%,var(--bg-hover) 100%);background-size:200% 100%;border-radius:var(--radius-md)}@keyframes rm-skeleton-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}@media (prefers-reduced-motion:reduce){.rm-skeleton{animation:none;background:var(--bg-hover)}}.rm-state-loading{display:flex;flex-direction:column;gap:10px;justify-content:center;min-height:240px;padding:8px 4px}.rm-spinner{animation:rm-spinner-turn .8s linear infinite;border:3px solid var(--border-default);border-radius:50%;border-top-color:var(--color-secondary);height:32px;width:32px}@keyframes rm-spinner-turn{to{transform:rotate(1turn)}}@media (prefers-reduced-motion:reduce){.rm-spinner{animation-duration:3s}}.rm-state{color:var(--text-secondary);flex-direction:column;font-family:var(--font-sans);gap:6px;min-height:240px;padding:36px 24px;text-align:center}.rm-state,.rm-state__icon{align-items:center;display:flex;justify-content:center}.rm-state__icon{background:var(--bg-surface-3);border-radius:50%;color:var(--text-muted);height:56px;margin-bottom:6px;width:56px}.rm-state__title{color:var(--text-primary);font-size:var(--font-14);font-weight:var(--weight-semibold);margin:0}.rm-state__text{color:var(--text-muted);font-size:var(--font-12);line-height:1.5;margin:0;max-width:280px}.rm-state__action{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-md);color:var(--text-secondary);cursor:pointer;display:inline-flex;font-family:inherit;font-size:var(--font-12);font-weight:var(--weight-semibold);gap:6px;margin-top:10px;padding:6px 14px}.rm-state__action:hover{background:var(--bg-surface-3);color:var(--text-primary)}.rm-state--error .rm-state__icon{background:var(--color-danger-bg);color:var(--color-danger)}.rm-state--error .rm-state__action{border-color:var(--color-danger);color:var(--color-danger)}.rm-state--error .rm-state__action:hover{background:var(--color-danger-bg)}
@@ -1 +1 @@
1
- :root{--rm-shell-bg:#f9f9fa;--rm-shell-surface:#fff;--rm-shell-border:#dbdef0;--rm-shell-subtle:#f5f5f9;--rm-shell-text:#1e293b;--rm-shell-text-dark:#475569;--rm-shell-text-muted:#878ca6;--rm-shell-accent:#2017ad;--rm-shell-inset:8px;--rm-shell-radius:12px;--rm-bar-h:56px;--rm-nav-w:240px;--rm-nav-collapsed-w:64px;--rm-rail-w:56px;--rm-dots-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAcUlEQVR42u3TwQmAMAxAUdfQfUoSZ3OBTNMFHEqLVBEtCB6M8B8EevwktOsAAACAl0SkNzNXtaw6zut7SikNYeJq2Hksh4gs27rG7ZH+eWA7bttigMC78x4TIdBbceX8fJKnkSL1s5RY8zBxAAAA+LUF0ula10BxVDoAAAAASUVORK5CYII=");--rm-dots-size:12px;--rm-dots-opacity:0.1}body.night{--rm-shell-bg:var(--bg-app);--rm-shell-surface:var(--bg-surface,#1a1a24);--rm-shell-border:var(--border-default);--rm-shell-subtle:var(--bg-subtle,#22222e);--rm-shell-text:var(--text-primary);--rm-shell-text-dark:var(--text-secondary);--rm-shell-text-muted:var(--text-muted);--rm-shell-accent:var(--color-primary);--rm-dots-opacity:0.06}.rm-shell{background:var(--rm-shell-bg);color:var(--rm-shell-text);font-family:var(--font-sans,"Figtree",sans-serif);inset:0;overflow:hidden;position:fixed}.rm-shell__dots{background-image:var(--rm-dots-image);background-repeat:repeat;background-size:var(--rm-dots-size) var(--rm-dots-size);inset:0;opacity:var(--rm-dots-opacity);pointer-events:none;position:absolute}.rm-shell__frame{border:1px solid var(--rm-shell-border);border-radius:var(--rm-shell-radius);display:flex;inset:var(--rm-shell-inset);overflow:hidden;position:absolute}.rm-shell__body{display:flex;flex:1;flex-direction:column;min-width:0}.rm-shell__main{flex:1;min-width:0;overflow-y:auto}.rm-sb{background:var(--rm-shell-surface);display:flex;flex-direction:column;flex-shrink:0;height:100%}.rm-sb--left{border-right:1px solid var(--rm-shell-border);position:relative;transition:width .2s;width:var(--rm-nav-w);z-index:200}.rm-sb--left.rm-sb--collapsed{width:var(--rm-nav-collapsed-w)}.rm-sb--right{border-left:1px solid var(--rm-shell-border);width:var(--rm-rail-w)}.rm-sb__header{align-items:center;border-bottom:1px solid var(--rm-shell-border);display:flex;flex-shrink:0;gap:8px;height:var(--rm-bar-h);padding:0 16px}.rm-sb--collapsed .rm-sb__header{justify-content:center;padding:0}.rm-sb__logo{align-items:center;color:var(--rm-shell-text);display:flex;flex:1;font-size:20px;font-weight:700;gap:8px;line-height:1.4;min-width:0;overflow:hidden;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.rm-sb__toggle{-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:0;box-shadow:none;color:var(--rm-shell-text-muted);cursor:pointer;display:block;flex-shrink:0;height:20px;margin:0;padding:0;transition:color .15s;width:20px}.rm-sb__toggle:hover{color:var(--rm-shell-text)}.rm-sb__toggle svg{display:block;height:100%;width:100%}.rm-sb__nav{display:flex;flex:1;flex-direction:column;gap:16px;overflow-y:auto;padding:16px}.rm-sb--collapsed .rm-sb__nav{padding-left:8px;padding-right:8px}.rm-sb__group{display:flex;flex-direction:column;gap:4px}.rm-sb__label{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:0;box-shadow:none;color:var(--rm-shell-text-muted);display:flex;font-family:inherit;font-size:10px;font-weight:500;gap:4px;letter-spacing:.025em;line-height:1.6;margin:0;padding:0 2px;text-align:left;text-transform:uppercase;width:100%}button.rm-sb__label{cursor:pointer}button.rm-sb__label:hover{color:var(--rm-shell-text)}.rm-sb__label-text{flex:1;min-width:0}.rm-sb__chev{display:block;flex-shrink:0;height:12px;transition:transform .2s;width:12px}.rm-sb__label[aria-expanded=true] .rm-sb__chev{transform:rotate(90deg)}.rm-sb__rule{background:var(--rm-shell-border);height:1px;margin:4px}.rm-mi{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:4px;box-shadow:none;color:var(--rm-shell-text);cursor:pointer;display:flex;font-family:inherit;gap:8px;height:26px;margin:0;padding:2px;text-align:left;text-decoration:none;transition:background-color .15s;width:100%}.rm-mi:hover{background:color-mix(in srgb,var(--rm-shell-subtle) 50%,transparent)}.rm-mi.rm-mi--active{background:var(--rm-shell-subtle)}.rm-mi.rm-mi--current{background:color-mix(in srgb,var(--rm-shell-accent) 5%,transparent)}.rm-mi.rm-mi--current .rm-mi__icon{background:var(--rm-shell-accent);color:#fff}.rm-mi.rm-mi--current .rm-mi__icon svg{stroke:#fff}.rm-mi__icon{align-items:center;background:var(--rm-shell-subtle);border-radius:4px;color:var(--rm-shell-text-dark);display:flex;flex-shrink:0;height:22px;justify-content:center;line-height:1;width:22px}.rm-mi__icon svg{display:block;height:14px;width:14px}.rm-mi__icon i{font-size:14px;line-height:1}.rm-mi__label{color:var(--rm-shell-text);flex:1;font-size:14px;line-height:1.625;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rm-mi__badge{background:var(--rm-shell-subtle);border-radius:4px;color:var(--rm-shell-text-muted);flex-shrink:0;font-size:9px;font-weight:700;letter-spacing:.025em;padding:2px 4px;text-transform:uppercase}.rm-sb--collapsed .rm-mi{justify-content:center}.rm-sb--collapsed .rm-mi>:not(.rm-mi__icon),.rm-sb--collapsed .rm-sb__sub{display:none}.rm-sb__sub{display:flex;flex-direction:column;gap:2px;padding:2px 0 2px 28px}.rm-sb__sub-item{border-radius:4px;color:var(--rm-shell-text);display:block;font-size:14px;line-height:1.625;overflow:hidden;padding:2px 4px;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.rm-sb__sub-item:hover{background:color-mix(in srgb,var(--rm-shell-subtle) 50%,transparent)}.rm-rail__item{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:0;box-shadow:none;cursor:pointer;display:flex;flex-direction:column;font-family:inherit;gap:4px;margin:0;padding:12px 0 8px;text-decoration:none;width:100%}.rm-rail__avatar{align-items:center;border:2px solid var(--rm-shell-surface);border-radius:9999px;color:#fff;display:flex;font-size:14px;height:32px;justify-content:center;transition:opacity .15s;width:32px}.rm-rail__item:hover .rm-rail__avatar{opacity:.8}.rm-rail__tile{align-items:center;background:var(--rm-shell-subtle);border-radius:8px;color:var(--rm-shell-text-dark);display:flex;font-size:16px;height:32px;justify-content:center;transition:background-color .15s,color .15s;width:32px}.rm-rail__item:hover .rm-rail__tile{background:var(--rm-shell-border)}.rm-rail__item.rm-rail__item--active .rm-rail__tile{background:var(--rm-shell-accent);color:#fff}.rm-rail__tile{position:relative}.rm-rail__badge{background:var(--rm-shell-accent);border-radius:9999px;color:#fff;font-size:10px;font-weight:700;height:16px;line-height:16px;min-width:16px;padding:0 4px;position:absolute;right:-4px;text-align:center;top:-4px}.rm-rail__item--active .rm-rail__badge{background:#fff;color:var(--rm-shell-accent)}.rm-rail__caption{color:var(--rm-shell-text-muted);font-size:10px;line-height:1;max-width:100%;overflow:hidden;padding:0 2px;text-overflow:ellipsis;transition:color .15s;white-space:nowrap}.rm-rail__item--active .rm-rail__caption,.rm-rail__item:hover .rm-rail__caption{color:var(--rm-shell-text)}.rm-rail__bottom{display:flex;justify-content:center;margin-top:auto;padding-bottom:16px}.rm-lang{background:var(--rm-shell-subtle);border:1px solid var(--rm-shell-accent);border-radius:9999px;display:grid;gap:2px;grid-template-rows:1fr 1fr;padding:3px;position:relative}.rm-lang__thumb{background:var(--rm-shell-surface);border-radius:9999px;box-shadow:0 1px 4px rgba(30,41,59,.16);height:32px;left:3px;position:absolute;top:3px;transition:transform .26s cubic-bezier(.34,1.56,.64,1);width:32px}.rm-lang--second .rm-lang__thumb{transform:translateY(34px)}.rm-lang__option{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:9999px;box-shadow:none;cursor:pointer;display:flex;filter:grayscale(1);font-size:17px;height:32px;justify-content:center;line-height:1;margin:0;opacity:.5;padding:0;position:relative;transition:filter .18s ease,opacity .18s ease,transform .18s ease;width:32px}.rm-lang__option:hover{filter:grayscale(0);opacity:.85}.rm-lang__option.rm-lang__option--active{filter:grayscale(0);opacity:1}.rm-lang__option:focus-visible{outline:2px solid var(--rm-shell-accent);outline-offset:2px}@media (prefers-reduced-motion:reduce){.rm-lang__option,.rm-lang__thumb{transition:none}}.rm-hd{background:var(--rm-shell-surface);border-bottom:1px solid var(--rm-shell-border);display:flex;flex-direction:column;flex-shrink:0}.rm-hd__row{gap:16px;height:var(--rm-bar-h);padding:0 16px}.rm-hd__crumbs,.rm-hd__row{align-items:center;display:flex}.rm-hd__crumbs{flex:1;font-size:14px;gap:4px;min-width:0}.rm-hd__crumb{align-items:center;color:var(--rm-shell-text-muted);display:inline-flex;gap:6px;text-decoration:none;transition:color .15s;white-space:nowrap}a.rm-hd__crumb:hover{color:var(--rm-shell-text)}.rm-hd__sep{color:var(--rm-shell-text-muted)}.rm-hd__picker{display:inline-flex;min-width:0;position:relative}.rm-hd__crumb--picker{-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:4px;box-shadow:none;cursor:pointer;font:inherit;font-size:14px;margin:0;padding:2px 4px;transition:background-color .15s,color .15s}.rm-hd__crumb--picker:hover{background:var(--rm-shell-subtle)}.rm-hd__caret{display:block;flex-shrink:0;height:12px;opacity:.7;transition:transform .15s;width:12px}.rm-hd__caret--open{transform:rotate(180deg)}.rm-hd__menu{background:var(--rm-shell-surface);border:1px solid var(--rm-shell-border);border-radius:8px;box-shadow:0 8px 24px rgba(30,41,59,.12);left:0;max-height:60vh;max-width:320px;min-width:200px;overflow-y:auto;padding:4px;position:absolute;top:calc(100% + 4px);z-index:300}.rm-hd__menu-item{align-items:center;border-radius:4px;color:var(--rm-shell-text);display:flex;font-size:14px;gap:8px;line-height:1.5;overflow:hidden;padding:4px 6px;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.rm-hd__menu-item:hover{background:var(--rm-shell-subtle)}.rm-hd__menu-group{color:var(--rm-shell-text-muted);display:block;font-size:10px;font-weight:500;letter-spacing:.025em;line-height:1.6;padding:8px 6px 2px;text-transform:uppercase}.rm-hd__menu-group:first-child{padding-top:2px}.rm-hd__menu-item--active{background:color-mix(in srgb,var(--rm-shell-accent) 5%,transparent);font-weight:600}.rm-hd__menu-icon{align-items:center;background:var(--rm-shell-subtle);border-radius:4px;color:var(--rm-shell-text-dark);display:flex;flex-shrink:0;height:22px;justify-content:center;line-height:1;width:22px}.rm-hd__menu-icon i{font-size:14px;line-height:1}.rm-hd__menu-item--active .rm-hd__menu-icon{background:var(--rm-shell-accent);color:#fff}.rm-hd__crumb--last{color:var(--rm-shell-text);font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rm-hd__search{align-items:center;background:var(--rm-shell-surface);border:1px solid var(--rm-shell-border);border-radius:9999px;display:flex;flex-shrink:0;gap:4px;padding:4px 12px;width:300px}.rm-hd__search input{background:transparent;border:none;color:var(--rm-shell-text);flex:1;font-family:inherit;font-size:14px;line-height:20px;min-width:0;outline:none;padding:0}.rm-hd__search input::-moz-placeholder{color:var(--rm-shell-text-muted)}.rm-hd__search input::placeholder{color:var(--rm-shell-text-muted)}.rm-hd__search i,.rm-hd__search svg{color:var(--rm-shell-text-muted);display:block;flex-shrink:0;font-size:14px;height:14px;line-height:20px;width:14px}.rm-hd__tabs{display:flex;gap:16px;padding:0 16px}.rm-hd__tab{align-items:flex-start;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-bottom:2px solid transparent;border-radius:0;box-shadow:none;color:var(--rm-shell-text-dark);cursor:pointer;display:flex;flex-direction:column;font-family:inherit;font-size:14px;font-weight:700;line-height:1.6;margin:0;padding:4px 4px 8px;white-space:nowrap}.rm-hd__tab--active{border-bottom-color:var(--rm-shell-accent);color:var(--rm-shell-text)}@media (max-width:767px){.rm-sb--left{inset:0 auto 0 0;position:absolute;transform:translateX(-100%);transition:transform .2s;z-index:40}.rm-sb--left.rm-sb--open{transform:translateX(0)}.rm-sb--right{display:none}.rm-hd__search{flex:1;width:auto}}.rm-panel{background:var(--rm-shell-surface);border-left:1px solid var(--rm-shell-border);bottom:0;box-shadow:-8px 0 24px rgba(30,41,59,.08);display:flex;flex-direction:column;position:absolute;right:0;top:0;width:var(--rm-panel-w,360px);z-index:401}.rm-panel__veil{backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);background:var(--rm-panel-veil,rgba(100,116,139,.12));cursor:default;inset:0;position:absolute;z-index:400}.rm-panel-veil-enter-active,.rm-panel-veil-leave-active{transition:opacity .22s ease}.rm-panel-veil-enter-from,.rm-panel-veil-leave-to{opacity:0}@media (prefers-reduced-motion:reduce){.rm-panel-veil-enter-active,.rm-panel-veil-leave-active{transition:none}}.rm-sb--right{position:relative;z-index:402}.rm-panel-enter-active,.rm-panel-leave-active,.rm-sb--right{transition:transform .22s cubic-bezier(.4,0,.2,1)}.rm-panel-enter-from,.rm-panel-leave-to{transform:translateX(100%)}@media (prefers-reduced-motion:reduce){.rm-panel-enter-active,.rm-panel-leave-active,.rm-sb--right{transition:none}}.rm-panel__header{align-items:center;border-bottom:1px solid var(--rm-shell-border);display:flex;flex-shrink:0;gap:8px;height:var(--rm-bar-h);padding:0 16px}.rm-panel__title{color:var(--rm-shell-text);flex:1;font-size:14px;font-weight:600;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rm-panel__close{-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:0;box-shadow:none;color:var(--rm-shell-text-muted);cursor:pointer;display:block;flex-shrink:0;height:20px;margin:0;padding:0;transition:color .15s;width:20px}.rm-panel__close:hover{color:var(--rm-shell-text)}.rm-panel__close svg{display:block;height:100%;width:100%}.rm-panel__body{flex:1;min-height:0;overflow-y:auto;padding:16px}@media (max-width:767px){.rm-panel{border-left:none;left:0;width:auto}}
1
+ :root{--rm-shell-bg:#f9f9fa;--rm-shell-surface:#fff;--rm-shell-border:#dbdef0;--rm-shell-subtle:#f5f5f9;--rm-shell-text:#1e293b;--rm-shell-text-dark:#475569;--rm-shell-text-muted:#878ca6;--rm-shell-accent:#2017ad;--rm-shell-inset:8px;--rm-shell-radius:12px;--rm-bar-h:56px;--rm-nav-w:240px;--rm-nav-collapsed-w:64px;--rm-rail-w:56px;--rm-dots-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAcUlEQVR42u3TwQmAMAxAUdfQfUoSZ3OBTNMFHEqLVBEtCB6M8B8EevwktOsAAACAl0SkNzNXtaw6zut7SikNYeJq2Hksh4gs27rG7ZH+eWA7bttigMC78x4TIdBbceX8fJKnkSL1s5RY8zBxAAAA+LUF0ula10BxVDoAAAAASUVORK5CYII=");--rm-dots-size:12px;--rm-dots-opacity:0.1}body.night{--rm-shell-bg:var(--bg-app);--rm-shell-surface:var(--bg-surface,#1a1a24);--rm-shell-border:var(--border-default);--rm-shell-subtle:var(--bg-subtle,#22222e);--rm-shell-text:var(--text-primary);--rm-shell-text-dark:var(--text-secondary);--rm-shell-text-muted:var(--text-muted);--rm-shell-accent:var(--color-primary);--rm-dots-opacity:0.06}.rm-shell{background:var(--rm-shell-bg);color:var(--rm-shell-text);font-family:var(--font-sans,"Figtree",sans-serif);inset:0;overflow:hidden;position:fixed}.rm-shell__dots{background-image:var(--rm-dots-image);background-repeat:repeat;background-size:var(--rm-dots-size) var(--rm-dots-size);inset:0;opacity:var(--rm-dots-opacity);pointer-events:none;position:absolute}.rm-shell__frame{border:1px solid var(--rm-shell-border);border-radius:var(--rm-shell-radius);display:flex;inset:var(--rm-shell-inset);overflow:hidden;position:absolute}.rm-shell__body{display:flex;flex:1;flex-direction:column;min-width:0}.rm-shell__main{flex:1;min-width:0;overflow-y:auto}.rm-sb{background:var(--rm-shell-surface);display:flex;flex-direction:column;flex-shrink:0;height:100%}.rm-sb--left{border-right:1px solid var(--rm-shell-border);position:relative;transition:width .2s;width:var(--rm-nav-w);z-index:200}.rm-sb--left.rm-sb--collapsed{width:var(--rm-nav-collapsed-w)}.rm-sb--right{border-left:1px solid var(--rm-shell-border);width:var(--rm-rail-w)}.rm-sb__header{align-items:center;border-bottom:1px solid var(--rm-shell-border);display:flex;flex-shrink:0;gap:8px;height:var(--rm-bar-h);padding:0 16px}.rm-sb--collapsed .rm-sb__header{justify-content:center;padding:0}.rm-sb__logo{align-items:center;color:var(--rm-shell-text);display:flex;flex:1;font-size:20px;font-weight:700;gap:8px;line-height:1.4;min-width:0;overflow:hidden;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.rm-sb__toggle{-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:0;box-shadow:none;color:var(--rm-shell-text-muted);cursor:pointer;display:block;flex-shrink:0;height:20px;margin:0;padding:0;transition:color .15s;width:20px}.rm-sb__toggle:hover{color:var(--rm-shell-text)}.rm-sb__toggle svg{display:block;height:100%;width:100%}.rm-sb__nav{display:flex;flex:1;flex-direction:column;gap:16px;overflow-y:auto;padding:16px}.rm-sb--collapsed .rm-sb__nav{padding-left:8px;padding-right:8px}.rm-sb__group{display:flex;flex-direction:column;gap:4px}.rm-sb__label{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:0;box-shadow:none;color:var(--rm-shell-text-muted);display:flex;font-family:inherit;font-size:10px;font-weight:500;gap:4px;letter-spacing:.025em;line-height:1.6;margin:0;padding:0 2px;text-align:left;text-transform:uppercase;width:100%}button.rm-sb__label{cursor:pointer}button.rm-sb__label:hover{color:var(--rm-shell-text)}.rm-sb__label-text{flex:1;min-width:0}.rm-sb__chev{display:block;flex-shrink:0;height:12px;transition:transform .2s;width:12px}.rm-sb__label[aria-expanded=true] .rm-sb__chev{transform:rotate(90deg)}.rm-sb__rule{background:var(--rm-shell-border);height:1px;margin:4px}.rm-mi{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:4px;box-shadow:none;color:var(--rm-shell-text);cursor:pointer;display:flex;font-family:inherit;gap:8px;height:26px;margin:0;padding:2px;text-align:left;text-decoration:none;transition:background-color .15s;width:100%}.rm-mi:hover{background:color-mix(in srgb,var(--rm-shell-subtle) 50%,transparent)}.rm-mi.rm-mi--active{background:var(--rm-shell-subtle)}.rm-mi.rm-mi--current{background:color-mix(in srgb,var(--rm-shell-accent) 5%,transparent)}.rm-mi.rm-mi--current .rm-mi__icon{background:var(--rm-shell-accent);color:#fff}.rm-mi.rm-mi--current .rm-mi__icon svg{stroke:#fff}.rm-mi__icon{align-items:center;background:var(--rm-shell-subtle);border-radius:4px;color:var(--rm-shell-text-dark);display:flex;flex-shrink:0;height:22px;justify-content:center;line-height:1;width:22px}.rm-mi__icon svg{display:block;height:14px;width:14px}.rm-mi__icon i{font-size:14px;line-height:1}.rm-mi__label{color:var(--rm-shell-text);flex:1;font-size:14px;line-height:1.625;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rm-mi__badge{background:var(--rm-shell-subtle);border-radius:4px;color:var(--rm-shell-text-muted);flex-shrink:0;font-size:9px;font-weight:700;letter-spacing:.025em;padding:2px 4px;text-transform:uppercase}.rm-sb--collapsed .rm-mi{justify-content:center}.rm-sb--collapsed .rm-mi>:not(.rm-mi__icon),.rm-sb--collapsed .rm-sb__sub{display:none}.rm-sb__tip{background:var(--rm-shell-surface);border:1px solid var(--rm-shell-border);border-radius:6px;box-shadow:0 4px 12px rgba(30,41,59,.1);color:var(--rm-shell-text);font-size:12px;line-height:1.3;padding:5px 9px;pointer-events:none;position:fixed;transform:translateY(-50%);white-space:nowrap;z-index:60}.rm-sb__sub{display:flex;flex-direction:column;gap:2px;padding:2px 0 2px 28px}.rm-sb__sub-item{border-radius:4px;color:var(--rm-shell-text);display:block;font-size:14px;line-height:1.625;overflow:hidden;padding:2px 4px;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.rm-sb__sub-item:hover{background:color-mix(in srgb,var(--rm-shell-subtle) 50%,transparent)}.rm-rail__item{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:0;box-shadow:none;cursor:pointer;display:flex;flex-direction:column;font-family:inherit;gap:4px;margin:0;padding:12px 0 8px;text-decoration:none;width:100%}.rm-rail__avatar{align-items:center;border:2px solid var(--rm-shell-surface);border-radius:9999px;color:#fff;display:flex;font-size:14px;height:32px;justify-content:center;transition:opacity .15s;width:32px}.rm-rail__item:hover .rm-rail__avatar{opacity:.8}.rm-rail__tile{align-items:center;background:var(--rm-shell-subtle);border-radius:8px;color:var(--rm-shell-text-dark);display:flex;font-size:16px;height:32px;justify-content:center;transition:background-color .15s,color .15s;width:32px}.rm-rail__item:hover .rm-rail__tile{background:var(--rm-shell-border)}.rm-rail__item.rm-rail__item--active .rm-rail__tile{background:var(--rm-shell-accent);color:#fff}.rm-rail__tile{position:relative}.rm-rail__badge{background:var(--rm-shell-accent);border-radius:9999px;color:#fff;font-size:10px;font-weight:700;height:16px;line-height:16px;min-width:16px;padding:0 4px;position:absolute;right:-4px;text-align:center;top:-4px}.rm-rail__item--active .rm-rail__badge{background:#fff;color:var(--rm-shell-accent)}.rm-rail__caption{color:var(--rm-shell-text-muted);font-size:10px;line-height:1;max-width:100%;overflow:hidden;padding:0 2px;text-overflow:ellipsis;transition:color .15s;white-space:nowrap}.rm-rail__item--active .rm-rail__caption,.rm-rail__item:hover .rm-rail__caption{color:var(--rm-shell-text)}.rm-rail__bottom{display:flex;justify-content:center;margin-top:auto;padding-bottom:16px}.rm-lang{background:var(--rm-shell-subtle);border:1px solid var(--rm-shell-accent);border-radius:9999px;display:grid;gap:2px;grid-template-rows:1fr 1fr;padding:3px;position:relative}.rm-lang__thumb{background:var(--rm-shell-surface);border-radius:9999px;box-shadow:0 1px 4px rgba(30,41,59,.16);height:32px;left:3px;position:absolute;top:3px;transition:transform .26s cubic-bezier(.34,1.56,.64,1);width:32px}.rm-lang--second .rm-lang__thumb{transform:translateY(34px)}.rm-lang__option{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:9999px;box-shadow:none;cursor:pointer;display:flex;filter:grayscale(1);font-size:17px;height:32px;justify-content:center;line-height:1;margin:0;opacity:.5;padding:0;position:relative;transition:filter .18s ease,opacity .18s ease,transform .18s ease;width:32px}.rm-lang__option:hover{filter:grayscale(0);opacity:.85}.rm-lang__option.rm-lang__option--active{filter:grayscale(0);opacity:1}.rm-lang__option:focus-visible{outline:2px solid var(--rm-shell-accent);outline-offset:2px}@media (prefers-reduced-motion:reduce){.rm-lang__option,.rm-lang__thumb{transition:none}}.rm-hd{background:var(--rm-shell-surface);border-bottom:1px solid var(--rm-shell-border);display:flex;flex-direction:column;flex-shrink:0}.rm-hd__row{gap:16px;height:var(--rm-bar-h);padding:0 16px}.rm-hd__crumbs,.rm-hd__row{align-items:center;display:flex}.rm-hd__crumbs{flex:1;font-size:14px;gap:4px;min-width:0}.rm-hd__crumb{align-items:center;color:var(--rm-shell-text-muted);display:inline-flex;gap:6px;text-decoration:none;transition:color .15s;white-space:nowrap}a.rm-hd__crumb:hover{color:var(--rm-shell-text)}.rm-hd__sep{color:var(--rm-shell-text-muted)}.rm-hd__picker{display:inline-flex;min-width:0;position:relative}.rm-hd__crumb--picker{-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:4px;box-shadow:none;cursor:pointer;font:inherit;font-size:14px;margin:0;padding:2px 4px;transition:background-color .15s,color .15s}.rm-hd__crumb--picker:hover{background:var(--rm-shell-subtle)}.rm-hd__caret{display:block;flex-shrink:0;height:12px;opacity:.7;transition:transform .15s;width:12px}.rm-hd__caret--open{transform:rotate(180deg)}.rm-hd__menu{background:var(--rm-shell-surface);border:1px solid var(--rm-shell-border);border-radius:8px;box-shadow:0 8px 24px rgba(30,41,59,.12);left:0;max-height:60vh;max-width:320px;min-width:200px;overflow-y:auto;padding:4px;position:absolute;top:calc(100% + 4px);z-index:300}.rm-hd__menu-item{align-items:center;border-radius:4px;color:var(--rm-shell-text);display:flex;font-size:14px;gap:8px;line-height:1.5;overflow:hidden;padding:4px 6px;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.rm-hd__menu-item:hover{background:var(--rm-shell-subtle)}.rm-hd__menu-group{color:var(--rm-shell-text-muted);display:block;font-size:10px;font-weight:500;letter-spacing:.025em;line-height:1.6;padding:8px 6px 2px;text-transform:uppercase}.rm-hd__menu-group:first-child{padding-top:2px}.rm-hd__menu-item--active{background:color-mix(in srgb,var(--rm-shell-accent) 5%,transparent);font-weight:600}.rm-hd__menu-icon{align-items:center;background:var(--rm-shell-subtle);border-radius:4px;color:var(--rm-shell-text-dark);display:flex;flex-shrink:0;height:22px;justify-content:center;line-height:1;width:22px}.rm-hd__menu-icon i{font-size:14px;line-height:1}.rm-hd__menu-item--active .rm-hd__menu-icon{background:var(--rm-shell-accent);color:#fff}.rm-hd__crumb--last{color:var(--rm-shell-text);font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rm-hd__search{align-items:center;background:var(--rm-shell-surface);border:1px solid var(--rm-shell-border);border-radius:9999px;display:flex;flex-shrink:0;gap:4px;padding:4px 12px;width:300px}.rm-hd__search input{background:transparent;border:none;color:var(--rm-shell-text);flex:1;font-family:inherit;font-size:14px;line-height:20px;min-width:0;outline:none;padding:0}.rm-hd__search input::-moz-placeholder{color:var(--rm-shell-text-muted)}.rm-hd__search input::placeholder{color:var(--rm-shell-text-muted)}.rm-hd__search i,.rm-hd__search svg{color:var(--rm-shell-text-muted);display:block;flex-shrink:0;font-size:14px;height:14px;line-height:20px;width:14px}.rm-hd__tabs{display:flex;gap:16px;padding:0 16px}.rm-hd__tab{align-items:flex-start;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-bottom:2px solid transparent;border-radius:0;box-shadow:none;color:var(--rm-shell-text-dark);cursor:pointer;display:flex;flex-direction:column;font-family:inherit;font-size:14px;font-weight:700;line-height:1.6;margin:0;padding:4px 4px 8px;white-space:nowrap}.rm-hd__tab--active{border-bottom-color:var(--rm-shell-accent);color:var(--rm-shell-text)}@media (max-width:767px){.rm-sb--left{inset:0 auto 0 0;position:absolute;transform:translateX(-100%);transition:transform .2s;z-index:40}.rm-sb--left.rm-sb--open{transform:translateX(0)}.rm-sb--right{display:none}.rm-hd__search{flex:1;width:auto}}.rm-panel{background:var(--rm-shell-surface);border-left:1px solid var(--rm-shell-border);bottom:0;box-shadow:-8px 0 24px rgba(30,41,59,.08);display:flex;flex-direction:column;position:absolute;right:0;top:0;width:var(--rm-panel-w,360px);z-index:401}.rm-panel__veil{backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);background:var(--rm-panel-veil,rgba(100,116,139,.12));cursor:default;inset:0;position:absolute;z-index:400}.rm-panel-veil-enter-active,.rm-panel-veil-leave-active{transition:opacity .22s ease}.rm-panel-veil-enter-from,.rm-panel-veil-leave-to{opacity:0}@media (prefers-reduced-motion:reduce){.rm-panel-veil-enter-active,.rm-panel-veil-leave-active{transition:none}}.rm-sb--right{position:relative;z-index:402}.rm-panel-enter-active,.rm-panel-leave-active,.rm-sb--right{transition:transform .22s cubic-bezier(.4,0,.2,1)}.rm-panel-enter-from,.rm-panel-leave-to{transform:translateX(100%)}@media (prefers-reduced-motion:reduce){.rm-panel-enter-active,.rm-panel-leave-active,.rm-sb--right{transition:none}}.rm-panel__header{align-items:center;border-bottom:1px solid var(--rm-shell-border);display:flex;flex-shrink:0;gap:8px;height:var(--rm-bar-h);padding:0 16px}.rm-panel__title{color:var(--rm-shell-text);flex:1;font-size:14px;font-weight:600;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rm-panel__close{-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:0;box-shadow:none;color:var(--rm-shell-text-muted);cursor:pointer;display:block;flex-shrink:0;height:20px;margin:0;padding:0;transition:color .15s;width:20px}.rm-panel__close:hover{color:var(--rm-shell-text)}.rm-panel__close svg{display:block;height:100%;width:100%}.rm-panel__body{flex:1;min-height:0;overflow-y:auto;padding:16px}@media (max-width:767px){.rm-panel{border-left:none;left:0;width:auto}}
@@ -14,10 +14,16 @@
14
14
 
15
15
  Les evenements sont emis EN PLUS de l'action, jamais a sa place : ils
16
16
  servent au suivi (PostHog), pas a implementer l'export dans la page.
17
+
18
+ Le menu est pose en `position: fixed`, calcule a l'ouverture : les cartes
19
+ rognent ce qui depasse (`overflow: hidden`, sans quoi le fond du pied
20
+ deborderait des coins arrondis), et un menu en flux etait coupe par le bord
21
+ de la carte. Il se referme au defilement, faute de quoi il resterait plante
22
+ ou la carte n'est plus.
17
23
  -->
18
24
  <script setup lang="ts">
19
- import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
20
- import type { RmExportSheet } from '../utils/export'
25
+ import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
26
+ import type { RmExportPayload } from '../utils/export'
21
27
  import { rmCardCharts, rmCardOf } from '../utils/cardData'
22
28
  import { useRmCardActions } from '../composables/useRmCardActions'
23
29
 
@@ -29,10 +35,15 @@ const props = withDefaults(defineProps<{
29
35
  /** Nom du fichier et de l'onglet. A defaut, le titre lu dans la carte. */
30
36
  name?: string
31
37
  /**
32
- * Onglets fournis par la page, quand elle a mieux que le rendu : la donnee
33
- * complete, ou les nombres bruts derriere des cellules mises en forme.
38
+ * Ce que la page a de mieux que le rendu de la carte : ses onglets complets
39
+ * (`sheets`), mais aussi ses chiffres cles, ses sources, son sous-titre. Ce
40
+ * qui est fourni ici remplace ce que le DOM aurait donne, pour que l'export
41
+ * d'une carte se presente comme l'export global de la page.
42
+ *
43
+ * Une fonction est preferable des qu'une liste monte la meme carte vingt
44
+ * fois : elle n'est appelee qu'a l'export.
34
45
  */
35
- sheets?: RmExportSheet[]
46
+ payload?: Partial<RmExportPayload> | (() => Partial<RmExportPayload> | null | undefined)
36
47
  /**
37
48
  * Force les items visuels. Non renseigne : ils suivent la presence d'un
38
49
  * graphe dans la carte, ce qui est le bon defaut dans les deux sens.
@@ -42,7 +53,7 @@ const props = withDefaults(defineProps<{
42
53
  lang: 'fr',
43
54
  link: undefined,
44
55
  name: undefined,
45
- sheets: undefined,
56
+ payload: undefined,
46
57
  visual: undefined,
47
58
  })
48
59
 
@@ -91,7 +102,9 @@ const t = computed(() => WORDS[props.lang])
91
102
  const { runCardAction, toggleCardFullscreen } = useRmCardActions()
92
103
 
93
104
  const root = ref<HTMLElement | null>(null)
105
+ const menu = ref<HTMLElement | null>(null)
94
106
  const menuOpen = ref(false)
107
+ const menuStyle = ref<Record<string, string>>({})
95
108
  const busy = ref(false)
96
109
  const note = ref('')
97
110
  const hasChart = ref(false)
@@ -103,11 +116,38 @@ function cardEl(): Element | null {
103
116
  return rmCardOf(root.value)
104
117
  }
105
118
 
106
- function toggleMenu() {
119
+ /** Marge minimale entre le menu et un bord de la fenetre. */
120
+ const EDGE = 8
121
+
122
+ /**
123
+ * Place le menu sous le bouton, en coordonnees de fenetre. Il bascule au-dessus
124
+ * quand le bas manque : une carte en fin de page ouvrait un menu dont les
125
+ * derniers items tombaient hors de l'ecran.
126
+ */
127
+ function place() {
128
+ const button = root.value?.querySelector('.cc-export')
129
+ const box = button?.getBoundingClientRect()
130
+ const size = menu.value?.getBoundingClientRect()
131
+ if (!box || !size) return
132
+
133
+ const left = Math.max(EDGE, Math.min(box.right - size.width, window.innerWidth - size.width - EDGE))
134
+ const below = box.bottom + 6
135
+ const above = box.top - size.height - 6
136
+ const top = below + size.height > window.innerHeight - EDGE && above > EDGE ? above : below
137
+
138
+ menuStyle.value = { position: 'fixed', left: `${Math.round(left)}px`, top: `${Math.round(top)}px`, right: 'auto' }
139
+ }
140
+
141
+ async function toggleMenu() {
107
142
  const card = cardEl()
108
143
  hasChart.value = card ? rmCardCharts(card).length > 0 : false
109
144
  note.value = ''
110
145
  menuOpen.value = !menuOpen.value
146
+ if (!menuOpen.value) return
147
+ // Le menu doit etre rendu pour etre mesure : les items visuels viennent
148
+ // d'etre ajoutes ou retires, sa hauteur en depend.
149
+ await nextTick()
150
+ place()
111
151
  }
112
152
 
113
153
  /**
@@ -127,7 +167,7 @@ async function act(action: 'png' | 'svg' | 'csv' | 'xlsx' | 'link') {
127
167
  lang: props.lang,
128
168
  name: props.name,
129
169
  link: props.link,
130
- sheets: props.sheets,
170
+ payload: props.payload,
131
171
  })
132
172
  if (action === 'link') {
133
173
  // Le presse-papier ne se voit pas : sans un mot, rien ne distingue un
@@ -174,14 +214,22 @@ function onDocumentClick(event: MouseEvent) {
174
214
  function onEscape(event: KeyboardEvent) {
175
215
  if (event.key === 'Escape') menuOpen.value = false
176
216
  }
217
+ /** Le menu est fixe : au defilement, il ne suit plus sa carte. */
218
+ function onScroll() {
219
+ if (menuOpen.value) menuOpen.value = false
220
+ }
177
221
 
178
222
  onMounted(() => {
179
223
  document.addEventListener('click', onDocumentClick)
180
224
  document.addEventListener('keydown', onEscape)
225
+ window.addEventListener('scroll', onScroll, true)
226
+ window.addEventListener('resize', onScroll)
181
227
  })
182
228
  onBeforeUnmount(() => {
183
229
  document.removeEventListener('click', onDocumentClick)
184
230
  document.removeEventListener('keydown', onEscape)
231
+ window.removeEventListener('scroll', onScroll, true)
232
+ window.removeEventListener('resize', onScroll)
185
233
  })
186
234
  </script>
187
235
 
@@ -199,7 +247,7 @@ onBeforeUnmount(() => {
199
247
  <RmIcon name="Gear" :size="14" />
200
248
  </button>
201
249
 
202
- <div class="cc-menu" role="menu">
250
+ <div ref="menu" class="cc-menu" role="menu" :style="menuStyle">
203
251
  <div class="cc-menu-section">{{ t.exportSection }}</div>
204
252
  <button v-if="showVisual" class="cc-menu-item" type="button" role="menuitem" :disabled="busy" @click="act('png')">
205
253
  <RmIcon name="Image" :size="14" /> {{ t.png }}
@@ -14,7 +14,7 @@
14
14
  France, UK +3 » se lit sans ouvrir le panneau, « Pays (5) » non.
15
15
  -->
16
16
  <script setup lang="ts">
17
- import { computed, onMounted, onUnmounted, ref } from 'vue'
17
+ import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
18
18
  import {
19
19
  PhCaretDown,
20
20
  PhMagnifyingGlass,
@@ -71,6 +71,33 @@ const isOpen = ref(false)
71
71
  const wrapperRef = ref<HTMLElement>()
72
72
  const delSearch = ref('')
73
73
 
74
+ /**
75
+ * Saisie de la colonne d'ajout, telle qu'elle est tapee. En mode `remote`,
76
+ * chaque frappe declencherait une requete : « pembro » en lancerait six, et
77
+ * l'utilisateur verrait la liste d'origine tant que la derniere n'est pas
78
+ * revenue, ce qui se lit comme une recherche qui ne marche pas. Le champ reste
79
+ * donc immediat, et le modele que l'appelant interroge ne bouge qu'une fois la
80
+ * frappe posee.
81
+ */
82
+ const REMOTE_DEBOUNCE = 250
83
+ const addInput = ref(addSearch.value)
84
+ let debounce: ReturnType<typeof setTimeout> | undefined
85
+
86
+ // La saisie peut aussi etre remise a zero de l'exterieur (fermeture du
87
+ // panneau) : le champ suit.
88
+ watch(addSearch, (value) => {
89
+ if (value !== addInput.value) addInput.value = value
90
+ })
91
+
92
+ watch(addInput, (value) => {
93
+ if (!props.remote) {
94
+ addSearch.value = value
95
+ return
96
+ }
97
+ clearTimeout(debounce)
98
+ debounce = setTimeout(() => { addSearch.value = value }, REMOTE_DEBOUNCE)
99
+ })
100
+
74
101
  /** Le chrome du panneau, dans les deux langues. */
75
102
  const WORDS = {
76
103
  fr: {
@@ -84,6 +111,7 @@ const WORDS = {
84
111
  searchSelection: 'Filtrer la sélection...',
85
112
  noResult: 'Aucun résultat',
86
113
  noSelection: 'Aucune sélection',
114
+ more: 'valeurs de plus, faites défiler',
87
115
  },
88
116
  en: {
89
117
  quick: 'Quick selection',
@@ -96,6 +124,7 @@ const WORDS = {
96
124
  searchSelection: 'Filter selection...',
97
125
  noResult: 'No results',
98
126
  noSelection: 'No selection',
127
+ more: 'more values, keep scrolling',
99
128
  },
100
129
  } as const
101
130
 
@@ -113,6 +142,32 @@ const filteredAvailable = computed(() => {
113
142
  return availableOptions.value.filter(o => o.label.toLowerCase().includes(q))
114
143
  })
115
144
 
145
+ /**
146
+ * Rendu par tranches. Un referentiel entier (cinq mille marques) pose autant
147
+ * de boutons dans le DOM : le panneau mettait plus d'une seconde a s'ouvrir.
148
+ * On affiche donc les premieres valeurs, et la suite arrive quand on descend.
149
+ * Rien n'est cache : la recherche, elle, travaille sur la liste complete.
150
+ */
151
+ const CHUNK = 300
152
+ const shown = ref(CHUNK)
153
+
154
+ const visibleAvailable = computed(() => filteredAvailable.value.slice(0, shown.value))
155
+ const hiddenCount = computed(() => Math.max(0, filteredAvailable.value.length - shown.value))
156
+
157
+ // Toute nouvelle liste repart du haut : sinon une recherche affiche encore les
158
+ // tranches ouvertes pour la precedente.
159
+ watch(filteredAvailable, () => { shown.value = CHUNK })
160
+
161
+ function onListScroll(event: Event) {
162
+ if (hiddenCount.value === 0) return
163
+ const el = event.target as HTMLElement
164
+ // Une hauteur d'ecran avant le bas : la tranche suivante est prete quand on
165
+ // y arrive.
166
+ if (el.scrollTop + el.clientHeight >= el.scrollHeight - el.clientHeight) {
167
+ shown.value += CHUNK
168
+ }
169
+ }
170
+
116
171
  /**
117
172
  * La selection se lit sur `selected`, pas sur `options` : une valeur choisie
118
173
  * puis sortie de la liste (recherche suivante, referentiel pagine) doit rester
@@ -196,6 +251,7 @@ function clearAll() {
196
251
  function toggle() {
197
252
  isOpen.value = !isOpen.value
198
253
  if (!isOpen.value) {
254
+ addInput.value = ''
199
255
  addSearch.value = ''
200
256
  delSearch.value = ''
201
257
  }
@@ -204,13 +260,17 @@ function toggle() {
204
260
  function onClickOutside(e: Event) {
205
261
  if (wrapperRef.value && !wrapperRef.value.contains(e.target as Node)) {
206
262
  isOpen.value = false
263
+ addInput.value = ''
207
264
  addSearch.value = ''
208
265
  delSearch.value = ''
209
266
  }
210
267
  }
211
268
 
212
269
  onMounted(() => document.addEventListener('click', onClickOutside))
213
- onUnmounted(() => document.removeEventListener('click', onClickOutside))
270
+ onUnmounted(() => {
271
+ clearTimeout(debounce)
272
+ document.removeEventListener('click', onClickOutside)
273
+ })
214
274
  </script>
215
275
 
216
276
  <template>
@@ -268,22 +328,22 @@ onUnmounted(() => document.removeEventListener('click', onClickOutside))
268
328
  <div class="filter-complex__search">
269
329
  <PhMagnifyingGlass :size="14" color="var(--text-muted)" />
270
330
  <input
271
- v-model="addSearch"
331
+ v-model="addInput"
272
332
  type="text"
273
333
  :placeholder="t.searchAdd"
274
334
  class="filter-complex__search-input"
275
335
  >
276
336
  <PhX
277
- v-if="addSearch"
337
+ v-if="addInput"
278
338
  :size="12"
279
339
  class="filter-complex__search-clear"
280
- @click="addSearch = ''"
340
+ @click="addInput = ''"
281
341
  />
282
342
  </div>
283
343
 
284
- <div class="filter-complex__list">
344
+ <div class="filter-complex__list" @scroll.passive="onListScroll">
285
345
  <button
286
- v-for="option in filteredAvailable"
346
+ v-for="option in visibleAvailable"
287
347
  :key="option.value"
288
348
  class="filter-complex__item"
289
349
  @click="addOption(option.value)"
@@ -298,6 +358,9 @@ onUnmounted(() => document.removeEventListener('click', onClickOutside))
298
358
  <p v-if="filteredAvailable.length === 0" class="filter-complex__empty">
299
359
  {{ t.noResult }}
300
360
  </p>
361
+ <p v-else-if="hiddenCount > 0" class="filter-complex__more">
362
+ {{ hiddenCount }} {{ t.more }}
363
+ </p>
301
364
  </div>
302
365
  </div>
303
366
 
@@ -358,5 +421,5 @@ onUnmounted(() => document.removeEventListener('click', onClickOutside))
358
421
  </template>
359
422
 
360
423
  <style scoped>
361
- .filter-complex{position:relative}.filter-complex__trigger{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-pill);box-sizing:border-box;color:var(--text-primary);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:var(--font-14);font-weight:var(--weight-medium);gap:6px;height:var(--rm-fbar-field-h,32px);max-width:320px;padding:0 12px;transition:all var(--duration-fast) var(--ease-in-out);white-space:nowrap}.filter-complex__trigger:hover{background:var(--bg-surface-3)}.filter-complex__icon{color:var(--color-primary);flex-shrink:0}.filter-complex__trigger--active{border-color:var(--color-primary)}.filter-complex__label{color:var(--text-muted);flex-shrink:0;font-weight:var(--weight-normal)}.filter-complex__summary{font-weight:var(--weight-semibold);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.filter-complex__badge{align-items:center;background-color:color-mix(in srgb,var(--color-primary) 10%,transparent);border-radius:var(--radius-pill);color:var(--color-primary);display:inline-flex;flex-shrink:0;font-size:var(--font-12);font-weight:var(--weight-semibold);height:18px;justify-content:center;line-height:1;min-width:18px;padding:0 6px}.filter-complex__caret{color:var(--text-muted);flex-shrink:0;transition:transform var(--duration-fast) var(--ease-in-out)}.filter-complex__caret--open{transform:rotate(180deg)}.filter-complex__panel{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-xl,12px);box-shadow:0 4px 24px rgba(var(--shadow-rgb),.1),0 2px 8px rgba(var(--shadow-rgb),.05);display:flex;left:0;max-height:460px;max-width:calc(100vw - 32px);overflow:hidden;position:absolute;top:calc(100% + 6px);width:720px;z-index:50}.filter-complex__left,.filter-complex__right{display:flex;flex-direction:column;gap:10px;overflow:hidden;padding:20px}.filter-complex__left{flex:1 1 50%}.filter-complex__right{border-left:1px solid var(--border-default);flex:1 1 50%}.filter-complex__head{align-items:center;display:flex;gap:12px;justify-content:space-between}.filter-complex__title{color:var(--text-primary);font-size:var(--font-14);font-weight:var(--weight-bold);margin:0}.filter-complex__presets{display:flex;flex-wrap:wrap;gap:8px}.filter-complex__preset{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-pill);color:var(--text-primary);cursor:pointer;font-family:var(--font-sans);font-size:var(--font-12);font-weight:var(--weight-medium);height:26px;padding:0 12px;transition:all var(--duration-fast) var(--ease-in-out);white-space:nowrap}.filter-complex__preset:hover{border-color:var(--color-primary);color:var(--color-primary)}.filter-complex__preset.is-active{background:var(--color-primary);border-color:var(--color-primary);color:var(--color-text-inverse)}.filter-complex__search{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-md,8px);display:flex;gap:8px;padding:7px 10px}.filter-complex__search:focus-within{border-color:var(--color-primary)}.filter-complex__search-input{background:transparent;border:none;color:var(--text-primary);flex:1;font-family:var(--font-sans);font-size:var(--font-14);outline:none}.filter-complex__search-input::-moz-placeholder{color:var(--text-muted)}.filter-complex__search-input::placeholder{color:var(--text-muted)}.filter-complex__search-clear{color:var(--text-muted);cursor:pointer;flex-shrink:0}.filter-complex__search-clear:hover{color:var(--text-secondary)}.filter-complex__action{background:none;border:none;cursor:pointer;font-family:var(--font-sans);font-size:var(--font-12);font-weight:var(--weight-medium);padding:0;text-decoration:underline;white-space:nowrap}.filter-complex__action--add{color:var(--color-primary)}.filter-complex__action--add:hover{color:var(--color-primary-hover)}.filter-complex__action--clear{color:var(--color-danger)}.filter-complex__list{display:flex;flex:1;flex-direction:column;overflow-y:auto}.filter-complex__list::-webkit-scrollbar{width:4px}.filter-complex__list::-webkit-scrollbar-thumb{background:var(--border-default);border-radius:100px}.filter-complex__list::-webkit-scrollbar-thumb:hover{background:var(--text-muted)}.filter-complex__item{align-items:center;background:none;border:none;border-radius:var(--radius-md,8px);color:var(--text-primary);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:var(--font-14);gap:10px;padding:7px 8px;text-align:left;transition:background-color var(--duration-fast) var(--ease-in-out)}.filter-complex__item:hover{background-color:var(--bg-surface-3)}.filter-complex__item-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.filter-complex__item-icon{flex-shrink:0}.filter-complex__item-icon--add{color:var(--color-primary)}.filter-complex__item-icon--remove{color:var(--color-danger)}.filter-complex__empty{color:var(--text-muted);font-size:var(--font-14);font-style:italic;padding:8px}.dropdown-enter-active,.dropdown-leave-active{transition:opacity var(--duration-fast) var(--ease-in-out),transform var(--duration-fast) var(--ease-in-out)}.dropdown-enter-from,.dropdown-leave-to{opacity:0;transform:translateY(-4px)}
424
+ .filter-complex{position:relative}.filter-complex__trigger{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-pill);box-sizing:border-box;color:var(--text-primary);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:var(--font-14);font-weight:var(--weight-medium);gap:6px;height:var(--rm-fbar-field-h,32px);max-width:320px;padding:0 12px;transition:all var(--duration-fast) var(--ease-in-out);white-space:nowrap}.filter-complex__trigger:hover{background:var(--bg-surface-3)}.filter-complex__icon{color:var(--color-primary);flex-shrink:0}.filter-complex__trigger--active{border-color:var(--color-primary)}.filter-complex__label{color:var(--text-muted);flex-shrink:0;font-weight:var(--weight-normal)}.filter-complex__summary{font-weight:var(--weight-semibold);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.filter-complex__badge{align-items:center;background-color:color-mix(in srgb,var(--color-primary) 10%,transparent);border-radius:var(--radius-pill);color:var(--color-primary);display:inline-flex;flex-shrink:0;font-size:var(--font-12);font-weight:var(--weight-semibold);height:18px;justify-content:center;line-height:1;min-width:18px;padding:0 6px}.filter-complex__caret{color:var(--text-muted);flex-shrink:0;transition:transform var(--duration-fast) var(--ease-in-out)}.filter-complex__caret--open{transform:rotate(180deg)}.filter-complex__panel{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-xl,12px);box-shadow:0 4px 24px rgba(var(--shadow-rgb),.1),0 2px 8px rgba(var(--shadow-rgb),.05);display:flex;left:0;max-height:460px;max-width:calc(100vw - 32px);overflow:hidden;position:absolute;top:calc(100% + 6px);width:720px;z-index:50}.filter-complex__left,.filter-complex__right{display:flex;flex-direction:column;gap:10px;overflow:hidden;padding:20px}.filter-complex__left{flex:1 1 50%}.filter-complex__right{border-left:1px solid var(--border-default);flex:1 1 50%}.filter-complex__head{align-items:center;display:flex;gap:12px;justify-content:space-between}.filter-complex__title{color:var(--text-primary);font-size:var(--font-14);font-weight:var(--weight-bold);margin:0}.filter-complex__presets{display:flex;flex-wrap:wrap;gap:8px}.filter-complex__preset{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-pill);color:var(--text-primary);cursor:pointer;font-family:var(--font-sans);font-size:var(--font-12);font-weight:var(--weight-medium);height:26px;padding:0 12px;transition:all var(--duration-fast) var(--ease-in-out);white-space:nowrap}.filter-complex__preset:hover{border-color:var(--color-primary);color:var(--color-primary)}.filter-complex__preset.is-active{background:var(--color-primary);border-color:var(--color-primary);color:var(--color-text-inverse)}.filter-complex__search{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-md,8px);display:flex;gap:8px;padding:7px 10px}.filter-complex__search:focus-within{border-color:var(--color-primary)}.filter-complex__search-input{background:transparent;border:none;color:var(--text-primary);flex:1;font-family:var(--font-sans);font-size:var(--font-14);outline:none}.filter-complex__search-input::-moz-placeholder{color:var(--text-muted)}.filter-complex__search-input::placeholder{color:var(--text-muted)}.filter-complex__search-clear{color:var(--text-muted);cursor:pointer;flex-shrink:0}.filter-complex__search-clear:hover{color:var(--text-secondary)}.filter-complex__action{background:none;border:none;cursor:pointer;font-family:var(--font-sans);font-size:var(--font-12);font-weight:var(--weight-medium);padding:0;text-decoration:underline;white-space:nowrap}.filter-complex__action--add{color:var(--color-primary)}.filter-complex__action--add:hover{color:var(--color-primary-hover)}.filter-complex__action--clear{color:var(--color-danger)}.filter-complex__list{display:flex;flex:1;flex-direction:column;overflow-y:auto}.filter-complex__list::-webkit-scrollbar{width:4px}.filter-complex__list::-webkit-scrollbar-thumb{background:var(--border-default);border-radius:100px}.filter-complex__list::-webkit-scrollbar-thumb:hover{background:var(--text-muted)}.filter-complex__item{align-items:center;background:none;border:none;border-radius:var(--radius-md,8px);color:var(--text-primary);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:var(--font-14);gap:10px;padding:7px 8px;text-align:left;transition:background-color var(--duration-fast) var(--ease-in-out)}.filter-complex__item:hover{background-color:var(--bg-surface-3)}.filter-complex__item-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.filter-complex__item-icon{flex-shrink:0}.filter-complex__item-icon--add{color:var(--color-primary)}.filter-complex__item-icon--remove{color:var(--color-danger)}.filter-complex__more{color:var(--text-muted);font-size:var(--font-12);padding:8px}.filter-complex__empty{color:var(--text-muted);font-size:var(--font-14);font-style:italic;padding:8px}.dropdown-enter-active,.dropdown-leave-active{transition:opacity var(--duration-fast) var(--ease-in-out),transform var(--duration-fast) var(--ease-in-out)}.dropdown-enter-from,.dropdown-leave-to{opacity:0;transform:translateY(-4px)}
362
425
  </style>
@@ -30,7 +30,7 @@
30
30
  .rm-rcard__desc description 14px, gris
31
31
  -->
32
32
  <script setup lang="ts">
33
- import type { RmExportSheet } from '../utils/export'
33
+ import type { RmExportPayload } from '../utils/export'
34
34
 
35
35
  withDefaults(
36
36
  defineProps<{
@@ -59,11 +59,14 @@ withDefaults(
59
59
  /** Langue du menu d'actions. Le contenu projete garde la sienne. */
60
60
  lang?: 'fr' | 'en'
61
61
  /**
62
- * Onglets d'export fournis par la page, quand elle a mieux que le rendu de
63
- * la carte (les cinq avis derriere le compteur). A defaut, les champs lus
64
- * dans la carte.
62
+ * Ce que la page a de mieux que le rendu de la carte : ses onglets
63
+ * complets (les cinq avis derriere le compteur), ses chiffres cles, ses
64
+ * sources. A defaut, les champs lus dans la carte.
65
+ *
66
+ * Une fonction est preferable dans une liste : elle n'est appelee qu'a
67
+ * l'export, alors qu'un objet est recalcule a chaque rendu de la carte.
65
68
  */
66
- sheets?: RmExportSheet[]
69
+ payload?: Partial<RmExportPayload> | (() => Partial<RmExportPayload> | null | undefined)
67
70
  }>(),
68
71
  {
69
72
  count: null,
@@ -74,7 +77,7 @@ withDefaults(
74
77
  actions: true,
75
78
  link: undefined,
76
79
  lang: 'fr',
77
- sheets: undefined,
80
+ payload: undefined,
78
81
  },
79
82
  )
80
83
  </script>
@@ -92,7 +95,7 @@ withDefaults(
92
95
  v-if="actions"
93
96
  :lang="lang"
94
97
  :link="link"
95
- :sheets="sheets"
98
+ :payload="payload"
96
99
  :visual="false"
97
100
  />
98
101
  </div>
@@ -138,6 +138,36 @@ const isCollapsible = (s: RmNavSection) => !!s.label && s.collapsible !== false
138
138
  const isOpen = (s: RmNavSection) =>
139
139
  !isCollapsible(s) || props.collapsed || openSection.value === s.key
140
140
  const isActive = (item: RmNavItem) => item.active ?? (!!item.to && item.to === props.activePath)
141
+
142
+ /**
143
+ * Infobulle du rail replie.
144
+ *
145
+ * Replie, il ne reste que l'icone. Le `title` du navigateur nommait deja
146
+ * l'entree, mais il se fait attendre une seconde environ, et ce delai n'est
147
+ * pas reglable : parcourir un rail de vingt icones devenait une suite
148
+ * d'attentes. Celle-ci sort a l'entree du pointeur, sans delai.
149
+ *
150
+ * Elle est posee en `position: fixed`, hors de `.rm-sb__nav` : ce dernier
151
+ * defile (`overflow-y: auto`), ce qui force `overflow-x` a `auto` lui aussi
152
+ * et rognait toute bulle debordant du rail de 64px. Ses coordonnees viennent
153
+ * de l'entree survolee plutot que de la largeur du rail, pour rester justes
154
+ * quel que soit le placement de la barre.
155
+ */
156
+ const tip = ref<{ label: string; x: number; y: number } | null>(null)
157
+
158
+ function showTip(event: Event, item: RmNavItem) {
159
+ if (!props.collapsed) return
160
+ const box = (event.currentTarget as HTMLElement | null)?.getBoundingClientRect()
161
+ if (!box) return
162
+ tip.value = { label: item.label, x: box.right + 8, y: box.top + box.height / 2 }
163
+ }
164
+
165
+ function hideTip() {
166
+ tip.value = null
167
+ }
168
+
169
+ // Deplier la barre pendant un survol laisserait la bulle seule a l'ecran.
170
+ watch(() => props.collapsed, hideTip)
141
171
  </script>
142
172
 
143
173
  <template>
@@ -210,7 +240,11 @@ const isActive = (item: RmNavItem) => item.active ?? (!!item.to && item.to === p
210
240
  target="_blank"
211
241
  rel="noopener noreferrer"
212
242
  class="rm-mi"
213
- :title="item.label"
243
+ :title="collapsed ? undefined : item.label"
244
+ @mouseenter="showTip($event, item)"
245
+ @mouseleave="hideTip"
246
+ @focus="showTip($event, item)"
247
+ @blur="hideTip"
214
248
  >
215
249
  <span class="rm-mi__icon"><i v-if="item.icon" :class="`ph ph-${item.icon}`" /></span>
216
250
  <span class="rm-mi__label">{{ item.label }}</span>
@@ -225,9 +259,13 @@ const isActive = (item: RmNavItem) => item.active ?? (!!item.to && item.to === p
225
259
  :to="item.to"
226
260
  class="rm-mi"
227
261
  :class="{ 'rm-mi--active': isActive(item) }"
228
- :title="item.label"
262
+ :title="collapsed ? undefined : item.label"
229
263
  :aria-current="isActive(item) ? 'page' : undefined"
230
264
  @click="item.to ? emit('navigate', item.to) : undefined"
265
+ @mouseenter="showTip($event, item)"
266
+ @mouseleave="hideTip"
267
+ @focus="showTip($event, item)"
268
+ @blur="hideTip"
231
269
  >
232
270
  <span class="rm-mi__icon"><i v-if="item.icon" :class="`ph ph-${item.icon}`" /></span>
233
271
  <span class="rm-mi__label">{{ item.label }}</span>
@@ -241,5 +279,13 @@ const isActive = (item: RmNavItem) => item.active ?? (!!item.to && item.to === p
241
279
  </nav>
242
280
 
243
281
  <slot name="footer" />
282
+
283
+ <!-- Hors de `.rm-sb__nav`, qui defile et rognerait la bulle. -->
284
+ <div
285
+ v-if="tip"
286
+ class="rm-sb__tip"
287
+ role="tooltip"
288
+ :style="{ left: `${tip.x}px`, top: `${tip.y}px` }"
289
+ >{{ tip.label }}</div>
244
290
  </aside>
245
291
  </template>
@@ -9,7 +9,7 @@
9
9
  pagination).
10
10
  -->
11
11
  <script setup lang="ts">
12
- import type { RmExportSheet } from '../utils/export'
12
+ import type { RmExportPayload } from '../utils/export'
13
13
 
14
14
  withDefaults(
15
15
  defineProps<{
@@ -24,12 +24,12 @@ withDefaults(
24
24
  /** Langue du menu d'actions. Le contenu projete garde la sienne. */
25
25
  lang?: 'fr' | 'en'
26
26
  /**
27
- * Onglets d'export fournis par la page, quand elle a mieux que le rendu :
28
- * la donnee complete plutot que la page affichee du tableau.
27
+ * Ce que la page a de mieux que le rendu : la donnee complete plutot que
28
+ * la page affichee du tableau, ses chiffres cles, ses sources.
29
29
  */
30
- sheets?: RmExportSheet[]
30
+ payload?: Partial<RmExportPayload> | (() => Partial<RmExportPayload> | null | undefined)
31
31
  }>(),
32
- { actions: true, link: undefined, lang: 'fr', sheets: undefined },
32
+ { actions: true, link: undefined, lang: 'fr', payload: undefined },
33
33
  )
34
34
  </script>
35
35
 
@@ -37,7 +37,7 @@ withDefaults(
37
37
  <section class="rm-table-card">
38
38
  <div v-if="$slots.header || actions" class="rm-table-card__header">
39
39
  <slot name="header" />
40
- <RmCardActions v-if="actions" :lang="lang" :link="link" :sheets="sheets" />
40
+ <RmCardActions v-if="actions" :lang="lang" :link="link" :payload="payload" />
41
41
  </div>
42
42
  <slot />
43
43
  </section>
@@ -12,9 +12,10 @@
12
12
  * d'une declaration de la page : c'est ce qui rend le menu utilisable sur les
13
13
  * cartes deja ecrites, sans les rouvrir une par une.
14
14
  */
15
- import type { RmExportSheet } from '../utils/export.js';
15
+ import type { RmExportPayload } from '../utils/export.js';
16
16
  /** Les actions qu'une carte propose. `link` copie, il ne telecharge pas. */
17
17
  export type RmCardAction = 'png' | 'svg' | 'csv' | 'xlsx' | 'link';
18
+ export type RmCardPayload = Partial<RmExportPayload>;
18
19
  export interface RmCardActionContext {
19
20
  /** La carte visee. */
20
21
  card: Element;
@@ -23,8 +24,16 @@ export interface RmCardActionContext {
23
24
  name?: string;
24
25
  /** Lien copie par « Copier le lien direct ». A defaut, l'URL courante. */
25
26
  link?: string;
26
- /** Onglets fournis par la page, quand elle a mieux que le rendu a l'ecran. */
27
- sheets?: RmExportSheet[];
27
+ /**
28
+ * Ce que la page a de mieux que le rendu a l'ecran : ses onglets complets,
29
+ * ses chiffres cles, ses sources, son sous-titre. Ce qui est pose ici
30
+ * remplace ce que le DOM aurait donne.
31
+ *
32
+ * Une fonction est preferable des qu'une liste monte la meme carte vingt
33
+ * fois : elle n'est appelee qu'a l'export, alors qu'un objet lie en prop est
34
+ * recalcule a chaque rendu de chaque carte.
35
+ */
36
+ payload?: RmCardPayload | (() => RmCardPayload | null | undefined);
28
37
  }
29
38
  export declare function useRmCardActions(): {
30
39
  runCardAction: (action: RmCardAction, context: RmCardActionContext) => Promise<import("./useRmExport").RmExportResult>;
@@ -27,18 +27,30 @@ function absolute(link) {
27
27
  if (typeof window === "undefined") return link;
28
28
  return new URL(link, window.location.href).href;
29
29
  }
30
+ const ZOOM_CLASS = "rm-card-zoom";
31
+ const ZOOM_BODY_CLASS = "rm-card-zoom-open";
32
+ function closeZoom() {
33
+ document.querySelectorAll(`.${ZOOM_CLASS}`).forEach((el) => el.classList.remove(ZOOM_CLASS));
34
+ document.body.classList.remove(ZOOM_BODY_CLASS);
35
+ document.removeEventListener("keydown", onZoomKey);
36
+ }
37
+ function onZoomKey(event) {
38
+ if (event.key === "Escape") closeZoom();
39
+ }
30
40
  export function useRmCardActions() {
31
41
  const { run } = useRmExport();
32
42
  async function runCardAction(action, context) {
33
43
  const { card, lang = "fr" } = context;
34
- const title = context.name || rmCardTitle(card) || "Export";
44
+ const provided = (typeof context.payload === "function" ? context.payload() : context.payload) ?? {};
45
+ const title = context.name || provided.title || rmCardTitle(card) || "Export";
35
46
  const format = action === "link" ? "link" : action;
36
- const sheets = action === "png" || action === "svg" ? [] : context.sheets?.length ? context.sheets : await rmCardSheets(card, title, lang);
47
+ const sheets = action === "png" || action === "svg" ? [] : provided.sheets?.length ? provided.sheets : await rmCardSheets(card, title, lang);
37
48
  return run(configOf(format, fileNameOf(title)), {
38
49
  title,
39
- sheets,
40
50
  charts: rmCardCharts(card).map((el) => ({ title, el })),
41
- permalink: absolute(context.link)
51
+ permalink: absolute(context.link),
52
+ ...provided,
53
+ sheets
42
54
  }, lang);
43
55
  }
44
56
  async function toggleCardFullscreen(card) {
@@ -47,7 +59,18 @@ export function useRmCardActions() {
47
59
  await document.exitFullscreen();
48
60
  return;
49
61
  }
50
- await card.requestFullscreen?.();
62
+ if (card.classList.contains(ZOOM_CLASS)) {
63
+ closeZoom();
64
+ return;
65
+ }
66
+ try {
67
+ await card.requestFullscreen?.();
68
+ return;
69
+ } catch {
70
+ }
71
+ card.classList.add(ZOOM_CLASS);
72
+ document.body.classList.add(ZOOM_BODY_CLASS);
73
+ document.addEventListener("keydown", onZoomKey);
51
74
  }
52
75
  return { runCardAction, toggleCardFullscreen };
53
76
  }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Les options d'un filtre a referentiel long, cherchees dans l'index.
3
+ *
4
+ * Un filtre agrege un champ (les marques, les molecules) et cherche sur
5
+ * d'autres. La regle qui compte est la : sur un index ou une ligne porte a la
6
+ * fois la marque, la molecule, la classe et le laboratoire, chercher dans un
7
+ * filtre doit interroger AUSSI les champs freres, sans changer ce que le
8
+ * filtre remonte. « keytruda » tape dans le filtre molecule trouve la ligne
9
+ * par sa marque et l'agregation en sort PEMBROLIZUMAB ; « pembrolizumab »
10
+ * tape dans le filtre marque en sort Keytruda. Sans cela l'utilisateur doit
11
+ * connaitre le nom qu'il cherche dans le vocabulaire exact du filtre, ce qui
12
+ * est justement ce qu'il vient demander.
13
+ *
14
+ * Le module ne parle pas au moteur : il ecrit le corps de la requete et relit
15
+ * les seaux. Le transport (proxy, cache, index) reste a l'application.
16
+ */
17
+ export interface RmFilterOption {
18
+ label: string;
19
+ value: string;
20
+ }
21
+ export interface RmFilterOptionsSpec {
22
+ /**
23
+ * Ce que le filtre agrege. Un champ : la valeur est son propre libelle
24
+ * (`brand.keyword`). Deux champs `[libelle, valeur]` : le premier s'affiche,
25
+ * le second est ce que l'analyse filtre (un libelle ATC et son code).
26
+ */
27
+ field: string | [string, string];
28
+ /**
29
+ * Champs interroges par la saisie, dans l'ordre : le champ propre au filtre
30
+ * d'abord, ses synonymes ensuite. Champs analyses (`brand`), pas leur
31
+ * `.keyword` : un `.keyword` ne repond pas a une recherche partielle.
32
+ */
33
+ search: string[];
34
+ /** La saisie, telle qu'elle est tapee. Vide, tout le referentiel remonte. */
35
+ input: string;
36
+ /**
37
+ * Plafond de la liste. Au-dela, c'est la recherche qui trie : une liste
38
+ * coupee a cent laisse croire que ce qui suit n'existe pas.
39
+ */
40
+ size?: number;
41
+ }
42
+ /** Le corps de la requete d'agregation, a poster tel quel sur l'index. */
43
+ export declare function rmFilterOptionsQuery(spec: RmFilterOptionsSpec): {
44
+ size: number;
45
+ query: {
46
+ bool: {
47
+ must: {
48
+ query_string: {
49
+ fields: string[];
50
+ query: string;
51
+ analyze_wildcard: boolean;
52
+ default_operator: string;
53
+ };
54
+ }[];
55
+ };
56
+ };
57
+ aggs: {
58
+ count: {
59
+ multi_terms: {
60
+ terms: {
61
+ field: string;
62
+ }[];
63
+ size: number;
64
+ order: {
65
+ _key: string;
66
+ };
67
+ };
68
+ terms?: undefined;
69
+ } | {
70
+ terms: {
71
+ field: string;
72
+ size: number;
73
+ order: {
74
+ _key: string;
75
+ };
76
+ };
77
+ multi_terms?: undefined;
78
+ };
79
+ };
80
+ };
81
+ /** Les options, relues dans la reponse du moteur. */
82
+ export declare function rmFilterOptions(response: any): RmFilterOption[];
@@ -0,0 +1,40 @@
1
+ const DEFAULT_SIZE = 5e3;
2
+ function wildcard(input) {
3
+ return ("*" + input + "*").normalize("NFD").replace(/[\u0300-\u036f]/g, "");
4
+ }
5
+ export function rmFilterOptionsQuery(spec) {
6
+ const size = spec.size ?? DEFAULT_SIZE;
7
+ const count = Array.isArray(spec.field) ? {
8
+ multi_terms: {
9
+ terms: spec.field.map((field) => ({ field })),
10
+ size,
11
+ order: { _key: "asc" }
12
+ }
13
+ } : { terms: { field: spec.field, size, order: { _key: "asc" } } };
14
+ return {
15
+ size: 0,
16
+ query: {
17
+ bool: {
18
+ must: [
19
+ {
20
+ query_string: {
21
+ // Plusieurs champs valent un OU : la ligne repond des qu'un seul
22
+ // d'entre eux porte la saisie. C'est ce qui rend les synonymes.
23
+ fields: spec.search,
24
+ query: wildcard(spec.input),
25
+ analyze_wildcard: true,
26
+ default_operator: "AND"
27
+ }
28
+ }
29
+ ]
30
+ }
31
+ },
32
+ aggs: { count }
33
+ };
34
+ }
35
+ export function rmFilterOptions(response) {
36
+ const buckets = response?.aggregations?.count?.buckets ?? [];
37
+ return buckets.map(
38
+ (bucket) => Array.isArray(bucket.key) ? { label: String(bucket.key[0]), value: String(bucket.key[1]) } : { label: String(bucket.key), value: String(bucket.key) }
39
+ );
40
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekkare/romulus",
3
- "version": "0.1.52",
3
+ "version": "0.1.54",
4
4
  "description": "Romulus Design System - Nuxt module by Tekkare",
5
5
  "license": "MIT",
6
6
  "type": "module",