@tekkare/romulus 0.1.46 → 0.1.48

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.45",
7
+ "version": "0.1.47",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
@@ -244,5 +244,5 @@ onBeforeUnmount(() => {
244
244
  </template>
245
245
 
246
246
  <style scoped>
247
- .rm-fbar{margin-inline:calc((min(var(--oip-content-max, 100%), 100%) - var(--oip-main-w, 100%))/2);position:sticky;width:var(--oip-main-w,100%);z-index:91;--rm-fbar-field-h:32px;backdrop-filter:blur(12px);background:rgba(245,245,249,.8);border-bottom:1px solid var(--border-default);display:flex;flex-direction:column;font-family:var(--font-sans)}.rm-fbar__row{align-items:flex-start;display:flex;gap:12px;padding-block:8px;padding-inline:max(32px,(100% - var(--oip-content-max,100%))/2 + 32px)}.rm-fbar--open .rm-fbar__row{border-bottom:1px solid var(--border-default)}.rm-fbar__toggle :deep(p),.rm-fbar__toggle :deep(span){font-size:inherit;font-weight:inherit;line-height:inherit;margin:0}.rm-fbar__toggle{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-pill);color:var(--text-primary);cursor:pointer;display:flex;flex:0 0 auto;font-family:inherit;font-size:var(--font-14);font-weight:var(--weight-medium);gap:6px;height:var(--rm-fbar-field-h);justify-content:center;padding:0 14px}.rm-fbar__toggle:hover{background:var(--bg-surface-3)}.rm-fbar__toggle.is-active,.rm-fbar__toggle.is-active:hover{background:var(--color-primary);border-color:var(--color-primary);color:var(--color-text-inverse)}.rm-fbar__toggle.is-active .rm-fbar__badge{background:var(--color-text-inverse);color:var(--color-primary)}.rm-fbar__badge{align-items:center;background:var(--color-primary);border-radius:16px;color:var(--color-text-inverse);display:inline-flex;font-size:var(--font-11);font-weight:var(--weight-bold);justify-content:center;min-width:18px;padding:0 5px}.rm-fbar__view{align-items:center;display:flex;flex:0 0 auto}.rm-fbar__advanced{align-items:center;background:none;border:none;border-left:1px solid var(--border-default);color:var(--color-primary);cursor:pointer;display:inline-flex;font-family:inherit;font-size:var(--font-13);font-weight:var(--weight-medium);height:var(--rm-fbar-field-h);padding:0 0 0 12px;position:absolute;right:14px;text-decoration-line:underline;top:0;white-space:nowrap;z-index:2}.rm-fbar__advanced.is-active,.rm-fbar__advanced:hover{color:var(--color-primary-hover,var(--color-primary));text-decoration-thickness:2px}.rm-fbar__rail{align-items:center;display:flex;flex:0 1 auto;min-width:0}.rm-fbar__rail:empty{display:none}.rm-fbar__search{align-items:center;display:flex;flex:1 1 auto;gap:8px;min-width:0;position:relative}.rm-fbar__search:empty{display:none}.rm-fbar__search :deep(.filters_input_search){border-radius:var(--radius-pill);box-sizing:border-box;height:auto;min-height:var(--rm-fbar-field-h);padding-right:calc(var(--rm-fbar-advanced-w, 0px) + 12px);width:100%}.rm-fbar__search :deep(.research_tags:not(:has(*))){display:none}.rm-fbar__search :deep(.select_shortcut){box-sizing:border-box;font-size:var(--font-11);height:24px;padding-block:0}.rm-fbar__search :deep(.select_shortcut:hover){background:var(--bg-surface-3)!important;border-color:var(--border-default)!important;color:inherit!important}.rm-fbar__search :deep(.select_shortcut:hover svg){color:inherit!important}.rm-fbar__search :deep(.remove_search_section){border-left:none;border-radius:0 var(--radius-pill) var(--radius-pill) 0}.rm-fbar__search :deep(.remove_search_section:hover){background:transparent!important}.rm-fbar__search :deep(.remove_search_section:hover svg){color:var(--text-primary)!important}.rm-fbar__search :deep(.filters_input_search.no_bottom){border-bottom:1.5px solid var(--color-primary)!important;border-radius:var(--radius-pill)}.rm-fbar__search :deep(.search_suggestions){border:1px solid var(--border-default);border-radius:var(--radius-2xl);box-shadow:var(--shadow-lg);left:0;padding:6px;right:0;top:calc(100% + 6px);width:auto}.rm-fbar__search :deep(.sugg_container>*){border-radius:var(--radius-lg)}.rm-fbar__search :deep(.sugg_container>.selected){background:color-mix(in srgb,var(--color-primary) 6%,transparent)!important}.rm-fbar__panel{--rm-filter-col:224px;align-items:end;display:grid;gap:16px;grid-template-columns:repeat(auto-fit,minmax(0,var(--rm-filter-col)));justify-content:center;padding-block:16px 20px;padding-inline:56px}.rm-fbar__panel--wide{align-items:stretch;display:flex;flex-direction:column;gap:12px}.rm-fbar__filters{display:contents}.rm-fbar__panel-head{align-items:baseline;display:flex;gap:16px;grid-column:1/-1;width:100%}.rm-fbar__customize{grid-column:1/-1}.rm-fbar__panel :deep(.filters_row_break){grid-column:1/-1}.rm-fbar__panel-title{font-size:var(--font-18);font-weight:800;margin:0}.rm-fbar__action{background:none;border:none;cursor:pointer;font-family:inherit;font-size:var(--font-14);font-weight:var(--weight-medium);padding:0;text-decoration-line:underline}.rm-fbar__action--save{color:var(--color-primary)}.rm-fbar__action--clear{color:var(--text-secondary)}
247
+ .rm-fbar{margin-inline:calc((min(var(--oip-content-max, 100%), 100%) - var(--oip-main-w, 100%))/2);position:sticky;width:var(--oip-main-w,100%);z-index:91;--rm-fbar-field-h:32px;backdrop-filter:blur(12px);background:rgba(245,245,249,.8);border-bottom:1px solid var(--border-default);display:flex;flex-direction:column;font-family:var(--font-sans)}.rm-fbar__row{align-items:flex-start;display:flex;gap:12px;padding-block:8px;padding-inline:max(32px,(100% - var(--oip-content-max,100%))/2 + 32px)}.rm-fbar--open .rm-fbar__row{border-bottom:1px solid var(--border-default)}.rm-fbar__toggle :deep(p),.rm-fbar__toggle :deep(span){font-size:inherit;font-weight:inherit;line-height:inherit;margin:0}.rm-fbar__toggle{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-pill);color:var(--text-primary);cursor:pointer;display:flex;flex:0 0 auto;font-family:inherit;font-size:var(--font-14);font-weight:var(--weight-medium);gap:6px;height:var(--rm-fbar-field-h);justify-content:center;padding:0 14px}.rm-fbar__toggle:hover{background:var(--bg-surface-3)}.rm-fbar__toggle.is-active,.rm-fbar__toggle.is-active:hover{background:var(--color-primary);border-color:var(--color-primary);color:var(--color-text-inverse)}.rm-fbar__toggle.is-active .rm-fbar__badge{background:var(--color-text-inverse);color:var(--color-primary)}.rm-fbar__badge{align-items:center;background:var(--color-primary);border-radius:16px;color:var(--color-text-inverse);display:inline-flex;font-size:var(--font-11);font-weight:var(--weight-bold);justify-content:center;min-width:18px;padding:0 5px}.rm-fbar__view{align-items:center;display:flex;flex:0 0 auto}.rm-fbar__advanced{align-items:center;background:none;border:none;border-left:1px solid var(--border-default);color:var(--color-primary);cursor:pointer;display:inline-flex;font-family:inherit;font-size:var(--font-13);font-weight:var(--weight-medium);height:var(--rm-fbar-field-h);padding:0 0 0 12px;position:absolute;right:14px;text-decoration-line:underline;top:0;white-space:nowrap;z-index:2}.rm-fbar__advanced.is-active,.rm-fbar__advanced:hover{color:var(--color-primary-hover,var(--color-primary));text-decoration-thickness:2px}.rm-fbar__rail{align-items:center;display:flex;flex:0 1 auto;min-width:0}.rm-fbar__rail:empty{display:none}.rm-fbar__search{align-items:center;display:flex;flex:1 1 auto;gap:8px;min-width:0;position:relative}.rm-fbar__search:empty{display:none}.rm-fbar__search :deep(.filters_input_search){border-radius:var(--radius-pill);box-sizing:border-box;height:auto;min-height:var(--rm-fbar-field-h);padding-right:calc(var(--rm-fbar-advanced-w, 0px) + 12px);width:100%}.rm-fbar__search :deep(.research_tags:not(:has(*))){display:none}.rm-fbar__search :deep(.select_shortcut){box-sizing:border-box;font-size:var(--font-11);height:24px;padding-block:0}.rm-fbar__search :deep(.select_shortcut:hover){background:var(--bg-surface-3)!important;border-color:var(--border-default)!important;color:inherit!important}.rm-fbar__search :deep(.select_shortcut:hover svg){color:inherit!important}.rm-fbar__search :deep(.remove_search_section){border-left:none;border-radius:0 var(--radius-pill) var(--radius-pill) 0}.rm-fbar__search :deep(.remove_search_section:hover){background:transparent!important}.rm-fbar__search :deep(.remove_search_section:hover svg){color:var(--text-primary)!important}.rm-fbar__search :deep(.filters_input_search.no_bottom){border-bottom:1.5px solid var(--color-primary)!important;border-radius:var(--radius-pill)}.rm-fbar__search :deep(.search_suggestions){border:1px solid var(--border-default);border-radius:var(--radius-2xl);box-shadow:var(--shadow-lg);left:0;padding:6px;right:0;top:calc(100% + 6px);width:auto}.rm-fbar__search :deep(.sugg_container>*){border-radius:var(--radius-lg)}.rm-fbar__search :deep(.sugg_container>.selected){background:color-mix(in srgb,var(--color-primary) 6%,transparent)!important}.rm-fbar__panel{--rm-filter-col:224px;--rm-fbar-panel-field-h:40px;align-items:end;display:grid;gap:16px;grid-template-columns:repeat(auto-fit,minmax(0,var(--rm-filter-col)));justify-content:center;padding-block:16px 20px;padding-inline:56px}.rm-fbar__panel--wide{align-items:stretch;display:flex;flex-direction:column;gap:12px}.rm-fbar__filters{display:contents}.rm-fbar__filters :deep(.filter-chip),.rm-fbar__filters :deep(.filter-complex__trigger),.rm-fbar__filters :deep(.rm-number__box){border-radius:var(--radius-lg);height:var(--rm-fbar-panel-field-h);width:100%}.rm-fbar__panel-head{align-items:baseline;display:flex;gap:16px;grid-column:1/-1;width:100%}.rm-fbar__customize{grid-column:1/-1}.rm-fbar__panel :deep(.filters_row_break){grid-column:1/-1}.rm-fbar__panel-title{font-size:var(--font-18);font-weight:800;margin:0}.rm-fbar__action{background:none;border:none;cursor:pointer;font-family:inherit;font-size:var(--font-14);font-weight:var(--weight-medium);padding:0;text-decoration-line:underline}.rm-fbar__action--save{color:var(--color-primary)}.rm-fbar__action--clear{color:var(--text-secondary)}
248
248
  </style>
