@cbm-common/xml-modal
v0.0.1
Published
Componente modal de Angular para visualizar y gestionar contenido XML con resaltado de sintaxis, funcionalidades de copiado y descarga. Implementa un modal moderno con soporte para temas oscuro/claro y animaciones personalizadas.
Readme
XML Modal
Componente modal de Angular para visualizar y gestionar contenido XML con resaltado de sintaxis, funcionalidades de copiado y descarga. Implementa un modal moderno con soporte para temas oscuro/claro y animaciones personalizadas.
📦 Instalación
npm install @cbm-common/xml-modal⚙️ Configuración
Importación del Componente
El componente es standalone, por lo que se puede importar directamente:
import { CbmXmlModalComponent } from '@cbm-common/xml-modal';
@Component({
selector: 'app-xml-viewer',
standalone: true,
imports: [CbmXmlModalComponent],
template: `
<cbm-xml-modal
[isOpen]="showModal"
[xml]="xmlContent"
[titleText]="customTitle"
[fileName]="downloadFileName"
[theme]="selectedTheme"
(close)="showModal = false"
(copy)="onXmlCopied($event)"
(download)="onXmlDownloaded($event)"
/>
`
})
export class XmlViewerComponent {
showModal = false;
xmlContent = '<root><item>Example XML</item></root>';
customTitle = 'Vista XML Personalizada';
downloadFileName = 'document.xml';
selectedTheme: 'light' | 'dark' = 'dark';
onXmlCopied(xml: string) {
console.log('XML copiado:', xml);
}
onXmlDownloaded(blob: Blob) {
console.log('XML descargado:', blob);
}
}🎯 Propiedades de Entrada (Inputs)
isOpen: boolean (Requerido)
Controla la visibilidad del modal.
// Abrir modal
this.showModal = true;
// Cerrar modal
this.closeModal = false;xml: string
Contenido XML a mostrar en el modal.
xmlContent = `<?xml version="1.0" encoding="UTF-8"?>
<root>
<item id="1">
<name>Ejemplo</name>
<value>123</value>
</item>
</root>`;titleText: string
Texto personalizado para el título del modal.
titleText = 'Documento XML';fileName: string
Nombre del archivo para la descarga (por defecto usa timestamp).
fileName = 'factura-electronica.xml';theme: 'light' | 'dark'
Tema de resaltado de sintaxis para el código XML.
// Tema oscuro (predeterminado)
theme = 'dark';
// Tema claro
theme = 'light';📤 Eventos de Salida (Outputs)
open: void
Se emite cuando el modal se abre.
(open)="onModalOpen()"close: void
Se emite cuando el modal se cierra.
(close)="onModalClose()"copy: string
Se emite cuando se copia el contenido XML al portapapeles.
(copy)="onXmlCopied($event)"Parámetro del evento:
xml: string- Contenido XML copiado
download: Blob
Se emite cuando se descarga el archivo XML.
(download)="onXmlDownloaded($event)"Parámetro del evento:
blob: Blob- Archivo Blob del XML descargado
🎨 Características Visuales
Temas de Resaltado de Sintaxis
Tema Oscuro (Predeterminado)
- Fondo: GitHub Dark (#0d1117)
- Texto: GitHub Light (#c9d1d9)
- Palabras clave: Rojo coral (#ff7b72)
- Cadenas: Azul claro (#a5c9ea)
- Comentarios: Gris (#8b949e)
- Etiquetas: Verde (#7ee787)
- Atributos: Púrpura (#d2a8ff)
Tema Claro
- Fondo: Blanco (#ffffff)
- Texto: GitHub Dark (#24292f)
- Palabras clave: Rojo (#cf222e)
- Cadenas: Azul oscuro (#0a3069)
- Comentarios: Gris (#6e7781)
- Etiquetas: Verde oscuro (#116329)
- Atributos: Púrpura (#8250df)
Diseño Responsive
- Modal adaptable: Dimensiones dinámicas (85vw x 70dvh)
- Scroll automático: Para contenido largo
- Mobile-friendly: Optimizado para dispositivos táctiles
Animaciones
- Entrada/Salida: Transiciones suaves con opacidad
- Modal: Animación de deslizamiento desde arriba
- Botones: Efectos hover y transiciones de color
🔧 Funcionalidades
Copiar al Portapapeles
copyXml(): void {
// Copia el contenido XML completo al portapapeles
// Muestra notificación de éxito/error
}Descargar Archivo
downloadXml(): void {
// Crea un Blob con el contenido XML
// Descarga el archivo con el nombre especificado
// Tipo MIME: text/xml
}Navegación por Teclado
- ESC: Cerrar modal
- Tab: Navegación entre elementos interactivos
🚀 Ejemplos de Uso
Ejemplo Básico
@Component({
selector: 'app-simple-xml-viewer',
standalone: true,
imports: [CbmXmlModalComponent],
template: `
<div class="xml-viewer">
<button (click)="openModal()" class="btn-primary">
Ver XML
</button>
<cbm-xml-modal
[isOpen]="showModal"
[xml]="xmlData"
(close)="showModal = false"
/>
</div>
`
})
export class SimpleXmlViewerComponent {
showModal = false;
xmlData = '<root><message>Hello World</message></root>';
openModal() {
this.showModal = true;
}
}Ejemplo con Configuración Completa
@Component({
selector: 'app-advanced-xml-viewer',
standalone: true,
imports: [CbmXmlModalComponent],
template: `
<div class="advanced-viewer">
<div class="controls">
<select [(ngModel)]="selectedTheme">
<option value="light">Tema Claro</option>
<option value="dark">Tema Oscuro</option>
</select>
<input
[(ngModel)]="customTitle"
placeholder="Título del modal"
>
<input
[(ngModel)]="fileName"
placeholder="Nombre del archivo"
>
</div>
<button (click)="loadXml()" class="btn-primary">
Cargar XML desde API
</button>
<cbm-xml-modal
[isOpen]="showModal"
[xml]="xmlContent"
[titleText]="customTitle"
[fileName]="fileName"
[theme]="selectedTheme"
(close)="showModal = false"
(copy)="handleCopy($event)"
(download)="handleDownload($event)"
/>
</div>
`
})
export class AdvancedXmlViewerComponent {
showModal = false;
selectedTheme: 'light' | 'dark' = 'dark';
customTitle = 'Documento XML';
fileName = 'documento.xml';
xmlContent = '';
loadXml() {
// Simular carga desde API
this.xmlContent = `<?xml version="1.0" encoding="UTF-8"?>
<invoice>
<header>
<number>001-001-0000001</number>
<date>2024-01-15</date>
<customer>
<name>Cliente Ejemplo</name>
<id>123456789</id>
</customer>
</header>
<details>
<item>
<description>Producto 1</description>
<quantity>2</quantity>
<price>100.00</price>
</item>
</details>
</invoice>`;
this.showModal = true;
}
handleCopy(xml: string) {
console.log('XML copiado exitosamente:', xml.length, 'caracteres');
}
handleDownload(blob: Blob) {
console.log('Archivo descargado:', blob.size, 'bytes');
}
}Ejemplo con Integración de Servicios
@Component({
selector: 'app-xml-from-service',
standalone: true,
imports: [CbmXmlModalComponent],
template: `
<div class="service-integration">
<div class="actions">
<button (click)="loadInvoiceXml()" class="btn-primary">
Ver Factura XML
</button>
<button (click)="loadReceiptXml()" class="btn-secondary">
Ver Recibo XML
</button>
</div>
<cbm-xml-modal
[isOpen]="showModal"
[xml]="currentXml"
[titleText]="modalTitle"
[fileName]="downloadName"
[theme]="selectedTheme"
(close)="showModal = false"
(copy)="onXmlAction('copied', $event)"
(download)="onXmlAction('downloaded', $event)"
/>
</div>
`
})
export class XmlFromServiceComponent {
showModal = false;
currentXml = '';
modalTitle = '';
downloadName = '';
selectedTheme: 'light' | 'dark' = 'dark';
constructor(private xmlService: XmlDocumentService) {}
loadInvoiceXml() {
this.xmlService.getInvoiceXml('INV001').subscribe({
next: (xml) => {
this.currentXml = xml;
this.modalTitle = 'Factura Electrónica XML';
this.downloadName = 'factura-INV001.xml';
this.showModal = true;
},
error: (error) => {
console.error('Error cargando XML:', error);
}
});
}
loadReceiptXml() {
this.xmlService.getReceiptXml('REC001').subscribe({
next: (xml) => {
this.currentXml = xml;
this.modalTitle = 'Recibo XML';
this.downloadName = 'recibo-REC001.xml';
this.showModal = true;
}
});
}
onXmlAction(action: string, data: string | Blob) {
if (action === 'copied') {
this.xmlService.logAction('XML_COPIED', data as string);
} else {
this.xmlService.logAction('XML_DOWNLOADED', (data as Blob).size.toString());
}
}
}Ejemplo con Tema Dinámico
@Component({
selector: 'app-dynamic-theme-xml',
standalone: true,
imports: [CbmXmlModalComponent],
template: `
<div class="theme-switcher" [class.dark]="isDarkMode">
<div class="theme-toggle">
<button (click)="toggleTheme()" class="theme-btn">
{{ isDarkMode ? '☀️' : '🌙' }}
{{ isDarkMode ? 'Modo Claro' : 'Modo Oscuro' }}
</button>
</div>
<cbm-xml-modal
[isOpen]="showModal"
[xml]="xmlContent"
[theme]="isDarkMode ? 'dark' : 'light'"
(close)="showModal = false"
/>
</div>
`,
styles: [`
.theme-switcher.dark {
background-color: #1a1a1a;
color: #ffffff;
}
.theme-btn {
padding: 0.5rem 1rem;
border: 1px solid #ccc;
border-radius: 0.5rem;
background: white;
cursor: pointer;
}
.dark .theme-btn {
background: #333;
border-color: #555;
color: #fff;
}
`]
})
export class DynamicThemeXmlComponent {
isDarkMode = false;
showModal = false;
xmlContent = '<root><theme>Example</theme></root>';
toggleTheme() {
this.isDarkMode = !this.isDarkMode;
}
openModal() {
this.showModal = true;
}
}🎭 Configuración de Temas Avanzada
Importación de Temas Personalizados
// En el componente que usa xml-modal
import 'highlight.js/styles/github-dark.css'; // Tema oscuro moderno
import 'highlight.js/styles/github.css'; // Tema claro
// Opciones alternativas de temas oscuros:
// import 'highlight.js/styles/vs2015.css'; // VS Code oscuro
// import 'highlight.js/styles/atom-one-dark.css'; // Atom oscuro
// import 'highlight.js/styles/monokai.css'; // Monokai clásico
// import 'highlight.js/styles/dracula.css'; // Dracula
// Opciones alternativas de temas claros:
// import 'highlight.js/styles/vs.css'; // VS claro
// import 'highlight.js/styles/atom-one-light.css'; // Atom claro
// import 'highlight.js/styles/xcode.css'; // XcodePersonalización de Estilos CSS
/* Personalizar colores del tema oscuro */
.hljs-dark-theme {
--hljs-background: #0d1117;
--hljs-foreground: #c9d1d9;
--hljs-keyword: #ff7b72;
--hljs-string: #a5c9ea;
--hljs-comment: #8b949e;
--hljs-tag: #7ee787;
--hljs-attr: #d2a8ff;
}
/* Personalizar colores del tema claro */
.hljs-light-theme {
--hljs-background: #ffffff;
--hljs-foreground: #24292f;
--hljs-keyword: #cf222e;
--hljs-string: #0a3069;
--hljs-comment: #6e7781;
--hljs-tag: #116329;
--hljs-attr: #8250df;
}⚠️ Manejo de Errores
Errores de Copiado
// El componente maneja automáticamente errores de:
- Permisos denegados para portapapeles
- Contenido vacío
- Errores de navegadorErrores de Descarga
// El componente maneja automáticamente errores de:
- Creación de Blob fallida
- Errores de URL.createObjectURL
- Problemas de descarga del navegadorNotificaciones
El componente integra automáticamente con CbmNotificationService para mostrar:
- ✅ Éxito: "Copiado exitosamente" / "Descargando archivo..."
- ❌ Error: Mensajes específicos de error
📋 Dependencias
Peer Dependencies (Requeridas)
{
"@angular/common": ">=20.1.5",
"@angular/core": ">=20.1.5",
"ngx-highlightjs": "14.0.1",
"@cbm-common/tooltip-directive": "0.0.x",
"@cbm-common/notification-service": "0.0.x"
}Dependencias de Desarrollo
{
"tailwindcss": "^4.1.11",
"postcss": "^8.5.6",
"@tailwindcss/postcss": "^4.1.11"
}🛠️ Desarrollo
Estructura del Proyecto
xml-modal/
├── src/
│ ├── lib/
│ │ ├── xml-modal.component.ts # Componente principal
│ │ ├── xml-modal.component.html # Template del modal
│ │ ├── xml-modal.component.css # Estilos específicos
│ │ ├── styles.css # Estilos Tailwind + temas
│ │ ├── animations.ts # Animaciones Angular
│ │ └── index.ts # Exportaciones
│ └── public-api.ts # API pública
├── ng-package.json # Configuración empaquetado
├── package.json # Dependencias
└── README.md # Esta documentaciónConstrucción
# Construir la librería
ng build xml-modal
# Construir en modo watch
ng build xml-modal --watch
# Construir para producción
ng build xml-modal --configuration productionPruebas
# Ejecutar pruebas unitarias
ng test xml-modal
# Ejecutar pruebas con coverage
ng test xml-modal --code-coverageDesarrollo con Tailwind
# Compilar estilos Tailwind
npm run tailwind
# Modo watch para desarrollo
npm run tailwind --watch🎯 Mejores Prácticas
1. Configuración de Tema Global
// En app.config.ts o main.ts
import 'highlight.js/styles/github-dark.css'; // Tema global
// O configurar dinámicamente
export const HIGHLIGHT_THEMES = {
dark: () => import('highlight.js/styles/github-dark.css'),
light: () => import('highlight.js/styles/github.css')
};2. Manejo de Contenido Grande
// Para XML muy grandes, considerar lazy loading
@Component({...})
export class LargeXmlViewerComponent {
xmlChunks: string[] = [];
currentChunk = 0;
loadXmlChunk() {
// Cargar chunks del XML progresivamente
this.xmlService.getXmlChunk(this.currentChunk).subscribe(chunk => {
this.xmlChunks.push(chunk);
this.currentXml = this.xmlChunks.join('');
});
}
}3. Integración con Sistema de Archivos
// Para guardar/cargar archivos XML
export class XmlFileManager {
async saveXmlToFile(xml: string, filename: string) {
const blob = new Blob([xml], { type: 'text/xml' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
async loadXmlFromFile(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result as string);
reader.onerror = () => reject(reader.error);
reader.readAsText(file);
});
}
}🤝 Contribución
- Fork el repositorio
- Crea una rama para tu feature (
git checkout -b feature/nueva-funcionalidad) - Commit tus cambios (
git commit -am 'Agrega nueva funcionalidad') - Push a la rama (
git push origin feature/nueva-funcionalidad) - Abre un Pull Request
📄 Licencia
Este proyecto está bajo la Licencia MIT - ver el archivo LICENSE para más detalles.
📞 Soporte
Para soporte técnico o reportes de bugs, contacta al equipo de desarrollo de CBM:
- 📧 Email: [email protected]
- 💬 Slack: #desarrollo-cbm
- 📋 Issues: GitHub Issues
🔄 Changelog
v0.0.1
- ✅ Componente modal standalone con animaciones personalizadas
- ✅ Resaltado de sintaxis XML con ngx-highlightjs
- ✅ Soporte para temas oscuro y claro
- ✅ Funcionalidades de copiar y descargar XML
- ✅ Integración con servicios de notificación y tooltips
- ✅ Diseño responsive con Tailwind CSS
- ✅ Encapsulación Shadow DOM emulada
- ✅ Documentación completa en español
Nota: Este componente está optimizado para trabajar con archivos XML de facturación electrónica y documentos similares. Para contenido XML muy grande, considera implementar paginación o carga diferida.
