@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,8 @@
1
+ /**
2
+ * autocreated commitinfo by @push.rocks/commitinfo
3
+ */
4
+ export const commitinfo = {
5
+ name: '@design.estate/wcctools',
6
+ version: '5.1.0',
7
+ description: 'A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.'
8
+ }
@@ -0,0 +1,291 @@
1
+ import { DeesElement, property, html, customElement, type TemplateResult, state, css, cssManager } from '@design.estate/dees-element';
2
+
3
+ export interface IContextMenuItem {
4
+ name: string;
5
+ iconName?: string;
6
+ action: () => void | Promise<void>;
7
+ disabled?: boolean;
8
+ }
9
+
10
+ export interface IContextMenuOptions {
11
+ /** The element the menu belongs to: focus returns to it when the menu closes */
12
+ ownerElement?: HTMLElement;
13
+ }
14
+
15
+ @customElement('wcc-contextmenu')
16
+ export class WccContextmenu extends DeesElement {
17
+ /**
18
+ * Show a context menu at the event's position. The menu takes focus (its first item),
19
+ * is operated with the arrow keys, Home/End, Enter/Space and Escape/Tab, and returns
20
+ * focus to `ownerElement` when it closes from the keyboard or after an action.
21
+ */
22
+ public static async show(
23
+ event: MouseEvent,
24
+ menuItems: IContextMenuItem[],
25
+ options: IContextMenuOptions = {}
26
+ ): Promise<void> {
27
+ event.preventDefault();
28
+ event.stopPropagation();
29
+
30
+ // Remove any existing context menu
31
+ const existing = document.querySelector('wcc-contextmenu');
32
+ if (existing) {
33
+ existing.remove();
34
+ }
35
+
36
+ const menu = new WccContextmenu();
37
+ menu.menuItems = menuItems;
38
+ menu.x = event.clientX;
39
+ menu.y = event.clientY;
40
+ menu.ownerElement = options.ownerElement ?? null;
41
+
42
+ document.body.appendChild(menu);
43
+
44
+ // Wait for render then adjust position if needed
45
+ await menu.updateComplete;
46
+ menu.adjustPosition();
47
+ menu.focusItem(0);
48
+ }
49
+
50
+ /** Focus returns here when the menu closes from the keyboard or after an action */
51
+ private ownerElement: HTMLElement | null = null;
52
+ private closing = false;
53
+
54
+ @property({ type: Array })
55
+ accessor menuItems: IContextMenuItem[] = [];
56
+
57
+ @property({ type: Number })
58
+ accessor x: number = 0;
59
+
60
+ @property({ type: Number })
61
+ accessor y: number = 0;
62
+
63
+ @state()
64
+ accessor visible: boolean = false;
65
+
66
+ private boundHandleOutsideClick = this.handleOutsideClick.bind(this);
67
+ private boundHandleKeydown = this.handleKeydown.bind(this);
68
+
69
+ public static styles = [
70
+ css`
71
+ :host {
72
+ position: fixed;
73
+ z-index: 10000;
74
+ opacity: 0;
75
+ transform: scale(0.95) translateY(-5px);
76
+ transition: opacity 0.15s ease, transform 0.15s ease;
77
+ pointer-events: none;
78
+ }
79
+
80
+ :host(.visible) {
81
+ opacity: 1;
82
+ transform: scale(1) translateY(0);
83
+ pointer-events: auto;
84
+ }
85
+
86
+ .menu {
87
+ min-width: 160px;
88
+ background: #0f0f0f;
89
+ border: 1px solid rgba(255, 255, 255, 0.1);
90
+ border-radius: 6px;
91
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
92
+ padding: 4px 0;
93
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
94
+ font-size: 12px;
95
+ }
96
+
97
+ .menu-item {
98
+ display: flex;
99
+ align-items: center;
100
+ gap: 8px;
101
+ padding: 8px 12px;
102
+ color: #ccc;
103
+ cursor: pointer;
104
+ transition: background 0.1s ease;
105
+ user-select: none;
106
+ }
107
+
108
+ .menu {
109
+ outline: none;
110
+ }
111
+
112
+ .menu-item:hover,
113
+ .menu-item:focus {
114
+ background: rgba(59, 130, 246, 0.15);
115
+ color: #fff;
116
+ outline: none;
117
+ }
118
+
119
+ .menu-item:focus-visible {
120
+ box-shadow: inset 0 0 0 2px #3b82f6;
121
+ }
122
+
123
+ .menu-item.disabled {
124
+ opacity: 0.4;
125
+ cursor: not-allowed;
126
+ pointer-events: none;
127
+ }
128
+
129
+ .menu-item .icon {
130
+ font-family: 'Material Symbols Outlined';
131
+ font-size: 16px;
132
+ font-weight: normal;
133
+ font-style: normal;
134
+ line-height: 1;
135
+ letter-spacing: normal;
136
+ text-transform: none;
137
+ white-space: nowrap;
138
+ word-wrap: normal;
139
+ direction: ltr;
140
+ font-variation-settings: 'FILL' 0, 'wght' 300, 'GRAD' 0, 'opsz' 24;
141
+ opacity: 0.7;
142
+ }
143
+
144
+ .menu-item:hover .icon {
145
+ opacity: 1;
146
+ }
147
+
148
+ .menu-item .label {
149
+ flex: 1;
150
+ }
151
+ `
152
+ ];
153
+
154
+ public render(): TemplateResult {
155
+ return html`
156
+ <div class="menu" role="menu" tabindex="-1" @keydown=${this.handleMenuKeydown}>
157
+ ${this.menuItems.map(item => html`
158
+ <div
159
+ class="menu-item ${item.disabled ? 'disabled' : ''}"
160
+ role="menuitem"
161
+ tabindex="-1"
162
+ aria-disabled=${item.disabled ? 'true' : 'false'}
163
+ @click=${() => this.handleItemClick(item)}
164
+ >
165
+ ${item.iconName ? html`<span class="icon" aria-hidden="true">${item.iconName}</span>` : null}
166
+ <span class="label">${item.name}</span>
167
+ </div>
168
+ `)}
169
+ </div>
170
+ `;
171
+ }
172
+
173
+ async connectedCallback() {
174
+ await super.connectedCallback();
175
+ // Delay adding listeners to avoid immediate close
176
+ requestAnimationFrame(() => {
177
+ document.addEventListener('click', this.boundHandleOutsideClick);
178
+ document.addEventListener('contextmenu', this.boundHandleOutsideClick);
179
+ document.addEventListener('keydown', this.boundHandleKeydown);
180
+ this.classList.add('visible');
181
+ });
182
+ }
183
+
184
+ async disconnectedCallback() {
185
+ await super.disconnectedCallback();
186
+ document.removeEventListener('click', this.boundHandleOutsideClick);
187
+ document.removeEventListener('contextmenu', this.boundHandleOutsideClick);
188
+ document.removeEventListener('keydown', this.boundHandleKeydown);
189
+ }
190
+
191
+ private adjustPosition() {
192
+ const rect = this.getBoundingClientRect();
193
+ const windowWidth = window.innerWidth;
194
+ const windowHeight = window.innerHeight;
195
+
196
+ let x = this.x;
197
+ let y = this.y;
198
+
199
+ // Adjust if menu goes off right edge
200
+ if (x + rect.width > windowWidth - 10) {
201
+ x = windowWidth - rect.width - 10;
202
+ }
203
+
204
+ // Adjust if menu goes off bottom edge
205
+ if (y + rect.height > windowHeight - 10) {
206
+ y = windowHeight - rect.height - 10;
207
+ }
208
+
209
+ // Ensure not off left or top
210
+ if (x < 10) x = 10;
211
+ if (y < 10) y = 10;
212
+
213
+ this.style.left = `${x}px`;
214
+ this.style.top = `${y}px`;
215
+ }
216
+
217
+ private handleOutsideClick(e: Event) {
218
+ const path = e.composedPath();
219
+ if (!path.includes(this)) {
220
+ this.close();
221
+ }
222
+ }
223
+
224
+ private handleKeydown(e: KeyboardEvent) {
225
+ if (e.key === 'Escape') {
226
+ this.close(true);
227
+ }
228
+ }
229
+
230
+ private getEnabledItems(): HTMLElement[] {
231
+ return Array.from(this.shadowRoot?.querySelectorAll<HTMLElement>('.menu-item:not(.disabled)') ?? []);
232
+ }
233
+
234
+ /** Focus an enabled item by index (wrapping); the menu itself if there is none */
235
+ private focusItem(indexArg: number) {
236
+ const items = this.getEnabledItems();
237
+ if (items.length === 0) {
238
+ (this.shadowRoot?.querySelector('.menu') as HTMLElement | null)?.focus({ preventScroll: true });
239
+ return;
240
+ }
241
+ const index = ((indexArg % items.length) + items.length) % items.length;
242
+ items[index].focus({ preventScroll: true });
243
+ }
244
+
245
+ private handleMenuKeydown = (e: KeyboardEvent) => {
246
+ const items = this.getEnabledItems();
247
+ const current = items.indexOf(this.shadowRoot?.activeElement as HTMLElement);
248
+ switch (e.key) {
249
+ case 'ArrowDown':
250
+ this.focusItem(current + 1);
251
+ break;
252
+ case 'ArrowUp':
253
+ this.focusItem(current < 0 ? -1 : current - 1);
254
+ break;
255
+ case 'Home':
256
+ this.focusItem(0);
257
+ break;
258
+ case 'End':
259
+ this.focusItem(-1);
260
+ break;
261
+ case 'Enter':
262
+ case ' ':
263
+ if (current >= 0) items[current].click();
264
+ break;
265
+ case 'Escape':
266
+ case 'Tab':
267
+ this.close(true);
268
+ break;
269
+ default:
270
+ return;
271
+ }
272
+ e.preventDefault();
273
+ e.stopPropagation();
274
+ };
275
+
276
+ private async handleItemClick(item: IContextMenuItem) {
277
+ if (item.disabled) return;
278
+ await item.action();
279
+ this.close(true);
280
+ }
281
+
282
+ private close(returnFocus = false) {
283
+ if (this.closing) return;
284
+ this.closing = true;
285
+ if (returnFocus && this.ownerElement?.isConnected) {
286
+ this.ownerElement.focus({ preventScroll: true });
287
+ }
288
+ this.classList.remove('visible');
289
+ setTimeout(() => this.remove(), 150);
290
+ }
291
+ }