@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":"multiselect.js","sources":["../../../src/components/multiselect/multiselect.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-multiselect> — Seleção múltipla .\n *\n * Dropdown customizado com painel (overlay), checkbox por item, pesquisa\n * opcional e ação de limpar. Opções via <option> nativos no light DOM.\n *\n * Atributos: values (CSV), searchable, clearable, disabled, placeholder,\n * size (sm|md|lg), no-results.\n * Evento: `change` (composed, detail: { values: string[] }).\n */\nexport class FxMultiselect 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 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-multiselect-width, 240px);\n }\n .trigger {\n display: inline-flex;\n align-items: center;\n flex-wrap: nowrap;\n gap: var(--fx-space-xs);\n /* Acompanha a largura definida no :host. */\n width: 100%;\n box-sizing: border-box;\n min-height: var(--fx-size-md);\n padding: var(--fx-space-xs) var(--fx-space-md);\n font: inherit;\n /* Altura determinística: a caixa fica no token --fx-size-*. */\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 text-align: left;\n transition: border-color var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n /* Área dos chips: encolhe e quebra linha internamente,\n mantendo os ícones sempre dentro do componente. */\n .lead {\n display: inline-flex;\n flex-wrap: wrap;\n gap: var(--fx-space-xs);\n flex: 1;\n min-width: 0;\n overflow: hidden;\n }\n :host([size='sm']) { width: var(--fx-multiselect-width-sm, 200px); }\n :host([size='sm']) .trigger { min-height: var(--fx-size-sm); }\n :host([size='lg']) { width: var(--fx-multiselect-width-lg, 280px); }\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]) .trigger { width: 100%; }\n /* Validação — borda e brilho JÁ EM REPOUSO (igual ao fx-input); o brilho\n segue o token --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 .trigger:hover { border-color: var(--fx-border-hover); }\n .trigger:focus-visible {\n outline: none;\n border-color: var(--fx-color-primary);\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n .trigger[aria-expanded='true'] { border-color: var(--fx-color-primary); }\n .placeholder { color: var(--fx-text-muted); }\n .chip {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n background: color-mix(in srgb, var(--fx-color-primary) 14%, transparent);\n color: var(--fx-color-primary);\n border-radius: var(--fx-radius-sm);\n padding: 2px 6px;\n font-size: calc(var(--fx-font-size) - 2px);\n }\n .chip__x {\n border: none;\n background: none;\n color: inherit;\n cursor: pointer;\n font-size: calc(var(--fx-font-size) - 1px);\n line-height: 1;\n padding: 0;\n }\n .trigger-right {\n margin-left: auto;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n color: var(--fx-text-muted);\n }\n .icon-btn {\n border: none;\n background: none;\n color: inherit;\n cursor: pointer;\n font-size: calc(var(--fx-font-size) + 2px);\n line-height: 1;\n padding: 0 2px;\n border-radius: var(--fx-radius-full);\n }\n .icon-btn:hover { color: var(--fx-color-danger); }\n .caret {\n font-size: calc(var(--fx-font-size) - 3px);\n transition: transform var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n :host([open]) .caret { transform: rotate(180deg); }\n\n /* Painel (overlay ) */\n .panel {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n z-index: var(--fx-z-dropdown, 1000);\n width: max(100%, 240px);\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 display: none;\n overflow: hidden;\n }\n :host([up]) .panel {\n top: auto;\n bottom: calc(100% + 4px);\n }\n :host([open]) .panel { display: block; }\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--fx-space-sm);\n padding: var(--fx-space-xs) var(--fx-space-md);\n border-bottom: 1px solid var(--fx-border-default);\n background: var(--fx-surface-surface-hover);\n }\n .count { font-size: calc(var(--fx-font-size) - 2px); color: var(--fx-text-muted); }\n .clear-all {\n border: none;\n background: none;\n font: inherit;\n font-size: calc(var(--fx-font-size) - 2px);\n color: var(--fx-color-primary);\n cursor: pointer;\n padding: 0;\n }\n .clear-all:hover { text-decoration: underline; }\n .search {\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 .list {\n max-height: 240px;\n overflow-y: auto;\n list-style: none;\n margin: 0;\n padding: var(--fx-space-3xs, 4px);\n }\n .opt {\n display: flex;\n align-items: center;\n gap: var(--fx-space-sm);\n width: 100%;\n padding: var(--fx-space-xs) var(--fx-space-md);\n border: none;\n background: none;\n font: inherit;\n color: var(--fx-text-default);\n cursor: pointer;\n border-radius: var(--fx-radius-sm);\n text-align: left;\n }\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) 16%, transparent);\n color: var(--fx-color-primary);\n font-weight: var(--fx-font-weight);\n }\n /* Checkbox visual */\n .box {\n width: 16px;\n height: 16px;\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: 2px solid var(--fx-border-default);\n border-radius: calc(var(--fx-radius-sm) / 2);\n font-size: 11px;\n line-height: 1;\n color: transparent;\n transition:\n background-color var(--fx-motion-duration-fast) var(--fx-motion-easing),\n border-color var(--fx-motion-duration-fast) var(--fx-motion-easing),\n color var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n .opt[aria-selected='true'] .box {\n background: var(--fx-color-primary);\n border-color: var(--fx-color-primary);\n color: #fff;\n }\n .opt[aria-selected='true'] .box::after { content: '✓'; }\n .empty { padding: var(--fx-space-md); color: var(--fx-text-muted); text-align: center; }\n :host([disabled]) .trigger { opacity: 0.55; cursor: not-allowed; background: var(--fx-surface-surface-hover); }\n `;\n\n static override get observedAttributes(): string[] {\n // `error`/`success` (+ aliases `invalid`/`valid`) PRECISAM ser observados:\n // é assim que o toggle no playground / setAttribute no submit re-renderiza.\n return ['disabled', 'placeholder', 'error', 'invalid', 'success', 'valid'];\n }\n\n private observer?: MutationObserver;\n private docListener?: (e: Event) => void;\n private selected = new Set<string>();\n\n /** Valores selecionados (CSV no atributo / array na propriedade). */\n get values(): string[] {\n const csv = this.getAttr('values');\n return csv ? csv.split(',').map((v) => v.trim()).filter(Boolean) : [];\n }\n set values(list: string[]) {\n this.setAttribute('values', list.join(','));\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 // Popula a seleção ANTES do primeiro render (super chama render).\n this.selected = new Set(this.values);\n super.connectedCallback();\n this.observer = new MutationObserver(() => this.render());\n this.observer.observe(this, { childList: true, subtree: true, characterData: true });\n this.docListener = (e: Event) => {\n if (!this.hasAttr('open')) return;\n if (e.composedPath().includes(this)) return;\n this.removeAttribute('open');\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 }\n\n /** Aplica a seleção, reflete e emite change. */\n private commit(): void {\n this.values = [...this.selected];\n this.render();\n this.dispatchEvent(\n new CustomEvent('change', {\n bubbles: true,\n composed: true,\n detail: { values: [...this.selected] },\n }),\n );\n }\n\n private toggleValue(value: string): void {\n if (this.selected.has(value)) this.selected.delete(value);\n else this.selected.add(value);\n this.commit();\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.toggleAttribute('open');\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 wasOpen = this.hasAttr('open');\n // Preserva o estado do campo de pesquisa entre re-renders.\n const search = this.root.querySelector<HTMLInputElement>('.search')?.value ?? '';\n\n const opts = this.options;\n for (const v of [...this.selected]) {\n if (!opts.some((o) => o.value === v)) this.selected.delete(v);\n }\n\n const chips = opts.filter((o) => this.selected.has(o.value));\n const placeholder = this.getAttr('placeholder', 'Selecione…');\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=\"${wasOpen}\" aria-disabled=\"${this.disabled}\">\n <span class=\"lead\">\n ${\n chips.length\n ? chips.map((c) => `\n <span class=\"chip\" data-value=\"${esc(c.value)}\">\n ${esc(c.label)}\n <span class=\"chip__x\" role=\"button\" aria-label=\"Remover ${esc(c.label)}\">×</span>\n </span>`).join('')\n : `<span class=\"placeholder\">${esc(placeholder)}</span>`\n }\n </span>\n <span class=\"trigger-right\">\n ${this.hasAttr('clearable') && chips.length ? '<button type=\"button\" class=\"icon-btn clear\" part=\"clear\" aria-label=\"Limpar seleção\">×</button>' : ''}\n <span class=\"caret\">▼</span>\n </span>\n </span>\n <div class=\"panel\" part=\"panel\" role=\"listbox\" aria-multiselectable=\"true\">\n <div class=\"panel-header\">\n <span class=\"count\">${chips.length} selecionado${chips.length === 1 ? '' : 's'}</span>\n ${this.hasAttr('clearable') && chips.length ? '<button type=\"button\" class=\"clear-all\">Limpar tudo</button>' : ''}\n </div>\n ${this.hasAttr('searchable') ? `<input class=\"search\" part=\"search\" type=\"text\" placeholder=\"${esc(this.getAttr('search-placeholder', 'Pesquisar…'))}\">` : ''}\n <ul class=\"list\">\n ${filtered.map((o) => `\n <li><button type=\"button\" class=\"opt\" role=\"option\" data-value=\"${esc(o.value)}\"\n aria-selected=\"${this.selected.has(o.value)}\">\n <span class=\"box\" aria-hidden=\"true\"></span>${esc(o.label)}\n </button></li>`).join('')}\n ${filtered.length === 0 ? `<li class=\"empty\">${esc(this.getAttr('no-results', 'Nenhum resultado'))}</li>` : ''}\n </ul>\n </div>\n `);\n\n // Reabre o painel se estava aberto antes do re-render.\n if (wasOpen) this.setAttribute('open', '');\n\n const searchInput = this.root.querySelector<HTMLInputElement>('.search');\n if (searchInput) {\n searchInput.value = search;\n // Preserva foco e posição do cursor entre os re-renders da digitação.\n searchInput.addEventListener('input', () => {\n const pos = searchInput.selectionStart ?? searchInput.value.length;\n this.render();\n const si = this.root.querySelector<HTMLInputElement>('.search');\n if (si) {\n si.focus();\n si.setSelectionRange(pos, pos);\n }\n });\n searchInput.addEventListener('click', (e) => e.stopPropagation());\n }\n\n const trigger = this.root.querySelector<HTMLElement>('.trigger');\n if (!trigger) return;\n\n trigger.addEventListener('click', (e) => {\n if (this.disabled) return;\n if ((e.target as HTMLElement).closest('.clear')) return;\n if ((e.target as HTMLElement).closest('.chip__x')) return;\n this.toggleAttribute('open');\n // Flip vertical: se não cabe abaixo, abre para cima.\n if (this.hasAttr('open')) {\n const r = this.getBoundingClientRect();\n this.toggleAttribute('up', r.bottom + 300 > window.innerHeight && r.top > 300);\n } else {\n this.removeAttribute('up');\n }\n this.render();\n });\n\n // Acessibilidade: Enter/Espaço abrem o painel (span role=button); setas navegam.\n trigger.addEventListener('keydown', (e) => {\n if (this.disabled) return;\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n trigger.dispatchEvent(new MouseEvent('click', { bubbles: true }));\n return;\n }\n if (e.key === 'Escape') {\n if (this.hasAttr('open')) {\n this.removeAttribute('open');\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 // × de limpar tudo no trigger.\n this.root.querySelector('.clear')?.addEventListener('click', (e) => {\n e.stopPropagation();\n this.selected.clear();\n this.commit();\n });\n\n // \"Limpar tudo\" no cabeçalho do painel.\n this.root.querySelector('.clear-all')?.addEventListener('click', (e) => {\n e.stopPropagation();\n this.selected.clear();\n this.commit();\n });\n\n // Remover chip individual.\n this.root.querySelectorAll<HTMLElement>('.chip').forEach((chip) => {\n chip.querySelector('.chip__x')?.addEventListener('click', (e) => {\n e.stopPropagation();\n this.toggleValue(chip.dataset.value!);\n });\n });\n\n // Alternar opções.\n this.root.querySelectorAll<HTMLElement>('.opt').forEach((opt) => {\n opt.addEventListener('click', () => this.toggleValue(opt.dataset.value!));\n });\n }\n}\n\nexport function defineFxMultiselect(): typeof FxMultiselect {\n return defineElement('fx-multiselect', FxMultiselect);\n}\n\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"multiselect.js","sources":["../../../src/components/multiselect/multiselect.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-multiselect> — Seleção múltipla .\n *\n * Form-Associated Custom Element: dentro de um <form> entra no FormData com\n * UMA entrada por valor selecionado (via atributo `name`).\n *\n * Dropdown customizado com painel (overlay), checkbox por item, pesquisa\n * opcional e ação de limpar. Opções via <option> nativos no light DOM.\n *\n * Atributos: values (CSV), searchable, clearable, disabled, placeholder,\n * size (sm|md|lg), no-results.\n * Eventos: `change` e `valuesChange` (composed, detail: { values: string[] }) —\n * `valuesChange` é o two-way binding do Angular [(value)] em seleção múltipla.\n */\nexport class FxMultiselect 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 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-multiselect-width, 240px);\n }\n .trigger {\n display: inline-flex;\n align-items: center;\n flex-wrap: nowrap;\n gap: var(--fx-space-xs);\n /* Acompanha a largura definida no :host. */\n width: 100%;\n box-sizing: border-box;\n min-height: var(--fx-size-md);\n padding: var(--fx-space-xs) var(--fx-space-md);\n font: inherit;\n /* Altura determinística: a caixa fica no token --fx-size-*. */\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 text-align: left;\n transition: border-color var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n /* Área dos chips: encolhe e quebra linha internamente,\n mantendo os ícones sempre dentro do componente. */\n .lead {\n display: inline-flex;\n flex-wrap: wrap;\n gap: var(--fx-space-xs);\n flex: 1;\n min-width: 0;\n overflow: hidden;\n }\n :host([size='sm']) { width: var(--fx-multiselect-width-sm, 200px); }\n :host([size='sm']) .trigger { min-height: var(--fx-size-sm); }\n :host([size='lg']) { width: var(--fx-multiselect-width-lg, 280px); }\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]) .trigger { width: 100%; }\n /* Validação — borda e brilho JÁ EM REPOUSO (igual ao fx-input); o brilho\n segue o token --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 .trigger:hover { border-color: var(--fx-border-hover); }\n .trigger:focus-visible {\n outline: none;\n border-color: var(--fx-color-primary);\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n .trigger[aria-expanded='true'] { border-color: var(--fx-color-primary); }\n .placeholder { color: var(--fx-text-muted); }\n .chip {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n background: color-mix(in srgb, var(--fx-color-primary) 14%, transparent);\n color: var(--fx-color-primary);\n border-radius: var(--fx-radius-sm);\n padding: 2px 6px;\n font-size: calc(var(--fx-font-size) - 2px);\n }\n .chip__x {\n border: none;\n background: none;\n color: inherit;\n cursor: pointer;\n font-size: calc(var(--fx-font-size) - 1px);\n line-height: 1;\n padding: 0;\n }\n .trigger-right {\n margin-left: auto;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n color: var(--fx-text-muted);\n }\n .icon-btn {\n border: none;\n background: none;\n color: inherit;\n cursor: pointer;\n font-size: calc(var(--fx-font-size) + 2px);\n line-height: 1;\n padding: 0 2px;\n border-radius: var(--fx-radius-full);\n }\n .icon-btn:hover { color: var(--fx-color-danger); }\n .caret {\n font-size: calc(var(--fx-font-size) - 3px);\n transition: transform var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n :host([open]) .caret { transform: rotate(180deg); }\n\n /* Painel (overlay ) */\n .panel {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n z-index: var(--fx-z-dropdown, 1000);\n width: max(100%, 240px);\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 display: none;\n overflow: hidden;\n }\n :host([up]) .panel {\n top: auto;\n bottom: calc(100% + 4px);\n }\n :host([open]) .panel { display: block; }\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--fx-space-sm);\n padding: var(--fx-space-xs) var(--fx-space-md);\n border-bottom: 1px solid var(--fx-border-default);\n background: var(--fx-surface-surface-hover);\n }\n .count { font-size: calc(var(--fx-font-size) - 2px); color: var(--fx-text-muted); }\n .clear-all {\n border: none;\n background: none;\n font: inherit;\n font-size: calc(var(--fx-font-size) - 2px);\n color: var(--fx-color-primary);\n cursor: pointer;\n padding: 0;\n }\n .clear-all:hover { text-decoration: underline; }\n .search {\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 .list {\n max-height: 240px;\n overflow-y: auto;\n list-style: none;\n margin: 0;\n padding: var(--fx-space-3xs, 4px);\n }\n .opt {\n display: flex;\n align-items: center;\n gap: var(--fx-space-sm);\n width: 100%;\n padding: var(--fx-space-xs) var(--fx-space-md);\n border: none;\n background: none;\n font: inherit;\n color: var(--fx-text-default);\n cursor: pointer;\n border-radius: var(--fx-radius-sm);\n text-align: left;\n }\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) 16%, transparent);\n color: var(--fx-color-primary);\n font-weight: var(--fx-font-weight);\n }\n /* Checkbox visual */\n .box {\n width: 16px;\n height: 16px;\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: 2px solid var(--fx-border-default);\n border-radius: calc(var(--fx-radius-sm) / 2);\n font-size: 11px;\n line-height: 1;\n color: transparent;\n transition:\n background-color var(--fx-motion-duration-fast) var(--fx-motion-easing),\n border-color var(--fx-motion-duration-fast) var(--fx-motion-easing),\n color var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n .opt[aria-selected='true'] .box {\n background: var(--fx-color-primary);\n border-color: var(--fx-color-primary);\n color: #fff;\n }\n .opt[aria-selected='true'] .box::after { content: '✓'; }\n .empty { padding: var(--fx-space-md); color: var(--fx-text-muted); text-align: center; }\n :host([disabled]) .trigger { opacity: 0.55; cursor: not-allowed; background: var(--fx-surface-surface-hover); }\n `;\n\n static override get observedAttributes(): string[] {\n // `error`/`success` (+ aliases `invalid`/`valid`) PRECISAM ser observados:\n // é assim que o toggle no playground / setAttribute no submit re-renderiza.\n return ['disabled', 'placeholder', 'error', 'invalid', 'success', 'valid'];\n }\n\n private observer?: MutationObserver;\n private docListener?: (e: Event) => void;\n private selected = new Set<string>();\n /** Seleção inicial (para form.reset() restaurar o estado). */\n private _initialValues?: string[];\n\n /** Valores selecionados (CSV no atributo / array na propriedade). */\n get values(): string[] {\n const csv = this.getAttr('values');\n return csv ? csv.split(',').map((v) => v.trim()).filter(Boolean) : [];\n }\n set values(list: string[]) {\n this.setAttribute('values', list.join(','));\n }\n\n /** No FormData do form: uma entrada `name` por valor selecionado. */\n protected override getFormValue(): string | FormData | null {\n const values = this.values;\n if (!this.name || values.length === 0) return null;\n const fd = new FormData();\n values.forEach((v) => fd.append(this.name, v));\n return fd;\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 // Popula a seleção ANTES do primeiro render (super chama render).\n this.selected = new Set(this.values);\n if (!this._initialValues) this._initialValues = [...this.values];\n super.connectedCallback();\n this.observer = new MutationObserver(() => this.render());\n this.observer.observe(this, { childList: true, subtree: true, characterData: true });\n this.docListener = (e: Event) => {\n if (!this.hasAttr('open')) return;\n if (e.composedPath().includes(this)) return;\n this.removeAttribute('open');\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 }\n\n /** Aplica a seleção, reflete e emite change. */\n private commit(): void {\n this.values = [...this.selected];\n this.render();\n this.updateFormValue();\n this.dispatchEvent(\n new CustomEvent('change', {\n bubbles: true,\n composed: true,\n detail: { values: [...this.selected] },\n }),\n );\n // Two-way binding do Angular: [(value)] em selecao multipla -> valuesChange.\n this.dispatchEvent(\n new CustomEvent('valuesChange', {\n bubbles: true,\n composed: true,\n detail: { values: [...this.selected] },\n }),\n );\n }\n\n /** form.reset(): restaura a seleção inicial. */\n override formResetCallback(): void {\n const initial = this._initialValues ?? [];\n this.values = initial;\n this.selected = new Set(initial);\n this.render();\n this.updateFormValue();\n }\n\n private toggleValue(value: string): void {\n if (this.selected.has(value)) this.selected.delete(value);\n else this.selected.add(value);\n this.commit();\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.toggleAttribute('open');\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 wasOpen = this.hasAttr('open');\n // Preserva o estado do campo de pesquisa entre re-renders.\n const search = this.root.querySelector<HTMLInputElement>('.search')?.value ?? '';\n\n const opts = this.options;\n for (const v of [...this.selected]) {\n if (!opts.some((o) => o.value === v)) this.selected.delete(v);\n }\n\n const chips = opts.filter((o) => this.selected.has(o.value));\n const placeholder = this.getAttr('placeholder', 'Selecione…');\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=\"${wasOpen}\" aria-disabled=\"${this.disabled}\">\n <span class=\"lead\">\n ${\n chips.length\n ? chips.map((c) => `\n <span class=\"chip\" data-value=\"${esc(c.value)}\">\n ${esc(c.label)}\n <span class=\"chip__x\" role=\"button\" aria-label=\"Remover ${esc(c.label)}\">×</span>\n </span>`).join('')\n : `<span class=\"placeholder\">${esc(placeholder)}</span>`\n }\n </span>\n <span class=\"trigger-right\">\n ${this.hasAttr('clearable') && chips.length ? '<button type=\"button\" class=\"icon-btn clear\" part=\"clear\" aria-label=\"Limpar seleção\">×</button>' : ''}\n <span class=\"caret\">▼</span>\n </span>\n </span>\n <div class=\"panel\" part=\"panel\" role=\"listbox\" aria-multiselectable=\"true\">\n <div class=\"panel-header\">\n <span class=\"count\">${chips.length} selecionado${chips.length === 1 ? '' : 's'}</span>\n ${this.hasAttr('clearable') && chips.length ? '<button type=\"button\" class=\"clear-all\">Limpar tudo</button>' : ''}\n </div>\n ${this.hasAttr('searchable') ? `<input class=\"search\" part=\"search\" type=\"text\" placeholder=\"${esc(this.getAttr('search-placeholder', 'Pesquisar…'))}\">` : ''}\n <ul class=\"list\">\n ${filtered.map((o) => `\n <li><button type=\"button\" class=\"opt\" role=\"option\" data-value=\"${esc(o.value)}\"\n aria-selected=\"${this.selected.has(o.value)}\">\n <span class=\"box\" aria-hidden=\"true\"></span>${esc(o.label)}\n </button></li>`).join('')}\n ${filtered.length === 0 ? `<li class=\"empty\">${esc(this.getAttr('no-results', 'Nenhum resultado'))}</li>` : ''}\n </ul>\n </div>\n `);\n\n // Reabre o painel se estava aberto antes do re-render.\n if (wasOpen) this.setAttribute('open', '');\n\n const searchInput = this.root.querySelector<HTMLInputElement>('.search');\n if (searchInput) {\n searchInput.value = search;\n // Preserva foco e posição do cursor entre os re-renders da digitação.\n searchInput.addEventListener('input', () => {\n const pos = searchInput.selectionStart ?? searchInput.value.length;\n this.render();\n const si = this.root.querySelector<HTMLInputElement>('.search');\n if (si) {\n si.focus();\n si.setSelectionRange(pos, pos);\n }\n });\n searchInput.addEventListener('click', (e) => e.stopPropagation());\n }\n\n const trigger = this.root.querySelector<HTMLElement>('.trigger');\n if (!trigger) return;\n\n trigger.addEventListener('click', (e) => {\n if (this.disabled) return;\n if ((e.target as HTMLElement).closest('.clear')) return;\n if ((e.target as HTMLElement).closest('.chip__x')) return;\n this.toggleAttribute('open');\n // Flip vertical: se não cabe abaixo, abre para cima.\n if (this.hasAttr('open')) {\n const r = this.getBoundingClientRect();\n this.toggleAttribute('up', r.bottom + 300 > window.innerHeight && r.top > 300);\n } else {\n this.removeAttribute('up');\n }\n this.render();\n });\n\n // Acessibilidade: Enter/Espaço abrem o painel (span role=button); setas navegam.\n trigger.addEventListener('keydown', (e) => {\n if (this.disabled) return;\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n trigger.dispatchEvent(new MouseEvent('click', { bubbles: true }));\n return;\n }\n if (e.key === 'Escape') {\n if (this.hasAttr('open')) {\n this.removeAttribute('open');\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 // × de limpar tudo no trigger.\n this.root.querySelector('.clear')?.addEventListener('click', (e) => {\n e.stopPropagation();\n this.selected.clear();\n this.commit();\n });\n\n // \"Limpar tudo\" no cabeçalho do painel.\n this.root.querySelector('.clear-all')?.addEventListener('click', (e) => {\n e.stopPropagation();\n this.selected.clear();\n this.commit();\n });\n\n // Remover chip individual.\n this.root.querySelectorAll<HTMLElement>('.chip').forEach((chip) => {\n chip.querySelector('.chip__x')?.addEventListener('click', (e) => {\n e.stopPropagation();\n this.toggleValue(chip.dataset.value!);\n });\n });\n\n // Alternar opções.\n this.root.querySelectorAll<HTMLElement>('.opt').forEach((opt) => {\n opt.addEventListener('click', () => this.toggleValue(opt.dataset.value!));\n });\n }\n}\n\nexport function defineFxMultiselect(): typeof FxMultiselect {\n return defineElement('fx-multiselect', FxMultiselect);\n}\n\n"],"names":[],"mappings":"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"}
|
|
@@ -1,23 +1,46 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FxFormElement } from '../../core/form-element';
|
|
2
2
|
/**
|
|
3
3
|
* <fx-radio> — Botão de opção. Radios com o mesmo `name` formam um grupo
|
|
4
|
-
* (seleção exclusiva gerenciada pelo próprio componente
|
|
4
|
+
* (seleção exclusiva gerenciada pelo próprio componente, escopado à árvore
|
|
5
|
+
* em que o radio vive — document OU o shadow DOM do app consumidor).
|
|
6
|
+
*
|
|
7
|
+
* Form-Associated Custom Element: dentro de um <form> só o radio MARCADO
|
|
8
|
+
* entra no FormData (via atributo `name`, com `value` ou 'on'), igual ao
|
|
9
|
+
* radio nativo. Recebe reset/disable do form.
|
|
5
10
|
*
|
|
6
11
|
* Atributos: checked, disabled, value, name, size (sm|md|lg).
|
|
7
|
-
*
|
|
12
|
+
* Eventos: `change` e `checkedChange` (composed, detail: { checked, value }) —
|
|
13
|
+
* `checkedChange` é o two-way binding do Angular [(checked)].
|
|
8
14
|
*/
|
|
9
|
-
export declare class FxRadio extends
|
|
15
|
+
export declare class FxRadio extends FxFormElement {
|
|
10
16
|
static styles: string;
|
|
11
17
|
static get observedAttributes(): string[];
|
|
12
18
|
get checked(): boolean;
|
|
13
19
|
set checked(value: boolean);
|
|
14
20
|
get disabled(): boolean;
|
|
15
21
|
set disabled(value: boolean);
|
|
22
|
+
/**
|
|
23
|
+
* Grupo = radios com o mesmo `name` NA MESMA árvore. `getRootNode()` devolve
|
|
24
|
+
* o document OU o ShadowRoot do app consumidor — grupos com o mesmo name
|
|
25
|
+
* em shadow DOMs diferentes ficam isolados (o `document` global cruzaria
|
|
26
|
+
* fronteiras indevidamente).
|
|
27
|
+
*/
|
|
28
|
+
private _group;
|
|
29
|
+
/** No FormData: só o radio MARCADO envia `value` (ou 'on'). */
|
|
30
|
+
protected getFormValue(): string | FormData | null;
|
|
16
31
|
/** Tamanho do controle. Padrão: `'md'`. */
|
|
17
32
|
get size(): string;
|
|
18
33
|
set size(value: string);
|
|
19
34
|
protected render(): void;
|
|
35
|
+
/** Move o foco/marcação para o próximo (ou anterior) radio do grupo. */
|
|
36
|
+
private _moveFocus;
|
|
20
37
|
private select;
|
|
38
|
+
/** Estado marcado inicial (para form.reset() restaurar o grupo). */
|
|
39
|
+
private _initialChecked;
|
|
40
|
+
private _initialCheckedCaptured;
|
|
41
|
+
protected connectedCallback(): void;
|
|
42
|
+
/** form.reset(): restaura o estado marcado inicial do grupo. */
|
|
43
|
+
formResetCallback(): void;
|
|
21
44
|
}
|
|
22
45
|
export declare function defineFxRadio(): typeof FxRadio;
|
|
23
46
|
//# sourceMappingURL=radio.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"radio.d.ts","sourceRoot":"","sources":["../../../src/components/radio/radio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"radio.d.ts","sourceRoot":"","sources":["../../../src/components/radio/radio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAIxD;;;;;;;;;;;;GAYG;AACH,qBAAa,OAAQ,SAAQ,aAAa;IACxC,OAAgB,MAAM,SAwDpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAEzB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAE1B;IAED;;;;;OAKG;IACH,OAAO,CAAC,MAAM;IAYd,+DAA+D;cAC5C,YAAY,IAAI,MAAM,GAAG,QAAQ,GAAG,IAAI;IAI3D,2CAA2C;IAC3C,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAErB;cAEkB,MAAM,IAAI,IAAI;IAoCjC,wEAAwE;IACxE,OAAO,CAAC,UAAU;IAalB,OAAO,CAAC,MAAM;IAwBd,oEAAoE;IACpE,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,uBAAuB,CAAS;cAErB,iBAAiB,IAAI,IAAI;IAQ5C,gEAAgE;IACvD,iBAAiB,IAAI,IAAI;CAInC;AAED,wBAAgB,aAAa,IAAI,OAAO,OAAO,CAE9C"}
|
|
@@ -1,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 _FxRadio = class _FxRadio extends
|
|
4
|
+
const _FxRadio = class _FxRadio 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"];
|
|
7
12
|
}
|
|
@@ -17,6 +22,25 @@ const _FxRadio = class _FxRadio extends FxElement {
|
|
|
17
22
|
set disabled(value) {
|
|
18
23
|
this.toggleAttr("disabled", Boolean(value));
|
|
19
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Grupo = radios com o mesmo `name` NA MESMA árvore. `getRootNode()` devolve
|
|
27
|
+
* o document OU o ShadowRoot do app consumidor — grupos com o mesmo name
|
|
28
|
+
* em shadow DOMs diferentes ficam isolados (o `document` global cruzaria
|
|
29
|
+
* fronteiras indevidamente).
|
|
30
|
+
*/
|
|
31
|
+
_group() {
|
|
32
|
+
const name = this.getAttr("name");
|
|
33
|
+
if (!name) return [this];
|
|
34
|
+
const root = this.getRootNode();
|
|
35
|
+
const safeName = name.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
36
|
+
return Array.from(
|
|
37
|
+
root.querySelectorAll(`fx-radio[name="${safeName}"]`)
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
/** No FormData: só o radio MARCADO envia `value` (ou 'on'). */
|
|
41
|
+
getFormValue() {
|
|
42
|
+
return this.checked ? this.getAttr("value") || "on" : null;
|
|
43
|
+
}
|
|
20
44
|
/** Tamanho do controle. Padrão: `'md'`. */
|
|
21
45
|
get size() {
|
|
22
46
|
const s = this.getAttr("size", "md");
|
|
@@ -41,19 +65,35 @@ const _FxRadio = class _FxRadio extends FxElement {
|
|
|
41
65
|
if (!box) return;
|
|
42
66
|
box.addEventListener("click", () => this.select());
|
|
43
67
|
box.addEventListener("keydown", (e) => {
|
|
44
|
-
|
|
68
|
+
const key = e.key;
|
|
69
|
+
if (key === " " || key === "Enter") {
|
|
45
70
|
e.preventDefault();
|
|
46
71
|
this.select();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (key === "ArrowDown" || key === "ArrowRight" || key === "ArrowUp" || key === "ArrowLeft") {
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
this._moveFocus(key === "ArrowDown" || key === "ArrowRight" ? 1 : -1);
|
|
47
77
|
}
|
|
48
78
|
});
|
|
79
|
+
this.updateFormValue();
|
|
80
|
+
}
|
|
81
|
+
/** Move o foco/marcação para o próximo (ou anterior) radio do grupo. */
|
|
82
|
+
_moveFocus(dir) {
|
|
83
|
+
const group = this._group().filter((r) => !r.disabled);
|
|
84
|
+
if (group.length < 2) return;
|
|
85
|
+
const i = group.indexOf(this);
|
|
86
|
+
const next = group[(i + dir + group.length) % group.length];
|
|
87
|
+
next.focus();
|
|
88
|
+
if (!next.root.activeElement) {
|
|
89
|
+
next.root.querySelector(".box")?.focus();
|
|
90
|
+
}
|
|
91
|
+
if (!next.checked) next.select();
|
|
49
92
|
}
|
|
50
93
|
select() {
|
|
51
94
|
if (this.disabled || this.checked) return;
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
document.querySelectorAll(`fx-radio[name="${CSS.escape(name)}"]`).forEach((r) => {
|
|
55
|
-
r.checked = false;
|
|
56
|
-
});
|
|
95
|
+
for (const r of this._group()) {
|
|
96
|
+
if (r !== this) r.checked = false;
|
|
57
97
|
}
|
|
58
98
|
this.checked = true;
|
|
59
99
|
this.dispatchEvent(
|
|
@@ -63,6 +103,25 @@ const _FxRadio = class _FxRadio extends FxElement {
|
|
|
63
103
|
detail: { checked: true, value: this.getAttr("value") }
|
|
64
104
|
})
|
|
65
105
|
);
|
|
106
|
+
this.dispatchEvent(
|
|
107
|
+
new CustomEvent("checkedChange", {
|
|
108
|
+
bubbles: true,
|
|
109
|
+
composed: true,
|
|
110
|
+
detail: { checked: true, value: this.getAttr("value") }
|
|
111
|
+
})
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
connectedCallback() {
|
|
115
|
+
if (!this._initialCheckedCaptured) {
|
|
116
|
+
this._initialCheckedCaptured = true;
|
|
117
|
+
this._initialChecked = this.checked;
|
|
118
|
+
}
|
|
119
|
+
super.connectedCallback();
|
|
120
|
+
}
|
|
121
|
+
/** form.reset(): restaura o estado marcado inicial do grupo. */
|
|
122
|
+
formResetCallback() {
|
|
123
|
+
this.checked = this._initialChecked;
|
|
124
|
+
this.updateFormValue();
|
|
66
125
|
}
|
|
67
126
|
};
|
|
68
127
|
_FxRadio.styles = css`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"radio.js","sources":["../../../src/components/radio/radio.ts"],"sourcesContent":["import { FxElement } from '../../core/base';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\n\n/**\n * <fx-radio> — Botão de opção. Radios com o mesmo `name` formam um grupo\n * (seleção exclusiva gerenciada pelo próprio componente).\n *\n * Atributos: checked, disabled, value, name, size (sm|md|lg).\n * Evento: `change` (composed, detail: { checked, value }).\n */\nexport class FxRadio extends FxElement {\n static override styles = css`\n :host {\n display: inline-flex;\n vertical-align: middle;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n }\n .box {\n display: inline-flex;\n align-items: center;\n gap: var(--fx-space-sm);\n cursor: pointer;\n user-select: none;\n color: var(--fx-text-default);\n -webkit-tap-highlight-color: transparent;\n }\n .control {\n width: var(--fx-size-radio, 18px);\n height: var(--fx-size-radio, 18px);\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: 2px solid var(--fx-border-hover);\n border-radius: var(--fx-radius-full);\n background: var(--fx-surface-background);\n transition:\n border-color var(--fx-motion-duration-fast) var(--fx-motion-easing),\n box-shadow var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n .box:hover .control { border-color: var(--fx-color-primary); }\n .box:focus-visible .control {\n outline: none;\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n .dot {\n width: calc(100% - 8px);\n height: calc(100% - 8px);\n border-radius: var(--fx-radius-full);\n background: var(--fx-color-primary);\n transform: scale(0);\n transition: transform var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n :host([checked]) .control { border-color: var(--fx-color-primary); }\n :host([checked]) .dot { transform: scale(1); }\n /* Validação */\n :host([error]) .control,\n :host([invalid]) .control { border-color: var(--fx-color-danger, #dc2626); }\n :host([success]) .control,\n :host([valid]) .control { border-color: var(--fx-color-success, #16a34a); }\n .label { display: inline-flex; align-items: center; }\n :host([disabled]) .box {\n opacity: 0.55;\n cursor: not-allowed;\n pointer-events: none;\n }\n `;\n\n static override get observedAttributes(): string[] {\n return ['checked', 'disabled'];\n }\n\n get checked(): boolean {\n return this.hasAttr('checked');\n }\n set checked(value: boolean) {\n this.toggleAttr('checked', Boolean(value));\n }\n\n get disabled(): boolean {\n return this.hasAttr('disabled');\n }\n set disabled(value: boolean) {\n this.toggleAttr('disabled', Boolean(value));\n }\n\n /** Tamanho do controle. Padrão: `'md'`. */\n get size(): string {\n const s = this.getAttr('size', 'md');\n return s === 'sm' || s === 'lg' ? s : 'md';\n }\n set size(value: string) {\n this.setAttribute('size', value);\n }\n\n protected override render(): void {\n const px = this.size === 'sm' ? '14px' : this.size === 'lg' ? '22px' : '18px';\n if (!this.style.getPropertyValue('--fx-size-radio')) {\n this.style.setProperty('--fx-size-radio', px);\n }\n\n this.setTemplate(`\n <span class=\"box\" part=\"box\" role=\"radio\" tabindex=\"0\"\n aria-checked=\"${this.checked}\" aria-disabled=\"${this.disabled}\">\n <span class=\"control\" part=\"control\"><span class=\"dot\" part=\"dot\"></span></span>\n <span class=\"label\" part=\"label\"><slot></slot></span>\n </span>\n `);\n\n const box = this.root.querySelector<HTMLElement>('.box');\n if (!box) return;\n box.addEventListener('click', () => this.select());\n box.addEventListener('keydown', (e) => {\n if ((e as KeyboardEvent).key === ' ' || (e as KeyboardEvent).key === 'Enter') {\n e.preventDefault();\n this.select();\n }\n });\n }\n\n private select(): void {\n if (this.disabled || this.checked) return;\n // Desmarca os outros radios do mesmo grupo (mesmo name).\n const name = this.getAttr('name');\n if (name) {\n document.querySelectorAll(`fx-radio[name=\"${CSS.escape(name)}\"]`).forEach((r) => {\n (r as FxRadio).checked = false;\n });\n }\n this.checked = true;\n this.dispatchEvent(\n new CustomEvent('change', {\n bubbles: true,\n composed: true,\n detail: { checked: true, value: this.getAttr('value') },\n }),\n );\n }\n}\n\nexport function defineFxRadio(): typeof FxRadio {\n return defineElement('fx-radio', FxRadio);\n}\n"],"names":[],"mappings":";;;AAWO,MAAM,WAAN,MAAM,iBAAgB,UAAU;AAAA,EA2DrC,WAAoB,qBAA+B;AACjD,WAAO,CAAC,WAAW,UAAU;AAAA,EAC/B;AAAA,EAEA,IAAI,UAAmB;AACrB,WAAO,KAAK,QAAQ,SAAS;AAAA,EAC/B;AAAA,EACA,IAAI,QAAQ,OAAgB;AAC1B,SAAK,WAAW,WAAW,QAAQ,KAAK,CAAC;AAAA,EAC3C;AAAA,EAEA,IAAI,WAAoB;AACtB,WAAO,KAAK,QAAQ,UAAU;AAAA,EAChC;AAAA,EACA,IAAI,SAAS,OAAgB;AAC3B,SAAK,WAAW,YAAY,QAAQ,KAAK,CAAC;AAAA,EAC5C;AAAA;AAAA,EAGA,IAAI,OAAe;AACjB,UAAM,IAAI,KAAK,QAAQ,QAAQ,IAAI;AACnC,WAAO,MAAM,QAAQ,MAAM,OAAO,IAAI;AAAA,EACxC;AAAA,EACA,IAAI,KAAK,OAAe;AACtB,SAAK,aAAa,QAAQ,KAAK;AAAA,EACjC;AAAA,EAEmB,SAAe;AAChC,UAAM,KAAK,KAAK,SAAS,OAAO,SAAS,KAAK,SAAS,OAAO,SAAS;AACvE,QAAI,CAAC,KAAK,MAAM,iBAAiB,iBAAiB,GAAG;AACnD,WAAK,MAAM,YAAY,mBAAmB,EAAE;AAAA,IAC9C;AAEA,SAAK,YAAY;AAAA;AAAA,wBAEG,KAAK,OAAO,oBAAoB,KAAK,QAAQ;AAAA;AAAA;AAAA;AAAA,KAIhE;AAED,UAAM,MAAM,KAAK,KAAK,cAA2B,MAAM;AACvD,QAAI,CAAC,IAAK;AACV,QAAI,iBAAiB,SAAS,MAAM,KAAK,QAAQ;AACjD,QAAI,iBAAiB,WAAW,CAAC,MAAM;AACrC,UAAK,EAAoB,QAAQ,OAAQ,EAAoB,QAAQ,SAAS;AAC5E,UAAE,eAAA;AACF,aAAK,OAAA;AAAA,MACP;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,SAAe;AACrB,QAAI,KAAK,YAAY,KAAK,QAAS;AAEnC,UAAM,OAAO,KAAK,QAAQ,MAAM;AAChC,QAAI,MAAM;AACR,eAAS,iBAAiB,kBAAkB,IAAI,OAAO,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM;AAC9E,UAAc,UAAU;AAAA,MAC3B,CAAC;AAAA,IACH;AACA,SAAK,UAAU;AACf,SAAK;AAAA,MACH,IAAI,YAAY,UAAU;AAAA,QACxB,SAAS;AAAA,QACT,UAAU;AAAA,QACV,QAAQ,EAAE,SAAS,MAAM,OAAO,KAAK,QAAQ,OAAO,EAAA;AAAA,MAAE,CACvD;AAAA,IAAA;AAAA,EAEL;AACF;AAhIE,SAAgB,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;AADpB,IAAM,UAAN;AAmIA,SAAS,gBAAgC;AAC9C,SAAO,cAAc,YAAY,OAAO;AAC1C;"}
|
|
1
|
+
{"version":3,"file":"radio.js","sources":["../../../src/components/radio/radio.ts"],"sourcesContent":["import { FxFormElement } from '../../core/form-element';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\n\n/**\n * <fx-radio> — Botão de opção. Radios com o mesmo `name` formam um grupo\n * (seleção exclusiva gerenciada pelo próprio componente, escopado à árvore\n * em que o radio vive — document OU o shadow DOM do app consumidor).\n *\n * Form-Associated Custom Element: dentro de um <form> só o radio MARCADO\n * entra no FormData (via atributo `name`, com `value` ou 'on'), igual ao\n * radio nativo. Recebe reset/disable do form.\n *\n * Atributos: checked, disabled, value, name, size (sm|md|lg).\n * Eventos: `change` e `checkedChange` (composed, detail: { checked, value }) —\n * `checkedChange` é o two-way binding do Angular [(checked)].\n */\nexport class FxRadio extends FxFormElement {\n static override styles = css`\n :host {\n display: inline-flex;\n vertical-align: middle;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n }\n .box {\n display: inline-flex;\n align-items: center;\n gap: var(--fx-space-sm);\n cursor: pointer;\n user-select: none;\n color: var(--fx-text-default);\n -webkit-tap-highlight-color: transparent;\n }\n .control {\n width: var(--fx-size-radio, 18px);\n height: var(--fx-size-radio, 18px);\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: 2px solid var(--fx-border-hover);\n border-radius: var(--fx-radius-full);\n background: var(--fx-surface-background);\n transition:\n border-color var(--fx-motion-duration-fast) var(--fx-motion-easing),\n box-shadow var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n .box:hover .control { border-color: var(--fx-color-primary); }\n .box:focus-visible .control {\n outline: none;\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n .dot {\n width: calc(100% - 8px);\n height: calc(100% - 8px);\n border-radius: var(--fx-radius-full);\n background: var(--fx-color-primary);\n transform: scale(0);\n transition: transform var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n :host([checked]) .control { border-color: var(--fx-color-primary); }\n :host([checked]) .dot { transform: scale(1); }\n /* Validação */\n :host([error]) .control,\n :host([invalid]) .control { border-color: var(--fx-color-danger, #dc2626); }\n :host([success]) .control,\n :host([valid]) .control { border-color: var(--fx-color-success, #16a34a); }\n .label { display: inline-flex; align-items: center; }\n :host([disabled]) .box {\n opacity: 0.55;\n cursor: not-allowed;\n pointer-events: none;\n }\n `;\n\n static override get observedAttributes(): string[] {\n return ['checked', 'disabled'];\n }\n\n get checked(): boolean {\n return this.hasAttr('checked');\n }\n set checked(value: boolean) {\n this.toggleAttr('checked', Boolean(value));\n }\n\n get disabled(): boolean {\n return this.hasAttr('disabled');\n }\n set disabled(value: boolean) {\n this.toggleAttr('disabled', Boolean(value));\n }\n\n /**\n * Grupo = radios com o mesmo `name` NA MESMA árvore. `getRootNode()` devolve\n * o document OU o ShadowRoot do app consumidor — grupos com o mesmo name\n * em shadow DOMs diferentes ficam isolados (o `document` global cruzaria\n * fronteiras indevidamente).\n */\n private _group(): FxRadio[] {\n const name = this.getAttr('name');\n if (!name) return [this];\n const root = this.getRootNode() as ParentNode;\n // Escape manual para seletor de atributo com aspas duplas (o global `CSS`\n // não existe em todos os ambientes — ex.: jsdom).\n const safeName = name.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n return Array.from(\n root.querySelectorAll(`fx-radio[name=\"${safeName}\"]`),\n ) as FxRadio[];\n }\n\n /** No FormData: só o radio MARCADO envia `value` (ou 'on'). */\n protected override getFormValue(): string | FormData | null {\n return this.checked ? (this.getAttr('value') || 'on') : null;\n }\n\n /** Tamanho do controle. Padrão: `'md'`. */\n get size(): string {\n const s = this.getAttr('size', 'md');\n return s === 'sm' || s === 'lg' ? s : 'md';\n }\n set size(value: string) {\n this.setAttribute('size', value);\n }\n\n protected override render(): void {\n const px = this.size === 'sm' ? '14px' : this.size === 'lg' ? '22px' : '18px';\n if (!this.style.getPropertyValue('--fx-size-radio')) {\n this.style.setProperty('--fx-size-radio', px);\n }\n\n this.setTemplate(`\n <span class=\"box\" part=\"box\" role=\"radio\" tabindex=\"0\"\n aria-checked=\"${this.checked}\" aria-disabled=\"${this.disabled}\">\n <span class=\"control\" part=\"control\"><span class=\"dot\" part=\"dot\"></span></span>\n <span class=\"label\" part=\"label\"><slot></slot></span>\n </span>\n `);\n\n const box = this.root.querySelector<HTMLElement>('.box');\n if (!box) return;\n box.addEventListener('click', () => this.select());\n box.addEventListener('keydown', (e) => {\n const key = (e as KeyboardEvent).key;\n if (key === ' ' || key === 'Enter') {\n e.preventDefault();\n this.select();\n return;\n }\n // Setas: movem o foco (e marcam) entre os radios do grupo — igual ao\n // <input type=\"radio\"> nativo (roam pelo grupo, sem sair dele).\n if (key === 'ArrowDown' || key === 'ArrowRight' || key === 'ArrowUp' || key === 'ArrowLeft') {\n e.preventDefault();\n this._moveFocus(key === 'ArrowDown' || key === 'ArrowRight' ? 1 : -1);\n }\n });\n\n // Publica name/value no form (ElementInternals) a cada render.\n this.updateFormValue();\n }\n\n /** Move o foco/marcação para o próximo (ou anterior) radio do grupo. */\n private _moveFocus(dir: 1 | -1): void {\n const group = this._group().filter((r) => !r.disabled);\n if (group.length < 2) return;\n const i = group.indexOf(this);\n const next = group[(i + dir + group.length) % group.length]!;\n next.focus(); // delegatesFocus leva o foco ao .box interno\n // Fallback (ex.: jsdom sem delegatesFocus): foca o .box interno direto.\n if (!next.root.activeElement) {\n next.root.querySelector<HTMLElement>('.box')?.focus();\n }\n if (!next.checked) next.select();\n }\n\n private select(): void {\n if (this.disabled || this.checked) return;\n // Desmarca os outros radios do mesmo grupo (mesmo name, mesma árvore).\n for (const r of this._group()) {\n if (r !== this) r.checked = false;\n }\n this.checked = true;\n this.dispatchEvent(\n new CustomEvent('change', {\n bubbles: true,\n composed: true,\n detail: { checked: true, value: this.getAttr('value') },\n }),\n );\n // Two-way binding do Angular: [(checked)] = [checked] + (checkedChange).\n this.dispatchEvent(\n new CustomEvent('checkedChange', {\n bubbles: true,\n composed: true,\n detail: { checked: true, value: this.getAttr('value') },\n }),\n );\n }\n\n /** Estado marcado inicial (para form.reset() restaurar o grupo). */\n private _initialChecked = false;\n private _initialCheckedCaptured = false;\n\n protected override connectedCallback(): void {\n if (!this._initialCheckedCaptured) {\n this._initialCheckedCaptured = true;\n this._initialChecked = this.checked;\n }\n super.connectedCallback();\n }\n\n /** form.reset(): restaura o estado marcado inicial do grupo. */\n override formResetCallback(): void {\n this.checked = this._initialChecked;\n this.updateFormValue();\n }\n}\n\nexport function defineFxRadio(): typeof FxRadio {\n return defineElement('fx-radio', FxRadio);\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FxFormElement } from '../../core/form-element';
|
|
2
2
|
/**
|
|
3
3
|
* <fx-rating> — Evaluación por estrellas.
|
|
4
4
|
*
|
|
5
|
+
* Form-Associated Custom Element: dentro de un <form> entra en el FormData
|
|
6
|
+
* (via atributo `name`) y recibe reset/disable del form.
|
|
7
|
+
*
|
|
5
8
|
* Atributos: value (0 a max), max (número de estrellas, default 5),
|
|
6
|
-
* readonly, disabled, allow-half (permite medias estrellas),
|
|
9
|
+
* name, readonly, disabled, allow-half (permite medias estrellas),
|
|
7
10
|
* size (sm|md|lg).
|
|
8
|
-
* Eventos (composed): `change` (detail: { value })
|
|
11
|
+
* Eventos (composed): `change`, `input` e `valueChange` (detail: { value }) —
|
|
12
|
+
* `input` aciona o v-model do Vue e `valueChange` o [(value)] do Angular.
|
|
9
13
|
*/
|
|
10
|
-
export declare class FxRating extends
|
|
14
|
+
export declare class FxRating extends FxFormElement {
|
|
11
15
|
static styles: string;
|
|
12
16
|
static get observedAttributes(): string[];
|
|
13
17
|
get value(): number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rating.d.ts","sourceRoot":"","sources":["../../../src/components/rating/rating.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"rating.d.ts","sourceRoot":"","sources":["../../../src/components/rating/rating.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAIxD;;;;;;;;;;;GAWG;AACH,qBAAa,QAAS,SAAQ,aAAa;IACzC,OAAgB,MAAM,SAmCpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,IAAI,KAAK,IAAI,MAAM,CAGlB;IACD,IAAI,KAAK,CAAC,CAAC,EAAE,MAAM,EAKlB;IAED,IAAI,GAAG,IAAI,MAAM,CAIhB;IACD,IAAI,GAAG,CAAC,CAAC,EAAE,MAAM,EAA0C;cAExC,MAAM,IAAI,IAAI;IAkBjC,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,UAAU;IAuBlB,OAAO,CAAC,MAAM;CAMf;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 _FxRating = class _FxRating extends
|
|
4
|
+
const _FxRating = class _FxRating extends FxFormElement {
|
|
5
5
|
static get observedAttributes() {
|
|
6
6
|
return ["value", "max", "readonly", "disabled", "allow-half", "size"];
|
|
7
7
|
}
|
|
@@ -12,6 +12,7 @@ const _FxRating = class _FxRating extends FxElement {
|
|
|
12
12
|
set value(v) {
|
|
13
13
|
this.setAttribute("value", String(Math.min(this.max, Math.max(0, v))));
|
|
14
14
|
this._syncVisual();
|
|
15
|
+
this.updateFormValue();
|
|
15
16
|
}
|
|
16
17
|
get max() {
|
|
17
18
|
const n = Number(this.getAttr("max", "5"));
|
|
@@ -31,6 +32,7 @@ const _FxRating = class _FxRating extends FxElement {
|
|
|
31
32
|
`);
|
|
32
33
|
this._bindStars();
|
|
33
34
|
this._syncVisual();
|
|
35
|
+
this.updateFormValue();
|
|
34
36
|
}
|
|
35
37
|
_starNodes() {
|
|
36
38
|
return Array.from(this.root.querySelectorAll(".star"));
|
|
@@ -54,6 +56,7 @@ const _FxRating = class _FxRating extends FxElement {
|
|
|
54
56
|
let next = n;
|
|
55
57
|
if (this.hasAttr("allow-half")) next += -0.5;
|
|
56
58
|
this.value = next;
|
|
59
|
+
this.emitValueEvents();
|
|
57
60
|
this.dispatchEvent(
|
|
58
61
|
new CustomEvent("change", { bubbles: true, composed: true, detail: { value: this.value } })
|
|
59
62
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rating.js","sources":["../../../src/components/rating/rating.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"rating.js","sources":["../../../src/components/rating/rating.ts"],"sourcesContent":["import { FxFormElement } from '../../core/form-element';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\n\n/**\n * <fx-rating> — Evaluación por estrellas.\n *\n * Form-Associated Custom Element: dentro de un <form> entra en el FormData\n * (via atributo `name`) y recibe reset/disable del form.\n *\n * Atributos: value (0 a max), max (número de estrellas, default 5),\n * name, readonly, disabled, allow-half (permite medias estrellas),\n * size (sm|md|lg).\n * Eventos (composed): `change`, `input` e `valueChange` (detail: { value }) —\n * `input` aciona o v-model do Vue e `valueChange` o [(value)] do Angular.\n */\nexport class FxRating extends FxFormElement {\n static override styles = css`\n :host {\n display: inline-flex;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n --_star: calc(var(--fx-font-size) + 8px);\n }\n :host([size='sm']) { --_star: calc(var(--fx-font-size) + 4px); }\n :host([size='lg']) { --_star: calc(var(--fx-font-size) + 14px); }\n :host([disabled]) { opacity: 0.55; }\n :host([disabled]) .stars { pointer-events: none; }\n :host([readonly]) .stars { pointer-events: none; }\n\n .stars {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n cursor: pointer;\n }\n .star {\n position: relative;\n display: inline-flex;\n width: var(--_star);\n height: var(--_star);\n font-size: var(--_star);\n line-height: 1;\n color: var(--fx-text-muted);\n cursor: pointer;\n user-select: none;\n transition: transform var(--fx-motion-duration-fast) var(--fx-motion-easing), color var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n .star:hover { transform: scale(1.1); }\n .star[hidden] { display: none; }\n .star.filled { color: var(--fx-color-warning); }\n .star.hovered { color: var(--fx-color-warning); }\n `;\n\n static override get observedAttributes(): string[] {\n return ['value', 'max', 'readonly', 'disabled', 'allow-half', 'size'];\n }\n\n get value(): number {\n const n = Number(this.getAttr('value', '0'));\n return Number.isFinite(n) ? Math.min(this.max, Math.max(0, n)) : 0;\n }\n set value(v: number) {\n this.setAttribute('value', String(Math.min(this.max, Math.max(0, v))));\n this._syncVisual();\n // `value` é observado (re-render), mas o setter cobre o caminho sem render.\n this.updateFormValue();\n }\n\n get max(): number {\n const n = Number(this.getAttr('max', '5'));\n const m = Number.isFinite(n) ? Math.round(n) : 5;\n return Math.min(10, Math.max(1, m));\n }\n set max(v: number) { this.setAttribute('max', String(v)); }\n\n protected override render(): void {\n const stars = this.max;\n const starsHtml = Array.from({ length: stars }, (_, i) => i + 1)\n .map((n) => `<span class=\"star\" data-n=\"${n}\" part=\"star\" title=\"${n} ${this._label(n)}\">★</span>`)\n .join('');\n\n this.setTemplate(`\n <span class=\"stars\" part=\"stars\" role=\"radiogroup\">\n ${starsHtml}\n </span>\n `);\n\n this._bindStars();\n this._syncVisual();\n // Publica name/value no form (ElementInternals) a cada render.\n this.updateFormValue();\n }\n\n private _starNodes(): HTMLElement[] {\n return Array.from(this.root.querySelectorAll<HTMLElement>('.star'));\n }\n\n private _label(n: number): string {\n return n === 1 ? 'estrella' : 'estrellas';\n }\n\n private _syncVisual(): void {\n const v = this.value;\n this._starNodes().forEach((star) => {\n const n = Number(star.dataset.n);\n star.classList.toggle('filled', v >= n);\n });\n }\n\n private _bindStars(): void {\n const stars = this._starNodes();\n stars.forEach((star) => {\n const n = Number(star.dataset.n);\n star.addEventListener('click', () => {\n if (this.hasAttr('readonly') || this.hasAttr('disabled')) return;\n let next = n;\n if (this.hasAttr('allow-half')) next += -0.5; // para simplificar, valor entero\n this.value = next;\n // input (v-model do Vue) + valueChange ([(value)] do Angular).\n this.emitValueEvents();\n this.dispatchEvent(\n new CustomEvent('change', { bubbles: true, composed: true, detail: { value: this.value } }),\n );\n });\n star.addEventListener('mouseenter', () => {\n if (this.hasAttr('readonly') || this.hasAttr('disabled')) return;\n this._hover(n);\n });\n star.addEventListener('mouseleave', () => this._syncVisual());\n });\n }\n\n private _hover(n: number): void {\n this._starNodes().forEach((star) => {\n const nn = Number(star.dataset.n);\n star.classList.toggle('hovered', nn <= n);\n });\n }\n}\n\nexport function defineFxRating(): typeof FxRating {\n return defineElement('fx-rating', FxRating);\n}"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FxFormElement } from '../../core/form-element';
|
|
2
2
|
/**
|
|
3
3
|
* <fx-select> — Campo de seleção com dropdown customizado.
|
|
4
4
|
*
|
|
5
|
+
* Form-Associated Custom Element: dentro de um <form> entra no FormData
|
|
6
|
+
* (via atributo `name`) e recebe reset/disable do form.
|
|
7
|
+
*
|
|
5
8
|
* Escreva os filhos como `<option>` nativos no light DOM; eles são
|
|
6
9
|
* espelhados automaticamente (MutationObserver):
|
|
7
10
|
*
|
|
@@ -14,9 +17,10 @@ import { FxElement } from '../../core/base';
|
|
|
14
17
|
* searchable, clearable, search-placeholder, no-results,
|
|
15
18
|
* placement (auto|top|bottom — direção do painel; `auto` inverte quando
|
|
16
19
|
* não cabe na viewport).
|
|
17
|
-
*
|
|
20
|
+
* Eventos: `change`, `input` e `valueChange` (composed, detail: { value }) —
|
|
21
|
+
* `input` aciona o v-model do Vue e `valueChange` o [(value)] do Angular.
|
|
18
22
|
*/
|
|
19
|
-
export declare class FxSelect extends
|
|
23
|
+
export declare class FxSelect extends FxFormElement {
|
|
20
24
|
static styles: string;
|
|
21
25
|
static get observedAttributes(): string[];
|
|
22
26
|
/** Espaço (px) entre o campo e o painel — espelha `--fx-select-panel-offset`. */
|
|
@@ -61,6 +65,8 @@ export declare class FxSelect extends FxElement {
|
|
|
61
65
|
/** Navegação por teclado entre as opções do listbox (WCAG 2.1.1). */
|
|
62
66
|
private _navigateOptions;
|
|
63
67
|
protected render(): void;
|
|
68
|
+
/** form.reset(): restaura o valor inicial e re-renderiza o rótulo. */
|
|
69
|
+
formResetCallback(): void;
|
|
64
70
|
}
|
|
65
71
|
export declare function defineFxSelect(): typeof FxSelect;
|
|
66
72
|
//# sourceMappingURL=select.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../../src/components/select/select.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../../src/components/select/select.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAKxD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,QAAS,SAAQ,aAAa;IACzC,OAAgB,MAAM,SA+KpB;IAIF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAIjD;IAED,iFAAiF;IACjF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAK;IAChC,kEAAkE;IAClE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAK;IACnC,sEAAsE;IACtE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAM;IAExC,OAAO,CAAC,QAAQ,CAAC,CAAmB;IACpC,OAAO,CAAC,WAAW,CAAC,CAAqB;IACzC,4EAA4E;IAC5E,OAAO,CAAC,QAAQ,CAAC,CAAa;IAE9B,wCAAwC;IACxC,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAErB;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;IACD,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,EAMtB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAE1B;IAED,OAAO,KAAK,OAAO,GAKlB;cAEkB,iBAAiB,IAAI,IAAI;cAezB,oBAAoB,IAAI,IAAI;IAO/C,8EAA8E;IAC9E,OAAO,CAAC,MAAM;IAKd,+DAA+D;IAC/D,OAAO,CAAC,OAAO;IAQf,qEAAqE;IACrE,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,aAAa;IAOrB;;;;;;;OAOG;IACH,SAAS,CAAC,cAAc,IAAI,IAAI;IAuChC,0EAA0E;IAC1E,OAAO,CAAC,MAAM,CAAC,UAAU;IAMzB,OAAO,CAAC,MAAM;IAUd,qEAAqE;IACrE,OAAO,CAAC,gBAAgB;cAiBL,MAAM,IAAI,IAAI;IA6GjC,sEAAsE;IAC7D,iBAAiB,IAAI,IAAI;CAInC;AAED,wBAAgB,cAAc,IAAI,OAAO,QAAQ,CAEhD"}
|
|
@@ -1,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 _FxSelect = class _FxSelect extends
|
|
5
|
+
const _FxSelect = class _FxSelect extends FxFormElement {
|
|
6
6
|
// `value` fica FORA da observação: refleti-lo no change não pode
|
|
7
7
|
// re-renderizar o template e fechar o dropdown.
|
|
8
8
|
static get observedAttributes() {
|
|
@@ -21,6 +21,7 @@ const _FxSelect = class _FxSelect extends FxElement {
|
|
|
21
21
|
}
|
|
22
22
|
set value(value) {
|
|
23
23
|
this.setAttribute("value", value);
|
|
24
|
+
this.updateFormValue();
|
|
24
25
|
}
|
|
25
26
|
get disabled() {
|
|
26
27
|
return this.hasAttr("disabled");
|
|
@@ -122,6 +123,7 @@ const _FxSelect = class _FxSelect extends FxElement {
|
|
|
122
123
|
this.value = value;
|
|
123
124
|
this._fechar();
|
|
124
125
|
this.render();
|
|
126
|
+
this.emitValueEvents();
|
|
125
127
|
this.dispatchEvent(
|
|
126
128
|
new CustomEvent("change", { bubbles: true, composed: true, detail: { value } })
|
|
127
129
|
);
|
|
@@ -224,6 +226,7 @@ const _FxSelect = class _FxSelect extends FxElement {
|
|
|
224
226
|
this.value = "";
|
|
225
227
|
this._fechar();
|
|
226
228
|
this.render();
|
|
229
|
+
this.emitValueEvents();
|
|
227
230
|
this.dispatchEvent(
|
|
228
231
|
new CustomEvent("change", { bubbles: true, composed: true, detail: { value: "" } })
|
|
229
232
|
);
|
|
@@ -231,6 +234,12 @@ const _FxSelect = class _FxSelect extends FxElement {
|
|
|
231
234
|
this.root.querySelectorAll(".opt").forEach((opt) => {
|
|
232
235
|
opt.addEventListener("click", () => this.select(opt.dataset.value));
|
|
233
236
|
});
|
|
237
|
+
this.updateFormValue();
|
|
238
|
+
}
|
|
239
|
+
/** form.reset(): restaura o valor inicial e re-renderiza o rótulo. */
|
|
240
|
+
formResetCallback() {
|
|
241
|
+
super.formResetCallback();
|
|
242
|
+
this.render();
|
|
234
243
|
}
|
|
235
244
|
};
|
|
236
245
|
_FxSelect.styles = css`
|