TypeScript, testing, and custom development patterns
The components library provides comprehensive TypeScript support with full type definitions.
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';
// 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
}
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;
};
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
Components emit custom events for lifecycle and interaction handling.
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');
});
Components support reactive updates through 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 });
// 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);
});
Best practices for testing components in your application.
// __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();
});
});
Extend the component library with your own custom components.
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
});
Techniques for optimizing component performance in production.
// 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);
}
// 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']
}
}
}
}
};
loading="lazy"
attribute on images within
components