@@ -18,17 +18,58 @@ const props = withDefaults(defineProps<{
18
18
  icon?: Component
19
19
  label: string
20
20
  options: FilterComplexOption[]
21
+ /** Langue du chrome du panneau : titres, recherches, actions. */
22
+ lang?: 'fr' | 'en'
23
+ /**
24
+ * La recherche est servie par l'appelant : la liste des options arrive
25
+ * deja filtree et le filtre local est retire. Indispensable des que le
26
+ * referentiel ne tient pas dans `options` (une recherche locale sur les
27
+ * cent premieres valeurs laisse croire que le reste n'existe pas).
28
+ */
29
+ remote?: boolean
21
30
  }>(), {
22
31
  icon: undefined,
32
+ lang: 'en',
33
+ remote: false,
23
34
  })
24
35
 
25
36
  const selected = defineModel<string[]>({ default: () => [] })
37
+ /**
38
+ * Texte saisi dans la colonne « ajouter ». Expose pour que l'appelant puisse
39
+ * aller chercher les options correspondantes, cf. `remote`.
40
+ */
41
+ const addSearch = defineModel<string>('search', { default: '' })
26
42
 
27
43
  const isOpen = ref(false)
28
44
  const wrapperRef = ref<HTMLElement>()
29
- const addSearch = ref('')
30
45
  const delSearch = ref('')
