@cityway/bo-ui 1.1.0-beta010 → 1.1.0-beta013

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
@@ -29,11 +29,11 @@ Liste hiérarchique réordonnable par glisser-déposer (CDK drag-drop), pilotée
29
29
  | Input | Type | Défaut | Description |
30
30
  |-------|------|--------|-------------|
31
31
  | `items` | `DragItem[]` | `[]` | Arbre des items à afficher. Le composant en fait une **copie interne mutable** sur laquelle le CDK opère. Voir §3. |
32
- | `itemTemplate` | `TemplateRef<any> \| undefined` | `undefined` | Template du **corps** de carte (slot). Reçoit `$implicit = item` et `depth`. Si absent, fallback interne (label + code). |
32
+ | `itemTemplate` | `TemplateRef<any> \| undefined` | `undefined` | Template du **corps** de carte (slot). Reçoit `$implicit = item` et `depth`. Si absent, **aucun corps n'est rendu** — sauf `item.code`, affiché seul (le titre de l'en-tête porte déjà `title ?? label`). |
33
33
  | `column` | `DragListColumn \| undefined` | `undefined` | Configuration de la colonne : titre, défauts de visibilité des actions, libellés, plafonds. Voir §3. |
34
34
  | `reorderOnDrop` | `boolean` | `true` | Renumérote `order` (1..n par niveau) récursivement à chaque drop. `false` = déplacement visuel seul, `order` intact. |
35
35
  | `maxDepth` | `number \| undefined` | `undefined` | Profondeur max (0-based, racine = 0). Une carte à `depth < maxDepth` peut recevoir des enfants ; `>= maxDepth` est terminale. `undefined` = imbrication infinie. Ex. `maxDepth = 1` → 2 niveaux. |
36
- | `selectedId` | `number \| string \| undefined` | `undefined` | Sélection **exclusive** gérée par la lib : la carte dont `id === selectedId` reçoit la classe `ddn-list-card-selected`. Une seule carte à la fois, tout niveau confondu. Le flag `DragItem.selected` reste supporté en parallèle. |
36
+ | `selectedId` | `number \| string \| undefined` | `undefined` | Sélection **exclusive** gérée par la lib : la carte dont `id === selectedId` reçoit la classe `ddn-list-card-selected` — **elle seule**, jamais ses parents (voir §5, « Rendu de la sélection en profondeur »). Le flag `DragItem.selected` reste supporté en parallèle. ⚠️ Les `id` doivent être uniques **sur tout l'arbre**, pas seulement par niveau. |
37
37
 
38
38
  ### Outputs
39
39
 
