@wrrdev/fenix-ui 1.0.3 → 1.0.5

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.
@@ -1,82 +1,4473 @@
1
- const fenixComponentMap = {
2
- "fx-button": "@wrrdev/fenix-ui/button",
3
- "fx-badge": "@wrrdev/fenix-ui/badge",
4
- "fx-spinner": "@wrrdev/fenix-ui/spinner",
5
- "fx-select": "@wrrdev/fenix-ui/select",
6
- "fx-multiselect": "@wrrdev/fenix-ui/multiselect",
7
- "fx-input": "@wrrdev/fenix-ui/input",
8
- "fx-switch": "@wrrdev/fenix-ui/switch",
9
- "fx-calendar": "@wrrdev/fenix-ui/calendar",
10
- "fx-datepicker": "@wrrdev/fenix-ui/datepicker",
11
- "fx-checkbox": "@wrrdev/fenix-ui/checkbox",
12
- "fx-radio": "@wrrdev/fenix-ui/radio",
13
- "fx-table": "@wrrdev/fenix-ui/table",
14
- "fx-floatlabel": "@wrrdev/fenix-ui/floatlabel",
15
- "fx-textarea": "@wrrdev/fenix-ui/textarea",
16
- "fx-dialog": "@wrrdev/fenix-ui/dialog",
17
- "fx-drawer": "@wrrdev/fenix-ui/drawer",
18
- "fx-toast": "@wrrdev/fenix-ui/toast",
19
- "fx-tooltip": "@wrrdev/fenix-ui/tooltip",
20
- "fx-tabs": "@wrrdev/fenix-ui/tabs",
21
- "fx-tab-panel": "@wrrdev/fenix-ui/tabs",
22
- "fx-progress": "@wrrdev/fenix-ui/progress",
23
- "fx-skeleton": "@wrrdev/fenix-ui/skeleton",
24
- "fx-alert": "@wrrdev/fenix-ui/alert",
25
- "fx-dropdown": "@wrrdev/fenix-ui/dropdown",
26
- "fx-dropdown-item": "@wrrdev/fenix-ui/dropdown",
27
- "fx-pagination": "@wrrdev/fenix-ui/pagination",
28
- "fx-autocomplete": "@wrrdev/fenix-ui/autocomplete"
29
- };
30
- const TAG_RE = /<(fx-[a-z][a-z-]*)(?=[\s/>])/g;
31
- function transformSource(code, options = {}) {
32
- const pkg = options.packageName ?? "@wrrdev/fenix-ui";
33
- const resolve = (sub) => pkg === "@wrrdev/fenix-ui" ? sub : sub.replace("@wrrdev/fenix-ui", pkg);
34
- const needed = /* @__PURE__ */ new Set();
35
- for (const m of code.matchAll(TAG_RE)) {
36
- const sub = fenixComponentMap[m[1]];
37
- if (sub) {
38
- const target = resolve(sub);
39
- if (!code.includes(`'${target}'`) && !code.includes(`"${target}"`)) {
40
- needed.add(target);
41
- }
42
- }
43
- }
44
- if (!needed.size) return code;
45
- const lines = [...needed].map((s) => `import '${s}';`);
46
- const vueScript = /(<script[^>]*>)\n/.exec(code);
47
- if (vueScript) {
48
- return code.replace(vueScript[0], `${vueScript[1]}
49
- ${lines.join("\n")}
50
- `);
51
- }
52
- let lastEnd = -1;
53
- for (const m of code.matchAll(/^[ \t]*import\b[^;]*?['"][^'"]+['"];?[^\S\n]*$/gm)) {
54
- lastEnd = Math.max(lastEnd, m.index + m[0].length);
55
- }
56
- if (lastEnd === -1) return `${lines.join("\n")}
57
- ${code}`;
58
- return `${code.slice(0, lastEnd)}
59
- ${lines.join("\n")}${code.slice(lastEnd)}`;
60
- }
61
- function shouldTransform(id) {
62
- return !id.includes("node_modules") && !id.endsWith(".d.ts") && !id.endsWith(".css") && /\.(ts|js|tsx|jsx|vue|html|svelte)$/.test(id);
63
- }
64
- function FenixAutoImport(options = {}) {
65
- return {
66
- name: "fenix-ui-auto-import",
67
- enforce: "pre",
68
- transform(code, id) {
69
- if (!shouldTransform(id)) return void 0;
70
- const result = transformSource(code, options);
71
- if (result === code) return void 0;
72
- return { code: result, map: null };
73
- }
74
- };
1
+ var __typeError = (msg) => {
2
+ throw TypeError(msg);
3
+ };
4
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
5
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
6
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
7
+ var _FxToast_instances, render_fn;
8
+ const lightTokens = {
9
+ color: {
10
+ primary: "#4f46e5",
11
+ secondary: "#8b5cf6",
12
+ success: "#10b981",
13
+ warning: "#f59e0b",
14
+ danger: "#f43f5e",
15
+ info: "#0ea5e9"
16
+ },
17
+ surface: {
18
+ background: "#ffffff",
19
+ surface: "#f8fafc",
20
+ "surface-hover": "#eef2f7"
21
+ },
22
+ text: {
23
+ default: "#0f172a",
24
+ muted: "#64748b",
25
+ disabled: "#94a3b8"
26
+ },
27
+ border: {
28
+ default: "#e2e8f0",
29
+ hover: "#cbd5e1"
30
+ },
31
+ font: {
32
+ family: "'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif",
33
+ size: "14px",
34
+ weight: "500",
35
+ "line-height": "1.5"
36
+ },
37
+ space: { xs: "4px", sm: "8px", md: "12px", lg: "16px", xl: "24px" },
38
+ radius: { none: "0", sm: "4px", md: "8px", lg: "12px", full: "9999px" },
39
+ size: { sm: "32px", md: "40px", lg: "48px" },
40
+ shadow: {
41
+ sm: "0 1px 2px rgba(15, 23, 42, 0.06)",
42
+ md: "0 4px 12px rgba(15, 23, 42, 0.10)",
43
+ lg: "0 12px 32px rgba(15, 23, 42, 0.18)"
44
+ },
45
+ motion: {
46
+ "duration-fast": "120ms",
47
+ "duration-normal": "240ms",
48
+ easing: "cubic-bezier(0.25, 0.1, 0.25, 1)"
49
+ },
50
+ effect: {
51
+ /** '0' desativa o efeito ripple do botão. */
52
+ ripple: "1",
53
+ /** Anel de foco dos campos de formulário. Use 'none' para campos sem sobra. */
54
+ "focus-ring": "0 0 0 3px color-mix(in srgb, var(--fx-color-primary) 22%, transparent)"
55
+ },
56
+ z: { base: "auto", dropdown: "1000", modal: "1100", toast: "1200" }
57
+ };
58
+ const darkTokens = {
59
+ color: { ...lightTokens.color },
60
+ surface: {
61
+ background: "#0f172a",
62
+ surface: "#1e293b",
63
+ "surface-hover": "#334155"
64
+ },
65
+ text: {
66
+ default: "#f8fafc",
67
+ muted: "#94a3b8",
68
+ disabled: "#64748b"
69
+ },
70
+ border: { default: "#334155", hover: "#475569" },
71
+ font: { ...lightTokens.font },
72
+ space: { ...lightTokens.space },
73
+ radius: { ...lightTokens.radius },
74
+ size: { ...lightTokens.size },
75
+ shadow: {
76
+ sm: "0 1px 2px rgba(0, 0, 0, 0.4)",
77
+ md: "0 6px 16px rgba(0, 0, 0, 0.5)",
78
+ lg: "0 16px 40px rgba(0, 0, 0, 0.6)"
79
+ },
80
+ motion: { ...lightTokens.motion },
81
+ effect: { ...lightTokens.effect },
82
+ z: { ...lightTokens.z }
83
+ };
84
+ const defaultTokens = lightTokens;
85
+ const PREFIX = "--fx";
86
+ const VERSION = "0.1.0";
87
+ let activeTheme = "light";
88
+ let activeOverrides = null;
89
+ function isPlainObject(value) {
90
+ return typeof value === "object" && value !== null && !Array.isArray(value);
91
+ }
92
+ function deepMerge(base, override) {
93
+ if (!override) return base;
94
+ const out = { ...base };
95
+ for (const key2 of Object.keys(override)) {
96
+ const baseValue = base[key2];
97
+ const overrideValue = override[key2];
98
+ out[key2] = isPlainObject(baseValue) && isPlainObject(overrideValue) ? deepMerge(baseValue, overrideValue) : overrideValue;
99
+ }
100
+ return out;
101
+ }
102
+ function tokenCssVars(tokens) {
103
+ const out = {};
104
+ for (const [group, values] of Object.entries(tokens)) {
105
+ for (const [key2, value] of Object.entries(values)) {
106
+ out[`${PREFIX}-${group}-${key2}`] = String(value);
107
+ }
108
+ }
109
+ return out;
110
+ }
111
+ function applyTokens(themeName, overrides, target = document.documentElement) {
112
+ const base = themeName === "dark" ? darkTokens : defaultTokens;
113
+ const finalTokens = deepMerge(base, overrides);
114
+ const vars = tokenCssVars(finalTokens);
115
+ for (const [name, value] of Object.entries(vars)) {
116
+ target.style.setProperty(name, value);
117
+ }
118
+ if (typeof window !== "undefined") {
119
+ window.dispatchEvent(new CustomEvent("fenix:theme", { detail: { theme: themeName, tokens: finalTokens } }));
120
+ }
121
+ }
122
+ function configure(options = {}, target = document.documentElement) {
123
+ if (options.theme) activeTheme = options.theme;
124
+ if (options.tokens) activeOverrides = options.tokens;
125
+ applyTokens(activeTheme, activeOverrides, target);
126
+ return { theme: activeTheme };
127
+ }
128
+ function theme(name) {
129
+ return configure({ theme: name });
130
+ }
131
+ function setTokens(tokens) {
132
+ return configure({ tokens });
133
+ }
134
+ function resetTheme() {
135
+ activeTheme = "light";
136
+ activeOverrides = null;
137
+ return configure({ theme: "light" });
138
+ }
139
+ const FenixUI = {
140
+ version: VERSION,
141
+ configure,
142
+ theme,
143
+ setTokens,
144
+ resetTheme,
145
+ tokenCssVars,
146
+ deepMerge
147
+ };
148
+ const registry = /* @__PURE__ */ new Map();
149
+ function register(preset) {
150
+ registry.set(preset.name, preset);
151
+ return preset;
152
+ }
153
+ const themePresets = {
154
+ fenix: register({
155
+ name: "fenix",
156
+ label: "Fenix (padrão)",
157
+ tokens: {}
158
+ }),
159
+ seiya: register({
160
+ name: "seiya",
161
+ label: "Seiya",
162
+ tokens: {
163
+ color: { primary: "#e11d48", secondary: "#fb7185", info: "#f43f5e", danger: "#be123c" },
164
+ radius: { sm: "6px", md: "10px", lg: "16px" }
165
+ }
166
+ }),
167
+ shiryu: register({
168
+ name: "shiryu",
169
+ label: "Shiryu",
170
+ tokens: {
171
+ color: { primary: "#0d9488", secondary: "#2dd4bf", success: "#059669", info: "#14b8a6" },
172
+ radius: { sm: "4px", md: "8px", lg: "14px" }
173
+ }
174
+ }),
175
+ hyoga: register({
176
+ name: "hyoga",
177
+ label: "Hyoga",
178
+ tokens: {
179
+ color: { primary: "#0284c7", secondary: "#38bdf8", info: "#06b6d4", danger: "#e11d48" },
180
+ surface: { surface: "#f0f9ff", "surface-hover": "#e0f2fe" },
181
+ radius: { sm: "8px", md: "12px", lg: "18px" }
182
+ }
183
+ }),
184
+ shun: register({
185
+ name: "shun",
186
+ label: "Shun",
187
+ tokens: {
188
+ color: { primary: "#db2777", secondary: "#f472b6", info: "#ec4899", warning: "#c026d3" },
189
+ radius: { sm: "10px", md: "14px", lg: "20px" }
190
+ }
191
+ }),
192
+ ikki: register({
193
+ name: "ikki",
194
+ label: "Ikki",
195
+ tokens: {
196
+ color: { primary: "#ea580c", secondary: "#fb923c", danger: "#dc2626", warning: "#f59e0b" },
197
+ radius: { sm: "2px", md: "4px", lg: "6px" }
198
+ }
199
+ }),
200
+ aiolia: register({
201
+ name: "aiolia",
202
+ label: "Aiolia",
203
+ tokens: {
204
+ color: { primary: "#b45309", secondary: "#f59e0b", warning: "#eab308", info: "#84cc16" },
205
+ radius: { sm: "6px", md: "12px", lg: "20px" }
206
+ }
207
+ })
208
+ };
209
+ function listPresets() {
210
+ return [...registry.values()];
211
+ }
212
+ function defineCustomPreset(name, label, tokens) {
213
+ return register({ name, label, tokens });
214
+ }
215
+ function applyPreset(presetName, mode = "light") {
216
+ const preset = registry.get(presetName) ?? registry.get("fenix");
217
+ const state = configure({ theme: mode, tokens: preset.tokens });
218
+ return { ...state, preset: preset.name };
219
+ }
220
+ function css(strings, ...values) {
221
+ return strings.reduce((acc, part, i) => acc + part + (i < values.length ? String(values[i]) : ""), "");
222
+ }
223
+ function kebabToCamel(value) {
224
+ return value.replace(/-+([a-z])/g, (_, c) => c.toUpperCase());
225
+ }
226
+ const _FxElement = class _FxElement extends HTMLElement {
227
+ static get observedAttributes() {
228
+ return [];
229
+ }
230
+ constructor() {
231
+ super();
232
+ this.root = this.attachShadow({ mode: "open" });
233
+ }
234
+ /** Chamado quando um atributo observado muda. */
235
+ attributeChangedCallback() {
236
+ if (this.isConnected) this.render();
237
+ }
238
+ /** Chamado quando o elemento é anexado ao DOM. */
239
+ connectedCallback() {
240
+ this.render();
241
+ }
242
+ /** Chamado quando o elemento é removido do DOM (limpeza de recursos). */
243
+ disconnectedCallback() {
244
+ }
245
+ hasAttr(name) {
246
+ return this.hasAttribute(name);
247
+ }
248
+ getAttr(name, fallback = "") {
249
+ return this.getAttribute(name) ?? fallback;
250
+ }
251
+ toggleAttr(name, on) {
252
+ if (on) this.setAttribute(name, "");
253
+ else this.removeAttribute(name);
254
+ }
255
+ /** Injeta `<style>` + template no Shadow DOM. */
256
+ setTemplate(html) {
257
+ const styles = this.constructor.styles;
258
+ this.root.innerHTML = `<style>${styles}</style>${html}`;
259
+ }
260
+ };
261
+ _FxElement.styles = "";
262
+ let FxElement = _FxElement;
263
+ function defineElement(tag, ctor) {
264
+ if (typeof customElements !== "undefined" && !customElements.get(tag)) {
265
+ customElements.define(tag, ctor);
266
+ }
267
+ return customElements.get(tag) ?? ctor;
268
+ }
269
+ const FX_JSX_TYPES = true;
270
+ const _FxButton = class _FxButton extends FxElement {
271
+ constructor() {
272
+ super(...arguments);
273
+ this.spawnRipple = (event) => {
274
+ if (this.hasAttr("disabled") || this.hasAttr("loading")) return;
275
+ const enabled = getComputedStyle(this).getPropertyValue("--fx-effect-ripple").trim();
276
+ if (enabled === "0" || enabled === "false") return;
277
+ const btn = this.root.querySelector(".btn");
278
+ if (!btn) return;
279
+ const rect = btn.getBoundingClientRect();
280
+ const size = Math.max(rect.width, rect.height);
281
+ const ripple = document.createElement("span");
282
+ ripple.className = "btn__ripple";
283
+ ripple.style.width = ripple.style.height = `${size}px`;
284
+ ripple.style.left = `${event.clientX - rect.left - size / 2}px`;
285
+ ripple.style.top = `${event.clientY - rect.top - size / 2}px`;
286
+ btn.appendChild(ripple);
287
+ ripple.addEventListener("animationend", () => ripple.remove());
288
+ };
289
+ }
290
+ static get observedAttributes() {
291
+ return ["variant", "size", "disabled", "loading", "type", "full"];
292
+ }
293
+ /** Tamanho. Padrão: `'md'`. */
294
+ get size() {
295
+ const s = this.getAttr("size", "md");
296
+ return s === "sm" || s === "lg" ? s : "md";
297
+ }
298
+ set size(value) {
299
+ this.setAttribute("size", value);
300
+ }
301
+ get disabled() {
302
+ return this.hasAttr("disabled");
303
+ }
304
+ set disabled(value) {
305
+ this.toggleAttr("disabled", Boolean(value));
306
+ }
307
+ get loading() {
308
+ return this.hasAttr("loading");
309
+ }
310
+ set loading(value) {
311
+ this.toggleAttr("loading", Boolean(value));
312
+ }
313
+ render() {
314
+ const loading = this.hasAttr("loading");
315
+ const disabled = this.hasAttr("disabled") || loading;
316
+ const btnType = this.getAttr("type", "button");
317
+ this.setTemplate(`
318
+ <button class="btn" part="button" type="${btnType}">
319
+ <span class="btn__icon" part="icon"><slot name="icon"></slot></span>
320
+ <span class="btn__label" part="label"><slot></slot></span>
321
+ ${loading ? '<span class="btn__spinner" aria-hidden="true"></span>' : ""}
322
+ </button>
323
+ `);
324
+ const btn = this.root.querySelector(".btn");
325
+ if (!btn) return;
326
+ const iconSlot = btn.querySelector(".btn__icon slot");
327
+ const iconWrap = this.root.querySelector(".btn__icon");
328
+ const hasIcon = Boolean(iconSlot?.assignedNodes({ flatten: true }).some(
329
+ (n) => n.nodeType === Node.ELEMENT_NODE || (n.textContent ?? "").trim() !== ""
330
+ ));
331
+ iconWrap?.toggleAttribute("hidden", !hasIcon);
332
+ if (disabled) {
333
+ btn.setAttribute("disabled", "");
334
+ btn.setAttribute("aria-disabled", "true");
335
+ } else {
336
+ btn.removeAttribute("disabled");
337
+ btn.removeAttribute("aria-disabled");
338
+ }
339
+ if (loading) btn.setAttribute("aria-busy", "true");
340
+ else btn.removeAttribute("aria-busy");
341
+ }
342
+ connectedCallback() {
343
+ super.connectedCallback();
344
+ this.addEventListener("pointerdown", this.spawnRipple);
345
+ }
346
+ disconnectedCallback() {
347
+ super.disconnectedCallback();
348
+ this.removeEventListener("pointerdown", this.spawnRipple);
349
+ }
350
+ };
351
+ _FxButton.styles = css`
352
+ :host {
353
+ display: inline-block;
354
+ vertical-align: middle;
355
+ }
356
+ .btn {
357
+ display: inline-flex;
358
+ align-items: center;
359
+ justify-content: center;
360
+ text-align: center;
361
+ gap: var(--fx-space-sm);
362
+ font-family: var(--fx-font-family);
363
+ font-size: var(--fx-font-size);
364
+ font-weight: var(--fx-font-weight);
365
+ line-height: var(--fx-font-line-height);
366
+ padding: var(--fx-space-md) var(--fx-space-xl);
367
+ border-radius: var(--fx-radius-md);
368
+ border: 1px solid transparent;
369
+ color: #fff;
370
+ background: var(--fx-color-primary);
371
+ cursor: pointer;
372
+ user-select: none;
373
+ text-decoration: none;
374
+ transition:
375
+ background-color var(--fx-motion-duration-normal) var(--fx-motion-easing),
376
+ border-color var(--fx-motion-duration-normal) var(--fx-motion-easing),
377
+ transform var(--fx-motion-duration-fast) var(--fx-motion-easing),
378
+ box-shadow var(--fx-motion-duration-normal) var(--fx-motion-easing);
379
+ }
380
+ .btn:focus-visible {
381
+ outline: 2px solid var(--fx-color-primary);
382
+ outline-offset: 2px;
383
+ }
384
+ .btn:hover { filter: brightness(0.88); }
385
+ .btn:active { transform: translateY(1px); }
386
+
387
+ /* Tamanhos — alturas vindas dos tokens (personalizáveis por preset) */
388
+ .btn { min-height: var(--fx-size-md); }
389
+ :host([size='sm']) .btn { padding: var(--fx-space-sm) var(--fx-space-lg); font-size: var(--fx-font-size); min-height: var(--fx-size-sm); }
390
+ :host([size='lg']) .btn { padding: var(--fx-space-lg) var(--fx-space-xl); font-size: calc(var(--fx-font-size) + 4px); min-height: var(--fx-size-lg); }
391
+
392
+ /* Full width */
393
+ :host([full]) .btn { width: 100%; }
394
+
395
+ /* Variantes */
396
+ :host([variant='secondary']) .btn { background: var(--fx-color-secondary); }
397
+ :host([variant='success']) .btn { background: var(--fx-color-success); }
398
+ :host([variant='warning']) .btn { background: var(--fx-color-warning); }
399
+ :host([variant='danger']) .btn { background: var(--fx-color-danger); }
400
+ :host([variant='ghost']) .btn { background: transparent; color: var(--fx-text-default); }
401
+ :host([variant='ghost']) .btn:hover { background: var(--fx-surface-surface-hover); filter: none; }
402
+ :host([variant='outline']) .btn { background: transparent; color: var(--fx-color-primary); border-color: var(--fx-color-primary); }
403
+ :host([variant='outline']) .btn:hover { background: var(--fx-surface-surface-hover); filter: none; }
404
+
405
+ /* Ícone e spinner */
406
+ .btn__icon { display: inline-flex; }
407
+ .btn__icon[hidden] { display: none; }
408
+ .btn__label { display: inline-flex; align-items: center; justify-content: center; }
409
+ .btn[hidden] { display: none; }
410
+ .btn__spinner {
411
+ width: 1em;
412
+ height: 1em;
413
+ border: 2px solid currentColor;
414
+ border-top-color: transparent;
415
+ border-radius: var(--fx-radius-full);
416
+ animation: fx-spin var(--fx-motion-duration-normal) linear infinite;
417
+ }
418
+ @keyframes fx-spin { to { transform: rotate(360deg); } }
419
+
420
+ /* Estados */
421
+ :host([disabled]) .btn,
422
+ :host([loading]) .btn {
423
+ opacity: 0.55;
424
+ cursor: not-allowed;
425
+ pointer-events: none;
426
+ }
427
+
428
+ /* Efeito ripple (). Desative com effect.ripple: '0' no preset. */
429
+ .btn {
430
+ position: relative;
431
+ overflow: hidden;
432
+ }
433
+ .btn__ripple {
434
+ position: absolute;
435
+ border-radius: var(--fx-radius-full);
436
+ background: currentColor;
437
+ opacity: 0.28;
438
+ transform: scale(0);
439
+ animation: fx-ripple 600ms var(--fx-motion-easing) forwards;
440
+ pointer-events: none;
441
+ }
442
+ @keyframes fx-ripple {
443
+ to {
444
+ transform: scale(2.6);
445
+ opacity: 0;
446
+ }
447
+ }
448
+ @media (prefers-reduced-motion: reduce) {
449
+ .btn__ripple { display: none; }
450
+ }
451
+ `;
452
+ let FxButton = _FxButton;
453
+ function defineFxButton() {
454
+ return defineElement("fx-button", FxButton);
455
+ }
456
+ defineFxButton();
457
+ const _FxBadge = class _FxBadge extends FxElement {
458
+ static get observedAttributes() {
459
+ return ["variant", "round"];
460
+ }
461
+ render() {
462
+ this.setTemplate(`<span class="badge" part="badge"><slot></slot></span>`);
463
+ }
464
+ };
465
+ _FxBadge.styles = css`
466
+ :host {
467
+ display: inline-flex;
468
+ vertical-align: middle;
469
+ }
470
+ .badge {
471
+ display: inline-flex;
472
+ align-items: center;
473
+ font-family: var(--fx-font-family);
474
+ font-size: calc(var(--fx-font-size) - 2px);
475
+ font-weight: var(--fx-font-weight);
476
+ line-height: 1;
477
+ padding: var(--fx-space-xs) var(--fx-space-sm);
478
+ border-radius: var(--fx-radius-full);
479
+ border: 1px solid var(--fx-border-default);
480
+ background: var(--fx-surface-background);
481
+ color: var(--fx-text-muted);
482
+ white-space: nowrap;
483
+ }
484
+ :host([variant='primary']) .badge { background: var(--fx-color-primary); color: #fff; border-color: transparent; }
485
+ :host([variant='success']) .badge { background: var(--fx-color-success); color: #fff; border-color: transparent; }
486
+ :host([variant='warning']) .badge { background: var(--fx-color-warning); color: #fff; border-color: transparent; }
487
+ :host([variant='danger']) .badge { background: var(--fx-color-danger); color: #fff; border-color: transparent; }
488
+ :host([variant='info']) .badge { background: var(--fx-color-info); color: #fff; border-color: transparent; }
489
+ :host(:not([round])) .badge { border-radius: var(--fx-radius-sm); }
490
+ `;
491
+ let FxBadge = _FxBadge;
492
+ function defineFxBadge() {
493
+ return defineElement("fx-badge", FxBadge);
494
+ }
495
+ defineFxBadge();
496
+ const _FxSpinner = class _FxSpinner extends FxElement {
497
+ static get observedAttributes() {
498
+ return ["size"];
499
+ }
500
+ /** Tamanho do spinner. Padrão: `'md'`. */
501
+ get size() {
502
+ const s = this.getAttr("size", "md");
503
+ return s === "sm" || s === "lg" ? s : "md";
504
+ }
505
+ set size(value) {
506
+ this.setAttribute("size", value);
507
+ }
508
+ render() {
509
+ this.setTemplate(`<span class="spinner" part="spinner" role="status" aria-label="Carregando"></span>`);
510
+ }
511
+ };
512
+ _FxSpinner.styles = css`
513
+ :host {
514
+ display: inline-block;
515
+ vertical-align: middle;
516
+ }
517
+ /* Escala: sm 16px · md 24px (padrão) · lg 32px */
518
+ .spinner {
519
+ display: inline-block;
520
+ width: var(--fx-space-xl, 20px);
521
+ height: var(--fx-space-xl, 20px);
522
+ border: 2px solid var(--fx-border-default, #e2e8f0);
523
+ border-top-color: var(--fx-color-primary, #2563eb);
524
+ border-radius: var(--fx-radius-full, 9999px);
525
+ animation: fx-spin 0.8s linear infinite;
526
+ }
527
+ :host([size='sm']) .spinner { width: var(--fx-space-lg, 16px); height: var(--fx-space-lg, 16px); }
528
+ :host([size='lg']) .spinner { width: 32px; height: 32px; border-width: 3px; }
529
+ @keyframes fx-spin { to { transform: rotate(360deg); } }
530
+ @media (prefers-reduced-motion: reduce) {
531
+ .spinner { animation-duration: 2.5s; }
532
+ }
533
+ `;
534
+ let FxSpinner = _FxSpinner;
535
+ function defineFxSpinner() {
536
+ return defineElement("fx-spinner", FxSpinner);
537
+ }
538
+ defineFxSpinner();
539
+ const _FxSelect = class _FxSelect extends FxElement {
540
+ // `value` fica FORA da observação: refleti-lo no change não pode
541
+ // re-renderizar o template e fechar o dropdown.
542
+ static get observedAttributes() {
543
+ return ["size", "disabled", "placeholder", "searchable", "clearable", "error", "success"];
544
+ }
545
+ /** Tamanho do campo. Padrão: `'md'`. */
546
+ get size() {
547
+ const s = this.getAttr("size", "md");
548
+ return s === "sm" || s === "lg" ? s : "md";
549
+ }
550
+ set size(value) {
551
+ this.setAttribute("size", value);
552
+ }
553
+ get value() {
554
+ return this.getAttr("value");
555
+ }
556
+ set value(value) {
557
+ this.setAttribute("value", value);
558
+ }
559
+ get disabled() {
560
+ return this.hasAttr("disabled");
561
+ }
562
+ set disabled(value) {
563
+ this.toggleAttr("disabled", Boolean(value));
564
+ }
565
+ get options() {
566
+ return [...this.querySelectorAll("option")].map((o) => ({
567
+ value: o.getAttribute("value") ?? o.textContent?.trim() ?? "",
568
+ label: o.textContent?.trim() ?? ""
569
+ }));
570
+ }
571
+ connectedCallback() {
572
+ super.connectedCallback();
573
+ this.observer = new MutationObserver(() => this.render());
574
+ this.observer.observe(this, { childList: true, subtree: true, characterData: true });
575
+ this.docListener = (e) => {
576
+ if (!this.hasAttr("open")) return;
577
+ if (e.composedPath().includes(this)) return;
578
+ this.removeAttribute("open");
579
+ this.render();
580
+ };
581
+ document.addEventListener("click", this.docListener);
582
+ }
583
+ disconnectedCallback() {
584
+ super.disconnectedCallback();
585
+ this.observer?.disconnect();
586
+ if (this.docListener) document.removeEventListener("click", this.docListener);
587
+ }
588
+ select(value) {
589
+ this.value = value;
590
+ this.removeAttribute("open");
591
+ this.render();
592
+ this.dispatchEvent(
593
+ new CustomEvent("change", { bubbles: true, composed: true, detail: { value } })
594
+ );
595
+ }
596
+ render() {
597
+ const prevOpen = this.hasAttr("open");
598
+ const prevSearch = this.root.querySelector(".search");
599
+ const search = prevSearch?.value ?? "";
600
+ const searchFocused = prevSearch != null && this.root.activeElement === prevSearch;
601
+ const caretPos = searchFocused ? prevSearch.selectionStart : null;
602
+ const opts = this.options;
603
+ let current = this.getAttr("value");
604
+ if (!current) {
605
+ const explicit = this.querySelector("option[selected]");
606
+ current = explicit?.getAttribute("value") ?? explicit?.textContent?.trim() ?? "";
607
+ }
608
+ const selectedLabel = opts.find((o) => o.value === current)?.label ?? "";
609
+ const placeholder = this.getAttr("placeholder", "Selecione…");
610
+ const filtered = search ? opts.filter((o) => o.label.toLowerCase().includes(search.toLowerCase())) : opts;
611
+ this.setTemplate(`
612
+ <span class="trigger" part="trigger" role="button" tabindex="${this.disabled ? -1 : 0}" aria-haspopup="listbox" aria-expanded="${prevOpen}">
613
+ <span class="${selectedLabel ? "label" : "placeholder"}">${selectedLabel || placeholder}</span>
614
+ <span class="actions">
615
+ ${this.hasAttr("clearable") && current ? '<button type="button" class="clear" part="clear" aria-label="Limpar">×</button>' : ""}
616
+ <span class="caret">▼</span>
617
+ </span>
618
+ </span>
619
+ <div class="panel" part="panel" role="listbox">
620
+ ${this.hasAttr("searchable") ? `<input class="search" part="search" type="text" placeholder="${this.getAttr("search-placeholder", "Pesquisar…")}">` : ""}
621
+ ${filtered.map((o) => `
622
+ <button type="button" class="opt" role="option" data-value="${o.value}"
623
+ aria-selected="${o.value === current}">${o.label}</button>`).join("")}
624
+ ${filtered.length === 0 ? `<div class="empty">${this.getAttr("no-results", "Nenhum resultado")}</div>` : ""}
625
+ </div>
626
+ `);
627
+ if (prevOpen) this.setAttribute("open", "");
628
+ const searchInput = this.root.querySelector(".search");
629
+ if (searchInput) {
630
+ searchInput.value = search;
631
+ searchInput.addEventListener("input", () => this.render());
632
+ searchInput.addEventListener("click", (e) => e.stopPropagation());
633
+ if (searchFocused) {
634
+ searchInput.focus();
635
+ try {
636
+ searchInput.setSelectionRange(caretPos, caretPos);
637
+ } catch {
638
+ }
639
+ }
640
+ }
641
+ const trigger = this.root.querySelector(".trigger");
642
+ if (!trigger) return;
643
+ if (this.disabled) trigger.setAttribute("aria-disabled", "true");
644
+ trigger.addEventListener("click", (e) => {
645
+ if (this.disabled) return;
646
+ if (e.target.closest(".clear")) return;
647
+ this.toggleAttribute("open");
648
+ this.render();
649
+ this.root.querySelector(".search")?.focus();
650
+ });
651
+ trigger.addEventListener("keydown", (e) => {
652
+ if (e.key === "Enter" || e.key === " ") {
653
+ e.preventDefault();
654
+ trigger.click();
655
+ }
656
+ });
657
+ this.root.querySelector(".clear")?.addEventListener("click", (e) => {
658
+ e.stopPropagation();
659
+ this.value = "";
660
+ this.removeAttribute("open");
661
+ this.render();
662
+ this.dispatchEvent(
663
+ new CustomEvent("change", { bubbles: true, composed: true, detail: { value: "" } })
664
+ );
665
+ });
666
+ this.root.querySelectorAll(".opt").forEach((opt) => {
667
+ opt.addEventListener("click", () => this.select(opt.dataset.value));
668
+ });
669
+ }
670
+ };
671
+ _FxSelect.styles = css`
672
+ :host {
673
+ display: inline-block;
674
+ vertical-align: middle;
675
+ font-family: var(--fx-font-family);
676
+ font-size: var(--fx-font-size);
677
+ position: relative;
678
+ }
679
+ .trigger {
680
+ display: inline-flex;
681
+ align-items: center;
682
+ justify-content: space-between;
683
+ gap: var(--fx-space-sm);
684
+ box-sizing: border-box;
685
+ min-height: var(--fx-size-md);
686
+ min-width: 200px;
687
+ font: inherit;
688
+ font-weight: var(--fx-font-weight);
689
+ color: var(--fx-text-default);
690
+ text-align: left;
691
+ background-color: var(--fx-surface-background);
692
+ border: 1px solid var(--fx-border-default);
693
+ border-radius: var(--fx-radius-md);
694
+ padding: var(--fx-space-xs) var(--fx-space-md);
695
+ cursor: pointer;
696
+ transition:
697
+ border-color var(--fx-motion-duration-normal) var(--fx-motion-easing),
698
+ box-shadow var(--fx-motion-duration-normal) var(--fx-motion-easing);
699
+ }
700
+ .trigger:hover { border-color: var(--fx-border-hover); }
701
+ .trigger:focus-visible,
702
+ :host([open]) .trigger {
703
+ outline: none;
704
+ border-color: var(--fx-color-primary);
705
+ box-shadow: var(--fx-effect-focus-ring, none);
706
+ }
707
+ :host([size='sm']) .trigger { min-width: 180px; min-height: var(--fx-size-sm); }
708
+ :host([size='lg']) .trigger { min-height: var(--fx-size-lg); font-size: calc(var(--fx-font-size) + 4px); }
709
+ /* Validação */
710
+ :host([error]) .trigger,
711
+ :host([invalid]) .trigger {
712
+ border-color: var(--fx-color-danger, #dc2626);
713
+ }
714
+ :host([error]) .trigger:focus-visible,
715
+ :host([invalid]) .trigger:focus-visible,
716
+ :host([error][open]) .trigger,
717
+ :host([invalid][open]) .trigger {
718
+ border-color: var(--fx-color-danger, #dc2626);
719
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--fx-color-danger, #dc2626) 18%, transparent);
720
+ }
721
+ :host([success]) .trigger,
722
+ :host([valid]) .trigger {
723
+ border-color: var(--fx-color-success, #16a34a);
724
+ }
725
+ :host([success]) .trigger:focus-visible,
726
+ :host([valid]) .trigger:focus-visible,
727
+ :host([success][open]) .trigger,
728
+ :host([valid][open]) .trigger {
729
+ border-color: var(--fx-color-success, #16a34a);
730
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--fx-color-success, #16a34a) 18%, transparent);
731
+ }
732
+ :host([disabled]) .trigger,
733
+ .trigger[aria-disabled='true'] { opacity: 0.55; cursor: not-allowed; background-color: var(--fx-surface-surface-hover); }
734
+ /* O rótulo encolhe (ellipsis) e os ícones NUNCA saem do campo. */
735
+ .label,
736
+ .placeholder {
737
+ flex: 1 1 auto;
738
+ min-width: 0;
739
+ overflow: hidden;
740
+ text-overflow: ellipsis;
741
+ white-space: nowrap;
742
+ }
743
+ .placeholder { color: var(--fx-text-muted); }
744
+ .actions {
745
+ display: inline-flex;
746
+ align-items: center;
747
+ gap: var(--fx-space-xs, 8px);
748
+ flex-shrink: 0;
749
+ }
750
+ .caret { font-size: calc(var(--fx-font-size) - 3px); color: var(--fx-text-muted); pointer-events: none; }
751
+
752
+ /* Painel do dropdown */
753
+ .panel {
754
+ position: absolute;
755
+ top: calc(100% + 4px);
756
+ left: 0;
757
+ z-index: var(--fx-z-dropdown, 1000);
758
+ width: max(100%, 220px);
759
+ max-height: 260px;
760
+ overflow-y: auto;
761
+ display: none;
762
+ background: var(--fx-surface-background);
763
+ border: 1px solid var(--fx-border-default);
764
+ border-radius: var(--fx-radius-md);
765
+ box-shadow: var(--fx-shadow-lg);
766
+ }
767
+ :host([open]) .panel { display: block; }
768
+ .search {
769
+ position: sticky;
770
+ top: 0;
771
+ width: 100%;
772
+ box-sizing: border-box;
773
+ font: inherit;
774
+ color: var(--fx-text-default);
775
+ background: var(--fx-surface-background);
776
+ border: none;
777
+ border-bottom: 1px solid var(--fx-border-default);
778
+ padding: var(--fx-space-sm) var(--fx-space-md);
779
+ outline: none;
780
+ }
781
+ .opt {
782
+ display: block;
783
+ width: 100%;
784
+ text-align: left;
785
+ font: inherit;
786
+ color: var(--fx-text-default);
787
+ background: none;
788
+ border: none;
789
+ padding: var(--fx-space-sm) var(--fx-space-md);
790
+ cursor: pointer;
791
+ }
792
+ /* Hover e selecionado com a cor primária do tema (igual ao multiselect). */
793
+ .opt:hover { background: color-mix(in srgb, var(--fx-color-primary) 12%, transparent); }
794
+ .opt[aria-selected='true'] {
795
+ background: color-mix(in srgb, var(--fx-color-primary) 18%, transparent);
796
+ color: var(--fx-color-primary);
797
+ font-weight: var(--fx-font-weight);
798
+ }
799
+ .empty { padding: var(--fx-space-sm) var(--fx-space-md); color: var(--fx-text-muted); }
800
+
801
+ /* Clearable */
802
+ .clear {
803
+ border: none;
804
+ background: transparent;
805
+ color: var(--fx-text-muted);
806
+ font-size: calc(var(--fx-font-size) + 2px);
807
+ line-height: 1;
808
+ cursor: pointer;
809
+ padding: 0;
810
+ }
811
+ .clear:hover { color: var(--fx-color-danger); }
812
+ .clear[hidden] { display: none; }
813
+ `;
814
+ let FxSelect = _FxSelect;
815
+ function defineFxSelect() {
816
+ return defineElement("fx-select", FxSelect);
817
+ }
818
+ defineFxSelect();
819
+ const _FxInput = class _FxInput extends FxElement {
820
+ // `value` fica FORA da observação: refleti-lo a cada tecla não pode
821
+ // re-renderizar o template, senão o campo perde o foco ao digitar.
822
+ static get observedAttributes() {
823
+ return [
824
+ "type",
825
+ "size",
826
+ "placeholder",
827
+ "disabled",
828
+ "readonly",
829
+ "min",
830
+ "max",
831
+ "step"
832
+ ];
833
+ }
834
+ /** Tamanho do campo. Padrão: `'md'`. */
835
+ get size() {
836
+ const s = this.getAttr("size", "md");
837
+ return s === "sm" || s === "lg" ? s : "md";
838
+ }
839
+ set size(value) {
840
+ this.setAttribute("size", value);
841
+ }
842
+ get value() {
843
+ return this.getAttr("value");
844
+ }
845
+ set value(value) {
846
+ this.setAttribute("value", value);
847
+ }
848
+ get disabled() {
849
+ return this.hasAttr("disabled");
850
+ }
851
+ set disabled(value) {
852
+ this.toggleAttr("disabled", Boolean(value));
853
+ }
854
+ render() {
855
+ const type = this.getAttr("type", "text");
856
+ const placeholder = this.getAttr("placeholder");
857
+ const readonly = this.hasAttr("readonly");
858
+ const disabled = this.hasAttr("disabled");
859
+ const min = this.getAttr("min");
860
+ const max = this.getAttr("max");
861
+ const step = this.getAttr("step");
862
+ this.setTemplate(`
863
+ ${this.hasAttr("clearable") ? '<div class="wrap">' : ""}
864
+ <input class="field" part="input" type="${type}"
865
+ ${placeholder ? `placeholder="${placeholder}"` : ""}
866
+ ${min ? `min="${min}"` : ""} ${max ? `max="${max}"` : ""} ${step ? `step="${step}"` : ""}
867
+ />
868
+ ${this.hasAttr("clearable") ? '<button type="button" class="clear" part="clear" aria-label="Limpar" tabindex="-1">×</button></div>' : ""}
869
+ `);
870
+ const field = this.root.querySelector(".field");
871
+ if (!field) return;
872
+ field.value = this.getAttr("value");
873
+ if (disabled) field.setAttribute("disabled", "");
874
+ if (readonly) field.setAttribute("readonly", "");
875
+ const emit = (event) => {
876
+ this.value = field.value;
877
+ this.dispatchEvent(
878
+ new CustomEvent(event, { bubbles: true, composed: true, detail: { value: field.value } })
879
+ );
880
+ };
881
+ field.addEventListener("input", (e) => {
882
+ e.stopPropagation();
883
+ emit("input");
884
+ });
885
+ field.addEventListener("change", (e) => {
886
+ e.stopPropagation();
887
+ emit("change");
888
+ });
889
+ const clearBtn = this.root.querySelector(".clear");
890
+ const syncClear = () => {
891
+ clearBtn?.toggleAttribute("hidden", field.value === "");
892
+ };
893
+ syncClear();
894
+ clearBtn?.addEventListener("click", () => {
895
+ field.value = "";
896
+ syncClear();
897
+ emit("input");
898
+ emit("change");
899
+ field.focus();
900
+ });
901
+ field.addEventListener("input", syncClear);
902
+ }
903
+ };
904
+ _FxInput.styles = css`
905
+ :host {
906
+ display: inline-block;
907
+ vertical-align: middle;
908
+ font-family: var(--fx-font-family);
909
+ font-size: var(--fx-font-size);
910
+ }
911
+ .field {
912
+ font-family: inherit;
913
+ font-size: inherit;
914
+ min-height: var(--fx-size-md);
915
+ font-weight: var(--fx-font-weight);
916
+ color: var(--fx-text-default);
917
+ background-color: var(--fx-surface-background);
918
+ border: 1px solid var(--fx-border-default);
919
+ border-radius: var(--fx-radius-md);
920
+ padding: var(--fx-space-md) var(--fx-space-lg);
921
+ width: 260px;
922
+ box-sizing: border-box;
923
+ transition:
924
+ border-color var(--fx-motion-duration-normal) var(--fx-motion-easing),
925
+ box-shadow var(--fx-motion-duration-normal) var(--fx-motion-easing);
926
+ }
927
+ .field::placeholder { color: var(--fx-text-muted); opacity: 1; }
928
+ .field:hover { border-color: var(--fx-border-hover); }
929
+ .field:focus-visible {
930
+ outline: none;
931
+ border-color: var(--fx-color-primary);
932
+ box-shadow: var(--fx-effect-focus-ring, none);
933
+ }
934
+ .field:disabled,
935
+ .field[readonly] {
936
+ opacity: 0.55;
937
+ cursor: not-allowed;
938
+ background-color: var(--fx-surface-surface-hover);
939
+ }
940
+ /* Validação: sobrescrevem a borda/foco via token do preset. */
941
+ :host([error]) .field,
942
+ :host([invalid]) .field,
943
+ :host([error]) .field:focus-visible,
944
+ :host([invalid]) .field:focus-visible {
945
+ border-color: var(--fx-color-danger, #dc2626);
946
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--fx-color-danger, #dc2626) 18%, transparent);
947
+ }
948
+ :host([success]) .field,
949
+ :host([valid]) .field,
950
+ :host([success]) .field:focus-visible,
951
+ :host([valid]) .field:focus-visible {
952
+ border-color: var(--fx-color-success, #16a34a);
953
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--fx-color-success, #16a34a) 18%, transparent);
954
+ }
955
+ :host([size='sm']) .field { padding: var(--fx-space-sm) var(--fx-space-md); font-size: var(--fx-font-size); width: 220px; min-height: var(--fx-size-sm); }
956
+ :host([size='lg']) .field { padding: var(--fx-space-lg) var(--fx-space-xl); font-size: calc(var(--fx-font-size) + 4px); width: 300px; min-height: var(--fx-size-lg); }
957
+ /* Clearable */
958
+ :host([clearable]) { position: relative; display: inline-flex; }
959
+ .wrap { position: relative; display: inline-flex; align-items: center; }
960
+ :host([clearable]) .field { padding-right: var(--fx-space-xl); }
961
+ .clear {
962
+ position: absolute;
963
+ right: var(--fx-space-xs);
964
+ border: none;
965
+ background: transparent;
966
+ color: var(--fx-text-muted);
967
+ font-size: calc(var(--fx-font-size) + 4px);
968
+ line-height: 1;
969
+ cursor: pointer;
970
+ padding: 0 var(--fx-space-xs);
971
+ border-radius: var(--fx-radius-full);
972
+ }
973
+ .clear:hover { color: var(--fx-color-danger); }
974
+ .clear[hidden] { display: none; }
975
+ `;
976
+ let FxInput = _FxInput;
977
+ function defineFxInput() {
978
+ return defineElement("fx-input", FxInput);
979
+ }
980
+ defineFxInput();
981
+ const _FxSwitch = class _FxSwitch extends FxElement {
982
+ static get observedAttributes() {
983
+ return ["checked", "disabled", "size"];
984
+ }
985
+ /** Tamanho da trilha. Padrão: `'md'`. */
986
+ get size() {
987
+ const s = this.getAttr("size", "md");
988
+ return s === "sm" || s === "lg" ? s : "md";
989
+ }
990
+ set size(value) {
991
+ this.setAttribute("size", value);
992
+ }
993
+ get checked() {
994
+ return this.hasAttr("checked");
995
+ }
996
+ set checked(value) {
997
+ this.toggleAttr("checked", Boolean(value));
998
+ }
999
+ get disabled() {
1000
+ return this.hasAttr("disabled");
1001
+ }
1002
+ set disabled(value) {
1003
+ this.toggleAttr("disabled", Boolean(value));
1004
+ }
1005
+ render() {
1006
+ this.setTemplate(`
1007
+ <button class="switch" part="switch" type="button" role="switch"
1008
+ aria-checked="${this.checked}" ${this.hasAttr("disabled") ? "disabled" : ""}>
1009
+ <span class="track" part="track"><span class="thumb" part="thumb"></span></span>
1010
+ <span class="label" part="label"><slot></slot></span>
1011
+ </button>
1012
+ `);
1013
+ const btn = this.root.querySelector(".switch");
1014
+ if (!btn) return;
1015
+ btn.addEventListener("click", () => {
1016
+ if (this.hasAttr("disabled")) return;
1017
+ this.checked = !this.checked;
1018
+ btn.setAttribute("aria-checked", String(this.checked));
1019
+ this.dispatchEvent(
1020
+ new CustomEvent("change", {
1021
+ bubbles: true,
1022
+ composed: true,
1023
+ detail: { checked: this.checked }
1024
+ })
1025
+ );
1026
+ });
1027
+ }
1028
+ };
1029
+ _FxSwitch.styles = css`
1030
+ :host {
1031
+ display: inline-flex;
1032
+ vertical-align: middle;
1033
+ font-family: var(--fx-font-family);
1034
+ font-size: var(--fx-font-size);
1035
+ }
1036
+ .switch {
1037
+ display: inline-flex;
1038
+ align-items: center;
1039
+ gap: var(--fx-space-sm);
1040
+ cursor: pointer;
1041
+ user-select: none;
1042
+ background: none;
1043
+ border: none;
1044
+ padding: 0;
1045
+ color: var(--fx-text-default);
1046
+ font: inherit;
1047
+ }
1048
+ .track {
1049
+ position: relative;
1050
+ width: 40px;
1051
+ height: 22px;
1052
+ border-radius: var(--fx-radius-full);
1053
+ background: var(--fx-border-default);
1054
+ flex-shrink: 0;
1055
+ transition: background-color var(--fx-motion-duration-normal) var(--fx-motion-easing);
1056
+ }
1057
+ .thumb {
1058
+ position: absolute;
1059
+ top: 3px;
1060
+ left: 3px;
1061
+ width: 16px;
1062
+ height: 16px;
1063
+ border-radius: var(--fx-radius-full);
1064
+ background: #fff;
1065
+ box-shadow: var(--fx-shadow-sm);
1066
+ transition: transform var(--fx-motion-duration-normal) var(--fx-motion-easing);
1067
+ }
1068
+ :host([checked]) .track { background: var(--fx-color-primary); }
1069
+ :host([checked]) .thumb { transform: translateX(18px); }
1070
+ .switch:focus-visible .track {
1071
+ outline: none;
1072
+ box-shadow: var(--fx-effect-focus-ring, none);
1073
+ }
1074
+ .label { display: inline-flex; align-items: center; }
1075
+ :host([disabled]) .switch { opacity: 0.55; cursor: not-allowed; pointer-events: none; }
1076
+ /* Escala: sm 32×18 · md 40×22 (padrão) · lg 48×26 */
1077
+ :host([size='sm']) .track { width: 32px; height: 18px; }
1078
+ :host([size='sm']) .thumb { top: 2px; left: 2px; width: 14px; height: 14px; }
1079
+ :host([size='sm'][checked]) .thumb { transform: translateX(14px); }
1080
+ :host([size='lg']) .track { width: 48px; height: 26px; }
1081
+ :host([size='lg']) .thumb { top: 4px; left: 4px; width: 18px; height: 18px; }
1082
+ :host([size='lg'][checked]) .thumb { transform: translateX(22px); }
1083
+ `;
1084
+ let FxSwitch = _FxSwitch;
1085
+ function defineFxSwitch() {
1086
+ return defineElement("fx-switch", FxSwitch);
1087
+ }
1088
+ defineFxSwitch();
1089
+ const _FxMultiselect = class _FxMultiselect extends FxElement {
1090
+ constructor() {
1091
+ super(...arguments);
1092
+ this.selected = /* @__PURE__ */ new Set();
1093
+ }
1094
+ static get observedAttributes() {
1095
+ return ["disabled", "placeholder"];
1096
+ }
1097
+ /** Valores selecionados (CSV no atributo / array na propriedade). */
1098
+ get values() {
1099
+ const csv = this.getAttr("values");
1100
+ return csv ? csv.split(",").map((v) => v.trim()).filter(Boolean) : [];
1101
+ }
1102
+ set values(list) {
1103
+ this.setAttribute("values", list.join(","));
1104
+ }
1105
+ get disabled() {
1106
+ return this.hasAttr("disabled");
1107
+ }
1108
+ set disabled(value) {
1109
+ this.toggleAttr("disabled", Boolean(value));
1110
+ }
1111
+ get options() {
1112
+ return [...this.querySelectorAll("option")].map((o) => ({
1113
+ value: o.getAttribute("value") ?? o.textContent?.trim() ?? "",
1114
+ label: o.textContent?.trim() ?? ""
1115
+ }));
1116
+ }
1117
+ connectedCallback() {
1118
+ this.selected = new Set(this.values);
1119
+ super.connectedCallback();
1120
+ this.observer = new MutationObserver(() => this.render());
1121
+ this.observer.observe(this, { childList: true, subtree: true, characterData: true });
1122
+ this.docListener = (e) => {
1123
+ if (!this.hasAttr("open")) return;
1124
+ if (e.composedPath().includes(this)) return;
1125
+ this.removeAttribute("open");
1126
+ this.render();
1127
+ };
1128
+ document.addEventListener("click", this.docListener);
1129
+ }
1130
+ disconnectedCallback() {
1131
+ super.disconnectedCallback();
1132
+ this.observer?.disconnect();
1133
+ if (this.docListener) document.removeEventListener("click", this.docListener);
1134
+ }
1135
+ /** Aplica a seleção, reflete e emite change. */
1136
+ commit() {
1137
+ this.values = [...this.selected];
1138
+ this.render();
1139
+ this.dispatchEvent(
1140
+ new CustomEvent("change", {
1141
+ bubbles: true,
1142
+ composed: true,
1143
+ detail: { values: [...this.selected] }
1144
+ })
1145
+ );
1146
+ }
1147
+ toggleValue(value) {
1148
+ if (this.selected.has(value)) this.selected.delete(value);
1149
+ else this.selected.add(value);
1150
+ this.commit();
1151
+ }
1152
+ render() {
1153
+ const wasOpen = this.hasAttr("open");
1154
+ const search = this.root.querySelector(".search")?.value ?? "";
1155
+ const opts = this.options;
1156
+ for (const v of [...this.selected]) {
1157
+ if (!opts.some((o) => o.value === v)) this.selected.delete(v);
1158
+ }
1159
+ const chips = opts.filter((o) => this.selected.has(o.value));
1160
+ const placeholder = this.getAttr("placeholder", "Selecione…");
1161
+ const filtered = search ? opts.filter((o) => o.label.toLowerCase().includes(search.toLowerCase())) : opts;
1162
+ this.setTemplate(`
1163
+ <span class="trigger" part="trigger" role="button" tabindex="${this.disabled ? -1 : 0}" aria-haspopup="listbox" aria-expanded="${wasOpen}" aria-disabled="${this.disabled}">
1164
+ <span class="lead">
1165
+ ${chips.length ? chips.map((c) => `
1166
+ <span class="chip" data-value="${c.value}">
1167
+ ${c.label}
1168
+ <span class="chip__x" role="button" aria-label="Remover ${c.label}">×</span>
1169
+ </span>`).join("") : `<span class="placeholder">${placeholder}</span>`}
1170
+ </span>
1171
+ <span class="trigger-right">
1172
+ ${this.hasAttr("clearable") && chips.length ? '<button type="button" class="icon-btn clear" part="clear" aria-label="Limpar seleção">×</button>' : ""}
1173
+ <span class="caret">▼</span>
1174
+ </span>
1175
+ </span>
1176
+ <div class="panel" part="panel" role="listbox" aria-multiselectable="true">
1177
+ <div class="panel-header">
1178
+ <span class="count">${chips.length} selecionado${chips.length === 1 ? "" : "s"}</span>
1179
+ ${this.hasAttr("clearable") && chips.length ? '<button type="button" class="clear-all">Limpar tudo</button>' : ""}
1180
+ </div>
1181
+ ${this.hasAttr("searchable") ? `<input class="search" part="search" type="text" placeholder="${this.getAttr("search-placeholder", "Pesquisar…")}">` : ""}
1182
+ <ul class="list">
1183
+ ${filtered.map((o) => `
1184
+ <li><button type="button" class="opt" role="option" data-value="${o.value}"
1185
+ aria-selected="${this.selected.has(o.value)}">
1186
+ <span class="box" aria-hidden="true"></span>${o.label}
1187
+ </button></li>`).join("")}
1188
+ ${filtered.length === 0 ? `<li class="empty">${this.getAttr("no-results", "Nenhum resultado")}</li>` : ""}
1189
+ </ul>
1190
+ </div>
1191
+ `);
1192
+ if (wasOpen) this.setAttribute("open", "");
1193
+ const searchInput = this.root.querySelector(".search");
1194
+ if (searchInput) {
1195
+ searchInput.value = search;
1196
+ searchInput.addEventListener("input", () => {
1197
+ const pos = searchInput.selectionStart ?? searchInput.value.length;
1198
+ this.render();
1199
+ const si = this.root.querySelector(".search");
1200
+ if (si) {
1201
+ si.focus();
1202
+ si.setSelectionRange(pos, pos);
1203
+ }
1204
+ });
1205
+ searchInput.addEventListener("click", (e) => e.stopPropagation());
1206
+ }
1207
+ const trigger = this.root.querySelector(".trigger");
1208
+ if (!trigger) return;
1209
+ trigger.addEventListener("click", (e) => {
1210
+ if (this.disabled) return;
1211
+ if (e.target.closest(".clear")) return;
1212
+ if (e.target.closest(".chip__x")) return;
1213
+ this.toggleAttribute("open");
1214
+ if (this.hasAttr("open")) {
1215
+ const r = this.getBoundingClientRect();
1216
+ this.toggleAttribute("up", r.bottom + 300 > window.innerHeight && r.top > 300);
1217
+ } else {
1218
+ this.removeAttribute("up");
1219
+ }
1220
+ this.render();
1221
+ });
1222
+ trigger.addEventListener("keydown", (e) => {
1223
+ if (this.disabled) return;
1224
+ if (e.key === "Enter" || e.key === " ") {
1225
+ e.preventDefault();
1226
+ trigger.dispatchEvent(new MouseEvent("click", { bubbles: true }));
1227
+ }
1228
+ });
1229
+ this.root.querySelector(".clear")?.addEventListener("click", (e) => {
1230
+ e.stopPropagation();
1231
+ this.selected.clear();
1232
+ this.commit();
1233
+ });
1234
+ this.root.querySelector(".clear-all")?.addEventListener("click", (e) => {
1235
+ e.stopPropagation();
1236
+ this.selected.clear();
1237
+ this.commit();
1238
+ });
1239
+ this.root.querySelectorAll(".chip").forEach((chip) => {
1240
+ chip.querySelector(".chip__x")?.addEventListener("click", (e) => {
1241
+ e.stopPropagation();
1242
+ this.toggleValue(chip.dataset.value);
1243
+ });
1244
+ });
1245
+ this.root.querySelectorAll(".opt").forEach((opt) => {
1246
+ opt.addEventListener("click", () => this.toggleValue(opt.dataset.value));
1247
+ });
1248
+ }
1249
+ };
1250
+ _FxMultiselect.styles = css`
1251
+ :host {
1252
+ display: inline-block;
1253
+ vertical-align: middle;
1254
+ font-family: var(--fx-font-family);
1255
+ font-size: var(--fx-font-size);
1256
+ position: relative;
1257
+ }
1258
+ .trigger {
1259
+ display: inline-flex;
1260
+ align-items: center;
1261
+ flex-wrap: nowrap;
1262
+ gap: var(--fx-space-xs);
1263
+ width: 240px;
1264
+ box-sizing: border-box;
1265
+ min-height: var(--fx-size-md);
1266
+ padding: var(--fx-space-xs) var(--fx-space-md);
1267
+ font: inherit;
1268
+ color: var(--fx-text-default);
1269
+ background-color: var(--fx-surface-background);
1270
+ border: 1px solid var(--fx-border-default);
1271
+ border-radius: var(--fx-radius-md);
1272
+ cursor: pointer;
1273
+ text-align: left;
1274
+ transition: border-color var(--fx-motion-duration-normal) var(--fx-motion-easing);
1275
+ }
1276
+ /* Área dos chips: encolhe e quebra linha internamente,
1277
+ mantendo os ícones sempre dentro do componente. */
1278
+ .lead {
1279
+ display: inline-flex;
1280
+ flex-wrap: wrap;
1281
+ gap: var(--fx-space-xs);
1282
+ flex: 1;
1283
+ min-width: 0;
1284
+ overflow: hidden;
1285
+ }
1286
+ :host([size='sm']) .trigger { min-height: var(--fx-size-sm); width: 200px; }
1287
+ :host([size='lg']) .trigger { min-height: var(--fx-size-lg); width: 280px; font-size: calc(var(--fx-font-size) + 4px); }
1288
+ /* Validação */
1289
+ :host([error]) .trigger,
1290
+ :host([invalid]) .trigger {
1291
+ border-color: var(--fx-color-danger, #dc2626);
1292
+ }
1293
+ :host([error]) .trigger:focus-visible,
1294
+ :host([invalid]) .trigger:focus-visible,
1295
+ :host([error][open]) .trigger,
1296
+ :host([invalid][open]) .trigger {
1297
+ border-color: var(--fx-color-danger, #dc2626);
1298
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--fx-color-danger, #dc2626) 18%, transparent);
1299
+ }
1300
+ :host([success]) .trigger,
1301
+ :host([valid]) .trigger {
1302
+ border-color: var(--fx-color-success, #16a34a);
1303
+ }
1304
+ :host([success]) .trigger:focus-visible,
1305
+ :host([valid]) .trigger:focus-visible,
1306
+ :host([success][open]) .trigger,
1307
+ :host([valid][open]) .trigger {
1308
+ border-color: var(--fx-color-success, #16a34a);
1309
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--fx-color-success, #16a34a) 18%, transparent);
1310
+ }
1311
+ .trigger:hover { border-color: var(--fx-border-hover); }
1312
+ .trigger:focus-visible {
1313
+ outline: none;
1314
+ border-color: var(--fx-color-primary);
1315
+ box-shadow: var(--fx-effect-focus-ring, none);
1316
+ }
1317
+ .trigger[aria-expanded='true'] { border-color: var(--fx-color-primary); }
1318
+ .placeholder { color: var(--fx-text-muted); }
1319
+ .chip {
1320
+ display: inline-flex;
1321
+ align-items: center;
1322
+ gap: 4px;
1323
+ background: color-mix(in srgb, var(--fx-color-primary) 14%, transparent);
1324
+ color: var(--fx-color-primary);
1325
+ border-radius: var(--fx-radius-sm);
1326
+ padding: 2px 6px;
1327
+ font-size: calc(var(--fx-font-size) - 2px);
1328
+ }
1329
+ .chip__x {
1330
+ border: none;
1331
+ background: none;
1332
+ color: inherit;
1333
+ cursor: pointer;
1334
+ font-size: calc(var(--fx-font-size) - 1px);
1335
+ line-height: 1;
1336
+ padding: 0;
1337
+ }
1338
+ .trigger-right {
1339
+ margin-left: auto;
1340
+ display: inline-flex;
1341
+ align-items: center;
1342
+ gap: 4px;
1343
+ color: var(--fx-text-muted);
1344
+ }
1345
+ .icon-btn {
1346
+ border: none;
1347
+ background: none;
1348
+ color: inherit;
1349
+ cursor: pointer;
1350
+ font-size: calc(var(--fx-font-size) + 2px);
1351
+ line-height: 1;
1352
+ padding: 0 2px;
1353
+ border-radius: var(--fx-radius-full);
1354
+ }
1355
+ .icon-btn:hover { color: var(--fx-color-danger); }
1356
+ .caret {
1357
+ font-size: calc(var(--fx-font-size) - 3px);
1358
+ transition: transform var(--fx-motion-duration-normal) var(--fx-motion-easing);
1359
+ }
1360
+ :host([open]) .caret { transform: rotate(180deg); }
1361
+
1362
+ /* Painel (overlay ) */
1363
+ .panel {
1364
+ position: absolute;
1365
+ top: calc(100% + 4px);
1366
+ left: 0;
1367
+ z-index: var(--fx-z-dropdown, 1000);
1368
+ width: max(100%, 240px);
1369
+ background: var(--fx-surface-background);
1370
+ border: 1px solid var(--fx-border-default);
1371
+ border-radius: var(--fx-radius-md);
1372
+ box-shadow: var(--fx-shadow-lg);
1373
+ display: none;
1374
+ overflow: hidden;
1375
+ }
1376
+ :host([up]) .panel {
1377
+ top: auto;
1378
+ bottom: calc(100% + 4px);
1379
+ }
1380
+ :host([open]) .panel { display: block; }
1381
+ .panel-header {
1382
+ display: flex;
1383
+ align-items: center;
1384
+ justify-content: space-between;
1385
+ gap: var(--fx-space-sm);
1386
+ padding: var(--fx-space-xs) var(--fx-space-md);
1387
+ border-bottom: 1px solid var(--fx-border-default);
1388
+ background: var(--fx-surface-surface-hover);
1389
+ }
1390
+ .count { font-size: calc(var(--fx-font-size) - 2px); color: var(--fx-text-muted); }
1391
+ .clear-all {
1392
+ border: none;
1393
+ background: none;
1394
+ font: inherit;
1395
+ font-size: calc(var(--fx-font-size) - 2px);
1396
+ color: var(--fx-color-primary);
1397
+ cursor: pointer;
1398
+ padding: 0;
1399
+ }
1400
+ .clear-all:hover { text-decoration: underline; }
1401
+ .search {
1402
+ width: 100%;
1403
+ box-sizing: border-box;
1404
+ font: inherit;
1405
+ color: var(--fx-text-default);
1406
+ background: var(--fx-surface-background);
1407
+ border: none;
1408
+ border-bottom: 1px solid var(--fx-border-default);
1409
+ padding: var(--fx-space-sm) var(--fx-space-md);
1410
+ outline: none;
1411
+ }
1412
+ .list {
1413
+ max-height: 240px;
1414
+ overflow-y: auto;
1415
+ list-style: none;
1416
+ margin: 0;
1417
+ padding: var(--fx-space-3xs, 4px);
1418
+ }
1419
+ .opt {
1420
+ display: flex;
1421
+ align-items: center;
1422
+ gap: var(--fx-space-sm);
1423
+ width: 100%;
1424
+ padding: var(--fx-space-xs) var(--fx-space-md);
1425
+ border: none;
1426
+ background: none;
1427
+ font: inherit;
1428
+ color: var(--fx-text-default);
1429
+ cursor: pointer;
1430
+ border-radius: var(--fx-radius-sm);
1431
+ text-align: left;
1432
+ }
1433
+ .opt:hover { background: color-mix(in srgb, var(--fx-color-primary) 12%, transparent); }
1434
+ .opt[aria-selected='true'] {
1435
+ background: color-mix(in srgb, var(--fx-color-primary) 16%, transparent);
1436
+ color: var(--fx-color-primary);
1437
+ font-weight: var(--fx-font-weight);
1438
+ }
1439
+ /* Checkbox visual */
1440
+ .box {
1441
+ width: 16px;
1442
+ height: 16px;
1443
+ flex-shrink: 0;
1444
+ display: inline-flex;
1445
+ align-items: center;
1446
+ justify-content: center;
1447
+ border: 2px solid var(--fx-border-default);
1448
+ border-radius: calc(var(--fx-radius-sm) / 2);
1449
+ font-size: 11px;
1450
+ line-height: 1;
1451
+ color: transparent;
1452
+ transition:
1453
+ background-color var(--fx-motion-duration-fast) var(--fx-motion-easing),
1454
+ border-color var(--fx-motion-duration-fast) var(--fx-motion-easing),
1455
+ color var(--fx-motion-duration-fast) var(--fx-motion-easing);
1456
+ }
1457
+ .opt[aria-selected='true'] .box {
1458
+ background: var(--fx-color-primary);
1459
+ border-color: var(--fx-color-primary);
1460
+ color: #fff;
1461
+ }
1462
+ .opt[aria-selected='true'] .box::after { content: '✓'; }
1463
+ .empty { padding: var(--fx-space-md); color: var(--fx-text-muted); text-align: center; }
1464
+ :host([disabled]) .trigger { opacity: 0.55; cursor: not-allowed; background: var(--fx-surface-surface-hover); }
1465
+ `;
1466
+ let FxMultiselect = _FxMultiselect;
1467
+ function defineFxMultiselect() {
1468
+ return defineElement("fx-multiselect", FxMultiselect);
1469
+ }
1470
+ defineFxMultiselect();
1471
+ const key = (y, m1, d) => y * 1e4 + m1 * 100 + d;
1472
+ function parseBound(raw) {
1473
+ const m = /^(\d{4})(?:-(\d{2}))?(?:-(\d{2}))?$/.exec(raw.trim());
1474
+ if (!m) return null;
1475
+ const y = +m[1];
1476
+ const m1 = m[2] ? +m[2] : 1;
1477
+ const d = m[3] ? +m[3] : null;
1478
+ if (d !== null) return { lo: key(y, m1, d), hi: key(y, m1, d) };
1479
+ if (m[2]) return { lo: key(y, m1, 1), hi: key(y, m1, 31) };
1480
+ return { lo: key(y, 1, 1), hi: key(y, 12, 31) };
1481
+ }
1482
+ const _FxCalendar = class _FxCalendar extends FxElement {
1483
+ constructor() {
1484
+ super(...arguments);
1485
+ this.view = "days";
1486
+ this.cursor = { y: (/* @__PURE__ */ new Date()).getFullYear(), m: (/* @__PURE__ */ new Date()).getMonth() };
1487
+ this.pickStart = null;
1488
+ this.pickEnd = null;
1489
+ }
1490
+ static get observedAttributes() {
1491
+ return ["value", "start", "end", "min", "max", "range", "mode", "values", "locale", "disabled"];
1492
+ }
1493
+ /** Modo de seleção: 'single' | 'range' | 'multiple'. */
1494
+ get mode() {
1495
+ const m = this.getAttr("mode");
1496
+ if (m === "range" || m === "multiple") return m;
1497
+ return this.hasAttr("range") ? "range" : "single";
1498
+ }
1499
+ set mode(m) {
1500
+ this.setAttribute("mode", m);
1501
+ }
1502
+ get range() {
1503
+ return this.mode === "range";
1504
+ }
1505
+ set range(value) {
1506
+ this.toggleAttr("range", Boolean(value));
1507
+ }
1508
+ /** Datas selecionadas no modo multiple (CSV no atributo). */
1509
+ get values() {
1510
+ const csv = this.getAttr("values");
1511
+ return csv ? csv.split(",").map((v) => v.trim()).filter(Boolean) : [];
1512
+ }
1513
+ set values(list) {
1514
+ this.setAttribute("values", list.join(","));
1515
+ }
1516
+ get value() {
1517
+ return this.getAttr("value");
1518
+ }
1519
+ set value(v) {
1520
+ this.setAttribute("value", v);
1521
+ }
1522
+ bounds() {
1523
+ return {
1524
+ min: parseBound(this.getAttr("min", "")),
1525
+ max: parseBound(this.getAttr("max", ""))
1526
+ };
1527
+ }
1528
+ locale() {
1529
+ return this.getAttr("locale", "pt-BR");
1530
+ }
1531
+ fmt(opts, d) {
1532
+ try {
1533
+ return new Intl.DateTimeFormat(this.locale(), opts).format(d);
1534
+ } catch {
1535
+ return new Intl.DateTimeFormat("pt-BR", opts).format(d);
1536
+ }
1537
+ }
1538
+ dayDisabled(y, m, d) {
1539
+ const { min, max } = this.bounds();
1540
+ const k = key(y, m + 1, d);
1541
+ return min !== null && k < min.lo || max !== null && k > max.hi;
1542
+ }
1543
+ monthDisabled(y, m1) {
1544
+ const { min, max } = this.bounds();
1545
+ const lo = key(y, m1, 1);
1546
+ const hi = key(y, m1, 31);
1547
+ return min !== null && hi < min.lo || max !== null && lo > max.hi;
1548
+ }
1549
+ yearDisabled(y) {
1550
+ const { min, max } = this.bounds();
1551
+ const lo = key(y, 1, 1);
1552
+ const hi = key(y, 12, 31);
1553
+ return min !== null && hi < min.lo || max !== null && lo > max.hi;
1554
+ }
1555
+ dayKeyOf(date) {
1556
+ return key(date.getFullYear(), date.getMonth() + 1, date.getDate());
1557
+ }
1558
+ keyToDate(k) {
1559
+ return new Date(Math.floor(k / 1e4), Math.floor(k / 100) % 100 - 1, k % 100);
1560
+ }
1561
+ iso(k) {
1562
+ const d = this.keyToDate(k);
1563
+ const p = (n) => String(n).padStart(2, "0");
1564
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
1565
+ }
1566
+ /** Seleção atual para pintura (chaves). */
1567
+ selection() {
1568
+ const multi = new Set(this.values.map((iso) => parseBound(iso)?.lo ?? 0).filter(Boolean));
1569
+ if (this.mode === "range") {
1570
+ let s = this.pickStart;
1571
+ let e = this.pickEnd;
1572
+ const attrS = parseBound(this.getAttr("start", ""));
1573
+ const attrE = parseBound(this.getAttr("end", ""));
1574
+ if (s === null && attrS) s = attrS.lo;
1575
+ if (e === null && attrE) e = attrE.lo;
1576
+ return { start: s, end: e, single: null, multi };
1577
+ }
1578
+ if (this.mode === "multiple") {
1579
+ return { start: null, end: null, single: null, multi };
1580
+ }
1581
+ const v = parseBound(this.getAttr("value", ""));
1582
+ return { start: null, end: null, single: v ? v.lo : null, multi };
1583
+ }
1584
+ emit() {
1585
+ if (this.mode === "range") {
1586
+ const detail = {
1587
+ start: this.pickStart !== null ? this.iso(this.pickStart) : this.getAttr("start", ""),
1588
+ end: this.pickEnd !== null ? this.iso(this.pickEnd) : this.getAttr("end", "")
1589
+ };
1590
+ this.setAttribute("start", detail.start);
1591
+ this.setAttribute("end", detail.end);
1592
+ this.dispatchEvent(new CustomEvent("change", { bubbles: true, composed: true, detail }));
1593
+ } else if (this.mode === "multiple") {
1594
+ const values = this.values;
1595
+ this.dispatchEvent(new CustomEvent("change", { bubbles: true, composed: true, detail: { values } }));
1596
+ } else {
1597
+ const value = this.getAttr("value");
1598
+ this.dispatchEvent(new CustomEvent("change", { bubbles: true, composed: true, detail: { value } }));
1599
+ }
1600
+ }
1601
+ pickDay(k) {
1602
+ if (this.mode === "range") {
1603
+ if (this.pickStart === null || this.pickEnd !== null) {
1604
+ this.pickStart = k;
1605
+ this.pickEnd = null;
1606
+ this.setAttribute("start", this.iso(k));
1607
+ this.removeAttribute("end");
1608
+ this.render();
1609
+ return;
1610
+ }
1611
+ if (k < this.pickStart) [this.pickStart, this.pickEnd] = [k, this.pickStart];
1612
+ else this.pickEnd = k;
1613
+ this.emit();
1614
+ } else if (this.mode === "multiple") {
1615
+ const set = new Set(this.values);
1616
+ const iso = this.iso(k);
1617
+ if (set.has(iso)) set.delete(iso);
1618
+ else set.add(iso);
1619
+ this.values = [...set].sort();
1620
+ this.emit();
1621
+ } else {
1622
+ this.value = this.iso(k);
1623
+ this.emit();
1624
+ }
1625
+ this.render();
1626
+ }
1627
+ navigate(dir) {
1628
+ if (this.view === "days") {
1629
+ const d = new Date(this.cursor.y, this.cursor.m + dir, 1);
1630
+ this.cursor = { y: d.getFullYear(), m: d.getMonth() };
1631
+ } else if (this.view === "months") {
1632
+ this.cursor.y += dir;
1633
+ } else {
1634
+ this.cursor.y += dir * 12;
1635
+ }
1636
+ this.render();
1637
+ }
1638
+ render() {
1639
+ const todayK = this.dayKeyOf(/* @__PURE__ */ new Date());
1640
+ const sel = this.selection();
1641
+ const loc = this.locale();
1642
+ const titles = {
1643
+ days: this.fmt({ month: "long", year: "numeric" }, new Date(this.cursor.y, this.cursor.m, 1)),
1644
+ months: String(this.cursor.y),
1645
+ years: `${Math.floor(this.cursor.y / 12) * 12} – ${Math.floor(this.cursor.y / 12) * 12 + 11}`
1646
+ };
1647
+ let cells = "";
1648
+ if (this.view === "days") {
1649
+ const wds = [...Array(7)].map(
1650
+ (_, i) => this.fmt({ weekday: "short" }, new Date(2024, 8, 1 + i))
1651
+ // 01/09/2024 foi domingo
1652
+ );
1653
+ cells = wds.map((w) => `<span class="wd">${w}</span>`).join("") + this.daysHtml(todayK, sel, loc);
1654
+ } else if (this.view === "months") {
1655
+ cells = [...Array(12)].map((_, i) => {
1656
+ const dis = this.monthDisabled(this.cursor.y, i + 1);
1657
+ return `<button type="button" class="cell" data-month="${i}" ${dis ? "disabled" : ""}>${this.fmt({ month: "short" }, new Date(this.cursor.y, i, 1))}</button>`;
1658
+ }).join("");
1659
+ } else {
1660
+ const base = Math.floor(this.cursor.y / 12) * 12;
1661
+ cells = [...Array(12)].map((_, i) => {
1662
+ const y = base + i;
1663
+ const dis = this.yearDisabled(y);
1664
+ return `<button type="button" class="cell" data-year="${y}" ${dis ? "disabled" : ""}>${y}</button>`;
1665
+ }).join("");
1666
+ }
1667
+ const titleAction = this.view === "days" ? `data-goto="months"` : this.view === "months" ? `data-goto="years"` : "";
1668
+ this.setTemplate(`
1669
+ <div class="head">
1670
+ <button type="button" class="nav" data-nav="-1" aria-label="Anterior">‹</button>
1671
+ <button type="button" class="title" part="title" ${titleAction}>${titles[this.view]}</button>
1672
+ <button type="button" class="nav" data-nav="1" aria-label="Próximo">›</button>
1673
+ </div>
1674
+ <div class="grid ${this.view}">${cells}</div>
1675
+ `);
1676
+ this.root.querySelectorAll("[data-nav]").forEach(
1677
+ (b) => b.addEventListener("click", () => this.navigate(Number(b.dataset.nav)))
1678
+ );
1679
+ const gotoBtn = this.root.querySelector("[data-goto]");
1680
+ gotoBtn?.addEventListener("click", () => {
1681
+ this.view = gotoBtn.dataset.goto;
1682
+ this.render();
1683
+ });
1684
+ this.root.querySelectorAll("[data-day]").forEach(
1685
+ (b) => b.addEventListener("click", () => this.pickDay(Number(b.dataset.day)))
1686
+ );
1687
+ this.root.querySelectorAll("[data-month]").forEach(
1688
+ (b) => b.addEventListener("click", () => {
1689
+ this.cursor.m = Number(b.dataset.month);
1690
+ this.view = "days";
1691
+ this.render();
1692
+ })
1693
+ );
1694
+ this.root.querySelectorAll("[data-year]").forEach(
1695
+ (b) => b.addEventListener("click", () => {
1696
+ this.cursor.y = Number(b.dataset.year);
1697
+ this.view = "months";
1698
+ this.render();
1699
+ })
1700
+ );
1701
+ }
1702
+ /** Grade de dias com preenchimento do mês anterior. */
1703
+ daysHtml(todayK, sel, _loc) {
1704
+ const { y, m } = this.cursor;
1705
+ const first = new Date(y, m, 1).getDay();
1706
+ const total = new Date(y, m + 1, 0).getDate();
1707
+ const prevTotal = new Date(y, m, 0).getDate();
1708
+ let html = "";
1709
+ for (let i = first - 1; i >= 0; i--) {
1710
+ html += `<span class="cell muted">${prevTotal - i}</span>`;
1711
+ }
1712
+ for (let d = 1; d <= total; d++) {
1713
+ const k = key(y, m + 1, d);
1714
+ const isSel = sel.single === k || sel.start === k || sel.end === k || sel.multi.has(k);
1715
+ const inRange = sel.start !== null && sel.end !== null && k > sel.start && k < sel.end;
1716
+ const cls = ["cell", isSel ? "sel" : "", inRange ? "in-range" : "", k === todayK ? "today" : ""].filter(Boolean).join(" ");
1717
+ html += `<button type="button" class="${cls}" data-day="${k}" ${this.dayDisabled(y, m, d) ? "disabled" : ""}>${d}</button>`;
1718
+ }
1719
+ return html;
1720
+ }
1721
+ };
1722
+ _FxCalendar.styles = css`
1723
+ :host {
1724
+ display: inline-block;
1725
+ font-family: var(--fx-font-family);
1726
+ font-size: var(--fx-font-size);
1727
+ color: var(--fx-text-default);
1728
+ background: var(--fx-surface-background);
1729
+ border: 1px solid var(--fx-border-default);
1730
+ border-radius: var(--fx-radius-md);
1731
+ box-shadow: var(--fx-shadow-sm);
1732
+ padding: var(--fx-space-sm);
1733
+ user-select: none;
1734
+ }
1735
+ :host([disabled]) { opacity: 0.55; pointer-events: none; }
1736
+ .head {
1737
+ display: flex;
1738
+ align-items: center;
1739
+ justify-content: space-between;
1740
+ gap: var(--fx-space-xs);
1741
+ margin-bottom: var(--fx-space-xs);
1742
+ }
1743
+ .nav,
1744
+ .title {
1745
+ border: none;
1746
+ background: none;
1747
+ color: var(--fx-text-default);
1748
+ cursor: pointer;
1749
+ font: inherit;
1750
+ border-radius: var(--fx-radius-sm);
1751
+ padding: var(--fx-space-3xs, 4px) var(--fx-space-xs);
1752
+ }
1753
+ .nav:hover, .title:hover { background: var(--fx-surface-surface-hover); }
1754
+ .title { font-weight: var(--fx-font-weight); text-transform: capitalize; flex: 1; }
1755
+ .grid {
1756
+ display: grid;
1757
+ grid-template-columns: repeat(7, 34px);
1758
+ gap: 2px;
1759
+ }
1760
+ .grid.months, .grid.years { grid-template-columns: repeat(4, minmax(52px, 1fr)); }
1761
+ .wd {
1762
+ text-align: center;
1763
+ color: var(--fx-text-muted);
1764
+ font-size: calc(var(--fx-font-size) - 2px);
1765
+ padding: var(--fx-space-3xs, 4px) 0;
1766
+ text-transform: capitalize;
1767
+ }
1768
+ .cell {
1769
+ position: relative;
1770
+ border: none;
1771
+ background: none;
1772
+ font: inherit;
1773
+ color: var(--fx-text-default);
1774
+ height: 32px;
1775
+ border-radius: var(--fx-radius-sm);
1776
+ cursor: pointer;
1777
+ display: inline-flex;
1778
+ align-items: center;
1779
+ justify-content: center;
1780
+ }
1781
+ .cell:hover { background: color-mix(in srgb, var(--fx-color-primary) 12%, transparent); }
1782
+ .cell.muted { color: var(--fx-text-disabled); }
1783
+ .cell.today::after {
1784
+ content: '';
1785
+ position: absolute;
1786
+ bottom: 3px;
1787
+ width: 4px;
1788
+ height: 4px;
1789
+ border-radius: var(--fx-radius-full);
1790
+ background: var(--fx-color-primary);
1791
+ }
1792
+ .cell.sel {
1793
+ background: var(--fx-color-primary);
1794
+ color: #fff;
1795
+ font-weight: var(--fx-font-weight);
1796
+ }
1797
+ .cell.sel::after { background: #fff; }
1798
+ .cell.in-range {
1799
+ background: color-mix(in srgb, var(--fx-color-primary) 16%, transparent);
1800
+ border-radius: 0;
1801
+ }
1802
+ .grid.months .cell, .grid.years .cell { height: 40px; }
1803
+ .cell[disabled] { opacity: 0.35; cursor: not-allowed; pointer-events: none; }
1804
+ `;
1805
+ let FxCalendar = _FxCalendar;
1806
+ function defineFxCalendar() {
1807
+ return defineElement("fx-calendar", FxCalendar);
1808
+ }
1809
+ defineFxCalendar();
1810
+ const _FxDatepicker = class _FxDatepicker extends FxElement {
1811
+ constructor() {
1812
+ super(...arguments);
1813
+ this.time = { h: 0, m: 0, s: 0 };
1814
+ }
1815
+ static get observedAttributes() {
1816
+ return ["value", "start", "end", "values", "mode", "min", "max", "placeholder", "disabled", "format", "show-time", "free-text"];
1817
+ }
1818
+ get mode() {
1819
+ const m = this.getAttr("mode", "single");
1820
+ return ["single", "range", "multiple"].includes(m) ? m : "single";
1821
+ }
1822
+ set mode(value) {
1823
+ this.setAttribute("mode", value);
1824
+ }
1825
+ get value() {
1826
+ return this.getAttr("value");
1827
+ }
1828
+ set value(v) {
1829
+ this.setAttribute("value", v);
1830
+ }
1831
+ /** Datas do modo múltiplo (CSV no atributo). */
1832
+ get values() {
1833
+ const csv = this.getAttr("values");
1834
+ return csv ? csv.split(",").map((v) => v.trim()).filter(Boolean) : [];
1835
+ }
1836
+ set values(list) {
1837
+ this.setAttribute("values", list.join(","));
1838
+ }
1839
+ get disabled() {
1840
+ return this.hasAttr("disabled");
1841
+ }
1842
+ set disabled(value) {
1843
+ this.toggleAttr("disabled", Boolean(value));
1844
+ }
1845
+ connectedCallback() {
1846
+ super.connectedCallback();
1847
+ this.docListener = (e) => {
1848
+ if (!this.hasAttr("open")) return;
1849
+ if (e.composedPath().includes(this)) return;
1850
+ this.removeAttribute("open");
1851
+ };
1852
+ document.addEventListener("click", this.docListener);
1853
+ }
1854
+ disconnectedCallback() {
1855
+ super.disconnectedCallback();
1856
+ if (this.docListener) document.removeEventListener("click", this.docListener);
1857
+ }
1858
+ /** Formata uma data ISO ("YYYY-MM-DD" ou com "THH:mm:ss") segundo o atributo format. */
1859
+ formatDate(iso) {
1860
+ if (!iso) return "";
1861
+ const fmt = this.getAttr("format");
1862
+ if (!fmt) return iso.split("T")[0];
1863
+ const [datePart, timePart] = iso.split("T");
1864
+ const [y = "", m = "", d = ""] = datePart.split("-");
1865
+ const [H = "00", M = "00", S = "00"] = (timePart ?? "").split(":");
1866
+ return fmt.replace(/yyyy/gi, "").replace(/MM/g, "").replace(/mm/g, "").replace(/dd/gi, "").replace(/HH/g, "").replace(/SS/gi, "").replace(/\u0001/g, y).replace(/\u0002/g, m).replace(/\u0003/g, d).replace(/\u0004/g, H).replace(/\u0005/g, M).replace(/\u0006/g, S);
1867
+ }
1868
+ /** Texto exibido no input conforme o modo. */
1869
+ displayText() {
1870
+ if (this.mode === "range") {
1871
+ const s = this.getAttr("start");
1872
+ const e = this.getAttr("end");
1873
+ this.hasAttr("show-time") ? " " : " → ";
1874
+ if (!s && !e) return "";
1875
+ if (this.hasAttr("show-time")) {
1876
+ const f = (iso) => iso ? `${this.formatDate(iso)} ${this.timeText()}` : iso ? `${this.formatDate(iso)} …` : "";
1877
+ return s && e ? `${f(s)} → ${f(e)}` : f(s);
1878
+ }
1879
+ return s && e ? `${this.formatDate(s)} → ${this.formatDate(e)}` : s ? `${this.formatDate(s)} → …` : "";
1880
+ }
1881
+ if (this.mode === "multiple") {
1882
+ return this.values.map((v) => this.formatDate(v)).join(", ");
1883
+ }
1884
+ const base = this.formatDate(this.value);
1885
+ return base && this.hasAttr("show-time") ? `${base} ${this.timeText()}` : base;
1886
+ }
1887
+ /** "HH:MM:SS" do estado interno de show-time. */
1888
+ timeText() {
1889
+ const p = (n) => String(n).padStart(2, "0");
1890
+ return `${p(this.time.h)}:${p(this.time.m)}:${p(this.time.s)}`;
1891
+ }
1892
+ /** Extrai hora de um valor ISO inicial, se houver. */
1893
+ loadTimeFromValue() {
1894
+ const src = this.value || this.getAttr("start") || "";
1895
+ const t = src.split("T")[1];
1896
+ if (!t) return;
1897
+ const [h, m, s] = t.split(":").map((n) => parseInt(n, 10));
1898
+ this.time = { h: h || 0, m: m || 0, s: s || 0 };
1899
+ }
1900
+ /** Anexa o horário interno ao ISO puro vindo do calendário. */
1901
+ withTime(iso) {
1902
+ if (!iso) return iso;
1903
+ return this.hasAttr("show-time") ? `${iso}T${this.timeText()}` : iso;
1904
+ }
1905
+ render() {
1906
+ const disabled = this.disabled;
1907
+ const clearable = this.hasAttr("clearable");
1908
+ const text = this.displayText();
1909
+ const hasValue = text.length > 0;
1910
+ const showTime = this.hasAttr("show-time");
1911
+ this.loadTimeFromValue();
1912
+ let calAttrs = `min="${this.getAttr("min")}" max="${this.getAttr("max")}"`;
1913
+ if (this.mode === "range") {
1914
+ calAttrs += ` range start="${this.getAttr("start")}" end="${this.getAttr("end")}"`;
1915
+ } else if (this.mode === "multiple") {
1916
+ const dates = this.values.map((v) => v.split("T")[0]).join(",");
1917
+ calAttrs += ` mode="multiple" values="${dates}"`;
1918
+ } else {
1919
+ const v = this.value.split("T")[0] ?? "";
1920
+ calAttrs += ` value="${v}"`;
1921
+ }
1922
+ this.setTemplate(`
1923
+ <div class="field" part="field">
1924
+ <input class="display" part="display" type="text" ${this.hasAttr("free-text") && !disabled ? "" : "readonly"}
1925
+ placeholder="${this.getAttr("placeholder", "dd/mm/aaaa")}"
1926
+ value="${text}" ${disabled ? "disabled" : ""}>
1927
+ <button type="button" class="clear" part="clear" aria-label="Limpar"
1928
+ ${clearable && hasValue ? "" : "hidden"}>
1929
+ ×
1930
+ </button>
1931
+ <span class="cal-icon" part="icon" aria-hidden="true">
1932
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none"
1933
+ stroke="currentColor" stroke-width="2" stroke-linecap="round">
1934
+ <rect x="3" y="4" width="18" height="18" rx="2"/>
1935
+ <path d="M16 2v4M8 2v4M3 10h18"/>
1936
+ </svg>
1937
+ </span>
1938
+ </div>
1939
+ <div class="pop" part="popover">
1940
+ <fx-calendar ${calAttrs}></fx-calendar>
1941
+ ${showTime ? `<div class="time-row" part="time">
1942
+ <input class="t-h" type="number" min="0" max="23" value="${this.time.h}" aria-label="Hora"><span class="sep">:</span>
1943
+ <input class="t-m" type="number" min="0" max="59" value="${this.time.m}" aria-label="Minuto"><span class="sep">:</span>
1944
+ <input class="t-s" type="number" min="0" max="59" value="${this.time.s}" aria-label="Segundo">
1945
+ </div>` : ""}
1946
+ </div>
1947
+ `);
1948
+ const field = this.root.querySelector(".field");
1949
+ if (!field) return;
1950
+ if (disabled) return;
1951
+ field.addEventListener("click", (e) => {
1952
+ if (e.target.closest(".clear")) return;
1953
+ if (this.hasAttr("free-text") && e.target.classList.contains("display")) return;
1954
+ this.toggleAttribute("open");
1955
+ if (this.hasAttr("open")) {
1956
+ const r = this.getBoundingClientRect();
1957
+ this.toggleAttribute("up", r.bottom + 340 > window.innerHeight && r.top > 340);
1958
+ } else {
1959
+ this.removeAttribute("up");
1960
+ }
1961
+ });
1962
+ this.root.querySelector(".clear")?.addEventListener("click", (e) => {
1963
+ e.stopPropagation();
1964
+ this.removeAttribute("value");
1965
+ this.removeAttribute("start");
1966
+ this.removeAttribute("end");
1967
+ this.removeAttribute("values");
1968
+ this.removeAttribute("open");
1969
+ this.render();
1970
+ this.emitChange({});
1971
+ });
1972
+ const cal = this.root.querySelector(".pop fx-calendar");
1973
+ cal?.addEventListener("change", (e) => {
1974
+ const d = e.detail ?? {};
1975
+ if (d.start !== void 0 || d.end !== void 0) {
1976
+ if (d.start) this.setAttribute("start", this.withTime(d.start));
1977
+ else this.removeAttribute("start");
1978
+ if (d.end) this.setAttribute("end", this.withTime(d.end));
1979
+ else this.removeAttribute("end");
1980
+ }
1981
+ if (d.values !== void 0) {
1982
+ this.values = d.values.map((v) => this.withTime(v));
1983
+ }
1984
+ if (d.value !== void 0) {
1985
+ if (d.value) this.setAttribute("value", this.withTime(d.value));
1986
+ else this.removeAttribute("value");
1987
+ if (this.mode === "single") this.removeAttribute("open");
1988
+ }
1989
+ this.render();
1990
+ this.emitChange(d);
1991
+ });
1992
+ const display = this.root.querySelector(".display");
1993
+ if (display && this.hasAttr("free-text") && !disabled) {
1994
+ display.addEventListener("keydown", (e) => {
1995
+ if (e.key === "Enter") {
1996
+ e.preventDefault();
1997
+ this.commitTyped(display);
1998
+ }
1999
+ });
2000
+ display.addEventListener("blur", () => this.commitTyped(display));
2001
+ }
2002
+ if (showTime) {
2003
+ const applyTime = () => {
2004
+ const h = this.root.querySelector(".t-h");
2005
+ const m = this.root.querySelector(".t-m");
2006
+ const s = this.root.querySelector(".t-s");
2007
+ const clamp = (v, max) => Math.min(max, Math.max(0, parseInt(v ?? "0", 10) || 0));
2008
+ this.time = {
2009
+ h: clamp(h?.value, 23),
2010
+ m: clamp(m?.value, 59),
2011
+ s: clamp(s?.value, 59)
2012
+ };
2013
+ const reattach = (iso) => iso.split("T")[0] ? `${iso.split("T")[0]}T${this.timeText()}` : iso;
2014
+ if (this.value) this.setAttribute("value", reattach(this.value));
2015
+ if (this.getAttr("start")) this.setAttribute("start", reattach(this.getAttr("start")));
2016
+ if (this.getAttr("end")) this.setAttribute("end", reattach(this.getAttr("end")));
2017
+ if (this.values.length) {
2018
+ this.values = this.values.map((v) => reattach(v));
2019
+ }
2020
+ const display2 = this.root.querySelector(".display");
2021
+ if (display2) display2.value = this.displayText();
2022
+ this.emitChange({ value: this.value });
2023
+ };
2024
+ [".t-h", ".t-m", ".t-s"].forEach((sel) => {
2025
+ this.root.querySelector(sel)?.addEventListener("input", applyTime);
2026
+ });
2027
+ }
2028
+ }
2029
+ emitChange(detail) {
2030
+ this.dispatchEvent(
2031
+ new CustomEvent("change", { bubbles: true, composed: true, detail })
2032
+ );
2033
+ }
2034
+ /**
2035
+ * Interpreta o texto digitado livremente e devolve ISO "YYYY-MM-DD"
2036
+ * (ou null se inválido). Aceita a máscara do atributo `format`
2037
+ * (tokens dd/mm/yyyy) ou ISO direto.
2038
+ */
2039
+ parseTyped(raw) {
2040
+ const text = raw.trim();
2041
+ if (!text) return null;
2042
+ const fmt = this.getAttr("format", "dd/mm/yyyy").toLowerCase();
2043
+ const order = ["dd", "mm", "yyyy"].filter((t) => fmt.includes(t));
2044
+ if (order.length < 3) return null;
2045
+ const parts = text.split(new RegExp(`[^0-9]+`)).filter(Boolean);
2046
+ if (parts.length !== 3) return null;
2047
+ const map = {};
2048
+ order.forEach((t, i) => map[t] = parts[i]);
2049
+ const iso = `${map["yyyy"]}-${(map["mm"] ?? "").padStart(2, "0")}-${(map["dd"] ?? "").padStart(2, "0")}`;
2050
+ const d = /* @__PURE__ */ new Date(`${iso}T12:00:00`);
2051
+ if (Number.isNaN(d.getTime())) return null;
2052
+ const [y, m, day] = iso.split("-").map((n) => parseInt(n, 10));
2053
+ if (d.getFullYear() !== y || d.getMonth() + 1 !== m || d.getDate() !== day) return null;
2054
+ const cmp = (a, b) => a.split("T")[0].localeCompare(b.split("T")[0]);
2055
+ const min = this.getAttr("min");
2056
+ const max = this.getAttr("max");
2057
+ if (min && cmp(iso, min) < 0) return null;
2058
+ if (max && cmp(iso, max) > 0) return null;
2059
+ return iso;
2060
+ }
2061
+ /** Aplica o texto digitado no modo free-text (blur/Enter). */
2062
+ commitTyped(input) {
2063
+ const parsed = this.parseTyped(input.value);
2064
+ if (!parsed) {
2065
+ input.classList.add("invalid");
2066
+ this.dispatchEvent(
2067
+ new CustomEvent("invalid", { bubbles: true, composed: true, detail: { text: input.value } })
2068
+ );
2069
+ input.value = this.displayText();
2070
+ setTimeout(() => input.classList.remove("invalid"), 1500);
2071
+ return;
2072
+ }
2073
+ if (this.mode === "single") {
2074
+ this.setAttribute("value", this.withTime(parsed));
2075
+ this.emitChange({ value: this.value });
2076
+ } else if (this.mode === "range") {
2077
+ if (!this.getAttr("start") || this.getAttr("start") && this.getAttr("end")) {
2078
+ this.setAttribute("start", this.withTime(parsed));
2079
+ this.removeAttribute("end");
2080
+ } else {
2081
+ const [a, b] = [this.getAttr("start").split("T")[0], parsed].sort();
2082
+ this.setAttribute("start", this.withTime(a));
2083
+ this.setAttribute("end", this.withTime(b));
2084
+ }
2085
+ this.emitChange({ start: this.getAttr("start"), end: this.getAttr("end") });
2086
+ } else {
2087
+ const list = this.values.filter((v) => v.split("T")[0] !== parsed);
2088
+ list.push(this.withTime(parsed));
2089
+ this.values = list;
2090
+ this.emitChange({ values: this.values });
2091
+ }
2092
+ this.render();
2093
+ }
2094
+ };
2095
+ _FxDatepicker.styles = css`
2096
+ :host {
2097
+ display: inline-block;
2098
+ vertical-align: middle;
2099
+ font-family: var(--fx-font-family);
2100
+ font-size: var(--fx-font-size);
2101
+ position: relative;
2102
+ }
2103
+ .field {
2104
+ display: inline-flex;
2105
+ align-items: center;
2106
+ gap: var(--fx-space-xs);
2107
+ width: 100%;
2108
+ box-sizing: border-box;
2109
+ /* Base = tamanho padrão (md): 40px */
2110
+ min-height: var(--fx-size-md, 40px);
2111
+ min-width: 150px;
2112
+ padding: var(--fx-space-xs) var(--fx-space-md);
2113
+ background-color: var(--fx-surface-background);
2114
+ border: 1px solid var(--fx-border-default);
2115
+ border-radius: var(--fx-radius-md);
2116
+ cursor: pointer;
2117
+ transition:
2118
+ border-color var(--fx-motion-duration-normal) var(--fx-motion-easing),
2119
+ box-shadow var(--fx-motion-duration-normal) var(--fx-motion-easing);
2120
+ }
2121
+ :host([size='sm']) .field { min-height: 32px; min-width: 180px; }
2122
+ :host([size='md']) .field { min-height: 40px; }
2123
+ :host([size='lg']) .field { min-height: 48px; min-width: 220px; font-size: calc(var(--fx-font-size) + 4px); }
2124
+ /* Range e multiple exibem mais de uma data: campo mais largo */
2125
+ :host([mode='range']) .field { min-width: 250px; }
2126
+ :host([mode='multiple']) .field { min-width: 220px; }
2127
+ :host([mode='range'][size='lg']) .field { min-width: 360px; }
2128
+ :host([mode='multiple'][size='lg']) .field { min-width: 320px; }
2129
+ :host([show-time]) .field { min-width: 250px; }
2130
+ /* Validação */
2131
+ :host([error]) .field,
2132
+ :host([invalid]) .field {
2133
+ border-color: var(--fx-color-danger, #dc2626);
2134
+ }
2135
+ :host([error]) .field:focus-within,
2136
+ :host([invalid]) .field:focus-within,
2137
+ :host([error][open]) .field,
2138
+ :host([invalid][open]) .field {
2139
+ border-color: var(--fx-color-danger, #dc2626);
2140
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--fx-color-danger, #dc2626) 18%, transparent);
2141
+ }
2142
+ :host([success]) .field,
2143
+ :host([valid]) .field {
2144
+ border-color: var(--fx-color-success, #16a34a);
2145
+ }
2146
+ :host([success]) .field:focus-within,
2147
+ :host([valid]) .field:focus-within,
2148
+ :host([success][open]) .field,
2149
+ :host([valid][open]) .field {
2150
+ border-color: var(--fx-color-success, #16a34a);
2151
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--fx-color-success, #16a34a) 18%, transparent);
2152
+ }
2153
+ .field:hover { border-color: var(--fx-border-hover); }
2154
+ .field:focus-within {
2155
+ outline: none;
2156
+ border-color: var(--fx-color-primary);
2157
+ box-shadow: var(--fx-effect-focus-ring, none);
2158
+ }
2159
+ .display {
2160
+ flex: 1;
2161
+ border: none;
2162
+ background: none;
2163
+ font: inherit;
2164
+ color: var(--fx-text-default);
2165
+ outline: none;
2166
+ cursor: pointer;
2167
+ text-align: left;
2168
+ padding: 0;
2169
+ min-width: 0;
2170
+ }
2171
+ .display::placeholder { color: var(--fx-text-muted); }
2172
+ .cal-icon { color: var(--fx-text-muted); display: inline-flex; pointer-events: none; }
2173
+ .clear {
2174
+ border: none;
2175
+ background: none;
2176
+ color: var(--fx-text-muted);
2177
+ cursor: pointer;
2178
+ font-size: calc(var(--fx-font-size) + 4px);
2179
+ line-height: 1;
2180
+ padding: 0 2px;
2181
+ border-radius: var(--fx-radius-full);
2182
+ }
2183
+ .clear:hover { color: var(--fx-color-danger); }
2184
+ .clear[hidden] { display: none; }
2185
+ .pop {
2186
+ position: absolute;
2187
+ top: calc(100% + 4px);
2188
+ left: 0;
2189
+ z-index: var(--fx-z-dropdown, 1000);
2190
+ display: none;
2191
+ background: var(--fx-surface-background);
2192
+ border: 1px solid var(--fx-border-default);
2193
+ border-radius: var(--fx-radius-md);
2194
+ box-shadow: var(--fx-shadow-lg);
2195
+ overflow: hidden;
2196
+ }
2197
+ :host([up]) .pop {
2198
+ top: auto;
2199
+ bottom: calc(100% + 4px);
2200
+ }
2201
+ :host([open]) .pop { display: block; }
2202
+ .time-row {
2203
+ display: flex;
2204
+ align-items: center;
2205
+ gap: var(--fx-space-xs);
2206
+ padding: var(--fx-space-sm) var(--fx-space-md);
2207
+ border-top: 1px solid var(--fx-border-default);
2208
+ background: var(--fx-surface-background);
2209
+ border-radius: 0 0 var(--fx-radius-md) var(--fx-radius-md);
2210
+ font-size: calc(var(--fx-font-size) - 2px);
2211
+ color: var(--fx-text-default);
2212
+ }
2213
+ .time-row label { color: var(--fx-text-muted); }
2214
+ .time-row input {
2215
+ width: 38px;
2216
+ font: inherit;
2217
+ color: var(--fx-text-default);
2218
+ background: var(--fx-surface-background);
2219
+ border: 1px solid var(--fx-border-default);
2220
+ border-radius: var(--fx-radius-sm);
2221
+ padding: 2px 2px;
2222
+ outline: none;
2223
+ text-align: center;
2224
+ -moz-appearance: textfield;
2225
+ appearance: textfield;
2226
+ }
2227
+ .time-row input::-webkit-outer-spin-button,
2228
+ .time-row input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
2229
+ .time-row .sep { color: var(--fx-text-muted); font-weight: bold; padding: 0 1px; }
2230
+ .time-row input:focus {
2231
+ border-color: var(--fx-color-primary);
2232
+ box-shadow: var(--fx-effect-focus-ring, none);
2233
+ }
2234
+ :host([disabled]) .field { opacity: 0.55; cursor: not-allowed; background: var(--fx-surface-surface-hover); }
2235
+ :host([disabled]) .display { cursor: not-allowed; }
2236
+ :host([free-text]) .display { cursor: text; }
2237
+ :host([free-text]) .display.invalid { border-bottom: 2px solid var(--fx-color-danger); }
2238
+ `;
2239
+ let FxDatepicker = _FxDatepicker;
2240
+ function defineFxDatepicker() {
2241
+ return defineElement("fx-datepicker", FxDatepicker);
2242
+ }
2243
+ defineFxDatepicker();
2244
+ const _FxCheckbox = class _FxCheckbox extends FxElement {
2245
+ static get observedAttributes() {
2246
+ return ["checked", "disabled", "indeterminate"];
2247
+ }
2248
+ get checked() {
2249
+ return this.hasAttr("checked");
2250
+ }
2251
+ set checked(value) {
2252
+ this.toggleAttr("checked", Boolean(value));
2253
+ }
2254
+ get indeterminate() {
2255
+ return this.hasAttr("indeterminate");
2256
+ }
2257
+ set indeterminate(value) {
2258
+ this.toggleAttr("indeterminate", Boolean(value));
2259
+ }
2260
+ get disabled() {
2261
+ return this.hasAttr("disabled");
2262
+ }
2263
+ set disabled(value) {
2264
+ this.toggleAttr("disabled", Boolean(value));
2265
+ }
2266
+ /** Tamanho do controle. Padrão: `'md'`. */
2267
+ get size() {
2268
+ const s = this.getAttr("size", "md");
2269
+ return s === "sm" || s === "lg" ? s : "md";
2270
+ }
2271
+ set size(value) {
2272
+ this.setAttribute("size", value);
2273
+ }
2274
+ render() {
2275
+ const px = this.size === "sm" ? "14px" : this.size === "lg" ? "22px" : "18px";
2276
+ if (!this.style.getPropertyValue("--fx-size-checkbox")) {
2277
+ this.style.setProperty("--fx-size-checkbox", px);
2278
+ }
2279
+ this.setTemplate(`
2280
+ <span class="box" part="box" role="checkbox" tabindex="0"
2281
+ aria-checked="${this.indeterminate ? "mixed" : this.checked}"
2282
+ aria-disabled="${this.disabled}">
2283
+ <span class="control" part="control">
2284
+ <span class="mark">${this.indeterminate ? "–" : "✓"}</span>
2285
+ </span>
2286
+ <span class="label" part="label"><slot></slot></span>
2287
+ </span>
2288
+ `);
2289
+ const box = this.root.querySelector(".box");
2290
+ if (!box) return;
2291
+ box.addEventListener("click", () => this.toggle());
2292
+ box.addEventListener("keydown", (e) => {
2293
+ if (e.key === " " || e.key === "Enter") {
2294
+ e.preventDefault();
2295
+ this.toggle();
2296
+ }
2297
+ });
2298
+ }
2299
+ toggle() {
2300
+ if (this.disabled) return;
2301
+ this.indeterminate = false;
2302
+ this.checked = !this.checked;
2303
+ this.dispatchEvent(
2304
+ new CustomEvent("change", {
2305
+ bubbles: true,
2306
+ composed: true,
2307
+ detail: { checked: this.checked, value: this.getAttr("value") }
2308
+ })
2309
+ );
2310
+ }
2311
+ };
2312
+ _FxCheckbox.styles = css`
2313
+ :host {
2314
+ display: inline-flex;
2315
+ vertical-align: middle;
2316
+ font-family: var(--fx-font-family);
2317
+ font-size: var(--fx-font-size);
2318
+ }
2319
+ .box {
2320
+ display: inline-flex;
2321
+ align-items: center;
2322
+ gap: var(--fx-space-sm);
2323
+ cursor: pointer;
2324
+ user-select: none;
2325
+ color: var(--fx-text-default);
2326
+ -webkit-tap-highlight-color: transparent;
2327
+ }
2328
+ .control {
2329
+ width: var(--fx-size-checkbox, 18px);
2330
+ height: var(--fx-size-checkbox, 18px);
2331
+ flex-shrink: 0;
2332
+ display: inline-flex;
2333
+ align-items: center;
2334
+ justify-content: center;
2335
+ border: 2px solid var(--fx-border-hover);
2336
+ border-radius: calc(var(--fx-radius-sm) + 2px);
2337
+ background: var(--fx-surface-background);
2338
+ color: #fff;
2339
+ font-size: calc(var(--fx-size-checkbox, 18px) - 6px);
2340
+ line-height: 1;
2341
+ transition:
2342
+ background-color var(--fx-motion-duration-fast) var(--fx-motion-easing),
2343
+ border-color var(--fx-motion-duration-fast) var(--fx-motion-easing),
2344
+ box-shadow var(--fx-motion-duration-fast) var(--fx-motion-easing);
2345
+ }
2346
+ .box:hover .control { border-color: var(--fx-color-primary); }
2347
+ .box:focus-visible .control {
2348
+ outline: none;
2349
+ box-shadow: var(--fx-effect-focus-ring, none);
2350
+ }
2351
+ :host([checked]) .control {
2352
+ background: var(--fx-color-primary);
2353
+ border-color: var(--fx-color-primary);
2354
+ }
2355
+ :host([indeterminate]) .control {
2356
+ background: var(--fx-color-primary);
2357
+ border-color: var(--fx-color-primary);
2358
+ }
2359
+ /* Validação */
2360
+ :host([error]) .control,
2361
+ :host([invalid]) .control { border-color: var(--fx-color-danger, #dc2626); }
2362
+ :host([success]) .control,
2363
+ :host([valid]) .control { border-color: var(--fx-color-success, #16a34a); }
2364
+ .mark { visibility: hidden; }
2365
+ :host([checked]) .mark,
2366
+ :host([indeterminate]) .mark { visibility: visible; }
2367
+ .label { display: inline-flex; align-items: center; }
2368
+ :host([disabled]) .box {
2369
+ opacity: 0.55;
2370
+ cursor: not-allowed;
2371
+ pointer-events: none;
2372
+ }
2373
+ `;
2374
+ let FxCheckbox = _FxCheckbox;
2375
+ function defineFxCheckbox() {
2376
+ return defineElement("fx-checkbox", FxCheckbox);
2377
+ }
2378
+ defineFxCheckbox();
2379
+ const _FxRadio = class _FxRadio extends FxElement {
2380
+ static get observedAttributes() {
2381
+ return ["checked", "disabled"];
2382
+ }
2383
+ get checked() {
2384
+ return this.hasAttr("checked");
2385
+ }
2386
+ set checked(value) {
2387
+ this.toggleAttr("checked", Boolean(value));
2388
+ }
2389
+ get disabled() {
2390
+ return this.hasAttr("disabled");
2391
+ }
2392
+ set disabled(value) {
2393
+ this.toggleAttr("disabled", Boolean(value));
2394
+ }
2395
+ /** Tamanho do controle. Padrão: `'md'`. */
2396
+ get size() {
2397
+ const s = this.getAttr("size", "md");
2398
+ return s === "sm" || s === "lg" ? s : "md";
2399
+ }
2400
+ set size(value) {
2401
+ this.setAttribute("size", value);
2402
+ }
2403
+ render() {
2404
+ const px = this.size === "sm" ? "14px" : this.size === "lg" ? "22px" : "18px";
2405
+ if (!this.style.getPropertyValue("--fx-size-radio")) {
2406
+ this.style.setProperty("--fx-size-radio", px);
2407
+ }
2408
+ this.setTemplate(`
2409
+ <span class="box" part="box" role="radio" tabindex="0"
2410
+ aria-checked="${this.checked}" aria-disabled="${this.disabled}">
2411
+ <span class="control" part="control"><span class="dot" part="dot"></span></span>
2412
+ <span class="label" part="label"><slot></slot></span>
2413
+ </span>
2414
+ `);
2415
+ const box = this.root.querySelector(".box");
2416
+ if (!box) return;
2417
+ box.addEventListener("click", () => this.select());
2418
+ box.addEventListener("keydown", (e) => {
2419
+ if (e.key === " " || e.key === "Enter") {
2420
+ e.preventDefault();
2421
+ this.select();
2422
+ }
2423
+ });
2424
+ }
2425
+ select() {
2426
+ if (this.disabled || this.checked) return;
2427
+ const name = this.getAttr("name");
2428
+ if (name) {
2429
+ document.querySelectorAll(`fx-radio[name="${name}"]`).forEach((r) => {
2430
+ r.checked = false;
2431
+ });
2432
+ }
2433
+ this.checked = true;
2434
+ this.dispatchEvent(
2435
+ new CustomEvent("change", {
2436
+ bubbles: true,
2437
+ composed: true,
2438
+ detail: { checked: true, value: this.getAttr("value") }
2439
+ })
2440
+ );
2441
+ }
2442
+ };
2443
+ _FxRadio.styles = css`
2444
+ :host {
2445
+ display: inline-flex;
2446
+ vertical-align: middle;
2447
+ font-family: var(--fx-font-family);
2448
+ font-size: var(--fx-font-size);
2449
+ }
2450
+ .box {
2451
+ display: inline-flex;
2452
+ align-items: center;
2453
+ gap: var(--fx-space-sm);
2454
+ cursor: pointer;
2455
+ user-select: none;
2456
+ color: var(--fx-text-default);
2457
+ -webkit-tap-highlight-color: transparent;
2458
+ }
2459
+ .control {
2460
+ width: var(--fx-size-radio, 18px);
2461
+ height: var(--fx-size-radio, 18px);
2462
+ flex-shrink: 0;
2463
+ display: inline-flex;
2464
+ align-items: center;
2465
+ justify-content: center;
2466
+ border: 2px solid var(--fx-border-hover);
2467
+ border-radius: var(--fx-radius-full);
2468
+ background: var(--fx-surface-background);
2469
+ transition:
2470
+ border-color var(--fx-motion-duration-fast) var(--fx-motion-easing),
2471
+ box-shadow var(--fx-motion-duration-fast) var(--fx-motion-easing);
2472
+ }
2473
+ .box:hover .control { border-color: var(--fx-color-primary); }
2474
+ .box:focus-visible .control {
2475
+ outline: none;
2476
+ box-shadow: var(--fx-effect-focus-ring, none);
2477
+ }
2478
+ .dot {
2479
+ width: calc(100% - 8px);
2480
+ height: calc(100% - 8px);
2481
+ border-radius: var(--fx-radius-full);
2482
+ background: var(--fx-color-primary);
2483
+ transform: scale(0);
2484
+ transition: transform var(--fx-motion-duration-fast) var(--fx-motion-easing);
2485
+ }
2486
+ :host([checked]) .control { border-color: var(--fx-color-primary); }
2487
+ :host([checked]) .dot { transform: scale(1); }
2488
+ /* Validação */
2489
+ :host([error]) .control,
2490
+ :host([invalid]) .control { border-color: var(--fx-color-danger, #dc2626); }
2491
+ :host([success]) .control,
2492
+ :host([valid]) .control { border-color: var(--fx-color-success, #16a34a); }
2493
+ .label { display: inline-flex; align-items: center; }
2494
+ :host([disabled]) .box {
2495
+ opacity: 0.55;
2496
+ cursor: not-allowed;
2497
+ pointer-events: none;
2498
+ }
2499
+ `;
2500
+ let FxRadio = _FxRadio;
2501
+ function defineFxRadio() {
2502
+ return defineElement("fx-radio", FxRadio);
2503
+ }
2504
+ defineFxRadio();
2505
+ const _FxTable = class _FxTable extends FxElement {
2506
+ constructor() {
2507
+ super(...arguments);
2508
+ this.data = [];
2509
+ this.page = 0;
2510
+ this.sortField = "";
2511
+ this.sortDir = 1;
2512
+ this.filters = {};
2513
+ }
2514
+ static get observedAttributes() {
2515
+ return ["pagination", "rows", "rows-options", "striped", "empty-message", "pagination-position"];
2516
+ }
2517
+ get rowsPerPage() {
2518
+ const r = Number(this.getAttr("rows", "10"));
2519
+ return r > 0 ? r : 10;
2520
+ }
2521
+ set rowsPerPage(value) {
2522
+ this.setAttribute("rows", String(value));
2523
+ }
2524
+ /** Colunas declaradas no light DOM (<fx-column>). */
2525
+ get columns() {
2526
+ return [...this.querySelectorAll("fx-column")].map((c) => ({
2527
+ field: c.getAttribute("field") ?? "",
2528
+ header: c.getAttribute("header") ?? c.getAttribute("field") ?? "",
2529
+ sortable: c.hasAttribute("sortable"),
2530
+ filterable: c.hasAttribute("filterable"),
2531
+ align: c.getAttribute("align") ?? "left",
2532
+ template: c.querySelector("template") ?? void 0
2533
+ }));
2534
+ }
2535
+ connectedCallback() {
2536
+ super.connectedCallback();
2537
+ if (!this.data.length) {
2538
+ try {
2539
+ const json = this.getAttr("data");
2540
+ if (json) this.data = JSON.parse(json);
2541
+ } catch {
2542
+ }
2543
+ }
2544
+ new MutationObserver(() => this.render()).observe(this, { childList: true, subtree: true });
2545
+ }
2546
+ /** Pipeline: filtra → ordena → pagina. */
2547
+ computeRows() {
2548
+ let out = [...this.data];
2549
+ for (const [field, term] of Object.entries(this.filters)) {
2550
+ if (!term) continue;
2551
+ const t = term.toLowerCase();
2552
+ out = out.filter((r) => String(r[field] ?? "").toLowerCase().includes(t));
2553
+ }
2554
+ const total = out.length;
2555
+ if (this.sortField) {
2556
+ out.sort((a, b) => {
2557
+ const av = a[this.sortField], bv = b[this.sortField];
2558
+ if (typeof av === "number" && typeof bv === "number") return (av - bv) * this.sortDir;
2559
+ return String(av ?? "").localeCompare(String(bv ?? ""), "pt-BR") * this.sortDir;
2560
+ });
2561
+ }
2562
+ if (this.hasAttr("pagination")) {
2563
+ const start = this.page * this.rowsPerPage;
2564
+ out = out.slice(start, start + this.rowsPerPage);
2565
+ }
2566
+ return { rows: out, total };
2567
+ }
2568
+ /** Célula: template customizado ({{value}}, {{campo}}, {{row}}) ou valor direto. */
2569
+ cellHtml(col, row) {
2570
+ const value = row[col.field];
2571
+ if (col.template) {
2572
+ return col.template.innerHTML.replace(/\{\{(\w+)\}\}/g, (_, k) => k === "value" ? String(value ?? "") : k === "row" ? "" : String(row[k] ?? ""));
2573
+ }
2574
+ return String(value ?? "");
2575
+ }
2576
+ render() {
2577
+ const cols = this.columns.filter((c) => c.field);
2578
+ const { rows, total } = this.computeRows();
2579
+ const paginated = this.hasAttr("pagination");
2580
+ const pages = Math.max(1, Math.ceil(total / this.rowsPerPage));
2581
+ if (this.page >= pages) this.page = pages - 1;
2582
+ const headHtml = cols.map((c) => {
2583
+ const ind = this.sortField === c.field ? this.sortDir === 1 ? "▲" : "▼" : "";
2584
+ return `<th part="header" class="${c.sortable ? "sortable" : ""}" style="text-align:${c.align}" data-field="${c.field}">${c.header}${ind ? `<span class="sort-ind">${ind}</span>` : ""}</th>`;
2585
+ }).join("");
2586
+ 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>` : "";
2587
+ const bodyHtml = rows.length ? rows.map((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>`).join("") : `<tr><td class="empty" colspan="${cols.length || 1}">${this.getAttr("empty-message", "Nenhum registro encontrado")}</td></tr>`;
2588
+ const pagerHtml = !paginated ? "" : `
2589
+ <div class="pager" part="pager">
2590
+ <span class="info">Página ${this.page + 1} de ${pages} · ${total} registros</span>
2591
+ <button type="button" class="pg-btn" data-pg="first" ${this.page === 0 ? "disabled" : ""}>«</button>
2592
+ <button type="button" class="pg-btn" data-pg="prev" ${this.page === 0 ? "disabled" : ""}>‹</button>
2593
+ ${Array.from({ length: pages }, (_, p) => `<button type="button" class="pg-btn" data-pg="${p}" aria-current="${p === this.page}">${p + 1}</button>`).join("")}
2594
+ <button type="button" class="pg-btn" data-pg="next" ${this.page >= pages - 1 ? "disabled" : ""}>›</button>
2595
+ <button type="button" class="pg-btn" data-pg="last" ${this.page >= pages - 1 ? "disabled" : ""}>»</button>
2596
+ <label class="info">Por página:
2597
+ <select class="rows-sel">${this.getAttr("rows-options", "5,10,20,50").split(",").map((n) => n.trim()).map((n) => `<option value="${n}" ${Number(n) === this.rowsPerPage ? "selected" : ""}>${n}</option>`).join("")}</select>
2598
+ </label>
2599
+ </div>`;
2600
+ this.setTemplate(`
2601
+ <div class="scroll">
2602
+ <table part="table">
2603
+ <thead><tr>${headHtml}</tr>${filterRow}</thead>
2604
+ <tbody>${bodyHtml}</tbody>
2605
+ </table>
2606
+ </div>
2607
+ ${pagerHtml}
2608
+ `);
2609
+ this.root.querySelectorAll("th.sortable").forEach((th) => {
2610
+ th.addEventListener("click", () => {
2611
+ const f = th.dataset.field;
2612
+ if (this.sortField === f) this.sortDir = this.sortDir === 1 ? -1 : 1;
2613
+ else {
2614
+ this.sortField = f;
2615
+ this.sortDir = 1;
2616
+ }
2617
+ this.render();
2618
+ this.dispatchEvent(new CustomEvent("sort-change", {
2619
+ bubbles: true,
2620
+ composed: true,
2621
+ detail: { field: this.sortField, direction: this.sortDir === 1 ? "asc" : "desc" }
2622
+ }));
2623
+ });
2624
+ });
2625
+ this.root.querySelectorAll(".filter").forEach((inp) => {
2626
+ inp.addEventListener("input", () => {
2627
+ this.filters[inp.dataset.field] = inp.value;
2628
+ this.page = 0;
2629
+ const field = inp.dataset.field;
2630
+ const pos = inp.selectionStart;
2631
+ this.render();
2632
+ const again = this.root.querySelector(`.filter[data-field="${field}"]`);
2633
+ if (again) {
2634
+ again.focus();
2635
+ again.setSelectionRange(pos, pos);
2636
+ }
2637
+ });
2638
+ inp.addEventListener("click", (e) => e.stopPropagation());
2639
+ });
2640
+ this.root.querySelectorAll(".pg-btn").forEach((btn) => {
2641
+ btn.addEventListener("click", () => {
2642
+ const pg = btn.dataset.pg;
2643
+ 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);
2644
+ this.render();
2645
+ this.dispatchEvent(new CustomEvent("page-change", {
2646
+ bubbles: true,
2647
+ composed: true,
2648
+ detail: { page: this.page + 1, pages, rowsPerPage: this.rowsPerPage, total }
2649
+ }));
2650
+ });
2651
+ });
2652
+ this.root.querySelector(".rows-sel")?.addEventListener("change", (e) => {
2653
+ this.rowsPerPage = Number(e.target.value);
2654
+ this.page = 0;
2655
+ this.render();
2656
+ });
2657
+ this.root.querySelectorAll("tbody tr[data-index]").forEach((tr) => {
2658
+ tr.addEventListener("click", () => {
2659
+ this.dispatchEvent(new CustomEvent("row-click", {
2660
+ bubbles: true,
2661
+ composed: true,
2662
+ detail: { row: rows[Number(tr.dataset.index)], index: Number(tr.dataset.index) }
2663
+ }));
2664
+ });
2665
+ });
2666
+ }
2667
+ };
2668
+ _FxTable.styles = css`
2669
+ :host {
2670
+ display: block;
2671
+ font-family: var(--fx-font-family);
2672
+ font-size: var(--fx-font-size);
2673
+ color: var(--fx-text-default);
2674
+ }
2675
+ .scroll { overflow-x: auto; border: 1px solid var(--fx-border-default); border-radius: var(--fx-radius-md); }
2676
+ table { width: 100%; border-collapse: collapse; }
2677
+ th {
2678
+ text-align: left;
2679
+ padding: var(--fx-space-sm) var(--fx-space-md);
2680
+ background: var(--fx-surface-background);
2681
+ border-bottom: 2px solid var(--fx-border-default);
2682
+ white-space: nowrap;
2683
+ user-select: none;
2684
+ }
2685
+ th.sortable { cursor: pointer; }
2686
+ th.sortable:hover { color: var(--fx-color-primary); }
2687
+ .sort-ind { font-size: calc(var(--fx-font-size) - 4px); margin-left: var(--fx-space-3xs, 2px); }
2688
+ .filter-row th { padding: var(--fx-space-3xs, 4px) var(--fx-space-md) var(--fx-space-sm); border-bottom-width: 1px; }
2689
+ .filter {
2690
+ width: 100%;
2691
+ box-sizing: border-box;
2692
+ font: inherit;
2693
+ font-size: calc(var(--fx-font-size) - 2px);
2694
+ color: var(--fx-text-default);
2695
+ background: var(--fx-surface-background);
2696
+ border: 1px solid var(--fx-border-default);
2697
+ border-radius: var(--fx-radius-sm);
2698
+ padding: var(--fx-space-3xs, 4px) var(--fx-space-xs);
2699
+ outline: none;
2700
+ }
2701
+ .filter:focus { border-color: var(--fx-color-primary); box-shadow: var(--fx-effect-focus-ring, none); }
2702
+ td {
2703
+ padding: var(--fx-space-sm) var(--fx-space-md);
2704
+ border-bottom: 1px solid var(--fx-border-default);
2705
+ }
2706
+ tbody tr { cursor: pointer; transition: background-color var(--fx-motion-duration-fast) var(--fx-motion-easing); }
2707
+ tbody tr:hover { background: color-mix(in srgb, var(--fx-color-primary) 8%, transparent); }
2708
+ :host([striped]) tbody tr:nth-child(even) { background: var(--fx-surface-background); }
2709
+ :host([striped]) tbody tr:hover { background: color-mix(in srgb, var(--fx-color-primary) 8%, transparent); }
2710
+ .empty { text-align: center; padding: var(--fx-space-xl); color: var(--fx-text-muted); }
2711
+ .pager {
2712
+ display: flex;
2713
+ align-items: center;
2714
+ gap: var(--fx-space-sm);
2715
+ flex-wrap: wrap;
2716
+ padding: var(--fx-space-sm) 0;
2717
+ }
2718
+ :host([pagination-position='center']) .pager { justify-content: center; }
2719
+ :host([pagination-position='right']) .pager { justify-content: flex-end; }
2720
+ .pager .info { color: var(--fx-text-muted); font-size: calc(var(--fx-font-size) - 2px); }
2721
+ .pg-btn {
2722
+ font: inherit;
2723
+ font-size: calc(var(--fx-font-size) - 2px);
2724
+ color: var(--fx-text-default);
2725
+ background: var(--fx-surface-background);
2726
+ border: 1px solid var(--fx-border-default);
2727
+ border-radius: var(--fx-radius-sm);
2728
+ padding: var(--fx-space-3xs, 4px) var(--fx-space-sm);
2729
+ cursor: pointer;
2730
+ transition: border-color var(--fx-motion-duration-fast) var(--fx-motion-easing);
2731
+ }
2732
+ .pg-btn:hover:not(:disabled) { border-color: var(--fx-color-primary); color: var(--fx-color-primary); }
2733
+ .pg-btn:disabled { opacity: 0.5; cursor: not-allowed; }
2734
+ .pg-btn[aria-current='true'] {
2735
+ background: var(--fx-color-primary);
2736
+ border-color: var(--fx-color-primary);
2737
+ color: #fff;
2738
+ font-weight: var(--fx-font-weight);
2739
+ }
2740
+ .rows-sel {
2741
+ font: inherit;
2742
+ font-size: calc(var(--fx-font-size) - 2px);
2743
+ color: var(--fx-text-default);
2744
+ background: var(--fx-surface-background);
2745
+ border: 1px solid var(--fx-border-default);
2746
+ border-radius: var(--fx-radius-sm);
2747
+ padding: 2px var(--fx-space-xs);
2748
+ outline: none;
2749
+ }
2750
+ `;
2751
+ let FxTable = _FxTable;
2752
+ function defineFxTable() {
2753
+ return defineElement("fx-table", FxTable);
2754
+ }
2755
+ defineFxTable();
2756
+ const _FxFloatlabel = class _FxFloatlabel extends FxElement {
2757
+ constructor() {
2758
+ super(...arguments);
2759
+ this.targetControl = null;
2760
+ this.observer = null;
2761
+ this.listenersReady = false;
2762
+ }
2763
+ static get observedAttributes() {
2764
+ return ["variant", "error", "invalid", "success", "valid"];
2765
+ }
2766
+ render() {
2767
+ const control = this.findControl();
2768
+ const label = this.findLabel();
2769
+ const originalLabel = label;
2770
+ const labelText = originalLabel ? originalLabel.textContent?.replace(/\s+/g, " ").trim() : "";
2771
+ const labelFor = originalLabel ? originalLabel.getAttribute("for") || "" : "";
2772
+ const isOver = this.getAttribute("variant") === "over";
2773
+ this.setTemplate(`
2774
+ <div class="wrapper" part="wrapper">
2775
+ ${isOver ? `<label class="flabel" part="label" for="${labelFor}">${labelText}</label>` : ""}
2776
+ <div class="field-box" part="field">
2777
+ <slot></slot>
2778
+ ${isOver ? "" : `<label class="flabel" part="label" for="${labelFor}">${labelText}</label>`}
2779
+ </div>
2780
+ <div class="error-message" part="error"></div>
2781
+ </div>
2782
+ `);
2783
+ if (originalLabel) originalLabel.setAttribute("hidden", "");
2784
+ this.attachEvents();
2785
+ this.syncControlReference(control);
2786
+ this.syncState();
2787
+ this.applyError();
2788
+ }
2789
+ disconnectedCallback() {
2790
+ super.disconnectedCallback();
2791
+ this.observer?.disconnect();
2792
+ }
2793
+ /** Campo de controle = qualquer filho direto que não seja label. */
2794
+ findControl() {
2795
+ return Array.from(this.children).find(
2796
+ (n) => n.tagName.toLowerCase() !== "label"
2797
+ ) ?? null;
2798
+ }
2799
+ /** Label = filho direto <label>. */
2800
+ findLabel() {
2801
+ return Array.from(this.children).find(
2802
+ (n) => n.tagName.toLowerCase() === "label"
2803
+ ) ?? null;
2804
+ }
2805
+ attachEvents() {
2806
+ const slot = this.root.querySelector("slot");
2807
+ if (slot) {
2808
+ slot.addEventListener("slotchange", () => {
2809
+ const control = this.findControl();
2810
+ this.syncControlReference(control);
2811
+ this.syncState();
2812
+ this.applyError();
2813
+ });
2814
+ }
2815
+ if (this.listenersReady) return;
2816
+ this.listenersReady = true;
2817
+ this.addEventListener("focusin", () => this.syncState());
2818
+ this.addEventListener(
2819
+ "focusout",
2820
+ () => setTimeout(() => this.syncState(), 60)
2821
+ );
2822
+ this.addEventListener("input", () => this.syncState());
2823
+ this.addEventListener("change", () => this.syncState());
2824
+ }
2825
+ /** Observa mudanças de atributos do controle (value, open, values...). */
2826
+ syncControlReference(control = this.targetControl) {
2827
+ if (control !== this.targetControl) {
2828
+ this.observer?.disconnect();
2829
+ this.observer = null;
2830
+ this.targetControl = control;
2831
+ if (control) {
2832
+ this.observer = new MutationObserver(() => this.syncState());
2833
+ this.observer.observe(control, {
2834
+ attributes: true,
2835
+ attributeFilter: ["value", "values", "start", "end", "open"]
2836
+ });
2837
+ }
2838
+ }
2839
+ }
2840
+ syncState() {
2841
+ if (this.targetControl && !this.targetControl.isConnected) {
2842
+ this.targetControl = null;
2843
+ this.observer?.disconnect();
2844
+ this.observer = null;
2845
+ }
2846
+ if (!this.targetControl) {
2847
+ this.removeAttribute("active");
2848
+ this.removeAttribute("focus");
2849
+ return;
2850
+ }
2851
+ const c = this.targetControl;
2852
+ const attrValue = c.getAttribute ? (c.getAttribute("value") ?? "").trim() : "";
2853
+ const attrValues = c.getAttribute ? (c.getAttribute("values") ?? "").trim() : "";
2854
+ const hasValue = Boolean(
2855
+ typeof c.value === "string" && c.value.trim() !== "" || Array.isArray(c.values) && c.values.length > 0 || attrValue !== "" || attrValues !== "" || c.hasAttribute && (c.hasAttribute("open") || c.hasAttribute("start"))
2856
+ );
2857
+ const focused = this.isFocusedInside();
2858
+ if (hasValue || focused) {
2859
+ this.setAttribute("active", "");
2860
+ if (focused) this.setAttribute("focus", "");
2861
+ else this.removeAttribute("focus");
2862
+ } else {
2863
+ this.removeAttribute("active");
2864
+ this.removeAttribute("focus");
2865
+ }
2866
+ }
2867
+ applyError() {
2868
+ const hasError = this.hasAttr("error") || this.hasAttr("invalid");
2869
+ const errMsg = this.root.querySelector(
2870
+ ".error-message"
2871
+ );
2872
+ if (errMsg) {
2873
+ errMsg.textContent = hasError ? this.getAttribute("error-text")?.trim() || "Valor inválido" : "";
2874
+ }
2875
+ if (this.targetControl && typeof this.targetControl.setAttribute === "function") {
2876
+ const t = this.targetControl;
2877
+ if (hasError) {
2878
+ t.setAttribute("error", "");
2879
+ t.removeAttribute("success");
2880
+ } else if (this.hasAttr("success") || this.hasAttr("valid")) {
2881
+ t.setAttribute("success", "");
2882
+ t.removeAttribute("error");
2883
+ } else {
2884
+ t.removeAttribute("error");
2885
+ t.removeAttribute("success");
2886
+ }
2887
+ }
2888
+ }
2889
+ isWithin(activeEl) {
2890
+ let current = activeEl;
2891
+ while (current && current !== this) {
2892
+ current = current.parentElement;
2893
+ }
2894
+ return current === this;
2895
+ }
2896
+ isFocusedInside() {
2897
+ const activeEl = document.activeElement;
2898
+ return Boolean(activeEl && this.isWithin(activeEl));
2899
+ }
2900
+ };
2901
+ _FxFloatlabel.styles = css`
2902
+ :host {
2903
+ display: inline-block;
2904
+ position: relative;
2905
+ font-family: var(--fx-font-family);
2906
+ font-size: var(--fx-font-size);
2907
+ vertical-align: middle;
2908
+ }
2909
+ .wrapper {
2910
+ position: relative;
2911
+ display: inline-block;
2912
+ width: 100%;
2913
+ }
2914
+ /* Caixa do campo: referência de posicionamento da label flutuante
2915
+ (isolada da mensagem de erro, que cresce o wrapper). */
2916
+ .field-box {
2917
+ position: relative;
2918
+ display: inline-block;
2919
+ width: 100%;
2920
+ }
2921
+ :host([variant="in"]) .field-box {
2922
+ padding-top: var(--fx-space-xs, 6px);
2923
+ }
2924
+
2925
+ /* ---- label base (variant="on", padrão): sobre a borda superior ---- */
2926
+ .flabel {
2927
+ position: absolute;
2928
+ left: var(--fx-space-md, 12px);
2929
+ top: 0;
2930
+ transform: translateY(-50%);
2931
+ font-size: calc(var(--fx-font-size, 14px) - 3px);
2932
+ font-weight: 600;
2933
+ color: var(--fx-text-muted, #94a3b8);
2934
+ background-color: var(--fx-surface-background, #fff);
2935
+ padding: 0 4px;
2936
+ line-height: 1;
2937
+ border-radius: 2px;
2938
+ pointer-events: none;
2939
+ z-index: 2;
2940
+ white-space: nowrap;
2941
+ transition:
2942
+ top var(--fx-motion-duration-normal, 180ms)
2943
+ var(--fx-motion-easing, ease-in-out),
2944
+ transform var(--fx-motion-duration-normal, 180ms)
2945
+ var(--fx-motion-easing, ease-in-out),
2946
+ font-size var(--fx-motion-duration-normal, 180ms)
2947
+ var(--fx-motion-easing, ease-in-out),
2948
+ color var(--fx-motion-duration-normal, 180ms)
2949
+ var(--fx-motion-easing, ease-in-out);
2950
+ }
2951
+ :host([active]) .flabel {
2952
+ color: var(--fx-color-primary, #4f46e5);
2953
+ }
2954
+
2955
+ /* ---- variant="in": label como placeholder dentro, sobe ao focar/valor ---- */
2956
+ :host([variant="in"]) .flabel {
2957
+ top: 50%;
2958
+ transform: translateY(-50%);
2959
+ font-size: var(--fx-font-size, 14px);
2960
+ font-weight: var(--fx-font-weight, 400);
2961
+ background: transparent;
2962
+ padding: 0;
2963
+ }
2964
+ :host([variant="in"][active]) .flabel {
2965
+ top: 0;
2966
+ transform: translateY(-50%);
2967
+ font-size: calc(var(--fx-font-size, 14px) - 3px);
2968
+ font-weight: 600;
2969
+ background-color: var(--fx-surface-background, #fff);
2970
+ padding: 0 5px;
2971
+ color: var(--fx-color-primary, #4f46e5);
2972
+ }
2973
+
2974
+ /* ---- variant="over": label estática ACIMA do campo (fora, em fluxo) ---- */
2975
+ :host([variant="over"]) {
2976
+ display: block;
2977
+ }
2978
+ :host([variant="over"]) .flabel {
2979
+ position: static;
2980
+ display: block;
2981
+ margin-bottom: var(--fx-space-xs, 4px);
2982
+ background: transparent;
2983
+ padding: 0;
2984
+ font-size: calc(var(--fx-font-size, 14px) - 3px);
2985
+ font-weight: 600;
2986
+ white-space: normal;
2987
+ }
2988
+
2989
+ /* ---- estados de erro / sucesso ---- */
2990
+ :host([error]) .flabel,
2991
+ :host([invalid]) .flabel {
2992
+ color: var(--fx-color-danger, #dc2626);
2993
+ }
2994
+ :host([success]) .flabel,
2995
+ :host([valid]) .flabel {
2996
+ color: var(--fx-color-success, #10b981);
2997
+ }
2998
+
2999
+ /* ---- mensagem de erro sob o campo (fonte menor) ---- */
3000
+ .error-message {
3001
+ display: none;
3002
+ margin-top: var(--fx-space-xs, 4px);
3003
+ margin-left: var(--fx-space-md, 12px);
3004
+ font-size: calc(var(--fx-font-size, 14px) - 3px);
3005
+ font-weight: 500;
3006
+ color: var(--fx-color-danger, #dc2626);
3007
+ line-height: 1.2;
3008
+ }
3009
+ :host([error]) .error-message,
3010
+ :host([invalid]) .error-message {
3011
+ display: block;
3012
+ }
3013
+ `;
3014
+ let FxFloatlabel = _FxFloatlabel;
3015
+ function defineFxFloatlabel() {
3016
+ return defineElement("fx-floatlabel", FxFloatlabel);
3017
+ }
3018
+ defineFxFloatlabel();
3019
+ const _FxTextarea = class _FxTextarea extends FxElement {
3020
+ static get observedAttributes() {
3021
+ return ["size", "placeholder", "disabled", "readonly", "rows", "maxlength"];
3022
+ }
3023
+ get size() {
3024
+ const s = this.getAttr("size", "md");
3025
+ return s === "sm" || s === "lg" ? s : "md";
3026
+ }
3027
+ set size(value) {
3028
+ this.setAttribute("size", value);
3029
+ }
3030
+ get value() {
3031
+ return this.getAttr("value");
3032
+ }
3033
+ set value(value) {
3034
+ this.setAttribute("value", value);
3035
+ }
3036
+ render() {
3037
+ const placeholder = this.getAttr("placeholder");
3038
+ const readonly = this.hasAttr("readonly");
3039
+ const disabled = this.hasAttr("disabled");
3040
+ const rows = this.getAttr("rows");
3041
+ const maxlength = this.getAttr("maxlength");
3042
+ this.setTemplate(`<textarea class="field" part="textarea"
3043
+ ${placeholder ? `placeholder="${placeholder}"` : ""}
3044
+ ${rows ? `rows="${rows}"` : ""}
3045
+ ${maxlength ? `maxlength="${maxlength}"` : ""}
3046
+ ></textarea>`);
3047
+ const field = this.root.querySelector(".field");
3048
+ if (!field) return;
3049
+ field.value = this.getAttr("value");
3050
+ if (disabled) field.setAttribute("disabled", "");
3051
+ if (readonly) field.setAttribute("readonly", "");
3052
+ const emit = (event) => {
3053
+ this.value = field.value;
3054
+ this.dispatchEvent(
3055
+ new CustomEvent(event, { bubbles: true, composed: true, detail: { value: field.value } })
3056
+ );
3057
+ };
3058
+ field.addEventListener("input", (e) => {
3059
+ e.stopPropagation();
3060
+ emit("input");
3061
+ });
3062
+ field.addEventListener("change", (e) => {
3063
+ e.stopPropagation();
3064
+ emit("change");
3065
+ });
3066
+ }
3067
+ };
3068
+ _FxTextarea.styles = css`
3069
+ :host {
3070
+ display: inline-block;
3071
+ vertical-align: middle;
3072
+ font-family: var(--fx-font-family);
3073
+ font-size: var(--fx-font-size);
3074
+ }
3075
+ .field {
3076
+ font-family: inherit;
3077
+ font-size: inherit;
3078
+ font-weight: var(--fx-font-weight);
3079
+ color: var(--fx-text-default);
3080
+ background-color: var(--fx-surface-background);
3081
+ border: 1px solid var(--fx-border-default);
3082
+ border-radius: var(--fx-radius-md);
3083
+ padding: var(--fx-space-md) var(--fx-space-lg);
3084
+ width: 260px;
3085
+ min-height: calc(var(--fx-size-md) + 40px);
3086
+ resize: vertical;
3087
+ box-sizing: border-box;
3088
+ transition:
3089
+ border-color var(--fx-motion-duration-normal) var(--fx-motion-easing),
3090
+ box-shadow var(--fx-motion-duration-normal) var(--fx-motion-easing);
3091
+ }
3092
+ .field::placeholder { color: var(--fx-text-muted); opacity: 1; }
3093
+ .field:hover { border-color: var(--fx-border-hover); }
3094
+ .field:focus-visible {
3095
+ outline: none;
3096
+ border-color: var(--fx-color-primary);
3097
+ box-shadow: var(--fx-effect-focus-ring, none);
3098
+ }
3099
+ .field:disabled,
3100
+ .field[readonly] {
3101
+ opacity: 0.55;
3102
+ cursor: not-allowed;
3103
+ background-color: var(--fx-surface-surface-hover);
3104
+ }
3105
+ :host([error]) .field, :host([invalid]) .field {
3106
+ border-color: var(--fx-color-danger, #dc2626);
3107
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--fx-color-danger, #dc2626) 18%, transparent);
3108
+ }
3109
+ :host([success]) .field, :host([valid]) .field {
3110
+ border-color: var(--fx-color-success, #16a34a);
3111
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--fx-color-success, #16a34a) 18%, transparent);
3112
+ }
3113
+ :host([size='sm']) .field { width: 220px; min-height: var(--fx-size-sm); padding: var(--fx-space-sm) var(--fx-space-md); }
3114
+ :host([size='lg']) .field { width: 300px; min-height: calc(var(--fx-size-lg) + 80px); }
3115
+ `;
3116
+ let FxTextarea = _FxTextarea;
3117
+ function defineFxTextarea() {
3118
+ return defineElement("fx-textarea", FxTextarea);
3119
+ }
3120
+ defineFxTextarea();
3121
+ const _FxDialog = class _FxDialog extends FxElement {
3122
+ static get observedAttributes() {
3123
+ return ["open", "size", "heading"];
3124
+ }
3125
+ get open() {
3126
+ return this.hasAttr("open");
3127
+ }
3128
+ set open(value) {
3129
+ this.toggleAttr("open", value);
3130
+ }
3131
+ render() {
3132
+ const isOpen = this.open;
3133
+ const heading = this.getAttr("heading");
3134
+ this.setTemplate(`
3135
+ <div class="overlay" part="overlay" ${isOpen ? "" : "hidden"}>
3136
+ <div class="dialog" role="dialog" aria-modal="true" aria-label="${heading}">
3137
+ <header>
3138
+ <h2>${heading}</h2>
3139
+ <button type="button" class="close" part="close" aria-label="Fechar">×</button>
3140
+ </header>
3141
+ <div class="body"><slot></slot></div>
3142
+ <footer part="footer"><slot name="footer"></slot></footer>
3143
+ </div>
3144
+ </div>
3145
+ `);
3146
+ if (!isOpen) return;
3147
+ const overlay = this.root.querySelector(".overlay");
3148
+ const closeBtn = this.root.querySelector(".close");
3149
+ const close = () => {
3150
+ this.open = false;
3151
+ this.dispatchEvent(new CustomEvent("close", { bubbles: true, composed: true }));
3152
+ };
3153
+ overlay?.addEventListener("click", (e) => {
3154
+ if (e.target === overlay) close();
3155
+ });
3156
+ closeBtn?.addEventListener("click", close);
3157
+ const onKey = (e) => {
3158
+ if (e.key === "Escape") {
3159
+ e.stopPropagation();
3160
+ close();
3161
+ }
3162
+ };
3163
+ document.addEventListener("keydown", onKey, { once: true });
3164
+ this._cleanup = () => document.removeEventListener("keydown", onKey);
3165
+ requestAnimationFrame(() => this.root.querySelector(".dialog")?.focus());
3166
+ this.dispatchEvent(new CustomEvent("open", { bubbles: true, composed: true }));
3167
+ }
3168
+ disconnectedCallback() {
3169
+ super.disconnectedCallback();
3170
+ this._cleanup?.();
3171
+ }
3172
+ };
3173
+ _FxDialog.styles = css`
3174
+ :host { display: contents; }
3175
+ .overlay {
3176
+ position: fixed;
3177
+ inset: 0;
3178
+ background: color-mix(in srgb, var(--fx-text-default, #000) 45%, transparent);
3179
+ backdrop-filter: blur(3px);
3180
+ -webkit-backdrop-filter: blur(3px);
3181
+ display: flex;
3182
+ align-items: center;
3183
+ justify-content: center;
3184
+ z-index: var(--fx-z-modal, 1100);
3185
+ animation: fade var(--fx-motion-duration-fast) var(--fx-motion-easing);
3186
+ }
3187
+ .overlay[hidden] { display: none; }
3188
+ @keyframes fade { from { opacity: 0; } to { opacity: 1; } }
3189
+ .dialog {
3190
+ background: var(--fx-surface-background);
3191
+ border: 1px solid var(--fx-border-default);
3192
+ border-radius: var(--fx-radius-lg);
3193
+ box-shadow: var(--fx-shadow-xl);
3194
+ width: min(560px, calc(100vw - 32px));
3195
+ max-height: calc(100vh - 64px);
3196
+ display: flex;
3197
+ flex-direction: column;
3198
+ font-family: var(--fx-font-family);
3199
+ animation: pop var(--fx-motion-duration-normal) var(--fx-motion-easing);
3200
+ }
3201
+ @keyframes pop { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } }
3202
+ :host([size='sm']) .dialog { width: min(400px, calc(100vw - 32px)); }
3203
+ :host([size='lg']) .dialog { width: min(800px, calc(100vw - 32px)); }
3204
+ header {
3205
+ display: flex;
3206
+ align-items: center;
3207
+ justify-content: space-between;
3208
+ padding: var(--fx-space-lg) var(--fx-space-xl);
3209
+ border-bottom: 1px solid var(--fx-border-default);
3210
+ }
3211
+ h2 {
3212
+ margin: 0;
3213
+ font-size: calc(var(--fx-font-size) + 2px);
3214
+ font-weight: 600;
3215
+ color: var(--fx-text-default);
3216
+ }
3217
+ .close {
3218
+ border: none;
3219
+ background: transparent;
3220
+ color: var(--fx-text-muted);
3221
+ font-size: calc(var(--fx-font-size) + 6px);
3222
+ cursor: pointer;
3223
+ line-height: 1;
3224
+ padding: 0 var(--fx-space-xs);
3225
+ border-radius: var(--fx-radius-full);
3226
+ }
3227
+ .close:hover { color: var(--fx-color-danger); }
3228
+ .body {
3229
+ padding: var(--fx-space-lg) var(--fx-space-xl);
3230
+ overflow-y: auto;
3231
+ color: var(--fx-text-default);
3232
+ font-size: var(--fx-font-size);
3233
+ }
3234
+ footer {
3235
+ padding: var(--fx-space-md) var(--fx-space-xl);
3236
+ border-top: 1px solid var(--fx-border-default);
3237
+ display: flex;
3238
+ justify-content: flex-end;
3239
+ gap: var(--fx-space-sm);
3240
+ }
3241
+ footer[hidden] { display: none; }
3242
+ `;
3243
+ let FxDialog = _FxDialog;
3244
+ function defineFxDialog() {
3245
+ return defineElement("fx-dialog", FxDialog);
3246
+ }
3247
+ defineFxDialog();
3248
+ const KIND_COLOR = {
3249
+ success: "var(--fx-color-success, #10b981)",
3250
+ error: "var(--fx-color-danger, #f43f5e)",
3251
+ info: "var(--fx-color-info, #0ea5e9)",
3252
+ warning: "var(--fx-color-warning, #f59e0b)"
3253
+ };
3254
+ const KIND_ICON = {
3255
+ success: "✓",
3256
+ error: "✕",
3257
+ warning: "!",
3258
+ info: "i"
3259
+ };
3260
+ const CARD_CSS = `
3261
+ :host { display: contents; }
3262
+ .card {
3263
+ display: flex; align-items: flex-start; gap: 10px;
3264
+ min-width: 260px; max-width: 360px;
3265
+ background: var(--fx-surface-background, #fff);
3266
+ color: var(--fx-text-default, #1e293b);
3267
+ border: 1px solid var(--fx-border-default, #e2e8f0);
3268
+ border-left: 4px solid var(--kind);
3269
+ border-radius: var(--fx-radius-md, 8px);
3270
+ box-shadow: var(--fx-shadow-lg, 0 10px 30px rgba(0,0,0,.14));
3271
+ padding: var(--fx-space-sm, 8px) var(--fx-space-md, 12px);
3272
+ font-family: inherit;
3273
+ animation: fx-toast-in .25s ease;
3274
+ }
3275
+ @keyframes fx-toast-in { from { opacity: 0; transform: translateY(-6px); } }
3276
+ .card.leaving { opacity: 0; transition: opacity .2s ease; }
3277
+ .icon {
3278
+ width: 22px; height: 22px; border-radius: 50%;
3279
+ background: var(--kind);
3280
+ color: #fff;
3281
+ display: flex; align-items: center; justify-content: center;
3282
+ font-size: 12px; font-weight: 700;
3283
+ flex: none; margin-top: 2px;
3284
+ user-select: none;
3285
+ }
3286
+ .body { flex: 1; min-width: 0; }
3287
+ .title { font-weight: 600; font-size: 14px; }
3288
+ .msg { font-size: calc(var(--fx-font-size, 14px) - 2px); color: var(--fx-text-muted, #64748b); margin-top: 2px; word-break: break-word; white-space: pre-line; }
3289
+ .close {
3290
+ all: unset; cursor: pointer; flex: none; line-height: 1;
3291
+ color: var(--fx-text-muted, #64748b); font-size: 15px; padding: 2px 4px; border-radius: 4px;
3292
+ }
3293
+ .close:hover { color: var(--fx-color-danger, #f43f5e); background: var(--fx-surface-surface-hover, rgba(0,0,0,.05)); }
3294
+ `;
3295
+ class FxToast extends HTMLElement {
3296
+ constructor() {
3297
+ super();
3298
+ __privateAdd(this, _FxToast_instances);
3299
+ this.timer = null;
3300
+ this.attachShadow({ mode: "open" });
3301
+ }
3302
+ static get observedAttributes() {
3303
+ return ["kind", "title", "message", "duration"];
3304
+ }
3305
+ connectedCallback() {
3306
+ if (!this.shadowRoot.firstChild) __privateMethod(this, _FxToast_instances, render_fn).call(this);
3307
+ const dur = Number(this.getAttribute("duration") ?? "4000");
3308
+ const safe = dur === 0 ? 0 : Math.max(Number.isNaN(dur) ? 4e3 : dur, 1e3);
3309
+ if (!Number.isNaN(safe) && safe > 0 && !this.timer) {
3310
+ this.timer = setTimeout(() => this.dismiss(), safe);
3311
+ }
3312
+ }
3313
+ disconnectedCallback() {
3314
+ if (this.timer) clearTimeout(this.timer);
3315
+ this.timer = null;
3316
+ }
3317
+ dismiss() {
3318
+ if (this.timer) clearTimeout(this.timer);
3319
+ this.timer = null;
3320
+ const card = this.shadowRoot.querySelector(".card");
3321
+ if (card) {
3322
+ card.classList.add("leaving");
3323
+ setTimeout(() => this.remove(), 200);
3324
+ } else {
3325
+ this.remove();
3326
+ }
3327
+ }
3328
+ }
3329
+ _FxToast_instances = new WeakSet();
3330
+ render_fn = function() {
3331
+ const kind = this.getAttribute("kind") || "info";
3332
+ const title = this.getAttribute("title") ?? "";
3333
+ const msg = this.getAttribute("message") ?? "";
3334
+ const color = KIND_COLOR[kind] ?? KIND_COLOR.info;
3335
+ this.shadowRoot.innerHTML = `
3336
+ <style>${CARD_CSS}</style>
3337
+ <div class="card" style="--kind:${color}" role="status" part="card">
3338
+ <span class="icon">${KIND_ICON[kind] ?? KIND_ICON.info}</span>
3339
+ <div class="body">
3340
+ ${title ? '<div class="title"></div>' : ""}
3341
+ ${msg ? '<div class="msg"></div>' : ""}
3342
+ </div>
3343
+ <button class="close" aria-label="Fechar">✕</button>
3344
+ </div>`;
3345
+ const t = this.shadowRoot.querySelector(".title");
3346
+ const m = this.shadowRoot.querySelector(".msg");
3347
+ if (t) t.textContent = title;
3348
+ if (m) m.textContent = msg;
3349
+ this.shadowRoot.querySelector(".close").addEventListener("click", () => this.dismiss());
3350
+ };
3351
+ function regionFor(position) {
3352
+ let region = document.querySelector(`[data-fx-toast-region="${position}"]`);
3353
+ if (!region) {
3354
+ region = document.createElement("div");
3355
+ region.setAttribute("data-fx-toast-region", position);
3356
+ const [vertical, horizontal] = position.split("-");
3357
+ const style = region.style;
3358
+ style.position = "fixed";
3359
+ style[vertical] = "16px";
3360
+ if (horizontal === "center") {
3361
+ style.left = "50%";
3362
+ style.transform = "translateX(-50%)";
3363
+ } else {
3364
+ style[horizontal] = "16px";
3365
+ }
3366
+ style.display = "flex";
3367
+ style.flexDirection = "column";
3368
+ style.gap = "8px";
3369
+ style.zIndex = "1100";
3370
+ style.pointerEvents = "none";
3371
+ new MutationObserver(() => {
3372
+ region.querySelectorAll("*").forEach((c) => c.style.pointerEvents = "auto");
3373
+ }).observe(region, { childList: true });
3374
+ document.body.appendChild(region);
3375
+ }
3376
+ return region;
3377
+ }
3378
+ class ToastApi {
3379
+ constructor() {
3380
+ this.seq = 0;
3381
+ this.map = /* @__PURE__ */ new Map();
3382
+ this.success = (t, m, o) => this.push("success", t, m, o);
3383
+ this.error = (t, m, o) => this.push("error", t, m, o);
3384
+ this.info = (t, m, o) => this.push("info", t, m, o);
3385
+ this.warning = (t, m, o) => this.push("warning", t, m, o);
3386
+ }
3387
+ push(kind, title, messageOrOpts, opts) {
3388
+ const id = ++this.seq;
3389
+ const message = typeof messageOrOpts === "string" ? messageOrOpts : messageOrOpts?.message;
3390
+ const o = { ...typeof messageOrOpts === "object" && messageOrOpts ? messageOrOpts : void 0, ...opts };
3391
+ const el = document.createElement("fx-toast");
3392
+ el.setAttribute("kind", kind);
3393
+ el.setAttribute("title", title);
3394
+ if (message) el.setAttribute("message", message);
3395
+ el.setAttribute("position", o.position ?? "top-right");
3396
+ el.setAttribute("duration", String(o.duration ?? 4e3));
3397
+ regionFor(o.position ?? "top-right").appendChild(el);
3398
+ this.map.set(id, el);
3399
+ return id;
3400
+ }
3401
+ close(id) {
3402
+ this.map.get(id)?.dismiss();
3403
+ this.map.delete(id);
3404
+ }
3405
+ }
3406
+ const FenixToast = new ToastApi();
3407
+ globalThis.FenixToast = FenixToast;
3408
+ function defineFxToast() {
3409
+ if (!customElements.get("fx-toast")) customElements.define("fx-toast", FxToast);
3410
+ }
3411
+ defineFxToast();
3412
+ const _FxTooltip = class _FxTooltip extends FxElement {
3413
+ static get observedAttributes() {
3414
+ return ["content", "position"];
3415
+ }
3416
+ render() {
3417
+ const content = this.getAttr("content");
3418
+ this.setTemplate(`
3419
+ <slot></slot>
3420
+ <span class="bubble" part="bubble" role="tooltip">${content}</span>
3421
+ `);
3422
+ }
3423
+ };
3424
+ _FxTooltip.styles = css`
3425
+ :host {
3426
+ position: relative;
3427
+ display: inline-flex;
3428
+ font-family: var(--fx-font-family);
3429
+ }
3430
+ .bubble {
3431
+ position: absolute;
3432
+ background: var(--fx-text-default, #1e293b);
3433
+ color: var(--fx-surface-background, #fff);
3434
+ font-size: calc(var(--fx-font-size, 14px) - 2px);
3435
+ line-height: 1.45;
3436
+ padding: var(--fx-space-xs) var(--fx-space-md);
3437
+ border-radius: var(--fx-radius-sm);
3438
+ box-shadow: var(--fx-shadow-md);
3439
+ width: max-content;
3440
+ min-width: 60px;
3441
+ max-width: var(--fx-tooltip-max-width, 320px);
3442
+ white-space: normal;
3443
+ text-align: center;
3444
+ opacity: 0;
3445
+ visibility: hidden;
3446
+ transition: opacity var(--fx-motion-duration-fast) var(--fx-motion-easing);
3447
+ z-index: var(--fx-z-tooltip, 1050);
3448
+ pointer-events: none;
3449
+ }
3450
+ :host(:hover) .bubble,
3451
+ :host(:focus-within) .bubble {
3452
+ opacity: 1;
3453
+ visibility: visible;
3454
+ }
3455
+ /* position top (padrão) */
3456
+ .bubble { bottom: calc(100% + 6px); left: 50%; transform: translateX(-50%); }
3457
+ .bubble::after {
3458
+ content: '';
3459
+ position: absolute;
3460
+ top: 100%;
3461
+ left: 50%;
3462
+ transform: translateX(-50%);
3463
+ border: 5px solid transparent;
3464
+ border-top-color: var(--fx-text-default, #1e293b);
3465
+ }
3466
+ /* bottom */
3467
+ :host([position='bottom']) .bubble { bottom: auto; top: calc(100% + 6px); }
3468
+ :host([position='bottom']) .bubble::after { top: auto; bottom: 100%; border-top-color: transparent; border-bottom-color: var(--fx-text-default, #1e293b); }
3469
+ /* left */
3470
+ :host([position='left']) .bubble {
3471
+ right: calc(100% + 6px); left: auto; top: 50%; bottom: auto; transform: translateY(-50%);
3472
+ }
3473
+ :host([position='left']) .bubble::after {
3474
+ top: 50%; left: 100%; transform: translateY(-50%);
3475
+ border: 5px solid transparent; border-left-color: var(--fx-text-default, #1e293b);
3476
+ }
3477
+ /* right */
3478
+ :host([position='right']) .bubble {
3479
+ right: auto; left: calc(100% + 6px); top: 50%; bottom: auto; transform: translateY(-50%);
3480
+ }
3481
+ :host([position='right']) .bubble::after {
3482
+ top: 50%; left: auto; right: 100%; transform: translateY(-50%);
3483
+ border: 5px solid transparent; border-right-color: var(--fx-text-default, #1e293b);
3484
+ }
3485
+ `;
3486
+ let FxTooltip = _FxTooltip;
3487
+ function defineFxTooltip() {
3488
+ return defineElement("fx-tooltip", FxTooltip);
3489
+ }
3490
+ defineFxTooltip();
3491
+ const _FxTabs = class _FxTabs extends FxElement {
3492
+ static get observedAttributes() {
3493
+ return ["value"];
3494
+ }
3495
+ get value() {
3496
+ return this.getAttr("value");
3497
+ }
3498
+ set value(value) {
3499
+ this.setAttribute("value", value);
3500
+ }
3501
+ render() {
3502
+ const tabs = Array.from(this.querySelectorAll("fx-tab"));
3503
+ const active = this.value || tabs[0]?.getAttribute("tab") || "";
3504
+ this.setTemplate(`
3505
+ <div class="list" role="tablist">
3506
+ ${tabs.map((t) => {
3507
+ const tabId = t.getAttribute("tab") ?? "";
3508
+ const disabled = t.hasAttribute("disabled");
3509
+ return `<button type="button" class="tab" role="tab" part="tab"
3510
+ data-tab="${tabId}" aria-selected="${tabId === active}"
3511
+ ${disabled ? 'aria-disabled="true" data-disabled="true"' : ""}>${t.textContent?.trim()}</button>`;
3512
+ }).join("")}
3513
+ </div>
3514
+ <slot></slot>
3515
+ `);
3516
+ this.panels.forEach((p) => {
3517
+ const show = p.getAttribute("tab") === active;
3518
+ if (show) p.removeAttribute("hidden");
3519
+ else p.setAttribute("hidden", "");
3520
+ });
3521
+ this.root.querySelectorAll(".tab").forEach((btn) => {
3522
+ btn.addEventListener("click", () => {
3523
+ if (btn.hasAttribute("data-disabled")) return;
3524
+ this.value = btn.dataset.tab ?? "";
3525
+ this.dispatchEvent(
3526
+ new CustomEvent("change", { bubbles: true, composed: true, detail: { value: this.value } })
3527
+ );
3528
+ });
3529
+ });
3530
+ }
3531
+ /** Painéis associados: descendentes do host ou irmãos seguintes no DOM. */
3532
+ get panels() {
3533
+ const inside = Array.from(this.querySelectorAll("fx-tab-panel"));
3534
+ if (inside.length) return inside;
3535
+ const out = [];
3536
+ let el = this.nextElementSibling;
3537
+ while (el && !el.matches("fx-tabs")) {
3538
+ if (el.matches("fx-tab-panel")) out.push(el);
3539
+ else out.push(...Array.from(el.querySelectorAll(":scope > fx-tab-panel")));
3540
+ el = el.nextElementSibling;
3541
+ }
3542
+ return out;
3543
+ }
3544
+ };
3545
+ _FxTabs.styles = css`
3546
+ :host {
3547
+ display: block;
3548
+ font-family: var(--fx-font-family);
3549
+ }
3550
+ .list {
3551
+ display: flex;
3552
+ gap: var(--fx-space-xs);
3553
+ border-bottom: 2px solid var(--fx-border-default);
3554
+ }
3555
+ .tab {
3556
+ border: none;
3557
+ background: transparent;
3558
+ font-family: inherit;
3559
+ font-size: var(--fx-font-size);
3560
+ color: var(--fx-text-muted);
3561
+ padding: var(--fx-space-md) var(--fx-space-lg);
3562
+ cursor: pointer;
3563
+ position: relative;
3564
+ transition: color var(--fx-motion-duration-fast) var(--fx-motion-easing);
3565
+ }
3566
+ .tab::after {
3567
+ content: '';
3568
+ position: absolute;
3569
+ left: 0; right: 0; bottom: -2px;
3570
+ height: 2px;
3571
+ background: transparent;
3572
+ transition: background var(--fx-motion-duration-fast) var(--fx-motion-easing);
3573
+ }
3574
+ .tab:hover { color: var(--fx-text-default); }
3575
+ .tab[data-disabled] {
3576
+ color: var(--fx-border-default);
3577
+ cursor: not-allowed;
3578
+ opacity: 0.6;
3579
+ }
3580
+ .tab[data-disabled]:hover { color: var(--fx-border-default); }
3581
+ .tab[aria-selected='true'] {
3582
+ color: var(--fx-color-primary);
3583
+ font-weight: 600;
3584
+ }
3585
+ .tab[aria-selected='true']::after { background: var(--fx-color-primary); }
3586
+ .tab:focus-visible {
3587
+ outline: none;
3588
+ box-shadow: var(--fx-effect-focus-ring, none);
3589
+ border-radius: var(--fx-radius-sm);
3590
+ }
3591
+ /* Os <fx-tab> originais são renderizados como botões no shadow DOM.
3592
+ Sem isto, o <slot> os projetaria de novo como texto cru abaixo das abas. */
3593
+ ::slotted(fx-tab) {
3594
+ display: none !important;
3595
+ }
3596
+ `;
3597
+ let FxTabs = _FxTabs;
3598
+ const _FxTabPanel = class _FxTabPanel extends FxElement {
3599
+ constructor() {
3600
+ super(...arguments);
3601
+ this._initialized = false;
3602
+ }
3603
+ static get observedAttributes() {
3604
+ return ["tab"];
3605
+ }
3606
+ get tab() {
3607
+ return this.getAttr("tab");
3608
+ }
3609
+ get visible() {
3610
+ return !this.hasAttr("hidden");
3611
+ }
3612
+ set visible(value) {
3613
+ this.toggleAttr("hidden", !value);
3614
+ }
3615
+ render() {
3616
+ this.setTemplate("<slot></slot>");
3617
+ if (!this._initialized) {
3618
+ const owner = this.closest("fx-tabs") ?? (() => {
3619
+ let el = this.previousElementSibling;
3620
+ while (el) {
3621
+ if (el.matches("fx-tabs")) return el;
3622
+ el = el.previousElementSibling;
3623
+ }
3624
+ return null;
3625
+ })();
3626
+ const active = owner ? owner.getAttribute("value") || "" : "";
3627
+ if (this.getAttribute("tab") !== active) this.setAttribute("hidden", "");
3628
+ this._initialized = true;
3629
+ }
3630
+ }
3631
+ };
3632
+ _FxTabPanel.styles = css`
3633
+ :host { display: block; padding: var(--fx-space-lg) 0; font-family: var(--fx-font-family); color: var(--fx-text-default); }
3634
+ :host([hidden]) { display: none; }
3635
+ `;
3636
+ let FxTabPanel = _FxTabPanel;
3637
+ function defineFxTabs() {
3638
+ return defineElement("fx-tabs", FxTabs);
3639
+ }
3640
+ function defineFxTabPanel() {
3641
+ return defineElement("fx-tab-panel", FxTabPanel);
3642
+ }
3643
+ defineFxTabs();
3644
+ defineFxTabPanel();
3645
+ const _FxProgress = class _FxProgress extends FxElement {
3646
+ constructor() {
3647
+ super(...arguments);
3648
+ this._done = false;
3649
+ }
3650
+ static get observedAttributes() {
3651
+ return ["value", "indeterminate", "variant", "label", "hide-label", "size"];
3652
+ }
3653
+ get value() {
3654
+ return Number(this.getAttr("value", "0")) || 0;
3655
+ }
3656
+ set value(v) {
3657
+ this.setAttribute("value", String(Math.min(100, Math.max(0, v))));
3658
+ }
3659
+ render() {
3660
+ const pct = Math.min(100, Math.max(0, this.value));
3661
+ const indeterminate = this.hasAttr("indeterminate");
3662
+ const labelText = this.getAttr("label");
3663
+ const showPct = !indeterminate && !this.hasAttr("hide-label");
3664
+ this.setTemplate(`
3665
+ ${labelText ? `<div class="caption" part="caption">${labelText}</div>` : ""}
3666
+ <div class="row">
3667
+ <div class="track" part="track" role="progressbar"
3668
+ aria-valuenow="${pct}" aria-valuemin="0" aria-valuemax="100">
3669
+ <div class="bar" part="bar" style="width: ${indeterminate ? "40%" : `${pct}%`}"></div>
3670
+ </div>
3671
+ ${showPct ? `<span class="pct">${pct}%</span>` : ""}
3672
+ </div>
3673
+ `);
3674
+ if (!this._done && !this.hasAttr("indeterminate") && pct >= 100) {
3675
+ this._done = true;
3676
+ this.dispatchEvent(new CustomEvent("complete", { bubbles: true, composed: true }));
3677
+ }
3678
+ if (pct < 100) this._done = false;
3679
+ }
3680
+ };
3681
+ _FxProgress.styles = css`
3682
+ :host {
3683
+ display: block;
3684
+ font-family: var(--fx-font-family);
3685
+ --_color: var(--fx-color-primary);
3686
+ }
3687
+ :host([variant='success']) { --_color: var(--fx-color-success); }
3688
+ :host([variant='warning']) { --_color: var(--fx-color-warning); }
3689
+ :host([variant='danger']) { --_color: var(--fx-color-danger); }
3690
+ .row {
3691
+ display: flex;
3692
+ align-items: center;
3693
+ gap: var(--fx-space-md);
3694
+ }
3695
+ .track {
3696
+ flex: 1;
3697
+ height: 8px;
3698
+ background: var(--fx-surface-surface-hover);
3699
+ border-radius: var(--fx-radius-full);
3700
+ overflow: hidden;
3701
+ }
3702
+ :host([size='sm']) .track { height: 4px; }
3703
+ :host([size='lg']) .track { height: 12px; }
3704
+ .bar {
3705
+ height: 100%;
3706
+ width: 0%;
3707
+ background: var(--_color);
3708
+ border-radius: var(--fx-radius-full);
3709
+ transition: width var(--fx-motion-duration-normal) var(--fx-motion-easing);
3710
+ }
3711
+ :host([indeterminate]) .bar {
3712
+ width: 40%;
3713
+ animation: slide 1.2s ease-in-out infinite;
3714
+ }
3715
+ @keyframes slide {
3716
+ from { transform: translateX(-100%); }
3717
+ to { transform: translateX(350%); }
3718
+ }
3719
+ .pct {
3720
+ font-size: calc(var(--fx-font-size) - 2px);
3721
+ color: var(--fx-text-default);
3722
+ font-weight: 600;
3723
+ min-width: 34px;
3724
+ text-align: right;
3725
+ }
3726
+ .caption {
3727
+ font-size: calc(var(--fx-font-size) - 2px);
3728
+ color: var(--fx-text-muted);
3729
+ margin-bottom: var(--fx-space-xs);
3730
+ }
3731
+ `;
3732
+ let FxProgress = _FxProgress;
3733
+ function defineFxProgress() {
3734
+ return defineElement("fx-progress", FxProgress);
3735
+ }
3736
+ defineFxProgress();
3737
+ const _FxSkeleton = class _FxSkeleton extends FxElement {
3738
+ static get observedAttributes() {
3739
+ return ["variant", "width", "height", "lines"];
3740
+ }
3741
+ render() {
3742
+ const variant = this.getAttr("variant", "text");
3743
+ const width = this.getAttr("width");
3744
+ const height = this.getAttr("height");
3745
+ const style = `${width ? `width:${width};` : ""}${height ? `height:${height};` : ""}`;
3746
+ if (variant === "circle") {
3747
+ const size = height || width || "40px";
3748
+ const diameter = !style.includes("height") ? `width:${size};height:${size};` : style;
3749
+ this.setTemplate(`<div class="bone circle" part="bone" style="${diameter}"></div>`);
3750
+ return;
3751
+ }
3752
+ if (variant === "rect") {
3753
+ const s = style || "width:100%;height:80px;";
3754
+ this.setTemplate(`<div class="bone" part="bone" style="${s}"></div>`);
3755
+ return;
3756
+ }
3757
+ const lines = Number(this.getAttr("lines", "3")) || 3;
3758
+ this.setTemplate(`
3759
+ ${Array.from({ length: lines }, () => '<div class="bone text"></div>').join("")}
3760
+ `);
3761
+ }
3762
+ };
3763
+ _FxSkeleton.styles = css`
3764
+ :host { display: block; width: 100%; min-width: 80px; font-family: var(--fx-font-family); }
3765
+ .bone {
3766
+ background: linear-gradient(
3767
+ 90deg,
3768
+ var(--fx-surface-surface-hover) 25%,
3769
+ color-mix(in srgb, var(--fx-surface-surface-hover) 55%, var(--fx-surface-background)) 50%,
3770
+ var(--fx-surface-surface-hover) 75%
3771
+ );
3772
+ background-size: 200% 100%;
3773
+ animation: shimmer 1.4s ease-in-out infinite;
3774
+ border-radius: var(--fx-radius-sm);
3775
+ }
3776
+ @keyframes shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
3777
+ .bone[hidden] { display: none; }
3778
+ .text { height: 12px; width: 100%; margin-bottom: var(--fx-space-sm); }
3779
+ .text:last-of-type { width: 60%; margin-bottom: 0; }
3780
+ .circle { border-radius: var(--fx-radius-full); }
3781
+ `;
3782
+ let FxSkeleton = _FxSkeleton;
3783
+ function defineFxSkeleton() {
3784
+ return defineElement("fx-skeleton", FxSkeleton);
3785
+ }
3786
+ defineFxSkeleton();
3787
+ const _FxAlert = class _FxAlert extends FxElement {
3788
+ static get observedAttributes() {
3789
+ return ["variant", "title", "dismissible"];
3790
+ }
3791
+ render() {
3792
+ const variant = this.getAttr("variant", "info");
3793
+ const title = this.getAttr("title");
3794
+ const icons = {
3795
+ info: "ℹ",
3796
+ success: "✓",
3797
+ warning: "⚠",
3798
+ danger: "✕"
3799
+ };
3800
+ this.setTemplate(`
3801
+ <div class="alert" part="alert" role="alert">
3802
+ <span class="icon">${icons[variant] ?? "ℹ"}</span>
3803
+ <div class="content">
3804
+ ${title ? `<div class="title">${title}</div>` : ""}
3805
+ <div class="body"><slot></slot></div>
3806
+ </div>
3807
+ ${this.hasAttr("dismissible") ? '<button type="button" class="close" aria-label="Fechar">×</button>' : ""}
3808
+ </div>
3809
+ `);
3810
+ this.root.querySelector(".close")?.addEventListener("click", () => {
3811
+ this.hidden = true;
3812
+ this.dispatchEvent(new CustomEvent("dismiss", { bubbles: true, composed: true }));
3813
+ });
3814
+ }
3815
+ };
3816
+ _FxAlert.styles = css`
3817
+ :host {
3818
+ display: block;
3819
+ font-family: var(--fx-font-family);
3820
+ --_color: var(--fx-color-primary);
3821
+ --_bg: color-mix(in srgb, var(--fx-color-primary) 10%, var(--fx-surface-background));
3822
+ }
3823
+ :host([variant='success']) { --_color: var(--fx-color-success); --_bg: color-mix(in srgb, var(--fx-color-success) 10%, var(--fx-surface-background)); }
3824
+ :host([variant='warning']) { --_color: var(--fx-color-warning); --_bg: color-mix(in srgb, var(--fx-color-warning) 12%, var(--fx-surface-background)); }
3825
+ :host([variant='danger']) { --_color: var(--fx-color-danger); --_bg: color-mix(in srgb, var(--fx-color-danger) 10%, var(--fx-surface-background)); }
3826
+ .alert {
3827
+ display: flex;
3828
+ align-items: flex-start;
3829
+ gap: var(--fx-space-md);
3830
+ background: var(--_bg);
3831
+ border: 1px solid color-mix(in srgb, var(--_color) 30%, transparent);
3832
+ border-radius: var(--fx-radius-md);
3833
+ padding: var(--fx-space-md) var(--fx-space-lg);
3834
+ font-size: var(--fx-font-size);
3835
+ color: var(--fx-text-default);
3836
+ }
3837
+ :host([hidden]) { display: none; }
3838
+ .icon { color: var(--_color); font-weight: bold; line-height: 1.4; }
3839
+ .content { flex: 1; }
3840
+ .title { font-weight: 600; }
3841
+ .body { margin-top: 2px; }
3842
+ .close {
3843
+ border: none; background: transparent; cursor: pointer;
3844
+ color: var(--fx-text-muted); font-size: calc(var(--fx-font-size) + 2px);
3845
+ line-height: 1; padding: 0; border-radius: var(--fx-radius-full);
3846
+ }
3847
+ .close:hover { color: var(--_color); }
3848
+ `;
3849
+ let FxAlert = _FxAlert;
3850
+ function defineFxAlert() {
3851
+ return defineElement("fx-alert", FxAlert);
3852
+ }
3853
+ defineFxAlert();
3854
+ const _FxDropdown = class _FxDropdown extends FxElement {
3855
+ static get observedAttributes() {
3856
+ return ["label", "position", "open"];
3857
+ }
3858
+ get open() {
3859
+ return this.hasAttr("open");
3860
+ }
3861
+ set open(value) {
3862
+ this.toggleAttr("open", value);
3863
+ }
3864
+ render() {
3865
+ const label = this.getAttr("label", "Ações");
3866
+ const items = Array.from(this.querySelectorAll("fx-dropdown-item"));
3867
+ this.setTemplate(`
3868
+ <span class="trigger" role="button" tabindex="0" part="trigger"
3869
+ aria-haspopup="menu" aria-expanded="${this.open}">
3870
+ ${label} ▾
3871
+ </span>
3872
+ <div class="panel" role="menu" part="panel">
3873
+ ${items.length ? items.map((i) => `<div class="item-slot"><slot name="_${i.getAttribute("value") ?? ""}"></slot></div>`) : '<div class="empty">Nenhuma ação</div>'}
3874
+ <slot></slot>
3875
+ </div>
3876
+ `);
3877
+ const trigger = this.root.querySelector(".trigger");
3878
+ trigger?.addEventListener("click", (e) => {
3879
+ e.stopPropagation();
3880
+ document.querySelectorAll("fx-dropdown[open]").forEach((d) => {
3881
+ if (d !== this) d.open = false;
3882
+ });
3883
+ this.open = !this.open;
3884
+ });
3885
+ trigger?.addEventListener("keydown", (e) => {
3886
+ if (e.key === "Enter" || e.key === " ") {
3887
+ e.preventDefault();
3888
+ this.open = !this.open;
3889
+ }
3890
+ });
3891
+ items.forEach((item) => {
3892
+ item.addEventListener("click", () => {
3893
+ this.open = false;
3894
+ this.dispatchEvent(new CustomEvent("select", {
3895
+ bubbles: true,
3896
+ composed: true,
3897
+ detail: { value: item.getAttribute("value") ?? "" }
3898
+ }));
3899
+ });
3900
+ });
3901
+ }
3902
+ };
3903
+ _FxDropdown.styles = css`
3904
+ :host {
3905
+ position: relative;
3906
+ display: inline-block;
3907
+ font-family: var(--fx-font-family);
3908
+ font-size: var(--fx-font-size);
3909
+ }
3910
+ .trigger { min-height: var(--fx-size-md); }
3911
+ .panel {
3912
+ position: absolute;
3913
+ top: calc(100% + 4px);
3914
+ min-width: 180px;
3915
+ background: var(--fx-surface-background);
3916
+ border: 1px solid var(--fx-border-default);
3917
+ border-radius: var(--fx-radius-md);
3918
+ box-shadow: var(--fx-shadow-lg);
3919
+ padding: var(--fx-space-xs);
3920
+ z-index: var(--fx-z-dropdown, 1000);
3921
+ display: none;
3922
+ }
3923
+ :host([open]) .panel { display: block; }
3924
+ /* left = alinhado à esquerda do trigger; right = à direita; center = centrado */
3925
+ :host([position='left']) .panel,
3926
+ :host([position='bottom-left']) .panel { left: 0; right: auto; }
3927
+ :host([position='center']) .panel {
3928
+ left: 50%;
3929
+ transform: translateX(-50%);
3930
+ }
3931
+ :host([position='right']) .panel,
3932
+ :host([position='bottom-right']) .panel { right: 0; left: auto; }
3933
+ :host(:not([position])) .panel { left: 0; right: auto; }
3934
+ ::slotted(fx-dropdown-item) { display: block; }
3935
+ .empty { color: var(--fx-text-muted); font-size: calc(var(--fx-font-size) - 2px); padding: var(--fx-space-sm); }
3936
+ `;
3937
+ let FxDropdown = _FxDropdown;
3938
+ const _FxDropdownItem = class _FxDropdownItem extends FxElement {
3939
+ static get observedAttributes() {
3940
+ return ["value"];
3941
+ }
3942
+ get value() {
3943
+ return this.getAttr("value");
3944
+ }
3945
+ set value(v) {
3946
+ this.setAttribute("value", v);
3947
+ }
3948
+ render() {
3949
+ this.setTemplate('<button type="button" class="item" part="item" role="menuitem"><slot></slot></button>');
3950
+ }
3951
+ };
3952
+ _FxDropdownItem.styles = css`
3953
+ :host {
3954
+ display: block;
3955
+ font-family: var(--fx-font-family);
3956
+ font-size: var(--fx-font-size);
3957
+ }
3958
+ .item {
3959
+ display: block;
3960
+ width: 100%;
3961
+ text-align: left;
3962
+ border: none;
3963
+ background: transparent;
3964
+ font-family: inherit;
3965
+ font-size: inherit;
3966
+ color: var(--fx-text-default);
3967
+ padding: var(--fx-space-sm) var(--fx-space-md);
3968
+ cursor: pointer;
3969
+ border-radius: var(--fx-radius-sm);
3970
+ transition: background var(--fx-motion-duration-fast) var(--fx-motion-easing);
3971
+ }
3972
+ .item:hover { background: color-mix(in srgb, var(--fx-color-primary) 12%, var(--fx-surface-background)); color: var(--fx-color-primary); }
3973
+ `;
3974
+ let FxDropdownItem = _FxDropdownItem;
3975
+ function defineFxDropdown() {
3976
+ return defineElement("fx-dropdown", FxDropdown);
3977
+ }
3978
+ function defineFxDropdownItem() {
3979
+ return defineElement("fx-dropdown-item", FxDropdownItem);
3980
+ }
3981
+ defineFxDropdown();
3982
+ defineFxDropdownItem();
3983
+ const _FxDrawer = class _FxDrawer extends FxElement {
3984
+ constructor() {
3985
+ super(...arguments);
3986
+ this._onKeydown = () => {
3987
+ };
3988
+ }
3989
+ static get observedAttributes() {
3990
+ return ["open", "title", "position"];
3991
+ }
3992
+ get open() {
3993
+ return this.hasAttr("open");
3994
+ }
3995
+ set open(value) {
3996
+ this.toggleAttr("open", value);
3997
+ }
3998
+ get title() {
3999
+ return this.getAttr("title");
4000
+ }
4001
+ set title(v) {
4002
+ this.setAttribute("title", v);
4003
+ }
4004
+ get position() {
4005
+ const p = this.getAttr("position");
4006
+ return ["left", "right", "top", "bottom"].includes(p) ? p : "right";
4007
+ }
4008
+ set position(v) {
4009
+ this.setAttribute("position", v);
4010
+ }
4011
+ render() {
4012
+ const title = this.getAttr("title");
4013
+ this.setTemplate(`
4014
+ <div class="overlay" part="overlay"></div>
4015
+ <div class="panel" part="panel" role="dialog" aria-modal="true" aria-label="${title || "Drawer"}">
4016
+ ${title !== "" ? `<div class="header" part="header"><span class="title">${title}</span><button type="button" class="close" part="close" aria-label="Fechar">✕</button></div>` : ""}
4017
+ <div class="body" part="body"><slot></slot></div>
4018
+ </div>
4019
+ `);
4020
+ const close = () => {
4021
+ if (!this.open) return;
4022
+ this.open = false;
4023
+ this.dispatchEvent(
4024
+ new CustomEvent("close", { bubbles: true, composed: true })
4025
+ );
4026
+ };
4027
+ this.root.querySelector(".overlay")?.addEventListener("click", close);
4028
+ this.root.querySelector(".close")?.addEventListener("click", close);
4029
+ }
4030
+ connectedCallback() {
4031
+ super.connectedCallback?.();
4032
+ this._onKeydown = (e) => {
4033
+ if (e.key === "Escape" && this.open) {
4034
+ this.open = false;
4035
+ this.dispatchEvent(
4036
+ new CustomEvent("close", { bubbles: true, composed: true })
4037
+ );
4038
+ }
4039
+ };
4040
+ document.addEventListener("keydown", this._onKeydown);
4041
+ }
4042
+ disconnectedCallback() {
4043
+ document.removeEventListener("keydown", this._onKeydown);
4044
+ super.disconnectedCallback?.();
4045
+ }
4046
+ };
4047
+ _FxDrawer.styles = css`
4048
+ :host {
4049
+ display: contents;
4050
+ font-family: var(--fx-font-family);
4051
+ font-size: var(--fx-font-size);
4052
+ color: var(--fx-text-default);
4053
+ }
4054
+ .overlay {
4055
+ display: none;
4056
+ position: fixed;
4057
+ inset: 0;
4058
+ background: color-mix(
4059
+ in srgb,
4060
+ var(--fx-text-default, #000) 45%,
4061
+ transparent
4062
+ );
4063
+ backdrop-filter: blur(3px);
4064
+ -webkit-backdrop-filter: blur(3px);
4065
+ z-index: var(--fx-z-overlay, 1090);
4066
+ animation: fade var(--fx-motion-duration-fast) var(--fx-motion-easing);
4067
+ }
4068
+ :host([open]) .overlay {
4069
+ display: block;
4070
+ }
4071
+
4072
+ .panel {
4073
+ position: fixed;
4074
+ background: var(--fx-surface-background);
4075
+ box-shadow: var(--fx-shadow-xl);
4076
+ z-index: var(--fx-z-modal, 1000);
4077
+ display: flex;
4078
+ flex-direction: column;
4079
+ overflow: auto;
4080
+ }
4081
+ :host(:not([open])) .panel {
4082
+ display: none;
4083
+ }
4084
+ :host([open]) .panel {
4085
+ display: flex;
4086
+ }
4087
+ /* left / right — altura total, largura mínima fixa e expansível */
4088
+ :host([position="left"]) .panel,
4089
+ :host(:not([position])) .panel,
4090
+ :host([position="right"]) .panel {
4091
+ top: 0;
4092
+ bottom: 0;
4093
+ width: var(--fx-drawer-width, 360px);
4094
+ min-width: 300px;
4095
+ max-width: 90vw;
4096
+ }
4097
+ :host([position="left"]) .panel {
4098
+ left: 0;
4099
+ }
4100
+ :host([position="right"]) .panel,
4101
+ :host(:not([position])) .panel {
4102
+ right: 0;
4103
+ }
4104
+
4105
+ /* top / bottom — largura total, altura mínima fixa e expansível */
4106
+ :host([position="top"]) .panel,
4107
+ :host([position="bottom"]) .panel {
4108
+ left: 0;
4109
+ right: 0;
4110
+ height: var(--fx-drawer-height, 280px);
4111
+ min-height: 180px;
4112
+ max-height: 85vh;
4113
+ }
4114
+ :host([position="top"]) .panel {
4115
+ top: 0;
4116
+ }
4117
+ :host([position="bottom"]) .panel {
4118
+ bottom: 0;
4119
+ }
4120
+
4121
+ .header {
4122
+ display: flex;
4123
+ align-items: center;
4124
+ gap: var(--fx-space-sm);
4125
+ min-height: var(--fx-size-lg);
4126
+ padding: var(--fx-space-sm) var(--fx-space-md);
4127
+ border-bottom: 1px solid var(--fx-border-default);
4128
+ flex: none;
4129
+ position: sticky;
4130
+ top: 0;
4131
+ background: var(--fx-surface-background);
4132
+ }
4133
+ .title {
4134
+ font-weight: 700;
4135
+ flex: 1;
4136
+ overflow: hidden;
4137
+ text-overflow: ellipsis;
4138
+ white-space: nowrap;
4139
+ }
4140
+ .close {
4141
+ border: none;
4142
+ background: transparent;
4143
+ cursor: pointer;
4144
+ color: var(--fx-text-muted);
4145
+ width: 28px;
4146
+ height: 28px;
4147
+ line-height: 1;
4148
+ border-radius: var(--fx-radius-sm);
4149
+ font-size: 16px;
4150
+ transition: background var(--fx-motion-duration-fast)
4151
+ var(--fx-motion-easing);
4152
+ }
4153
+ .close:hover {
4154
+ background: color-mix(
4155
+ in srgb,
4156
+ var(--fx-color-danger) 14%,
4157
+ var(--fx-surface-background)
4158
+ );
4159
+ color: var(--fx-color-danger);
4160
+ }
4161
+ .body {
4162
+ padding: var(--fx-space-md);
4163
+ flex: 1;
4164
+ }
4165
+ `;
4166
+ let FxDrawer = _FxDrawer;
4167
+ function defineFxDrawer() {
4168
+ return defineElement("fx-drawer", FxDrawer);
4169
+ }
4170
+ defineFxDrawer();
4171
+ const _FxPagination = class _FxPagination extends FxElement {
4172
+ static get observedAttributes() {
4173
+ return ["page", "total", "rows", "rows-options", "position"];
4174
+ }
4175
+ get page() {
4176
+ return Number(this.getAttr("page", "1")) || 1;
4177
+ }
4178
+ set page(v) {
4179
+ this.setAttribute("page", String(v));
4180
+ }
4181
+ get total() {
4182
+ return Number(this.getAttr("total", "0")) || 0;
4183
+ }
4184
+ set total(v) {
4185
+ this.setAttribute("total", String(v));
4186
+ }
4187
+ get rows() {
4188
+ return Number(this.getAttr("rows", "10")) || 10;
4189
+ }
4190
+ set rows(v) {
4191
+ this.setAttribute("rows", String(v));
4192
+ }
4193
+ get pages() {
4194
+ return Math.max(1, Math.ceil(this.total / this.rows));
4195
+ }
4196
+ render() {
4197
+ const pages = this.pages;
4198
+ const current = Math.min(Math.max(1, this.page), pages);
4199
+ const opts = (this.getAttr("rows-options", "5,10,20,50") || "").split(",").map((s) => s.trim()).filter(Boolean);
4200
+ const window_ = [];
4201
+ for (let p = Math.max(1, current - 2); p <= Math.min(pages, current + 2); p++) window_.push(p);
4202
+ const from = total0(current, this.rows);
4203
+ const to = Math.min(current * this.rows, this.total);
4204
+ this.setTemplate(`
4205
+ <span class="info">${this.total ? `${from}-${to} de ${this.total}` : "0 itens"}</span>
4206
+ <button type="button" class="nav" part="prev" data-go="${current - 1}" ${current <= 1 ? "disabled" : ""}>‹</button>
4207
+ ${window_[0] !== 1 && pages > 5 ? `<button type="button" class="nav" data-go="1">1</button>` : ""}
4208
+ ${window_.map((p) => `<button type="button" class="nav ${p === current ? "active" : ""}" data-go="${p}">${p}</button>`).join("")}
4209
+ ${window_[window_.length - 1] !== pages && pages > 5 ? `<button type="button" class="nav" data-go="${pages}">${pages}</button>` : ""}
4210
+ <button type="button" class="nav" part="next" data-go="${current + 1}" ${current >= pages ? "disabled" : ""}>›</button>
4211
+ ${opts.length ? `
4212
+ <select class="nav" part="rows" aria-label="Itens por página">
4213
+ ${opts.map((o) => `<option value="${o}" ${Number(o) === this.rows ? "selected" : ""}>${o}</option>`).join("")}
4214
+ </select>` : ""}
4215
+ `);
4216
+ function total0(page, rows) {
4217
+ return (page - 1) * rows + 1;
4218
+ }
4219
+ this.root.querySelectorAll(".nav[data-go]").forEach((btn) => {
4220
+ btn.addEventListener("click", () => {
4221
+ const target = Number(btn.dataset.go);
4222
+ if (target >= 1 && target <= pages && target !== current) {
4223
+ this.page = target;
4224
+ this.emit();
4225
+ }
4226
+ });
4227
+ });
4228
+ const sel = this.root.querySelector("select.nav");
4229
+ sel?.addEventListener("change", () => {
4230
+ this.rows = Number(sel.value);
4231
+ this.page = 1;
4232
+ this.emit();
4233
+ });
4234
+ }
4235
+ emit() {
4236
+ this.dispatchEvent(new CustomEvent("page-change", {
4237
+ bubbles: true,
4238
+ composed: true,
4239
+ detail: { page: this.page, rows: this.rows }
4240
+ }));
4241
+ }
4242
+ };
4243
+ _FxPagination.styles = css`
4244
+ :host {
4245
+ display: flex;
4246
+ align-items: center;
4247
+ gap: var(--fx-space-sm);
4248
+ font-family: var(--fx-font-family);
4249
+ font-size: var(--fx-font-size);
4250
+ color: var(--fx-text-default);
4251
+ }
4252
+ :host([position='center']) { justify-content: center; }
4253
+ :host([position='right']) { justify-content: flex-end; }
4254
+ .nav {
4255
+ min-width: var(--fx-size-sm);
4256
+ height: var(--fx-size-sm);
4257
+ border: 1px solid var(--fx-border-default);
4258
+ background: var(--fx-surface-background);
4259
+ color: var(--fx-text-default);
4260
+ font-family: inherit;
4261
+ font-size: calc(var(--fx-font-size) - 2px);
4262
+ border-radius: var(--fx-radius-sm);
4263
+ cursor: pointer;
4264
+ padding: 0 var(--fx-space-sm);
4265
+ transition:
4266
+ border-color var(--fx-motion-duration-fast) var(--fx-motion-easing),
4267
+ background var(--fx-motion-duration-fast) var(--fx-motion-easing);
4268
+ }
4269
+ .nav:hover:not([disabled]) { border-color: var(--fx-color-primary); color: var(--fx-color-primary); }
4270
+ .nav[disabled] { opacity: 0.5; cursor: not-allowed; }
4271
+ .nav.active {
4272
+ background: var(--fx-color-primary);
4273
+ border-color: var(--fx-color-primary);
4274
+ color: #fff;
4275
+ font-weight: 600;
4276
+ }
4277
+ .info { color: var(--fx-text-muted); font-size: calc(var(--fx-font-size) - 2px); }
4278
+ select.nav { cursor: pointer; }
4279
+ `;
4280
+ let FxPagination = _FxPagination;
4281
+ function defineFxPagination() {
4282
+ return defineElement("fx-pagination", FxPagination);
4283
+ }
4284
+ defineFxPagination();
4285
+ const _FxAutocomplete = class _FxAutocomplete extends FxElement {
4286
+ static get observedAttributes() {
4287
+ return ["size", "placeholder", "source", "disabled", "min-chars"];
4288
+ }
4289
+ get size() {
4290
+ const s = this.getAttr("size", "md");
4291
+ return s === "sm" || s === "lg" ? s : "md";
4292
+ }
4293
+ set size(v) {
4294
+ this.setAttribute("size", v);
4295
+ }
4296
+ get value() {
4297
+ return this.getAttr("value");
4298
+ }
4299
+ set value(v) {
4300
+ this.setAttribute("value", v);
4301
+ }
4302
+ get source() {
4303
+ try {
4304
+ const raw = JSON.parse(this.getAttr("source", "[]"));
4305
+ return Array.isArray(raw) ? raw.map(String) : [];
4306
+ } catch {
4307
+ return [];
4308
+ }
4309
+ }
4310
+ render() {
4311
+ const placeholder = this.getAttr("placeholder");
4312
+ this.setTemplate(`
4313
+ <input class="field" part="input" type="text"
4314
+ ${placeholder ? `placeholder="${placeholder}"` : ""} autocomplete="off"/>
4315
+ <div class="list" part="list" role="listbox"></div>
4316
+ `);
4317
+ const field = this.root.querySelector(".field");
4318
+ if (!field) return;
4319
+ field.value = this.value;
4320
+ if (this.hasAttr("disabled")) field.setAttribute("disabled", "");
4321
+ const list = this.root.querySelector(".list");
4322
+ const minChars = Number(this.getAttr("min-chars", "1")) || 1;
4323
+ const openList = () => {
4324
+ if (!list) return;
4325
+ const q = field.value.toLowerCase();
4326
+ const matches = this.source.filter((s) => s.toLowerCase().includes(q)).slice(0, 8);
4327
+ 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>';
4328
+ list.querySelectorAll(".opt").forEach((btn) => {
4329
+ btn.addEventListener("click", () => {
4330
+ field.value = btn.dataset.v ?? "";
4331
+ this.value = field.value;
4332
+ this.toggleAttr("open", false);
4333
+ this.dispatchEvent(new CustomEvent("select", {
4334
+ bubbles: true,
4335
+ composed: true,
4336
+ detail: { value: this.value }
4337
+ }));
4338
+ });
4339
+ });
4340
+ this.toggleAttr("open", true);
4341
+ };
4342
+ field.addEventListener("input", () => {
4343
+ this.value = field.value;
4344
+ openList();
4345
+ });
4346
+ field.addEventListener("focus", () => {
4347
+ if (field.value.length >= minChars) openList();
4348
+ });
4349
+ document.addEventListener("click", (e) => {
4350
+ if (!this.contains(e.target)) this.toggleAttr("open", false);
4351
+ });
4352
+ }
4353
+ };
4354
+ _FxAutocomplete.styles = css`
4355
+ :host {
4356
+ position: relative;
4357
+ display: inline-block;
4358
+ font-family: var(--fx-font-family);
4359
+ font-size: var(--fx-font-size);
4360
+ }
4361
+ input {
4362
+ font-family: inherit;
4363
+ font-size: inherit;
4364
+ font-weight: var(--fx-font-weight);
4365
+ color: var(--fx-text-default);
4366
+ background-color: var(--fx-surface-background);
4367
+ border: 1px solid var(--fx-border-default);
4368
+ border-radius: var(--fx-radius-md);
4369
+ padding: var(--fx-space-md) var(--fx-space-lg);
4370
+ width: 260px;
4371
+ min-height: var(--fx-size-md);
4372
+ box-sizing: border-box;
4373
+ transition:
4374
+ border-color var(--fx-motion-duration-normal) var(--fx-motion-easing),
4375
+ box-shadow var(--fx-motion-duration-normal) var(--fx-motion-easing);
4376
+ }
4377
+ input::placeholder { color: var(--fx-text-muted); opacity: 1; }
4378
+ input:hover { border-color: var(--fx-border-hover); }
4379
+ input:focus-visible {
4380
+ outline: none;
4381
+ border-color: var(--fx-color-primary);
4382
+ box-shadow: var(--fx-effect-focus-ring, none);
4383
+ }
4384
+ :host([size='sm']) input { width: 220px; min-height: var(--fx-size-sm); padding: var(--fx-space-sm) var(--fx-space-md); }
4385
+ :host([size='lg']) input { width: 300px; min-height: var(--fx-size-lg); }
4386
+ .list {
4387
+ position: absolute;
4388
+ top: calc(100% + 4px);
4389
+ left: 0;
4390
+ right: 0;
4391
+ background: var(--fx-surface-background);
4392
+ border: 1px solid var(--fx-border-default);
4393
+ border-radius: var(--fx-radius-md);
4394
+ box-shadow: var(--fx-shadow-lg);
4395
+ z-index: var(--fx-z-dropdown, 1000);
4396
+ max-height: 220px;
4397
+ overflow-y: auto;
4398
+ display: none;
4399
+ }
4400
+ :host([open]) .list { display: block; }
4401
+ .opt {
4402
+ display: block;
4403
+ width: 100%;
4404
+ text-align: left;
4405
+ border: none;
4406
+ background: transparent;
4407
+ font-family: inherit;
4408
+ font-size: inherit;
4409
+ color: var(--fx-text-default);
4410
+ padding: var(--fx-space-sm) var(--fx-space-md);
4411
+ cursor: pointer;
4412
+ transition: background var(--fx-motion-duration-fast) var(--fx-motion-easing);
4413
+ }
4414
+ .opt:hover { background: color-mix(in srgb, var(--fx-color-primary) 12%, var(--fx-surface-background)); color: var(--fx-color-primary); }
4415
+ .empty { color: var(--fx-text-muted); font-size: calc(var(--fx-font-size) - 2px); padding: var(--fx-space-sm) var(--fx-space-md); }
4416
+ `;
4417
+ let FxAutocomplete = _FxAutocomplete;
4418
+ function defineFxAutocomplete() {
4419
+ return defineElement("fx-autocomplete", FxAutocomplete);
75
4420
  }
