@promptui-lib/figma-parser 0.1.9 → 0.1.11
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/parser/index.d.ts +2 -0
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/index.js +1 -0
- package/dist/parser/layout-parser.d.ts.map +1 -1
- package/dist/parser/layout-parser.js +39 -2
- package/dist/parser/node-parser.d.ts.map +1 -1
- package/dist/parser/node-parser.js +60 -7
- package/dist/parser/position-parser.d.ts +32 -0
- package/dist/parser/position-parser.d.ts.map +1 -0
- package/dist/parser/position-parser.js +196 -0
- package/dist/parser/semantic-detector.d.ts.map +1 -1
- package/dist/parser/semantic-detector.js +10 -4
- package/dist/parser/style-parser.d.ts.map +1 -1
- package/dist/parser/style-parser.js +19 -3
- package/package.json +2 -2
package/dist/parser/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export { parseStyles, parseBackgroundColor, parseBorder, parseBorderRadius, pars
|
|
|
4
4
|
export type { IStyleProperties } from './style-parser.js';
|
|
5
5
|
export { parseTextStyles, extractTextContent } from './text-parser.js';
|
|
6
6
|
export type { ITextProperties } from './text-parser.js';
|
|
7
|
+
export { parsePosition, parseContainerPosition, hasAutoLayout } from './position-parser.js';
|
|
8
|
+
export type { IPositionProperties } from './position-parser.js';
|
|
7
9
|
export { parseNode, parseNodes, parseFrameName } from './node-parser.js';
|
|
8
10
|
export type { IParseOptions } from './node-parser.js';
|
|
9
11
|
export { detectSemanticTag, normalizeName, getSemanticAttributes } from './semantic-detector.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/parser/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AACpF,YAAY,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,WAAW,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACtH,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACvE,YAAY,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACzE,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/parser/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AACpF,YAAY,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,WAAW,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACtH,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACvE,YAAY,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC5F,YAAY,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACzE,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/parser/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { parseLayout, parseSizing, parseLayoutAndSizing } from './layout-parser.js';
|
|
2
2
|
export { parseStyles, parseBackgroundColor, parseBorder, parseBorderRadius, parseBoxShadow } from './style-parser.js';
|
|
3
3
|
export { parseTextStyles, extractTextContent } from './text-parser.js';
|
|
4
|
+
export { parsePosition, parseContainerPosition, hasAutoLayout } from './position-parser.js';
|
|
4
5
|
export { parseNode, parseNodes, parseFrameName } from './node-parser.js';
|
|
5
6
|
export { detectSemanticTag, normalizeName, getSemanticAttributes } from './semantic-detector.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layout-parser.d.ts","sourceRoot":"","sources":["../../src/parser/layout-parser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"layout-parser.d.ts","sourceRoot":"","sources":["../../src/parser/layout-parser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AA4BrE,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,aAAa,CAAC,EAAE,cAAc,CAAC;IAC/B,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,iBAAiB,CA2F/D;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,iBAAiB,CAoE/D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,EAAE,CASvE"}
|
|
@@ -3,6 +3,29 @@
|
|
|
3
3
|
* Converte AutoLayout do Figma para propriedades CSS flexbox
|
|
4
4
|
*/
|
|
5
5
|
import { findGapToken, generatePaddingToken } from '@promptui-lib/core';
|
|
6
|
+
/**
|
|
7
|
+
* Arredonda valores de pixel para evitar decimais desnecessários
|
|
8
|
+
* Valores inteiros ou próximos de .5 são preservados
|
|
9
|
+
*/
|
|
10
|
+
function roundPixelValue(value) {
|
|
11
|
+
// Se é inteiro ou muito próximo de inteiro, retorna inteiro
|
|
12
|
+
if (Math.abs(value - Math.round(value)) < 0.01) {
|
|
13
|
+
return Math.round(value);
|
|
14
|
+
}
|
|
15
|
+
// Arredonda para 1 casa decimal no máximo
|
|
16
|
+
return Math.round(value * 10) / 10;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Formata valor de pixel arredondado
|
|
20
|
+
*/
|
|
21
|
+
function formatPixels(value) {
|
|
22
|
+
const rounded = roundPixelValue(value);
|
|
23
|
+
// Se é inteiro, não mostra decimal
|
|
24
|
+
if (Number.isInteger(rounded)) {
|
|
25
|
+
return `${rounded}px`;
|
|
26
|
+
}
|
|
27
|
+
return `${rounded}px`;
|
|
28
|
+
}
|
|
6
29
|
/**
|
|
7
30
|
* Parseia AutoLayout de um node
|
|
8
31
|
*/
|
|
@@ -103,12 +126,19 @@ export function parseSizing(node) {
|
|
|
103
126
|
if (node.absoluteBoundingBox?.width) {
|
|
104
127
|
properties.width = {
|
|
105
128
|
property: 'width',
|
|
106
|
-
value:
|
|
129
|
+
value: formatPixels(node.absoluteBoundingBox.width),
|
|
107
130
|
};
|
|
108
131
|
}
|
|
109
132
|
break;
|
|
110
133
|
}
|
|
111
134
|
}
|
|
135
|
+
else if (node.absoluteBoundingBox?.width) {
|
|
136
|
+
// Fallback: Se não tem layoutSizing mas tem dimensões, usa o tamanho absoluto
|
|
137
|
+
properties.width = {
|
|
138
|
+
property: 'width',
|
|
139
|
+
value: formatPixels(node.absoluteBoundingBox.width),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
112
142
|
// Height
|
|
113
143
|
if (node.layoutSizingVertical) {
|
|
114
144
|
switch (node.layoutSizingVertical) {
|
|
@@ -128,12 +158,19 @@ export function parseSizing(node) {
|
|
|
128
158
|
if (node.absoluteBoundingBox?.height) {
|
|
129
159
|
properties.height = {
|
|
130
160
|
property: 'height',
|
|
131
|
-
value:
|
|
161
|
+
value: formatPixels(node.absoluteBoundingBox.height),
|
|
132
162
|
};
|
|
133
163
|
}
|
|
134
164
|
break;
|
|
135
165
|
}
|
|
136
166
|
}
|
|
167
|
+
else if (node.absoluteBoundingBox?.height) {
|
|
168
|
+
// Fallback: Se não tem layoutSizing mas tem dimensões, usa o tamanho absoluto
|
|
169
|
+
properties.height = {
|
|
170
|
+
property: 'height',
|
|
171
|
+
value: formatPixels(node.absoluteBoundingBox.height),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
137
174
|
return properties;
|
|
138
175
|
}
|
|
139
176
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-parser.d.ts","sourceRoot":"","sources":["../../src/parser/node-parser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,UAAU,EACV,aAAa,
|
|
1
|
+
{"version":3,"file":"node-parser.d.ts","sourceRoot":"","sources":["../../src/parser/node-parser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,UAAU,EACV,aAAa,EAMb,cAAc,EACd,gBAAgB,EACjB,MAAM,oBAAoB,CAAC;AAiB5B,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAwB7D;AAkND;;GAEG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,UAAU,EAChB,OAAO,GAAE,aAAkB,GAC1B,aAAa,CA6Cf;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,UAAU,EAAE,EACnB,OAAO,GAAE,aAAkB,GAC1B,aAAa,EAAE,CAEjB"}
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
* Node Parser
|
|
3
3
|
* Parser principal que converte node do Figma em AST do componente
|
|
4
4
|
*/
|
|
5
|
-
import { generateComponentName, generateFileName, generateBEMBlock, generateBEMElement, classifyComponent, extractBaseName, extractVariants, } from '@promptui-lib/core';
|
|
6
|
-
import { parseLayoutAndSizing } from './layout-parser.js';
|
|
5
|
+
import { generateComponentName, generateFileName, generateBEMBlock, generateBEMElement, classifyComponent, extractBaseName, extractVariants, isComponentNode, } from '@promptui-lib/core';
|
|
6
|
+
import { parseLayoutAndSizing, parseSizing } from './layout-parser.js';
|
|
7
7
|
import { parseStyles } from './style-parser.js';
|
|
8
8
|
import { parseTextStyles } from './text-parser.js';
|
|
9
9
|
import { detectSemanticTag, normalizeName, getSemanticAttributes } from './semantic-detector.js';
|
|
10
|
+
import { parsePosition, parseContainerPosition } from './position-parser.js';
|
|
10
11
|
/**
|
|
11
12
|
* Parseia o nome do frame para extrair informações
|
|
12
13
|
*/
|
|
@@ -33,6 +34,7 @@ export function parseFrameName(name) {
|
|
|
33
34
|
}
|
|
34
35
|
/**
|
|
35
36
|
* Converte um node em elemento JSX
|
|
37
|
+
* Se o filho for um componente (marcado com #), retorna IJSXComponent em vez de JSX inline
|
|
36
38
|
*/
|
|
37
39
|
function nodeToJSX(node, blockName, depth = 0, parentNode) {
|
|
38
40
|
const isRoot = depth === 0;
|
|
@@ -57,7 +59,21 @@ function nodeToJSX(node, blockName, depth = 0, parentNode) {
|
|
|
57
59
|
if (child.visible === false || child.name.startsWith('_')) {
|
|
58
60
|
continue;
|
|
59
61
|
}
|
|
60
|
-
|
|
62
|
+
// Se o filho é um componente (marcado com #), cria referência ao componente
|
|
63
|
+
if (isComponentNode(child.name)) {
|
|
64
|
+
const childComponentName = generateComponentName(child.name);
|
|
65
|
+
const childFileName = generateFileName(childComponentName);
|
|
66
|
+
const childLayer = classifyComponent(child);
|
|
67
|
+
children.push({
|
|
68
|
+
type: 'component',
|
|
69
|
+
componentName: childComponentName,
|
|
70
|
+
layer: childLayer,
|
|
71
|
+
fileName: childFileName,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
children.push(nodeToJSX(child, blockName, depth + 1, node));
|
|
76
|
+
}
|
|
61
77
|
}
|
|
62
78
|
}
|
|
63
79
|
// Tags self-closing
|
|
@@ -75,7 +91,7 @@ function nodeToJSX(node, blockName, depth = 0, parentNode) {
|
|
|
75
91
|
/**
|
|
76
92
|
* Coleta estilos de um node recursivamente
|
|
77
93
|
*/
|
|
78
|
-
function collectStyles(node, blockName, depth = 0) {
|
|
94
|
+
function collectStyles(node, blockName, depth = 0, parentNode) {
|
|
79
95
|
const blocks = [];
|
|
80
96
|
const isRoot = depth === 0;
|
|
81
97
|
// Usa nome normalizado (traduzido)
|
|
@@ -86,9 +102,41 @@ function collectStyles(node, blockName, depth = 0) {
|
|
|
86
102
|
: `.${generateBEMElement(blockName, elementName)}`;
|
|
87
103
|
// Coleta propriedades deste node
|
|
88
104
|
const properties = [];
|
|
89
|
-
// Layout
|
|
105
|
+
// Layout e sizing
|
|
106
|
+
// Para FRAME, COMPONENT, INSTANCE: extrai layout completo (flex, gap, etc)
|
|
107
|
+
// Para GROUP: extrai sizing e position: relative (container sem flex)
|
|
108
|
+
// Para outros tipos (RECTANGLE, etc): extrai apenas sizing (width, height)
|
|
90
109
|
if (node.type === 'FRAME' || node.type === 'COMPONENT' || node.type === 'INSTANCE') {
|
|
91
110
|
properties.push(...parseLayoutAndSizing(node));
|
|
111
|
+
// Se é um container sem Auto Layout, adiciona position: relative
|
|
112
|
+
const containerPos = parseContainerPosition(node);
|
|
113
|
+
if (containerPos) {
|
|
114
|
+
properties.push(containerPos);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else if (node.type === 'GROUP') {
|
|
118
|
+
// GROUPs são containers visuais - precisam de position: relative e sizing
|
|
119
|
+
const sizingProps = parseSizing(node);
|
|
120
|
+
const sizingArray = Object.values(sizingProps).filter((p) => p !== undefined);
|
|
121
|
+
properties.push(...sizingArray);
|
|
122
|
+
// GROUP sempre precisa de position: relative se tiver filhos
|
|
123
|
+
if (node.children && node.children.length > 0) {
|
|
124
|
+
properties.push({
|
|
125
|
+
property: 'position',
|
|
126
|
+
value: 'relative',
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else if (node.type !== 'TEXT' && node.absoluteBoundingBox) {
|
|
131
|
+
// Para elementos não-texto com dimensões, extrai apenas sizing
|
|
132
|
+
const sizingProps = parseSizing(node);
|
|
133
|
+
const sizingArray = Object.values(sizingProps).filter((p) => p !== undefined);
|
|
134
|
+
properties.push(...sizingArray);
|
|
135
|
+
}
|
|
136
|
+
// Posicionamento absoluto (quando pai não tem Auto Layout)
|
|
137
|
+
if (parentNode && !isRoot) {
|
|
138
|
+
const positionProps = parsePosition(node, parentNode);
|
|
139
|
+
properties.push(...positionProps);
|
|
92
140
|
}
|
|
93
141
|
// Estilos visuais
|
|
94
142
|
properties.push(...parseStyles(node));
|
|
@@ -103,13 +151,18 @@ function collectStyles(node, blockName, depth = 0) {
|
|
|
103
151
|
properties,
|
|
104
152
|
});
|
|
105
153
|
}
|
|
106
|
-
// Processa filhos
|
|
154
|
+
// Processa filhos (passa o node atual como pai)
|
|
155
|
+
// Ignora filhos marcados com # pois são componentes com seus próprios estilos
|
|
107
156
|
if (node.children) {
|
|
108
157
|
for (const child of node.children) {
|
|
109
158
|
if (child.visible === false || child.name.startsWith('_')) {
|
|
110
159
|
continue;
|
|
111
160
|
}
|
|
112
|
-
|
|
161
|
+
// Ignora componentes filhos - eles têm seus próprios estilos
|
|
162
|
+
if (isComponentNode(child.name)) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
blocks.push(...collectStyles(child, blockName, depth + 1, node));
|
|
113
166
|
}
|
|
114
167
|
}
|
|
115
168
|
return blocks;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Position Parser
|
|
3
|
+
* Converte posicionamento e constraints do Figma para CSS
|
|
4
|
+
* Usado quando elementos não estão dentro de Auto Layout
|
|
5
|
+
*/
|
|
6
|
+
import type { IFigmaNode, IStyleProperty } from '@promptui-lib/core';
|
|
7
|
+
export interface IPositionProperties {
|
|
8
|
+
position?: IStyleProperty;
|
|
9
|
+
top?: IStyleProperty;
|
|
10
|
+
right?: IStyleProperty;
|
|
11
|
+
bottom?: IStyleProperty;
|
|
12
|
+
left?: IStyleProperty;
|
|
13
|
+
transform?: IStyleProperty;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Verifica se um node pai tem Auto Layout
|
|
17
|
+
*/
|
|
18
|
+
export declare function hasAutoLayout(node: IFigmaNode): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Parseia constraints e posição de um node
|
|
21
|
+
*/
|
|
22
|
+
export declare function parsePosition(node: IFigmaNode, parentNode?: IFigmaNode): IStyleProperty[];
|
|
23
|
+
/**
|
|
24
|
+
* Verifica se o pai precisa de position: relative
|
|
25
|
+
* (necessário quando tem filhos com position: absolute)
|
|
26
|
+
*/
|
|
27
|
+
export declare function needsRelativePosition(node: IFigmaNode): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Parseia position: relative para containers sem Auto Layout
|
|
30
|
+
*/
|
|
31
|
+
export declare function parseContainerPosition(node: IFigmaNode): IStyleProperty | null;
|
|
32
|
+
//# sourceMappingURL=position-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"position-parser.d.ts","sourceRoot":"","sources":["../../src/parser/position-parser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAgB,MAAM,oBAAoB,CAAC;AAEnF,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,SAAS,CAAC,EAAE,cAAc,CAAC;CAC5B;AAoBD;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAEvD;AAwBD;;GAEG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,UAAU,EAChB,UAAU,CAAC,EAAE,UAAU,GACtB,cAAc,EAAE,CAmIlB;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAM/D;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAQ9E"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Position Parser
|
|
3
|
+
* Converte posicionamento e constraints do Figma para CSS
|
|
4
|
+
* Usado quando elementos não estão dentro de Auto Layout
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Arredonda valores de pixel para evitar decimais desnecessários
|
|
8
|
+
*/
|
|
9
|
+
function roundPixelValue(value) {
|
|
10
|
+
if (Math.abs(value - Math.round(value)) < 0.01) {
|
|
11
|
+
return Math.round(value);
|
|
12
|
+
}
|
|
13
|
+
return Math.round(value * 10) / 10;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Formata valor de pixel arredondado
|
|
17
|
+
*/
|
|
18
|
+
function formatPixels(value) {
|
|
19
|
+
const rounded = roundPixelValue(value);
|
|
20
|
+
return `${rounded}px`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Verifica se um node pai tem Auto Layout
|
|
24
|
+
*/
|
|
25
|
+
export function hasAutoLayout(node) {
|
|
26
|
+
return node.layoutMode !== undefined && node.layoutMode !== 'NONE';
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Calcula a posição relativa de um filho em relação ao pai
|
|
30
|
+
*/
|
|
31
|
+
function calculateRelativePosition(child, parent) {
|
|
32
|
+
if (!child.absoluteBoundingBox || !parent.absoluteBoundingBox) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const childBox = child.absoluteBoundingBox;
|
|
36
|
+
const parentBox = parent.absoluteBoundingBox;
|
|
37
|
+
return {
|
|
38
|
+
top: childBox.y - parentBox.y,
|
|
39
|
+
left: childBox.x - parentBox.x,
|
|
40
|
+
right: parentBox.x + parentBox.width - (childBox.x + childBox.width),
|
|
41
|
+
bottom: parentBox.y + parentBox.height - (childBox.y + childBox.height),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Parseia constraints e posição de um node
|
|
46
|
+
*/
|
|
47
|
+
export function parsePosition(node, parentNode) {
|
|
48
|
+
const properties = [];
|
|
49
|
+
// Se não tem pai ou o pai tem Auto Layout, não precisa de posicionamento absoluto
|
|
50
|
+
if (!parentNode || hasAutoLayout(parentNode)) {
|
|
51
|
+
return properties;
|
|
52
|
+
}
|
|
53
|
+
// Calcula posição relativa
|
|
54
|
+
const relPos = calculateRelativePosition(node, parentNode);
|
|
55
|
+
if (!relPos) {
|
|
56
|
+
return properties;
|
|
57
|
+
}
|
|
58
|
+
const constraints = node.constraints;
|
|
59
|
+
// Se é um elemento dentro de um container sem Auto Layout, usa posição absoluta
|
|
60
|
+
properties.push({
|
|
61
|
+
property: 'position',
|
|
62
|
+
value: 'absolute',
|
|
63
|
+
});
|
|
64
|
+
// Aplica constraints ou usa posição padrão (top-left)
|
|
65
|
+
if (constraints) {
|
|
66
|
+
// Horizontal constraints
|
|
67
|
+
switch (constraints.horizontal) {
|
|
68
|
+
case 'LEFT':
|
|
69
|
+
properties.push({
|
|
70
|
+
property: 'left',
|
|
71
|
+
value: formatPixels(relPos.left),
|
|
72
|
+
});
|
|
73
|
+
break;
|
|
74
|
+
case 'RIGHT':
|
|
75
|
+
properties.push({
|
|
76
|
+
property: 'right',
|
|
77
|
+
value: formatPixels(relPos.right),
|
|
78
|
+
});
|
|
79
|
+
break;
|
|
80
|
+
case 'CENTER':
|
|
81
|
+
properties.push({
|
|
82
|
+
property: 'left',
|
|
83
|
+
value: '50%',
|
|
84
|
+
});
|
|
85
|
+
properties.push({
|
|
86
|
+
property: 'transform',
|
|
87
|
+
value: 'translateX(-50%)',
|
|
88
|
+
});
|
|
89
|
+
break;
|
|
90
|
+
case 'LEFT_RIGHT':
|
|
91
|
+
properties.push({
|
|
92
|
+
property: 'left',
|
|
93
|
+
value: formatPixels(relPos.left),
|
|
94
|
+
});
|
|
95
|
+
properties.push({
|
|
96
|
+
property: 'right',
|
|
97
|
+
value: formatPixels(relPos.right),
|
|
98
|
+
});
|
|
99
|
+
break;
|
|
100
|
+
case 'SCALE':
|
|
101
|
+
// Usa porcentagem
|
|
102
|
+
const parentWidth = parentNode.absoluteBoundingBox?.width ?? 1;
|
|
103
|
+
const leftPercent = (relPos.left / parentWidth) * 100;
|
|
104
|
+
properties.push({
|
|
105
|
+
property: 'left',
|
|
106
|
+
value: `${roundPixelValue(leftPercent)}%`,
|
|
107
|
+
});
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
// Vertical constraints
|
|
111
|
+
switch (constraints.vertical) {
|
|
112
|
+
case 'TOP':
|
|
113
|
+
properties.push({
|
|
114
|
+
property: 'top',
|
|
115
|
+
value: formatPixels(relPos.top),
|
|
116
|
+
});
|
|
117
|
+
break;
|
|
118
|
+
case 'BOTTOM':
|
|
119
|
+
properties.push({
|
|
120
|
+
property: 'bottom',
|
|
121
|
+
value: formatPixels(relPos.bottom),
|
|
122
|
+
});
|
|
123
|
+
break;
|
|
124
|
+
case 'CENTER':
|
|
125
|
+
// Se já tem transform de X, combina
|
|
126
|
+
const existingTransform = properties.find(p => p.property === 'transform');
|
|
127
|
+
if (existingTransform) {
|
|
128
|
+
existingTransform.value = 'translate(-50%, -50%)';
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
properties.push({
|
|
132
|
+
property: 'transform',
|
|
133
|
+
value: 'translateY(-50%)',
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
properties.push({
|
|
137
|
+
property: 'top',
|
|
138
|
+
value: '50%',
|
|
139
|
+
});
|
|
140
|
+
break;
|
|
141
|
+
case 'TOP_BOTTOM':
|
|
142
|
+
properties.push({
|
|
143
|
+
property: 'top',
|
|
144
|
+
value: formatPixels(relPos.top),
|
|
145
|
+
});
|
|
146
|
+
properties.push({
|
|
147
|
+
property: 'bottom',
|
|
148
|
+
value: formatPixels(relPos.bottom),
|
|
149
|
+
});
|
|
150
|
+
break;
|
|
151
|
+
case 'SCALE':
|
|
152
|
+
const parentHeight = parentNode.absoluteBoundingBox?.height ?? 1;
|
|
153
|
+
const topPercent = (relPos.top / parentHeight) * 100;
|
|
154
|
+
properties.push({
|
|
155
|
+
property: 'top',
|
|
156
|
+
value: `${roundPixelValue(topPercent)}%`,
|
|
157
|
+
});
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
// Sem constraints, usa posição top-left padrão
|
|
163
|
+
properties.push({
|
|
164
|
+
property: 'top',
|
|
165
|
+
value: formatPixels(relPos.top),
|
|
166
|
+
});
|
|
167
|
+
properties.push({
|
|
168
|
+
property: 'left',
|
|
169
|
+
value: formatPixels(relPos.left),
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return properties;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Verifica se o pai precisa de position: relative
|
|
176
|
+
* (necessário quando tem filhos com position: absolute)
|
|
177
|
+
*/
|
|
178
|
+
export function needsRelativePosition(node) {
|
|
179
|
+
// Se não tem Auto Layout e tem filhos, precisa de relative
|
|
180
|
+
if (!hasAutoLayout(node) && node.children && node.children.length > 0) {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Parseia position: relative para containers sem Auto Layout
|
|
187
|
+
*/
|
|
188
|
+
export function parseContainerPosition(node) {
|
|
189
|
+
if (needsRelativePosition(node)) {
|
|
190
|
+
return {
|
|
191
|
+
property: 'position',
|
|
192
|
+
value: 'relative',
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"semantic-detector.d.ts","sourceRoot":"","sources":["../../src/parser/semantic-detector.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAmQrD;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAalD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,UAAU,EAChB,UAAU,CAAC,EAAE,UAAU,EACvB,KAAK,GAAE,MAAU,GAChB,MAAM,CAmKR;AA4JD;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,MAAM,EACX,cAAc,EAAE,MAAM,GACrB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"semantic-detector.d.ts","sourceRoot":"","sources":["../../src/parser/semantic-detector.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAmQrD;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAalD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,UAAU,EAChB,UAAU,CAAC,EAAE,UAAU,EACvB,KAAK,GAAE,MAAU,GAChB,MAAM,CAmKR;AA4JD;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,MAAM,EACX,cAAc,EAAE,MAAM,GACrB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAwKxB"}
|
|
@@ -513,11 +513,17 @@ function hasInputChild(node) {
|
|
|
513
513
|
*/
|
|
514
514
|
export function getSemanticAttributes(node, tag, normalizedName) {
|
|
515
515
|
const attrs = {};
|
|
516
|
-
// Links -
|
|
516
|
+
// Links - adiciona href placeholder para evitar warnings de a11y
|
|
517
517
|
// O desenvolvedor deve preencher o href correto
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
518
|
+
if (tag === 'a') {
|
|
519
|
+
// Gera um href placeholder baseado no nome do link
|
|
520
|
+
const hrefSlug = normalizedName
|
|
521
|
+
.replace(/link$/i, '')
|
|
522
|
+
.replace(/[-\s]+/g, '-')
|
|
523
|
+
.replace(/^-|-$/g, '')
|
|
524
|
+
.toLowerCase();
|
|
525
|
+
attrs['href'] = hrefSlug ? `/${hrefSlug}` : '/';
|
|
526
|
+
}
|
|
521
527
|
// Inputs
|
|
522
528
|
if (tag === 'input') {
|
|
523
529
|
// Detecta tipo de input
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"style-parser.d.ts","sourceRoot":"","sources":["../../src/parser/style-parser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,UAAU,EAIV,cAAc,EACf,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"style-parser.d.ts","sourceRoot":"","sources":["../../src/parser/style-parser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,UAAU,EAIV,cAAc,EACf,MAAM,oBAAoB,CAAC;AAmB5B,MAAM,WAAW,gBAAgB;IAC/B,eAAe,CAAC,EAAE,cAAc,CAAC;IACjC,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,YAAY,CAAC,EAAE,cAAc,CAAC;IAC9B,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B;AAWD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CA2B5E;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAwBnE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAyBzE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAuCtE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAWpE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,EAAE,CAmB9D"}
|
|
@@ -3,6 +3,15 @@
|
|
|
3
3
|
* Converte fills, strokes, effects do Figma para CSS
|
|
4
4
|
*/
|
|
5
5
|
import { rgbaToHex, findColorToken, findRadiusToken, findShadowToken, generateBorderRadius, } from '@promptui-lib/core';
|
|
6
|
+
/**
|
|
7
|
+
* Arredonda valores de pixel para evitar decimais desnecessários
|
|
8
|
+
*/
|
|
9
|
+
function roundPixelValue(value) {
|
|
10
|
+
if (Math.abs(value - Math.round(value)) < 0.01) {
|
|
11
|
+
return Math.round(value);
|
|
12
|
+
}
|
|
13
|
+
return Math.round(value * 10) / 10;
|
|
14
|
+
}
|
|
6
15
|
/**
|
|
7
16
|
* Extrai cor de um fill sólido
|
|
8
17
|
*/
|
|
@@ -49,9 +58,10 @@ export function parseBorder(node) {
|
|
|
49
58
|
}
|
|
50
59
|
const hex = rgbaToHex(stroke.color.r, stroke.color.g, stroke.color.b, stroke.opacity ?? 1);
|
|
51
60
|
const token = findColorToken(hex);
|
|
61
|
+
const strokeWidth = roundPixelValue(node.strokeWeight);
|
|
52
62
|
return {
|
|
53
63
|
property: 'border',
|
|
54
|
-
value: `${
|
|
64
|
+
value: `${strokeWidth}px solid ${token ?? hex}`,
|
|
55
65
|
token: token ?? undefined,
|
|
56
66
|
};
|
|
57
67
|
}
|
|
@@ -105,9 +115,13 @@ export function parseBoxShadow(node) {
|
|
|
105
115
|
// Gera box-shadow customizado
|
|
106
116
|
const { offset, radius, spread, color } = shadow;
|
|
107
117
|
const hex = rgbaToHex(color.r, color.g, color.b, color.a);
|
|
118
|
+
const x = roundPixelValue(offset.x);
|
|
119
|
+
const y = roundPixelValue(offset.y);
|
|
120
|
+
const r = roundPixelValue(radius);
|
|
121
|
+
const s = roundPixelValue(spread ?? 0);
|
|
108
122
|
return {
|
|
109
123
|
property: 'box-shadow',
|
|
110
|
-
value: `${
|
|
124
|
+
value: `${x}px ${y}px ${r}px ${s}px ${hex}`,
|
|
111
125
|
};
|
|
112
126
|
}
|
|
113
127
|
/**
|
|
@@ -115,9 +129,11 @@ export function parseBoxShadow(node) {
|
|
|
115
129
|
*/
|
|
116
130
|
export function parseOpacity(node) {
|
|
117
131
|
if (node.opacity !== undefined && node.opacity < 1) {
|
|
132
|
+
// Arredonda para 2 casas decimais
|
|
133
|
+
const rounded = Math.round(node.opacity * 100) / 100;
|
|
118
134
|
return {
|
|
119
135
|
property: 'opacity',
|
|
120
|
-
value: `${
|
|
136
|
+
value: `${rounded}`,
|
|
121
137
|
};
|
|
122
138
|
}
|
|
123
139
|
return null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@promptui-lib/figma-parser",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Figma API client and parser for PromptUI",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"dist"
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@promptui-lib/core": "0.1.
|
|
33
|
+
"@promptui-lib/core": "0.1.11"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/node": "^20.0.0",
|