@design.estate/wcctools 5.1.0

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.
Files changed (55) hide show
  1. package/.smartconfig.json +65 -0
  2. package/changelog.md +414 -0
  3. package/dist_bundle/bundle.js +92419 -0
  4. package/dist_bundle/bundle.js.map +1 -0
  5. package/dist_ts_web/00_commitinfo_data.d.ts +8 -0
  6. package/dist_ts_web/00_commitinfo_data.js +9 -0
  7. package/dist_ts_web/elements/wcc-contextmenu.d.ts +41 -0
  8. package/dist_ts_web/elements/wcc-contextmenu.js +329 -0
  9. package/dist_ts_web/elements/wcc-dashboard.d.ts +119 -0
  10. package/dist_ts_web/elements/wcc-dashboard.js +700 -0
  11. package/dist_ts_web/elements/wcc-frame.d.ts +22 -0
  12. package/dist_ts_web/elements/wcc-frame.js +256 -0
  13. package/dist_ts_web/elements/wcc-properties.d.ts +43 -0
  14. package/dist_ts_web/elements/wcc-properties.js +1147 -0
  15. package/dist_ts_web/elements/wcc-record-button.d.ts +12 -0
  16. package/dist_ts_web/elements/wcc-record-button.js +165 -0
  17. package/dist_ts_web/elements/wcc-recording-panel.d.ts +44 -0
  18. package/dist_ts_web/elements/wcc-recording-panel.js +1103 -0
  19. package/dist_ts_web/elements/wcc-sidebar.d.ts +91 -0
  20. package/dist_ts_web/elements/wcc-sidebar.js +1497 -0
  21. package/dist_ts_web/elements/wcctools.helpers.d.ts +46 -0
  22. package/dist_ts_web/elements/wcctools.helpers.js +106 -0
  23. package/dist_ts_web/index.d.ts +18 -0
  24. package/dist_ts_web/index.js +30 -0
  25. package/dist_ts_web/pages/index.d.ts +1 -0
  26. package/dist_ts_web/pages/index.js +2 -0
  27. package/dist_ts_web/services/recorder.service.d.ts +55 -0
  28. package/dist_ts_web/services/recorder.service.js +353 -0
  29. package/dist_ts_web/wcctools.interfaces.d.ts +29 -0
  30. package/dist_ts_web/wcctools.interfaces.js +2 -0
  31. package/dist_ts_web/wcctools.plugins.d.ts +2 -0
  32. package/dist_ts_web/wcctools.plugins.js +3 -0
  33. package/license.md +19 -0
  34. package/package.json +64 -0
  35. package/readme.md +568 -0
  36. package/ts_web/00_commitinfo_data.ts +8 -0
  37. package/ts_web/elements/wcc-contextmenu.ts +291 -0
  38. package/ts_web/elements/wcc-dashboard.ts +672 -0
  39. package/ts_web/elements/wcc-frame.ts +189 -0
  40. package/ts_web/elements/wcc-properties.ts +1089 -0
  41. package/ts_web/elements/wcc-record-button.ts +108 -0
  42. package/ts_web/elements/wcc-recording-panel.ts +1010 -0
  43. package/ts_web/elements/wcc-sidebar.ts +1470 -0
  44. package/ts_web/elements/wcctools.helpers.ts +153 -0
  45. package/ts_web/index.ts +38 -0
  46. package/ts_web/pages/index.ts +1 -0
  47. package/ts_web/readme.md +177 -0
  48. package/ts_web/services/recorder.service.ts +451 -0
  49. package/ts_web/tspublish.json +3 -0
  50. package/ts_web/types/dom-mediacapture-stub/index.d.ts +12 -0
  51. package/ts_web/types/dom-mediacapture-stub/package.json +6 -0
  52. package/ts_web/types/dom-webcodecs-stub/index.d.ts +2 -0
  53. package/ts_web/types/dom-webcodecs-stub/package.json +6 -0
  54. package/ts_web/wcctools.interfaces.ts +31 -0
  55. package/ts_web/wcctools.plugins.ts +5 -0
