@wrrdev/fenix-ui 1.1.16 → 1.1.17

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 (98) hide show
  1. package/README.md +2 -2
  2. package/dist/components/autocomplete/autocomplete.d.ts +3 -1
  3. package/dist/components/autocomplete/autocomplete.d.ts.map +1 -1
  4. package/dist/components/autocomplete/autocomplete.js +7 -1
  5. package/dist/components/autocomplete/autocomplete.js.map +1 -1
  6. package/dist/components/avatar/avatar.d.ts +1 -0
  7. package/dist/components/avatar/avatar.d.ts.map +1 -1
  8. package/dist/components/avatar/avatar.js +3 -0
  9. package/dist/components/avatar/avatar.js.map +1 -1
  10. package/dist/components/badge/badge.d.ts +4 -0
  11. package/dist/components/badge/badge.d.ts.map +1 -1
  12. package/dist/components/badge/badge.js +12 -0
  13. package/dist/components/badge/badge.js.map +1 -1
  14. package/dist/components/button/button.d.ts +2 -0
  15. package/dist/components/button/button.d.ts.map +1 -1
  16. package/dist/components/button/button.js +6 -0
  17. package/dist/components/button/button.js.map +1 -1
  18. package/dist/components/card/card.d.ts +2 -0
  19. package/dist/components/card/card.d.ts.map +1 -1
  20. package/dist/components/card/card.js +6 -0
  21. package/dist/components/card/card.js.map +1 -1
  22. package/dist/components/checkbox/checkbox.d.ts +5 -1
  23. package/dist/components/checkbox/checkbox.d.ts.map +1 -1
  24. package/dist/components/checkbox/checkbox.js +7 -8
  25. package/dist/components/checkbox/checkbox.js.map +1 -1
  26. package/dist/components/datepicker/datepicker.d.ts +17 -2
  27. package/dist/components/datepicker/datepicker.d.ts.map +1 -1
  28. package/dist/components/datepicker/datepicker.js +113 -42
  29. package/dist/components/datepicker/datepicker.js.map +1 -1
  30. package/dist/components/dialog/dialog.d.ts +4 -1
  31. package/dist/components/dialog/dialog.d.ts.map +1 -1
  32. package/dist/components/dialog/dialog.js +41 -18
  33. package/dist/components/dialog/dialog.js.map +1 -1
  34. package/dist/components/drawer/drawer.d.ts +5 -1
  35. package/dist/components/drawer/drawer.d.ts.map +1 -1
  36. package/dist/components/drawer/drawer.js +39 -17
  37. package/dist/components/drawer/drawer.js.map +1 -1
  38. package/dist/components/fileupload/fileupload.d.ts +13 -1
  39. package/dist/components/fileupload/fileupload.d.ts.map +1 -1
  40. package/dist/components/fileupload/fileupload.js +29 -1
  41. package/dist/components/fileupload/fileupload.js.map +1 -1
  42. package/dist/components/floatlabel/floatlabel.d.ts +15 -0
  43. package/dist/components/floatlabel/floatlabel.d.ts.map +1 -1
  44. package/dist/components/floatlabel/floatlabel.js +47 -1
  45. package/dist/components/floatlabel/floatlabel.js.map +1 -1
  46. package/dist/components/icon/icon.d.ts +4 -0
  47. package/dist/components/icon/icon.d.ts.map +1 -1
  48. package/dist/components/icon/icon.js +12 -0
  49. package/dist/components/icon/icon.js.map +1 -1
  50. package/dist/components/input/input.d.ts +9 -2
  51. package/dist/components/input/input.d.ts.map +1 -1
  52. package/dist/components/input/input.js +56 -8
  53. package/dist/components/input/input.js.map +1 -1
  54. package/dist/components/multiselect/multiselect.d.ts +8 -1
  55. package/dist/components/multiselect/multiselect.d.ts.map +1 -1
  56. package/dist/components/multiselect/multiselect.js +61 -9
  57. package/dist/components/multiselect/multiselect.js.map +1 -1
  58. package/dist/components/orderlist/orderlist.d.ts +7 -0
  59. package/dist/components/orderlist/orderlist.d.ts.map +1 -1
  60. package/dist/components/orderlist/orderlist.js +34 -2
  61. package/dist/components/orderlist/orderlist.js.map +1 -1
  62. package/dist/components/radio/radio.d.ts +6 -0
  63. package/dist/components/radio/radio.d.ts.map +1 -1
  64. package/dist/components/radio/radio.js +12 -6
  65. package/dist/components/radio/radio.js.map +1 -1
  66. package/dist/components/select/select.d.ts +19 -1
  67. package/dist/components/select/select.d.ts.map +1 -1
  68. package/dist/components/select/select.js +116 -10
  69. package/dist/components/select/select.js.map +1 -1
  70. package/dist/components/switch/switch.d.ts +4 -1
  71. package/dist/components/switch/switch.d.ts.map +1 -1
  72. package/dist/components/switch/switch.js +2 -1
  73. package/dist/components/switch/switch.js.map +1 -1
  74. package/dist/components/table/table.d.ts +27 -0
  75. package/dist/components/table/table.d.ts.map +1 -1
  76. package/dist/components/table/table.js +106 -21
  77. package/dist/components/table/table.js.map +1 -1
  78. package/dist/components/tabs/tabs.d.ts +6 -0
  79. package/dist/components/tabs/tabs.d.ts.map +1 -1
  80. package/dist/components/tabs/tabs.js +34 -0
  81. package/dist/components/tabs/tabs.js.map +1 -1
  82. package/dist/components/textarea/textarea.d.ts +5 -1
  83. package/dist/components/textarea/textarea.d.ts.map +1 -1
  84. package/dist/components/textarea/textarea.js +25 -6
  85. package/dist/components/textarea/textarea.js.map +1 -1
  86. package/dist/core/jsx.d.ts +11 -5
  87. package/dist/core/jsx.d.ts.map +1 -1
  88. package/dist/core/jsx.js.map +1 -1
  89. package/dist/core/modal.d.ts +31 -0
  90. package/dist/core/modal.d.ts.map +1 -0
  91. package/dist/core/modal.js +61 -0
  92. package/dist/core/modal.js.map +1 -0
  93. package/dist/docs/componentes/input.doc.d.ts.map +1 -1
  94. package/dist/docs/componentes/orderlist.doc.d.ts.map +1 -1
  95. package/dist/fenix-ui.umd.min.js +232 -222
  96. package/dist/fenix-ui4.esm.js +816 -151
  97. package/dist/fenix-ui4.esm.js.map +1 -1
  98. package/package.json +1 -1
