@wrrdev/fenix-ui 1.1.16 → 1.2.0

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 (111) hide show
  1. package/README.md +12 -3
  2. package/dist/components/autocomplete/autocomplete.d.ts +3 -1
  3. package/dist/components/autocomplete/autocomplete.d.ts.map +1 -1
  4. package/dist/components/autocomplete/autocomplete.js +7 -1
  5. package/dist/components/autocomplete/autocomplete.js.map +1 -1
  6. package/dist/components/avatar/avatar.d.ts +1 -0
  7. package/dist/components/avatar/avatar.d.ts.map +1 -1
  8. package/dist/components/avatar/avatar.js +3 -0
  9. package/dist/components/avatar/avatar.js.map +1 -1
  10. package/dist/components/badge/badge.d.ts +4 -0
  11. package/dist/components/badge/badge.d.ts.map +1 -1
  12. package/dist/components/badge/badge.js +12 -0
  13. package/dist/components/badge/badge.js.map +1 -1
  14. package/dist/components/button/button.d.ts +2 -0
  15. package/dist/components/button/button.d.ts.map +1 -1
  16. package/dist/components/button/button.js +6 -0
  17. package/dist/components/button/button.js.map +1 -1
  18. package/dist/components/card/card.d.ts +2 -0
  19. package/dist/components/card/card.d.ts.map +1 -1
  20. package/dist/components/card/card.js +6 -0
  21. package/dist/components/card/card.js.map +1 -1
  22. package/dist/components/checkbox/checkbox.d.ts +5 -1
  23. package/dist/components/checkbox/checkbox.d.ts.map +1 -1
  24. package/dist/components/checkbox/checkbox.js +7 -8
  25. package/dist/components/checkbox/checkbox.js.map +1 -1
  26. package/dist/components/datepicker/datepicker.d.ts +17 -2
  27. package/dist/components/datepicker/datepicker.d.ts.map +1 -1
  28. package/dist/components/datepicker/datepicker.js +113 -42
  29. package/dist/components/datepicker/datepicker.js.map +1 -1
  30. package/dist/components/dialog/dialog.d.ts +4 -1
  31. package/dist/components/dialog/dialog.d.ts.map +1 -1
  32. package/dist/components/dialog/dialog.js +41 -18
  33. package/dist/components/dialog/dialog.js.map +1 -1
  34. package/dist/components/drawer/drawer.d.ts +5 -1
  35. package/dist/components/drawer/drawer.d.ts.map +1 -1
  36. package/dist/components/drawer/drawer.js +39 -17
  37. package/dist/components/drawer/drawer.js.map +1 -1
  38. package/dist/components/fileupload/fileupload.d.ts +13 -1
  39. package/dist/components/fileupload/fileupload.d.ts.map +1 -1
  40. package/dist/components/fileupload/fileupload.js +29 -1
  41. package/dist/components/fileupload/fileupload.js.map +1 -1
  42. package/dist/components/floatlabel/floatlabel.d.ts +20 -0
  43. package/dist/components/floatlabel/floatlabel.d.ts.map +1 -1
  44. package/dist/components/floatlabel/floatlabel.js +99 -26
  45. package/dist/components/floatlabel/floatlabel.js.map +1 -1
  46. package/dist/components/icon/icon.d.ts +4 -0
  47. package/dist/components/icon/icon.d.ts.map +1 -1
  48. package/dist/components/icon/icon.js +12 -0
  49. package/dist/components/icon/icon.js.map +1 -1
  50. package/dist/components/input/input.d.ts +9 -2
  51. package/dist/components/input/input.d.ts.map +1 -1
  52. package/dist/components/input/input.js +56 -8
  53. package/dist/components/input/input.js.map +1 -1
  54. package/dist/components/multiselect/multiselect.d.ts +8 -1
  55. package/dist/components/multiselect/multiselect.d.ts.map +1 -1
  56. package/dist/components/multiselect/multiselect.js +61 -9
  57. package/dist/components/multiselect/multiselect.js.map +1 -1
  58. package/dist/components/orderlist/orderlist.d.ts +7 -0
  59. package/dist/components/orderlist/orderlist.d.ts.map +1 -1
  60. package/dist/components/orderlist/orderlist.js +34 -2
  61. package/dist/components/orderlist/orderlist.js.map +1 -1
  62. package/dist/components/radio/radio.d.ts +6 -0
  63. package/dist/components/radio/radio.d.ts.map +1 -1
  64. package/dist/components/radio/radio.js +12 -6
  65. package/dist/components/radio/radio.js.map +1 -1
  66. package/dist/components/select/select.d.ts +19 -1
  67. package/dist/components/select/select.d.ts.map +1 -1
  68. package/dist/components/select/select.js +116 -10
  69. package/dist/components/select/select.js.map +1 -1
  70. package/dist/components/switch/switch.d.ts +4 -1
  71. package/dist/components/switch/switch.d.ts.map +1 -1
  72. package/dist/components/switch/switch.js +2 -1
  73. package/dist/components/switch/switch.js.map +1 -1
  74. package/dist/components/table/table.d.ts +27 -0
  75. package/dist/components/table/table.d.ts.map +1 -1
  76. package/dist/components/table/table.js +119 -21
  77. package/dist/components/table/table.js.map +1 -1
  78. package/dist/components/tabs/tabs.d.ts +6 -0
  79. package/dist/components/tabs/tabs.d.ts.map +1 -1
  80. package/dist/components/tabs/tabs.js +34 -0
  81. package/dist/components/tabs/tabs.js.map +1 -1
  82. package/dist/components/textarea/textarea.d.ts +5 -1
  83. package/dist/components/textarea/textarea.d.ts.map +1 -1
  84. package/dist/components/textarea/textarea.js +25 -6
  85. package/dist/components/textarea/textarea.js.map +1 -1
  86. package/dist/core/jsx.d.ts +11 -5
  87. package/dist/core/jsx.d.ts.map +1 -1
  88. package/dist/core/jsx.js.map +1 -1
  89. package/dist/core/modal.d.ts +31 -0
  90. package/dist/core/modal.d.ts.map +1 -0
  91. package/dist/core/modal.js +61 -0
  92. package/dist/core/modal.js.map +1 -0
  93. package/dist/core/tokens.d.ts +2 -0
  94. package/dist/core/tokens.d.ts.map +1 -1
  95. package/dist/core/tokens.js +9 -0
  96. package/dist/core/tokens.js.map +1 -1
  97. package/dist/docs/app.d.ts.map +1 -1
  98. package/dist/docs/componentes/floatlabel.doc.d.ts.map +1 -1
  99. package/dist/docs/componentes/input.doc.d.ts.map +1 -1
  100. package/dist/docs/componentes/orderlist.doc.d.ts.map +1 -1
  101. package/dist/docs/componentes/table.doc.d.ts.map +1 -1
  102. package/dist/docs/shell.d.ts +10 -1
  103. package/dist/docs/shell.d.ts.map +1 -1
  104. package/dist/docs/theme-reference.d.ts +53 -0
  105. package/dist/docs/theme-reference.d.ts.map +1 -0
  106. package/dist/docs/types.d.ts +2 -0
  107. package/dist/docs/types.d.ts.map +1 -1
  108. package/dist/fenix-ui.umd.min.js +270 -233
  109. package/dist/fenix-ui4.esm.js +890 -176
  110. package/dist/fenix-ui4.esm.js.map +1 -1
  111. package/package.json +1 -1
@@ -11,6 +11,9 @@ import { FxFormElement } from '../../core/form-element';
11
11
  * Atributos: checked, disabled, value, name, size (sm|md|lg).
12
12
  * Eventos: `change` e `checkedChange` (composed, detail: { checked, value }) —
13
13
  * `checkedChange` é o two-way binding do Angular [(checked)].
14
+ *
15
+ * Vue: use `:checked="estado"` + `@change` (o `v-model` padrão de elemento
16
+ * customizado usa `value` + `input`, não `checked`).
14
17
  */
