UIP Core API
This document consolidates all internal technical details. Use the higher-level user guide at Core Concepts for consumer-facing information. This file targets authors extending or integrating deeply with UI Playground.
Table of Contents
- Architecture Overview
- Event Surface
- UIPRoot
- UIPPlugin Base
- State Model (UIPStateModel)
- Snippet Representation
- Persistent State (UIPStateStorage)
- Preview Integration Notes
- Attribute Mutation API
- Lifecycle Timeline
- Extensibility & Best Practices
- Performance Considerations
- Theme & Direction Handling
- Glossary
Architecture Overview
<uip-root>
├─ Template/script snippet holders (uip-snippet, uip-snippet-js, uip-snippet-note)
├─ UIPPreview (required) ← listens to uip:change, snippet switches
├─ Plugins (UIPPlugin descendants: editor, settings, list, etc.)
│ └─ Read model via root.model
│ └─ Mutate state via setHtml / setJS / changeAttribute / reset
└─ UIPStateModel (html, js, note, snippet registry, change queue)
└─ Emits internal events: uip:model:change / uip:model:snippet:change
└─ Normalization & rendering preprocessors
└─ Optional UIPStateStorage persistence
Flow summary:
- Plugin triggers a mutation on
UIPStateModel. - Model batches changes (microtask) and fires
uip:model:change. UIPRootwraps it intoUIPChangeEvent→uip:change.- Preview & other plugins react; preview may inline-render or iframe-refresh.
- Storage saves updated html/js/note if enabled.
Event Surface
| Public Event | Dispatcher | Payload | Purpose |
|---|---|---|---|
uip:root:ready | UIPRoot | none | Root initialized, initial snippet applied. |
uip:change | UIPRoot | UIPChangeEvent | Batched state change (html/js/note). |
uip:snippet:change | UIPRoot | { detail: UIPStateModel } | Active snippet changed. |
uip:theme:change | UIPRoot | { detail: boolean } | dark-theme toggled. |
uip:dirchange | UIPPreview | none | dir attribute updated on preview. |
Internal (do not rely externally unless extending core):
uip:model:change(target:UIPStateModel)uip:model:snippet:change(target:UIPStateModel)
UIPChangeEvent convenience API:
changes: UIPChangeInfo[]force: booleanjsChanges / htmlChangesisOnlyModifier(modifier)
UIPRoot
Responsibilities:
- Collect snippet template/script holders.
- Resolve initial active snippet (URL hash > explicit
active> first). - Expose shared
model(memoized instance ofUIPStateModel). - Mediate internal model events → stable public events.
- Manage theme (
dark-themeattribute) & optionally persistence viastore-key.
Attributes:
| Attribute | Type | Meaning |
|---|---|---|
dark-theme | boolean | Dark UI theme state. Emits uip:theme:change. |
store-key | string | Enables persistence storage namespace. |
ready | boolean (readonly) | Set true after initial snippet applied. |
Getters / Properties:
model: UIPStateModel(memoized)$snippets: UIPSnippetTemplate[]raw DOM snippet holdersstorage?: UIPStateStorageactive only ifstore-keypresent
Lifecycle key points:
- Connect → optionally construct storage.
- Assign
model.snippets. - Apply current snippet (
anchor>active> index 0). - Mark
readyand dispatchuip:root:ready. - Scroll into view if anchor-based activation.
- Disconnect → clear model snippets.
UIPPlugin
Abstract base for all plugins.
Key features:
- Adds
uip-pluginclass on connect. - Provides
$rootmemoized reference via traversing query inrootattribute. - Provides
modelgetter delegating to$root.model.
Attributes:
| Attr | Default | Purpose |
|---|---|---|
label | '' | Human-readable label, mirrored to aria-label. |
root | ::parent(uip-root) | Traversal query to resolve nearest root. |
Overridable hooks: standard CustomElement lifecycle plus attribute changes.
Recommended event subscription pattern:
@listen({event: 'uip:change', target: (that: MyPlugin) => that.$root})
_onChange(e: UIPChangeEvent) { /* react */ }
Guidelines:
- Always pass
thisas modifier in model mutations for self-change filtering. - Avoid direct DOM mutation of preview output; use state model.
- Use microtask batching naturally (multiple sequential mutations collapse into one event).
UIPStateModel
State container for: current html/js/note, snippet registry, and batched change metadata.
Public getters:
| Getter | Type | Notes |
|---|---|---|
html | string | Current normalized HTML inner markup. |
js | string | Current normalized JS. |
note | string | Current normalized note content. |
snippets | UIPSnippetItem[] | All snippet wrappers. |
activeSnippet | UIPSnippetItem? | Active snippet wrapper. |
anchorSnippet | UIPSnippetItem? | Snippet resolved by URL hash. |
Mutation API:
| Method | Purpose |
|---|---|
setHtml(markup, modifier, force?) | Update current HTML (optional full refresh flag). |
setJS(js, modifier) | Update JS (forces preview rebuild when isolated). |
setNote(text, modifier) | Update note. |
changeAttribute(cfg) | Batch attribute updates across selected nodes. |
getAttribute(selector, attr) | Collect attribute values from live HTML mirror. |
reset(source, modifier) | Reset html or js to original snippet content. |
applySnippet(snippet, modifier) | Switch active snippet; fires snippet change event. |
applyCurrentSnippet(modifier) | Helper to choose resolved active snippet. |
isHTMLChanged() / isJSChanged() | Compare current edited vs original snippet. |
Batching: _changes array collects metadata; dispatchChange (microtask) flushes once per tick.
Change attribution: modifier object allows plugins to ignore self-generated updates (e.isOnlyModifier(this)).
Normalization:
- HTML: extracts
<style>tags from<head>and injects at top of body (reverse order). - JS / Note: passes through configured preprocessors (e.g., trimming, indentation fixes).
Snippet Representation
A snippet comprises:
- Template or script element marked
uip-snippet(HTML body) - Optional JS container:
<script type="text/plain" id="..." uip-snippet-js>referenced by ID attribute on template - Optional note container:
<script type="text/plain" id="..." uip-snippet-note>
Snippet attributes:
| Attr | Meaning |
|---|---|
label | Display name. |
active | Preferred initial active snippet if no anchor. |
anchor | Deep link slug (#hash). |
uip-snippet-js | ID of JS source holder. |
uip-snippet-note | ID of note holder. |
uip-isolated | Forces iframe rendering. |
isolated-template | Named template key for iframe HTML wrapper. |
uip-js-readonly | Disables JS editing (auto true if not isolated). |
UIPSnippetItem exposes memoized getters: html, js, note, isolated, isolatedTemplate, label, anchor, active (get/set), isJsReadonly.
Activation priority: URL hash > element with active > first snippet.
Persistent State (UIPStateStorage)
Optional layer keyed by combination of store-key + original snippet HTML to avoid collisions after snippet content updates.
Key characteristics:
| Aspect | Description |
|---|---|
| Storage bucket | localStorage['uip-editor-storage'] JSON map |
| Entry schema | { ts, snippets } where snippets holds serialized { js, html, note } |
| Expiration | 12 hours (entry dropped on access if stale) |
| Load trigger | uip:model:snippet:change |
| Save trigger | uip:model:change |
Public methods: loadState(), saveState(), resetState(source).
Reset flow invalidates entry then calls model.reset(source, this).
Resetting the State
Use the reset plugin (<uip-reset>) or remove persisted state (store-key) to revert edited HTML/JS back to the original snippet source.
Preview Integration
Preview listens to uip:change and renders HTML either inline or via iframe for isolated snippets.
Isolated mode distinctions:
- Full refresh scenarios: snippet switch (
force),force-updateattribute, explicitforceflag insetHtmlcall. - Incremental update: modifies existing iframe body root content.
- Resize loop: rAF-based content height mirroring (gated by intersection visibility &
resizeLoopflag).
For user-focused behaviors (attributes & usage) see ../preview/README.md.
Attribute Mutation API
changeAttribute(cfg: ChangeAttrConfig) enables structured modifications within the current HTML mirror without reparsing full markup.
ChangeAttrConfig:
{
target: string; // CSS selector
attribute: string; // Attribute to set/toggle
modifier: object; // Initiator (plugin instance)
} & (
{ value: string | boolean } | { transform: (current: string | null) => string | boolean | null }
);
Semantics:
- Boolean results from
transformset/remove attribute presence. nullresult leaves attribute untouched for that element.- Pushes an
htmlchange entry and schedules batched dispatch.
Lifecycle Timeline
- DOM Construction: Snippet templates and plugin elements appended inside
<uip-root>. - Root Connected: Storage (optional) created; snippet collector runs.
- Initial Snippet Apply: Model populated, events batched →
uip:root:ready. - User Interaction: Plugins mutate model; preview & others update.
- Snippet Switch:
applySnippetsets html/js/note and triggers refresh logic. - Disconnect: Root clears snippet registry (preventing stale mutations).
Extensibility & Best Practices
| Goal | Recommendation |
|---|---|
| Add new tool panel | Extend UIPPlugin or existing panel abstraction; subscribe to uip:change. |
| Apply style variants | Use root-level theme class toggles; avoid inline style mutations of preview DOM. |
| Track self vs external changes | Use e.isOnlyModifier(this) on uip:change. |
| Complex multi-step edits | Chain model mutations sequentially; rely on microtask batching. |
| Undo/redo | Maintain external stack keyed off UIPChangeEvent.changes. |
| Large snippet sets | Lazy inject snippet templates before they are needed; then re-run snippet assignment if dynamic. |
Avoid:
- Direct
innerHTMLwrites to preview output outside model (causes divergence). - Long-running synchronous processors (blocks microtask flush & UI responsiveness).
Performance Considerations
- Microtask batching ensures multiple rapid mutations group into one paint-impacting cycle.
- Iframe resize loop stops when preview scrolled out (intersection gating) minimizing layout thrash.
- Normalization pre-processors should remain O(n) and stateless; customize sparingly.
- Attribute changes via
changeAttributeavoid serializing full markup string repeatedly.
Theme & Direction Handling
| Concern | Mechanism |
|---|---|
| Dark mode | Toggle dark-theme attribute on <uip-root> → event consumers can react (uip:theme:change). |
| Text direction | Set dir on <uip-preview> (mirrors to inner container) → uip:dirchange. |
Plugins should prefer CSS variables / contextual classes over inline style rewrites for theme adjustments.
Glossary
| Term | Meaning |
|---|---|
| Snippet | Self-contained example (HTML + optional JS + note). |
| Isolated Snippet | Snippet rendered inside an iframe sandbox. |
| Modifier | Object passed to model mutation APIs to identify change origin. |
| Force Refresh | A change requiring full iframe rebuild (e.g., snippet switch). |
| Storage Key | User-provided namespace enabling persistence. |
Migration Notes
All previous per-topic markdown files now redirect here. External links will continue working via legacy anchors. For the user-facing overview see ../README.md.
Return to the User Overview or inspect the Preview User Guide.