Advanced Component Usage

TypeScript, testing, and custom development patterns

TypeScript Integration

The components library provides comprehensive TypeScript support with full type definitions.

Type Imports

import { 
  Components,
  ComponentRef,
  SlotConfiguration,
  ComponentEvents,
  CreateComponentFunction,
  TypedComponentRef
} from '@universityofmaryland/web-components-library';

// Component-specific types
import type { 
  HeroProps,
  CardProps,
  NavigationConfig 
} from '@universityofmaryland/web-components-library/types';

Typed Component References

// Get typed component reference
const hero = document.querySelector<HTMLElement & ComponentEvents>('umd-element-hero');

if (hero) {
  // TypeScript knows about component events
  hero.addEventListener('component:ready', (event) => {
    console.log('Hero component initialized', event.detail);
  });
  
  // Set attributes with type safety
  hero.setAttribute('data-theme', 'dark'); // Valid
  // hero.setAttribute('data-theme', 'invalid'); // TypeScript error
}

Creating Components Programmatically

import { Components } from '@universityofmaryland/web-components-library';

// Initialize components
Components.hero.base();

// Create element programmatically with type safety
const createTypedHero = (): HTMLElement => {
  const hero = document.createElement('umd-element-hero') as HTMLElement & {
    setTheme: (theme: 'light' | 'dark') => void;
    updateContent: (content: HeroContent) => void;
  };
  
  hero.setAttribute('data-theme', 'dark');
  hero.setAttribute('data-display', 'overlay');
  
  return hero;
};
Key Types:
  • ComponentRef - Base reference returned by component factories
  • SlotConfiguration - Defines allowed slots and their constraints
  • ComponentEvents - Interface for component event handling
  • CreateComponentFunction - Standard signature for component creation

Event Handling

Components emit custom events for lifecycle and interaction handling.

Component Lifecycle Events

const card = document.querySelector('umd-element-card');

// Component initialization
card.addEventListener('component:ready', (event) => {
  console.log('Component initialized with:', event.detail);
});

// Content changes
card.addEventListener('component:updated', (event) => {
  console.log('Component updated:', event.detail.changes);
});

// Before removal
card.addEventListener('component:destroy', (event) => {
  console.log('Component being removed');
});

Reactive Components

Components support reactive updates through observed attributes.

Observed Attributes

// Visual state changes
const drawer = document.querySelector('umd-element-navigation-drawer');

// Open drawer programmatically
drawer.setAttribute('is-visual-open', 'true');

// Close drawer
drawer.setAttribute('is-visual-open', 'false');

// Listen for state changes
const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    if (mutation.attributeName === 'is-visual-open') {
      const isOpen = drawer.getAttribute('is-visual-open') === 'true';
      console.log('Drawer state:', isOpen ? 'open' : 'closed');
    }
  });
});

observer.observe(drawer, { attributes: true });

Responsive Behavior

// Trigger component resize calculations
const hero = document.querySelector('umd-element-hero');

// Manual resize trigger
hero.setAttribute('resize', 'true');

// Automatic resize handling
const resizeObserver = new ResizeObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.target.tagName.startsWith('UMD-ELEMENT-')) {
      entry.target.setAttribute('resize', 'true');
    }
  });
});

// Observe all components
document.querySelectorAll('[class*="umd-element-"]').forEach((element) => {
  resizeObserver.observe(element);
});

Testing Components

Best practices for testing components in your application.

Jest Testing Setup

// __tests__/components.test.js
import { Components } from '@universityofmaryland/web-components-library';

describe('UMD Components', () => {
  beforeEach(() => {
    document.body.innerHTML = '';
    Components.card.standard();
  });
  
  test('creates card component', () => {
    const card = document.createElement('umd-element-card');
    card.innerHTML = `
      

Test Headline

Test content

`; document.body.appendChild(card); expect(card.shadowRoot).toBeTruthy(); expect(card.querySelector('[slot="headline"]').textContent) .toBe('Test Headline'); }); test('handles missing required slots', () => { const card = document.createElement('umd-element-card'); // Missing headline slot card.innerHTML = '

Test content

'; // Component should still render document.body.appendChild(card); expect(card.shadowRoot).toBeTruthy(); }); });

Building Custom Components

Extend the component library with your own custom components.

Custom Component Template

import { 
  Register, 
  Slots, 
  Attributes,
  CommonSlots
} from '@universityofmaryland/web-components-library/core';

// Define your component
const tagName = 'umd-custom-feature';

const slots = {
  headline: CommonSlots.headline,
  text: CommonSlots.text,
  media: {
    allowedElements: ['img', 'video', 'iframe'],
    required: false
  }
};

const createComponent = (element) => {
  const headline = Slots.headline.default({ element });
  const text = Slots.text.default({ element });
  const media = element.querySelector('[slot="media"]');
  
  // Create shadow DOM structure
  const template = `
    
    
${headline ? `

${headline.outerHTML}

` : ''} ${media ? `
${media.outerHTML}
` : ''} ${text ? `
${text.outerHTML}
` : ''}
`; return { element: element, template: template }; }; // Register the component export default Register.webComponent({ tagName, slots, createComponent });

Performance Optimization

Techniques for optimizing component performance in production.

Lazy Loading Components

// Load components only when needed
const loadHeroComponents = () => 
  import('@universityofmaryland/web-components-library').then(({ Components }) => {
    Components.hero.base();
    Components.hero.expand();
  });

// Intersection Observer for lazy loading
const heroObserver = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      loadHeroComponents().then(() => {
        console.log('Hero components loaded');
      });
      heroObserver.disconnect();
    }
  });
});

// Observe hero section
const heroSection = document.querySelector('.hero-section');
if (heroSection) {
  heroObserver.observe(heroSection);
}

Bundle Optimization

// webpack.config.js
module.exports = {
  optimization: {
    splitChunks: {
      cacheGroups: {
        umdComponents: {
          test: /[\\/]node_modules[\\/]@universityofmaryland[\\/]/,
          name: 'umd-components',
          priority: 10,
          chunks: 'all'
        }
      }
    }
  }
};

// Vite configuration
export default {
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          'umd-components': ['@universityofmaryland/web-components-library'],
          'umd-styles': ['@universityofmaryland/web-styles-library']
        }
      }
    }
  }
};
💡 Performance Tips
  • Load only the components you use
  • Use dynamic imports for below-the-fold components
  • Enable production mode for smaller bundles
  • Consider server-side rendering for SEO-critical content
  • Use the loading="lazy" attribute on images within components