@wrrdev/fenix-ui 1.1.13 → 1.1.14

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 (110) hide show
  1. package/README.md +3 -3
  2. package/dist/components/autocomplete/autocomplete.d.ts +11 -5
  3. package/dist/components/autocomplete/autocomplete.d.ts.map +1 -1
  4. package/dist/components/autocomplete/autocomplete.js +18 -2
  5. package/dist/components/autocomplete/autocomplete.js.map +1 -1
  6. package/dist/components/button/button.d.ts +15 -0
  7. package/dist/components/button/button.d.ts.map +1 -1
  8. package/dist/components/button/button.js +20 -1
  9. package/dist/components/button/button.js.map +1 -1
  10. package/dist/components/checkbox/checkbox.d.ts +20 -4
  11. package/dist/components/checkbox/checkbox.d.ts.map +1 -1
  12. package/dist/components/checkbox/checkbox.js +39 -2
  13. package/dist/components/checkbox/checkbox.js.map +1 -1
  14. package/dist/components/datepicker/datepicker.d.ts +16 -2
  15. package/dist/components/datepicker/datepicker.d.ts.map +1 -1
  16. package/dist/components/datepicker/datepicker.js +46 -2
  17. package/dist/components/datepicker/datepicker.js.map +1 -1
  18. package/dist/components/fileupload/fileupload.d.ts +10 -2
  19. package/dist/components/fileupload/fileupload.d.ts.map +1 -1
  20. package/dist/components/fileupload/fileupload.js +17 -2
  21. package/dist/components/fileupload/fileupload.js.map +1 -1
  22. package/dist/components/floatlabel/floatlabel.d.ts +9 -3
  23. package/dist/components/floatlabel/floatlabel.d.ts.map +1 -1
  24. package/dist/components/floatlabel/floatlabel.js +22 -7
  25. package/dist/components/floatlabel/floatlabel.js.map +1 -1
  26. package/dist/components/input/input.d.ts +14 -7
  27. package/dist/components/input/input.d.ts.map +1 -1
  28. package/dist/components/input/input.js +18 -2
  29. package/dist/components/input/input.js.map +1 -1
  30. package/dist/components/knob/knob.d.ts +7 -3
  31. package/dist/components/knob/knob.d.ts.map +1 -1
  32. package/dist/components/knob/knob.js +4 -2
  33. package/dist/components/knob/knob.js.map +1 -1
  34. package/dist/components/multiselect/multiselect.d.ts +13 -3
  35. package/dist/components/multiselect/multiselect.d.ts.map +1 -1
  36. package/dist/components/multiselect/multiselect.js +27 -2
  37. package/dist/components/multiselect/multiselect.js.map +1 -1
  38. package/dist/components/radio/radio.d.ts +27 -4
  39. package/dist/components/radio/radio.d.ts.map +1 -1
  40. package/dist/components/radio/radio.js +67 -8
  41. package/dist/components/radio/radio.js.map +1 -1
  42. package/dist/components/rating/rating.d.ts +8 -4
  43. package/dist/components/rating/rating.d.ts.map +1 -1
  44. package/dist/components/rating/rating.js +5 -2
  45. package/dist/components/rating/rating.js.map +1 -1
  46. package/dist/components/select/select.d.ts +9 -3
  47. package/dist/components/select/select.d.ts.map +1 -1
  48. package/dist/components/select/select.js +11 -2
  49. package/dist/components/select/select.js.map +1 -1
  50. package/dist/components/slider/slider.d.ts +12 -6
  51. package/dist/components/slider/slider.d.ts.map +1 -1
  52. package/dist/components/slider/slider.js +10 -2
  53. package/dist/components/slider/slider.js.map +1 -1
  54. package/dist/components/switch/switch.d.ts +20 -4
  55. package/dist/components/switch/switch.d.ts.map +1 -1
  56. package/dist/components/switch/switch.js +40 -2
  57. package/dist/components/switch/switch.js.map +1 -1
  58. package/dist/components/textarea/textarea.d.ts +12 -5
  59. package/dist/components/textarea/textarea.d.ts.map +1 -1
  60. package/dist/components/textarea/textarea.js +12 -2
  61. package/dist/components/textarea/textarea.js.map +1 -1
  62. package/dist/components/toast/toast.d.ts +17 -5
  63. package/dist/components/toast/toast.d.ts.map +1 -1
  64. package/dist/components/toast/toast.js +161 -21
  65. package/dist/components/toast/toast.js.map +1 -1
  66. package/dist/components/toggle-button-group/toggle-button-group.d.ts +13 -5
  67. package/dist/components/toggle-button-group/toggle-button-group.d.ts.map +1 -1
  68. package/dist/components/toggle-button-group/toggle-button-group.js +19 -2
  69. package/dist/components/toggle-button-group/toggle-button-group.js.map +1 -1
  70. package/dist/core/base.d.ts +7 -0
  71. package/dist/core/base.d.ts.map +1 -1
  72. package/dist/core/base.js +4 -1
  73. package/dist/core/base.js.map +1 -1
  74. package/dist/core/form-element.d.ts +81 -0
  75. package/dist/core/form-element.d.ts.map +1 -0
  76. package/dist/core/form-element.js +129 -0
  77. package/dist/core/form-element.js.map +1 -0
  78. package/dist/core/presets.d.ts +8 -3
  79. package/dist/core/presets.d.ts.map +1 -1
  80. package/dist/core/presets.js +6 -17
  81. package/dist/core/presets.js.map +1 -1
  82. package/dist/core/types.d.ts +4 -0
  83. package/dist/core/types.d.ts.map +1 -1
  84. package/dist/docs/app.d.ts.map +1 -1
  85. package/dist/docs/componentes/autocomplete.doc.d.ts.map +1 -1
  86. package/dist/docs/componentes/checkbox.doc.d.ts.map +1 -1
  87. package/dist/docs/componentes/datepicker.doc.d.ts.map +1 -1
  88. package/dist/docs/componentes/fileupload.doc.d.ts.map +1 -1
  89. package/dist/docs/componentes/input.doc.d.ts.map +1 -1
  90. package/dist/docs/componentes/knob.doc.d.ts.map +1 -1
  91. package/dist/docs/componentes/multiselect.doc.d.ts.map +1 -1
  92. package/dist/docs/componentes/rating.doc.d.ts.map +1 -1
  93. package/dist/docs/componentes/select.doc.d.ts.map +1 -1
  94. package/dist/docs/componentes/slider.doc.d.ts.map +1 -1
  95. package/dist/docs/componentes/switch.doc.d.ts.map +1 -1
  96. package/dist/docs/componentes/textarea.doc.d.ts.map +1 -1
  97. package/dist/docs/componentes/toast.doc.d.ts.map +1 -1
  98. package/dist/docs/componentes/toggle-button-group.doc.d.ts.map +1 -1
  99. package/dist/docs/frameworks.d.ts +75 -0
  100. package/dist/docs/frameworks.d.ts.map +1 -0
  101. package/dist/docs/ui/code.d.ts +4 -0
  102. package/dist/docs/ui/code.d.ts.map +1 -1
  103. package/dist/fenix-ui.umd.min.js +214 -176
  104. package/dist/fenix-ui4.esm.js +657 -67
  105. package/dist/fenix-ui4.esm.js.map +1 -1
  106. package/dist/index.d.ts +1 -0
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +4 -1
  109. package/dist/index.js.map +1 -1
  110. package/package.json +1 -1
