@wrrdev/fenix-ui 1.1.10 → 1.1.12

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 (39) hide show
  1. package/README.md +20 -2
  2. package/dist/assets/{font-CF0TXsAP.js → font-DPeWTz-n.js} +58 -13
  3. package/dist/assets/font-DPeWTz-n.js.map +1 -0
  4. package/dist/components/breadcrumb/breadcrumb.d.ts +4 -0
  5. package/dist/components/breadcrumb/breadcrumb.d.ts.map +1 -1
  6. package/dist/components/breadcrumb/breadcrumb.js +12 -2
  7. package/dist/components/breadcrumb/breadcrumb.js.map +1 -1
  8. package/dist/components/table/table.d.ts +18 -1
  9. package/dist/components/table/table.d.ts.map +1 -1
  10. package/dist/components/table/table.js +120 -23
  11. package/dist/components/table/table.js.map +1 -1
  12. package/dist/components/toast/toast.d.ts +8 -0
  13. package/dist/components/toast/toast.d.ts.map +1 -1
  14. package/dist/components/toast/toast.js +13 -0
  15. package/dist/components/toast/toast.js.map +1 -1
  16. package/dist/components/tooltip/directive.d.ts +8 -0
  17. package/dist/components/tooltip/directive.d.ts.map +1 -1
  18. package/dist/components/tooltip/directive.js +59 -18
  19. package/dist/components/tooltip/directive.js.map +1 -1
  20. package/dist/core/jsx.d.ts +19 -0
  21. package/dist/core/jsx.d.ts.map +1 -1
  22. package/dist/core/jsx.js.map +1 -1
  23. package/dist/docs/app.d.ts.map +1 -1
  24. package/dist/docs/componentes/table.doc.d.ts.map +1 -1
  25. package/dist/fenix-ui.umd.min.js +237 -170
  26. package/dist/fenix-ui3.esm.js +8 -13
  27. package/dist/fenix-ui3.esm.js.map +1 -1
  28. package/dist/fenix-ui4.esm.js +275 -114
  29. package/dist/fenix-ui4.esm.js.map +1 -1
  30. package/dist/icons/font.d.ts +23 -0
  31. package/dist/icons/font.d.ts.map +1 -1
  32. package/dist/icons/font.js +54 -8
  33. package/dist/icons/font.js.map +1 -1
  34. package/dist/icons/index.d.ts +7 -2
  35. package/dist/icons/index.d.ts.map +1 -1
  36. package/dist/icons/index.js +6 -11
  37. package/dist/icons/index.js.map +1 -1
  38. package/package.json +1 -1
  39. package/dist/assets/font-CF0TXsAP.js.map +0 -1
package/README.md CHANGED
@@ -18,7 +18,7 @@ import { FenixUI } from '@wrrdev/fenix-ui'; // API de tema/tokens (índice)
18
18
  FenixUI.theme('dark');
