fix(properties-panel): enhance element detection and error handling for nested structures
This commit is contained in:
		| @@ -12,10 +12,16 @@ The properties panel had timing issues detecting rendered elements because: | ||||
| 1. Added a 100ms initial delay to allow render completion | ||||
| 2. Implemented recursive element search that: | ||||
|    - Searches through nested children up to 5 levels deep | ||||
|    - Checks shadow roots of elements | ||||
|    - Handles complex DOM structures | ||||
|    - Checks both light DOM and shadow DOM for all elements | ||||
|    - Handles complex DOM structures generically | ||||
|    - Works with any wrapper elements, not specific to dees-demowrapper | ||||
| 3. Added retry mechanism with up to 5 attempts (200ms between retries) | ||||
| 4. Improved error messages to show retry count | ||||
| 5. Comprehensive error handling: | ||||
|    - Errors in element search don't break the update cycle | ||||
|    - Individual property errors don't prevent other properties from rendering | ||||
|    - scheduleUpdate always completes even if createProperties fails | ||||
|    - Clears warnings and property content appropriately on errors | ||||
|  | ||||
| ### Code Flow | ||||
| 1. Dashboard renders element demo into viewport using `render(anonItem.demo(), viewport)` | ||||
|   | ||||
| @@ -61,4 +61,28 @@ The properties panel has timing issues detecting rendered elements because: | ||||
| - Access children via wrapper.children property | ||||
| - Updated documentation with correct import path (lowercase 'demotools') | ||||
| - Examples show how to use querySelector for powerful element selection | ||||
| - Added clarifying comment about querySelector working on slotted content | ||||
| - Added clarifying comment about querySelector working on slotted content | ||||
|  | ||||
| ## Fixed Properties Panel Compatibility: | ||||
| - Made element search generic - works with any container elements | ||||
| - Searches both light DOM and shadow DOM recursively | ||||
| - Improved error handling to prevent breaking the update cycle | ||||
| - Errors in one property don't prevent others from rendering | ||||
| - Detection continues working even after errors occur | ||||
| - Maintains compatibility with all element structures | ||||
|  | ||||
| # Test Elements Created (COMPLETED) | ||||
|  | ||||
| ## Created comprehensive test elements: | ||||
| 1. **test-noprops** - Element with no @property decorators | ||||
| 2. **test-complextypes** - Element with arrays, objects, dates, and complex nested data | ||||
| 3. **test-withwrapper** - Element that uses dees-demowrapper in its demo | ||||
| 4. **test-edgecases** - Element with edge cases (null, undefined, NaN, Infinity, circular refs) | ||||
| 5. **test-nested** - Element with deeply nested structure to test recursive search | ||||
|  | ||||
| These test various scenarios: | ||||
| - Properties panel handling of elements without properties | ||||
| - Complex data type display and editing | ||||
| - Element detection inside dees-demowrapper | ||||
| - Error handling for problematic values | ||||
| - Deep nesting and shadow DOM traversal | ||||
| @@ -1 +1,6 @@ | ||||
| export * from './test-demoelement.js'; | ||||
| export * from './test-noprops.js'; | ||||
| export * from './test-complextypes.js'; | ||||
| export * from './test-withwrapper.js'; | ||||
| export * from './test-edgecases.js'; | ||||
| export * from './test-nested.js'; | ||||
|   | ||||
							
								
								
									
										137
									
								
								test/elements/test-complextypes.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								test/elements/test-complextypes.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,137 @@ | ||||
