@tekkare/romulus 0.1.54 → 0.1.56

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.53",
7
+ "version": "0.1.55",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
@@ -110,7 +110,7 @@ const WORDS = {
110
110
  clear: 'Tout effacer',
111
111
  searchSelection: 'Filtrer la sélection...',
112
112
  noResult: 'Aucun résultat',
113
- noSelection: 'Aucune sélection',
113
+ noSelection: 'Aucune sélection, ajoutez des éléments depuis la gauche',
114
114
  more: 'valeurs de plus, faites défiler',
115
115
  },
116
116
  en: {
@@ -123,7 +123,7 @@ const WORDS = {
123
123
  clear: 'Clear all',
124
124
  searchSelection: 'Filter selection...',
125
125
  noResult: 'No results',
126
- noSelection: 'No selection',
126
+ noSelection: 'No selection, add items from the left',
127
127
  more: 'more values, keep scrolling',
128
128
  },
129
129
  } as const
@@ -212,8 +212,21 @@ const summaryNames = computed(() => {
212
212
  return names
213
213
  })
214
214
 
215
- const summary = computed(() => summaryNames.value.join(', '))
216
- const overflow = computed(() => Math.max(0, selected.value.length - summaryNames.value.length))
215
+ /**
216
+ * La selection rapide qui decrit exactement l'ecran, s'il y en a une. Elle
217
+ * nomme alors la pastille : « Pays : Big 5 Europe » se lit d'un coup, la liste
218
+ * des cinq pays non.
219
+ */
220
+ const activePreset = computed(() => props.presets.find(isPresetActive))
221
+
222
+ const summary = computed(() =>
223
+ activePreset.value ? activePreset.value.label : summaryNames.value.join(', '),
224
+ )
225
+ const overflow = computed(() =>
226
+ activePreset.value
227
+ ? 0
228
+ : Math.max(0, selected.value.length - summaryNames.value.length),
229
+ )
217
230
 
218
231
  /** Une selection rapide est active quand elle decrit exactement l'ecran. */
219
232
  function isPresetActive(preset: FilterComplexPreset) {
@@ -257,19 +270,35 @@ function toggle() {
257
270
  }
258
271
  }
259
272
 
273
+ /**
274
+ * Fermer laisse la selection en place : le panneau n'a pas de bouton
275
+ * « Appliquer », chaque clic a deja porte.
276
+ */
277
+ function close() {
278
+ isOpen.value = false
279
+ addInput.value = ''
280
+ addSearch.value = ''
281
+ delSearch.value = ''
282
+ }
283
+
260
284
  function onClickOutside(e: Event) {
261
- if (wrapperRef.value && !wrapperRef.value.contains(e.target as Node)) {
262
- isOpen.value = false
263
- addInput.value = ''
264
- addSearch.value = ''
265
- delSearch.value = ''
266
- }
285
+ if (wrapperRef.value && !wrapperRef.value.contains(e.target as Node)) close()
286
+ }
287
+
288
+ // Echap ferme, comme partout ailleurs. Sans ca, un panneau ouvert au clavier
289
+ // ne se referme qu'a la souris.
290
+ function onKeydown(e: KeyboardEvent) {
291
+ if (e.key === 'Escape' && isOpen.value) close()
267
292
  }
268
293
 
269
- onMounted(() => document.addEventListener('click', onClickOutside))
294
+ onMounted(() => {
295
+ document.addEventListener('click', onClickOutside)
296
+ document.addEventListener('keydown', onKeydown)
297
+ })
270
298
  onUnmounted(() => {
271
299
  clearTimeout(debounce)
272
300
  document.removeEventListener('click', onClickOutside)
301
+ document.removeEventListener('keydown', onKeydown)
273
302
  })
274
303
  </script>
275
304
 
