@tekkare/romulus 0.1.71 → 0.1.73

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.70",
7
+ "version": "0.1.72",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
@@ -8,7 +8,6 @@
8
8
  Laquelle des barres du paquet prendre :
9
9
  - celle-ci quand la page compose son menu en balises (`RmMenuItem`,
10
10
  `RmSubMenu`) et doit glisser ses propres blocs entre les entrees ;
11
- -
12
11
  - `RmSidebarLeft` pour le chrome du portail, decrit par une prop `sections` ;
13
12
  - `RmSidebarRight` pour le rail d'icones.
14
13
 
@@ -0,0 +1,228 @@
1
+ <!--
2
+ RmDropdownSelect - champ de filtre a choix unique.
3
+
4
+ Reprend `arg-dropdown-select` d'Auriga : un intitule, un champ qui montre la
5
+ valeur retenue, et un panneau qui liste les valeurs possibles avec, au
6
+ besoin, une recherche.
7
+
8
+ Laquelle prendre : celle-ci pour un choix unique pose dans un panneau de
9
+ filtres (periode, tri, devise) ; `RmFilterDropdown` pour la pastille d'une
10
+ barre de filtres ; `RmMultipleSelect` quand plusieurs valeurs peuvent tenir
11
+ ensemble.
12
+
13
+ Trois choses changent par rapport a Auriga.
14
+
15
+ La valeur est un `v-model` sur la chaine retenue, plus un index
16
+ (`default_select`) double d'un drapeau `return_value` : l'appelant passait
17
+ systematiquement `options.indexOf(valeur)` pour retrouver ce qu'il avait
18
+ deja, et une valeur absente de la liste donnait l'index -1, donc un champ
19
+ vide sans explication.
20
+
21
+ La liste est un `role="listbox"` navigable au clavier (fleches, Entree,
22
+ Echap) : chez Auriga c'etait une pile de `div` cliquables, hors d'atteinte
23
+ au clavier.
24
+
25
+ Les libelles du chrome sont dans le composant plutot que charges depuis des
26
+ JSON de locale au montage.
27
+ -->
28
+ <script setup lang="ts">
29
+ import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
30
+
31
+ const props = withDefaults(defineProps<{
32
+ /** Intitule pose au-dessus du champ. */
33
+ label: string
34
+ options: string[]
35
+ /** Champ de recherche en tete du panneau. */
36
+ search?: boolean
37
+ disabled?: boolean
38
+ /** Liste en cours de chargement : le panneau montre son attente. */
39
+ loading?: boolean
40
+ /** Chevrons haut-bas au lieu du chevron bas, pour un choix de tri. */
41
+ sortIcon?: boolean
42
+ /** Texte du champ quand rien n'est retenu. */
43
+ emptyLabel?: string
44
+ /** Texte du panneau quand la liste est vide. */
45
+ emptyContent?: string
46
+ searchPlaceholder?: string
47
+ /**
48
+ * Filtrage local de la liste sur la saisie. A passer a `false` quand les
49
+ * options arrivent deja filtrees du serveur : sinon un resultat trouve a
50
+ * distance mais dont le libelle ne contient pas la saisie (un synonyme,
51
+ * une marque cherchee par sa molecule) est refiltre puis efface a l'ecran.
52
+ */
53
+ filterSearch?: boolean
54
+ /** Le panneau se ferme des qu'une valeur est prise. */
55
+ closeOnClick?: boolean
56
+ lang?: 'fr' | 'en'
57
+ }>(), {
58
+ search: true,
59
+ disabled: false,
60
+ loading: false,
61
+ sortIcon: false,
62
+ emptyLabel: undefined,
63
+ emptyContent: undefined,
64
+ searchPlaceholder: undefined,
65
+ filterSearch: true,
66
+ closeOnClick: true,
67
+ lang: 'fr',
68
+ })
69
+
70
+ const emit = defineEmits<{
71
+ /** Valeur retenue. Double le `v-model`, pour les effets de bord. */
72
+ change: [value: string]
73
+ /** Saisie de recherche, amortie a 500 ms. */
74
+ search: [value: string]
75
+ }>()
76
+
77
+ const selected = defineModel<string | undefined>({ default: undefined })
78
+
79
+ const WORDS = {
80
+ fr: { empty: 'Aucune sélection', search: 'Rechercher...', notFound: 'Aucun résultat', loading: 'Chargement' },
81
+ en: { empty: 'No selection', search: 'Search...', notFound: 'No result', loading: 'Loading' },
82
+ } as const
83
+
84
+ const t = computed(() => WORDS[props.lang])
85
+
86
+ const isOpen = ref(false)
87
+ const searchTerm = ref('')
88
+ const activeIndex = ref(-1)
89
+ const rootRef = ref<HTMLElement>()
90
+ const searchRef = ref<HTMLInputElement>()
91
+
92
+ const visibleOptions = computed(() => {
93
+ const needle = searchTerm.value.trim().toLowerCase()
94
+ if (!needle || !props.filterSearch) return props.options
95
+ // Une saisie en plusieurs mots vaut « tous ces mots », dans n'importe quel
96
+ // ordre : « keytruda 25 » doit trouver « 25 MG KEYTRUDA ».
97
+ const parts = needle.split(' ').filter(Boolean)
98
+ return props.options.filter(option => parts.every(part => option.toLowerCase().includes(part)))
99
+ })
100
+
101
+ /** La recherche part au serveur amortie : une frappe par lettre le noierait. */
102
+ let debounceId: ReturnType<typeof setTimeout> | undefined
103
+ watch(searchTerm, (value) => {
104
+ clearTimeout(debounceId)
105
+ debounceId = setTimeout(() => emit('search', value), 500)
106
+ })
107
+
108
+ function close() {
109
+ isOpen.value = false
110
+ activeIndex.value = -1
111
+ document.removeEventListener('click', onDocumentClick)
112
+ }
113
+
114
+ function onDocumentClick(event: MouseEvent) {
115
+ if (!rootRef.value?.contains(event.target as Node)) close()
116
+ }
117
+
118
+ async function toggle() {
119
+ if (props.disabled) return
120
+ if (isOpen.value) return close()
121
+
122
+ isOpen.value = true
123
+ searchTerm.value = ''
124
+ activeIndex.value = props.options.indexOf(selected.value ?? '')
125
+ document.addEventListener('click', onDocumentClick)
126
+ if (!props.search) return
127
+ await nextTick()
128
+ searchRef.value?.focus()
129
+ }
130
+
131
+ function pick(option: string) {
132
+ selected.value = option
133
+ emit('change', option)
134
+ if (props.closeOnClick) close()
135
+ }
136
+
137
+ /** Les fleches parcourent la liste affichee, pas la liste complete. */
138
+ function move(step: 1 | -1) {
139
+ const total = visibleOptions.value.length
140
+ if (total === 0) return
141
+ activeIndex.value = (activeIndex.value + step + total) % total
142
+ }
143
+
144
+ function onKeydown(event: KeyboardEvent) {
145
+ if (event.key === 'Escape') return close()
146
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
147
+ event.preventDefault()
148
+ if (!isOpen.value) return toggle()
149
+ return move(event.key === 'ArrowDown' ? 1 : -1)
150
+ }
151
+ if (event.key === 'Enter' && isOpen.value && activeIndex.value >= 0) {
152
+ event.preventDefault()
153
+ const option = visibleOptions.value[activeIndex.value]
154
+ if (option !== undefined) pick(option)
155
+ }
156
+ }
157
+
158
+ onUnmounted(() => {
159
+ clearTimeout(debounceId)
160
+ document.removeEventListener('click', onDocumentClick)
161
+ })
162
+ </script>
163
+
164
+ <template>
165
+ <div ref="rootRef" class="rm-dsel" @keydown="onKeydown">
166
+ <p class="rm-dsel__label">{{ label }}</p>
167
+
168
+ <button
169
+ type="button"
170
+ class="rm-dsel__field"
171
+ :class="{ 'is-open': isOpen, 'is-disabled': disabled }"
172
+ :disabled="disabled"
173
+ :aria-expanded="isOpen"
174
+ aria-haspopup="listbox"
175
+ @click.stop="toggle"
176
+ >
177
+ <RmTooltip :text="selected ?? ''" :disabled="!selected" class="rm-dsel__value-slot">
178
+ <span class="rm-dsel__value" :class="{ 'is-empty': !selected }">
179
+ {{ selected || emptyLabel || t.empty }}
180
+ </span>
181
+ </RmTooltip>
182
+ <RmIcon :name="sortIcon ? 'CaretUpDown' : 'CaretDown'" :size="16" />
183
+ </button>
184
+
185
+ <div v-show="isOpen" class="rm-dsel__panel" @click.stop>
186
+ <div v-if="search" class="rm-dsel__search">
187
+ <RmIcon name="MagnifyingGlass" :size="16" />
188
+ <input
189
+ ref="searchRef"
190
+ v-model="searchTerm"
191
+ type="search"
192
+ class="rm-dsel__search-input"
193
+ :placeholder="searchPlaceholder || t.search"
194
+ :aria-label="searchPlaceholder || t.search"
195
+ >
196
+ </div>
197
+
198
+ <p v-if="loading" class="rm-dsel__state" aria-busy="true">{{ t.loading }}</p>
199
+ <p v-else-if="visibleOptions.length === 0" class="rm-dsel__state">
200
+ {{ emptyContent || t.notFound }}
201
+ </p>
202
+
203
+ <ul v-else class="rm-dsel__list" role="listbox" :aria-label="label">
204
+ <li
205
+ v-for="(option, index) in visibleOptions"
206
+ :key="option"
207
+ role="option"
208
+ class="rm-dsel__option"
209
+ :class="{ 'is-selected': option === selected, 'is-active': index === activeIndex }"
210
+ :aria-selected="option === selected"
211
+ @click="pick(option)"
212
+ @mousemove="activeIndex = index"
213
+ >
214
+ <RmIcon
215
+ :name="option === selected ? 'CheckCircle' : 'Circle'"
216
+ :size="16"
217
+ :weight="option === selected ? 'fill' : 'regular'"
218
+ />
219
+ <span>{{ option }}</span>
220
+ </li>
221
+ </ul>
222
+ </div>
223
+ </div>
224
+ </template>
225
+
226
+ <style scoped>
227
+ .rm-dsel{align-items:flex-start;display:flex;flex-direction:column;gap:4px;position:relative}.rm-dsel__label{color:var(--text-secondary);font-size:var(--font-12,12px);line-height:14px}.rm-dsel__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-dsel__field:hover:not(.is-disabled){border-color:rgba(var(--brand-rgb),.3)}.rm-dsel__field.is-open{border-color:var(--color-primary)}.rm-dsel__field.is-disabled{background:var(--bg-surface-2);color:var(--text-muted);cursor:not-allowed}.rm-dsel__field:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}.rm-dsel__value-slot{display:block;flex:1;min-width:0}.rm-dsel__value{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rm-dsel__value.is-empty{color:var(--text-muted);font-style:italic;font-weight:400}.rm-dsel__field :deep(svg){color:var(--text-secondary);flex-shrink:0}.rm-dsel__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-dsel__panel,.rm-dsel__search{background:var(--bg-surface);display:flex}.rm-dsel__search{align-items:center;border:1px solid var(--border-default);border-radius:var(--radius-md,8px);gap:8px;padding:8px 12px}.rm-dsel__search :deep(svg){color:var(--text-muted);flex-shrink:0}.rm-dsel__search-input{background:none;border:none;color:var(--text-primary);font-family:inherit;font-size:var(--font-14,14px);width:100%}.rm-dsel__search-input::-moz-placeholder{color:var(--text-muted);font-size:var(--font-12,12px)}.rm-dsel__search-input::placeholder{color:var(--text-muted);font-size:var(--font-12,12px)}.rm-dsel__search-input:focus-visible{outline:none}.rm-dsel__state{color:var(--text-secondary);font-size:var(--font-14,14px);font-style:italic;padding-top:16px;text-align:center}.rm-dsel__list{display:flex;flex-direction:column;list-style:none;margin:0;min-height:10px;overflow-y:auto;padding:0}.rm-dsel__list:not(:only-child){margin-top:16px}.rm-dsel__option{align-items:center;border-radius:var(--radius-sm,4px);cursor:pointer;display:flex;font-size:var(--font-14,14px);gap:8px;line-height:160%;margin:4px 0;min-height:24px;padding:2px 4px}.rm-dsel__option :deep(svg){color:var(--text-muted);flex-shrink:0}.rm-dsel__option.is-active{background:rgba(var(--brand-rgb),.05)}.rm-dsel__option.is-selected{font-weight:700}.rm-dsel__option.is-active :deep(svg),.rm-dsel__option.is-selected :deep(svg){color:var(--color-primary)}
228
+ </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>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekkare/romulus",
3
- "version": "0.1.71",
3
+ "version": "0.1.73",
4
4
  "description": "Romulus Design System - Nuxt module by Tekkare",
5
5
  "license": "MIT",
6
6
  "type": "module",