@dsivd/prestations-ng 19.0.6-beta.2 → 19.0.7

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/CONTRIBUTING.md +25 -0
  3. package/ESLINT_PLUGIN.md +198 -0
  4. package/README.md +3 -0
  5. package/UPGRADING_V19.md +38 -143
  6. package/dsivd-prestations-ng-19.0.7.tgz +0 -0
  7. package/eslint/configs/template-base.mjs +10 -0
  8. package/eslint/configs/template-recommended.mjs +24 -0
  9. package/eslint/configs/template-rules.mjs +17 -0
  10. package/eslint/configs/ts-base.mjs +10 -0
  11. package/eslint/configs/ts-recommended.mjs +142 -0
  12. package/eslint/configs/ts-rules.mjs +14 -0
  13. package/eslint/index.mjs +20 -5
  14. package/eslint/rules/index.mjs +7 -1
  15. package/eslint/rules/no-direct-signal-mutation.mjs +240 -136
  16. package/eslint/rules/no-uninvoked-signal-in-template.mjs +170 -0
  17. package/eslint/signal-names.mjs +232 -0
  18. package/eslint/template-ast.mjs +26 -0
  19. package/fesm2022/dsivd-prestations-ng.mjs +7 -9
  20. package/fesm2022/dsivd-prestations-ng.mjs.map +1 -1
  21. package/package.json +1 -1
  22. package/src/eslint/configs/__tests__/configs.test.mjs +135 -0
  23. package/src/eslint/configs/template-base.mjs +10 -0
  24. package/src/eslint/configs/template-recommended.mjs +24 -0
  25. package/src/eslint/configs/template-rules.mjs +17 -0
  26. package/src/eslint/configs/ts-base.mjs +10 -0
  27. package/src/eslint/configs/ts-recommended.mjs +142 -0
  28. package/src/eslint/configs/ts-rules.mjs +14 -0
  29. package/src/eslint/index.mjs +20 -5
  30. package/src/eslint/rules/__tests__/no-direct-signal-mutation.test.mjs +86 -4
  31. package/src/eslint/rules/__tests__/no-uninvoked-signal-in-template.test.mjs +291 -0
  32. package/src/eslint/rules/index.mjs +7 -1
  33. package/src/eslint/rules/no-direct-signal-mutation.mjs +240 -136
  34. package/src/eslint/rules/no-uninvoked-signal-in-template.mjs +170 -0
  35. package/src/eslint/signal-names.mjs +232 -0
  36. package/src/eslint/template-ast.mjs +26 -0
  37. package/types/dsivd-prestations-ng.d.ts +0 -2
  38. package/dsivd-prestations-ng-19.0.6-beta.2.tgz +0 -0
  39. package/eslint/rules/__tests__/no-direct-signal-mutation.test.mjs +0 -98