15
18
  export declare class FxRadio extends FxFormElement {
16
19
  static styles: string;
@@ -28,6 +31,9 @@ export declare class FxRadio extends FxFormElement {
28
31
  private _group;
29
32
  /** No FormData: só o radio MARCADO envia `value` (ou 'on'). */
30
33
  protected getFormValue(): string | FormData | null;
34
+ /** getter/setter `value` — sem o setter o Vue não consegue escrever a propriedade. */
35
+ get value(): string;
36
+ set value(v: string | null | undefined);
31
37
  /** Tamanho do controle. Padrão: `'md'`. */
32
38
  get size(): string;
33
39
  set size(value: string);
@@ -1 +1 @@
1
- {"version":3,"file":"radio.d.ts","sourceRoot":"","sources":["../../../src/components/radio/radio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAIxD;;;;;;;;;;;;GAYG;AACH,qBAAa,OAAQ,SAAQ,aAAa;IACxC,OAAgB,MAAM,SAwDpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAEzB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAE1B;IAED;;;;;OAKG;IACH,OAAO,CAAC,MAAM;IAYd,+DAA+D;cAC5C,YAAY,IAAI,MAAM,GAAG,QAAQ,GAAG,IAAI;IAI3D,2CAA2C;IAC3C,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAErB;cAEkB,MAAM,IAAI,IAAI;IAoCjC,wEAAwE;IACxE,OAAO,CAAC,UAAU;IAalB,OAAO,CAAC,MAAM;IAwBd,oEAAoE;IACpE,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,uBAAuB,CAAS;cAErB,iBAAiB,IAAI,IAAI;IAQ5C,gEAAgE;IACvD,iBAAiB,IAAI,IAAI;CAInC;AAED,wBAAgB,aAAa,IAAI,OAAO,OAAO,CAE9C"}
1
+ {"version":3,"file":"radio.d.ts","sourceRoot":"","sources":["../../../src/components/radio/radio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAIxD;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,OAAQ,SAAQ,aAAa;IACxC,OAAgB,MAAM,SAwDpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAEzB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAE1B;IAED;;;;;OAKG;IACH,OAAO,CAAC,MAAM;IAYd,+DAA+D;cAC5C,YAAY,IAAI,MAAM,GAAG,QAAQ,GAAG,IAAI;IAI3D,sFAAsF;IACtF,IAAI,KAAK,IAAI,MAAM,CAAkC;IACrD,IAAI,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAGrC;IAED,2CAA2C;IAC3C,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAErB;cAEkB,MAAM,IAAI,IAAI;IAmCjC,wEAAwE;IACxE,OAAO,CAAC,UAAU;IAalB,OAAO,CAAC,MAAM;IAwBd,oEAAoE;IACpE,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,uBAAuB,CAAS;cAErB,iBAAiB,IAAI,IAAI;IAQ5C,gEAAgE;IACvD,iBAAiB,IAAI,IAAI;CAInC;AAED,wBAAgB,aAAa,IAAI,OAAO,OAAO,CAE9C"}
@@ -8,7 +8,7 @@ const _FxRadio = class _FxRadio extends FxFormElement {
8
8
  this._initialCheckedCaptured = false;
9
9
  }
10
10
  static get observedAttributes() {
11
- return ["checked", "disabled"];
11
+ return ["checked", "disabled", "size"];
12
12
  }
13
13
  get checked() {
14
14
  return this.hasAttr("checked");
@@ -41,6 +41,14 @@ const _FxRadio = class _FxRadio extends FxFormElement {
41
41
  getFormValue() {
42
42
  return this.checked ? this.getAttr("value") || "on" : null;
43
43
  }
44
+ /** getter/setter `value` — sem o setter o Vue não consegue escrever a propriedade. */
45
+ get value() {
46
+ return this.getAttr("value");
47
+ }
48
+ set value(v) {
49
+ this.setAttribute("value", v == null ? "" : String(v));
50
+ this.updateFormValue();
51
+ }
44
52
  /** Tamanho do controle. Padrão: `'md'`. */
45
53
  get size() {
46
54
  const s = this.getAttr("size", "md");
@@ -51,9 +59,7 @@ const _FxRadio = class _FxRadio extends FxFormElement {
51
59
  }
52
60
  render() {
53
61
  const px = this.size === "sm" ? "14px" : this.size === "lg" ? "22px" : "18px";
54
- if (!this.style.getPropertyValue("--fx-size-radio")) {
55
- this.style.setProperty("--fx-size-radio", px);
56
- }
62
+ this.style.setProperty("--_fx-size-radio", px);
57
63
  this.setTemplate(`
58
64
  <span class="box" part="box" role="radio" tabindex="0"
59
65
  aria-checked="${this.checked}" aria-disabled="${this.disabled}">
@@ -141,8 +147,8 @@ _FxRadio.styles = css`
141
147
  -webkit-tap-highlight-color: transparent;
142
148
  }
143
149
  .control {
144
- width: var(--fx-size-radio, 18px);
145
- height: var(--fx-size-radio, 18px);
150
+ width: var(--fx-size-radio, var(--_fx-size-radio, 18px));
151
+ height: var(--fx-size-radio, var(--_fx-size-radio, 18px));
146
152
  flex-shrink: 0;
147
153
  display: inline-flex;
148
154
  align-items: center;
@@ -1 +1 @@
1
- {"version":3,"file":"radio.js","sources":["../../../src/components/radio/radio.ts"],"sourcesContent":["import { FxFormElement } from '../../core/form-element';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\n\n/**\n * <fx-radio> — Botão de opção. Radios com o mesmo `name` formam um grupo\n * (seleção exclusiva gerenciada pelo próprio componente, escopado à árvore\n * em que o radio vive — document OU o shadow DOM do app consumidor).\n *\n * Form-Associated Custom Element: dentro de um <form> só o radio MARCADO\n * entra no FormData (via atributo `name`, com `value` ou 'on'), igual ao\n * radio nativo. Recebe reset/disable do form.\n *\n * Atributos: checked, 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 FxRadio 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-radio, 18px);\n height: var(--fx-size-radio, 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: var(--fx-radius-full);\n background: var(--fx-surface-background);\n transition:\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 .dot {\n width: calc(100% - 8px);\n height: calc(100% - 8px);\n border-radius: var(--fx-radius-full);\n background: var(--fx-color-primary);\n transform: scale(0);\n transition: transform var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n :host([checked]) .control { border-color: var(--fx-color-primary); }\n :host([checked]) .dot { transform: scale(1); }\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 .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'];\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 disabled(): boolean {\n return this.hasAttr('disabled');\n }\n set disabled(value: boolean) {\n this.toggleAttr('disabled', Boolean(value));\n }\n\n /**\n * Grupo = radios com o mesmo `name` NA MESMA árvore. `getRootNode()` devolve\n * o document OU o ShadowRoot do app consumidor — grupos com o mesmo name\n * em shadow DOMs diferentes ficam isolados (o `document` global cruzaria\n * fronteiras indevidamente).\n */\n private _group(): FxRadio[] {\n const name = this.getAttr('name');\n if (!name) return [this];\n const root = this.getRootNode() as ParentNode;\n // Escape manual para seletor de atributo com aspas duplas (o global `CSS`\n // não existe em todos os ambientes — ex.: jsdom).\n const safeName = name.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n return Array.from(\n root.querySelectorAll(`fx-radio[name=\"${safeName}\"]`),\n ) as FxRadio[];\n }\n\n /** No FormData: só o radio MARCADO envia `value` (ou 'on'). */\n protected override getFormValue(): string | FormData | null {\n return this.checked ? (this.getAttr('value') || 'on') : null;\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 const px = this.size === 'sm' ? '14px' : this.size === 'lg' ? '22px' : '18px';\n if (!this.style.getPropertyValue('--fx-size-radio')) {\n this.style.setProperty('--fx-size-radio', px);\n }\n\n this.setTemplate(`\n <span class=\"box\" part=\"box\" role=\"radio\" tabindex=\"0\"\n aria-checked=\"${this.checked}\" aria-disabled=\"${this.disabled}\">\n <span class=\"control\" part=\"control\"><span class=\"dot\" part=\"dot\"></span></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.select());\n box.addEventListener('keydown', (e) => {\n const key = (e as KeyboardEvent).key;\n if (key === ' ' || key === 'Enter') {\n e.preventDefault();\n this.select();\n return;\n }\n // Setas: movem o foco (e marcam) entre os radios do grupo — igual ao\n // <input type=\"radio\"> nativo (roam pelo grupo, sem sair dele).\n if (key === 'ArrowDown' || key === 'ArrowRight' || key === 'ArrowUp' || key === 'ArrowLeft') {\n e.preventDefault();\n this._moveFocus(key === 'ArrowDown' || key === 'ArrowRight' ? 1 : -1);\n }\n });\n\n // Publica name/value no form (ElementInternals) a cada render.\n this.updateFormValue();\n }\n\n /** Move o foco/marcação para o próximo (ou anterior) radio do grupo. */\n private _moveFocus(dir: 1 | -1): void {\n const group = this._group().filter((r) => !r.disabled);\n if (group.length < 2) return;\n const i = group.indexOf(this);\n const next = group[(i + dir + group.length) % group.length]!;\n next.focus(); // delegatesFocus leva o foco ao .box interno\n // Fallback (ex.: jsdom sem delegatesFocus): foca o .box interno direto.\n if (!next.root.activeElement) {\n next.root.querySelector<HTMLElement>('.box')?.focus();\n }\n if (!next.checked) next.select();\n }\n\n private select(): void {\n if (this.disabled || this.checked) return;\n // Desmarca os outros radios do mesmo grupo (mesmo name, mesma árvore).\n for (const r of this._group()) {\n if (r !== this) r.checked = false;\n }\n this.checked = true;\n this.dispatchEvent(\n new CustomEvent('change', {\n bubbles: true,\n composed: true,\n detail: { checked: true, 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: true, value: this.getAttr('value') },\n }),\n );\n }\n\n /** Estado marcado inicial (para form.reset() restaurar o grupo). */\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 do grupo. */\n override formResetCallback(): void {\n this.checked = this._initialChecked;\n this.updateFormValue();\n }\n}\n\nexport function defineFxRadio(): typeof FxRadio {\n return defineElement('fx-radio', FxRadio);\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"radio.js","sources":["../../../src/components/radio/radio.ts"],"sourcesContent":["import { FxFormElement } from '../../core/form-element';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\n\n/**\n * <fx-radio> — Botão de opção. Radios com o mesmo `name` formam um grupo\n * (seleção exclusiva gerenciada pelo próprio componente, escopado à árvore\n * em que o radio vive — document OU o shadow DOM do app consumidor).\n *\n * Form-Associated Custom Element: dentro de um <form> só o radio MARCADO\n * entra no FormData (via atributo `name`, com `value` ou 'on'), igual ao\n * radio nativo. Recebe reset/disable do form.\n *\n * Atributos: checked, 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 *\n * Vue: use `:checked=\"estado\"` + `@change` (o `v-model` padrão de elemento\n * customizado usa `value` + `input`, não `checked`).\n */\nexport class FxRadio 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-radio, var(--_fx-size-radio, 18px));\n height: var(--fx-size-radio, var(--_fx-size-radio, 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: var(--fx-radius-full);\n background: var(--fx-surface-background);\n transition:\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 .dot {\n width: calc(100% - 8px);\n height: calc(100% - 8px);\n border-radius: var(--fx-radius-full);\n background: var(--fx-color-primary);\n transform: scale(0);\n transition: transform var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n :host([checked]) .control { border-color: var(--fx-color-primary); }\n :host([checked]) .dot { transform: scale(1); }\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 .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', 'size'];\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 disabled(): boolean {\n return this.hasAttr('disabled');\n }\n set disabled(value: boolean) {\n this.toggleAttr('disabled', Boolean(value));\n }\n\n /**\n * Grupo = radios com o mesmo `name` NA MESMA árvore. `getRootNode()` devolve\n * o document OU o ShadowRoot do app consumidor — grupos com o mesmo name\n * em shadow DOMs diferentes ficam isolados (o `document` global cruzaria\n * fronteiras indevidamente).\n */\n private _group(): FxRadio[] {\n const name = this.getAttr('name');\n if (!name) return [this];\n const root = this.getRootNode() as ParentNode;\n // Escape manual para seletor de atributo com aspas duplas (o global `CSS`\n // não existe em todos os ambientes — ex.: jsdom).\n const safeName = name.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n return Array.from(\n root.querySelectorAll(`fx-radio[name=\"${safeName}\"]`),\n ) as FxRadio[];\n }\n\n /** No FormData: só o radio MARCADO envia `value` (ou 'on'). */\n protected override getFormValue(): string | FormData | null {\n return this.checked ? (this.getAttr('value') || 'on') : null;\n }\n\n /** getter/setter `value` — sem o setter o Vue não consegue escrever a propriedade. */\n get value(): string { return this.getAttr('value'); }\n set value(v: string | null | undefined) {\n this.setAttribute('value', v == null ? '' : String(v));\n this.updateFormValue();\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 const px = this.size === 'sm' ? '14px' : this.size === 'lg' ? '22px' : '18px';\n // Token interno: um `--fx-size-radio` do consumidor continua vencendo.\n this.style.setProperty('--_fx-size-radio', px);\n\n this.setTemplate(`\n <span class=\"box\" part=\"box\" role=\"radio\" tabindex=\"0\"\n aria-checked=\"${this.checked}\" aria-disabled=\"${this.disabled}\">\n <span class=\"control\" part=\"control\"><span class=\"dot\" part=\"dot\"></span></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.select());\n box.addEventListener('keydown', (e) => {\n const key = (e as KeyboardEvent).key;\n if (key === ' ' || key === 'Enter') {\n e.preventDefault();\n this.select();\n return;\n }\n // Setas: movem o foco (e marcam) entre os radios do grupo — igual ao\n // <input type=\"radio\"> nativo (roam pelo grupo, sem sair dele).\n if (key === 'ArrowDown' || key === 'ArrowRight' || key === 'ArrowUp' || key === 'ArrowLeft') {\n e.preventDefault();\n this._moveFocus(key === 'ArrowDown' || key === 'ArrowRight' ? 1 : -1);\n }\n });\n\n // Publica name/value no form (ElementInternals) a cada render.\n this.updateFormValue();\n }\n\n /** Move o foco/marcação para o próximo (ou anterior) radio do grupo. */\n private _moveFocus(dir: 1 | -1): void {\n const group = this._group().filter((r) => !r.disabled);\n if (group.length < 2) return;\n const i = group.indexOf(this);\n const next = group[(i + dir + group.length) % group.length]!;\n next.focus(); // delegatesFocus leva o foco ao .box interno\n // Fallback (ex.: jsdom sem delegatesFocus): foca o .box interno direto.\n if (!next.root.activeElement) {\n next.root.querySelector<HTMLElement>('.box')?.focus();\n }\n if (!next.checked) next.select();\n }\n\n private select(): void {\n if (this.disabled || this.checked) return;\n // Desmarca os outros radios do mesmo grupo (mesmo name, mesma árvore).\n for (const r of this._group()) {\n if (r !== this) r.checked = false;\n }\n this.checked = true;\n this.dispatchEvent(\n new CustomEvent('change', {\n bubbles: true,\n composed: true,\n detail: { checked: true, 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: true, value: this.getAttr('value') },\n }),\n );\n }\n\n /** Estado marcado inicial (para form.reset() restaurar o grupo). */\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 do grupo. */\n override formResetCallback(): void {\n this.checked = this._initialChecked;\n this.updateFormValue();\n }\n}\n\nexport function defineFxRadio(): typeof FxRadio {\n return defineElement('fx-radio', FxRadio);\n}\n"],"names":[],"mappings":"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"}
@@ -23,6 +23,12 @@ import { FxFormElement } from '../../core/form-element';
23
23
  export declare class FxSelect extends FxFormElement {
24
24
  static styles: string;
25
25
  static get observedAttributes(): string[];
26
+ /** Evita renders em cascata durante mudanças internas agrupadas. */
27
+ private _suppressRender;
28
+ /** Resultado de `_precisaFixed` cacheado por abertura (evita getComputedStyle no scroll). */
29
+ private _fixedMode;
30
+ private _fixedResolved;
31
+ protected attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
26
32
  /** Espaço (px) entre o campo e o painel — espelha `--fx-select-panel-offset`. */
27
33
  private static readonly GAP;
28
34
  /** Margem (px) mantida entre o painel e as bordas da viewport. */
@@ -37,9 +43,15 @@ export declare class FxSelect extends FxFormElement {
37
43
  get size(): string;
38
44
  set size(value: string);
39
45
  get value(): string;
40
- set value(value: string);
46
+ set value(value: string | number | null | undefined);
41
47
  get disabled(): boolean;
42
48
  set disabled(value: boolean);
49
+ get searchable(): boolean;
50
+ set searchable(value: boolean);
51
+ get clearable(): boolean;
52
+ set clearable(value: boolean);
53
+ get full(): boolean;
54
+ set full(value: boolean);
43
55
  private get options();
44
56
  protected connectedCallback(): void;
45
57
  protected disconnectedCallback(): void;
@@ -59,6 +71,12 @@ export declare class FxSelect extends FxFormElement {
59
71
  * horizontal próximo à borda direita.
60
72
  */
61
73
  protected ajustarPosicao(): void;
74
+ /**
75
+ * Existe ancestral com overflow que cortaria o painel absoluto?
76
+ * `getComputedStyle` é defensivo: em jsdom retorna vazio e o modo
77
+ * histórico (absoluto) é mantido.
78
+ */
79
+ private static _precisaFixed;
62
80
  /** Altura máxima do painel a partir de `--fx-select-panel-max-height`. */
63
81
  private static _maxAltura;
64
82
  private select;
@@ -1 +1 @@
1
- {"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../../src/components/select/select.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAKxD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,QAAS,SAAQ,aAAa;IACzC,OAAgB,MAAM,SA+KpB;IAIF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAIjD;IAED,iFAAiF;IACjF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAK;IAChC,kEAAkE;IAClE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAK;IACnC,sEAAsE;IACtE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAM;IAExC,OAAO,CAAC,QAAQ,CAAC,CAAmB;IACpC,OAAO,CAAC,WAAW,CAAC,CAAqB;IACzC,4EAA4E;IAC5E,OAAO,CAAC,QAAQ,CAAC,CAAa;IAE9B,wCAAwC;IACxC,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAErB;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;IACD,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,EAMtB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAE1B;IAED,OAAO,KAAK,OAAO,GAKlB;cAEkB,iBAAiB,IAAI,IAAI;cAezB,oBAAoB,IAAI,IAAI;IAO/C,8EAA8E;IAC9E,OAAO,CAAC,MAAM;IAKd,+DAA+D;IAC/D,OAAO,CAAC,OAAO;IAQf,qEAAqE;IACrE,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,aAAa;IAOrB;;;;;;;OAOG;IACH,SAAS,CAAC,cAAc,IAAI,IAAI;IAuChC,0EAA0E;IAC1E,OAAO,CAAC,MAAM,CAAC,UAAU;IAMzB,OAAO,CAAC,MAAM;IAUd,qEAAqE;IACrE,OAAO,CAAC,gBAAgB;cAiBL,MAAM,IAAI,IAAI;IA6GjC,sEAAsE;IAC7D,iBAAiB,IAAI,IAAI;CAInC;AAED,wBAAgB,cAAc,IAAI,OAAO,QAAQ,CAEhD"}
1
+ {"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../../src/components/select/select.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAKxD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,QAAS,SAAQ,aAAa;IACzC,OAAgB,MAAM,SAoLpB;IAKF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAIjD;IAED,oEAAoE;IACpE,OAAO,CAAC,eAAe,CAAS;IAChC,6FAA6F;IAC7F,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,cAAc,CAAS;cAEZ,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAKjH,iFAAiF;IACjF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAK;IAChC,kEAAkE;IAClE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAK;IACnC,sEAAsE;IACtE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAM;IAExC,OAAO,CAAC,QAAQ,CAAC,CAAmB;IACpC,OAAO,CAAC,WAAW,CAAC,CAAqB;IACzC,4EAA4E;IAC5E,OAAO,CAAC,QAAQ,CAAC,CAAa;IAE9B,wCAAwC;IACxC,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAErB;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;IACD,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,EAOlD;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAE1B;IAED,IAAI,UAAU,IAAI,OAAO,CAExB;IACD,IAAI,UAAU,CAAC,KAAK,EAAE,OAAO,EAE5B;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IACD,IAAI,SAAS,CAAC,KAAK,EAAE,OAAO,EAE3B;IAED,IAAI,IAAI,IAAI,OAAO,CAElB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,EAEtB;IAED,OAAO,KAAK,OAAO,GAKlB;cAEkB,iBAAiB,IAAI,IAAI;cA2BzB,oBAAoB,IAAI,IAAI;IAO/C,8EAA8E;IAC9E,OAAO,CAAC,MAAM;IASd,+DAA+D;IAC/D,OAAO,CAAC,OAAO;IAUf,qEAAqE;IACrE,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,aAAa;IAOrB;;;;;;;OAOG;IACH,SAAS,CAAC,cAAc,IAAI,IAAI;IAuEhC;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,aAAa;IAW5B,0EAA0E;IAC1E,OAAO,CAAC,MAAM,CAAC,UAAU;IAMzB,OAAO,CAAC,MAAM;IAiBd,qEAAqE;IACrE,OAAO,CAAC,gBAAgB;cAiBL,MAAM,IAAI,IAAI;IAsHjC,sEAAsE;IAC7D,iBAAiB,IAAI,IAAI;CASnC;AAED,wBAAgB,cAAc,IAAI,OAAO,QAAQ,CAEhD"}
@@ -3,10 +3,21 @@ import { css } from "../../core/css.js";
3
3
  import { defineElement } from "../../core/define.js";
4
4
  import { esc } from "../../core/sanitize.js";
5
5
  const _FxSelect = class _FxSelect extends FxFormElement {
6
- // `value` fica FORA da observação: refleti-lo no change não pode
7
- // re-renderizar o template e fechar o dropdown.
6
+ constructor() {
7
+ super(...arguments);
8
+ this._suppressRender = false;
9
+ this._fixedMode = false;
10
+ this._fixedResolved = false;
11
+ }
12
+ // `value` é observado para que `setAttribute('value', ...)` (HTML puro)
13
+ // também atualize o rótulo. Os caminhos internos de change usam
14
+ // `_suppressRender` para renderizar UMA vez, depois de fechar o painel.
8
15
  static get observedAttributes() {
9
- return ["size", "disabled", "placeholder", "searchable", "clearable", "error", "invalid", "success", "valid", "placement"];
16
+ return ["value", "size", "disabled", "placeholder", "searchable", "clearable", "full", "error", "invalid", "success", "valid", "placement"];
17
+ }
18
+ attributeChangedCallback(name, oldValue, newValue) {
19
+ if (this._suppressRender) return;
20
+ super.attributeChangedCallback(name, oldValue, newValue);
10
21
  }
11
22
  /** Tamanho do campo. Padrão: `'md'`. */
12
23
  get size() {
@@ -20,7 +31,9 @@ const _FxSelect = class _FxSelect extends FxFormElement {
20
31
  return this.getAttr("value");
21
32
  }
22
33
  set value(value) {
23
- this.setAttribute("value", value);
34
+ const next = value == null ? "" : String(value);
35
+ const prev = this.getAttr("value");
36
+ if (prev !== next) this.setAttribute("value", next);
24
37
  this.updateFormValue();
25
38
  }
26
39
  get disabled() {
@@ -29,6 +42,24 @@ const _FxSelect = class _FxSelect extends FxFormElement {
29
42
  set disabled(value) {
30
43
  this.toggleAttr("disabled", Boolean(value));
31
44
  }
45
+ get searchable() {
46
+ return this.hasAttr("searchable");
47
+ }
48
+ set searchable(value) {
49
+ this.toggleAttr("searchable", Boolean(value));
50
+ }
51
+ get clearable() {
52
+ return this.hasAttr("clearable");
53
+ }
54
+ set clearable(value) {
55
+ this.toggleAttr("clearable", Boolean(value));
56
+ }
57
+ get full() {
58
+ return this.hasAttr("full");
59
+ }
60
+ set full(value) {
61
+ this.toggleAttr("full", Boolean(value));
62
+ }
32
63
  get options() {
33
64
  return [...this.querySelectorAll("option")].map((o) => ({
34
65
  value: o.getAttribute("value") ?? o.textContent?.trim() ?? "",
@@ -37,8 +68,17 @@ const _FxSelect = class _FxSelect extends FxFormElement {
37
68
  }
38
69
  connectedCallback() {
39
70
  super.connectedCallback();
40
- this.observer = new MutationObserver(() => this.render());
41
- this.observer.observe(this, { childList: true, subtree: true, characterData: true });
71
+ this.observer = new MutationObserver((mutations) => {
72
+ if (mutations.every((m) => m.type === "attributes" && m.target === this)) return;
73
+ this.render();
74
+ });
75
+ this.observer.observe(this, {
76
+ childList: true,
77
+ subtree: true,
78
+ characterData: true,
79
+ attributes: true,
80
+ attributeFilter: ["value", "selected"]
81
+ });
42
82
  this.docListener = (e) => {
43
83
  if (!this.hasAttr("open")) return;
44
84
  if (e.composedPath().includes(this)) return;
@@ -55,6 +95,8 @@ const _FxSelect = class _FxSelect extends FxFormElement {
55
95
  }
56
96
  /** Abre o dropdown, posiciona o painel e passa a reposicioná-lo no reflow. */
57
97
  _abrir() {
98
+ this._fixedMode = _FxSelect._precisaFixed(this);
99
+ this._fixedResolved = true;
58
100
  this.setAttribute("open", "");
59
101
  this._bindReflow();
60
102
  }
@@ -62,6 +104,8 @@ const _FxSelect = class _FxSelect extends FxFormElement {
62
104
  _fechar() {
63
105
  this.removeAttribute("open");
64
106
  this.removeAttribute("data-placement");
107
+ this._fixedMode = false;
108
+ this._fixedResolved = false;
65
109
  this._unbindReflow();
66
110
  }
67
111
  /** Liga o reposicionamento (resize + scroll interno, em capture). */
@@ -104,7 +148,30 @@ const _FxSelect = class _FxSelect extends FxFormElement {
104
148
  _FxSelect.MIN_ALTURA,
105
149
  (paraCima ? acima : abaixo) - _FxSelect.GAP
106
150
  );
107
- painel.style.maxHeight = `${Math.min(desejado || disponivel, disponivel, maxAltura)}px`;
151
+ const altura = Math.min(desejado || disponivel, disponivel, maxAltura);
152
+ painel.style.maxHeight = `${altura}px`;
153
+ if (!this._fixedResolved) {
154
+ this._fixedMode = _FxSelect._precisaFixed(this);
155
+ this._fixedResolved = true;
156
+ }
157
+ if (this._fixedMode) {
158
+ this.setAttribute("data-fixed", "");
159
+ const largura2 = Math.max(r.width, 220);
160
+ const esquerda = Math.max(
161
+ _FxSelect.MARGEM,
162
+ Math.min(r.left, window.innerWidth - largura2 - _FxSelect.MARGEM)
163
+ );
164
+ const topo = paraCima ? Math.max(_FxSelect.MARGEM, r.top - _FxSelect.GAP - altura) : r.bottom + _FxSelect.GAP;
165
+ painel.style.bottom = "auto";
166
+ painel.style.width = `${largura2}px`;
167
+ painel.style.left = `${esquerda}px`;
168
+ painel.style.top = `${topo}px`;
169
+ return;
170
+ }
171
+ this.removeAttribute("data-fixed");
172
+ painel.style.top = "";
173
+ painel.style.bottom = "";
174
+ painel.style.width = "";
108
175
  painel.style.left = "";
109
176
  const largura = painel.offsetWidth;
110
177
  const excesso = r.left + largura - (window.innerWidth - _FxSelect.MARGEM);
@@ -113,6 +180,21 @@ const _FxSelect = class _FxSelect extends FxFormElement {
113
180
  painel.style.right = "auto";
114
181
  }
115
182
  }
183
+ /**
184
+ * Existe ancestral com overflow que cortaria o painel absoluto?
185
+ * `getComputedStyle` é defensivo: em jsdom retorna vazio e o modo
186
+ * histórico (absoluto) é mantido.
187
+ */
188
+ static _precisaFixed(host) {
189
+ let el = host.parentElement;
190
+ while (el && el !== document.body && el !== document.documentElement) {
191
+ const cs = getComputedStyle(el);
192
+ const overflow = `${cs.overflow}${cs.overflowX}${cs.overflowY}`;
193
+ if (/(hidden|clip|auto|scroll|overlay)/.test(overflow)) return true;
194
+ el = el.parentElement;
195
+ }
196
+ return false;
197
+ }
116
198
  /** Altura máxima do painel a partir de `--fx-select-panel-max-height`. */
117
199
  static _maxAltura(host) {
118
200
  const raw = getComputedStyle(host).getPropertyValue("--fx-select-panel-max-height");
@@ -120,7 +202,12 @@ const _FxSelect = class _FxSelect extends FxFormElement {
120
202
  return Number.isFinite(n) && n > 0 ? n : 260;
121
203
  }
122
204
  select(value) {
123
- this.value = value;
205
+ this._suppressRender = true;
206
+ try {
207
+ this.value = value;
208
+ } finally {
209
+ this._suppressRender = false;
210
+ }
124
211
  this._fechar();
125
212
  this.render();
126
213
  this.emitValueEvents();
@@ -147,6 +234,10 @@ const _FxSelect = class _FxSelect extends FxFormElement {
147
234
  }
148
235
  render() {
149
236
  const prevOpen = this.hasAttr("open");
237
+ if (!prevOpen) {
238
+ this._fixedMode = false;
239
+ this._fixedResolved = false;
240
+ }
150
241
  const prevSearch = this.root.querySelector(".search");
151
242
  const search = prevSearch?.value ?? "";
152
243
  const searchFocused = prevSearch != null && this.root.activeElement === prevSearch;
@@ -223,7 +314,12 @@ const _FxSelect = class _FxSelect extends FxFormElement {
223
314
  });
224
315
  this.root.querySelector(".clear")?.addEventListener("click", (e) => {
225
316
  e.stopPropagation();
226
- this.value = "";
317
+ this._suppressRender = true;
318
+ try {
319
+ this.value = "";
320
+ } finally {
321
+ this._suppressRender = false;
322
+ }
227
323
  this._fechar();
228
324
  this.render();
229
325
  this.emitValueEvents();
@@ -238,7 +334,12 @@ const _FxSelect = class _FxSelect extends FxFormElement {
238
334
  }
239
335
  /** form.reset(): restaura o valor inicial e re-renderiza o rótulo. */
240
336
  formResetCallback() {
241
- super.formResetCallback();
337
+ this._suppressRender = true;
338
+ try {
339
+ super.formResetCallback();
340
+ } finally {
341
+ this._suppressRender = false;
342
+ }
242
343
  this.render();
243
344
  }
244
345
  };
@@ -361,6 +462,11 @@ _FxSelect.styles = css`
361
462
  border-radius: var(--fx-radius-md);
362
463
  box-shadow: var(--fx-shadow-lg);
363
464
  }
465
+ /* Ancestral com overflow (ex.: fx-card) cortaria o painel absoluto:
466
+ nesse caso o JS posiciona com position:fixed a partir do trigger. */
467
+ :host([data-fixed]) .panel {
468
+ position: fixed;
469
+ }
364
470
  /* Padrão: abre para baixo. */
365
471
  :host([open]:not([data-placement='top'])) .panel {
366
472
  top: calc(100% + var(--fx-select-panel-offset, 4px));
@@ -1 +1 @@
1
- {"version":3,"file":"select.js","sources":["../../../src/components/select/select.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-select> — Campo de seleção com dropdown customizado.\n *\n * Form-Associated Custom Element: dentro de um <form> entra no FormData\n * (via atributo `name`) e recebe reset/disable do form.\n *\n * Escreva os filhos como `<option>` nativos no light DOM; eles são\n * espelhados automaticamente (MutationObserver):\n *\n * <fx-select value=\"b\" searchable clearable>\n * <option value=\"a\">Opção A</option>\n * <option value=\"b\">Opção B</option>\n * </fx-select>\n *\n * Atributos: value, size (sm|md|lg), disabled, placeholder,\n * searchable, clearable, search-placeholder, no-results,\n * placement (auto|top|bottom — direção do painel; `auto` inverte quando\n * não cabe na viewport).\n * Eventos: `change`, `input` e `valueChange` (composed, detail: { value }) —\n * `input` aciona o v-model do Vue e `valueChange` o [(value)] do Angular.\n */\nexport class FxSelect extends FxFormElement {\n static override styles = css`\n :host {\n display: inline-block;\n vertical-align: middle;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n position: relative;\n /* Largura no HOST (padrão: conteúdo com mín. 200px). CSS externo, classes\n e style inline no elemento definem a largura sem precisar do full. */\n width: var(--fx-select-width, max-content);\n min-width: var(--fx-select-min-width, 200px);\n /* Espaço entre o campo e o painel + altura máxima do painel. Expostos\n como custom property porque ::part() do lado do consumidor tem\n prioridade MENOR que a folha do shadow root e não vence o top. */\n --fx-select-panel-offset: 4px;\n --fx-select-panel-max-height: 260px;\n }\n .trigger {\n display: inline-flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--fx-space-sm);\n box-sizing: border-box;\n min-height: var(--fx-size-md);\n /* Acompanha a largura definida no :host. */\n width: 100%;\n font: inherit;\n /* Altura/linha determinísticas: a caixa fica no token --fx-size-*\n (o line-height fixo impede que a herança da página estoure o min-height). */\n line-height: var(--fx-font-line-height);\n font-weight: var(--fx-font-weight);\n color: var(--fx-text-default);\n text-align: left;\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-xs) var(--fx-space-md);\n cursor: pointer;\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 .trigger:hover { border-color: var(--fx-border-hover); }\n .trigger:focus-visible,\n :host([open]) .trigger {\n outline: none;\n border-color: var(--fx-color-primary);\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n :host([size='sm']) { min-width: var(--fx-select-min-width-sm, 180px); }\n :host([size='sm']) .trigger { min-height: var(--fx-size-sm); }\n :host([size='lg']) .trigger { min-height: var(--fx-size-lg); font-size: calc(var(--fx-font-size) + 4px); }\n /* Full width: o host estica até o pai e o trigger acompanha. */\n :host([full]) { display: block; width: 100%; }\n :host([full]) { min-width: 0; }\n :host([full]) .trigger { width: 100%; min-width: 0; }\n :host([full]) .panel { width: 100%; }\n /* Validação — borda e brilho JÁ EM REPOUSO (igual ao fx-input); o\n brilho segue --fx-effect-error-ring, que acompanha effect.focus-ring. */\n :host([error]) .trigger,\n :host([invalid]) .trigger {\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([error]) .trigger:focus-visible,\n :host([invalid]) .trigger:focus-visible,\n :host([error][open]) .trigger,\n :host([invalid][open]) .trigger {\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]) .trigger,\n :host([valid]) .trigger {\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 :host([success]) .trigger:focus-visible,\n :host([valid]) .trigger:focus-visible,\n :host([success][open]) .trigger,\n :host([valid][open]) .trigger {\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 :host([disabled]) .trigger,\n .trigger[aria-disabled='true'] { opacity: 0.55; cursor: not-allowed; background-color: var(--fx-surface-surface-hover); }\n /* O rótulo encolhe (ellipsis) e os ícones NUNCA saem do campo. */\n .label,\n .placeholder {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .placeholder { color: var(--fx-text-muted); }\n .actions {\n display: inline-flex;\n align-items: center;\n gap: var(--fx-space-xs, 8px);\n flex-shrink: 0;\n }\n .caret { font-size: calc(var(--fx-font-size) - 3px); color: var(--fx-text-muted); pointer-events: none; }\n\n /* Painel do dropdown — a POSIÇÃO vertical é definida pelas regras\n [data-placement] abaixo (o JS de ajustarPosicao decide), por isso\n top/bottom não ficam aqui. */\n .panel {\n position: absolute;\n left: 0;\n z-index: var(--fx-z-dropdown, 1000);\n width: max(100%, 220px);\n max-height: var(--fx-select-panel-max-height, 260px);\n overflow-y: auto;\n display: none;\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 }\n /* Padrão: abre para baixo. */\n :host([open]:not([data-placement='top'])) .panel {\n top: calc(100% + var(--fx-select-panel-offset, 4px));\n }\n /* placement=\"top\" explícito ou auto-flip: abre para cima. */\n :host([open][placement='top']) .panel,\n :host([open][data-placement='top']) .panel {\n top: auto;\n bottom: calc(100% + var(--fx-select-panel-offset, 4px));\n }\n :host([open]) .panel { display: block; }\n .search {\n position: sticky;\n top: 0;\n width: 100%;\n box-sizing: border-box;\n font: inherit;\n color: var(--fx-text-default);\n background: var(--fx-surface-background);\n border: none;\n border-bottom: 1px solid var(--fx-border-default);\n padding: var(--fx-space-sm) var(--fx-space-md);\n outline: none;\n }\n .opt {\n display: block;\n width: 100%;\n text-align: left;\n font: inherit;\n color: var(--fx-text-default);\n background: none;\n border: none;\n padding: var(--fx-space-sm) var(--fx-space-md);\n cursor: pointer;\n }\n /* Hover e selecionado com a cor primária do tema (igual ao multiselect). */\n .opt:hover { background: color-mix(in srgb, var(--fx-color-primary) 12%, transparent); }\n .opt[aria-selected='true'] {\n background: color-mix(in srgb, var(--fx-color-primary) 18%, transparent);\n color: var(--fx-color-primary);\n font-weight: var(--fx-font-weight);\n }\n .empty { padding: var(--fx-space-sm) var(--fx-space-md); color: var(--fx-text-muted); }\n\n /* Clearable */\n .clear {\n border: none;\n background: transparent;\n color: var(--fx-text-muted);\n font-size: calc(var(--fx-font-size) + 2px);\n line-height: 1;\n cursor: pointer;\n padding: 0;\n }\n .clear:hover { color: var(--fx-color-danger); }\n .clear[hidden] { display: none; }\n `;\n\n // `value` fica FORA da observação: refleti-lo no change não pode\n // re-renderizar o template e fechar o dropdown.\n static override get observedAttributes(): string[] {\n // `invalid`/`valid` são aliases aceitos no CSS; sem observá-los o\n // setAttribute no submit não re-renderiza.\n return ['size', 'disabled', 'placeholder', 'searchable', 'clearable', 'error', 'invalid', 'success', 'valid', 'placement'];\n }\n\n /** Espaço (px) entre o campo e o painel — espelha `--fx-select-panel-offset`. */\n private static readonly GAP = 4;\n /** Margem (px) mantida entre o painel e as bordas da viewport. */\n private static readonly MARGEM = 8;\n /** Altura mínima do painel ao ser limitado pelo espaço disponível. */\n private static readonly MIN_ALTURA = 80;\n\n private observer?: MutationObserver;\n private docListener?: (e: Event) => void;\n /** Reposiciona o painel enquanto ele estiver aberto (bound em `_abrir`). */\n private onReflow?: () => void;\n\n /** Tamanho do campo. 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 value(): string {\n return this.getAttr('value');\n }\n set value(value: string) {\n this.setAttribute('value', value);\n // `value` não é observado (não pode re-renderizar/fechar o dropdown no\n // change): publica no form aqui; o rótulo do trigger é re-renderizado\n // pelos caminhos que já chamam render().\n this.updateFormValue();\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 private get options(): { value: string; label: string }[] {\n return [...this.querySelectorAll('option')].map((o) => ({\n value: o.getAttribute('value') ?? o.textContent?.trim() ?? '',\n label: o.textContent?.trim() ?? '',\n }));\n }\n\n protected override connectedCallback(): void {\n super.connectedCallback();\n // Espelha mudanças nas <option> do light DOM.\n this.observer = new MutationObserver(() => this.render());\n this.observer.observe(this, { childList: true, subtree: true, characterData: true });\n // Fecha o dropdown ao clicar fora.\n this.docListener = (e: Event) => {\n if (!this.hasAttr('open')) return;\n if (e.composedPath().includes(this)) return;\n this._fechar();\n this.render();\n };\n document.addEventListener('click', this.docListener);\n }\n\n protected override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.observer?.disconnect();\n if (this.docListener) document.removeEventListener('click', this.docListener);\n this._unbindReflow();\n }\n\n /** Abre o dropdown, posiciona o painel e passa a reposicioná-lo no reflow. */\n private _abrir(): void {\n this.setAttribute('open', '');\n this._bindReflow();\n }\n\n /** Fecha o dropdown e libera os listeners de resize/scroll. */\n private _fechar(): void {\n this.removeAttribute('open');\n // Sem isto o `data-placement` fica preso no host e a próxima abertura\n // mostraria o painel na posição invertida antes do ajuste rodar.\n this.removeAttribute('data-placement');\n this._unbindReflow();\n }\n\n /** Liga o reposicionamento (resize + scroll interno, em capture). */\n private _bindReflow(): void {\n if (this.onReflow) return;\n this.onReflow = () => {\n if (this.hasAttr('open')) this.ajustarPosicao();\n };\n window.addEventListener('resize', this.onReflow);\n window.addEventListener('scroll', this.onReflow, true);\n }\n\n private _unbindReflow(): void {\n if (!this.onReflow) return;\n window.removeEventListener('resize', this.onReflow);\n window.removeEventListener('scroll', this.onReflow, true);\n this.onReflow = undefined;\n }\n\n /**\n * Decide a posição vertical do painel medindo o espaço real da viewport.\n *\n * `placement=\"top\"`/`\"bottom\"` são respeitados literalmente; o padrão\n * (`auto`) só inverte quando o painel não cabe embaixo E couber melhor\n * acima. Também limita a altura ao espaço disponível e evita overflow\n * horizontal próximo à borda direita.\n */\n protected ajustarPosicao(): void {\n const painel = this.root.querySelector<HTMLElement>('.panel');\n const trigger = this.root.querySelector<HTMLElement>('.trigger');\n if (!painel || !trigger) return;\n\n const r = trigger.getBoundingClientRect();\n const abaixo = window.innerHeight - r.bottom; // espaço livre embaixo\n const acima = r.top; // espaço livre acima\n const desejado = painel.scrollHeight; // altura já com a busca\n const maxAltura = FxSelect._maxAltura(this);\n\n const pedido = this.getAttr('placement', 'auto');\n const paraCima =\n pedido === 'top' ||\n (pedido !== 'bottom' && abaixo < Math.min(desejado, maxAltura) && acima > abaixo);\n\n // O atributo placement do host tem precedência no CSS; data-placement\n // é o canal interno do auto-flip e usa a mesma regra de estilo.\n if (paraCima) this.setAttribute('data-placement', 'top');\n else this.removeAttribute('data-placement');\n\n // Limita a altura ao espaço real disponível (evita o painel vazar da tela).\n const disponivel = Math.max(\n FxSelect.MIN_ALTURA,\n (paraCima ? acima : abaixo) - FxSelect.GAP,\n );\n painel.style.maxHeight = `${Math.min(desejado || disponivel, disponivel, maxAltura)}px`;\n\n // Reset do deslocamento horizontal antes de remedir (o painel é recriado\n // a cada render, mas o ajuste pode rodar várias vezes com o mesmo nó).\n painel.style.left = '';\n const largura = painel.offsetWidth;\n const excesso = r.left + largura - (window.innerWidth - FxSelect.MARGEM);\n if (excesso > 0) {\n painel.style.left = `${-excesso}px`;\n painel.style.right = 'auto';\n }\n }\n\n /** Altura máxima do painel a partir de `--fx-select-panel-max-height`. */\n private static _maxAltura(host: HTMLElement): number {\n const raw = getComputedStyle(host).getPropertyValue('--fx-select-panel-max-height');\n const n = parseInt(raw, 10);\n return Number.isFinite(n) && n > 0 ? n : 260;\n }\n\n private select(value: string): void {\n this.value = value;\n this._fechar();\n this.render();\n this.emitValueEvents();\n this.dispatchEvent(\n new CustomEvent('change', { bubbles: true, composed: true, detail: { value } }),\n );\n }\n\n /** Navegação por teclado entre as opções do listbox (WCAG 2.1.1). */\n private _navigateOptions(e: KeyboardEvent): void {\n e.preventDefault();\n if (!this.hasAttr('open')) {\n this._abrir();\n this.render();\n }\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 let next = 0;\n if (e.key === 'ArrowDown') next = current === -1 ? 0 : Math.min(current + 1, opts.length - 1);\n else if (e.key === 'ArrowUp') next = current === -1 ? opts.length - 1 : Math.max(current - 1, 0);\n else if (e.key === 'Home') next = 0;\n else if (e.key === 'End') next = opts.length - 1;\n opts[next]?.focus();\n }\n\n protected override render(): void {\n const prevOpen = this.hasAttr('open');\n const prevSearch = this.root.querySelector<HTMLInputElement>('.search');\n // Estado do campo de busca antes de re-renderizar (valor, foco e cursor).\n const search = prevSearch?.value ?? '';\n const searchFocused =\n prevSearch != null && this.root.activeElement === prevSearch;\n const caretPos = searchFocused ? prevSearch!.selectionStart : null;\n\n const opts = this.options;\n let current = this.getAttr('value');\n if (!current) {\n const explicit = this.querySelector('option[selected]');\n current = explicit?.getAttribute('value') ?? explicit?.textContent?.trim() ?? '';\n }\n const selectedLabel = opts.find((o) => o.value === current)?.label ?? '';\n const placeholder = this.getAttr('placeholder', 'Selecione…');\n\n const filtered = search\n ? opts.filter((o) => o.label.toLowerCase().includes(search.toLowerCase()))\n : opts;\n\n this.setTemplate(`\n <span class=\"trigger\" part=\"trigger\" role=\"button\" tabindex=\"${this.disabled ? -1 : 0}\" aria-haspopup=\"listbox\" aria-expanded=\"${prevOpen}\">\n <span class=\"${selectedLabel ? 'label' : 'placeholder'}\">${esc(selectedLabel) || esc(placeholder)}</span>\n <span class=\"actions\">\n ${this.hasAttr('clearable') && current ? '<button type=\"button\" class=\"clear\" part=\"clear\" aria-label=\"Limpar\">×</button>' : ''}\n <span class=\"caret\">▼</span>\n </span>\n </span>\n <div class=\"panel\" part=\"panel\" role=\"listbox\">\n ${this.hasAttr('searchable') ? `<input class=\"search\" part=\"search\" type=\"text\" placeholder=\"${esc(this.getAttr('search-placeholder', 'Pesquisar…'))}\">` : ''}\n ${filtered.map((o) => `\n <button type=\"button\" class=\"opt\" role=\"option\" data-value=\"${esc(o.value)}\"\n aria-selected=\"${o.value === current}\">${esc(o.label)}</button>`).join('')}\n ${filtered.length === 0 ? `<div class=\"empty\">${esc(this.getAttr('no-results', 'Nenhum resultado'))}</div>` : ''}\n </div>\n `);\n\n if (prevOpen) this.setAttribute('open', '');\n // A medição só é válida com o painel já `display: block` (open aplicado).\n if (this.hasAttr('open')) this.ajustarPosicao();\n const searchInput = this.root.querySelector<HTMLInputElement>('.search');\n if (searchInput) {\n searchInput.value = search;\n searchInput.addEventListener('input', () => this.render());\n searchInput.addEventListener('click', (e) => e.stopPropagation());\n // Re-render não pode roubar o foco nem perder a posição do cursor.\n if (searchFocused) {\n searchInput.focus();\n try { searchInput.setSelectionRange(caretPos!, caretPos!); } catch { /* type=text sempre suporta */ }\n }\n }\n\n const trigger = this.root.querySelector<HTMLElement>('.trigger');\n if (!trigger) return;\n if (this.disabled) trigger.setAttribute('aria-disabled', 'true');\n\n trigger.addEventListener('click', (e) => {\n if (this.disabled) return;\n if ((e.target as HTMLElement).closest('.clear')) return;\n if (this.hasAttr('open')) this._fechar();\n else this._abrir();\n this.render();\n this.root.querySelector<HTMLInputElement>('.search')?.focus();\n });\n\n // Teclado: Enter/Espaço abrem o dropdown; setas/Home/End/Escape navegam.\n trigger.addEventListener('keydown', (e) => {\n if (this.disabled) return;\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n trigger.click();\n return;\n }\n if (e.key === 'Escape') {\n if (this.hasAttr('open')) {\n this._fechar();\n this.render();\n this.root.querySelector<HTMLElement>('.trigger')?.focus();\n }\n return;\n }\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Home' || e.key === 'End') {\n this._navigateOptions(e);\n }\n });\n\n // Limpar.\n this.root.querySelector('.clear')?.addEventListener('click', (e) => {\n e.stopPropagation();\n this.value = '';\n this._fechar();\n this.render();\n this.emitValueEvents();\n this.dispatchEvent(\n new CustomEvent('change', { bubbles: true, composed: true, detail: { value: '' } }),\n );\n });\n\n // Escolher opção.\n this.root.querySelectorAll<HTMLElement>('.opt').forEach((opt) => {\n opt.addEventListener('click', () => this.select(opt.dataset.value!));\n });\n\n // Publica name/value no form (ElementInternals) a cada render.\n this.updateFormValue();\n }\n\n /** form.reset(): restaura o valor inicial e re-renderiza o rótulo. */\n override formResetCallback(): void {\n super.formResetCallback();\n this.render();\n }\n}\n\nexport function defineFxSelect(): typeof FxSelect {\n return defineElement('fx-select', FxSelect);\n}\n\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"select.js","sources":["../../../src/components/select/select.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-select> — Campo de seleção com dropdown customizado.\n *\n * Form-Associated Custom Element: dentro de um <form> entra no FormData\n * (via atributo `name`) e recebe reset/disable do form.\n *\n * Escreva os filhos como `<option>` nativos no light DOM; eles são\n * espelhados automaticamente (MutationObserver):\n *\n * <fx-select value=\"b\" searchable clearable>\n * <option value=\"a\">Opção A</option>\n * <option value=\"b\">Opção B</option>\n * </fx-select>\n *\n * Atributos: value, size (sm|md|lg), disabled, placeholder,\n * searchable, clearable, search-placeholder, no-results,\n * placement (auto|top|bottom — direção do painel; `auto` inverte quando\n * não cabe na viewport).\n * Eventos: `change`, `input` e `valueChange` (composed, detail: { value }) —\n * `input` aciona o v-model do Vue e `valueChange` o [(value)] do Angular.\n */\nexport class FxSelect extends FxFormElement {\n static override styles = css`\n :host {\n display: inline-block;\n vertical-align: middle;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n position: relative;\n /* Largura no HOST (padrão: conteúdo com mín. 200px). CSS externo, classes\n e style inline no elemento definem a largura sem precisar do full. */\n width: var(--fx-select-width, max-content);\n min-width: var(--fx-select-min-width, 200px);\n /* Espaço entre o campo e o painel + altura máxima do painel. Expostos\n como custom property porque ::part() do lado do consumidor tem\n prioridade MENOR que a folha do shadow root e não vence o top. */\n --fx-select-panel-offset: 4px;\n --fx-select-panel-max-height: 260px;\n }\n .trigger {\n display: inline-flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--fx-space-sm);\n box-sizing: border-box;\n min-height: var(--fx-size-md);\n /* Acompanha a largura definida no :host. */\n width: 100%;\n font: inherit;\n /* Altura/linha determinísticas: a caixa fica no token --fx-size-*\n (o line-height fixo impede que a herança da página estoure o min-height). */\n line-height: var(--fx-font-line-height);\n font-weight: var(--fx-font-weight);\n color: var(--fx-text-default);\n text-align: left;\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-xs) var(--fx-space-md);\n cursor: pointer;\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 .trigger:hover { border-color: var(--fx-border-hover); }\n .trigger:focus-visible,\n :host([open]) .trigger {\n outline: none;\n border-color: var(--fx-color-primary);\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n :host([size='sm']) { min-width: var(--fx-select-min-width-sm, 180px); }\n :host([size='sm']) .trigger { min-height: var(--fx-size-sm); }\n :host([size='lg']) .trigger { min-height: var(--fx-size-lg); font-size: calc(var(--fx-font-size) + 4px); }\n /* Full width: o host estica até o pai e o trigger acompanha. */\n :host([full]) { display: block; width: 100%; }\n :host([full]) { min-width: 0; }\n :host([full]) .trigger { width: 100%; min-width: 0; }\n :host([full]) .panel { width: 100%; }\n /* Validação — borda e brilho JÁ EM REPOUSO (igual ao fx-input); o\n brilho segue --fx-effect-error-ring, que acompanha effect.focus-ring. */\n :host([error]) .trigger,\n :host([invalid]) .trigger {\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([error]) .trigger:focus-visible,\n :host([invalid]) .trigger:focus-visible,\n :host([error][open]) .trigger,\n :host([invalid][open]) .trigger {\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]) .trigger,\n :host([valid]) .trigger {\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 :host([success]) .trigger:focus-visible,\n :host([valid]) .trigger:focus-visible,\n :host([success][open]) .trigger,\n :host([valid][open]) .trigger {\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 :host([disabled]) .trigger,\n .trigger[aria-disabled='true'] { opacity: 0.55; cursor: not-allowed; background-color: var(--fx-surface-surface-hover); }\n /* O rótulo encolhe (ellipsis) e os ícones NUNCA saem do campo. */\n .label,\n .placeholder {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .placeholder { color: var(--fx-text-muted); }\n .actions {\n display: inline-flex;\n align-items: center;\n gap: var(--fx-space-xs, 8px);\n flex-shrink: 0;\n }\n .caret { font-size: calc(var(--fx-font-size) - 3px); color: var(--fx-text-muted); pointer-events: none; }\n\n /* Painel do dropdown — a POSIÇÃO vertical é definida pelas regras\n [data-placement] abaixo (o JS de ajustarPosicao decide), por isso\n top/bottom não ficam aqui. */\n .panel {\n position: absolute;\n left: 0;\n z-index: var(--fx-z-dropdown, 1000);\n width: max(100%, 220px);\n max-height: var(--fx-select-panel-max-height, 260px);\n overflow-y: auto;\n display: none;\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 }\n /* Ancestral com overflow (ex.: fx-card) cortaria o painel absoluto:\n nesse caso o JS posiciona com position:fixed a partir do trigger. */\n :host([data-fixed]) .panel {\n position: fixed;\n }\n /* Padrão: abre para baixo. */\n :host([open]:not([data-placement='top'])) .panel {\n top: calc(100% + var(--fx-select-panel-offset, 4px));\n }\n /* placement=\"top\" explícito ou auto-flip: abre para cima. */\n :host([open][placement='top']) .panel,\n :host([open][data-placement='top']) .panel {\n top: auto;\n bottom: calc(100% + var(--fx-select-panel-offset, 4px));\n }\n :host([open]) .panel { display: block; }\n .search {\n position: sticky;\n top: 0;\n width: 100%;\n box-sizing: border-box;\n font: inherit;\n color: var(--fx-text-default);\n background: var(--fx-surface-background);\n border: none;\n border-bottom: 1px solid var(--fx-border-default);\n padding: var(--fx-space-sm) var(--fx-space-md);\n outline: none;\n }\n .opt {\n display: block;\n width: 100%;\n text-align: left;\n font: inherit;\n color: var(--fx-text-default);\n background: none;\n border: none;\n padding: var(--fx-space-sm) var(--fx-space-md);\n cursor: pointer;\n }\n /* Hover e selecionado com a cor primária do tema (igual ao multiselect). */\n .opt:hover { background: color-mix(in srgb, var(--fx-color-primary) 12%, transparent); }\n .opt[aria-selected='true'] {\n background: color-mix(in srgb, var(--fx-color-primary) 18%, transparent);\n color: var(--fx-color-primary);\n font-weight: var(--fx-font-weight);\n }\n .empty { padding: var(--fx-space-sm) var(--fx-space-md); color: var(--fx-text-muted); }\n\n /* Clearable */\n .clear {\n border: none;\n background: transparent;\n color: var(--fx-text-muted);\n font-size: calc(var(--fx-font-size) + 2px);\n line-height: 1;\n cursor: pointer;\n padding: 0;\n }\n .clear:hover { color: var(--fx-color-danger); }\n .clear[hidden] { display: none; }\n `;\n\n // `value` é observado para que `setAttribute('value', ...)` (HTML puro)\n // também atualize o rótulo. Os caminhos internos de change usam\n // `_suppressRender` para renderizar UMA vez, depois de fechar o painel.\n static override get observedAttributes(): string[] {\n // `invalid`/`valid` são aliases aceitos no CSS; sem observá-los o\n // setAttribute no submit não re-renderiza.\n return ['value', 'size', 'disabled', 'placeholder', 'searchable', 'clearable', 'full', 'error', 'invalid', 'success', 'valid', 'placement'];\n }\n\n /** Evita renders em cascata durante mudanças internas agrupadas. */\n private _suppressRender = false;\n /** Resultado de `_precisaFixed` cacheado por abertura (evita getComputedStyle no scroll). */\n private _fixedMode = false;\n private _fixedResolved = false;\n\n protected override attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (this._suppressRender) return;\n super.attributeChangedCallback(name, oldValue, newValue);\n }\n\n /** Espaço (px) entre o campo e o painel — espelha `--fx-select-panel-offset`. */\n private static readonly GAP = 4;\n /** Margem (px) mantida entre o painel e as bordas da viewport. */\n private static readonly MARGEM = 8;\n /** Altura mínima do painel ao ser limitado pelo espaço disponível. */\n private static readonly MIN_ALTURA = 80;\n\n private observer?: MutationObserver;\n private docListener?: (e: Event) => void;\n /** Reposiciona o painel enquanto ele estiver aberto (bound em `_abrir`). */\n private onReflow?: () => void;\n\n /** Tamanho do campo. 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 value(): string {\n return this.getAttr('value');\n }\n set value(value: string | number | null | undefined) {\n const next = value == null ? '' : String(value);\n const prev = this.getAttr('value');\n\n // setAttribute dispara attributeChangedCallback → render (quando conectado).\n if (prev !== next) this.setAttribute('value', next);\n this.updateFormValue();\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 searchable(): boolean {\n return this.hasAttr('searchable');\n }\n set searchable(value: boolean) {\n this.toggleAttr('searchable', Boolean(value));\n }\n\n get clearable(): boolean {\n return this.hasAttr('clearable');\n }\n set clearable(value: boolean) {\n this.toggleAttr('clearable', Boolean(value));\n }\n\n get full(): boolean {\n return this.hasAttr('full');\n }\n set full(value: boolean) {\n this.toggleAttr('full', Boolean(value));\n }\n\n private get options(): { value: string; label: string }[] {\n return [...this.querySelectorAll('option')].map((o) => ({\n value: o.getAttribute('value') ?? o.textContent?.trim() ?? '',\n label: o.textContent?.trim() ?? '',\n }));\n }\n\n protected override connectedCallback(): void {\n super.connectedCallback();\n // Espelha mudanças nas <option> do light DOM — inclusive `value`/`selected`\n // alterados em runtime pelo framework (Vue atualiza atributos, não filhos).\n this.observer = new MutationObserver((mutations) => {\n // Mudanças no `value` do próprio host são tratadas pelo setter (que já\n // renderiza); ignorá-las evita um segundo render assíncrono.\n if (mutations.every((m) => m.type === 'attributes' && m.target === this)) return;\n this.render();\n });\n this.observer.observe(this, {\n childList: true,\n subtree: true,\n characterData: true,\n attributes: true,\n attributeFilter: ['value', 'selected'],\n });\n // Fecha o dropdown ao clicar fora.\n this.docListener = (e: Event) => {\n if (!this.hasAttr('open')) return;\n if (e.composedPath().includes(this)) return;\n this._fechar();\n this.render();\n };\n document.addEventListener('click', this.docListener);\n }\n\n protected override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.observer?.disconnect();\n if (this.docListener) document.removeEventListener('click', this.docListener);\n this._unbindReflow();\n }\n\n /** Abre o dropdown, posiciona o painel e passa a reposicioná-lo no reflow. */\n private _abrir(): void {\n // Resolve o modo (fixed/absolute) UMA vez por abertura; o scroll só\n // recalcula coordenadas, sem varrer ancestrais/getComputedStyle.\n this._fixedMode = FxSelect._precisaFixed(this);\n this._fixedResolved = true;\n this.setAttribute('open', '');\n this._bindReflow();\n }\n\n /** Fecha o dropdown e libera os listeners de resize/scroll. */\n private _fechar(): void {\n this.removeAttribute('open');\n // Sem isto o `data-placement` fica preso no host e a próxima abertura\n // mostraria o painel na posição invertida antes do ajuste rodar.\n this.removeAttribute('data-placement');\n this._fixedMode = false;\n this._fixedResolved = false;\n this._unbindReflow();\n }\n\n /** Liga o reposicionamento (resize + scroll interno, em capture). */\n private _bindReflow(): void {\n if (this.onReflow) return;\n this.onReflow = () => {\n if (this.hasAttr('open')) this.ajustarPosicao();\n };\n window.addEventListener('resize', this.onReflow);\n window.addEventListener('scroll', this.onReflow, true);\n }\n\n private _unbindReflow(): void {\n if (!this.onReflow) return;\n window.removeEventListener('resize', this.onReflow);\n window.removeEventListener('scroll', this.onReflow, true);\n this.onReflow = undefined;\n }\n\n /**\n * Decide a posição vertical do painel medindo o espaço real da viewport.\n *\n * `placement=\"top\"`/`\"bottom\"` são respeitados literalmente; o padrão\n * (`auto`) só inverte quando o painel não cabe embaixo E couber melhor\n * acima. Também limita a altura ao espaço disponível e evita overflow\n * horizontal próximo à borda direita.\n */\n protected ajustarPosicao(): void {\n const painel = this.root.querySelector<HTMLElement>('.panel');\n const trigger = this.root.querySelector<HTMLElement>('.trigger');\n if (!painel || !trigger) return;\n\n const r = trigger.getBoundingClientRect();\n const abaixo = window.innerHeight - r.bottom; // espaço livre embaixo\n const acima = r.top; // espaço livre acima\n const desejado = painel.scrollHeight; // altura já com a busca\n const maxAltura = FxSelect._maxAltura(this);\n\n const pedido = this.getAttr('placement', 'auto');\n const paraCima =\n pedido === 'top' ||\n (pedido !== 'bottom' && abaixo < Math.min(desejado, maxAltura) && acima > abaixo);\n\n // O atributo placement do host tem precedência no CSS; data-placement\n // é o canal interno do auto-flip e usa a mesma regra de estilo.\n if (paraCima) this.setAttribute('data-placement', 'top');\n else this.removeAttribute('data-placement');\n\n // Limita a altura ao espaço real disponível (evita o painel vazar da tela).\n const disponivel = Math.max(\n FxSelect.MIN_ALTURA,\n (paraCima ? acima : abaixo) - FxSelect.GAP,\n );\n const altura = Math.min(desejado || disponivel, disponivel, maxAltura);\n painel.style.maxHeight = `${altura}px`;\n\n // Dentro de containers com overflow (fx-card e afins) o painel absoluto é\n // cortado. Nesse caso usa coordenadas fixas a partir do trigger — imune\n // ao clipping do ancestral. Fora disso mantém o fluxo absoluto histórico.\n if (!this._fixedResolved) {\n this._fixedMode = FxSelect._precisaFixed(this);\n this._fixedResolved = true;\n }\n if (this._fixedMode) {\n this.setAttribute('data-fixed', '');\n const largura = Math.max(r.width, 220);\n const esquerda = Math.max(\n FxSelect.MARGEM,\n Math.min(r.left, window.innerWidth - largura - FxSelect.MARGEM),\n );\n const topo = paraCima\n ? Math.max(FxSelect.MARGEM, r.top - FxSelect.GAP - altura)\n : r.bottom + FxSelect.GAP;\n\n painel.style.bottom = 'auto';\n painel.style.width = `${largura}px`;\n painel.style.left = `${esquerda}px`;\n painel.style.top = `${topo}px`;\n return;\n }\n\n // Reset do estado fixed (caso o select tenha sido movido de contexto).\n this.removeAttribute('data-fixed');\n painel.style.top = '';\n painel.style.bottom = '';\n painel.style.width = '';\n\n // Reset do deslocamento horizontal antes de remedir (o painel é recriado\n // a cada render, mas o ajuste pode rodar várias vezes com o mesmo nó).\n painel.style.left = '';\n const largura = painel.offsetWidth;\n const excesso = r.left + largura - (window.innerWidth - FxSelect.MARGEM);\n if (excesso > 0) {\n painel.style.left = `${-excesso}px`;\n painel.style.right = 'auto';\n }\n }\n\n /**\n * Existe ancestral com overflow que cortaria o painel absoluto?\n * `getComputedStyle` é defensivo: em jsdom retorna vazio e o modo\n * histórico (absoluto) é mantido.\n */\n private static _precisaFixed(host: HTMLElement): boolean {\n let el = host.parentElement;\n while (el && el !== document.body && el !== document.documentElement) {\n const cs = getComputedStyle(el);\n const overflow = `${cs.overflow}${cs.overflowX}${cs.overflowY}`;\n if (/(hidden|clip|auto|scroll|overlay)/.test(overflow)) return true;\n el = el.parentElement;\n }\n return false;\n }\n\n /** Altura máxima do painel a partir de `--fx-select-panel-max-height`. */\n private static _maxAltura(host: HTMLElement): number {\n const raw = getComputedStyle(host).getPropertyValue('--fx-select-panel-max-height');\n const n = parseInt(raw, 10);\n return Number.isFinite(n) && n > 0 ? n : 260;\n }\n\n private select(value: string): void {\n // Agrupa: o setter NÃO renderiza aqui; o render acontece uma única vez\n // depois de fechar o painel.\n this._suppressRender = true;\n try {\n this.value = value;\n } finally {\n this._suppressRender = false;\n }\n this._fechar();\n this.render();\n this.emitValueEvents();\n this.dispatchEvent(\n new CustomEvent('change', { bubbles: true, composed: true, detail: { value } }),\n );\n }\n\n /** Navegação por teclado entre as opções do listbox (WCAG 2.1.1). */\n private _navigateOptions(e: KeyboardEvent): void {\n e.preventDefault();\n if (!this.hasAttr('open')) {\n this._abrir();\n this.render();\n }\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 let next = 0;\n if (e.key === 'ArrowDown') next = current === -1 ? 0 : Math.min(current + 1, opts.length - 1);\n else if (e.key === 'ArrowUp') next = current === -1 ? opts.length - 1 : Math.max(current - 1, 0);\n else if (e.key === 'Home') next = 0;\n else if (e.key === 'End') next = opts.length - 1;\n opts[next]?.focus();\n }\n\n protected override render(): void {\n const prevOpen = this.hasAttr('open');\n if (!prevOpen) {\n this._fixedMode = false;\n this._fixedResolved = false;\n }\n const prevSearch = this.root.querySelector<HTMLInputElement>('.search');\n // Estado do campo de busca antes de re-renderizar (valor, foco e cursor).\n const search = prevSearch?.value ?? '';\n const searchFocused =\n prevSearch != null && this.root.activeElement === prevSearch;\n const caretPos = searchFocused ? prevSearch!.selectionStart : null;\n\n const opts = this.options;\n let current = this.getAttr('value');\n if (!current) {\n const explicit = this.querySelector('option[selected]');\n current = explicit?.getAttribute('value') ?? explicit?.textContent?.trim() ?? '';\n }\n const selectedLabel = opts.find((o) => o.value === current)?.label ?? '';\n const placeholder = this.getAttr('placeholder', 'Selecione…');\n\n const filtered = search\n ? opts.filter((o) => o.label.toLowerCase().includes(search.toLowerCase()))\n : opts;\n\n this.setTemplate(`\n <span class=\"trigger\" part=\"trigger\" role=\"button\" tabindex=\"${this.disabled ? -1 : 0}\" aria-haspopup=\"listbox\" aria-expanded=\"${prevOpen}\">\n <span class=\"${selectedLabel ? 'label' : 'placeholder'}\">${esc(selectedLabel) || esc(placeholder)}</span>\n <span class=\"actions\">\n ${this.hasAttr('clearable') && current ? '<button type=\"button\" class=\"clear\" part=\"clear\" aria-label=\"Limpar\">×</button>' : ''}\n <span class=\"caret\">▼</span>\n </span>\n </span>\n <div class=\"panel\" part=\"panel\" role=\"listbox\">\n ${this.hasAttr('searchable') ? `<input class=\"search\" part=\"search\" type=\"text\" placeholder=\"${esc(this.getAttr('search-placeholder', 'Pesquisar…'))}\">` : ''}\n ${filtered.map((o) => `\n <button type=\"button\" class=\"opt\" role=\"option\" data-value=\"${esc(o.value)}\"\n aria-selected=\"${o.value === current}\">${esc(o.label)}</button>`).join('')}\n ${filtered.length === 0 ? `<div class=\"empty\">${esc(this.getAttr('no-results', 'Nenhum resultado'))}</div>` : ''}\n </div>\n `);\n\n if (prevOpen) this.setAttribute('open', '');\n // A medição só é válida com o painel já `display: block` (open aplicado).\n if (this.hasAttr('open')) this.ajustarPosicao();\n const searchInput = this.root.querySelector<HTMLInputElement>('.search');\n if (searchInput) {\n searchInput.value = search;\n searchInput.addEventListener('input', () => this.render());\n searchInput.addEventListener('click', (e) => e.stopPropagation());\n // Re-render não pode roubar o foco nem perder a posição do cursor.\n if (searchFocused) {\n searchInput.focus();\n try { searchInput.setSelectionRange(caretPos!, caretPos!); } catch { /* type=text sempre suporta */ }\n }\n }\n\n const trigger = this.root.querySelector<HTMLElement>('.trigger');\n if (!trigger) return;\n if (this.disabled) trigger.setAttribute('aria-disabled', 'true');\n\n trigger.addEventListener('click', (e) => {\n if (this.disabled) return;\n if ((e.target as HTMLElement).closest('.clear')) return;\n if (this.hasAttr('open')) this._fechar();\n else this._abrir();\n this.render();\n this.root.querySelector<HTMLInputElement>('.search')?.focus();\n });\n\n // Teclado: Enter/Espaço abrem o dropdown; setas/Home/End/Escape navegam.\n trigger.addEventListener('keydown', (e) => {\n if (this.disabled) return;\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n trigger.click();\n return;\n }\n if (e.key === 'Escape') {\n if (this.hasAttr('open')) {\n this._fechar();\n this.render();\n this.root.querySelector<HTMLElement>('.trigger')?.focus();\n }\n return;\n }\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Home' || e.key === 'End') {\n this._navigateOptions(e);\n }\n });\n\n // Limpar.\n this.root.querySelector('.clear')?.addEventListener('click', (e) => {\n e.stopPropagation();\n this._suppressRender = true;\n try {\n this.value = '';\n } finally {\n this._suppressRender = false;\n }\n this._fechar();\n this.render();\n this.emitValueEvents();\n this.dispatchEvent(\n new CustomEvent('change', { bubbles: true, composed: true, detail: { value: '' } }),\n );\n });\n\n // Escolher opção.\n this.root.querySelectorAll<HTMLElement>('.opt').forEach((opt) => {\n opt.addEventListener('click', () => this.select(opt.dataset.value!));\n });\n\n // Publica name/value no form (ElementInternals) a cada render.\n this.updateFormValue();\n }\n\n /** form.reset(): restaura o valor inicial e re-renderiza o rótulo. */\n override formResetCallback(): void {\n this._suppressRender = true;\n try {\n super.formResetCallback();\n } finally {\n this._suppressRender = false;\n }\n this.render();\n }\n}\n\nexport function defineFxSelect(): typeof FxSelect {\n return defineElement('fx-select', FxSelect);\n}\n\n"],"names":["largura"],"mappings":"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"}
@@ -10,6 +10,9 @@ import { FxFormElement } from '../../core/form-element';
10
10
  * Atributos: checked, disabled, value, name, size (sm|md|lg).
11
11
  * Eventos: `change` e `checkedChange` (composed, detail: { checked, value }) —
12
12
  * `checkedChange` é o two-way binding do Angular [(checked)].
13
+ *
14
+ * Vue: use `:checked="estado"` + `@change` (o `v-model` padrão de elemento
15
+ * customizado usa `value` + `input`, não `checked`).
13
16
  */
14
17
  export declare class FxSwitch extends FxFormElement {
15
18
  static styles: string;
@@ -23,7 +26,7 @@ export declare class FxSwitch extends FxFormElement {
23
26
  set disabled(value: boolean);
24
27
  /** getter/setter `value` → espelho do atributo `value` (compat com v-model / [(value)]). */
25
28
  get value(): string;
26
- set value(v: string);
29
+ set value(v: string | null | undefined);
27
30
  /** No FormData: `value` (ou 'on') quando ligado; null (não envia) quando não. */
28
31
  protected getFormValue(): string | FormData | null;
29
32
  protected render(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"switch.d.ts","sourceRoot":"","sources":["../../../src/components/switch/switch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAIxD;;;;;;;;;;;GAWG;AACH,qBAAa,QAAS,SAAQ,aAAa;IACzC,OAAgB,MAAM,SAsDpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,yCAAyC;IACzC,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAErB;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAEzB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAE1B;IAED,4FAA4F;IAC5F,IAAI,KAAK,IAAI,MAAM,CAAkC;IACrD,IAAI,KAAK,CAAC,CAAC,EAAE,MAAM,EAAoC;IAEvD,iFAAiF;cAC9D,YAAY,IAAI,MAAM,GAAG,QAAQ,GAAG,IAAI;cAIxC,MAAM,IAAI,IAAI;IAoCjC,4DAA4D;IAC5D,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,uBAAuB,CAAS;cAErB,iBAAiB,IAAI,IAAI;IAQ5C,sEAAsE;IAC7D,iBAAiB,IAAI,IAAI;CAMnC;AAED,wBAAgB,cAAc,IAAI,OAAO,QAAQ,CAEhD"}
1
+ {"version":3,"file":"switch.d.ts","sourceRoot":"","sources":["../../../src/components/switch/switch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAIxD;;;;;;;;;;;;;;GAcG;AACH,qBAAa,QAAS,SAAQ,aAAa;IACzC,OAAgB,MAAM,SAsDpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,yCAAyC;IACzC,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAErB;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAEzB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAE1B;IAED,4FAA4F;IAC5F,IAAI,KAAK,IAAI,MAAM,CAAkC;IACrD,IAAI,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAIrC;IAED,iFAAiF;cAC9D,YAAY,IAAI,MAAM,GAAG,QAAQ,GAAG,IAAI;cAIxC,MAAM,IAAI,IAAI;IAoCjC,4DAA4D;IAC5D,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,uBAAuB,CAAS;cAErB,iBAAiB,IAAI,IAAI;IAQ5C,sEAAsE;IAC7D,iBAAiB,IAAI,IAAI;CAMnC;AAED,wBAAgB,cAAc,IAAI,OAAO,QAAQ,CAEhD"}
@@ -35,7 +35,8 @@ const _FxSwitch = class _FxSwitch extends FxFormElement {
35
35
  return this.getAttr("value");
36
36
  }
37
37
  set value(v) {
38
- this.setAttribute("value", v);
38
+ this.setAttribute("value", v == null ? "" : String(v));
39
+ this.updateFormValue();
39
40
  }
40
41
  /** No FormData: `value` (ou 'on') quando ligado; null (não envia) quando não. */
41
42
  getFormValue() {