javascript

examples

examples.js⚔
/**
 * Shadow DOM - Examples
 *
 * Comprehensive examples of using Shadow DOM for encapsulation
 */

// ============================================
// EXAMPLE 1: Basic Shadow DOM
// ============================================

class BasicShadow extends HTMLElement {
  constructor() {
    super();

    // Create shadow root with open mode
    const shadow = this.attachShadow({ mode: 'open' });

    // Add content to shadow root
    shadow.innerHTML = `
            <style>
                /* These styles are scoped to this component */
                p {
                    color: blue;
                    font-family: Georgia, serif;
                    padding: 10px;
                    border-left: 3px solid blue;
                }
            </style>
            <p>This paragraph is styled within Shadow DOM</p>
        `;
  }
}

customElements.define('basic-shadow', BasicShadow);

/* 
Note: External CSS like 'p { color: red; }' will NOT affect 
the paragraph inside the shadow DOM
*/

// ============================================
// EXAMPLE 2: Open vs Closed Mode
// ============================================

class OpenShadow extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.innerHTML = `<p>Open shadow - accessible</p>`;
  }
}

class ClosedShadow extends HTMLElement {
  constructor() {
    super();
    // Store reference privately
    this._shadowRoot = this.attachShadow({ mode: 'closed' });
    this._shadowRoot.innerHTML = `<p>Closed shadow - not accessible</p>`;
  }

  // Provide controlled access
  updateContent(text) {
    this._shadowRoot.querySelector('p').textContent = text;
  }
}

customElements.define('open-shadow', OpenShadow);
customElements.define('closed-shadow', ClosedShadow);

// Demo
function demonstrateModes() {
  const open = document.createElement('open-shadow');
  const closed = document.createElement('closed-shadow');

  console.log('Open mode shadowRoot:', open.shadowRoot); // ShadowRoot
  console.log('Closed mode shadowRoot:', closed.shadowRoot); // null
}

// ============================================
// EXAMPLE 3: :host Selector Patterns
// ============================================

class HostStyling extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });

    this.shadowRoot.innerHTML = `
            <style>
                /* Base host styles */
                :host {
                    display: block;
                    padding: 20px;
                    border: 2px solid #e5e7eb;
                    border-radius: 8px;
                    font-family: system-ui, sans-serif;
                    background: white;
                    transition: all 0.3s ease;
                }
                
                /* Host with specific attribute */
                :host([variant="primary"]) {
                    background: #3b82f6;
                    color: white;
                    border-color: #2563eb;
                }
                
                :host([variant="success"]) {
                    background: #22c55e;
                    color: white;
                    border-color: #16a34a;
                }
                
                :host([variant="warning"]) {
                    background: #f59e0b;
                    color: white;
                    border-color: #d97706;
                }
                
                :host([variant="danger"]) {
                    background: #ef4444;
                    color: white;
                    border-color: #dc2626;
                }
                
                /* Host with class */
                :host(.elevated) {
                    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
                }
                
                :host(.rounded) {
                    border-radius: 20px;
                }
                
                /* Host when disabled */
                :host([disabled]) {
                    opacity: 0.5;
                    pointer-events: none;
                }
                
                /* Host context - when inside specific container */
                :host-context(.dark-theme) {
                    background: #1f2937;
                    color: #f3f4f6;
                    border-color: #374151;
                }
                
                /* Host on hover */
                :host(:hover) {
                    transform: translateY(-2px);
                    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.1);
                }
                
                /* Internal styles */
                .content {
                    font-size: 14px;
                    line-height: 1.6;
                }
            </style>
            
            <div class="content">
                <slot>Default content</slot>
            </div>
        `;
  }
}

customElements.define('host-styling', HostStyling);

/* Usage:
<host-styling variant="primary" class="elevated">
    Primary button with elevation
</host-styling>
*/

// ============================================
// EXAMPLE 4: Slot Composition
// ============================================

