@wrrdev/fenix-ui 1.1.12 → 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/card/card.d.ts +10 -0
- package/dist/components/card/card.d.ts.map +1 -1
- package/dist/components/card/card.js +18 -0
- package/dist/components/card/card.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 +38 -4
- package/dist/components/select/select.d.ts.map +1 -1
- package/dist/components/select/select.js +109 -12
- 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/card.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 +233 -179
- package/dist/fenix-ui4.esm.js +773 -77
- 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,11 +753,11 @@ 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() {
|
|
626
|
-
return ["size", "disabled", "placeholder", "searchable", "clearable", "error", "invalid", "success", "valid"];
|
|
760
|
+
return ["size", "disabled", "placeholder", "searchable", "clearable", "error", "invalid", "success", "valid", "placement"];
|
|
627
761
|
}
|
|
628
762
|
/** Tamanho do campo. Padrão: `'md'`. */
|
|
629
763
|
get size() {
|
|
@@ -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");
|
|
@@ -658,7 +793,7 @@ const _FxSelect = class _FxSelect extends FxElement {
|
|
|
658
793
|
this.docListener = (e) => {
|
|
659
794
|
if (!this.hasAttr("open")) return;
|
|
660
795
|
if (e.composedPath().includes(this)) return;
|
|
661
|
-
this.
|
|
796
|
+
this._fechar();
|
|
662
797
|
this.render();
|
|
663
798
|
};
|
|
664
799
|
document.addEventListener("click", this.docListener);
|
|
@@ -667,11 +802,79 @@ const _FxSelect = class _FxSelect extends FxElement {
|
|
|
667
802
|
super.disconnectedCallback();
|
|
668
803
|
this.observer?.disconnect();
|
|
669
804
|
if (this.docListener) document.removeEventListener("click", this.docListener);
|
|
805
|
+
this._unbindReflow();
|
|
806
|
+
}
|
|
807
|
+
/** Abre o dropdown, posiciona o painel e passa a reposicioná-lo no reflow. */
|
|
808
|
+
_abrir() {
|
|
809
|
+
this.setAttribute("open", "");
|
|
810
|
+
this._bindReflow();
|
|
811
|
+
}
|
|
812
|
+
/** Fecha o dropdown e libera os listeners de resize/scroll. */
|
|
813
|
+
_fechar() {
|
|
814
|
+
this.removeAttribute("open");
|
|
815
|
+
this.removeAttribute("data-placement");
|
|
816
|
+
this._unbindReflow();
|
|
817
|
+
}
|
|
818
|
+
/** Liga o reposicionamento (resize + scroll interno, em capture). */
|
|
819
|
+
_bindReflow() {
|
|
820
|
+
if (this.onReflow) return;
|
|
821
|
+
this.onReflow = () => {
|
|
822
|
+
if (this.hasAttr("open")) this.ajustarPosicao();
|
|
823
|
+
};
|
|
824
|
+
window.addEventListener("resize", this.onReflow);
|
|
825
|
+
window.addEventListener("scroll", this.onReflow, true);
|
|
826
|
+
}
|
|
827
|
+
_unbindReflow() {
|
|
828
|
+
if (!this.onReflow) return;
|
|
829
|
+
window.removeEventListener("resize", this.onReflow);
|
|
830
|
+
window.removeEventListener("scroll", this.onReflow, true);
|
|
831
|
+
this.onReflow = void 0;
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Decide a posição vertical do painel medindo o espaço real da viewport.
|
|
835
|
+
*
|
|
836
|
+
* `placement="top"`/`"bottom"` são respeitados literalmente; o padrão
|
|
837
|
+
* (`auto`) só inverte quando o painel não cabe embaixo E couber melhor
|
|
838
|
+
* acima. Também limita a altura ao espaço disponível e evita overflow
|
|
839
|
+
* horizontal próximo à borda direita.
|
|
840
|
+
*/
|
|
841
|
+
ajustarPosicao() {
|
|
842
|
+
const painel = this.root.querySelector(".panel");
|
|
843
|
+
const trigger = this.root.querySelector(".trigger");
|
|
844
|
+
if (!painel || !trigger) return;
|
|
845
|
+
const r = trigger.getBoundingClientRect();
|
|
846
|
+
const abaixo = window.innerHeight - r.bottom;
|
|
847
|
+
const acima = r.top;
|
|
848
|
+
const desejado = painel.scrollHeight;
|
|
849
|
+
const maxAltura = _FxSelect._maxAltura(this);
|
|
850
|
+
const pedido = this.getAttr("placement", "auto");
|
|
851
|
+
const paraCima = pedido === "top" || pedido !== "bottom" && abaixo < Math.min(desejado, maxAltura) && acima > abaixo;
|
|
852
|
+
if (paraCima) this.setAttribute("data-placement", "top");
|
|
853
|
+
else this.removeAttribute("data-placement");
|
|
854
|
+
const disponivel = Math.max(
|
|
855
|
+
_FxSelect.MIN_ALTURA,
|
|
856
|
+
(paraCima ? acima : abaixo) - _FxSelect.GAP
|
|
857
|
+
);
|
|
858
|
+
painel.style.maxHeight = `${Math.min(desejado || disponivel, disponivel, maxAltura)}px`;
|
|
859
|
+
painel.style.left = "";
|
|
860
|
+
const largura = painel.offsetWidth;
|
|
861
|
+
const excesso = r.left + largura - (window.innerWidth - _FxSelect.MARGEM);
|
|
862
|
+
if (excesso > 0) {
|
|
863
|
+
painel.style.left = `${-excesso}px`;
|
|
864
|
+
painel.style.right = "auto";
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
/** Altura máxima do painel a partir de `--fx-select-panel-max-height`. */
|
|
868
|
+
static _maxAltura(host) {
|
|
869
|
+
const raw = getComputedStyle(host).getPropertyValue("--fx-select-panel-max-height");
|
|
870
|
+
const n = parseInt(raw, 10);
|
|
871
|
+
return Number.isFinite(n) && n > 0 ? n : 260;
|
|
670
872
|
}
|
|
671
873
|
select(value) {
|
|
672
874
|
this.value = value;
|
|
673
|
-
this.
|
|
875
|
+
this._fechar();
|
|
674
876
|
this.render();
|
|
877
|
+
this.emitValueEvents();
|
|
675
878
|
this.dispatchEvent(
|
|
676
879
|
new CustomEvent("change", { bubbles: true, composed: true, detail: { value } })
|
|
677
880
|
);
|
|
@@ -680,7 +883,7 @@ const _FxSelect = class _FxSelect extends FxElement {
|
|
|
680
883
|
_navigateOptions(e) {
|
|
681
884
|
e.preventDefault();
|
|
682
885
|
if (!this.hasAttr("open")) {
|
|
683
|
-
this.
|
|
886
|
+
this._abrir();
|
|
684
887
|
this.render();
|
|
685
888
|
}
|
|
686
889
|
const opts = Array.from(this.root.querySelectorAll(".opt"));
|
|
@@ -725,6 +928,7 @@ const _FxSelect = class _FxSelect extends FxElement {
|
|
|
725
928
|
</div>
|
|
726
929
|
`);
|
|
727
930
|
if (prevOpen) this.setAttribute("open", "");
|
|
931
|
+
if (this.hasAttr("open")) this.ajustarPosicao();
|
|
728
932
|
const searchInput = this.root.querySelector(".search");
|
|
729
933
|
if (searchInput) {
|
|
730
934
|
searchInput.value = search;
|
|
@@ -744,7 +948,8 @@ const _FxSelect = class _FxSelect extends FxElement {
|
|
|
744
948
|
trigger.addEventListener("click", (e) => {
|
|
745
949
|
if (this.disabled) return;
|
|
746
950
|
if (e.target.closest(".clear")) return;
|
|
747
|
-
this.
|
|
951
|
+
if (this.hasAttr("open")) this._fechar();
|
|
952
|
+
else this._abrir();
|
|
748
953
|
this.render();
|
|
749
954
|
this.root.querySelector(".search")?.focus();
|
|
750
955
|
});
|
|
@@ -757,7 +962,7 @@ const _FxSelect = class _FxSelect extends FxElement {
|
|
|
757
962
|
}
|
|
758
963
|
if (e.key === "Escape") {
|
|
759
964
|
if (this.hasAttr("open")) {
|
|
760
|
-
this.
|
|
965
|
+
this._fechar();
|
|
761
966
|
this.render();
|
|
762
967
|
this.root.querySelector(".trigger")?.focus();
|
|
763
968
|
}
|
|
@@ -770,8 +975,9 @@ const _FxSelect = class _FxSelect extends FxElement {
|
|
|
770
975
|
this.root.querySelector(".clear")?.addEventListener("click", (e) => {
|
|
771
976
|
e.stopPropagation();
|
|
772
977
|
this.value = "";
|
|
773
|
-
this.
|
|
978
|
+
this._fechar();
|
|
774
979
|
this.render();
|
|
980
|
+
this.emitValueEvents();
|
|
775
981
|
this.dispatchEvent(
|
|
776
982
|
new CustomEvent("change", { bubbles: true, composed: true, detail: { value: "" } })
|
|
777
983
|
);
|
|
@@ -779,6 +985,12 @@ const _FxSelect = class _FxSelect extends FxElement {
|
|
|
779
985
|
this.root.querySelectorAll(".opt").forEach((opt) => {
|
|
780
986
|
opt.addEventListener("click", () => this.select(opt.dataset.value));
|
|
781
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();
|
|
782
994
|
}
|
|
783
995
|
};
|
|
784
996
|
_FxSelect.styles = css`
|
|
@@ -792,6 +1004,11 @@ _FxSelect.styles = css`
|
|
|
792
1004
|
e style inline no elemento definem a largura sem precisar do full. */
|
|
793
1005
|
width: var(--fx-select-width, max-content);
|
|
794
1006
|
min-width: var(--fx-select-min-width, 200px);
|
|
1007
|
+
/* Espaço entre o campo e o painel + altura máxima do painel. Expostos
|
|
1008
|
+
como custom property porque ::part() do lado do consumidor tem
|
|
1009
|
+
prioridade MENOR que a folha do shadow root e não vence o top. */
|
|
1010
|
+
--fx-select-panel-offset: 4px;
|
|
1011
|
+
--fx-select-panel-max-height: 260px;
|
|
795
1012
|
}
|
|
796
1013
|
.trigger {
|
|
797
1014
|
display: inline-flex;
|
|
@@ -879,14 +1096,15 @@ _FxSelect.styles = css`
|
|
|
879
1096
|
}
|
|
880
1097
|
.caret { font-size: calc(var(--fx-font-size) - 3px); color: var(--fx-text-muted); pointer-events: none; }
|
|
881
1098
|
|
|
882
|
-
/* Painel do dropdown
|
|
1099
|
+
/* Painel do dropdown — a POSIÇÃO vertical é definida pelas regras
|
|
1100
|
+
[data-placement] abaixo (o JS de ajustarPosicao decide), por isso
|
|
1101
|
+
top/bottom não ficam aqui. */
|
|
883
1102
|
.panel {
|
|
884
1103
|
position: absolute;
|
|
885
|
-
top: calc(100% + 4px);
|
|
886
1104
|
left: 0;
|
|
887
1105
|
z-index: var(--fx-z-dropdown, 1000);
|
|
888
1106
|
width: max(100%, 220px);
|
|
889
|
-
max-height: 260px;
|
|
1107
|
+
max-height: var(--fx-select-panel-max-height, 260px);
|
|
890
1108
|
overflow-y: auto;
|
|
891
1109
|
display: none;
|
|
892
1110
|
background: var(--fx-surface-background);
|
|
@@ -894,6 +1112,16 @@ _FxSelect.styles = css`
|
|
|
894
1112
|
border-radius: var(--fx-radius-md);
|
|
895
1113
|
box-shadow: var(--fx-shadow-lg);
|
|
896
1114
|
}
|
|
1115
|
+
/* Padrão: abre para baixo. */
|
|
1116
|
+
:host([open]:not([data-placement='top'])) .panel {
|
|
1117
|
+
top: calc(100% + var(--fx-select-panel-offset, 4px));
|
|
1118
|
+
}
|
|
1119
|
+
/* placement="top" explícito ou auto-flip: abre para cima. */
|
|
1120
|
+
:host([open][placement='top']) .panel,
|
|
1121
|
+
:host([open][data-placement='top']) .panel {
|
|
1122
|
+
top: auto;
|
|
1123
|
+
bottom: calc(100% + var(--fx-select-panel-offset, 4px));
|
|
1124
|
+
}
|
|
897
1125
|
:host([open]) .panel { display: block; }
|
|
898
1126
|
.search {
|
|
899
1127
|
position: sticky;
|
|
@@ -941,12 +1169,15 @@ _FxSelect.styles = css`
|
|
|
941
1169
|
.clear:hover { color: var(--fx-color-danger); }
|
|
942
1170
|
.clear[hidden] { display: none; }
|
|
943
1171
|
`;
|
|
1172
|
+
_FxSelect.GAP = 4;
|
|
1173
|
+
_FxSelect.MARGEM = 8;
|
|
1174
|
+
_FxSelect.MIN_ALTURA = 80;
|
|
944
1175
|
let FxSelect = _FxSelect;
|
|
945
1176
|
function defineFxSelect() {
|
|
946
1177
|
return defineElement("fx-select", FxSelect);
|
|
947
1178
|
}
|
|
948
1179
|
defineFxSelect();
|
|
949
|
-
const _FxInput = class _FxInput extends
|
|
1180
|
+
const _FxInput = class _FxInput extends FxFormElement {
|
|
950
1181
|
// `value` fica FORA da observação: refleti-lo a cada tecla não pode
|
|
951
1182
|
// re-renderizar o template, senão o campo perde o foco ao digitar.
|
|
952
1183
|
// `error`/`success` (+ aliases `invalid`/`valid`) PRECISAM ser observados:
|
|
@@ -982,6 +1213,8 @@ const _FxInput = class _FxInput extends FxElement {
|
|
|
982
1213
|
}
|
|
983
1214
|
set value(value) {
|
|
984
1215
|
this.setAttribute("value", value);
|
|
1216
|
+
this.syncInternalControl();
|
|
1217
|
+
this.updateFormValue();
|
|
985
1218
|
}
|
|
986
1219
|
get disabled() {
|
|
987
1220
|
return this.hasAttr("disabled");
|
|
@@ -1021,6 +1254,7 @@ const _FxInput = class _FxInput extends FxElement {
|
|
|
1021
1254
|
if (readonly) field.setAttribute("readonly", "");
|
|
1022
1255
|
const emit = (event) => {
|
|
1023
1256
|
this.value = field.value;
|
|
1257
|
+
this.emitValueEvents();
|
|
1024
1258
|
this.dispatchEvent(
|
|
1025
1259
|
new CustomEvent(event, { bubbles: true, composed: true, detail: { value: field.value } })
|
|
1026
1260
|
);
|
|
@@ -1033,6 +1267,12 @@ const _FxInput = class _FxInput extends FxElement {
|
|
|
1033
1267
|
e.stopPropagation();
|
|
1034
1268
|
emit("change");
|
|
1035
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
|
+
});
|
|
1036
1276
|
const clearBtn = this.root.querySelector(".clear");
|
|
1037
1277
|
const syncClear = () => {
|
|
1038
1278
|
clearBtn?.toggleAttribute("hidden", field.value === "");
|
|
@@ -1046,6 +1286,13 @@ const _FxInput = class _FxInput extends FxElement {
|
|
|
1046
1286
|
field.focus();
|
|
1047
1287
|
});
|
|
1048
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;
|
|
1049
1296
|
}
|
|
1050
1297
|
};
|
|
1051
1298
|
_FxInput.styles = css`
|
|
@@ -1189,7 +1436,12 @@ function defineFxInput() {
|
|
|
1189
1436
|
return defineElement("fx-input", FxInput);
|
|
1190
1437
|
}
|
|
1191
1438
|
defineFxInput();
|
|
1192
|
-
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
|
+
}
|
|
1193
1445
|
static get observedAttributes() {
|
|
1194
1446
|
return ["checked", "disabled", "size"];
|
|
1195
1447
|
}
|
|
@@ -1213,6 +1465,17 @@ const _FxSwitch = class _FxSwitch extends FxElement {
|
|
|
1213
1465
|
set disabled(value) {
|
|
1214
1466
|
this.toggleAttr("disabled", Boolean(value));
|
|
1215
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
|
+
}
|
|
1216
1479
|
render() {
|
|
1217
1480
|
this.setTemplate(`
|
|
1218
1481
|
<button class="switch" part="switch" type="button" role="switch"
|
|
@@ -1234,7 +1497,29 @@ const _FxSwitch = class _FxSwitch extends FxElement {
|
|
|
1234
1497
|
detail: { checked: this.checked }
|
|
1235
1498
|
})
|
|
1236
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
|
+
);
|
|
1237
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();
|
|
1238
1523
|
}
|
|
1239
1524
|
};
|
|
1240
1525
|
_FxSwitch.styles = css`
|
|
@@ -1297,7 +1582,7 @@ function defineFxSwitch() {
|
|
|
1297
1582
|
return defineElement("fx-switch", FxSwitch);
|
|
1298
1583
|
}
|
|
1299
1584
|
defineFxSwitch();
|
|
1300
|
-
const _FxMultiselect = class _FxMultiselect extends
|
|
1585
|
+
const _FxMultiselect = class _FxMultiselect extends FxFormElement {
|
|
1301
1586
|
constructor() {
|
|
1302
1587
|
super(...arguments);
|
|
1303
1588
|
this.selected = /* @__PURE__ */ new Set();
|
|
@@ -1313,6 +1598,14 @@ const _FxMultiselect = class _FxMultiselect extends FxElement {
|
|
|
1313
1598
|
set values(list) {
|
|
1314
1599
|
this.setAttribute("values", list.join(","));
|
|
1315
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
|
+
}
|
|
1316
1609
|
get disabled() {
|
|
1317
1610
|
return this.hasAttr("disabled");
|
|
1318
1611
|
}
|
|
@@ -1327,6 +1620,7 @@ const _FxMultiselect = class _FxMultiselect extends FxElement {
|
|
|
1327
1620
|
}
|
|
1328
1621
|
connectedCallback() {
|
|
1329
1622
|
this.selected = new Set(this.values);
|
|
1623
|
+
if (!this._initialValues) this._initialValues = [...this.values];
|
|
1330
1624
|
super.connectedCallback();
|
|
1331
1625
|
this.observer = new MutationObserver(() => this.render());
|
|
1332
1626
|
this.observer.observe(this, { childList: true, subtree: true, characterData: true });
|
|
@@ -1347,6 +1641,7 @@ const _FxMultiselect = class _FxMultiselect extends FxElement {
|
|
|
1347
1641
|
commit() {
|
|
1348
1642
|
this.values = [...this.selected];
|
|
1349
1643
|
this.render();
|
|
1644
|
+
this.updateFormValue();
|
|
1350
1645
|
this.dispatchEvent(
|
|
1351
1646
|
new CustomEvent("change", {
|
|
1352
1647
|
bubbles: true,
|
|
@@ -1354,6 +1649,21 @@ const _FxMultiselect = class _FxMultiselect extends FxElement {
|
|
|
1354
1649
|
detail: { values: [...this.selected] }
|
|
1355
1650
|
})
|
|
1356
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();
|
|
1357
1667
|
}
|
|
1358
1668
|
toggleValue(value) {
|
|
1359
1669
|
if (this.selected.has(value)) this.selected.delete(value);
|
|
@@ -2159,7 +2469,7 @@ function defineFxCalendar() {
|
|
|
2159
2469
|
return defineElement("fx-calendar", FxCalendar);
|
|
2160
2470
|
}
|
|
2161
2471
|
defineFxCalendar();
|
|
2162
|
-
const _FxDatepicker = class _FxDatepicker extends
|
|
2472
|
+
const _FxDatepicker = class _FxDatepicker extends FxFormElement {
|
|
2163
2473
|
constructor() {
|
|
2164
2474
|
super(...arguments);
|
|
2165
2475
|
this.time = { h: 0, m: 0, s: 0 };
|
|
@@ -2167,6 +2477,32 @@ const _FxDatepicker = class _FxDatepicker extends FxElement {
|
|
|
2167
2477
|
static get observedAttributes() {
|
|
2168
2478
|
return ["value", "start", "end", "values", "mode", "min", "max", "placeholder", "disabled", "format", "show-time", "free-text", "error", "invalid", "success", "valid"];
|
|
2169
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
|
+
}
|
|
2170
2506
|
get mode() {
|
|
2171
2507
|
const m = this.getAttr("mode", "single");
|
|
2172
2508
|
return ["single", "range", "multiple"].includes(m) ? m : "single";
|
|
@@ -2195,6 +2531,14 @@ const _FxDatepicker = class _FxDatepicker extends FxElement {
|
|
|
2195
2531
|
this.toggleAttr("disabled", Boolean(value));
|
|
2196
2532
|
}
|
|
2197
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
|
+
}
|
|
2198
2542
|
super.connectedCallback();
|
|
2199
2543
|
this.docListener = (e) => {
|
|
2200
2544
|
if (!this.hasAttr("open")) return;
|
|
@@ -2207,6 +2551,15 @@ const _FxDatepicker = class _FxDatepicker extends FxElement {
|
|
|
2207
2551
|
super.disconnectedCallback();
|
|
2208
2552
|
if (this.docListener) document.removeEventListener("click", this.docListener);
|
|
2209
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
|
+
}
|
|
2210
2563
|
/** Formata uma data ISO ("YYYY-MM-DD" ou com "THH:mm:ss") segundo o atributo format. */
|
|
2211
2564
|
formatDate(iso) {
|
|
2212
2565
|
if (!iso) return "";
|
|
@@ -2379,6 +2732,7 @@ const _FxDatepicker = class _FxDatepicker extends FxElement {
|
|
|
2379
2732
|
}
|
|
2380
2733
|
}
|
|
2381
2734
|
emitChange(detail) {
|
|
2735
|
+
this.emitValueEvents(detail);
|
|
2382
2736
|
this.dispatchEvent(
|
|
2383
2737
|
new CustomEvent("change", { bubbles: true, composed: true, detail })
|
|
2384
2738
|
);
|
|
@@ -2602,7 +2956,12 @@ function defineFxDatepicker() {
|
|
|
2602
2956
|
return defineElement("fx-datepicker", FxDatepicker);
|
|
2603
2957
|
}
|
|
2604
2958
|
defineFxDatepicker();
|
|
2605
|
-
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
|
+
}
|
|
2606
2965
|
static get observedAttributes() {
|
|
2607
2966
|
return ["checked", "disabled", "indeterminate"];
|
|
2608
2967
|
}
|
|
@@ -2624,6 +2983,17 @@ const _FxCheckbox = class _FxCheckbox extends FxElement {
|
|
|
2624
2983
|
set disabled(value) {
|
|
2625
2984
|
this.toggleAttr("disabled", Boolean(value));
|
|
2626
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
|
+
}
|
|
2627
2997
|
/** Tamanho do controle. Padrão: `'md'`. */
|
|
2628
2998
|
get size() {
|
|
2629
2999
|
const s = this.getAttr("size", "md");
|
|
@@ -2656,6 +3026,20 @@ const _FxCheckbox = class _FxCheckbox extends FxElement {
|
|
|
2656
3026
|
this.toggle();
|
|
2657
3027
|
}
|
|
2658
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();
|
|
2659
3043
|
}
|
|
2660
3044
|
toggle() {
|
|
2661
3045
|
if (this.disabled) return;
|
|
@@ -2668,6 +3052,13 @@ const _FxCheckbox = class _FxCheckbox extends FxElement {
|
|
|
2668
3052
|
detail: { checked: this.checked, value: this.getAttr("value") }
|
|
2669
3053
|
})
|
|
2670
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
|
+
);
|
|
2671
3062
|
}
|
|
2672
3063
|
};
|
|
2673
3064
|
_FxCheckbox.styles = css`
|
|
@@ -2737,7 +3128,12 @@ function defineFxCheckbox() {
|
|
|
2737
3128
|
return defineElement("fx-checkbox", FxCheckbox);
|
|
2738
3129
|
}
|
|
2739
3130
|
defineFxCheckbox();
|
|
2740
|
-
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
|
+
}
|
|
2741
3137
|
static get observedAttributes() {
|
|
2742
3138
|
return ["checked", "disabled"];
|
|
2743
3139
|
}
|
|
@@ -2753,6 +3149,25 @@ const _FxRadio = class _FxRadio extends FxElement {
|
|
|
2753
3149
|
set disabled(value) {
|
|
2754
3150
|
this.toggleAttr("disabled", Boolean(value));
|
|
2755
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
|
+
}
|
|
2756
3171
|
/** Tamanho do controle. Padrão: `'md'`. */
|
|
2757
3172
|
get size() {
|
|
2758
3173
|
const s = this.getAttr("size", "md");
|
|
@@ -2777,19 +3192,35 @@ const _FxRadio = class _FxRadio extends FxElement {
|
|
|
2777
3192
|
if (!box) return;
|
|
2778
3193
|
box.addEventListener("click", () => this.select());
|
|
2779
3194
|
box.addEventListener("keydown", (e) => {
|
|
2780
|
-
|
|
3195
|
+
const key2 = e.key;
|
|
3196
|
+
if (key2 === " " || key2 === "Enter") {
|
|
2781
3197
|
e.preventDefault();
|
|
2782
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);
|
|
2783
3204
|
}
|
|
2784
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();
|
|
2785
3219
|
}
|
|
2786
3220
|
select() {
|
|
2787
3221
|
if (this.disabled || this.checked) return;
|
|
2788
|
-
const
|
|
2789
|
-
|
|
2790
|
-
document.querySelectorAll(`fx-radio[name="${CSS.escape(name)}"]`).forEach((r) => {
|
|
2791
|
-
r.checked = false;
|
|
2792
|
-
});
|
|
3222
|
+
for (const r of this._group()) {
|
|
3223
|
+
if (r !== this) r.checked = false;
|
|
2793
3224
|
}
|
|
2794
3225
|
this.checked = true;
|
|
2795
3226
|
this.dispatchEvent(
|
|
@@ -2799,6 +3230,25 @@ const _FxRadio = class _FxRadio extends FxElement {
|
|
|
2799
3230
|
detail: { checked: true, value: this.getAttr("value") }
|
|
2800
3231
|
})
|
|
2801
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();
|
|
2802
3252
|
}
|
|
2803
3253
|
};
|
|
2804
3254
|
_FxRadio.styles = css`
|
|
@@ -3974,28 +4424,43 @@ const _FxFloatlabel = class _FxFloatlabel extends FxElement {
|
|
|
3974
4424
|
this.targetControl = null;
|
|
3975
4425
|
this.observer = null;
|
|
3976
4426
|
this.listenersReady = false;
|
|
4427
|
+
this._label = null;
|
|
4428
|
+
this._labelClickBound = false;
|
|
3977
4429
|
}
|
|
3978
4430
|
static get observedAttributes() {
|
|
3979
4431
|
return ["variant", "error", "invalid", "success", "valid", "full", "icon", "icon-pos"];
|
|
3980
4432
|
}
|
|
3981
4433
|
render() {
|
|
3982
4434
|
const control = this.findControl();
|
|
3983
|
-
const
|
|
3984
|
-
|
|
3985
|
-
const
|
|
3986
|
-
const labelFor = originalLabel ? originalLabel.getAttribute("for") || "" : "";
|
|
4435
|
+
const lightLabel = this.findLabel();
|
|
4436
|
+
if (lightLabel) this._label = lightLabel;
|
|
4437
|
+
const label = this._label;
|
|
3987
4438
|
const isOver = this.getAttribute("variant") === "over";
|
|
3988
4439
|
this.setTemplate(`
|
|
3989
4440
|
<div class="wrapper" part="wrapper">
|
|
3990
|
-
${isOver ? `<label class="flabel" part="label" for="${labelFor}">${labelText}</label>` : ""}
|
|
3991
4441
|
<div class="field-box" part="field">
|
|
3992
4442
|
<slot></slot>
|
|
3993
|
-
${isOver ? "" : `<label class="flabel" part="label" for="${labelFor}">${labelText}</label>`}
|
|
3994
4443
|
</div>
|
|
3995
4444
|
<div class="error-message" part="error"></div>
|
|
3996
4445
|
</div>
|
|
3997
4446
|
`);
|
|
3998
|
-
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
|
+
}
|
|
3999
4464
|
this.attachEvents();
|
|
4000
4465
|
this.syncControlReference(control);
|
|
4001
4466
|
this.syncState();
|
|
@@ -4294,7 +4759,7 @@ function defineFxFloatlabel() {
|
|
|
4294
4759
|
return defineElement("fx-floatlabel", FxFloatlabel);
|
|
4295
4760
|
}
|
|
4296
4761
|
defineFxFloatlabel();
|
|
4297
|
-
const _FxTextarea = class _FxTextarea extends
|
|
4762
|
+
const _FxTextarea = class _FxTextarea extends FxFormElement {
|
|
4298
4763
|
static get observedAttributes() {
|
|
4299
4764
|
return ["size", "placeholder", "disabled", "readonly", "rows", "maxlength", "error", "invalid", "success", "valid"];
|
|
4300
4765
|
}
|
|
@@ -4310,6 +4775,8 @@ const _FxTextarea = class _FxTextarea extends FxElement {
|
|
|
4310
4775
|
}
|
|
4311
4776
|
set value(value) {
|
|
4312
4777
|
this.setAttribute("value", value);
|
|
4778
|
+
this.syncInternalControl();
|
|
4779
|
+
this.updateFormValue();
|
|
4313
4780
|
}
|
|
4314
4781
|
render() {
|
|
4315
4782
|
const placeholder = this.getAttr("placeholder");
|
|
@@ -4329,6 +4796,7 @@ const _FxTextarea = class _FxTextarea extends FxElement {
|
|
|
4329
4796
|
if (readonly) field.setAttribute("readonly", "");
|
|
4330
4797
|
const emit = (event) => {
|
|
4331
4798
|
this.value = field.value;
|
|
4799
|
+
this.emitValueEvents();
|
|
4332
4800
|
this.dispatchEvent(
|
|
4333
4801
|
new CustomEvent(event, { bubbles: true, composed: true, detail: { value: field.value } })
|
|
4334
4802
|
);
|
|
@@ -4341,6 +4809,13 @@ const _FxTextarea = class _FxTextarea extends FxElement {
|
|
|
4341
4809
|
e.stopPropagation();
|
|
4342
4810
|
emit("change");
|
|
4343
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;
|
|
4344
4819
|
}
|
|
4345
4820
|
};
|
|
4346
4821
|
_FxTextarea.styles = css`
|
|
@@ -4883,7 +5358,7 @@ function defineFxConfirmPopup() {
|
|
|
4883
5358
|
return defineElement("fx-confirmpopup", FxConfirmPopup);
|
|
4884
5359
|
}
|
|
4885
5360
|
defineFxConfirmPopup();
|
|
4886
|
-
const _FxFileUpload = class _FxFileUpload extends
|
|
5361
|
+
const _FxFileUpload = class _FxFileUpload extends FxFormElement {
|
|
4887
5362
|
constructor() {
|
|
4888
5363
|
super(...arguments);
|
|
4889
5364
|
this._files = [];
|
|
@@ -4921,6 +5396,14 @@ const _FxFileUpload = class _FxFileUpload extends FxElement {
|
|
|
4921
5396
|
this.value = this._files.map((f) => f.name).join(", ");
|
|
4922
5397
|
this._syncFilesArea();
|
|
4923
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
|
+
}
|
|
4924
5407
|
render() {
|
|
4925
5408
|
const mode = this.getAttr("mode", "basic");
|
|
4926
5409
|
const advanced = mode === "advanced";
|
|
@@ -4953,6 +5436,7 @@ const _FxFileUpload = class _FxFileUpload extends FxElement {
|
|
|
4953
5436
|
`);
|
|
4954
5437
|
this._bindPicker();
|
|
4955
5438
|
this._syncFilesArea();
|
|
5439
|
+
this.updateFormValue();
|
|
4956
5440
|
if (!this._done && showProgress && pct >= 100 && this._files.length) {
|
|
4957
5441
|
this._done = true;
|
|
4958
5442
|
this.dispatchEvent(new CustomEvent("complete", { bubbles: true, composed: true }));
|
|
@@ -5001,6 +5485,12 @@ const _FxFileUpload = class _FxFileUpload extends FxElement {
|
|
|
5001
5485
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
5002
5486
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
5003
5487
|
}
|
|
5488
|
+
/** form.reset(): limpa os arquivos selecionados. */
|
|
5489
|
+
formResetCallback() {
|
|
5490
|
+
this._files = [];
|
|
5491
|
+
this.value = "";
|
|
5492
|
+
this.updateFormValue();
|
|
5493
|
+
}
|
|
5004
5494
|
/** Renderiza a lista de arquivos sem re-renderizar o template (mantém estado). */
|
|
5005
5495
|
_syncFilesArea() {
|
|
5006
5496
|
const area = this.root?.querySelector?.(".files");
|
|
@@ -5172,7 +5662,7 @@ function defineFxFileUpload() {
|
|
|
5172
5662
|
return defineElement("fx-fileupload", FxFileUpload);
|
|
5173
5663
|
}
|
|
5174
5664
|
defineFxFileUpload();
|
|
5175
|
-
const _FxSlider = class _FxSlider extends
|
|
5665
|
+
const _FxSlider = class _FxSlider extends FxFormElement {
|
|
5176
5666
|
constructor() {
|
|
5177
5667
|
super(...arguments);
|
|
5178
5668
|
this._dragging = false;
|
|
@@ -5196,6 +5686,7 @@ const _FxSlider = class _FxSlider extends FxElement {
|
|
|
5196
5686
|
const snapped = this._snap(v);
|
|
5197
5687
|
this.setAttribute("value", String(snapped));
|
|
5198
5688
|
if (this.isConnected) this._syncVisual();
|
|
5689
|
+
this.updateFormValue();
|
|
5199
5690
|
}
|
|
5200
5691
|
_num(attr, fallback) {
|
|
5201
5692
|
const raw = this.getAttribute(attr);
|
|
@@ -5270,6 +5761,7 @@ const _FxSlider = class _FxSlider extends FxElement {
|
|
|
5270
5761
|
window.removeEventListener("pointerup", up);
|
|
5271
5762
|
this.value = valueFromEvent(e);
|
|
5272
5763
|
this._syncVisual();
|
|
5764
|
+
this.emitValueEvents();
|
|
5273
5765
|
this.dispatchEvent(
|
|
5274
5766
|
new CustomEvent("change", { bubbles: true, composed: true, detail: { value: this.value } })
|
|
5275
5767
|
);
|
|
@@ -5324,6 +5816,7 @@ const _FxSlider = class _FxSlider extends FxElement {
|
|
|
5324
5816
|
ev.preventDefault();
|
|
5325
5817
|
this.value = next;
|
|
5326
5818
|
this._syncVisual();
|
|
5819
|
+
this.emitValueEvents();
|
|
5327
5820
|
this.dispatchEvent(
|
|
5328
5821
|
new CustomEvent("input", { bubbles: true, composed: true, detail: { value: this.value } })
|
|
5329
5822
|
);
|
|
@@ -5350,6 +5843,11 @@ const _FxSlider = class _FxSlider extends FxElement {
|
|
|
5350
5843
|
const labelPct = this.root.querySelector(".label span:last-child");
|
|
5351
5844
|
if (labelPct) labelPct.textContent = String(this.value);
|
|
5352
5845
|
}
|
|
5846
|
+
/** form.reset(): restaura o valor inicial e o visual sem re-render. */
|
|
5847
|
+
formResetCallback() {
|
|
5848
|
+
super.formResetCallback();
|
|
5849
|
+
this._syncVisual();
|
|
5850
|
+
}
|
|
5353
5851
|
};
|
|
5354
5852
|
_FxSlider.styles = css`
|
|
5355
5853
|
:host {
|
|
@@ -5720,6 +6218,24 @@ const _FxCard = class _FxCard extends FxElement {
|
|
|
5720
6218
|
<footer part="footer"><slot name="footer"></slot></footer>
|
|
5721
6219
|
</div>
|
|
5722
6220
|
`);
|
|
6221
|
+
this._syncSlots();
|
|
6222
|
+
for (const name of ["header", "footer"]) {
|
|
6223
|
+
this.root.querySelector(`slot[name="${name}"]`)?.addEventListener("slotchange", () => this._syncSlots());
|
|
6224
|
+
}
|
|
6225
|
+
}
|
|
6226
|
+
/**
|
|
6227
|
+
* Esconde `header`/`footer` quando o respectivo slot não recebeu nenhum nó.
|
|
6228
|
+
* O `<slot>` fica no shadow e os nós no light DOM, então a checagem é feita
|
|
6229
|
+
* via `assignedNodes()` — cobre conteúdo adicionado dinamicamente.
|
|
6230
|
+
*/
|
|
6231
|
+
_syncSlots() {
|
|
6232
|
+
for (const name of ["header", "footer"]) {
|
|
6233
|
+
const slot = this.root.querySelector(`slot[name="${name}"]`);
|
|
6234
|
+
const section = this.root.querySelector(name);
|
|
6235
|
+
if (!slot || !section) continue;
|
|
6236
|
+
const temConteudo = slot.assignedNodes({ flatten: true }).some((n) => n.nodeType === 1 || (n.textContent ?? "").trim() !== "") || name === "header" && Boolean(this.getAttr("heading"));
|
|
6237
|
+
section.toggleAttribute("hidden", !temConteudo);
|
|
6238
|
+
}
|
|
5723
6239
|
}
|
|
5724
6240
|
};
|
|
5725
6241
|
_FxCard.styles = css`
|
|
@@ -6274,7 +6790,7 @@ function defineFxStepper() {
|
|
|
6274
6790
|
return defineElement("fx-stepper", FxStepper);
|
|
6275
6791
|
}
|
|
6276
6792
|
defineFxStepper();
|
|
6277
|
-
const _FxRating = class _FxRating extends
|
|
6793
|
+
const _FxRating = class _FxRating extends FxFormElement {
|
|
6278
6794
|
static get observedAttributes() {
|
|
6279
6795
|
return ["value", "max", "readonly", "disabled", "allow-half", "size"];
|
|
6280
6796
|
}
|
|
@@ -6285,6 +6801,7 @@ const _FxRating = class _FxRating extends FxElement {
|
|
|
6285
6801
|
set value(v) {
|
|
6286
6802
|
this.setAttribute("value", String(Math.min(this.max, Math.max(0, v))));
|
|
6287
6803
|
this._syncVisual();
|
|
6804
|
+
this.updateFormValue();
|
|
6288
6805
|
}
|
|
6289
6806
|
get max() {
|
|
6290
6807
|
const n = Number(this.getAttr("max", "5"));
|
|
@@ -6304,6 +6821,7 @@ const _FxRating = class _FxRating extends FxElement {
|
|
|
6304
6821
|
`);
|
|
6305
6822
|
this._bindStars();
|
|
6306
6823
|
this._syncVisual();
|
|
6824
|
+
this.updateFormValue();
|
|
6307
6825
|
}
|
|
6308
6826
|
_starNodes() {
|
|
6309
6827
|
return Array.from(this.root.querySelectorAll(".star"));
|
|
@@ -6327,6 +6845,7 @@ const _FxRating = class _FxRating extends FxElement {
|
|
|
6327
6845
|
let next = n;
|
|
6328
6846
|
if (this.hasAttr("allow-half")) next += -0.5;
|
|
6329
6847
|
this.value = next;
|
|
6848
|
+
this.emitValueEvents();
|
|
6330
6849
|
this.dispatchEvent(
|
|
6331
6850
|
new CustomEvent("change", { bubbles: true, composed: true, detail: { value: this.value } })
|
|
6332
6851
|
);
|
|
@@ -6533,7 +7052,7 @@ function defineFxMenu() {
|
|
|
6533
7052
|
return defineElement("fx-menu", FxMenu);
|
|
6534
7053
|
}
|
|
6535
7054
|
defineFxMenu();
|
|
6536
|
-
const _FxToggleButtonGroup = class _FxToggleButtonGroup extends
|
|
7055
|
+
const _FxToggleButtonGroup = class _FxToggleButtonGroup extends FxFormElement {
|
|
6537
7056
|
static get observedAttributes() {
|
|
6538
7057
|
return ["multiple", "size"];
|
|
6539
7058
|
}
|
|
@@ -6543,10 +7062,19 @@ const _FxToggleButtonGroup = class _FxToggleButtonGroup extends FxElement {
|
|
|
6543
7062
|
}
|
|
6544
7063
|
set value(v) {
|
|
6545
7064
|
this.setAttribute("value", v.join(","));
|
|
7065
|
+
this.updateFormValue();
|
|
6546
7066
|
}
|
|
6547
7067
|
get multiple() {
|
|
6548
7068
|
return this.hasAttr("multiple");
|
|
6549
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
|
+
}
|
|
6550
7078
|
get options() {
|
|
6551
7079
|
return Array.from(this.children).filter((el) => el.matches('[slot="option"]')).map((el) => el);
|
|
6552
7080
|
}
|
|
@@ -6564,6 +7092,13 @@ const _FxToggleButtonGroup = class _FxToggleButtonGroup extends FxElement {
|
|
|
6564
7092
|
</div>
|
|
6565
7093
|
`);
|
|
6566
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();
|
|
6567
7102
|
}
|
|
6568
7103
|
_bindOptions() {
|
|
6569
7104
|
Array.from(this.root.querySelectorAll(".option") || []).forEach((opt) => {
|
|
@@ -6593,6 +7128,7 @@ const _FxToggleButtonGroup = class _FxToggleButtonGroup extends FxElement {
|
|
|
6593
7128
|
o.setAttribute("aria-pressed", String(on));
|
|
6594
7129
|
});
|
|
6595
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] } }));
|
|
6596
7132
|
}
|
|
6597
7133
|
};
|
|
6598
7134
|
_FxToggleButtonGroup.styles = css`
|
|
@@ -7273,6 +7809,7 @@ const KIND_ICON = {
|
|
|
7273
7809
|
warning: "!",
|
|
7274
7810
|
info: "i"
|
|
7275
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>';
|
|
7276
7813
|
const CARD_CSS = `
|
|
7277
7814
|
:host { display: contents; pointer-events: auto; }
|
|
7278
7815
|
/* Modo explícito: sobrepõe os tokens herdados do tema global SOMENTE neste card. */
|
|
@@ -7306,8 +7843,11 @@ const CARD_CSS = `
|
|
|
7306
7843
|
user-select: none;
|
|
7307
7844
|
}
|
|
7308
7845
|
.card {
|
|
7309
|
-
display: flex; align-items:
|
|
7846
|
+
display: flex; align-items: center; gap: 10px;
|
|
7310
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;
|
|
7311
7851
|
background: var(--fx-surface-background, #fff);
|
|
7312
7852
|
color: var(--fx-text-default, #1e293b);
|
|
7313
7853
|
border: 1px solid var(--fx-border-default, #e2e8f0);
|
|
@@ -7326,16 +7866,45 @@ const CARD_CSS = `
|
|
|
7326
7866
|
color: #fff;
|
|
7327
7867
|
display: flex; align-items: center; justify-content: center;
|
|
7328
7868
|
font-size: 12px; font-weight: 700;
|
|
7329
|
-
flex: none;
|
|
7869
|
+
flex: none; align-self: center;
|
|
7330
7870
|
user-select: none;
|
|
7331
7871
|
}
|
|
7332
|
-
.body { flex: 1; min-width: 0; }
|
|
7872
|
+
.body { flex: 1; min-width: 0; align-self: center; }
|
|
7333
7873
|
.title { font-weight: 600; font-size: 14px; }
|
|
7334
|
-
.msg {
|
|
7335
|
-
|
|
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 {
|
|
7336
7900
|
all: unset; cursor: pointer; flex: none; line-height: 1;
|
|
7337
7901
|
color: var(--fx-text-muted, #64748b); font-size: 15px; padding: 2px 4px; border-radius: 4px;
|
|
7338
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)); }
|
|
7339
7908
|
.close:hover { color: var(--fx-color-danger, #f43f5e); background: var(--fx-surface-surface-hover, rgba(0,0,0,.05)); }
|
|
7340
7909
|
|
|
7341
7910
|
/* --- Barra de contagem regressiva (atributo progress) ---
|
|
@@ -7364,9 +7933,12 @@ const CARD_CSS = `
|
|
|
7364
7933
|
will-change: transform;
|
|
7365
7934
|
}
|
|
7366
7935
|
.progress.run { animation: fx-toast-drain var(--toast-duration, 4000ms) linear forwards; }
|
|
7367
|
-
/*
|
|
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). */
|
|
7368
7940
|
.card:hover .progress.run,
|
|
7369
|
-
.card:focus-within .progress.run { animation-play-state:
|
|
7941
|
+
.card:focus-within .progress.run { animation-play-state: paused; }
|
|
7370
7942
|
@keyframes fx-toast-drain {
|
|
7371
7943
|
from { transform: scaleX(1); }
|
|
7372
7944
|
to { transform: scaleX(0); }
|
|
@@ -7385,6 +7957,12 @@ class FxToast extends HTMLElement {
|
|
|
7385
7957
|
super();
|
|
7386
7958
|
__privateAdd(this, _FxToast_instances);
|
|
7387
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;
|
|
7388
7966
|
this.attachShadow({ mode: "open" });
|
|
7389
7967
|
}
|
|
7390
7968
|
static get observedAttributes() {
|
|
@@ -7393,19 +7971,19 @@ class FxToast extends HTMLElement {
|
|
|
7393
7971
|
/**
|
|
7394
7972
|
* Exibe a barra de contagem regressiva na base do card.
|
|
7395
7973
|
*
|
|
7396
|
-
* A barra esvazia
|
|
7397
|
-
*
|
|
7398
|
-
* 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.
|
|
7399
7976
|
*
|
|
7400
7977
|
* - `'always'` (ou atributo vazio): barra sempre correndo.
|
|
7401
|
-
* - `'hover'`: a barra só corre enquanto o cursor estiver sobre o toast;
|
|
7402
|
-
* ao sair, a animação pausa, então dá tempo de ler a mensagem.
|
|
7403
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.
|
|
7404
7982
|
*/
|
|
7405
7983
|
get progress() {
|
|
7406
7984
|
if (!this.hasAttribute("progress")) return void 0;
|
|
7407
7985
|
const v = this.getAttribute("progress");
|
|
7408
|
-
return v === "
|
|
7986
|
+
return v === "paused" ? "paused" : "always";
|
|
7409
7987
|
}
|
|
7410
7988
|
set progress(value) {
|
|
7411
7989
|
if (value) this.setAttribute("progress", value);
|
|
@@ -7445,11 +8023,13 @@ class FxToast extends HTMLElement {
|
|
|
7445
8023
|
}
|
|
7446
8024
|
connectedCallback() {
|
|
7447
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);
|
|
7448
8028
|
const safe = this.safeDuration;
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
}
|
|
8029
|
+
this.counting = safe > 0;
|
|
8030
|
+
if (safe > 0 && !this.timer) __privateMethod(this, _FxToast_instances, startTimer_fn).call(this, safe);
|
|
7452
8031
|
__privateMethod(this, _FxToast_instances, syncProgress_fn).call(this);
|
|
8032
|
+
__privateMethod(this, _FxToast_instances, syncMore_fn).call(this);
|
|
7453
8033
|
}
|
|
7454
8034
|
/**
|
|
7455
8035
|
* Limpa recursos ao sair do DOM e avisa a API imperativa (`FenixToast`).
|
|
@@ -7460,11 +8040,21 @@ class FxToast extends HTMLElement {
|
|
|
7460
8040
|
* sem limite em uso prolongado.
|
|
7461
8041
|
*/
|
|
7462
8042
|
disconnectedCallback() {
|
|
8043
|
+
this.disposed = true;
|
|
7463
8044
|
if (this.timer) clearTimeout(this.timer);
|
|
7464
8045
|
this.timer = null;
|
|
7465
8046
|
this.dispatchEvent(new CustomEvent(REMOVED_EVENT, { bubbles: false, composed: false }));
|
|
7466
8047
|
}
|
|
7467
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
|
+
}
|
|
7468
8058
|
if (name === "duration" || name === "progress" || name === "progress-origin" || name === "kind") {
|
|
7469
8059
|
__privateMethod(this, _FxToast_instances, syncProgress_fn).call(this);
|
|
7470
8060
|
}
|
|
@@ -7482,6 +8072,49 @@ class FxToast extends HTMLElement {
|
|
|
7482
8072
|
}
|
|
7483
8073
|
}
|
|
7484
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
|
+
};
|
|
7485
8118
|
/**
|
|
7486
8119
|
* Aplica (ou remove) a barra de contagem regressiva.
|
|
7487
8120
|
*
|
|
@@ -7526,17 +8159,60 @@ render_fn = function() {
|
|
|
7526
8159
|
<div class="card" style="--kind:${color}" role="status" part="card">
|
|
7527
8160
|
<span class="icon${isFxIcon ? " fx-icon" : ""}">${iconContent}</span>
|
|
7528
8161
|
<div class="body">
|
|
7529
|
-
${title ? '<div class="title"></div>' : ""}
|
|
7530
|
-
${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>
|
|
7531
8169
|
</div>
|
|
7532
|
-
<button class="close" aria-label="Fechar">✕</button>
|
|
7533
8170
|
</div>`;
|
|
7534
8171
|
const t = this.shadowRoot.querySelector(".title");
|
|
7535
8172
|
const m = this.shadowRoot.querySelector(".msg");
|
|
7536
8173
|
if (t) t.textContent = title;
|
|
7537
|
-
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
|
+
});
|
|
7538
8199
|
this.shadowRoot.querySelector(".close").addEventListener("click", () => this.dismiss());
|
|
7539
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
|
+
};
|
|
7540
8216
|
function regionFor(position) {
|
|
7541
8217
|
let region = document.querySelector(`[data-fx-toast-region="${position}"]`);
|
|
7542
8218
|
if (!region) {
|
|
@@ -8904,7 +9580,7 @@ function defineFxPagination() {
|
|
|
8904
9580
|
return defineElement("fx-pagination", FxPagination);
|
|
8905
9581
|
}
|
|
8906
9582
|
defineFxPagination();
|
|
8907
|
-
const _FxAutocomplete = class _FxAutocomplete extends
|
|
9583
|
+
const _FxAutocomplete = class _FxAutocomplete extends FxFormElement {
|
|
8908
9584
|
static get observedAttributes() {
|
|
8909
9585
|
return ["size", "placeholder", "source", "disabled", "min-chars", "error", "invalid", "success", "valid"];
|
|
8910
9586
|
}
|
|
@@ -8920,6 +9596,8 @@ const _FxAutocomplete = class _FxAutocomplete extends FxElement {
|
|
|
8920
9596
|
}
|
|
8921
9597
|
set value(v) {
|
|
8922
9598
|
this.setAttribute("value", v);
|
|
9599
|
+
this.syncInternalControl();
|
|
9600
|
+
this.updateFormValue();
|
|
8923
9601
|
}
|
|
8924
9602
|
get source() {
|
|
8925
9603
|
try {
|
|
@@ -8969,6 +9647,7 @@ const _FxAutocomplete = class _FxAutocomplete extends FxElement {
|
|
|
8969
9647
|
field.value = btn.dataset.v ?? "";
|
|
8970
9648
|
this.value = field.value;
|
|
8971
9649
|
this.toggleAttr("open", false);
|
|
9650
|
+
this.emitValueEvents();
|
|
8972
9651
|
this.dispatchEvent(new CustomEvent("select", {
|
|
8973
9652
|
bubbles: true,
|
|
8974
9653
|
composed: true,
|
|
@@ -8980,6 +9659,7 @@ const _FxAutocomplete = class _FxAutocomplete extends FxElement {
|
|
|
8980
9659
|
};
|
|
8981
9660
|
field.addEventListener("input", () => {
|
|
8982
9661
|
this.value = field.value;
|
|
9662
|
+
this.emitValueEvents();
|
|
8983
9663
|
openList();
|
|
8984
9664
|
});
|
|
8985
9665
|
field.addEventListener("focus", () => {
|
|
@@ -8990,6 +9670,11 @@ const _FxAutocomplete = class _FxAutocomplete extends FxElement {
|
|
|
8990
9670
|
this.toggleAttr("open", false);
|
|
8991
9671
|
return;
|
|
8992
9672
|
}
|
|
9673
|
+
if (e.key === "Enter" && !e.isComposing && !this.hasAttr("open") && this.form) {
|
|
9674
|
+
e.preventDefault();
|
|
9675
|
+
this.form.requestSubmit();
|
|
9676
|
+
return;
|
|
9677
|
+
}
|
|
8993
9678
|
if (e.key !== "ArrowDown" && e.key !== "ArrowUp") return;
|
|
8994
9679
|
e.preventDefault();
|
|
8995
9680
|
if (!this.hasAttr("open")) openList();
|
|
@@ -8999,6 +9684,13 @@ const _FxAutocomplete = class _FxAutocomplete extends FxElement {
|
|
|
8999
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);
|
|
9000
9685
|
opts[next]?.focus();
|
|
9001
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;
|
|
9002
9694
|
}
|
|
9003
9695
|
};
|
|
9004
9696
|
_FxAutocomplete.styles = css`
|
|
@@ -9092,7 +9784,7 @@ function defineFxAutocomplete() {
|
|
|
9092
9784
|
return defineElement("fx-autocomplete", FxAutocomplete);
|
|
9093
9785
|
}
|
|
9094
9786
|
defineFxAutocomplete();
|
|
9095
|
-
const _FxKnob = class _FxKnob extends
|
|
9787
|
+
const _FxKnob = class _FxKnob extends FxFormElement {
|
|
9096
9788
|
constructor() {
|
|
9097
9789
|
super(...arguments);
|
|
9098
9790
|
this._isDragging = false;
|
|
@@ -9237,6 +9929,7 @@ const _FxKnob = class _FxKnob extends FxElement {
|
|
|
9237
9929
|
</div>
|
|
9238
9930
|
`);
|
|
9239
9931
|
this.attachListeners();
|
|
9932
|
+
this.updateFormValue();
|
|
9240
9933
|
}
|
|
9241
9934
|
getSizeNum() {
|
|
9242
9935
|
const s = this.size;
|
|
@@ -9268,6 +9961,7 @@ const _FxKnob = class _FxKnob extends FxElement {
|
|
|
9268
9961
|
const clamped = this.clampValue(newValue);
|
|
9269
9962
|
if (clamped !== this.value) {
|
|
9270
9963
|
this.value = clamped;
|
|
9964
|
+
this.emitValueEvents();
|
|
9271
9965
|
this.dispatchEvent(
|
|
9272
9966
|
new CustomEvent("change", {
|
|
9273
9967
|
bubbles: true,
|
|
@@ -11097,6 +11791,7 @@ function defineFxAccordionPanel() {
|
|
|
11097
11791
|
defineFxAccordion();
|
|
11098
11792
|
defineFxAccordionPanel();
|
|
11099
11793
|
export {
|
|
11794
|
+
DEFAULT_PRESET,
|
|
11100
11795
|
FX_JSX_TYPES,
|
|
11101
11796
|
FenixToast,
|
|
11102
11797
|
FenixUI,
|
|
@@ -11123,6 +11818,7 @@ export {
|
|
|
11123
11818
|
FxEmptyState,
|
|
11124
11819
|
FxFileUpload,
|
|
11125
11820
|
FxFloatlabel,
|
|
11821
|
+
FxFormElement,
|
|
11126
11822
|
FxIcon,
|
|
11127
11823
|
FxInput,
|
|
11128
11824
|
FxKnob,
|