@wrrdev/fenix-ui 1.1.13 → 1.1.15
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 +6 -5
- 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/editor/commands.d.ts +27 -0
- package/dist/components/editor/commands.d.ts.map +1 -0
- package/dist/components/editor/commands.js +235 -0
- package/dist/components/editor/commands.js.map +1 -0
- package/dist/components/editor/editor.d.ts +83 -0
- package/dist/components/editor/editor.d.ts.map +1 -0
- package/dist/components/editor/editor.js +1951 -0
- package/dist/components/editor/editor.js.map +1 -0
- package/dist/components/editor/index.d.ts +12 -0
- package/dist/components/editor/index.d.ts.map +1 -0
- package/dist/components/editor/index.js +7 -0
- package/dist/components/editor/index.js.map +1 -0
- package/dist/components/editor/normalize-html.d.ts +33 -0
- package/dist/components/editor/normalize-html.d.ts.map +1 -0
- package/dist/components/editor/normalize-html.js +316 -0
- package/dist/components/editor/normalize-html.js.map +1 -0
- package/dist/components/editor/sanitize-html.d.ts +37 -0
- package/dist/components/editor/sanitize-html.d.ts.map +1 -0
- package/dist/components/editor/sanitize-html.js +247 -0
- package/dist/components/editor/sanitize-html.js.map +1 -0
- package/dist/components/editor/schema.d.ts +36 -0
- package/dist/components/editor/schema.d.ts.map +1 -0
- package/dist/components/editor/schema.js +232 -0
- package/dist/components/editor/schema.js.map +1 -0
- package/dist/components/editor/selection.d.ts +22 -0
- package/dist/components/editor/selection.d.ts.map +1 -0
- package/dist/components/editor/selection.js +97 -0
- package/dist/components/editor/selection.js.map +1 -0
- package/dist/components/editor/styles.d.ts +10 -0
- package/dist/components/editor/styles.d.ts.map +1 -0
- package/dist/components/editor/styles.js +446 -0
- package/dist/components/editor/styles.js.map +1 -0
- package/dist/components/editor/table-utils.d.ts +33 -0
- package/dist/components/editor/table-utils.d.ts.map +1 -0
- package/dist/components/editor/table-utils.js +150 -0
- package/dist/components/editor/table-utils.js.map +1 -0
- 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 +18 -7
- package/dist/components/input/input.d.ts.map +1 -1
- package/dist/components/input/input.js +33 -3
- 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 +27 -15
- package/dist/components/toast/toast.d.ts.map +1 -1
- package/dist/components/toast/toast.js +205 -32
- 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/jsx.d.ts +25 -1
- package/dist/core/jsx.d.ts.map +1 -1
- package/dist/core/jsx.js.map +1 -1
- 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/core/vue.d.ts +3 -1
- package/dist/core/vue.d.ts.map +1 -1
- package/dist/core/vue.js.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/editor.doc.d.ts +6 -0
- package/dist/docs/componentes/editor.doc.d.ts.map +1 -0
- package/dist/docs/componentes/fileupload.doc.d.ts.map +1 -1
- package/dist/docs/componentes/index.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 +786 -254
- package/dist/fenix-ui2.esm.js.map +1 -1
- package/dist/fenix-ui4.esm.js +4396 -172
- package/dist/fenix-ui4.esm.js.map +1 -1
- package/dist/fenix-ui5.esm.js +1 -0
- package/dist/fenix-ui5.esm.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -1
- package/dist/index.js.map +1 -1
- package/dist/plugins/auto-import.d.ts.map +1 -1
- package/dist/plugins/auto-import.js +1 -0
- package/dist/plugins/auto-import.js.map +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table-utils.js","sources":["../../../src/components/editor/table-utils.ts"],"sourcesContent":["/**\n * Utilitários de tabela do <fx-editor>.\n *\n * O editor usa `td/th` com colspan/rowspan; para mesclar/dividir células\n * precisamos de uma matriz lógica que considere os spans. Este módulo é puro\n * (recebe a tabela e o Range) para poder ser testado sem o componente.\n */\n\nexport interface TableCellPosition {\n row: number;\n col: number;\n}\n\nexport interface TableMatrix {\n rows: HTMLTableRowElement[];\n matrix: Array<Array<HTMLTableCellElement | null>>;\n}\n\n/** Constrói a matriz lógica da tabela respeitando colspan/rowspan. */\nexport function tableMatrix(table: HTMLTableElement): TableMatrix {\n const rows = Array.from(table.rows);\n const matrix: Array<Array<HTMLTableCellElement | null>> = rows.map(() => []);\n\n for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {\n const row = rows[rowIndex];\n for (const cell of Array.from(row.cells)) {\n let col = 0;\n while (matrix[rowIndex][col]) col++;\n\n const rowspan = Math.max(1, Math.min(cell.rowSpan || 1, rows.length - rowIndex));\n const colspan = Math.max(1, cell.colSpan || 1);\n\n for (let r = rowIndex; r < rowIndex + rowspan; r++) {\n if (!matrix[r]) matrix[r] = [];\n for (let c = col; c < col + colspan; c++) matrix[r][c] = cell;\n }\n }\n }\n\n const width = matrix.reduce((max, row) => Math.max(max, row.length), 0);\n for (const row of matrix) {\n while (row.length < width) row.push(null);\n }\n\n return { rows, matrix };\n}\n\n/** Posição lógica (primeira célula da matriz) de uma célula. */\nexport function cellPosition(table: HTMLTableElement, cell: HTMLTableCellElement): TableCellPosition | null {\n const { matrix } = tableMatrix(table);\n for (let row = 0; row < matrix.length; row++) {\n for (let col = 0; col < matrix[row].length; col++) {\n if (matrix[row][col] === cell) return { row, col };\n }\n }\n return null;\n}\n\n/** Células tocadas pela seleção atual. */\nexport function selectedCells(table: HTMLTableElement, range: Range): HTMLTableCellElement[] {\n return Array.from(table.querySelectorAll<HTMLTableCellElement>('td,th')).filter((cell) => {\n try {\n return range.intersectsNode(cell);\n } catch {\n return false;\n }\n });\n}\n\nfunction copyCellAttributes(from: HTMLTableCellElement, to: HTMLTableCellElement): void {\n for (const attr of Array.from(from.attributes)) {\n if (attr.name === 'colspan' || attr.name === 'rowspan') continue;\n to.setAttribute(attr.name, attr.value);\n }\n}\n\n/**\n * Mescla as células selecionadas em uma única célula retangular.\n * Retorna a célula mesclada ou null quando a seleção não forma um retângulo\n * simples (spans que saem da área, seleção em uma única célula etc.).\n */\nexport function mergeSelectedCells(\n table: HTMLTableElement,\n range: Range,\n): HTMLTableCellElement | null {\n const cells = selectedCells(table, range);\n if (cells.length < 2) return null;\n\n const { matrix } = tableMatrix(table);\n const positions = cells\n .map((cell) => ({ cell, position: cellPosition(table, cell) }))\n .filter((item): item is { cell: HTMLTableCellElement; position: TableCellPosition } => Boolean(item.position));\n if (positions.length !== cells.length) return null;\n\n const minRow = Math.min(...positions.map((item) => item.position.row));\n const maxRow = Math.max(...positions.map((item) => item.position.row));\n const minCol = Math.min(...positions.map((item) => item.position.col));\n const maxCol = Math.max(...positions.map((item) => item.position.col));\n\n // Nenhuma célula selecionada pode extrapolar o retângulo.\n for (const { cell, position } of positions) {\n if (position.row < minRow || position.col < minCol) return null;\n if (position.row + Math.max(1, cell.rowSpan || 1) - 1 > maxRow) return null;\n if (position.col + Math.max(1, cell.colSpan || 1) - 1 > maxCol) return null;\n }\n\n // O retângulo inteiro precisa estar coberto pelas células selecionadas.\n const selected = new Set(cells);\n for (let row = minRow; row <= maxRow; row++) {\n for (let col = minCol; col <= maxCol; col++) {\n const cell = matrix[row]?.[col];\n if (!cell || !selected.has(cell)) return null;\n }\n }\n\n const anchor = matrix[minRow]?.[minCol];\n if (!anchor) return null;\n\n const tag = cells.every((cell) => cell.tagName.toLowerCase() === 'th') ? 'th' : 'td';\n const merged = document.createElement(tag) as HTMLTableCellElement;\n copyCellAttributes(anchor, merged);\n if (tag === 'td') merged.removeAttribute('scope');\n\n merged.colSpan = maxCol - minCol + 1;\n merged.rowSpan = maxRow - minRow + 1;\n if (merged.colSpan === 1) merged.removeAttribute('colspan');\n if (merged.rowSpan === 1) merged.removeAttribute('rowspan');\n\n for (const cell of cells) {\n while (cell.firstChild) merged.appendChild(cell.firstChild);\n }\n\n anchor.replaceWith(merged);\n for (const cell of cells) {\n if (cell !== anchor && cell.parentNode) cell.remove();\n }\n\n return merged;\n}\n\n/** Insere uma célula na posição lógica `col` de uma linha. */\nfunction insertCellAtColumn(\n table: HTMLTableElement,\n row: HTMLTableRowElement,\n col: number,\n cell: HTMLTableCellElement,\n): void {\n const rowIndex = Array.from(table.rows).indexOf(row);\n const { matrix } = tableMatrix(table);\n let before: HTMLTableCellElement | null = null;\n\n for (const existing of Array.from(row.cells)) {\n let start = -1;\n for (let c = 0; c < (matrix[rowIndex]?.length ?? 0); c++) {\n if (matrix[rowIndex][c] === existing) {\n start = c;\n break;\n }\n }\n if (start >= col) {\n before = existing;\n break;\n }\n }\n\n row.insertBefore(cell, before);\n}\n\n/**\n * Divide uma célula mesclada.\n * Retorna as células criadas (sem a original) ou [] se não havia merge.\n */\nexport function splitCell(table: HTMLTableElement, cell: HTMLTableCellElement): HTMLTableCellElement[] {\n const colspan = Math.max(1, cell.colSpan || 1);\n const rowspan = Math.max(1, cell.rowSpan || 1);\n if (colspan === 1 && rowspan === 1) return [];\n\n const position = cellPosition(table, cell);\n if (!position) return [];\n\n const rows = Array.from(table.rows);\n const makeCell = (): HTMLTableCellElement => {\n const next = document.createElement(cell.tagName.toLowerCase()) as HTMLTableCellElement;\n if (next.tagName.toLowerCase() === 'th') {\n next.setAttribute('scope', cell.getAttribute('scope') ?? 'col');\n }\n next.innerHTML = '<p><br></p>';\n return next;\n };\n\n const created: HTMLTableCellElement[] = [];\n\n // Linha da célula original: abre as colunas extras ao lado dela.\n let last: Element = cell;\n for (let index = 1; index < colspan; index++) {\n const next = makeCell();\n last.after(next);\n last = next;\n created.push(next);\n }\n\n // Libera a área lógica antes de preencher as linhas abaixo.\n cell.removeAttribute('colspan');\n cell.removeAttribute('rowspan');\n\n // Linhas abaixo: preenche a área do rowspan.\n for (let index = 1; index < rowspan; index++) {\n const row = rows[position.row + index];\n if (!row) break;\n for (let offset = 0; offset < colspan; offset++) {\n const next = makeCell();\n insertCellAtColumn(table, row, position.col + offset, next);\n created.push(next);\n }\n }\n\n return created;\n}\n\n"],"names":[],"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();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"floatlabel.js","sources":["../../../src/components/floatlabel/floatlabel.ts"],"sourcesContent":["import { FxElement } from \"../../core/base\";\nimport { css } from \"../../core/css\";\nimport { defineElement } from \"../../core/define\";\n\n/**\n * Rótulo flutuante .\n *\n * Envolve um campo de formulário (fx-input, fx-select, fx-multiselect,\n * fx-datepicker, ...) + um <label>. A label se comporta como placeholder\n * quando o campo está vazio e \"sobe\" quando o campo é focado, contém valor\n * ou abre dropdown.\n *\n * - variant=\"on\" (padrão): label sobre a borda superior\n * - variant=\"in\" : label como placeholder interno, sobe ao focar/valor\n * - variant=\"over\" : label estática acima do campo\n *\n * - error / invalid: label + borda em vermelho, mensagem menor abaixo.\n * - success / valid: label + borda em verde.\n *\n * A label do light DOM é MOVIDA para dentro do Shadow DOM (como `.flabel`),\n * usando classes de shadow (não `::slotted`), o que é confiável mesmo com\n * componentes aninhados que possuem shadow root próprio.\n */\nexport class FxFloatlabel extends FxElement {\n\tstatic override get observedAttributes(): string[] {\n\t\treturn [\"variant\", \"error\", \"invalid\", \"success\", \"valid\", \"full\", \"icon\", \"icon-pos\"];\n\t}\n\t// 'active' NÃO entra em observedAttributes: togglá-lo apenas atualiza CSS\n\t// via :host([active]), sem re-render destrutivo (mantém o foco do campo).\n\n\tstatic override styles = css`\n\t\t:host {\n\t\t\tdisplay: inline-block;\n\t\t\tposition: relative;\n\t\t\tfont-family: var(--fx-font-family);\n\t\t\tfont-size: var(--fx-font-size);\n\t\t\tvertical-align: middle;\n\t\t}\n\t\t/* Full width: o host estica até o pai; o controle interno com\n\t\t atributo full (ou largura herdada) acompanha. */\n\t\t:host([full]) {\n\t\t\tdisplay: block;\n\t\t\twidth: 100%;\n\t\t}\n\t\t.wrapper {\n\t\t\tposition: relative;\n\t\t\tdisplay: inline-block;\n\t\t\twidth: 100%;\n\t\t}\n\t\t/* Caixa do campo: referência de posicionamento da label flutuante\n (isolada da mensagem de erro, que cresce o wrapper). */\n\t\t.field-box {\n\t\t\tposition: relative;\n\t\t\tdisplay: inline-block;\n\t\t\twidth: 100%;\n\t\t}\n\t\t:host([variant=\"in\"]) .field-box {\n\t\t\tpadding-top: var(--fx-space-xs, 6px);\n\t\t}\n\n\t\t/* ---- label base (variant=\"on\", padrão): sobre a borda superior ---- */\n\t\t.flabel {\n\t\t\tposition: absolute;\n\t\t\tleft: var(--fx-space-md, 12px);\n\t\t\ttop: 0;\n\t\t\ttransform: translateY(-50%);\n\t\t\tfont-size: calc(var(--fx-font-size, 14px) - 3px);\n\t\t\tfont-weight: 600;\n\t\t\tcolor: var(--fx-text-muted, #94a3b8);\n\t\t\tbackground-color: var(--fx-surface-background, #fff);\n\t\t\tpadding: 0 4px;\n\t\t\tline-height: 1;\n\t\t\tborder-radius: 2px;\n\t\t\tpointer-events: none;\n\t\t\tz-index: 2;\n\t\t\twhite-space: nowrap;\n\t\t\ttransition:\n\t\t\t\ttop var(--fx-motion-duration-normal, 180ms)\n\t\t\t\t\tvar(--fx-motion-easing, ease-in-out),\n\t\t\t\ttransform var(--fx-motion-duration-normal, 180ms)\n\t\t\t\t\tvar(--fx-motion-easing, ease-in-out),\n\t\t\t\tfont-size var(--fx-motion-duration-normal, 180ms)\n\t\t\t\t\tvar(--fx-motion-easing, ease-in-out),\n\t\t\t\tcolor var(--fx-motion-duration-normal, 180ms)\n\t\t\t\t\tvar(--fx-motion-easing, ease-in-out);\n\t\t}\n\t\t:host([active]) .flabel {\n\t\t\tcolor: var(--fx-color-primary, #4f46e5);\n\t\t}\n\n\t\t/* ---- variant=\"in\": label como placeholder dentro, sobe ao focar/valor ---- */\n\t\t:host([variant=\"in\"]) .flabel {\n\t\t\ttop: 50%;\n\t\t\ttransform: translateY(-50%);\n\t\t\tfont-size: var(--fx-font-size, 14px);\n\t\t\tfont-weight: var(--fx-font-weight, 400);\n\t\t\tbackground: transparent;\n\t\t\tpadding: 0;\n\t\t}\n\t\t:host([variant=\"in\"][active]) .flabel {\n\t\t\ttop: 0;\n\t\t\ttransform: translateY(-50%);\n\t\t\tfont-size: calc(var(--fx-font-size, 14px) - 3px);\n\t\t\tfont-weight: 600;\n\t\t\tbackground-color: var(--fx-surface-background, #fff);\n\t\t\tpadding: 0 5px;\n\t\t\tcolor: var(--fx-color-primary, #4f46e5);\n\t\t}\n\n\t\t/* ---- variant=\"in\" com ícone dentro do campo (fx-input icon):\n\t\t enquanto a label funciona como placeholder (sem foco/valor), ela\n\t\t começa DEPOIS do glifo (mesma métrica de padding do fx-input).\n\t\t Ao subir (active), volta ao offset padrão da borda. ---- */\n\t\t:host([variant=\"in\"]) .flabel.icon-indent {\n\t\t\tleft: calc(\n\t\t\t\tvar(--fx-space-md, 12px) +\n\t\t\t\tvar(--fx-input-icon-size, calc(var(--fx-font-size, 14px) + 6px)) +\n\t\t\t\tvar(--fx-space-sm, 8px)\n\t\t\t);\n\t\t}\n\t\t:host([variant=\"in\"][active]) .flabel.icon-indent {\n\t\t\tleft: var(--fx-space-md, 12px);\n\t\t}\n\n\t\t/* ---- variant=\"over\": label estática ACIMA do campo (fora, em fluxo) ---- */\n\t\t:host([variant=\"over\"]) {\n\t\t\tdisplay: block;\n\t\t}\n\t\t:host([variant=\"over\"]) .flabel {\n\t\t\tposition: static;\n\t\t\tdisplay: block;\n\t\t\tmargin-bottom: var(--fx-space-xs, 4px);\n\t\t\tbackground: transparent;\n\t\t\tpadding: 0;\n\t\t\tfont-size: calc(var(--fx-font-size, 14px) - 3px);\n\t\t\tfont-weight: 600;\n\t\t\twhite-space: normal;\n\t\t}\n\n\t\t/* ---- estados de erro / sucesso ---- */\n\t\t:host([error]) .flabel,\n\t\t:host([invalid]) .flabel {\n\t\t\tcolor: var(--fx-color-danger, #dc2626);\n\t\t}\n\t\t:host([success]) .flabel,\n\t\t:host([valid]) .flabel {\n\t\t\tcolor: var(--fx-color-success, #10b981);\n\t\t}\n\n\t\t/* ---- mensagem de erro sob o campo (fonte menor) ---- */\n\t\t.error-message {\n\t\t\tdisplay: none;\n\t\t\tmargin-top: var(--fx-space-xs, 4px);\n\t\t\tmargin-left: var(--fx-space-md, 12px);\n\t\t\tfont-size: calc(var(--fx-font-size, 14px) - 3px);\n\t\t\tfont-weight: 500;\n\t\t\tcolor: var(--fx-color-danger, #dc2626);\n\t\t\tline-height: 1.2;\n\t\t}\n\t\t:host([error]) .error-message,\n\t\t:host([invalid]) .error-message {\n\t\t\tdisplay: block;\n\t\t}\n\t`;\n\tprivate targetControl: HTMLElement | null = null;\n\tprivate observer: MutationObserver | null = null;\n\tprivate listenersReady = false;\n\n\tprotected render(): void {\n\t\tconst control = this.findControl();\n\t\tconst label = this.findLabel();\n\t\tconst originalLabel = label as HTMLElement | null;\n\t\tconst labelText = originalLabel\n\t\t\t? originalLabel.textContent?.replace(/\\s+/g, \" \").trim()\n\t\t\t: \"\";\n\t\tconst labelFor = originalLabel\n\t\t\t? originalLabel.getAttribute(\"for\") || \"\"\n\t\t\t: \"\";\n\t\tconst isOver = this.getAttribute(\"variant\") === \"over\";\n\n\t\tthis.setTemplate(`\n <div class=\"wrapper\" part=\"wrapper\">\n ${isOver ? `<label class=\"flabel\" part=\"label\" for=\"${labelFor}\">${labelText}</label>` : \"\"}\n <div class=\"field-box\" part=\"field\">\n <slot></slot>\n ${isOver ? \"\" : `<label class=\"flabel\" part=\"label\" for=\"${labelFor}\">${labelText}</label>`}\n </div>\n <div class=\"error-message\" part=\"error\"></div>\n </div>\n `);\n\n\t\t// Esconde a label original do light DOM (a renderizada no shadow assume o papel).\n\t\tif (originalLabel) originalLabel.setAttribute(\"hidden\", \"\");\n\n\t\tthis.attachEvents();\n\t\tthis.syncControlReference(control);\n\t\tthis.syncState();\n\t\tthis.applyError();\n\t\tthis.applyFull();\n\t\tthis.applyIcon();\n\t\tthis.syncIconIndent();\n\t}\n\n\tprotected override disconnectedCallback(): void {\n\t\tsuper.disconnectedCallback();\n\t\tthis.observer?.disconnect();\n\t}\n\n\t/** Campo de controle = qualquer filho direto que não seja label. */\n\tprivate findControl(): HTMLElement | null {\n\t\treturn (\n\t\t\t(Array.from(this.children).find(\n\t\t\t\t(n) => n.tagName.toLowerCase() !== \"label\",\n\t\t\t) as HTMLElement) ?? null\n\t\t);\n\t}\n\n\t/** Label = filho direto <label>. */\n\tprivate findLabel(): HTMLElement | null {\n\t\treturn (\n\t\t\t(Array.from(this.children).find(\n\t\t\t\t(n) => n.tagName.toLowerCase() === \"label\",\n\t\t\t) as HTMLElement) ?? null\n\t\t);\n\t}\n\n\tprivate attachEvents(): void {\n\t\tconst slot = this.root.querySelector(\"slot\");\n\t\tif (slot) {\n\t\t\tslot.addEventListener(\"slotchange\", () => {\n\t\t\t\t// Se o usuário trocar o controle/label em tempo de execução, re-sincroniza.\n\t\t\t\tconst control = this.findControl();\n\t\t\t\tthis.syncControlReference(control);\n\t\t\t\tthis.syncState();\n\t\t\t\tthis.applyError();\n\t\t\t\tthis.applyFull();\n\t\t\t});\n\t\t}\n\n\t\tif (this.listenersReady) return;\n\t\tthis.listenersReady = true;\n\n\t\tthis.addEventListener(\"focusin\", () => this.syncState());\n\t\tthis.addEventListener(\"focusout\", () =>\n\t\t\tsetTimeout(() => this.syncState(), 60),\n\t\t);\n\t\tthis.addEventListener(\"input\", () => this.syncState());\n\t\tthis.addEventListener(\"change\", () => this.syncState());\n\t}\n\n\t/** Observa mudanças de atributos do controle (value, open, values...). */\n\tprivate syncControlReference(\n\t\tcontrol: HTMLElement | null = this.targetControl,\n\t): void {\n\t\tif (control !== this.targetControl) {\n\t\t\tthis.observer?.disconnect();\n\t\t\tthis.observer = null;\n\t\t\tthis.targetControl = control;\n\t\t\tif (control) {\n\t\t\t\tthis.observer = new MutationObserver(() => {\n\t\t\t\t\tthis.syncState();\n\t\t\t\t\tthis.syncIconIndent();\n\t\t\t\t});\n\t\t\t\tthis.observer.observe(control, {\n\t\t\t\t\tattributes: true,\n\t\t\t\t\tattributeFilter: [\"value\", \"values\", \"start\", \"end\", \"open\", \"icon\", \"icon-pos\"],\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate syncState(): void {\n\t\tif (this.targetControl && !this.targetControl.isConnected) {\n\t\t\tthis.targetControl = null;\n\t\t\tthis.observer?.disconnect();\n\t\t\tthis.observer = null;\n\t\t}\n\n\t\tif (!this.targetControl) {\n\t\t\tthis.removeAttribute(\"active\");\n\t\t\tthis.removeAttribute(\"focus\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst c = this.targetControl as any;\n\t\tconst attrValue = c.getAttribute\n\t\t\t? (c.getAttribute(\"value\") ?? \"\").trim()\n\t\t\t: \"\";\n\t\tconst attrValues = c.getAttribute\n\t\t\t? (c.getAttribute(\"values\") ?? \"\").trim()\n\t\t\t: \"\";\n\t\tconst hasValue = Boolean(\n\t\t\t(typeof c.value === \"string\" && c.value.trim() !== \"\") ||\n\t\t\t(Array.isArray(c.values) && c.values.length > 0) ||\n\t\t\tattrValue !== \"\" ||\n\t\t\tattrValues !== \"\" ||\n\t\t\t(c.hasAttribute &&\n\t\t\t\t(c.hasAttribute(\"open\") || c.hasAttribute(\"start\"))),\n\t\t);\n\n\t\tconst focused = this.isFocusedInside();\n\t\tif (hasValue || focused) {\n\t\t\tthis.setAttribute(\"active\", \"\");\n\t\t\tif (focused) this.setAttribute(\"focus\", \"\");\n\t\t\telse this.removeAttribute(\"focus\");\n\t\t} else {\n\t\t\tthis.removeAttribute(\"active\");\n\t\t\tthis.removeAttribute(\"focus\");\n\t\t}\n\t}\n\n\t/**\n\t * variant=\"in\" + ícone à esquerda dentro do campo (fx-input icon):\n\t * enquanto a label é placeholder ela precisa começar depois do glifo.\n\t * Detecta via atributos icon/icon-pos (ou slot icon) do controle.\n\t */\n\tprivate syncIconIndent(): void {\n\t\tconst label = this.root.querySelector(\".flabel\");\n\t\tif (!label) return;\n\t\tconst c = this.targetControl;\n\t\tconst hasLeftIcon = Boolean(\n\t\t\tc &&\n\t\t\t(c.hasAttribute(\"icon\") || c.querySelector(':scope > [slot=\"icon\"]')) &&\n\t\t\tc.getAttribute(\"icon-pos\") !== \"right\",\n\t\t);\n\t\tlabel.classList.toggle(\"icon-indent\", hasLeftIcon);\n\t}\n\n\tprivate applyFull(): void {\n\t\t// Espelha o full no controle interno: o CSS :host([full]) do floatlabel\n\t\t// estica apenas o HOST — sem isso o campo interno (ex.: fx-input com\n\t\t// width 260px fixa) não acompanharia o container.\n\t\tif (\n\t\t\tthis.targetControl &&\n\t\t\ttypeof this.targetControl.setAttribute === \"function\"\n\t\t) {\n\t\t\tif (this.hasAttr(\"full\")) {\n\t\t\t\tthis.targetControl.setAttribute(\"full\", \"\");\n\t\t\t} else {\n\t\t\t\tthis.targetControl.removeAttribute(\"full\");\n\t\t\t}\n\t\t\t}\n\t}\n\n\tprivate applyIcon(): void {\n\t\tif (\n\t\t\tthis.targetControl &&\n\t\t\ttypeof this.targetControl.setAttribute === \"function\"\n\t\t) {\n\t\t\t// Só propaga icon/icon-pos se o FLOATLABEL possuir o atributo.\n\t\t\t// Se o ícone foi pondo diretamente no controle (ex.: <fx-input icon=\"lock\">),\n\t\t\t// não remove — deixa o controle manter o seu próprio ícone.\n\t\t\tif (this.hasAttr(\"icon\")) {\n\t\t\t\tconst icon = this.getAttr(\"icon\");\n\t\t\t\tconst iconPos = this.getAttr(\"icon-pos\");\n\t\t\t\tif (icon !== \"\") this.targetControl.setAttribute(\"icon\", icon);\n\t\t\t\telse this.targetControl.setAttribute(\"icon\", \"\");\n\t\t\t\tif (iconPos && iconPos !== \"left\") this.targetControl.setAttribute(\"icon-pos\", iconPos);\n\t\t\t\telse this.targetControl.removeAttribute(\"icon-pos\");\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate applyError(): void {\n\t\tconst hasError = this.hasAttr(\"error\") || this.hasAttr(\"invalid\");\n\t\tconst errMsg = this.root.querySelector(\n\t\t\t\".error-message\",\n\t\t) as HTMLElement | null;\n\t\tif (errMsg) {\n\t\t\terrMsg.textContent = hasError\n\t\t\t\t? this.getAttribute(\"error-text\")?.trim() || \"Valor inválido\"\n\t\t\t\t: \"\";\n\t\t}\n\n\t\t// Espelha o estado no controle interno para que a borda também fique vermelha/verde.\n\t\tif (\n\t\t\tthis.targetControl &&\n\t\t\ttypeof this.targetControl.setAttribute === \"function\"\n\t\t) {\n\t\t\tconst t = this.targetControl;\n\t\t\tif (hasError) {\n\t\t\t\tt.setAttribute(\"error\", \"\");\n\t\t\t\tt.removeAttribute(\"success\");\n\t\t\t} else if (this.hasAttr(\"success\") || this.hasAttr(\"valid\")) {\n\t\t\t\tt.setAttribute(\"success\", \"\");\n\t\t\t\tt.removeAttribute(\"error\");\n\t\t\t} else {\n\t\t\t\tt.removeAttribute(\"error\");\n\t\t\t\tt.removeAttribute(\"success\");\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate isWithin(activeEl: Element): boolean {\n\t\tlet current: Element | null = activeEl;\n\t\twhile (current && current !== this) {\n\t\t\tcurrent = current.parentElement;\n\t\t}\n\t\treturn current === this;\n\t}\n\n\tprivate isFocusedInside(): boolean {\n\t\tconst activeEl = (document as Document).activeElement;\n\t\treturn Boolean(activeEl && this.isWithin(activeEl));\n\t}\n}\n\nexport function defineFxFloatlabel(): typeof FxFloatlabel {\n\treturn defineElement(\"fx-floatlabel\", FxFloatlabel);\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"floatlabel.js","sources":["../../../src/components/floatlabel/floatlabel.ts"],"sourcesContent":["import { FxElement } from \"../../core/base\";\nimport { css } from \"../../core/css\";\nimport { defineElement } from \"../../core/define\";\n\n/**\n * Rótulo flutuante .\n *\n * Envolve um campo de formulário (fx-input, fx-select, fx-multiselect,\n * fx-datepicker, ...) + um <label>. A label se comporta como placeholder\n * quando o campo está vazio e \"sobe\" quando o campo é focado, contém valor\n * ou abre dropdown.\n *\n * - variant=\"on\" (padrão): label sobre a borda superior\n * - variant=\"in\" : label como placeholder interno, sobe ao focar/valor\n * - variant=\"over\" : label estática acima do campo\n *\n * - error / invalid: label + borda em vermelho, mensagem menor abaixo.\n * - success / valid: label + borda em verde.\n *\n * A <label> do light DOM é MOVIDA para dentro do Shadow DOM (como `.flabel`)\n * na primeira renderização — a partir daí NÃO existe cópia no light DOM.\n * Ocultá-la com `hidden` (solução anterior) era frágil: o consumidor, o\n * framework ou uma folha de estilo podiam remover o atributo e a label\n * original reaparecia embaixo do campo, quebrando o floatlabel. Mover o\n * elemento é estrutural: não há nada removível lá fora.\n */\nexport class FxFloatlabel extends FxElement {\n\tstatic override get observedAttributes(): string[] {\n\t\treturn [\"variant\", \"error\", \"invalid\", \"success\", \"valid\", \"full\", \"icon\", \"icon-pos\"];\n\t}\n\t// 'active' NÃO entra em observedAttributes: togglá-lo apenas atualiza CSS\n\t// via :host([active]), sem re-render destrutivo (mantém o foco do campo).\n\n\tstatic override styles = css`\n\t\t:host {\n\t\t\tdisplay: inline-block;\n\t\t\tposition: relative;\n\t\t\tfont-family: var(--fx-font-family);\n\t\t\tfont-size: var(--fx-font-size);\n\t\t\tvertical-align: middle;\n\t\t}\n\t\t/* Full width: o host estica até o pai; o controle interno com\n\t\t atributo full (ou largura herdada) acompanha. */\n\t\t:host([full]) {\n\t\t\tdisplay: block;\n\t\t\twidth: 100%;\n\t\t}\n\t\t.wrapper {\n\t\t\tposition: relative;\n\t\t\tdisplay: inline-block;\n\t\t\twidth: 100%;\n\t\t}\n\t\t/* Caixa do campo: referência de posicionamento da label flutuante\n (isolada da mensagem de erro, que cresce o wrapper). */\n\t\t.field-box {\n\t\t\tposition: relative;\n\t\t\tdisplay: inline-block;\n\t\t\twidth: 100%;\n\t\t}\n\t\t:host([variant=\"in\"]) .field-box {\n\t\t\tpadding-top: var(--fx-space-xs, 6px);\n\t\t}\n\n\t\t/* ---- label base (variant=\"on\", padrão): sobre a borda superior ---- */\n\t\t.flabel {\n\t\t\tposition: absolute;\n\t\t\tleft: var(--fx-space-md, 12px);\n\t\t\ttop: 0;\n\t\t\ttransform: translateY(-50%);\n\t\t\tfont-size: calc(var(--fx-font-size, 14px) - 3px);\n\t\t\tfont-weight: 600;\n\t\t\tcolor: var(--fx-text-muted, #94a3b8);\n\t\t\tbackground-color: var(--fx-surface-background, #fff);\n\t\t\tpadding: 0 4px;\n\t\t\tline-height: 1;\n\t\t\tborder-radius: 2px;\n\t\t\tpointer-events: none;\n\t\t\tz-index: 2;\n\t\t\twhite-space: nowrap;\n\t\t\ttransition:\n\t\t\t\ttop var(--fx-motion-duration-normal, 180ms)\n\t\t\t\t\tvar(--fx-motion-easing, ease-in-out),\n\t\t\t\ttransform var(--fx-motion-duration-normal, 180ms)\n\t\t\t\t\tvar(--fx-motion-easing, ease-in-out),\n\t\t\t\tfont-size var(--fx-motion-duration-normal, 180ms)\n\t\t\t\t\tvar(--fx-motion-easing, ease-in-out),\n\t\t\t\tcolor var(--fx-motion-duration-normal, 180ms)\n\t\t\t\t\tvar(--fx-motion-easing, ease-in-out);\n\t\t}\n\t\t:host([active]) .flabel {\n\t\t\tcolor: var(--fx-color-primary, #4f46e5);\n\t\t}\n\n\t\t/* ---- variant=\"in\": label como placeholder dentro, sobe ao focar/valor ---- */\n\t\t:host([variant=\"in\"]) .flabel {\n\t\t\ttop: 50%;\n\t\t\ttransform: translateY(-50%);\n\t\t\tfont-size: var(--fx-font-size, 14px);\n\t\t\tfont-weight: var(--fx-font-weight, 400);\n\t\t\tbackground: transparent;\n\t\t\tpadding: 0;\n\t\t}\n\t\t:host([variant=\"in\"][active]) .flabel {\n\t\t\ttop: 0;\n\t\t\ttransform: translateY(-50%);\n\t\t\tfont-size: calc(var(--fx-font-size, 14px) - 3px);\n\t\t\tfont-weight: 600;\n\t\t\tbackground-color: var(--fx-surface-background, #fff);\n\t\t\tpadding: 0 5px;\n\t\t\tcolor: var(--fx-color-primary, #4f46e5);\n\t\t}\n\n\t\t/* ---- variant=\"in\" com ícone dentro do campo (fx-input icon):\n\t\t enquanto a label funciona como placeholder (sem foco/valor), ela\n\t\t começa DEPOIS do glifo (mesma métrica de padding do fx-input).\n\t\t Ao subir (active), volta ao offset padrão da borda. ---- */\n\t\t:host([variant=\"in\"]) .flabel.icon-indent {\n\t\t\tleft: calc(\n\t\t\t\tvar(--fx-space-md, 12px) +\n\t\t\t\tvar(--fx-input-icon-size, calc(var(--fx-font-size, 14px) + 6px)) +\n\t\t\t\tvar(--fx-space-sm, 8px)\n\t\t\t);\n\t\t}\n\t\t:host([variant=\"in\"][active]) .flabel.icon-indent {\n\t\t\tleft: var(--fx-space-md, 12px);\n\t\t}\n\n\t\t/* ---- variant=\"over\": label estática ACIMA do campo (fora, em fluxo) ---- */\n\t\t:host([variant=\"over\"]) {\n\t\t\tdisplay: block;\n\t\t}\n\t\t:host([variant=\"over\"]) .flabel {\n\t\t\tposition: static;\n\t\t\tdisplay: block;\n\t\t\tmargin-bottom: var(--fx-space-xs, 4px);\n\t\t\tbackground: transparent;\n\t\t\tpadding: 0;\n\t\t\tfont-size: calc(var(--fx-font-size, 14px) - 3px);\n\t\t\tfont-weight: 600;\n\t\t\twhite-space: normal;\n\t\t}\n\n\t\t/* ---- estados de erro / sucesso ---- */\n\t\t:host([error]) .flabel,\n\t\t:host([invalid]) .flabel {\n\t\t\tcolor: var(--fx-color-danger, #dc2626);\n\t\t}\n\t\t:host([success]) .flabel,\n\t\t:host([valid]) .flabel {\n\t\t\tcolor: var(--fx-color-success, #10b981);\n\t\t}\n\n\t\t/* ---- mensagem de erro sob o campo (fonte menor) ---- */\n\t\t.error-message {\n\t\t\tdisplay: none;\n\t\t\tmargin-top: var(--fx-space-xs, 4px);\n\t\t\tmargin-left: var(--fx-space-md, 12px);\n\t\t\tfont-size: calc(var(--fx-font-size, 14px) - 3px);\n\t\t\tfont-weight: 500;\n\t\t\tcolor: var(--fx-color-danger, #dc2626);\n\t\t\tline-height: 1.2;\n\t\t}\n\t\t:host([error]) .error-message,\n\t\t:host([invalid]) .error-message {\n\t\t\tdisplay: block;\n\t\t}\n\t`;\n\tprivate targetControl: HTMLElement | null = null;\n\tprivate observer: MutationObserver | null = null;\n\tprivate listenersReady = false;\n\t/** <label> adotada do light DOM — vive no Shadow DOM após o 1º render. */\n\tprivate _label: HTMLElement | null = null;\n\tprivate _labelClickBound = false;\n\n\tprotected render(): void {\n\t\tconst control = this.findControl();\n\t\t// Adota a <label> do light DOM (na 1ª renderização, ou se o consumidor\n\t\t// trocar por outra depois). Uma vez movida, o render seguinte não a\n\t\t// encontra no light DOM — a referência `_label` é reutilizada.\n\t\tconst lightLabel = this.findLabel();\n\t\tif (lightLabel) this._label = lightLabel;\n\t\tconst label = this._label;\n\t\tconst isOver = this.getAttribute(\"variant\") === \"over\";\n\n\t\tthis.setTemplate(`\n <div class=\"wrapper\" part=\"wrapper\">\n <div class=\"field-box\" part=\"field\">\n <slot></slot>\n </div>\n <div class=\"error-message\" part=\"error\"></div>\n </div>\n `);\n\n\t\t// A label original é INSERIDA no Shadow DOM (move o próprio elemento —\n\t\t// não é uma cópia). `setTemplate` reconstrói o template a cada render e\n\t\t// a label desanexada é reinserida no novo wrapper, com estado intacto.\n\t\tif (label) {\n\t\t\tlabel.classList.add(\"flabel\");\n\t\t\tlabel.setAttribute(\"part\", \"label\");\n\t\t\tconst wrapper = this.root.querySelector(\".wrapper\");\n\t\t\tconst fieldBox = this.root.querySelector(\".field-box\");\n\t\t\tif (wrapper && fieldBox) {\n\t\t\t\tif (isOver) wrapper.insertBefore(label, fieldBox);\n\t\t\t\telse fieldBox.appendChild(label);\n\t\t\t}\n\t\t\t// `for` não cruza a fronteira do shadow (o id do controle vive no\n\t\t\t// light DOM): o clique na label foca o campo manualmente.\n\t\t\tif (!this._labelClickBound) {\n\t\t\t\tthis._labelClickBound = true;\n\t\t\t\tlabel.addEventListener(\"click\", (e) => {\n\t\t\t\t\te.preventDefault();\n\t\t\t\t\t(this.targetControl as HTMLElement | null)?.focus?.();\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tthis.attachEvents();\n\t\tthis.syncControlReference(control);\n\t\tthis.syncState();\n\t\tthis.applyError();\n\t\tthis.applyFull();\n\t\tthis.applyIcon();\n\t\tthis.syncIconIndent();\n\t}\n\n\tprotected override disconnectedCallback(): void {\n\t\tsuper.disconnectedCallback();\n\t\tthis.observer?.disconnect();\n\t}\n\n\t/** Campo de controle = qualquer filho direto que não seja label. */\n\tprivate findControl(): HTMLElement | null {\n\t\treturn (\n\t\t\t(Array.from(this.children).find(\n\t\t\t\t(n) => n.tagName.toLowerCase() !== \"label\",\n\t\t\t) as HTMLElement) ?? null\n\t\t);\n\t}\n\n\t/** Label = filho direto <label>. */\n\tprivate findLabel(): HTMLElement | null {\n\t\treturn (\n\t\t\t(Array.from(this.children).find(\n\t\t\t\t(n) => n.tagName.toLowerCase() === \"label\",\n\t\t\t) as HTMLElement) ?? null\n\t\t);\n\t}\n\n\tprivate attachEvents(): void {\n\t\tconst slot = this.root.querySelector(\"slot\");\n\t\tif (slot) {\n\t\t\tslot.addEventListener(\"slotchange\", () => {\n\t\t\t\t// Se o usuário trocar o controle/label em tempo de execução, re-sincroniza.\n\t\t\t\tconst control = this.findControl();\n\t\t\t\tthis.syncControlReference(control);\n\t\t\t\tthis.syncState();\n\t\t\t\tthis.applyError();\n\t\t\t\tthis.applyFull();\n\t\t\t});\n\t\t}\n\n\t\tif (this.listenersReady) return;\n\t\tthis.listenersReady = true;\n\n\t\tthis.addEventListener(\"focusin\", () => this.syncState());\n\t\tthis.addEventListener(\"focusout\", () =>\n\t\t\tsetTimeout(() => this.syncState(), 60),\n\t\t);\n\t\tthis.addEventListener(\"input\", () => this.syncState());\n\t\tthis.addEventListener(\"change\", () => this.syncState());\n\t}\n\n\t/** Observa mudanças de atributos do controle (value, open, values...). */\n\tprivate syncControlReference(\n\t\tcontrol: HTMLElement | null = this.targetControl,\n\t): void {\n\t\tif (control !== this.targetControl) {\n\t\t\tthis.observer?.disconnect();\n\t\t\tthis.observer = null;\n\t\t\tthis.targetControl = control;\n\t\t\tif (control) {\n\t\t\t\tthis.observer = new MutationObserver(() => {\n\t\t\t\t\tthis.syncState();\n\t\t\t\t\tthis.syncIconIndent();\n\t\t\t\t});\n\t\t\t\tthis.observer.observe(control, {\n\t\t\t\t\tattributes: true,\n\t\t\t\t\tattributeFilter: [\"value\", \"values\", \"start\", \"end\", \"open\", \"icon\", \"icon-pos\"],\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate syncState(): void {\n\t\tif (this.targetControl && !this.targetControl.isConnected) {\n\t\t\tthis.targetControl = null;\n\t\t\tthis.observer?.disconnect();\n\t\t\tthis.observer = null;\n\t\t}\n\n\t\tif (!this.targetControl) {\n\t\t\tthis.removeAttribute(\"active\");\n\t\t\tthis.removeAttribute(\"focus\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst c = this.targetControl as any;\n\t\tconst attrValue = c.getAttribute\n\t\t\t? (c.getAttribute(\"value\") ?? \"\").trim()\n\t\t\t: \"\";\n\t\tconst attrValues = c.getAttribute\n\t\t\t? (c.getAttribute(\"values\") ?? \"\").trim()\n\t\t\t: \"\";\n\t\tconst hasValue = Boolean(\n\t\t\t(typeof c.value === \"string\" && c.value.trim() !== \"\") ||\n\t\t\t(Array.isArray(c.values) && c.values.length > 0) ||\n\t\t\tattrValue !== \"\" ||\n\t\t\tattrValues !== \"\" ||\n\t\t\t(c.hasAttribute &&\n\t\t\t\t(c.hasAttribute(\"open\") || c.hasAttribute(\"start\"))),\n\t\t);\n\n\t\tconst focused = this.isFocusedInside();\n\t\tif (hasValue || focused) {\n\t\t\tthis.setAttribute(\"active\", \"\");\n\t\t\tif (focused) this.setAttribute(\"focus\", \"\");\n\t\t\telse this.removeAttribute(\"focus\");\n\t\t} else {\n\t\t\tthis.removeAttribute(\"active\");\n\t\t\tthis.removeAttribute(\"focus\");\n\t\t}\n\t}\n\n\t/**\n\t * variant=\"in\" + ícone à esquerda dentro do campo (fx-input icon):\n\t * enquanto a label é placeholder ela precisa começar depois do glifo.\n\t * Detecta via atributos icon/icon-pos (ou slot icon) do controle.\n\t */\n\tprivate syncIconIndent(): void {\n\t\tconst label = this.root.querySelector(\".flabel\");\n\t\tif (!label) return;\n\t\tconst c = this.targetControl;\n\t\tconst hasLeftIcon = Boolean(\n\t\t\tc &&\n\t\t\t(c.hasAttribute(\"icon\") || c.querySelector(':scope > [slot=\"icon\"]')) &&\n\t\t\tc.getAttribute(\"icon-pos\") !== \"right\",\n\t\t);\n\t\tlabel.classList.toggle(\"icon-indent\", hasLeftIcon);\n\t}\n\n\tprivate applyFull(): void {\n\t\t// Espelha o full no controle interno: o CSS :host([full]) do floatlabel\n\t\t// estica apenas o HOST — sem isso o campo interno (ex.: fx-input com\n\t\t// width 260px fixa) não acompanharia o container.\n\t\tif (\n\t\t\tthis.targetControl &&\n\t\t\ttypeof this.targetControl.setAttribute === \"function\"\n\t\t) {\n\t\t\tif (this.hasAttr(\"full\")) {\n\t\t\t\tthis.targetControl.setAttribute(\"full\", \"\");\n\t\t\t} else {\n\t\t\t\tthis.targetControl.removeAttribute(\"full\");\n\t\t\t}\n\t\t\t}\n\t}\n\n\tprivate applyIcon(): void {\n\t\tif (\n\t\t\tthis.targetControl &&\n\t\t\ttypeof this.targetControl.setAttribute === \"function\"\n\t\t) {\n\t\t\t// Só propaga icon/icon-pos se o FLOATLABEL possuir o atributo.\n\t\t\t// Se o ícone foi pondo diretamente no controle (ex.: <fx-input icon=\"lock\">),\n\t\t\t// não remove — deixa o controle manter o seu próprio ícone.\n\t\t\tif (this.hasAttr(\"icon\")) {\n\t\t\t\tconst icon = this.getAttr(\"icon\");\n\t\t\t\tconst iconPos = this.getAttr(\"icon-pos\");\n\t\t\t\tif (icon !== \"\") this.targetControl.setAttribute(\"icon\", icon);\n\t\t\t\telse this.targetControl.setAttribute(\"icon\", \"\");\n\t\t\t\tif (iconPos && iconPos !== \"left\") this.targetControl.setAttribute(\"icon-pos\", iconPos);\n\t\t\t\telse this.targetControl.removeAttribute(\"icon-pos\");\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate applyError(): void {\n\t\tconst hasError = this.hasAttr(\"error\") || this.hasAttr(\"invalid\");\n\t\tconst errMsg = this.root.querySelector(\n\t\t\t\".error-message\",\n\t\t) as HTMLElement | null;\n\t\tif (errMsg) {\n\t\t\terrMsg.textContent = hasError\n\t\t\t\t? this.getAttribute(\"error-text\")?.trim() || \"Valor inválido\"\n\t\t\t\t: \"\";\n\t\t}\n\n\t\t// Espelha o estado no controle interno para que a borda também fique vermelha/verde.\n\t\tif (\n\t\t\tthis.targetControl &&\n\t\t\ttypeof this.targetControl.setAttribute === \"function\"\n\t\t) {\n\t\t\tconst t = this.targetControl;\n\t\t\tif (hasError) {\n\t\t\t\tt.setAttribute(\"error\", \"\");\n\t\t\t\tt.removeAttribute(\"success\");\n\t\t\t} else if (this.hasAttr(\"success\") || this.hasAttr(\"valid\")) {\n\t\t\t\tt.setAttribute(\"success\", \"\");\n\t\t\t\tt.removeAttribute(\"error\");\n\t\t\t} else {\n\t\t\t\tt.removeAttribute(\"error\");\n\t\t\t\tt.removeAttribute(\"success\");\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate isWithin(activeEl: Element): boolean {\n\t\tlet current: Element | null = activeEl;\n\t\twhile (current && current !== this) {\n\t\t\tcurrent = current.parentElement;\n\t\t}\n\t\treturn current === this;\n\t}\n\n\tprivate isFocusedInside(): boolean {\n\t\tconst activeEl = (document as Document).activeElement;\n\t\treturn Boolean(activeEl && this.isWithin(activeEl));\n\t}\n}\n\nexport function defineFxFloatlabel(): typeof FxFloatlabel {\n\treturn defineElement(\"fx-floatlabel\", FxFloatlabel);\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,20 +1,29 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FxFormElement } from '../../core/form-element';
|
|
2
|
+
import type { FxInputType } from '../../core/jsx';
|
|
2
3
|
/**
|
|
3
4
|
* <fx-input> — Campo de texto estilizado com os tokens do tema.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Form-Associated Custom Element: dentro de um <form> entra no FormData
|
|
7
|
+
* (via atributo `name`), recebe reset/disable do form, é alvo de
|
|
8
|
+
* `<label for>`/`autofocus` e o Enter submete o form.
|
|
9
|
+
*
|
|
10
|
+
* Atributos: type (text|number|password), value, name,
|
|
11
|
+
* size, placeholder, disabled, readonly, min, max, step, autofocus,
|
|
12
|
+
* clearable, full (largura 100% acompanhando o elemento pai), icon (nome do
|
|
13
|
+
* glifo Fenix Icons ou emoji/texto livre) e icon-pos (left|right, padrão left).
|
|
9
14
|
* Largura: o default (260px; sm 220px / lg 300px) fica no HOST via tokens
|
|
10
15
|
* --fx-input-width(-sm|-lg) — CSS externo, classes e style inline no elemento
|
|
11
16
|
* vencem o default sem precisar de atributo.
|
|
12
17
|
* Slots: `icon` (ícone customizado no lugar do atributo).
|
|
13
|
-
* Eventos: `input` e `
|
|
18
|
+
* Eventos: `input`, `change` e `valueChange` (composed, detail: { value }) —
|
|
19
|
+
* `input` aciona o v-model do Vue e `valueChange` o [(value)] do Angular.
|
|
14
20
|
*/
|
|
15
|
-
export declare class FxInput extends
|
|
21
|
+
export declare class FxInput extends FxFormElement {
|
|
16
22
|
static styles: string;
|
|
17
23
|
static get observedAttributes(): string[];
|
|
24
|
+
/** Tipo do input interno. Padrão: `'text'`. */
|
|
25
|
+
get type(): FxInputType;
|
|
26
|
+
set type(value: FxInputType);
|
|
18
27
|
/** Tamanho do campo. Padrão: `'md'`. */
|
|
19
28
|
get size(): string;
|
|
20
29
|
set size(value: string);
|
|
@@ -23,6 +32,8 @@ export declare class FxInput extends FxElement {
|
|
|
23
32
|
get disabled(): boolean;
|
|
24
33
|
set disabled(value: boolean);
|
|
25
34
|
protected render(): void;
|
|
35
|
+
/** Empurra o atributo `value` para o campo interno sem re-renderizar. */
|
|
36
|
+
protected syncInternalControl(): void;
|
|
26
37
|
}
|
|
27
38
|
export declare function defineFxInput(): typeof FxInput;
|
|
28
39
|
//# sourceMappingURL=input.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../../src/components/input/input.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../../src/components/input/input.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAKxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAclD;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,OAAQ,SAAQ,aAAa;IACxC,OAAgB,MAAM,SAuIpB;IAMF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAMjD;IAED,+CAA+C;IAC/C,IAAa,IAAI,IAAI,WAAW,CAE/B;IACD,IAAa,IAAI,CAAC,KAAK,EAAE,WAAW,EAEnC;IAED,wCAAwC;IACxC,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAErB;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;IACD,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,EAMtB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAE1B;cAEkB,MAAM,IAAI,IAAI;IAiFjC,yEAAyE;cACtD,mBAAmB,IAAI,IAAI;CAO/C;AAED,wBAAgB,aAAa,IAAI,OAAO,OAAO,CAE9C"}
|
|
@@ -1,9 +1,14 @@
|
|
|
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
|
|
6
|
+
const INPUT_TYPES = /* @__PURE__ */ new Set(["text", "number", "password"]);
|
|
7
|
+
function normalizeInputType(value) {
|
|
8
|
+
const type = (value || "text").toLowerCase();
|
|
9
|
+
return INPUT_TYPES.has(type) ? type : "text";
|
|
10
|
+
}
|
|
11
|
+
const _FxInput = class _FxInput extends FxFormElement {
|
|
7
12
|
// `value` fica FORA da observação: refleti-lo a cada tecla não pode
|
|
8
13
|
// re-renderizar o template, senão o campo perde o foco ao digitar.
|
|
9
14
|
// `error`/`success` (+ aliases `invalid`/`valid`) PRECISAM ser observados:
|
|
@@ -26,6 +31,13 @@ const _FxInput = class _FxInput extends FxElement {
|
|
|
26
31
|
"valid"
|
|
27
32
|
];
|
|
28
33
|
}
|
|
34
|
+
/** Tipo do input interno. Padrão: `'text'`. */
|
|
35
|
+
get type() {
|
|
36
|
+
return normalizeInputType(this.getAttr("type", "text"));
|
|
37
|
+
}
|
|
38
|
+
set type(value) {
|
|
39
|
+
this.setAttribute("type", normalizeInputType(value));
|
|
40
|
+
}
|
|
29
41
|
/** Tamanho do campo. Padrão: `'md'`. */
|
|
30
42
|
get size() {
|
|
31
43
|
const s = this.getAttr("size", "md");
|
|
@@ -39,6 +51,8 @@ const _FxInput = class _FxInput extends FxElement {
|
|
|
39
51
|
}
|
|
40
52
|
set value(value) {
|
|
41
53
|
this.setAttribute("value", value);
|
|
54
|
+
this.syncInternalControl();
|
|
55
|
+
this.updateFormValue();
|
|
42
56
|
}
|
|
43
57
|
get disabled() {
|
|
44
58
|
return this.hasAttr("disabled");
|
|
@@ -47,7 +61,7 @@ const _FxInput = class _FxInput extends FxElement {
|
|
|
47
61
|
this.toggleAttr("disabled", Boolean(value));
|
|
48
62
|
}
|
|
49
63
|
render() {
|
|
50
|
-
const type = this.
|
|
64
|
+
const type = this.type;
|
|
51
65
|
const placeholder = this.getAttr("placeholder");
|
|
52
66
|
const readonly = this.hasAttr("readonly");
|
|
53
67
|
const disabled = this.hasAttr("disabled");
|
|
@@ -78,6 +92,7 @@ const _FxInput = class _FxInput extends FxElement {
|
|
|
78
92
|
if (readonly) field.setAttribute("readonly", "");
|
|
79
93
|
const emit = (event) => {
|
|
80
94
|
this.value = field.value;
|
|
95
|
+
this.emitValueEvents();
|
|
81
96
|
this.dispatchEvent(
|
|
82
97
|
new CustomEvent(event, { bubbles: true, composed: true, detail: { value: field.value } })
|
|
83
98
|
);
|
|
@@ -90,6 +105,12 @@ const _FxInput = class _FxInput extends FxElement {
|
|
|
90
105
|
e.stopPropagation();
|
|
91
106
|
emit("change");
|
|
92
107
|
});
|
|
108
|
+
field.addEventListener("keydown", (e) => {
|
|
109
|
+
if (e.key !== "Enter" || e.isComposing) return;
|
|
110
|
+
if (!this.form) return;
|
|
111
|
+
e.preventDefault();
|
|
112
|
+
this.form.requestSubmit();
|
|
113
|
+
});
|
|
93
114
|
const clearBtn = this.root.querySelector(".clear");
|
|
94
115
|
const syncClear = () => {
|
|
95
116
|
clearBtn?.toggleAttribute("hidden", field.value === "");
|
|
@@ -103,6 +124,15 @@ const _FxInput = class _FxInput extends FxElement {
|
|
|
103
124
|
field.focus();
|
|
104
125
|
});
|
|
105
126
|
field.addEventListener("input", syncClear);
|
|
127
|
+
this.updateFormValue();
|
|
128
|
+
}
|
|
129
|
+
/** Empurra o atributo `value` para o campo interno sem re-renderizar. */
|
|
130
|
+
syncInternalControl() {
|
|
131
|
+
const field = this.root.querySelector(".field");
|
|
132
|
+
const value = this.getAttr("value");
|
|
133
|
+
const type = this.type;
|
|
134
|
+
if (field && field.type !== type) field.type = type;
|
|
135
|
+
if (field && field.value !== value) field.value = value;
|
|
106
136
|
}
|
|
107
137
|
};
|
|
108
138
|
_FxInput.styles = css`
|