@tekkare/romulus 0.1.59 → 0.1.61

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.58",
7
+ "version": "0.1.60",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
@@ -0,0 +1,84 @@
1
+ <!--
2
+ RmTagRow - une rangee d'etiquettes sur une seule ligne, le reste compte.
3
+
4
+ Reprend `arg-tags-row` d'Auriga : les etiquettes qui tiennent s'affichent, les
5
+ autres deviennent « +N » avec la liste complete en bulle. Une carte de
6
+ resultat porte parfois quinze laboratoires ; les laisser passer a la ligne
7
+ ferait grandir la carte au point de repousser ce qui suit.
8
+
9
+ Le tri se fait sur la position reelle des etiquettes dans le DOM : on les
10
+ pose toutes, puis on garde celles qui sont restees sur la premiere ligne.
11
+ Auriga mesurait le texte au canvas avec « 12px Figtree » ecrit en dur, ce qui
12
+ se decalait des que la police n'etait pas chargee ou pas celle-la.
13
+ -->
14
+ <script setup lang="ts">
15
+ import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
16
+ import type { RmTagVariant } from './Tag.vue'
17
+
18
+ const props = withDefaults(defineProps<{
19
+ tags: string[]
20
+ variant?: RmTagVariant
21
+ /** Cote ou sort la bulle du compteur. */
22
+ tooltipDir?: 'top' | 'right' | 'bottom' | 'left'
23
+ }>(), {
24
+ variant: 'gray',
25
+ tooltipDir: 'bottom',
26
+ })
27
+
28
+ const rowRef = ref<HTMLElement>()
29
+ const visibleCount = ref(props.tags.length)
30
+
31
+ const shown = computed(() => props.tags.slice(0, visibleCount.value))
32
+ const hidden = computed(() => props.tags.slice(visibleCount.value))
33
+
34
+ /**
35
+ * Compte les etiquettes restees sur la premiere ligne. La rangee est mesuree
36
+ * en affichage complet : sans cette remise a zero, un redimensionnement qui
37
+ * elargit la carte ne reafficherait jamais ce qui avait ete masque.
38
+ */
39
+ async function measure() {
40
+ visibleCount.value = props.tags.length
41
+ await nextTick()
42
+ const row = rowRef.value
43
+ if (!row) return
44
+ const items = [...row.querySelectorAll<HTMLElement>('[data-rm-tag]')]
45
+ if (items.length === 0) return
46
+
47
+ const firstTop = items[0].offsetTop
48
+ // Au moins une etiquette reste, meme trop large : « +15 » tout seul ne dit
49
+ // plus de quoi la carte parle.
50
+ let count = 1
51
+ for (let i = 1; i < items.length; i++) {
52
+ if (items[i].offsetTop > firstTop) break
53
+ count++
54
+ }
55
+ visibleCount.value = count
56
+ }
57
+
58
+ let observer: ResizeObserver | undefined
59
+
60
+ onMounted(() => {
61
+ measure()
62
+ if (typeof ResizeObserver !== 'undefined' && rowRef.value) {
63
+ observer = new ResizeObserver(() => measure())
64
+ observer.observe(rowRef.value)
65
+ }
66
+ })
67
+ onBeforeUnmount(() => observer?.disconnect())
68
+ watch(() => props.tags, measure, { deep: true })
69
+ </script>
70
+
71
+ <template>
72
+ <div class="rm-tag-row">
73
+ <div ref="rowRef" class="rm-tag-row__items">
74
+ <RmTag v-for="tag in shown" :key="tag" data-rm-tag :variant="variant">{{ tag }}</RmTag>
75
+ </div>
76
+ <RmTooltip v-if="hidden.length > 0" :text="hidden.join('\n')" :dir="tooltipDir" full>
77
+ <span class="rm-tag-row__more">+{{ hidden.length }}</span>
78
+ </RmTooltip>
79
+ </div>
80
+ </template>
81
+
82
+ <style scoped>
83
+ .rm-tag-row{align-items:center;display:flex;gap:8px;width:100%}.rm-tag-row__items{display:flex;flex:1;flex-wrap:wrap;gap:8px;max-height:24px;min-width:0;overflow:hidden}.rm-tag-row__more{color:var(--text-secondary);font-size:var(--font-12,12px);white-space:nowrap}
84
+ </style>
@@ -1,4 +1,4 @@
1
- import { ref } from "vue";
1
+ import { ref, toRaw } from "vue";
2
2
  const view = ref(null);
3
3
  export function useRmFilterBarView() {
4
4
  let mine = null;
@@ -9,7 +9,7 @@ export function useRmFilterBarView() {
9
9
  view.value = next;
10
10
  },
11
11
  clear: () => {
12
- if (view.value === mine) view.value = null;
12
+ if (view.value && toRaw(view.value) === mine) view.value = null;
13
13
  mine = null;
14
14
  }
15
15
  };
@@ -94,6 +94,48 @@ export declare function rmBaseOption(theme: RmChartTheme, decal?: boolean): {
94
94
  borderWidth: number;
95
95
  };
96
96
  };
