UNPKG

@trimble-oss/moduswebcomponents

Version:

Modus Web Components is a modern, accessible UI library built with Stencil JS that provides reusable web components following Trimble's Modus design system. This updated version focuses on improved flexibility, enhanced theming options, comprehensive cust

108 lines (107 loc) 3.83 kB
/** * Generic Shadow DOM Host Helper for Storybook * * This helper creates a reusable shadow host element that can wrap any Modus component * without naming conflicts between different stories. */ /** * Creates a generic shadow host component class. * Each story should create its own unique element name to avoid conflicts. * * @example * ```typescript * // In button.stories.ts * const ButtonShadowHost = createShadowHostClass('button-shadow-host', { * componentTag: 'modus-wc-button', * propsMapper: (args, el) => { * el.color = args.color; * el.size = args.size; * // ... other props * }, * defaultContent: 'Click me' * }); * * export const ShadowDomParent: Story = { * render: (args) => { * if (!customElements.get('button-shadow-host')) { * customElements.define('button-shadow-host', ButtonShadowHost); * } * return html`<button-shadow-host .props=${{ ...args }}></button-shadow-host>`; * } * }; * ``` */ export function createShadowHostClass(config) { return class ShadowHost extends HTMLElement { constructor() { super(); this.themeObserver = null; // Create shadow root this.shadowRootRef = this.attachShadow({ mode: 'open' }); // Create wrapper and component const wrapper = document.createElement('div'); // Make wrapper invisible in layout but keep it for data-theme attribute wrapper.style.display = 'contents'; // Sync data-theme attribute from document so CSS selectors work const syncTheme = () => { const theme = document.documentElement.getAttribute('data-theme'); if (theme) { wrapper.setAttribute('data-theme', theme); } }; syncTheme(); // Watch for theme changes this.themeObserver = new MutationObserver(syncTheme); this.themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'], }); this.componentEl = document.createElement(config.componentTag); // Add default content if provided if (config.defaultContent) { if (typeof config.defaultContent === 'string') { this.componentEl.textContent = config.defaultContent; } else { config.defaultContent.forEach((node) => { this.componentEl.appendChild(node); }); } } wrapper.appendChild(this.componentEl); this.shadowRootRef.appendChild(wrapper); } /** * Setter for props - called by Lit's .props=${} binding * This ensures Stencil updates without remounting */ set props(value) { if (!this.componentEl) return; this._props = value; // Apply props to component config.propsMapper(value, this.componentEl); } get props() { return this._props; } disconnectedCallback() { // Clean up observer to prevent memory leaks if (this.themeObserver) { this.themeObserver.disconnect(); this.themeObserver = null; } } }; } /** * Alternative: Simple function to create shadow host inline * Use this if you prefer inline class definition in stories */ export function createShadowHost(elementName, config) { if (customElements.get(elementName)) { return; // Already defined } const HostClass = createShadowHostClass(config); customElements.define(elementName, HostClass); }