@tekkare/romulus 0.1.227 → 0.1.229

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.226",
7
+ "version": "0.1.228",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
@@ -109,6 +109,24 @@ function toggle(which: RmFilterBarPanel) {
109
109
  panel.value = panel.value === which ? null : which
110
110
  }
111
111
 
112
+ /**
113
+ * Un clic hors de la barre referme le panneau ouvert. Les menus des listes
114
+ * deroulantes, les popovers et les modales sont teleportes sous <body>, hors
115
+ * de l'arbre de l'app : un clic dedans appartient encore au panneau (choisir
116
+ * une option ne doit pas le fermer), on ne ferme donc que pour un clic tombe
117
+ * dans le meme conteneur racine que la barre.
118
+ */
119
+ const root = ref<HTMLElement | null>(null)
120
+
121
+ function onDocPointerDown(event: PointerEvent) {
122
+ if (!panel.value || !root.value) return
123
+ const target = event.target as Node
124
+ if (root.value.contains(target)) return
125
+ let appRoot: HTMLElement = root.value
126
+ while (appRoot.parentElement && appRoot.parentElement !== document.body) appRoot = appRoot.parentElement
127
+ if (appRoot.contains(target)) panel.value = null
128
+ }
129
+
112
130
  /**
113
131
  * Le lien de recherche avancee est pose DANS le champ, a son bout : le champ
114
132
  * doit donc lui reserver sa place, sinon le texte saisi et la croix passent
@@ -157,6 +175,7 @@ function watchHeader() {
157
175
  onMounted(() => {
158
176
  watchHeader()
159
177
  watchAdvanced()
178
+ document.addEventListener('pointerdown', onDocPointerDown)
160
179
  })
161
180
 
162
181
  onBeforeUnmount(() => {
@@ -164,12 +183,14 @@ onBeforeUnmount(() => {
164
183
  headerSize = null
165
184
  advancedSize?.disconnect()
166
185
  advancedSize = null
186
+ document.removeEventListener('pointerdown', onDocPointerDown)
167
187
  })
168
188
  </script>
169
189
 
170
190
  <template>
171
191
  <div
172
192
  id="rm_filter_bar"
193
+ ref="root"
173
194
  class="rm-fbar"
174
195
  :class="panel ? 'rm-fbar--open' : 'rm-fbar--closed'"
175
196
  :style="{ top: `${stickyTop}px`, '--rm-fbar-advanced-w': `${advancedWidth}px` }"
@@ -26,6 +26,7 @@ import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
26
26
  import { PhCaretDown, PhCaretRight, PhCheck, PhMagnifyingGlass, PhMinus } from '@phosphor-icons/vue'
27
27
  import type { Component } from 'vue'
28
28
  import { rmPanelOpened, rmPanelRegister } from '../utils/panels'
29
+ import { rmTreeCommit, rmTreeSettle } from '../utils/filterTree'
29
30
 
30
31
  export interface RmTreeNode {
31
32
  /** Ce qui part dans la selection. Unique dans tout l'arbre. */