@@ -60,6 +60,8 @@ Le **corps** de chaque carte est fourni par un `TemplateRef` passé à `[itemTem
60
60
 
61
61
  > L'en-tête (titre, sous-titre, badge, compteur, actions, poignée) reste **toujours** data-driven ; seul le corps est personnalisable.
62
62
 
63
+ Sans `[itemTemplate]`, la carte se réduit à son en-tête : le composant ne réaffiche pas le label, qui est déjà le titre. Seul `item.code`, s'il est renseigné, est rendu dans le corps. C'est le mode à privilégier quand l'en-tête suffit — chaque corps redondant coûte une hauteur de carte à tous les niveaux de l'arbre.
64
+
63
65
  ---
64
66
 
65
67
  ## 3. Modèles & types
@@ -67,8 +69,8 @@ Le **corps** de chaque carte est fourni par un `TemplateRef` passé à `[itemTem
67
69
  ```ts
68
70
  interface DragItem {
69
71
  id: number | string; // clé de tri (trackBy) — unique
70
- label: string; // libellé (fallback de title, corps par défaut)
71
- code?: string; // affiché dans le corps par défaut
72
+ label: string; // libellé (fallback de title dans l'en-tête)
73
+ code?: string; // affiché seul dans le corps quand il n'y a pas d'itemTemplate
72
74
  order?: number; // position 1..n (renumérotée si reorderOnDrop)
73
75
  disabled?: boolean; // verrouille le drag de CETTE carte (poignée inactive)
74
76
  children?: DragItem[]; // sous-arbre
@@ -101,8 +103,8 @@ interface DragItemActions { // chaque champ = visibilité (booléen). unde
101
103
 
102
104
  interface DragListColumn {
103
105
  title: string; // titre de colonne (clé i18n) — affiché « Titre (count) » / « (count/max) »
104
- description?: string;
105
- descriptionRwd?: string;
106
+ description?: string; // IGNORÉ par ce composant (n'existe que pour cw-drag-n-drop-list)
107
+ descriptionRwd?: string; // idem
106
108
  cssClass?: string; // classe sur chaque <ul> de niveau
107
109
 
108
110
  collapsible?: boolean; // cartes pliables (défaut true) ; false désactive globalement
@@ -256,6 +258,10 @@ restore(t: DragItem) {
256
258
  onReorder(items: DragItem[]) { this.items = items; }
257
259
  ```
258
260
 
261
+ **Rendu en profondeur.** Une seule carte est peinte à l'écran, jamais toute une branche. La carte sélectionnée porte `ddn-list-card-selected` ; un parent qui la contient ne porte `ddn-list-card-has-selection` que **s'il est replié** — la carte sélectionnée n'étant alors pas rendue (`destroyOnHide` de `cw-card`), le parent en reprend le rendu et signale « un élément actif est là-dedans ». Déplié, il redevient neutre. La lib suit le pliage réel de chaque carte via `(collapseClicked)`, pas seulement l'état initial.
262
+
263
+ ⚠️ **Unicité des `id`.** La correspondance se fait sur `id === selectedId` à tous les niveaux. Si un item de niveau 1 et un item de niveau 2 partagent le même `id` (typique quand chaque niveau vient d'une table différente : thème `1`, fichier `1`), **les deux cartes s'allument**. Préfixer côté hôte (`` `theme-${t.id}` ``, `` `file-${f.id}` ``) ou utiliser une clé composite.
264
+
259
265
  ### Corps de carte custom (`itemTemplate`)
260
266
 
261
267
  ```html
@@ -272,12 +278,12 @@ onReorder(items: DragItem[]) { this.items = items; }
272
278
 
273
279
  ## 5. Notes d'intégration
274
280
 
275
- - **CDK drag-drop** : repose sur `@angular/cdk/drag-drop` (`DragDropModule`). Chaque niveau est un `cdkDropList` **isolé** (pas de `cdkDropListConnectedTo`) → le tri est **intra-liste à chaque niveau**, jamais entre niveaux ni entre listes. Le drag ne démarre que depuis la **poignée** (`cdkDragHandle`).
281
+ - **CDK drag-drop** : repose sur `@angular/cdk/drag-drop` (`DragDropModule`). Chaque niveau est un `cdkDropList` **isolé** (pas de `cdkDropListConnectedTo`) → le tri est **intra-liste à chaque niveau**, jamais entre niveaux ni entre listes. Le drag ne démarre que depuis la **poignée** (`cdkDragHandle`). Poignée inactive (carte `disabled` ou `pendingDelete`) : elle garde son rendu normal, seule l'interaction disparaît — le rendu `disabled` du `.btn` (aplat plein `base-disabled`) est neutralisé sur `.ddn-list-handle`, sinon une poignée morte est plus voyante qu'une poignée active.
276
282
  - **Source de vérité** : le composant clone `items` dans une copie interne mutable (`_items`) à chaque changement (effect). C'est cette copie que le CDK déplace ; l'arbre réordonné est émis via `itemsChange`. **Persister** ce payload côté hôte (sinon un re-rendu ultérieur repart de l'ordre périmé).
277
283
  - **Imbrication & `maxDepth`** : `maxDepth` est 0-based (racine = 0). `maxDepth = 1` ⇒ 2 niveaux. `undefined` ⇒ imbrication infinie (ajout possible partout).
278
284
  - **Type de carte alterné** : la lib alterne `CardTypeEnum.default` (profondeur paire) / `light` (impaire).
279
285
  - **i18n** : tous les libellés (`column.title`, `addLabel`, `maxReachedLabel`, `restoreBlockedLabel`, libellés de badge…) passent par `translate` → fournir des **clés**, pas du texte en dur.
280
- - **États & styles** : la lib **lit** `selected` / `selectedId` / `pendingDelete` et pose les classes DS (`ddn-list-card-selected`, `ddn-list-card-deleting`) ; elle ne mute pas les flags. C'est l'hôte qui les flippe et re-passe `items`. Préférer `selectedId` (exclusif, sans parcours d'arbre) au flag `selected` (multi-sélection non garantie).
286
+ - **États & styles** : la lib **lit** `selected` / `selectedId` / `pendingDelete` et pose les classes DS (`ddn-list-card-selected`, `ddn-list-card-has-selection`, `ddn-list-card-deleting`) ; elle ne mute pas les flags. C'est l'hôte qui les flippe et re-passe `items`. Préférer `selectedId` (exclusif, sans parcours d'arbre) au flag `selected` (multi-sélection non garantie).
281
287
  - **Soft-delete** : sur une carte `pendingDelete`, le corps est masqué (carte compacte, non pliable, non déplaçable) et la corbeille devient « Restaurer » (émet `restore`, pas `delete`). En mode `excludePendingDeleteFromMax`, restaurer peut être **bloqué** si le niveau est entre-temps redevenu plein.
282
288
 
283
289
  ---
@@ -109,7 +109,7 @@ interface ImportWizardConfig {
109
109
  modes: readonly [ImportModeOption, ...ImportModeOption[]]; // jamais vide ; 1 élément = imposé
110
110
  interruption: ImportInterruption; // requis : ce qui se passe vraiment à l'arrêt
111
111
  showErrorDetails?: boolean; // [Option]
112
- scope?: ImportScopeEnum; // défaut list
112
+ scope: ImportScope; // requis — voir ci-dessous
113
113
  }
114
114
  ```
115
115
 
@@ -139,6 +139,26 @@ Aucune de ces situations n'est rattrapée à l'exécution : elles ne compilent p
139
139
 
140
140
  ---
141
141
 
142
+ ### `scope` — et le nom de l'objet importé
143
+
144
+ `scope` est un type discriminé : le libellé de l'objet **voyage avec la portée qui l'affiche**, comme `keyFieldHint` voyage avec son mode. Indéclarable en `singleConfig` (aucun compteur n'y est rendu), inoubliable en `list`.
145
+
146
+ ```ts
147
+ scope: { kind: ImportScopeEnum.list, objectLabel: i18nKey('DOMAIN.USER') }
148
+ // ou
149
+ scope: { kind: ImportScopeEnum.singleConfig }
150
+ ```
151
+
152
+ `objectLabel` est une clé **racine**, suffixée par le composant de la catégorie de pluriel de la langue active — exactement comme les libellés de compteur. Le bundle de l'application doit donc déclarer les trois catégories :
153
+
154
+ ```jsonc
155
+ "DOMAIN": { "USER": { "one": "utilisateur", "many": "utilisateurs", "other": "utilisateurs" } }
156
+ ```
157
+
158
+ Rendu : « **2 431** utilisateurs à ajouter ».
159
+
160
+ > ⚠️ **Accord.** Les libellés du *rapport* sont des participes passés accordés au masculin pluriel (« … ajoutés », « … supprimés »). Un nom féminin (« lignes ») donnerait « lignes ajoutés ». Le cas échéant, surcharger `_COMMON._IMPORT._COUNTS.REPORT_*` dans le bundle de l'application — le deepmerge du loader donne le dernier mot à l'application.
161
+
142
162
  ## 4. Exemples
143
163
 
144
164
  ### Import d'une liste, ouvert depuis le bouton *Importer* de l'en-tête
@@ -270,7 +290,7 @@ readonly configImport: ImportWizardConfig = {
270
290
  accept: '.json',
271
291
  acceptLabel: 'JSON',
272
292
  modes: [{ mode: ImportModeEnum.replaceAll }], // un seul mode => imposé, lecture seule
273
- scope: ImportScopeEnum.singleConfig, // pas de compteurs, message de conformité
293
+ scope: { kind: ImportScopeEnum.singleConfig }, // pas de compteurs, message de conformité
274
294
  interruption: { kind: ImportInterruptionEnum.atomic }, // back-end transactionnel
275
295
  source: { analyze: …, execute: … },
276
296
  };