@wrrdev/fenix-ui 1.1.12 → 1.1.14

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 (115) hide show
  1. package/README.md +3 -3
  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/card/card.d.ts +10 -0
  11. package/dist/components/card/card.d.ts.map +1 -1
  12. package/dist/components/card/card.js +18 -0
  13. package/dist/components/card/card.js.map +1 -1
  14. package/dist/components/checkbox/checkbox.d.ts +20 -4
  15. package/dist/components/checkbox/checkbox.d.ts.map +1 -1
  16. package/dist/components/checkbox/checkbox.js +39 -2
  17. package/dist/components/checkbox/checkbox.js.map +1 -1
  18. package/dist/components/datepicker/datepicker.d.ts +16 -2
  19. package/dist/components/datepicker/datepicker.d.ts.map +1 -1
  20. package/dist/components/datepicker/datepicker.js +46 -2
  21. package/dist/components/datepicker/datepicker.js.map +1 -1
  22. package/dist/components/fileupload/fileupload.d.ts +10 -2
  23. package/dist/components/fileupload/fileupload.d.ts.map +1 -1
  24. package/dist/components/fileupload/fileupload.js +17 -2
  25. package/dist/components/fileupload/fileupload.js.map +1 -1
  26. package/dist/components/floatlabel/floatlabel.d.ts +9 -3
  27. package/dist/components/floatlabel/floatlabel.d.ts.map +1 -1
  28. package/dist/components/floatlabel/floatlabel.js +22 -7
  29. package/dist/components/floatlabel/floatlabel.js.map +1 -1
  30. package/dist/components/input/input.d.ts +14 -7
  31. package/dist/components/input/input.d.ts.map +1 -1
  32. package/dist/components/input/input.js +18 -2
  33. package/dist/components/input/input.js.map +1 -1
  34. package/dist/components/knob/knob.d.ts +7 -3
  35. package/dist/components/knob/knob.d.ts.map +1 -1
  36. package/dist/components/knob/knob.js +4 -2
  37. package/dist/components/knob/knob.js.map +1 -1
  38. package/dist/components/multiselect/multiselect.d.ts +13 -3
  39. package/dist/components/multiselect/multiselect.d.ts.map +1 -1
  40. package/dist/components/multiselect/multiselect.js +27 -2
  41. package/dist/components/multiselect/multiselect.js.map +1 -1
  42. package/dist/components/radio/radio.d.ts +27 -4
  43. package/dist/components/radio/radio.d.ts.map +1 -1
  44. package/dist/components/radio/radio.js +67 -8
  45. package/dist/components/radio/radio.js.map +1 -1
  46. package/dist/components/rating/rating.d.ts +8 -4
  47. package/dist/components/rating/rating.d.ts.map +1 -1
  48. package/dist/components/rating/rating.js +5 -2
  49. package/dist/components/rating/rating.js.map +1 -1
  50. package/dist/components/select/select.d.ts +38 -4
  51. package/dist/components/select/select.d.ts.map +1 -1
  52. package/dist/components/select/select.js +109 -12
  53. package/dist/components/select/select.js.map +1 -1
  54. package/dist/components/slider/slider.d.ts +12 -6
  55. package/dist/components/slider/slider.d.ts.map +1 -1
  56. package/dist/components/slider/slider.js +10 -2
  57. package/dist/components/slider/slider.js.map +1 -1
  58. package/dist/components/switch/switch.d.ts +20 -4
  59. package/dist/components/switch/switch.d.ts.map +1 -1
  60. package/dist/components/switch/switch.js +40 -2
  61. package/dist/components/switch/switch.js.map +1 -1
  62. package/dist/components/textarea/textarea.d.ts +12 -5
  63. package/dist/components/textarea/textarea.d.ts.map +1 -1
  64. package/dist/components/textarea/textarea.js +12 -2
  65. package/dist/components/textarea/textarea.js.map +1 -1
  66. package/dist/components/toast/toast.d.ts +17 -5
  67. package/dist/components/toast/toast.d.ts.map +1 -1
  68. package/dist/components/toast/toast.js +161 -21
  69. package/dist/components/toast/toast.js.map +1 -1
  70. package/dist/components/toggle-button-group/toggle-button-group.d.ts +13 -5
  71. package/dist/components/toggle-button-group/toggle-button-group.d.ts.map +1 -1
  72. package/dist/components/toggle-button-group/toggle-button-group.js +19 -2
  73. package/dist/components/toggle-button-group/toggle-button-group.js.map +1 -1
  74. package/dist/core/base.d.ts +7 -0
  75. package/dist/core/base.d.ts.map +1 -1
  76. package/dist/core/base.js +4 -1
  77. package/dist/core/base.js.map +1 -1
  78. package/dist/core/form-element.d.ts +81 -0
  79. package/dist/core/form-element.d.ts.map +1 -0
  80. package/dist/core/form-element.js +129 -0
  81. package/dist/core/form-element.js.map +1 -0
  82. package/dist/core/presets.d.ts +8 -3
  83. package/dist/core/presets.d.ts.map +1 -1
  84. package/dist/core/presets.js +6 -17
  85. package/dist/core/presets.js.map +1 -1
  86. package/dist/core/types.d.ts +4 -0
  87. package/dist/core/types.d.ts.map +1 -1
  88. package/dist/docs/app.d.ts.map +1 -1
  89. package/dist/docs/componentes/autocomplete.doc.d.ts.map +1 -1
  90. package/dist/docs/componentes/card.doc.d.ts.map +1 -1
  91. package/dist/docs/componentes/checkbox.doc.d.ts.map +1 -1
  92. package/dist/docs/componentes/datepicker.doc.d.ts.map +1 -1
  93. package/dist/docs/componentes/fileupload.doc.d.ts.map +1 -1
  94. package/dist/docs/componentes/input.doc.d.ts.map +1 -1
  95. package/dist/docs/componentes/knob.doc.d.ts.map +1 -1
  96. package/dist/docs/componentes/multiselect.doc.d.ts.map +1 -1
  97. package/dist/docs/componentes/rating.doc.d.ts.map +1 -1
  98. package/dist/docs/componentes/select.doc.d.ts.map +1 -1
  99. package/dist/docs/componentes/slider.doc.d.ts.map +1 -1
  100. package/dist/docs/componentes/switch.doc.d.ts.map +1 -1
  101. package/dist/docs/componentes/textarea.doc.d.ts.map +1 -1
  102. package/dist/docs/componentes/toast.doc.d.ts.map +1 -1
  103. package/dist/docs/componentes/toggle-button-group.doc.d.ts.map +1 -1
  104. package/dist/docs/frameworks.d.ts +75 -0
  105. package/dist/docs/frameworks.d.ts.map +1 -0
  106. package/dist/docs/ui/code.d.ts +4 -0
  107. package/dist/docs/ui/code.d.ts.map +1 -1
  108. package/dist/fenix-ui.umd.min.js +233 -179
  109. package/dist/fenix-ui4.esm.js +773 -77
  110. package/dist/fenix-ui4.esm.js.map +1 -1
  111. package/dist/index.d.ts +1 -0
  112. package/dist/index.d.ts.map +1 -1
  113. package/dist/index.js +4 -1
  114. package/dist/index.js.map +1 -1
  115. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"switch.js","sources":["../../../src/components/switch/switch.ts"],"sourcesContent":["import { FxElement } from '../../core/base';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\n\n/**\n * <fx-switch> — Interruptor ligado/desligado (role=\"switch\").\n *\n * O texto do slot (light DOM) é o rótulo clicável.\n * Atributos: checked, disabled, size (sm|md|lg).\n * Evento: `change` (composed, detail: { checked }).\n */\nexport class FxSwitch 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 .switch {\n display: inline-flex;\n align-items: center;\n gap: var(--fx-space-sm);\n cursor: pointer;\n user-select: none;\n background: none;\n border: none;\n padding: 0;\n color: var(--fx-text-default);\n font: inherit;\n }\n .track {\n position: relative;\n width: 40px;\n height: 22px;\n border-radius: var(--fx-radius-full);\n background: var(--fx-border-default);\n flex-shrink: 0;\n transition: background-color var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n .thumb {\n position: absolute;\n top: 3px;\n left: 3px;\n width: 16px;\n height: 16px;\n border-radius: var(--fx-radius-full);\n background: #fff;\n box-shadow: var(--fx-shadow-sm);\n transition: transform var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n :host([checked]) .track { background: var(--fx-color-primary); }\n :host([checked]) .thumb { transform: translateX(18px); }\n .switch:focus-visible .track {\n outline: none;\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n .label { display: inline-flex; align-items: center; }\n :host([disabled]) .switch { opacity: 0.55; cursor: not-allowed; pointer-events: none; }\n /* Escala: sm 32×18 · md 40×22 (padrão) · lg 48×26 */\n :host([size='sm']) .track { width: 32px; height: 18px; }\n :host([size='sm']) .thumb { top: 2px; left: 2px; width: 14px; height: 14px; }\n :host([size='sm'][checked]) .thumb { transform: translateX(14px); }\n :host([size='lg']) .track { width: 48px; height: 26px; }\n :host([size='lg']) .thumb { top: 4px; left: 4px; width: 18px; height: 18px; }\n :host([size='lg'][checked]) .thumb { transform: translateX(22px); }\n `;\n\n static override get observedAttributes(): string[] {\n return ['checked', 'disabled', 'size'];\n }\n\n /** Tamanho da trilha. 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 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 protected override render(): void {\n this.setTemplate(`\n <button class=\"switch\" part=\"switch\" type=\"button\" role=\"switch\"\n aria-checked=\"${this.checked}\" ${this.hasAttr('disabled') ? 'disabled' : ''}>\n <span class=\"track\" part=\"track\"><span class=\"thumb\" part=\"thumb\"></span></span>\n <span class=\"label\" part=\"label\"><slot></slot></span>\n </button>\n `);\n\n const btn = this.root.querySelector<HTMLButtonElement>('.switch');\n if (!btn) return;\n btn.addEventListener('click', () => {\n if (this.hasAttr('disabled')) return;\n this.checked = !this.checked;\n btn.setAttribute('aria-checked', String(this.checked));\n this.dispatchEvent(\n new CustomEvent('change', {\n bubbles: true,\n composed: true,\n detail: { checked: this.checked },\n }),\n );\n });\n }\n}\n\nexport function defineFxSwitch(): typeof FxSwitch {\n return defineElement('fx-switch', FxSwitch);\n}\n"],"names":[],"mappings":";;;AAWO,MAAM,YAAN,MAAM,kBAAiB,UAAU;AAAA,EAyDtC,WAAoB,qBAA+B;AACjD,WAAO,CAAC,WAAW,YAAY,MAAM;AAAA,EACvC;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,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,WAAoB;AACtB,WAAO,KAAK,QAAQ,UAAU;AAAA,EAChC;AAAA,EACA,IAAI,SAAS,OAAgB;AAC3B,SAAK,WAAW,YAAY,QAAQ,KAAK,CAAC;AAAA,EAC5C;AAAA,EAEmB,SAAe;AAChC,SAAK,YAAY;AAAA;AAAA,wBAEG,KAAK,OAAO,KAAK,KAAK,QAAQ,UAAU,IAAI,aAAa,EAAE;AAAA;AAAA;AAAA;AAAA,KAI9E;AAED,UAAM,MAAM,KAAK,KAAK,cAAiC,SAAS;AAChE,QAAI,CAAC,IAAK;AACV,QAAI,iBAAiB,SAAS,MAAM;AAClC,UAAI,KAAK,QAAQ,UAAU,EAAG;AAC9B,WAAK,UAAU,CAAC,KAAK;AACrB,UAAI,aAAa,gBAAgB,OAAO,KAAK,OAAO,CAAC;AACrD,WAAK;AAAA,QACH,IAAI,YAAY,UAAU;AAAA,UACxB,SAAS;AAAA,UACT,UAAU;AAAA,UACV,QAAQ,EAAE,SAAS,KAAK,QAAA;AAAA,QAAQ,CACjC;AAAA,MAAA;AAAA,IAEL,CAAC;AAAA,EACH;AACF;AA3GE,UAAgB,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;AADpB,IAAM,WAAN;AA8GA,SAAS,iBAAkC;AAChD,SAAO,cAAc,aAAa,QAAQ;AAC5C;"}
