@tekkare/romulus 0.1.159 → 0.1.161
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
|
@@ -162,22 +162,97 @@ const t = computed(() => WORDS[props.lang])
|
|
|
162
162
|
const isOpen = ref(false)
|
|
163
163
|
const removeSearch = ref('')
|
|
164
164
|
const activePreset = ref<string>()
|
|
165
|
-
const alignRight = ref(false)
|
|
166
165
|
const rootRef = ref<HTMLElement>()
|
|
166
|
+
const panelRef = ref<HTMLElement>()
|
|
167
167
|
const addRef = ref<HTMLInputElement>()
|
|
168
168
|
|
|
169
169
|
/** Largeur du panneau, tenue en phase avec la regle `.rm-csel__panel`. */
|
|
170
170
|
const PANEL_WIDTH = 820
|
|
171
171
|
|
|
172
|
+
/** Marge gardee contre les bords de la fenetre. */
|
|
173
|
+
const PANEL_GAP = 8
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Largeur en dessous de laquelle un conteneur n'est pas pris pour cadre : une
|
|
177
|
+
* carte etroite qui cache son debordement reduirait le panneau a un couloir,
|
|
178
|
+
* alors qu'elle ne le decoupe plus (il est pose sur la fenetre).
|
|
179
|
+
*/
|
|
180
|
+
const MIN_BOUNDS = 360
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Le cadre dans lequel le panneau doit tenir.
|
|
184
|
+
*
|
|
185
|
+
* La fenetre ne suffit pas. Un filtre pose dans un tiroir colle au bord droit
|
|
186
|
+
* de l'ecran tient contre ce bord : cale a gauche le panneau sort du tiroir
|
|
187
|
+
* par la droite, cale a droite il en sort par la gauche et couvre la page.
|
|
188
|
+
* Dans les deux cas il flotte hors de la surface qui le porte, et le lecteur
|
|
189
|
+
* lit un panneau qui n'appartient visiblement plus a rien.
|
|
190
|
+
*
|
|
191
|
+
* On remonte donc jusqu'au premier ancetre qui borne son contenu (un tiroir,
|
|
192
|
+
* une modale, un panneau du cadre : tout ce qui n'a pas `overflow: visible`),
|
|
193
|
+
* et le panneau se range dedans. Sans ancetre de ce genre — le cas courant
|
|
194
|
+
* d'un filtre pose en pleine page — le cadre reste la fenetre, et le placement
|
|
195
|
+
* ne change pas.
|
|
196
|
+
*/
|
|
197
|
+
function panelBounds(): { left: number, right: number } {
|
|
198
|
+
const viewport = { left: PANEL_GAP, right: window.innerWidth - PANEL_GAP }
|
|
199
|
+
let node = rootRef.value?.parentElement
|
|
200
|
+
|
|
201
|
+
while (node && node !== document.body) {
|
|
202
|
+
const style = getComputedStyle(node)
|
|
203
|
+
const clips = `${style.overflowX}${style.overflowY}`.replace(/visible/g, '') !== ''
|
|
204
|
+
if (clips) {
|
|
205
|
+
const box = node.getBoundingClientRect()
|
|
206
|
+
if (box.width >= MIN_BOUNDS) {
|
|
207
|
+
return {
|
|
208
|
+
left: Math.max(viewport.left, box.left + PANEL_GAP),
|
|
209
|
+
right: Math.min(viewport.right, box.right - PANEL_GAP),
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
node = node.parentElement
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return viewport
|
|
217
|
+
}
|
|
218
|
+
|
|
172
219
|
/**
|
|
173
|
-
* Le panneau
|
|
174
|
-
*
|
|
175
|
-
*
|
|
220
|
+
* Le panneau est pose sur la fenetre, pas dans le champ : depose dans le flux,
|
|
221
|
+
* il etait coupe des que le filtre vivait dans un conteneur qui defile (un
|
|
222
|
+
* tiroir, une carte a debordement cache), et aucun reglage de `overflow` chez
|
|
223
|
+
* l'appelant ne le rattrapait.
|
|
224
|
+
*
|
|
225
|
+
* Il sort donc par un `Teleport` et se positionne a la main, sous le champ,
|
|
226
|
+
* cale a gauche du champ et ramene dans son cadre quand il en depasserait.
|
|
176
227
|
*/
|
|
228
|
+
const panelStyle = ref<Record<string, string>>({})
|
|
229
|
+
|
|
177
230
|
function placePanel() {
|
|
178
231
|
const rect = rootRef.value?.getBoundingClientRect()
|
|
179
232
|
if (!rect) return
|
|
180
|
-
|
|
233
|
+
|
|
234
|
+
const bounds = panelBounds()
|
|
235
|
+
const width = Math.min(PANEL_WIDTH, bounds.right - bounds.left)
|
|
236
|
+
// Cale a gauche du champ, puis ramene dans le cadre : le bord droit d'abord,
|
|
237
|
+
// le gauche ensuite, pour qu'un cadre plus etroit que le panneau laisse voir
|
|
238
|
+
// son debut plutot que sa fin.
|
|
239
|
+
const left = Math.max(bounds.left, Math.min(rect.left, bounds.right - width))
|
|
240
|
+
|
|
241
|
+
// Sous le champ, sauf quand il ne reste rien dessous : le panneau se pose
|
|
242
|
+
// alors au-dessus, et sa hauteur se borne a la place reellement libre.
|
|
243
|
+
const below = window.innerHeight - rect.bottom - PANEL_GAP * 2
|
|
244
|
+
const above = rect.top - PANEL_GAP * 2
|
|
245
|
+
const flip = below < 320 && above > below
|
|
246
|
+
|
|
247
|
+
panelStyle.value = {
|
|
248
|
+
position: 'fixed',
|
|
249
|
+
left: `${left}px`,
|
|
250
|
+
width: `${width}px`,
|
|
251
|
+
maxHeight: `${Math.max(240, flip ? above : below)}px`,
|
|
252
|
+
...(flip
|
|
253
|
+
? { bottom: `${window.innerHeight - rect.top + PANEL_GAP}px` }
|
|
254
|
+
: { top: `${rect.bottom + PANEL_GAP}px` }),
|
|
255
|
+
}
|
|
181
256
|
}
|
|
182
257
|
|
|
183
258
|
/** Les chaines nues valent libelle et valeur a la fois. */
|
|
@@ -227,13 +302,31 @@ watch(addInput, (value) => {
|
|
|
227
302
|
}, 500)
|
|
228
303
|
})
|
|
229
304
|
|
|
305
|
+
/**
|
|
306
|
+
* Le panneau suit le champ tant qu'il est ouvert : pose sur la fenetre, il
|
|
307
|
+
* resterait sinon la ou le champ etait avant que la page (ou le tiroir) ne
|
|
308
|
+
* defile.
|
|
309
|
+
*/
|
|
310
|
+
function trackPanel() {
|
|
311
|
+
window.addEventListener('scroll', placePanel, true)
|
|
312
|
+
window.addEventListener('resize', placePanel)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function untrackPanel() {
|
|
316
|
+
window.removeEventListener('scroll', placePanel, true)
|
|
317
|
+
window.removeEventListener('resize', placePanel)
|
|
318
|
+
}
|
|
319
|
+
|
|
230
320
|
function close() {
|
|
231
321
|
isOpen.value = false
|
|
232
322
|
document.removeEventListener('click', onDocumentClick)
|
|
323
|
+
untrackPanel()
|
|
233
324
|
}
|
|
234
325
|
|
|
235
326
|
function onDocumentClick(event: MouseEvent) {
|
|
236
|
-
|
|
327
|
+
const target = event.target as Node
|
|
328
|
+
if (rootRef.value?.contains(target) || panelRef.value?.contains(target)) return
|
|
329
|
+
close()
|
|
237
330
|
}
|
|
238
331
|
|
|
239
332
|
async function toggle() {
|
|
@@ -242,6 +335,7 @@ async function toggle() {
|
|
|
242
335
|
|
|
243
336
|
isOpen.value = true
|
|
244
337
|
placePanel()
|
|
338
|
+
trackPanel()
|
|
245
339
|
rmPanelOpened(close)
|
|
246
340
|
document.addEventListener('click', onDocumentClick)
|
|
247
341
|
await nextTick()
|
|
@@ -292,6 +386,7 @@ onUnmounted(() => {
|
|
|
292
386
|
unregister?.()
|
|
293
387
|
clearTimeout(debounceId)
|
|
294
388
|
document.removeEventListener('click', onDocumentClick)
|
|
389
|
+
untrackPanel()
|
|
295
390
|
})
|
|
296
391
|
</script>
|
|
297
392
|
|
|
@@ -324,11 +419,14 @@ onUnmounted(() => {
|
|
|
324
419
|
<PhCaretDown :size="16" />
|
|
325
420
|
</button>
|
|
326
421
|
|
|
422
|
+
<Teleport to="body">
|
|
327
423
|
<div
|
|
328
424
|
v-show="isOpen"
|
|
425
|
+
ref="panelRef"
|
|
329
426
|
class="rm-csel__panel"
|
|
330
|
-
:
|
|
427
|
+
:style="panelStyle"
|
|
331
428
|
@click.stop
|
|
429
|
+
@keydown.escape="close"
|
|
332
430
|
>
|
|
333
431
|
<div class="rm-csel__column">
|
|
334
432
|
<div v-if="presets && presets.length > 0" class="rm-csel__presets">
|
|
@@ -437,9 +535,10 @@ onUnmounted(() => {
|
|
|
437
535
|
</div>
|
|
438
536
|
</div>
|
|
439
537
|
</div>
|
|
538
|
+
</Teleport>
|
|
440
539
|
</div>
|
|
441
540
|
</template>
|
|
442
541
|
|
|
443
542
|
<style scoped>
|
|
444
|
-
.rm-csel{align-items:stretch;flex-direction:column;position:relative;width:196px}.rm-csel,.rm-csel__label{display:flex;gap:var(--space-4)}.rm-csel__label{align-items:center;color:var(--text-secondary);font-size:var(--font-12,12px);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);box-sizing:border-box;color:var(--text-primary);cursor:pointer;display:flex;font-family:inherit;font-size:var(--font-14,14px);font-weight:700;gap:var(--space-4);line-height:16px;padding:var(--space-12);text-align:left;width:100%}.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;
|
|
543
|
+
.rm-csel{align-items:stretch;flex-direction:column;position:relative;width:196px}.rm-csel,.rm-csel__label{display:flex;gap:var(--space-4)}.rm-csel__label{align-items:center;color:var(--text-secondary);font-size:var(--font-12,12px);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);box-sizing:border-box;color:var(--text-primary);cursor:pointer;display:flex;font-family:inherit;font-size:var(--font-14,14px);font-weight:700;gap:var(--space-4);line-height:16px;padding:var(--space-12);text-align:left;width:100%}.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;max-height:calc(100vh - 32px);max-width:calc(100vw - 16px);overflow:hidden;position:fixed;z-index:var(--z-popover,1000)}.rm-csel__column{display:flex;flex:1;flex-direction:column;gap:var(--space-8);min-width:0;padding:var(--space-16)}.rm-csel__column--right{border-left:1px solid var(--border-soft);flex:0 0 300px}.rm-csel__presets{border-bottom:1px solid var(--border-soft);display:flex;flex-direction:column;gap:var(--space-8);padding-bottom:var(--space-8)}.rm-csel__pills{display:flex;flex-wrap:wrap;gap:var(--space-4)}.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:var(--space-4) var(--space-10)}.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:var(--space-8);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:var(--space-8);padding:var(--space-6) var(--space-10)}.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-13,13px);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-13,13px);font-style:italic;padding-top:var(--space-8)}.rm-csel__loading{display:flex;flex-direction:column;gap:var(--space-10);padding-top:var(--space-8)}.rm-csel__sr{height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;clip:rect(0,0,0,0);border:0;white-space:nowrap}.rm-csel__list{display:flex;flex-direction:column;gap:var(--space-4);height:260px;overflow-x:hidden;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-13,13px);gap:var(--space-8);line-height:150%;min-width:0;padding:var(--space-2) var(--space-4);text-align:left}.rm-csel__option span{min-width:0;overflow-wrap:anywhere}.rm-csel__option :deep(svg){background:var(--bg-surface);border-radius:50%;flex-shrink:0;margin-top:3px}.rm-csel__hint{color:var(--text-muted);flex-shrink:0;font-size:var(--font-11,11px);white-space:nowrap}.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}.rm-csel__column--right{border-left:none;border-top:1px solid var(--border-soft)}.rm-csel__list{height:180px}}
|
|
445
544
|
</style>
|