@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/src/commands/init.ts
CHANGED
|
@@ -1,19 +1,66 @@
|
|
|
1
|
-
import fs from 'fs-extra';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import { getDefaultConfig, type Config } from '../utils/config.js';
|
|
8
|
-
import { getStylesTemplate } from '../templates/styles.js';
|
|
9
|
-
import { getUtilsTemplate } from '../templates/utils.js';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import prompts from 'prompts';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import { getDefaultConfig, type Config } from '../utils/config.js';
|
|
8
|
+
import { getStylesTemplate } from '../templates/styles.js';
|
|
9
|
+
import { getUtilsTemplate } from '../templates/utils.js';
|
|
10
|
+
import { installPackages } from '../utils/package-manager.js';
|
|
11
|
+
import { writeShortcutRegistryIndex } from '../utils/shortcut-registry.js';
|
|
12
|
+
|
|
13
|
+
const LIB_REGISTRY_BASE_URL = 'https://raw.githubusercontent.com/gilav21/shadcn-angular/master/packages/components/lib';
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = path.dirname(__filename);
|
|
16
|
+
|
|
17
|
+
function getLocalLibDir(): string | null {
|
|
18
|
+
const fromDist = path.resolve(__dirname, '../../../components/lib');
|
|
19
|
+
if (fs.existsSync(fromDist)) {
|
|
20
|
+
return fromDist;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function fetchLibFileContent(file: string): Promise<string> {
|
|
26
|
+
const localLibDir = getLocalLibDir();
|
|
27
|
+
if (localLibDir) {
|
|
28
|
+
const localPath = path.join(localLibDir, file);
|
|
29
|
+
if (await fs.pathExists(localPath)) {
|
|
30
|
+
return fs.readFile(localPath, 'utf-8');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const url = `${LIB_REGISTRY_BASE_URL}/${file}`;
|
|
35
|
+
const response = await fetch(url);
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
throw new Error(`Failed to fetch library file from ${url}: ${response.statusText}`);
|
|
38
|
+
}
|
|
39
|
+
return response.text();
|
|
40
|
+
}
|
|
15
41
|
|
|
16
|
-
|
|
42
|
+
interface InitOptions {
|
|
43
|
+
yes?: boolean;
|
|
44
|
+
defaults?: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function resolveProjectPath(cwd: string, inputPath: string): string {
|
|
48
|
+
const resolved = path.resolve(cwd, inputPath);
|
|
49
|
+
const relative = path.relative(cwd, resolved);
|
|
50
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
51
|
+
throw new Error(`Path must stay inside the project directory: ${inputPath}`);
|
|
52
|
+
}
|
|
53
|
+
return resolved;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function resolveAliasOrPath(cwd: string, aliasOrPath: string): string {
|
|
57
|
+
const normalized = aliasOrPath.startsWith('@/')
|
|
58
|
+
? path.join('src', aliasOrPath.slice(2))
|
|
59
|
+
: aliasOrPath;
|
|
60
|
+
return resolveProjectPath(cwd, normalized);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function init(options: InitOptions) {
|
|
17
64
|
console.log(chalk.bold('\nšØ Welcome to shadcn-angular!\n'));
|
|
18
65
|
|
|
19
66
|
const cwd = process.cwd();
|
|
@@ -26,26 +73,30 @@ export async function init(options: InitOptions) {
|
|
|
26
73
|
process.exit(1);
|
|
27
74
|
}
|
|
28
75
|
|
|
29
|
-
// Check if already initialized
|
|
30
|
-
const componentsJsonPath = path.join(cwd, 'components.json');
|
|
31
|
-
if (await fs.pathExists(componentsJsonPath)) {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
76
|
+
// Check if already initialized
|
|
77
|
+
const componentsJsonPath = path.join(cwd, 'components.json');
|
|
78
|
+
if (await fs.pathExists(componentsJsonPath)) {
|
|
79
|
+
const overwrite = options.yes
|
|
80
|
+
? true
|
|
81
|
+
: (await prompts({
|
|
82
|
+
type: 'confirm',
|
|
83
|
+
name: 'overwrite',
|
|
84
|
+
message: 'components.json already exists. Overwrite?',
|
|
85
|
+
initial: false,
|
|
86
|
+
})).overwrite;
|
|
87
|
+
if (!overwrite) {
|
|
88
|
+
console.log(chalk.dim('Initialization cancelled.'));
|
|
89
|
+
return;
|
|
41
90
|
}
|
|
42
91
|
}
|
|
43
92
|
|
|
44
|
-
let config: Config;
|
|
93
|
+
let config: Config;
|
|
94
|
+
let createShortcutRegistry = true;
|
|
45
95
|
|
|
46
|
-
if (options.defaults || options.yes) {
|
|
47
|
-
config = getDefaultConfig();
|
|
48
|
-
|
|
96
|
+
if (options.defaults || options.yes) {
|
|
97
|
+
config = getDefaultConfig();
|
|
98
|
+
createShortcutRegistry = true;
|
|
99
|
+
} else {
|
|
49
100
|
const THEME_COLORS: Record<string, string> = {
|
|
50
101
|
zinc: '#71717a',
|
|
51
102
|
slate: '#64748b',
|
|
@@ -123,16 +174,22 @@ export async function init(options: InitOptions) {
|
|
|
123
174
|
message: 'Where would you like to install utils?',
|
|
124
175
|
initial: 'src/components/lib',
|
|
125
176
|
},
|
|
126
|
-
{
|
|
127
|
-
type: 'text',
|
|
128
|
-
name: 'globalCss',
|
|
129
|
-
message: 'Where is your global styles file?',
|
|
130
|
-
initial: 'src/styles.scss',
|
|
131
|
-
},
|
|
132
|
-
|
|
177
|
+
{
|
|
178
|
+
type: 'text',
|
|
179
|
+
name: 'globalCss',
|
|
180
|
+
message: 'Where is your global styles file?',
|
|
181
|
+
initial: 'src/styles.scss',
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
type: 'confirm',
|
|
185
|
+
name: 'createShortcutRegistry',
|
|
186
|
+
message: 'Would you like to create a shortcut registry scaffold?',
|
|
187
|
+
initial: true,
|
|
188
|
+
},
|
|
189
|
+
]);
|
|
133
190
|
|
|
134
|
-
config = {
|
|
135
|
-
$schema: 'https://shadcn-angular.dev/schema.json',
|
|
191
|
+
config = {
|
|
192
|
+
$schema: 'https://shadcn-angular.dev/schema.json',
|
|
136
193
|
style: 'default',
|
|
137
194
|
tailwind: {
|
|
138
195
|
css: responses.globalCss,
|
|
@@ -144,9 +201,10 @@ export async function init(options: InitOptions) {
|
|
|
144
201
|
components: responses.componentsPath.replace('src/', '@/'), // Basic heuristic
|
|
145
202
|
utils: responses.utilsPath.replace('src/', '@/').replace('.ts', ''),
|
|
146
203
|
ui: responses.componentsPath.replace('src/', '@/'),
|
|
147
|
-
},
|
|
148
|
-
};
|
|
149
|
-
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
createShortcutRegistry = responses.createShortcutRegistry ?? true;
|
|
207
|
+
}
|
|
150
208
|
|
|
151
209
|
const spinner = ora('Initializing project...').start();
|
|
152
210
|
|
|
@@ -162,26 +220,37 @@ export async function init(options: InitOptions) {
|
|
|
162
220
|
// If config came from defaults, aliases are set.
|
|
163
221
|
// We can reverse-map alias to path: @/ -> src/
|
|
164
222
|
|
|
165
|
-
const utilsPathResolved = config.aliases.utils
|
|
166
|
-
const utilsDir = path.dirname(
|
|
167
|
-
|
|
168
|
-
await fs.ensureDir(utilsDir);
|
|
169
|
-
await fs.writeFile(
|
|
170
|
-
spinner.text = 'Created utils.ts';
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
223
|
+
const utilsPathResolved = resolveAliasOrPath(cwd, config.aliases.utils + '.ts');
|
|
224
|
+
const utilsDir = path.dirname(utilsPathResolved); // utils usually ends in path/to/utils
|
|
225
|
+
|
|
226
|
+
await fs.ensureDir(utilsDir);
|
|
227
|
+
await fs.writeFile(utilsPathResolved, getUtilsTemplate());
|
|
228
|
+
spinner.text = 'Created utils.ts';
|
|
229
|
+
|
|
230
|
+
const shortcutServicePath = path.join(utilsDir, 'shortcut-binding.service.ts');
|
|
231
|
+
const shortcutServiceContent = await fetchLibFileContent('shortcut-binding.service.ts');
|
|
232
|
+
await fs.writeFile(shortcutServicePath, shortcutServiceContent);
|
|
233
|
+
spinner.text = 'Created shortcut-binding.service.ts';
|
|
234
|
+
|
|
235
|
+
if (createShortcutRegistry) {
|
|
236
|
+
await writeShortcutRegistryIndex(cwd, config, []);
|
|
237
|
+
spinner.text = 'Created shortcut-registry.index.ts';
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Create tailwind.css file in the same directory as the global styles
|
|
241
|
+
const userStylesPath = resolveProjectPath(cwd, config.tailwind.css);
|
|
242
|
+
const stylesDir = path.dirname(userStylesPath);
|
|
243
|
+
const tailwindCssPath = path.join(stylesDir, 'tailwind.css');
|
|
244
|
+
|
|
245
|
+
// Write the tailwind.css file with all Tailwind directives
|
|
246
|
+
await fs.ensureDir(stylesDir);
|
|
247
|
+
await fs.writeFile(tailwindCssPath, getStylesTemplate(config.tailwind.baseColor, config.tailwind.theme));
|
|
248
|
+
spinner.text = 'Created tailwind.css';
|
|
249
|
+
|
|
250
|
+
// Add import to the user's global styles file if not already present
|
|
251
|
+
let userStyles = await fs.pathExists(userStylesPath)
|
|
252
|
+
? await fs.readFile(userStylesPath, 'utf-8')
|
|
253
|
+
: '';
|
|
185
254
|
|
|
186
255
|
const tailwindImport = '@import "./tailwind.css";';
|
|
187
256
|
if (!userStyles.includes('tailwind.css')) {
|
|
@@ -189,25 +258,24 @@ export async function init(options: InitOptions) {
|
|
|
189
258
|
userStyles = tailwindImport + '\n\n' + userStyles;
|
|
190
259
|
await fs.writeFile(userStylesPath, userStyles);
|
|
191
260
|
spinner.text = 'Added tailwind.css import to styles';
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Create components/ui directory
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
spinner.text = 'Created components directory';
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Create components/ui directory
|
|
264
|
+
const uiDir = resolveAliasOrPath(cwd, config.aliases.ui);
|
|
265
|
+
await fs.ensureDir(uiDir);
|
|
266
|
+
spinner.text = 'Created components directory';
|
|
199
267
|
|
|
200
268
|
// Install dependencies
|
|
201
269
|
spinner.text = 'Installing dependencies...';
|
|
202
|
-
const dependencies = [
|
|
203
|
-
'clsx',
|
|
204
|
-
'tailwind-merge',
|
|
270
|
+
const dependencies = [
|
|
271
|
+
'clsx',
|
|
272
|
+
'tailwind-merge',
|
|
205
273
|
'class-variance-authority',
|
|
206
274
|
'tailwindcss',
|
|
207
|
-
'postcss',
|
|
208
|
-
'@tailwindcss/postcss'
|
|
209
|
-
];
|
|
210
|
-
await
|
|
275
|
+
'postcss',
|
|
276
|
+
'@tailwindcss/postcss'
|
|
277
|
+
];
|
|
278
|
+
await installPackages(dependencies, { cwd });
|
|
211
279
|
|
|
212
280
|
// Setup PostCSS - create .postcssrc.json which is the preferred format for Angular
|
|
213
281
|
spinner.text = 'Configuring PostCSS...';
|
package/src/registry/index.ts
CHANGED
|
@@ -6,6 +6,12 @@ export interface ComponentDefinition {
|
|
|
6
6
|
files: string[]; // Relative paths to component files
|
|
7
7
|
dependencies?: string[]; // Other components this depends on
|
|
8
8
|
npmDependencies?: string[]; // NPM packages this depends on
|
|
9
|
+
libFiles?: string[]; // Lib utility files this component requires (e.g. 'xlsx.ts')
|
|
10
|
+
shortcutDefinitions?: {
|
|
11
|
+
exportName: string;
|
|
12
|
+
componentName: string;
|
|
13
|
+
sourceFile: string;
|
|
14
|
+
}[];
|
|
9
15
|
}
|
|
10
16
|
|
|
11
17
|
export type ComponentName = keyof typeof registry;
|
|
@@ -49,6 +55,7 @@ export const registry: Record<string, ComponentDefinition> = {
|
|
|
49
55
|
button: {
|
|
50
56
|
name: 'button',
|
|
51
57
|
files: ['button.component.ts'],
|
|
58
|
+
dependencies: ['ripple'],
|
|
52
59
|
},
|
|
53
60
|
'button-group': {
|
|
54
61
|
name: 'button-group',
|
|
@@ -89,6 +96,13 @@ export const registry: Record<string, ComponentDefinition> = {
|
|
|
89
96
|
name: 'command',
|
|
90
97
|
files: ['command.component.ts'],
|
|
91
98
|
dependencies: ['dialog'],
|
|
99
|
+
shortcutDefinitions: [
|
|
100
|
+
{
|
|
101
|
+
exportName: 'COMMAND_DIALOG_SHORTCUT_DEFINITIONS',
|
|
102
|
+
componentName: 'command-dialog',
|
|
103
|
+
sourceFile: 'command.component.ts',
|
|
104
|
+
},
|
|
105
|
+
],
|
|
92
106
|
},
|
|
93
107
|
'context-menu': {
|
|
94
108
|
name: 'context-menu',
|
|
@@ -142,6 +156,7 @@ export const registry: Record<string, ComponentDefinition> = {
|
|
|
142
156
|
'component-outlet',
|
|
143
157
|
'icon',
|
|
144
158
|
],
|
|
159
|
+
libFiles: ['xlsx.ts'],
|
|
145
160
|
},
|
|
146
161
|
dialog: {
|
|
147
162
|
name: 'dialog',
|
|
@@ -353,7 +368,7 @@ export const registry: Record<string, ComponentDefinition> = {
|
|
|
353
368
|
'emoji-picker': {
|
|
354
369
|
name: 'emoji-picker',
|
|
355
370
|
files: ['emoji-picker.component.ts', 'emoji-data.ts'],
|
|
356
|
-
dependencies: ['
|
|
371
|
+
dependencies: ['input', 'scroll-area', 'tooltip'],
|
|
357
372
|
},
|
|
358
373
|
'rich-text-editor': {
|
|
359
374
|
name: 'rich-text-editor',
|
|
@@ -362,18 +377,31 @@ export const registry: Record<string, ComponentDefinition> = {
|
|
|
362
377
|
'rich-text-toolbar.component.ts',
|
|
363
378
|
'rich-text-sanitizer.service.ts',
|
|
364
379
|
'rich-text-markdown.service.ts',
|
|
380
|
+
'rich-text-paste-normalizer.service.ts',
|
|
381
|
+
'rich-text-command-registry.service.ts',
|
|
365
382
|
'rich-text-mention.component.ts',
|
|
366
383
|
'rich-text-image-resizer.component.ts',
|
|
384
|
+
'rich-text-locales.ts',
|
|
367
385
|
],
|
|
368
386
|
dependencies: [
|
|
369
387
|
'button',
|
|
370
388
|
'separator',
|
|
371
389
|
'popover',
|
|
372
390
|
'emoji-picker',
|
|
391
|
+
'autocomplete',
|
|
373
392
|
'select',
|
|
374
393
|
'input',
|
|
394
|
+
'dialog',
|
|
375
395
|
'scroll-area',
|
|
376
396
|
],
|
|
397
|
+
libFiles: ['pdf-parser.ts', 'image-validator.ts', 'svg-sanitizer.ts'],
|
|
398
|
+
shortcutDefinitions: [
|
|
399
|
+
{
|
|
400
|
+
exportName: 'RICH_TEXT_SHORTCUT_DEFINITIONS',
|
|
401
|
+
componentName: 'rich-text-editor',
|
|
402
|
+
sourceFile: 'rich-text-editor.component.ts',
|
|
403
|
+
},
|
|
404
|
+
],
|
|
377
405
|
},
|
|
378
406
|
// Chart Components
|
|
379
407
|
'pie-chart': {
|
|
@@ -475,4 +503,75 @@ export const registry: Record<string, ComponentDefinition> = {
|
|
|
475
503
|
files: ['split-button.component.ts'],
|
|
476
504
|
dependencies: ['button', 'dropdown-menu'],
|
|
477
505
|
},
|
|
506
|
+
// Animations
|
|
507
|
+
'gradient-text': {
|
|
508
|
+
name: 'gradient-text',
|
|
509
|
+
files: ['gradient-text.component.ts'],
|
|
510
|
+
},
|
|
511
|
+
'flip-text': {
|
|
512
|
+
name: 'flip-text',
|
|
513
|
+
files: ['flip-text.component.ts'],
|
|
514
|
+
},
|
|
515
|
+
meteors: {
|
|
516
|
+
name: 'meteors',
|
|
517
|
+
files: ['meteors.component.ts'],
|
|
518
|
+
},
|
|
519
|
+
'shine-border': {
|
|
520
|
+
name: 'shine-border',
|
|
521
|
+
files: ['shine-border.component.ts'],
|
|
522
|
+
},
|
|
523
|
+
'scroll-progress': {
|
|
524
|
+
name: 'scroll-progress',
|
|
525
|
+
files: ['scroll-progress.component.ts'],
|
|
526
|
+
},
|
|
527
|
+
'blur-fade': {
|
|
528
|
+
name: 'blur-fade',
|
|
529
|
+
files: ['blur-fade.component.ts'],
|
|
530
|
+
},
|
|
531
|
+
ripple: {
|
|
532
|
+
name: 'ripple',
|
|
533
|
+
files: ['ripple.directive.ts'],
|
|
534
|
+
},
|
|
535
|
+
marquee: {
|
|
536
|
+
name: 'marquee',
|
|
537
|
+
files: ['marquee.component.ts'],
|
|
538
|
+
},
|
|
539
|
+
'word-rotate': {
|
|
540
|
+
name: 'word-rotate',
|
|
541
|
+
files: ['word-rotate.component.ts'],
|
|
542
|
+
},
|
|
543
|
+
'morphing-text': {
|
|
544
|
+
name: 'morphing-text',
|
|
545
|
+
files: ['morphing-text.component.ts'],
|
|
546
|
+
},
|
|
547
|
+
'typing-animation': {
|
|
548
|
+
name: 'typing-animation',
|
|
549
|
+
files: ['typing-animation.component.ts'],
|
|
550
|
+
},
|
|
551
|
+
'wobble-card': {
|
|
552
|
+
name: 'wobble-card',
|
|
553
|
+
files: ['wobble-card.component.ts'],
|
|
554
|
+
},
|
|
555
|
+
magnetic: {
|
|
556
|
+
name: 'magnetic',
|
|
557
|
+
files: ['magnetic.directive.ts'],
|
|
558
|
+
},
|
|
559
|
+
orbit: {
|
|
560
|
+
name: 'orbit',
|
|
561
|
+
files: ['orbit.component.ts'],
|
|
562
|
+
},
|
|
563
|
+
'stagger-children': {
|
|
564
|
+
name: 'stagger-children',
|
|
565
|
+
files: ['stagger-children.component.ts'],
|
|
566
|
+
},
|
|
567
|
+
particles: {
|
|
568
|
+
name: 'particles',
|
|
569
|
+
files: ['particles.component.ts'],
|
|
570
|
+
},
|
|
571
|
+
// Kanban
|
|
572
|
+
kanban: {
|
|
573
|
+
name: 'kanban',
|
|
574
|
+
files: ['kanban.component.ts'],
|
|
575
|
+
dependencies: ['badge', 'avatar', 'scroll-area', 'separator'],
|
|
576
|
+
},
|
|
478
577
|
};
|
package/src/templates/utils.ts
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,79 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import type { Config } from './config.js';
|
|
4
|
+
|
|
5
|
+
export interface ShortcutRegistryEntry {
|
|
6
|
+
exportName: string;
|
|
7
|
+
componentName: string;
|
|
8
|
+
sourceFile: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function aliasToProjectPath(aliasOrPath: string): string {
|
|
12
|
+
return aliasOrPath.startsWith('@/')
|
|
13
|
+
? path.join('src', aliasOrPath.slice(2))
|
|
14
|
+
: aliasOrPath;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function resolveProjectPath(cwd: string, inputPath: string): string {
|
|
18
|
+
const resolved = path.resolve(cwd, inputPath);
|
|
19
|
+
const relative = path.relative(cwd, resolved);
|
|
20
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
21
|
+
throw new Error(`Path must stay inside the project directory: ${inputPath}`);
|
|
22
|
+
}
|
|
23
|
+
return resolved;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getShortcutRegistryIndexPath(cwd: string, config: Config): string {
|
|
27
|
+
const utilsFilePath = resolveProjectPath(cwd, aliasToProjectPath(config.aliases.utils) + '.ts');
|
|
28
|
+
const utilsDir = path.dirname(utilsFilePath);
|
|
29
|
+
return path.join(utilsDir, 'shortcut-registry.index.ts');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function writeShortcutRegistryIndex(
|
|
33
|
+
cwd: string,
|
|
34
|
+
config: Config,
|
|
35
|
+
entries: ShortcutRegistryEntry[],
|
|
36
|
+
): Promise<string> {
|
|
37
|
+
const registryPath = getShortcutRegistryIndexPath(cwd, config);
|
|
38
|
+
await fs.ensureDir(path.dirname(registryPath));
|
|
39
|
+
|
|
40
|
+
const uniqueEntries = Array.from(new Map(entries.map(entry => [entry.exportName, entry])).values())
|
|
41
|
+
.sort((a, b) => a.exportName.localeCompare(b.exportName));
|
|
42
|
+
|
|
43
|
+
const uiAlias = config.aliases.ui;
|
|
44
|
+
const utilsAliasDir = config.aliases.utils.includes('/')
|
|
45
|
+
? config.aliases.utils.slice(0, config.aliases.utils.lastIndexOf('/'))
|
|
46
|
+
: config.aliases.utils;
|
|
47
|
+
const shortcutServiceImport = `${utilsAliasDir}/shortcut-binding.service`;
|
|
48
|
+
|
|
49
|
+
const imports = uniqueEntries
|
|
50
|
+
.map(entry => {
|
|
51
|
+
const importPath = `${uiAlias}/${entry.sourceFile.replace(/\.ts$/, '')}`;
|
|
52
|
+
return `import { ${entry.exportName} } from '${importPath}';`;
|
|
53
|
+
})
|
|
54
|
+
.join('\n');
|
|
55
|
+
|
|
56
|
+
const catalogItems = uniqueEntries
|
|
57
|
+
.map(entry => ` { componentName: '${entry.componentName}', definitions: ${entry.exportName} },`)
|
|
58
|
+
.join('\n');
|
|
59
|
+
|
|
60
|
+
const content = `// Auto-generated by shadcn-angular CLI. Do not edit manually.
|
|
61
|
+
import type { ShortcutBindingService, ShortcutDefinition } from '${shortcutServiceImport}';
|
|
62
|
+
${imports ? `${imports}\n` : ''}
|
|
63
|
+
export const GENERATED_SHORTCUT_CATALOG: ReadonlyArray<{
|
|
64
|
+
componentName: string;
|
|
65
|
+
definitions: ReadonlyArray<ShortcutDefinition>;
|
|
66
|
+
}> = [
|
|
67
|
+
${catalogItems}
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
export function registerGeneratedShortcutCatalog(shortcuts: ShortcutBindingService): void {
|
|
71
|
+
for (const entry of GENERATED_SHORTCUT_CATALOG) {
|
|
72
|
+
shortcuts.defineShortcuts(entry.componentName, [...entry.definitions]);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
`;
|
|
76
|
+
|
|
77
|
+
await fs.writeFile(registryPath, content);
|
|
78
|
+
return registryPath;
|
|
79
|
+
}
|