@truenas/ui-components 0.1.47 → 0.1.48
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/README.md +28 -1
- package/assets/tn-icons/forwarding-mappings.json +33 -0
- package/fesm2022/truenas-ui-components.mjs.map +1 -1
- package/package.json +1 -1
- package/scripts/icon-sprite/lib/find-icons-in-forwarding-components.spec.ts +159 -54
- package/scripts/icon-sprite/lib/find-icons-in-forwarding-components.ts +35 -176
- package/types/truenas-ui-components.d.ts +4 -35
- package/scripts/icon-sprite/__fixtures__/forwarding-components/multi-icon.component.html +0 -7
- package/scripts/icon-sprite/__fixtures__/forwarding-components/multi-icon.component.ts +0 -15
- package/scripts/icon-sprite/__fixtures__/forwarding-components/no-library.component.html +0 -3
- package/scripts/icon-sprite/__fixtures__/forwarding-components/no-library.component.ts +0 -11
- package/scripts/icon-sprite/__fixtures__/forwarding-components/not-forwarding.component.html +0 -1
- package/scripts/icon-sprite/__fixtures__/forwarding-components/not-forwarding.component.ts +0 -10
- package/scripts/icon-sprite/__fixtures__/forwarding-components/single-icon.component.html +0 -3
- package/scripts/icon-sprite/__fixtures__/forwarding-components/single-icon.component.ts +0 -13
package/package.json
CHANGED
|
@@ -1,77 +1,182 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
1
3
|
import path from 'path';
|
|
2
|
-
import {
|
|
4
|
+
import { discoverForwardingMappings } from './find-icons-in-forwarding-components';
|
|
5
|
+
import type { ForwardingComponentMapping } from './find-icons-in-forwarding-components';
|
|
3
6
|
|
|
4
|
-
|
|
7
|
+
function writeManifest(dir: string, mappings: ForwardingComponentMapping[]): void {
|
|
8
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
9
|
+
fs.writeFileSync(
|
|
10
|
+
path.join(dir, 'forwarding-mappings.json'),
|
|
11
|
+
JSON.stringify(mappings),
|
|
12
|
+
);
|
|
13
|
+
}
|
|
5
14
|
|
|
6
|
-
describe('
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
15
|
+
describe('discoverForwardingMappings', () => {
|
|
16
|
+
let tempDir: string;
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'fwd-mappings-'));
|
|
11
20
|
});
|
|
12
21
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const selectors = mappings.map(m => m.selector);
|
|
16
|
-
expect(selectors).not.toContain('tn-button');
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
17
24
|
});
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
26
|
+
describe('library manifest loading', () => {
|
|
27
|
+
it('should load mappings from the installed library manifest', () => {
|
|
28
|
+
const manifestDir = path.join(tempDir, 'node_modules/@truenas/ui-components/assets/tn-icons');
|
|
29
|
+
writeManifest(manifestDir, [
|
|
30
|
+
{
|
|
31
|
+
selector: 'tn-empty',
|
|
32
|
+
iconSlots: [{ iconAttribute: 'icon', libraryAttribute: 'iconLibrary', defaultLibrary: 'mdi' }],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
selector: 'tn-chip',
|
|
36
|
+
iconSlots: [{ iconAttribute: 'icon' }],
|
|
37
|
+
},
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
const mappings = discoverForwardingMappings([], tempDir);
|
|
41
|
+
expect(mappings).toHaveLength(2);
|
|
22
42
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
expect(empty!.iconSlots[0]).toEqual({
|
|
26
|
-
iconAttribute: 'icon',
|
|
27
|
-
libraryAttribute: 'iconLibrary',
|
|
28
|
-
defaultLibrary: 'mdi',
|
|
43
|
+
const selectors = mappings.map(m => m.selector).sort();
|
|
44
|
+
expect(selectors).toEqual(['tn-chip', 'tn-empty']);
|
|
29
45
|
});
|
|
30
|
-
});
|
|
31
46
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
47
|
+
it('should extract icon slot details from manifest', () => {
|
|
48
|
+
const manifestDir = path.join(tempDir, 'node_modules/@truenas/ui-components/assets/tn-icons');
|
|
49
|
+
writeManifest(manifestDir, [
|
|
50
|
+
{
|
|
51
|
+
selector: 'tn-empty',
|
|
52
|
+
iconSlots: [{ iconAttribute: 'icon', libraryAttribute: 'iconLibrary', defaultLibrary: 'mdi' }],
|
|
53
|
+
},
|
|
54
|
+
]);
|
|
35
55
|
|
|
36
|
-
|
|
37
|
-
|
|
56
|
+
const mappings = discoverForwardingMappings([], tempDir);
|
|
57
|
+
expect(mappings[0].iconSlots[0]).toEqual({
|
|
58
|
+
iconAttribute: 'icon',
|
|
59
|
+
libraryAttribute: 'iconLibrary',
|
|
60
|
+
defaultLibrary: 'mdi',
|
|
61
|
+
});
|
|
62
|
+
});
|
|
38
63
|
|
|
39
|
-
|
|
40
|
-
|
|
64
|
+
it('should return empty array when library is not installed', () => {
|
|
65
|
+
const mappings = discoverForwardingMappings([], tempDir);
|
|
66
|
+
expect(mappings).toEqual([]);
|
|
67
|
+
});
|
|
41
68
|
|
|
42
|
-
|
|
43
|
-
|
|
69
|
+
it('should handle corrupt library manifest gracefully', () => {
|
|
70
|
+
const manifestDir = path.join(tempDir, 'node_modules/@truenas/ui-components/assets/tn-icons');
|
|
71
|
+
fs.mkdirSync(manifestDir, { recursive: true });
|
|
72
|
+
fs.writeFileSync(path.join(manifestDir, 'forwarding-mappings.json'), 'not valid json');
|
|
44
73
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
74
|
+
const mappings = discoverForwardingMappings([], tempDir);
|
|
75
|
+
expect(mappings).toEqual([]);
|
|
76
|
+
});
|
|
48
77
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
78
|
+
it('should handle manifest with non-array content gracefully', () => {
|
|
79
|
+
const manifestDir = path.join(tempDir, 'node_modules/@truenas/ui-components/assets/tn-icons');
|
|
80
|
+
fs.mkdirSync(manifestDir, { recursive: true });
|
|
81
|
+
fs.writeFileSync(path.join(manifestDir, 'forwarding-mappings.json'), '{"not": "an array"}');
|
|
52
82
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
expect(chip!.iconSlots[0].libraryAttribute).toBeUndefined();
|
|
57
|
-
expect(chip!.iconSlots[0].defaultLibrary).toBeUndefined();
|
|
83
|
+
const mappings = discoverForwardingMappings([], tempDir);
|
|
84
|
+
expect(mappings).toEqual([]);
|
|
85
|
+
});
|
|
58
86
|
});
|
|
59
87
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
88
|
+
describe('consumer manifest loading', () => {
|
|
89
|
+
it('should load mappings from a consumer source directory', () => {
|
|
90
|
+
const consumerSrc = path.join(tempDir, 'src/app');
|
|
91
|
+
writeManifest(consumerSrc, [
|
|
92
|
+
{
|
|
93
|
+
selector: 'app-status',
|
|
94
|
+
iconSlots: [{ iconAttribute: 'statusIcon', libraryAttribute: 'statusIconLibrary' }],
|
|
95
|
+
},
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
const mappings = discoverForwardingMappings([consumerSrc], tempDir);
|
|
99
|
+
expect(mappings).toHaveLength(1);
|
|
100
|
+
expect(mappings[0].selector).toBe('app-status');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should ignore source dirs without a manifest', () => {
|
|
104
|
+
const emptySrc = path.join(tempDir, 'src/app');
|
|
105
|
+
fs.mkdirSync(emptySrc, { recursive: true });
|
|
106
|
+
|
|
107
|
+
const mappings = discoverForwardingMappings([emptySrc], tempDir);
|
|
108
|
+
expect(mappings).toEqual([]);
|
|
109
|
+
});
|
|
64
110
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
111
|
+
it('should handle non-existent source dirs', () => {
|
|
112
|
+
const mappings = discoverForwardingMappings(['/non/existent/path'], tempDir);
|
|
113
|
+
expect(mappings).toEqual([]);
|
|
114
|
+
});
|
|
68
115
|
});
|
|
69
116
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
117
|
+
describe('merging library and consumer manifests', () => {
|
|
118
|
+
it('should merge mappings from library and consumer', () => {
|
|
119
|
+
// Library provides tn-empty
|
|
120
|
+
const libraryDir = path.join(tempDir, 'node_modules/@truenas/ui-components/assets/tn-icons');
|
|
121
|
+
writeManifest(libraryDir, [
|
|
122
|
+
{
|
|
123
|
+
selector: 'tn-empty',
|
|
124
|
+
iconSlots: [{ iconAttribute: 'icon', libraryAttribute: 'iconLibrary', defaultLibrary: 'mdi' }],
|
|
125
|
+
},
|
|
126
|
+
]);
|
|
127
|
+
|
|
128
|
+
// Consumer provides app-status
|
|
129
|
+
const consumerSrc = path.join(tempDir, 'src/app');
|
|
130
|
+
writeManifest(consumerSrc, [
|
|
131
|
+
{
|
|
132
|
+
selector: 'app-status',
|
|
133
|
+
iconSlots: [{ iconAttribute: 'statusIcon' }],
|
|
134
|
+
},
|
|
135
|
+
]);
|
|
136
|
+
|
|
137
|
+
const mappings = discoverForwardingMappings([consumerSrc], tempDir);
|
|
138
|
+
const selectors = mappings.map(m => m.selector).sort();
|
|
139
|
+
expect(selectors).toEqual(['app-status', 'tn-empty']);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should allow consumer to override a library mapping by selector', () => {
|
|
143
|
+
// Library defines tn-empty with defaultLibrary: 'mdi'
|
|
144
|
+
const libraryDir = path.join(tempDir, 'node_modules/@truenas/ui-components/assets/tn-icons');
|
|
145
|
+
writeManifest(libraryDir, [
|
|
146
|
+
{
|
|
147
|
+
selector: 'tn-empty',
|
|
148
|
+
iconSlots: [{ iconAttribute: 'icon', libraryAttribute: 'iconLibrary', defaultLibrary: 'mdi' }],
|
|
149
|
+
},
|
|
150
|
+
]);
|
|
151
|
+
|
|
152
|
+
// Consumer overrides tn-empty with a different default
|
|
153
|
+
const consumerSrc = path.join(tempDir, 'src/app');
|
|
154
|
+
writeManifest(consumerSrc, [
|
|
155
|
+
{
|
|
156
|
+
selector: 'tn-empty',
|
|
157
|
+
iconSlots: [{ iconAttribute: 'icon', libraryAttribute: 'iconLibrary', defaultLibrary: 'material' }],
|
|
158
|
+
},
|
|
159
|
+
]);
|
|
160
|
+
|
|
161
|
+
const mappings = discoverForwardingMappings([consumerSrc], tempDir);
|
|
162
|
+
expect(mappings).toHaveLength(1);
|
|
163
|
+
expect(mappings[0].iconSlots[0].defaultLibrary).toBe('material');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should merge manifests from multiple consumer source dirs', () => {
|
|
167
|
+
const srcApp = path.join(tempDir, 'src/app');
|
|
168
|
+
writeManifest(srcApp, [
|
|
169
|
+
{ selector: 'app-foo', iconSlots: [{ iconAttribute: 'icon' }] },
|
|
170
|
+
]);
|
|
171
|
+
|
|
172
|
+
const srcLib = path.join(tempDir, 'src/lib');
|
|
173
|
+
writeManifest(srcLib, [
|
|
174
|
+
{ selector: 'app-bar', iconSlots: [{ iconAttribute: 'barIcon' }] },
|
|
175
|
+
]);
|
|
176
|
+
|
|
177
|
+
const mappings = discoverForwardingMappings([srcApp, srcLib], tempDir);
|
|
178
|
+
const selectors = mappings.map(m => m.selector).sort();
|
|
179
|
+
expect(selectors).toEqual(['app-bar', 'app-foo']);
|
|
180
|
+
});
|
|
76
181
|
});
|
|
77
182
|
});
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process';
|
|
2
1
|
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
2
|
import { resolve } from 'path';
|
|
5
|
-
import * as cheerio from 'cheerio';
|
|
6
3
|
|
|
7
4
|
export interface IconSlotMapping {
|
|
8
5
|
iconAttribute: string;
|
|
@@ -16,23 +13,33 @@ export interface ForwardingComponentMapping {
|
|
|
16
13
|
}
|
|
17
14
|
|
|
18
15
|
/**
|
|
19
|
-
*
|
|
20
|
-
* their icon attribute mappings by reading their templates.
|
|
16
|
+
* Load forwarding component mappings from JSON manifest files.
|
|
21
17
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
18
|
+
* Mappings tell the sprite scanner which component attributes forward icon
|
|
19
|
+
* values to `<tn-icon>`. For example, `<tn-empty icon="inbox">` forwards
|
|
20
|
+
* its `icon` attribute to an internal `<tn-icon [name]>`.
|
|
21
|
+
*
|
|
22
|
+
* Sources (merged in order, later entries override by selector):
|
|
23
|
+
* 1. The library's published manifest at
|
|
24
|
+
* `node_modules/@truenas/ui-components/assets/tn-icons/forwarding-mappings.json`
|
|
25
|
+
* 2. Any `forwarding-mappings.json` files found in the consumer's source dirs
|
|
28
26
|
*/
|
|
29
|
-
export function
|
|
27
|
+
export function discoverForwardingMappings(srcDirs: string[], projectRoot: string): ForwardingComponentMapping[] {
|
|
30
28
|
const bySelector = new Map<string, ForwardingComponentMapping>();
|
|
31
|
-
const componentFiles = findForwardingComponentFiles(searchPaths);
|
|
32
29
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
// Load library manifest (published with the npm package)
|
|
31
|
+
const libraryMappings = loadManifest(resolve(
|
|
32
|
+
projectRoot,
|
|
33
|
+
'node_modules/@truenas/ui-components/assets/tn-icons/forwarding-mappings.json',
|
|
34
|
+
));
|
|
35
|
+
for (const mapping of libraryMappings) {
|
|
36
|
+
bySelector.set(mapping.selector, mapping);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Load consumer manifests from source dirs
|
|
40
|
+
for (const srcDir of srcDirs) {
|
|
41
|
+
const consumerManifest = resolve(srcDir, 'forwarding-mappings.json');
|
|
42
|
+
for (const mapping of loadManifest(consumerManifest)) {
|
|
36
43
|
bySelector.set(mapping.selector, mapping);
|
|
37
44
|
}
|
|
38
45
|
}
|
|
@@ -41,169 +48,21 @@ export function findForwardingComponentMappings(searchPaths: string[]): Forwardi
|
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
51
|
+
* Read and parse a forwarding-mappings.json file.
|
|
52
|
+
* Returns an empty array if the file is missing or malformed.
|
|
46
53
|
*/
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const libSrcPath = resolve(projectRoot, 'node_modules/@truenas/ui-components/src/lib');
|
|
51
|
-
if (fs.existsSync(libSrcPath)) {
|
|
52
|
-
searchPaths.push(libSrcPath);
|
|
54
|
+
function loadManifest(filePath: string): ForwardingComponentMapping[] {
|
|
55
|
+
if (!fs.existsSync(filePath)) {
|
|
56
|
+
return [];
|
|
53
57
|
}
|
|
54
58
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
* Grep for .ts files containing `implements TnIconForwardingComponent`
|
|
60
|
-
* (handles multi-interface: `implements TnIconForwardingComponent, AfterViewInit`)
|
|
61
|
-
*/
|
|
62
|
-
function findForwardingComponentFiles(searchPaths: string[]): string[] {
|
|
63
|
-
const files: string[] = [];
|
|
64
|
-
|
|
65
|
-
for (const searchPath of searchPaths) {
|
|
66
|
-
if (!fs.existsSync(searchPath)) {
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const result = spawnSync(
|
|
71
|
-
'grep',
|
|
72
|
-
['-rl', 'implements.*TnIconForwardingComponent', '--include=*.ts', searchPath],
|
|
73
|
-
{ encoding: 'utf-8' },
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
if (result.status === 1 && !result.stderr) {
|
|
77
|
-
// grep returns exit code 1 when no matches found
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
if (result.status !== 0 && result.status !== 1) {
|
|
81
|
-
throw new Error(`grep failed: ${result.stderr}`);
|
|
59
|
+
try {
|
|
60
|
+
const data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
61
|
+
if (!Array.isArray(data)) {
|
|
62
|
+
return [];
|
|
82
63
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
.filter(Boolean)
|
|
87
|
-
.forEach((file) => files.push(file));
|
|
64
|
+
return data;
|
|
65
|
+
} catch {
|
|
66
|
+
return [];
|
|
88
67
|
}
|
|
89
|
-
|
|
90
|
-
return files;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Extract the forwarding component mapping from a single component .ts file.
|
|
95
|
-
*
|
|
96
|
-
* Reads the TS file to get the selector and templateUrl, then reads the
|
|
97
|
-
* template to find <tn-icon> bindings and maps them back to input names.
|
|
98
|
-
*/
|
|
99
|
-
function extractMappingFromComponent(tsFilePath: string): ForwardingComponentMapping | null {
|
|
100
|
-
const tsContent = fs.readFileSync(tsFilePath, 'utf-8');
|
|
101
|
-
|
|
102
|
-
// Extract selector from @Component({ selector: '...' })
|
|
103
|
-
const selectorMatch = /selector:\s*['"]([^'"]+)['"]/.exec(tsContent);
|
|
104
|
-
if (!selectorMatch) {
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
const selector = selectorMatch[1];
|
|
108
|
-
|
|
109
|
-
// Extract templateUrl from @Component({ templateUrl: '...' })
|
|
110
|
-
const templateUrlMatch = /templateUrl:\s*['"]([^'"]+)['"]/.exec(tsContent);
|
|
111
|
-
if (!templateUrlMatch) {
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Resolve template path relative to the TS file
|
|
116
|
-
const templatePath = path.resolve(path.dirname(tsFilePath), templateUrlMatch[1]);
|
|
117
|
-
if (!fs.existsSync(templatePath)) {
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const templateContent = fs.readFileSync(templatePath, 'utf-8');
|
|
122
|
-
const iconSlots = extractIconSlotsFromTemplate(templateContent, tsContent);
|
|
123
|
-
|
|
124
|
-
if (iconSlots.length === 0) {
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return { selector, iconSlots };
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Find <tn-icon> elements in the template and extract the input names
|
|
133
|
-
* from their [name] and [library] bindings.
|
|
134
|
-
*
|
|
135
|
-
* For example:
|
|
136
|
-
* <tn-icon [name]="icon()!" [library]="iconLibrary()">
|
|
137
|
-
* yields: { iconAttribute: 'icon', libraryAttribute: 'iconLibrary' }
|
|
138
|
-
*
|
|
139
|
-
* <tn-icon [name]="prefixIcon()!">
|
|
140
|
-
* yields: { iconAttribute: 'prefixIcon' }
|
|
141
|
-
*/
|
|
142
|
-
function extractIconSlotsFromTemplate(templateContent: string, tsContent: string): IconSlotMapping[] {
|
|
143
|
-
const $ = cheerio.load(templateContent);
|
|
144
|
-
const slots: IconSlotMapping[] = [];
|
|
145
|
-
|
|
146
|
-
$('tn-icon').each((_, el) => {
|
|
147
|
-
const boundName = $(el).attr('[name]');
|
|
148
|
-
if (!boundName) {
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Extract the signal/input name from the binding expression
|
|
153
|
-
// Handles: "icon()", "icon()!", "prefixIcon()!", etc.
|
|
154
|
-
const inputName = extractInputName(boundName);
|
|
155
|
-
if (!inputName) {
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const slot: IconSlotMapping = { iconAttribute: inputName };
|
|
160
|
-
|
|
161
|
-
// Check for [library] binding on the same <tn-icon>
|
|
162
|
-
const boundLibrary = $(el).attr('[library]');
|
|
163
|
-
if (boundLibrary) {
|
|
164
|
-
const libraryInputName = extractInputName(boundLibrary);
|
|
165
|
-
if (libraryInputName) {
|
|
166
|
-
slot.libraryAttribute = libraryInputName;
|
|
167
|
-
|
|
168
|
-
// Try to extract the default value from the input declaration in the TS
|
|
169
|
-
const defaultLibrary = extractInputDefault(tsContent, libraryInputName);
|
|
170
|
-
if (defaultLibrary) {
|
|
171
|
-
slot.defaultLibrary = defaultLibrary;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
slots.push(slot);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
return slots;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Extract a simple input/signal name from a template binding expression.
|
|
184
|
-
*
|
|
185
|
-
* Matches patterns like:
|
|
186
|
-
* "icon()" -> "icon"
|
|
187
|
-
* "icon()!" -> "icon"
|
|
188
|
-
* "prefixIcon()!" -> "prefixIcon"
|
|
189
|
-
*
|
|
190
|
-
* Returns null for complex expressions (ternaries, method calls with args, etc.)
|
|
191
|
-
* since those represent computed values, not direct input forwarding.
|
|
192
|
-
*/
|
|
193
|
-
function extractInputName(expression: string): string | null {
|
|
194
|
-
const match = /^\s*(\w+)\(\)!?\s*$/.exec(expression);
|
|
195
|
-
return match ? match[1] : null;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Extract the default value from an Angular input declaration.
|
|
200
|
-
*
|
|
201
|
-
* Matches patterns like:
|
|
202
|
-
* iconLibrary = input<IconLibraryType>('mdi') -> "mdi"
|
|
203
|
-
* iconLibrary = input('mdi') -> "mdi"
|
|
204
|
-
*/
|
|
205
|
-
function extractInputDefault(tsContent: string, inputName: string): string | null {
|
|
206
|
-
const regex = new RegExp(`${inputName}\\s*=\\s*input[^(]*\\(\\s*['"]([^'"]+)['"]`);
|
|
207
|
-
const match = regex.exec(tsContent);
|
|
208
|
-
return match ? match[1] : null;
|
|
209
68
|
}
|
|
@@ -839,38 +839,7 @@ declare enum InputType {
|
|
|
839
839
|
PlainText = "text"
|
|
840
840
|
}
|
|
841
841
|
|
|
842
|
-
|
|
843
|
-
* Marker interface for components that forward icon inputs to `<tn-icon>`.
|
|
844
|
-
*
|
|
845
|
-
* When a component implements this interface, the sprite generation scanner
|
|
846
|
-
* will read its template to discover which inputs map to `<tn-icon>` `[name]`
|
|
847
|
-
* and `[library]` bindings, then scan consumer templates for those attributes.
|
|
848
|
-
*
|
|
849
|
-
* This means icons passed as template attributes to these components are
|
|
850
|
-
* automatically included in the sprite — no `tnIconMarker()` needed.
|
|
851
|
-
*
|
|
852
|
-
* @example
|
|
853
|
-
* ```typescript
|
|
854
|
-
* @Component({
|
|
855
|
-
* selector: 'tn-empty',
|
|
856
|
-
* templateUrl: './empty.component.html',
|
|
857
|
-
* })
|
|
858
|
-
* export class TnEmptyComponent implements TnIconForwardingComponent {
|
|
859
|
-
* icon = input<string>();
|
|
860
|
-
* iconLibrary = input<IconLibraryType>('mdi');
|
|
861
|
-
* }
|
|
862
|
-
* ```
|
|
863
|
-
*
|
|
864
|
-
* The scanner will detect that `tn-empty` forwards `icon` to `<tn-icon [name]>`,
|
|
865
|
-
* so `<tn-empty icon="inbox">` in consumer templates will automatically include
|
|
866
|
-
* `mdi-inbox` in the sprite.
|
|
867
|
-
*
|
|
868
|
-
* @public
|
|
869
|
-
*/
|
|
870
|
-
interface TnIconForwardingComponent {
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
declare class TnInputComponent implements TnIconForwardingComponent, AfterViewInit, ControlValueAccessor {
|
|
842
|
+
declare class TnInputComponent implements AfterViewInit, ControlValueAccessor {
|
|
874
843
|
inputEl: _angular_core.Signal<ElementRef<HTMLInputElement | HTMLTextAreaElement>>;
|
|
875
844
|
inputType: _angular_core.InputSignal<InputType>;
|
|
876
845
|
placeholder: _angular_core.InputSignal<string>;
|
|
@@ -1212,7 +1181,7 @@ declare class TnInputDirective {
|
|
|
1212
1181
|
}
|
|
1213
1182
|
|
|
1214
1183
|
type ChipColor = 'primary' | 'secondary' | 'accent';
|
|
1215
|
-
declare class TnChipComponent implements
|
|
1184
|
+
declare class TnChipComponent implements AfterViewInit, OnDestroy {
|
|
1216
1185
|
chipEl: _angular_core.Signal<ElementRef<HTMLElement>>;
|
|
1217
1186
|
label: _angular_core.InputSignal<string>;
|
|
1218
1187
|
icon: _angular_core.InputSignal<string | undefined>;
|
|
@@ -5552,7 +5521,7 @@ declare class TnFilePickerPopupComponent implements OnInit, AfterViewInit, After
|
|
|
5552
5521
|
}
|
|
5553
5522
|
|
|
5554
5523
|
type TnEmptySize = 'default' | 'compact';
|
|
5555
|
-
declare class TnEmptyComponent
|
|
5524
|
+
declare class TnEmptyComponent {
|
|
5556
5525
|
title: _angular_core.InputSignal<string>;
|
|
5557
5526
|
description: _angular_core.InputSignal<string | undefined>;
|
|
5558
5527
|
icon: _angular_core.InputSignal<string | undefined>;
|
|
@@ -6100,4 +6069,4 @@ declare const TN_THEME_DEFINITIONS: readonly TnThemeDefinition[];
|
|
|
6100
6069
|
declare const THEME_MAP: Map<TnTheme, TnThemeDefinition>;
|
|
6101
6070
|
|
|
6102
6071
|
export { CommonShortcuts, DEFAULT_THEME, DiskIconComponent, DiskType, FileSizePipe, InputType, LIGHT_THEME, LinuxModifierKeys, LinuxShortcuts, ModifierKeys, QuickShortcuts, ShortcutBuilder, StripMntPrefixPipe, THEME_MAP, THEME_STORAGE_KEY, TN_THEME_DEFINITIONS, TnAutocompleteComponent, TnAutocompleteHarness, TnBannerActionDirective, TnBannerComponent, TnBannerHarness, TnBrandedSpinnerComponent, TnButtonComponent, TnButtonHarness, TnButtonToggleComponent, TnButtonToggleGroupComponent, TnButtonToggleGroupHarness, TnButtonToggleHarness, TnCalendarComponent, TnCalendarHeaderComponent, TnCardComponent, TnCellDefDirective, TnCheckboxComponent, TnCheckboxHarness, TnCheckboxLabelDirective, TnChipComponent, TnConfirmDialogComponent, TnDateInputComponent, TnDateInputHarness, TnDateRangeInputComponent, TnDateRangeInputHarness, TnDetailRowDefDirective, TnDialog, TnDialogHarness, TnDialogShellComponent, TnDialogTesting, TnDividerComponent, TnDividerDirective, TnDrawerComponent, TnDrawerContainerComponent, TnDrawerContainerHarness, TnDrawerContentComponent, TnDrawerHarness, TnEmptyComponent, TnEmptyHarness, TnExpansionPanelComponent, TnExpansionPanelHarness, TnFilePickerComponent, TnFilePickerPopupComponent, TnFormFieldComponent, TnFormFieldHarness, TnHeaderCellDefDirective, TnIconButtonComponent, TnIconButtonHarness, TnIconComponent, TnIconHarness, TnIconRegistryService, TnIconTesting, TnInputComponent, TnInputDirective, TnInputHarness, TnKeyboardShortcutComponent, TnKeyboardShortcutService, TnListAvatarDirective, TnListComponent, TnListIconDirective, TnListItemComponent, TnListItemLineDirective, TnListItemPrimaryDirective, TnListItemSecondaryDirective, TnListItemTitleDirective, TnListItemTrailingDirective, TnListOptionComponent, TnListSubheaderComponent, TnMenuActivateHoverDirective, TnMenuComponent, TnMenuHarness, TnMenuTesting, TnMenuTriggerDirective, TnMonthViewComponent, TnMultiYearViewComponent, TnNestedTreeNodeComponent, TnParticleProgressBarComponent, TnProgressBarComponent, TnRadioComponent, TnRadioHarness, TnSelectComponent, TnSelectHarness, TnSelectionListComponent, TnSidePanelActionDirective, TnSidePanelComponent, TnSidePanelHarness, TnSidePanelHeaderActionDirective, TnSlideToggleComponent, TnSlideToggleHarness, TnSliderComponent, TnSliderThumbDirective, TnSliderWithLabelDirective, TnSpinnerComponent, TnSpriteLoaderService, TnStepComponent, TnStepperComponent, TnTabComponent, TnTabHarness, TnTabPanelComponent, TnTabPanelHarness, TnTableColumnDirective, TnTableComponent, TnTableHarness, TnTabsComponent, TnTabsHarness, TnTheme, TnThemeService, TnTimeInputComponent, TnToastComponent, TnToastMock, TnToastPosition, TnToastRef, TnToastService, TnToastTesting, TnToastType, TnTooltipComponent, TnTooltipDirective, TnTreeComponent, TnTreeFlatDataSource, TnTreeFlattener, TnTreeNodeComponent, TnTreeNodeOutletDirective, TruncatePathPipe, WindowsModifierKeys, WindowsShortcuts, createLucideLibrary, createShortcut, defaultSpriteBasePath, defaultSpriteConfigPath, libIconMarker, registerLucideIcons, setupLucideIntegration, tnIconMarker };
|
|
6103
|
-
export type { AutocompleteHarnessFilters, BannerHarnessFilters, ButtonHarnessFilters, ButtonToggleHarnessFilters, CalendarCell, CheckboxHarnessFilters, ChipColor, CreateFolderEvent, DateInputHarnessFilters, DateRange, DateRangeInputHarnessFilters, DialogHarnessFilters, EmptyHarnessFilters, ExpansionPanelHarnessFilters, FilePickerCallbacks, FilePickerError, FilePickerMode, FileSystemItem, FormFieldHarnessFilters, IconButtonHarnessFilters, IconHarnessFilters, IconLibrary, IconLibraryType, IconResult, IconSize, IconSource, IconTestingMockOverrides, InputHarnessFilters, KeyCombination, LabelType, LucideIconOptions, MenuHarnessFilters, MockIconRegistry, MockSpriteLoader, PathSegment, PlatformType, ProgressBarMode, RadioHarnessFilters, ResolvedIcon, SelectHarnessFilters, ShortcutHandler, SidePanelHarnessFilters, SlideToggleColor, SlideToggleHarnessFilters, SpinnerMode, SpriteConfig, SubscriptSizing, TabChangeEvent, TabHarnessFilters, TabPanelHarnessFilters, TabsHarnessFilters, TnBannerType, TnButtonToggleType, TnCardAction, TnCardControl, TnCardFooterLink, TnCardHeaderStatus, TnConfirmDialogData, TnDialogDefaults, TnDialogOpenTarget, TnDrawerMode, TnDrawerPosition, TnEmptySize, TnFlatTreeNode,
|
|
6072
|
+
export type { AutocompleteHarnessFilters, BannerHarnessFilters, ButtonHarnessFilters, ButtonToggleHarnessFilters, CalendarCell, CheckboxHarnessFilters, ChipColor, CreateFolderEvent, DateInputHarnessFilters, DateRange, DateRangeInputHarnessFilters, DialogHarnessFilters, EmptyHarnessFilters, ExpansionPanelHarnessFilters, FilePickerCallbacks, FilePickerError, FilePickerMode, FileSystemItem, FormFieldHarnessFilters, IconButtonHarnessFilters, IconHarnessFilters, IconLibrary, IconLibraryType, IconResult, IconSize, IconSource, IconTestingMockOverrides, InputHarnessFilters, KeyCombination, LabelType, LucideIconOptions, MenuHarnessFilters, MockIconRegistry, MockSpriteLoader, PathSegment, PlatformType, ProgressBarMode, RadioHarnessFilters, ResolvedIcon, SelectHarnessFilters, ShortcutHandler, SidePanelHarnessFilters, SlideToggleColor, SlideToggleHarnessFilters, SpinnerMode, SpriteConfig, SubscriptSizing, TabChangeEvent, TabHarnessFilters, TabPanelHarnessFilters, TabsHarnessFilters, TnBannerType, TnButtonToggleType, TnCardAction, TnCardControl, TnCardFooterLink, TnCardHeaderStatus, TnConfirmDialogData, TnDialogDefaults, TnDialogOpenTarget, TnDrawerMode, TnDrawerPosition, TnEmptySize, TnFlatTreeNode, TnMenuItem, TnSelectOption, TnSelectOptionGroup, TnSelectionChange, TnSortEvent, TnTableDataSource, TnTableHarnessFilters, TnThemeDefinition, TnToastCall, TnToastConfig, TooltipPosition, YearCell };
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { Component, input } from '@angular/core';
|
|
2
|
-
import type { TnIconForwardingComponent } from '../icon/icon-forwarding';
|
|
3
|
-
import type { IconLibraryType } from '../icon/icon.component';
|
|
4
|
-
|
|
5
|
-
@Component({
|
|
6
|
-
selector: 'tn-input',
|
|
7
|
-
standalone: true,
|
|
8
|
-
templateUrl: './multi-icon.component.html',
|
|
9
|
-
})
|
|
10
|
-
export class TnInputComponent implements TnIconForwardingComponent, AfterViewInit {
|
|
11
|
-
prefixIcon = input<string | undefined>(undefined);
|
|
12
|
-
prefixIconLibrary = input<IconLibraryType | undefined>(undefined);
|
|
13
|
-
suffixIcon = input<string | undefined>(undefined);
|
|
14
|
-
suffixIconLibrary = input<IconLibraryType | undefined>(undefined);
|
|
15
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { Component, input } from '@angular/core';
|
|
2
|
-
import type { TnIconForwardingComponent } from '../icon/icon-forwarding';
|
|
3
|
-
|
|
4
|
-
@Component({
|
|
5
|
-
selector: 'tn-chip',
|
|
6
|
-
standalone: true,
|
|
7
|
-
templateUrl: './no-library.component.html',
|
|
8
|
-
})
|
|
9
|
-
export class TnChipComponent implements TnIconForwardingComponent {
|
|
10
|
-
icon = input<string | undefined>(undefined);
|
|
11
|
-
}
|
package/scripts/icon-sprite/__fixtures__/forwarding-components/not-forwarding.component.html
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<button class="tn-button">{{ label() }}</button>
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { Component, input } from '@angular/core';
|
|
2
|
-
import type { TnIconForwardingComponent } from '../icon/icon-forwarding';
|
|
3
|
-
import type { IconLibraryType } from '../icon/icon.component';
|
|
4
|
-
|
|
5
|
-
@Component({
|
|
6
|
-
selector: 'tn-empty',
|
|
7
|
-
standalone: true,
|
|
8
|
-
templateUrl: './single-icon.component.html',
|
|
9
|
-
})
|
|
10
|
-
export class TnEmptyComponent implements TnIconForwardingComponent {
|
|
11
|
-
icon = input<string>();
|
|
12
|
-
iconLibrary = input<IconLibraryType>('mdi');
|
|
13
|
-
}
|