@promptui-lib/figma-parser 0.1.22 → 0.1.23

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.
@@ -14,6 +14,7 @@ export interface ILayoutProperties {
14
14
  height?: IStyleProperty;
15
15
  flex?: IStyleProperty;
16
16
  flexWrap?: IStyleProperty;
17
+ flexShrink?: IStyleProperty;
17
18
  }
18
19
  /**
19
20
  * Parseia padding de um node (funciona com ou sem Auto Layout)
@@ -26,11 +27,13 @@ export declare function parseLayout(node: IFigmaNode): ILayoutProperties;
26
27
  /**
27
28
  * Parseia sizing de um node
28
29
  * @param isRoot - indica se é o node raiz do componente
30
+ * @param parentHasAutoLayout - indica se o pai tem Auto Layout (para aplicar flex-shrink)
29
31
  */
30
- export declare function parseSizing(node: IFigmaNode, isRoot?: boolean): ILayoutProperties;
32
+ export declare function parseSizing(node: IFigmaNode, isRoot?: boolean, parentHasAutoLayout?: boolean): ILayoutProperties;
31
33
  /**
32
34
  * Combina layout e sizing
33
35
  * @param isRoot - indica se é o node raiz do componente
36
+ * @param parentHasAutoLayout - indica se o pai tem Auto Layout
34
37
  */
35
- export declare function parseLayoutAndSizing(node: IFigmaNode, isRoot?: boolean): IStyleProperty[];
38
+ export declare function parseLayoutAndSizing(node: IFigmaNode, isRoot?: boolean, parentHasAutoLayout?: boolean): IStyleProperty[];
36
39
  //# sourceMappingURL=layout-parser.d.ts.map
