@wrrdev/fenix-ui 1.1.4 → 1.1.5
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 +2 -2
- package/dist/assets/base-css-Be_r8aTo.js +74 -0
- package/dist/assets/base-css-Be_r8aTo.js.map +1 -0
- package/dist/components/autocomplete/autocomplete.d.ts.map +1 -1
- package/dist/components/autocomplete/autocomplete.js +21 -7
- package/dist/components/autocomplete/autocomplete.js.map +1 -1
- package/dist/components/button/button.d.ts +4 -2
- package/dist/components/button/button.d.ts.map +1 -1
- package/dist/components/button/button.js +29 -9
- package/dist/components/button/button.js.map +1 -1
- package/dist/components/chip/chip.d.ts.map +1 -1
- package/dist/components/chip/chip.js +4 -2
- package/dist/components/chip/chip.js.map +1 -1
- package/dist/components/datepicker/datepicker.d.ts.map +1 -1
- package/dist/components/datepicker/datepicker.js +5 -1
- package/dist/components/datepicker/datepicker.js.map +1 -1
- package/dist/components/empty-state/empty-state.d.ts.map +1 -1
- package/dist/components/empty-state/empty-state.js +4 -1
- package/dist/components/empty-state/empty-state.js.map +1 -1
- package/dist/components/fileupload/fileupload.d.ts.map +1 -1
- package/dist/components/fileupload/fileupload.js +10 -3
- package/dist/components/fileupload/fileupload.js.map +1 -1
- package/dist/components/floatlabel/floatlabel.d.ts +8 -0
- package/dist/components/floatlabel/floatlabel.d.ts.map +1 -1
- package/dist/components/floatlabel/floatlabel.js +66 -3
- package/dist/components/floatlabel/floatlabel.js.map +1 -1
- package/dist/components/input/input.d.ts +4 -1
- package/dist/components/input/input.d.ts.map +1 -1
- package/dist/components/input/input.js +68 -2
- package/dist/components/input/input.js.map +1 -1
- package/dist/components/multiselect/multiselect.d.ts.map +1 -1
- package/dist/components/multiselect/multiselect.js +4 -1
- package/dist/components/multiselect/multiselect.js.map +1 -1
- package/dist/components/select/select.d.ts.map +1 -1
- package/dist/components/select/select.js +5 -1
- package/dist/components/select/select.js.map +1 -1
- package/dist/components/textarea/textarea.d.ts.map +1 -1
- package/dist/components/textarea/textarea.js +5 -2
- package/dist/components/textarea/textarea.js.map +1 -1
- package/dist/core/jsx.d.ts +25 -0
- package/dist/core/jsx.d.ts.map +1 -1
- package/dist/core/jsx.js.map +1 -1
- package/dist/core/vue.d.ts +3 -1
- package/dist/core/vue.d.ts.map +1 -1
- package/dist/core/vue.js.map +1 -1
- package/dist/docs/app.d.ts.map +1 -1
- package/dist/docs/componentes/alert.doc.d.ts.map +1 -1
- package/dist/docs/componentes/autocomplete.doc.d.ts.map +1 -1
- package/dist/docs/componentes/button.doc.d.ts.map +1 -1
- package/dist/docs/componentes/confirmpopup.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/floatlabel.doc.d.ts.map +1 -1
- package/dist/docs/componentes/input.doc.d.ts.map +1 -1
- package/dist/docs/componentes/multiselect.doc.d.ts.map +1 -1
- package/dist/docs/componentes/select.doc.d.ts.map +1 -1
- package/dist/docs/componentes/textarea.doc.d.ts.map +1 -1
- package/dist/fenix-ui.esm.js.map +1 -1
- package/dist/fenix-ui.umd.min.js +342 -218
- package/dist/fenix-ui2.esm.js +2 -2
- package/dist/fenix-ui3.esm.js +302 -147
- package/dist/fenix-ui3.esm.js.map +1 -1
- package/dist/icons/base-css.d.ts +16 -0
- package/dist/icons/base-css.d.ts.map +1 -1
- package/dist/icons/base-css.js +10 -0
- package/dist/icons/base-css.js.map +1 -1
- package/package.json +1 -1
- package/dist/assets/base-css-BifSeW2H.js +0 -31
- package/dist/assets/base-css-BifSeW2H.js.map +0 -1
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ import { FenixUI } from '@wrrdev/fenix-ui'; // API de tema/tokens (índice)
|
|
|
18
18
|
FenixUI.theme('dark');
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
> 📦 npm: [`@wrrdev/fenix-ui`](https://www.npmjs.com/package/@wrrdev/fenix-ui) — **v1.1.
|
|
21
|
+
> 📦 npm: [`@wrrdev/fenix-ui`](https://www.npmjs.com/package/@wrrdev/fenix-ui) — **v1.1.5**, acesso público.
|
|
22
22
|
> Subpaths disponíveis: `./theme`, `./tokens`, `./jsx`, `./vue`, `./button`, `./badge`, `./spinner`, `./select`, `./input`, `./switch`, `./textarea`, `./checkbox`, `./radio`, `./calendar`, `./datepicker`, `./multiselect`, `./table`, `./floatlabel`, `./dialog`, `./toast`, `./tooltip`, `./tabs`, `./progress`, `./skeleton`, `./alert`, `./drawer`, `./dropdown`, `./sidebar`, `./pagination`, `./autocomplete`, `./auto-import` e o curinga `./components/*`.
|
|
23
23
|
|
|
24
24
|
|
|
@@ -65,7 +65,7 @@ Carregue o bundle **UMD** único: ele registra todos os componentes e expõe os
|
|
|
65
65
|
<fx-toast></fx-toast>
|
|
66
66
|
|
|
67
67
|
<!--
|
|
68
|
-
🔗 Links diretos para CDN (versão 1.1.
|
|
68
|
+
🔗 Links diretos para CDN (versão 1.1.5):
|
|
69
69
|
• https://cdn.jsdelivr.net/npm/@wrrdev/fenix-ui@latest/dist/fenix-ui.umd.min.js
|
|
70
70
|
• https://unpkg.com/@wrrdev/fenix-ui@latest/dist/fenix-ui.umd.min.js
|
|
71
71
|
-->
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const HTML_ENTITIES = {
|
|
2
|
+
"&": "&",
|
|
3
|
+
"<": "<",
|
|
4
|
+
">": ">",
|
|
5
|
+
'"': """,
|
|
6
|
+
"'": "'",
|
|
7
|
+
"/": "/",
|
|
8
|
+
"`": "`",
|
|
9
|
+
"=": "="
|
|
10
|
+
};
|
|
11
|
+
const SANITIZE_REGEX = /[&<>"'`=/]/g;
|
|
12
|
+
function esc(value) {
|
|
13
|
+
if (value == null) return "";
|
|
14
|
+
return String(value).replace(SANITIZE_REGEX, (char) => HTML_ENTITIES[char] || char);
|
|
15
|
+
}
|
|
16
|
+
function stripTags(html) {
|
|
17
|
+
return html.replace(/<[^>]*>/g, "");
|
|
18
|
+
}
|
|
19
|
+
function sanitizeData(data) {
|
|
20
|
+
if (data == null) return data;
|
|
21
|
+
if (typeof data === "string") return esc(data);
|
|
22
|
+
if (Array.isArray(data)) return data.map(sanitizeData);
|
|
23
|
+
if (typeof data === "object") {
|
|
24
|
+
const result = {};
|
|
25
|
+
for (const [key, value] of Object.entries(data)) {
|
|
26
|
+
result[key] = sanitizeData(value);
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
return data;
|
|
31
|
+
}
|
|
32
|
+
const FENIX_ICON_FONT_FAMILY = "Fenix Icons";
|
|
33
|
+
const FENIX_ICON_FONT_VAR = `var(--fx-icon-font, ${FENIX_ICON_FONT_FAMILY})`;
|
|
34
|
+
const FENIX_ICON_BASE_CSS = `
|
|
35
|
+
.fx-icon {
|
|
36
|
+
font-family: ${FENIX_ICON_FONT_VAR};
|
|
37
|
+
font-weight: normal;
|
|
38
|
+
font-style: normal;
|
|
39
|
+
line-height: 1;
|
|
40
|
+
letter-spacing: normal;
|
|
41
|
+
text-transform: none;
|
|
42
|
+
display: inline-block;
|
|
43
|
+
white-space: nowrap;
|
|
44
|
+
word-wrap: normal;
|
|
45
|
+
direction: ltr;
|
|
46
|
+
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
|
47
|
+
-webkit-font-smoothing: antialiased;
|
|
48
|
+
text-rendering: optimizeLegibility;
|
|
49
|
+
user-select: none;
|
|
50
|
+
}
|
|
51
|
+
.fx-icon.fx-icon-fill { font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 24; }
|
|
52
|
+
.fx-icon.fx-icon-bold { font-variation-settings: 'FILL' 0, 'wght' 600, 'GRAD' 0, 'opsz' 24; }
|
|
53
|
+
`;
|
|
54
|
+
function isFenixIconName(value) {
|
|
55
|
+
return /^[a-z][a-z0-9_]*$/.test(value.trim());
|
|
56
|
+
}
|
|
57
|
+
function fenixIconHtml(icon) {
|
|
58
|
+
const value = (icon ?? "").trim();
|
|
59
|
+
if (!value) return "";
|
|
60
|
+
if (isFenixIconName(value)) {
|
|
61
|
+
return `<i class="fx-icon" aria-hidden="true">${value}</i>`;
|
|
62
|
+
}
|
|
63
|
+
return `<span aria-hidden="true">${esc(value)}</span>`;
|
|
64
|
+
}
|
|
65
|
+
export {
|
|
66
|
+
FENIX_ICON_FONT_FAMILY as F,
|
|
67
|
+
FENIX_ICON_BASE_CSS as a,
|
|
68
|
+
stripTags as b,
|
|
69
|
+
esc as e,
|
|
70
|
+
fenixIconHtml as f,
|
|
71
|
+
isFenixIconName as i,
|
|
72
|
+
sanitizeData as s
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=base-css-Be_r8aTo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-css-Be_r8aTo.js","sources":["../../src/core/sanitize.ts","../../src/icons/base-css.ts"],"sourcesContent":["/**\n * Sanitização de strings para injeção segura em HTML.\n *\n * Previne XSS ao escapar caracteres especiais antes de usar innerHTML.\n * Use SEMPRE que dados externos (API, usuário) forem injetados no DOM.\n */\n\n/** Caracteres que devem ser escapados para prevenir XSS. */\nconst HTML_ENTITIES: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n '/': '/',\n '`': '`',\n '=': '=',\n};\n\n/** Regex que captura todos os caracteres perigosos. */\nconst SANITIZE_REGEX = /[&<>\"'`=/]/g;\n\n/**\n * Escapa caracteres especiais de HTML para prevenir XSS.\n *\n * @param value - Valor a ser escapado (convertido para string)\n * @returns String segura para innerHTML\n *\n * @example\n * // Seguro contra XSS\n * const userInput = '<img src=x onerror=alert(1)>';\n * element.innerHTML = `<div>${esc(userInput)}</div>`;\n * // Resultado: <img src=x onerror=alert(1)>\n */\nexport function esc(value: unknown): string {\n if (value == null) return '';\n return String(value).replace(SANITIZE_REGEX, (char) => HTML_ENTITIES[char] || char);\n}\n\n/**\n * Remove todas as tags HTML da string (strip tags).\n * Use quando quiser apenas texto puro, sem nenhuma marcação.\n *\n * @param html - String potencialmente contendo HTML\n * @returns String sem tags HTML\n */\nexport function stripTags(html: string): string {\n return html.replace(/<[^>]*>/g, '');\n}\n\n/**\n * Sanitiza um objeto de dados recursivamente.\n * Útil para sanitizar arrays de registros antes de renderizar.\n *\n * @param data - Objeto ou array a ser sanitizado\n * @returns Cópia sanitizada do objeto\n */\nexport function sanitizeData<T>(data: T): T {\n if (data == null) return data;\n if (typeof data === 'string') return esc(data) as unknown as T;\n if (Array.isArray(data)) return data.map(sanitizeData) as unknown as T;\n if (typeof data === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data as Record<string, unknown>)) {\n result[key] = sanitizeData(value);\n }\n return result as T;\n }\n return data;\n}\n","/**\n * CSS base da biblioteca de ícones — módulo LEVE (não importa a lista de nomes\n * nem dispara o carregamento da fonte). Usado dentro dos Shadow DOMs dos\n * componentes que suportam o atributo `icon`.\n *\n * O @font-face em si é injetado no <head> por `@wrrdev/fenix-ui/icons`\n * (ver ./index.ts) — @font-face registrado no documento também se aplica\n * dentro dos Shadow DOMs.\n */\n\nimport { esc } from '../core/sanitize';\n\n/** Nome da família de fontes dos ícones (self-hosted, sem dependência externa). */\nexport const FENIX_ICON_FONT_FAMILY = 'Fenix Icons';\n\n/** Família configurável via CSS custom property (fallback para o nome padrão). */\nexport const FENIX_ICON_FONT_VAR = `var(--fx-icon-font, ${FENIX_ICON_FONT_FAMILY})`;\n\n/**\n * Regras da classe `.fx-icon` para uso DENTRO de Shadow DOM\n * (concatene às `static styles` do componente).\n */\nexport const FENIX_ICON_BASE_CSS = `\n.fx-icon {\n font-family: ${FENIX_ICON_FONT_VAR};\n font-weight: normal;\n font-style: normal;\n line-height: 1;\n letter-spacing: normal;\n text-transform: none;\n display: inline-block;\n white-space: nowrap;\n word-wrap: normal;\n direction: ltr;\n font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;\n -webkit-font-smoothing: antialiased;\n text-rendering: optimizeLegibility;\n user-select: none;\n}\n.fx-icon.fx-icon-fill { font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 24; }\n.fx-icon.fx-icon-bold { font-variation-settings: 'FILL' 0, 'wght' 600, 'GRAD' 0, 'opsz' 24; }\n`;\n\n/** Verifica se o valor parece um nome de ícone da Fenix Icons (e não emoji/texto livre). */\nexport function isFenixIconName(value: string): boolean {\n return /^[a-z][a-z0-9_]*$/.test(value.trim());\n}\n\n/**\n * HTML do ícone para uso DENTRO de Shadow DOM (concatenar ao template).\n *\n * - Nome válido da Fenix Icons (ex.: `home`) → glifo via LIGADURA: o nome\n * vira o conteúdo de texto do elemento com a font-family de ícones.\n * Isso é obrigatório dentro de Shadow DOM: as regras\n * `.fx-icon-<nome>::before` são injetadas no <head> do documento e NÃO\n * atravessam a fronteira do shadow root (apenas o @font-face vale\n * dentro dele) — a classe vazia renderizaria um elemento sem glifo.\n * - Qualquer outro valor (emoji, texto) → texto puro escapado.\n *\n * A classe base `.fx-icon` é fornecida por `FENIX_ICON_BASE_CSS`\n * (concatenada às `static styles` do componente); o @font-face vem do\n * `@wrrdev/fenix-ui/icons` (injetado no <head>).\n */\nexport function fenixIconHtml(icon: string): string {\n const value = (icon ?? '').trim();\n if (!value) return '';\n if (isFenixIconName(value)) {\n return `<i class=\"fx-icon\" aria-hidden=\"true\">${value}</i>`;\n }\n return `<span aria-hidden=\"true\">${esc(value)}</span>`;\n}\n"],"names":[],"mappings":"AAQA,MAAM,gBAAwC;AAAA,EAC5C,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AACP;AAGA,MAAM,iBAAiB;AAchB,SAAS,IAAI,OAAwB;AAC1C,MAAI,SAAS,KAAM,QAAO;AAC1B,SAAO,OAAO,KAAK,EAAE,QAAQ,gBAAgB,CAAC,SAAS,cAAc,IAAI,KAAK,IAAI;AACpF;AASO,SAAS,UAAU,MAAsB;AAC9C,SAAO,KAAK,QAAQ,YAAY,EAAE;AACpC;AASO,SAAS,aAAgB,MAAY;AAC1C,MAAI,QAAQ,KAAM,QAAO;AACzB,MAAI,OAAO,SAAS,SAAU,QAAO,IAAI,IAAI;AAC7C,MAAI,MAAM,QAAQ,IAAI,EAAG,QAAO,KAAK,IAAI,YAAY;AACrD,MAAI,OAAO,SAAS,UAAU;AAC5B,UAAM,SAAkC,CAAA;AACxC,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAA+B,GAAG;AAC1E,aAAO,GAAG,IAAI,aAAa,KAAK;AAAA,IAClC;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;ACxDO,MAAM,yBAAyB;AAG/B,MAAM,sBAAsB,uBAAuB,sBAAsB;AAMzE,MAAM,sBAAsB;AAAA;AAAA,iBAElB,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoB7B,SAAS,gBAAgB,OAAwB;AACtD,SAAO,oBAAoB,KAAK,MAAM,KAAA,CAAM;AAC9C;AAiBO,SAAS,cAAc,MAAsB;AAClD,QAAM,SAAS,QAAQ,IAAI,KAAA;AAC3B,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,gBAAgB,KAAK,GAAG;AAC1B,WAAO,yCAAyC,KAAK;AAAA,EACvD;AACA,SAAO,4BAA4B,IAAI,KAAK,CAAC;AAC/C;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autocomplete.d.ts","sourceRoot":"","sources":["../../../src/components/autocomplete/autocomplete.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAK5C;;;;;;GAMG;AACH,qBAAa,cAAe,SAAQ,SAAS;IAC3C,OAAgB,MAAM,
|
|
1
|
+
{"version":3,"file":"autocomplete.d.ts","sourceRoot":"","sources":["../../../src/components/autocomplete/autocomplete.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAK5C;;;;;;GAMG;AACH,qBAAa,cAAe,SAAQ,SAAS;IAC3C,OAAgB,MAAM,SA4EpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAIjD;IAED,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,CAAC,EAAE,MAAM,EAAmC;IACrD,IAAI,KAAK,IAAI,MAAM,CAAkC;IACrD,IAAI,KAAK,CAAC,CAAC,EAAE,MAAM,EAAoC;IAEvD,IAAI,MAAM,IAAI,MAAM,EAAE,CAOrB;IACD;2DACuD;IACvD,IAAI,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EAElC;IAED,OAAO,CAAC,WAAW,CAAC,CAAqB;cAEtB,iBAAiB,IAAI,IAAI;cAQzB,oBAAoB,IAAI,IAAI;cAM5B,MAAM,IAAI,IAAI;CA6DlC;AAED,wBAAgB,oBAAoB,IAAI,OAAO,cAAc,CAE5D"}
|
|
@@ -4,7 +4,7 @@ import { defineElement } from "../../core/define.js";
|
|
|
4
4
|
import { esc } from "../../core/sanitize.js";
|
|
5
5
|
const _FxAutocomplete = class _FxAutocomplete extends FxElement {
|
|
6
6
|
static get observedAttributes() {
|
|
7
|
-
return ["size", "placeholder", "source", "disabled", "min-chars"];
|
|
7
|
+
return ["size", "placeholder", "source", "disabled", "min-chars", "error", "invalid", "success", "valid"];
|
|
8
8
|
}
|
|
9
9
|
get size() {
|
|
10
10
|
const s = this.getAttr("size", "md");
|
|
@@ -106,7 +106,7 @@ _FxAutocomplete.styles = css`
|
|
|
106
106
|
font-family: var(--fx-font-family);
|
|
107
107
|
font-size: var(--fx-font-size);
|
|
108
108
|
}
|
|
109
|
-
|
|
109
|
+
.field {
|
|
110
110
|
font-family: inherit;
|
|
111
111
|
font-size: inherit;
|
|
112
112
|
font-weight: var(--fx-font-weight);
|
|
@@ -122,15 +122,29 @@ _FxAutocomplete.styles = css`
|
|
|
122
122
|
border-color var(--fx-motion-duration-normal) var(--fx-motion-easing),
|
|
123
123
|
box-shadow var(--fx-motion-duration-normal) var(--fx-motion-easing);
|
|
124
124
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
.field::placeholder { color: var(--fx-text-muted); opacity: 1; }
|
|
126
|
+
.field:hover { border-color: var(--fx-border-hover); }
|
|
127
|
+
.field:focus-visible {
|
|
128
128
|
outline: none;
|
|
129
129
|
border-color: var(--fx-color-primary);
|
|
130
130
|
box-shadow: var(--fx-effect-focus-ring, none);
|
|
131
131
|
}
|
|
132
|
-
:host([size='sm'])
|
|
133
|
-
:host([size='lg'])
|
|
132
|
+
:host([size='sm']) .field { width: 220px; min-height: var(--fx-size-sm); padding: var(--fx-space-sm) var(--fx-space-md); }
|
|
133
|
+
:host([size='lg']) .field { width: 300px; min-height: var(--fx-size-lg); }
|
|
134
|
+
/* Full width: o host estica até o pai e o campo interno acompanha. */
|
|
135
|
+
:host([full]) { display: block; width: 100%; }
|
|
136
|
+
:host([full]) .field { width: 100%; }
|
|
137
|
+
/* Validação: sobrescrevem a borda/foco via token do preset. */
|
|
138
|
+
:host([error]) .field,
|
|
139
|
+
:host([invalid]) .field {
|
|
140
|
+
border-color: var(--fx-color-danger, #dc2626);
|
|
141
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--fx-color-danger, #dc2626) 18%, transparent);
|
|
142
|
+
}
|
|
143
|
+
:host([success]) .field,
|
|
144
|
+
:host([valid]) .field {
|
|
145
|
+
border-color: var(--fx-color-success, #16a34a);
|
|
146
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--fx-color-success, #16a34a) 18%, transparent);
|
|
147
|
+
}
|
|
134
148
|
.list {
|
|
135
149
|
position: absolute;
|
|
136
150
|
top: calc(100% + 4px);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autocomplete.js","sources":["../../../src/components/autocomplete/autocomplete.ts"],"sourcesContent":["import { FxElement } from '../../core/base';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\nimport { esc } from '../../core/sanitize';\n\n/**\n * <fx-autocomplete> — Campo de texto com sugestões filtradas.\n *\n * Atributos: value, size, placeholder, source (JSON array de strings),\n * disabled, min-chars (padrão 1).\n * Evento: `select` (composed, detail: { value }).\n */\nexport class FxAutocomplete extends FxElement {\n static override styles = css`\n :host {\n position: relative;\n display: inline-block;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n }\n input {\n font-family: inherit;\n font-size: inherit;\n font-weight: var(--fx-font-weight);\n color: var(--fx-text-default);\n background-color: var(--fx-surface-background);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-md);\n padding: var(--fx-space-md) var(--fx-space-lg);\n width: 260px;\n min-height: var(--fx-size-md);\n box-sizing: border-box;\n transition:\n border-color var(--fx-motion-duration-normal) var(--fx-motion-easing),\n box-shadow var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n input::placeholder { color: var(--fx-text-muted); opacity: 1; }\n input:hover { border-color: var(--fx-border-hover); }\n input:focus-visible {\n outline: none;\n border-color: var(--fx-color-primary);\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n :host([size='sm']) input { width: 220px; min-height: var(--fx-size-sm); padding: var(--fx-space-sm) var(--fx-space-md); }\n :host([size='lg']) input { width: 300px; min-height: var(--fx-size-lg); }\n .list {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n right: 0;\n background: var(--fx-surface-background);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-md);\n box-shadow: var(--fx-shadow-lg);\n z-index: var(--fx-z-dropdown, 1000);\n max-height: 220px;\n overflow-y: auto;\n display: none;\n }\n :host([open]) .list { display: block; }\n .opt {\n display: block;\n width: 100%;\n text-align: left;\n border: none;\n background: transparent;\n font-family: inherit;\n font-size: inherit;\n color: var(--fx-text-default);\n padding: var(--fx-space-sm) var(--fx-space-md);\n cursor: pointer;\n transition: background var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n .opt:hover { background: color-mix(in srgb, var(--fx-color-primary) 12%, var(--fx-surface-background)); color: var(--fx-color-primary); }\n .empty { color: var(--fx-text-muted); font-size: calc(var(--fx-font-size) - 2px); padding: var(--fx-space-sm) var(--fx-space-md); }\n `;\n\n static override get observedAttributes(): string[] {\n return ['size', 'placeholder', 'source', 'disabled', 'min-chars'];\n }\n\n get size(): string {\n const s = this.getAttr('size', 'md');\n return s === 'sm' || s === 'lg' ? s : 'md';\n }\n set size(v: string) { this.setAttribute('size', v); }\n get value(): string { return this.getAttr('value'); }\n set value(v: string) { this.setAttribute('value', v); }\n\n get source(): string[] {\n try {\n const raw = JSON.parse(this.getAttr('source', '[]'));\n return Array.isArray(raw) ? raw.map(String) : [];\n } catch {\n return [];\n }\n }\n /** Setter necessário para o Vue (patchDOMProp seta `source` como\n * propriedade, pois o getter existe no prototype). */\n set source(value: string | string[]) {\n this.setAttribute('source', typeof value === 'string' ? value : JSON.stringify(value));\n }\n\n private docListener?: (e: Event) => void;\n\n protected override connectedCallback(): void {\n super.connectedCallback();\n this.docListener = (e: Event) => {\n if (!this.contains(e.target as Node)) this.toggleAttr('open', false);\n };\n document.addEventListener('click', this.docListener);\n }\n\n protected override disconnectedCallback(): void {\n super.disconnectedCallback();\n if (this.docListener) document.removeEventListener('click', this.docListener);\n this.docListener = undefined;\n }\n\n protected override render(): void {\n const placeholder = this.getAttr('placeholder');\n\n this.setTemplate(`\n <input class=\"field\" part=\"input\" type=\"text\"\n ${placeholder ? `placeholder=\"${esc(placeholder)}\"` : ''} autocomplete=\"off\"/>\n <div class=\"list\" part=\"list\" role=\"listbox\"></div>\n `);\n\n const field = this.root.querySelector<HTMLInputElement>('.field');\n if (!field) return;\n field.value = this.value;\n if (this.hasAttr('disabled')) field.setAttribute('disabled', '');\n\n const list = this.root.querySelector<HTMLElement>('.list');\n const minChars = Number(this.getAttr('min-chars', '1')) || 1;\n\n const openList = (): void => {\n if (!list) return;\n const q = field.value.toLowerCase();\n const matches = this.source.filter((s) => s.toLowerCase().includes(q)).slice(0, 8);\n list.innerHTML = matches.length\n ? matches.map((m) => `<button type=\"button\" class=\"opt\" role=\"option\" data-v=\"${esc(m)}\">${esc(m)}</button>`).join('')\n : '<div class=\"empty\">Nenhum resultado</div>';\n list.querySelectorAll<HTMLButtonElement>('.opt').forEach((btn) => {\n btn.addEventListener('click', () => {\n field.value = btn.dataset.v ?? '';\n this.value = field.value;\n this.toggleAttr('open', false);\n this.dispatchEvent(new CustomEvent('select', {\n bubbles: true, composed: true, detail: { value: this.value },\n }));\n });\n });\n this.toggleAttr('open', true);\n };\n\n // Reabrir com foco sem re-render (evita perder digitação)\n field.addEventListener('input', () => {\n this.value = field.value;\n openList();\n });\n field.addEventListener('focus', () => { if (field.value.length >= minChars) openList(); });\n // Navegação por teclado: setas movem o foco entre as sugestões; ESC fecha.\n field.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') {\n this.toggleAttr('open', false);\n return;\n }\n if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') return;\n e.preventDefault();\n if (!this.hasAttr('open')) openList();\n const opts = Array.from(this.root.querySelectorAll<HTMLButtonElement>('.opt'));\n if (!opts.length) return;\n const current = opts.indexOf(this.root.activeElement as HTMLButtonElement);\n const next = e.key === 'ArrowDown'\n ? (current === -1 ? 0 : Math.min(current + 1, opts.length - 1))\n : (current === -1 ? opts.length - 1 : Math.max(current - 1, 0));\n opts[next]?.focus();\n });\n }\n}\n\nexport function defineFxAutocomplete(): typeof FxAutocomplete {\n return defineElement('fx-autocomplete', FxAutocomplete);\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"autocomplete.js","sources":["../../../src/components/autocomplete/autocomplete.ts"],"sourcesContent":["import { FxElement } from '../../core/base';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\nimport { esc } from '../../core/sanitize';\n\n/**\n * <fx-autocomplete> — Campo de texto com sugestões filtradas.\n *\n * Atributos: value, size, placeholder, source (JSON array de strings),\n * disabled, min-chars (padrão 1).\n * Evento: `select` (composed, detail: { value }).\n */\nexport class FxAutocomplete extends FxElement {\n static override styles = css`\n :host {\n position: relative;\n display: inline-block;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n }\n .field {\n font-family: inherit;\n font-size: inherit;\n font-weight: var(--fx-font-weight);\n color: var(--fx-text-default);\n background-color: var(--fx-surface-background);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-md);\n padding: var(--fx-space-md) var(--fx-space-lg);\n width: 260px;\n min-height: var(--fx-size-md);\n box-sizing: border-box;\n transition:\n border-color var(--fx-motion-duration-normal) var(--fx-motion-easing),\n box-shadow var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n .field::placeholder { color: var(--fx-text-muted); opacity: 1; }\n .field:hover { border-color: var(--fx-border-hover); }\n .field:focus-visible {\n outline: none;\n border-color: var(--fx-color-primary);\n box-shadow: var(--fx-effect-focus-ring, none);\n }\n :host([size='sm']) .field { width: 220px; min-height: var(--fx-size-sm); padding: var(--fx-space-sm) var(--fx-space-md); }\n :host([size='lg']) .field { width: 300px; min-height: var(--fx-size-lg); }\n /* Full width: o host estica até o pai e o campo interno acompanha. */\n :host([full]) { display: block; width: 100%; }\n :host([full]) .field { width: 100%; }\n /* Validação: sobrescrevem a borda/foco via token do preset. */\n :host([error]) .field,\n :host([invalid]) .field {\n border-color: var(--fx-color-danger, #dc2626);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--fx-color-danger, #dc2626) 18%, transparent);\n }\n :host([success]) .field,\n :host([valid]) .field {\n border-color: var(--fx-color-success, #16a34a);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--fx-color-success, #16a34a) 18%, transparent);\n }\n .list {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n right: 0;\n background: var(--fx-surface-background);\n border: 1px solid var(--fx-border-default);\n border-radius: var(--fx-radius-md);\n box-shadow: var(--fx-shadow-lg);\n z-index: var(--fx-z-dropdown, 1000);\n max-height: 220px;\n overflow-y: auto;\n display: none;\n }\n :host([open]) .list { display: block; }\n .opt {\n display: block;\n width: 100%;\n text-align: left;\n border: none;\n background: transparent;\n font-family: inherit;\n font-size: inherit;\n color: var(--fx-text-default);\n padding: var(--fx-space-sm) var(--fx-space-md);\n cursor: pointer;\n transition: background var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n .opt:hover { background: color-mix(in srgb, var(--fx-color-primary) 12%, var(--fx-surface-background)); color: var(--fx-color-primary); }\n .empty { color: var(--fx-text-muted); font-size: calc(var(--fx-font-size) - 2px); padding: var(--fx-space-sm) var(--fx-space-md); }\n `;\n\n static override get observedAttributes(): string[] {\n // `error`/`success` (+ aliases `invalid`/`valid`) PRECISAM ser observados:\n // é assim que o toggle no playground / setAttribute no submit re-renderiza.\n return ['size', 'placeholder', 'source', 'disabled', 'min-chars', 'error', 'invalid', 'success', 'valid'];\n }\n\n get size(): string {\n const s = this.getAttr('size', 'md');\n return s === 'sm' || s === 'lg' ? s : 'md';\n }\n set size(v: string) { this.setAttribute('size', v); }\n get value(): string { return this.getAttr('value'); }\n set value(v: string) { this.setAttribute('value', v); }\n\n get source(): string[] {\n try {\n const raw = JSON.parse(this.getAttr('source', '[]'));\n return Array.isArray(raw) ? raw.map(String) : [];\n } catch {\n return [];\n }\n }\n /** Setter necessário para o Vue (patchDOMProp seta `source` como\n * propriedade, pois o getter existe no prototype). */\n set source(value: string | string[]) {\n this.setAttribute('source', typeof value === 'string' ? value : JSON.stringify(value));\n }\n\n private docListener?: (e: Event) => void;\n\n protected override connectedCallback(): void {\n super.connectedCallback();\n this.docListener = (e: Event) => {\n if (!this.contains(e.target as Node)) this.toggleAttr('open', false);\n };\n document.addEventListener('click', this.docListener);\n }\n\n protected override disconnectedCallback(): void {\n super.disconnectedCallback();\n if (this.docListener) document.removeEventListener('click', this.docListener);\n this.docListener = undefined;\n }\n\n protected override render(): void {\n const placeholder = this.getAttr('placeholder');\n\n this.setTemplate(`\n <input class=\"field\" part=\"input\" type=\"text\"\n ${placeholder ? `placeholder=\"${esc(placeholder)}\"` : ''} autocomplete=\"off\"/>\n <div class=\"list\" part=\"list\" role=\"listbox\"></div>\n `);\n\n const field = this.root.querySelector<HTMLInputElement>('.field');\n if (!field) return;\n field.value = this.value;\n if (this.hasAttr('disabled')) field.setAttribute('disabled', '');\n\n const list = this.root.querySelector<HTMLElement>('.list');\n const minChars = Number(this.getAttr('min-chars', '1')) || 1;\n\n const openList = (): void => {\n if (!list) return;\n const q = field.value.toLowerCase();\n const matches = this.source.filter((s) => s.toLowerCase().includes(q)).slice(0, 8);\n list.innerHTML = matches.length\n ? matches.map((m) => `<button type=\"button\" class=\"opt\" role=\"option\" data-v=\"${esc(m)}\">${esc(m)}</button>`).join('')\n : '<div class=\"empty\">Nenhum resultado</div>';\n list.querySelectorAll<HTMLButtonElement>('.opt').forEach((btn) => {\n btn.addEventListener('click', () => {\n field.value = btn.dataset.v ?? '';\n this.value = field.value;\n this.toggleAttr('open', false);\n this.dispatchEvent(new CustomEvent('select', {\n bubbles: true, composed: true, detail: { value: this.value },\n }));\n });\n });\n this.toggleAttr('open', true);\n };\n\n // Reabrir com foco sem re-render (evita perder digitação)\n field.addEventListener('input', () => {\n this.value = field.value;\n openList();\n });\n field.addEventListener('focus', () => { if (field.value.length >= minChars) openList(); });\n // Navegação por teclado: setas movem o foco entre as sugestões; ESC fecha.\n field.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') {\n this.toggleAttr('open', false);\n return;\n }\n if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') return;\n e.preventDefault();\n if (!this.hasAttr('open')) openList();\n const opts = Array.from(this.root.querySelectorAll<HTMLButtonElement>('.opt'));\n if (!opts.length) return;\n const current = opts.indexOf(this.root.activeElement as HTMLButtonElement);\n const next = e.key === 'ArrowDown'\n ? (current === -1 ? 0 : Math.min(current + 1, opts.length - 1))\n : (current === -1 ? opts.length - 1 : Math.max(current - 1, 0));\n opts[next]?.focus();\n });\n }\n}\n\nexport function defineFxAutocomplete(): typeof FxAutocomplete {\n return defineElement('fx-autocomplete', FxAutocomplete);\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -2,8 +2,10 @@ import { FxElement } from '../../core/base';
|
|
|
2
2
|
/**
|
|
3
3
|
* <fx-button> — Componente de ação (piloto).
|
|
4
4
|
*
|
|
5
|
-
* Atributos: variant, size, disabled, loading, type, full
|
|
6
|
-
*
|
|
5
|
+
* Atributos: variant, size, disabled, loading, type, full (largura 100%
|
|
6
|
+
* acompanhando o elemento pai), icon (nome do glifo Fenix Icons ou
|
|
7
|
+
* emoji/texto livre) e icon-pos (left|right, padrão left).
|
|
8
|
+
* Slots: default (rótulo), `icon` (ícone customizado — vence o atributo).
|
|
7
9
|
* Evento: `click` nativo atravessa o Shadow DOM (composed) — ouça no host.
|
|
8
10
|
*/
|
|
9
11
|
export declare class FxButton extends FxElement {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../../src/components/button/button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../../src/components/button/button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAK5C;;;;;;;;GAQG;AACH,qBAAa,QAAS,SAAQ,SAAS;IACrC,OAAgB,MAAM,SAiHpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,+BAA+B;IAC/B,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAErB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAE1B;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAEzB;cAEkB,MAAM,IAAI,IAAI;cAkDd,iBAAiB,IAAI,IAAI;cAKzB,oBAAoB,IAAI,IAAI;IAK/C;;;;OAIG;IACH,OAAO,CAAC,WAAW,CAmBjB;CACH;AAED,wBAAgB,cAAc,IAAI,OAAO,QAAQ,CAEhD"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FxElement } from "../../core/base.js";
|
|
2
2
|
import { css } from "../../core/css.js";
|
|
3
3
|
import { defineElement } from "../../core/define.js";
|
|
4
|
+
import { FENIX_ICON_BASE_CSS, fenixIconHtml } from "../../icons/base-css.js";
|
|
4
5
|
const _FxButton = class _FxButton extends FxElement {
|
|
5
6
|
constructor() {
|
|
6
7
|
super(...arguments);
|
|
@@ -22,7 +23,7 @@ const _FxButton = class _FxButton extends FxElement {
|
|
|
22
23
|
};
|
|
23
24
|
}
|
|
24
25
|
static get observedAttributes() {
|
|
25
|
-
return ["variant", "size", "disabled", "loading", "type", "full"];
|
|
26
|
+
return ["variant", "size", "disabled", "loading", "type", "full", "icon", "icon-pos"];
|
|
26
27
|
}
|
|
27
28
|
/** Tamanho. Padrão: `'md'`. */
|
|
28
29
|
get size() {
|
|
@@ -48,21 +49,28 @@ const _FxButton = class _FxButton extends FxElement {
|
|
|
48
49
|
const loading = this.hasAttr("loading");
|
|
49
50
|
const disabled = this.hasAttr("disabled") || loading;
|
|
50
51
|
const btnType = this.getAttr("type", "button");
|
|
52
|
+
const icon = this.getAttr("icon");
|
|
53
|
+
const iconRight = this.getAttr("icon-pos", "left") === "right";
|
|
54
|
+
const slottedIcon = this.querySelector('[slot="icon"]');
|
|
55
|
+
const iconContent = slottedIcon ? '<slot name="icon"></slot>' : icon ? fenixIconHtml(icon) : "";
|
|
51
56
|
this.setTemplate(`
|
|
52
57
|
<button class="btn" part="button" type="${btnType}">
|
|
53
|
-
|
|
58
|
+
${!iconRight ? `<span class="btn__icon" part="icon">${iconContent}</span>` : ""}
|
|
54
59
|
<span class="btn__label" part="label"><slot></slot></span>
|
|
60
|
+
${iconRight ? `<span class="btn__icon" part="icon">${iconContent}</span>` : ""}
|
|
55
61
|
${loading ? '<span class="btn__spinner" aria-hidden="true"></span>' : ""}
|
|
56
62
|
</button>
|
|
57
63
|
`);
|
|
58
64
|
const btn = this.root.querySelector(".btn");
|
|
59
65
|
if (!btn) return;
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
const hasIcon = iconContent.trim() !== "";
|
|
67
|
+
for (const wrap of this.root.querySelectorAll(".btn__icon")) {
|
|
68
|
+
wrap.toggleAttribute("hidden", !hasIcon);
|
|
69
|
+
}
|
|
70
|
+
const hasLabel = [...this.childNodes ?? []].some(
|
|
71
|
+
(n) => n.nodeType === Node.ELEMENT_NODE ? n.getAttribute?.("slot") !== "icon" : (n.textContent ?? "").trim() !== ""
|
|
72
|
+
);
|
|
73
|
+
btn.classList.toggle("btn--icon-only", !hasLabel && hasIcon);
|
|
66
74
|
if (disabled) {
|
|
67
75
|
btn.setAttribute("disabled", "");
|
|
68
76
|
btn.setAttribute("aria-disabled", "true");
|
|
@@ -83,10 +91,18 @@ const _FxButton = class _FxButton extends FxElement {
|
|
|
83
91
|
}
|
|
84
92
|
};
|
|
85
93
|
_FxButton.styles = css`
|
|
94
|
+
${FENIX_ICON_BASE_CSS}
|
|
86
95
|
:host {
|
|
87
96
|
display: inline-block;
|
|
88
97
|
vertical-align: middle;
|
|
89
98
|
}
|
|
99
|
+
/* Full width: o host inline-block é shrink-to-fit, então precisa virar
|
|
100
|
+
block para o 100% do .btn interno ter contra o que esticar. */
|
|
101
|
+
:host([full]) {
|
|
102
|
+
display: block;
|
|
103
|
+
width: 100%;
|
|
104
|
+
max-width: 100%;
|
|
105
|
+
}
|
|
90
106
|
.btn {
|
|
91
107
|
display: inline-flex;
|
|
92
108
|
align-items: center;
|
|
@@ -138,9 +154,13 @@ _FxButton.styles = css`
|
|
|
138
154
|
:host([variant='outline']) .btn:hover { background: var(--fx-surface-surface-hover); filter: none; }
|
|
139
155
|
|
|
140
156
|
/* Ícone e spinner */
|
|
141
|
-
.btn__icon { display: inline-flex; }
|
|
157
|
+
.btn__icon { display: inline-flex; align-items: center; font-size: var(--fx-button-icon-size, calc(var(--fx-font-size) + 6px)); line-height: 1; }
|
|
142
158
|
.btn__icon[hidden] { display: none; }
|
|
143
159
|
.btn__label { display: inline-flex; align-items: center; justify-content: center; }
|
|
160
|
+
/* Icon-only: a label some (o gap do flex some junto) e o botão vira
|
|
161
|
+
quadrado com padding simétrico — ícone perfeitamente centralizado. */
|
|
162
|
+
.btn--icon-only { padding-left: var(--fx-space-md); padding-right: var(--fx-space-md); }
|
|
163
|
+
.btn--icon-only .btn__label { display: none; }
|
|
144
164
|
.btn[hidden] { display: none; }
|
|
145
165
|
.btn__spinner {
|
|
146
166
|
width: 1em;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.js","sources":["../../../src/components/button/button.ts"],"sourcesContent":["import { FxElement } from '../../core/base';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\n\n/**\n * <fx-button> — Componente de ação (piloto).\n *\n * Atributos: variant, size, disabled, loading, type, full.\n * Slots: default (rótulo), `icon` (ícone opcional).\n * Evento: `click` nativo atravessa o Shadow DOM (composed) — ouça no host.\n */\nexport class FxButton extends FxElement {\n static override styles = css`\n :host {\n display: inline-block;\n vertical-align: middle;\n }\n .btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n gap: var(--fx-space-sm);\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n font-weight: var(--fx-font-weight);\n line-height: var(--fx-font-line-height);\n padding: var(--fx-space-md) var(--fx-space-xl);\n border-radius: var(--fx-radius-md);\n border: 1px solid transparent;\n color: #fff;\n background: var(--fx-color-primary);\n cursor: pointer;\n user-select: none;\n text-decoration: none;\n transition:\n background-color var(--fx-motion-duration-normal) var(--fx-motion-easing),\n border-color var(--fx-motion-duration-normal) var(--fx-motion-easing),\n transform var(--fx-motion-duration-fast) var(--fx-motion-easing),\n box-shadow var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n .btn:focus-visible {\n outline: 2px solid var(--fx-color-primary);\n outline-offset: 2px;\n }\n .btn:hover { filter: brightness(0.88); }\n .btn:active { transform: translateY(1px); }\n\n /* Tamanhos — alturas vindas dos tokens (personalizáveis por preset) */\n .btn { min-height: var(--fx-size-md); }\n :host([size='sm']) .btn { padding: var(--fx-space-sm) var(--fx-space-lg); font-size: var(--fx-font-size); min-height: var(--fx-size-sm); }\n :host([size='lg']) .btn { padding: var(--fx-space-lg) var(--fx-space-xl); font-size: calc(var(--fx-font-size) + 4px); min-height: var(--fx-size-lg); }\n\n /* Full width */\n :host([full]) .btn { width: 100%; }\n\n /* Variantes */\n :host([variant='secondary']) .btn { background: var(--fx-color-secondary); }\n :host([variant='success']) .btn { background: var(--fx-color-success); }\n :host([variant='warning']) .btn { background: var(--fx-color-warning); }\n :host([variant='danger']) .btn { background: var(--fx-color-danger); }\n :host([variant='info']) .btn { background: var(--fx-color-info); }\n :host([variant='ghost']) .btn { background: transparent; color: var(--fx-text-default); }\n :host([variant='ghost']) .btn:hover { background: var(--fx-surface-surface-hover); filter: none; }\n :host([variant='outline']) .btn { background: transparent; color: var(--fx-color-primary); border-color: var(--fx-color-primary); }\n :host([variant='outline']) .btn:hover { background: var(--fx-surface-surface-hover); filter: none; }\n\n /* Ícone e spinner */\n .btn__icon { display: inline-flex; }\n .btn__icon[hidden] { display: none; }\n .btn__label { display: inline-flex; align-items: center; justify-content: center; }\n .btn[hidden] { display: none; }\n .btn__spinner {\n width: 1em;\n height: 1em;\n border: 2px solid currentColor;\n border-top-color: transparent;\n border-radius: var(--fx-radius-full);\n animation: fx-spin var(--fx-motion-duration-normal) linear infinite;\n }\n @keyframes fx-spin { to { transform: rotate(360deg); } }\n\n /* Estados */\n :host([disabled]) .btn,\n :host([loading]) .btn {\n opacity: 0.55;\n cursor: not-allowed;\n pointer-events: none;\n }\n\n /* Efeito ripple (). Desative com effect.ripple: '0' no preset. */\n .btn {\n position: relative;\n overflow: hidden;\n }\n .btn__ripple {\n position: absolute;\n border-radius: var(--fx-radius-full);\n background: currentColor;\n opacity: 0.28;\n transform: scale(0);\n animation: fx-ripple 600ms var(--fx-motion-easing) forwards;\n pointer-events: none;\n }\n @keyframes fx-ripple {\n to {\n transform: scale(2.6);\n opacity: 0;\n }\n }\n @media (prefers-reduced-motion: reduce) {\n .btn__ripple { display: none; }\n }\n `;\n\n static override get observedAttributes(): string[] {\n return ['variant', 'size', 'disabled', 'loading', 'type', 'full'];\n }\n\n /** Tamanho. Padrão: `'md'`. */\n get size(): string {\n const s = this.getAttr('size', 'md');\n return s === 'sm' || s === 'lg' ? s : 'md';\n }\n set size(value: string) {\n this.setAttribute('size', value);\n }\n\n get disabled(): boolean {\n return this.hasAttr('disabled');\n }\n set disabled(value: boolean) {\n this.toggleAttr('disabled', Boolean(value));\n }\n\n get loading(): boolean {\n return this.hasAttr('loading');\n }\n set loading(value: boolean) {\n this.toggleAttr('loading', Boolean(value));\n }\n\n protected override render(): void {\n const loading = this.hasAttr('loading');\n const disabled = this.hasAttr('disabled') || loading;\n const btnType = this.getAttr('type', 'button');\n\n this.setTemplate(`\n <button class=\"btn\" part=\"button\" type=\"${btnType}\">\n <span class=\"btn__icon\" part=\"icon\"><slot name=\"icon\"></slot></span>\n <span class=\"btn__label\" part=\"label\"><slot></slot></span>\n ${loading ? '<span class=\"btn__spinner\" aria-hidden=\"true\"></span>' : ''}\n </button>\n `);\n\n const btn = this.root.querySelector<HTMLButtonElement>('.btn');\n if (!btn) return;\n\n // Esconde o container do ícone quando o slot está vazio,\n // evitando espaço fantasma do `gap` que descentraliza o rótulo.\n const iconSlot = btn.querySelector<HTMLSlotElement>('.btn__icon slot');\n const iconWrap = this.root.querySelector<HTMLElement>('.btn__icon');\n const hasIcon = Boolean(iconSlot?.assignedNodes({ flatten: true }).some((n) =>\n n.nodeType === Node.ELEMENT_NODE || (n.textContent ?? '').trim() !== '',\n ));\n iconWrap?.toggleAttribute('hidden', !hasIcon);\n if (disabled) {\n btn.setAttribute('disabled', '');\n btn.setAttribute('aria-disabled', 'true');\n } else {\n btn.removeAttribute('disabled');\n btn.removeAttribute('aria-disabled');\n }\n if (loading) btn.setAttribute('aria-busy', 'true');\n else btn.removeAttribute('aria-busy');\n }\n\n protected override connectedCallback(): void {\n super.connectedCallback();\n this.addEventListener('pointerdown', this.spawnRipple);\n }\n\n protected override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeEventListener('pointerdown', this.spawnRipple);\n }\n\n /**\n * Efeito ripple : cria um círculo que expande do ponto\n * de clique e desaparece. Respeita o token `effect.ripple` ('0' desativa)\n * e `prefers-reduced-motion` (via CSS).\n */\n private spawnRipple = (event: PointerEvent): void => {\n if (this.hasAttr('disabled') || this.hasAttr('loading')) return;\n const enabled = getComputedStyle(this)\n .getPropertyValue('--fx-effect-ripple')\n .trim();\n if (enabled === '0' || enabled === 'false') return;\n\n const btn = this.root.querySelector<HTMLElement>('.btn');\n if (!btn) return;\n\n const rect = btn.getBoundingClientRect();\n const size = Math.max(rect.width, rect.height);\n const ripple = document.createElement('span');\n ripple.className = 'btn__ripple';\n ripple.style.width = ripple.style.height = `${size}px`;\n ripple.style.left = `${event.clientX - rect.left - size / 2}px`;\n ripple.style.top = `${event.clientY - rect.top - size / 2}px`;\n btn.appendChild(ripple);\n ripple.addEventListener('animationend', () => ripple.remove());\n };\n}\n\nexport function defineFxButton(): typeof FxButton {\n return defineElement('fx-button', FxButton);\n}"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"button.js","sources":["../../../src/components/button/button.ts"],"sourcesContent":["import { FxElement } from '../../core/base';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\nimport { FENIX_ICON_BASE_CSS, fenixIconHtml } from '../../icons/base-css';\n\n/**\n * <fx-button> — Componente de ação (piloto).\n *\n * Atributos: variant, size, disabled, loading, type, full (largura 100%\n * acompanhando o elemento pai), icon (nome do glifo Fenix Icons ou\n * emoji/texto livre) e icon-pos (left|right, padrão left).\n * Slots: default (rótulo), `icon` (ícone customizado — vence o atributo).\n * Evento: `click` nativo atravessa o Shadow DOM (composed) — ouça no host.\n */\nexport class FxButton extends FxElement {\n static override styles = css`\n ${FENIX_ICON_BASE_CSS}\n :host {\n display: inline-block;\n vertical-align: middle;\n }\n /* Full width: o host inline-block é shrink-to-fit, então precisa virar\n block para o 100% do .btn interno ter contra o que esticar. */\n :host([full]) {\n display: block;\n width: 100%;\n max-width: 100%;\n }\n .btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n gap: var(--fx-space-sm);\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n font-weight: var(--fx-font-weight);\n line-height: var(--fx-font-line-height);\n padding: var(--fx-space-md) var(--fx-space-xl);\n border-radius: var(--fx-radius-md);\n border: 1px solid transparent;\n color: #fff;\n background: var(--fx-color-primary);\n cursor: pointer;\n user-select: none;\n text-decoration: none;\n transition:\n background-color var(--fx-motion-duration-normal) var(--fx-motion-easing),\n border-color var(--fx-motion-duration-normal) var(--fx-motion-easing),\n transform var(--fx-motion-duration-fast) var(--fx-motion-easing),\n box-shadow var(--fx-motion-duration-normal) var(--fx-motion-easing);\n }\n .btn:focus-visible {\n outline: 2px solid var(--fx-color-primary);\n outline-offset: 2px;\n }\n .btn:hover { filter: brightness(0.88); }\n .btn:active { transform: translateY(1px); }\n\n /* Tamanhos — alturas vindas dos tokens (personalizáveis por preset) */\n .btn { min-height: var(--fx-size-md); }\n :host([size='sm']) .btn { padding: var(--fx-space-sm) var(--fx-space-lg); font-size: var(--fx-font-size); min-height: var(--fx-size-sm); }\n :host([size='lg']) .btn { padding: var(--fx-space-lg) var(--fx-space-xl); font-size: calc(var(--fx-font-size) + 4px); min-height: var(--fx-size-lg); }\n\n /* Full width */\n :host([full]) .btn { width: 100%; }\n\n /* Variantes */\n :host([variant='secondary']) .btn { background: var(--fx-color-secondary); }\n :host([variant='success']) .btn { background: var(--fx-color-success); }\n :host([variant='warning']) .btn { background: var(--fx-color-warning); }\n :host([variant='danger']) .btn { background: var(--fx-color-danger); }\n :host([variant='info']) .btn { background: var(--fx-color-info); }\n :host([variant='ghost']) .btn { background: transparent; color: var(--fx-text-default); }\n :host([variant='ghost']) .btn:hover { background: var(--fx-surface-surface-hover); filter: none; }\n :host([variant='outline']) .btn { background: transparent; color: var(--fx-color-primary); border-color: var(--fx-color-primary); }\n :host([variant='outline']) .btn:hover { background: var(--fx-surface-surface-hover); filter: none; }\n\n /* Ícone e spinner */\n .btn__icon { display: inline-flex; align-items: center; font-size: var(--fx-button-icon-size, calc(var(--fx-font-size) + 6px)); line-height: 1; }\n .btn__icon[hidden] { display: none; }\n .btn__label { display: inline-flex; align-items: center; justify-content: center; }\n /* Icon-only: a label some (o gap do flex some junto) e o botão vira\n quadrado com padding simétrico — ícone perfeitamente centralizado. */\n .btn--icon-only { padding-left: var(--fx-space-md); padding-right: var(--fx-space-md); }\n .btn--icon-only .btn__label { display: none; }\n .btn[hidden] { display: none; }\n .btn__spinner {\n width: 1em;\n height: 1em;\n border: 2px solid currentColor;\n border-top-color: transparent;\n border-radius: var(--fx-radius-full);\n animation: fx-spin var(--fx-motion-duration-normal) linear infinite;\n }\n @keyframes fx-spin { to { transform: rotate(360deg); } }\n\n /* Estados */\n :host([disabled]) .btn,\n :host([loading]) .btn {\n opacity: 0.55;\n cursor: not-allowed;\n pointer-events: none;\n }\n\n /* Efeito ripple (). Desative com effect.ripple: '0' no preset. */\n .btn {\n position: relative;\n overflow: hidden;\n }\n .btn__ripple {\n position: absolute;\n border-radius: var(--fx-radius-full);\n background: currentColor;\n opacity: 0.28;\n transform: scale(0);\n animation: fx-ripple 600ms var(--fx-motion-easing) forwards;\n pointer-events: none;\n }\n @keyframes fx-ripple {\n to {\n transform: scale(2.6);\n opacity: 0;\n }\n }\n @media (prefers-reduced-motion: reduce) {\n .btn__ripple { display: none; }\n }\n `;\n\n static override get observedAttributes(): string[] {\n return ['variant', 'size', 'disabled', 'loading', 'type', 'full', 'icon', 'icon-pos'];\n }\n\n /** Tamanho. Padrão: `'md'`. */\n get size(): string {\n const s = this.getAttr('size', 'md');\n return s === 'sm' || s === 'lg' ? s : 'md';\n }\n set size(value: string) {\n this.setAttribute('size', value);\n }\n\n get disabled(): boolean {\n return this.hasAttr('disabled');\n }\n set disabled(value: boolean) {\n this.toggleAttr('disabled', Boolean(value));\n }\n\n get loading(): boolean {\n return this.hasAttr('loading');\n }\n set loading(value: boolean) {\n this.toggleAttr('loading', Boolean(value));\n }\n\n protected override render(): void {\n const loading = this.hasAttr('loading');\n const disabled = this.hasAttr('disabled') || loading;\n const btnType = this.getAttr('type', 'button');\n const icon = this.getAttr('icon');\n const iconRight = this.getAttr('icon-pos', 'left') === 'right';\n // Slot `icon` vence o atributo `icon`; sem nenhum dos dois, sem ícone.\n const slottedIcon = this.querySelector<HTMLElement>('[slot=\"icon\"]');\n const iconContent = slottedIcon\n ? '<slot name=\"icon\"></slot>'\n : (icon ? fenixIconHtml(icon) : '');\n\n this.setTemplate(`\n <button class=\"btn\" part=\"button\" type=\"${btnType}\">\n ${!iconRight ? `<span class=\"btn__icon\" part=\"icon\">${iconContent}</span>` : ''}\n <span class=\"btn__label\" part=\"label\"><slot></slot></span>\n ${iconRight ? `<span class=\"btn__icon\" part=\"icon\">${iconContent}</span>` : ''}\n ${loading ? '<span class=\"btn__spinner\" aria-hidden=\"true\"></span>' : ''}\n </button>\n `);\n\n const btn = this.root.querySelector<HTMLButtonElement>('.btn');\n if (!btn) return;\n\n // Esconde o container do ícone quando vazio, evitando espaço fantasma\n // do `gap` que descentraliza o rótulo.\n const hasIcon = iconContent.trim() !== '';\n for (const wrap of this.root.querySelectorAll<HTMLElement>('.btn__icon')) {\n wrap.toggleAttribute('hidden', !hasIcon);\n }\n\n // Icon-only: sem texto no slot default, o botão vira quadrado\n // (padding simétrico) em vez de retangular largo.\n const hasLabel = [...(this.childNodes ?? [])].some((n) =>\n n.nodeType === Node.ELEMENT_NODE\n ? (n as Element).getAttribute?.('slot') !== 'icon'\n : (n.textContent ?? '').trim() !== '',\n );\n btn.classList.toggle('btn--icon-only', !hasLabel && hasIcon);\n if (disabled) {\n btn.setAttribute('disabled', '');\n btn.setAttribute('aria-disabled', 'true');\n } else {\n btn.removeAttribute('disabled');\n btn.removeAttribute('aria-disabled');\n }\n if (loading) btn.setAttribute('aria-busy', 'true');\n else btn.removeAttribute('aria-busy');\n }\n\n protected override connectedCallback(): void {\n super.connectedCallback();\n this.addEventListener('pointerdown', this.spawnRipple);\n }\n\n protected override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeEventListener('pointerdown', this.spawnRipple);\n }\n\n /**\n * Efeito ripple : cria um círculo que expande do ponto\n * de clique e desaparece. Respeita o token `effect.ripple` ('0' desativa)\n * e `prefers-reduced-motion` (via CSS).\n */\n private spawnRipple = (event: PointerEvent): void => {\n if (this.hasAttr('disabled') || this.hasAttr('loading')) return;\n const enabled = getComputedStyle(this)\n .getPropertyValue('--fx-effect-ripple')\n .trim();\n if (enabled === '0' || enabled === 'false') return;\n\n const btn = this.root.querySelector<HTMLElement>('.btn');\n if (!btn) return;\n\n const rect = btn.getBoundingClientRect();\n const size = Math.max(rect.width, rect.height);\n const ripple = document.createElement('span');\n ripple.className = 'btn__ripple';\n ripple.style.width = ripple.style.height = `${size}px`;\n ripple.style.left = `${event.clientX - rect.left - size / 2}px`;\n ripple.style.top = `${event.clientY - rect.top - size / 2}px`;\n btn.appendChild(ripple);\n ripple.addEventListener('animationend', () => ripple.remove());\n };\n}\n\nexport function defineFxButton(): typeof FxButton {\n return defineElement('fx-button', FxButton);\n}"],"names":[],"mappings":";;;;AAcO,MAAM,YAAN,MAAM,kBAAiB,UAAU;AAAA,EAAjC,cAAA;AAAA,UAAA,GAAA,SAAA;AAgNL,SAAQ,cAAc,CAAC,UAA8B;AACnD,UAAI,KAAK,QAAQ,UAAU,KAAK,KAAK,QAAQ,SAAS,EAAG;AACzD,YAAM,UAAU,iBAAiB,IAAI,EAClC,iBAAiB,oBAAoB,EACrC,KAAA;AACH,UAAI,YAAY,OAAO,YAAY,QAAS;AAE5C,YAAM,MAAM,KAAK,KAAK,cAA2B,MAAM;AACvD,UAAI,CAAC,IAAK;AAEV,YAAM,OAAO,IAAI,sBAAA;AACjB,YAAM,OAAO,KAAK,IAAI,KAAK,OAAO,KAAK,MAAM;AAC7C,YAAM,SAAS,SAAS,cAAc,MAAM;AAC5C,aAAO,YAAY;AACnB,aAAO,MAAM,QAAQ,OAAO,MAAM,SAAS,GAAG,IAAI;AAClD,aAAO,MAAM,OAAO,GAAG,MAAM,UAAU,KAAK,OAAO,OAAO,CAAC;AAC3D,aAAO,MAAM,MAAM,GAAG,MAAM,UAAU,KAAK,MAAM,OAAO,CAAC;AACzD,UAAI,YAAY,MAAM;AACtB,aAAO,iBAAiB,gBAAgB,MAAM,OAAO,QAAQ;AAAA,IAC/D;AAAA,EAAA;AAAA,EA/GA,WAAoB,qBAA+B;AACjD,WAAO,CAAC,WAAW,QAAQ,YAAY,WAAW,QAAQ,QAAQ,QAAQ,UAAU;AAAA,EACtF;AAAA;AAAA,EAGA,IAAI,OAAe;AACjB,UAAM,IAAI,KAAK,QAAQ,QAAQ,IAAI;AACnC,WAAO,MAAM,QAAQ,MAAM,OAAO,IAAI;AAAA,EACxC;AAAA,EACA,IAAI,KAAK,OAAe;AACtB,SAAK,aAAa,QAAQ,KAAK;AAAA,EACjC;AAAA,EAEA,IAAI,WAAoB;AACtB,WAAO,KAAK,QAAQ,UAAU;AAAA,EAChC;AAAA,EACA,IAAI,SAAS,OAAgB;AAC3B,SAAK,WAAW,YAAY,QAAQ,KAAK,CAAC;AAAA,EAC5C;AAAA,EAEA,IAAI,UAAmB;AACrB,WAAO,KAAK,QAAQ,SAAS;AAAA,EAC/B;AAAA,EACA,IAAI,QAAQ,OAAgB;AAC1B,SAAK,WAAW,WAAW,QAAQ,KAAK,CAAC;AAAA,EAC3C;AAAA,EAEmB,SAAe;AAChC,UAAM,UAAU,KAAK,QAAQ,SAAS;AACtC,UAAM,WAAW,KAAK,QAAQ,UAAU,KAAK;AAC7C,UAAM,UAAU,KAAK,QAAQ,QAAQ,QAAQ;AAC7C,UAAM,OAAO,KAAK,QAAQ,MAAM;AAChC,UAAM,YAAY,KAAK,QAAQ,YAAY,MAAM,MAAM;AAEvD,UAAM,cAAc,KAAK,cAA2B,eAAe;AACnE,UAAM,cAAc,cAChB,8BACC,OAAO,cAAc,IAAI,IAAI;AAElC,SAAK,YAAY;AAAA,gDAC2B,OAAO;AAAA,UAC7C,CAAC,YAAY,uCAAuC,WAAW,YAAY,EAAE;AAAA;AAAA,UAE7E,YAAY,uCAAuC,WAAW,YAAY,EAAE;AAAA,UAC5E,UAAU,0DAA0D,EAAE;AAAA;AAAA,KAE3E;AAED,UAAM,MAAM,KAAK,KAAK,cAAiC,MAAM;AAC7D,QAAI,CAAC,IAAK;AAIV,UAAM,UAAU,YAAY,KAAA,MAAW;AACvC,eAAW,QAAQ,KAAK,KAAK,iBAA8B,YAAY,GAAG;AACxE,WAAK,gBAAgB,UAAU,CAAC,OAAO;AAAA,IACzC;AAIA,UAAM,WAAW,CAAC,GAAI,KAAK,cAAc,CAAA,CAAG,EAAE;AAAA,MAAK,CAAC,MAClD,EAAE,aAAa,KAAK,eACf,EAAc,eAAe,MAAM,MAAM,UACzC,EAAE,eAAe,IAAI,WAAW;AAAA,IAAA;AAEvC,QAAI,UAAU,OAAO,kBAAkB,CAAC,YAAY,OAAO;AAC3D,QAAI,UAAU;AACZ,UAAI,aAAa,YAAY,EAAE;AAC/B,UAAI,aAAa,iBAAiB,MAAM;AAAA,IAC1C,OAAO;AACL,UAAI,gBAAgB,UAAU;AAC9B,UAAI,gBAAgB,eAAe;AAAA,IACrC;AACA,QAAI,QAAS,KAAI,aAAa,aAAa,MAAM;AAAA,QAC5C,KAAI,gBAAgB,WAAW;AAAA,EACtC;AAAA,EAEmB,oBAA0B;AAC3C,UAAM,kBAAA;AACN,SAAK,iBAAiB,eAAe,KAAK,WAAW;AAAA,EACvD;AAAA,EAEmB,uBAA6B;AAC9C,UAAM,qBAAA;AACN,SAAK,oBAAoB,eAAe,KAAK,WAAW;AAAA,EAC1D;AA2BF;AAnOE,UAAgB,SAAS;AAAA,MACrB,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAFlB,IAAM,WAAN;AAsOA,SAAS,iBAAkC;AAChD,SAAO,cAAc,aAAa,QAAQ;AAC5C;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chip.d.ts","sourceRoot":"","sources":["../../../src/components/chip/chip.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAK5C;;;;;;;;GAQG;AACH,qBAAa,MAAO,SAAQ,SAAS;IACnC,OAAgB,MAAM,
|
|
1
|
+
{"version":3,"file":"chip.d.ts","sourceRoot":"","sources":["../../../src/components/chip/chip.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAK5C;;;;;;;;GAQG;AACH,qBAAa,MAAO,SAAQ,SAAS;IACnC,OAAgB,MAAM,SAiEpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,IAAI,QAAQ,IAAI,OAAO,CAAqC;IAC5D,IAAI,QAAQ,CAAC,CAAC,EAAE,OAAO,EAAqC;cAEzC,MAAM,IAAI,IAAI;CA0ClC;AAED,wBAAgB,YAAY,IAAI,OAAO,MAAM,CAE5C"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FxElement } from "../../core/base.js";
|
|
2
2
|
import { css } from "../../core/css.js";
|
|
3
3
|
import { defineElement } from "../../core/define.js";
|
|
4
|
-
import {
|
|
4
|
+
import { FENIX_ICON_BASE_CSS, fenixIconHtml } from "../../icons/base-css.js";
|
|
5
5
|
const _FxChip = class _FxChip extends FxElement {
|
|
6
6
|
static get observedAttributes() {
|
|
7
7
|
return ["variant", "size", "removable", "selectable", "selected", "disabled", "icon"];
|
|
@@ -20,7 +20,7 @@ const _FxChip = class _FxChip extends FxElement {
|
|
|
20
20
|
this.setTemplate(`
|
|
21
21
|
<span class="chip ${clickable ? "clickable" : ""}" part="chip" role="${clickable ? "button" : "status"}"
|
|
22
22
|
${clickable ? `tabindex="0" aria-pressed="${this.selected}"` : ""} ${disabled ? 'aria-disabled="true"' : ""}>
|
|
23
|
-
${icon ? `<span class="icon" aria-hidden="true">${
|
|
23
|
+
${icon ? `<span class="icon" aria-hidden="true">${fenixIconHtml(icon)}</span>` : ""}
|
|
24
24
|
<slot></slot>
|
|
25
25
|
<button type="button" class="remove" part="remove" aria-label="Remover" ${removable ? "" : "hidden"}>×</button>
|
|
26
26
|
</span>
|
|
@@ -97,6 +97,8 @@ _FxChip.styles = css`
|
|
|
97
97
|
.chip.clickable:active { transform: scale(0.97); }
|
|
98
98
|
|
|
99
99
|
.icon { line-height: 1; }
|
|
100
|
+
/* Base dos glifos Fenix Icons (o @font-face vem de @wrrdev/fenix-ui/icons). */
|
|
101
|
+
${FENIX_ICON_BASE_CSS}
|
|
100
102
|
.remove {
|
|
101
103
|
display: inline-flex;
|
|
102
104
|
align-items: center;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chip.js","sources":["../../../src/components/chip/chip.ts"],"sourcesContent":["import { FxElement } from '../../core/base';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\nimport {
|
|
1
|
+
{"version":3,"file":"chip.js","sources":["../../../src/components/chip/chip.ts"],"sourcesContent":["import { FxElement } from '../../core/base';\nimport { css } from '../../core/css';\nimport { defineElement } from '../../core/define';\nimport { FENIX_ICON_BASE_CSS, fenixIconHtml } from '../../icons/base-css';\n\n/**\n * <fx-chip> — Chip/Tag compacto para labels, filtros e seleções.\n *\n * Atributos: variant (primary|secondary|success|warning|danger|info),\n * size (sm|md|lg), removable (mostra o botão ×), selectable (clica alterna\n * o estado selected), selected, disabled, icon (glifo/emoji).\n * Slot padrão: texto do chip.\n * Eventos (composed): `remove` (ao clicar em ×) e `chip-click` (ao clicar).\n */\nexport class FxChip extends FxElement {\n static override styles = css`\n :host {\n display: inline-block;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n }\n .chip {\n display: inline-flex;\n align-items: center;\n gap: var(--fx-space-xs);\n padding: var(--fx-space-xs) var(--fx-space-md);\n min-height: var(--fx-size-sm);\n box-sizing: border-box;\n font-size: calc(var(--fx-font-size) - 2px);\n font-weight: var(--fx-font-weight);\n color: var(--fx-text-default);\n background: var(--fx-surface-surface-hover, rgba(0, 0, 0, 0.06));\n border: 1px solid transparent;\n border-radius: var(--fx-radius-full);\n user-select: none;\n transition:\n background var(--fx-motion-duration-fast) var(--fx-motion-easing),\n border-color var(--fx-motion-duration-fast) var(--fx-motion-easing),\n filter var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n :host([size='md']) .chip { min-height: var(--fx-size-md); padding: var(--fx-space-xs) var(--fx-space-lg); }\n :host([size='lg']) .chip { min-height: var(--fx-size-lg); font-size: var(--fx-font-size); padding: var(--fx-space-sm) var(--fx-space-xl); }\n\n :host([variant='primary']) .chip { background: color-mix(in srgb, var(--fx-color-primary) 14%, transparent); color: var(--fx-color-primary); }\n :host([variant='secondary']) .chip { background: color-mix(in srgb, var(--fx-color-secondary) 14%, transparent); color: var(--fx-color-secondary); }\n :host([variant='success']) .chip { background: color-mix(in srgb, var(--fx-color-success) 14%, transparent); color: var(--fx-color-success); }\n :host([variant='warning']) .chip { background: color-mix(in srgb, var(--fx-color-warning) 14%, transparent); color: var(--fx-color-warning); }\n :host([variant='danger']) .chip { background: color-mix(in srgb, var(--fx-color-danger) 14%, transparent); color: var(--fx-color-danger); }\n :host([variant='info']) .chip { background: color-mix(in srgb, var(--fx-color-info) 14%, transparent); color: var(--fx-color-info); }\n\n .chip.clickable { cursor: pointer; }\n .chip.clickable:hover { filter: brightness(0.95); }\n :host([selected]) .chip {\n background: var(--fx-color-primary);\n color: #fff;\n }\n :host([selected]) .chip:hover { filter: brightness(0.92); }\n :host([disabled]) .chip { opacity: 0.55; cursor: not-allowed; }\n .chip.clickable:active { transform: scale(0.97); }\n\n .icon { line-height: 1; }\n /* Base dos glifos Fenix Icons (o @font-face vem de @wrrdev/fenix-ui/icons). */\n ${FENIX_ICON_BASE_CSS}\n .remove {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n border: none;\n border-radius: var(--fx-radius-full);\n background: transparent;\n color: inherit;\n font-size: calc(var(--fx-font-size) + 2px);\n line-height: 1;\n cursor: pointer;\n padding: 0;\n }\n .remove:hover { background: rgba(0, 0, 0, 0.18); }\n .remove[hidden] { display: none; }\n `;\n\n static override get observedAttributes(): string[] {\n return ['variant', 'size', 'removable', 'selectable', 'selected', 'disabled', 'icon'];\n }\n\n get selected(): boolean { return this.hasAttr('selected'); }\n set selected(v: boolean) { this.toggleAttr('selected', v); }\n\n protected override render(): void {\n const icon = this.getAttr('icon');\n const removable = this.hasAttr('removable');\n const disabled = this.hasAttr('disabled');\n const clickable = (this.hasAttr('selectable') || this.hasAttr('href')) && !disabled;\n\n this.setTemplate(`\n <span class=\"chip ${clickable ? 'clickable' : ''}\" part=\"chip\" role=\"${clickable ? 'button' : 'status'}\"\n ${clickable ? `tabindex=\"0\" aria-pressed=\"${this.selected}\"` : ''} ${disabled ? 'aria-disabled=\"true\"' : ''}>\n ${icon ? `<span class=\"icon\" aria-hidden=\"true\">${fenixIconHtml(icon)}</span>` : ''}\n <slot></slot>\n <button type=\"button\" class=\"remove\" part=\"remove\" aria-label=\"Remover\" ${removable ? '' : 'hidden'}>×</button>\n </span>\n `);\n\n const chip = this.root.querySelector<HTMLElement>('.chip');\n const remove = this.root.querySelector<HTMLButtonElement>('.remove');\n if (!chip || !remove) return;\n\n remove.addEventListener('click', (e) => {\n e.stopPropagation();\n this.dispatchEvent(new CustomEvent('remove', { bubbles: true, composed: true }));\n });\n\n if (clickable) {\n const toggle = (): void => {\n if (this.hasAttr('selectable')) {\n this.selected = !this.selected;\n chip.setAttribute('aria-pressed', String(this.selected));\n }\n this.dispatchEvent(new CustomEvent('chip-click', { bubbles: true, composed: true }));\n };\n chip.addEventListener('click', toggle);\n chip.addEventListener('keydown', (e) => {\n const ev = e as KeyboardEvent;\n if (ev.key === 'Enter' || ev.key === ' ') {\n ev.preventDefault();\n toggle();\n }\n });\n }\n }\n}\n\nexport function defineFxChip(): typeof FxChip {\n return defineElement('fx-chip', FxChip);\n}\n"],"names":[],"mappings":";;;;AAcO,MAAM,UAAN,MAAM,gBAAe,UAAU;AAAA,EAoEpC,WAAoB,qBAA+B;AACjD,WAAO,CAAC,WAAW,QAAQ,aAAa,cAAc,YAAY,YAAY,MAAM;AAAA,EACtF;AAAA,EAEA,IAAI,WAAoB;AAAE,WAAO,KAAK,QAAQ,UAAU;AAAA,EAAG;AAAA,EAC3D,IAAI,SAAS,GAAY;AAAE,SAAK,WAAW,YAAY,CAAC;AAAA,EAAG;AAAA,EAExC,SAAe;AAChC,UAAM,OAAO,KAAK,QAAQ,MAAM;AAChC,UAAM,YAAY,KAAK,QAAQ,WAAW;AAC1C,UAAM,WAAW,KAAK,QAAQ,UAAU;AACxC,UAAM,aAAa,KAAK,QAAQ,YAAY,KAAK,KAAK,QAAQ,MAAM,MAAM,CAAC;AAE3E,SAAK,YAAY;AAAA,0BACK,YAAY,cAAc,EAAE,uBAAuB,YAAY,WAAW,QAAQ;AAAA,UAClG,YAAY,8BAA8B,KAAK,QAAQ,MAAM,EAAE,IAAI,WAAW,yBAAyB,EAAE;AAAA,UACzG,OAAO,yCAAyC,cAAc,IAAI,CAAC,YAAY,EAAE;AAAA;AAAA,kFAET,YAAY,KAAK,QAAQ;AAAA;AAAA,KAEtG;AAED,UAAM,OAAO,KAAK,KAAK,cAA2B,OAAO;AACzD,UAAM,SAAS,KAAK,KAAK,cAAiC,SAAS;AACnE,QAAI,CAAC,QAAQ,CAAC,OAAQ;AAEtB,WAAO,iBAAiB,SAAS,CAAC,MAAM;AACtC,QAAE,gBAAA;AACF,WAAK,cAAc,IAAI,YAAY,UAAU,EAAE,SAAS,MAAM,UAAU,KAAA,CAAM,CAAC;AAAA,IACjF,CAAC;AAED,QAAI,WAAW;AACb,YAAM,SAAS,MAAY;AACzB,YAAI,KAAK,QAAQ,YAAY,GAAG;AAC9B,eAAK,WAAW,CAAC,KAAK;AACtB,eAAK,aAAa,gBAAgB,OAAO,KAAK,QAAQ,CAAC;AAAA,QACzD;AACA,aAAK,cAAc,IAAI,YAAY,cAAc,EAAE,SAAS,MAAM,UAAU,KAAA,CAAM,CAAC;AAAA,MACrF;AACA,WAAK,iBAAiB,SAAS,MAAM;AACrC,WAAK,iBAAiB,WAAW,CAAC,MAAM;AACtC,cAAM,KAAK;AACX,YAAI,GAAG,QAAQ,WAAW,GAAG,QAAQ,KAAK;AACxC,aAAG,eAAA;AACH,iBAAA;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;AApHE,QAAgB,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MA+CrB,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAhDlB,IAAM,SAAN;AAuHA,SAAS,eAA8B;AAC5C,SAAO,cAAc,WAAW,MAAM;AACxC;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"datepicker.d.ts","sourceRoot":"","sources":["../../../src/components/datepicker/datepicker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAK5C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,YAAa,SAAQ,SAAS;IACzC,OAAgB,MAAM,
|
|
1
|
+
{"version":3,"file":"datepicker.d.ts","sourceRoot":"","sources":["../../../src/components/datepicker/datepicker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAK5C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,YAAa,SAAQ,SAAS;IACzC,OAAgB,MAAM,SAmJpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;IAED,OAAO,CAAC,WAAW,CAAC,CAAqB;IACzC,6CAA6C;IAC7C,OAAO,CAAC,IAAI,CAAwB;IAEpC,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAErB;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;IACD,IAAI,KAAK,CAAC,CAAC,EAAE,MAAM,EAElB;IAED,gDAAgD;IAChD,IAAI,MAAM,IAAI,MAAM,EAAE,CAGrB;IACD,IAAI,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAExB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAE1B;cAEkB,iBAAiB,IAAI,IAAI;cAWzB,oBAAoB,IAAI,IAAI;IAK/C,wFAAwF;IACxF,OAAO,CAAC,UAAU;IAyBlB,8CAA8C;IAC9C,OAAO,CAAC,WAAW;IAoBnB,iDAAiD;IACjD,OAAO,CAAC,QAAQ;IAKhB,sDAAsD;IACtD,OAAO,CAAC,iBAAiB;IAQzB,+DAA+D;IAC/D,OAAO,CAAC,QAAQ;cAOG,MAAM,IAAI,IAAI;IAqJjC,OAAO,CAAC,UAAU;IAMlB;;;;OAIG;IACH,OAAO,CAAC,UAAU;IAgClB,8DAA8D;IAC9D,OAAO,CAAC,WAAW;CAkCpB;AAED,wBAAgB,kBAAkB,IAAI,OAAO,YAAY,CAExD"}
|
|
@@ -8,7 +8,7 @@ const _FxDatepicker = class _FxDatepicker extends FxElement {
|
|
|
8
8
|
this.time = { h: 0, m: 0, s: 0 };
|
|
9
9
|
}
|
|
10
10
|
static get observedAttributes() {
|
|
11
|
-
return ["value", "start", "end", "values", "mode", "min", "max", "placeholder", "disabled", "format", "show-time", "free-text"];
|
|
11
|
+
return ["value", "start", "end", "values", "mode", "min", "max", "placeholder", "disabled", "format", "show-time", "free-text", "error", "invalid", "success", "valid"];
|
|
12
12
|
}
|
|
13
13
|
get mode() {
|
|
14
14
|
const m = this.getAttr("mode", "single");
|
|
@@ -322,6 +322,10 @@ _FxDatepicker.styles = css`
|
|
|
322
322
|
:host([mode='range'][size='lg']) .field { min-width: 360px; }
|
|
323
323
|
:host([mode='multiple'][size='lg']) .field { min-width: 320px; }
|
|
324
324
|
:host([show-time]) .field { min-width: 250px; }
|
|
325
|
+
/* Full width: o host estica até o pai (os min-width dos modos são
|
|
326
|
+
descartados para permitir layouts estreitos sob controle do pai). */
|
|
327
|
+
:host([full]) { display: block; width: 100%; }
|
|
328
|
+
:host([full]) .field { min-width: 0; }
|
|
325
329
|
/* Validação */
|
|
326
330
|
:host([error]) .field,
|
|
327
331
|
:host([invalid]) .field {
|