31
46
 
47
+ /** Le chrome du panneau, dans les deux langues. */
48
+ const WORDS = {
49
+ fr: {
50
+ custom: 'Sélection libre',
51
+ searchAdd: 'Rechercher pour ajouter...',
52
+ addAll: 'Tout ajouter',
53
+ current: 'Sélection en cours',
54
+ clear: 'Effacer',
55
+ searchSelection: 'Rechercher dans la sélection...',
56
+ noResult: 'Aucun résultat',
57
+ noSelection: 'Aucune sélection',
58
+ },
59
+ en: {
60
+ custom: 'Custom selection',
61
+ searchAdd: 'Search to add...',
62
+ addAll: 'Add all',
63
+ current: 'Current selection',
64
+ clear: 'Clear',
65
+ searchSelection: 'Search selection...',
66
+ noResult: 'No results',
67
+ noSelection: 'No selection',
68
+ },
69
+ } as const
70
+
71
+ const t = computed(() => WORDS[props.lang])
72
+
32
73
  const isActive = computed(() => selected.value.length > 0)
33
74
 
34
75
  const availableOptions = computed(() => {
@@ -36,14 +77,22 @@ const availableOptions = computed(() => {
36
77
  })
37
78
 
38
79
  const filteredAvailable = computed(() => {
39
- if (!addSearch.value) return availableOptions.value
80
+ if (props.remote || !addSearch.value) return availableOptions.value
40
81
  const q = addSearch.value.toLowerCase()
41
82
  return availableOptions.value.filter(o => o.label.toLowerCase().includes(q))
42
83
  })
43
84
 
44
- const selectedOptions = computed(() => {
45
- return props.options.filter(o => selected.value.includes(o.value))
46
- })
85
+ /**
86
+ * La selection se lit sur `selected`, pas sur `options` : une valeur choisie
87
+ * puis sortie de la liste (recherche suivante, referentiel pagine) doit rester
88
+ * visible et retirable, sinon elle filtre l'analyse sans plus apparaitre
89
+ * nulle part.
90
+ */
91
+ const selectedOptions = computed(() =>
92
+ selected.value.map(
93
+ value => props.options.find(o => o.value === value) ?? { label: value, value },
94
+ ),
95
+ )
47
96
 
48
97
  const filteredSelected = computed(() => {
49
98
  if (!delSearch.value) return selectedOptions.value
@@ -110,14 +159,14 @@ onUnmounted(() => document.removeEventListener('click', onClickOutside))
110
159
  <div v-if="isOpen" class="filter-complex__panel" @click.stop>
111
160
  <!-- Left: Add -->
112
161
  <div class="filter-complex__left">
113
- <h3 class="filter-complex__title">Custom selection</h3>
162
+ <h3 class="filter-complex__title">{{ t.custom }}</h3>
114
163
 
115
164
  <div class="filter-complex__search">
116
165
  <PhMagnifyingGlass :size="14" color="var(--text-muted)" />
117
166
  <input
118
167
  v-model="addSearch"
119
168
  type="text"
120
- placeholder="Search to add..."
169
+ :placeholder="t.searchAdd"
121
170
  class="filter-complex__search-input"
122
171
  >
123
172
  <PhX
@@ -133,7 +182,7 @@ onUnmounted(() => document.removeEventListener('click', onClickOutside))
133
182
  class="filter-complex__action filter-complex__action--add"
134
183
  @click="addAll"
135
184
  >
136
- Add all ({{ filteredAvailable.length }})
185
+ {{ t.addAll }} ({{ filteredAvailable.length }})
137
186
  </button>
138
187
 
139
188
  <div class="filter-complex__list">
@@ -147,7 +196,7 @@ onUnmounted(() => document.removeEventListener('click', onClickOutside))
147
196
  <PhPlusCircle :size="16" class="filter-complex__item-icon filter-complex__item-icon--add" />
148
197
  </button>
149
198
  <p v-if="filteredAvailable.length === 0" class="filter-complex__empty">
150
- No results
199
+ {{ t.noResult }}
151
200
  </p>
152
201
  </div>
153
202
  </div>
@@ -155,13 +204,13 @@ onUnmounted(() => document.removeEventListener('click', onClickOutside))
155
204
  <!-- Right: Selection -->
156
205
  <div class="filter-complex__right">
157
206
  <div class="filter-complex__right-header">
158
- <h3 class="filter-complex__title">Current selection</h3>
207
+ <h3 class="filter-complex__title">{{ t.current }}</h3>
159
208
  <button
160
209
  v-if="selected.length > 0"
161
210
  class="filter-complex__action filter-complex__action--clear"
162
211
  @click="clearAll"
163
212
  >
164
- Clear
213
+ {{ t.clear }}
165
214
  </button>
166
215
  </div>
167
216
 
@@ -170,7 +219,7 @@ onUnmounted(() => document.removeEventListener('click', onClickOutside))
170
219
  <input
171
220
  v-model="delSearch"
172
221
  type="text"
173
- placeholder="Search selection..."
222
+ :placeholder="t.searchSelection"
174
223
  class="filter-complex__search-input"
175
224
  >
176
225
  <PhX
@@ -192,7 +241,7 @@ onUnmounted(() => document.removeEventListener('click', onClickOutside))
192
241
  <PhMinusCircle :size="16" class="filter-complex__item-icon filter-complex__item-icon--remove" />
193
242
  </button>
194
243
  <p v-if="selected.length === 0" class="filter-complex__empty">
195
- No selection
244
+ {{ t.noSelection }}
196
245
  </p>
197
246
  </div>
198
247
  </div>
@@ -27,7 +27,9 @@ const props = withDefaults(defineProps<{
27
27
  axisLabels?: [string, string]
28
28
  /**
29
29
  * Etiquette chaque point de sa valeur, en pastille de la couleur de la
30
- * serie. A reserver aux series courtes, sinon les pastilles se chevauchent.
30
+ * serie. Laisse libre, elle se pose tant que le graphe reste aere (peu de
31
+ * courbes ET peu de points) et se retire au-dela, ou les pastilles se
32
+ * chevauchent. `true` ou `false` tranche a la place de la regle.
31
33
  */
32
34
  valueLabels?: boolean
33
35
  /**
@@ -55,7 +57,7 @@ const props = withDefaults(defineProps<{
55
57
  step: false,
56
58
  smooth: true,
57
59
  axisLabels: undefined,
58
- valueLabels: false,
60
+ valueLabels: undefined,
59
61
  hideValueAxis: false,
60
62
  legendPosition: 'bottom',
61
63
  startAtZero: false,
@@ -320,8 +320,8 @@ export declare function rmLineOption(categories: (string | number)[], series: Rm
320
320
  axisLabels?: [string, string];
321
321
  /**
322
322
  * Etiquette la valeur de chaque point, en pastille de la couleur de sa
323
- * serie. Reserve aux series courtes : au-dela d'une quinzaine de points
324
- * les pastilles se chevauchent et deviennent illisibles.
323
+ * serie. Non precise : automatique tant que le graphe reste aere (voir
324
+ * LABEL_MAX_SERIES et LABEL_MAX_POINTS).
325
325
  */
326
326
  valueLabels?: boolean;
327
327
  /**
@@ -527,7 +527,12 @@ export declare function rmLineOption(categories: (string | number)[], series: Rm
527
527
  type: string;
528
528
  name: string | undefined;
529
529
  yAxisIndex: 0 | 1 | undefined;
530
- data: (number | null)[];
530
+ data: (number | {
531
+ value: number;
532
+ label: {
533
+ offset: number[];
534
+ };
535
+ } | null)[];
531
536
  smooth: boolean;
532
537
  step: string | undefined;
533
538
  symbol: string;
@@ -166,12 +166,15 @@ export function rmLineOption(categories, series, theme, palette, options = {}) {
166
166
  smooth = true,
167
167
  decal = false,
168
168
  axisLabels,
169
- valueLabels = false,
169
+ valueLabels,
170
170
  hideValueAxis = false,
171
171
  legendPosition = "bottom",
172
172
  startAtZero = false,
173
173
  valueFormatter
174
174
  } = options;
175
+ const LABEL_MAX_SERIES = 5;
176
+ const LABEL_MAX_POINTS = 8;
177
+ const labelled = valueLabels ?? (series.length <= LABEL_MAX_SERIES && categories.length <= LABEL_MAX_POINTS);
175
178
  const AREA_MAX_SERIES = 5;
176
179
  const filled = area ?? series.length <= AREA_MAX_SERIES;
177
180
  const dual = series.some((s) => s.axis === 1);
@@ -202,7 +205,7 @@ export function rmLineOption(categories, series, theme, palette, options = {}) {
202
205
  right: dual ? 56 : 32,
203
206
  // Les pastilles du point le plus haut depassent la courbe : sans marge
204
207
  // supplementaire en haut, ECharts les rogne.
205
- top: dual ? 40 : valueLabels ? 32 : 24,
208
+ top: dual ? 40 : labelled ? 32 : 24,
206
209
  bottom: bottomLegend ? 56 : 36,
207
210
  containLabel: true
208
211
  },
@@ -219,18 +222,25 @@ export function rmLineOption(categories, series, theme, palette, options = {}) {
219
222
  type: "line",
220
223
  name: s.name,
221
224
  yAxisIndex: dual ? s.axis ?? 0 : void 0,
222
- data: s.data,
225
+ /*
226
+ Les points des deux bords sont poses SUR le cadre : leur pastille,
227
+ centree, mord sur la graduation a gauche et sort de la carte a
228
+ droite. On la decale vers l'interieur, elle reste rattachee a son
229
+ point par la proximite.
230
+ */
231
+ data: labelled ? s.data.map((value, position) => {
232
+ const edge = position === 0 ? 1 : position === s.data.length - 1 ? -1 : 0;
233
+ if (!edge || value === null || value === void 0) return value;
234
+ return { value, label: { offset: [edge * 16, 0] } };
235
+ }) : s.data,
223
236
  smooth: step ? false : smooth,
224
237
  step: step ? "end" : void 0,
225
238
  symbol: "circle",
226
- // La pastille de valeur marque deja le point, le symbole ferait un
227
- // second marqueur au meme endroit. On l'annule par sa taille et non
228
- // par `symbol: 'none'` : ECharts accroche les etiquettes aux
229
- // symboles, les supprimer supprime aussi les etiquettes.
230
239
  // Le point est la cible de survol : trop petit, l'infobulle se derobe
231
- // et les series courtes n'ont plus de reperes lisibles.
232
- symbolSize: valueLabels ? 0 : 8,
233
- label: valueLabels ? {
240
+ // et les series courtes n'ont plus de reperes lisibles. Il reste
241
+ // visible sous la pastille de valeur, qui se pose au-dessus de lui.
242
+ symbolSize: 8,
243
+ label: labelled ? {
234
244
  show: true,
235
245
  position: "top",
236
246
  distance: 6,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekkare/romulus",
3
- "version": "0.1.46",
3
+ "version": "0.1.48",
4
4
  "description": "Romulus Design System - Nuxt module by Tekkare",
5
5
  "license": "MIT",
6
6
  "type": "module",