@@ -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;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,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAuBpE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,iBAAiB,CA2F/D;AAwBD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,GAAE,OAAe,GAAG,iBAAiB,CA+ExF;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,GAAE,OAAe,GAAG,cAAc,EAAE,CAShG"}
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;IAC1B,UAAU,CAAC,EAAE,cAAc,CAAC;CAC7B;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAuBpE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,iBAAiB,CA2F/D;AAwBD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,GAAE,OAAe,EAAE,mBAAmB,GAAE,OAAe,GAAG,iBAAiB,CA+F9H;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,GAAE,OAAe,EAAE,mBAAmB,GAAE,OAAe,GAAG,cAAc,EAAE,CAStI"}
@@ -141,11 +141,17 @@ function isScreenNode(node) {
141
141
  /**
142
142
  * Parseia sizing de um node
143
143
  * @param isRoot - indica se é o node raiz do componente
144
+ * @param parentHasAutoLayout - indica se o pai tem Auto Layout (para aplicar flex-shrink)
144
145
  */
145
- export function parseSizing(node, isRoot = false) {
146
+ export function parseSizing(node, isRoot = false, parentHasAutoLayout = false) {
146
147
  const properties = {};
147
148
  // Se é o node raiz e é uma tela, usa viewport units
148
149
  const isScreen = isRoot && isScreenNode(node);
150
+ // Verifica se precisa de flex-shrink: 0 (quando tem sizing HUG ou FIXED dentro de flex)
151
+ const needsFlexShrink = parentHasAutoLayout && (node.layoutSizingHorizontal === 'HUG' ||
152
+ node.layoutSizingHorizontal === 'FIXED' ||
153
+ node.layoutSizingVertical === 'HUG' ||
154
+ node.layoutSizingVertical === 'FIXED');
149
155
  // Width
150
156
  if (node.layoutSizingHorizontal) {
151
157
  switch (node.layoutSizingHorizontal) {
@@ -217,15 +223,23 @@ export function parseSizing(node, isRoot = false) {
217
223
  value: 'hidden',
218
224
  };
219
225
  }
226
+ // Adiciona flex-shrink: 0 quando necessário
227
+ if (needsFlexShrink) {
228
+ properties.flexShrink = {
229
+ property: 'flex-shrink',
230
+ value: '0',
231
+ };
232
+ }
220
233
  return properties;
221
234
  }
222
235
  /**
223
236
  * Combina layout e sizing
224
237
  * @param isRoot - indica se é o node raiz do componente
238
+ * @param parentHasAutoLayout - indica se o pai tem Auto Layout
225
239
  */
226
- export function parseLayoutAndSizing(node, isRoot = false) {
240
+ export function parseLayoutAndSizing(node, isRoot = false, parentHasAutoLayout = false) {
227
241
  const layout = parseLayout(node);
228
- const sizing = parseSizing(node, isRoot);
242
+ const sizing = parseSizing(node, isRoot, parentHasAutoLayout);
229
243
  const combined = { ...layout, ...sizing };
230
244
  return Object.values(combined).filter((prop) => prop !== undefined);
231
245
  }
@@ -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,EAOb,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;AAqmBD;;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"}
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,EAOb,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;AAulBD;;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"}
@@ -61,24 +61,10 @@ function isAssetNode(node) {
61
61
  return false;
62
62
  }
63
63
  /**
64
- * Patterns para detectar elementos de formulário simulados
64
+ * Patterns para detectar elementos de formulário com prefixo explícito $tipo:nome
65
+ * O usuário deve marcar explicitamente os elementos para conversão
65
66
  */
66
- const FORM_ELEMENT_PATTERNS = {
67
- // Input de texto (uma linha)
68
- input: /^(input|text-?input|entrada|campo-texto|field-input|input-field)$/i,
69
- // Textarea (múltiplas linhas)
70
- textarea: /^(textarea|text-?area|area-?texto|multiline|multi-line|mensagem-input|message-input)$/i,
71
- // Select/Dropdown
72
- select: /^(select|dropdown|combo-?box|picker|seletor|selecao|lista-selecao|select-input)$/i,
73
- // Checkbox
74
- checkbox: /^(checkbox|check-?box|caixa-?selecao|toggle|switch)$/i,
75
- // Radio button
76
- radio: /^(radio|radio-?button|opcao-?radio|radio-?input)$/i,
77
- // Button
78
- button: /^(button|btn|botao|submit|cancel|action|cta)$/i,
79
- // Label
80
- label: /^(label|rotulo|field-?label|input-?label)$/i,
81
- };
67
+ const FORM_ELEMENT_PREFIX_PATTERN = /^\$(input|textarea|select|checkbox|radio|button|label):/i;
82
68
  /**
83
69
  * Verifica se um node é um frame válido (FRAME, INSTANCE ou COMPONENT)
84
70
  */
@@ -135,51 +121,44 @@ function extractAllTexts(node) {
135
121
  }
136
122
  /**
137
123
  * Detecta qual tipo de elemento de formulário um node representa
124
+ *
125
+ * IMPORTANTE: A detecção só acontece quando o nome tem prefixo explícito:
126
+ * $input:nome, $textarea:nome, $select:nome, etc.
127
+ *
128
+ * Isso preserva a estrutura visual do Figma por padrão.
138
129
  */
139
130
  function detectSimulatedFormElement(node) {
140
131
  if (!isValidFrameNode(node)) {
141
132
  return null;
142
133
  }
143
- const name = node.name.toLowerCase().trim();
144
- // Verifica cada pattern
145
- if (FORM_ELEMENT_PATTERNS.textarea.test(name)) {
146
- return 'textarea';
147
- }
148
- if (FORM_ELEMENT_PATTERNS.select.test(name)) {
149
- return 'select';
150
- }
151
- if (FORM_ELEMENT_PATTERNS.checkbox.test(name)) {
152
- return 'checkbox';
153
- }
154
- if (FORM_ELEMENT_PATTERNS.radio.test(name)) {
155
- return 'radio';
156
- }
157
- if (FORM_ELEMENT_PATTERNS.button.test(name)) {
158
- // Só é button se tiver texto dentro
159
- if (hasTextChild(node)) {
160
- return 'button';
161
- }
162
- }
163
- if (FORM_ELEMENT_PATTERNS.label.test(name)) {
164
- // Só é label se tiver texto direto como filho
165
- if (node.children?.some(child => child.type === 'TEXT')) {
166
- return 'label';
167
- }
168
- }
169
- if (FORM_ELEMENT_PATTERNS.input.test(name)) {
170
- // Só é input se tiver texto dentro (placeholder)
171
- if (hasTextChild(node)) {
172
- return 'input';
173
- }
134
+ const name = node.name.trim();
135
+ // Verifica se tem prefixo explícito $tipo:nome
136
+ const match = name.match(FORM_ELEMENT_PREFIX_PATTERN);
137
+ if (!match) {
138
+ // Sem prefixo explícito = não é um form element
139
+ return null;
174
140
  }
175
- return null;
141
+ const elementType = match[1].toLowerCase();
142
+ return elementType;
143
+ }
144
+ /**
145
+ * Extrai o nome limpo do node (remove prefixos especiais como $input:, #, etc.)
146
+ */
147
+ function extractCleanName(name) {
148
+ // Remove prefixo de form element $tipo:
149
+ let clean = name.replace(FORM_ELEMENT_PREFIX_PATTERN, '');
150
+ // Remove prefixo de exportação #
151
+ clean = clean.replace(/^#/, '');
152
+ // Remove prefixo interno _
153
+ clean = clean.replace(/^_/, '');
154
+ return clean.trim();
176
155
  }
177
156
  /**
178
157
  * Detecta o tipo específico de input baseado no contexto
179
158
  */
180
159
  function detectInputType(node, parentNode) {
181
- const nodeName = node.name.toLowerCase();
182
- const parentName = parentNode?.name.toLowerCase() || '';
160
+ const nodeName = extractCleanName(node.name).toLowerCase();
161
+ const parentName = parentNode ? extractCleanName(parentNode.name).toLowerCase() : '';
183
162
  const combinedContext = `${parentName} ${nodeName}`;
184
163
  // Detecta por palavras-chave no contexto
185
164
  if (combinedContext.includes('senha') || combinedContext.includes('password')) {
@@ -209,7 +188,7 @@ function detectInputType(node, parentNode) {
209
188
  * Detecta o tipo de button baseado no contexto
210
189
  */
211
190
  function detectButtonType(node) {
212
- const name = node.name.toLowerCase();
191
+ const name = extractCleanName(node.name).toLowerCase();
213
192
  const text = extractFirstText(node).toLowerCase();
214
193
  const context = `${name} ${text}`;
215
194
  if (context.includes('submit') || context.includes('enviar') || context.includes('entrar') || context.includes('login') || context.includes('cadastrar')) {
@@ -430,12 +409,14 @@ function collectStyles(node, blockName, depth = 0, parentNode) {
430
409
  : `.${generateBEMElement(blockName, elementName)}`;
431
410
  // Coleta propriedades deste node
432
411
  const properties = [];
412
+ // Verifica se o pai tem Auto Layout (para aplicar flex-shrink nos filhos)
413
+ const parentHasAutoLayoutMode = parentNode ? hasAutoLayout(parentNode) : false;
433
414
  // Layout e sizing
434
415
  // Para FRAME, COMPONENT, INSTANCE: extrai layout completo (flex, gap, etc)
435
416
  // Para GROUP: extrai sizing e position: relative (container sem flex)
436
417
  // Para outros tipos (RECTANGLE, etc): extrai apenas sizing (width, height)
437
418
  if (node.type === 'FRAME' || node.type === 'COMPONENT' || node.type === 'INSTANCE') {
438
- properties.push(...parseLayoutAndSizing(node, isRoot));
419
+ properties.push(...parseLayoutAndSizing(node, isRoot, parentHasAutoLayoutMode));
439
420
  // Se é um container sem Auto Layout, adiciona position: relative
440
421
  const containerPos = parseContainerPosition(node);
441
422
  if (containerPos) {
@@ -452,7 +433,7 @@ function collectStyles(node, blockName, depth = 0, parentNode) {
452
433
  }
453
434
  else if (node.type === 'GROUP') {
454
435
  // GROUPs são containers visuais - precisam de position: relative e sizing
455
- const sizingProps = parseSizing(node);
436
+ const sizingProps = parseSizing(node, false, parentHasAutoLayoutMode);
456
437
  const sizingArray = Object.values(sizingProps).filter((p) => p !== undefined);
457
438
  properties.push(...sizingArray);
458
439
  // GROUP sempre precisa de position: relative se tiver filhos
@@ -465,7 +446,7 @@ function collectStyles(node, blockName, depth = 0, parentNode) {
465
446
  }
466
447
  else if (node.type !== 'TEXT' && node.absoluteBoundingBox) {
467
448
  // Para elementos não-texto com dimensões, extrai apenas sizing
468
- const sizingProps = parseSizing(node);
449
+ const sizingProps = parseSizing(node, false, parentHasAutoLayoutMode);
469
450
  const sizingArray = Object.values(sizingProps).filter((p) => p !== undefined);
470
451
  properties.push(...sizingArray);
471
452
  }
@@ -1 +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;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAEhE;AAwBD;;GAEG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,UAAU,EAChB,UAAU,CAAC,EAAE,UAAU,GACtB,cAAc,EAAE,CA6IlB;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAa/D;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAQ9E"}
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;AA6BD;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAEvD;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAEhE;AAwBD;;GAEG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,UAAU,EAChB,UAAU,CAAC,EAAE,UAAU,GACtB,cAAc,EAAE,CA6IlB;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAa/D;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAQ9E"}
@@ -13,12 +13,20 @@ function roundPixelValue(value) {
13
13
  return Math.round(value * 10) / 10;
14
14
  }
15
15
  /**
16
- * Formata valor de pixel arredondado
16
+ * Formata valor de pixel arredondado (suporta valores negativos)
17
17
  */
18
18
  function formatPixels(value) {
19
19
  const rounded = roundPixelValue(value);
20
20
  return `${rounded}px`;
21
21
  }
22
+ /**
23
+ * Formata valor de pixel para CSS, incluindo valores negativos
24
+ * Valores negativos são importantes para elementos que "vazam" do container
25
+ */
26
+ function formatPixelsWithNegative(value) {
27
+ const rounded = roundPixelValue(value);
28
+ return `${rounded}px`;
29
+ }
22
30
  /**
23
31
  * Verifica se um node pai tem Auto Layout
24
32
  */
@@ -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;AAkRrD;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAalD;AA0DD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,UAAU,EAChB,UAAU,CAAC,EAAE,UAAU,EACvB,MAAM,GAAE,MAAU,EAClB,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GACrB,MAAM,CA4MR;AA+ID;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,MAAM,EACX,cAAc,EAAE,MAAM,GACrB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAgLxB"}
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;AAuRrD;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAalD;AA0DD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,UAAU,EAChB,UAAU,CAAC,EAAE,UAAU,EACvB,MAAM,GAAE,MAAU,EAClB,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GACrB,MAAM,CA4MR;AAgJD;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,MAAM,EACX,cAAc,EAAE,MAAM,GACrB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAgLxB"}
@@ -184,13 +184,18 @@ const SEMANTIC_PATTERNS = {
184
184
  // Links
185
185
  link: /(link|anchor|href|ancora|forgot|esqueci|saiba-mais|ver-mais|leia-mais)/i,
186
186
  // Formulários
187
+ // NOTA: Patterns de form elements são desativados por padrão para preservar a estrutura visual.
188
+ // Use prefixo $input:, $label:, etc. no Figma para ativar a conversão.
189
+ // Os patterns abaixo são mantidos apenas para elementos claramente marcados.
187
190
  form: /^(form|formulario|login-form|signup-form|contact-form|search-form)/i,
188
- input: /^(input|field|textfield|entrada|campo|text-input)/i,
189
- label: /^(label|rotulo|field-label|input-label)/i,
190
- select: /^(select|dropdown|picker|seletor|selecao|combobox)/i,
191
- textarea: /^(textarea|text-area|area-texto|multiline)/i,
192
- checkbox: /^(checkbox|check-box|caixa-selecao|toggle)/i,
193
- radio: /^(radio|radio-button|opcao-radio)/i,
191
+ // input/label/select/etc NÃO são mais detectados automaticamente
192
+ // para evitar converter estruturas visuais em elementos HTML
193
+ input: /^(\$input:|never-match-automatically)/i, // Desativado - use $input:nome
194
+ label: /^(\$label:|never-match-automatically)/i, // Desativado - use $label:nome
195
+ select: /^(\$select:|never-match-automatically)/i, // Desativado - use $select:nome
196
+ textarea: /^(\$textarea:|never-match-automatically)/i, // Desativado - use $textarea:nome
197
+ checkbox: /^(\$checkbox:|never-match-automatically)/i, // Desativado - use $checkbox:nome
198
+ radio: /^(\$radio:|never-match-automatically)/i, // Desativado - use $radio:nome
194
199
  fieldset: /^(fieldset|field-group|grupo-campos)/i,
195
200
  // Navegação - expandido para mais variações
196
201
  nav: /^(nav|navigation|navbar|menu|navegacao|breadcrumb|tabs|pagination|nav-bar|top-nav|main-nav|side-nav)/i,
@@ -486,8 +491,9 @@ function detectTextTag(node, parentNode, normalizedName) {
486
491
  if (fontSize >= 24 && fontWeight >= 600) {
487
492
  return getHeadingLevel(fontSize);
488
493
  }
489
- // Labels (geralmente associados a inputs)
490
- if (SEMANTIC_PATTERNS.label.test(name)) {
494
+ // Labels - detecta com prefixo explícito $label:
495
+ // NÃO detecta automaticamente para preservar estrutura visual
496
+ if (name.startsWith('$label:')) {
491
497
  return 'label';
492
498
  }
493
499
  // Links - mas não se o pai já é um link (evita <a> aninhado)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promptui-lib/figma-parser",
3
- "version": "0.1.22",
3
+ "version": "0.1.23",
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.22"
33
+ "@promptui-lib/core": "0.1.23"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^20.0.0",