From f8b934b53ec5b7719837c477dabd50717bbc536d Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Fri, 8 Feb 2019 16:29:47 +0100
Subject: [PATCH] Materialification des modules de calcul
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

style personnalisé pour les mat-button-toggle
breakpoint personnalisé "xxs" pour angular-flex
placement des sous-calculettes répétables
réparation des select
erreurs sur les input
placement des paramètres liés
simplification du code et nouveau rendu pour les paramètres calculés
suppression totale de MDBootstrap
---
 angular.json                                  |   6 +-
 package-lock.json                             |   9 +-
 package.json                                  |   1 -
 src/app/app.module.ts                         |  33 +-
 .../base-param-input.component.ts             |  21 -
 .../dialog-edit-param-computed.component.html |  16 +
 .../dialog-edit-param-computed.component.scss |   3 +
 .../dialog-edit-param-computed.component.ts   |  40 ++
 .../dialog-edit-param-values.component.html   |  84 ++++
 .../dialog-edit-param-values.component.scss   |  16 +
 .../dialog-edit-param-values.component.ts     | 271 +++++++++++++
 .../field-set/field-set.component.html        |  52 +--
 .../field-set/field-set.component.scss        |  93 ++++-
 .../field-set/field-set.component.ts          |  60 +--
 .../fieldset-container.component.html         |  19 +-
 .../fieldset-container.component.scss         |  26 ++
 .../fieldset-container.component.ts           |   5 +-
 .../calc-name.component.scss                  |  11 +
 .../generic-calculator/calc-name.component.ts |  10 +-
 .../calculator.component.html                 | 107 ++---
 .../calculator.component.scss                 |  66 ++-
 .../calculator.component.ts                   |   4 +
 .../generic-input.component.html              |  13 +-
 .../generic-input/generic-input.component.ts  |  77 +---
 .../generic-select.component.html             |  15 +-
 .../generic-select.component.ts               |  65 ---
 .../log-entry/log-entry.component.html        |   6 +-
 src/app/components/log/log.component.html     |  18 +-
 .../ngparam-input.component.scss              |  13 +
 .../ngparam-input/ngparam-input.component.ts  |  26 +-
 .../param-computed.component.html             |   7 +
 .../param-computed.component.scss             |  21 +
 .../param-computed.component.ts               |  50 +++
 .../param-field-line.component.html           |  78 ++--
 .../param-field-line.component.scss           |  13 +
 .../param-field-line.component.ts             | 155 ++-----
 .../param-link/param-link.component.html      |  21 +-
 .../param-link/param-link.component.scss      |  23 ++
 .../param-link/param-link.component.ts        |  55 +--
 .../param-values/ngparam-max.component.ts     |  74 ----
 .../param-values/ngparam-min.component.ts     |  74 ----
 .../param-values/ngparam-step.component.ts    |  82 ----
 .../param-values/param-values.component.html  |  31 +-
 .../param-values/param-values.component.scss  |  21 +
 .../param-values/param-values.component.ts    | 380 ++----------------
 .../param-values/value-list.component.ts      | 106 -----
 .../horizontal-result-element.component.html  |   5 +-
 .../vertical-result-element.component.html    |   3 +-
 .../results-graph/graph-type.component.ts     |   8 +-
 .../results-graph.component.html              |  14 +-
 .../select-field-line.component.html          |  16 -
 .../select-field-line.component.scss          |  14 +
 .../select-field-line.component.ts            |   9 +-
 src/app/directives/flex-xxs.directive.ts      |  65 +++
 .../concrete/form-regime-uniforme.ts          |   2 +-
 .../definition/form-def-fixedvar.ts           |   7 +-
 src/app/formulaire/ngparam.ts                 |  13 +-
 src/locale/messages.en.json                   |   6 +-
 src/locale/messages.fr.json                   |   6 +-
 src/styles.scss                               |  10 +
 src/theme.scss                                |  16 +
 tsconfig.json                                 |   1 -
 62 files changed, 1248 insertions(+), 1324 deletions(-)
 create mode 100644 src/app/components/dialog-edit-param-computed/dialog-edit-param-computed.component.html
 create mode 100644 src/app/components/dialog-edit-param-computed/dialog-edit-param-computed.component.scss
 create mode 100644 src/app/components/dialog-edit-param-computed/dialog-edit-param-computed.component.ts
 create mode 100644 src/app/components/dialog-edit-param-values/dialog-edit-param-values.component.html
 create mode 100644 src/app/components/dialog-edit-param-values/dialog-edit-param-values.component.scss
 create mode 100644 src/app/components/dialog-edit-param-values/dialog-edit-param-values.component.ts
 create mode 100644 src/app/components/fieldset-container/fieldset-container.component.scss
 create mode 100644 src/app/components/generic-calculator/calc-name.component.scss
 delete mode 100644 src/app/components/generic-select/generic-select.component.ts
 create mode 100644 src/app/components/ngparam-input/ngparam-input.component.scss
 create mode 100644 src/app/components/param-computed/param-computed.component.html
 create mode 100644 src/app/components/param-computed/param-computed.component.scss
 create mode 100644 src/app/components/param-computed/param-computed.component.ts
 create mode 100644 src/app/components/param-field-line/param-field-line.component.scss
 create mode 100644 src/app/components/param-link/param-link.component.scss
 delete mode 100644 src/app/components/param-values/ngparam-max.component.ts
 delete mode 100644 src/app/components/param-values/ngparam-min.component.ts
 delete mode 100644 src/app/components/param-values/ngparam-step.component.ts
 create mode 100644 src/app/components/param-values/param-values.component.scss
 delete mode 100644 src/app/components/param-values/value-list.component.ts
 delete mode 100644 src/app/components/select-field-line/select-field-line.component.html
 create mode 100644 src/app/components/select-field-line/select-field-line.component.scss
 create mode 100644 src/app/directives/flex-xxs.directive.ts

diff --git a/angular.json b/angular.json
index 1bdcb122c..cb7ab0bee 100644
--- a/angular.json
+++ b/angular.json
@@ -25,8 +25,6 @@
               { "glob": "**/*.png", "input": "src/", "output": "/" }
             ],
             "styles": [
-              "node_modules/angular-bootstrap-md/scss/bootstrap/bootstrap.scss",
-              "node_modules/angular-bootstrap-md/scss/mdb-free.scss",
               "src/styles.scss",
               "src/theme.scss"
             ],
@@ -42,7 +40,7 @@
               "aot": true,
               "extractLicenses": true,
               "vendorChunk": false,
