v1.0.0

πŸŒ€ Portal System

OnigiriJS Portal provides powerful DOM teleportation and overlay management for modals, dialogs, tooltips, and dynamic content mounting.

Quick Start

<script src="onigiri-core.js"></script>
<script src="onigiri-portal.js"></script>

<script>
// Create a simple modal
const modal = Onigiri.modal('<h2>Hello World!</h2><p>This is a modal.</p>');
modal.mount();

// Create a toast notification
Onigiri.toast('Profile updated successfully!', {
    icon: 'βœ…',
    duration: 3000
});
</script>

Interactive Demo

Modal Examples

Toast Notifications

Toast Positions

DOM Teleportation

Source Container

πŸ™ I can teleport!

Target Container

Element will appear here...

Creating Portals

Basic Portal

// Create a portal with HTML content
const portal = Onigiri.portal.create('<div>Portal content</div>', {
    overlay: true,
    closeButton: true,
    closeOnOverlayClick: true,
    closeOnEscape: true
});

// Mount the portal
portal.mount();

// Unmount the portal
portal.unmount();

// Destroy the portal completely
portal.destroy();

Portal from Element

// Create portal from existing element
const content = document.querySelector('#my-content');
const portal = Onigiri.portal.create(content, options);

// Or use OnigiriJS selector
const portal = O('#my-content').portal();
portal.mount();

Portal Configuration

const portal = Onigiri.portal.create(content, {
    overlay: true,              // Show backdrop overlay
    closeButton: true,          // Show close button
    closeOnOverlayClick: true,  // Close when clicking overlay
    closeOnEscape: true,        // Close on ESC key
    lockScroll: true,           // Prevent body scrolling
    animationDuration: 300,     // Animation speed (ms)
    zIndexBase: 9000           // Base z-index for stacking
});

Modal Helper

Simple Modal

// Quick modal creation
const modal = Onigiri.modal('<h2>Confirmation</h2><p>Are you sure?</p>');
modal.mount();

// Modal with custom options
const customModal = Onigiri.modal(content, {
    closeOnOverlayClick: false,  // Require explicit close
    lockScroll: true
});

Form Modal Example

const formContent = `
    <div class="modal-form">
        <h2>Contact Us</h2>
        <form id="contactForm">
            <div class="form-group">
                <label>Name</label>
                <input type="text" name="name" required>
            </div>
            <div class="form-group">
                <label>Email</label>
                <input type="email" name="email" required>
            </div>
            <div class="form-group">
                <label>Message</label>
                <textarea name="message" required></textarea>
            </div>
            <button type="submit" class="btn btn-primary">Send</button>
        </form>
    </div>
`;

const modal = Onigiri.modal(formContent);
modal.mount();

// Handle form submission
O(document).on('submit', '#contactForm', async (e) => {
    e.preventDefault();
    // Process form...
    modal.unmount();
    Onigiri.toast('Message sent!', { icon: 'βœ…' });
});

Confirmation Modal

function confirmAction(message, onConfirm) {
    const content = `
        <div class="confirm-modal">
            <h3>Confirm Action</h3>
            <p>${message}</p>
            <div class="button-group">
                <button class="btn btn-secondary" data-action="cancel">Cancel</button>
                <button class="btn btn-danger" data-action="confirm">Confirm</button>
            </div>
        </div>
    `;
    
    const modal = Onigiri.modal(content, {
        closeButton: false,
        closeOnOverlayClick: false,
        closeOnEscape: false
    });
    
    modal.mount();
    
    // Handle buttons
    O(document).on('click', '[data-action="confirm"]', () => {
        modal.unmount();
        onConfirm();
    });
    
    O(document).on('click', '[data-action="cancel"]', () => {
        modal.unmount();
    });
}

// Usage
confirmAction('Delete this item?', () => {
    console.log('Item deleted');
    Onigiri.toast('Item deleted', { icon: 'πŸ—‘οΈ' });
});

Toast Notifications

Basic Toast

// Simple toast
Onigiri.toast('Hello World!');

// Toast with icon
Onigiri.toast('Success!', { icon: 'βœ…' });