@@ -0,0 +1,170 @@
1
+ import { collectSignalNames } from "../signal-names.mjs";
2
+ import { handlerExpressionOf, isComponentReceiver } from "../template-ast.mjs";
3
+
4
+ /**
5
+ * ESLint rule to detect signals referenced without being called in a template.
6
+ * Warns against patterns like: {{ mySignal }}, !mySignal, mySignal + 'x'
7
+ * Suggests calling the signal instead: mySignal()
8
+ *
9
+ * The compiler only covers part of these cases (NG8109 / NG8117): it lets through
10
+ * the negation (`!mySignal`, always false) and the concatenation (`mySignal + 'x'`,
11
+ * which injects the source code of the function).
12
+ *
13
+ * Signal names are resolved from the template's twin `.ts` and from its inheritance
14
+ * chain, base classes of other packages included. Nothing has to be configured.
15
+ */
16
+
17
+ // `mySignal.set(…)` targets the signal itself, not its value: no parentheses needed.
18
+ const SIGNAL_MEMBERS = new Set(["set", "update", "asReadonly"]);
19
+
20
+ // Implicit template variables: never signals of the component.
21
+ const TEMPLATE_BUILTINS = [
22
+ "$event",
23
+ "$index",
24
+ "$count",
25
+ "$first",
26
+ "$last",
27
+ "$even",
28
+ "$odd",
29
+ "$any",
30
+ "$implicit",
31
+ ];
32
+
33
+ // An inline template is a virtual block whose path is derived from the `.ts`, and whose
34
+ // name ends with `.html` too: `foo.component.ts/1_inline-template-….component.html`.
35
+ // It has to be matched BEFORE the external template, otherwise it resolves to garbage.
36
+ function resolveComponentPath(filename) {
37
+ const inlineMatch = filename.match(/^(.*\.ts)(?:[/\\]|$)/);
38
+ if (inlineMatch) {
39
+ return inlineMatch[1];
40
+ }
41
+ return filename.endsWith(".html")
42
+ ? `${filename.slice(0, -".html".length)}.ts`
43
+ : undefined;
44
+ }
45
+
46
+ export default {
47
+ meta: {
48
+ type: "problem",
49
+ docs: {
50
+ description:
51
+ "Warn on signals referenced without being called in an Angular template",
52
+ category: "Best Practices",
53
+ recommended: true,
54
+ },
55
+ fixable: "code",
56
+ schema: [],
57
+ messages: {
58
+ uninvoked:
59
+ "Signal '{{name}}' is referenced without being called. Use '{{name}}()' instead: a bare reference is the function itself, always truthy and serialized as source code.",
60
+ },
61
+ },
62
+
63
+ create(context) {
64
+ const componentPath = resolveComponentPath(context.filename);
65
+ if (!componentPath) {
66
+ return {};
67
+ }
68
+
69
+ const signalNames = collectSignalNames(componentPath);
70
+ if (signalNames.size === 0) {
71
+ return {};
72
+ }
73
+
74
+ // Names bound by the template (#ref, @for items, @if aliases, @let, let-x): they
75
+ // shadow the class members, so they are never reported.
76
+ const shadowed = new Set(TEMPLATE_BUILTINS);
77
+ // References that already carry parentheses (`x()`) or that address the signal
78
+ // itself (`x.set(…)`).
79
+ const exempt = new Set();
80
+ // Spans already handled, by position. Holds the written positions (lvalue): a
81
+ // writable signal is bound WITHOUT parentheses — `[(x)]="mySignal"` — Angular
82
+ // calling `.set()` itself. A two-way binding also duplicates its expression
83
+ // (BoundAttribute + BoundEvent), so the same set dedupes the reports.
84
+ const handledSpans = new Set();
85
+ const candidates = [];
86
+
87
+ const markHandled = (node) => {
88
+ const { start, end } = node?.sourceSpan ?? {};
89
+ if (typeof start === "number") {
90
+ handledSpans.add(`${start}:${end}`);
91
+ }
92
+ };
93
+
94
+ const rememberDeclarations = (node) => {
95
+ for (const declaration of [
96
+ ...(node.references ?? []),
97
+ ...(node.variables ?? []),
98
+ ]) {
99
+ shadowed.add(declaration.name);
100
+ }
101
+ };
102
+
103
+ return {
104
+ Call: (node) => exempt.add(node.receiver),
105
+ SafeCall: (node) => exempt.add(node.receiver),
106
+ Element: rememberDeclarations,
107
+ Template: rememberDeclarations,
108
+ Content: rememberDeclarations,
109
+ BoundEvent(node) {
110
+ // `[(x)]="expr"` generates a BoundEvent `xChange` whose handler IS the written
111
+ // target. Its span is the one of the twin BoundAttribute expression: marking
112
+ // the span neutralizes both copies at once.
113
+ if (node.name?.endsWith("Change")) {
114
+ markHandled(handlerExpressionOf(node));
115
+ }
116
+ },
117
+ Binary(node) {
118
+ if (node.operation === "=") {
119
+ markHandled(node.left);
120
+ }
121
+ },
122
+ LetDeclaration(node) {
123
+ shadowed.add(node.name);
124
+ },
125
+ IfBlockBranch(node) {
126
+ if (node.expressionAlias) {
127
+ shadowed.add(node.expressionAlias.name);
128
+ }
129
+ },
130
+ ForLoopBlock(node) {
131
+ shadowed.add(node.item.name);
132
+ for (const contextVariable of node.contextVariables ?? []) {
133
+ shadowed.add(contextVariable.name);
134
+ }
135
+ },
136
+ PropertyRead(node) {
137
+ if (SIGNAL_MEMBERS.has(node.name)) {
138
+ exempt.add(node.receiver);
139
+ }
140
+ if (isComponentReceiver(node.receiver) && signalNames.has(node.name)) {
141
+ candidates.push(node);
142
+ }
143
+ },
144
+ "Program:exit"() {
145
+ for (const node of candidates) {
146
+ const { start, end } = node.sourceSpan;
147
+ const span = `${start}:${end}`;
148
+ if (
149
+ exempt.has(node) ||
150
+ shadowed.has(node.name) ||
151
+ handledSpans.has(span)
152
+ ) {
153
+ continue;
154
+ }
155
+ handledSpans.add(span);
156
+
157
+ context.report({
158
+ messageId: "uninvoked",
159
+ data: { name: node.name },
160
+ loc: {
161
+ start: context.sourceCode.getLocFromIndex(start),
162
+ end: context.sourceCode.getLocFromIndex(end),
163
+ },
164
+ fix: (fixer) => fixer.insertTextAfterRange([start, end], "()"),
165
+ });
166
+ }
167
+ },
168
+ };
169
+ },
170
+ };
@@ -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.internalListItemDescriptions();
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.internalList().filter((_item, itemIndex) => itemIndex !== index);
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.internalList(), sortEvent);
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.internalListItemDescriptions();
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.internalList()) {
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 &nbsp;\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 &nbsp;\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 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 &nbsp;\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"] }]
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 &nbsp;\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 {