@design.estate/dees-wcctools 3.4.0 → 3.5.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@design.estate/dees-wcctools",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "private": false,
5
5
  "description": "A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.",
6
6
  "exports": {
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@design.estate/dees-wcctools',
6
- version: '3.4.0',
6
+ version: '3.5.0',
7
7
  description: 'A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.'
8
8
  }
@@ -0,0 +1,211 @@
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
+ @customElement('wcc-contextmenu')
11
+ export class WccContextmenu extends DeesElement {
12
+ // Static method to show context menu at position
13
+ public static async show(
14
+ event: MouseEvent,
15
+ menuItems: IContextMenuItem[]
16
+ ): Promise<void> {
17
+ event.preventDefault();
18
+ event.stopPropagation();
19
+
20
+ // Remove any existing context menu
21
+ const existing = document.querySelector('wcc-contextmenu');
22
+ if (existing) {
23
+ existing.remove();
24
+ }
25
+
26
+ const menu = new WccContextmenu();
27
+ menu.menuItems = menuItems;
28
+ menu.x = event.clientX;
29
+ menu.y = event.clientY;
30
+
31
+ document.body.appendChild(menu);
32
+
33
+ // Wait for render then adjust position if needed
34
+ await menu.updateComplete;
35
+ menu.adjustPosition();
36
+ }
37
+
38
+ @property({ type: Array })
39
+ accessor menuItems: IContextMenuItem[] = [];
40
+
41
+ @property({ type: Number })
42
+ accessor x: number = 0;
43
+
44
+ @property({ type: Number })
45
+ accessor y: number = 0;
46
+
47
+ @state()
48
+ accessor visible: boolean = false;
49
+
50
+ private boundHandleOutsideClick = this.handleOutsideClick.bind(this);
51
+ private boundHandleKeydown = this.handleKeydown.bind(this);
52
+
53
+ public static styles = [
54
+ css`
55
+ :host {
56
+ position: fixed;
57
+ z-index: 10000;
58
+ opacity: 0;
59
+ transform: scale(0.95) translateY(-5px);
60
+ transition: opacity 0.15s ease, transform 0.15s ease;
61
+ pointer-events: none;
62
+ }
63
+
64
+ :host(.visible) {
65
+ opacity: 1;
66
+ transform: scale(1) translateY(0);
67
+ pointer-events: auto;
68
+ }
69
+
70
+ .menu {
71
+ min-width: 160px;
72
+ background: #0f0f0f;
73
+ border: 1px solid rgba(255, 255, 255, 0.1);
74
+ border-radius: 6px;
75
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
76
+ padding: 4px 0;
77
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
78
+ font-size: 12px;
79
+ }
80
+
81
+ .menu-item {
82
+ display: flex;
83
+ align-items: center;
84
+ gap: 8px;
85
+ padding: 8px 12px;
86
+ color: #ccc;
87
+ cursor: pointer;
88
+ transition: background 0.1s ease;
89
+ user-select: none;
90
+ }
91
+
92
+ .menu-item:hover {
93
+ background: rgba(59, 130, 246, 0.15);
94
+ color: #fff;
95
+ }
96
+
97
+ .menu-item.disabled {
98
+ opacity: 0.4;
99
+ cursor: not-allowed;
100
+ pointer-events: none;
101
+ }
102
+
103
+ .menu-item .icon {
104
+ font-family: 'Material Symbols Outlined';
105
+ font-size: 16px;
106
+ font-weight: normal;
107
+ font-style: normal;
108
+ line-height: 1;
109
+ letter-spacing: normal;
110
+ text-transform: none;
111
+ white-space: nowrap;
112
+ word-wrap: normal;
113
+ direction: ltr;
114
+ font-variation-settings: 'FILL' 0, 'wght' 300, 'GRAD' 0, 'opsz' 24;
115
+ opacity: 0.7;
116
+ }
117
+
118
+ .menu-item:hover .icon {
119
+ opacity: 1;
120
+ }
121
+
122
+ .menu-item .label {
123
+ flex: 1;
124
+ }
125
+ `
126
+ ];
127
+
128
+ public render(): TemplateResult {
129
+ return html`
130
+ <div class="menu">
131
+ ${this.menuItems.map(item => html`
132
+ <div
133
+ class="menu-item ${item.disabled ? 'disabled' : ''}"
134
+ @click=${() => this.handleItemClick(item)}
135
+ >
136
+ ${item.iconName ? html`<span class="icon">${item.iconName}</span>` : null}
137
+ <span class="label">${item.name}</span>
138
+ </div>
139
+ `)}
140
+ </div>
141
+ `;
142
+ }
143
+
144
+ async connectedCallback() {
145
+ await super.connectedCallback();
146
+ // Delay adding listeners to avoid immediate close
147
+ requestAnimationFrame(() => {
148
+ document.addEventListener('click', this.boundHandleOutsideClick);
149
+ document.addEventListener('contextmenu', this.boundHandleOutsideClick);
150
+ document.addEventListener('keydown', this.boundHandleKeydown);
151
+ this.classList.add('visible');
152
+ });
153
+ }
154
+
155
+ async disconnectedCallback() {
156
+ await super.disconnectedCallback();
157
+ document.removeEventListener('click', this.boundHandleOutsideClick);
158
+ document.removeEventListener('contextmenu', this.boundHandleOutsideClick);
159
+ document.removeEventListener('keydown', this.boundHandleKeydown);
160
+ }
161
+
162
+ private adjustPosition() {
163
+ const rect = this.getBoundingClientRect();
164
+ const windowWidth = window.innerWidth;
165
+ const windowHeight = window.innerHeight;
166
+
167
+ let x = this.x;
168
+ let y = this.y;
169
+
170
+ // Adjust if menu goes off right edge
171
+ if (x + rect.width > windowWidth - 10) {
172
+ x = windowWidth - rect.width - 10;
173
+ }
174
+
175
+ // Adjust if menu goes off bottom edge
176
+ if (y + rect.height > windowHeight - 10) {
177
+ y = windowHeight - rect.height - 10;
178
+ }
179
+
180
+ // Ensure not off left or top
181
+ if (x < 10) x = 10;
182
+ if (y < 10) y = 10;
183
+
184
+ this.style.left = `${x}px`;
185
+ this.style.top = `${y}px`;
186
+ }
187
+
188
+ private handleOutsideClick(e: Event) {
189
+ const path = e.composedPath();
190
+ if (!path.includes(this)) {
191
+ this.close();
192
+ }
193
+ }
194
+
195
+ private handleKeydown(e: KeyboardEvent) {
196
+ if (e.key === 'Escape') {
197
+ this.close();
198
+ }
199
+ }
200
+
201
+ private async handleItemClick(item: IContextMenuItem) {
202
+ if (item.disabled) return;
203
+ await item.action();
204
+ this.close();
205
+ }
206
+
207
+ private close() {
208
+ this.classList.remove('visible');
209
+ setTimeout(() => this.remove(), 150);
210
+ }
211
+ }
@@ -62,6 +62,10 @@ export class WccDashboard extends DeesElement {
62
62
  @property()
63
63
  accessor searchQuery: string = '';
64
64
 
65
+ // Pinned items as Set of "sectionName::itemName"
66
+ @property({ attribute: false })
67
+ accessor pinnedItems: Set<string> = new Set();
68
+
65
69
  // Derived from selectedViewport - no need for separate property
66
70
  public get isNative(): boolean {
67
71
  return this.selectedViewport === 'native';
@@ -122,6 +126,7 @@ export class WccDashboard extends DeesElement {
122
126
  .dashboardRef=${this}
123
127
  .selectedItem=${this.selectedItem}
124
128
  .searchQuery=${this.searchQuery}
129
+ .pinnedItems=${this.pinnedItems}
125
130
  .isNative=${this.isNative}
126
131
  @selectedType=${(eventArg) => {
127
132
  this.selectedType = eventArg.detail;
@@ -136,6 +141,10 @@ export class WccDashboard extends DeesElement {
136
141
  this.searchQuery = eventArg.detail;
137
142
  this.updateUrlWithScrollState();
138
143
  }}
144
+ @pinnedChanged=${(eventArg: CustomEvent) => {
145
+ this.pinnedItems = eventArg.detail;
146
+ this.updateUrlWithScrollState();
147
+ }}
139
148
  ></wcc-sidebar>
140
149
  <wcc-properties
141
150
  .dashboardRef=${this}
@@ -237,6 +246,7 @@ export class WccDashboard extends DeesElement {
237
246
  const search = routeInfo.queryParams.search;
238
247
  const frameScrollY = routeInfo.queryParams.frameScrollY;
239
248
  const sidebarScrollY = routeInfo.queryParams.sidebarScrollY;
249
+ const pinned = routeInfo.queryParams.pinned;
240
250
 
241
251
  if (search) {
242
252
  this.searchQuery = search;
@@ -249,6 +259,16 @@ export class WccDashboard extends DeesElement {
249
259
  if (sidebarScrollY) {
250
260
  this.sidebarScrollY = parseInt(sidebarScrollY);
251
261
  }
262
+ if (pinned) {
263
+ const newPinned = new Set(pinned.split(',').filter(Boolean));
264
+ // Only update if actually different to avoid update loops
265
+ if (this.pinnedItems.size !== newPinned.size ||
266
+ ![...newPinned].every(k => this.pinnedItems.has(k))) {
267
+ this.pinnedItems = newPinned;
268
+ }
269
+ } else if (this.pinnedItems.size > 0) {
270
+ this.pinnedItems = new Set();
271
+ }
252
272
 
253
273
  // Apply scroll positions after a short delay to ensure DOM is ready
254
274
  setTimeout(() => {
@@ -256,6 +276,10 @@ export class WccDashboard extends DeesElement {
256
276
  }, 100);
257
277
  } else {
258
278
  this.searchQuery = '';
279
+ // Only clear if not already empty to avoid update loops
280
+ if (this.pinnedItems.size > 0) {
281
+ this.pinnedItems = new Set();
282
+ }
259
283
  }
260
284
 
261
285
  const domtoolsInstance = await plugins.deesDomtools.elementBasic.setup();
@@ -301,6 +325,7 @@ export class WccDashboard extends DeesElement {
301
325
  const search = routeInfo.queryParams.search;
302
326
  const frameScrollY = routeInfo.queryParams.frameScrollY;
303
327
  const sidebarScrollY = routeInfo.queryParams.sidebarScrollY;
328
+ const pinned = routeInfo.queryParams.pinned;
304
329
 
305
330
  if (search) {
306
331
  this.searchQuery = search;
@@ -313,6 +338,16 @@ export class WccDashboard extends DeesElement {
313
338
  if (sidebarScrollY) {
314
339
  this.sidebarScrollY = parseInt(sidebarScrollY);
315
340
  }
341
+ if (pinned) {
342
+ const newPinned = new Set(pinned.split(',').filter(Boolean));
343
+ // Only update if actually different to avoid update loops
344
+ if (this.pinnedItems.size !== newPinned.size ||
345
+ ![...newPinned].every(k => this.pinnedItems.has(k))) {
346
+ this.pinnedItems = newPinned;
347
+ }
348
+ } else if (this.pinnedItems.size > 0) {
349
+ this.pinnedItems = new Set();
350
+ }
316
351
 
317
352
  // Apply scroll positions after a short delay to ensure DOM is ready
318
353
  setTimeout(() => {
@@ -320,6 +355,10 @@ export class WccDashboard extends DeesElement {
320
355
  }, 100);
321
356
  } else {
322
357
  this.searchQuery = '';
358
+ // Only clear if not already empty to avoid update loops
359
+ if (this.pinnedItems.size > 0) {
360
+ this.pinnedItems = new Set();
361
+ }
323
362
  }
324
363
 
325
364
  const domtoolsInstance = await plugins.deesDomtools.elementBasic.setup();
@@ -402,6 +441,9 @@ export class WccDashboard extends DeesElement {
402
441
  if (this.sidebarScrollY > 0) {
403
442
  queryParams.set('sidebarScrollY', this.sidebarScrollY.toString());
404
443
  }
444
+ if (this.pinnedItems.size > 0) {
445
+ queryParams.set('pinned', Array.from(this.pinnedItems).join(','));
446
+ }
405
447
 
406
448
  const queryString = queryParams.toString();
407
449
  const fullUrl = queryString ? `${baseUrl}?${queryString}` : baseUrl;
@@ -462,6 +504,9 @@ export class WccDashboard extends DeesElement {
462
504
  if (this.sidebarScrollY > 0) {
463
505
  queryParams.set('sidebarScrollY', this.sidebarScrollY.toString());
464
506
  }
507
+ if (this.pinnedItems.size > 0) {
508
+ queryParams.set('pinned', Array.from(this.pinnedItems).join(','));
509
+ }
465
510
 
466
511
  const queryString = queryParams.toString();
467
512
  const fullUrl = queryString ? `${baseUrl}?${queryString}` : baseUrl;