vitral 0.0.0-stage → 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 João (joaogabriel15)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,325 @@
1
- # Temporary Holding Version
1
+ <p align="center"><img src="docs/logo.svg" width="96" alt="vitral"></p>
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ <h1 align="center">vitral</h1>
4
+
5
+ <p align="center"><strong>Feito de partes. Visto como um todo.</strong></p>
6
+
7
+ [![CI](https://github.com/joaogabriel15/vitral/actions/workflows/ci.yml/badge.svg)](https://github.com/joaogabriel15/vitral/actions/workflows/ci.yml)
8
+ [![npm](https://img.shields.io/npm/v/vitral)](https://www.npmjs.com/package/vitral)
9
+ [![tamanho](https://img.shields.io/bundlejs/size/vitral)](https://bundlejs.com/?q=vitral)
10
+ [![licença](https://img.shields.io/npm/l/vitral)](LICENSE)
11
+
12
+ ```bash
13
+ npm install vitral
14
+ ```
15
+
16
+ Junte micro-frontends de qualquer framework numa página só, sem configuração de build no host.
17
+
18
+ - Cada app publica um módulo e o host só precisa da URL dele.
19
+ - React, Vue, Angular, Svelte e os outros convivem na mesma tela.
20
+ - CSS, erros e deploys ficam isolados por app.
21
+
22
+ ```
23
+ host (qualquer framework) remote (qualquer framework)
24
+ ┌────────────────────────────┐ URL ┌──────────────────────────────┐
25
+ │ configure({ remotes }) │ ─────────▶ │ vitral.json { entry, css, │
26
+ │ <Remote name="carrinho" /> │ │ integrity } │
27
+ │ │ ◀───────── │ remote.js mount/update/ │
28
+ │ events (barramento global) │ import() │ unmount │
29
+ └────────────────────────────┘ └──────────────────────────────┘
30
+ ```
31
+
32
+ ## Por que é diferente
33
+
34
+ | Dor comum (Module Federation, single-spa) | Como o vitral resolve |
35
+ |---|---|
36
+ | Host e remote precisam de config de build combinada | Host não tem config de build nenhuma. Só `configure({ remotes })` em runtime. |
37
+ | `shared` / singletons quebram com versões diferentes de React/Vue | Por padrão cada remote leva o próprio framework, isolado na sua própria root. Nenhum conflito de versão. Compartilhar é opcional (import map). |
38
+ | Erro de um remote derruba a página | Cada remote tem estado de loading, erro e "tentar de novo" próprios. |
39
+ | CSS de um vaza no outro; Tailwind do remote sem classes ou quebrando o host | O remote leva o próprio CSS, já com escopo automático. `isolate="shadow"` isola de vez, e o `@property` do Tailwind v4 continua funcionando. |
40
+ | Cache velho depois do deploy (`ChunkLoadError`) | Arquivos com hash; só o `vitral.json` é revalidado. |
41
+ | Roteadores brigando | `navigate(url)` mantém host e remotes na mesma URL. |
42
+ | Executar JS de outra origem sem garantia | Allowlist de origens + verificação de hash SRI antes de executar. |
43
+ | Muitos conceitos (containers, exposes, lifecycles, root config) | Um contrato: `mount(el, props, ctx) → { update, unmount }`. |
44
+
45
+ ## Frameworks suportados
46
+
47
+ | Framework | Remote (publicar) | Host (consumir) | Import |
48
+ |---|---|---|---|
49
+ | React 18/19 | `reactRemote(App)` | `<Remote>` | `vitral/react` |
50
+ | Next.js (App e Pages Router) | igual ao React | `<Remote>` (já é `'use client'`, SSR seguro) | `vitral/react` |
51
+ | Vue 3 | `vueRemote(App, { setup })` | `<VitralRemote>` | `vitral/vue` |
52
+ | Nuxt 3/4 | igual ao Vue | `<VitralRemote>` (SSR seguro) | `vitral/vue` |
53
+ | Angular 17+ (standalone, com ou sem zone.js) | `angularRemote(AppComponent, { providers })` | `<vitral-remote>` + `provideVitral()` | `vitral/angular` |
54
+ | Svelte 5 / SvelteKit | `svelteRemote(App)` | `use:remote` ou `<vitral-remote>` | `vitral/svelte` |
55
+ | Solid / SolidStart | `solidRemote(App)` | `<Remote>` | `vitral/solid` |
56
+ | Preact | `preactRemote(App)` | `<Remote>` | `vitral/preact` |
57
+ | Lit, Stencil, Web Components | `elementRemote('meu-widget')` | `<vitral-remote>` | `vitral/element` |
58
+ | JS / TypeScript puro | `component(render)` | `<vitral-remote>` | `vitral`, `vitral/element` |
59
+ | Qualquer outro (Astro, Qwik, jQuery, HTML do servidor…) | `defineRemote({ mount })` | `<vitral-remote>` | `vitral/element` |
60
+
61
+ Todas as 72 combinações host × remote dos exemplos (9 hosts × 8 remotes) são
62
+ testadas no navegador a cada execução do e2e.
63
+
64
+ ## Uso
65
+
66
+ ### Publicar um remote
67
+
68
+ ```js
69
+ // React // Vue
70
+ import { reactRemote } from 'vitral/react' import { vueRemote } from 'vitral/vue'
71
+ export default reactRemote(Carrinho) export default vueRemote(Catalogo, { setup: (app) => app.use(pinia) })
72
+
73
+ // Svelte (o contexto chega na prop `vitral`)
74
+ import { svelteRemote } from 'vitral/svelte'
75
+ export default svelteRemote(Cartao)
76
+
77
+ // Solid / Preact (o contexto chega na prop `vitral`)
78
+ export default solidRemote(Cartao) export default preactRemote(Cartao)
79
+
80
+ // Angular: props viram inputs; contexto com injectVitral()
81
+ import { angularRemote } from 'vitral/angular'
82
+ export default angularRemote(CartaoComponent, { providers: [provideHttpClient()] })
83
+
84
+ // Lit / Web Component: props viram propriedades; contexto em `this.vitral`
85
+ import { elementRemote } from 'vitral/element'
86
+ export default elementRemote('lit-cartao', () => customElements.define('lit-cartao', LitCartao))
87
+ ```
88
+
89
+ JS ou TypeScript puro, sem framework nenhum:
90
+
91
+ ```ts
92
+ import { component, html } from 'vitral'
93
+
94
+ export default component<{ mensagem?: string }>({
95
+ state: { cliques: 0 },
96
+ render: ({ mensagem }, state, ctx) => html`
97
+ <p>${mensagem}</p>
98
+ <button onclick=${() => state.cliques++}>cliques: ${state.cliques}</button>
99
+ `,
100
+ })
101
+ ```
102
+
103
+ Sem estado, basta a função: ``component(({ nome }) => html`<p>Olá, ${nome}</p>`)``.
104
+ Tudo que entra no `${}` é escapado, então dá para colocar dado do usuário sem
105
+ risco de XSS. Mudar o `state` ou receber props novas atualiza a tela sozinho.
106
+ Se precisar de controle total, `defineRemote({ mount(el, props, ctx) { ... } })`
107
+ continua disponível.
108
+
109
+ Build com o plugin (o mesmo para todos os frameworks):
110
+
111
+ ```js
112
+ // vite.config.js
113
+ import vitral from 'vitral/vite'
114
+ export default { plugins: [react() /* ou vue(), svelte(), solid(), angular()… */, vitral({ entry: 'src/remote.jsx' })] }
115
+ ```
116
+
117
+ `vite build` gera `dist/remote-[hash].js`, `dist/remote-[hash].css` e
118
+ `dist/vitral.json` (com o hash `sha384` do JS). Suba a pasta em qualquer CDN com
119
+ CORS. Para Angular use `@analogjs/vite-plugin-angular` (compila em AOT).
120
+
121
+ ### CSS e Tailwind
122
+
123
+ Use Tailwind (ou qualquer CSS) no remote normalmente. O plugin prefixa o CSS
124
+ com `[data-vitral-scope="nome"]` e transforma `:root`/`html`/`body` na raiz do
125
+ remote, então o preflight e o tema não vazam para o host, e o host não precisa
126
+ escanear os arquivos do remote. Com `isolate="shadow"`, as regras `@property`
127
+ do Tailwind v4 sobem para o documento para que sombras, rings e gradientes
128
+ funcionem. Veja [docs/PROBLEMAS.md](docs/PROBLEMAS.md).
129
+
130
+ Sem Vite? Qualquer bundler serve (webpack, Rspack, esbuild, Rollup, Angular
131
+ CLI): basta gerar um módulo ES cujo `default` tenha `mount(el, props, ctx)`.
132
+
133
+ ### Consumir no host (React / Next)
134
+
135
+ ```jsx
136
+ import { configure } from 'vitral'
137
+ import { Remote, useEventValue } from 'vitral/react'
138
+
139
+ configure({ remotes: '/remotes.json' }) // ou um objeto { nome: url }
140
+
141
+ function App() {
142
+ const total = useEventValue('carrinho:total')
143
+ return (
144
+ <Remote
145
+ name="carrinho"
146
+ props={{ userId: 42 }}
147
+ isolate="shadow"
148
+ fallback={<Spinner />}
149
+ error={(err, retry) => <button onClick={retry}>Tentar de novo</button>}
150
+ />
151
+ )
152
+ }
153
+ ```
154
+
155
+ `<Remote>` já é `'use client'`. No Next ele renderiza o `fallback` no servidor
156
+ e monta o remote no cliente, então funciona no App Router e no Pages Router
157
+ sem `dynamic(..., { ssr: false })`.
158
+
159
+ ### Consumir no host (Vue / Nuxt)
160
+
161
+ ```vue
162
+ <script setup>
163
+ import { VitralRemote } from 'vitral/vue'
164
+ </script>
165
+
166
+ <template>
167
+ <VitralRemote name="carrinho" :props="{ userId: 42 }" isolate="shadow">
168
+ <template #fallback>Carregando…</template>
169
+ <template #error="{ error, retry }"><button @click="retry">Tentar de novo</button></template>
170
+ </VitralRemote>
171
+ </template>
172
+ ```
173
+
174
+ No Nuxt, chame `configure()` num plugin `.client.ts`. O componente renderiza o
175
+ fallback no SSR e monta o remote no cliente.
176
+
177
+ ### Consumir no host (JS/TS puro, Angular, Svelte, qualquer coisa)
178
+
179
+ O Web Component `<vitral-remote>` funciona em qualquer lugar que renderize HTML:
180
+
181
+ ```html
182
+ <script type="module">
183
+ import { configure } from 'vitral'
184
+ import { defineVitralElement } from 'vitral/element'
185
+ configure({ remotes: '/remotes.json' })
186
+ defineVitralElement()
187
+ </script>
188
+
189
+ <vitral-remote name="carrinho" props='{"userId": 42}' isolate="shadow">
190
+ <p slot="fallback">Carregando…</p>
191
+ <p slot="error">Indisponível</p>
192
+ </vitral-remote>
193
+ ```
194
+
195
+ `el.props = {...}` atualiza sem remontar, `el.retry()` remonta, e os eventos
196
+ `vitral-ready` / `vitral-error` avisam o estado.
197
+
198
+ **Angular:**
199
+
200
+ ```ts
201
+ bootstrapApplication(App, { providers: [provideVitral({ remotes: '/remotes.json' })] })
202
+
203
+ @Component({
204
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
205
+ template: `<vitral-remote name="carrinho" [props]="{ userId: id() }" (vitral-error)="falhou()"></vitral-remote>`,
206
+ })
207
+ ```
208
+
209
+ **Svelte:** `<div use:remote={{ name: 'carrinho', props: { userId } }}></div>`
210
+ (com `import { remote } from 'vitral/svelte'`).
211
+
212
+ **Solid / Preact:** `<Remote name="carrinho" props={{ userId }} fallback={…} error={(e, retry) => …} />`.
213
+
214
+ ### Comunicação
215
+
216
+ ```js
217
+ import { events } from 'vitral'
218
+ events.emit('carrinho:adicionar', produto)
219
+ const off = events.on('carrinho:adicionar', (p) => …)
220
+ events.last('usuario') // último valor emitido
221
+ ```
222
+
223
+ Dentro de remotes: `useVitral()` (contexto), `useEvent(tipo, fn)` e
224
+ `useEventValue(tipo)` existem em `vitral/react` e `vitral/vue`. O barramento é o
225
+ mesmo para todos, mesmo que cada remote tenha sua cópia da lib, e o que o
226
+ remote assina por `ctx.events` é cancelado sozinho no unmount.
227
+
228
+ Para navegar de dentro de um remote, use `navigate('/pedidos/42')` (ou
229
+ `ctx.navigate`). Ele atualiza o histórico e avisa todos os roteadores.
230
+
231
+ ### Erros
232
+
233
+ Todo erro é um `RemoteError` com `code` e o nome do remote. Falhas de
234
+ download e erros de render dentro do remote (React, Vue) aparecem no `error`
235
+ do componente de host (ou no slot `error` do `<vitral-remote>`), com `retry`
236
+ para remontar só aquele remote.
237
+
238
+ ### Configuração
239
+
240
+ ```js
241
+ configure({
242
+ remotes: { carrinho: 'https://cdn.empresa.com/carrinho/vitral.json' }, // ou URL de um JSON
243
+ allowedOrigins: ['https://cdn.empresa.com'], // padrão: página + origens registradas
244
+ timeout: 15000,
245
+ retries: 1,
246
+ isolate: 'none', // ou 'shadow' para todos
247
+ })
248
+ ```
249
+
250
+ Entradas podem ser objetos: `{ url, integrity, css, isolate }`, ou
251
+ `{ module: () => import('./carrinho') }` para um remote local (monorepo) sem
252
+ rede. A `integrity`
253
+ declarada pelo host vence a do manifesto (fixe o hash no host quando o remote
254
+ for de terceiros).
255
+
256
+ ## Segurança
257
+
258
+ - **Allowlist de origens**: nenhum código é importado de origem não permitida,
259
+ nem mesmo se um manifesto apontar para fora.
260
+ - **SRI**: quando há `integrity`, a lib baixa o JS, confere o hash e executa
261
+ exatamente os bytes verificados (via `blob:`). Com CSP, inclua `blob:` em
262
+ `script-src`.
263
+ - **Sem `eval`**, sem iframes, sem globais além de um `Symbol.for('vitral.state.v1')`.
264
+ - Shadow DOM isola CSS, não JS: um remote ainda roda com os privilégios da
265
+ página. Para código não confiável de verdade, use iframe.
266
+
267
+ ## Dependências compartilhadas (opcional)
268
+
269
+ Para não baixar React/Vue duas vezes, marque-os como `shared` no remote e
270
+ declare um import map no host:
271
+
272
+ ```js
273
+ vitral({ entry: 'src/remote.jsx', shared: ['react', 'react-dom', 'react-dom/client'] })
274
+ ```
275
+
276
+ ```html
277
+ <script type="importmap">
278
+ { "imports": { "react": "https://esm.sh/react@19.2.0", "react-dom/client": "https://esm.sh/react-dom@19.2.0/client" } }
279
+ </script>
280
+ ```
281
+
282
+ ## Testes
283
+
284
+ ```ts
285
+ // Teste do host sem subir nenhum remote
286
+ import { mockRemote, failingRemote } from 'vitral/testing'
287
+
288
+ const carrinho = mockRemote('carrinho', (el, props) => (el.textContent = `itens: ${props.itens}`))
289
+ // …renderize o host…
290
+ expect(carrinho.lastProps).toEqual({ itens: 2 })
291
+ failingRemote('recomendacoes') // para testar a tela de erro
292
+ ```
293
+
294
+ No repositório (veja [CONTRIBUTING.md](CONTRIBUTING.md)):
295
+
296
+ ```bash
297
+ pnpm install
298
+ pnpm check # build, tipos, publint/attw, tamanho e testes unitários
299
+ pnpm test:e2e # builda tudo e roda o Playwright: 9 hosts × 8 remotes + demo
300
+ pnpm examples # sobe os exemplos (hosts em :4201-:4207, remotes em :4100)
301
+ ```
302
+
303
+ O CI roda tudo isso em Node 20, 22 e 24 a cada push e PR. Versões novas saem
304
+ pelo Changesets: `pnpm changeset`, merge do PR "Versionar pacote" e o GitHub
305
+ Actions publica no npm com proveniência.
306
+
307
+ ## Exemplos e demo
308
+
309
+ - `examples/remotes/*`: o mesmo cartão em React, Vue, Svelte, Solid, Preact,
310
+ Angular, Lit e TypeScript puro.
311
+ - `examples/hosts/*`: hosts em JS puro (:4201), React (:4202), Vue (:4203),
312
+ Svelte (:4204), Solid (:4205), Preact (:4206), Angular (:4207), Next (:4208,
313
+ `pnpm --dir examples/hosts/next start`) e Nuxt (:4209,
314
+ `pnpm --dir examples/hosts/nuxt start`), cada um carregando os 8 remotes.
315
+ - `demo/`: catálogo Vue + carrinho React com Tailwind (`pnpm demo`, em
316
+ http://localhost:5173 e :5174).
317
+
318
+ ## Estado
319
+
320
+ Protótipo 0.1. Testado (32 testes unitários e 43 e2e): carregamento via
321
+ manifesto com SRI, Shadow DOM, props reativas sem remontar, props que mudam
322
+ durante o download, desmontar/remontar, StrictMode do React, erros de download
323
+ e de render, eventos entre frameworks, Tailwind v4 com e sem Shadow DOM, escopo
324
+ de CSS e SSR em Next e Nuxt. O que ainda falta está em
325
+ [docs/PROBLEMAS.md](docs/PROBLEMAS.md#ainda-não-resolvidos-próximos-passos-sugeridos).
@@ -0,0 +1,27 @@
1
+ import { InjectionToken, Type, ApplicationConfig, Provider, EnvironmentProviders } from '@angular/core';
2
+ import { RemoteContext, Props, RemoteModule, VitralConfig } from './index.js';
3
+ export { events, navigate } from './index.js';
4
+
5
+ /**
6
+ * Adaptador Angular (standalone, zoneless ou com zone.js).
7
+ *
8
+ * Remote: export default angularRemote(AppComponent, { providers: [provideHttpClient()] })
9
+ * Props viram inputs (`input()` ou `@Input()`); o contexto vem de `inject(VITRAL_CONTEXT)`.
10
+ *
11
+ * Host: providers: [provideVitral({ remotes: '/remotes.json' })]
12
+ * schemas: [CUSTOM_ELEMENTS_SCHEMA]
13
+ * <vitral-remote name="carrinho" [props]="{ userId }" isolate="shadow"></vitral-remote>
14
+ */
15
+
16
+ declare const VITRAL_CONTEXT: InjectionToken<RemoteContext>;
17
+ /** Transforma um componente standalone em um remote. */
18
+ declare function angularRemote<P extends Props>(component: Type<unknown>, config?: Partial<ApplicationConfig>): RemoteModule<P>;
19
+ /** Dentro de um remote Angular: `const vitral = injectVitral()`. */
20
+ declare function injectVitral(): RemoteContext | null;
21
+ /**
22
+ * Host Angular: configura o vitral e registra `<vitral-remote>`. Use junto com
23
+ * `CUSTOM_ELEMENTS_SCHEMA` no componente que renderiza o remote.
24
+ */
25
+ declare function provideVitral(config?: VitralConfig): (Provider | EnvironmentProviders)[];
26
+
27
+ export { VITRAL_CONTEXT, angularRemote, injectVitral, provideVitral };
@@ -0,0 +1,70 @@
1
+ import {
2
+ defineVitralElement
3
+ } from "./chunk-IZK6ITIJ.js";
4
+ import {
5
+ configure,
6
+ defineRemote,
7
+ events,
8
+ navigate
9
+ } from "./chunk-TTL52BF2.js";
10
+
11
+ // src/angular.ts
12
+ import {
13
+ InjectionToken,
14
+ createComponent,
15
+ inject,
16
+ provideAppInitializer,
17
+ provideZonelessChangeDetection,
18
+ reflectComponentType
19
+ } from "@angular/core";
20
+ import { createApplication } from "@angular/platform-browser";
21
+ var VITRAL_CONTEXT = new InjectionToken("vitral.context");
22
+ function angularRemote(component, config = {}) {
23
+ const inputs = new Set((reflectComponentType(component)?.inputs ?? []).map((i) => i.templateName));
24
+ return defineRemote({
25
+ async mount(el, props, ctx) {
26
+ const app = await createApplication({
27
+ providers: [provideZonelessChangeDetection(), { provide: VITRAL_CONTEXT, useValue: ctx }, ...config.providers ?? []]
28
+ });
29
+ const hostElement = document.createElement("div");
30
+ el.appendChild(hostElement);
31
+ const ref = createComponent(component, { environmentInjector: app.injector, hostElement });
32
+ const apply = (p) => {
33
+ for (const [k, v] of Object.entries(p)) if (inputs.has(k)) ref.setInput(k, v);
34
+ };
35
+ apply(props);
36
+ app.attachView(ref.hostView);
37
+ ref.changeDetectorRef.detectChanges();
38
+ return {
39
+ update(next) {
40
+ apply(next);
41
+ app.tick();
42
+ },
43
+ unmount() {
44
+ app.destroy();
45
+ hostElement.remove();
46
+ }
47
+ };
48
+ }
49
+ });
50
+ }
51
+ function injectVitral() {
52
+ return inject(VITRAL_CONTEXT, { optional: true });
53
+ }
54
+ function provideVitral(config = {}) {
55
+ return [
56
+ provideAppInitializer(() => {
57
+ if (typeof window === "undefined") return;
58
+ configure(config);
59
+ defineVitralElement();
60
+ })
61
+ ];
62
+ }
63
+ export {
64
+ VITRAL_CONTEXT,
65
+ angularRemote,
66
+ events,
67
+ injectVitral,
68
+ navigate,
69
+ provideVitral
70
+ };
@@ -0,0 +1,96 @@
1
+ import {
2
+ createSlot,
3
+ defineRemote
4
+ } from "./chunk-TTL52BF2.js";
5
+
6
+ // src/element.ts
7
+ var Base = typeof HTMLElement === "undefined" ? class {
8
+ } : HTMLElement;
9
+ var VitralRemoteElement = class extends Base {
10
+ static {
11
+ this.observedAttributes = ["name", "props", "isolate"];
12
+ }
13
+ #slot = null;
14
+ #props = {};
15
+ get props() {
16
+ return this.#props;
17
+ }
18
+ set props(value) {
19
+ this.#props = value ?? {};
20
+ this.#slot?.update(this.#props);
21
+ }
22
+ get status() {
23
+ return this.#slot?.status ?? "loading";
24
+ }
25
+ retry() {
26
+ this.#slot?.retry();
27
+ }
28
+ connectedCallback() {
29
+ if (this.#slot) return;
30
+ const name = this.getAttribute("name");
31
+ if (!name) return;
32
+ if (this.hasAttribute("props") && !Object.keys(this.#props).length) this.#props = parseProps(this.getAttribute("props"));
33
+ this.#slot = createSlot(this, {
34
+ name,
35
+ props: this.#props,
36
+ isolate: this.getAttribute("isolate") || void 0,
37
+ onStatus: (status, error) => {
38
+ this.setAttribute("data-vitral-status", status);
39
+ for (const el of this.querySelectorAll(':scope > [slot="fallback"]')) el.hidden = status !== "loading";
40
+ for (const el of this.querySelectorAll(':scope > [slot="error"]')) el.hidden = status !== "error";
41
+ if (status === "ready") this.dispatchEvent(new CustomEvent("vitral-ready", { bubbles: true }));
42
+ if (status === "error") this.dispatchEvent(new CustomEvent("vitral-error", { bubbles: true, detail: { error } }));
43
+ }
44
+ });
45
+ }
46
+ disconnectedCallback() {
47
+ queueMicrotask(() => {
48
+ if (!this.isConnected) {
49
+ this.#slot?.destroy();
50
+ this.#slot = null;
51
+ }
52
+ });
53
+ }
54
+ attributeChangedCallback(attr, _old, value) {
55
+ if (attr === "props") return void (this.props = parseProps(value));
56
+ if (!this.#slot) return void (this.isConnected && this.connectedCallback());
57
+ if (attr === "name" && value) this.#slot.set({ name: value });
58
+ if (attr === "isolate") this.#slot.set({ isolate: value || void 0 });
59
+ }
60
+ };
61
+ function parseProps(value) {
62
+ if (!value) return {};
63
+ try {
64
+ return JSON.parse(value);
65
+ } catch {
66
+ console.error("[vitral] atributo props n\xE3o \xE9 JSON v\xE1lido:", value);
67
+ return {};
68
+ }
69
+ }
70
+ function defineVitralElement(tag = "vitral-remote") {
71
+ if (typeof customElements !== "undefined" && !customElements.get(tag)) {
72
+ customElements.define(tag, class extends VitralRemoteElement {
73
+ });
74
+ }
75
+ }
76
+ function elementRemote(tag, setup) {
77
+ return defineRemote({
78
+ mount(el, props, ctx) {
79
+ setup?.();
80
+ const node = document.createElement(tag);
81
+ node.vitral = ctx;
82
+ Object.assign(node, props);
83
+ el.appendChild(node);
84
+ return {
85
+ update: (next) => Object.assign(node, next),
86
+ unmount: () => node.remove()
87
+ };
88
+ }
89
+ });
90
+ }
91
+
92
+ export {
93
+ VitralRemoteElement,
94
+ defineVitralElement,
95
+ elementRemote
96
+ };