19
19
  ```
20
20
 
21
- > 📦 npm: [`@wrrdev/fenix-ui`](https://www.npmjs.com/package/@wrrdev/fenix-ui) — **v1.1.10**, acesso público.
21
+ > 📦 npm: [`@wrrdev/fenix-ui`](https://www.npmjs.com/package/@wrrdev/fenix-ui) — **v1.1.12**, acesso público.
22
22
  > Subpaths disponíveis: `./theme`, `./tokens`, `./jsx`, `./vue`, `./react`, `./button`, `./badge`, `./spinner`, `./select`, `./input`, `./switch`, `./textarea`, `./checkbox`, `./radio`, `./calendar`, `./datepicker`, `./multiselect`, `./table`, `./floatlabel`, `./dialog`, `./toast`, `./tooltip`, `./tabs`, `./progress`, `./skeleton`, `./alert`, `./drawer`, `./dropdown`, `./icon`, `./icons`, `./pagination`, `./autocomplete`, `./knob`, `./orderlist`, `./picklist`, `./auto-import` e o curinga `./components/*`.
23
23
 
24
24
 
@@ -65,7 +65,7 @@ Carregue o bundle **UMD** único: ele registra todos os componentes e expõe os
65
65
  <fx-toast></fx-toast>
66
66
 
67
67
  <!--
68
- 🔗 Links diretos para CDN (versão 1.1.10):
68
+ 🔗 Links diretos para CDN (versão 1.1.12):
69
69
  • https://cdn.jsdelivr.net/npm/@wrrdev/fenix-ui@latest/dist/fenix-ui.umd.min.js
70
70
  • https://unpkg.com/@wrrdev/fenix-ui@latest/dist/fenix-ui.umd.min.js
71
71
  -->
@@ -349,6 +349,24 @@ Requisitos:
349
349
  - Volar / vue-tsc (já incluído no `@vue/language-tools` usado pelo VS Code + Volar);
350
350
  - no `tsconfig`, `"moduleResolution": "bundler"`.
351
351
 
352
+ #### Reduzindo o peso da fonte de ícones (opcional)
353
+
354
+ A fonte embarca **todos** os ~4.300 glifos, o que dá ~5 MB. Ela já usa
355
+ `font-display: swap` (o ícone aparece na hora com a fonte de fallback — nunca
356
+ mais a tela em branco esperando o download). Se o seu app usa apenas algumas
357
+ dezenas de ícones, gere um *subset* e aponte a biblioteca para ele:
358
+
359
+ ```ts
360
+ import { setFenixIconsFontUrl } from '@wrrdev/fenix-ui/icons';
361
+
362
+ setFenixIconsFontUrl('/fonts/fenix-icons-subset.woff2');
363
+ ```
364
+
365
+ Isso derruba a fonte para algumas dezenas de KB. **A tipagem não muda**:
366
+ `FenixIconName` continua sugerindo todos os nomes no editor — garanta apenas
367
+ que o subset contenha os glifos que o app usa. Chame a função antes do primeiro
368
+ `<fx-icon>` ser montado; use `setFenixIconsFontUrl('')` para voltar ao padrão.
369
+
352
370
  Com isso o editor mostra as propriedades de cada componente (`variant`, `size`, `loading`, …)
353
371
  nos templates. Para além do autocomplete, **validar** valores inválidos (ex.: `variant="foo"`),
354
372
  ative o modo estrito do Volar no `tsconfig.json`:
@@ -69,26 +69,69 @@ function resolveFontUrl() {
69
69
  return new URL("" + new URL("fenix-icons-DmgotBzD.woff2", import.meta.url).href, import.meta.url).toString();
70
70
  }
71
71
  const FENIX_ICONS_FONT_URL = resolveFontUrl();
72
+ function setFenixIconsFontUrl(url) {
73
+ const next = url.trim() || void 0;
74
+ if (fontUrlOverride === next) return;
75
+ fontUrlOverride = next;
76
+ const target = typeof document !== "undefined" ? document : void 0;
77
+ if (!target) return;
78
+ const hadFull = !!target.getElementById(FENIX_ICONS_STYLE_ID);
79
+ const hadFont = !!target.getElementById(FENIX_ICONS_FONT_STYLE_ID);
80
+ resetInjectedStyles(target);
81
+ if (hadFull) reinjectFull(target, fullNamesRef);
82
+ if (hadFont) injectFontStyle(target);
83
+ }
84
+ let fullNamesRef = [];
85
+ function __setFenixIconNames(names) {
86
+ fullNamesRef = names;
87
+ }
88
+ let fontUrlOverride;
89
+ function currentFontUrl() {
90
+ return fontUrlOverride ?? FENIX_ICONS_FONT_URL;
91
+ }
72
92
  const FENIX_ICONS_FONT_STYLE_ID = "fenix-icons-font";
73
93
  const FENIX_ICONS_STYLE_ID = "fenix-icons";
74
94
  function buildFenixIconsFontFaceCss() {
75
- return `@font-face{font-family:'${FENIX_ICON_FONT_FAMILY}';font-style:normal;font-weight:100 700;font-display:block;src:url('${FENIX_ICONS_FONT_URL}') format('woff2');}`;
95
+ return `@font-face{font-family:'${FENIX_ICON_FONT_FAMILY}';font-style:normal;font-weight:100 700;font-display:swap;src:url('${currentFontUrl()}') format('woff2');}`;
76
96
  }
77
97
  function buildFenixIconsFontCss() {
78
98
  return buildFenixIconsFontFaceCss() + FENIX_ICON_BASE_CSS;
79
99
  }
100
+ function buildFullCss(names) {
101
+ let rules = "";
102
+ for (const name of names) {
103
+ rules += `.fx-icon-${name}::before{content:"${name}"}`;
104
+ }
105
+ return buildFenixIconsFontFaceCss() + FENIX_ICON_BASE_CSS + rules;
106
+ }
80
107
  let fontInjected = false;
108
+ function resetInjectedStyles(target = typeof document !== "undefined" ? document : void 0) {
109
+ fontInjected = false;
110
+ if (!target) return;
111
+ target.getElementById(FENIX_ICONS_FONT_STYLE_ID)?.remove();
112
+ target.getElementById(FENIX_ICONS_STYLE_ID)?.remove();
113
+ }
114
+ function injectFontStyle(target) {
115
+ const style = target.createElement("style");
116
+ style.id = FENIX_ICONS_FONT_STYLE_ID;
117
+ style.textContent = buildFenixIconsFontCss();
118
+ target.head.appendChild(style);
119
+ fontInjected = true;
120
+ }
121
+ function reinjectFull(target, names) {
122
+ if (target.getElementById(FENIX_ICONS_STYLE_ID)) return;
123
+ const style = target.createElement("style");
124
+ style.id = FENIX_ICONS_STYLE_ID;
125
+ style.textContent = buildFullCss(names);
126
+ target.head.appendChild(style);
127
+ }
81
128
  function loadFenixIconsFont(target = typeof document !== "undefined" ? document : void 0) {
82
129
  if (fontInjected || !target) return;
83
130
  if (target.getElementById(FENIX_ICONS_STYLE_ID)) {
84
131
  fontInjected = true;
85
132
  return;
86
133
  }
87
- const style = target.createElement("style");
88
- style.id = FENIX_ICONS_FONT_STYLE_ID;
89
- style.textContent = buildFenixIconsFontCss();
90
- target.head.appendChild(style);
91
- fontInjected = true;
134
+ injectFontStyle(target);
92
135
  }
93
136
  function __resetFenixIconsFontInjection() {
94
137
  fontInjected = false;
@@ -97,14 +140,16 @@ export {
97
140
  FENIX_ICONS_STYLE_ID as F,
98
141
  __resetFenixIconsFontInjection as _,
99
142
  FENIX_ICON_BASE_CSS as a,
100
- buildFenixIconsFontFaceCss as b,
101
- FENIX_ICON_FONT_FAMILY as c,
102
- buildFenixIconsFontCss as d,
103
- esc as e,
143
+ buildFullCss as b,
144
+ __setFenixIconNames as c,
145
+ FENIX_ICON_FONT_FAMILY as d,
146
+ buildFenixIconsFontCss as e,
104
147
  fenixIconHtml as f,
105
- stripTags as g,
148
+ esc as g,
149
+ sanitizeData as h,
106
150
  isFenixIconName as i,
151
+ stripTags as j,
107
152
  loadFenixIconsFont as l,
108
- sanitizeData as s
153
+ setFenixIconsFontUrl as s
109
154
  };
110
- //# sourceMappingURL=font-CF0TXsAP.js.map
155
+ //# sourceMappingURL=font-DPeWTz-n.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"font-DPeWTz-n.js","sources":["../../src/core/sanitize.ts","../../src/icons/base-css.ts","../../src/icons/font.ts"],"sourcesContent":["/**\n * Sanitização de strings para injeção segura em HTML.\n *\n * Previne XSS ao escapar caracteres especiais antes de usar innerHTML.\n * Use SEMPRE que dados externos (API, usuário) forem injetados no DOM.\n */\n\n/** Caracteres que devem ser escapados para prevenir XSS. */\nconst HTML_ENTITIES: Record<string, string> = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#39;',\n '/': '&#x2F;',\n '`': '&#x60;',\n '=': '&#x3D;',\n};\n\n/** Regex que captura todos os caracteres perigosos. */\nconst SANITIZE_REGEX = /[&<>\"'`=/]/g;\n\n/**\n * Escapa caracteres especiais de HTML para prevenir XSS.\n *\n * @param value - Valor a ser escapado (convertido para string)\n * @returns String segura para innerHTML\n *\n * @example\n * // Seguro contra XSS\n * const userInput = '<img src=x onerror=alert(1)>';\n * element.innerHTML = `<div>${esc(userInput)}</div>`;\n * // Resultado: &lt;img src=x onerror=alert(1)&gt;\n */\nexport function esc(value: unknown): string {\n if (value == null) return '';\n return String(value).replace(SANITIZE_REGEX, (char) => HTML_ENTITIES[char] || char);\n}\n\n/**\n * Remove todas as tags HTML da string (strip tags).\n * Use quando quiser apenas texto puro, sem nenhuma marcação.\n *\n * @param html - String potencialmente contendo HTML\n * @returns String sem tags HTML\n */\nexport function stripTags(html: string): string {\n return html.replace(/<[^>]*>/g, '');\n}\n\n/**\n * Sanitiza um objeto de dados recursivamente.\n * Útil para sanitizar arrays de registros antes de renderizar.\n *\n * @param data - Objeto ou array a ser sanitizado\n * @returns Cópia sanitizada do objeto\n */\nexport function sanitizeData<T>(data: T): T {\n if (data == null) return data;\n if (typeof data === 'string') return esc(data) as unknown as T;\n if (Array.isArray(data)) return data.map(sanitizeData) as unknown as T;\n if (typeof data === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data as Record<string, unknown>)) {\n result[key] = sanitizeData(value);\n }\n return result as T;\n }\n return data;\n}\n","/**\n * CSS base da biblioteca de ícones — módulo LEVE (não importa a lista de nomes\n * nem dispara o carregamento da fonte). Usado dentro dos Shadow DOMs dos\n * componentes que suportam o atributo `icon`.\n *\n * O @font-face em si é injetado no <head> por `@wrrdev/fenix-ui/icons`\n * (ver ./index.ts) — @font-face registrado no documento também se aplica\n * dentro dos Shadow DOMs.\n */\n\nimport { esc } from '../core/sanitize';\n\n/** Nome da família de fontes dos ícones (self-hosted, sem dependência externa). */\nexport const FENIX_ICON_FONT_FAMILY = 'Fenix Icons';\n\n/** Família configurável via CSS custom property (fallback para o nome padrão). */\nexport const FENIX_ICON_FONT_VAR = `var(--fx-icon-font, ${FENIX_ICON_FONT_FAMILY})`;\n\n/**\n * Regras da classe `.fx-icon` para uso DENTRO de Shadow DOM\n * (concatene às `static styles` do componente).\n */\nexport const FENIX_ICON_BASE_CSS = `\n.fx-icon {\n font-family: ${FENIX_ICON_FONT_VAR};\n font-weight: normal;\n font-style: normal;\n line-height: 1;\n letter-spacing: normal;\n text-transform: none;\n display: inline-block;\n white-space: nowrap;\n word-wrap: normal;\n direction: ltr;\n font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;\n -webkit-font-smoothing: antialiased;\n text-rendering: optimizeLegibility;\n user-select: none;\n}\n.fx-icon.fx-icon-fill { font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 24; }\n.fx-icon.fx-icon-bold { font-variation-settings: 'FILL' 0, 'wght' 600, 'GRAD' 0, 'opsz' 24; }\n`;\n\n/** Verifica se o valor parece um nome de ícone da Fenix Icons (e não emoji/texto livre). */\nexport function isFenixIconName(value: string): boolean {\n return /^[a-z][a-z0-9_]*$/.test(value.trim());\n}\n\n/**\n * HTML do ícone para uso DENTRO de Shadow DOM (concatenar ao template).\n *\n * - Nome válido da Fenix Icons (ex.: `home`) → glifo via LIGADURA: o nome\n * vira o conteúdo de texto do elemento com a font-family de ícones.\n * Isso é obrigatório dentro de Shadow DOM: as regras\n * `.fx-icon-<nome>::before` são injetadas no <head> do documento e NÃO\n * atravessam a fronteira do shadow root (apenas o @font-face vale\n * dentro dele) — a classe vazia renderizaria um elemento sem glifo.\n * - Qualquer outro valor (emoji, texto) → texto puro escapado.\n *\n * A classe base `.fx-icon` é fornecida por `FENIX_ICON_BASE_CSS`\n * (concatenada às `static styles` do componente); o @font-face vem do\n * `@wrrdev/fenix-ui/icons` (injetado no <head>).\n */\nexport function fenixIconHtml(icon: string): string {\n const value = (icon ?? '').trim();\n if (!value) return '';\n if (isFenixIconName(value)) {\n return `<i class=\"fx-icon\" aria-hidden=\"true\">${value}</i>`;\n }\n return `<span aria-hidden=\"true\">${esc(value)}</span>`;\n}\n","/**\n * Carregamento LEVE da fonte de ícones (@font-face + classe base).\n *\n * Separado de ./index de propósito: importar este módulo NÃO injeta o CSS\n * completo com as ~4.300 regras `fx-icon-<nome>`. É o que o <fx-icon> usa —\n * dentro do Shadow DOM o glifo vem da ligadura (o nome como conteúdo), então\n * as classes por ícone não atravessam a fronteira do shadow root.\n */\n\nimport { FENIX_ICON_BASE_CSS, FENIX_ICON_FONT_FAMILY } from './base-css';\n\n/** URL da fonte woff2: no build ESM a fonte vive na mesma pasta do módulo (dist/icons). */\ndeclare const __FENIX_ICONS_FONT_URL__: string | undefined;\n\nfunction resolveFontUrl(): string {\n if (typeof __FENIX_ICONS_FONT_URL__ !== 'undefined' && __FENIX_ICONS_FONT_URL__) {\n return __FENIX_ICONS_FONT_URL__;\n }\n // IMPORTANTE: o `import.meta.url` precisa ser usado DIRETAMENTE como 2º\n // argumento de `new URL` (sem variável intermediária). É assim que Vite,\n // webpack e Rollup reconhecem o padrão de asset e copiam/reescrevem a URL\n // da fonte no build do PROJETO CONSUMIDOR — com variável, a URL permanece\n // dinâmica e quebra em produção (a fonte 404 e os ícones viram texto).\n return new URL('./fenix-icons.woff2', import.meta.url).toString();\n}\n\n/** URL resolvida da fonte (reutilizada por ./index para o CSS completo). */\nexport const FENIX_ICONS_FONT_URL = resolveFontUrl();\n\n/**\n * Sobrescreve a URL da fonte em tempo de execução.\n *\n * Use para apontar para um SUBSET próprio (só os glifos que o app usa), o que\n * derruba a fonte de ~5 MB para algumas dezenas de KB:\n *\n * import { setFenixIconsFontUrl } from '@wrrdev/fenix-ui/icons';\n * setFenixIconsFontUrl('/fonts/fenix-icons-subset.woff2');\n *\n * Deve ser chamado ANTES do primeiro `loadFenixIcons()`/`loadFenixIconsFont()`\n * (ou de `<fx-icon>` ser montado). A tipagem dos ícones não muda: continua\n * valendo a lista completa de `FenixIconName` — o subset precisa conter os\n * glifos que você realmente usa. Passe `''` para voltar à URL do build.\n */\nexport function setFenixIconsFontUrl(url: string): void {\n const next = url.trim() || undefined;\n if (fontUrlOverride === next) return;\n fontUrlOverride = next;\n // O CSS já injetado embute a URL antiga: remove e reinjeta com a nova,\n // senão a troca não teria efeito em apps que já carregaram a fonte.\n const target = typeof document !== 'undefined' ? document : undefined;\n if (!target) return;\n const hadFull = !!target.getElementById(FENIX_ICONS_STYLE_ID);\n const hadFont = !!target.getElementById(FENIX_ICONS_FONT_STYLE_ID);\n resetInjectedStyles(target);\n if (hadFull) reinjectFull(target, fullNamesRef);\n if (hadFont) injectFontStyle(target);\n}\n\n/**\n * Nomes registrados por ./index, usados só se for preciso recriar o CSS\n * completo. Evita que ./font dependa da lista de ~4.300 nomes.\n */\nlet fullNamesRef: readonly string[] = [];\n\n/** Informa os nomes disponíveis para a reinjeção do CSS completo. */\nexport function __setFenixIconNames(names: readonly string[]): void {\n fullNamesRef = names;\n}\n\nlet fontUrlOverride: string | undefined;\n\n/** URL efetiva: a sobrescrita em runtime tem prioridade sobre a do build. */\nfunction currentFontUrl(): string {\n return fontUrlOverride ?? FENIX_ICONS_FONT_URL;\n}\n\n/** Id do <style> com @font-face + classe base. */\nexport const FENIX_ICONS_FONT_STYLE_ID = 'fenix-icons-font';\n\n/** Id do <style> com o CSS completo (@font-face + base + classes por ícone). */\nexport const FENIX_ICONS_STYLE_ID = 'fenix-icons';\n\n/** CSS do @font-face. */\nexport function buildFenixIconsFontFaceCss(): string {\n return (\n `@font-face{font-family:'${FENIX_ICON_FONT_FAMILY}';font-style:normal;font-weight:100 700;` +\n // `swap` (e não `block`): com `block` o texto fica INVISÍVEL até a fonte\n // baixar — em 3G/4G isso vira tela branca por vários segundos. `swap`\n // mostra o glifo de fallback imediatamente e troca quando a fonte chega.\n `font-display:swap;src:url('${currentFontUrl()}') format('woff2');}`\n );\n}\n\n/** CSS leve: @font-face + classe base (sem as classes por ícone). */\nexport function buildFenixIconsFontCss(): string {\n return buildFenixIconsFontFaceCss() + FENIX_ICON_BASE_CSS;\n}\n\n/**\n * CSS completo (@font-face + base + uma regra por ícone), com a URL de fonte\n * vigente. Vive aqui (e não só em ./index) para que `setFenixIconsFontUrl`\n * consiga recriar o estilo completo sem importar outro módulo.\n */\nexport function buildFullCss(names: readonly string[]): string {\n let rules = '';\n for (const name of names) {\n rules += `.fx-icon-${name}::before{content:\"${name}\"}`;\n }\n return buildFenixIconsFontFaceCss() + FENIX_ICON_BASE_CSS + rules;\n}\n\nlet fontInjected = false;\n\n/**\n * Remove os <style> de ícones já injetados, para reinjetar com outra URL.\n * Usado por `setFenixIconsFontUrl`.\n */\nfunction resetInjectedStyles(\n target: Document | undefined = typeof document !== 'undefined' ? document : undefined,\n): void {\n fontInjected = false;\n if (!target) return;\n target.getElementById(FENIX_ICONS_FONT_STYLE_ID)?.remove();\n target.getElementById(FENIX_ICONS_STYLE_ID)?.remove();\n}\n\n/** Cria e anexa o <style> leve (@font-face + classe base) no <head>. */\nfunction injectFontStyle(target: Document): void {\n const style = target.createElement('style');\n style.id = FENIX_ICONS_FONT_STYLE_ID;\n style.textContent = buildFenixIconsFontCss();\n target.head.appendChild(style);\n fontInjected = true;\n}\n\n/**\n * Reinjeta o CSS completo (o de `./index`).\n *\n * Fica aqui para que a troca de URL da fonte recrie o estilo. Não usa o flag\n * `injected` de ./index: o <style> antigo já foi removido do DOM, então\n * recriá-lo aqui é sempre seguro e evita depender do estado de outro módulo.\n */\nfunction reinjectFull(target: Document, names: readonly string[]): void {\n if (target.getElementById(FENIX_ICONS_STYLE_ID)) return;\n const style = target.createElement('style');\n style.id = FENIX_ICONS_STYLE_ID;\n style.textContent = buildFullCss(names);\n target.head.appendChild(style);\n}\n\n/**\n * Injeta @font-face + classe base no <head> (idempotente). Não duplica quando\n * o CSS completo (`fenix-icons`) já foi carregado por `import '.../icons'`.\n */\nexport function loadFenixIconsFont(\n target: Document | undefined = typeof document !== 'undefined' ? document : undefined,\n): void {\n if (fontInjected || !target) return;\n if (target.getElementById(FENIX_ICONS_STYLE_ID)) {\n fontInjected = true;\n return;\n }\n injectFontStyle(target);\n}\n\n/** Reseta o estado de injeção da fonte (uso interno/testes). */\nexport function __resetFenixIconsFontInjection(): void {\n fontInjected = false;\n}"],"names":[],"mappings":"AAQA,MAAM,gBAAwC;AAAA,EAC5C,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AACP;AAGA,MAAM,iBAAiB;AAchB,SAAS,IAAI,OAAwB;AAC1C,MAAI,SAAS,KAAM,QAAO;AAC1B,SAAO,OAAO,KAAK,EAAE,QAAQ,gBAAgB,CAAC,SAAS,cAAc,IAAI,KAAK,IAAI;AACpF;AASO,SAAS,UAAU,MAAsB;AAC9C,SAAO,KAAK,QAAQ,YAAY,EAAE;AACpC;AASO,SAAS,aAAgB,MAAY;AAC1C,MAAI,QAAQ,KAAM,QAAO;AACzB,MAAI,OAAO,SAAS,SAAU,QAAO,IAAI,IAAI;AAC7C,MAAI,MAAM,QAAQ,IAAI,EAAG,QAAO,KAAK,IAAI,YAAY;AACrD,MAAI,OAAO,SAAS,UAAU;AAC5B,UAAM,SAAkC,CAAA;AACxC,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAA+B,GAAG;AAC1E,aAAO,GAAG,IAAI,aAAa,KAAK;AAAA,IAClC;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;ACxDO,MAAM,yBAAyB;AAG/B,MAAM,sBAAsB,uBAAuB,sBAAsB;AAMzE,MAAM,sBAAsB;AAAA;AAAA,iBAElB,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoB7B,SAAS,gBAAgB,OAAwB;AACtD,SAAO,oBAAoB,KAAK,MAAM,KAAA,CAAM;AAC9C;AAiBO,SAAS,cAAc,MAAsB;AAClD,QAAM,SAAS,QAAQ,IAAI,KAAA;AAC3B,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,gBAAgB,KAAK,GAAG;AAC1B,WAAO,yCAAyC,KAAK;AAAA,EACvD;AACA,SAAO,4BAA4B,IAAI,KAAK,CAAC;AAC/C;ACxDA,SAAS,iBAAyB;AAChC,MAAI,OAAO,6BAA6B,eAAe,0BAA0B;AAC/E,WAAO;AAAA,EACT;AAMA,SAAO,IAAA,IAAA,kEAAA,YAAA,GAAA,EAAgD,SAAA;AACzD;AAGO,MAAM,uBAAuB,eAAA;AAgB7B,SAAS,qBAAqB,KAAmB;AACtD,QAAM,OAAO,IAAI,KAAA,KAAU;AAC3B,MAAI,oBAAoB,KAAM;AAC9B,oBAAkB;AAGlB,QAAM,SAAS,OAAO,aAAa,cAAc,WAAW;AAC5D,MAAI,CAAC,OAAQ;AACb,QAAM,UAAU,CAAC,CAAC,OAAO,eAAe,oBAAoB;AAC5D,QAAM,UAAU,CAAC,CAAC,OAAO,eAAe,yBAAyB;AACjE,sBAAoB,MAAM;AAC1B,MAAI,QAAS,cAAa,QAAQ,YAAY;AAC9C,MAAI,yBAAyB,MAAM;AACrC;AAMA,IAAI,eAAkC,CAAA;AAG/B,SAAS,oBAAoB,OAAgC;AAClE,iBAAe;AACjB;AAEA,IAAI;AAGJ,SAAS,iBAAyB;AAChC,SAAO,mBAAmB;AAC5B;AAGO,MAAM,4BAA4B;AAGlC,MAAM,uBAAuB;AAG7B,SAAS,6BAAqC;AACnD,SACE,2BAA2B,sBAAsB,sEAInB,eAAA,CAAgB;AAElD;AAGO,SAAS,yBAAiC;AAC/C,SAAO,+BAA+B;AACxC;AAOO,SAAS,aAAa,OAAkC;AAC7D,MAAI,QAAQ;AACZ,aAAW,QAAQ,OAAO;AACxB,aAAS,YAAY,IAAI,qBAAqB,IAAI;AAAA,EACpD;AACA,SAAO,2BAAA,IAA+B,sBAAsB;AAC9D;AAEA,IAAI,eAAe;AAMnB,SAAS,oBACP,SAA+B,OAAO,aAAa,cAAc,WAAW,QACtE;AACN,iBAAe;AACf,MAAI,CAAC,OAAQ;AACb,SAAO,eAAe,yBAAyB,GAAG,OAAA;AAClD,SAAO,eAAe,oBAAoB,GAAG,OAAA;AAC/C;AAGA,SAAS,gBAAgB,QAAwB;AAC/C,QAAM,QAAQ,OAAO,cAAc,OAAO;AAC1C,QAAM,KAAK;AACX,QAAM,cAAc,uBAAA;AACpB,SAAO,KAAK,YAAY,KAAK;AAC7B,iBAAe;AACjB;AASA,SAAS,aAAa,QAAkB,OAAgC;AACtE,MAAI,OAAO,eAAe,oBAAoB,EAAG;AACjD,QAAM,QAAQ,OAAO,cAAc,OAAO;AAC1C,QAAM,KAAK;AACX,QAAM,cAAc,aAAa,KAAK;AACtC,SAAO,KAAK,YAAY,KAAK;AAC/B;AAMO,SAAS,mBACd,SAA+B,OAAO,aAAa,cAAc,WAAW,QACtE;AACN,MAAI,gBAAgB,CAAC,OAAQ;AAC7B,MAAI,OAAO,eAAe,oBAAoB,GAAG;AAC/C,mBAAe;AACf;AAAA,EACF;AACA,kBAAgB,MAAM;AACxB;AAGO,SAAS,iCAAuC;AACrD,iBAAe;AACjB;"}
@@ -28,7 +28,11 @@ export declare class FxBreadcrumb extends FxElement {
28
28
  */
29
29
  private _decorateItems;
30
30
  private _items;
31
+ /** Observer de upgrade dos filhos; precisa ser solto no disconnect. */
32
+ private _childObserver?;
31
33
  protected connectedCallback(): void;
34
+ /** Desconecta o observer de filhos (evita trabalho eterno após o connect). */
35
+ private _stopObservingChildren;
32
36
  protected disconnectedCallback(): void;
33
37
  }
