@wrrdev/fenix-ui 1.1.14 → 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.
Files changed (69) hide show
  1. package/README.md +5 -4
  2. package/dist/components/editor/commands.d.ts +27 -0
  3. package/dist/components/editor/commands.d.ts.map +1 -0
  4. package/dist/components/editor/commands.js +235 -0
  5. package/dist/components/editor/commands.js.map +1 -0
  6. package/dist/components/editor/editor.d.ts +83 -0
  7. package/dist/components/editor/editor.d.ts.map +1 -0
  8. package/dist/components/editor/editor.js +1951 -0
  9. package/dist/components/editor/editor.js.map +1 -0
  10. package/dist/components/editor/index.d.ts +12 -0
  11. package/dist/components/editor/index.d.ts.map +1 -0
  12. package/dist/components/editor/index.js +7 -0
  13. package/dist/components/editor/index.js.map +1 -0
  14. package/dist/components/editor/normalize-html.d.ts +33 -0
  15. package/dist/components/editor/normalize-html.d.ts.map +1 -0
  16. package/dist/components/editor/normalize-html.js +316 -0
  17. package/dist/components/editor/normalize-html.js.map +1 -0
  18. package/dist/components/editor/sanitize-html.d.ts +37 -0
  19. package/dist/components/editor/sanitize-html.d.ts.map +1 -0
  20. package/dist/components/editor/sanitize-html.js +247 -0
  21. package/dist/components/editor/sanitize-html.js.map +1 -0
  22. package/dist/components/editor/schema.d.ts +36 -0
  23. package/dist/components/editor/schema.d.ts.map +1 -0
  24. package/dist/components/editor/schema.js +232 -0
  25. package/dist/components/editor/schema.js.map +1 -0
  26. package/dist/components/editor/selection.d.ts +22 -0
  27. package/dist/components/editor/selection.d.ts.map +1 -0
  28. package/dist/components/editor/selection.js +97 -0
  29. package/dist/components/editor/selection.js.map +1 -0
  30. package/dist/components/editor/styles.d.ts +10 -0
  31. package/dist/components/editor/styles.d.ts.map +1 -0
  32. package/dist/components/editor/styles.js +446 -0
  33. package/dist/components/editor/styles.js.map +1 -0
  34. package/dist/components/editor/table-utils.d.ts +33 -0
  35. package/dist/components/editor/table-utils.d.ts.map +1 -0
  36. package/dist/components/editor/table-utils.js +150 -0
  37. package/dist/components/editor/table-utils.js.map +1 -0
  38. package/dist/components/input/input.d.ts +5 -1
  39. package/dist/components/input/input.d.ts.map +1 -1
  40. package/dist/components/input/input.js +15 -1
  41. package/dist/components/input/input.js.map +1 -1
  42. package/dist/components/toast/toast.d.ts +10 -10
  43. package/dist/components/toast/toast.d.ts.map +1 -1
  44. package/dist/components/toast/toast.js +46 -13
  45. package/dist/components/toast/toast.js.map +1 -1
  46. package/dist/core/jsx.d.ts +25 -1
  47. package/dist/core/jsx.d.ts.map +1 -1
  48. package/dist/core/jsx.js.map +1 -1
  49. package/dist/core/vue.d.ts +3 -1
  50. package/dist/core/vue.d.ts.map +1 -1
  51. package/dist/core/vue.js.map +1 -1
  52. package/dist/docs/componentes/editor.doc.d.ts +6 -0
  53. package/dist/docs/componentes/editor.doc.d.ts.map +1 -0
  54. package/dist/docs/componentes/index.d.ts.map +1 -1
  55. package/dist/docs/componentes/input.doc.d.ts.map +1 -1
  56. package/dist/fenix-ui.umd.min.js +748 -254
  57. package/dist/fenix-ui2.esm.js.map +1 -1
  58. package/dist/fenix-ui4.esm.js +3817 -183
  59. package/dist/fenix-ui4.esm.js.map +1 -1
  60. package/dist/fenix-ui5.esm.js +1 -0
  61. package/dist/fenix-ui5.esm.js.map +1 -1
  62. package/dist/index.d.ts +1 -0
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +14 -0
  65. package/dist/index.js.map +1 -1
  66. package/dist/plugins/auto-import.d.ts.map +1 -1
  67. package/dist/plugins/auto-import.js +1 -0
  68. package/dist/plugins/auto-import.js.map +1 -1
  69. 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,4 +1,5 @@
1
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
  *
@@ -6,7 +7,7 @@ import { FxFormElement } from '../../core/form-element';
6
7
  * (via atributo `name`), recebe reset/disable do form, é alvo de
7
8
  * `<label for>`/`autofocus` e o Enter submete o form.
8
9
  *
9
- * Atributos: type (text|number|email|password|search|tel|url), value, name,
10
+ * Atributos: type (text|number|password), value, name,
10
11
  * size, placeholder, disabled, readonly, min, max, step, autofocus,
11
12
  * clearable, full (largura 100% acompanhando o elemento pai), icon (nome do
12
13
  * glifo Fenix Icons ou emoji/texto livre) e icon-pos (left|right, padrão left).
