@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.
- package/README.md +3 -3
- package/dist/components/autocomplete/autocomplete.d.ts +11 -5
- package/dist/components/autocomplete/autocomplete.d.ts.map +1 -1
- package/dist/components/autocomplete/autocomplete.js +18 -2
- package/dist/components/autocomplete/autocomplete.js.map +1 -1
- package/dist/components/button/button.d.ts +15 -0
- package/dist/components/button/button.d.ts.map +1 -1
- package/dist/components/button/button.js +20 -1
- package/dist/components/button/button.js.map +1 -1
- package/dist/components/checkbox/checkbox.d.ts +20 -4
- package/dist/components/checkbox/checkbox.d.ts.map +1 -1
- package/dist/components/checkbox/checkbox.js +39 -2
- package/dist/components/checkbox/checkbox.js.map +1 -1
- package/dist/components/datepicker/datepicker.d.ts +16 -2
- package/dist/components/datepicker/datepicker.d.ts.map +1 -1
- package/dist/components/datepicker/datepicker.js +46 -2
- package/dist/components/datepicker/datepicker.js.map +1 -1
- package/dist/components/fileupload/fileupload.d.ts +10 -2
- package/dist/components/fileupload/fileupload.d.ts.map +1 -1
- package/dist/components/fileupload/fileupload.js +17 -2
- package/dist/components/fileupload/fileupload.js.map +1 -1
- package/dist/components/floatlabel/floatlabel.d.ts +9 -3
- package/dist/components/floatlabel/floatlabel.d.ts.map +1 -1
- package/dist/components/floatlabel/floatlabel.js +22 -7
- package/dist/components/floatlabel/floatlabel.js.map +1 -1
- package/dist/components/input/input.d.ts +14 -7
- package/dist/components/input/input.d.ts.map +1 -1
- package/dist/components/input/input.js +18 -2
- package/dist/components/input/input.js.map +1 -1
- package/dist/components/knob/knob.d.ts +7 -3
- package/dist/components/knob/knob.d.ts.map +1 -1
- package/dist/components/knob/knob.js +4 -2
- package/dist/components/knob/knob.js.map +1 -1
- package/dist/components/multiselect/multiselect.d.ts +13 -3
- package/dist/components/multiselect/multiselect.d.ts.map +1 -1
- package/dist/components/multiselect/multiselect.js +27 -2
- package/dist/components/multiselect/multiselect.js.map +1 -1
- package/dist/components/radio/radio.d.ts +27 -4
- package/dist/components/radio/radio.d.ts.map +1 -1
- package/dist/components/radio/radio.js +67 -8
- package/dist/components/radio/radio.js.map +1 -1
- package/dist/components/rating/rating.d.ts +8 -4
- package/dist/components/rating/rating.d.ts.map +1 -1
- package/dist/components/rating/rating.js +5 -2
- package/dist/components/rating/rating.js.map +1 -1
- package/dist/components/select/select.d.ts +9 -3
- package/dist/components/select/select.d.ts.map +1 -1
- package/dist/components/select/select.js +11 -2
- package/dist/components/select/select.js.map +1 -1
- package/dist/components/slider/slider.d.ts +12 -6
- package/dist/components/slider/slider.d.ts.map +1 -1
- package/dist/components/slider/slider.js +10 -2
- package/dist/components/slider/slider.js.map +1 -1
- package/dist/components/switch/switch.d.ts +20 -4
- package/dist/components/switch/switch.d.ts.map +1 -1
- package/dist/components/switch/switch.js +40 -2
- package/dist/components/switch/switch.js.map +1 -1
- package/dist/components/textarea/textarea.d.ts +12 -5
- package/dist/components/textarea/textarea.d.ts.map +1 -1
- package/dist/components/textarea/textarea.js +12 -2
- package/dist/components/textarea/textarea.js.map +1 -1
- package/dist/components/toast/toast.d.ts +17 -5
- package/dist/components/toast/toast.d.ts.map +1 -1
- package/dist/components/toast/toast.js +161 -21
- package/dist/components/toast/toast.js.map +1 -1
- package/dist/components/toggle-button-group/toggle-button-group.d.ts +13 -5
- package/dist/components/toggle-button-group/toggle-button-group.d.ts.map +1 -1
- package/dist/components/toggle-button-group/toggle-button-group.js +19 -2
- package/dist/components/toggle-button-group/toggle-button-group.js.map +1 -1
- package/dist/core/base.d.ts +7 -0
- package/dist/core/base.d.ts.map +1 -1
- package/dist/core/base.js +4 -1
- package/dist/core/base.js.map +1 -1
- package/dist/core/form-element.d.ts +81 -0
- package/dist/core/form-element.d.ts.map +1 -0
- package/dist/core/form-element.js +129 -0
- package/dist/core/form-element.js.map +1 -0
- package/dist/core/presets.d.ts +8 -3
- package/dist/core/presets.d.ts.map +1 -1
- package/dist/core/presets.js +6 -17
- package/dist/core/presets.js.map +1 -1
- package/dist/core/types.d.ts +4 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/docs/app.d.ts.map +1 -1
- package/dist/docs/componentes/autocomplete.doc.d.ts.map +1 -1
- package/dist/docs/componentes/checkbox.doc.d.ts.map +1 -1
- package/dist/docs/componentes/datepicker.doc.d.ts.map +1 -1
- package/dist/docs/componentes/fileupload.doc.d.ts.map +1 -1
- package/dist/docs/componentes/input.doc.d.ts.map +1 -1
- package/dist/docs/componentes/knob.doc.d.ts.map +1 -1
- package/dist/docs/componentes/multiselect.doc.d.ts.map +1 -1
- package/dist/docs/componentes/rating.doc.d.ts.map +1 -1
- package/dist/docs/componentes/select.doc.d.ts.map +1 -1
- package/dist/docs/componentes/slider.doc.d.ts.map +1 -1
- package/dist/docs/componentes/switch.doc.d.ts.map +1 -1
- package/dist/docs/componentes/textarea.doc.d.ts.map +1 -1
- package/dist/docs/componentes/toast.doc.d.ts.map +1 -1
- package/dist/docs/componentes/toggle-button-group.doc.d.ts.map +1 -1
- package/dist/docs/frameworks.d.ts +75 -0
- package/dist/docs/frameworks.d.ts.map +1 -0
- package/dist/docs/ui/code.d.ts +4 -0
- package/dist/docs/ui/code.d.ts.map +1 -1
- package/dist/fenix-ui.umd.min.js +214 -176
- package/dist/fenix-ui4.esm.js +657 -67
- package/dist/fenix-ui4.esm.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/fenix-ui4.esm.js
CHANGED
|
@@ -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
|
-
|
|
177
|
-
name: "
|
|
178
|
-
label: "
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
2877
|
-
|
|
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
|
|
4072
|
-
|
|
4073
|
-
const
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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;
|
|
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 {
|
|
7441
|
-
|
|
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
|
-
/*
|
|
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:
|
|
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
|
|
7503
|
-
*
|
|
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 === "
|
|
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
|
-
|
|
7556
|
-
|
|
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)
|
|
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
|
|
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
|
|
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,
|