class CardComponent extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });

    this.shadowRoot.innerHTML = `
            <style>
                :host {
                    display: block;
                    font-family: system-ui, sans-serif;
                }
                
                .card {
                    background: white;
                    border-radius: 12px;
                    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
                    overflow: hidden;
                }
                
                .card-header {
                    padding: 16px 20px;
                    background: #f8fafc;
                    border-bottom: 1px solid #e2e8f0;
                }
                
                .card-body {
                    padding: 20px;
                }
                
                .card-footer {
                    padding: 16px 20px;
                    background: #f8fafc;
                    border-top: 1px solid #e2e8f0;
                    display: flex;
                    justify-content: flex-end;
                    gap: 8px;
                }
                
                /* Style slotted content */
                ::slotted(h2) {
                    margin: 0;
                    font-size: 18px;
                    font-weight: 600;
                    color: #1e293b;
                }
                
                ::slotted(p) {
                    margin: 0;
                    color: #64748b;
                    line-height: 1.6;
                }
                
                ::slotted(button) {
                    padding: 8px 16px;
                    border-radius: 6px;
                    font-size: 14px;
                    cursor: pointer;
                }
                
                /* Hide empty slots */
                .card-header:not(:has(::slotted(*))) {
                    display: none;
                }
                
                .card-footer:not(:has(::slotted(*))) {
                    display: none;
                }
            </style>
            
            <div class="card">
                <div class="card-header">
                    <slot name="header"></slot>
                </div>
                <div class="card-body">
                    <slot>No content provided</slot>
                </div>
                <div class="card-footer">
                    <slot name="footer"></slot>
                </div>
            </div>
        `;
  }
}

customElements.define('card-component', CardComponent);

/* Usage:
<card-component>
    <h2 slot="header">Card Title</h2>
    <p>This is the main card content.</p>
    <button slot="footer">Cancel</button>
    <button slot="footer">Save</button>
</card-component>
*/

// ============================================
// EXAMPLE 5: Slot Change Events
// ============================================

class SlotObserver extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });

    this.shadowRoot.innerHTML = `
            <style>
                :host {
                    display: block;
                    padding: 16px;
                    background: #f0f9ff;
                    border-radius: 8px;
                }
                
                .info {
                    font-size: 12px;
                    color: #64748b;
                    margin-top: 8px;
                }
            </style>
            
            <slot></slot>
            <div class="info"></div>
        `;
  }

  connectedCallback() {
    const slot = this.shadowRoot.querySelector('slot');
    const info = this.shadowRoot.querySelector('.info');

    // Update info when slot content changes
    const updateInfo = () => {
      const elements = slot.assignedElements();
      const nodes = slot.assignedNodes();

      info.innerHTML = `
                Assigned elements: ${elements.length}<br>
                Assigned nodes: ${nodes.length}<br>
                Element types: ${
                  elements.map((el) => el.tagName).join(', ') || 'none'
                }
            `;
    };

    // Listen for slot changes
    slot.addEventListener('slotchange', (e) => {
      console.log('Slot content changed');
      updateInfo();
    });

    // Initial update
    updateInfo();
  }
}

customElements.define('slot-observer', SlotObserver);

// ============================================
// EXAMPLE 6: CSS Custom Properties for Theming
// ============================================

class ThemeableButton extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });

    this.shadowRoot.innerHTML = `
            <style>
                :host {
                    /* Default values for custom properties */
                    --btn-bg: #3b82f6;
                    --btn-color: white;
                    --btn-padding: 10px 20px;
                    --btn-radius: 6px;
                    --btn-font-size: 14px;
                    --btn-hover-bg: #2563eb;
                    --btn-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
                    
                    display: inline-block;
                }
                
                button {
                    background: var(--btn-bg);
                    color: var(--btn-color);
                    padding: var(--btn-padding);
                    border-radius: var(--btn-radius);
                    font-size: var(--btn-font-size);
                    border: none;
                    cursor: pointer;
                    box-shadow: var(--btn-shadow);
                    transition: all 0.2s ease;
                    font-family: inherit;
                }
                
                button:hover {
                    background: var(--btn-hover-bg);
                    transform: translateY(-1px);
                    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
                }
                
                button:active {
                    transform: translateY(0);
                }
                
                /* Variant classes that can be set via attribute */
                :host([size="small"]) {
                    --btn-padding: 6px 12px;
                    --btn-font-size: 12px;
                }
                
                :host([size="large"]) {
                    --btn-padding: 14px 28px;
                    --btn-font-size: 16px;
                }
            </style>
            
            <button>
                <slot>Button</slot>
            </button>
        `;
  }
}

customElements.define('themeable-button', ThemeableButton);

/* External theming:
<style>
themeable-button.danger {
    --btn-bg: #ef4444;
    --btn-hover-bg: #dc2626;
}

themeable-button.success {
    --btn-bg: #22c55e;
    --btn-hover-bg: #16a34a;
}
</style>

<themeable-button>Default</themeable-button>
<themeable-button class="danger">Delete</themeable-button>
<themeable-button class="success" size="large">Confirm</themeable-button>
*/