@@ -421,5 +450,5 @@ onUnmounted(() => {
421
450
  </template>
422
451
 
423
452
  <style scoped>
424
- .filter-complex{position:relative}.filter-complex__trigger{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-pill);box-sizing:border-box;color:var(--text-primary);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:var(--font-14);font-weight:var(--weight-medium);gap:6px;height:var(--rm-fbar-field-h,32px);max-width:320px;padding:0 12px;transition:all var(--duration-fast) var(--ease-in-out);white-space:nowrap}.filter-complex__trigger:hover{background:var(--bg-surface-3)}.filter-complex__icon{color:var(--color-primary);flex-shrink:0}.filter-complex__trigger--active{border-color:var(--color-primary)}.filter-complex__label{color:var(--text-muted);flex-shrink:0;font-weight:var(--weight-normal)}.filter-complex__summary{font-weight:var(--weight-semibold);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.filter-complex__badge{align-items:center;background-color:color-mix(in srgb,var(--color-primary) 10%,transparent);border-radius:var(--radius-pill);color:var(--color-primary);display:inline-flex;flex-shrink:0;font-size:var(--font-12);font-weight:var(--weight-semibold);height:18px;justify-content:center;line-height:1;min-width:18px;padding:0 6px}.filter-complex__caret{color:var(--text-muted);flex-shrink:0;transition:transform var(--duration-fast) var(--ease-in-out)}.filter-complex__caret--open{transform:rotate(180deg)}.filter-complex__panel{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-xl,12px);box-shadow:0 4px 24px rgba(var(--shadow-rgb),.1),0 2px 8px rgba(var(--shadow-rgb),.05);display:flex;left:0;max-height:460px;max-width:calc(100vw - 32px);overflow:hidden;position:absolute;top:calc(100% + 6px);width:720px;z-index:50}.filter-complex__left,.filter-complex__right{display:flex;flex-direction:column;gap:10px;overflow:hidden;padding:20px}.filter-complex__left{flex:1 1 50%}.filter-complex__right{border-left:1px solid var(--border-default);flex:1 1 50%}.filter-complex__head{align-items:center;display:flex;gap:12px;justify-content:space-between}.filter-complex__title{color:var(--text-primary);font-size:var(--font-14);font-weight:var(--weight-bold);margin:0}.filter-complex__presets{display:flex;flex-wrap:wrap;gap:8px}.filter-complex__preset{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-pill);color:var(--text-primary);cursor:pointer;font-family:var(--font-sans);font-size:var(--font-12);font-weight:var(--weight-medium);height:26px;padding:0 12px;transition:all var(--duration-fast) var(--ease-in-out);white-space:nowrap}.filter-complex__preset:hover{border-color:var(--color-primary);color:var(--color-primary)}.filter-complex__preset.is-active{background:var(--color-primary);border-color:var(--color-primary);color:var(--color-text-inverse)}.filter-complex__search{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-md,8px);display:flex;gap:8px;padding:7px 10px}.filter-complex__search:focus-within{border-color:var(--color-primary)}.filter-complex__search-input{background:transparent;border:none;color:var(--text-primary);flex:1;font-family:var(--font-sans);font-size:var(--font-14);outline:none}.filter-complex__search-input::-moz-placeholder{color:var(--text-muted)}.filter-complex__search-input::placeholder{color:var(--text-muted)}.filter-complex__search-clear{color:var(--text-muted);cursor:pointer;flex-shrink:0}.filter-complex__search-clear:hover{color:var(--text-secondary)}.filter-complex__action{background:none;border:none;cursor:pointer;font-family:var(--font-sans);font-size:var(--font-12);font-weight:var(--weight-medium);padding:0;text-decoration:underline;white-space:nowrap}.filter-complex__action--add{color:var(--color-primary)}.filter-complex__action--add:hover{color:var(--color-primary-hover)}.filter-complex__action--clear{color:var(--color-danger)}.filter-complex__list{display:flex;flex:1;flex-direction:column;overflow-y:auto}.filter-complex__list::-webkit-scrollbar{width:4px}.filter-complex__list::-webkit-scrollbar-thumb{background:var(--border-default);border-radius:100px}.filter-complex__list::-webkit-scrollbar-thumb:hover{background:var(--text-muted)}.filter-complex__item{align-items:center;background:none;border:none;border-radius:var(--radius-md,8px);color:var(--text-primary);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:var(--font-14);gap:10px;padding:7px 8px;text-align:left;transition:background-color var(--duration-fast) var(--ease-in-out)}.filter-complex__item:hover{background-color:var(--bg-surface-3)}.filter-complex__item-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.filter-complex__item-icon{flex-shrink:0}.filter-complex__item-icon--add{color:var(--color-primary)}.filter-complex__item-icon--remove{color:var(--color-danger)}.filter-complex__more{color:var(--text-muted);font-size:var(--font-12);padding:8px}.filter-complex__empty{color:var(--text-muted);font-size:var(--font-14);font-style:italic;padding:8px}.dropdown-enter-active,.dropdown-leave-active{transition:opacity var(--duration-fast) var(--ease-in-out),transform var(--duration-fast) var(--ease-in-out)}.dropdown-enter-from,.dropdown-leave-to{opacity:0;transform:translateY(-4px)}
453
+ .filter-complex{position:relative}.filter-complex__trigger{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-pill);box-sizing:border-box;color:var(--text-primary);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:var(--font-14);font-weight:var(--weight-medium);gap:6px;height:var(--rm-fbar-field-h,32px);max-width:320px;padding:0 12px;transition:all var(--duration-fast) var(--ease-in-out);white-space:nowrap}.filter-complex__trigger:hover{background:var(--bg-surface-3)}.filter-complex__icon{color:var(--color-primary);flex-shrink:0}.filter-complex__trigger--active{border-color:var(--color-primary)}.filter-complex__label{color:var(--text-muted);flex-shrink:0;font-weight:var(--weight-normal)}.filter-complex__summary{font-weight:var(--weight-semibold);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.filter-complex__badge{align-items:center;background-color:color-mix(in srgb,var(--color-primary) 10%,transparent);border-radius:var(--radius-pill);color:var(--color-primary);display:inline-flex;flex-shrink:0;font-size:var(--font-12);font-weight:var(--weight-semibold);height:18px;justify-content:center;line-height:1;min-width:18px;padding:0 6px}.filter-complex__caret{color:var(--text-muted);flex-shrink:0;transition:transform var(--duration-fast) var(--ease-in-out)}.filter-complex__caret--open{transform:rotate(180deg)}.filter-complex__panel{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-xl,12px);box-shadow:0 4px 24px rgba(var(--shadow-rgb),.1),0 2px 8px rgba(var(--shadow-rgb),.05);display:flex;left:0;max-height:460px;max-width:calc(100vw - 32px);overflow:hidden;position:absolute;top:calc(100% + 6px);width:720px;z-index:50}.filter-complex__left,.filter-complex__right{display:flex;flex-direction:column;gap:10px;overflow:hidden;padding:20px}.filter-complex__left{flex:1 1 50%}.filter-complex__right{border-left:1px solid var(--border-default);flex:1 1 50%}@media (max-width:767px){.filter-complex__panel{border:none;border-radius:0;flex-direction:column;inset:0;left:0;max-height:none;max-width:none;position:fixed;top:0;width:100%;z-index:100}.filter-complex__left,.filter-complex__right{flex:1 1 50%;min-height:0}.filter-complex__right{border-left:none;border-top:1px solid var(--border-default)}}.filter-complex__head{align-items:center;display:flex;gap:12px;justify-content:space-between}.filter-complex__title{color:var(--text-primary);font-size:var(--font-14);font-weight:var(--weight-bold);margin:0}.filter-complex__presets{display:flex;flex-wrap:wrap;gap:8px}.filter-complex__preset{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-pill);color:var(--text-primary);cursor:pointer;font-family:var(--font-sans);font-size:var(--font-12);font-weight:var(--weight-medium);height:26px;padding:0 12px;transition:all var(--duration-fast) var(--ease-in-out);white-space:nowrap}.filter-complex__preset:hover{border-color:var(--color-primary);color:var(--color-primary)}.filter-complex__preset.is-active{background:var(--color-primary);border-color:var(--color-primary);color:var(--color-text-inverse)}.filter-complex__search{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-md,8px);display:flex;gap:8px;padding:7px 10px}.filter-complex__search:focus-within{border-color:var(--color-primary)}.filter-complex__search-input{background:transparent;border:none;color:var(--text-primary);flex:1;font-family:var(--font-sans);font-size:var(--font-14);outline:none}.filter-complex__search-input::-moz-placeholder{color:var(--text-muted)}.filter-complex__search-input::placeholder{color:var(--text-muted)}.filter-complex__search-clear{color:var(--text-muted);cursor:pointer;flex-shrink:0}.filter-complex__search-clear:hover{color:var(--text-secondary)}.filter-complex__action{background:none;border:none;cursor:pointer;font-family:var(--font-sans);font-size:var(--font-12);font-weight:var(--weight-medium);padding:0;text-decoration:underline;white-space:nowrap}.filter-complex__action--add{color:var(--color-primary)}.filter-complex__action--add:hover{color:var(--color-primary-hover)}.filter-complex__action--clear{color:var(--color-danger)}.filter-complex__list{display:flex;flex:1;flex-direction:column;overflow-y:auto}.filter-complex__list::-webkit-scrollbar{width:4px}.filter-complex__list::-webkit-scrollbar-thumb{background:var(--border-default);border-radius:100px}.filter-complex__list::-webkit-scrollbar-thumb:hover{background:var(--text-muted)}.filter-complex__item{align-items:center;background:none;border:none;border-radius:var(--radius-md,8px);color:var(--text-primary);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:var(--font-14);gap:10px;padding:7px 8px;text-align:left;transition:background-color var(--duration-fast) var(--ease-in-out)}.filter-complex__item:hover{background-color:var(--bg-surface-3)}.filter-complex__item-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.filter-complex__item-icon{flex-shrink:0}.filter-complex__item-icon--add{color:var(--color-primary)}.filter-complex__item-icon--remove{color:var(--color-danger)}.filter-complex__more{color:var(--text-muted);font-size:var(--font-12);padding:8px}.filter-complex__empty{color:var(--text-muted);font-size:var(--font-14);font-style:italic;padding:8px}.dropdown-enter-active,.dropdown-leave-active{transition:opacity var(--duration-fast) var(--ease-in-out),transform var(--duration-fast) var(--ease-in-out)}.dropdown-enter-from,.dropdown-leave-to{opacity:0;transform:translateY(-4px)}
425
454
  </style>
