@rimelight/ui 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,681 +0,0 @@
1
- ---
2
- /**
3
- * Dynamic Showcase with Auto-Generated Controls
4
- *
5
- * Uses Starwind components for all controls.
6
- * Shows a single component instance that updates dynamically.
7
- */
8
- import Badge from "@rimelight/ui/components/badge/Badge.astro";
9
- import Button from "@rimelight/ui/components/button/Button.astro";
10
- import Input from "@rimelight/ui/components/input/Input.astro";
11
- import Label from "@rimelight/ui/components/label/Label.astro";
12
- import Switch from "@rimelight/ui/components/switch/Switch.astro";
13
- import Table from "@rimelight/ui/components/table/Table.astro";
14
- import TableBody from "@rimelight/ui/components/table/TableBody.astro";
15
- import TableCell from "@rimelight/ui/components/table/TableCell.astro";
16
- import TableHead from "@rimelight/ui/components/table/TableHead.astro";
17
- import TableHeader from "@rimelight/ui/components/table/TableHeader.astro";
18
- import TableRow from "@rimelight/ui/components/table/TableRow.astro";
19
- import Select from "@rimelight/ui/components/select/Select.astro";
20
- import SelectContent from "@rimelight/ui/components/select/SelectContent.astro";
21
- import SelectItem from "@rimelight/ui/components/select/SelectItem.astro";
22
- import SelectTrigger from "@rimelight/ui/components/select/SelectTrigger.astro";
23
- import SelectValue from "@rimelight/ui/components/select/SelectValue.astro";
24
- import Textarea from "@rimelight/ui/components/textarea/Textarea.astro";
25
- import Checkbox from "@rimelight/ui/components/checkbox/Checkbox.astro";
26
-
27
- export interface PropDef {
28
- name: string;
29
- type: string;
30
- default?: any;
31
- description?: string;
32
- }
33
-
34
- export interface DynamicShowcaseProps {
35
- /** Unique identifier for this showcase instance */
36
- id: string;
37
- /** Display name of the component */
38
- componentName: string;
39
- /** Description of what the component does */
40
- description?: string;
41
- /** Prop definitions */
42
- props: PropDef[];
43
- /** Initial prop values */
44
- initialProps?: Record<string, any>;
45
- /** Additional classes for the preview container */
46
- previewClassName?: string;
47
- }
48
-
49
- type Props = DynamicShowcaseProps;
50
-
51
- const {
52
- id,
53
- componentName,
54
- description,
55
- props,
56
- initialProps = {},
57
- previewClassName
58
- } = Astro.props;
59
-
60
- // Build initial state
61
- const initialState: Record<string, { value: any; omitted: boolean }> = {};
62
- props.forEach(prop => {
63
- if (prop.name in initialProps) {
64
- initialState[prop.name] = { value: initialProps[prop.name], omitted: false };
65
- } else if (prop.default !== undefined) {
66
- initialState[prop.name] = { value: prop.default, omitted: false };
67
- } else {
68
- initialState[prop.name] = { value: undefined, omitted: true };
69
- }
70
- });
71
-
72
- function getPropKind(type: string): 'string' | 'number' | 'boolean' | 'enum' | 'array' | 'object' {
73
- const lower = type.toLowerCase();
74
- if (lower === 'boolean') return 'boolean';
75
- if (lower === 'number') return 'number';
76
- if (lower.includes('[]')) return 'array';
77
- if (lower.startsWith('{') || lower.includes('record')) return 'object';
78
- if (lower.includes('|')) return 'enum';
79
- return 'string';
80
- }
81
-
82
- function extractEnumValues(type: string): string[] {
83
- const matches = type.match(/'([^']+)'|"([^"]+)"/g);
84
- if (matches) {
85
- return matches.map(m => m.replace(/['"]/g, ''));
86
- }
87
- return [];
88
- }
89
- ---
90
-
91
- <section class="py-12 space-y-12 border-t border-border first:border-t-0" data-showcase={id}>
92
- <div class="container space-y-8">
93
- <!-- Header -->
94
- <div class="space-y-4">
95
- <div class="space-y-1">
96
- <h2 class="text-3xl font-semibold tracking-tight">{componentName}</h2>
97
- {description && <p class="text-muted-foreground text-lg">{description}</p>}
98
- </div>
99
- </div>
100
-
101
- <!-- Interactive Playground -->
102
- <div class="grid lg:grid-cols-2 gap-8">
103
- <!-- Controls Panel -->
104
- <div class="space-y-6">
105
- <div class="flex items-center justify-between">
106
- <h3 class="text-sm font-medium text-muted-foreground uppercase tracking-wider">Controls</h3>
107
- <Button variant="outline" size="sm" data-action="reset" class="text-xs">Reset</Button>
108
- </div>
109
-
110
- <div class="space-y-4" data-controls>
111
- {props.map((prop) => {
112
- const kind = getPropKind(prop.type);
113
- const enumValues = extractEnumValues(prop.type);
114
- const state = initialState[prop.name];
115
- if (!state) return null;
116
- const isOmitted = state.omitted;
117
-
118
- return (
119
- <div class="space-y-2 p-4 rounded-lg border border-border bg-card/50" data-prop-control={prop.name}>
120
- <div class="flex items-center justify-between gap-md">
121
- <Label class="text-sm font-medium flex items-center gap-md">
122
- {prop.name}
123
- <Badge variant="outline" class="font-mono text-[10px] uppercase">
124
- {prop.type}
125
- </Badge>
126
- </Label>
127
- <div class="flex items-center gap-md">
128
- <Label for={`${id}-omit-${prop.name}`} class="text-xs text-muted-foreground">Omit</Label>
129
- <Switch
130
- id={`${id}-omit-${prop.name}`}
131
- data-prop={prop.name}
132
- checked={isOmitted}
133
- data-state={isOmitted ? 'checked' : 'unchecked'}
134
- size="sm"
135
- />
136
- </div>
137
- </div>
138
-
139
- {kind === 'boolean' && (
140
- <div class="flex items-center gap-md">
141
- <Checkbox
142
- id={`${id}-value-${prop.name}`}
143
- data-prop={prop.name}
144
- data-type="boolean"
145
- checked={!!state.value}
146
- disabled={isOmitted}
147
- size="md"
148
- />
149
- <Label for={`${id}-value-${prop.name}`} class="text-sm">
150
- {state.value ? 'True' : 'False'}
151
- </Label>
152
- </div>
153
- )}
154
-
155
- {kind === 'string' && (
156
- <Input
157
- id={`${id}-value-${prop.name}`}
158
- data-prop={prop.name}
159
- data-type="string"
160
- value={state.value ?? ''}
161
- placeholder={prop.default ?? `Enter ${prop.name}`}
162
- class="w-full"
163
- disabled={isOmitted}
164
- size="md"
165
- />
166
- )}
167
-
168
- {kind === 'number' && (
169
- <Input
170
- id={`${id}-value-${prop.name}`}
171
- data-prop={prop.name}
172
- data-type="number"
173
- type="number"
174
- value={state.value ?? ''}
175
- placeholder={String(prop.default ?? '')}
176
- class="w-full"
177
- disabled={isOmitted}
178
- size="md"
179
- />
180
- )}
181
-
182
- {kind === 'enum' && enumValues.length > 0 && (
183
- <div class="w-full" data-prop-control={prop.name}>
184
- <Select
185
- id={`${id}-select-${prop.name}`}
186
- data-prop={prop.name}
187
- data-type="enum"
188
- data-value={state.value ?? ''}
189
- class="w-full"
190
- >
191
- <SelectTrigger size="md">
192
- <SelectValue placeholder="Select..." />
193
- </SelectTrigger>
194
- <SelectContent size="md">
195
- <SelectItem value="">(default)</SelectItem>
196
- {enumValues.map((value) => (
197
- <SelectItem value={value} data-selected={state.value === value ? 'true' : undefined}>
198
- {value}
199
- </SelectItem>
200
- ))}
201
- </SelectContent>
202
- </Select>
203
- </div>
204
- )}
205
-
206
- {kind === 'array' && (
207
- <Textarea
208
- id={`${id}-value-${prop.name}`}
209
- data-prop={prop.name}
210
- data-type="array"
211
- placeholder="JSON array"
212
- class="w-full font-mono text-xs"
213
- disabled={isOmitted}
214
- size="md"
215
- >{state.value != null ? JSON.stringify(state.value, null, 2) : ''}</Textarea>
216
- )}
217
-
218
- {kind === 'object' && (
219
- <Textarea
220
- id={`${id}-value-${prop.name}`}
221
- data-prop={prop.name}
222
- data-type="object"
223
- placeholder="JSON object"
224
- class="w-full font-mono text-xs"
225
- disabled={isOmitted}
226
- size="md"
227
- >{state.value != null ? JSON.stringify(state.value, null, 2) : ''}</Textarea>
228
- )}
229
-
230
- {prop.description && (
231
- <p class="text-xs text-muted-foreground">{prop.description}</p>
232
- )}
233
- </div>
234
- );
235
- })}
236
- </div>
237
- </div>
238
-
239
- <!-- Preview Panel -->
240
- <div class="space-y-4">
241
- <h3 class="text-sm font-medium text-muted-foreground uppercase tracking-wider">Preview</h3>
242
- <div class:list={[
243
- "w-full min-h-[400px] rounded-xl border border-border bg-muted/5 p-6 flex items-center justify-center overflow-auto",
244
- previewClassName
245
- ]}>
246
- <div
247
- id={`${id}-preview`}
248
- data-preview
249
- data-showcase-id={id}
250
- data-component-name={componentName}
251
- data-initial-props={JSON.stringify(initialState)}
252
- class="w-full h-full"
253
- >
254
- <slot />
255
- </div>
256
- </div>
257
-
258
- <!-- Active Props JSON -->
259
- <div class="rounded-lg border border-border bg-card overflow-hidden">
260
- <div class="px-4 py-2 bg-muted/50 border-b border-border flex items-center justify-between">
261
- <h4 class="text-xs font-medium text-muted-foreground uppercase tracking-wider">Active Props</h4>
262
- <Button variant="ghost" size="sm" data-action="copy" class="h-6 text-xs">Copy</Button>
263
- </div>
264
- <pre class="p-4 text-xs font-mono text-muted-foreground overflow-x-auto max-h-[300px]">
265
- <code data-active-props>{JSON.stringify(
266
- Object.fromEntries(
267
- Object.entries(initialState)
268
- .filter(([_, v]) => !v.omitted && v.value !== undefined)
269
- .map(([k, v]) => [k, v.value])
270
- ), null, 2
271
- )}</code>
272
- </pre>
273
- </div>
274
- </div>
275
- </div>
276
-
277
- <!-- Prop Reference Table -->
278
- {props.length > 0 && (
279
- <div>
280
- <h3 class="text-sm font-medium text-muted-foreground uppercase tracking-wider mb-4">Prop Reference</h3>
281
- <div class="rounded-xl border border-border bg-card overflow-hidden">
282
- <Table>
283
- <TableHeader>
284
- <TableRow>
285
- <TableHead class="w-[15%]">Prop</TableHead>
286
- <TableHead class="w-[15%]">Type</TableHead>
287
- <TableHead class="w-[15%]">Default</TableHead>
288
- <TableHead>Description</TableHead>
289
- </TableRow>
290
- </TableHeader>
291
- <TableBody>
292
- {props.map((prop) => (
293
- <TableRow>
294
- <TableCell class="font-mono text-sm font-medium text-primary">
295
- {prop.name}
296
- </TableCell>
297
- <TableCell>
298
- <Badge variant="outline" class="font-mono text-[10px] uppercase">
299
- {prop.type}
300
- </Badge>
301
- </TableCell>
302
- <TableCell class="font-mono text-xs text-muted-foreground">
303
- {prop.default !== undefined ? String(prop.default) : "—"}
304
- </TableCell>
305
- <TableCell class="text-sm">
306
- {prop.description || "—"}
307
- </TableCell>
308
- </TableRow>
309
- ))}
310
- </TableBody>
311
- </Table>
312
- </div>
313
- </div>
314
- )}
315
- </div>
316
- </section>
317
-
318
- <script>
319
- interface PropState {
320
- value: unknown;
321
- omitted: boolean;
322
- }
323
-
324
- interface CurrentState {
325
- [key: string]: PropState;
326
- }
327
-
328
- function initShowcase(showcaseEl: HTMLElement) {
329
- const id = showcaseEl.dataset.showcase;
330
- if (!id) return;
331
-
332
- const controlsContainer = showcaseEl.querySelector('[data-controls]') as HTMLElement | null;
333
- const previewContainer = showcaseEl.querySelector('[data-preview]') as HTMLElement | null;
334
- const activePropsCode = showcaseEl.querySelector('[data-active-props]') as HTMLElement | null;
335
- const resetBtn = showcaseEl.querySelector('[data-action="reset"]') as HTMLElement | null;
336
- const copyBtn = showcaseEl.querySelector('[data-action="copy"]') as HTMLElement | null;
337
-
338
- if (!controlsContainer || !previewContainer || !activePropsCode) return;
339
-
340
- // Parse initial state
341
- const initialPropsJson = previewContainer.dataset.initialProps;
342
- if (!initialPropsJson) return;
343
-
344
- let currentState: CurrentState = JSON.parse(initialPropsJson);
345
-
346
- function getActiveProps(): Record<string, unknown> {
347
- const active: Record<string, unknown> = {};
348
- Object.entries(currentState).forEach(([key, state]) => {
349
- if (!state.omitted && state.value !== undefined) {
350
- active[key] = state.value;
351
- }
352
- });
353
- return active;
354
- }
355
-
356
- function updateActivePropsDisplay() {
357
- if (activePropsCode) {
358
- activePropsCode.textContent = JSON.stringify(getActiveProps(), null, 2);
359
- }
360
- }
361
-
362
- function updatePreview() {
363
- updateActivePropsDisplay();
364
-
365
- const activeProps = getActiveProps();
366
-
367
- // Dispatch custom event with new props
368
- if (previewContainer) {
369
- previewContainer.dispatchEvent(new CustomEvent('showcase:props-update', {
370
- detail: { props: activeProps, allProps: currentState }
371
- }));
372
- }
373
- }
374
-
375
- function setPropOmitted(propName: string, omitted: boolean) {
376
- if (currentState[propName]) {
377
- currentState[propName].omitted = omitted;
378
- }
379
-
380
- // Update control disabled state
381
- const controlEl = controlsContainer?.querySelector(`[data-prop-control="${propName}"]`) as HTMLElement | null;
382
- if (controlEl) {
383
- const input = controlEl.querySelector('input, select, textarea, [data-type]') as HTMLElement | null;
384
- if (input) {
385
- if (omitted) {
386
- input.setAttribute('disabled', 'true');
387
- input.classList.add('opacity-50');
388
- } else {
389
- input.removeAttribute('disabled');
390
- input.classList.remove('opacity-50');
391
- }
392
- }
393
- }
394
- }
395
-
396
- function updatePropValue(propName: string, value: unknown) {
397
- if (currentState[propName]) {
398
- currentState[propName].value = value;
399
- }
400
- }
401
-
402
- // Handle omit switch changes
403
- controlsContainer?.querySelectorAll('[id^="-omit-"]').forEach(el => {
404
- el.addEventListener('starwind-switch:change', (e: Event) => {
405
- const customEvent = e as CustomEvent<{ checked: boolean }>;
406
- const { checked: isOmitted } = customEvent.detail;
407
- const propName = (el as HTMLElement).dataset.prop;
408
- if (!propName) return;
409
-
410
- setPropOmitted(propName, isOmitted);
411
- updatePreview();
412
- });
413
- });
414
-
415
- // Handle Input changes (string, number)
416
- controlsContainer?.querySelectorAll('[id*="-value-"]').forEach(el => {
417
- // Handle text input changes
418
- el.addEventListener('input', (e: Event) => {
419
- const target = e.target as HTMLInputElement | HTMLTextAreaElement;
420
- const propName = (el as HTMLElement).dataset.prop;
421
- const type = (el as HTMLElement).dataset.type;
422
- if (!propName) return;
423
-
424
- let value: unknown = target.value;
425
-
426
- if (type === 'number') {
427
- value = value === '' ? undefined : Number(value);
428
- } else if (type === 'array' || type === 'object') {
429
- try {
430
- value = value === '' ? undefined : JSON.parse(value as string);
431
- } catch {
432
- // Keep as string if invalid JSON
433
- }
434
- }
435
-
436
- updatePropValue(propName, value);
437
- updatePreview();
438
- });
439
-
440
- // Handle checkbox changes (boolean) - Starwind Checkbox
441
- el.addEventListener('starwind-checkbox:change', (e: Event) => {
442
- const customEvent = e as CustomEvent<{ checked: boolean }>;
443
- const { checked } = customEvent.detail;
444
- const propName = (el as HTMLElement).dataset.prop;
445
- console.log('[Showcase Checkbox] prop:', propName, 'checked:', checked);
446
- if (!propName) return;
447
-
448
- updatePropValue(propName, checked);
449
- updatePreview();
450
- });
451
- });
452
-
453
- // Handle Starwind Select changes - listen on the wrapper
454
- controlsContainer?.querySelectorAll('[data-prop-control]').forEach((controlEl: Element) => {
455
- const selectWrapper = controlEl.querySelector('.starwind-select') as HTMLElement | null;
456
- if (selectWrapper) {
457
- selectWrapper.addEventListener('starwind-select:change', (e: Event) => {
458
- const customEvent = e as CustomEvent<{ value: string }>;
459
- const { value } = customEvent.detail;
460
- const propName = (controlEl as HTMLElement).dataset.propControl;
461
- if (!propName) return;
462
-
463
- updatePropValue(propName, value || undefined);
464
- updatePreview();
465
- });
466
- }
467
- });
468
-
469
- // Reset button
470
- resetBtn?.addEventListener('click', () => {
471
- const freshState: CurrentState = JSON.parse(initialPropsJson);
472
- currentState = freshState;
473
-
474
- // Reset all controls
475
- Object.keys(freshState).forEach((propName) => {
476
- const state = freshState[propName];
477
- if (!state) return;
478
- const controlEl = controlsContainer?.querySelector(`[data-prop-control="${propName}"]`) as HTMLElement | null;
479
- if (!controlEl) return;
480
-
481
- const omitSwitch = controlEl.querySelector('[id^*="-omit-"]') as HTMLElement | null;
482
- const valueInput = controlEl.querySelector('[id^*="-value-"]') as HTMLElement | null;
483
- const selectWrapper = controlEl.querySelector('.starwind-select') as HTMLElement | null;
484
-
485
- if (omitSwitch) {
486
- // Reset Switch component
487
- omitSwitch.setAttribute('data-state', state.omitted ? 'checked' : 'unchecked');
488
- omitSwitch.setAttribute('aria-checked', state.omitted ? 'true' : 'false');
489
- omitSwitch.dispatchEvent(new CustomEvent('starwind-switch:change', {
490
- detail: { checked: state.omitted, switchId: omitSwitch.id },
491
- bubbles: true
492
- }));
493
- }
494
-
495
- if (valueInput) {
496
- if (valueInput.tagName === 'INPUT' && (valueInput as HTMLInputElement).type === 'checkbox') {
497
- // Starwind checkbox
498
- (valueInput as HTMLInputElement).checked = !!state.value;
499
- valueInput.setAttribute('aria-checked', state.value ? 'true' : 'false');
500
- valueInput.dispatchEvent(new CustomEvent('starwind-checkbox:change', {
501
- detail: { checked: !!state.value },
502
- bubbles: true
503
- }));
504
- } else if (valueInput.tagName === 'INPUT') {
505
- (valueInput as HTMLInputElement).value = String(state.value ?? '');
506
- } else if (valueInput.tagName === 'TEXTAREA') {
507
- (valueInput as HTMLTextAreaElement).value = state.value !== undefined ? JSON.stringify(state.value, null, 2) : '';
508
- }
509
- }
510
-
511
- if (selectWrapper) {
512
- // Starwind Select - use programmatic select event
513
- selectWrapper.dispatchEvent(new CustomEvent('starwind-select:select', {
514
- detail: { value: String(state.value ?? ''), selectId: selectWrapper.id },
515
- bubbles: true
516
- }));
517
- }
518
-
519
- if (valueInput || selectWrapper) {
520
- const element: HTMLElement = (valueInput || selectWrapper)!;
521
- if (state.omitted) {
522
- element.setAttribute('disabled', 'true');
523
- element.classList.add('opacity-50');
524
- } else {
525
- element.removeAttribute('disabled');
526
- element.classList.remove('opacity-50');
527
- }
528
- }
529
- });
530
-
531
- updatePreview();
532
- });
533
-
534
- // Copy button
535
- copyBtn?.addEventListener('click', async () => {
536
- const text = activePropsCode?.textContent;
537
- if (text) {
538
- await navigator.clipboard.writeText(text);
539
- if (copyBtn) {
540
- copyBtn.textContent = 'Copied!';
541
- setTimeout(() => {
542
- copyBtn.textContent = 'Copy';
543
- }, 2000);
544
- }
545
- }
546
- });
547
-
548
- // Initial setup
549
- Object.entries(currentState).forEach(([propName, state]) => {
550
- if (state.omitted) {
551
- setPropOmitted(propName, true);
552
- }
553
- });
554
-
555
- updateActivePropsDisplay();
556
- }
557
-
558
- // Initialize
559
- if (document.readyState === 'loading') {
560
- document.addEventListener('DOMContentLoaded', () => {
561
- document.querySelectorAll('[data-showcase]').forEach(el => initShowcase(el as HTMLElement));
562
- });
563
- } else {
564
- document.querySelectorAll('[data-showcase]').forEach(el => initShowcase(el as HTMLElement));
565
- }
566
-
567
- document.addEventListener('astro:page-load', () => {
568
- document.querySelectorAll('[data-showcase]').forEach(el => initShowcase(el as HTMLElement));
569
- });
570
-
571
- // Client-side preview renderer - singleton pattern
572
- // This script runs once globally, not per-component
573
-
574
- interface ShowcasePreviewAPI {
575
- renderers: Map<string, (props: Record<string, unknown>, container: HTMLElement) => void>;
576
- registerRenderer: (name: string, renderer: (props: Record<string, unknown>, container: HTMLElement) => void) => void;
577
- }
578
-
579
- declare global {
580
- interface Window {
581
- ShowcasePreview?: ShowcasePreviewAPI;
582
- }
583
- }
584
-
585
- if (!window.ShowcasePreview) {
586
- window.ShowcasePreview = {
587
- renderers: new Map<string, (props: Record<string, unknown>, container: HTMLElement) => void>(),
588
- registerRenderer: function(name: string, renderer: (props: Record<string, unknown>, container: HTMLElement) => void) {
589
- this.renderers.set(name, renderer);
590
- }
591
- };
592
- }
593
-
594
- // Initialize preview handlers
595
- function initPreviewHandlers() {
596
- // Initialize showcase controls for each showcase section
597
- document.querySelectorAll('[data-showcase]').forEach(el => {
598
- initShowcase(el as HTMLElement);
599
- });
600
-
601
- // Initialize preview containers
602
- document.querySelectorAll('div[data-preview]').forEach(el => {
603
- if (!el.hasAttribute('data-preview-initialized')) {
604
- el.setAttribute('data-preview-initialized', 'true');
605
- const componentName = (el as HTMLElement).dataset.componentName;
606
- const initialPropsJson = (el as HTMLElement).dataset.initialProps;
607
-
608
- if (componentName && initialPropsJson) {
609
- const props: Record<string, PropState> = JSON.parse(initialPropsJson);
610
-
611
- // Look up renderer at event time, not initialization time
612
- el.addEventListener('showcase:props-update', (e: Event) => {
613
- const newProps = (e as CustomEvent<{ props: Record<string, unknown> }>).detail.props;
614
- const renderer = window.ShowcasePreview?.renderers.get(componentName);
615
- if (renderer) {
616
- renderer(newProps, el as HTMLElement);
617
- } else {
618
- // Update data attributes
619
- Object.entries(newProps).forEach(([key, value]) => {
620
- if (value === undefined || value === null) {
621
- el.removeAttribute(`data-${key}`);
622
- } else if (typeof value === 'boolean') {
623
- if (value) {
624
- el.setAttribute(`data-${key}`, 'true');
625
- } else {
626
- el.removeAttribute(`data-${key}`);
627
- }
628
- } else {
629
- el.setAttribute(`data-${key}`, String(value));
630
- }
631
- });
632
- }
633
- });
634
-
635
- // Initial render - look up renderer
636
- const renderer = window.ShowcasePreview?.renderers.get(componentName);
637
- if (renderer) {
638
- renderer(Object.fromEntries(
639
- Object.entries(props).filter(([_, v]) => !v.omitted && v.value !== undefined).map(([k, v]) => [k, v.value])
640
- ), el as HTMLElement);
641
- }
642
- }
643
- }
644
- });
645
- }
646
-
647
- // Initialize on load - defer until DOM is ready
648
- function initShowcasePreview() {
649
- if (document.readyState === 'loading') {
650
- document.addEventListener('DOMContentLoaded', () => {
651
- initPreviewHandlers();
652
- });
653
- } else {
654
- initPreviewHandlers();
655
- }
656
- }
657
-
658
- initShowcasePreview();
659
- document.addEventListener('astro:page-load', initPreviewHandlers);
660
-
661
- // Export function for page to trigger initial render
662
- window.dispatchEvent(new CustomEvent('showcase-preview:ready'));
663
- </script>
664
-
665
- <style>
666
- [data-showcase] [data-controls] [disabled] {
667
- cursor: not-allowed;
668
- }
669
-
670
- [data-showcase] select {
671
- cursor: pointer;
672
- }
673
-
674
- [data-showcase] select:disabled,
675
- [data-showcase] input:disabled,
676
- [data-showcase] textarea:disabled {
677
- opacity: 0.5;
678
- cursor: not-allowed;
679
- }
680
- </style>
681
-