@wippy-fe/webcomponent-core
v0.0.17
Published
Framework-agnostic base class for Wippy web components — shadow DOM, CSS loading, schema-driven prop parsing, and lifecycle management.
Downloads
1,150
Readme
@wippy-fe/webcomponent-core
Framework-agnostic base class for building Wippy web components. Handles the boilerplate every component needs — shadow DOM, CSS loading, schema-driven prop parsing, ElementInternals state, and lifecycle events — so your subclass only deals with framework-specific mounting.
What it does
- Shadow DOM setup with configurable mode (
open/closed) and container<div> - Host CSS inheritance — loads Wippy platform CSS into the shadow root so the component matches the host theme
- Inline CSS injection — injects component-specific styles from
?inlineimports - Schema-driven prop parsing — reads
wippy.propsfrom package.json and auto-converts attributes to typed JS values - ElementInternals — manages
loading→ready/errorstates - Lifecycle hooks —
onInit,onMount,onReady,onError,onUnmount,onPropsChanged - Lifecycle events — emits
load,unload,erroras CustomEvents that cross shadow boundaries - Icon registration — calls
addIcons(addCollection)from@wippy-fe/proxy
What it does NOT do
- Any framework setup (Vue, React, Svelte, etc.) — that's for framework-specific packages like
@wippy-fe/webcomponent-vue - Component registration — use
define(import.meta.url, YourElement)from this package - State management — the base class is stateless; your framework layer manages state
CSS: How Styling Works in Shadow DOM
Shadow DOM blocks style inheritance from the host page. A web component must explicitly bring in any styles it needs. There are two mechanisms:
Inline CSS (inlineCss)
Your component's own styles — Tailwind utilities, custom classes, layout rules. Bundled at build time via Vite's ?inline import and injected synchronously into the shadow root before mount.
import stylesText from './styles.css?inline'
static get wippyConfig() {
return {
propsSchema: pkg.wippy.props,
inlineCss: stylesText, // your component's CSS, injected immediately
}
}Every component with its own stylesheet needs this.
Host CSS Inheritance (hostCssKeys)
Shared platform CSS loaded at runtime from the Wippy host into the shadow root. This is how your component inherits the host app's look-and-feel. Loaded asynchronously (non-blocking — the component becomes interactive before CSS finishes loading).
| Key | What it provides | When to include |
|-----|-----------------|-----------------|
| fontCssUrl | Platform font definitions | Almost always — skip only if using fully custom fonts |
| themeConfigUrl | CSS custom properties (color scales, spacing, radii, etc.) matching the host theme | Recommended for all components. This is what makes your component look consistent with the host. At dev time, a local theme-config.css provides fallback values; at runtime the host injects the real theme. |
| primeVueCssUrl | PrimeVue component classes (p-button, p-input, etc.) in unstyled mode, styled to match the host | Include if using any PrimeVue components (buttons, forms, tables, etc.). Skip only for fully custom UI with zero PrimeVue usage. |
| markdownCssUrl | Styles for rendered markdown blocks | Include only if rendering markdown. |
| iframeCssUrl | Scrollbar styling and iframe-related styles | Recommended for all components so scrollbars match the host. |
Choose what your component actually uses:
// Standard component using PrimeVue UI (most common)
hostCssKeys: ['fontCssUrl', 'themeConfigUrl', 'primeVueCssUrl', 'iframeCssUrl']
// Also renders markdown content
hostCssKeys: ['fontCssUrl', 'themeConfigUrl', 'primeVueCssUrl', 'markdownCssUrl', 'iframeCssUrl']
// Minimal: just theme variables, no PrimeVue
hostCssKeys: ['fontCssUrl', 'themeConfigUrl', 'iframeCssUrl']
// Fully self-styled, no host inheritance
hostCssKeys: []Default (when omitted): all five keys.
Note on dev-time duplication: Your component's
styles.cssmay importtheme-config.cssfor local dev (so Tailwind/PostCSS can resolve theme variables). At runtime, the host provides the real theme viathemeConfigUrl. This duplication is a known trade-off — the host's runtime values take precedence.
API Reference
WippyElement (abstract class)
Extend this class and implement the hooks you need.
Static getters to override
static get wippyConfig(): WippyElementConfigReturns the component configuration. Must be overridden — the default returns an empty schema.
static get observedAttributes(): string[]Automatically derived from wippyConfig.propsSchema.properties + extraObservedAttributes. You rarely need to override this.
Lifecycle hooks
All hooks are optional except onMount and onUnmount (abstract).
| Hook | When it runs | Use case |
|------|-------------|----------|
| onInit(shadow) | After shadow DOM attached, before CSS/container | Add extra DOM elements, configure shadow root |
| onMount(shadow, container, props, errors) | After CSS, container, icons, and props are ready | Abstract. Mount your framework here |
| onReady() | After internals state → ready, before load event | Post-mount logic, telemetry, deferred setup |
| onError(error) | When connectedCallback throws | Custom error reporting (default: console.error) |
| onUnmount() | During disconnectedCallback | Abstract. Tear down framework |
| onPropsChanged(props, errors) | When observed attributes change | Push new values into framework reactivity |
Full lifecycle order:
- Shadow DOM attached (
openorclosed) onInit(shadow)- Inline CSS injected (sync)
- Host CSS loading started (async, non-blocking)
- Container
<div>created and appended - Icons registered
- Props parsed from attributes
onMount(shadow, container, props, errors)- Internals state →
ready onReady()loadevent emitted
On error: onError(error) → state → error → error event
On disconnect: onUnmount() → unload event → states cleared
Utility
protected emitEvent(eventName: string, detail?: unknown): voidDispatches a CustomEvent with bubbles: true, composed: true.
define(importMetaUrl, ComponentClass)
Re-exported from @wippy-fe/proxy. Registers the custom element if the import URL contains a declare-tag parameter.
parseProps(element, schema)
Parses all attributes on an element according to a WippyPropsSchema. Returns { props, errors }.
loadHostCss(shadow, keys?)
Loads host CSS URLs into a shadow root. Non-blocking (returns a Promise).
injectInlineCss(shadow, text)
Injects a <style> element with the given CSS text into the shadow root. Synchronous.
attrToCamel(attr)
Converts kebab-case (allowed-types) to camelCase (allowedTypes).
Types
interface WippyElementConfig {
propsSchema: WippyPropsSchema
shadowMode?: 'open' | 'closed' // default: 'open'
hostCssKeys?: HostCssKey[] // default: font + theme + primeVue + markdown
inlineCss?: string
containerClasses?: string[] // default: none
extraObservedAttributes?: string[]
}
interface WippyPropsSchema {
type?: string
properties: Record<string, WippyPropDefinition>
}
interface WippyPropDefinition {
type: 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object'
default?: unknown
description?: string
items?: { type: string }
}
type HostCssKey = 'fontCssUrl' | 'themeConfigUrl' | 'primeVueCssUrl' | 'markdownCssUrl' | 'iframeCssUrl'Migration from monolithic pattern
Before — 170+ lines of boilerplate in every component:
class MyElement extends HTMLElement {
// Manual shadow DOM, CSS loading, prop parsing, Vue setup, ...
}After — extend WippyElement (or WippyVueElement for Vue components):
class MyElement extends WippyElement {
static get wippyConfig() {
return {
propsSchema: pkg.wippy.props,
hostCssKeys: ['fontCssUrl', 'themeConfigUrl', 'primeVueCssUrl', 'iframeCssUrl'],
containerClasses: ['h-full'],
inlineCss: stylesText,
}
}
onMount(shadow, container, props, errors) { /* framework setup */ }
onUnmount() { /* cleanup */ }
}