// ============================================
// EXAMPLE 7: Event Handling Across Shadow Boundary
// ============================================

class EventBubbling extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });

    this.shadowRoot.innerHTML = `
            <style>
                :host {
                    display: block;
                    padding: 20px;
                    background: #f1f5f9;
                    border-radius: 8px;
                }
                
                button {
                    padding: 10px 20px;
                    margin: 5px;
                    border: none;
                    border-radius: 4px;
                    cursor: pointer;
                }
                
                .standard { background: #e2e8f0; }
                .composed { background: #bfdbfe; }
            </style>
            
            <p>Click the buttons and check the console:</p>
            <button class="standard">Standard Event</button>
            <button class="composed">Composed Event</button>
        `;
  }

  connectedCallback() {
    const standardBtn = this.shadowRoot.querySelector('.standard');
    const composedBtn = this.shadowRoot.querySelector('.composed');

    // Standard custom event - stops at shadow boundary
    standardBtn.addEventListener('click', () => {
      this.dispatchEvent(
        new CustomEvent('standard-click', {
          bubbles: true,
          composed: false, // Won't cross shadow boundary
          detail: { message: 'Standard event' },
        })
      );
    });

    // Composed custom event - crosses shadow boundary
    composedBtn.addEventListener('click', () => {
      this.dispatchEvent(
        new CustomEvent('composed-click', {
          bubbles: true,
          composed: true, // Crosses shadow boundary
          detail: { message: 'Composed event' },
        })
      );
    });

    // Internal click handler to show retargeting
    this.shadowRoot.addEventListener('click', (e) => {
      console.log('Inside shadow - event.target:', e.target);
    });
  }
}

customElements.define('event-bubbling', EventBubbling);

/* 
document.addEventListener('click', (e) => {
    // e.target will be the host element, not internal button
    console.log('Document - event.target:', e.target);
    console.log('Full path:', e.composedPath());
});
*/

// ============================================
// EXAMPLE 8: Focus Delegation
// ============================================

class FocusDelegate extends HTMLElement {
  constructor() {
    super();

    // Enable focus delegation
    this.attachShadow({
      mode: 'open',
      delegatesFocus: true,
    });

    this.shadowRoot.innerHTML = `
            <style>
                :host {
                    display: block;
                    padding: 16px;
                    border: 2px solid #e5e7eb;
                    border-radius: 8px;
                    transition: border-color 0.2s;
                }
                
                :host(:focus-within) {
                    border-color: #3b82f6;
                    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
                }
                
                input {
                    width: 100%;
                    padding: 10px;
                    border: 1px solid #d1d5db;
                    border-radius: 4px;
                    font-size: 14px;
                    box-sizing: border-box;
                }
                
                input:focus {
                    outline: none;
                    border-color: #3b82f6;
                }
            </style>
            
            <label>
                <slot name="label">Enter value:</slot>
            </label>
            <input type="text" placeholder="Focus delegates here">
        `;
  }
}

customElements.define('focus-delegate', FocusDelegate);

/* 
When you click anywhere on the component or call .focus(),
focus will automatically go to the first focusable element (input)
*/

// ============================================
// EXAMPLE 9: Adoptable Stylesheets
// ============================================

class AdoptedStyles extends HTMLElement {
  static styles = null;

  static {
    // Create shared stylesheet (only once)
    if ('adoptedStyleSheets' in Document.prototype) {
      const sheet = new CSSStyleSheet();
      sheet.replaceSync(`
                :host {
                    display: block;
                    padding: 16px;
                }
                
                .container {
                    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                    color: white;
                    padding: 20px;
                    border-radius: 8px;
                }
                
                h3 {
                    margin: 0 0 8px 0;
                }
                
                p {
                    margin: 0;
                    opacity: 0.9;
                }
            `);
      AdoptedStyles.styles = sheet;
    }
  }

  constructor() {
    super();
    this.attachShadow({ mode: 'open' });

    // Use adopted stylesheets if supported
    if (AdoptedStyles.styles) {
      this.shadowRoot.adoptedStyleSheets = [AdoptedStyles.styles];
    } else {
      // Fallback for older browsers
      this.shadowRoot.innerHTML = `<style>/* fallback styles */</style>`;
    }

    this.shadowRoot.innerHTML += `
            <div class="container">
                <h3><slot name="title">Title</slot></h3>
                <p><slot>Content</slot></p>
            </div>
        `;
  }
}