34
38
  export declare function defineFxBreadcrumb(): typeof FxBreadcrumb;
@@ -1 +1 @@
1
- {"version":3,"file":"breadcrumb.d.ts","sourceRoot":"","sources":["../../../src/components/breadcrumb/breadcrumb.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAK5C;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,YAAa,SAAQ,SAAS;IACzC,OAAgB,MAAM,SAuCpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;cAEkB,MAAM,IAAI,IAAI;IAUjC;;;;;OAKG;IACH,OAAO,CAAC,cAAc;IA4BtB,OAAO,CAAC,MAAM,CAAiB;cAEZ,iBAAiB,IAAI,IAAI;cAczB,oBAAoB,IAAI,IAAI;CAIhD;AAED,wBAAgB,kBAAkB,IAAI,OAAO,YAAY,CAExD"}
1
+ {"version":3,"file":"breadcrumb.d.ts","sourceRoot":"","sources":["../../../src/components/breadcrumb/breadcrumb.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAK5C;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,YAAa,SAAQ,SAAS;IACzC,OAAgB,MAAM,SAuCpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;cAEkB,MAAM,IAAI,IAAI;IAUjC;;;;;OAKG;IACH,OAAO,CAAC,cAAc;IA4BtB,OAAO,CAAC,MAAM,CAAiB;IAC/B,uEAAuE;IACvE,OAAO,CAAC,cAAc,CAAC,CAAmB;cAEvB,iBAAiB,IAAI,IAAI;IAkB5C,8EAA8E;IAC9E,OAAO,CAAC,sBAAsB;cAKX,oBAAoB,IAAI,IAAI;CAKhD;AAED,wBAAgB,kBAAkB,IAAI,OAAO,YAAY,CAExD"}
@@ -50,14 +50,24 @@ const _FxBreadcrumb = class _FxBreadcrumb extends FxElement {
50
50
  super.connectedCallback();
51
51
  const obs = new MutationObserver(() => {
52
52
  if (this.children.length > 0) {
53
- obs.disconnect();
53
+ this._stopObservingChildren();
54
54
  this._decorateItems();
55
55
  }
56
56
  });
57
+ this._childObserver = obs;
57
58
  obs.observe(this, { childList: true });
58
- if (this.children.length > 0) this._decorateItems();
59
+ if (this.children.length > 0) {
60
+ this._stopObservingChildren();
61
+ this._decorateItems();
62
+ }
63
+ }
64
+ /** Desconecta o observer de filhos (evita trabalho eterno após o connect). */
65
+ _stopObservingChildren() {
66
+ this._childObserver?.disconnect();
67
+ this._childObserver = void 0;
59
68
  }
60
69
  disconnectedCallback() {
70
+ this._stopObservingChildren();
61
71
  super.disconnectedCallback();
62
72
  this._items = [];
63
73
  }
@@ -1 +1 @@
1
- {"version":3,"file":"breadcrumb.js","sources":["../../../src/components/breadcrumb/breadcrumb.ts"],"sourcesContent":["import { FxElement } from '../../core/base';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\nimport { esc } from '../../core/sanitize';\n\n/**\n * <fx-breadcrumb> — Trilha de navegação hierárquica.\n *\n * Atributos: size (sm|md|lg), separator (glifo entre itens, padrão '/'),\n * label (acessibilidade, padrão 'Breadcrumb').\n * Slots: `item` (repetível — cada <fx-breadcrumb-item> ou elemento com\n * [slot=\"item\"] vira um passo) e `separator` (customiza o separador).\n *\n * Marcação declarativa recomendada:\n * <fx-breadcrumb>\n * <fx-breadcrumb-item href=\"/\">Home</fx-breadcrumb-item>\n * <fx-breadcrumb-item href=\"/produtos\">Produtos</fx-breadcrumb-item>\n * <fx-breadcrumb-item>Detalhe</fx-breadcrumb-item>\n * </fx-breadcrumb>\n *\n * Eventos (composed): `select` (detail: { item, index }) ao clicar num item.\n */\nexport class FxBreadcrumb extends FxElement {\n static override styles = css`\n :host {\n display: block;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n }\n nav {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: var(--fx-space-xs);\n }\n :host([size='sm']) nav { font-size: calc(var(--fx-font-size) - 2px); }\n :host([size='lg']) nav { font-size: calc(var(--fx-font-size) + 2px); }\n\n /* itens (movidos para o nav no shadow DOM) */\n nav [slot='item'] {\n display: inline-flex;\n align-items: center;\n gap: var(--fx-space-xs);\n color: var(--fx-text-muted);\n text-decoration: none;\n cursor: pointer;\n transition: color var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n nav [slot='item']:hover { color: var(--fx-color-primary); }\n nav [slot='item'][aria-current='page'] {\n color: var(--fx-text-default);\n font-weight: 600;\n cursor: default;\n }\n nav [slot='item'][aria-current='page']:hover { color: var(--fx-text-default); }\n\n .sep {\n color: var(--fx-text-muted);\n user-select: none;\n display: inline-flex;\n align-items: center;\n }\n `;\n\n static override get observedAttributes(): string[] {\n return ['size', 'separator', 'label'];\n }\n\n protected override render(): void {\n const label = this.getAttr('label', 'Breadcrumb');\n\n this.setTemplate(`\n <nav part=\"nav\" aria-label=\"${esc(label)}\"></nav>\n `);\n\n this._decorateItems();\n }\n\n /**\n * Move os itens do light DOM para o nav e intercala separadores.\n * Como o playground atribui innerHTML (filhos chegam assincronamente) e\n * re-renders posteriores já terão os itens no nav, a lógica abaixo é\n * idempotente: coleta filhos do light DOM apenas na 1ª chamada.\n */\n private _decorateItems(): void {\n const nav = this.shadowRoot?.querySelector('nav');\n if (!nav) return;\n const sep = this.getAttr('separator', '/');\n\n // 1ª vez: captura itens do light DOM (idempotente — só coleta uma vez)\n if (this._items.length === 0) {\n this._items = Array.from(this.children).filter((el) =>\n el.matches('[slot=\"item\"]'),\n );\n }\n\n // limpa nav e reconstrói com os itens armazenados\n nav.innerHTML = '';\n this._items.forEach((item, i) => {\n const isLast = i === this._items.length - 1;\n item.setAttribute('aria-current', isLast ? 'page' : 'false');\n nav.appendChild(item);\n if (isLast) return;\n const s = document.createElement('span');\n s.className = 'sep';\n s.setAttribute('part', 'separator');\n s.setAttribute('aria-hidden', 'true');\n s.textContent = sep;\n nav.appendChild(s);\n });\n }\n\n private _items: Element[] = [];\n\n protected override connectedCallback(): void {\n super.connectedCallback();\n // filhos podem não estar disponíveis ainda (innerHTML do playground)\n const obs = new MutationObserver(() => {\n if (this.children.length > 0) {\n obs.disconnect();\n this._decorateItems();\n }\n });\n obs.observe(this, { childList: true });\n // se já tem filhos, decora na hora\n if (this.children.length > 0) this._decorateItems();\n }\n\n protected override disconnectedCallback(): void {\n super.disconnectedCallback();\n this._items = [];\n }\n}\n\nexport function defineFxBreadcrumb(): typeof FxBreadcrumb {\n return defineElement('fx-breadcrumb', FxBreadcrumb);\n}\n"],"names":[],"mappings":";;;;AAsBO,MAAM,gBAAN,MAAM,sBAAqB,UAAU;AAAA,EAArC,cAAA;AAAA,UAAA,GAAA,SAAA;AA0FL,SAAQ,SAAoB,CAAA;AAAA,EAAC;AAAA,EAhD7B,WAAoB,qBAA+B;AACjD,WAAO,CAAC,QAAQ,aAAa,OAAO;AAAA,EACtC;AAAA,EAEmB,SAAe;AAChC,UAAM,QAAQ,KAAK,QAAQ,SAAS,YAAY;AAEhD,SAAK,YAAY;AAAA,oCACe,IAAI,KAAK,CAAC;AAAA,KACzC;AAED,SAAK,eAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,iBAAuB;AAC7B,UAAM,MAAM,KAAK,YAAY,cAAc,KAAK;AAChD,QAAI,CAAC,IAAK;AACV,UAAM,MAAM,KAAK,QAAQ,aAAa,GAAG;AAGzC,QAAI,KAAK,OAAO,WAAW,GAAG;AAC5B,WAAK,SAAS,MAAM,KAAK,KAAK,QAAQ,EAAE;AAAA,QAAO,CAAC,OAC9C,GAAG,QAAQ,eAAe;AAAA,MAAA;AAAA,IAE9B;AAGA,QAAI,YAAY;AAChB,SAAK,OAAO,QAAQ,CAAC,MAAM,MAAM;AAC/B,YAAM,SAAS,MAAM,KAAK,OAAO,SAAS;AAC1C,WAAK,aAAa,gBAAgB,SAAS,SAAS,OAAO;AAC3D,UAAI,YAAY,IAAI;AACpB,UAAI,OAAQ;AACZ,YAAM,IAAI,SAAS,cAAc,MAAM;AACvC,QAAE,YAAY;AACd,QAAE,aAAa,QAAQ,WAAW;AAClC,QAAE,aAAa,eAAe,MAAM;AACpC,QAAE,cAAc;AAChB,UAAI,YAAY,CAAC;AAAA,IACnB,CAAC;AAAA,EACH;AAAA,EAImB,oBAA0B;AAC3C,UAAM,kBAAA;AAEN,UAAM,MAAM,IAAI,iBAAiB,MAAM;AACrC,UAAI,KAAK,SAAS,SAAS,GAAG;AAC5B,YAAI,WAAA;AACJ,aAAK,eAAA;AAAA,MACP;AAAA,IACF,CAAC;AACD,QAAI,QAAQ,MAAM,EAAE,WAAW,MAAM;AAErC,QAAI,KAAK,SAAS,SAAS,QAAQ,eAAA;AAAA,EACrC;AAAA,EAEmB,uBAA6B;AAC9C,UAAM,qBAAA;AACN,SAAK,SAAS,CAAA;AAAA,EAChB;AACF;AA7GE,cAAgB,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AADpB,IAAM,eAAN;AAgHA,SAAS,qBAA0C;AACxD,SAAO,cAAc,iBAAiB,YAAY;AACpD;"}
1
+ {"version":3,"file":"breadcrumb.js","sources":["../../../src/components/breadcrumb/breadcrumb.ts"],"sourcesContent":["import { FxElement } from '../../core/base';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\nimport { esc } from '../../core/sanitize';\n\n/**\n * <fx-breadcrumb> — Trilha de navegação hierárquica.\n *\n * Atributos: size (sm|md|lg), separator (glifo entre itens, padrão '/'),\n * label (acessibilidade, padrão 'Breadcrumb').\n * Slots: `item` (repetível — cada <fx-breadcrumb-item> ou elemento com\n * [slot=\"item\"] vira um passo) e `separator` (customiza o separador).\n *\n * Marcação declarativa recomendada:\n * <fx-breadcrumb>\n * <fx-breadcrumb-item href=\"/\">Home</fx-breadcrumb-item>\n * <fx-breadcrumb-item href=\"/produtos\">Produtos</fx-breadcrumb-item>\n * <fx-breadcrumb-item>Detalhe</fx-breadcrumb-item>\n * </fx-breadcrumb>\n *\n * Eventos (composed): `select` (detail: { item, index }) ao clicar num item.\n */\nexport class FxBreadcrumb extends FxElement {\n static override styles = css`\n :host {\n display: block;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n }\n nav {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: var(--fx-space-xs);\n }\n :host([size='sm']) nav { font-size: calc(var(--fx-font-size) - 2px); }\n :host([size='lg']) nav { font-size: calc(var(--fx-font-size) + 2px); }\n\n /* itens (movidos para o nav no shadow DOM) */\n nav [slot='item'] {\n display: inline-flex;\n align-items: center;\n gap: var(--fx-space-xs);\n color: var(--fx-text-muted);\n text-decoration: none;\n cursor: pointer;\n transition: color var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n nav [slot='item']:hover { color: var(--fx-color-primary); }\n nav [slot='item'][aria-current='page'] {\n color: var(--fx-text-default);\n font-weight: 600;\n cursor: default;\n }\n nav [slot='item'][aria-current='page']:hover { color: var(--fx-text-default); }\n\n .sep {\n color: var(--fx-text-muted);\n user-select: none;\n display: inline-flex;\n align-items: center;\n }\n `;\n\n static override get observedAttributes(): string[] {\n return ['size', 'separator', 'label'];\n }\n\n protected override render(): void {\n const label = this.getAttr('label', 'Breadcrumb');\n\n this.setTemplate(`\n <nav part=\"nav\" aria-label=\"${esc(label)}\"></nav>\n `);\n\n this._decorateItems();\n }\n\n /**\n * Move os itens do light DOM para o nav e intercala separadores.\n * Como o playground atribui innerHTML (filhos chegam assincronamente) e\n * re-renders posteriores já terão os itens no nav, a lógica abaixo é\n * idempotente: coleta filhos do light DOM apenas na 1ª chamada.\n */\n private _decorateItems(): void {\n const nav = this.shadowRoot?.querySelector('nav');\n if (!nav) return;\n const sep = this.getAttr('separator', '/');\n\n // 1ª vez: captura itens do light DOM (idempotente — só coleta uma vez)\n if (this._items.length === 0) {\n this._items = Array.from(this.children).filter((el) =>\n el.matches('[slot=\"item\"]'),\n );\n }\n\n // limpa nav e reconstrói com os itens armazenados\n nav.innerHTML = '';\n this._items.forEach((item, i) => {\n const isLast = i === this._items.length - 1;\n item.setAttribute('aria-current', isLast ? 'page' : 'false');\n nav.appendChild(item);\n if (isLast) return;\n const s = document.createElement('span');\n s.className = 'sep';\n s.setAttribute('part', 'separator');\n s.setAttribute('aria-hidden', 'true');\n s.textContent = sep;\n nav.appendChild(s);\n });\n }\n\n private _items: Element[] = [];\n /** Observer de upgrade dos filhos; precisa ser solto no disconnect. */\n private _childObserver?: MutationObserver;\n\n protected override connectedCallback(): void {\n super.connectedCallback();\n // filhos podem não estar disponíveis ainda (innerHTML do playground)\n const obs = new MutationObserver(() => {\n if (this.children.length > 0) {\n this._stopObservingChildren();\n this._decorateItems();\n }\n });\n this._childObserver = obs;\n obs.observe(this, { childList: true });\n // se já tem filhos, decora na hora\n if (this.children.length > 0) {\n this._stopObservingChildren();\n this._decorateItems();\n }\n }\n\n /** Desconecta o observer de filhos (evita trabalho eterno após o connect). */\n private _stopObservingChildren(): void {\n this._childObserver?.disconnect();\n this._childObserver = undefined;\n }\n\n protected override disconnectedCallback(): void {\n this._stopObservingChildren();\n super.disconnectedCallback();\n this._items = [];\n }\n}\n\nexport function defineFxBreadcrumb(): typeof FxBreadcrumb {\n return defineElement('fx-breadcrumb', FxBreadcrumb);\n}\n"],"names":[],"mappings":";;;;AAsBO,MAAM,gBAAN,MAAM,sBAAqB,UAAU;AAAA,EAArC,cAAA;AAAA,UAAA,GAAA,SAAA;AA0FL,SAAQ,SAAoB,CAAA;AAAA,EAAC;AAAA,EAhD7B,WAAoB,qBAA+B;AACjD,WAAO,CAAC,QAAQ,aAAa,OAAO;AAAA,EACtC;AAAA,EAEmB,SAAe;AAChC,UAAM,QAAQ,KAAK,QAAQ,SAAS,YAAY;AAEhD,SAAK,YAAY;AAAA,oCACe,IAAI,KAAK,CAAC;AAAA,KACzC;AAED,SAAK,eAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,iBAAuB;AAC7B,UAAM,MAAM,KAAK,YAAY,cAAc,KAAK;AAChD,QAAI,CAAC,IAAK;AACV,UAAM,MAAM,KAAK,QAAQ,aAAa,GAAG;AAGzC,QAAI,KAAK,OAAO,WAAW,GAAG;AAC5B,WAAK,SAAS,MAAM,KAAK,KAAK,QAAQ,EAAE;AAAA,QAAO,CAAC,OAC9C,GAAG,QAAQ,eAAe;AAAA,MAAA;AAAA,IAE9B;AAGA,QAAI,YAAY;AAChB,SAAK,OAAO,QAAQ,CAAC,MAAM,MAAM;AAC/B,YAAM,SAAS,MAAM,KAAK,OAAO,SAAS;AAC1C,WAAK,aAAa,gBAAgB,SAAS,SAAS,OAAO;AAC3D,UAAI,YAAY,IAAI;AACpB,UAAI,OAAQ;AACZ,YAAM,IAAI,SAAS,cAAc,MAAM;AACvC,QAAE,YAAY;AACd,QAAE,aAAa,QAAQ,WAAW;AAClC,QAAE,aAAa,eAAe,MAAM;AACpC,QAAE,cAAc;AAChB,UAAI,YAAY,CAAC;AAAA,IACnB,CAAC;AAAA,EACH;AAAA,EAMmB,oBAA0B;AAC3C,UAAM,kBAAA;AAEN,UAAM,MAAM,IAAI,iBAAiB,MAAM;AACrC,UAAI,KAAK,SAAS,SAAS,GAAG;AAC5B,aAAK,uBAAA;AACL,aAAK,eAAA;AAAA,MACP;AAAA,IACF,CAAC;AACD,SAAK,iBAAiB;AACtB,QAAI,QAAQ,MAAM,EAAE,WAAW,MAAM;AAErC,QAAI,KAAK,SAAS,SAAS,GAAG;AAC5B,WAAK,uBAAA;AACL,WAAK,eAAA;AAAA,IACP;AAAA,EACF;AAAA;AAAA,EAGQ,yBAA+B;AACrC,SAAK,gBAAgB,WAAA;AACrB,SAAK,iBAAiB;AAAA,EACxB;AAAA,EAEmB,uBAA6B;AAC9C,SAAK,uBAAA;AACL,UAAM,qBAAA;AACN,SAAK,SAAS,CAAA;AAAA,EAChB;AACF;AA1HE,cAAgB,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AADpB,IAAM,eAAN;AA6HA,SAAS,qBAA0C;AACxD,SAAO,cAAc,iBAAiB,YAAY;AACpD;"}
@@ -45,7 +45,8 @@ import "../select";
45
45
  *
46
46
  * Atributos: pagination, rows, rows-options, pagination-position (left|center|right),
47
47
  * striped, hover, empty-message, lazy, total, loading, loading-message,
48
- * sort-field, sort-order.
48
+ * sort-field, sort-order, rounded (none|sm|md|lg, padrão md; `radius` é alias),
49
+ * no-borders-x (remove apenas as bordas laterais, mantendo topo e base).
49
50
  * Eventos: page-change, sort-change, row-click, filter-change (todos composed).
50
51
  */
51
52
  export declare class FxTable extends FxElement {
@@ -105,7 +106,23 @@ export declare class FxTable extends FxElement {
105
106
  private computeRows;
106
107
  private _getSearchFields;
107
108
  private cellHtml;
109
+ /**
110
+ * Assinatura da "casca" da tabela: tudo que NÃO depende das linhas
111
+ * (colunas, toolbar, filtros, paginação, loading).
112
+ *
113
+ * Serve para o caminho rápido: quando só `data` muda, a casca é idêntica e
114
+ * podemos trocar apenas o `<tbody>`, sem recriar `<style>`, `<thead>`,
115
+ * toolbar e paginação — nem religar todos os listeners.
116
+ */
117
+ private _shellKey;
108
118
  protected render(): void;
119
+ /**
120
+ * Liga o clique das linhas ao evento `row-click`.
121
+ *
122
+ * Extraído do render para ser reaproveitado no caminho rápido (troca só do
123
+ * <tbody>): os <tr> são novos, então precisam ser rebound.
124
+ */
125
+ private _bindRowClicks;
109
126
  private _restoreToolbarSearch;
110
127
  }
111
128
  export declare function defineFxTable(): typeof FxTable;
@@ -1 +1 @@
1
- {"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../../src/components/table/table.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAO5C,OAAO,WAAW,CAAC;AAEnB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,qBAAa,OAAQ,SAAQ,SAAS;IACrC,OAAgB,MAAM,SAiNpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAiBjD;IAED,OAAO,CAAC,KAAK,CAAiC;IAC9C,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,SAAS,CAAM;IACvB,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,eAAe,CAAC,CAAmB;IAE3C,IAAI,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAEpC;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAGxC;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;IACD,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,EAGtB;IAED,IAAI,WAAW,IAAI,MAAM,CAGxB;IACD,IAAI,WAAW,CAAC,KAAK,EAAE,MAAM,EAE5B;IAED,IAAI,IAAI,IAAI,OAAO,CAElB;IAED,OAAO,KAAK,OAAO,GAuBlB;IAED;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,OAAO,CAAC,oBAAoB;IAW5B,OAAO,KAAK,eAAe,GAU1B;IAED;;;;OAIG;IACH,OAAO,CAAC,WAAW;cAeA,iBAAiB,IAAI,IAAI;cAWzB,oBAAoB,IAAI,IAAI;IAK/C,+EAA+E;IAC/E,OAAO,CAAC,oBAAoB;IAO5B,OAAO,CAAC,mBAAmB;IAgB3B,OAAO,CAAC,WAAW;IAiDnB,OAAO,CAAC,gBAAgB;IAaxB,OAAO,CAAC,QAAQ;cAaG,MAAM,IAAI,IAAI;IAyNjC,OAAO,CAAC,qBAAqB;CAM7B;AAED,wBAAgB,aAAa,IAAI,OAAO,OAAO,CAE9C"}
1
+ {"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../../src/components/table/table.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAO5C,OAAO,WAAW,CAAC;AAEnB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,qBAAa,OAAQ,SAAQ,SAAS;IACrC,OAAgB,MAAM,SAkRpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAoBjD;IAED,OAAO,CAAC,KAAK,CAAiC;IAC9C,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,SAAS,CAAM;IACvB,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,eAAe,CAAC,CAAmB;IAE3C,IAAI,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAEpC;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAGxC;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;IACD,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,EAGtB;IAED,IAAI,WAAW,IAAI,MAAM,CAGxB;IACD,IAAI,WAAW,CAAC,KAAK,EAAE,MAAM,EAE5B;IAED,IAAI,IAAI,IAAI,OAAO,CAElB;IAED,OAAO,KAAK,OAAO,GAuBlB;IAED;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,OAAO,CAAC,oBAAoB;IAW5B,OAAO,KAAK,eAAe,GAU1B;IAED;;;;OAIG;IACH,OAAO,CAAC,WAAW;cAeA,iBAAiB,IAAI,IAAI;cAWzB,oBAAoB,IAAI,IAAI;IAK/C,+EAA+E;IAC/E,OAAO,CAAC,oBAAoB;IAO5B,OAAO,CAAC,mBAAmB;IAgB3B,OAAO,CAAC,WAAW;IAiDnB,OAAO,CAAC,gBAAgB;IAaxB,OAAO,CAAC,QAAQ;IAahB;;;;;;;OAOG;IACH,OAAO,CAAC,SAAS,CAAM;cAEJ,MAAM,IAAI,IAAI;IAkOjC;;;;;OAKG;IACH,OAAO,CAAC,cAAc;IAmBtB,OAAO,CAAC,qBAAqB;CAM7B;AAED,wBAAgB,aAAa,IAAI,OAAO,OAAO,CAE9C"}
@@ -15,6 +15,7 @@ const _FxTable = class _FxTable extends FxElement {
15
15
  this.sortDir = 1;
16
16
  this.filters = {};
17
17
  this.globalSearch = "";
18
+ this._shellKey = "";
18
19
  }
19
20
  static get observedAttributes() {
20
21
  return [
@@ -31,7 +32,10 @@ const _FxTable = class _FxTable extends FxElement {
31
32
  "loading",
32
33
  "loading-message",
33
34
  "sort-field",
34
- "sort-order"
35
+ "sort-order",
36
+ "rounded",
37
+ "radius",
38
+ "no-borders-x"
35
39
  ];
36
40
  }
37
41
  get data() {
@@ -251,18 +255,37 @@ const _FxTable = class _FxTable extends FxElement {
251
255
  </div>`;
252
256
  const loading = this.hasAttr("loading");
253
257
  const loadingHtml = loading ? `<div class="loading-overlay" part="loading-overlay"><div class="tbl-spinner" part="spinner"></div><span class="loading-text">${esc(this.getAttr("loading-message", "Carregando…"))}</span></div>` : "";
258
+ const shellKey = [
259
+ toolbarHtml,
260
+ headHtml,
261
+ filterRow,
262
+ pagerHtml,
263
+ loadingHtml,
264
+ cols.length
265
+ ].join("|");
266
+ if (shellKey === this._shellKey) {
267
+ const tbody = this.root.querySelector("tbody");
268
+ if (tbody) {
269
+ tbody.innerHTML = bodyHtml;
270
+ this._bindRowClicks(rows);
271
+ return;
272
+ }
273
+ }
274
+ this._shellKey = shellKey;
254
275
  this.setTemplate(`
255
276
  ${toolbarHtml}
256
- <div class="table-wrap">
257
- <div class="scroll">
258
- <table part="table">
259
- <thead><tr>${headHtml}</tr>${filterRow}</thead>
260
- <tbody>${bodyHtml}</tbody>
261
- </table>
277
+ <div class="frame${toolbar ? " has-toolbar" : ""}" part="frame">
278
+ <div class="table-wrap">
279
+ <div class="scroll">
280
+ <table part="table">
281
+ <thead><tr>${headHtml}</tr>${filterRow}</thead>
282
+ <tbody>${bodyHtml}</tbody>
283
+ </table>
284
+ </div>
285
+ ${loadingHtml}
262
286
  </div>
263
- ${loadingHtml}
287
+ ${pagerHtml}
264
288
  </div>
265
- ${pagerHtml}
266
289
  `);
267
290
  this.root.querySelectorAll("th.sortable").forEach((th) => {
268
291
  th.addEventListener("click", () => {
@@ -354,6 +377,16 @@ const _FxTable = class _FxTable extends FxElement {
354
377
  this.page = 0;
355
378
  this.render();
356
379
  });
380
+ this._bindRowClicks(rows);
381
+ this._restoreToolbarSearch();
382
+ }
383
+ /**
384
+ * Liga o clique das linhas ao evento `row-click`.
385
+ *
386
+ * Extraído do render para ser reaproveitado no caminho rápido (troca só do
387
+ * <tbody>): os <tr> são novos, então precisam ser rebound.
388
+ */
389
+ _bindRowClicks(rows) {
357
390
  this.root.querySelectorAll("tbody tr[data-index]").forEach((tr) => {
358
391
  tr.addEventListener("click", () => {
359
392
  this.dispatchEvent(
@@ -368,7 +401,6 @@ const _FxTable = class _FxTable extends FxElement {
368
401
  );
369
402
  });
370
403
  });
371
- this._restoreToolbarSearch();
372
404
  }
373
405
  _restoreToolbarSearch() {
374
406
  const input = this.root.querySelector(
@@ -385,14 +417,61 @@ _FxTable.styles = css`
385
417
  font-size: var(--fx-font-size);
386
418
  color: var(--fx-text-default);
387
419
  background: var(--fx-surface-background);
420
+ /* Raio da moldura (tabela + paginação). O atributo "rounded"
421
+ sobrescreve este valor e o consumidor também pode sobrescrever
422
+ por CSS: fx-table { --fx-table-radius: 20px; }. */
423
+ --fx-table-radius: var(--fx-radius-md);
424
+ }
425
+ /* Arredondamento das bordas — "none" deixa tudo reto, como nos demais
426
+ tokens de radius do design system. "rounded" é o nome atual; "radius"
427
+ segue aceito como alias para ficar consistente com o <fx-card>. */
428
+ :host([rounded="none"]),
429
+ :host([radius="none"]) {
430
+ --fx-table-radius: var(--fx-radius-none, 0);
431
+ }
432
+ :host([rounded="sm"]),
433
+ :host([radius="sm"]) {
434
+ --fx-table-radius: var(--fx-radius-sm);
435
+ }
436
+ :host([rounded="md"]),
437
+ :host([radius="md"]) {
438
+ --fx-table-radius: var(--fx-radius-md);
439
+ }
440
+ :host([rounded="lg"]),
441
+ :host([radius="lg"]) {
442
+ --fx-table-radius: var(--fx-radius-lg);
443
+ }
444
+ /* Moldura: uma única borda envolvendo a tabela E a paginação, para que
445
+ o pager não fique "solto" fora do contorno da tabela. */
446
+ .frame {
447
+ border: 1px solid var(--fx-border-default);
448
+ border-radius: var(--fx-table-radius);
449
+ background: var(--fx-surface-background);
450
+ }
451
+ /* Com toolbar no topo, quem desenha a borda superior é a própria
452
+ toolbar — a moldura desenha apenas o contorno restante. */
453
+ .frame.has-toolbar {
454
+ border-top: none;
455
+ }
456
+ /* "no-borders-x": mantém somente as bordas de topo e de base
457
+ (tabela encaixada em um layout que já desenha as laterais). */
458
+ :host([no-borders-x]) .frame,
459
+ :host([no-borders-x]) .toolbar {
460
+ border-left: none;
461
+ border-right: none;
388
462
  }
389
463
  .table-wrap {
390
464
  position: relative;
391
465
  }
392
466
  .scroll {
393
467
  overflow-x: auto;
394
- border: 1px solid var(--fx-border-default);
395
- border-radius: var(--fx-radius-md);
468
+ /* O topo acompanha o raio da moldura; a base fica reta porque o
469
+ rodapé (.pager) é quem a desenha. */
470
+ border-radius: var(--fx-table-radius) var(--fx-table-radius) 0 0;
471
+ }
472
+ /* Sem paginação não existe rodapé: a moldura fecha embaixo. */
473
+ :host(:not([pagination])) .scroll {
474
+ border-radius: var(--fx-table-radius);
396
475
  }
397
476
  table {
398
477
  width: 100%;
@@ -479,7 +558,13 @@ _FxTable.styles = css`
479
558
  align-items: center;
480
559
  gap: var(--fx-space-sm);
481
560
  flex-wrap: wrap;
482
- padding: var(--fx-space-sm) 0;
561
+ /* O rodapé é parte da moldura: borda só no topo (separando da
562
+ tabela) e o mesmo padding horizontal das células. Antes ele
563
+ ficava fora do contorno, sem borda e sem respiro lateral. */
564
+ padding: var(--fx-space-sm) var(--fx-space-md);
565
+ border-top: 1px solid var(--fx-border-default);
566
+ border-radius: 0 0 var(--fx-table-radius) var(--fx-table-radius);
567
+ background: var(--fx-surface-background);
483
568
  }
484
569
  :host([pagination-position="center"]) .pager {
485
570
  justify-content: center;
@@ -528,15 +613,27 @@ _FxTable.styles = css`
528
613
  color: #fff;
529
614
  font-weight: var(--fx-font-weight);
530
615
  }
531
- fx-select {
616
+ /* O seletor "Por página" só mostra números, então não pode herdar a
617
+ largura padrão do <fx-select> (200px / 180px em size="sm"), que
618
+ deixava o rodapé da paginação desproporcional. O limite tem que ser
619
+ no HOST — apertar só ::part(trigger) não funciona, porque o
620
+ trigger é width:100% e continua herdando o min-width do host.
621
+ O painel (dropdown) continua largo (220px) em select.ts. */
622
+ fx-select.rows-sel {
532
623
  vertical-align: middle;
533
- }
534
- fx-select::part(trigger) {
535
- min-width: 64px !important;
536
- min-height: var(--fx-size-sm) !important;
537
- padding: 0 var(--fx-space-sm) !important;
538
- border-radius: var(--fx-radius-sm) !important;
539
- font-size: calc(var(--fx-font-size) - 2px) !important;
624
+ /* Não deixa o flex do .pager esticar nem esmagar o controle. */
625
+ flex: none;
626
+ width: auto;
627
+ --fx-select-min-width: 56px;
628
+ --fx-select-min-width-sm: 56px;
629
+ }
630
+ fx-select.rows-sel::part(trigger) {
631
+ min-width: 0;
632
+ min-height: var(--fx-size-sm);
633
+ /* Padding curto: sobra espaço para o número + caret. */
634
+ padding: 0 var(--fx-space-xs);
635
+ border-radius: var(--fx-radius-sm);
636
+ font-size: calc(var(--fx-font-size) - 2px);
540
637
  }
541
638
  .toolbar {
542
639
  display: flex;
@@ -546,7 +643,7 @@ _FxTable.styles = css`
546
643
  padding: var(--fx-space-sm) var(--fx-space-md);
547
644
  border: 1px solid var(--fx-border-default);
548
645
  border-bottom: none;
549
- border-radius: var(--fx-radius-md) var(--fx-radius-md) 0 0;
646
+ border-radius: var(--fx-table-radius) var(--fx-table-radius) 0 0;
550
647
  background: var(--fx-surface-background);
551
648
  }
552
649
  .toolbar:empty {
@@ -567,7 +664,7 @@ _FxTable.styles = css`
567
664
  );
568
665
  backdrop-filter: blur(2px);
569
666
  z-index: 10;
570
- border-radius: var(--fx-radius-md);
667
+ border-radius: var(--fx-table-radius) var(--fx-table-radius) 0 0;
571
668
  }
572
669
  .tbl-spinner {
573
670
  width: 32px;