@dsivd/prestations-ng 19.0.6-beta.2 → 19.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/CONTRIBUTING.md +24 -0
- package/ESLINT_PLUGIN.md +177 -0
- package/README.md +3 -0
- package/UPGRADING_V19.md +24 -6
- package/dsivd-prestations-ng-19.0.6.tgz +0 -0
- package/eslint/configs/template-base.mjs +10 -0
- package/eslint/configs/template-recommended.mjs +15 -0
- package/eslint/configs/ts-base.mjs +10 -0
- package/eslint/configs/ts-recommended.mjs +12 -0
- package/eslint/index.mjs +14 -5
- package/eslint/rules/index.mjs +7 -1
- package/eslint/rules/no-direct-signal-mutation.mjs +240 -136
- package/eslint/rules/no-uninvoked-signal-in-template.mjs +170 -0
- package/eslint/signal-names.mjs +232 -0
- package/eslint/template-ast.mjs +26 -0
- package/fesm2022/dsivd-prestations-ng.mjs +7 -9
- package/fesm2022/dsivd-prestations-ng.mjs.map +1 -1
- package/package.json +1 -1
- package/src/eslint/configs/__tests__/configs.test.mjs +70 -0
- package/src/eslint/configs/template-base.mjs +10 -0
- package/src/eslint/configs/template-recommended.mjs +15 -0
- package/src/eslint/configs/ts-base.mjs +10 -0
- package/src/eslint/configs/ts-recommended.mjs +12 -0
- package/src/eslint/index.mjs +14 -5
- package/src/eslint/rules/__tests__/no-direct-signal-mutation.test.mjs +86 -4
- package/src/eslint/rules/__tests__/no-uninvoked-signal-in-template.test.mjs +291 -0
- package/src/eslint/rules/index.mjs +7 -1
- package/src/eslint/rules/no-direct-signal-mutation.mjs +240 -136
- package/src/eslint/rules/no-uninvoked-signal-in-template.mjs +170 -0
- package/src/eslint/signal-names.mjs +232 -0
- package/src/eslint/template-ast.mjs +26 -0
- package/types/dsivd-prestations-ng.d.ts +0 -2
- package/dsivd-prestations-ng-19.0.6-beta.2.tgz +0 -0
- package/eslint/rules/__tests__/no-direct-signal-mutation.test.mjs +0 -98
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import ts from "typescript";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Collects the signal member names of a component, walking up its inheritance chain.
|
|
7
|
+
*
|
|
8
|
+
* Base classes are resolved automatically: the `extends` clause is matched against the
|
|
9
|
+
* imports of the file, the module is resolved the way TypeScript would (relative paths,
|
|
10
|
+
* `node_modules`, `paths` mappings) and the base class is read from there. Nothing has
|
|
11
|
+
* to be declared by hand.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// `output()` is absent: an output is never called, it is emitted.
|
|
15
|
+
const SIGNAL_FACTORIES = new Set([
|
|
16
|
+
"input",
|
|
17
|
+
"model",
|
|
18
|
+
"computed",
|
|
19
|
+
"signal",
|
|
20
|
+
"linkedSignal",
|
|
21
|
+
"toSignal",
|
|
22
|
+
"viewChild",
|
|
23
|
+
"viewChildren",
|
|
24
|
+
"contentChild",
|
|
25
|
+
"contentChildren",
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
// Types of a signal member. Declaration files qualify them with the namespace they were
|
|
29
|
+
// imported under: `readonly icon: _angular_core.ModelSignal<IconProp>`, hence the
|
|
30
|
+
// comparison on the last segment only.
|
|
31
|
+
const SIGNAL_TYPES = new Set([
|
|
32
|
+
"Signal",
|
|
33
|
+
"WritableSignal",
|
|
34
|
+
"InputSignal",
|
|
35
|
+
"InputSignalWithTransform",
|
|
36
|
+
"ModelSignal",
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
const parsedFiles = new Map();
|
|
40
|
+
const compilerOptionsByDirectory = new Map();
|
|
41
|
+
|
|
42
|
+
const parseFile = (path) => {
|
|
43
|
+
let stamp;
|
|
44
|
+
try {
|
|
45
|
+
stamp = statSync(path).mtimeMs;
|
|
46
|
+
} catch {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const cached = parsedFiles.get(path);
|
|
51
|
+
if (cached?.stamp === stamp) {
|
|
52
|
+
return cached.sourceFile;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const sourceFile = ts.createSourceFile(
|
|
56
|
+
path,
|
|
57
|
+
readFileSync(path, "utf8"),
|
|
58
|
+
ts.ScriptTarget.Latest,
|
|
59
|
+
false,
|
|
60
|
+
);
|
|
61
|
+
parsedFiles.set(path, { stamp, sourceFile });
|
|
62
|
+
return sourceFile;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const compilerOptionsFor = (path) => {
|
|
66
|
+
const directory = dirname(path);
|
|
67
|
+
if (compilerOptionsByDirectory.has(directory)) {
|
|
68
|
+
return compilerOptionsByDirectory.get(directory);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const configPath = ts.findConfigFile(directory, ts.sys.fileExists);
|
|
72
|
+
const config = configPath
|
|
73
|
+
? ts.readConfigFile(configPath, ts.sys.readFile).config
|
|
74
|
+
: undefined;
|
|
75
|
+
// Only the options are converted: reading the whole config would glob `include`.
|
|
76
|
+
const { options } = ts.convertCompilerOptionsFromJson(
|
|
77
|
+
config?.compilerOptions ?? {},
|
|
78
|
+
configPath ? dirname(configPath) : directory,
|
|
79
|
+
);
|
|
80
|
+
// Without these, `node_modules` is not searched at all.
|
|
81
|
+
options.module ??= ts.ModuleKind.ESNext;
|
|
82
|
+
options.moduleResolution ??= ts.ModuleResolutionKind.Bundler;
|
|
83
|
+
|
|
84
|
+
compilerOptionsByDirectory.set(directory, options);
|
|
85
|
+
return options;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const resolveModule = (specifier, containingFile) =>
|
|
89
|
+
ts.resolveModuleName(
|
|
90
|
+
specifier,
|
|
91
|
+
containingFile,
|
|
92
|
+
compilerOptionsFor(containingFile),
|
|
93
|
+
ts.sys,
|
|
94
|
+
).resolvedModule?.resolvedFileName;
|
|
95
|
+
|
|
96
|
+
const lastSegmentOf = (entityName) =>
|
|
97
|
+
ts.isQualifiedName(entityName) ? entityName.right.text : entityName.text;
|
|
98
|
+
|
|
99
|
+
const isSignalType = (typeNode) =>
|
|
100
|
+
!!typeNode &&
|
|
101
|
+
ts.isTypeReferenceNode(typeNode) &&
|
|
102
|
+
SIGNAL_TYPES.has(lastSegmentOf(typeNode.typeName));
|
|
103
|
+
|
|
104
|
+
const isSignalFactoryCall = (expression) => {
|
|
105
|
+
if (!expression || !ts.isCallExpression(expression)) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
// `input.required<T>()` and `viewChild.required(…)` unwrap to their factory.
|
|
109
|
+
let callee = expression.expression;
|
|
110
|
+
while (ts.isPropertyAccessExpression(callee)) {
|
|
111
|
+
callee = callee.expression;
|
|
112
|
+
}
|
|
113
|
+
return ts.isIdentifier(callee) && SIGNAL_FACTORIES.has(callee.text);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const isSignalMember = (member) => {
|
|
117
|
+
if (ts.isGetAccessor(member)) {
|
|
118
|
+
return isSignalType(member.type);
|
|
119
|
+
}
|
|
120
|
+
return (
|
|
121
|
+
ts.isPropertyDeclaration(member) &&
|
|
122
|
+
(isSignalType(member.type) || isSignalFactoryCall(member.initializer))
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const findClass = (sourceFile, className) => {
|
|
127
|
+
let found;
|
|
128
|
+
const visit = (node) => {
|
|
129
|
+
if (found) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (ts.isClassDeclaration(node) && node.name?.text === className) {
|
|
133
|
+
found = node;
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
node.forEachChild(visit);
|
|
137
|
+
};
|
|
138
|
+
sourceFile.forEachChild(visit);
|
|
139
|
+
return found;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const extendedClassNameOf = (classDeclaration) => {
|
|
143
|
+
const clause = classDeclaration.heritageClauses?.find(
|
|
144
|
+
(heritageClause) => heritageClause.token === ts.SyntaxKind.ExtendsKeyword,
|
|
145
|
+
);
|
|
146
|
+
const expression = clause?.types[0]?.expression;
|
|
147
|
+
return expression && ts.isIdentifier(expression) ? expression.text : undefined;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// The import that brings `localName` into the file, with the name it is exported under.
|
|
151
|
+
const importOf = (sourceFile, localName) => {
|
|
152
|
+
for (const statement of sourceFile.statements) {
|
|
153
|
+
const bindings = ts.isImportDeclaration(statement)
|
|
154
|
+
? statement.importClause?.namedBindings
|
|
155
|
+
: undefined;
|
|
156
|
+
if (!bindings || !ts.isNamedImports(bindings)) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
const element = bindings.elements.find(
|
|
160
|
+
(namedImport) => namedImport.name.text === localName,
|
|
161
|
+
);
|
|
162
|
+
if (element) {
|
|
163
|
+
return {
|
|
164
|
+
specifier: statement.moduleSpecifier.text,
|
|
165
|
+
exportedName: (element.propertyName ?? element.name).text,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return undefined;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const reExportedModulesOf = (sourceFile) =>
|
|
173
|
+
sourceFile.statements
|
|
174
|
+
.filter((statement) => ts.isExportDeclaration(statement) && statement.moduleSpecifier)
|
|
175
|
+
.map((statement) => statement.moduleSpecifier.text);
|
|
176
|
+
|
|
177
|
+
const collect = (path, className, names, visited) => {
|
|
178
|
+
const key = `${path}::${className ?? "*"}`;
|
|
179
|
+
if (visited.has(key)) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
visited.add(key);
|
|
183
|
+
|
|
184
|
+
const sourceFile = parseFile(path);
|
|
185
|
+
if (!sourceFile) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const classDeclarations = className
|
|
190
|
+
? [findClass(sourceFile, className)].filter(Boolean)
|
|
191
|
+
: sourceFile.statements.filter(ts.isClassDeclaration);
|
|
192
|
+
|
|
193
|
+
// A barrel: the class is only re-exported from here.
|
|
194
|
+
if (className && classDeclarations.length === 0) {
|
|
195
|
+
for (const specifier of reExportedModulesOf(sourceFile)) {
|
|
196
|
+
const target = resolveModule(specifier, path);
|
|
197
|
+
if (target) {
|
|
198
|
+
collect(target, className, names, visited);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
for (const classDeclaration of classDeclarations) {
|
|
205
|
+
for (const member of classDeclaration.members) {
|
|
206
|
+
if (member.name && ts.isIdentifier(member.name) && isSignalMember(member)) {
|
|
207
|
+
names.add(member.name.text);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const baseName = extendedClassNameOf(classDeclaration);
|
|
212
|
+
if (!baseName) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
// Declaration bundles are flattened: the base class often sits in the same file.
|
|
216
|
+
if (findClass(sourceFile, baseName)) {
|
|
217
|
+
collect(path, baseName, names, visited);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const imported = importOf(sourceFile, baseName);
|
|
221
|
+
const target = imported && resolveModule(imported.specifier, path);
|
|
222
|
+
if (target) {
|
|
223
|
+
collect(target, imported.exportedName, names, visited);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
export const collectSignalNames = (componentPath) => {
|
|
229
|
+
const names = new Set();
|
|
230
|
+
collect(componentPath, undefined, names, new Set());
|
|
231
|
+
return names;
|
|
232
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two subtleties of the Angular template AST that every template rule runs into.
|
|
3
|
+
* Both are easy to get wrong, hence the shared names.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The expression carried by a `BoundEvent` handler.
|
|
8
|
+
*
|
|
9
|
+
* A two-way binding `[(x)]="expr"` generates a `BoundEvent` named `xChange` whose
|
|
10
|
+
* handler is the very expression of the twin `BoundAttribute` — same source span, two
|
|
11
|
+
* AST nodes. A rule that reports on both copies reports the binding twice.
|
|
12
|
+
*/
|
|
13
|
+
export const handlerExpressionOf = (boundEvent) => {
|
|
14
|
+
const { handler } = boundEvent;
|
|
15
|
+
return handler?.type === "ASTWithSource" ? handler.ast : handler;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Whether a node is read directly on the component.
|
|
20
|
+
*
|
|
21
|
+
* `mySignal` reads the member on an `ImplicitReceiver`, `this.mySignal` on a
|
|
22
|
+
* `ThisReceiver` — two distinct node types for the same intent. Anything else
|
|
23
|
+
* (`obj.mySignal`) is read on a foreign object.
|
|
24
|
+
*/
|
|
25
|
+
export const isComponentReceiver = (node) =>
|
|
26
|
+
node?.type === "ImplicitReceiver" || node?.type === "ThisReceiver";
|
|
@@ -14444,8 +14444,6 @@ class FoehnListSummaryComponent extends FoehnInputComponent {
|
|
|
14444
14444
|
this.showAsTable = input(false, ...(ngDevMode ? [{ debugName: "showAsTable" }] : /* istanbul ignore next */ []));
|
|
14445
14445
|
this.tableActionButtons = viewChild('tableActionButtons', ...(ngDevMode ? [{ debugName: "tableActionButtons" }] : /* istanbul ignore next */ []));
|
|
14446
14446
|
this.canAddItems = input(true, ...(ngDevMode ? [{ debugName: "canAddItems" }] : /* istanbul ignore next */ []));
|
|
14447
|
-
this.internalList = linkedSignal(() => this.model(), ...(ngDevMode ? [{ debugName: "internalList" }] : /* istanbul ignore next */ []));
|
|
14448
|
-
this.internalListItemDescriptions = linkedSignal(() => this.listItemDescriptions(), ...(ngDevMode ? [{ debugName: "internalListItemDescriptions" }] : /* istanbul ignore next */ []));
|
|
14449
14447
|
this.itemRemoved = output();
|
|
14450
14448
|
this.itemEdit = output();
|
|
14451
14449
|
this.tableSort = {
|
|
@@ -14464,7 +14462,7 @@ class FoehnListSummaryComponent extends FoehnInputComponent {
|
|
|
14464
14462
|
ngAfterViewInit() {
|
|
14465
14463
|
super.ngAfterViewInit();
|
|
14466
14464
|
this.ngZone.onMicrotaskEmpty.pipe(first()).subscribe(() => {
|
|
14467
|
-
const listItemDescriptions = this.
|
|
14465
|
+
const listItemDescriptions = this.listItemDescriptions();
|
|
14468
14466
|
this.tableConfiguration = listItemDescriptions.map((item, index) => ({
|
|
14469
14467
|
id: `col-${index}`,
|
|
14470
14468
|
sortAttribute: index.toString(),
|
|
@@ -14497,7 +14495,7 @@ class FoehnListSummaryComponent extends FoehnInputComponent {
|
|
|
14497
14495
|
htmlContent: 'Souhaitez-vous vraiment supprimer cet élément ?',
|
|
14498
14496
|
})
|
|
14499
14497
|
.then(() => {
|
|
14500
|
-
const updatedList = this.
|
|
14498
|
+
const updatedList = this.model().filter((_item, itemIndex) => itemIndex !== index);
|
|
14501
14499
|
this.updateNgModel(updatedList);
|
|
14502
14500
|
this.updateListCopyForTable(updatedList);
|
|
14503
14501
|
this.itemRemoved.emit();
|
|
@@ -14506,13 +14504,13 @@ class FoehnListSummaryComponent extends FoehnInputComponent {
|
|
|
14506
14504
|
});
|
|
14507
14505
|
}
|
|
14508
14506
|
changeSort(sortEvent) {
|
|
14509
|
-
const sortedList = this.sortNow(this.
|
|
14507
|
+
const sortedList = this.sortNow(this.model(), sortEvent);
|
|
14510
14508
|
this.updateNgModel(sortedList);
|
|
14511
14509
|
this.updateListCopyForTable(sortedList);
|
|
14512
14510
|
this.tableSort = sortEvent;
|
|
14513
14511
|
}
|
|
14514
14512
|
sortNow(elems, sortEvent) {
|
|
14515
|
-
const listItemDescriptions = this.
|
|
14513
|
+
const listItemDescriptions = this.listItemDescriptions();
|
|
14516
14514
|
return [...elems].sort((a, b) => {
|
|
14517
14515
|
const valueA = listItemDescriptions[sortEvent.sortAttribute].getFormattedValue(a);
|
|
14518
14516
|
const valueB = listItemDescriptions[sortEvent.sortAttribute].getFormattedValue(b);
|
|
@@ -14528,7 +14526,7 @@ class FoehnListSummaryComponent extends FoehnInputComponent {
|
|
|
14528
14526
|
return valueB.localeCompare(valueA);
|
|
14529
14527
|
});
|
|
14530
14528
|
}
|
|
14531
|
-
updateListCopyForTable(list = this.
|
|
14529
|
+
updateListCopyForTable(list = this.model()) {
|
|
14532
14530
|
if (!this.showAsTable()) {
|
|
14533
14531
|
return;
|
|
14534
14532
|
}
|
|
@@ -14542,7 +14540,7 @@ class FoehnListSummaryComponent extends FoehnInputComponent {
|
|
|
14542
14540
|
provide: FoehnInputComponent,
|
|
14543
14541
|
useExisting: forwardRef(() => FoehnListSummaryComponent),
|
|
14544
14542
|
},
|
|
14545
|
-
], viewQueries: [{ propertyName: "tableActionButtons", first: true, predicate: ["tableActionButtons"], descendants: true, isSignal: true }], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<section\n class=\"form-group clearable-input-form-group\"\n [class.has-danger]=\"hasErrorsToDisplay()\"\n [class.vd-form-group-danger]=\"hasErrorsToDisplay()\"\n [attr.id]=\"buildId('Container')\"\n tabindex=\"-1\"\n>\n @if (label() && type !== 'hidden') {\n <label\n [class]=\"\n 'form-label ' +\n (isLabelSrOnly()\n ? 'visually-hidden'\n : (labelStyleModifier() ?? ''))\n \"\n [attr.for]=\"buildChildId()\"\n >\n <span [innerHTML]=\"label()\"></span>\n @if (!required() && !hideNotRequiredExtraLabel()) {\n <span aria-hidden=\"true\">\n {{ 'foehn-input.optional' | fromDictionary }}\n </span>\n }\n </label>\n }\n\n <foehn-validation-alerts [component]=\"this\" />\n\n @if (helpText() && type !== 'hidden') {\n <small\n [attr.id]=\"buildChildId() + 'Help'\"\n class=\"form-text text-secondary\"\n [innerHTML]=\"helpText()\"\n ></small>\n }\n\n @if (!showAsTable()) {\n <section>\n <ul class=\"list-unstyled\" aria-describedby=\"sommaire-help-alt\">\n @for (\n item of internalList();\n track trackFoehnListItem(itemIndex, item);\n let itemIndex = $index\n ) {\n <li\n [id]=\"'list-summary-' + item.trackingIndex\"\n class=\"mt-3 border-bottom\"\n >\n <div class=\"d-flex align-items-baseline flex-wrap\">\n <h4 class=\"mt-0 me-3\">\n {{ getListItemTitle()(item) }}\n </h4>\n <span class=\"ms-auto\">\n <foehn-error-pill\n [incompleteIndicatorOnly]=\"true\"\n [errorPrefix]=\"\n name() + '[' + itemIndex + ']'\n \"\n />\n </span>\n </div>\n <dl class=\"mb-0\">\n @for (\n itemParam of internalListItemDescriptions();\n track trackFoehnListItemDescription(\n $index,\n itemParam\n )\n ) {\n @if (showLine(item, itemParam)) {\n <div class=\"d-flex flex-wrap item-line\">\n <dt class=\"me-1\">\n {{ itemParam.label }}\n </dt>\n <dd class=\"ms-auto text-end\">\n {{\n itemParam.getFormattedValue(\n item\n )\n }}\n </dd>\n </div>\n }\n }\n </dl>\n <ul class=\"list-inline mb-3 mt-0\">\n @if (item.disableEdition && item.disableDeletion) {\n <li class=\"list-inline-item\">\n <a\n [id]=\"\n buildChildId(\n '-see-link-' + itemIndex\n )\n \"\n href=\"#\"\n (click)=\"\n $event.preventDefault();\n editItem(itemIndex)\n \"\n >\n Consulter\n </a>\n <span class=\"visually-hidden\">\n {{ getListItemTitle()(item) }}\n </span>\n </li>\n }\n @if (!item.disableEdition) {\n <li class=\"list-inline-item\">\n <a\n [id]=\"\n buildChildId(\n '-edit-link-' + itemIndex\n )\n \"\n href=\"#\"\n (click)=\"\n $event.preventDefault();\n editItem(itemIndex)\n \"\n >\n Modifier\n </a>\n <span class=\"visually-hidden\">\n {{ getListItemTitle()(item) }}\n </span>\n </li>\n }\n @if (!item.disableDeletion) {\n <li class=\"list-inline-item\">\n \n <a\n [id]=\"\n buildChildId(\n '-delete-link-' + itemIndex\n )\n \"\n href=\"#\"\n (click)=\"\n $event.preventDefault();\n removeItem(itemIndex)\n \"\n >\n Supprimer\n <span class=\"visually-hidden\">\n {{ getListItemTitle()(item) }}\n </span>\n </a>\n </li>\n }\n </ul>\n </li>\n }\n </ul>\n </section>\n }\n\n @if (showAsTable() && !!tableConfiguration?.length) {\n <section>\n @if (!!listCopyForTable?.length) {\n <foehn-table\n [name]=\"buildChildName('table')\"\n [model]=\"listCopyForTable\"\n [itemsPerPage]=\"1000000\"\n [columnsConfiguration]=\"tableConfiguration\"\n [sort]=\"tableSort\"\n (sortChange)=\"changeSort($event)\"\n [trackByFn]=\"trackFoehnListItem\"\n />\n }\n </section>\n }\n\n @if (canAddItems()) {\n <section [class.mt-5]=\"!showAsTable()\">\n <h2 class=\"visually-hidden\">Action</h2>\n <ul class=\"list-inline mb-3\">\n <li class=\"list-inline-item\">\n <button\n [id]=\"buildChildId('-add-button')\"\n type=\"button\"\n class=\"btn btn-primary\"\n (click)=\"editItem()\"\n >\n Ajouter\n </button>\n </li>\n </ul>\n </section>\n }\n</section>\n\n<ng-template #tableActionButtons let-index=\"index\" let-item=\"item\">\n <div class=\"d-inline-flex\">\n @if (item.disableEdition && item.disableDeletion) {\n <button\n [id]=\"buildChildId('-see-button-' + index)\"\n type=\"button\"\n class=\"btn bg-transparent me-3\"\n (click)=\"editItem(index)\"\n >\n <foehn-icon-search [title]=\"'Consulter'\" />\n </button>\n }\n @if (!item.disableEdition) {\n <button\n [id]=\"buildChildId('-edit-button-' + index)\"\n type=\"button\"\n class=\"btn bg-transparent me-3\"\n (click)=\"editItem(index)\"\n >\n <foehn-icon-edit [title]=\"'Modifier'\" />\n </button>\n }\n @if (!item.disableDeletion) {\n <button\n [id]=\"buildChildId('-delete-button-' + index)\"\n type=\"button\"\n class=\"btn bg-transparent\"\n (click)=\"removeItem(index)\"\n >\n <foehn-icon-trash-alt [title]=\"'Supprimer'\" />\n </button>\n }\n </div>\n</ng-template>\n", styles: [":host ::ng-deep .bg-transparent.btn .svg-inline--fa{color:var(--vd-neutral-darker)!important}\n"], dependencies: [{ kind: "component", type: FoehnValidationAlertsComponent, selector: "foehn-validation-alerts", inputs: ["component", "shouldErrorsBeLive"] }, { kind: "component", type: FoehnErrorPillComponent, selector: "foehn-error-pill", inputs: ["errorPrefix", "incompleteIndicatorOnly"] }, { kind: "component", type: FoehnTableComponent, selector: "foehn-table", inputs: ["columnsConfiguration", "itemsPerPage", "fixedPageCount", "sort", "title", "totalElements", "titleSrOnly", "previousLabel", "nextLabel", "tableClass", "trackByFn"], outputs: ["columnsConfigurationChange", "sortChange", "pageChange", "rowClick"] }, { kind: "component", type: FoehnIconSearchComponent, selector: "foehn-icon-search" }, { kind: "component", type: FoehnIconEditComponent, selector: "foehn-icon-edit" }, { kind: "component", type: FoehnIconTrashAltComponent, selector: "foehn-icon-trash-alt" }, { kind: "pipe", type: SdkDictionaryPipe, name: "fromDictionary" }] }); }
|
|
14543
|
+
], viewQueries: [{ propertyName: "tableActionButtons", first: true, predicate: ["tableActionButtons"], descendants: true, isSignal: true }], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<section\n class=\"form-group clearable-input-form-group\"\n [class.has-danger]=\"hasErrorsToDisplay()\"\n [class.vd-form-group-danger]=\"hasErrorsToDisplay()\"\n [attr.id]=\"buildId('Container')\"\n tabindex=\"-1\"\n>\n @if (label() && type !== 'hidden') {\n <label\n [class]=\"\n 'form-label ' +\n (isLabelSrOnly()\n ? 'visually-hidden'\n : (labelStyleModifier() ?? ''))\n \"\n [attr.for]=\"buildChildId()\"\n >\n <span [innerHTML]=\"label()\"></span>\n @if (!required() && !hideNotRequiredExtraLabel()) {\n <span aria-hidden=\"true\">\n {{ 'foehn-input.optional' | fromDictionary }}\n </span>\n }\n </label>\n }\n\n <foehn-validation-alerts [component]=\"this\" />\n\n @if (helpText() && type !== 'hidden') {\n <small\n [attr.id]=\"buildChildId() + 'Help'\"\n class=\"form-text text-secondary\"\n [innerHTML]=\"helpText()\"\n ></small>\n }\n\n @if (!showAsTable()) {\n <section>\n <ul class=\"list-unstyled\" aria-describedby=\"sommaire-help-alt\">\n @for (\n item of model();\n track trackFoehnListItem(itemIndex, item);\n let itemIndex = $index\n ) {\n <li\n [id]=\"'list-summary-' + item.trackingIndex\"\n class=\"mt-3 border-bottom\"\n >\n <div class=\"d-flex align-items-baseline flex-wrap\">\n <h4 class=\"mt-0 me-3\">\n {{ getListItemTitle()(item) }}\n </h4>\n <span class=\"ms-auto\">\n <foehn-error-pill\n [incompleteIndicatorOnly]=\"true\"\n [errorPrefix]=\"\n name() + '[' + itemIndex + ']'\n \"\n />\n </span>\n </div>\n <dl class=\"mb-0\">\n @for (\n itemParam of listItemDescriptions();\n track trackFoehnListItemDescription(\n $index,\n itemParam\n )\n ) {\n @if (showLine(item, itemParam)) {\n <div class=\"d-flex flex-wrap item-line\">\n <dt class=\"me-1\">\n {{ itemParam.label }}\n </dt>\n <dd class=\"ms-auto text-end\">\n {{\n itemParam.getFormattedValue(\n item\n )\n }}\n </dd>\n </div>\n }\n }\n </dl>\n <ul class=\"list-inline mb-3 mt-0\">\n @if (item.disableEdition && item.disableDeletion) {\n <li class=\"list-inline-item\">\n <a\n [id]=\"\n buildChildId(\n '-see-link-' + itemIndex\n )\n \"\n href=\"#\"\n (click)=\"\n $event.preventDefault();\n editItem(itemIndex)\n \"\n >\n Consulter\n </a>\n <span class=\"visually-hidden\">\n {{ getListItemTitle()(item) }}\n </span>\n </li>\n }\n @if (!item.disableEdition) {\n <li class=\"list-inline-item\">\n <a\n [id]=\"\n buildChildId(\n '-edit-link-' + itemIndex\n )\n \"\n href=\"#\"\n (click)=\"\n $event.preventDefault();\n editItem(itemIndex)\n \"\n >\n Modifier\n </a>\n <span class=\"visually-hidden\">\n {{ getListItemTitle()(item) }}\n </span>\n </li>\n }\n @if (!item.disableDeletion) {\n <li class=\"list-inline-item\">\n \n <a\n [id]=\"\n buildChildId(\n '-delete-link-' + itemIndex\n )\n \"\n href=\"#\"\n (click)=\"\n $event.preventDefault();\n removeItem(itemIndex)\n \"\n >\n Supprimer\n <span class=\"visually-hidden\">\n {{ getListItemTitle()(item) }}\n </span>\n </a>\n </li>\n }\n </ul>\n </li>\n }\n </ul>\n </section>\n }\n\n @if (showAsTable() && !!tableConfiguration?.length) {\n <section>\n @if (!!listCopyForTable?.length) {\n <foehn-table\n [name]=\"buildChildName('table')\"\n [model]=\"listCopyForTable\"\n [itemsPerPage]=\"1000000\"\n [columnsConfiguration]=\"tableConfiguration\"\n [sort]=\"tableSort\"\n (sortChange)=\"changeSort($event)\"\n [trackByFn]=\"trackFoehnListItem\"\n />\n }\n </section>\n }\n\n @if (canAddItems()) {\n <section [class.mt-5]=\"!showAsTable()\">\n <h2 class=\"visually-hidden\">Action</h2>\n <ul class=\"list-inline mb-3\">\n <li class=\"list-inline-item\">\n <button\n [id]=\"buildChildId('-add-button')\"\n type=\"button\"\n class=\"btn btn-primary\"\n (click)=\"editItem()\"\n >\n Ajouter\n </button>\n </li>\n </ul>\n </section>\n }\n</section>\n\n<ng-template #tableActionButtons let-index=\"index\" let-item=\"item\">\n <div class=\"d-inline-flex\">\n @if (item.disableEdition && item.disableDeletion) {\n <button\n [id]=\"buildChildId('-see-button-' + index)\"\n type=\"button\"\n class=\"btn bg-transparent me-3\"\n (click)=\"editItem(index)\"\n >\n <foehn-icon-search [title]=\"'Consulter'\" />\n </button>\n }\n @if (!item.disableEdition) {\n <button\n [id]=\"buildChildId('-edit-button-' + index)\"\n type=\"button\"\n class=\"btn bg-transparent me-3\"\n (click)=\"editItem(index)\"\n >\n <foehn-icon-edit [title]=\"'Modifier'\" />\n </button>\n }\n @if (!item.disableDeletion) {\n <button\n [id]=\"buildChildId('-delete-button-' + index)\"\n type=\"button\"\n class=\"btn bg-transparent\"\n (click)=\"removeItem(index)\"\n >\n <foehn-icon-trash-alt [title]=\"'Supprimer'\" />\n </button>\n }\n </div>\n</ng-template>\n", styles: [":host ::ng-deep .bg-transparent.btn .svg-inline--fa{color:var(--vd-neutral-darker)!important}\n"], dependencies: [{ kind: "component", type: FoehnValidationAlertsComponent, selector: "foehn-validation-alerts", inputs: ["component", "shouldErrorsBeLive"] }, { kind: "component", type: FoehnErrorPillComponent, selector: "foehn-error-pill", inputs: ["errorPrefix", "incompleteIndicatorOnly"] }, { kind: "component", type: FoehnTableComponent, selector: "foehn-table", inputs: ["columnsConfiguration", "itemsPerPage", "fixedPageCount", "sort", "title", "totalElements", "titleSrOnly", "previousLabel", "nextLabel", "tableClass", "trackByFn"], outputs: ["columnsConfigurationChange", "sortChange", "pageChange", "rowClick"] }, { kind: "component", type: FoehnIconSearchComponent, selector: "foehn-icon-search" }, { kind: "component", type: FoehnIconEditComponent, selector: "foehn-icon-edit" }, { kind: "component", type: FoehnIconTrashAltComponent, selector: "foehn-icon-trash-alt" }, { kind: "pipe", type: SdkDictionaryPipe, name: "fromDictionary" }] }); }
|
|
14546
14544
|
}
|
|
14547
14545
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: FoehnListSummaryComponent, decorators: [{
|
|
14548
14546
|
type: Component,
|
|
@@ -14559,7 +14557,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
|
|
|
14559
14557
|
FoehnIconEditComponent,
|
|
14560
14558
|
FoehnIconTrashAltComponent,
|
|
14561
14559
|
SdkDictionaryPipe,
|
|
14562
|
-
], template: "<section\n class=\"form-group clearable-input-form-group\"\n [class.has-danger]=\"hasErrorsToDisplay()\"\n [class.vd-form-group-danger]=\"hasErrorsToDisplay()\"\n [attr.id]=\"buildId('Container')\"\n tabindex=\"-1\"\n>\n @if (label() && type !== 'hidden') {\n <label\n [class]=\"\n 'form-label ' +\n (isLabelSrOnly()\n ? 'visually-hidden'\n : (labelStyleModifier() ?? ''))\n \"\n [attr.for]=\"buildChildId()\"\n >\n <span [innerHTML]=\"label()\"></span>\n @if (!required() && !hideNotRequiredExtraLabel()) {\n <span aria-hidden=\"true\">\n {{ 'foehn-input.optional' | fromDictionary }}\n </span>\n }\n </label>\n }\n\n <foehn-validation-alerts [component]=\"this\" />\n\n @if (helpText() && type !== 'hidden') {\n <small\n [attr.id]=\"buildChildId() + 'Help'\"\n class=\"form-text text-secondary\"\n [innerHTML]=\"helpText()\"\n ></small>\n }\n\n @if (!showAsTable()) {\n <section>\n <ul class=\"list-unstyled\" aria-describedby=\"sommaire-help-alt\">\n @for (\n item of
|
|
14560
|
+
], template: "<section\n class=\"form-group clearable-input-form-group\"\n [class.has-danger]=\"hasErrorsToDisplay()\"\n [class.vd-form-group-danger]=\"hasErrorsToDisplay()\"\n [attr.id]=\"buildId('Container')\"\n tabindex=\"-1\"\n>\n @if (label() && type !== 'hidden') {\n <label\n [class]=\"\n 'form-label ' +\n (isLabelSrOnly()\n ? 'visually-hidden'\n : (labelStyleModifier() ?? ''))\n \"\n [attr.for]=\"buildChildId()\"\n >\n <span [innerHTML]=\"label()\"></span>\n @if (!required() && !hideNotRequiredExtraLabel()) {\n <span aria-hidden=\"true\">\n {{ 'foehn-input.optional' | fromDictionary }}\n </span>\n }\n </label>\n }\n\n <foehn-validation-alerts [component]=\"this\" />\n\n @if (helpText() && type !== 'hidden') {\n <small\n [attr.id]=\"buildChildId() + 'Help'\"\n class=\"form-text text-secondary\"\n [innerHTML]=\"helpText()\"\n ></small>\n }\n\n @if (!showAsTable()) {\n <section>\n <ul class=\"list-unstyled\" aria-describedby=\"sommaire-help-alt\">\n @for (\n item of model();\n track trackFoehnListItem(itemIndex, item);\n let itemIndex = $index\n ) {\n <li\n [id]=\"'list-summary-' + item.trackingIndex\"\n class=\"mt-3 border-bottom\"\n >\n <div class=\"d-flex align-items-baseline flex-wrap\">\n <h4 class=\"mt-0 me-3\">\n {{ getListItemTitle()(item) }}\n </h4>\n <span class=\"ms-auto\">\n <foehn-error-pill\n [incompleteIndicatorOnly]=\"true\"\n [errorPrefix]=\"\n name() + '[' + itemIndex + ']'\n \"\n />\n </span>\n </div>\n <dl class=\"mb-0\">\n @for (\n itemParam of listItemDescriptions();\n track trackFoehnListItemDescription(\n $index,\n itemParam\n )\n ) {\n @if (showLine(item, itemParam)) {\n <div class=\"d-flex flex-wrap item-line\">\n <dt class=\"me-1\">\n {{ itemParam.label }}\n </dt>\n <dd class=\"ms-auto text-end\">\n {{\n itemParam.getFormattedValue(\n item\n )\n }}\n </dd>\n </div>\n }\n }\n </dl>\n <ul class=\"list-inline mb-3 mt-0\">\n @if (item.disableEdition && item.disableDeletion) {\n <li class=\"list-inline-item\">\n <a\n [id]=\"\n buildChildId(\n '-see-link-' + itemIndex\n )\n \"\n href=\"#\"\n (click)=\"\n $event.preventDefault();\n editItem(itemIndex)\n \"\n >\n Consulter\n </a>\n <span class=\"visually-hidden\">\n {{ getListItemTitle()(item) }}\n </span>\n </li>\n }\n @if (!item.disableEdition) {\n <li class=\"list-inline-item\">\n <a\n [id]=\"\n buildChildId(\n '-edit-link-' + itemIndex\n )\n \"\n href=\"#\"\n (click)=\"\n $event.preventDefault();\n editItem(itemIndex)\n \"\n >\n Modifier\n </a>\n <span class=\"visually-hidden\">\n {{ getListItemTitle()(item) }}\n </span>\n </li>\n }\n @if (!item.disableDeletion) {\n <li class=\"list-inline-item\">\n \n <a\n [id]=\"\n buildChildId(\n '-delete-link-' + itemIndex\n )\n \"\n href=\"#\"\n (click)=\"\n $event.preventDefault();\n removeItem(itemIndex)\n \"\n >\n Supprimer\n <span class=\"visually-hidden\">\n {{ getListItemTitle()(item) }}\n </span>\n </a>\n </li>\n }\n </ul>\n </li>\n }\n </ul>\n </section>\n }\n\n @if (showAsTable() && !!tableConfiguration?.length) {\n <section>\n @if (!!listCopyForTable?.length) {\n <foehn-table\n [name]=\"buildChildName('table')\"\n [model]=\"listCopyForTable\"\n [itemsPerPage]=\"1000000\"\n [columnsConfiguration]=\"tableConfiguration\"\n [sort]=\"tableSort\"\n (sortChange)=\"changeSort($event)\"\n [trackByFn]=\"trackFoehnListItem\"\n />\n }\n </section>\n }\n\n @if (canAddItems()) {\n <section [class.mt-5]=\"!showAsTable()\">\n <h2 class=\"visually-hidden\">Action</h2>\n <ul class=\"list-inline mb-3\">\n <li class=\"list-inline-item\">\n <button\n [id]=\"buildChildId('-add-button')\"\n type=\"button\"\n class=\"btn btn-primary\"\n (click)=\"editItem()\"\n >\n Ajouter\n </button>\n </li>\n </ul>\n </section>\n }\n</section>\n\n<ng-template #tableActionButtons let-index=\"index\" let-item=\"item\">\n <div class=\"d-inline-flex\">\n @if (item.disableEdition && item.disableDeletion) {\n <button\n [id]=\"buildChildId('-see-button-' + index)\"\n type=\"button\"\n class=\"btn bg-transparent me-3\"\n (click)=\"editItem(index)\"\n >\n <foehn-icon-search [title]=\"'Consulter'\" />\n </button>\n }\n @if (!item.disableEdition) {\n <button\n [id]=\"buildChildId('-edit-button-' + index)\"\n type=\"button\"\n class=\"btn bg-transparent me-3\"\n (click)=\"editItem(index)\"\n >\n <foehn-icon-edit [title]=\"'Modifier'\" />\n </button>\n }\n @if (!item.disableDeletion) {\n <button\n [id]=\"buildChildId('-delete-button-' + index)\"\n type=\"button\"\n class=\"btn bg-transparent\"\n (click)=\"removeItem(index)\"\n >\n <foehn-icon-trash-alt [title]=\"'Supprimer'\" />\n </button>\n }\n </div>\n</ng-template>\n", styles: [":host ::ng-deep .bg-transparent.btn .svg-inline--fa{color:var(--vd-neutral-darker)!important}\n"] }]
|
|
14563
14561
|
}], propDecorators: { getListItemTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "getListItemTitle", required: false }] }], listItemDescriptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "listItemDescriptions", required: false }] }], showAsTable: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAsTable", required: false }] }], tableActionButtons: [{ type: i0.ViewChild, args: ['tableActionButtons', { isSignal: true }] }], canAddItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "canAddItems", required: false }] }], itemRemoved: [{ type: i0.Output, args: ["itemRemoved"] }], itemEdit: [{ type: i0.Output, args: ["itemEdit"] }] } });
|
|
14564
14562
|
|
|
14565
14563
|
class FoehnMenuItemComponent {
|