@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,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 _FxDatepicker = class _FxDatepicker extends
|
|
5
|
+
const _FxDatepicker = class _FxDatepicker extends FxFormElement {
|
|
6
6
|
constructor() {
|
|
7
7
|
super(...arguments);
|
|
8
8
|
this.time = { h: 0, m: 0, s: 0 };
|
|
@@ -10,6 +10,32 @@ const _FxDatepicker = class _FxDatepicker extends FxElement {
|
|
|
10
10
|
static get observedAttributes() {
|
|
11
11
|
return ["value", "start", "end", "values", "mode", "min", "max", "placeholder", "disabled", "format", "show-time", "free-text", "error", "invalid", "success", "valid"];
|
|
12
12
|
}
|
|
13
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
14
|
+
super.attributeChangedCallback(name, oldValue, newValue);
|
|
15
|
+
if (name === "value" || name === "start" || name === "end" || name === "values") {
|
|
16
|
+
this.updateFormValue();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** No FormData do form, conforme o modo (single/range/multiple). */
|
|
20
|
+
getFormValue() {
|
|
21
|
+
if (this.mode === "range") {
|
|
22
|
+
const start = this.getAttr("start");
|
|
23
|
+
const end = this.getAttr("end");
|
|
24
|
+
if (!this.name || !start && !end) return null;
|
|
25
|
+
const fd = new FormData();
|
|
26
|
+
if (start) fd.append(`${this.name}-start`, start);
|
|
27
|
+
if (end) fd.append(`${this.name}-end`, end);
|
|
28
|
+
return fd;
|
|
29
|
+
}
|
|
30
|
+
if (this.mode === "multiple") {
|
|
31
|
+
const values = this.values;
|
|
32
|
+
if (!this.name || values.length === 0) return null;
|
|
33
|
+
const fd = new FormData();
|
|
34
|
+
values.forEach((v) => fd.append(this.name, v));
|
|
35
|
+
return fd;
|
|
36
|
+
}
|
|
37
|
+
return this.getAttr("value") || null;
|
|
38
|
+
}
|
|
13
39
|
get mode() {
|
|
14
40
|
const m = this.getAttr("mode", "single");
|
|
15
41
|
return ["single", "range", "multiple"].includes(m) ? m : "single";
|
|
@@ -38,6 +64,14 @@ const _FxDatepicker = class _FxDatepicker extends FxElement {
|
|
|
38
64
|
this.toggleAttr("disabled", Boolean(value));
|
|
39
65
|
}
|
|
40
66
|
connectedCallback() {
|
|
67
|
+
if (!this._initialState) {
|
|
68
|
+
this._initialState = {
|
|
69
|
+
value: this.getAttr("value"),
|
|
70
|
+
start: this.getAttr("start"),
|
|
71
|
+
end: this.getAttr("end"),
|
|
72
|
+
values: this.getAttr("values")
|
|
73
|
+
};
|
|
74
|
+
}
|
|
41
75
|
super.connectedCallback();
|
|
42
76
|
this.docListener = (e) => {
|
|
43
77
|
if (!this.hasAttr("open")) return;
|
|
@@ -50,6 +84,15 @@ const _FxDatepicker = class _FxDatepicker extends FxElement {
|
|
|
50
84
|
super.disconnectedCallback();
|
|
51
85
|
if (this.docListener) document.removeEventListener("click", this.docListener);
|
|
52
86
|
}
|
|
87
|
+
/** form.reset(): restaura value/start/end/values iniciais (re-render via atributos observados). */
|
|
88
|
+
formResetCallback() {
|
|
89
|
+
const s = this._initialState ?? { value: "", start: "", end: "", values: "" };
|
|
90
|
+
this.setAttribute("value", s.value);
|
|
91
|
+
this.setAttribute("start", s.start);
|
|
92
|
+
this.setAttribute("end", s.end);
|
|
93
|
+
this.setAttribute("values", s.values);
|
|
94
|
+
this.updateFormValue();
|
|
95
|
+
}
|
|
53
96
|
/** Formata uma data ISO ("YYYY-MM-DD" ou com "THH:mm:ss") segundo o atributo format. */
|
|
54
97
|
formatDate(iso) {
|
|
55
98
|
if (!iso) return "";
|
|
@@ -222,6 +265,7 @@ const _FxDatepicker = class _FxDatepicker extends FxElement {
|
|
|
222
265
|
}
|
|
223
266
|
}
|
|
224
267
|
emitChange(detail) {
|
|
268
|
+
this.emitValueEvents(detail);
|
|
225
269
|
this.dispatchEvent(
|
|
226
270
|
new CustomEvent("change", { bubbles: true, composed: true, detail })
|
|
227
271
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"datepicker.js","sources":["../../../src/components/datepicker/datepicker.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-datepicker> — Campo de data com calendário suspenso.\n *\n * Um input com ícone de calendário no final; ao clicar, abre o\n * <fx-calendar> em um popover. As datas selecionadas aparecem no input.\n *\n * Atributos:\n * - mode=\"single\" | \"range\" | \"multiple\" (padrão: single)\n * - value=\"YYYY-MM-DD\" → single\n * - start/end → range\n * - values=\"a,b,c\" → múltiplas datas (CSV)\n * - min/max, placeholder, disabled, clearable, size (sm|md|lg)\n * - format=\"dd/mm/yyyy\" → máscara de exibição (tokens: dd mm yyyy, HH MM SS)\n * - show-time → inclui seleção de hora:minuto:segundo no popover;\n * o valor passa a ser \"YYYY-MM-DDTHH:mm:ss\"\n *\n * Evento: `change` (composed):\n * - single: { value }\n * - range: { value, start, end }\n * - multiple: { values: string[] }\n */\nexport class FxDatepicker 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 }\n .field {\n display: inline-flex;\n align-items: center;\n gap: var(--fx-space-xs);\n width: 100%;\n box-sizing: border-box;\n /* Base = tamanho padrão (md): 40px */\n min-height: var(--fx-size-md, 40px);\n min-width: 150px;\n padding: var(--fx-space-xs) var(--fx-space-md);\n /* Altura determinística: a caixa fica EXATAMENTE no token --fx-size-*. */\n line-height: var(--fx-font-line-height);\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 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 :host([size='sm']) .field { min-height: var(--fx-size-sm); min-width: 180px; }\n :host([size='md']) .field { min-height: var(--fx-size-md); }\n :host([size='lg']) .field { min-height: var(--fx-size-lg); min-width: 220px; font-size: calc(var(--fx-font-size) + 4px); }\n /* Range e multiple exibem mais de uma data: campo mais largo */\n :host([mode='range']) .field { min-width: 250px; }\n :host([mode='multiple']) .field { min-width: 220px; }\n :host([mode='range'][size='lg']) .field { min-width: 360px; }\n :host([mode='multiple'][size='lg']) .field { min-width: 320px; }\n :host([show-time]) .field { min-width: 250px; }\n /* Full width: o host estica até o pai (os min-width dos modos são\n descartados para permitir layouts estreitos sob controle do pai). */\n :host([full]) { display: block; width: 100%; }\n :host([full]) .field { min-width: 0; }\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]) .field,\n :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([error]) .field:focus-within,\n :host([invalid]) .field:focus-within,\n :host([error][open]) .field,\n :host([invalid][open]) .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,\n :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([success]) .field:focus-within,\n :host([valid]) .field:focus-within,\n :host([success][open]) .field,\n :host([valid][open]) .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 .field:hover { border-color: var(--fx-border-hover); }\n .field:focus-within {\n outline: none;\n border-color: var(--fx-color-primary);\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n .display {\n flex: 1;\n border: none;\n background: none;\n font: inherit;\n color: var(--fx-text-default);\n outline: none;\n cursor: pointer;\n text-align: left;\n padding: 0;\n min-width: 0;\n }\n .display::placeholder { color: var(--fx-text-muted); }\n .cal-icon { color: var(--fx-text-muted); display: inline-flex; pointer-events: none; }\n .clear {\n border: none;\n background: none;\n color: var(--fx-text-muted);\n cursor: pointer;\n font-size: calc(var(--fx-font-size) + 4px);\n line-height: 1;\n padding: 0 2px;\n border-radius: var(--fx-radius-full);\n }\n .clear:hover { color: var(--fx-color-danger); }\n .clear[hidden] { display: none; }\n .pop {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n z-index: var(--fx-z-dropdown, 1000);\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 overflow: hidden;\n }\n :host([up]) .pop {\n top: auto;\n bottom: calc(100% + 4px);\n }\n :host([open]) .pop { display: block; }\n .time-row {\n display: flex;\n align-items: center;\n gap: var(--fx-space-xs);\n padding: var(--fx-space-sm) var(--fx-space-md);\n border-top: 1px solid var(--fx-border-default);\n background: var(--fx-surface-background);\n border-radius: 0 0 var(--fx-radius-md) var(--fx-radius-md);\n font-size: calc(var(--fx-font-size) - 2px);\n color: var(--fx-text-default);\n }\n .time-row label { color: var(--fx-text-muted); }\n .time-row input {\n width: 38px;\n font: inherit;\n color: var(--fx-text-default);\n background: var(--fx-surface-background);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-sm);\n padding: 2px 2px;\n outline: none;\n text-align: center;\n -moz-appearance: textfield;\n appearance: textfield;\n }\n .time-row input::-webkit-outer-spin-button,\n .time-row input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }\n .time-row .sep { color: var(--fx-text-muted); font-weight: bold; padding: 0 1px; }\n .time-row input:focus {\n border-color: var(--fx-color-primary);\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n :host([disabled]) .field { opacity: 0.55; cursor: not-allowed; background: var(--fx-surface-surface-hover); }\n :host([disabled]) .display { cursor: not-allowed; }\n :host([free-text]) .display { cursor: text; }\n :host([free-text]) .display.invalid { border-bottom: 2px solid var(--fx-color-danger); }\n `;\n\n static override get observedAttributes(): string[] {\n return ['value', 'start', 'end', 'values', 'mode', 'min', 'max', 'placeholder', 'disabled', 'format', 'show-time', 'free-text', 'error', 'invalid', 'success', 'valid'];\n }\n\n private docListener?: (e: Event) => void;\n /** Hora/minuto/segundo do modo show-time. */\n private time = { h: 0, m: 0, s: 0 };\n\n get mode(): string {\n const m = this.getAttr('mode', 'single');\n return ['single', 'range', 'multiple'].includes(m) ? m : 'single';\n }\n set mode(value: string) {\n this.setAttribute('mode', value);\n }\n\n get value(): string {\n return this.getAttr('value');\n }\n set value(v: string) {\n this.setAttribute('value', v);\n }\n\n /** Datas do modo múltiplo (CSV no atributo). */\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 protected override connectedCallback(): void {\n super.connectedCallback();\n // Fecha o popover ao clicar fora.\n this.docListener = (e: Event) => {\n if (!this.hasAttr('open')) return;\n if (e.composedPath().includes(this)) return;\n this.removeAttribute('open');\n };\n document.addEventListener('click', this.docListener);\n }\n\n protected override disconnectedCallback(): void {\n super.disconnectedCallback();\n if (this.docListener) document.removeEventListener('click', this.docListener);\n }\n\n /** Formata uma data ISO (\"YYYY-MM-DD\" ou com \"THH:mm:ss\") segundo o atributo format. */\n private formatDate(iso: string): string {\n if (!iso) return '';\n const fmt = this.getAttr('format');\n // Sem máscara: exibe apenas a parte da data (a hora entra via timeText\n // quando show-time está ativo — nunca duplicada).\n if (!fmt) return iso.split('T')[0]!;\n const [datePart, timePart] = iso.split('T');\n const [y = '', m = '', d = ''] = datePart.split('-');\n const [H = '00', M = '00', S = '00'] = (timePart ?? '').split(':');\n // Placeholders para evitar colisão entre mm (mês) e MM (minuto).\n return fmt\n .replace(/yyyy/gi, '\\u0001')\n .replace(/MM/g, '\\u0005')\n .replace(/mm/g, '\\u0002')\n .replace(/dd/gi, '\\u0003')\n .replace(/HH/g, '\\u0004')\n .replace(/SS/gi, '\\u0006')\n .replace(/\\u0001/g, y)\n .replace(/\\u0002/g, m)\n .replace(/\\u0003/g, d)\n .replace(/\\u0004/g, H)\n .replace(/\\u0005/g, M)\n .replace(/\\u0006/g, S);\n }\n\n /** Texto exibido no input conforme o modo. */\n private displayText(): string {\n if (this.mode === 'range') {\n const s = this.getAttr('start');\n const e = this.getAttr('end');\n const arrow = this.hasAttr('show-time') ? ' ' : ' → ';\n if (!s && !e) return '';\n if (this.hasAttr('show-time')) {\n const f = (iso?: string): string => (iso ? `${this.formatDate(iso)} ${this.timeText()}` : iso ? `${this.formatDate(iso)} …` : '');\n return s && e ? `${f(s)} → ${f(e)}` : f(s);\n }\n void arrow;\n return s && e ? `${this.formatDate(s)} → ${this.formatDate(e)}` : s ? `${this.formatDate(s)} → …` : '';\n }\n if (this.mode === 'multiple') {\n return this.values.map((v) => this.formatDate(v)).join(', ');\n }\n const base = this.formatDate(this.value);\n return base && this.hasAttr('show-time') ? `${base} ${this.timeText()}` : base;\n }\n\n /** \"HH:MM:SS\" do estado interno de show-time. */\n private timeText(): string {\n const p = (n: number): string => String(n).padStart(2, '0');\n return `${p(this.time.h)}:${p(this.time.m)}:${p(this.time.s)}`;\n }\n\n /** Extrai hora de um valor ISO inicial, se houver. */\n private loadTimeFromValue(): void {\n const src = this.value || this.getAttr('start') || '';\n const t = src.split('T')[1];\n if (!t) return;\n const [h, m, s] = t.split(':').map((n) => parseInt(n, 10));\n this.time = { h: h || 0, m: m || 0, s: s || 0 };\n }\n\n /** Anexa o horário interno ao ISO puro vindo do calendário. */\n private withTime(iso: string): string {\n if (!iso) return iso;\n return this.hasAttr('show-time')\n ? `${iso}T${this.timeText()}`\n : iso;\n }\n\n protected override render(): void {\n const disabled = this.disabled;\n const clearable = this.hasAttr('clearable');\n const text = this.displayText();\n const hasValue = text.length > 0;\n const showTime = this.hasAttr('show-time');\n this.loadTimeFromValue();\n\n // Atributos repassados ao calendário interno, conforme o modo.\n let calAttrs = `min=\"${esc(this.getAttr('min'))}\" max=\"${esc(this.getAttr('max'))}\"`;\n if (this.mode === 'range') {\n calAttrs += ` range start=\"${esc(this.getAttr('start'))}\" end=\"${esc(this.getAttr('end'))}\"`;\n } else if (this.mode === 'multiple') {\n // Repassa apenas as partes de data (sem hora) para o calendário pintar.\n const dates = this.values.map((v) => v.split('T')[0]).join(',');\n calAttrs += ` mode=\"multiple\" values=\"${esc(dates)}\"`;\n } else {\n const v = this.value.split('T')[0] ?? '';\n calAttrs += ` value=\"${esc(v)}\"`;\n }\n\n this.setTemplate(`\n <div class=\"field\" part=\"field\">\n <input class=\"display\" part=\"display\" type=\"text\" ${this.hasAttr('free-text') && !disabled ? '' : 'readonly'}\n placeholder=\"${esc(this.getAttr('placeholder', 'dd/mm/aaaa'))}\"\n value=\"${esc(text)}\" ${disabled ? 'disabled' : ''}>\n <button type=\"button\" class=\"clear\" part=\"clear\" aria-label=\"Limpar\"\n ${clearable && hasValue ? '' : 'hidden'}>\n ×\n </button>\n <span class=\"cal-icon\" part=\"icon\" aria-hidden=\"true\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"18\" rx=\"2\"/>\n <path d=\"M16 2v4M8 2v4M3 10h18\"/>\n </svg>\n </span>\n </div>\n <div class=\"pop\" part=\"popover\">\n <fx-calendar ${calAttrs}></fx-calendar>\n ${\n showTime\n ? `<div class=\"time-row\" part=\"time\">\n <input class=\"t-h\" type=\"number\" min=\"0\" max=\"23\" value=\"${this.time.h}\" aria-label=\"Hora\"><span class=\"sep\">:</span>\n <input class=\"t-m\" type=\"number\" min=\"0\" max=\"59\" value=\"${this.time.m}\" aria-label=\"Minuto\"><span class=\"sep\">:</span>\n <input class=\"t-s\" type=\"number\" min=\"0\" max=\"59\" value=\"${this.time.s}\" aria-label=\"Segundo\">\n </div>`\n : ''\n }\n </div>\n `);\n\n const field = this.root.querySelector('.field');\n if (!field) return;\n if (disabled) return;\n\n field.addEventListener('click', (e) => {\n if ((e.target as HTMLElement).closest('.clear')) return;\n // Com free-text, clicar no próprio input serve para digitar — não abre.\n if (this.hasAttr('free-text') && (e.target as HTMLElement).classList.contains('display')) 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 + 340 > window.innerHeight && r.top > 340);\n } else {\n this.removeAttribute('up');\n }\n });\n\n // Limpar: zera tudo conforme o modo.\n this.root.querySelector('.clear')?.addEventListener('click', (e) => {\n e.stopPropagation();\n this.removeAttribute('value');\n this.removeAttribute('start');\n this.removeAttribute('end');\n this.removeAttribute('values');\n this.removeAttribute('open');\n this.render();\n this.emitChange({});\n });\n\n // Sincroniza seleção do calendário de volta para os atributos.\n const cal = this.root.querySelector<HTMLElement>('.pop fx-calendar');\n cal?.addEventListener('change', (e) => {\n const d = (e as CustomEvent).detail ?? {};\n if (d.start !== undefined || d.end !== undefined) {\n if (d.start) this.setAttribute('start', this.withTime(d.start));\n else this.removeAttribute('start');\n if (d.end) this.setAttribute('end', this.withTime(d.end));\n else this.removeAttribute('end');\n }\n if (d.values !== undefined) {\n this.values = d.values.map((v: string) => this.withTime(v));\n }\n if (d.value !== undefined) {\n if (d.value) this.setAttribute('value', this.withTime(d.value));\n else this.removeAttribute('value');\n // No modo single, fecha ao escolher.\n if (this.mode === 'single') this.removeAttribute('open');\n }\n this.render();\n this.emitChange(d);\n });\n\n // free-text: valida ao sair do campo ou pressionar Enter.\n const display = this.root.querySelector<HTMLInputElement>('.display');\n if (display && this.hasAttr('free-text') && !disabled) {\n display.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n this.commitTyped(display);\n }\n });\n display.addEventListener('blur', () => this.commitTyped(display));\n }\n\n // show-time: captura alterações de hora/minuto/segundo.\n if (showTime) {\n const applyTime = (): void => {\n const h = this.root.querySelector<HTMLInputElement>('.t-h');\n const m = this.root.querySelector<HTMLInputElement>('.t-m');\n const s = this.root.querySelector<HTMLInputElement>('.t-s');\n const clamp = (v: string | undefined, max: number): number =>\n Math.min(max, Math.max(0, parseInt(v ?? '0', 10) || 0));\n this.time = {\n h: clamp(h?.value, 23),\n m: clamp(m?.value, 59),\n s: clamp(s?.value, 59),\n };\n // Reanexa o horário aos valores já selecionados.\n const reattach = (iso: string): string => iso.split('T')[0] ? `${iso.split('T')[0]}T${this.timeText()}` : iso;\n if (this.value) this.setAttribute('value', reattach(this.value));\n if (this.getAttr('start')) this.setAttribute('start', reattach(this.getAttr('start')));\n if (this.getAttr('end')) this.setAttribute('end', reattach(this.getAttr('end')));\n if (this.values.length) {\n this.values = this.values.map((v) => reattach(v));\n }\n // Atualiza apenas o texto do input, sem re-render completo.\n const display = this.root.querySelector<HTMLInputElement>('.display');\n if (display) display.value = this.displayText();\n this.emitChange({ value: this.value });\n };\n ['.t-h', '.t-m', '.t-s'].forEach((sel) => {\n this.root.querySelector(sel)?.addEventListener('input', applyTime);\n });\n }\n }\n\n private emitChange(detail: Record<string, unknown>): void {\n this.dispatchEvent(\n new CustomEvent('change', { bubbles: true, composed: true, detail }),\n );\n }\n\n /**\n * Interpreta o texto digitado livremente e devolve ISO \"YYYY-MM-DD\"\n * (ou null se inválido). Aceita a máscara do atributo `format`\n * (tokens dd/mm/yyyy) ou ISO direto.\n */\n private parseTyped(raw: string): string | null {\n const text = raw.trim();\n if (!text) return null;\n const fmt = this.getAttr('format', 'dd/mm/yyyy').toLowerCase();\n\n // Ordem dos tokens conforme a máscara informada.\n const order = ['dd', 'mm', 'yyyy'].filter((t) => fmt.includes(t));\n if (order.length < 3) return null;\n\n const parts = text.split(new RegExp(`[^0-9]+`)).filter(Boolean);\n if (parts.length !== 3) return null;\n\n const map: Record<string, string> = {};\n order.forEach((t, i) => (map[t] = parts[i]!));\n const iso = `${map['yyyy']}-${(map['mm'] ?? '').padStart(2, '0')}-${(map['dd'] ?? '').padStart(2, '0')}`;\n\n // Validação de data real.\n const d = new Date(`${iso}T12:00:00`);\n if (Number.isNaN(d.getTime())) return null;\n const [y, m, day] = iso.split('-').map((n) => parseInt(n, 10));\n if (d.getFullYear() !== y || d.getMonth() + 1 !== m || d.getDate() !== day) return null;\n\n // Limites min/max (apenas a parte da data).\n const cmp = (a: string, b: string): number => a.split('T')[0]!.localeCompare(b.split('T')[0]!);\n const min = this.getAttr('min');\n const max = this.getAttr('max');\n if (min && cmp(iso, min) < 0) return null;\n if (max && cmp(iso, max) > 0) return null;\n\n return iso;\n }\n\n /** Aplica o texto digitado no modo free-text (blur/Enter). */\n private commitTyped(input: HTMLInputElement): void {\n const parsed = this.parseTyped(input.value);\n if (!parsed) {\n // Inválido: sinaliza, reverte o texto e emite evento.\n input.classList.add('invalid');\n this.dispatchEvent(\n new CustomEvent('invalid', { bubbles: true, composed: true, detail: { text: input.value } }),\n );\n input.value = this.displayText();\n setTimeout(() => input.classList.remove('invalid'), 1500);\n return;\n }\n if (this.mode === 'single') {\n this.setAttribute('value', this.withTime(parsed));\n this.emitChange({ value: this.value });\n } else if (this.mode === 'range') {\n // Primeira digitação vira start; com start preenchido, vira end.\n if (!this.getAttr('start') || (this.getAttr('start') && this.getAttr('end'))) {\n this.setAttribute('start', this.withTime(parsed));\n this.removeAttribute('end');\n } else {\n const [a, b] = [this.getAttr('start')!.split('T')[0]!, parsed].sort();\n this.setAttribute('start', this.withTime(a));\n this.setAttribute('end', this.withTime(b));\n }\n this.emitChange({ start: this.getAttr('start'), end: this.getAttr('end') });\n } else {\n const list = this.values.filter((v) => v.split('T')[0] !== parsed);\n list.push(this.withTime(parsed));\n this.values = list;\n this.emitChange({ values: this.values });\n }\n this.render();\n }\n}\n\nexport function defineFxDatepicker(): typeof FxDatepicker {\n return defineElement('fx-datepicker', FxDatepicker);\n}\n\n"],"names":["display"],"mappings":";;;;AA0BO,MAAM,gBAAN,MAAM,sBAAqB,UAAU;AAAA,EAArC,cAAA;AAAA,UAAA,GAAA,SAAA;AAiKL,SAAQ,OAAO,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,EAAA;AAAA,EAAE;AAAA,EANlC,WAAoB,qBAA+B;AACjD,WAAO,CAAC,SAAS,SAAS,OAAO,UAAU,QAAQ,OAAO,OAAO,eAAe,YAAY,UAAU,aAAa,aAAa,SAAS,WAAW,WAAW,OAAO;AAAA,EACxK;AAAA,EAMA,IAAI,OAAe;AACjB,UAAM,IAAI,KAAK,QAAQ,QAAQ,QAAQ;AACvC,WAAO,CAAC,UAAU,SAAS,UAAU,EAAE,SAAS,CAAC,IAAI,IAAI;AAAA,EAC3D;AAAA,EACA,IAAI,KAAK,OAAe;AACtB,SAAK,aAAa,QAAQ,KAAK;AAAA,EACjC;AAAA,EAEA,IAAI,QAAgB;AAClB,WAAO,KAAK,QAAQ,OAAO;AAAA,EAC7B;AAAA,EACA,IAAI,MAAM,GAAW;AACnB,SAAK,aAAa,SAAS,CAAC;AAAA,EAC9B;AAAA;AAAA,EAGA,IAAI,SAAmB;AACrB,UAAM,MAAM,KAAK,QAAQ,QAAQ;AACjC,WAAO,MAAM,IAAI,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,OAAO,IAAI,CAAA;AAAA,EACrE;AAAA,EACA,IAAI,OAAO,MAAgB;AACzB,SAAK,aAAa,UAAU,KAAK,KAAK,GAAG,CAAC;AAAA,EAC5C;AAAA,EAEA,IAAI,WAAoB;AACtB,WAAO,KAAK,QAAQ,UAAU;AAAA,EAChC;AAAA,EACA,IAAI,SAAS,OAAgB;AAC3B,SAAK,WAAW,YAAY,QAAQ,KAAK,CAAC;AAAA,EAC5C;AAAA,EAEmB,oBAA0B;AAC3C,UAAM,kBAAA;AAEN,SAAK,cAAc,CAAC,MAAa;AAC/B,UAAI,CAAC,KAAK,QAAQ,MAAM,EAAG;AAC3B,UAAI,EAAE,aAAA,EAAe,SAAS,IAAI,EAAG;AACrC,WAAK,gBAAgB,MAAM;AAAA,IAC7B;AACA,aAAS,iBAAiB,SAAS,KAAK,WAAW;AAAA,EACrD;AAAA,EAEmB,uBAA6B;AAC9C,UAAM,qBAAA;AACN,QAAI,KAAK,YAAa,UAAS,oBAAoB,SAAS,KAAK,WAAW;AAAA,EAC9E;AAAA;AAAA,EAGQ,WAAW,KAAqB;AACtC,QAAI,CAAC,IAAK,QAAO;AACjB,UAAM,MAAM,KAAK,QAAQ,QAAQ;AAGjC,QAAI,CAAC,IAAK,QAAO,IAAI,MAAM,GAAG,EAAE,CAAC;AACjC,UAAM,CAAC,UAAU,QAAQ,IAAI,IAAI,MAAM,GAAG;AAC1C,UAAM,CAAC,IAAI,IAAI,IAAI,IAAI,IAAI,EAAE,IAAI,SAAS,MAAM,GAAG;AACnD,UAAM,CAAC,IAAI,MAAM,IAAI,MAAM,IAAI,IAAI,KAAK,YAAY,IAAI,MAAM,GAAG;AAEjE,WAAO,IACJ,QAAQ,UAAU,GAAQ,EAC1B,QAAQ,OAAO,GAAQ,EACvB,QAAQ,OAAO,GAAQ,EACvB,QAAQ,QAAQ,GAAQ,EACxB,QAAQ,OAAO,GAAQ,EACvB,QAAQ,QAAQ,GAAQ,EACxB,QAAQ,WAAW,CAAC,EACpB,QAAQ,WAAW,CAAC,EACpB,QAAQ,WAAW,CAAC,EACpB,QAAQ,WAAW,CAAC,EACpB,QAAQ,WAAW,CAAC,EACpB,QAAQ,WAAW,CAAC;AAAA,EACzB;AAAA;AAAA,EAGQ,cAAsB;AAC5B,QAAI,KAAK,SAAS,SAAS;AACzB,YAAM,IAAI,KAAK,QAAQ,OAAO;AAC9B,YAAM,IAAI,KAAK,QAAQ,KAAK;AACd,WAAK,QAAQ,WAAW,IAAI,MAAM;AAChD,UAAI,CAAC,KAAK,CAAC,EAAG,QAAO;AACrB,UAAI,KAAK,QAAQ,WAAW,GAAG;AAC7B,cAAM,IAAI,CAAC,QAA0B,MAAM,GAAG,KAAK,WAAW,GAAG,CAAC,IAAI,KAAK,UAAU,KAAK,MAAM,GAAG,KAAK,WAAW,GAAG,CAAC,OAAO;AAC9H,eAAO,KAAK,IAAI,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;AAAA,MAC3C;AAEA,aAAO,KAAK,IAAI,GAAG,KAAK,WAAW,CAAC,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,KAAK,IAAI,GAAG,KAAK,WAAW,CAAC,CAAC,SAAS;AAAA,IACtG;AACA,QAAI,KAAK,SAAS,YAAY;AAC5B,aAAO,KAAK,OAAO,IAAI,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,EAAE,KAAK,IAAI;AAAA,IAC7D;AACA,UAAM,OAAO,KAAK,WAAW,KAAK,KAAK;AACvC,WAAO,QAAQ,KAAK,QAAQ,WAAW,IAAI,GAAG,IAAI,IAAI,KAAK,SAAA,CAAU,KAAK;AAAA,EAC5E;AAAA;AAAA,EAGQ,WAAmB;AACzB,UAAM,IAAI,CAAC,MAAsB,OAAO,CAAC,EAAE,SAAS,GAAG,GAAG;AAC1D,WAAO,GAAG,EAAE,KAAK,KAAK,CAAC,CAAC,IAAI,EAAE,KAAK,KAAK,CAAC,CAAC,IAAI,EAAE,KAAK,KAAK,CAAC,CAAC;AAAA,EAC9D;AAAA;AAAA,EAGQ,oBAA0B;AAChC,UAAM,MAAM,KAAK,SAAS,KAAK,QAAQ,OAAO,KAAK;AACnD,UAAM,IAAI,IAAI,MAAM,GAAG,EAAE,CAAC;AAC1B,QAAI,CAAC,EAAG;AACR,UAAM,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC;AACzD,SAAK,OAAO,EAAE,GAAG,KAAK,GAAG,GAAG,KAAK,GAAG,GAAG,KAAK,EAAA;AAAA,EAC9C;AAAA;AAAA,EAGQ,SAAS,KAAqB;AACpC,QAAI,CAAC,IAAK,QAAO;AACjB,WAAO,KAAK,QAAQ,WAAW,IAC3B,GAAG,GAAG,IAAI,KAAK,SAAA,CAAU,KACzB;AAAA,EACN;AAAA,EAEmB,SAAe;AAChC,UAAM,WAAW,KAAK;AACtB,UAAM,YAAY,KAAK,QAAQ,WAAW;AAC1C,UAAM,OAAO,KAAK,YAAA;AAClB,UAAM,WAAW,KAAK,SAAS;AAC/B,UAAM,WAAW,KAAK,QAAQ,WAAW;AACzC,SAAK,kBAAA;AAGL,QAAI,WAAW,QAAQ,IAAI,KAAK,QAAQ,KAAK,CAAC,CAAC,UAAU,IAAI,KAAK,QAAQ,KAAK,CAAC,CAAC;AACjF,QAAI,KAAK,SAAS,SAAS;AACzB,kBAAY,iBAAiB,IAAI,KAAK,QAAQ,OAAO,CAAC,CAAC,UAAU,IAAI,KAAK,QAAQ,KAAK,CAAC,CAAC;AAAA,IAC3F,WAAW,KAAK,SAAS,YAAY;AAEnC,YAAM,QAAQ,KAAK,OAAO,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,GAAG;AAC9D,kBAAY,4BAA4B,IAAI,KAAK,CAAC;AAAA,IACpD,OAAO;AACL,YAAM,IAAI,KAAK,MAAM,MAAM,GAAG,EAAE,CAAC,KAAK;AACtC,kBAAY,WAAW,IAAI,CAAC,CAAC;AAAA,IAC/B;AAEA,SAAK,YAAY;AAAA;AAAA,4DAEuC,KAAK,QAAQ,WAAW,KAAK,CAAC,WAAW,KAAK,UAAU;AAAA,yBAC3F,IAAI,KAAK,QAAQ,eAAe,YAAY,CAAC,CAAC;AAAA,mBACpD,IAAI,IAAI,CAAC,KAAK,WAAW,aAAa,EAAE;AAAA;AAAA,YAE/C,aAAa,WAAW,KAAK,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAY1B,QAAQ;AAAA,UAErB,WACI;AAAA,2EAC6D,KAAK,KAAK,CAAC;AAAA,2EACX,KAAK,KAAK,CAAC;AAAA,2EACX,KAAK,KAAK,CAAC;AAAA,wBAExE,EACN;AAAA;AAAA,KAEH;AAED,UAAM,QAAQ,KAAK,KAAK,cAAc,QAAQ;AAC9C,QAAI,CAAC,MAAO;AACZ,QAAI,SAAU;AAEd,UAAM,iBAAiB,SAAS,CAAC,MAAM;AACrC,UAAK,EAAE,OAAuB,QAAQ,QAAQ,EAAG;AAEjD,UAAI,KAAK,QAAQ,WAAW,KAAM,EAAE,OAAuB,UAAU,SAAS,SAAS,EAAG;AAC1F,WAAK,gBAAgB,MAAM;AAE3B,UAAI,KAAK,QAAQ,MAAM,GAAG;AACxB,cAAM,IAAI,KAAK,sBAAA;AACf,aAAK,gBAAgB,MAAM,EAAE,SAAS,MAAM,OAAO,eAAe,EAAE,MAAM,GAAG;AAAA,MAC/E,OAAO;AACL,aAAK,gBAAgB,IAAI;AAAA,MAC3B;AAAA,IACF,CAAC;AAGD,SAAK,KAAK,cAAc,QAAQ,GAAG,iBAAiB,SAAS,CAAC,MAAM;AAClE,QAAE,gBAAA;AACF,WAAK,gBAAgB,OAAO;AAC5B,WAAK,gBAAgB,OAAO;AAC5B,WAAK,gBAAgB,KAAK;AAC1B,WAAK,gBAAgB,QAAQ;AAC7B,WAAK,gBAAgB,MAAM;AAC3B,WAAK,OAAA;AACL,WAAK,WAAW,EAAE;AAAA,IACpB,CAAC;AAGD,UAAM,MAAM,KAAK,KAAK,cAA2B,kBAAkB;AACnE,SAAK,iBAAiB,UAAU,CAAC,MAAM;AACrC,YAAM,IAAK,EAAkB,UAAU,CAAA;AACvC,UAAI,EAAE,UAAU,UAAa,EAAE,QAAQ,QAAW;AAChD,YAAI,EAAE,MAAO,MAAK,aAAa,SAAS,KAAK,SAAS,EAAE,KAAK,CAAC;AAAA,YACzD,MAAK,gBAAgB,OAAO;AACjC,YAAI,EAAE,IAAK,MAAK,aAAa,OAAO,KAAK,SAAS,EAAE,GAAG,CAAC;AAAA,YACnD,MAAK,gBAAgB,KAAK;AAAA,MACjC;AACA,UAAI,EAAE,WAAW,QAAW;AAC1B,aAAK,SAAS,EAAE,OAAO,IAAI,CAAC,MAAc,KAAK,SAAS,CAAC,CAAC;AAAA,MAC5D;AACA,UAAI,EAAE,UAAU,QAAW;AACzB,YAAI,EAAE,MAAO,MAAK,aAAa,SAAS,KAAK,SAAS,EAAE,KAAK,CAAC;AAAA,YACzD,MAAK,gBAAgB,OAAO;AAEjC,YAAI,KAAK,SAAS,SAAU,MAAK,gBAAgB,MAAM;AAAA,MACzD;AACA,WAAK,OAAA;AACL,WAAK,WAAW,CAAC;AAAA,IACnB,CAAC;AAGD,UAAM,UAAU,KAAK,KAAK,cAAgC,UAAU;AACpE,QAAI,WAAW,KAAK,QAAQ,WAAW,KAAK,CAAC,UAAU;AACrD,cAAQ,iBAAiB,WAAW,CAAC,MAAM;AACzC,YAAI,EAAE,QAAQ,SAAS;AACrB,YAAE,eAAA;AACF,eAAK,YAAY,OAAO;AAAA,QAC1B;AAAA,MACF,CAAC;AACD,cAAQ,iBAAiB,QAAQ,MAAM,KAAK,YAAY,OAAO,CAAC;AAAA,IAClE;AAGA,QAAI,UAAU;AACZ,YAAM,YAAY,MAAY;AAC5B,cAAM,IAAI,KAAK,KAAK,cAAgC,MAAM;AAC1D,cAAM,IAAI,KAAK,KAAK,cAAgC,MAAM;AAC1D,cAAM,IAAI,KAAK,KAAK,cAAgC,MAAM;AAC1D,cAAM,QAAQ,CAAC,GAAuB,QACpC,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,SAAS,KAAK,KAAK,EAAE,KAAK,CAAC,CAAC;AACxD,aAAK,OAAO;AAAA,UACV,GAAG,MAAM,GAAG,OAAO,EAAE;AAAA,UACrB,GAAG,MAAM,GAAG,OAAO,EAAE;AAAA,UACrB,GAAG,MAAM,GAAG,OAAO,EAAE;AAAA,QAAA;AAGvB,cAAM,WAAW,CAAC,QAAwB,IAAI,MAAM,GAAG,EAAE,CAAC,IAAI,GAAG,IAAI,MAAM,GAAG,EAAE,CAAC,CAAC,IAAI,KAAK,UAAU,KAAK;AAC1G,YAAI,KAAK,MAAO,MAAK,aAAa,SAAS,SAAS,KAAK,KAAK,CAAC;AAC/D,YAAI,KAAK,QAAQ,OAAO,EAAG,MAAK,aAAa,SAAS,SAAS,KAAK,QAAQ,OAAO,CAAC,CAAC;AACrF,YAAI,KAAK,QAAQ,KAAK,EAAG,MAAK,aAAa,OAAO,SAAS,KAAK,QAAQ,KAAK,CAAC,CAAC;AAC/E,YAAI,KAAK,OAAO,QAAQ;AACtB,eAAK,SAAS,KAAK,OAAO,IAAI,CAAC,MAAM,SAAS,CAAC,CAAC;AAAA,QAClD;AAEA,cAAMA,WAAU,KAAK,KAAK,cAAgC,UAAU;AACpE,YAAIA,SAASA,UAAQ,QAAQ,KAAK,YAAA;AAClC,aAAK,WAAW,EAAE,OAAO,KAAK,OAAO;AAAA,MACvC;AACA,OAAC,QAAQ,QAAQ,MAAM,EAAE,QAAQ,CAAC,QAAQ;AACxC,aAAK,KAAK,cAAc,GAAG,GAAG,iBAAiB,SAAS,SAAS;AAAA,MACnE,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEQ,WAAW,QAAuC;AACxD,SAAK;AAAA,MACH,IAAI,YAAY,UAAU,EAAE,SAAS,MAAM,UAAU,MAAM,OAAA,CAAQ;AAAA,IAAA;AAAA,EAEvE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,WAAW,KAA4B;AAC7C,UAAM,OAAO,IAAI,KAAA;AACjB,QAAI,CAAC,KAAM,QAAO;AAClB,UAAM,MAAM,KAAK,QAAQ,UAAU,YAAY,EAAE,YAAA;AAGjD,UAAM,QAAQ,CAAC,MAAM,MAAM,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,SAAS,CAAC,CAAC;AAChE,QAAI,MAAM,SAAS,EAAG,QAAO;AAE7B,UAAM,QAAQ,KAAK,MAAM,IAAI,OAAO,SAAS,CAAC,EAAE,OAAO,OAAO;AAC9D,QAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,UAAM,MAA8B,CAAA;AACpC,UAAM,QAAQ,CAAC,GAAG,MAAO,IAAI,CAAC,IAAI,MAAM,CAAC,CAAG;AAC5C,UAAM,MAAM,GAAG,IAAI,MAAM,CAAC,KAAK,IAAI,IAAI,KAAK,IAAI,SAAS,GAAG,GAAG,CAAC,KAAK,IAAI,IAAI,KAAK,IAAI,SAAS,GAAG,GAAG,CAAC;AAGtG,UAAM,IAAI,oBAAI,KAAK,GAAG,GAAG,WAAW;AACpC,QAAI,OAAO,MAAM,EAAE,QAAA,CAAS,EAAG,QAAO;AACtC,UAAM,CAAC,GAAG,GAAG,GAAG,IAAI,IAAI,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC;AAC7D,QAAI,EAAE,YAAA,MAAkB,KAAK,EAAE,SAAA,IAAa,MAAM,KAAK,EAAE,QAAA,MAAc,IAAK,QAAO;AAGnF,UAAM,MAAM,CAAC,GAAW,MAAsB,EAAE,MAAM,GAAG,EAAE,CAAC,EAAG,cAAc,EAAE,MAAM,GAAG,EAAE,CAAC,CAAE;AAC7F,UAAM,MAAM,KAAK,QAAQ,KAAK;AAC9B,UAAM,MAAM,KAAK,QAAQ,KAAK;AAC9B,QAAI,OAAO,IAAI,KAAK,GAAG,IAAI,EAAG,QAAO;AACrC,QAAI,OAAO,IAAI,KAAK,GAAG,IAAI,EAAG,QAAO;AAErC,WAAO;AAAA,EACT;AAAA;AAAA,EAGQ,YAAY,OAA+B;AACjD,UAAM,SAAS,KAAK,WAAW,MAAM,KAAK;AAC1C,QAAI,CAAC,QAAQ;AAEX,YAAM,UAAU,IAAI,SAAS;AAC7B,WAAK;AAAA,QACH,IAAI,YAAY,WAAW,EAAE,SAAS,MAAM,UAAU,MAAM,QAAQ,EAAE,MAAM,MAAM,MAAA,GAAS;AAAA,MAAA;AAE7F,YAAM,QAAQ,KAAK,YAAA;AACnB,iBAAW,MAAM,MAAM,UAAU,OAAO,SAAS,GAAG,IAAI;AACxD;AAAA,IACF;AACA,QAAI,KAAK,SAAS,UAAU;AAC1B,WAAK,aAAa,SAAS,KAAK,SAAS,MAAM,CAAC;AAChD,WAAK,WAAW,EAAE,OAAO,KAAK,OAAO;AAAA,IACvC,WAAW,KAAK,SAAS,SAAS;AAEhC,UAAI,CAAC,KAAK,QAAQ,OAAO,KAAM,KAAK,QAAQ,OAAO,KAAK,KAAK,QAAQ,KAAK,GAAI;AAC5E,aAAK,aAAa,SAAS,KAAK,SAAS,MAAM,CAAC;AAChD,aAAK,gBAAgB,KAAK;AAAA,MAC5B,OAAO;AACL,cAAM,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,QAAQ,OAAO,EAAG,MAAM,GAAG,EAAE,CAAC,GAAI,MAAM,EAAE,KAAA;AAC/D,aAAK,aAAa,SAAS,KAAK,SAAS,CAAC,CAAC;AAC3C,aAAK,aAAa,OAAO,KAAK,SAAS,CAAC,CAAC;AAAA,MAC3C;AACA,WAAK,WAAW,EAAE,OAAO,KAAK,QAAQ,OAAO,GAAG,KAAK,KAAK,QAAQ,KAAK,EAAA,CAAG;AAAA,IAC5E,OAAO;AACL,YAAM,OAAO,KAAK,OAAO,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,EAAE,CAAC,MAAM,MAAM;AACjE,WAAK,KAAK,KAAK,SAAS,MAAM,CAAC;AAC/B,WAAK,SAAS;AACd,WAAK,WAAW,EAAE,QAAQ,KAAK,QAAQ;AAAA,IACzC;AACA,SAAK,OAAA;AAAA,EACP;AACF;AA1fE,cAAgB,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AADpB,IAAM,eAAN;AA6fA,SAAS,qBAA0C;AACxD,SAAO,cAAc,iBAAiB,YAAY;AACpD;"}
|
|
1
|
+
{"version":3,"file":"datepicker.js","sources":["../../../src/components/datepicker/datepicker.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-datepicker> — Campo de data com calendário suspenso.\n *\n * Form-Associated Custom Element: dentro de um <form> entra no FormData\n * (via atributo `name`): single → `name`; range → `name-start`/`name-end`;\n * multiple → uma entrada `name` por data.\n *\n * Um input com ícone de calendário no final; ao clicar, abre o\n * <fx-calendar> em um popover. As datas selecionadas aparecem no input.\n *\n * Atributos:\n * - mode=\"single\" | \"range\" | \"multiple\" (padrão: single)\n * - value=\"YYYY-MM-DD\" → single\n * - start/end → range\n * - values=\"a,b,c\" → múltiplas datas (CSV)\n * - min/max, placeholder, disabled, clearable, size (sm|md|lg)\n * - format=\"dd/mm/yyyy\" → máscara de exibição (tokens: dd mm yyyy, HH MM SS)\n * - show-time → inclui seleção de hora:minuto:segundo no popover;\n * o valor passa a ser \"YYYY-MM-DDTHH:mm:ss\"\n *\n * Evento: `change` (composed):\n * - single: { value }\n * - range: { value, start, end }\n * - multiple: { values: string[] }\n *\n * Além do `change`, emite `input`/`valueChange` com o MESMO payload\n * (v-model do Vue e [(value)] do Angular).\n */\nexport class FxDatepicker 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 }\n .field {\n display: inline-flex;\n align-items: center;\n gap: var(--fx-space-xs);\n width: 100%;\n box-sizing: border-box;\n /* Base = tamanho padrão (md): 40px */\n min-height: var(--fx-size-md, 40px);\n min-width: 150px;\n padding: var(--fx-space-xs) var(--fx-space-md);\n /* Altura determinística: a caixa fica EXATAMENTE no token --fx-size-*. */\n line-height: var(--fx-font-line-height);\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 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 :host([size='sm']) .field { min-height: var(--fx-size-sm); min-width: 180px; }\n :host([size='md']) .field { min-height: var(--fx-size-md); }\n :host([size='lg']) .field { min-height: var(--fx-size-lg); min-width: 220px; font-size: calc(var(--fx-font-size) + 4px); }\n /* Range e multiple exibem mais de uma data: campo mais largo */\n :host([mode='range']) .field { min-width: 250px; }\n :host([mode='multiple']) .field { min-width: 220px; }\n :host([mode='range'][size='lg']) .field { min-width: 360px; }\n :host([mode='multiple'][size='lg']) .field { min-width: 320px; }\n :host([show-time]) .field { min-width: 250px; }\n /* Full width: o host estica até o pai (os min-width dos modos são\n descartados para permitir layouts estreitos sob controle do pai). */\n :host([full]) { display: block; width: 100%; }\n :host([full]) .field { min-width: 0; }\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]) .field,\n :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([error]) .field:focus-within,\n :host([invalid]) .field:focus-within,\n :host([error][open]) .field,\n :host([invalid][open]) .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,\n :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([success]) .field:focus-within,\n :host([valid]) .field:focus-within,\n :host([success][open]) .field,\n :host([valid][open]) .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 .field:hover { border-color: var(--fx-border-hover); }\n .field:focus-within {\n outline: none;\n border-color: var(--fx-color-primary);\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n .display {\n flex: 1;\n border: none;\n background: none;\n font: inherit;\n color: var(--fx-text-default);\n outline: none;\n cursor: pointer;\n text-align: left;\n padding: 0;\n min-width: 0;\n }\n .display::placeholder { color: var(--fx-text-muted); }\n .cal-icon { color: var(--fx-text-muted); display: inline-flex; pointer-events: none; }\n .clear {\n border: none;\n background: none;\n color: var(--fx-text-muted);\n cursor: pointer;\n font-size: calc(var(--fx-font-size) + 4px);\n line-height: 1;\n padding: 0 2px;\n border-radius: var(--fx-radius-full);\n }\n .clear:hover { color: var(--fx-color-danger); }\n .clear[hidden] { display: none; }\n .pop {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n z-index: var(--fx-z-dropdown, 1000);\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 overflow: hidden;\n }\n :host([up]) .pop {\n top: auto;\n bottom: calc(100% + 4px);\n }\n :host([open]) .pop { display: block; }\n .time-row {\n display: flex;\n align-items: center;\n gap: var(--fx-space-xs);\n padding: var(--fx-space-sm) var(--fx-space-md);\n border-top: 1px solid var(--fx-border-default);\n background: var(--fx-surface-background);\n border-radius: 0 0 var(--fx-radius-md) var(--fx-radius-md);\n font-size: calc(var(--fx-font-size) - 2px);\n color: var(--fx-text-default);\n }\n .time-row label { color: var(--fx-text-muted); }\n .time-row input {\n width: 38px;\n font: inherit;\n color: var(--fx-text-default);\n background: var(--fx-surface-background);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-sm);\n padding: 2px 2px;\n outline: none;\n text-align: center;\n -moz-appearance: textfield;\n appearance: textfield;\n }\n .time-row input::-webkit-outer-spin-button,\n .time-row input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }\n .time-row .sep { color: var(--fx-text-muted); font-weight: bold; padding: 0 1px; }\n .time-row input:focus {\n border-color: var(--fx-color-primary);\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n :host([disabled]) .field { opacity: 0.55; cursor: not-allowed; background: var(--fx-surface-surface-hover); }\n :host([disabled]) .display { cursor: not-allowed; }\n :host([free-text]) .display { cursor: text; }\n :host([free-text]) .display.invalid { border-bottom: 2px solid var(--fx-color-danger); }\n `;\n\n static override get observedAttributes(): string[] {\n return ['value', 'start', 'end', 'values', 'mode', 'min', 'max', 'placeholder', 'disabled', 'format', 'show-time', 'free-text', 'error', 'invalid', 'success', 'valid'];\n }\n\n protected override attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n super.attributeChangedCallback(name, oldValue, newValue);\n // Mantém o FormData do form sincronizado sem tocar no render.\n if (name === 'value' || name === 'start' || name === 'end' || name === 'values') {\n this.updateFormValue();\n }\n }\n\n /** No FormData do form, conforme o modo (single/range/multiple). */\n protected override getFormValue(): string | FormData | null {\n if (this.mode === 'range') {\n const start = this.getAttr('start');\n const end = this.getAttr('end');\n if (!this.name || (!start && !end)) return null;\n const fd = new FormData();\n if (start) fd.append(`${this.name}-start`, start);\n if (end) fd.append(`${this.name}-end`, end);\n return fd;\n }\n if (this.mode === 'multiple') {\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 return this.getAttr('value') || null;\n }\n\n private docListener?: (e: Event) => void;\n /** Hora/minuto/segundo do modo show-time. */\n private time = { h: 0, m: 0, s: 0 };\n /** Estado inicial (value/start/end/values) para form.reset(). */\n private _initialState?: { value: string; start: string; end: string; values: string };\n\n get mode(): string {\n const m = this.getAttr('mode', 'single');\n return ['single', 'range', 'multiple'].includes(m) ? m : 'single';\n }\n set mode(value: string) {\n this.setAttribute('mode', value);\n }\n\n get value(): string {\n return this.getAttr('value');\n }\n set value(v: string) {\n this.setAttribute('value', v);\n }\n\n /** Datas do modo múltiplo (CSV no atributo). */\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 protected override connectedCallback(): void {\n if (!this._initialState) {\n this._initialState = {\n value: this.getAttr('value'),\n start: this.getAttr('start'),\n end: this.getAttr('end'),\n values: this.getAttr('values'),\n };\n }\n super.connectedCallback();\n // Fecha o popover ao clicar fora.\n this.docListener = (e: Event) => {\n if (!this.hasAttr('open')) return;\n if (e.composedPath().includes(this)) return;\n this.removeAttribute('open');\n };\n document.addEventListener('click', this.docListener);\n }\n\n protected override disconnectedCallback(): void {\n super.disconnectedCallback();\n if (this.docListener) document.removeEventListener('click', this.docListener);\n }\n\n /** form.reset(): restaura value/start/end/values iniciais (re-render via atributos observados). */\n override formResetCallback(): void {\n const s = this._initialState ?? { value: '', start: '', end: '', values: '' };\n this.setAttribute('value', s.value);\n this.setAttribute('start', s.start);\n this.setAttribute('end', s.end);\n this.setAttribute('values', s.values);\n this.updateFormValue();\n }\n\n /** Formata uma data ISO (\"YYYY-MM-DD\" ou com \"THH:mm:ss\") segundo o atributo format. */\n private formatDate(iso: string): string {\n if (!iso) return '';\n const fmt = this.getAttr('format');\n // Sem máscara: exibe apenas a parte da data (a hora entra via timeText\n // quando show-time está ativo — nunca duplicada).\n if (!fmt) return iso.split('T')[0]!;\n const [datePart, timePart] = iso.split('T');\n const [y = '', m = '', d = ''] = datePart.split('-');\n const [H = '00', M = '00', S = '00'] = (timePart ?? '').split(':');\n // Placeholders para evitar colisão entre mm (mês) e MM (minuto).\n return fmt\n .replace(/yyyy/gi, '\\u0001')\n .replace(/MM/g, '\\u0005')\n .replace(/mm/g, '\\u0002')\n .replace(/dd/gi, '\\u0003')\n .replace(/HH/g, '\\u0004')\n .replace(/SS/gi, '\\u0006')\n .replace(/\\u0001/g, y)\n .replace(/\\u0002/g, m)\n .replace(/\\u0003/g, d)\n .replace(/\\u0004/g, H)\n .replace(/\\u0005/g, M)\n .replace(/\\u0006/g, S);\n }\n\n /** Texto exibido no input conforme o modo. */\n private displayText(): string {\n if (this.mode === 'range') {\n const s = this.getAttr('start');\n const e = this.getAttr('end');\n const arrow = this.hasAttr('show-time') ? ' ' : ' → ';\n if (!s && !e) return '';\n if (this.hasAttr('show-time')) {\n const f = (iso?: string): string => (iso ? `${this.formatDate(iso)} ${this.timeText()}` : iso ? `${this.formatDate(iso)} …` : '');\n return s && e ? `${f(s)} → ${f(e)}` : f(s);\n }\n void arrow;\n return s && e ? `${this.formatDate(s)} → ${this.formatDate(e)}` : s ? `${this.formatDate(s)} → …` : '';\n }\n if (this.mode === 'multiple') {\n return this.values.map((v) => this.formatDate(v)).join(', ');\n }\n const base = this.formatDate(this.value);\n return base && this.hasAttr('show-time') ? `${base} ${this.timeText()}` : base;\n }\n\n /** \"HH:MM:SS\" do estado interno de show-time. */\n private timeText(): string {\n const p = (n: number): string => String(n).padStart(2, '0');\n return `${p(this.time.h)}:${p(this.time.m)}:${p(this.time.s)}`;\n }\n\n /** Extrai hora de um valor ISO inicial, se houver. */\n private loadTimeFromValue(): void {\n const src = this.value || this.getAttr('start') || '';\n const t = src.split('T')[1];\n if (!t) return;\n const [h, m, s] = t.split(':').map((n) => parseInt(n, 10));\n this.time = { h: h || 0, m: m || 0, s: s || 0 };\n }\n\n /** Anexa o horário interno ao ISO puro vindo do calendário. */\n private withTime(iso: string): string {\n if (!iso) return iso;\n return this.hasAttr('show-time')\n ? `${iso}T${this.timeText()}`\n : iso;\n }\n\n protected override render(): void {\n const disabled = this.disabled;\n const clearable = this.hasAttr('clearable');\n const text = this.displayText();\n const hasValue = text.length > 0;\n const showTime = this.hasAttr('show-time');\n this.loadTimeFromValue();\n\n // Atributos repassados ao calendário interno, conforme o modo.\n let calAttrs = `min=\"${esc(this.getAttr('min'))}\" max=\"${esc(this.getAttr('max'))}\"`;\n if (this.mode === 'range') {\n calAttrs += ` range start=\"${esc(this.getAttr('start'))}\" end=\"${esc(this.getAttr('end'))}\"`;\n } else if (this.mode === 'multiple') {\n // Repassa apenas as partes de data (sem hora) para o calendário pintar.\n const dates = this.values.map((v) => v.split('T')[0]).join(',');\n calAttrs += ` mode=\"multiple\" values=\"${esc(dates)}\"`;\n } else {\n const v = this.value.split('T')[0] ?? '';\n calAttrs += ` value=\"${esc(v)}\"`;\n }\n\n this.setTemplate(`\n <div class=\"field\" part=\"field\">\n <input class=\"display\" part=\"display\" type=\"text\" ${this.hasAttr('free-text') && !disabled ? '' : 'readonly'}\n placeholder=\"${esc(this.getAttr('placeholder', 'dd/mm/aaaa'))}\"\n value=\"${esc(text)}\" ${disabled ? 'disabled' : ''}>\n <button type=\"button\" class=\"clear\" part=\"clear\" aria-label=\"Limpar\"\n ${clearable && hasValue ? '' : 'hidden'}>\n ×\n </button>\n <span class=\"cal-icon\" part=\"icon\" aria-hidden=\"true\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"18\" rx=\"2\"/>\n <path d=\"M16 2v4M8 2v4M3 10h18\"/>\n </svg>\n </span>\n </div>\n <div class=\"pop\" part=\"popover\">\n <fx-calendar ${calAttrs}></fx-calendar>\n ${\n showTime\n ? `<div class=\"time-row\" part=\"time\">\n <input class=\"t-h\" type=\"number\" min=\"0\" max=\"23\" value=\"${this.time.h}\" aria-label=\"Hora\"><span class=\"sep\">:</span>\n <input class=\"t-m\" type=\"number\" min=\"0\" max=\"59\" value=\"${this.time.m}\" aria-label=\"Minuto\"><span class=\"sep\">:</span>\n <input class=\"t-s\" type=\"number\" min=\"0\" max=\"59\" value=\"${this.time.s}\" aria-label=\"Segundo\">\n </div>`\n : ''\n }\n </div>\n `);\n\n const field = this.root.querySelector('.field');\n if (!field) return;\n if (disabled) return;\n\n field.addEventListener('click', (e) => {\n if ((e.target as HTMLElement).closest('.clear')) return;\n // Com free-text, clicar no próprio input serve para digitar — não abre.\n if (this.hasAttr('free-text') && (e.target as HTMLElement).classList.contains('display')) 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 + 340 > window.innerHeight && r.top > 340);\n } else {\n this.removeAttribute('up');\n }\n });\n\n // Limpar: zera tudo conforme o modo.\n this.root.querySelector('.clear')?.addEventListener('click', (e) => {\n e.stopPropagation();\n this.removeAttribute('value');\n this.removeAttribute('start');\n this.removeAttribute('end');\n this.removeAttribute('values');\n this.removeAttribute('open');\n this.render();\n this.emitChange({});\n });\n\n // Sincroniza seleção do calendário de volta para os atributos.\n const cal = this.root.querySelector<HTMLElement>('.pop fx-calendar');\n cal?.addEventListener('change', (e) => {\n const d = (e as CustomEvent).detail ?? {};\n if (d.start !== undefined || d.end !== undefined) {\n if (d.start) this.setAttribute('start', this.withTime(d.start));\n else this.removeAttribute('start');\n if (d.end) this.setAttribute('end', this.withTime(d.end));\n else this.removeAttribute('end');\n }\n if (d.values !== undefined) {\n this.values = d.values.map((v: string) => this.withTime(v));\n }\n if (d.value !== undefined) {\n if (d.value) this.setAttribute('value', this.withTime(d.value));\n else this.removeAttribute('value');\n // No modo single, fecha ao escolher.\n if (this.mode === 'single') this.removeAttribute('open');\n }\n this.render();\n this.emitChange(d);\n });\n\n // free-text: valida ao sair do campo ou pressionar Enter.\n const display = this.root.querySelector<HTMLInputElement>('.display');\n if (display && this.hasAttr('free-text') && !disabled) {\n display.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n this.commitTyped(display);\n }\n });\n display.addEventListener('blur', () => this.commitTyped(display));\n }\n\n // show-time: captura alterações de hora/minuto/segundo.\n if (showTime) {\n const applyTime = (): void => {\n const h = this.root.querySelector<HTMLInputElement>('.t-h');\n const m = this.root.querySelector<HTMLInputElement>('.t-m');\n const s = this.root.querySelector<HTMLInputElement>('.t-s');\n const clamp = (v: string | undefined, max: number): number =>\n Math.min(max, Math.max(0, parseInt(v ?? '0', 10) || 0));\n this.time = {\n h: clamp(h?.value, 23),\n m: clamp(m?.value, 59),\n s: clamp(s?.value, 59),\n };\n // Reanexa o horário aos valores já selecionados.\n const reattach = (iso: string): string => iso.split('T')[0] ? `${iso.split('T')[0]}T${this.timeText()}` : iso;\n if (this.value) this.setAttribute('value', reattach(this.value));\n if (this.getAttr('start')) this.setAttribute('start', reattach(this.getAttr('start')));\n if (this.getAttr('end')) this.setAttribute('end', reattach(this.getAttr('end')));\n if (this.values.length) {\n this.values = this.values.map((v) => reattach(v));\n }\n // Atualiza apenas o texto do input, sem re-render completo.\n const display = this.root.querySelector<HTMLInputElement>('.display');\n if (display) display.value = this.displayText();\n this.emitChange({ value: this.value });\n };\n ['.t-h', '.t-m', '.t-s'].forEach((sel) => {\n this.root.querySelector(sel)?.addEventListener('input', applyTime);\n });\n }\n }\n\n private emitChange(detail: Record<string, unknown>): void {\n // input (v-model do Vue) + valueChange ([(value)] do Angular) com o\n // mesmo payload do change (single: { value } · range: { start, end }).\n this.emitValueEvents(detail);\n this.dispatchEvent(\n new CustomEvent('change', { bubbles: true, composed: true, detail }),\n );\n }\n\n /**\n * Interpreta o texto digitado livremente e devolve ISO \"YYYY-MM-DD\"\n * (ou null se inválido). Aceita a máscara do atributo `format`\n * (tokens dd/mm/yyyy) ou ISO direto.\n */\n private parseTyped(raw: string): string | null {\n const text = raw.trim();\n if (!text) return null;\n const fmt = this.getAttr('format', 'dd/mm/yyyy').toLowerCase();\n\n // Ordem dos tokens conforme a máscara informada.\n const order = ['dd', 'mm', 'yyyy'].filter((t) => fmt.includes(t));\n if (order.length < 3) return null;\n\n const parts = text.split(new RegExp(`[^0-9]+`)).filter(Boolean);\n if (parts.length !== 3) return null;\n\n const map: Record<string, string> = {};\n order.forEach((t, i) => (map[t] = parts[i]!));\n const iso = `${map['yyyy']}-${(map['mm'] ?? '').padStart(2, '0')}-${(map['dd'] ?? '').padStart(2, '0')}`;\n\n // Validação de data real.\n const d = new Date(`${iso}T12:00:00`);\n if (Number.isNaN(d.getTime())) return null;\n const [y, m, day] = iso.split('-').map((n) => parseInt(n, 10));\n if (d.getFullYear() !== y || d.getMonth() + 1 !== m || d.getDate() !== day) return null;\n\n // Limites min/max (apenas a parte da data).\n const cmp = (a: string, b: string): number => a.split('T')[0]!.localeCompare(b.split('T')[0]!);\n const min = this.getAttr('min');\n const max = this.getAttr('max');\n if (min && cmp(iso, min) < 0) return null;\n if (max && cmp(iso, max) > 0) return null;\n\n return iso;\n }\n\n /** Aplica o texto digitado no modo free-text (blur/Enter). */\n private commitTyped(input: HTMLInputElement): void {\n const parsed = this.parseTyped(input.value);\n if (!parsed) {\n // Inválido: sinaliza, reverte o texto e emite evento.\n input.classList.add('invalid');\n this.dispatchEvent(\n new CustomEvent('invalid', { bubbles: true, composed: true, detail: { text: input.value } }),\n );\n input.value = this.displayText();\n setTimeout(() => input.classList.remove('invalid'), 1500);\n return;\n }\n if (this.mode === 'single') {\n this.setAttribute('value', this.withTime(parsed));\n this.emitChange({ value: this.value });\n } else if (this.mode === 'range') {\n // Primeira digitação vira start; com start preenchido, vira end.\n if (!this.getAttr('start') || (this.getAttr('start') && this.getAttr('end'))) {\n this.setAttribute('start', this.withTime(parsed));\n this.removeAttribute('end');\n } else {\n const [a, b] = [this.getAttr('start')!.split('T')[0]!, parsed].sort();\n this.setAttribute('start', this.withTime(a));\n this.setAttribute('end', this.withTime(b));\n }\n this.emitChange({ start: this.getAttr('start'), end: this.getAttr('end') });\n } else {\n const list = this.values.filter((v) => v.split('T')[0] !== parsed);\n list.push(this.withTime(parsed));\n this.values = list;\n this.emitChange({ values: this.values });\n }\n this.render();\n }\n}\n\nexport function defineFxDatepicker(): typeof FxDatepicker {\n return defineElement('fx-datepicker', FxDatepicker);\n}\n\n"],"names":["display"],"mappings":"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"}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FxFormElement } from '../../core/form-element';
|
|
2
2
|
/**
|
|
3
3
|
* <fx-fileupload> — Seletor de arquivos com upload e progresso.
|
|
4
4
|
*
|
|
5
|
+
* Form-Associated Custom Element: dentro de um <form> entra no FormData
|
|
6
|
+
* (via atributo `name`) com os File's selecionados — o upload é enviado
|
|
7
|
+
* junto com o submit do form, como um <input type="file"> nativo.
|
|
8
|
+
*
|
|
5
9
|
* Atributos:
|
|
6
10
|
* - mode (basic|advanced — `basic`: botão; `advanced`: dropzone com drag & drop);
|
|
7
11
|
* - size (sm|md|lg, padrão md);
|
|
@@ -15,7 +19,7 @@ import { FxElement } from '../../core/base';
|
|
|
15
19
|
*
|
|
16
20
|
* Eventos (composed): `select` (detail: { files: File[] }), `remove`, `complete`.
|
|
17
21
|
*/
|
|
18
|
-
export declare class FxFileUpload extends
|
|
22
|
+
export declare class FxFileUpload extends FxFormElement {
|
|
19
23
|
static styles: string;
|
|
20
24
|
static get observedAttributes(): string[];
|
|
21
25
|
get size(): string;
|
|
@@ -28,6 +32,8 @@ export declare class FxFileUpload extends FxElement {
|
|
|
28
32
|
/** Arquivos selecionados (não serializável em atributo). */
|
|
29
33
|
get files(): File[];
|
|
30
34
|
set files(files: File[]);
|
|
35
|
+
/** No FormData do form: os arquivos selecionados, como o <input type="file">. */
|
|
36
|
+
protected getFormValue(): string | FormData | null;
|
|
31
37
|
protected render(): void;
|
|
32
38
|
private _files;
|
|
33
39
|
private _done;
|
|
@@ -35,6 +41,8 @@ export declare class FxFileUpload extends FxElement {
|
|
|
35
41
|
private _bindPicker;
|
|
36
42
|
private _acceptFiles;
|
|
37
43
|
private _fmtSize;
|
|
44
|
+
/** form.reset(): limpa os arquivos selecionados. */
|
|
45
|
+
formResetCallback(): void;
|
|
38
46
|
/** Renderiza a lista de arquivos sem re-renderizar o template (mantém estado). */
|
|
39
47
|
private _syncFilesArea;
|
|
40
48
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileupload.d.ts","sourceRoot":"","sources":["../../../src/components/fileupload/fileupload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"fileupload.d.ts","sourceRoot":"","sources":["../../../src/components/fileupload/fileupload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAMxD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,YAAa,SAAQ,aAAa;IAC7C,OAAgB,MAAM,SA4IpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAAuC;IAE7D,4DAA4D;IAC5D,IAAI,KAAK,IAAI,MAAM,CAAkC;IACrD,IAAI,KAAK,CAAC,CAAC,EAAE,MAAM,EAAoC;IAEvD,IAAI,QAAQ,IAAI,MAAM,CAAmF;IACzG,IAAI,QAAQ,CAAC,CAAC,EAAE,MAAM,EAA+C;IAErE,4DAA4D;IAC5D,IAAI,KAAK,IAAI,IAAI,EAAE,CAAwB;IAC3C,IAAI,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,EAItB;IAED,iFAAiF;cAC9D,YAAY,IAAI,MAAM,GAAG,QAAQ,GAAG,IAAI;cAQxC,MAAM,IAAI,IAAI;IA8CjC,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,KAAK,CAAS;IAEtB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,WAAW;IAyBnB,OAAO,CAAC,YAAY;IAUpB,OAAO,CAAC,QAAQ;IAMhB,oDAAoD;IAC3C,iBAAiB,IAAI,IAAI;IAMlC,kFAAkF;IAClF,OAAO,CAAC,cAAc;CAyBvB;AAED,wBAAgB,kBAAkB,IAAI,OAAO,YAAY,CAExD"}
|
|
@@ -1,9 +1,9 @@
|
|
|
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
5
|
import { FENIX_ICON_BASE_CSS, fenixIconHtml } from "../../icons/base-css.js";
|
|
6
|
-
const _FxFileUpload = class _FxFileUpload extends
|
|
6
|
+
const _FxFileUpload = class _FxFileUpload extends FxFormElement {
|
|
7
7
|
constructor() {
|
|
8
8
|
super(...arguments);
|
|
9
9
|
this._files = [];
|
|
@@ -41,6 +41,14 @@ const _FxFileUpload = class _FxFileUpload extends FxElement {
|
|
|
41
41
|
this.value = this._files.map((f) => f.name).join(", ");
|
|
42
42
|
this._syncFilesArea();
|
|
43
43
|
}
|
|
44
|
+
/** No FormData do form: os arquivos selecionados, como o <input type="file">. */
|
|
45
|
+
getFormValue() {
|
|
46
|
+
const files = this._files;
|
|
47
|
+
if (!this.name || files.length === 0) return null;
|
|
48
|
+
const fd = new FormData();
|
|
49
|
+
for (const f of files) fd.append(this.name, f, f.name);
|
|
50
|
+
return fd;
|
|
51
|
+
}
|
|
44
52
|
render() {
|
|
45
53
|
const mode = this.getAttr("mode", "basic");
|
|
46
54
|
const advanced = mode === "advanced";
|
|
@@ -73,6 +81,7 @@ const _FxFileUpload = class _FxFileUpload extends FxElement {
|
|
|
73
81
|
`);
|
|
74
82
|
this._bindPicker();
|
|
75
83
|
this._syncFilesArea();
|
|
84
|
+
this.updateFormValue();
|
|
76
85
|
if (!this._done && showProgress && pct >= 100 && this._files.length) {
|
|
77
86
|
this._done = true;
|
|
78
87
|
this.dispatchEvent(new CustomEvent("complete", { bubbles: true, composed: true }));
|
|
@@ -121,6 +130,12 @@ const _FxFileUpload = class _FxFileUpload extends FxElement {
|
|
|
121
130
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
122
131
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
123
132
|
}
|
|
133
|
+
/** form.reset(): limpa os arquivos selecionados. */
|
|
134
|
+
formResetCallback() {
|
|
135
|
+
this._files = [];
|
|
136
|
+
this.value = "";
|
|
137
|
+
this.updateFormValue();
|
|
138
|
+
}
|
|
124
139
|
/** Renderiza a lista de arquivos sem re-renderizar o template (mantém estado). */
|
|
125
140
|
_syncFilesArea() {
|
|
126
141
|
const area = this.root?.querySelector?.(".files");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileupload.js","sources":["../../../src/components/fileupload/fileupload.ts"],"sourcesContent":["import { FxElement } from '../../core/base';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\nimport { esc } from '../../core/sanitize';\nimport { FENIX_ICON_BASE_CSS, fenixIconHtml } from '../../icons/base-css';\n\n/**\n * <fx-fileupload> — Seletor de arquivos com upload e progresso.\n *\n * Atributos:\n * - mode (basic|advanced — `basic`: botão; `advanced`: dropzone com drag & drop);\n * - size (sm|md|lg, padrão md);\n * - severity (primary|secondary|success|warning|danger|info — cor do botão no modo basic);\n * - label (texto do botão/área, padrão 'Escolher arquivo');\n * - icon (glifo/emoji exibido antes do texto — sem o atributo, sem ícone);\n * - accept, multiple, disabled (como no <input type=\"file\">);\n * - show-progress (exibe barra + % de upload);\n * - progress (0-100 — o consumidor atualiza durante o upload real);\n * - value (nome(s) do(s) arquivo(s) carregado(s), somente leitura/espelho).\n *\n * Eventos (composed): `select` (detail: { files: File[] }), `remove`, `complete`.\n */\nexport class FxFileUpload extends FxElement {\n static override styles = css`\n :host {\n display: inline-block;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n }\n :host([disabled]) { opacity: 0.55; pointer-events: none; }\n input[type='file'] { display: none; }\n\n /* ---- botão (basic) ---- */\n .btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--fx-space-sm);\n /* Largura acompanha o host: CSS externo/classe/inline no elemento esticam\n o botão sem precisar do full (host é shrink-to-fit por padrão). */\n width: 100%;\n min-height: var(--fx-size-md);\n /* Padding vertical no orçamento do token --fx-size-* ⇒ altura padrão. */\n padding: var(--fx-space-sm) var(--fx-space-lg);\n font-family: inherit;\n font-size: inherit;\n font-weight: var(--fx-font-weight);\n line-height: var(--fx-font-line-height);\n color: var(--fx-text-default);\n background-color: var(--fx-surface-background);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-md);\n cursor: pointer;\n box-sizing: border-box;\n transition:\n border-color var(--fx-motion-duration-normal) var(--fx-motion-easing),\n box-shadow var(--fx-motion-duration-normal) var(--fx-motion-easing),\n background var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n .btn:hover { border-color: var(--fx-border-hover); background: var(--fx-surface-surface-hover, rgba(0,0,0,0.03)); }\n .btn:focus-visible {\n outline: none;\n border-color: var(--fx-color-primary);\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n /* Severities (como no <fx-button>) — aplicam-se ao botão do modo basic */\n :host([severity='primary']) .btn { background: var(--fx-color-primary); border-color: transparent; color: #fff; }\n :host([severity='secondary']) .btn { background: var(--fx-color-secondary); border-color: transparent; color: #fff; }\n :host([severity='success']) .btn { background: var(--fx-color-success); border-color: transparent; color: #fff; }\n :host([severity='warning']) .btn { background: var(--fx-color-warning); border-color: transparent; color: #fff; }\n :host([severity='danger']) .btn { background: var(--fx-color-danger); border-color: transparent; color: #fff; }\n :host([severity='info']) .btn { background: var(--fx-color-info); border-color: transparent; color: #fff; }\n :host([severity]) .btn:hover { filter: brightness(0.88); }\n :host([severity]) .btn:focus-visible { border-color: transparent; }\n :host([size='sm']) .btn { min-height: var(--fx-size-sm); padding: var(--fx-space-xs) var(--fx-space-md); font-size: calc(var(--fx-font-size) - 2px); }\n :host([size='lg']) .btn { min-height: var(--fx-size-lg); padding: var(--fx-space-sm) var(--fx-space-xl); font-size: calc(var(--fx-font-size) + 4px); }\n :host([size='sm']) .drop { min-height: 72px; }\n :host([size='lg']) .drop { min-height: 132px; }\n\n /* ---- dropzone (advanced) ---- */\n .drop {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--fx-space-sm);\n /* Acompanha a largura do host (definida por CSS externo/classe/inline). */\n width: 100%;\n min-height: 100px;\n padding: var(--fx-space-lg);\n border: 2px dashed var(--fx-border-default);\n border-radius: var(--fx-radius-md);\n color: var(--fx-text-muted);\n cursor: pointer;\n text-align: center;\n transition: border-color var(--fx-motion-duration-normal) var(--fx-motion-easing), background var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n .drop:hover, .drop.over {\n border-color: var(--fx-color-primary);\n background: color-mix(in srgb, var(--fx-color-primary) 6%, transparent);\n }\n .drop .icon { font-size: calc(var(--fx-font-size) + 12px); line-height: 1; }\n .drop .hint { font-size: calc(var(--fx-font-size) - 2px); }\n\n /* ---- ícone comum ---- */\n .icon { line-height: 1; }\n\n /* ---- arquivo(s) selecionado(s) ---- */\n .files { display: flex; flex-direction: column; gap: var(--fx-space-xs); margin-top: var(--fx-space-sm); }\n .file {\n display: flex;\n align-items: center;\n gap: var(--fx-space-sm);\n padding: var(--fx-space-xs) var(--fx-space-sm);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-sm);\n font-size: calc(var(--fx-font-size) - 2px);\n color: var(--fx-text-default);\n }\n .file .name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .file .size { color: var(--fx-text-muted); white-space: nowrap; }\n .file .remove {\n border: none;\n background: transparent;\n color: var(--fx-text-muted);\n cursor: pointer;\n font-size: calc(var(--fx-font-size) + 2px);\n line-height: 1;\n padding: 0 var(--fx-space-xs);\n border-radius: var(--fx-radius-full);\n }\n .file .remove:hover { color: var(--fx-color-danger); }\n\n /* ---- progresso ---- */\n .progress-row { display: flex; align-items: center; gap: var(--fx-space-sm); margin-top: var(--fx-space-sm); }\n .track {\n flex: 1;\n height: 6px;\n background: var(--fx-surface-surface-hover);\n border-radius: var(--fx-radius-full);\n overflow: hidden;\n }\n .bar {\n height: 100%;\n width: 0%;\n background: var(--fx-color-primary);\n border-radius: var(--fx-radius-full);\n transition: width var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n :host([progress='100']) .bar { background: var(--fx-color-success); }\n .pct {\n font-size: calc(var(--fx-font-size) - 2px);\n font-weight: 600;\n color: var(--fx-text-default);\n min-width: 34px;\n text-align: right;\n }\n /* Full width: o host estica até o pai e o botão/dropzone acompanha. */\n :host([full]) { display: block; width: 100%; }\n :host([full]) .btn { width: 100%; }\n :host([full]) .drop { width: 100%; }\n /* Base dos glifos Fenix Icons (o @font-face vem de @wrrdev/fenix-ui/icons). */\n ${FENIX_ICON_BASE_CSS}\n `;\n\n static override get observedAttributes(): string[] {\n return ['mode', 'size', 'severity', 'label', 'icon', 'accept', 'multiple', 'disabled', 'show-progress', 'progress', 'value', 'full'];\n }\n\n get size(): string {\n const s = this.getAttr('size', 'md');\n return s === 'sm' || s === 'lg' ? s : 'md';\n }\n set size(value: string) { this.setAttribute('size', value); }\n\n /** Nomes dos arquivos selecionados (espelho em `value`). */\n get value(): string { return this.getAttr('value'); }\n set value(v: string) { this.setAttribute('value', v); }\n\n get progress(): number { return Math.min(100, Math.max(0, Number(this.getAttr('progress', '0')) || 0)); }\n set progress(v: number) { this.setAttribute('progress', String(v)); }\n\n /** Arquivos selecionados (não serializável em atributo). */\n get files(): File[] { return this._files; }\n set files(files: File[]) {\n this._files = Array.from(files);\n this.value = this._files.map((f) => f.name).join(', ');\n this._syncFilesArea();\n }\n\n protected override render(): void {\n const mode = this.getAttr('mode', 'basic');\n const advanced = mode === 'advanced';\n const label = this.getAttr('label', 'Escolher arquivo');\n const icon = this.getAttr('icon');\n const accept = this.getAttr('accept');\n const multiple = this.hasAttr('multiple');\n const disabled = this.hasAttr('disabled');\n const showProgress = this.hasAttr('show-progress');\n const pct = this.progress;\n\n const fileInput = `<input type=\"file\" class=\"picker\" ${accept ? `accept=\"${esc(accept)}\"` : ''} ${multiple ? 'multiple' : ''} ${disabled ? 'disabled' : ''} />`;\n\n this.setTemplate(`\n ${advanced\n ? `<div class=\"drop\" part=\"dropzone\" role=\"button\" tabindex=\"0\" aria-label=\"${esc(label)}\">\n ${icon ? `<span class=\"icon\" part=\"icon\" aria-hidden=\"true\">${fenixIconHtml(icon)}</span>` : ''}\n <span>${esc(label)}</span>\n <span class=\"hint\">ou arraste e solte aqui</span>\n </div>`\n : `<button type=\"button\" class=\"btn\" part=\"button\">\n ${icon ? `<span class=\"icon\" part=\"icon\" aria-hidden=\"true\">${fenixIconHtml(icon)}</span>` : ''}\n <span>${esc(label)}</span>\n </button>`}\n ${fileInput}\n <div class=\"files\" part=\"files\"></div>\n ${showProgress ? `\n <div class=\"progress-row\" part=\"progress\">\n <div class=\"track\" role=\"progressbar\" aria-valuenow=\"${pct}\" aria-valuemin=\"0\" aria-valuemax=\"100\">\n <div class=\"bar\" style=\"width: ${pct}%\"></div>\n </div>\n <span class=\"pct\">${pct}%</span>\n </div>` : ''}\n `);\n\n this._bindPicker();\n this._syncFilesArea();\n\n if (!this._done && showProgress && pct >= 100 && this._files.length) {\n this._done = true;\n this.dispatchEvent(new CustomEvent('complete', { bubbles: true, composed: true }));\n }\n if (pct < 100) this._done = false;\n }\n\n private _files: File[] = [];\n private _done = false;\n\n private _picker(): HTMLInputElement | null {\n return this.root.querySelector<HTMLInputElement>('.picker');\n }\n\n private _bindPicker(): void {\n const picker = this._picker();\n if (!picker) return;\n const trigger = this.root.querySelector('.btn') ?? this.root.querySelector('.drop');\n trigger?.addEventListener('click', () => picker.click());\n trigger?.addEventListener('keydown', (e) => {\n if ((e as KeyboardEvent).key === 'Enter' || (e as KeyboardEvent).key === ' ') {\n e.preventDefault();\n picker.click();\n }\n });\n picker.addEventListener('change', () => this._acceptFiles(picker.files ?? []));\n\n // drag & drop (advanced)\n const drop = this.root.querySelector<HTMLElement>('.drop');\n if (!drop) return;\n drop.addEventListener('dragover', (e) => { e.preventDefault(); drop.classList.add('over'); });\n drop.addEventListener('dragleave', () => drop.classList.remove('over'));\n drop.addEventListener('drop', (e) => {\n e.preventDefault();\n drop.classList.remove('over');\n if (e.dataTransfer?.files.length) this._acceptFiles(e.dataTransfer.files);\n });\n }\n\n private _acceptFiles(list: FileList | File[]): void {\n const picked = this.hasAttr('multiple') ? Array.from(list) : [list[0]].filter(Boolean);\n this.files = picked;\n const picker = this._picker();\n if (picker) picker.value = '';\n this.dispatchEvent(\n new CustomEvent('select', { bubbles: true, composed: true, detail: { files: picked } }),\n );\n }\n\n private _fmtSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n }\n\n /** Renderiza a lista de arquivos sem re-renderizar o template (mantém estado). */\n private _syncFilesArea(): void {\n const area = this.root?.querySelector?.('.files') as HTMLElement | null;\n if (!area) return;\n area.innerHTML = this._files\n .map(\n (f, i) => `\n <div class=\"file\" part=\"file\">\n <span class=\"name\">${esc(f.name)}</span>\n <span class=\"size\">${this._fmtSize(f.size)}</span>\n <button type=\"button\" class=\"remove\" data-i=\"${i}\" part=\"remove\" aria-label=\"Remover ${esc(f.name)}\">×</button>\n </div>`,\n )\n .join('');\n area.querySelectorAll<HTMLButtonElement>('.remove').forEach((btn) =>\n btn.addEventListener('click', () => {\n const removed = this._files.splice(Number(btn.dataset.i), 1)[0];\n this.value = this._files.map((f) => f.name).join(', ');\n this.progress = 0;\n this._syncFilesArea();\n this.dispatchEvent(\n new CustomEvent('remove', { bubbles: true, composed: true, detail: { file: removed } }),\n );\n }),\n );\n }\n}\n\nexport function defineFxFileUpload(): typeof FxFileUpload {\n return defineElement('fx-fileupload', FxFileUpload);\n}\n\n\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fileupload.js","sources":["../../../src/components/fileupload/fileupload.ts"],"sourcesContent":["import { FxFormElement } from '../../core/form-element';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\nimport { esc } from '../../core/sanitize';\nimport { FENIX_ICON_BASE_CSS, fenixIconHtml } from '../../icons/base-css';\n\n/**\n * <fx-fileupload> — Seletor de arquivos com upload e progresso.\n *\n * Form-Associated Custom Element: dentro de um <form> entra no FormData\n * (via atributo `name`) com os File's selecionados — o upload é enviado\n * junto com o submit do form, como um <input type=\"file\"> nativo.\n *\n * Atributos:\n * - mode (basic|advanced — `basic`: botão; `advanced`: dropzone com drag & drop);\n * - size (sm|md|lg, padrão md);\n * - severity (primary|secondary|success|warning|danger|info — cor do botão no modo basic);\n * - label (texto do botão/área, padrão 'Escolher arquivo');\n * - icon (glifo/emoji exibido antes do texto — sem o atributo, sem ícone);\n * - accept, multiple, disabled (como no <input type=\"file\">);\n * - show-progress (exibe barra + % de upload);\n * - progress (0-100 — o consumidor atualiza durante o upload real);\n * - value (nome(s) do(s) arquivo(s) carregado(s), somente leitura/espelho).\n *\n * Eventos (composed): `select` (detail: { files: File[] }), `remove`, `complete`.\n */\nexport class FxFileUpload extends FxFormElement {\n static override styles = css`\n :host {\n display: inline-block;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n }\n :host([disabled]) { opacity: 0.55; pointer-events: none; }\n input[type='file'] { display: none; }\n\n /* ---- botão (basic) ---- */\n .btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--fx-space-sm);\n /* Largura acompanha o host: CSS externo/classe/inline no elemento esticam\n o botão sem precisar do full (host é shrink-to-fit por padrão). */\n width: 100%;\n min-height: var(--fx-size-md);\n /* Padding vertical no orçamento do token --fx-size-* ⇒ altura padrão. */\n padding: var(--fx-space-sm) var(--fx-space-lg);\n font-family: inherit;\n font-size: inherit;\n font-weight: var(--fx-font-weight);\n line-height: var(--fx-font-line-height);\n color: var(--fx-text-default);\n background-color: var(--fx-surface-background);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-md);\n cursor: pointer;\n box-sizing: border-box;\n transition:\n border-color var(--fx-motion-duration-normal) var(--fx-motion-easing),\n box-shadow var(--fx-motion-duration-normal) var(--fx-motion-easing),\n background var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n .btn:hover { border-color: var(--fx-border-hover); background: var(--fx-surface-surface-hover, rgba(0,0,0,0.03)); }\n .btn:focus-visible {\n outline: none;\n border-color: var(--fx-color-primary);\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n /* Severities (como no <fx-button>) — aplicam-se ao botão do modo basic */\n :host([severity='primary']) .btn { background: var(--fx-color-primary); border-color: transparent; color: #fff; }\n :host([severity='secondary']) .btn { background: var(--fx-color-secondary); border-color: transparent; color: #fff; }\n :host([severity='success']) .btn { background: var(--fx-color-success); border-color: transparent; color: #fff; }\n :host([severity='warning']) .btn { background: var(--fx-color-warning); border-color: transparent; color: #fff; }\n :host([severity='danger']) .btn { background: var(--fx-color-danger); border-color: transparent; color: #fff; }\n :host([severity='info']) .btn { background: var(--fx-color-info); border-color: transparent; color: #fff; }\n :host([severity]) .btn:hover { filter: brightness(0.88); }\n :host([severity]) .btn:focus-visible { border-color: transparent; }\n :host([size='sm']) .btn { min-height: var(--fx-size-sm); padding: var(--fx-space-xs) var(--fx-space-md); font-size: calc(var(--fx-font-size) - 2px); }\n :host([size='lg']) .btn { min-height: var(--fx-size-lg); padding: var(--fx-space-sm) var(--fx-space-xl); font-size: calc(var(--fx-font-size) + 4px); }\n :host([size='sm']) .drop { min-height: 72px; }\n :host([size='lg']) .drop { min-height: 132px; }\n\n /* ---- dropzone (advanced) ---- */\n .drop {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--fx-space-sm);\n /* Acompanha a largura do host (definida por CSS externo/classe/inline). */\n width: 100%;\n min-height: 100px;\n padding: var(--fx-space-lg);\n border: 2px dashed var(--fx-border-default);\n border-radius: var(--fx-radius-md);\n color: var(--fx-text-muted);\n cursor: pointer;\n text-align: center;\n transition: border-color var(--fx-motion-duration-normal) var(--fx-motion-easing), background var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n .drop:hover, .drop.over {\n border-color: var(--fx-color-primary);\n background: color-mix(in srgb, var(--fx-color-primary) 6%, transparent);\n }\n .drop .icon { font-size: calc(var(--fx-font-size) + 12px); line-height: 1; }\n .drop .hint { font-size: calc(var(--fx-font-size) - 2px); }\n\n /* ---- ícone comum ---- */\n .icon { line-height: 1; }\n\n /* ---- arquivo(s) selecionado(s) ---- */\n .files { display: flex; flex-direction: column; gap: var(--fx-space-xs); margin-top: var(--fx-space-sm); }\n .file {\n display: flex;\n align-items: center;\n gap: var(--fx-space-sm);\n padding: var(--fx-space-xs) var(--fx-space-sm);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-sm);\n font-size: calc(var(--fx-font-size) - 2px);\n color: var(--fx-text-default);\n }\n .file .name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .file .size { color: var(--fx-text-muted); white-space: nowrap; }\n .file .remove {\n border: none;\n background: transparent;\n color: var(--fx-text-muted);\n cursor: pointer;\n font-size: calc(var(--fx-font-size) + 2px);\n line-height: 1;\n padding: 0 var(--fx-space-xs);\n border-radius: var(--fx-radius-full);\n }\n .file .remove:hover { color: var(--fx-color-danger); }\n\n /* ---- progresso ---- */\n .progress-row { display: flex; align-items: center; gap: var(--fx-space-sm); margin-top: var(--fx-space-sm); }\n .track {\n flex: 1;\n height: 6px;\n background: var(--fx-surface-surface-hover);\n border-radius: var(--fx-radius-full);\n overflow: hidden;\n }\n .bar {\n height: 100%;\n width: 0%;\n background: var(--fx-color-primary);\n border-radius: var(--fx-radius-full);\n transition: width var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n :host([progress='100']) .bar { background: var(--fx-color-success); }\n .pct {\n font-size: calc(var(--fx-font-size) - 2px);\n font-weight: 600;\n color: var(--fx-text-default);\n min-width: 34px;\n text-align: right;\n }\n /* Full width: o host estica até o pai e o botão/dropzone acompanha. */\n :host([full]) { display: block; width: 100%; }\n :host([full]) .btn { width: 100%; }\n :host([full]) .drop { width: 100%; }\n /* Base dos glifos Fenix Icons (o @font-face vem de @wrrdev/fenix-ui/icons). */\n ${FENIX_ICON_BASE_CSS}\n `;\n\n static override get observedAttributes(): string[] {\n return ['mode', 'size', 'severity', 'label', 'icon', 'accept', 'multiple', 'disabled', 'show-progress', 'progress', 'value', 'full'];\n }\n\n get size(): string {\n const s = this.getAttr('size', 'md');\n return s === 'sm' || s === 'lg' ? s : 'md';\n }\n set size(value: string) { this.setAttribute('size', value); }\n\n /** Nomes dos arquivos selecionados (espelho em `value`). */\n get value(): string { return this.getAttr('value'); }\n set value(v: string) { this.setAttribute('value', v); }\n\n get progress(): number { return Math.min(100, Math.max(0, Number(this.getAttr('progress', '0')) || 0)); }\n set progress(v: number) { this.setAttribute('progress', String(v)); }\n\n /** Arquivos selecionados (não serializável em atributo). */\n get files(): File[] { return this._files; }\n set files(files: File[]) {\n this._files = Array.from(files);\n this.value = this._files.map((f) => f.name).join(', ');\n this._syncFilesArea();\n }\n\n /** No FormData do form: os arquivos selecionados, como o <input type=\"file\">. */\n protected override getFormValue(): string | FormData | null {\n const files = this._files;\n if (!this.name || files.length === 0) return null;\n const fd = new FormData();\n for (const f of files) fd.append(this.name, f, f.name);\n return fd;\n }\n\n protected override render(): void {\n const mode = this.getAttr('mode', 'basic');\n const advanced = mode === 'advanced';\n const label = this.getAttr('label', 'Escolher arquivo');\n const icon = this.getAttr('icon');\n const accept = this.getAttr('accept');\n const multiple = this.hasAttr('multiple');\n const disabled = this.hasAttr('disabled');\n const showProgress = this.hasAttr('show-progress');\n const pct = this.progress;\n\n const fileInput = `<input type=\"file\" class=\"picker\" ${accept ? `accept=\"${esc(accept)}\"` : ''} ${multiple ? 'multiple' : ''} ${disabled ? 'disabled' : ''} />`;\n\n this.setTemplate(`\n ${advanced\n ? `<div class=\"drop\" part=\"dropzone\" role=\"button\" tabindex=\"0\" aria-label=\"${esc(label)}\">\n ${icon ? `<span class=\"icon\" part=\"icon\" aria-hidden=\"true\">${fenixIconHtml(icon)}</span>` : ''}\n <span>${esc(label)}</span>\n <span class=\"hint\">ou arraste e solte aqui</span>\n </div>`\n : `<button type=\"button\" class=\"btn\" part=\"button\">\n ${icon ? `<span class=\"icon\" part=\"icon\" aria-hidden=\"true\">${fenixIconHtml(icon)}</span>` : ''}\n <span>${esc(label)}</span>\n </button>`}\n ${fileInput}\n <div class=\"files\" part=\"files\"></div>\n ${showProgress ? `\n <div class=\"progress-row\" part=\"progress\">\n <div class=\"track\" role=\"progressbar\" aria-valuenow=\"${pct}\" aria-valuemin=\"0\" aria-valuemax=\"100\">\n <div class=\"bar\" style=\"width: ${pct}%\"></div>\n </div>\n <span class=\"pct\">${pct}%</span>\n </div>` : ''}\n `);\n\n this._bindPicker();\n this._syncFilesArea();\n this.updateFormValue();\n\n if (!this._done && showProgress && pct >= 100 && this._files.length) {\n this._done = true;\n this.dispatchEvent(new CustomEvent('complete', { bubbles: true, composed: true }));\n }\n if (pct < 100) this._done = false;\n }\n\n private _files: File[] = [];\n private _done = false;\n\n private _picker(): HTMLInputElement | null {\n return this.root.querySelector<HTMLInputElement>('.picker');\n }\n\n private _bindPicker(): void {\n const picker = this._picker();\n if (!picker) return;\n const trigger = this.root.querySelector('.btn') ?? this.root.querySelector('.drop');\n trigger?.addEventListener('click', () => picker.click());\n trigger?.addEventListener('keydown', (e) => {\n if ((e as KeyboardEvent).key === 'Enter' || (e as KeyboardEvent).key === ' ') {\n e.preventDefault();\n picker.click();\n }\n });\n picker.addEventListener('change', () => this._acceptFiles(picker.files ?? []));\n\n // drag & drop (advanced)\n const drop = this.root.querySelector<HTMLElement>('.drop');\n if (!drop) return;\n drop.addEventListener('dragover', (e) => { e.preventDefault(); drop.classList.add('over'); });\n drop.addEventListener('dragleave', () => drop.classList.remove('over'));\n drop.addEventListener('drop', (e) => {\n e.preventDefault();\n drop.classList.remove('over');\n if (e.dataTransfer?.files.length) this._acceptFiles(e.dataTransfer.files);\n });\n }\n\n private _acceptFiles(list: FileList | File[]): void {\n const picked = this.hasAttr('multiple') ? Array.from(list) : [list[0]].filter(Boolean);\n this.files = picked;\n const picker = this._picker();\n if (picker) picker.value = '';\n this.dispatchEvent(\n new CustomEvent('select', { bubbles: true, composed: true, detail: { files: picked } }),\n );\n }\n\n private _fmtSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n }\n\n /** form.reset(): limpa os arquivos selecionados. */\n override formResetCallback(): void {\n this._files = [];\n this.value = ''; // `value` é observado → re-render + _syncFilesArea\n this.updateFormValue();\n }\n\n /** Renderiza a lista de arquivos sem re-renderizar o template (mantém estado). */\n private _syncFilesArea(): void {\n const area = this.root?.querySelector?.('.files') as HTMLElement | null;\n if (!area) return;\n area.innerHTML = this._files\n .map(\n (f, i) => `\n <div class=\"file\" part=\"file\">\n <span class=\"name\">${esc(f.name)}</span>\n <span class=\"size\">${this._fmtSize(f.size)}</span>\n <button type=\"button\" class=\"remove\" data-i=\"${i}\" part=\"remove\" aria-label=\"Remover ${esc(f.name)}\">×</button>\n </div>`,\n )\n .join('');\n area.querySelectorAll<HTMLButtonElement>('.remove').forEach((btn) =>\n btn.addEventListener('click', () => {\n const removed = this._files.splice(Number(btn.dataset.i), 1)[0];\n this.value = this._files.map((f) => f.name).join(', ');\n this.progress = 0;\n this._syncFilesArea();\n this.dispatchEvent(\n new CustomEvent('remove', { bubbles: true, composed: true, detail: { file: removed } }),\n );\n }),\n );\n }\n}\n\nexport function defineFxFileUpload(): typeof FxFileUpload {\n return defineElement('fx-fileupload', FxFileUpload);\n}\n\n\n"],"names":[],"mappings":"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"}
|
|
@@ -14,9 +14,12 @@ import { FxElement } from "../../core/base";
|
|
|
14
14
|
* - error / invalid: label + borda em vermelho, mensagem menor abaixo.
|
|
15
15
|
* - success / valid: label + borda em verde.
|
|
16
16
|
*
|
|
17
|
-
* A label do light DOM é MOVIDA para dentro do Shadow DOM (como `.flabel`)
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* A <label> do light DOM é MOVIDA para dentro do Shadow DOM (como `.flabel`)
|
|
18
|
+
* na primeira renderização — a partir daí NÃO existe cópia no light DOM.
|
|
19
|
+
* Ocultá-la com `hidden` (solução anterior) era frágil: o consumidor, o
|
|
20
|
+
* framework ou uma folha de estilo podiam remover o atributo e a label
|
|
21
|
+
* original reaparecia embaixo do campo, quebrando o floatlabel. Mover o
|
|
22
|
+
* elemento é estrutural: não há nada removível lá fora.
|
|
20
23
|
*/
|
|
21
24
|
export declare class FxFloatlabel extends FxElement {
|
|
22
25
|
static get observedAttributes(): string[];
|
|
@@ -24,6 +27,9 @@ export declare class FxFloatlabel extends FxElement {
|
|
|
24
27
|
private targetControl;
|
|
25
28
|
private observer;
|
|
26
29
|
private listenersReady;
|
|
30
|
+
/** <label> adotada do light DOM — vive no Shadow DOM após o 1º render. */
|
|
31
|
+
private _label;
|
|
32
|
+
private _labelClickBound;
|
|
27
33
|
protected render(): void;
|
|
28
34
|
protected disconnectedCallback(): void;
|
|
29
35
|
/** Campo de controle = qualquer filho direto que não seja label. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"floatlabel.d.ts","sourceRoot":"","sources":["../../../src/components/floatlabel/floatlabel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAI5C
|
|
1
|
+
{"version":3,"file":"floatlabel.d.ts","sourceRoot":"","sources":["../../../src/components/floatlabel/floatlabel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAI5C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,YAAa,SAAQ,SAAS;IAC1C,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAID,OAAgB,MAAM,SAqIpB;IACF,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,cAAc,CAAS;IAC/B,0EAA0E;IAC1E,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,gBAAgB,CAAS;IAEjC,SAAS,CAAC,MAAM,IAAI,IAAI;cAmDL,oBAAoB,IAAI,IAAI;IAK/C,oEAAoE;IACpE,OAAO,CAAC,WAAW;IAQnB,oCAAoC;IACpC,OAAO,CAAC,SAAS;IAQjB,OAAO,CAAC,YAAY;IAwBpB,0EAA0E;IAC1E,OAAO,CAAC,oBAAoB;IAoB5B,OAAO,CAAC,SAAS;IAwCjB;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,SAAS;IAgBjB,OAAO,CAAC,SAAS;IAmBjB,OAAO,CAAC,UAAU;IA8BlB,OAAO,CAAC,QAAQ;IAQhB,OAAO,CAAC,eAAe;CAIvB;AAED,wBAAgB,kBAAkB,IAAI,OAAO,YAAY,CAExD"}
|
|
@@ -7,28 +7,43 @@ const _FxFloatlabel = class _FxFloatlabel extends FxElement {
|
|
|
7
7
|
this.targetControl = null;
|
|
8
8
|
this.observer = null;
|
|
9
9
|
this.listenersReady = false;
|
|
10
|
+
this._label = null;
|
|
11
|
+
this._labelClickBound = false;
|
|
10
12
|
}
|
|
11
13
|
static get observedAttributes() {
|
|
12
14
|
return ["variant", "error", "invalid", "success", "valid", "full", "icon", "icon-pos"];
|
|
13
15
|
}
|
|
14
16
|
render() {
|
|
15
17
|
const control = this.findControl();
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
const labelFor = originalLabel ? originalLabel.getAttribute("for") || "" : "";
|
|
18
|
+
const lightLabel = this.findLabel();
|
|
19
|
+
if (lightLabel) this._label = lightLabel;
|
|
20
|
+
const label = this._label;
|
|
20
21
|
const isOver = this.getAttribute("variant") === "over";
|
|
21
22
|
this.setTemplate(`
|
|
22
23
|
<div class="wrapper" part="wrapper">
|
|
23
|
-
${isOver ? `<label class="flabel" part="label" for="${labelFor}">${labelText}</label>` : ""}
|
|
24
24
|
<div class="field-box" part="field">
|
|
25
25
|
<slot></slot>
|
|
26
|
-
${isOver ? "" : `<label class="flabel" part="label" for="${labelFor}">${labelText}</label>`}
|
|
27
26
|
</div>
|
|
28
27
|
<div class="error-message" part="error"></div>
|
|
29
28
|
</div>
|
|
30
29
|
`);
|
|
31
|
-
if (
|
|
30
|
+
if (label) {
|
|
31
|
+
label.classList.add("flabel");
|
|
32
|
+
label.setAttribute("part", "label");
|
|
33
|
+
const wrapper = this.root.querySelector(".wrapper");
|
|
34
|
+
const fieldBox = this.root.querySelector(".field-box");
|
|
35
|
+
if (wrapper && fieldBox) {
|
|
36
|
+
if (isOver) wrapper.insertBefore(label, fieldBox);
|
|
37
|
+
else fieldBox.appendChild(label);
|
|
38
|
+
}
|
|
39
|
+
if (!this._labelClickBound) {
|
|
40
|
+
this._labelClickBound = true;
|
|
41
|
+
label.addEventListener("click", (e) => {
|
|
42
|
+
e.preventDefault();
|
|
43
|
+
this.targetControl?.focus?.();
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
32
47
|
this.attachEvents();
|
|
33
48
|
this.syncControlReference(control);
|
|
34
49
|
this.syncState();
|