97
+ export declare function rmLegend(theme: RmChartTheme, count: number, position?: 'top' | 'bottom'): {
98
+ pageIconSize?: number | undefined;
99
+ pageIconColor?: string | undefined;
100
+ pageIconInactiveColor?: string | undefined;
101
+ pageTextStyle?: {
102
+ color: string;
103
+ fontSize: number;
104
+ fontFamily: string;
105
+ } | undefined;
106
+ type: string;
107
+ tooltip: {
108
+ show: boolean;
109
+ };
110
+ formatter: (name: string) => string;
111
+ textStyle: {
112
+ color: string;
113
+ fontSize: number;
114
+ fontFamily: string;
115
+ };
116
+ bottom: number;
117
+ left: string;
118
+ } | {
119
+ pageIconSize?: number | undefined;
120
+ pageIconColor?: string | undefined;
121
+ pageIconInactiveColor?: string | undefined;
122
+ pageTextStyle?: {
123
+ color: string;
124
+ fontSize: number;
125
+ fontFamily: string;
126
+ } | undefined;
127
+ type: string;
128
+ tooltip: {
129
+ show: boolean;
130
+ };
131
+ formatter: (name: string) => string;
132
+ textStyle: {
133
+ color: string;
134
+ fontSize: number;
135
+ fontFamily: string;
136
+ };
137
+ top: number;
138
+ };
97
139
  /**
98
140
  * Axe de valeurs, avec son libelle d'unite au-dessus quand il est fourni.
99
141
  *
@@ -136,13 +178,46 @@ export interface RmSeriesInput {
136
178
  /** Bar chart vertical, une ou plusieurs series, empilable. */
