@wrrdev/fenix-ui 1.1.3 → 1.1.5

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 (78) hide show
  1. package/README.md +2 -2
  2. package/dist/assets/base-css-Be_r8aTo.js +74 -0
  3. package/dist/assets/base-css-Be_r8aTo.js.map +1 -0
  4. package/dist/components/autocomplete/autocomplete.d.ts.map +1 -1
  5. package/dist/components/autocomplete/autocomplete.js +21 -7
  6. package/dist/components/autocomplete/autocomplete.js.map +1 -1
  7. package/dist/components/button/button.d.ts +4 -2
  8. package/dist/components/button/button.d.ts.map +1 -1
  9. package/dist/components/button/button.js +29 -9
  10. package/dist/components/button/button.js.map +1 -1
  11. package/dist/components/chip/chip.d.ts.map +1 -1
  12. package/dist/components/chip/chip.js +4 -2
  13. package/dist/components/chip/chip.js.map +1 -1
  14. package/dist/components/confirmpopup/confirmpopup.d.ts.map +1 -1
  15. package/dist/components/confirmpopup/confirmpopup.js +2 -0
  16. package/dist/components/confirmpopup/confirmpopup.js.map +1 -1
  17. package/dist/components/datepicker/datepicker.d.ts.map +1 -1
  18. package/dist/components/datepicker/datepicker.js +5 -1
  19. package/dist/components/datepicker/datepicker.js.map +1 -1
  20. package/dist/components/dialog/dialog.d.ts.map +1 -1
  21. package/dist/components/dialog/dialog.js +2 -0
  22. package/dist/components/dialog/dialog.js.map +1 -1
  23. package/dist/components/empty-state/empty-state.d.ts.map +1 -1
  24. package/dist/components/empty-state/empty-state.js +4 -1
  25. package/dist/components/empty-state/empty-state.js.map +1 -1
  26. package/dist/components/fileupload/fileupload.d.ts.map +1 -1
  27. package/dist/components/fileupload/fileupload.js +10 -3
  28. package/dist/components/fileupload/fileupload.js.map +1 -1
  29. package/dist/components/floatlabel/floatlabel.d.ts +8 -0
  30. package/dist/components/floatlabel/floatlabel.d.ts.map +1 -1
  31. package/dist/components/floatlabel/floatlabel.js +66 -3
  32. package/dist/components/floatlabel/floatlabel.js.map +1 -1
  33. package/dist/components/input/input.d.ts +4 -1
  34. package/dist/components/input/input.d.ts.map +1 -1
  35. package/dist/components/input/input.js +68 -2
  36. package/dist/components/input/input.js.map +1 -1
  37. package/dist/components/multiselect/multiselect.d.ts.map +1 -1
  38. package/dist/components/multiselect/multiselect.js +4 -1
  39. package/dist/components/multiselect/multiselect.js.map +1 -1
  40. package/dist/components/popover/popover.d.ts.map +1 -1
  41. package/dist/components/popover/popover.js +2 -0
  42. package/dist/components/popover/popover.js.map +1 -1
  43. package/dist/components/select/select.d.ts.map +1 -1
  44. package/dist/components/select/select.js +5 -1
  45. package/dist/components/select/select.js.map +1 -1
  46. package/dist/components/textarea/textarea.d.ts.map +1 -1
  47. package/dist/components/textarea/textarea.js +5 -2
  48. package/dist/components/textarea/textarea.js.map +1 -1
  49. package/dist/core/jsx.d.ts +25 -0
  50. package/dist/core/jsx.d.ts.map +1 -1
  51. package/dist/core/jsx.js.map +1 -1
  52. package/dist/core/vue.d.ts +3 -1
  53. package/dist/core/vue.d.ts.map +1 -1
  54. package/dist/core/vue.js.map +1 -1
  55. package/dist/docs/app.d.ts.map +1 -1
  56. package/dist/docs/componentes/alert.doc.d.ts.map +1 -1
  57. package/dist/docs/componentes/autocomplete.doc.d.ts.map +1 -1
  58. package/dist/docs/componentes/button.doc.d.ts.map +1 -1
  59. package/dist/docs/componentes/confirmpopup.doc.d.ts.map +1 -1
  60. package/dist/docs/componentes/datepicker.doc.d.ts.map +1 -1
  61. package/dist/docs/componentes/fileupload.doc.d.ts.map +1 -1
  62. package/dist/docs/componentes/floatlabel.doc.d.ts.map +1 -1
  63. package/dist/docs/componentes/input.doc.d.ts.map +1 -1
  64. package/dist/docs/componentes/multiselect.doc.d.ts.map +1 -1
  65. package/dist/docs/componentes/select.doc.d.ts.map +1 -1
  66. package/dist/docs/componentes/textarea.doc.d.ts.map +1 -1
  67. package/dist/fenix-ui.esm.js.map +1 -1
  68. package/dist/fenix-ui.umd.min.js +342 -218
  69. package/dist/fenix-ui2.esm.js +2 -2
  70. package/dist/fenix-ui3.esm.js +308 -147
  71. package/dist/fenix-ui3.esm.js.map +1 -1
  72. package/dist/icons/base-css.d.ts +16 -0
  73. package/dist/icons/base-css.d.ts.map +1 -1
  74. package/dist/icons/base-css.js +10 -0
  75. package/dist/icons/base-css.js.map +1 -1
  76. package/package.json +1 -1
  77. package/dist/assets/base-css-BifSeW2H.js +0 -31
  78. package/dist/assets/base-css-BifSeW2H.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.3**, acesso público.
