@wrrdev/fenix-ui 1.1.13 → 1.1.15

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 (161) hide show
  1. package/README.md +6 -5
  2. package/dist/components/autocomplete/autocomplete.d.ts +11 -5
  3. package/dist/components/autocomplete/autocomplete.d.ts.map +1 -1
  4. package/dist/components/autocomplete/autocomplete.js +18 -2
  5. package/dist/components/autocomplete/autocomplete.js.map +1 -1
  6. package/dist/components/button/button.d.ts +15 -0
  7. package/dist/components/button/button.d.ts.map +1 -1
  8. package/dist/components/button/button.js +20 -1
  9. package/dist/components/button/button.js.map +1 -1
  10. package/dist/components/checkbox/checkbox.d.ts +20 -4
  11. package/dist/components/checkbox/checkbox.d.ts.map +1 -1
  12. package/dist/components/checkbox/checkbox.js +39 -2
  13. package/dist/components/checkbox/checkbox.js.map +1 -1
  14. package/dist/components/datepicker/datepicker.d.ts +16 -2
  15. package/dist/components/datepicker/datepicker.d.ts.map +1 -1
  16. package/dist/components/datepicker/datepicker.js +46 -2
  17. package/dist/components/datepicker/datepicker.js.map +1 -1
  18. package/dist/components/editor/commands.d.ts +27 -0
  19. package/dist/components/editor/commands.d.ts.map +1 -0
  20. package/dist/components/editor/commands.js +235 -0
  21. package/dist/components/editor/commands.js.map +1 -0
  22. package/dist/components/editor/editor.d.ts +83 -0
  23. package/dist/components/editor/editor.d.ts.map +1 -0
  24. package/dist/components/editor/editor.js +1951 -0
  25. package/dist/components/editor/editor.js.map +1 -0
  26. package/dist/components/editor/index.d.ts +12 -0
  27. package/dist/components/editor/index.d.ts.map +1 -0
  28. package/dist/components/editor/index.js +7 -0
  29. package/dist/components/editor/index.js.map +1 -0
  30. package/dist/components/editor/normalize-html.d.ts +33 -0
  31. package/dist/components/editor/normalize-html.d.ts.map +1 -0
  32. package/dist/components/editor/normalize-html.js +316 -0
  33. package/dist/components/editor/normalize-html.js.map +1 -0
  34. package/dist/components/editor/sanitize-html.d.ts +37 -0
  35. package/dist/components/editor/sanitize-html.d.ts.map +1 -0
  36. package/dist/components/editor/sanitize-html.js +247 -0
  37. package/dist/components/editor/sanitize-html.js.map +1 -0
  38. package/dist/components/editor/schema.d.ts +36 -0
  39. package/dist/components/editor/schema.d.ts.map +1 -0
  40. package/dist/components/editor/schema.js +232 -0
  41. package/dist/components/editor/schema.js.map +1 -0
  42. package/dist/components/editor/selection.d.ts +22 -0
  43. package/dist/components/editor/selection.d.ts.map +1 -0
  44. package/dist/components/editor/selection.js +97 -0
  45. package/dist/components/editor/selection.js.map +1 -0
  46. package/dist/components/editor/styles.d.ts +10 -0
  47. package/dist/components/editor/styles.d.ts.map +1 -0
  48. package/dist/components/editor/styles.js +446 -0
  49. package/dist/components/editor/styles.js.map +1 -0
  50. package/dist/components/editor/table-utils.d.ts +33 -0
  51. package/dist/components/editor/table-utils.d.ts.map +1 -0
  52. package/dist/components/editor/table-utils.js +150 -0
  53. package/dist/components/editor/table-utils.js.map +1 -0
  54. package/dist/components/fileupload/fileupload.d.ts +10 -2
  55. package/dist/components/fileupload/fileupload.d.ts.map +1 -1
  56. package/dist/components/fileupload/fileupload.js +17 -2
  57. package/dist/components/fileupload/fileupload.js.map +1 -1
  58. package/dist/components/floatlabel/floatlabel.d.ts +9 -3
  59. package/dist/components/floatlabel/floatlabel.d.ts.map +1 -1
  60. package/dist/components/floatlabel/floatlabel.js +22 -7
  61. package/dist/components/floatlabel/floatlabel.js.map +1 -1
  62. package/dist/components/input/input.d.ts +18 -7
  63. package/dist/components/input/input.d.ts.map +1 -1
  64. package/dist/components/input/input.js +33 -3
  65. package/dist/components/input/input.js.map +1 -1
  66. package/dist/components/knob/knob.d.ts +7 -3
  67. package/dist/components/knob/knob.d.ts.map +1 -1
  68. package/dist/components/knob/knob.js +4 -2
  69. package/dist/components/knob/knob.js.map +1 -1
  70. package/dist/components/multiselect/multiselect.d.ts +13 -3
  71. package/dist/components/multiselect/multiselect.d.ts.map +1 -1
  72. package/dist/components/multiselect/multiselect.js +27 -2
  73. package/dist/components/multiselect/multiselect.js.map +1 -1
  74. package/dist/components/radio/radio.d.ts +27 -4
  75. package/dist/components/radio/radio.d.ts.map +1 -1
  76. package/dist/components/radio/radio.js +67 -8
  77. package/dist/components/radio/radio.js.map +1 -1
  78. package/dist/components/rating/rating.d.ts +8 -4
  79. package/dist/components/rating/rating.d.ts.map +1 -1
  80. package/dist/components/rating/rating.js +5 -2
  81. package/dist/components/rating/rating.js.map +1 -1
  82. package/dist/components/select/select.d.ts +9 -3
  83. package/dist/components/select/select.d.ts.map +1 -1
  84. package/dist/components/select/select.js +11 -2
  85. package/dist/components/select/select.js.map +1 -1
  86. package/dist/components/slider/slider.d.ts +12 -6
  87. package/dist/components/slider/slider.d.ts.map +1 -1
  88. package/dist/components/slider/slider.js +10 -2
  89. package/dist/components/slider/slider.js.map +1 -1
  90. package/dist/components/switch/switch.d.ts +20 -4
  91. package/dist/components/switch/switch.d.ts.map +1 -1
  92. package/dist/components/switch/switch.js +40 -2
  93. package/dist/components/switch/switch.js.map +1 -1
  94. package/dist/components/textarea/textarea.d.ts +12 -5
  95. package/dist/components/textarea/textarea.d.ts.map +1 -1
  96. package/dist/components/textarea/textarea.js +12 -2
  97. package/dist/components/textarea/textarea.js.map +1 -1
  98. package/dist/components/toast/toast.d.ts +27 -15
  99. package/dist/components/toast/toast.d.ts.map +1 -1
  100. package/dist/components/toast/toast.js +205 -32
  101. package/dist/components/toast/toast.js.map +1 -1
  102. package/dist/components/toggle-button-group/toggle-button-group.d.ts +13 -5
  103. package/dist/components/toggle-button-group/toggle-button-group.d.ts.map +1 -1
  104. package/dist/components/toggle-button-group/toggle-button-group.js +19 -2
  105. package/dist/components/toggle-button-group/toggle-button-group.js.map +1 -1
  106. package/dist/core/base.d.ts +7 -0
  107. package/dist/core/base.d.ts.map +1 -1
  108. package/dist/core/base.js +4 -1
  109. package/dist/core/base.js.map +1 -1
  110. package/dist/core/form-element.d.ts +81 -0
  111. package/dist/core/form-element.d.ts.map +1 -0
  112. package/dist/core/form-element.js +129 -0
  113. package/dist/core/form-element.js.map +1 -0
  114. package/dist/core/jsx.d.ts +25 -1
  115. package/dist/core/jsx.d.ts.map +1 -1
  116. package/dist/core/jsx.js.map +1 -1
  117. package/dist/core/presets.d.ts +8 -3
  118. package/dist/core/presets.d.ts.map +1 -1
  119. package/dist/core/presets.js +6 -17
  120. package/dist/core/presets.js.map +1 -1
  121. package/dist/core/types.d.ts +4 -0
  122. package/dist/core/types.d.ts.map +1 -1
  123. package/dist/core/vue.d.ts +3 -1
  124. package/dist/core/vue.d.ts.map +1 -1
  125. package/dist/core/vue.js.map +1 -1
  126. package/dist/docs/app.d.ts.map +1 -1
  127. package/dist/docs/componentes/autocomplete.doc.d.ts.map +1 -1
  128. package/dist/docs/componentes/checkbox.doc.d.ts.map +1 -1
  129. package/dist/docs/componentes/datepicker.doc.d.ts.map +1 -1
  130. package/dist/docs/componentes/editor.doc.d.ts +6 -0
  131. package/dist/docs/componentes/editor.doc.d.ts.map +1 -0
  132. package/dist/docs/componentes/fileupload.doc.d.ts.map +1 -1
  133. package/dist/docs/componentes/index.d.ts.map +1 -1
  134. package/dist/docs/componentes/input.doc.d.ts.map +1 -1
  135. package/dist/docs/componentes/knob.doc.d.ts.map +1 -1
  136. package/dist/docs/componentes/multiselect.doc.d.ts.map +1 -1
  137. package/dist/docs/componentes/rating.doc.d.ts.map +1 -1
  138. package/dist/docs/componentes/select.doc.d.ts.map +1 -1
  139. package/dist/docs/componentes/slider.doc.d.ts.map +1 -1
  140. package/dist/docs/componentes/switch.doc.d.ts.map +1 -1
  141. package/dist/docs/componentes/textarea.doc.d.ts.map +1 -1
  142. package/dist/docs/componentes/toast.doc.d.ts.map +1 -1
  143. package/dist/docs/componentes/toggle-button-group.doc.d.ts.map +1 -1
  144. package/dist/docs/frameworks.d.ts +75 -0
  145. package/dist/docs/frameworks.d.ts.map +1 -0
  146. package/dist/docs/ui/code.d.ts +4 -0
  147. package/dist/docs/ui/code.d.ts.map +1 -1
  148. package/dist/fenix-ui.umd.min.js +786 -254
  149. package/dist/fenix-ui2.esm.js.map +1 -1
  150. package/dist/fenix-ui4.esm.js +4396 -172
  151. package/dist/fenix-ui4.esm.js.map +1 -1
  152. package/dist/fenix-ui5.esm.js +1 -0
  153. package/dist/fenix-ui5.esm.js.map +1 -1
  154. package/dist/index.d.ts +2 -0
  155. package/dist/index.d.ts.map +1 -1
  156. package/dist/index.js +18 -1
  157. package/dist/index.js.map +1 -1
  158. package/dist/plugins/auto-import.d.ts.map +1 -1
  159. package/dist/plugins/auto-import.js +1 -0
  160. package/dist/plugins/auto-import.js.map +1 -1
  161. package/package.json +5 -1