| import { | ||||
|   DeesElement, | ||||
|   customElement, | ||||
|   type TemplateResult, | ||||
|   html, | ||||
|   property, | ||||
|   css, | ||||
| } from '@design.estate/dees-element'; | ||||
|  | ||||
| interface IComplexData { | ||||
|   name: string; | ||||
|   age: number; | ||||
|   tags: string[]; | ||||
|   metadata: { | ||||
|     created: Date; | ||||
|     modified: Date; | ||||
|     author: string; | ||||
|   }; | ||||
| } | ||||
|  | ||||
| @customElement('test-complextypes') | ||||
| export class TestComplexTypes extends DeesElement { | ||||
|   public static demo = () => html` | ||||
|     <test-complextypes  | ||||
|       .complexData=${{ | ||||
|         name: 'Test User', | ||||
|         age: 25, | ||||
|         tags: ['developer', 'designer'], | ||||
|         metadata: { | ||||
|           created: new Date(), | ||||
|           modified: new Date(), | ||||
|           author: 'System' | ||||
|         } | ||||
|       }} | ||||
|     ></test-complextypes> | ||||
|   `; | ||||
|  | ||||
|   @property({ type: Array }) | ||||
|   public stringArray: string[] = ['apple', 'banana', 'cherry']; | ||||
|  | ||||
|   @property({ type: Array }) | ||||
|   public numberArray: number[] = [1, 2, 3, 4, 5]; | ||||
|  | ||||
|   @property({ attribute: false }) | ||||
|   public complexData: IComplexData = { | ||||
|     name: 'Default Name', | ||||
|     age: 0, | ||||
|     tags: [], | ||||
|     metadata: { | ||||
|       created: new Date(), | ||||
|       modified: new Date(), | ||||
|       author: 'Unknown' | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   @property({ type: Object }) | ||||
|   public simpleObject = { | ||||
|     key1: 'value1', | ||||
|     key2: 'value2', | ||||
|     key3: 123 | ||||
|   }; | ||||
|  | ||||
|   @property({ attribute: false }) | ||||
|   public functionProperty = () => { | ||||
|     console.log('This is a function property'); | ||||
|   }; | ||||
|  | ||||
|   @property({ type: Date }) | ||||
|   public dateProperty = new Date(); | ||||
|  | ||||
|   public static styles = [ | ||||
|     css` | ||||
|       :host { | ||||
|         display: block; | ||||
|         padding: 20px; | ||||
|         background: #f5f5f5; | ||||
|         border: 2px solid #ddd; | ||||
|         border-radius: 8px; | ||||
|         font-family: monospace; | ||||
|       } | ||||
|       .section { | ||||
|         margin: 10px 0; | ||||
|         padding: 10px; | ||||
|         background: white; | ||||
|         border-radius: 4px; | ||||
|       } | ||||
|       .label { | ||||
|         font-weight: bold; | ||||
|         color: #333; | ||||
|       } | ||||
|       .value { | ||||
|         color: #666; | ||||
|         margin-left: 10px; | ||||
|       } | ||||
|       pre { | ||||
|         background: #f0f0f0; | ||||
|         padding: 8px; | ||||
|         border-radius: 4px; | ||||
|         overflow-x: auto; | ||||
|       } | ||||
|     ` | ||||
|   ]; | ||||
|  | ||||
|   public render() { | ||||
|     return html` | ||||
|       <div class="section"> | ||||
|         <span class="label">String Array:</span> | ||||
|         <span class="value">${this.stringArray.join(', ')}</span> | ||||
|       </div> | ||||
|        | ||||
|       <div class="section"> | ||||
|         <span class="label">Number Array:</span> | ||||
|         <span class="value">${this.numberArray.join(', ')}</span> | ||||
|       </div> | ||||
|        | ||||
|       <div class="section"> | ||||
|         <span class="label">Complex Data:</span> | ||||
|         <pre>${JSON.stringify(this.complexData, null, 2)}</pre> | ||||
|       </div> | ||||
|        | ||||
|       <div class="section"> | ||||
|         <span class="label">Simple Object:</span> | ||||
|         <pre>${JSON.stringify(this.simpleObject, null, 2)}</pre> | ||||
|       </div> | ||||
|        | ||||
|       <div class="section"> | ||||
|         <span class="label">Date Property:</span> | ||||
|         <span class="value">${this.dateProperty.toLocaleString()}</span> | ||||
|       </div> | ||||
|        | ||||
|       <div class="section"> | ||||
|         <span class="label">Function Property:</span> | ||||
|         <span class="value">${typeof this.functionProperty}</span> | ||||
|       </div> | ||||
|     `; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										195
									
								
								test/elements/test-edgecases.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										195
									
								
								test/elements/test-edgecases.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,195 @@ | ||||
| import { | ||||
|   DeesElement, | ||||
|   customElement, | ||||
|   type TemplateResult, | ||||
|   html, | ||||
|   property, | ||||
|   css, | ||||
| } from '@design.estate/dees-element'; | ||||
|  | ||||
| @customElement('test-edgecases') | ||||
| export class TestEdgeCases extends DeesElement { | ||||
|   public static demo = () => html`<test-edgecases></test-edgecases>`; | ||||
|  | ||||
|   // Property with null value | ||||
|   @property({ type: String }) | ||||
|   public nullableString: string | null = null; | ||||
|  | ||||
|   // Property with undefined value | ||||
|   @property({ type: Number }) | ||||
|   public undefinedNumber: number | undefined = undefined; | ||||
|  | ||||
|   // Very long string | ||||
|   @property({ type: String }) | ||||
|   public longString: string = 'Lorem ipsum '.repeat(50); | ||||
|  | ||||
|   // Property with special characters | ||||
|   @property({ type: String }) | ||||
|   public specialChars: string = '!@#$%^&*()_+-=[]{}|;\':",./<>?`~'; | ||||
|  | ||||
|   // Property that could cause rendering issues | ||||
|   @property({ type: String }) | ||||
|   public htmlString: string = '<script>alert("test")</script><b>Bold text</b>'; | ||||
|  | ||||
|   // Numeric edge cases | ||||
|   @property({ type: Number }) | ||||
|   public infinityNumber: number = Infinity; | ||||
|  | ||||
|   @property({ type: Number }) | ||||
|   public nanNumber: number = NaN; | ||||
|  | ||||
|   @property({ type: Number }) | ||||
|   public veryLargeNumber: number = Number.MAX_SAFE_INTEGER; | ||||
|  | ||||
|   @property({ type: Number }) | ||||
|   public verySmallNumber: number = Number.MIN_SAFE_INTEGER; | ||||
|  | ||||
|   @property({ type: Number }) | ||||
|   public floatNumber: number = 3.14159265359; | ||||
|  | ||||
|   // Boolean-like values | ||||
|   @property({ type: String }) | ||||
|   public booleanString: string = 'false'; | ||||
|  | ||||
|   @property({ type: Number }) | ||||
|   public booleanNumber: number = 0; | ||||
|  | ||||
|   // Empty values | ||||
|   @property({ type: String }) | ||||
|   public emptyString: string = ''; | ||||
|  | ||||
|   @property({ type: Array }) | ||||
|   public emptyArray: any[] = []; | ||||
|  | ||||
|   @property({ type: Object }) | ||||
|   public emptyObject: {} = {}; | ||||
|  | ||||
|   // Circular reference (should not break properties panel) | ||||
|   @property({ attribute: false }) | ||||
|   public circularRef: any = (() => { | ||||
|     const obj: any = { name: 'circular' }; | ||||
|     obj.self = obj; | ||||
|     return obj; | ||||
|   })(); | ||||
|  | ||||
|   public static styles = [ | ||||
|     css` | ||||
|       :host { | ||||
|         display: block; | ||||
|         padding: 20px; | ||||
|         background: #fff3e0; | ||||
|         border: 2px solid #ff9800; | ||||
|         border-radius: 8px; | ||||
|         font-family: monospace; | ||||
|         font-size: 12px; | ||||
|       } | ||||
|       .warning { | ||||
|         background: #ffe0b2; | ||||
|         padding: 10px; | ||||
|         border-radius: 4px; | ||||
|         margin-bottom: 10px; | ||||
|         color: #e65100; | ||||
|       } | ||||
|       .property { | ||||
|         margin: 5px 0; | ||||
|         padding: 5px; | ||||
|         background: white; | ||||
|         border-radius: 2px; | ||||
|         word-break: break-all; | ||||
|       } | ||||
|       .label { | ||||
|         font-weight: bold; | ||||
|         color: #f57c00; | ||||
|       } | ||||
|       .value { | ||||
|         color: #666; | ||||
|       } | ||||
|       .special { | ||||
|         background: #ffccbc; | ||||
|         padding: 2px 4px; | ||||
|         border-radius: 2px; | ||||
|       } | ||||
|     ` | ||||
|   ]; | ||||
|  | ||||
|   private formatValue(value: any): string { | ||||
|     if (value === null) return 'null'; | ||||
|     if (value === undefined) return 'undefined'; | ||||
|     if (value === Infinity) return 'Infinity'; | ||||
|     if (Number.isNaN(value)) return 'NaN'; | ||||
|     if (typeof value === 'string' && value.length > 50) { | ||||
|       return value.substring(0, 50) + '...'; | ||||
|     } | ||||
|     if (typeof value === 'object') { | ||||
|       try { | ||||
|         return JSON.stringify(value); | ||||
|       } catch (e) { | ||||
|         return '[Circular Reference]'; | ||||
|       } | ||||
|     } | ||||
|     return String(value); | ||||
|   } | ||||
|  | ||||
|   public render() { | ||||
|     return html` | ||||
|       <div class="warning"> | ||||
|         ⚠️ This element tests edge cases and problematic values | ||||
|       </div> | ||||
|        | ||||
|       <div class="property"> | ||||
|         <span class="label">Nullable String:</span> | ||||
|         <span class="value special">${this.formatValue(this.nullableString)}</span> | ||||
|       </div> | ||||
|        | ||||
|       <div class="property"> | ||||
|         <span class="label">Undefined Number:</span> | ||||
|         <span class="value special">${this.formatValue(this.undefinedNumber)}</span> | ||||
|       </div> | ||||
|        | ||||
|       <div class="property"> | ||||
|         <span class="label">Long String:</span> | ||||
|         <span class="value">${this.formatValue(this.longString)}</span> | ||||
|       </div> | ||||
|        | ||||
|       <div class="property"> | ||||
|         <span class="label">Special Characters:</span> | ||||
|         <span class="value">${this.specialChars}</span> | ||||
|       </div> | ||||
|        | ||||
|       <div class="property"> | ||||
|         <span class="label">HTML String (escaped):</span> | ||||
|         <span class="value">${this.htmlString}</span> | ||||
|       </div> | ||||
|        | ||||
|       <div class="property"> | ||||
|         <span class="label">Infinity:</span> | ||||
|         <span class="value special">${this.formatValue(this.infinityNumber)}</span> | ||||
|       </div> | ||||
|        | ||||
|       <div class="property"> | ||||
|         <span class="label">NaN:</span> | ||||
|         <span class="value special">${this.formatValue(this.nanNumber)}</span> | ||||
|       </div> | ||||
|        | ||||
|       <div class="property"> | ||||
|         <span class="label">Very Large Number:</span> | ||||
|         <span class="value">${this.veryLargeNumber}</span> | ||||
|       </div> | ||||
|        | ||||
|       <div class="property"> | ||||
|         <span class="label">Float Number:</span> | ||||
|         <span class="value">${this.floatNumber}</span> | ||||
|       </div> | ||||
|        | ||||
|       <div class="property"> | ||||
|         <span class="label">Empty String:</span> | ||||
|         <span class="value special">[empty]</span> | ||||
|       </div> | ||||
|        | ||||
|       <div class="property"> | ||||
|         <span class="label">Circular Reference:</span> | ||||
|         <span class="value special">${this.formatValue(this.circularRef)}</span> | ||||
|       </div> | ||||
|     `; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										127
									
								
								test/elements/test-nested.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								test/elements/test-nested.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,127 @@ | ||||
| import { | ||||
|   DeesElement, | ||||
|   customElement, | ||||
|   type TemplateResult, | ||||
|   html, | ||||
|   property, | ||||
|   css, | ||||
| } from '@design.estate/dees-element'; | ||||
|  | ||||
| // Helper component for nesting | ||||
| @customElement('test-nested-wrapper') | ||||
| class TestNestedWrapper extends DeesElement { | ||||
|   public render() { | ||||
|     return html` | ||||
|       <div style="border: 1px dashed #ccc; padding: 10px; margin: 5px;"> | ||||
|         <slot></slot> | ||||
|       </div> | ||||
|     `; | ||||
|   } | ||||
| } | ||||
|  | ||||
| // The actual test element deeply nested | ||||
| @customElement('test-nested-target') | ||||
| class TestNestedTarget extends DeesElement { | ||||
|   @property({ type: String }) | ||||
|   public message: string = 'I am deeply nested!'; | ||||
|  | ||||
|   @property({ type: Number }) | ||||
|   public depth: number = 0; | ||||
|  | ||||
|   @property({ type: Boolean }) | ||||
|   public found: boolean = false; | ||||
|  | ||||
|   public static styles = [ | ||||
|     css` | ||||
|       :host { | ||||
|         display: block; | ||||
|         padding: 15px; | ||||
|         background: #e1f5fe; | ||||
|         border: 2px solid #0288d1; | ||||
|         border-radius: 4px; | ||||
|         margin: 5px; | ||||
|       } | ||||
|       .info { | ||||
|         font-family: monospace; | ||||
|         color: #01579b; | ||||
|       } | ||||
|     ` | ||||
|   ]; | ||||
|  | ||||
|   public render() { | ||||
|     return html` | ||||
|       <div class="info"> | ||||
|         <strong>Nested Target Element</strong><br> | ||||
|         Message: ${this.message}<br> | ||||
|         Depth: ${this.depth}<br> | ||||
|         Found by properties panel: ${this.found ? '✅' : '❌'} | ||||
|       </div> | ||||
|     `; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @customElement('test-nested') | ||||
| export class TestNested extends DeesElement { | ||||
|   public static demo = () => html` | ||||
|     <test-nested></test-nested> | ||||
|   `; | ||||
|  | ||||
|   @property({ type: String }) | ||||
|   public testId: string = 'nested-test'; | ||||
|  | ||||
|   public static styles = [ | ||||
|     css` | ||||
|       :host { | ||||
|         display: block; | ||||
|         padding: 20px; | ||||
|         background: #f5f5f5; | ||||
|         border: 2px solid #999; | ||||
|         border-radius: 8px; | ||||
|       } | ||||
|       .explanation { | ||||
|         background: #fff; | ||||
|         padding: 10px; | ||||
|         border-radius: 4px; | ||||
|         margin-bottom: 10px; | ||||
|       } | ||||
|       .structure { | ||||
|         background: #f0f0f0; | ||||
|         padding: 10px; | ||||
|         border-radius: 4px; | ||||
|       } | ||||
|     ` | ||||
|   ]; | ||||
|  | ||||
|   public render() { | ||||
|     return html` | ||||
|       <div class="explanation"> | ||||
|         <h3>Nested Structure Test</h3> | ||||
|         <p>The actual element with properties is nested deep inside multiple layers:</p> | ||||
|       </div> | ||||
|        | ||||
|       <div class="structure"> | ||||
|         <test-nested-wrapper> | ||||
|           <div style="padding: 10px; background: #ffe;"> | ||||
|             <test-nested-wrapper> | ||||
|               <div style="padding: 10px; background: #efe;"> | ||||
|                 <test-nested-wrapper> | ||||
|                   <div style="padding: 10px; background: #eef;"> | ||||
|                     <!-- The target element is here, 3 levels deep --> | ||||
|                     <test-nested-target | ||||
|                       .message=${'Found me at depth 3!'} | ||||
|                       .depth=${3} | ||||
|                     ></test-nested-target> | ||||
|                   </div> | ||||
|                 </test-nested-wrapper> | ||||
|               </div> | ||||
|             </test-nested-wrapper> | ||||
|           </div> | ||||
|         </test-nested-wrapper> | ||||
|       </div> | ||||
|        | ||||
|       <div style="margin-top: 10px; font-style: italic; color: #666;"> | ||||
|         Properties panel should find the test-nested-target element despite the deep nesting. | ||||
|       </div> | ||||
|     `; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										37
									
								
								test/elements/test-noprops.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								test/elements/test-noprops.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| import { | ||||
|   DeesElement, | ||||
|   customElement, | ||||
|   type TemplateResult, | ||||
|   html, | ||||
|   css, | ||||
| } from '@design.estate/dees-element'; | ||||
|  | ||||
| @customElement('test-noprops') | ||||
| export class TestNoProps extends DeesElement { | ||||
|   public static demo = () => html`<test-noprops></test-noprops>`; | ||||
|  | ||||
|   public static styles = [ | ||||
|     css` | ||||
|       :host { | ||||
|         display: block; | ||||
|         padding: 20px; | ||||
|         background: #f0f0f0; | ||||
|         border: 2px solid #ccc; | ||||
|         border-radius: 8px; | ||||
|       } | ||||
|       .message { | ||||
|         font-family: monospace; | ||||
|         color: #666; | ||||
|       } | ||||
|     ` | ||||
|   ]; | ||||
|  | ||||
|   public render() { | ||||
|     return html` | ||||
|       <div class="message"> | ||||
|         This element has no @property decorators. | ||||
|         Properties panel should handle this gracefully. | ||||
|       </div> | ||||
|     `; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										111
									
								
								test/elements/test-withwrapper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								test/elements/test-withwrapper.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | ||||
| import { | ||||
|   DeesElement, | ||||
|   customElement, | ||||
|   type TemplateResult, | ||||
|   html, | ||||
|   property, | ||||
|   css, | ||||
| } from '@design.estate/dees-element'; | ||||
|  | ||||
| // Import from local demotools | ||||
| import '../../ts_demotools/demotools.js'; | ||||
|  | ||||
| @customElement('test-withwrapper') | ||||
| export class TestWithWrapper extends DeesElement { | ||||
|   public static demo = () => html` | ||||
|     <dees-demowrapper .runAfterRender=${async (wrapper) => { | ||||
|       console.log('DemoWrapper: Found wrapper element', wrapper); | ||||
|        | ||||
|       const testElement = wrapper.querySelector('test-withwrapper'); | ||||
|       if (testElement) { | ||||
|         console.log('DemoWrapper: Found test-withwrapper element'); | ||||
|         testElement.dynamicValue = 'Set by demo wrapper!'; | ||||
|         testElement.counter = 100; | ||||
|          | ||||
|         // Test querySelector functionality | ||||
|         const innerDiv = wrapper.querySelector('.inner-content'); | ||||
|         console.log('DemoWrapper: Found inner div:', innerDiv); | ||||
|          | ||||
|         // Test querySelectorAll | ||||
|         const allButtons = wrapper.querySelectorAll('button'); | ||||
|         console.log(`DemoWrapper: Found ${allButtons.length} buttons`); | ||||
|       } | ||||
|     }}> | ||||
|       <test-withwrapper></test-withwrapper> | ||||
|       <div style="margin-top: 10px; padding: 10px; background: #e0e0e0;"> | ||||
|         This div is also inside the wrapper | ||||
|       </div> | ||||
|     </dees-demowrapper> | ||||
|   `; | ||||
|  | ||||
|   @property({ type: String }) | ||||
|   public dynamicValue: string = 'Initial value'; | ||||
|  | ||||
|   @property({ type: Number }) | ||||
|   public counter: number = 0; | ||||
|  | ||||
|   @property({ type: Boolean }) | ||||
|   public isActive: boolean = false; | ||||
|  | ||||
|   public static styles = [ | ||||
|     css` | ||||
|       :host { | ||||
|         display: block; | ||||
|         padding: 20px; | ||||
|         background: #e8f5e9; | ||||
|         border: 2px solid #4caf50; | ||||
|         border-radius: 8px; | ||||
|       } | ||||
|       .wrapper-info { | ||||
|         background: #c8e6c9; | ||||
|         padding: 10px; | ||||
|         border-radius: 4px; | ||||
|         margin-bottom: 10px; | ||||
|       } | ||||
|       .inner-content { | ||||
|         background: white; | ||||
|         padding: 15px; | ||||
|         border-radius: 4px; | ||||
|         margin: 10px 0; | ||||
|       } | ||||
|       button { | ||||
|         background: #4caf50; | ||||
|         color: white; | ||||
|         border: none; | ||||
|         padding: 8px 16px; | ||||
|         border-radius: 4px; | ||||
|         cursor: pointer; | ||||
|         margin-right: 10px; | ||||
|       } | ||||
|       button:hover { | ||||
|         background: #45a049; | ||||
|       } | ||||
|       .status { | ||||
|         margin-top: 10px; | ||||
|         font-family: monospace; | ||||
|       } | ||||
|     ` | ||||
|   ]; | ||||
|  | ||||
|   public render() { | ||||
|     return html` | ||||
|       <div class="wrapper-info"> | ||||
|         This element is wrapped with dees-demowrapper in its demo | ||||
|       </div> | ||||
|        | ||||
|       <div class="inner-content"> | ||||
|         <h3>Dynamic Value: ${this.dynamicValue}</h3> | ||||
|         <p>Counter: ${this.counter}</p> | ||||
|         <p>Active: ${this.isActive ? 'Yes' : 'No'}</p> | ||||
|          | ||||
|         <button @click=${() => this.counter++}>Increment</button> | ||||
|         <button @click=${() => this.isActive = !this.isActive}>Toggle Active</button> | ||||
|         <button @click=${() => this.dynamicValue = 'Clicked!'}>Change Value</button> | ||||
|       </div> | ||||
|        | ||||
|       <div class="status"> | ||||
|         Properties panel should detect this element inside the wrapper | ||||
|       </div> | ||||
|     `; | ||||
|   } | ||||
| } | ||||
| @@ -229,23 +229,28 @@ export class WccProperties extends DeesElement { | ||||
|   private async findElementRecursively(container: Element, elementClass: any, maxDepth: number = 5): Promise<HTMLElement | null> { | ||||
|     if (maxDepth <= 0) return null; | ||||
|      | ||||
|     // Check direct children | ||||
|     for (const child of Array.from(container.children)) { | ||||
|       if (child instanceof elementClass) { | ||||
|         return child as HTMLElement; | ||||
|       } | ||||
|     } | ||||
|      | ||||
|     // Check shadow roots of children | ||||
|     for (const child of Array.from(container.children)) { | ||||
|       if (child.shadowRoot) { | ||||
|         const found = await this.findElementRecursively(child.shadowRoot as any, elementClass, maxDepth - 1); | ||||
|         if (found) return found; | ||||
|     try { | ||||
|       // Check direct children | ||||
|       for (const child of Array.from(container.children)) { | ||||
|         if (child instanceof elementClass) { | ||||
|           return child as HTMLElement; | ||||
|         } | ||||
|       } | ||||
|        | ||||
|       // Also check nested children | ||||
|       const found = await this.findElementRecursively(child, elementClass, maxDepth - 1); | ||||
|       if (found) return found; | ||||
|       // Search in all children recursively | ||||
|       for (const child of Array.from(container.children)) { | ||||
|         // First, always check the light DOM children | ||||
|         const found = await this.findElementRecursively(child, elementClass, maxDepth - 1); | ||||
|         if (found) return found; | ||||
|          | ||||
|         // Also check shadow root if it exists | ||||
|         if (child.shadowRoot) { | ||||
|           const shadowFound = await this.findElementRecursively(child.shadowRoot as any, elementClass, maxDepth - 1); | ||||
|           if (shadowFound) return shadowFound; | ||||
|         } | ||||
|       } | ||||
|     } catch (error) { | ||||
|       console.error('Error in findElementRecursively:', error); | ||||
|     } | ||||
|      | ||||
|     return null; | ||||
| @@ -254,6 +259,9 @@ export class WccProperties extends DeesElement { | ||||
|   public async createProperties() { | ||||
|     console.log('creating properties for:'); | ||||
|     console.log(this.selectedItem); | ||||
|      | ||||
|     // Clear any previous warnings | ||||
|     this.warning = null; | ||||
|     const isEnumeration = (propertyArg): boolean => { | ||||
|       const keys = Object.keys(propertyArg.type); | ||||
|       const values = []; | ||||
| @@ -315,15 +323,20 @@ export class WccProperties extends DeesElement { | ||||
|       let retries = 0; | ||||
|       while (!firstFoundInstantiatedElement && retries < 5) { | ||||
|         await new Promise(resolve => setTimeout(resolve, 200)); | ||||
|         firstFoundInstantiatedElement = await this.findElementRecursively( | ||||
|           viewport, | ||||
|           this.selectedItem as any | ||||
|         ); | ||||
|         try { | ||||
|           firstFoundInstantiatedElement = await this.findElementRecursively( | ||||
|             viewport, | ||||
|             this.selectedItem as any | ||||
|           ); | ||||
|         } catch (error) { | ||||
|           console.error('Error during element search retry:', error); | ||||
|         } | ||||
|         retries++; | ||||
|       } | ||||
|        | ||||
|       if (!firstFoundInstantiatedElement) { | ||||
|         this.warning = `no first instantiated element found for >>${anonItem.name}<< after ${retries} retries`; | ||||
|         this.propertyContent = []; | ||||
|         return; | ||||
|       } | ||||
|       const classProperties: Map<string, any> = anonItem.elementProperties; | ||||
| @@ -337,9 +350,10 @@ export class WccProperties extends DeesElement { | ||||
|         if (key === 'goBright' || key === 'domtools') { | ||||
|           continue; | ||||
|         } | ||||
|         const property = classProperties.get(key); | ||||
|         const propertyTypeString = await determinePropertyType(property); | ||||
|         propertyArray.push( | ||||
|         try { | ||||
|           const property = classProperties.get(key); | ||||
|           const propertyTypeString = await determinePropertyType(property); | ||||
|           propertyArray.push( | ||||
|           html` | ||||
|             <div class="property"> | ||||
|               ${key} / ${propertyTypeString}<br /> | ||||
| @@ -392,6 +406,10 @@ export class WccProperties extends DeesElement { | ||||
|             </div> | ||||
|           ` | ||||
|         ); | ||||
|         } catch (error) { | ||||
|           console.error(`Error processing property ${key}:`, error); | ||||
|           // Continue with next property even if this one fails | ||||
|         } | ||||
|       } | ||||
|       this.propertyContent = propertyArray; | ||||
|     } else { | ||||
| @@ -413,7 +431,14 @@ export class WccProperties extends DeesElement { | ||||
|   } | ||||
|  | ||||
|   public async scheduleUpdate() { | ||||
|     await this.createProperties(); | ||||
|     try { | ||||
|       await this.createProperties(); | ||||
|     } catch (error) { | ||||
|       console.error('Error creating properties:', error); | ||||
|       // Clear property content on error to show clean state | ||||
|       this.propertyContent = []; | ||||
|     } | ||||
|     // Always call super.scheduleUpdate to ensure component updates | ||||
|     super.scheduleUpdate(); | ||||
|   } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user