@wrrdev/fenix-ui 1.1.7 → 1.1.9

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 (64) hide show
  1. package/README.md +18 -3
  2. package/dist/assets/{base-css-Be_r8aTo.js → font-CF0TXsAP.js} +39 -3
  3. package/dist/assets/font-CF0TXsAP.js.map +1 -0
  4. package/dist/components/drawer/drawer.d.ts.map +1 -1
  5. package/dist/components/drawer/drawer.js +4 -0
  6. package/dist/components/drawer/drawer.js.map +1 -1
  7. package/dist/components/icon/icon.js +79 -0
  8. package/dist/components/icon/icon.js.map +1 -0
  9. package/dist/components/icon/index.js +7 -0
  10. package/dist/components/icon/index.js.map +1 -0
  11. package/dist/components/pagination/pagination.d.ts +1 -0
  12. package/dist/components/pagination/pagination.d.ts.map +1 -1
  13. package/dist/components/pagination/pagination.js +18 -2
  14. package/dist/components/pagination/pagination.js.map +1 -1
  15. package/dist/components/table/table.d.ts +38 -4
  16. package/dist/components/table/table.d.ts.map +1 -1
  17. package/dist/components/table/table.js +90 -18
  18. package/dist/components/table/table.js.map +1 -1
  19. package/dist/components/toast/toast.d.ts +46 -1
  20. package/dist/components/toast/toast.d.ts.map +1 -1
  21. package/dist/components/toast/toast.js +128 -5
  22. package/dist/components/toast/toast.js.map +1 -1
  23. package/dist/core/jsx.d.ts +63 -1
  24. package/dist/core/jsx.d.ts.map +1 -1
  25. package/dist/core/jsx.js.map +1 -1
  26. package/dist/core/vue.d.ts +7 -1
  27. package/dist/core/vue.d.ts.map +1 -1
  28. package/dist/core/vue.js.map +1 -1
  29. package/dist/docs/app.d.ts +2 -6
  30. package/dist/docs/app.d.ts.map +1 -1
  31. package/dist/docs/componentes/table.doc.d.ts.map +1 -1
  32. package/dist/docs/componentes/toast.doc.d.ts.map +1 -1
  33. package/dist/docs/router.d.ts +17 -0
  34. package/dist/docs/router.d.ts.map +1 -0
  35. package/dist/docs/shell.d.ts +43 -0
  36. package/dist/docs/shell.d.ts.map +1 -0
  37. package/dist/docs/ui/code.d.ts +12 -0
  38. package/dist/docs/ui/code.d.ts.map +1 -0
  39. package/dist/docs/ui/kit.d.ts +18 -0
  40. package/dist/docs/ui/kit.d.ts.map +1 -0
  41. package/dist/docs/ui/tabs.d.ts +23 -0
  42. package/dist/docs/ui/tabs.d.ts.map +1 -0
  43. package/dist/fenix-ui.umd.min.js +199 -116
  44. package/dist/fenix-ui2.esm.js.map +1 -1
  45. package/dist/fenix-ui3.esm.js +5 -36
  46. package/dist/fenix-ui3.esm.js.map +1 -1
  47. package/dist/fenix-ui4.esm.js +326 -45
  48. package/dist/fenix-ui4.esm.js.map +1 -1
  49. package/dist/fenix-ui5.esm.js +44 -16
  50. package/dist/fenix-ui5.esm.js.map +1 -1
  51. package/dist/index.d.ts +2 -0
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +3 -0
  54. package/dist/index.js.map +1 -1
  55. package/dist/plugins/auto-import.d.ts +19 -2
  56. package/dist/plugins/auto-import.d.ts.map +1 -1
  57. package/dist/plugins/auto-import.js +43 -15
  58. package/dist/plugins/auto-import.js.map +1 -1
  59. package/dist/plugins/index.d.ts +5 -0
  60. package/dist/plugins/index.d.ts.map +1 -1
  61. package/dist/plugins/index.js +1 -1
  62. package/dist/plugins/index.js.map +1 -1
  63. package/package.json +3 -2
  64. package/dist/assets/base-css-Be_r8aTo.js.map +0 -1