4421
+ defineFxAutocomplete();
76
4422
  export {
77
- FenixAutoImport,
78
- fenixComponentMap,
79
- shouldTransform,
80
- transformSource
4423
+ FX_JSX_TYPES,
4424
+ FenixToast,
4425
+ FenixUI,
4426
+ FxAlert,
4427
+ FxAutocomplete,
4428
+ FxBadge,
4429
+ FxButton,
4430
+ FxCalendar,
4431
+ FxCheckbox,
4432
+ FxDatepicker,
4433
+ FxDialog,
4434
+ FxDrawer,
4435
+ FxDropdown,
4436
+ FxDropdownItem,
4437
+ FxElement,
4438
+ FxFloatlabel,
4439
+ FxInput,
4440
+ FxMultiselect,
4441
+ FxPagination,
4442
+ FxProgress,
4443
+ FxRadio,
4444
+ FxSelect,
4445
+ FxSkeleton,
4446
+ FxSpinner,
4447
+ FxSwitch,
4448
+ FxTabPanel,
4449
+ FxTable,
4450
+ FxTabs,
4451
+ FxTextarea,
4452
+ FxToast,
4453
+ FxTooltip,
4454
+ VERSION,
4455
+ applyPreset,
4456
+ applyTokens,
4457
+ configure,
4458
+ css,
4459
+ darkTokens,
4460
+ deepMerge,
4461
+ defaultTokens,
4462
+ defineCustomPreset,
4463
+ defineElement,
4464
+ kebabToCamel,
4465
+ lightTokens,
4466
+ listPresets,
4467
+ resetTheme,
4468
+ setTokens,
4469
+ theme,
4470
+ themePresets,
4471
+ tokenCssVars
81
4472
  };
82
4473
  //# sourceMappingURL=fenix-ui2.esm.js.map