@@ -4,7 +4,7 @@ var __typeError = (msg) => {
4
4
  var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
5
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
6
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
7
- var _FxToast_instances, syncProgress_fn, render_fn;
7
+ var _FxToast_instances, wirePause_fn, startTimer_fn, pauseTimer_fn, resumeTimer_fn, syncProgress_fn, render_fn, syncMore_fn;
8
8
  import { a as FENIX_ICON_BASE_CSS, f as fenixIconHtml, g as esc, l as loadFenixIconsFont, i as isFenixIconName } from "./assets/font-DPeWTz-n.js";
9
9
  import { h, j } from "./assets/font-DPeWTz-n.js";
10
10
  const lightTokens = {
@@ -167,15 +167,16 @@ const FenixUI = {
167
167
  tokenCssVars,
168
168
  deepMerge
169
169
  };
170
+ const DEFAULT_PRESET = "ikki";
170
171
  const registry = /* @__PURE__ */ new Map();
171
172
  function register(preset) {
172
173
  registry.set(preset.name, preset);
173
174
  return preset;
174
175
  }
175
176
  const themePresets = {
176
- fenix: register({
177
- name: "fenix",
178
- label: "Fenix (padrão)",
177
+ ikki: register({
178
+ name: "ikki",
179
+ label: "Ikki (padrão)",
179
180
  tokens: { effect: { "focus-ring": "none" } }
180
181
  }),
181
182
  seiya: register({
@@ -237,19 +238,6 @@ const themePresets = {
237
238
  radius: { sm: "10px", md: "14px", lg: "20px" }
238
239
  }
239
240
  }),
240
- ikki: register({
241
- name: "ikki",
242
- label: "Ikki",
243
- tokens: {
244
- color: {
245
- primary: "#ea580c",
246
- secondary: "#fb923c",
247
- danger: "#dc2626",
248
- warning: "#f59e0b"
249
- },
250
- radius: { sm: "2px", md: "4px", lg: "6px" }
251
- }
252
- }),
253
241
  aiolia: register({
254
242
  name: "aiolia",
255
243
  label: "Aiolia",
@@ -274,7 +262,7 @@ function defineCustomPreset(name, label, tokens) {
274
262
  return register({ name, label, tokens });
275
263
  }
276
264
  function applyPreset(presetName, mode = "light") {
277
- const preset = registry.get(presetName) ?? registry.get("fenix");
265
+ const preset = registry.get(presetName) ?? registry.get(DEFAULT_PRESET);
278
266
  const state = configure({ theme: mode, tokens: preset.tokens });
279
267
  return { ...state, preset: preset.name };
280
268
  }
@@ -290,7 +278,9 @@ const _FxElement = class _FxElement extends HTMLElement {
290
278
  }
291
279
  constructor() {
292
280
  super();
293
- this.root = this.attachShadow({ mode: "open" });
281
+ this.root = this.attachShadow(
282
+ this.constructor.shadowOptions
283
+ );
294
284
  }
295
285
  /** Chamado quando um atributo observado muda. */
296
286
  attributeChangedCallback(_name, _oldValue, _newValue) {
@@ -320,7 +310,132 @@ const _FxElement = class _FxElement extends HTMLElement {
320
310
  }
321
311
  };
322
312
  _FxElement.styles = "";
313
+ _FxElement.shadowOptions = { mode: "open" };
323
314
  let FxElement = _FxElement;
315
+ const _FxFormElement = class _FxFormElement extends FxElement {
316
+ constructor() {
317
+ super();
318
+ this._initialValue = "";
319
+ this._initialCaptured = false;
320
+ this._autofocused = false;
321
+ this.internals = typeof this.attachInternals === "function" ? this.attachInternals() : null;
322
+ }
323
+ /** Nome do campo no FormData (atributo `name`, como no nativo). */
324
+ get name() {
325
+ return this.getAttribute("name") ?? "";
326
+ }
327
+ /** Form ao qual o componente está associado (null se fora de um form). */
328
+ get form() {
329
+ return this.internals?.form ?? this.closest("form");
330
+ }
331
+ get type() {
332
+ return this.localName;
333
+ }
334
+ get willValidate() {
335
+ return this.internals?.willValidate ?? false;
336
+ }
337
+ get validity() {
338
+ return this.internals?.validity ?? null;
339
+ }
340
+ get validationMessage() {
341
+ return this.internals?.validationMessage ?? "";
342
+ }
343
+ get labels() {
344
+ return this.internals?.labels ?? this.childNodes;
345
+ }
346
+ checkValidity() {
347
+ return this.internals?.checkValidity?.() ?? true;
348
+ }
349
+ reportValidity() {
350
+ return this.internals?.reportValidity?.() ?? true;
351
+ }
352
+ connectedCallback() {
353
+ super.connectedCallback();
354
+ if (!this._initialCaptured) {
355
+ this._initialCaptured = true;
356
+ this._initialValue = this.getAttr("value");
357
+ }
358
+ this.updateFormValue();
359
+ if (!this._autofocused && this.hasAttr("autofocus")) {
360
+ this._autofocused = true;
361
+ queueMicrotask(() => {
362
+ if (this.isConnected && this.hasAttr("autofocus")) this.focusInnerControl();
363
+ });
364
+ }
365
+ }
366
+ /**
367
+ * Foca o controle interno: primeiro o HOST (com `delegatesFocus` o browser
368
+ * repassa ao controle interno) e, se nada for focado (ex.: ambientes sem
369
+ * delegatesFocus, como jsdom nos testes), o primeiro focável do shadow.
370
+ */
371
+ focusInnerControl() {
372
+ this.focus();
373
+ if (!this.root.activeElement) {
374
+ const first = this.root.querySelector(
375
+ 'input, textarea, button, select, [tabindex]:not([tabindex="-1"])'
376
+ );
377
+ first?.focus();
378
+ }
379
+ }
380
+ /**
381
+ * Valor publicado no FormData. Sobrescreva quando o valor NÃO é o atributo
382
+ * `value` (checkbox/switch enviariam `value || 'on'` quando marcados,
383
+ * seleção múltipla enviaria várias entradas, fileupload enviaria arquivos).
384
+ */
385
+ getFormValue() {
386
+ return this.getAttr("value") || null;
387
+ }
388
+ /**
389
+ * Sincroniza o campo interno, publica name/value no form (ElementInternals)
390
+ * e emite `input` (v-model do Vue) + `valueChange` ([(value)] do Angular).
391
+ *
392
+ * Chame quando o valor mudar — junto do evento semântico (`change`,
393
+ * `select`, ...) que o componente já dispara. `detail` opcional complementa
394
+ * o payload (ex.: { start, end } do fx-datepicker).
395
+ */
396
+ emitValueEvents(detail) {
397
+ const value = this.getAttr("value");
398
+ this.syncInternalControl();
399
+ this.updateFormValue();
400
+ const payload = { value, ...detail };
401
+ for (const type of ["input", "valueChange"]) {
402
+ this.dispatchEvent(
403
+ new CustomEvent(type, { bubbles: true, composed: true, detail: payload })
404
+ );
405
+ }
406
+ }
407
+ /** Sincroniza name/value com o form (ElementInternals). */
408
+ updateFormValue() {
409
+ if (!this.internals || typeof this.internals.setFormValue !== "function") return;
410
+ this.internals.setFormValue(this.getFormValue());
411
+ }
412
+ /**
413
+ * <form disabled>/<fieldset disabled>: espelha no atributo `disabled`
414
+ * observado — o re-render desabilita o controle interno.
415
+ */
416
+ formDisabledCallback(disabled) {
417
+ if (disabled) this.setAttribute("disabled", "");
418
+ else this.removeAttribute("disabled");
419
+ }
420
+ /** form.reset(): restaura o valor inicial e sincroniza o campo interno. */
421
+ formResetCallback() {
422
+ this.setAttribute("value", this._initialValue);
423
+ this.syncInternalControl();
424
+ this.updateFormValue();
425
+ }
426
+ /**
427
+ * Hook: empurra o valor atual do atributo `value` para o controle interno
428
+ * SEM re-renderizar (componentes com campo nativo interno sobrescrevem).
429
+ */
430
+ syncInternalControl() {
431
+ }
432
+ };
433
+ _FxFormElement.formAssociated = true;
434
+ _FxFormElement.shadowOptions = {
435
+ mode: "open",
436
+ delegatesFocus: true
437
+ };
438
+ let FxFormElement = _FxFormElement;
324
439
  function defineElement(tag, ctor) {
325
440
  if (typeof customElements !== "undefined" && !customElements.get(tag)) {
326
441
  customElements.define(tag, ctor);
@@ -330,7 +445,7 @@ function defineElement(tag, ctor) {
330
445
  const FX_JSX_TYPES = true;
331
446
  const _FxButton = class _FxButton extends FxElement {
332
447
  constructor() {
333
- super(...arguments);
448
+ super();
334
449
  this.spawnRipple = (event) => {
335
450
  if (this.hasAttr("disabled") || this.hasAttr("loading")) return;
336
451
  const enabled = getComputedStyle(this).getPropertyValue("--fx-effect-ripple").trim();
@@ -347,10 +462,20 @@ const _FxButton = class _FxButton extends FxElement {
347
462
  btn.appendChild(ripple);
348
463
  ripple.addEventListener("animationend", () => ripple.remove());
349
464
  };
465
+ this.internals = typeof this.attachInternals === "function" ? this.attachInternals() : null;
350
466
  }
351
467
  static get observedAttributes() {
352
468
  return ["variant", "size", "disabled", "loading", "type", "full", "icon", "icon-pos"];
353
469
  }
470
+ /** Form ao qual o botão está associado (null se fora de um form). */
471
+ get form() {
472
+ return this.internals?.form ?? this.closest("form");
473
+ }
474
+ /** <form disabled>/<fieldset disabled>: espelha no atributo observado. */
475
+ formDisabledCallback(disabled) {
476
+ if (disabled) this.setAttribute("disabled", "");
477
+ else this.removeAttribute("disabled");
478
+ }
354
479
  /** Tamanho. Padrão: `'md'`. */
355
480
  get size() {
356
481
  const s = this.getAttr("size", "md");
@@ -406,6 +531,13 @@ const _FxButton = class _FxButton extends FxElement {
406
531
  }
407
532
  if (loading) btn.setAttribute("aria-busy", "true");
408
533
  else btn.removeAttribute("aria-busy");
534
+ if (btnType === "submit" || btnType === "reset") {
535
+ btn.addEventListener("click", () => {
536
+ if (this.hasAttr("disabled") || this.hasAttr("loading")) return;
537
+ if (btnType === "submit" && this.form) this.form.requestSubmit();
538
+ else if (btnType === "reset" && this.form) this.form.reset();
539
+ });
540
+ }
409
541
  }
410
542
  connectedCallback() {
411
543
  super.connectedCallback();
@@ -416,6 +548,8 @@ const _FxButton = class _FxButton extends FxElement {
416
548
  this.removeEventListener("pointerdown", this.spawnRipple);
417
549
  }
418
550
  };
551
+ _FxButton.formAssociated = true;
552
+ _FxButton.shadowOptions = { mode: "open", delegatesFocus: true };
419
553
  _FxButton.styles = css`
420
554
  ${FENIX_ICON_BASE_CSS}
421
555
  :host {
@@ -619,7 +753,7 @@ function defineFxSpinner() {
619
753
  return defineElement("fx-spinner", FxSpinner);
620
754
  }
621
755
  defineFxSpinner();
622
- const _FxSelect = class _FxSelect extends FxElement {
756
+ const _FxSelect = class _FxSelect extends FxFormElement {
623
757
  // `value` fica FORA da observação: refleti-lo no change não pode
624
758
  // re-renderizar o template e fechar o dropdown.
625
759
  static get observedAttributes() {
@@ -638,6 +772,7 @@ const _FxSelect = class _FxSelect extends FxElement {
638
772
  }
639
773
  set value(value) {
640
774
  this.setAttribute("value", value);
775
+ this.updateFormValue();
641
776
  }
642
777
  get disabled() {
643
778
  return this.hasAttr("disabled");
@@ -739,6 +874,7 @@ const _FxSelect = class _FxSelect extends FxElement {
739
874
  this.value = value;
740
875
  this._fechar();
741
876
  this.render();
877
+ this.emitValueEvents();
742
878
  this.dispatchEvent(
743
879
  new CustomEvent("change", { bubbles: true, composed: true, detail: { value } })
744
880
  );
@@ -841,6 +977,7 @@ const _FxSelect = class _FxSelect extends FxElement {
841
977
  this.value = "";
842
978
  this._fechar();
843
979
  this.render();
980
+ this.emitValueEvents();
844
981
  this.dispatchEvent(
845
982
  new CustomEvent("change", { bubbles: true, composed: true, detail: { value: "" } })
846
983
  );
@@ -848,6 +985,12 @@ const _FxSelect = class _FxSelect extends FxElement {
848
985
  this.root.querySelectorAll(".opt").forEach((opt) => {
849
986
  opt.addEventListener("click", () => this.select(opt.dataset.value));
850
987
  });
988
+ this.updateFormValue();
989
+ }
990
+ /** form.reset(): restaura o valor inicial e re-renderiza o rótulo. */
991
+ formResetCallback() {
992
+ super.formResetCallback();
993
+ this.render();
851
994
  }
852
995
  };
853
996
  _FxSelect.styles = css`
@@ -1034,7 +1177,7 @@ function defineFxSelect() {
1034
1177
  return defineElement("fx-select", FxSelect);
1035
1178
  }
1036
1179
  defineFxSelect();
1037
- const _FxInput = class _FxInput extends FxElement {
1180
+ const _FxInput = class _FxInput extends FxFormElement {
1038
1181
  // `value` fica FORA da observação: refleti-lo a cada tecla não pode
1039
1182
  // re-renderizar o template, senão o campo perde o foco ao digitar.
1040
1183
  // `error`/`success` (+ aliases `invalid`/`valid`) PRECISAM ser observados:
@@ -1070,6 +1213,8 @@ const _FxInput = class _FxInput extends FxElement {
1070
1213
  }
1071
1214
  set value(value) {
1072
1215
  this.setAttribute("value", value);
1216
+ this.syncInternalControl();
1217
+ this.updateFormValue();
1073
1218
  }
1074
1219
  get disabled() {
1075
1220
  return this.hasAttr("disabled");
@@ -1109,6 +1254,7 @@ const _FxInput = class _FxInput extends FxElement {
1109
1254
  if (readonly) field.setAttribute("readonly", "");
1110
1255
  const emit = (event) => {
1111
1256
  this.value = field.value;
1257
+ this.emitValueEvents();
1112
1258
  this.dispatchEvent(
1113
1259
  new CustomEvent(event, { bubbles: true, composed: true, detail: { value: field.value } })
1114
1260
  );
@@ -1121,6 +1267,12 @@ const _FxInput = class _FxInput extends FxElement {
1121
1267
  e.stopPropagation();
1122
1268
  emit("change");
1123
1269
  });
1270
+ field.addEventListener("keydown", (e) => {
1271
+ if (e.key !== "Enter" || e.isComposing) return;
1272
+ if (!this.form) return;
1273
+ e.preventDefault();
1274
+ this.form.requestSubmit();
1275
+ });
1124
1276
  const clearBtn = this.root.querySelector(".clear");
1125
1277
  const syncClear = () => {
1126
1278
  clearBtn?.toggleAttribute("hidden", field.value === "");
@@ -1134,6 +1286,13 @@ const _FxInput = class _FxInput extends FxElement {
1134
1286
  field.focus();
1135
1287
  });
1136
1288
  field.addEventListener("input", syncClear);
1289
+ this.updateFormValue();
1290
+ }
1291
+ /** Empurra o atributo `value` para o campo interno sem re-renderizar. */
1292
+ syncInternalControl() {
1293
+ const field = this.root.querySelector(".field");
1294
+ const value = this.getAttr("value");
1295
+ if (field && field.value !== value) field.value = value;
1137
1296
  }
1138
1297
  };
1139
1298
  _FxInput.styles = css`
@@ -1277,7 +1436,12 @@ function defineFxInput() {
1277
1436
  return defineElement("fx-input", FxInput);
1278
1437
  }
1279
1438
  defineFxInput();
1280
- const _FxSwitch = class _FxSwitch extends FxElement {
1439
+ const _FxSwitch = class _FxSwitch extends FxFormElement {
1440
+ constructor() {
1441
+ super(...arguments);
1442
+ this._initialChecked = false;
1443
+ this._initialCheckedCaptured = false;
1444
+ }
1281
1445
  static get observedAttributes() {
1282
1446
  return ["checked", "disabled", "size"];
1283
1447
  }
@@ -1301,6 +1465,17 @@ const _FxSwitch = class _FxSwitch extends FxElement {
1301
1465
  set disabled(value) {
1302
1466
  this.toggleAttr("disabled", Boolean(value));
1303
1467
  }
1468
+ /** getter/setter `value` → espelho do atributo `value` (compat com v-model / [(value)]). */
1469
+ get value() {
1470
+ return this.getAttr("value");
1471
+ }
1472
+ set value(v) {
1473
+ this.setAttribute("value", v);
1474
+ }
1475
+ /** No FormData: `value` (ou 'on') quando ligado; null (não envia) quando não. */
1476
+ getFormValue() {
1477
+ return this.checked ? this.getAttr("value") || "on" : null;
1478
+ }
1304
1479
  render() {
1305
1480
  this.setTemplate(`
1306
1481
  <button class="switch" part="switch" type="button" role="switch"
@@ -1322,7 +1497,29 @@ const _FxSwitch = class _FxSwitch extends FxElement {
1322
1497
  detail: { checked: this.checked }
1323
1498
  })
1324
1499
  );
1500
+ this.dispatchEvent(
1501
+ new CustomEvent("checkedChange", {
1502
+ bubbles: true,
1503
+ composed: true,
1504
+ detail: { checked: this.checked, value: this.getAttr("value") }
1505
+ })
1506
+ );
1325
1507
  });
1508
+ this.updateFormValue();
1509
+ }
1510
+ connectedCallback() {
1511
+ if (!this._initialCheckedCaptured) {
1512
+ this._initialCheckedCaptured = true;
1513
+ this._initialChecked = this.checked;
1514
+ }
1515
+ super.connectedCallback();
1516
+ }
1517
+ /** form.reset(): restaura o estado inicial (desligado por padrão). */
1518
+ formResetCallback() {
1519
+ this.checked = this._initialChecked;
1520
+ const btn = this.root.querySelector(".switch");
1521
+ if (btn) btn.setAttribute("aria-checked", String(this.checked));
1522
+ this.updateFormValue();
1326
1523
  }
1327
1524
  };
1328
1525
  _FxSwitch.styles = css`
@@ -1385,7 +1582,7 @@ function defineFxSwitch() {
1385
1582
  return defineElement("fx-switch", FxSwitch);
1386
1583
  }
1387
1584
  defineFxSwitch();
1388
- const _FxMultiselect = class _FxMultiselect extends FxElement {
1585
+ const _FxMultiselect = class _FxMultiselect extends FxFormElement {
1389
1586
  constructor() {
1390
1587
  super(...arguments);
1391
1588
  this.selected = /* @__PURE__ */ new Set();
@@ -1401,6 +1598,14 @@ const _FxMultiselect = class _FxMultiselect extends FxElement {
1401
1598
  set values(list) {
1402
1599
  this.setAttribute("values", list.join(","));
1403
1600
  }
1601
+ /** No FormData do form: uma entrada `name` por valor selecionado. */
1602
+ getFormValue() {
1603
+ const values = this.values;
1604
+ if (!this.name || values.length === 0) return null;
1605
+ const fd = new FormData();
1606
+ values.forEach((v) => fd.append(this.name, v));
1607
+ return fd;
1608
+ }
1404
1609
  get disabled() {
1405
1610
  return this.hasAttr("disabled");
1406
1611
  }
@@ -1415,6 +1620,7 @@ const _FxMultiselect = class _FxMultiselect extends FxElement {
1415
1620
  }
1416
1621
  connectedCallback() {
1417
1622
  this.selected = new Set(this.values);
1623
+ if (!this._initialValues) this._initialValues = [...this.values];
1418
1624
  super.connectedCallback();
1419
1625
  this.observer = new MutationObserver(() => this.render());
1420
1626
  this.observer.observe(this, { childList: true, subtree: true, characterData: true });
@@ -1435,6 +1641,7 @@ const _FxMultiselect = class _FxMultiselect extends FxElement {
1435
1641
  commit() {
1436
1642
  this.values = [...this.selected];
1437
1643
  this.render();
1644
+ this.updateFormValue();
1438
1645
  this.dispatchEvent(
1439
1646
  new CustomEvent("change", {
1440
1647
  bubbles: true,
@@ -1442,6 +1649,21 @@ const _FxMultiselect = class _FxMultiselect extends FxElement {
1442
1649
  detail: { values: [...this.selected] }
1443
1650
  })
1444
1651
  );
1652
+ this.dispatchEvent(
1653
+ new CustomEvent("valuesChange", {
1654
+ bubbles: true,
1655
+ composed: true,
1656
+ detail: { values: [...this.selected] }
1657
+ })
1658
+ );
1659
+ }
1660
+ /** form.reset(): restaura a seleção inicial. */
1661
+ formResetCallback() {
1662
+ const initial = this._initialValues ?? [];
1663
+ this.values = initial;
1664
+ this.selected = new Set(initial);
1665
+ this.render();
1666
+ this.updateFormValue();
1445
1667
  }
1446
1668
  toggleValue(value) {
1447
1669
  if (this.selected.has(value)) this.selected.delete(value);
@@ -2247,7 +2469,7 @@ function defineFxCalendar() {
2247
2469
  return defineElement("fx-calendar", FxCalendar);
2248
2470
  }
2249
2471
  defineFxCalendar();
2250
- const _FxDatepicker = class _FxDatepicker extends FxElement {
2472
+ const _FxDatepicker = class _FxDatepicker extends FxFormElement {
2251
2473
  constructor() {
2252
2474
  super(...arguments);
2253
2475
  this.time = { h: 0, m: 0, s: 0 };
@@ -2255,6 +2477,32 @@ const _FxDatepicker = class _FxDatepicker extends FxElement {
2255
2477
  static get observedAttributes() {
2256
2478
  return ["value", "start", "end", "values", "mode", "min", "max", "placeholder", "disabled", "format", "show-time", "free-text", "error", "invalid", "success", "valid"];
2257
2479
  }
2480
+ attributeChangedCallback(name, oldValue, newValue) {
2481
+ super.attributeChangedCallback(name, oldValue, newValue);
2482
+ if (name === "value" || name === "start" || name === "end" || name === "values") {
2483
+ this.updateFormValue();
2484
+ }
2485
+ }
2486
+ /** No FormData do form, conforme o modo (single/range/multiple). */
2487
+ getFormValue() {
2488
+ if (this.mode === "range") {
2489
+ const start = this.getAttr("start");
2490
+ const end = this.getAttr("end");
2491
+ if (!this.name || !start && !end) return null;
2492
+ const fd = new FormData();
2493
+ if (start) fd.append(`${this.name}-start`, start);
2494
+ if (end) fd.append(`${this.name}-end`, end);
2495
+ return fd;
2496
+ }
2497
+ if (this.mode === "multiple") {
2498
+ const values = this.values;
2499
+ if (!this.name || values.length === 0) return null;
2500
+ const fd = new FormData();
2501
+ values.forEach((v) => fd.append(this.name, v));
2502
+ return fd;
2503
+ }
2504
+ return this.getAttr("value") || null;
2505
+ }
2258
2506
  get mode() {
2259
2507
  const m = this.getAttr("mode", "single");
2260
2508
  return ["single", "range", "multiple"].includes(m) ? m : "single";
@@ -2283,6 +2531,14 @@ const _FxDatepicker = class _FxDatepicker extends FxElement {
2283
2531
  this.toggleAttr("disabled", Boolean(value));
2284
2532
  }
2285
2533
  connectedCallback() {
2534
+ if (!this._initialState) {
2535
+ this._initialState = {
2536
+ value: this.getAttr("value"),
2537
+ start: this.getAttr("start"),
2538
+ end: this.getAttr("end"),
2539
+ values: this.getAttr("values")
2540
+ };
2541
+ }
2286
2542
  super.connectedCallback();
2287
2543
  this.docListener = (e) => {
2288
2544
  if (!this.hasAttr("open")) return;
@@ -2295,6 +2551,15 @@ const _FxDatepicker = class _FxDatepicker extends FxElement {
2295
2551
  super.disconnectedCallback();
2296
2552
  if (this.docListener) document.removeEventListener("click", this.docListener);
2297
2553
  }
2554
+ /** form.reset(): restaura value/start/end/values iniciais (re-render via atributos observados). */
2555
+ formResetCallback() {
2556
+ const s = this._initialState ?? { value: "", start: "", end: "", values: "" };
2557
+ this.setAttribute("value", s.value);
2558
+ this.setAttribute("start", s.start);
2559
+ this.setAttribute("end", s.end);
2560
+ this.setAttribute("values", s.values);
2561
+ this.updateFormValue();
2562
+ }
2298
2563
  /** Formata uma data ISO ("YYYY-MM-DD" ou com "THH:mm:ss") segundo o atributo format. */
2299
2564
  formatDate(iso) {
2300
2565
  if (!iso) return "";
@@ -2467,6 +2732,7 @@ const _FxDatepicker = class _FxDatepicker extends FxElement {
2467
2732
  }
2468
2733
  }
2469
2734
  emitChange(detail) {
2735
+ this.emitValueEvents(detail);
2470
2736
  this.dispatchEvent(
2471
2737
  new CustomEvent("change", { bubbles: true, composed: true, detail })
2472
2738
  );
@@ -2690,7 +2956,12 @@ function defineFxDatepicker() {
2690
2956
  return defineElement("fx-datepicker", FxDatepicker);
2691
2957
  }
2692
2958
  defineFxDatepicker();
2693
- const _FxCheckbox = class _FxCheckbox extends FxElement {
2959
+ const _FxCheckbox = class _FxCheckbox extends FxFormElement {
2960
+ constructor() {
2961
+ super(...arguments);
2962
+ this._initialChecked = false;
2963
+ this._initialCheckedCaptured = false;
2964
+ }
2694
2965
  static get observedAttributes() {
2695
2966
  return ["checked", "disabled", "indeterminate"];
2696
2967
  }
@@ -2712,6 +2983,17 @@ const _FxCheckbox = class _FxCheckbox extends FxElement {
2712
2983
  set disabled(value) {
2713
2984
  this.toggleAttr("disabled", Boolean(value));
2714
2985
  }
2986
+ /** No FormData: `value` (ou 'on') quando marcado; null (não envia) quando não. */
2987
+ getFormValue() {
2988
+ return this.checked ? this.getAttr("value") || "on" : null;
2989
+ }
2990
+ /** getter/setter `value` → espelho do atributo `value` (compat com v-model / [(value)]). */
2991
+ get value() {
2992
+ return this.getAttr("value");
2993
+ }
2994
+ set value(v) {
2995
+ this.setAttribute("value", v);
2996
+ }
2715
2997
  /** Tamanho do controle. Padrão: `'md'`. */
2716
2998
  get size() {
2717
2999
  const s = this.getAttr("size", "md");
@@ -2744,6 +3026,20 @@ const _FxCheckbox = class _FxCheckbox extends FxElement {
2744
3026
  this.toggle();
2745
3027
  }
2746
3028
  });
3029
+ this.updateFormValue();
3030
+ }
3031
+ connectedCallback() {
3032
+ if (!this._initialCheckedCaptured) {
3033
+ this._initialCheckedCaptured = true;
3034
+ this._initialChecked = this.checked;
3035
+ }
3036
+ super.connectedCallback();
3037
+ }
3038
+ /** form.reset(): restaura o estado marcado inicial (checked/indeterminate não enviam nada). */
3039
+ formResetCallback() {
3040
+ this.checked = this._initialChecked;
3041
+ this.indeterminate = false;
3042
+ this.updateFormValue();
2747
3043
  }
2748
3044
  toggle() {
2749
3045
  if (this.disabled) return;
@@ -2756,6 +3052,13 @@ const _FxCheckbox = class _FxCheckbox extends FxElement {
2756
3052
  detail: { checked: this.checked, value: this.getAttr("value") }
2757
3053
  })
2758
3054
  );
3055
+ this.dispatchEvent(
3056
+ new CustomEvent("checkedChange", {
3057
+ bubbles: true,
3058
+ composed: true,
3059
+ detail: { checked: this.checked, value: this.getAttr("value") }
3060
+ })
3061
+ );
2759
3062
  }
2760
3063
  };
2761
3064
  _FxCheckbox.styles = css`
@@ -2825,7 +3128,12 @@ function defineFxCheckbox() {
2825
3128
  return defineElement("fx-checkbox", FxCheckbox);
2826
3129
  }
2827
3130
  defineFxCheckbox();
2828
- const _FxRadio = class _FxRadio extends FxElement {
3131
+ const _FxRadio = class _FxRadio extends FxFormElement {
3132
+ constructor() {
3133
+ super(...arguments);
3134
+ this._initialChecked = false;
3135
+ this._initialCheckedCaptured = false;
3136
+ }
2829
3137
  static get observedAttributes() {
2830
3138
  return ["checked", "disabled"];
2831
3139
  }
@@ -2841,6 +3149,25 @@ const _FxRadio = class _FxRadio extends FxElement {
2841
3149
  set disabled(value) {
2842
3150
  this.toggleAttr("disabled", Boolean(value));
2843
3151
  }
3152
+ /**
3153
+ * Grupo = radios com o mesmo `name` NA MESMA árvore. `getRootNode()` devolve
3154
+ * o document OU o ShadowRoot do app consumidor — grupos com o mesmo name
3155
+ * em shadow DOMs diferentes ficam isolados (o `document` global cruzaria
3156
+ * fronteiras indevidamente).
3157
+ */
3158
+ _group() {
3159
+ const name = this.getAttr("name");
3160
+ if (!name) return [this];
3161
+ const root = this.getRootNode();
3162
+ const safeName = name.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
3163
+ return Array.from(
3164
+ root.querySelectorAll(`fx-radio[name="${safeName}"]`)
3165
+ );
3166
+ }
3167
+ /** No FormData: só o radio MARCADO envia `value` (ou 'on'). */
3168
+ getFormValue() {
3169
+ return this.checked ? this.getAttr("value") || "on" : null;
3170
+ }
2844
3171
  /** Tamanho do controle. Padrão: `'md'`. */
2845
3172
  get size() {
2846
3173
  const s = this.getAttr("size", "md");
@@ -2865,19 +3192,35 @@ const _FxRadio = class _FxRadio extends FxElement {
2865
3192
  if (!box) return;
2866
3193
  box.addEventListener("click", () => this.select());
2867
3194
  box.addEventListener("keydown", (e) => {
2868
- if (e.key === " " || e.key === "Enter") {
3195
+ const key2 = e.key;
3196
+ if (key2 === " " || key2 === "Enter") {
2869
3197
  e.preventDefault();
2870
3198
  this.select();
3199
+ return;
3200
+ }
3201
+ if (key2 === "ArrowDown" || key2 === "ArrowRight" || key2 === "ArrowUp" || key2 === "ArrowLeft") {
3202
+ e.preventDefault();
3203
+ this._moveFocus(key2 === "ArrowDown" || key2 === "ArrowRight" ? 1 : -1);
2871
3204
  }
2872
3205
  });
3206
+ this.updateFormValue();
3207
+ }
3208
+ /** Move o foco/marcação para o próximo (ou anterior) radio do grupo. */
3209
+ _moveFocus(dir) {
3210
+ const group = this._group().filter((r) => !r.disabled);
3211
+ if (group.length < 2) return;
3212
+ const i = group.indexOf(this);
3213
+ const next = group[(i + dir + group.length) % group.length];
3214
+ next.focus();
3215
+ if (!next.root.activeElement) {
3216
+ next.root.querySelector(".box")?.focus();
3217
+ }
3218
+ if (!next.checked) next.select();
2873
3219
  }
2874
3220
  select() {
2875
3221
  if (this.disabled || this.checked) return;
2876
- const name = this.getAttr("name");
2877
- if (name) {
2878
- document.querySelectorAll(`fx-radio[name="${CSS.escape(name)}"]`).forEach((r) => {
2879
- r.checked = false;
2880
- });
3222
+ for (const r of this._group()) {
3223
+ if (r !== this) r.checked = false;
2881
3224
  }
2882
3225
  this.checked = true;
2883
3226
  this.dispatchEvent(
@@ -2887,6 +3230,25 @@ const _FxRadio = class _FxRadio extends FxElement {
2887
3230
  detail: { checked: true, value: this.getAttr("value") }
2888
3231
  })
2889
3232
  );
3233
+ this.dispatchEvent(
3234
+ new CustomEvent("checkedChange", {
3235
+ bubbles: true,
3236
+ composed: true,
3237
+ detail: { checked: true, value: this.getAttr("value") }
3238
+ })
3239
+ );
3240
+ }
3241
+ connectedCallback() {
3242
+ if (!this._initialCheckedCaptured) {
3243
+ this._initialCheckedCaptured = true;
3244
+ this._initialChecked = this.checked;
3245
+ }
3246
+ super.connectedCallback();
3247
+ }
3248
+ /** form.reset(): restaura o estado marcado inicial do grupo. */
3249
+ formResetCallback() {
3250
+ this.checked = this._initialChecked;
3251
+ this.updateFormValue();
2890
3252
  }
2891
3253
  };
2892
3254
  _FxRadio.styles = css`
@@ -4062,28 +4424,43 @@ const _FxFloatlabel = class _FxFloatlabel extends FxElement {
4062
4424
  this.targetControl = null;
4063
4425
  this.observer = null;
4064
4426
  this.listenersReady = false;
4427
+ this._label = null;
4428
+ this._labelClickBound = false;
4065
4429
  }
4066
4430
  static get observedAttributes() {
4067
4431
  return ["variant", "error", "invalid", "success", "valid", "full", "icon", "icon-pos"];
4068
4432
  }
4069
4433
  render() {
4070
4434
  const control = this.findControl();
4071
- const label = this.findLabel();
4072
- const originalLabel = label;
4073
- const labelText = originalLabel ? originalLabel.textContent?.replace(/\s+/g, " ").trim() : "";
4074
- const labelFor = originalLabel ? originalLabel.getAttribute("for") || "" : "";
4435
+ const lightLabel = this.findLabel();
4436
+ if (lightLabel) this._label = lightLabel;
4437
+ const label = this._label;
4075
4438
  const isOver = this.getAttribute("variant") === "over";
4076
4439
  this.setTemplate(`
4077
4440
  <div class="wrapper" part="wrapper">
4078
- ${isOver ? `<label class="flabel" part="label" for="${labelFor}">${labelText}</label>` : ""}
4079
4441
  <div class="field-box" part="field">
4080
4442
  <slot></slot>
4081
- ${isOver ? "" : `<label class="flabel" part="label" for="${labelFor}">${labelText}</label>`}
4082
4443
  </div>
4083
4444
  <div class="error-message" part="error"></div>
4084
4445
  </div>
4085
4446
  `);
4086
- if (originalLabel) originalLabel.setAttribute("hidden", "");
4447
+ if (label) {
4448
+ label.classList.add("flabel");
4449
+ label.setAttribute("part", "label");
4450
+ const wrapper = this.root.querySelector(".wrapper");
4451
+ const fieldBox = this.root.querySelector(".field-box");
4452
+ if (wrapper && fieldBox) {
4453
+ if (isOver) wrapper.insertBefore(label, fieldBox);
4454
+ else fieldBox.appendChild(label);
4455
+ }
4456
+ if (!this._labelClickBound) {
4457
+ this._labelClickBound = true;
4458
+ label.addEventListener("click", (e) => {
4459
+ e.preventDefault();
4460
+ this.targetControl?.focus?.();
4461
+ });
4462
+ }
4463
+ }
4087
4464
  this.attachEvents();
4088
4465
  this.syncControlReference(control);
4089
4466
  this.syncState();
@@ -4382,7 +4759,7 @@ function defineFxFloatlabel() {
4382
4759
  return defineElement("fx-floatlabel", FxFloatlabel);
4383
4760
  }
4384
4761
  defineFxFloatlabel();
4385
- const _FxTextarea = class _FxTextarea extends FxElement {
4762
+ const _FxTextarea = class _FxTextarea extends FxFormElement {
4386
4763
  static get observedAttributes() {
4387
4764
  return ["size", "placeholder", "disabled", "readonly", "rows", "maxlength", "error", "invalid", "success", "valid"];
4388
4765
  }
@@ -4398,6 +4775,8 @@ const _FxTextarea = class _FxTextarea extends FxElement {
4398
4775
  }
4399
4776
  set value(value) {
4400
4777
  this.setAttribute("value", value);
4778
+ this.syncInternalControl();
4779
+ this.updateFormValue();
4401
4780
  }
4402
4781
  render() {
4403
4782
  const placeholder = this.getAttr("placeholder");
@@ -4417,6 +4796,7 @@ const _FxTextarea = class _FxTextarea extends FxElement {
4417
4796
  if (readonly) field.setAttribute("readonly", "");
4418
4797
  const emit = (event) => {
4419
4798
  this.value = field.value;
4799
+ this.emitValueEvents();
4420
4800
  this.dispatchEvent(
4421
4801
  new CustomEvent(event, { bubbles: true, composed: true, detail: { value: field.value } })
4422
4802
  );
@@ -4429,6 +4809,13 @@ const _FxTextarea = class _FxTextarea extends FxElement {
4429
4809
  e.stopPropagation();
4430
4810
  emit("change");
4431
4811
  });
4812
+ this.updateFormValue();
4813
+ }
4814
+ /** Empurra o atributo `value` para o campo interno sem re-renderizar. */
4815
+ syncInternalControl() {
4816
+ const field = this.root.querySelector(".field");
4817
+ const value = this.getAttr("value");
4818
+ if (field && field.value !== value) field.value = value;
4432
4819
  }
4433
4820
  };
4434
4821
  _FxTextarea.styles = css`
@@ -4971,7 +5358,7 @@ function defineFxConfirmPopup() {
4971
5358
  return defineElement("fx-confirmpopup", FxConfirmPopup);
4972
5359
  }
4973
5360
  defineFxConfirmPopup();
4974
- const _FxFileUpload = class _FxFileUpload extends FxElement {
5361
+ const _FxFileUpload = class _FxFileUpload extends FxFormElement {
4975
5362
  constructor() {
4976
5363
  super(...arguments);
4977
5364
  this._files = [];
@@ -5009,6 +5396,14 @@ const _FxFileUpload = class _FxFileUpload extends FxElement {
5009
5396
  this.value = this._files.map((f) => f.name).join(", ");
5010
5397
  this._syncFilesArea();
5011
5398
  }
5399
+ /** No FormData do form: os arquivos selecionados, como o <input type="file">. */
5400
+ getFormValue() {
5401
+ const files = this._files;
5402
+ if (!this.name || files.length === 0) return null;
5403
+ const fd = new FormData();
5404
+ for (const f of files) fd.append(this.name, f, f.name);
5405
+ return fd;
5406
+ }
5012
5407
  render() {
5013
5408
  const mode = this.getAttr("mode", "basic");
5014
5409
  const advanced = mode === "advanced";
@@ -5041,6 +5436,7 @@ const _FxFileUpload = class _FxFileUpload extends FxElement {
5041
5436
  `);
5042
5437
  this._bindPicker();
5043
5438
  this._syncFilesArea();
5439
+ this.updateFormValue();
5044
5440
  if (!this._done && showProgress && pct >= 100 && this._files.length) {
5045
5441
  this._done = true;
5046
5442
  this.dispatchEvent(new CustomEvent("complete", { bubbles: true, composed: true }));
@@ -5089,6 +5485,12 @@ const _FxFileUpload = class _FxFileUpload extends FxElement {
5089
5485
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
5090
5486
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
5091
5487
  }
5488
+ /** form.reset(): limpa os arquivos selecionados. */
5489
+ formResetCallback() {
5490
+ this._files = [];
5491
+ this.value = "";
5492
+ this.updateFormValue();
5493
+ }
5092
5494
  /** Renderiza a lista de arquivos sem re-renderizar o template (mantém estado). */
5093
5495
  _syncFilesArea() {
5094
5496
  const area = this.root?.querySelector?.(".files");
@@ -5260,7 +5662,7 @@ function defineFxFileUpload() {
5260
5662
  return defineElement("fx-fileupload", FxFileUpload);
5261
5663
  }
5262
5664
  defineFxFileUpload();
5263
- const _FxSlider = class _FxSlider extends FxElement {
5665
+ const _FxSlider = class _FxSlider extends FxFormElement {
5264
5666
  constructor() {
5265
5667
  super(...arguments);
5266
5668
  this._dragging = false;
@@ -5284,6 +5686,7 @@ const _FxSlider = class _FxSlider extends FxElement {
5284
5686
  const snapped = this._snap(v);
5285
5687
  this.setAttribute("value", String(snapped));
5286
5688
  if (this.isConnected) this._syncVisual();
5689
+ this.updateFormValue();
5287
5690
  }
5288
5691
  _num(attr, fallback) {
5289
5692
  const raw = this.getAttribute(attr);
@@ -5358,6 +5761,7 @@ const _FxSlider = class _FxSlider extends FxElement {
5358
5761
  window.removeEventListener("pointerup", up);
5359
5762
  this.value = valueFromEvent(e);
5360
5763
  this._syncVisual();
5764
+ this.emitValueEvents();
5361
5765
  this.dispatchEvent(
5362
5766
  new CustomEvent("change", { bubbles: true, composed: true, detail: { value: this.value } })
5363
5767
  );
@@ -5412,6 +5816,7 @@ const _FxSlider = class _FxSlider extends FxElement {
5412
5816
  ev.preventDefault();
5413
5817
  this.value = next;
5414
5818
  this._syncVisual();
5819
+ this.emitValueEvents();
5415
5820
  this.dispatchEvent(
5416
5821
  new CustomEvent("input", { bubbles: true, composed: true, detail: { value: this.value } })
5417
5822
  );
@@ -5438,6 +5843,11 @@ const _FxSlider = class _FxSlider extends FxElement {
5438
5843
  const labelPct = this.root.querySelector(".label span:last-child");
5439
5844
  if (labelPct) labelPct.textContent = String(this.value);
5440
5845
  }
5846
+ /** form.reset(): restaura o valor inicial e o visual sem re-render. */
5847
+ formResetCallback() {
5848
+ super.formResetCallback();
5849
+ this._syncVisual();
5850
+ }
5441
5851
  };
5442
5852
  _FxSlider.styles = css`
5443
5853
  :host {
@@ -6380,7 +6790,7 @@ function defineFxStepper() {
6380
6790
  return defineElement("fx-stepper", FxStepper);
6381
6791
  }
6382
6792
  defineFxStepper();
6383
- const _FxRating = class _FxRating extends FxElement {
6793
+ const _FxRating = class _FxRating extends FxFormElement {
6384
6794
  static get observedAttributes() {
6385
6795
  return ["value", "max", "readonly", "disabled", "allow-half", "size"];
6386
6796
  }
@@ -6391,6 +6801,7 @@ const _FxRating = class _FxRating extends FxElement {
6391
6801
  set value(v) {
6392
6802
  this.setAttribute("value", String(Math.min(this.max, Math.max(0, v))));
6393
6803
  this._syncVisual();
6804
+ this.updateFormValue();
6394
6805
  }
6395
6806
  get max() {
6396
6807
  const n = Number(this.getAttr("max", "5"));
@@ -6410,6 +6821,7 @@ const _FxRating = class _FxRating extends FxElement {
6410
6821
  `);
6411
6822
  this._bindStars();
6412
6823
  this._syncVisual();
6824
+ this.updateFormValue();
6413
6825
  }
6414
6826
  _starNodes() {
6415
6827
  return Array.from(this.root.querySelectorAll(".star"));
@@ -6433,6 +6845,7 @@ const _FxRating = class _FxRating extends FxElement {
6433
6845
  let next = n;
6434
6846
  if (this.hasAttr("allow-half")) next += -0.5;
6435
6847
  this.value = next;
6848
+ this.emitValueEvents();
6436
6849
  this.dispatchEvent(
6437
6850
  new CustomEvent("change", { bubbles: true, composed: true, detail: { value: this.value } })
6438
6851
  );
@@ -6639,7 +7052,7 @@ function defineFxMenu() {
6639
7052
  return defineElement("fx-menu", FxMenu);
6640
7053
  }
6641
7054
  defineFxMenu();
6642
- const _FxToggleButtonGroup = class _FxToggleButtonGroup extends FxElement {
7055
+ const _FxToggleButtonGroup = class _FxToggleButtonGroup extends FxFormElement {
6643
7056
  static get observedAttributes() {
6644
7057
  return ["multiple", "size"];
6645
7058
  }
@@ -6649,10 +7062,19 @@ const _FxToggleButtonGroup = class _FxToggleButtonGroup extends FxElement {
6649
7062
  }
6650
7063
  set value(v) {
6651
7064
  this.setAttribute("value", v.join(","));
7065
+ this.updateFormValue();
6652
7066
  }
6653
7067
  get multiple() {
6654
7068
  return this.hasAttr("multiple");
6655
7069
  }
7070
+ /** No FormData do form: uma entrada `name` por valor selecionado. */
7071
+ getFormValue() {
7072
+ const values = this.value;
7073
+ if (!this.name || values.length === 0) return null;
7074
+ const fd = new FormData();
7075
+ values.forEach((v) => fd.append(this.name, v));
7076
+ return fd;
7077
+ }
6656
7078
  get options() {
6657
7079
  return Array.from(this.children).filter((el) => el.matches('[slot="option"]')).map((el) => el);
6658
7080
  }
@@ -6670,6 +7092,13 @@ const _FxToggleButtonGroup = class _FxToggleButtonGroup extends FxElement {
6670
7092
  </div>
6671
7093
  `);
6672
7094
  this._bindOptions();
7095
+ this.updateFormValue();
7096
+ }
7097
+ /** form.reset(): restaura a seleção inicial e re-renderiza os botões. */
7098
+ formResetCallback() {
7099
+ this.setAttribute("value", this._initialValue);
7100
+ this.render();
7101
+ this.updateFormValue();
6673
7102
  }
6674
7103
  _bindOptions() {
6675
7104
  Array.from(this.root.querySelectorAll(".option") || []).forEach((opt) => {
@@ -6699,6 +7128,7 @@ const _FxToggleButtonGroup = class _FxToggleButtonGroup extends FxElement {
6699
7128
  o.setAttribute("aria-pressed", String(on));
6700
7129
  });
6701
7130
  this.dispatchEvent(new CustomEvent("change", { bubbles: true, composed: true, detail: { value: current } }));
7131
+ this.dispatchEvent(new CustomEvent("valuesChange", { bubbles: true, composed: true, detail: { values: [...current] } }));
6702
7132
  }
6703
7133
  };
6704
7134
  _FxToggleButtonGroup.styles = css`
@@ -7379,6 +7809,7 @@ const KIND_ICON = {
7379
7809
  warning: "!",
7380
7810
  info: "i"
7381
7811
  };
7812
+ const COPY_SVG = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="12" height="12" rx="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>';
7382
7813
  const CARD_CSS = `
7383
7814
  :host { display: contents; pointer-events: auto; }
7384
7815
  /* Modo explícito: sobrepõe os tokens herdados do tema global SOMENTE neste card. */
@@ -7412,8 +7843,11 @@ const CARD_CSS = `
7412
7843
  user-select: none;
7413
7844
  }
7414
7845
  .card {
7415
- display: flex; align-items: flex-start; gap: 10px;
7846
+ display: flex; align-items: center; gap: 10px;
7416
7847
  min-width: 260px; max-width: 360px;
7848
+ /* Altura mínima idêntica à de um toast COM título: sem título o card não
7849
+ fica "achatado" e o conjunto mantém o mesmo ritmo visual. */
7850
+ min-height: 56px;
7417
7851
  background: var(--fx-surface-background, #fff);
7418
7852
  color: var(--fx-text-default, #1e293b);
7419
7853
  border: 1px solid var(--fx-border-default, #e2e8f0);
@@ -7432,16 +7866,45 @@ const CARD_CSS = `
7432
7866
  color: #fff;
7433
7867
  display: flex; align-items: center; justify-content: center;
7434
7868
  font-size: 12px; font-weight: 700;
7435
- flex: none; margin-top: 2px;
7869
+ flex: none; align-self: center;
7436
7870
  user-select: none;
7437
7871
  }
7438
- .body { flex: 1; min-width: 0; }
7872
+ .body { flex: 1; min-width: 0; align-self: center; }
7439
7873
  .title { font-weight: 600; font-size: 14px; }
7440
- .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; }
7441
- .close {
7874
+ .msg {
7875
+ font-size: calc(var(--fx-font-size, 14px) - 2px);
7876
+ color: var(--fx-text-muted, #64748b);
7877
+ margin-top: 2px;
7878
+ word-break: break-word; white-space: pre-line;
7879
+ /* Mensagens longas mantêm o card no tamanho padrão e terminam em "…";
7880
+ "Ver mais" expande o toast (sem perder o restante do texto). */
7881
+ display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
7882
+ line-clamp: 2; overflow: hidden;
7883
+ }
7884
+ .card.expanded .msg {
7885
+ display: block; overflow: visible;
7886
+ -webkit-line-clamp: unset; line-clamp: unset;
7887
+ }
7888
+ .more {
7889
+ all: unset; cursor: pointer; display: inline-block;
7890
+ margin-top: 4px; padding: 2px 0;
7891
+ font-size: 11px; font-weight: 600; color: var(--kind);
7892
+ }
7893
+ .more[hidden] { display: none; }
7894
+ .more:hover { text-decoration: underline; }
7895
+ .actions {
7896
+ display: flex; align-items: center; gap: 2px;
7897
+ flex: none; align-self: flex-start;
7898
+ }
7899
+ .copy, .close {
7442
7900
  all: unset; cursor: pointer; flex: none; line-height: 1;
7443
7901
  color: var(--fx-text-muted, #64748b); font-size: 15px; padding: 2px 4px; border-radius: 4px;
7444
7902
  }
7903
+ .copy[hidden] { display: none; }
7904
+ .copy { font-size: 14px; display: inline-flex; align-items: center; }
7905
+ .copy svg { display: block; }
7906
+ .copy.done { color: var(--fx-color-success, #10b981); }
7907
+ .copy:hover { color: var(--fx-color-primary, #6366f1); background: var(--fx-surface-surface-hover, rgba(0,0,0,.05)); }
7445
7908
  .close:hover { color: var(--fx-color-danger, #f43f5e); background: var(--fx-surface-surface-hover, rgba(0,0,0,.05)); }
7446
7909
 
7447
7910
  /* --- Barra de contagem regressiva (atributo progress) ---
@@ -7470,9 +7933,12 @@ const CARD_CSS = `
7470
7933
  will-change: transform;
7471
7934
  }
7472
7935
  .progress.run { animation: fx-toast-drain var(--toast-duration, 4000ms) linear forwards; }
7473
- /* No modo hover a animação fica parada até o cursor/foco entrar no card. */
7936
+ /* Passar o mouse (ou focar) no toast pausa a contagem: a barra e o timer
7937
+ congelam juntos até o cursor sair — tempo de ler a mensagem inteira.
7938
+ progress="hover" é mantido por compatibilidade e se comporta como
7939
+ "always" (o hover já pausa a contagem). */
7474
7940
  .card:hover .progress.run,
7475
- .card:focus-within .progress.run { animation-play-state: running; }
7941
+ .card:focus-within .progress.run { animation-play-state: paused; }
7476
7942
  @keyframes fx-toast-drain {
7477
7943
  from { transform: scaleX(1); }
7478
7944
  to { transform: scaleX(0); }
@@ -7491,6 +7957,12 @@ class FxToast extends HTMLElement {
7491
7957
  super();
7492
7958
  __privateAdd(this, _FxToast_instances);
7493
7959
  this.timer = null;
7960
+ this.remaining = 0;
7961
+ this.startedAt = 0;
7962
+ this.hovering = false;
7963
+ this.disposed = false;
7964
+ this.counting = false;
7965
+ this.wired = false;
7494
7966
  this.attachShadow({ mode: "open" });
7495
7967
  }
7496
7968
  static get observedAttributes() {
@@ -7499,19 +7971,19 @@ class FxToast extends HTMLElement {
7499
7971
  /**
7500
7972
  * Exibe a barra de contagem regressiva na base do card.
7501
7973
  *
7502
- * A barra esvazia da esquerda para a direita durante exatamente `duration`
7503
- * e usa a cor do `kind`. Com `duration="0"` (toast fixo) a barra é
7504
- * omitida — não há tempo restante a representar.
7974
+ * A barra esvazia durante exatamente `duration` e usa a cor do `kind`.
7975
+ * Com `duration="0"` (toast fixo) a barra é omitida — não há tempo restante.
7505
7976
  *
7506
7977
  * - `'always'` (ou atributo vazio): barra sempre correndo.
7507
- * - `'hover'`: a barra só corre enquanto o cursor estiver sobre o toast;
7508
- * ao sair, a animação pausa, então dá tempo de ler a mensagem.
7509
7978
  * - `'paused'`: barra visível, porém parada.
7979
+ * - `'hover'`: **compatibilidade** — equivale a `'always'`. O hover agora
7980
+ * pausa a contagem (timer + barra) em qualquer modo, então a barra "só
7981
+ * correr sob o cursor" deixou de fazer sentido.
7510
7982
  */
7511
7983
  get progress() {
7512
7984
  if (!this.hasAttribute("progress")) return void 0;
7513
7985
  const v = this.getAttribute("progress");
7514
- return v === "hover" || v === "paused" ? v : "always";
7986
+ return v === "paused" ? "paused" : "always";
7515
7987
  }
7516
7988
  set progress(value) {
7517
7989
  if (value) this.setAttribute("progress", value);
@@ -7551,11 +8023,13 @@ class FxToast extends HTMLElement {
7551
8023
  }
7552
8024
  connectedCallback() {
7553
8025
  if (!this.shadowRoot.firstChild) __privateMethod(this, _FxToast_instances, render_fn).call(this);
8026
+ this.disposed = false;
8027
+ __privateMethod(this, _FxToast_instances, wirePause_fn).call(this);
7554
8028
  const safe = this.safeDuration;
7555
- if (safe > 0 && !this.timer) {
7556
- this.timer = setTimeout(() => this.dismiss(), safe);
7557
- }
8029
+ this.counting = safe > 0;
8030
+ if (safe > 0 && !this.timer) __privateMethod(this, _FxToast_instances, startTimer_fn).call(this, safe);
7558
8031
  __privateMethod(this, _FxToast_instances, syncProgress_fn).call(this);
8032
+ __privateMethod(this, _FxToast_instances, syncMore_fn).call(this);
7559
8033
  }
7560
8034
  /**
7561
8035
  * Limpa recursos ao sair do DOM e avisa a API imperativa (`FenixToast`).
@@ -7566,11 +8040,21 @@ class FxToast extends HTMLElement {
7566
8040
  * sem limite em uso prolongado.
7567
8041
  */
7568
8042
  disconnectedCallback() {
8043
+ this.disposed = true;
7569
8044
  if (this.timer) clearTimeout(this.timer);
7570
8045
  this.timer = null;
7571
8046
  this.dispatchEvent(new CustomEvent(REMOVED_EVENT, { bubbles: false, composed: false }));
7572
8047
  }
7573
8048
  attributeChangedCallback(name) {
8049
+ if (name === "duration") {
8050
+ if (this.timer) {
8051
+ clearTimeout(this.timer);
8052
+ this.timer = null;
8053
+ }
8054
+ const safe = this.safeDuration;
8055
+ this.counting = safe > 0;
8056
+ if (safe > 0 && this.isConnected && !this.disposed) __privateMethod(this, _FxToast_instances, startTimer_fn).call(this, safe);
8057
+ }
7574
8058
  if (name === "duration" || name === "progress" || name === "progress-origin" || name === "kind") {
7575
8059
  __privateMethod(this, _FxToast_instances, syncProgress_fn).call(this);
7576
8060
  }
@@ -7588,6 +8072,49 @@ class FxToast extends HTMLElement {
7588
8072
  }
7589
8073
  }
7590
8074
  _FxToast_instances = new WeakSet();
8075
+ /**
8076
+ * Pausa a contagem enquanto o cursor (ou o foco) estiver no toast e retoma
8077
+ * de onde parou ao sair — assim uma mensagem longa pode ser lida inteira.
8078
+ * A barra de progresso pausa junto (regra CSS de :hover/:focus-within).
8079
+ */
8080
+ wirePause_fn = function() {
8081
+ if (this.wired) return;
8082
+ this.wired = true;
8083
+ this.addEventListener("mouseenter", () => {
8084
+ this.hovering = true;
8085
+ __privateMethod(this, _FxToast_instances, pauseTimer_fn).call(this);
8086
+ });
8087
+ this.addEventListener("mouseleave", () => {
8088
+ this.hovering = false;
8089
+ __privateMethod(this, _FxToast_instances, resumeTimer_fn).call(this);
8090
+ });
8091
+ this.addEventListener("focusin", () => __privateMethod(this, _FxToast_instances, pauseTimer_fn).call(this));
8092
+ this.addEventListener("focusout", () => {
8093
+ if (!this.hovering) __privateMethod(this, _FxToast_instances, resumeTimer_fn).call(this);
8094
+ });
8095
+ };
8096
+ startTimer_fn = function(ms) {
8097
+ this.remaining = ms;
8098
+ this.startedAt = Date.now();
8099
+ this.timer = setTimeout(() => {
8100
+ this.timer = null;
8101
+ this.dismiss();
8102
+ }, ms);
8103
+ };
8104
+ pauseTimer_fn = function() {
8105
+ if (!this.timer) return;
8106
+ clearTimeout(this.timer);
8107
+ this.timer = null;
8108
+ this.remaining = Math.max(0, this.remaining - (Date.now() - this.startedAt));
8109
+ };
8110
+ resumeTimer_fn = function() {
8111
+ if (!this.counting || this.timer || this.disposed) return;
8112
+ if (this.remaining <= 0) {
8113
+ this.dismiss();
8114
+ return;
8115
+ }
8116
+ __privateMethod(this, _FxToast_instances, startTimer_fn).call(this, this.remaining);
8117
+ };
7591
8118
  /**
7592
8119
  * Aplica (ou remove) a barra de contagem regressiva.
7593
8120
  *
@@ -7632,17 +8159,60 @@ render_fn = function() {
7632
8159
  <div class="card" style="--kind:${color}" role="status" part="card">
7633
8160
  <span class="icon${isFxIcon ? " fx-icon" : ""}">${iconContent}</span>
7634
8161
  <div class="body">
7635
- ${title ? '<div class="title"></div>' : ""}
7636
- ${msg ? '<div class="msg"></div>' : ""}
8162
+ ${title ? '<div class="title" part="title"></div>' : ""}
8163
+ ${msg ? '<div class="msg" part="message"></div>' : ""}
8164
+ ${msg ? '<button class="more" part="more" hidden>Ver mais</button>' : ""}
8165
+ </div>
8166
+ <div class="actions">
8167
+ ${msg ? `<button class="copy" part="copy" hidden aria-label="Copiar mensagem" title="Copiar mensagem">${COPY_SVG}</button>` : ""}
8168
+ <button class="close" aria-label="Fechar" title="Fechar">✕</button>
7637
8169
  </div>
7638
- <button class="close" aria-label="Fechar">✕</button>
7639
8170
  </div>`;
7640
8171
  const t = this.shadowRoot.querySelector(".title");
7641
8172
  const m = this.shadowRoot.querySelector(".msg");
7642
8173
  if (t) t.textContent = title;
7643
- if (m) m.textContent = msg;
8174
+ if (m) {
8175
+ m.textContent = msg;
8176
+ m.title = msg;
8177
+ }
8178
+ const card = this.shadowRoot.querySelector(".card");
8179
+ const more = this.shadowRoot.querySelector(".more");
8180
+ const copy = this.shadowRoot.querySelector(".copy");
8181
+ more?.addEventListener("click", () => {
8182
+ const expanded = card.classList.toggle("expanded");
8183
+ more.textContent = expanded ? "Ver menos" : "Ver mais";
8184
+ if (copy) copy.hidden = !expanded;
8185
+ if (expanded) __privateMethod(this, _FxToast_instances, pauseTimer_fn).call(this);
8186
+ });
8187
+ copy?.addEventListener("click", (e) => {
8188
+ const btn = e.currentTarget;
8189
+ const text = [title, msg].filter(Boolean).join("\n");
8190
+ navigator.clipboard?.writeText(text).catch(() => {
8191
+ });
8192
+ btn.classList.add("done");
8193
+ btn.title = "Copiado!";
8194
+ setTimeout(() => {
8195
+ btn.classList.remove("done");
8196
+ btn.title = "Copiar mensagem";
8197
+ }, 1200);
8198
+ });
7644
8199
  this.shadowRoot.querySelector(".close").addEventListener("click", () => this.dismiss());
7645
8200
  };
8201
+ /**
8202
+ * Mostra "Ver mais" somente quando a mensagem passou de 2 linhas.
8203
+ * Sem layout (jsdom) scrollHeight/clientHeight são 0 → fica oculto.
8204
+ */
8205
+ syncMore_fn = function() {
8206
+ const card = this.shadowRoot?.querySelector(".card");
8207
+ const more = this.shadowRoot?.querySelector(".more");
8208
+ const msg = this.shadowRoot?.querySelector(".msg");
8209
+ if (!card || !more || !msg) return;
8210
+ if (card.classList.contains("expanded")) {
8211
+ more.hidden = false;
8212
+ return;
8213
+ }
8214
+ more.hidden = !(msg.scrollHeight - msg.clientHeight > 1);
8215
+ };
7646
8216
  function regionFor(position) {
7647
8217
  let region = document.querySelector(`[data-fx-toast-region="${position}"]`);
7648
8218
  if (!region) {
@@ -9010,7 +9580,7 @@ function defineFxPagination() {
9010
9580
  return defineElement("fx-pagination", FxPagination);
9011
9581
  }
9012
9582
  defineFxPagination();
9013
- const _FxAutocomplete = class _FxAutocomplete extends FxElement {
9583
+ const _FxAutocomplete = class _FxAutocomplete extends FxFormElement {
9014
9584
  static get observedAttributes() {
9015
9585
  return ["size", "placeholder", "source", "disabled", "min-chars", "error", "invalid", "success", "valid"];
9016
9586
  }
@@ -9026,6 +9596,8 @@ const _FxAutocomplete = class _FxAutocomplete extends FxElement {
9026
9596
  }
9027
9597
  set value(v) {
9028
9598
  this.setAttribute("value", v);
9599
+ this.syncInternalControl();
9600
+ this.updateFormValue();
9029
9601
  }
9030
9602
  get source() {
9031
9603
  try {
@@ -9075,6 +9647,7 @@ const _FxAutocomplete = class _FxAutocomplete extends FxElement {
9075
9647
  field.value = btn.dataset.v ?? "";
9076
9648
  this.value = field.value;
9077
9649
  this.toggleAttr("open", false);
9650
+ this.emitValueEvents();
9078
9651
  this.dispatchEvent(new CustomEvent("select", {
9079
9652
  bubbles: true,
9080
9653
  composed: true,
@@ -9086,6 +9659,7 @@ const _FxAutocomplete = class _FxAutocomplete extends FxElement {
9086
9659
  };
9087
9660
  field.addEventListener("input", () => {
9088
9661
  this.value = field.value;
9662
+ this.emitValueEvents();
9089
9663
  openList();
9090
9664
  });
9091
9665
  field.addEventListener("focus", () => {
@@ -9096,6 +9670,11 @@ const _FxAutocomplete = class _FxAutocomplete extends FxElement {
9096
9670
  this.toggleAttr("open", false);
9097
9671
  return;
9098
9672
  }
9673
+ if (e.key === "Enter" && !e.isComposing && !this.hasAttr("open") && this.form) {
9674
+ e.preventDefault();
9675
+ this.form.requestSubmit();
9676
+ return;
9677
+ }
9099
9678
  if (e.key !== "ArrowDown" && e.key !== "ArrowUp") return;
9100
9679
  e.preventDefault();
9101
9680
  if (!this.hasAttr("open")) openList();
@@ -9105,6 +9684,13 @@ const _FxAutocomplete = class _FxAutocomplete extends FxElement {
9105
9684
  const next = e.key === "ArrowDown" ? current === -1 ? 0 : Math.min(current + 1, opts.length - 1) : current === -1 ? opts.length - 1 : Math.max(current - 1, 0);
9106
9685
  opts[next]?.focus();
9107
9686
  });
9687
+ this.updateFormValue();
9688
+ }
9689
+ /** Empurra o atributo `value` para o campo interno sem re-renderizar. */
9690
+ syncInternalControl() {
9691
+ const field = this.root.querySelector(".field");
9692
+ const value = this.getAttr("value");
9693
+ if (field && field.value !== value) field.value = value;
9108
9694
  }
9109
9695
  };
9110
9696
  _FxAutocomplete.styles = css`
@@ -9198,7 +9784,7 @@ function defineFxAutocomplete() {
9198
9784
  return defineElement("fx-autocomplete", FxAutocomplete);
9199
9785
  }
9200
9786
  defineFxAutocomplete();
9201
- const _FxKnob = class _FxKnob extends FxElement {
9787
+ const _FxKnob = class _FxKnob extends FxFormElement {
9202
9788
  constructor() {
9203
9789
  super(...arguments);
9204
9790
  this._isDragging = false;
@@ -9343,6 +9929,7 @@ const _FxKnob = class _FxKnob extends FxElement {
9343
9929
  </div>
9344
9930
  `);
9345
9931
  this.attachListeners();
9932
+ this.updateFormValue();
9346
9933
  }
9347
9934
  getSizeNum() {
9348
9935
  const s = this.size;
@@ -9374,6 +9961,7 @@ const _FxKnob = class _FxKnob extends FxElement {
9374
9961
  const clamped = this.clampValue(newValue);
9375
9962
  if (clamped !== this.value) {
9376
9963
  this.value = clamped;
9964
+ this.emitValueEvents();
9377
9965
  this.dispatchEvent(
9378
9966
  new CustomEvent("change", {
9379
9967
  bubbles: true,
@@ -11203,6 +11791,7 @@ function defineFxAccordionPanel() {
11203
11791
  defineFxAccordion();
11204
11792
  defineFxAccordionPanel();
11205
11793
  export {
11794
+ DEFAULT_PRESET,
11206
11795
  FX_JSX_TYPES,
11207
11796
  FenixToast,
11208
11797
  FenixUI,
@@ -11229,6 +11818,7 @@ export {
11229
11818
  FxEmptyState,
11230
11819
  FxFileUpload,
11231
11820
  FxFloatlabel,
11821
+ FxFormElement,
11232
11822
  FxIcon,
11233
11823
  FxInput,
11234
11824
  FxKnob,