@gilav21/shadcn-angular 0.0.15 → 0.0.17
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/dist/commands/add.js +100 -10
- package/dist/commands/init.js +75 -15
- package/dist/registry/index.d.ts +6 -0
- package/dist/registry/index.js +94 -1
- package/dist/templates/utils.js +31 -1
- package/dist/utils/shortcut-registry.d.ts +7 -0
- package/dist/utils/shortcut-registry.js +58 -0
- package/package.json +1 -1
- package/src/commands/add.ts +405 -303
- package/src/commands/init.ts +145 -77
- package/src/registry/index.ts +100 -1
- package/src/templates/utils.ts +31 -1
- package/src/utils/shortcut-registry.ts +79 -0
package/dist/commands/add.js
CHANGED
|
@@ -7,10 +7,12 @@ import ora from 'ora';
|
|
|
7
7
|
import { getConfig } from '../utils/config.js';
|
|
8
8
|
import { registry } from '../registry/index.js';
|
|
9
9
|
import { installPackages } from '../utils/package-manager.js';
|
|
10
|
+
import { writeShortcutRegistryIndex } from '../utils/shortcut-registry.js';
|
|
10
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
12
|
const __dirname = path.dirname(__filename);
|
|
12
13
|
// Base URL for the component registry (GitHub raw content)
|
|
13
14
|
const REGISTRY_BASE_URL = 'https://raw.githubusercontent.com/gilav21/shadcn-angular/master/packages/components/ui';
|
|
15
|
+
const LIB_REGISTRY_BASE_URL = 'https://raw.githubusercontent.com/gilav21/shadcn-angular/master/packages/components/lib';
|
|
14
16
|
// Components source directory (relative to CLI dist folder) for local dev
|
|
15
17
|
function getLocalComponentsDir() {
|
|
16
18
|
// From dist/commands/add.js -> packages/components/ui
|
|
@@ -25,6 +27,26 @@ function getLocalComponentsDir() {
|
|
|
25
27
|
}
|
|
26
28
|
return null;
|
|
27
29
|
}
|
|
30
|
+
function getLocalLibDir() {
|
|
31
|
+
const fromDist = path.resolve(__dirname, '../../../components/lib');
|
|
32
|
+
if (fs.existsSync(fromDist)) {
|
|
33
|
+
return fromDist;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
function resolveProjectPath(cwd, inputPath) {
|
|
38
|
+
const resolved = path.resolve(cwd, inputPath);
|
|
39
|
+
const relative = path.relative(cwd, resolved);
|
|
40
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
41
|
+
throw new Error(`Path must stay inside the project directory: ${inputPath}`);
|
|
42
|
+
}
|
|
43
|
+
return resolved;
|
|
44
|
+
}
|
|
45
|
+
function aliasToProjectPath(aliasOrPath) {
|
|
46
|
+
return aliasOrPath.startsWith('@/')
|
|
47
|
+
? path.join('src', aliasOrPath.slice(2))
|
|
48
|
+
: aliasOrPath;
|
|
49
|
+
}
|
|
28
50
|
async function fetchComponentContent(file, options) {
|
|
29
51
|
const localDir = getLocalComponentsDir();
|
|
30
52
|
// 1. Prefer local if available and not forced remote
|
|
@@ -50,6 +72,36 @@ async function fetchComponentContent(file, options) {
|
|
|
50
72
|
throw error;
|
|
51
73
|
}
|
|
52
74
|
}
|
|
75
|
+
async function fetchLibContent(file, options) {
|
|
76
|
+
const localDir = getLocalLibDir();
|
|
77
|
+
if (localDir && !options.remote) {
|
|
78
|
+
const localPath = path.join(localDir, file);
|
|
79
|
+
if (await fs.pathExists(localPath)) {
|
|
80
|
+
return fs.readFile(localPath, 'utf-8');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const url = `${LIB_REGISTRY_BASE_URL}/${file}`;
|
|
84
|
+
const response = await fetch(url);
|
|
85
|
+
if (!response.ok) {
|
|
86
|
+
throw new Error(`Failed to fetch library file from ${url}: ${response.statusText}`);
|
|
87
|
+
}
|
|
88
|
+
return response.text();
|
|
89
|
+
}
|
|
90
|
+
function collectInstalledShortcutEntries(targetDir) {
|
|
91
|
+
const entries = [];
|
|
92
|
+
for (const definition of Object.values(registry)) {
|
|
93
|
+
if (!definition.shortcutDefinitions?.length) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
for (const shortcutDefinition of definition.shortcutDefinitions) {
|
|
97
|
+
const sourcePath = path.join(targetDir, shortcutDefinition.sourceFile);
|
|
98
|
+
if (fs.existsSync(sourcePath)) {
|
|
99
|
+
entries.push(shortcutDefinition);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return entries;
|
|
104
|
+
}
|
|
53
105
|
export async function add(components, options) {
|
|
54
106
|
const cwd = process.cwd();
|
|
55
107
|
// Load config
|
|
@@ -103,9 +155,8 @@ export async function add(components, options) {
|
|
|
103
155
|
}
|
|
104
156
|
};
|
|
105
157
|
componentsToAdd.forEach(c => resolveDeps(c));
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
: path.join(cwd, 'src/components/ui');
|
|
158
|
+
const uiBasePath = options.path ?? aliasToProjectPath(config.aliases.ui || 'src/components/ui');
|
|
159
|
+
const targetDir = resolveProjectPath(cwd, uiBasePath);
|
|
109
160
|
// Check for existing files and diff
|
|
110
161
|
const componentsToInstall = [];
|
|
111
162
|
const componentsToSkip = [];
|
|
@@ -122,10 +173,10 @@ export async function add(components, options) {
|
|
|
122
173
|
const localContent = await fs.readFile(targetPath, 'utf-8');
|
|
123
174
|
try {
|
|
124
175
|
let remoteContent = await fetchComponentContent(file, options);
|
|
125
|
-
// Transform imports for comparison
|
|
126
|
-
const
|
|
127
|
-
remoteContent = remoteContent.replace(/(\.\.\/)+lib
|
|
128
|
-
const normalize = (str) => str.replace(/\
|
|
176
|
+
// Transform all lib/ imports for comparison
|
|
177
|
+
const libAlias = config.aliases.utils.replace(/\/[^/]+$/, '');
|
|
178
|
+
remoteContent = remoteContent.replace(/(\.\.\/)+lib\//g, libAlias + '/');
|
|
179
|
+
const normalize = (str) => str.replace(/\r\n/g, '\n').trim();
|
|
129
180
|
if (normalize(localContent) !== normalize(remoteContent)) {
|
|
130
181
|
hasChanges = true;
|
|
131
182
|
}
|
|
@@ -204,9 +255,9 @@ export async function add(components, options) {
|
|
|
204
255
|
let content = contentCache.get(file);
|
|
205
256
|
if (!content) {
|
|
206
257
|
content = await fetchComponentContent(file, options);
|
|
207
|
-
// Transform imports if not already transformed (cached is transformed)
|
|
208
|
-
const
|
|
209
|
-
content = content.replace(/(\.\.\/)+lib
|
|
258
|
+
// Transform all lib/ imports if not already transformed (cached is transformed)
|
|
259
|
+
const libAlias = config.aliases.utils.replace(/\/[^/]+$/, '');
|
|
260
|
+
content = content.replace(/(\.\.\/)+lib\//g, libAlias + '/');
|
|
210
261
|
}
|
|
211
262
|
await fs.ensureDir(path.dirname(targetPath));
|
|
212
263
|
await fs.writeFile(targetPath, content);
|
|
@@ -232,6 +283,33 @@ export async function add(components, options) {
|
|
|
232
283
|
else {
|
|
233
284
|
spinner.info('No new components installed.');
|
|
234
285
|
}
|
|
286
|
+
// Install required lib utility files
|
|
287
|
+
if (finalComponents.length > 0) {
|
|
288
|
+
const requiredLibFiles = new Set();
|
|
289
|
+
for (const name of allComponents) {
|
|
290
|
+
const component = registry[name];
|
|
291
|
+
if (component.libFiles) {
|
|
292
|
+
component.libFiles.forEach(f => requiredLibFiles.add(f));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (requiredLibFiles.size > 0) {
|
|
296
|
+
const utilsPathResolved = resolveProjectPath(cwd, aliasToProjectPath(config.aliases.utils) + '.ts');
|
|
297
|
+
const libDir = path.dirname(utilsPathResolved);
|
|
298
|
+
await fs.ensureDir(libDir);
|
|
299
|
+
for (const libFile of requiredLibFiles) {
|
|
300
|
+
const libTargetPath = path.join(libDir, libFile);
|
|
301
|
+
if (!await fs.pathExists(libTargetPath) || options.overwrite) {
|
|
302
|
+
try {
|
|
303
|
+
const libContent = await fetchLibContent(libFile, options);
|
|
304
|
+
await fs.writeFile(libTargetPath, libContent);
|
|
305
|
+
}
|
|
306
|
+
catch (err) {
|
|
307
|
+
console.warn(chalk.yellow(`Could not install lib file ${libFile}: ${err.message}`));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
235
313
|
if (finalComponents.length > 0) {
|
|
236
314
|
const npmDependencies = new Set();
|
|
237
315
|
for (const name of finalComponents) {
|
|
@@ -252,6 +330,18 @@ export async function add(components, options) {
|
|
|
252
330
|
}
|
|
253
331
|
}
|
|
254
332
|
}
|
|
333
|
+
const shortcutEntries = collectInstalledShortcutEntries(targetDir);
|
|
334
|
+
if (shortcutEntries.length > 0) {
|
|
335
|
+
const utilsPathResolved = resolveProjectPath(cwd, aliasToProjectPath(config.aliases.utils) + '.ts');
|
|
336
|
+
const utilsDir = path.dirname(utilsPathResolved);
|
|
337
|
+
const shortcutServicePath = path.join(utilsDir, 'shortcut-binding.service.ts');
|
|
338
|
+
if (!await fs.pathExists(shortcutServicePath)) {
|
|
339
|
+
const shortcutServiceContent = await fetchLibContent('shortcut-binding.service.ts', options);
|
|
340
|
+
await fs.ensureDir(utilsDir);
|
|
341
|
+
await fs.writeFile(shortcutServicePath, shortcutServiceContent);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
await writeShortcutRegistryIndex(cwd, config, shortcutEntries);
|
|
255
345
|
if (componentsToSkip.length > 0) {
|
|
256
346
|
console.log('\n' + chalk.dim('Components skipped (up to date):'));
|
|
257
347
|
componentsToSkip.forEach(name => {
|
package/dist/commands/init.js
CHANGED
|
@@ -1,12 +1,53 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
3
4
|
import prompts from 'prompts';
|
|
4
5
|
import chalk from 'chalk';
|
|
5
6
|
import ora from 'ora';
|
|
6
|
-
import { execa } from 'execa';
|
|
7
7
|
import { getDefaultConfig } from '../utils/config.js';
|
|
8
8
|
import { getStylesTemplate } from '../templates/styles.js';
|
|
9
9
|
import { getUtilsTemplate } from '../templates/utils.js';
|
|
10
|
+
import { installPackages } from '../utils/package-manager.js';
|
|
11
|
+
import { writeShortcutRegistryIndex } from '../utils/shortcut-registry.js';
|
|
12
|
+
const LIB_REGISTRY_BASE_URL = 'https://raw.githubusercontent.com/gilav21/shadcn-angular/master/packages/components/lib';
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = path.dirname(__filename);
|
|
15
|
+
function getLocalLibDir() {
|
|
16
|
+
const fromDist = path.resolve(__dirname, '../../../components/lib');
|
|
17
|
+
if (fs.existsSync(fromDist)) {
|
|
18
|
+
return fromDist;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
async function fetchLibFileContent(file) {
|
|
23
|
+
const localLibDir = getLocalLibDir();
|
|
24
|
+
if (localLibDir) {
|
|
25
|
+
const localPath = path.join(localLibDir, file);
|
|
26
|
+
if (await fs.pathExists(localPath)) {
|
|
27
|
+
return fs.readFile(localPath, 'utf-8');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const url = `${LIB_REGISTRY_BASE_URL}/${file}`;
|
|
31
|
+
const response = await fetch(url);
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
throw new Error(`Failed to fetch library file from ${url}: ${response.statusText}`);
|
|
34
|
+
}
|
|
35
|
+
return response.text();
|
|
36
|
+
}
|
|
37
|
+
function resolveProjectPath(cwd, inputPath) {
|
|
38
|
+
const resolved = path.resolve(cwd, inputPath);
|
|
39
|
+
const relative = path.relative(cwd, resolved);
|
|
40
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
41
|
+
throw new Error(`Path must stay inside the project directory: ${inputPath}`);
|
|
42
|
+
}
|
|
43
|
+
return resolved;
|
|
44
|
+
}
|
|
45
|
+
function resolveAliasOrPath(cwd, aliasOrPath) {
|
|
46
|
+
const normalized = aliasOrPath.startsWith('@/')
|
|
47
|
+
? path.join('src', aliasOrPath.slice(2))
|
|
48
|
+
: aliasOrPath;
|
|
49
|
+
return resolveProjectPath(cwd, normalized);
|
|
50
|
+
}
|
|
10
51
|
export async function init(options) {
|
|
11
52
|
console.log(chalk.bold('\n🎨 Welcome to shadcn-angular!\n'));
|
|
12
53
|
const cwd = process.cwd();
|
|
@@ -20,20 +61,24 @@ export async function init(options) {
|
|
|
20
61
|
// Check if already initialized
|
|
21
62
|
const componentsJsonPath = path.join(cwd, 'components.json');
|
|
22
63
|
if (await fs.pathExists(componentsJsonPath)) {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
64
|
+
const overwrite = options.yes
|
|
65
|
+
? true
|
|
66
|
+
: (await prompts({
|
|
67
|
+
type: 'confirm',
|
|
68
|
+
name: 'overwrite',
|
|
69
|
+
message: 'components.json already exists. Overwrite?',
|
|
70
|
+
initial: false,
|
|
71
|
+
})).overwrite;
|
|
29
72
|
if (!overwrite) {
|
|
30
73
|
console.log(chalk.dim('Initialization cancelled.'));
|
|
31
74
|
return;
|
|
32
75
|
}
|
|
33
76
|
}
|
|
34
77
|
let config;
|
|
78
|
+
let createShortcutRegistry = true;
|
|
35
79
|
if (options.defaults || options.yes) {
|
|
36
80
|
config = getDefaultConfig();
|
|
81
|
+
createShortcutRegistry = true;
|
|
37
82
|
}
|
|
38
83
|
else {
|
|
39
84
|
const THEME_COLORS = {
|
|
@@ -115,6 +160,12 @@ export async function init(options) {
|
|
|
115
160
|
message: 'Where is your global styles file?',
|
|
116
161
|
initial: 'src/styles.scss',
|
|
117
162
|
},
|
|
163
|
+
{
|
|
164
|
+
type: 'confirm',
|
|
165
|
+
name: 'createShortcutRegistry',
|
|
166
|
+
message: 'Would you like to create a shortcut registry scaffold?',
|
|
167
|
+
initial: true,
|
|
168
|
+
},
|
|
118
169
|
]);
|
|
119
170
|
config = {
|
|
120
171
|
$schema: 'https://shadcn-angular.dev/schema.json',
|
|
@@ -131,6 +182,7 @@ export async function init(options) {
|
|
|
131
182
|
ui: responses.componentsPath.replace('src/', '@/'),
|
|
132
183
|
},
|
|
133
184
|
};
|
|
185
|
+
createShortcutRegistry = responses.createShortcutRegistry ?? true;
|
|
134
186
|
}
|
|
135
187
|
const spinner = ora('Initializing project...').start();
|
|
136
188
|
try {
|
|
@@ -143,19 +195,28 @@ export async function init(options) {
|
|
|
143
195
|
// So we should rely on config to reconstruct the path, or better yet, if we are in 'defaults' mode, check what config is.
|
|
144
196
|
// If config came from defaults, aliases are set.
|
|
145
197
|
// We can reverse-map alias to path: @/ -> src/
|
|
146
|
-
const utilsPathResolved = config.aliases.utils
|
|
147
|
-
const utilsDir = path.dirname(
|
|
198
|
+
const utilsPathResolved = resolveAliasOrPath(cwd, config.aliases.utils + '.ts');
|
|
199
|
+
const utilsDir = path.dirname(utilsPathResolved); // utils usually ends in path/to/utils
|
|
148
200
|
await fs.ensureDir(utilsDir);
|
|
149
|
-
await fs.writeFile(
|
|
201
|
+
await fs.writeFile(utilsPathResolved, getUtilsTemplate());
|
|
150
202
|
spinner.text = 'Created utils.ts';
|
|
203
|
+
const shortcutServicePath = path.join(utilsDir, 'shortcut-binding.service.ts');
|
|
204
|
+
const shortcutServiceContent = await fetchLibFileContent('shortcut-binding.service.ts');
|
|
205
|
+
await fs.writeFile(shortcutServicePath, shortcutServiceContent);
|
|
206
|
+
spinner.text = 'Created shortcut-binding.service.ts';
|
|
207
|
+
if (createShortcutRegistry) {
|
|
208
|
+
await writeShortcutRegistryIndex(cwd, config, []);
|
|
209
|
+
spinner.text = 'Created shortcut-registry.index.ts';
|
|
210
|
+
}
|
|
151
211
|
// Create tailwind.css file in the same directory as the global styles
|
|
152
|
-
const
|
|
212
|
+
const userStylesPath = resolveProjectPath(cwd, config.tailwind.css);
|
|
213
|
+
const stylesDir = path.dirname(userStylesPath);
|
|
153
214
|
const tailwindCssPath = path.join(stylesDir, 'tailwind.css');
|
|
154
215
|
// Write the tailwind.css file with all Tailwind directives
|
|
216
|
+
await fs.ensureDir(stylesDir);
|
|
155
217
|
await fs.writeFile(tailwindCssPath, getStylesTemplate(config.tailwind.baseColor, config.tailwind.theme));
|
|
156
218
|
spinner.text = 'Created tailwind.css';
|
|
157
219
|
// Add import to the user's global styles file if not already present
|
|
158
|
-
const userStylesPath = path.join(cwd, config.tailwind.css);
|
|
159
220
|
let userStyles = await fs.pathExists(userStylesPath)
|
|
160
221
|
? await fs.readFile(userStylesPath, 'utf-8')
|
|
161
222
|
: '';
|
|
@@ -167,8 +228,7 @@ export async function init(options) {
|
|
|
167
228
|
spinner.text = 'Added tailwind.css import to styles';
|
|
168
229
|
}
|
|
169
230
|
// Create components/ui directory
|
|
170
|
-
const
|
|
171
|
-
const uiDir = path.join(cwd, uiPathResolved);
|
|
231
|
+
const uiDir = resolveAliasOrPath(cwd, config.aliases.ui);
|
|
172
232
|
await fs.ensureDir(uiDir);
|
|
173
233
|
spinner.text = 'Created components directory';
|
|
174
234
|
// Install dependencies
|
|
@@ -181,7 +241,7 @@ export async function init(options) {
|
|
|
181
241
|
'postcss',
|
|
182
242
|
'@tailwindcss/postcss'
|
|
183
243
|
];
|
|
184
|
-
await
|
|
244
|
+
await installPackages(dependencies, { cwd });
|
|
185
245
|
// Setup PostCSS - create .postcssrc.json which is the preferred format for Angular
|
|
186
246
|
spinner.text = 'Configuring PostCSS...';
|
|
187
247
|
const postcssrcPath = path.join(cwd, '.postcssrc.json');
|
package/dist/registry/index.d.ts
CHANGED
|
@@ -3,6 +3,12 @@ export interface ComponentDefinition {
|
|
|
3
3
|
files: string[];
|
|
4
4
|
dependencies?: string[];
|
|
5
5
|
npmDependencies?: string[];
|
|
6
|
+
libFiles?: string[];
|
|
7
|
+
shortcutDefinitions?: {
|
|
8
|
+
exportName: string;
|
|
9
|
+
componentName: string;
|
|
10
|
+
sourceFile: string;
|
|
11
|
+
}[];
|
|
6
12
|
}
|
|
7
13
|
export type ComponentName = keyof typeof registry;
|
|
8
14
|
export declare const registry: Record<string, ComponentDefinition>;
|
package/dist/registry/index.js
CHANGED
|
@@ -39,6 +39,7 @@ export const registry = {
|
|
|
39
39
|
button: {
|
|
40
40
|
name: 'button',
|
|
41
41
|
files: ['button.component.ts'],
|
|
42
|
+
dependencies: ['ripple'],
|
|
42
43
|
},
|
|
43
44
|
'button-group': {
|
|
44
45
|
name: 'button-group',
|
|
@@ -79,6 +80,13 @@ export const registry = {
|
|
|
79
80
|
name: 'command',
|
|
80
81
|
files: ['command.component.ts'],
|
|
81
82
|
dependencies: ['dialog'],
|
|
83
|
+
shortcutDefinitions: [
|
|
84
|
+
{
|
|
85
|
+
exportName: 'COMMAND_DIALOG_SHORTCUT_DEFINITIONS',
|
|
86
|
+
componentName: 'command-dialog',
|
|
87
|
+
sourceFile: 'command.component.ts',
|
|
88
|
+
},
|
|
89
|
+
],
|
|
82
90
|
},
|
|
83
91
|
'context-menu': {
|
|
84
92
|
name: 'context-menu',
|
|
@@ -132,6 +140,7 @@ export const registry = {
|
|
|
132
140
|
'component-outlet',
|
|
133
141
|
'icon',
|
|
134
142
|
],
|
|
143
|
+
libFiles: ['xlsx.ts'],
|
|
135
144
|
},
|
|
136
145
|
dialog: {
|
|
137
146
|
name: 'dialog',
|
|
@@ -342,7 +351,7 @@ export const registry = {
|
|
|
342
351
|
'emoji-picker': {
|
|
343
352
|
name: 'emoji-picker',
|
|
344
353
|
files: ['emoji-picker.component.ts', 'emoji-data.ts'],
|
|
345
|
-
dependencies: ['
|
|
354
|
+
dependencies: ['input', 'scroll-area', 'tooltip'],
|
|
346
355
|
},
|
|
347
356
|
'rich-text-editor': {
|
|
348
357
|
name: 'rich-text-editor',
|
|
@@ -351,18 +360,31 @@ export const registry = {
|
|
|
351
360
|
'rich-text-toolbar.component.ts',
|
|
352
361
|
'rich-text-sanitizer.service.ts',
|
|
353
362
|
'rich-text-markdown.service.ts',
|
|
363
|
+
'rich-text-paste-normalizer.service.ts',
|
|
364
|
+
'rich-text-command-registry.service.ts',
|
|
354
365
|
'rich-text-mention.component.ts',
|
|
355
366
|
'rich-text-image-resizer.component.ts',
|
|
367
|
+
'rich-text-locales.ts',
|
|
356
368
|
],
|
|
357
369
|
dependencies: [
|
|
358
370
|
'button',
|
|
359
371
|
'separator',
|
|
360
372
|
'popover',
|
|
361
373
|
'emoji-picker',
|
|
374
|
+
'autocomplete',
|
|
362
375
|
'select',
|
|
363
376
|
'input',
|
|
377
|
+
'dialog',
|
|
364
378
|
'scroll-area',
|
|
365
379
|
],
|
|
380
|
+
libFiles: ['pdf-parser.ts', 'image-validator.ts', 'svg-sanitizer.ts'],
|
|
381
|
+
shortcutDefinitions: [
|
|
382
|
+
{
|
|
383
|
+
exportName: 'RICH_TEXT_SHORTCUT_DEFINITIONS',
|
|
384
|
+
componentName: 'rich-text-editor',
|
|
385
|
+
sourceFile: 'rich-text-editor.component.ts',
|
|
386
|
+
},
|
|
387
|
+
],
|
|
366
388
|
},
|
|
367
389
|
// Chart Components
|
|
368
390
|
'pie-chart': {
|
|
@@ -464,4 +486,75 @@ export const registry = {
|
|
|
464
486
|
files: ['split-button.component.ts'],
|
|
465
487
|
dependencies: ['button', 'dropdown-menu'],
|
|
466
488
|
},
|
|
489
|
+
// Animations
|
|
490
|
+
'gradient-text': {
|
|
491
|
+
name: 'gradient-text',
|
|
492
|
+
files: ['gradient-text.component.ts'],
|
|
493
|
+
},
|
|
494
|
+
'flip-text': {
|
|
495
|
+
name: 'flip-text',
|
|
496
|
+
files: ['flip-text.component.ts'],
|
|
497
|
+
},
|
|
498
|
+
meteors: {
|
|
499
|
+
name: 'meteors',
|
|
500
|
+
files: ['meteors.component.ts'],
|
|
501
|
+
},
|
|
502
|
+
'shine-border': {
|
|
503
|
+
name: 'shine-border',
|
|
504
|
+
files: ['shine-border.component.ts'],
|
|
505
|
+
},
|
|
506
|
+
'scroll-progress': {
|
|
507
|
+
name: 'scroll-progress',
|
|
508
|
+
files: ['scroll-progress.component.ts'],
|
|
509
|
+
},
|
|
510
|
+
'blur-fade': {
|
|
511
|
+
name: 'blur-fade',
|
|
512
|
+
files: ['blur-fade.component.ts'],
|
|
513
|
+
},
|
|
514
|
+
ripple: {
|
|
515
|
+
name: 'ripple',
|
|
516
|
+
files: ['ripple.directive.ts'],
|
|
517
|
+
},
|
|
518
|
+
marquee: {
|
|
519
|
+
name: 'marquee',
|
|
520
|
+
files: ['marquee.component.ts'],
|
|
521
|
+
},
|
|
522
|
+
'word-rotate': {
|
|
523
|
+
name: 'word-rotate',
|
|
524
|
+
files: ['word-rotate.component.ts'],
|
|
525
|
+
},
|
|
526
|
+
'morphing-text': {
|
|
527
|
+
name: 'morphing-text',
|
|
528
|
+
files: ['morphing-text.component.ts'],
|
|
529
|
+
},
|
|
530
|
+
'typing-animation': {
|
|
531
|
+
name: 'typing-animation',
|
|
532
|
+
files: ['typing-animation.component.ts'],
|
|
533
|
+
},
|
|
534
|
+
'wobble-card': {
|
|
535
|
+
name: 'wobble-card',
|
|
536
|
+
files: ['wobble-card.component.ts'],
|
|
537
|
+
},
|
|
538
|
+
magnetic: {
|
|
539
|
+
name: 'magnetic',
|
|
540
|
+
files: ['magnetic.directive.ts'],
|
|
541
|
+
},
|
|
542
|
+
orbit: {
|
|
543
|
+
name: 'orbit',
|
|
544
|
+
files: ['orbit.component.ts'],
|
|
545
|
+
},
|
|
546
|
+
'stagger-children': {
|
|
547
|
+
name: 'stagger-children',
|
|
548
|
+
files: ['stagger-children.component.ts'],
|
|
549
|
+
},
|
|
550
|
+
particles: {
|
|
551
|
+
name: 'particles',
|
|
552
|
+
files: ['particles.component.ts'],
|
|
553
|
+
},
|
|
554
|
+
// Kanban
|
|
555
|
+
kanban: {
|
|
556
|
+
name: 'kanban',
|
|
557
|
+
files: ['kanban.component.ts'],
|
|
558
|
+
dependencies: ['badge', 'avatar', 'scroll-area', 'separator'],
|
|
559
|
+
},
|
|
467
560
|
};
|
package/dist/templates/utils.js
CHANGED
|
@@ -6,7 +6,7 @@ import { twMerge } from 'tailwind-merge';
|
|
|
6
6
|
* Utility function for merging Tailwind CSS classes with proper precedence
|
|
7
7
|
*/
|
|
8
8
|
export function cn(...inputs: ClassValue[]): string {
|
|
9
|
-
|
|
9
|
+
return twMerge(clsx(inputs));
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -17,5 +17,35 @@ export function isRtl(el: HTMLElement): boolean {
|
|
|
17
17
|
return getComputedStyle(el).direction === 'rtl';
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Returns the bounding rect of the nearest ancestor that clips overflow
|
|
22
|
+
* (overflow: hidden | auto | scroll | clip on either axis).
|
|
23
|
+
* Falls back to the full viewport rect when no such ancestor exists.
|
|
24
|
+
*
|
|
25
|
+
* Use this instead of \`window.innerWidth/innerHeight\` when calculating
|
|
26
|
+
* popup collision boundaries so that containers like sidebars or
|
|
27
|
+
* fixed-height scroll panes are respected.
|
|
28
|
+
*/
|
|
29
|
+
export function getClippingRect(element: HTMLElement): DOMRect {
|
|
30
|
+
let parent = element.parentElement;
|
|
31
|
+
while (parent && parent !== document.documentElement) {
|
|
32
|
+
const style = window.getComputedStyle(parent);
|
|
33
|
+
if (
|
|
34
|
+
/^(hidden|auto|scroll|clip)$/.test(style.overflowX) ||
|
|
35
|
+
/^(hidden|auto|scroll|clip)$/.test(style.overflowY)
|
|
36
|
+
) {
|
|
37
|
+
return parent.getBoundingClientRect();
|
|
38
|
+
}
|
|
39
|
+
parent = parent.parentElement;
|
|
40
|
+
}
|
|
41
|
+
return new DOMRect(0, 0, window.innerWidth, window.innerHeight);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Check if the user prefers reduced motion via the OS-level accessibility setting.
|
|
46
|
+
*/
|
|
47
|
+
export function prefersReducedMotion(): boolean {
|
|
48
|
+
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
49
|
+
}
|
|
20
50
|
`;
|
|
21
51
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Config } from './config.js';
|
|
2
|
+
export interface ShortcutRegistryEntry {
|
|
3
|
+
exportName: string;
|
|
4
|
+
componentName: string;
|
|
5
|
+
sourceFile: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function writeShortcutRegistryIndex(cwd: string, config: Config, entries: ShortcutRegistryEntry[]): Promise<string>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
function aliasToProjectPath(aliasOrPath) {
|
|
4
|
+
return aliasOrPath.startsWith('@/')
|
|
5
|
+
? path.join('src', aliasOrPath.slice(2))
|
|
6
|
+
: aliasOrPath;
|
|
7
|
+
}
|
|
8
|
+
function resolveProjectPath(cwd, inputPath) {
|
|
9
|
+
const resolved = path.resolve(cwd, inputPath);
|
|
10
|
+
const relative = path.relative(cwd, resolved);
|
|
11
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
12
|
+
throw new Error(`Path must stay inside the project directory: ${inputPath}`);
|
|
13
|
+
}
|
|
14
|
+
return resolved;
|
|
15
|
+
}
|
|
16
|
+
function getShortcutRegistryIndexPath(cwd, config) {
|
|
17
|
+
const utilsFilePath = resolveProjectPath(cwd, aliasToProjectPath(config.aliases.utils) + '.ts');
|
|
18
|
+
const utilsDir = path.dirname(utilsFilePath);
|
|
19
|
+
return path.join(utilsDir, 'shortcut-registry.index.ts');
|
|
20
|
+
}
|
|
21
|
+
export async function writeShortcutRegistryIndex(cwd, config, entries) {
|
|
22
|
+
const registryPath = getShortcutRegistryIndexPath(cwd, config);
|
|
23
|
+
await fs.ensureDir(path.dirname(registryPath));
|
|
24
|
+
const uniqueEntries = Array.from(new Map(entries.map(entry => [entry.exportName, entry])).values())
|
|
25
|
+
.sort((a, b) => a.exportName.localeCompare(b.exportName));
|
|
26
|
+
const uiAlias = config.aliases.ui;
|
|
27
|
+
const utilsAliasDir = config.aliases.utils.includes('/')
|
|
28
|
+
? config.aliases.utils.slice(0, config.aliases.utils.lastIndexOf('/'))
|
|
29
|
+
: config.aliases.utils;
|
|
30
|
+
const shortcutServiceImport = `${utilsAliasDir}/shortcut-binding.service`;
|
|
31
|
+
const imports = uniqueEntries
|
|
32
|
+
.map(entry => {
|
|
33
|
+
const importPath = `${uiAlias}/${entry.sourceFile.replace(/\.ts$/, '')}`;
|
|
34
|
+
return `import { ${entry.exportName} } from '${importPath}';`;
|
|
35
|
+
})
|
|
36
|
+
.join('\n');
|
|
37
|
+
const catalogItems = uniqueEntries
|
|
38
|
+
.map(entry => ` { componentName: '${entry.componentName}', definitions: ${entry.exportName} },`)
|
|
39
|
+
.join('\n');
|
|
40
|
+
const content = `// Auto-generated by shadcn-angular CLI. Do not edit manually.
|
|
41
|
+
import type { ShortcutBindingService, ShortcutDefinition } from '${shortcutServiceImport}';
|
|
42
|
+
${imports ? `${imports}\n` : ''}
|
|
43
|
+
export const GENERATED_SHORTCUT_CATALOG: ReadonlyArray<{
|
|
44
|
+
componentName: string;
|
|
45
|
+
definitions: ReadonlyArray<ShortcutDefinition>;
|
|
46
|
+
}> = [
|
|
47
|
+
${catalogItems}
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
export function registerGeneratedShortcutCatalog(shortcuts: ShortcutBindingService): void {
|
|
51
|
+
for (const entry of GENERATED_SHORTCUT_CATALOG) {
|
|
52
|
+
shortcuts.defineShortcuts(entry.componentName, [...entry.definitions]);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
`;
|
|
56
|
+
await fs.writeFile(registryPath, content);
|
|
57
|
+
return registryPath;
|
|
58
|
+
}
|