customElements.define('adopted-styles', AdoptedStyles);

// ============================================
// EXAMPLE 10: Part and Exportparts
// ============================================

class PartExample extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });

    this.shadowRoot.innerHTML = `
            <style>
                :host {
                    display: block;
                    padding: 16px;
                }
                
                .button {
                    padding: 10px 20px;
                    border: none;
                    border-radius: 4px;
                    background: #e5e7eb;
                    cursor: pointer;
                }
                
                .icon {
                    margin-right: 8px;
                }
                
                .label {
                    font-weight: 500;
                }
            </style>
            
            <button class="button" part="button">
                <span class="icon" part="icon">⭐</span>
                <span class="label" part="label">
                    <slot>Click me</slot>
                </span>
            </button>
        `;
  }
}

customElements.define('part-example', PartExample);

/* External styling with ::part:
<style>
part-example::part(button) {
    background: #3b82f6;
    color: white;
}

part-example::part(button):hover {
    background: #2563eb;
}

part-example::part(icon) {
    font-size: 1.2em;
}
</style>
*/

// ============================================
// EXAMPLE 11: Complex Shadow DOM Layout
// ============================================

class DashboardPanel extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });

    this.shadowRoot.innerHTML = `
            <style>
                :host {
                    --panel-bg: #ffffff;
                    --panel-border: #e5e7eb;
                    --header-bg: #f8fafc;
                    --header-color: #1e293b;
                    --content-padding: 20px;
                    
                    display: block;
                    background: var(--panel-bg);
                    border: 1px solid var(--panel-border);
                    border-radius: 12px;
                    overflow: hidden;
                    font-family: system-ui, sans-serif;
                }
                
                .header {
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    padding: 16px var(--content-padding);
                    background: var(--header-bg);
                    border-bottom: 1px solid var(--panel-border);
                }
                
                .title {
                    font-size: 16px;
                    font-weight: 600;
                    color: var(--header-color);
                    margin: 0;
                }
                
                .actions {
                    display: flex;
                    gap: 8px;
                }
                
                .content {
                    padding: var(--content-padding);
                    min-height: 100px;
                }
                
                .footer {
                    padding: 12px var(--content-padding);
                    background: var(--header-bg);
                    border-top: 1px solid var(--panel-border);
                    font-size: 12px;
                    color: #64748b;
                }
                
                /* Responsive */
                @media (max-width: 600px) {
                    :host {
                        --content-padding: 16px;
                    }
                    
                    .header {
                        flex-direction: column;
                        gap: 12px;
                    }
                }
                
                /* Hide empty footer */
                .footer:empty {
                    display: none;
                }
            </style>
            
            <div class="header">
                <h2 class="title">
                    <slot name="title">Panel Title</slot>
                </h2>
                <div class="actions">
                    <slot name="actions"></slot>
                </div>
            </div>
            
            <div class="content">
                <slot></slot>
            </div>
            
            <div class="footer">
                <slot name="footer"></slot>
            </div>
        `;
  }
}

customElements.define('dashboard-panel', DashboardPanel);

// ============================================
// USAGE DEMONSTRATION
// ============================================

function demonstrateShadowDOM() {
  console.log('=== Shadow DOM Demonstration ===\n');

  // Check support
  console.log('Shadow DOM supported:', 'attachShadow' in Element.prototype);

  console.log(
    'Adopted StyleSheets supported:',
    'adoptedStyleSheets' in Document.prototype
  );

  // Show registered components
  console.log('\nComponents with Shadow DOM:');
  const components = [
    'basic-shadow',
    'host-styling',
    'card-component',
    'themeable-button',
    'dashboard-panel',
  ];

  components.forEach((name) => {
    console.log(`  <${name}>`);
  });
}

// Export for module usage
if (typeof module !== 'undefined' && module.exports) {
  module.exports = {
    BasicShadow,
    OpenShadow,
    ClosedShadow,
    HostStyling,
    CardComponent,
    SlotObserver,
    ThemeableButton,
    EventBubbling,
    FocusDelegate,
    AdoptedStyles,
    PartExample,
    DashboardPanel,
    demonstrateShadowDOM,
  };
}
Examples - JavaScript Tutorial | DeepML