# Un editor que conserva su estado

Prueba ventanas y PiP, drag-and-drop, transiciones y keep-alive con dos microapps reales.

Abre [Apps en movimiento](/es/labs/interaction/). React conserva el borrador del editor y Vue mantiene una bandeja de notas recibidas. WU monta ambas apps en el mismo documento, con Shadow DOM independiente, y coordina su interacción. El ejemplo usa WU Framework `4.0.0` incluido en el build.

<span class="section-anchor" id="section-1"></span>

## Cuatro tareas, un escenario

| Prueba | Qué observar |
| --- | --- |
| Escribe y envía una nota | Arrastra la tarjeta a Vue o usa el botón de envío. La bandeja recibe una copia del dato después de validar tipo y permisos. |
| Oculta y muestra el editor | El borrador y la instancia React permanecen. No se ejecuta un nuevo montaje. |
| Cambia la distribución | La transición cambia la distribución; escribir y recibir notas siguen funcionando. |
| Separa y devuelve el editor | Abre una ventana, escribe allí y vuelve. Los mismos nodos regresan al host. PiP se ofrece cuando el navegador expone esa API. |

<span class="section-anchor" id="section-2"></span>

## Qué pertenece a cada app

El shell posee una instancia `new WuCore()`, registra las dos apps y concede los permisos. React posee el borrador y ofrece el tipo `docs.note`; Vue valida y recibe ese tipo. Los controles visuales son Custom Elements Lit. El contexto `context.wu` conserva la autoridad de cada microapp al enviar o aceptar datos.

Los fragmentos siguientes se ejecutan después de inicializar el runtime y montar las apps. `runtime` es la instancia del shell. Para reproducir el arranque completo, consulta las fuentes al final de esta guía.

<span class="section-anchor" id="section-3"></span>

## Ventanas y Picture-in-Picture

```js
// Prepare the optional subsystem before the user clicks.
await runtime.windowsReady();

openButton.addEventListener('click', async () => {
  try {
    await runtime.detach('interaction-editor', {
      window: 'popup', width: 560, height: 520,
    });
    status.textContent = String(runtime.isDetached('interaction-editor'));
  } catch (error) {
    status.textContent = error.message;
  }
});

returnButton.addEventListener('click', () => {
  runtime.attach('interaction-editor');
});
```

Crea `openButton`, `returnButton` y `status` antes del fragmento. Para PiP usa `window: "pip"` desde un clic, después de comprobar soporte. `windowOf(name)` permite consultar la ventana actual. Cerrar la ventana devuelve la app; navegar fuera del escenario la desmonta. El módulo continúa ejecutándose en la página original y el editor conserva los mismos nodos y estado.

<span class="section-anchor" id="section-4"></span>

## Entrega tipada con ratón o teclado

```js
// Sending app: its manifest requests drag.offer:docs.note.
const stopOffer = await context.wu.drag.offer(card, {
  type: 'docs.note',
  payload: () => ({ text: readDraft() }),
});

// The send button uses the same permission and validation path.
await context.wu.drag.deliver('docs.note', { text: readDraft() }, {
  to: 'interaction-board',
});

// Receiving app: its manifest requests drag.accept:docs.note.
const stopAccept = await context.wu.drop.accept(inbox, {
  type: 'docs.note',
  validate(note) {
    if (!note || typeof note.text !== 'string' || !note.text.trim()) {
      throw new TypeError('A non-empty note is required');
    }
    return note;
  },
  onDrop: note => addToInbox(note),
});
```

Este fragmento reúne dos lados: oferta y envío pertenecen al editor; aceptación pertenece a la bandeja. Sustituye `card`, `inbox`, `readDraft` y `addToInbox` por las referencias de cada app. El host debe conceder ambos grants a sus respectivas apps. Conserva `stopOffer` y `stopAccept` y ejecútalos durante el desmontaje.

<span class="section-anchor" id="section-5"></span>

## Conservar estado y cambiar la distribución

```js
await runtime.unmount('interaction-editor', { keepAlive: true });
await runtime.show('interaction-editor');

// panelA and panelB have stable data-wu-vt keys inside workspace.
const animation = runtime.transition(() => {
  workspace.classList.toggle('stacked');
}, { root: workspace, duration: 320 });
await animation.updateCallbackDone;
await animation.finished;
```

Define la clase `stacked` para disponer tus paneles en una columna y añade `data-wu-vt` a los elementos que WU medirá. Ocultar conserva la instancia; no detiene automáticamente intervalos o conexiones que hayas creado. La transición respeta movimiento reducido. Para retirar el escenario por completo, llama a `runtime.destroy()` y libera los recursos externos del host.

<span class="section-anchor" id="section-6"></span>

## Fuentes y referencias

El [ejemplo completo en el repositorio oficial](https://git.wu-framework.com/Padre/wu-website/src/branch/main/apps/interaction-lab) incluye los manifiestos y las instrucciones de compilación. Sus [controles Lit](https://git.wu-framework.com/Padre/wu-website/src/branch/main/packages/interaction-lab-ui) forman parte del mismo proyecto. Clona el repositorio completo y sigue su README para ejecutarlo; los tres archivos descargables de abajo muestran las integraciones y el controlador.

Lee el [controlador del shell](/microfrontends/interaction-lab/source/controller.js.txt), el [editor React](/microfrontends/interaction-lab/source/react-editor.js.txt) y la [bandeja Vue](/microfrontends/interaction-lab/source/vue-board.js.txt). Son las fuentes usadas para compilar el ejemplo. Consulta [drag-and-drop](/es/docs/framework/drag-and-drop/), [ventanas y foco](/es/docs/framework/ui-coordination/) y [lifecycle](/es/docs/framework/lifecycle/) para conocer firmas, permisos y errores.

El soporte de PiP depende del navegador; la [documentación de Chrome](https://developer.chrome.com/docs/web-platform/document-picture-in-picture) describe el gesto del usuario y el ciclo de vida de la ventana. El botón de envío permite completar la tarea sin arrastrar, siguiendo el criterio [WCAG sobre movimientos de arrastre](https://www.w3.org/WAI/WCAG22/Understanding/dragging-movements.html).