@zseven-w/pen-core 0.5.2 → 0.7.0
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 +18 -9
- package/package.json +5 -5
- package/src/__tests__/arc-path.test.ts +23 -23
- package/src/__tests__/codegen-utils.test.ts +50 -0
- package/src/__tests__/design-md-parser.test.ts +49 -0
- package/src/__tests__/font-utils.test.ts +15 -15
- package/src/__tests__/layout-engine.test.ts +169 -83
- package/src/__tests__/merge-helpers.test.ts +143 -0
- package/src/__tests__/node-diff.test.ts +139 -0
- package/src/__tests__/node-helpers.test.ts +19 -19
- package/src/__tests__/node-merge.test.ts +425 -0
- package/src/__tests__/normalize-stroke-fill-schema.test.ts +416 -0
- package/src/__tests__/normalize-tree-layout.test.ts +294 -0
- package/src/__tests__/normalize.test.ts +119 -80
- package/src/__tests__/path-anchors.test.ts +98 -0
- package/src/__tests__/resolve-variables-recursive.test.ts +173 -0
- package/src/__tests__/strip-redundant-section-fills.test.ts +278 -0
- package/src/__tests__/text-measure.test.ts +84 -79
- package/src/__tests__/tree-utils.test.ts +133 -102
- package/src/__tests__/unwrap-fake-phone-mockup.test.ts +322 -0
- package/src/__tests__/variables.test.ts +68 -65
- package/src/arc-path.ts +35 -35
- package/src/boolean-ops.ts +158 -111
- package/src/constants.ts +36 -36
- package/src/design-md-parser.ts +363 -0
- package/src/font-utils.ts +30 -15
- package/src/id.ts +1 -1
- package/src/index.ts +47 -13
- package/src/layout/engine.ts +255 -230
- package/src/layout/normalize-tree.ts +140 -0
- package/src/layout/strip-redundant-section-fills.ts +155 -0
- package/src/layout/text-measure.ts +133 -105
- package/src/layout/unwrap-fake-phone-mockup.ts +147 -0
- package/src/merge/index.ts +16 -0
- package/src/merge/merge-helpers.ts +113 -0
- package/src/merge/node-diff.ts +123 -0
- package/src/merge/node-merge.ts +651 -0
- package/src/node-helpers.ts +18 -5
- package/src/normalize/normalize-stroke-fill-schema.ts +297 -0
- package/src/normalize.ts +79 -79
- package/src/path-anchors.ts +331 -0
- package/src/sync-lock.ts +3 -3
- package/src/tree-utils.ts +180 -158
- package/src/variables/replace-refs.ts +63 -60
- package/src/variables/resolve.ts +107 -102
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import type { DesignMdSpec, DesignMdColor, DesignMdTypography } from '@zseven-w/pen-types';
|
|
2
|
+
import type { PenDocument } from '@zseven-w/pen-types';
|
|
3
|
+
import type { VariableDefinition } from '@zseven-w/pen-types';
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Section header patterns (fuzzy matching)
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
const SECTION_PATTERNS: Record<string, RegExp> = {
|
|
10
|
+
visualTheme: /visual\s*theme|atmosphere|mood/i,
|
|
11
|
+
colorPalette: /colou?r\s*(palette|roles?|system)?/i,
|
|
12
|
+
typography: /typography|type\s*rules?|fonts?/i,
|
|
13
|
+
componentStyles: /component\s*(styl|pattern)|button|card|input/i,
|
|
14
|
+
layoutPrinciples: /layout\s*(principle|rule|system)?|grid|whitespace/i,
|
|
15
|
+
generationNotes: /generation\s*notes?|design\s*system\s*notes?|stitch|prompting/i,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
interface ParsedSection {
|
|
19
|
+
key: string;
|
|
20
|
+
title: string;
|
|
21
|
+
content: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Parse markdown into sections
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
function splitSections(markdown: string): { projectName?: string; sections: ParsedSection[] } {
|
|
29
|
+
const lines = markdown.split('\n');
|
|
30
|
+
let projectName: string | undefined;
|
|
31
|
+
|
|
32
|
+
// Extract project name from H1
|
|
33
|
+
const h1Match = markdown.match(/^#\s+(?:Design\s+System:\s*)?(.+)$/m);
|
|
34
|
+
if (h1Match) projectName = h1Match[1].trim();
|
|
35
|
+
|
|
36
|
+
const sections: ParsedSection[] = [];
|
|
37
|
+
let currentTitle = '';
|
|
38
|
+
let currentLines: string[] = [];
|
|
39
|
+
let currentKey = '';
|
|
40
|
+
|
|
41
|
+
for (const line of lines) {
|
|
42
|
+
const h2Match = line.match(/^##\s+(?:\d+\.\s*)?(.+)$/);
|
|
43
|
+
if (h2Match) {
|
|
44
|
+
// Flush previous section
|
|
45
|
+
if (currentKey) {
|
|
46
|
+
sections.push({
|
|
47
|
+
key: currentKey,
|
|
48
|
+
title: currentTitle,
|
|
49
|
+
content: currentLines.join('\n').trim(),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
currentTitle = h2Match[1].trim();
|
|
53
|
+
currentLines = [];
|
|
54
|
+
currentKey = matchSectionKey(currentTitle);
|
|
55
|
+
} else {
|
|
56
|
+
currentLines.push(line);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Flush last section
|
|
61
|
+
if (currentKey) {
|
|
62
|
+
sections.push({
|
|
63
|
+
key: currentKey,
|
|
64
|
+
title: currentTitle,
|
|
65
|
+
content: currentLines.join('\n').trim(),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { projectName, sections };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function matchSectionKey(title: string): string {
|
|
73
|
+
for (const [key, pattern] of Object.entries(SECTION_PATTERNS)) {
|
|
74
|
+
if (pattern.test(title)) return key;
|
|
75
|
+
}
|
|
76
|
+
// Fallback: try to match common section names loosely
|
|
77
|
+
const lower = title.toLowerCase();
|
|
78
|
+
if (lower.includes('theme') || lower.includes('vibe') || lower.includes('aesthetic'))
|
|
79
|
+
return 'visualTheme';
|
|
80
|
+
if (lower.includes('color') || lower.includes('palette') || lower.includes('colour'))
|
|
81
|
+
return 'colorPalette';
|
|
82
|
+
if (lower.includes('type') || lower.includes('font') || lower.includes('typo'))
|
|
83
|
+
return 'typography';
|
|
84
|
+
if (lower.includes('component') || lower.includes('style') || lower.includes('element'))
|
|
85
|
+
return 'componentStyles';
|
|
86
|
+
if (lower.includes('layout') || lower.includes('spacing') || lower.includes('grid'))
|
|
87
|
+
return 'layoutPrinciples';
|
|
88
|
+
if (lower.includes('note') || lower.includes('generation') || lower.includes('usage'))
|
|
89
|
+
return 'generationNotes';
|
|
90
|
+
return 'unknown';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// Color extraction
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
const HEX_COLOR_RE = /#([0-9A-Fa-f]{6})\b/;
|
|
98
|
+
|
|
99
|
+
function parseColors(content: string): DesignMdColor[] {
|
|
100
|
+
const colors: DesignMdColor[] = [];
|
|
101
|
+
const lines = content.split('\n');
|
|
102
|
+
|
|
103
|
+
for (const line of lines) {
|
|
104
|
+
// Skip JSON/code artifacts
|
|
105
|
+
if (line.trimStart().startsWith('{') || line.trimStart().startsWith('`')) continue;
|
|
106
|
+
|
|
107
|
+
const hexMatch = line.match(HEX_COLOR_RE);
|
|
108
|
+
if (!hexMatch) continue;
|
|
109
|
+
|
|
110
|
+
const hex = `#${hexMatch[1].toUpperCase()}`;
|
|
111
|
+
|
|
112
|
+
// Try pattern: **Name** (hex) — role OR - **Name** (#hex) – role
|
|
113
|
+
const namedMatch = line.match(/\*\*([^*]+)\*\*\s*\(?#?[0-9A-Fa-f]{6}\)?\s*[–—-]\s*(.+)/);
|
|
114
|
+
if (namedMatch) {
|
|
115
|
+
colors.push({ name: namedMatch[1].trim(), hex, role: namedMatch[2].trim() });
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Try pattern: - Name (#hex) — role OR Name (#hex): role
|
|
120
|
+
const dashMatch = line.match(/[-*]\s*(.+?)\s*\(#?[0-9A-Fa-f]{6}\)\s*[–—:-]\s*(.+)/);
|
|
121
|
+
if (dashMatch) {
|
|
122
|
+
colors.push({ name: dashMatch[1].trim(), hex, role: dashMatch[2].trim() });
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Try pattern: - name: #hex (role) OR - name: #hex — role
|
|
127
|
+
const colonHexMatch = line.match(/[-*]\s*([^:#(]+?)\s*:\s*#?[0-9A-Fa-f]{6}\s*\(?([^)]*)\)?/);
|
|
128
|
+
if (colonHexMatch) {
|
|
129
|
+
colors.push({
|
|
130
|
+
name: colonHexMatch[1].trim(),
|
|
131
|
+
hex,
|
|
132
|
+
role: colonHexMatch[2].trim(),
|
|
133
|
+
});
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Fallback: just grab the hex and surrounding text
|
|
138
|
+
const before = line
|
|
139
|
+
.substring(0, hexMatch.index)
|
|
140
|
+
.replace(/[-*#\s]+$/, '')
|
|
141
|
+
.trim();
|
|
142
|
+
const after = line
|
|
143
|
+
.substring((hexMatch.index ?? 0) + hexMatch[0].length)
|
|
144
|
+
.replace(/^[)\s–—:-]+/, '')
|
|
145
|
+
.trim();
|
|
146
|
+
colors.push({
|
|
147
|
+
name: before || hex,
|
|
148
|
+
hex,
|
|
149
|
+
role: after || '',
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return colors;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
// Typography extraction
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
function parseTypography(content: string): DesignMdTypography {
|
|
161
|
+
const typo: DesignMdTypography = {};
|
|
162
|
+
|
|
163
|
+
// Font family
|
|
164
|
+
const fontMatch = content.match(/(?:font\s*family|primary\s*font)[:\s]*\*?\*?([^*\n]+)/i);
|
|
165
|
+
if (fontMatch) typo.fontFamily = fontMatch[1].trim();
|
|
166
|
+
|
|
167
|
+
// Try to extract heading/body descriptions
|
|
168
|
+
const headingMatch = content.match(/(?:heading|display|h1)[^:]*:\s*([^\n]+)/i);
|
|
169
|
+
if (headingMatch) typo.headings = headingMatch[1].trim();
|
|
170
|
+
|
|
171
|
+
const bodyMatch = content.match(/(?:body\s*text|paragraph)[^:]*:\s*([^\n]+)/i);
|
|
172
|
+
if (bodyMatch) typo.body = bodyMatch[1].trim();
|
|
173
|
+
|
|
174
|
+
// Keep full content as scale description
|
|
175
|
+
typo.scale = content;
|
|
176
|
+
|
|
177
|
+
return typo;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// Public API
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
/** Parse a design.md markdown string into a structured DesignMdSpec. */
|
|
185
|
+
export function parseDesignMd(markdown: string): DesignMdSpec {
|
|
186
|
+
const { projectName, sections } = splitSections(markdown);
|
|
187
|
+
|
|
188
|
+
const spec: DesignMdSpec = { raw: markdown, projectName, colorPalette: [] };
|
|
189
|
+
|
|
190
|
+
for (const section of sections) {
|
|
191
|
+
switch (section.key) {
|
|
192
|
+
case 'visualTheme':
|
|
193
|
+
spec.visualTheme = section.content;
|
|
194
|
+
break;
|
|
195
|
+
case 'colorPalette':
|
|
196
|
+
spec.colorPalette = parseColors(section.content);
|
|
197
|
+
break;
|
|
198
|
+
case 'typography':
|
|
199
|
+
spec.typography = parseTypography(section.content);
|
|
200
|
+
break;
|
|
201
|
+
case 'componentStyles':
|
|
202
|
+
spec.componentStyles = section.content;
|
|
203
|
+
break;
|
|
204
|
+
case 'layoutPrinciples':
|
|
205
|
+
spec.layoutPrinciples = section.content;
|
|
206
|
+
break;
|
|
207
|
+
case 'generationNotes':
|
|
208
|
+
spec.generationNotes = section.content;
|
|
209
|
+
break;
|
|
210
|
+
case 'unknown':
|
|
211
|
+
// Append to componentStyles as catch-all
|
|
212
|
+
spec.componentStyles = spec.componentStyles
|
|
213
|
+
? spec.componentStyles + '\n\n' + section.content
|
|
214
|
+
: section.content;
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Fallback: if no sections were parsed at all, try to extract colors
|
|
220
|
+
// and typography from the entire markdown
|
|
221
|
+
if (!spec.colorPalette || spec.colorPalette.length === 0) {
|
|
222
|
+
const colors = parseColors(markdown);
|
|
223
|
+
if (colors.length > 0) spec.colorPalette = colors;
|
|
224
|
+
}
|
|
225
|
+
if (!spec.typography) {
|
|
226
|
+
const typo = parseTypography(markdown);
|
|
227
|
+
if (typo.fontFamily) spec.typography = typo;
|
|
228
|
+
}
|
|
229
|
+
// If still nothing parsed, store the whole text as visual theme
|
|
230
|
+
if (!spec.visualTheme && !spec.colorPalette && !spec.componentStyles && sections.length === 0) {
|
|
231
|
+
spec.visualTheme = markdown.trim();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return spec;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Generate a design.md markdown string from a DesignMdSpec. */
|
|
238
|
+
export function generateDesignMd(spec: DesignMdSpec): string {
|
|
239
|
+
// If raw was set and nothing was structurally changed, return as-is
|
|
240
|
+
if (spec.raw) return spec.raw;
|
|
241
|
+
|
|
242
|
+
const lines: string[] = [];
|
|
243
|
+
|
|
244
|
+
lines.push(`# Design System: ${spec.projectName ?? 'Untitled'}`);
|
|
245
|
+
lines.push('');
|
|
246
|
+
|
|
247
|
+
if (spec.visualTheme) {
|
|
248
|
+
lines.push('## 1. Visual Theme & Atmosphere');
|
|
249
|
+
lines.push(spec.visualTheme);
|
|
250
|
+
lines.push('');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (spec.colorPalette?.length) {
|
|
254
|
+
lines.push('## 2. Color Palette & Roles');
|
|
255
|
+
lines.push('');
|
|
256
|
+
for (const c of spec.colorPalette) {
|
|
257
|
+
lines.push(`- **${c.name}** (${c.hex}) — ${c.role}`);
|
|
258
|
+
}
|
|
259
|
+
lines.push('');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (spec.typography) {
|
|
263
|
+
lines.push('## 3. Typography Rules');
|
|
264
|
+
if (spec.typography.fontFamily) {
|
|
265
|
+
lines.push(`**Primary Font Family:** ${spec.typography.fontFamily}`);
|
|
266
|
+
}
|
|
267
|
+
if (spec.typography.scale) {
|
|
268
|
+
lines.push(spec.typography.scale);
|
|
269
|
+
}
|
|
270
|
+
lines.push('');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (spec.componentStyles) {
|
|
274
|
+
lines.push('## 4. Component Stylings');
|
|
275
|
+
lines.push(spec.componentStyles);
|
|
276
|
+
lines.push('');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (spec.layoutPrinciples) {
|
|
280
|
+
lines.push('## 5. Layout Principles');
|
|
281
|
+
lines.push(spec.layoutPrinciples);
|
|
282
|
+
lines.push('');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (spec.generationNotes) {
|
|
286
|
+
lines.push('## 6. Design System Notes');
|
|
287
|
+
lines.push(spec.generationNotes);
|
|
288
|
+
lines.push('');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return lines.join('\n');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Auto-extract a DesignMdSpec from an existing PenDocument. */
|
|
295
|
+
export function extractDesignMdFromDocument(doc: PenDocument): DesignMdSpec {
|
|
296
|
+
const colors: DesignMdColor[] = [];
|
|
297
|
+
|
|
298
|
+
// Extract colors from document variables
|
|
299
|
+
if (doc.variables) {
|
|
300
|
+
for (const [name, def] of Object.entries(doc.variables)) {
|
|
301
|
+
if (def.type !== 'color') continue;
|
|
302
|
+
const value =
|
|
303
|
+
typeof def.value === 'string'
|
|
304
|
+
? def.value
|
|
305
|
+
: Array.isArray(def.value)
|
|
306
|
+
? String(def.value[0]?.value ?? '')
|
|
307
|
+
: String(def.value);
|
|
308
|
+
if (/^#[0-9A-Fa-f]{6,8}$/.test(value)) {
|
|
309
|
+
colors.push({
|
|
310
|
+
name: name.replace(/^[$]/, ''),
|
|
311
|
+
hex: value.substring(0, 7).toUpperCase(),
|
|
312
|
+
role: `Design variable $${name}`,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Collect font families from text nodes
|
|
319
|
+
const fonts = new Set<string>();
|
|
320
|
+
const collectFonts = (nodes: { fontFamily?: string; children?: unknown[] }[]) => {
|
|
321
|
+
for (const n of nodes) {
|
|
322
|
+
if ('fontFamily' in n && typeof n.fontFamily === 'string') fonts.add(n.fontFamily);
|
|
323
|
+
if ('children' in n && Array.isArray(n.children))
|
|
324
|
+
collectFonts(n.children as { fontFamily?: string; children?: unknown[] }[]);
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
collectFonts(doc.children as { fontFamily?: string; children?: unknown[] }[]);
|
|
328
|
+
if (doc.pages) {
|
|
329
|
+
for (const page of doc.pages) {
|
|
330
|
+
collectFonts(page.children as { fontFamily?: string; children?: unknown[] }[]);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const typography: DesignMdTypography = {};
|
|
335
|
+
if (fonts.size > 0) typography.fontFamily = [...fonts].join(', ');
|
|
336
|
+
|
|
337
|
+
const spec: DesignMdSpec = {
|
|
338
|
+
raw: '',
|
|
339
|
+
projectName: doc.name ?? 'Untitled',
|
|
340
|
+
colorPalette: colors.length > 0 ? colors : undefined,
|
|
341
|
+
typography: fonts.size > 0 ? typography : undefined,
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
// Generate the markdown and set as raw
|
|
345
|
+
spec.raw = generateDesignMd(spec);
|
|
346
|
+
|
|
347
|
+
return spec;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** Convert design.md colors to document variables. */
|
|
351
|
+
export function designMdColorsToVariables(
|
|
352
|
+
colors: DesignMdColor[],
|
|
353
|
+
): Record<string, VariableDefinition> {
|
|
354
|
+
const vars: Record<string, VariableDefinition> = {};
|
|
355
|
+
for (const color of colors) {
|
|
356
|
+
const key = color.name
|
|
357
|
+
.toLowerCase()
|
|
358
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
359
|
+
.replace(/^-|-$/g, '');
|
|
360
|
+
vars[key] = { type: 'color', value: color.hex };
|
|
361
|
+
}
|
|
362
|
+
return vars;
|
|
363
|
+
}
|
package/src/font-utils.ts
CHANGED
|
@@ -3,21 +3,36 @@
|
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
4
|
|
|
5
5
|
const GENERIC_FAMILIES = new Set([
|
|
6
|
-
'serif',
|
|
7
|
-
'
|
|
8
|
-
'
|
|
9
|
-
|
|
6
|
+
'serif',
|
|
7
|
+
'sans-serif',
|
|
8
|
+
'monospace',
|
|
9
|
+
'cursive',
|
|
10
|
+
'fantasy',
|
|
11
|
+
'system-ui',
|
|
12
|
+
'ui-serif',
|
|
13
|
+
'ui-sans-serif',
|
|
14
|
+
'ui-monospace',
|
|
15
|
+
'ui-rounded',
|
|
16
|
+
'-apple-system',
|
|
17
|
+
'blinkmacsystemfont',
|
|
18
|
+
]);
|
|
10
19
|
|
|
11
20
|
export function cssFontFamily(family: string): string {
|
|
12
|
-
return family
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
return family
|
|
22
|
+
.split(',')
|
|
23
|
+
.map((f) => {
|
|
24
|
+
const trimmed = f.trim();
|
|
25
|
+
if (!trimmed) return trimmed;
|
|
26
|
+
// Already quoted
|
|
27
|
+
if (
|
|
28
|
+
(trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
29
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"))
|
|
30
|
+
)
|
|
31
|
+
return trimmed;
|
|
32
|
+
// Generic families must not be quoted
|
|
33
|
+
if (GENERIC_FAMILIES.has(trimmed.toLowerCase())) return trimmed;
|
|
34
|
+
// Quote everything else (safe even for single-word names)
|
|
35
|
+
return `"${trimmed}"`;
|
|
36
|
+
})
|
|
37
|
+
.join(', ');
|
|
23
38
|
}
|
package/src/id.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { nanoid as generateId } from 'nanoid'
|
|
1
|
+
export { nanoid as generateId } from 'nanoid';
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// ID generation
|
|
2
|
-
export { generateId } from './id.js'
|
|
2
|
+
export { generateId } from './id.js';
|
|
3
3
|
|
|
4
4
|
// Tree utilities
|
|
5
5
|
export {
|
|
@@ -26,7 +26,8 @@ export {
|
|
|
26
26
|
deepCloneNode,
|
|
27
27
|
cloneNodeWithNewIds,
|
|
28
28
|
cloneNodesWithNewIds,
|
|
29
|
-
|
|
29
|
+
nodeTreeToSummary,
|
|
30
|
+
} from './tree-utils.js';
|
|
30
31
|
|
|
31
32
|
// Variables
|
|
32
33
|
export {
|
|
@@ -36,11 +37,11 @@ export {
|
|
|
36
37
|
resolveColorRef,
|
|
37
38
|
resolveNumericRef,
|
|
38
39
|
resolveNodeForCanvas,
|
|
39
|
-
} from './variables/resolve.js'
|
|
40
|
-
export { replaceVariableRefsInTree } from './variables/replace-refs.js'
|
|
40
|
+
} from './variables/resolve.js';
|
|
41
|
+
export { replaceVariableRefsInTree } from './variables/replace-refs.js';
|
|
41
42
|
|
|
42
43
|
// Normalization
|
|
43
|
-
export { normalizePenDocument } from './normalize.js'
|
|
44
|
+
export { normalizePenDocument } from './normalize.js';
|
|
44
45
|
|
|
45
46
|
// Layout
|
|
46
47
|
export {
|
|
@@ -55,7 +56,11 @@ export {
|
|
|
55
56
|
getNodeWidth,
|
|
56
57
|
getNodeHeight,
|
|
57
58
|
computeLayoutPositions,
|
|
58
|
-
} from './layout/engine.js'
|
|
59
|
+
} from './layout/engine.js';
|
|
60
|
+
export { normalizeTreeLayout } from './layout/normalize-tree.js';
|
|
61
|
+
export { unwrapFakePhoneMockups } from './layout/unwrap-fake-phone-mockup.js';
|
|
62
|
+
export { stripRedundantSectionFills } from './layout/strip-redundant-section-fills.js';
|
|
63
|
+
export { normalizeStrokeFillSchema } from './normalize/normalize-stroke-fill-schema.js';
|
|
59
64
|
|
|
60
65
|
// Text measurement
|
|
61
66
|
export {
|
|
@@ -75,7 +80,7 @@ export {
|
|
|
75
80
|
type WrappedLineCounter,
|
|
76
81
|
setWrappedLineCounter,
|
|
77
82
|
estimateTextHeight,
|
|
78
|
-
} from './layout/text-measure.js'
|
|
83
|
+
} from './layout/text-measure.js';
|
|
79
84
|
|
|
80
85
|
// Constants
|
|
81
86
|
export {
|
|
@@ -115,19 +120,48 @@ export {
|
|
|
115
120
|
GUIDE_COLOR,
|
|
116
121
|
GUIDE_LINE_WIDTH,
|
|
117
122
|
GUIDE_DASH,
|
|
118
|
-
} from './constants.js'
|
|
123
|
+
} from './constants.js';
|
|
119
124
|
|
|
120
125
|
// Sync lock
|
|
121
|
-
export { isFabricSyncLocked, setFabricSyncLock } from './sync-lock.js'
|
|
126
|
+
export { isFabricSyncLocked, setFabricSyncLock } from './sync-lock.js';
|
|
122
127
|
|
|
123
128
|
// Arc path
|
|
124
|
-
export { buildEllipseArcPath, isArcEllipse } from './arc-path.js'
|
|
129
|
+
export { buildEllipseArcPath, isArcEllipse } from './arc-path.js';
|
|
130
|
+
export {
|
|
131
|
+
anchorsToPathData,
|
|
132
|
+
getPathBoundsFromAnchors,
|
|
133
|
+
inferPathAnchorPointType,
|
|
134
|
+
pathDataToAnchors,
|
|
135
|
+
type PathBounds,
|
|
136
|
+
type PathAnchorParseResult,
|
|
137
|
+
} from './path-anchors.js';
|
|
125
138
|
|
|
126
139
|
// Boolean operations
|
|
127
|
-
export { type BooleanOpType, canBooleanOp, executeBooleanOp } from './boolean-ops.js'
|
|
140
|
+
export { type BooleanOpType, canBooleanOp, executeBooleanOp } from './boolean-ops.js';
|
|
128
141
|
|
|
129
142
|
// Font utilities
|
|
130
|
-
export { cssFontFamily } from './font-utils.js'
|
|
143
|
+
export { cssFontFamily } from './font-utils.js';
|
|
131
144
|
|
|
132
145
|
// Node helpers
|
|
133
|
-
export { isBadgeOverlayNode } from './node-helpers.js'
|
|
146
|
+
export { isBadgeOverlayNode, sanitizeName } from './node-helpers.js';
|
|
147
|
+
|
|
148
|
+
// Design-MD parser
|
|
149
|
+
export {
|
|
150
|
+
parseDesignMd,
|
|
151
|
+
generateDesignMd,
|
|
152
|
+
designMdColorsToVariables,
|
|
153
|
+
extractDesignMdFromDocument,
|
|
154
|
+
} from './design-md-parser.js';
|
|
155
|
+
|
|
156
|
+
// --- Merge module ---
|
|
157
|
+
export type { NodePatch } from './merge/node-diff.js';
|
|
158
|
+
export { diffDocuments } from './merge/node-diff.js';
|
|
159
|
+
export type {
|
|
160
|
+
MergeInput,
|
|
161
|
+
MergeResult,
|
|
162
|
+
NodeConflict,
|
|
163
|
+
NodeConflictReason,
|
|
164
|
+
DocFieldConflict,
|
|
165
|
+
DocFieldName,
|
|
166
|
+
} from './merge/node-merge.js';
|
|
167
|
+
export { mergeDocuments } from './merge/node-merge.js';
|