--- type: ai-agent-documentation version: 2.0 audience: ai-coding-agents-only style: exact-patterns human-readable: false attributes-reference: /docs/ATTRIBUTES.md --- # Framework Integration Guide DEFINITION: This guide provides exact patterns and rules for integrating PM7-UI components into modern JavaScript frameworks like React, Vue, and Angular. It addresses common challenges such as timing, state synchronization, and event handling. ## 🚨 CRITICAL: VERSION REQUIREMENT **BEFORE ANYTHING ELSE**: Ensure you have the LATEST version of @pm7/core! ```bash # CHECK VERSION FIRST - ALWAYS! npm list @pm7/core # If not latest, UPDATE IMMEDIATELY: npm install @pm7/core@latest ``` **MINIMUM REQUIRED VERSION**: 2.5.0+ (for `initFramework()` and self-healing features) **WARNING**: Older versions (< 2.x) lack critical framework integration features and WILL cause issues like: - Dialogs appearing inline instead of as modals - Components not initializing after framework renders - Missing `initFramework()` method - No self-healing capabilities ## Installation ```bash npm install @pm7/core@latest # ALWAYS specify @latest! ``` ### CSS & JavaScript Import REQUIRED: Import both the CSS and the main JavaScript file. ```javascript // ES modules import '@pm7/core/dist/pm7.css'; import '@pm7/core'; // Imports and initializes all components // HTML ``` ## Initialization in Frameworks PM7-UI components auto-initialize on `DOMContentLoaded`. However, frameworks often render their components after this event. Use `window.PM7.initFramework()` to ensure proper initialization and self-healing. ### Pattern: Standard Framework Initialization WHEN: Initializing PM7-UI in a framework's root component or layout. ```javascript // React (in a root component's useEffect hook) import { useEffect } from 'react'; useEffect(() => { window.PM7?.initFramework(); // Handles timing and enables self-healing }, []); // Vue (in a root component's mounted hook) import { onMounted } from 'vue'; onMounted(() => { window.PM7?.initFramework(); }); // Angular (in a root component's ngAfterViewInit hook) import { AfterViewInit } from '@angular/core'; export class AppComponent implements AfterViewInit { ngAfterViewInit() { window.PM7?.initFramework(); } } ``` ### Pattern: Dynamic Component Addition WHEN: Adding PM7-UI components to the DOM after initial page load (e.g., via conditional rendering or AJAX). ```javascript // After adding new PM7-UI elements to the DOM window.PM7?.init(); ``` ## Self-Healing Components (v2.5.0+) All interactive PM7-UI components automatically detect and recover from framework re-renders. This eliminates the need for manual re-initialization workarounds. ### How Self-Healing Works 1. Components detect when their DOM elements have been re-rendered by a framework. 2. Their internal state (e.g., open/closed, active tab) is automatically preserved. 3. Event listeners are cleaned up and re-attached to the new DOM elements. 4. The component restores its previous state, ensuring seamless user experience. ### Manual Healing (for advanced use cases) ```javascript // Heal all components of all types window.PM7.heal(); // Heal specific component types window.PM7.healMenus(); window.PM7.healAccordions(); window.PM7.healTabSelectors(); window.PM7.healTooltips(); window.PM7.healSidebars(); ``` ## State Synchronization PM7-UI components manage their own internal DOM state. When integrating with frameworks, it is CRITICAL to synchronize the framework's state with the component's DOM state. ### Pattern: Dialog State Synchronization WHEN: Using PM7-UI Dialogs in a framework where the framework's state controls dialog visibility. ```jsx // React Example import React, { useState, useEffect, useRef } from 'react'; function MyDialogComponent({ isOpen, onClose }) { const dialogRef = useRef(null); // Effect to open/close PM7 dialog based on React's isOpen state useEffect(() => { if (isOpen) { window.openDialog?.('my-dialog-id'); } else { window.closeDialog?.('my-dialog-id'); } }, [isOpen]); // Effect to synchronize React's isOpen state with PM7's internal state // This handles cases where PM7 dialog is closed by ESC key or overlay click useEffect(() => { if (!dialogRef.current) return; const observer = new MutationObserver(() => { const pm7State = dialogRef.current.getAttribute('data-state'); if (pm7State === 'closed' && isOpen) { onClose(); // Call framework's onClose to update its state } }); observer.observe(dialogRef.current, { attributes: true, attributeFilter: ['data-state'] }); return () => observer.disconnect(); }, [isOpen, onClose]); // CRITICAL: Conditionally render the dialog. Return null when not open. if (!isOpen) return null; return (
My Dialog
Content
); } ``` ## Event Handling PM7-UI components attach their own event listeners. When using frameworks, avoid attaching duplicate event listeners or relying on framework-specific event delegation for PM7-UI's internal elements. ### Pattern: Dialog Footer Buttons with `onClick` WHEN: Placing buttons in a `data-pm7-footer` section of a dialog, where direct framework `onClick` handlers might not work due to DOM manipulation. ```jsx // React Example import React, { useEffect } from 'react'; function MyDialogWithAction({ onConfirm }) { useEffect(() => { // Expose a global function that can be called from inline HTML window.handleDialogConfirm = () => { onConfirm(); window.closeDialog?.('my-action-dialog'); }; // Clean up the global function when component unmounts return () => delete window.handleDialogConfirm; }, [onConfirm]); return (
Confirm Action
Are you sure?
{/* Use dangerouslySetInnerHTML to render HTML with inline onclick */}
Confirm' }} />
); } ``` ## Anti-Patterns ### NEVER: Manually re-initialize components after every render ```javascript // React Example useEffect(() => { // NEVER do this after every render window.PM7?.init(); }); // BECAUSE This is inefficient and can lead to duplicate event listeners or unexpected behavior. PM7-UI's self-healing handles re-renders automatically. // INSTEAD // Use `window.PM7.initFramework()` once in the root component's mount hook. useEffect(() => { window.PM7?.initFramework(); }, []); ``` ### NEVER: Omit conditional rendering for interactive components ```jsx // React Example function MyComponent({ showDialog }) { return ( <> {/* NEVER render the dialog element if showDialog is false */}
...
); } // BECAUSE PM7-UI components are designed to be added/removed from the DOM. Simply hiding them with CSS breaks their lifecycle, focus management, and self-healing. // INSTEAD function MyComponent({ showDialog }) { if (!showDialog) return null; // CRITICAL: Return null when not visible return (
...
); } ``` ## Rules ### ALWAYS - **ALWAYS**: Call `window.PM7.initFramework()` once in the root component's mount lifecycle hook (e.g., `useEffect` for React, `onMounted` for Vue, `ngAfterViewInit` for Angular). - **ALWAYS**: Conditionally render interactive PM7-UI components in frameworks (return `null` or `v-if="false"` when the component should not be visible). - **ALWAYS**: Synchronize framework state with PM7-UI's internal DOM state for interactive components (e.g., using `MutationObserver` for dialogs). ### NEVER - **NEVER**: Manually call `window.PM7.init()` or component-specific `init` methods on every framework render. - **NEVER**: Use framework-specific event handlers directly on PM7-UI's internal elements if they are dynamically manipulated by PM7-UI (e.g., dialog footer buttons). - **NEVER**: Attempt to control PM7-UI component visibility or state using framework-specific CSS classes or inline styles. ## Troubleshooting ### Problem: PM7-UI component not working in framework - **CAUSE**: PM7-UI JavaScript not initialized or initialized at the wrong time. - **FIX**: Ensure `window.PM7?.initFramework()` is called in the framework's root component's mount lifecycle hook. ### Problem: Dialog closes via ESC/overlay but framework state is out of sync - **CAUSE**: Framework state is not being updated when PM7-UI internally closes the dialog. - **FIX**: Implement a `MutationObserver` to watch for `data-state="closed"` on the dialog element and update the framework's state accordingly (see `Pattern: Dialog State Synchronization`). ### Problem: `onClick` handlers on dialog/menu items not firing in React - **CAUSE**: React's event delegation is bypassed when PM7-UI dynamically manipulates the DOM structure of the dialog/menu. - **FIX**: Use global functions with `dangerouslySetInnerHTML` for `onClick` handlers on such elements (see `Pattern: Dialog Footer Buttons with onClick`). ### Problem: Components not self-healing after re-render - **CAUSE**: `window.PM7.initFramework()` was not called, or an older version of PM7-UI is being used. - **FIX**: Update to PM7-UI v2.5.0+ and ensure `window.PM7.initFramework()` is called correctly. ## Complete Example: React Application with PM7-UI Dialog SCENARIO: A React application that uses a PM7-UI Dialog for user confirmation, demonstrating proper state synchronization and event handling. ```jsx import React, { useState, useEffect, useRef } from 'react'; import '@pm7/core/dist/pm7.css'; import '@pm7/core'; // Imports PM7-UI core and initializes components function ConfirmDialog({ message, onConfirm, onCancel, isOpen }) { const dialogRef = useRef(null); // Effect to open/close PM7 dialog based on React's isOpen state useEffect(() => { if (isOpen) { window.openDialog?.('confirm-dialog'); } else { window.closeDialog?.('confirm-dialog'); } }, [isOpen]); // Effect to synchronize React's isOpen state with PM7's internal state // This handles cases where PM7 dialog is closed by ESC key or overlay click useEffect(() => { if (!dialogRef.current) return; const observer = new MutationObserver(() => { const pm7State = dialogRef.current.getAttribute('data-state'); if (pm7State === 'closed' && isOpen) { onCancel(); // Call framework's onClose to update its state } }); observer.observe(dialogRef.current, { attributes: true, attributeFilter: ['data-state'] }); return () => observer.disconnect(); }, [isOpen, onCancel]); // CRITICAL: Conditionally render the dialog. Return null when not open. if (!isOpen) return null; return (
Confirmation

{message}

{/* Use dangerouslySetInnerHTML for the confirm button to ensure event fires */}
Confirm' }} />
); } function App() { const [showConfirm, setShowConfirm] = useState(false); // Expose global function for dialog's inline onclick useEffect(() => { window.handleConfirmAction = () => { alert('Action Confirmed!'); setShowConfirm(false); }; return () => delete window.handleConfirmAction; }, []); // Initialize PM7-UI for the entire application useEffect(() => { window.PM7?.initFramework(); }, []); return (

PM7-UI React Integration Demo

setShowConfirm(false)} onCancel={() => setShowConfirm(false)} />
); } export default App; ```