@cityway/bo-ui 1.0.10-beta027 → 1.1.0-beta002

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.
Binary file
Binary file
@@ -0,0 +1,151 @@
1
+ # `cw-custom-list` — Liste descriptive
2
+
3
+ > Lib : `@cityway/bo-ui` · Sélecteur : `cw-custom-list` · Standalone : oui
4
+ > Import : `import { CustomListComponent } from '@cityway/bo-ui';`
5
+
6
+ ---
7
+
8
+ ## 1. Rôle & quand l'utiliser
9
+
10
+ Liste descriptive en lecture seule : affiche une suite de couples **libellé / valeur**, organisés en **groupes** optionnellement titrés, dans une carte. Couvre :
11
+
12
+ - l'affichage d'une fiche de **détail** (récapitulatif d'une entité) ;
13
+ - le rendu **typé** des valeurs : texte, date, booléen (Oui/Non), HTML ;
14
+ - le **masquage automatique** des champs vides (`hideIfEmpty`) ;
15
+ - la **traduction** des libellés, et optionnellement des valeurs (`translateValue`).
16
+
17
+ À ne PAS utiliser pour : un tableau de données paginé/triable (utiliser `cw-table`), un formulaire éditable (utiliser `cw-form-field`).
18
+
19
+ ---
20
+
21
+ ## 2. API
22
+
23
+ ### Inputs
24
+
25
+ | Input | Type | Défaut | Description |
26
+ |-------|------|--------|-------------|
27
+ | `customListData` | `DataCustomListGroup[]` | `[]` | Signal input (`input()`). Liste des groupes à afficher. Chaque groupe contient un titre optionnel et un tableau de champs. Voir types §3. |
28
+
29
+ > Composant en lecture seule : **aucun Output**.
30
+
31
+ ### Méthode interne
32
+
33
+ | Méthode | Signature | Description |
34
+ |---------|-----------|-------------|
35
+ | `hasValue` | `(value: any): boolean` | Utilisée par le template pour `hideIfEmpty`. Renvoie `false` pour `null`/`undefined`, chaîne vide (après `trim`), tableau vide, objet sans clés. |
36
+
37
+ ### Contenu projeté
38
+
39
+ Aucun (`ng-content`). Tout le rendu est piloté par `customListData`.
40
+
41
+ ### Rendu par type de champ (`field.type`)
42
+
43
+ | `type` | Rendu |
44
+ |--------|-------|
45
+ | `'date'` | `field.value` via le pipe `date: 'short'`. |
46
+ | `'boolean'` | clé i18n `_COMMON._PRO_SENTENCE.YES` / `.NO` selon la valeur, via `translate`. |
47
+ | `'html'` | `field.value` injecté tel quel via `[innerHTML]`. |
48
+ | `'text'` / absent (défaut) | `field.value` brut ; traduit via `translate` si `translateValue` est `true`. |
49
+
50
+ > Le **libellé** de chaque champ est rendu via la clé i18n `_LABEL.COLON` (interpolation `{ label }`), le `label` lui-même étant d'abord passé au pipe `translate`.
51
+
52
+ ---
53
+
54
+ ## 3. Types
55
+
56
+ ```ts
57
+ interface DataCustomListGroup {
58
+ title?: string; // clé i18n du titre de groupe (passée à translate) ; titre masqué si absent
59
+ fields: DataCustomList[];
60
+ }
61
+
62
+ interface DataCustomList {
63
+ label: string; // clé i18n du libellé (passée à translate)
64
+ value: any; // valeur affichée (string, Date, boolean, HTML…)
65
+ type?: 'date' | 'boolean' | 'text' | 'html'; // mode de rendu ; défaut = texte brut
66
+ translateValue?: boolean; // si true et type texte : value est une clé i18n (passée à translate)
67
+ context?: any; // champ libre (non utilisé par le rendu actuel)
68
+ hideIfEmpty?: boolean; // si true : champ masqué quand hasValue(value) est false
69
+ }
70
+ ```
71
+
72
+ > ℹ️ `context` est présent dans le modèle mais **non consommé** par le template actuel.
73
+
74
+ ---
75
+
76
+ ## 4. Exemples
77
+
78
+ ### Fiche de détail (groupes titrés, types variés)
79
+
80
+ ```html
81
+ <cw-custom-list [customListData]="details"></cw-custom-list>
82
+ ```
83
+
84
+ ```ts
85
+ import { CustomListComponent, DataCustomListGroup } from '@cityway/bo-ui';
86
+
87
+ // imports: [CustomListComponent]
88
+
89
+ details: DataCustomListGroup[] = [
90
+ {
91
+ title: '_SECTION.IDENTITY',
92
+ fields: [
93
+ { label: '_LABEL.LAST_NAME', value: 'Dupont', type: 'text' },
94
+ { label: '_LABEL.BIRTHDATE', value: new Date('1985-06-15'), type: 'date' },
95
+ { label: '_LABEL.STATUS', value: true, type: 'boolean' },
96
+ ],
97
+ },
98
+ ];
99
+ ```
100
+
101
+ ### Champs masqués si vides
102
+
103
+ ```ts
104
+ details: DataCustomListGroup[] = [
105
+ {
106
+ title: '_SECTION.IDENTITY',
107
+ fields: [
108
+ { label: '_LABEL.LAST_NAME', value: 'Martin', type: 'text' },
109
+ { label: '_LABEL.FIRST_NAME', value: '', type: 'text', hideIfEmpty: true }, // masqué
110
+ { label: '_LABEL.EMAIL', value: null, type: 'text', hideIfEmpty: true }, // masqué
111
+ ],
112
+ },
113
+ ];
114
+ ```
115
+
116
+ ### Valeur traduite (clé i18n en valeur)
117
+
118
+ ```ts
119
+ { label: '_LABEL.ROLE', value: '_ROLE.ADMIN', type: 'text', translateValue: true }
120
+ ```
121
+
122
+ ### Valeur HTML
123
+
124
+ ```ts
125
+ { label: '_LABEL.NOTES', value: '<strong>Note</strong> : à rappeler.', type: 'html' }
126
+ ```
127
+
128
+ ---
129
+
130
+ ## 5. Notes d'intégration
131
+
132
+ - **i18n** : `group.title` et `field.label` passent par `translate` → fournir des **clés**, pas du texte en dur. Idem `field.value` quand `translateValue` est `true`.
133
+ - **Clés requises** : le template s'appuie sur `_LABEL.COLON` (format « libellé : ») et, pour les booléens, sur `_COMMON._PRO_SENTENCE.YES` / `_COMMON._PRO_SENTENCE.NO` — ces clés doivent exister dans les fichiers de traduction de l'app.
134
+ - **Sécurité HTML** : `type: 'html'` injecte la valeur via `[innerHTML]` → ne jamais y mettre de contenu utilisateur non assaini.
135
+ - **Titre de groupe** : un groupe sans `title` n'affiche pas de `<h3>` mais rend bien ses champs.
136
+ - **Signal input** : `customListData` est un `input()` (Angular signals) → binder via `[customListData]="..."`.
137
+
138
+ ---
139
+
140
+ ## 6. Prompts-types (génération assistée)
141
+
142
+ > Exemples de formulation pour demander à Claude de générer du code consommant ce composant.
143
+
144
+ - « Affiche une fiche de détail *Identité* (nom, prénom, date de naissance, statut actif/inactif) avec `cw-custom-list`, libellés en clés i18n. »
145
+ - « Ajoute un groupe *Contact* avec email et téléphone, en masquant les champs vides. »
146
+ - « Rends une valeur de rôle traduite (clé i18n) et une note en HTML dans la liste descriptive. »
147
+
148
+ **Rappels pour Claude :**
149
+ - Importer `CustomListComponent` (standalone) dans les `imports` du composant hôte.
150
+ - Typer les données avec `DataCustomListGroup[]` ; binder via `[customListData]`.
151
+ - Utiliser des clés i18n pour `title`, `label` (et `value` si `translateValue: true`) ; vérifier la présence des clés `_LABEL.COLON` et `_COMMON._PRO_SENTENCE.YES/NO`.
@@ -0,0 +1,229 @@
1
+ # `cw-drag-n-drop-list` — Liste à glisser-déposer (deux colonnes)
2
+
3
+ > Lib : `@cityway/bo-ui` · Sélecteur : `cw-drag-n-drop-list` · Standalone : oui
4
+ > Import : `import { DragNDropListComponent } from '@cityway/bo-ui';`
5
+
6
+ ---
7
+
8
+ ## 1. Rôle & quand l'utiliser
9
+
10
+ Composant de répartition d'items entre **deux colonnes** (« Disponibles » / « Activés ») par glisser-déposer, avec réordonnancement intra-colonne et transfert inter-colonnes. Couvre :
11
+
12
+ - déplacer un item d'une colonne à l'autre (transfert) ;
13
+ - réordonner les items au sein d'une même colonne ;
14
+ - **renumérotation automatique** de l'ordre au drop (`reorderOnDrop`) ;
15
+ - items **non-draggables** (`disabled` par item) ;
16
+ - **rendu personnalisé** de chaque carte via un `ng-template` projeté ;
17
+ - branchement sur une source `DragItem[]` brute **ou** sur un `FormArray` réactif, via un **adaptateur**.
18
+
19
+ À ne PAS utiliser pour : une arborescence à plusieurs niveaux avec actions/badges par carte (utiliser `cw-drag-n-drop-nested-list`) ; un simple tri d'une seule liste (un `cdkDropList` direct suffit).
20
+
21
+ ---
22
+
23
+ ## 2. API
24
+
25
+ ### Inputs (signal inputs)
26
+
27
+ | Input | Type | Défaut | Description |
28
+ |-------|------|--------|-------------|
29
+ | `availableItems` | `any` (source colonne gauche) | `undefined` | Source de la colonne « Disponibles ». Type libre : `DragItem[]` avec `ArrayAdapter`, `FormArray` avec `FormArrayAdapter`. |
30
+ | `activeItems` | `any` (source colonne droite) | `undefined` | Source de la colonne « Activés ». Même typage que `availableItems`. |
31
+ | `adapter` | `DragDropAdapter` | `new ArrayAdapter()` | Convertit la source ↔ `DragItem[]` interne. Voir §3. |
32
+ | `itemTemplate` | `TemplateRef<any> \| undefined` | `undefined` | Gabarit de rendu d'une carte. Contexte `$implicit` = le `DragItem`. Si absent, gabarit par défaut (label + code + #ordre). |
33
+ | `availableColumn` | `DragListColumn` | `{ title: 'Disponibles' }` | Configuration de la colonne gauche (titre, descriptions, classe CSS). |
34
+ | `activeColumn` | `DragListColumn` | `{ title: 'Activés' }` | Configuration de la colonne droite. |
35
+ | `reorderOnDrop` | `boolean` | `false` | Si `true`, recalcule `order = index + 1` sur les deux colonnes à chaque drop. |
36
+
37
+ ### Outputs
38
+
39
+ | Output | Payload | Description |
40
+ |--------|---------|-------------|
41
+ | `availableItemsChange` | `any` (la source `availableItems`) | Émis après un drop, avec la source gauche mise à jour par l'adaptateur. |
42
+ | `activeItemsChange` | `any` (la source `activeItems`) | Émis après un drop, avec la source droite mise à jour par l'adaptateur. |
43
+
44
+ > Le composant ne fait pas de `two-way binding` automatique : les sorties émettent la **même référence** de source mutée en place par l'adaptateur. Brancher `(availableItemsChange)` / `(activeItemsChange)` pour réagir (persistance, recalcul).
45
+
46
+ ### Contenu projeté (gabarit de carte)
47
+
48
+ Le rendu d'une carte est délégué à `itemTemplate`. Chaque carte est enveloppée dans un `cw-card` (`@cityway/basic-ui`). Le template reçoit le `DragItem` courant en `$implicit` :
49
+
50
+ ```html
51
+ <cw-drag-n-drop-list [itemTemplate]="tpl" ...>
52
+ </cw-drag-n-drop-list>
53
+
54
+ <ng-template #tpl let-item>
55
+ <div class="d-flex justify-content-between w-100">
56
+ <span>{{ item.label }} <span class="text-muted">({{ item.code }})</span></span>
57
+ <span class="fw-bold">#{{ item.order }}</span>
58
+ </div>
59
+ </ng-template>
60
+ ```
61
+
62
+ Gabarit par défaut (si `itemTemplate` non fourni) : affiche `item.label`, `(item.code)` si présent, et `#item.order` si `order != null`.
63
+
64
+ ### Configuration de colonne (`DragListColumn`)
65
+
66
+ Les champs effectivement consommés par ce composant :
67
+
68
+ | Champ | Type | Description |
69
+ |-------|------|-------------|
70
+ | `title` | `string` | Titre de la colonne. Passé au pipe `translate` → **clé i18n**. |
71
+ | `description` | `string` | Paragraphe sous le titre. Passé au pipe `translate`. |
72
+ | `descriptionRwd` | `string` | Variante affichée uniquement en mobile (`d-sm-none`), rendue via `[innerHTML]`. Passé au pipe `translate`. |
73
+ | `cssClass` | `string` | Classe(s) CSS appliquée(s) à la `<ul>` de la colonne (`ngClass`). |
74
+
75
+ > `DragListColumn` déclare d'autres champs (`collapsible`, `showView/Edit/Delete/Add`, `maxItemsByDepth`, etc.) destinés à la variante **nested-list** ; ils ne sont **pas** lus par `cw-drag-n-drop-list`.
76
+
77
+ ### Modèle d'item (`DragItem`) — champs utilisés ici
78
+
79
+ | Champ | Type | Description |
80
+ |-------|------|-------------|
81
+ | `id` | `number \| string` | Identité (clé `track` du `@for`). Obligatoire. |
82
+ | `label` | `string` | Libellé (gabarit par défaut). |
83
+ | `code` | `string` | Code optionnel affiché entre parenthèses (gabarit par défaut). |
84
+ | `order` | `number` | Ordre affiché `#N` ; recalculé si `reorderOnDrop`. |
85
+ | `disabled` | `boolean` | Si `true`, carte non-draggable (`cdkDragDisabled`). |
86
+
87
+ > `DragItem` porte beaucoup d'autres champs (`title`, `subtitle`, `badge`, `children`, `actions`, `selected`, `pendingDelete`…) exploités par la variante **nested-list**, ignorés ici.
88
+
89
+ ---
90
+
91
+ ## 3. Adaptateurs (`DragDropAdapter`)
92
+
93
+ L'adaptateur découple la source du consommateur de la représentation interne `DragItem[]`.
94
+
95
+ ```ts
96
+ interface DragDropAdapter<T = any> {
97
+ toDragItems(source: T): DragItem[];
98
+ fromDragItems(items: DragItem[], source: T, peers?: T[]): void;
99
+ }
100
+ ```
101
+
102
+ - **`ArrayAdapter`** (défaut) — la source EST déjà un `DragItem[]`. Mutation in-place via `splice`.
103
+ - **`FormArrayAdapter`** — la source est un `FormArray`. Construit au moyen de :
104
+ - `mapper: (ctrl, index) => DragItem` — projette chaque `AbstractControl` en `DragItem` ;
105
+ - `reorderFn?: (ctrl, newOrder) => void` — applique le nouvel ordre sur le control (appelé si présent, à chaque repositionnement). Gère le transfert inter-`FormArray` (déplacement réel du control entre tableaux, sans `emitEvent`).
106
+
107
+ ---
108
+
109
+ ## 4. Exemples
110
+
111
+ ### Cas simple (`DragItem[]` + `ArrayAdapter`)
112
+
113
+ ```html
114
+ <cw-drag-n-drop-list
115
+ [availableItems]="available"
116
+ [activeItems]="active"
117
+ [reorderOnDrop]="true"
118
+ [availableColumn]="{ title: 'TRANSPORT.AVAILABLE_MODES' }"
119
+ [activeColumn]="{ title: 'TRANSPORT.ACTIVE_MODES' }"
120
+ (availableItemsChange)="onAvailableChange($event)"
121
+ (activeItemsChange)="onActiveChange($event)">
122
+ </cw-drag-n-drop-list>
123
+ ```
124
+
125
+ ```ts
126
+ import { DragNDropListComponent, DragItem } from '@cityway/bo-ui';
127
+
128
+ available: DragItem[] = [
129
+ { id: 1, label: 'Vélo', code: 'BIKE', order: 1 },
130
+ { id: 2, label: 'Bus', code: 'BUS', order: 2 },
131
+ ];
132
+ active: DragItem[] = [
133
+ { id: 3, label: 'Tramway', code: 'TRAM', order: 1 },
134
+ ];
135
+
136
+ onAvailableChange(items: DragItem[]) { /* persister */ }
137
+ onActiveChange(items: DragItem[]) { /* persister */ }
138
+ ```
139
+
140
+ ### Items non-draggables
141
+
142
+ ```ts
143
+ available: DragItem[] = [
144
+ { id: 1, label: 'Vélo', code: 'BIKE', order: 1 },
145
+ { id: 2, label: 'Bus', code: 'BUS', order: 2, disabled: true },
146
+ ];
147
+ ```
148
+
149
+ ### Gabarit de carte personnalisé
150
+
151
+ ```html
152
+ <cw-drag-n-drop-list
153
+ [availableItems]="available"
154
+ [activeItems]="active"
155
+ [itemTemplate]="tpl"
156
+ (activeItemsChange)="onActiveChange($event)">
157
+ </cw-drag-n-drop-list>
158
+
159
+ <ng-template #tpl let-item>
160
+ <div class="d-flex justify-content-between w-100">
161
+ <span><strong>{{ item.label }}</strong></span>
162
+ <span class="text-muted">({{ item.code }}) — #{{ item.order }}</span>
163
+ </div>
164
+ </ng-template>
165
+ ```
166
+
167
+ ### Source `FormArray` (adaptateur dédié)
168
+
169
+ ```ts
170
+ import { FormArray, FormControl, FormGroup, AbstractControl } from '@angular/forms';
171
+ import { FormArrayAdapter, DragItem } from '@cityway/bo-ui';
172
+
173
+ available = new FormArray([
174
+ new FormGroup({ mode: new FormControl('BIKE'), order: new FormControl(1) }),
175
+ new FormGroup({ mode: new FormControl('BUS'), order: new FormControl(2) }),
176
+ ]);
177
+ active = new FormArray([
178
+ new FormGroup({ mode: new FormControl('TRAM'), order: new FormControl(1) }),
179
+ ]);
180
+
181
+ adapter = new FormArrayAdapter(
182
+ (ctrl: AbstractControl): DragItem => ({
183
+ id: ctrl.get('mode')!.value,
184
+ label: ctrl.get('mode')!.value,
185
+ code: ctrl.get('mode')!.value,
186
+ order: ctrl.get('order')!.value,
187
+ }),
188
+ (ctrl, newOrder) => ctrl.get('order')!.setValue(newOrder, { emitEvent: false }),
189
+ );
190
+ ```
191
+
192
+ ```html
193
+ <cw-drag-n-drop-list
194
+ [availableItems]="available"
195
+ [activeItems]="active"
196
+ [adapter]="adapter"
197
+ [reorderOnDrop]="true"
198
+ (activeItemsChange)="onActiveChange($event)">
199
+ </cw-drag-n-drop-list>
200
+ ```
201
+
202
+ ---
203
+
204
+ ## 5. Notes d'intégration
205
+
206
+ - **Dépendance `@angular/cdk/drag-drop`** : le composant importe `DragDropModule` (standalone) et utilise `cdkDropList` connectés (`cdkDropListConnectedTo`), `moveItemInArray`, `transferArrayItem`. Aucune configuration côté hôte requise — l'import du composant suffit.
207
+ - **Source mutée en place** : l'adaptateur modifie la source d'origine (`splice` pour `ArrayAdapter`, `push/removeAt` pour `FormArrayAdapter`). Les outputs ré-émettent cette même référence. Ne pas se reposer sur l'immuabilité.
208
+ - **`effect` de synchro** : les copies internes (`_available` / `_active`) sont reconstruites via un `effect` quand les inputs `availableItems` / `activeItems` ou `adapter` changent. Remplacer la référence source pour forcer une resynchro.
209
+ - **i18n** : `title`, `description`, `descriptionRwd` des colonnes passent par `translate` → fournir des **clés**, pas du texte en dur. Les libellés d'items (`label`, `code`) du gabarit par défaut ne sont **pas** traduits (texte brut) — gérer la traduction dans un gabarit custom si besoin.
210
+ - **`descriptionRwd`** est injectée via `[innerHTML]` (mobile uniquement) : n'y mettre que du contenu de confiance.
211
+ - **`track`** repose sur `item.id` : garantir l'unicité des `id` au sein des deux colonnes réunies.
212
+ - **`reorderOnDrop`** ne renumérote que la propriété `order` du `DragItem` ; avec `FormArrayAdapter`, fournir `reorderFn` pour répercuter sur le control.
213
+
214
+ ---
215
+
216
+ ## 6. Prompts-types (génération assistée)
217
+
218
+ > Exemples de formulation pour demander à Claude de générer du code consommant ce composant.
219
+
220
+ - « Crée une répartition de modes de transport en deux colonnes *Disponibles* / *Activés* (clés i18n `TRANSPORT.AVAILABLE_MODES` / `TRANSPORT.ACTIVE_MODES`), avec renumérotation automatique au drop, branchée sur deux `DragItem[]`. »
221
+ - « Branche ce drag-n-drop-list sur un `FormArray` réactif via `FormArrayAdapter` : mappe chaque `FormGroup` (champs `mode`, `order`) en `DragItem` et répercute le nouvel ordre dans le control. »
222
+ - « Ajoute un gabarit de carte personnalisé affichant le libellé en gras à gauche et le code + l'ordre à droite. »
223
+
224
+ **Rappels pour Claude :**
225
+ - Importer `DragNDropListComponent` (standalone) dans les `imports` du composant hôte.
226
+ - Importer les types `DragItem`, `DragListColumn` et l'adaptateur (`ArrayAdapter` ou `FormArrayAdapter`) depuis `@cityway/bo-ui`.
227
+ - Garantir des `id` uniques entre les deux colonnes (clé de `track`).
228
+ - Brancher `(availableItemsChange)` / `(activeItemsChange)` pour la persistance ; ne pas supposer l'immuabilité des sources.
229
+ - Pour une arborescence multi-niveaux avec actions/badges, utiliser plutôt `cw-drag-n-drop-nested-list`.