package/README.md CHANGED
@@ -18,8 +18,8 @@ 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.13**, acesso público.
22
- > Subpaths disponíveis: `./theme`, `./tokens`, `./jsx`, `./vue`, `./react`, `./button`, `./badge`, `./spinner`, `./select`, `./input`, `./switch`, `./textarea`, `./checkbox`, `./radio`, `./calendar`, `./datepicker`, `./multiselect`, `./table`, `./floatlabel`, `./dialog`, `./toast`, `./tooltip`, `./tabs`, `./progress`, `./skeleton`, `./alert`, `./drawer`, `./dropdown`, `./icon`, `./icons`, `./pagination`, `./autocomplete`, `./knob`, `./orderlist`, `./picklist`, `./auto-import` e o curinga `./components/*`.
21
+ > 📦 npm: [`@wrrdev/fenix-ui`](https://www.npmjs.com/package/@wrrdev/fenix-ui) — **v1.1.15**, acesso público.
22
+ > Subpaths disponíveis: `./theme`, `./tokens`, `./jsx`, `./vue`, `./react`, `./button`, `./badge`, `./spinner`, `./select`, `./input`, `./switch`, `./textarea`, `./editor`, `./checkbox`, `./radio`, `./calendar`, `./datepicker`, `./multiselect`, `./table`, `./floatlabel`, `./dialog`, `./toast`, `./tooltip`, `./tabs`, `./progress`, `./skeleton`, `./alert`, `./drawer`, `./dropdown`, `./icon`, `./icons`, `./pagination`, `./autocomplete`, `./knob`, `./orderlist`, `./picklist`, `./auto-import` e o curinga `./components/*`.
23
23
 
24
24
 
25
25
  ## Consumo
@@ -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.13):
68
+ 🔗 Links diretos para CDN (versão 1.1.15):
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
  -->
