@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
JavaScript
/**
* 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);
}