@@ -161,7 +162,20 @@ const index = computed(() => {
161
162
  const draft = ref(new Set<string>())
162
163
  const expanded = ref(new Set<string>())
163
164
 
165
+ /**
166
+ * « Tout deselectionner » vide aussi ce que l'arbre ne montre pas : le
167
+ * prochain `commit` ne garde alors rien de cache (cf. rmTreeCommit).
168
+ */
169
+ let dropHidden = false
170
+
171
+ /**
172
+ * L'arbre ne montre qu'une partie de la taxonomie : l'appelant cherche cote
173
+ * serveur et ne passe que les noeuds trouves (cf. `filterSearch`).
174
+ */
175
+ const partial = computed(() => !props.filterSearch && search.value.trim() !== '')
176
+
164
177
  function resetDraft() {
178
+ dropHidden = false
165
179
  draft.value = new Set(selected.value)
166
180
  // Les branches qui portent la selection s'ouvrent : arriver sur un arbre
167
181
  // replie alors qu'il est filtre ne dit pas sur quoi.
@@ -203,12 +217,7 @@ const settleOrder = computed(() =>
203
217
  )
204
218
 
205
219
  function settle() {
206
- settleOrder.value.forEach(({ node }) => {
207
- if (!node.children?.length) return
208
- const all = node.children.every(child => draft.value.has(child.value))
209
- if (all) draft.value.add(node.value)
210
- else draft.value.delete(node.value)
211
- })
220
+ rmTreeSettle(settleOrder.value, draft.value, partial.value)
212
221
  }
213
222
 
214
223
  function toggle(value: string) {
@@ -368,6 +377,7 @@ watch(isOpen, async (open) => {
368
377
 
369
378
  function clear() {
370
379
  draft.value = new Set()
380
+ dropHidden = true
371
381
  }
372
382
 
373
383
  /**
@@ -376,7 +386,8 @@ function clear() {
376
386
  * selections identiques donnent la meme liste.
377
387
  */
378
388
  function commit() {
379
- const values = [...index.value.keys()].filter(value => draft.value.has(value))
389
+ const values = rmTreeCommit(index.value.keys(), draft.value, selected.value, !dropHidden)
390
+ dropHidden = false
380
391
  selected.value = values
381
392
  emit('apply', values)
382
393
  }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Selection de RmFilterTree, hors du composant pour pouvoir la tester.
3
+ *
4
+ * Deux regles comptent quand l'arbre affiche n'est qu'une partie de la
5
+ * taxonomie, ce qui arrive des que l'appelant cherche cote serveur
6
+ * (`filterSearch` a faux) : il ne passe alors que les noeuds trouves.
7
+ */
8
+ /** Un noeud de l'index, reduit a ce que la remontee lit. */
9
+ export interface RmTreeSettleEntry {
10
+ node: {
11
+ value: string;
12
+ children?: {
13
+ value: string;
14
+ }[];
15
+ };
16
+ }
17
+ /**
18
+ * La selection a ecrire dans le modele.
19
+ *
20
+ * Les valeurs affichees suivent le brouillon, dans l'ordre de l'arbre. Celles
21
+ * que l'arbre ne montre pas (cochees lors d'une recherche precedente) sont
22
+ * gardees telles quelles : l'arbre ne sait rien d'elles, il n'a pas a les
23
+ * retirer. Sans cette regle, cocher un code apres une nouvelle recherche
24
+ * effacait tous ceux choisis avant.
25
+ *
26
+ * `keepHidden` a faux pour « Tout deselectionner », qui vide tout.
27
+ */
28
+ export declare function rmTreeCommit(shown: Iterable<string>, draft: Set<string>, previous: string[], keepHidden?: boolean): string[];
29
+ /**
30
+ * Remonte la coherence apres un clic : un parent est decoche des qu'il lui
31
+ * manque un enfant, et coche quand il les a tous.
32
+ *
33
+ * `partial` : l'arbre ne montre qu'une partie des enfants (recherche cote
34
+ * serveur). Avoir coche tous les enfants AFFICHES ne veut alors pas dire avoir
35
+ * coche la branche : le parent n'est plus coche d'office, sans quoi chercher
36
+ * « C500 » et le cocher cochait aussi « C50 », et la selection annoncait tout
37
+ * le chapitre. Cocher le parent lui-meme reste possible.
38
+ */
39
+ export declare function rmTreeSettle(order: RmTreeSettleEntry[], draft: Set<string>, partial?: boolean): void;
@@ -0,0 +1,14 @@
1
+ export function rmTreeCommit(shown, draft, previous, keepHidden = true) {
2
+ const known = new Set(shown);
3
+ const hidden = keepHidden ? previous.filter((value) => !known.has(value)) : [];
4
+ const kept = [...known].filter((value) => draft.has(value));
5
+ return [...hidden, ...kept.filter((value) => !hidden.includes(value))];
6
+ }
7
+ export function rmTreeSettle(order, draft, partial = false) {
8
+ order.forEach(({ node }) => {
9
+ if (!node.children?.length) return;
10
+ const all = node.children.every((child) => draft.has(child.value));
11
+ if (!all) draft.delete(node.value);
12
+ else if (!partial) draft.add(node.value);
13
+ });
14
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekkare/romulus",
3
- "version": "0.1.227",
3
+ "version": "0.1.229",
4
4
  "description": "Romulus Design System - Nuxt module by Tekkare",
5
5
  "license": "MIT",
6
6
  "type": "module",