@wrrdev/fenix-ui 1.1.10 → 1.1.12
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 +20 -2
- package/dist/assets/{font-CF0TXsAP.js → font-DPeWTz-n.js} +58 -13
- package/dist/assets/font-DPeWTz-n.js.map +1 -0
- package/dist/components/breadcrumb/breadcrumb.d.ts +4 -0
- package/dist/components/breadcrumb/breadcrumb.d.ts.map +1 -1
- package/dist/components/breadcrumb/breadcrumb.js +12 -2
- package/dist/components/breadcrumb/breadcrumb.js.map +1 -1
- package/dist/components/table/table.d.ts +18 -1
- package/dist/components/table/table.d.ts.map +1 -1
- package/dist/components/table/table.js +120 -23
- package/dist/components/table/table.js.map +1 -1
- package/dist/components/toast/toast.d.ts +8 -0
- package/dist/components/toast/toast.d.ts.map +1 -1
- package/dist/components/toast/toast.js +13 -0
- package/dist/components/toast/toast.js.map +1 -1
- package/dist/components/tooltip/directive.d.ts +8 -0
- package/dist/components/tooltip/directive.d.ts.map +1 -1
- package/dist/components/tooltip/directive.js +59 -18
- package/dist/components/tooltip/directive.js.map +1 -1
- package/dist/core/jsx.d.ts +19 -0
- package/dist/core/jsx.d.ts.map +1 -1
- package/dist/core/jsx.js.map +1 -1
- package/dist/docs/app.d.ts.map +1 -1
- package/dist/docs/componentes/table.doc.d.ts.map +1 -1
- package/dist/fenix-ui.umd.min.js +237 -170
- package/dist/fenix-ui3.esm.js +8 -13
- package/dist/fenix-ui3.esm.js.map +1 -1
- package/dist/fenix-ui4.esm.js +275 -114
- package/dist/fenix-ui4.esm.js.map +1 -1
- package/dist/icons/font.d.ts +23 -0
- package/dist/icons/font.d.ts.map +1 -1
- package/dist/icons/font.js +54 -8
- package/dist/icons/font.js.map +1 -1
- package/dist/icons/index.d.ts +7 -2
- package/dist/icons/index.d.ts.map +1 -1
- package/dist/icons/index.js +6 -11
- package/dist/icons/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/assets/font-CF0TXsAP.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.12**, acesso público.
|
|
22
22
|
> Subpaths disponíveis: `./theme`, `./tokens`, `./jsx`, `./vue`, `./react`, `./button`, `./badge`, `./spinner`, `./select`, `./input`, `./switch`, `./textarea`, `./checkbox`, `./radio`, `./calendar`, `./datepicker`, `./multiselect`, `./table`, `./floatlabel`, `./dialog`, `./toast`, `./tooltip`, `./tabs`, `./progress`, `./skeleton`, `./alert`, `./drawer`, `./dropdown`, `./icon`, `./icons`, `./pagination`, `./autocomplete`, `./knob`, `./orderlist`, `./picklist`, `./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.12):
|
|
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
|
-->
|
|
@@ -349,6 +349,24 @@ Requisitos:
|
|
|
349
349
|
- Volar / vue-tsc (já incluído no `@vue/language-tools` usado pelo VS Code + Volar);
|
|
350
350
|
- no `tsconfig`, `"moduleResolution": "bundler"`.
|
|
351
351
|
|
|
352
|
+
#### Reduzindo o peso da fonte de ícones (opcional)
|
|
353
|
+
|
|
354
|
+
A fonte embarca **todos** os ~4.300 glifos, o que dá ~5 MB. Ela já usa
|
|
355
|
+
`font-display: swap` (o ícone aparece na hora com a fonte de fallback — nunca
|
|
356
|
+
mais a tela em branco esperando o download). Se o seu app usa apenas algumas
|
|
357
|
+
dezenas de ícones, gere um *subset* e aponte a biblioteca para ele:
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
import { setFenixIconsFontUrl } from '@wrrdev/fenix-ui/icons';
|
|
361
|
+
|
|
362
|
+
setFenixIconsFontUrl('/fonts/fenix-icons-subset.woff2');
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Isso derruba a fonte para algumas dezenas de KB. **A tipagem não muda**:
|
|
366
|
+
`FenixIconName` continua sugerindo todos os nomes no editor — garanta apenas
|
|
367
|
+
que o subset contenha os glifos que o app usa. Chame a função antes do primeiro
|
|
368
|
+
`<fx-icon>` ser montado; use `setFenixIconsFontUrl('')` para voltar ao padrão.
|
|
369
|
+
|
|
352
370
|
Com isso o editor mostra as propriedades de cada componente (`variant`, `size`, `loading`, …)
|
|
353
371
|
nos templates. Para além do autocomplete, **validar** valores inválidos (ex.: `variant="foo"`),
|
|
354
372
|
ative o modo estrito do Volar no `tsconfig.json`:
|
|
@@ -69,26 +69,69 @@ function resolveFontUrl() {
|
|
|
69
69
|
return new URL("" + new URL("fenix-icons-DmgotBzD.woff2", import.meta.url).href, import.meta.url).toString();
|
|
70
70
|
}
|
|
71
71
|
const FENIX_ICONS_FONT_URL = resolveFontUrl();
|
|
72
|
+
function setFenixIconsFontUrl(url) {
|
|
73
|
+
const next = url.trim() || void 0;
|
|
74
|
+
if (fontUrlOverride === next) return;
|
|
75
|
+
fontUrlOverride = next;
|
|
76
|
+
const target = typeof document !== "undefined" ? document : void 0;
|
|
77
|
+
if (!target) return;
|
|
78
|
+
const hadFull = !!target.getElementById(FENIX_ICONS_STYLE_ID);
|
|
79
|
+
const hadFont = !!target.getElementById(FENIX_ICONS_FONT_STYLE_ID);
|
|
80
|
+
resetInjectedStyles(target);
|
|
81
|
+
if (hadFull) reinjectFull(target, fullNamesRef);
|
|
82
|
+
if (hadFont) injectFontStyle(target);
|
|
83
|
+
}
|
|
84
|
+
let fullNamesRef = [];
|
|
85
|
+
function __setFenixIconNames(names) {
|
|
86
|
+
fullNamesRef = names;
|
|
87
|
+
}
|
|
88
|
+
let fontUrlOverride;
|
|
89
|
+
function currentFontUrl() {
|
|
90
|
+
return fontUrlOverride ?? FENIX_ICONS_FONT_URL;
|
|
91
|
+
}
|
|
72
92
|
const FENIX_ICONS_FONT_STYLE_ID = "fenix-icons-font";
|
|
73
93
|
const FENIX_ICONS_STYLE_ID = "fenix-icons";
|
|
74
94
|
function buildFenixIconsFontFaceCss() {
|
|
75
|
-
return `@font-face{font-family:'${FENIX_ICON_FONT_FAMILY}';font-style:normal;font-weight:100 700;font-display:
|
|
95
|
+
return `@font-face{font-family:'${FENIX_ICON_FONT_FAMILY}';font-style:normal;font-weight:100 700;font-display:swap;src:url('${currentFontUrl()}') format('woff2');}`;
|
|
76
96
|
}
|
|
77
97
|
function buildFenixIconsFontCss() {
|
|
78
98
|
return buildFenixIconsFontFaceCss() + FENIX_ICON_BASE_CSS;
|
|
79
99
|
}
|
|
100
|
+
function buildFullCss(names) {
|
|
101
|
+
let rules = "";
|
|
102
|
+
for (const name of names) {
|
|
103
|
+
rules += `.fx-icon-${name}::before{content:"${name}"}`;
|
|
104
|
+
}
|
|
105
|
+
return buildFenixIconsFontFaceCss() + FENIX_ICON_BASE_CSS + rules;
|
|
106
|
+
}
|
|
80
107
|
let fontInjected = false;
|
|
108
|
+
function resetInjectedStyles(target = typeof document !== "undefined" ? document : void 0) {
|
|
109
|
+
fontInjected = false;
|
|
110
|
+
if (!target) return;
|
|
111
|
+
target.getElementById(FENIX_ICONS_FONT_STYLE_ID)?.remove();
|
|
112
|
+
target.getElementById(FENIX_ICONS_STYLE_ID)?.remove();
|
|
113
|
+
}
|
|
114
|
+
function injectFontStyle(target) {
|
|
115
|
+
const style = target.createElement("style");
|
|
116
|
+
style.id = FENIX_ICONS_FONT_STYLE_ID;
|
|
117
|
+
style.textContent = buildFenixIconsFontCss();
|
|
118
|
+
target.head.appendChild(style);
|
|
119
|
+
fontInjected = true;
|
|
120
|
+
}
|
|
121
|
+
function reinjectFull(target, names) {
|
|
122
|
+
if (target.getElementById(FENIX_ICONS_STYLE_ID)) return;
|
|
123
|
+
const style = target.createElement("style");
|
|
124
|
+
style.id = FENIX_ICONS_STYLE_ID;
|
|
125
|
+
style.textContent = buildFullCss(names);
|
|
126
|
+
target.head.appendChild(style);
|
|
127
|
+
}
|
|
81
128
|
function loadFenixIconsFont(target = typeof document !== "undefined" ? document : void 0) {
|
|
82
129
|
if (fontInjected || !target) return;
|
|
83
130
|
if (target.getElementById(FENIX_ICONS_STYLE_ID)) {
|
|
84
131
|
fontInjected = true;
|
|
85
132
|
return;
|
|
86
133
|
}
|
|
87
|
-
|
|
88
|
-
style.id = FENIX_ICONS_FONT_STYLE_ID;
|
|
89
|
-
style.textContent = buildFenixIconsFontCss();
|
|
90
|
-
target.head.appendChild(style);
|
|
91
|
-
fontInjected = true;
|
|
134
|
+
injectFontStyle(target);
|
|
92
135
|
}
|
|
93
136
|
function __resetFenixIconsFontInjection() {
|
|
94
137
|
fontInjected = false;
|
|
@@ -97,14 +140,16 @@ export {
|
|
|
97
140
|
FENIX_ICONS_STYLE_ID as F,
|
|
98
141
|
__resetFenixIconsFontInjection as _,
|
|
99
142
|
FENIX_ICON_BASE_CSS as a,
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
143
|
+
buildFullCss as b,
|
|
144
|
+
__setFenixIconNames as c,
|
|
145
|
+
FENIX_ICON_FONT_FAMILY as d,
|
|
146
|
+
buildFenixIconsFontCss as e,
|
|
104
147
|
fenixIconHtml as f,
|
|
105
|
-
|
|
148
|
+
esc as g,
|
|
149
|
+
sanitizeData as h,
|
|
106
150
|
isFenixIconName as i,
|
|
151
|
+
stripTags as j,
|
|
107
152
|
loadFenixIconsFont as l,
|
|
108
|
-
|
|
153
|
+
setFenixIconsFontUrl as s
|
|
109
154
|
};
|
|
110
|
-
//# sourceMappingURL=font-
|
|
155
|
+
//# sourceMappingURL=font-DPeWTz-n.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"font-DPeWTz-n.js","sources":["../../src/core/sanitize.ts","../../src/icons/base-css.ts","../../src/icons/font.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","/**\n * Carregamento LEVE da fonte de ícones (@font-face + classe base).\n *\n * Separado de ./index de propósito: importar este módulo NÃO injeta o CSS\n * completo com as ~4.300 regras `fx-icon-<nome>`. É o que o <fx-icon> usa —\n * dentro do Shadow DOM o glifo vem da ligadura (o nome como conteúdo), então\n * as classes por ícone não atravessam a fronteira do shadow root.\n */\n\nimport { FENIX_ICON_BASE_CSS, FENIX_ICON_FONT_FAMILY } from './base-css';\n\n/** URL da fonte woff2: no build ESM a fonte vive na mesma pasta do módulo (dist/icons). */\ndeclare const __FENIX_ICONS_FONT_URL__: string | undefined;\n\nfunction resolveFontUrl(): string {\n if (typeof __FENIX_ICONS_FONT_URL__ !== 'undefined' && __FENIX_ICONS_FONT_URL__) {\n return __FENIX_ICONS_FONT_URL__;\n }\n // IMPORTANTE: o `import.meta.url` precisa ser usado DIRETAMENTE como 2º\n // argumento de `new URL` (sem variável intermediária). É assim que Vite,\n // webpack e Rollup reconhecem o padrão de asset e copiam/reescrevem a URL\n // da fonte no build do PROJETO CONSUMIDOR — com variável, a URL permanece\n // dinâmica e quebra em produção (a fonte 404 e os ícones viram texto).\n return new URL('./fenix-icons.woff2', import.meta.url).toString();\n}\n\n/** URL resolvida da fonte (reutilizada por ./index para o CSS completo). */\nexport const FENIX_ICONS_FONT_URL = resolveFontUrl();\n\n/**\n * Sobrescreve a URL da fonte em tempo de execução.\n *\n * Use para apontar para um SUBSET próprio (só os glifos que o app usa), o que\n * derruba a fonte de ~5 MB para algumas dezenas de KB:\n *\n * import { setFenixIconsFontUrl } from '@wrrdev/fenix-ui/icons';\n * setFenixIconsFontUrl('/fonts/fenix-icons-subset.woff2');\n *\n * Deve ser chamado ANTES do primeiro `loadFenixIcons()`/`loadFenixIconsFont()`\n * (ou de `<fx-icon>` ser montado). A tipagem dos ícones não muda: continua\n * valendo a lista completa de `FenixIconName` — o subset precisa conter os\n * glifos que você realmente usa. Passe `''` para voltar à URL do build.\n */\nexport function setFenixIconsFontUrl(url: string): void {\n const next = url.trim() || undefined;\n if (fontUrlOverride === next) return;\n fontUrlOverride = next;\n // O CSS já injetado embute a URL antiga: remove e reinjeta com a nova,\n // senão a troca não teria efeito em apps que já carregaram a fonte.\n const target = typeof document !== 'undefined' ? document : undefined;\n if (!target) return;\n const hadFull = !!target.getElementById(FENIX_ICONS_STYLE_ID);\n const hadFont = !!target.getElementById(FENIX_ICONS_FONT_STYLE_ID);\n resetInjectedStyles(target);\n if (hadFull) reinjectFull(target, fullNamesRef);\n if (hadFont) injectFontStyle(target);\n}\n\n/**\n * Nomes registrados por ./index, usados só se for preciso recriar o CSS\n * completo. Evita que ./font dependa da lista de ~4.300 nomes.\n */\nlet fullNamesRef: readonly string[] = [];\n\n/** Informa os nomes disponíveis para a reinjeção do CSS completo. */\nexport function __setFenixIconNames(names: readonly string[]): void {\n fullNamesRef = names;\n}\n\nlet fontUrlOverride: string | undefined;\n\n/** URL efetiva: a sobrescrita em runtime tem prioridade sobre a do build. */\nfunction currentFontUrl(): string {\n return fontUrlOverride ?? FENIX_ICONS_FONT_URL;\n}\n\n/** Id do <style> com @font-face + classe base. */\nexport const FENIX_ICONS_FONT_STYLE_ID = 'fenix-icons-font';\n\n/** Id do <style> com o CSS completo (@font-face + base + classes por ícone). */\nexport const FENIX_ICONS_STYLE_ID = 'fenix-icons';\n\n/** CSS do @font-face. */\nexport function buildFenixIconsFontFaceCss(): string {\n return (\n `@font-face{font-family:'${FENIX_ICON_FONT_FAMILY}';font-style:normal;font-weight:100 700;` +\n // `swap` (e não `block`): com `block` o texto fica INVISÍVEL até a fonte\n // baixar — em 3G/4G isso vira tela branca por vários segundos. `swap`\n // mostra o glifo de fallback imediatamente e troca quando a fonte chega.\n `font-display:swap;src:url('${currentFontUrl()}') format('woff2');}`\n );\n}\n\n/** CSS leve: @font-face + classe base (sem as classes por ícone). */\nexport function buildFenixIconsFontCss(): string {\n return buildFenixIconsFontFaceCss() + FENIX_ICON_BASE_CSS;\n}\n\n/**\n * CSS completo (@font-face + base + uma regra por ícone), com a URL de fonte\n * vigente. Vive aqui (e não só em ./index) para que `setFenixIconsFontUrl`\n * consiga recriar o estilo completo sem importar outro módulo.\n */\nexport function buildFullCss(names: readonly string[]): string {\n let rules = '';\n for (const name of names) {\n rules += `.fx-icon-${name}::before{content:\"${name}\"}`;\n }\n return buildFenixIconsFontFaceCss() + FENIX_ICON_BASE_CSS + rules;\n}\n\nlet fontInjected = false;\n\n/**\n * Remove os <style> de ícones já injetados, para reinjetar com outra URL.\n * Usado por `setFenixIconsFontUrl`.\n */\nfunction resetInjectedStyles(\n target: Document | undefined = typeof document !== 'undefined' ? document : undefined,\n): void {\n fontInjected = false;\n if (!target) return;\n target.getElementById(FENIX_ICONS_FONT_STYLE_ID)?.remove();\n target.getElementById(FENIX_ICONS_STYLE_ID)?.remove();\n}\n\n/** Cria e anexa o <style> leve (@font-face + classe base) no <head>. */\nfunction injectFontStyle(target: Document): void {\n const style = target.createElement('style');\n style.id = FENIX_ICONS_FONT_STYLE_ID;\n style.textContent = buildFenixIconsFontCss();\n target.head.appendChild(style);\n fontInjected = true;\n}\n\n/**\n * Reinjeta o CSS completo (o de `./index`).\n *\n * Fica aqui para que a troca de URL da fonte recrie o estilo. Não usa o flag\n * `injected` de ./index: o <style> antigo já foi removido do DOM, então\n * recriá-lo aqui é sempre seguro e evita depender do estado de outro módulo.\n */\nfunction reinjectFull(target: Document, names: readonly string[]): void {\n if (target.getElementById(FENIX_ICONS_STYLE_ID)) return;\n const style = target.createElement('style');\n style.id = FENIX_ICONS_STYLE_ID;\n style.textContent = buildFullCss(names);\n target.head.appendChild(style);\n}\n\n/**\n * Injeta @font-face + classe base no <head> (idempotente). Não duplica quando\n * o CSS completo (`fenix-icons`) já foi carregado por `import '.../icons'`.\n */\nexport function loadFenixIconsFont(\n target: Document | undefined = typeof document !== 'undefined' ? document : undefined,\n): void {\n if (fontInjected || !target) return;\n if (target.getElementById(FENIX_ICONS_STYLE_ID)) {\n fontInjected = true;\n return;\n }\n injectFontStyle(target);\n}\n\n/** Reseta o estado de injeção da fonte (uso interno/testes). */\nexport function __resetFenixIconsFontInjection(): void {\n fontInjected = false;\n}"],"names":[],"mappings":"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"}
|
|
@@ -28,7 +28,11 @@ export declare class FxBreadcrumb extends FxElement {
|
|
|
28
28
|
*/
|
|
29
29
|
private _decorateItems;
|
|
30
30
|
private _items;
|
|
31
|
+
/** Observer de upgrade dos filhos; precisa ser solto no disconnect. */
|
|
32
|
+
private _childObserver?;
|
|
31
33
|
protected connectedCallback(): void;
|
|
34
|
+
/** Desconecta o observer de filhos (evita trabalho eterno após o connect). */
|
|
35
|
+
private _stopObservingChildren;
|
|
32
36
|
protected disconnectedCallback(): void;
|
|
33
37
|
}
|
|
34
38
|
export declare function defineFxBreadcrumb(): typeof FxBreadcrumb;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"breadcrumb.d.ts","sourceRoot":"","sources":["../../../src/components/breadcrumb/breadcrumb.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAK5C;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,YAAa,SAAQ,SAAS;IACzC,OAAgB,MAAM,SAuCpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;cAEkB,MAAM,IAAI,IAAI;IAUjC;;;;;OAKG;IACH,OAAO,CAAC,cAAc;IA4BtB,OAAO,CAAC,MAAM,CAAiB;
|
|
1
|
+
{"version":3,"file":"breadcrumb.d.ts","sourceRoot":"","sources":["../../../src/components/breadcrumb/breadcrumb.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAK5C;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,YAAa,SAAQ,SAAS;IACzC,OAAgB,MAAM,SAuCpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAEjD;cAEkB,MAAM,IAAI,IAAI;IAUjC;;;;;OAKG;IACH,OAAO,CAAC,cAAc;IA4BtB,OAAO,CAAC,MAAM,CAAiB;IAC/B,uEAAuE;IACvE,OAAO,CAAC,cAAc,CAAC,CAAmB;cAEvB,iBAAiB,IAAI,IAAI;IAkB5C,8EAA8E;IAC9E,OAAO,CAAC,sBAAsB;cAKX,oBAAoB,IAAI,IAAI;CAKhD;AAED,wBAAgB,kBAAkB,IAAI,OAAO,YAAY,CAExD"}
|
|
@@ -50,14 +50,24 @@ const _FxBreadcrumb = class _FxBreadcrumb extends FxElement {
|
|
|
50
50
|
super.connectedCallback();
|
|
51
51
|
const obs = new MutationObserver(() => {
|
|
52
52
|
if (this.children.length > 0) {
|
|
53
|
-
|
|
53
|
+
this._stopObservingChildren();
|
|
54
54
|
this._decorateItems();
|
|
55
55
|
}
|
|
56
56
|
});
|
|
57
|
+
this._childObserver = obs;
|
|
57
58
|
obs.observe(this, { childList: true });
|
|
58
|
-
if (this.children.length > 0)
|
|
59
|
+
if (this.children.length > 0) {
|
|
60
|
+
this._stopObservingChildren();
|
|
61
|
+
this._decorateItems();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Desconecta o observer de filhos (evita trabalho eterno após o connect). */
|
|
65
|
+
_stopObservingChildren() {
|
|
66
|
+
this._childObserver?.disconnect();
|
|
67
|
+
this._childObserver = void 0;
|
|
59
68
|
}
|
|
60
69
|
disconnectedCallback() {
|
|
70
|
+
this._stopObservingChildren();
|
|
61
71
|
super.disconnectedCallback();
|
|
62
72
|
this._items = [];
|
|
63
73
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"breadcrumb.js","sources":["../../../src/components/breadcrumb/breadcrumb.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-breadcrumb> — Trilha de navegação hierárquica.\n *\n * Atributos: size (sm|md|lg), separator (glifo entre itens, padrão '/'),\n * label (acessibilidade, padrão 'Breadcrumb').\n * Slots: `item` (repetível — cada <fx-breadcrumb-item> ou elemento com\n * [slot=\"item\"] vira um passo) e `separator` (customiza o separador).\n *\n * Marcação declarativa recomendada:\n * <fx-breadcrumb>\n * <fx-breadcrumb-item href=\"/\">Home</fx-breadcrumb-item>\n * <fx-breadcrumb-item href=\"/produtos\">Produtos</fx-breadcrumb-item>\n * <fx-breadcrumb-item>Detalhe</fx-breadcrumb-item>\n * </fx-breadcrumb>\n *\n * Eventos (composed): `select` (detail: { item, index }) ao clicar num item.\n */\nexport class FxBreadcrumb extends FxElement {\n static override styles = css`\n :host {\n display: block;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n }\n nav {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: var(--fx-space-xs);\n }\n :host([size='sm']) nav { font-size: calc(var(--fx-font-size) - 2px); }\n :host([size='lg']) nav { font-size: calc(var(--fx-font-size) + 2px); }\n\n /* itens (movidos para o nav no shadow DOM) */\n nav [slot='item'] {\n display: inline-flex;\n align-items: center;\n gap: var(--fx-space-xs);\n color: var(--fx-text-muted);\n text-decoration: none;\n cursor: pointer;\n transition: color var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n nav [slot='item']:hover { color: var(--fx-color-primary); }\n nav [slot='item'][aria-current='page'] {\n color: var(--fx-text-default);\n font-weight: 600;\n cursor: default;\n }\n nav [slot='item'][aria-current='page']:hover { color: var(--fx-text-default); }\n\n .sep {\n color: var(--fx-text-muted);\n user-select: none;\n display: inline-flex;\n align-items: center;\n }\n `;\n\n static override get observedAttributes(): string[] {\n return ['size', 'separator', 'label'];\n }\n\n protected override render(): void {\n const label = this.getAttr('label', 'Breadcrumb');\n\n this.setTemplate(`\n <nav part=\"nav\" aria-label=\"${esc(label)}\"></nav>\n `);\n\n this._decorateItems();\n }\n\n /**\n * Move os itens do light DOM para o nav e intercala separadores.\n * Como o playground atribui innerHTML (filhos chegam assincronamente) e\n * re-renders posteriores já terão os itens no nav, a lógica abaixo é\n * idempotente: coleta filhos do light DOM apenas na 1ª chamada.\n */\n private _decorateItems(): void {\n const nav = this.shadowRoot?.querySelector('nav');\n if (!nav) return;\n const sep = this.getAttr('separator', '/');\n\n // 1ª vez: captura itens do light DOM (idempotente — só coleta uma vez)\n if (this._items.length === 0) {\n this._items = Array.from(this.children).filter((el) =>\n el.matches('[slot=\"item\"]'),\n );\n }\n\n // limpa nav e reconstrói com os itens armazenados\n nav.innerHTML = '';\n this._items.forEach((item, i) => {\n const isLast = i === this._items.length - 1;\n item.setAttribute('aria-current', isLast ? 'page' : 'false');\n nav.appendChild(item);\n if (isLast) return;\n const s = document.createElement('span');\n s.className = 'sep';\n s.setAttribute('part', 'separator');\n s.setAttribute('aria-hidden', 'true');\n s.textContent = sep;\n nav.appendChild(s);\n });\n }\n\n private _items: Element[] = [];\n\n protected override connectedCallback(): void {\n super.connectedCallback();\n // filhos podem não estar disponíveis ainda (innerHTML do playground)\n const obs = new MutationObserver(() => {\n if (this.children.length > 0) {\n
|
|
1
|
+
{"version":3,"file":"breadcrumb.js","sources":["../../../src/components/breadcrumb/breadcrumb.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-breadcrumb> — Trilha de navegação hierárquica.\n *\n * Atributos: size (sm|md|lg), separator (glifo entre itens, padrão '/'),\n * label (acessibilidade, padrão 'Breadcrumb').\n * Slots: `item` (repetível — cada <fx-breadcrumb-item> ou elemento com\n * [slot=\"item\"] vira um passo) e `separator` (customiza o separador).\n *\n * Marcação declarativa recomendada:\n * <fx-breadcrumb>\n * <fx-breadcrumb-item href=\"/\">Home</fx-breadcrumb-item>\n * <fx-breadcrumb-item href=\"/produtos\">Produtos</fx-breadcrumb-item>\n * <fx-breadcrumb-item>Detalhe</fx-breadcrumb-item>\n * </fx-breadcrumb>\n *\n * Eventos (composed): `select` (detail: { item, index }) ao clicar num item.\n */\nexport class FxBreadcrumb extends FxElement {\n static override styles = css`\n :host {\n display: block;\n font-family: var(--fx-font-family);\n font-size: var(--fx-font-size);\n }\n nav {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: var(--fx-space-xs);\n }\n :host([size='sm']) nav { font-size: calc(var(--fx-font-size) - 2px); }\n :host([size='lg']) nav { font-size: calc(var(--fx-font-size) + 2px); }\n\n /* itens (movidos para o nav no shadow DOM) */\n nav [slot='item'] {\n display: inline-flex;\n align-items: center;\n gap: var(--fx-space-xs);\n color: var(--fx-text-muted);\n text-decoration: none;\n cursor: pointer;\n transition: color var(--fx-motion-duration-fast) var(--fx-motion-easing);\n }\n nav [slot='item']:hover { color: var(--fx-color-primary); }\n nav [slot='item'][aria-current='page'] {\n color: var(--fx-text-default);\n font-weight: 600;\n cursor: default;\n }\n nav [slot='item'][aria-current='page']:hover { color: var(--fx-text-default); }\n\n .sep {\n color: var(--fx-text-muted);\n user-select: none;\n display: inline-flex;\n align-items: center;\n }\n `;\n\n static override get observedAttributes(): string[] {\n return ['size', 'separator', 'label'];\n }\n\n protected override render(): void {\n const label = this.getAttr('label', 'Breadcrumb');\n\n this.setTemplate(`\n <nav part=\"nav\" aria-label=\"${esc(label)}\"></nav>\n `);\n\n this._decorateItems();\n }\n\n /**\n * Move os itens do light DOM para o nav e intercala separadores.\n * Como o playground atribui innerHTML (filhos chegam assincronamente) e\n * re-renders posteriores já terão os itens no nav, a lógica abaixo é\n * idempotente: coleta filhos do light DOM apenas na 1ª chamada.\n */\n private _decorateItems(): void {\n const nav = this.shadowRoot?.querySelector('nav');\n if (!nav) return;\n const sep = this.getAttr('separator', '/');\n\n // 1ª vez: captura itens do light DOM (idempotente — só coleta uma vez)\n if (this._items.length === 0) {\n this._items = Array.from(this.children).filter((el) =>\n el.matches('[slot=\"item\"]'),\n );\n }\n\n // limpa nav e reconstrói com os itens armazenados\n nav.innerHTML = '';\n this._items.forEach((item, i) => {\n const isLast = i === this._items.length - 1;\n item.setAttribute('aria-current', isLast ? 'page' : 'false');\n nav.appendChild(item);\n if (isLast) return;\n const s = document.createElement('span');\n s.className = 'sep';\n s.setAttribute('part', 'separator');\n s.setAttribute('aria-hidden', 'true');\n s.textContent = sep;\n nav.appendChild(s);\n });\n }\n\n private _items: Element[] = [];\n /** Observer de upgrade dos filhos; precisa ser solto no disconnect. */\n private _childObserver?: MutationObserver;\n\n protected override connectedCallback(): void {\n super.connectedCallback();\n // filhos podem não estar disponíveis ainda (innerHTML do playground)\n const obs = new MutationObserver(() => {\n if (this.children.length > 0) {\n this._stopObservingChildren();\n this._decorateItems();\n }\n });\n this._childObserver = obs;\n obs.observe(this, { childList: true });\n // se já tem filhos, decora na hora\n if (this.children.length > 0) {\n this._stopObservingChildren();\n this._decorateItems();\n }\n }\n\n /** Desconecta o observer de filhos (evita trabalho eterno após o connect). */\n private _stopObservingChildren(): void {\n this._childObserver?.disconnect();\n this._childObserver = undefined;\n }\n\n protected override disconnectedCallback(): void {\n this._stopObservingChildren();\n super.disconnectedCallback();\n this._items = [];\n }\n}\n\nexport function defineFxBreadcrumb(): typeof FxBreadcrumb {\n return defineElement('fx-breadcrumb', FxBreadcrumb);\n}\n"],"names":[],"mappings":";;;;AAsBO,MAAM,gBAAN,MAAM,sBAAqB,UAAU;AAAA,EAArC,cAAA;AAAA,UAAA,GAAA,SAAA;AA0FL,SAAQ,SAAoB,CAAA;AAAA,EAAC;AAAA,EAhD7B,WAAoB,qBAA+B;AACjD,WAAO,CAAC,QAAQ,aAAa,OAAO;AAAA,EACtC;AAAA,EAEmB,SAAe;AAChC,UAAM,QAAQ,KAAK,QAAQ,SAAS,YAAY;AAEhD,SAAK,YAAY;AAAA,oCACe,IAAI,KAAK,CAAC;AAAA,KACzC;AAED,SAAK,eAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,iBAAuB;AAC7B,UAAM,MAAM,KAAK,YAAY,cAAc,KAAK;AAChD,QAAI,CAAC,IAAK;AACV,UAAM,MAAM,KAAK,QAAQ,aAAa,GAAG;AAGzC,QAAI,KAAK,OAAO,WAAW,GAAG;AAC5B,WAAK,SAAS,MAAM,KAAK,KAAK,QAAQ,EAAE;AAAA,QAAO,CAAC,OAC9C,GAAG,QAAQ,eAAe;AAAA,MAAA;AAAA,IAE9B;AAGA,QAAI,YAAY;AAChB,SAAK,OAAO,QAAQ,CAAC,MAAM,MAAM;AAC/B,YAAM,SAAS,MAAM,KAAK,OAAO,SAAS;AAC1C,WAAK,aAAa,gBAAgB,SAAS,SAAS,OAAO;AAC3D,UAAI,YAAY,IAAI;AACpB,UAAI,OAAQ;AACZ,YAAM,IAAI,SAAS,cAAc,MAAM;AACvC,QAAE,YAAY;AACd,QAAE,aAAa,QAAQ,WAAW;AAClC,QAAE,aAAa,eAAe,MAAM;AACpC,QAAE,cAAc;AAChB,UAAI,YAAY,CAAC;AAAA,IACnB,CAAC;AAAA,EACH;AAAA,EAMmB,oBAA0B;AAC3C,UAAM,kBAAA;AAEN,UAAM,MAAM,IAAI,iBAAiB,MAAM;AACrC,UAAI,KAAK,SAAS,SAAS,GAAG;AAC5B,aAAK,uBAAA;AACL,aAAK,eAAA;AAAA,MACP;AAAA,IACF,CAAC;AACD,SAAK,iBAAiB;AACtB,QAAI,QAAQ,MAAM,EAAE,WAAW,MAAM;AAErC,QAAI,KAAK,SAAS,SAAS,GAAG;AAC5B,WAAK,uBAAA;AACL,WAAK,eAAA;AAAA,IACP;AAAA,EACF;AAAA;AAAA,EAGQ,yBAA+B;AACrC,SAAK,gBAAgB,WAAA;AACrB,SAAK,iBAAiB;AAAA,EACxB;AAAA,EAEmB,uBAA6B;AAC9C,SAAK,uBAAA;AACL,UAAM,qBAAA;AACN,SAAK,SAAS,CAAA;AAAA,EAChB;AACF;AA1HE,cAAgB,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;AADpB,IAAM,eAAN;AA6HA,SAAS,qBAA0C;AACxD,SAAO,cAAc,iBAAiB,YAAY;AACpD;"}
|
|
@@ -45,7 +45,8 @@ import "../select";
|
|
|
45
45
|
*
|
|
46
46
|
* Atributos: pagination, rows, rows-options, pagination-position (left|center|right),
|
|
47
47
|
* striped, hover, empty-message, lazy, total, loading, loading-message,
|
|
48
|
-
* sort-field, sort-order
|
|
48
|
+
* sort-field, sort-order, rounded (none|sm|md|lg, padrão md; `radius` é alias),
|
|
49
|
+
* no-borders-x (remove apenas as bordas laterais, mantendo topo e base).
|
|
49
50
|
* Eventos: page-change, sort-change, row-click, filter-change (todos composed).
|
|
50
51
|
*/
|
|
51
52
|
export declare class FxTable extends FxElement {
|
|
@@ -105,7 +106,23 @@ export declare class FxTable extends FxElement {
|
|
|
105
106
|
private computeRows;
|
|
106
107
|
private _getSearchFields;
|
|
107
108
|
private cellHtml;
|
|
109
|
+
/**
|
|
110
|
+
* Assinatura da "casca" da tabela: tudo que NÃO depende das linhas
|
|
111
|
+
* (colunas, toolbar, filtros, paginação, loading).
|
|
112
|
+
*
|
|
113
|
+
* Serve para o caminho rápido: quando só `data` muda, a casca é idêntica e
|
|
114
|
+
* podemos trocar apenas o `<tbody>`, sem recriar `<style>`, `<thead>`,
|
|
115
|
+
* toolbar e paginação — nem religar todos os listeners.
|
|
116
|
+
*/
|
|
117
|
+
private _shellKey;
|
|
108
118
|
protected render(): void;
|
|
119
|
+
/**
|
|
120
|
+
* Liga o clique das linhas ao evento `row-click`.
|
|
121
|
+
*
|
|
122
|
+
* Extraído do render para ser reaproveitado no caminho rápido (troca só do
|
|
123
|
+
* <tbody>): os <tr> são novos, então precisam ser rebound.
|
|
124
|
+
*/
|
|
125
|
+
private _bindRowClicks;
|
|
109
126
|
private _restoreToolbarSearch;
|
|
110
127
|
}
|
|
111
128
|
export declare function defineFxTable(): typeof FxTable;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../../src/components/table/table.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAO5C,OAAO,WAAW,CAAC;AAEnB
|
|
1
|
+
{"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../../src/components/table/table.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAO5C,OAAO,WAAW,CAAC;AAEnB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,qBAAa,OAAQ,SAAQ,SAAS;IACrC,OAAgB,MAAM,SAkRpB;IAEF,WAAoB,kBAAkB,IAAI,MAAM,EAAE,CAoBjD;IAED,OAAO,CAAC,KAAK,CAAiC;IAC9C,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,SAAS,CAAM;IACvB,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,eAAe,CAAC,CAAmB;IAE3C,IAAI,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAEpC;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAGxC;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;IACD,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,EAGtB;IAED,IAAI,WAAW,IAAI,MAAM,CAGxB;IACD,IAAI,WAAW,CAAC,KAAK,EAAE,MAAM,EAE5B;IAED,IAAI,IAAI,IAAI,OAAO,CAElB;IAED,OAAO,KAAK,OAAO,GAuBlB;IAED;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,OAAO,CAAC,oBAAoB;IAW5B,OAAO,KAAK,eAAe,GAU1B;IAED;;;;OAIG;IACH,OAAO,CAAC,WAAW;cAeA,iBAAiB,IAAI,IAAI;cAWzB,oBAAoB,IAAI,IAAI;IAK/C,+EAA+E;IAC/E,OAAO,CAAC,oBAAoB;IAO5B,OAAO,CAAC,mBAAmB;IAgB3B,OAAO,CAAC,WAAW;IAiDnB,OAAO,CAAC,gBAAgB;IAaxB,OAAO,CAAC,QAAQ;IAahB;;;;;;;OAOG;IACH,OAAO,CAAC,SAAS,CAAM;cAEJ,MAAM,IAAI,IAAI;IAkOjC;;;;;OAKG;IACH,OAAO,CAAC,cAAc;IAmBtB,OAAO,CAAC,qBAAqB;CAM7B;AAED,wBAAgB,aAAa,IAAI,OAAO,OAAO,CAE9C"}
|
|
@@ -15,6 +15,7 @@ const _FxTable = class _FxTable extends FxElement {
|
|
|
15
15
|
this.sortDir = 1;
|
|
16
16
|
this.filters = {};
|
|
17
17
|
this.globalSearch = "";
|
|
18
|
+
this._shellKey = "";
|
|
18
19
|
}
|
|
19
20
|
static get observedAttributes() {
|
|
20
21
|
return [
|
|
@@ -31,7 +32,10 @@ const _FxTable = class _FxTable extends FxElement {
|
|
|
31
32
|
"loading",
|
|
32
33
|
"loading-message",
|
|
33
34
|
"sort-field",
|
|
34
|
-
"sort-order"
|
|
35
|
+
"sort-order",
|
|
36
|
+
"rounded",
|
|
37
|
+
"radius",
|
|
38
|
+
"no-borders-x"
|
|
35
39
|
];
|
|
36
40
|
}
|
|
37
41
|
get data() {
|
|
@@ -251,18 +255,37 @@ const _FxTable = class _FxTable extends FxElement {
|
|
|
251
255
|
</div>`;
|
|
252
256
|
const loading = this.hasAttr("loading");
|
|
253
257
|
const loadingHtml = loading ? `<div class="loading-overlay" part="loading-overlay"><div class="tbl-spinner" part="spinner"></div><span class="loading-text">${esc(this.getAttr("loading-message", "Carregando…"))}</span></div>` : "";
|
|
258
|
+
const shellKey = [
|
|
259
|
+
toolbarHtml,
|
|
260
|
+
headHtml,
|
|
261
|
+
filterRow,
|
|
262
|
+
pagerHtml,
|
|
263
|
+
loadingHtml,
|
|
264
|
+
cols.length
|
|
265
|
+
].join("|");
|
|
266
|
+
if (shellKey === this._shellKey) {
|
|
267
|
+
const tbody = this.root.querySelector("tbody");
|
|
268
|
+
if (tbody) {
|
|
269
|
+
tbody.innerHTML = bodyHtml;
|
|
270
|
+
this._bindRowClicks(rows);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
this._shellKey = shellKey;
|
|
254
275
|
this.setTemplate(`
|
|
255
276
|
${toolbarHtml}
|
|
256
|
-
<div class="
|
|
257
|
-
<div class="
|
|
258
|
-
<
|
|
259
|
-
<
|
|
260
|
-
|
|
261
|
-
|
|
277
|
+
<div class="frame${toolbar ? " has-toolbar" : ""}" part="frame">
|
|
278
|
+
<div class="table-wrap">
|
|
279
|
+
<div class="scroll">
|
|
280
|
+
<table part="table">
|
|
281
|
+
<thead><tr>${headHtml}</tr>${filterRow}</thead>
|
|
282
|
+
<tbody>${bodyHtml}</tbody>
|
|
283
|
+
</table>
|
|
284
|
+
</div>
|
|
285
|
+
${loadingHtml}
|
|
262
286
|
</div>
|
|
263
|
-
${
|
|
287
|
+
${pagerHtml}
|
|
264
288
|
</div>
|
|
265
|
-
${pagerHtml}
|
|
266
289
|
`);
|
|
267
290
|
this.root.querySelectorAll("th.sortable").forEach((th) => {
|
|
268
291
|
th.addEventListener("click", () => {
|
|
@@ -354,6 +377,16 @@ const _FxTable = class _FxTable extends FxElement {
|
|
|
354
377
|
this.page = 0;
|
|
355
378
|
this.render();
|
|
356
379
|
});
|
|
380
|
+
this._bindRowClicks(rows);
|
|
381
|
+
this._restoreToolbarSearch();
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Liga o clique das linhas ao evento `row-click`.
|
|
385
|
+
*
|
|
386
|
+
* Extraído do render para ser reaproveitado no caminho rápido (troca só do
|
|
387
|
+
* <tbody>): os <tr> são novos, então precisam ser rebound.
|
|
388
|
+
*/
|
|
389
|
+
_bindRowClicks(rows) {
|
|
357
390
|
this.root.querySelectorAll("tbody tr[data-index]").forEach((tr) => {
|
|
358
391
|
tr.addEventListener("click", () => {
|
|
359
392
|
this.dispatchEvent(
|
|
@@ -368,7 +401,6 @@ const _FxTable = class _FxTable extends FxElement {
|
|
|
368
401
|
);
|
|
369
402
|
});
|
|
370
403
|
});
|
|
371
|
-
this._restoreToolbarSearch();
|
|
372
404
|
}
|
|
373
405
|
_restoreToolbarSearch() {
|
|
374
406
|
const input = this.root.querySelector(
|
|
@@ -385,14 +417,61 @@ _FxTable.styles = css`
|
|
|
385
417
|
font-size: var(--fx-font-size);
|
|
386
418
|
color: var(--fx-text-default);
|
|
387
419
|
background: var(--fx-surface-background);
|
|
420
|
+
/* Raio da moldura (tabela + paginação). O atributo "rounded"
|
|
421
|
+
sobrescreve este valor e o consumidor também pode sobrescrever
|
|
422
|
+
por CSS: fx-table { --fx-table-radius: 20px; }. */
|
|
423
|
+
--fx-table-radius: var(--fx-radius-md);
|
|
424
|
+
}
|
|
425
|
+
/* Arredondamento das bordas — "none" deixa tudo reto, como nos demais
|
|
426
|
+
tokens de radius do design system. "rounded" é o nome atual; "radius"
|
|
427
|
+
segue aceito como alias para ficar consistente com o <fx-card>. */
|
|
428
|
+
:host([rounded="none"]),
|
|
429
|
+
:host([radius="none"]) {
|
|
430
|
+
--fx-table-radius: var(--fx-radius-none, 0);
|
|
431
|
+
}
|
|
432
|
+
:host([rounded="sm"]),
|
|
433
|
+
:host([radius="sm"]) {
|
|
434
|
+
--fx-table-radius: var(--fx-radius-sm);
|
|
435
|
+
}
|
|
436
|
+
:host([rounded="md"]),
|
|
437
|
+
:host([radius="md"]) {
|
|
438
|
+
--fx-table-radius: var(--fx-radius-md);
|
|
439
|
+
}
|
|
440
|
+
:host([rounded="lg"]),
|
|
441
|
+
:host([radius="lg"]) {
|
|
442
|
+
--fx-table-radius: var(--fx-radius-lg);
|
|
443
|
+
}
|
|
444
|
+
/* Moldura: uma única borda envolvendo a tabela E a paginação, para que
|
|
445
|
+
o pager não fique "solto" fora do contorno da tabela. */
|
|
446
|
+
.frame {
|
|
447
|
+
border: 1px solid var(--fx-border-default);
|
|
448
|
+
border-radius: var(--fx-table-radius);
|
|
449
|
+
background: var(--fx-surface-background);
|
|
450
|
+
}
|
|
451
|
+
/* Com toolbar no topo, quem desenha a borda superior é a própria
|
|
452
|
+
toolbar — a moldura desenha apenas o contorno restante. */
|
|
453
|
+
.frame.has-toolbar {
|
|
454
|
+
border-top: none;
|
|
455
|
+
}
|
|
456
|
+
/* "no-borders-x": mantém somente as bordas de topo e de base
|
|
457
|
+
(tabela encaixada em um layout que já desenha as laterais). */
|
|
458
|
+
:host([no-borders-x]) .frame,
|
|
459
|
+
:host([no-borders-x]) .toolbar {
|
|
460
|
+
border-left: none;
|
|
461
|
+
border-right: none;
|
|
388
462
|
}
|
|
389
463
|
.table-wrap {
|
|
390
464
|
position: relative;
|
|
391
465
|
}
|
|
392
466
|
.scroll {
|
|
393
467
|
overflow-x: auto;
|
|
394
|
-
|
|
395
|
-
|
|
468
|
+
/* O topo acompanha o raio da moldura; a base fica reta porque o
|
|
469
|
+
rodapé (.pager) é quem a desenha. */
|
|
470
|
+
border-radius: var(--fx-table-radius) var(--fx-table-radius) 0 0;
|
|
471
|
+
}
|
|
472
|
+
/* Sem paginação não existe rodapé: a moldura fecha embaixo. */
|
|
473
|
+
:host(:not([pagination])) .scroll {
|
|
474
|
+
border-radius: var(--fx-table-radius);
|
|
396
475
|
}
|
|
397
476
|
table {
|
|
398
477
|
width: 100%;
|
|
@@ -479,7 +558,13 @@ _FxTable.styles = css`
|
|
|
479
558
|
align-items: center;
|
|
480
559
|
gap: var(--fx-space-sm);
|
|
481
560
|
flex-wrap: wrap;
|
|
482
|
-
|
|
561
|
+
/* O rodapé é parte da moldura: borda só no topo (separando da
|
|
562
|
+
tabela) e o mesmo padding horizontal das células. Antes ele
|
|
563
|
+
ficava fora do contorno, sem borda e sem respiro lateral. */
|
|
564
|
+
padding: var(--fx-space-sm) var(--fx-space-md);
|
|
565
|
+
border-top: 1px solid var(--fx-border-default);
|
|
566
|
+
border-radius: 0 0 var(--fx-table-radius) var(--fx-table-radius);
|
|
567
|
+
background: var(--fx-surface-background);
|
|
483
568
|
}
|
|
484
569
|
:host([pagination-position="center"]) .pager {
|
|
485
570
|
justify-content: center;
|
|
@@ -528,15 +613,27 @@ _FxTable.styles = css`
|
|
|
528
613
|
color: #fff;
|
|
529
614
|
font-weight: var(--fx-font-weight);
|
|
530
615
|
}
|
|
531
|
-
|
|
616
|
+
/* O seletor "Por página" só mostra números, então não pode herdar a
|
|
617
|
+
largura padrão do <fx-select> (200px / 180px em size="sm"), que
|
|
618
|
+
deixava o rodapé da paginação desproporcional. O limite tem que ser
|
|
619
|
+
no HOST — apertar só ::part(trigger) não funciona, porque o
|
|
620
|
+
trigger é width:100% e continua herdando o min-width do host.
|
|
621
|
+
O painel (dropdown) continua largo (220px) em select.ts. */
|
|
622
|
+
fx-select.rows-sel {
|
|
532
623
|
vertical-align: middle;
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
624
|
+
/* Não deixa o flex do .pager esticar nem esmagar o controle. */
|
|
625
|
+
flex: none;
|
|
626
|
+
width: auto;
|
|
627
|
+
--fx-select-min-width: 56px;
|
|
628
|
+
--fx-select-min-width-sm: 56px;
|
|
629
|
+
}
|
|
630
|
+
fx-select.rows-sel::part(trigger) {
|
|
631
|
+
min-width: 0;
|
|
632
|
+
min-height: var(--fx-size-sm);
|
|
633
|
+
/* Padding curto: sobra espaço para o número + caret. */
|
|
634
|
+
padding: 0 var(--fx-space-xs);
|
|
635
|
+
border-radius: var(--fx-radius-sm);
|
|
636
|
+
font-size: calc(var(--fx-font-size) - 2px);
|
|
540
637
|
}
|
|
541
638
|
.toolbar {
|
|
542
639
|
display: flex;
|
|
@@ -546,7 +643,7 @@ _FxTable.styles = css`
|
|
|
546
643
|
padding: var(--fx-space-sm) var(--fx-space-md);
|
|
547
644
|
border: 1px solid var(--fx-border-default);
|
|
548
645
|
border-bottom: none;
|
|
549
|
-
border-radius: var(--fx-radius
|
|
646
|
+
border-radius: var(--fx-table-radius) var(--fx-table-radius) 0 0;
|
|
550
647
|
background: var(--fx-surface-background);
|
|
551
648
|
}
|
|
552
649
|
.toolbar:empty {
|
|
@@ -567,7 +664,7 @@ _FxTable.styles = css`
|
|
|
567
664
|
);
|
|
568
665
|
backdrop-filter: blur(2px);
|
|
569
666
|
z-index: 10;
|
|
570
|
-
border-radius: var(--fx-radius-
|
|
667
|
+
border-radius: var(--fx-table-radius) var(--fx-table-radius) 0 0;
|
|
571
668
|
}
|
|
572
669
|
.tbl-spinner {
|
|
573
670
|
width: 32px;
|