@tekkare/romulus 0.1.72 → 0.1.74
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
|
@@ -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__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}}
|
|
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__dot{animation:rm-rail-dot-pulse 2s ease-out infinite;background:var(--color-success,#16a34a);border:2px solid var(--rm-shell-surface);border-radius:9999px;height:10px;position:absolute;right:-2px;top:-2px;width:10px}@keyframes rm-rail-dot-pulse{0%{box-shadow:0 0 0 0 rgba(22,163,74,.55)}70%{box-shadow:0 0 0 6px rgba(22,163,74,0)}to{box-shadow:0 0 0 0 rgba(22,163,74,0)}}@media (prefers-reduced-motion:reduce){.rm-rail__dot{animation:none}}.rm-rail__item--active .rm-rail__dot{border-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}}
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
RmComplexSelect - champ de filtre a deux colonnes : ajouter d'un cote,
|
|
3
|
+
retirer de l'autre.
|
|
4
|
+
|
|
5
|
+
Reprend `arg-complex-select` d'Auriga : un intitule, un champ qui enumere la
|
|
6
|
+
selection, et un panneau large ou la colonne de gauche propose les valeurs
|
|
7
|
+
disponibles (avec selections rapides et recherche) pendant que celle de
|
|
8
|
+
droite tient la selection en cours.
|
|
9
|
+
|
|
10
|
+
Laquelle prendre : celle-ci quand le referentiel est trop long pour se
|
|
11
|
+
cocher d'un bloc (des milliers de marques, de molecules, de pays) ;
|
|
12
|
+
`RmMultipleSelect` pour une liste courte a cocher ; `RmFilterComplex` pour
|
|
13
|
+
la pastille equivalente dans une barre de filtres.
|
|
14
|
+
|
|
15
|
+
Trois choses changent par rapport a Auriga.
|
|
16
|
+
|
|
17
|
+
La selection est un `v-model`, plus le couple `already_selected` en entree
|
|
18
|
+
et `update:Selection` en sortie : les deux se repondaient en boucle des que
|
|
19
|
+
la page reposait la selection elle-meme.
|
|
20
|
+
|
|
21
|
+
Les lignes sont des boutons : la colonne se parcourt au clavier, alors
|
|
22
|
+
qu'Auriga posait des `div` avec un `@click` et un `@mouseover` pour changer
|
|
23
|
+
l'icone, invisible pour qui n'a pas de souris.
|
|
24
|
+
|
|
25
|
+
Le mode « appliquer / annuler » a ete laisse de cote : il etait deja mis en
|
|
26
|
+
commentaire dans Auriga, et aucune page ne l'appelait.
|
|
27
|
+
-->
|
|
28
|
+
<script setup lang="ts">
|
|
29
|
+
import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
|
|
30
|
+
|
|
31
|
+
export interface RmComplexSelectPreset {
|
|
32
|
+
label: string
|
|
33
|
+
values: string[]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const props = withDefaults(defineProps<{
|
|
37
|
+
/** Intitule pose au-dessus du champ. */
|
|
38
|
+
label: string
|
|
39
|
+
options: string[]
|
|
40
|
+
loading?: boolean
|
|
41
|
+
disabled?: boolean
|
|
42
|
+
/** Selections rapides. Vides, la section ne sort pas. */
|
|
43
|
+
presets?: RmComplexSelectPreset[] | null
|
|
44
|
+
/** Action « Tout » en tete de la colonne de gauche. */
|
|
45
|
+
showSelectAll?: boolean
|
|
46
|
+
/** L'action « Tout » annonce combien de valeurs elle ajouterait. */
|
|
47
|
+
selectAllCount?: boolean
|
|
48
|
+
/**
|
|
49
|
+
* La liste est tronquee par le serveur : le compte de l'action « Tout »
|
|
50
|
+
* s'annonce comme un maximum, sinon il se lit comme le total du
|
|
51
|
+
* referentiel.
|
|
52
|
+
*/
|
|
53
|
+
hasMore?: boolean
|
|
54
|
+
addPlaceholder?: string
|
|
55
|
+
removePlaceholder?: string
|
|
56
|
+
emptyLabel?: string
|
|
57
|
+
emptyContent?: string
|
|
58
|
+
/**
|
|
59
|
+
* Filtrage local de la colonne de gauche. A passer a `false` quand les
|
|
60
|
+
* options arrivent deja filtrees du serveur (cf. `RmDropdownSelect`).
|
|
61
|
+
*/
|
|
62
|
+
filterSearch?: boolean
|
|
63
|
+
/** Pastille du nombre de valeurs retenues, posee sur le champ. */
|
|
64
|
+
showAmount?: boolean
|
|
65
|
+
/** Pastille d'aide a cote de l'intitule, qui emet `openInfo`. */
|
|
66
|
+
labelInfo?: boolean
|
|
67
|
+
/** Nombre de lignes posees d'un coup dans la colonne de gauche. */
|
|
68
|
+
maxOptions?: number
|
|
69
|
+
lang?: 'fr' | 'en'
|
|
70
|
+
}>(), {
|
|
71
|
+
loading: false,
|
|
72
|
+
disabled: false,
|
|
73
|
+
presets: null,
|
|
74
|
+
showSelectAll: false,
|
|
75
|
+
selectAllCount: false,
|
|
76
|
+
hasMore: false,
|
|
77
|
+
addPlaceholder: undefined,
|
|
78
|
+
removePlaceholder: undefined,
|
|
79
|
+
emptyLabel: undefined,
|
|
80
|
+
emptyContent: undefined,
|
|
81
|
+
filterSearch: true,
|
|
82
|
+
showAmount: true,
|
|
83
|
+
labelInfo: false,
|
|
84
|
+
maxOptions: 500,
|
|
85
|
+
lang: 'fr',
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
const emit = defineEmits<{
|
|
89
|
+
/** Selection complete apres le changement. Double le `v-model`. */
|
|
90
|
+
change: [values: string[]]
|
|
91
|
+
/** Saisie de la colonne « ajouter », amortie a 500 ms. */
|
|
92
|
+
search: [value: string]
|
|
93
|
+
openInfo: []
|
|
94
|
+
}>()
|
|
95
|
+
|
|
96
|
+
const selected = defineModel<string[]>({ default: () => [] })
|
|
97
|
+
|
|
98
|
+
const WORDS = {
|
|
99
|
+
fr: {
|
|
100
|
+
empty: 'Aucune sélection',
|
|
101
|
+
quick: 'Sélection rapide',
|
|
102
|
+
add: 'Ajouter',
|
|
103
|
+
custom: 'Sélection personnalisée',
|
|
104
|
+
all: 'Tout',
|
|
105
|
+
current: 'Sélection en cours',
|
|
106
|
+
clear: 'Tout effacer',
|
|
107
|
+
searchAdd: 'Rechercher une valeur',
|
|
108
|
+
searchDel: 'Rechercher dans la sélection',
|
|
109
|
+
notFound: 'Aucun résultat',
|
|
110
|
+
loading: 'Chargement',
|
|
111
|
+
noSelection: 'Rien de retenu pour le moment',
|
|
112
|
+
},
|
|
113
|
+
en: {
|
|
114
|
+
empty: 'No selection',
|
|
115
|
+
quick: 'Quick selection',
|
|
116
|
+
add: 'Add',
|
|
117
|
+
custom: 'Custom selection',
|
|
118
|
+
all: 'All',
|
|
119
|
+
current: 'Current selection',
|
|
120
|
+
clear: 'Clear all',
|
|
121
|
+
searchAdd: 'Search a value',
|
|
122
|
+
searchDel: 'Search in the selection',
|
|
123
|
+
notFound: 'No result',
|
|
124
|
+
loading: 'Loading',
|
|
125
|
+
noSelection: 'Nothing selected yet',
|
|
126
|
+
},
|
|
127
|
+
} as const
|
|
128
|
+
|
|
129
|
+
const t = computed(() => WORDS[props.lang])
|
|
130
|
+
|
|
131
|
+
const isOpen = ref(false)
|
|
132
|
+
const addSearch = ref('')
|
|
133
|
+
const removeSearch = ref('')
|
|
134
|
+
const activePreset = ref<string>()
|
|
135
|
+
const rootRef = ref<HTMLElement>()
|
|
136
|
+
const addRef = ref<HTMLInputElement>()
|
|
137
|
+
|
|
138
|
+
/** La colonne de gauche ne propose que ce qui n'est pas deja retenu. */
|
|
139
|
+
const addOptions = computed(() => {
|
|
140
|
+
const needle = addSearch.value.trim().toLowerCase()
|
|
141
|
+
const parts = needle && props.filterSearch ? needle.split(' ').filter(Boolean) : []
|
|
142
|
+
return props.options
|
|
143
|
+
.filter(option => !selected.value.includes(option))
|
|
144
|
+
.filter(option => parts.every(part => option.toLowerCase().includes(part)))
|
|
145
|
+
.slice(0, props.maxOptions)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
const removeOptions = computed(() => {
|
|
149
|
+
const needle = removeSearch.value.trim().toLowerCase()
|
|
150
|
+
if (!needle) return selected.value
|
|
151
|
+
return selected.value.filter(option => option.toLowerCase().includes(needle))
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
const summary = computed(() => selected.value.join(', '))
|
|
155
|
+
|
|
156
|
+
const selectAllLabel = computed(() => {
|
|
157
|
+
if (!props.selectAllCount) return t.value.all
|
|
158
|
+
const count = props.hasMore ? `Max: ${addOptions.value.length}` : addOptions.value.length
|
|
159
|
+
return `${t.value.all} (${count})`
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
let debounceId: ReturnType<typeof setTimeout> | undefined
|
|
163
|
+
watch(addSearch, (value) => {
|
|
164
|
+
clearTimeout(debounceId)
|
|
165
|
+
debounceId = setTimeout(() => emit('search', value), 500)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
function close() {
|
|
169
|
+
isOpen.value = false
|
|
170
|
+
document.removeEventListener('click', onDocumentClick)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function onDocumentClick(event: MouseEvent) {
|
|
174
|
+
if (!rootRef.value?.contains(event.target as Node)) close()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function toggle() {
|
|
178
|
+
if (props.disabled) return
|
|
179
|
+
if (isOpen.value) return close()
|
|
180
|
+
|
|
181
|
+
isOpen.value = true
|
|
182
|
+
document.addEventListener('click', onDocumentClick)
|
|
183
|
+
await nextTick()
|
|
184
|
+
addRef.value?.focus()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function commit(values: string[]) {
|
|
188
|
+
selected.value = values
|
|
189
|
+
emit('change', values)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function add(option: string) {
|
|
193
|
+
activePreset.value = undefined
|
|
194
|
+
commit([...selected.value, option])
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function remove(option: string) {
|
|
198
|
+
activePreset.value = undefined
|
|
199
|
+
commit(selected.value.filter(value => value !== option))
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function addAll() {
|
|
203
|
+
activePreset.value = undefined
|
|
204
|
+
commit([...selected.value, ...addOptions.value])
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function clearAll() {
|
|
208
|
+
activePreset.value = undefined
|
|
209
|
+
commit([])
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Une selection rapide remplace la selection : elle ne s'y ajoute pas. */
|
|
213
|
+
function applyPreset(preset: RmComplexSelectPreset) {
|
|
214
|
+
if (activePreset.value === preset.label) {
|
|
215
|
+
clearAll()
|
|
216
|
+
return
|
|
217
|
+
}
|
|
218
|
+
commit([...new Set(preset.values)])
|
|
219
|
+
activePreset.value = preset.label
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
onUnmounted(() => {
|
|
223
|
+
clearTimeout(debounceId)
|
|
224
|
+
document.removeEventListener('click', onDocumentClick)
|
|
225
|
+
})
|
|
226
|
+
</script>
|
|
227
|
+
|
|
228
|
+
<template>
|
|
229
|
+
<div ref="rootRef" class="rm-csel" @keydown.escape="close">
|
|
230
|
+
<p class="rm-csel__label">
|
|
231
|
+
{{ label }}
|
|
232
|
+
<button v-if="labelInfo" type="button" class="rm-csel__info" @click="emit('openInfo')">
|
|
233
|
+
<RmIcon name="Info" :size="14" />
|
|
234
|
+
</button>
|
|
235
|
+
</p>
|
|
236
|
+
|
|
237
|
+
<button
|
|
238
|
+
type="button"
|
|
239
|
+
class="rm-csel__field"
|
|
240
|
+
:class="{ 'is-open': isOpen, 'is-disabled': disabled }"
|
|
241
|
+
:disabled="disabled"
|
|
242
|
+
:aria-expanded="isOpen"
|
|
243
|
+
aria-haspopup="true"
|
|
244
|
+
@click.stop="toggle"
|
|
245
|
+
>
|
|
246
|
+
<RmTooltip :text="summary" :disabled="selected.length === 0" class="rm-csel__value-slot">
|
|
247
|
+
<span class="rm-csel__value" :class="{ 'is-empty': selected.length === 0 }">
|
|
248
|
+
{{ selected.length > 0 ? summary : emptyLabel || t.empty }}
|
|
249
|
+
</span>
|
|
250
|
+
</RmTooltip>
|
|
251
|
+
<span v-if="showAmount && selected.length > 0" class="rm-csel__count">
|
|
252
|
+
{{ selected.length > 99 ? '99+' : selected.length }}
|
|
253
|
+
</span>
|
|
254
|
+
<RmIcon name="CaretDown" :size="16" />
|
|
255
|
+
</button>
|
|
256
|
+
|
|
257
|
+
<div v-show="isOpen" class="rm-csel__panel" @click.stop>
|
|
258
|
+
<div class="rm-csel__column">
|
|
259
|
+
<div v-if="presets && presets.length > 0" class="rm-csel__presets">
|
|
260
|
+
<h2 class="rm-csel__section">{{ t.quick }}</h2>
|
|
261
|
+
<div class="rm-csel__pills">
|
|
262
|
+
<button
|
|
263
|
+
v-for="preset in presets"
|
|
264
|
+
:key="preset.label"
|
|
265
|
+
type="button"
|
|
266
|
+
class="rm-csel__pill"
|
|
267
|
+
:class="{ 'is-active': activePreset === preset.label }"
|
|
268
|
+
:aria-pressed="activePreset === preset.label"
|
|
269
|
+
@click="applyPreset(preset)"
|
|
270
|
+
>
|
|
271
|
+
{{ preset.label }}
|
|
272
|
+
</button>
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
|
|
276
|
+
<div class="rm-csel__head">
|
|
277
|
+
<h2 class="rm-csel__section">{{ presets && presets.length > 0 ? t.custom : t.add }}</h2>
|
|
278
|
+
<button
|
|
279
|
+
v-if="showSelectAll || selectAllCount"
|
|
280
|
+
type="button"
|
|
281
|
+
class="rm-csel__action"
|
|
282
|
+
:disabled="addOptions.length === 0"
|
|
283
|
+
@click="addAll"
|
|
284
|
+
>
|
|
285
|
+
{{ selectAllLabel }}
|
|
286
|
+
</button>
|
|
287
|
+
</div>
|
|
288
|
+
|
|
289
|
+
<div class="rm-csel__search">
|
|
290
|
+
<RmIcon name="MagnifyingGlass" :size="14" />
|
|
291
|
+
<input
|
|
292
|
+
ref="addRef"
|
|
293
|
+
v-model="addSearch"
|
|
294
|
+
type="search"
|
|
295
|
+
class="rm-csel__search-input"
|
|
296
|
+
:placeholder="addPlaceholder || t.searchAdd"
|
|
297
|
+
:aria-label="addPlaceholder || t.searchAdd"
|
|
298
|
+
>
|
|
299
|
+
</div>
|
|
300
|
+
|
|
301
|
+
<p v-if="loading" class="rm-csel__state" aria-busy="true">{{ t.loading }}</p>
|
|
302
|
+
<p v-else-if="addOptions.length === 0" class="rm-csel__state">
|
|
303
|
+
{{ emptyContent || t.notFound }}
|
|
304
|
+
</p>
|
|
305
|
+
<div v-else class="rm-csel__list">
|
|
306
|
+
<button
|
|
307
|
+
v-for="option in addOptions"
|
|
308
|
+
:key="option"
|
|
309
|
+
type="button"
|
|
310
|
+
class="rm-csel__option rm-csel__option--add"
|
|
311
|
+
@click="add(option)"
|
|
312
|
+
>
|
|
313
|
+
<RmIcon name="PlusCircle" :size="14" />
|
|
314
|
+
<span>{{ option }}</span>
|
|
315
|
+
</button>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
|
|
319
|
+
<div class="rm-csel__column rm-csel__column--right">
|
|
320
|
+
<div class="rm-csel__head">
|
|
321
|
+
<h2 class="rm-csel__section">{{ t.current }}</h2>
|
|
322
|
+
<button
|
|
323
|
+
type="button"
|
|
324
|
+
class="rm-csel__action rm-csel__action--clear"
|
|
325
|
+
:disabled="selected.length === 0"
|
|
326
|
+
@click="clearAll"
|
|
327
|
+
>
|
|
328
|
+
{{ t.clear }}
|
|
329
|
+
</button>
|
|
330
|
+
</div>
|
|
331
|
+
|
|
332
|
+
<div class="rm-csel__search">
|
|
333
|
+
<RmIcon name="MagnifyingGlass" :size="14" />
|
|
334
|
+
<input
|
|
335
|
+
v-model="removeSearch"
|
|
336
|
+
type="search"
|
|
337
|
+
class="rm-csel__search-input"
|
|
338
|
+
:placeholder="removePlaceholder || t.searchDel"
|
|
339
|
+
:aria-label="removePlaceholder || t.searchDel"
|
|
340
|
+
>
|
|
341
|
+
</div>
|
|
342
|
+
|
|
343
|
+
<p v-if="selected.length === 0" class="rm-csel__state">{{ t.noSelection }}</p>
|
|
344
|
+
<p v-else-if="removeOptions.length === 0" class="rm-csel__state">
|
|
345
|
+
{{ emptyContent || t.notFound }}
|
|
346
|
+
</p>
|
|
347
|
+
<div v-else class="rm-csel__list">
|
|
348
|
+
<button
|
|
349
|
+
v-for="option in removeOptions"
|
|
350
|
+
:key="option"
|
|
351
|
+
type="button"
|
|
352
|
+
class="rm-csel__option rm-csel__option--remove"
|
|
353
|
+
@click="remove(option)"
|
|
354
|
+
>
|
|
355
|
+
<RmIcon name="MinusCircle" :size="16" />
|
|
356
|
+
<span>{{ option }}</span>
|
|
357
|
+
</button>
|
|
358
|
+
</div>
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
362
|
+
</template>
|
|
363
|
+
|
|
364
|
+
<style scoped>
|
|
365
|
+
.rm-csel{align-items:flex-start;display:flex;flex-direction:column;gap:4px;position:relative}.rm-csel__label{align-items:center;color:var(--text-secondary);display:flex;font-size:var(--font-12,12px);gap:4px;line-height:14px}.rm-csel__info{background:none;border:none;color:var(--text-muted);cursor:pointer;display:flex;padding:0}.rm-csel__field{align-items:center;background:var(--bg-surface);border:1.5px solid var(--border-default);border-radius:var(--radius-md,8px);color:var(--text-primary);cursor:pointer;display:flex;font-family:inherit;font-size:var(--font-14,14px);font-weight:700;gap:4px;line-height:16px;padding:12px;text-align:left;width:196px}.rm-csel__field:hover:not(.is-disabled){border-color:rgba(var(--brand-rgb),.3)}.rm-csel__field.is-open{border-color:var(--color-primary)}.rm-csel__field.is-disabled{background:var(--bg-surface-2);color:var(--text-muted);cursor:not-allowed}.rm-csel__field:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}.rm-csel__value-slot{display:block;flex:1;min-width:0}.rm-csel__value{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rm-csel__value.is-empty{color:var(--text-muted);font-style:italic;font-weight:400}.rm-csel__field :deep(svg){color:var(--text-secondary);flex-shrink:0}.rm-csel__count{align-items:center;background:rgba(var(--brand-rgb),.1);border-radius:50%;color:var(--color-primary);display:flex;flex-shrink:0;font-size:var(--font-12,12px);font-weight:600;height:20px;justify-content:center;width:20px}.rm-csel__panel{background:var(--bg-surface);border:1.5px solid var(--border-default);border-radius:var(--radius-lg,12px);box-shadow:var(--r-shadow-lg,0 8px 24px rgba(0,0,0,.12));box-sizing:border-box;display:flex;left:0;margin-top:8px;max-width:calc(100vw - 32px);position:absolute;top:100%;width:620px;z-index:12}.rm-csel__column{display:flex;flex:1;flex-direction:column;gap:8px;min-width:0;padding:16px}.rm-csel__column--right{background:var(--bg-surface-2);border-left:1px solid var(--border-soft);border-radius:0 var(--radius-lg,12px) var(--radius-lg,12px) 0}.rm-csel__presets{border-bottom:1px solid var(--border-soft);display:flex;flex-direction:column;gap:8px;padding-bottom:8px}.rm-csel__pills{display:flex;flex-wrap:wrap;gap:4px}.rm-csel__pill{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:9999px;color:var(--text-secondary);cursor:pointer;font-family:inherit;font-size:var(--font-12,12px);padding:4px 10px}.rm-csel__pill.is-active,.rm-csel__pill:hover{border-color:var(--color-primary);color:var(--color-primary)}.rm-csel__pill.is-active{background:rgba(var(--brand-rgb),.1);font-weight:700}.rm-csel__head{align-items:center;display:flex;gap:8px;justify-content:space-between}.rm-csel__section{color:var(--text-secondary);font-size:var(--font-12,12px);font-weight:700;margin:0}.rm-csel__action{background:none;border:none;color:var(--color-primary);cursor:pointer;font-family:inherit;font-size:var(--font-12,12px);font-weight:600;padding:0}.rm-csel__action:disabled{color:var(--text-muted);cursor:not-allowed}.rm-csel__action:hover:not(:disabled){text-decoration:underline}.rm-csel__action--clear:not(:disabled){color:var(--color-danger)}.rm-csel__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:6px 10px}.rm-csel__search :deep(svg){color:var(--text-muted);flex-shrink:0}.rm-csel__search-input{background:none;border:none;color:var(--text-primary);font-family:inherit;font-size:var(--font-14,14px);width:100%}.rm-csel__search-input::-moz-placeholder{color:var(--text-muted);font-size:var(--font-12,12px)}.rm-csel__search-input::placeholder{color:var(--text-muted);font-size:var(--font-12,12px)}.rm-csel__search-input:focus-visible{outline:none}.rm-csel__state{color:var(--text-secondary);font-size:var(--font-14,14px);font-style:italic;padding-top:8px}.rm-csel__list{display:flex;flex-direction:column;gap:4px;height:260px;overflow-y:auto;scrollbar-width:thin}.rm-csel__option{align-items:flex-start;background:none;border:none;border-radius:var(--radius-sm,4px);color:var(--text-primary);cursor:pointer;display:flex;font-family:inherit;font-size:var(--font-14,14px);gap:8px;line-height:160%;padding:2px 4px;text-align:left}.rm-csel__option :deep(svg){flex-shrink:0;margin-top:4px}.rm-csel__option:hover{background:rgba(var(--brand-rgb),.05)}.rm-csel__option:focus-visible{outline:2px solid var(--color-primary);outline-offset:-2px}.rm-csel__option--add :deep(svg){color:var(--color-primary)}.rm-csel__option--remove :deep(svg){color:var(--color-danger)}@media (max-width:767px){.rm-csel__panel{flex-direction:column;width:calc(100vw - 32px)}.rm-csel__column--right{border-left:none;border-top:1px solid var(--border-soft)}.rm-csel__list{height:180px}}
|
|
366
|
+
</style>
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
RmMultipleSelect - champ de filtre a choix multiple.
|
|
3
|
+
|
|
4
|
+
Reprend `arg-multiple-select` d'Auriga : un intitule, un champ qui enumere
|
|
5
|
+
ce qui est retenu avec le compte a droite, et un panneau de cases a cocher
|
|
6
|
+
avec, au besoin, une recherche et une ligne « Tout ».
|
|
7
|
+
|
|
8
|
+
Laquelle prendre : celle-ci pour cocher plusieurs valeurs dans un panneau de
|
|
9
|
+
filtres ; `RmDropdownSelect` pour un choix unique ; `RmComplexSelect` quand
|
|
10
|
+
la liste est trop longue pour se cocher et se mene en deux colonnes
|
|
11
|
+
(ajouter / retirer).
|
|
12
|
+
|
|
13
|
+
Trois choses changent par rapport a Auriga.
|
|
14
|
+
|
|
15
|
+
La selection est un `v-model` sur les valeurs retenues, plus un couple
|
|
16
|
+
`default_select` en entree et `update:Selection` en sortie : les deux se
|
|
17
|
+
desynchronisaient des que la page reposait la selection elle-meme.
|
|
18
|
+
|
|
19
|
+
Les options sont des cases a cocher (`role="checkbox"`, `aria-checked`)
|
|
20
|
+
atteignables au clavier, la ou Auriga posait des `div` cliquables.
|
|
21
|
+
|
|
22
|
+
Les colonnes multiples, les listes imbriquees et les options desactivees par
|
|
23
|
+
rang ont ete laissees de cote : aucune page ne s'en sert, et le rang comme
|
|
24
|
+
identifiant cassait des que la liste etait filtree.
|
|
25
|
+
-->
|
|
26
|
+
<script setup lang="ts">
|
|
27
|
+
import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
|
|
28
|
+
|
|
29
|
+
const props = withDefaults(defineProps<{
|
|
30
|
+
/** Intitule pose au-dessus du champ. */
|
|
31
|
+
label: string
|
|
32
|
+
options: string[]
|
|
33
|
+
/** Champ de recherche en tete du panneau. */
|
|
34
|
+
search?: boolean
|
|
35
|
+
disabled?: boolean
|
|
36
|
+
loading?: boolean
|
|
37
|
+
/** Ligne « Tout » en tete de liste. */
|
|
38
|
+
showAll?: boolean
|
|
39
|
+
/** Pastille du nombre de valeurs retenues, posee sur le champ. */
|
|
40
|
+
showAmount?: boolean
|
|
41
|
+
/** Tout est retenu tant que la page n'a rien choisi. */
|
|
42
|
+
preselectAll?: boolean
|
|
43
|
+
emptyLabel?: string
|
|
44
|
+
emptyContent?: string
|
|
45
|
+
searchPlaceholder?: string
|
|
46
|
+
/**
|
|
47
|
+
* Filtrage local de la liste sur la saisie. A passer a `false` quand les
|
|
48
|
+
* options arrivent deja filtrees du serveur (cf. `RmDropdownSelect`).
|
|
49
|
+
*/
|
|
50
|
+
filterSearch?: boolean
|
|
51
|
+
/** Pastille d'aide a cote de l'intitule, qui emet `openInfo`. */
|
|
52
|
+
labelInfo?: boolean
|
|
53
|
+
lang?: 'fr' | 'en'
|
|
54
|
+
}>(), {
|
|
55
|
+
search: true,
|
|
56
|
+
disabled: false,
|
|
57
|
+
loading: false,
|
|
58
|
+
showAll: false,
|
|
59
|
+
showAmount: true,
|
|
60
|
+
preselectAll: false,
|
|
61
|
+
emptyLabel: undefined,
|
|
62
|
+
emptyContent: undefined,
|
|
63
|
+
searchPlaceholder: undefined,
|
|
64
|
+
filterSearch: true,
|
|
65
|
+
labelInfo: false,
|
|
66
|
+
lang: 'fr',
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const emit = defineEmits<{
|
|
70
|
+
/** Selection complete apres le changement. Double le `v-model`. */
|
|
71
|
+
change: [values: string[]]
|
|
72
|
+
/** Saisie de recherche, amortie a 500 ms. */
|
|
73
|
+
search: [value: string]
|
|
74
|
+
openInfo: []
|
|
75
|
+
close: []
|
|
76
|
+
}>()
|
|
77
|
+
|
|
78
|
+
const selected = defineModel<string[]>({ default: () => [] })
|
|
79
|
+
|
|
80
|
+
const WORDS = {
|
|
81
|
+
fr: { empty: 'Aucune sélection', search: 'Rechercher...', notFound: 'Aucun résultat', loading: 'Chargement', all: 'Tout' },
|
|
82
|
+
en: { empty: 'No selection', search: 'Search...', notFound: 'No result', loading: 'Loading', all: 'All' },
|
|
83
|
+
} as const
|
|
84
|
+
|
|
85
|
+
const t = computed(() => WORDS[props.lang])
|
|
86
|
+
|
|
87
|
+
const isOpen = ref(false)
|
|
88
|
+
const searchTerm = ref('')
|
|
89
|
+
const rootRef = ref<HTMLElement>()
|
|
90
|
+
const searchRef = ref<HTMLInputElement>()
|
|
91
|
+
|
|
92
|
+
// Tout cocher d'entree, une fois : refaire la selection a chaque arrivee
|
|
93
|
+
// d'options ecraserait ce que la page vient de decocher.
|
|
94
|
+
const preselectDone = ref(false)
|
|
95
|
+
watch(() => props.options, (options) => {
|
|
96
|
+
if (!props.preselectAll || preselectDone.value || options.length === 0) return
|
|
97
|
+
preselectDone.value = true
|
|
98
|
+
commit([...options])
|
|
99
|
+
}, { immediate: true })
|
|
100
|
+
|
|
101
|
+
const visibleOptions = computed(() => {
|
|
102
|
+
const needle = searchTerm.value.trim().toLowerCase()
|
|
103
|
+
if (!needle || !props.filterSearch) return props.options
|
|
104
|
+
const parts = needle.split(' ').filter(Boolean)
|
|
105
|
+
return props.options.filter(option => parts.every(part => option.toLowerCase().includes(part)))
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
const allSelected = computed(() =>
|
|
109
|
+
props.options.length > 0 && props.options.every(option => selected.value.includes(option)),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
/** Le champ enumere ce qui est retenu : le compte seul ne dit pas quoi. */
|
|
113
|
+
const summary = computed(() => selected.value.join(', '))
|
|
114
|
+
|
|
115
|
+
let debounceId: ReturnType<typeof setTimeout> | undefined
|
|
116
|
+
watch(searchTerm, (value) => {
|
|
117
|
+
clearTimeout(debounceId)
|
|
118
|
+
debounceId = setTimeout(() => emit('search', value), 500)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
function close() {
|
|
122
|
+
if (!isOpen.value) return
|
|
123
|
+
isOpen.value = false
|
|
124
|
+
document.removeEventListener('click', onDocumentClick)
|
|
125
|
+
emit('close')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function onDocumentClick(event: MouseEvent) {
|
|
129
|
+
if (!rootRef.value?.contains(event.target as Node)) close()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function toggle() {
|
|
133
|
+
if (props.disabled) return
|
|
134
|
+
if (isOpen.value) return close()
|
|
135
|
+
|
|
136
|
+
isOpen.value = true
|
|
137
|
+
searchTerm.value = ''
|
|
138
|
+
document.addEventListener('click', onDocumentClick)
|
|
139
|
+
if (!props.search) return
|
|
140
|
+
await nextTick()
|
|
141
|
+
searchRef.value?.focus()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function commit(values: string[]) {
|
|
145
|
+
selected.value = values
|
|
146
|
+
emit('change', values)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function toggleOption(option: string) {
|
|
150
|
+
commit(selected.value.includes(option)
|
|
151
|
+
? selected.value.filter(value => value !== option)
|
|
152
|
+
: [...selected.value, option])
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function toggleAll() {
|
|
156
|
+
commit(allSelected.value ? [] : [...props.options])
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
onUnmounted(() => {
|
|
160
|
+
clearTimeout(debounceId)
|
|
161
|
+
document.removeEventListener('click', onDocumentClick)
|
|
162
|
+
})
|
|
163
|
+
</script>
|
|
164
|
+
|
|
165
|
+
<template>
|
|
166
|
+
<div ref="rootRef" class="rm-msel" @keydown.escape="close">
|
|
167
|
+
<p class="rm-msel__label">
|
|
168
|
+
{{ label }}
|
|
169
|
+
<button v-if="labelInfo" type="button" class="rm-msel__info" @click="emit('openInfo')">
|
|
170
|
+
<RmIcon name="Info" :size="14" />
|
|
171
|
+
</button>
|
|
172
|
+
</p>
|
|
173
|
+
|
|
174
|
+
<button
|
|
175
|
+
type="button"
|
|
176
|
+
class="rm-msel__field"
|
|
177
|
+
:class="{ 'is-open': isOpen, 'is-disabled': disabled }"
|
|
178
|
+
:disabled="disabled"
|
|
179
|
+
:aria-expanded="isOpen"
|
|
180
|
+
aria-haspopup="true"
|
|
181
|
+
@click.stop="toggle"
|
|
182
|
+
>
|
|
183
|
+
<RmTooltip :text="summary" :disabled="selected.length === 0" class="rm-msel__value-slot">
|
|
184
|
+
<span class="rm-msel__value" :class="{ 'is-empty': selected.length === 0 }">
|
|
185
|
+
{{ selected.length > 0 ? summary : emptyLabel || t.empty }}
|
|
186
|
+
</span>
|
|
187
|
+
</RmTooltip>
|
|
188
|
+
<span v-if="showAmount && selected.length > 0" class="rm-msel__count">
|
|
189
|
+
{{ selected.length > 99 ? '99+' : selected.length }}
|
|
190
|
+
</span>
|
|
191
|
+
<RmIcon name="CaretDown" :size="16" />
|
|
192
|
+
</button>
|
|
193
|
+
|
|
194
|
+
<div v-show="isOpen" class="rm-msel__panel" @click.stop>
|
|
195
|
+
<div v-if="search" class="rm-msel__search">
|
|
196
|
+
<RmIcon name="MagnifyingGlass" :size="16" />
|
|
197
|
+
<input
|
|
198
|
+
ref="searchRef"
|
|
199
|
+
v-model="searchTerm"
|
|
200
|
+
type="search"
|
|
201
|
+
class="rm-msel__search-input"
|
|
202
|
+
:placeholder="searchPlaceholder || t.search"
|
|
203
|
+
:aria-label="searchPlaceholder || t.search"
|
|
204
|
+
>
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
<p v-if="loading" class="rm-msel__state" aria-busy="true">{{ t.loading }}</p>
|
|
208
|
+
<p v-else-if="visibleOptions.length === 0" class="rm-msel__state">
|
|
209
|
+
{{ emptyContent || t.notFound }}
|
|
210
|
+
</p>
|
|
211
|
+
|
|
212
|
+
<div v-else class="rm-msel__list">
|
|
213
|
+
<button
|
|
214
|
+
v-if="showAll"
|
|
215
|
+
type="button"
|
|
216
|
+
role="checkbox"
|
|
217
|
+
class="rm-msel__option"
|
|
218
|
+
:class="{ 'is-checked': allSelected }"
|
|
219
|
+
:aria-checked="allSelected"
|
|
220
|
+
@click="toggleAll"
|
|
221
|
+
>
|
|
222
|
+
<RmIcon
|
|
223
|
+
:name="allSelected ? 'CheckSquare' : 'Square'"
|
|
224
|
+
:size="16"
|
|
225
|
+
:weight="allSelected ? 'fill' : 'regular'"
|
|
226
|
+
/>
|
|
227
|
+
<span>{{ t.all }}</span>
|
|
228
|
+
</button>
|
|
229
|
+
|
|
230
|
+
<div :class="{ 'rm-msel__indent': showAll }">
|
|
231
|
+
<button
|
|
232
|
+
v-for="option in visibleOptions"
|
|
233
|
+
:key="option"
|
|
234
|
+
type="button"
|
|
235
|
+
role="checkbox"
|
|
236
|
+
class="rm-msel__option"
|
|
237
|
+
:class="{ 'is-checked': selected.includes(option) }"
|
|
238
|
+
:aria-checked="selected.includes(option)"
|
|
239
|
+
@click="toggleOption(option)"
|
|
240
|
+
>
|
|
241
|
+
<RmIcon
|
|
242
|
+
:name="selected.includes(option) ? 'CheckSquare' : 'Square'"
|
|
243
|
+
:size="16"
|
|
244
|
+
:weight="selected.includes(option) ? 'fill' : 'regular'"
|
|
245
|
+
/>
|
|
246
|
+
<span>{{ option }}</span>
|
|
247
|
+
</button>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
</template>
|
|
253
|
+
|
|
254
|
+
<style scoped>
|
|
255
|
+
.rm-msel{align-items:flex-start;display:flex;flex-direction:column;gap:4px;position:relative}.rm-msel__label{align-items:center;color:var(--text-secondary);display:flex;font-size:var(--font-12,12px);gap:4px;line-height:14px}.rm-msel__info{background:none;border:none;color:var(--text-muted);cursor:pointer;display:flex;padding:0}.rm-msel__field{align-items:center;background:var(--bg-surface);border:1.5px solid var(--border-default);border-radius:var(--radius-md,8px);color:var(--text-primary);cursor:pointer;display:flex;font-family:inherit;font-size:var(--font-14,14px);font-weight:700;gap:4px;line-height:16px;padding:12px;text-align:left;width:196px}.rm-msel__field:hover:not(.is-disabled){border-color:rgba(var(--brand-rgb),.3)}.rm-msel__field.is-open{border-color:var(--color-primary)}.rm-msel__field.is-disabled{background:var(--bg-surface-2);color:var(--text-muted);cursor:not-allowed}.rm-msel__field:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}.rm-msel__value-slot{display:block;flex:1;min-width:0}.rm-msel__value{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rm-msel__value.is-empty{color:var(--text-muted);font-style:italic;font-weight:400}.rm-msel__field :deep(svg){color:var(--text-secondary);flex-shrink:0}.rm-msel__count{align-items:center;background:rgba(var(--brand-rgb),.1);border-radius:50%;color:var(--color-primary);display:flex;flex-shrink:0;font-size:var(--font-12,12px);font-weight:600;height:20px;justify-content:center;width:20px}.rm-msel__panel{border:1.5px solid var(--border-default);border-radius:var(--radius-lg,12px);box-shadow:var(--r-shadow-lg,0 8px 24px rgba(0,0,0,.12));box-sizing:border-box;flex-direction:column;left:0;margin-top:8px;max-height:500px;max-width:320px;min-width:230px;padding:16px;position:absolute;top:100%;z-index:12}.rm-msel__panel,.rm-msel__search{background:var(--bg-surface);display:flex}.rm-msel__search{align-items:center;border:1px solid var(--border-default);border-radius:var(--radius-md,8px);gap:8px;padding:8px 12px}.rm-msel__search :deep(svg){color:var(--text-muted);flex-shrink:0}.rm-msel__search-input{background:none;border:none;color:var(--text-primary);font-family:inherit;font-size:var(--font-14,14px);width:100%}.rm-msel__search-input::-moz-placeholder{color:var(--text-muted);font-size:var(--font-12,12px)}.rm-msel__search-input::placeholder{color:var(--text-muted);font-size:var(--font-12,12px)}.rm-msel__search-input:focus-visible{outline:none}.rm-msel__state{color:var(--text-secondary);font-size:var(--font-14,14px);font-style:italic;padding-top:16px;text-align:center}.rm-msel__list{display:flex;flex-direction:column;min-height:10px;overflow-y:auto}.rm-msel__list:not(:only-child){margin-top:16px}.rm-msel__indent{margin-left:16px}.rm-msel__option{align-items:center;background:none;border:none;border-radius:var(--radius-sm,4px);color:var(--text-primary);cursor:pointer;display:flex;font-family:inherit;font-size:var(--font-14,14px);gap:8px;line-height:160%;margin:4px 0;min-height:24px;padding:2px 4px;text-align:left;width:100%}.rm-msel__option :deep(svg){color:var(--text-muted);flex-shrink:0}.rm-msel__option:hover{background:rgba(var(--brand-rgb),.05)}.rm-msel__option:focus-visible{outline:2px solid var(--color-primary);outline-offset:-2px}.rm-msel__option.is-checked{font-weight:700}.rm-msel__option.is-checked :deep(svg),.rm-msel__option:hover :deep(svg){color:var(--color-primary)}
|
|
256
|
+
</style>
|
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
enregistrees, basculer sur une vue favorite, ouvrir le reste (toutes les
|
|
8
8
|
vues, enregistrer, et ce que la page ajoute).
|
|
9
9
|
|
|
10
|
-
Un point
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
Un point pulse a droite du nom des qu'il y a un etat a dire : vert quand une
|
|
11
|
+
vue est posee, orange quand l'ecran ne correspond plus a cette vue. Le second
|
|
12
|
+
couvre le premier, parce qu'une modification non enregistree est ce qui se
|
|
13
|
+
perd si on ne le voit pas. Meme forme que le point du rail : un seul signe a
|
|
14
|
+
apprendre pour les deux endroits ou l'etat d'une vue se lit.
|
|
13
15
|
|
|
14
16
|
La pastille ne connait ni l'API ni le magasin de vues : elle recoit une
|
|
15
17
|
liste et emet ce que l'utilisateur demande.
|
|
@@ -74,6 +76,7 @@ const WORDS = {
|
|
|
74
76
|
favorites: 'Favoris',
|
|
75
77
|
actions: 'Actions',
|
|
76
78
|
unsaved: 'Modifications non enregistrées',
|
|
79
|
+
applied: 'Vue appliquée',
|
|
77
80
|
save: 'Sauvegarder',
|
|
78
81
|
aria: 'Vue affichée',
|
|
79
82
|
},
|
|
@@ -83,6 +86,7 @@ const WORDS = {
|
|
|
83
86
|
favorites: 'Favorites',
|
|
84
87
|
actions: 'Actions',
|
|
85
88
|
unsaved: 'Unsaved changes',
|
|
89
|
+
applied: 'View applied',
|
|
86
90
|
save: 'Save',
|
|
87
91
|
aria: 'Current view',
|
|
88
92
|
},
|
|
@@ -142,7 +146,18 @@ onBeforeUnmount(() => {
|
|
|
142
146
|
<RmIcon name="BookmarkSimple" :size="16" class="rm-vpill__icon" />
|
|
143
147
|
<span class="rm-vpill__key">{{ t.key }}</span>
|
|
144
148
|
<span class="rm-vpill__value">{{ label }}</span>
|
|
145
|
-
<span
|
|
149
|
+
<span
|
|
150
|
+
v-if="dirty"
|
|
151
|
+
class="rm-vpill__dot rm-vpill__dot--unsaved"
|
|
152
|
+
:title="t.unsaved"
|
|
153
|
+
aria-hidden="true"
|
|
154
|
+
/>
|
|
155
|
+
<span
|
|
156
|
+
v-else-if="viewName"
|
|
157
|
+
class="rm-vpill__dot rm-vpill__dot--applied"
|
|
158
|
+
:title="t.applied"
|
|
159
|
+
aria-hidden="true"
|
|
160
|
+
/>
|
|
146
161
|
<RmIcon class="rm-vpill__caret" name="CaretDown" :size="14" />
|
|
147
162
|
</button>
|
|
148
163
|
|
|
@@ -194,5 +209,5 @@ onBeforeUnmount(() => {
|
|
|
194
209
|
</template>
|
|
195
210
|
|
|
196
211
|
<style scoped>
|
|
197
|
-
.rm-vpill{position:relative}.rm-vpill,.rm-vpill__pill{align-items:center;display:flex}.rm-vpill__pill{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-14);font-weight:var(--weight-medium);gap:6px;height:var(--rm-fbar-field-h,32px);padding:0 14px;white-space:nowrap}.rm-vpill__pill:hover{background:var(--bg-surface-3)}.rm-vpill__pill.is-open{border-color:var(--color-primary);color:var(--color-primary)}.rm-vpill__icon{color:currentColor;flex-shrink:0}.rm-vpill__key{color:var(--text-muted);font-weight:var(--weight-normal)}.rm-vpill__value{max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rm-vpill__pill.has-unsaved{border-color:var(--color-warning)}.rm-vpill__dot{animation:rm-vpill-pulse 2s ease-out infinite;background:var(--color-
|
|
212
|
+
.rm-vpill{position:relative}.rm-vpill,.rm-vpill__pill{align-items:center;display:flex}.rm-vpill__pill{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-14);font-weight:var(--weight-medium);gap:6px;height:var(--rm-fbar-field-h,32px);padding:0 14px;white-space:nowrap}.rm-vpill__pill:hover{background:var(--bg-surface-3)}.rm-vpill__pill.is-open{border-color:var(--color-primary);color:var(--color-primary)}.rm-vpill__icon{color:currentColor;flex-shrink:0}.rm-vpill__key{color:var(--text-muted);font-weight:var(--weight-normal)}.rm-vpill__value{max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rm-vpill__pill.has-unsaved{border-color:var(--color-warning)}.rm-vpill__dot{border-radius:50%;flex-shrink:0;height:8px;width:8px}.rm-vpill__dot--applied{animation:rm-vpill-pulse-applied 2s ease-out infinite;background:var(--color-success)}.rm-vpill__dot--unsaved{animation:rm-vpill-pulse 2s ease-out infinite;background:var(--color-warning)}@keyframes rm-vpill-pulse{0%{box-shadow:0 0 0 0 rgba(217,119,6,.6)}70%{box-shadow:0 0 0 6px rgba(217,119,6,0)}to{box-shadow:0 0 0 0 rgba(217,119,6,0)}}@keyframes rm-vpill-pulse-applied{0%{box-shadow:0 0 0 0 rgba(22,163,74,.55)}70%{box-shadow:0 0 0 6px rgba(22,163,74,0)}to{box-shadow:0 0 0 0 rgba(22,163,74,0)}}@media (prefers-reduced-motion:reduce){.rm-vpill__dot,.rm-vpill__unsaved-dot:before{animation:none}}.rm-vpill__caret{color:var(--text-muted);flex-shrink:0}.rm-vpill__menu{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:8px;box-shadow:var(--shadow-lg);box-sizing:border-box;font-family:var(--font-sans);left:0;max-width:460px;min-width:320px;padding:6px;position:absolute;top:calc(100% + 6px);z-index:60}.rm-vpill__unsaved{align-items:center;background:var(--color-warning-bg);border-radius:var(--radius-md);color:var(--color-warning);display:flex;font-size:var(--font-12);gap:8px;padding:8px 12px}.rm-vpill__unsaved-dot{align-items:center;display:inline-flex;flex-shrink:0;height:14px;justify-content:center;width:14px}.rm-vpill__unsaved-dot:before{animation:rm-vpill-pulse 2s ease-out infinite;background:var(--color-warning);border-radius:50%;content:"";height:7px;width:7px}.rm-vpill__unsaved-label{flex:1;font-weight:var(--weight-semibold)}.rm-vpill__unsaved-cta{background:transparent;border:none;color:var(--color-warning);cursor:pointer;font-family:inherit;font-size:inherit;font-weight:var(--weight-bold);padding:0;text-decoration:underline;text-underline-offset:2px}.rm-vpill__unsaved-cta:hover{color:var(--text-primary)}.rm-vpill__divider{background:var(--border-soft);height:1px;margin:6px -6px}.rm-vpill__section{color:var(--text-muted);font-size:var(--font-10);font-weight:var(--weight-bold);letter-spacing:.08em;margin:0;padding:8px 12px 4px 34px;text-align:left;text-transform:uppercase}.rm-vpill__item{align-items:center;background:none;border:none;border-radius:var(--radius-md);color:var(--text-secondary);cursor:pointer;display:flex;font-family:inherit;font-size:var(--font-13);gap:8px;padding:8px 12px;text-align:left;transition:background .1s,color .1s;width:100%}.rm-vpill__item:hover{background:var(--bg-hover);color:var(--text-primary)}.rm-vpill__item.is-active{background:var(--bg-hover);color:var(--color-secondary);font-weight:var(--weight-semibold)}.rm-vpill__name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rm-vpill__item--view .rm-vpill__check{color:var(--color-secondary);flex-shrink:0;opacity:0;transition:opacity .1s}.rm-vpill__item--view.is-active .rm-vpill__check{opacity:1}
|
|
198
213
|
</style>
|