21
+ > 📦 npm: [`@wrrdev/fenix-ui`](https://www.npmjs.com/package/@wrrdev/fenix-ui) — **v1.1.5**, acesso público.
22
22
  > Subpaths disponíveis: `./theme`, `./tokens`, `./jsx`, `./vue`, `./button`, `./badge`, `./spinner`, `./select`, `./input`, `./switch`, `./textarea`, `./checkbox`, `./radio`, `./calendar`, `./datepicker`, `./multiselect`, `./table`, `./floatlabel`, `./dialog`, `./toast`, `./tooltip`, `./tabs`, `./progress`, `./skeleton`, `./alert`, `./drawer`, `./dropdown`, `./sidebar`, `./pagination`, `./autocomplete`, `./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.3):
68
+ 🔗 Links diretos para CDN (versão 1.1.5):
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
  -->
@@ -0,0 +1,74 @@
1
+ const HTML_ENTITIES = {
2
+ "&": "&amp;",
3
+ "<": "&lt;",
4
+ ">": "&gt;",
5
+ '"': "&quot;",
6
+ "'": "&#39;",
7
+ "/": "&#x2F;",
8
+ "`": "&#x60;",
9
+ "=": "&#x3D;"
10
+ };
11
+ const SANITIZE_REGEX = /[&<>"'`=/]/g;
12
+ function esc(value) {
13
+ if (value == null) return "";
14
+ return String(value).replace(SANITIZE_REGEX, (char) => HTML_ENTITIES[char] || char);
15
+ }
16
+ function stripTags(html) {
17
+ return html.replace(/<[^>]*>/g, "");
18
+ }
19
+ function sanitizeData(data) {
20
+ if (data == null) return data;
21
+ if (typeof data === "string") return esc(data);
22
+ if (Array.isArray(data)) return data.map(sanitizeData);
23
+ if (typeof data === "object") {
24
+ const result = {};
25
+ for (const [key, value] of Object.entries(data)) {
26
+ result[key] = sanitizeData(value);
27
+ }
28
+ return result;
29
+ }
30
+ return data;
31
+ }
32
+ const FENIX_ICON_FONT_FAMILY = "Fenix Icons";
33
+ const FENIX_ICON_FONT_VAR = `var(--fx-icon-font, ${FENIX_ICON_FONT_FAMILY})`;
34
+ const FENIX_ICON_BASE_CSS = `
35
+ .fx-icon {
36
+ font-family: ${FENIX_ICON_FONT_VAR};
37
+ font-weight: normal;
38
+ font-style: normal;
39
+ line-height: 1;
40
+ letter-spacing: normal;
41
+ text-transform: none;
42
+ display: inline-block;
43
+ white-space: nowrap;
44
+ word-wrap: normal;
45
+ direction: ltr;
46
+ font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
47
+ -webkit-font-smoothing: antialiased;
48
+ text-rendering: optimizeLegibility;
49
+ user-select: none;
50
+ }
51
+ .fx-icon.fx-icon-fill { font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 24; }
52
+ .fx-icon.fx-icon-bold { font-variation-settings: 'FILL' 0, 'wght' 600, 'GRAD' 0, 'opsz' 24; }
53
+ `;
54
+ function isFenixIconName(value) {
55
+ return /^[a-z][a-z0-9_]*$/.test(value.trim());
56
+ }
57
+ function fenixIconHtml(icon) {
58
+ const value = (icon ?? "").trim();
59
+ if (!value) return "";
60
+ if (isFenixIconName(value)) {
61
+ return `<i class="fx-icon" aria-hidden="true">${value}</i>`;
62
+ }
63
+ return `<span aria-hidden="true">${esc(value)}</span>`;
64
+ }
65
+ export {
66
+ FENIX_ICON_FONT_FAMILY as F,
67
+ FENIX_ICON_BASE_CSS as a,
68
+ stripTags as b,
69
+ esc as e,
70
+ fenixIconHtml as f,
71
+ isFenixIconName as i,
72
+ sanitizeData as s
73
+ };
74
+ //# sourceMappingURL=base-css-Be_r8aTo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base-css-Be_r8aTo.js","sources":["../../src/core/sanitize.ts","../../src/icons/base-css.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"],"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;"}
@@ -1 +1 @@
1
- {"version":3,"file":"autocomplete.d.ts","sourceRoot":"","sources":["../../../src/components/autocomplete/autocomplete.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAK5C;;;;;;GAMG;AACH,qBAAa,cAAe,SAAQ,SAAS;IAC3C,OAAgB,MAAM,SA8DpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,CAAC,EAAE,MAAM,EAAmC;IACrD,IAAI,KAAK,IAAI,MAAM,CAAkC;IACrD,IAAI,KAAK,CAAC,CAAC,EAAE,MAAM,EAAoC;IAEvD,IAAI,MAAM,IAAI,MAAM,EAAE,CAOrB;IACD;2DACuD;IACvD,IAAI,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EAElC;IAED,OAAO,CAAC,WAAW,CAAC,CAAqB;cAEtB,iBAAiB,IAAI,IAAI;cAQzB,oBAAoB,IAAI,IAAI;cAM5B,MAAM,IAAI,IAAI;CA6DlC;AAED,wBAAgB,oBAAoB,IAAI,OAAO,cAAc,CAE5D"}
1
+ {"version":3,"file":"autocomplete.d.ts","sourceRoot":"","sources":["../../../src/components/autocomplete/autocomplete.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAK5C;;;;;;GAMG;AACH,qBAAa,cAAe,SAAQ,SAAS;IAC3C,OAAgB,MAAM,SA4EpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAIjD;IAED,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,CAAC,EAAE,MAAM,EAAmC;IACrD,IAAI,KAAK,IAAI,MAAM,CAAkC;IACrD,IAAI,KAAK,CAAC,CAAC,EAAE,MAAM,EAAoC;IAEvD,IAAI,MAAM,IAAI,MAAM,EAAE,CAOrB;IACD;2DACuD;IACvD,IAAI,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EAElC;IAED,OAAO,CAAC,WAAW,CAAC,CAAqB;cAEtB,iBAAiB,IAAI,IAAI;cAQzB,oBAAoB,IAAI,IAAI;cAM5B,MAAM,IAAI,IAAI;CA6DlC;AAED,wBAAgB,oBAAoB,IAAI,OAAO,cAAc,CAE5D"}
@@ -4,7 +4,7 @@ import { defineElement } from "../../core/define.js";
4
4
  import { esc } from "../../core/sanitize.js";
5
5
  const _FxAutocomplete = class _FxAutocomplete extends FxElement {
6
6
  static get observedAttributes() {
7
- return ["size", "placeholder", "source", "disabled", "min-chars"];
7
+ return ["size", "placeholder", "source", "disabled", "min-chars", "error", "invalid", "success", "valid"];
8
8
  }
9
9
  get size() {
10
10
  const s = this.getAttr("size", "md");
@@ -106,7 +106,7 @@ _FxAutocomplete.styles = css`
106
106
  font-family: var(--fx-font-family);
107
107
  font-size: var(--fx-font-size);
108
108
  }
109
- input {
109
+ .field {
110
110
  font-family: inherit;
111
111
  font-size: inherit;
112
112
  font-weight: var(--fx-font-weight);
@@ -122,15 +122,29 @@ _FxAutocomplete.styles = css`
122
122
  border-color var(--fx-motion-duration-normal) var(--fx-motion-easing),
123
123
  box-shadow var(--fx-motion-duration-normal) var(--fx-motion-easing);
124
124
  }
125
- input::placeholder { color: var(--fx-text-muted); opacity: 1; }
126
- input:hover { border-color: var(--fx-border-hover); }
127
- input:focus-visible {
125
+ .field::placeholder { color: var(--fx-text-muted); opacity: 1; }
126
+ .field:hover { border-color: var(--fx-border-hover); }
127
+ .field:focus-visible {
128
128
  outline: none;
129
129
  border-color: var(--fx-color-primary);
130
130
  box-shadow: var(--fx-effect-focus-ring, none);
131
131
  }
132
- :host([size='sm']) input { width: 220px; min-height: var(--fx-size-sm); padding: var(--fx-space-sm) var(--fx-space-md); }
133
- :host([size='lg']) input { width: 300px; min-height: var(--fx-size-lg); }
132
+ :host([size='sm']) .field { width: 220px; min-height: var(--fx-size-sm); padding: var(--fx-space-sm) var(--fx-space-md); }
133
+ :host([size='lg']) .field { width: 300px; min-height: var(--fx-size-lg); }
134
+ /* Full width: o host estica até o pai e o campo interno acompanha. */
135
+ :host([full]) { display: block; width: 100%; }
136
+ :host([full]) .field { width: 100%; }
137
+ /* Validação: sobrescrevem a borda/foco via token do preset. */
138
+ :host([error]) .field,
139
+ :host([invalid]) .field {
140
+ border-color: var(--fx-color-danger, #dc2626);
141
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--fx-color-danger, #dc2626) 18%, transparent);
142
+ }
143
+ :host([success]) .field,
144
+ :host([valid]) .field {
145
+ border-color: var(--fx-color-success, #16a34a);
146
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--fx-color-success, #16a34a) 18%, transparent);
147
+ }
134
148
  .list {
135
149
  position: absolute;
136
150
  top: calc(100% + 4px);
@@ -1 +1 @@
1
- {"version":3,"file":"autocomplete.js","sources":["../../../src/components/autocomplete/autocomplete.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-autocomplete> — Campo de texto com sugestões filtradas.\n *\n * Atributos: value, size, placeholder, source (JSON array de strings),\n * disabled, min-chars (padrão 1).\n * Evento: `select` (composed, detail: { value }).\n */\nexport class FxAutocomplete extends FxElement {\n static override styles = css`\n :host {\n position: relative;\n display: inline-block;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n }\n input {\n font-family: inherit;\n font-size: inherit;\n font-weight: var(--fx-font-weight);\n color: var(--fx-text-default);\n background-color: var(--fx-surface-background);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-md);\n padding: var(--fx-space-md) var(--fx-space-lg);\n width: 260px;\n min-height: var(--fx-size-md);\n box-sizing: border-box;\n transition:\n border-color var(--fx-motion-duration-normal) var(--fx-motion-easing),\n box-shadow var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n input::placeholder { color: var(--fx-text-muted); opacity: 1; }\n input:hover { border-color: var(--fx-border-hover); }\n input:focus-visible {\n outline: none;\n border-color: var(--fx-color-primary);\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n :host([size='sm']) input { width: 220px; min-height: var(--fx-size-sm); padding: var(--fx-space-sm) var(--fx-space-md); }\n :host([size='lg']) input { width: 300px; min-height: var(--fx-size-lg); }\n .list {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n right: 0;\n background: var(--fx-surface-background);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-md);\n box-shadow: var(--fx-shadow-lg);\n z-index: var(--fx-z-dropdown, 1000);\n max-height: 220px;\n overflow-y: auto;\n display: none;\n }\n :host([open]) .list { display: block; }\n .opt {\n display: block;\n width: 100%;\n text-align: left;\n border: none;\n background: transparent;\n font-family: inherit;\n font-size: inherit;\n color: var(--fx-text-default);\n padding: var(--fx-space-sm) var(--fx-space-md);\n cursor: pointer;\n transition: background var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n .opt:hover { background: color-mix(in srgb, var(--fx-color-primary) 12%, var(--fx-surface-background)); color: var(--fx-color-primary); }\n .empty { color: var(--fx-text-muted); font-size: calc(var(--fx-font-size) - 2px); padding: var(--fx-space-sm) var(--fx-space-md); }\n `;\n\n static override get observedAttributes(): string[] {\n return ['size', 'placeholder', 'source', 'disabled', 'min-chars'];\n }\n\n get size(): string {\n const s = this.getAttr('size', 'md');\n return s === 'sm' || s === 'lg' ? s : 'md';\n }\n set size(v: string) { this.setAttribute('size', v); }\n get value(): string { return this.getAttr('value'); }\n set value(v: string) { this.setAttribute('value', v); }\n\n get source(): string[] {\n try {\n const raw = JSON.parse(this.getAttr('source', '[]'));\n return Array.isArray(raw) ? raw.map(String) : [];\n } catch {\n return [];\n }\n }\n /** Setter necessário para o Vue (patchDOMProp seta `source` como\n * propriedade, pois o getter existe no prototype). */\n set source(value: string | string[]) {\n this.setAttribute('source', typeof value === 'string' ? value : JSON.stringify(value));\n }\n\n private docListener?: (e: Event) => void;\n\n protected override connectedCallback(): void {\n super.connectedCallback();\n this.docListener = (e: Event) => {\n if (!this.contains(e.target as Node)) this.toggleAttr('open', false);\n };\n document.addEventListener('click', this.docListener);\n }\n\n protected override disconnectedCallback(): void {\n super.disconnectedCallback();\n if (this.docListener) document.removeEventListener('click', this.docListener);\n this.docListener = undefined;\n }\n\n protected override render(): void {\n const placeholder = this.getAttr('placeholder');\n\n this.setTemplate(`\n <input class=\"field\" part=\"input\" type=\"text\"\n ${placeholder ? `placeholder=\"${esc(placeholder)}\"` : ''} autocomplete=\"off\"/>\n <div class=\"list\" part=\"list\" role=\"listbox\"></div>\n `);\n\n const field = this.root.querySelector<HTMLInputElement>('.field');\n if (!field) return;\n field.value = this.value;\n if (this.hasAttr('disabled')) field.setAttribute('disabled', '');\n\n const list = this.root.querySelector<HTMLElement>('.list');\n const minChars = Number(this.getAttr('min-chars', '1')) || 1;\n\n const openList = (): void => {\n if (!list) return;\n const q = field.value.toLowerCase();\n const matches = this.source.filter((s) => s.toLowerCase().includes(q)).slice(0, 8);\n list.innerHTML = matches.length\n ? matches.map((m) => `<button type=\"button\" class=\"opt\" role=\"option\" data-v=\"${esc(m)}\">${esc(m)}</button>`).join('')\n : '<div class=\"empty\">Nenhum resultado</div>';\n list.querySelectorAll<HTMLButtonElement>('.opt').forEach((btn) => {\n btn.addEventListener('click', () => {\n field.value = btn.dataset.v ?? '';\n this.value = field.value;\n this.toggleAttr('open', false);\n this.dispatchEvent(new CustomEvent('select', {\n bubbles: true, composed: true, detail: { value: this.value },\n }));\n });\n });\n this.toggleAttr('open', true);\n };\n\n // Reabrir com foco sem re-render (evita perder digitação)\n field.addEventListener('input', () => {\n this.value = field.value;\n openList();\n });\n field.addEventListener('focus', () => { if (field.value.length >= minChars) openList(); });\n // Navegação por teclado: setas movem o foco entre as sugestões; ESC fecha.\n field.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') {\n this.toggleAttr('open', false);\n return;\n }\n if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') return;\n e.preventDefault();\n if (!this.hasAttr('open')) openList();\n const opts = Array.from(this.root.querySelectorAll<HTMLButtonElement>('.opt'));\n if (!opts.length) return;\n const current = opts.indexOf(this.root.activeElement as HTMLButtonElement);\n const next = e.key === 'ArrowDown'\n ? (current === -1 ? 0 : Math.min(current + 1, opts.length - 1))\n : (current === -1 ? opts.length - 1 : Math.max(current - 1, 0));\n opts[next]?.focus();\n });\n }\n}\n\nexport function defineFxAutocomplete(): typeof FxAutocomplete {\n return defineElement('fx-autocomplete', FxAutocomplete);\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"autocomplete.js","sources":["../../../src/components/autocomplete/autocomplete.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-autocomplete> — Campo de texto com sugestões filtradas.\n *\n * Atributos: value, size, placeholder, source (JSON array de strings),\n * disabled, min-chars (padrão 1).\n * Evento: `select` (composed, detail: { value }).\n */\nexport class FxAutocomplete extends FxElement {\n static override styles = css`\n :host {\n position: relative;\n display: inline-block;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n }\n .field {\n font-family: inherit;\n font-size: inherit;\n font-weight: var(--fx-font-weight);\n color: var(--fx-text-default);\n background-color: var(--fx-surface-background);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-md);\n padding: var(--fx-space-md) var(--fx-space-lg);\n width: 260px;\n min-height: var(--fx-size-md);\n box-sizing: border-box;\n transition:\n border-color var(--fx-motion-duration-normal) var(--fx-motion-easing),\n box-shadow var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n .field::placeholder { color: var(--fx-text-muted); opacity: 1; }\n .field:hover { border-color: var(--fx-border-hover); }\n .field:focus-visible {\n outline: none;\n border-color: var(--fx-color-primary);\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n :host([size='sm']) .field { width: 220px; min-height: var(--fx-size-sm); padding: var(--fx-space-sm) var(--fx-space-md); }\n :host([size='lg']) .field { width: 300px; min-height: var(--fx-size-lg); }\n /* Full width: o host estica até o pai e o campo interno acompanha. */\n :host([full]) { display: block; width: 100%; }\n :host([full]) .field { width: 100%; }\n /* Validação: sobrescrevem a borda/foco via token do preset. */\n :host([error]) .field,\n :host([invalid]) .field {\n border-color: var(--fx-color-danger, #dc2626);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--fx-color-danger, #dc2626) 18%, transparent);\n }\n :host([success]) .field,\n :host([valid]) .field {\n border-color: var(--fx-color-success, #16a34a);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--fx-color-success, #16a34a) 18%, transparent);\n }\n .list {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n right: 0;\n background: var(--fx-surface-background);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-md);\n box-shadow: var(--fx-shadow-lg);\n z-index: var(--fx-z-dropdown, 1000);\n max-height: 220px;\n overflow-y: auto;\n display: none;\n }\n :host([open]) .list { display: block; }\n .opt {\n display: block;\n width: 100%;\n text-align: left;\n border: none;\n background: transparent;\n font-family: inherit;\n font-size: inherit;\n color: var(--fx-text-default);\n padding: var(--fx-space-sm) var(--fx-space-md);\n cursor: pointer;\n transition: background var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n .opt:hover { background: color-mix(in srgb, var(--fx-color-primary) 12%, var(--fx-surface-background)); color: var(--fx-color-primary); }\n .empty { color: var(--fx-text-muted); font-size: calc(var(--fx-font-size) - 2px); padding: var(--fx-space-sm) var(--fx-space-md); }\n `;\n\n static override get observedAttributes(): string[] {\n // `error`/`success` (+ aliases `invalid`/`valid`) PRECISAM ser observados:\n // é assim que o toggle no playground / setAttribute no submit re-renderiza.\n return ['size', 'placeholder', 'source', 'disabled', 'min-chars', 'error', 'invalid', 'success', 'valid'];\n }\n\n get size(): string {\n const s = this.getAttr('size', 'md');\n return s === 'sm' || s === 'lg' ? s : 'md';\n }\n set size(v: string) { this.setAttribute('size', v); }\n get value(): string { return this.getAttr('value'); }\n set value(v: string) { this.setAttribute('value', v); }\n\n get source(): string[] {\n try {\n const raw = JSON.parse(this.getAttr('source', '[]'));\n return Array.isArray(raw) ? raw.map(String) : [];\n } catch {\n return [];\n }\n }\n /** Setter necessário para o Vue (patchDOMProp seta `source` como\n * propriedade, pois o getter existe no prototype). */\n set source(value: string | string[]) {\n this.setAttribute('source', typeof value === 'string' ? value : JSON.stringify(value));\n }\n\n private docListener?: (e: Event) => void;\n\n protected override connectedCallback(): void {\n super.connectedCallback();\n this.docListener = (e: Event) => {\n if (!this.contains(e.target as Node)) this.toggleAttr('open', false);\n };\n document.addEventListener('click', this.docListener);\n }\n\n protected override disconnectedCallback(): void {\n super.disconnectedCallback();\n if (this.docListener) document.removeEventListener('click', this.docListener);\n this.docListener = undefined;\n }\n\n protected override render(): void {\n const placeholder = this.getAttr('placeholder');\n\n this.setTemplate(`\n <input class=\"field\" part=\"input\" type=\"text\"\n ${placeholder ? `placeholder=\"${esc(placeholder)}\"` : ''} autocomplete=\"off\"/>\n <div class=\"list\" part=\"list\" role=\"listbox\"></div>\n `);\n\n const field = this.root.querySelector<HTMLInputElement>('.field');\n if (!field) return;\n field.value = this.value;\n if (this.hasAttr('disabled')) field.setAttribute('disabled', '');\n\n const list = this.root.querySelector<HTMLElement>('.list');\n const minChars = Number(this.getAttr('min-chars', '1')) || 1;\n\n const openList = (): void => {\n if (!list) return;\n const q = field.value.toLowerCase();\n const matches = this.source.filter((s) => s.toLowerCase().includes(q)).slice(0, 8);\n list.innerHTML = matches.length\n ? matches.map((m) => `<button type=\"button\" class=\"opt\" role=\"option\" data-v=\"${esc(m)}\">${esc(m)}</button>`).join('')\n : '<div class=\"empty\">Nenhum resultado</div>';\n list.querySelectorAll<HTMLButtonElement>('.opt').forEach((btn) => {\n btn.addEventListener('click', () => {\n field.value = btn.dataset.v ?? '';\n this.value = field.value;\n this.toggleAttr('open', false);\n this.dispatchEvent(new CustomEvent('select', {\n bubbles: true, composed: true, detail: { value: this.value },\n }));\n });\n });\n this.toggleAttr('open', true);\n };\n\n // Reabrir com foco sem re-render (evita perder digitação)\n field.addEventListener('input', () => {\n this.value = field.value;\n openList();\n });\n field.addEventListener('focus', () => { if (field.value.length >= minChars) openList(); });\n // Navegação por teclado: setas movem o foco entre as sugestões; ESC fecha.\n field.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') {\n this.toggleAttr('open', false);\n return;\n }\n if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') return;\n e.preventDefault();\n if (!this.hasAttr('open')) openList();\n const opts = Array.from(this.root.querySelectorAll<HTMLButtonElement>('.opt'));\n if (!opts.length) return;\n const current = opts.indexOf(this.root.activeElement as HTMLButtonElement);\n const next = e.key === 'ArrowDown'\n ? (current === -1 ? 0 : Math.min(current + 1, opts.length - 1))\n : (current === -1 ? opts.length - 1 : Math.max(current - 1, 0));\n opts[next]?.focus();\n });\n }\n}\n\nexport function defineFxAutocomplete(): typeof FxAutocomplete {\n return defineElement('fx-autocomplete', FxAutocomplete);\n}\n"],"names":[],"mappings":"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"}
@@ -2,8 +2,10 @@ import { FxElement } from '../../core/base';
2
2
  /**
3
3
  * <fx-button> — Componente de ação (piloto).
4
4
  *
5
- * Atributos: variant, size, disabled, loading, type, full.
6
- * Slots: default (rótulo), `icon` (ícone opcional).
5
+ * Atributos: variant, size, disabled, loading, type, full (largura 100%
6
+ * acompanhando o elemento pai), icon (nome do glifo Fenix Icons ou
7
+ * emoji/texto livre) e icon-pos (left|right, padrão left).
8
+ * Slots: default (rótulo), `icon` (ícone customizado — vence o atributo).
7
9
  * Evento: `click` nativo atravessa o Shadow DOM (composed) — ouça no host.
8
10
  */
9
11
  export declare class FxButton extends FxElement {
@@ -1 +1 @@
1
- {"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../../src/components/button/button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAI5C;;;;;;GAMG;AACH,qBAAa,QAAS,SAAQ,SAAS;IACrC,OAAgB,MAAM,SAqGpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,+BAA+B;IAC/B,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAErB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAE1B;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAEzB;cAEkB,MAAM,IAAI,IAAI;cAmCd,iBAAiB,IAAI,IAAI;cAKzB,oBAAoB,IAAI,IAAI;IAK/C;;;;OAIG;IACH,OAAO,CAAC,WAAW,CAmBjB;CACH;AAED,wBAAgB,cAAc,IAAI,OAAO,QAAQ,CAEhD"}
1
+ {"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../../src/components/button/button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAK5C;;;;;;;;GAQG;AACH,qBAAa,QAAS,SAAQ,SAAS;IACrC,OAAgB,MAAM,SAiHpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,+BAA+B;IAC/B,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAErB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAE1B;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAEzB;cAEkB,MAAM,IAAI,IAAI;cAkDd,iBAAiB,IAAI,IAAI;cAKzB,oBAAoB,IAAI,IAAI;IAK/C;;;;OAIG;IACH,OAAO,CAAC,WAAW,CAmBjB;CACH;AAED,wBAAgB,cAAc,IAAI,OAAO,QAAQ,CAEhD"}
@@ -1,6 +1,7 @@
1
1
  import { FxElement } from "../../core/base.js";
2
2
  import { css } from "../../core/css.js";
3
3
  import { defineElement } from "../../core/define.js";
4
+ import { FENIX_ICON_BASE_CSS, fenixIconHtml } from "../../icons/base-css.js";
4
5
  const _FxButton = class _FxButton extends FxElement {
5
6
  constructor() {
6
7
  super(...arguments);
@@ -22,7 +23,7 @@ const _FxButton = class _FxButton extends FxElement {
22
23
  };
23
24
  }
24
25
  static get observedAttributes() {
25
- return ["variant", "size", "disabled", "loading", "type", "full"];
26
+ return ["variant", "size", "disabled", "loading", "type", "full", "icon", "icon-pos"];
26
27
  }
27
28
  /** Tamanho. Padrão: `'md'`. */
28
29
  get size() {
@@ -48,21 +49,28 @@ const _FxButton = class _FxButton extends FxElement {
48
49
  const loading = this.hasAttr("loading");
49
50
  const disabled = this.hasAttr("disabled") || loading;
50
51
  const btnType = this.getAttr("type", "button");
52
+ const icon = this.getAttr("icon");
53
+ const iconRight = this.getAttr("icon-pos", "left") === "right";
54
+ const slottedIcon = this.querySelector('[slot="icon"]');
55
+ const iconContent = slottedIcon ? '<slot name="icon"></slot>' : icon ? fenixIconHtml(icon) : "";
51
56
  this.setTemplate(`
52
57
  <button class="btn" part="button" type="${btnType}">
53
- <span class="btn__icon" part="icon"><slot name="icon"></slot></span>
58
+ ${!iconRight ? `<span class="btn__icon" part="icon">${iconContent}</span>` : ""}
54
59
  <span class="btn__label" part="label"><slot></slot></span>
60
+ ${iconRight ? `<span class="btn__icon" part="icon">${iconContent}</span>` : ""}
55
61
  ${loading ? '<span class="btn__spinner" aria-hidden="true"></span>' : ""}
56
62
  </button>
57
63
  `);
58
64
  const btn = this.root.querySelector(".btn");
59
65
  if (!btn) return;
60
- const iconSlot = btn.querySelector(".btn__icon slot");
61
- const iconWrap = this.root.querySelector(".btn__icon");
62
- const hasIcon = Boolean(iconSlot?.assignedNodes({ flatten: true }).some(
63
- (n) => n.nodeType === Node.ELEMENT_NODE || (n.textContent ?? "").trim() !== ""
64
- ));
65
- iconWrap?.toggleAttribute("hidden", !hasIcon);
66
+ const hasIcon = iconContent.trim() !== "";
67
+ for (const wrap of this.root.querySelectorAll(".btn__icon")) {
68
+ wrap.toggleAttribute("hidden", !hasIcon);
69
+ }
70
+ const hasLabel = [...this.childNodes ?? []].some(
71
+ (n) => n.nodeType === Node.ELEMENT_NODE ? n.getAttribute?.("slot") !== "icon" : (n.textContent ?? "").trim() !== ""
72
+ );
73
+ btn.classList.toggle("btn--icon-only", !hasLabel && hasIcon);
66
74
  if (disabled) {
67
75
  btn.setAttribute("disabled", "");
68
76
  btn.setAttribute("aria-disabled", "true");
@@ -83,10 +91,18 @@ const _FxButton = class _FxButton extends FxElement {
83
91
  }
84
92
  };
85
93
  _FxButton.styles = css`
94
+ ${FENIX_ICON_BASE_CSS}
86
95
  :host {
87
96
  display: inline-block;
88
97
  vertical-align: middle;
89
98
  }
99
+ /* Full width: o host inline-block é shrink-to-fit, então precisa virar
100
+ block para o 100% do .btn interno ter contra o que esticar. */
101
+ :host([full]) {
102
+ display: block;
103
+ width: 100%;
104
+ max-width: 100%;
105
+ }
90
106
  .btn {
91
107
  display: inline-flex;
92
108
  align-items: center;
@@ -138,9 +154,13 @@ _FxButton.styles = css`
138
154
  :host([variant='outline']) .btn:hover { background: var(--fx-surface-surface-hover); filter: none; }
139
155
 
140
156
  /* Ícone e spinner */
141
- .btn__icon { display: inline-flex; }
157
+ .btn__icon { display: inline-flex; align-items: center; font-size: var(--fx-button-icon-size, calc(var(--fx-font-size) + 6px)); line-height: 1; }
142
158
  .btn__icon[hidden] { display: none; }
143
159
  .btn__label { display: inline-flex; align-items: center; justify-content: center; }
160
+ /* Icon-only: a label some (o gap do flex some junto) e o botão vira
161
+ quadrado com padding simétrico — ícone perfeitamente centralizado. */
162
+ .btn--icon-only { padding-left: var(--fx-space-md); padding-right: var(--fx-space-md); }
163
+ .btn--icon-only .btn__label { display: none; }
144
164
  .btn[hidden] { display: none; }
145
165
  .btn__spinner {
146
166
  width: 1em;
@@ -1 +1 @@
1
- {"version":3,"file":"button.js","sources":["../../../src/components/button/button.ts"],"sourcesContent":["import { FxElement } from '../../core/base';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\n\n/**\n * <fx-button> — Componente de ação (piloto).\n *\n * Atributos: variant, size, disabled, loading, type, full.\n * Slots: default (rótulo), `icon` (ícone opcional).\n * Evento: `click` nativo atravessa o Shadow DOM (composed) — ouça no host.\n */\nexport class FxButton extends FxElement {\n static override styles = css`\n :host {\n display: inline-block;\n vertical-align: middle;\n }\n .btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n gap: var(--fx-space-sm);\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n font-weight: var(--fx-font-weight);\n line-height: var(--fx-font-line-height);\n padding: var(--fx-space-md) var(--fx-space-xl);\n border-radius: var(--fx-radius-md);\n border: 1px solid transparent;\n color: #fff;\n background: var(--fx-color-primary);\n cursor: pointer;\n user-select: none;\n text-decoration: none;\n transition:\n background-color var(--fx-motion-duration-normal) var(--fx-motion-easing),\n border-color var(--fx-motion-duration-normal) var(--fx-motion-easing),\n transform var(--fx-motion-duration-fast) var(--fx-motion-easing),\n box-shadow var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n .btn:focus-visible {\n outline: 2px solid var(--fx-color-primary);\n outline-offset: 2px;\n }\n .btn:hover { filter: brightness(0.88); }\n .btn:active { transform: translateY(1px); }\n\n /* Tamanhos — alturas vindas dos tokens (personalizáveis por preset) */\n .btn { min-height: var(--fx-size-md); }\n :host([size='sm']) .btn { padding: var(--fx-space-sm) var(--fx-space-lg); font-size: var(--fx-font-size); min-height: var(--fx-size-sm); }\n :host([size='lg']) .btn { padding: var(--fx-space-lg) var(--fx-space-xl); font-size: calc(var(--fx-font-size) + 4px); min-height: var(--fx-size-lg); }\n\n /* Full width */\n :host([full]) .btn { width: 100%; }\n\n /* Variantes */\n :host([variant='secondary']) .btn { background: var(--fx-color-secondary); }\n :host([variant='success']) .btn { background: var(--fx-color-success); }\n :host([variant='warning']) .btn { background: var(--fx-color-warning); }\n :host([variant='danger']) .btn { background: var(--fx-color-danger); }\n :host([variant='info']) .btn { background: var(--fx-color-info); }\n :host([variant='ghost']) .btn { background: transparent; color: var(--fx-text-default); }\n :host([variant='ghost']) .btn:hover { background: var(--fx-surface-surface-hover); filter: none; }\n :host([variant='outline']) .btn { background: transparent; color: var(--fx-color-primary); border-color: var(--fx-color-primary); }\n :host([variant='outline']) .btn:hover { background: var(--fx-surface-surface-hover); filter: none; }\n\n /* Ícone e spinner */\n .btn__icon { display: inline-flex; }\n .btn__icon[hidden] { display: none; }\n .btn__label { display: inline-flex; align-items: center; justify-content: center; }\n .btn[hidden] { display: none; }\n .btn__spinner {\n width: 1em;\n height: 1em;\n border: 2px solid currentColor;\n border-top-color: transparent;\n border-radius: var(--fx-radius-full);\n animation: fx-spin var(--fx-motion-duration-normal) linear infinite;\n }\n @keyframes fx-spin { to { transform: rotate(360deg); } }\n\n /* Estados */\n :host([disabled]) .btn,\n :host([loading]) .btn {\n opacity: 0.55;\n cursor: not-allowed;\n pointer-events: none;\n }\n\n /* Efeito ripple (). Desative com effect.ripple: '0' no preset. */\n .btn {\n position: relative;\n overflow: hidden;\n }\n .btn__ripple {\n position: absolute;\n border-radius: var(--fx-radius-full);\n background: currentColor;\n opacity: 0.28;\n transform: scale(0);\n animation: fx-ripple 600ms var(--fx-motion-easing) forwards;\n pointer-events: none;\n }\n @keyframes fx-ripple {\n to {\n transform: scale(2.6);\n opacity: 0;\n }\n }\n @media (prefers-reduced-motion: reduce) {\n .btn__ripple { display: none; }\n }\n `;\n\n static override get observedAttributes(): string[] {\n return ['variant', 'size', 'disabled', 'loading', 'type', 'full'];\n }\n\n /** Tamanho. Padrão: `'md'`. */\n get size(): string {\n const s = this.getAttr('size', 'md');\n return s === 'sm' || s === 'lg' ? s : 'md';\n }\n set size(value: string) {\n this.setAttribute('size', value);\n }\n\n get disabled(): boolean {\n return this.hasAttr('disabled');\n }\n set disabled(value: boolean) {\n this.toggleAttr('disabled', Boolean(value));\n }\n\n get loading(): boolean {\n return this.hasAttr('loading');\n }\n set loading(value: boolean) {\n this.toggleAttr('loading', Boolean(value));\n }\n\n protected override render(): void {\n const loading = this.hasAttr('loading');\n const disabled = this.hasAttr('disabled') || loading;\n const btnType = this.getAttr('type', 'button');\n\n this.setTemplate(`\n <button class=\"btn\" part=\"button\" type=\"${btnType}\">\n <span class=\"btn__icon\" part=\"icon\"><slot name=\"icon\"></slot></span>\n <span class=\"btn__label\" part=\"label\"><slot></slot></span>\n ${loading ? '<span class=\"btn__spinner\" aria-hidden=\"true\"></span>' : ''}\n </button>\n `);\n\n const btn = this.root.querySelector<HTMLButtonElement>('.btn');\n if (!btn) return;\n\n // Esconde o container do ícone quando o slot está vazio,\n // evitando espaço fantasma do `gap` que descentraliza o rótulo.\n const iconSlot = btn.querySelector<HTMLSlotElement>('.btn__icon slot');\n const iconWrap = this.root.querySelector<HTMLElement>('.btn__icon');\n const hasIcon = Boolean(iconSlot?.assignedNodes({ flatten: true }).some((n) =>\n n.nodeType === Node.ELEMENT_NODE || (n.textContent ?? '').trim() !== '',\n ));\n iconWrap?.toggleAttribute('hidden', !hasIcon);\n if (disabled) {\n btn.setAttribute('disabled', '');\n btn.setAttribute('aria-disabled', 'true');\n } else {\n btn.removeAttribute('disabled');\n btn.removeAttribute('aria-disabled');\n }\n if (loading) btn.setAttribute('aria-busy', 'true');\n else btn.removeAttribute('aria-busy');\n }\n\n protected override connectedCallback(): void {\n super.connectedCallback();\n this.addEventListener('pointerdown', this.spawnRipple);\n }\n\n protected override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeEventListener('pointerdown', this.spawnRipple);\n }\n\n /**\n * Efeito ripple : cria um círculo que expande do ponto\n * de clique e desaparece. Respeita o token `effect.ripple` ('0' desativa)\n * e `prefers-reduced-motion` (via CSS).\n */\n private spawnRipple = (event: PointerEvent): void => {\n if (this.hasAttr('disabled') || this.hasAttr('loading')) return;\n const enabled = getComputedStyle(this)\n .getPropertyValue('--fx-effect-ripple')\n .trim();\n if (enabled === '0' || enabled === 'false') return;\n\n const btn = this.root.querySelector<HTMLElement>('.btn');\n if (!btn) return;\n\n const rect = btn.getBoundingClientRect();\n const size = Math.max(rect.width, rect.height);\n const ripple = document.createElement('span');\n ripple.className = 'btn__ripple';\n ripple.style.width = ripple.style.height = `${size}px`;\n ripple.style.left = `${event.clientX - rect.left - size / 2}px`;\n ripple.style.top = `${event.clientY - rect.top - size / 2}px`;\n btn.appendChild(ripple);\n ripple.addEventListener('animationend', () => ripple.remove());\n };\n}\n\nexport function defineFxButton(): typeof FxButton {\n return defineElement('fx-button', FxButton);\n}"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"button.js","sources":["../../../src/components/button/button.ts"],"sourcesContent":["import { FxElement } from '../../core/base';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\nimport { FENIX_ICON_BASE_CSS, fenixIconHtml } from '../../icons/base-css';\n\n/**\n * <fx-button> — Componente de ação (piloto).\n *\n * Atributos: variant, size, disabled, loading, type, full (largura 100%\n * acompanhando o elemento pai), icon (nome do glifo Fenix Icons ou\n * emoji/texto livre) e icon-pos (left|right, padrão left).\n * Slots: default (rótulo), `icon` (ícone customizado — vence o atributo).\n * Evento: `click` nativo atravessa o Shadow DOM (composed) — ouça no host.\n */\nexport class FxButton extends FxElement {\n static override styles = css`\n ${FENIX_ICON_BASE_CSS}\n :host {\n display: inline-block;\n vertical-align: middle;\n }\n /* Full width: o host inline-block é shrink-to-fit, então precisa virar\n block para o 100% do .btn interno ter contra o que esticar. */\n :host([full]) {\n display: block;\n width: 100%;\n max-width: 100%;\n }\n .btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n gap: var(--fx-space-sm);\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n font-weight: var(--fx-font-weight);\n line-height: var(--fx-font-line-height);\n padding: var(--fx-space-md) var(--fx-space-xl);\n border-radius: var(--fx-radius-md);\n border: 1px solid transparent;\n color: #fff;\n background: var(--fx-color-primary);\n cursor: pointer;\n user-select: none;\n text-decoration: none;\n transition:\n background-color var(--fx-motion-duration-normal) var(--fx-motion-easing),\n border-color var(--fx-motion-duration-normal) var(--fx-motion-easing),\n transform var(--fx-motion-duration-fast) var(--fx-motion-easing),\n box-shadow var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n .btn:focus-visible {\n outline: 2px solid var(--fx-color-primary);\n outline-offset: 2px;\n }\n .btn:hover { filter: brightness(0.88); }\n .btn:active { transform: translateY(1px); }\n\n /* Tamanhos — alturas vindas dos tokens (personalizáveis por preset) */\n .btn { min-height: var(--fx-size-md); }\n :host([size='sm']) .btn { padding: var(--fx-space-sm) var(--fx-space-lg); font-size: var(--fx-font-size); min-height: var(--fx-size-sm); }\n :host([size='lg']) .btn { padding: var(--fx-space-lg) var(--fx-space-xl); font-size: calc(var(--fx-font-size) + 4px); min-height: var(--fx-size-lg); }\n\n /* Full width */\n :host([full]) .btn { width: 100%; }\n\n /* Variantes */\n :host([variant='secondary']) .btn { background: var(--fx-color-secondary); }\n :host([variant='success']) .btn { background: var(--fx-color-success); }\n :host([variant='warning']) .btn { background: var(--fx-color-warning); }\n :host([variant='danger']) .btn { background: var(--fx-color-danger); }\n :host([variant='info']) .btn { background: var(--fx-color-info); }\n :host([variant='ghost']) .btn { background: transparent; color: var(--fx-text-default); }\n :host([variant='ghost']) .btn:hover { background: var(--fx-surface-surface-hover); filter: none; }\n :host([variant='outline']) .btn { background: transparent; color: var(--fx-color-primary); border-color: var(--fx-color-primary); }\n :host([variant='outline']) .btn:hover { background: var(--fx-surface-surface-hover); filter: none; }\n\n /* Ícone e spinner */\n .btn__icon { display: inline-flex; align-items: center; font-size: var(--fx-button-icon-size, calc(var(--fx-font-size) + 6px)); line-height: 1; }\n .btn__icon[hidden] { display: none; }\n .btn__label { display: inline-flex; align-items: center; justify-content: center; }\n /* Icon-only: a label some (o gap do flex some junto) e o botão vira\n quadrado com padding simétrico — ícone perfeitamente centralizado. */\n .btn--icon-only { padding-left: var(--fx-space-md); padding-right: var(--fx-space-md); }\n .btn--icon-only .btn__label { display: none; }\n .btn[hidden] { display: none; }\n .btn__spinner {\n width: 1em;\n height: 1em;\n border: 2px solid currentColor;\n border-top-color: transparent;\n border-radius: var(--fx-radius-full);\n animation: fx-spin var(--fx-motion-duration-normal) linear infinite;\n }\n @keyframes fx-spin { to { transform: rotate(360deg); } }\n\n /* Estados */\n :host([disabled]) .btn,\n :host([loading]) .btn {\n opacity: 0.55;\n cursor: not-allowed;\n pointer-events: none;\n }\n\n /* Efeito ripple (). Desative com effect.ripple: '0' no preset. */\n .btn {\n position: relative;\n overflow: hidden;\n }\n .btn__ripple {\n position: absolute;\n border-radius: var(--fx-radius-full);\n background: currentColor;\n opacity: 0.28;\n transform: scale(0);\n animation: fx-ripple 600ms var(--fx-motion-easing) forwards;\n pointer-events: none;\n }\n @keyframes fx-ripple {\n to {\n transform: scale(2.6);\n opacity: 0;\n }\n }\n @media (prefers-reduced-motion: reduce) {\n .btn__ripple { display: none; }\n }\n `;\n\n static override get observedAttributes(): string[] {\n return ['variant', 'size', 'disabled', 'loading', 'type', 'full', 'icon', 'icon-pos'];\n }\n\n /** Tamanho. Padrão: `'md'`. */\n get size(): string {\n const s = this.getAttr('size', 'md');\n return s === 'sm' || s === 'lg' ? s : 'md';\n }\n set size(value: string) {\n this.setAttribute('size', value);\n }\n\n get disabled(): boolean {\n return this.hasAttr('disabled');\n }\n set disabled(value: boolean) {\n this.toggleAttr('disabled', Boolean(value));\n }\n\n get loading(): boolean {\n return this.hasAttr('loading');\n }\n set loading(value: boolean) {\n this.toggleAttr('loading', Boolean(value));\n }\n\n protected override render(): void {\n const loading = this.hasAttr('loading');\n const disabled = this.hasAttr('disabled') || loading;\n const btnType = this.getAttr('type', 'button');\n const icon = this.getAttr('icon');\n const iconRight = this.getAttr('icon-pos', 'left') === 'right';\n // Slot `icon` vence o atributo `icon`; sem nenhum dos dois, sem ícone.\n const slottedIcon = this.querySelector<HTMLElement>('[slot=\"icon\"]');\n const iconContent = slottedIcon\n ? '<slot name=\"icon\"></slot>'\n : (icon ? fenixIconHtml(icon) : '');\n\n this.setTemplate(`\n <button class=\"btn\" part=\"button\" type=\"${btnType}\">\n ${!iconRight ? `<span class=\"btn__icon\" part=\"icon\">${iconContent}</span>` : ''}\n <span class=\"btn__label\" part=\"label\"><slot></slot></span>\n ${iconRight ? `<span class=\"btn__icon\" part=\"icon\">${iconContent}</span>` : ''}\n ${loading ? '<span class=\"btn__spinner\" aria-hidden=\"true\"></span>' : ''}\n </button>\n `);\n\n const btn = this.root.querySelector<HTMLButtonElement>('.btn');\n if (!btn) return;\n\n // Esconde o container do ícone quando vazio, evitando espaço fantasma\n // do `gap` que descentraliza o rótulo.\n const hasIcon = iconContent.trim() !== '';\n for (const wrap of this.root.querySelectorAll<HTMLElement>('.btn__icon')) {\n wrap.toggleAttribute('hidden', !hasIcon);\n }\n\n // Icon-only: sem texto no slot default, o botão vira quadrado\n // (padding simétrico) em vez de retangular largo.\n const hasLabel = [...(this.childNodes ?? [])].some((n) =>\n n.nodeType === Node.ELEMENT_NODE\n ? (n as Element).getAttribute?.('slot') !== 'icon'\n : (n.textContent ?? '').trim() !== '',\n );\n btn.classList.toggle('btn--icon-only', !hasLabel && hasIcon);\n if (disabled) {\n btn.setAttribute('disabled', '');\n btn.setAttribute('aria-disabled', 'true');\n } else {\n btn.removeAttribute('disabled');\n btn.removeAttribute('aria-disabled');\n }\n if (loading) btn.setAttribute('aria-busy', 'true');\n else btn.removeAttribute('aria-busy');\n }\n\n protected override connectedCallback(): void {\n super.connectedCallback();\n this.addEventListener('pointerdown', this.spawnRipple);\n }\n\n protected override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeEventListener('pointerdown', this.spawnRipple);\n }\n\n /**\n * Efeito ripple : cria um círculo que expande do ponto\n * de clique e desaparece. Respeita o token `effect.ripple` ('0' desativa)\n * e `prefers-reduced-motion` (via CSS).\n */\n private spawnRipple = (event: PointerEvent): void => {\n if (this.hasAttr('disabled') || this.hasAttr('loading')) return;\n const enabled = getComputedStyle(this)\n .getPropertyValue('--fx-effect-ripple')\n .trim();\n if (enabled === '0' || enabled === 'false') return;\n\n const btn = this.root.querySelector<HTMLElement>('.btn');\n if (!btn) return;\n\n const rect = btn.getBoundingClientRect();\n const size = Math.max(rect.width, rect.height);\n const ripple = document.createElement('span');\n ripple.className = 'btn__ripple';\n ripple.style.width = ripple.style.height = `${size}px`;\n ripple.style.left = `${event.clientX - rect.left - size / 2}px`;\n ripple.style.top = `${event.clientY - rect.top - size / 2}px`;\n btn.appendChild(ripple);\n ripple.addEventListener('animationend', () => ripple.remove());\n };\n}\n\nexport function defineFxButton(): typeof FxButton {\n return defineElement('fx-button', FxButton);\n}"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"chip.d.ts","sourceRoot":"","sources":["../../../src/components/chip/chip.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAK5C;;;;;;;;GAQG;AACH,qBAAa,MAAO,SAAQ,SAAS;IACnC,OAAgB,MAAM,SA+DpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,IAAI,QAAQ,IAAI,OAAO,CAAqC;IAC5D,IAAI,QAAQ,CAAC,CAAC,EAAE,OAAO,EAAqC;cAEzC,MAAM,IAAI,IAAI;CA0ClC;AAED,wBAAgB,YAAY,IAAI,OAAO,MAAM,CAE5C"}
1
+ {"version":3,"file":"chip.d.ts","sourceRoot":"","sources":["../../../src/components/chip/chip.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAK5C;;;;;;;;GAQG;AACH,qBAAa,MAAO,SAAQ,SAAS;IACnC,OAAgB,MAAM,SAiEpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,IAAI,QAAQ,IAAI,OAAO,CAAqC;IAC5D,IAAI,QAAQ,CAAC,CAAC,EAAE,OAAO,EAAqC;cAEzC,MAAM,IAAI,IAAI;CA0ClC;AAED,wBAAgB,YAAY,IAAI,OAAO,MAAM,CAE5C"}
@@ -1,7 +1,7 @@
1
1
  import { FxElement } from "../../core/base.js";
2
2
  import { css } from "../../core/css.js";
3
3
  import { defineElement } from "../../core/define.js";
4
- import { esc } from "../../core/sanitize.js";
4
+ import { FENIX_ICON_BASE_CSS, fenixIconHtml } from "../../icons/base-css.js";
5
5
  const _FxChip = class _FxChip extends FxElement {
6
6
  static get observedAttributes() {
7
7
  return ["variant", "size", "removable", "selectable", "selected", "disabled", "icon"];
@@ -20,7 +20,7 @@ const _FxChip = class _FxChip extends FxElement {
20
20
  this.setTemplate(`
21
21
  <span class="chip ${clickable ? "clickable" : ""}" part="chip" role="${clickable ? "button" : "status"}"
22
22
  ${clickable ? `tabindex="0" aria-pressed="${this.selected}"` : ""} ${disabled ? 'aria-disabled="true"' : ""}>
23
- ${icon ? `<span class="icon" aria-hidden="true">${esc(icon)}</span>` : ""}
23
+ ${icon ? `<span class="icon" aria-hidden="true">${fenixIconHtml(icon)}</span>` : ""}
24
24
  <slot></slot>
25
25
  <button type="button" class="remove" part="remove" aria-label="Remover" ${removable ? "" : "hidden"}>×</button>
26
26
  </span>
@@ -97,6 +97,8 @@ _FxChip.styles = css`
97
97
  .chip.clickable:active { transform: scale(0.97); }
98
98
 
99
99
  .icon { line-height: 1; }
100
+ /* Base dos glifos Fenix Icons (o @font-face vem de @wrrdev/fenix-ui/icons). */
101
+ ${FENIX_ICON_BASE_CSS}
100
102
  .remove {
101
103
  display: inline-flex;
102
104
  align-items: center;
@@ -1 +1 @@
1
- {"version":3,"file":"chip.js","sources":["../../../src/components/chip/chip.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-chip> — Chip/Tag compacto para labels, filtros e seleções.\n *\n * Atributos: variant (primary|secondary|success|warning|danger|info),\n * size (sm|md|lg), removable (mostra o botão ×), selectable (clica alterna\n * o estado selected), selected, disabled, icon (glifo/emoji).\n * Slot padrão: texto do chip.\n * Eventos (composed): `remove` (ao clicar em ×) e `chip-click` (ao clicar).\n */\nexport class FxChip extends FxElement {\n static override styles = css`\n :host {\n display: inline-block;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n }\n .chip {\n display: inline-flex;\n align-items: center;\n gap: var(--fx-space-xs);\n padding: var(--fx-space-xs) var(--fx-space-md);\n min-height: var(--fx-size-sm);\n box-sizing: border-box;\n font-size: calc(var(--fx-font-size) - 2px);\n font-weight: var(--fx-font-weight);\n color: var(--fx-text-default);\n background: var(--fx-surface-surface-hover, rgba(0, 0, 0, 0.06));\n border: 1px solid transparent;\n border-radius: var(--fx-radius-full);\n user-select: none;\n transition:\n background var(--fx-motion-duration-fast) var(--fx-motion-easing),\n border-color var(--fx-motion-duration-fast) var(--fx-motion-easing),\n filter var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n :host([size='md']) .chip { min-height: var(--fx-size-md); padding: var(--fx-space-xs) var(--fx-space-lg); }\n :host([size='lg']) .chip { min-height: var(--fx-size-lg); font-size: var(--fx-font-size); padding: var(--fx-space-sm) var(--fx-space-xl); }\n\n :host([variant='primary']) .chip { background: color-mix(in srgb, var(--fx-color-primary) 14%, transparent); color: var(--fx-color-primary); }\n :host([variant='secondary']) .chip { background: color-mix(in srgb, var(--fx-color-secondary) 14%, transparent); color: var(--fx-color-secondary); }\n :host([variant='success']) .chip { background: color-mix(in srgb, var(--fx-color-success) 14%, transparent); color: var(--fx-color-success); }\n :host([variant='warning']) .chip { background: color-mix(in srgb, var(--fx-color-warning) 14%, transparent); color: var(--fx-color-warning); }\n :host([variant='danger']) .chip { background: color-mix(in srgb, var(--fx-color-danger) 14%, transparent); color: var(--fx-color-danger); }\n :host([variant='info']) .chip { background: color-mix(in srgb, var(--fx-color-info) 14%, transparent); color: var(--fx-color-info); }\n\n .chip.clickable { cursor: pointer; }\n .chip.clickable:hover { filter: brightness(0.95); }\n :host([selected]) .chip {\n background: var(--fx-color-primary);\n color: #fff;\n }\n :host([selected]) .chip:hover { filter: brightness(0.92); }\n :host([disabled]) .chip { opacity: 0.55; cursor: not-allowed; }\n .chip.clickable:active { transform: scale(0.97); }\n\n .icon { line-height: 1; }\n .remove {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n border: none;\n border-radius: var(--fx-radius-full);\n background: transparent;\n color: inherit;\n font-size: calc(var(--fx-font-size) + 2px);\n line-height: 1;\n cursor: pointer;\n padding: 0;\n }\n .remove:hover { background: rgba(0, 0, 0, 0.18); }\n .remove[hidden] { display: none; }\n `;\n\n static override get observedAttributes(): string[] {\n return ['variant', 'size', 'removable', 'selectable', 'selected', 'disabled', 'icon'];\n }\n\n get selected(): boolean { return this.hasAttr('selected'); }\n set selected(v: boolean) { this.toggleAttr('selected', v); }\n\n protected override render(): void {\n const icon = this.getAttr('icon');\n const removable = this.hasAttr('removable');\n const disabled = this.hasAttr('disabled');\n const clickable = (this.hasAttr('selectable') || this.hasAttr('href')) && !disabled;\n\n this.setTemplate(`\n <span class=\"chip ${clickable ? 'clickable' : ''}\" part=\"chip\" role=\"${clickable ? 'button' : 'status'}\"\n ${clickable ? `tabindex=\"0\" aria-pressed=\"${this.selected}\"` : ''} ${disabled ? 'aria-disabled=\"true\"' : ''}>\n ${icon ? `<span class=\"icon\" aria-hidden=\"true\">${esc(icon)}</span>` : ''}\n <slot></slot>\n <button type=\"button\" class=\"remove\" part=\"remove\" aria-label=\"Remover\" ${removable ? '' : 'hidden'}>×</button>\n </span>\n `);\n\n const chip = this.root.querySelector<HTMLElement>('.chip');\n const remove = this.root.querySelector<HTMLButtonElement>('.remove');\n if (!chip || !remove) return;\n\n remove.addEventListener('click', (e) => {\n e.stopPropagation();\n this.dispatchEvent(new CustomEvent('remove', { bubbles: true, composed: true }));\n });\n\n if (clickable) {\n const toggle = (): void => {\n if (this.hasAttr('selectable')) {\n this.selected = !this.selected;\n chip.setAttribute('aria-pressed', String(this.selected));\n }\n this.dispatchEvent(new CustomEvent('chip-click', { bubbles: true, composed: true }));\n };\n chip.addEventListener('click', toggle);\n chip.addEventListener('keydown', (e) => {\n const ev = e as KeyboardEvent;\n if (ev.key === 'Enter' || ev.key === ' ') {\n ev.preventDefault();\n toggle();\n }\n });\n }\n }\n}\n\nexport function defineFxChip(): typeof FxChip {\n return defineElement('fx-chip', FxChip);\n}\n"],"names":[],"mappings":";;;;AAcO,MAAM,UAAN,MAAM,gBAAe,UAAU;AAAA,EAkEpC,WAAoB,qBAA+B;AACjD,WAAO,CAAC,WAAW,QAAQ,aAAa,cAAc,YAAY,YAAY,MAAM;AAAA,EACtF;AAAA,EAEA,IAAI,WAAoB;AAAE,WAAO,KAAK,QAAQ,UAAU;AAAA,EAAG;AAAA,EAC3D,IAAI,SAAS,GAAY;AAAE,SAAK,WAAW,YAAY,CAAC;AAAA,EAAG;AAAA,EAExC,SAAe;AAChC,UAAM,OAAO,KAAK,QAAQ,MAAM;AAChC,UAAM,YAAY,KAAK,QAAQ,WAAW;AAC1C,UAAM,WAAW,KAAK,QAAQ,UAAU;AACxC,UAAM,aAAa,KAAK,QAAQ,YAAY,KAAK,KAAK,QAAQ,MAAM,MAAM,CAAC;AAE3E,SAAK,YAAY;AAAA,0BACK,YAAY,cAAc,EAAE,uBAAuB,YAAY,WAAW,QAAQ;AAAA,UAClG,YAAY,8BAA8B,KAAK,QAAQ,MAAM,EAAE,IAAI,WAAW,yBAAyB,EAAE;AAAA,UACzG,OAAO,yCAAyC,IAAI,IAAI,CAAC,YAAY,EAAE;AAAA;AAAA,kFAEC,YAAY,KAAK,QAAQ;AAAA;AAAA,KAEtG;AAED,UAAM,OAAO,KAAK,KAAK,cAA2B,OAAO;AACzD,UAAM,SAAS,KAAK,KAAK,cAAiC,SAAS;AACnE,QAAI,CAAC,QAAQ,CAAC,OAAQ;AAEtB,WAAO,iBAAiB,SAAS,CAAC,MAAM;AACtC,QAAE,gBAAA;AACF,WAAK,cAAc,IAAI,YAAY,UAAU,EAAE,SAAS,MAAM,UAAU,KAAA,CAAM,CAAC;AAAA,IACjF,CAAC;AAED,QAAI,WAAW;AACb,YAAM,SAAS,MAAY;AACzB,YAAI,KAAK,QAAQ,YAAY,GAAG;AAC9B,eAAK,WAAW,CAAC,KAAK;AACtB,eAAK,aAAa,gBAAgB,OAAO,KAAK,QAAQ,CAAC;AAAA,QACzD;AACA,aAAK,cAAc,IAAI,YAAY,cAAc,EAAE,SAAS,MAAM,UAAU,KAAA,CAAM,CAAC;AAAA,MACrF;AACA,WAAK,iBAAiB,SAAS,MAAM;AACrC,WAAK,iBAAiB,WAAW,CAAC,MAAM;AACtC,cAAM,KAAK;AACX,YAAI,GAAG,QAAQ,WAAW,GAAG,QAAQ,KAAK;AACxC,aAAG,eAAA;AACH,iBAAA;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAlHE,QAAgB,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;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,SAAN;AAqHA,SAAS,eAA8B;AAC5C,SAAO,cAAc,WAAW,MAAM;AACxC;"}
1
+ {"version":3,"file":"chip.js","sources":["../../../src/components/chip/chip.ts"],"sourcesContent":["import { FxElement } from '../../core/base';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\nimport { FENIX_ICON_BASE_CSS, fenixIconHtml } from '../../icons/base-css';\n\n/**\n * <fx-chip> — Chip/Tag compacto para labels, filtros e seleções.\n *\n * Atributos: variant (primary|secondary|success|warning|danger|info),\n * size (sm|md|lg), removable (mostra o botão ×), selectable (clica alterna\n * o estado selected), selected, disabled, icon (glifo/emoji).\n * Slot padrão: texto do chip.\n * Eventos (composed): `remove` (ao clicar em ×) e `chip-click` (ao clicar).\n */\nexport class FxChip extends FxElement {\n static override styles = css`\n :host {\n display: inline-block;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n }\n .chip {\n display: inline-flex;\n align-items: center;\n gap: var(--fx-space-xs);\n padding: var(--fx-space-xs) var(--fx-space-md);\n min-height: var(--fx-size-sm);\n box-sizing: border-box;\n font-size: calc(var(--fx-font-size) - 2px);\n font-weight: var(--fx-font-weight);\n color: var(--fx-text-default);\n background: var(--fx-surface-surface-hover, rgba(0, 0, 0, 0.06));\n border: 1px solid transparent;\n border-radius: var(--fx-radius-full);\n user-select: none;\n transition:\n background var(--fx-motion-duration-fast) var(--fx-motion-easing),\n border-color var(--fx-motion-duration-fast) var(--fx-motion-easing),\n filter var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n :host([size='md']) .chip { min-height: var(--fx-size-md); padding: var(--fx-space-xs) var(--fx-space-lg); }\n :host([size='lg']) .chip { min-height: var(--fx-size-lg); font-size: var(--fx-font-size); padding: var(--fx-space-sm) var(--fx-space-xl); }\n\n :host([variant='primary']) .chip { background: color-mix(in srgb, var(--fx-color-primary) 14%, transparent); color: var(--fx-color-primary); }\n :host([variant='secondary']) .chip { background: color-mix(in srgb, var(--fx-color-secondary) 14%, transparent); color: var(--fx-color-secondary); }\n :host([variant='success']) .chip { background: color-mix(in srgb, var(--fx-color-success) 14%, transparent); color: var(--fx-color-success); }\n :host([variant='warning']) .chip { background: color-mix(in srgb, var(--fx-color-warning) 14%, transparent); color: var(--fx-color-warning); }\n :host([variant='danger']) .chip { background: color-mix(in srgb, var(--fx-color-danger) 14%, transparent); color: var(--fx-color-danger); }\n :host([variant='info']) .chip { background: color-mix(in srgb, var(--fx-color-info) 14%, transparent); color: var(--fx-color-info); }\n\n .chip.clickable { cursor: pointer; }\n .chip.clickable:hover { filter: brightness(0.95); }\n :host([selected]) .chip {\n background: var(--fx-color-primary);\n color: #fff;\n }\n :host([selected]) .chip:hover { filter: brightness(0.92); }\n :host([disabled]) .chip { opacity: 0.55; cursor: not-allowed; }\n .chip.clickable:active { transform: scale(0.97); }\n\n .icon { line-height: 1; }\n /* Base dos glifos Fenix Icons (o @font-face vem de @wrrdev/fenix-ui/icons). */\n ${FENIX_ICON_BASE_CSS}\n .remove {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n border: none;\n border-radius: var(--fx-radius-full);\n background: transparent;\n color: inherit;\n font-size: calc(var(--fx-font-size) + 2px);\n line-height: 1;\n cursor: pointer;\n padding: 0;\n }\n .remove:hover { background: rgba(0, 0, 0, 0.18); }\n .remove[hidden] { display: none; }\n `;\n\n static override get observedAttributes(): string[] {\n return ['variant', 'size', 'removable', 'selectable', 'selected', 'disabled', 'icon'];\n }\n\n get selected(): boolean { return this.hasAttr('selected'); }\n set selected(v: boolean) { this.toggleAttr('selected', v); }\n\n protected override render(): void {\n const icon = this.getAttr('icon');\n const removable = this.hasAttr('removable');\n const disabled = this.hasAttr('disabled');\n const clickable = (this.hasAttr('selectable') || this.hasAttr('href')) && !disabled;\n\n this.setTemplate(`\n <span class=\"chip ${clickable ? 'clickable' : ''}\" part=\"chip\" role=\"${clickable ? 'button' : 'status'}\"\n ${clickable ? `tabindex=\"0\" aria-pressed=\"${this.selected}\"` : ''} ${disabled ? 'aria-disabled=\"true\"' : ''}>\n ${icon ? `<span class=\"icon\" aria-hidden=\"true\">${fenixIconHtml(icon)}</span>` : ''}\n <slot></slot>\n <button type=\"button\" class=\"remove\" part=\"remove\" aria-label=\"Remover\" ${removable ? '' : 'hidden'}>×</button>\n </span>\n `);\n\n const chip = this.root.querySelector<HTMLElement>('.chip');\n const remove = this.root.querySelector<HTMLButtonElement>('.remove');\n if (!chip || !remove) return;\n\n remove.addEventListener('click', (e) => {\n e.stopPropagation();\n this.dispatchEvent(new CustomEvent('remove', { bubbles: true, composed: true }));\n });\n\n if (clickable) {\n const toggle = (): void => {\n if (this.hasAttr('selectable')) {\n this.selected = !this.selected;\n chip.setAttribute('aria-pressed', String(this.selected));\n }\n this.dispatchEvent(new CustomEvent('chip-click', { bubbles: true, composed: true }));\n };\n chip.addEventListener('click', toggle);\n chip.addEventListener('keydown', (e) => {\n const ev = e as KeyboardEvent;\n if (ev.key === 'Enter' || ev.key === ' ') {\n ev.preventDefault();\n toggle();\n }\n });\n }\n }\n}\n\nexport function defineFxChip(): typeof FxChip {\n return defineElement('fx-chip', FxChip);\n}\n"],"names":[],"mappings":";;;;AAcO,MAAM,UAAN,MAAM,gBAAe,UAAU;AAAA,EAoEpC,WAAoB,qBAA+B;AACjD,WAAO,CAAC,WAAW,QAAQ,aAAa,cAAc,YAAY,YAAY,MAAM;AAAA,EACtF;AAAA,EAEA,IAAI,WAAoB;AAAE,WAAO,KAAK,QAAQ,UAAU;AAAA,EAAG;AAAA,EAC3D,IAAI,SAAS,GAAY;AAAE,SAAK,WAAW,YAAY,CAAC;AAAA,EAAG;AAAA,EAExC,SAAe;AAChC,UAAM,OAAO,KAAK,QAAQ,MAAM;AAChC,UAAM,YAAY,KAAK,QAAQ,WAAW;AAC1C,UAAM,WAAW,KAAK,QAAQ,UAAU;AACxC,UAAM,aAAa,KAAK,QAAQ,YAAY,KAAK,KAAK,QAAQ,MAAM,MAAM,CAAC;AAE3E,SAAK,YAAY;AAAA,0BACK,YAAY,cAAc,EAAE,uBAAuB,YAAY,WAAW,QAAQ;AAAA,UAClG,YAAY,8BAA8B,KAAK,QAAQ,MAAM,EAAE,IAAI,WAAW,yBAAyB,EAAE;AAAA,UACzG,OAAO,yCAAyC,cAAc,IAAI,CAAC,YAAY,EAAE;AAAA;AAAA,kFAET,YAAY,KAAK,QAAQ;AAAA;AAAA,KAEtG;AAED,UAAM,OAAO,KAAK,KAAK,cAA2B,OAAO;AACzD,UAAM,SAAS,KAAK,KAAK,cAAiC,SAAS;AACnE,QAAI,CAAC,QAAQ,CAAC,OAAQ;AAEtB,WAAO,iBAAiB,SAAS,CAAC,MAAM;AACtC,QAAE,gBAAA;AACF,WAAK,cAAc,IAAI,YAAY,UAAU,EAAE,SAAS,MAAM,UAAU,KAAA,CAAM,CAAC;AAAA,IACjF,CAAC;AAED,QAAI,WAAW;AACb,YAAM,SAAS,MAAY;AACzB,YAAI,KAAK,QAAQ,YAAY,GAAG;AAC9B,eAAK,WAAW,CAAC,KAAK;AACtB,eAAK,aAAa,gBAAgB,OAAO,KAAK,QAAQ,CAAC;AAAA,QACzD;AACA,aAAK,cAAc,IAAI,YAAY,cAAc,EAAE,SAAS,MAAM,UAAU,KAAA,CAAM,CAAC;AAAA,MACrF;AACA,WAAK,iBAAiB,SAAS,MAAM;AACrC,WAAK,iBAAiB,WAAW,CAAC,MAAM;AACtC,cAAM,KAAK;AACX,YAAI,GAAG,QAAQ,WAAW,GAAG,QAAQ,KAAK;AACxC,aAAG,eAAA;AACH,iBAAA;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;AApHE,QAAgB,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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MA+CrB,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAhDlB,IAAM,SAAN;AAuHA,SAAS,eAA8B;AAC5C,SAAO,cAAc,WAAW,MAAM;AACxC;"}
@@ -1 +1 @@
1
- {"version":3,"file":"confirmpopup.d.ts","sourceRoot":"","sources":["../../../src/components/confirmpopup/confirmpopup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAM5C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,cAAe,SAAQ,SAAS;IAC3C,OAAgB,MAAM,SAkFpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,IAAI,IAAI,IAAI,OAAO,CAAiC;IACpD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,EAAqC;cAEzC,MAAM,IAAI,IAAI;IAiEjC,OAAO,CAAC,QAAQ,CAAC,CAAa;IAC9B,OAAO,CAAC,kBAAkB,CAA4B;IAEtD,OAAO,CAAC,OAAO;IAUf;;;;OAIG;IACH,OAAO,CAAC,SAAS;IA+BjB,OAAO,CAAC,aAAa;IAKZ,iBAAiB,IAAI,IAAI;IAOzB,oBAAoB,IAAI,IAAI;IASrC,OAAO,CAAC,aAAa,CAInB;CACH;AAED,wBAAgB,oBAAoB,IAAI,OAAO,cAAc,CAE5D"}
1
+ {"version":3,"file":"confirmpopup.d.ts","sourceRoot":"","sources":["../../../src/components/confirmpopup/confirmpopup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAM5C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,cAAe,SAAQ,SAAS;IAC3C,OAAgB,MAAM,SAkFpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,IAAI,IAAI,IAAI,OAAO,CAAiC;IACpD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,EAAqC;cAEzC,MAAM,IAAI,IAAI;IAsEjC,OAAO,CAAC,QAAQ,CAAC,CAAa;IAC9B,OAAO,CAAC,kBAAkB,CAA4B;IAEtD,OAAO,CAAC,OAAO;IAUf;;;;OAIG;IACH,OAAO,CAAC,SAAS;IA+BjB,OAAO,CAAC,aAAa;IAKZ,iBAAiB,IAAI,IAAI;IAOzB,oBAAoB,IAAI,IAAI;IASrC,OAAO,CAAC,aAAa,CAInB;CACH;AAED,wBAAgB,oBAAoB,IAAI,OAAO,cAAc,CAE5D"}
@@ -49,6 +49,8 @@ const _FxConfirmPopup = class _FxConfirmPopup extends FxElement {
49
49
  if (!this.open) return;
50
50
  const popup = this.root.querySelector(".popup");
51
51
  if (!popup) return;
52
+ this._cleanup?.();
53
+ this._cleanup = void 0;
52
54
  const close = (event) => {
53
55
  this.open = false;
54
56
  this._cleanup?.();
@@ -1 +1 @@
1
- {"version":3,"mappings":"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","names":[],"ignoreList":[],"sources":["../../../src/components/confirmpopup/confirmpopup.ts"],"sourcesContent":["import { FxElement } from '../../core/base';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\nimport { esc } from '../../core/sanitize';\nimport { FENIX_ICON_BASE_CSS, isFenixIconName } from '../../icons/base-css';\n\n/**\n * <fx-confirmpopup> — Popup de confirmação não-modal ancorado em um elemento,\n * exibido acima ou abaixo dinamicamente (com flip automático quando não há\n * espaço na viewport), no estilo ConfirmPopup do PrimeVue.\n *\n * Atributos:\n * - target (seletor CSS do elemento âncora — obrigatório para abrir);\n * - message (texto da mensagem, quando o slot padrão não é usado);\n * - icon (emoji/glifo exibido antes da mensagem; se for um nome da Fenix Icons,\n * ex.: icon=\"help\", é renderizado com a fonte de ícones);\n * - accept-label / reject-label (textos dos botões padrão);\n * - position (top|bottom|auto — padrão `auto`, que faz flip conforme o espaço);\n * - open (exibe o popup).\n *\n * Slots:\n * - padrão: corpo da mensagem (template \"message\");\n * - `accept`: conteúdo do botão de confirmação (template \"accept\");\n * - `reject`: conteúdo do botão de rejeição (template \"reject\").\n *\n * Eventos (composed): `accept` e `reject` — ambos fecham o popup.\n */\nexport class FxConfirmPopup extends FxElement {\n static override styles = css`\n :host { display: contents; }\n .popup {\n position: fixed;\n margin: 0;\n background: var(--fx-surface-background);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-md);\n box-shadow: var(--fx-shadow-lg);\n padding: var(--fx-space-md) var(--fx-space-lg);\n min-width: 220px;\n max-width: min(380px, calc(100vw - 24px));\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n color: var(--fx-text-default);\n z-index: var(--fx-z-modal, 1100);\n animation: fxcp-pop var(--fx-motion-duration-fast, 150ms) var(--fx-motion-easing, ease);\n }\n .popup[hidden] { display: none; }\n @keyframes fxcp-pop { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } }\n /* seta indicadora */\n .popup::before {\n content: '';\n position: absolute;\n left: var(--fxcp-arrow-x, 50%);\n border: 7px solid transparent;\n }\n .popup[data-side='bottom']::before {\n top: -14px;\n border-bottom-color: var(--fx-border-default);\n }\n .popup[data-side='top']::before {\n bottom: -14px;\n border-top-color: var(--fx-border-default);\n }\n .popup::after {\n content: '';\n position: absolute;\n left: var(--fxcp-arrow-x, 50%);\n border: 6px solid transparent;\n }\n .popup[data-side='bottom']::after {\n top: -12px;\n border-bottom-color: var(--fx-surface-background);\n }\n .popup[data-side='top']::after {\n bottom: -12px;\n border-top-color: var(--fx-surface-background);\n }\n .message {\n display: flex;\n align-items: flex-start;\n gap: var(--fx-space-sm);\n }\n .icon { font-size: calc(var(--fx-font-size) + 4px); line-height: 1.2; }\n ${FENIX_ICON_BASE_CSS}\n .icon.fx-icon { font-size: calc(var(--fx-font-size) + 6px); color: var(--fx-color-primary); }\n .text { margin: 0; }\n footer {\n display: flex;\n justify-content: flex-end;\n gap: var(--fx-space-sm);\n margin-top: var(--fx-space-md);\n }\n footer button {\n font-family: var(--fx-font-family);\n font-size: calc(var(--fx-font-size) - 2px);\n padding: var(--fx-space-xs) var(--fx-space-md);\n border-radius: var(--fx-radius-sm);\n border: 1px solid var(--fx-border-default);\n background: var(--fx-surface-background);\n color: var(--fx-text-default);\n cursor: pointer;\n transition: background var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n footer button:hover { background: var(--fx-surface-surface-hover, rgba(0, 0, 0, 0.04)); }\n footer button.accept {\n background: var(--fx-color-primary);\n border-color: var(--fx-color-primary);\n color: #fff;\n }\n footer button.accept:hover { filter: brightness(0.92); background: var(--fx-color-primary); }\n `;\n\n static override get observedAttributes(): string[] {\n return ['open', 'target', 'message', 'icon', 'accept-label', 'reject-label', 'position'];\n }\n\n get open(): boolean { return this.hasAttr('open'); }\n set open(value: boolean) { this.toggleAttr('open', value); }\n\n protected override render(): void {\n const message = this.getAttr('message');\n const icon = this.getAttr('icon');\n const iconIsFx = isFenixIconName(icon);\n if (iconIsFx) void import('../../icons');\n const acceptLabel = this.getAttr('accept-label', 'Sim');\n const rejectLabel = this.getAttr('reject-label', 'Não');\n\n this.setTemplate(`\n <div class=\"popup\" part=\"popup\" role=\"alertdialog\" aria-modal=\"false\" ${this.open ? '' : 'hidden'}>\n <div class=\"message\" part=\"message\">\n ${icon ? `<span class=\"icon${iconIsFx ? ' fx-icon' : ''}\" aria-hidden=\"true\">${esc(icon)}</span>` : ''}\n <p class=\"text\"><slot>${esc(message)}</slot></p>\n </div>\n <footer part=\"footer\">\n <slot name=\"reject\">\n <button type=\"button\" class=\"reject\" part=\"reject-button\">${esc(rejectLabel)}</button>\n </slot>\n <slot name=\"accept\">\n <button type=\"button\" class=\"accept\" part=\"accept-button\">${esc(acceptLabel)}</button>\n </slot>\n </footer>\n </div>\n `);\n\n if (!this.open) return;\n\n const popup = this.root.querySelector<HTMLElement>('.popup');\n if (!popup) return;\n\n const close = (event: 'accept' | 'reject'): void => {\n this.open = false;\n this._cleanup?.();\n this._cleanup = undefined;\n this._restoreFocus();\n this.dispatchEvent(new CustomEvent(event, { bubbles: true, composed: true }));\n };\n\n this.root.querySelector('.reject')?.addEventListener('click', () => close('reject'));\n this.root.querySelector('.accept')?.addEventListener('click', () => close('accept'));\n\n // ESC fecha (reject) — clique fora fecha (reject)\n const onKey = (e: KeyboardEvent): void => {\n if (e.key === 'Escape') { e.stopPropagation(); close('reject'); }\n };\n const onPointerDown = (e: MouseEvent): void => {\n const path = e.composedPath();\n const anchor = this._anchor();\n const hitAnchor = anchor ? path.includes(anchor) : false;\n if (!path.includes(popup) && !hitAnchor) close('reject');\n };\n document.addEventListener('keydown', onKey);\n document.addEventListener('pointerdown', onPointerDown, true);\n this._cleanup = () => {\n document.removeEventListener('keydown', onKey);\n document.removeEventListener('pointerdown', onPointerDown, true);\n };\n\n this._position(popup);\n this._previouslyFocused = document.activeElement as HTMLElement | null;\n requestAnimationFrame(() => {\n this.root.querySelector<HTMLElement>('button.accept')?.focus();\n });\n }\n\n private _cleanup?: () => void;\n private _previouslyFocused: HTMLElement | null = null;\n\n private _anchor(): HTMLElement | null {\n const selector = this.getAttr('target');\n if (!selector) return null;\n try {\n return document.querySelector<HTMLElement>(selector);\n } catch {\n return null;\n }\n }\n\n /**\n * Posiciona o popup fixo junto ao âncora, acima ou abaixo dinamicamente:\n * `position=\"auto\"` (padrão) prefere abaixo e faz flip para cima quando\n * não há espaço; `top`/`bottom` forçam o lado.\n */\n private _position(popup: HTMLElement): void {\n const anchor = this._anchor();\n if (!anchor) return;\n popup.hidden = false;\n\n const a = anchor.getBoundingClientRect();\n const p = popup.getBoundingClientRect();\n const gap = 8;\n const margin = 8;\n\n let side: 'top' | 'bottom';\n const pos = this.getAttr('position', 'auto');\n if (pos === 'top') side = 'top';\n else if (pos === 'bottom') side = 'bottom';\n else side = a.bottom + gap + p.height <= window.innerHeight - margin ? 'bottom' : 'top';\n\n // clamp horizontal para nunca sair da viewport\n const left = Math.min(\n Math.max(margin, a.left + a.width / 2 - p.width / 2),\n window.innerWidth - p.width - margin,\n );\n const top = side === 'bottom' ? a.bottom + gap : a.top - gap - p.height;\n\n popup.dataset.side = side;\n popup.style.left = `${Math.round(left)}px`;\n popup.style.top = `${Math.round(Math.max(margin, top))}px`;\n // posição da seta relativa ao popup\n const arrowX = Math.min(Math.max(a.left + a.width / 2 - left, 16), p.width - 16);\n popup.style.setProperty('--fxcp-arrow-x', `${Math.round(arrowX)}px`);\n }\n\n private _restoreFocus(): void {\n if (this._previouslyFocused?.isConnected) this._previouslyFocused.focus();\n this._previouslyFocused = null;\n }\n\n override connectedCallback(): void {\n super.connectedCallback();\n // reposiciona em scroll/resize enquanto aberto\n window.addEventListener('scroll', this._onReposition, true);\n window.addEventListener('resize', this._onReposition);\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n window.removeEventListener('scroll', this._onReposition, true);\n window.removeEventListener('resize', this._onReposition);\n this._cleanup?.();\n this._cleanup = undefined;\n this._restoreFocus();\n }\n\n private _onReposition = (): void => {\n if (!this.open) return;\n const popup = this.root.querySelector<HTMLElement>('.popup');\n if (popup && !popup.hidden) this._position(popup);\n };\n}\n\nexport function defineFxConfirmPopup(): typeof FxConfirmPopup {\n return defineElement('fx-confirmpopup', FxConfirmPopup);\n}\n\n"],"file":"components/confirmpopup/confirmpopup.js"}
1
+ {"version":3,"mappings":"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","names":[],"ignoreList":[],"sources":["../../../src/components/confirmpopup/confirmpopup.ts"],"sourcesContent":["import { FxElement } from '../../core/base';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\nimport { esc } from '../../core/sanitize';\nimport { FENIX_ICON_BASE_CSS, isFenixIconName } from '../../icons/base-css';\n\n/**\n * <fx-confirmpopup> — Popup de confirmação não-modal ancorado em um elemento,\n * exibido acima ou abaixo dinamicamente (com flip automático quando não há\n * espaço na viewport), no estilo ConfirmPopup do PrimeVue.\n *\n * Atributos:\n * - target (seletor CSS do elemento âncora — obrigatório para abrir);\n * - message (texto da mensagem, quando o slot padrão não é usado);\n * - icon (emoji/glifo exibido antes da mensagem; se for um nome da Fenix Icons,\n * ex.: icon=\"help\", é renderizado com a fonte de ícones);\n * - accept-label / reject-label (textos dos botões padrão);\n * - position (top|bottom|auto — padrão `auto`, que faz flip conforme o espaço);\n * - open (exibe o popup).\n *\n * Slots:\n * - padrão: corpo da mensagem (template \"message\");\n * - `accept`: conteúdo do botão de confirmação (template \"accept\");\n * - `reject`: conteúdo do botão de rejeição (template \"reject\").\n *\n * Eventos (composed): `accept` e `reject` — ambos fecham o popup.\n */\nexport class FxConfirmPopup extends FxElement {\n static override styles = css`\n :host { display: contents; }\n .popup {\n position: fixed;\n margin: 0;\n background: var(--fx-surface-background);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-md);\n box-shadow: var(--fx-shadow-lg);\n padding: var(--fx-space-md) var(--fx-space-lg);\n min-width: 220px;\n max-width: min(380px, calc(100vw - 24px));\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n color: var(--fx-text-default);\n z-index: var(--fx-z-modal, 1100);\n animation: fxcp-pop var(--fx-motion-duration-fast, 150ms) var(--fx-motion-easing, ease);\n }\n .popup[hidden] { display: none; }\n @keyframes fxcp-pop { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } }\n /* seta indicadora */\n .popup::before {\n content: '';\n position: absolute;\n left: var(--fxcp-arrow-x, 50%);\n border: 7px solid transparent;\n }\n .popup[data-side='bottom']::before {\n top: -14px;\n border-bottom-color: var(--fx-border-default);\n }\n .popup[data-side='top']::before {\n bottom: -14px;\n border-top-color: var(--fx-border-default);\n }\n .popup::after {\n content: '';\n position: absolute;\n left: var(--fxcp-arrow-x, 50%);\n border: 6px solid transparent;\n }\n .popup[data-side='bottom']::after {\n top: -12px;\n border-bottom-color: var(--fx-surface-background);\n }\n .popup[data-side='top']::after {\n bottom: -12px;\n border-top-color: var(--fx-surface-background);\n }\n .message {\n display: flex;\n align-items: flex-start;\n gap: var(--fx-space-sm);\n }\n .icon { font-size: calc(var(--fx-font-size) + 4px); line-height: 1.2; }\n ${FENIX_ICON_BASE_CSS}\n .icon.fx-icon { font-size: calc(var(--fx-font-size) + 6px); color: var(--fx-color-primary); }\n .text { margin: 0; }\n footer {\n display: flex;\n justify-content: flex-end;\n gap: var(--fx-space-sm);\n margin-top: var(--fx-space-md);\n }\n footer button {\n font-family: var(--fx-font-family);\n font-size: calc(var(--fx-font-size) - 2px);\n padding: var(--fx-space-xs) var(--fx-space-md);\n border-radius: var(--fx-radius-sm);\n border: 1px solid var(--fx-border-default);\n background: var(--fx-surface-background);\n color: var(--fx-text-default);\n cursor: pointer;\n transition: background var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n footer button:hover { background: var(--fx-surface-surface-hover, rgba(0, 0, 0, 0.04)); }\n footer button.accept {\n background: var(--fx-color-primary);\n border-color: var(--fx-color-primary);\n color: #fff;\n }\n footer button.accept:hover { filter: brightness(0.92); background: var(--fx-color-primary); }\n `;\n\n static override get observedAttributes(): string[] {\n return ['open', 'target', 'message', 'icon', 'accept-label', 'reject-label', 'position'];\n }\n\n get open(): boolean { return this.hasAttr('open'); }\n set open(value: boolean) { this.toggleAttr('open', value); }\n\n protected override render(): void {\n const message = this.getAttr('message');\n const icon = this.getAttr('icon');\n const iconIsFx = isFenixIconName(icon);\n if (iconIsFx) void import('../../icons');\n const acceptLabel = this.getAttr('accept-label', 'Sim');\n const rejectLabel = this.getAttr('reject-label', 'Não');\n\n this.setTemplate(`\n <div class=\"popup\" part=\"popup\" role=\"alertdialog\" aria-modal=\"false\" ${this.open ? '' : 'hidden'}>\n <div class=\"message\" part=\"message\">\n ${icon ? `<span class=\"icon${iconIsFx ? ' fx-icon' : ''}\" aria-hidden=\"true\">${esc(icon)}</span>` : ''}\n <p class=\"text\"><slot>${esc(message)}</slot></p>\n </div>\n <footer part=\"footer\">\n <slot name=\"reject\">\n <button type=\"button\" class=\"reject\" part=\"reject-button\">${esc(rejectLabel)}</button>\n </slot>\n <slot name=\"accept\">\n <button type=\"button\" class=\"accept\" part=\"accept-button\">${esc(acceptLabel)}</button>\n </slot>\n </footer>\n </div>\n `);\n\n if (!this.open) return;\n\n const popup = this.root.querySelector<HTMLElement>('.popup');\n if (!popup) return;\n\n // Evita listeners duplicados em re-renders enquanto aberto\n // (ex.: mudança de message/icon/accept-label com o popup aberto).\n this._cleanup?.();\n this._cleanup = undefined;\n\n const close = (event: 'accept' | 'reject'): void => {\n this.open = false;\n this._cleanup?.();\n this._cleanup = undefined;\n this._restoreFocus();\n this.dispatchEvent(new CustomEvent(event, { bubbles: true, composed: true }));\n };\n\n this.root.querySelector('.reject')?.addEventListener('click', () => close('reject'));\n this.root.querySelector('.accept')?.addEventListener('click', () => close('accept'));\n\n // ESC fecha (reject) — clique fora fecha (reject)\n const onKey = (e: KeyboardEvent): void => {\n if (e.key === 'Escape') { e.stopPropagation(); close('reject'); }\n };\n const onPointerDown = (e: MouseEvent): void => {\n const path = e.composedPath();\n const anchor = this._anchor();\n const hitAnchor = anchor ? path.includes(anchor) : false;\n if (!path.includes(popup) && !hitAnchor) close('reject');\n };\n document.addEventListener('keydown', onKey);\n document.addEventListener('pointerdown', onPointerDown, true);\n this._cleanup = () => {\n document.removeEventListener('keydown', onKey);\n document.removeEventListener('pointerdown', onPointerDown, true);\n };\n\n this._position(popup);\n this._previouslyFocused = document.activeElement as HTMLElement | null;\n requestAnimationFrame(() => {\n this.root.querySelector<HTMLElement>('button.accept')?.focus();\n });\n }\n\n private _cleanup?: () => void;\n private _previouslyFocused: HTMLElement | null = null;\n\n private _anchor(): HTMLElement | null {\n const selector = this.getAttr('target');\n if (!selector) return null;\n try {\n return document.querySelector<HTMLElement>(selector);\n } catch {\n return null;\n }\n }\n\n /**\n * Posiciona o popup fixo junto ao âncora, acima ou abaixo dinamicamente:\n * `position=\"auto\"` (padrão) prefere abaixo e faz flip para cima quando\n * não há espaço; `top`/`bottom` forçam o lado.\n */\n private _position(popup: HTMLElement): void {\n const anchor = this._anchor();\n if (!anchor) return;\n popup.hidden = false;\n\n const a = anchor.getBoundingClientRect();\n const p = popup.getBoundingClientRect();\n const gap = 8;\n const margin = 8;\n\n let side: 'top' | 'bottom';\n const pos = this.getAttr('position', 'auto');\n if (pos === 'top') side = 'top';\n else if (pos === 'bottom') side = 'bottom';\n else side = a.bottom + gap + p.height <= window.innerHeight - margin ? 'bottom' : 'top';\n\n // clamp horizontal para nunca sair da viewport\n const left = Math.min(\n Math.max(margin, a.left + a.width / 2 - p.width / 2),\n window.innerWidth - p.width - margin,\n );\n const top = side === 'bottom' ? a.bottom + gap : a.top - gap - p.height;\n\n popup.dataset.side = side;\n popup.style.left = `${Math.round(left)}px`;\n popup.style.top = `${Math.round(Math.max(margin, top))}px`;\n // posição da seta relativa ao popup\n const arrowX = Math.min(Math.max(a.left + a.width / 2 - left, 16), p.width - 16);\n popup.style.setProperty('--fxcp-arrow-x', `${Math.round(arrowX)}px`);\n }\n\n private _restoreFocus(): void {\n if (this._previouslyFocused?.isConnected) this._previouslyFocused.focus();\n this._previouslyFocused = null;\n }\n\n override connectedCallback(): void {\n super.connectedCallback();\n // reposiciona em scroll/resize enquanto aberto\n window.addEventListener('scroll', this._onReposition, true);\n window.addEventListener('resize', this._onReposition);\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n window.removeEventListener('scroll', this._onReposition, true);\n window.removeEventListener('resize', this._onReposition);\n this._cleanup?.();\n this._cleanup = undefined;\n this._restoreFocus();\n }\n\n private _onReposition = (): void => {\n if (!this.open) return;\n const popup = this.root.querySelector<HTMLElement>('.popup');\n if (popup && !popup.hidden) this._position(popup);\n };\n}\n\nexport function defineFxConfirmPopup(): typeof FxConfirmPopup {\n return defineElement('fx-confirmpopup', FxConfirmPopup);\n}\n\n"],"file":"components/confirmpopup/confirmpopup.js"}
@@ -1 +1 @@
1
- {"version":3,"file":"datepicker.d.ts","sourceRoot":"","sources":["../../../src/components/datepicker/datepicker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAK5C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,YAAa,SAAQ,SAAS;IACzC,OAAgB,MAAM,SA+IpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,OAAO,CAAC,WAAW,CAAC,CAAqB;IACzC,6CAA6C;IAC7C,OAAO,CAAC,IAAI,CAAwB;IAEpC,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAErB;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;IACD,IAAI,KAAK,CAAC,CAAC,EAAE,MAAM,EAElB;IAED,gDAAgD;IAChD,IAAI,MAAM,IAAI,MAAM,EAAE,CAGrB;IACD,IAAI,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAExB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAE1B;cAEkB,iBAAiB,IAAI,IAAI;cAWzB,oBAAoB,IAAI,IAAI;IAK/C,wFAAwF;IACxF,OAAO,CAAC,UAAU;IAyBlB,8CAA8C;IAC9C,OAAO,CAAC,WAAW;IAoBnB,iDAAiD;IACjD,OAAO,CAAC,QAAQ;IAKhB,sDAAsD;IACtD,OAAO,CAAC,iBAAiB;IAQzB,+DAA+D;IAC/D,OAAO,CAAC,QAAQ;cAOG,MAAM,IAAI,IAAI;IAqJjC,OAAO,CAAC,UAAU;IAMlB;;;;OAIG;IACH,OAAO,CAAC,UAAU;IAgClB,8DAA8D;IAC9D,OAAO,CAAC,WAAW;CAkCpB;AAED,wBAAgB,kBAAkB,IAAI,OAAO,YAAY,CAExD"}
1
+ {"version":3,"file":"datepicker.d.ts","sourceRoot":"","sources":["../../../src/components/datepicker/datepicker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAK5C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,YAAa,SAAQ,SAAS;IACzC,OAAgB,MAAM,SAmJpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,OAAO,CAAC,WAAW,CAAC,CAAqB;IACzC,6CAA6C;IAC7C,OAAO,CAAC,IAAI,CAAwB;IAEpC,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAErB;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;IACD,IAAI,KAAK,CAAC,CAAC,EAAE,MAAM,EAElB;IAED,gDAAgD;IAChD,IAAI,MAAM,IAAI,MAAM,EAAE,CAGrB;IACD,IAAI,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAExB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAE1B;cAEkB,iBAAiB,IAAI,IAAI;cAWzB,oBAAoB,IAAI,IAAI;IAK/C,wFAAwF;IACxF,OAAO,CAAC,UAAU;IAyBlB,8CAA8C;IAC9C,OAAO,CAAC,WAAW;IAoBnB,iDAAiD;IACjD,OAAO,CAAC,QAAQ;IAKhB,sDAAsD;IACtD,OAAO,CAAC,iBAAiB;IAQzB,+DAA+D;IAC/D,OAAO,CAAC,QAAQ;cAOG,MAAM,IAAI,IAAI;IAqJjC,OAAO,CAAC,UAAU;IAMlB;;;;OAIG;IACH,OAAO,CAAC,UAAU;IAgClB,8DAA8D;IAC9D,OAAO,CAAC,WAAW;CAkCpB;AAED,wBAAgB,kBAAkB,IAAI,OAAO,YAAY,CAExD"}
@@ -8,7 +8,7 @@ const _FxDatepicker = class _FxDatepicker extends FxElement {
8
8
  this.time = { h: 0, m: 0, s: 0 };
9
9
  }
10
10
  static get observedAttributes() {
11
- return ["value", "start", "end", "values", "mode", "min", "max", "placeholder", "disabled", "format", "show-time", "free-text"];
11
+ return ["value", "start", "end", "values", "mode", "min", "max", "placeholder", "disabled", "format", "show-time", "free-text", "error", "invalid", "success", "valid"];
12
12
  }
13
13
  get mode() {
14
14
  const m = this.getAttr("mode", "single");
@@ -322,6 +322,10 @@ _FxDatepicker.styles = css`
322
322
  :host([mode='range'][size='lg']) .field { min-width: 360px; }
323
323
  :host([mode='multiple'][size='lg']) .field { min-width: 320px; }
324
324
  :host([show-time]) .field { min-width: 250px; }
325
+ /* Full width: o host estica até o pai (os min-width dos modos são
326
+ descartados para permitir layouts estreitos sob controle do pai). */
327
+ :host([full]) { display: block; width: 100%; }
328
+ :host([full]) .field { min-width: 0; }
325
329
  /* Validação */
326
330
  :host([error]) .field,
327
331
  :host([invalid]) .field {