` | Callback executed after content renders |
+
+### Example: Basic Usage
+
+```typescript
+import { html } from 'lit';
+import '@design.estate/dees-wcctools/demotools';
+
+public static demo = () => html`
+ {
+ const button = wrapper.querySelector('my-button');
+ console.log('Button found:', button);
+ }}>
+ Click Me
+
+`;
+```
+
+### Example: Async Operations
+
+```typescript
+public static demo = () => html`
+ {
+ const form = wrapper.querySelector('my-form');
+
+ // Wait for component initialization
+ await form.updateComplete;
+
+ // Simulate user input
+ form.values = { name: 'Test User', email: 'test@example.com' };
+
+ // Trigger validation
+ await form.validate();
+
+ console.log('Form state:', form.isValid);
+ }}>
+
+
+`;
+```
+
+### Example: Multiple Elements
+
+```typescript
+public static demo = () => html`
+ {
+ // Find all cards
+ const cards = wrapper.querySelectorAll('my-card');
+ console.log(`Found ${cards.length} cards`);
+
+ // Access by index
+ Array.from(wrapper.children).forEach((child, i) => {
+ console.log(`Child ${i}:`, child.tagName);
+ });
+
+ // Add event listeners
+ wrapper.querySelectorAll('button').forEach(btn => {
+ btn.addEventListener('click', () => console.log('Clicked!'));
+ });
+ }}>
+
+
+
+
+`;
+```
+
+### Example: Component State Manipulation
+
+```typescript
+public static demo = () => html`
+ {
+ const tabs = wrapper.querySelector('my-tabs');
+
+ // Programmatically switch tabs
+ tabs.activeTab = 'settings';
+ await tabs.updateComplete;
+
+ // Verify content updated
+ const content = tabs.shadowRoot.querySelector('.tab-content');
+ console.log('Active content:', content.textContent);
+ }}>
+
+ Home Content
+ Settings Content
+
+
+`;
+```
+
+## How It Works
+
+1. The wrapper renders its slot content immediately
+2. After a brief delay (50ms) to allow slotted content to initialize
+3. The `runAfterRender` callback is invoked with the wrapper element
+4. You have full DOM API access to query and manipulate children
+
+## Key Features
+
+- ๐ฆ **Light DOM Access** โ Slotted elements remain accessible via standard DOM APIs
+- โฑ๏ธ **Async Support** โ Return a Promise for async operations
+- ๐ฏ **Full DOM API** โ Use `querySelector`, `querySelectorAll`, `children`, etc.
+- ๐ก๏ธ **Error Handling** โ Errors in callbacks are caught and logged
+
+## CSS Behavior
+
+The wrapper uses `display: contents` so it doesn't affect layout:
+
+```css
+:host {
+ display: contents;
+}
+```
+
+This means the wrapper is "invisible" in the layout โ its children render as if they were direct children of the wrapper's parent.
diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts
index 486b109..4b3a3c8 100644
--- a/ts_web/00_commitinfo_data.ts
+++ b/ts_web/00_commitinfo_data.ts
@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@design.estate/dees-wcctools',
- version: '1.2.1',
+ version: '1.3.0',
description: 'A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.'
}
diff --git a/ts_web/elements/wcc-recording-panel.ts b/ts_web/elements/wcc-recording-panel.ts
index 572fbef..4abe107 100644
--- a/ts_web/elements/wcc-recording-panel.ts
+++ b/ts_web/elements/wcc-recording-panel.ts
@@ -678,16 +678,16 @@ export class WccRecordingPanel extends DeesElement {
{ e.stopPropagation(); this.isDraggingTrim = 'start'; }}
>
{ e.stopPropagation(); this.isDraggingTrim = 'end'; }}
>
@@ -850,9 +850,12 @@ export class WccRecordingPanel extends DeesElement {
// ==================== Trim Methods ====================
private handleVideoLoaded(video: HTMLVideoElement): void {
- this.videoDuration = video.duration;
+ // WebM files from MediaRecorder may have Infinity/NaN duration
+ // Fall back to the tracked recording duration
+ const duration = Number.isFinite(video.duration) ? video.duration : this.recordingDuration;
+ this.videoDuration = duration;
this.trimStart = 0;
- this.trimEnd = video.duration;
+ this.trimEnd = duration;
}
private formatDuration(seconds: number): string {
@@ -861,18 +864,23 @@ export class WccRecordingPanel extends DeesElement {
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
- private getHandlePosition(time: number): number {
- if (this.videoDuration === 0) return 12;
+ private getHandlePositionStyle(time: number): string {
+ if (this.videoDuration === 0) return '12px';
const percentage = time / this.videoDuration;
- const trackWidth = 336;
- return 12 + (percentage * trackWidth);
+ // Formula: 12px padding + percentage of remaining width (total - 24px padding)
+ // At 0%: 12px (left edge of track)
+ // At 100%: calc(100% - 12px) (right edge of track)
+ return `calc(12px + ${(percentage * 100).toFixed(2)}% - ${(percentage * 24).toFixed(2)}px)`;
}
- private getHandlePositionFromEnd(time: number): number {
- if (this.videoDuration === 0) return 12;
- const percentage = (this.videoDuration - time) / this.videoDuration;
- const trackWidth = 336;
- return 12 + (percentage * trackWidth);
+ private getHandlePositionFromEndStyle(time: number): string {
+ if (this.videoDuration === 0) return '12px';
+ const percentage = time / this.videoDuration;
+ const remainingPercentage = 1 - percentage;
+ // For CSS 'right' property: distance from right edge
+ // At trimEnd = 100%: right = 12px (at right edge of track)
+ // At trimEnd = 0%: right = calc(100% - 12px) (at left edge of track)
+ return `calc(12px + ${(remainingPercentage * 100).toFixed(2)}% - ${(remainingPercentage * 24).toFixed(2)}px)`;
}
private handleTimelineClick(e: MouseEvent): void {
diff --git a/ts_web/readme.md b/ts_web/readme.md
new file mode 100644
index 0000000..3b6788c
--- /dev/null
+++ b/ts_web/readme.md
@@ -0,0 +1,123 @@
+# @design.estate/dees-wcctools
+
+๐ ๏ธ **Web Component Catalogue Tools** โ The core dashboard and UI components for building interactive component catalogues
+
+## Overview
+
+This is the main module of `@design.estate/dees-wcctools`, providing the complete dashboard experience for developing, testing, and documenting web components.
+
+## Installation
+
+```bash
+pnpm add -D @design.estate/dees-wcctools
+```
+
+## Usage
+
+```typescript
+import { setupWccTools } from '@design.estate/dees-wcctools';
+import { MyButton } from './components/my-button.js';
+
+setupWccTools({
+ 'my-button': MyButton,
+});
+```
+
+## Exports
+
+### Main Entry Point
+
+| Export | Description |
+|--------|-------------|
+| `setupWccTools` | Initialize the component catalogue dashboard |
+
+### Recording Components
+
+| Export | Description |
+|--------|-------------|
+| `RecorderService` | Service class for screen/viewport recording |
+| `WccRecordButton` | Record button UI component |
+| `WccRecordingPanel` | Recording options and preview panel |
+| `IRecorderEvents` | TypeScript interface for recorder callbacks |
+| `IRecordingOptions` | TypeScript interface for recording options |
+
+## Internal Components
+
+The module includes these internal web components:
+
+| Component | Description |
+|-----------|-------------|
+| `wcc-dashboard` | Main dashboard container with routing |
+| `wcc-sidebar` | Navigation sidebar with element/page listing |
+| `wcc-frame` | Iframe viewport with responsive sizing |
+| `wcc-properties` | Property panel with live editing |
+| `wcc-record-button` | Recording state indicator button |
+| `wcc-recording-panel` | Recording workflow UI |
+
+## RecorderService API
+
+For programmatic recording control:
+
+```typescript
+import { RecorderService, type IRecorderEvents } from '@design.estate/dees-wcctools';
+
+const events: IRecorderEvents = {
+ onDurationUpdate: (duration) => console.log(`Recording: ${duration}s`),
+ onRecordingComplete: (blob) => saveBlob(blob),
+ onAudioLevelUpdate: (level) => updateMeter(level),
+ onError: (error) => console.error(error),
+ onStreamEnded: () => console.log('User stopped sharing'),
+};
+
+const recorder = new RecorderService(events);
+
+// Load available microphones
+const mics = await recorder.loadMicrophones(true); // true = request permission
+
+// Start audio level monitoring
+await recorder.startAudioMonitoring(mics[0].deviceId);
+
+// Start recording
+await recorder.startRecording({
+ mode: 'viewport', // or 'screen'
+ audioDeviceId: mics[0].deviceId,
+ viewportElement: document.querySelector('.viewport'),
+});
+
+// Stop recording
+recorder.stopRecording();
+
+// Export trimmed video
+const trimmedBlob = await recorder.exportTrimmedVideo(videoElement, startTime, endTime);
+
+// Cleanup
+recorder.dispose();
+```
+
+## Architecture
+
+```
+ts_web/
+โโโ index.ts # Main exports
+โโโ elements/
+โ โโโ wcc-dashboard.ts # Root dashboard component
+โ โโโ wcc-sidebar.ts # Navigation sidebar
+โ โโโ wcc-frame.ts # Responsive iframe viewport
+โ โโโ wcc-properties.ts # Property editing panel
+โ โโโ wcc-record-button.ts # Recording button
+โ โโโ wcc-recording-panel.ts # Recording options/preview
+โ โโโ wcctools.helpers.ts # Shared utilities
+โโโ services/
+โ โโโ recorder.service.ts # MediaRecorder abstraction
+โโโ pages/
+ โโโ index.ts # Built-in pages
+```
+
+## Features
+
+- ๐จ Interactive component preview
+- ๐ง Real-time property editing with type detection
+- ๐ Theme switching (light/dark)
+- ๐ฑ Responsive viewport testing
+- ๐ฌ Screen recording with trimming
+- ๐ URL-based deep linking