@design.estate/dees-wcctools 3.4.0 → 3.5.1

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.1",
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.1',
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,14 @@ 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
+
69
+ // Sidebar width (resizable)
70
+ @property({ type: Number })
71
+ accessor sidebarWidth: number = 200;
72
+
65
73
  // Derived from selectedViewport - no need for separate property
66
74
  public get isNative(): boolean {
67
75
  return this.selectedViewport === 'native';
@@ -122,6 +130,8 @@ export class WccDashboard extends DeesElement {
122
130
  .dashboardRef=${this}
123
131
  .selectedItem=${this.selectedItem}
124
132
  .searchQuery=${this.searchQuery}
133
+ .pinnedItems=${this.pinnedItems}
134
+ .sidebarWidth=${this.sidebarWidth}
125
135
  .isNative=${this.isNative}
126
136
  @selectedType=${(eventArg) => {
127
137
  this.selectedType = eventArg.detail;
@@ -136,6 +146,19 @@ export class WccDashboard extends DeesElement {
136
146
  this.searchQuery = eventArg.detail;
137
147
  this.updateUrlWithScrollState();
138
148
  }}
149
+ @pinnedChanged=${(eventArg: CustomEvent) => {
150
+ this.pinnedItems = eventArg.detail;
151
+ this.updateUrlWithScrollState();
152
+ }}
153
+ @widthChanged=${async (eventArg: CustomEvent) => {
154
+ this.sidebarWidth = eventArg.detail;
155
+ this.updateUrlWithScrollState();
156
+ const frame = await this.wccFrame;
157
+ if (frame) {
158
+ frame.sidebarWidth = eventArg.detail;
159
+ frame.requestUpdate();
160
+ }
161
+ }}
139
162
  ></wcc-sidebar>
140
163
  <wcc-properties
141
164
  .dashboardRef=${this}
@@ -144,6 +167,7 @@ export class WccDashboard extends DeesElement {
144
167
  .selectedViewport=${this.selectedViewport}
145
168
  .selectedTheme=${this.selectedTheme}
146
169
  .isNative=${this.isNative}
170
+ .sidebarWidth=${this.sidebarWidth}
147
171
  @selectedViewport=${(eventArg) => {
148
172
  this.selectedViewport = eventArg.detail;
149
173
  this.scheduleUpdate();
@@ -162,7 +186,7 @@ export class WccDashboard extends DeesElement {
162
186
  this.toggleNative();
163
187
  }}
164
188
  ></wcc-properties>
165
- <wcc-frame id="wccFrame" viewport=${this.selectedViewport} .isNative=${this.isNative}>
189
+ <wcc-frame id="wccFrame" viewport=${this.selectedViewport} .isNative=${this.isNative} .sidebarWidth=${this.sidebarWidth}>
166
190
  </wcc-frame>
167
191
  `;
168
192
  }
@@ -237,6 +261,8 @@ export class WccDashboard extends DeesElement {
237
261
  const search = routeInfo.queryParams.search;
238
262
  const frameScrollY = routeInfo.queryParams.frameScrollY;
239
263
  const sidebarScrollY = routeInfo.queryParams.sidebarScrollY;
264
+ const pinned = routeInfo.queryParams.pinned;
265
+ const sidebarWidth = routeInfo.queryParams.sidebarWidth;
240
266
 
241
267
  if (search) {
242
268
  this.searchQuery = search;
@@ -249,13 +275,36 @@ export class WccDashboard extends DeesElement {
249
275
  if (sidebarScrollY) {
250
276
  this.sidebarScrollY = parseInt(sidebarScrollY);
251
277
  }
278
+ if (pinned) {
279
+ const newPinned = new Set(pinned.split(',').filter(Boolean));
280
+ // Only update if actually different to avoid update loops
281
+ if (this.pinnedItems.size !== newPinned.size ||
282
+ ![...newPinned].every(k => this.pinnedItems.has(k))) {
283
+ this.pinnedItems = newPinned;
284
+ }
285
+ } else if (this.pinnedItems.size > 0) {
286
+ this.pinnedItems = new Set();
287
+ }
288
+ if (sidebarWidth) {
289
+ this.sidebarWidth = parseInt(sidebarWidth, 10);
290
+ }
252
291
 
253
- // Apply scroll positions after a short delay to ensure DOM is ready
254
- setTimeout(() => {
292
+ // Apply scroll positions and update frame after a short delay to ensure DOM is ready
293
+ setTimeout(async () => {
255
294
  this.applyScrollPositions();
295
+ // Ensure frame gets the sidebarWidth
296
+ const frame = await this.wccFrame;
297
+ if (frame) {
298
+ frame.sidebarWidth = this.sidebarWidth;
299
+ frame.requestUpdate();
300
+ }
256
301
  }, 100);
257
302
  } else {
258
303
  this.searchQuery = '';
304
+ // Only clear if not already empty to avoid update loops
305
+ if (this.pinnedItems.size > 0) {
306
+ this.pinnedItems = new Set();
307
+ }
259
308
  }
260
309
 
261
310
  const domtoolsInstance = await plugins.deesDomtools.elementBasic.setup();
@@ -301,6 +350,8 @@ export class WccDashboard extends DeesElement {
301
350
  const search = routeInfo.queryParams.search;
302
351
  const frameScrollY = routeInfo.queryParams.frameScrollY;
303
352
  const sidebarScrollY = routeInfo.queryParams.sidebarScrollY;
353
+ const pinned = routeInfo.queryParams.pinned;
354
+ const sidebarWidth = routeInfo.queryParams.sidebarWidth;
304
355
 
305
356
  if (search) {
306
357
  this.searchQuery = search;
@@ -313,6 +364,19 @@ export class WccDashboard extends DeesElement {
313
364
  if (sidebarScrollY) {
314
365
  this.sidebarScrollY = parseInt(sidebarScrollY);
315
366
  }
367
+ if (pinned) {
368
+ const newPinned = new Set(pinned.split(',').filter(Boolean));
369
+ // Only update if actually different to avoid update loops
370
+ if (this.pinnedItems.size !== newPinned.size ||
371
+ ![...newPinned].every(k => this.pinnedItems.has(k))) {
372
+ this.pinnedItems = newPinned;
373
+ }
374
+ } else if (this.pinnedItems.size > 0) {
375
+ this.pinnedItems = new Set();
376
+ }
377
+ if (sidebarWidth) {
378
+ this.sidebarWidth = parseInt(sidebarWidth, 10);
379
+ }
316
380
 
317
381
  // Apply scroll positions after a short delay to ensure DOM is ready
318
382
  setTimeout(() => {
@@ -320,6 +384,10 @@ export class WccDashboard extends DeesElement {
320
384
  }, 100);
321
385
  } else {
322
386
  this.searchQuery = '';
387
+ // Only clear if not already empty to avoid update loops
388
+ if (this.pinnedItems.size > 0) {
389
+ this.pinnedItems = new Set();
390
+ }
323
391
  }
324
392
 
325
393
  const domtoolsInstance = await plugins.deesDomtools.elementBasic.setup();
@@ -402,6 +470,12 @@ export class WccDashboard extends DeesElement {
402
470
  if (this.sidebarScrollY > 0) {
403
471
  queryParams.set('sidebarScrollY', this.sidebarScrollY.toString());
404
472
  }
473
+ if (this.pinnedItems.size > 0) {
474
+ queryParams.set('pinned', Array.from(this.pinnedItems).join(','));
475
+ }
476
+ if (this.sidebarWidth !== 200) {
477
+ queryParams.set('sidebarWidth', this.sidebarWidth.toString());
478
+ }
405
479
 
406
480
  const queryString = queryParams.toString();
407
481
  const fullUrl = queryString ? `${baseUrl}?${queryString}` : baseUrl;
@@ -462,6 +536,12 @@ export class WccDashboard extends DeesElement {
462
536
  if (this.sidebarScrollY > 0) {
463
537
  queryParams.set('sidebarScrollY', this.sidebarScrollY.toString());
464
538
  }
539
+ if (this.pinnedItems.size > 0) {
540
+ queryParams.set('pinned', Array.from(this.pinnedItems).join(','));
541
+ }
542
+ if (this.sidebarWidth !== 200) {
543
+ queryParams.set('sidebarWidth', this.sidebarWidth.toString());
544
+ }
465
545
 
466
546
  const queryString = queryParams.toString();
467
547
  const fullUrl = queryString ? `${baseUrl}?${queryString}` : baseUrl;
@@ -19,13 +19,18 @@ export class WccFrame extends DeesElement {
19
19
  @property({ type: Boolean })
20
20
  accessor isNative: boolean = false;
21
21
 
22
+ @property({ type: Number })
23
+ accessor sidebarWidth: number = 200;
24
+
25
+ @property({ type: Boolean })
26
+ accessor isResizing: boolean = false;
27
+
22
28
  public static styles = [
23
29
  css`
24
30
  :host {
25
31
  border: 10px solid #ffaeaf;
26
32
  position: absolute;
27
33
  background: ${cssManager.bdTheme('#333', '#000')};
28
- left: 200px;
29
34
  right: 0px;
30
35
  top: 0px;
31
36
  overflow-y: auto;
@@ -55,9 +60,9 @@ export class WccFrame extends DeesElement {
55
60
  ` : `
56
61
  bottom: ${this.advancedEditorOpen ? '400px' : '100px'};
57
62
  border: 10px solid #ffaeaf;
58
- left: 200px;
63
+ left: ${this.sidebarWidth}px;
59
64
  `}
60
- transition: all 0.3s ease;
65
+ transition: ${this.isResizing ? 'none' : 'all 0.3s ease'};
61
66
  ${this.isNative ? 'padding: 0px;' : (() => {
62
67
  switch (this.viewport) {
63
68
  case 'desktop':
@@ -67,19 +72,19 @@ export class WccFrame extends DeesElement {
67
72
  case 'tablet':
68
73
  return `
69
74
  padding: 0px ${
70
- (document.body.clientWidth - 200 - domtools.breakpoints.tablet) / 2
75
+ (document.body.clientWidth - this.sidebarWidth - domtools.breakpoints.tablet) / 2
71
76
  }px;
72
77
  `;
73
78
  case 'phablet':
74
79
  return `
75
80
  padding: 0px ${
76
- (document.body.clientWidth - 200 - domtools.breakpoints.phablet) / 2
81
+ (document.body.clientWidth - this.sidebarWidth - domtools.breakpoints.phablet) / 2
77
82
  }px;
78
83
  `;
79
84
  case 'phone':
80
85
  return `
81
86
  padding: 0px ${
82
- (document.body.clientWidth - 200 - domtools.breakpoints.phone) / 2
87
+ (document.body.clientWidth - this.sidebarWidth - domtools.breakpoints.phone) / 2
83
88
  }px;
84
89
  `;
85
90
  }
@@ -37,6 +37,9 @@ export class WccProperties extends DeesElement {
37
37
  @property()
38
38
  accessor isNative: boolean = false;
39
39
 
40
+ @property({ type: Number })
41
+ accessor sidebarWidth: number = 200;
42
+
40
43
  @state()
41
44
  accessor propertyContent: TemplateResult[] = [];
42
45
 
@@ -89,7 +92,7 @@ export class WccProperties extends DeesElement {
89
92
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
90
93
  box-sizing: border-box;
91
94
  position: absolute;
92
- left: 200px;
95
+ left: ${this.sidebarWidth}px;
93
96
  height: ${this.editingProperties.length > 0 ? 100 + this.editorHeight : 100}px;
94
97
  bottom: 0px;
95
98
  right: 0px;