@wrrdev/fenix-ui 1.1.16 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -3
- package/dist/components/autocomplete/autocomplete.d.ts +3 -1
- package/dist/components/autocomplete/autocomplete.d.ts.map +1 -1
- package/dist/components/autocomplete/autocomplete.js +7 -1
- package/dist/components/autocomplete/autocomplete.js.map +1 -1
- package/dist/components/avatar/avatar.d.ts +1 -0
- package/dist/components/avatar/avatar.d.ts.map +1 -1
- package/dist/components/avatar/avatar.js +3 -0
- package/dist/components/avatar/avatar.js.map +1 -1
- package/dist/components/badge/badge.d.ts +4 -0
- package/dist/components/badge/badge.d.ts.map +1 -1
- package/dist/components/badge/badge.js +12 -0
- package/dist/components/badge/badge.js.map +1 -1
- package/dist/components/button/button.d.ts +2 -0
- package/dist/components/button/button.d.ts.map +1 -1
- package/dist/components/button/button.js +6 -0
- package/dist/components/button/button.js.map +1 -1
- package/dist/components/card/card.d.ts +2 -0
- package/dist/components/card/card.d.ts.map +1 -1
- package/dist/components/card/card.js +6 -0
- package/dist/components/card/card.js.map +1 -1
- package/dist/components/checkbox/checkbox.d.ts +5 -1
- package/dist/components/checkbox/checkbox.d.ts.map +1 -1
- package/dist/components/checkbox/checkbox.js +7 -8
- package/dist/components/checkbox/checkbox.js.map +1 -1
- package/dist/components/datepicker/datepicker.d.ts +17 -2
- package/dist/components/datepicker/datepicker.d.ts.map +1 -1
- package/dist/components/datepicker/datepicker.js +113 -42
- package/dist/components/datepicker/datepicker.js.map +1 -1
- package/dist/components/dialog/dialog.d.ts +4 -1
- package/dist/components/dialog/dialog.d.ts.map +1 -1
- package/dist/components/dialog/dialog.js +41 -18
- package/dist/components/dialog/dialog.js.map +1 -1
- package/dist/components/drawer/drawer.d.ts +5 -1
- package/dist/components/drawer/drawer.d.ts.map +1 -1
- package/dist/components/drawer/drawer.js +39 -17
- package/dist/components/drawer/drawer.js.map +1 -1
- package/dist/components/fileupload/fileupload.d.ts +13 -1
- package/dist/components/fileupload/fileupload.d.ts.map +1 -1
- package/dist/components/fileupload/fileupload.js +29 -1
- package/dist/components/fileupload/fileupload.js.map +1 -1
- package/dist/components/floatlabel/floatlabel.d.ts +20 -0
- package/dist/components/floatlabel/floatlabel.d.ts.map +1 -1
- package/dist/components/floatlabel/floatlabel.js +99 -26
- package/dist/components/floatlabel/floatlabel.js.map +1 -1
- package/dist/components/icon/icon.d.ts +4 -0
- package/dist/components/icon/icon.d.ts.map +1 -1
- package/dist/components/icon/icon.js +12 -0
- package/dist/components/icon/icon.js.map +1 -1
- package/dist/components/input/input.d.ts +9 -2
- package/dist/components/input/input.d.ts.map +1 -1
- package/dist/components/input/input.js +56 -8
- package/dist/components/input/input.js.map +1 -1
- package/dist/components/multiselect/multiselect.d.ts +8 -1
- package/dist/components/multiselect/multiselect.d.ts.map +1 -1
- package/dist/components/multiselect/multiselect.js +61 -9
- package/dist/components/multiselect/multiselect.js.map +1 -1
- package/dist/components/orderlist/orderlist.d.ts +7 -0
- package/dist/components/orderlist/orderlist.d.ts.map +1 -1
- package/dist/components/orderlist/orderlist.js +34 -2
- package/dist/components/orderlist/orderlist.js.map +1 -1
- package/dist/components/radio/radio.d.ts +6 -0
- package/dist/components/radio/radio.d.ts.map +1 -1
- package/dist/components/radio/radio.js +12 -6
- package/dist/components/radio/radio.js.map +1 -1
- package/dist/components/select/select.d.ts +19 -1
- package/dist/components/select/select.d.ts.map +1 -1
- package/dist/components/select/select.js +116 -10
- package/dist/components/select/select.js.map +1 -1
- package/dist/components/switch/switch.d.ts +4 -1
- package/dist/components/switch/switch.d.ts.map +1 -1
- package/dist/components/switch/switch.js +2 -1
- package/dist/components/switch/switch.js.map +1 -1
- package/dist/components/table/table.d.ts +27 -0
- package/dist/components/table/table.d.ts.map +1 -1
- package/dist/components/table/table.js +119 -21
- package/dist/components/table/table.js.map +1 -1
- package/dist/components/tabs/tabs.d.ts +6 -0
- package/dist/components/tabs/tabs.d.ts.map +1 -1
- package/dist/components/tabs/tabs.js +34 -0
- package/dist/components/tabs/tabs.js.map +1 -1
- package/dist/components/textarea/textarea.d.ts +5 -1
- package/dist/components/textarea/textarea.d.ts.map +1 -1
- package/dist/components/textarea/textarea.js +25 -6
- package/dist/components/textarea/textarea.js.map +1 -1
- package/dist/core/jsx.d.ts +11 -5
- package/dist/core/jsx.d.ts.map +1 -1
- package/dist/core/jsx.js.map +1 -1
- package/dist/core/modal.d.ts +31 -0
- package/dist/core/modal.d.ts.map +1 -0
- package/dist/core/modal.js +61 -0
- package/dist/core/modal.js.map +1 -0
- package/dist/core/tokens.d.ts +2 -0
- package/dist/core/tokens.d.ts.map +1 -1
- package/dist/core/tokens.js +9 -0
- package/dist/core/tokens.js.map +1 -1
- package/dist/docs/app.d.ts.map +1 -1
- package/dist/docs/componentes/floatlabel.doc.d.ts.map +1 -1
- package/dist/docs/componentes/input.doc.d.ts.map +1 -1
- package/dist/docs/componentes/orderlist.doc.d.ts.map +1 -1
- package/dist/docs/componentes/table.doc.d.ts.map +1 -1
- package/dist/docs/shell.d.ts +10 -1
- package/dist/docs/shell.d.ts.map +1 -1
- package/dist/docs/theme-reference.d.ts +53 -0
- package/dist/docs/theme-reference.d.ts.map +1 -0
- package/dist/docs/types.d.ts +2 -0
- package/dist/docs/types.d.ts.map +1 -1
- package/dist/fenix-ui.umd.min.js +270 -233
- package/dist/fenix-ui4.esm.js +890 -176
- package/dist/fenix-ui4.esm.js.map +1 -1
- package/package.json +1 -1
package/dist/fenix-ui4.esm.js
CHANGED
|
@@ -73,6 +73,14 @@ const lightTokens = {
|
|
|
73
73
|
/** Brilho dos estados `success`/`valid` — acompanha `focus-ring` igualmente. */
|
|
74
74
|
"success-ring": "0 0 0 3px color-mix(in srgb, var(--fx-color-success, #16a34a) 18%, transparent)"
|
|
75
75
|
},
|
|
76
|
+
table: {
|
|
77
|
+
/** Cor do corpo do fx-table; `var()` mantém claro/escuro automáticos. */
|
|
78
|
+
text: "var(--fx-text-default)",
|
|
79
|
+
/** Cabeçalho acompanha o corpo por padrão. */
|
|
80
|
+
"header-text": "var(--fx-table-text)",
|
|
81
|
+
/** O UA aplica bold no <th>; aqui o valor fica explícito e tokenizado. */
|
|
82
|
+
"header-weight": "bold"
|
|
83
|
+
},
|
|
76
84
|
z: { base: "auto", dropdown: "1000", modal: "1100", toast: "1200" }
|
|
77
85
|
};
|
|
78
86
|
const darkTokens = {
|
|
@@ -99,6 +107,7 @@ const darkTokens = {
|
|
|
99
107
|
},
|
|
100
108
|
motion: { ...lightTokens.motion },
|
|
101
109
|
effect: { ...lightTokens.effect },
|
|
110
|
+
table: { ...lightTokens.table },
|
|
102
111
|
z: { ...lightTokens.z }
|
|
103
112
|
};
|
|
104
113
|
const defaultTokens = lightTokens;
|
|
@@ -506,6 +515,12 @@ const _FxButton = class _FxButton extends FxElement {
|
|
|
506
515
|
set loading(value) {
|
|
507
516
|
this.toggleAttr("loading", Boolean(value));
|
|
508
517
|
}
|
|
518
|
+
get full() {
|
|
519
|
+
return this.hasAttr("full");
|
|
520
|
+
}
|
|
521
|
+
set full(value) {
|
|
522
|
+
this.toggleAttr("full", Boolean(value));
|
|
523
|
+
}
|
|
509
524
|
render() {
|
|
510
525
|
const loading = this.hasAttr("loading");
|
|
511
526
|
const disabled = this.hasAttr("disabled") || loading;
|
|
@@ -685,6 +700,18 @@ const _FxBadge = class _FxBadge extends FxElement {
|
|
|
685
700
|
static get observedAttributes() {
|
|
686
701
|
return ["variant", "round"];
|
|
687
702
|
}
|
|
703
|
+
get variant() {
|
|
704
|
+
return this.getAttr("variant", "default");
|
|
705
|
+
}
|
|
706
|
+
set variant(v) {
|
|
707
|
+
this.setAttribute("variant", v);
|
|
708
|
+
}
|
|
709
|
+
get round() {
|
|
710
|
+
return this.hasAttr("round");
|
|
711
|
+
}
|
|
712
|
+
set round(v) {
|
|
713
|
+
this.toggleAttr("round", Boolean(v));
|
|
714
|
+
}
|
|
688
715
|
render() {
|
|
689
716
|
this.setTemplate(`<span class="badge" part="badge"><slot></slot></span>`);
|
|
690
717
|
}
|
|
@@ -764,10 +791,21 @@ function defineFxSpinner() {
|
|
|
764
791
|
}
|
|
765
792
|
defineFxSpinner();
|
|
766
793
|
const _FxSelect = class _FxSelect extends FxFormElement {
|
|
767
|
-
|
|
768
|
-
|
|
794
|
+
constructor() {
|
|
795
|
+
super(...arguments);
|
|
796
|
+
this._suppressRender = false;
|
|
797
|
+
this._fixedMode = false;
|
|
798
|
+
this._fixedResolved = false;
|
|
799
|
+
}
|
|
800
|
+
// `value` é observado para que `setAttribute('value', ...)` (HTML puro)
|
|
801
|
+
// também atualize o rótulo. Os caminhos internos de change usam
|
|
802
|
+
// `_suppressRender` para renderizar UMA vez, depois de fechar o painel.
|
|
769
803
|
static get observedAttributes() {
|
|
770
|
-
return ["size", "disabled", "placeholder", "searchable", "clearable", "error", "invalid", "success", "valid", "placement"];
|
|
804
|
+
return ["value", "size", "disabled", "placeholder", "searchable", "clearable", "full", "error", "invalid", "success", "valid", "placement"];
|
|
805
|
+
}
|
|
806
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
807
|
+
if (this._suppressRender) return;
|
|
808
|
+
super.attributeChangedCallback(name, oldValue, newValue);
|
|
771
809
|
}
|
|
772
810
|
/** Tamanho do campo. Padrão: `'md'`. */
|
|
773
811
|
get size() {
|
|
@@ -781,7 +819,9 @@ const _FxSelect = class _FxSelect extends FxFormElement {
|
|
|
781
819
|
return this.getAttr("value");
|
|
782
820
|
}
|
|
783
821
|
set value(value) {
|
|
784
|
-
|
|
822
|
+
const next = value == null ? "" : String(value);
|
|
823
|
+
const prev = this.getAttr("value");
|
|
824
|
+
if (prev !== next) this.setAttribute("value", next);
|
|
785
825
|
this.updateFormValue();
|
|
786
826
|
}
|
|
787
827
|
get disabled() {
|
|
@@ -790,6 +830,24 @@ const _FxSelect = class _FxSelect extends FxFormElement {
|
|
|
790
830
|
set disabled(value) {
|
|
791
831
|
this.toggleAttr("disabled", Boolean(value));
|
|
792
832
|
}
|
|
833
|
+
get searchable() {
|
|
834
|
+
return this.hasAttr("searchable");
|
|
835
|
+
}
|
|
836
|
+
set searchable(value) {
|
|
837
|
+
this.toggleAttr("searchable", Boolean(value));
|
|
838
|
+
}
|
|
839
|
+
get clearable() {
|
|
840
|
+
return this.hasAttr("clearable");
|
|
841
|
+
}
|
|
842
|
+
set clearable(value) {
|
|
843
|
+
this.toggleAttr("clearable", Boolean(value));
|
|
844
|
+
}
|
|
845
|
+
get full() {
|
|
846
|
+
return this.hasAttr("full");
|
|
847
|
+
}
|
|
848
|
+
set full(value) {
|
|
849
|
+
this.toggleAttr("full", Boolean(value));
|
|
850
|
+
}
|
|
793
851
|
get options() {
|
|
794
852
|
return [...this.querySelectorAll("option")].map((o) => ({
|
|
795
853
|
value: o.getAttribute("value") ?? o.textContent?.trim() ?? "",
|
|
@@ -798,8 +856,17 @@ const _FxSelect = class _FxSelect extends FxFormElement {
|
|
|
798
856
|
}
|
|
799
857
|
connectedCallback() {
|
|
800
858
|
super.connectedCallback();
|
|
801
|
-
this.observer = new MutationObserver(() =>
|
|
802
|
-
|
|
859
|
+
this.observer = new MutationObserver((mutations) => {
|
|
860
|
+
if (mutations.every((m) => m.type === "attributes" && m.target === this)) return;
|
|
861
|
+
this.render();
|
|
862
|
+
});
|
|
863
|
+
this.observer.observe(this, {
|
|
864
|
+
childList: true,
|
|
865
|
+
subtree: true,
|
|
866
|
+
characterData: true,
|
|
867
|
+
attributes: true,
|
|
868
|
+
attributeFilter: ["value", "selected"]
|
|
869
|
+
});
|
|
803
870
|
this.docListener = (e) => {
|
|
804
871
|
if (!this.hasAttr("open")) return;
|
|
805
872
|
if (e.composedPath().includes(this)) return;
|
|
@@ -816,6 +883,8 @@ const _FxSelect = class _FxSelect extends FxFormElement {
|
|
|
816
883
|
}
|
|
817
884
|
/** Abre o dropdown, posiciona o painel e passa a reposicioná-lo no reflow. */
|
|
818
885
|
_abrir() {
|
|
886
|
+
this._fixedMode = _FxSelect._precisaFixed(this);
|
|
887
|
+
this._fixedResolved = true;
|
|
819
888
|
this.setAttribute("open", "");
|
|
820
889
|
this._bindReflow();
|
|
821
890
|
}
|
|
@@ -823,6 +892,8 @@ const _FxSelect = class _FxSelect extends FxFormElement {
|
|
|
823
892
|
_fechar() {
|
|
824
893
|
this.removeAttribute("open");
|
|
825
894
|
this.removeAttribute("data-placement");
|
|
895
|
+
this._fixedMode = false;
|
|
896
|
+
this._fixedResolved = false;
|
|
826
897
|
this._unbindReflow();
|
|
827
898
|
}
|
|
828
899
|
/** Liga o reposicionamento (resize + scroll interno, em capture). */
|
|
@@ -865,7 +936,30 @@ const _FxSelect = class _FxSelect extends FxFormElement {
|
|
|
865
936
|
_FxSelect.MIN_ALTURA,
|
|
866
937
|
(paraCima ? acima : abaixo) - _FxSelect.GAP
|
|
867
938
|
);
|
|
868
|
-
|
|
939
|
+
const altura = Math.min(desejado || disponivel, disponivel, maxAltura);
|
|
940
|
+
painel.style.maxHeight = `${altura}px`;
|
|
941
|
+
if (!this._fixedResolved) {
|
|
942
|
+
this._fixedMode = _FxSelect._precisaFixed(this);
|
|
943
|
+
this._fixedResolved = true;
|
|
944
|
+
}
|
|
945
|
+
if (this._fixedMode) {
|
|
946
|
+
this.setAttribute("data-fixed", "");
|
|
947
|
+
const largura2 = Math.max(r.width, 220);
|
|
948
|
+
const esquerda = Math.max(
|
|
949
|
+
_FxSelect.MARGEM,
|
|
950
|
+
Math.min(r.left, window.innerWidth - largura2 - _FxSelect.MARGEM)
|
|
951
|
+
);
|
|
952
|
+
const topo = paraCima ? Math.max(_FxSelect.MARGEM, r.top - _FxSelect.GAP - altura) : r.bottom + _FxSelect.GAP;
|
|
953
|
+
painel.style.bottom = "auto";
|
|
954
|
+
painel.style.width = `${largura2}px`;
|
|
955
|
+
painel.style.left = `${esquerda}px`;
|
|
956
|
+
painel.style.top = `${topo}px`;
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
this.removeAttribute("data-fixed");
|
|
960
|
+
painel.style.top = "";
|
|
961
|
+
painel.style.bottom = "";
|
|
962
|
+
painel.style.width = "";
|
|
869
963
|
painel.style.left = "";
|
|
870
964
|
const largura = painel.offsetWidth;
|
|
871
965
|
const excesso = r.left + largura - (window.innerWidth - _FxSelect.MARGEM);
|
|
@@ -874,6 +968,21 @@ const _FxSelect = class _FxSelect extends FxFormElement {
|
|
|
874
968
|
painel.style.right = "auto";
|
|
875
969
|
}
|
|
876
970
|
}
|
|
971
|
+
/**
|
|
972
|
+
* Existe ancestral com overflow que cortaria o painel absoluto?
|
|
973
|
+
* `getComputedStyle` é defensivo: em jsdom retorna vazio e o modo
|
|
974
|
+
* histórico (absoluto) é mantido.
|
|
975
|
+
*/
|
|
976
|
+
static _precisaFixed(host) {
|
|
977
|
+
let el = host.parentElement;
|
|
978
|
+
while (el && el !== document.body && el !== document.documentElement) {
|
|
979
|
+
const cs = getComputedStyle(el);
|
|
980
|
+
const overflow = `${cs.overflow}${cs.overflowX}${cs.overflowY}`;
|
|
981
|
+
if (/(hidden|clip|auto|scroll|overlay)/.test(overflow)) return true;
|
|
982
|
+
el = el.parentElement;
|
|
983
|
+
}
|
|
984
|
+
return false;
|
|
985
|
+
}
|
|
877
986
|
/** Altura máxima do painel a partir de `--fx-select-panel-max-height`. */
|
|
878
987
|
static _maxAltura(host) {
|
|
879
988
|
const raw = getComputedStyle(host).getPropertyValue("--fx-select-panel-max-height");
|
|
@@ -881,7 +990,12 @@ const _FxSelect = class _FxSelect extends FxFormElement {
|
|
|
881
990
|
return Number.isFinite(n) && n > 0 ? n : 260;
|
|
882
991
|
}
|
|
883
992
|
select(value) {
|
|
884
|
-
this.
|
|
993
|
+
this._suppressRender = true;
|
|
994
|
+
try {
|
|
995
|
+
this.value = value;
|
|
996
|
+
} finally {
|
|
997
|
+
this._suppressRender = false;
|
|
998
|
+
}
|
|
885
999
|
this._fechar();
|
|
886
1000
|
this.render();
|
|
887
1001
|
this.emitValueEvents();
|
|
@@ -908,6 +1022,10 @@ const _FxSelect = class _FxSelect extends FxFormElement {
|
|
|
908
1022
|
}
|
|
909
1023
|
render() {
|
|
910
1024
|
const prevOpen = this.hasAttr("open");
|
|
1025
|
+
if (!prevOpen) {
|
|
1026
|
+
this._fixedMode = false;
|
|
1027
|
+
this._fixedResolved = false;
|
|
1028
|
+
}
|
|
911
1029
|
const prevSearch = this.root.querySelector(".search");
|
|
912
1030
|
const search = prevSearch?.value ?? "";
|
|
913
1031
|
const searchFocused = prevSearch != null && this.root.activeElement === prevSearch;
|
|
@@ -984,7 +1102,12 @@ const _FxSelect = class _FxSelect extends FxFormElement {
|
|
|
984
1102
|
});
|
|
985
1103
|
this.root.querySelector(".clear")?.addEventListener("click", (e) => {
|
|
986
1104
|
e.stopPropagation();
|
|
987
|
-
this.
|
|
1105
|
+
this._suppressRender = true;
|
|
1106
|
+
try {
|
|
1107
|
+
this.value = "";
|
|
1108
|
+
} finally {
|
|
1109
|
+
this._suppressRender = false;
|
|
1110
|
+
}
|
|
988
1111
|
this._fechar();
|
|
989
1112
|
this.render();
|
|
990
1113
|
this.emitValueEvents();
|
|
@@ -999,7 +1122,12 @@ const _FxSelect = class _FxSelect extends FxFormElement {
|
|
|
999
1122
|
}
|
|
1000
1123
|
/** form.reset(): restaura o valor inicial e re-renderiza o rótulo. */
|
|
1001
1124
|
formResetCallback() {
|
|
1002
|
-
|
|
1125
|
+
this._suppressRender = true;
|
|
1126
|
+
try {
|
|
1127
|
+
super.formResetCallback();
|
|
1128
|
+
} finally {
|
|
1129
|
+
this._suppressRender = false;
|
|
1130
|
+
}
|
|
1003
1131
|
this.render();
|
|
1004
1132
|
}
|
|
1005
1133
|
};
|
|
@@ -1122,6 +1250,11 @@ _FxSelect.styles = css`
|
|
|
1122
1250
|
border-radius: var(--fx-radius-md);
|
|
1123
1251
|
box-shadow: var(--fx-shadow-lg);
|
|
1124
1252
|
}
|
|
1253
|
+
/* Ancestral com overflow (ex.: fx-card) cortaria o painel absoluto:
|
|
1254
|
+
nesse caso o JS posiciona com position:fixed a partir do trigger. */
|
|
1255
|
+
:host([data-fixed]) .panel {
|
|
1256
|
+
position: fixed;
|
|
1257
|
+
}
|
|
1125
1258
|
/* Padrão: abre para baixo. */
|
|
1126
1259
|
:host([open]:not([data-placement='top'])) .panel {
|
|
1127
1260
|
top: calc(100% + var(--fx-select-panel-offset, 4px));
|
|
@@ -1187,7 +1320,21 @@ function defineFxSelect() {
|
|
|
1187
1320
|
return defineElement("fx-select", FxSelect);
|
|
1188
1321
|
}
|
|
1189
1322
|
defineFxSelect();
|
|
1190
|
-
const INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
1323
|
+
const INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
1324
|
+
"text",
|
|
1325
|
+
"number",
|
|
1326
|
+
"password",
|
|
1327
|
+
"email",
|
|
1328
|
+
"tel",
|
|
1329
|
+
"url",
|
|
1330
|
+
"search",
|
|
1331
|
+
"date",
|
|
1332
|
+
"time",
|
|
1333
|
+
"datetime-local",
|
|
1334
|
+
"month",
|
|
1335
|
+
"week",
|
|
1336
|
+
"color"
|
|
1337
|
+
]);
|
|
1191
1338
|
function normalizeInputType(value) {
|
|
1192
1339
|
const type = (value || "text").toLowerCase();
|
|
1193
1340
|
return INPUT_TYPES.has(type) ? type : "text";
|
|
@@ -1207,6 +1354,8 @@ const _FxInput = class _FxInput extends FxFormElement {
|
|
|
1207
1354
|
"min",
|
|
1208
1355
|
"max",
|
|
1209
1356
|
"step",
|
|
1357
|
+
"clearable",
|
|
1358
|
+
"full",
|
|
1210
1359
|
"icon",
|
|
1211
1360
|
"icon-pos",
|
|
1212
1361
|
"error",
|
|
@@ -1234,7 +1383,7 @@ const _FxInput = class _FxInput extends FxFormElement {
|
|
|
1234
1383
|
return this.getAttr("value");
|
|
1235
1384
|
}
|
|
1236
1385
|
set value(value) {
|
|
1237
|
-
this.setAttribute("value", value);
|
|
1386
|
+
this.setAttribute("value", value == null ? "" : String(value));
|
|
1238
1387
|
this.syncInternalControl();
|
|
1239
1388
|
this.updateFormValue();
|
|
1240
1389
|
}
|
|
@@ -1244,6 +1393,24 @@ const _FxInput = class _FxInput extends FxFormElement {
|
|
|
1244
1393
|
set disabled(value) {
|
|
1245
1394
|
this.toggleAttr("disabled", Boolean(value));
|
|
1246
1395
|
}
|
|
1396
|
+
get readonly() {
|
|
1397
|
+
return this.hasAttr("readonly");
|
|
1398
|
+
}
|
|
1399
|
+
set readonly(value) {
|
|
1400
|
+
this.toggleAttr("readonly", Boolean(value));
|
|
1401
|
+
}
|
|
1402
|
+
get clearable() {
|
|
1403
|
+
return this.hasAttr("clearable");
|
|
1404
|
+
}
|
|
1405
|
+
set clearable(value) {
|
|
1406
|
+
this.toggleAttr("clearable", Boolean(value));
|
|
1407
|
+
}
|
|
1408
|
+
get full() {
|
|
1409
|
+
return this.hasAttr("full");
|
|
1410
|
+
}
|
|
1411
|
+
set full(value) {
|
|
1412
|
+
this.toggleAttr("full", Boolean(value));
|
|
1413
|
+
}
|
|
1247
1414
|
render() {
|
|
1248
1415
|
const type = this.type;
|
|
1249
1416
|
const placeholder = this.getAttr("placeholder");
|
|
@@ -1274,20 +1441,27 @@ const _FxInput = class _FxInput extends FxFormElement {
|
|
|
1274
1441
|
field.value = this.getAttr("value");
|
|
1275
1442
|
if (disabled) field.setAttribute("disabled", "");
|
|
1276
1443
|
if (readonly) field.setAttribute("readonly", "");
|
|
1277
|
-
const
|
|
1444
|
+
const notifyInput = () => {
|
|
1278
1445
|
this.value = field.value;
|
|
1279
1446
|
this.emitValueEvents();
|
|
1447
|
+
};
|
|
1448
|
+
const notifyChange = () => {
|
|
1449
|
+
this.value = field.value;
|
|
1280
1450
|
this.dispatchEvent(
|
|
1281
|
-
new CustomEvent(
|
|
1451
|
+
new CustomEvent("change", {
|
|
1452
|
+
bubbles: true,
|
|
1453
|
+
composed: true,
|
|
1454
|
+
detail: { value: field.value }
|
|
1455
|
+
})
|
|
1282
1456
|
);
|
|
1283
1457
|
};
|
|
1284
1458
|
field.addEventListener("input", (e) => {
|
|
1285
1459
|
e.stopPropagation();
|
|
1286
|
-
|
|
1460
|
+
notifyInput();
|
|
1287
1461
|
});
|
|
1288
1462
|
field.addEventListener("change", (e) => {
|
|
1289
1463
|
e.stopPropagation();
|
|
1290
|
-
|
|
1464
|
+
notifyChange();
|
|
1291
1465
|
});
|
|
1292
1466
|
field.addEventListener("keydown", (e) => {
|
|
1293
1467
|
if (e.key !== "Enter" || e.isComposing) return;
|
|
@@ -1303,8 +1477,15 @@ const _FxInput = class _FxInput extends FxFormElement {
|
|
|
1303
1477
|
clearBtn?.addEventListener("click", () => {
|
|
1304
1478
|
field.value = "";
|
|
1305
1479
|
syncClear();
|
|
1306
|
-
|
|
1307
|
-
|
|
1480
|
+
this.value = "";
|
|
1481
|
+
this.emitValueEvents();
|
|
1482
|
+
this.dispatchEvent(
|
|
1483
|
+
new CustomEvent("change", {
|
|
1484
|
+
bubbles: true,
|
|
1485
|
+
composed: true,
|
|
1486
|
+
detail: { value: "" }
|
|
1487
|
+
})
|
|
1488
|
+
);
|
|
1308
1489
|
field.focus();
|
|
1309
1490
|
});
|
|
1310
1491
|
field.addEventListener("input", syncClear);
|
|
@@ -1494,7 +1675,8 @@ const _FxSwitch = class _FxSwitch extends FxFormElement {
|
|
|
1494
1675
|
return this.getAttr("value");
|
|
1495
1676
|
}
|
|
1496
1677
|
set value(v) {
|
|
1497
|
-
this.setAttribute("value", v);
|
|
1678
|
+
this.setAttribute("value", v == null ? "" : String(v));
|
|
1679
|
+
this.updateFormValue();
|
|
1498
1680
|
}
|
|
1499
1681
|
/** No FormData: `value` (ou 'on') quando ligado; null (não envia) quando não. */
|
|
1500
1682
|
getFormValue() {
|
|
@@ -1612,7 +1794,23 @@ const _FxMultiselect = class _FxMultiselect extends FxFormElement {
|
|
|
1612
1794
|
this.selected = /* @__PURE__ */ new Set();
|
|
1613
1795
|
}
|
|
1614
1796
|
static get observedAttributes() {
|
|
1615
|
-
return [
|
|
1797
|
+
return [
|
|
1798
|
+
"values",
|
|
1799
|
+
"size",
|
|
1800
|
+
"disabled",
|
|
1801
|
+
"placeholder",
|
|
1802
|
+
"searchable",
|
|
1803
|
+
"clearable",
|
|
1804
|
+
"full",
|
|
1805
|
+
"error",
|
|
1806
|
+
"invalid",
|
|
1807
|
+
"success",
|
|
1808
|
+
"valid"
|
|
1809
|
+
];
|
|
1810
|
+
}
|
|
1811
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
1812
|
+
if (name === "values") this.selected = new Set(this.values);
|
|
1813
|
+
super.attributeChangedCallback(name, oldValue, newValue);
|
|
1616
1814
|
}
|
|
1617
1815
|
/** Valores selecionados (CSV no atributo / array na propriedade). */
|
|
1618
1816
|
get values() {
|
|
@@ -1620,7 +1818,15 @@ const _FxMultiselect = class _FxMultiselect extends FxFormElement {
|
|
|
1620
1818
|
return csv ? csv.split(",").map((v) => v.trim()).filter(Boolean) : [];
|
|
1621
1819
|
}
|
|
1622
1820
|
set values(list) {
|
|
1623
|
-
|
|
1821
|
+
const values = Array.isArray(list) ? list.map(String) : [];
|
|
1822
|
+
this.selected = new Set(values);
|
|
1823
|
+
const csv = values.join(",");
|
|
1824
|
+
if (this.getAttr("values") !== csv) {
|
|
1825
|
+
this.setAttribute("values", csv);
|
|
1826
|
+
} else if (this.isConnected) {
|
|
1827
|
+
this.render();
|
|
1828
|
+
}
|
|
1829
|
+
this.updateFormValue();
|
|
1624
1830
|
}
|
|
1625
1831
|
/** No FormData do form: uma entrada `name` por valor selecionado. */
|
|
1626
1832
|
getFormValue() {
|
|
@@ -1636,6 +1842,24 @@ const _FxMultiselect = class _FxMultiselect extends FxFormElement {
|
|
|
1636
1842
|
set disabled(value) {
|
|
1637
1843
|
this.toggleAttr("disabled", Boolean(value));
|
|
1638
1844
|
}
|
|
1845
|
+
get searchable() {
|
|
1846
|
+
return this.hasAttr("searchable");
|
|
1847
|
+
}
|
|
1848
|
+
set searchable(value) {
|
|
1849
|
+
this.toggleAttr("searchable", Boolean(value));
|
|
1850
|
+
}
|
|
1851
|
+
get clearable() {
|
|
1852
|
+
return this.hasAttr("clearable");
|
|
1853
|
+
}
|
|
1854
|
+
set clearable(value) {
|
|
1855
|
+
this.toggleAttr("clearable", Boolean(value));
|
|
1856
|
+
}
|
|
1857
|
+
get full() {
|
|
1858
|
+
return this.hasAttr("full");
|
|
1859
|
+
}
|
|
1860
|
+
set full(value) {
|
|
1861
|
+
this.toggleAttr("full", Boolean(value));
|
|
1862
|
+
}
|
|
1639
1863
|
get options() {
|
|
1640
1864
|
return [...this.querySelectorAll("option")].map((o) => ({
|
|
1641
1865
|
value: o.getAttribute("value") ?? o.textContent?.trim() ?? "",
|
|
@@ -1647,7 +1871,15 @@ const _FxMultiselect = class _FxMultiselect extends FxFormElement {
|
|
|
1647
1871
|
if (!this._initialValues) this._initialValues = [...this.values];
|
|
1648
1872
|
super.connectedCallback();
|
|
1649
1873
|
this.observer = new MutationObserver(() => this.render());
|
|
1650
|
-
this.observer.observe(this, {
|
|
1874
|
+
this.observer.observe(this, {
|
|
1875
|
+
childList: true,
|
|
1876
|
+
subtree: true,
|
|
1877
|
+
characterData: true,
|
|
1878
|
+
// Vue atualiza `:value`/`:selected` das <option> no light DOM; sem
|
|
1879
|
+
// observar atributos o label/valor antigo permanecia em tela.
|
|
1880
|
+
attributes: true,
|
|
1881
|
+
attributeFilter: ["value", "selected"]
|
|
1882
|
+
});
|
|
1651
1883
|
this.docListener = (e) => {
|
|
1652
1884
|
if (!this.hasAttr("open")) return;
|
|
1653
1885
|
if (e.composedPath().includes(this)) return;
|
|
@@ -1663,8 +1895,10 @@ const _FxMultiselect = class _FxMultiselect extends FxFormElement {
|
|
|
1663
1895
|
}
|
|
1664
1896
|
/** Aplica a seleção, reflete e emite change. */
|
|
1665
1897
|
commit() {
|
|
1666
|
-
|
|
1667
|
-
this.
|
|
1898
|
+
const next = [...this.selected];
|
|
1899
|
+
const same = next.join(",") === this.values.join(",");
|
|
1900
|
+
this.values = next;
|
|
1901
|
+
if (same && this.isConnected) this.render();
|
|
1668
1902
|
this.updateFormValue();
|
|
1669
1903
|
this.dispatchEvent(
|
|
1670
1904
|
new CustomEvent("change", {
|
|
@@ -1685,8 +1919,6 @@ const _FxMultiselect = class _FxMultiselect extends FxFormElement {
|
|
|
1685
1919
|
formResetCallback() {
|
|
1686
1920
|
const initial = this._initialValues ?? [];
|
|
1687
1921
|
this.values = initial;
|
|
1688
|
-
this.selected = new Set(initial);
|
|
1689
|
-
this.render();
|
|
1690
1922
|
this.updateFormValue();
|
|
1691
1923
|
}
|
|
1692
1924
|
toggleValue(value) {
|
|
@@ -1715,8 +1947,10 @@ const _FxMultiselect = class _FxMultiselect extends FxFormElement {
|
|
|
1715
1947
|
const wasOpen = this.hasAttr("open");
|
|
1716
1948
|
const search = this.root.querySelector(".search")?.value ?? "";
|
|
1717
1949
|
const opts = this.options;
|
|
1718
|
-
|
|
1719
|
-
|
|
1950
|
+
if (opts.length > 0) {
|
|
1951
|
+
for (const v of [...this.selected]) {
|
|
1952
|
+
if (!opts.some((o) => o.value === v)) this.selected.delete(v);
|
|
1953
|
+
}
|
|
1720
1954
|
}
|
|
1721
1955
|
const chips = opts.filter((o) => this.selected.has(o.value));
|
|
1722
1956
|
const placeholder = this.getAttr("placeholder", "Selecione…");
|
|
@@ -2496,13 +2730,16 @@ defineFxCalendar();
|
|
|
2496
2730
|
const _FxDatepicker = class _FxDatepicker extends FxFormElement {
|
|
2497
2731
|
constructor() {
|
|
2498
2732
|
super(...arguments);
|
|
2733
|
+
this._suppressRender = false;
|
|
2499
2734
|
this.time = { h: 0, m: 0, s: 0 };
|
|
2500
2735
|
}
|
|
2501
2736
|
static get observedAttributes() {
|
|
2502
|
-
return ["value", "start", "end", "values", "mode", "min", "max", "placeholder", "disabled", "format", "show-time", "free-text", "error", "invalid", "success", "valid"];
|
|
2737
|
+
return ["value", "start", "end", "values", "mode", "min", "max", "placeholder", "disabled", "format", "show-time", "free-text", "clearable", "full", "error", "invalid", "success", "valid"];
|
|
2503
2738
|
}
|
|
2504
2739
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
2505
|
-
|
|
2740
|
+
if (!this._suppressRender) {
|
|
2741
|
+
super.attributeChangedCallback(name, oldValue, newValue);
|
|
2742
|
+
}
|
|
2506
2743
|
if (name === "value" || name === "start" || name === "end" || name === "values") {
|
|
2507
2744
|
this.updateFormValue();
|
|
2508
2745
|
}
|
|
@@ -2538,7 +2775,19 @@ const _FxDatepicker = class _FxDatepicker extends FxFormElement {
|
|
|
2538
2775
|
return this.getAttr("value");
|
|
2539
2776
|
}
|
|
2540
2777
|
set value(v) {
|
|
2541
|
-
this.setAttribute("value", v);
|
|
2778
|
+
this.setAttribute("value", v == null ? "" : String(v));
|
|
2779
|
+
}
|
|
2780
|
+
get start() {
|
|
2781
|
+
return this.getAttr("start");
|
|
2782
|
+
}
|
|
2783
|
+
set start(v) {
|
|
2784
|
+
this.setAttribute("start", v == null ? "" : String(v));
|
|
2785
|
+
}
|
|
2786
|
+
get end() {
|
|
2787
|
+
return this.getAttr("end");
|
|
2788
|
+
}
|
|
2789
|
+
set end(v) {
|
|
2790
|
+
this.setAttribute("end", v == null ? "" : String(v));
|
|
2542
2791
|
}
|
|
2543
2792
|
/** Datas do modo múltiplo (CSV no atributo). */
|
|
2544
2793
|
get values() {
|
|
@@ -2546,7 +2795,8 @@ const _FxDatepicker = class _FxDatepicker extends FxFormElement {
|
|
|
2546
2795
|
return csv ? csv.split(",").map((v) => v.trim()).filter(Boolean) : [];
|
|
2547
2796
|
}
|
|
2548
2797
|
set values(list) {
|
|
2549
|
-
|
|
2798
|
+
const values = Array.isArray(list) ? list.map(String) : [];
|
|
2799
|
+
this.setAttribute("values", values.join(","));
|
|
2550
2800
|
}
|
|
2551
2801
|
get disabled() {
|
|
2552
2802
|
return this.hasAttr("disabled");
|
|
@@ -2554,6 +2804,31 @@ const _FxDatepicker = class _FxDatepicker extends FxFormElement {
|
|
|
2554
2804
|
set disabled(value) {
|
|
2555
2805
|
this.toggleAttr("disabled", Boolean(value));
|
|
2556
2806
|
}
|
|
2807
|
+
/** `:show-time` procura a propriedade LITERAL no Vue (sem camelização). */
|
|
2808
|
+
get "show-time"() {
|
|
2809
|
+
return this.hasAttr("show-time");
|
|
2810
|
+
}
|
|
2811
|
+
set "show-time"(value) {
|
|
2812
|
+
this.toggleAttr("show-time", Boolean(value));
|
|
2813
|
+
}
|
|
2814
|
+
get "free-text"() {
|
|
2815
|
+
return this.hasAttr("free-text");
|
|
2816
|
+
}
|
|
2817
|
+
set "free-text"(value) {
|
|
2818
|
+
this.toggleAttr("free-text", Boolean(value));
|
|
2819
|
+
}
|
|
2820
|
+
get clearable() {
|
|
2821
|
+
return this.hasAttr("clearable");
|
|
2822
|
+
}
|
|
2823
|
+
set clearable(value) {
|
|
2824
|
+
this.toggleAttr("clearable", Boolean(value));
|
|
2825
|
+
}
|
|
2826
|
+
get full() {
|
|
2827
|
+
return this.hasAttr("full");
|
|
2828
|
+
}
|
|
2829
|
+
set full(value) {
|
|
2830
|
+
this.toggleAttr("full", Boolean(value));
|
|
2831
|
+
}
|
|
2557
2832
|
connectedCallback() {
|
|
2558
2833
|
if (!this._initialState) {
|
|
2559
2834
|
this._initialState = {
|
|
@@ -2578,11 +2853,17 @@ const _FxDatepicker = class _FxDatepicker extends FxFormElement {
|
|
|
2578
2853
|
/** form.reset(): restaura value/start/end/values iniciais (re-render via atributos observados). */
|
|
2579
2854
|
formResetCallback() {
|
|
2580
2855
|
const s = this._initialState ?? { value: "", start: "", end: "", values: "" };
|
|
2581
|
-
this.
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2856
|
+
this._suppressRender = true;
|
|
2857
|
+
try {
|
|
2858
|
+
this.setAttribute("value", s.value);
|
|
2859
|
+
this.setAttribute("start", s.start);
|
|
2860
|
+
this.setAttribute("end", s.end);
|
|
2861
|
+
this.setAttribute("values", s.values);
|
|
2862
|
+
} finally {
|
|
2863
|
+
this._suppressRender = false;
|
|
2864
|
+
}
|
|
2585
2865
|
this.updateFormValue();
|
|
2866
|
+
if (this.isConnected) this.render();
|
|
2586
2867
|
}
|
|
2587
2868
|
/** Formata uma data ISO ("YYYY-MM-DD" ou com "THH:mm:ss") segundo o atributo format. */
|
|
2588
2869
|
formatDate(iso) {
|
|
@@ -2690,10 +2971,15 @@ const _FxDatepicker = class _FxDatepicker extends FxFormElement {
|
|
|
2690
2971
|
});
|
|
2691
2972
|
this.root.querySelector(".clear")?.addEventListener("click", (e) => {
|
|
2692
2973
|
e.stopPropagation();
|
|
2693
|
-
this.
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2974
|
+
this._suppressRender = true;
|
|
2975
|
+
try {
|
|
2976
|
+
this.removeAttribute("value");
|
|
2977
|
+
this.removeAttribute("start");
|
|
2978
|
+
this.removeAttribute("end");
|
|
2979
|
+
this.removeAttribute("values");
|
|
2980
|
+
} finally {
|
|
2981
|
+
this._suppressRender = false;
|
|
2982
|
+
}
|
|
2697
2983
|
this.removeAttribute("open");
|
|
2698
2984
|
this.render();
|
|
2699
2985
|
this.emitChange({});
|
|
@@ -2701,19 +2987,24 @@ const _FxDatepicker = class _FxDatepicker extends FxFormElement {
|
|
|
2701
2987
|
const cal = this.root.querySelector(".pop fx-calendar");
|
|
2702
2988
|
cal?.addEventListener("change", (e) => {
|
|
2703
2989
|
const d = e.detail ?? {};
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2990
|
+
this._suppressRender = true;
|
|
2991
|
+
try {
|
|
2992
|
+
if (d.start !== void 0 || d.end !== void 0) {
|
|
2993
|
+
if (d.start) this.setAttribute("start", this.withTime(d.start));
|
|
2994
|
+
else this.removeAttribute("start");
|
|
2995
|
+
if (d.end) this.setAttribute("end", this.withTime(d.end));
|
|
2996
|
+
else this.removeAttribute("end");
|
|
2997
|
+
}
|
|
2998
|
+
if (d.values !== void 0) {
|
|
2999
|
+
this.values = d.values.map((v) => this.withTime(v));
|
|
3000
|
+
}
|
|
3001
|
+
if (d.value !== void 0) {
|
|
3002
|
+
if (d.value) this.setAttribute("value", this.withTime(d.value));
|
|
3003
|
+
else this.removeAttribute("value");
|
|
3004
|
+
if (this.mode === "single") this.removeAttribute("open");
|
|
3005
|
+
}
|
|
3006
|
+
} finally {
|
|
3007
|
+
this._suppressRender = false;
|
|
2717
3008
|
}
|
|
2718
3009
|
this.render();
|
|
2719
3010
|
this.emitChange(d);
|
|
@@ -2740,11 +3031,16 @@ const _FxDatepicker = class _FxDatepicker extends FxFormElement {
|
|
|
2740
3031
|
s: clamp(s?.value, 59)
|
|
2741
3032
|
};
|
|
2742
3033
|
const reattach = (iso) => iso.split("T")[0] ? `${iso.split("T")[0]}T${this.timeText()}` : iso;
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
this.
|
|
3034
|
+
this._suppressRender = true;
|
|
3035
|
+
try {
|
|
3036
|
+
if (this.value) this.setAttribute("value", reattach(this.value));
|
|
3037
|
+
if (this.getAttr("start")) this.setAttribute("start", reattach(this.getAttr("start")));
|
|
3038
|
+
if (this.getAttr("end")) this.setAttribute("end", reattach(this.getAttr("end")));
|
|
3039
|
+
if (this.values.length) {
|
|
3040
|
+
this.values = this.values.map((v) => reattach(v));
|
|
3041
|
+
}
|
|
3042
|
+
} finally {
|
|
3043
|
+
this._suppressRender = false;
|
|
2748
3044
|
}
|
|
2749
3045
|
const display2 = this.root.querySelector(".display");
|
|
2750
3046
|
if (display2) display2.value = this.displayText();
|
|
@@ -2800,23 +3096,32 @@ const _FxDatepicker = class _FxDatepicker extends FxFormElement {
|
|
|
2800
3096
|
setTimeout(() => input.classList.remove("invalid"), 1500);
|
|
2801
3097
|
return;
|
|
2802
3098
|
}
|
|
3099
|
+
this._suppressRender = true;
|
|
3100
|
+
try {
|
|
3101
|
+
if (this.mode === "single") {
|
|
3102
|
+
this.setAttribute("value", this.withTime(parsed));
|
|
3103
|
+
} else if (this.mode === "range") {
|
|
3104
|
+
if (!this.getAttr("start") || this.getAttr("start") && this.getAttr("end")) {
|
|
3105
|
+
this.setAttribute("start", this.withTime(parsed));
|
|
3106
|
+
this.removeAttribute("end");
|
|
3107
|
+
} else {
|
|
3108
|
+
const [a, b] = [this.getAttr("start").split("T")[0], parsed].sort();
|
|
3109
|
+
this.setAttribute("start", this.withTime(a));
|
|
3110
|
+
this.setAttribute("end", this.withTime(b));
|
|
3111
|
+
}
|
|
3112
|
+
} else {
|
|
3113
|
+
const list = this.values.filter((v) => v.split("T")[0] !== parsed);
|
|
3114
|
+
list.push(this.withTime(parsed));
|
|
3115
|
+
this.values = list;
|
|
3116
|
+
}
|
|
3117
|
+
} finally {
|
|
3118
|
+
this._suppressRender = false;
|
|
3119
|
+
}
|
|
2803
3120
|
if (this.mode === "single") {
|
|
2804
|
-
this.setAttribute("value", this.withTime(parsed));
|
|
2805
3121
|
this.emitChange({ value: this.value });
|
|
2806
3122
|
} 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
3123
|
this.emitChange({ start: this.getAttr("start"), end: this.getAttr("end") });
|
|
2816
3124
|
} else {
|
|
2817
|
-
const list = this.values.filter((v) => v.split("T")[0] !== parsed);
|
|
2818
|
-
list.push(this.withTime(parsed));
|
|
2819
|
-
this.values = list;
|
|
2820
3125
|
this.emitChange({ values: this.values });
|
|
2821
3126
|
}
|
|
2822
3127
|
this.render();
|
|
@@ -2987,7 +3292,7 @@ const _FxCheckbox = class _FxCheckbox extends FxFormElement {
|
|
|
2987
3292
|
this._initialCheckedCaptured = false;
|
|
2988
3293
|
}
|
|
2989
3294
|
static get observedAttributes() {
|
|
2990
|
-
return ["checked", "disabled", "indeterminate"];
|
|
3295
|
+
return ["checked", "disabled", "indeterminate", "size"];
|
|
2991
3296
|
}
|
|
2992
3297
|
get checked() {
|
|
2993
3298
|
return this.hasAttr("checked");
|
|
@@ -3016,7 +3321,8 @@ const _FxCheckbox = class _FxCheckbox extends FxFormElement {
|
|
|
3016
3321
|
return this.getAttr("value");
|
|
3017
3322
|
}
|
|
3018
3323
|
set value(v) {
|
|
3019
|
-
this.setAttribute("value", v);
|
|
3324
|
+
this.setAttribute("value", v == null ? "" : String(v));
|
|
3325
|
+
this.updateFormValue();
|
|
3020
3326
|
}
|
|
3021
3327
|
/** Tamanho do controle. Padrão: `'md'`. */
|
|
3022
3328
|
get size() {
|
|
@@ -3028,9 +3334,7 @@ const _FxCheckbox = class _FxCheckbox extends FxFormElement {
|
|
|
3028
3334
|
}
|
|
3029
3335
|
render() {
|
|
3030
3336
|
const px = this.size === "sm" ? "14px" : this.size === "lg" ? "22px" : "18px";
|
|
3031
|
-
|
|
3032
|
-
this.style.setProperty("--fx-size-checkbox", px);
|
|
3033
|
-
}
|
|
3337
|
+
this.style.setProperty("--_fx-size-checkbox", px);
|
|
3034
3338
|
this.setTemplate(`
|
|
3035
3339
|
<span class="box" part="box" role="checkbox" tabindex="0"
|
|
3036
3340
|
aria-checked="${this.indeterminate ? "mixed" : this.checked}"
|
|
@@ -3102,8 +3406,8 @@ _FxCheckbox.styles = css`
|
|
|
3102
3406
|
-webkit-tap-highlight-color: transparent;
|
|
3103
3407
|
}
|
|
3104
3408
|
.control {
|
|
3105
|
-
width: var(--fx-size-checkbox, 18px);
|
|
3106
|
-
height: var(--fx-size-checkbox, 18px);
|
|
3409
|
+
width: var(--fx-size-checkbox, var(--_fx-size-checkbox, 18px));
|
|
3410
|
+
height: var(--fx-size-checkbox, var(--_fx-size-checkbox, 18px));
|
|
3107
3411
|
flex-shrink: 0;
|
|
3108
3412
|
display: inline-flex;
|
|
3109
3413
|
align-items: center;
|
|
@@ -3112,7 +3416,7 @@ _FxCheckbox.styles = css`
|
|
|
3112
3416
|
border-radius: calc(var(--fx-radius-sm) + 2px);
|
|
3113
3417
|
background: var(--fx-surface-background);
|
|
3114
3418
|
color: #fff;
|
|
3115
|
-
font-size: calc(var(--fx-size-checkbox, 18px) - 6px);
|
|
3419
|
+
font-size: calc(var(--fx-size-checkbox, var(--_fx-size-checkbox, 18px)) - 6px);
|
|
3116
3420
|
line-height: 1;
|
|
3117
3421
|
transition:
|
|
3118
3422
|
background-color var(--fx-motion-duration-fast) var(--fx-motion-easing),
|
|
@@ -3159,7 +3463,7 @@ const _FxRadio = class _FxRadio extends FxFormElement {
|
|
|
3159
3463
|
this._initialCheckedCaptured = false;
|
|
3160
3464
|
}
|
|
3161
3465
|
static get observedAttributes() {
|
|
3162
|
-
return ["checked", "disabled"];
|
|
3466
|
+
return ["checked", "disabled", "size"];
|
|
3163
3467
|
}
|
|
3164
3468
|
get checked() {
|
|
3165
3469
|
return this.hasAttr("checked");
|
|
@@ -3192,6 +3496,14 @@ const _FxRadio = class _FxRadio extends FxFormElement {
|
|
|
3192
3496
|
getFormValue() {
|
|
3193
3497
|
return this.checked ? this.getAttr("value") || "on" : null;
|
|
3194
3498
|
}
|
|
3499
|
+
/** getter/setter `value` — sem o setter o Vue não consegue escrever a propriedade. */
|
|
3500
|
+
get value() {
|
|
3501
|
+
return this.getAttr("value");
|
|
3502
|
+
}
|
|
3503
|
+
set value(v) {
|
|
3504
|
+
this.setAttribute("value", v == null ? "" : String(v));
|
|
3505
|
+
this.updateFormValue();
|
|
3506
|
+
}
|
|
3195
3507
|
/** Tamanho do controle. Padrão: `'md'`. */
|
|
3196
3508
|
get size() {
|
|
3197
3509
|
const s = this.getAttr("size", "md");
|
|
@@ -3202,9 +3514,7 @@ const _FxRadio = class _FxRadio extends FxFormElement {
|
|
|
3202
3514
|
}
|
|
3203
3515
|
render() {
|
|
3204
3516
|
const px = this.size === "sm" ? "14px" : this.size === "lg" ? "22px" : "18px";
|
|
3205
|
-
|
|
3206
|
-
this.style.setProperty("--fx-size-radio", px);
|
|
3207
|
-
}
|
|
3517
|
+
this.style.setProperty("--_fx-size-radio", px);
|
|
3208
3518
|
this.setTemplate(`
|
|
3209
3519
|
<span class="box" part="box" role="radio" tabindex="0"
|
|
3210
3520
|
aria-checked="${this.checked}" aria-disabled="${this.disabled}">
|
|
@@ -3292,8 +3602,8 @@ _FxRadio.styles = css`
|
|
|
3292
3602
|
-webkit-tap-highlight-color: transparent;
|
|
3293
3603
|
}
|
|
3294
3604
|
.control {
|
|
3295
|
-
width: var(--fx-size-radio, 18px);
|
|
3296
|
-
height: var(--fx-size-radio, 18px);
|
|
3605
|
+
width: var(--fx-size-radio, var(--_fx-size-radio, 18px));
|
|
3606
|
+
height: var(--fx-size-radio, var(--_fx-size-radio, 18px));
|
|
3297
3607
|
flex-shrink: 0;
|
|
3298
3608
|
display: inline-flex;
|
|
3299
3609
|
align-items: center;
|
|
@@ -3805,6 +4115,7 @@ const _FxTable = class _FxTable extends FxElement {
|
|
|
3805
4115
|
this.sortDir = 1;
|
|
3806
4116
|
this.filters = {};
|
|
3807
4117
|
this.globalSearch = "";
|
|
4118
|
+
this._boundToolbarInputs = /* @__PURE__ */ new WeakSet();
|
|
3808
4119
|
this._shellKey = "";
|
|
3809
4120
|
}
|
|
3810
4121
|
static get observedAttributes() {
|
|
@@ -3852,6 +4163,44 @@ const _FxTable = class _FxTable extends FxElement {
|
|
|
3852
4163
|
get lazy() {
|
|
3853
4164
|
return this.hasAttr("lazy");
|
|
3854
4165
|
}
|
|
4166
|
+
set lazy(value) {
|
|
4167
|
+
this.toggleAttr("lazy", Boolean(value));
|
|
4168
|
+
}
|
|
4169
|
+
get pagination() {
|
|
4170
|
+
return this.hasAttr("pagination");
|
|
4171
|
+
}
|
|
4172
|
+
set pagination(value) {
|
|
4173
|
+
this.toggleAttr("pagination", Boolean(value));
|
|
4174
|
+
}
|
|
4175
|
+
get striped() {
|
|
4176
|
+
return this.hasAttr("striped");
|
|
4177
|
+
}
|
|
4178
|
+
set striped(value) {
|
|
4179
|
+
this.toggleAttr("striped", Boolean(value));
|
|
4180
|
+
}
|
|
4181
|
+
get hover() {
|
|
4182
|
+
return this.hasAttr("hover");
|
|
4183
|
+
}
|
|
4184
|
+
set hover(value) {
|
|
4185
|
+
this.toggleAttr("hover", Boolean(value));
|
|
4186
|
+
}
|
|
4187
|
+
get loading() {
|
|
4188
|
+
return this.hasAttr("loading");
|
|
4189
|
+
}
|
|
4190
|
+
set loading(value) {
|
|
4191
|
+
this.toggleAttr("loading", Boolean(value));
|
|
4192
|
+
}
|
|
4193
|
+
/**
|
|
4194
|
+
* Vue não cameliza atributos de elementos customizados:
|
|
4195
|
+
* `:no-borders-x="false"` procura a propriedade LITERAL
|
|
4196
|
+
* `'no-borders-x'` — o accessor literal evita o contorno `.attr`.
|
|
4197
|
+
*/
|
|
4198
|
+
get "no-borders-x"() {
|
|
4199
|
+
return this.hasAttr("no-borders-x");
|
|
4200
|
+
}
|
|
4201
|
+
set "no-borders-x"(value) {
|
|
4202
|
+
this.toggleAttr("no-borders-x", Boolean(value));
|
|
4203
|
+
}
|
|
3855
4204
|
get columns() {
|
|
3856
4205
|
return [...this.querySelectorAll("fx-column")].map((c) => {
|
|
3857
4206
|
const tpl = this._resolveCellTemplate(c);
|
|
@@ -3935,7 +4284,14 @@ const _FxTable = class _FxTable extends FxElement {
|
|
|
3935
4284
|
loadFenixIconsFont();
|
|
3936
4285
|
super.connectedCallback();
|
|
3937
4286
|
this._columnObserver = new MutationObserver(() => this.render());
|
|
3938
|
-
this._columnObserver.observe(this, {
|
|
4287
|
+
this._columnObserver.observe(this, {
|
|
4288
|
+
childList: true,
|
|
4289
|
+
subtree: true,
|
|
4290
|
+
// Colunas com header/field/align/sortable/filterable alterados
|
|
4291
|
+
// em runtime também precisam re-renderizar.
|
|
4292
|
+
attributes: true,
|
|
4293
|
+
attributeFilter: ["header", "field", "align", "sortable", "filterable", "template"]
|
|
4294
|
+
});
|
|
3939
4295
|
}
|
|
3940
4296
|
disconnectedCallback() {
|
|
3941
4297
|
this._columnObserver?.disconnect();
|
|
@@ -4017,7 +4373,14 @@ const _FxTable = class _FxTable extends FxElement {
|
|
|
4017
4373
|
const pages = Math.max(1, Math.ceil(total / this.rowsPerPage));
|
|
4018
4374
|
if (this.page >= pages) this.page = pages - 1;
|
|
4019
4375
|
const toolbar = this.toolbarTemplate;
|
|
4020
|
-
const
|
|
4376
|
+
const toolbarSlotted = toolbar?.localName === "fx-toolbar";
|
|
4377
|
+
const toolbarHasContent = Boolean(
|
|
4378
|
+
toolbar && (toolbarSlotted ? toolbar.children.length > 0 || (toolbar.textContent ?? "").trim() !== "" : toolbar.innerHTML.trim() !== "")
|
|
4379
|
+
);
|
|
4380
|
+
if (toolbarSlotted && toolbarHasContent) {
|
|
4381
|
+
toolbar.setAttribute("slot", "fx-toolbar");
|
|
4382
|
+
}
|
|
4383
|
+
const toolbarHtml = toolbarHasContent ? `<div class="toolbar" part="toolbar">${toolbarSlotted ? '<slot name="fx-toolbar"></slot>' : toolbar.innerHTML}</div>` : "";
|
|
4021
4384
|
const headHtml = cols.map((c) => {
|
|
4022
4385
|
const isSorted = this.sortField === c.field;
|
|
4023
4386
|
const ind = isSorted ? this.sortDir === 1 ? "▲" : "▼" : c.sortable ? "⇅" : "";
|
|
@@ -4040,7 +4403,7 @@ const _FxTable = class _FxTable extends FxElement {
|
|
|
4040
4403
|
<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
4404
|
<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
4405
|
<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(
|
|
4406
|
+
<fx-select class="rows-sel" size="sm" placement="top" value="${this.rowsPerPage}" aria-label="Itens por página">${this.getAttr(
|
|
4044
4407
|
"rows-options",
|
|
4045
4408
|
"5,10,20,50"
|
|
4046
4409
|
).split(",").map((n) => n.trim()).map((n) => `<option value="${esc(n)}">${esc(n)}</option>`).join("")}</fx-select>
|
|
@@ -4061,13 +4424,15 @@ const _FxTable = class _FxTable extends FxElement {
|
|
|
4061
4424
|
if (tbody) {
|
|
4062
4425
|
tbody.innerHTML = bodyHtml;
|
|
4063
4426
|
this._bindRowClicks(rows);
|
|
4427
|
+
this._bindSlottedToolbarSearch(toolbar);
|
|
4428
|
+
this._restoreToolbarSearch();
|
|
4064
4429
|
return;
|
|
4065
4430
|
}
|
|
4066
4431
|
}
|
|
4067
4432
|
this._shellKey = shellKey;
|
|
4068
4433
|
this.setTemplate(`
|
|
4069
4434
|
${toolbarHtml}
|
|
4070
|
-
<div class="frame${
|
|
4435
|
+
<div class="frame${toolbarHasContent ? " has-toolbar" : ""}" part="frame">
|
|
4071
4436
|
<div class="table-wrap">
|
|
4072
4437
|
<div class="scroll">
|
|
4073
4438
|
<table part="table">
|
|
@@ -4128,21 +4493,10 @@ const _FxTable = class _FxTable extends FxElement {
|
|
|
4128
4493
|
});
|
|
4129
4494
|
inp.addEventListener("click", (e) => e.stopPropagation());
|
|
4130
4495
|
});
|
|
4131
|
-
this.root.querySelectorAll("[data-search-fields]").forEach((inp) =>
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
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
|
-
});
|
|
4496
|
+
this.root.querySelectorAll("[data-search-fields]").forEach((inp) => this._bindSearchInput(inp, false));
|
|
4497
|
+
if (toolbarSlotted && toolbar) {
|
|
4498
|
+
this._bindSlottedToolbarSearch(toolbar);
|
|
4499
|
+
}
|
|
4146
4500
|
this.root.querySelectorAll(".pg-btn").forEach((btn) => {
|
|
4147
4501
|
btn.addEventListener("click", () => {
|
|
4148
4502
|
const pg = btn.dataset.pg;
|
|
@@ -4195,11 +4549,47 @@ const _FxTable = class _FxTable extends FxElement {
|
|
|
4195
4549
|
});
|
|
4196
4550
|
});
|
|
4197
4551
|
}
|
|
4552
|
+
/** Liga (uma vez) os inputs de busca de um <fx-toolbar> slotted. */
|
|
4553
|
+
_bindSlottedToolbarSearch(toolbar) {
|
|
4554
|
+
if (!toolbar || toolbar.localName !== "fx-toolbar") return;
|
|
4555
|
+
toolbar.querySelectorAll("[data-search-fields]").forEach((inp) => {
|
|
4556
|
+
if (this._boundToolbarInputs.has(inp)) return;
|
|
4557
|
+
this._boundToolbarInputs.add(inp);
|
|
4558
|
+
this._bindSearchInput(inp, true);
|
|
4559
|
+
});
|
|
4560
|
+
}
|
|
4561
|
+
/**
|
|
4562
|
+
* Liga um input da toolbar à busca global.
|
|
4563
|
+
* `persistent` = input slotted (não é recriado pelo render): o foco volta
|
|
4564
|
+
* no próprio nó. Inputs clonados são substituídos, então o foco é
|
|
4565
|
+
* restaurado no clone novo do shadow.
|
|
4566
|
+
*/
|
|
4567
|
+
_bindSearchInput(inp, persistent) {
|
|
4568
|
+
inp.addEventListener("input", () => {
|
|
4569
|
+
this.globalSearch = inp.value;
|
|
4570
|
+
this.page = 0;
|
|
4571
|
+
const pos = inp.selectionStart;
|
|
4572
|
+
this.render();
|
|
4573
|
+
const again = persistent && inp.isConnected ? inp : this.root.querySelector("[data-search-fields]");
|
|
4574
|
+
if (again) {
|
|
4575
|
+
again.focus();
|
|
4576
|
+
again.setSelectionRange(pos, pos);
|
|
4577
|
+
}
|
|
4578
|
+
});
|
|
4579
|
+
}
|
|
4198
4580
|
_restoreToolbarSearch() {
|
|
4199
|
-
const
|
|
4581
|
+
const shadow = this.root.querySelector(
|
|
4200
4582
|
"[data-search-fields]"
|
|
4201
4583
|
);
|
|
4202
|
-
if (
|
|
4584
|
+
if (shadow && this.globalSearch) shadow.value = this.globalSearch;
|
|
4585
|
+
const toolbar = this.toolbarTemplate;
|
|
4586
|
+
if (toolbar?.localName === "fx-toolbar") {
|
|
4587
|
+
toolbar.querySelectorAll("[data-search-fields]").forEach((input) => {
|
|
4588
|
+
if (this.globalSearch && input.value !== this.globalSearch) {
|
|
4589
|
+
input.value = this.globalSearch;
|
|
4590
|
+
}
|
|
4591
|
+
});
|
|
4592
|
+
}
|
|
4203
4593
|
}
|
|
4204
4594
|
};
|
|
4205
4595
|
_FxTable.styles = css`
|
|
@@ -4277,6 +4667,13 @@ _FxTable.styles = css`
|
|
|
4277
4667
|
border-bottom: 2px solid var(--fx-border-default);
|
|
4278
4668
|
white-space: nowrap;
|
|
4279
4669
|
user-select: none;
|
|
4670
|
+
/* Cor/peso do cabeçalho por token de TABELA. Sem isso o th herda
|
|
4671
|
+
--fx-text-default do host e o peso vem do UA (bold). */
|
|
4672
|
+
color: var(
|
|
4673
|
+
--fx-table-header-text,
|
|
4674
|
+
var(--fx-table-text, var(--fx-text-default))
|
|
4675
|
+
);
|
|
4676
|
+
font-weight: var(--fx-table-header-weight, bold);
|
|
4280
4677
|
}
|
|
4281
4678
|
th.sortable {
|
|
4282
4679
|
cursor: pointer;
|
|
@@ -4321,6 +4718,12 @@ _FxTable.styles = css`
|
|
|
4321
4718
|
td {
|
|
4322
4719
|
padding: var(--fx-space-sm) var(--fx-space-md);
|
|
4323
4720
|
border-bottom: 1px solid var(--fx-border-default);
|
|
4721
|
+
/* Texto do corpo: token próprio para não depender de
|
|
4722
|
+
--fx-text-default (que afeta TODOS os textos do app) e para
|
|
4723
|
+
não herdar o font-weight do body da página — era isso que
|
|
4724
|
+
fazia a célula parecer mais clara que o cabeçalho. */
|
|
4725
|
+
color: var(--fx-table-text, var(--fx-text-default));
|
|
4726
|
+
font-weight: var(--fx-table-text-weight, inherit);
|
|
4324
4727
|
}
|
|
4325
4728
|
tbody tr {
|
|
4326
4729
|
cursor: pointer;
|
|
@@ -4455,6 +4858,11 @@ _FxTable.styles = css`
|
|
|
4455
4858
|
.toolbar:empty {
|
|
4456
4859
|
display: none;
|
|
4457
4860
|
}
|
|
4861
|
+
/* <fx-toolbar> é apenas um invólucro no light DOM: o slot projeta
|
|
4862
|
+
seus filhos (com listeners do Vue preservados) direto no flex. */
|
|
4863
|
+
::slotted(fx-toolbar) {
|
|
4864
|
+
display: contents;
|
|
4865
|
+
}
|
|
4458
4866
|
.loading-overlay {
|
|
4459
4867
|
position: absolute;
|
|
4460
4868
|
inset: 0;
|
|
@@ -4505,7 +4913,51 @@ const _FxFloatlabel = class _FxFloatlabel extends FxElement {
|
|
|
4505
4913
|
this._labelClickBound = false;
|
|
4506
4914
|
}
|
|
4507
4915
|
static get observedAttributes() {
|
|
4508
|
-
return ["variant", "error", "invalid", "success", "valid", "full", "icon", "icon-pos"];
|
|
4916
|
+
return ["variant", "error", "invalid", "success", "valid", "full", "icon", "icon-pos", "error-text"];
|
|
4917
|
+
}
|
|
4918
|
+
get error() {
|
|
4919
|
+
return this.hasAttr("error");
|
|
4920
|
+
}
|
|
4921
|
+
set error(value) {
|
|
4922
|
+
this.toggleAttr("error", Boolean(value));
|
|
4923
|
+
}
|
|
4924
|
+
get invalid() {
|
|
4925
|
+
return this.hasAttr("invalid");
|
|
4926
|
+
}
|
|
4927
|
+
set invalid(value) {
|
|
4928
|
+
this.toggleAttr("invalid", Boolean(value));
|
|
4929
|
+
}
|
|
4930
|
+
get success() {
|
|
4931
|
+
return this.hasAttr("success");
|
|
4932
|
+
}
|
|
4933
|
+
set success(value) {
|
|
4934
|
+
this.toggleAttr("success", Boolean(value));
|
|
4935
|
+
}
|
|
4936
|
+
get valid() {
|
|
4937
|
+
return this.hasAttr("valid");
|
|
4938
|
+
}
|
|
4939
|
+
set valid(value) {
|
|
4940
|
+
this.toggleAttr("valid", Boolean(value));
|
|
4941
|
+
}
|
|
4942
|
+
get full() {
|
|
4943
|
+
return this.hasAttr("full");
|
|
4944
|
+
}
|
|
4945
|
+
set full(value) {
|
|
4946
|
+
this.toggleAttr("full", Boolean(value));
|
|
4947
|
+
}
|
|
4948
|
+
get errorText() {
|
|
4949
|
+
return this.getAttr("error-text");
|
|
4950
|
+
}
|
|
4951
|
+
set errorText(value) {
|
|
4952
|
+
this.setAttribute("error-text", value == null ? "" : String(value));
|
|
4953
|
+
}
|
|
4954
|
+
connectedCallback() {
|
|
4955
|
+
super.connectedCallback();
|
|
4956
|
+
this._labelObserver = new MutationObserver(() => {
|
|
4957
|
+
const label = this.findLabel();
|
|
4958
|
+
if (label && label !== this._label) this.render();
|
|
4959
|
+
});
|
|
4960
|
+
this._labelObserver.observe(this, { childList: true });
|
|
4509
4961
|
}
|
|
4510
4962
|
render() {
|
|
4511
4963
|
const control = this.findControl();
|
|
@@ -4548,7 +5000,10 @@ const _FxFloatlabel = class _FxFloatlabel extends FxElement {
|
|
|
4548
5000
|
}
|
|
4549
5001
|
disconnectedCallback() {
|
|
4550
5002
|
super.disconnectedCallback();
|
|
5003
|
+
this.cancelScheduledSync();
|
|
4551
5004
|
this.observer?.disconnect();
|
|
5005
|
+
this._labelObserver?.disconnect();
|
|
5006
|
+
this._labelObserver = void 0;
|
|
4552
5007
|
}
|
|
4553
5008
|
/** Campo de controle = qualquer filho direto que não seja label. */
|
|
4554
5009
|
findControl() {
|
|
@@ -4575,14 +5030,28 @@ const _FxFloatlabel = class _FxFloatlabel extends FxElement {
|
|
|
4575
5030
|
}
|
|
4576
5031
|
if (this.listenersReady) return;
|
|
4577
5032
|
this.listenersReady = true;
|
|
4578
|
-
this.addEventListener("focusin", () =>
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
);
|
|
5033
|
+
this.addEventListener("focusin", () => {
|
|
5034
|
+
this.cancelScheduledSync();
|
|
5035
|
+
this.syncState();
|
|
5036
|
+
});
|
|
5037
|
+
this.addEventListener("focusout", () => this.scheduleSync());
|
|
4583
5038
|
this.addEventListener("input", () => this.syncState());
|
|
4584
5039
|
this.addEventListener("change", () => this.syncState());
|
|
4585
5040
|
}
|
|
5041
|
+
/** Reavalia o estado no próximo tick (o foco já saiu do campo). */
|
|
5042
|
+
scheduleSync() {
|
|
5043
|
+
this.cancelScheduledSync();
|
|
5044
|
+
this._syncTimer = setTimeout(() => {
|
|
5045
|
+
this._syncTimer = void 0;
|
|
5046
|
+
this.syncState();
|
|
5047
|
+
}, 0);
|
|
5048
|
+
}
|
|
5049
|
+
cancelScheduledSync() {
|
|
5050
|
+
if (this._syncTimer !== void 0) {
|
|
5051
|
+
clearTimeout(this._syncTimer);
|
|
5052
|
+
this._syncTimer = void 0;
|
|
5053
|
+
}
|
|
5054
|
+
}
|
|
4586
5055
|
/** Observa mudanças de atributos do controle (value, open, values...). */
|
|
4587
5056
|
syncControlReference(control = this.targetControl) {
|
|
4588
5057
|
if (control !== this.targetControl) {
|
|
@@ -4615,18 +5084,16 @@ const _FxFloatlabel = class _FxFloatlabel extends FxElement {
|
|
|
4615
5084
|
const c = this.targetControl;
|
|
4616
5085
|
const attrValue = c.getAttribute ? (c.getAttribute("value") ?? "").trim() : "";
|
|
4617
5086
|
const attrValues = c.getAttribute ? (c.getAttribute("values") ?? "").trim() : "";
|
|
5087
|
+
const isOpen = Boolean(c.hasAttribute?.("open"));
|
|
4618
5088
|
const hasValue = Boolean(
|
|
4619
|
-
typeof c.value === "string" && c.value.trim() !== "" || Array.isArray(c.values) && c.values.length > 0 || attrValue !== "" || attrValues !== "" || c.hasAttribute &&
|
|
5089
|
+
typeof c.value === "string" && c.value.trim() !== "" || Array.isArray(c.values) && c.values.length > 0 || attrValue !== "" || attrValues !== "" || c.hasAttribute && c.hasAttribute("start")
|
|
4620
5090
|
);
|
|
4621
5091
|
const focused = this.isFocusedInside();
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
this.removeAttribute("active");
|
|
4628
|
-
this.removeAttribute("focus");
|
|
4629
|
-
}
|
|
5092
|
+
const highlighted = focused || isOpen;
|
|
5093
|
+
if (hasValue || highlighted) this.setAttribute("active", "");
|
|
5094
|
+
else this.removeAttribute("active");
|
|
5095
|
+
if (highlighted) this.setAttribute("focus", "");
|
|
5096
|
+
else this.removeAttribute("focus");
|
|
4630
5097
|
}
|
|
4631
5098
|
/**
|
|
4632
5099
|
* variant="in" + ícone à esquerda dentro do campo (fx-input icon):
|
|
@@ -4704,6 +5171,10 @@ _FxFloatlabel.styles = css`
|
|
|
4704
5171
|
font-family: var(--fx-font-family);
|
|
4705
5172
|
font-size: var(--fx-font-size);
|
|
4706
5173
|
vertical-align: middle;
|
|
5174
|
+
/* Cor da label em UM único lugar. active (valor OU foco OU
|
|
5175
|
+
dropdown) só posiciona; focus (foco OU dropdown) só destaca —
|
|
5176
|
+
assim a label acompanha a borda, que só fica primária em foco. */
|
|
5177
|
+
--_flabel-color: var(--fx-text-muted, #94a3b8);
|
|
4707
5178
|
}
|
|
4708
5179
|
/* Full width: o host estica até o pai; o controle interno com
|
|
4709
5180
|
atributo full (ou largura herdada) acompanha. */
|
|
@@ -4735,7 +5206,7 @@ _FxFloatlabel.styles = css`
|
|
|
4735
5206
|
transform: translateY(-50%);
|
|
4736
5207
|
font-size: calc(var(--fx-font-size, 14px) - 3px);
|
|
4737
5208
|
font-weight: 600;
|
|
4738
|
-
color: var(--
|
|
5209
|
+
color: var(--_flabel-color);
|
|
4739
5210
|
background-color: var(--fx-surface-background, #fff);
|
|
4740
5211
|
padding: 0 4px;
|
|
4741
5212
|
line-height: 1;
|
|
@@ -4753,8 +5224,11 @@ _FxFloatlabel.styles = css`
|
|
|
4753
5224
|
color var(--fx-motion-duration-normal, 180ms)
|
|
4754
5225
|
var(--fx-motion-easing, ease-in-out);
|
|
4755
5226
|
}
|
|
4756
|
-
|
|
4757
|
-
|
|
5227
|
+
/* Destacada = foco (ou dropdown aberto). NÃO usar [active]: ele
|
|
5228
|
+
também é verdadeiro com valor e sem foco — era o que deixava a
|
|
5229
|
+
label primária com a borda neutra. */
|
|
5230
|
+
:host([focus]) {
|
|
5231
|
+
--_flabel-color: var(--fx-color-primary, #4f46e5);
|
|
4758
5232
|
}
|
|
4759
5233
|
|
|
4760
5234
|
/* ---- variant="in": label como placeholder dentro, sobe ao focar/valor ---- */
|
|
@@ -4773,7 +5247,8 @@ _FxFloatlabel.styles = css`
|
|
|
4773
5247
|
font-weight: 600;
|
|
4774
5248
|
background-color: var(--fx-surface-background, #fff);
|
|
4775
5249
|
padding: 0 5px;
|
|
4776
|
-
color:
|
|
5250
|
+
/* Sem color aqui: a especificidade desta regra
|
|
5251
|
+
(variant + active) venceria :host([error])/:host([focus]). */
|
|
4777
5252
|
}
|
|
4778
5253
|
|
|
4779
5254
|
/* ---- variant="in" com ícone dentro do campo (fx-input icon):
|
|
@@ -4804,16 +5279,22 @@ _FxFloatlabel.styles = css`
|
|
|
4804
5279
|
font-size: calc(var(--fx-font-size, 14px) - 3px);
|
|
4805
5280
|
font-weight: 600;
|
|
4806
5281
|
white-space: normal;
|
|
5282
|
+
/* Fora do campo, a label pode receber clique: o listener do
|
|
5283
|
+
render() foca o controle (nas variantes on/in o
|
|
5284
|
+
pointer-events: none é intencional, para não bloquear a borda). */
|
|
5285
|
+
pointer-events: auto;
|
|
4807
5286
|
}
|
|
4808
5287
|
|
|
4809
|
-
/* ---- estados de erro / sucesso ----
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
5288
|
+
/* ---- estados de erro / sucesso ----
|
|
5289
|
+
Declarados DEPOIS de :host([focus]) e com a mesma especificidade:
|
|
5290
|
+
a validação vence o foco pela ordem, sem depender de acidente. */
|
|
5291
|
+
:host([error]),
|
|
5292
|
+
:host([invalid]) {
|
|
5293
|
+
--_flabel-color: var(--fx-color-danger, #dc2626);
|
|
4813
5294
|
}
|
|
4814
|
-
:host([success])
|
|
4815
|
-
:host([valid])
|
|
4816
|
-
color: var(--fx-color-success, #10b981);
|
|
5295
|
+
:host([success]),
|
|
5296
|
+
:host([valid]) {
|
|
5297
|
+
--_flabel-color: var(--fx-color-success, #10b981);
|
|
4817
5298
|
}
|
|
4818
5299
|
|
|
4819
5300
|
/* ---- mensagem de erro sob o campo (fonte menor) ---- */
|
|
@@ -4838,7 +5319,7 @@ function defineFxFloatlabel() {
|
|
|
4838
5319
|
defineFxFloatlabel();
|
|
4839
5320
|
const _FxTextarea = class _FxTextarea extends FxFormElement {
|
|
4840
5321
|
static get observedAttributes() {
|
|
4841
|
-
return ["size", "placeholder", "disabled", "readonly", "rows", "maxlength", "error", "invalid", "success", "valid"];
|
|
5322
|
+
return ["size", "placeholder", "disabled", "readonly", "full", "rows", "maxlength", "error", "invalid", "success", "valid"];
|
|
4842
5323
|
}
|
|
4843
5324
|
get size() {
|
|
4844
5325
|
const s = this.getAttr("size", "md");
|
|
@@ -4851,10 +5332,22 @@ const _FxTextarea = class _FxTextarea extends FxFormElement {
|
|
|
4851
5332
|
return this.getAttr("value");
|
|
4852
5333
|
}
|
|
4853
5334
|
set value(value) {
|
|
4854
|
-
this.setAttribute("value", value);
|
|
5335
|
+
this.setAttribute("value", value == null ? "" : String(value));
|
|
4855
5336
|
this.syncInternalControl();
|
|
4856
5337
|
this.updateFormValue();
|
|
4857
5338
|
}
|
|
5339
|
+
get readonly() {
|
|
5340
|
+
return this.hasAttr("readonly");
|
|
5341
|
+
}
|
|
5342
|
+
set readonly(value) {
|
|
5343
|
+
this.toggleAttr("readonly", Boolean(value));
|
|
5344
|
+
}
|
|
5345
|
+
get full() {
|
|
5346
|
+
return this.hasAttr("full");
|
|
5347
|
+
}
|
|
5348
|
+
set full(value) {
|
|
5349
|
+
this.toggleAttr("full", Boolean(value));
|
|
5350
|
+
}
|
|
4858
5351
|
render() {
|
|
4859
5352
|
const placeholder = this.getAttr("placeholder");
|
|
4860
5353
|
const readonly = this.hasAttr("readonly");
|
|
@@ -4871,20 +5364,27 @@ const _FxTextarea = class _FxTextarea extends FxFormElement {
|
|
|
4871
5364
|
field.value = this.getAttr("value");
|
|
4872
5365
|
if (disabled) field.setAttribute("disabled", "");
|
|
4873
5366
|
if (readonly) field.setAttribute("readonly", "");
|
|
4874
|
-
const
|
|
5367
|
+
const notifyInput = () => {
|
|
4875
5368
|
this.value = field.value;
|
|
4876
5369
|
this.emitValueEvents();
|
|
5370
|
+
};
|
|
5371
|
+
const notifyChange = () => {
|
|
5372
|
+
this.value = field.value;
|
|
4877
5373
|
this.dispatchEvent(
|
|
4878
|
-
new CustomEvent(
|
|
5374
|
+
new CustomEvent("change", {
|
|
5375
|
+
bubbles: true,
|
|
5376
|
+
composed: true,
|
|
5377
|
+
detail: { value: field.value }
|
|
5378
|
+
})
|
|
4879
5379
|
);
|
|
4880
5380
|
};
|
|
4881
5381
|
field.addEventListener("input", (e) => {
|
|
4882
5382
|
e.stopPropagation();
|
|
4883
|
-
|
|
5383
|
+
notifyInput();
|
|
4884
5384
|
});
|
|
4885
5385
|
field.addEventListener("change", (e) => {
|
|
4886
5386
|
e.stopPropagation();
|
|
4887
|
-
|
|
5387
|
+
notifyChange();
|
|
4888
5388
|
});
|
|
4889
5389
|
this.updateFormValue();
|
|
4890
5390
|
}
|
|
@@ -8530,10 +9030,61 @@ function defineFxEditor() {
|
|
|
8530
9030
|
return defineElement("fx-editor", FxEditor);
|
|
8531
9031
|
}
|
|
8532
9032
|
defineFxEditor();
|
|
9033
|
+
const FOCUSABLE_SELECTOR = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"]), [contenteditable="true"]';
|
|
9034
|
+
function usable(nodes) {
|
|
9035
|
+
return [...nodes].filter(
|
|
9036
|
+
(el) => !el.hasAttribute("disabled") && el.getAttribute("aria-hidden") !== "true" && el.getAttribute("tabindex") !== "-1"
|
|
9037
|
+
);
|
|
9038
|
+
}
|
|
9039
|
+
function modalFocusables(panel, host) {
|
|
9040
|
+
const shadow = panel.querySelectorAll(FOCUSABLE_SELECTOR);
|
|
9041
|
+
const light = host.querySelectorAll(FOCUSABLE_SELECTOR);
|
|
9042
|
+
return usable([...shadow, ...light]);
|
|
9043
|
+
}
|
|
9044
|
+
function firstLightFocusable(host) {
|
|
9045
|
+
return usable(host.querySelectorAll(FOCUSABLE_SELECTOR))[0] ?? null;
|
|
9046
|
+
}
|
|
9047
|
+
const modalStack = [];
|
|
9048
|
+
function registerModalOpen(host) {
|
|
9049
|
+
if (!modalStack.includes(host)) modalStack.push(host);
|
|
9050
|
+
}
|
|
9051
|
+
function registerModalClose(host) {
|
|
9052
|
+
const index = modalStack.indexOf(host);
|
|
9053
|
+
if (index !== -1) modalStack.splice(index, 1);
|
|
9054
|
+
}
|
|
9055
|
+
function isTopmostModal(host) {
|
|
9056
|
+
if (modalStack.length === 0) return true;
|
|
9057
|
+
return modalStack[modalStack.length - 1] === host;
|
|
9058
|
+
}
|
|
9059
|
+
let scrollLocks = 0;
|
|
9060
|
+
let previousOverflow = "";
|
|
9061
|
+
let previousPaddingRight = "";
|
|
9062
|
+
function lockBodyScroll() {
|
|
9063
|
+
if (typeof document === "undefined" || !document.body) return;
|
|
9064
|
+
if (scrollLocks === 0) {
|
|
9065
|
+
const body = document.body;
|
|
9066
|
+
previousOverflow = body.style.overflow;
|
|
9067
|
+
previousPaddingRight = body.style.paddingRight;
|
|
9068
|
+
const scrollbar = window.innerWidth - document.documentElement.clientWidth;
|
|
9069
|
+
body.style.overflow = "hidden";
|
|
9070
|
+
if (scrollbar > 0) body.style.paddingRight = `${scrollbar}px`;
|
|
9071
|
+
}
|
|
9072
|
+
scrollLocks += 1;
|
|
9073
|
+
}
|
|
9074
|
+
function unlockBodyScroll() {
|
|
9075
|
+
if (typeof document === "undefined" || !document.body) return;
|
|
9076
|
+
if (scrollLocks === 0) return;
|
|
9077
|
+
scrollLocks -= 1;
|
|
9078
|
+
if (scrollLocks === 0) {
|
|
9079
|
+
document.body.style.overflow = previousOverflow;
|
|
9080
|
+
document.body.style.paddingRight = previousPaddingRight;
|
|
9081
|
+
}
|
|
9082
|
+
}
|
|
8533
9083
|
const _FxDialog = class _FxDialog extends FxElement {
|
|
8534
9084
|
constructor() {
|
|
8535
9085
|
super(...arguments);
|
|
8536
9086
|
this._previouslyFocused = null;
|
|
9087
|
+
this._locked = false;
|
|
8537
9088
|
}
|
|
8538
9089
|
static get observedAttributes() {
|
|
8539
9090
|
return ["open", "size", "heading"];
|
|
@@ -8547,6 +9098,11 @@ const _FxDialog = class _FxDialog extends FxElement {
|
|
|
8547
9098
|
render() {
|
|
8548
9099
|
const isOpen = this.open;
|
|
8549
9100
|
const heading = this.getAttr("heading");
|
|
9101
|
+
if (!isOpen) {
|
|
9102
|
+
this._release();
|
|
9103
|
+
this.setTemplate("");
|
|
9104
|
+
return;
|
|
9105
|
+
}
|
|
8550
9106
|
this.setTemplate(`
|
|
8551
9107
|
<div class="overlay" part="overlay" ${isOpen ? "" : "hidden"}>
|
|
8552
9108
|
<div class="dialog" role="dialog" aria-modal="true" aria-label="${esc(heading)}" tabindex="-1">
|
|
@@ -8565,10 +9121,8 @@ const _FxDialog = class _FxDialog extends FxElement {
|
|
|
8565
9121
|
const overlay = this.root.querySelector(".overlay");
|
|
8566
9122
|
const closeBtn = this.root.querySelector(".close");
|
|
8567
9123
|
const close = () => {
|
|
9124
|
+
if (!this.open) return;
|
|
8568
9125
|
this.open = false;
|
|
8569
|
-
this._cleanup?.();
|
|
8570
|
-
this._cleanup = void 0;
|
|
8571
|
-
this._restoreFocus();
|
|
8572
9126
|
this.dispatchEvent(new CustomEvent("close", { bubbles: true, composed: true }));
|
|
8573
9127
|
};
|
|
8574
9128
|
overlay?.addEventListener("click", (e) => {
|
|
@@ -8577,6 +9131,7 @@ const _FxDialog = class _FxDialog extends FxElement {
|
|
|
8577
9131
|
closeBtn?.addEventListener("click", close);
|
|
8578
9132
|
const onKey = (e) => {
|
|
8579
9133
|
if (e.key === "Escape") {
|
|
9134
|
+
if (!isTopmostModal(this)) return;
|
|
8580
9135
|
e.stopPropagation();
|
|
8581
9136
|
close();
|
|
8582
9137
|
return;
|
|
@@ -8585,29 +9140,48 @@ const _FxDialog = class _FxDialog extends FxElement {
|
|
|
8585
9140
|
};
|
|
8586
9141
|
document.addEventListener("keydown", onKey);
|
|
8587
9142
|
this._cleanup = () => document.removeEventListener("keydown", onKey);
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
9143
|
+
const alreadyOpen = this._locked;
|
|
9144
|
+
if (!this._locked) {
|
|
9145
|
+
lockBodyScroll();
|
|
9146
|
+
registerModalOpen(this);
|
|
9147
|
+
this._locked = true;
|
|
9148
|
+
}
|
|
9149
|
+
if (!alreadyOpen) {
|
|
9150
|
+
requestAnimationFrame(() => {
|
|
9151
|
+
if (!this.open) return;
|
|
9152
|
+
this._previouslyFocused = document.activeElement;
|
|
9153
|
+
const first = firstLightFocusable(this) ?? this.root.querySelector(".dialog");
|
|
9154
|
+
first?.focus();
|
|
9155
|
+
});
|
|
9156
|
+
}
|
|
8592
9157
|
this.dispatchEvent(new CustomEvent("open", { bubbles: true, composed: true }));
|
|
8593
9158
|
}
|
|
8594
|
-
/**
|
|
9159
|
+
/** Desfaz listeners, foco e scroll lock ao fechar/desconectar. */
|
|
9160
|
+
_release() {
|
|
9161
|
+
this._cleanup?.();
|
|
9162
|
+
this._cleanup = void 0;
|
|
9163
|
+
registerModalClose(this);
|
|
9164
|
+
if (this._locked) {
|
|
9165
|
+
unlockBodyScroll();
|
|
9166
|
+
this._locked = false;
|
|
9167
|
+
}
|
|
9168
|
+
this._restoreFocus();
|
|
9169
|
+
}
|
|
9170
|
+
/** Mantém o foco dentro do modal (WCAG 2.4.3 / 2.1.2), incluindo o slot. */
|
|
8595
9171
|
_trapFocus(e) {
|
|
8596
9172
|
const dialog = this.root.querySelector(".dialog");
|
|
8597
9173
|
if (!dialog) return;
|
|
8598
|
-
const focusables = dialog
|
|
8599
|
-
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
8600
|
-
);
|
|
9174
|
+
const focusables = modalFocusables(dialog, this);
|
|
8601
9175
|
if (!focusables.length) return;
|
|
8602
9176
|
const first = focusables[0];
|
|
8603
9177
|
const last = focusables[focusables.length - 1];
|
|
8604
|
-
const active = this.root.activeElement;
|
|
8605
|
-
if (e.shiftKey && (active === first || active === dialog)) {
|
|
9178
|
+
const active = this.root.activeElement ?? document.activeElement;
|
|
9179
|
+
if (e.shiftKey && (active === first || active === dialog || active === this)) {
|
|
8606
9180
|
e.preventDefault();
|
|
8607
|
-
last
|
|
9181
|
+
last?.focus();
|
|
8608
9182
|
} else if (!e.shiftKey && active === last) {
|
|
8609
9183
|
e.preventDefault();
|
|
8610
|
-
first
|
|
9184
|
+
first?.focus();
|
|
8611
9185
|
}
|
|
8612
9186
|
}
|
|
8613
9187
|
_restoreFocus() {
|
|
@@ -8616,9 +9190,7 @@ const _FxDialog = class _FxDialog extends FxElement {
|
|
|
8616
9190
|
}
|
|
8617
9191
|
disconnectedCallback() {
|
|
8618
9192
|
super.disconnectedCallback();
|
|
8619
|
-
this.
|
|
8620
|
-
this._cleanup = void 0;
|
|
8621
|
-
this._restoreFocus();
|
|
9193
|
+
this._release();
|
|
8622
9194
|
}
|
|
8623
9195
|
};
|
|
8624
9196
|
_FxDialog.styles = css`
|
|
@@ -9024,7 +9596,35 @@ const _FxFileUpload = class _FxFileUpload extends FxFormElement {
|
|
|
9024
9596
|
return this.getAttr("value");
|
|
9025
9597
|
}
|
|
9026
9598
|
set value(v) {
|
|
9027
|
-
this.setAttribute("value", v);
|
|
9599
|
+
this.setAttribute("value", v == null ? "" : String(v));
|
|
9600
|
+
}
|
|
9601
|
+
get disabled() {
|
|
9602
|
+
return this.hasAttr("disabled");
|
|
9603
|
+
}
|
|
9604
|
+
set disabled(v) {
|
|
9605
|
+
this.toggleAttr("disabled", Boolean(v));
|
|
9606
|
+
}
|
|
9607
|
+
get multiple() {
|
|
9608
|
+
return this.hasAttr("multiple");
|
|
9609
|
+
}
|
|
9610
|
+
set multiple(v) {
|
|
9611
|
+
this.toggleAttr("multiple", Boolean(v));
|
|
9612
|
+
}
|
|
9613
|
+
get full() {
|
|
9614
|
+
return this.hasAttr("full");
|
|
9615
|
+
}
|
|
9616
|
+
set full(v) {
|
|
9617
|
+
this.toggleAttr("full", Boolean(v));
|
|
9618
|
+
}
|
|
9619
|
+
/**
|
|
9620
|
+
* Nome literal: o Vue NÃO cameliza atributos em elementos customizados,
|
|
9621
|
+
* então `:show-progress` procura a propriedade `'show-progress'`.
|
|
9622
|
+
*/
|
|
9623
|
+
get "show-progress"() {
|
|
9624
|
+
return this.hasAttr("show-progress");
|
|
9625
|
+
}
|
|
9626
|
+
set "show-progress"(v) {
|
|
9627
|
+
this.toggleAttr("show-progress", Boolean(v));
|
|
9028
9628
|
}
|
|
9029
9629
|
get progress() {
|
|
9030
9630
|
return Math.min(100, Math.max(0, Number(this.getAttr("progress", "0")) || 0));
|
|
@@ -9705,6 +10305,9 @@ const _FxAvatar = class _FxAvatar extends FxElement {
|
|
|
9705
10305
|
get variant() {
|
|
9706
10306
|
return this.getAttr("variant", "text");
|
|
9707
10307
|
}
|
|
10308
|
+
set variant(v) {
|
|
10309
|
+
this.setAttribute("variant", v);
|
|
10310
|
+
}
|
|
9708
10311
|
get src() {
|
|
9709
10312
|
return this.getAttr("src");
|
|
9710
10313
|
}
|
|
@@ -9808,6 +10411,18 @@ const _FxIcon = class _FxIcon extends FxElement {
|
|
|
9808
10411
|
if (value) this.setAttribute("label", value);
|
|
9809
10412
|
else this.removeAttribute("label");
|
|
9810
10413
|
}
|
|
10414
|
+
get fill() {
|
|
10415
|
+
return this.hasAttr("fill");
|
|
10416
|
+
}
|
|
10417
|
+
set fill(value) {
|
|
10418
|
+
this.toggleAttr("fill", Boolean(value));
|
|
10419
|
+
}
|
|
10420
|
+
get bold() {
|
|
10421
|
+
return this.hasAttr("bold");
|
|
10422
|
+
}
|
|
10423
|
+
set bold(value) {
|
|
10424
|
+
this.toggleAttr("bold", Boolean(value));
|
|
10425
|
+
}
|
|
9811
10426
|
connectedCallback() {
|
|
9812
10427
|
loadFenixIconsFont();
|
|
9813
10428
|
super.connectedCallback();
|
|
@@ -9854,6 +10469,12 @@ const _FxCard = class _FxCard extends FxElement {
|
|
|
9854
10469
|
static get observedAttributes() {
|
|
9855
10470
|
return ["variant", "radius", "size", "padded", "heading"];
|
|
9856
10471
|
}
|
|
10472
|
+
get padded() {
|
|
10473
|
+
return this.hasAttr("padded");
|
|
10474
|
+
}
|
|
10475
|
+
set padded(value) {
|
|
10476
|
+
this.toggleAttr("padded", Boolean(value));
|
|
10477
|
+
}
|
|
9857
10478
|
render() {
|
|
9858
10479
|
const heading = this.getAttr("heading");
|
|
9859
10480
|
this.setTemplate(`
|
|
@@ -12374,6 +12995,40 @@ const _FxTabs = class _FxTabs extends FxElement {
|
|
|
12374
12995
|
set value(value) {
|
|
12375
12996
|
this.setAttribute("value", value);
|
|
12376
12997
|
}
|
|
12998
|
+
connectedCallback() {
|
|
12999
|
+
super.connectedCallback();
|
|
13000
|
+
this._observeLightDom();
|
|
13001
|
+
}
|
|
13002
|
+
disconnectedCallback() {
|
|
13003
|
+
super.disconnectedCallback();
|
|
13004
|
+
this._lightObserver?.disconnect();
|
|
13005
|
+
this._siblingObserver?.disconnect();
|
|
13006
|
+
this._lightObserver = void 0;
|
|
13007
|
+
this._siblingObserver = void 0;
|
|
13008
|
+
}
|
|
13009
|
+
_observeLightDom() {
|
|
13010
|
+
this._lightObserver?.disconnect();
|
|
13011
|
+
this._lightObserver = new MutationObserver(() => this.render());
|
|
13012
|
+
this._lightObserver.observe(this, {
|
|
13013
|
+
childList: true,
|
|
13014
|
+
subtree: true,
|
|
13015
|
+
attributes: true,
|
|
13016
|
+
attributeFilter: ["tab", "disabled"]
|
|
13017
|
+
});
|
|
13018
|
+
this._siblingObserver?.disconnect();
|
|
13019
|
+
const parent = this.parentNode;
|
|
13020
|
+
if (parent) {
|
|
13021
|
+
this._siblingObserver = new MutationObserver((mutations) => {
|
|
13022
|
+
const affectsPanels = mutations.some(
|
|
13023
|
+
(m) => [...m.addedNodes, ...m.removedNodes].some(
|
|
13024
|
+
(node) => node.nodeType === Node.ELEMENT_NODE && (node.matches("fx-tab-panel") || Boolean(node.querySelector("fx-tab-panel")))
|
|
13025
|
+
)
|
|
13026
|
+
);
|
|
13027
|
+
if (affectsPanels) this.render();
|
|
13028
|
+
});
|
|
13029
|
+
this._siblingObserver.observe(parent, { childList: true });
|
|
13030
|
+
}
|
|
13031
|
+
}
|
|
12377
13032
|
render() {
|
|
12378
13033
|
const tabs = Array.from(this.querySelectorAll("fx-tab"));
|
|
12379
13034
|
const active = this.value || tabs[0]?.getAttribute("tab") || "";
|
|
@@ -12902,10 +13557,11 @@ const _FxDrawer = class _FxDrawer extends FxElement {
|
|
|
12902
13557
|
this._onKeydown = () => {
|
|
12903
13558
|
};
|
|
12904
13559
|
this._previouslyFocused = null;
|
|
13560
|
+
this._locked = false;
|
|
13561
|
+
this._wasOpen = false;
|
|
12905
13562
|
this._close = () => {
|
|
12906
13563
|
if (!this.open) return;
|
|
12907
13564
|
this.open = false;
|
|
12908
|
-
this._restoreFocus();
|
|
12909
13565
|
this.dispatchEvent(
|
|
12910
13566
|
new CustomEvent("close", { bubbles: true, composed: true })
|
|
12911
13567
|
);
|
|
@@ -12935,6 +13591,8 @@ const _FxDrawer = class _FxDrawer extends FxElement {
|
|
|
12935
13591
|
}
|
|
12936
13592
|
render() {
|
|
12937
13593
|
const title = this.getAttr("title");
|
|
13594
|
+
const wasOpen = this._wasOpen;
|
|
13595
|
+
this._wasOpen = this.open;
|
|
12938
13596
|
this.setTemplate(`
|
|
12939
13597
|
<div class="overlay" part="overlay"></div>
|
|
12940
13598
|
<div class="panel" part="panel" role="dialog" aria-modal="true" aria-label="${esc(title || "Drawer")}" tabindex="-1">
|
|
@@ -12945,20 +13603,40 @@ const _FxDrawer = class _FxDrawer extends FxElement {
|
|
|
12945
13603
|
this.root.querySelector(".overlay")?.addEventListener("click", this._close);
|
|
12946
13604
|
this.root.querySelector(".close")?.addEventListener("click", this._close);
|
|
12947
13605
|
if (this.open) {
|
|
12948
|
-
|
|
12949
|
-
|
|
13606
|
+
if (!this._locked) {
|
|
13607
|
+
lockBodyScroll();
|
|
13608
|
+
registerModalOpen(this);
|
|
13609
|
+
this._locked = true;
|
|
13610
|
+
}
|
|
13611
|
+
if (!wasOpen) {
|
|
13612
|
+
requestAnimationFrame(() => {
|
|
13613
|
+
if (!this.open) return;
|
|
12950
13614
|
this._previouslyFocused = document.activeElement;
|
|
12951
|
-
|
|
12952
|
-
|
|
12953
|
-
|
|
13615
|
+
const first = firstLightFocusable(this) ?? this.root.querySelector(".panel");
|
|
13616
|
+
first?.focus();
|
|
13617
|
+
});
|
|
13618
|
+
}
|
|
13619
|
+
} else {
|
|
13620
|
+
this._release();
|
|
12954
13621
|
}
|
|
12955
13622
|
}
|
|
13623
|
+
/** Desfaz scroll lock e restaura o foco ao fechar/desconectar. */
|
|
13624
|
+
_release() {
|
|
13625
|
+
registerModalClose(this);
|
|
13626
|
+
if (this._locked) {
|
|
13627
|
+
unlockBodyScroll();
|
|
13628
|
+
this._locked = false;
|
|
13629
|
+
}
|
|
13630
|
+
this._restoreFocus();
|
|
13631
|
+
}
|
|
12956
13632
|
connectedCallback() {
|
|
12957
13633
|
super.connectedCallback?.();
|
|
12958
13634
|
this._onKeydown = (e) => {
|
|
12959
|
-
if (
|
|
13635
|
+
if (!this.open) return;
|
|
13636
|
+
if (e.key === "Escape") {
|
|
13637
|
+
if (!isTopmostModal(this)) return;
|
|
12960
13638
|
this._close();
|
|
12961
|
-
} else if (e.key === "Tab"
|
|
13639
|
+
} else if (e.key === "Tab") {
|
|
12962
13640
|
this._trapFocus(e);
|
|
12963
13641
|
}
|
|
12964
13642
|
};
|
|
@@ -12966,26 +13644,24 @@ const _FxDrawer = class _FxDrawer extends FxElement {
|
|
|
12966
13644
|
}
|
|
12967
13645
|
disconnectedCallback() {
|
|
12968
13646
|
document.removeEventListener("keydown", this._onKeydown);
|
|
12969
|
-
this.
|
|
13647
|
+
this._release();
|
|
12970
13648
|
super.disconnectedCallback?.();
|
|
12971
13649
|
}
|
|
12972
|
-
/** Mantém o foco dentro do drawer (WCAG 2.4.3 / 2.1.2). */
|
|
13650
|
+
/** Mantém o foco dentro do drawer (WCAG 2.4.3 / 2.1.2), incluindo o slot. */
|
|
12973
13651
|
_trapFocus(e) {
|
|
12974
13652
|
const panel = this.root.querySelector(".panel");
|
|
12975
13653
|
if (!panel) return;
|
|
12976
|
-
const focusables = panel
|
|
12977
|
-
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
12978
|
-
);
|
|
13654
|
+
const focusables = modalFocusables(panel, this);
|
|
12979
13655
|
if (!focusables.length) return;
|
|
12980
13656
|
const first = focusables[0];
|
|
12981
13657
|
const last = focusables[focusables.length - 1];
|
|
12982
|
-
const active = this.root.activeElement;
|
|
12983
|
-
if (e.shiftKey && (active === first || active === panel)) {
|
|
13658
|
+
const active = this.root.activeElement ?? document.activeElement;
|
|
13659
|
+
if (e.shiftKey && (active === first || active === panel || active === this)) {
|
|
12984
13660
|
e.preventDefault();
|
|
12985
|
-
last
|
|
13661
|
+
last?.focus();
|
|
12986
13662
|
} else if (!e.shiftKey && active === last) {
|
|
12987
13663
|
e.preventDefault();
|
|
12988
|
-
first
|
|
13664
|
+
first?.focus();
|
|
12989
13665
|
}
|
|
12990
13666
|
}
|
|
12991
13667
|
_restoreFocus() {
|
|
@@ -13278,11 +13954,17 @@ const _FxAutocomplete = class _FxAutocomplete extends FxFormElement {
|
|
|
13278
13954
|
set size(v) {
|
|
13279
13955
|
this.setAttribute("size", v);
|
|
13280
13956
|
}
|
|
13957
|
+
get disabled() {
|
|
13958
|
+
return this.hasAttr("disabled");
|
|
13959
|
+
}
|
|
13960
|
+
set disabled(v) {
|
|
13961
|
+
this.toggleAttr("disabled", Boolean(v));
|
|
13962
|
+
}
|
|
13281
13963
|
get value() {
|
|
13282
13964
|
return this.getAttr("value");
|
|
13283
13965
|
}
|
|
13284
13966
|
set value(v) {
|
|
13285
|
-
this.setAttribute("value", v);
|
|
13967
|
+
this.setAttribute("value", v == null ? "" : String(v));
|
|
13286
13968
|
this.syncInternalControl();
|
|
13287
13969
|
this.updateFormValue();
|
|
13288
13970
|
}
|
|
@@ -13816,6 +14498,7 @@ const _FxOrderList = class _FxOrderList extends FxElement {
|
|
|
13816
14498
|
static get observedAttributes() {
|
|
13817
14499
|
return [
|
|
13818
14500
|
"data",
|
|
14501
|
+
"items",
|
|
13819
14502
|
"data-key",
|
|
13820
14503
|
"filter",
|
|
13821
14504
|
"filter-by",
|
|
@@ -13829,13 +14512,35 @@ const _FxOrderList = class _FxOrderList extends FxElement {
|
|
|
13829
14512
|
];
|
|
13830
14513
|
}
|
|
13831
14514
|
set data(value) {
|
|
13832
|
-
this._data = value;
|
|
14515
|
+
this._data = Array.isArray(value) ? value : [];
|
|
13833
14516
|
this._filteredData = null;
|
|
13834
14517
|
this.render();
|
|
13835
14518
|
}
|
|
13836
14519
|
get data() {
|
|
13837
14520
|
return this._data;
|
|
13838
14521
|
}
|
|
14522
|
+
/**
|
|
14523
|
+
* Alias de `data` para o binding do Vue/React: `data` colide com a
|
|
14524
|
+
* tipagem nativa de atributos HTML (`dataset`/`data-*`) e não reflete em
|
|
14525
|
+
* atributo. `items` funciona como propriedade e como atributo JSON.
|
|
14526
|
+
*/
|
|
14527
|
+
get items() {
|
|
14528
|
+
return this._data;
|
|
14529
|
+
}
|
|
14530
|
+
set items(value) {
|
|
14531
|
+
const list = Array.isArray(value) ? value : [];
|
|
14532
|
+
this._data = list;
|
|
14533
|
+
this._filteredData = null;
|
|
14534
|
+
const json = (() => {
|
|
14535
|
+
try {
|
|
14536
|
+
return JSON.stringify(list);
|
|
14537
|
+
} catch {
|
|
14538
|
+
return null;
|
|
14539
|
+
}
|
|
14540
|
+
})();
|
|
14541
|
+
if (json !== null && this.getAttr("items") !== json) this.setAttribute("items", json);
|
|
14542
|
+
else if (this.isConnected) this.render();
|
|
14543
|
+
}
|
|
13839
14544
|
set selection(value) {
|
|
13840
14545
|
this._selection = value;
|
|
13841
14546
|
this.render();
|
|
@@ -13915,7 +14620,16 @@ const _FxOrderList = class _FxOrderList extends FxElement {
|
|
|
13915
14620
|
super.connectedCallback();
|
|
13916
14621
|
}
|
|
13917
14622
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
13918
|
-
if (
|
|
14623
|
+
if (name === "items") {
|
|
14624
|
+
try {
|
|
14625
|
+
const parsed = JSON.parse(newValue ?? "[]");
|
|
14626
|
+
this._data = Array.isArray(parsed) ? parsed : [];
|
|
14627
|
+
this._filteredData = null;
|
|
14628
|
+
} catch {
|
|
14629
|
+
this._data = [];
|
|
14630
|
+
this._filteredData = null;
|
|
14631
|
+
}
|
|
14632
|
+
} else if (this.getAttr("data", "") !== "") {
|
|
13919
14633
|
this._parseDataAttribute();
|
|
13920
14634
|
}
|
|
13921
14635
|
super.attributeChangedCallback(name, oldValue, newValue);
|