@@ -140,6 +140,7 @@ export default defineConfig({
140
140
  | `fx-table` | `@wrrdev/fenix-ui/table` |
141
141
  | `fx-floatlabel` | `@wrrdev/fenix-ui/floatlabel` |
142
142
  | `fx-textarea` | `@wrrdev/fenix-ui/textarea` |
143
+ | `fx-editor` | `@wrrdev/fenix-ui/editor` |
143
144
  | `fx-dialog` | `@wrrdev/fenix-ui/dialog` |
144
145
  | `fx-drawer` | `@wrrdev/fenix-ui/drawer` |
145
146
  | `fx-toast` | `@wrrdev/fenix-ui/toast` |
@@ -214,7 +215,7 @@ applyPreset('seiya'); // aplica o preset Seiya em modo claro
214
215
  applyPreset('shiryu', 'dark'); // preset + modo escuro
215
216
  ```
216
217
 
217
- Presets disponíveis: `fenix` (padrão), `seiya`, `shiryu`, `hyoga`, `shun`, `ikki`, `aiolia`.
218
+ Presets disponíveis: `ikki` (padrão), `seiya`, `shiryu`, `hyoga`, `shun`, `aiolia`.
218
219
 
219
220
  ```ts
220
221
  import { listPresets } from '@wrrdev/fenix-ui';
@@ -392,7 +393,7 @@ ative o modo estrito do Volar no `tsconfig.json`:
392
393
  |--------------|-----------------------------------------------|--------------|
393
394
  | `fx-button` | `variant` (primary/secondary/success/warning/danger/ghost/outline), `size`, `disabled`, `loading`, `type`, `full` | default, `icon` |
394
395
  | `fx-badge` | `variant` (default/primary/success/warning/danger/info), `round` | default |
395
- | `fx-input` | `type` (text/number/email/password/search/tel/url), `value`, `size`, `placeholder`, `disabled`, `readonly`, `min/max/step` | — |
396
+ | `fx-input` | `type` (text/number/password), `value`, `size`, `placeholder`, `disabled`, `readonly`, `min/max/step` | — |
396
397
  | `fx-select` | `value`, `size` (sm/md/lg), `disabled`, `placeholder` — filhos `<option>` nativos | `(padrão)` |
397
398
  | `fx-switch` | `checked`, `disabled`, `size` (sm/md/lg) | `(padrão)` |
398
399
  | `fx-spinner` | `size` (sm/md/lg) | — |
@@ -1,12 +1,16 @@
1
- import { FxElement } from '../../core/base';
1
+ import { FxFormElement } from '../../core/form-element';
2
2
  /**
3
3
  * <fx-autocomplete> — Campo de texto com sugestões filtradas.
4
4
  *
5
- * Atributos: value, size, placeholder, source (JSON array de strings),
6
- * disabled, min-chars (padrão 1).
7
- * Evento: `select` (composed, detail: { value }).
5
+ * Form-Associated Custom Element: dentro de um <form> entra no FormData
6
+ * (via atributo `name`) e é alvo de `<label for>`/`autofocus`.
7
+ *
8
+ * Atributos: value, name, size, placeholder, source (JSON array de strings),
9
+ * disabled, min-chars (padrão 1), autofocus.
10
+ * Eventos: `select`, `input` e `valueChange` (composed, detail: { value }) —
11
+ * `input` aciona o v-model do Vue e `valueChange` o [(value)] do Angular.
8
12
  */
9
- export declare class FxAutocomplete extends FxElement {
13
+ export declare class FxAutocomplete extends FxFormElement {
10
14
  static styles: string;
11
15
  static get observedAttributes(): string[];
12
16
  get size(): string;
@@ -21,6 +25,8 @@ export declare class FxAutocomplete extends FxElement {
21
25
  protected connectedCallback(): void;
22
26
  protected disconnectedCallback(): void;
23
27
  protected render(): void;
28
+ /** Empurra o atributo `value` para o campo interno sem re-renderizar. */
29
+ protected syncInternalControl(): void;
24
30
  }
25
31
  export declare function defineFxAutocomplete(): typeof FxAutocomplete;
26
32
  //# sourceMappingURL=autocomplete.d.ts.map
@@ -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,SAqFpB;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"}
1
+ {"version":3,"file":"autocomplete.d.ts","sourceRoot":"","sources":["../../../src/components/autocomplete/autocomplete.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAKxD;;;;;;;;;;GAUG;AACH,qBAAa,cAAe,SAAQ,aAAa;IAC/C,OAAgB,MAAM,SAqFpB;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,EAMlB;IAED,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;IA0EjC,yEAAyE;cACtD,mBAAmB,IAAI,IAAI;CAK/C;AAED,wBAAgB,oBAAoB,IAAI,OAAO,cAAc,CAE5D"}
@@ -1,8 +1,8 @@
1
- import { FxElement } from "../../core/base.js";
1
+ import { FxFormElement } from "../../core/form-element.js";
2
2
  import { css } from "../../core/css.js";
3
3
  import { defineElement } from "../../core/define.js";
4
4
  import { esc } from "../../core/sanitize.js";
5
- const _FxAutocomplete = class _FxAutocomplete extends FxElement {
5
+ const _FxAutocomplete = class _FxAutocomplete extends FxFormElement {
6
6
  static get observedAttributes() {
7
7
  return ["size", "placeholder", "source", "disabled", "min-chars", "error", "invalid", "success", "valid"];
8
8
  }
@@ -18,6 +18,8 @@ const _FxAutocomplete = class _FxAutocomplete extends FxElement {
18
18
  }
19
19
  set value(v) {
20
20
  this.setAttribute("value", v);
21
+ this.syncInternalControl();
22
+ this.updateFormValue();
21
23
  }
22
24
  get source() {
23
25
  try {
@@ -67,6 +69,7 @@ const _FxAutocomplete = class _FxAutocomplete extends FxElement {
67
69
  field.value = btn.dataset.v ?? "";
68
70
  this.value = field.value;
69
71
  this.toggleAttr("open", false);
72
+ this.emitValueEvents();
70
73
  this.dispatchEvent(new CustomEvent("select", {
71
74
  bubbles: true,
72
75
  composed: true,
@@ -78,6 +81,7 @@ const _FxAutocomplete = class _FxAutocomplete extends FxElement {
78
81
  };
79
82
  field.addEventListener("input", () => {
80
83
  this.value = field.value;
84
+ this.emitValueEvents();
81
85
  openList();
82
86
  });
83
87
  field.addEventListener("focus", () => {
@@ -88,6 +92,11 @@ const _FxAutocomplete = class _FxAutocomplete extends FxElement {
88
92
  this.toggleAttr("open", false);
89
93
  return;
90
94
  }
95
+ if (e.key === "Enter" && !e.isComposing && !this.hasAttr("open") && this.form) {
96
+ e.preventDefault();
97
+ this.form.requestSubmit();
98
+ return;
99
+ }
91
100
  if (e.key !== "ArrowDown" && e.key !== "ArrowUp") return;
92
101
  e.preventDefault();
93
102
  if (!this.hasAttr("open")) openList();
@@ -97,6 +106,13 @@ const _FxAutocomplete = class _FxAutocomplete extends FxElement {
97
106
  const next = e.key === "ArrowDown" ? current === -1 ? 0 : Math.min(current + 1, opts.length - 1) : current === -1 ? opts.length - 1 : Math.max(current - 1, 0);
98
107
  opts[next]?.focus();
99
108
  });
109
+ this.updateFormValue();
110
+ }
111
+ /** Empurra o atributo `value` para o campo interno sem re-renderizar. */
112
+ syncInternalControl() {
113
+ const field = this.root.querySelector(".field");
114
+ const value = this.getAttr("value");
115
+ if (field && field.value !== value) field.value = value;
100
116
  }
101
117
  };
102
118
  _FxAutocomplete.styles = css`
@@ -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 /* Largura padrão no HOST: CSS externo, classes e style inline no elemento\n vencem este default naturalmente (regras do documento > :host). */\n width: var(--fx-autocomplete-width, 260px);\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 /* Altura determinística: a caixa fica EXATAMENTE no token --fx-size-*. */\n line-height: var(--fx-font-line-height);\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-sm) var(--fx-space-lg);\n /* Acompanha a largura definida no :host. */\n width: 100%;\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']) { width: var(--fx-autocomplete-width-sm, 220px); }\n :host([size='sm']) .field { min-height: var(--fx-size-sm); padding: var(--fx-space-xs) var(--fx-space-md); }\n :host([size='lg']) { width: var(--fx-autocomplete-width-lg, 300px); }\n :host([size='lg']) .field { min-height: var(--fx-size-lg); padding: var(--fx-space-sm) var(--fx-space-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: borda sempre; brilho (anel) via token que acompanha\n effect.focus-ring — desligado ⇒ erro só com a borda vermelha. */\n :host([error]) .field,\n :host([invalid]) .field {\n border-color: var(--fx-color-danger, #dc2626);\n box-shadow: var(--fx-effect-error-ring, 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: var(--fx-effect-success-ring, 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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"}
1
+ {"version":3,"file":"autocomplete.js","sources":["../../../src/components/autocomplete/autocomplete.ts"],"sourcesContent":["import { FxFormElement } from '../../core/form-element';\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 * Form-Associated Custom Element: dentro de um <form> entra no FormData\n * (via atributo `name`) e é alvo de `<label for>`/`autofocus`.\n *\n * Atributos: value, name, size, placeholder, source (JSON array de strings),\n * disabled, min-chars (padrão 1), autofocus.\n * Eventos: `select`, `input` e `valueChange` (composed, detail: { value }) —\n * `input` aciona o v-model do Vue e `valueChange` o [(value)] do Angular.\n */\nexport class FxAutocomplete extends FxFormElement {\n static override styles = css`\n :host {\n position: relative;\n display: inline-block;\n /* Largura padrão no HOST: CSS externo, classes e style inline no elemento\n vencem este default naturalmente (regras do documento > :host). */\n width: var(--fx-autocomplete-width, 260px);\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 /* Altura determinística: a caixa fica EXATAMENTE no token --fx-size-*. */\n line-height: var(--fx-font-line-height);\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-sm) var(--fx-space-lg);\n /* Acompanha a largura definida no :host. */\n width: 100%;\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']) { width: var(--fx-autocomplete-width-sm, 220px); }\n :host([size='sm']) .field { min-height: var(--fx-size-sm); padding: var(--fx-space-xs) var(--fx-space-md); }\n :host([size='lg']) { width: var(--fx-autocomplete-width-lg, 300px); }\n :host([size='lg']) .field { min-height: var(--fx-size-lg); padding: var(--fx-space-sm) var(--fx-space-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: borda sempre; brilho (anel) via token que acompanha\n effect.focus-ring — desligado ⇒ erro só com a borda vermelha. */\n :host([error]) .field,\n :host([invalid]) .field {\n border-color: var(--fx-color-danger, #dc2626);\n box-shadow: var(--fx-effect-error-ring, 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: var(--fx-effect-success-ring, 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) {\n this.setAttribute('value', v);\n // `value` não é observado (não pode re-renderizar por tecla): sincroniza\n // o campo interno direto, sem perder foco/cursor, e publica no form.\n this.syncInternalControl();\n this.updateFormValue();\n }\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.emitValueEvents();\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 this.emitValueEvents();\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 // Enter com a lista FECHADA submete o form (o input interno não se associa\n // ao <form> do light DOM — submissão implícita acionada manualmente).\n field.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') {\n this.toggleAttr('open', false);\n return;\n }\n if (e.key === 'Enter' && !e.isComposing && !this.hasAttr('open') && this.form) {\n e.preventDefault();\n this.form.requestSubmit();\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 // Publica name/value no form (ElementInternals) a cada render.\n this.updateFormValue();\n }\n\n /** Empurra o atributo `value` para o campo interno sem re-renderizar. */\n protected override syncInternalControl(): void {\n const field = this.root.querySelector<HTMLInputElement>('.field');\n const value = this.getAttr('value');\n if (field && field.value !== value) field.value = value;\n }\n}\n\nexport function defineFxAutocomplete(): typeof FxAutocomplete {\n return defineElement('fx-autocomplete', FxAutocomplete);\n}\n"],"names":[],"mappings":";;;;AAgBO,MAAM,kBAAN,MAAM,wBAAuB,cAAc;AAAA,EAwFhD,WAAoB,qBAA+B;AAGjD,WAAO,CAAC,QAAQ,eAAe,UAAU,YAAY,aAAa,SAAS,WAAW,WAAW,OAAO;AAAA,EAC1G;AAAA,EAEA,IAAI,OAAe;AACjB,UAAM,IAAI,KAAK,QAAQ,QAAQ,IAAI;AACnC,WAAO,MAAM,QAAQ,MAAM,OAAO,IAAI;AAAA,EACxC;AAAA,EACA,IAAI,KAAK,GAAW;AAAE,SAAK,aAAa,QAAQ,CAAC;AAAA,EAAG;AAAA,EACpD,IAAI,QAAgB;AAAE,WAAO,KAAK,QAAQ,OAAO;AAAA,EAAG;AAAA,EACpD,IAAI,MAAM,GAAW;AACnB,SAAK,aAAa,SAAS,CAAC;AAG5B,SAAK,oBAAA;AACL,SAAK,gBAAA;AAAA,EACP;AAAA,EAEA,IAAI,SAAmB;AACrB,QAAI;AACF,YAAM,MAAM,KAAK,MAAM,KAAK,QAAQ,UAAU,IAAI,CAAC;AACnD,aAAO,MAAM,QAAQ,GAAG,IAAI,IAAI,IAAI,MAAM,IAAI,CAAA;AAAA,IAChD,QAAQ;AACN,aAAO,CAAA;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA,EAGA,IAAI,OAAO,OAA0B;AACnC,SAAK,aAAa,UAAU,OAAO,UAAU,WAAW,QAAQ,KAAK,UAAU,KAAK,CAAC;AAAA,EACvF;AAAA,EAImB,oBAA0B;AAC3C,UAAM,kBAAA;AACN,SAAK,cAAc,CAAC,MAAa;AAC/B,UAAI,CAAC,KAAK,SAAS,EAAE,MAAc,EAAG,MAAK,WAAW,QAAQ,KAAK;AAAA,IACrE;AACA,aAAS,iBAAiB,SAAS,KAAK,WAAW;AAAA,EACrD;AAAA,EAEmB,uBAA6B;AAC9C,UAAM,qBAAA;AACN,QAAI,KAAK,YAAa,UAAS,oBAAoB,SAAS,KAAK,WAAW;AAC5E,SAAK,cAAc;AAAA,EACrB;AAAA,EAEmB,SAAe;AAChC,UAAM,cAAc,KAAK,QAAQ,aAAa;AAE9C,SAAK,YAAY;AAAA;AAAA,UAEX,cAAc,gBAAgB,IAAI,WAAW,CAAC,MAAM,EAAE;AAAA;AAAA,KAE3D;AAED,UAAM,QAAQ,KAAK,KAAK,cAAgC,QAAQ;AAChE,QAAI,CAAC,MAAO;AACZ,UAAM,QAAQ,KAAK;AACnB,QAAI,KAAK,QAAQ,UAAU,EAAG,OAAM,aAAa,YAAY,EAAE;AAE/D,UAAM,OAAO,KAAK,KAAK,cAA2B,OAAO;AACzD,UAAM,WAAW,OAAO,KAAK,QAAQ,aAAa,GAAG,CAAC,KAAK;AAE3D,UAAM,WAAW,MAAY;AAC3B,UAAI,CAAC,KAAM;AACX,YAAM,IAAI,MAAM,MAAM,YAAA;AACtB,YAAM,UAAU,KAAK,OAAO,OAAO,CAAC,MAAM,EAAE,YAAA,EAAc,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC;AACjF,WAAK,YAAY,QAAQ,SACrB,QAAQ,IAAI,CAAC,MAAM,2DAA2D,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,WAAW,EAAE,KAAK,EAAE,IACnH;AACJ,WAAK,iBAAoC,MAAM,EAAE,QAAQ,CAAC,QAAQ;AAChE,YAAI,iBAAiB,SAAS,MAAM;AAClC,gBAAM,QAAQ,IAAI,QAAQ,KAAK;AAC/B,eAAK,QAAQ,MAAM;AACnB,eAAK,WAAW,QAAQ,KAAK;AAC7B,eAAK,gBAAA;AACL,eAAK,cAAc,IAAI,YAAY,UAAU;AAAA,YAC3C,SAAS;AAAA,YAAM,UAAU;AAAA,YAAM,QAAQ,EAAE,OAAO,KAAK,MAAA;AAAA,UAAM,CAC5D,CAAC;AAAA,QACJ,CAAC;AAAA,MACH,CAAC;AACD,WAAK,WAAW,QAAQ,IAAI;AAAA,IAC9B;AAGA,UAAM,iBAAiB,SAAS,MAAM;AACpC,WAAK,QAAQ,MAAM;AACnB,WAAK,gBAAA;AACL,eAAA;AAAA,IACF,CAAC;AACD,UAAM,iBAAiB,SAAS,MAAM;AAAE,UAAI,MAAM,MAAM,UAAU,SAAU,UAAA;AAAA,IAAY,CAAC;AAIzF,UAAM,iBAAiB,WAAW,CAAC,MAAM;AACvC,UAAI,EAAE,QAAQ,UAAU;AACtB,aAAK,WAAW,QAAQ,KAAK;AAC7B;AAAA,MACF;AACA,UAAI,EAAE,QAAQ,WAAW,CAAC,EAAE,eAAe,CAAC,KAAK,QAAQ,MAAM,KAAK,KAAK,MAAM;AAC7E,UAAE,eAAA;AACF,aAAK,KAAK,cAAA;AACV;AAAA,MACF;AACA,UAAI,EAAE,QAAQ,eAAe,EAAE,QAAQ,UAAW;AAClD,QAAE,eAAA;AACF,UAAI,CAAC,KAAK,QAAQ,MAAM,EAAG,UAAA;AAC3B,YAAM,OAAO,MAAM,KAAK,KAAK,KAAK,iBAAoC,MAAM,CAAC;AAC7E,UAAI,CAAC,KAAK,OAAQ;AAClB,YAAM,UAAU,KAAK,QAAQ,KAAK,KAAK,aAAkC;AACzE,YAAM,OAAO,EAAE,QAAQ,cAClB,YAAY,KAAK,IAAI,KAAK,IAAI,UAAU,GAAG,KAAK,SAAS,CAAC,IAC1D,YAAY,KAAK,KAAK,SAAS,IAAI,KAAK,IAAI,UAAU,GAAG,CAAC;AAC/D,WAAK,IAAI,GAAG,MAAA;AAAA,IACd,CAAC;AAGD,SAAK,gBAAA;AAAA,EACP;AAAA;AAAA,EAGmB,sBAA4B;AAC7C,UAAM,QAAQ,KAAK,KAAK,cAAgC,QAAQ;AAChE,UAAM,QAAQ,KAAK,QAAQ,OAAO;AAClC,QAAI,SAAS,MAAM,UAAU,aAAa,QAAQ;AAAA,EACpD;AACF;AAzNE,gBAAgB,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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AADpB,IAAM,iBAAN;AA4NA,SAAS,uBAA8C;AAC5D,SAAO,cAAc,mBAAmB,cAAc;AACxD;"}
@@ -2,6 +2,11 @@ import { FxElement } from '../../core/base';
2
2
  /**
3
3
  * <fx-button> — Componente de ação (piloto).
4
4
  *
5
+ * Form-Associated Custom Element: `type="submit"` submete o <form> em que
6
+ * vive e `type="reset"` o limpa (o <button> interno está no Shadow DOM e não
7
+ * se associa ao form sozinho — a ação é acionada via ElementInternals).
8
+ * `<form disabled>`/`<fieldset disabled>` desabilitam o botão.
9
+ *
5
10
  * Atributos: variant, size, disabled, loading, type, full (largura 100%
6
11
  * acompanhando o elemento pai), icon (nome do glifo Fenix Icons ou
7
12
  * emoji/texto livre) e icon-pos (left|right, padrão left).
@@ -9,8 +14,18 @@ import { FxElement } from '../../core/base';
9
14
  * Evento: `click` nativo atravessa o Shadow DOM (composed) — ouça no host.
10
15
  */
11
16
  export declare class FxButton extends FxElement {
17
+ /** Participa de <form> como botão (submit/reset via ElementInternals). */
18
+ static formAssociated: boolean;
19
+ /** focus() e clique em <label for> atingem o botão interno do Shadow DOM. */
20
+ static shadowOptions: ShadowRootInit;
12
21
  static styles: string;
13
22
  static get observedAttributes(): string[];
23
+ protected readonly internals: ElementInternals | null;
24
+ constructor();
25
+ /** Form ao qual o botão está associado (null se fora de um form). */
26
+ get form(): HTMLFormElement | null;
27
+ /** <form disabled>/<fieldset disabled>: espelha no atributo observado. */
28
+ formDisabledCallback(disabled: boolean): void;
14
29
  /** Tamanho. Padrão: `'md'`. */
15
30
  get size(): string;
16
31
  set size(value: string);
@@ -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;AAK5C;;;;;;;;GAQG;AACH,qBAAa,QAAS,SAAQ,SAAS;IACrC,OAAgB,MAAM,SAmHpB;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
+ {"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../../src/components/button/button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAK5C;;;;;;;;;;;;;GAaG;AACH,qBAAa,QAAS,SAAQ,SAAS;IACrC,0EAA0E;IAC1E,MAAM,CAAC,cAAc,UAAQ;IAC7B,6EAA6E;IAC7E,OAAgB,aAAa,EAAE,cAAc,CAA0C;IACvF,OAAgB,MAAM,SAmHpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,SAAS,CAAC,QAAQ,CAAC,SAAS,EAAE,gBAAgB,GAAG,IAAI,CAAC;;IAUtD,qEAAqE;IACrE,IAAI,IAAI,IAAI,eAAe,GAAG,IAAI,CAIjC;IAED,0EAA0E;IAC1E,oBAAoB,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI;IAK7C,+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;cA8Dd,iBAAiB,IAAI,IAAI;cAKzB,oBAAoB,IAAI,IAAI;IAK/C;;;;OAIG;IACH,OAAO,CAAC,WAAW,CAmBjB;CACH;AAED,wBAAgB,cAAc,IAAI,OAAO,QAAQ,CAEhD"}
@@ -4,7 +4,7 @@ import { defineElement } from "../../core/define.js";
4
4
  import { FENIX_ICON_BASE_CSS, fenixIconHtml } from "../../icons/base-css.js";
5
5
  const _FxButton = class _FxButton extends FxElement {
6
6
  constructor() {
7
- super(...arguments);
7
+ super();
8
8
  this.spawnRipple = (event) => {
9
9
  if (this.hasAttr("disabled") || this.hasAttr("loading")) return;
10
10
  const enabled = getComputedStyle(this).getPropertyValue("--fx-effect-ripple").trim();
@@ -21,10 +21,20 @@ const _FxButton = class _FxButton extends FxElement {
21
21
  btn.appendChild(ripple);
22
22
  ripple.addEventListener("animationend", () => ripple.remove());
23
23
  };
24
+ this.internals = typeof this.attachInternals === "function" ? this.attachInternals() : null;
24
25
  }
25
26
  static get observedAttributes() {
26
27
  return ["variant", "size", "disabled", "loading", "type", "full", "icon", "icon-pos"];
27
28
  }
29
+ /** Form ao qual o botão está associado (null se fora de um form). */
30
+ get form() {
31
+ return this.internals?.form ?? this.closest("form");
32
+ }
33
+ /** <form disabled>/<fieldset disabled>: espelha no atributo observado. */
34
+ formDisabledCallback(disabled) {
35
+ if (disabled) this.setAttribute("disabled", "");
36
+ else this.removeAttribute("disabled");
37
+ }
28
38
  /** Tamanho. Padrão: `'md'`. */
29
39
  get size() {
30
40
  const s = this.getAttr("size", "md");
@@ -80,6 +90,13 @@ const _FxButton = class _FxButton extends FxElement {
80
90
  }
81
91
  if (loading) btn.setAttribute("aria-busy", "true");
82
92
  else btn.removeAttribute("aria-busy");
93
+ if (btnType === "submit" || btnType === "reset") {
94
+ btn.addEventListener("click", () => {
95
+ if (this.hasAttr("disabled") || this.hasAttr("loading")) return;
96
+ if (btnType === "submit" && this.form) this.form.requestSubmit();
97
+ else if (btnType === "reset" && this.form) this.form.reset();
98
+ });
99
+ }
83
100
  }
84
101
  connectedCallback() {
85
102
  super.connectedCallback();
@@ -90,6 +107,8 @@ const _FxButton = class _FxButton extends FxElement {
90
107
  this.removeEventListener("pointerdown", this.spawnRipple);
91
108
  }
92
109
  };
110
+ _FxButton.formAssociated = true;
111
+ _FxButton.shadowOptions = { mode: "open", delegatesFocus: true };
93
112
  _FxButton.styles = css`
94
113
  ${FENIX_ICON_BASE_CSS}
95
114
  :host {
@@ -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';\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 vertical limitado ao orçamento do token --fx-size-*: a altura\n fica EXATAMENTE em sm 32 / md 40 / lg 48 (mesma medida dos campos). */\n padding: var(--fx-space-sm) 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: none;\n box-shadow: var(--fx-effect-focus-ring, none);\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-xs) 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-sm) 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
+ {"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 * Form-Associated Custom Element: `type=\"submit\"` submete o <form> em que\n * vive e `type=\"reset\"` o limpa (o <button> interno está no Shadow DOM e não\n * se associa ao form sozinho — a ação é acionada via ElementInternals).\n * `<form disabled>`/`<fieldset disabled>` desabilitam o botão.\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 /** Participa de <form> como botão (submit/reset via ElementInternals). */\n static formAssociated = true;\n /** focus() e clique em <label for> atingem o botão interno do Shadow DOM. */\n static override shadowOptions: ShadowRootInit = { mode: 'open', delegatesFocus: true };\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 vertical limitado ao orçamento do token --fx-size-*: a altura\n fica EXATAMENTE em sm 32 / md 40 / lg 48 (mesma medida dos campos). */\n padding: var(--fx-space-sm) 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: none;\n box-shadow: var(--fx-effect-focus-ring, none);\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-xs) 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-sm) 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 protected readonly internals: ElementInternals | null;\n\n constructor() {\n super();\n // Ambientes sem ElementInternals (ex.: jsdom nos testes) funcionam sem\n // a integração nativa de form — o getter `form` cai no closest('form').\n this.internals =\n typeof this.attachInternals === 'function' ? this.attachInternals() : null;\n }\n\n /** Form ao qual o botão está associado (null se fora de um form). */\n get form(): HTMLFormElement | null {\n return (\n this.internals?.form ?? (this.closest('form') as HTMLFormElement | null)\n );\n }\n\n /** <form disabled>/<fieldset disabled>: espelha no atributo observado. */\n formDisabledCallback(disabled: boolean): void {\n if (disabled) this.setAttribute('disabled', '');\n else this.removeAttribute('disabled');\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 // type=\"submit\"/\"reset\": o <button> interno vive no Shadow DOM e NÃO se\n // associa ao <form> do light DOM (a fronteira do shadow corta a associação\n // por árvore) — a ação do botão é acionada manualmente via requestSubmit/\n // reset do form associado. Ouvintes de submit seguem recebendo o evento.\n if (btnType === 'submit' || btnType === 'reset') {\n btn.addEventListener('click', () => {\n if (this.hasAttr('disabled') || this.hasAttr('loading')) return;\n if (btnType === 'submit' && this.form) this.form.requestSubmit();\n else if (btnType === 'reset' && this.form) this.form.reset();\n });\n }\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,11 +1,16 @@
1
- import { FxElement } from '../../core/base';
1
+ import { FxFormElement } from '../../core/form-element';
2
2
  /**
3
3
  * <fx-checkbox> — Caixa de seleção com suporte a estado indeterminado.
4
4
  *
5
- * Atributos: checked, indeterminate, disabled, value, size (sm|md|lg).
6
- * Evento: `change` (composed, detail: { checked, value }).
5
+ * Form-Associated Custom Element: dentro de um <form> entra no FormData
6
+ * (via atributo `name`) com `value` (ou 'on') quando marcado — desmarcado
7
+ * não envia nada, igual ao checkbox nativo. Recebe reset/disable do form.
8
+ *
9
+ * Atributos: checked, indeterminate, disabled, value, name, size (sm|md|lg).
10
+ * Eventos: `change` e `checkedChange` (composed, detail: { checked, value }) —
11
+ * `checkedChange` é o two-way binding do Angular [(checked)].
7
12
  */
8
- export declare class FxCheckbox extends FxElement {
13
+ export declare class FxCheckbox extends FxFormElement {
9
14
  static styles: string;
10
15
  static get observedAttributes(): string[];
11
16
  get checked(): boolean;
@@ -14,10 +19,21 @@ export declare class FxCheckbox extends FxElement {
14
19
  set indeterminate(value: boolean);
15
20
  get disabled(): boolean;
16
21
  set disabled(value: boolean);
22
+ /** No FormData: `value` (ou 'on') quando marcado; null (não envia) quando não. */
23
+ protected getFormValue(): string | FormData | null;
24
+ /** getter/setter `value` → espelho do atributo `value` (compat com v-model / [(value)]). */
25
+ get value(): string;
26
+ set value(v: string);
17
27
  /** Tamanho do controle. Padrão: `'md'`. */
18
28
  get size(): string;
19
29
  set size(value: string);
20
30
  protected render(): void;
31
+ /** Estado marcado inicial (para form.reset() restaurar). */
32
+ private _initialChecked;
33
+ private _initialCheckedCaptured;
34
+ protected connectedCallback(): void;
35
+ /** form.reset(): restaura o estado marcado inicial (checked/indeterminate não enviam nada). */
36
+ formResetCallback(): void;
21
37
  private toggle;
22
38
  }
23
39
  export declare function defineFxCheckbox(): typeof FxCheckbox;
@@ -1 +1 @@
1
- {"version":3,"file":"checkbox.d.ts","sourceRoot":"","sources":["../../../src/components/checkbox/checkbox.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAI5C;;;;;GAKG;AACH,qBAAa,UAAW,SAAQ,SAAS;IACvC,OAAgB,MAAM,SA6DpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAEzB;IAED,IAAI,aAAa,IAAI,OAAO,CAE3B;IACD,IAAI,aAAa,CAAC,KAAK,EAAE,OAAO,EAE/B;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAE1B;IAED,2CAA2C;IAC3C,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAErB;cAEkB,MAAM,IAAI,IAAI;IA6BjC,OAAO,CAAC,MAAM;CAYf;AAED,wBAAgB,gBAAgB,IAAI,OAAO,UAAU,CAEpD"}
1
+ {"version":3,"file":"checkbox.d.ts","sourceRoot":"","sources":["../../../src/components/checkbox/checkbox.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAIxD;;;;;;;;;;GAUG;AACH,qBAAa,UAAW,SAAQ,aAAa;IAC3C,OAAgB,MAAM,SA6DpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAEzB;IAED,IAAI,aAAa,IAAI,OAAO,CAE3B;IACD,IAAI,aAAa,CAAC,KAAK,EAAE,OAAO,EAE/B;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAE1B;IAED,kFAAkF;cAC/D,YAAY,IAAI,MAAM,GAAG,QAAQ,GAAG,IAAI;IAI3D,4FAA4F;IAC5F,IAAI,KAAK,IAAI,MAAM,CAAkC;IACrD,IAAI,KAAK,CAAC,CAAC,EAAE,MAAM,EAAoC;IAEvD,2CAA2C;IAC3C,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAErB;cAEkB,MAAM,IAAI,IAAI;IAgCjC,4DAA4D;IAC5D,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,uBAAuB,CAAS;cAErB,iBAAiB,IAAI,IAAI;IAQ5C,+FAA+F;IACtF,iBAAiB,IAAI,IAAI;IAMlC,OAAO,CAAC,MAAM;CAoBf;AAED,wBAAgB,gBAAgB,IAAI,OAAO,UAAU,CAEpD"}
@@ -1,7 +1,12 @@
1
- import { FxElement } from "../../core/base.js";
1
+ import { FxFormElement } from "../../core/form-element.js";
2
2
  import { css } from "../../core/css.js";
3
3
  import { defineElement } from "../../core/define.js";
4
- const _FxCheckbox = class _FxCheckbox extends FxElement {
4
+ const _FxCheckbox = class _FxCheckbox extends FxFormElement {
5
+ constructor() {
6
+ super(...arguments);
7
+ this._initialChecked = false;
8
+ this._initialCheckedCaptured = false;
9
+ }
5
10
  static get observedAttributes() {
6
11
  return ["checked", "disabled", "indeterminate"];
7
12
  }
@@ -23,6 +28,17 @@ const _FxCheckbox = class _FxCheckbox extends FxElement {
23
28
  set disabled(value) {
24
29
  this.toggleAttr("disabled", Boolean(value));
25
30
  }
31
+ /** No FormData: `value` (ou 'on') quando marcado; null (não envia) quando não. */
32
+ getFormValue() {
33
+ return this.checked ? this.getAttr("value") || "on" : null;
34
+ }
35
+ /** getter/setter `value` → espelho do atributo `value` (compat com v-model / [(value)]). */
36
+ get value() {
37
+ return this.getAttr("value");
38
+ }
39
+ set value(v) {
40
+ this.setAttribute("value", v);
41
+ }
26
42
  /** Tamanho do controle. Padrão: `'md'`. */
27
43
  get size() {
28
44
  const s = this.getAttr("size", "md");
@@ -55,6 +71,20 @@ const _FxCheckbox = class _FxCheckbox extends FxElement {
55
71
  this.toggle();
56
72
  }
57
73
  });
74
+ this.updateFormValue();
75
+ }
76
+ connectedCallback() {
77
+ if (!this._initialCheckedCaptured) {
78
+ this._initialCheckedCaptured = true;
79
+ this._initialChecked = this.checked;
80
+ }
81
+ super.connectedCallback();
82
+ }
83
+ /** form.reset(): restaura o estado marcado inicial (checked/indeterminate não enviam nada). */
84
+ formResetCallback() {
85
+ this.checked = this._initialChecked;
86
+ this.indeterminate = false;
87
+ this.updateFormValue();
58
88
  }
59
89
  toggle() {
60
90
  if (this.disabled) return;
@@ -67,6 +97,13 @@ const _FxCheckbox = class _FxCheckbox extends FxElement {
67
97
  detail: { checked: this.checked, value: this.getAttr("value") }
68
98
  })
69
99
  );
100
+ this.dispatchEvent(
101
+ new CustomEvent("checkedChange", {
102
+ bubbles: true,
103
+ composed: true,
104
+ detail: { checked: this.checked, value: this.getAttr("value") }
105
+ })
106
+ );
70
107
  }
71
108
  };
72
109
  _FxCheckbox.styles = css`
@@ -1 +1 @@
1
- {"version":3,"file":"checkbox.js","sources":["../../../src/components/checkbox/checkbox.ts"],"sourcesContent":["import { FxElement } from '../../core/base';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\n\n/**\n * <fx-checkbox> — Caixa de seleção com suporte a estado indeterminado.\n *\n * Atributos: checked, indeterminate, disabled, value, size (sm|md|lg).\n * Evento: `change` (composed, detail: { checked, value }).\n */\nexport class FxCheckbox extends FxElement {\n static override styles = css`\n :host {\n display: inline-flex;\n vertical-align: middle;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n }\n .box {\n display: inline-flex;\n align-items: center;\n gap: var(--fx-space-sm);\n cursor: pointer;\n user-select: none;\n color: var(--fx-text-default);\n -webkit-tap-highlight-color: transparent;\n }\n .control {\n width: var(--fx-size-checkbox, 18px);\n height: var(--fx-size-checkbox, 18px);\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: 2px solid var(--fx-border-hover);\n border-radius: calc(var(--fx-radius-sm) + 2px);\n background: var(--fx-surface-background);\n color: #fff;\n font-size: calc(var(--fx-size-checkbox, 18px) - 6px);\n line-height: 1;\n transition:\n background-color var(--fx-motion-duration-fast) var(--fx-motion-easing),\n border-color var(--fx-motion-duration-fast) var(--fx-motion-easing),\n box-shadow var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n .box:hover .control { border-color: var(--fx-color-primary); }\n .box:focus-visible .control {\n outline: none;\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n :host([checked]) .control {\n background: var(--fx-color-primary);\n border-color: var(--fx-color-primary);\n }\n :host([indeterminate]) .control {\n background: var(--fx-color-primary);\n border-color: var(--fx-color-primary);\n }\n /* Validação */\n :host([error]) .control,\n :host([invalid]) .control { border-color: var(--fx-color-danger, #dc2626); }\n :host([success]) .control,\n :host([valid]) .control { border-color: var(--fx-color-success, #16a34a); }\n .mark { visibility: hidden; }\n :host([checked]) .mark,\n :host([indeterminate]) .mark { visibility: visible; }\n .label { display: inline-flex; align-items: center; }\n :host([disabled]) .box {\n opacity: 0.55;\n cursor: not-allowed;\n pointer-events: none;\n }\n `;\n\n static override get observedAttributes(): string[] {\n return ['checked', 'disabled', 'indeterminate'];\n }\n\n get checked(): boolean {\n return this.hasAttr('checked');\n }\n set checked(value: boolean) {\n this.toggleAttr('checked', Boolean(value));\n }\n\n get indeterminate(): boolean {\n return this.hasAttr('indeterminate');\n }\n set indeterminate(value: boolean) {\n this.toggleAttr('indeterminate', Boolean(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 /** Tamanho do controle. 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 protected override render(): void {\n // Escala por tamanho via token local.\n const px = this.size === 'sm' ? '14px' : this.size === 'lg' ? '22px' : '18px';\n if (!this.style.getPropertyValue('--fx-size-checkbox')) {\n this.style.setProperty('--fx-size-checkbox', px);\n }\n\n this.setTemplate(`\n <span class=\"box\" part=\"box\" role=\"checkbox\" tabindex=\"0\"\n aria-checked=\"${this.indeterminate ? 'mixed' : this.checked}\"\n aria-disabled=\"${this.disabled}\">\n <span class=\"control\" part=\"control\">\n <span class=\"mark\">${this.indeterminate ? '–' : '✓'}</span>\n </span>\n <span class=\"label\" part=\"label\"><slot></slot></span>\n </span>\n `);\n\n const box = this.root.querySelector<HTMLElement>('.box');\n if (!box) return;\n box.addEventListener('click', () => this.toggle());\n box.addEventListener('keydown', (e) => {\n if ((e as KeyboardEvent).key === ' ' || (e as KeyboardEvent).key === 'Enter') {\n e.preventDefault();\n this.toggle();\n }\n });\n }\n\n private toggle(): void {\n if (this.disabled) return;\n this.indeterminate = false; // interagir resolve o estado misto\n this.checked = !this.checked;\n this.dispatchEvent(\n new CustomEvent('change', {\n bubbles: true,\n composed: true,\n detail: { checked: this.checked, value: this.getAttr('value') },\n }),\n );\n }\n}\n\nexport function defineFxCheckbox(): typeof FxCheckbox {\n return defineElement('fx-checkbox', FxCheckbox);\n}\n"],"names":[],"mappings":";;;AAUO,MAAM,cAAN,MAAM,oBAAmB,UAAU;AAAA,EAgExC,WAAoB,qBAA+B;AACjD,WAAO,CAAC,WAAW,YAAY,eAAe;AAAA,EAChD;AAAA,EAEA,IAAI,UAAmB;AACrB,WAAO,KAAK,QAAQ,SAAS;AAAA,EAC/B;AAAA,EACA,IAAI,QAAQ,OAAgB;AAC1B,SAAK,WAAW,WAAW,QAAQ,KAAK,CAAC;AAAA,EAC3C;AAAA,EAEA,IAAI,gBAAyB;AAC3B,WAAO,KAAK,QAAQ,eAAe;AAAA,EACrC;AAAA,EACA,IAAI,cAAc,OAAgB;AAChC,SAAK,WAAW,iBAAiB,QAAQ,KAAK,CAAC;AAAA,EACjD;AAAA,EAEA,IAAI,WAAoB;AACtB,WAAO,KAAK,QAAQ,UAAU;AAAA,EAChC;AAAA,EACA,IAAI,SAAS,OAAgB;AAC3B,SAAK,WAAW,YAAY,QAAQ,KAAK,CAAC;AAAA,EAC5C;AAAA;AAAA,EAGA,IAAI,OAAe;AACjB,UAAM,IAAI,KAAK,QAAQ,QAAQ,IAAI;AACnC,WAAO,MAAM,QAAQ,MAAM,OAAO,IAAI;AAAA,EACxC;AAAA,EACA,IAAI,KAAK,OAAe;AACtB,SAAK,aAAa,QAAQ,KAAK;AAAA,EACjC;AAAA,EAEmB,SAAe;AAEhC,UAAM,KAAK,KAAK,SAAS,OAAO,SAAS,KAAK,SAAS,OAAO,SAAS;AACvE,QAAI,CAAC,KAAK,MAAM,iBAAiB,oBAAoB,GAAG;AACtD,WAAK,MAAM,YAAY,sBAAsB,EAAE;AAAA,IACjD;AAEA,SAAK,YAAY;AAAA;AAAA,wBAEG,KAAK,gBAAgB,UAAU,KAAK,OAAO;AAAA,yBAC1C,KAAK,QAAQ;AAAA;AAAA,+BAEP,KAAK,gBAAgB,MAAM,GAAG;AAAA;AAAA;AAAA;AAAA,KAIxD;AAED,UAAM,MAAM,KAAK,KAAK,cAA2B,MAAM;AACvD,QAAI,CAAC,IAAK;AACV,QAAI,iBAAiB,SAAS,MAAM,KAAK,QAAQ;AACjD,QAAI,iBAAiB,WAAW,CAAC,MAAM;AACrC,UAAK,EAAoB,QAAQ,OAAQ,EAAoB,QAAQ,SAAS;AAC5E,UAAE,eAAA;AACF,aAAK,OAAA;AAAA,MACP;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,SAAe;AACrB,QAAI,KAAK,SAAU;AACnB,SAAK,gBAAgB;AACrB,SAAK,UAAU,CAAC,KAAK;AACrB,SAAK;AAAA,MACH,IAAI,YAAY,UAAU;AAAA,QACxB,SAAS;AAAA,QACT,UAAU;AAAA,QACV,QAAQ,EAAE,SAAS,KAAK,SAAS,OAAO,KAAK,QAAQ,OAAO,EAAA;AAAA,MAAE,CAC/D;AAAA,IAAA;AAAA,EAEL;AACF;AA1IE,YAAgB,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;AADpB,IAAM,aAAN;AA6IA,SAAS,mBAAsC;AACpD,SAAO,cAAc,eAAe,UAAU;AAChD;"}
1
+ {"version":3,"file":"checkbox.js","sources":["../../../src/components/checkbox/checkbox.ts"],"sourcesContent":["import { FxFormElement } from '../../core/form-element';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\n\n/**\n * <fx-checkbox> — Caixa de seleção com suporte a estado indeterminado.\n *\n * Form-Associated Custom Element: dentro de um <form> entra no FormData\n * (via atributo `name`) com `value` (ou 'on') quando marcado — desmarcado\n * não envia nada, igual ao checkbox nativo. Recebe reset/disable do form.\n *\n * Atributos: checked, indeterminate, disabled, value, name, size (sm|md|lg).\n * Eventos: `change` e `checkedChange` (composed, detail: { checked, value }) —\n * `checkedChange` é o two-way binding do Angular [(checked)].\n */\nexport class FxCheckbox extends FxFormElement {\n static override styles = css`\n :host {\n display: inline-flex;\n vertical-align: middle;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n }\n .box {\n display: inline-flex;\n align-items: center;\n gap: var(--fx-space-sm);\n cursor: pointer;\n user-select: none;\n color: var(--fx-text-default);\n -webkit-tap-highlight-color: transparent;\n }\n .control {\n width: var(--fx-size-checkbox, 18px);\n height: var(--fx-size-checkbox, 18px);\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: 2px solid var(--fx-border-hover);\n border-radius: calc(var(--fx-radius-sm) + 2px);\n background: var(--fx-surface-background);\n color: #fff;\n font-size: calc(var(--fx-size-checkbox, 18px) - 6px);\n line-height: 1;\n transition:\n background-color var(--fx-motion-duration-fast) var(--fx-motion-easing),\n border-color var(--fx-motion-duration-fast) var(--fx-motion-easing),\n box-shadow var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n .box:hover .control { border-color: var(--fx-color-primary); }\n .box:focus-visible .control {\n outline: none;\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n :host([checked]) .control {\n background: var(--fx-color-primary);\n border-color: var(--fx-color-primary);\n }\n :host([indeterminate]) .control {\n background: var(--fx-color-primary);\n border-color: var(--fx-color-primary);\n }\n /* Validação */\n :host([error]) .control,\n :host([invalid]) .control { border-color: var(--fx-color-danger, #dc2626); }\n :host([success]) .control,\n :host([valid]) .control { border-color: var(--fx-color-success, #16a34a); }\n .mark { visibility: hidden; }\n :host([checked]) .mark,\n :host([indeterminate]) .mark { visibility: visible; }\n .label { display: inline-flex; align-items: center; }\n :host([disabled]) .box {\n opacity: 0.55;\n cursor: not-allowed;\n pointer-events: none;\n }\n `;\n\n static override get observedAttributes(): string[] {\n return ['checked', 'disabled', 'indeterminate'];\n }\n\n get checked(): boolean {\n return this.hasAttr('checked');\n }\n set checked(value: boolean) {\n this.toggleAttr('checked', Boolean(value));\n }\n\n get indeterminate(): boolean {\n return this.hasAttr('indeterminate');\n }\n set indeterminate(value: boolean) {\n this.toggleAttr('indeterminate', Boolean(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 /** No FormData: `value` (ou 'on') quando marcado; null (não envia) quando não. */\n protected override getFormValue(): string | FormData | null {\n return this.checked ? (this.getAttr('value') || 'on') : null;\n }\n\n /** getter/setter `value` → espelho do atributo `value` (compat com v-model / [(value)]). */\n get value(): string { return this.getAttr('value'); }\n set value(v: string) { this.setAttribute('value', v); }\n\n /** Tamanho do controle. 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 protected override render(): void {\n // Escala por tamanho via token local.\n const px = this.size === 'sm' ? '14px' : this.size === 'lg' ? '22px' : '18px';\n if (!this.style.getPropertyValue('--fx-size-checkbox')) {\n this.style.setProperty('--fx-size-checkbox', px);\n }\n\n this.setTemplate(`\n <span class=\"box\" part=\"box\" role=\"checkbox\" tabindex=\"0\"\n aria-checked=\"${this.indeterminate ? 'mixed' : this.checked}\"\n aria-disabled=\"${this.disabled}\">\n <span class=\"control\" part=\"control\">\n <span class=\"mark\">${this.indeterminate ? '–' : '✓'}</span>\n </span>\n <span class=\"label\" part=\"label\"><slot></slot></span>\n </span>\n `);\n\n const box = this.root.querySelector<HTMLElement>('.box');\n if (!box) return;\n box.addEventListener('click', () => this.toggle());\n box.addEventListener('keydown', (e) => {\n if ((e as KeyboardEvent).key === ' ' || (e as KeyboardEvent).key === 'Enter') {\n e.preventDefault();\n this.toggle();\n }\n });\n\n // Publica name/value no form (ElementInternals) a cada render.\n this.updateFormValue();\n }\n\n /** Estado marcado inicial (para form.reset() restaurar). */\n private _initialChecked = false;\n private _initialCheckedCaptured = false;\n\n protected override connectedCallback(): void {\n if (!this._initialCheckedCaptured) {\n this._initialCheckedCaptured = true;\n this._initialChecked = this.checked;\n }\n super.connectedCallback();\n }\n\n /** form.reset(): restaura o estado marcado inicial (checked/indeterminate não enviam nada). */\n override formResetCallback(): void {\n this.checked = this._initialChecked;\n this.indeterminate = false;\n this.updateFormValue();\n }\n\n private toggle(): void {\n if (this.disabled) return;\n this.indeterminate = false; // interagir resolve o estado misto\n this.checked = !this.checked;\n this.dispatchEvent(\n new CustomEvent('change', {\n bubbles: true,\n composed: true,\n detail: { checked: this.checked, value: this.getAttr('value') },\n }),\n );\n // Two-way binding do Angular: [(checked)] = [checked] + (checkedChange).\n this.dispatchEvent(\n new CustomEvent('checkedChange', {\n bubbles: true,\n composed: true,\n detail: { checked: this.checked, value: this.getAttr('value') },\n }),\n );\n }\n}\n\nexport function defineFxCheckbox(): typeof FxCheckbox {\n return defineElement('fx-checkbox', FxCheckbox);\n}\n"],"names":[],"mappings":"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"}
@@ -1,7 +1,11 @@
1
- import { FxElement } from '../../core/base';
1
+ import { FxFormElement } from '../../core/form-element';
2
2
  /**
3
3
  * <fx-datepicker> — Campo de data com calendário suspenso.
4
4
  *
5
+ * Form-Associated Custom Element: dentro de um <form> entra no FormData
6
+ * (via atributo `name`): single → `name`; range → `name-start`/`name-end`;
7
+ * multiple → uma entrada `name` por data.
8
+ *
5
9
  * Um input com ícone de calendário no final; ao clicar, abre o
6
10
  * <fx-calendar> em um popover. As datas selecionadas aparecem no input.
7
11
  *
@@ -19,13 +23,21 @@ import { FxElement } from '../../core/base';
19
23
  * - single: { value }
20
24
  * - range: { value, start, end }
21
25
  * - multiple: { values: string[] }
26
+ *
27
+ * Além do `change`, emite `input`/`valueChange` com o MESMO payload
28
+ * (v-model do Vue e [(value)] do Angular).
22
29
  */
23
- export declare class FxDatepicker extends FxElement {
30
+ export declare class FxDatepicker extends FxFormElement {
24
31
  static styles: string;
25
32
  static get observedAttributes(): string[];
33
+ protected attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
34
+ /** No FormData do form, conforme o modo (single/range/multiple). */
35
+ protected getFormValue(): string | FormData | null;
26
36
  private docListener?;
27
37
  /** Hora/minuto/segundo do modo show-time. */
28
38
  private time;
39
+ /** Estado inicial (value/start/end/values) para form.reset(). */
40
+ private _initialState?;
29
41
  get mode(): string;
30
42
  set mode(value: string);
31
43
  get value(): string;
@@ -37,6 +49,8 @@ export declare class FxDatepicker extends FxElement {
37
49
  set disabled(value: boolean);
38
50
  protected connectedCallback(): void;
39
51
  protected disconnectedCallback(): void;
52
+ /** form.reset(): restaura value/start/end/values iniciais (re-render via atributos observados). */
53
+ formResetCallback(): void;
40
54
  /** Formata uma data ISO ("YYYY-MM-DD" ou com "THH:mm:ss") segundo o atributo format. */
41
55
  private formatDate;
42
56
  /** Texto exibido no input conforme o modo. */
@@ -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,SAwJpB;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,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAKxD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,YAAa,SAAQ,aAAa;IAC7C,OAAgB,MAAM,SAwJpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;cAEkB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAQjH,oEAAoE;cACjD,YAAY,IAAI,MAAM,GAAG,QAAQ,GAAG,IAAI;IAoB3D,OAAO,CAAC,WAAW,CAAC,CAAqB;IACzC,6CAA6C;IAC7C,OAAO,CAAC,IAAI,CAAwB;IACpC,iEAAiE;IACjE,OAAO,CAAC,aAAa,CAAC,CAAgE;IAEtF,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;cAmBzB,oBAAoB,IAAI,IAAI;IAK/C,mGAAmG;IAC1F,iBAAiB,IAAI,IAAI;IASlC,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;IASlB;;;;OAIG;IACH,OAAO,CAAC,UAAU;IAgClB,8DAA8D;IAC9D,OAAO,CAAC,WAAW;CAkCpB;AAED,wBAAgB,kBAAkB,IAAI,OAAO,YAAY,CAExD"}