1
+ {"version":3,"file":"switch.js","sources":["../../../src/components/switch/switch.ts"],"sourcesContent":["import { FxFormElement } from '../../core/form-element';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\n\n/**\n * <fx-switch> — Interruptor ligado/desligado (role=\"switch\").\n *\n * Form-Associated Custom Element: dentro de um <form> entra no FormData\n * (via atributo `name`) com `value` (ou 'on') quando ligado — desligado\n * não envia nada, igual ao checkbox. Recebe reset/disable do form.\n *\n * O texto do slot (light DOM) é o rótulo clicável.\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 FxSwitch 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 .switch {\n display: inline-flex;\n align-items: center;\n gap: var(--fx-space-sm);\n cursor: pointer;\n user-select: none;\n background: none;\n border: none;\n padding: 0;\n color: var(--fx-text-default);\n font: inherit;\n }\n .track {\n position: relative;\n width: 40px;\n height: 22px;\n border-radius: var(--fx-radius-full);\n background: var(--fx-border-default);\n flex-shrink: 0;\n transition: background-color var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n .thumb {\n position: absolute;\n top: 3px;\n left: 3px;\n width: 16px;\n height: 16px;\n border-radius: var(--fx-radius-full);\n background: #fff;\n box-shadow: var(--fx-shadow-sm);\n transition: transform var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n :host([checked]) .track { background: var(--fx-color-primary); }\n :host([checked]) .thumb { transform: translateX(18px); }\n .switch:focus-visible .track {\n outline: none;\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n .label { display: inline-flex; align-items: center; }\n :host([disabled]) .switch { opacity: 0.55; cursor: not-allowed; pointer-events: none; }\n /* Escala: sm 32×18 · md 40×22 (padrão) · lg 48×26 */\n :host([size='sm']) .track { width: 32px; height: 18px; }\n :host([size='sm']) .thumb { top: 2px; left: 2px; width: 14px; height: 14px; }\n :host([size='sm'][checked]) .thumb { transform: translateX(14px); }\n :host([size='lg']) .track { width: 48px; height: 26px; }\n :host([size='lg']) .thumb { top: 4px; left: 4px; width: 18px; height: 18px; }\n :host([size='lg'][checked]) .thumb { transform: translateX(22px); }\n `;\n\n static override get observedAttributes(): string[] {\n return ['checked', 'disabled', 'size'];\n }\n\n /** Tamanho da trilha. 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 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 /** 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 /** No FormData: `value` (ou 'on') quando ligado; 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 protected override render(): void {\n this.setTemplate(`\n <button class=\"switch\" part=\"switch\" type=\"button\" role=\"switch\"\n aria-checked=\"${this.checked}\" ${this.hasAttr('disabled') ? 'disabled' : ''}>\n <span class=\"track\" part=\"track\"><span class=\"thumb\" part=\"thumb\"></span></span>\n <span class=\"label\" part=\"label\"><slot></slot></span>\n </button>\n `);\n\n const btn = this.root.querySelector<HTMLButtonElement>('.switch');\n if (!btn) return;\n btn.addEventListener('click', () => {\n if (this.hasAttr('disabled')) return;\n this.checked = !this.checked;\n btn.setAttribute('aria-checked', String(this.checked));\n this.dispatchEvent(\n new CustomEvent('change', {\n bubbles: true,\n composed: true,\n detail: { checked: this.checked },\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 // 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 inicial (desligado por padrão). */\n override formResetCallback(): void {\n this.checked = this._initialChecked;\n const btn = this.root.querySelector<HTMLButtonElement>('.switch');\n if (btn) btn.setAttribute('aria-checked', String(this.checked));\n this.updateFormValue();\n }\n}\n\nexport function defineFxSwitch(): typeof FxSwitch {\n return defineElement('fx-switch', FxSwitch);\n}\n"],"names":[],"mappings":";;;AAgBO,MAAM,YAAN,MAAM,kBAAiB,cAAc;AAAA,EAArC,cAAA;AAAA,UAAA,GAAA,SAAA;AAkIL,SAAQ,kBAAkB;AAC1B,SAAQ,0BAA0B;AAAA,EAAA;AAAA,EA1ElC,WAAoB,qBAA+B;AACjD,WAAO,CAAC,WAAW,YAAY,MAAM;AAAA,EACvC;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,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,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,QAAgB;AAAE,WAAO,KAAK,QAAQ,OAAO;AAAA,EAAG;AAAA,EACpD,IAAI,MAAM,GAAW;AAAE,SAAK,aAAa,SAAS,CAAC;AAAA,EAAG;AAAA;AAAA,EAGnC,eAAyC;AAC1D,WAAO,KAAK,UAAW,KAAK,QAAQ,OAAO,KAAK,OAAQ;AAAA,EAC1D;AAAA,EAEmB,SAAe;AAChC,SAAK,YAAY;AAAA;AAAA,wBAEG,KAAK,OAAO,KAAK,KAAK,QAAQ,UAAU,IAAI,aAAa,EAAE;AAAA;AAAA;AAAA;AAAA,KAI9E;AAED,UAAM,MAAM,KAAK,KAAK,cAAiC,SAAS;AAChE,QAAI,CAAC,IAAK;AACV,QAAI,iBAAiB,SAAS,MAAM;AAClC,UAAI,KAAK,QAAQ,UAAU,EAAG;AAC9B,WAAK,UAAU,CAAC,KAAK;AACrB,UAAI,aAAa,gBAAgB,OAAO,KAAK,OAAO,CAAC;AACrD,WAAK;AAAA,QACH,IAAI,YAAY,UAAU;AAAA,UACxB,SAAS;AAAA,UACT,UAAU;AAAA,UACV,QAAQ,EAAE,SAAS,KAAK,QAAA;AAAA,QAAQ,CACjC;AAAA,MAAA;AAGH,WAAK;AAAA,QACH,IAAI,YAAY,iBAAiB;AAAA,UAC/B,SAAS;AAAA,UACT,UAAU;AAAA,UACV,QAAQ,EAAE,SAAS,KAAK,SAAS,OAAO,KAAK,QAAQ,OAAO,EAAA;AAAA,QAAE,CAC/D;AAAA,MAAA;AAAA,IAEL,CAAC;AAGD,SAAK,gBAAA;AAAA,EACP;AAAA,EAMmB,oBAA0B;AAC3C,QAAI,CAAC,KAAK,yBAAyB;AACjC,WAAK,0BAA0B;AAC/B,WAAK,kBAAkB,KAAK;AAAA,IAC9B;AACA,UAAM,kBAAA;AAAA,EACR;AAAA;AAAA,EAGS,oBAA0B;AACjC,SAAK,UAAU,KAAK;AACpB,UAAM,MAAM,KAAK,KAAK,cAAiC,SAAS;AAChE,QAAI,IAAK,KAAI,aAAa,gBAAgB,OAAO,KAAK,OAAO,CAAC;AAC9D,SAAK,gBAAA;AAAA,EACP;AACF;AAnJE,UAAgB,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;AADpB,IAAM,WAAN;AAsJA,SAAS,iBAAkC;AAChD,SAAO,cAAc,aAAa,QAAQ;AAC5C;"}
@@ -1,12 +1,17 @@
1
- import { FxElement } from '../../core/base';
1
+ import { FxFormElement } from '../../core/form-element';
2
2
  /**
3
3
  * <fx-textarea> — Área de texto multilinha estilizada com os tokens do tema.
4
4
  *
5
- * Atributos: value, size (sm|md|lg), placeholder, disabled, readonly,
6
- * rows, maxlength.
7
- * Eventos: `input` e `change` (composed, detail: { value }).
5
+ * Form-Associated Custom Element: dentro de um <form> entra no FormData
6
+ * (via atributo `name`), recebe reset/disable do form e é alvo de
7
+ * `<label for>`/`autofocus`. Enter continua sendo quebra de linha.
8
+ *
9
+ * Atributos: value, name, size (sm|md|lg), placeholder, disabled, readonly,
10
+ * rows, maxlength, autofocus.
11
+ * Eventos: `input`, `change` e `valueChange` (composed, detail: { value }) —
12
+ * `input` aciona o v-model do Vue e `valueChange` o [(value)] do Angular.
8
13
  */