// Toast with custom duration
Onigiri.toast('This stays longer', { duration: 5000 });

Toast Positions

// Position options
Onigiri.toast('Top Right', { position: 'top-right' });
Onigiri.toast('Top Left', { position: 'top-left' });
Onigiri.toast('Bottom Right', { position: 'bottom-right' });
Onigiri.toast('Bottom Left', { position: 'bottom-left' });
Onigiri.toast('Top Center', { position: 'top-center' });
Onigiri.toast('Bottom Center', { position: 'bottom-center' });

Toast Types

// Success
Onigiri.toast('Profile saved!', { 
    icon: 'βœ…',
    position: 'top-right' 
});

// Error
Onigiri.toast('Failed to save', { 
    icon: '❌',
    position: 'top-right' 
});

// Warning
Onigiri.toast('Your session will expire soon', { 
    icon: '⚠️',
    duration: 5000 
});

// Info
Onigiri.toast('New message received', { 
    icon: 'ℹ️' 
});

Persistent Toast

// Toast that doesn't auto-dismiss
const toast = Onigiri.toast('Manual dismiss', { 
    duration: 0  // Never auto-dismiss
});

// Dismiss manually later
setTimeout(() => {
    toast.unmount();
}, 10000);

DOM Teleportation

Teleport Element

// Teleport element to target
const element = document.querySelector('#myElement');
const teleport = Onigiri.portal.teleport(element, '#targetContainer');

// Return element to original position
teleport.return();

// Or use OnigiriJS selector
const teleport = O('#myElement').teleport('#targetContainer');
teleport.return();

Teleport with Animation

const teleport = Onigiri.portal.teleport('#element', '#target', {
    animate: true,
    returnOnUnmount: true
});

// Element smoothly animates to target
// Can be returned to original position later
teleport.return();

Multiple Element Teleportation

// Teleport multiple elements
O('.teleport-items').teleport('#target');

// Returns array of teleport objects
const teleports = O('.items').teleport('#target');
teleports.forEach(t => {
    setTimeout(() => t.return(), 3000);
});

Advanced Portal Features

Portal Stacking

// Open multiple portals - they stack automatically
const modal1 = Onigiri.modal('First Modal');
modal1.mount();

const modal2 = Onigiri.modal('Second Modal');
modal2.mount();

// ESC closes top modal
// Each portal gets proper z-index

// Close top portal
Onigiri.portal.closeTop();

// Close all portals
Onigiri.portal.closeAll();

Get Active Portal

// Get currently active (top) portal
const active = Onigiri.portal.getActive();

if (active) {
    console.log('Active portal:', active.element);
}

Update Portal Content

const portal = Onigiri.modal('Initial content');
portal.mount();

// Update content later
setTimeout(() => {
    portal.update('<h2>Updated!</h2><p>Content changed</p>');
}, 2000);

Portal Lifecycle Events

// Listen for portal events
document.addEventListener('onigiri:portal:mounted', (e) => {
    console.log('Portal mounted:', e.detail.portalId);
});

document.addEventListener('onigiri:portal:unmounted', (e) => {
    console.log('Portal unmounted:', e.detail.portalId);
});

Custom Portal Styles

Custom Modal Styling

<style>
/* Override default portal styles */
.portal-content {
    max-width: 600px;
    border-radius: 16px;
    padding: 2.5rem;
}

.onigiri-overlay {
    background: rgba(0, 0, 0, 0.8);
    backdrop-filter: blur(4px);
}

/* Dark mode support */
[data-theme="dark"] .portal-content {
    background: #1f2937;
    color: #f9fafb;
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
}

/* Custom toast styles */
.onigiri-toast {
    border-left: 4px solid #10b981;
    padding: 1.25rem 1.75rem;
}

.onigiri-toast .toast-icon {
    font-size: 1.5rem;
}
</style>

Real-World Examples

Image Lightbox

function createLightbox(imageUrl, caption) {
    const content = `
        <div class="lightbox">
            <img src="${imageUrl}" alt="${caption}">
            ${caption ? `<p class="caption">${caption}</p>` : ''}
        </div>
    `;
    
    const lightbox = Onigiri.portal.create(content, {
        overlay: true,
        closeButton: true,
        closeOnOverlayClick: true,
        closeOnEscape: true
    });
    
    lightbox.mount();
    return lightbox;
}