@@ -0,0 +1,1089 @@
1
+ import { DeesElement, property, html, customElement, type TemplateResult, state } from '@design.estate/dees-element';
2
+ import { WccDashboard } from './wcc-dashboard.js';
3
+ import { determinePropertyType, findElementInstance, type TPropertyType, type TTemplateFactory } from './wcctools.helpers.js';
4
+ import './wcc-record-button.js';
5
+ import './wcc-recording-panel.js';
6
+
7
+ export type { TPropertyType } from './wcctools.helpers.js';
8
+
9
+ export type TEnvironment = 'native' | 'desktop' | 'tablet' | 'phablet' | 'phone';
10
+ export type TTheme = 'bright' | 'dark';
11
+
12
+ let environment: TEnvironment = 'native';
13
+
14
+ export const setEnvironment = (envArg) => {
15
+ environment = envArg;
16
+ };
17
+
18
+ @customElement('wcc-properties')
19
+ export class WccProperties extends DeesElement {
20
+ @property({
21
+ type: WccDashboard
22
+ })
23
+ accessor dashboardRef: WccDashboard;
24
+
25
+ @property()
26
+ accessor selectedItem: TTemplateFactory | DeesElement;
27
+
28
+ @property()
29
+ accessor selectedViewport: TEnvironment = 'native';
30
+
31
+ @property()
32
+ accessor selectedTheme: TTheme = 'dark';
33
+
34
+ @property()
35
+ accessor warning: string = null;
36
+
37
+ @property()
38
+ accessor isNative: boolean = false;
39
+
40
+ @property({ type: Number })
41
+ accessor sidebarWidth: number = 200;
42
+
43
+ @state()
44
+ accessor propertyContent: TemplateResult[] = [];
45
+
46
+ @state()
47
+ accessor editingProperties: Array<{
48
+ id: string;
49
+ name: string;
50
+ value: any;
51
+ element: HTMLElement;
52
+ editorValue: string;
53
+ editorError: string;
54
+ }> = [];
55
+
56
+ // Recording coordination state
57
+ @state()
58
+ accessor showRecordingPanel: boolean = false;
59
+
60
+ @state()
61
+ accessor isRecording: boolean = false;
62
+
63
+ @state()
64
+ accessor recordingDuration: number = 0;
65
+
66
+ // Delayed hide for native mode transition
67
+ @state()
68
+ accessor isHidden: boolean = false;
69
+
70
+ public editorHeight: number = 300;
71
+
72
+ public render(): TemplateResult {
73
+ return html`
74
+ <style>
75
+ :host {
76
+ /* CSS Variables - Always dark theme */
77
+ --background: #0a0a0a;
78
+ --foreground: #e5e5e5;
79
+ --card: #0f0f0f;
80
+ --card-foreground: #f0f0f0;
81
+ --muted: #1a1a1a;
82
+ --muted-foreground: #666;
83
+ --accent: #222;
84
+ --accent-foreground: #fff;
85
+ --border: rgba(255, 255, 255, 0.06);
86
+ --input: #141414;
87
+ --primary: #3b82f6;
88
+ --primary-foreground: #fff;
89
+ --ring: #3b82f6;
90
+ --radius: 4px;
91
+ --radius-sm: 2px;
92
+ --radius-md: 4px;
93
+ --radius-lg: 6px;
94
+
95
+ /* Base styles */
96
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
97
+ box-sizing: border-box;
98
+ position: absolute;
99
+ left: ${this.sidebarWidth}px;
100
+ height: ${this.editingProperties.length > 0 ? 100 + this.editorHeight : 100}px;
101
+ bottom: 0px;
102
+ right: 0px;
103
+ overflow: hidden;
104
+ background: var(--background);
105
+ color: var(--foreground);
106
+ display: ${this.isHidden ? 'none' : 'block'};
107
+ }
108
+ .grid {
109
+ display: grid;
110
+ grid-template-columns: 1fr 150px 350px 100px;
111
+ height: 100%;
112
+ }
113
+ .properties {
114
+ background: transparent;
115
+ overflow-y: auto;
116
+ display: grid;
117
+ grid-template-columns: repeat(3, 1fr);
118
+ border-right: 1px solid var(--border);
119
+ align-content: start;
120
+ }
121
+
122
+ .material-symbols-outlined {
123
+ font-family: 'Material Symbols Outlined';
124
+ font-weight: normal;
125
+ font-style: normal;
126
+ font-size: 24px; /* Preferred icon size */
127
+ display: inline-block;
128
+ line-height: 1;
129
+ text-transform: none;
130
+ letter-spacing: normal;
131
+ word-wrap: normal;
132
+ white-space: nowrap;
133
+ direction: ltr;
134
+ font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 48;
135
+ }
136
+
137
+ .properties .property {
138
+ padding: 0.4rem;
139
+ background: transparent;
140
+ border-right: 1px solid var(--border);
141
+ border-bottom: 1px solid var(--border);
142
+ transition: all 0.15s ease;
143
+ }
144
+
145
+ .properties .property:hover {
146
+ background: rgba(255, 255, 255, 0.02);
147
+ }
148
+
149
+ .properties .property-label {
150
+ font-size: 0.65rem;
151
+ font-weight: 400;
152
+ color: #888;
153
+ margin-bottom: 0.2rem;
154
+ text-transform: capitalize;
155
+ white-space: nowrap;
156
+ overflow: hidden;
157
+ text-overflow: ellipsis;
158
+ }
159
+
160
+ .properties input[type="text"],
161
+ .properties input[type="number"],
162
+ .properties select {
163
+ display: block;
164
+ width: 100%;
165
+ padding: 0.25rem 0.4rem;
166
+ background: var(--input);
167
+ border: 1px solid transparent;
168
+ color: var(--foreground);
169
+ border-radius: var(--radius-sm);
170
+ font-size: 0.7rem;
171
+ transition: all 0.15s ease;
172
+ outline: none;
173
+ }
174
+
175
+ .properties input[type="text"]:focus,
176
+ .properties input[type="number"]:focus,
177
+ .properties select:focus {
178
+ border-color: var(--primary);
179
+ background: rgba(59, 130, 246, 0.1);
180
+ }
181
+
182
+ .properties input[type="checkbox"] {
183
+ width: 1rem;
184
+ height: 1rem;
185
+ cursor: pointer;
186
+ accent-color: var(--primary);
187
+ }
188
+
189
+ .properties .editor-button {
190
+ padding: 0.25rem 0.5rem;
191
+ background: var(--input);
192
+ border: 1px solid transparent;
193
+ border-radius: var(--radius-sm);
194
+ color: var(--foreground);
195
+ font-size: 0.7rem;
196
+ cursor: pointer;
197
+ transition: all 0.15s ease;
198
+ text-align: center;
199
+ }
200
+
201
+ .properties .editor-button:hover {
202
+ border-color: var(--primary);
203
+ background: rgba(59, 130, 246, 0.1);
204
+ }
205
+
206
+ .viewportSelector,
207
+ .themeSelector,
208
+ .shareSelector {
209
+ user-select: none;
210
+ background: transparent;
211
+ display: flex;
212
+ flex-direction: column;
213
+ border-right: 1px solid var(--border);
214
+ }
215
+ .selectorButtons2 {
216
+ display: grid;
217
+ grid-template-columns: repeat(2, minmax(0, 1fr));
218
+ flex: 1;
219
+ }
220
+ .selectorButtons4 {
221
+ display: grid;
222
+ grid-template-columns: repeat(4, minmax(0, 1fr));
223
+ flex: 1;
224
+ }
225
+ .selectorButtons5 {
226
+ display: grid;
227
+ grid-template-columns: repeat(5, minmax(0, 1fr));
228
+ flex: 1;
229
+ }
230
+ .selectorButtons1 {
231
+ display: grid;
232
+ grid-template-columns: minmax(0, 1fr);
233
+ flex: 1;
234
+ }
235
+ .button {
236
+ appearance: none;
237
+ margin: 0;
238
+ font-family: inherit;
239
+ line-height: 1.2;
240
+ display: flex;
241
+ flex-direction: column;
242
+ align-items: center;
243
+ justify-content: center;
244
+ padding: 0.5rem 0.25rem;
245
+ text-align: center;
246
+ background: transparent;
247
+ border: 1px solid var(--border);
248
+ transition: all 0.15s ease;
249
+ cursor: pointer;
250
+ font-size: 0.65rem;
251
+ gap: 0.2rem;
252
+ color: #999;
253
+ }
254
+
255
+ .button:hover {
256
+ background: rgba(59, 130, 246, 0.05);
257
+ color: #bbb;
258
+ }
259
+
260
+ .button.selected {
261
+ background: rgba(59, 130, 246, 0.15);
262
+ color: var(--primary);
263
+ border-color: rgba(59, 130, 246, 0.3);
264
+ }
265
+
266
+ .button.selected:hover {
267
+ background: rgba(59, 130, 246, 0.2);
268
+ }
269
+
270
+ .button:focus-visible,
271
+ .properties .editor-button:focus-visible,
272
+ .editor-close-all:focus-visible {
273
+ outline: 2px solid var(--ring);
274
+ outline-offset: -2px;
275
+ }
276
+
277
+ .properties input:focus-visible,
278
+ .properties select:focus-visible {
279
+ outline: 2px solid var(--ring);
280
+ outline-offset: 1px;
281
+ }
282
+
283
+ .button .material-symbols-outlined {
284
+ font-size: 18px;
285
+ font-variation-settings: 'FILL' 0, 'wght' 300;
286
+ min-width: 18px;
287
+ min-height: 18px;
288
+ display: inline-flex;
289
+ align-items: center;
290
+ justify-content: center;
291
+ }
292
+
293
+ .button.selected .material-symbols-outlined {
294
+ font-variation-settings: 'FILL' 1, 'wght' 400;
295
+ }
296
+
297
+ .panelheading {
298
+ padding: 0.3rem 0.5rem;
299
+ font-weight: 500;
300
+ font-size: 0.65rem;
301
+ background: rgba(59, 130, 246, 0.03);
302
+ border-bottom: 1px solid var(--border);
303
+ color: #888;
304
+ text-transform: uppercase;
305
+ letter-spacing: 0.08em;
306
+ }
307
+ .docs {
308
+ display: flex;
309
+ align-items: center;
310
+ justify-content: center;
311
+ text-transform: uppercase;
312
+ background: transparent;
313
+ cursor: pointer;
314
+ font-size: 0.65rem;
315
+ font-weight: 500;
316
+ letter-spacing: 0.08em;
317
+ transition: all 0.15s ease;
318
+ color: #666;
319
+ }
320
+
321
+ .docs:hover {
322
+ background: rgba(59, 130, 246, 0.05);
323
+ color: #999;
324
+ }
325
+
326
+ .warning {
327
+ position: absolute;
328
+ background: rgba(20, 20, 20, 0.8);
329
+ color: #888;
330
+ top: 0.5rem;
331
+ bottom: 0.5rem;
332
+ left: 0.5rem;
333
+ right: calc(600px + 0.5rem);
334
+ display: flex;
335
+ align-items: center;
336
+ justify-content: center;
337
+ padding: 1.5rem;
338
+ font-size: 0.85rem;
339
+ border-radius: var(--radius-md);
340
+ border: 1px solid var(--border);
341
+ backdrop-filter: blur(8px);
342
+ }
343
+
344
+ .advanced-editor-container {
345
+ position: absolute;
346
+ left: 0;
347
+ right: 0;
348
+ top: 0;
349
+ height: ${this.editorHeight}px;
350
+ background: #050505;
351
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
352
+ display: flex;
353
+ flex-direction: column;
354
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
355
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
356
+ }
357
+
358
+ .editor-header-bar {
359
+ padding: 0.5rem 0.75rem;
360
+ background: transparent;
361
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
362
+ display: flex;
363
+ justify-content: space-between;
364
+ align-items: center;
365
+ height: 36px;
366
+ }
367
+
368
+ .editor-header-title {
369
+ font-size: 0.7rem;
370
+ font-weight: 500;
371
+ color: #666;
372
+ text-transform: uppercase;
373
+ letter-spacing: 0.08em;
374
+ }
375
+
376
+ .editor-close-all {
377
+ padding: 0.25rem 0.5rem;
378
+ background: transparent;
379
+ border: 1px solid rgba(255, 255, 255, 0.08);
380
+ border-radius: var(--radius-sm);
381
+ color: #999;
382
+ font-size: 0.65rem;
383
+ font-weight: 500;
384
+ cursor: pointer;
385
+ transition: all 0.15s ease;
386
+ }
387
+
388
+ .editor-close-all:hover {
389
+ background: rgba(255, 255, 255, 0.05);
390
+ border-color: rgba(255, 255, 255, 0.12);
391
+ color: #f87171;
392
+ }
393
+
394
+ .editors-container {
395
+ flex: 1;
396
+ display: flex;
397
+ overflow-x: auto;
398
+ overflow-y: hidden;
399
+ gap: 0;
400
+ background: rgba(255, 255, 255, 0.02);
401
+ padding: 0.75rem;
402
+ }
403
+
404
+ .editors-container::-webkit-scrollbar {
405
+ height: 8px;
406
+ }
407
+
408
+ .editors-container::-webkit-scrollbar-track {
409
+ background: rgba(255, 255, 255, 0.02);
410
+ border-radius: 4px;
411
+ }
412
+
413
+ .editors-container::-webkit-scrollbar-thumb {
414
+ background: rgba(255, 255, 255, 0.08);
415
+ border-radius: 4px;
416
+ }
417
+
418
+ .editors-container::-webkit-scrollbar-thumb:hover {
419
+ background: rgba(255, 255, 255, 0.12);
420
+ }
421
+
422
+ .editor-instance {
423
+ min-width: 320px;
424
+ flex: 1;
425
+ max-width: 480px;
426
+ background: rgba(10, 10, 10, 0.6);
427
+ display: flex;
428
+ flex-direction: column;
429
+ border-radius: var(--radius);
430
+ overflow: hidden;
431
+ margin-right: 0.75rem;
432
+ border: 1px solid rgba(255, 255, 255, 0.06);
433
+ transition: all 0.2s ease;
434
+ }
435
+
436
+ .editor-instance:hover {
437
+ border-color: rgba(255, 255, 255, 0.1);
438
+ }
439
+
440
+ .editor-instance:last-child {
441
+ margin-right: 0;
442
+ }
443
+
444
+ .editor-header {
445
+ padding: 0.5rem 0.75rem;
446
+ background: rgba(255, 255, 255, 0.02);
447
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
448
+ display: flex;
449
+ justify-content: space-between;
450
+ align-items: center;
451
+ height: 36px;
452
+ }
453
+
454
+ .editor-title {
455
+ font-size: 0.75rem;
456
+ font-weight: 500;
457
+ color: #999;
458
+ overflow: hidden;
459
+ text-overflow: ellipsis;
460
+ white-space: nowrap;
461
+ font-family: 'Consolas', 'Monaco', monospace;
462
+ }
463
+
464
+ .editor-actions {
465
+ display: flex;
466
+ gap: 0.25rem;
467
+ }
468
+
469
+ .editor-button {
470
+ width: 24px;
471
+ height: 24px;
472
+ padding: 0;
473
+ background: transparent;
474
+ border: none;
475
+ color: #666;
476
+ font-size: 1rem;
477
+ cursor: pointer;
478
+ transition: all 0.15s ease;
479
+ border-radius: var(--radius-sm);
480
+ display: flex;
481
+ align-items: center;
482
+ justify-content: center;
483
+ }
484
+
485
+ .editor-button:hover {
486
+ background: rgba(255, 255, 255, 0.05);
487
+ color: #999;
488
+ }
489
+
490
+ .editor-button.primary {
491
+ color: #4ade80;
492
+ }
493
+
494
+ .editor-button.primary:hover {
495
+ background: rgba(74, 222, 128, 0.1);
496
+ }
497
+
498
+ .editor-content {
499
+ flex: 1;
500
+ display: flex;
501
+ flex-direction: column;
502
+ overflow: hidden;
503
+ min-height: 0;
504
+ position: relative;
505
+ }
506
+
507
+ .editor-textarea {
508
+ width: 100%;
509
+ height: 100%;
510
+ background: transparent;
511
+ border: none;
512
+ color: #d0d0d0;
513
+ font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
514
+ font-size: 0.8125rem;
515
+ line-height: 1.6;
516
+ padding: 0.75rem;
517
+ resize: none;
518
+ outline: none;
519
+ transition: all 0.15s ease;
520
+ overflow: auto;
521
+ }
522
+
523
+ .editor-textarea:focus {
524
+ background: rgba(255, 255, 255, 0.01);
525
+ }
526
+
527
+ .editor-textarea::selection {
528
+ background: rgba(59, 130, 246, 0.3);
529
+ }
530
+
531
+ .editor-error {
532
+ position: absolute;
533
+ bottom: 0;
534
+ left: 0;
535
+ right: 0;
536
+ padding: 0.5rem 0.75rem;
537
+ background: rgba(239, 68, 68, 0.9);
538
+ backdrop-filter: blur(4px);
539
+ color: #fff;
540
+ font-size: 0.7rem;
541
+ font-weight: 500;
542
+ display: flex;
543
+ align-items: center;
544
+ gap: 0.375rem;
545
+ border-top: 1px solid rgba(239, 68, 68, 0.5);
546
+ }
547
+
548
+ .editor-error::before {
549
+ content: '!';
550
+ display: inline-flex;
551
+ width: 16px;
552
+ height: 16px;
553
+ align-items: center;
554
+ justify-content: center;
555
+ background: rgba(255, 255, 255, 0.2);
556
+ border-radius: 50%;
557
+ font-size: 0.65rem;
558
+ font-weight: bold;
559
+ }
560
+
561
+ .main-content {
562
+ position: absolute;
563
+ left: 0;
564
+ right: 0;
565
+ bottom: 0;
566
+ height: 100px;
567
+ }
568
+
569
+ @container wccDashboard (max-width: 600px) {
570
+ :host {
571
+ left: 0;
572
+ height: ${this.editingProperties.length > 0 ? 160 + this.editorHeight : 160}px;
573
+ }
574
+
575
+ .main-content {
576
+ height: 160px;
577
+ }
578
+
579
+ .grid {
580
+ grid-template-columns: minmax(90px, 2fr) minmax(0, 5fr) minmax(52px, 1fr);
581
+ grid-template-rows: 60px 100px;
582
+ }
583
+
584
+ .properties {
585
+ grid-column: 1 / -1;
586
+ grid-row: 1;
587
+ display: flex;
588
+ overflow-x: auto;
589
+ overflow-y: hidden;
590
+ border-right: 0;
591
+ border-bottom: 1px solid var(--border);
592
+ }
593
+
594
+ .properties .property {
595
+ flex: 0 0 120px;
596
+ min-width: 0;
597
+ }
598
+
599
+ .themeSelector {
600
+ grid-column: 1;
601
+ grid-row: 2;
602
+ }
603
+
604
+ .viewportSelector {
605
+ grid-column: 2;
606
+ grid-row: 2;
607
+ }
608
+
609
+ .shareSelector {
610
+ grid-column: 3;
611
+ grid-row: 2;
612
+ }
613
+
614
+ .themeSelector,
615
+ .viewportSelector,
616
+ .shareSelector {
617
+ min-width: 0;
618
+ }
619
+
620
+ .button {
621
+ min-width: 0;
622
+ overflow: hidden;
623
+ padding: 0.25rem 0.1rem;
624
+ font-size: 0.55rem;
625
+ }
626
+
627
+ .panelheading {
628
+ overflow: hidden;
629
+ padding: 0.3rem 0.25rem;
630
+ text-overflow: ellipsis;
631
+ white-space: nowrap;
632
+ }
633
+
634
+ .warning {
635
+ top: 0.25rem;
636
+ right: 0.25rem;
637
+ bottom: calc(100px + 0.25rem);
638
+ left: 0.25rem;
639
+ padding: 0.5rem;
640
+ }
641
+ }
642
+ </style>
643
+ ${this.editingProperties.length > 0 ? html`
644
+ <div class="advanced-editor-container">
645
+ <div class="editor-header-bar">
646
+ <div class="editor-header-title">Property Editors</div>
647
+ <button class="editor-close-all" @click=${this.closeAllEditors}>
648
+ Close All
649
+ </button>
650
+ </div>
651
+ <div class="editors-container">
652
+ ${this.editingProperties.length === 0 ? html`
653
+ <div style="
654
+ flex: 1;
655
+ display: flex;
656
+ align-items: center;
657
+ justify-content: center;
658
+ color: #666;
659
+ font-size: 0.875rem;
660
+ text-align: center;
661
+ padding: 2rem;
662
+ ">
663
+ <div>
664
+ <div style="margin-bottom: 0.5rem; font-size: 1.5rem; opacity: 0.5;">{ }</div>
665
+ <div>No properties being edited</div>
666
+ <div style="font-size: 0.75rem; margin-top: 0.25rem; opacity: 0.7;">Click "Edit Object/Array" buttons to start editing</div>
667
+ </div>
668
+ </div>
669
+ ` : null}
670
+ ${this.editingProperties.map(prop => html`
671
+ <div class="editor-instance">
672
+ <div class="editor-header">
673
+ <div class="editor-title">${prop.name}</div>
674
+ <div class="editor-actions">
675
+ <button class="editor-button" @click=${() => this.handleEditorCancel(prop.id)}>✕</button>
676
+ <button class="editor-button primary" @click=${() => this.handleEditorSave(prop.id)}>✓</button>
677
+ </div>
678
+ </div>
679
+ <div class="editor-content">
680
+ <textarea
681
+ class="editor-textarea"
682
+ .value=${prop.editorValue}
683
+ @input=${(e: InputEvent) => {
684
+ const editor = this.editingProperties.find(p => p.id === prop.id);
685
+ if (editor) {
686
+ editor.editorValue = (e.target as HTMLTextAreaElement).value;
687
+ editor.editorError = '';
688
+ this.requestUpdate();
689
+ }
690
+ }}
691
+ @keydown=${(e: KeyboardEvent) => {
692
+ if (e.key === 'Tab') {
693
+ e.preventDefault();
694
+ const target = e.target as HTMLTextAreaElement;
695
+ const start = target.selectionStart;
696
+ const end = target.selectionEnd;
697
+ const value = target.value;
698
+ target.value = value.substring(0, start) + ' ' + value.substring(end);
699
+ target.selectionStart = target.selectionEnd = start + 2;
700
+ }
701
+ }}
702
+ ></textarea>
703
+ ${prop.editorError ? html`
704
+ <div class="editor-error">${prop.editorError}</div>
705
+ ` : null}
706
+ </div>
707
+ </div>
708
+ `)}
709
+ </div>
710
+ </div>
711
+ ` : null}
712
+ <div class="main-content">
713
+ <div class="grid">
714
+ <div class="properties" role="group" aria-label="Properties">
715
+ ${this.propertyContent}
716
+ </div>
717
+ <div class="themeSelector" role="group" aria-labelledby="wcc-theme-heading">
718
+ <div class="panelheading" id="wcc-theme-heading">Theme</div>
719
+ <div class="selectorButtons2">
720
+ ${([
721
+ ['dark', 'Dark', 'brightness_3'],
722
+ ['bright', 'Bright', 'flare'],
723
+ ] as Array<[TTheme, string, string]>).map(([theme, label, icon]) => html`
724
+ <button
725
+ type="button"
726
+ class="button ${this.selectedTheme === theme ? 'selected' : ''}"
727
+ aria-pressed=${this.selectedTheme === theme ? 'true' : 'false'}
728
+ @click=${() => this.selectTheme(theme)}
729
+ >
730
+ ${label}<i class="material-symbols-outlined" aria-hidden="true">${icon}</i>
731
+ </button>
732
+ `)}
733
+ </div>
734
+ </div>
735
+ <div class="viewportSelector" role="group" aria-labelledby="wcc-viewport-heading">
736
+ <div class="panelheading" id="wcc-viewport-heading">Viewport</div>
737
+ <div class="selectorButtons5">
738
+ ${([
739
+ ['phone', 'Phone', 'smartphone'],
740
+ ['phablet', 'Phablet', 'smartphone'],
741
+ ['tablet', 'Tablet', 'tablet'],
742
+ ['desktop', 'Desktop', 'desktop_windows'],
743
+ ['native', 'Native', 'fullscreen'],
744
+ ] as Array<[TEnvironment, string, string]>).map(([viewport, label, icon]) => html`
745
+ <button
746
+ type="button"
747
+ class="button ${this.selectedViewport === viewport ? 'selected' : ''}"
748
+ aria-pressed=${this.selectedViewport === viewport ? 'true' : 'false'}
749
+ @click=${() => this.selectViewport(viewport)}
750
+ >
751
+ ${label}<i class="material-symbols-outlined" aria-hidden="true">${icon}</i>
752
+ </button>
753
+ `)}
754
+ </div>
755
+ </div>
756
+ <div class="shareSelector" role="group" aria-labelledby="wcc-share-heading">
757
+ <div class="panelheading" id="wcc-share-heading">Share</div>
758
+ <div class="selectorButtons1">
759
+ <button
760
+ type="button"
761
+ class="button ${this.isRecording ? 'selected' : ''}"
762
+ aria-expanded=${this.showRecordingPanel ? 'true' : 'false'}
763
+ @click=${() => this.handleRecordButtonClick()}
764
+ >
765
+ ${this.isRecording ? 'Stop' : 'Record'}<i class="material-symbols-outlined" aria-hidden="true">${this.isRecording ? 'stop_circle' : 'videocam'}</i>
766
+ </button>
767
+ </div>
768
+ </div>
769
+ </div>
770
+ ${this.warning ? html`<div class="warning">${this.warning}</div>` : null}
771
+ </div>
772
+
773
+ <!-- Recording Panel (options + preview) -->
774
+ ${this.showRecordingPanel ? html`
775
+ <wcc-recording-panel
776
+ .dashboardRef=${this.dashboardRef}
777
+ @recording-start=${() => { this.isRecording = true; }}
778
+ @recording-stop=${() => { this.isRecording = false; }}
779
+ @duration-update=${(e: CustomEvent) => { this.recordingDuration = e.detail.duration; }}
780
+ @close=${() => { this.showRecordingPanel = false; this.isRecording = false; this.recordingDuration = 0; }}
781
+ ></wcc-recording-panel>
782
+ ` : null}
783
+ `;
784
+ }
785
+
786
+ public async createProperties() {
787
+ // Clear any previous warnings
788
+ this.warning = null;
789
+ if (!this.selectedItem) {
790
+ this.propertyContent = [];
791
+ return;
792
+ }
793
+ const getEnumValues = (propertyArg): any[] => {
794
+ const enumValues: any[] = [];
795
+ Object.keys(propertyArg.type).forEach((valueArg: string) => {
796
+ enumValues.push(valueArg);
797
+ });
798
+ return enumValues;
799
+ };
800
+ if (this.selectedItem && (this.selectedItem as any).demo) {
801
+ const anonItem: any = this.selectedItem;
802
+ if (!anonItem) {
803
+ this.warning = 'no element selected';
804
+ return;
805
+ }
806
+ const wccFrame = await this.dashboardRef.wccFrame;
807
+
808
+ // Wait for render to complete and any demo wrappers to run
809
+ await new Promise(resolve => setTimeout(resolve, 200));
810
+
811
+ // Try to find the element with recursive search
812
+ const viewport = await wccFrame.getViewportElement();
813
+ let firstFoundInstantiatedElement: HTMLElement = findElementInstance(
814
+ viewport,
815
+ this.selectedItem as any
816
+ );
817
+
818
+ // Retry logic if element not found
819
+ let retries = 0;
820
+ while (!firstFoundInstantiatedElement && retries < 5) {
821
+ await new Promise(resolve => setTimeout(resolve, 200));
822
+ try {
823
+ firstFoundInstantiatedElement = findElementInstance(
824
+ viewport,
825
+ this.selectedItem as any
826
+ );
827
+ } catch (error) {
828
+ console.error('Error during element search retry:', error);
829
+ }
830
+ retries++;
831
+ }
832
+
833
+ if (!firstFoundInstantiatedElement) {
834
+ this.warning = `no first instantiated element found for >>${anonItem.name}<< after ${retries} retries`;
835
+ this.propertyContent = [];
836
+ return;
837
+ }
838
+ const classProperties: Map<string, any> = anonItem.elementProperties;
839
+ if (!classProperties) {
840
+ this.warning = `selected element >>${anonItem.name}<< does not expose element properties`;
841
+ return;
842
+ }
843
+ this.warning = null;
844
+ const propertyArray: TemplateResult[] = [];
845
+ for (const [key, property] of classProperties) {
846
+ if (typeof key !== 'string' || key.startsWith('#') || key === 'goBright' || key === 'domtools') {
847
+ continue;
848
+ }
849
+ const propertyTypeString = determinePropertyType(property);
850
+ if (!propertyTypeString) {
851
+ continue;
852
+ }
853
+ propertyArray.push(
854
+ html`
855
+ <div class="property">
856
+ <div class="property-label">${key} (${propertyTypeString})</div>
857
+ ${(() => {
858
+ switch (propertyTypeString) {
859
+ case 'Boolean':
860
+ return html`<input
861
+ aria-label=${key}
862
+ type="checkbox"
863
+ ?checked=${firstFoundInstantiatedElement[key]}
864
+ @input="${(eventArg: any) => {
865
+ firstFoundInstantiatedElement[key] = eventArg.target.checked;
866
+ }}"
867
+ />`;
868
+ case 'String':
869
+ return html`<input
870
+ aria-label=${key}
871
+ type="text"
872
+ .value=${firstFoundInstantiatedElement[key] || ''}
873
+ @input="${(eventArg: any) => {
874
+ firstFoundInstantiatedElement[key] = eventArg.target.value;
875
+ }}"
876
+ />`;
877
+ case 'Number':
878
+ return html`<input
879
+ aria-label=${key}
880
+ type="number"
881
+ .value=${firstFoundInstantiatedElement[key] ?? ''}
882
+ @input="${(eventArg: any) => {
883
+ firstFoundInstantiatedElement[key] = parseFloat(eventArg.target.value) || 0;
884
+ }}"
885
+ />`;
886
+ case 'Enum':
887
+ const enumValues: any[] = getEnumValues(property);
888
+ return html`<select
889
+ aria-label=${key}
890
+ .value=${firstFoundInstantiatedElement[key] || ''}
891
+ @change="${(eventArg: any) => {
892
+ firstFoundInstantiatedElement[key] = eventArg.target.value;
893
+ }}"
894
+ >
895
+ ${enumValues.map((valueArg) => {
896
+ return html`
897
+ <option
898
+ value="${valueArg}"
899
+ >
900
+ ${valueArg}
901
+ </option>
902
+ `;
903
+ })}
904
+ </select>`;
905
+ case 'Object':
906
+ case 'Array':
907
+ return html`<button
908
+ type="button"
909
+ aria-label=${`Edit ${key}`}
910
+ class="editor-button"
911
+ style="width: 100%; margin-top: 0.25rem;"
912
+ @click="${() => this.openAdvancedEditor(key, firstFoundInstantiatedElement[key], firstFoundInstantiatedElement)}"
913
+ >
914
+ Edit ${propertyTypeString}
915
+ </button>`;
916
+ default:
917
+ return html`<div style="color: #666; font-size: 0.7rem;">Unsupported type</div>`;
918
+ }
919
+ })()}
920
+ </div>
921
+ `
922
+ );
923
+ }
924
+ this.propertyContent = propertyArray;
925
+ } else {
926
+ this.propertyContent = [];
927
+ this.warning = `You selected a page.`;
928
+ return null;
929
+ }
930
+ }
931
+
932
+ public selectTheme(themeArg: TTheme) {
933
+ this.selectedTheme = themeArg;
934
+ this.dispatchEvent(
935
+ new CustomEvent('selectedTheme', {
936
+ detail: themeArg,
937
+ })
938
+ );
939
+ this.dashboardRef.buildUrl();
940
+ }
941
+
942
+ protected updated(changedProperties: Map<string, unknown>) {
943
+ super.updated(changedProperties);
944
+
945
+ // Handle delayed hide for native mode transition
946
+ if (changedProperties.has('isNative')) {
947
+ if (this.isNative) {
948
+ // Delay hiding until frame animation completes
949
+ setTimeout(() => {
950
+ this.isHidden = true;
951
+ }, 300);
952
+ } else {
953
+ // Show immediately when exiting native mode
954
+ this.isHidden = false;
955
+ }
956
+ }
957
+
958
+ // Only recreate properties when selectedItem changes
959
+ if (changedProperties.has('selectedItem')) {
960
+ this.createProperties().catch(error => {
961
+ console.error('Error creating properties:', error);
962
+ this.propertyContent = [];
963
+ });
964
+ }
965
+ }
966
+
967
+ public selectViewport(viewport: TEnvironment) {
968
+ this.selectedViewport = viewport;
969
+ setEnvironment(viewport);
970
+ this.dispatchEvent(
971
+ new CustomEvent('selectedViewport', {
972
+ detail: viewport,
973
+ })
974
+ );
975
+ this.dashboardRef.buildUrl();
976
+ }
977
+
978
+ private openAdvancedEditor(propertyName: string, value: any, element: HTMLElement) {
979
+ // Check if this property is already being edited
980
+ const existingEditor = this.editingProperties.find(p => p.name === propertyName && p.element === element);
981
+ if (existingEditor) {
982
+ return; // Property is already open for editing
983
+ }
984
+
985
+ const newEditor = {
986
+ id: `${propertyName}-${Date.now()}`,
987
+ name: propertyName,
988
+ value: value,
989
+ element: element,
990
+ editorValue: JSON.stringify(value, null, 2),
991
+ editorError: ''
992
+ };
993
+
994
+ this.editingProperties = [...this.editingProperties, newEditor];
995
+
996
+ // Notify parent to resize frame if this is the first editor
997
+ if (this.editingProperties.length === 1) {
998
+ this.dispatchEvent(
999
+ new CustomEvent('editorStateChanged', {
1000
+ detail: { isOpen: true },
1001
+ bubbles: true
1002
+ })
1003
+ );
1004
+ }
1005
+ }
1006
+
1007
+ private handleEditorSave(editorId: string) {
1008
+ const editor = this.editingProperties.find(p => p.id === editorId);
1009
+ if (!editor) return;
1010
+
1011
+ try {
1012
+ const parsedValue = JSON.parse(editor.editorValue);
1013
+ editor.element[editor.name] = parsedValue;
1014
+
1015
+ // Remove this editor from the list
1016
+ this.editingProperties = this.editingProperties.filter(p => p.id !== editorId);
1017
+
1018
+ // If no more editors, notify parent to resize frame
1019
+ if (this.editingProperties.length === 0) {
1020
+ this.dispatchEvent(
1021
+ new CustomEvent('editorStateChanged', {
1022
+ detail: { isOpen: false },
1023
+ bubbles: true
1024
+ })
1025
+ );
1026
+ }
1027
+
1028
+ // Refresh properties display
1029
+ this.createProperties();
1030
+ } catch (error) {
1031
+ // Update error for this specific editor
1032
+ const editorIndex = this.editingProperties.findIndex(p => p.id === editorId);
1033
+ if (editorIndex !== -1) {
1034
+ this.editingProperties[editorIndex].editorError = `Invalid JSON: ${error.message}`;
1035
+ this.requestUpdate();
1036
+ }
1037
+ }
1038
+ }
1039
+
1040
+ private handleEditorCancel(editorId: string) {
1041
+ // Remove this editor from the list
1042
+ this.editingProperties = this.editingProperties.filter(p => p.id !== editorId);
1043
+
1044
+ // If no more editors, notify parent to resize frame
1045
+ if (this.editingProperties.length === 0) {
1046
+ this.dispatchEvent(
1047
+ new CustomEvent('editorStateChanged', {
1048
+ detail: { isOpen: false },
1049
+ bubbles: true
1050
+ })
1051
+ );
1052
+ }
1053
+ }
1054
+
1055
+ private closeAllEditors() {
1056
+ this.editingProperties = [];
1057
+
1058
+ // Notify parent to resize frame back
1059
+ this.dispatchEvent(
1060
+ new CustomEvent('editorStateChanged', {
1061
+ detail: { isOpen: false },
1062
+ bubbles: true
1063
+ })
1064
+ );
1065
+ }
1066
+
1067
+ private toggleNative() {
1068
+ this.dispatchEvent(
1069
+ new CustomEvent('toggleNative', {
1070
+ bubbles: true
1071
+ })
1072
+ );
1073
+ }
1074
+
1075
+ // ==================== Recording Methods ====================
1076
+
1077
+ private handleRecordButtonClick() {
1078
+ if (this.isRecording) {
1079
+ // Stop recording by calling the panel's stopRecording method
1080
+ const panel = this.shadowRoot?.querySelector('wcc-recording-panel') as any;
1081
+ if (panel && panel.stopRecording) {
1082
+ panel.stopRecording();
1083
+ }
1084
+ } else {
1085
+ // Toggle the recording panel
1086
+ this.showRecordingPanel = !this.showRecordingPanel;
1087
+ }
1088
+ }
1089
+ }