@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.
- package/CHANGELOG.md +31 -0
- package/CONTRIBUTING.md +25 -0
- package/ESLINT_PLUGIN.md +198 -0
- package/README.md +3 -0
- package/UPGRADING_V19.md +38 -143
- package/dsivd-prestations-ng-19.0.7.tgz +0 -0
- package/eslint/configs/template-base.mjs +10 -0
- package/eslint/configs/template-recommended.mjs +24 -0
- package/eslint/configs/template-rules.mjs +17 -0
- package/eslint/configs/ts-base.mjs +10 -0
- package/eslint/configs/ts-recommended.mjs +142 -0
- package/eslint/configs/ts-rules.mjs +14 -0
- package/eslint/index.mjs +20 -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 +135 -0
- package/src/eslint/configs/template-base.mjs +10 -0
- package/src/eslint/configs/template-recommended.mjs +24 -0
- package/src/eslint/configs/template-rules.mjs +17 -0
- package/src/eslint/configs/ts-base.mjs +10 -0
- package/src/eslint/configs/ts-recommended.mjs +142 -0
- package/src/eslint/configs/ts-rules.mjs +14 -0
- package/src/eslint/index.mjs +20 -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,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.
|
|
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 {
|