feat: add async demo support and enhance template resolution

- Introduced async demo functionality in the README, allowing for asynchronous data preparation before rendering components.
- Updated WccDashboard, WccProperties, and WccSidebar to support Promise-based template factories.
- Implemented resolveTemplateFactory to handle both synchronous and asynchronous template results.
- Added tests for resolveTemplateFactory to ensure correct behavior for both sync and async templates.
- Updated pnpm workspace configuration.
This commit is contained in:
2025-09-19 13:02:16 +00:00
parent 7148b12066
commit ca28dbd9db
11 changed files with 3931 additions and 1801 deletions

View File

@@ -17,18 +17,20 @@
"author": "Lossless GmbH", "author": "Lossless GmbH",
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
"@design.estate/dees-domtools": "^2.0.57", "@design.estate/dees-domtools": "^2.3.3",
"@design.estate/dees-element": "^2.0.34", "@design.estate/dees-element": "^2.1.2",
"@push.rocks/smartdelay": "^3.0.5", "@push.rocks/smartdelay": "^3.0.5",
"lit": "^3.1.3" "lit": "^3.3.1"
}, },
"devDependencies": { "devDependencies": {
"@api.global/typedserver": "^3.0.29", "@api.global/typedserver": "^3.0.79",
"@git.zone/tsbuild": "^2.1.72", "@git.zone/tsbuild": "^2.6.8",
"@git.zone/tsbundle": "^2.0.15", "@git.zone/tsbundle": "^2.5.1",
"@git.zone/tsrun": "^1.2.44", "@git.zone/tsrun": "^1.2.44",
"@git.zone/tswatch": "^2.0.23", "@git.zone/tstest": "^2.3.8",
"@push.rocks/projectinfo": "^5.0.2" "@git.zone/tswatch": "^2.2.1",
"@push.rocks/projectinfo": "^5.0.2",
"@types/node": "^22.18.6"
}, },
"files": [ "files": [
"ts/**/*", "ts/**/*",

5624
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

4
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,4 @@
onlyBuiltDependencies:
- esbuild
- mongodb-memory-server
- puppeteer

View File

@@ -211,6 +211,19 @@ export class MyComponent extends DeesElement {
} }
``` ```
### ⏳ Async Demos
If your catalogue needs additional setup before rendering, return a `Promise` from the `demo` function. The dashboard waits for the result before inserting it into the viewport:
```typescript
public static demo = async () => {
await Promise.resolve(); // e.g. fetch data, load fixtures, or await wrappers
return html`<my-component .value=${'Loaded asynchronously'}></my-component>`;
};
```
The same pattern works for page factories you pass into `setupWccTools`, enabling asynchronous data preparation across the entire demo surface.
### 🎭 Container Queries Support ### 🎭 Container Queries Support
Components can respond to their container size: Components can respond to their container size:

View File

@@ -98,3 +98,10 @@ Properties panel was overwriting values set by demo functions
3. Added proper number parsing for number inputs 3. Added proper number parsing for number inputs
4. Increased initial wait to 200ms for demo wrappers to complete 4. Increased initial wait to 200ms for demo wrappers to complete
5. Simplified select element handling to use property binding 5. Simplified select element handling to use property binding
# Async Demo Support (IN PROGRESS)
## Tasks
- [ ] Allow dashboard-selected items to return Promise-based TemplateResults
- [ ] Await async demos/pages before rendering them into the viewport
- [ ] Add regression test covering async demo usage
- [ ] Document async demo pattern in README and verify with pnpm scripts

View File

@@ -0,0 +1,22 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import { resolveTemplateFactory } from '../ts_web/elements/wcctools.helpers.js';
import { html } from 'lit';
const waitFor = (durationMs: number) => new Promise(resolve => setTimeout(resolve, durationMs));
tap.test('resolveTemplateFactory returns sync TemplateResult', async () => {
const template = html`<p>sync demo</p>`;
const resolvedTemplate = await resolveTemplateFactory(() => template);
expect(resolvedTemplate).toEqual(template);
});
tap.test('resolveTemplateFactory awaits async TemplateResult', async () => {
const template = html`<p>async demo</p>`;
const resolvedTemplate = await resolveTemplateFactory(async () => {
await waitFor(5);
return template;
});
expect(resolvedTemplate).toEqual(template);
});
export default tap.start();

View File

@@ -1,4 +1,6 @@
import { DeesElement, property, html, customElement, type TemplateResult, queryAsync, render, domtools } from '@design.estate/dees-element'; import { DeesElement, property, html, customElement, type TemplateResult, queryAsync, render, domtools } from '@design.estate/dees-element';
import { resolveTemplateFactory } from './wcctools.helpers.js';
import type { TTemplateFactory } from './wcctools.helpers.js';
import * as plugins from '../wcctools.plugins.js'; import * as plugins from '../wcctools.plugins.js';
@@ -21,7 +23,7 @@ export class WccDashboard extends DeesElement {
public selectedItemName: string; public selectedItemName: string;
@property() @property()
public selectedItem: (() => TemplateResult) | DeesElement; public selectedItem: TTemplateFactory | DeesElement;
@property() @property()
public selectedViewport: plugins.deesDomtools.breakpoints.TViewport = 'desktop'; public selectedViewport: plugins.deesDomtools.breakpoints.TViewport = 'desktop';
@@ -33,7 +35,7 @@ export class WccDashboard extends DeesElement {
public isFullscreen: boolean = false; public isFullscreen: boolean = false;
@property() @property()
public pages: { [key: string]: () => TemplateResult } = {}; public pages: Record<string, TTemplateFactory> = {};
@property() @property()
public elements: { [key: string]: DeesElement } = {}; public elements: { [key: string]: DeesElement } = {};
@@ -50,7 +52,7 @@ export class WccDashboard extends DeesElement {
constructor( constructor(
elementsArg?: { [key: string]: DeesElement }, elementsArg?: { [key: string]: DeesElement },
pagesArg?: { [key: string]: () => TemplateResult } pagesArg?: Record<string, TTemplateFactory>
) { ) {
super(); super();
if (elementsArg) { if (elementsArg) {
@@ -201,7 +203,9 @@ export class WccDashboard extends DeesElement {
if (typeof this.selectedItem === 'function') { if (typeof this.selectedItem === 'function') {
console.log('slotting page.'); console.log('slotting page.');
const viewport = await wccFrame.getViewportElement(); const viewport = await wccFrame.getViewportElement();
render(this.selectedItem(), viewport); const pageFactory = this.selectedItem as TTemplateFactory;
const pageTemplate = await resolveTemplateFactory(pageFactory);
render(pageTemplate, viewport);
console.log('rendered page.'); console.log('rendered page.');
} else { } else {
console.error('The selected item looks strange:'); console.error('The selected item looks strange:');
@@ -222,7 +226,8 @@ export class WccDashboard extends DeesElement {
} }
this.setWarning(null); this.setWarning(null);
const viewport = await wccFrame.getViewportElement(); const viewport = await wccFrame.getViewportElement();
render(anonItem.demo(), viewport);; const demoTemplate = await resolveTemplateFactory(() => anonItem.demo());
render(demoTemplate, viewport);
} }
} }

View File

@@ -1,5 +1,6 @@
import { DeesElement, property, html, customElement, type TemplateResult, state } from '@design.estate/dees-element'; import { DeesElement, property, html, customElement, type TemplateResult, state } from '@design.estate/dees-element';
import { WccDashboard } from './wcc-dashboard.js'; import { WccDashboard } from './wcc-dashboard.js';
import type { TTemplateFactory } from './wcctools.helpers.js';
export type TPropertyType = 'String' | 'Number' | 'Boolean' | 'Object' | 'Enum' | 'Array'; export type TPropertyType = 'String' | 'Number' | 'Boolean' | 'Object' | 'Enum' | 'Array';
@@ -20,7 +21,7 @@ export class WccProperties extends DeesElement {
public dashboardRef: WccDashboard; public dashboardRef: WccDashboard;
@property() @property()
public selectedItem: (() => TemplateResult) | DeesElement; public selectedItem: TTemplateFactory | DeesElement;
@property() @property()
public selectedViewport: TEnvironment = 'native'; public selectedViewport: TEnvironment = 'native';

View File

@@ -1,13 +1,14 @@
import * as plugins from '../wcctools.plugins.js'; import * as plugins from '../wcctools.plugins.js';
import { DeesElement, property, html, customElement, type TemplateResult } from '@design.estate/dees-element'; import { DeesElement, property, html, customElement, type TemplateResult } from '@design.estate/dees-element';
import { WccDashboard } from './wcc-dashboard.js'; import { WccDashboard } from './wcc-dashboard.js';
import type { TTemplateFactory } from './wcctools.helpers.js';
export type TElementType = 'element' | 'page'; export type TElementType = 'element' | 'page';
@customElement('wcc-sidebar') @customElement('wcc-sidebar')
export class WccSidebar extends DeesElement { export class WccSidebar extends DeesElement {
@property({ attribute: false }) @property({ attribute: false })
public selectedItem: DeesElement | (() => TemplateResult); public selectedItem: DeesElement | TTemplateFactory;
@property({ attribute: false }) @property({ attribute: false })
public selectedType: TElementType; public selectedType: TElementType;
@@ -202,7 +203,7 @@ export class WccSidebar extends DeesElement {
`; `;
} }
public selectItem(typeArg: TElementType, itemNameArg: string, itemArg: (() => TemplateResult) | DeesElement) { public selectItem(typeArg: TElementType, itemNameArg: string, itemArg: TTemplateFactory | DeesElement) {
console.log('selected item'); console.log('selected item');
console.log(itemNameArg); console.log(itemNameArg);
console.log(itemArg); console.log(itemArg);

View File

@@ -0,0 +1,9 @@
import type { TemplateResult } from 'lit';
export type TTemplateFactory = () => TemplateResult | Promise<TemplateResult>;
export const resolveTemplateFactory = async (
factoryArg: TTemplateFactory
): Promise<TemplateResult> => {
return await Promise.resolve(factoryArg());
};

View File

@@ -1,7 +1,11 @@
import { WccDashboard } from './elements/wcc-dashboard.js'; import { WccDashboard } from './elements/wcc-dashboard.js';
import { LitElement, type TemplateResult } from 'lit'; import { LitElement } from 'lit';
import type { TTemplateFactory } from './elements/wcctools.helpers.js';
const setupWccTools = (elementsArg?: { [key: string]: LitElement }, pagesArg?: { [key: string]: () => TemplateResult }) => { const setupWccTools = (
elementsArg?: { [key: string]: LitElement },
pagesArg?: Record<string, TTemplateFactory>
) => {
let hasRun = false; let hasRun = false;
const runWccToolsSetup = async () => { const runWccToolsSetup = async () => {
if (document.readyState === 'complete' && !hasRun) { if (document.readyState === 'complete' && !hasRun) {