@@ -80,3 +80,32 @@ export declare function rmFilterOptionsQuery(spec: RmFilterOptionsSpec): {
80
80
  };
81
81
  /** Les options, relues dans la reponse du moteur. */
82
82
  export declare function rmFilterOptions(response: any): RmFilterOption[];
83
+ /** Un champ interroge par la saisie d'un filtre. */
84
+ export interface RmFilterSearchField {
85
+ /** Champ interroge. La recherche porte sur son `.keyword`. */
86
+ field: string;
87
+ /**
88
+ * Chemins nested a traverser pour l'atteindre, du plus large au plus
89
+ * etroit (`['brand_history']`, ou `['a', 'a.b']` pour deux niveaux).
90
+ * Absent, le champ est a la racine du document.
91
+ */
92
+ nested?: string[];
93
+ }
94
+ /**
95
+ * La clause qui restreint les documents a la saisie, sur plusieurs champs a la
96
+ * fois. Meme regle que `rmFilterOptionsQuery`, pour un index ou le champ frere
97
+ * ne vit pas au meme niveau que le champ agrege : le filtre marque d'un index
98
+ * d'avis agrege `brand_group` a la racine et cherche la molecule dans
99
+ * `brand_history`, si bien que « pembro » sort KEYTRUDA. Un champ nested
100
+ * interroge sans son enveloppe `nested` ne repond jamais : c'est un filtre qui
101
+ * parait vide plutot qu'un filtre en erreur, d'ou cette enveloppe explicite.
102
+ *
103
+ * Trois facons de reconnaitre la saisie par champ : le fragment n'importe ou
104
+ * (`regexp`), la valeur entiere, et le debut. La derniere remonte en tete des
105
+ * listes ordonnees par pertinence.
106
+ */
107
+ export declare function rmFilterSearchClause(fields: RmFilterSearchField[], input: string): {
108
+ bool: {
109
+ should: any[];
110
+ };
111
+ };
@@ -38,3 +38,32 @@ export function rmFilterOptions(response) {
38
38
  (bucket) => Array.isArray(bucket.key) ? { label: String(bucket.key[0]), value: String(bucket.key[1]) } : { label: String(bucket.key), value: String(bucket.key) }
39
39
  );
40
40
  }
41
+ export function rmFilterSearchClause(fields, input) {
42
+ return {
43
+ bool: {
44
+ should: fields.map(({ field, nested }) => {
45
+ const clause = {
46
+ bool: {
47
+ should: [
48
+ {
49
+ regexp: {
50
+ [`${field}.keyword`]: {
51
+ value: `.*${input}.*`,
52
+ flags: "ALL",
53
+ case_insensitive: true
54
+ }
55
+ }
56
+ },
57
+ { match_phrase: { [`${field}.keyword`]: input } },
58
+ { prefix: { [`${field}.keyword`]: input } }
59
+ ]
60
+ }
61
+ };
62
+ return (nested ?? []).reduceRight(
63
+ (query, path) => ({ nested: { path, query } }),
64
+ clause
65
+ );
66
+ })
67
+ }
68
+ };
69
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekkare/romulus",
3
- "version": "0.1.54",
3
+ "version": "0.1.56",
4
4
  "description": "Romulus Design System - Nuxt module by Tekkare",
5
5
  "license": "MIT",
6
6
  "type": "module",