@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
@@ -1 +1 @@
1
- {"version":3,"file":"dialog.js","sources":["../../../src/components/dialog/dialog.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-dialog> — Janela modal com overlay, ESC para fechar e foco preso.\n *\n * Atributos: open, size (sm|md|lg), heading (título).\n * Slots: padrão (conteúdo), footer (botões de ação).\n * Eventos: `open`, `close` (composed).\n */\nexport class FxDialog extends FxElement {\n static override styles = css`\n :host { display: contents; }\n .overlay {\n position: fixed;\n inset: 0;\n background: color-mix(in srgb, var(--fx-text-default, #000) 45%, transparent);\n backdrop-filter: blur(3px);\n -webkit-backdrop-filter: blur(3px);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: var(--fx-z-modal, 1100);\n animation: fade var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n .overlay[hidden] { display: none; }\n @keyframes fade { from { opacity: 0; } to { opacity: 1; } }\n .dialog {\n background: var(--fx-surface-background);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-lg);\n box-shadow: var(--fx-shadow-xl);\n /* Largura customizável por instância: defina --fx-dialog-width no elemento. */\n width: min(var(--fx-dialog-width, 560px), calc(100vw - 32px));\n max-height: calc(100vh - 64px);\n display: flex;\n flex-direction: column;\n font-family: var(--fx-font-family);\n animation: pop var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n @keyframes pop { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } }\n :host([size='sm']) .dialog { width: min(var(--fx-dialog-width, 400px), calc(100vw - 32px)); }\n :host([size='lg']) .dialog { width: min(var(--fx-dialog-width, 800px), calc(100vw - 32px)); }\n /* Mobile: dialog ocupa praticamente toda a largura da tela. */\n @media (max-width: 640px) {\n .dialog, :host([size='sm']) .dialog, :host([size='lg']) .dialog {\n width: calc(100vw - 16px);\n max-width: calc(100vw - 16px);\n }\n }\n header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--fx-space-lg) var(--fx-space-xl);\n border-bottom: 1px solid var(--fx-border-default);\n }\n h2 {\n margin: 0;\n font-size: calc(var(--fx-font-size) + 2px);\n font-weight: 600;\n color: var(--fx-text-default);\n }\n .close {\n border: none;\n background: transparent;\n color: var(--fx-text-muted);\n font-size: calc(var(--fx-font-size) + 6px);\n cursor: pointer;\n line-height: 1;\n padding: 0 var(--fx-space-xs);\n border-radius: var(--fx-radius-full);\n }\n .close:hover { color: var(--fx-color-danger); }\n .body {\n padding: var(--fx-space-lg) var(--fx-space-xl);\n overflow-y: auto;\n color: var(--fx-text-default);\n font-size: var(--fx-font-size);\n }\n footer {\n padding: var(--fx-space-md) var(--fx-space-xl);\n border-top: 1px solid var(--fx-border-default);\n display: flex;\n justify-content: flex-end;\n gap: var(--fx-space-sm);\n }\n footer[hidden] { display: none; }\n `;\n\n static override get observedAttributes(): string[] {\n return ['open', 'size', 'heading'];\n }\n\n get open(): boolean { return this.hasAttr('open'); }\n set open(value: boolean) { this.toggleAttr('open', value); }\n\n protected override render(): void {\n const isOpen = this.open;\n const heading = this.getAttr('heading');\n\n this.setTemplate(`\n <div class=\"overlay\" part=\"overlay\" ${isOpen ? '' : 'hidden'}>\n <div class=\"dialog\" role=\"dialog\" aria-modal=\"true\" aria-label=\"${esc(heading)}\" tabindex=\"-1\">\n <header>\n <h2>${esc(heading)}</h2>\n <button type=\"button\" class=\"close\" part=\"close\" aria-label=\"Fechar\">×</button>\n </header>\n <div class=\"body\"><slot></slot></div>\n <footer part=\"footer\"><slot name=\"footer\"></slot></footer>\n </div>\n </div>\n `);\n\n if (!isOpen) return;\n\n // Evita listeners duplicados em re-renders enquanto aberto\n // (ex.: mudança de heading/size com o dialog aberto sobrescreveria\n // o _cleanup anterior, deixando listeners de document órfãos).\n this._cleanup?.();\n this._cleanup = undefined;\n\n const overlay = this.root.querySelector<HTMLElement>('.overlay');\n const closeBtn = this.root.querySelector<HTMLButtonElement>('.close');\n const close = (): void => {\n this.open = false;\n this._cleanup?.();\n this._cleanup = undefined;\n this._restoreFocus();\n this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }));\n };\n overlay?.addEventListener('click', (e) => { if (e.target === overlay) close(); });\n closeBtn?.addEventListener('click', close);\n\n // ESC fecha + foco preso no diálogo\n const onKey = (e: KeyboardEvent): void => {\n if (e.key === 'Escape') { e.stopPropagation(); close(); return; }\n if (e.key === 'Tab') this._trapFocus(e);\n };\n document.addEventListener('keydown', onKey);\n this._cleanup = () => document.removeEventListener('keydown', onKey);\n\n requestAnimationFrame(() => {\n this._previouslyFocused = document.activeElement as HTMLElement | null;\n this.root.querySelector<HTMLElement>('.dialog')?.focus();\n });\n this.dispatchEvent(new CustomEvent('open', { bubbles: true, composed: true }));\n }\n\n private _cleanup?: () => void;\n private _previouslyFocused: HTMLElement | null = null;\n\n /** Mantém o foco dentro do modal (WCAG 2.4.3 / 2.1.2). */\n private _trapFocus(e: KeyboardEvent): void {\n const dialog = this.root.querySelector<HTMLElement>('.dialog');\n if (!dialog) return;\n const focusables = dialog.querySelectorAll<HTMLElement>(\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])',\n );\n if (!focusables.length) return;\n const first = focusables[0];\n const last = focusables[focusables.length - 1];\n const active = this.root.activeElement;\n if (e.shiftKey && (active === first || active === dialog)) {\n e.preventDefault();\n last.focus();\n } else if (!e.shiftKey && active === last) {\n e.preventDefault();\n first.focus();\n }\n }\n\n private _restoreFocus(): void {\n if (this._previouslyFocused?.isConnected) this._previouslyFocused.focus();\n this._previouslyFocused = null;\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this._cleanup?.();\n this._cleanup = undefined;\n this._restoreFocus();\n }\n}\n\nexport function defineFxDialog(): typeof FxDialog {\n return defineElement('fx-dialog', FxDialog);\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dialog.js","sources":["../../../src/components/dialog/dialog.ts"],"sourcesContent":["import { FxElement } from '../../core/base';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\nimport { esc } from '../../core/sanitize';\nimport {\n firstLightFocusable,\n isTopmostModal,\n lockBodyScroll,\n modalFocusables,\n registerModalClose,\n registerModalOpen,\n unlockBodyScroll,\n} from '../../core/modal';\n\n/**\n * <fx-dialog> — Janela modal com overlay, ESC para fechar e foco preso.\n *\n * Atributos: open, size (sm|md|lg), heading (título).\n * Slots: padrão (conteúdo), footer (botões de ação).\n * Eventos: `open`, `close` (composed).\n */\nexport class FxDialog extends FxElement {\n static override styles = css`\n :host { display: contents; }\n .overlay {\n position: fixed;\n inset: 0;\n background: color-mix(in srgb, var(--fx-text-default, #000) 45%, transparent);\n backdrop-filter: blur(3px);\n -webkit-backdrop-filter: blur(3px);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: var(--fx-z-modal, 1100);\n animation: fade var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n .overlay[hidden] { display: none; }\n @keyframes fade { from { opacity: 0; } to { opacity: 1; } }\n .dialog {\n background: var(--fx-surface-background);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-lg);\n box-shadow: var(--fx-shadow-xl);\n /* Largura customizável por instância: defina --fx-dialog-width no elemento. */\n width: min(var(--fx-dialog-width, 560px), calc(100vw - 32px));\n max-height: calc(100vh - 64px);\n display: flex;\n flex-direction: column;\n font-family: var(--fx-font-family);\n animation: pop var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n @keyframes pop { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } }\n :host([size='sm']) .dialog { width: min(var(--fx-dialog-width, 400px), calc(100vw - 32px)); }\n :host([size='lg']) .dialog { width: min(var(--fx-dialog-width, 800px), calc(100vw - 32px)); }\n /* Mobile: dialog ocupa praticamente toda a largura da tela. */\n @media (max-width: 640px) {\n .dialog, :host([size='sm']) .dialog, :host([size='lg']) .dialog {\n width: calc(100vw - 16px);\n max-width: calc(100vw - 16px);\n }\n }\n header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--fx-space-lg) var(--fx-space-xl);\n border-bottom: 1px solid var(--fx-border-default);\n }\n h2 {\n margin: 0;\n font-size: calc(var(--fx-font-size) + 2px);\n font-weight: 600;\n color: var(--fx-text-default);\n }\n .close {\n border: none;\n background: transparent;\n color: var(--fx-text-muted);\n font-size: calc(var(--fx-font-size) + 6px);\n cursor: pointer;\n line-height: 1;\n padding: 0 var(--fx-space-xs);\n border-radius: var(--fx-radius-full);\n }\n .close:hover { color: var(--fx-color-danger); }\n .body {\n padding: var(--fx-space-lg) var(--fx-space-xl);\n overflow-y: auto;\n color: var(--fx-text-default);\n font-size: var(--fx-font-size);\n }\n footer {\n padding: var(--fx-space-md) var(--fx-space-xl);\n border-top: 1px solid var(--fx-border-default);\n display: flex;\n justify-content: flex-end;\n gap: var(--fx-space-sm);\n }\n footer[hidden] { display: none; }\n `;\n\n static override get observedAttributes(): string[] {\n return ['open', 'size', 'heading'];\n }\n\n get open(): boolean { return this.hasAttr('open'); }\n set open(value: boolean) { this.toggleAttr('open', value); }\n\n protected override render(): void {\n const isOpen = this.open;\n const heading = this.getAttr('heading');\n\n if (!isOpen) {\n // Fechado por atributo (v-model do Vue, remoção externa): desfaz os\n // efeitos colaterais de abertura e remove o overlay do shadow.\n this._release();\n this.setTemplate('');\n return;\n }\n\n this.setTemplate(`\n <div class=\"overlay\" part=\"overlay\" ${isOpen ? '' : 'hidden'}>\n <div class=\"dialog\" role=\"dialog\" aria-modal=\"true\" aria-label=\"${esc(heading)}\" tabindex=\"-1\">\n <header>\n <h2>${esc(heading)}</h2>\n <button type=\"button\" class=\"close\" part=\"close\" aria-label=\"Fechar\">×</button>\n </header>\n <div class=\"body\"><slot></slot></div>\n <footer part=\"footer\"><slot name=\"footer\"></slot></footer>\n </div>\n </div>\n `);\n\n if (!isOpen) return;\n\n // Evita listeners duplicados em re-renders enquanto aberto\n // (ex.: mudança de heading/size com o dialog aberto sobrescreveria\n // o _cleanup anterior, deixando listeners de document órfãos).\n this._cleanup?.();\n this._cleanup = undefined;\n\n const overlay = this.root.querySelector<HTMLElement>('.overlay');\n const closeBtn = this.root.querySelector<HTMLButtonElement>('.close');\n const close = (): void => {\n if (!this.open) return;\n this.open = false; // attributeChanged → render(false) → _release()\n this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }));\n };\n overlay?.addEventListener('click', (e) => { if (e.target === overlay) close(); });\n closeBtn?.addEventListener('click', close);\n\n // ESC fecha apenas o modal do topo + foco preso no diálogo.\n const onKey = (e: KeyboardEvent): void => {\n if (e.key === 'Escape') {\n if (!isTopmostModal(this)) return;\n e.stopPropagation();\n close();\n return;\n }\n if (e.key === 'Tab') this._trapFocus(e);\n };\n document.addEventListener('keydown', onKey);\n this._cleanup = () => document.removeEventListener('keydown', onKey);\n\n const alreadyOpen = this._locked;\n if (!this._locked) {\n lockBodyScroll();\n registerModalOpen(this);\n this._locked = true;\n }\n\n if (!alreadyOpen) {\n requestAnimationFrame(() => {\n if (!this.open) return;\n this._previouslyFocused = document.activeElement as HTMLElement | null;\n // O conteúdo do consumidor (light DOM/slotted) tem prioridade; o painel\n // é apenas fallback quando não há nenhum campo focável no slot.\n const first = firstLightFocusable(this) ?? this.root.querySelector<HTMLElement>('.dialog');\n first?.focus();\n });\n }\n this.dispatchEvent(new CustomEvent('open', { bubbles: true, composed: true }));\n }\n\n private _cleanup?: () => void;\n private _previouslyFocused: HTMLElement | null = null;\n private _locked = false;\n\n /** Desfaz listeners, foco e scroll lock ao fechar/desconectar. */\n private _release(): void {\n this._cleanup?.();\n this._cleanup = undefined;\n registerModalClose(this);\n if (this._locked) {\n unlockBodyScroll();\n this._locked = false;\n }\n this._restoreFocus();\n }\n\n /** Mantém o foco dentro do modal (WCAG 2.4.3 / 2.1.2), incluindo o slot. */\n private _trapFocus(e: KeyboardEvent): void {\n const dialog = this.root.querySelector<HTMLElement>('.dialog');\n if (!dialog) return;\n const focusables = modalFocusables(dialog, this);\n if (!focusables.length) return;\n const first = focusables[0];\n const last = focusables[focusables.length - 1];\n // Com foco no light DOM, `shadowRoot.activeElement` é null: usa o\n // activeElement do documento como fallback.\n const active = this.root.activeElement ?? document.activeElement;\n if (e.shiftKey && (active === first || active === dialog || active === this)) {\n e.preventDefault();\n last?.focus();\n } else if (!e.shiftKey && active === last) {\n e.preventDefault();\n first?.focus();\n }\n }\n\n private _restoreFocus(): void {\n if (this._previouslyFocused?.isConnected) this._previouslyFocused.focus();\n this._previouslyFocused = null;\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this._release();\n }\n}\n\nexport function defineFxDialog(): typeof FxDialog {\n return defineElement('fx-dialog', FxDialog);\n}\n"],"names":[],"mappings":"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"}
@@ -19,12 +19,16 @@ export declare class FxDrawer extends FxElement {
19
19
  get position(): string;
20
20
  set position(v: string);
21
21
  protected render(): void;
22
+ /** Desfaz scroll lock e restaura o foco ao fechar/desconectar. */
23
+ private _release;
22
24
  protected connectedCallback(): void;
23
25
  protected disconnectedCallback(): void;
24
26
  private _onKeydown;
25
27
  private _previouslyFocused;
28
+ private _locked;
29
+ private _wasOpen;
26
30
  private _close;
27
- /** Mantém o foco dentro do drawer (WCAG 2.4.3 / 2.1.2). */
31
+ /** Mantém o foco dentro do drawer (WCAG 2.4.3 / 2.1.2), incluindo o slot. */
28
32
  private _trapFocus;
29
33
  private _restoreFocus;
30
34
  }
@@ -1 +1 @@
1
- {"version":3,"file":"drawer.d.ts","sourceRoot":"","sources":["../../../src/components/drawer/drawer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAK5C;;;;;;;;;GASG;AACH,qBAAa,QAAS,SAAQ,SAAS;IACtC,OAAgB,MAAM,SA0HpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,IAAI,IAAI,IAAI,OAAO,CAElB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,EAEtB;IAED,IAAa,KAAK,IAAI,MAAM,CAE3B;IACD,IAAa,KAAK,CAAC,CAAC,EAAE,MAAM,EAE3B;IAED,IAAI,QAAQ,IAAI,MAAM,CAGrB;IACD,IAAI,QAAQ,CAAC,CAAC,EAAE,MAAM,EAErB;cAEkB,MAAM,IAAI,IAAI;cAwBd,iBAAiB,IAAI,IAAI;cAYzB,oBAAoB,IAAI,IAAI;IAM/C,OAAO,CAAC,UAAU,CAAwC;IAC1D,OAAO,CAAC,kBAAkB,CAA4B;IAEtD,OAAO,CAAC,MAAM,CAOZ;IAEF,2DAA2D;IAC3D,OAAO,CAAC,UAAU;IAmBlB,OAAO,CAAC,aAAa;CAIrB;AAED,wBAAgB,cAAc,IAAI,OAAO,QAAQ,CAEhD"}
1
+ {"version":3,"file":"drawer.d.ts","sourceRoot":"","sources":["../../../src/components/drawer/drawer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAc5C;;;;;;;;;GASG;AACH,qBAAa,QAAS,SAAQ,SAAS;IACtC,OAAgB,MAAM,SA0HpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,IAAI,IAAI,IAAI,OAAO,CAElB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,EAEtB;IAED,IAAa,KAAK,IAAI,MAAM,CAE3B;IACD,IAAa,KAAK,CAAC,CAAC,EAAE,MAAM,EAE3B;IAED,IAAI,QAAQ,IAAI,MAAM,CAGrB;IACD,IAAI,QAAQ,CAAC,CAAC,EAAE,MAAM,EAErB;cAEkB,MAAM,IAAI,IAAI;IAqCjC,kEAAkE;IAClE,OAAO,CAAC,QAAQ;cASG,iBAAiB,IAAI,IAAI;cAezB,oBAAoB,IAAI,IAAI;IAM/C,OAAO,CAAC,UAAU,CAAwC;IAC1D,OAAO,CAAC,kBAAkB,CAA4B;IACtD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAS;IAEzB,OAAO,CAAC,MAAM,CAMZ;IAEF,6EAA6E;IAC7E,OAAO,CAAC,UAAU;IAiBlB,OAAO,CAAC,aAAa;CAIrB;AAED,wBAAgB,cAAc,IAAI,OAAO,QAAQ,CAEhD"}
@@ -2,16 +2,18 @@ import { FxElement } from "../../core/base.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
+ import { lockBodyScroll, registerModalOpen, firstLightFocusable, registerModalClose, unlockBodyScroll, isTopmostModal, modalFocusables } from "../../core/modal.js";
5
6
  const _FxDrawer = class _FxDrawer extends FxElement {
6
7
  constructor() {
7
8
  super(...arguments);
8
9
  this._onKeydown = () => {
9
10
  };
10
11
  this._previouslyFocused = null;
12
+ this._locked = false;
13
+ this._wasOpen = false;
11
14
  this._close = () => {
12
15
  if (!this.open) return;
13
16
  this.open = false;
14
- this._restoreFocus();
15
17
  this.dispatchEvent(
16
18
  new CustomEvent("close", { bubbles: true, composed: true })
17
19
  );
@@ -41,6 +43,8 @@ const _FxDrawer = class _FxDrawer extends FxElement {
41
43
  }
42
44
  render() {
43
45
  const title = this.getAttr("title");
46
+ const wasOpen = this._wasOpen;
47
+ this._wasOpen = this.open;
44
48
  this.setTemplate(`
45
49
  <div class="overlay" part="overlay"></div>
46
50
  <div class="panel" part="panel" role="dialog" aria-modal="true" aria-label="${esc(title || "Drawer")}" tabindex="-1">
@@ -51,20 +55,40 @@ const _FxDrawer = class _FxDrawer extends FxElement {
51
55
  this.root.querySelector(".overlay")?.addEventListener("click", this._close);
52
56
  this.root.querySelector(".close")?.addEventListener("click", this._close);
53
57
  if (this.open) {
54
- requestAnimationFrame(() => {
55
- if (this._previouslyFocused === null) {
58
+ if (!this._locked) {
59
+ lockBodyScroll();
60
+ registerModalOpen(this);
61
+ this._locked = true;
62
+ }
63
+ if (!wasOpen) {
64
+ requestAnimationFrame(() => {
65
+ if (!this.open) return;
56
66
  this._previouslyFocused = document.activeElement;
57
- }
58
- this.root.querySelector(".panel")?.focus();
59
- });
67
+ const first = firstLightFocusable(this) ?? this.root.querySelector(".panel");
68
+ first?.focus();
69
+ });
70
+ }
71
+ } else {
72
+ this._release();
60
73
  }
61
74
  }
75
+ /** Desfaz scroll lock e restaura o foco ao fechar/desconectar. */
76
+ _release() {
77
+ registerModalClose(this);
78
+ if (this._locked) {
79
+ unlockBodyScroll();
80
+ this._locked = false;
81
+ }
82
+ this._restoreFocus();
83
+ }
62
84
  connectedCallback() {
63
85
  super.connectedCallback?.();
64
86
  this._onKeydown = (e) => {
65
- if (e.key === "Escape" && this.open) {
87
+ if (!this.open) return;
88
+ if (e.key === "Escape") {
89
+ if (!isTopmostModal(this)) return;
66
90
  this._close();
67
- } else if (e.key === "Tab" && this.open) {
91
+ } else if (e.key === "Tab") {
68
92
  this._trapFocus(e);
69
93
  }
70
94
  };
@@ -72,26 +96,24 @@ const _FxDrawer = class _FxDrawer extends FxElement {
72
96
  }
73
97
  disconnectedCallback() {
74
98
  document.removeEventListener("keydown", this._onKeydown);
75
- this._restoreFocus();
99
+ this._release();
76
100
  super.disconnectedCallback?.();
77
101
  }
78
- /** Mantém o foco dentro do drawer (WCAG 2.4.3 / 2.1.2). */
102
+ /** Mantém o foco dentro do drawer (WCAG 2.4.3 / 2.1.2), incluindo o slot. */
79
103
  _trapFocus(e) {
80
104
  const panel = this.root.querySelector(".panel");
81
105
  if (!panel) return;
82
- const focusables = panel.querySelectorAll(
83
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
84
- );
106
+ const focusables = modalFocusables(panel, this);
85
107
  if (!focusables.length) return;
86
108
  const first = focusables[0];
87
109
  const last = focusables[focusables.length - 1];
88
- const active = this.root.activeElement;
89
- if (e.shiftKey && (active === first || active === panel)) {
110
+ const active = this.root.activeElement ?? document.activeElement;
111
+ if (e.shiftKey && (active === first || active === panel || active === this)) {
90
112
  e.preventDefault();
91
- last.focus();
113
+ last?.focus();
92
114
  } else if (!e.shiftKey && active === last) {
93
115
  e.preventDefault();
94
- first.focus();
116
+ first?.focus();
95
117
  }
96
118
  }
97
119
  _restoreFocus() {
@@ -1 +1 @@
1
- {"version":3,"file":"drawer.js","sources":["../../../src/components/drawer/drawer.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-drawer> — Painel deslizante (drawer) com overlay.\n *\n * Atributos:\n * - position: 'left' | 'right' | 'top' | 'bottom' (padrão: right);\n * - title: texto do cabeçalho;\n * - open: exibe o drawer.\n * Conteúdo: livre, via slot.\n * Eventos: `open` e `close` (composed). Fecha por ESC ou clique no overlay.\n */\nexport class FxDrawer extends FxElement {\n\tstatic override styles = css`\n\t\t:host {\n\t\t\tdisplay: contents;\n\t\t\tfont-family: var(--fx-font-family);\n\t\t\tfont-size: var(--fx-font-size);\n\t\t\tcolor: var(--fx-text-default);\n\t\t}\n\t\t.overlay {\n\t\t\tdisplay: none;\n\t\t\tposition: fixed;\n\t\t\tinset: 0;\n\t\t\tbackground: color-mix(\n\t\t\t\tin srgb,\n\t\t\t\tvar(--fx-text-default, #000) 45%,\n\t\t\t\ttransparent\n\t\t\t);\n\t\t\tbackdrop-filter: blur(3px);\n\t\t\t-webkit-backdrop-filter: blur(3px);\n\t\t\tz-index: var(--fx-z-overlay, 1090);\n\t\t\tanimation: fade var(--fx-motion-duration-fast) var(--fx-motion-easing);\n\t\t}\n\t\t:host([open]) .overlay {\n\t\t\tdisplay: block;\n\t\t}\n\n\t\t.panel {\n\t\t\tposition: fixed;\n\t\t\tbackground: var(--fx-surface-background);\n\t\t\tbox-shadow: var(--fx-shadow-xl);\n\t\t\tz-index: var(--fx-z-modal, 1000);\n\t\t\tdisplay: flex;\n\t\t\tflex-direction: column;\n\t\t\toverflow: auto;\n\t\t}\n\t\t:host(:not([open])) .panel {\n\t\t\tdisplay: none;\n\t\t}\n\t\t:host([open]) .panel {\n\t\t\tdisplay: flex;\n\t\t}\n\t\t/* left / right — altura total, largura mínima fixa e expansível */\n\t\t:host([position=\"left\"]) .panel,\n\t\t:host(:not([position])) .panel,\n\t\t:host([position=\"right\"]) .panel {\n\t\t\ttop: 0;\n\t\t\tbottom: 0;\n\t\t\twidth: var(--fx-drawer-width, 360px);\n\t\t\tmin-width: 300px;\n\t\t\tmax-width: 90vw;\n\t\t}\n\t\t:host([position=\"left\"]) .panel {\n\t\t\tleft: 0;\n\t\t}\n\t\t:host([position=\"right\"]) .panel,\n\t\t:host(:not([position])) .panel {\n\t\t\tright: 0;\n\t\t}\n\n\t\t/* top / bottom — largura total, altura mínima fixa e expansível */\n\t\t:host([position=\"top\"]) .panel,\n\t\t:host([position=\"bottom\"]) .panel {\n\t\t\tleft: 0;\n\t\t\tright: 0;\n\t\t\theight: var(--fx-drawer-height, 280px);\n\t\t\tmin-height: 180px;\n\t\t\tmax-height: 85vh;\n\t\t}\n\t\t:host([position=\"top\"]) .panel {\n\t\t\ttop: 0;\n\t\t}\n\t\t:host([position=\"bottom\"]) .panel {\n\t\t\tbottom: 0;\n\t\t}\n\n\t\t.header {\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tgap: var(--fx-space-sm);\n\t\t\tmin-height: var(--fx-size-lg);\n\t\t\tpadding: var(--fx-space-sm) var(--fx-space-md);\n\t\t\tborder-bottom: 1px solid var(--fx-border-default);\n\t\t\tflex: none;\n\t\t\tposition: sticky;\n\t\t\ttop: 0;\n\t\t\tbackground: var(--fx-surface-background);\n\t\t\t/* O conteúdo do drawer (ex.: abas da customização de tema)\n\t\t\t rola por trás do header: sem z-index o texto passava POR CIMA\n\t\t\t e o header parecia transparente. */\n\t\t\tz-index: 1;\n\t\t}\n\t\t.title {\n\t\t\tfont-weight: 700;\n\t\t\tflex: 1;\n\t\t\toverflow: hidden;\n\t\t\ttext-overflow: ellipsis;\n\t\t\twhite-space: nowrap;\n\t\t}\n\t\t.close {\n\t\t\tborder: none;\n\t\t\tbackground: transparent;\n\t\t\tcursor: pointer;\n\t\t\tcolor: var(--fx-text-muted);\n\t\t\twidth: 28px;\n\t\t\theight: 28px;\n\t\t\tline-height: 1;\n\t\t\tborder-radius: var(--fx-radius-sm);\n\t\t\tfont-size: 16px;\n\t\t\ttransition: background var(--fx-motion-duration-fast)\n\t\t\t\tvar(--fx-motion-easing);\n\t\t}\n\t\t.close:hover {\n\t\t\tbackground: color-mix(\n\t\t\t\tin srgb,\n\t\t\t\tvar(--fx-color-danger) 14%,\n\t\t\t\tvar(--fx-surface-background)\n\t\t\t);\n\t\t\tcolor: var(--fx-color-danger);\n\t\t}\n\t\t.body {\n\t\t\tpadding: var(--fx-space-md);\n\t\t\tflex: 1;\n\t\t}\n\t`;\n\n\tstatic override get observedAttributes(): string[] {\n\t\treturn [\"open\", \"title\", \"position\"];\n\t}\n\n\tget open(): boolean {\n\t\treturn this.hasAttr(\"open\");\n\t}\n\tset open(value: boolean) {\n\t\tthis.toggleAttr(\"open\", value);\n\t}\n\n\toverride get title(): string {\n\t\treturn this.getAttr(\"title\");\n\t}\n\toverride set title(v: string) {\n\t\tthis.setAttribute(\"title\", v);\n\t}\n\n\tget position(): string {\n\t\tconst p = this.getAttr(\"position\");\n\t\treturn [\"left\", \"right\", \"top\", \"bottom\"].includes(p) ? p : \"right\";\n\t}\n\tset position(v: string) {\n\t\tthis.setAttribute(\"position\", v);\n\t}\n\n\tprotected override render(): void {\n\t\tconst title = this.getAttr(\"title\");\n\n\t\tthis.setTemplate(`\n <div class=\"overlay\" part=\"overlay\"></div>\n <div class=\"panel\" part=\"panel\" role=\"dialog\" aria-modal=\"true\" aria-label=\"${esc(title || \"Drawer\")}\" tabindex=\"-1\">\n ${title !== \"\" ? `<div class=\"header\" part=\"header\"><span class=\"title\">${esc(title)}</span><button type=\"button\" class=\"close\" part=\"close\" aria-label=\"Fechar\">✕</button></div>` : \"\"}\n <div class=\"body\" part=\"body\"><slot></slot></div>\n </div>\n `);\n\n\t\tthis.root.querySelector(\".overlay\")?.addEventListener(\"click\", this._close);\n\t\tthis.root.querySelector(\".close\")?.addEventListener(\"click\", this._close);\n\n\t\tif (this.open) {\n\t\t\trequestAnimationFrame(() => {\n\t\t\t\tif (this._previouslyFocused === null) {\n\t\t\t\t\tthis._previouslyFocused = document.activeElement as HTMLElement | null;\n\t\t\t\t}\n\t\t\t\tthis.root.querySelector<HTMLElement>(\".panel\")?.focus();\n\t\t\t});\n\t\t}\n\t}\n\n\tprotected override connectedCallback(): void {\n\t\tsuper.connectedCallback?.();\n\t\tthis._onKeydown = (e: KeyboardEvent) => {\n\t\t\tif (e.key === \"Escape\" && this.open) {\n\t\t\t\tthis._close();\n\t\t\t} else if (e.key === \"Tab\" && this.open) {\n\t\t\t\tthis._trapFocus(e);\n\t\t\t}\n\t\t};\n\t\tdocument.addEventListener(\"keydown\", this._onKeydown);\n\t}\n\n\tprotected override disconnectedCallback(): void {\n\t\tdocument.removeEventListener(\"keydown\", this._onKeydown);\n\t\tthis._restoreFocus();\n\t\tsuper.disconnectedCallback?.();\n\t}\n\n\tprivate _onKeydown: (e: KeyboardEvent) => void = () => {};\n\tprivate _previouslyFocused: HTMLElement | null = null;\n\n\tprivate _close = (): void => {\n\t\tif (!this.open) return;\n\t\tthis.open = false;\n\t\tthis._restoreFocus();\n\t\tthis.dispatchEvent(\n\t\t\tnew CustomEvent(\"close\", { bubbles: true, composed: true }),\n\t\t);\n\t};\n\n\t/** Mantém o foco dentro do drawer (WCAG 2.4.3 / 2.1.2). */\n\tprivate _trapFocus(e: KeyboardEvent): void {\n\t\tconst panel = this.root.querySelector<HTMLElement>(\".panel\");\n\t\tif (!panel) return;\n\t\tconst focusables = panel.querySelectorAll<HTMLElement>(\n\t\t\t'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])',\n\t\t);\n\t\tif (!focusables.length) return;\n\t\tconst first = focusables[0];\n\t\tconst last = focusables[focusables.length - 1];\n\t\tconst active = this.root.activeElement;\n\t\tif (e.shiftKey && (active === first || active === panel)) {\n\t\t\te.preventDefault();\n\t\t\tlast.focus();\n\t\t} else if (!e.shiftKey && active === last) {\n\t\t\te.preventDefault();\n\t\t\tfirst.focus();\n\t\t}\n\t}\n\n\tprivate _restoreFocus(): void {\n\t\tif (this._previouslyFocused?.isConnected) this._previouslyFocused.focus();\n\t\tthis._previouslyFocused = null;\n\t}\n}\n\nexport function defineFxDrawer(): typeof FxDrawer {\n return defineElement('fx-drawer', FxDrawer);\n}"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"drawer.js","sources":["../../../src/components/drawer/drawer.ts"],"sourcesContent":["import { FxElement } from '../../core/base';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\nimport { esc } from '../../core/sanitize';\nimport {\n firstLightFocusable,\n isTopmostModal,\n lockBodyScroll,\n modalFocusables,\n registerModalClose,\n registerModalOpen,\n unlockBodyScroll,\n} from '../../core/modal';\n\n/**\n * <fx-drawer> — Painel deslizante (drawer) com overlay.\n *\n * Atributos:\n * - position: 'left' | 'right' | 'top' | 'bottom' (padrão: right);\n * - title: texto do cabeçalho;\n * - open: exibe o drawer.\n * Conteúdo: livre, via slot.\n * Eventos: `open` e `close` (composed). Fecha por ESC ou clique no overlay.\n */\nexport class FxDrawer extends FxElement {\n\tstatic override styles = css`\n\t\t:host {\n\t\t\tdisplay: contents;\n\t\t\tfont-family: var(--fx-font-family);\n\t\t\tfont-size: var(--fx-font-size);\n\t\t\tcolor: var(--fx-text-default);\n\t\t}\n\t\t.overlay {\n\t\t\tdisplay: none;\n\t\t\tposition: fixed;\n\t\t\tinset: 0;\n\t\t\tbackground: color-mix(\n\t\t\t\tin srgb,\n\t\t\t\tvar(--fx-text-default, #000) 45%,\n\t\t\t\ttransparent\n\t\t\t);\n\t\t\tbackdrop-filter: blur(3px);\n\t\t\t-webkit-backdrop-filter: blur(3px);\n\t\t\tz-index: var(--fx-z-overlay, 1090);\n\t\t\tanimation: fade var(--fx-motion-duration-fast) var(--fx-motion-easing);\n\t\t}\n\t\t:host([open]) .overlay {\n\t\t\tdisplay: block;\n\t\t}\n\n\t\t.panel {\n\t\t\tposition: fixed;\n\t\t\tbackground: var(--fx-surface-background);\n\t\t\tbox-shadow: var(--fx-shadow-xl);\n\t\t\tz-index: var(--fx-z-modal, 1000);\n\t\t\tdisplay: flex;\n\t\t\tflex-direction: column;\n\t\t\toverflow: auto;\n\t\t}\n\t\t:host(:not([open])) .panel {\n\t\t\tdisplay: none;\n\t\t}\n\t\t:host([open]) .panel {\n\t\t\tdisplay: flex;\n\t\t}\n\t\t/* left / right — altura total, largura mínima fixa e expansível */\n\t\t:host([position=\"left\"]) .panel,\n\t\t:host(:not([position])) .panel,\n\t\t:host([position=\"right\"]) .panel {\n\t\t\ttop: 0;\n\t\t\tbottom: 0;\n\t\t\twidth: var(--fx-drawer-width, 360px);\n\t\t\tmin-width: 300px;\n\t\t\tmax-width: 90vw;\n\t\t}\n\t\t:host([position=\"left\"]) .panel {\n\t\t\tleft: 0;\n\t\t}\n\t\t:host([position=\"right\"]) .panel,\n\t\t:host(:not([position])) .panel {\n\t\t\tright: 0;\n\t\t}\n\n\t\t/* top / bottom — largura total, altura mínima fixa e expansível */\n\t\t:host([position=\"top\"]) .panel,\n\t\t:host([position=\"bottom\"]) .panel {\n\t\t\tleft: 0;\n\t\t\tright: 0;\n\t\t\theight: var(--fx-drawer-height, 280px);\n\t\t\tmin-height: 180px;\n\t\t\tmax-height: 85vh;\n\t\t}\n\t\t:host([position=\"top\"]) .panel {\n\t\t\ttop: 0;\n\t\t}\n\t\t:host([position=\"bottom\"]) .panel {\n\t\t\tbottom: 0;\n\t\t}\n\n\t\t.header {\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tgap: var(--fx-space-sm);\n\t\t\tmin-height: var(--fx-size-lg);\n\t\t\tpadding: var(--fx-space-sm) var(--fx-space-md);\n\t\t\tborder-bottom: 1px solid var(--fx-border-default);\n\t\t\tflex: none;\n\t\t\tposition: sticky;\n\t\t\ttop: 0;\n\t\t\tbackground: var(--fx-surface-background);\n\t\t\t/* O conteúdo do drawer (ex.: abas da customização de tema)\n\t\t\t rola por trás do header: sem z-index o texto passava POR CIMA\n\t\t\t e o header parecia transparente. */\n\t\t\tz-index: 1;\n\t\t}\n\t\t.title {\n\t\t\tfont-weight: 700;\n\t\t\tflex: 1;\n\t\t\toverflow: hidden;\n\t\t\ttext-overflow: ellipsis;\n\t\t\twhite-space: nowrap;\n\t\t}\n\t\t.close {\n\t\t\tborder: none;\n\t\t\tbackground: transparent;\n\t\t\tcursor: pointer;\n\t\t\tcolor: var(--fx-text-muted);\n\t\t\twidth: 28px;\n\t\t\theight: 28px;\n\t\t\tline-height: 1;\n\t\t\tborder-radius: var(--fx-radius-sm);\n\t\t\tfont-size: 16px;\n\t\t\ttransition: background var(--fx-motion-duration-fast)\n\t\t\t\tvar(--fx-motion-easing);\n\t\t}\n\t\t.close:hover {\n\t\t\tbackground: color-mix(\n\t\t\t\tin srgb,\n\t\t\t\tvar(--fx-color-danger) 14%,\n\t\t\t\tvar(--fx-surface-background)\n\t\t\t);\n\t\t\tcolor: var(--fx-color-danger);\n\t\t}\n\t\t.body {\n\t\t\tpadding: var(--fx-space-md);\n\t\t\tflex: 1;\n\t\t}\n\t`;\n\n\tstatic override get observedAttributes(): string[] {\n\t\treturn [\"open\", \"title\", \"position\"];\n\t}\n\n\tget open(): boolean {\n\t\treturn this.hasAttr(\"open\");\n\t}\n\tset open(value: boolean) {\n\t\tthis.toggleAttr(\"open\", value);\n\t}\n\n\toverride get title(): string {\n\t\treturn this.getAttr(\"title\");\n\t}\n\toverride set title(v: string) {\n\t\tthis.setAttribute(\"title\", v);\n\t}\n\n\tget position(): string {\n\t\tconst p = this.getAttr(\"position\");\n\t\treturn [\"left\", \"right\", \"top\", \"bottom\"].includes(p) ? p : \"right\";\n\t}\n\tset position(v: string) {\n\t\tthis.setAttribute(\"position\", v);\n\t}\n\n\tprotected override render(): void {\n\t\tconst title = this.getAttr(\"title\");\n\t\tconst wasOpen = this._wasOpen;\n\t\tthis._wasOpen = this.open;\n\n\t\tthis.setTemplate(`\n <div class=\"overlay\" part=\"overlay\"></div>\n <div class=\"panel\" part=\"panel\" role=\"dialog\" aria-modal=\"true\" aria-label=\"${esc(title || \"Drawer\")}\" tabindex=\"-1\">\n ${title !== \"\" ? `<div class=\"header\" part=\"header\"><span class=\"title\">${esc(title)}</span><button type=\"button\" class=\"close\" part=\"close\" aria-label=\"Fechar\">✕</button></div>` : \"\"}\n <div class=\"body\" part=\"body\"><slot></slot></div>\n </div>\n `);\n\n\t\tthis.root.querySelector(\".overlay\")?.addEventListener(\"click\", this._close);\n\t\tthis.root.querySelector(\".close\")?.addEventListener(\"click\", this._close);\n\n\t\tif (this.open) {\n\t\t\tif (!this._locked) {\n\t\t\t\tlockBodyScroll();\n\t\t\t\tregisterModalOpen(this);\n\t\t\t\tthis._locked = true;\n\t\t\t}\n\t\t\tif (!wasOpen) {\n\t\t\t\trequestAnimationFrame(() => {\n\t\t\t\t\tif (!this.open) return;\n\t\t\t\t\tthis._previouslyFocused = document.activeElement as HTMLElement | null;\n\t\t\t\t\tconst first =\n\t\t\t\t\t\tfirstLightFocusable(this) ??\n\t\t\t\t\t\tthis.root.querySelector<HTMLElement>(\".panel\");\n\t\t\t\t\tfirst?.focus();\n\t\t\t\t});\n\t\t\t}\n\t\t} else {\n\t\t\tthis._release();\n\t\t}\n\t}\n\n\t/** Desfaz scroll lock e restaura o foco ao fechar/desconectar. */\n\tprivate _release(): void {\n\t\tregisterModalClose(this);\n\t\tif (this._locked) {\n\t\t\tunlockBodyScroll();\n\t\t\tthis._locked = false;\n\t\t}\n\t\tthis._restoreFocus();\n\t}\n\n\tprotected override connectedCallback(): void {\n\t\tsuper.connectedCallback?.();\n\t\tthis._onKeydown = (e: KeyboardEvent) => {\n\t\t\tif (!this.open) return;\n\t\t\tif (e.key === \"Escape\") {\n\t\t\t\t// Apenas o modal do topo responde ao Escape.\n\t\t\t\tif (!isTopmostModal(this)) return;\n\t\t\t\tthis._close();\n\t\t\t} else if (e.key === \"Tab\") {\n\t\t\t\tthis._trapFocus(e);\n\t\t\t}\n\t\t};\n\t\tdocument.addEventListener(\"keydown\", this._onKeydown);\n\t}\n\n\tprotected override disconnectedCallback(): void {\n\t\tdocument.removeEventListener(\"keydown\", this._onKeydown);\n\t\tthis._release();\n\t\tsuper.disconnectedCallback?.();\n\t}\n\n\tprivate _onKeydown: (e: KeyboardEvent) => void = () => {};\n\tprivate _previouslyFocused: HTMLElement | null = null;\n\tprivate _locked = false;\n\tprivate _wasOpen = false;\n\n\tprivate _close = (): void => {\n\t\tif (!this.open) return;\n\t\tthis.open = false; // render(false) → _release()\n\t\tthis.dispatchEvent(\n\t\t\tnew CustomEvent(\"close\", { bubbles: true, composed: true }),\n\t\t);\n\t};\n\n\t/** Mantém o foco dentro do drawer (WCAG 2.4.3 / 2.1.2), incluindo o slot. */\n\tprivate _trapFocus(e: KeyboardEvent): void {\n\t\tconst panel = this.root.querySelector<HTMLElement>(\".panel\");\n\t\tif (!panel) return;\n\t\tconst focusables = modalFocusables(panel, this);\n\t\tif (!focusables.length) return;\n\t\tconst first = focusables[0];\n\t\tconst last = focusables[focusables.length - 1];\n\t\tconst active = this.root.activeElement ?? document.activeElement;\n\t\tif (e.shiftKey && (active === first || active === panel || active === this)) {\n\t\t\te.preventDefault();\n\t\t\tlast?.focus();\n\t\t} else if (!e.shiftKey && active === last) {\n\t\t\te.preventDefault();\n\t\t\tfirst?.focus();\n\t\t}\n\t}\n\n\tprivate _restoreFocus(): void {\n\t\tif (this._previouslyFocused?.isConnected) this._previouslyFocused.focus();\n\t\tthis._previouslyFocused = null;\n\t}\n}\n\nexport function defineFxDrawer(): typeof FxDrawer {\n return defineElement('fx-drawer', FxDrawer);\n}"],"names":[],"mappings":"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"}
@@ -26,7 +26,19 @@ export declare class FxFileUpload extends FxFormElement {
26
26
  set size(value: string);
27
27
  /** Nomes dos arquivos selecionados (espelho em `value`). */
28
28
  get value(): string;
29
- set value(v: string);
29
+ set value(v: string | null | undefined);
30
+ get disabled(): boolean;
31
+ set disabled(v: boolean);
32
+ get multiple(): boolean;
33
+ set multiple(v: boolean);
34
+ get full(): boolean;
35
+ set full(v: boolean);
36
+ /**
37
+ * Nome literal: o Vue NÃO cameliza atributos em elementos customizados,
38
+ * então `:show-progress` procura a propriedade `'show-progress'`.
39
+ */
40
+ get 'show-progress'(): boolean;
41
+ set 'show-progress'(v: boolean);
30
42
  get progress(): number;
31
43
  set progress(v: number);
32
44
  /** Arquivos selecionados (não serializável em atributo). */
@@ -1 +1 @@
1
- {"version":3,"file":"fileupload.d.ts","sourceRoot":"","sources":["../../../src/components/fileupload/fileupload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAMxD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,YAAa,SAAQ,aAAa;IAC7C,OAAgB,MAAM,SA4IpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAAuC;IAE7D,4DAA4D;IAC5D,IAAI,KAAK,IAAI,MAAM,CAAkC;IACrD,IAAI,KAAK,CAAC,CAAC,EAAE,MAAM,EAAoC;IAEvD,IAAI,QAAQ,IAAI,MAAM,CAAmF;IACzG,IAAI,QAAQ,CAAC,CAAC,EAAE,MAAM,EAA+C;IAErE,4DAA4D;IAC5D,IAAI,KAAK,IAAI,IAAI,EAAE,CAAwB;IAC3C,IAAI,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,EAItB;IAED,iFAAiF;cAC9D,YAAY,IAAI,MAAM,GAAG,QAAQ,GAAG,IAAI;cAQxC,MAAM,IAAI,IAAI;IA8CjC,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,KAAK,CAAS;IAEtB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,WAAW;IAyBnB,OAAO,CAAC,YAAY;IAUpB,OAAO,CAAC,QAAQ;IAMhB,oDAAoD;IAC3C,iBAAiB,IAAI,IAAI;IAMlC,kFAAkF;IAClF,OAAO,CAAC,cAAc;CAyBvB;AAED,wBAAgB,kBAAkB,IAAI,OAAO,YAAY,CAExD"}
1
+ {"version":3,"file":"fileupload.d.ts","sourceRoot":"","sources":["../../../src/components/fileupload/fileupload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAMxD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,YAAa,SAAQ,aAAa;IAC7C,OAAgB,MAAM,SA4IpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IACD,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAAuC;IAE7D,4DAA4D;IAC5D,IAAI,KAAK,IAAI,MAAM,CAAkC;IACrD,IAAI,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAErC;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,CAAC,EAAE,OAAO,EAEtB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,CAAC,EAAE,OAAO,EAEtB;IAED,IAAI,IAAI,IAAI,OAAO,CAElB;IACD,IAAI,IAAI,CAAC,CAAC,EAAE,OAAO,EAElB;IAED;;;OAGG;IACH,IAAI,eAAe,IAAI,OAAO,CAE7B;IACD,IAAI,eAAe,CAAC,CAAC,EAAE,OAAO,EAE7B;IAED,IAAI,QAAQ,IAAI,MAAM,CAAmF;IACzG,IAAI,QAAQ,CAAC,CAAC,EAAE,MAAM,EAA+C;IAErE,4DAA4D;IAC5D,IAAI,KAAK,IAAI,IAAI,EAAE,CAAwB;IAC3C,IAAI,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,EAItB;IAED,iFAAiF;cAC9D,YAAY,IAAI,MAAM,GAAG,QAAQ,GAAG,IAAI;cAQxC,MAAM,IAAI,IAAI;IA8CjC,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,KAAK,CAAS;IAEtB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,WAAW;IAyBnB,OAAO,CAAC,YAAY;IAUpB,OAAO,CAAC,QAAQ;IAMhB,oDAAoD;IAC3C,iBAAiB,IAAI,IAAI;IAMlC,kFAAkF;IAClF,OAAO,CAAC,cAAc;CAyBvB;AAED,wBAAgB,kBAAkB,IAAI,OAAO,YAAY,CAExD"}
@@ -24,7 +24,35 @@ const _FxFileUpload = class _FxFileUpload extends FxFormElement {
24
24
  return this.getAttr("value");
25
25
  }
26
26
  set value(v) {
27
- this.setAttribute("value", v);
27
+ this.setAttribute("value", v == null ? "" : String(v));
28
+ }
29
+ get disabled() {
30
+ return this.hasAttr("disabled");
31
+ }
32
+ set disabled(v) {
33
+ this.toggleAttr("disabled", Boolean(v));
34
+ }
35
+ get multiple() {
36
+ return this.hasAttr("multiple");
37
+ }
38
+ set multiple(v) {
39
+ this.toggleAttr("multiple", Boolean(v));
40
+ }
41
+ get full() {
42
+ return this.hasAttr("full");
43
+ }
44
+ set full(v) {
45
+ this.toggleAttr("full", Boolean(v));
46
+ }
47
+ /**
48
+ * Nome literal: o Vue NÃO cameliza atributos em elementos customizados,
49
+ * então `:show-progress` procura a propriedade `'show-progress'`.
50
+ */
51
+ get "show-progress"() {
52
+ return this.hasAttr("show-progress");
53
+ }
54
+ set "show-progress"(v) {
55
+ this.toggleAttr("show-progress", Boolean(v));
28
56
  }
29
57
  get progress() {
30
58
  return Math.min(100, Math.max(0, Number(this.getAttr("progress", "0")) || 0));
@@ -1 +1 @@
1
- {"version":3,"file":"fileupload.js","sources":["../../../src/components/fileupload/fileupload.ts"],"sourcesContent":["import { FxFormElement } from '../../core/form-element';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\nimport { esc } from '../../core/sanitize';\nimport { FENIX_ICON_BASE_CSS, fenixIconHtml } from '../../icons/base-css';\n\n/**\n * <fx-fileupload> — Seletor de arquivos com upload e progresso.\n *\n * Form-Associated Custom Element: dentro de um <form> entra no FormData\n * (via atributo `name`) com os File's selecionados — o upload é enviado\n * junto com o submit do form, como um <input type=\"file\"> nativo.\n *\n * Atributos:\n * - mode (basic|advanced — `basic`: botão; `advanced`: dropzone com drag & drop);\n * - size (sm|md|lg, padrão md);\n * - severity (primary|secondary|success|warning|danger|info — cor do botão no modo basic);\n * - label (texto do botão/área, padrão 'Escolher arquivo');\n * - icon (glifo/emoji exibido antes do texto — sem o atributo, sem ícone);\n * - accept, multiple, disabled (como no <input type=\"file\">);\n * - show-progress (exibe barra + % de upload);\n * - progress (0-100 — o consumidor atualiza durante o upload real);\n * - value (nome(s) do(s) arquivo(s) carregado(s), somente leitura/espelho).\n *\n * Eventos (composed): `select` (detail: { files: File[] }), `remove`, `complete`.\n */\nexport class FxFileUpload extends FxFormElement {\n static override styles = css`\n :host {\n display: inline-block;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n }\n :host([disabled]) { opacity: 0.55; pointer-events: none; }\n input[type='file'] { display: none; }\n\n /* ---- botão (basic) ---- */\n .btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--fx-space-sm);\n /* Largura acompanha o host: CSS externo/classe/inline no elemento esticam\n o botão sem precisar do full (host é shrink-to-fit por padrão). */\n width: 100%;\n min-height: var(--fx-size-md);\n /* Padding vertical no orçamento do token --fx-size-* ⇒ altura padrão. */\n padding: var(--fx-space-sm) var(--fx-space-lg);\n font-family: inherit;\n font-size: inherit;\n font-weight: var(--fx-font-weight);\n line-height: var(--fx-font-line-height);\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 cursor: pointer;\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 background var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n .btn:hover { border-color: var(--fx-border-hover); background: var(--fx-surface-surface-hover, rgba(0,0,0,0.03)); }\n .btn:focus-visible {\n outline: none;\n border-color: var(--fx-color-primary);\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n /* Severities (como no <fx-button>) — aplicam-se ao botão do modo basic */\n :host([severity='primary']) .btn { background: var(--fx-color-primary); border-color: transparent; color: #fff; }\n :host([severity='secondary']) .btn { background: var(--fx-color-secondary); border-color: transparent; color: #fff; }\n :host([severity='success']) .btn { background: var(--fx-color-success); border-color: transparent; color: #fff; }\n :host([severity='warning']) .btn { background: var(--fx-color-warning); border-color: transparent; color: #fff; }\n :host([severity='danger']) .btn { background: var(--fx-color-danger); border-color: transparent; color: #fff; }\n :host([severity='info']) .btn { background: var(--fx-color-info); border-color: transparent; color: #fff; }\n :host([severity]) .btn:hover { filter: brightness(0.88); }\n :host([severity]) .btn:focus-visible { border-color: transparent; }\n :host([size='sm']) .btn { min-height: var(--fx-size-sm); padding: var(--fx-space-xs) var(--fx-space-md); font-size: calc(var(--fx-font-size) - 2px); }\n :host([size='lg']) .btn { min-height: var(--fx-size-lg); padding: var(--fx-space-sm) var(--fx-space-xl); font-size: calc(var(--fx-font-size) + 4px); }\n :host([size='sm']) .drop { min-height: 72px; }\n :host([size='lg']) .drop { min-height: 132px; }\n\n /* ---- dropzone (advanced) ---- */\n .drop {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--fx-space-sm);\n /* Acompanha a largura do host (definida por CSS externo/classe/inline). */\n width: 100%;\n min-height: 100px;\n padding: var(--fx-space-lg);\n border: 2px dashed var(--fx-border-default);\n border-radius: var(--fx-radius-md);\n color: var(--fx-text-muted);\n cursor: pointer;\n text-align: center;\n transition: border-color var(--fx-motion-duration-normal) var(--fx-motion-easing), background var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n .drop:hover, .drop.over {\n border-color: var(--fx-color-primary);\n background: color-mix(in srgb, var(--fx-color-primary) 6%, transparent);\n }\n .drop .icon { font-size: calc(var(--fx-font-size) + 12px); line-height: 1; }\n .drop .hint { font-size: calc(var(--fx-font-size) - 2px); }\n\n /* ---- ícone comum ---- */\n .icon { line-height: 1; }\n\n /* ---- arquivo(s) selecionado(s) ---- */\n .files { display: flex; flex-direction: column; gap: var(--fx-space-xs); margin-top: var(--fx-space-sm); }\n .file {\n display: flex;\n align-items: center;\n gap: var(--fx-space-sm);\n padding: var(--fx-space-xs) var(--fx-space-sm);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-sm);\n font-size: calc(var(--fx-font-size) - 2px);\n color: var(--fx-text-default);\n }\n .file .name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .file .size { color: var(--fx-text-muted); white-space: nowrap; }\n .file .remove {\n border: none;\n background: transparent;\n color: var(--fx-text-muted);\n cursor: pointer;\n font-size: calc(var(--fx-font-size) + 2px);\n line-height: 1;\n padding: 0 var(--fx-space-xs);\n border-radius: var(--fx-radius-full);\n }\n .file .remove:hover { color: var(--fx-color-danger); }\n\n /* ---- progresso ---- */\n .progress-row { display: flex; align-items: center; gap: var(--fx-space-sm); margin-top: var(--fx-space-sm); }\n .track {\n flex: 1;\n height: 6px;\n background: var(--fx-surface-surface-hover);\n border-radius: var(--fx-radius-full);\n overflow: hidden;\n }\n .bar {\n height: 100%;\n width: 0%;\n background: var(--fx-color-primary);\n border-radius: var(--fx-radius-full);\n transition: width var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n :host([progress='100']) .bar { background: var(--fx-color-success); }\n .pct {\n font-size: calc(var(--fx-font-size) - 2px);\n font-weight: 600;\n color: var(--fx-text-default);\n min-width: 34px;\n text-align: right;\n }\n /* Full width: o host estica até o pai e o botão/dropzone acompanha. */\n :host([full]) { display: block; width: 100%; }\n :host([full]) .btn { width: 100%; }\n :host([full]) .drop { width: 100%; }\n /* Base dos glifos Fenix Icons (o @font-face vem de @wrrdev/fenix-ui/icons). */\n ${FENIX_ICON_BASE_CSS}\n `;\n\n static override get observedAttributes(): string[] {\n return ['mode', 'size', 'severity', 'label', 'icon', 'accept', 'multiple', 'disabled', 'show-progress', 'progress', 'value', 'full'];\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 /** Nomes dos arquivos selecionados (espelho em `value`). */\n get value(): string { return this.getAttr('value'); }\n set value(v: string) { this.setAttribute('value', v); }\n\n get progress(): number { return Math.min(100, Math.max(0, Number(this.getAttr('progress', '0')) || 0)); }\n set progress(v: number) { this.setAttribute('progress', String(v)); }\n\n /** Arquivos selecionados (não serializável em atributo). */\n get files(): File[] { return this._files; }\n set files(files: File[]) {\n this._files = Array.from(files);\n this.value = this._files.map((f) => f.name).join(', ');\n this._syncFilesArea();\n }\n\n /** No FormData do form: os arquivos selecionados, como o <input type=\"file\">. */\n protected override getFormValue(): string | FormData | null {\n const files = this._files;\n if (!this.name || files.length === 0) return null;\n const fd = new FormData();\n for (const f of files) fd.append(this.name, f, f.name);\n return fd;\n }\n\n protected override render(): void {\n const mode = this.getAttr('mode', 'basic');\n const advanced = mode === 'advanced';\n const label = this.getAttr('label', 'Escolher arquivo');\n const icon = this.getAttr('icon');\n const accept = this.getAttr('accept');\n const multiple = this.hasAttr('multiple');\n const disabled = this.hasAttr('disabled');\n const showProgress = this.hasAttr('show-progress');\n const pct = this.progress;\n\n const fileInput = `<input type=\"file\" class=\"picker\" ${accept ? `accept=\"${esc(accept)}\"` : ''} ${multiple ? 'multiple' : ''} ${disabled ? 'disabled' : ''} />`;\n\n this.setTemplate(`\n ${advanced\n ? `<div class=\"drop\" part=\"dropzone\" role=\"button\" tabindex=\"0\" aria-label=\"${esc(label)}\">\n ${icon ? `<span class=\"icon\" part=\"icon\" aria-hidden=\"true\">${fenixIconHtml(icon)}</span>` : ''}\n <span>${esc(label)}</span>\n <span class=\"hint\">ou arraste e solte aqui</span>\n </div>`\n : `<button type=\"button\" class=\"btn\" part=\"button\">\n ${icon ? `<span class=\"icon\" part=\"icon\" aria-hidden=\"true\">${fenixIconHtml(icon)}</span>` : ''}\n <span>${esc(label)}</span>\n </button>`}\n ${fileInput}\n <div class=\"files\" part=\"files\"></div>\n ${showProgress ? `\n <div class=\"progress-row\" part=\"progress\">\n <div class=\"track\" role=\"progressbar\" aria-valuenow=\"${pct}\" aria-valuemin=\"0\" aria-valuemax=\"100\">\n <div class=\"bar\" style=\"width: ${pct}%\"></div>\n </div>\n <span class=\"pct\">${pct}%</span>\n </div>` : ''}\n `);\n\n this._bindPicker();\n this._syncFilesArea();\n this.updateFormValue();\n\n if (!this._done && showProgress && pct >= 100 && this._files.length) {\n this._done = true;\n this.dispatchEvent(new CustomEvent('complete', { bubbles: true, composed: true }));\n }\n if (pct < 100) this._done = false;\n }\n\n private _files: File[] = [];\n private _done = false;\n\n private _picker(): HTMLInputElement | null {\n return this.root.querySelector<HTMLInputElement>('.picker');\n }\n\n private _bindPicker(): void {\n const picker = this._picker();\n if (!picker) return;\n const trigger = this.root.querySelector('.btn') ?? this.root.querySelector('.drop');\n trigger?.addEventListener('click', () => picker.click());\n trigger?.addEventListener('keydown', (e) => {\n if ((e as KeyboardEvent).key === 'Enter' || (e as KeyboardEvent).key === ' ') {\n e.preventDefault();\n picker.click();\n }\n });\n picker.addEventListener('change', () => this._acceptFiles(picker.files ?? []));\n\n // drag & drop (advanced)\n const drop = this.root.querySelector<HTMLElement>('.drop');\n if (!drop) return;\n drop.addEventListener('dragover', (e) => { e.preventDefault(); drop.classList.add('over'); });\n drop.addEventListener('dragleave', () => drop.classList.remove('over'));\n drop.addEventListener('drop', (e) => {\n e.preventDefault();\n drop.classList.remove('over');\n if (e.dataTransfer?.files.length) this._acceptFiles(e.dataTransfer.files);\n });\n }\n\n private _acceptFiles(list: FileList | File[]): void {\n const picked = this.hasAttr('multiple') ? Array.from(list) : [list[0]].filter(Boolean);\n this.files = picked;\n const picker = this._picker();\n if (picker) picker.value = '';\n this.dispatchEvent(\n new CustomEvent('select', { bubbles: true, composed: true, detail: { files: picked } }),\n );\n }\n\n private _fmtSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n }\n\n /** form.reset(): limpa os arquivos selecionados. */\n override formResetCallback(): void {\n this._files = [];\n this.value = ''; // `value` é observado → re-render + _syncFilesArea\n this.updateFormValue();\n }\n\n /** Renderiza a lista de arquivos sem re-renderizar o template (mantém estado). */\n private _syncFilesArea(): void {\n const area = this.root?.querySelector?.('.files') as HTMLElement | null;\n if (!area) return;\n area.innerHTML = this._files\n .map(\n (f, i) => `\n <div class=\"file\" part=\"file\">\n <span class=\"name\">${esc(f.name)}</span>\n <span class=\"size\">${this._fmtSize(f.size)}</span>\n <button type=\"button\" class=\"remove\" data-i=\"${i}\" part=\"remove\" aria-label=\"Remover ${esc(f.name)}\">×</button>\n </div>`,\n )\n .join('');\n area.querySelectorAll<HTMLButtonElement>('.remove').forEach((btn) =>\n btn.addEventListener('click', () => {\n const removed = this._files.splice(Number(btn.dataset.i), 1)[0];\n this.value = this._files.map((f) => f.name).join(', ');\n this.progress = 0;\n this._syncFilesArea();\n this.dispatchEvent(\n new CustomEvent('remove', { bubbles: true, composed: true, detail: { file: removed } }),\n );\n }),\n );\n }\n}\n\nexport function defineFxFileUpload(): typeof FxFileUpload {\n return defineElement('fx-fileupload', FxFileUpload);\n}\n\n\n"],"names":[],"mappings":";;;;;AA0BO,MAAM,gBAAN,MAAM,sBAAqB,cAAc;AAAA,EAAzC,cAAA;AAAA,UAAA,GAAA,SAAA;AA+NL,SAAQ,SAAiB,CAAA;AACzB,SAAQ,QAAQ;AAAA,EAAA;AAAA,EAjFhB,WAAoB,qBAA+B;AACjD,WAAO,CAAC,QAAQ,QAAQ,YAAY,SAAS,QAAQ,UAAU,YAAY,YAAY,iBAAiB,YAAY,SAAS,MAAM;AAAA,EACrI;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;AAAA,EAG5D,IAAI,QAAgB;AAAE,WAAO,KAAK,QAAQ,OAAO;AAAA,EAAG;AAAA,EACpD,IAAI,MAAM,GAAW;AAAE,SAAK,aAAa,SAAS,CAAC;AAAA,EAAG;AAAA,EAEtD,IAAI,WAAmB;AAAE,WAAO,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,OAAO,KAAK,QAAQ,YAAY,GAAG,CAAC,KAAK,CAAC,CAAC;AAAA,EAAG;AAAA,EACxG,IAAI,SAAS,GAAW;AAAE,SAAK,aAAa,YAAY,OAAO,CAAC,CAAC;AAAA,EAAG;AAAA;AAAA,EAGpE,IAAI,QAAgB;AAAE,WAAO,KAAK;AAAA,EAAQ;AAAA,EAC1C,IAAI,MAAM,OAAe;AACvB,SAAK,SAAS,MAAM,KAAK,KAAK;AAC9B,SAAK,QAAQ,KAAK,OAAO,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI;AACrD,SAAK,eAAA;AAAA,EACP;AAAA;AAAA,EAGmB,eAAyC;AAC1D,UAAM,QAAQ,KAAK;AACnB,QAAI,CAAC,KAAK,QAAQ,MAAM,WAAW,EAAG,QAAO;AAC7C,UAAM,KAAK,IAAI,SAAA;AACf,eAAW,KAAK,MAAO,IAAG,OAAO,KAAK,MAAM,GAAG,EAAE,IAAI;AACrD,WAAO;AAAA,EACT;AAAA,EAEmB,SAAe;AAChC,UAAM,OAAO,KAAK,QAAQ,QAAQ,OAAO;AACzC,UAAM,WAAW,SAAS;AAC1B,UAAM,QAAQ,KAAK,QAAQ,SAAS,kBAAkB;AACtD,UAAM,OAAO,KAAK,QAAQ,MAAM;AAChC,UAAM,SAAS,KAAK,QAAQ,QAAQ;AACpC,UAAM,WAAW,KAAK,QAAQ,UAAU;AACxC,UAAM,WAAW,KAAK,QAAQ,UAAU;AACxC,UAAM,eAAe,KAAK,QAAQ,eAAe;AACjD,UAAM,MAAM,KAAK;AAEjB,UAAM,YAAY,qCAAqC,SAAS,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,IAAI,WAAW,aAAa,EAAE,IAAI,WAAW,aAAa,EAAE;AAE1J,SAAK,YAAY;AAAA,QACb,WACE,4EAA4E,IAAI,KAAK,CAAC;AAAA,eACjF,OAAO,qDAAqD,cAAc,IAAI,CAAC,YAAY,EAAE;AAAA,qBACvF,IAAI,KAAK,CAAC;AAAA;AAAA,qBAGrB;AAAA,eACK,OAAO,qDAAqD,cAAc,IAAI,CAAC,YAAY,EAAE;AAAA,qBACvF,IAAI,KAAK,CAAC;AAAA,qBACV;AAAA,QACb,SAAS;AAAA;AAAA,QAET,eAAe;AAAA;AAAA,+DAEwC,GAAG;AAAA,2CACvB,GAAG;AAAA;AAAA,4BAElB,GAAG;AAAA,gBACf,EAAE;AAAA,KACb;AAED,SAAK,YAAA;AACL,SAAK,eAAA;AACL,SAAK,gBAAA;AAEL,QAAI,CAAC,KAAK,SAAS,gBAAgB,OAAO,OAAO,KAAK,OAAO,QAAQ;AACnE,WAAK,QAAQ;AACb,WAAK,cAAc,IAAI,YAAY,YAAY,EAAE,SAAS,MAAM,UAAU,KAAA,CAAM,CAAC;AAAA,IACnF;AACA,QAAI,MAAM,IAAK,MAAK,QAAQ;AAAA,EAC9B;AAAA,EAKQ,UAAmC;AACzC,WAAO,KAAK,KAAK,cAAgC,SAAS;AAAA,EAC5D;AAAA,EAEQ,cAAoB;AAC1B,UAAM,SAAS,KAAK,QAAA;AACpB,QAAI,CAAC,OAAQ;AACb,UAAM,UAAU,KAAK,KAAK,cAAc,MAAM,KAAK,KAAK,KAAK,cAAc,OAAO;AAClF,aAAS,iBAAiB,SAAS,MAAM,OAAO,OAAO;AACvD,aAAS,iBAAiB,WAAW,CAAC,MAAM;AAC1C,UAAK,EAAoB,QAAQ,WAAY,EAAoB,QAAQ,KAAK;AAC5E,UAAE,eAAA;AACF,eAAO,MAAA;AAAA,MACT;AAAA,IACF,CAAC;AACD,WAAO,iBAAiB,UAAU,MAAM,KAAK,aAAa,OAAO,SAAS,CAAA,CAAE,CAAC;AAG7E,UAAM,OAAO,KAAK,KAAK,cAA2B,OAAO;AACzD,QAAI,CAAC,KAAM;AACX,SAAK,iBAAiB,YAAY,CAAC,MAAM;AAAE,QAAE,eAAA;AAAkB,WAAK,UAAU,IAAI,MAAM;AAAA,IAAG,CAAC;AAC5F,SAAK,iBAAiB,aAAa,MAAM,KAAK,UAAU,OAAO,MAAM,CAAC;AACtE,SAAK,iBAAiB,QAAQ,CAAC,MAAM;AACnC,QAAE,eAAA;AACF,WAAK,UAAU,OAAO,MAAM;AAC5B,UAAI,EAAE,cAAc,MAAM,aAAa,aAAa,EAAE,aAAa,KAAK;AAAA,IAC1E,CAAC;AAAA,EACH;AAAA,EAEQ,aAAa,MAA+B;AAClD,UAAM,SAAS,KAAK,QAAQ,UAAU,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,OAAO;AACrF,SAAK,QAAQ;AACb,UAAM,SAAS,KAAK,QAAA;AACpB,QAAI,eAAe,QAAQ;AAC3B,SAAK;AAAA,MACH,IAAI,YAAY,UAAU,EAAE,SAAS,MAAM,UAAU,MAAM,QAAQ,EAAE,OAAO,OAAA,GAAU;AAAA,IAAA;AAAA,EAE1F;AAAA,EAEQ,SAAS,OAAuB;AACtC,QAAI,QAAQ,KAAM,QAAO,GAAG,KAAK;AACjC,QAAI,QAAQ,OAAO,KAAM,QAAO,IAAI,QAAQ,MAAM,QAAQ,CAAC,CAAC;AAC5D,WAAO,IAAI,SAAS,OAAO,OAAO,QAAQ,CAAC,CAAC;AAAA,EAC9C;AAAA;AAAA,EAGS,oBAA0B;AACjC,SAAK,SAAS,CAAA;AACd,SAAK,QAAQ;AACb,SAAK,gBAAA;AAAA,EACP;AAAA;AAAA,EAGQ,iBAAuB;AAC7B,UAAM,OAAO,KAAK,MAAM,gBAAgB,QAAQ;AAChD,QAAI,CAAC,KAAM;AACX,SAAK,YAAY,KAAK,OACnB;AAAA,MACC,CAAC,GAAG,MAAM;AAAA;AAAA,+BAEa,IAAI,EAAE,IAAI,CAAC;AAAA,+BACX,KAAK,SAAS,EAAE,IAAI,CAAC;AAAA,yDACK,CAAC,uCAAuC,IAAI,EAAE,IAAI,CAAC;AAAA;AAAA,IAAA,EAGrG,KAAK,EAAE;AACV,SAAK,iBAAoC,SAAS,EAAE;AAAA,MAAQ,CAAC,QAC3D,IAAI,iBAAiB,SAAS,MAAM;AAClC,cAAM,UAAU,KAAK,OAAO,OAAO,OAAO,IAAI,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;AAC9D,aAAK,QAAQ,KAAK,OAAO,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI;AACrD,aAAK,WAAW;AAChB,aAAK,eAAA;AACL,aAAK;AAAA,UACH,IAAI,YAAY,UAAU,EAAE,SAAS,MAAM,UAAU,MAAM,QAAQ,EAAE,MAAM,QAAA,GAAW;AAAA,QAAA;AAAA,MAE1F,CAAC;AAAA,IAAA;AAAA,EAEL;AACF;AA/SE,cAAgB,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MA2IrB,mBAAmB;AAAA;AA5IlB,IAAM,eAAN;AAkTA,SAAS,qBAA0C;AACxD,SAAO,cAAc,iBAAiB,YAAY;AACpD;"}
1
+ {"version":3,"file":"fileupload.js","sources":["../../../src/components/fileupload/fileupload.ts"],"sourcesContent":["import { FxFormElement } from '../../core/form-element';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\nimport { esc } from '../../core/sanitize';\nimport { FENIX_ICON_BASE_CSS, fenixIconHtml } from '../../icons/base-css';\n\n/**\n * <fx-fileupload> — Seletor de arquivos com upload e progresso.\n *\n * Form-Associated Custom Element: dentro de um <form> entra no FormData\n * (via atributo `name`) com os File's selecionados — o upload é enviado\n * junto com o submit do form, como um <input type=\"file\"> nativo.\n *\n * Atributos:\n * - mode (basic|advanced — `basic`: botão; `advanced`: dropzone com drag & drop);\n * - size (sm|md|lg, padrão md);\n * - severity (primary|secondary|success|warning|danger|info — cor do botão no modo basic);\n * - label (texto do botão/área, padrão 'Escolher arquivo');\n * - icon (glifo/emoji exibido antes do texto — sem o atributo, sem ícone);\n * - accept, multiple, disabled (como no <input type=\"file\">);\n * - show-progress (exibe barra + % de upload);\n * - progress (0-100 — o consumidor atualiza durante o upload real);\n * - value (nome(s) do(s) arquivo(s) carregado(s), somente leitura/espelho).\n *\n * Eventos (composed): `select` (detail: { files: File[] }), `remove`, `complete`.\n */\nexport class FxFileUpload extends FxFormElement {\n static override styles = css`\n :host {\n display: inline-block;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n }\n :host([disabled]) { opacity: 0.55; pointer-events: none; }\n input[type='file'] { display: none; }\n\n /* ---- botão (basic) ---- */\n .btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--fx-space-sm);\n /* Largura acompanha o host: CSS externo/classe/inline no elemento esticam\n o botão sem precisar do full (host é shrink-to-fit por padrão). */\n width: 100%;\n min-height: var(--fx-size-md);\n /* Padding vertical no orçamento do token --fx-size-* ⇒ altura padrão. */\n padding: var(--fx-space-sm) var(--fx-space-lg);\n font-family: inherit;\n font-size: inherit;\n font-weight: var(--fx-font-weight);\n line-height: var(--fx-font-line-height);\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 cursor: pointer;\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 background var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n .btn:hover { border-color: var(--fx-border-hover); background: var(--fx-surface-surface-hover, rgba(0,0,0,0.03)); }\n .btn:focus-visible {\n outline: none;\n border-color: var(--fx-color-primary);\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n /* Severities (como no <fx-button>) — aplicam-se ao botão do modo basic */\n :host([severity='primary']) .btn { background: var(--fx-color-primary); border-color: transparent; color: #fff; }\n :host([severity='secondary']) .btn { background: var(--fx-color-secondary); border-color: transparent; color: #fff; }\n :host([severity='success']) .btn { background: var(--fx-color-success); border-color: transparent; color: #fff; }\n :host([severity='warning']) .btn { background: var(--fx-color-warning); border-color: transparent; color: #fff; }\n :host([severity='danger']) .btn { background: var(--fx-color-danger); border-color: transparent; color: #fff; }\n :host([severity='info']) .btn { background: var(--fx-color-info); border-color: transparent; color: #fff; }\n :host([severity]) .btn:hover { filter: brightness(0.88); }\n :host([severity]) .btn:focus-visible { border-color: transparent; }\n :host([size='sm']) .btn { min-height: var(--fx-size-sm); padding: var(--fx-space-xs) var(--fx-space-md); font-size: calc(var(--fx-font-size) - 2px); }\n :host([size='lg']) .btn { min-height: var(--fx-size-lg); padding: var(--fx-space-sm) var(--fx-space-xl); font-size: calc(var(--fx-font-size) + 4px); }\n :host([size='sm']) .drop { min-height: 72px; }\n :host([size='lg']) .drop { min-height: 132px; }\n\n /* ---- dropzone (advanced) ---- */\n .drop {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--fx-space-sm);\n /* Acompanha a largura do host (definida por CSS externo/classe/inline). */\n width: 100%;\n min-height: 100px;\n padding: var(--fx-space-lg);\n border: 2px dashed var(--fx-border-default);\n border-radius: var(--fx-radius-md);\n color: var(--fx-text-muted);\n cursor: pointer;\n text-align: center;\n transition: border-color var(--fx-motion-duration-normal) var(--fx-motion-easing), background var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n .drop:hover, .drop.over {\n border-color: var(--fx-color-primary);\n background: color-mix(in srgb, var(--fx-color-primary) 6%, transparent);\n }\n .drop .icon { font-size: calc(var(--fx-font-size) + 12px); line-height: 1; }\n .drop .hint { font-size: calc(var(--fx-font-size) - 2px); }\n\n /* ---- ícone comum ---- */\n .icon { line-height: 1; }\n\n /* ---- arquivo(s) selecionado(s) ---- */\n .files { display: flex; flex-direction: column; gap: var(--fx-space-xs); margin-top: var(--fx-space-sm); }\n .file {\n display: flex;\n align-items: center;\n gap: var(--fx-space-sm);\n padding: var(--fx-space-xs) var(--fx-space-sm);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-sm);\n font-size: calc(var(--fx-font-size) - 2px);\n color: var(--fx-text-default);\n }\n .file .name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .file .size { color: var(--fx-text-muted); white-space: nowrap; }\n .file .remove {\n border: none;\n background: transparent;\n color: var(--fx-text-muted);\n cursor: pointer;\n font-size: calc(var(--fx-font-size) + 2px);\n line-height: 1;\n padding: 0 var(--fx-space-xs);\n border-radius: var(--fx-radius-full);\n }\n .file .remove:hover { color: var(--fx-color-danger); }\n\n /* ---- progresso ---- */\n .progress-row { display: flex; align-items: center; gap: var(--fx-space-sm); margin-top: var(--fx-space-sm); }\n .track {\n flex: 1;\n height: 6px;\n background: var(--fx-surface-surface-hover);\n border-radius: var(--fx-radius-full);\n overflow: hidden;\n }\n .bar {\n height: 100%;\n width: 0%;\n background: var(--fx-color-primary);\n border-radius: var(--fx-radius-full);\n transition: width var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n :host([progress='100']) .bar { background: var(--fx-color-success); }\n .pct {\n font-size: calc(var(--fx-font-size) - 2px);\n font-weight: 600;\n color: var(--fx-text-default);\n min-width: 34px;\n text-align: right;\n }\n /* Full width: o host estica até o pai e o botão/dropzone acompanha. */\n :host([full]) { display: block; width: 100%; }\n :host([full]) .btn { width: 100%; }\n :host([full]) .drop { width: 100%; }\n /* Base dos glifos Fenix Icons (o @font-face vem de @wrrdev/fenix-ui/icons). */\n ${FENIX_ICON_BASE_CSS}\n `;\n\n static override get observedAttributes(): string[] {\n return ['mode', 'size', 'severity', 'label', 'icon', 'accept', 'multiple', 'disabled', 'show-progress', 'progress', 'value', 'full'];\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 /** Nomes dos arquivos selecionados (espelho em `value`). */\n get value(): string { return this.getAttr('value'); }\n set value(v: string | null | undefined) {\n this.setAttribute('value', v == null ? '' : String(v));\n }\n\n get disabled(): boolean {\n return this.hasAttr('disabled');\n }\n set disabled(v: boolean) {\n this.toggleAttr('disabled', Boolean(v));\n }\n\n get multiple(): boolean {\n return this.hasAttr('multiple');\n }\n set multiple(v: boolean) {\n this.toggleAttr('multiple', Boolean(v));\n }\n\n get full(): boolean {\n return this.hasAttr('full');\n }\n set full(v: boolean) {\n this.toggleAttr('full', Boolean(v));\n }\n\n /**\n * Nome literal: o Vue NÃO cameliza atributos em elementos customizados,\n * então `:show-progress` procura a propriedade `'show-progress'`.\n */\n get 'show-progress'(): boolean {\n return this.hasAttr('show-progress');\n }\n set 'show-progress'(v: boolean) {\n this.toggleAttr('show-progress', Boolean(v));\n }\n\n get progress(): number { return Math.min(100, Math.max(0, Number(this.getAttr('progress', '0')) || 0)); }\n set progress(v: number) { this.setAttribute('progress', String(v)); }\n\n /** Arquivos selecionados (não serializável em atributo). */\n get files(): File[] { return this._files; }\n set files(files: File[]) {\n this._files = Array.from(files);\n this.value = this._files.map((f) => f.name).join(', ');\n this._syncFilesArea();\n }\n\n /** No FormData do form: os arquivos selecionados, como o <input type=\"file\">. */\n protected override getFormValue(): string | FormData | null {\n const files = this._files;\n if (!this.name || files.length === 0) return null;\n const fd = new FormData();\n for (const f of files) fd.append(this.name, f, f.name);\n return fd;\n }\n\n protected override render(): void {\n const mode = this.getAttr('mode', 'basic');\n const advanced = mode === 'advanced';\n const label = this.getAttr('label', 'Escolher arquivo');\n const icon = this.getAttr('icon');\n const accept = this.getAttr('accept');\n const multiple = this.hasAttr('multiple');\n const disabled = this.hasAttr('disabled');\n const showProgress = this.hasAttr('show-progress');\n const pct = this.progress;\n\n const fileInput = `<input type=\"file\" class=\"picker\" ${accept ? `accept=\"${esc(accept)}\"` : ''} ${multiple ? 'multiple' : ''} ${disabled ? 'disabled' : ''} />`;\n\n this.setTemplate(`\n ${advanced\n ? `<div class=\"drop\" part=\"dropzone\" role=\"button\" tabindex=\"0\" aria-label=\"${esc(label)}\">\n ${icon ? `<span class=\"icon\" part=\"icon\" aria-hidden=\"true\">${fenixIconHtml(icon)}</span>` : ''}\n <span>${esc(label)}</span>\n <span class=\"hint\">ou arraste e solte aqui</span>\n </div>`\n : `<button type=\"button\" class=\"btn\" part=\"button\">\n ${icon ? `<span class=\"icon\" part=\"icon\" aria-hidden=\"true\">${fenixIconHtml(icon)}</span>` : ''}\n <span>${esc(label)}</span>\n </button>`}\n ${fileInput}\n <div class=\"files\" part=\"files\"></div>\n ${showProgress ? `\n <div class=\"progress-row\" part=\"progress\">\n <div class=\"track\" role=\"progressbar\" aria-valuenow=\"${pct}\" aria-valuemin=\"0\" aria-valuemax=\"100\">\n <div class=\"bar\" style=\"width: ${pct}%\"></div>\n </div>\n <span class=\"pct\">${pct}%</span>\n </div>` : ''}\n `);\n\n this._bindPicker();\n this._syncFilesArea();\n this.updateFormValue();\n\n if (!this._done && showProgress && pct >= 100 && this._files.length) {\n this._done = true;\n this.dispatchEvent(new CustomEvent('complete', { bubbles: true, composed: true }));\n }\n if (pct < 100) this._done = false;\n }\n\n private _files: File[] = [];\n private _done = false;\n\n private _picker(): HTMLInputElement | null {\n return this.root.querySelector<HTMLInputElement>('.picker');\n }\n\n private _bindPicker(): void {\n const picker = this._picker();\n if (!picker) return;\n const trigger = this.root.querySelector('.btn') ?? this.root.querySelector('.drop');\n trigger?.addEventListener('click', () => picker.click());\n trigger?.addEventListener('keydown', (e) => {\n if ((e as KeyboardEvent).key === 'Enter' || (e as KeyboardEvent).key === ' ') {\n e.preventDefault();\n picker.click();\n }\n });\n picker.addEventListener('change', () => this._acceptFiles(picker.files ?? []));\n\n // drag & drop (advanced)\n const drop = this.root.querySelector<HTMLElement>('.drop');\n if (!drop) return;\n drop.addEventListener('dragover', (e) => { e.preventDefault(); drop.classList.add('over'); });\n drop.addEventListener('dragleave', () => drop.classList.remove('over'));\n drop.addEventListener('drop', (e) => {\n e.preventDefault();\n drop.classList.remove('over');\n if (e.dataTransfer?.files.length) this._acceptFiles(e.dataTransfer.files);\n });\n }\n\n private _acceptFiles(list: FileList | File[]): void {\n const picked = this.hasAttr('multiple') ? Array.from(list) : [list[0]].filter(Boolean);\n this.files = picked;\n const picker = this._picker();\n if (picker) picker.value = '';\n this.dispatchEvent(\n new CustomEvent('select', { bubbles: true, composed: true, detail: { files: picked } }),\n );\n }\n\n private _fmtSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n }\n\n /** form.reset(): limpa os arquivos selecionados. */\n override formResetCallback(): void {\n this._files = [];\n this.value = ''; // `value` é observado → re-render + _syncFilesArea\n this.updateFormValue();\n }\n\n /** Renderiza a lista de arquivos sem re-renderizar o template (mantém estado). */\n private _syncFilesArea(): void {\n const area = this.root?.querySelector?.('.files') as HTMLElement | null;\n if (!area) return;\n area.innerHTML = this._files\n .map(\n (f, i) => `\n <div class=\"file\" part=\"file\">\n <span class=\"name\">${esc(f.name)}</span>\n <span class=\"size\">${this._fmtSize(f.size)}</span>\n <button type=\"button\" class=\"remove\" data-i=\"${i}\" part=\"remove\" aria-label=\"Remover ${esc(f.name)}\">×</button>\n </div>`,\n )\n .join('');\n area.querySelectorAll<HTMLButtonElement>('.remove').forEach((btn) =>\n btn.addEventListener('click', () => {\n const removed = this._files.splice(Number(btn.dataset.i), 1)[0];\n this.value = this._files.map((f) => f.name).join(', ');\n this.progress = 0;\n this._syncFilesArea();\n this.dispatchEvent(\n new CustomEvent('remove', { bubbles: true, composed: true, detail: { file: removed } }),\n );\n }),\n );\n }\n}\n\nexport function defineFxFileUpload(): typeof FxFileUpload {\n return defineElement('fx-fileupload', FxFileUpload);\n}\n\n\n"],"names":[],"mappings":"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"}
@@ -23,6 +23,18 @@ import { FxElement } from "../../core/base";
23
23
  */
24
24
  export declare class FxFloatlabel extends FxElement {
25
25
  static get observedAttributes(): string[];
26
+ get error(): boolean;
27
+ set error(value: boolean);
28
+ get invalid(): boolean;
29
+ set invalid(value: boolean);
30
+ get success(): boolean;
31
+ set success(value: boolean);
32
+ get valid(): boolean;
33
+ set valid(value: boolean);
34
+ get full(): boolean;
35
+ set full(value: boolean);
36
+ get errorText(): string;
37
+ set errorText(value: string | null | undefined);
26
38
  static styles: string;
27
39
  private targetControl;
28
40
  private observer;
@@ -30,6 +42,11 @@ export declare class FxFloatlabel extends FxElement {
30
42
  /** <label> adotada do light DOM — vive no Shadow DOM após o 1º render. */
31
43
  private _label;
32
44
  private _labelClickBound;
45
+ /** Observa o light DOM para readotar uma <label> recriada pelo framework. */
46
+ private _labelObserver?;
47
+ /** Timer do `focusout`: confirma o estado no próximo tick (foco já movido). */
48
+ private _syncTimer;
49
+ protected connectedCallback(): void;
33
50
  protected render(): void;
34
51
  protected disconnectedCallback(): void;
35
52
  /** Campo de controle = qualquer filho direto que não seja label. */
@@ -37,6 +54,9 @@ export declare class FxFloatlabel extends FxElement {
37
54
  /** Label = filho direto <label>. */
38
55
  private findLabel;
39
56
  private attachEvents;
57
+ /** Reavalia o estado no próximo tick (o foco já saiu do campo). */
58
+ private scheduleSync;
59
+ private cancelScheduledSync;
40
60
  /** Observa mudanças de atributos do controle (value, open, values...). */
41
61
  private syncControlReference;
42
62
  private syncState;
@@ -1 +1 @@
1
- {"version":3,"file":"floatlabel.d.ts","sourceRoot":"","sources":["../../../src/components/floatlabel/floatlabel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAI5C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,YAAa,SAAQ,SAAS;IAC1C,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAID,OAAgB,MAAM,SAqIpB;IACF,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,cAAc,CAAS;IAC/B,0EAA0E;IAC1E,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,gBAAgB,CAAS;IAEjC,SAAS,CAAC,MAAM,IAAI,IAAI;cAmDL,oBAAoB,IAAI,IAAI;IAK/C,oEAAoE;IACpE,OAAO,CAAC,WAAW;IAQnB,oCAAoC;IACpC,OAAO,CAAC,SAAS;IAQjB,OAAO,CAAC,YAAY;IAwBpB,0EAA0E;IAC1E,OAAO,CAAC,oBAAoB;IAoB5B,OAAO,CAAC,SAAS;IAwCjB;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,SAAS;IAgBjB,OAAO,CAAC,SAAS;IAmBjB,OAAO,CAAC,UAAU;IA8BlB,OAAO,CAAC,QAAQ;IAQhB,OAAO,CAAC,eAAe;CAIvB;AAED,wBAAgB,kBAAkB,IAAI,OAAO,YAAY,CAExD"}
1
+ {"version":3,"file":"floatlabel.d.ts","sourceRoot":"","sources":["../../../src/components/floatlabel/floatlabel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAI5C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,YAAa,SAAQ,SAAS;IAC1C,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,IAAI,KAAK,IAAI,OAAO,CAEnB;IACD,IAAI,KAAK,CAAC,KAAK,EAAE,OAAO,EAEvB;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAEzB;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAEzB;IAED,IAAI,KAAK,IAAI,OAAO,CAEnB;IACD,IAAI,KAAK,CAAC,KAAK,EAAE,OAAO,EAEvB;IAED,IAAI,IAAI,IAAI,OAAO,CAElB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,EAEtB;IAED,IAAI,SAAS,IAAI,MAAM,CAEtB;IACD,IAAI,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAE7C;IAKD,OAAgB,MAAM,SAmJpB;IACF,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,cAAc,CAAS;IAC/B,0EAA0E;IAC1E,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,gBAAgB,CAAS;IACjC,6EAA6E;IAC7E,OAAO,CAAC,cAAc,CAAC,CAAmB;IAC1C,+EAA+E;IAC/E,OAAO,CAAC,UAAU,CAA4C;cAE3C,iBAAiB,IAAI,IAAI;IAS5C,SAAS,CAAC,MAAM,IAAI,IAAI;cAmDL,oBAAoB,IAAI,IAAI;IAQ/C,oEAAoE;IACpE,OAAO,CAAC,WAAW;IAQnB,oCAAoC;IACpC,OAAO,CAAC,SAAS;IAQjB,OAAO,CAAC,YAAY;IA2BpB,mEAAmE;IACnE,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,mBAAmB;IAO3B,0EAA0E;IAC1E,OAAO,CAAC,oBAAoB;IAoB5B,OAAO,CAAC,SAAS;IA4CjB;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,SAAS;IAgBjB,OAAO,CAAC,SAAS;IAmBjB,OAAO,CAAC,UAAU;IA8BlB,OAAO,CAAC,QAAQ;IAQhB,OAAO,CAAC,eAAe;CAIvB;AAED,wBAAgB,kBAAkB,IAAI,OAAO,YAAY,CAExD"}