@@ -4,9 +4,9 @@ var __typeError = (msg) => {
4
4
  var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
5
5
  var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
6
6
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
7
- var _FxToast_instances, render_fn;
8
- import { a as FENIX_ICON_BASE_CSS, f as fenixIconHtml, e as esc, i as isFenixIconName } from "./assets/base-css-Be_r8aTo.js";
9
- import { s, b } from "./assets/base-css-Be_r8aTo.js";
7
+ var _FxToast_instances, syncProgress_fn, render_fn;
8
+ import { a as FENIX_ICON_BASE_CSS, f as fenixIconHtml, e as esc, l as loadFenixIconsFont, i as isFenixIconName } from "./assets/font-CF0TXsAP.js";
9
+ import { s, g } from "./assets/font-CF0TXsAP.js";
10
10
  const lightTokens = {
11
11
  color: {
12
12
  primary: "#c2410c",
@@ -1967,9 +1967,9 @@ const _FxCalendar = class _FxCalendar extends FxElement {
1967
1967
  <div class="grid ${this.view}">${cells}</div>
1968
1968
  `);
1969
1969
  this.root.querySelectorAll("[data-nav]").forEach(
1970
- (b2) => b2.addEventListener(
1970
+ (b) => b.addEventListener(
1971
1971
  "click",
1972
- () => this.navigate(Number(b2.dataset.nav))
1972
+ () => this.navigate(Number(b.dataset.nav))
1973
1973
  )
1974
1974
  );
1975
1975
  const gotoBtn = this.root.querySelector("[data-goto]");
@@ -1978,21 +1978,21 @@ const _FxCalendar = class _FxCalendar extends FxElement {
1978
1978
  this.render();
1979
1979
  });
1980
1980
  this.root.querySelectorAll("[data-day]").forEach(
1981
- (b2) => b2.addEventListener(
1981
+ (b) => b.addEventListener(
1982
1982
  "click",
1983
- () => this.pickDay(Number(b2.dataset.day))
1983
+ () => this.pickDay(Number(b.dataset.day))
1984
1984
  )
1985
1985
  );
1986
1986
  this.root.querySelectorAll("[data-month]").forEach(
1987
- (b2) => b2.addEventListener("click", () => {
1988
- this.cursor.m = Number(b2.dataset.month);
1987
+ (b) => b.addEventListener("click", () => {
1988
+ this.cursor.m = Number(b.dataset.month);
1989
1989
  this.view = "days";
1990
1990
  this.render();
1991
1991
  })
1992
1992
  );
1993
1993
  this.root.querySelectorAll("[data-year]").forEach(
1994
- (b2) => b2.addEventListener("click", () => {
1995
- this.cursor.y = Number(b2.dataset.year);
1994
+ (b) => b.addEventListener("click", () => {
1995
+ this.cursor.y = Number(b.dataset.year);
1996
1996
  this.view = "months";
1997
1997
  this.render();
1998
1998
  })
@@ -2403,7 +2403,7 @@ const _FxDatepicker = class _FxDatepicker extends FxElement {
2403
2403
  if (Number.isNaN(d.getTime())) return null;
2404
2404
  const [y, m, day] = iso.split("-").map((n) => parseInt(n, 10));
2405
2405
  if (d.getFullYear() !== y || d.getMonth() + 1 !== m || d.getDate() !== day) return null;
2406
- const cmp = (a, b2) => a.split("T")[0].localeCompare(b2.split("T")[0]);
2406
+ const cmp = (a, b) => a.split("T")[0].localeCompare(b.split("T")[0]);
2407
2407
  const min = this.getAttr("min");
2408
2408
  const max = this.getAttr("max");
2409
2409
  if (min && cmp(iso, min) < 0) return null;
@@ -2430,9 +2430,9 @@ const _FxDatepicker = class _FxDatepicker extends FxElement {
2430
2430
  this.setAttribute("start", this.withTime(parsed));
2431
2431
  this.removeAttribute("end");
2432
2432
  } else {
2433
- const [a, b2] = [this.getAttr("start").split("T")[0], parsed].sort();
2433
+ const [a, b] = [this.getAttr("start").split("T")[0], parsed].sort();
2434
2434
  this.setAttribute("start", this.withTime(a));
2435
- this.setAttribute("end", this.withTime(b2));
2435
+ this.setAttribute("end", this.withTime(b));
2436
2436
  }
2437
2437
  this.emitChange({ start: this.getAttr("start"), end: this.getAttr("end") });
2438
2438
  } else {
@@ -3301,12 +3301,16 @@ const _FxTable = class _FxTable extends FxElement {
3301
3301
  "rows",
3302
3302
  "rows-options",
3303
3303
  "striped",
3304
+ "hover",
3305
+ "toolbar",
3304
3306
  "empty-message",
3305
3307
  "pagination-position",
3306
3308
  "lazy",
3307
3309
  "total",
3308
3310
  "loading",
3309
- "loading-message"
3311
+ "loading-message",
3312
+ "sort-field",
3313
+ "sort-order"
3310
3314
  ];
3311
3315
  }
3312
3316
  get data() {
@@ -3335,7 +3339,7 @@ const _FxTable = class _FxTable extends FxElement {
3335
3339
  }
3336
3340
  get columns() {
3337
3341
  return [...this.querySelectorAll("fx-column")].map((c) => {
3338
- const tpl = c.querySelector("template");
3342
+ const tpl = this._resolveCellTemplate(c);
3339
3343
  const direct = tpl ? void 0 : c.innerHTML.trim();
3340
3344
  const align = c.getAttribute("align") ?? "left";
3341
3345
  return {
@@ -3349,13 +3353,65 @@ const _FxTable = class _FxTable extends FxElement {
3349
3353
  };
3350
3354
  });
3351
3355
  }
3356
+ /**
3357
+ * Resolve o template de célula de uma `<fx-column>`, em ordem de prioridade:
3358
+ *
3359
+ * 1. `<fx-cell>` — **recomendado**. Tag própria, sem significado especial
3360
+ * para Vue/React/Angular/Svelte: nenhum framework a intercepta, então o
3361
+ * `innerHTML` sempre chega intacto ao componente. O light DOM do
3362
+ * `<fx-table>` não é exibido (a tabela não usa `<slot>`), então o
3363
+ * conteúdo-fonte nunca aparece na tela.
3364
+ * 2. `template="#id"` (ou `template="id"`) — aponta para um `<template>`
3365
+ * declarado **fora** da tabela, útil em React/Angular, onde é mais
3366
+ * simples manter o markup num único lugar.
3367
+ * 3. `<template>` dentro da coluna — forma **legada**, mantida por
3368
+ * compatibilidade. Em Vue 3 ela vira um bloco do compilador e em React os
3369
+ * filhos são gerenciados pelo framework; nos dois casos o `innerHTML`
3370
+ * chega vazio e a célula não renderiza. Prefira `<fx-cell>`.
3371
+ */
3372
+ _resolveCellTemplate(c) {
3373
+ const cell = c.querySelector(":scope > fx-cell");
3374
+ if (cell) return cell;
3375
+ const ref = c.getAttribute("template");
3376
+ if (ref) {
3377
+ const alvo = this._resolveRef(ref);
3378
+ if (alvo) return alvo;
3379
+ }
3380
+ return c.querySelector("template");
3381
+ }
3352
3382
  get toolbarTemplate() {
3353
- return this.querySelector(
3354
- 'template[slot="toolbar"]'
3355
- );
3383
+ const bar = this.querySelector(":scope > fx-toolbar");
3384
+ if (bar) return bar;
3385
+ const ref = this.getAttr("toolbar");
3386
+ if (ref) {
3387
+ const alvo = this._resolveRef(ref);
3388
+ if (alvo) return alvo;
3389
+ }
3390
+ return this.querySelector('template[slot="toolbar"]');
3391
+ }
3392
+ /**
3393
+ * Resolve `ref` para um elemento: `#id`/`id` ⇒ `getElementById` no
3394
+ * documento; qualquer outro valor é tratado como seletor CSS relativo à
3395
+ * própria tabela. Valores inválidos devolvem `null` (sem exceção).
3396
+ */
3397
+ _resolveRef(ref) {
3398
+ const valor = ref.trim();
3399
+ if (!valor) return null;
3400
+ const doc = this.ownerDocument;
3401
+ if (valor.startsWith("#")) return doc?.getElementById(valor.slice(1)) ?? null;
3402
+ if (/^[.#[>]/.test(valor)) {
3403
+ try {
3404
+ return this.querySelector(valor);
3405
+ } catch {
3406
+ return null;
3407
+ }
3408
+ }
3409
+ return doc?.getElementById(valor) ?? null;
3356
3410
  }
3357
3411
  connectedCallback() {
3358
3412
  this._parseDataAttribute();
3413
+ this._parseSortAttributes();
3414
+ loadFenixIconsFont();
3359
3415
  super.connectedCallback();
3360
3416
  this._columnObserver = new MutationObserver(() => this.render());
3361
3417
  this._columnObserver.observe(this, { childList: true, subtree: true });
@@ -3364,6 +3420,13 @@ const _FxTable = class _FxTable extends FxElement {
3364
3420
  this._columnObserver?.disconnect();
3365
3421
  super.disconnectedCallback();
3366
3422
  }
3423
+ /** Ordenação inicial via `sort-field` / `sort-order` (documentados na API). */
3424
+ _parseSortAttributes() {
3425
+ const field = this.getAttr("sort-field");
3426
+ if (field) this.sortField = field;
3427
+ const order = this.getAttr("sort-order");
3428
+ if (order === "asc" || order === "desc") this.sortDir = order === "asc" ? 1 : -1;
3429
+ }
3367
3430
  _parseDataAttribute() {
3368
3431
  if (!this._data.length) {
3369
3432
  try {
@@ -3399,8 +3462,8 @@ const _FxTable = class _FxTable extends FxElement {
3399
3462
  }
3400
3463
  const total = this.lazy ? this.total : out.length;
3401
3464
  if (this.sortField) {
3402
- out.sort((a, b2) => {
3403
- const av = a[this.sortField], bv = b2[this.sortField];
3465
+ out.sort((a, b) => {
3466
+ const av = a[this.sortField], bv = b[this.sortField];
3404
3467
  if (typeof av === "number" && typeof bv === "number")
3405
3468
  return (av - bv) * this.sortDir;
3406
3469
  return String(av ?? "").localeCompare(String(bv ?? ""), "pt-BR") * this.sortDir;
@@ -3447,11 +3510,11 @@ const _FxTable = class _FxTable extends FxElement {
3447
3510
  const pagerHtml = !paginated ? "" : `
3448
3511
  <div class="pager" part="pager">
3449
3512
  <span class="info">Página ${this.page + 1} de ${pages} · ${total} registros</span>
3450
- <button type="button" class="pg-btn" data-pg="first" ${this.page === 0 ? "disabled" : ""}>«</button>
3451
- <button type="button" class="pg-btn" data-pg="prev" ${this.page === 0 ? "disabled" : ""}>‹</button>
3513
+ <button type="button" class="pg-btn" part="first" aria-label="Primeira página" data-pg="first" ${this.page === 0 ? "disabled" : ""}>${fenixIconHtml("first_page")}</button>
3514
+ <button type="button" class="pg-btn" part="prev" aria-label="Página anterior" data-pg="prev" ${this.page === 0 ? "disabled" : ""}>${fenixIconHtml("navigate_before")}</button>
3452
3515
  ${Array.from({ length: pages }, (_, p) => `<button type="button" class="pg-btn" data-pg="${p}" aria-current="${p === this.page}">${p + 1}</button>`).join("")}
3453
- <button type="button" class="pg-btn" data-pg="next" ${this.page >= pages - 1 ? "disabled" : ""}>›</button>
3454
- <button type="button" class="pg-btn" data-pg="last" ${this.page >= pages - 1 ? "disabled" : ""}>»</button>
3516
+ <button type="button" class="pg-btn" part="next" aria-label="Próxima página" data-pg="next" ${this.page >= pages - 1 ? "disabled" : ""}>${fenixIconHtml("navigate_next")}</button>
3517
+ <button type="button" class="pg-btn" part="last" aria-label="Última página" data-pg="last" ${this.page >= pages - 1 ? "disabled" : ""}>${fenixIconHtml("last_page")}</button>
3455
3518
  <label class="info">${esc("Por página:")}
3456
3519
  <fx-select class="rows-sel" size="sm" value="${this.rowsPerPage}" aria-label="Itens por página">${this.getAttr(
3457
3520
  "rows-options",
@@ -3588,6 +3651,7 @@ const _FxTable = class _FxTable extends FxElement {
3588
3651
  }
3589
3652
  };
3590
3653
  _FxTable.styles = css`
3654
+ ${FENIX_ICON_BASE_CSS}
3591
3655
  :host {
3592
3656
  display: block;
3593
3657
  font-family: var(--fx-font-family);
@@ -3664,17 +3728,14 @@ _FxTable.styles = css`
3664
3728
  transition: background-color var(--fx-motion-duration-fast)
3665
3729
  var(--fx-motion-easing);
3666
3730
  }
3667
- tbody tr:hover {
3668
- background: color-mix(
3669
- in srgb,
3670
- var(--fx-color-primary) 8%,
3671
- transparent
3672
- );
3673
- }
3731
+ /* Listrado: alterna a superfície (cinza). Antes usava
3732
+ --fx-surface-background, que é a MESMA cor do fundo do :host e
3733
+ portanto pintava branco sobre branco (listrado invisível). */
3674
3734
  :host([striped]) tbody tr:nth-child(even) {
3675
- background: var(--fx-surface-background);
3735
+ background: var(--fx-surface-surface, #f8fafc);
3676
3736
  }
3677
- :host([striped]) tbody tr:hover {
3737
+ /* O hover é opt-in via atributo (o clique na linha continua sempre ativo). */
3738
+ :host([hover]) tbody tr:hover {
3678
3739
  background: color-mix(
3679
3740
  in srgb,
3680
3741
  var(--fx-color-primary) 8%,
@@ -3704,6 +3765,9 @@ _FxTable.styles = css`
3704
3765
  font-size: calc(var(--fx-font-size) - 2px);
3705
3766
  }
3706
3767
  .pg-btn {
3768
+ display: inline-flex;
3769
+ align-items: center;
3770
+ justify-content: center;
3707
3771
  min-width: var(--fx-size-sm);
3708
3772
  height: var(--fx-size-sm);
3709
3773
  font: inherit;
@@ -3717,6 +3781,12 @@ _FxTable.styles = css`
3717
3781
  transition: border-color var(--fx-motion-duration-fast)
3718
3782
  var(--fx-motion-easing);
3719
3783
  }
3784
+ /* O glifo precisa de 1px a mais que o dígito: a altura de traço do ícone é
3785
+ maior que a do número, então igualar os dois font-size deixa a seta
3786
+ visivelmente maior que o texto. */
3787
+ .pg-btn .fx-icon {
3788
+ font-size: calc(var(--fx-font-size) - 1px);
3789
+ }
3720
3790
  .pg-btn:hover:not(:disabled):not([aria-current="true"]) {
3721
3791
  border-color: var(--fx-color-primary);
3722
3792
  color: var(--fx-color-primary);
@@ -5465,6 +5535,75 @@ function defineFxAvatar() {
5465
5535
  return defineElement("fx-avatar", FxAvatar);
5466
5536
  }
5467
5537
  defineFxAvatar();
5538
+ const _FxIcon = class _FxIcon extends FxElement {
5539
+ static get observedAttributes() {
5540
+ return ["name", "size", "label", "fill", "bold"];
5541
+ }
5542
+ /** Nome do glifo (ou texto livre/emoji). */
5543
+ get name() {
5544
+ return this.getAttr("name");
5545
+ }
5546
+ set name(value) {
5547
+ this.setAttribute("name", value);
5548
+ }
5549
+ /** Tamanho: '' (herda 1em) | 'sm' | 'md' | 'lg' | 'xl'. */
5550
+ get size() {
5551
+ return this.getAttr("size");
5552
+ }
5553
+ set size(value) {
5554
+ if (value) this.setAttribute("size", value);
5555
+ else this.removeAttribute("size");
5556
+ }
5557
+ /** Nome acessível; vazio = ícone decorativo (aria-hidden). */
5558
+ get label() {
5559
+ return this.getAttr("label");
5560
+ }
5561
+ set label(value) {
5562
+ if (value) this.setAttribute("label", value);
5563
+ else this.removeAttribute("label");
5564
+ }
5565
+ connectedCallback() {
5566
+ loadFenixIconsFont();
5567
+ super.connectedCallback();
5568
+ }
5569
+ render() {
5570
+ const name = this.getAttr("name").trim();
5571
+ const label = this.getAttr("label").trim();
5572
+ const a11y = label ? `role="img" aria-label="${esc(label)}"` : 'aria-hidden="true"';
5573
+ if (!name) {
5574
+ this.setTemplate(`<span class="icon" part="icon" ${a11y}></span>`);
5575
+ return;
5576
+ }
5577
+ const inner = isFenixIconName(name) ? `<i class="icon fx-icon" part="icon" ${a11y}>${esc(name)}</i>` : `<span class="icon" part="icon" ${a11y}>${esc(name)}</span>`;
5578
+ this.setTemplate(inner);
5579
+ }
5580
+ };
5581
+ _FxIcon.styles = css`
5582
+ :host {
5583
+ display: inline-flex;
5584
+ align-items: center;
5585
+ justify-content: center;
5586
+ vertical-align: middle;
5587
+ line-height: 1;
5588
+ color: currentColor;
5589
+ font-size: var(--fx-icon-size, 1em);
5590
+ }
5591
+ :host([size='sm']) { font-size: var(--fx-icon-size-sm, 16px); }
5592
+ :host([size='md']) { font-size: var(--fx-icon-size-md, 20px); }
5593
+ :host([size='lg']) { font-size: var(--fx-icon-size-lg, 24px); }
5594
+ :host([size='xl']) { font-size: var(--fx-icon-size-xl, 32px); }
5595
+ :host([hidden]) { display: none; }
5596
+ .icon { display: inline-block; }
5597
+ /* Variantes da fonte (FILL/wght): vencem a classe base .fx-icon. */
5598
+ :host([fill]) .icon { font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 24; }
5599
+ :host([bold]) .icon { font-variation-settings: 'FILL' 0, 'wght' 600, 'GRAD' 0, 'opsz' 24; }
5600
+ ${FENIX_ICON_BASE_CSS}
5601
+ `;
5602
+ let FxIcon = _FxIcon;
5603
+ function defineFxIcon() {
5604
+ return defineElement("fx-icon", FxIcon);
5605
+ }
5606
+ defineFxIcon();
5468
5607
  const _FxCard = class _FxCard extends FxElement {
5469
5608
  static get observedAttributes() {
5470
5609
  return ["variant", "radius", "size", "padded", "heading"];
@@ -7084,6 +7223,48 @@ const CARD_CSS = `
7084
7223
  color: var(--fx-text-muted, #64748b); font-size: 15px; padding: 2px 4px; border-radius: 4px;
7085
7224
  }
7086
7225
  .close:hover { color: var(--fx-color-danger, #f43f5e); background: var(--fx-surface-surface-hover, rgba(0,0,0,.05)); }
7226
+
7227
+ /* --- Barra de contagem regressiva (atributo progress) ---
7228
+ Fica na base do card e esvazia durante a duração, na cor do kind (--kind) —
7229
+ ou seja, acompanha success/error/warning/info sem configuração extra.
7230
+
7231
+ O lado é configurável pelo atributo progress-origin (padrão: left):
7232
+ left → a barra drena da direita para a esquerda (ancorada à esquerda)
7233
+ right → a barra drena da esquerda para a direita (ancorada à direita)
7234
+ A origem entra como custom property, então os keyframes são compartilhados.
7235
+
7236
+ Usa escalaX em vez de width: a animação escreve só na compositor, sem
7237
+ reflow por frame. */
7238
+ .card { position: relative; overflow: hidden; }
7239
+ .progress {
7240
+ position: absolute;
7241
+ left: 0;
7242
+ bottom: 0;
7243
+ width: 100%;
7244
+ height: 3px;
7245
+ background: var(--kind);
7246
+ transform-origin: var(--toast-progress-origin, left center);
7247
+ transform: scaleX(1);
7248
+ border-radius: 0 0 var(--fx-radius-md, 8px) var(--fx-radius-md, 8px);
7249
+ pointer-events: none;
7250
+ will-change: transform;
7251
+ }
7252
+ .progress.run { animation: fx-toast-drain var(--toast-duration, 4000ms) linear forwards; }
7253
+ /* No modo hover a animação fica parada até o cursor/foco entrar no card. */
7254
+ .card:hover .progress.run,
7255
+ .card:focus-within .progress.run { animation-play-state: running; }
7256
+ @keyframes fx-toast-drain {
7257
+ from { transform: scaleX(1); }
7258
+ to { transform: scaleX(0); }
7259
+ }
7260
+ /* A barra só começa a correr no 2º frame: no 1º ela já apareceria zerada
7261
+ (o elemento acabou de ser inserido e a animação aplica o estado final). */
7262
+ .card.entering .progress { animation: none; }
7263
+ /* Ao dispensar, congela a barra: ela some junto com o card em .2s. */
7264
+ .card.leaving .progress { animation-play-state: paused; }
7265
+ @media (prefers-reduced-motion: reduce) {
7266
+ .progress.run { animation-duration: 0.01ms; }
7267
+ }
7087
7268
  `;
7088
7269
  class FxToast extends HTMLElement {
7089
7270
  constructor() {
@@ -7093,7 +7274,51 @@ class FxToast extends HTMLElement {
7093
7274
  this.attachShadow({ mode: "open" });
7094
7275
  }
7095
7276
  static get observedAttributes() {
7096
- return ["kind", "title", "message", "duration", "icon", "mode"];
7277
+ return ["kind", "title", "message", "duration", "icon", "mode", "progress", "progress-origin"];
7278
+ }
7279
+ /**
7280
+ * Exibe a barra de contagem regressiva na base do card.
7281
+ *
7282
+ * A barra esvazia da esquerda para a direita durante exatamente `duration`
7283
+ * e usa a cor do `kind`. Com `duration="0"` (toast fixo) a barra é
7284
+ * omitida — não há tempo restante a representar.
7285
+ *
7286
+ * - `'always'` (ou atributo vazio): barra sempre correndo.
7287
+ * - `'hover'`: a barra só corre enquanto o cursor estiver sobre o toast;
7288
+ * ao sair, a animação pausa, então dá tempo de ler a mensagem.
7289
+ * - `'paused'`: barra visível, porém parada.
7290
+ */
7291
+ get progress() {
7292
+ if (!this.hasAttribute("progress")) return void 0;
7293
+ const v = this.getAttribute("progress");
7294
+ return v === "hover" || v === "paused" ? v : "always";
7295
+ }
7296
+ set progress(value) {
7297
+ if (value) this.setAttribute("progress", value);
7298
+ else this.removeAttribute("progress");
7299
+ }
7300
+ /**
7301
+ * Lado a partir do qual a barra esvazia.
7302
+ *
7303
+ * - `'left'` (padrão): a barra fica ancorada à esquerda e drena da direita
7304
+ * para a esquerda.
7305
+ * - `'right'`: a barra fica ancorada à direita e drena da esquerda para a
7306
+ * direita (espelhado).
7307
+ *
7308
+ * Só tem efeito junto com `progress`; qualquer valor fora da lista cai em
7309
+ * `'left'`.
7310
+ */
7311
+ get progressOrigin() {
7312
+ return this.getAttribute("progress-origin") === "right" ? "right" : "left";
7313
+ }
7314
+ set progressOrigin(value) {
7315
+ this.setAttribute("progress-origin", value);
7316
+ }
7317
+ /** Duração efetiva em ms (0 = fixo). Mesma regra usada no timer. */
7318
+ get safeDuration() {
7319
+ const dur = Number(this.getAttribute("duration") ?? "4000");
7320
+ if (dur === 0) return 0;
7321
+ return Math.max(Number.isNaN(dur) ? 4e3 : dur, 1e3);
7097
7322
  }
7098
7323
  /** Esquema de cores forçado do card: 'light' | 'dark' | undefined (segue o tema). */
7099
7324
  get mode() {
@@ -7106,16 +7331,21 @@ class FxToast extends HTMLElement {
7106
7331
  }
7107
7332
  connectedCallback() {
7108
7333
  if (!this.shadowRoot.firstChild) __privateMethod(this, _FxToast_instances, render_fn).call(this);
7109
- const dur = Number(this.getAttribute("duration") ?? "4000");
7110
- const safe = dur === 0 ? 0 : Math.max(Number.isNaN(dur) ? 4e3 : dur, 1e3);
7111
- if (!Number.isNaN(safe) && safe > 0 && !this.timer) {
7334
+ const safe = this.safeDuration;
7335
+ if (safe > 0 && !this.timer) {
7112
7336
  this.timer = setTimeout(() => this.dismiss(), safe);
7113
7337
  }
7338
+ __privateMethod(this, _FxToast_instances, syncProgress_fn).call(this);
7114
7339
  }
7115
7340
  disconnectedCallback() {
7116
7341
  if (this.timer) clearTimeout(this.timer);
7117
7342
  this.timer = null;
7118
7343
  }
7344
+ attributeChangedCallback(name) {
7345
+ if (name === "duration" || name === "progress" || name === "progress-origin" || name === "kind") {
7346
+ __privateMethod(this, _FxToast_instances, syncProgress_fn).call(this);
7347
+ }
7348
+ }
7119
7349
  dismiss() {
7120
7350
  if (this.timer) clearTimeout(this.timer);
7121
7351
  this.timer = null;
@@ -7129,6 +7359,36 @@ class FxToast extends HTMLElement {
7129
7359
  }
7130
7360
  }
7131
7361
  _FxToast_instances = new WeakSet();
7362
+ /**
7363
+ * Aplica (ou remove) a barra de contagem regressiva.
7364
+ *
7365
+ * A barra só existe com `progress` e `duration > 0`. O tempo da animação
7366
+ * vem de `--toast-duration`, sempre igual ao do `setTimeout` que fecha o
7367
+ * toast — assim a barra e o desaparecimento nunca desalinham.
7368
+ */
7369
+ syncProgress_fn = function() {
7370
+ const card = this.shadowRoot?.querySelector(".card");
7371
+ if (!card) return;
7372
+ const bar = this.shadowRoot.querySelector(".progress");
7373
+ const mode = this.progress;
7374
+ const dur = this.safeDuration;
7375
+ const shouldShow = !!mode && dur > 0;
7376
+ if (!shouldShow) {
7377
+ bar?.remove();
7378
+ card.classList.remove("entering");
7379
+ return;
7380
+ }
7381
+ if (!bar) {
7382
+ this.shadowRoot.querySelector(".card").insertAdjacentHTML("beforeend", '<span class="progress" part="progress" aria-hidden="true"></span>');
7383
+ }
7384
+ const el = this.shadowRoot.querySelector(".progress");
7385
+ el.style.setProperty("--toast-duration", `${dur}ms`);
7386
+ el.style.setProperty("--toast-progress-origin", `${this.progressOrigin} center`);
7387
+ el.classList.remove("run");
7388
+ if (mode !== "paused") el.classList.add("run");
7389
+ card.classList.add("entering");
7390
+ requestAnimationFrame(() => requestAnimationFrame(() => card.classList.remove("entering")));
7391
+ };
7132
7392
  render_fn = function() {
7133
7393
  const kind = this.getAttribute("kind") || "info";
7134
7394
  const title = this.getAttribute("title") ?? "";
@@ -7198,6 +7458,8 @@ class ToastApi {
7198
7458
  el.setAttribute("position", o.position ?? "top-right");
7199
7459
  el.setAttribute("duration", String(o.duration ?? 4e3));
7200
7460
  if (o.mode) el.setAttribute("mode", o.mode);
7461
+ if (o.progress) el.setAttribute("progress", o.progress);
7462
+ if (o.progressOrigin) el.setAttribute("progress-origin", o.progressOrigin);
7201
7463
  regionFor(o.position ?? "top-right").appendChild(el);
7202
7464
  this.map.set(id, el);
7203
7465
  return id;
@@ -8295,6 +8557,10 @@ _FxDrawer.styles = css`
8295
8557
  position: sticky;
8296
8558
  top: 0;
8297
8559
  background: var(--fx-surface-background);
8560
+ /* O conteúdo do drawer (ex.: abas da customização de tema)
8561
+ rola por trás do header: sem z-index o texto passava POR CIMA
8562
+ e o header parecia transparente. */
8563
+ z-index: 1;
8298
8564
  }
8299
8565
  .title {
8300
8566
  font-weight: 700;
@@ -8359,6 +8625,10 @@ const _FxPagination = class _FxPagination extends FxElement {
8359
8625
  get pages() {
8360
8626
  return Math.max(1, Math.ceil(this.total / this.rows));
8361
8627
  }
8628
+ connectedCallback() {
8629
+ loadFenixIconsFont();
8630
+ super.connectedCallback();
8631
+ }
8362
8632
  render() {
8363
8633
  const pages = this.pages;
8364
8634
  const current = Math.min(Math.max(1, this.page), pages);
@@ -8369,11 +8639,13 @@ const _FxPagination = class _FxPagination extends FxElement {
8369
8639
  const to = Math.min(current * this.rows, this.total);
8370
8640
  this.setTemplate(`
8371
8641
  <span class="info">${this.total ? `${from}-${to} de ${this.total}` : "0 itens"}</span>
8372
- <button type="button" class="nav" part="prev" data-go="${current - 1}" ${current <= 1 ? "disabled" : ""}>‹</button>
8642
+ <button type="button" class="nav" part="first" aria-label="Primeira página" data-go="1" ${current <= 1 ? "disabled" : ""}>${fenixIconHtml("first_page")}</button>
8643
+ <button type="button" class="nav" part="prev" aria-label="Página anterior" data-go="${current - 1}" ${current <= 1 ? "disabled" : ""}>${fenixIconHtml("navigate_before")}</button>
8373
8644
  ${window_[0] !== 1 && pages > 5 ? `<button type="button" class="nav" data-go="1">1</button>` : ""}
8374
8645
  ${window_.map((p) => `<button type="button" class="nav ${p === current ? "active" : ""}" data-go="${p}">${p}</button>`).join("")}
8375
8646
  ${window_[window_.length - 1] !== pages && pages > 5 ? `<button type="button" class="nav" data-go="${pages}">${pages}</button>` : ""}
8376
- <button type="button" class="nav" part="next" data-go="${current + 1}" ${current >= pages ? "disabled" : ""}>›</button>
8647
+ <button type="button" class="nav" part="next" aria-label="Próxima página" data-go="${current + 1}" ${current >= pages ? "disabled" : ""}>${fenixIconHtml("navigate_next")}</button>
8648
+ <button type="button" class="nav" part="last" aria-label="Última página" data-go="${pages}" ${current >= pages ? "disabled" : ""}>${fenixIconHtml("last_page")}</button>
8377
8649
  ${opts.length ? `
8378
8650
  <fx-select class="rows-sel" part="rows" size="sm" value="${this.rows}" aria-label="Itens por página">
8379
8651
  ${opts.map((o) => `<option value="${esc(o)}">${esc(o)}</option>`).join("")}
@@ -8409,6 +8681,7 @@ const _FxPagination = class _FxPagination extends FxElement {
8409
8681
  }
8410
8682
  };
8411
8683
  _FxPagination.styles = css`
8684
+ ${FENIX_ICON_BASE_CSS}
8412
8685
  :host {
8413
8686
  display: flex;
8414
8687
  align-items: center;
@@ -8420,6 +8693,9 @@ _FxPagination.styles = css`
8420
8693
  :host([position='center']) { justify-content: center; }
8421
8694
  :host([position='right']) { justify-content: flex-end; }
8422
8695
  .nav {
8696
+ display: inline-flex;
8697
+ align-items: center;
8698
+ justify-content: center;
8423
8699
  min-width: var(--fx-size-sm);
8424
8700
  height: var(--fx-size-sm);
8425
8701
  border: 1px solid var(--fx-border-default);
@@ -8434,6 +8710,10 @@ _FxPagination.styles = css`
8434
8710
  border-color var(--fx-motion-duration-fast) var(--fx-motion-easing),
8435
8711
  background var(--fx-motion-duration-fast) var(--fx-motion-easing);
8436
8712
  }
8713
+ /* O glifo precisa de 1px a mais que o dígito: a altura de traço do ícone é
8714
+ maior que a do número, então igualar os dois font-size deixa a seta
8715
+ visivelmente maior que o texto. */
8716
+ .nav .fx-icon { font-size: calc(var(--fx-font-size) - 1px); }
8437
8717
  .nav:hover:not([disabled]):not(.active) { border-color: var(--fx-color-primary); color: var(--fx-color-primary); }
8438
8718
  .nav[disabled] { opacity: 0.5; cursor: not-allowed; }
8439
8719
  .nav.active {
@@ -9451,7 +9731,7 @@ const _FxOrderList = class _FxOrderList extends FxElement {
9451
9731
  _handleMoveAction(action) {
9452
9732
  if (!this._selection.length) return;
9453
9733
  const displayData = this._getDisplayData();
9454
- const selectedIndices = this._selection.map((s2) => displayData.findIndex((d) => this._getItemKey(d, -1) === this._getItemKey(s2, -1))).filter((i) => i >= 0).sort((a, b2) => a - b2);
9734
+ const selectedIndices = this._selection.map((s2) => displayData.findIndex((d) => this._getItemKey(d, -1) === this._getItemKey(s2, -1))).filter((i) => i >= 0).sort((a, b) => a - b);
9455
9735
  if (selectedIndices.length === 0) return;
9456
9736
  switch (action) {
9457
9737
  case "top":
@@ -9476,7 +9756,7 @@ const _FxOrderList = class _FxOrderList extends FxElement {
9476
9756
  items.push(this._data[actualIdx]);
9477
9757
  actualIndices.push(actualIdx);
9478
9758
  }
9479
- actualIndices.sort((a, b2) => b2 - a).forEach((i) => this._data.splice(i, 1));
9759
+ actualIndices.sort((a, b) => b - a).forEach((i) => this._data.splice(i, 1));
9480
9760
  const insertIdx = this._getActualIndex(0);
9481
9761
  this._data.splice(insertIdx, 0, ...items);
9482
9762
  this._applyFilter();
@@ -9492,7 +9772,7 @@ const _FxOrderList = class _FxOrderList extends FxElement {
9492
9772
  items.push(this._data[actualIdx]);
9493
9773
  actualIndices.push(actualIdx);
9494
9774
  }
9495
- actualIndices.sort((a, b2) => b2 - a).forEach((i) => this._data.splice(i, 1));
9775
+ actualIndices.sort((a, b) => b - a).forEach((i) => this._data.splice(i, 1));
9496
9776
  const displayData = this._getDisplayData();
9497
9777
  const insertIdx = this._getActualIndex(displayData.length - 1);
9498
9778
  this._data.splice(insertIdx + 1, 0, ...items);
@@ -9504,7 +9784,7 @@ const _FxOrderList = class _FxOrderList extends FxElement {
9504
9784
  _moveSelectedByOne(indices, delta) {
9505
9785
  const displayData = this._getDisplayData();
9506
9786
  if (displayData.length === 0) return;
9507
- const actualIndices = indices.map((idx) => this._getActualIndex(idx)).filter((idx) => idx >= 0).sort((a, b2) => a - b2);
9787
+ const actualIndices = indices.map((idx) => this._getActualIndex(idx)).filter((idx) => idx >= 0).sort((a, b) => a - b);
9508
9788
  if (actualIndices.length === 0) return;
9509
9789
  if (delta < 0 && actualIndices[0] <= 0) return;
9510
9790
  if (delta > 0 && actualIndices[actualIndices.length - 1] >= this._data.length - 1) return;
@@ -10676,6 +10956,7 @@ export {
10676
10956
  FxEmptyState,
10677
10957
  FxFileUpload,
10678
10958
  FxFloatlabel,
10959
+ FxIcon,
10679
10960
  FxInput,
10680
10961
  FxKnob,
10681
10962
  FxMenu,
@@ -10722,7 +11003,7 @@ export {
10722
11003
  resetTheme,
10723
11004
  s as sanitizeData,
10724
11005
  setTokens,
10725
- b as stripTags,
11006
+ g as stripTags,
10726
11007
  theme,
10727
11008
  themePresets,
10728
11009
  tokenCssVars