9
- export declare class FxTextarea extends FxElement {
14
+ export declare class FxTextarea extends FxFormElement {
10
15
  static styles: string;
11
16
  static get observedAttributes(): string[];
12
17
  get size(): string;
@@ -14,6 +19,8 @@ export declare class FxTextarea extends FxElement {
14
19
  get value(): string;
15
20
  set value(value: string);
16
21
  protected render(): void;
22
+ /** Empurra o atributo `value` para o campo interno sem re-renderizar. */
23
+ protected syncInternalControl(): void;
17
24
  }
18
25
  export declare function defineFxTextarea(): typeof FxTextarea;
19
26
  //# sourceMappingURL=textarea.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"textarea.d.ts","sourceRoot":"","sources":["../../../src/components/textarea/textarea.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAK5C;;;;;;GAMG;AACH,qBAAa,UAAW,SAAQ,SAAS;IACvC,OAAgB,MAAM,SA6DpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAAuC;IAE7D,IAAI,KAAK,IAAI,MAAM,CAAkC;IACrD,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,EAAwC;cAE5C,MAAM,IAAI,IAAI;CA8BlC;AAED,wBAAgB,gBAAgB,IAAI,OAAO,UAAU,CAEpD"}
1
+ {"version":3,"file":"textarea.d.ts","sourceRoot":"","sources":["../../../src/components/textarea/textarea.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAKxD;;;;;;;;;;;GAWG;AACH,qBAAa,UAAW,SAAQ,aAAa;IAC3C,OAAgB,MAAM,SA6DpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAAuC;IAE7D,IAAI,KAAK,IAAI,MAAM,CAAkC;IACrD,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,EAMtB;cAEkB,MAAM,IAAI,IAAI;IAoCjC,yEAAyE;cACtD,mBAAmB,IAAI,IAAI;CAK/C;AAED,wBAAgB,gBAAgB,IAAI,OAAO,UAAU,CAEpD"}
@@ -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 _FxTextarea = class _FxTextarea extends FxElement {
5
+ const _FxTextarea = class _FxTextarea extends FxFormElement {
6
6
  static get observedAttributes() {
7
7
  return ["size", "placeholder", "disabled", "readonly", "rows", "maxlength", "error", "invalid", "success", "valid"];
8
8
  }
@@ -18,6 +18,8 @@ const _FxTextarea = class _FxTextarea extends FxElement {
18
18
  }
19
19
  set value(value) {
20
20
  this.setAttribute("value", value);
21
+ this.syncInternalControl();
22
+ this.updateFormValue();
21
23
  }
22
24
  render() {
23
25
  const placeholder = this.getAttr("placeholder");
@@ -37,6 +39,7 @@ const _FxTextarea = class _FxTextarea extends FxElement {
37
39
  if (readonly) field.setAttribute("readonly", "");
38
40
  const emit = (event) => {
39
41
  this.value = field.value;
42
+ this.emitValueEvents();
40
43
  this.dispatchEvent(
41
44
  new CustomEvent(event, { bubbles: true, composed: true, detail: { value: field.value } })
42
45
  );
@@ -49,6 +52,13 @@ const _FxTextarea = class _FxTextarea extends FxElement {
49
52
  e.stopPropagation();
50
53
  emit("change");
51
54
  });
55
+ this.updateFormValue();
56
+ }
57
+ /** Empurra o atributo `value` para o campo interno sem re-renderizar. */
58
+ syncInternalControl() {
59
+ const field = this.root.querySelector(".field");
60
+ const value = this.getAttr("value");
61
+ if (field && field.value !== value) field.value = value;
52
62
  }
53
63
  };
54
64
  _FxTextarea.styles = css`
@@ -1 +1 @@
1
- {"version":3,"file":"textarea.js","sources":["../../../src/components/textarea/textarea.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-textarea> — Área de texto multilinha estilizada com os tokens do tema.\n *\n * Atributos: value, size (sm|md|lg), placeholder, disabled, readonly,\n * rows, maxlength.\n * Eventos: `input` e `change` (composed, detail: { value }).\n */\nexport class FxTextarea extends FxElement {\n static override styles = css`\n :host {\n display: inline-block;\n vertical-align: middle;\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-textarea-width, 260px);\n vertical-align: middle;\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-md) var(--fx-space-lg);\n /* Acompanha a largura definida no :host. */\n width: 100%;\n min-height: calc(var(--fx-size-md) + 40px);\n resize: vertical;\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 .field:disabled,\n .field[readonly] {\n opacity: 0.55;\n cursor: not-allowed;\n background-color: var(--fx-surface-surface-hover);\n }\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, :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, :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 :host([size='sm']) { width: var(--fx-textarea-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-textarea-width-lg, 300px); }\n :host([size='lg']) .field { min-height: calc(var(--fx-size-lg) + 80px); }\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 `;\n\n static override get observedAttributes(): string[] {\n return ['size', 'placeholder', 'disabled', 'readonly', 'rows', 'maxlength', '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(value: string) { this.setAttribute('size', value); }\n\n get value(): string { return this.getAttr('value'); }\n set value(value: string) { this.setAttribute('value', value); }\n\n protected override render(): void {\n const placeholder = this.getAttr('placeholder');\n const readonly = this.hasAttr('readonly');\n const disabled = this.hasAttr('disabled');\n const rows = this.getAttr('rows');\n const maxlength = this.getAttr('maxlength');\n\n this.setTemplate(`<textarea class=\"field\" part=\"textarea\"\n ${placeholder ? `placeholder=\"${esc(placeholder)}\"` : ''}\n ${rows ? `rows=\"${esc(rows)}\"` : ''}\n ${maxlength ? `maxlength=\"${esc(maxlength)}\"` : ''}\n ></textarea>`);\n\n const field = this.root.querySelector<HTMLTextAreaElement>('.field');\n if (!field) return;\n field.value = this.getAttr('value');\n if (disabled) field.setAttribute('disabled', '');\n if (readonly) field.setAttribute('readonly', '');\n\n const emit = (event: string): void => {\n this.value = field.value;\n this.dispatchEvent(\n new CustomEvent(event, { bubbles: true, composed: true, detail: { value: field.value } }),\n );\n };\n // Bloqueia o evento nativo (composed, sem detail) para que só o\n // CustomEvent com detail chegue aos consumidores.\n field.addEventListener('input', (e) => { e.stopPropagation(); emit('input'); });\n field.addEventListener('change', (e) => { e.stopPropagation(); emit('change'); });\n }\n}\n\nexport function defineFxTextarea(): typeof FxTextarea {\n return defineElement('fx-textarea', FxTextarea);\n}\n"],"names":[],"mappings":";;;;AAYO,MAAM,cAAN,MAAM,oBAAmB,UAAU;AAAA,EAgExC,WAAoB,qBAA+B;AACjD,WAAO,CAAC,QAAQ,eAAe,YAAY,YAAY,QAAQ,aAAa,SAAS,WAAW,WAAW,OAAO;AAAA,EACpH;AAAA,EAEA,IAAI,OAAe;AACjB,UAAM,IAAI,KAAK,QAAQ,QAAQ,IAAI;AACnC,WAAO,MAAM,QAAQ,MAAM,OAAO,IAAI;AAAA,EACxC;AAAA,EACA,IAAI,KAAK,OAAe;AAAE,SAAK,aAAa,QAAQ,KAAK;AAAA,EAAG;AAAA,EAE5D,IAAI,QAAgB;AAAE,WAAO,KAAK,QAAQ,OAAO;AAAA,EAAG;AAAA,EACpD,IAAI,MAAM,OAAe;AAAE,SAAK,aAAa,SAAS,KAAK;AAAA,EAAG;AAAA,EAE3C,SAAe;AAChC,UAAM,cAAc,KAAK,QAAQ,aAAa;AAC9C,UAAM,WAAW,KAAK,QAAQ,UAAU;AACxC,UAAM,WAAW,KAAK,QAAQ,UAAU;AACxC,UAAM,OAAO,KAAK,QAAQ,MAAM;AAChC,UAAM,YAAY,KAAK,QAAQ,WAAW;AAE1C,SAAK,YAAY;AAAA,QACb,cAAc,gBAAgB,IAAI,WAAW,CAAC,MAAM,EAAE;AAAA,QACtD,OAAO,SAAS,IAAI,IAAI,CAAC,MAAM,EAAE;AAAA,QACjC,YAAY,cAAc,IAAI,SAAS,CAAC,MAAM,EAAE;AAAA,iBACvC;AAEb,UAAM,QAAQ,KAAK,KAAK,cAAmC,QAAQ;AACnE,QAAI,CAAC,MAAO;AACZ,UAAM,QAAQ,KAAK,QAAQ,OAAO;AAClC,QAAI,SAAU,OAAM,aAAa,YAAY,EAAE;AAC/C,QAAI,SAAU,OAAM,aAAa,YAAY,EAAE;AAE/C,UAAM,OAAO,CAAC,UAAwB;AACpC,WAAK,QAAQ,MAAM;AACnB,WAAK;AAAA,QACH,IAAI,YAAY,OAAO,EAAE,SAAS,MAAM,UAAU,MAAM,QAAQ,EAAE,OAAO,MAAM,MAAA,GAAS;AAAA,MAAA;AAAA,IAE5F;AAGA,UAAM,iBAAiB,SAAS,CAAC,MAAM;AAAE,QAAE,gBAAA;AAAmB,WAAK,OAAO;AAAA,IAAG,CAAC;AAC9E,UAAM,iBAAiB,UAAU,CAAC,MAAM;AAAE,QAAE,gBAAA;AAAmB,WAAK,QAAQ;AAAA,IAAG,CAAC;AAAA,EAClF;AACF;AA1GE,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;AA6GA,SAAS,mBAAsC;AACpD,SAAO,cAAc,eAAe,UAAU;AAChD;"}
1
+ {"version":3,"file":"textarea.js","sources":["../../../src/components/textarea/textarea.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-textarea> — Área de texto multilinha estilizada com os tokens do tema.\n *\n * Form-Associated Custom Element: dentro de um <form> entra no FormData\n * (via atributo `name`), recebe reset/disable do form e é alvo de\n * `<label for>`/`autofocus`. Enter continua sendo quebra de linha.\n *\n * Atributos: value, name, size (sm|md|lg), placeholder, disabled, readonly,\n * rows, maxlength, autofocus.\n * Eventos: `input`, `change` e `valueChange` (composed, detail: { value }) —\n * `input` aciona o v-model do Vue e `valueChange` o [(value)] do Angular.\n */\nexport class FxTextarea extends FxFormElement {\n static override styles = css`\n :host {\n display: inline-block;\n vertical-align: middle;\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-textarea-width, 260px);\n vertical-align: middle;\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-md) var(--fx-space-lg);\n /* Acompanha a largura definida no :host. */\n width: 100%;\n min-height: calc(var(--fx-size-md) + 40px);\n resize: vertical;\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 .field:disabled,\n .field[readonly] {\n opacity: 0.55;\n cursor: not-allowed;\n background-color: var(--fx-surface-surface-hover);\n }\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, :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, :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 :host([size='sm']) { width: var(--fx-textarea-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-textarea-width-lg, 300px); }\n :host([size='lg']) .field { min-height: calc(var(--fx-size-lg) + 80px); }\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 `;\n\n static override get observedAttributes(): string[] {\n return ['size', 'placeholder', 'disabled', 'readonly', 'rows', 'maxlength', '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(value: string) { this.setAttribute('size', value); }\n\n get value(): string { return this.getAttr('value'); }\n set value(value: string) {\n this.setAttribute('value', value);\n // `value` NÃO é observado (re-render perderia o foco): o campo interno é\n // sincronizado aqui, sem re-render, preservando foco/cursor.\n this.syncInternalControl();\n this.updateFormValue();\n }\n\n protected override render(): void {\n const placeholder = this.getAttr('placeholder');\n const readonly = this.hasAttr('readonly');\n const disabled = this.hasAttr('disabled');\n const rows = this.getAttr('rows');\n const maxlength = this.getAttr('maxlength');\n\n this.setTemplate(`<textarea class=\"field\" part=\"textarea\"\n ${placeholder ? `placeholder=\"${esc(placeholder)}\"` : ''}\n ${rows ? `rows=\"${esc(rows)}\"` : ''}\n ${maxlength ? `maxlength=\"${esc(maxlength)}\"` : ''}\n ></textarea>`);\n\n const field = this.root.querySelector<HTMLTextAreaElement>('.field');\n if (!field) return;\n field.value = this.getAttr('value');\n if (disabled) field.setAttribute('disabled', '');\n if (readonly) field.setAttribute('readonly', '');\n\n const emit = (event: string): void => {\n this.value = field.value;\n // input (v-model do Vue) + valueChange ([(value)] do Angular).\n this.emitValueEvents();\n this.dispatchEvent(\n new CustomEvent(event, { bubbles: true, composed: true, detail: { value: field.value } }),\n );\n };\n // Bloqueia o evento nativo (composed, sem detail) para que só o\n // CustomEvent com detail chegue aos consumidores.\n field.addEventListener('input', (e) => { e.stopPropagation(); emit('input'); });\n field.addEventListener('change', (e) => { e.stopPropagation(); emit('change'); });\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<HTMLTextAreaElement>('.field');\n const value = this.getAttr('value');\n if (field && field.value !== value) field.value = value;\n }\n}\n\nexport function defineFxTextarea(): typeof FxTextarea {\n return defineElement('fx-textarea', FxTextarea);\n}\n"],"names":[],"mappings":";;;;AAiBO,MAAM,cAAN,MAAM,oBAAmB,cAAc;AAAA,EAgE5C,WAAoB,qBAA+B;AACjD,WAAO,CAAC,QAAQ,eAAe,YAAY,YAAY,QAAQ,aAAa,SAAS,WAAW,WAAW,OAAO;AAAA,EACpH;AAAA,EAEA,IAAI,OAAe;AACjB,UAAM,IAAI,KAAK,QAAQ,QAAQ,IAAI;AACnC,WAAO,MAAM,QAAQ,MAAM,OAAO,IAAI;AAAA,EACxC;AAAA,EACA,IAAI,KAAK,OAAe;AAAE,SAAK,aAAa,QAAQ,KAAK;AAAA,EAAG;AAAA,EAE5D,IAAI,QAAgB;AAAE,WAAO,KAAK,QAAQ,OAAO;AAAA,EAAG;AAAA,EACpD,IAAI,MAAM,OAAe;AACvB,SAAK,aAAa,SAAS,KAAK;AAGhC,SAAK,oBAAA;AACL,SAAK,gBAAA;AAAA,EACP;AAAA,EAEmB,SAAe;AAChC,UAAM,cAAc,KAAK,QAAQ,aAAa;AAC9C,UAAM,WAAW,KAAK,QAAQ,UAAU;AACxC,UAAM,WAAW,KAAK,QAAQ,UAAU;AACxC,UAAM,OAAO,KAAK,QAAQ,MAAM;AAChC,UAAM,YAAY,KAAK,QAAQ,WAAW;AAE1C,SAAK,YAAY;AAAA,QACb,cAAc,gBAAgB,IAAI,WAAW,CAAC,MAAM,EAAE;AAAA,QACtD,OAAO,SAAS,IAAI,IAAI,CAAC,MAAM,EAAE;AAAA,QACjC,YAAY,cAAc,IAAI,SAAS,CAAC,MAAM,EAAE;AAAA,iBACvC;AAEb,UAAM,QAAQ,KAAK,KAAK,cAAmC,QAAQ;AACnE,QAAI,CAAC,MAAO;AACZ,UAAM,QAAQ,KAAK,QAAQ,OAAO;AAClC,QAAI,SAAU,OAAM,aAAa,YAAY,EAAE;AAC/C,QAAI,SAAU,OAAM,aAAa,YAAY,EAAE;AAE/C,UAAM,OAAO,CAAC,UAAwB;AACpC,WAAK,QAAQ,MAAM;AAEnB,WAAK,gBAAA;AACL,WAAK;AAAA,QACH,IAAI,YAAY,OAAO,EAAE,SAAS,MAAM,UAAU,MAAM,QAAQ,EAAE,OAAO,MAAM,MAAA,GAAS;AAAA,MAAA;AAAA,IAE5F;AAGA,UAAM,iBAAiB,SAAS,CAAC,MAAM;AAAE,QAAE,gBAAA;AAAmB,WAAK,OAAO;AAAA,IAAG,CAAC;AAC9E,UAAM,iBAAiB,UAAU,CAAC,MAAM;AAAE,QAAE,gBAAA;AAAmB,WAAK,QAAQ;AAAA,IAAG,CAAC;AAGhF,SAAK,gBAAA;AAAA,EACP;AAAA;AAAA,EAGmB,sBAA4B;AAC7C,UAAM,QAAQ,KAAK,KAAK,cAAmC,QAAQ;AACnE,UAAM,QAAQ,KAAK,QAAQ,OAAO;AAClC,QAAI,SAAS,MAAM,UAAU,aAAa,QAAQ;AAAA,EACpD;AACF;AA5HE,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;AA+HA,SAAS,mBAAsC;AACpD,SAAO,cAAc,eAAe,UAAU;AAChD;"}
@@ -17,14 +17,14 @@ export declare class FxToast extends HTMLElement {
17
17
  /**
18
18
  * Exibe a barra de contagem regressiva na base do card.
19
19
  *
20
- * A barra esvazia da esquerda para a direita durante exatamente `duration`
21
- * e usa a cor do `kind`. Com `duration="0"` (toast fixo) a barra é
22
- * omitida — não há tempo restante a representar.
20
+ * A barra esvazia durante exatamente `duration` e usa a cor do `kind`.
21
+ * Com `duration="0"` (toast fixo) a barra é omitida — não há tempo restante.
23
22
  *
24
23
  * - `'always'` (ou atributo vazio): barra sempre correndo.
25
- * - `'hover'`: a barra só corre enquanto o cursor estiver sobre o toast;
26
- * ao sair, a animação pausa, então dá tempo de ler a mensagem.
27
24
  * - `'paused'`: barra visível, porém parada.
25
+ * - `'hover'`: **compatibilidade** — equivale a `'always'`. O hover agora
26
+ * pausa a contagem (timer + barra) em qualquer modo, então a barra "só
27
+ * correr sob o cursor" deixou de fazer sentido.
28
28
  */
29
29
  get progress(): 'always' | 'hover' | 'paused' | undefined;
30
30
  set progress(value: 'always' | 'hover' | 'paused' | undefined);
@@ -47,6 +47,18 @@ export declare class FxToast extends HTMLElement {
47
47
  get mode(): 'light' | 'dark' | undefined;
48
48
  set mode(value: 'light' | 'dark' | undefined);
49
49
  private timer;
50
+ /** Tempo restante (ms) quando a contagem está pausada no hover/foco. */
51
+ private remaining;
52
+ /** Instante em que o timer atual começou (para descontar o tempo decorrido). */
53
+ private startedAt;
54
+ /** Cursor sobre o toast: não retoma a contagem no focusout. */
55
+ private hovering;
56
+ /** Já saiu do DOM — impede retomar a contagem. */
57
+ private disposed;
58
+ /** Existe contagem regressiva (duration > 0) — toasts fixos nunca retomam. */
59
+ private counting;
60
+ /** Listeners de pausa/retomada registrados uma única vez. */
61
+ private wired;
50
62
  constructor();
51
63
  connectedCallback(): void;
52
64
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"toast.d.ts","sourceRoot":"","sources":["../../../src/components/toast/toast.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,QAAA,MAAM,SAAS,kGAGL,CAAC;AACX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AACvD,KAAK,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;AAoI1D,qBAAa,OAAQ,SAAQ,WAAW;;IACtC,MAAM,KAAK,kBAAkB,aAE5B;IAED;;;;;;;;;;;OAWG;IACH,IAAI,QAAQ,IAAI,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAIxD;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,EAG5D;IAED;;;;;;;;;;OAUG;IACH,IAAI,cAAc,IAAI,MAAM,GAAG,OAAO,CAErC;IACD,IAAI,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,EAEzC;IAED,oEAAoE;IACpE,OAAO,KAAK,YAAY,GAIvB;IAED,qFAAqF;IACrF,IAAI,IAAI,IAAI,OAAO,GAAG,MAAM,GAAG,SAAS,CAGvC;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,EAG3C;IAED,OAAO,CAAC,KAAK,CAA8C;;IAO3D,iBAAiB;IAWjB;;;;;;;OAOG;IACH,oBAAoB;IAMpB,wBAAwB,CAAC,IAAI,EAAE,MAAM;IA6CrC,OAAO;CAqCR;AA8BD,MAAM,WAAW,YAAY;IAC3B,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;IACzC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,0EAA0E;IAC1E,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CACzB;AAED,cAAM,QAAQ;IACZ,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,GAAG,CAA8B;IAEzC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,YAAY,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,MAAM;IA0BxG,KAAK,CAAC,EAAE,EAAE,MAAM;IAKhB,OAAO,GAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,YAAY,YAAmC;IACrF,KAAK,GAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,YAAY,YAAiC;IACjF,IAAI,GAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,YAAY,YAAgC;IAC/E,OAAO,GAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,YAAY,YAAmC;CACtF;AAED,eAAO,MAAM,UAAU,UAAiB,CAAC;AAIzC,wBAAgB,aAAa,SAE5B"}
1
+ {"version":3,"file":"toast.d.ts","sourceRoot":"","sources":["../../../src/components/toast/toast.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,QAAA,MAAM,SAAS,kGAGL,CAAC;AACX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AACvD,KAAK,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;AA2K1D,qBAAa,OAAQ,SAAQ,WAAW;;IACtC,MAAM,KAAK,kBAAkB,aAE5B;IAED;;;;;;;;;;;OAWG;IACH,IAAI,QAAQ,IAAI,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAIxD;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,EAG5D;IAED;;;;;;;;;;OAUG;IACH,IAAI,cAAc,IAAI,MAAM,GAAG,OAAO,CAErC;IACD,IAAI,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,EAEzC;IAED,oEAAoE;IACpE,OAAO,KAAK,YAAY,GAIvB;IAED,qFAAqF;IACrF,IAAI,IAAI,IAAI,OAAO,GAAG,MAAM,GAAG,SAAS,CAGvC;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,EAG3C;IAED,OAAO,CAAC,KAAK,CAA8C;IAC3D,wEAAwE;IACxE,OAAO,CAAC,SAAS,CAAK;IACtB,gFAAgF;IAChF,OAAO,CAAC,SAAS,CAAK;IACtB,+DAA+D;IAC/D,OAAO,CAAC,QAAQ,CAAS;IACzB,kDAAkD;IAClD,OAAO,CAAC,QAAQ,CAAS;IACzB,8EAA8E;IAC9E,OAAO,CAAC,QAAQ,CAAS;IACzB,6DAA6D;IAC7D,OAAO,CAAC,KAAK,CAAS;;IAOtB,iBAAiB;IA8DjB;;;;;;;OAOG;IACH,oBAAoB;IAOpB,wBAAwB,CAAC,IAAI,EAAE,MAAM;IAsDrC,OAAO;CAqFR;AA8BD,MAAM,WAAW,YAAY;IAC3B,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;IACzC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,0EAA0E;IAC1E,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CACzB;AAED,cAAM,QAAQ;IACZ,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,GAAG,CAA8B;IAEzC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,YAAY,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,MAAM;IA0BxG,KAAK,CAAC,EAAE,EAAE,MAAM;IAKhB,OAAO,GAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,YAAY,YAAmC;IACrF,KAAK,GAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,YAAY,YAAiC;IACjF,IAAI,GAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,YAAY,YAAgC;IAC/E,OAAO,GAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,YAAY,YAAmC;CACtF;AAED,eAAO,MAAM,UAAU,UAAiB,CAAC;AAIzC,wBAAgB,aAAa,SAE5B"}
@@ -4,7 +4,7 @@ var __typeError = (msg) => {
4
4
  var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
5
5
  var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
6
6
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
7
- var _FxToast_instances, syncProgress_fn, render_fn;
7
+ var _FxToast_instances, wirePause_fn, startTimer_fn, pauseTimer_fn, resumeTimer_fn, syncProgress_fn, render_fn, syncMore_fn;
8
8
  import { __fenixPreload } from "../../_virtual/preload-helper.js";
9
9
  const REMOVED_EVENT = "fx-toast:removed";
10
10
  const KIND_COLOR = {
@@ -19,6 +19,7 @@ const KIND_ICON = {
19
19
  warning: "!",
20
20
  info: "i"
21
21
  };
22
+ const COPY_SVG = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="12" height="12" rx="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>';
22
23
  const CARD_CSS = `
23
24
  :host { display: contents; pointer-events: auto; }
24
25
  /* Modo explícito: sobrepõe os tokens herdados do tema global SOMENTE neste card. */
@@ -52,8 +53,11 @@ const CARD_CSS = `
52
53
  user-select: none;
53
54
  }
54
55
  .card {
55
- display: flex; align-items: flex-start; gap: 10px;
56
+ display: flex; align-items: center; gap: 10px;
56
57
  min-width: 260px; max-width: 360px;
58
+ /* Altura mínima idêntica à de um toast COM título: sem título o card não
59
+ fica "achatado" e o conjunto mantém o mesmo ritmo visual. */
60
+ min-height: 56px;
57
61
  background: var(--fx-surface-background, #fff);
58
62
  color: var(--fx-text-default, #1e293b);
59
63
  border: 1px solid var(--fx-border-default, #e2e8f0);
@@ -72,16 +76,45 @@ const CARD_CSS = `
72
76
  color: #fff;
73
77
  display: flex; align-items: center; justify-content: center;
74
78
  font-size: 12px; font-weight: 700;
75
- flex: none; margin-top: 2px;
79
+ flex: none; align-self: center;
76
80
  user-select: none;
77
81
  }
78
- .body { flex: 1; min-width: 0; }
82
+ .body { flex: 1; min-width: 0; align-self: center; }
79
83
  .title { font-weight: 600; font-size: 14px; }
80
- .msg { font-size: calc(var(--fx-font-size, 14px) - 2px); color: var(--fx-text-muted, #64748b); margin-top: 2px; word-break: break-word; white-space: pre-line; }
81
- .close {
84
+ .msg {
85
+ font-size: calc(var(--fx-font-size, 14px) - 2px);
86
+ color: var(--fx-text-muted, #64748b);
87
+ margin-top: 2px;
88
+ word-break: break-word; white-space: pre-line;
89
+ /* Mensagens longas mantêm o card no tamanho padrão e terminam em "…";
90
+ "Ver mais" expande o toast (sem perder o restante do texto). */
91
+ display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
92
+ line-clamp: 2; overflow: hidden;
93
+ }
94
+ .card.expanded .msg {
95
+ display: block; overflow: visible;
96
+ -webkit-line-clamp: unset; line-clamp: unset;
97
+ }
98
+ .more {
99
+ all: unset; cursor: pointer; display: inline-block;
100
+ margin-top: 4px; padding: 2px 0;
101
+ font-size: 11px; font-weight: 600; color: var(--kind);
102
+ }
103
+ .more[hidden] { display: none; }
104
+ .more:hover { text-decoration: underline; }
105
+ .actions {
106
+ display: flex; align-items: center; gap: 2px;
107
+ flex: none; align-self: flex-start;
108
+ }
109
+ .copy, .close {
82
110
  all: unset; cursor: pointer; flex: none; line-height: 1;
83
111
  color: var(--fx-text-muted, #64748b); font-size: 15px; padding: 2px 4px; border-radius: 4px;
84
112
  }
113
+ .copy[hidden] { display: none; }
114
+ .copy { font-size: 14px; display: inline-flex; align-items: center; }
115
+ .copy svg { display: block; }
116
+ .copy.done { color: var(--fx-color-success, #10b981); }
117
+ .copy:hover { color: var(--fx-color-primary, #6366f1); background: var(--fx-surface-surface-hover, rgba(0,0,0,.05)); }
85
118
  .close:hover { color: var(--fx-color-danger, #f43f5e); background: var(--fx-surface-surface-hover, rgba(0,0,0,.05)); }
86
119
 
87
120
  /* --- Barra de contagem regressiva (atributo progress) ---
@@ -110,9 +143,12 @@ const CARD_CSS = `
110
143
  will-change: transform;
111
144
  }
112
145
  .progress.run { animation: fx-toast-drain var(--toast-duration, 4000ms) linear forwards; }
113
- /* No modo hover a animação fica parada até o cursor/foco entrar no card. */
146
+ /* Passar o mouse (ou focar) no toast pausa a contagem: a barra e o timer
147
+ congelam juntos até o cursor sair — tempo de ler a mensagem inteira.
148
+ progress="hover" é mantido por compatibilidade e se comporta como
149
+ "always" (o hover já pausa a contagem). */
114
150
  .card:hover .progress.run,
115
- .card:focus-within .progress.run { animation-play-state: running; }
151
+ .card:focus-within .progress.run { animation-play-state: paused; }
116
152
  @keyframes fx-toast-drain {
117
153
  from { transform: scaleX(1); }
118
154
  to { transform: scaleX(0); }
@@ -131,6 +167,12 @@ class FxToast extends HTMLElement {
131
167
  super();
132
168
  __privateAdd(this, _FxToast_instances);
133
169
  this.timer = null;
170
+ this.remaining = 0;
171
+ this.startedAt = 0;
172
+ this.hovering = false;
173
+ this.disposed = false;
174
+ this.counting = false;
175
+ this.wired = false;
134
176
  this.attachShadow({ mode: "open" });
135
177
  }
136
178
  static get observedAttributes() {
@@ -139,19 +181,19 @@ class FxToast extends HTMLElement {
139
181
  /**
140
182
  * Exibe a barra de contagem regressiva na base do card.
141
183
  *
142
- * A barra esvazia da esquerda para a direita durante exatamente `duration`
143
- * e usa a cor do `kind`. Com `duration="0"` (toast fixo) a barra é
144
- * omitida — não há tempo restante a representar.
184
+ * A barra esvazia durante exatamente `duration` e usa a cor do `kind`.
185
+ * Com `duration="0"` (toast fixo) a barra é omitida — não há tempo restante.
145
186
  *
146
187
  * - `'always'` (ou atributo vazio): barra sempre correndo.
147
- * - `'hover'`: a barra só corre enquanto o cursor estiver sobre o toast;
148
- * ao sair, a animação pausa, então dá tempo de ler a mensagem.
149
188
  * - `'paused'`: barra visível, porém parada.
189
+ * - `'hover'`: **compatibilidade** — equivale a `'always'`. O hover agora
190
+ * pausa a contagem (timer + barra) em qualquer modo, então a barra "só
191
+ * correr sob o cursor" deixou de fazer sentido.
150
192
  */
151
193
  get progress() {
152
194
  if (!this.hasAttribute("progress")) return void 0;
153
195
  const v = this.getAttribute("progress");
154
- return v === "hover" || v === "paused" ? v : "always";
196
+ return v === "paused" ? "paused" : "always";
155
197
  }
156
198
  set progress(value) {
157
199
  if (value) this.setAttribute("progress", value);
@@ -191,11 +233,13 @@ class FxToast extends HTMLElement {
191
233
  }
192
234
  connectedCallback() {
193
235
  if (!this.shadowRoot.firstChild) __privateMethod(this, _FxToast_instances, render_fn).call(this);
236
+ this.disposed = false;
237
+ __privateMethod(this, _FxToast_instances, wirePause_fn).call(this);
194
238
  const safe = this.safeDuration;
195
- if (safe > 0 && !this.timer) {
196
- this.timer = setTimeout(() => this.dismiss(), safe);
197
- }
239
+ this.counting = safe > 0;
240
+ if (safe > 0 && !this.timer) __privateMethod(this, _FxToast_instances, startTimer_fn).call(this, safe);
198
241
  __privateMethod(this, _FxToast_instances, syncProgress_fn).call(this);
242
+ __privateMethod(this, _FxToast_instances, syncMore_fn).call(this);
199
243
  }
200
244
  /**
201
245
  * Limpa recursos ao sair do DOM e avisa a API imperativa (`FenixToast`).
@@ -206,11 +250,21 @@ class FxToast extends HTMLElement {
206
250
  * sem limite em uso prolongado.
207
251
  */
208
252
  disconnectedCallback() {
253
+ this.disposed = true;
209
254
  if (this.timer) clearTimeout(this.timer);
210
255
  this.timer = null;
211
256
  this.dispatchEvent(new CustomEvent(REMOVED_EVENT, { bubbles: false, composed: false }));
212
257
  }
213
258
  attributeChangedCallback(name) {
259
+ if (name === "duration") {
260
+ if (this.timer) {
261
+ clearTimeout(this.timer);
262
+ this.timer = null;
263
+ }
264
+ const safe = this.safeDuration;
265
+ this.counting = safe > 0;
266
+ if (safe > 0 && this.isConnected && !this.disposed) __privateMethod(this, _FxToast_instances, startTimer_fn).call(this, safe);
267
+ }
214
268
  if (name === "duration" || name === "progress" || name === "progress-origin" || name === "kind") {
215
269
  __privateMethod(this, _FxToast_instances, syncProgress_fn).call(this);
216
270
  }
@@ -228,6 +282,49 @@ class FxToast extends HTMLElement {
228
282
  }
229
283
  }
230
284
  _FxToast_instances = new WeakSet();
285
+ /**
286
+ * Pausa a contagem enquanto o cursor (ou o foco) estiver no toast e retoma
287
+ * de onde parou ao sair — assim uma mensagem longa pode ser lida inteira.
288
+ * A barra de progresso pausa junto (regra CSS de :hover/:focus-within).
289
+ */
290
+ wirePause_fn = function() {
291
+ if (this.wired) return;
292
+ this.wired = true;
293
+ this.addEventListener("mouseenter", () => {
294
+ this.hovering = true;
295
+ __privateMethod(this, _FxToast_instances, pauseTimer_fn).call(this);
296
+ });
297
+ this.addEventListener("mouseleave", () => {
298
+ this.hovering = false;
299
+ __privateMethod(this, _FxToast_instances, resumeTimer_fn).call(this);
300
+ });
301
+ this.addEventListener("focusin", () => __privateMethod(this, _FxToast_instances, pauseTimer_fn).call(this));
302
+ this.addEventListener("focusout", () => {
303
+ if (!this.hovering) __privateMethod(this, _FxToast_instances, resumeTimer_fn).call(this);
304
+ });
305
+ };
306
+ startTimer_fn = function(ms) {
307
+ this.remaining = ms;
308
+ this.startedAt = Date.now();
309
+ this.timer = setTimeout(() => {
310
+ this.timer = null;
311
+ this.dismiss();
312
+ }, ms);
313
+ };
314
+ pauseTimer_fn = function() {
315
+ if (!this.timer) return;
316
+ clearTimeout(this.timer);
317
+ this.timer = null;
318
+ this.remaining = Math.max(0, this.remaining - (Date.now() - this.startedAt));
319
+ };
320
+ resumeTimer_fn = function() {
321
+ if (!this.counting || this.timer || this.disposed) return;
322
+ if (this.remaining <= 0) {
323
+ this.dismiss();
324
+ return;
325
+ }
326
+ __privateMethod(this, _FxToast_instances, startTimer_fn).call(this, this.remaining);
327
+ };
231
328
  /**
232
329
  * Aplica (ou remove) a barra de contagem regressiva.
233
330
  *
@@ -272,17 +369,60 @@ render_fn = function() {
272
369
  <div class="card" style="--kind:${color}" role="status" part="card">
273
370
  <span class="icon${isFxIcon ? " fx-icon" : ""}">${iconContent}</span>
274
371
  <div class="body">
275
- ${title ? '<div class="title"></div>' : ""}
276
- ${msg ? '<div class="msg"></div>' : ""}
372
+ ${title ? '<div class="title" part="title"></div>' : ""}
373
+ ${msg ? '<div class="msg" part="message"></div>' : ""}
374
+ ${msg ? '<button class="more" part="more" hidden>Ver mais</button>' : ""}
375
+ </div>
376
+ <div class="actions">
377
+ ${msg ? `<button class="copy" part="copy" hidden aria-label="Copiar mensagem" title="Copiar mensagem">${COPY_SVG}</button>` : ""}
378
+ <button class="close" aria-label="Fechar" title="Fechar">✕</button>
277
379
  </div>
278
- <button class="close" aria-label="Fechar">✕</button>
279
380
  </div>`;
280
381
  const t = this.shadowRoot.querySelector(".title");
281
382
  const m = this.shadowRoot.querySelector(".msg");
282
383
  if (t) t.textContent = title;
283
- if (m) m.textContent = msg;
384
+ if (m) {
385
+ m.textContent = msg;
386
+ m.title = msg;
387
+ }
388
+ const card = this.shadowRoot.querySelector(".card");
389
+ const more = this.shadowRoot.querySelector(".more");
390
+ const copy = this.shadowRoot.querySelector(".copy");
391
+ more?.addEventListener("click", () => {
392
+ const expanded = card.classList.toggle("expanded");
393
+ more.textContent = expanded ? "Ver menos" : "Ver mais";
394
+ if (copy) copy.hidden = !expanded;
395
+ if (expanded) __privateMethod(this, _FxToast_instances, pauseTimer_fn).call(this);
396
+ });
397
+ copy?.addEventListener("click", (e) => {
398
+ const btn = e.currentTarget;
399
+ const text = [title, msg].filter(Boolean).join("\n");
400
+ navigator.clipboard?.writeText(text).catch(() => {
401
+ });
402
+ btn.classList.add("done");
403
+ btn.title = "Copiado!";
404
+ setTimeout(() => {
405
+ btn.classList.remove("done");
406
+ btn.title = "Copiar mensagem";
407
+ }, 1200);
408
+ });
284
409
  this.shadowRoot.querySelector(".close").addEventListener("click", () => this.dismiss());
285
410
  };
411
+ /**
412
+ * Mostra "Ver mais" somente quando a mensagem passou de 2 linhas.
413
+ * Sem layout (jsdom) scrollHeight/clientHeight são 0 → fica oculto.
414
+ */
415
+ syncMore_fn = function() {
416
+ const card = this.shadowRoot?.querySelector(".card");
417
+ const more = this.shadowRoot?.querySelector(".more");
418
+ const msg = this.shadowRoot?.querySelector(".msg");
419
+ if (!card || !more || !msg) return;
420
+ if (card.classList.contains("expanded")) {
421
+ more.hidden = false;
422
+ return;
423
+ }
424
+ more.hidden = !(msg.scrollHeight - msg.clientHeight > 1);
425
+ };
286
426
  function regionFor(position) {
287
427
  let region = document.querySelector(`[data-fx-toast-region="${position}"]`);
288
428
  if (!region) {