@wrrdev/fenix-ui 1.0.0

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