@tekkare/romulus 0.1.223 → 0.1.225
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,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
|
|
3
|
-
import { PhCaretDown, PhCheck, PhMagnifyingGlass } from '@phosphor-icons/vue'
|
|
3
|
+
import { PhCaretDown, PhCheck, PhMagnifyingGlass, PhPlus } from '@phosphor-icons/vue'
|
|
4
4
|
import type { Component } from 'vue'
|
|
5
5
|
import { rmPanelOpened, rmPanelRegister } from '../utils/panels'
|
|
6
6
|
|
|
@@ -37,8 +37,23 @@ const props = withDefaults(defineProps<{
|
|
|
37
37
|
searchPlaceholder?: string
|
|
38
38
|
/** Langue des textes propres au composant (tout selectionner, aucun resultat). */
|
|
39
39
|
lang?: 'fr' | 'en'
|
|
40
|
+
/**
|
|
41
|
+
* La saisie de recherche peut devenir une valeur nouvelle (des tags) : une
|
|
42
|
+
* ligne « Creer « … » » apparait quand aucune option ne porte exactement ce
|
|
43
|
+
* libelle. La valeur est ajoutee a la selection et `create` previent
|
|
44
|
+
* l'appelant, qui l'ajoute a ses options. Implique la recherche.
|
|
45
|
+
*/
|
|
46
|
+
creatable?: boolean
|
|
47
|
+
/**
|
|
48
|
+
* `chip` : la puce de la barre de filtres. `add` : un simple « + » a la
|
|
49
|
+
* hauteur d'une RmTag, pose au bout d'une rangee d'etiquettes (les tags
|
|
50
|
+
* d'une page) ; `label` devient son intitule accessible.
|
|
51
|
+
*/
|
|
52
|
+
trigger?: 'chip' | 'add'
|
|
40
53
|
}>(), {
|
|
41
54
|
icon: undefined,
|
|
55
|
+
creatable: false,
|
|
56
|
+
trigger: 'chip',
|
|
42
57
|
multiple: true,
|
|
43
58
|
showSelectAll: false,
|
|
44
59
|
searchable: 'auto',
|
|
@@ -47,8 +62,10 @@ const props = withDefaults(defineProps<{
|
|
|
47
62
|
})
|
|
48
63
|
|
|
49
64
|
const texts = computed(() => props.lang === 'en'
|
|
50
|
-
? { selectAll: 'Select all', deselectAll: 'Deselect all', noMatch: 'No match' }
|
|
51
|
-
: { selectAll: 'Tout sélectionner', deselectAll: 'Tout désélectionner', noMatch: 'Aucun résultat' })
|
|
65
|
+
? { selectAll: 'Select all', deselectAll: 'Deselect all', noMatch: 'No match', create: (q: string) => `Create « ${q} »` }
|
|
66
|
+
: { selectAll: 'Tout sélectionner', deselectAll: 'Tout désélectionner', noMatch: 'Aucun résultat', create: (q: string) => `Créer « ${q} »` })
|
|
67
|
+
|
|
68
|
+
const emit = defineEmits<{ create: [value: string] }>()
|
|
52
69
|
|
|
53
70
|
const selected = defineModel<string[]>({ default: () => [] })
|
|
54
71
|
|
|
@@ -80,9 +97,27 @@ const displayLabel = computed(() => {
|
|
|
80
97
|
})
|
|
81
98
|
|
|
82
99
|
const hasSearch = computed(() =>
|
|
83
|
-
props.searchable === 'auto' ? props.options.length > SEARCH_THRESHOLD : props.searchable,
|
|
100
|
+
props.creatable || (props.searchable === 'auto' ? props.options.length > SEARCH_THRESHOLD : props.searchable),
|
|
84
101
|
)
|
|
85
102
|
|
|
103
|
+
/** La saisie, quand elle peut devenir une option nouvelle. */
|
|
104
|
+
const creatableValue = computed(() => {
|
|
105
|
+
const q = search.value.trim()
|
|
106
|
+
if (!props.creatable || !q) return ''
|
|
107
|
+
const exists = props.options.some(o => o.label.toLowerCase() === q.toLowerCase())
|
|
108
|
+
return exists ? '' : q
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
function createOption() {
|
|
112
|
+
const value = creatableValue.value
|
|
113
|
+
if (!value) return
|
|
114
|
+
hasUserChanged.value = true
|
|
115
|
+
emit('create', value)
|
|
116
|
+
selected.value = props.multiple ? [...selected.value, value] : [value]
|
|
117
|
+
search.value = ''
|
|
118
|
+
if (!props.multiple) isOpen.value = false
|
|
119
|
+
}
|
|
120
|
+
|
|
86
121
|
const filteredOptions = computed(() => {
|
|
87
122
|
if (!hasSearch.value || !search.value.trim()) return props.options
|
|
88
123
|
const needle = search.value.trim().toLowerCase()
|
|
@@ -176,8 +211,21 @@ onUnmounted(() => {
|
|
|
176
211
|
</script>
|
|
177
212
|
|
|
178
213
|
<template>
|
|
179
|
-
<div ref="chipRef" class="filter-chip-wrapper">
|
|
214
|
+
<div ref="chipRef" class="filter-chip-wrapper" :class="{ 'filter-chip-wrapper--add': trigger === 'add' }">
|
|
180
215
|
<button
|
|
216
|
+
v-if="trigger === 'add'"
|
|
217
|
+
type="button"
|
|
218
|
+
class="filter-chip__add"
|
|
219
|
+
:class="{ 'filter-chip__add--open': isOpen }"
|
|
220
|
+
:aria-label="label"
|
|
221
|
+
:title="label"
|
|
222
|
+
:aria-expanded="isOpen"
|
|
223
|
+
@click="toggle"
|
|
224
|
+
>
|
|
225
|
+
<PhPlus :size="12" weight="bold" />
|
|
226
|
+
</button>
|
|
227
|
+
<button
|
|
228
|
+
v-else
|
|
181
229
|
class="filter-chip"
|
|
182
230
|
:class="{ 'filter-chip--active': isActive }"
|
|
183
231
|
@click="toggle"
|
|
@@ -204,6 +252,7 @@ onUnmounted(() => {
|
|
|
204
252
|
type="search"
|
|
205
253
|
:placeholder="searchPlaceholder"
|
|
206
254
|
@click.stop
|
|
255
|
+
@keydown.enter.prevent="createOption"
|
|
207
256
|
/>
|
|
208
257
|
</label>
|
|
209
258
|
<button
|
|
@@ -217,7 +266,16 @@ onUnmounted(() => {
|
|
|
217
266
|
<span>{{ allSelected ? texts.deselectAll : texts.selectAll }}</span>
|
|
218
267
|
</button>
|
|
219
268
|
<div :class="{ 'filter-chip__options-group': showSelectAll && multiple && options.length > 0 }">
|
|
220
|
-
<
|
|
269
|
+
<button
|
|
270
|
+
v-if="creatableValue"
|
|
271
|
+
type="button"
|
|
272
|
+
class="filter-chip__option filter-chip__create"
|
|
273
|
+
@click.stop="createOption"
|
|
274
|
+
>
|
|
275
|
+
<PhPlus :size="12" weight="bold" />
|
|
276
|
+
<span>{{ texts.create(creatableValue) }}</span>
|
|
277
|
+
</button>
|
|
278
|
+
<p v-if="hasSearch && filteredOptions.length === 0 && !creatableValue" class="filter-chip__no-match">
|
|
221
279
|
{{ texts.noMatch }}
|
|
222
280
|
</p>
|
|
223
281
|
<template v-for="row in rows" :key="row.key">
|
|
@@ -248,5 +306,5 @@ onUnmounted(() => {
|
|
|
248
306
|
</template>
|
|
249
307
|
|
|
250
308
|
<style scoped>
|
|
251
|
-
.filter-chip-wrapper{position:relative}.filter-chip{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:6px;box-sizing:border-box;color:var(--text-secondary);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:var(--font-14);font-weight:var(--weight-medium);gap:var(--space-8);height:32px;padding:var(--space-8);transition:all var(--duration-fast) var(--ease-in-out);white-space:nowrap;width:193px}.filter-chip:hover{border-color:var(--border-default)}.filter-chip__icon{color:var(--color-healthcare);flex-shrink:0}.filter-chip--active{border-color:var(--color-healthcare);box-shadow:0 0 8px 0 rgba(var(--healthcare-rgb),.1)}.filter-chip__label{flex:1;min-width:0;overflow:hidden;text-align:left;text-overflow:ellipsis;white-space:nowrap}.filter-chip__badge{align-items:center;background-color:var(--color-healthcare);border-radius:100px;color:var(--color-text-inverse);display:inline-flex;flex-shrink:0;font-size:var(--font-11);font-weight:600;height:18px;justify-content:center;line-height:1;min-width:18px;padding:0 5px}.filter-chip__caret{color:var(--text-muted);flex-shrink:0;transition:transform var(--duration-fast) var(--ease-in-out)}.filter-chip__caret--open{transform:rotate(180deg)}.filter-chip__dropdown{background-color:var(--bg-surface);border:1px solid var(--border-default);border-radius:6px;box-shadow:0 4px 12px rgba(var(--shadow-rgb),.08),0 2px 4px rgba(var(--shadow-rgb),.04);left:0;max-height:300px;max-width:420px;min-width:380px;overflow-y:auto;padding:var(--space-4);position:absolute;top:calc(100% + 4px);width:-moz-max-content;width:max-content;z-index:50}.filter-chip__search{align-items:center;background-color:var(--bg-surface);border-bottom:1px solid var(--border-soft);color:var(--text-muted);display:flex;gap:var(--space-6);margin:-4px -4px 4px;padding:var(--space-8);position:sticky;top:-4px;z-index:1}.filter-chip__search input{background:none;border:none;color:var(--text-primary);flex:1;font-family:var(--font-sans);font-size:var(--font-14);min-width:0;outline:none}.filter-chip__search input::-webkit-search-cancel-button{cursor:pointer}.filter-chip__no-match{font-size:var(--font-14);margin:0;padding:var(--space-10) var(--space-8)}.filter-chip__no-match,.filter-chip__section{color:var(--text-muted);font-family:var(--font-sans)}.filter-chip__section{border-top:1px solid var(--border-default);font-size:var(--font-12);font-weight:var(--weight-bold);letter-spacing:.04em;margin:8px 0 2px;padding:var(--space-8) var(--space-8) 0;text-transform:uppercase}.filter-chip__section:first-child{border-top:none;margin-top:0;padding-top:0}.filter-chip__option{align-items:center;background:none;border:none;border-radius:4px;color:var(--text-secondary);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:var(--font-14);gap:var(--space-8);padding:var(--space-6) var(--space-8);text-align:left;transition:background-color var(--duration-fast) var(--ease-in-out);width:100%}.filter-chip__option:hover{background-color:var(--bg-surface-2)}.filter-chip__option--selected{color:var(--text-primary);font-weight:var(--weight-medium)}.filter-chip__checkbox{align-items:center;border:1.5px solid var(--border-default);border-radius:3px;display:flex;flex-shrink:0;height:16px;justify-content:center;transition:all var(--duration-fast) var(--ease-in-out);width:16px}.filter-chip__checkbox--checked{background-color:var(--color-healthcare);border-color:var(--color-healthcare);color:var(--color-text-inverse)}.filter-chip__radio{align-items:center;border:1.5px solid var(--border-default);border-radius:100%;display:flex;flex-shrink:0;height:16px;justify-content:center;transition:all var(--duration-fast) var(--ease-in-out);width:16px}.filter-chip__radio--checked{border-color:var(--color-healthcare)}.filter-chip__radio--checked:after{background-color:var(--color-healthcare);border-radius:100%;content:"";height:8px;width:8px}.filter-chip__select-all{align-items:center;background:none;border:none;border-radius:4px;color:var(--color-healthcare);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:var(--font-14);font-weight:500;gap:var(--space-8);padding:var(--space-6) var(--space-8);text-align:left;width:100%}.filter-chip__select-all:hover{background-color:rgba(var(--healthcare-rgb),.04)}.filter-chip__options-group{border-left:1px solid var(--border-default);margin-left:14px;padding-left:var(--space-6)}.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)}
|
|
309
|
+
.filter-chip-wrapper{position:relative}.filter-chip-wrapper--add{display:inline-flex}.filter-chip{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:6px;box-sizing:border-box;color:var(--text-secondary);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:var(--font-14);font-weight:var(--weight-medium);gap:var(--space-8);height:32px;padding:var(--space-8);transition:all var(--duration-fast) var(--ease-in-out);white-space:nowrap;width:193px}.filter-chip:hover{border-color:var(--border-default)}.filter-chip__icon{color:var(--color-healthcare);flex-shrink:0}.filter-chip--active{border-color:var(--color-healthcare);box-shadow:0 0 8px 0 rgba(var(--healthcare-rgb),.1)}.filter-chip__label{flex:1;min-width:0;overflow:hidden;text-align:left;text-overflow:ellipsis;white-space:nowrap}.filter-chip__badge{align-items:center;background-color:var(--color-healthcare);border-radius:100px;color:var(--color-text-inverse);display:inline-flex;flex-shrink:0;font-size:var(--font-11);font-weight:600;height:18px;justify-content:center;line-height:1;min-width:18px;padding:0 5px}.filter-chip__caret{color:var(--text-muted);flex-shrink:0;transition:transform var(--duration-fast) var(--ease-in-out)}.filter-chip__caret--open{transform:rotate(180deg)}.filter-chip__dropdown{background-color:var(--bg-surface);border:1px solid var(--border-default);border-radius:6px;box-shadow:0 4px 12px rgba(var(--shadow-rgb),.08),0 2px 4px rgba(var(--shadow-rgb),.04);left:0;max-height:300px;max-width:420px;min-width:380px;overflow-y:auto;padding:var(--space-4);position:absolute;top:calc(100% + 4px);width:-moz-max-content;width:max-content;z-index:50}.filter-chip__search{align-items:center;background-color:var(--bg-surface);border-bottom:1px solid var(--border-soft);color:var(--text-muted);display:flex;gap:var(--space-6);margin:-4px -4px 4px;padding:var(--space-8);position:sticky;top:-4px;z-index:1}.filter-chip__search input{background:none;border:none;color:var(--text-primary);flex:1;font-family:var(--font-sans);font-size:var(--font-14);min-width:0;outline:none}.filter-chip__search input::-webkit-search-cancel-button{cursor:pointer}.filter-chip__no-match{font-size:var(--font-14);margin:0;padding:var(--space-10) var(--space-8)}.filter-chip__no-match,.filter-chip__section{color:var(--text-muted);font-family:var(--font-sans)}.filter-chip__section{border-top:1px solid var(--border-default);font-size:var(--font-12);font-weight:var(--weight-bold);letter-spacing:.04em;margin:8px 0 2px;padding:var(--space-8) var(--space-8) 0;text-transform:uppercase}.filter-chip__section:first-child{border-top:none;margin-top:0;padding-top:0}.filter-chip__add{align-items:center;background:transparent;border:1px dashed var(--border-default);border-radius:var(--radius-sm);color:var(--text-secondary);cursor:pointer;display:inline-flex;font-size:var(--font-12);justify-content:center;line-height:normal;padding:calc(var(--space-4) - 1px) calc(var(--space-6) - 1px)}.filter-chip__add svg{height:1lh}.filter-chip__add--open,.filter-chip__add:hover{border-color:var(--color-primary);border-style:solid;color:var(--color-primary)}.filter-chip__add:focus-visible{box-shadow:var(--shadow-focus);outline:none}.filter-chip__create{color:var(--color-primary);font-weight:var(--weight-medium)}.filter-chip__option{align-items:center;background:none;border:none;border-radius:4px;color:var(--text-secondary);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:var(--font-14);gap:var(--space-8);padding:var(--space-6) var(--space-8);text-align:left;transition:background-color var(--duration-fast) var(--ease-in-out);width:100%}.filter-chip__option:hover{background-color:var(--bg-surface-2)}.filter-chip__option--selected{color:var(--text-primary);font-weight:var(--weight-medium)}.filter-chip__checkbox{align-items:center;border:1.5px solid var(--border-default);border-radius:3px;display:flex;flex-shrink:0;height:16px;justify-content:center;transition:all var(--duration-fast) var(--ease-in-out);width:16px}.filter-chip__checkbox--checked{background-color:var(--color-healthcare);border-color:var(--color-healthcare);color:var(--color-text-inverse)}.filter-chip__radio{align-items:center;border:1.5px solid var(--border-default);border-radius:100%;display:flex;flex-shrink:0;height:16px;justify-content:center;transition:all var(--duration-fast) var(--ease-in-out);width:16px}.filter-chip__radio--checked{border-color:var(--color-healthcare)}.filter-chip__radio--checked:after{background-color:var(--color-healthcare);border-radius:100%;content:"";height:8px;width:8px}.filter-chip__select-all{align-items:center;background:none;border:none;border-radius:4px;color:var(--color-healthcare);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:var(--font-14);font-weight:500;gap:var(--space-8);padding:var(--space-6) var(--space-8);text-align:left;width:100%}.filter-chip__select-all:hover{background-color:rgba(var(--healthcare-rgb),.04)}.filter-chip__options-group{border-left:1px solid var(--border-default);margin-left:14px;padding-left:var(--space-6)}.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)}
|
|
252
310
|
</style>
|