@birdapi/velinstyle 1.2.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/README.de.md +150 -24
  2. package/README.md +155 -33
  3. package/cli/__fixtures__/atelier-library/showcases/01-login/app.css +1 -0
  4. package/cli/__fixtures__/atelier-library/showcases/01-login/app.js +2 -0
  5. package/cli/__fixtures__/atelier-library/showcases/01-login/index.html +5 -0
  6. package/cli/__fixtures__/atelier-library/showcases/04-pricing/app.css +1 -0
  7. package/cli/__fixtures__/atelier-library/showcases/04-pricing/app.js +2 -0
  8. package/cli/__fixtures__/atelier-library/showcases/04-pricing/index.html +5 -0
  9. package/cli/__fixtures__/atelier-library/showcases/36-calendar/app.css +1 -0
  10. package/cli/__fixtures__/atelier-library/showcases/36-calendar/app.js +2 -0
  11. package/cli/__fixtures__/atelier-library/showcases/36-calendar/index.html +5 -0
  12. package/cli/atelier-catalog.json +1267 -0
  13. package/cli/atelier-formats.js +159 -0
  14. package/cli/atelier.js +382 -0
  15. package/cli/blueprints/empty-state.html +3 -5
  16. package/cli/cli-manifest.json +23 -10
  17. package/cli/docgen/extract-attributes.js +2 -0
  18. package/cli/docgen/extract-cli.js +1 -1
  19. package/cli/index.js +181 -6
  20. package/cli/production/component-graph.json +166 -0
  21. package/cli/production/explain.js +52 -0
  22. package/cli/production/extract.js +238 -0
  23. package/cli/production/graph.js +102 -0
  24. package/cli/production/report.js +78 -0
  25. package/cli/production/run.js +312 -0
  26. package/cli/production/trim-css.js +177 -0
  27. package/cli/production/trim-fonts.js +23 -0
  28. package/cli/production/trim-icons.js +38 -0
  29. package/cli/production/trim-js.js +48 -0
  30. package/cli/production/trim-motion.js +22 -0
  31. package/cli/production/trim-themes.js +50 -0
  32. package/cli/production/watch.js +38 -0
  33. package/cli/review.js +48 -1
  34. package/cli/scripts/write-atelier-fixtures.mjs +33 -0
  35. package/cli/transparency.js +221 -0
  36. package/components/index.js +3 -0
  37. package/components/runtime/component-loaders.js +3 -0
  38. package/components/velin-drawer.js +43 -4
  39. package/components/velin-empty-state.js +83 -0
  40. package/components/velin-modal.js +76 -15
  41. package/components/velin-otp-input.js +220 -0
  42. package/components/velin-password-strength.js +147 -0
  43. package/components/velin-sheet.js +49 -4
  44. package/core/a11y/component-contracts.json +23 -4
  45. package/core/attributes/registry.js +14 -0
  46. package/core/meta/knowledge/components.json +75 -3
  47. package/core/meta/schema.js +10 -0
  48. package/core/transparency/attach.js +74 -0
  49. package/core/transparency/claims.js +97 -0
  50. package/core/transparency/doctor.js +142 -0
  51. package/core/transparency/engine.js +75 -0
  52. package/core/transparency/export.js +98 -0
  53. package/core/transparency/index.js +30 -0
  54. package/core/transparency/migrate.js +130 -0
  55. package/core/transparency/normalize.js +125 -0
  56. package/core/transparency/policy.js +105 -0
  57. package/core/transparency/providers.js +235 -0
  58. package/core/transparency/registry.js +104 -0
  59. package/core/transparency/renderer.js +111 -0
  60. package/core/transparency/reporter.js +113 -0
  61. package/core/transparency/validator.js +112 -0
  62. package/dist/chunks/attach-H2ZSEAE6.js +365 -0
  63. package/dist/chunks/attributes-2ORR27KA.js +404 -0
  64. package/dist/chunks/attributes-DZCXX2RZ.js +404 -0
  65. package/dist/chunks/chunk-CQMTCEI6.js +113 -0
  66. package/dist/chunks/chunk-Y6HN7HWX.js +116 -0
  67. package/dist/chunks/runtime-entry.js +1 -1
  68. package/dist/chunks/velin-drawer-A7Q7EBOS.js +162 -0
  69. package/dist/chunks/velin-empty-state-3NTUH2RF.js +81 -0
  70. package/dist/chunks/velin-modal-3MV4FYBK.js +231 -0
  71. package/dist/chunks/velin-otp-input-PSK42MM6.js +207 -0
  72. package/dist/chunks/velin-password-strength-TAXMLSED.js +136 -0
  73. package/dist/chunks/velin-sheet-N2VVYXFP.js +157 -0
  74. package/dist/llms.txt +5 -4
  75. package/dist/search-index.json +94 -5
  76. package/dist/velin-agent.json +290 -23
  77. package/dist/velinstyle-components.iife.js +3189 -2202
  78. package/dist/velinstyle-components.js +3187 -2196
  79. package/dist/velinstyle-components.min.js +261 -119
  80. package/dist/velinstyle.css +214 -6
  81. package/dist/velinstyle.d.ts +3 -0
  82. package/dist/velinstyle.min.css +1 -1
  83. package/package.json +143 -142
  84. package/packages/velinstyle-skills/catalog.json +1 -1
  85. package/src/components/data-table.css +19 -0
  86. package/src/components/empty-auth.css +33 -0
  87. package/src/components/table.css +16 -6
  88. package/src/components/transparency.css +138 -0
  89. package/src/velinstyle.css +2 -0
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Content extract for Production Builder — used classes, WC tags, themes, icons, motion, fonts.
3
+ */
4
+ import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
5
+ import { join, extname, resolve, relative } from 'path';
6
+
7
+ export const DEFAULT_IGNORE = [
8
+ 'node_modules', 'dist', '.git', '.next', '.nuxt', 'vendor', 'build', 'coverage',
9
+ ];
10
+
11
+ export const CONTENT_EXTS = new Set([
12
+ '.html', '.htm', '.js', '.jsx', '.mjs', '.cjs', '.ts', '.tsx', '.vue', '.md', '.css',
13
+ ]);
14
+
15
+ const CLASS_ATTR_RE = /class(?:Name)?\s*=\s*["'`]([^"'`]+)["'`]/gi;
16
+ const CLASS_TOKEN_RE = /\b((?:velin|expo)-[\w:-]+)\b/g;
17
+ const TAG_RE = /<(velin-[a-z0-9-]+)\b/gi;
18
+ const DATA_COMPONENT_RE = /data-velin-component\s*=\s*["'](velin-[a-z0-9-]+)["']/gi;
19
+ const THEME_RE = /data-velin-theme\s*=\s*["']([^"']+)["']/gi;
20
+ const THEMES_LIST_RE = /themes\s*[:=]\s*\[([^\]]+)\]/gi;
21
+ const ICON_NAME_RE = /(?:name|icon)\s*=\s*["']([a-z0-9][a-z0-9_-]*)["']/gi;
22
+ const ICON_HASH_RE = /velin-icons\.svg#([a-z0-9][a-z0-9_-]*)/gi;
23
+ const MOTION_ATTR_RE = /data-velin-(?:animate|motion|scroll)\s*=\s*["']([^"']+)["']/gi;
24
+ const FONT_FACE_RE = /@font-face\s*\{([\s\S]*?)\}/gi;
25
+ const FONT_WEIGHT_RE = /font-weight\s*:\s*([0-9]+|normal|bold)/gi;
26
+
27
+ function shouldIgnore(name, ignore) {
28
+ return ignore.includes(name);
29
+ }
30
+
31
+ export function walkContentFiles(dir, { ignore = DEFAULT_IGNORE, out = [], root = dir } = {}) {
32
+ if (!existsSync(dir)) return out;
33
+ let entries;
34
+ try {
35
+ entries = readdirSync(dir, { withFileTypes: true });
36
+ } catch {
37
+ return out;
38
+ }
39
+ for (const entry of entries) {
40
+ if (shouldIgnore(entry.name, ignore)) continue;
41
+ const full = join(dir, entry.name);
42
+ if (entry.isDirectory()) {
43
+ walkContentFiles(full, { ignore, out, root });
44
+ continue;
45
+ }
46
+ if (!entry.isFile()) continue;
47
+ const ext = extname(entry.name).toLowerCase();
48
+ if (!CONTENT_EXTS.has(ext)) continue;
49
+ out.push(full);
50
+ }
51
+ return out;
52
+ }
53
+
54
+ export function collectContentFiles(targetPath, { ignore = DEFAULT_IGNORE, extraIgnore = [] } = {}) {
55
+ const abs = resolve(targetPath);
56
+ const ignoreList = [...new Set([...ignore, ...extraIgnore])];
57
+ if (!existsSync(abs)) return [];
58
+ const st = statSync(abs);
59
+ if (st.isFile()) {
60
+ const ext = extname(abs).toLowerCase();
61
+ return CONTENT_EXTS.has(ext) ? [abs] : [];
62
+ }
63
+ return walkContentFiles(abs, { ignore: ignoreList });
64
+ }
65
+
66
+ function addClassTokens(text, classes) {
67
+ for (const m of String(text || '').matchAll(CLASS_TOKEN_RE)) {
68
+ classes.add(m[1]);
69
+ // Responsive / state variants: velin-md:flex → also keep flex base if present as velin-flex
70
+ const bare = m[1].replace(/^(?:velin|expo)-(?:sm|md|lg|xl|2xl):/, (pref) => {
71
+ const ns = pref.startsWith('expo') ? 'expo-' : 'velin-';
72
+ return ns;
73
+ });
74
+ if (bare !== m[1] && bare.startsWith('velin-')) classes.add(bare);
75
+ }
76
+ }
77
+
78
+ /**
79
+ * @param {string} content
80
+ * @param {{ isCss?: boolean }} [opts]
81
+ */
82
+ export function extractFromText(content, { isCss = false } = {}) {
83
+ const classes = new Set();
84
+ const tags = new Set();
85
+ const themes = new Set();
86
+ const icons = new Set();
87
+ const motion = new Set();
88
+ const fontWeights = new Set();
89
+ const attrs = new Set();
90
+
91
+ const text = String(content || '');
92
+
93
+ if (!isCss) {
94
+ for (const m of text.matchAll(CLASS_ATTR_RE)) {
95
+ addClassTokens(m[1], classes);
96
+ }
97
+ }
98
+ addClassTokens(text, classes);
99
+
100
+ for (const m of text.matchAll(TAG_RE)) tags.add(m[1].toLowerCase());
101
+ for (const m of text.matchAll(DATA_COMPONENT_RE)) tags.add(m[1].toLowerCase());
102
+
103
+ for (const m of text.matchAll(THEME_RE)) {
104
+ for (const t of m[1].split(/[\s,|]+/)) {
105
+ if (t) themes.add(t.trim().toLowerCase());
106
+ }
107
+ }
108
+ for (const m of text.matchAll(THEMES_LIST_RE)) {
109
+ for (const part of m[1].split(/[,'"`\s]+/)) {
110
+ if (part && /^[a-z][a-z0-9_-]*$/i.test(part)) themes.add(part.toLowerCase());
111
+ }
112
+ }
113
+
114
+ for (const m of text.matchAll(ICON_NAME_RE)) icons.add(m[1].toLowerCase());
115
+ for (const m of text.matchAll(ICON_HASH_RE)) icons.add(m[1].toLowerCase());
116
+
117
+ for (const m of text.matchAll(MOTION_ATTR_RE)) {
118
+ for (const part of m[1].split(/[\s,]+/)) {
119
+ if (part) motion.add(part.trim());
120
+ }
121
+ }
122
+ for (const cls of classes) {
123
+ if (cls.includes('animate') || cls.includes('motion') || cls.startsWith('velin-scroll-')) {
124
+ motion.add(cls);
125
+ }
126
+ }
127
+
128
+ for (const cls of classes) {
129
+ if (cls.startsWith('velin-font-') || cls.startsWith('velin-text-')) {
130
+ fontWeights.add(cls);
131
+ }
132
+ }
133
+
134
+ for (const m of text.matchAll(FONT_FACE_RE)) {
135
+ for (const w of m[1].matchAll(FONT_WEIGHT_RE)) fontWeights.add(String(w[1]));
136
+ }
137
+
138
+ for (const m of text.matchAll(/data-velin-([\w-]+)/gi)) {
139
+ attrs.add(`data-velin-${m[1].toLowerCase()}`);
140
+ }
141
+
142
+ return { classes, tags, themes, icons, motion, fontWeights, attrs };
143
+ }
144
+
145
+ /**
146
+ * Scan a project path and merge all extracts.
147
+ * @returns {{
148
+ * files: string[],
149
+ * classes: Set<string>,
150
+ * tags: Set<string>,
151
+ * themes: Set<string>,
152
+ * icons: Set<string>,
153
+ * motion: Set<string>,
154
+ * fontWeights: Set<string>,
155
+ * attrs: Set<string>,
156
+ * }}
157
+ */
158
+ export function extractProject(targetPath, options = {}) {
159
+ const {
160
+ ignore = DEFAULT_IGNORE,
161
+ contentGlobs, // reserved
162
+ safelist = [],
163
+ outDir,
164
+ cwd = process.cwd(),
165
+ } = options;
166
+
167
+ const extraIgnore = [];
168
+ if (outDir) {
169
+ try {
170
+ const rel = relative(resolve(cwd), resolve(outDir)).split(/[/\\]/)[0];
171
+ if (rel && rel !== '..' && rel !== '.') extraIgnore.push(rel);
172
+ } catch { /* ignore */ }
173
+ }
174
+
175
+ const files = collectContentFiles(targetPath, { ignore, extraIgnore });
176
+ const merged = {
177
+ files,
178
+ classes: new Set(),
179
+ tags: new Set(),
180
+ themes: new Set(),
181
+ icons: new Set(),
182
+ motion: new Set(),
183
+ fontWeights: new Set(),
184
+ attrs: new Set(),
185
+ };
186
+
187
+ for (const file of files) {
188
+ let body = '';
189
+ try {
190
+ body = readFileSync(file, 'utf-8');
191
+ } catch {
192
+ continue;
193
+ }
194
+ const isCss = extname(file).toLowerCase() === '.css';
195
+ const part = extractFromText(body, { isCss });
196
+ for (const c of part.classes) merged.classes.add(c);
197
+ for (const t of part.tags) merged.tags.add(t);
198
+ for (const t of part.themes) merged.themes.add(t);
199
+ for (const i of part.icons) merged.icons.add(i);
200
+ for (const m of part.motion) merged.motion.add(m);
201
+ for (const f of part.fontWeights) merged.fontWeights.add(f);
202
+ for (const a of part.attrs) merged.attrs.add(a);
203
+ }
204
+
205
+ for (const item of safelist) {
206
+ const s = String(item).trim();
207
+ if (!s) continue;
208
+ if (s.startsWith('velin-') && !s.includes('.')) {
209
+ if (s.includes('-') && /^velin-[a-z]+-[a-z]/.test(s) && !s.includes('--') && s.split('-').length >= 3 && !s.startsWith('velin-btn') && !s.startsWith('velin-card')) {
210
+ // could be tag or class — add both conservatively
211
+ merged.tags.add(s);
212
+ merged.classes.add(s);
213
+ } else {
214
+ merged.classes.add(s);
215
+ }
216
+ } else if (s.endsWith('.css') || s.includes('/')) {
217
+ // handled by trim safelist files
218
+ } else if (/^[a-z][a-z0-9_-]*$/i.test(s)) {
219
+ merged.themes.add(s.toLowerCase());
220
+ merged.icons.add(s.toLowerCase());
221
+ }
222
+ }
223
+
224
+ return merged;
225
+ }
226
+
227
+ export function extractToPlain(extracted) {
228
+ return {
229
+ files: extracted.files,
230
+ classes: [...extracted.classes].sort(),
231
+ tags: [...extracted.tags].sort(),
232
+ themes: [...extracted.themes].sort(),
233
+ icons: [...extracted.icons].sort(),
234
+ motion: [...extracted.motion].sort(),
235
+ fontWeights: [...extracted.fontWeights].sort(),
236
+ attrs: [...extracted.attrs].sort(),
237
+ };
238
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Component dependency graph — closure over used classes/tags → CSS files + WC tags.
3
+ */
4
+ import { readFileSync, existsSync, readdirSync } from 'fs';
5
+ import { dirname, join } from 'path';
6
+ import { fileURLToPath } from 'url';
7
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const GRAPH_PATH = join(__dirname, 'component-graph.json');
10
+
11
+ let _graphCache = null;
12
+
13
+ export function loadGraph(path = GRAPH_PATH) {
14
+ if (_graphCache && path === GRAPH_PATH) return _graphCache;
15
+ const graph = JSON.parse(readFileSync(path, 'utf-8'));
16
+ if (path === GRAPH_PATH) _graphCache = graph;
17
+ return graph;
18
+ }
19
+
20
+ function longestPrefixMatch(className, prefixes) {
21
+ let best = null;
22
+ let bestLen = -1;
23
+ for (const prefix of Object.keys(prefixes)) {
24
+ if (className === prefix || className.startsWith(`${prefix}-`) || className.startsWith(`${prefix}--`)) {
25
+ if (prefix.length > bestLen) {
26
+ best = prefix;
27
+ bestLen = prefix.length;
28
+ }
29
+ }
30
+ }
31
+ return best;
32
+ }
33
+
34
+ /**
35
+ * @param {{ classes: Iterable<string>, tags: Iterable<string> }} used
36
+ * @param {{ graph?: object, safelistFiles?: string[] }} [opts]
37
+ */
38
+ export function resolveClosure(used, opts = {}) {
39
+ const graph = opts.graph || loadGraph();
40
+ const cssFiles = new Set(graph.alwaysCss || []);
41
+ const tags = new Set();
42
+ const matchedPrefixes = new Set();
43
+ const explain = [];
44
+
45
+ for (const f of opts.safelistFiles || []) {
46
+ const norm = String(f).replace(/^src\//, '');
47
+ cssFiles.add(norm);
48
+ explain.push({ keep: norm, reason: 'safelist' });
49
+ }
50
+
51
+ for (const cls of used.classes || []) {
52
+ const prefix = longestPrefixMatch(cls, graph.classPrefixes || {});
53
+ if (!prefix) continue;
54
+ matchedPrefixes.add(prefix);
55
+ const entry = graph.classPrefixes[prefix];
56
+ for (const file of entry.css || []) {
57
+ cssFiles.add(file);
58
+ explain.push({ keep: file, reason: `class ${cls} → ${prefix}` });
59
+ }
60
+ }
61
+
62
+ const queue = [...(used.tags || [])];
63
+ const seen = new Set();
64
+
65
+ while (queue.length) {
66
+ const tag = queue.shift();
67
+ if (!tag || seen.has(tag)) continue;
68
+ seen.add(tag);
69
+ tags.add(tag);
70
+ const entry = graph.wc?.[tag];
71
+ if (!entry) {
72
+ explain.push({ keep: tag, reason: 'unknown WC — keep tag, no CSS edges' });
73
+ continue;
74
+ }
75
+ for (const file of entry.css || []) {
76
+ cssFiles.add(file);
77
+ explain.push({ keep: file, reason: `wc ${tag}` });
78
+ }
79
+ for (const peer of entry.peers || []) {
80
+ if (!seen.has(peer)) queue.push(peer);
81
+ }
82
+ }
83
+
84
+ return {
85
+ cssFiles: [...cssFiles].sort(),
86
+ tags: [...tags].sort(),
87
+ matchedPrefixes: [...matchedPrefixes].sort(),
88
+ utilityFiles: [...(graph.utilityFiles || [])],
89
+ motionFiles: [...(graph.motionFiles || [])],
90
+ alwaysCss: [...(graph.alwaysCss || [])],
91
+ explain,
92
+ };
93
+ }
94
+
95
+ export function listKnownThemeNames(pkgRoot) {
96
+ const dir = join(pkgRoot, 'src', 'themes');
97
+ if (!existsSync(dir)) return [];
98
+ return readdirSync(dir)
99
+ .filter((f) => f.endsWith('.css'))
100
+ .map((f) => f.replace(/\.css$/, ''))
101
+ .sort();
102
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Production report — text + JSON.
3
+ */
4
+ export function buildReport(data) {
5
+ const {
6
+ originalBytes = 0,
7
+ productionBytes = 0,
8
+ breakdown = {},
9
+ themes = [],
10
+ icons = [],
11
+ components = [],
12
+ tags = [],
13
+ imagesSkipped = true,
14
+ filesScanned = 0,
15
+ } = data;
16
+
17
+ const saved = Math.max(0, originalBytes - productionBytes);
18
+ const pct = originalBytes > 0 ? Math.round((saved / originalBytes) * 100) : 0;
19
+
20
+ const lines = [
21
+ 'Velin Production Report',
22
+ `Original ${formatKb(originalBytes)}`,
23
+ '↓',
24
+ `Production ${formatKb(productionBytes)}`,
25
+ '↓',
26
+ `${pct} % gespart`,
27
+ '',
28
+ ];
29
+
30
+ for (const [key, delta] of Object.entries(breakdown)) {
31
+ if (delta == null) continue;
32
+ const label = key.padEnd(8);
33
+ const sign = delta > 0 ? '-' : delta < 0 ? '+' : ' ';
34
+ lines.push(`${label} ${sign}${formatKb(Math.abs(delta))}`);
35
+ }
36
+
37
+ if (imagesSkipped) {
38
+ lines.push('');
39
+ lines.push('Images: skipped (coming soon)');
40
+ }
41
+
42
+ lines.push('');
43
+ lines.push(`Scanned files: ${filesScanned}`);
44
+ if (themes.length) lines.push(`Themes: ${themes.join(', ')}`);
45
+ if (icons.length) lines.push(`Icons (${icons.length}): ${icons.slice(0, 12).join(', ')}${icons.length > 12 ? '…' : ''}`);
46
+ if (tags.length) lines.push(`Web Components: ${tags.join(', ')}`);
47
+ if (components.length) lines.push(`CSS modules: ${components.length}`);
48
+
49
+ return {
50
+ version: 1,
51
+ originalBytes,
52
+ productionBytes,
53
+ savedBytes: saved,
54
+ savedPercent: pct,
55
+ breakdown,
56
+ themes,
57
+ icons,
58
+ tags,
59
+ components,
60
+ filesScanned,
61
+ images: imagesSkipped ? { status: 'skipped', note: 'coming soon' } : null,
62
+ text: lines.join('\n'),
63
+ };
64
+ }
65
+
66
+ export function formatKb(bytes) {
67
+ return `${(bytes / 1024).toFixed(bytes >= 10240 ? 0 : 1)} KB`;
68
+ }
69
+
70
+ export function measureBytes(parts) {
71
+ let total = 0;
72
+ for (const p of parts) {
73
+ if (typeof p === 'number') total += p;
74
+ else if (typeof p === 'string') total += Buffer.byteLength(p, 'utf-8');
75
+ else if (p && typeof p.bytes === 'number') total += p.bytes;
76
+ }
77
+ return total;
78
+ }