@wrrdev/fenix-ui 1.0.14 → 1.0.16
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 +332 -332
- package/dist/components/accordion/accordion.d.ts +1 -0
- package/dist/components/accordion/accordion.d.ts.map +1 -1
- package/dist/components/accordion/accordion.js +18 -13
- package/dist/components/accordion/accordion.js.map +1 -1
- package/dist/components/accordion/index.js.map +1 -1
- package/dist/components/alert/alert.d.ts.map +1 -1
- package/dist/components/alert/alert.js +2 -1
- package/dist/components/alert/alert.js.map +1 -1
- package/dist/components/alert/index.js.map +1 -1
- package/dist/components/autocomplete/autocomplete.d.ts +3 -0
- package/dist/components/autocomplete/autocomplete.d.ts.map +1 -1
- package/dist/components/autocomplete/autocomplete.js +28 -4
- package/dist/components/autocomplete/autocomplete.js.map +1 -1
- package/dist/components/autocomplete/index.js.map +1 -1
- package/dist/components/datepicker/datepicker.d.ts.map +1 -1
- package/dist/components/datepicker/datepicker.js +7 -6
- package/dist/components/datepicker/datepicker.js.map +1 -1
- package/dist/components/dialog/dialog.d.ts +4 -0
- package/dist/components/dialog/dialog.d.ts.map +1 -1
- package/dist/components/dialog/dialog.js +42 -4
- package/dist/components/dialog/dialog.js.map +1 -1
- package/dist/components/dialog/index.js.map +1 -1
- package/dist/components/drawer/drawer.d.ts +5 -0
- package/dist/components/drawer/drawer.d.ts.map +1 -1
- package/dist/components/drawer/drawer.js +49 -15
- package/dist/components/drawer/drawer.js.map +1 -1
- package/dist/components/dropdown/dropdown.d.ts +5 -0
- package/dist/components/dropdown/dropdown.d.ts.map +1 -1
- package/dist/components/dropdown/dropdown.js +34 -10
- package/dist/components/dropdown/dropdown.js.map +1 -1
- package/dist/components/input/input.d.ts.map +1 -1
- package/dist/components/input/input.js +4 -3
- package/dist/components/input/input.js.map +1 -1
- package/dist/components/knob/index.js.map +1 -1
- package/dist/components/knob/knob.d.ts +2 -0
- package/dist/components/knob/knob.d.ts.map +1 -1
- package/dist/components/knob/knob.js +15 -3
- package/dist/components/knob/knob.js.map +1 -1
- package/dist/components/multiselect/multiselect.d.ts +2 -0
- package/dist/components/multiselect/multiselect.d.ts.map +1 -1
- package/dist/components/multiselect/multiselect.js +38 -8
- package/dist/components/multiselect/multiselect.js.map +1 -1
- package/dist/components/orderlist/orderlist.d.ts.map +1 -1
- package/dist/components/orderlist/orderlist.js +1 -3
- package/dist/components/orderlist/orderlist.js.map +1 -1
- package/dist/components/pagination/index.js.map +1 -1
- package/dist/components/pagination/pagination.d.ts +1 -0
- package/dist/components/pagination/pagination.d.ts.map +1 -1
- package/dist/components/pagination/pagination.js +19 -8
- package/dist/components/pagination/pagination.js.map +1 -1
- package/dist/components/picklist/picklist.d.ts.map +1 -1
- package/dist/components/picklist/picklist.js +1 -3
- package/dist/components/picklist/picklist.js.map +1 -1
- package/dist/components/progress/index.js.map +1 -1
- package/dist/components/progress/progress.d.ts.map +1 -1
- package/dist/components/progress/progress.js +2 -1
- package/dist/components/progress/progress.js.map +1 -1
- package/dist/components/radio/radio.js +1 -1
- package/dist/components/radio/radio.js.map +1 -1
- package/dist/components/select/select.d.ts +2 -0
- package/dist/components/select/select.d.ts.map +1 -1
- package/dist/components/select/select.js +36 -5
- package/dist/components/select/select.js.map +1 -1
- package/dist/components/skeleton/index.js.map +1 -1
- package/dist/components/skeleton/skeleton.d.ts +2 -0
- package/dist/components/skeleton/skeleton.d.ts.map +1 -1
- package/dist/components/skeleton/skeleton.js +7 -2
- package/dist/components/skeleton/skeleton.js.map +1 -1
- package/dist/components/table/expr.d.ts +38 -0
- package/dist/components/table/expr.d.ts.map +1 -0
- package/dist/components/table/expr.js +429 -0
- package/dist/components/table/expr.js.map +1 -0
- package/dist/components/table/table.d.ts +26 -8
- package/dist/components/table/table.d.ts.map +1 -1
- package/dist/components/table/table.js +402 -140
- package/dist/components/table/table.js.map +1 -1
- package/dist/components/tabs/tabs.d.ts.map +1 -1
- package/dist/components/tabs/tabs.js +3 -2
- package/dist/components/tabs/tabs.js.map +1 -1
- package/dist/components/textarea/index.js.map +1 -1
- package/dist/components/textarea/textarea.d.ts.map +1 -1
- package/dist/components/textarea/textarea.js +4 -3
- package/dist/components/textarea/textarea.js.map +1 -1
- package/dist/components/toast/index.js.map +1 -1
- package/dist/components/tooltip/directive.js.map +1 -1
- package/dist/components/tooltip/index.js.map +1 -1
- package/dist/components/tooltip/tooltip.d.ts.map +1 -1
- package/dist/components/tooltip/tooltip.js +2 -1
- package/dist/components/tooltip/tooltip.js.map +1 -1
- package/dist/core/jsx.js.map +1 -1
- package/dist/core/presets.js.map +1 -1
- package/dist/core/sanitize.d.ts +36 -0
- package/dist/core/sanitize.d.ts.map +1 -0
- package/dist/core/sanitize.js +37 -0
- package/dist/core/sanitize.js.map +1 -0
- package/dist/core/tokens.js.map +1 -1
- package/dist/core/vue.js.map +1 -1
- package/dist/docs/app.d.ts.map +1 -1
- package/dist/docs/componentes/table.doc.d.ts.map +1 -1
- package/dist/fenix-ui.esm.js.map +1 -1
- package/dist/fenix-ui.umd.min.js +339 -208
- package/dist/fenix-ui2.esm.js +1156 -243
- package/dist/fenix-ui2.esm.js.map +1 -1
- package/dist/fenix-ui3.esm.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/plugins/auto-import.js.map +1 -1
- package/package.json +212 -212
package/dist/fenix-ui2.esm.js
CHANGED
|
@@ -305,6 +305,37 @@ function defineElement(tag, ctor) {
|
|
|
305
305
|
}
|
|
306
306
|
return customElements.get(tag) ?? ctor;
|
|
307
307
|
}
|
|
308
|
+
const HTML_ENTITIES = {
|
|
309
|
+
"&": "&",
|
|
310
|
+
"<": "<",
|
|
311
|
+
">": ">",
|
|
312
|
+
'"': """,
|
|
313
|
+
"'": "'",
|
|
314
|
+
"/": "/",
|
|
315
|
+
"`": "`",
|
|
316
|
+
"=": "="
|
|
317
|
+
};
|
|
318
|
+
const SANITIZE_REGEX = /[&<>"'`=/]/g;
|
|
319
|
+
function esc(value) {
|
|
320
|
+
if (value == null) return "";
|
|
321
|
+
return String(value).replace(SANITIZE_REGEX, (char) => HTML_ENTITIES[char] || char);
|
|
322
|
+
}
|
|
323
|
+
function stripTags(html) {
|
|
324
|
+
return html.replace(/<[^>]*>/g, "");
|
|
325
|
+
}
|
|
326
|
+
function sanitizeData(data) {
|
|
327
|
+
if (data == null) return data;
|
|
328
|
+
if (typeof data === "string") return esc(data);
|
|
329
|
+
if (Array.isArray(data)) return data.map(sanitizeData);
|
|
330
|
+
if (typeof data === "object") {
|
|
331
|
+
const result = {};
|
|
332
|
+
for (const [key2, value] of Object.entries(data)) {
|
|
333
|
+
result[key2] = sanitizeData(value);
|
|
334
|
+
}
|
|
335
|
+
return result;
|
|
336
|
+
}
|
|
337
|
+
return data;
|
|
338
|
+
}
|
|
308
339
|
const FX_JSX_TYPES = true;
|
|
309
340
|
const _FxButton = class _FxButton extends FxElement {
|
|
310
341
|
constructor() {
|
|
@@ -633,6 +664,23 @@ const _FxSelect = class _FxSelect extends FxElement {
|
|
|
633
664
|
new CustomEvent("change", { bubbles: true, composed: true, detail: { value } })
|
|
634
665
|
);
|
|
635
666
|
}
|
|
667
|
+
/** Navegação por teclado entre as opções do listbox (WCAG 2.1.1). */
|
|
668
|
+
_navigateOptions(e) {
|
|
669
|
+
e.preventDefault();
|
|
670
|
+
if (!this.hasAttr("open")) {
|
|
671
|
+
this.toggleAttribute("open");
|
|
672
|
+
this.render();
|
|
673
|
+
}
|
|
674
|
+
const opts = Array.from(this.root.querySelectorAll(".opt"));
|
|
675
|
+
if (!opts.length) return;
|
|
676
|
+
const current = opts.indexOf(this.root.activeElement);
|
|
677
|
+
let next = 0;
|
|
678
|
+
if (e.key === "ArrowDown") next = current === -1 ? 0 : Math.min(current + 1, opts.length - 1);
|
|
679
|
+
else if (e.key === "ArrowUp") next = current === -1 ? opts.length - 1 : Math.max(current - 1, 0);
|
|
680
|
+
else if (e.key === "Home") next = 0;
|
|
681
|
+
else if (e.key === "End") next = opts.length - 1;
|
|
682
|
+
opts[next]?.focus();
|
|
683
|
+
}
|
|
636
684
|
render() {
|
|
637
685
|
const prevOpen = this.hasAttr("open");
|
|
638
686
|
const prevSearch = this.root.querySelector(".search");
|
|
@@ -650,18 +698,18 @@ const _FxSelect = class _FxSelect extends FxElement {
|
|
|
650
698
|
const filtered = search ? opts.filter((o) => o.label.toLowerCase().includes(search.toLowerCase())) : opts;
|
|
651
699
|
this.setTemplate(`
|
|
652
700
|
<span class="trigger" part="trigger" role="button" tabindex="${this.disabled ? -1 : 0}" aria-haspopup="listbox" aria-expanded="${prevOpen}">
|
|
653
|
-
<span class="${selectedLabel ? "label" : "placeholder"}">${selectedLabel || placeholder}</span>
|
|
701
|
+
<span class="${selectedLabel ? "label" : "placeholder"}">${esc(selectedLabel) || esc(placeholder)}</span>
|
|
654
702
|
<span class="actions">
|
|
655
703
|
${this.hasAttr("clearable") && current ? '<button type="button" class="clear" part="clear" aria-label="Limpar">×</button>' : ""}
|
|
656
704
|
<span class="caret">▼</span>
|
|
657
705
|
</span>
|
|
658
706
|
</span>
|
|
659
707
|
<div class="panel" part="panel" role="listbox">
|
|
660
|
-
${this.hasAttr("searchable") ? `<input class="search" part="search" type="text" placeholder="${this.getAttr("search-placeholder", "Pesquisar…")}">` : ""}
|
|
708
|
+
${this.hasAttr("searchable") ? `<input class="search" part="search" type="text" placeholder="${esc(this.getAttr("search-placeholder", "Pesquisar…"))}">` : ""}
|
|
661
709
|
${filtered.map((o) => `
|
|
662
|
-
<button type="button" class="opt" role="option" data-value="${o.value}"
|
|
663
|
-
aria-selected="${o.value === current}">${o.label}</button>`).join("")}
|
|
664
|
-
${filtered.length === 0 ? `<div class="empty">${this.getAttr("no-results", "Nenhum resultado")}</div>` : ""}
|
|
710
|
+
<button type="button" class="opt" role="option" data-value="${esc(o.value)}"
|
|
711
|
+
aria-selected="${o.value === current}">${esc(o.label)}</button>`).join("")}
|
|
712
|
+
${filtered.length === 0 ? `<div class="empty">${esc(this.getAttr("no-results", "Nenhum resultado"))}</div>` : ""}
|
|
665
713
|
</div>
|
|
666
714
|
`);
|
|
667
715
|
if (prevOpen) this.setAttribute("open", "");
|
|
@@ -689,9 +737,22 @@ const _FxSelect = class _FxSelect extends FxElement {
|
|
|
689
737
|
this.root.querySelector(".search")?.focus();
|
|
690
738
|
});
|
|
691
739
|
trigger.addEventListener("keydown", (e) => {
|
|
740
|
+
if (this.disabled) return;
|
|
692
741
|
if (e.key === "Enter" || e.key === " ") {
|
|
693
742
|
e.preventDefault();
|
|
694
743
|
trigger.click();
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
if (e.key === "Escape") {
|
|
747
|
+
if (this.hasAttr("open")) {
|
|
748
|
+
this.removeAttribute("open");
|
|
749
|
+
this.render();
|
|
750
|
+
this.root.querySelector(".trigger")?.focus();
|
|
751
|
+
}
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
if (e.key === "ArrowDown" || e.key === "ArrowUp" || e.key === "Home" || e.key === "End") {
|
|
755
|
+
this._navigateOptions(e);
|
|
695
756
|
}
|
|
696
757
|
});
|
|
697
758
|
this.root.querySelector(".clear")?.addEventListener("click", (e) => {
|
|
@@ -901,9 +962,9 @@ const _FxInput = class _FxInput extends FxElement {
|
|
|
901
962
|
const step = this.getAttr("step");
|
|
902
963
|
this.setTemplate(`
|
|
903
964
|
${this.hasAttr("clearable") ? '<div class="wrap">' : ""}
|
|
904
|
-
<input class="field" part="input" type="${type}"
|
|
905
|
-
${placeholder ? `placeholder="${placeholder}"` : ""}
|
|
906
|
-
${min ? `min="${min}"` : ""} ${max ? `max="${max}"` : ""} ${step ? `step="${step}"` : ""}
|
|
965
|
+
<input class="field" part="input" type="${esc(type)}"
|
|
966
|
+
${placeholder ? `placeholder="${esc(placeholder)}"` : ""}
|
|
967
|
+
${min ? `min="${esc(min)}"` : ""} ${max ? `max="${esc(max)}"` : ""} ${step ? `step="${esc(step)}"` : ""}
|
|
907
968
|
/>
|
|
908
969
|
${this.hasAttr("clearable") ? '<button type="button" class="clear" part="clear" aria-label="Limpar" tabindex="-1">×</button></div>' : ""}
|
|
909
970
|
`);
|
|
@@ -1189,6 +1250,23 @@ const _FxMultiselect = class _FxMultiselect extends FxElement {
|
|
|
1189
1250
|
else this.selected.add(value);
|
|
1190
1251
|
this.commit();
|
|
1191
1252
|
}
|
|
1253
|
+
/** Navegação por teclado entre as opções do listbox (WCAG 2.1.1). */
|
|
1254
|
+
_navigateOptions(e) {
|
|
1255
|
+
e.preventDefault();
|
|
1256
|
+
if (!this.hasAttr("open")) {
|
|
1257
|
+
this.toggleAttribute("open");
|
|
1258
|
+
this.render();
|
|
1259
|
+
}
|
|
1260
|
+
const opts = Array.from(this.root.querySelectorAll(".opt"));
|
|
1261
|
+
if (!opts.length) return;
|
|
1262
|
+
const current = opts.indexOf(this.root.activeElement);
|
|
1263
|
+
let next = 0;
|
|
1264
|
+
if (e.key === "ArrowDown") next = current === -1 ? 0 : Math.min(current + 1, opts.length - 1);
|
|
1265
|
+
else if (e.key === "ArrowUp") next = current === -1 ? opts.length - 1 : Math.max(current - 1, 0);
|
|
1266
|
+
else if (e.key === "Home") next = 0;
|
|
1267
|
+
else if (e.key === "End") next = opts.length - 1;
|
|
1268
|
+
opts[next]?.focus();
|
|
1269
|
+
}
|
|
1192
1270
|
render() {
|
|
1193
1271
|
const wasOpen = this.hasAttr("open");
|
|
1194
1272
|
const search = this.root.querySelector(".search")?.value ?? "";
|
|
@@ -1203,10 +1281,10 @@ const _FxMultiselect = class _FxMultiselect extends FxElement {
|
|
|
1203
1281
|
<span class="trigger" part="trigger" role="button" tabindex="${this.disabled ? -1 : 0}" aria-haspopup="listbox" aria-expanded="${wasOpen}" aria-disabled="${this.disabled}">
|
|
1204
1282
|
<span class="lead">
|
|
1205
1283
|
${chips.length ? chips.map((c) => `
|
|
1206
|
-
<span class="chip" data-value="${c.value}">
|
|
1207
|
-
${c.label}
|
|
1208
|
-
<span class="chip__x" role="button" aria-label="Remover ${c.label}">×</span>
|
|
1209
|
-
</span>`).join("") : `<span class="placeholder">${placeholder}</span>`}
|
|
1284
|
+
<span class="chip" data-value="${esc(c.value)}">
|
|
1285
|
+
${esc(c.label)}
|
|
1286
|
+
<span class="chip__x" role="button" aria-label="Remover ${esc(c.label)}">×</span>
|
|
1287
|
+
</span>`).join("") : `<span class="placeholder">${esc(placeholder)}</span>`}
|
|
1210
1288
|
</span>
|
|
1211
1289
|
<span class="trigger-right">
|
|
1212
1290
|
${this.hasAttr("clearable") && chips.length ? '<button type="button" class="icon-btn clear" part="clear" aria-label="Limpar seleção">×</button>' : ""}
|
|
@@ -1218,14 +1296,14 @@ const _FxMultiselect = class _FxMultiselect extends FxElement {
|
|
|
1218
1296
|
<span class="count">${chips.length} selecionado${chips.length === 1 ? "" : "s"}</span>
|
|
1219
1297
|
${this.hasAttr("clearable") && chips.length ? '<button type="button" class="clear-all">Limpar tudo</button>' : ""}
|
|
1220
1298
|
</div>
|
|
1221
|
-
${this.hasAttr("searchable") ? `<input class="search" part="search" type="text" placeholder="${this.getAttr("search-placeholder", "Pesquisar…")}">` : ""}
|
|
1299
|
+
${this.hasAttr("searchable") ? `<input class="search" part="search" type="text" placeholder="${esc(this.getAttr("search-placeholder", "Pesquisar…"))}">` : ""}
|
|
1222
1300
|
<ul class="list">
|
|
1223
1301
|
${filtered.map((o) => `
|
|
1224
|
-
<li><button type="button" class="opt" role="option" data-value="${o.value}"
|
|
1302
|
+
<li><button type="button" class="opt" role="option" data-value="${esc(o.value)}"
|
|
1225
1303
|
aria-selected="${this.selected.has(o.value)}">
|
|
1226
|
-
<span class="box" aria-hidden="true"></span>${o.label}
|
|
1304
|
+
<span class="box" aria-hidden="true"></span>${esc(o.label)}
|
|
1227
1305
|
</button></li>`).join("")}
|
|
1228
|
-
${filtered.length === 0 ? `<li class="empty">${this.getAttr("no-results", "Nenhum resultado")}</li>` : ""}
|
|
1306
|
+
${filtered.length === 0 ? `<li class="empty">${esc(this.getAttr("no-results", "Nenhum resultado"))}</li>` : ""}
|
|
1229
1307
|
</ul>
|
|
1230
1308
|
</div>
|
|
1231
1309
|
`);
|
|
@@ -1264,6 +1342,18 @@ const _FxMultiselect = class _FxMultiselect extends FxElement {
|
|
|
1264
1342
|
if (e.key === "Enter" || e.key === " ") {
|
|
1265
1343
|
e.preventDefault();
|
|
1266
1344
|
trigger.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
if (e.key === "Escape") {
|
|
1348
|
+
if (this.hasAttr("open")) {
|
|
1349
|
+
this.removeAttribute("open");
|
|
1350
|
+
this.render();
|
|
1351
|
+
this.root.querySelector(".trigger")?.focus();
|
|
1352
|
+
}
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
if (e.key === "ArrowDown" || e.key === "ArrowUp" || e.key === "Home" || e.key === "End") {
|
|
1356
|
+
this._navigateOptions(e);
|
|
1267
1357
|
}
|
|
1268
1358
|
});
|
|
1269
1359
|
this.root.querySelector(".clear")?.addEventListener("click", (e) => {
|
|
@@ -2047,21 +2137,21 @@ const _FxDatepicker = class _FxDatepicker extends FxElement {
|
|
|
2047
2137
|
const hasValue = text.length > 0;
|
|
2048
2138
|
const showTime = this.hasAttr("show-time");
|
|
2049
2139
|
this.loadTimeFromValue();
|
|
2050
|
-
let calAttrs = `min="${this.getAttr("min")}" max="${this.getAttr("max")}"`;
|
|
2140
|
+
let calAttrs = `min="${esc(this.getAttr("min"))}" max="${esc(this.getAttr("max"))}"`;
|
|
2051
2141
|
if (this.mode === "range") {
|
|
2052
|
-
calAttrs += ` range start="${this.getAttr("start")}" end="${this.getAttr("end")}"`;
|
|
2142
|
+
calAttrs += ` range start="${esc(this.getAttr("start"))}" end="${esc(this.getAttr("end"))}"`;
|
|
2053
2143
|
} else if (this.mode === "multiple") {
|
|
2054
2144
|
const dates = this.values.map((v) => v.split("T")[0]).join(",");
|
|
2055
|
-
calAttrs += ` mode="multiple" values="${dates}"`;
|
|
2145
|
+
calAttrs += ` mode="multiple" values="${esc(dates)}"`;
|
|
2056
2146
|
} else {
|
|
2057
2147
|
const v = this.value.split("T")[0] ?? "";
|
|
2058
|
-
calAttrs += ` value="${v}"`;
|
|
2148
|
+
calAttrs += ` value="${esc(v)}"`;
|
|
2059
2149
|
}
|
|
2060
2150
|
this.setTemplate(`
|
|
2061
2151
|
<div class="field" part="field">
|
|
2062
2152
|
<input class="display" part="display" type="text" ${this.hasAttr("free-text") && !disabled ? "" : "readonly"}
|
|
2063
|
-
placeholder="${this.getAttr("placeholder", "dd/mm/aaaa")}"
|
|
2064
|
-
value="${text}" ${disabled ? "disabled" : ""}>
|
|
2153
|
+
placeholder="${esc(this.getAttr("placeholder", "dd/mm/aaaa"))}"
|
|
2154
|
+
value="${esc(text)}" ${disabled ? "disabled" : ""}>
|
|
2065
2155
|
<button type="button" class="clear" part="clear" aria-label="Limpar"
|
|
2066
2156
|
${clearable && hasValue ? "" : "hidden"}>
|
|
2067
2157
|
×
|
|
@@ -2564,7 +2654,7 @@ const _FxRadio = class _FxRadio extends FxElement {
|
|
|
2564
2654
|
if (this.disabled || this.checked) return;
|
|
2565
2655
|
const name = this.getAttr("name");
|
|
2566
2656
|
if (name) {
|
|
2567
|
-
document.querySelectorAll(`fx-radio[name="${name}"]`).forEach((r) => {
|
|
2657
|
+
document.querySelectorAll(`fx-radio[name="${CSS.escape(name)}"]`).forEach((r) => {
|
|
2568
2658
|
r.checked = false;
|
|
2569
2659
|
});
|
|
2570
2660
|
}
|
|
@@ -2640,17 +2730,465 @@ function defineFxRadio() {
|
|
|
2640
2730
|
return defineElement("fx-radio", FxRadio);
|
|
2641
2731
|
}
|
|
2642
2732
|
defineFxRadio();
|
|
2733
|
+
const LOCALE = "pt-BR";
|
|
2734
|
+
const PIPES = {
|
|
2735
|
+
/** Formata como moeda brasileira (R$). */
|
|
2736
|
+
currency: (v) => {
|
|
2737
|
+
if (v == null || v === "") return "";
|
|
2738
|
+
try {
|
|
2739
|
+
return new Intl.NumberFormat(LOCALE, {
|
|
2740
|
+
style: "currency",
|
|
2741
|
+
currency: "BRL"
|
|
2742
|
+
}).format(Number(v));
|
|
2743
|
+
} catch {
|
|
2744
|
+
return String(v);
|
|
2745
|
+
}
|
|
2746
|
+
},
|
|
2747
|
+
/** Formata como data. Estilos: short | medium | long | full (default medium). */
|
|
2748
|
+
date: (v, arg) => {
|
|
2749
|
+
const d = toDate(v);
|
|
2750
|
+
if (!d) return String(v ?? "");
|
|
2751
|
+
const styles = {
|
|
2752
|
+
short: { dateStyle: "short" },
|
|
2753
|
+
medium: { dateStyle: "medium" },
|
|
2754
|
+
long: { dateStyle: "long" },
|
|
2755
|
+
full: { dateStyle: "full" }
|
|
2756
|
+
};
|
|
2757
|
+
const opts = arg && styles[arg] ? styles[arg] : styles.medium;
|
|
2758
|
+
try {
|
|
2759
|
+
return new Intl.DateTimeFormat(LOCALE, opts).format(d);
|
|
2760
|
+
} catch {
|
|
2761
|
+
return d.toLocaleDateString(LOCALE);
|
|
2762
|
+
}
|
|
2763
|
+
},
|
|
2764
|
+
/** Formata como data + hora (medium/short). */
|
|
2765
|
+
dateTime: (v) => {
|
|
2766
|
+
const d = toDate(v);
|
|
2767
|
+
if (!d) return String(v ?? "");
|
|
2768
|
+
try {
|
|
2769
|
+
return new Intl.DateTimeFormat(LOCALE, {
|
|
2770
|
+
dateStyle: "medium",
|
|
2771
|
+
timeStyle: "short"
|
|
2772
|
+
}).format(d);
|
|
2773
|
+
} catch {
|
|
2774
|
+
return d.toLocaleString(LOCALE);
|
|
2775
|
+
}
|
|
2776
|
+
},
|
|
2777
|
+
/** Formata como número (pt-BR). Opcional: `number: 2` (casas decimais). */
|
|
2778
|
+
number: (v, arg) => {
|
|
2779
|
+
if (v == null || v === "") return "";
|
|
2780
|
+
try {
|
|
2781
|
+
const opt = {};
|
|
2782
|
+
if (arg !== void 0) {
|
|
2783
|
+
const dec = Number(arg);
|
|
2784
|
+
opt.minimumFractionDigits = dec;
|
|
2785
|
+
opt.maximumFractionDigits = dec;
|
|
2786
|
+
}
|
|
2787
|
+
return new Intl.NumberFormat(LOCALE, opt).format(Number(v));
|
|
2788
|
+
} catch {
|
|
2789
|
+
return String(v);
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
};
|
|
2793
|
+
function toDate(v) {
|
|
2794
|
+
if (v == null || v === "") return null;
|
|
2795
|
+
if (v instanceof Date) return isNaN(v.getTime()) ? null : v;
|
|
2796
|
+
if (typeof v === "number") {
|
|
2797
|
+
const d = new Date(v);
|
|
2798
|
+
return isNaN(d.getTime()) ? null : d;
|
|
2799
|
+
}
|
|
2800
|
+
if (typeof v === "string") {
|
|
2801
|
+
const d = new Date(v);
|
|
2802
|
+
return isNaN(d.getTime()) ? null : d;
|
|
2803
|
+
}
|
|
2804
|
+
return null;
|
|
2805
|
+
}
|
|
2806
|
+
const IDENT_START = (ch) => /[a-z_$]/i.test(ch);
|
|
2807
|
+
const IDENT_PART = (ch) => /[a-z0-9_$]/i.test(ch);
|
|
2808
|
+
function tokenize(expr) {
|
|
2809
|
+
const tokens = [];
|
|
2810
|
+
let i = 0;
|
|
2811
|
+
while (i < expr.length) {
|
|
2812
|
+
const ch = expr[i];
|
|
2813
|
+
if (/\s/.test(ch)) {
|
|
2814
|
+
i++;
|
|
2815
|
+
continue;
|
|
2816
|
+
}
|
|
2817
|
+
if (ch === "'" || ch === '"') {
|
|
2818
|
+
const quote = ch;
|
|
2819
|
+
let str = "";
|
|
2820
|
+
i++;
|
|
2821
|
+
while (i < expr.length && expr[i] !== quote) {
|
|
2822
|
+
if (expr[i] === "\\" && i + 1 < expr.length) {
|
|
2823
|
+
str += expr[i + 1];
|
|
2824
|
+
i += 2;
|
|
2825
|
+
} else {
|
|
2826
|
+
str += expr[i];
|
|
2827
|
+
i++;
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
i++;
|
|
2831
|
+
tokens.push({ type: "str", value: str });
|
|
2832
|
+
continue;
|
|
2833
|
+
}
|
|
2834
|
+
if (ch >= "0" && ch <= "9") {
|
|
2835
|
+
let numStr = "";
|
|
2836
|
+
while (i < expr.length && (expr[i] >= "0" && expr[i] <= "9" || expr[i] === ".")) {
|
|
2837
|
+
numStr += expr[i];
|
|
2838
|
+
i++;
|
|
2839
|
+
}
|
|
2840
|
+
tokens.push({ type: "num", value: numStr });
|
|
2841
|
+
continue;
|
|
2842
|
+
}
|
|
2843
|
+
if (IDENT_START(ch)) {
|
|
2844
|
+
let ident = "";
|
|
2845
|
+
while (i < expr.length && IDENT_PART(expr[i])) {
|
|
2846
|
+
ident += expr[i];
|
|
2847
|
+
i++;
|
|
2848
|
+
}
|
|
2849
|
+
if (ident === "true" || ident === "false" || ident === "null") {
|
|
2850
|
+
tokens.push({ type: "kw", value: ident });
|
|
2851
|
+
} else {
|
|
2852
|
+
tokens.push({ type: "id", value: ident });
|
|
2853
|
+
}
|
|
2854
|
+
continue;
|
|
2855
|
+
}
|
|
2856
|
+
const two = expr.slice(i, i + 2);
|
|
2857
|
+
if (two === "==" || two === "!=" || two === ">=" || two === "<=") {
|
|
2858
|
+
tokens.push({ type: "cmp", value: two });
|
|
2859
|
+
i += 2;
|
|
2860
|
+
continue;
|
|
2861
|
+
}
|
|
2862
|
+
if (ch === "+") {
|
|
2863
|
+
tokens.push({ type: "op", value: "+" });
|
|
2864
|
+
i++;
|
|
2865
|
+
continue;
|
|
2866
|
+
}
|
|
2867
|
+
if (ch === "-") {
|
|
2868
|
+
tokens.push({ type: "op", value: "-" });
|
|
2869
|
+
i++;
|
|
2870
|
+
continue;
|
|
2871
|
+
}
|
|
2872
|
+
if (ch === "*") {
|
|
2873
|
+
tokens.push({ type: "op", value: "*" });
|
|
2874
|
+
i++;
|
|
2875
|
+
continue;
|
|
2876
|
+
}
|
|
2877
|
+
if (ch === "/") {
|
|
2878
|
+
tokens.push({ type: "op", value: "/" });
|
|
2879
|
+
i++;
|
|
2880
|
+
continue;
|
|
2881
|
+
}
|
|
2882
|
+
if (ch === ">") {
|
|
2883
|
+
tokens.push({ type: "cmp", value: ">" });
|
|
2884
|
+
i++;
|
|
2885
|
+
continue;
|
|
2886
|
+
}
|
|
2887
|
+
if (ch === "<") {
|
|
2888
|
+
tokens.push({ type: "cmp", value: "<" });
|
|
2889
|
+
i++;
|
|
2890
|
+
continue;
|
|
2891
|
+
}
|
|
2892
|
+
if (ch === "!") {
|
|
2893
|
+
tokens.push({ type: "op", value: "!" });
|
|
2894
|
+
i++;
|
|
2895
|
+
continue;
|
|
2896
|
+
}
|
|
2897
|
+
if (ch === "?") {
|
|
2898
|
+
tokens.push({ type: "qm", value: "?" });
|
|
2899
|
+
i++;
|
|
2900
|
+
continue;
|
|
2901
|
+
}
|
|
2902
|
+
if (ch === ":") {
|
|
2903
|
+
tokens.push({ type: "colon", value: ":" });
|
|
2904
|
+
i++;
|
|
2905
|
+
continue;
|
|
2906
|
+
}
|
|
2907
|
+
if (ch === "|") {
|
|
2908
|
+
tokens.push({ type: "pipe", value: "|" });
|
|
2909
|
+
i++;
|
|
2910
|
+
continue;
|
|
2911
|
+
}
|
|
2912
|
+
if (ch === "(") {
|
|
2913
|
+
tokens.push({ type: "lparen", value: "(" });
|
|
2914
|
+
i++;
|
|
2915
|
+
continue;
|
|
2916
|
+
}
|
|
2917
|
+
if (ch === ")") {
|
|
2918
|
+
tokens.push({ type: "rparen", value: ")" });
|
|
2919
|
+
i++;
|
|
2920
|
+
continue;
|
|
2921
|
+
}
|
|
2922
|
+
if (ch === ".") {
|
|
2923
|
+
tokens.push({ type: "dot", value: "." });
|
|
2924
|
+
i++;
|
|
2925
|
+
continue;
|
|
2926
|
+
}
|
|
2927
|
+
i++;
|
|
2928
|
+
}
|
|
2929
|
+
return tokens;
|
|
2930
|
+
}
|
|
2931
|
+
class Parser {
|
|
2932
|
+
constructor(tokens, ctx) {
|
|
2933
|
+
this.tokens = tokens;
|
|
2934
|
+
this.ctx = ctx;
|
|
2935
|
+
this.pos = 0;
|
|
2936
|
+
}
|
|
2937
|
+
peek() {
|
|
2938
|
+
return this.pos < this.tokens.length ? this.tokens[this.pos] : null;
|
|
2939
|
+
}
|
|
2940
|
+
next() {
|
|
2941
|
+
return this.pos < this.tokens.length ? this.tokens[this.pos++] : null;
|
|
2942
|
+
}
|
|
2943
|
+
matchType(type) {
|
|
2944
|
+
if (this.peek()?.type === type) return this.next();
|
|
2945
|
+
return null;
|
|
2946
|
+
}
|
|
2947
|
+
parse() {
|
|
2948
|
+
return this.parsePipe();
|
|
2949
|
+
}
|
|
2950
|
+
/* pipeExpr := ternary ('|' IDENT (':' pipeArg)? )* */
|
|
2951
|
+
parsePipe() {
|
|
2952
|
+
let val = this.parseTernary();
|
|
2953
|
+
while (this.peek()?.type === "pipe") {
|
|
2954
|
+
this.next();
|
|
2955
|
+
const nameTok = this.matchType("id");
|
|
2956
|
+
if (!nameTok) break;
|
|
2957
|
+
let arg;
|
|
2958
|
+
if (this.peek()?.type === "colon") {
|
|
2959
|
+
this.next();
|
|
2960
|
+
const argTok = this.peek();
|
|
2961
|
+
if (argTok && (argTok.type === "str" || argTok.type === "num" || argTok.type === "id")) {
|
|
2962
|
+
arg = argTok.type === "str" ? argTok.value : String(argTok.value);
|
|
2963
|
+
this.next();
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
val = this.applyPipe(nameTok.value, arg, val);
|
|
2967
|
+
}
|
|
2968
|
+
return val;
|
|
2969
|
+
}
|
|
2970
|
+
/* ternary := comparison ('?' pipeExpr ':' pipeExpr)? */
|
|
2971
|
+
parseTernary() {
|
|
2972
|
+
const cond = this.parseComparison();
|
|
2973
|
+
if (this.peek()?.type === "qm") {
|
|
2974
|
+
this.next();
|
|
2975
|
+
const trueVal = this.parsePipe();
|
|
2976
|
+
if (this.peek()?.type === "colon") {
|
|
2977
|
+
this.next();
|
|
2978
|
+
const falseVal = this.parsePipe();
|
|
2979
|
+
return cond ? trueVal : falseVal;
|
|
2980
|
+
}
|
|
2981
|
+
return trueVal;
|
|
2982
|
+
}
|
|
2983
|
+
return cond;
|
|
2984
|
+
}
|
|
2985
|
+
/* comparison := additive (COMP_OP additive)? */
|
|
2986
|
+
parseComparison() {
|
|
2987
|
+
const left = this.parseAdditive();
|
|
2988
|
+
const tok = this.peek();
|
|
2989
|
+
if (tok?.type === "cmp") {
|
|
2990
|
+
this.next();
|
|
2991
|
+
const right = this.parseAdditive();
|
|
2992
|
+
return this.compare(left, tok.value, right);
|
|
2993
|
+
}
|
|
2994
|
+
return left;
|
|
2995
|
+
}
|
|
2996
|
+
compare(left, op, right) {
|
|
2997
|
+
const l = toComparable(left);
|
|
2998
|
+
const r = toComparable(right);
|
|
2999
|
+
switch (op) {
|
|
3000
|
+
case "==":
|
|
3001
|
+
return l == r;
|
|
3002
|
+
case "!=":
|
|
3003
|
+
return l != r;
|
|
3004
|
+
case ">":
|
|
3005
|
+
return l > r;
|
|
3006
|
+
case "<":
|
|
3007
|
+
return l < r;
|
|
3008
|
+
case ">=":
|
|
3009
|
+
return l >= r;
|
|
3010
|
+
case "<=":
|
|
3011
|
+
return l <= r;
|
|
3012
|
+
}
|
|
3013
|
+
return false;
|
|
3014
|
+
}
|
|
3015
|
+
/* additive := multiplicative (('+' | '-') multiplicative)* */
|
|
3016
|
+
parseAdditive() {
|
|
3017
|
+
let left = this.parseMultiplicative();
|
|
3018
|
+
for (; ; ) {
|
|
3019
|
+
const tok = this.peek();
|
|
3020
|
+
if (tok?.type === "op" && (tok.value === "+" || tok.value === "-")) {
|
|
3021
|
+
this.next();
|
|
3022
|
+
const right = this.parseMultiplicative();
|
|
3023
|
+
left = tok.value === "+" ? this.add(left, right) : this.subtract(left, right);
|
|
3024
|
+
} else break;
|
|
3025
|
+
}
|
|
3026
|
+
return left;
|
|
3027
|
+
}
|
|
3028
|
+
add(l, r) {
|
|
3029
|
+
if (typeof l === "string" || typeof r === "string") {
|
|
3030
|
+
return String(l ?? "") + String(r ?? "");
|
|
3031
|
+
}
|
|
3032
|
+
return Number(l) + Number(r);
|
|
3033
|
+
}
|
|
3034
|
+
subtract(l, r) {
|
|
3035
|
+
return Number(l) - Number(r);
|
|
3036
|
+
}
|
|
3037
|
+
/* multiplicative := unary (('*' | '/') unary)* */
|
|
3038
|
+
parseMultiplicative() {
|
|
3039
|
+
let left = this.parseUnary();
|
|
3040
|
+
for (; ; ) {
|
|
3041
|
+
const tok = this.peek();
|
|
3042
|
+
if (tok?.type === "op" && (tok.value === "*" || tok.value === "/")) {
|
|
3043
|
+
this.next();
|
|
3044
|
+
const right = this.parseUnary();
|
|
3045
|
+
left = tok.value === "*" ? Number(left) * Number(right) : Number(left) / Number(right);
|
|
3046
|
+
} else break;
|
|
3047
|
+
}
|
|
3048
|
+
return left;
|
|
3049
|
+
}
|
|
3050
|
+
/* unary := ('+' | '-' | '!') unary | primary */
|
|
3051
|
+
parseUnary() {
|
|
3052
|
+
const tok = this.peek();
|
|
3053
|
+
if (tok?.type === "op" && (tok.value === "+" || tok.value === "-")) {
|
|
3054
|
+
this.next();
|
|
3055
|
+
const val = Number(this.parseUnary());
|
|
3056
|
+
return tok.value === "-" ? -val : val;
|
|
3057
|
+
}
|
|
3058
|
+
if (tok?.type === "op" && tok.value === "!") {
|
|
3059
|
+
this.next();
|
|
3060
|
+
return !this.parseUnary();
|
|
3061
|
+
}
|
|
3062
|
+
return this.parsePrimary();
|
|
3063
|
+
}
|
|
3064
|
+
/* primary := STRING | NUMBER | kw | id ( '.' id )* | '(' pipeExpr ')' */
|
|
3065
|
+
parsePrimary() {
|
|
3066
|
+
const tok = this.peek();
|
|
3067
|
+
if (!tok) return void 0;
|
|
3068
|
+
if (tok.type === "lparen") {
|
|
3069
|
+
this.next();
|
|
3070
|
+
const val = this.parsePipe();
|
|
3071
|
+
this.matchType("rparen");
|
|
3072
|
+
return val;
|
|
3073
|
+
}
|
|
3074
|
+
if (tok.type === "str") {
|
|
3075
|
+
this.next();
|
|
3076
|
+
return tok.value;
|
|
3077
|
+
}
|
|
3078
|
+
if (tok.type === "num") {
|
|
3079
|
+
this.next();
|
|
3080
|
+
return Number(tok.value);
|
|
3081
|
+
}
|
|
3082
|
+
if (tok.type === "kw") {
|
|
3083
|
+
this.next();
|
|
3084
|
+
return tok.value === "true" ? true : tok.value === "false" ? false : null;
|
|
3085
|
+
}
|
|
3086
|
+
if (tok.type === "id") {
|
|
3087
|
+
this.next();
|
|
3088
|
+
let result = this.resolveId(tok.value);
|
|
3089
|
+
while (this.peek()?.type === "dot") {
|
|
3090
|
+
this.next();
|
|
3091
|
+
const propTok = this.peek();
|
|
3092
|
+
if (propTok?.type === "id") {
|
|
3093
|
+
this.next();
|
|
3094
|
+
result = result == null ? void 0 : result[propTok.value];
|
|
3095
|
+
} else break;
|
|
3096
|
+
}
|
|
3097
|
+
return result;
|
|
3098
|
+
}
|
|
3099
|
+
this.next();
|
|
3100
|
+
return void 0;
|
|
3101
|
+
}
|
|
3102
|
+
resolveId(name) {
|
|
3103
|
+
switch (name) {
|
|
3104
|
+
case "value":
|
|
3105
|
+
return this.ctx.value;
|
|
3106
|
+
case "row":
|
|
3107
|
+
return this.ctx.row;
|
|
3108
|
+
case "true":
|
|
3109
|
+
return true;
|
|
3110
|
+
case "false":
|
|
3111
|
+
return false;
|
|
3112
|
+
case "null":
|
|
3113
|
+
return null;
|
|
3114
|
+
default:
|
|
3115
|
+
return this.ctx.row[name];
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
applyPipe(name, arg, input) {
|
|
3119
|
+
const pipe = PIPES[name];
|
|
3120
|
+
if (!pipe) return formatDefault(input);
|
|
3121
|
+
try {
|
|
3122
|
+
return pipe(input, arg);
|
|
3123
|
+
} catch {
|
|
3124
|
+
return formatDefault(input);
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
function toComparable(v) {
|
|
3129
|
+
if (typeof v === "number") return v;
|
|
3130
|
+
if (typeof v === "boolean") return v ? 1 : 0;
|
|
3131
|
+
return String(v ?? "");
|
|
3132
|
+
}
|
|
3133
|
+
function formatDefault(v) {
|
|
3134
|
+
if (v == null) return "";
|
|
3135
|
+
if (typeof v === "object") return "[object Object]";
|
|
3136
|
+
return String(v);
|
|
3137
|
+
}
|
|
3138
|
+
function evaluate(expr, ctx) {
|
|
3139
|
+
try {
|
|
3140
|
+
const tokens = tokenize(expr);
|
|
3141
|
+
const parser = new Parser(tokens, ctx);
|
|
3142
|
+
const result = parser.parse();
|
|
3143
|
+
return formatDefault(result);
|
|
3144
|
+
} catch {
|
|
3145
|
+
return "";
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3148
|
+
function renderCell(template, row, field) {
|
|
3149
|
+
const ctx = { value: row[field], row };
|
|
3150
|
+
return template.replace(/\{\{([^}]+)\}\}/g, (_, expr) => esc(evaluate(decodeEntities(expr.trim()), ctx)));
|
|
3151
|
+
}
|
|
3152
|
+
function decodeEntities(str) {
|
|
3153
|
+
return str.replace(/&/g, "&").replace(/>/g, ">").replace(/</g, "<").replace(/"/g, '"').replace(/'/g, "'");
|
|
3154
|
+
}
|
|
2643
3155
|
const _FxTable = class _FxTable extends FxElement {
|
|
2644
3156
|
constructor() {
|
|
2645
3157
|
super(...arguments);
|
|
2646
|
-
this.
|
|
3158
|
+
this._data = [];
|
|
2647
3159
|
this.page = 0;
|
|
2648
3160
|
this.sortField = "";
|
|
2649
3161
|
this.sortDir = 1;
|
|
2650
3162
|
this.filters = {};
|
|
3163
|
+
this.globalSearch = "";
|
|
2651
3164
|
}
|
|
2652
3165
|
static get observedAttributes() {
|
|
2653
|
-
return [
|
|
3166
|
+
return [
|
|
3167
|
+
"pagination",
|
|
3168
|
+
"rows",
|
|
3169
|
+
"rows-options",
|
|
3170
|
+
"striped",
|
|
3171
|
+
"empty-message",
|
|
3172
|
+
"pagination-position",
|
|
3173
|
+
"lazy",
|
|
3174
|
+
"total",
|
|
3175
|
+
"loading",
|
|
3176
|
+
"loading-message"
|
|
3177
|
+
];
|
|
3178
|
+
}
|
|
3179
|
+
get data() {
|
|
3180
|
+
return this._data;
|
|
3181
|
+
}
|
|
3182
|
+
set data(value) {
|
|
3183
|
+
this._data = Array.isArray(value) ? value : [];
|
|
3184
|
+
this.render();
|
|
3185
|
+
}
|
|
3186
|
+
get total() {
|
|
3187
|
+
return this._total ?? this._data.length;
|
|
3188
|
+
}
|
|
3189
|
+
set total(value) {
|
|
3190
|
+
this._total = value;
|
|
3191
|
+
this.setAttribute("total", String(value));
|
|
2654
3192
|
}
|
|
2655
3193
|
get rowsPerPage() {
|
|
2656
3194
|
const r = Number(this.getAttr("rows", "10"));
|
|
@@ -2659,57 +3197,101 @@ const _FxTable = class _FxTable extends FxElement {
|
|
|
2659
3197
|
set rowsPerPage(value) {
|
|
2660
3198
|
this.setAttribute("rows", String(value));
|
|
2661
3199
|
}
|
|
2662
|
-
|
|
3200
|
+
get lazy() {
|
|
3201
|
+
return this.hasAttr("lazy");
|
|
3202
|
+
}
|
|
2663
3203
|
get columns() {
|
|
2664
|
-
return [...this.querySelectorAll("fx-column")].map((c) =>
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
3204
|
+
return [...this.querySelectorAll("fx-column")].map((c) => {
|
|
3205
|
+
const tpl = c.querySelector("template");
|
|
3206
|
+
const direct = tpl ? void 0 : c.innerHTML.trim();
|
|
3207
|
+
const align = c.getAttribute("align") ?? "left";
|
|
3208
|
+
return {
|
|
3209
|
+
field: c.getAttribute("field") ?? "",
|
|
3210
|
+
header: c.getAttribute("header") ?? c.getAttribute("field") ?? "",
|
|
3211
|
+
sortable: c.hasAttribute("sortable"),
|
|
3212
|
+
filterable: c.hasAttribute("filterable"),
|
|
3213
|
+
align: ["left", "center", "right", "justify", "start", "end"].includes(align) ? align : "left",
|
|
3214
|
+
template: tpl ?? void 0,
|
|
3215
|
+
directTemplate: direct || void 0
|
|
3216
|
+
};
|
|
3217
|
+
});
|
|
3218
|
+
}
|
|
3219
|
+
get toolbarTemplate() {
|
|
3220
|
+
return this.querySelector(
|
|
3221
|
+
'template[slot="toolbar"]'
|
|
3222
|
+
);
|
|
2672
3223
|
}
|
|
2673
3224
|
connectedCallback() {
|
|
3225
|
+
this._parseDataAttribute();
|
|
2674
3226
|
super.connectedCallback();
|
|
2675
|
-
|
|
3227
|
+
this._columnObserver = new MutationObserver(() => this.render());
|
|
3228
|
+
this._columnObserver.observe(this, { childList: true, subtree: true });
|
|
3229
|
+
}
|
|
3230
|
+
disconnectedCallback() {
|
|
3231
|
+
this._columnObserver?.disconnect();
|
|
3232
|
+
super.disconnectedCallback();
|
|
3233
|
+
}
|
|
3234
|
+
_parseDataAttribute() {
|
|
3235
|
+
if (!this._data.length) {
|
|
2676
3236
|
try {
|
|
2677
3237
|
const json = this.getAttr("data");
|
|
2678
|
-
if (json) this.
|
|
3238
|
+
if (json) this._data = JSON.parse(json);
|
|
2679
3239
|
} catch {
|
|
2680
3240
|
}
|
|
2681
3241
|
}
|
|
2682
|
-
|
|
3242
|
+
const totalAttr = this.getAttr("total");
|
|
3243
|
+
if (totalAttr) {
|
|
3244
|
+
const n = Number(totalAttr);
|
|
3245
|
+
if (!isNaN(n)) this._total = n;
|
|
3246
|
+
}
|
|
2683
3247
|
}
|
|
2684
|
-
/** Pipeline: filtra → ordena → pagina. */
|
|
2685
3248
|
computeRows() {
|
|
2686
|
-
let out = [...this.
|
|
3249
|
+
let out = [...this._data];
|
|
2687
3250
|
for (const [field, term] of Object.entries(this.filters)) {
|
|
2688
3251
|
if (!term) continue;
|
|
2689
3252
|
const t = term.toLowerCase();
|
|
2690
|
-
out = out.filter(
|
|
3253
|
+
out = out.filter(
|
|
3254
|
+
(r) => String(r[field] ?? "").toLowerCase().includes(t)
|
|
3255
|
+
);
|
|
3256
|
+
}
|
|
3257
|
+
if (this.globalSearch) {
|
|
3258
|
+
const t = this.globalSearch.toLowerCase();
|
|
3259
|
+
const searchFields = this._getSearchFields();
|
|
3260
|
+
out = out.filter((r) => {
|
|
3261
|
+
const fields = searchFields.length ? searchFields : Object.keys(r);
|
|
3262
|
+
return fields.some(
|
|
3263
|
+
(f) => String(r[f] ?? "").toLowerCase().includes(t)
|
|
3264
|
+
);
|
|
3265
|
+
});
|
|
2691
3266
|
}
|
|
2692
|
-
const total = out.length;
|
|
3267
|
+
const total = this.lazy ? this.total : out.length;
|
|
2693
3268
|
if (this.sortField) {
|
|
2694
3269
|
out.sort((a, b) => {
|
|
2695
3270
|
const av = a[this.sortField], bv = b[this.sortField];
|
|
2696
|
-
if (typeof av === "number" && typeof bv === "number")
|
|
3271
|
+
if (typeof av === "number" && typeof bv === "number")
|
|
3272
|
+
return (av - bv) * this.sortDir;
|
|
2697
3273
|
return String(av ?? "").localeCompare(String(bv ?? ""), "pt-BR") * this.sortDir;
|
|
2698
3274
|
});
|
|
2699
3275
|
}
|
|
2700
|
-
if (this.hasAttr("pagination")) {
|
|
3276
|
+
if (this.hasAttr("pagination") && !this.lazy) {
|
|
2701
3277
|
const start = this.page * this.rowsPerPage;
|
|
2702
3278
|
out = out.slice(start, start + this.rowsPerPage);
|
|
2703
3279
|
}
|
|
2704
3280
|
return { rows: out, total };
|
|
2705
3281
|
}
|
|
2706
|
-
|
|
3282
|
+
_getSearchFields() {
|
|
3283
|
+
const input = this.root.querySelector(
|
|
3284
|
+
"[data-search-fields]"
|
|
3285
|
+
);
|
|
3286
|
+
if (!input) return [];
|
|
3287
|
+
const attr = input.getAttribute("data-search-fields");
|
|
3288
|
+
if (attr === null) return [];
|
|
3289
|
+
return attr.split(",").map((s) => s.trim()).filter(Boolean);
|
|
3290
|
+
}
|
|
2707
3291
|
cellHtml(col, row) {
|
|
2708
|
-
const
|
|
2709
|
-
if (col.
|
|
2710
|
-
|
|
2711
|
-
}
|
|
2712
|
-
return String(value ?? "");
|
|
3292
|
+
const template = col.template?.innerHTML ?? col.directTemplate;
|
|
3293
|
+
if (template) return renderCell(template, row, col.field);
|
|
3294
|
+
return esc(String(row[col.field] ?? ""));
|
|
2713
3295
|
}
|
|
2714
3296
|
render() {
|
|
2715
3297
|
const cols = this.columns.filter((c) => c.field);
|
|
@@ -2717,12 +3299,18 @@ const _FxTable = class _FxTable extends FxElement {
|
|
|
2717
3299
|
const paginated = this.hasAttr("pagination");
|
|
2718
3300
|
const pages = Math.max(1, Math.ceil(total / this.rowsPerPage));
|
|
2719
3301
|
if (this.page >= pages) this.page = pages - 1;
|
|
3302
|
+
const toolbar = this.toolbarTemplate;
|
|
3303
|
+
const toolbarHtml = toolbar ? `<div class="toolbar" part="toolbar">${toolbar.innerHTML}</div>` : "";
|
|
2720
3304
|
const headHtml = cols.map((c) => {
|
|
2721
|
-
const
|
|
2722
|
-
|
|
3305
|
+
const isSorted = this.sortField === c.field;
|
|
3306
|
+
const ind = isSorted ? this.sortDir === 1 ? "▲" : "▼" : c.sortable ? "⇅" : "";
|
|
3307
|
+
const activeClass = isSorted ? "active" : "";
|
|
3308
|
+
return `<th part="header" class="${c.sortable ? "sortable" : ""}" style="text-align:${c.align}" data-field="${esc(c.field)}">${esc(c.header)}${ind ? `<span class="sort-ind ${activeClass}">${ind}</span>` : ""}</th>`;
|
|
2723
3309
|
}).join("");
|
|
2724
|
-
const filterRow = cols.some((c) => c.filterable) ? `<tr class="filter-row">${cols.map((c) => `<th style="text-align:${c.align}">${c.filterable ? `<input class="filter" data-field="${c.field}" placeholder="Filtrar…" value="${this.filters[c.field] ?? ""}">` : ""}</th>`).join("")}</tr>` : "";
|
|
2725
|
-
const bodyHtml = rows.length ? rows.map(
|
|
3310
|
+
const filterRow = cols.some((c) => c.filterable) ? `<tr class="filter-row">${cols.map((c) => `<th style="text-align:${c.align}">${c.filterable ? `<input class="filter" data-field="${esc(c.field)}" placeholder="${esc("Filtrar…")}" value="${esc(this.filters[c.field] ?? "")}">` : ""}</th>`).join("")}</tr>` : "";
|
|
3311
|
+
const bodyHtml = rows.length ? rows.map(
|
|
3312
|
+
(row, i) => `<tr part="row" data-index="${i}">${cols.map((c) => `<td part="cell" style="text-align:${c.align}">${this.cellHtml(c, row)}</td>`).join("")}</tr>`
|
|
3313
|
+
).join("") : `<tr><td class="empty" colspan="${cols.length || 1}">${esc(this.getAttr("empty-message", "Nenhum registro encontrado"))}</td></tr>`;
|
|
2726
3314
|
const pagerHtml = !paginated ? "" : `
|
|
2727
3315
|
<div class="pager" part="pager">
|
|
2728
3316
|
<span class="info">Página ${this.page + 1} de ${pages} · ${total} registros</span>
|
|
@@ -2731,43 +3319,69 @@ const _FxTable = class _FxTable extends FxElement {
|
|
|
2731
3319
|
${Array.from({ length: pages }, (_, p) => `<button type="button" class="pg-btn" data-pg="${p}" aria-current="${p === this.page}">${p + 1}</button>`).join("")}
|
|
2732
3320
|
<button type="button" class="pg-btn" data-pg="next" ${this.page >= pages - 1 ? "disabled" : ""}>›</button>
|
|
2733
3321
|
<button type="button" class="pg-btn" data-pg="last" ${this.page >= pages - 1 ? "disabled" : ""}>»</button>
|
|
2734
|
-
<label class="info"
|
|
2735
|
-
<select class="rows-sel"
|
|
3322
|
+
<label class="info">${esc("Por página:")}
|
|
3323
|
+
<fx-select class="rows-sel" size="sm" value="${this.rowsPerPage}" aria-label="Itens por página">${this.getAttr(
|
|
3324
|
+
"rows-options",
|
|
3325
|
+
"5,10,20,50"
|
|
3326
|
+
).split(",").map((n) => n.trim()).map((n) => `<option value="${esc(n)}">${esc(n)}</option>`).join("")}</fx-select>
|
|
2736
3327
|
</label>
|
|
2737
3328
|
</div>`;
|
|
3329
|
+
const loading = this.hasAttr("loading");
|
|
3330
|
+
const loadingHtml = loading ? `<div class="loading-overlay" part="loading-overlay"><div class="tbl-spinner" part="spinner"></div><span class="loading-text">${esc(this.getAttr("loading-message", "Carregando…"))}</span></div>` : "";
|
|
2738
3331
|
this.setTemplate(`
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
<
|
|
2743
|
-
|
|
3332
|
+
${toolbarHtml}
|
|
3333
|
+
<div class="table-wrap">
|
|
3334
|
+
<div class="scroll">
|
|
3335
|
+
<table part="table">
|
|
3336
|
+
<thead><tr>${headHtml}</tr>${filterRow}</thead>
|
|
3337
|
+
<tbody>${bodyHtml}</tbody>
|
|
3338
|
+
</table>
|
|
3339
|
+
</div>
|
|
3340
|
+
${loadingHtml}
|
|
2744
3341
|
</div>
|
|
2745
3342
|
${pagerHtml}
|
|
2746
3343
|
`);
|
|
2747
3344
|
this.root.querySelectorAll("th.sortable").forEach((th) => {
|
|
2748
3345
|
th.addEventListener("click", () => {
|
|
2749
3346
|
const f = th.dataset.field;
|
|
2750
|
-
if (this.sortField === f)
|
|
3347
|
+
if (this.sortField === f)
|
|
3348
|
+
this.sortDir = this.sortDir === 1 ? -1 : 1;
|
|
2751
3349
|
else {
|
|
2752
3350
|
this.sortField = f;
|
|
2753
3351
|
this.sortDir = 1;
|
|
2754
3352
|
}
|
|
2755
3353
|
this.render();
|
|
2756
|
-
this.dispatchEvent(
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
3354
|
+
this.dispatchEvent(
|
|
3355
|
+
new CustomEvent("sort-change", {
|
|
3356
|
+
bubbles: true,
|
|
3357
|
+
composed: true,
|
|
3358
|
+
detail: {
|
|
3359
|
+
field: this.sortField,
|
|
3360
|
+
direction: this.sortDir === 1 ? "asc" : "desc",
|
|
3361
|
+
lazy: this.lazy
|
|
3362
|
+
}
|
|
3363
|
+
})
|
|
3364
|
+
);
|
|
2761
3365
|
});
|
|
2762
3366
|
});
|
|
2763
3367
|
this.root.querySelectorAll(".filter").forEach((inp) => {
|
|
2764
3368
|
inp.addEventListener("input", () => {
|
|
2765
|
-
this.filters[inp.dataset.field] = inp.value;
|
|
2766
|
-
this.page = 0;
|
|
2767
3369
|
const field = inp.dataset.field;
|
|
3370
|
+
const value = inp.value;
|
|
2768
3371
|
const pos = inp.selectionStart;
|
|
3372
|
+
this.filters[field] = value;
|
|
3373
|
+
this.page = 0;
|
|
2769
3374
|
this.render();
|
|
2770
|
-
|
|
3375
|
+
this.dispatchEvent(
|
|
3376
|
+
new CustomEvent("filter-change", {
|
|
3377
|
+
bubbles: true,
|
|
3378
|
+
composed: true,
|
|
3379
|
+
detail: { field, value, lazy: this.lazy }
|
|
3380
|
+
})
|
|
3381
|
+
);
|
|
3382
|
+
const again = this.root.querySelector(
|
|
3383
|
+
`.filter[data-field="${esc(field)}"]`
|
|
3384
|
+
);
|
|
2771
3385
|
if (again) {
|
|
2772
3386
|
again.focus();
|
|
2773
3387
|
again.setSelectionRange(pos, pos);
|
|
@@ -2775,117 +3389,274 @@ const _FxTable = class _FxTable extends FxElement {
|
|
|
2775
3389
|
});
|
|
2776
3390
|
inp.addEventListener("click", (e) => e.stopPropagation());
|
|
2777
3391
|
});
|
|
3392
|
+
this.root.querySelectorAll("[data-search-fields]").forEach((inp) => {
|
|
3393
|
+
inp.addEventListener("input", () => {
|
|
3394
|
+
this.globalSearch = inp.value;
|
|
3395
|
+
this.page = 0;
|
|
3396
|
+
const pos = inp.selectionStart;
|
|
3397
|
+
this.render();
|
|
3398
|
+
const again = this.root.querySelector(
|
|
3399
|
+
"[data-search-fields]"
|
|
3400
|
+
);
|
|
3401
|
+
if (again) {
|
|
3402
|
+
again.focus();
|
|
3403
|
+
again.setSelectionRange(pos, pos);
|
|
3404
|
+
}
|
|
3405
|
+
});
|
|
3406
|
+
});
|
|
2778
3407
|
this.root.querySelectorAll(".pg-btn").forEach((btn) => {
|
|
2779
3408
|
btn.addEventListener("click", () => {
|
|
2780
3409
|
const pg = btn.dataset.pg;
|
|
2781
3410
|
this.page = pg === "first" ? 0 : pg === "prev" ? Math.max(0, this.page - 1) : pg === "next" ? Math.min(pages - 1, this.page + 1) : pg === "last" ? pages - 1 : Number(pg);
|
|
2782
3411
|
this.render();
|
|
2783
|
-
this.dispatchEvent(
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
3412
|
+
this.dispatchEvent(
|
|
3413
|
+
new CustomEvent("page-change", {
|
|
3414
|
+
bubbles: true,
|
|
3415
|
+
composed: true,
|
|
3416
|
+
detail: {
|
|
3417
|
+
page: this.page + 1,
|
|
3418
|
+
pages,
|
|
3419
|
+
rowsPerPage: this.rowsPerPage,
|
|
3420
|
+
total,
|
|
3421
|
+
lazy: this.lazy
|
|
3422
|
+
}
|
|
3423
|
+
})
|
|
3424
|
+
);
|
|
2788
3425
|
});
|
|
2789
3426
|
});
|
|
2790
|
-
this.root.querySelector(".rows-sel")?.addEventListener("change", (e) => {
|
|
2791
|
-
|
|
3427
|
+
this.root.querySelector("fx-select.rows-sel")?.addEventListener("change", (e) => {
|
|
3428
|
+
const value = Number(e.detail?.value);
|
|
3429
|
+
if (!value || value === this.rowsPerPage) return;
|
|
3430
|
+
this.rowsPerPage = value;
|
|
2792
3431
|
this.page = 0;
|
|
2793
3432
|
this.render();
|
|
2794
3433
|
});
|
|
2795
3434
|
this.root.querySelectorAll("tbody tr[data-index]").forEach((tr) => {
|
|
2796
3435
|
tr.addEventListener("click", () => {
|
|
2797
|
-
this.dispatchEvent(
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
3436
|
+
this.dispatchEvent(
|
|
3437
|
+
new CustomEvent("row-click", {
|
|
3438
|
+
bubbles: true,
|
|
3439
|
+
composed: true,
|
|
3440
|
+
detail: {
|
|
3441
|
+
row: rows[Number(tr.dataset.index)],
|
|
3442
|
+
index: Number(tr.dataset.index)
|
|
3443
|
+
}
|
|
3444
|
+
})
|
|
3445
|
+
);
|
|
2802
3446
|
});
|
|
2803
3447
|
});
|
|
3448
|
+
this._restoreToolbarSearch();
|
|
3449
|
+
}
|
|
3450
|
+
_restoreToolbarSearch() {
|
|
3451
|
+
const input = this.root.querySelector(
|
|
3452
|
+
"[data-search-fields]"
|
|
3453
|
+
);
|
|
3454
|
+
if (input && this.globalSearch) input.value = this.globalSearch;
|
|
2804
3455
|
}
|
|
2805
3456
|
};
|
|
2806
3457
|
_FxTable.styles = css`
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
3458
|
+
:host {
|
|
3459
|
+
display: block;
|
|
3460
|
+
font-family: var(--fx-font-family);
|
|
3461
|
+
font-size: var(--fx-font-size);
|
|
3462
|
+
color: var(--fx-text-default);
|
|
3463
|
+
background: var(--fx-surface-background);
|
|
3464
|
+
}
|
|
3465
|
+
.table-wrap {
|
|
3466
|
+
position: relative;
|
|
3467
|
+
}
|
|
3468
|
+
.scroll {
|
|
3469
|
+
overflow-x: auto;
|
|
3470
|
+
border: 1px solid var(--fx-border-default);
|
|
3471
|
+
border-radius: var(--fx-radius-md);
|
|
3472
|
+
}
|
|
3473
|
+
table {
|
|
3474
|
+
width: 100%;
|
|
3475
|
+
border-collapse: collapse;
|
|
3476
|
+
}
|
|
3477
|
+
th {
|
|
3478
|
+
text-align: left;
|
|
3479
|
+
padding: var(--fx-space-sm) var(--fx-space-md);
|
|
3480
|
+
background: var(--fx-surface-surface);
|
|
3481
|
+
border-bottom: 2px solid var(--fx-border-default);
|
|
3482
|
+
white-space: nowrap;
|
|
3483
|
+
user-select: none;
|
|
3484
|
+
}
|
|
3485
|
+
th.sortable {
|
|
3486
|
+
cursor: pointer;
|
|
3487
|
+
}
|
|
3488
|
+
th.sortable:hover {
|
|
3489
|
+
color: var(--fx-color-primary);
|
|
3490
|
+
}
|
|
3491
|
+
.sort-ind {
|
|
3492
|
+
font-size: calc(var(--fx-font-size) - 4px);
|
|
3493
|
+
margin-left: var(--fx-space-3xs, 2px);
|
|
3494
|
+
opacity: 0.45;
|
|
3495
|
+
transition: opacity var(--fx-motion-duration-fast)
|
|
3496
|
+
var(--fx-motion-easing);
|
|
3497
|
+
}
|
|
3498
|
+
th.sortable:hover .sort-ind {
|
|
3499
|
+
opacity: 0.85;
|
|
3500
|
+
}
|
|
3501
|
+
.sort-ind.active {
|
|
3502
|
+
opacity: 1;
|
|
3503
|
+
color: var(--fx-color-primary);
|
|
3504
|
+
}
|
|
3505
|
+
.filter-row th {
|
|
3506
|
+
padding: var(--fx-space-3xs, 4px) var(--fx-space-md) var(--fx-space-sm);
|
|
3507
|
+
border-bottom-width: 1px;
|
|
3508
|
+
}
|
|
3509
|
+
.filter {
|
|
3510
|
+
width: 100%;
|
|
3511
|
+
box-sizing: border-box;
|
|
3512
|
+
font: inherit;
|
|
3513
|
+
font-size: calc(var(--fx-font-size) - 2px);
|
|
3514
|
+
color: var(--fx-text-default);
|
|
3515
|
+
background: var(--fx-surface-background);
|
|
3516
|
+
border: 1px solid var(--fx-border-default);
|
|
3517
|
+
border-radius: var(--fx-radius-sm);
|
|
3518
|
+
padding: var(--fx-space-3xs, 4px) var(--fx-space-xs);
|
|
3519
|
+
outline: none;
|
|
3520
|
+
}
|
|
3521
|
+
.filter:focus {
|
|
3522
|
+
border-color: var(--fx-color-primary);
|
|
3523
|
+
box-shadow: var(--fx-effect-focus-ring, none);
|
|
3524
|
+
}
|
|
3525
|
+
td {
|
|
3526
|
+
padding: var(--fx-space-sm) var(--fx-space-md);
|
|
3527
|
+
border-bottom: 1px solid var(--fx-border-default);
|
|
3528
|
+
}
|
|
3529
|
+
tbody tr {
|
|
3530
|
+
cursor: pointer;
|
|
3531
|
+
transition: background-color var(--fx-motion-duration-fast)
|
|
3532
|
+
var(--fx-motion-easing);
|
|
3533
|
+
}
|
|
3534
|
+
tbody tr:hover {
|
|
3535
|
+
background: color-mix(
|
|
3536
|
+
in srgb,
|
|
3537
|
+
var(--fx-color-primary) 8%,
|
|
3538
|
+
transparent
|
|
3539
|
+
);
|
|
3540
|
+
}
|
|
3541
|
+
:host([striped]) tbody tr:nth-child(even) {
|
|
3542
|
+
background: var(--fx-surface-background);
|
|
3543
|
+
}
|
|
3544
|
+
:host([striped]) tbody tr:hover {
|
|
3545
|
+
background: color-mix(
|
|
3546
|
+
in srgb,
|
|
3547
|
+
var(--fx-color-primary) 8%,
|
|
3548
|
+
transparent
|
|
3549
|
+
);
|
|
3550
|
+
}
|
|
3551
|
+
.empty {
|
|
3552
|
+
text-align: center;
|
|
3553
|
+
padding: var(--fx-space-xl);
|
|
3554
|
+
color: var(--fx-text-muted);
|
|
3555
|
+
}
|
|
3556
|
+
.pager {
|
|
3557
|
+
display: flex;
|
|
3558
|
+
align-items: center;
|
|
3559
|
+
gap: var(--fx-space-sm);
|
|
3560
|
+
flex-wrap: wrap;
|
|
3561
|
+
padding: var(--fx-space-sm) 0;
|
|
3562
|
+
}
|
|
3563
|
+
:host([pagination-position="center"]) .pager {
|
|
3564
|
+
justify-content: center;
|
|
3565
|
+
}
|
|
3566
|
+
:host([pagination-position="right"]) .pager {
|
|
3567
|
+
justify-content: flex-end;
|
|
3568
|
+
}
|
|
3569
|
+
.pager .info {
|
|
3570
|
+
color: var(--fx-text-muted);
|
|
3571
|
+
font-size: calc(var(--fx-font-size) - 2px);
|
|
3572
|
+
}
|
|
3573
|
+
.pg-btn {
|
|
3574
|
+
min-width: var(--fx-size-sm);
|
|
3575
|
+
height: var(--fx-size-sm);
|
|
3576
|
+
font: inherit;
|
|
3577
|
+
font-size: calc(var(--fx-font-size) - 2px);
|
|
3578
|
+
color: var(--fx-text-default);
|
|
3579
|
+
background: var(--fx-surface-background);
|
|
3580
|
+
border: 1px solid var(--fx-border-default);
|
|
3581
|
+
border-radius: var(--fx-radius-sm);
|
|
3582
|
+
padding: var(--fx-space-3xs, 4px) var(--fx-space-sm);
|
|
3583
|
+
cursor: pointer;
|
|
3584
|
+
transition: border-color var(--fx-motion-duration-fast)
|
|
3585
|
+
var(--fx-motion-easing);
|
|
3586
|
+
}
|
|
3587
|
+
.pg-btn:hover:not(:disabled):not([aria-current="true"]) {
|
|
3588
|
+
border-color: var(--fx-color-primary);
|
|
3589
|
+
color: var(--fx-color-primary);
|
|
3590
|
+
}
|
|
3591
|
+
.pg-btn:disabled {
|
|
3592
|
+
opacity: 0.5;
|
|
3593
|
+
cursor: not-allowed;
|
|
3594
|
+
}
|
|
3595
|
+
.pg-btn[aria-current="true"] {
|
|
3596
|
+
background: var(--fx-color-primary);
|
|
3597
|
+
border-color: var(--fx-color-primary);
|
|
3598
|
+
color: #fff;
|
|
3599
|
+
font-weight: var(--fx-font-weight);
|
|
3600
|
+
}
|
|
3601
|
+
fx-select {
|
|
3602
|
+
vertical-align: middle;
|
|
3603
|
+
}
|
|
3604
|
+
fx-select::part(trigger) {
|
|
3605
|
+
min-width: 64px !important;
|
|
3606
|
+
min-height: var(--fx-size-sm) !important;
|
|
3607
|
+
padding: 0 var(--fx-space-sm) !important;
|
|
3608
|
+
border-radius: var(--fx-radius-sm) !important;
|
|
3609
|
+
font-size: calc(var(--fx-font-size) - 2px) !important;
|
|
3610
|
+
}
|
|
3611
|
+
.toolbar {
|
|
3612
|
+
display: flex;
|
|
3613
|
+
align-items: center;
|
|
3614
|
+
gap: var(--fx-space-sm);
|
|
3615
|
+
flex-wrap: wrap;
|
|
3616
|
+
padding: var(--fx-space-sm) var(--fx-space-md);
|
|
3617
|
+
border: 1px solid var(--fx-border-default);
|
|
3618
|
+
border-bottom: none;
|
|
3619
|
+
border-radius: var(--fx-radius-md) var(--fx-radius-md) 0 0;
|
|
3620
|
+
background: var(--fx-surface-background);
|
|
3621
|
+
}
|
|
3622
|
+
.toolbar:empty {
|
|
3623
|
+
display: none;
|
|
3624
|
+
}
|
|
3625
|
+
.loading-overlay {
|
|
3626
|
+
position: absolute;
|
|
3627
|
+
inset: 0;
|
|
3628
|
+
display: flex;
|
|
3629
|
+
flex-direction: column;
|
|
3630
|
+
align-items: center;
|
|
3631
|
+
justify-content: center;
|
|
3632
|
+
gap: var(--fx-space-sm);
|
|
3633
|
+
background: color-mix(
|
|
3634
|
+
in srgb,
|
|
3635
|
+
var(--fx-surface-background) 85%,
|
|
3636
|
+
transparent
|
|
3637
|
+
);
|
|
3638
|
+
backdrop-filter: blur(2px);
|
|
3639
|
+
z-index: 10;
|
|
3640
|
+
border-radius: var(--fx-radius-md);
|
|
3641
|
+
}
|
|
3642
|
+
.tbl-spinner {
|
|
3643
|
+
width: 32px;
|
|
3644
|
+
height: 32px;
|
|
3645
|
+
border: 3px solid var(--fx-border-default);
|
|
3646
|
+
border-top-color: var(--fx-color-primary);
|
|
3647
|
+
border-radius: 50%;
|
|
3648
|
+
animation: tbl-spin 1s linear infinite;
|
|
3649
|
+
}
|
|
3650
|
+
@keyframes tbl-spin {
|
|
3651
|
+
to {
|
|
3652
|
+
transform: rotate(360deg);
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
.loading-text {
|
|
3656
|
+
color: var(--fx-text-muted);
|
|
3657
|
+
font-size: calc(var(--fx-font-size) - 1px);
|
|
3658
|
+
}
|
|
3659
|
+
`;
|
|
2889
3660
|
let FxTable = _FxTable;
|
|
2890
3661
|
function defineFxTable() {
|
|
2891
3662
|
return defineElement("fx-table", FxTable);
|
|
@@ -3178,9 +3949,9 @@ const _FxTextarea = class _FxTextarea extends FxElement {
|
|
|
3178
3949
|
const rows = this.getAttr("rows");
|
|
3179
3950
|
const maxlength = this.getAttr("maxlength");
|
|
3180
3951
|
this.setTemplate(`<textarea class="field" part="textarea"
|
|
3181
|
-
${placeholder ? `placeholder="${placeholder}"` : ""}
|
|
3182
|
-
${rows ? `rows="${rows}"` : ""}
|
|
3183
|
-
${maxlength ? `maxlength="${maxlength}"` : ""}
|
|
3952
|
+
${placeholder ? `placeholder="${esc(placeholder)}"` : ""}
|
|
3953
|
+
${rows ? `rows="${esc(rows)}"` : ""}
|
|
3954
|
+
${maxlength ? `maxlength="${esc(maxlength)}"` : ""}
|
|
3184
3955
|
></textarea>`);
|
|
3185
3956
|
const field = this.root.querySelector(".field");
|
|
3186
3957
|
if (!field) return;
|
|
@@ -3257,6 +4028,10 @@ function defineFxTextarea() {
|
|
|
3257
4028
|
}
|
|
3258
4029
|
defineFxTextarea();
|
|
3259
4030
|
const _FxDialog = class _FxDialog extends FxElement {
|
|
4031
|
+
constructor() {
|
|
4032
|
+
super(...arguments);
|
|
4033
|
+
this._previouslyFocused = null;
|
|
4034
|
+
}
|
|
3260
4035
|
static get observedAttributes() {
|
|
3261
4036
|
return ["open", "size", "heading"];
|
|
3262
4037
|
}
|
|
@@ -3271,9 +4046,9 @@ const _FxDialog = class _FxDialog extends FxElement {
|
|
|
3271
4046
|
const heading = this.getAttr("heading");
|
|
3272
4047
|
this.setTemplate(`
|
|
3273
4048
|
<div class="overlay" part="overlay" ${isOpen ? "" : "hidden"}>
|
|
3274
|
-
<div class="dialog" role="dialog" aria-modal="true" aria-label="${heading}">
|
|
4049
|
+
<div class="dialog" role="dialog" aria-modal="true" aria-label="${esc(heading)}" tabindex="-1">
|
|
3275
4050
|
<header>
|
|
3276
|
-
<h2>${heading}</h2>
|
|
4051
|
+
<h2>${esc(heading)}</h2>
|
|
3277
4052
|
<button type="button" class="close" part="close" aria-label="Fechar">×</button>
|
|
3278
4053
|
</header>
|
|
3279
4054
|
<div class="body"><slot></slot></div>
|
|
@@ -3286,6 +4061,9 @@ const _FxDialog = class _FxDialog extends FxElement {
|
|
|
3286
4061
|
const closeBtn = this.root.querySelector(".close");
|
|
3287
4062
|
const close = () => {
|
|
3288
4063
|
this.open = false;
|
|
4064
|
+
this._cleanup?.();
|
|
4065
|
+
this._cleanup = void 0;
|
|
4066
|
+
this._restoreFocus();
|
|
3289
4067
|
this.dispatchEvent(new CustomEvent("close", { bubbles: true, composed: true }));
|
|
3290
4068
|
};
|
|
3291
4069
|
overlay?.addEventListener("click", (e) => {
|
|
@@ -3296,16 +4074,46 @@ const _FxDialog = class _FxDialog extends FxElement {
|
|
|
3296
4074
|
if (e.key === "Escape") {
|
|
3297
4075
|
e.stopPropagation();
|
|
3298
4076
|
close();
|
|
4077
|
+
return;
|
|
3299
4078
|
}
|
|
4079
|
+
if (e.key === "Tab") this._trapFocus(e);
|
|
3300
4080
|
};
|
|
3301
|
-
document.addEventListener("keydown", onKey
|
|
4081
|
+
document.addEventListener("keydown", onKey);
|
|
3302
4082
|
this._cleanup = () => document.removeEventListener("keydown", onKey);
|
|
3303
|
-
requestAnimationFrame(() =>
|
|
4083
|
+
requestAnimationFrame(() => {
|
|
4084
|
+
this._previouslyFocused = document.activeElement;
|
|
4085
|
+
this.root.querySelector(".dialog")?.focus();
|
|
4086
|
+
});
|
|
3304
4087
|
this.dispatchEvent(new CustomEvent("open", { bubbles: true, composed: true }));
|
|
3305
4088
|
}
|
|
4089
|
+
/** Mantém o foco dentro do modal (WCAG 2.4.3 / 2.1.2). */
|
|
4090
|
+
_trapFocus(e) {
|
|
4091
|
+
const dialog = this.root.querySelector(".dialog");
|
|
4092
|
+
if (!dialog) return;
|
|
4093
|
+
const focusables = dialog.querySelectorAll(
|
|
4094
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
4095
|
+
);
|
|
4096
|
+
if (!focusables.length) return;
|
|
4097
|
+
const first = focusables[0];
|
|
4098
|
+
const last = focusables[focusables.length - 1];
|
|
4099
|
+
const active = this.root.activeElement;
|
|
4100
|
+
if (e.shiftKey && (active === first || active === dialog)) {
|
|
4101
|
+
e.preventDefault();
|
|
4102
|
+
last.focus();
|
|
4103
|
+
} else if (!e.shiftKey && active === last) {
|
|
4104
|
+
e.preventDefault();
|
|
4105
|
+
first.focus();
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4108
|
+
_restoreFocus() {
|
|
4109
|
+
if (this._previouslyFocused?.isConnected) this._previouslyFocused.focus();
|
|
4110
|
+
this._previouslyFocused = null;
|
|
4111
|
+
}
|
|
3306
4112
|
disconnectedCallback() {
|
|
3307
4113
|
super.disconnectedCallback();
|
|
3308
4114
|
this._cleanup?.();
|
|
4115
|
+
this._cleanup = void 0;
|
|
4116
|
+
this._restoreFocus();
|
|
3309
4117
|
}
|
|
3310
4118
|
};
|
|
3311
4119
|
_FxDialog.styles = css`
|
|
@@ -3555,7 +4363,7 @@ const _FxTooltip = class _FxTooltip extends FxElement {
|
|
|
3555
4363
|
const content = this.getAttr("content");
|
|
3556
4364
|
this.setTemplate(`
|
|
3557
4365
|
<slot></slot>
|
|
3558
|
-
<span class="bubble" part="bubble" role="tooltip">${content}</span>
|
|
4366
|
+
<span class="bubble" part="bubble" role="tooltip">${esc(content)}</span>
|
|
3559
4367
|
`);
|
|
3560
4368
|
}
|
|
3561
4369
|
};
|
|
@@ -3934,8 +4742,8 @@ const _FxTabs = class _FxTabs extends FxElement {
|
|
|
3934
4742
|
const tabId = t.getAttribute("tab") ?? "";
|
|
3935
4743
|
const disabled = t.hasAttribute("disabled");
|
|
3936
4744
|
return `<button type="button" class="tab" role="tab" part="tab"
|
|
3937
|
-
data-tab="${tabId}" aria-selected="${tabId === active}"
|
|
3938
|
-
${disabled ? 'aria-disabled="true" data-disabled="true"' : ""}>${t.textContent?.trim()}</button>`;
|
|
4745
|
+
data-tab="${esc(tabId)}" aria-selected="${tabId === active}"
|
|
4746
|
+
${disabled ? 'aria-disabled="true" data-disabled="true"' : ""}>${esc(t.textContent?.trim() ?? "")}</button>`;
|
|
3939
4747
|
}).join("")}
|
|
3940
4748
|
</div>
|
|
3941
4749
|
<slot></slot>
|
|
@@ -4095,7 +4903,7 @@ const _FxProgress = class _FxProgress extends FxElement {
|
|
|
4095
4903
|
const labelText = this.getAttr("label");
|
|
4096
4904
|
const showPct = !indeterminate && !this.hasAttr("hide-label");
|
|
4097
4905
|
this.setTemplate(`
|
|
4098
|
-
${labelText ? `<div class="caption" part="caption">${labelText}</div>` : ""}
|
|
4906
|
+
${labelText ? `<div class="caption" part="caption">${esc(labelText)}</div>` : ""}
|
|
4099
4907
|
<div class="row">
|
|
4100
4908
|
<div class="track" part="track" role="progressbar"
|
|
4101
4909
|
aria-valuenow="${pct}" aria-valuemin="0" aria-valuemax="100">
|
|
@@ -4171,10 +4979,15 @@ const _FxSkeleton = class _FxSkeleton extends FxElement {
|
|
|
4171
4979
|
static get observedAttributes() {
|
|
4172
4980
|
return ["variant", "width", "height", "lines"];
|
|
4173
4981
|
}
|
|
4982
|
+
/** Valida um valor de dimensão CSS, retornando-o seguro ou vazio. */
|
|
4983
|
+
safeSize(value) {
|
|
4984
|
+
const v = value.trim();
|
|
4985
|
+
return /^-?\d*\.?\d+(px|em|rem|%|vw|vh|vmin|vmax|ch|ex|cm|mm|in|pt|pc)$/.test(v) ? v : "";
|
|
4986
|
+
}
|
|
4174
4987
|
render() {
|
|
4175
4988
|
const variant = this.getAttr("variant", "text");
|
|
4176
|
-
const width = this.getAttr("width");
|
|
4177
|
-
const height = this.getAttr("height");
|
|
4989
|
+
const width = this.safeSize(this.getAttr("width"));
|
|
4990
|
+
const height = this.safeSize(this.getAttr("height"));
|
|
4178
4991
|
const style = `${width ? `width:${width};` : ""}${height ? `height:${height};` : ""}`;
|
|
4179
4992
|
if (variant === "circle") {
|
|
4180
4993
|
const size = height || width || "40px";
|
|
@@ -4234,7 +5047,7 @@ const _FxAlert = class _FxAlert extends FxElement {
|
|
|
4234
5047
|
<div class="alert" part="alert" role="alert">
|
|
4235
5048
|
<span class="icon">${icons[variant] ?? "ℹ"}</span>
|
|
4236
5049
|
<div class="content">
|
|
4237
|
-
${title ? `<div class="title">${title}</div>` : ""}
|
|
5050
|
+
${title ? `<div class="title">${esc(title)}</div>` : ""}
|
|
4238
5051
|
<div class="body"><slot></slot></div>
|
|
4239
5052
|
</div>
|
|
4240
5053
|
${this.hasAttr("dismissible") ? '<button type="button" class="close" aria-label="Fechar">×</button>' : ""}
|
|
@@ -4286,6 +5099,10 @@ function defineFxAlert() {
|
|
|
4286
5099
|
}
|
|
4287
5100
|
defineFxAlert();
|
|
4288
5101
|
const _FxDropdown = class _FxDropdown extends FxElement {
|
|
5102
|
+
constructor() {
|
|
5103
|
+
super(...arguments);
|
|
5104
|
+
this._listenersAttached = false;
|
|
5105
|
+
}
|
|
4289
5106
|
static get observedAttributes() {
|
|
4290
5107
|
return ["label", "position", "open"];
|
|
4291
5108
|
}
|
|
@@ -4301,7 +5118,7 @@ const _FxDropdown = class _FxDropdown extends FxElement {
|
|
|
4301
5118
|
this.setTemplate(`
|
|
4302
5119
|
<span class="trigger" role="button" tabindex="0" part="trigger"
|
|
4303
5120
|
aria-haspopup="menu" aria-expanded="${this.open}">
|
|
4304
|
-
${label} ▾
|
|
5121
|
+
${esc(label)} ▾
|
|
4305
5122
|
</span>
|
|
4306
5123
|
<div class="panel" role="menu" part="panel">
|
|
4307
5124
|
${items.length ? "" : '<div class="empty">Nenhuma ação</div>'}
|
|
@@ -4320,18 +5137,37 @@ const _FxDropdown = class _FxDropdown extends FxElement {
|
|
|
4320
5137
|
if (e.key === "Enter" || e.key === " ") {
|
|
4321
5138
|
e.preventDefault();
|
|
4322
5139
|
this.open = !this.open;
|
|
5140
|
+
} else if (e.key === "Escape") {
|
|
5141
|
+
this.open = false;
|
|
4323
5142
|
}
|
|
4324
5143
|
});
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
5144
|
+
this._attachDelegatedListeners();
|
|
5145
|
+
}
|
|
5146
|
+
/** Delegação de cliques nos itens + fechamento ao clicar fora (registrado uma vez). */
|
|
5147
|
+
_attachDelegatedListeners() {
|
|
5148
|
+
if (this._listenersAttached) return;
|
|
5149
|
+
this._listenersAttached = true;
|
|
5150
|
+
this.addEventListener("click", (e) => {
|
|
5151
|
+
const item = e.composedPath().find(
|
|
5152
|
+
(n) => n.tagName?.toLowerCase() === "fx-dropdown-item"
|
|
5153
|
+
);
|
|
5154
|
+
if (!item) return;
|
|
5155
|
+
this.open = false;
|
|
5156
|
+
this.dispatchEvent(new CustomEvent("select", {
|
|
5157
|
+
bubbles: true,
|
|
5158
|
+
composed: true,
|
|
5159
|
+
detail: { value: item.getAttribute("value") ?? "" }
|
|
5160
|
+
}));
|
|
4334
5161
|
});
|
|
5162
|
+
this.docListener = (e) => {
|
|
5163
|
+
if (!this.contains(e.target)) this.open = false;
|
|
5164
|
+
};
|
|
5165
|
+
document.addEventListener("click", this.docListener);
|
|
5166
|
+
}
|
|
5167
|
+
disconnectedCallback() {
|
|
5168
|
+
super.disconnectedCallback();
|
|
5169
|
+
if (this.docListener) document.removeEventListener("click", this.docListener);
|
|
5170
|
+
this.docListener = void 0;
|
|
4335
5171
|
}
|
|
4336
5172
|
};
|
|
4337
5173
|
_FxDropdown.styles = css`
|
|
@@ -4419,6 +5255,15 @@ const _FxDrawer = class _FxDrawer extends FxElement {
|
|
|
4419
5255
|
super(...arguments);
|
|
4420
5256
|
this._onKeydown = () => {
|
|
4421
5257
|
};
|
|
5258
|
+
this._previouslyFocused = null;
|
|
5259
|
+
this._close = () => {
|
|
5260
|
+
if (!this.open) return;
|
|
5261
|
+
this.open = false;
|
|
5262
|
+
this._restoreFocus();
|
|
5263
|
+
this.dispatchEvent(
|
|
5264
|
+
new CustomEvent("close", { bubbles: true, composed: true })
|
|
5265
|
+
);
|
|
5266
|
+
};
|
|
4422
5267
|
}
|
|
4423
5268
|
static get observedAttributes() {
|
|
4424
5269
|
return ["open", "title", "position"];
|
|
@@ -4446,37 +5291,61 @@ const _FxDrawer = class _FxDrawer extends FxElement {
|
|
|
4446
5291
|
const title = this.getAttr("title");
|
|
4447
5292
|
this.setTemplate(`
|
|
4448
5293
|
<div class="overlay" part="overlay"></div>
|
|
4449
|
-
<div class="panel" part="panel" role="dialog" aria-modal="true" aria-label="${title || "Drawer"}">
|
|
4450
|
-
${title !== "" ? `<div class="header" part="header"><span class="title">${title}</span><button type="button" class="close" part="close" aria-label="Fechar">✕</button></div>` : ""}
|
|
5294
|
+
<div class="panel" part="panel" role="dialog" aria-modal="true" aria-label="${esc(title || "Drawer")}" tabindex="-1">
|
|
5295
|
+
${title !== "" ? `<div class="header" part="header"><span class="title">${esc(title)}</span><button type="button" class="close" part="close" aria-label="Fechar">✕</button></div>` : ""}
|
|
4451
5296
|
<div class="body" part="body"><slot></slot></div>
|
|
4452
5297
|
</div>
|
|
4453
5298
|
`);
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
5299
|
+
this.root.querySelector(".overlay")?.addEventListener("click", this._close);
|
|
5300
|
+
this.root.querySelector(".close")?.addEventListener("click", this._close);
|
|
5301
|
+
if (this.open) {
|
|
5302
|
+
requestAnimationFrame(() => {
|
|
5303
|
+
if (this._previouslyFocused === null) {
|
|
5304
|
+
this._previouslyFocused = document.activeElement;
|
|
5305
|
+
}
|
|
5306
|
+
this.root.querySelector(".panel")?.focus();
|
|
5307
|
+
});
|
|
5308
|
+
}
|
|
4463
5309
|
}
|
|
4464
5310
|
connectedCallback() {
|
|
4465
5311
|
super.connectedCallback?.();
|
|
4466
5312
|
this._onKeydown = (e) => {
|
|
4467
5313
|
if (e.key === "Escape" && this.open) {
|
|
4468
|
-
this.
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
);
|
|
5314
|
+
this._close();
|
|
5315
|
+
} else if (e.key === "Tab" && this.open) {
|
|
5316
|
+
this._trapFocus(e);
|
|
4472
5317
|
}
|
|
4473
5318
|
};
|
|
4474
5319
|
document.addEventListener("keydown", this._onKeydown);
|
|
4475
5320
|
}
|
|
4476
5321
|
disconnectedCallback() {
|
|
4477
5322
|
document.removeEventListener("keydown", this._onKeydown);
|
|
5323
|
+
this._restoreFocus();
|
|
4478
5324
|
super.disconnectedCallback?.();
|
|
4479
5325
|
}
|
|
5326
|
+
/** Mantém o foco dentro do drawer (WCAG 2.4.3 / 2.1.2). */
|
|
5327
|
+
_trapFocus(e) {
|
|
5328
|
+
const panel = this.root.querySelector(".panel");
|
|
5329
|
+
if (!panel) return;
|
|
5330
|
+
const focusables = panel.querySelectorAll(
|
|
5331
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
5332
|
+
);
|
|
5333
|
+
if (!focusables.length) return;
|
|
5334
|
+
const first = focusables[0];
|
|
5335
|
+
const last = focusables[focusables.length - 1];
|
|
5336
|
+
const active = this.root.activeElement;
|
|
5337
|
+
if (e.shiftKey && (active === first || active === panel)) {
|
|
5338
|
+
e.preventDefault();
|
|
5339
|
+
last.focus();
|
|
5340
|
+
} else if (!e.shiftKey && active === last) {
|
|
5341
|
+
e.preventDefault();
|
|
5342
|
+
first.focus();
|
|
5343
|
+
}
|
|
5344
|
+
}
|
|
5345
|
+
_restoreFocus() {
|
|
5346
|
+
if (this._previouslyFocused?.isConnected) this._previouslyFocused.focus();
|
|
5347
|
+
this._previouslyFocused = null;
|
|
5348
|
+
}
|
|
4480
5349
|
};
|
|
4481
5350
|
_FxDrawer.styles = css`
|
|
4482
5351
|
:host {
|
|
@@ -4643,9 +5512,9 @@ const _FxPagination = class _FxPagination extends FxElement {
|
|
|
4643
5512
|
${window_[window_.length - 1] !== pages && pages > 5 ? `<button type="button" class="nav" data-go="${pages}">${pages}</button>` : ""}
|
|
4644
5513
|
<button type="button" class="nav" part="next" data-go="${current + 1}" ${current >= pages ? "disabled" : ""}>›</button>
|
|
4645
5514
|
${opts.length ? `
|
|
4646
|
-
<select class="
|
|
4647
|
-
${opts.map((o) => `<option value="${o}"
|
|
4648
|
-
</select>` : ""}
|
|
5515
|
+
<fx-select class="rows-sel" part="rows" size="sm" value="${this.rows}" aria-label="Itens por página">
|
|
5516
|
+
${opts.map((o) => `<option value="${esc(o)}">${esc(o)}</option>`).join("")}
|
|
5517
|
+
</fx-select>` : ""}
|
|
4649
5518
|
`);
|
|
4650
5519
|
function total0(page, rows) {
|
|
4651
5520
|
return (page - 1) * rows + 1;
|
|
@@ -4659,9 +5528,11 @@ const _FxPagination = class _FxPagination extends FxElement {
|
|
|
4659
5528
|
}
|
|
4660
5529
|
});
|
|
4661
5530
|
});
|
|
4662
|
-
const sel = this.root.querySelector("select.
|
|
4663
|
-
sel?.addEventListener("change", () => {
|
|
4664
|
-
|
|
5531
|
+
const sel = this.root.querySelector("fx-select.rows-sel");
|
|
5532
|
+
sel?.addEventListener("change", (e) => {
|
|
5533
|
+
const value = Number(e.detail?.value);
|
|
5534
|
+
if (!value || value === this.rows) return;
|
|
5535
|
+
this.rows = value;
|
|
4665
5536
|
this.page = 1;
|
|
4666
5537
|
this.emit();
|
|
4667
5538
|
});
|
|
@@ -4700,7 +5571,7 @@ _FxPagination.styles = css`
|
|
|
4700
5571
|
border-color var(--fx-motion-duration-fast) var(--fx-motion-easing),
|
|
4701
5572
|
background var(--fx-motion-duration-fast) var(--fx-motion-easing);
|
|
4702
5573
|
}
|
|
4703
|
-
.nav:hover:not([disabled]) { border-color: var(--fx-color-primary); color: var(--fx-color-primary); }
|
|
5574
|
+
.nav:hover:not([disabled]):not(.active) { border-color: var(--fx-color-primary); color: var(--fx-color-primary); }
|
|
4704
5575
|
.nav[disabled] { opacity: 0.5; cursor: not-allowed; }
|
|
4705
5576
|
.nav.active {
|
|
4706
5577
|
background: var(--fx-color-primary);
|
|
@@ -4709,7 +5580,14 @@ _FxPagination.styles = css`
|
|
|
4709
5580
|
font-weight: 600;
|
|
4710
5581
|
}
|
|
4711
5582
|
.info { color: var(--fx-text-muted); font-size: calc(var(--fx-font-size) - 2px); }
|
|
4712
|
-
select
|
|
5583
|
+
fx-select { vertical-align: middle; }
|
|
5584
|
+
fx-select::part(trigger) {
|
|
5585
|
+
min-width: 64px !important;
|
|
5586
|
+
min-height: var(--fx-size-sm) !important;
|
|
5587
|
+
padding: 0 var(--fx-space-sm) !important;
|
|
5588
|
+
border-radius: var(--fx-radius-sm) !important;
|
|
5589
|
+
font-size: calc(var(--fx-font-size) - 2px) !important;
|
|
5590
|
+
}
|
|
4713
5591
|
`;
|
|
4714
5592
|
let FxPagination = _FxPagination;
|
|
4715
5593
|
function defineFxPagination() {
|
|
@@ -4746,11 +5624,23 @@ const _FxAutocomplete = class _FxAutocomplete extends FxElement {
|
|
|
4746
5624
|
set source(value) {
|
|
4747
5625
|
this.setAttribute("source", typeof value === "string" ? value : JSON.stringify(value));
|
|
4748
5626
|
}
|
|
5627
|
+
connectedCallback() {
|
|
5628
|
+
super.connectedCallback();
|
|
5629
|
+
this.docListener = (e) => {
|
|
5630
|
+
if (!this.contains(e.target)) this.toggleAttr("open", false);
|
|
5631
|
+
};
|
|
5632
|
+
document.addEventListener("click", this.docListener);
|
|
5633
|
+
}
|
|
5634
|
+
disconnectedCallback() {
|
|
5635
|
+
super.disconnectedCallback();
|
|
5636
|
+
if (this.docListener) document.removeEventListener("click", this.docListener);
|
|
5637
|
+
this.docListener = void 0;
|
|
5638
|
+
}
|
|
4749
5639
|
render() {
|
|
4750
5640
|
const placeholder = this.getAttr("placeholder");
|
|
4751
5641
|
this.setTemplate(`
|
|
4752
5642
|
<input class="field" part="input" type="text"
|
|
4753
|
-
${placeholder ? `placeholder="${placeholder}"` : ""} autocomplete="off"/>
|
|
5643
|
+
${placeholder ? `placeholder="${esc(placeholder)}"` : ""} autocomplete="off"/>
|
|
4754
5644
|
<div class="list" part="list" role="listbox"></div>
|
|
4755
5645
|
`);
|
|
4756
5646
|
const field = this.root.querySelector(".field");
|
|
@@ -4763,7 +5653,7 @@ const _FxAutocomplete = class _FxAutocomplete extends FxElement {
|
|
|
4763
5653
|
if (!list) return;
|
|
4764
5654
|
const q = field.value.toLowerCase();
|
|
4765
5655
|
const matches = this.source.filter((s) => s.toLowerCase().includes(q)).slice(0, 8);
|
|
4766
|
-
list.innerHTML = matches.length ? matches.map((m) => `<button type="button" class="opt" role="option" data-v="${m}">${m}</button>`).join("") : '<div class="empty">Nenhum resultado</div>';
|
|
5656
|
+
list.innerHTML = matches.length ? matches.map((m) => `<button type="button" class="opt" role="option" data-v="${esc(m)}">${esc(m)}</button>`).join("") : '<div class="empty">Nenhum resultado</div>';
|
|
4767
5657
|
list.querySelectorAll(".opt").forEach((btn) => {
|
|
4768
5658
|
btn.addEventListener("click", () => {
|
|
4769
5659
|
field.value = btn.dataset.v ?? "";
|
|
@@ -4785,8 +5675,19 @@ const _FxAutocomplete = class _FxAutocomplete extends FxElement {
|
|
|
4785
5675
|
field.addEventListener("focus", () => {
|
|
4786
5676
|
if (field.value.length >= minChars) openList();
|
|
4787
5677
|
});
|
|
4788
|
-
|
|
4789
|
-
if (
|
|
5678
|
+
field.addEventListener("keydown", (e) => {
|
|
5679
|
+
if (e.key === "Escape") {
|
|
5680
|
+
this.toggleAttr("open", false);
|
|
5681
|
+
return;
|
|
5682
|
+
}
|
|
5683
|
+
if (e.key !== "ArrowDown" && e.key !== "ArrowUp") return;
|
|
5684
|
+
e.preventDefault();
|
|
5685
|
+
if (!this.hasAttr("open")) openList();
|
|
5686
|
+
const opts = Array.from(this.root.querySelectorAll(".opt"));
|
|
5687
|
+
if (!opts.length) return;
|
|
5688
|
+
const current = opts.indexOf(this.root.activeElement);
|
|
5689
|
+
const next = e.key === "ArrowDown" ? current === -1 ? 0 : Math.min(current + 1, opts.length - 1) : current === -1 ? opts.length - 1 : Math.max(current - 1, 0);
|
|
5690
|
+
opts[next]?.focus();
|
|
4790
5691
|
});
|
|
4791
5692
|
}
|
|
4792
5693
|
};
|
|
@@ -4981,8 +5882,10 @@ const _FxKnob = class _FxKnob extends FxElement {
|
|
|
4981
5882
|
const radius = (sizeNum - strokeW) / 2;
|
|
4982
5883
|
const circumference = 2 * Math.PI * radius;
|
|
4983
5884
|
const offset = circumference * (1 - pct);
|
|
4984
|
-
const
|
|
4985
|
-
const
|
|
5885
|
+
const vc = this.valueColor ? this.safeColor(this.valueColor) : "";
|
|
5886
|
+
const rc = this.rangeColor ? this.safeColor(this.rangeColor) : "";
|
|
5887
|
+
const valueColorStyle = vc ? `--_value-color: ${vc};` : "";
|
|
5888
|
+
const rangeColorStyle = rc ? `--_range-color: ${rc};` : "";
|
|
4986
5889
|
const displayValue = this.valueTemplate.replace("{value}", String(Math.round(value)));
|
|
4987
5890
|
this.setTemplate(`
|
|
4988
5891
|
<div class="knob" part="knob" tabindex="${this.disabled ? "-1" : "0"}"
|
|
@@ -4997,7 +5900,7 @@ const _FxKnob = class _FxKnob extends FxElement {
|
|
|
4997
5900
|
stroke-dasharray="${circumference}"
|
|
4998
5901
|
stroke-dashoffset="${offset}" />
|
|
4999
5902
|
</svg>
|
|
5000
|
-
<div class="label" part="label">${displayValue}</div>
|
|
5903
|
+
<div class="label" part="label">${esc(displayValue)}</div>
|
|
5001
5904
|
</div>
|
|
5002
5905
|
`);
|
|
5003
5906
|
this.attachListeners();
|
|
@@ -5008,6 +5911,15 @@ const _FxKnob = class _FxKnob extends FxElement {
|
|
|
5008
5911
|
if (s === "lg") return 140;
|
|
5009
5912
|
return 100;
|
|
5010
5913
|
}
|
|
5914
|
+
/** Valida um valor de cor CSS, retornando-o seguro ou vazio. */
|
|
5915
|
+
safeColor(value) {
|
|
5916
|
+
const v = value.trim();
|
|
5917
|
+
if (/^#[0-9a-fA-F]{3,8}$/.test(v)) return v;
|
|
5918
|
+
if (/^(rgb|rgba|hsl|hsla)\([\d\s.,%()]+\)$/i.test(v)) return v;
|
|
5919
|
+
if (/^var\(--[\w-]+(,\s*[^)]*)?\)$/.test(v)) return v;
|
|
5920
|
+
if (/^[a-zA-Z]+$/.test(v)) return v;
|
|
5921
|
+
return "";
|
|
5922
|
+
}
|
|
5011
5923
|
clampValue(v) {
|
|
5012
5924
|
const min = this.min;
|
|
5013
5925
|
const max = this.max;
|
|
@@ -5421,7 +6333,7 @@ const _FxOrderList = class _FxOrderList extends FxElement {
|
|
|
5421
6333
|
<div class="filter-wrapper" part="filter-wrapper">
|
|
5422
6334
|
<input type="text" class="filter-input" part="filter-input"
|
|
5423
6335
|
placeholder="${this.filterPlaceholder}"
|
|
5424
|
-
value="${esc
|
|
6336
|
+
value="${esc(this._filterValue)}"
|
|
5425
6337
|
aria-label="Filtrar lista" />
|
|
5426
6338
|
</div>
|
|
5427
6339
|
` : ""}
|
|
@@ -5467,10 +6379,10 @@ const _FxOrderList = class _FxOrderList extends FxElement {
|
|
|
5467
6379
|
const key2 = this._getItemKey(item, index);
|
|
5468
6380
|
const label = this._getItemLabel(item);
|
|
5469
6381
|
const isSelected = this._isSelected(item);
|
|
5470
|
-
const content = this._optionTemplate ? this._optionTemplate(item) : esc
|
|
6382
|
+
const content = this._optionTemplate ? this._optionTemplate(item) : esc(label);
|
|
5471
6383
|
return `
|
|
5472
6384
|
<li class="list-item${isSelected ? " selected" : ""}" part="list-item"
|
|
5473
|
-
data-index="${index}" data-key="${esc
|
|
6385
|
+
data-index="${index}" data-key="${esc(key2)}"
|
|
5474
6386
|
role="option" aria-selected="${isSelected}"
|
|
5475
6387
|
${this.dragdrop ? 'draggable="true"' : ""}
|
|
5476
6388
|
tabindex="0">
|
|
@@ -5478,7 +6390,7 @@ const _FxOrderList = class _FxOrderList extends FxElement {
|
|
|
5478
6390
|
${hasSelection ? `
|
|
5479
6391
|
<input type="checkbox" class="checkbox" part="checkbox"
|
|
5480
6392
|
${isSelected ? "checked" : ""}
|
|
5481
|
-
aria-label="Selecionar ${esc
|
|
6393
|
+
aria-label="Selecionar ${esc(label)}" />
|
|
5482
6394
|
` : ""}
|
|
5483
6395
|
<span class="item-content" part="item-content">${content}</span>
|
|
5484
6396
|
</li>
|
|
@@ -5626,10 +6538,10 @@ const _FxOrderList = class _FxOrderList extends FxElement {
|
|
|
5626
6538
|
const key2 = this._getItemKey(item, index);
|
|
5627
6539
|
const label = this._getItemLabel(item);
|
|
5628
6540
|
const isSelected = this._isSelected(item);
|
|
5629
|
-
const content = this._optionTemplate ? this._optionTemplate(item) : esc
|
|
6541
|
+
const content = this._optionTemplate ? this._optionTemplate(item) : esc(label);
|
|
5630
6542
|
return `
|
|
5631
6543
|
<li class="list-item${isSelected ? " selected" : ""}" part="list-item"
|
|
5632
|
-
data-index="${index}" data-key="${esc
|
|
6544
|
+
data-index="${index}" data-key="${esc(key2)}"
|
|
5633
6545
|
role="option" aria-selected="${isSelected}"
|
|
5634
6546
|
${this.dragdrop ? 'draggable="true"' : ""}
|
|
5635
6547
|
tabindex="0">
|
|
@@ -5637,7 +6549,7 @@ const _FxOrderList = class _FxOrderList extends FxElement {
|
|
|
5637
6549
|
${hasSelection ? `
|
|
5638
6550
|
<input type="checkbox" class="checkbox" part="checkbox"
|
|
5639
6551
|
${isSelected ? "checked" : ""}
|
|
5640
|
-
aria-label="Selecionar ${esc
|
|
6552
|
+
aria-label="Selecionar ${esc(label)}" />
|
|
5641
6553
|
` : ""}
|
|
5642
6554
|
<span class="item-content" part="item-content">${content}</span>
|
|
5643
6555
|
</li>
|
|
@@ -5825,9 +6737,6 @@ _FxOrderList.styles = css`
|
|
|
5825
6737
|
.list-item { animation: slideIn 0.2s ease-out; }
|
|
5826
6738
|
`;
|
|
5827
6739
|
let FxOrderList = _FxOrderList;
|
|
5828
|
-
function esc$1(s) {
|
|
5829
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
5830
|
-
}
|
|
5831
6740
|
function defineFxOrderList() {
|
|
5832
6741
|
return defineElement("fx-orderlist", FxOrderList);
|
|
5833
6742
|
}
|
|
@@ -6567,9 +7476,6 @@ _FxPickList.styles = css`
|
|
|
6567
7476
|
.list-item { animation: slideIn 0.2s ease-out; }
|
|
6568
7477
|
`;
|
|
6569
7478
|
let FxPickList = _FxPickList;
|
|
6570
|
-
function esc(s) {
|
|
6571
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
6572
|
-
}
|
|
6573
7479
|
function defineFxPickList() {
|
|
6574
7480
|
return defineElement("fx-picklist", FxPickList);
|
|
6575
7481
|
}
|
|
@@ -6578,6 +7484,7 @@ const _FxAccordionPanel = class _FxAccordionPanel extends FxElement {
|
|
|
6578
7484
|
constructor() {
|
|
6579
7485
|
super(...arguments);
|
|
6580
7486
|
this._mo = null;
|
|
7487
|
+
this._clickListenerAttached = false;
|
|
6581
7488
|
}
|
|
6582
7489
|
static get observedAttributes() {
|
|
6583
7490
|
return ["header", "expanded", "disabled"];
|
|
@@ -6608,18 +7515,21 @@ const _FxAccordionPanel = class _FxAccordionPanel extends FxElement {
|
|
|
6608
7515
|
}
|
|
6609
7516
|
connectedCallback() {
|
|
6610
7517
|
super.connectedCallback();
|
|
6611
|
-
this.
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
|
|
6615
|
-
|
|
6616
|
-
|
|
6617
|
-
|
|
6618
|
-
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
7518
|
+
if (!this._clickListenerAttached) {
|
|
7519
|
+
this._clickListenerAttached = true;
|
|
7520
|
+
this.addEventListener("click", (e) => {
|
|
7521
|
+
const path = e.composedPath();
|
|
7522
|
+
if (!path.some((el) => el.classList?.contains("header"))) return;
|
|
7523
|
+
if (this.disabled) return;
|
|
7524
|
+
this.dispatchEvent(
|
|
7525
|
+
new CustomEvent("fx-accordion-toggle", {
|
|
7526
|
+
bubbles: true,
|
|
7527
|
+
composed: true,
|
|
7528
|
+
detail: { panel: this }
|
|
7529
|
+
})
|
|
7530
|
+
);
|
|
7531
|
+
});
|
|
7532
|
+
}
|
|
6623
7533
|
this._mo = new MutationObserver(() => this.render());
|
|
6624
7534
|
this._mo.observe(this, { attributes: true, attributeFilter: ["expanded", "header", "disabled"] });
|
|
6625
7535
|
}
|
|
@@ -6634,7 +7544,7 @@ const _FxAccordionPanel = class _FxAccordionPanel extends FxElement {
|
|
|
6634
7544
|
aria-expanded="${this.expanded}"
|
|
6635
7545
|
${this.disabled ? 'aria-disabled="true"' : ""}>
|
|
6636
7546
|
<span class="header-text" part="header-text">
|
|
6637
|
-
<slot name="header">${this.header}</slot>
|
|
7547
|
+
<slot name="header">${esc(this.header)}</slot>
|
|
6638
7548
|
</span>
|
|
6639
7549
|
<span class="chevron" part="chevron" aria-hidden="true">
|
|
6640
7550
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none"
|
|
@@ -6899,11 +7809,14 @@ export {
|
|
|
6899
7809
|
defineElement,
|
|
6900
7810
|
defineFxTooltipDirective,
|
|
6901
7811
|
destroyFxTooltipDirective,
|
|
7812
|
+
esc,
|
|
6902
7813
|
kebabToCamel,
|
|
6903
7814
|
lightTokens,
|
|
6904
7815
|
listPresets,
|
|
6905
7816
|
resetTheme,
|
|
7817
|
+
sanitizeData,
|
|
6906
7818
|
setTokens,
|
|
7819
|
+
stripTags,
|
|
6907
7820
|
theme,
|
|
6908
7821
|
themePresets,
|
|
6909
7822
|
tokenCssVars
|