@tekkare/romulus 0.1.158 → 0.1.160

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.157",
7
+ "version": "0.1.159",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
@@ -43,6 +43,13 @@ export interface RmComplexSelectPreset {
43
43
  export interface RmComplexSelectOption {
44
44
  label: string
45
45
  value: string
46
+ /**
47
+ * Mention posee a cote du libelle dans la colonne « ajouter ». Sert a dire
48
+ * d'ou sort une option que la liste ne portait pas : une valeur trouvee
49
+ * au-dela du perimetre courant se retient comme les autres, mais ne se lit
50
+ * pas comme les autres.
51
+ */
52
+ hint?: string
46
53
  }
47
54
 
48
55
  const props = withDefaults(defineProps<{
@@ -155,22 +162,52 @@ const t = computed(() => WORDS[props.lang])
155
162
  const isOpen = ref(false)
156
163
  const removeSearch = ref('')
157
164
  const activePreset = ref<string>()
158
- const alignRight = ref(false)
159
165
  const rootRef = ref<HTMLElement>()
166
+ const panelRef = ref<HTMLElement>()
160
167
  const addRef = ref<HTMLInputElement>()
161
168
 
162
169
  /** Largeur du panneau, tenue en phase avec la regle `.rm-csel__panel`. */
163
170
  const PANEL_WIDTH = 820
164
171
 
172
+ /** Marge gardee contre les bords de la fenetre. */
173
+ const PANEL_GAP = 8
174
+
165
175
  /**
166
- * Le panneau sort a gauche du champ par defaut. Pose sur un filtre de bout de
167
- * rangee, il depasserait de l'ecran : il se cale alors sur le bord droit du
168
- * champ.
176
+ * Le panneau est pose sur la fenetre, pas dans le champ : depose dans le flux,
177
+ * il etait coupe des que le filtre vivait dans un conteneur qui defile (un
178
+ * tiroir, une carte a debordement cache), et aucun reglage de `overflow` chez
179
+ * l'appelant ne le rattrapait.
180
+ *
181
+ * Il sort donc par un `Teleport` et se positionne a la main, sous le champ,
182
+ * cale a gauche par defaut et sur le bord droit du champ quand il depasserait
183
+ * de l'ecran.
169
184
  */
185
+ const panelStyle = ref<Record<string, string>>({})
186
+
170
187
  function placePanel() {
171
188
  const rect = rootRef.value?.getBoundingClientRect()
172
189
  if (!rect) return
173
- alignRight.value = rect.left + PANEL_WIDTH > window.innerWidth - 8
190
+
191
+ const width = Math.min(PANEL_WIDTH, window.innerWidth - PANEL_GAP * 2)
192
+ const left = rect.left + width > window.innerWidth - PANEL_GAP
193
+ ? Math.max(PANEL_GAP, rect.right - width)
194
+ : rect.left
195
+
196
+ // Sous le champ, sauf quand il ne reste rien dessous : le panneau se pose
197
+ // alors au-dessus, et sa hauteur se borne a la place reellement libre.
198
+ const below = window.innerHeight - rect.bottom - PANEL_GAP * 2
199
+ const above = rect.top - PANEL_GAP * 2
200
+ const flip = below < 320 && above > below
201
+
202
+ panelStyle.value = {
203
+ position: 'fixed',
204
+ left: `${left}px`,
205
+ width: `${width}px`,
206
+ maxHeight: `${Math.max(240, flip ? above : below)}px`,
207
+ ...(flip
208
+ ? { bottom: `${window.innerHeight - rect.top + PANEL_GAP}px` }
209
+ : { top: `${rect.bottom + PANEL_GAP}px` }),
210
+ }
174
211
  }
175
212
 
176
213
  /** Les chaines nues valent libelle et valeur a la fois. */
@@ -220,13 +257,31 @@ watch(addInput, (value) => {
220
257
  }, 500)
221
258
  })
222
259
 
260
+ /**
261
+ * Le panneau suit le champ tant qu'il est ouvert : pose sur la fenetre, il
262
+ * resterait sinon la ou le champ etait avant que la page (ou le tiroir) ne
263
+ * defile.
264
+ */
265
+ function trackPanel() {
266
+ window.addEventListener('scroll', placePanel, true)
267
+ window.addEventListener('resize', placePanel)
268
+ }
269
+
270
+ function untrackPanel() {
271
+ window.removeEventListener('scroll', placePanel, true)
272
+ window.removeEventListener('resize', placePanel)
273
+ }
274
+
223
275
  function close() {
224
276
  isOpen.value = false
225
277
  document.removeEventListener('click', onDocumentClick)
278
+ untrackPanel()
226
279
  }
227
280
 
228
281
  function onDocumentClick(event: MouseEvent) {
229
- if (!rootRef.value?.contains(event.target as Node)) close()
282
+ const target = event.target as Node
283
+ if (rootRef.value?.contains(target) || panelRef.value?.contains(target)) return
284
+ close()
230
285
  }
231
286
 
232
287
  async function toggle() {
@@ -235,6 +290,7 @@ async function toggle() {
235
290
 
236
291
  isOpen.value = true
237
292
  placePanel()
293
+ trackPanel()
238
294
  rmPanelOpened(close)
239
295
  document.addEventListener('click', onDocumentClick)
240
296
  await nextTick()
@@ -285,6 +341,7 @@ onUnmounted(() => {
285
341
  unregister?.()
286
342
  clearTimeout(debounceId)
287
343
  document.removeEventListener('click', onDocumentClick)
344
+ untrackPanel()
288
345
  })
289
346
  </script>
290
347
 
@@ -317,11 +374,14 @@ onUnmounted(() => {
317
374
  <PhCaretDown :size="16" />
318
375
  </button>
319
376
 
377
+ <Teleport to="body">
320
378
  <div
321
379
  v-show="isOpen"
380
+ ref="panelRef"
322
381
  class="rm-csel__panel"
323
- :class="{ 'rm-csel__panel--right': alignRight }"
382
+ :style="panelStyle"
324
383
  @click.stop
384
+ @keydown.escape="close"
325
385
  >
326
386
  <div class="rm-csel__column">
327
387
  <div v-if="presets && presets.length > 0" class="rm-csel__presets">
@@ -383,6 +443,7 @@ onUnmounted(() => {
383
443
  >
384
444
  <PhPlusCircle :size="16" weight="fill" />
385
445
  <span>{{ option.label }}</span>
446
+ <span v-if="option.hint" class="rm-csel__hint">{{ option.hint }}</span>
386
447
  </button>
387
448
  </div>
388
449
  </div>
@@ -429,9 +490,10 @@ onUnmounted(() => {
429
490
  </div>
430
491
  </div>
431
492
  </div>
493
+ </Teleport>
432
494
  </div>
433
495
  </template>
434
496
 
435
497
  <style scoped>
436
- .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;left:0;margin-top:8px;max-width:calc(100vw - 32px);position:absolute;right:auto;top:100%;width:820px;z-index:12}.rm-csel__panel--right{left:auto;right:0}.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__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}}
498
+ .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}}
437
499
  </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekkare/romulus",
3
- "version": "0.1.158",
3
+ "version": "0.1.160",
4
4
  "description": "Romulus Design System - Nuxt module by Tekkare",
5
5
  "license": "MIT",
6
6
  "type": "module",