@@ -20,6 +21,9 @@ import { FxFormElement } from '../../core/form-element';
20
21
  export declare class FxInput extends FxFormElement {
21
22
  static styles: string;
22
23
  static get observedAttributes(): string[];
24
+ /** Tipo do input interno. Padrão: `'text'`. */
25
+ get type(): FxInputType;
26
+ set type(value: FxInputType);
23
27
  /** Tamanho do campo. Padrão: `'md'`. */
24
28
  get size(): string;
25
29
  set size(value: string);
@@ -1 +1 @@
1
- {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../../src/components/input/input.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAMxD;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,OAAQ,SAAQ,aAAa;IACxC,OAAgB,MAAM,SAuIpB;IAMF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAMjD;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;CAK/C;AAED,wBAAgB,aAAa,IAAI,OAAO,OAAO,CAE9C"}
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"}
@@ -3,6 +3,11 @@ 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 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
+ }
6
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.
@@ -26,6 +31,13 @@ const _FxInput = class _FxInput extends FxFormElement {
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");
@@ -49,7 +61,7 @@ const _FxInput = class _FxInput extends FxFormElement {
49
61
  this.toggleAttr("disabled", Boolean(value));
50
62
  }
51
63
  render() {
52
- const type = this.getAttr("type", "text");
64
+ const type = this.type;
53
65
  const placeholder = this.getAttr("placeholder");
54
66
  const readonly = this.hasAttr("readonly");
55
67
  const disabled = this.hasAttr("disabled");
@@ -118,6 +130,8 @@ const _FxInput = class _FxInput extends FxFormElement {
118
130
  syncInternalControl() {
119
131
  const field = this.root.querySelector(".field");
120
132
  const value = this.getAttr("value");
133
+ const type = this.type;
134
+ if (field && field.type !== type) field.type = type;
121
135
  if (field && field.value !== value) field.value = value;
122
136
  }
123
137
  };
@@ -1 +1 @@
1
- {"version":3,"file":"input.js","sources":["../../../src/components/input/input.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-input> — Campo de texto estilizado com os tokens do tema.\n *\n * Form-Associated Custom Element: dentro de um <form> entra no FormData\n * (via atributo `name`), recebe reset/disable do form, é alvo de\n * `<label for>`/`autofocus` e o Enter submete o form.\n *\n * Atributos: type (text|number|email|password|search|tel|url), value, name,\n * size, placeholder, disabled, readonly, min, max, step, autofocus,\n * clearable, full (largura 100% acompanhando o elemento pai), icon (nome do\n * glifo Fenix Icons ou emoji/texto livre) e icon-pos (left|right, padrão left).\n * Largura: o default (260px; sm 220px / lg 300px) fica no HOST via tokens\n * --fx-input-width(-sm|-lg) — CSS externo, classes e style inline no elemento\n * vencem o default sem precisar de atributo.\n * Slots: `icon` (ícone customizado no lugar do atributo).\n * Eventos: `input`, `change` e `valueChange` (composed, detail: { value }) —\n * `input` aciona o v-model do Vue e `valueChange` o [(value)] do Angular.\n */\nexport class FxInput extends FxFormElement {\n static override styles = css`\n :host {\n display: inline-block;\n vertical-align: middle;\n /* Largura padrão no HOST (e não no .field interno): assim CSS externo,\n classes do framework (ex.: w-full) e style inline no elemento vencem\n este default naturalmente — regras do documento têm precedência sobre\n :host. Default global trocável via token: --fx-input-width (-sm/-lg). */\n width: var(--fx-input-width, 260px);\n vertical-align: middle;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n }\n .field {\n font-family: inherit;\n font-size: inherit;\n /* Altura determinística: com line-height fixo, o padding vertical cabe no\n token --fx-size-* e a caixa fica EXATAMENTE no tamanho padrão\n (sm 32 / md 40 / lg 48), igual para todos os campos. */\n line-height: var(--fx-font-line-height);\n min-height: var(--fx-size-md);\n font-weight: var(--fx-font-weight);\n color: var(--fx-text-default);\n background-color: var(--fx-surface-background);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-md);\n padding: var(--fx-space-sm) var(--fx-space-lg);\n /* Acompanha a largura definida no :host. */\n width: 100%;\n box-sizing: border-box;\n transition:\n border-color var(--fx-motion-duration-normal) var(--fx-motion-easing),\n box-shadow var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n .field::placeholder { color: var(--fx-text-muted); opacity: 1; }\n .field:hover { border-color: var(--fx-border-hover); }\n .field:focus-visible {\n outline: none;\n border-color: var(--fx-color-primary);\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n .field:disabled,\n .field[readonly] {\n opacity: 0.55;\n cursor: not-allowed;\n background-color: var(--fx-surface-surface-hover);\n }\n /* Validação: borda sempre; o brilho (anel) segue o token do preset\n --fx-effect-error-ring, que acompanha effect.focus-ring — anel\n desligado ⇒ erro só com a borda vermelha, ligado ⇒ soma o brilho. */\n :host([error]) .field,\n :host([invalid]) .field,\n :host([error]) .field:focus-visible,\n :host([invalid]) .field:focus-visible {\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 :host([success]) .field:focus-visible,\n :host([valid]) .field:focus-visible {\n border-color: var(--fx-color-success, #16a34a);\n box-shadow: var(--fx-effect-success-ring, 0 0 0 3px color-mix(in srgb, var(--fx-color-success, #16a34a) 18%, transparent));\n }\n :host([size='sm']) { width: var(--fx-input-width-sm, 220px); }\n :host([size='sm']) .field { padding: var(--fx-space-xs) var(--fx-space-md); font-size: var(--fx-font-size); min-height: var(--fx-size-sm); }\n :host([size='lg']) { width: var(--fx-input-width-lg, 300px); }\n :host([size='lg']) .field { padding: var(--fx-space-sm) var(--fx-space-xl); font-size: calc(var(--fx-font-size) + 4px); min-height: var(--fx-size-lg); }\n /* Full width: o host estica até o pai e o campo interno acompanha\n (usar com um container de largura controlada, ex. w-full no Vue). */\n :host([full]) { display: block; width: 100%; }\n :host([full]) .field { width: 100%; }\n /* Clearable */\n :host([clearable]) { position: relative; display: inline-flex; }\n :host([clearable][full]) { display: block; }\n .wrap { position: relative; display: inline-flex; align-items: center; width: 100%; }\n :host([full]) .wrap { display: flex; width: 100%; }\n :host([clearable]) .field { padding-right: var(--fx-space-xl); }\n :host([clearable][full]) .field { flex: 1 1 auto; }\n /* Ícones (attr icon + slot icon) — DENTRO do campo, sobre o padding\n (padrão Material/PrimeVue). Tamanho ajustável via --fx-input-icon-size. */\n .field-icon {\n position: relative;\n display: inline-flex;\n box-sizing: border-box;\n width: 100%;\n }\n .field-icon .field { flex: 1 1 auto; min-width: 0; }\n :host([full]) .field-icon { display: flex; width: 100%; }\n .field-icon .fx-icon,\n .field-icon ::slotted([slot='icon']) {\n position: absolute;\n top: 50%;\n transform: translateY(-50%);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: var(--fx-input-icon-size, calc(var(--fx-font-size) + 6px));\n line-height: 1;\n color: var(--fx-text-muted);\n pointer-events: none;\n z-index: 1;\n }\n .field-icon.icon-left .fx-icon,\n .field-icon.icon-left ::slotted([slot='icon']) { left: var(--fx-space-md); }\n .field-icon.icon-right .fx-icon,\n .field-icon.icon-right ::slotted([slot='icon']) { right: var(--fx-space-md); }\n /* padding = posição do ícone + largura + folga extra para o texto\n não colar no glifo. */\n .field-icon.icon-left .field {\n padding-left: calc(var(--fx-input-icon-size, calc(var(--fx-font-size) + 6px)) + var(--fx-space-md) + var(--fx-space-sm));\n }\n .field-icon.icon-right .field {\n padding-right: calc(var(--fx-input-icon-size, calc(var(--fx-font-size) + 6px)) + var(--fx-space-md) + var(--fx-space-sm));\n }\n /* icon-pos right + clearable: desloca o × para não sobrepor o ícone. */\n .field-icon.icon-right + .clear {\n right: calc(var(--fx-input-icon-size, calc(var(--fx-font-size) + 6px)) + var(--fx-space-lg));\n }\n .clear {\n position: absolute;\n right: var(--fx-space-xs);\n border: none;\n background: transparent;\n color: var(--fx-text-muted);\n font-size: calc(var(--fx-font-size) + 4px);\n line-height: 1;\n cursor: pointer;\n padding: 0 var(--fx-space-xs);\n border-radius: var(--fx-radius-full);\n }\n .clear:hover { color: var(--fx-color-danger); }\n .clear[hidden] { display: none; }\n /* Base dos glifos Fenix Icons (o @font-face vem de @wrrdev/fenix-ui/icons). */\n ${FENIX_ICON_BASE_CSS}\n `;\n\n // `value` fica FORA da observação: refleti-lo a cada tecla não pode\n // re-renderizar o template, senão o campo perde o foco ao digitar.\n // `error`/`success` (+ aliases `invalid`/`valid`) PRECISAM ser observados:\n // é assim que o toggle no playground / setAttribute no submit re-renderiza.\n static override get observedAttributes(): string[] {\n return [\n 'type', 'size', 'placeholder',\n 'disabled', 'readonly', 'min', 'max', 'step',\n 'icon', 'icon-pos', 'error', 'invalid', 'success', 'valid',\n ];\n }\n\n /** Tamanho do campo. Padrão: `'md'`. */\n get size(): string {\n const s = this.getAttr('size', 'md');\n return s === 'sm' || s === 'lg' ? s : 'md';\n }\n set size(value: string) {\n this.setAttribute('size', value);\n }\n\n get value(): string {\n return this.getAttr('value');\n }\n set value(value: string) {\n this.setAttribute('value', value);\n // `value` NÃO é observado (re-render a cada tecla perderia o foco): o\n // campo interno é sincronizado aqui, sem re-render, preservando foco/cursor.\n this.syncInternalControl();\n this.updateFormValue();\n }\n\n get disabled(): boolean {\n return this.hasAttr('disabled');\n }\n set disabled(value: boolean) {\n this.toggleAttr('disabled', Boolean(value));\n }\n\n protected override render(): void {\n const type = this.getAttr('type', 'text');\n const placeholder = this.getAttr('placeholder');\n const readonly = this.hasAttr('readonly');\n const disabled = this.hasAttr('disabled');\n const min = this.getAttr('min');\n const max = this.getAttr('max');\n const step = this.getAttr('step');\n const icon = this.getAttr('icon');\n const iconPos = this.getAttr('icon-pos', 'left') === 'right' ? 'right' : 'left';\n // Slot `icon` vence o atributo `icon`; sem nenhum dos dois, sem ícone.\n const slottedIcon = this.querySelector<HTMLElement>('[slot=\"icon\"]');\n const iconContent = slottedIcon\n ? '<slot name=\"icon\" part=\"icon\"></slot>'\n : (icon ? fenixIconHtml(icon) : '');\n const useIconBox = Boolean(iconContent);\n\n this.setTemplate(`\n ${this.hasAttr('clearable') ? '<div class=\"wrap\" part=\"wrap\">' : ''}\n ${useIconBox ? `<div class=\"field-icon icon-${iconPos}\">` : ''}\n ${useIconBox && iconPos === 'left' ? iconContent : ''}\n <input class=\"field\" part=\"input\" type=\"${esc(type)}\"\n ${placeholder ? `placeholder=\"${esc(placeholder)}\"` : ''}\n ${min ? `min=\"${esc(min)}\"` : ''} ${max ? `max=\"${esc(max)}\"` : ''} ${step ? `step=\"${esc(step)}\"` : ''}\n />\n ${useIconBox && iconPos === 'right' ? iconContent : ''}\n ${useIconBox ? '</div>' : ''}\n ${this.hasAttr('clearable') ? '<button type=\"button\" class=\"clear\" part=\"clear\" aria-label=\"Limpar\" tabindex=\"-1\">×</button></div>' : ''}\n `);\n\n const field = this.root.querySelector<HTMLInputElement>('.field');\n if (!field) return;\n field.value = this.getAttr('value');\n if (disabled) field.setAttribute('disabled', '');\n if (readonly) field.setAttribute('readonly', '');\n\n const emit = (event: string): void => {\n this.value = field.value;\n // input (v-model do Vue) + valueChange ([(value)] do Angular).\n this.emitValueEvents();\n this.dispatchEvent(\n new CustomEvent(event, { bubbles: true, composed: true, detail: { value: field.value } }),\n );\n };\n // Bloqueia o evento nativo (composed, sem detail) para que só o\n // CustomEvent com detail chegue aos consumidores.\n field.addEventListener('input', (e) => { e.stopPropagation(); emit('input'); });\n field.addEventListener('change', (e) => { e.stopPropagation(); emit('change'); });\n\n // Enter submete o form: o <input> interno vive no Shadow DOM e NÃO se\n // associa ao <form> do light DOM (a fronteira do shadow corta a associação\n // por árvore) — por isso a submissão implícita é acionada manualmente.\n // Ouvintes de submit no form (ex.: submit.prevent de frameworks) seguem\n // recebendo o evento normalmente.\n field.addEventListener('keydown', (e) => {\n if (e.key !== 'Enter' || e.isComposing) return;\n if (!this.form) return;\n e.preventDefault();\n this.form.requestSubmit();\n });\n\n // Clearable: botão × que limpa o campo e emite input/change vazios.\n const clearBtn = this.root.querySelector<HTMLButtonElement>('.clear');\n const syncClear = (): void => {\n clearBtn?.toggleAttribute('hidden', field.value === '');\n };\n syncClear();\n clearBtn?.addEventListener('click', () => {\n field.value = '';\n syncClear();\n emit('input');\n emit('change');\n field.focus();\n });\n field.addEventListener('input', syncClear);\n\n // Publica name/value no form (ElementInternals) a cada render — cobre o\n // estado inicial e mudanças de atributos observados.\n this.updateFormValue();\n }\n\n /** Empurra o atributo `value` para o campo interno sem re-renderizar. */\n protected override syncInternalControl(): void {\n const field = this.root.querySelector<HTMLInputElement>('.field');\n const value = this.getAttr('value');\n if (field && field.value !== value) field.value = value;\n }\n}\n\nexport function defineFxInput(): typeof FxInput {\n return defineElement('fx-input', FxInput);\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"input.js","sources":["../../../src/components/input/input.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';\nimport type { FxInputType } from '../../core/jsx';\n\n/**\n * Tipos suportados pelo <fx-input>: text, number e password.\n * Qualquer outro tipo (email, search, date, file, range, checkbox, radio…)\n * cai em `text` em runtime.\n */\nconst INPUT_TYPES = new Set(['text', 'number', 'password']);\n\nfunction normalizeInputType(value: string): string {\n const type = (value || 'text').toLowerCase();\n return INPUT_TYPES.has(type) ? type : 'text';\n}\n\n/**\n * <fx-input> — Campo de texto estilizado com os tokens do tema.\n *\n * Form-Associated Custom Element: dentro de um <form> entra no FormData\n * (via atributo `name`), recebe reset/disable do form, é alvo de\n * `<label for>`/`autofocus` e o Enter submete o form.\n *\n * Atributos: type (text|number|password), value, name,\n * size, placeholder, disabled, readonly, min, max, step, autofocus,\n * clearable, full (largura 100% acompanhando o elemento pai), icon (nome do\n * glifo Fenix Icons ou emoji/texto livre) e icon-pos (left|right, padrão left).\n * Largura: o default (260px; sm 220px / lg 300px) fica no HOST via tokens\n * --fx-input-width(-sm|-lg) — CSS externo, classes e style inline no elemento\n * vencem o default sem precisar de atributo.\n * Slots: `icon` (ícone customizado no lugar do atributo).\n * Eventos: `input`, `change` e `valueChange` (composed, detail: { value }) —\n * `input` aciona o v-model do Vue e `valueChange` o [(value)] do Angular.\n */\nexport class FxInput extends FxFormElement {\n static override styles = css`\n :host {\n display: inline-block;\n vertical-align: middle;\n /* Largura padrão no HOST (e não no .field interno): assim CSS externo,\n classes do framework (ex.: w-full) e style inline no elemento vencem\n este default naturalmente — regras do documento têm precedência sobre\n :host. Default global trocável via token: --fx-input-width (-sm/-lg). */\n width: var(--fx-input-width, 260px);\n vertical-align: middle;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n }\n .field {\n font-family: inherit;\n font-size: inherit;\n /* Altura determinística: com line-height fixo, o padding vertical cabe no\n token --fx-size-* e a caixa fica EXATAMENTE no tamanho padrão\n (sm 32 / md 40 / lg 48), igual para todos os campos. */\n line-height: var(--fx-font-line-height);\n min-height: var(--fx-size-md);\n font-weight: var(--fx-font-weight);\n color: var(--fx-text-default);\n background-color: var(--fx-surface-background);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-md);\n padding: var(--fx-space-sm) var(--fx-space-lg);\n /* Acompanha a largura definida no :host. */\n width: 100%;\n box-sizing: border-box;\n transition:\n border-color var(--fx-motion-duration-normal) var(--fx-motion-easing),\n box-shadow var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n .field::placeholder { color: var(--fx-text-muted); opacity: 1; }\n .field:hover { border-color: var(--fx-border-hover); }\n .field:focus-visible {\n outline: none;\n border-color: var(--fx-color-primary);\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n .field:disabled,\n .field[readonly] {\n opacity: 0.55;\n cursor: not-allowed;\n background-color: var(--fx-surface-surface-hover);\n }\n /* Validação: borda sempre; o brilho (anel) segue o token do preset\n --fx-effect-error-ring, que acompanha effect.focus-ring — anel\n desligado ⇒ erro só com a borda vermelha, ligado ⇒ soma o brilho. */\n :host([error]) .field,\n :host([invalid]) .field,\n :host([error]) .field:focus-visible,\n :host([invalid]) .field:focus-visible {\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 :host([success]) .field:focus-visible,\n :host([valid]) .field:focus-visible {\n border-color: var(--fx-color-success, #16a34a);\n box-shadow: var(--fx-effect-success-ring, 0 0 0 3px color-mix(in srgb, var(--fx-color-success, #16a34a) 18%, transparent));\n }\n :host([size='sm']) { width: var(--fx-input-width-sm, 220px); }\n :host([size='sm']) .field { padding: var(--fx-space-xs) var(--fx-space-md); font-size: var(--fx-font-size); min-height: var(--fx-size-sm); }\n :host([size='lg']) { width: var(--fx-input-width-lg, 300px); }\n :host([size='lg']) .field { padding: var(--fx-space-sm) var(--fx-space-xl); font-size: calc(var(--fx-font-size) + 4px); min-height: var(--fx-size-lg); }\n /* Full width: o host estica até o pai e o campo interno acompanha\n (usar com um container de largura controlada, ex. w-full no Vue). */\n :host([full]) { display: block; width: 100%; }\n :host([full]) .field { width: 100%; }\n /* Clearable */\n :host([clearable]) { position: relative; display: inline-flex; }\n :host([clearable][full]) { display: block; }\n .wrap { position: relative; display: inline-flex; align-items: center; width: 100%; }\n :host([full]) .wrap { display: flex; width: 100%; }\n :host([clearable]) .field { padding-right: var(--fx-space-xl); }\n :host([clearable][full]) .field { flex: 1 1 auto; }\n /* Ícones (attr icon + slot icon) — DENTRO do campo, sobre o padding\n (padrão Material/PrimeVue). Tamanho ajustável via --fx-input-icon-size. */\n .field-icon {\n position: relative;\n display: inline-flex;\n box-sizing: border-box;\n width: 100%;\n }\n .field-icon .field { flex: 1 1 auto; min-width: 0; }\n :host([full]) .field-icon { display: flex; width: 100%; }\n .field-icon .fx-icon,\n .field-icon ::slotted([slot='icon']) {\n position: absolute;\n top: 50%;\n transform: translateY(-50%);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: var(--fx-input-icon-size, calc(var(--fx-font-size) + 6px));\n line-height: 1;\n color: var(--fx-text-muted);\n pointer-events: none;\n z-index: 1;\n }\n .field-icon.icon-left .fx-icon,\n .field-icon.icon-left ::slotted([slot='icon']) { left: var(--fx-space-md); }\n .field-icon.icon-right .fx-icon,\n .field-icon.icon-right ::slotted([slot='icon']) { right: var(--fx-space-md); }\n /* padding = posição do ícone + largura + folga extra para o texto\n não colar no glifo. */\n .field-icon.icon-left .field {\n padding-left: calc(var(--fx-input-icon-size, calc(var(--fx-font-size) + 6px)) + var(--fx-space-md) + var(--fx-space-sm));\n }\n .field-icon.icon-right .field {\n padding-right: calc(var(--fx-input-icon-size, calc(var(--fx-font-size) + 6px)) + var(--fx-space-md) + var(--fx-space-sm));\n }\n /* icon-pos right + clearable: desloca o × para não sobrepor o ícone. */\n .field-icon.icon-right + .clear {\n right: calc(var(--fx-input-icon-size, calc(var(--fx-font-size) + 6px)) + var(--fx-space-lg));\n }\n .clear {\n position: absolute;\n right: var(--fx-space-xs);\n border: none;\n background: transparent;\n color: var(--fx-text-muted);\n font-size: calc(var(--fx-font-size) + 4px);\n line-height: 1;\n cursor: pointer;\n padding: 0 var(--fx-space-xs);\n border-radius: var(--fx-radius-full);\n }\n .clear:hover { color: var(--fx-color-danger); }\n .clear[hidden] { display: none; }\n /* Base dos glifos Fenix Icons (o @font-face vem de @wrrdev/fenix-ui/icons). */\n ${FENIX_ICON_BASE_CSS}\n `;\n\n // `value` fica FORA da observação: refleti-lo a cada tecla não pode\n // re-renderizar o template, senão o campo perde o foco ao digitar.\n // `error`/`success` (+ aliases `invalid`/`valid`) PRECISAM ser observados:\n // é assim que o toggle no playground / setAttribute no submit re-renderiza.\n static override get observedAttributes(): string[] {\n return [\n 'type', 'size', 'placeholder',\n 'disabled', 'readonly', 'min', 'max', 'step',\n 'icon', 'icon-pos', 'error', 'invalid', 'success', 'valid',\n ];\n }\n\n /** Tipo do input interno. Padrão: `'text'`. */\n override get type(): FxInputType {\n return normalizeInputType(this.getAttr('type', 'text')) as FxInputType;\n }\n override set type(value: FxInputType) {\n this.setAttribute('type', normalizeInputType(value));\n }\n\n /** Tamanho do campo. Padrão: `'md'`. */\n get size(): string {\n const s = this.getAttr('size', 'md');\n return s === 'sm' || s === 'lg' ? s : 'md';\n }\n set size(value: string) {\n this.setAttribute('size', value);\n }\n\n get value(): string {\n return this.getAttr('value');\n }\n set value(value: string) {\n this.setAttribute('value', value);\n // `value` NÃO é observado (re-render a cada tecla perderia o foco): o\n // campo interno é sincronizado aqui, sem re-render, preservando foco/cursor.\n this.syncInternalControl();\n this.updateFormValue();\n }\n\n get disabled(): boolean {\n return this.hasAttr('disabled');\n }\n set disabled(value: boolean) {\n this.toggleAttr('disabled', Boolean(value));\n }\n\n protected override render(): void {\n const type = this.type;\n const placeholder = this.getAttr('placeholder');\n const readonly = this.hasAttr('readonly');\n const disabled = this.hasAttr('disabled');\n const min = this.getAttr('min');\n const max = this.getAttr('max');\n const step = this.getAttr('step');\n const icon = this.getAttr('icon');\n const iconPos = this.getAttr('icon-pos', 'left') === 'right' ? 'right' : 'left';\n // Slot `icon` vence o atributo `icon`; sem nenhum dos dois, sem ícone.\n const slottedIcon = this.querySelector<HTMLElement>('[slot=\"icon\"]');\n const iconContent = slottedIcon\n ? '<slot name=\"icon\" part=\"icon\"></slot>'\n : (icon ? fenixIconHtml(icon) : '');\n const useIconBox = Boolean(iconContent);\n\n this.setTemplate(`\n ${this.hasAttr('clearable') ? '<div class=\"wrap\" part=\"wrap\">' : ''}\n ${useIconBox ? `<div class=\"field-icon icon-${iconPos}\">` : ''}\n ${useIconBox && iconPos === 'left' ? iconContent : ''}\n <input class=\"field\" part=\"input\" type=\"${esc(type)}\"\n ${placeholder ? `placeholder=\"${esc(placeholder)}\"` : ''}\n ${min ? `min=\"${esc(min)}\"` : ''} ${max ? `max=\"${esc(max)}\"` : ''} ${step ? `step=\"${esc(step)}\"` : ''}\n />\n ${useIconBox && iconPos === 'right' ? iconContent : ''}\n ${useIconBox ? '</div>' : ''}\n ${this.hasAttr('clearable') ? '<button type=\"button\" class=\"clear\" part=\"clear\" aria-label=\"Limpar\" tabindex=\"-1\">×</button></div>' : ''}\n `);\n\n const field = this.root.querySelector<HTMLInputElement>('.field');\n if (!field) return;\n field.value = this.getAttr('value');\n if (disabled) field.setAttribute('disabled', '');\n if (readonly) field.setAttribute('readonly', '');\n\n const emit = (event: string): void => {\n this.value = field.value;\n // input (v-model do Vue) + valueChange ([(value)] do Angular).\n this.emitValueEvents();\n this.dispatchEvent(\n new CustomEvent(event, { bubbles: true, composed: true, detail: { value: field.value } }),\n );\n };\n // Bloqueia o evento nativo (composed, sem detail) para que só o\n // CustomEvent com detail chegue aos consumidores.\n field.addEventListener('input', (e) => { e.stopPropagation(); emit('input'); });\n field.addEventListener('change', (e) => { e.stopPropagation(); emit('change'); });\n\n // Enter submete o form: o <input> interno vive no Shadow DOM e NÃO se\n // associa ao <form> do light DOM (a fronteira do shadow corta a associação\n // por árvore) — por isso a submissão implícita é acionada manualmente.\n // Ouvintes de submit no form (ex.: submit.prevent de frameworks) seguem\n // recebendo o evento normalmente.\n field.addEventListener('keydown', (e) => {\n if (e.key !== 'Enter' || e.isComposing) return;\n if (!this.form) return;\n e.preventDefault();\n this.form.requestSubmit();\n });\n\n // Clearable: botão × que limpa o campo e emite input/change vazios.\n const clearBtn = this.root.querySelector<HTMLButtonElement>('.clear');\n const syncClear = (): void => {\n clearBtn?.toggleAttribute('hidden', field.value === '');\n };\n syncClear();\n clearBtn?.addEventListener('click', () => {\n field.value = '';\n syncClear();\n emit('input');\n emit('change');\n field.focus();\n });\n field.addEventListener('input', syncClear);\n\n // Publica name/value no form (ElementInternals) a cada render — cobre o\n // estado inicial e mudanças de atributos observados.\n this.updateFormValue();\n }\n\n /** Empurra o atributo `value` para o campo interno sem re-renderizar. */\n protected override syncInternalControl(): void {\n const field = this.root.querySelector<HTMLInputElement>('.field');\n const value = this.getAttr('value');\n const type = this.type;\n if (field && field.type !== type) field.type = type;\n if (field && field.value !== value) field.value = value;\n }\n}\n\nexport function defineFxInput(): typeof FxInput {\n return defineElement('fx-input', FxInput);\n}\n"],"names":[],"mappings":"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"}
@@ -10,7 +10,7 @@
10
10
  */
11
11
  declare const POSITIONS: readonly ["top-left", "top-center", "top-right", "bottom-left", "bottom-center", "bottom-right"];
12
12
  export type ToastPosition = (typeof POSITIONS)[number];
13
- type ToastKind = 'success' | 'error' | 'info' | 'warning';
13
+ type ToastKind = "success" | "error" | "info" | "warning";
14
14
  export declare class FxToast extends HTMLElement {
15
15
  #private;
16
16
  static get observedAttributes(): string[];
@@ -26,8 +26,8 @@ export declare class FxToast extends HTMLElement {
26
26
  * pausa a contagem (timer + barra) em qualquer modo, então a barra "só
27
27
  * correr sob o cursor" deixou de fazer sentido.
28
28
  */
29
- get progress(): 'always' | 'hover' | 'paused' | undefined;
30
- set progress(value: 'always' | 'hover' | 'paused' | undefined);
29
+ get progress(): "always" | "hover" | "paused" | undefined;
30
+ set progress(value: "always" | "hover" | "paused" | undefined);
31
31
  /**
32
32
  * Lado a partir do qual a barra esvazia.
33
33
  *
@@ -39,13 +39,13 @@ export declare class FxToast extends HTMLElement {
39
39
  * Só tem efeito junto com `progress`; qualquer valor fora da lista cai em
40
40
  * `'left'`.
41
41
  */
42
- get progressOrigin(): 'left' | 'right';
43
- set progressOrigin(value: 'left' | 'right');
42
+ get progressOrigin(): "left" | "right";
43
+ set progressOrigin(value: "left" | "right");
44
44
  /** Duração efetiva em ms (0 = fixo). Mesma regra usada no timer. */
45
45
  private get safeDuration();
46
46
  /** Esquema de cores forçado do card: 'light' | 'dark' | undefined (segue o tema). */
47
- get mode(): 'light' | 'dark' | undefined;
48
- set mode(value: 'light' | 'dark' | undefined);
47
+ get mode(): "light" | "dark" | undefined;
48
+ set mode(value: "light" | "dark" | undefined);
49
49
  private timer;
50
50
  /** Tempo restante (ms) quando a contagem está pausada no hover/foco. */
51
51
  private remaining;
@@ -85,15 +85,15 @@ export interface ToastOptions {
85
85
  * sobre o toast; `'paused'` exibe a barra cheia, porém imóvel.
86
86
  * Sem efeito com `duration: 0` (não há tempo restante a representar).
87
87
  */
88
- progress?: 'always' | 'hover' | 'paused';
88
+ progress?: "always" | "hover" | "paused";
89
89
  /**
90
90
  * Lado a partir do qual a barra esvazia. `'left'` (padrão) deixa a barra
91
91
  * ancorada à esquerda; `'right'` a ancora à direita (espelhado).
92
92
  * Só tem efeito junto com `progress`.
93
93
  */
94
- progressOrigin?: 'left' | 'right';
94
+ progressOrigin?: "left" | "right";
95
95
  /** Força o esquema de cores do card, independentemente do tema global. */
96
- mode?: 'light' | 'dark';
96
+ mode?: "light" | "dark";
97
97
  }
98
98
  declare class ToastApi {
99
99
  private seq;
@@ -1 +1 @@
1
- {"version":3,"file":"toast.d.ts","sourceRoot":"","sources":["../../../src/components/toast/toast.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,QAAA,MAAM,SAAS,kGAGL,CAAC;AACX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AACvD,KAAK,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;AA2K1D,qBAAa,OAAQ,SAAQ,WAAW;;IACtC,MAAM,KAAK,kBAAkB,aAE5B;IAED;;;;;;;;;;;OAWG;IACH,IAAI,QAAQ,IAAI,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAIxD;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,EAG5D;IAED;;;;;;;;;;OAUG;IACH,IAAI,cAAc,IAAI,MAAM,GAAG,OAAO,CAErC;IACD,IAAI,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,EAEzC;IAED,oEAAoE;IACpE,OAAO,KAAK,YAAY,GAIvB;IAED,qFAAqF;IACrF,IAAI,IAAI,IAAI,OAAO,GAAG,MAAM,GAAG,SAAS,CAGvC;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,EAG3C;IAED,OAAO,CAAC,KAAK,CAA8C;IAC3D,wEAAwE;IACxE,OAAO,CAAC,SAAS,CAAK;IACtB,gFAAgF;IAChF,OAAO,CAAC,SAAS,CAAK;IACtB,+DAA+D;IAC/D,OAAO,CAAC,QAAQ,CAAS;IACzB,kDAAkD;IAClD,OAAO,CAAC,QAAQ,CAAS;IACzB,8EAA8E;IAC9E,OAAO,CAAC,QAAQ,CAAS;IACzB,6DAA6D;IAC7D,OAAO,CAAC,KAAK,CAAS;;IAOtB,iBAAiB;IA8DjB;;;;;;;OAOG;IACH,oBAAoB;IAOpB,wBAAwB,CAAC,IAAI,EAAE,MAAM;IAsDrC,OAAO;CAqFR;AA8BD,MAAM,WAAW,YAAY;IAC3B,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;IACzC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,0EAA0E;IAC1E,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CACzB;AAED,cAAM,QAAQ;IACZ,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,GAAG,CAA8B;IAEzC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,YAAY,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,MAAM;IA0BxG,KAAK,CAAC,EAAE,EAAE,MAAM;IAKhB,OAAO,GAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,YAAY,YAAmC;IACrF,KAAK,GAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,YAAY,YAAiC;IACjF,IAAI,GAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,YAAY,YAAgC;IAC/E,OAAO,GAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,YAAY,YAAmC;CACtF;AAED,eAAO,MAAM,UAAU,UAAiB,CAAC;AAIzC,wBAAgB,aAAa,SAE5B"}
1
+ {"version":3,"file":"toast.d.ts","sourceRoot":"","sources":["../../../src/components/toast/toast.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,QAAA,MAAM,SAAS,kGAOL,CAAC;AACX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AACvD,KAAK,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;AA4K1D,qBAAa,OAAQ,SAAQ,WAAW;;IACtC,MAAM,KAAK,kBAAkB,aAW5B;IAED;;;;;;;;;;;OAWG;IACH,IAAI,QAAQ,IAAI,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAIxD;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,EAG5D;IAED;;;;;;;;;;OAUG;IACH,IAAI,cAAc,IAAI,MAAM,GAAG,OAAO,CAErC;IACD,IAAI,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,EAEzC;IAED,oEAAoE;IACpE,OAAO,KAAK,YAAY,GAIvB;IAED,qFAAqF;IACrF,IAAI,IAAI,IAAI,OAAO,GAAG,MAAM,GAAG,SAAS,CAGvC;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,EAG3C;IAED,OAAO,CAAC,KAAK,CAA8C;IAC3D,wEAAwE;IACxE,OAAO,CAAC,SAAS,CAAK;IACtB,gFAAgF;IAChF,OAAO,CAAC,SAAS,CAAK;IACtB,+DAA+D;IAC/D,OAAO,CAAC,QAAQ,CAAS;IACzB,kDAAkD;IAClD,OAAO,CAAC,QAAQ,CAAS;IACzB,8EAA8E;IAC9E,OAAO,CAAC,QAAQ,CAAS;IACzB,6DAA6D;IAC7D,OAAO,CAAC,KAAK,CAAS;;IAOtB,iBAAiB;IAiEjB;;;;;;;OAOG;IACH,oBAAoB;IASpB,wBAAwB,CAAC,IAAI,EAAE,MAAM;IAoErC,OAAO;CA2FR;AAgCD,MAAM,WAAW,YAAY;IAC3B,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;IACzC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,0EAA0E;IAC1E,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CACzB;AAED,cAAM,QAAQ;IACZ,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,GAAG,CAA8B;IAEzC,IAAI,CACF,IAAI,EAAE,SAAS,EACf,KAAK,EAAE,MAAM,EACb,aAAa,CAAC,EAAE,MAAM,GAAG,YAAY,EACrC,IAAI,CAAC,EAAE,YAAY,GAClB,MAAM;IAkCT,KAAK,CAAC,EAAE,EAAE,MAAM;IAKhB,OAAO,GAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,YAAY,YAClB;IAChC,KAAK,GAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,YAAY,YAClB;IAC9B,IAAI,GAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,YAAY,YAClB;IAC7B,OAAO,GAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,YAAY,YAClB;CACjC;AAED,eAAO,MAAM,UAAU,UAAiB,CAAC;AAIzC,wBAAgB,aAAa,SAG5B"}
@@ -64,12 +64,13 @@ const CARD_CSS = `
64
64
  border-left: 4px solid var(--kind);
65
65
  border-radius: var(--fx-radius-md, 8px);
66
66
  box-shadow: var(--fx-shadow-lg, 0 10px 30px rgba(0,0,0,.14));
67
- padding: var(--fx-space-sm, 8px) var(--fx-space-md, 12px);
67
+ padding: var(--fx-space-xs, 8px) var(--fx-space-md, 12px);
68
68
  font-family: inherit;
69
69
  animation: fx-toast-in .25s ease;
70
70
  }
71
71
  @keyframes fx-toast-in { from { opacity: 0; transform: translateY(-6px); } }
72
72
  .card.leaving { opacity: 0; transition: opacity .2s ease; }
73
+ .card.no-title { min-height: 48px; }
73
74
  .icon {
74
75
  width: 22px; height: 22px; border-radius: 50%;
75
76
  background: var(--kind);
@@ -176,7 +177,16 @@ class FxToast extends HTMLElement {
176
177
  this.attachShadow({ mode: "open" });
177
178
  }
178
179
  static get observedAttributes() {
179
- return ["kind", "title", "message", "duration", "icon", "mode", "progress", "progress-origin"];
180
+ return [
181
+ "kind",
182
+ "title",
183
+ "message",
184
+ "duration",
185
+ "icon",
186
+ "mode",
187
+ "progress",
188
+ "progress-origin"
189
+ ];
180
190
  }
181
191
  /**
182
192
  * Exibe a barra de contagem regressiva na base do card.
@@ -253,7 +263,9 @@ class FxToast extends HTMLElement {
253
263
  this.disposed = true;
254
264
  if (this.timer) clearTimeout(this.timer);
255
265
  this.timer = null;
256
- this.dispatchEvent(new CustomEvent(REMOVED_EVENT, { bubbles: false, composed: false }));
266
+ this.dispatchEvent(
267
+ new CustomEvent(REMOVED_EVENT, { bubbles: false, composed: false })
268
+ );
257
269
  }
258
270
  attributeChangedCallback(name) {
259
271
  if (name === "duration") {
@@ -263,7 +275,8 @@ class FxToast extends HTMLElement {
263
275
  }
264
276
  const safe = this.safeDuration;
265
277
  this.counting = safe > 0;
266
- if (safe > 0 && this.isConnected && !this.disposed) __privateMethod(this, _FxToast_instances, startTimer_fn).call(this, safe);
278
+ if (safe > 0 && this.isConnected && !this.disposed)
279
+ __privateMethod(this, _FxToast_instances, startTimer_fn).call(this, safe);
267
280
  }
268
281
  if (name === "duration" || name === "progress" || name === "progress-origin" || name === "kind") {
269
282
  __privateMethod(this, _FxToast_instances, syncProgress_fn).call(this);
@@ -315,7 +328,10 @@ pauseTimer_fn = function() {
315
328
  if (!this.timer) return;
316
329
  clearTimeout(this.timer);
317
330
  this.timer = null;
318
- this.remaining = Math.max(0, this.remaining - (Date.now() - this.startedAt));
331
+ this.remaining = Math.max(
332
+ 0,
333
+ this.remaining - (Date.now() - this.startedAt)
334
+ );
319
335
  };
320
336
  resumeTimer_fn = function() {
321
337
  if (!this.counting || this.timer || this.disposed) return;
@@ -345,15 +361,23 @@ syncProgress_fn = function() {
345
361
  return;
346
362
  }
347
363
  if (!bar) {
348
- this.shadowRoot.querySelector(".card").insertAdjacentHTML("beforeend", '<span class="progress" part="progress" aria-hidden="true"></span>');
364
+ this.shadowRoot.querySelector(".card").insertAdjacentHTML(
365
+ "beforeend",
366
+ '<span class="progress" part="progress" aria-hidden="true"></span>'
367
+ );
349
368
  }
350
369
  const el = this.shadowRoot.querySelector(".progress");
351
370
  el.style.setProperty("--toast-duration", `${dur}ms`);
352
- el.style.setProperty("--toast-progress-origin", `${this.progressOrigin} center`);
371
+ el.style.setProperty(
372
+ "--toast-progress-origin",
373
+ `${this.progressOrigin} center`
374
+ );
353
375
  el.classList.remove("run");
354
376
  if (mode !== "paused") el.classList.add("run");
355
377
  card.classList.add("entering");
356
- requestAnimationFrame(() => requestAnimationFrame(() => card.classList.remove("entering")));
378
+ requestAnimationFrame(
379
+ () => requestAnimationFrame(() => card.classList.remove("entering"))
380
+ );
357
381
  };
358
382
  render_fn = function() {
359
383
  const kind = this.getAttribute("kind") || "info";
@@ -366,7 +390,7 @@ render_fn = function() {
366
390
  const iconContent = isFxIcon ? iconName : KIND_ICON[kind] ?? KIND_ICON.info;
367
391
  this.shadowRoot.innerHTML = `
368
392
  <style>${CARD_CSS}</style>
369
- <div class="card" style="--kind:${color}" role="status" part="card">
393
+ <div class="card${title ? "" : " no-title"}" style="--kind:${color}" role="status" part="card">
370
394
  <span class="icon${isFxIcon ? " fx-icon" : ""}">${iconContent}</span>
371
395
  <div class="body">
372
396
  ${title ? '<div class="title" part="title"></div>' : ""}
@@ -406,7 +430,10 @@ render_fn = function() {
406
430
  btn.title = "Copiar mensagem";
407
431
  }, 1200);
408
432
  });
409
- this.shadowRoot.querySelector(".close").addEventListener("click", () => this.dismiss());
433
+ this.shadowRoot.querySelector(".close").addEventListener(
434
+ "click",
435
+ () => this.dismiss()
436
+ );
410
437
  };
411
438
  /**
412
439
  * Mostra "Ver mais" somente quando a mensagem passou de 2 linhas.
@@ -424,7 +451,9 @@ syncMore_fn = function() {
424
451
  more.hidden = !(msg.scrollHeight - msg.clientHeight > 1);
425
452
  };
426
453
  function regionFor(position) {
427
- let region = document.querySelector(`[data-fx-toast-region="${position}"]`);
454
+ let region = document.querySelector(
455
+ `[data-fx-toast-region="${position}"]`
456
+ );
428
457
  if (!region) {
429
458
  region = document.createElement("div");
430
459
  region.setAttribute("data-fx-toast-region", position);
@@ -459,7 +488,10 @@ class ToastApi {
459
488
  push(kind, title, messageOrOpts, opts) {
460
489
  const id = ++this.seq;
461
490
  const message = typeof messageOrOpts === "string" ? messageOrOpts : messageOrOpts?.message;
462
- const o = { ...typeof messageOrOpts === "object" && messageOrOpts ? messageOrOpts : void 0, ...opts };
491
+ const o = {
492
+ ...typeof messageOrOpts === "object" && messageOrOpts ? messageOrOpts : void 0,
493
+ ...opts
494
+ };
463
495
  const el = document.createElement("fx-toast");
464
496
  el.setAttribute("kind", kind);
465
497
  el.setAttribute("title", title);
@@ -484,7 +516,8 @@ class ToastApi {
484
516
  const FenixToast = new ToastApi();
485
517
  globalThis.FenixToast = FenixToast;
486
518
  function defineFxToast() {
487
- if (!customElements.get("fx-toast")) customElements.define("fx-toast", FxToast);
519
+ if (!customElements.get("fx-toast"))
520
+ customElements.define("fx-toast", FxToast);
488
521
  }
489
522
  export {
490
523
  FenixToast,
@@ -1 +1 @@
1
- {"version":3,"mappings":"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","names":[],"ignoreList":[],"sources":["../../../src/components/toast/toast.ts"],"sourcesContent":["/**\n * <fx-toast> — notificações imperativas com posição e duração configuráveis.\n *\n * Atributos: kind (success|error|info|warning), title, message,\n * position (top-left|top-center|top-right|bottom-left|bottom-center|bottom-right),\n * duration (ms; 0 = fixo até fechar), mode ('light'|'dark' — força o esquema de\n * cores do card independentemente do tema global), progress (booleano ou\n * 'always'|'hover'|'paused' — barra de contagem regressiva na cor do kind),\n * progress-origin (left|right — lado pelo qual a barra esvazia; padrão left).\n */\nconst POSITIONS = [\n 'top-left', 'top-center', 'top-right',\n 'bottom-left', 'bottom-center', 'bottom-right',\n] as const;\nexport type ToastPosition = (typeof POSITIONS)[number];\ntype ToastKind = 'success' | 'error' | 'info' | 'warning';\n\n/**\n * Evento interno disparado quando o <fx-toast> sai do DOM.\n * A API `FenixToast` o escuta para remover a entrada do mapa interno —\n * é o que impede o acúmulo de toasts já dispensados.\n */\nconst REMOVED_EVENT = 'fx-toast:removed';\n\nconst KIND_COLOR: Record<ToastKind, string> = {\n success: 'var(--fx-color-success, #10b981)',\n error: 'var(--fx-color-danger, #f43f5e)',\n info: 'var(--fx-color-info, #0ea5e9)',\n warning: 'var(--fx-color-warning, #f59e0b)',\n};\n\n/** Ícone exibido no lugar da bolinha, conforme a variante. */\nconst KIND_ICON: Record<ToastKind, string> = {\n success: '✓',\n error: '✕',\n warning: '!',\n info: 'i',\n};\n\n/** Ícone de copiar (SVG inline: não depende da fonte de ícones). */\nconst COPY_SVG =\n '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"9\" y=\"9\" width=\"12\" height=\"12\" rx=\"2\"></rect><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"></path></svg>';\n\nconst CARD_CSS = `\n:host { display: contents; pointer-events: auto; }\n/* Modo explícito: sobrepõe os tokens herdados do tema global SOMENTE neste card. */\n:host([mode='dark']) {\n --fx-surface-background: #1e293b;\n --fx-text-default: #f1f5f9;\n --fx-text-muted: #94a3b8;\n --fx-border-default: #334155;\n --fx-shadow-lg: 0 10px 30px rgba(0, 0, 0, .5);\n --fx-surface-surface-hover: rgba(255, 255, 255, .08);\n}\n:host([mode='light']) {\n --fx-surface-background: #ffffff;\n --fx-text-default: #1e293b;\n --fx-text-muted: #64748b;\n --fx-border-default: #e2e8f0;\n --fx-shadow-lg: 0 10px 30px rgba(0, 0, 0, .14);\n --fx-surface-surface-hover: rgba(0, 0, 0, .05);\n}\n.fx-icon {\n font-family: var(--fx-icon-font, 'Fenix Icons');\n font-weight: normal;\n font-style: normal;\n line-height: 1;\n letter-spacing: normal;\n display: inline-block;\n white-space: nowrap;\n direction: ltr;\n font-variation-settings: 'FILL' 0, 'wght' 500, 'GRAD' 0, 'opsz' 24;\n -webkit-font-smoothing: antialiased;\n user-select: none;\n}\n.card {\n display: flex; align-items: center; gap: 10px;\n min-width: 260px; max-width: 360px;\n /* Altura mínima idêntica à de um toast COM título: sem título o card não\n fica \"achatado\" e o conjunto mantém o mesmo ritmo visual. */\n min-height: 56px;\n background: var(--fx-surface-background, #fff);\n color: var(--fx-text-default, #1e293b);\n border: 1px solid var(--fx-border-default, #e2e8f0);\n border-left: 4px solid var(--kind);\n border-radius: var(--fx-radius-md, 8px);\n box-shadow: var(--fx-shadow-lg, 0 10px 30px rgba(0,0,0,.14));\n padding: var(--fx-space-sm, 8px) var(--fx-space-md, 12px);\n font-family: inherit;\n animation: fx-toast-in .25s ease;\n}\n@keyframes fx-toast-in { from { opacity: 0; transform: translateY(-6px); } }\n.card.leaving { opacity: 0; transition: opacity .2s ease; }\n.icon {\n width: 22px; height: 22px; border-radius: 50%;\n background: var(--kind);\n color: #fff;\n display: flex; align-items: center; justify-content: center;\n font-size: 12px; font-weight: 700;\n flex: none; align-self: center;\n user-select: none;\n}\n.body { flex: 1; min-width: 0; align-self: center; }\n.title { font-weight: 600; font-size: 14px; }\n.msg {\n font-size: calc(var(--fx-font-size, 14px) - 2px);\n color: var(--fx-text-muted, #64748b);\n margin-top: 2px;\n word-break: break-word; white-space: pre-line;\n /* Mensagens longas mantêm o card no tamanho padrão e terminam em \"…\";\n \"Ver mais\" expande o toast (sem perder o restante do texto). */\n display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;\n line-clamp: 2; overflow: hidden;\n}\n.card.expanded .msg {\n display: block; overflow: visible;\n -webkit-line-clamp: unset; line-clamp: unset;\n}\n.more {\n all: unset; cursor: pointer; display: inline-block;\n margin-top: 4px; padding: 2px 0;\n font-size: 11px; font-weight: 600; color: var(--kind);\n}\n.more[hidden] { display: none; }\n.more:hover { text-decoration: underline; }\n.actions {\n display: flex; align-items: center; gap: 2px;\n flex: none; align-self: flex-start;\n}\n.copy, .close {\n all: unset; cursor: pointer; flex: none; line-height: 1;\n color: var(--fx-text-muted, #64748b); font-size: 15px; padding: 2px 4px; border-radius: 4px;\n}\n.copy[hidden] { display: none; }\n.copy { font-size: 14px; display: inline-flex; align-items: center; }\n.copy svg { display: block; }\n.copy.done { color: var(--fx-color-success, #10b981); }\n.copy:hover { color: var(--fx-color-primary, #6366f1); background: var(--fx-surface-surface-hover, rgba(0,0,0,.05)); }\n.close:hover { color: var(--fx-color-danger, #f43f5e); background: var(--fx-surface-surface-hover, rgba(0,0,0,.05)); }\n\n/* --- Barra de contagem regressiva (atributo progress) ---\n Fica na base do card e esvazia durante a duração, na cor do kind (--kind) —\n ou seja, acompanha success/error/warning/info sem configuração extra.\n\n O lado é configurável pelo atributo progress-origin (padrão: left):\n left → a barra drena da direita para a esquerda (ancorada à esquerda)\n right → a barra drena da esquerda para a direita (ancorada à direita)\n A origem entra como custom property, então os keyframes são compartilhados.\n\n Usa escalaX em vez de width: a animação escreve só na compositor, sem\n reflow por frame. */\n.card { position: relative; overflow: hidden; }\n.progress {\n position: absolute;\n left: 0;\n bottom: 0;\n width: 100%;\n height: 3px;\n background: var(--kind);\n transform-origin: var(--toast-progress-origin, left center);\n transform: scaleX(1);\n border-radius: 0 0 var(--fx-radius-md, 8px) var(--fx-radius-md, 8px);\n pointer-events: none;\n will-change: transform;\n}\n.progress.run { animation: fx-toast-drain var(--toast-duration, 4000ms) linear forwards; }\n/* Passar o mouse (ou focar) no toast pausa a contagem: a barra e o timer\n congelam juntos até o cursor sair — tempo de ler a mensagem inteira.\n progress=\"hover\" é mantido por compatibilidade e se comporta como\n \"always\" (o hover já pausa a contagem). */\n.card:hover .progress.run,\n.card:focus-within .progress.run { animation-play-state: paused; }\n@keyframes fx-toast-drain {\n from { transform: scaleX(1); }\n to { transform: scaleX(0); }\n}\n/* A barra só começa a correr no 2º frame: no 1º ela já apareceria zerada\n (o elemento acabou de ser inserido e a animação aplica o estado final). */\n.card.entering .progress { animation: none; }\n/* Ao dispensar, congela a barra: ela some junto com o card em .2s. */\n.card.leaving .progress { animation-play-state: paused; }\n@media (prefers-reduced-motion: reduce) {\n .progress.run { animation-duration: 0.01ms; }\n}\n`;\n\nexport class FxToast extends HTMLElement {\n static get observedAttributes() {\n return ['kind', 'title', 'message', 'duration', 'icon', 'mode', 'progress', 'progress-origin'];\n }\n\n /**\n * Exibe a barra de contagem regressiva na base do card.\n *\n * A barra esvazia durante exatamente `duration` e usa a cor do `kind`.\n * Com `duration=\"0\"` (toast fixo) a barra é omitida — não há tempo restante.\n *\n * - `'always'` (ou atributo vazio): barra sempre correndo.\n * - `'paused'`: barra visível, porém parada.\n * - `'hover'`: **compatibilidade** — equivale a `'always'`. O hover agora\n * pausa a contagem (timer + barra) em qualquer modo, então a barra \"só\n * correr sob o cursor\" deixou de fazer sentido.\n */\n get progress(): 'always' | 'hover' | 'paused' | undefined {\n if (!this.hasAttribute('progress')) return undefined;\n const v = this.getAttribute('progress');\n return v === 'paused' ? 'paused' : 'always';\n }\n set progress(value: 'always' | 'hover' | 'paused' | undefined) {\n if (value) this.setAttribute('progress', value);\n else this.removeAttribute('progress');\n }\n\n /**\n * Lado a partir do qual a barra esvazia.\n *\n * - `'left'` (padrão): a barra fica ancorada à esquerda e drena da direita\n * para a esquerda.\n * - `'right'`: a barra fica ancorada à direita e drena da esquerda para a\n * direita (espelhado).\n *\n * Só tem efeito junto com `progress`; qualquer valor fora da lista cai em\n * `'left'`.\n */\n get progressOrigin(): 'left' | 'right' {\n return this.getAttribute('progress-origin') === 'right' ? 'right' : 'left';\n }\n set progressOrigin(value: 'left' | 'right') {\n this.setAttribute('progress-origin', value);\n }\n\n /** Duração efetiva em ms (0 = fixo). Mesma regra usada no timer. */\n private get safeDuration(): number {\n const dur = Number(this.getAttribute('duration') ?? '4000');\n if (dur === 0) return 0;\n return Math.max(Number.isNaN(dur) ? 4000 : dur, 1000);\n }\n\n /** Esquema de cores forçado do card: 'light' | 'dark' | undefined (segue o tema). */\n get mode(): 'light' | 'dark' | undefined {\n const m = this.getAttribute('mode');\n return m === 'light' || m === 'dark' ? m : undefined;\n }\n set mode(value: 'light' | 'dark' | undefined) {\n if (value) this.setAttribute('mode', value);\n else this.removeAttribute('mode');\n }\n\n private timer: ReturnType<typeof setTimeout> | null = null;\n /** Tempo restante (ms) quando a contagem está pausada no hover/foco. */\n private remaining = 0;\n /** Instante em que o timer atual começou (para descontar o tempo decorrido). */\n private startedAt = 0;\n /** Cursor sobre o toast: não retoma a contagem no focusout. */\n private hovering = false;\n /** Já saiu do DOM — impede retomar a contagem. */\n private disposed = false;\n /** Existe contagem regressiva (duration > 0) — toasts fixos nunca retomam. */\n private counting = false;\n /** Listeners de pausa/retomada registrados uma única vez. */\n private wired = false;\n\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n }\n\n connectedCallback() {\n if (!this.shadowRoot!.firstChild) this.#render();\n this.disposed = false;\n this.#wirePause();\n // Durações mínimas: valores muito baixos (ex.: digitação parcial no\n // playground) são elevados para 1s; 0 permanece fixo até fechar.\n const safe = this.safeDuration;\n this.counting = safe > 0;\n if (safe > 0 && !this.timer) this.#startTimer(safe);\n this.#syncProgress();\n // jsdom não calcula layout: o \"Ver mais\" fica oculto (scrollHeight 0).\n this.#syncMore();\n }\n\n /**\n * Pausa a contagem enquanto o cursor (ou o foco) estiver no toast e retoma\n * de onde parou ao sair — assim uma mensagem longa pode ser lida inteira.\n * A barra de progresso pausa junto (regra CSS de :hover/:focus-within).\n */\n #wirePause(): void {\n if (this.wired) return;\n this.wired = true;\n this.addEventListener('mouseenter', () => {\n this.hovering = true;\n this.#pauseTimer();\n });\n this.addEventListener('mouseleave', () => {\n this.hovering = false;\n this.#resumeTimer();\n });\n // Teclado (botões copiar/fechar/ver mais) também mantém o toast na tela.\n this.addEventListener('focusin', () => this.#pauseTimer());\n this.addEventListener('focusout', () => {\n if (!this.hovering) this.#resumeTimer();\n });\n }\n\n #startTimer(ms: number): void {\n this.remaining = ms;\n this.startedAt = Date.now();\n this.timer = setTimeout(() => {\n this.timer = null;\n this.dismiss();\n }, ms);\n }\n\n #pauseTimer(): void {\n if (!this.timer) return;\n clearTimeout(this.timer);\n this.timer = null;\n this.remaining = Math.max(0, this.remaining - (Date.now() - this.startedAt));\n }\n\n #resumeTimer(): void {\n if (!this.counting || this.timer || this.disposed) return;\n if (this.remaining <= 0) {\n this.dismiss();\n return;\n }\n this.#startTimer(this.remaining);\n }\n\n /**\n * Limpa recursos ao sair do DOM e avisa a API imperativa (`FenixToast`).\n *\n * O aviso é essencial: `dismiss()` (timer ou botão de fechar) tira o\n * elemento do DOM mas, sem isto, `ToastApi.map` continuaria segurando uma\n * referência forte para cada toast já dispensado — crescimento de memória\n * sem limite em uso prolongado.\n */\n disconnectedCallback() {\n this.disposed = true;\n if (this.timer) clearTimeout(this.timer);\n this.timer = null;\n this.dispatchEvent(new CustomEvent(REMOVED_EVENT, { bubbles: false, composed: false }));\n }\n\n attributeChangedCallback(name: string) {\n // A duração é lida em dois lugares (timer + barra): se mudar com o toast\n // na tela, ambos precisam se realinhar.\n if (name === 'duration') {\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n const safe = this.safeDuration;\n this.counting = safe > 0;\n if (safe > 0 && this.isConnected && !this.disposed) this.#startTimer(safe);\n }\n if (name === 'duration' || name === 'progress' || name === 'progress-origin' || name === 'kind') {\n this.#syncProgress();\n }\n }\n\n /**\n * Aplica (ou remove) a barra de contagem regressiva.\n *\n * A barra só existe com `progress` e `duration > 0`. O tempo da animação\n * vem de `--toast-duration`, sempre igual ao do `setTimeout` que fecha o\n * toast — assim a barra e o desaparecimento nunca desalinham.\n */\n #syncProgress(): void {\n const card = this.shadowRoot?.querySelector<HTMLElement>('.card');\n if (!card) return;\n const bar = this.shadowRoot!.querySelector<HTMLElement>('.progress');\n const mode = this.progress;\n const dur = this.safeDuration;\n const shouldShow = !!mode && dur > 0;\n\n if (!shouldShow) {\n bar?.remove();\n card.classList.remove('entering');\n return;\n }\n if (!bar) {\n this.shadowRoot!.querySelector('.card')!.insertAdjacentHTML('beforeend', '<span class=\"progress\" part=\"progress\" aria-hidden=\"true\"></span>');\n }\n const el = this.shadowRoot!.querySelector<HTMLElement>('.progress')!;\n el.style.setProperty('--toast-duration', `${dur}ms`);\n // `left` (padrão) => origem à esquerda; `right` espelha a drenagem.\n el.style.setProperty('--toast-progress-origin', `${this.progressOrigin} center`);\n el.classList.remove('run');\n // `paused` mantém a barra cheia e imóvel; os demais modos animam\n // (o hover pausa a animação junto com o timer, via CSS).\n if (mode !== 'paused') el.classList.add('run');\n // 1 frame sem animação para o browser registrar o estado inicial cheio\n // antes de a barra começar a esvaziar.\n card.classList.add('entering');\n requestAnimationFrame(() => requestAnimationFrame(() => card.classList.remove('entering')));\n }\n\n dismiss() {\n if (this.timer) clearTimeout(this.timer);\n this.timer = null;\n const card = this.shadowRoot!.querySelector('.card');\n if (card) {\n card.classList.add('leaving');\n setTimeout(() => this.remove(), 200);\n } else {\n this.remove();\n }\n }\n\n #render() {\n const kind = (this.getAttribute('kind') || 'info') as ToastKind;\n const title = this.getAttribute('title') ?? '';\n const msg = this.getAttribute('message') ?? '';\n const color = KIND_COLOR[kind] ?? KIND_COLOR.info;\n const iconName = this.getAttribute('icon');\n const isFxIcon = !!iconName && /^[a-z][a-z0-9_]*$/.test(iconName);\n if (isFxIcon) void import('../../icons');\n const iconContent = isFxIcon ? iconName : KIND_ICON[kind] ?? KIND_ICON.info;\n this.shadowRoot!.innerHTML = `\n <style>${CARD_CSS}</style>\n <div class=\"card\" style=\"--kind:${color}\" role=\"status\" part=\"card\">\n <span class=\"icon${isFxIcon ? ' fx-icon' : ''}\">${iconContent}</span>\n <div class=\"body\">\n ${title ? '<div class=\"title\" part=\"title\"></div>' : ''}\n ${msg ? '<div class=\"msg\" part=\"message\"></div>' : ''}\n ${msg ? '<button class=\"more\" part=\"more\" hidden>Ver mais</button>' : ''}\n </div>\n <div class=\"actions\">\n ${msg ? `<button class=\"copy\" part=\"copy\" hidden aria-label=\"Copiar mensagem\" title=\"Copiar mensagem\">${COPY_SVG}</button>` : ''}\n <button class=\"close\" aria-label=\"Fechar\" title=\"Fechar\">✕</button>\n </div>\n </div>`;\n const t = this.shadowRoot!.querySelector<HTMLElement>('.title');\n const m = this.shadowRoot!.querySelector<HTMLElement>('.msg');\n if (t) t.textContent = title;\n if (m) {\n m.textContent = msg;\n // Dica nativa com o texto completo: passar o mouse na mensagem mostra\n // tudo, mesmo quando o card está truncado em 2 linhas.\n m.title = msg;\n }\n const card = this.shadowRoot!.querySelector<HTMLElement>('.card')!;\n const more = this.shadowRoot!.querySelector<HTMLButtonElement>('.more');\n const copy = this.shadowRoot!.querySelector<HTMLButtonElement>('.copy');\n more?.addEventListener('click', () => {\n const expanded = card.classList.toggle('expanded');\n more.textContent = expanded ? 'Ver menos' : 'Ver mais';\n // O botão de copiar só aparece com o texto aberto (não polui o card no\n // tamanho padrão). Enquanto o texto está expandido o toast não fecha.\n if (copy) copy.hidden = !expanded;\n if (expanded) this.#pauseTimer();\n });\n copy?.addEventListener('click', (e) => {\n const btn = e.currentTarget as HTMLButtonElement;\n const text = [title, msg].filter(Boolean).join('\\n');\n navigator.clipboard?.writeText(text).catch(() => { /* clipboard indisponível */ });\n // Feedback: check verde + dica \"Copiado!\" por 1,2s.\n btn.classList.add('done');\n btn.title = 'Copiado!';\n setTimeout(() => {\n btn.classList.remove('done');\n btn.title = 'Copiar mensagem';\n }, 1200);\n });\n this.shadowRoot!.querySelector('.close')!.addEventListener('click', () => this.dismiss());\n }\n\n /**\n * Mostra \"Ver mais\" somente quando a mensagem passou de 2 linhas.\n * Sem layout (jsdom) scrollHeight/clientHeight são 0 → fica oculto.\n */\n #syncMore(): void {\n const card = this.shadowRoot?.querySelector<HTMLElement>('.card');\n const more = this.shadowRoot?.querySelector<HTMLButtonElement>('.more');\n const msg = this.shadowRoot?.querySelector<HTMLElement>('.msg');\n if (!card || !more || !msg) return;\n if (card.classList.contains('expanded')) {\n more.hidden = false;\n return;\n }\n more.hidden = !(msg.scrollHeight - msg.clientHeight > 1);\n }\n}\n\n/* ---------- Regiões por posição na tela ---------- */\nfunction regionFor(position: ToastPosition): HTMLElement {\n let region = document.querySelector<HTMLElement>(`[data-fx-toast-region=\"${position}\"]`);\n if (!region) {\n region = document.createElement('div');\n region.setAttribute('data-fx-toast-region', position);\n const [vertical, horizontal] = position.split('-') as [string, string];\n const style = region.style as CSSStyleDeclaration & Record<string, string>;\n style.position = 'fixed';\n style[vertical] = '16px';\n if (horizontal === 'center') {\n style.left = '50%';\n style.transform = 'translateX(-50%)';\n } else {\n style[horizontal] = '16px';\n }\n style.display = 'flex';\n style.flexDirection = 'column';\n style.gap = '8px';\n style.zIndex = '1100';\n style.pointerEvents = 'none';\n // Os cards reabilitam pointer-events via `:host { pointer-events: auto }` (herança),\n // sem necessidade de MutationObserver.\n document.body.appendChild(region);\n }\n return region;\n}\n\nexport interface ToastOptions {\n /** Texto secundário (também aceito como 2º argumento dos métodos). */\n message?: string;\n position?: ToastPosition;\n /** Tempo em ms até sumir. 0 = fixo até fechar. Padrão: 4000 */\n duration?: number;\n /**\n * Barra de contagem regressiva na base do card, na cor do `kind`.\n * `'always'` (padrão) corre sempre; `'hover'` só enquanto o cursor estiver\n * sobre o toast; `'paused'` exibe a barra cheia, porém imóvel.\n * Sem efeito com `duration: 0` (não há tempo restante a representar).\n */\n progress?: 'always' | 'hover' | 'paused';\n /**\n * Lado a partir do qual a barra esvazia. `'left'` (padrão) deixa a barra\n * ancorada à esquerda; `'right'` a ancora à direita (espelhado).\n * Só tem efeito junto com `progress`.\n */\n progressOrigin?: 'left' | 'right';\n /** Força o esquema de cores do card, independentemente do tema global. */\n mode?: 'light' | 'dark';\n}\n\nclass ToastApi {\n private seq = 0;\n private map = new Map<number, FxToast>();\n\n push(kind: ToastKind, title: string, messageOrOpts?: string | ToastOptions, opts?: ToastOptions): number {\n const id = ++this.seq;\n // Assinaturas suportadas:\n // push(kind, title, options)\n // push(kind, title, message, options) <- usada pela doc e mais intuitiva\n const message = typeof messageOrOpts === 'string' ? messageOrOpts : messageOrOpts?.message;\n const o: ToastOptions = { ...(typeof messageOrOpts === 'object' && messageOrOpts ? messageOrOpts : undefined), ...opts };\n const el = document.createElement('fx-toast') as FxToast;\n el.setAttribute('kind', kind);\n el.setAttribute('title', title);\n if (message) el.setAttribute('message', message);\n el.setAttribute('position', o.position ?? 'top-right');\n el.setAttribute('duration', String(o.duration ?? 4000));\n if (o.mode) el.setAttribute('mode', o.mode);\n if (o.progress) el.setAttribute('progress', o.progress);\n if (o.progressOrigin) el.setAttribute('progress-origin', o.progressOrigin);\n regionFor(o.position ?? 'top-right').appendChild(el);\n // O toast avisa quando sai do DOM (timer esgotado, botão de fechar ou\n // remoção externa). Sem este sync o mapa retém cada toast da sessão.\n el.addEventListener(REMOVED_EVENT, () => {\n if (this.map.get(id) === el) this.map.delete(id);\n });\n this.map.set(id, el);\n return id;\n }\n\n close(id: number) {\n this.map.get(id)?.dismiss();\n this.map.delete(id);\n }\n\n success = (t: string, m?: string, o?: ToastOptions) => this.push('success', t, m, o);\n error = (t: string, m?: string, o?: ToastOptions) => this.push('error', t, m, o);\n info = (t: string, m?: string, o?: ToastOptions) => this.push('info', t, m, o);\n warning = (t: string, m?: string, o?: ToastOptions) => this.push('warning', t, m, o);\n}\n\nexport const FenixToast = new ToastApi();\n// Exposto globalmente para uso em HTML puro (onclick=\"FenixToast.success(...)\")\n(globalThis as unknown as Record<string, unknown>).FenixToast = FenixToast;\n\nexport function defineFxToast() {\n if (!customElements.get('fx-toast')) customElements.define('fx-toast', FxToast);\n}\n\n"],"file":"components/toast/toast.js"}
1
+ {"version":3,"mappings":"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","names":[],"ignoreList":[],"sources":["../../../src/components/toast/toast.ts"],"sourcesContent":["/**\n * <fx-toast> — notificações imperativas com posição e duração configuráveis.\n *\n * Atributos: kind (success|error|info|warning), title, message,\n * position (top-left|top-center|top-right|bottom-left|bottom-center|bottom-right),\n * duration (ms; 0 = fixo até fechar), mode ('light'|'dark' — força o esquema de\n * cores do card independentemente do tema global), progress (booleano ou\n * 'always'|'hover'|'paused' — barra de contagem regressiva na cor do kind),\n * progress-origin (left|right — lado pelo qual a barra esvazia; padrão left).\n */\nconst POSITIONS = [\n \"top-left\",\n \"top-center\",\n \"top-right\",\n \"bottom-left\",\n \"bottom-center\",\n \"bottom-right\",\n] as const;\nexport type ToastPosition = (typeof POSITIONS)[number];\ntype ToastKind = \"success\" | \"error\" | \"info\" | \"warning\";\n\n/**\n * Evento interno disparado quando o <fx-toast> sai do DOM.\n * A API `FenixToast` o escuta para remover a entrada do mapa interno —\n * é o que impede o acúmulo de toasts já dispensados.\n */\nconst REMOVED_EVENT = \"fx-toast:removed\";\n\nconst KIND_COLOR: Record<ToastKind, string> = {\n success: \"var(--fx-color-success, #10b981)\",\n error: \"var(--fx-color-danger, #f43f5e)\",\n info: \"var(--fx-color-info, #0ea5e9)\",\n warning: \"var(--fx-color-warning, #f59e0b)\",\n};\n\n/** Ícone exibido no lugar da bolinha, conforme a variante. */\nconst KIND_ICON: Record<ToastKind, string> = {\n success: \"✓\",\n error: \"✕\",\n warning: \"!\",\n info: \"i\",\n};\n\n/** Ícone de copiar (SVG inline: não depende da fonte de ícones). */\nconst COPY_SVG =\n '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"9\" y=\"9\" width=\"12\" height=\"12\" rx=\"2\"></rect><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"></path></svg>';\n\nconst CARD_CSS = `\n:host { display: contents; pointer-events: auto; }\n/* Modo explícito: sobrepõe os tokens herdados do tema global SOMENTE neste card. */\n:host([mode='dark']) {\n --fx-surface-background: #1e293b;\n --fx-text-default: #f1f5f9;\n --fx-text-muted: #94a3b8;\n --fx-border-default: #334155;\n --fx-shadow-lg: 0 10px 30px rgba(0, 0, 0, .5);\n --fx-surface-surface-hover: rgba(255, 255, 255, .08);\n}\n:host([mode='light']) {\n --fx-surface-background: #ffffff;\n --fx-text-default: #1e293b;\n --fx-text-muted: #64748b;\n --fx-border-default: #e2e8f0;\n --fx-shadow-lg: 0 10px 30px rgba(0, 0, 0, .14);\n --fx-surface-surface-hover: rgba(0, 0, 0, .05);\n}\n.fx-icon {\n font-family: var(--fx-icon-font, 'Fenix Icons');\n font-weight: normal;\n font-style: normal;\n line-height: 1;\n letter-spacing: normal;\n display: inline-block;\n white-space: nowrap;\n direction: ltr;\n font-variation-settings: 'FILL' 0, 'wght' 500, 'GRAD' 0, 'opsz' 24;\n -webkit-font-smoothing: antialiased;\n user-select: none;\n}\n.card {\n display: flex; align-items: center; gap: 10px;\n min-width: 260px; max-width: 360px;\n /* Altura mínima idêntica à de um toast COM título: sem título o card não\n fica \"achatado\" e o conjunto mantém o mesmo ritmo visual. */\n min-height: 56px;\n background: var(--fx-surface-background, #fff);\n color: var(--fx-text-default, #1e293b);\n border: 1px solid var(--fx-border-default, #e2e8f0);\n border-left: 4px solid var(--kind);\n border-radius: var(--fx-radius-md, 8px);\n box-shadow: var(--fx-shadow-lg, 0 10px 30px rgba(0,0,0,.14));\n padding: var(--fx-space-xs, 8px) var(--fx-space-md, 12px);\n font-family: inherit;\n animation: fx-toast-in .25s ease;\n}\n@keyframes fx-toast-in { from { opacity: 0; transform: translateY(-6px); } }\n.card.leaving { opacity: 0; transition: opacity .2s ease; }\n.card.no-title { min-height: 48px; }\n.icon {\n width: 22px; height: 22px; border-radius: 50%;\n background: var(--kind);\n color: #fff;\n display: flex; align-items: center; justify-content: center;\n font-size: 12px; font-weight: 700;\n flex: none; align-self: center;\n user-select: none;\n}\n.body { flex: 1; min-width: 0; align-self: center; }\n.title { font-weight: 600; font-size: 14px; }\n.msg {\n font-size: calc(var(--fx-font-size, 14px) - 2px);\n color: var(--fx-text-muted, #64748b);\n margin-top: 2px;\n word-break: break-word; white-space: pre-line;\n /* Mensagens longas mantêm o card no tamanho padrão e terminam em \"…\";\n \"Ver mais\" expande o toast (sem perder o restante do texto). */\n display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;\n line-clamp: 2; overflow: hidden;\n}\n.card.expanded .msg {\n display: block; overflow: visible;\n -webkit-line-clamp: unset; line-clamp: unset;\n}\n.more {\n all: unset; cursor: pointer; display: inline-block;\n margin-top: 4px; padding: 2px 0;\n font-size: 11px; font-weight: 600; color: var(--kind);\n}\n.more[hidden] { display: none; }\n.more:hover { text-decoration: underline; }\n.actions {\n display: flex; align-items: center; gap: 2px;\n flex: none; align-self: flex-start;\n}\n.copy, .close {\n all: unset; cursor: pointer; flex: none; line-height: 1;\n color: var(--fx-text-muted, #64748b); font-size: 15px; padding: 2px 4px; border-radius: 4px;\n}\n.copy[hidden] { display: none; }\n.copy { font-size: 14px; display: inline-flex; align-items: center; }\n.copy svg { display: block; }\n.copy.done { color: var(--fx-color-success, #10b981); }\n.copy:hover { color: var(--fx-color-primary, #6366f1); background: var(--fx-surface-surface-hover, rgba(0,0,0,.05)); }\n.close:hover { color: var(--fx-color-danger, #f43f5e); background: var(--fx-surface-surface-hover, rgba(0,0,0,.05)); }\n\n/* --- Barra de contagem regressiva (atributo progress) ---\n Fica na base do card e esvazia durante a duração, na cor do kind (--kind) —\n ou seja, acompanha success/error/warning/info sem configuração extra.\n\n O lado é configurável pelo atributo progress-origin (padrão: left):\n left → a barra drena da direita para a esquerda (ancorada à esquerda)\n right → a barra drena da esquerda para a direita (ancorada à direita)\n A origem entra como custom property, então os keyframes são compartilhados.\n\n Usa escalaX em vez de width: a animação escreve só na compositor, sem\n reflow por frame. */\n.card { position: relative; overflow: hidden; }\n.progress {\n position: absolute;\n left: 0;\n bottom: 0;\n width: 100%;\n height: 3px;\n background: var(--kind);\n transform-origin: var(--toast-progress-origin, left center);\n transform: scaleX(1);\n border-radius: 0 0 var(--fx-radius-md, 8px) var(--fx-radius-md, 8px);\n pointer-events: none;\n will-change: transform;\n}\n.progress.run { animation: fx-toast-drain var(--toast-duration, 4000ms) linear forwards; }\n/* Passar o mouse (ou focar) no toast pausa a contagem: a barra e o timer\n congelam juntos até o cursor sair — tempo de ler a mensagem inteira.\n progress=\"hover\" é mantido por compatibilidade e se comporta como\n \"always\" (o hover já pausa a contagem). */\n.card:hover .progress.run,\n.card:focus-within .progress.run { animation-play-state: paused; }\n@keyframes fx-toast-drain {\n from { transform: scaleX(1); }\n to { transform: scaleX(0); }\n}\n/* A barra só começa a correr no 2º frame: no 1º ela já apareceria zerada\n (o elemento acabou de ser inserido e a animação aplica o estado final). */\n.card.entering .progress { animation: none; }\n/* Ao dispensar, congela a barra: ela some junto com o card em .2s. */\n.card.leaving .progress { animation-play-state: paused; }\n@media (prefers-reduced-motion: reduce) {\n .progress.run { animation-duration: 0.01ms; }\n}\n`;\n\nexport class FxToast extends HTMLElement {\n static get observedAttributes() {\n return [\n \"kind\",\n \"title\",\n \"message\",\n \"duration\",\n \"icon\",\n \"mode\",\n \"progress\",\n \"progress-origin\",\n ];\n }\n\n /**\n * Exibe a barra de contagem regressiva na base do card.\n *\n * A barra esvazia durante exatamente `duration` e usa a cor do `kind`.\n * Com `duration=\"0\"` (toast fixo) a barra é omitida — não há tempo restante.\n *\n * - `'always'` (ou atributo vazio): barra sempre correndo.\n * - `'paused'`: barra visível, porém parada.\n * - `'hover'`: **compatibilidade** — equivale a `'always'`. O hover agora\n * pausa a contagem (timer + barra) em qualquer modo, então a barra \"só\n * correr sob o cursor\" deixou de fazer sentido.\n */\n get progress(): \"always\" | \"hover\" | \"paused\" | undefined {\n if (!this.hasAttribute(\"progress\")) return undefined;\n const v = this.getAttribute(\"progress\");\n return v === \"paused\" ? \"paused\" : \"always\";\n }\n set progress(value: \"always\" | \"hover\" | \"paused\" | undefined) {\n if (value) this.setAttribute(\"progress\", value);\n else this.removeAttribute(\"progress\");\n }\n\n /**\n * Lado a partir do qual a barra esvazia.\n *\n * - `'left'` (padrão): a barra fica ancorada à esquerda e drena da direita\n * para a esquerda.\n * - `'right'`: a barra fica ancorada à direita e drena da esquerda para a\n * direita (espelhado).\n *\n * Só tem efeito junto com `progress`; qualquer valor fora da lista cai em\n * `'left'`.\n */\n get progressOrigin(): \"left\" | \"right\" {\n return this.getAttribute(\"progress-origin\") === \"right\" ? \"right\" : \"left\";\n }\n set progressOrigin(value: \"left\" | \"right\") {\n this.setAttribute(\"progress-origin\", value);\n }\n\n /** Duração efetiva em ms (0 = fixo). Mesma regra usada no timer. */\n private get safeDuration(): number {\n const dur = Number(this.getAttribute(\"duration\") ?? \"4000\");\n if (dur === 0) return 0;\n return Math.max(Number.isNaN(dur) ? 4000 : dur, 1000);\n }\n\n /** Esquema de cores forçado do card: 'light' | 'dark' | undefined (segue o tema). */\n get mode(): \"light\" | \"dark\" | undefined {\n const m = this.getAttribute(\"mode\");\n return m === \"light\" || m === \"dark\" ? m : undefined;\n }\n set mode(value: \"light\" | \"dark\" | undefined) {\n if (value) this.setAttribute(\"mode\", value);\n else this.removeAttribute(\"mode\");\n }\n\n private timer: ReturnType<typeof setTimeout> | null = null;\n /** Tempo restante (ms) quando a contagem está pausada no hover/foco. */\n private remaining = 0;\n /** Instante em que o timer atual começou (para descontar o tempo decorrido). */\n private startedAt = 0;\n /** Cursor sobre o toast: não retoma a contagem no focusout. */\n private hovering = false;\n /** Já saiu do DOM — impede retomar a contagem. */\n private disposed = false;\n /** Existe contagem regressiva (duration > 0) — toasts fixos nunca retomam. */\n private counting = false;\n /** Listeners de pausa/retomada registrados uma única vez. */\n private wired = false;\n\n constructor() {\n super();\n this.attachShadow({ mode: \"open\" });\n }\n\n connectedCallback() {\n if (!this.shadowRoot!.firstChild) this.#render();\n this.disposed = false;\n this.#wirePause();\n // Durações mínimas: valores muito baixos (ex.: digitação parcial no\n // playground) são elevados para 1s; 0 permanece fixo até fechar.\n const safe = this.safeDuration;\n this.counting = safe > 0;\n if (safe > 0 && !this.timer) this.#startTimer(safe);\n this.#syncProgress();\n // jsdom não calcula layout: o \"Ver mais\" fica oculto (scrollHeight 0).\n this.#syncMore();\n }\n\n /**\n * Pausa a contagem enquanto o cursor (ou o foco) estiver no toast e retoma\n * de onde parou ao sair — assim uma mensagem longa pode ser lida inteira.\n * A barra de progresso pausa junto (regra CSS de :hover/:focus-within).\n */\n #wirePause(): void {\n if (this.wired) return;\n this.wired = true;\n this.addEventListener(\"mouseenter\", () => {\n this.hovering = true;\n this.#pauseTimer();\n });\n this.addEventListener(\"mouseleave\", () => {\n this.hovering = false;\n this.#resumeTimer();\n });\n // Teclado (botões copiar/fechar/ver mais) também mantém o toast na tela.\n this.addEventListener(\"focusin\", () => this.#pauseTimer());\n this.addEventListener(\"focusout\", () => {\n if (!this.hovering) this.#resumeTimer();\n });\n }\n\n #startTimer(ms: number): void {\n this.remaining = ms;\n this.startedAt = Date.now();\n this.timer = setTimeout(() => {\n this.timer = null;\n this.dismiss();\n }, ms);\n }\n\n #pauseTimer(): void {\n if (!this.timer) return;\n clearTimeout(this.timer);\n this.timer = null;\n this.remaining = Math.max(\n 0,\n this.remaining - (Date.now() - this.startedAt),\n );\n }\n\n #resumeTimer(): void {\n if (!this.counting || this.timer || this.disposed) return;\n if (this.remaining <= 0) {\n this.dismiss();\n return;\n }\n this.#startTimer(this.remaining);\n }\n\n /**\n * Limpa recursos ao sair do DOM e avisa a API imperativa (`FenixToast`).\n *\n * O aviso é essencial: `dismiss()` (timer ou botão de fechar) tira o\n * elemento do DOM mas, sem isto, `ToastApi.map` continuaria segurando uma\n * referência forte para cada toast já dispensado — crescimento de memória\n * sem limite em uso prolongado.\n */\n disconnectedCallback() {\n this.disposed = true;\n if (this.timer) clearTimeout(this.timer);\n this.timer = null;\n this.dispatchEvent(\n new CustomEvent(REMOVED_EVENT, { bubbles: false, composed: false }),\n );\n }\n\n attributeChangedCallback(name: string) {\n // A duração é lida em dois lugares (timer + barra): se mudar com o toast\n // na tela, ambos precisam se realinhar.\n if (name === \"duration\") {\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n const safe = this.safeDuration;\n this.counting = safe > 0;\n if (safe > 0 && this.isConnected && !this.disposed)\n this.#startTimer(safe);\n }\n if (\n name === \"duration\" ||\n name === \"progress\" ||\n name === \"progress-origin\" ||\n name === \"kind\"\n ) {\n this.#syncProgress();\n }\n }\n\n /**\n * Aplica (ou remove) a barra de contagem regressiva.\n *\n * A barra só existe com `progress` e `duration > 0`. O tempo da animação\n * vem de `--toast-duration`, sempre igual ao do `setTimeout` que fecha o\n * toast — assim a barra e o desaparecimento nunca desalinham.\n */\n #syncProgress(): void {\n const card = this.shadowRoot?.querySelector<HTMLElement>(\".card\");\n if (!card) return;\n const bar = this.shadowRoot!.querySelector<HTMLElement>(\".progress\");\n const mode = this.progress;\n const dur = this.safeDuration;\n const shouldShow = !!mode && dur > 0;\n\n if (!shouldShow) {\n bar?.remove();\n card.classList.remove(\"entering\");\n return;\n }\n if (!bar) {\n this.shadowRoot!.querySelector(\".card\")!.insertAdjacentHTML(\n \"beforeend\",\n '<span class=\"progress\" part=\"progress\" aria-hidden=\"true\"></span>',\n );\n }\n const el = this.shadowRoot!.querySelector<HTMLElement>(\".progress\")!;\n el.style.setProperty(\"--toast-duration\", `${dur}ms`);\n // `left` (padrão) => origem à esquerda; `right` espelha a drenagem.\n el.style.setProperty(\n \"--toast-progress-origin\",\n `${this.progressOrigin} center`,\n );\n el.classList.remove(\"run\");\n // `paused` mantém a barra cheia e imóvel; os demais modos animam\n // (o hover pausa a animação junto com o timer, via CSS).\n if (mode !== \"paused\") el.classList.add(\"run\");\n // 1 frame sem animação para o browser registrar o estado inicial cheio\n // antes de a barra começar a esvaziar.\n card.classList.add(\"entering\");\n requestAnimationFrame(() =>\n requestAnimationFrame(() => card.classList.remove(\"entering\")),\n );\n }\n\n dismiss() {\n if (this.timer) clearTimeout(this.timer);\n this.timer = null;\n const card = this.shadowRoot!.querySelector(\".card\");\n if (card) {\n card.classList.add(\"leaving\");\n setTimeout(() => this.remove(), 200);\n } else {\n this.remove();\n }\n }\n\n #render() {\n const kind = (this.getAttribute(\"kind\") || \"info\") as ToastKind;\n const title = this.getAttribute(\"title\") ?? \"\";\n const msg = this.getAttribute(\"message\") ?? \"\";\n const color = KIND_COLOR[kind] ?? KIND_COLOR.info;\n const iconName = this.getAttribute(\"icon\");\n const isFxIcon = !!iconName && /^[a-z][a-z0-9_]*$/.test(iconName);\n if (isFxIcon) void import(\"../../icons\");\n const iconContent = isFxIcon\n ? iconName\n : (KIND_ICON[kind] ?? KIND_ICON.info);\n this.shadowRoot!.innerHTML = `\n <style>${CARD_CSS}</style>\n <div class=\"card${title ? \"\" : \" no-title\"}\" style=\"--kind:${color}\" role=\"status\" part=\"card\">\n <span class=\"icon${isFxIcon ? \" fx-icon\" : \"\"}\">${iconContent}</span>\n <div class=\"body\">\n ${title ? '<div class=\"title\" part=\"title\"></div>' : \"\"}\n ${msg ? '<div class=\"msg\" part=\"message\"></div>' : \"\"}\n ${msg ? '<button class=\"more\" part=\"more\" hidden>Ver mais</button>' : \"\"}\n </div>\n <div class=\"actions\">\n ${msg ? `<button class=\"copy\" part=\"copy\" hidden aria-label=\"Copiar mensagem\" title=\"Copiar mensagem\">${COPY_SVG}</button>` : \"\"}\n <button class=\"close\" aria-label=\"Fechar\" title=\"Fechar\">✕</button>\n </div>\n </div>`;\n const t = this.shadowRoot!.querySelector<HTMLElement>(\".title\");\n const m = this.shadowRoot!.querySelector<HTMLElement>(\".msg\");\n if (t) t.textContent = title;\n if (m) {\n m.textContent = msg;\n // Dica nativa com o texto completo: passar o mouse na mensagem mostra\n // tudo, mesmo quando o card está truncado em 2 linhas.\n m.title = msg;\n }\n const card = this.shadowRoot!.querySelector<HTMLElement>(\".card\")!;\n const more = this.shadowRoot!.querySelector<HTMLButtonElement>(\".more\");\n const copy = this.shadowRoot!.querySelector<HTMLButtonElement>(\".copy\");\n more?.addEventListener(\"click\", () => {\n const expanded = card.classList.toggle(\"expanded\");\n more.textContent = expanded ? \"Ver menos\" : \"Ver mais\";\n // O botão de copiar só aparece com o texto aberto (não polui o card no\n // tamanho padrão). Enquanto o texto está expandido o toast não fecha.\n if (copy) copy.hidden = !expanded;\n if (expanded) this.#pauseTimer();\n });\n copy?.addEventListener(\"click\", (e) => {\n const btn = e.currentTarget as HTMLButtonElement;\n const text = [title, msg].filter(Boolean).join(\"\\n\");\n navigator.clipboard?.writeText(text).catch(() => {\n /* clipboard indisponível */\n });\n // Feedback: check verde + dica \"Copiado!\" por 1,2s.\n btn.classList.add(\"done\");\n btn.title = \"Copiado!\";\n setTimeout(() => {\n btn.classList.remove(\"done\");\n btn.title = \"Copiar mensagem\";\n }, 1200);\n });\n this.shadowRoot!.querySelector(\".close\")!.addEventListener(\"click\", () =>\n this.dismiss(),\n );\n }\n\n /**\n * Mostra \"Ver mais\" somente quando a mensagem passou de 2 linhas.\n * Sem layout (jsdom) scrollHeight/clientHeight são 0 → fica oculto.\n */\n #syncMore(): void {\n const card = this.shadowRoot?.querySelector<HTMLElement>(\".card\");\n const more = this.shadowRoot?.querySelector<HTMLButtonElement>(\".more\");\n const msg = this.shadowRoot?.querySelector<HTMLElement>(\".msg\");\n if (!card || !more || !msg) return;\n if (card.classList.contains(\"expanded\")) {\n more.hidden = false;\n return;\n }\n more.hidden = !(msg.scrollHeight - msg.clientHeight > 1);\n }\n}\n\n/* ---------- Regiões por posição na tela ---------- */\nfunction regionFor(position: ToastPosition): HTMLElement {\n let region = document.querySelector<HTMLElement>(\n `[data-fx-toast-region=\"${position}\"]`,\n );\n if (!region) {\n region = document.createElement(\"div\");\n region.setAttribute(\"data-fx-toast-region\", position);\n const [vertical, horizontal] = position.split(\"-\") as [string, string];\n const style = region.style as CSSStyleDeclaration & Record<string, string>;\n style.position = \"fixed\";\n style[vertical] = \"16px\";\n if (horizontal === \"center\") {\n style.left = \"50%\";\n style.transform = \"translateX(-50%)\";\n } else {\n style[horizontal] = \"16px\";\n }\n style.display = \"flex\";\n style.flexDirection = \"column\";\n style.gap = \"8px\";\n style.zIndex = \"1100\";\n style.pointerEvents = \"none\";\n // Os cards reabilitam pointer-events via `:host { pointer-events: auto }` (herança),\n // sem necessidade de MutationObserver.\n document.body.appendChild(region);\n }\n return region;\n}\n\nexport interface ToastOptions {\n /** Texto secundário (também aceito como 2º argumento dos métodos). */\n message?: string;\n position?: ToastPosition;\n /** Tempo em ms até sumir. 0 = fixo até fechar. Padrão: 4000 */\n duration?: number;\n /**\n * Barra de contagem regressiva na base do card, na cor do `kind`.\n * `'always'` (padrão) corre sempre; `'hover'` só enquanto o cursor estiver\n * sobre o toast; `'paused'` exibe a barra cheia, porém imóvel.\n * Sem efeito com `duration: 0` (não há tempo restante a representar).\n */\n progress?: \"always\" | \"hover\" | \"paused\";\n /**\n * Lado a partir do qual a barra esvazia. `'left'` (padrão) deixa a barra\n * ancorada à esquerda; `'right'` a ancora à direita (espelhado).\n * Só tem efeito junto com `progress`.\n */\n progressOrigin?: \"left\" | \"right\";\n /** Força o esquema de cores do card, independentemente do tema global. */\n mode?: \"light\" | \"dark\";\n}\n\nclass ToastApi {\n private seq = 0;\n private map = new Map<number, FxToast>();\n\n push(\n kind: ToastKind,\n title: string,\n messageOrOpts?: string | ToastOptions,\n opts?: ToastOptions,\n ): number {\n const id = ++this.seq;\n // Assinaturas suportadas:\n // push(kind, title, options)\n // push(kind, title, message, options) <- usada pela doc e mais intuitiva\n const message =\n typeof messageOrOpts === \"string\"\n ? messageOrOpts\n : messageOrOpts?.message;\n const o: ToastOptions = {\n ...(typeof messageOrOpts === \"object\" && messageOrOpts\n ? messageOrOpts\n : undefined),\n ...opts,\n };\n const el = document.createElement(\"fx-toast\") as FxToast;\n el.setAttribute(\"kind\", kind);\n el.setAttribute(\"title\", title);\n if (message) el.setAttribute(\"message\", message);\n el.setAttribute(\"position\", o.position ?? \"top-right\");\n el.setAttribute(\"duration\", String(o.duration ?? 4000));\n if (o.mode) el.setAttribute(\"mode\", o.mode);\n if (o.progress) el.setAttribute(\"progress\", o.progress);\n if (o.progressOrigin) el.setAttribute(\"progress-origin\", o.progressOrigin);\n regionFor(o.position ?? \"top-right\").appendChild(el);\n // O toast avisa quando sai do DOM (timer esgotado, botão de fechar ou\n // remoção externa). Sem este sync o mapa retém cada toast da sessão.\n el.addEventListener(REMOVED_EVENT, () => {\n if (this.map.get(id) === el) this.map.delete(id);\n });\n this.map.set(id, el);\n return id;\n }\n\n close(id: number) {\n this.map.get(id)?.dismiss();\n this.map.delete(id);\n }\n\n success = (t: string, m?: string, o?: ToastOptions) =>\n this.push(\"success\", t, m, o);\n error = (t: string, m?: string, o?: ToastOptions) =>\n this.push(\"error\", t, m, o);\n info = (t: string, m?: string, o?: ToastOptions) =>\n this.push(\"info\", t, m, o);\n warning = (t: string, m?: string, o?: ToastOptions) =>\n this.push(\"warning\", t, m, o);\n}\n\nexport const FenixToast = new ToastApi();\n// Exposto globalmente para uso em HTML puro (onclick=\"FenixToast.success(...)\")\n(globalThis as unknown as Record<string, unknown>).FenixToast = FenixToast;\n\nexport function defineFxToast() {\n if (!customElements.get(\"fx-toast\"))\n customElements.define(\"fx-toast\", FxToast);\n}\n"],"file":"components/toast/toast.js"}