137
179
  export declare function rmBarVerticalOption(categories: (string | number)[], series: RmSeriesInput[], theme: RmChartTheme, palette: string[], decal?: boolean): {
138
180
  legend: {
181
+ pageIconSize?: number | undefined;
182
+ pageIconColor?: string | undefined;
183
+ pageIconInactiveColor?: string | undefined;
184
+ pageTextStyle?: {
185
+ color: string;
186
+ fontSize: number;
187
+ fontFamily: string;
188
+ } | undefined;
189
+ type: string;
190
+ tooltip: {
191
+ show: boolean;
192
+ };
193
+ formatter: (name: string) => string;
194
+ textStyle: {
195
+ color: string;
196
+ fontSize: number;
197
+ fontFamily: string;
198
+ };
139
199
  bottom: number;
140
200
  left: string;
201
+ } | {
202
+ pageIconSize?: number | undefined;
203
+ pageIconColor?: string | undefined;
204
+ pageIconInactiveColor?: string | undefined;
205
+ pageTextStyle?: {
206
+ color: string;
207
+ fontSize: number;
208
+ fontFamily: string;
209
+ } | undefined;
210
+ type: string;
211
+ tooltip: {
212
+ show: boolean;
213
+ };
214
+ formatter: (name: string) => string;
141
215
  textStyle: {
142
216
  color: string;
143
217
  fontSize: number;
144
218
  fontFamily: string;
145
219
  };
220
+ top: number;
146
221
  } | undefined;
147
222
  grid: {
148
223
  bottom: number;
@@ -369,6 +444,19 @@ export declare function rmLineOption(categories: (string | number)[], series: Rm
369
444
  borderWidth: number;
370
445
  };
371
446
  legend: {
447
+ pageIconSize?: number | undefined;
448
+ pageIconColor?: string | undefined;
449
+ pageIconInactiveColor?: string | undefined;
450
+ pageTextStyle?: {
451
+ color: string;
452
+ fontSize: number;
453
+ fontFamily: string;
454
+ } | undefined;
455
+ type: string;
456
+ tooltip: {
457
+ show: boolean;
458
+ };
459
+ formatter: (name: string) => string;
372
460
  textStyle: {
373
461
  color: string;
374
462
  fontSize: number;
@@ -377,6 +465,19 @@ export declare function rmLineOption(categories: (string | number)[], series: Rm
377
465
  bottom: number;
378
466
  left: string;
379
467
  } | {
468
+ pageIconSize?: number | undefined;
469
+ pageIconColor?: string | undefined;
470
+ pageIconInactiveColor?: string | undefined;
471
+ pageTextStyle?: {
472
+ color: string;
473
+ fontSize: number;
474
+ fontFamily: string;
475
+ } | undefined;
476
+ type: string;
477
+ tooltip: {
478
+ show: boolean;
479
+ };
480
+ formatter: (name: string) => string;
380
481
  textStyle: {
381
482
  color: string;
382
483
  fontSize: number;
@@ -601,12 +702,46 @@ export declare function rmDonutOption(data: {
601
702
  borderWidth: number;
602
703
  };
603
704
  legend: {
705
+ pageIconSize?: number | undefined;
706
+ pageIconColor?: string | undefined;
707
+ pageIconInactiveColor?: string | undefined;
708
+ pageTextStyle?: {
709
+ color: string;
710
+ fontSize: number;
711
+ fontFamily: string;
712
+ } | undefined;
713
+ type: string;
714
+ tooltip: {
715
+ show: boolean;
716
+ };
717
+ formatter: (name: string) => string;
718
+ textStyle: {
719
+ color: string;
720
+ fontSize: number;
721
+ fontFamily: string;
722
+ };
604
723
  bottom: number;
724
+ left: string;
725
+ } | {
726
+ pageIconSize?: number | undefined;
727
+ pageIconColor?: string | undefined;
728
+ pageIconInactiveColor?: string | undefined;
729
+ pageTextStyle?: {
730
+ color: string;
731
+ fontSize: number;
732
+ fontFamily: string;
733
+ } | undefined;
734
+ type: string;
735
+ tooltip: {
736
+ show: boolean;
737
+ };
738
+ formatter: (name: string) => string;
605
739
  textStyle: {
606
740
  color: string;
607
741
  fontSize: number;
608
742
  fontFamily: string;
609
743
  };
744
+ top: number;
610
745
  };
611
746
  series: {
612
747
  type: string;
@@ -90,6 +90,27 @@ export function rmBaseOption(theme, decal = false) {
90
90
  }
91
91
  };
92
92
  }
93
+ const LEGEND_MAX_INLINE = 6;
94
+ const LEGEND_NAME_MAX = 38;
95
+ const LEGEND_NAME_TAIL = 12;
96
+ const rmLegendName = (name) => name.length > LEGEND_NAME_MAX ? `${name.slice(0, LEGEND_NAME_MAX - LEGEND_NAME_TAIL - 1)}\u2026${name.slice(-LEGEND_NAME_TAIL)}` : name;
97
+ export function rmLegend(theme, count, position = "bottom") {
98
+ const scroll = count > LEGEND_MAX_INLINE;
99
+ return {
100
+ ...position === "bottom" ? { bottom: 0, left: "center" } : { top: 0 },
101
+ type: scroll ? "scroll" : "plain",
102
+ // Le nom complet, non ecrete, au survol de l'entree.
103
+ tooltip: { show: true },
104
+ formatter: rmLegendName,
105
+ textStyle: { color: theme.mutedColor, fontSize: 11, fontFamily: FONT },
106
+ ...scroll ? {
107
+ pageIconSize: 10,
108
+ pageIconColor: theme.mutedColor,
109
+ pageIconInactiveColor: theme.axisLine,
110
+ pageTextStyle: { color: theme.mutedColor, fontSize: 11, fontFamily: FONT }
111
+ } : {}
112
+ };
113
+ }
93
114
  export function rmSeriesColor(palette, index) {
94
115
  const base = palette[index % palette.length];
95
116
  const cycle = Math.floor(index / palette.length);
@@ -120,7 +141,7 @@ const axisCommon = (theme) => ({
120
141
  export function rmBarVerticalOption(categories, series, theme, palette, decal = false) {
121
142
  return {
122
143
  ...rmBaseOption(theme, decal),
123
- legend: series.length > 1 ? { bottom: 0, left: "center", textStyle: { color: theme.mutedColor, fontSize: 11, fontFamily: FONT } } : void 0,
144
+ legend: series.length > 1 ? rmLegend(theme, series.length) : void 0,
124
145
  // La legende passee sous le graphe prend sa place dans la gouttiere basse,
125
146
  // sinon elle recouvre les libelles de l'axe des abscisses.
126
147
  grid: { ...rmBaseOption(theme, decal).grid, bottom: series.length > 1 ? 56 : 36 },
@@ -190,10 +211,7 @@ export function rmLineOption(categories, series, theme, palette, options = {}) {
190
211
  } : base.tooltip,
191
212
  // Legende obligatoire en double axe : sans elle, rien ne dit quelle courbe
192
213
  // se lit sur quel axe.
193
- legend: series.length > 1 || dual ? {
194
- ...bottomLegend ? { bottom: 0, left: "center" } : { top: 0 },
195
- textStyle: { color: theme.mutedColor, fontSize: 11, fontFamily: FONT }
196
- } : void 0,
214
+ legend: series.length > 1 || dual ? rmLegend(theme, series.length, legendPosition) : void 0,
197
215
  grid: {
198
216
  // Sans graduation a gauche, la gouttiere de 48 px ne sert plus qu'a
199
217
  // laisser depasser la premiere pastille de valeur.
@@ -275,7 +293,7 @@ export function rmDonutOption(data, theme, palette, decal = false) {
275
293
  ...rmBaseOption(theme, decal),
276
294
  grid: void 0,
277
295
  tooltip: { ...rmBaseOption(theme, decal).tooltip, trigger: "item" },
278
- legend: { bottom: 0, textStyle: { color: theme.mutedColor, fontSize: 11, fontFamily: FONT } },
296
+ legend: rmLegend(theme, data.length),
279
297
  series: [{
280
298
  type: "pie",
281
299
  radius: ["45%", "70%"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekkare/romulus",
3
- "version": "0.1.59",
3
+ "version": "0.1.61",
4
4
  "description": "Romulus Design System - Nuxt module by Tekkare",
5
5
  "license": "MIT",
6
6
  "type": "module",