@@ -506,6 +506,12 @@ const _FxButton = class _FxButton extends FxElement {
506
506
  set loading(value) {
507
507
  this.toggleAttr("loading", Boolean(value));
508
508
  }
509
+ get full() {
510
+ return this.hasAttr("full");
511
+ }
512
+ set full(value) {
513
+ this.toggleAttr("full", Boolean(value));
514
+ }
509
515
  render() {
510
516
  const loading = this.hasAttr("loading");
511
517
  const disabled = this.hasAttr("disabled") || loading;
@@ -685,6 +691,18 @@ const _FxBadge = class _FxBadge extends FxElement {
685
691
  static get observedAttributes() {
686
692
  return ["variant", "round"];
687
693
  }
694
+ get variant() {
695
+ return this.getAttr("variant", "default");
696
+ }
697
+ set variant(v) {
698
+ this.setAttribute("variant", v);
699
+ }
700
+ get round() {
701
+ return this.hasAttr("round");
702
+ }
703
+ set round(v) {
704
+ this.toggleAttr("round", Boolean(v));
705
+ }
688
706
  render() {
689
707
  this.setTemplate(`<span class="badge" part="badge"><slot></slot></span>`);
690
708
  }
@@ -764,10 +782,21 @@ function defineFxSpinner() {
764
782
  }
765
783
  defineFxSpinner();
766
784
  const _FxSelect = class _FxSelect extends FxFormElement {
767
- // `value` fica FORA da observação: refleti-lo no change não pode
768
- // re-renderizar o template e fechar o dropdown.
785
+ constructor() {
786
+ super(...arguments);
787
+ this._suppressRender = false;
788
+ this._fixedMode = false;
789
+ this._fixedResolved = false;
790
+ }
791
+ // `value` é observado para que `setAttribute('value', ...)` (HTML puro)
792
+ // também atualize o rótulo. Os caminhos internos de change usam
793
+ // `_suppressRender` para renderizar UMA vez, depois de fechar o painel.
769
794
  static get observedAttributes() {
770
- return ["size", "disabled", "placeholder", "searchable", "clearable", "error", "invalid", "success", "valid", "placement"];
795
+ return ["value", "size", "disabled", "placeholder", "searchable", "clearable", "full", "error", "invalid", "success", "valid", "placement"];
796
+ }
797
+ attributeChangedCallback(name, oldValue, newValue) {
798
+ if (this._suppressRender) return;
799
+ super.attributeChangedCallback(name, oldValue, newValue);
771
800
  }
772
801
  /** Tamanho do campo. Padrão: `'md'`. */
773
802
  get size() {
@@ -781,7 +810,9 @@ const _FxSelect = class _FxSelect extends FxFormElement {
781
810
  return this.getAttr("value");
782
811
  }
783
812
  set value(value) {
784
- this.setAttribute("value", value);
813
+ const next = value == null ? "" : String(value);
814
+ const prev = this.getAttr("value");
815
+ if (prev !== next) this.setAttribute("value", next);
785
816
  this.updateFormValue();
786
817
  }
787
818
  get disabled() {
@@ -790,6 +821,24 @@ const _FxSelect = class _FxSelect extends FxFormElement {
790
821
  set disabled(value) {
791
822
  this.toggleAttr("disabled", Boolean(value));
792
823
  }
824
+ get searchable() {
825
+ return this.hasAttr("searchable");
826
+ }
827
+ set searchable(value) {
828
+ this.toggleAttr("searchable", Boolean(value));
829
+ }
830
+ get clearable() {
831
+ return this.hasAttr("clearable");
832
+ }
833
+ set clearable(value) {
834
+ this.toggleAttr("clearable", Boolean(value));
835
+ }
836
+ get full() {
837
+ return this.hasAttr("full");
838
+ }
839
+ set full(value) {
840
+ this.toggleAttr("full", Boolean(value));
841
+ }
793
842
  get options() {
794
843
  return [...this.querySelectorAll("option")].map((o) => ({
795
844
  value: o.getAttribute("value") ?? o.textContent?.trim() ?? "",
@@ -798,8 +847,17 @@ const _FxSelect = class _FxSelect extends FxFormElement {
798
847
  }
799
848
  connectedCallback() {
800
849
  super.connectedCallback();
801
- this.observer = new MutationObserver(() => this.render());
802
- this.observer.observe(this, { childList: true, subtree: true, characterData: true });
850
+ this.observer = new MutationObserver((mutations) => {
851
+ if (mutations.every((m) => m.type === "attributes" && m.target === this)) return;
852
+ this.render();
853
+ });
854
+ this.observer.observe(this, {
855
+ childList: true,
856
+ subtree: true,
857
+ characterData: true,
858
+ attributes: true,
859
+ attributeFilter: ["value", "selected"]
860
+ });
803
861
  this.docListener = (e) => {
804
862
  if (!this.hasAttr("open")) return;
805
863
  if (e.composedPath().includes(this)) return;
@@ -816,6 +874,8 @@ const _FxSelect = class _FxSelect extends FxFormElement {
816
874
  }
817
875
  /** Abre o dropdown, posiciona o painel e passa a reposicioná-lo no reflow. */
818
876
  _abrir() {
877
+ this._fixedMode = _FxSelect._precisaFixed(this);
878
+ this._fixedResolved = true;
819
879
  this.setAttribute("open", "");
820
880
  this._bindReflow();
821
881
  }
@@ -823,6 +883,8 @@ const _FxSelect = class _FxSelect extends FxFormElement {
823
883
  _fechar() {
824
884
  this.removeAttribute("open");
825
885
  this.removeAttribute("data-placement");
886
+ this._fixedMode = false;
887
+ this._fixedResolved = false;
826
888
  this._unbindReflow();
827
889
  }
828
890
  /** Liga o reposicionamento (resize + scroll interno, em capture). */
@@ -865,7 +927,30 @@ const _FxSelect = class _FxSelect extends FxFormElement {
865
927
  _FxSelect.MIN_ALTURA,
866
928
  (paraCima ? acima : abaixo) - _FxSelect.GAP
867
929
  );
868
- painel.style.maxHeight = `${Math.min(desejado || disponivel, disponivel, maxAltura)}px`;
930
+ const altura = Math.min(desejado || disponivel, disponivel, maxAltura);
931
+ painel.style.maxHeight = `${altura}px`;
932
+ if (!this._fixedResolved) {
933
+ this._fixedMode = _FxSelect._precisaFixed(this);
934
+ this._fixedResolved = true;
935
+ }
936
+ if (this._fixedMode) {
937
+ this.setAttribute("data-fixed", "");
938
+ const largura2 = Math.max(r.width, 220);
939
+ const esquerda = Math.max(
940
+ _FxSelect.MARGEM,
941
+ Math.min(r.left, window.innerWidth - largura2 - _FxSelect.MARGEM)
942
+ );
943
+ const topo = paraCima ? Math.max(_FxSelect.MARGEM, r.top - _FxSelect.GAP - altura) : r.bottom + _FxSelect.GAP;
944
+ painel.style.bottom = "auto";
945
+ painel.style.width = `${largura2}px`;
946
+ painel.style.left = `${esquerda}px`;
947
+ painel.style.top = `${topo}px`;
948
+ return;
949
+ }
950
+ this.removeAttribute("data-fixed");
951
+ painel.style.top = "";
952
+ painel.style.bottom = "";
953
+ painel.style.width = "";
869
954
  painel.style.left = "";
870
955
  const largura = painel.offsetWidth;
871
956
  const excesso = r.left + largura - (window.innerWidth - _FxSelect.MARGEM);
@@ -874,6 +959,21 @@ const _FxSelect = class _FxSelect extends FxFormElement {
874
959
  painel.style.right = "auto";
875
960
  }
876
961
  }
962
+ /**
963
+ * Existe ancestral com overflow que cortaria o painel absoluto?
964
+ * `getComputedStyle` é defensivo: em jsdom retorna vazio e o modo
965
+ * histórico (absoluto) é mantido.
966
+ */
967
+ static _precisaFixed(host) {
968
+ let el = host.parentElement;
969
+ while (el && el !== document.body && el !== document.documentElement) {
970
+ const cs = getComputedStyle(el);
971
+ const overflow = `${cs.overflow}${cs.overflowX}${cs.overflowY}`;
972
+ if (/(hidden|clip|auto|scroll|overlay)/.test(overflow)) return true;
973
+ el = el.parentElement;
974
+ }
975
+ return false;
976
+ }
877
977
  /** Altura máxima do painel a partir de `--fx-select-panel-max-height`. */
878
978
  static _maxAltura(host) {
879
979
  const raw = getComputedStyle(host).getPropertyValue("--fx-select-panel-max-height");
@@ -881,7 +981,12 @@ const _FxSelect = class _FxSelect extends FxFormElement {
881
981
  return Number.isFinite(n) && n > 0 ? n : 260;
882
982
  }
883
983
  select(value) {
884
- this.value = value;
984
+ this._suppressRender = true;
985
+ try {
986
+ this.value = value;
987
+ } finally {
988
+ this._suppressRender = false;
989
+ }
885
990
  this._fechar();
886
991
  this.render();
887
992
  this.emitValueEvents();
@@ -908,6 +1013,10 @@ const _FxSelect = class _FxSelect extends FxFormElement {
908
1013
  }
909
1014
  render() {
910
1015
  const prevOpen = this.hasAttr("open");
1016
+ if (!prevOpen) {
1017
+ this._fixedMode = false;
1018
+ this._fixedResolved = false;
1019
+ }
911
1020
  const prevSearch = this.root.querySelector(".search");
912
1021
  const search = prevSearch?.value ?? "";
913
1022
  const searchFocused = prevSearch != null && this.root.activeElement === prevSearch;
@@ -984,7 +1093,12 @@ const _FxSelect = class _FxSelect extends FxFormElement {
984
1093
  });
985
1094
  this.root.querySelector(".clear")?.addEventListener("click", (e) => {
986
1095
  e.stopPropagation();
987
- this.value = "";
1096
+ this._suppressRender = true;
1097
+ try {
1098
+ this.value = "";
1099
+ } finally {
1100
+ this._suppressRender = false;
1101
+ }
988
1102
  this._fechar();
989
1103
  this.render();
990
1104
  this.emitValueEvents();
@@ -999,7 +1113,12 @@ const _FxSelect = class _FxSelect extends FxFormElement {
999
1113
  }
1000
1114
  /** form.reset(): restaura o valor inicial e re-renderiza o rótulo. */
1001
1115
  formResetCallback() {
1002
- super.formResetCallback();
1116
+ this._suppressRender = true;
1117
+ try {
1118
+ super.formResetCallback();
1119
+ } finally {
1120
+ this._suppressRender = false;
1121
+ }
1003
1122
  this.render();
1004
1123
  }
1005
1124
  };
@@ -1122,6 +1241,11 @@ _FxSelect.styles = css`
1122
1241
  border-radius: var(--fx-radius-md);
1123
1242
  box-shadow: var(--fx-shadow-lg);
1124
1243
  }
1244
+ /* Ancestral com overflow (ex.: fx-card) cortaria o painel absoluto:
1245
+ nesse caso o JS posiciona com position:fixed a partir do trigger. */
1246
+ :host([data-fixed]) .panel {
1247
+ position: fixed;
1248
+ }
1125
1249
  /* Padrão: abre para baixo. */
1126
1250
  :host([open]:not([data-placement='top'])) .panel {
1127
1251
  top: calc(100% + var(--fx-select-panel-offset, 4px));
@@ -1187,7 +1311,21 @@ function defineFxSelect() {
1187
1311
  return defineElement("fx-select", FxSelect);
1188
1312
  }
1189
1313
  defineFxSelect();
1190
- const INPUT_TYPES = /* @__PURE__ */ new Set(["text", "number", "password"]);
1314
+ const INPUT_TYPES = /* @__PURE__ */ new Set([
1315
+ "text",
1316
+ "number",
1317
+ "password",
1318
+ "email",
1319
+ "tel",
1320
+ "url",
1321
+ "search",
1322
+ "date",
1323
+ "time",
1324
+ "datetime-local",
1325
+ "month",
1326
+ "week",
1327
+ "color"
1328
+ ]);
1191
1329
  function normalizeInputType(value) {
1192
1330
  const type = (value || "text").toLowerCase();
1193
1331
  return INPUT_TYPES.has(type) ? type : "text";
@@ -1207,6 +1345,8 @@ const _FxInput = class _FxInput extends FxFormElement {
1207
1345
  "min",
1208
1346
  "max",
1209
1347
  "step",
1348
+ "clearable",
1349
+ "full",
1210
1350
  "icon",
1211
1351
  "icon-pos",
1212
1352
  "error",
@@ -1234,7 +1374,7 @@ const _FxInput = class _FxInput extends FxFormElement {
1234
1374
  return this.getAttr("value");
1235
1375
  }
1236
1376
  set value(value) {
1237
- this.setAttribute("value", value);
1377
+ this.setAttribute("value", value == null ? "" : String(value));
1238
1378
  this.syncInternalControl();
1239
1379
  this.updateFormValue();
1240
1380
  }
@@ -1244,6 +1384,24 @@ const _FxInput = class _FxInput extends FxFormElement {
1244
1384
  set disabled(value) {
1245
1385
  this.toggleAttr("disabled", Boolean(value));
1246
1386
  }
1387
+ get readonly() {
1388
+ return this.hasAttr("readonly");
1389
+ }
1390
+ set readonly(value) {
1391
+ this.toggleAttr("readonly", Boolean(value));
1392
+ }
1393
+ get clearable() {
1394
+ return this.hasAttr("clearable");
1395
+ }
1396
+ set clearable(value) {
1397
+ this.toggleAttr("clearable", Boolean(value));
1398
+ }
1399
+ get full() {
1400
+ return this.hasAttr("full");
1401
+ }
1402
+ set full(value) {
1403
+ this.toggleAttr("full", Boolean(value));
1404
+ }
1247
1405
  render() {
1248
1406
  const type = this.type;
1249
1407
  const placeholder = this.getAttr("placeholder");
@@ -1274,20 +1432,27 @@ const _FxInput = class _FxInput extends FxFormElement {
1274
1432
  field.value = this.getAttr("value");
1275
1433
  if (disabled) field.setAttribute("disabled", "");
1276
1434
  if (readonly) field.setAttribute("readonly", "");
1277
- const emit = (event) => {
1435
+ const notifyInput = () => {
1278
1436
  this.value = field.value;
1279
1437
  this.emitValueEvents();
1438
+ };
1439
+ const notifyChange = () => {
1440
+ this.value = field.value;
1280
1441
  this.dispatchEvent(
1281
- new CustomEvent(event, { bubbles: true, composed: true, detail: { value: field.value } })
1442
+ new CustomEvent("change", {
1443
+ bubbles: true,
1444
+ composed: true,
1445
+ detail: { value: field.value }
1446
+ })
1282
1447
  );
1283
1448
  };
1284
1449
  field.addEventListener("input", (e) => {
1285
1450
  e.stopPropagation();
1286
- emit("input");
1451
+ notifyInput();
1287
1452
  });
1288
1453
  field.addEventListener("change", (e) => {
1289
1454
  e.stopPropagation();
1290
- emit("change");
1455
+ notifyChange();
1291
1456
  });
1292
1457
  field.addEventListener("keydown", (e) => {
1293
1458
  if (e.key !== "Enter" || e.isComposing) return;
@@ -1303,8 +1468,15 @@ const _FxInput = class _FxInput extends FxFormElement {
1303
1468
  clearBtn?.addEventListener("click", () => {
1304
1469
  field.value = "";
1305
1470
  syncClear();
1306
- emit("input");
1307
- emit("change");
1471
+ this.value = "";
1472
+ this.emitValueEvents();
1473
+ this.dispatchEvent(
1474
+ new CustomEvent("change", {
1475
+ bubbles: true,
1476
+ composed: true,
1477
+ detail: { value: "" }
1478
+ })
1479
+ );
1308
1480
  field.focus();
1309
1481
  });
1310
1482
  field.addEventListener("input", syncClear);
@@ -1494,7 +1666,8 @@ const _FxSwitch = class _FxSwitch extends FxFormElement {
1494
1666
  return this.getAttr("value");
1495
1667
  }
1496
1668
  set value(v) {
1497
- this.setAttribute("value", v);
1669
+ this.setAttribute("value", v == null ? "" : String(v));
1670
+ this.updateFormValue();
1498
1671
  }
1499
1672
  /** No FormData: `value` (ou 'on') quando ligado; null (não envia) quando não. */
1500
1673
  getFormValue() {
@@ -1612,7 +1785,23 @@ const _FxMultiselect = class _FxMultiselect extends FxFormElement {
1612
1785
  this.selected = /* @__PURE__ */ new Set();
1613
1786
  }
1614
1787
  static get observedAttributes() {
1615
- return ["disabled", "placeholder", "error", "invalid", "success", "valid"];
1788
+ return [
1789
+ "values",
1790
+ "size",
1791
+ "disabled",
1792
+ "placeholder",
1793
+ "searchable",
1794
+ "clearable",
1795
+ "full",
1796
+ "error",
1797
+ "invalid",
1798
+ "success",
1799
+ "valid"
1800
+ ];
1801
+ }
1802
+ attributeChangedCallback(name, oldValue, newValue) {
1803
+ if (name === "values") this.selected = new Set(this.values);
1804
+ super.attributeChangedCallback(name, oldValue, newValue);
1616
1805
  }
1617
1806
  /** Valores selecionados (CSV no atributo / array na propriedade). */
1618
1807
  get values() {
@@ -1620,7 +1809,15 @@ const _FxMultiselect = class _FxMultiselect extends FxFormElement {
1620
1809
  return csv ? csv.split(",").map((v) => v.trim()).filter(Boolean) : [];
1621
1810
  }
1622
1811
  set values(list) {
1623
- this.setAttribute("values", list.join(","));
1812
+ const values = Array.isArray(list) ? list.map(String) : [];
1813
+ this.selected = new Set(values);
1814
+ const csv = values.join(",");
1815
+ if (this.getAttr("values") !== csv) {
1816
+ this.setAttribute("values", csv);
1817
+ } else if (this.isConnected) {
1818
+ this.render();
1819
+ }
1820
+ this.updateFormValue();
1624
1821
  }
1625
1822
  /** No FormData do form: uma entrada `name` por valor selecionado. */
1626
1823
  getFormValue() {
@@ -1636,6 +1833,24 @@ const _FxMultiselect = class _FxMultiselect extends FxFormElement {
1636
1833
  set disabled(value) {
1637
1834
  this.toggleAttr("disabled", Boolean(value));
1638
1835
  }
1836
+ get searchable() {
1837
+ return this.hasAttr("searchable");
1838
+ }
1839
+ set searchable(value) {
1840
+ this.toggleAttr("searchable", Boolean(value));
1841
+ }
1842
+ get clearable() {
1843
+ return this.hasAttr("clearable");
1844
+ }
1845
+ set clearable(value) {
1846
+ this.toggleAttr("clearable", Boolean(value));
1847
+ }
1848
+ get full() {
1849
+ return this.hasAttr("full");
1850
+ }
1851
+ set full(value) {
1852
+ this.toggleAttr("full", Boolean(value));
1853
+ }
1639
1854
  get options() {
1640
1855
  return [...this.querySelectorAll("option")].map((o) => ({
1641
1856
  value: o.getAttribute("value") ?? o.textContent?.trim() ?? "",
@@ -1647,7 +1862,15 @@ const _FxMultiselect = class _FxMultiselect extends FxFormElement {
1647
1862
  if (!this._initialValues) this._initialValues = [...this.values];
1648
1863
  super.connectedCallback();
1649
1864
  this.observer = new MutationObserver(() => this.render());
1650
- this.observer.observe(this, { childList: true, subtree: true, characterData: true });
1865
+ this.observer.observe(this, {
1866
+ childList: true,
1867
+ subtree: true,
1868
+ characterData: true,
1869
+ // Vue atualiza `:value`/`:selected` das <option> no light DOM; sem
1870
+ // observar atributos o label/valor antigo permanecia em tela.
1871
+ attributes: true,
1872
+ attributeFilter: ["value", "selected"]
1873
+ });
1651
1874
  this.docListener = (e) => {
1652
1875
  if (!this.hasAttr("open")) return;
1653
1876
  if (e.composedPath().includes(this)) return;
@@ -1663,8 +1886,10 @@ const _FxMultiselect = class _FxMultiselect extends FxFormElement {
1663
1886
  }
1664
1887
  /** Aplica a seleção, reflete e emite change. */
1665
1888
  commit() {
1666
- this.values = [...this.selected];
1667
- this.render();
1889
+ const next = [...this.selected];
1890
+ const same = next.join(",") === this.values.join(",");
1891
+ this.values = next;
1892
+ if (same && this.isConnected) this.render();
1668
1893
  this.updateFormValue();
1669
1894
  this.dispatchEvent(
1670
1895
  new CustomEvent("change", {
@@ -1685,8 +1910,6 @@ const _FxMultiselect = class _FxMultiselect extends FxFormElement {
1685
1910
  formResetCallback() {
1686
1911
  const initial = this._initialValues ?? [];
1687
1912
  this.values = initial;
1688
- this.selected = new Set(initial);
1689
- this.render();
1690
1913
  this.updateFormValue();
1691
1914
  }
1692
1915
  toggleValue(value) {
@@ -1715,8 +1938,10 @@ const _FxMultiselect = class _FxMultiselect extends FxFormElement {
1715
1938
  const wasOpen = this.hasAttr("open");
1716
1939
  const search = this.root.querySelector(".search")?.value ?? "";
1717
1940
  const opts = this.options;
1718
- for (const v of [...this.selected]) {
1719
- if (!opts.some((o) => o.value === v)) this.selected.delete(v);
1941
+ if (opts.length > 0) {
1942
+ for (const v of [...this.selected]) {
1943
+ if (!opts.some((o) => o.value === v)) this.selected.delete(v);
1944
+ }
1720
1945
  }
1721
1946
  const chips = opts.filter((o) => this.selected.has(o.value));
1722
1947
  const placeholder = this.getAttr("placeholder", "Selecione…");
@@ -2496,13 +2721,16 @@ defineFxCalendar();
2496
2721
  const _FxDatepicker = class _FxDatepicker extends FxFormElement {
2497
2722
  constructor() {
2498
2723
  super(...arguments);
2724
+ this._suppressRender = false;
2499
2725
  this.time = { h: 0, m: 0, s: 0 };
2500
2726
  }
2501
2727
  static get observedAttributes() {
2502
- return ["value", "start", "end", "values", "mode", "min", "max", "placeholder", "disabled", "format", "show-time", "free-text", "error", "invalid", "success", "valid"];
2728
+ return ["value", "start", "end", "values", "mode", "min", "max", "placeholder", "disabled", "format", "show-time", "free-text", "clearable", "full", "error", "invalid", "success", "valid"];
2503
2729
  }
2504
2730
  attributeChangedCallback(name, oldValue, newValue) {
2505
- super.attributeChangedCallback(name, oldValue, newValue);
2731
+ if (!this._suppressRender) {
2732
+ super.attributeChangedCallback(name, oldValue, newValue);
2733
+ }
2506
2734
  if (name === "value" || name === "start" || name === "end" || name === "values") {
2507
2735
  this.updateFormValue();
2508
2736
  }
@@ -2538,7 +2766,19 @@ const _FxDatepicker = class _FxDatepicker extends FxFormElement {
2538
2766
  return this.getAttr("value");
2539
2767
  }
2540
2768
  set value(v) {
2541
- this.setAttribute("value", v);
2769
+ this.setAttribute("value", v == null ? "" : String(v));
2770
+ }
2771
+ get start() {
2772
+ return this.getAttr("start");
2773
+ }
2774
+ set start(v) {
2775
+ this.setAttribute("start", v == null ? "" : String(v));
2776
+ }
2777
+ get end() {
2778
+ return this.getAttr("end");
2779
+ }
2780
+ set end(v) {
2781
+ this.setAttribute("end", v == null ? "" : String(v));
2542
2782
  }
2543
2783
  /** Datas do modo múltiplo (CSV no atributo). */
2544
2784
  get values() {
@@ -2546,7 +2786,8 @@ const _FxDatepicker = class _FxDatepicker extends FxFormElement {
2546
2786
  return csv ? csv.split(",").map((v) => v.trim()).filter(Boolean) : [];
2547
2787
  }
2548
2788
  set values(list) {
2549
- this.setAttribute("values", list.join(","));
2789
+ const values = Array.isArray(list) ? list.map(String) : [];
2790
+ this.setAttribute("values", values.join(","));
2550
2791
  }
2551
2792
  get disabled() {
2552
2793
  return this.hasAttr("disabled");
@@ -2554,6 +2795,31 @@ const _FxDatepicker = class _FxDatepicker extends FxFormElement {
2554
2795
  set disabled(value) {
2555
2796
  this.toggleAttr("disabled", Boolean(value));
2556
2797
  }
2798
+ /** `:show-time` procura a propriedade LITERAL no Vue (sem camelização). */
2799
+ get "show-time"() {
2800
+ return this.hasAttr("show-time");
2801
+ }
2802
+ set "show-time"(value) {
2803
+ this.toggleAttr("show-time", Boolean(value));
2804
+ }
2805
+ get "free-text"() {
2806
+ return this.hasAttr("free-text");
2807
+ }
2808
+ set "free-text"(value) {
2809
+ this.toggleAttr("free-text", Boolean(value));
2810
+ }
2811
+ get clearable() {
2812
+ return this.hasAttr("clearable");
2813
+ }
2814
+ set clearable(value) {
2815
+ this.toggleAttr("clearable", Boolean(value));
2816
+ }
2817
+ get full() {
2818
+ return this.hasAttr("full");
2819
+ }
2820
+ set full(value) {
2821
+ this.toggleAttr("full", Boolean(value));
2822
+ }
2557
2823
  connectedCallback() {
2558
2824
  if (!this._initialState) {
2559
2825
  this._initialState = {
@@ -2578,11 +2844,17 @@ const _FxDatepicker = class _FxDatepicker extends FxFormElement {
2578
2844
  /** form.reset(): restaura value/start/end/values iniciais (re-render via atributos observados). */
2579
2845
  formResetCallback() {
2580
2846
  const s = this._initialState ?? { value: "", start: "", end: "", values: "" };
2581
- this.setAttribute("value", s.value);
2582
- this.setAttribute("start", s.start);
2583
- this.setAttribute("end", s.end);
2584
- this.setAttribute("values", s.values);
2847
+ this._suppressRender = true;
2848
+ try {
2849
+ this.setAttribute("value", s.value);
2850
+ this.setAttribute("start", s.start);
2851
+ this.setAttribute("end", s.end);
2852
+ this.setAttribute("values", s.values);
2853
+ } finally {
2854
+ this._suppressRender = false;
2855
+ }
2585
2856
  this.updateFormValue();
2857
+ if (this.isConnected) this.render();
2586
2858
  }
2587
2859
  /** Formata uma data ISO ("YYYY-MM-DD" ou com "THH:mm:ss") segundo o atributo format. */
2588
2860
  formatDate(iso) {
@@ -2690,10 +2962,15 @@ const _FxDatepicker = class _FxDatepicker extends FxFormElement {
2690
2962
  });
2691
2963
  this.root.querySelector(".clear")?.addEventListener("click", (e) => {
2692
2964
  e.stopPropagation();
2693
- this.removeAttribute("value");
2694
- this.removeAttribute("start");
2695
- this.removeAttribute("end");
2696
- this.removeAttribute("values");
2965
+ this._suppressRender = true;
2966
+ try {
2967
+ this.removeAttribute("value");
2968
+ this.removeAttribute("start");
2969
+ this.removeAttribute("end");
2970
+ this.removeAttribute("values");
2971
+ } finally {
2972
+ this._suppressRender = false;
2973
+ }
2697
2974
  this.removeAttribute("open");
2698
2975
  this.render();
2699
2976
  this.emitChange({});
@@ -2701,19 +2978,24 @@ const _FxDatepicker = class _FxDatepicker extends FxFormElement {
2701
2978
  const cal = this.root.querySelector(".pop fx-calendar");
2702
2979
  cal?.addEventListener("change", (e) => {
2703
2980
  const d = e.detail ?? {};
2704
- if (d.start !== void 0 || d.end !== void 0) {
2705
- if (d.start) this.setAttribute("start", this.withTime(d.start));
2706
- else this.removeAttribute("start");
2707
- if (d.end) this.setAttribute("end", this.withTime(d.end));
2708
- else this.removeAttribute("end");
2709
- }
2710
- if (d.values !== void 0) {
2711
- this.values = d.values.map((v) => this.withTime(v));
2712
- }
2713
- if (d.value !== void 0) {
2714
- if (d.value) this.setAttribute("value", this.withTime(d.value));
2715
- else this.removeAttribute("value");
2716
- if (this.mode === "single") this.removeAttribute("open");
2981
+ this._suppressRender = true;
2982
+ try {
2983
+ if (d.start !== void 0 || d.end !== void 0) {
2984
+ if (d.start) this.setAttribute("start", this.withTime(d.start));
2985
+ else this.removeAttribute("start");
2986
+ if (d.end) this.setAttribute("end", this.withTime(d.end));
2987
+ else this.removeAttribute("end");
2988
+ }
2989
+ if (d.values !== void 0) {
2990
+ this.values = d.values.map((v) => this.withTime(v));
2991
+ }
2992
+ if (d.value !== void 0) {
2993
+ if (d.value) this.setAttribute("value", this.withTime(d.value));
2994
+ else this.removeAttribute("value");
2995
+ if (this.mode === "single") this.removeAttribute("open");
2996
+ }
2997
+ } finally {
2998
+ this._suppressRender = false;
2717
2999
  }
2718
3000
  this.render();
2719
3001
  this.emitChange(d);
@@ -2740,11 +3022,16 @@ const _FxDatepicker = class _FxDatepicker extends FxFormElement {
2740
3022
  s: clamp(s?.value, 59)
2741
3023
  };
2742
3024
  const reattach = (iso) => iso.split("T")[0] ? `${iso.split("T")[0]}T${this.timeText()}` : iso;
2743
- if (this.value) this.setAttribute("value", reattach(this.value));
2744
- if (this.getAttr("start")) this.setAttribute("start", reattach(this.getAttr("start")));
2745
- if (this.getAttr("end")) this.setAttribute("end", reattach(this.getAttr("end")));
2746
- if (this.values.length) {
2747
- this.values = this.values.map((v) => reattach(v));
3025
+ this._suppressRender = true;
3026
+ try {
3027
+ if (this.value) this.setAttribute("value", reattach(this.value));
3028
+ if (this.getAttr("start")) this.setAttribute("start", reattach(this.getAttr("start")));
3029
+ if (this.getAttr("end")) this.setAttribute("end", reattach(this.getAttr("end")));
3030
+ if (this.values.length) {
3031
+ this.values = this.values.map((v) => reattach(v));
3032
+ }
3033
+ } finally {
3034
+ this._suppressRender = false;
2748
3035
  }
2749
3036
  const display2 = this.root.querySelector(".display");
2750
3037
  if (display2) display2.value = this.displayText();
@@ -2800,23 +3087,32 @@ const _FxDatepicker = class _FxDatepicker extends FxFormElement {
2800
3087
  setTimeout(() => input.classList.remove("invalid"), 1500);
2801
3088
  return;
2802
3089
  }
3090
+ this._suppressRender = true;
3091
+ try {
3092
+ if (this.mode === "single") {
3093
+ this.setAttribute("value", this.withTime(parsed));
3094
+ } else if (this.mode === "range") {
3095
+ if (!this.getAttr("start") || this.getAttr("start") && this.getAttr("end")) {
3096
+ this.setAttribute("start", this.withTime(parsed));
3097
+ this.removeAttribute("end");
3098
+ } else {
3099
+ const [a, b] = [this.getAttr("start").split("T")[0], parsed].sort();
3100
+ this.setAttribute("start", this.withTime(a));
3101
+ this.setAttribute("end", this.withTime(b));
3102
+ }
3103
+ } else {
3104
+ const list = this.values.filter((v) => v.split("T")[0] !== parsed);
3105
+ list.push(this.withTime(parsed));
3106
+ this.values = list;
3107
+ }
3108
+ } finally {
3109
+ this._suppressRender = false;
3110
+ }
2803
3111
  if (this.mode === "single") {
2804
- this.setAttribute("value", this.withTime(parsed));
2805
3112
  this.emitChange({ value: this.value });
2806
3113
  } else if (this.mode === "range") {
2807
- if (!this.getAttr("start") || this.getAttr("start") && this.getAttr("end")) {
2808
- this.setAttribute("start", this.withTime(parsed));
2809
- this.removeAttribute("end");
2810
- } else {
2811
- const [a, b] = [this.getAttr("start").split("T")[0], parsed].sort();
2812
- this.setAttribute("start", this.withTime(a));
2813
- this.setAttribute("end", this.withTime(b));
2814
- }
2815
3114
  this.emitChange({ start: this.getAttr("start"), end: this.getAttr("end") });
2816
3115
  } else {
2817
- const list = this.values.filter((v) => v.split("T")[0] !== parsed);
2818
- list.push(this.withTime(parsed));
2819
- this.values = list;
2820
3116
  this.emitChange({ values: this.values });
2821
3117
  }
2822
3118
  this.render();
@@ -2987,7 +3283,7 @@ const _FxCheckbox = class _FxCheckbox extends FxFormElement {
2987
3283
  this._initialCheckedCaptured = false;
2988
3284
  }
2989
3285
  static get observedAttributes() {
2990
- return ["checked", "disabled", "indeterminate"];
3286
+ return ["checked", "disabled", "indeterminate", "size"];
2991
3287
  }
2992
3288
  get checked() {
2993
3289
  return this.hasAttr("checked");
@@ -3016,7 +3312,8 @@ const _FxCheckbox = class _FxCheckbox extends FxFormElement {
3016
3312
  return this.getAttr("value");
3017
3313
  }
3018
3314
  set value(v) {
3019
- this.setAttribute("value", v);
3315
+ this.setAttribute("value", v == null ? "" : String(v));
3316
+ this.updateFormValue();
3020
3317
  }
3021
3318
  /** Tamanho do controle. Padrão: `'md'`. */
3022
3319
  get size() {
@@ -3028,9 +3325,7 @@ const _FxCheckbox = class _FxCheckbox extends FxFormElement {
3028
3325
  }
3029
3326
  render() {
3030
3327
  const px = this.size === "sm" ? "14px" : this.size === "lg" ? "22px" : "18px";
3031
- if (!this.style.getPropertyValue("--fx-size-checkbox")) {
3032
- this.style.setProperty("--fx-size-checkbox", px);
3033
- }
3328
+ this.style.setProperty("--_fx-size-checkbox", px);
3034
3329
  this.setTemplate(`
3035
3330
  <span class="box" part="box" role="checkbox" tabindex="0"
3036
3331
  aria-checked="${this.indeterminate ? "mixed" : this.checked}"
@@ -3102,8 +3397,8 @@ _FxCheckbox.styles = css`
3102
3397
  -webkit-tap-highlight-color: transparent;
3103
3398
  }
3104
3399
  .control {
3105
- width: var(--fx-size-checkbox, 18px);
3106
- height: var(--fx-size-checkbox, 18px);
3400
+ width: var(--fx-size-checkbox, var(--_fx-size-checkbox, 18px));
3401
+ height: var(--fx-size-checkbox, var(--_fx-size-checkbox, 18px));
3107
3402
  flex-shrink: 0;
3108
3403
  display: inline-flex;
3109
3404
  align-items: center;
@@ -3112,7 +3407,7 @@ _FxCheckbox.styles = css`
3112
3407
  border-radius: calc(var(--fx-radius-sm) + 2px);
3113
3408
  background: var(--fx-surface-background);
3114
3409
  color: #fff;
3115
- font-size: calc(var(--fx-size-checkbox, 18px) - 6px);
3410
+ font-size: calc(var(--fx-size-checkbox, var(--_fx-size-checkbox, 18px)) - 6px);
3116
3411
  line-height: 1;
3117
3412
  transition:
3118
3413
  background-color var(--fx-motion-duration-fast) var(--fx-motion-easing),
@@ -3159,7 +3454,7 @@ const _FxRadio = class _FxRadio extends FxFormElement {
3159
3454
  this._initialCheckedCaptured = false;
3160
3455
  }
3161
3456
  static get observedAttributes() {
3162
- return ["checked", "disabled"];
3457
+ return ["checked", "disabled", "size"];
3163
3458
  }
3164
3459
  get checked() {
3165
3460
  return this.hasAttr("checked");
@@ -3192,6 +3487,14 @@ const _FxRadio = class _FxRadio extends FxFormElement {
3192
3487
  getFormValue() {
3193
3488
  return this.checked ? this.getAttr("value") || "on" : null;
3194
3489
  }
3490
+ /** getter/setter `value` — sem o setter o Vue não consegue escrever a propriedade. */
3491
+ get value() {
3492
+ return this.getAttr("value");
3493
+ }
3494
+ set value(v) {
3495
+ this.setAttribute("value", v == null ? "" : String(v));
3496
+ this.updateFormValue();
3497
+ }
3195
3498
  /** Tamanho do controle. Padrão: `'md'`. */
3196
3499
  get size() {
3197
3500
  const s = this.getAttr("size", "md");
@@ -3202,9 +3505,7 @@ const _FxRadio = class _FxRadio extends FxFormElement {
3202
3505
  }
3203
3506
  render() {
3204
3507
  const px = this.size === "sm" ? "14px" : this.size === "lg" ? "22px" : "18px";
3205
- if (!this.style.getPropertyValue("--fx-size-radio")) {
3206
- this.style.setProperty("--fx-size-radio", px);
3207
- }
3508
+ this.style.setProperty("--_fx-size-radio", px);
3208
3509
  this.setTemplate(`
3209
3510
  <span class="box" part="box" role="radio" tabindex="0"
3210
3511
  aria-checked="${this.checked}" aria-disabled="${this.disabled}">
@@ -3292,8 +3593,8 @@ _FxRadio.styles = css`
3292
3593
  -webkit-tap-highlight-color: transparent;
3293
3594
  }
3294
3595
  .control {
3295
- width: var(--fx-size-radio, 18px);
3296
- height: var(--fx-size-radio, 18px);
3596
+ width: var(--fx-size-radio, var(--_fx-size-radio, 18px));
3597
+ height: var(--fx-size-radio, var(--_fx-size-radio, 18px));
3297
3598
  flex-shrink: 0;
3298
3599
  display: inline-flex;
3299
3600
  align-items: center;
@@ -3805,6 +4106,7 @@ const _FxTable = class _FxTable extends FxElement {
3805
4106
  this.sortDir = 1;
3806
4107
  this.filters = {};
3807
4108
  this.globalSearch = "";
4109
+ this._boundToolbarInputs = /* @__PURE__ */ new WeakSet();
3808
4110
  this._shellKey = "";
3809
4111
  }
3810
4112
  static get observedAttributes() {
@@ -3852,6 +4154,44 @@ const _FxTable = class _FxTable extends FxElement {
3852
4154
  get lazy() {
3853
4155
  return this.hasAttr("lazy");
3854
4156
  }
4157
+ set lazy(value) {
4158
+ this.toggleAttr("lazy", Boolean(value));
4159
+ }
4160
+ get pagination() {
4161
+ return this.hasAttr("pagination");
4162
+ }
4163
+ set pagination(value) {
4164
+ this.toggleAttr("pagination", Boolean(value));
4165
+ }
4166
+ get striped() {
4167
+ return this.hasAttr("striped");
4168
+ }
4169
+ set striped(value) {
4170
+ this.toggleAttr("striped", Boolean(value));
4171
+ }
4172
+ get hover() {
4173
+ return this.hasAttr("hover");
4174
+ }
4175
+ set hover(value) {
4176
+ this.toggleAttr("hover", Boolean(value));
4177
+ }
4178
+ get loading() {
4179
+ return this.hasAttr("loading");
4180
+ }
4181
+ set loading(value) {
4182
+ this.toggleAttr("loading", Boolean(value));
4183
+ }
4184
+ /**
4185
+ * Vue não cameliza atributos de elementos customizados:
4186
+ * `:no-borders-x="false"` procura a propriedade LITERAL
4187
+ * `'no-borders-x'` — o accessor literal evita o contorno `.attr`.
4188
+ */
4189
+ get "no-borders-x"() {
4190
+ return this.hasAttr("no-borders-x");
4191
+ }
4192
+ set "no-borders-x"(value) {
4193
+ this.toggleAttr("no-borders-x", Boolean(value));
4194
+ }
3855
4195
  get columns() {
3856
4196
  return [...this.querySelectorAll("fx-column")].map((c) => {
3857
4197
  const tpl = this._resolveCellTemplate(c);
@@ -3935,7 +4275,14 @@ const _FxTable = class _FxTable extends FxElement {
3935
4275
  loadFenixIconsFont();
3936
4276
  super.connectedCallback();
3937
4277
  this._columnObserver = new MutationObserver(() => this.render());
3938
- this._columnObserver.observe(this, { childList: true, subtree: true });
4278
+ this._columnObserver.observe(this, {
4279
+ childList: true,
4280
+ subtree: true,
4281
+ // Colunas com header/field/align/sortable/filterable alterados
4282
+ // em runtime também precisam re-renderizar.
4283
+ attributes: true,
4284
+ attributeFilter: ["header", "field", "align", "sortable", "filterable", "template"]
4285
+ });
3939
4286
  }
3940
4287
  disconnectedCallback() {
3941
4288
  this._columnObserver?.disconnect();
@@ -4017,7 +4364,14 @@ const _FxTable = class _FxTable extends FxElement {
4017
4364
  const pages = Math.max(1, Math.ceil(total / this.rowsPerPage));
4018
4365
  if (this.page >= pages) this.page = pages - 1;
4019
4366
  const toolbar = this.toolbarTemplate;
4020
- const toolbarHtml = toolbar ? `<div class="toolbar" part="toolbar">${toolbar.innerHTML}</div>` : "";
4367
+ const toolbarSlotted = toolbar?.localName === "fx-toolbar";
4368
+ const toolbarHasContent = Boolean(
4369
+ toolbar && (toolbarSlotted ? toolbar.children.length > 0 || (toolbar.textContent ?? "").trim() !== "" : toolbar.innerHTML.trim() !== "")
4370
+ );
4371
+ if (toolbarSlotted && toolbarHasContent) {
4372
+ toolbar.setAttribute("slot", "fx-toolbar");
4373
+ }
4374
+ const toolbarHtml = toolbarHasContent ? `<div class="toolbar" part="toolbar">${toolbarSlotted ? '<slot name="fx-toolbar"></slot>' : toolbar.innerHTML}</div>` : "";
4021
4375
  const headHtml = cols.map((c) => {
4022
4376
  const isSorted = this.sortField === c.field;
4023
4377
  const ind = isSorted ? this.sortDir === 1 ? "▲" : "▼" : c.sortable ? "⇅" : "";
@@ -4040,7 +4394,7 @@ const _FxTable = class _FxTable extends FxElement {
4040
4394
  <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>
4041
4395
  <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>
4042
4396
  <label class="info">${esc("Por página:")}
4043
- <fx-select class="rows-sel" size="sm" value="${this.rowsPerPage}" aria-label="Itens por página">${this.getAttr(
4397
+ <fx-select class="rows-sel" size="sm" placement="top" value="${this.rowsPerPage}" aria-label="Itens por página">${this.getAttr(
4044
4398
  "rows-options",
4045
4399
  "5,10,20,50"
4046
4400
  ).split(",").map((n) => n.trim()).map((n) => `<option value="${esc(n)}">${esc(n)}</option>`).join("")}</fx-select>
@@ -4061,13 +4415,15 @@ const _FxTable = class _FxTable extends FxElement {
4061
4415
  if (tbody) {
4062
4416
  tbody.innerHTML = bodyHtml;
4063
4417
  this._bindRowClicks(rows);
4418
+ this._bindSlottedToolbarSearch(toolbar);
4419
+ this._restoreToolbarSearch();
4064
4420
  return;
4065
4421
  }
4066
4422
  }
4067
4423
  this._shellKey = shellKey;
4068
4424
  this.setTemplate(`
4069
4425
  ${toolbarHtml}
4070
- <div class="frame${toolbar ? " has-toolbar" : ""}" part="frame">
4426
+ <div class="frame${toolbarHasContent ? " has-toolbar" : ""}" part="frame">
4071
4427
  <div class="table-wrap">
4072
4428
  <div class="scroll">
4073
4429
  <table part="table">
@@ -4128,21 +4484,10 @@ const _FxTable = class _FxTable extends FxElement {
4128
4484
  });
4129
4485
  inp.addEventListener("click", (e) => e.stopPropagation());
4130
4486
  });
4131
- this.root.querySelectorAll("[data-search-fields]").forEach((inp) => {
4132
- inp.addEventListener("input", () => {
4133
- this.globalSearch = inp.value;
4134
- this.page = 0;
4135
- const pos = inp.selectionStart;
4136
- this.render();
4137
- const again = this.root.querySelector(
4138
- "[data-search-fields]"
4139
- );
4140
- if (again) {
4141
- again.focus();
4142
- again.setSelectionRange(pos, pos);
4143
- }
4144
- });
4145
- });
4487
+ this.root.querySelectorAll("[data-search-fields]").forEach((inp) => this._bindSearchInput(inp, false));
4488
+ if (toolbarSlotted && toolbar) {
4489
+ this._bindSlottedToolbarSearch(toolbar);
4490
+ }
4146
4491
  this.root.querySelectorAll(".pg-btn").forEach((btn) => {
4147
4492
  btn.addEventListener("click", () => {
4148
4493
  const pg = btn.dataset.pg;
@@ -4195,11 +4540,47 @@ const _FxTable = class _FxTable extends FxElement {
4195
4540
  });
4196
4541
  });
4197
4542
  }
4543
+ /** Liga (uma vez) os inputs de busca de um <fx-toolbar> slotted. */
4544
+ _bindSlottedToolbarSearch(toolbar) {
4545
+ if (!toolbar || toolbar.localName !== "fx-toolbar") return;
4546
+ toolbar.querySelectorAll("[data-search-fields]").forEach((inp) => {
4547
+ if (this._boundToolbarInputs.has(inp)) return;
4548
+ this._boundToolbarInputs.add(inp);
4549
+ this._bindSearchInput(inp, true);
4550
+ });
4551
+ }
4552
+ /**
4553
+ * Liga um input da toolbar à busca global.
4554
+ * `persistent` = input slotted (não é recriado pelo render): o foco volta
4555
+ * no próprio nó. Inputs clonados são substituídos, então o foco é
4556
+ * restaurado no clone novo do shadow.
4557
+ */
4558
+ _bindSearchInput(inp, persistent) {
4559
+ inp.addEventListener("input", () => {
4560
+ this.globalSearch = inp.value;
4561
+ this.page = 0;
4562
+ const pos = inp.selectionStart;
4563
+ this.render();
4564
+ const again = persistent && inp.isConnected ? inp : this.root.querySelector("[data-search-fields]");
4565
+ if (again) {
4566
+ again.focus();
4567
+ again.setSelectionRange(pos, pos);
4568
+ }
4569
+ });
4570
+ }
4198
4571
  _restoreToolbarSearch() {
4199
- const input = this.root.querySelector(
4572
+ const shadow = this.root.querySelector(
4200
4573
  "[data-search-fields]"
4201
4574
  );
4202
- if (input && this.globalSearch) input.value = this.globalSearch;
4575
+ if (shadow && this.globalSearch) shadow.value = this.globalSearch;
4576
+ const toolbar = this.toolbarTemplate;
4577
+ if (toolbar?.localName === "fx-toolbar") {
4578
+ toolbar.querySelectorAll("[data-search-fields]").forEach((input) => {
4579
+ if (this.globalSearch && input.value !== this.globalSearch) {
4580
+ input.value = this.globalSearch;
4581
+ }
4582
+ });
4583
+ }
4203
4584
  }
4204
4585
  };
4205
4586
  _FxTable.styles = css`
@@ -4455,6 +4836,11 @@ _FxTable.styles = css`
4455
4836
  .toolbar:empty {
4456
4837
  display: none;
4457
4838
  }
4839
+ /* <fx-toolbar> é apenas um invólucro no light DOM: o slot projeta
4840
+ seus filhos (com listeners do Vue preservados) direto no flex. */
4841
+ ::slotted(fx-toolbar) {
4842
+ display: contents;
4843
+ }
4458
4844
  .loading-overlay {
4459
4845
  position: absolute;
4460
4846
  inset: 0;
@@ -4505,7 +4891,51 @@ const _FxFloatlabel = class _FxFloatlabel extends FxElement {
4505
4891
  this._labelClickBound = false;
4506
4892
  }
4507
4893
  static get observedAttributes() {
4508
- return ["variant", "error", "invalid", "success", "valid", "full", "icon", "icon-pos"];
4894
+ return ["variant", "error", "invalid", "success", "valid", "full", "icon", "icon-pos", "error-text"];
4895
+ }
4896
+ get error() {
4897
+ return this.hasAttr("error");
4898
+ }
4899
+ set error(value) {
4900
+ this.toggleAttr("error", Boolean(value));
4901
+ }
4902
+ get invalid() {
4903
+ return this.hasAttr("invalid");
4904
+ }
4905
+ set invalid(value) {
4906
+ this.toggleAttr("invalid", Boolean(value));
4907
+ }
4908
+ get success() {
4909
+ return this.hasAttr("success");
4910
+ }
4911
+ set success(value) {
4912
+ this.toggleAttr("success", Boolean(value));
4913
+ }
4914
+ get valid() {
4915
+ return this.hasAttr("valid");
4916
+ }
4917
+ set valid(value) {
4918
+ this.toggleAttr("valid", Boolean(value));
4919
+ }
4920
+ get full() {
4921
+ return this.hasAttr("full");
4922
+ }
4923
+ set full(value) {
4924
+ this.toggleAttr("full", Boolean(value));
4925
+ }
4926
+ get errorText() {
4927
+ return this.getAttr("error-text");
4928
+ }
4929
+ set errorText(value) {
4930
+ this.setAttribute("error-text", value == null ? "" : String(value));
4931
+ }
4932
+ connectedCallback() {
4933
+ super.connectedCallback();
4934
+ this._labelObserver = new MutationObserver(() => {
4935
+ const label = this.findLabel();
4936
+ if (label && label !== this._label) this.render();
4937
+ });
4938
+ this._labelObserver.observe(this, { childList: true });
4509
4939
  }
4510
4940
  render() {
4511
4941
  const control = this.findControl();
@@ -4549,6 +4979,8 @@ const _FxFloatlabel = class _FxFloatlabel extends FxElement {
4549
4979
  disconnectedCallback() {
4550
4980
  super.disconnectedCallback();
4551
4981
  this.observer?.disconnect();
4982
+ this._labelObserver?.disconnect();
4983
+ this._labelObserver = void 0;
4552
4984
  }
4553
4985
  /** Campo de controle = qualquer filho direto que não seja label. */
4554
4986
  findControl() {
@@ -4838,7 +5270,7 @@ function defineFxFloatlabel() {
4838
5270
  defineFxFloatlabel();
4839
5271
  const _FxTextarea = class _FxTextarea extends FxFormElement {
4840
5272
  static get observedAttributes() {
4841
- return ["size", "placeholder", "disabled", "readonly", "rows", "maxlength", "error", "invalid", "success", "valid"];
5273
+ return ["size", "placeholder", "disabled", "readonly", "full", "rows", "maxlength", "error", "invalid", "success", "valid"];
4842
5274
  }
4843
5275
  get size() {
4844
5276
  const s = this.getAttr("size", "md");
@@ -4851,10 +5283,22 @@ const _FxTextarea = class _FxTextarea extends FxFormElement {
4851
5283
  return this.getAttr("value");
4852
5284
  }
4853
5285
  set value(value) {
4854
- this.setAttribute("value", value);
5286
+ this.setAttribute("value", value == null ? "" : String(value));
4855
5287
  this.syncInternalControl();
4856
5288
  this.updateFormValue();
4857
5289
  }
5290
+ get readonly() {
5291
+ return this.hasAttr("readonly");
5292
+ }
5293
+ set readonly(value) {
5294
+ this.toggleAttr("readonly", Boolean(value));
5295
+ }
5296
+ get full() {
5297
+ return this.hasAttr("full");
5298
+ }
5299
+ set full(value) {
5300
+ this.toggleAttr("full", Boolean(value));
5301
+ }
4858
5302
  render() {
4859
5303
  const placeholder = this.getAttr("placeholder");
4860
5304
  const readonly = this.hasAttr("readonly");
@@ -4871,20 +5315,27 @@ const _FxTextarea = class _FxTextarea extends FxFormElement {
4871
5315
  field.value = this.getAttr("value");
4872
5316
  if (disabled) field.setAttribute("disabled", "");
4873
5317
  if (readonly) field.setAttribute("readonly", "");
4874
- const emit = (event) => {
5318
+ const notifyInput = () => {
4875
5319
  this.value = field.value;
4876
5320
  this.emitValueEvents();
5321
+ };
5322
+ const notifyChange = () => {
5323
+ this.value = field.value;
4877
5324
  this.dispatchEvent(
4878
- new CustomEvent(event, { bubbles: true, composed: true, detail: { value: field.value } })
5325
+ new CustomEvent("change", {
5326
+ bubbles: true,
5327
+ composed: true,
5328
+ detail: { value: field.value }
5329
+ })
4879
5330
  );
4880
5331
  };
4881
5332
  field.addEventListener("input", (e) => {
4882
5333
  e.stopPropagation();
4883
- emit("input");
5334
+ notifyInput();
4884
5335
  });
4885
5336
  field.addEventListener("change", (e) => {
4886
5337
  e.stopPropagation();
4887
- emit("change");
5338
+ notifyChange();
4888
5339
  });
4889
5340
  this.updateFormValue();
4890
5341
  }
@@ -8530,10 +8981,61 @@ function defineFxEditor() {
8530
8981
  return defineElement("fx-editor", FxEditor);
8531
8982
  }
8532
8983
  defineFxEditor();
8984
+ const FOCUSABLE_SELECTOR = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"]), [contenteditable="true"]';
8985
+ function usable(nodes) {
8986
+ return [...nodes].filter(
8987
+ (el) => !el.hasAttribute("disabled") && el.getAttribute("aria-hidden") !== "true" && el.getAttribute("tabindex") !== "-1"
8988
+ );
8989
+ }
8990
+ function modalFocusables(panel, host) {
8991
+ const shadow = panel.querySelectorAll(FOCUSABLE_SELECTOR);
8992
+ const light = host.querySelectorAll(FOCUSABLE_SELECTOR);
8993
+ return usable([...shadow, ...light]);
8994
+ }
8995
+ function firstLightFocusable(host) {
8996
+ return usable(host.querySelectorAll(FOCUSABLE_SELECTOR))[0] ?? null;
8997
+ }
8998
+ const modalStack = [];
8999
+ function registerModalOpen(host) {
9000
+ if (!modalStack.includes(host)) modalStack.push(host);
9001
+ }
9002
+ function registerModalClose(host) {
9003
+ const index = modalStack.indexOf(host);
9004
+ if (index !== -1) modalStack.splice(index, 1);
9005
+ }
9006
+ function isTopmostModal(host) {
9007
+ if (modalStack.length === 0) return true;
9008
+ return modalStack[modalStack.length - 1] === host;
9009
+ }
9010
+ let scrollLocks = 0;
9011
+ let previousOverflow = "";
9012
+ let previousPaddingRight = "";
9013
+ function lockBodyScroll() {
9014
+ if (typeof document === "undefined" || !document.body) return;
9015
+ if (scrollLocks === 0) {
9016
+ const body = document.body;
9017
+ previousOverflow = body.style.overflow;
9018
+ previousPaddingRight = body.style.paddingRight;
9019
+ const scrollbar = window.innerWidth - document.documentElement.clientWidth;
9020
+ body.style.overflow = "hidden";
9021
+ if (scrollbar > 0) body.style.paddingRight = `${scrollbar}px`;
9022
+ }
9023
+ scrollLocks += 1;
9024
+ }
9025
+ function unlockBodyScroll() {
9026
+ if (typeof document === "undefined" || !document.body) return;
9027
+ if (scrollLocks === 0) return;
9028
+ scrollLocks -= 1;
9029
+ if (scrollLocks === 0) {
9030
+ document.body.style.overflow = previousOverflow;
9031
+ document.body.style.paddingRight = previousPaddingRight;
9032
+ }
9033
+ }
8533
9034
  const _FxDialog = class _FxDialog extends FxElement {
8534
9035
  constructor() {
8535
9036
  super(...arguments);
8536
9037
  this._previouslyFocused = null;
9038
+ this._locked = false;
8537
9039
  }
8538
9040
  static get observedAttributes() {
8539
9041
  return ["open", "size", "heading"];
@@ -8547,6 +9049,11 @@ const _FxDialog = class _FxDialog extends FxElement {
8547
9049
  render() {
8548
9050
  const isOpen = this.open;
8549
9051
  const heading = this.getAttr("heading");
9052
+ if (!isOpen) {
9053
+ this._release();
9054
+ this.setTemplate("");
9055
+ return;
9056
+ }
8550
9057
  this.setTemplate(`
8551
9058
  <div class="overlay" part="overlay" ${isOpen ? "" : "hidden"}>
8552
9059
  <div class="dialog" role="dialog" aria-modal="true" aria-label="${esc(heading)}" tabindex="-1">
@@ -8565,10 +9072,8 @@ const _FxDialog = class _FxDialog extends FxElement {
8565
9072
  const overlay = this.root.querySelector(".overlay");
8566
9073
  const closeBtn = this.root.querySelector(".close");
8567
9074
  const close = () => {
9075
+ if (!this.open) return;
8568
9076
  this.open = false;
8569
- this._cleanup?.();
8570
- this._cleanup = void 0;
8571
- this._restoreFocus();
8572
9077
  this.dispatchEvent(new CustomEvent("close", { bubbles: true, composed: true }));
8573
9078
  };
8574
9079
  overlay?.addEventListener("click", (e) => {
@@ -8577,6 +9082,7 @@ const _FxDialog = class _FxDialog extends FxElement {
8577
9082
  closeBtn?.addEventListener("click", close);
8578
9083
  const onKey = (e) => {
8579
9084
  if (e.key === "Escape") {
9085
+ if (!isTopmostModal(this)) return;
8580
9086
  e.stopPropagation();
8581
9087
  close();
8582
9088
  return;
@@ -8585,29 +9091,48 @@ const _FxDialog = class _FxDialog extends FxElement {
8585
9091
  };
8586
9092
  document.addEventListener("keydown", onKey);
8587
9093
  this._cleanup = () => document.removeEventListener("keydown", onKey);
8588
- requestAnimationFrame(() => {
8589
- this._previouslyFocused = document.activeElement;
8590
- this.root.querySelector(".dialog")?.focus();
8591
- });
9094
+ const alreadyOpen = this._locked;
9095
+ if (!this._locked) {
9096
+ lockBodyScroll();
9097
+ registerModalOpen(this);
9098
+ this._locked = true;
9099
+ }
9100
+ if (!alreadyOpen) {
9101
+ requestAnimationFrame(() => {
9102
+ if (!this.open) return;
9103
+ this._previouslyFocused = document.activeElement;
9104
+ const first = firstLightFocusable(this) ?? this.root.querySelector(".dialog");
9105
+ first?.focus();
9106
+ });
9107
+ }
8592
9108
  this.dispatchEvent(new CustomEvent("open", { bubbles: true, composed: true }));
8593
9109
  }
8594
- /** Mantém o foco dentro do modal (WCAG 2.4.3 / 2.1.2). */
9110
+ /** Desfaz listeners, foco e scroll lock ao fechar/desconectar. */
9111
+ _release() {
9112
+ this._cleanup?.();
9113
+ this._cleanup = void 0;
9114
+ registerModalClose(this);
9115
+ if (this._locked) {
9116
+ unlockBodyScroll();
9117
+ this._locked = false;
9118
+ }
9119
+ this._restoreFocus();
9120
+ }
9121
+ /** Mantém o foco dentro do modal (WCAG 2.4.3 / 2.1.2), incluindo o slot. */
8595
9122
  _trapFocus(e) {
8596
9123
  const dialog = this.root.querySelector(".dialog");
8597
9124
  if (!dialog) return;
8598
- const focusables = dialog.querySelectorAll(
8599
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
8600
- );
9125
+ const focusables = modalFocusables(dialog, this);
8601
9126
  if (!focusables.length) return;
8602
9127
  const first = focusables[0];
8603
9128
  const last = focusables[focusables.length - 1];
8604
- const active = this.root.activeElement;
8605
- if (e.shiftKey && (active === first || active === dialog)) {
9129
+ const active = this.root.activeElement ?? document.activeElement;
9130
+ if (e.shiftKey && (active === first || active === dialog || active === this)) {
8606
9131
  e.preventDefault();
8607
- last.focus();
9132
+ last?.focus();
8608
9133
  } else if (!e.shiftKey && active === last) {
8609
9134
  e.preventDefault();
8610
- first.focus();
9135
+ first?.focus();
8611
9136
  }
8612
9137
  }
8613
9138
  _restoreFocus() {
@@ -8616,9 +9141,7 @@ const _FxDialog = class _FxDialog extends FxElement {
8616
9141
  }
8617
9142
  disconnectedCallback() {
8618
9143
  super.disconnectedCallback();
8619
- this._cleanup?.();
8620
- this._cleanup = void 0;
8621
- this._restoreFocus();
9144
+ this._release();
8622
9145
  }
8623
9146
  };
8624
9147
  _FxDialog.styles = css`
@@ -9024,7 +9547,35 @@ const _FxFileUpload = class _FxFileUpload extends FxFormElement {
9024
9547
  return this.getAttr("value");
9025
9548
  }
9026
9549
  set value(v) {
9027
- this.setAttribute("value", v);
9550
+ this.setAttribute("value", v == null ? "" : String(v));
9551
+ }
9552
+ get disabled() {
9553
+ return this.hasAttr("disabled");
9554
+ }
9555
+ set disabled(v) {
9556
+ this.toggleAttr("disabled", Boolean(v));
9557
+ }
9558
+ get multiple() {
9559
+ return this.hasAttr("multiple");
9560
+ }
9561
+ set multiple(v) {
9562
+ this.toggleAttr("multiple", Boolean(v));
9563
+ }
9564
+ get full() {
9565
+ return this.hasAttr("full");
9566
+ }
9567
+ set full(v) {
9568
+ this.toggleAttr("full", Boolean(v));
9569
+ }
9570
+ /**
9571
+ * Nome literal: o Vue NÃO cameliza atributos em elementos customizados,
9572
+ * então `:show-progress` procura a propriedade `'show-progress'`.
9573
+ */
9574
+ get "show-progress"() {
9575
+ return this.hasAttr("show-progress");
9576
+ }
9577
+ set "show-progress"(v) {
9578
+ this.toggleAttr("show-progress", Boolean(v));
9028
9579
  }
9029
9580
  get progress() {
9030
9581
  return Math.min(100, Math.max(0, Number(this.getAttr("progress", "0")) || 0));
@@ -9705,6 +10256,9 @@ const _FxAvatar = class _FxAvatar extends FxElement {
9705
10256
  get variant() {
9706
10257
  return this.getAttr("variant", "text");
9707
10258
  }
10259
+ set variant(v) {
10260
+ this.setAttribute("variant", v);
10261
+ }
9708
10262
  get src() {
9709
10263
  return this.getAttr("src");
9710
10264
  }
@@ -9808,6 +10362,18 @@ const _FxIcon = class _FxIcon extends FxElement {
9808
10362
  if (value) this.setAttribute("label", value);
9809
10363
  else this.removeAttribute("label");
9810
10364
  }
10365
+ get fill() {
10366
+ return this.hasAttr("fill");
10367
+ }
10368
+ set fill(value) {
10369
+ this.toggleAttr("fill", Boolean(value));
10370
+ }
10371
+ get bold() {
10372
+ return this.hasAttr("bold");
10373
+ }
10374
+ set bold(value) {
10375
+ this.toggleAttr("bold", Boolean(value));
10376
+ }
9811
10377
  connectedCallback() {
9812
10378
  loadFenixIconsFont();
9813
10379
  super.connectedCallback();
@@ -9854,6 +10420,12 @@ const _FxCard = class _FxCard extends FxElement {
9854
10420
  static get observedAttributes() {
9855
10421
  return ["variant", "radius", "size", "padded", "heading"];
9856
10422
  }
10423
+ get padded() {
10424
+ return this.hasAttr("padded");
10425
+ }
10426
+ set padded(value) {
10427
+ this.toggleAttr("padded", Boolean(value));
10428
+ }
9857
10429
  render() {
9858
10430
  const heading = this.getAttr("heading");
9859
10431
  this.setTemplate(`
@@ -12374,6 +12946,40 @@ const _FxTabs = class _FxTabs extends FxElement {
12374
12946
  set value(value) {
12375
12947
  this.setAttribute("value", value);
12376
12948
  }
12949
+ connectedCallback() {
12950
+ super.connectedCallback();
12951
+ this._observeLightDom();
12952
+ }
12953
+ disconnectedCallback() {
12954
+ super.disconnectedCallback();
12955
+ this._lightObserver?.disconnect();
12956
+ this._siblingObserver?.disconnect();
12957
+ this._lightObserver = void 0;
12958
+ this._siblingObserver = void 0;
12959
+ }
12960
+ _observeLightDom() {
12961
+ this._lightObserver?.disconnect();
12962
+ this._lightObserver = new MutationObserver(() => this.render());
12963
+ this._lightObserver.observe(this, {
12964
+ childList: true,
12965
+ subtree: true,
12966
+ attributes: true,
12967
+ attributeFilter: ["tab", "disabled"]
12968
+ });
12969
+ this._siblingObserver?.disconnect();
12970
+ const parent = this.parentNode;
12971
+ if (parent) {
12972
+ this._siblingObserver = new MutationObserver((mutations) => {
12973
+ const affectsPanels = mutations.some(
12974
+ (m) => [...m.addedNodes, ...m.removedNodes].some(
12975
+ (node) => node.nodeType === Node.ELEMENT_NODE && (node.matches("fx-tab-panel") || Boolean(node.querySelector("fx-tab-panel")))
12976
+ )
12977
+ );
12978
+ if (affectsPanels) this.render();
12979
+ });
12980
+ this._siblingObserver.observe(parent, { childList: true });
12981
+ }
12982
+ }
12377
12983
  render() {
12378
12984
  const tabs = Array.from(this.querySelectorAll("fx-tab"));
12379
12985
  const active = this.value || tabs[0]?.getAttribute("tab") || "";
@@ -12902,10 +13508,11 @@ const _FxDrawer = class _FxDrawer extends FxElement {
12902
13508
  this._onKeydown = () => {
12903
13509
  };
12904
13510
  this._previouslyFocused = null;
13511
+ this._locked = false;
13512
+ this._wasOpen = false;
12905
13513
  this._close = () => {
12906
13514
  if (!this.open) return;
12907
13515
  this.open = false;
12908
- this._restoreFocus();
12909
13516
  this.dispatchEvent(
12910
13517
  new CustomEvent("close", { bubbles: true, composed: true })
12911
13518
  );
@@ -12935,6 +13542,8 @@ const _FxDrawer = class _FxDrawer extends FxElement {
12935
13542
  }
12936
13543
  render() {
12937
13544
  const title = this.getAttr("title");
13545
+ const wasOpen = this._wasOpen;
13546
+ this._wasOpen = this.open;
12938
13547
  this.setTemplate(`
12939
13548
  <div class="overlay" part="overlay"></div>
12940
13549
  <div class="panel" part="panel" role="dialog" aria-modal="true" aria-label="${esc(title || "Drawer")}" tabindex="-1">
@@ -12945,20 +13554,40 @@ const _FxDrawer = class _FxDrawer extends FxElement {
12945
13554
  this.root.querySelector(".overlay")?.addEventListener("click", this._close);
12946
13555
  this.root.querySelector(".close")?.addEventListener("click", this._close);
12947
13556
  if (this.open) {
12948
- requestAnimationFrame(() => {
12949
- if (this._previouslyFocused === null) {
13557
+ if (!this._locked) {
13558
+ lockBodyScroll();
13559
+ registerModalOpen(this);
13560
+ this._locked = true;
13561
+ }
13562
+ if (!wasOpen) {
13563
+ requestAnimationFrame(() => {
13564
+ if (!this.open) return;
12950
13565
  this._previouslyFocused = document.activeElement;
12951
- }
12952
- this.root.querySelector(".panel")?.focus();
12953
- });
13566
+ const first = firstLightFocusable(this) ?? this.root.querySelector(".panel");
13567
+ first?.focus();
13568
+ });
13569
+ }
13570
+ } else {
13571
+ this._release();
13572
+ }
13573
+ }
13574
+ /** Desfaz scroll lock e restaura o foco ao fechar/desconectar. */
13575
+ _release() {
13576
+ registerModalClose(this);
13577
+ if (this._locked) {
13578
+ unlockBodyScroll();
13579
+ this._locked = false;
12954
13580
  }
13581
+ this._restoreFocus();
12955
13582
  }
12956
13583
  connectedCallback() {
12957
13584
  super.connectedCallback?.();
12958
13585
  this._onKeydown = (e) => {
12959
- if (e.key === "Escape" && this.open) {
13586
+ if (!this.open) return;
13587
+ if (e.key === "Escape") {
13588
+ if (!isTopmostModal(this)) return;
12960
13589
  this._close();
12961
- } else if (e.key === "Tab" && this.open) {
13590
+ } else if (e.key === "Tab") {
12962
13591
  this._trapFocus(e);
12963
13592
  }
12964
13593
  };
@@ -12966,26 +13595,24 @@ const _FxDrawer = class _FxDrawer extends FxElement {
12966
13595
  }
12967
13596
  disconnectedCallback() {
12968
13597
  document.removeEventListener("keydown", this._onKeydown);
12969
- this._restoreFocus();
13598
+ this._release();
12970
13599
  super.disconnectedCallback?.();
12971
13600
  }
12972
- /** Mantém o foco dentro do drawer (WCAG 2.4.3 / 2.1.2). */
13601
+ /** Mantém o foco dentro do drawer (WCAG 2.4.3 / 2.1.2), incluindo o slot. */
12973
13602
  _trapFocus(e) {
12974
13603
  const panel = this.root.querySelector(".panel");
12975
13604
  if (!panel) return;
12976
- const focusables = panel.querySelectorAll(
12977
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
12978
- );
13605
+ const focusables = modalFocusables(panel, this);
12979
13606
  if (!focusables.length) return;
12980
13607
  const first = focusables[0];
12981
13608
  const last = focusables[focusables.length - 1];
12982
- const active = this.root.activeElement;
12983
- if (e.shiftKey && (active === first || active === panel)) {
13609
+ const active = this.root.activeElement ?? document.activeElement;
13610
+ if (e.shiftKey && (active === first || active === panel || active === this)) {
12984
13611
  e.preventDefault();
12985
- last.focus();
13612
+ last?.focus();
12986
13613
  } else if (!e.shiftKey && active === last) {
12987
13614
  e.preventDefault();
12988
- first.focus();
13615
+ first?.focus();
12989
13616
  }
12990
13617
  }
12991
13618
  _restoreFocus() {
@@ -13278,11 +13905,17 @@ const _FxAutocomplete = class _FxAutocomplete extends FxFormElement {
13278
13905
  set size(v) {
13279
13906
  this.setAttribute("size", v);
13280
13907
  }
13908
+ get disabled() {
13909
+ return this.hasAttr("disabled");
13910
+ }
13911
+ set disabled(v) {
13912
+ this.toggleAttr("disabled", Boolean(v));
13913
+ }
13281
13914
  get value() {
13282
13915
  return this.getAttr("value");
13283
13916
  }
13284
13917
  set value(v) {
13285
- this.setAttribute("value", v);
13918
+ this.setAttribute("value", v == null ? "" : String(v));
13286
13919
  this.syncInternalControl();
13287
13920
  this.updateFormValue();
13288
13921
  }
@@ -13816,6 +14449,7 @@ const _FxOrderList = class _FxOrderList extends FxElement {
13816
14449
  static get observedAttributes() {
13817
14450
  return [
13818
14451
  "data",
14452
+ "items",
13819
14453
  "data-key",
13820
14454
  "filter",
13821
14455
  "filter-by",
@@ -13829,13 +14463,35 @@ const _FxOrderList = class _FxOrderList extends FxElement {
13829
14463
  ];
13830
14464
  }
13831
14465
  set data(value) {
13832
- this._data = value;
14466
+ this._data = Array.isArray(value) ? value : [];
13833
14467
  this._filteredData = null;
13834
14468
  this.render();
13835
14469
  }
13836
14470
  get data() {
13837
14471
  return this._data;
13838
14472
  }
14473
+ /**
14474
+ * Alias de `data` para o binding do Vue/React: `data` colide com a
14475
+ * tipagem nativa de atributos HTML (`dataset`/`data-*`) e não reflete em
14476
+ * atributo. `items` funciona como propriedade e como atributo JSON.
14477
+ */
14478
+ get items() {
14479
+ return this._data;
14480
+ }
14481
+ set items(value) {
14482
+ const list = Array.isArray(value) ? value : [];
14483
+ this._data = list;
14484
+ this._filteredData = null;
14485
+ const json = (() => {
14486
+ try {
14487
+ return JSON.stringify(list);
14488
+ } catch {
14489
+ return null;
14490
+ }
14491
+ })();
14492
+ if (json !== null && this.getAttr("items") !== json) this.setAttribute("items", json);
14493
+ else if (this.isConnected) this.render();
14494
+ }
13839
14495
  set selection(value) {
13840
14496
  this._selection = value;
13841
14497
  this.render();
@@ -13915,7 +14571,16 @@ const _FxOrderList = class _FxOrderList extends FxElement {
13915
14571
  super.connectedCallback();
13916
14572
  }
13917
14573
  attributeChangedCallback(name, oldValue, newValue) {
13918
- if (this.getAttr("data", "") !== "") {
14574
+ if (name === "items") {
14575
+ try {
14576
+ const parsed = JSON.parse(newValue ?? "[]");
14577
+ this._data = Array.isArray(parsed) ? parsed : [];
14578
+ this._filteredData = null;
14579
+ } catch {
14580
+ this._data = [];
14581
+ this._filteredData = null;
14582
+ }
14583
+ } else if (this.getAttr("data", "") !== "") {
13919
14584
  this._parseDataAttribute();
13920
14585
  }
13921
14586
  super.attributeChangedCallback(name, oldValue, newValue);