// Usage
O('.gallery img').on('click', function() {
    const src = this.src;
    const alt = this.alt;
    createLightbox(src, alt);
});

Loading Spinner Portal

function showLoading(message = 'Loading...') {
    const content = `
        <div class="loading-portal">
            <div class="spinner"></div>
            <p>${message}</p>
        </div>
    `;
    
    const loading = Onigiri.portal.create(content, {
        overlay: true,
        closeButton: false,
        closeOnOverlayClick: false,
        closeOnEscape: false,
        lockScroll: true
    });
    
    loading.mount();
    return loading;
}

// Usage
async function saveData() {
    const loading = showLoading('Saving...');
    
    try {
        await Onigiri.post('/api/save', data);
        loading.unmount();
        Onigiri.toast('Saved successfully!', { icon: 'βœ…' });
    } catch (error) {
        loading.unmount();
        Onigiri.toast('Save failed', { icon: '❌' });
    }
}

Notification Center

function createNotificationCenter(notifications) {
    const content = `
        <div class="notification-center">
            <h2>Notifications</h2>
            <div class="notification-list">
                ${notifications.map(n => `
                    <div class="notification-item">
                        <span class="icon">${n.icon}</span>
                        <div>
                            <strong>${n.title}</strong>
                            <p>${n.message}</p>
                            <small>${n.time}</small>
                        </div>
                    </div>
                `).join('')}
            </div>
        </div>
    `;
    
    return Onigiri.modal(content);
}

// Usage
const notifications = [
    { icon: 'πŸ’¬', title: 'New Message', message: 'John sent you a message', time: '2 min ago' },
    { icon: '❀️', title: 'New Like', message: 'Sarah liked your post', time: '1 hour ago' }
];

const center = createNotificationCenter(notifications);
center.mount();
πŸ™ Portal Best Practices:
  • Use modals for important user interactions that require attention
  • Use toasts for brief, non-intrusive notifications
  • Always provide a way to close modals (ESC, overlay, close button)
  • Limit portal stacking to avoid overwhelming users
  • Use animations sparingly for better performance
  • Test keyboard navigation and screen reader compatibility

Configuration Reference

Option Type Default Description
overlay boolean true Show backdrop overlay
closeButton boolean true Show close button
closeOnOverlayClick boolean true Close when clicking overlay
closeOnEscape boolean true Close on ESC key press
lockScroll boolean true Prevent body scrolling
animationDuration number 300 Animation duration (ms)
stackPortals boolean true Allow portal stacking
zIndexBase number 9000 Base z-index value

βœ… Development Roadmap

Track the progress of OnigiriJS modules. Tasks are marked complete by the development team.

OnigiriJS Module Roadmap

Implementation progress of planned modules

6 / 21 completed (29%)
onigiri-state
Shared global & scoped state management
onigiri-directives
Declarative DOM bindings (o-show, o-model, etc.)
onigiri-resource
REST-style data models over AJAX
onigiri-observe
Intersection & Mutation observer helpers
onigiri-humhub-ui
Standard HumHub UI abstractions (modal, notify, confirm)
onigiri-lifecycle
Component lifecycle hooks
onigiri-guard
Debounce, throttle, single-run guards
onigiri-scroll
Scroll save/restore & helpers (PJAX-friendly)
onigiri-permission
Client-side permission awareness
onigiri-portal
DOM teleport / overlay mounting
onigiri-router
Micro router (non-SPA, PJAX-first)
onigiri-sanitize
HTML & input sanitization
onigiri-shortcut
Keyboard shortcut manager
onigiri-queue
Sequential async task runner
onigiri-gesture
Touch & swipe helpers
onigiri-devtools
Debugging & inspection helpers
onigiri-plugin
Plugin registration system
onigiri-time
Relative time & timezone utilities
onigiri-emojis
Emoji Picker and Manager
onigiri-tasks
Task Management
onigiri-polls
Polls creation and management
Note: Task completion is managed by the OnigiriJS development team.