-              "buildOptimizer": true,
+              "buildOptimizer": false,
               "fileReplacements": [
                 {
                   "replace": "src/environments/environment.ts",
@@ -79,8 +77,6 @@
             "tsConfig": "src/tsconfig.spec.json",
             "scripts": [],
             "styles": [
-              "node_modules/angular-bootstrap-md/scss/bootstrap/bootstrap.scss",
-              "node_modules/angular-bootstrap-md/scss/mdb-free.scss",
               "src/styles.scss",
               "src/theme.scss"
             ],
diff --git a/package-lock.json b/package-lock.json
index f8fa41871..e5839dd8a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1481,11 +1481,6 @@
       "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
       "dev": true
     },
-    "angular-bootstrap-md": {
-      "version": "7.3.0",
-      "resolved": "https://registry.npmjs.org/angular-bootstrap-md/-/angular-bootstrap-md-7.3.0.tgz",
-      "integrity": "sha512-FACXdj+fGe7aA1yNBoFFV6I8Gs9+ithMdGAl4ZJ7DxqD5JudtWqlAwapNpqXzf7r17b9+vIGAAMmVTfcc+i2Dw=="
-    },
     "angular2-chartjs": {
       "version": "0.5.1",
       "resolved": "https://registry.npmjs.org/angular2-chartjs/-/angular2-chartjs-0.5.1.tgz",
@@ -6782,8 +6777,12 @@
       "version": "file:../jalhyd",
 =======
       "version": "file:../jalhyd/jalhyd-1.0.0.tgz",
+<<<<<<< e94eb7b6543d2a58a90ae771c869e558f09f5cf5
       "integrity": "sha512-sT+DU/Uyg2s04TqEHCoM8dKQDvFwjj30ax8MO2EOHa2AOzd9+OHO0z5KbvwAHYadrW4p/kxz8p2SK/KLDsfQ0g==",
 >>>>>>> Màj dépendances
+=======
+      "integrity": "sha512-aB8rBtIjyt5EKGXOWRCarFduWhLpct4gEzIHwxeU2BTQpo043CybEDN7kC2w6NprK/NSfcgZBDC9APdcjnBsig==",
+>>>>>>> [WIP] materialification des modules de calcul
       "requires": {
         "buffer": "^5.2.1"
       },
diff --git a/package.json b/package.json
index 3f6f70219..9d2e430d1 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,6 @@
     "@angular/platform-browser": "^7.2.1",
     "@angular/platform-browser-dynamic": "^7.2.1",
     "@angular/router": "^7.2.1",
-    "angular-bootstrap-md": "^7.3.0",
     "angular2-chartjs": "^0.5.1",
     "core-js": "^2.6.3",
     "file-saver": "^2.0.0",
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 974987c22..f4473c1a0 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -16,12 +16,19 @@ import {
   MatInputModule,
   MatListModule,
   MatCardModule,
-  ErrorStateMatcher
+  ErrorStateMatcher,
+  MatButtonToggleModule
 } from "@angular/material";
 import { MaterialFileInputModule } from "ngx-material-file-input";
 
 import { FlexLayoutModule } from "@angular/flex-layout";
-import { MDBBootstrapModule } from "angular-bootstrap-md";
+import {
+  CustomBreakPointsProvider,
+  FlexGtXxsShowHideDirective,
+  FlexXxsShowHideDirective,
+  FlexLtXsShowHideDirective
+} from "./directives/flex-xxs.directive";
+
 import { HttpClientModule } from "@angular/common/http";
 import { FormsModule, ReactiveFormsModule } from "@angular/forms"; // <-- NgModel lives here
 import { ChartModule } from "angular2-chartjs";
@@ -39,11 +46,7 @@ import { NgParamInputComponent } from "./components/ngparam-input/ngparam-input.
 import { FieldSetComponent } from "./components/field-set/field-set.component";
 import { FieldsetContainerComponent } from "./components/fieldset-container/fieldset-container.component";
 import { ParamFieldLineComponent } from "./components/param-field-line/param-field-line.component";
-import { NgParamMinComponent } from "./components/param-values/ngparam-min.component";
-import { NgParamMaxComponent } from "./components/param-values/ngparam-max.component";
-import { NgParamStepComponent } from "./components/param-values/ngparam-step.component";
 import { ParamValuesComponent } from "./components/param-values/param-values.component";
-import { ValueListComponent } from "./components/param-values/value-list.component";
 import { SelectFieldLineComponent } from "./components/select-field-line/select-field-line.component";
 import { CheckFieldLineComponent } from "./components/check-field-line/check-field-line.component";
 import { CalculatorResultsComponent } from "./components/calculator-results/calculator-results.component";
@@ -70,12 +73,15 @@ import { ParamLinkComponent } from "./components/param-link/param-link.component
 
 import { DialogConfirmEmptySessionComponent } from "./components/dialog-confirm-empty-session/dialog-confirm-empty-session.component";
 import { DialogConfirmCloseCalcComponent } from "./components/dialog-confirm-close-calc/dialog-confirm-close-calc.component";
+import { DialogEditParamComputedComponent } from "./components/dialog-edit-param-computed/dialog-edit-param-computed.component";
+import { DialogEditParamValuesComponent } from "./components/dialog-edit-param-values/dialog-edit-param-values.component";
 import { DialogLoadSessionComponent } from "./components/dialog-load-session/dialog-load-session.component";
 import { DialogSaveSessionComponent } from "./components/dialog-save-session/dialog-save-session.component";
 
 import { JalhydAsyncModelValidationDirective } from "./directives/jalhyd-async-model-validation.directive";
 import { JalhydModelValidationDirective } from "./directives/jalhyd-model-validation.directive";
 import { ImmediateErrorStateMatcher } from "./formulaire/immediate-error-state-matcher";
+import { ParamComputedComponent } from "./components/param-computed/param-computed.component";
 
 const appRoutes: Routes = [
   { path: "list", component: CalculatorListComponent },
@@ -94,6 +100,7 @@ const appRoutes: Routes = [
     HttpClientModule,
     FlexLayoutModule,
     MatButtonModule,
+    MatButtonToggleModule,
     MatCardModule,
     MatCheckboxModule,
     MatDialogModule,
@@ -107,7 +114,6 @@ const appRoutes: Routes = [
     MatSidenavModule,
     MatTabsModule,
     MatToolbarModule,
-    MDBBootstrapModule.forRoot(),
     NgxMdModule.forRoot(),
     RouterModule.forRoot(
       appRoutes,
@@ -128,12 +134,17 @@ const appRoutes: Routes = [
     CheckFieldLineComponent,
     DialogConfirmCloseCalcComponent,
     DialogConfirmEmptySessionComponent,
+    DialogEditParamComputedComponent,
+    DialogEditParamValuesComponent,
     DialogLoadSessionComponent,
     DialogSaveSessionComponent,
     FieldSetComponent,
     FieldsetContainerComponent,
     FixedResultsComponent,
     FixedVarResultsComponent,
+    FlexGtXxsShowHideDirective,
+    FlexLtXsShowHideDirective,
+    FlexXxsShowHideDirective,
     GenericCalculatorComponent,
     GraphTypeSelectComponent,
     HorizontalResultElementComponent,
@@ -142,9 +153,7 @@ const appRoutes: Routes = [
     LogComponent,
     LogEntryComponent,
     NgParamInputComponent,
-    NgParamMaxComponent,
-    NgParamMinComponent,
-    NgParamStepComponent,
+    ParamComputedComponent,
     ParamFieldLineComponent,
     ParamLinkComponent,
     ParamValuesComponent,
@@ -154,18 +163,20 @@ const appRoutes: Routes = [
     SectionCanvasComponent,
     SectionResultsComponent,
     SelectFieldLineComponent,
-    ValueListComponent,
     VarResultsComponent,
     VerticalResultElementComponent
   ],
   entryComponents: [
     DialogConfirmCloseCalcComponent,
     DialogConfirmEmptySessionComponent,
+    DialogEditParamComputedComponent,
+    DialogEditParamValuesComponent,
     DialogSaveSessionComponent,
     DialogLoadSessionComponent
   ],
   providers: [ // services
     ApplicationSetupService,
+    CustomBreakPointsProvider,
     FormulaireService,
     HttpService,
     I18nService,
diff --git a/src/app/components/base-param-input/base-param-input.component.ts b/src/app/components/base-param-input/base-param-input.component.ts
index a919e31a7..fb65a845c 100644
--- a/src/app/components/base-param-input/base-param-input.component.ts
+++ b/src/app/components/base-param-input/base-param-input.component.ts
@@ -112,25 +112,4 @@ export class BaseParamInputComponent extends GenericInputComponent {
     protected validateModelValue(v: any): { isValid: boolean, message: string } {
         return this._model.validateModelValue(v);
     }
-
-    protected modelToUI(v: any): string {
-        return String(v);
-    }
-
-    protected validateUIValue(ui: string): { isValid: boolean, message: string } {
-        let valid = false;
-        let msg: string;
-
-        if (! isNumeric(ui)) {
-            msg = "Veuillez entrer une valeur numérique";
-        } else {
-            valid = true;
-        }
-
-        return { isValid: valid, message: msg };
-    }
-
-    protected uiToModel(ui: string) {
-        return +ui;
-    }
 }
diff --git a/src/app/components/dialog-edit-param-computed/dialog-edit-param-computed.component.html b/src/app/components/dialog-edit-param-computed/dialog-edit-param-computed.component.html
new file mode 100644
index 000000000..9efaf2887
--- /dev/null
+++ b/src/app/components/dialog-edit-param-computed/dialog-edit-param-computed.component.html
@@ -0,0 +1,16 @@
+<h1 mat-dialog-title [innerHTML]="uitextEditParamComputedInitialValue"></h1>
+
+<form>
+
+  <div mat-dialog-content>
+    <ngparam-input [title]="param.title" ></ngparam-input>
+    <!-- (change)="onInputChange($event)" -->
+  </div>
+
+  <div mat-dialog-actions>
+    <button mat-raised-button [mat-dialog-close]="true" cdkFocusInitial>
+      {{ uitextClose }}
+    </button>
+  </div>
+
+</form>
diff --git a/src/app/components/dialog-edit-param-computed/dialog-edit-param-computed.component.scss b/src/app/components/dialog-edit-param-computed/dialog-edit-param-computed.component.scss
new file mode 100644
index 000000000..bc76a27e2
--- /dev/null
+++ b/src/app/components/dialog-edit-param-computed/dialog-edit-param-computed.component.scss
@@ -0,0 +1,3 @@
+mat-form-field {
+    width: 100%;
+}
diff --git a/src/app/components/dialog-edit-param-computed/dialog-edit-param-computed.component.ts b/src/app/components/dialog-edit-param-computed/dialog-edit-param-computed.component.ts
new file mode 100644
index 000000000..e7979965f
--- /dev/null
+++ b/src/app/components/dialog-edit-param-computed/dialog-edit-param-computed.component.ts
@@ -0,0 +1,40 @@
+import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material";
+import { Inject, Component, ViewChild, OnInit } from "@angular/core";
+import { I18nService } from "../../services/internationalisation/internationalisation.service";
+import { NgParameter } from "../../formulaire/ngparam";
+import { NgParamInputComponent } from "../ngparam-input/ngparam-input.component";
+
+@Component({
+    selector: "dialog-edit-param-computed",
+    templateUrl: "dialog-edit-param-computed.component.html",
+    styleUrls: ["dialog-edit-param-computed.component.scss"]
+})
+export class DialogEditParamComputedComponent implements OnInit {
+
+    /** the related parameter to change the "fixed" value of */
+    public param: NgParameter;
+
+    @ViewChild(NgParamInputComponent)
+    private _ngParamInputComponent: NgParamInputComponent;
+
+    constructor(
+      public dialogRef: MatDialogRef<DialogEditParamComputedComponent>,
+      private intlService: I18nService,
+      @Inject(MAT_DIALOG_DATA) public data: any
+    ) {
+      this.param = data.param;
+    }
+
+    public get uitextClose() {
+      return this.intlService.localizeText("INFO_OPTION_CLOSE");
+    }
+
+    public get uitextEditParamComputedInitialValue() {
+      return "Modifier la valeur initiale (à traduire)";
+      // return this.intlService.localizeText("INFO_OPTION_ALL");
+    }
+
+    public ngOnInit() {
+      this._ngParamInputComponent.model = this.param;
+    }
+}
diff --git a/src/app/components/dialog-edit-param-values/dialog-edit-param-values.component.html b/src/app/components/dialog-edit-param-values/dialog-edit-param-values.component.html
new file mode 100644
index 000000000..61ae3b59b
--- /dev/null
+++ b/src/app/components/dialog-edit-param-values/dialog-edit-param-values.component.html
@@ -0,0 +1,84 @@
+<h1 mat-dialog-title [innerHTML]="uitextEditParamComputedInitialValue"></h1>
+
+  <div mat-dialog-content>
+
+    <mat-form-field>
+      <mat-select [placeholder]="uiTextModeSelection" [(value)]="selectedValueMode" (selectionChange)="onValueModeChange($event)"
+        data-testid="variable-value-mode-select">
+          <mat-option *ngFor="let e of valueModes" [value]="e.value">
+              {{ e.label }}
+          </mat-option>
+      </mat-select>
+    </mat-form-field>
+
+    <div *ngIf="isMinMax" class="min-max-step-container">
+        <form>
+            <mat-form-field>
+                <input matInput class="form-control" type="number" inputmode="numeric" name="min-value" min="0" step="0.01"
+                    [placeholder]="uitextValeurMini" [(ngModel)]="param.minValue" required>
+
+                <mat-error>{{ errorMessage }}</mat-error>
+            </mat-form-field>
+
+            <mat-form-field>
+                <input matInput class="form-control" type="number" inputmode="numeric" name="max-value" min="0" step="0.01"
+                    [placeholder]="uitextValeurMaxi" [(ngModel)]="param.maxValue" required>
+
+                <mat-error>{{ errorMessage }}</mat-error>
+            </mat-form-field>
+
+            <mat-form-field>
+                <input matInput class="form-control" type="number" inputmode="numeric" name="step-value" min="0" step="0.01"
+                    [placeholder]="uitextPasVariation" [(ngModel)]="param.stepValue" required>
+
+                <mat-error>{{ errorMessage }}</mat-error>
+            </mat-form-field>
+        </form>
+    </div>
+
+    <div *ngIf="isListe">
+        <form [formGroup]="valuesListForm">
+            <mat-form-field>
+                <textarea matInput matTextareaAutosize [placeholder]="uitextListeValeurs" [ngModel]="valuesList"
+                    name="values-list" #vl="ngModel" required [pattern]="valuesListPattern"></textarea>
+                <mat-error *ngIf="vl.errors">
+                    <div *ngIf="vl.errors.required || vl.errors.pattern">
+                        {{ uitextMustBeListOfNumbers }}
+                    </div>
+                    <!-- <div *ngIf="! vl.errors.required && vl.errors.jalhydModel">
+                        {{ vl.errors.jalhydModel.message }}
+                    </div> -->
+                </mat-error>
+            </mat-form-field>
+
+            <div class="decimal-separator-and-file-container" fxLayout="row wrap" fxLayoutAlign="space-between start">
+                <mat-form-field class="decimal-separator" fxFlex.gt-xs="1 0 auto" fxFlex.lt-sm="1 0 100%">
+                    <mat-select [placeholder]="uitextDecimalSeparator" [(value)]="decimalSeparator"
+                        data-testid="decimal-separator-select">
+                        <mat-option *ngFor="let e of decimalSeparators" [value]="e.value">
+                            {{ e.label }}
+                        </mat-option>
+                    </mat-select>
+                </mat-form-field>
+
+                <div fxHide.xs fxFlex.gt-xs="0 0 16px"></div>
+
+                <mat-form-field class="values-file" fxFlex.gt-xs="1 0 auto" fxFlex.lt-sm="1 0 100%">
+                    <ngx-mat-file-input #valuesFile [placeholder]="uitextImportFile"
+                        (change)="onFileSelected($event)" formControlName="file">
+                    </ngx-mat-file-input>
+                    <button mat-icon-button matSuffix *ngIf="!valuesFile.empty" (click)="valuesFile.clear($event)">
+                        <mat-icon>clear</mat-icon>
+                    </button>
+                </mat-form-field>
+            </div>
+        </form>
+    </div>
+
+  </div>
+
+  <div mat-dialog-actions>
+    <button mat-raised-button [mat-dialog-close]="true" cdkFocusInitial>
+      {{ uitextClose }}
+    </button>
+  </div>
diff --git a/src/app/components/dialog-edit-param-values/dialog-edit-param-values.component.scss b/src/app/components/dialog-edit-param-values/dialog-edit-param-values.component.scss
new file mode 100644
index 000000000..c411650eb
--- /dev/null
+++ b/src/app/components/dialog-edit-param-values/dialog-edit-param-values.component.scss
@@ -0,0 +1,16 @@
+mat-form-field {
+    display: block;
+
+    textarea {
+        font-size: .8em;
+        max-height: 100px;
+    }
+
+    /*&.decimal-separator, &.values-file {
+        display: inline-block;
+    }*/
+}
+
+.min-max-step-container {
+    margin-top: -8px;
+}
diff --git a/src/app/components/dialog-edit-param-values/dialog-edit-param-values.component.ts b/src/app/components/dialog-edit-param-values/dialog-edit-param-values.component.ts
new file mode 100644
index 000000000..76b32a877
--- /dev/null
+++ b/src/app/components/dialog-edit-param-values/dialog-edit-param-values.component.ts
@@ -0,0 +1,271 @@
+import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material";
+import { Inject, Component, OnInit } from "@angular/core";
+import { FormBuilder, FormGroup, Validators } from "@angular/forms";
+import { I18nService } from "../../services/internationalisation/internationalisation.service";
+import { NgParameter } from "../../formulaire/ngparam";
+import { ParamValueMode } from "jalhyd";
+
+@Component({
+    selector: "dialog-edit-param-values",
+    templateUrl: "dialog-edit-param-values.component.html",
+    styleUrls: ["dialog-edit-param-values.component.scss"]
+})
+export class DialogEditParamValuesComponent implements OnInit {
+
+    /** the related parameter to change the "variable" value of */
+    public param: NgParameter;
+
+    /** available value modes (min / max, list) */
+    public valueModes;
+
+    /** available decimal separators */
+    public decimalSeparators;
+
+    /** current decimal separator */
+    public decimalSeparator;
+
+    public valuesListForm: FormGroup;
+
+    constructor(
+        public dialogRef: MatDialogRef<DialogEditParamValuesComponent>,
+        private intlService: I18nService,
+        private fb: FormBuilder,
+        @Inject(MAT_DIALOG_DATA) public data: any
+    ) {
+        this.param = data.param;
+        if (this.isMinMax) {
+            console.log("PARAM MIN VALUE", this.param.minValue);
+            console.log("PARAM MAX VALUE", this.param.maxValue);
+            console.log("PARAM STEP VALUE", this.param.stepValue);
+        }
+        if (this.isListe) {
+            console.log("PARAM LIST VALUE", this.param.valueList);
+        }
+
+        // an explicit ReactiveForm is required for file input component
+        this.valuesListForm = this.fb.group({
+            file: [null, null]
+        });
+
+        this.valueModes = [
+            {
+                value: ParamValueMode.MINMAX,
+                label: "Min/max"
+            },
+            {
+                value: ParamValueMode.LISTE,
+                label: "Liste (à traduire)"
+            }
+        ];
+        this.decimalSeparators = [
+            {
+                label: "Point (à traduire)",
+                value: "."
+            },
+            {
+                label: "Virgule (à traduire)",
+                value: ","
+            }
+        ];
+        this.decimalSeparator = this.decimalSeparators[0].value;
+    }
+
+    /** regular expression pattern for values list validation (depends on decimal separator) */
+    public get valuesListPattern() {
+        const escapedDecimalSeparator = (this.decimalSeparator === "." ? "\\." : this.decimalSeparator);
+        const numberSubPattern = "-?([0-9]+" + escapedDecimalSeparator + ")?[0-9E]+";
+        const re = numberSubPattern + "(" + this.separatorPattern + numberSubPattern + ")*";
+        // console.log("Re PAT", re);
+        return re;
+    }
+
+    /** accepted separator: everything but [numbers, E, +, -, decimal separator], any length */
+    public get separatorPattern() {
+        return "[^0-9-+E" + this.decimalSeparator  + "]+";
+    }
+
+    public get selectedValueMode() {
+        return this.param.valueMode;
+    }
+
+    public set selectedValueMode(v) {
+        this.param.valueMode = v;
+    }
+
+    public get uiTextModeSelection() {
+        return "Mode de variabilité (à traduire)";
+    }
+
+    public get uitextValeurMini() {
+        return this.intlService.localizeText("INFO_PARAMFIELD_VALEURMINI");
+    }
+
+    public get uitextValeurMaxi() {
+        return this.intlService.localizeText("INFO_PARAMFIELD_VALEURMAXI");
+    }
+
+    public get uitextPasVariation() {
+        return this.intlService.localizeText("INFO_PARAMFIELD_PASVARIATION");
+    }
+
+    public get uitextClose() {
+        return this.intlService.localizeText("INFO_OPTION_CLOSE");
+    }
+
+    public get uitextEditParamComputedInitialValue() {
+        return "Modifier les valeurs variables (à traduire)";
+        // return this.intlService.localizeText("INFO_OPTION_ALL");
+    }
+
+    public get uitextListeValeurs() {
+        return "valeurs séparées par ';' (à traduire)";
+    }
+
+    public get uitextMustBeListOfNumbers() {
+        return "les valeurs doivent être de la forme 3;4.5;6 (à traduire)";
+    }
+
+    public get uitextDecimalSeparator() {
+        return "Séparateur décimal (à traduire)";
+    }
+
+    public get uitextImportFile() {
+        return "Importer un fichier (à traduire)";
+    }
+
+    public get isMinMax() {
+        return this.param.valueMode === ParamValueMode.MINMAX;
+    }
+
+    public get isListe() {
+        return this.param.valueMode === ParamValueMode.LISTE;
+    }
+
+    public get valuesList() {
+        return (this.param.valueList || []).join(";");
+    }
+
+    public set valuesList(list: string) {
+        const vals = [];
+        list.split(";").forEach((e) => {
+            if (e.length > 0) {
+                vals.push(Number(e));
+            }
+        });
+        this.param.valueList = vals;
+    }
+
+    public onFileSelected(event: any) {
+        if (event.target.files && event.target.files.length) {
+            console.log("FICHIERS", event.target.files);
+            const fr = new FileReader();
+            fr.onload = () => {
+                console.log("CHARJAY", fr.result);
+                this.valuesList = String(fr.result);
+            };
+            fr.onerror = () => {
+                fr.abort();
+                throw new Error("Erreur de lecture du fichier");
+            };
+            fr.readAsText(event.target.files[0]);
+        }
+    }
+
+    public dumperr(obj) {
+        if (obj) { return Object.keys(obj).join(", "); }
+    }
+
+    public onValueModeChange(event) {
+        this.initVariableValues();
+    }
+
+    public ngOnInit() {
+        this.initVariableValues();
+    }
+
+    private initVariableValues() {
+        // init min / max / step
+        if (this.isMinMax) {
+            if (this.param.minValue === undefined) {
+                this.param.minValue = this.param.getValue() / 2;
+            }
+            if (this.param.maxValue === undefined) {
+                this.param.maxValue = this.param.getValue() * 2;
+            }
+            let step = this.param.stepValue;
+            if (step === undefined) {
+                step = (this.param.maxValue - this.param.minValue) / 20;
+            }
+            this.param.stepValue = step;
+        }
+        // init values list
+        if (this.isListe) {
+            if (this.param.valueList === undefined) {
+                if (this.param.isDefined) {
+                    console.log("SET LIST", [ this.param.getValue() ]);
+                    this.param.valueList = [ this.param.getValue() ];
+                } else {
+                    console.log("SET LIST", [ ]);
+                    this.param.valueList = [];
+                }
+            }
+        }
+    }
+
+
+  /*   protected validateModelValue(v: any): { isValid: boolean, message: string } {
+      let msg: string;
+      let valid = false;
+
+      if (this.param === undefined) {
+          msg = "internal error, model undefined";
+      } else {
+          if (!this.param.checkMin(v)) {
+              msg = "La valeur n'est pas dans [" + this.param.domain.minValue + " , " + this.param.maxValue + "[";
+          } else {
+              valid = true;
+          }
+      }
+
+      return { isValid: valid, message: msg };
+  }
+  protected validateModelValue(v: any): { isValid: boolean, message: string } {
+      let msg: string;
+      let valid = false;
+
+      if (this.param === undefined) {
+          msg = "internal error, model undefined";
+      } else {
+          if (!this.param.checkMax(v)) {
+              msg = "La valeur n'est pas dans ]" + this.param.minValue + " , " + this.param.domain.maxValue + "]";
+          } else {
+              valid = true;
+          }
+      }
+
+      return { isValid: valid, message: msg };
+  }
+  protected validateModelValue(v: any): { isValid: boolean, message: string } {
+      let msg: string;
+      let valid = false;
+
+      if (! this.param) {
+          msg = "internal error, model undefined";
+      } else {
+          if (this.param.isMinMaxValid) {
+              if (!this.param.checkStep(v)) {
+                  msg = "La valeur n'est pas dans " + this.param.stepRefValue.toString();
+              } else {
+                  valid = v > 0;
+                  if (!valid) {
+                      msg = "La valeur ne peut pas être <= 0";
+                  }
+              }
+          } else {
+              msg = "Veuillez corriger le min/max";
+          }
+      }
+
+      return { isValid: valid, message: msg };
+  } */
+}
diff --git a/src/app/components/field-set/field-set.component.html b/src/app/components/field-set/field-set.component.html
index b0c0cec84..3217d50f0 100644
--- a/src/app/components/field-set/field-set.component.html
+++ b/src/app/components/field-set/field-set.component.html
@@ -1,31 +1,31 @@
-<div class="row fieldset_backgrd">
-    <div class="col fieldset_title">
+<mat-card-header class="bg-accent-light">
+    <mat-card-title>
         {{ title }}
+    </mat-card-title>
+    <div *ngIf="showButtons" class="hyd-window-btns">
+        <button mat-icon-button (click)="onAddClick()">
+            <mat-icon>add_box</mat-icon>
+        </button>
+        <button mat-icon-button [disabled]="! enableRemoveButton" (click)="onRemoveClick()">
+            <mat-icon>delete</mat-icon>
+        </button>
+        <button mat-icon-button [disabled]="! enableUpButton" (click)="onMoveUpClick()">
+            <mat-icon>arrow_upward</mat-icon>
+        </button>
+        <button mat-icon-button [disabled]="! enableDownButton" (click)="onMoveDownClick()">
+            <mat-icon>arrow_downward</mat-icon>
+        </button>
     </div>
-    <div *ngIf="showButtons" class="col-sm-4 hyd-window-btns">
-        <mat-icon (click)='onAddClick()'>add</mat-icon>
-        <mat-icon [style.color]='removeButtonColor' (click)='onRemoveClick()'>delete</mat-icon>
-        <mat-icon [style.color]='upButtonColor' (click)='onMoveUpClick()'>arrow_upward</mat-icon>
-        <mat-icon [style.color]='downButtonColor' (click)='onMoveDownClick()'>arrow_downward</mat-icon>
-    </div>
-</div>
-
-<!--
-    <tag *ngFor="let var of array" *ngIf="...utilisation de var..." >
-    </tag>
-    peut être transformé en
-    <ng-template ngFor let-var [ngForOf]="array">
-        <tag *ngIf="...utilisation de var..." >
-        </tag>
-    </ng-template>
--->
+</mat-card-header>
 
-<ng-template ngFor let-p [ngForOf]="fields">
-    <param-field-line *ngIf="isInputField(p)" [param]=p (radio)=onRadioClick($event) (valid)=onParamLineValid() (inputChange)=onInputChange()>
-    </param-field-line>
+<mat-card-content>
+    <ng-template ngFor let-p [ngForOf]="fields">
+        <param-field-line *ngIf="isInputField(p)" [param]=p (radio)=onRadioClick($event) (valid)=onParamLineValid() (inputChange)=onInputChange()>
+        </param-field-line>
 
-    <select-field-line *ngIf="isSelectField(p)" [_select]=p>
-    </select-field-line>
+        <select-field-line *ngIf="isSelectField(p)" [_select]=p>
+        </select-field-line>
 
-    <check-field-line *ngIf="isCheckField(p)" [check]=p></check-field-line>
-</ng-template>
\ No newline at end of file
+        <check-field-line *ngIf="isCheckField(p)" [check]=p></check-field-line>
+    </ng-template>
+</mat-card-content>
diff --git a/src/app/components/field-set/field-set.component.scss b/src/app/components/field-set/field-set.component.scss
index a4533b195..373f5b268 100644
--- a/src/app/components/field-set/field-set.component.scss
+++ b/src/app/components/field-set/field-set.component.scss
@@ -1,19 +1,84 @@
-.fieldset_title {
-    font-weight: bold;
+param-field-line {
+    // border: solid green 2px;
+    display: block;
 }
-.radio_param_header {
-    width: 10em;
+
+select-field-line {
+    // border: solid blue 2px;
+    display: block;
+}
+
+mat-card-header {
+    margin-left: -16px;
+    margin-right: -16px;
+    padding-left: 16px;
+    padding-top: 8px;
+    color: white;
+
+    // Pourquoi n'est-ce pas hérité de calculator.component.scss ?
+    // À cause de la surcharge de mat-card-header ci-dessus ?
+    mat-card-title {
+        font-size: 16px;
+        margin-bottom: 8px;
+    }
+
+    .hyd-window-btns {
+        text-align: right;
+    
+        mat-icon {
+            cursor: pointer;
+            transform: scale(1.2);
+            margin-top: 6px;
+            margin-right: 5px;
+        }
+    }
 }
-.fieldset_backgrd {
-    background-color: #eeeeee;
+
+mat-card-content {
+    margin-top: 1em;
 }
-.hyd-window-btns {
-    text-align: right;
-
-    mat-icon {
-        cursor: pointer;
-        transform: scale(1.2);
-        margin-top: 6px;
-        margin-right: 5px;
+
+mat-card-actions {
+
+    button {
+        width: 100%;
+    }
+}
+
+:host {
+
+    &.fieldset-inner {
+
+        mat-card-header {
+            margin-left: 0;
+            margin-right: 0;
+            padding-left: 12px;
+
+            mat-card-title {
+                font-size: 14px;
+            }
+            
+            .hyd-window-btns {
+                position: absolute;
+                right: 18px;
+                margin-top: -4px;
+
+                button.mat-icon-button {
+                    width: 26px;
+                    height: 20px;
+                    line-height: 20px;
+
+                    mat-icon {
+                        margin-top: 0;
+                        margin-right: 0;
+                        transform: scale(1);
+                    }
+                }
+            }
+        }
+
+        mat-card-content {
+            padding: 0 1em;
+        }
     }
 }
diff --git a/src/app/components/field-set/field-set.component.ts b/src/app/components/field-set/field-set.component.ts
index 24a031d4d..5848aa4c1 100644
--- a/src/app/components/field-set/field-set.component.ts
+++ b/src/app/components/field-set/field-set.component.ts
@@ -24,26 +24,6 @@ export class FieldSetComponent implements DoCheck {
         return this._fieldSet.kids;
     }
 
-    public get showButtons() {
-        return this._showButtons;
-    }
-
-    public set showButtons(b: boolean) {
-        this._showButtons = b;
-    }
-
-    public set enableUpButton(b: boolean) {
-        this._enableUpButton = b;
-    }
-
-    public set enableDownButton(b: boolean) {
-        this._enableDownButton = b;
-    }
-
-    public set enableRemoveButton(b: boolean) {
-        this._enableRemoveButton = b;
-    }
-
     public set fieldsetNumber(n: number) {
         this._fieldSet.labelNumber = n;
     }
@@ -62,26 +42,6 @@ export class FieldSetComponent implements DoCheck {
         return this._isValid;
     }
 
-    /**
-     * couleur du bouton monter
-     */
-    private get upButtonColor(): string {
-        return this._enableUpButton ? "black" : "lightgrey";
-    }
-
-    /**
-     * couleur du bouton descendre
-     */
-    private get downButtonColor(): string {
-        return this._enableDownButton ? "black" : "lightgrey";
-    }
-
-    /**
-     * couleur du bouton supprimer
-     */
-    private get removeButtonColor(): string {
-        return this._enableRemoveButton ? "black" : "lightgrey";
-    }
     /**
     * field set attribute
     */
@@ -134,22 +94,22 @@ export class FieldSetComponent implements DoCheck {
     /**
      * flag d'affichage des boutons ajouter, supprimer, monter, descendre
      */
-    private _showButtons = false;
+    public showButtons = false;
 
     /**
      * flag d'activation du bouton monter
      */
-    private _enableUpButton = true;
+    public enableUpButton = true;
 
     /**
      * flag d'activation du bouton descendre
      */
-    private _enableDownButton = true;
+    public enableDownButton = true;
 
     /**
      * flag d'activation du bouton supprimer
      */
-    private _enableRemoveButton = true;
+    public enableRemoveButton = true;
 
     /**
      * événement de changement d'état d'un radio
@@ -300,26 +260,20 @@ export class FieldSetComponent implements DoCheck {
      * clic sur le bouton supprimer
      */
     private onRemoveClick() {
-        if (this._enableRemoveButton) {
-            this.removeFieldset.emit(this._fieldSet);
-        }
+        this.removeFieldset.emit(this._fieldSet);
     }
 
     /**
      * clic sur le bouton monter
      */
     private onMoveUpClick() {
-        if (this._enableUpButton) {
-            this.moveFieldsetUp.emit(this._fieldSet);
-        }
+        this.moveFieldsetUp.emit(this._fieldSet);
     }
 
     /**
      * clic sur le bouton descendre
      */
     private onMoveDownClick() {
-        if (this._enableDownButton) {
-            this.moveFieldsetDown.emit(this._fieldSet);
-        }
+        this.moveFieldsetDown.emit(this._fieldSet);
     }
 }
diff --git a/src/app/components/fieldset-container/fieldset-container.component.html b/src/app/components/fieldset-container/fieldset-container.component.html
index 84e4f78e5..08e323f44 100644
--- a/src/app/components/fieldset-container/fieldset-container.component.html
+++ b/src/app/components/fieldset-container/fieldset-container.component.html
@@ -1,10 +1,13 @@
-<div class="container-fluid" style="border-style:solid; border-color: lightgray; border-radius: 10px; margin-bottom: 10px;">
-    <div class="row">
-        <h4 class="col">{{ title }}</h4>
-    </div>
+<mat-card-header class="bg-accent-light">
+    <mat-card-title>
+        {{ title }}
+    </mat-card-title>
+</mat-card-header>
 
-    <field-set *ngFor="let fs of fieldsets" [fieldSet]=fs (radio)=onRadioClick($event) (valid)=onFieldsetValid() (inputChange)=onInputChange()
-        (addFieldset)=onAddFieldset($event) (removeFieldset)=onRemoveFieldset($event) (moveFieldsetUp)=onMoveFieldsetUp($event)
-        (moveFieldsetDown)=onMoveFieldsetDown($event)>
+<mat-card-content>
+    <field-set *ngFor="let fs of fieldsets" class="fieldset-inner" [fieldSet]=fs
+        (radio)=onRadioClick($event) (valid)=onFieldsetValid() (inputChange)=onInputChange()
+        (addFieldset)=onAddFieldset($event) (removeFieldset)=onRemoveFieldset($event)
+        (moveFieldsetDown)=onMoveFieldsetDown($event) (moveFieldsetUp)=onMoveFieldsetUp($event)>
     </field-set>
-</div>
\ No newline at end of file
+</mat-card-content>
diff --git a/src/app/components/fieldset-container/fieldset-container.component.scss b/src/app/components/fieldset-container/fieldset-container.component.scss
new file mode 100644
index 000000000..bbc65715f
--- /dev/null
+++ b/src/app/components/fieldset-container/fieldset-container.component.scss
@@ -0,0 +1,26 @@
+:host {
+    display: block;
+    background-color: #f0f0f0;
+    // reduce margins to avoid inner field-sets being too narrow on 360px display
+    margin-left: -8px;
+    margin-right: -8px;
+}
+
+mat-card-header {
+    margin-left: -8px;
+    margin-right: -8px;
+    padding-left: 16px;
+    padding-top: 8px;
+    color: white;
+
+    // Pourquoi n'est-ce pas hérité de calculator.component.scss ?
+    // À cause de la surcharge de mat-card-header ci-dessus ?
+    mat-card-title {
+        font-size: 16px;
+        margin-bottom: 8px;
+    }
+}
+
+mat-card-content {
+    margin-top: 1em;
+}
diff --git a/src/app/components/fieldset-container/fieldset-container.component.ts b/src/app/components/fieldset-container/fieldset-container.component.ts
index 36c8199c3..75caa880f 100644
--- a/src/app/components/fieldset-container/fieldset-container.component.ts
+++ b/src/app/components/fieldset-container/fieldset-container.component.ts
@@ -7,7 +7,10 @@ import { FormulaireDefinition } from "../../formulaire/definition/form-definitio
 
 @Component({
     selector: "fieldset-container",
-    templateUrl: "./fieldset-container.component.html"
+    templateUrl: "./fieldset-container.component.html",
+    styleUrls: [
+        "./fieldset-container.component.scss"
+    ]
 })
 export class FieldsetContainerComponent implements DoCheck, AfterViewInit {
 
diff --git a/src/app/components/generic-calculator/calc-name.component.scss b/src/app/components/generic-calculator/calc-name.component.scss
new file mode 100644
index 000000000..8e8060ff4
--- /dev/null
+++ b/src/app/components/generic-calculator/calc-name.component.scss
@@ -0,0 +1,11 @@
+:host {
+    display: block;
+}
+
+mat-form-field {
+    width: 100%;
+
+    &.mat-form-field-invalid {
+        margin-bottom: 1em;
+    }
+}
diff --git a/src/app/components/generic-calculator/calc-name.component.ts b/src/app/components/generic-calculator/calc-name.component.ts
index 860ed423f..a682903bd 100644
--- a/src/app/components/generic-calculator/calc-name.component.ts
+++ b/src/app/components/generic-calculator/calc-name.component.ts
@@ -5,6 +5,9 @@ import { FormulaireDefinition } from "../../formulaire/definition/form-definitio
 @Component({
     selector: "calc-name",
     templateUrl: "../generic-input/generic-input.component.html",
+    styleUrls: [
+        "./calc-name.component.scss"
+    ]
 })
 export class CalculatorNameComponent extends GenericInputComponent {
 
@@ -55,13 +58,6 @@ export class CalculatorNameComponent extends GenericInputComponent {
         return { isValid: valid, message: msg };
     }
 
-    /**
-     * convertit le modèle en valeur affichable par l'UI
-     */
-    protected modelToUI(v: any): string {
-        return v;
-    }
-
     /**
      * valide une valeur saisie dans l'UI (forme de la saisie : est ce bien une date, un nombre, ...)
      * @param ui valide la valeur saisie
diff --git a/src/app/components/generic-calculator/calculator.component.html b/src/app/components/generic-calculator/calculator.component.html
index 2d00e9c06..e2820e129 100644
--- a/src/app/components/generic-calculator/calculator.component.html
+++ b/src/app/components/generic-calculator/calculator.component.html
@@ -1,53 +1,64 @@
-<div class="row">
-    <!-- titre -->
-    <div class="col">
-        <!-- on utilise [innerHTML] pour que les codes HTML comme &nbsp; soient interprétés correctement -->
-        <h1 [innerHTML]="uitextTitre"></h1>
-    </div>
-
-    <div class="col-sm-3 px-0 mx-0 hyd-window-btns">
-        <!-- bouton d'aide -->
-        <mat-icon *ngIf="enableHelpButton" (click)="openHelp()" color="accent">help</mat-icon>
-        <!-- bouton de sauvegarde -->
-        <mat-icon (click)="saveCalculator()">save_alt</mat-icon>
-        <!-- bouton de fermeture -->
-        <mat-icon (click)="closeCalculator()">close</mat-icon>
-    </div>
-</div>
-
-<!-- nom de la calculette -->
-<div class="row">
-    <div class="col-md-6">
-        <calc-name title="Nom de la calculette"></calc-name>
-    </div>
-</div>
-
-<div class="row">
-    <div [ngClass]="(hasResults) ? 'col-12 col-lg-6' : 'col-12'">
-        <div class="container-fluid">
-            <!-- chapitres -->
-            <ng-template ngFor let-fe [ngForOf]="formElements">
-                <field-set *ngIf="isFieldset(fe)" [style.display]="getFieldsetStyleDisplay(fe.id)" [fieldSet]=fe (radio)=onRadioClick($event)
-                    (validChange)=OnFieldsetValid() (inputChange)=onInputChange()></field-set>
-
-                <fieldset-container *ngIf="isFieldsetContainer(fe)" [_container]=fe (radio)=onRadioClick($event) (validChange)=onFieldsetContainerValid()></fieldset-container>
-            </ng-template>
+<mat-card id="calculator-card">
+
+    <mat-card-header>
+
+        <div class="hyd-window-btns">
+            <!-- bouton d'aide -->
+            <mat-icon *ngIf="enableHelpButton" (click)="openHelp()" color="accent">help</mat-icon>
+            <!-- bouton de sauvegarde -->
+            <mat-icon (click)="saveCalculator()">save_alt</mat-icon>
+            <!-- bouton de fermeture -->
+            <mat-icon (click)="closeCalculator()">close</mat-icon>
         </div>
 
-        <!-- bouton calculer -->
-        <div class="row">
-            <div class="col-12 text-center">
-                <button type="button" [ngClass]="(isCalculateDisabled) ? 'button_compute_err' : 'button_compute_ok'" name="Calculer" (click)="doCompute()"
-                    [disabled]="isCalculateDisabled">{{ uitextCalculer }}</button>
-                <p></p>
-                <p></p>
+        <!-- titre -->
+        <!-- on utilise [innerHTML] pour que les codes HTML comme &nbsp; soient interprétés correctement -->
+        <mat-card-title>
+            <h1 [innerHTML]="uitextTitre"></h1>
+        </mat-card-title>
+
+    </mat-card-header>
+
+    <form>
+
+        <mat-card-content>
+
+            <!-- nom de la calculette -->
+            <calc-name [title]="uitextCalculatorName"></calc-name>
+
+            <div id="calc-cards-container" class="container" fxLayout="row wrap" fxLayoutAlign="space-around start">
+                <!-- chapitres -->
+                <mat-card id="calc-card-field-sets" fxFlex.gt-xs="1 0 400px" fxFlex.lt-sm="1 0 300px">
+                    <ng-template ngFor let-fe [ngForOf]="formElements">
+                        <field-set *ngIf="isFieldset(fe)" [style.display]="getFieldsetStyleDisplay(fe.id)" [fieldSet]=fe (radio)=onRadioClick($event)
+                            (validChange)=OnFieldsetValid() (inputChange)=onInputChange()></field-set>
+
+                        <fieldset-container *ngIf="isFieldsetContainer(fe)" [_container]=fe (radio)=onRadioClick($event) (validChange)=onFieldsetContainerValid()></fieldset-container>
+                    </ng-template>
+
+                    <mat-card-actions>
+                        <!-- bouton calculer -->
+                        <button mat-raised-button color="accent" name="Calculer" (click)="doCompute()"[disabled]="isCalculateDisabled">
+                            {{ uitextCalculer }}
+                        </button>
+                    </mat-card-actions>
+                </mat-card>
+
+                <!-- résultats -->
+                <mat-card id="calc-card-results" fxFlex.gt-xs="1 0 400px" fxFlex.lt-sm="1 0 300px">
+                    <mat-card-header>
+                        <mat-card-title>
+                            Resultats (à traduire)
+                        </mat-card-title>
+                    </mat-card-header>
+                    <mat-card-content>
+                        <calc-results id="resultsComp" (afterViewChecked)="onCalcResultsViewChecked()"></calc-results>
+                    </mat-card-content>
+                </mat-card>
+
             </div>
-        </div>
+        </mat-card-content>
 
-    </div>
+    </form>
 
-    <!-- résultats -->
-    <div [ngClass]="(hasResults) ? 'col-12 col-lg-6' : 'col-12'">
-        <calc-results id="resultsComp" (afterViewChecked)="onCalcResultsViewChecked()"></calc-results>
-    </div>
-</div>
+</mat-card>
diff --git a/src/app/components/generic-calculator/calculator.component.scss b/src/app/components/generic-calculator/calculator.component.scss
index 10f09de7a..699991e24 100644
--- a/src/app/components/generic-calculator/calculator.component.scss
+++ b/src/app/components/generic-calculator/calculator.component.scss
@@ -1,20 +1,60 @@
+.hyd-window-btns {
+    position: absolute;
+    right: 1em;
+    text-align: right;
 
-    .button_compute_ok {
-        height: 3em;
-        width: 30%;
+    mat-icon {
+        cursor: pointer;
+        margin-right: 5px;
     }
+}
 
-    .button_compute_err {
-        height: 3em;
-        width: 30%;
-        color: red
+#calc-cards-container {
+    margin-left: -1em;
+    margin-right: -1em;
+}
+
+mat-card {
+    margin-bottom: 2em;
+
+    // main card
+    &#calculator-card {
+
+        > mat-card-header {
+            margin-bottom: .5em;
+            padding-right: 6em; // space for right corner buttons
+        }
+    }
+
+    // cards inside main card
+
+    &#calc-card-field-sets {
+        margin-left: 1em;
+        margin-right: 1em;
+
+        mat-card-actions {
+            button {
+                width: 100%;
+            }
+        }
     }
 
-    .hyd-window-btns {
-        text-align: right;
+    &#calc-card-results {
+        margin-left: 1em;
+        margin-right: 1em;
+    }
 
-        mat-icon {
-            cursor: pointer;
-            margin-right: 5px;
+    // @WARNING ::ng-deep est déprécié, mais y a rien d'autre pour
+    // l'instant (en attente de normalisation par le W3C)
+    ::ng-deep .mat-card-header-text {
+        margin: 0;
+    }
+
+    mat-card-header {
+
+        mat-card-title {
+            font-size: 16px;
+            margin-bottom: 8px;
         }
-    }
\ No newline at end of file
+    }
+}
diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts
index bdde67531..e696082cb 100644
--- a/src/app/components/generic-calculator/calculator.component.ts
+++ b/src/app/components/generic-calculator/calculator.component.ts
@@ -138,6 +138,10 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
         return this.intlService.localizeText("INFO_CALCULATOR_CALCULER");
     }
 
+    public get uitextCalculatorName() {
+        return this.intlService.localizeText("INFO_CALCULATOR_CALC_NAME");
+    }
+
     ngOnInit() {
         this.intlService.addObserver(this);
         this.formulaireService.addObserver(this);
diff --git a/src/app/components/generic-input/generic-input.component.html b/src/app/components/generic-input/generic-input.component.html
index 1f81ebe23..28d062b05 100644
--- a/src/app/components/generic-input/generic-input.component.html
+++ b/src/app/components/generic-input/generic-input.component.html
@@ -1,6 +1,7 @@
-<div class="md-form form-sm">
-    <input mdbInputDirective [mdbValidate]="false" type="text" [id]="inputId" class="form-control" [disabled]="isDisabled" [(ngModel)]="uiValue">
-    <!-- on utilise [innerHTML] pour que les codes HTML comme &nbsp; soient interprétés correctement -->
-    <label [for]="inputId" [innerHTML]="title"></label>
-    <small *ngIf="showError" class="text-danger" [innerHTML]="errorMessage"></small>
-</div>
\ No newline at end of file
+<mat-form-field>
+    <input matInput class="form-control" type="text" inputmode="numeric"
+        [id]="inputId" [disabled]="isDisabled" [(ngModel)]="uiValue" [placeholder]="title"
+        required>
+
+    <mat-error>{{ errorMessage }}</mat-error>
+</mat-form-field>
diff --git a/src/app/components/generic-input/generic-input.component.ts b/src/app/components/generic-input/generic-input.component.ts
index cd3e9e7dc..15c56f546 100644
--- a/src/app/components/generic-input/generic-input.component.ts
+++ b/src/app/components/generic-input/generic-input.component.ts
@@ -1,17 +1,7 @@
 import { Input, Output, EventEmitter, ChangeDetectorRef, OnChanges } from "@angular/core";
 
 import { BaseComponent } from "../base/base.component";
-
-/*
-exemple de template :
-
-<div class="md-form form-sm">
-    <input mdbActive type="text" id="form1" class="form-control" [disabled]="isDisabled"
-        [ngModel]="uiValue" (ngModelChange)="setUIValue($event)">
-    <label for="form1">{{title}}</label>
-    <small class="text-danger">{{_message}}</small>
-</div>
-*/
+import { isNumeric } from "jalhyd";
 
 /**
  * classe de gestion générique d'un champ de saisie avec titre, validation et message d'erreur
@@ -160,7 +150,7 @@ export abstract class GenericInputComponent extends BaseComponent implements OnC
      * getter du message d'erreur affiché.
      * L'erreur de forme (UI) est prioritaire
      */
-    private get errorMessage() {
+    public get errorMessage() {
         if (this._errorMessageUI !== undefined) {
             return this._errorMessageUI;
         }
@@ -268,7 +258,9 @@ export abstract class GenericInputComponent extends BaseComponent implements OnC
     /**
      * convertit le modèle en valeur affichable par l'UI
      */
-    protected abstract modelToUI(v: any): string;
+    protected modelToUI(v: any): string {
+        return String(v);
+    }
 
     /**
      * valide une valeur saisie dans l'UI (forme de la saisie : est ce bien une date, un nombre, ...)
@@ -276,12 +268,25 @@ export abstract class GenericInputComponent extends BaseComponent implements OnC
      * @returns isValid : true si la valeur est valide, false sinon
      * @returns message : message d'erreur
      */
-    protected abstract validateUIValue(ui: string): { isValid: boolean, message: string };
+    protected validateUIValue(ui: string): { isValid: boolean, message: string } {
+        let valid = false;
+        let msg: string;
+
+        if (! isNumeric(ui)) {
+            msg = "Veuillez entrer une valeur numérique";
+        } else {
+            valid = true;
+        }
+
+        return { isValid: valid, message: msg };
+    }
 
     /**
      * convertit une valeur saisie dans l'UI en valeur affectable au modèle
      */
-    protected abstract uiToModel(ui: string): any;
+    protected uiToModel(ui: string): any {
+        return +ui;
+    }
 }
 
 /*
@@ -298,7 +303,7 @@ import { isNumeric, Message } from "jalhyd";
 @Component({
     selector: "test-input",
     template: `<div class="md-form form-sm">
-    <input mdbActive type="text" id="form1" class="form-control" [disabled]="isDisabled" [(ngModel)]="uiValue">
+    <input  type="text" id="form1" class="form-control" [disabled]="isDisabled" [(ngModel)]="uiValue">
     <label for="form1">{{title}}</label>
     <small *ngIf="showError" class="text-danger">{{errorMessage}}</small>
 </div>`
@@ -329,23 +334,6 @@ export class TestInputComponent extends GenericInputComponent {
         return { isValid: valid, message: msg };
     }
 
-    protected modelToUI(v: any): string {
-        if (typeof (v) === "number")
-            return String(v);
-    }
-
-    protected validateUIValue(ui: string): { isValid: boolean, message: string } {
-        let valid: boolean = false;
-        let msg: string = undefined;
-
-        if (! isNumeric(ui))
-            msg = "Veuillez entrer une valeur numérique";
-        else
-            valid = true;
-
-        return { isValid: valid, message: msg };
-    }
-
     protected uiToModel(ui: string): any {
         return +ui;
     }
@@ -359,7 +347,7 @@ import { ParamDefinition } from "jalhyd";
 @Component({
     selector: "test2-input",
     template: `<div class="md-form form-sm">
-    <input mdbActive type="text" id="form1" class="form-control" [disabled]="isDisabled" [(ngModel)]="uiValue">
+    <input type="text" id="form1" class="form-control" [disabled]="isDisabled" [(ngModel)]="uiValue">
     <label for="form1">{{title}}</label>
     <small *ngIf="showError" class="text-danger">{{errorMessage}}</small>
 </div>`
@@ -393,26 +381,5 @@ export class Test2InputComponent extends GenericInputComponent {
 
         return { isValid: valid, message: msg };
     }
-
-    protected modelToUI(v: any): string {
-        if (typeof (v) === "number")
-            return String(v);
-    }
-
-    protected validateUIValue(ui: string): { isValid: boolean, message: string } {
-        let valid: boolean = false;
-        let msg: string = undefined;
-
-        if (! isNumeric(ui))
-            msg = "Veuillez entrer une valeur numérique";
-        else
-            valid = true;
-
-        return { isValid: valid, message: msg };
-    }
-
-    protected uiToModel(ui: string): any {
-        return +ui;
-    }
 }
 /**/
diff --git a/src/app/components/generic-select/generic-select.component.html b/src/app/components/generic-select/generic-select.component.html
index 8edaab39d..da7a82515 100644
--- a/src/app/components/generic-select/generic-select.component.html
+++ b/src/app/components/generic-select/generic-select.component.html
@@ -1,8 +1,7 @@
-<div class="btn-group" dropdown (click)="onSelect($event)">
-    <button dropdownToggle class="btn btn-primary dropdown-toggle waves-light my-1" type="button" mdbRippleRadius>
-        {{ currentLabel }}
-    </button>
-    <div class="dropdown-menu">
-        <a class="dropdown-item" *ngFor="let e of entries" [value]=e>{{ entryLabel(e) }}</a>
-    </div>
-</div>
\ No newline at end of file
+<mat-form-field>
+    <mat-select [placeholder]="label" [(value)]="selectedValue">
+        <mat-option *ngFor="let e of entries" [value]="e">
+            {{ entryLabel(e) }}
+        </mat-option>
+    </mat-select>
+</mat-form-field>
diff --git a/src/app/components/generic-select/generic-select.component.ts b/src/app/components/generic-select/generic-select.component.ts
deleted file mode 100644
index 6614882ab..000000000
--- a/src/app/components/generic-select/generic-select.component.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import { Component, Output, EventEmitter } from "@angular/core";
-import { BaseComponent } from "../base/base.component";
-
-/*
-  exemple de template :
-
-<div class="btn-group" dropdown (click)="onSelect($event)">
-    <button dropdownToggle class="btn btn-primary dropdown-toggle waves-light my-1" type="button" mdbRippleRadius>
-        {{currentLabel}}
-    </button>
-    <div class="dropdown-menu">
-        <a class="dropdown-item" *ngFor="let e of entries" [value]=e>{{entryLabel(e)}}</a>
-    </div>
-</div>
-*/
-
-export abstract class GenericSelectComponent<T> {
-
-    private _currentLabel: string;
-
-    public get currentLabel(): string {
-        if (! this._currentLabel) {
-            this._currentLabel = this.selectedLabel;
-        }
-        return this._currentLabel;
-    }
-
-    /**
-     * appelé quand la valeur courante change dans l'UI
-     */
-    public onSelect(event: any) {
-        const val = event.target.value;
-        if (val !== undefined && val !== "") { // might be 0; opening the menu returns ""
-            this.selectedValue = val;
-            this._currentLabel = this.selectedLabel;
-        }
-    }
-
-    private get selectedLabel(): string {
-        for (const e of this.entries) {
-            if (e === this.selectedValue) {
-                return this.entryLabel(e);
-            }
-        }
-    }
-
-    /**
-     * liste des objets sélectionnables
-     */
-    public abstract get entries(): any[];
-
-    /**
-     * calcule l'étiquette d'une entrée (ce qui est affiché dans la liste déroulante)
-     */
-    protected abstract entryLabel(entry: any): string;
-
-    /**
-     *  valeur actuellement sélectionnée
-     * la valeur repère une entrée de la liste (cf. [value] dans le template)
-     */
-    public abstract get selectedValue(): T;
-
-
-    public abstract set selectedValue(v: T);
-}
diff --git a/src/app/components/log-entry/log-entry.component.html b/src/app/components/log-entry/log-entry.component.html
index 2c2194f95..4e725d2a8 100644
--- a/src/app/components/log-entry/log-entry.component.html
+++ b/src/app/components/log-entry/log-entry.component.html
@@ -1,10 +1,10 @@
-<div class="row entry">
-    <div class="col-1" style="text-align: center;">
+<div class="entry">
+    <div style="text-align: center;">
         <mat-icon *ngIf="levelInfo" style="color:green">check_circle</mat-icon>
         <mat-icon *ngIf="levelWarning" style="color:orange">error_outline</mat-icon>
         <mat-icon *ngIf="levelError" style="color:red">warning</mat-icon>
     </div>
-    <div class="col-11">
+    <div>
         <!-- on utilise [innerHTML] pour que les codes HTML comme &nbsp; soient interprétés correctement -->
         <span [innerHTML]="text"></span>
     </div>
diff --git a/src/app/components/log/log.component.html b/src/app/components/log/log.component.html
index 15d92c0f1..c2c1364aa 100644
--- a/src/app/components/log/log.component.html
+++ b/src/app/components/log/log.component.html
@@ -1,13 +1,11 @@
-<div class="row" *ngIf="hasEntries">
-    <div class="col-12">
-        <div class="hyd_log">
-            <!-- titre -->
-            <div class="titre">{{ uitextTitreJournal }}</div>
+<div *ngIf="hasEntries">
+    <div class="hyd_log">
+        <!-- titre -->
+        <div class="titre">{{ uitextTitreJournal }}</div>
 
-            <!-- entrées du journal -->
-            <ng-template ngFor let-m [ngForOf]="messages">
-                <log-entry [_message]=m></log-entry>
-            </ng-template>
-        </div>
+        <!-- entrées du journal -->
+        <ng-template ngFor let-m [ngForOf]="messages">
+            <log-entry [_message]=m></log-entry>
+        </ng-template>
     </div>
 </div>
\ No newline at end of file
diff --git a/src/app/components/ngparam-input/ngparam-input.component.scss b/src/app/components/ngparam-input/ngparam-input.component.scss
new file mode 100644
index 000000000..cf6a91666
--- /dev/null
+++ b/src/app/components/ngparam-input/ngparam-input.component.scss
@@ -0,0 +1,13 @@
+:host {
+    display: block;
+    margin-top: 14px;
+}
+
+mat-form-field {
+    width: calc(100% - 16px);
+    margin-right: 16px;
+    
+    ::ng-deep input.mat-input-element {
+        line-height: 1.3em;
+    }
+}
diff --git a/src/app/components/ngparam-input/ngparam-input.component.ts b/src/app/components/ngparam-input/ngparam-input.component.ts
index 11d004d75..69be65ac3 100644
--- a/src/app/components/ngparam-input/ngparam-input.component.ts
+++ b/src/app/components/ngparam-input/ngparam-input.component.ts
@@ -10,7 +10,10 @@ import { GenericInputComponent } from "../generic-input/generic-input.component"
 
 @Component({
     selector: "ngparam-input",
-    templateUrl: "../generic-input/generic-input.component.html"
+    templateUrl: "../generic-input/generic-input.component.html",
+    styleUrls: [
+        "./ngparam-input.component.scss"
+    ]
 })
 export class NgParamInputComponent extends GenericInputComponent implements Observer, OnDestroy {
     /**
@@ -86,27 +89,6 @@ export class NgParamInputComponent extends GenericInputComponent implements Obse
         return { isValid: valid, message: msg };
     }
 
-    protected modelToUI(v: any): string {
-        return String(v);
-    }
-
-    protected validateUIValue(ui: string): { isValid: boolean, message: string } {
-        let valid = false;
-        let msg: string;
-
-        if (! isNumeric(ui)) {
-            msg = "Veuillez entrer une valeur numérique";
-        } else {
-            valid = true;
-        }
-
-        return { isValid: valid, message: msg };
-    }
-
-    protected uiToModel(ui: string) {
-        return +ui;
-    }
-
     public update(sender: any, data: any): void {
         switch (data["action"]) {
             case "ngparamAfterValue":
diff --git a/src/app/components/param-computed/param-computed.component.html b/src/app/components/param-computed/param-computed.component.html
new file mode 100644
index 000000000..0751e36bf
--- /dev/null
+++ b/src/app/components/param-computed/param-computed.component.html
@@ -0,0 +1,7 @@
+<!-- a fake input bound to nothing, for the sake of UI consistency -->
+<mat-form-field>
+    <input matInput disabled class="form-control" type="text" [ngModel]="infoText" [placeholder]="param.title">
+    <button *ngIf="isDicho" mat-icon-button class="param-computed-more" (click)="openDialog()">
+        <mat-icon>more_horiz</mat-icon>
+    </button>
+</mat-form-field>
diff --git a/src/app/components/param-computed/param-computed.component.scss b/src/app/components/param-computed/param-computed.component.scss
new file mode 100644
index 000000000..100e77a81
--- /dev/null
+++ b/src/app/components/param-computed/param-computed.component.scss
@@ -0,0 +1,21 @@
+:host {
+    display: block;
+    margin-top: 14px;
+
+    mat-form-field {
+        width: calc(100% - 16px);
+        margin-right: 16px;
+
+        ::ng-deep input.mat-input-element {
+            line-height: 1.3em;
+            width: calc(100% - 40px);
+            text-overflow: ellipsis;
+        }
+
+        .param-computed-more {
+            position: absolute;
+            bottom: 0;
+            right: 0;
+        }
+    }
+}
diff --git a/src/app/components/param-computed/param-computed.component.ts b/src/app/components/param-computed/param-computed.component.ts
new file mode 100644
index 000000000..a1148d90b
--- /dev/null
+++ b/src/app/components/param-computed/param-computed.component.ts
@@ -0,0 +1,50 @@
+import { Component, Input } from "@angular/core";
+import { MatDialog } from "@angular/material";
+import { NgParameter } from "../../formulaire/ngparam";
+import { ParamCalculability } from "jalhyd";
+import { DialogEditParamComputedComponent } from "../dialog-edit-param-computed/dialog-edit-param-computed.component";
+
+@Component({
+    selector: "param-computed",
+    templateUrl: "./param-computed.component.html",
+    styleUrls: [
+        "./param-computed.component.scss"
+    ]
+})
+export class ParamComputedComponent {
+
+    @Input()
+    public param: NgParameter;
+
+    @Input()
+    public title: string;
+
+    constructor(
+        private editInitialValueDialog: MatDialog
+    ) { }
+
+    public get isDicho() {
+        return this.param.paramDefinition.calculability === ParamCalculability.DICHO;
+    }
+
+    public get infoText() {
+        let text = "En calcul (à traduire)"; // @TODO traduire "in calculation"
+        if (this.isDicho) {
+            text += " (valeur initiale: " + this.param.getValue() + ")";
+        }
+        return text;
+    }
+
+    public openDialog() {
+        // modification de la valeur initiale, sans avoir à remettre le mode de
+        // paramètre sur "fixé"
+        this.editInitialValueDialog.open(
+            DialogEditParamComputedComponent,
+            {
+                data: {
+                    param: this.param
+                }
+            }
+        );
+    }
+}
diff --git a/src/app/components/param-field-line/param-field-line.component.html b/src/app/components/param-field-line/param-field-line.component.html
index 41c5d1fea..90023511e 100644
--- a/src/app/components/param-field-line/param-field-line.component.html
+++ b/src/app/components/param-field-line/param-field-line.component.html
@@ -1,43 +1,49 @@
-<div class="row">
-    <!-- input de saisie de la valeur -->
-    <div [ngClass]="(formHasResults) ? 'col-xl-6 pt-3':'col-md-6 col-xl-8 pt-3'">
-        <ngparam-input [_inputDisabled]="isInputDisabled" [title]="title" (change)="onInputChange($event)"></ngparam-input>
-    </div>
 
-    <div class="btn-group col" role="group">
-        <!-- px-3 : padding left/right 3 -->
-        <!-- py-3 : padding top/bottom 3 -->
-        <!-- h-50 : hauteur relative de l'élément par rapport au parent à 50%-->
-        <!-- cf. https://getbootstrap.com/docs/4.0/utilities/spacing -->
+<div class="container" fxLayout="row wrap" fxLayoutAlign="space-between start">
+
+    <!-- input de saisie de la valeur -->
+    <div fxFlex="1 0 120px">
+        <!-- composant pour gérer le cas général (valeur numérique à saisir) -->
+        <ngparam-input [title]="param.title" [hidden]="! isRadioFixChecked" (change)="onInputChange($event)"></ngparam-input>
+ 
+        <!-- composant pour gérer le cas "paramètre calculé" -->
+        <param-computed *ngIf="isRadioCalChecked" [title]="title" [param]="param"></param-computed>
 
-        <!-- radio "fixé" -->
-        <label *ngIf="hasRadioFix()" class="{{radioFixClass}} h-75 px-3 py-3" [(ngModel)]="radioModel" mdbRadio="Left" name="radio_param_{{symbol}}"
-            value="fix" (click)="onRadioClick('fix')" [checked]=radioFixCheck [disabled]=isDisabled id="radio_fix">
-            {{ uitextParamFixe }}
-        </label>
+        <!-- composant pour gérer le cas "paramètre à varier" (min-max/liste de valeurs) -->
+        <param-values *ngIf="isRadioVarChecked" [title]="title" [param]="param" (valid)=onParamValuesValid($event)></param-values>
+    
+        <!-- composant pour gérer le cas "paramètre lié" -->
+        <param-link *ngIf="isRadioLinkChecked" [title]="title" [param]="param" (valid)=onParamValuesValid($event)></param-link>
+    </div>
 
-        <!-- radio "varier" -->
-        <label *ngIf="hasRadioVar()" class="{{radioVarClass}} h-75 px-3 py-3" [(ngModel)]="radioModel" mdbRadio="Middle" name="radio_param_{{symbol}}"
-            value="var" (click)="onRadioClick('var')" [checked]=radioVarCheck [disabled]=isDisabled id="radio_var">
-            {{ uitextParamVarier }}
-        </label>
+    <div class="toggle-group-container" fxFlex="0 0 auto">
+        <mat-button-toggle-group>
 
-        <!-- radio "calculer" -->
-        <label *ngIf="hasRadioCal()" class="{{radioCalClass}} h-75 px-3 py-3" [(ngModel)]="radioModel" mdbRadio="Right" name="radio_param_{{symbol}}"
-            value="cal" (click)="onRadioClick('cal')" [checked]=radioCalCheck [disabled]=isDisabled id="radio_cal">
-            {{ uitextParamCalculer }}
-        </label>
+            <mat-button-toggle id="radio_fix" value="radio_fix" *ngIf="hasRadioFix()"
+                (click)="onRadioClick('fix')" [checked]="isRadioFixChecked">
+                <span fxHide.xxs>{{ uitextParamFixe }}</span>
+                <span fxHide.gt-xxs>F</span>
+            </mat-button-toggle>
+        
+            <mat-button-toggle id="radio_var" value="radio_var" *ngIf="hasRadioVar()"
+                (click)="onRadioClick('var')" [checked]="isRadioVarChecked">
+                <span fxHide.xxs>{{ uitextParamVarier }}</span>
+                <span fxHide.gt-xxs>V</span>
+            </mat-button-toggle>
+        
+            <mat-button-toggle id="radio_cal" value="radio_cal" *ngIf="hasRadioCal()"
+                (click)="onRadioClick('cal')" [checked]="isRadioCalChecked">
+                <span fxHide.xxs>{{ uitextParamCalculer }}</span>
+                <span fxHide.gt-xxs>C</span>
+            </mat-button-toggle>
+        
+            <mat-button-toggle id="radio_link" value="radio_link" *ngIf="hasRadioLink()"
+                (click)="onRadioClick('link')" [checked]="isRadioLinkChecked">
+                <span fxHide.xxs>{{ uitextParamLie }}</span>
+                <span fxHide.gt-xxs>L</span>
+            </mat-button-toggle>
 
-        <!-- radio "lié" -->
-        <label *ngIf="hasRadioLink()" class="{{radioLinkClass}} h-75 px-3 py-3" [(ngModel)]="radioModel" mdbRadio="Right" name="radio_param_{{symbol}}"
-            value="link" (click)="onRadioClick('link')" [checked]=radioLinkCheck [disabled]=isDisabled id="radio_link">
-            {{ uitextParamLie }}
-        </label>
+        </mat-button-toggle-group>
     </div>
-</div>
 
-<!-- composant pour gérer le cas "paramètre à varier" (min-max/liste de valeurs) -->
-<param-values *ngIf="isRadioVarChecked" [param]="param" (valid)=onParamValuesValid($event)></param-values>
-
-<!-- composant pour gérer le cas "paramètre lié" -->
-<param-link *ngIf="isRadioLinkChecked" [param]="param" (valid)=onParamValuesValid($event)></param-link>
\ No newline at end of file
+</div>
diff --git a/src/app/components/param-field-line/param-field-line.component.scss b/src/app/components/param-field-line/param-field-line.component.scss
new file mode 100644
index 000000000..4cfe3dad9
--- /dev/null
+++ b/src/app/components/param-field-line/param-field-line.component.scss
@@ -0,0 +1,13 @@
+.toggle-group-container {
+    text-align: right;
+}
+
+mat-button-toggle-group {
+    margin-top: 4px;
+    box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12);
+
+    /*::ng-deep .mat-button-toggle-label-content {
+        line-height: 32px;
+        padding: 0 10px;
+    }*/
+}
diff --git a/src/app/components/param-field-line/param-field-line.component.ts b/src/app/components/param-field-line/param-field-line.component.ts
index 82608891a..50f368d07 100644
--- a/src/app/components/param-field-line/param-field-line.component.ts
+++ b/src/app/components/param-field-line/param-field-line.component.ts
@@ -7,25 +7,16 @@ import { ServiceFactory } from "../../services/service-factory";
 import { ParamValueMode, CalculatorType, ParallelStructure } from "jalhyd";
 import { FormulaireService } from "../../services/formulaire/formulaire.service";
 import { ParamLinkComponent } from "../param-link/param-link.component";
+import { ParamComputedComponent } from "../param-computed/param-computed.component";
 
+/**
+ * Sélecteur de mode pour chaque paramètre: fixé, varier, calculer, lié
+ */
 @Component({
     selector: "param-field-line",
     templateUrl: "./param-field-line.component.html",
-    styles: [
-        `.btn-on {
-            color:#505050;
-            border: 3px solid #505050;
-            background-color:white;
-            text-transform: uppercase;
-            font-size: 0.8em;
-        }`,
-        `.btn-off {
-            color:white;
-            border: 3px solid #505050;
-            background-color:#505050;
-            text-transform: uppercase;
-            font-size: 0.8em;
-        }`
+    styleUrls: [
+        "./param-field-line.component.scss"
     ]
 })
 export class ParamFieldLineComponent implements OnChanges {
@@ -37,17 +28,6 @@ export class ParamFieldLineComponent implements OnChanges {
         this.inputChange = new EventEmitter();
     }
 
-    public get title(): string {
-        let t = "";
-        if (this.param.label !== undefined) {
-            t = this.param.label;
-        }
-        if (this.param.unit !== undefined && this.param.unit !== "") {
-            t = t + " (" + this.param.unit + ")";
-        }
-        return t;
-    }
-
     private get uitextParamFixe() {
         return this.intlService.localizeText("INFO_PARAMFIELD_PARAMFIXE");
     }
@@ -71,110 +51,36 @@ export class ParamFieldLineComponent implements OnChanges {
         return this.param.symbol;
     }
 
-    /**
-    * calcule l'état du radio "paramètre fixé"
-    */
+    // états des boutons pour l'interface
+
     private get radioFixCheck(): string {
         return this.isRadioFixChecked ? "checked" : undefined;
     }
-
-    /**
-    * calcule l'état du radio "paramètre à varier"
-    */
     private get radioVarCheck(): string {
         return this.isRadioVarChecked ? "checked" : undefined;
     }
-
-    /**
-    * calcule l'état du radio "paramètre à calculer"
-    */
     private get radioCalCheck(): string {
-        if (this.param.radioState === ParamRadioConfig.CAL) {
-            return "checked";
-        }
+        return this.isRadioCalChecked ? "checked" : undefined;
     }
-
-    /**
-    * calcule l'état du radio "paramètre lié"
-    */
     private get radioLinkCheck(): string {
-        if (this.param.radioState === ParamRadioConfig.LINK) {
-            return "checked";
-        }
+        return this.isRadioLinkChecked ? "checked" : undefined;
     }
 
-    /**
-     * retourne l'état du radio "paramètre fixé" sous forme booléenne
-     */
+    // états booléens des boutons
+
     public get isRadioFixChecked(): boolean {
         return this.param.radioState === ParamRadioConfig.FIX;
     }
-
-    /**
-     * retourne l'état du radio "paramètre à varier" sous forme booléenne
-     */
     public get isRadioVarChecked(): boolean {
         return this.param.radioState === ParamRadioConfig.VAR;
     }
-
-    /**
-     * retourne l'état du radio "paramètre lié" sous forme booléenne
-     */
+    public get isRadioCalChecked(): boolean {
+        return this.param.radioState === ParamRadioConfig.CAL;
+    }
     public get isRadioLinkChecked(): boolean {
         return this.param.radioState === ParamRadioConfig.LINK;
     }
 
-    /**
-     * désactivation de tous les boutons radio si paramètre par défaut à "CAL"
-     */
-    private get isDisabled(): boolean {
-        return this.param.isDefault && this.param.radioState === ParamRadioConfig.CAL;
-    }
-
-    /**
-     * désactivation du champ de saisie
-     */
-    public get isInputDisabled(): boolean {
-        return this.param.radioState !== ParamRadioConfig.FIX;
-    }
-
-    private get radioFixClass(): string {
-        if (this.on) {
-            return this.radioFixCheck ? this.onClass : this.offClass;
-        }
-        return "";
-    }
-
-    /**
-     * classe du radio "varier"
-     */
-    private get radioVarClass(): string {
-        if (this.on) {
-            return this.radioVarCheck ? this.onClass : this.offClass;
-        }
-        return "";
-    }
-
-    /**
-     * classe du radio "calculer"
-     */
-    private get radioCalClass(): string {
-        if (this.on) {
-            return this.radioCalCheck ? this.onClass : this.offClass;
-        }
-        return "";
-    }
-
-    /**
-     * classe du radio "lié"
-     */
-    private get radioLinkClass(): string {
-        if (this.on) {
-            return this.radioLinkCheck ? this.onClass : this.offClass;
-        }
-        return "";
-    }
-
     /**
      * validité des saisies du composant
      */
@@ -186,6 +92,10 @@ export class ParamFieldLineComponent implements OnChanges {
             case ParamRadioConfig.VAR:
                 return this._isRangeValid;
 
+            case ParamRadioConfig.LINK:
+                // at first this._paramLinkComponent is undefined until view is refreshed
+                return this._paramLinkComponent ? this._paramLinkComponent.isValid : true;
+
             default:
                 return true;
         }
@@ -196,11 +106,14 @@ export class ParamFieldLineComponent implements OnChanges {
     }
 
     @Input()
-    private param: NgParameter;
+    public param: NgParameter;
 
     @ViewChild(NgParamInputComponent)
     private _ngParamInputComponent: NgParamInputComponent;
 
+    @ViewChild(ParamComputedComponent)
+    private _computedParamComponent: ParamComputedComponent;
+
     @ViewChild(ParamLinkComponent)
     private _paramLinkComponent: ParamLinkComponent;
 
@@ -210,14 +123,10 @@ export class ParamFieldLineComponent implements OnChanges {
     @Output()
     private inputChange: EventEmitter<void>;
 
-    /**
-     * true si la valeur saisie est valide
-     */
+    /** true si la valeur saisie est valide */
     private _isInputValid = false;
 
-    /**
-     * true si le min-max/liste est valide
-     */
+    /** true si le min-max/liste est valide */
     private _isRangeValid = true;
 
     private intlService: I18nService;
@@ -229,7 +138,6 @@ export class ParamFieldLineComponent implements OnChanges {
      * envoi d'un message au composant parent
      * cf. https://angular.io/guide/component-interaction#parent-listens-for-child-event
      */
-
     @Output()
     private radio = new EventEmitter<any>();
 
@@ -246,7 +154,7 @@ export class ParamFieldLineComponent implements OnChanges {
     public hasRadioFix(): boolean {
         switch (this.param.radioConfig) {
             case ParamRadioConfig.FIX:
-                return this.hasRadioLink();
+                return this.hasRadioLink(); // gné ?
 
             default:
                 return true;
@@ -304,16 +212,19 @@ export class ParamFieldLineComponent implements OnChanges {
 
     private onRadioClick(option: string) {
         const oldValue = this.param.valueMode;
-
         switch (option) {
             case "fix":
-                const oldValueMode = this.param.valueMode;
                 this.param.valueMode = ParamValueMode.SINGLE;
+                // @WTF why do we reset the value here ?
                 this.param.setValue(this, this.param.paramDefinition.paramValues.singleValue);
                 break;
 
             case "var":
-                this.param.valueMode = ParamValueMode.MINMAX; // min/max par défaut
+                // prevent setting LISTE mode back to MINMAX if someone clicks "variable" again
+                // after setting variable mode to LISTE
+                if (oldValue !== ParamValueMode.MINMAX && oldValue !== ParamValueMode.LISTE) {
+                    this.param.valueMode = ParamValueMode.MINMAX; // min/max par défaut
+                }
                 break;
 
             case "cal":
@@ -324,12 +235,10 @@ export class ParamFieldLineComponent implements OnChanges {
                 this.param.valueMode = ParamValueMode.LINK;
                 break;
         }
-
         this.radio.emit({
             "param": this.param,
             "oldValueMode": oldValue
         });
-
         // MAJ validité
         this.emitValidity();
     }
diff --git a/src/app/components/param-link/param-link.component.html b/src/app/components/param-link/param-link.component.html
index f7289b739..5b198816b 100644
--- a/src/app/components/param-link/param-link.component.html
+++ b/src/app/components/param-link/param-link.component.html
@@ -1,13 +1,10 @@
-<div class="row">
-    <div class="btn-group col-6 col-sm-3" dropdown (click)="onSelectLinkableParam($event)">
-        <button dropdownToggle class="btn btn-primary dropdown-toggle waves-light my-1" type="button" mdbRippleRadius>
-            {{ currentLinkedParamLabel }}
-        </button>
-        <div class="dropdown-menu">
-            <a class="dropdown-item" *ngFor="let e of linkableParams" [value]=e>{{ selectItemLabel(e) }}</a>
-        </div>
-    </div>
-    <div class="col-6 text-danger">
+<mat-form-field>
+    <mat-select [name]='"linked-param_" + param.uid' required [placeholder]="param.title" [(ngModel)]="currentLinkedParam">
+        <mat-option *ngFor="let e of linkableParams" [value]="e">
+            {{ selectItemLabel(e) }}
+        </mat-option>
+    </mat-select>
+    <mat-error>
         {{ message }}
-    </div>
-</div>
\ No newline at end of file
+    </mat-error>
+</mat-form-field>
diff --git a/src/app/components/param-link/param-link.component.scss b/src/app/components/param-link/param-link.component.scss
new file mode 100644
index 000000000..d4cfda5d9
--- /dev/null
+++ b/src/app/components/param-link/param-link.component.scss
@@ -0,0 +1,23 @@
+:host {
+    display: block;
+    // width: 100%;
+    // width: 70%; min-width: 264px; // for smallest screens (360)
+    // get the select closer to the related param line above
+    margin-top: 14px;
+
+    mat-form-field {
+        width: calc(100% - 16px);
+        margin-right: 16px;
+
+        mat-select {
+    
+            ::ng-deep .mat-select-value {
+                > span {
+                    > span {
+                        line-height: 1.3em;
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/src/app/components/param-link/param-link.component.ts b/src/app/components/param-link/param-link.component.ts
index b0246dee5..4add3db05 100644
--- a/src/app/components/param-link/param-link.component.ts
+++ b/src/app/components/param-link/param-link.component.ts
@@ -7,12 +7,18 @@ import { FormulaireService } from "../../services/formulaire/formulaire.service"
 
 @Component({
     selector: "param-link",
-    templateUrl: "./param-link.component.html"
+    templateUrl: "./param-link.component.html",
+    styleUrls: [
+        "./param-link.component.scss"
+    ]
 })
 export class ParamLinkComponent implements OnChanges, Observer, OnDestroy {
     // paramètre géré (qui sera lié à une valeur, cad qui importe cette valeur)
     @Input()
-    private param: NgParameter;
+    public param: NgParameter;
+
+    @Input()
+    public title: string;
 
     @Output()
     private valid: EventEmitter<boolean>;
@@ -47,6 +53,8 @@ export class ParamLinkComponent implements OnChanges, Observer, OnDestroy {
 
     private _formService: FormulaireService;
 
+    public label = "Choix du paramètre lié (à traduire)";
+
     constructor() {
         this.valid = new EventEmitter();
         this._formService = ServiceFactory.instance.formulaireService;
@@ -61,27 +69,21 @@ export class ParamLinkComponent implements OnChanges, Observer, OnDestroy {
         return this._message;
     }
 
-    /**
-     * envoi d'un événement de validité
-     */
-    private emitValidity() {
-        // this.valid.emit(this._validList);
+    public set currentLinkedParam(p: any) {
+        for (let i = 0; i < this._linkableParams.length; i++) {
+            if (this._linkableParams[i].value.uid === p.uid) {
+                this.linkTo(i);
+                break;
+            } else {
+                i++;
+            }
+        }
     }
 
-    /**
-     * réception d'un événement du menu des paramètres liables
-     */
-    public onSelectLinkableParam(event: any) {
-        const next = event.target.value;
-        if (next !== undefined && next !== "") { // opening the dropdown returns ""
-            let i = 0;
-            for (const e of this._linkableParams) {
-                if (this._linkableParams[i].value.uid === next.value.uid) {
-                    this.linkTo(i);
-                    break;
-                } else {
-                    i++;
-                }
+    public get currentLinkedParam() {
+        if (this._linkableParams !== undefined) {
+            if (this._currentIndex !== -1 && this._currentIndex < this._linkableParams.length) {
+                return this._linkableParams[this._currentIndex];
             }
         }
     }
@@ -90,13 +92,16 @@ export class ParamLinkComponent implements OnChanges, Observer, OnDestroy {
      * valeur courante affichée dans le select des paramètres liables
      */
     public get currentLinkedParamLabel(): string {
-        if (this._linkableParams !== undefined) {
-            if (this._currentIndex !== -1 && this._currentIndex < this._linkableParams.length) {
-                return this.selectItemLabel(this._linkableParams[this._currentIndex]);
-            }
+        const clp = this.currentLinkedParam();
+        if (clp) {
+            return this.selectItemLabel(clp);
         }
     }
 
+    public get isValid(): boolean {
+        return this._currentIndex !== -1 && this.param.isValid;
+    }
+
     /**
      * attribut "label" d'une entrée du select des paramètres
      */
diff --git a/src/app/components/param-values/ngparam-max.component.ts b/src/app/components/param-values/ngparam-max.component.ts
deleted file mode 100644
index d878dd68b..000000000
--- a/src/app/components/param-values/ngparam-max.component.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-import { Component, Input, ChangeDetectorRef } from "@angular/core";
-
-import { isNumeric } from "jalhyd";
-
-import { GenericInputComponent } from "../generic-input/generic-input.component";
-import { I18nService } from "../../services/internationalisation/internationalisation.service";
-import { NgParameter } from "../../formulaire/ngparam";
-
-@Component({
-    selector: "ngparam-max",
-    templateUrl: "../generic-input/generic-input.component.html"
-})
-export class NgParamMaxComponent extends GenericInputComponent {
-    constructor(private intlService: I18nService, cdRef: ChangeDetectorRef) {
-        super(cdRef);
-    }
-
-    /**
-     * paramètre géré
-     */
-    private get _param(): NgParameter {
-        return this._model;
-    }
-
-    protected getModelValue(): any {
-        if (this._param) {
-            return this._param.maxValue;
-        }
-    }
-
-    protected setModelValue(sender: any, v: any) {
-        this._param.maxValue = v;
-    }
-
-    protected validateModelValue(v: any): { isValid: boolean, message: string } {
-        let msg;
-        let valid = false;
-
-        if (this._param === undefined) {
-            msg = "internal error, model undefined";
-        } else {
-            if (!this._param.checkMax(v)) {
-                msg = "La valeur n'est pas dans ]" + this._param.minValue + " , " + this._param.domain.maxValue + "]";
-            } else {
-                valid = true;
-            }
-        }
-
-        return { isValid: valid, message: msg };
-    }
-
-    protected modelToUI(v: any): string {
-        if (typeof (v) === "number") {
-            return String(v);
-        }
-    }
-
-    protected validateUIValue(ui: string): { isValid: boolean, message: string } {
-        let valid = false;
-        let msg: string;
-
-        if (! isNumeric(ui)) {
-            msg = "Veuillez entrer une valeur numérique";
-        } else {
-            valid = true;
-        }
-
-        return { isValid: valid, message: msg };
-    }
-
-    protected uiToModel(ui: string) {
-        return +ui;
-    }
-}
diff --git a/src/app/components/param-values/ngparam-min.component.ts b/src/app/components/param-values/ngparam-min.component.ts
deleted file mode 100644
index 687df8d0e..000000000
--- a/src/app/components/param-values/ngparam-min.component.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-import { Component, Input, ChangeDetectorRef } from "@angular/core";
-
-import { isNumeric } from "jalhyd";
-
-import { GenericInputComponent } from "../generic-input/generic-input.component";
-import { I18nService } from "../../services/internationalisation/internationalisation.service";
-import { NgParameter } from "../../formulaire/ngparam";
-
-@Component({
-    selector: "ngparam-min",
-    templateUrl: "../generic-input/generic-input.component.html"
-})
-export class NgParamMinComponent extends GenericInputComponent {
-    constructor(private intlService: I18nService, cdRef: ChangeDetectorRef) {
-        super(cdRef);
-    }
-
-    /**
-     * paramètre géré
-     */
-    private get _param(): NgParameter {
-        return this._model;
-    }
-
-    protected getModelValue(): any {
-        if (this._param) {
-            return this._param.minValue;
-        }
-    }
-
-    protected setModelValue(sender: any, v: any) {
-        this._param.minValue = v;
-    }
-
-    protected validateModelValue(v: any): { isValid: boolean, message: string } {
-        let msg: string;
-        let valid = false;
-
-        if (this._param === undefined) {
-            msg = "internal error, model undefined";
-        } else {
-            if (!this._param.checkMin(v)) {
-                msg = "La valeur n'est pas dans [" + this._param.domain.minValue + " , " + this._param.maxValue + "[";
-            } else {
-                valid = true;
-            }
-        }
-
-        return { isValid: valid, message: msg };
-    }
-
-    protected modelToUI(v: any): string {
-        if (typeof (v) === "number") {
-            return String(v);
-        }
-    }
-
-    protected validateUIValue(ui: string): { isValid: boolean, message: string } {
-        let valid = false;
-        let msg: string;
-
-        if (! isNumeric(ui)) {
-            msg = "Veuillez entrer une valeur numérique";
-        } else {
-            valid = true;
-        }
-
-        return { isValid: valid, message: msg };
-    }
-
-    protected uiToModel(ui: string): any {
-        return +ui;
-    }
-}
diff --git a/src/app/components/param-values/ngparam-step.component.ts b/src/app/components/param-values/ngparam-step.component.ts
deleted file mode 100644
index ac0fd27fd..000000000
--- a/src/app/components/param-values/ngparam-step.component.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-import { Component, Input, ChangeDetectorRef } from "@angular/core";
-
-import { isNumeric } from "jalhyd";
-
-import { GenericInputComponent } from "../generic-input/generic-input.component";
-import { I18nService } from "../../services/internationalisation/internationalisation.service";
-import { NgParameter } from "../../formulaire/ngparam";
-
-@Component({
-    selector: "ngparam-step",
-    templateUrl: "../generic-input/generic-input.component.html"
-})
-export class NgParamStepComponent extends GenericInputComponent {
-    constructor(private intlService: I18nService, cdRef: ChangeDetectorRef) {
-        super(cdRef);
-    }
-
-    /**
-     * paramètre géré
-     */
-    private get _param(): NgParameter {
-        return this._model;
-    }
-
-    protected getModelValue(): any {
-        if (this._param) {
-            return this._param.stepValue;
-        }
-    }
-
-    protected setModelValue(sender: any, v: any) {
-        this._param.stepValue = v;
-    }
-
-    protected validateModelValue(v: any): { isValid: boolean, message: string } {
-        let msg: string;
-        let valid = false;
-
-        if (! this._param) {
-            msg = "internal error, model undefined";
-        } else {
-            if (this._param.isMinMaxValid) {
-                if (!this._param.checkStep(v)) {
-                    msg = "La valeur n'est pas dans " + this._param.stepRefValue.toString();
-                } else {
-                    valid = v > 0;
-                    if (!valid) {
-                        msg = "La valeur ne peut pas être <= 0";
-                    }
-                }
-            } else {
-                msg = "Veuillez corriger le min/max";
-            }
-        }
-
-        return { isValid: valid, message: msg };
-    }
-
-    protected modelToUI(v: any): string {
-        if (typeof (v) === "number") {
-            return String(v);
-        }
-        return "<invalid>";
-    }
-
-    protected validateUIValue(ui: string): { isValid: boolean, message: string } {
-        let valid = false;
-        let msg: string;
-
-        if (! isNumeric(ui)) {
-            msg = "Veuillez entrer une valeur numérique";
-        } else {
-            valid = true;
-        }
-
-        return { isValid: valid, message: msg };
-    }
-
-    protected uiToModel(ui: string) {
-        return +ui;
-    }
-}
diff --git a/src/app/components/param-values/param-values.component.html b/src/app/components/param-values/param-values.component.html
index f4dc49687..37cda318b 100644
--- a/src/app/components/param-values/param-values.component.html
+++ b/src/app/components/param-values/param-values.component.html
@@ -1,24 +1,7 @@
-<div class="row">
-    <div class="btn-group col-12 col-sm-3" dropdown (click)="onSelectValueMode($event)">
-        <button dropdownToggle class="btn btn-primary dropdown-toggle waves-light my-1" type="button" mdbRippleRadius>
-            {{ currentModeSelectLabel }}
-        </button>
-        <div class="dropdown-menu">
-            <a class="dropdown-item" *ngFor="let e of valueModes" [value]=e.value>{{ e.label }}</a>
-        </div>
-    </div>
-
-    <div *ngIf="isMinMax" class="col-12 col-sm-3">
-        <ngparam-min [title]="uitextValeurMini" (onChange)="onMinChanged($event)"></ngparam-min>
-    </div>
-    <div *ngIf="isMinMax" class="col-12 col-sm-3">
-        <ngparam-max [title]="uitextValeurMaxi" (onChange)="onMaxChanged($event)"></ngparam-max>
-    </div>
-    <div *ngIf="isMinMax" class="col-12 col-sm-3">
-        <ngparam-step [title]="uitextPasVariation" [param]="param" (onChange)="onStepChanged($event)"></ngparam-step>
-    </div>
-
-    <div *ngIf="isList" class="col-12 col-sm-6">
-        <value-list title="valeurs séparées par ';'" (onChange)="onListChanged($event)"></value-list>
-    </div>
-</div>
\ No newline at end of file
+<!-- a fake input bound to nothing, for the sake of UI consistency -->
+<mat-form-field>
+    <input matInput disabled class="form-control" type="text" [ngModel]="infoText" [placeholder]="param.title">
+    <button mat-icon-button class="param-values-more" (click)="openDialog()">
+        <mat-icon>more_horiz</mat-icon>
+    </button>
+</mat-form-field>
diff --git a/src/app/components/param-values/param-values.component.scss b/src/app/components/param-values/param-values.component.scss
new file mode 100644
index 000000000..720b9b04a
--- /dev/null
+++ b/src/app/components/param-values/param-values.component.scss
@@ -0,0 +1,21 @@
+:host {
+    display: block;
+    margin-top: 14px;
+
+    mat-form-field {
+        width: calc(100% - 16px);
+        margin-right: 16px;
+
+        ::ng-deep input.mat-input-element {
+            line-height: 1.3em;
+            width: calc(100% - 40px);
+            text-overflow: ellipsis;
+        }
+
+        .param-values-more {
+            position: absolute;
+            bottom: 0;
+            right: 0;
+        }
+    }
+}
diff --git a/src/app/components/param-values/param-values.component.ts b/src/app/components/param-values/param-values.component.ts
index d7f2b4b57..221442da9 100644
--- a/src/app/components/param-values/param-values.component.ts
+++ b/src/app/components/param-values/param-values.component.ts
@@ -1,361 +1,67 @@
-import { Component, Input, Output, EventEmitter, ViewChild, AfterViewChecked, OnChanges } from "@angular/core";
-
-import { ParamValueMode } from "jalhyd";
-
-import { I18nService } from "../../services/internationalisation/internationalisation.service";
+import { Component, Input, AfterViewInit } from "@angular/core";
 import { NgParameter } from "../../formulaire/ngparam";
-import { NgParamMinComponent } from "./ngparam-min.component";
-import { NgParamMaxComponent } from "./ngparam-max.component";
-import { NgParamStepComponent } from "./ngparam-step.component";
-import { BaseComponent } from "../base/base.component";
-import { ValueListComponent } from "./value-list.component";
+import { DialogEditParamValuesComponent } from "../dialog-edit-param-values/dialog-edit-param-values.component";
+import { MatDialog } from "@angular/material";
+import { ParamValueMode } from "jalhyd";
 
 @Component({
     selector: "param-values",
     templateUrl: "./param-values.component.html",
-    styles: [
-        `.btn-on {
-            color:#505050;
-            border: 3px solid #505050;
-            background-color:white;
-            text-transform: uppercase;
-            font-size: 0.8em;
-        }`,
-        `.btn-off {
-            color:white;
-            border: 3px solid #505050;
-            background-color:#505050;
-            text-transform: uppercase;
-            font-size: 0.8em;
-        }`
+    styleUrls: [
+        "./param-values.component.scss"
     ]
 })
-export class ParamValuesComponent extends BaseComponent implements AfterViewChecked, OnChanges {
-    @Input()
-    private param: NgParameter;
-
-    private _valueModes = [];
-
-    /**
-     * true quand les champs du composant et de ses enfants sont initialisés
-     */
-    private _initCompleted = false;
-
-    /**
-     * true si la valeur min est valide
-     */
-    private _validMin = false;
-
-    /**
-     * true si la valeur max est valide
-     */
-    private _validMax = false;
-
-    /**
-     * true si la valeur du pas est valide
-     */
-    private _validStep = false;
-
-    /**
-     * true si la liste de valeurs est valide
-     */
-    private _validList = false;
-
-    /**
-     * flag signalant qu'il faut initialiser le composant ValueListComponent (par ex quand on a sélectionné le mode "liste")
-     */
-    private _doInitList = false;
-
-    /**
-     * flag signalant qu'il faut initialiser les composants min/max/pas
-     */
-    private _doInitMinmax = false;
-
-    /**
-     * composant de saisie du minimum
-     */
-    @ViewChild(NgParamMinComponent)
-    private _minComponent: NgParamMinComponent;
-
-    /**
-     * composant de saisie du maximum
-     */
-    @ViewChild(NgParamMaxComponent)
-    private _maxComponent: NgParamMaxComponent;
-
-    /**
-     * composant de saisie du pas de variation
-     */
-    @ViewChild(NgParamStepComponent)
-    private _stepComponent: NgParamStepComponent;
-
-    /**
-     * composant de saisie d'une liste de valeurs
-     */
-    @ViewChild(ValueListComponent)
-    private _listComponent: ValueListComponent;
-
-    @Output()
-    private valid: EventEmitter<boolean>;
-
-    constructor(private intlService: I18nService) {
-        super();
-        this._valueModes.push({ "value": ParamValueMode.MINMAX, "label": "Min/max" });
-        this._valueModes.push({ "value": ParamValueMode.LISTE, "label": "Liste" });
-        this.valid = new EventEmitter();
-    }
-
-    /**
-     * init des champs min/max/pas
-     */
-    private initMinMaxStep() {
-        if (this.isMinMax && this._doInitMinmax) {
-            this._doInitMinmax = false;
-
-            // valeur pour min : celle déjà définie ou celle déduite de la valeur saisie
-            let min: number = this.param.minValue;
-            if (min === undefined) {
-                min = this.param.getValue() / 2;
-            }
-
-            // valeur pour max : celle déjà définie ou celle déduite de la valeur saisie
-            let max: number = this.param.maxValue;
-            if (max === undefined) {
-                max = this.param.getValue() * 2;
-            }
-
-            this.param.minValue = min;
-            this._minComponent.model = this.param;
-
-            this.param.maxValue = max;
-            this._maxComponent.model = this.param;
-
-            // valeur du pas
-            let step = this.param.stepValue;
-            if (step === undefined) {
-                step = (max - min) / 20;
-            }
-            this.param.stepValue = step;
-            this._stepComponent.model = this.param;
-
-            this.validateAll();
-
-            this._validMin = this._minComponent.isValid;
-            this._validMax = this._maxComponent.isValid;
-            this._validStep = this._stepComponent.isValid;
-            this.emitValidity();
-
-            this._initCompleted = true;
-        }
-    }
-
-    /**
-     * initialisation de la liste de valeurs avec celle du paramètre géré
-     */
-    private initList() {
-        if (this._doInitList && this._listComponent !== undefined) {
-            this._doInitList = false;
-            let l = this.param.valueList;
-            if (l === undefined) {
-                if (this.param.isDefined) {
-                    l = [this.param.getValue()];
-                } else {
-                    l = [];
-                }
-            }
-
-            this.param.valueList = l;
-            this._listComponent.model = this.param;
-        }
-    }
-
-    /**
-     * revalidation de tous les composants enfants
-     */
-    private validateAll() {
-        if (this._minComponent !== undefined) {
-            this._minComponent.validate();
-        }
-        if (this._maxComponent !== undefined) {
-            this._maxComponent.validate();
-        }
-        if (this._stepComponent !== undefined) {
-            this._stepComponent.validate();
-        }
-        if (this._listComponent !== undefined) {
-            this._listComponent.validate();
-        }
-    }
+export class ParamValuesComponent implements AfterViewInit {
 
-    /**
-     * envoi d'un événement de validité
-     */
-    private emitValidity() {
-        switch (this.param.valueMode) {
-            case ParamValueMode.LISTE:
-                this.valid.emit(this._validList);
-                break;
-
-            case ParamValueMode.MINMAX:
-                this.valid.emit(this._validMin && this._validMax && this._validStep);
-                break;
-        }
-    }
-
-    /**
-     * réception d'un événement de NgParamMinComponent
-     */
-    private onMinChanged(event: any) {
-        if (this._initCompleted) {
-            switch (event.action) {
-                case "model":
-                    this.validateAll();
-                    break;
-
-                case "valid":
-                    this._validMin = event.value;
-                    this.emitValidity();
-                    break;
-            }
-        }
-    }
-
-    /**
-     * réception d'un événement de NgParamMaxComponent
-     */
-    private onMaxChanged(event: any) {
-        if (this._initCompleted) {
-            switch (event.action) {
-                case "model":
-                    this.validateAll();
-                    break;
-
-                case "valid":
-                    this._validMax = event.value;
-                    this.emitValidity();
-                    break;
-            }
-        }
-    }
-
-    /**
-     * réception d'un événement de NgParamStepComponent
-     */
-    private onStepChanged(event: any) {
-        if (this._initCompleted) {
-            switch (event.action) {
-                case "model":
-                    this.validateAll();
-                    break;
-
-                case "valid":
-                    this._validStep = event.value;
-                    this.emitValidity();
-                    break;
-            }
-        }
-    }
-
-    /**
-     * réception d'un événement de ValueListComponent
-     */
-    private onListChanged(event: any) {
-        if (this._initCompleted) {
-            switch (event.action) {
-                case "model":
-                    this.validateAll();
-                    break;
-
-                case "valid":
-                    this._validList = event.value;
-                    this.emitValidity();
-                    break;
-            }
-        }
-    }
-
-    private get uitextValeurMini() {
-        return this.intlService.localizeText("INFO_PARAMFIELD_VALEURMINI");
-    }
-
-    private get uitextValeurMaxi() {
-        return this.intlService.localizeText("INFO_PARAMFIELD_VALEURMAXI");
-    }
+    @Input()
+    public param: NgParameter;
 
-    private get uitextPasVariation() {
-        return this.intlService.localizeText("INFO_PARAMFIELD_PASVARIATION");
-    }
+    @Input()
+    public title: string;
 
-    /**
-     * true si mode "liste de valeurs"
-     */
-    public get isList(): boolean {
-        return this.param.valueMode === ParamValueMode.LISTE;
-    }
 
-    /**
-     * true si mode "lié"
-     */
-    public get isLink(): boolean {
-        return this.param.valueMode === ParamValueMode.LINK;
-    }
+    constructor(
+        private editValuesDialog: MatDialog
+    ) { }
 
-    /**
-     * true si mode "min/max/pas"
-     */
-    public get isMinMax(): boolean {
+    public get isMinMax() {
         return this.param.valueMode === ParamValueMode.MINMAX;
     }
 
-    /**
-     * valeur courante affichée dans le select min-max/list
-     */
-    public get currentModeSelectLabel(): string {
-        return ParamValueMode[this.param.valueMode];
+    public get isListe() {
+        return this.param.valueMode === ParamValueMode.LISTE;
     }
 
-    /**
-     * réception d'un événement du menu "min/max/liste"
-     */
-    public onSelectValueMode(event: any) {
-        const next = event.target.value;
-
-        switch (next) {
-            // on a sélectionné "min/max" ?
-            case ParamValueMode.MINMAX:
-                this._doInitMinmax = true;
-                break;
-
-            // on a sélectionné "liste" ?
-            case ParamValueMode.LISTE:
-                this._doInitList = true;
-                break;
-
-            default:
-                throw new Error("valeur " + next + " de ParamValueMode non prise en charge");
+    public get infoText() {
+        let text = "(à traduire) ";
+        if (this.isMinMax) {
+            text += "min: " + this.param.minValue + ", max: " + this.param.maxValue + ", step: " + this.param.stepValue;
+        } else if (this.isListe) {
+            const vals = this.param.valueList || [];
+            console.log("VALS", vals, this.param.valueList);
+            text += "values: " + vals.slice(0, 20).join(";");
         }
-
-        this.param.valueMode = next;
+        return text;
     }
 
-    public get valueModes() {
-        return this._valueModes;
-    }
-
-    /**
-     * appelé quand les @Input changent
-     */
-    ngOnChanges() {
-        if (this.isMinMax) {
-            this._doInitMinmax = true;
-        } else {
-            this._doInitList = true;
-        }
+    public openDialog() {
+        // modification de la valeur initiale, sans avoir à remettre le mode de
+        // paramètre sur "fixé"
+        this.editValuesDialog.open(
+            DialogEditParamValuesComponent,
+            {
+                data: {
+                    param: this.param
+                }
+            }
+        );
     }
 
-    ngAfterViewChecked() {
-        super.ngAfterViewChecked();
-        this.initMinMaxStep();
-        this.initList();
+    public ngAfterViewInit() {
+        // use Promise trick to introduce a pseudo-timeout and avoid ExpressionChangedAfterItHasBeenCheckedError
+        Promise.resolve().then(() => {
+            // open dialog when switching to this mode, to ease the setup
+            this.openDialog();
+        });
     }
 }
diff --git a/src/app/components/param-values/value-list.component.ts b/src/app/components/param-values/value-list.component.ts
deleted file mode 100644
index fa2fd379e..000000000
--- a/src/app/components/param-values/value-list.component.ts
+++ /dev/null
@@ -1,106 +0,0 @@
-import { Component, Input, ChangeDetectorRef } from "@angular/core";
-
-import { GenericInputComponent } from "../generic-input/generic-input.component";
-import { I18nService } from "../../services/internationalisation/internationalisation.service";
-import { NgParameter } from "../../formulaire/ngparam";
-import { Message } from "jalhyd";
-
-@Component({
-    selector: "value-list",
-    templateUrl: "../generic-input/generic-input.component.html"
-})
-export class ValueListComponent extends GenericInputComponent {
-    constructor(private intlService: I18nService, cdRef: ChangeDetectorRef) {
-        super(cdRef);
-    }
-
-    /**
-     * paramètre géré
-     */
-    private get _param(): NgParameter {
-        return this._model;
-    }
-
-    protected getModelValue(): any {
-        if (! this._param) {
-            return this._param.valueList;
-        }
-    }
-
-    protected setModelValue(sender: any, l: any) {
-        if (typeof (l) === "number") {
-            this._param.valueList = [];
-            this._param.valueList.push(l);
-        } else {
-            this._param.valueList = l;
-        }
-    }
-
-    protected validateModelValue(v: any): { isValid: boolean, message: string } {
-        let msg;
-        let valid = false;
-
-        if (v instanceof Array) {
-            valid = true;
-            try {
-                this._param.checkList(v);
-            } catch (ex) {
-                valid = false;
-                if (ex instanceof Message) {
-                    msg = this.intlService.localizeMessage(ex);
-                } else {
-                    msg = "invalid value";
-                }
-            }
-        } else {
-            msg = "Veuillez entrer une liste de nombres";
-        }
-
-        return { isValid: valid, message: msg };
-    }
-
-    protected modelToUI(v: any): string {
-        let res = "";
-        if (v !== undefined && v !== null) {
-            for (const e of v) {
-                if (res !== "") {
-                    res += ";";
-                }
-                res += String(e);
-            }
-        }
-        return res;
-    }
-
-    protected validateUIValue(ui: string): { isValid: boolean, message: string } {
-        let valid = false;
-        let msg: string;
-
-        const tmp: string[] = ui.split(";");
-        let res = true;
-        for (const v of tmp) {
-            const isnum = v !== "" && (+v === +v);
-            res = res && isnum;
-            if (!res) {
-                break;
-            }
-        }
-
-        if (!res) {
-            msg = "Veuillez entrer une liste de nombres";
-        } else {
-            valid = true;
-        }
-
-        return { isValid: valid, message: msg };
-    }
-
-    protected uiToModel(ui: string) {
-        const tmp: string[] = ui.split(";");
-        const res = [];
-        for (const v of tmp) {
-            res.push(+v);
-        }
-        return res;
-    }
-}
diff --git a/src/app/components/result-element/horizontal-result-element.component.html b/src/app/components/result-element/horizontal-result-element.component.html
index 18f7cf7da..41b4809b4 100644
--- a/src/app/components/result-element/horizontal-result-element.component.html
+++ b/src/app/components/result-element/horizontal-result-element.component.html
@@ -21,10 +21,11 @@
 -->
 
 <!-- icône en cas d'erreur -->
-<mat-icon *ngIf="hasError" style="color:red" aria-hidden="true" [mdbTooltip]="popTemplate" [isDisabled]="tooltipDisabled">warning</mat-icon>
+<mat-icon *ngIf="hasError" style="color:red" aria-hidden="true" [isDisabled]="tooltipDisabled">warning</mat-icon>
 
 <!-- valeur  -->
-<span *ngIf="!hasError" [mdbTooltip]="popTemplate" [isDisabled]="tooltipDisabled">
+<!-- [mdbTooltip]="popTemplate" -->
+<span *ngIf="!hasError" [isDisabled]="tooltipDisabled">
     {{ resultValue }}
 </span>
 
diff --git a/src/app/components/result-element/vertical-result-element.component.html b/src/app/components/result-element/vertical-result-element.component.html
index 48886d327..fa199fa1d 100644
--- a/src/app/components/result-element/vertical-result-element.component.html
+++ b/src/app/components/result-element/vertical-result-element.component.html
@@ -7,7 +7,8 @@
     {{ resultLabel }}
 </td>
 
-<td *ngIf="hasValue||hasError" [mdbTooltip]="popTemplate" [isDisabled]="tooltipDisabled" class="value2">
+<!-- [mdbTooltip]="popTemplate" -->
+<td *ngIf="hasValue||hasError" [isDisabled]="tooltipDisabled" class="value2">
     <!-- icône en cas d'erreur -->
     <mat-icon *ngIf="hasError" style="color:red" aria-hidden="true">warning</mat-icon>
 
diff --git a/src/app/components/results-graph/graph-type.component.ts b/src/app/components/results-graph/graph-type.component.ts
index 5ccfbf764..c3d41ebd9 100644
--- a/src/app/components/results-graph/graph-type.component.ts
+++ b/src/app/components/results-graph/graph-type.component.ts
@@ -1,23 +1,21 @@
 import { Component } from "@angular/core";
-
 import { Observable, IObservable, Observer } from "jalhyd";
-
-import { GenericSelectComponent } from "../generic-select/generic-select.component";
 import { GraphType } from "../../results/var-results";
 
 @Component({
     selector: "graph-type",
     templateUrl: "../generic-select/generic-select.component.html"
 })
-export class GraphTypeSelectComponent extends GenericSelectComponent<GraphType> implements IObservable {
+export class GraphTypeSelectComponent implements IObservable {
     private _entries: GraphType[] = [GraphType.Histogram, GraphType.Scatter];
     private _entriesLabels: string[] = ["Histogramme", "XY"];
     private _selected: GraphType;
 
     private _observable: Observable;
 
+    public label = "Type de graphe (à traduire)";
+
     constructor() {
-        super();
         this._observable = new Observable();
     }
 
diff --git a/src/app/components/results-graph/results-graph.component.html b/src/app/components/results-graph/results-graph.component.html
index 73ad69b83..9f0dc91e2 100644
--- a/src/app/components/results-graph/results-graph.component.html
+++ b/src/app/components/results-graph/results-graph.component.html
@@ -1,12 +1,4 @@
-<div class="row">
-    <div class="col-12">
-        <chart [type]="graph_type" [data]="graph_data" [options]="graph_options">
-        </chart>
-    </div>
-</div>
+<chart [type]="graph_type" [data]="graph_data" [options]="graph_options">
+</chart>
 
-<div class="row">
-    <div class="col-4 mx-auto">
-        <graph-type></graph-type>
-    </div>
-</div>
\ No newline at end of file
+<graph-type></graph-type>
diff --git a/src/app/components/select-field-line/select-field-line.component.html b/src/app/components/select-field-line/select-field-line.component.html
deleted file mode 100644
index c298402bd..000000000
--- a/src/app/components/select-field-line/select-field-line.component.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<div class="row">
-    <!-- titre -->
-    <div class="col-12 col-sm-3">
-        {{ label }}
-    </div>
-
-    <!-- liste déroulante -->
-    <div class="btn-group col-12 col-sm-9" dropdown (click)="onSelect($event)">
-        <button dropdownToggle class="btn btn-primary dropdown-toggle waves-light my-1" type="button" mdbRippleRadius>
-            {{ currentLabel }}
-        </button>
-        <div class="dropdown-menu">
-            <a class="dropdown-item" *ngFor="let e of entries" [value]=e>{{ entryLabel(e) }}</a>
-        </div>
-    </div>
-</div>
\ No newline at end of file
diff --git a/src/app/components/select-field-line/select-field-line.component.scss b/src/app/components/select-field-line/select-field-line.component.scss
new file mode 100644
index 000000000..f1fe15c85
--- /dev/null
+++ b/src/app/components/select-field-line/select-field-line.component.scss
@@ -0,0 +1,14 @@
+mat-form-field {
+    width: 100%;
+
+    mat-select {
+
+        ::ng-deep .mat-select-value {
+            > span {
+                > span {
+                    line-height: 1.3em;
+                }
+            }
+        }
+    }
+}
diff --git a/src/app/components/select-field-line/select-field-line.component.ts b/src/app/components/select-field-line/select-field-line.component.ts
index 59f111c23..a7840110a 100644
--- a/src/app/components/select-field-line/select-field-line.component.ts
+++ b/src/app/components/select-field-line/select-field-line.component.ts
@@ -2,13 +2,16 @@ import { Component, Input } from "@angular/core";
 
 import { SelectField } from "../../formulaire/select-field";
 import { SelectEntry } from "../../formulaire/select-entry";
-import { GenericSelectComponent } from "../generic-select/generic-select.component";
 
 @Component({
     selector: "select-field-line",
-    templateUrl: "./select-field-line.component.html"
+    // templateUrl: "./select-field-line.component.html",
+    templateUrl: "../generic-select/generic-select.component.html",
+    styleUrls: [
+        "./select-field-line.component.scss"
+    ]
 })
-export class SelectFieldLineComponent extends GenericSelectComponent<SelectEntry> {
+export class SelectFieldLineComponent {
     @Input()
     private _select: SelectField;
 
diff --git a/src/app/directives/flex-xxs.directive.ts b/src/app/directives/flex-xxs.directive.ts
new file mode 100644
index 000000000..e4df90248
--- /dev/null
+++ b/src/app/directives/flex-xxs.directive.ts
@@ -0,0 +1,65 @@
+import { Directive } from "@angular/core";
+import { BREAKPOINT, ShowHideDirective, FlexDirective } from "@angular/flex-layout";
+
+const XXS_BREAKPOINTS = [
+    {
+        alias: "xxs",
+        mediaQuery: "screen and (max-width: 479px)",
+        overlapping: false
+    },
+    {
+        alias: "xs", // redéfinition
+        mediaQuery: "screen and (min-width: 480px) screen and (max-width: 599px)",
+        overlapping: false
+    },
+    {
+        alias: "gt-xxs",
+        mediaQuery: "screen and (min-width: 480px)",
+        overlapping: false
+    },
+    {
+        alias: "lt-xs",
+        mediaQuery: "screen and (max-width: 479px)",
+        overlapping: false
+    }
+];
+
+export const CustomBreakPointsProvider = {
+    provide: BREAKPOINT,
+    useValue: XXS_BREAKPOINTS,
+    multi: true
+};
+
+const inputsXxs = [ "fxHide.xxs" ];
+const inputsGtXxs = [ "fxHide.gt-xxs" ];
+const inputsLtXs = [ "fxHide.lt-xs" ];
+
+@Directive({
+    // tslint:disable-next-line:directive-selector
+    selector: `[fxHide.xxs]`,
+    // tslint:disable-next-line:use-input-property-decorator
+    inputs: inputsXxs
+})
+export class FlexXxsShowHideDirective extends ShowHideDirective {
+    protected inputs = inputsXxs;
+}
+
+@Directive({
+    // tslint:disable-next-line:directive-selector
+    selector: `[fxHide.gt-xxs]`,
+    // tslint:disable-next-line:use-input-property-decorator
+    inputs: inputsGtXxs
+})
+export class FlexGtXxsShowHideDirective extends ShowHideDirective {
+    protected inputs = inputsGtXxs;
+}
+
+@Directive({
+    // tslint:disable-next-line:directive-selector
+    selector: `[fxHide.lt-xs]`,
+    // tslint:disable-next-line:use-input-property-decorator
+    inputs: inputsLtXs
+})
+export class FlexLtXsShowHideDirective extends ShowHideDirective {
+    protected inputs = inputsLtXs;
+}
diff --git a/src/app/formulaire/definition/concrete/form-regime-uniforme.ts b/src/app/formulaire/definition/concrete/form-regime-uniforme.ts
index b7549d676..63ed5d5a8 100644
--- a/src/app/formulaire/definition/concrete/form-regime-uniforme.ts
+++ b/src/app/formulaire/definition/concrete/form-regime-uniforme.ts
@@ -1,5 +1,5 @@
 import { FormDefFixedVar } from "../form-def-fixedvar";
-import { CalculatorType, ComputeNodeType, IObservable, Observer } from "jalhyd";
+import { IObservable, Observer } from "jalhyd";
 import { FormResultFixedVar } from "../form-result-fixedvar";
 import { FormulaireDefinition } from "../form-definition";
 import { FormDefSection } from "../form-def-section";
diff --git a/src/app/formulaire/definition/form-def-fixedvar.ts b/src/app/formulaire/definition/form-def-fixedvar.ts
index 9e72537aa..9cd7bb4d1 100644
--- a/src/app/formulaire/definition/form-def-fixedvar.ts
+++ b/src/app/formulaire/definition/form-def-fixedvar.ts
@@ -143,6 +143,7 @@ export class FormDefFixedVar {
 
             for (const p of this._formBase.allFormElements) {
                 if (p instanceof NgParameter) {
+                    // change all radio button groups except the one that sent the event
                     if (p.radioConfig === ParamRadioConfig.CAL && p.radioState === ParamRadioConfig.FIX && p !== sourceParam) {
                         newCal = p;
                         break;
@@ -152,7 +153,8 @@ export class FormDefFixedVar {
                     break;
                 }
             }
-
+            // if the current calculated parameter was set to another mode, set a new param
+            // to calculated mode (there must always be at least one)
             if (newCal) {
                 newCal.valueMode = ParamValueMode.CALCUL;
             }
@@ -173,9 +175,6 @@ export class FormDefFixedVar {
     public onRadioClick(info: any) {
         const param: NgParameter = info.param; // paramètre source de l'événement radio
         const old: ParamValueMode = info.oldValueMode; // ancien état (radio)
-
-        // this.logParams();
         this.resetRadiosAndResults(param, old);
-        // this.logParams();
     }
 }
diff --git a/src/app/formulaire/ngparam.ts b/src/app/formulaire/ngparam.ts
index 50125790a..ad7caff50 100644
--- a/src/app/formulaire/ngparam.ts
+++ b/src/app/formulaire/ngparam.ts
@@ -54,7 +54,7 @@ export class NgParameter extends InputField implements Observer {
         this._confId = id;
     }
 
-    public get _paramValues() { // @TODO remettre en private ! (debug)
+    private get _paramValues() {
         return this._paramDef.paramValues;
     }
 
@@ -148,6 +148,17 @@ export class NgParameter extends InputField implements Observer {
         return this._paramDef.isValid;
     }
 
+    public get title(): string {
+        let t = "";
+        if (this.label !== undefined) {
+            t = this.label;
+        }
+        if (this.unit !== undefined && this.unit !== "") {
+            t = t + " (" + this.unit + ")";
+        }
+        return t;
+    }
+
     public get valuesIterator(): INumberIterator {
         return this._paramDef.valuesIterator;
     }
diff --git a/src/locale/messages.en.json b/src/locale/messages.en.json
index 3ba6ffc71..6f454dc41 100644
--- a/src/locale/messages.en.json
+++ b/src/locale/messages.en.json
@@ -31,6 +31,7 @@
     "ERROR_SECTION_NON_CONVERGENCE_NEWTON_HTOR": "Non convergence of the calculation of the corresponding height (Newton's method) for the calculation of the supercritical depth",
     "ERROR_SECTION_PENTE_NEG_NULLE_HNORMALE_INF": "The slope is negative or zero, the normal depth is infinite",
     "ERROR_STRUCTURE_Q_TROP_ELEVE": "The flow passing through the other devices is too high: the requested parameter is not calculable.",
+    "INFO_CALCULATOR_CALC_NAME": "Calculator name",
     "INFO_CALCULATOR_CALCULER": "Compute",
     "INFO_CALCULATOR_PARAMFIXES": "Fixed parameters",
     "INFO_CALCULATOR_VALEURS": "Values",
@@ -123,6 +124,7 @@
     "INFO_OPTION_NO": "No",
     "INFO_OPTION_YES": "Yes",
     "INFO_OPTION_CANCEL": "Cancel",
+    "INFO_OPTION_CLOSE": "Close",
     "INFO_OPTION_LOAD": "Load",
     "INFO_OPTION_SAVE": "Save",
     "INFO_OPTION_ALL": "All",
@@ -137,8 +139,8 @@
     "INFO_PARAMFIELD_PARAMFIXE": "Fixed",
     "INFO_PARAMFIELD_PARAMLIE": "Link",
     "INFO_PARAMFIELD_PARAMVARIER": "Vary",
-    "INFO_PARAMFIELD_PASVARIATION": "with a variation step of:",
-    "INFO_PARAMFIELD_VALEURMAXI": "to maximum value",
+    "INFO_PARAMFIELD_PASVARIATION": "With a variation step of",
+    "INFO_PARAMFIELD_VALEURMAXI": "To maximum value",
     "INFO_PARAMFIELD_VALEURMINI": "From minimum value",
     "INFO_REGIMEUNIFORME_TITRE": "Uniform flow calculation",
     "INFO_REMOUSRESULTS_ABSCISSE": "Abscissa (m)",
diff --git a/src/locale/messages.fr.json b/src/locale/messages.fr.json
index e5ad681f1..490f83bec 100644
--- a/src/locale/messages.fr.json
+++ b/src/locale/messages.fr.json
@@ -31,6 +31,7 @@
     "ERROR_SECTION_NON_CONVERGENCE_NEWTON_HTOR": "Non convergence du calcul de la hauteur correspondante (Méthode de Newton) pour le calcul de la hauteur torrentielle",
     "ERROR_SECTION_PENTE_NEG_NULLE_HNORMALE_INF": "La pente est négative ou nulle, la hauteur normale est infinie",
     "ERROR_STRUCTURE_Q_TROP_ELEVE": "Le débit passant par les autres ouvrages est trop élevé: le paramètre demandé n'est pas calculable.",
+    "INFO_CALCULATOR_CALC_NAME": "Nom du module de calcul",
     "INFO_CALCULATOR_CALCULER": "Calculer",
     "INFO_CALCULATOR_PARAMFIXES": "Paramètres fixés",
     "INFO_CALCULATOR_VALEURS": "Valeurs",
@@ -123,6 +124,7 @@
     "INFO_OPTION_NO": "Non",
     "INFO_OPTION_YES": "Oui",
     "INFO_OPTION_CANCEL": "Annuler",
+    "INFO_OPTION_CLOSE": "Fermer",
     "INFO_OPTION_LOAD": "Charger",
     "INFO_OPTION_SAVE": "Enregistrer",
     "INFO_OPTION_ALL": "Tous",
@@ -137,8 +139,8 @@
     "INFO_PARAMFIELD_PARAMFIXE": "fixé",
     "INFO_PARAMFIELD_PARAMLIE": "lié",
     "INFO_PARAMFIELD_PARAMVARIER": "varier",
-    "INFO_PARAMFIELD_PASVARIATION": "avec un pas de&nbsp;:",
-    "INFO_PARAMFIELD_VALEURMAXI": "à la valeur maximum",
+    "INFO_PARAMFIELD_PASVARIATION": "Avec un pas de",
+    "INFO_PARAMFIELD_VALEURMAXI": "À la valeur maximum",
     "INFO_PARAMFIELD_VALEURMINI": "De la valeur minimum",
     "INFO_REGIMEUNIFORME_TITRE": "Régime uniforme",
     "INFO_REMOUSRESULTS_ABSCISSE": "Abscisse (m)",
diff --git a/src/styles.scss b/src/styles.scss
index b78e18772..c7f1c492c 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -33,3 +33,13 @@ h1 {
     font-weight: 300;
     line-height: 1.2;
 }
+
+// debug
+field-set {
+    margin-bottom: 2em;
+}
+
+// hide elements having "hidden" attribute even if they have explicit "display:" property
+[hidden] {
+    display: none !important;
+}
diff --git a/src/theme.scss b/src/theme.scss
index fddf50617..3ff27ba26 100644
--- a/src/theme.scss
+++ b/src/theme.scss
@@ -182,6 +182,7 @@ $primary: map-get($nghyd-theme, primary);
 $accent: map-get($nghyd-theme, accent);
 $warn: map-get($nghyd-theme, warn);
 
+// convenience classes (functions mat-* cannot be used outside of this file)
 .color-primary {
     color: mat-color($primary);
 }
@@ -194,9 +195,24 @@ $warn: map-get($nghyd-theme, warn);
 .bg-accent {
     background-color: mat-color($accent);
 }
+.bg-accent-light {
+    background-color: mat-color($accent, 300);
+}
 .color-warn {
     color: mat-color($warn);
 }
 .bg-warn {
     background-color: mat-color($warn);
 }
+
+// make toggle buttons more visible
+/* .mat-button-toggle {
+    background-color: mat-color($primary);
+    color: mat-color($primary, default-contrast);
+}
+ */
+
+.mat-button-toggle-checked {
+    background-color: mat-color($accent);
+    color: mat-color($accent, default-contrast) !important;
+}
diff --git a/tsconfig.json b/tsconfig.json
index cb174fd8a..f6d913f28 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -20,7 +20,6 @@
     "baseUrl": "./"
   },
   "include": [
-    "node_modules/angular-bootstrap-md/**/*.ts",
     "src/**/*.ts"
   ]
 }
\ No newline at end of file
-- 
GitLab