@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
@@ -17,14 +17,14 @@ export declare class FxToast extends HTMLElement {
17
17
  /**
18
18
  * Exibe a barra de contagem regressiva na base do card.
19
19
  *
20
- * A barra esvazia da esquerda para a direita durante exatamente `duration`
21
- * e usa a cor do `kind`. Com `duration="0"` (toast fixo) a barra é
22
- * omitida — não há tempo restante a representar.
20
+ * A barra esvazia durante exatamente `duration` e usa a cor do `kind`.
21
+ * Com `duration="0"` (toast fixo) a barra é omitida — não há tempo restante.
23
22
  *
24
23
  * - `'always'` (ou atributo vazio): barra sempre correndo.
25
- * - `'hover'`: a barra só corre enquanto o cursor estiver sobre o toast;
26
- * ao sair, a animação pausa, então dá tempo de ler a mensagem.
27
24
  * - `'paused'`: barra visível, porém parada.
25
+ * - `'hover'`: **compatibilidade** — equivale a `'always'`. O hover agora
26
+ * pausa a contagem (timer + barra) em qualquer modo, então a barra "só
27
+ * correr sob o cursor" deixou de fazer sentido.
28
28
  */
29
29
  get progress(): 'always' | 'hover' | 'paused' | undefined;
30
30
  set progress(value: 'always' | 'hover' | 'paused' | undefined);
@@ -47,6 +47,18 @@ export declare class FxToast extends HTMLElement {
47
47
  get mode(): 'light' | 'dark' | undefined;
48
48
  set mode(value: 'light' | 'dark' | undefined);
49
49
  private timer;
50
+ /** Tempo restante (ms) quando a contagem está pausada no hover/foco. */
51
+ private remaining;
52
+ /** Instante em que o timer atual começou (para descontar o tempo decorrido). */
53
+ private startedAt;
54
+ /** Cursor sobre o toast: não retoma a contagem no focusout. */
55
+ private hovering;
56
+ /** Já saiu do DOM — impede retomar a contagem. */
57
+ private disposed;
58
+ /** Existe contagem regressiva (duration > 0) — toasts fixos nunca retomam. */
59
+ private counting;
60
+ /** Listeners de pausa/retomada registrados uma única vez. */
61
+ private wired;
50
62
  constructor();
51
63
  connectedCallback(): void;
52
64
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"toast.d.ts","sourceRoot":"","sources":["../../../src/components/toast/toast.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,QAAA,MAAM,SAAS,kGAGL,CAAC;AACX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AACvD,KAAK,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;AAoI1D,qBAAa,OAAQ,SAAQ,WAAW;;IACtC,MAAM,KAAK,kBAAkB,aAE5B;IAED;;;;;;;;;;;OAWG;IACH,IAAI,QAAQ,IAAI,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAIxD;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,EAG5D;IAED;;;;;;;;;;OAUG;IACH,IAAI,cAAc,IAAI,MAAM,GAAG,OAAO,CAErC;IACD,IAAI,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,EAEzC;IAED,oEAAoE;IACpE,OAAO,KAAK,YAAY,GAIvB;IAED,qFAAqF;IACrF,IAAI,IAAI,IAAI,OAAO,GAAG,MAAM,GAAG,SAAS,CAGvC;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,EAG3C;IAED,OAAO,CAAC,KAAK,CAA8C;;IAO3D,iBAAiB;IAWjB;;;;;;;OAOG;IACH,oBAAoB;IAMpB,wBAAwB,CAAC,IAAI,EAAE,MAAM;IA6CrC,OAAO;CAqCR;AA8BD,MAAM,WAAW,YAAY;IAC3B,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;IACzC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,0EAA0E;IAC1E,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CACzB;AAED,cAAM,QAAQ;IACZ,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,GAAG,CAA8B;IAEzC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,YAAY,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,MAAM;IA0BxG,KAAK,CAAC,EAAE,EAAE,MAAM;IAKhB,OAAO,GAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,YAAY,YAAmC;IACrF,KAAK,GAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,YAAY,YAAiC;IACjF,IAAI,GAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,YAAY,YAAgC;IAC/E,OAAO,GAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,YAAY,YAAmC;CACtF;AAED,eAAO,MAAM,UAAU,UAAiB,CAAC;AAIzC,wBAAgB,aAAa,SAE5B"}
1
+ {"version":3,"file":"toast.d.ts","sourceRoot":"","sources":["../../../src/components/toast/toast.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,QAAA,MAAM,SAAS,kGAGL,CAAC;AACX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AACvD,KAAK,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;AA2K1D,qBAAa,OAAQ,SAAQ,WAAW;;IACtC,MAAM,KAAK,kBAAkB,aAE5B;IAED;;;;;;;;;;;OAWG;IACH,IAAI,QAAQ,IAAI,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAIxD;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,EAG5D;IAED;;;;;;;;;;OAUG;IACH,IAAI,cAAc,IAAI,MAAM,GAAG,OAAO,CAErC;IACD,IAAI,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,EAEzC;IAED,oEAAoE;IACpE,OAAO,KAAK,YAAY,GAIvB;IAED,qFAAqF;IACrF,IAAI,IAAI,IAAI,OAAO,GAAG,MAAM,GAAG,SAAS,CAGvC;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,EAG3C;IAED,OAAO,CAAC,KAAK,CAA8C;IAC3D,wEAAwE;IACxE,OAAO,CAAC,SAAS,CAAK;IACtB,gFAAgF;IAChF,OAAO,CAAC,SAAS,CAAK;IACtB,+DAA+D;IAC/D,OAAO,CAAC,QAAQ,CAAS;IACzB,kDAAkD;IAClD,OAAO,CAAC,QAAQ,CAAS;IACzB,8EAA8E;IAC9E,OAAO,CAAC,QAAQ,CAAS;IACzB,6DAA6D;IAC7D,OAAO,CAAC,KAAK,CAAS;;IAOtB,iBAAiB;IA8DjB;;;;;;;OAOG;IACH,oBAAoB;IAOpB,wBAAwB,CAAC,IAAI,EAAE,MAAM;IAsDrC,OAAO;CAqFR;AA8BD,MAAM,WAAW,YAAY;IAC3B,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;IACzC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,0EAA0E;IAC1E,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CACzB;AAED,cAAM,QAAQ;IACZ,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,GAAG,CAA8B;IAEzC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,YAAY,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,MAAM;IA0BxG,KAAK,CAAC,EAAE,EAAE,MAAM;IAKhB,OAAO,GAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,YAAY,YAAmC;IACrF,KAAK,GAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,YAAY,YAAiC;IACjF,IAAI,GAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,YAAY,YAAgC;IAC/E,OAAO,GAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,YAAY,YAAmC;CACtF;AAED,eAAO,MAAM,UAAU,UAAiB,CAAC;AAIzC,wBAAgB,aAAa,SAE5B"}
@@ -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 { __fenixPreload } from "../../_virtual/preload-helper.js";
9
9
  const REMOVED_EVENT = "fx-toast:removed";
10
10
  const KIND_COLOR = {
@@ -19,6 +19,7 @@ const KIND_ICON = {
19
19
  warning: "!",
20
20
  info: "i"
21
21
  };
22
+ 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>';
22
23
  const CARD_CSS = `
23
24
  :host { display: contents; pointer-events: auto; }
24
25
  /* Modo explícito: sobrepõe os tokens herdados do tema global SOMENTE neste card. */
@@ -52,8 +53,11 @@ const CARD_CSS = `
52
53
  user-select: none;
53
54
  }
54
55
  .card {
55
- display: flex; align-items: flex-start; gap: 10px;
56
+ display: flex; align-items: center; gap: 10px;
56
57
  min-width: 260px; max-width: 360px;
58
+ /* Altura mínima idêntica à de um toast COM título: sem título o card não
59
+ fica "achatado" e o conjunto mantém o mesmo ritmo visual. */
60
+ min-height: 56px;
57
61
  background: var(--fx-surface-background, #fff);
58
62
  color: var(--fx-text-default, #1e293b);
59
63
  border: 1px solid var(--fx-border-default, #e2e8f0);
@@ -72,16 +76,45 @@ const CARD_CSS = `
72
76
  color: #fff;
73
77
  display: flex; align-items: center; justify-content: center;
74
78
  font-size: 12px; font-weight: 700;
75
- flex: none; margin-top: 2px;
79
+ flex: none; align-self: center;
76
80
  user-select: none;
77
81
  }
78
- .body { flex: 1; min-width: 0; }
82
+ .body { flex: 1; min-width: 0; align-self: center; }
79
83
  .title { font-weight: 600; font-size: 14px; }
80
- .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; }
81
- .close {
84
+ .msg {
85
+ font-size: calc(var(--fx-font-size, 14px) - 2px);
86
+ color: var(--fx-text-muted, #64748b);
87
+ margin-top: 2px;
88
+ word-break: break-word; white-space: pre-line;
89
+ /* Mensagens longas mantêm o card no tamanho padrão e terminam em "…";
90
+ "Ver mais" expande o toast (sem perder o restante do texto). */
91
+ display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
92
+ line-clamp: 2; overflow: hidden;
93
+ }
94
+ .card.expanded .msg {
95
+ display: block; overflow: visible;
96
+ -webkit-line-clamp: unset; line-clamp: unset;
97
+ }
98
+ .more {
99
+ all: unset; cursor: pointer; display: inline-block;
100
+ margin-top: 4px; padding: 2px 0;
101
+ font-size: 11px; font-weight: 600; color: var(--kind);
102
+ }
103
+ .more[hidden] { display: none; }
104
+ .more:hover { text-decoration: underline; }
105
+ .actions {
106
+ display: flex; align-items: center; gap: 2px;
107
+ flex: none; align-self: flex-start;
108
+ }
109
+ .copy, .close {
82
110
  all: unset; cursor: pointer; flex: none; line-height: 1;
83
111
  color: var(--fx-text-muted, #64748b); font-size: 15px; padding: 2px 4px; border-radius: 4px;
84
112
  }
113
+ .copy[hidden] { display: none; }
114
+ .copy { font-size: 14px; display: inline-flex; align-items: center; }
115
+ .copy svg { display: block; }
116
+ .copy.done { color: var(--fx-color-success, #10b981); }
117
+ .copy:hover { color: var(--fx-color-primary, #6366f1); background: var(--fx-surface-surface-hover, rgba(0,0,0,.05)); }
85
118
  .close:hover { color: var(--fx-color-danger, #f43f5e); background: var(--fx-surface-surface-hover, rgba(0,0,0,.05)); }
86
119
 
87
120
  /* --- Barra de contagem regressiva (atributo progress) ---
@@ -110,9 +143,12 @@ const CARD_CSS = `
110
143
  will-change: transform;
111
144
  }
112
145
  .progress.run { animation: fx-toast-drain var(--toast-duration, 4000ms) linear forwards; }
113
- /* No modo hover a animação fica parada até o cursor/foco entrar no card. */
146
+ /* Passar o mouse (ou focar) no toast pausa a contagem: a barra e o timer
147
+ congelam juntos até o cursor sair — tempo de ler a mensagem inteira.
148
+ progress="hover" é mantido por compatibilidade e se comporta como
149
+ "always" (o hover já pausa a contagem). */
114
150
  .card:hover .progress.run,
115
- .card:focus-within .progress.run { animation-play-state: running; }
151
+ .card:focus-within .progress.run { animation-play-state: paused; }
116
152
  @keyframes fx-toast-drain {
117
153
  from { transform: scaleX(1); }
118
154
  to { transform: scaleX(0); }
@@ -131,6 +167,12 @@ class FxToast extends HTMLElement {
131
167
  super();
132
168
  __privateAdd(this, _FxToast_instances);
133
169
  this.timer = null;
170
+ this.remaining = 0;
171
+ this.startedAt = 0;
172
+ this.hovering = false;
173
+ this.disposed = false;
174
+ this.counting = false;
175
+ this.wired = false;
134
176
  this.attachShadow({ mode: "open" });
135
177
  }
136
178
  static get observedAttributes() {
@@ -139,19 +181,19 @@ class FxToast extends HTMLElement {
139
181
  /**
140
182
  * Exibe a barra de contagem regressiva na base do card.
141
183
  *
142
- * A barra esvazia da esquerda para a direita durante exatamente `duration`
143
- * e usa a cor do `kind`. Com `duration="0"` (toast fixo) a barra é
144
- * omitida — não há tempo restante a representar.
184
+ * A barra esvazia durante exatamente `duration` e usa a cor do `kind`.
185
+ * Com `duration="0"` (toast fixo) a barra é omitida — não há tempo restante.
145
186
  *
146
187
  * - `'always'` (ou atributo vazio): barra sempre correndo.
147
- * - `'hover'`: a barra só corre enquanto o cursor estiver sobre o toast;
148
- * ao sair, a animação pausa, então dá tempo de ler a mensagem.
149
188
  * - `'paused'`: barra visível, porém parada.
189
+ * - `'hover'`: **compatibilidade** — equivale a `'always'`. O hover agora
190
+ * pausa a contagem (timer + barra) em qualquer modo, então a barra "só
191
+ * correr sob o cursor" deixou de fazer sentido.
150
192
  */
151
193
  get progress() {
152
194
  if (!this.hasAttribute("progress")) return void 0;
153
195
  const v = this.getAttribute("progress");
154
- return v === "hover" || v === "paused" ? v : "always";
196
+ return v === "paused" ? "paused" : "always";
155
197
  }
156
198
  set progress(value) {
157
199
  if (value) this.setAttribute("progress", value);
@@ -191,11 +233,13 @@ class FxToast extends HTMLElement {
191
233
  }
192
234
  connectedCallback() {
193
235
  if (!this.shadowRoot.firstChild) __privateMethod(this, _FxToast_instances, render_fn).call(this);
236
+ this.disposed = false;
237
+ __privateMethod(this, _FxToast_instances, wirePause_fn).call(this);
194
238
  const safe = this.safeDuration;
195
- if (safe > 0 && !this.timer) {
196
- this.timer = setTimeout(() => this.dismiss(), safe);
197
- }
239
+ this.counting = safe > 0;
240
+ if (safe > 0 && !this.timer) __privateMethod(this, _FxToast_instances, startTimer_fn).call(this, safe);
198
241
  __privateMethod(this, _FxToast_instances, syncProgress_fn).call(this);
242
+ __privateMethod(this, _FxToast_instances, syncMore_fn).call(this);
199
243
  }
200
244
  /**
201
245
  * Limpa recursos ao sair do DOM e avisa a API imperativa (`FenixToast`).
@@ -206,11 +250,21 @@ class FxToast extends HTMLElement {
206
250
  * sem limite em uso prolongado.
207
251
  */
208
252
  disconnectedCallback() {
253
+ this.disposed = true;
209
254
  if (this.timer) clearTimeout(this.timer);
210
255
  this.timer = null;
211
256
  this.dispatchEvent(new CustomEvent(REMOVED_EVENT, { bubbles: false, composed: false }));
212
257
  }
213
258
  attributeChangedCallback(name) {
259
+ if (name === "duration") {
260
+ if (this.timer) {
261
+ clearTimeout(this.timer);
262
+ this.timer = null;
263
+ }
264
+ const safe = this.safeDuration;
265
+ this.counting = safe > 0;
266
+ if (safe > 0 && this.isConnected && !this.disposed) __privateMethod(this, _FxToast_instances, startTimer_fn).call(this, safe);
267
+ }
214
268
  if (name === "duration" || name === "progress" || name === "progress-origin" || name === "kind") {
215
269
  __privateMethod(this, _FxToast_instances, syncProgress_fn).call(this);
216
270
  }
@@ -228,6 +282,49 @@ class FxToast extends HTMLElement {
228
282
  }
229
283
  }
230
284
  _FxToast_instances = new WeakSet();
285
+ /**
286
+ * Pausa a contagem enquanto o cursor (ou o foco) estiver no toast e retoma
287
+ * de onde parou ao sair — assim uma mensagem longa pode ser lida inteira.
288
+ * A barra de progresso pausa junto (regra CSS de :hover/:focus-within).
289
+ */
290
+ wirePause_fn = function() {
291
+ if (this.wired) return;
292
+ this.wired = true;
293
+ this.addEventListener("mouseenter", () => {
294
+ this.hovering = true;
295
+ __privateMethod(this, _FxToast_instances, pauseTimer_fn).call(this);
296
+ });
297
+ this.addEventListener("mouseleave", () => {
298
+ this.hovering = false;
299
+ __privateMethod(this, _FxToast_instances, resumeTimer_fn).call(this);
300
+ });
301
+ this.addEventListener("focusin", () => __privateMethod(this, _FxToast_instances, pauseTimer_fn).call(this));
302
+ this.addEventListener("focusout", () => {
303
+ if (!this.hovering) __privateMethod(this, _FxToast_instances, resumeTimer_fn).call(this);
304
+ });
305
+ };
306
+ startTimer_fn = function(ms) {
307
+ this.remaining = ms;
308
+ this.startedAt = Date.now();
309
+ this.timer = setTimeout(() => {
310
+ this.timer = null;
311
+ this.dismiss();
312
+ }, ms);
313
+ };
314
+ pauseTimer_fn = function() {
315
+ if (!this.timer) return;
316
+ clearTimeout(this.timer);
317
+ this.timer = null;
318
+ this.remaining = Math.max(0, this.remaining - (Date.now() - this.startedAt));
319
+ };
320
+ resumeTimer_fn = function() {
321
+ if (!this.counting || this.timer || this.disposed) return;
322
+ if (this.remaining <= 0) {
323
+ this.dismiss();
324
+ return;
325
+ }
326
+ __privateMethod(this, _FxToast_instances, startTimer_fn).call(this, this.remaining);
327
+ };
231
328
  /**
232
329
  * Aplica (ou remove) a barra de contagem regressiva.
233
330
  *
@@ -272,17 +369,60 @@ render_fn = function() {
272
369
  <div class="card" style="--kind:${color}" role="status" part="card">
273
370
  <span class="icon${isFxIcon ? " fx-icon" : ""}">${iconContent}</span>
274
371
  <div class="body">
275
- ${title ? '<div class="title"></div>' : ""}
276
- ${msg ? '<div class="msg"></div>' : ""}
372
+ ${title ? '<div class="title" part="title"></div>' : ""}
373
+ ${msg ? '<div class="msg" part="message"></div>' : ""}
374
+ ${msg ? '<button class="more" part="more" hidden>Ver mais</button>' : ""}
375
+ </div>
376
+ <div class="actions">
377
+ ${msg ? `<button class="copy" part="copy" hidden aria-label="Copiar mensagem" title="Copiar mensagem">${COPY_SVG}</button>` : ""}
378
+ <button class="close" aria-label="Fechar" title="Fechar">✕</button>
277
379
  </div>
278
- <button class="close" aria-label="Fechar">✕</button>
279
380
  </div>`;
280
381
  const t = this.shadowRoot.querySelector(".title");
281
382
  const m = this.shadowRoot.querySelector(".msg");
282
383
  if (t) t.textContent = title;
283
- if (m) m.textContent = msg;
384
+ if (m) {
385
+ m.textContent = msg;
386
+ m.title = msg;
387
+ }
388
+ const card = this.shadowRoot.querySelector(".card");
389
+ const more = this.shadowRoot.querySelector(".more");
390
+ const copy = this.shadowRoot.querySelector(".copy");
391
+ more?.addEventListener("click", () => {
392
+ const expanded = card.classList.toggle("expanded");
393
+ more.textContent = expanded ? "Ver menos" : "Ver mais";
394
+ if (copy) copy.hidden = !expanded;
395
+ if (expanded) __privateMethod(this, _FxToast_instances, pauseTimer_fn).call(this);
396
+ });
397
+ copy?.addEventListener("click", (e) => {
398
+ const btn = e.currentTarget;
399
+ const text = [title, msg].filter(Boolean).join("\n");
400
+ navigator.clipboard?.writeText(text).catch(() => {
401
+ });
402
+ btn.classList.add("done");
403
+ btn.title = "Copiado!";
404
+ setTimeout(() => {
405
+ btn.classList.remove("done");
406
+ btn.title = "Copiar mensagem";
407
+ }, 1200);
408
+ });
284
409
  this.shadowRoot.querySelector(".close").addEventListener("click", () => this.dismiss());
285
410
  };
411
+ /**
412
+ * Mostra "Ver mais" somente quando a mensagem passou de 2 linhas.
413
+ * Sem layout (jsdom) scrollHeight/clientHeight são 0 → fica oculto.
414
+ */
415
+ syncMore_fn = function() {
416
+ const card = this.shadowRoot?.querySelector(".card");
417
+ const more = this.shadowRoot?.querySelector(".more");
418
+ const msg = this.shadowRoot?.querySelector(".msg");
419
+ if (!card || !more || !msg) return;
420
+ if (card.classList.contains("expanded")) {
421
+ more.hidden = false;
422
+ return;
423
+ }
424
+ more.hidden = !(msg.scrollHeight - msg.clientHeight > 1);
425
+ };
286
426
  function regionFor(position) {
287
427
  let region = document.querySelector(`[data-fx-toast-region="${position}"]`);
288
428
  if (!region) {
@@ -1 +1 @@
1
- {"version":3,"mappings":"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","names":[],"ignoreList":[],"sources":["../../../src/components/toast/toast.ts"],"sourcesContent":["/**\n * <fx-toast> — notificações imperativas com posição e duração configuráveis.\n *\n * Atributos: kind (success|error|info|warning), title, message,\n * position (top-left|top-center|top-right|bottom-left|bottom-center|bottom-right),\n * duration (ms; 0 = fixo até fechar), mode ('light'|'dark' — força o esquema de\n * cores do card independentemente do tema global), progress (booleano ou\n * 'always'|'hover'|'paused' — barra de contagem regressiva na cor do kind),\n * progress-origin (left|right — lado pelo qual a barra esvazia; padrão left).\n */\nconst POSITIONS = [\n 'top-left', 'top-center', 'top-right',\n 'bottom-left', 'bottom-center', 'bottom-right',\n] as const;\nexport type ToastPosition = (typeof POSITIONS)[number];\ntype ToastKind = 'success' | 'error' | 'info' | 'warning';\n\n/**\n * Evento interno disparado quando o <fx-toast> sai do DOM.\n * A API `FenixToast` o escuta para remover a entrada do mapa interno —\n * é o que impede o acúmulo de toasts já dispensados.\n */\nconst REMOVED_EVENT = 'fx-toast:removed';\n\nconst KIND_COLOR: Record<ToastKind, string> = {\n success: 'var(--fx-color-success, #10b981)',\n error: 'var(--fx-color-danger, #f43f5e)',\n info: 'var(--fx-color-info, #0ea5e9)',\n warning: 'var(--fx-color-warning, #f59e0b)',\n};\n\n/** Ícone exibido no lugar da bolinha, conforme a variante. */\nconst KIND_ICON: Record<ToastKind, string> = {\n success: '✓',\n error: '✕',\n warning: '!',\n info: 'i',\n};\n\nconst CARD_CSS = `\n:host { display: contents; pointer-events: auto; }\n/* Modo explícito: sobrepõe os tokens herdados do tema global SOMENTE neste card. */\n:host([mode='dark']) {\n --fx-surface-background: #1e293b;\n --fx-text-default: #f1f5f9;\n --fx-text-muted: #94a3b8;\n --fx-border-default: #334155;\n --fx-shadow-lg: 0 10px 30px rgba(0, 0, 0, .5);\n --fx-surface-surface-hover: rgba(255, 255, 255, .08);\n}\n:host([mode='light']) {\n --fx-surface-background: #ffffff;\n --fx-text-default: #1e293b;\n --fx-text-muted: #64748b;\n --fx-border-default: #e2e8f0;\n --fx-shadow-lg: 0 10px 30px rgba(0, 0, 0, .14);\n --fx-surface-surface-hover: rgba(0, 0, 0, .05);\n}\n.fx-icon {\n font-family: var(--fx-icon-font, 'Fenix Icons');\n font-weight: normal;\n font-style: normal;\n line-height: 1;\n letter-spacing: normal;\n display: inline-block;\n white-space: nowrap;\n direction: ltr;\n font-variation-settings: 'FILL' 0, 'wght' 500, 'GRAD' 0, 'opsz' 24;\n -webkit-font-smoothing: antialiased;\n user-select: none;\n}\n.card {\n display: flex; align-items: flex-start; gap: 10px;\n min-width: 260px; max-width: 360px;\n background: var(--fx-surface-background, #fff);\n color: var(--fx-text-default, #1e293b);\n border: 1px solid var(--fx-border-default, #e2e8f0);\n border-left: 4px solid var(--kind);\n border-radius: var(--fx-radius-md, 8px);\n box-shadow: var(--fx-shadow-lg, 0 10px 30px rgba(0,0,0,.14));\n padding: var(--fx-space-sm, 8px) var(--fx-space-md, 12px);\n font-family: inherit;\n animation: fx-toast-in .25s ease;\n}\n@keyframes fx-toast-in { from { opacity: 0; transform: translateY(-6px); } }\n.card.leaving { opacity: 0; transition: opacity .2s ease; }\n.icon {\n width: 22px; height: 22px; border-radius: 50%;\n background: var(--kind);\n color: #fff;\n display: flex; align-items: center; justify-content: center;\n font-size: 12px; font-weight: 700;\n flex: none; margin-top: 2px;\n user-select: none;\n}\n.body { flex: 1; min-width: 0; }\n.title { font-weight: 600; font-size: 14px; }\n.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; }\n.close {\n all: unset; cursor: pointer; flex: none; line-height: 1;\n color: var(--fx-text-muted, #64748b); font-size: 15px; padding: 2px 4px; border-radius: 4px;\n}\n.close:hover { color: var(--fx-color-danger, #f43f5e); background: var(--fx-surface-surface-hover, rgba(0,0,0,.05)); }\n\n/* --- Barra de contagem regressiva (atributo progress) ---\n Fica na base do card e esvazia durante a duração, na cor do kind (--kind) —\n ou seja, acompanha success/error/warning/info sem configuração extra.\n\n O lado é configurável pelo atributo progress-origin (padrão: left):\n left → a barra drena da direita para a esquerda (ancorada à esquerda)\n right → a barra drena da esquerda para a direita (ancorada à direita)\n A origem entra como custom property, então os keyframes são compartilhados.\n\n Usa escalaX em vez de width: a animação escreve só na compositor, sem\n reflow por frame. */\n.card { position: relative; overflow: hidden; }\n.progress {\n position: absolute;\n left: 0;\n bottom: 0;\n width: 100%;\n height: 3px;\n background: var(--kind);\n transform-origin: var(--toast-progress-origin, left center);\n transform: scaleX(1);\n border-radius: 0 0 var(--fx-radius-md, 8px) var(--fx-radius-md, 8px);\n pointer-events: none;\n will-change: transform;\n}\n.progress.run { animation: fx-toast-drain var(--toast-duration, 4000ms) linear forwards; }\n/* No modo hover a animação fica parada até o cursor/foco entrar no card. */\n.card:hover .progress.run,\n.card:focus-within .progress.run { animation-play-state: running; }\n@keyframes fx-toast-drain {\n from { transform: scaleX(1); }\n to { transform: scaleX(0); }\n}\n/* A barra só começa a correr no 2º frame: no 1º ela já apareceria zerada\n (o elemento acabou de ser inserido e a animação aplica o estado final). */\n.card.entering .progress { animation: none; }\n/* Ao dispensar, congela a barra: ela some junto com o card em .2s. */\n.card.leaving .progress { animation-play-state: paused; }\n@media (prefers-reduced-motion: reduce) {\n .progress.run { animation-duration: 0.01ms; }\n}\n`;\n\nexport class FxToast extends HTMLElement {\n static get observedAttributes() {\n return ['kind', 'title', 'message', 'duration', 'icon', 'mode', 'progress', 'progress-origin'];\n }\n\n /**\n * Exibe a barra de contagem regressiva na base do card.\n *\n * A barra esvazia da esquerda para a direita durante exatamente `duration`\n * e usa a cor do `kind`. Com `duration=\"0\"` (toast fixo) a barra é\n * omitida — não há tempo restante a representar.\n *\n * - `'always'` (ou atributo vazio): barra sempre correndo.\n * - `'hover'`: a barra só corre enquanto o cursor estiver sobre o toast;\n * ao sair, a animação pausa, então dá tempo de ler a mensagem.\n * - `'paused'`: barra visível, porém parada.\n */\n get progress(): 'always' | 'hover' | 'paused' | undefined {\n if (!this.hasAttribute('progress')) return undefined;\n const v = this.getAttribute('progress');\n return v === 'hover' || v === 'paused' ? v : 'always';\n }\n set progress(value: 'always' | 'hover' | 'paused' | undefined) {\n if (value) this.setAttribute('progress', value);\n else this.removeAttribute('progress');\n }\n\n /**\n * Lado a partir do qual a barra esvazia.\n *\n * - `'left'` (padrão): a barra fica ancorada à esquerda e drena da direita\n * para a esquerda.\n * - `'right'`: a barra fica ancorada à direita e drena da esquerda para a\n * direita (espelhado).\n *\n * Só tem efeito junto com `progress`; qualquer valor fora da lista cai em\n * `'left'`.\n */\n get progressOrigin(): 'left' | 'right' {\n return this.getAttribute('progress-origin') === 'right' ? 'right' : 'left';\n }\n set progressOrigin(value: 'left' | 'right') {\n this.setAttribute('progress-origin', value);\n }\n\n /** Duração efetiva em ms (0 = fixo). Mesma regra usada no timer. */\n private get safeDuration(): number {\n const dur = Number(this.getAttribute('duration') ?? '4000');\n if (dur === 0) return 0;\n return Math.max(Number.isNaN(dur) ? 4000 : dur, 1000);\n }\n\n /** Esquema de cores forçado do card: 'light' | 'dark' | undefined (segue o tema). */\n get mode(): 'light' | 'dark' | undefined {\n const m = this.getAttribute('mode');\n return m === 'light' || m === 'dark' ? m : undefined;\n }\n set mode(value: 'light' | 'dark' | undefined) {\n if (value) this.setAttribute('mode', value);\n else this.removeAttribute('mode');\n }\n\n private timer: ReturnType<typeof setTimeout> | null = null;\n\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n }\n\n connectedCallback() {\n if (!this.shadowRoot!.firstChild) this.#render();\n // Durações mínimas: valores muito baixos (ex.: digitação parcial no\n // playground) são elevados para 1s; 0 permanece fixo até fechar.\n const safe = this.safeDuration;\n if (safe > 0 && !this.timer) {\n this.timer = setTimeout(() => this.dismiss(), safe);\n }\n this.#syncProgress();\n }\n\n /**\n * Limpa recursos ao sair do DOM e avisa a API imperativa (`FenixToast`).\n *\n * O aviso é essencial: `dismiss()` (timer ou botão de fechar) tira o\n * elemento do DOM mas, sem isto, `ToastApi.map` continuaria segurando uma\n * referência forte para cada toast já dispensado — crescimento de memória\n * sem limite em uso prolongado.\n */\n disconnectedCallback() {\n if (this.timer) clearTimeout(this.timer);\n this.timer = null;\n this.dispatchEvent(new CustomEvent(REMOVED_EVENT, { bubbles: false, composed: false }));\n }\n\n attributeChangedCallback(name: string) {\n // A duração é lida em dois lugares (timer + barra): se mudar com o toast\n // na tela, ambos precisam se realinhar.\n if (name === 'duration' || name === 'progress' || name === 'progress-origin' || name === 'kind') {\n this.#syncProgress();\n }\n }\n\n /**\n * Aplica (ou remove) a barra de contagem regressiva.\n *\n * A barra só existe com `progress` e `duration > 0`. O tempo da animação\n * vem de `--toast-duration`, sempre igual ao do `setTimeout` que fecha o\n * toast — assim a barra e o desaparecimento nunca desalinham.\n */\n #syncProgress(): void {\n const card = this.shadowRoot?.querySelector<HTMLElement>('.card');\n if (!card) return;\n const bar = this.shadowRoot!.querySelector<HTMLElement>('.progress');\n const mode = this.progress;\n const dur = this.safeDuration;\n const shouldShow = !!mode && dur > 0;\n\n if (!shouldShow) {\n bar?.remove();\n card.classList.remove('entering');\n return;\n }\n if (!bar) {\n this.shadowRoot!.querySelector('.card')!.insertAdjacentHTML('beforeend', '<span class=\"progress\" part=\"progress\" aria-hidden=\"true\"></span>');\n }\n const el = this.shadowRoot!.querySelector<HTMLElement>('.progress')!;\n el.style.setProperty('--toast-duration', `${dur}ms`);\n // `left` (padrão) => origem à esquerda; `right` espelha a drenagem.\n el.style.setProperty('--toast-progress-origin', `${this.progressOrigin} center`);\n el.classList.remove('run');\n // `paused` mantém a barra cheia e imóvel; `always` e `hover` deixam a\n // animação correr (no `hover`, o CSS só a dispara sob :hover do host).\n if (mode !== 'paused') el.classList.add('run');\n // 1 frame sem animação para o browser registrar o estado inicial cheio\n // antes de a barra começar a esvaziar.\n card.classList.add('entering');\n requestAnimationFrame(() => requestAnimationFrame(() => card.classList.remove('entering')));\n }\n\n dismiss() {\n if (this.timer) clearTimeout(this.timer);\n this.timer = null;\n const card = this.shadowRoot!.querySelector('.card');\n if (card) {\n card.classList.add('leaving');\n setTimeout(() => this.remove(), 200);\n } else {\n this.remove();\n }\n }\n\n #render() {\n const kind = (this.getAttribute('kind') || 'info') as ToastKind;\n const title = this.getAttribute('title') ?? '';\n const msg = this.getAttribute('message') ?? '';\n const color = KIND_COLOR[kind] ?? KIND_COLOR.info;\n const iconName = this.getAttribute('icon');\n const isFxIcon = !!iconName && /^[a-z][a-z0-9_]*$/.test(iconName);\n if (isFxIcon) void import('../../icons');\n const iconContent = isFxIcon ? iconName : KIND_ICON[kind] ?? KIND_ICON.info;\n this.shadowRoot!.innerHTML = `\n <style>${CARD_CSS}</style>\n <div class=\"card\" style=\"--kind:${color}\" role=\"status\" part=\"card\">\n <span class=\"icon${isFxIcon ? ' fx-icon' : ''}\">${iconContent}</span>\n <div class=\"body\">\n ${title ? '<div class=\"title\"></div>' : ''}\n ${msg ? '<div class=\"msg\"></div>' : ''}\n </div>\n <button class=\"close\" aria-label=\"Fechar\">✕</button>\n </div>`;\n const t = this.shadowRoot!.querySelector<HTMLElement>('.title');\n const m = this.shadowRoot!.querySelector<HTMLElement>('.msg');\n if (t) t.textContent = title;\n if (m) m.textContent = msg;\n this.shadowRoot!.querySelector('.close')!.addEventListener('click', () => this.dismiss());\n }\n}\n\n/* ---------- Regiões por posição na tela ---------- */\nfunction regionFor(position: ToastPosition): HTMLElement {\n let region = document.querySelector<HTMLElement>(`[data-fx-toast-region=\"${position}\"]`);\n if (!region) {\n region = document.createElement('div');\n region.setAttribute('data-fx-toast-region', position);\n const [vertical, horizontal] = position.split('-') as [string, string];\n const style = region.style as CSSStyleDeclaration & Record<string, string>;\n style.position = 'fixed';\n style[vertical] = '16px';\n if (horizontal === 'center') {\n style.left = '50%';\n style.transform = 'translateX(-50%)';\n } else {\n style[horizontal] = '16px';\n }\n style.display = 'flex';\n style.flexDirection = 'column';\n style.gap = '8px';\n style.zIndex = '1100';\n style.pointerEvents = 'none';\n // Os cards reabilitam pointer-events via `:host { pointer-events: auto }` (herança),\n // sem necessidade de MutationObserver.\n document.body.appendChild(region);\n }\n return region;\n}\n\nexport interface ToastOptions {\n /** Texto secundário (também aceito como 2º argumento dos métodos). */\n message?: string;\n position?: ToastPosition;\n /** Tempo em ms até sumir. 0 = fixo até fechar. Padrão: 4000 */\n duration?: number;\n /**\n * Barra de contagem regressiva na base do card, na cor do `kind`.\n * `'always'` (padrão) corre sempre; `'hover'` só enquanto o cursor estiver\n * sobre o toast; `'paused'` exibe a barra cheia, porém imóvel.\n * Sem efeito com `duration: 0` (não há tempo restante a representar).\n */\n progress?: 'always' | 'hover' | 'paused';\n /**\n * Lado a partir do qual a barra esvazia. `'left'` (padrão) deixa a barra\n * ancorada à esquerda; `'right'` a ancora à direita (espelhado).\n * Só tem efeito junto com `progress`.\n */\n progressOrigin?: 'left' | 'right';\n /** Força o esquema de cores do card, independentemente do tema global. */\n mode?: 'light' | 'dark';\n}\n\nclass ToastApi {\n private seq = 0;\n private map = new Map<number, FxToast>();\n\n push(kind: ToastKind, title: string, messageOrOpts?: string | ToastOptions, opts?: ToastOptions): number {\n const id = ++this.seq;\n // Assinaturas suportadas:\n // push(kind, title, options)\n // push(kind, title, message, options) <- usada pela doc e mais intuitiva\n const message = typeof messageOrOpts === 'string' ? messageOrOpts : messageOrOpts?.message;\n const o: ToastOptions = { ...(typeof messageOrOpts === 'object' && messageOrOpts ? messageOrOpts : undefined), ...opts };\n const el = document.createElement('fx-toast') as FxToast;\n el.setAttribute('kind', kind);\n el.setAttribute('title', title);\n if (message) el.setAttribute('message', message);\n el.setAttribute('position', o.position ?? 'top-right');\n el.setAttribute('duration', String(o.duration ?? 4000));\n if (o.mode) el.setAttribute('mode', o.mode);\n if (o.progress) el.setAttribute('progress', o.progress);\n if (o.progressOrigin) el.setAttribute('progress-origin', o.progressOrigin);\n regionFor(o.position ?? 'top-right').appendChild(el);\n // O toast avisa quando sai do DOM (timer esgotado, botão de fechar ou\n // remoção externa). Sem este sync o mapa retém cada toast da sessão.\n el.addEventListener(REMOVED_EVENT, () => {\n if (this.map.get(id) === el) this.map.delete(id);\n });\n this.map.set(id, el);\n return id;\n }\n\n close(id: number) {\n this.map.get(id)?.dismiss();\n this.map.delete(id);\n }\n\n success = (t: string, m?: string, o?: ToastOptions) => this.push('success', t, m, o);\n error = (t: string, m?: string, o?: ToastOptions) => this.push('error', t, m, o);\n info = (t: string, m?: string, o?: ToastOptions) => this.push('info', t, m, o);\n warning = (t: string, m?: string, o?: ToastOptions) => this.push('warning', t, m, o);\n}\n\nexport const FenixToast = new ToastApi();\n// Exposto globalmente para uso em HTML puro (onclick=\"FenixToast.success(...)\")\n(globalThis as unknown as Record<string, unknown>).FenixToast = FenixToast;\n\nexport function defineFxToast() {\n if (!customElements.get('fx-toast')) customElements.define('fx-toast', FxToast);\n}\n\n"],"file":"components/toast/toast.js"}
1
+ {"version":3,"mappings":"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","names":[],"ignoreList":[],"sources":["../../../src/components/toast/toast.ts"],"sourcesContent":["/**\n * <fx-toast> — notificações imperativas com posição e duração configuráveis.\n *\n * Atributos: kind (success|error|info|warning), title, message,\n * position (top-left|top-center|top-right|bottom-left|bottom-center|bottom-right),\n * duration (ms; 0 = fixo até fechar), mode ('light'|'dark' — força o esquema de\n * cores do card independentemente do tema global), progress (booleano ou\n * 'always'|'hover'|'paused' — barra de contagem regressiva na cor do kind),\n * progress-origin (left|right — lado pelo qual a barra esvazia; padrão left).\n */\nconst POSITIONS = [\n 'top-left', 'top-center', 'top-right',\n 'bottom-left', 'bottom-center', 'bottom-right',\n] as const;\nexport type ToastPosition = (typeof POSITIONS)[number];\ntype ToastKind = 'success' | 'error' | 'info' | 'warning';\n\n/**\n * Evento interno disparado quando o <fx-toast> sai do DOM.\n * A API `FenixToast` o escuta para remover a entrada do mapa interno —\n * é o que impede o acúmulo de toasts já dispensados.\n */\nconst REMOVED_EVENT = 'fx-toast:removed';\n\nconst KIND_COLOR: Record<ToastKind, string> = {\n success: 'var(--fx-color-success, #10b981)',\n error: 'var(--fx-color-danger, #f43f5e)',\n info: 'var(--fx-color-info, #0ea5e9)',\n warning: 'var(--fx-color-warning, #f59e0b)',\n};\n\n/** Ícone exibido no lugar da bolinha, conforme a variante. */\nconst KIND_ICON: Record<ToastKind, string> = {\n success: '✓',\n error: '✕',\n warning: '!',\n info: 'i',\n};\n\n/** Ícone de copiar (SVG inline: não depende da fonte de ícones). */\nconst COPY_SVG =\n '<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>';\n\nconst CARD_CSS = `\n:host { display: contents; pointer-events: auto; }\n/* Modo explícito: sobrepõe os tokens herdados do tema global SOMENTE neste card. */\n:host([mode='dark']) {\n --fx-surface-background: #1e293b;\n --fx-text-default: #f1f5f9;\n --fx-text-muted: #94a3b8;\n --fx-border-default: #334155;\n --fx-shadow-lg: 0 10px 30px rgba(0, 0, 0, .5);\n --fx-surface-surface-hover: rgba(255, 255, 255, .08);\n}\n:host([mode='light']) {\n --fx-surface-background: #ffffff;\n --fx-text-default: #1e293b;\n --fx-text-muted: #64748b;\n --fx-border-default: #e2e8f0;\n --fx-shadow-lg: 0 10px 30px rgba(0, 0, 0, .14);\n --fx-surface-surface-hover: rgba(0, 0, 0, .05);\n}\n.fx-icon {\n font-family: var(--fx-icon-font, 'Fenix Icons');\n font-weight: normal;\n font-style: normal;\n line-height: 1;\n letter-spacing: normal;\n display: inline-block;\n white-space: nowrap;\n direction: ltr;\n font-variation-settings: 'FILL' 0, 'wght' 500, 'GRAD' 0, 'opsz' 24;\n -webkit-font-smoothing: antialiased;\n user-select: none;\n}\n.card {\n display: flex; align-items: center; gap: 10px;\n min-width: 260px; max-width: 360px;\n /* Altura mínima idêntica à de um toast COM título: sem título o card não\n fica \"achatado\" e o conjunto mantém o mesmo ritmo visual. */\n min-height: 56px;\n background: var(--fx-surface-background, #fff);\n color: var(--fx-text-default, #1e293b);\n border: 1px solid var(--fx-border-default, #e2e8f0);\n border-left: 4px solid var(--kind);\n border-radius: var(--fx-radius-md, 8px);\n box-shadow: var(--fx-shadow-lg, 0 10px 30px rgba(0,0,0,.14));\n padding: var(--fx-space-sm, 8px) var(--fx-space-md, 12px);\n font-family: inherit;\n animation: fx-toast-in .25s ease;\n}\n@keyframes fx-toast-in { from { opacity: 0; transform: translateY(-6px); } }\n.card.leaving { opacity: 0; transition: opacity .2s ease; }\n.icon {\n width: 22px; height: 22px; border-radius: 50%;\n background: var(--kind);\n color: #fff;\n display: flex; align-items: center; justify-content: center;\n font-size: 12px; font-weight: 700;\n flex: none; align-self: center;\n user-select: none;\n}\n.body { flex: 1; min-width: 0; align-self: center; }\n.title { font-weight: 600; font-size: 14px; }\n.msg {\n font-size: calc(var(--fx-font-size, 14px) - 2px);\n color: var(--fx-text-muted, #64748b);\n margin-top: 2px;\n word-break: break-word; white-space: pre-line;\n /* Mensagens longas mantêm o card no tamanho padrão e terminam em \"…\";\n \"Ver mais\" expande o toast (sem perder o restante do texto). */\n display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;\n line-clamp: 2; overflow: hidden;\n}\n.card.expanded .msg {\n display: block; overflow: visible;\n -webkit-line-clamp: unset; line-clamp: unset;\n}\n.more {\n all: unset; cursor: pointer; display: inline-block;\n margin-top: 4px; padding: 2px 0;\n font-size: 11px; font-weight: 600; color: var(--kind);\n}\n.more[hidden] { display: none; }\n.more:hover { text-decoration: underline; }\n.actions {\n display: flex; align-items: center; gap: 2px;\n flex: none; align-self: flex-start;\n}\n.copy, .close {\n all: unset; cursor: pointer; flex: none; line-height: 1;\n color: var(--fx-text-muted, #64748b); font-size: 15px; padding: 2px 4px; border-radius: 4px;\n}\n.copy[hidden] { display: none; }\n.copy { font-size: 14px; display: inline-flex; align-items: center; }\n.copy svg { display: block; }\n.copy.done { color: var(--fx-color-success, #10b981); }\n.copy:hover { color: var(--fx-color-primary, #6366f1); background: var(--fx-surface-surface-hover, rgba(0,0,0,.05)); }\n.close:hover { color: var(--fx-color-danger, #f43f5e); background: var(--fx-surface-surface-hover, rgba(0,0,0,.05)); }\n\n/* --- Barra de contagem regressiva (atributo progress) ---\n Fica na base do card e esvazia durante a duração, na cor do kind (--kind) —\n ou seja, acompanha success/error/warning/info sem configuração extra.\n\n O lado é configurável pelo atributo progress-origin (padrão: left):\n left → a barra drena da direita para a esquerda (ancorada à esquerda)\n right → a barra drena da esquerda para a direita (ancorada à direita)\n A origem entra como custom property, então os keyframes são compartilhados.\n\n Usa escalaX em vez de width: a animação escreve só na compositor, sem\n reflow por frame. */\n.card { position: relative; overflow: hidden; }\n.progress {\n position: absolute;\n left: 0;\n bottom: 0;\n width: 100%;\n height: 3px;\n background: var(--kind);\n transform-origin: var(--toast-progress-origin, left center);\n transform: scaleX(1);\n border-radius: 0 0 var(--fx-radius-md, 8px) var(--fx-radius-md, 8px);\n pointer-events: none;\n will-change: transform;\n}\n.progress.run { animation: fx-toast-drain var(--toast-duration, 4000ms) linear forwards; }\n/* Passar o mouse (ou focar) no toast pausa a contagem: a barra e o timer\n congelam juntos até o cursor sair — tempo de ler a mensagem inteira.\n progress=\"hover\" é mantido por compatibilidade e se comporta como\n \"always\" (o hover já pausa a contagem). */\n.card:hover .progress.run,\n.card:focus-within .progress.run { animation-play-state: paused; }\n@keyframes fx-toast-drain {\n from { transform: scaleX(1); }\n to { transform: scaleX(0); }\n}\n/* A barra só começa a correr no 2º frame: no 1º ela já apareceria zerada\n (o elemento acabou de ser inserido e a animação aplica o estado final). */\n.card.entering .progress { animation: none; }\n/* Ao dispensar, congela a barra: ela some junto com o card em .2s. */\n.card.leaving .progress { animation-play-state: paused; }\n@media (prefers-reduced-motion: reduce) {\n .progress.run { animation-duration: 0.01ms; }\n}\n`;\n\nexport class FxToast extends HTMLElement {\n static get observedAttributes() {\n return ['kind', 'title', 'message', 'duration', 'icon', 'mode', 'progress', 'progress-origin'];\n }\n\n /**\n * Exibe a barra de contagem regressiva na base do card.\n *\n * A barra esvazia durante exatamente `duration` e usa a cor do `kind`.\n * Com `duration=\"0\"` (toast fixo) a barra é omitida — não há tempo restante.\n *\n * - `'always'` (ou atributo vazio): barra sempre correndo.\n * - `'paused'`: barra visível, porém parada.\n * - `'hover'`: **compatibilidade** — equivale a `'always'`. O hover agora\n * pausa a contagem (timer + barra) em qualquer modo, então a barra \"só\n * correr sob o cursor\" deixou de fazer sentido.\n */\n get progress(): 'always' | 'hover' | 'paused' | undefined {\n if (!this.hasAttribute('progress')) return undefined;\n const v = this.getAttribute('progress');\n return v === 'paused' ? 'paused' : 'always';\n }\n set progress(value: 'always' | 'hover' | 'paused' | undefined) {\n if (value) this.setAttribute('progress', value);\n else this.removeAttribute('progress');\n }\n\n /**\n * Lado a partir do qual a barra esvazia.\n *\n * - `'left'` (padrão): a barra fica ancorada à esquerda e drena da direita\n * para a esquerda.\n * - `'right'`: a barra fica ancorada à direita e drena da esquerda para a\n * direita (espelhado).\n *\n * Só tem efeito junto com `progress`; qualquer valor fora da lista cai em\n * `'left'`.\n */\n get progressOrigin(): 'left' | 'right' {\n return this.getAttribute('progress-origin') === 'right' ? 'right' : 'left';\n }\n set progressOrigin(value: 'left' | 'right') {\n this.setAttribute('progress-origin', value);\n }\n\n /** Duração efetiva em ms (0 = fixo). Mesma regra usada no timer. */\n private get safeDuration(): number {\n const dur = Number(this.getAttribute('duration') ?? '4000');\n if (dur === 0) return 0;\n return Math.max(Number.isNaN(dur) ? 4000 : dur, 1000);\n }\n\n /** Esquema de cores forçado do card: 'light' | 'dark' | undefined (segue o tema). */\n get mode(): 'light' | 'dark' | undefined {\n const m = this.getAttribute('mode');\n return m === 'light' || m === 'dark' ? m : undefined;\n }\n set mode(value: 'light' | 'dark' | undefined) {\n if (value) this.setAttribute('mode', value);\n else this.removeAttribute('mode');\n }\n\n private timer: ReturnType<typeof setTimeout> | null = null;\n /** Tempo restante (ms) quando a contagem está pausada no hover/foco. */\n private remaining = 0;\n /** Instante em que o timer atual começou (para descontar o tempo decorrido). */\n private startedAt = 0;\n /** Cursor sobre o toast: não retoma a contagem no focusout. */\n private hovering = false;\n /** Já saiu do DOM — impede retomar a contagem. */\n private disposed = false;\n /** Existe contagem regressiva (duration > 0) — toasts fixos nunca retomam. */\n private counting = false;\n /** Listeners de pausa/retomada registrados uma única vez. */\n private wired = false;\n\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n }\n\n connectedCallback() {\n if (!this.shadowRoot!.firstChild) this.#render();\n this.disposed = false;\n this.#wirePause();\n // Durações mínimas: valores muito baixos (ex.: digitação parcial no\n // playground) são elevados para 1s; 0 permanece fixo até fechar.\n const safe = this.safeDuration;\n this.counting = safe > 0;\n if (safe > 0 && !this.timer) this.#startTimer(safe);\n this.#syncProgress();\n // jsdom não calcula layout: o \"Ver mais\" fica oculto (scrollHeight 0).\n this.#syncMore();\n }\n\n /**\n * Pausa a contagem enquanto o cursor (ou o foco) estiver no toast e retoma\n * de onde parou ao sair — assim uma mensagem longa pode ser lida inteira.\n * A barra de progresso pausa junto (regra CSS de :hover/:focus-within).\n */\n #wirePause(): void {\n if (this.wired) return;\n this.wired = true;\n this.addEventListener('mouseenter', () => {\n this.hovering = true;\n this.#pauseTimer();\n });\n this.addEventListener('mouseleave', () => {\n this.hovering = false;\n this.#resumeTimer();\n });\n // Teclado (botões copiar/fechar/ver mais) também mantém o toast na tela.\n this.addEventListener('focusin', () => this.#pauseTimer());\n this.addEventListener('focusout', () => {\n if (!this.hovering) this.#resumeTimer();\n });\n }\n\n #startTimer(ms: number): void {\n this.remaining = ms;\n this.startedAt = Date.now();\n this.timer = setTimeout(() => {\n this.timer = null;\n this.dismiss();\n }, ms);\n }\n\n #pauseTimer(): void {\n if (!this.timer) return;\n clearTimeout(this.timer);\n this.timer = null;\n this.remaining = Math.max(0, this.remaining - (Date.now() - this.startedAt));\n }\n\n #resumeTimer(): void {\n if (!this.counting || this.timer || this.disposed) return;\n if (this.remaining <= 0) {\n this.dismiss();\n return;\n }\n this.#startTimer(this.remaining);\n }\n\n /**\n * Limpa recursos ao sair do DOM e avisa a API imperativa (`FenixToast`).\n *\n * O aviso é essencial: `dismiss()` (timer ou botão de fechar) tira o\n * elemento do DOM mas, sem isto, `ToastApi.map` continuaria segurando uma\n * referência forte para cada toast já dispensado — crescimento de memória\n * sem limite em uso prolongado.\n */\n disconnectedCallback() {\n this.disposed = true;\n if (this.timer) clearTimeout(this.timer);\n this.timer = null;\n this.dispatchEvent(new CustomEvent(REMOVED_EVENT, { bubbles: false, composed: false }));\n }\n\n attributeChangedCallback(name: string) {\n // A duração é lida em dois lugares (timer + barra): se mudar com o toast\n // na tela, ambos precisam se realinhar.\n if (name === 'duration') {\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n const safe = this.safeDuration;\n this.counting = safe > 0;\n if (safe > 0 && this.isConnected && !this.disposed) this.#startTimer(safe);\n }\n if (name === 'duration' || name === 'progress' || name === 'progress-origin' || name === 'kind') {\n this.#syncProgress();\n }\n }\n\n /**\n * Aplica (ou remove) a barra de contagem regressiva.\n *\n * A barra só existe com `progress` e `duration > 0`. O tempo da animação\n * vem de `--toast-duration`, sempre igual ao do `setTimeout` que fecha o\n * toast — assim a barra e o desaparecimento nunca desalinham.\n */\n #syncProgress(): void {\n const card = this.shadowRoot?.querySelector<HTMLElement>('.card');\n if (!card) return;\n const bar = this.shadowRoot!.querySelector<HTMLElement>('.progress');\n const mode = this.progress;\n const dur = this.safeDuration;\n const shouldShow = !!mode && dur > 0;\n\n if (!shouldShow) {\n bar?.remove();\n card.classList.remove('entering');\n return;\n }\n if (!bar) {\n this.shadowRoot!.querySelector('.card')!.insertAdjacentHTML('beforeend', '<span class=\"progress\" part=\"progress\" aria-hidden=\"true\"></span>');\n }\n const el = this.shadowRoot!.querySelector<HTMLElement>('.progress')!;\n el.style.setProperty('--toast-duration', `${dur}ms`);\n // `left` (padrão) => origem à esquerda; `right` espelha a drenagem.\n el.style.setProperty('--toast-progress-origin', `${this.progressOrigin} center`);\n el.classList.remove('run');\n // `paused` mantém a barra cheia e imóvel; os demais modos animam\n // (o hover pausa a animação junto com o timer, via CSS).\n if (mode !== 'paused') el.classList.add('run');\n // 1 frame sem animação para o browser registrar o estado inicial cheio\n // antes de a barra começar a esvaziar.\n card.classList.add('entering');\n requestAnimationFrame(() => requestAnimationFrame(() => card.classList.remove('entering')));\n }\n\n dismiss() {\n if (this.timer) clearTimeout(this.timer);\n this.timer = null;\n const card = this.shadowRoot!.querySelector('.card');\n if (card) {\n card.classList.add('leaving');\n setTimeout(() => this.remove(), 200);\n } else {\n this.remove();\n }\n }\n\n #render() {\n const kind = (this.getAttribute('kind') || 'info') as ToastKind;\n const title = this.getAttribute('title') ?? '';\n const msg = this.getAttribute('message') ?? '';\n const color = KIND_COLOR[kind] ?? KIND_COLOR.info;\n const iconName = this.getAttribute('icon');\n const isFxIcon = !!iconName && /^[a-z][a-z0-9_]*$/.test(iconName);\n if (isFxIcon) void import('../../icons');\n const iconContent = isFxIcon ? iconName : KIND_ICON[kind] ?? KIND_ICON.info;\n this.shadowRoot!.innerHTML = `\n <style>${CARD_CSS}</style>\n <div class=\"card\" style=\"--kind:${color}\" role=\"status\" part=\"card\">\n <span class=\"icon${isFxIcon ? ' fx-icon' : ''}\">${iconContent}</span>\n <div class=\"body\">\n ${title ? '<div class=\"title\" part=\"title\"></div>' : ''}\n ${msg ? '<div class=\"msg\" part=\"message\"></div>' : ''}\n ${msg ? '<button class=\"more\" part=\"more\" hidden>Ver mais</button>' : ''}\n </div>\n <div class=\"actions\">\n ${msg ? `<button class=\"copy\" part=\"copy\" hidden aria-label=\"Copiar mensagem\" title=\"Copiar mensagem\">${COPY_SVG}</button>` : ''}\n <button class=\"close\" aria-label=\"Fechar\" title=\"Fechar\">✕</button>\n </div>\n </div>`;\n const t = this.shadowRoot!.querySelector<HTMLElement>('.title');\n const m = this.shadowRoot!.querySelector<HTMLElement>('.msg');\n if (t) t.textContent = title;\n if (m) {\n m.textContent = msg;\n // Dica nativa com o texto completo: passar o mouse na mensagem mostra\n // tudo, mesmo quando o card está truncado em 2 linhas.\n m.title = msg;\n }\n const card = this.shadowRoot!.querySelector<HTMLElement>('.card')!;\n const more = this.shadowRoot!.querySelector<HTMLButtonElement>('.more');\n const copy = this.shadowRoot!.querySelector<HTMLButtonElement>('.copy');\n more?.addEventListener('click', () => {\n const expanded = card.classList.toggle('expanded');\n more.textContent = expanded ? 'Ver menos' : 'Ver mais';\n // O botão de copiar só aparece com o texto aberto (não polui o card no\n // tamanho padrão). Enquanto o texto está expandido o toast não fecha.\n if (copy) copy.hidden = !expanded;\n if (expanded) this.#pauseTimer();\n });\n copy?.addEventListener('click', (e) => {\n const btn = e.currentTarget as HTMLButtonElement;\n const text = [title, msg].filter(Boolean).join('\\n');\n navigator.clipboard?.writeText(text).catch(() => { /* clipboard indisponível */ });\n // Feedback: check verde + dica \"Copiado!\" por 1,2s.\n btn.classList.add('done');\n btn.title = 'Copiado!';\n setTimeout(() => {\n btn.classList.remove('done');\n btn.title = 'Copiar mensagem';\n }, 1200);\n });\n this.shadowRoot!.querySelector('.close')!.addEventListener('click', () => this.dismiss());\n }\n\n /**\n * Mostra \"Ver mais\" somente quando a mensagem passou de 2 linhas.\n * Sem layout (jsdom) scrollHeight/clientHeight são 0 → fica oculto.\n */\n #syncMore(): void {\n const card = this.shadowRoot?.querySelector<HTMLElement>('.card');\n const more = this.shadowRoot?.querySelector<HTMLButtonElement>('.more');\n const msg = this.shadowRoot?.querySelector<HTMLElement>('.msg');\n if (!card || !more || !msg) return;\n if (card.classList.contains('expanded')) {\n more.hidden = false;\n return;\n }\n more.hidden = !(msg.scrollHeight - msg.clientHeight > 1);\n }\n}\n\n/* ---------- Regiões por posição na tela ---------- */\nfunction regionFor(position: ToastPosition): HTMLElement {\n let region = document.querySelector<HTMLElement>(`[data-fx-toast-region=\"${position}\"]`);\n if (!region) {\n region = document.createElement('div');\n region.setAttribute('data-fx-toast-region', position);\n const [vertical, horizontal] = position.split('-') as [string, string];\n const style = region.style as CSSStyleDeclaration & Record<string, string>;\n style.position = 'fixed';\n style[vertical] = '16px';\n if (horizontal === 'center') {\n style.left = '50%';\n style.transform = 'translateX(-50%)';\n } else {\n style[horizontal] = '16px';\n }\n style.display = 'flex';\n style.flexDirection = 'column';\n style.gap = '8px';\n style.zIndex = '1100';\n style.pointerEvents = 'none';\n // Os cards reabilitam pointer-events via `:host { pointer-events: auto }` (herança),\n // sem necessidade de MutationObserver.\n document.body.appendChild(region);\n }\n return region;\n}\n\nexport interface ToastOptions {\n /** Texto secundário (também aceito como 2º argumento dos métodos). */\n message?: string;\n position?: ToastPosition;\n /** Tempo em ms até sumir. 0 = fixo até fechar. Padrão: 4000 */\n duration?: number;\n /**\n * Barra de contagem regressiva na base do card, na cor do `kind`.\n * `'always'` (padrão) corre sempre; `'hover'` só enquanto o cursor estiver\n * sobre o toast; `'paused'` exibe a barra cheia, porém imóvel.\n * Sem efeito com `duration: 0` (não há tempo restante a representar).\n */\n progress?: 'always' | 'hover' | 'paused';\n /**\n * Lado a partir do qual a barra esvazia. `'left'` (padrão) deixa a barra\n * ancorada à esquerda; `'right'` a ancora à direita (espelhado).\n * Só tem efeito junto com `progress`.\n */\n progressOrigin?: 'left' | 'right';\n /** Força o esquema de cores do card, independentemente do tema global. */\n mode?: 'light' | 'dark';\n}\n\nclass ToastApi {\n private seq = 0;\n private map = new Map<number, FxToast>();\n\n push(kind: ToastKind, title: string, messageOrOpts?: string | ToastOptions, opts?: ToastOptions): number {\n const id = ++this.seq;\n // Assinaturas suportadas:\n // push(kind, title, options)\n // push(kind, title, message, options) <- usada pela doc e mais intuitiva\n const message = typeof messageOrOpts === 'string' ? messageOrOpts : messageOrOpts?.message;\n const o: ToastOptions = { ...(typeof messageOrOpts === 'object' && messageOrOpts ? messageOrOpts : undefined), ...opts };\n const el = document.createElement('fx-toast') as FxToast;\n el.setAttribute('kind', kind);\n el.setAttribute('title', title);\n if (message) el.setAttribute('message', message);\n el.setAttribute('position', o.position ?? 'top-right');\n el.setAttribute('duration', String(o.duration ?? 4000));\n if (o.mode) el.setAttribute('mode', o.mode);\n if (o.progress) el.setAttribute('progress', o.progress);\n if (o.progressOrigin) el.setAttribute('progress-origin', o.progressOrigin);\n regionFor(o.position ?? 'top-right').appendChild(el);\n // O toast avisa quando sai do DOM (timer esgotado, botão de fechar ou\n // remoção externa). Sem este sync o mapa retém cada toast da sessão.\n el.addEventListener(REMOVED_EVENT, () => {\n if (this.map.get(id) === el) this.map.delete(id);\n });\n this.map.set(id, el);\n return id;\n }\n\n close(id: number) {\n this.map.get(id)?.dismiss();\n this.map.delete(id);\n }\n\n success = (t: string, m?: string, o?: ToastOptions) => this.push('success', t, m, o);\n error = (t: string, m?: string, o?: ToastOptions) => this.push('error', t, m, o);\n info = (t: string, m?: string, o?: ToastOptions) => this.push('info', t, m, o);\n warning = (t: string, m?: string, o?: ToastOptions) => this.push('warning', t, m, o);\n}\n\nexport const FenixToast = new ToastApi();\n// Exposto globalmente para uso em HTML puro (onclick=\"FenixToast.success(...)\")\n(globalThis as unknown as Record<string, unknown>).FenixToast = FenixToast;\n\nexport function defineFxToast() {\n if (!customElements.get('fx-toast')) customElements.define('fx-toast', FxToast);\n}\n\n"],"file":"components/toast/toast.js"}
@@ -1,19 +1,27 @@
1
- import { FxElement } from '../../core/base';
1
+ import { FxFormElement } from '../../core/form-element';
2
2
  /**
3
3
  * <fx-toggle-button-group> — Grupo de botones de selección múltiple o única.
4
4
  *
5
- * Atributos: multiple (permite varios seleccionados), value (lista), size.
6
- * Slots `option`: cada botón con [slot="option" value="..."].
7
- * Evento (composed): `change` (detail: { value: string[] }).
5
+ * Form-Associated Custom Element: dentro de un <form> entra en el FormData
6
+ * con UNA entrada por valor seleccionado (via atributo `name`).
7
+ *
8
+ * Atributos: multiple (permite varios seleccionados), value (lista), name,
9
+ * size. Slots `option`: cada botón con [slot="option" value="..."].
10
+ * Eventos (composed): `change` (detail: { value: string[] }) e `valuesChange`
11
+ * (detail: { values: string[] }, two-way binding do Angular [(value)]).
8
12
  */
9
- export declare class FxToggleButtonGroup extends FxElement {
13
+ export declare class FxToggleButtonGroup extends FxFormElement {
10
14
  static styles: string;
11
15
  static get observedAttributes(): string[];
12
16
  get value(): string[];
13
17
  set value(v: string[]);
14
18
  get multiple(): boolean;
19
+ /** No FormData do form: uma entrada `name` por valor selecionado. */
20
+ protected getFormValue(): string | FormData | null;
15
21
  get options(): HTMLElement[];
16
22
  protected render(): void;
23
+ /** form.reset(): restaura a seleção inicial e re-renderiza os botões. */
24
+ formResetCallback(): void;
17
25
  private _bindOptions;
18
26
  private _toggle;
19
27
  }
@@ -1 +1 @@
1
- {"version":3,"file":"toggle-button-group.d.ts","sourceRoot":"","sources":["../../../src/components/toggle-button-group/toggle-button-group.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAK5C;;;;;;GAMG;AACH,qBAAa,mBAAoB,SAAQ,SAAS;IAChD,OAAgB,MAAM,SA6BpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,IAAI,KAAK,IAAI,MAAM,EAAE,CAGpB;IACD,IAAI,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,EAA8C;IAEnE,IAAI,QAAQ,IAAI,OAAO,CAAqC;IAE5D,IAAI,OAAO,IAAI,WAAW,EAAE,CAE3B;cAEkB,MAAM,IAAI,IAAI;IAqBjC,OAAO,CAAC,YAAY;IAapB,OAAO,CAAC,OAAO;CAmBhB;AAED,wBAAgB,yBAAyB,IAAI,OAAO,mBAAmB,CAEtE"}
1
+ {"version":3,"file":"toggle-button-group.d.ts","sourceRoot":"","sources":["../../../src/components/toggle-button-group/toggle-button-group.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAKxD;;;;;;;;;;GAUG;AACH,qBAAa,mBAAoB,SAAQ,aAAa;IACpD,OAAgB,MAAM,SA6BpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,IAAI,KAAK,IAAI,MAAM,EAAE,CAGpB;IACD,IAAI,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,EAIpB;IAED,IAAI,QAAQ,IAAI,OAAO,CAAqC;IAE5D,qEAAqE;cAClD,YAAY,IAAI,MAAM,GAAG,QAAQ,GAAG,IAAI;IAQ3D,IAAI,OAAO,IAAI,WAAW,EAAE,CAE3B;cAEkB,MAAM,IAAI,IAAI;IAwBjC,yEAAyE;IAChE,iBAAiB,IAAI,IAAI;IAMlC,OAAO,CAAC,YAAY;IAapB,OAAO,CAAC,OAAO;CAqBhB;AAED,wBAAgB,yBAAyB,IAAI,OAAO,mBAAmB,CAEtE"}
@@ -1,8 +1,8 @@
1
- import { FxElement } from "../../core/base.js";
1
+ import { FxFormElement } from "../../core/form-element.js";
2
2
  import { css } from "../../core/css.js";
3
3
  import { defineElement } from "../../core/define.js";
4
4
  import { esc } from "../../core/sanitize.js";
5
- const _FxToggleButtonGroup = class _FxToggleButtonGroup extends FxElement {
5
+ const _FxToggleButtonGroup = class _FxToggleButtonGroup extends FxFormElement {
6
6
  static get observedAttributes() {
7
7
  return ["multiple", "size"];
8
8
  }
@@ -12,10 +12,19 @@ const _FxToggleButtonGroup = class _FxToggleButtonGroup extends FxElement {
12
12
  }
13
13
  set value(v) {
14
14
  this.setAttribute("value", v.join(","));
15
+ this.updateFormValue();
15
16
  }
16
17
  get multiple() {
17
18
  return this.hasAttr("multiple");
18
19
  }
20
+ /** No FormData do form: uma entrada `name` por valor selecionado. */
21
+ getFormValue() {
22
+ const values = this.value;
23
+ if (!this.name || values.length === 0) return null;
24
+ const fd = new FormData();
25
+ values.forEach((v) => fd.append(this.name, v));
26
+ return fd;
27
+ }
19
28
  get options() {
20
29
  return Array.from(this.children).filter((el) => el.matches('[slot="option"]')).map((el) => el);
21
30
  }
@@ -33,6 +42,13 @@ const _FxToggleButtonGroup = class _FxToggleButtonGroup extends FxElement {
33
42
  </div>
34
43
  `);
35
44
  this._bindOptions();
45
+ this.updateFormValue();
46
+ }
47
+ /** form.reset(): restaura a seleção inicial e re-renderiza os botões. */
48
+ formResetCallback() {
49
+ this.setAttribute("value", this._initialValue);
50
+ this.render();
51
+ this.updateFormValue();
36
52
  }
37
53
  _bindOptions() {
38
54
  Array.from(this.root.querySelectorAll(".option") || []).forEach((opt) => {
@@ -62,6 +78,7 @@ const _FxToggleButtonGroup = class _FxToggleButtonGroup extends FxElement {
62
78
  o.setAttribute("aria-pressed", String(on));
63
79
  });
64
80
  this.dispatchEvent(new CustomEvent("change", { bubbles: true, composed: true, detail: { value: current } }));
81
+ this.dispatchEvent(new CustomEvent("valuesChange", { bubbles: true, composed: true, detail: { values: [...current] } }));
65
82
  }
66
83
  };
67
84
  _FxToggleButtonGroup.styles = css`
@@ -1 +1 @@
1
- {"version":3,"file":"toggle-button-group.js","sources":["../../../src/components/toggle-button-group/toggle-button-group.ts"],"sourcesContent":["import { FxElement } from '../../core/base';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\nimport { esc } from '../../core/sanitize';\n\n/**\n * <fx-toggle-button-group> — Grupo de botones de selección múltiple o única.\n *\n * Atributos: multiple (permite varios seleccionados), value (lista), size.\n * Slots `option`: cada botón con [slot=\"option\" value=\"...\"].\n * Evento (composed): `change` (detail: { value: string[] }).\n */\nexport class FxToggleButtonGroup extends FxElement {\n static override styles = css`\n :host { display: inline-flex; font-family: var(--fx-font-family); font-size: var(--fx-font-size); gap: var(--fx-space-xs); }\n .option {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--fx-space-xs);\n min-height: var(--fx-size-md);\n padding: var(--fx-space-sm) var(--fx-space-lg);\n font-weight: var(--fx-font-weight);\n /* Altura determinística: a caixa fica EXATAMENTE no token --fx-size-*. */\n line-height: var(--fx-font-line-height);\n color: var(--fx-text-default);\n background: var(--fx-surface-background);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-md);\n cursor: pointer;\n user-select: none;\n transition: background var(--fx-motion-duration-fast) var(--fx-motion-easing), border-color var(--fx-motion-duration-fast) var(--fx-motion-easing), color var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n :host([size='sm']) .option { min-height: var(--fx-size-sm); padding: var(--fx-space-xs) var(--fx-space-md); font-size: calc(var(--fx-font-size) - 2px); }\n :host([size='lg']) .option { min-height: var(--fx-size-lg); padding: var(--fx-space-sm) var(--fx-space-xl); font-size: calc(var(--fx-font-size) + 2px); }\n .option:hover { border-color: var(--fx-color-primary); }\n .option.active {\n background: var(--fx-color-primary);\n border-color: var(--fx-color-primary);\n color: #fff;\n }\n .option.active:hover { filter: brightness(0.92); }\n `;\n\n static override get observedAttributes(): string[] {\n return ['multiple', 'size'];\n }\n\n get value(): string[] {\n const raw = this.getAttr('value');\n return raw ? raw.split(',').map((s) => s.trim()).filter(Boolean) : [];\n }\n set value(v: string[]) { this.setAttribute('value', v.join(',')); }\n\n get multiple(): boolean { return this.hasAttr('multiple'); }\n\n get options(): HTMLElement[] {\n return Array.from(this.children).filter((el) => el.matches('[slot=\"option\"]')).map((el) => el as HTMLElement);\n }\n\n protected override render(): void {\n const opts = this.options;\n const selected = this.value;\n\n const html = opts\n .map((o, i) => {\n const val = o.getAttribute('value') ?? '';\n const active = selected.includes(val);\n return `<button type=\"button\" class=\"option ${active ? 'active' : ''}\" part=\"option\" data-v=\"${esc(val)}\" aria-pressed=\"${active}\">${esc(o.textContent?.trim() || `Opción ${i + 1}`)}</button>`;\n })\n .join('');\n\n this.setTemplate(`\n <div class=\"options\" part=\"options\" role=\"group\" aria-label=\"Toggle buttons\">\n ${html}\n </div>\n `);\n\n this._bindOptions();\n }\n\n private _bindOptions(): void {\n Array.from(this.root.querySelectorAll<HTMLElement>('.option') || []).forEach((opt) => {\n opt.addEventListener('click', () => this._toggle(opt));\n opt.addEventListener('keydown', (e) => {\n const ev = e as KeyboardEvent;\n if (ev.key === 'Enter' || ev.key === ' ') {\n ev.preventDefault();\n this._toggle(opt);\n }\n });\n });\n }\n\n private _toggle(opt: HTMLElement): void {\n const val = opt.dataset.v ?? '';\n let current = this.value;\n\n if (this.multiple) {\n if (current.includes(val)) current = current.filter((v) => v !== val);\n else current.push(val);\n } else {\n current = current.includes(val) ? [] : [val];\n }\n\n this.value = current;\n this.root.querySelectorAll<HTMLElement>('.option').forEach((o) => {\n const on = current.includes(o.dataset.v ?? '');\n o.classList.toggle('active', on);\n o.setAttribute('aria-pressed', String(on));\n });\n this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true, detail: { value: current } }));\n }\n}\n\nexport function defineFxToggleButtonGroup(): typeof FxToggleButtonGroup {\n return defineElement('fx-toggle-button-group', FxToggleButtonGroup);\n}"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"toggle-button-group.js","sources":["../../../src/components/toggle-button-group/toggle-button-group.ts"],"sourcesContent":["import { FxFormElement } from '../../core/form-element';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\nimport { esc } from '../../core/sanitize';\n\n/**\n * <fx-toggle-button-group> — Grupo de botones de selección múltiple o única.\n *\n * Form-Associated Custom Element: dentro de un <form> entra en el FormData\n * con UNA entrada por valor seleccionado (via atributo `name`).\n *\n * Atributos: multiple (permite varios seleccionados), value (lista), name,\n * size. Slots `option`: cada botón con [slot=\"option\" value=\"...\"].\n * Eventos (composed): `change` (detail: { value: string[] }) e `valuesChange`\n * (detail: { values: string[] }, two-way binding do Angular [(value)]).\n */\nexport class FxToggleButtonGroup extends FxFormElement {\n static override styles = css`\n :host { display: inline-flex; font-family: var(--fx-font-family); font-size: var(--fx-font-size); gap: var(--fx-space-xs); }\n .option {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--fx-space-xs);\n min-height: var(--fx-size-md);\n padding: var(--fx-space-sm) var(--fx-space-lg);\n font-weight: var(--fx-font-weight);\n /* Altura determinística: a caixa fica EXATAMENTE no token --fx-size-*. */\n line-height: var(--fx-font-line-height);\n color: var(--fx-text-default);\n background: var(--fx-surface-background);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-md);\n cursor: pointer;\n user-select: none;\n transition: background var(--fx-motion-duration-fast) var(--fx-motion-easing), border-color var(--fx-motion-duration-fast) var(--fx-motion-easing), color var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n :host([size='sm']) .option { min-height: var(--fx-size-sm); padding: var(--fx-space-xs) var(--fx-space-md); font-size: calc(var(--fx-font-size) - 2px); }\n :host([size='lg']) .option { min-height: var(--fx-size-lg); padding: var(--fx-space-sm) var(--fx-space-xl); font-size: calc(var(--fx-font-size) + 2px); }\n .option:hover { border-color: var(--fx-color-primary); }\n .option.active {\n background: var(--fx-color-primary);\n border-color: var(--fx-color-primary);\n color: #fff;\n }\n .option.active:hover { filter: brightness(0.92); }\n `;\n\n static override get observedAttributes(): string[] {\n return ['multiple', 'size'];\n }\n\n get value(): string[] {\n const raw = this.getAttr('value');\n return raw ? raw.split(',').map((s) => s.trim()).filter(Boolean) : [];\n }\n set value(v: string[]) {\n this.setAttribute('value', v.join(','));\n // `value` não é observado: publica no form aqui.\n this.updateFormValue();\n }\n\n get multiple(): boolean { return this.hasAttr('multiple'); }\n\n /** No FormData do form: uma entrada `name` por valor selecionado. */\n protected override getFormValue(): string | FormData | null {\n const values = this.value;\n if (!this.name || values.length === 0) return null;\n const fd = new FormData();\n values.forEach((v) => fd.append(this.name, v));\n return fd;\n }\n\n get options(): HTMLElement[] {\n return Array.from(this.children).filter((el) => el.matches('[slot=\"option\"]')).map((el) => el as HTMLElement);\n }\n\n protected override render(): void {\n const opts = this.options;\n const selected = this.value;\n\n const html = opts\n .map((o, i) => {\n const val = o.getAttribute('value') ?? '';\n const active = selected.includes(val);\n return `<button type=\"button\" class=\"option ${active ? 'active' : ''}\" part=\"option\" data-v=\"${esc(val)}\" aria-pressed=\"${active}\">${esc(o.textContent?.trim() || `Opción ${i + 1}`)}</button>`;\n })\n .join('');\n\n this.setTemplate(`\n <div class=\"options\" part=\"options\" role=\"group\" aria-label=\"Toggle buttons\">\n ${html}\n </div>\n `);\n\n this._bindOptions();\n\n // Publica name/value no form (ElementInternals) a cada render.\n this.updateFormValue();\n }\n\n /** form.reset(): restaura a seleção inicial e re-renderiza os botões. */\n override formResetCallback(): void {\n this.setAttribute('value', this._initialValue);\n this.render();\n this.updateFormValue();\n }\n\n private _bindOptions(): void {\n Array.from(this.root.querySelectorAll<HTMLElement>('.option') || []).forEach((opt) => {\n opt.addEventListener('click', () => this._toggle(opt));\n opt.addEventListener('keydown', (e) => {\n const ev = e as KeyboardEvent;\n if (ev.key === 'Enter' || ev.key === ' ') {\n ev.preventDefault();\n this._toggle(opt);\n }\n });\n });\n }\n\n private _toggle(opt: HTMLElement): void {\n const val = opt.dataset.v ?? '';\n let current = this.value;\n\n if (this.multiple) {\n if (current.includes(val)) current = current.filter((v) => v !== val);\n else current.push(val);\n } else {\n current = current.includes(val) ? [] : [val];\n }\n\n this.value = current;\n this.root.querySelectorAll<HTMLElement>('.option').forEach((o) => {\n const on = current.includes(o.dataset.v ?? '');\n o.classList.toggle('active', on);\n o.setAttribute('aria-pressed', String(on));\n });\n this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true, detail: { value: current } }));\n // Two-way binding do Angular: [(value)] em seleção múltipla → valuesChange.\n this.dispatchEvent(new CustomEvent('valuesChange', { bubbles: true, composed: true, detail: { values: [...current] } }));\n }\n}\n\nexport function defineFxToggleButtonGroup(): typeof FxToggleButtonGroup {\n return defineElement('fx-toggle-button-group', FxToggleButtonGroup);\n}"],"names":[],"mappings":"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"}
@@ -10,6 +10,13 @@ export declare abstract class FxElement extends HTMLElement {
10
10
  /** CSS isolado no Shadow DOM. Deve usar apenas `var(--fx-*)`. */
11
11
  static styles: string;
12
12
  static get observedAttributes(): string[];
13
+ /**
14
+ * Opções do `attachShadow` — sobrescreva por componente quando precisar.
15
+ * `delegatesFocus` (usado pelos componentes de formulário, via FxFormElement)
16
+ * faz `focus()`, `autofocus` e o clique em `<label for>` atingirem o
17
+ * controle interno do Shadow DOM, e não só o host.
18
+ */
19
+ static shadowOptions: ShadowRootInit;
13
20
  protected readonly root: ShadowRoot;
14
21
  constructor();
15
22
  /** Chamado quando um atributo observado muda. */
@@ -1 +1 @@
1
- {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/core/base.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,8BAAsB,SAAU,SAAQ,WAAW;IACjD,iEAAiE;IACjE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM;IAE3B,MAAM,KAAK,kBAAkB,IAAI,MAAM,EAAE,CAExC;IAED,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;;IAOpC,iDAAiD;IACjD,SAAS,CAAC,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAI3G,kDAAkD;IAClD,SAAS,CAAC,iBAAiB,IAAI,IAAI;IAInC,yEAAyE;IACzE,SAAS,CAAC,oBAAoB,IAAI,IAAI;IAItC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIxC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,SAAK,GAAG,MAAM;IAItD,SAAS,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,GAAG,IAAI;IAKrD,iDAAiD;IACjD,SAAS,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKzC,wCAAwC;IACxC,SAAS,CAAC,QAAQ,CAAC,MAAM,IAAI,IAAI;CAClC"}
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/core/base.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,8BAAsB,SAAU,SAAQ,WAAW;IACjD,iEAAiE;IACjE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM;IAE3B,MAAM,KAAK,kBAAkB,IAAI,MAAM,EAAE,CAExC;IAED;;;;;OAKG;IACH,MAAM,CAAC,aAAa,EAAE,cAAc,CAAoB;IAExD,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;;IASpC,iDAAiD;IACjD,SAAS,CAAC,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAI3G,kDAAkD;IAClD,SAAS,CAAC,iBAAiB,IAAI,IAAI;IAInC,yEAAyE;IACzE,SAAS,CAAC,oBAAoB,IAAI,IAAI;IAItC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIxC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,SAAK,GAAG,MAAM;IAItD,SAAS,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,GAAG,IAAI;IAKrD,iDAAiD;IACjD,SAAS,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKzC,wCAAwC;IACxC,SAAS,CAAC,QAAQ,CAAC,MAAM,IAAI,IAAI;CAClC"}