@wrrdev/fenix-ui 1.1.13 → 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.
- package/README.md +3 -3
- package/dist/components/autocomplete/autocomplete.d.ts +11 -5
- package/dist/components/autocomplete/autocomplete.d.ts.map +1 -1
- package/dist/components/autocomplete/autocomplete.js +18 -2
- package/dist/components/autocomplete/autocomplete.js.map +1 -1
- package/dist/components/button/button.d.ts +15 -0
- package/dist/components/button/button.d.ts.map +1 -1
- package/dist/components/button/button.js +20 -1
- package/dist/components/button/button.js.map +1 -1
- package/dist/components/checkbox/checkbox.d.ts +20 -4
- package/dist/components/checkbox/checkbox.d.ts.map +1 -1
- package/dist/components/checkbox/checkbox.js +39 -2
- package/dist/components/checkbox/checkbox.js.map +1 -1
- package/dist/components/datepicker/datepicker.d.ts +16 -2
- package/dist/components/datepicker/datepicker.d.ts.map +1 -1
- package/dist/components/datepicker/datepicker.js +46 -2
- package/dist/components/datepicker/datepicker.js.map +1 -1
- package/dist/components/fileupload/fileupload.d.ts +10 -2
- package/dist/components/fileupload/fileupload.d.ts.map +1 -1
- package/dist/components/fileupload/fileupload.js +17 -2
- package/dist/components/fileupload/fileupload.js.map +1 -1
- package/dist/components/floatlabel/floatlabel.d.ts +9 -3
- package/dist/components/floatlabel/floatlabel.d.ts.map +1 -1
- package/dist/components/floatlabel/floatlabel.js +22 -7
- package/dist/components/floatlabel/floatlabel.js.map +1 -1
- package/dist/components/input/input.d.ts +14 -7
- package/dist/components/input/input.d.ts.map +1 -1
- package/dist/components/input/input.js +18 -2
- package/dist/components/input/input.js.map +1 -1
- package/dist/components/knob/knob.d.ts +7 -3
- package/dist/components/knob/knob.d.ts.map +1 -1
- package/dist/components/knob/knob.js +4 -2
- package/dist/components/knob/knob.js.map +1 -1
- package/dist/components/multiselect/multiselect.d.ts +13 -3
- package/dist/components/multiselect/multiselect.d.ts.map +1 -1
- package/dist/components/multiselect/multiselect.js +27 -2
- package/dist/components/multiselect/multiselect.js.map +1 -1
- package/dist/components/radio/radio.d.ts +27 -4
- package/dist/components/radio/radio.d.ts.map +1 -1
- package/dist/components/radio/radio.js +67 -8
- package/dist/components/radio/radio.js.map +1 -1
- package/dist/components/rating/rating.d.ts +8 -4
- package/dist/components/rating/rating.d.ts.map +1 -1
- package/dist/components/rating/rating.js +5 -2
- package/dist/components/rating/rating.js.map +1 -1
- package/dist/components/select/select.d.ts +9 -3
- package/dist/components/select/select.d.ts.map +1 -1
- package/dist/components/select/select.js +11 -2
- package/dist/components/select/select.js.map +1 -1
- package/dist/components/slider/slider.d.ts +12 -6
- package/dist/components/slider/slider.d.ts.map +1 -1
- package/dist/components/slider/slider.js +10 -2
- package/dist/components/slider/slider.js.map +1 -1
- package/dist/components/switch/switch.d.ts +20 -4
- package/dist/components/switch/switch.d.ts.map +1 -1
- package/dist/components/switch/switch.js +40 -2
- package/dist/components/switch/switch.js.map +1 -1
- package/dist/components/textarea/textarea.d.ts +12 -5
- package/dist/components/textarea/textarea.d.ts.map +1 -1
- package/dist/components/textarea/textarea.js +12 -2
- package/dist/components/textarea/textarea.js.map +1 -1
- package/dist/components/toast/toast.d.ts +17 -5
- package/dist/components/toast/toast.d.ts.map +1 -1
- package/dist/components/toast/toast.js +161 -21
- package/dist/components/toast/toast.js.map +1 -1
- package/dist/components/toggle-button-group/toggle-button-group.d.ts +13 -5
- package/dist/components/toggle-button-group/toggle-button-group.d.ts.map +1 -1
- package/dist/components/toggle-button-group/toggle-button-group.js +19 -2
- package/dist/components/toggle-button-group/toggle-button-group.js.map +1 -1
- package/dist/core/base.d.ts +7 -0
- package/dist/core/base.d.ts.map +1 -1
- package/dist/core/base.js +4 -1
- package/dist/core/base.js.map +1 -1
- package/dist/core/form-element.d.ts +81 -0
- package/dist/core/form-element.d.ts.map +1 -0
- package/dist/core/form-element.js +129 -0
- package/dist/core/form-element.js.map +1 -0
- package/dist/core/presets.d.ts +8 -3
- package/dist/core/presets.d.ts.map +1 -1
- package/dist/core/presets.js +6 -17
- package/dist/core/presets.js.map +1 -1
- package/dist/core/types.d.ts +4 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/docs/app.d.ts.map +1 -1
- package/dist/docs/componentes/autocomplete.doc.d.ts.map +1 -1
- package/dist/docs/componentes/checkbox.doc.d.ts.map +1 -1
- package/dist/docs/componentes/datepicker.doc.d.ts.map +1 -1
- package/dist/docs/componentes/fileupload.doc.d.ts.map +1 -1
- package/dist/docs/componentes/input.doc.d.ts.map +1 -1
- package/dist/docs/componentes/knob.doc.d.ts.map +1 -1
- package/dist/docs/componentes/multiselect.doc.d.ts.map +1 -1
- package/dist/docs/componentes/rating.doc.d.ts.map +1 -1
- package/dist/docs/componentes/select.doc.d.ts.map +1 -1
- package/dist/docs/componentes/slider.doc.d.ts.map +1 -1
- package/dist/docs/componentes/switch.doc.d.ts.map +1 -1
- package/dist/docs/componentes/textarea.doc.d.ts.map +1 -1
- package/dist/docs/componentes/toast.doc.d.ts.map +1 -1
- package/dist/docs/componentes/toggle-button-group.doc.d.ts.map +1 -1
- package/dist/docs/frameworks.d.ts +75 -0
- package/dist/docs/frameworks.d.ts.map +1 -0
- package/dist/docs/ui/code.d.ts +4 -0
- package/dist/docs/ui/code.d.ts.map +1 -1
- package/dist/fenix-ui.umd.min.js +214 -176
- package/dist/fenix-ui4.esm.js +657 -67
- package/dist/fenix-ui4.esm.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select.js","sources":["../../../src/components/select/select.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-select> — Campo de seleção com dropdown customizado.\n *\n * Escreva os filhos como `<option>` nativos no light DOM; eles são\n * espelhados automaticamente (MutationObserver):\n *\n * <fx-select value=\"b\" searchable clearable>\n * <option value=\"a\">Opção A</option>\n * <option value=\"b\">Opção B</option>\n * </fx-select>\n *\n * Atributos: value, size (sm|md|lg), disabled, placeholder,\n * searchable, clearable, search-placeholder, no-results,\n * placement (auto|top|bottom — direção do painel; `auto` inverte quando\n * não cabe na viewport).\n * Evento: `change` (composed, detail: { value }).\n */\nexport class FxSelect extends FxElement {\n static override styles = css`\n :host {\n display: inline-block;\n vertical-align: middle;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n position: relative;\n /* Largura no HOST (padrão: conteúdo com mín. 200px). CSS externo, classes\n e style inline no elemento definem a largura sem precisar do full. */\n width: var(--fx-select-width, max-content);\n min-width: var(--fx-select-min-width, 200px);\n /* Espaço entre o campo e o painel + altura máxima do painel. Expostos\n como custom property porque ::part() do lado do consumidor tem\n prioridade MENOR que a folha do shadow root e não vence o top. */\n --fx-select-panel-offset: 4px;\n --fx-select-panel-max-height: 260px;\n }\n .trigger {\n display: inline-flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--fx-space-sm);\n box-sizing: border-box;\n min-height: var(--fx-size-md);\n /* Acompanha a largura definida no :host. */\n width: 100%;\n font: inherit;\n /* Altura/linha determinísticas: a caixa fica no token --fx-size-*\n (o line-height fixo impede que a herança da página estoure o min-height). */\n line-height: var(--fx-font-line-height);\n font-weight: var(--fx-font-weight);\n color: var(--fx-text-default);\n text-align: left;\n background-color: var(--fx-surface-background);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-md);\n padding: var(--fx-space-xs) var(--fx-space-md);\n cursor: pointer;\n transition:\n border-color var(--fx-motion-duration-normal) var(--fx-motion-easing),\n box-shadow var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n .trigger:hover { border-color: var(--fx-border-hover); }\n .trigger:focus-visible,\n :host([open]) .trigger {\n outline: none;\n border-color: var(--fx-color-primary);\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n :host([size='sm']) { min-width: var(--fx-select-min-width-sm, 180px); }\n :host([size='sm']) .trigger { min-height: var(--fx-size-sm); }\n :host([size='lg']) .trigger { min-height: var(--fx-size-lg); font-size: calc(var(--fx-font-size) + 4px); }\n /* Full width: o host estica até o pai e o trigger acompanha. */\n :host([full]) { display: block; width: 100%; }\n :host([full]) { min-width: 0; }\n :host([full]) .trigger { width: 100%; min-width: 0; }\n :host([full]) .panel { width: 100%; }\n /* Validação — borda e brilho JÁ EM REPOUSO (igual ao fx-input); o\n brilho segue --fx-effect-error-ring, que acompanha effect.focus-ring. */\n :host([error]) .trigger,\n :host([invalid]) .trigger {\n border-color: var(--fx-color-danger, #dc2626);\n box-shadow: var(--fx-effect-error-ring, 0 0 0 3px color-mix(in srgb, var(--fx-color-danger, #dc2626) 18%, transparent));\n }\n :host([error]) .trigger:focus-visible,\n :host([invalid]) .trigger:focus-visible,\n :host([error][open]) .trigger,\n :host([invalid][open]) .trigger {\n border-color: var(--fx-color-danger, #dc2626);\n box-shadow: var(--fx-effect-error-ring, 0 0 0 3px color-mix(in srgb, var(--fx-color-danger, #dc2626) 18%, transparent));\n }\n :host([success]) .trigger,\n :host([valid]) .trigger {\n border-color: var(--fx-color-success, #16a34a);\n box-shadow: var(--fx-effect-success-ring, 0 0 0 3px color-mix(in srgb, var(--fx-color-success, #16a34a) 18%, transparent));\n }\n :host([success]) .trigger:focus-visible,\n :host([valid]) .trigger:focus-visible,\n :host([success][open]) .trigger,\n :host([valid][open]) .trigger {\n border-color: var(--fx-color-success, #16a34a);\n box-shadow: var(--fx-effect-success-ring, 0 0 0 3px color-mix(in srgb, var(--fx-color-success, #16a34a) 18%, transparent));\n }\n :host([disabled]) .trigger,\n .trigger[aria-disabled='true'] { opacity: 0.55; cursor: not-allowed; background-color: var(--fx-surface-surface-hover); }\n /* O rótulo encolhe (ellipsis) e os ícones NUNCA saem do campo. */\n .label,\n .placeholder {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .placeholder { color: var(--fx-text-muted); }\n .actions {\n display: inline-flex;\n align-items: center;\n gap: var(--fx-space-xs, 8px);\n flex-shrink: 0;\n }\n .caret { font-size: calc(var(--fx-font-size) - 3px); color: var(--fx-text-muted); pointer-events: none; }\n\n /* Painel do dropdown — a POSIÇÃO vertical é definida pelas regras\n [data-placement] abaixo (o JS de ajustarPosicao decide), por isso\n top/bottom não ficam aqui. */\n .panel {\n position: absolute;\n left: 0;\n z-index: var(--fx-z-dropdown, 1000);\n width: max(100%, 220px);\n max-height: var(--fx-select-panel-max-height, 260px);\n overflow-y: auto;\n display: none;\n background: var(--fx-surface-background);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-md);\n box-shadow: var(--fx-shadow-lg);\n }\n /* Padrão: abre para baixo. */\n :host([open]:not([data-placement='top'])) .panel {\n top: calc(100% + var(--fx-select-panel-offset, 4px));\n }\n /* placement=\"top\" explícito ou auto-flip: abre para cima. */\n :host([open][placement='top']) .panel,\n :host([open][data-placement='top']) .panel {\n top: auto;\n bottom: calc(100% + var(--fx-select-panel-offset, 4px));\n }\n :host([open]) .panel { display: block; }\n .search {\n position: sticky;\n top: 0;\n width: 100%;\n box-sizing: border-box;\n font: inherit;\n color: var(--fx-text-default);\n background: var(--fx-surface-background);\n border: none;\n border-bottom: 1px solid var(--fx-border-default);\n padding: var(--fx-space-sm) var(--fx-space-md);\n outline: none;\n }\n .opt {\n display: block;\n width: 100%;\n text-align: left;\n font: inherit;\n color: var(--fx-text-default);\n background: none;\n border: none;\n padding: var(--fx-space-sm) var(--fx-space-md);\n cursor: pointer;\n }\n /* Hover e selecionado com a cor primária do tema (igual ao multiselect). */\n .opt:hover { background: color-mix(in srgb, var(--fx-color-primary) 12%, transparent); }\n .opt[aria-selected='true'] {\n background: color-mix(in srgb, var(--fx-color-primary) 18%, transparent);\n color: var(--fx-color-primary);\n font-weight: var(--fx-font-weight);\n }\n .empty { padding: var(--fx-space-sm) var(--fx-space-md); color: var(--fx-text-muted); }\n\n /* Clearable */\n .clear {\n border: none;\n background: transparent;\n color: var(--fx-text-muted);\n font-size: calc(var(--fx-font-size) + 2px);\n line-height: 1;\n cursor: pointer;\n padding: 0;\n }\n .clear:hover { color: var(--fx-color-danger); }\n .clear[hidden] { display: none; }\n `;\n\n // `value` fica FORA da observação: refleti-lo no change não pode\n // re-renderizar o template e fechar o dropdown.\n static override get observedAttributes(): string[] {\n // `invalid`/`valid` são aliases aceitos no CSS; sem observá-los o\n // setAttribute no submit não re-renderiza.\n return ['size', 'disabled', 'placeholder', 'searchable', 'clearable', 'error', 'invalid', 'success', 'valid', 'placement'];\n }\n\n /** Espaço (px) entre o campo e o painel — espelha `--fx-select-panel-offset`. */\n private static readonly GAP = 4;\n /** Margem (px) mantida entre o painel e as bordas da viewport. */\n private static readonly MARGEM = 8;\n /** Altura mínima do painel ao ser limitado pelo espaço disponível. */\n private static readonly MIN_ALTURA = 80;\n\n private observer?: MutationObserver;\n private docListener?: (e: Event) => void;\n /** Reposiciona o painel enquanto ele estiver aberto (bound em `_abrir`). */\n private onReflow?: () => void;\n\n /** Tamanho do campo. Padrão: `'md'`. */\n get size(): string {\n const s = this.getAttr('size', 'md');\n return s === 'sm' || s === 'lg' ? s : 'md';\n }\n set size(value: string) {\n this.setAttribute('size', value);\n }\n\n get value(): string {\n return this.getAttr('value');\n }\n set value(value: string) {\n this.setAttribute('value', value);\n }\n\n get disabled(): boolean {\n return this.hasAttr('disabled');\n }\n set disabled(value: boolean) {\n this.toggleAttr('disabled', Boolean(value));\n }\n\n private get options(): { value: string; label: string }[] {\n return [...this.querySelectorAll('option')].map((o) => ({\n value: o.getAttribute('value') ?? o.textContent?.trim() ?? '',\n label: o.textContent?.trim() ?? '',\n }));\n }\n\n protected override connectedCallback(): void {\n super.connectedCallback();\n // Espelha mudanças nas <option> do light DOM.\n this.observer = new MutationObserver(() => this.render());\n this.observer.observe(this, { childList: true, subtree: true, characterData: true });\n // Fecha o dropdown ao clicar fora.\n this.docListener = (e: Event) => {\n if (!this.hasAttr('open')) return;\n if (e.composedPath().includes(this)) return;\n this._fechar();\n this.render();\n };\n document.addEventListener('click', this.docListener);\n }\n\n protected override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.observer?.disconnect();\n if (this.docListener) document.removeEventListener('click', this.docListener);\n this._unbindReflow();\n }\n\n /** Abre o dropdown, posiciona o painel e passa a reposicioná-lo no reflow. */\n private _abrir(): void {\n this.setAttribute('open', '');\n this._bindReflow();\n }\n\n /** Fecha o dropdown e libera os listeners de resize/scroll. */\n private _fechar(): void {\n this.removeAttribute('open');\n // Sem isto o `data-placement` fica preso no host e a próxima abertura\n // mostraria o painel na posição invertida antes do ajuste rodar.\n this.removeAttribute('data-placement');\n this._unbindReflow();\n }\n\n /** Liga o reposicionamento (resize + scroll interno, em capture). */\n private _bindReflow(): void {\n if (this.onReflow) return;\n this.onReflow = () => {\n if (this.hasAttr('open')) this.ajustarPosicao();\n };\n window.addEventListener('resize', this.onReflow);\n window.addEventListener('scroll', this.onReflow, true);\n }\n\n private _unbindReflow(): void {\n if (!this.onReflow) return;\n window.removeEventListener('resize', this.onReflow);\n window.removeEventListener('scroll', this.onReflow, true);\n this.onReflow = undefined;\n }\n\n /**\n * Decide a posição vertical do painel medindo o espaço real da viewport.\n *\n * `placement=\"top\"`/`\"bottom\"` são respeitados literalmente; o padrão\n * (`auto`) só inverte quando o painel não cabe embaixo E couber melhor\n * acima. Também limita a altura ao espaço disponível e evita overflow\n * horizontal próximo à borda direita.\n */\n protected ajustarPosicao(): void {\n const painel = this.root.querySelector<HTMLElement>('.panel');\n const trigger = this.root.querySelector<HTMLElement>('.trigger');\n if (!painel || !trigger) return;\n\n const r = trigger.getBoundingClientRect();\n const abaixo = window.innerHeight - r.bottom; // espaço livre embaixo\n const acima = r.top; // espaço livre acima\n const desejado = painel.scrollHeight; // altura já com a busca\n const maxAltura = FxSelect._maxAltura(this);\n\n const pedido = this.getAttr('placement', 'auto');\n const paraCima =\n pedido === 'top' ||\n (pedido !== 'bottom' && abaixo < Math.min(desejado, maxAltura) && acima > abaixo);\n\n // O atributo placement do host tem precedência no CSS; data-placement\n // é o canal interno do auto-flip e usa a mesma regra de estilo.\n if (paraCima) this.setAttribute('data-placement', 'top');\n else this.removeAttribute('data-placement');\n\n // Limita a altura ao espaço real disponível (evita o painel vazar da tela).\n const disponivel = Math.max(\n FxSelect.MIN_ALTURA,\n (paraCima ? acima : abaixo) - FxSelect.GAP,\n );\n painel.style.maxHeight = `${Math.min(desejado || disponivel, disponivel, maxAltura)}px`;\n\n // Reset do deslocamento horizontal antes de remedir (o painel é recriado\n // a cada render, mas o ajuste pode rodar várias vezes com o mesmo nó).\n painel.style.left = '';\n const largura = painel.offsetWidth;\n const excesso = r.left + largura - (window.innerWidth - FxSelect.MARGEM);\n if (excesso > 0) {\n painel.style.left = `${-excesso}px`;\n painel.style.right = 'auto';\n }\n }\n\n /** Altura máxima do painel a partir de `--fx-select-panel-max-height`. */\n private static _maxAltura(host: HTMLElement): number {\n const raw = getComputedStyle(host).getPropertyValue('--fx-select-panel-max-height');\n const n = parseInt(raw, 10);\n return Number.isFinite(n) && n > 0 ? n : 260;\n }\n\n private select(value: string): void {\n this.value = value;\n this._fechar();\n this.render();\n this.dispatchEvent(\n new CustomEvent('change', { bubbles: true, composed: true, detail: { value } }),\n );\n }\n\n /** Navegação por teclado entre as opções do listbox (WCAG 2.1.1). */\n private _navigateOptions(e: KeyboardEvent): void {\n e.preventDefault();\n if (!this.hasAttr('open')) {\n this._abrir();\n this.render();\n }\n const opts = Array.from(this.root.querySelectorAll<HTMLButtonElement>('.opt'));\n if (!opts.length) return;\n const current = opts.indexOf(this.root.activeElement as HTMLButtonElement);\n let next = 0;\n if (e.key === 'ArrowDown') next = current === -1 ? 0 : Math.min(current + 1, opts.length - 1);\n else if (e.key === 'ArrowUp') next = current === -1 ? opts.length - 1 : Math.max(current - 1, 0);\n else if (e.key === 'Home') next = 0;\n else if (e.key === 'End') next = opts.length - 1;\n opts[next]?.focus();\n }\n\n protected override render(): void {\n const prevOpen = this.hasAttr('open');\n const prevSearch = this.root.querySelector<HTMLInputElement>('.search');\n // Estado do campo de busca antes de re-renderizar (valor, foco e cursor).\n const search = prevSearch?.value ?? '';\n const searchFocused =\n prevSearch != null && this.root.activeElement === prevSearch;\n const caretPos = searchFocused ? prevSearch!.selectionStart : null;\n\n const opts = this.options;\n let current = this.getAttr('value');\n if (!current) {\n const explicit = this.querySelector('option[selected]');\n current = explicit?.getAttribute('value') ?? explicit?.textContent?.trim() ?? '';\n }\n const selectedLabel = opts.find((o) => o.value === current)?.label ?? '';\n const placeholder = this.getAttr('placeholder', 'Selecione…');\n\n const filtered = search\n ? opts.filter((o) => o.label.toLowerCase().includes(search.toLowerCase()))\n : opts;\n\n this.setTemplate(`\n <span class=\"trigger\" part=\"trigger\" role=\"button\" tabindex=\"${this.disabled ? -1 : 0}\" aria-haspopup=\"listbox\" aria-expanded=\"${prevOpen}\">\n <span class=\"${selectedLabel ? 'label' : 'placeholder'}\">${esc(selectedLabel) || esc(placeholder)}</span>\n <span class=\"actions\">\n ${this.hasAttr('clearable') && current ? '<button type=\"button\" class=\"clear\" part=\"clear\" aria-label=\"Limpar\">×</button>' : ''}\n <span class=\"caret\">▼</span>\n </span>\n </span>\n <div class=\"panel\" part=\"panel\" role=\"listbox\">\n ${this.hasAttr('searchable') ? `<input class=\"search\" part=\"search\" type=\"text\" placeholder=\"${esc(this.getAttr('search-placeholder', 'Pesquisar…'))}\">` : ''}\n ${filtered.map((o) => `\n <button type=\"button\" class=\"opt\" role=\"option\" data-value=\"${esc(o.value)}\"\n aria-selected=\"${o.value === current}\">${esc(o.label)}</button>`).join('')}\n ${filtered.length === 0 ? `<div class=\"empty\">${esc(this.getAttr('no-results', 'Nenhum resultado'))}</div>` : ''}\n </div>\n `);\n\n if (prevOpen) this.setAttribute('open', '');\n // A medição só é válida com o painel já `display: block` (open aplicado).\n if (this.hasAttr('open')) this.ajustarPosicao();\n const searchInput = this.root.querySelector<HTMLInputElement>('.search');\n if (searchInput) {\n searchInput.value = search;\n searchInput.addEventListener('input', () => this.render());\n searchInput.addEventListener('click', (e) => e.stopPropagation());\n // Re-render não pode roubar o foco nem perder a posição do cursor.\n if (searchFocused) {\n searchInput.focus();\n try { searchInput.setSelectionRange(caretPos!, caretPos!); } catch { /* type=text sempre suporta */ }\n }\n }\n\n const trigger = this.root.querySelector<HTMLElement>('.trigger');\n if (!trigger) return;\n if (this.disabled) trigger.setAttribute('aria-disabled', 'true');\n\n trigger.addEventListener('click', (e) => {\n if (this.disabled) return;\n if ((e.target as HTMLElement).closest('.clear')) return;\n if (this.hasAttr('open')) this._fechar();\n else this._abrir();\n this.render();\n this.root.querySelector<HTMLInputElement>('.search')?.focus();\n });\n\n // Teclado: Enter/Espaço abrem o dropdown; setas/Home/End/Escape navegam.\n trigger.addEventListener('keydown', (e) => {\n if (this.disabled) return;\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n trigger.click();\n return;\n }\n if (e.key === 'Escape') {\n if (this.hasAttr('open')) {\n this._fechar();\n this.render();\n this.root.querySelector<HTMLElement>('.trigger')?.focus();\n }\n return;\n }\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Home' || e.key === 'End') {\n this._navigateOptions(e);\n }\n });\n\n // Limpar.\n this.root.querySelector('.clear')?.addEventListener('click', (e) => {\n e.stopPropagation();\n this.value = '';\n this._fechar();\n this.render();\n this.dispatchEvent(\n new CustomEvent('change', { bubbles: true, composed: true, detail: { value: '' } }),\n );\n });\n\n // Escolher opção.\n this.root.querySelectorAll<HTMLElement>('.opt').forEach((opt) => {\n opt.addEventListener('click', () => this.select(opt.dataset.value!));\n });\n }\n}\n\nexport function defineFxSelect(): typeof FxSelect {\n return defineElement('fx-select', FxSelect);\n}\n\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"select.js","sources":["../../../src/components/select/select.ts"],"sourcesContent":["import { FxFormElement } from '../../core/form-element';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\nimport { esc } from '../../core/sanitize';\n\n/**\n * <fx-select> — Campo de seleção com dropdown customizado.\n *\n * Form-Associated Custom Element: dentro de um <form> entra no FormData\n * (via atributo `name`) e recebe reset/disable do form.\n *\n * Escreva os filhos como `<option>` nativos no light DOM; eles são\n * espelhados automaticamente (MutationObserver):\n *\n * <fx-select value=\"b\" searchable clearable>\n * <option value=\"a\">Opção A</option>\n * <option value=\"b\">Opção B</option>\n * </fx-select>\n *\n * Atributos: value, size (sm|md|lg), disabled, placeholder,\n * searchable, clearable, search-placeholder, no-results,\n * placement (auto|top|bottom — direção do painel; `auto` inverte quando\n * não cabe na viewport).\n * Eventos: `change`, `input` e `valueChange` (composed, detail: { value }) —\n * `input` aciona o v-model do Vue e `valueChange` o [(value)] do Angular.\n */\nexport class FxSelect extends FxFormElement {\n static override styles = css`\n :host {\n display: inline-block;\n vertical-align: middle;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n position: relative;\n /* Largura no HOST (padrão: conteúdo com mín. 200px). CSS externo, classes\n e style inline no elemento definem a largura sem precisar do full. */\n width: var(--fx-select-width, max-content);\n min-width: var(--fx-select-min-width, 200px);\n /* Espaço entre o campo e o painel + altura máxima do painel. Expostos\n como custom property porque ::part() do lado do consumidor tem\n prioridade MENOR que a folha do shadow root e não vence o top. */\n --fx-select-panel-offset: 4px;\n --fx-select-panel-max-height: 260px;\n }\n .trigger {\n display: inline-flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--fx-space-sm);\n box-sizing: border-box;\n min-height: var(--fx-size-md);\n /* Acompanha a largura definida no :host. */\n width: 100%;\n font: inherit;\n /* Altura/linha determinísticas: a caixa fica no token --fx-size-*\n (o line-height fixo impede que a herança da página estoure o min-height). */\n line-height: var(--fx-font-line-height);\n font-weight: var(--fx-font-weight);\n color: var(--fx-text-default);\n text-align: left;\n background-color: var(--fx-surface-background);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-md);\n padding: var(--fx-space-xs) var(--fx-space-md);\n cursor: pointer;\n transition:\n border-color var(--fx-motion-duration-normal) var(--fx-motion-easing),\n box-shadow var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n .trigger:hover { border-color: var(--fx-border-hover); }\n .trigger:focus-visible,\n :host([open]) .trigger {\n outline: none;\n border-color: var(--fx-color-primary);\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n :host([size='sm']) { min-width: var(--fx-select-min-width-sm, 180px); }\n :host([size='sm']) .trigger { min-height: var(--fx-size-sm); }\n :host([size='lg']) .trigger { min-height: var(--fx-size-lg); font-size: calc(var(--fx-font-size) + 4px); }\n /* Full width: o host estica até o pai e o trigger acompanha. */\n :host([full]) { display: block; width: 100%; }\n :host([full]) { min-width: 0; }\n :host([full]) .trigger { width: 100%; min-width: 0; }\n :host([full]) .panel { width: 100%; }\n /* Validação — borda e brilho JÁ EM REPOUSO (igual ao fx-input); o\n brilho segue --fx-effect-error-ring, que acompanha effect.focus-ring. */\n :host([error]) .trigger,\n :host([invalid]) .trigger {\n border-color: var(--fx-color-danger, #dc2626);\n box-shadow: var(--fx-effect-error-ring, 0 0 0 3px color-mix(in srgb, var(--fx-color-danger, #dc2626) 18%, transparent));\n }\n :host([error]) .trigger:focus-visible,\n :host([invalid]) .trigger:focus-visible,\n :host([error][open]) .trigger,\n :host([invalid][open]) .trigger {\n border-color: var(--fx-color-danger, #dc2626);\n box-shadow: var(--fx-effect-error-ring, 0 0 0 3px color-mix(in srgb, var(--fx-color-danger, #dc2626) 18%, transparent));\n }\n :host([success]) .trigger,\n :host([valid]) .trigger {\n border-color: var(--fx-color-success, #16a34a);\n box-shadow: var(--fx-effect-success-ring, 0 0 0 3px color-mix(in srgb, var(--fx-color-success, #16a34a) 18%, transparent));\n }\n :host([success]) .trigger:focus-visible,\n :host([valid]) .trigger:focus-visible,\n :host([success][open]) .trigger,\n :host([valid][open]) .trigger {\n border-color: var(--fx-color-success, #16a34a);\n box-shadow: var(--fx-effect-success-ring, 0 0 0 3px color-mix(in srgb, var(--fx-color-success, #16a34a) 18%, transparent));\n }\n :host([disabled]) .trigger,\n .trigger[aria-disabled='true'] { opacity: 0.55; cursor: not-allowed; background-color: var(--fx-surface-surface-hover); }\n /* O rótulo encolhe (ellipsis) e os ícones NUNCA saem do campo. */\n .label,\n .placeholder {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .placeholder { color: var(--fx-text-muted); }\n .actions {\n display: inline-flex;\n align-items: center;\n gap: var(--fx-space-xs, 8px);\n flex-shrink: 0;\n }\n .caret { font-size: calc(var(--fx-font-size) - 3px); color: var(--fx-text-muted); pointer-events: none; }\n\n /* Painel do dropdown — a POSIÇÃO vertical é definida pelas regras\n [data-placement] abaixo (o JS de ajustarPosicao decide), por isso\n top/bottom não ficam aqui. */\n .panel {\n position: absolute;\n left: 0;\n z-index: var(--fx-z-dropdown, 1000);\n width: max(100%, 220px);\n max-height: var(--fx-select-panel-max-height, 260px);\n overflow-y: auto;\n display: none;\n background: var(--fx-surface-background);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-md);\n box-shadow: var(--fx-shadow-lg);\n }\n /* Padrão: abre para baixo. */\n :host([open]:not([data-placement='top'])) .panel {\n top: calc(100% + var(--fx-select-panel-offset, 4px));\n }\n /* placement=\"top\" explícito ou auto-flip: abre para cima. */\n :host([open][placement='top']) .panel,\n :host([open][data-placement='top']) .panel {\n top: auto;\n bottom: calc(100% + var(--fx-select-panel-offset, 4px));\n }\n :host([open]) .panel { display: block; }\n .search {\n position: sticky;\n top: 0;\n width: 100%;\n box-sizing: border-box;\n font: inherit;\n color: var(--fx-text-default);\n background: var(--fx-surface-background);\n border: none;\n border-bottom: 1px solid var(--fx-border-default);\n padding: var(--fx-space-sm) var(--fx-space-md);\n outline: none;\n }\n .opt {\n display: block;\n width: 100%;\n text-align: left;\n font: inherit;\n color: var(--fx-text-default);\n background: none;\n border: none;\n padding: var(--fx-space-sm) var(--fx-space-md);\n cursor: pointer;\n }\n /* Hover e selecionado com a cor primária do tema (igual ao multiselect). */\n .opt:hover { background: color-mix(in srgb, var(--fx-color-primary) 12%, transparent); }\n .opt[aria-selected='true'] {\n background: color-mix(in srgb, var(--fx-color-primary) 18%, transparent);\n color: var(--fx-color-primary);\n font-weight: var(--fx-font-weight);\n }\n .empty { padding: var(--fx-space-sm) var(--fx-space-md); color: var(--fx-text-muted); }\n\n /* Clearable */\n .clear {\n border: none;\n background: transparent;\n color: var(--fx-text-muted);\n font-size: calc(var(--fx-font-size) + 2px);\n line-height: 1;\n cursor: pointer;\n padding: 0;\n }\n .clear:hover { color: var(--fx-color-danger); }\n .clear[hidden] { display: none; }\n `;\n\n // `value` fica FORA da observação: refleti-lo no change não pode\n // re-renderizar o template e fechar o dropdown.\n static override get observedAttributes(): string[] {\n // `invalid`/`valid` são aliases aceitos no CSS; sem observá-los o\n // setAttribute no submit não re-renderiza.\n return ['size', 'disabled', 'placeholder', 'searchable', 'clearable', 'error', 'invalid', 'success', 'valid', 'placement'];\n }\n\n /** Espaço (px) entre o campo e o painel — espelha `--fx-select-panel-offset`. */\n private static readonly GAP = 4;\n /** Margem (px) mantida entre o painel e as bordas da viewport. */\n private static readonly MARGEM = 8;\n /** Altura mínima do painel ao ser limitado pelo espaço disponível. */\n private static readonly MIN_ALTURA = 80;\n\n private observer?: MutationObserver;\n private docListener?: (e: Event) => void;\n /** Reposiciona o painel enquanto ele estiver aberto (bound em `_abrir`). */\n private onReflow?: () => void;\n\n /** Tamanho do campo. Padrão: `'md'`. */\n get size(): string {\n const s = this.getAttr('size', 'md');\n return s === 'sm' || s === 'lg' ? s : 'md';\n }\n set size(value: string) {\n this.setAttribute('size', value);\n }\n\n get value(): string {\n return this.getAttr('value');\n }\n set value(value: string) {\n this.setAttribute('value', value);\n // `value` não é observado (não pode re-renderizar/fechar o dropdown no\n // change): publica no form aqui; o rótulo do trigger é re-renderizado\n // pelos caminhos que já chamam render().\n this.updateFormValue();\n }\n\n get disabled(): boolean {\n return this.hasAttr('disabled');\n }\n set disabled(value: boolean) {\n this.toggleAttr('disabled', Boolean(value));\n }\n\n private get options(): { value: string; label: string }[] {\n return [...this.querySelectorAll('option')].map((o) => ({\n value: o.getAttribute('value') ?? o.textContent?.trim() ?? '',\n label: o.textContent?.trim() ?? '',\n }));\n }\n\n protected override connectedCallback(): void {\n super.connectedCallback();\n // Espelha mudanças nas <option> do light DOM.\n this.observer = new MutationObserver(() => this.render());\n this.observer.observe(this, { childList: true, subtree: true, characterData: true });\n // Fecha o dropdown ao clicar fora.\n this.docListener = (e: Event) => {\n if (!this.hasAttr('open')) return;\n if (e.composedPath().includes(this)) return;\n this._fechar();\n this.render();\n };\n document.addEventListener('click', this.docListener);\n }\n\n protected override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.observer?.disconnect();\n if (this.docListener) document.removeEventListener('click', this.docListener);\n this._unbindReflow();\n }\n\n /** Abre o dropdown, posiciona o painel e passa a reposicioná-lo no reflow. */\n private _abrir(): void {\n this.setAttribute('open', '');\n this._bindReflow();\n }\n\n /** Fecha o dropdown e libera os listeners de resize/scroll. */\n private _fechar(): void {\n this.removeAttribute('open');\n // Sem isto o `data-placement` fica preso no host e a próxima abertura\n // mostraria o painel na posição invertida antes do ajuste rodar.\n this.removeAttribute('data-placement');\n this._unbindReflow();\n }\n\n /** Liga o reposicionamento (resize + scroll interno, em capture). */\n private _bindReflow(): void {\n if (this.onReflow) return;\n this.onReflow = () => {\n if (this.hasAttr('open')) this.ajustarPosicao();\n };\n window.addEventListener('resize', this.onReflow);\n window.addEventListener('scroll', this.onReflow, true);\n }\n\n private _unbindReflow(): void {\n if (!this.onReflow) return;\n window.removeEventListener('resize', this.onReflow);\n window.removeEventListener('scroll', this.onReflow, true);\n this.onReflow = undefined;\n }\n\n /**\n * Decide a posição vertical do painel medindo o espaço real da viewport.\n *\n * `placement=\"top\"`/`\"bottom\"` são respeitados literalmente; o padrão\n * (`auto`) só inverte quando o painel não cabe embaixo E couber melhor\n * acima. Também limita a altura ao espaço disponível e evita overflow\n * horizontal próximo à borda direita.\n */\n protected ajustarPosicao(): void {\n const painel = this.root.querySelector<HTMLElement>('.panel');\n const trigger = this.root.querySelector<HTMLElement>('.trigger');\n if (!painel || !trigger) return;\n\n const r = trigger.getBoundingClientRect();\n const abaixo = window.innerHeight - r.bottom; // espaço livre embaixo\n const acima = r.top; // espaço livre acima\n const desejado = painel.scrollHeight; // altura já com a busca\n const maxAltura = FxSelect._maxAltura(this);\n\n const pedido = this.getAttr('placement', 'auto');\n const paraCima =\n pedido === 'top' ||\n (pedido !== 'bottom' && abaixo < Math.min(desejado, maxAltura) && acima > abaixo);\n\n // O atributo placement do host tem precedência no CSS; data-placement\n // é o canal interno do auto-flip e usa a mesma regra de estilo.\n if (paraCima) this.setAttribute('data-placement', 'top');\n else this.removeAttribute('data-placement');\n\n // Limita a altura ao espaço real disponível (evita o painel vazar da tela).\n const disponivel = Math.max(\n FxSelect.MIN_ALTURA,\n (paraCima ? acima : abaixo) - FxSelect.GAP,\n );\n painel.style.maxHeight = `${Math.min(desejado || disponivel, disponivel, maxAltura)}px`;\n\n // Reset do deslocamento horizontal antes de remedir (o painel é recriado\n // a cada render, mas o ajuste pode rodar várias vezes com o mesmo nó).\n painel.style.left = '';\n const largura = painel.offsetWidth;\n const excesso = r.left + largura - (window.innerWidth - FxSelect.MARGEM);\n if (excesso > 0) {\n painel.style.left = `${-excesso}px`;\n painel.style.right = 'auto';\n }\n }\n\n /** Altura máxima do painel a partir de `--fx-select-panel-max-height`. */\n private static _maxAltura(host: HTMLElement): number {\n const raw = getComputedStyle(host).getPropertyValue('--fx-select-panel-max-height');\n const n = parseInt(raw, 10);\n return Number.isFinite(n) && n > 0 ? n : 260;\n }\n\n private select(value: string): void {\n this.value = value;\n this._fechar();\n this.render();\n this.emitValueEvents();\n this.dispatchEvent(\n new CustomEvent('change', { bubbles: true, composed: true, detail: { value } }),\n );\n }\n\n /** Navegação por teclado entre as opções do listbox (WCAG 2.1.1). */\n private _navigateOptions(e: KeyboardEvent): void {\n e.preventDefault();\n if (!this.hasAttr('open')) {\n this._abrir();\n this.render();\n }\n const opts = Array.from(this.root.querySelectorAll<HTMLButtonElement>('.opt'));\n if (!opts.length) return;\n const current = opts.indexOf(this.root.activeElement as HTMLButtonElement);\n let next = 0;\n if (e.key === 'ArrowDown') next = current === -1 ? 0 : Math.min(current + 1, opts.length - 1);\n else if (e.key === 'ArrowUp') next = current === -1 ? opts.length - 1 : Math.max(current - 1, 0);\n else if (e.key === 'Home') next = 0;\n else if (e.key === 'End') next = opts.length - 1;\n opts[next]?.focus();\n }\n\n protected override render(): void {\n const prevOpen = this.hasAttr('open');\n const prevSearch = this.root.querySelector<HTMLInputElement>('.search');\n // Estado do campo de busca antes de re-renderizar (valor, foco e cursor).\n const search = prevSearch?.value ?? '';\n const searchFocused =\n prevSearch != null && this.root.activeElement === prevSearch;\n const caretPos = searchFocused ? prevSearch!.selectionStart : null;\n\n const opts = this.options;\n let current = this.getAttr('value');\n if (!current) {\n const explicit = this.querySelector('option[selected]');\n current = explicit?.getAttribute('value') ?? explicit?.textContent?.trim() ?? '';\n }\n const selectedLabel = opts.find((o) => o.value === current)?.label ?? '';\n const placeholder = this.getAttr('placeholder', 'Selecione…');\n\n const filtered = search\n ? opts.filter((o) => o.label.toLowerCase().includes(search.toLowerCase()))\n : opts;\n\n this.setTemplate(`\n <span class=\"trigger\" part=\"trigger\" role=\"button\" tabindex=\"${this.disabled ? -1 : 0}\" aria-haspopup=\"listbox\" aria-expanded=\"${prevOpen}\">\n <span class=\"${selectedLabel ? 'label' : 'placeholder'}\">${esc(selectedLabel) || esc(placeholder)}</span>\n <span class=\"actions\">\n ${this.hasAttr('clearable') && current ? '<button type=\"button\" class=\"clear\" part=\"clear\" aria-label=\"Limpar\">×</button>' : ''}\n <span class=\"caret\">▼</span>\n </span>\n </span>\n <div class=\"panel\" part=\"panel\" role=\"listbox\">\n ${this.hasAttr('searchable') ? `<input class=\"search\" part=\"search\" type=\"text\" placeholder=\"${esc(this.getAttr('search-placeholder', 'Pesquisar…'))}\">` : ''}\n ${filtered.map((o) => `\n <button type=\"button\" class=\"opt\" role=\"option\" data-value=\"${esc(o.value)}\"\n aria-selected=\"${o.value === current}\">${esc(o.label)}</button>`).join('')}\n ${filtered.length === 0 ? `<div class=\"empty\">${esc(this.getAttr('no-results', 'Nenhum resultado'))}</div>` : ''}\n </div>\n `);\n\n if (prevOpen) this.setAttribute('open', '');\n // A medição só é válida com o painel já `display: block` (open aplicado).\n if (this.hasAttr('open')) this.ajustarPosicao();\n const searchInput = this.root.querySelector<HTMLInputElement>('.search');\n if (searchInput) {\n searchInput.value = search;\n searchInput.addEventListener('input', () => this.render());\n searchInput.addEventListener('click', (e) => e.stopPropagation());\n // Re-render não pode roubar o foco nem perder a posição do cursor.\n if (searchFocused) {\n searchInput.focus();\n try { searchInput.setSelectionRange(caretPos!, caretPos!); } catch { /* type=text sempre suporta */ }\n }\n }\n\n const trigger = this.root.querySelector<HTMLElement>('.trigger');\n if (!trigger) return;\n if (this.disabled) trigger.setAttribute('aria-disabled', 'true');\n\n trigger.addEventListener('click', (e) => {\n if (this.disabled) return;\n if ((e.target as HTMLElement).closest('.clear')) return;\n if (this.hasAttr('open')) this._fechar();\n else this._abrir();\n this.render();\n this.root.querySelector<HTMLInputElement>('.search')?.focus();\n });\n\n // Teclado: Enter/Espaço abrem o dropdown; setas/Home/End/Escape navegam.\n trigger.addEventListener('keydown', (e) => {\n if (this.disabled) return;\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n trigger.click();\n return;\n }\n if (e.key === 'Escape') {\n if (this.hasAttr('open')) {\n this._fechar();\n this.render();\n this.root.querySelector<HTMLElement>('.trigger')?.focus();\n }\n return;\n }\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Home' || e.key === 'End') {\n this._navigateOptions(e);\n }\n });\n\n // Limpar.\n this.root.querySelector('.clear')?.addEventListener('click', (e) => {\n e.stopPropagation();\n this.value = '';\n this._fechar();\n this.render();\n this.emitValueEvents();\n this.dispatchEvent(\n new CustomEvent('change', { bubbles: true, composed: true, detail: { value: '' } }),\n );\n });\n\n // Escolher opção.\n this.root.querySelectorAll<HTMLElement>('.opt').forEach((opt) => {\n opt.addEventListener('click', () => this.select(opt.dataset.value!));\n });\n\n // Publica name/value no form (ElementInternals) a cada render.\n this.updateFormValue();\n }\n\n /** form.reset(): restaura o valor inicial e re-renderiza o rótulo. */\n override formResetCallback(): void {\n super.formResetCallback();\n this.render();\n }\n}\n\nexport function defineFxSelect(): typeof FxSelect {\n return defineElement('fx-select', FxSelect);\n}\n\n"],"names":[],"mappings":"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"}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FxFormElement } from '../../core/form-element';
|
|
2
2
|
/**
|
|
3
3
|
* <fx-slider> — Controle deslizante para seleção de valor numérico em um intervalo.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* Form-Associated Custom Element: dentro de um <form> entra no FormData
|
|
6
|
+
* (via atributo `name`) e recebe reset/disable do form.
|
|
7
|
+
*
|
|
8
|
+
* Atributos: min, max, step, value, name, size (sm|md|lg), disabled,
|
|
9
|
+
* show-value, label (rótulo acima do trilho).
|
|
10
|
+
* Eventos (composed): `input` (durante o arraste) e `change` (ao soltar),
|
|
11
|
+
* ambos com `detail: { value: number }`; `change`/solta também emitem
|
|
12
|
+
* `valueChange` ([(value)] do Angular) e o `input` aciona o v-model do Vue.
|
|
9
13
|
*/
|
|
10
|
-
export declare class FxSlider extends
|
|
14
|
+
export declare class FxSlider extends FxFormElement {
|
|
11
15
|
static styles: string;
|
|
12
16
|
static get observedAttributes(): string[];
|
|
13
17
|
get min(): number;
|
|
@@ -31,6 +35,8 @@ export declare class FxSlider extends FxElement {
|
|
|
31
35
|
private _bindKeyboard;
|
|
32
36
|
/** Atualiza fill/thumb/tooltip/aria sem re-renderizar o template (evita perder o drag). */
|
|
33
37
|
private _syncVisual;
|
|
38
|
+
/** form.reset(): restaura o valor inicial e o visual sem re-render. */
|
|
39
|
+
formResetCallback(): void;
|
|
34
40
|
}
|
|
35
41
|
export declare function defineFxSlider(): typeof FxSlider;
|
|
36
42
|
//# sourceMappingURL=slider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slider.d.ts","sourceRoot":"","sources":["../../../src/components/slider/slider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"slider.d.ts","sourceRoot":"","sources":["../../../src/components/slider/slider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAIxD;;;;;;;;;;;GAWG;AACH,qBAAa,QAAS,SAAQ,aAAa;IACzC,OAAgB,MAAM,SA8EpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,IAAI,GAAG,IAAI,MAAM,CAAgC;IACjD,IAAI,GAAG,IAAI,MAAM,CAAkC;IACnD,IAAI,IAAI,IAAI,MAAM,CAAiC;IAEnD,IAAI,KAAK,IAAI,MAAM,CAElB;IACD,IAAI,KAAK,CAAC,CAAC,EAAE,MAAM,EAOlB;IAED,OAAO,CAAC,IAAI;IAOZ,OAAO,CAAC,MAAM;IAId,4EAA4E;IAC5E,OAAO,CAAC,KAAK;IAMb,6DAA6D;IAC7D,OAAO,CAAC,IAAI;cAKO,MAAM,IAAI,IAAI;IAoBjC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAAC,CAA4B;IAC5C,OAAO,CAAC,KAAK,CAAC,CAA4B;cAEvB,oBAAoB,IAAI,IAAI;IAU/C,OAAO,CAAC,KAAK;IAIb,OAAO,CAAC,YAAY;IAkDpB,OAAO,CAAC,aAAa;IA8BrB,2FAA2F;IAC3F,OAAO,CAAC,WAAW;IAkBnB,uEAAuE;IAC9D,iBAAiB,IAAI,IAAI;CAInC;AAED,wBAAgB,cAAc,IAAI,OAAO,QAAQ,CAEhD"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FxFormElement } from "../../core/form-element.js";
|
|
2
2
|
import { css } from "../../core/css.js";
|
|
3
3
|
import { defineElement } from "../../core/define.js";
|
|
4
|
-
const _FxSlider = class _FxSlider extends
|
|
4
|
+
const _FxSlider = class _FxSlider extends FxFormElement {
|
|
5
5
|
constructor() {
|
|
6
6
|
super(...arguments);
|
|
7
7
|
this._dragging = false;
|
|
@@ -25,6 +25,7 @@ const _FxSlider = class _FxSlider extends FxElement {
|
|
|
25
25
|
const snapped = this._snap(v);
|
|
26
26
|
this.setAttribute("value", String(snapped));
|
|
27
27
|
if (this.isConnected) this._syncVisual();
|
|
28
|
+
this.updateFormValue();
|
|
28
29
|
}
|
|
29
30
|
_num(attr, fallback) {
|
|
30
31
|
const raw = this.getAttribute(attr);
|
|
@@ -99,6 +100,7 @@ const _FxSlider = class _FxSlider extends FxElement {
|
|
|
99
100
|
window.removeEventListener("pointerup", up);
|
|
100
101
|
this.value = valueFromEvent(e);
|
|
101
102
|
this._syncVisual();
|
|
103
|
+
this.emitValueEvents();
|
|
102
104
|
this.dispatchEvent(
|
|
103
105
|
new CustomEvent("change", { bubbles: true, composed: true, detail: { value: this.value } })
|
|
104
106
|
);
|
|
@@ -153,6 +155,7 @@ const _FxSlider = class _FxSlider extends FxElement {
|
|
|
153
155
|
ev.preventDefault();
|
|
154
156
|
this.value = next;
|
|
155
157
|
this._syncVisual();
|
|
158
|
+
this.emitValueEvents();
|
|
156
159
|
this.dispatchEvent(
|
|
157
160
|
new CustomEvent("input", { bubbles: true, composed: true, detail: { value: this.value } })
|
|
158
161
|
);
|
|
@@ -179,6 +182,11 @@ const _FxSlider = class _FxSlider extends FxElement {
|
|
|
179
182
|
const labelPct = this.root.querySelector(".label span:last-child");
|
|
180
183
|
if (labelPct) labelPct.textContent = String(this.value);
|
|
181
184
|
}
|
|
185
|
+
/** form.reset(): restaura o valor inicial e o visual sem re-render. */
|
|
186
|
+
formResetCallback() {
|
|
187
|
+
super.formResetCallback();
|
|
188
|
+
this._syncVisual();
|
|
189
|
+
}
|
|
182
190
|
};
|
|
183
191
|
_FxSlider.styles = css`
|
|
184
192
|
:host {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slider.js","sources":["../../../src/components/slider/slider.ts"],"sourcesContent":["import { FxElement } from '../../core/base';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\n\n/**\n * <fx-slider> — Controle deslizante para seleção de valor numérico em um intervalo.\n *\n * Atributos: min, max, step, value, size (sm|md|lg), disabled, show-value,\n * label (rótulo acima do trilho).\n * Eventos (composed): `input` (durante o arraste) e `change` (ao soltar) —\n * ambos com `detail: { value: number }`.\n */\nexport class FxSlider extends FxElement {\n static override styles = css`\n :host {\n display: block;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n --_track-h: 6px;\n --_thumb: 18px;\n }\n :host([size='sm']) { --_track-h: 4px; --_thumb: 14px; }\n :host([size='lg']) { --_track-h: 8px; --_thumb: 22px; }\n :host([disabled]) { opacity: 0.55; pointer-events: none; }\n\n .label {\n font-size: calc(var(--fx-font-size) - 2px);\n color: var(--fx-text-muted);\n margin-bottom: var(--fx-space-xs);\n display: flex;\n justify-content: space-between;\n gap: var(--fx-space-md);\n align-items: center;\n }\n .track-wrap {\n position: relative;\n display: flex;\n align-items: center;\n height: var(--_thumb);\n cursor: pointer;\n touch-action: none;\n }\n .track {\n width: 100%;\n height: var(--_track-h);\n border-radius: var(--fx-radius-full);\n background: var(--fx-surface-surface-hover, rgba(0, 0, 0, 0.08));\n overflow: hidden;\n }\n .fill {\n height: 100%;\n background: var(--fx-color-primary);\n border-radius: var(--fx-radius-full);\n }\n .thumb {\n position: absolute;\n top: 50%;\n width: var(--_thumb);\n height: var(--_thumb);\n border-radius: var(--fx-radius-full);\n background: var(--fx-surface-background);\n border: 2px solid var(--fx-color-primary);\n box-shadow: var(--fx-shadow-sm);\n transform: translate(-50%, -50%);\n transition: box-shadow var(--fx-motion-duration-fast) var(--fx-motion-easing);\n box-sizing: border-box;\n }\n .track-wrap:hover .thumb,\n .track-wrap.dragging .thumb { box-shadow: var(--fx-effect-focus-ring, none); }\n .track-wrap:focus-visible { outline: none; }\n .track-wrap:focus-visible .thumb {\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n .value {\n position: absolute;\n bottom: calc(100% + 6px);\n transform: translateX(-50%);\n background: var(--fx-text-default, #1e293b);\n color: var(--fx-surface-background, #fff);\n font-size: calc(var(--fx-font-size) - 3px);\n font-weight: 600;\n padding: 2px var(--fx-space-xs);\n border-radius: var(--fx-radius-sm);\n white-space: nowrap;\n pointer-events: none;\n opacity: 0;\n transition: opacity var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n .track-wrap:hover .value,\n .track-wrap.dragging .value,\n .track-wrap:focus-visible .value { opacity: 1; }\n `;\n\n static override get observedAttributes(): string[] {\n return ['min', 'max', 'step', 'size', 'disabled', 'show-value', 'label'];\n }\n\n get min(): number { return this._num('min', 0); }\n get max(): number { return this._num('max', 100); }\n get step(): number { return this._num('step', 1); }\n\n get value(): number {\n return this._clamp(this._num('value', this.min));\n }\n set value(v: number) {\n const snapped = this._snap(v);\n this.setAttribute('value', String(snapped));\n // atualiza visual sem re-render (preserva estado de drag/foco)\n if (this.isConnected) this._syncVisual();\n }\n\n private _num(attr: string, fallback: number): number {\n const raw = this.getAttribute(attr);\n if (raw === null || raw === '') return fallback;\n const n = Number(raw);\n return Number.isFinite(n) ? n : fallback;\n }\n\n private _clamp(v: number): number {\n return Math.min(this.max, Math.max(this.min, v));\n }\n\n /** Arredonda para o múltiplo de `step` mais próximo dentro do intervalo. */\n private _snap(v: number): number {\n const step = this.step || 1;\n const snapped = Math.round((this._clamp(v) - this.min) / step) * step + this.min;\n return Number(this._clamp(snapped).toFixed(10));\n }\n\n /** Percentual (0-100) do valor atual dentro do intervalo. */\n private _pct(): number {\n const span = this.max - this.min;\n return span <= 0 ? 0 : ((this.value - this.min) / span) * 100;\n }\n\n protected override render(): void {\n const pct = this._pct();\n const label = this.getAttr('label');\n const showValue = this.hasAttr('show-value');\n\n this.setTemplate(`\n ${label ? `<div class=\"label\" part=\"label\"><span>${label}</span>${showValue ? `<span>${this.value}</span>` : ''}</div>` : ''}\n <div class=\"track-wrap\" part=\"track\" tabindex=\"0\" role=\"slider\"\n aria-valuemin=\"${this.min}\" aria-valuemax=\"${this.max}\" aria-valuenow=\"${this.value}\"\n aria-label=\"${label || 'Slider'}\" ${this.hasAttr('disabled') ? 'aria-disabled=\"true\"' : ''}>\n <div class=\"track\"><div class=\"fill\" part=\"fill\" style=\"width: ${pct}%\"></div></div>\n <div class=\"thumb\" part=\"thumb\" style=\"left: ${pct}%\"></div>\n ${showValue ? `<span class=\"value\" style=\"left: ${pct}%\">${this.value}</span>` : ''}\n </div>\n `);\n\n this._bindPointer();\n this._bindKeyboard();\n }\n\n private _dragging = false;\n private _onMove?: (e: PointerEvent) => void;\n private _onUp?: (e: PointerEvent) => void;\n\n protected override disconnectedCallback(): void {\n super.disconnectedCallback();\n // Limpa listeners de janela se o elemento for removido durante um drag.\n if (this._dragging) {\n this._dragging = false;\n if (this._onMove) window.removeEventListener('pointermove', this._onMove);\n if (this._onUp) window.removeEventListener('pointerup', this._onUp);\n }\n }\n\n private _wrap(): HTMLElement | null {\n return this.root.querySelector<HTMLElement>('.track-wrap');\n }\n\n private _bindPointer(): void {\n const wrap = this._wrap();\n if (!wrap) return;\n\n const valueFromEvent = (e: PointerEvent): number => {\n const rect = wrap.getBoundingClientRect();\n const ratio = rect.width <= 0 ? 0 : (e.clientX - rect.left) / rect.width;\n return this.min + ratio * (this.max - this.min);\n };\n\n const move = (e: PointerEvent): void => {\n if (!this._dragging) return;\n this.value = valueFromEvent(e);\n this._syncVisual();\n this.dispatchEvent(\n new CustomEvent('input', { bubbles: true, composed: true, detail: { value: this.value } }),\n );\n };\n this._onMove = move;\n const up = (e: PointerEvent): void => {\n if (!this._dragging) return;\n this._dragging = false;\n wrap.classList.remove('dragging');\n window.removeEventListener('pointermove', move);\n window.removeEventListener('pointerup', up);\n this.value = valueFromEvent(e);\n this._syncVisual();\n this.dispatchEvent(\n new CustomEvent('change', { bubbles: true, composed: true, detail: { value: this.value } }),\n );\n };\n this._onUp = up;\n\n wrap.addEventListener('pointerdown', (e) => {\n if (this.hasAttr('disabled')) return;\n this._dragging = true;\n wrap.classList.add('dragging');\n wrap.focus();\n this.value = valueFromEvent(e);\n this._syncVisual();\n this.dispatchEvent(\n new CustomEvent('input', { bubbles: true, composed: true, detail: { value: this.value } }),\n );\n window.addEventListener('pointermove', move);\n window.addEventListener('pointerup', up);\n });\n }\n\n private _bindKeyboard(): void {\n const wrap = this._wrap();\n if (!wrap) return;\n wrap.addEventListener('keydown', (e) => {\n if (this.hasAttr('disabled')) return;\n const ev = e as KeyboardEvent;\n const big = (this.max - this.min) / 10;\n let next: number | null = null;\n switch (ev.key) {\n case 'ArrowRight': case 'ArrowUp': next = this.value + this.step; break;\n case 'ArrowLeft': case 'ArrowDown': next = this.value - this.step; break;\n case 'PageUp': next = this.value + big; break;\n case 'PageDown': next = this.value - big; break;\n case 'Home': next = this.min; break;\n case 'End': next = this.max; break;\n default: return;\n }\n ev.preventDefault();\n this.value = next;\n this._syncVisual();\n this.dispatchEvent(\n new CustomEvent('input', { bubbles: true, composed: true, detail: { value: this.value } }),\n );\n this.dispatchEvent(\n new CustomEvent('change', { bubbles: true, composed: true, detail: { value: this.value } }),\n );\n });\n }\n\n /** Atualiza fill/thumb/tooltip/aria sem re-renderizar o template (evita perder o drag). */\n private _syncVisual(): void {\n const pct = this._pct();\n const wrap = this._wrap();\n if (!wrap) return;\n const fill = wrap.querySelector<HTMLElement>('.fill');\n const thumb = wrap.querySelector<HTMLElement>('.thumb');\n const tip = wrap.querySelector<HTMLElement>('.value');\n if (fill) fill.style.width = `${pct}%`;\n if (thumb) thumb.style.left = `${pct}%`;\n if (tip) {\n tip.style.left = `${pct}%`;\n tip.textContent = String(this.value);\n }\n wrap.setAttribute('aria-valuenow', String(this.value));\n const labelPct = this.root.querySelector<HTMLElement>('.label span:last-child');\n if (labelPct) labelPct.textContent = String(this.value);\n }\n}\n\nexport function defineFxSlider(): typeof FxSlider {\n return defineElement('fx-slider', FxSlider);\n}\n\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"slider.js","sources":["../../../src/components/slider/slider.ts"],"sourcesContent":["import { FxFormElement } from '../../core/form-element';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\n\n/**\n * <fx-slider> — Controle deslizante para seleção de valor numérico em um intervalo.\n *\n * Form-Associated Custom Element: dentro de um <form> entra no FormData\n * (via atributo `name`) e recebe reset/disable do form.\n *\n * Atributos: min, max, step, value, name, size (sm|md|lg), disabled,\n * show-value, label (rótulo acima do trilho).\n * Eventos (composed): `input` (durante o arraste) e `change` (ao soltar),\n * ambos com `detail: { value: number }`; `change`/solta também emitem\n * `valueChange` ([(value)] do Angular) e o `input` aciona o v-model do Vue.\n */\nexport class FxSlider extends FxFormElement {\n static override styles = css`\n :host {\n display: block;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n --_track-h: 6px;\n --_thumb: 18px;\n }\n :host([size='sm']) { --_track-h: 4px; --_thumb: 14px; }\n :host([size='lg']) { --_track-h: 8px; --_thumb: 22px; }\n :host([disabled]) { opacity: 0.55; pointer-events: none; }\n\n .label {\n font-size: calc(var(--fx-font-size) - 2px);\n color: var(--fx-text-muted);\n margin-bottom: var(--fx-space-xs);\n display: flex;\n justify-content: space-between;\n gap: var(--fx-space-md);\n align-items: center;\n }\n .track-wrap {\n position: relative;\n display: flex;\n align-items: center;\n height: var(--_thumb);\n cursor: pointer;\n touch-action: none;\n }\n .track {\n width: 100%;\n height: var(--_track-h);\n border-radius: var(--fx-radius-full);\n background: var(--fx-surface-surface-hover, rgba(0, 0, 0, 0.08));\n overflow: hidden;\n }\n .fill {\n height: 100%;\n background: var(--fx-color-primary);\n border-radius: var(--fx-radius-full);\n }\n .thumb {\n position: absolute;\n top: 50%;\n width: var(--_thumb);\n height: var(--_thumb);\n border-radius: var(--fx-radius-full);\n background: var(--fx-surface-background);\n border: 2px solid var(--fx-color-primary);\n box-shadow: var(--fx-shadow-sm);\n transform: translate(-50%, -50%);\n transition: box-shadow var(--fx-motion-duration-fast) var(--fx-motion-easing);\n box-sizing: border-box;\n }\n .track-wrap:hover .thumb,\n .track-wrap.dragging .thumb { box-shadow: var(--fx-effect-focus-ring, none); }\n .track-wrap:focus-visible { outline: none; }\n .track-wrap:focus-visible .thumb {\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n .value {\n position: absolute;\n bottom: calc(100% + 6px);\n transform: translateX(-50%);\n background: var(--fx-text-default, #1e293b);\n color: var(--fx-surface-background, #fff);\n font-size: calc(var(--fx-font-size) - 3px);\n font-weight: 600;\n padding: 2px var(--fx-space-xs);\n border-radius: var(--fx-radius-sm);\n white-space: nowrap;\n pointer-events: none;\n opacity: 0;\n transition: opacity var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n .track-wrap:hover .value,\n .track-wrap.dragging .value,\n .track-wrap:focus-visible .value { opacity: 1; }\n `;\n\n static override get observedAttributes(): string[] {\n return ['min', 'max', 'step', 'size', 'disabled', 'show-value', 'label'];\n }\n\n get min(): number { return this._num('min', 0); }\n get max(): number { return this._num('max', 100); }\n get step(): number { return this._num('step', 1); }\n\n get value(): number {\n return this._clamp(this._num('value', this.min));\n }\n set value(v: number) {\n const snapped = this._snap(v);\n this.setAttribute('value', String(snapped));\n // atualiza visual sem re-render (preserva estado de drag/foco)\n if (this.isConnected) this._syncVisual();\n // `value` não é observado: publica no form aqui (setFormValue não re-renderiza).\n this.updateFormValue();\n }\n\n private _num(attr: string, fallback: number): number {\n const raw = this.getAttribute(attr);\n if (raw === null || raw === '') return fallback;\n const n = Number(raw);\n return Number.isFinite(n) ? n : fallback;\n }\n\n private _clamp(v: number): number {\n return Math.min(this.max, Math.max(this.min, v));\n }\n\n /** Arredonda para o múltiplo de `step` mais próximo dentro do intervalo. */\n private _snap(v: number): number {\n const step = this.step || 1;\n const snapped = Math.round((this._clamp(v) - this.min) / step) * step + this.min;\n return Number(this._clamp(snapped).toFixed(10));\n }\n\n /** Percentual (0-100) do valor atual dentro do intervalo. */\n private _pct(): number {\n const span = this.max - this.min;\n return span <= 0 ? 0 : ((this.value - this.min) / span) * 100;\n }\n\n protected override render(): void {\n const pct = this._pct();\n const label = this.getAttr('label');\n const showValue = this.hasAttr('show-value');\n\n this.setTemplate(`\n ${label ? `<div class=\"label\" part=\"label\"><span>${label}</span>${showValue ? `<span>${this.value}</span>` : ''}</div>` : ''}\n <div class=\"track-wrap\" part=\"track\" tabindex=\"0\" role=\"slider\"\n aria-valuemin=\"${this.min}\" aria-valuemax=\"${this.max}\" aria-valuenow=\"${this.value}\"\n aria-label=\"${label || 'Slider'}\" ${this.hasAttr('disabled') ? 'aria-disabled=\"true\"' : ''}>\n <div class=\"track\"><div class=\"fill\" part=\"fill\" style=\"width: ${pct}%\"></div></div>\n <div class=\"thumb\" part=\"thumb\" style=\"left: ${pct}%\"></div>\n ${showValue ? `<span class=\"value\" style=\"left: ${pct}%\">${this.value}</span>` : ''}\n </div>\n `);\n\n this._bindPointer();\n this._bindKeyboard();\n }\n\n private _dragging = false;\n private _onMove?: (e: PointerEvent) => void;\n private _onUp?: (e: PointerEvent) => void;\n\n protected override disconnectedCallback(): void {\n super.disconnectedCallback();\n // Limpa listeners de janela se o elemento for removido durante um drag.\n if (this._dragging) {\n this._dragging = false;\n if (this._onMove) window.removeEventListener('pointermove', this._onMove);\n if (this._onUp) window.removeEventListener('pointerup', this._onUp);\n }\n }\n\n private _wrap(): HTMLElement | null {\n return this.root.querySelector<HTMLElement>('.track-wrap');\n }\n\n private _bindPointer(): void {\n const wrap = this._wrap();\n if (!wrap) return;\n\n const valueFromEvent = (e: PointerEvent): number => {\n const rect = wrap.getBoundingClientRect();\n const ratio = rect.width <= 0 ? 0 : (e.clientX - rect.left) / rect.width;\n return this.min + ratio * (this.max - this.min);\n };\n\n const move = (e: PointerEvent): void => {\n if (!this._dragging) return;\n this.value = valueFromEvent(e);\n this._syncVisual();\n this.dispatchEvent(\n new CustomEvent('input', { bubbles: true, composed: true, detail: { value: this.value } }),\n );\n };\n this._onMove = move;\n const up = (e: PointerEvent): void => {\n if (!this._dragging) return;\n this._dragging = false;\n wrap.classList.remove('dragging');\n window.removeEventListener('pointermove', move);\n window.removeEventListener('pointerup', up);\n this.value = valueFromEvent(e);\n this._syncVisual();\n // input (v-model do Vue) + valueChange ([(value)] do Angular) no soltar.\n this.emitValueEvents();\n this.dispatchEvent(\n new CustomEvent('change', { bubbles: true, composed: true, detail: { value: this.value } }),\n );\n };\n this._onUp = up;\n\n wrap.addEventListener('pointerdown', (e) => {\n if (this.hasAttr('disabled')) return;\n this._dragging = true;\n wrap.classList.add('dragging');\n wrap.focus();\n this.value = valueFromEvent(e);\n this._syncVisual();\n this.dispatchEvent(\n new CustomEvent('input', { bubbles: true, composed: true, detail: { value: this.value } }),\n );\n window.addEventListener('pointermove', move);\n window.addEventListener('pointerup', up);\n });\n }\n\n private _bindKeyboard(): void {\n const wrap = this._wrap();\n if (!wrap) return;\n wrap.addEventListener('keydown', (e) => {\n if (this.hasAttr('disabled')) return;\n const ev = e as KeyboardEvent;\n const big = (this.max - this.min) / 10;\n let next: number | null = null;\n switch (ev.key) {\n case 'ArrowRight': case 'ArrowUp': next = this.value + this.step; break;\n case 'ArrowLeft': case 'ArrowDown': next = this.value - this.step; break;\n case 'PageUp': next = this.value + big; break;\n case 'PageDown': next = this.value - big; break;\n case 'Home': next = this.min; break;\n case 'End': next = this.max; break;\n default: return;\n }\n ev.preventDefault();\n this.value = next;\n this._syncVisual();\n this.emitValueEvents();\n this.dispatchEvent(\n new CustomEvent('input', { bubbles: true, composed: true, detail: { value: this.value } }),\n );\n this.dispatchEvent(\n new CustomEvent('change', { bubbles: true, composed: true, detail: { value: this.value } }),\n );\n });\n }\n\n /** Atualiza fill/thumb/tooltip/aria sem re-renderizar o template (evita perder o drag). */\n private _syncVisual(): void {\n const pct = this._pct();\n const wrap = this._wrap();\n if (!wrap) return;\n const fill = wrap.querySelector<HTMLElement>('.fill');\n const thumb = wrap.querySelector<HTMLElement>('.thumb');\n const tip = wrap.querySelector<HTMLElement>('.value');\n if (fill) fill.style.width = `${pct}%`;\n if (thumb) thumb.style.left = `${pct}%`;\n if (tip) {\n tip.style.left = `${pct}%`;\n tip.textContent = String(this.value);\n }\n wrap.setAttribute('aria-valuenow', String(this.value));\n const labelPct = this.root.querySelector<HTMLElement>('.label span:last-child');\n if (labelPct) labelPct.textContent = String(this.value);\n }\n\n /** form.reset(): restaura o valor inicial e o visual sem re-render. */\n override formResetCallback(): void {\n super.formResetCallback();\n this._syncVisual();\n }\n}\n\nexport function defineFxSlider(): typeof FxSlider {\n return defineElement('fx-slider', FxSlider);\n}\n\n"],"names":[],"mappings":"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"}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FxFormElement } from '../../core/form-element';
|
|
2
2
|
/**
|
|
3
3
|
* <fx-switch> — Interruptor ligado/desligado (role="switch").
|
|
4
4
|
*
|
|
5
|
+
* Form-Associated Custom Element: dentro de um <form> entra no FormData
|
|
6
|
+
* (via atributo `name`) com `value` (ou 'on') quando ligado — desligado
|
|
7
|
+
* não envia nada, igual ao checkbox. Recebe reset/disable do form.
|
|
8
|
+
*
|
|
5
9
|
* O texto do slot (light DOM) é o rótulo clicável.
|
|
6
|
-
* Atributos: checked, disabled, size (sm|md|lg).
|
|
7
|
-
*
|
|
10
|
+
* Atributos: checked, disabled, value, name, size (sm|md|lg).
|
|
11
|
+
* Eventos: `change` e `checkedChange` (composed, detail: { checked, value }) —
|
|
12
|
+
* `checkedChange` é o two-way binding do Angular [(checked)].
|
|
8
13
|
*/
|
|
9
|
-
export declare class FxSwitch extends
|
|
14
|
+
export declare class FxSwitch extends FxFormElement {
|
|
10
15
|
static styles: string;
|
|
11
16
|
static get observedAttributes(): string[];
|
|
12
17
|
/** Tamanho da trilha. Padrão: `'md'`. */
|
|
@@ -16,7 +21,18 @@ export declare class FxSwitch extends FxElement {
|
|
|
16
21
|
set checked(value: boolean);
|
|
17
22
|
get disabled(): boolean;
|
|
18
23
|
set disabled(value: boolean);
|
|
24
|
+
/** getter/setter `value` → espelho do atributo `value` (compat com v-model / [(value)]). */
|
|
25
|
+
get value(): string;
|
|
26
|
+
set value(v: string);
|
|
27
|
+
/** No FormData: `value` (ou 'on') quando ligado; null (não envia) quando não. */
|
|
28
|
+
protected getFormValue(): string | FormData | null;
|
|
19
29
|
protected render(): void;
|
|
30
|
+
/** Estado marcado inicial (para form.reset() restaurar). */
|
|
31
|
+
private _initialChecked;
|
|
32
|
+
private _initialCheckedCaptured;
|
|
33
|
+
protected connectedCallback(): void;
|
|
34
|
+
/** form.reset(): restaura o estado inicial (desligado por padrão). */
|
|
35
|
+
formResetCallback(): void;
|
|
20
36
|
}
|
|
21
37
|
export declare function defineFxSwitch(): typeof FxSwitch;
|
|
22
38
|
//# sourceMappingURL=switch.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"switch.d.ts","sourceRoot":"","sources":["../../../src/components/switch/switch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"switch.d.ts","sourceRoot":"","sources":["../../../src/components/switch/switch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAIxD;;;;;;;;;;;GAWG;AACH,qBAAa,QAAS,SAAQ,aAAa;IACzC,OAAgB,MAAM,SAsDpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,yCAAyC;IACzC,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAErB;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAEzB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAE1B;IAED,4FAA4F;IAC5F,IAAI,KAAK,IAAI,MAAM,CAAkC;IACrD,IAAI,KAAK,CAAC,CAAC,EAAE,MAAM,EAAoC;IAEvD,iFAAiF;cAC9D,YAAY,IAAI,MAAM,GAAG,QAAQ,GAAG,IAAI;cAIxC,MAAM,IAAI,IAAI;IAoCjC,4DAA4D;IAC5D,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,uBAAuB,CAAS;cAErB,iBAAiB,IAAI,IAAI;IAQ5C,sEAAsE;IAC7D,iBAAiB,IAAI,IAAI;CAMnC;AAED,wBAAgB,cAAc,IAAI,OAAO,QAAQ,CAEhD"}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FxFormElement } from "../../core/form-element.js";
|
|
2
2
|
import { css } from "../../core/css.js";
|
|
3
3
|
import { defineElement } from "../../core/define.js";
|
|
4
|
-
const _FxSwitch = class _FxSwitch extends
|
|
4
|
+
const _FxSwitch = class _FxSwitch extends FxFormElement {
|
|
5
|
+
constructor() {
|
|
6
|
+
super(...arguments);
|
|
7
|
+
this._initialChecked = false;
|
|
8
|
+
this._initialCheckedCaptured = false;
|
|
9
|
+
}
|
|
5
10
|
static get observedAttributes() {
|
|
6
11
|
return ["checked", "disabled", "size"];
|
|
7
12
|
}
|
|
@@ -25,6 +30,17 @@ const _FxSwitch = class _FxSwitch extends FxElement {
|
|
|
25
30
|
set disabled(value) {
|
|
26
31
|
this.toggleAttr("disabled", Boolean(value));
|
|
27
32
|
}
|
|
33
|
+
/** getter/setter `value` → espelho do atributo `value` (compat com v-model / [(value)]). */
|
|
34
|
+
get value() {
|
|
35
|
+
return this.getAttr("value");
|
|
36
|
+
}
|
|
37
|
+
set value(v) {
|
|
38
|
+
this.setAttribute("value", v);
|
|
39
|
+
}
|
|
40
|
+
/** No FormData: `value` (ou 'on') quando ligado; null (não envia) quando não. */
|
|
41
|
+
getFormValue() {
|
|
42
|
+
return this.checked ? this.getAttr("value") || "on" : null;
|
|
43
|
+
}
|
|
28
44
|
render() {
|
|
29
45
|
this.setTemplate(`
|
|
30
46
|
<button class="switch" part="switch" type="button" role="switch"
|
|
@@ -46,7 +62,29 @@ const _FxSwitch = class _FxSwitch extends FxElement {
|
|
|
46
62
|
detail: { checked: this.checked }
|
|
47
63
|
})
|
|
48
64
|
);
|
|
65
|
+
this.dispatchEvent(
|
|
66
|
+
new CustomEvent("checkedChange", {
|
|
67
|
+
bubbles: true,
|
|
68
|
+
composed: true,
|
|
69
|
+
detail: { checked: this.checked, value: this.getAttr("value") }
|
|
70
|
+
})
|
|
71
|
+
);
|
|
49
72
|
});
|
|
73
|
+
this.updateFormValue();
|
|
74
|
+
}
|
|
75
|
+
connectedCallback() {
|
|
76
|
+
if (!this._initialCheckedCaptured) {
|
|
77
|
+
this._initialCheckedCaptured = true;
|
|
78
|
+
this._initialChecked = this.checked;
|
|
79
|
+
}
|
|
80
|
+
super.connectedCallback();
|
|
81
|
+
}
|
|
82
|
+
/** form.reset(): restaura o estado inicial (desligado por padrão). */
|
|
83
|
+
formResetCallback() {
|
|
84
|
+
this.checked = this._initialChecked;
|
|
85
|
+
const btn = this.root.querySelector(".switch");
|
|
86
|
+
if (btn) btn.setAttribute("aria-checked", String(this.checked));
|
|
87
|
+
this.updateFormValue();
|
|
50
88
|
}
|
|
51
89
|
};
|
|
52
90
|
_FxSwitch.styles = css`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"switch.js","sources":["../../../src/components/switch/switch.ts"],"sourcesContent":["import {
|
|
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":"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"}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import {
|
|
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
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
|
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,
|
|
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 {
|
|
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
|
|
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 {
|
|
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;"}
|