Add Biome for lint and code quality check #2
							
								
								
									
										31
									
								
								biome.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								biome.json
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,31 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
 | 
				
			||||||
 | 
						"vcs": {
 | 
				
			||||||
 | 
							"enabled": true,
 | 
				
			||||||
 | 
							"clientKind": "git",
 | 
				
			||||||
 | 
							"useIgnoreFile": true
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"files": {
 | 
				
			||||||
 | 
							"ignoreUnknown": false,
 | 
				
			||||||
 | 
							"ignore": []
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"formatter": {
 | 
				
			||||||
 | 
							"enabled": true,
 | 
				
			||||||
 | 
							"indentStyle": "tab"
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"organizeImports": {
 | 
				
			||||||
 | 
							"enabled": true
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"linter": {
 | 
				
			||||||
 | 
							"enabled": true,
 | 
				
			||||||
 | 
							"rules": {
 | 
				
			||||||
 | 
								"recommended": true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"javascript": {
 | 
				
			||||||
 | 
							"formatter": {
 | 
				
			||||||
 | 
								"quoteStyle": "single",
 | 
				
			||||||
 | 
								"semicolons": "asNeeded"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										165
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										165
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| 
						 | 
					@ -9,6 +9,7 @@
 | 
				
			||||||
      "version": "0.0.1",
 | 
					      "version": "0.0.1",
 | 
				
			||||||
      "license": "MIT",
 | 
					      "license": "MIT",
 | 
				
			||||||
      "devDependencies": {
 | 
					      "devDependencies": {
 | 
				
			||||||
 | 
					        "@biomejs/biome": "1.9.4",
 | 
				
			||||||
        "@rollup/plugin-node-resolve": "^16.0.1",
 | 
					        "@rollup/plugin-node-resolve": "^16.0.1",
 | 
				
			||||||
        "@rollup/plugin-terser": "^0.4.4",
 | 
					        "@rollup/plugin-terser": "^0.4.4",
 | 
				
			||||||
        "@rollup/plugin-typescript": "^12.1.2",
 | 
					        "@rollup/plugin-typescript": "^12.1.2",
 | 
				
			||||||
| 
						 | 
					@ -49,6 +50,170 @@
 | 
				
			||||||
        "node": ">=6.9.0"
 | 
					        "node": ">=6.9.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@biomejs/biome": {
 | 
				
			||||||
 | 
					      "version": "1.9.4",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "hasInstallScript": true,
 | 
				
			||||||
 | 
					      "license": "MIT OR Apache-2.0",
 | 
				
			||||||
 | 
					      "bin": {
 | 
				
			||||||
 | 
					        "biome": "bin/biome"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=14.21.3"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "funding": {
 | 
				
			||||||
 | 
					        "type": "opencollective",
 | 
				
			||||||
 | 
					        "url": "https://opencollective.com/biome"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "optionalDependencies": {
 | 
				
			||||||
 | 
					        "@biomejs/cli-darwin-arm64": "1.9.4",
 | 
				
			||||||
 | 
					        "@biomejs/cli-darwin-x64": "1.9.4",
 | 
				
			||||||
 | 
					        "@biomejs/cli-linux-arm64": "1.9.4",
 | 
				
			||||||
 | 
					        "@biomejs/cli-linux-arm64-musl": "1.9.4",
 | 
				
			||||||
 | 
					        "@biomejs/cli-linux-x64": "1.9.4",
 | 
				
			||||||
 | 
					        "@biomejs/cli-linux-x64-musl": "1.9.4",
 | 
				
			||||||
 | 
					        "@biomejs/cli-win32-arm64": "1.9.4",
 | 
				
			||||||
 | 
					        "@biomejs/cli-win32-x64": "1.9.4"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@biomejs/cli-darwin-arm64": {
 | 
				
			||||||
 | 
					      "version": "1.9.4",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==",
 | 
				
			||||||
 | 
					      "cpu": [
 | 
				
			||||||
 | 
					        "arm64"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "license": "MIT OR Apache-2.0",
 | 
				
			||||||
 | 
					      "optional": true,
 | 
				
			||||||
 | 
					      "os": [
 | 
				
			||||||
 | 
					        "darwin"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=14.21.3"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@biomejs/cli-darwin-x64": {
 | 
				
			||||||
 | 
					      "version": "1.9.4",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==",
 | 
				
			||||||
 | 
					      "cpu": [
 | 
				
			||||||
 | 
					        "x64"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "license": "MIT OR Apache-2.0",
 | 
				
			||||||
 | 
					      "optional": true,
 | 
				
			||||||
 | 
					      "os": [
 | 
				
			||||||
 | 
					        "darwin"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=14.21.3"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@biomejs/cli-linux-arm64": {
 | 
				
			||||||
 | 
					      "version": "1.9.4",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==",
 | 
				
			||||||
 | 
					      "cpu": [
 | 
				
			||||||
 | 
					        "arm64"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "license": "MIT OR Apache-2.0",
 | 
				
			||||||
 | 
					      "optional": true,
 | 
				
			||||||
 | 
					      "os": [
 | 
				
			||||||
 | 
					        "linux"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=14.21.3"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@biomejs/cli-linux-arm64-musl": {
 | 
				
			||||||
 | 
					      "version": "1.9.4",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==",
 | 
				
			||||||
 | 
					      "cpu": [
 | 
				
			||||||
 | 
					        "arm64"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "license": "MIT OR Apache-2.0",
 | 
				
			||||||
 | 
					      "optional": true,
 | 
				
			||||||
 | 
					      "os": [
 | 
				
			||||||
 | 
					        "linux"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=14.21.3"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@biomejs/cli-linux-x64": {
 | 
				
			||||||
 | 
					      "version": "1.9.4",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==",
 | 
				
			||||||
 | 
					      "cpu": [
 | 
				
			||||||
 | 
					        "x64"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "license": "MIT OR Apache-2.0",
 | 
				
			||||||
 | 
					      "optional": true,
 | 
				
			||||||
 | 
					      "os": [
 | 
				
			||||||
 | 
					        "linux"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=14.21.3"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@biomejs/cli-linux-x64-musl": {
 | 
				
			||||||
 | 
					      "version": "1.9.4",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==",
 | 
				
			||||||
 | 
					      "cpu": [
 | 
				
			||||||
 | 
					        "x64"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "license": "MIT OR Apache-2.0",
 | 
				
			||||||
 | 
					      "optional": true,
 | 
				
			||||||
 | 
					      "os": [
 | 
				
			||||||
 | 
					        "linux"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=14.21.3"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@biomejs/cli-win32-arm64": {
 | 
				
			||||||
 | 
					      "version": "1.9.4",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==",
 | 
				
			||||||
 | 
					      "cpu": [
 | 
				
			||||||
 | 
					        "arm64"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "license": "MIT OR Apache-2.0",
 | 
				
			||||||
 | 
					      "optional": true,
 | 
				
			||||||
 | 
					      "os": [
 | 
				
			||||||
 | 
					        "win32"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=14.21.3"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@biomejs/cli-win32-x64": {
 | 
				
			||||||
 | 
					      "version": "1.9.4",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==",
 | 
				
			||||||
 | 
					      "cpu": [
 | 
				
			||||||
 | 
					        "x64"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "license": "MIT OR Apache-2.0",
 | 
				
			||||||
 | 
					      "optional": true,
 | 
				
			||||||
 | 
					      "os": [
 | 
				
			||||||
 | 
					        "win32"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=14.21.3"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/@isaacs/cliui": {
 | 
					    "node_modules/@isaacs/cliui": {
 | 
				
			||||||
      "version": "8.0.2",
 | 
					      "version": "8.0.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,6 +17,7 @@
 | 
				
			||||||
  "license": "MIT",
 | 
					  "license": "MIT",
 | 
				
			||||||
  "description": "",
 | 
					  "description": "",
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
 | 
					    "@biomejs/biome": "1.9.4",
 | 
				
			||||||
    "@rollup/plugin-node-resolve": "^16.0.1",
 | 
					    "@rollup/plugin-node-resolve": "^16.0.1",
 | 
				
			||||||
    "@rollup/plugin-terser": "^0.4.4",
 | 
					    "@rollup/plugin-terser": "^0.4.4",
 | 
				
			||||||
    "@rollup/plugin-typescript": "^12.1.2",
 | 
					    "@rollup/plugin-typescript": "^12.1.2",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										398
									
								
								src/main.ts
									
									
									
									
									
								
							
							
						
						
									
										398
									
								
								src/main.ts
									
									
									
									
									
								
							| 
						 | 
					@ -9,14 +9,28 @@ interface ComponentOptions {
 | 
				
			||||||
	style?: string
 | 
						style?: string
 | 
				
			||||||
	onMount?: (this: CustomElement) => void
 | 
						onMount?: (this: CustomElement) => void
 | 
				
			||||||
	onUnmount?: () => void
 | 
						onUnmount?: () => void
 | 
				
			||||||
	onAttributeChanged?: (attrName: string, oldValue: string, newValue: string) => void
 | 
						onAttributeChanged?: (
 | 
				
			||||||
 | 
							attrName: string,
 | 
				
			||||||
 | 
							oldValue: string,
 | 
				
			||||||
 | 
							newValue: string,
 | 
				
			||||||
 | 
						) => void
 | 
				
			||||||
	states?: Record<string, any>
 | 
						states?: Record<string, any>
 | 
				
			||||||
	statesListeners?: { [key: string]: (value: any) => void }
 | 
						statesListeners?: { [key: string]: (value: any) => void }
 | 
				
			||||||
	funcs?: { [key: string]: (...args: any[]) => void }
 | 
						funcs?: { [key: string]: (...args: any[]) => void }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default (options: ComponentOptions) => {
 | 
					export default (options: ComponentOptions) => {
 | 
				
			||||||
	const { tag, template, style, onMount, onUnmount, onAttributeChanged, states, statesListeners, funcs } = options
 | 
						const {
 | 
				
			||||||
 | 
							tag,
 | 
				
			||||||
 | 
							template,
 | 
				
			||||||
 | 
							style,
 | 
				
			||||||
 | 
							onMount,
 | 
				
			||||||
 | 
							onUnmount,
 | 
				
			||||||
 | 
							onAttributeChanged,
 | 
				
			||||||
 | 
							states,
 | 
				
			||||||
 | 
							statesListeners,
 | 
				
			||||||
 | 
							funcs,
 | 
				
			||||||
 | 
						} = options
 | 
				
			||||||
	const componentRegistry = new Map()
 | 
						const componentRegistry = new Map()
 | 
				
			||||||
	componentRegistry.set(tag, options)
 | 
						componentRegistry.set(tag, options)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,19 +39,33 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
		private _stateToElementsMap: Record<string, Set<HTMLElement>> = {}
 | 
							private _stateToElementsMap: Record<string, Set<HTMLElement>> = {}
 | 
				
			||||||
		private _currentRenderingElement: HTMLElement | null = null
 | 
							private _currentRenderingElement: HTMLElement | null = null
 | 
				
			||||||
		private _statesListeners: Record<string, Function> = {}
 | 
							private _statesListeners: Record<string, Function> = {}
 | 
				
			||||||
		private _textBindings: Array<{ node: Text, expr: string, originalContent: string }> = []
 | 
							private _textBindings: Array<{
 | 
				
			||||||
		private _attributeBindings: Array<{ element: Element, attrName: string, expr: string, template: string }> = []
 | 
								node: Text
 | 
				
			||||||
		private _conditionalElements: Map<Element, {
 | 
								expr: string
 | 
				
			||||||
			expr: string,
 | 
								originalContent: string
 | 
				
			||||||
			placeholder: Comment,
 | 
							}> = []
 | 
				
			||||||
 | 
							private _attributeBindings: Array<{
 | 
				
			||||||
 | 
								element: Element
 | 
				
			||||||
 | 
								attrName: string
 | 
				
			||||||
 | 
								expr: string
 | 
				
			||||||
 | 
								template: string
 | 
				
			||||||
 | 
							}> = []
 | 
				
			||||||
 | 
							private _conditionalElements: Map<
 | 
				
			||||||
 | 
								Element,
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									expr: string
 | 
				
			||||||
 | 
									placeholder: Comment
 | 
				
			||||||
				isPresent: boolean
 | 
									isPresent: boolean
 | 
				
			||||||
		}> = new Map()
 | 
								}
 | 
				
			||||||
 | 
							> = new Map()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		constructor() {
 | 
							constructor() {
 | 
				
			||||||
			super()
 | 
								super()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// copy state from options
 | 
								// copy state from options
 | 
				
			||||||
			this._states = new Proxy({ ...(states || {}) }, {
 | 
								this._states = new Proxy(
 | 
				
			||||||
 | 
									{ ...(states || {}) },
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
					set: (target: Record<string, any>, keyPath: string, value: any) => {
 | 
										set: (target: Record<string, any>, keyPath: string, value: any) => {
 | 
				
			||||||
						const valueRoute = keyPath.split('.')
 | 
											const valueRoute = keyPath.split('.')
 | 
				
			||||||
						let currentTarget = target
 | 
											let currentTarget = target
 | 
				
			||||||
| 
						 | 
					@ -75,7 +103,9 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
						if (this._currentRenderingElement) {
 | 
											if (this._currentRenderingElement) {
 | 
				
			||||||
							if (!this._stateToElementsMap[keyPath])
 | 
												if (!this._stateToElementsMap[keyPath])
 | 
				
			||||||
								this._stateToElementsMap[keyPath] = new Set()
 | 
													this._stateToElementsMap[keyPath] = new Set()
 | 
				
			||||||
						this._stateToElementsMap[keyPath].add(this._currentRenderingElement)
 | 
												this._stateToElementsMap[keyPath].add(
 | 
				
			||||||
 | 
													this._currentRenderingElement,
 | 
				
			||||||
 | 
												)
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						const valueRoute = keyPath.split('.')
 | 
											const valueRoute = keyPath.split('.')
 | 
				
			||||||
| 
						 | 
					@ -92,8 +122,9 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
							}
 | 
												}
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
						return undefined
 | 
											return undefined
 | 
				
			||||||
				}
 | 
										},
 | 
				
			||||||
			})
 | 
									},
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// initialize dom tree and append to shadow root
 | 
								// initialize dom tree and append to shadow root
 | 
				
			||||||
			this._initialize()
 | 
								this._initialize()
 | 
				
			||||||
| 
						 | 
					@ -132,7 +163,7 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
			if (this._stateToElementsMap[keyPath]) {
 | 
								if (this._stateToElementsMap[keyPath]) {
 | 
				
			||||||
				const updateQueue = new Set<HTMLElement>()
 | 
									const updateQueue = new Set<HTMLElement>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				this._stateToElementsMap[keyPath].forEach(element => {
 | 
									this._stateToElementsMap[keyPath].forEach((element) => {
 | 
				
			||||||
					updateQueue.add(element)
 | 
										updateQueue.add(element)
 | 
				
			||||||
				})
 | 
									})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -141,17 +172,27 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Update text bindings that depend on this state
 | 
								// Update text bindings that depend on this state
 | 
				
			||||||
			if (this._textBindings) {
 | 
								if (this._textBindings) {
 | 
				
			||||||
				this._textBindings.forEach(binding => {
 | 
									this._textBindings.forEach((binding) => {
 | 
				
			||||||
					if (binding.expr === keyPath || binding.expr.startsWith(keyPath + '.')) {
 | 
										if (
 | 
				
			||||||
						this._updateTextNode(binding.node, binding.expr, binding.originalContent)
 | 
											binding.expr === keyPath ||
 | 
				
			||||||
 | 
											binding.expr.startsWith(keyPath + '.')
 | 
				
			||||||
 | 
										) {
 | 
				
			||||||
 | 
											this._updateTextNode(
 | 
				
			||||||
 | 
												binding.node,
 | 
				
			||||||
 | 
												binding.expr,
 | 
				
			||||||
 | 
												binding.originalContent,
 | 
				
			||||||
 | 
											)
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				})
 | 
									})
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Update attribute bindings that depend on this state
 | 
								// Update attribute bindings that depend on this state
 | 
				
			||||||
			if (this._attributeBindings) {
 | 
								if (this._attributeBindings) {
 | 
				
			||||||
				this._attributeBindings.forEach(binding => {
 | 
									this._attributeBindings.forEach((binding) => {
 | 
				
			||||||
					if (binding.expr === keyPath || binding.expr.startsWith(keyPath + '.')) {
 | 
										if (
 | 
				
			||||||
 | 
											binding.expr === keyPath ||
 | 
				
			||||||
 | 
											binding.expr.startsWith(keyPath + '.')
 | 
				
			||||||
 | 
										) {
 | 
				
			||||||
						const value = this._getNestedState(binding.expr)
 | 
											const value = this._getNestedState(binding.expr)
 | 
				
			||||||
						if (value !== undefined) {
 | 
											if (value !== undefined) {
 | 
				
			||||||
							binding.element.setAttribute(binding.attrName, String(value))
 | 
												binding.element.setAttribute(binding.attrName, String(value))
 | 
				
			||||||
| 
						 | 
					@ -163,7 +204,7 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		private _scheduleUpdate(elements: Set<HTMLElement>) {
 | 
							private _scheduleUpdate(elements: Set<HTMLElement>) {
 | 
				
			||||||
			requestAnimationFrame(() => {
 | 
								requestAnimationFrame(() => {
 | 
				
			||||||
				elements.forEach(element => {
 | 
									elements.forEach((element) => {
 | 
				
			||||||
					this._updateElement(element)
 | 
										this._updateElement(element)
 | 
				
			||||||
				})
 | 
									})
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
| 
						 | 
					@ -203,16 +244,21 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
			const walker = document.createTreeWalker(
 | 
								const walker = document.createTreeWalker(
 | 
				
			||||||
				element,
 | 
									element,
 | 
				
			||||||
				NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT,
 | 
									NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT,
 | 
				
			||||||
				null
 | 
									null,
 | 
				
			||||||
			)
 | 
								)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Store nodes and expressions that need to be updated
 | 
								// Store nodes and expressions that need to be updated
 | 
				
			||||||
			const textBindings: Array<{ node: Text, expr: string, originalContent: string }> = []
 | 
								const textBindings: Array<{
 | 
				
			||||||
			const ifDirectivesToProcess: Array<{ element: Element, expr: string }> = []
 | 
									node: Text
 | 
				
			||||||
 | 
									expr: string
 | 
				
			||||||
 | 
									originalContent: string
 | 
				
			||||||
 | 
								}> = []
 | 
				
			||||||
 | 
								const ifDirectivesToProcess: Array<{ element: Element; expr: string }> =
 | 
				
			||||||
 | 
									[]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Traverse the DOM tree
 | 
								// Traverse the DOM tree
 | 
				
			||||||
			let currentNode: Node | null
 | 
								let currentNode: Node | null
 | 
				
			||||||
			while (currentNode = walker.nextNode()) {
 | 
								while ((currentNode = walker.nextNode())) {
 | 
				
			||||||
				// Handle text nodes
 | 
									// Handle text nodes
 | 
				
			||||||
				if (currentNode.nodeType === Node.TEXT_NODE) {
 | 
									if (currentNode.nodeType === Node.TEXT_NODE) {
 | 
				
			||||||
					const textContent = currentNode.textContent || ''
 | 
										const textContent = currentNode.textContent || ''
 | 
				
			||||||
| 
						 | 
					@ -226,7 +272,7 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
						// Record nodes and expressions that need to be updated
 | 
											// Record nodes and expressions that need to be updated
 | 
				
			||||||
						const matches = textContent.match(/\{\{\s*([^}]+)\s*\}\}/g)
 | 
											const matches = textContent.match(/\{\{\s*([^}]+)\s*\}\}/g)
 | 
				
			||||||
						if (matches) {
 | 
											if (matches) {
 | 
				
			||||||
							matches.forEach(match => {
 | 
												matches.forEach((match) => {
 | 
				
			||||||
								// Extract the expression content, removing {{ }} and spaces
 | 
													// Extract the expression content, removing {{ }} and spaces
 | 
				
			||||||
								const expr = match.replace(/\{\{\s*|\s*\}\}/g, '').trim()
 | 
													const expr = match.replace(/\{\{\s*|\s*\}\}/g, '').trim()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -240,7 +286,9 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
								if (!this._stateToElementsMap[expr])
 | 
													if (!this._stateToElementsMap[expr])
 | 
				
			||||||
									this._stateToElementsMap[expr] = new Set()
 | 
														this._stateToElementsMap[expr] = new Set()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
								this._stateToElementsMap[expr].add(textNode as unknown as HTMLElement)
 | 
													this._stateToElementsMap[expr].add(
 | 
				
			||||||
 | 
														textNode as unknown as HTMLElement,
 | 
				
			||||||
 | 
													)
 | 
				
			||||||
							})
 | 
												})
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
| 
						 | 
					@ -248,13 +296,12 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// Handle element nodes (can extend to handle attribute bindings, etc.)
 | 
									// Handle element nodes (can extend to handle attribute bindings, etc.)
 | 
				
			||||||
				else if (currentNode.nodeType === Node.ELEMENT_NODE) {
 | 
									else if (currentNode.nodeType === Node.ELEMENT_NODE) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
					const currentElementNode = currentNode as Element // Renamed to avoid conflict with outer 'element'
 | 
										const currentElementNode = currentNode as Element // Renamed to avoid conflict with outer 'element'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					// Traverse all macro attributes
 | 
										// Traverse all macro attributes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					// Detect :attr="" bindings, such as :src="imageUrl"
 | 
										// Detect :attr="" bindings, such as :src="imageUrl"
 | 
				
			||||||
					Array.from(currentElementNode.attributes).forEach(attr => {
 | 
										Array.from(currentElementNode.attributes).forEach((attr) => {
 | 
				
			||||||
						if (attr.name.startsWith(':')) {
 | 
											if (attr.name.startsWith(':')) {
 | 
				
			||||||
							const attrName = attr.name.substring(1) // Remove ':'
 | 
												const attrName = attr.name.substring(1) // Remove ':'
 | 
				
			||||||
							const expr = attr.value.trim()
 | 
												const expr = attr.value.trim()
 | 
				
			||||||
| 
						 | 
					@ -263,13 +310,20 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
							currentElementNode.removeAttribute(attr.name)
 | 
												currentElementNode.removeAttribute(attr.name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							// Set up attribute binding
 | 
												// Set up attribute binding
 | 
				
			||||||
							this._setupAttributeBinding(currentElementNode, attrName, expr, attr.value)
 | 
												this._setupAttributeBinding(
 | 
				
			||||||
 | 
													currentElementNode,
 | 
				
			||||||
 | 
													attrName,
 | 
				
			||||||
 | 
													expr,
 | 
				
			||||||
 | 
													attr.value,
 | 
				
			||||||
 | 
												)
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
					})
 | 
										})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					// Process @event bindings, such as @click="handleClick"
 | 
										// Process @event bindings, such as @click="handleClick"
 | 
				
			||||||
					const eventBindings = Array.from(currentElementNode.attributes).filter(attr => attr.name.startsWith('@'))
 | 
										const eventBindings = Array.from(
 | 
				
			||||||
					eventBindings.forEach(attr => {
 | 
											currentElementNode.attributes,
 | 
				
			||||||
 | 
										).filter((attr) => attr.name.startsWith('@'))
 | 
				
			||||||
 | 
										eventBindings.forEach((attr) => {
 | 
				
			||||||
						const eventName = attr.name.substring(1) // Remove '@'
 | 
											const eventName = attr.name.substring(1) // Remove '@'
 | 
				
			||||||
						const handlerValue = attr.value.trim()
 | 
											const handlerValue = attr.value.trim()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -279,22 +333,42 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
						// Handle different types of event handlers
 | 
											// Handle different types of event handlers
 | 
				
			||||||
						if (handlerValue.includes('=>')) {
 | 
											if (handlerValue.includes('=>')) {
 | 
				
			||||||
							// Handle arrow function: @click="e => setState('count', count + 1)"
 | 
												// Handle arrow function: @click="e => setState('count', count + 1)"
 | 
				
			||||||
							this._setupArrowFunctionHandler(currentElementNode, eventName, handlerValue)
 | 
												this._setupArrowFunctionHandler(
 | 
				
			||||||
						} else if (handlerValue.includes('(') && handlerValue.includes(')')) {
 | 
													currentElementNode,
 | 
				
			||||||
 | 
													eventName,
 | 
				
			||||||
 | 
													handlerValue,
 | 
				
			||||||
 | 
												)
 | 
				
			||||||
 | 
											} else if (
 | 
				
			||||||
 | 
												handlerValue.includes('(') &&
 | 
				
			||||||
 | 
												handlerValue.includes(')')
 | 
				
			||||||
 | 
											) {
 | 
				
			||||||
							// Handle function call: @click="increment(5)"
 | 
												// Handle function call: @click="increment(5)"
 | 
				
			||||||
							this._setupFunctionCallHandler(currentElementNode, eventName, handlerValue)
 | 
												this._setupFunctionCallHandler(
 | 
				
			||||||
 | 
													currentElementNode,
 | 
				
			||||||
 | 
													eventName,
 | 
				
			||||||
 | 
													handlerValue,
 | 
				
			||||||
 | 
												)
 | 
				
			||||||
						} else if (typeof (this as any)[handlerValue] === 'function') {
 | 
											} else if (typeof (this as any)[handlerValue] === 'function') {
 | 
				
			||||||
							// Handle method reference: @click="handleClick"
 | 
												// Handle method reference: @click="handleClick"
 | 
				
			||||||
							currentElementNode.addEventListener(eventName, (this as any)[handlerValue].bind(this))
 | 
												currentElementNode.addEventListener(
 | 
				
			||||||
 | 
													eventName,
 | 
				
			||||||
 | 
													(this as any)[handlerValue].bind(this),
 | 
				
			||||||
 | 
												)
 | 
				
			||||||
						} else {
 | 
											} else {
 | 
				
			||||||
							// Handle simple expression: @click="count++" or @input="name = $event.target.value"
 | 
												// Handle simple expression: @click="count++" or @input="name = $event.target.value"
 | 
				
			||||||
							this._setupExpressionHandler(currentElementNode, eventName, handlerValue)
 | 
												this._setupExpressionHandler(
 | 
				
			||||||
 | 
													currentElementNode,
 | 
				
			||||||
 | 
													eventName,
 | 
				
			||||||
 | 
													handlerValue,
 | 
				
			||||||
 | 
												)
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
					})
 | 
										})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					// Process %-started macros, such as %connect="stateName", %if="condition", %for="item in items"
 | 
										// Process %-started macros, such as %connect="stateName", %if="condition", %for="item in items"
 | 
				
			||||||
					const macroBindings = Array.from(currentElementNode.attributes).filter(attr => attr.name.startsWith('%'))
 | 
										const macroBindings = Array.from(
 | 
				
			||||||
					macroBindings.forEach(attr => {
 | 
											currentElementNode.attributes,
 | 
				
			||||||
 | 
										).filter((attr) => attr.name.startsWith('%'))
 | 
				
			||||||
 | 
										macroBindings.forEach((attr) => {
 | 
				
			||||||
						const macroName = attr.name.substring(1) // Remove '%'
 | 
											const macroName = attr.name.substring(1) // Remove '%'
 | 
				
			||||||
						const expr = attr.value.trim()
 | 
											const expr = attr.value.trim()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -302,19 +376,16 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
						currentElementNode.removeAttribute(attr.name)
 | 
											currentElementNode.removeAttribute(attr.name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						// Handle different types of macros
 | 
											// Handle different types of macros
 | 
				
			||||||
						if (macroName === 'connect') // Handle state connection: %connect="stateName"
 | 
											if (macroName === 'connect')
 | 
				
			||||||
 | 
												// Handle state connection: %connect="stateName"
 | 
				
			||||||
							this._setupTwoWayBinding(currentElementNode, expr)
 | 
												this._setupTwoWayBinding(currentElementNode, expr)
 | 
				
			||||||
						else if (macroName === 'if')
 | 
											else if (macroName === 'if')
 | 
				
			||||||
							ifDirectivesToProcess.push({ element: currentElementNode, expr })
 | 
												ifDirectivesToProcess.push({ element: currentElementNode, expr })
 | 
				
			||||||
						else if (macroName === 'for')
 | 
											else if (macroName === 'for')
 | 
				
			||||||
							this._setupListRendering(currentElementNode, expr)
 | 
												this._setupListRendering(currentElementNode, expr)
 | 
				
			||||||
						else if (macroName === 'key')
 | 
											else if (macroName === 'key') return
 | 
				
			||||||
							return
 | 
											else console.warn(`Unknown macro: %${macroName}`)
 | 
				
			||||||
						else
 | 
					 | 
				
			||||||
							console.warn(`Unknown macro: %${macroName}`)
 | 
					 | 
				
			||||||
					})
 | 
										})
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -336,7 +407,9 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
			if (value !== undefined)
 | 
								if (value !== undefined)
 | 
				
			||||||
				element.setAttribute('data-laterano-connect', String(value))
 | 
									element.setAttribute('data-laterano-connect', String(value))
 | 
				
			||||||
			else
 | 
								else
 | 
				
			||||||
				console.error(`State \`${expr}\` not found in the component state. Although Laterano will try to work with it, it may has potentially unexpected behavior.`)
 | 
									console.error(
 | 
				
			||||||
 | 
										`State \`${expr}\` not found in the component state. Although Laterano will try to work with it, it may has potentially unexpected behavior.`,
 | 
				
			||||||
 | 
									)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Add event listener for input events
 | 
								// Add event listener for input events
 | 
				
			||||||
			element.addEventListener('input', (event: Event) => {
 | 
								element.addEventListener('input', (event: Event) => {
 | 
				
			||||||
| 
						 | 
					@ -359,20 +432,19 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Handle condition rendering (%if macro)
 | 
							// Handle condition rendering (%if macro)
 | 
				
			||||||
		private _setupConditionRendering(element: Element, expr: string) {
 | 
							private _setupConditionRendering(element: Element, expr: string) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
			const placeholder = document.createComment(` %if: ${expr} `)
 | 
								const placeholder = document.createComment(` %if: ${expr} `)
 | 
				
			||||||
			element.parentNode?.insertBefore(placeholder, element)
 | 
								element.parentNode?.insertBefore(placeholder, element)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			this._conditionalElements.set(element, {
 | 
								this._conditionalElements.set(element, {
 | 
				
			||||||
				expr,
 | 
									expr,
 | 
				
			||||||
				placeholder,
 | 
									placeholder,
 | 
				
			||||||
				isPresent: true
 | 
									isPresent: true,
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			this._evaluateIfCondition(element, expr)
 | 
								this._evaluateIfCondition(element, expr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const statePaths = this._extractStatePathsFromExpression(expr)
 | 
								const statePaths = this._extractStatePathsFromExpression(expr)
 | 
				
			||||||
			statePaths.forEach(path => {
 | 
								statePaths.forEach((path) => {
 | 
				
			||||||
				if (!this._stateToElementsMap[path]) {
 | 
									if (!this._stateToElementsMap[path]) {
 | 
				
			||||||
					this._stateToElementsMap[path] = new Set()
 | 
										this._stateToElementsMap[path] = new Set()
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
| 
						 | 
					@ -383,7 +455,9 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
		// Handle list rendering (%for macro)
 | 
							// Handle list rendering (%for macro)
 | 
				
			||||||
		private _setupListRendering(element: Element, expr: string) {
 | 
							private _setupListRendering(element: Element, expr: string) {
 | 
				
			||||||
			// Parse the expression (e.g., "item in items" or "(item, index) in items")
 | 
								// Parse the expression (e.g., "item in items" or "(item, index) in items")
 | 
				
			||||||
			const match = expr.match(/(?:\(([^,]+),\s*([^)]+)\)|([^,\s]+))\s+in\s+(.+)/)
 | 
								const match = expr.match(
 | 
				
			||||||
 | 
									/(?:\(([^,]+),\s*([^)]+)\)|([^,\s]+))\s+in\s+(.+)/,
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
			if (!match) {
 | 
								if (!match) {
 | 
				
			||||||
				console.error(`Invalid %for expression: ${expr}`)
 | 
									console.error(`Invalid %for expression: ${expr}`)
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
| 
						 | 
					@ -404,9 +478,9 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Store current rendered items
 | 
								// Store current rendered items
 | 
				
			||||||
			const renderedItems: Array<{
 | 
								const renderedItems: Array<{
 | 
				
			||||||
				element: Element,
 | 
									element: Element
 | 
				
			||||||
				key: any,
 | 
									key: any
 | 
				
			||||||
				data: any,
 | 
									data: any
 | 
				
			||||||
				index: number
 | 
									index: number
 | 
				
			||||||
			}> = []
 | 
								}> = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -414,7 +488,9 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
			const updateList = () => {
 | 
								const updateList = () => {
 | 
				
			||||||
				const collection = this._evaluateExpression(collectionExpr)
 | 
									const collection = this._evaluateExpression(collectionExpr)
 | 
				
			||||||
				if (!collection || !Array.isArray(collection)) {
 | 
									if (!collection || !Array.isArray(collection)) {
 | 
				
			||||||
					console.warn(`Collection "${collectionExpr}" is not an array or does not exist`)
 | 
										console.warn(
 | 
				
			||||||
 | 
											`Collection "${collectionExpr}" is not an array or does not exist`,
 | 
				
			||||||
 | 
										)
 | 
				
			||||||
					return
 | 
										return
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -425,19 +501,22 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// Detach all currently rendered DOM items managed by this instance.
 | 
									// Detach all currently rendered DOM items managed by this instance.
 | 
				
			||||||
				renderedItems.forEach(item => {
 | 
									renderedItems.forEach((item) => {
 | 
				
			||||||
					if (item.element.parentNode === parentNode) {
 | 
										if (item.element.parentNode === parentNode) {
 | 
				
			||||||
						parentNode.removeChild(item.element);
 | 
											parentNode.removeChild(item.element)
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				});
 | 
									})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// Get key attribute if available
 | 
									// Get key attribute if available
 | 
				
			||||||
				const keyAttr = template.getAttribute('%key')
 | 
									const keyAttr = template.getAttribute('%key')
 | 
				
			||||||
				if (!keyAttr) console.warn(`%key attribute not found in the template, which is not a recommended practice.`)
 | 
									if (!keyAttr)
 | 
				
			||||||
 | 
										console.warn(
 | 
				
			||||||
 | 
											`%key attribute not found in the template, which is not a recommended practice.`,
 | 
				
			||||||
 | 
										)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// Store a map of existing items by key for reuse
 | 
									// Store a map of existing items by key for reuse
 | 
				
			||||||
				const existingElementsByKey = new Map()
 | 
									const existingElementsByKey = new Map()
 | 
				
			||||||
				renderedItems.forEach(item => {
 | 
									renderedItems.forEach((item) => {
 | 
				
			||||||
					if (item.key !== undefined) {
 | 
										if (item.key !== undefined) {
 | 
				
			||||||
						existingElementsByKey.set(item.key, item)
 | 
											existingElementsByKey.set(item.key, item)
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
| 
						 | 
					@ -452,7 +531,15 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
				// Create or update items in the list
 | 
									// Create or update items in the list
 | 
				
			||||||
				collection.forEach((item, index) => {
 | 
									collection.forEach((item, index) => {
 | 
				
			||||||
					// Determine the key for this item
 | 
										// Determine the key for this item
 | 
				
			||||||
					const key = keyAttr ? this._evaluateExpressionWithItemContext(keyAttr ?? '', item, index, itemVar, indexVar ? indexVar : undefined) : index
 | 
										const key = keyAttr
 | 
				
			||||||
 | 
											? this._evaluateExpressionWithItemContext(
 | 
				
			||||||
 | 
													keyAttr ?? '',
 | 
				
			||||||
 | 
													item,
 | 
				
			||||||
 | 
													index,
 | 
				
			||||||
 | 
													itemVar,
 | 
				
			||||||
 | 
													indexVar ? indexVar : undefined,
 | 
				
			||||||
 | 
												)
 | 
				
			||||||
 | 
											: index
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					// Check if we can reuse an existing element
 | 
										// Check if we can reuse an existing element
 | 
				
			||||||
					const existingItem = existingElementsByKey.get(key)
 | 
										const existingItem = existingElementsByKey.get(key)
 | 
				
			||||||
| 
						 | 
					@ -472,19 +559,21 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
						element: itemElement,
 | 
											element: itemElement,
 | 
				
			||||||
						key,
 | 
											key,
 | 
				
			||||||
						data: item,
 | 
											data: item,
 | 
				
			||||||
						index
 | 
											index,
 | 
				
			||||||
					})
 | 
										})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					// Create item context for this item
 | 
										// Create item context for this item
 | 
				
			||||||
					const itemContext = {
 | 
										const itemContext = {
 | 
				
			||||||
						[itemVar]: item
 | 
											[itemVar]: item,
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					if (indexVar)
 | 
										if (indexVar) itemContext[indexVar] = index
 | 
				
			||||||
						itemContext[indexVar] = index
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
					// insert %key attribute, which dynamically bind the key
 | 
										// insert %key attribute, which dynamically bind the key
 | 
				
			||||||
					if (keyAttr) {
 | 
										if (keyAttr) {
 | 
				
			||||||
						const keyValue = this._evaluateExpressionWithItemContext(keyAttr, itemContext)
 | 
											const keyValue = this._evaluateExpressionWithItemContext(
 | 
				
			||||||
 | 
												keyAttr,
 | 
				
			||||||
 | 
												itemContext,
 | 
				
			||||||
 | 
											)
 | 
				
			||||||
						itemElement.setAttribute('data-laterano-key', String(keyValue))
 | 
											itemElement.setAttribute('data-laterano-key', String(keyValue))
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -503,7 +592,7 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
				placeholder.parentNode?.insertBefore(fragment, placeholder.nextSibling)
 | 
									placeholder.parentNode?.insertBefore(fragment, placeholder.nextSibling)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// Remove any remaining unused items
 | 
									// Remove any remaining unused items
 | 
				
			||||||
				existingElementsByKey.forEach(item => {
 | 
									existingElementsByKey.forEach((item) => {
 | 
				
			||||||
					if (item.element.parentNode) {
 | 
										if (item.element.parentNode) {
 | 
				
			||||||
						item.element.parentNode.removeChild(item.element)
 | 
											item.element.parentNode.removeChild(item.element)
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
| 
						 | 
					@ -519,7 +608,9 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			// Using a unique identifier for this list rendering instance
 | 
								// Using a unique identifier for this list rendering instance
 | 
				
			||||||
			const listVirtualElement = document.createElement('div')
 | 
								const listVirtualElement = document.createElement('div')
 | 
				
			||||||
			this._stateToElementsMap[collectionExpr].add(listVirtualElement as HTMLElement)
 | 
								this._stateToElementsMap[collectionExpr].add(
 | 
				
			||||||
 | 
									listVirtualElement as HTMLElement,
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Add listener for state changes
 | 
								// Add listener for state changes
 | 
				
			||||||
			this._statesListeners[collectionExpr] = () => {
 | 
								this._statesListeners[collectionExpr] = () => {
 | 
				
			||||||
| 
						 | 
					@ -528,9 +619,12 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Recursively process the element and its children, applying the item context
 | 
							// Recursively process the element and its children, applying the item context
 | 
				
			||||||
		private _processElementWithItemContext(element: Element, itemContext: Record<string, any>) {
 | 
							private _processElementWithItemContext(
 | 
				
			||||||
 | 
								element: Element,
 | 
				
			||||||
 | 
								itemContext: Record<string, any>,
 | 
				
			||||||
 | 
							) {
 | 
				
			||||||
			// 1. Store the item context of the element so that subsequent updates can find it
 | 
								// 1. Store the item context of the element so that subsequent updates can find it
 | 
				
			||||||
			(element as any)._itemContext = itemContext
 | 
								;(element as any)._itemContext = itemContext
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// 2. Process bindings in text nodes
 | 
								// 2. Process bindings in text nodes
 | 
				
			||||||
			const processTextNodes = (node: Node) => {
 | 
								const processTextNodes = (node: Node) => {
 | 
				
			||||||
| 
						 | 
					@ -538,28 +632,37 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
					const textContent = node.textContent || ''
 | 
										const textContent = node.textContent || ''
 | 
				
			||||||
					if (textContent.includes('{{')) {
 | 
										if (textContent.includes('{{')) {
 | 
				
			||||||
						const textNode = node as Text
 | 
											const textNode = node as Text
 | 
				
			||||||
						const updatedContent = textContent.replace(/\{\{\s*([^}]+)\s*\}\}/g, (match, expr) => {
 | 
											const updatedContent = textContent.replace(
 | 
				
			||||||
							const value = this._evaluateExpressionWithItemContext(expr.trim(), itemContext)
 | 
												/\{\{\s*([^}]+)\s*\}\}/g,
 | 
				
			||||||
 | 
												(match, expr) => {
 | 
				
			||||||
 | 
													const value = this._evaluateExpressionWithItemContext(
 | 
				
			||||||
 | 
														expr.trim(),
 | 
				
			||||||
 | 
														itemContext,
 | 
				
			||||||
 | 
													)
 | 
				
			||||||
								return value !== undefined ? String(value) : ''
 | 
													return value !== undefined ? String(value) : ''
 | 
				
			||||||
						})
 | 
												},
 | 
				
			||||||
 | 
											)
 | 
				
			||||||
						textNode.textContent = updatedContent
 | 
											textNode.textContent = updatedContent
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Process the text nodes of the element itself
 | 
								// Process the text nodes of the element itself
 | 
				
			||||||
			Array.from(element.childNodes).forEach(node => {
 | 
								Array.from(element.childNodes).forEach((node) => {
 | 
				
			||||||
				if (node.nodeType === Node.TEXT_NODE) {
 | 
									if (node.nodeType === Node.TEXT_NODE) {
 | 
				
			||||||
					processTextNodes(node)
 | 
										processTextNodes(node)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// 3. Process attribute bindings (:attr)
 | 
								// 3. Process attribute bindings (:attr)
 | 
				
			||||||
			Array.from(element.attributes).forEach(attr => {
 | 
								Array.from(element.attributes).forEach((attr) => {
 | 
				
			||||||
				if (attr.name.startsWith(':')) {
 | 
									if (attr.name.startsWith(':')) {
 | 
				
			||||||
					const attrName = attr.name.substring(1)
 | 
										const attrName = attr.name.substring(1)
 | 
				
			||||||
					const expr = attr.value.trim()
 | 
										const expr = attr.value.trim()
 | 
				
			||||||
					const value = this._evaluateExpressionWithItemContext(expr, itemContext)
 | 
										const value = this._evaluateExpressionWithItemContext(
 | 
				
			||||||
 | 
											expr,
 | 
				
			||||||
 | 
											itemContext,
 | 
				
			||||||
 | 
										)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					if (value !== undefined) {
 | 
										if (value !== undefined) {
 | 
				
			||||||
						element.setAttribute(attrName, String(value))
 | 
											element.setAttribute(attrName, String(value))
 | 
				
			||||||
| 
						 | 
					@ -571,7 +674,7 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// 4. Process event bindings (@event)
 | 
								// 4. Process event bindings (@event)
 | 
				
			||||||
			Array.from(element.attributes).forEach(attr => {
 | 
								Array.from(element.attributes).forEach((attr) => {
 | 
				
			||||||
				if (attr.name.startsWith('@')) {
 | 
									if (attr.name.startsWith('@')) {
 | 
				
			||||||
					const eventName = attr.name.substring(1)
 | 
										const eventName = attr.name.substring(1)
 | 
				
			||||||
					const handlerValue = attr.value.trim()
 | 
										const handlerValue = attr.value.trim()
 | 
				
			||||||
| 
						 | 
					@ -587,14 +690,17 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
								...this._createHandlerContext(event, element),
 | 
													...this._createHandlerContext(event, element),
 | 
				
			||||||
								...itemContext,
 | 
													...itemContext,
 | 
				
			||||||
								$event: event,
 | 
													$event: event,
 | 
				
			||||||
								$el: element
 | 
													$el: element,
 | 
				
			||||||
							}
 | 
												}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							// Execute the expression
 | 
												// Execute the expression
 | 
				
			||||||
							const fnStr = `with(this) { ${handlerValue} }`
 | 
												const fnStr = `with(this) { ${handlerValue} }`
 | 
				
			||||||
							new Function(fnStr).call(mergedContext)
 | 
												new Function(fnStr).call(mergedContext)
 | 
				
			||||||
						} catch (err) {
 | 
											} catch (err) {
 | 
				
			||||||
							console.error(`Error executing event handler with item context: ${handlerValue}`, err)
 | 
												console.error(
 | 
				
			||||||
 | 
													`Error executing event handler with item context: ${handlerValue}`,
 | 
				
			||||||
 | 
													err,
 | 
				
			||||||
 | 
												)
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
					})
 | 
										})
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
| 
						 | 
					@ -604,7 +710,7 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
			let isConditional = false
 | 
								let isConditional = false
 | 
				
			||||||
			let shouldDisplay = true
 | 
								let shouldDisplay = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			Array.from(element.attributes).forEach(attr => {
 | 
								Array.from(element.attributes).forEach((attr) => {
 | 
				
			||||||
				if (attr.name === '%if') {
 | 
									if (attr.name === '%if') {
 | 
				
			||||||
					isConditional = true
 | 
										isConditional = true
 | 
				
			||||||
					const expr = attr.value.trim()
 | 
										const expr = attr.value.trim()
 | 
				
			||||||
| 
						 | 
					@ -613,12 +719,14 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
					element.removeAttribute(attr.name)
 | 
										element.removeAttribute(attr.name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					// Calculate the condition
 | 
										// Calculate the condition
 | 
				
			||||||
					const result = this._evaluateExpressionWithItemContext(expr, itemContext)
 | 
										const result = this._evaluateExpressionWithItemContext(
 | 
				
			||||||
 | 
											expr,
 | 
				
			||||||
 | 
											itemContext,
 | 
				
			||||||
 | 
										)
 | 
				
			||||||
					shouldDisplay = Boolean(result)
 | 
										shouldDisplay = Boolean(result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					// Apply the condition (in the list item context, we use display style to simplify)
 | 
										// Apply the condition (in the list item context, we use display style to simplify)
 | 
				
			||||||
					if (!shouldDisplay)
 | 
										if (!shouldDisplay) (element as HTMLElement).style.display = 'none'
 | 
				
			||||||
						(element as HTMLElement).style.display = 'none'
 | 
					 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -630,7 +738,7 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
			// 6. Process nested list rendering (%for)
 | 
								// 6. Process nested list rendering (%for)
 | 
				
			||||||
			let hasForDirective = false
 | 
								let hasForDirective = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			Array.from(element.attributes).forEach(attr => {
 | 
								Array.from(element.attributes).forEach((attr) => {
 | 
				
			||||||
				if (attr.name === '%for') {
 | 
									if (attr.name === '%for') {
 | 
				
			||||||
					hasForDirective = true
 | 
										hasForDirective = true
 | 
				
			||||||
					const forExpr = attr.value.trim()
 | 
										const forExpr = attr.value.trim()
 | 
				
			||||||
| 
						 | 
					@ -650,16 +758,22 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// 7. Recursively process all child elements
 | 
								// 7. Recursively process all child elements
 | 
				
			||||||
			Array.from(element.children).forEach(child => {
 | 
								Array.from(element.children).forEach((child) => {
 | 
				
			||||||
				this._processElementWithItemContext(child, itemContext)
 | 
									this._processElementWithItemContext(child, itemContext)
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Set up nested list rendering
 | 
							// Set up nested list rendering
 | 
				
			||||||
		private _setupNestedListRendering(element: Element, expr: string, parentItemContext: Record<string, any>) {
 | 
							private _setupNestedListRendering(
 | 
				
			||||||
 | 
								element: Element,
 | 
				
			||||||
 | 
								expr: string,
 | 
				
			||||||
 | 
								parentItemContext: Record<string, any>,
 | 
				
			||||||
 | 
							) {
 | 
				
			||||||
			// Similar to _setupListRendering, but applies to nested situations
 | 
								// Similar to _setupListRendering, but applies to nested situations
 | 
				
			||||||
			// Parse the expression (e.g., "subItem in item.subItems")
 | 
								// Parse the expression (e.g., "subItem in item.subItems")
 | 
				
			||||||
			const match = expr.match(/(?:\(([^,]+),\s*([^)]+)\)|([^,\s]+))\s+in\s+(.+)/)
 | 
								const match = expr.match(
 | 
				
			||||||
 | 
									/(?:\(([^,]+),\s*([^)]+)\)|([^,\s]+))\s+in\s+(.+)/,
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
			if (!match) {
 | 
								if (!match) {
 | 
				
			||||||
				console.error(`Invalid nested %for expression: ${expr}`)
 | 
									console.error(`Invalid nested %for expression: ${expr}`)
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
| 
						 | 
					@ -671,10 +785,15 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
			const collectionExpr = match[4].trim()
 | 
								const collectionExpr = match[4].trim()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Evaluate the collection expression, using the parent item context
 | 
								// Evaluate the collection expression, using the parent item context
 | 
				
			||||||
			const collection = this._evaluateExpressionWithItemContext(collectionExpr, parentItemContext)
 | 
								const collection = this._evaluateExpressionWithItemContext(
 | 
				
			||||||
 | 
									collectionExpr,
 | 
				
			||||||
 | 
									parentItemContext,
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (!collection || !Array.isArray(collection)) {
 | 
								if (!collection || !Array.isArray(collection)) {
 | 
				
			||||||
				console.warn(`Nested collection "${collectionExpr}" is not an array or does not exist`)
 | 
									console.warn(
 | 
				
			||||||
 | 
										`Nested collection "${collectionExpr}" is not an array or does not exist`,
 | 
				
			||||||
 | 
									)
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -693,7 +812,7 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
				// Create a nested item context, merging the parent context
 | 
									// Create a nested item context, merging the parent context
 | 
				
			||||||
				const nestedItemContext = {
 | 
									const nestedItemContext = {
 | 
				
			||||||
					...parentItemContext,
 | 
										...parentItemContext,
 | 
				
			||||||
					[itemVar]: item
 | 
										[itemVar]: item,
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				if (indexVar) {
 | 
									if (indexVar) {
 | 
				
			||||||
| 
						 | 
					@ -707,12 +826,21 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
				// to improve performance
 | 
									// to improve performance
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// Insert the item element into the DOM
 | 
									// Insert the item element into the DOM
 | 
				
			||||||
				placeholder.parentNode?.insertBefore(itemElement, placeholder.nextSibling)
 | 
									placeholder.parentNode?.insertBefore(
 | 
				
			||||||
 | 
										itemElement,
 | 
				
			||||||
 | 
										placeholder.nextSibling,
 | 
				
			||||||
 | 
									)
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Evaluate expressions using the item context
 | 
							// Evaluate expressions using the item context
 | 
				
			||||||
		private _evaluateExpressionWithItemContext(expression: string, itemContext: Record<string, any>, index?: number, itemVar?: string, indexVar?: string): any {
 | 
							private _evaluateExpressionWithItemContext(
 | 
				
			||||||
 | 
								expression: string,
 | 
				
			||||||
 | 
								itemContext: Record<string, any>,
 | 
				
			||||||
 | 
								index?: number,
 | 
				
			||||||
 | 
								itemVar?: string,
 | 
				
			||||||
 | 
								indexVar?: string,
 | 
				
			||||||
 | 
							): any {
 | 
				
			||||||
			try {
 | 
								try {
 | 
				
			||||||
				// Check if the expression directly references the item variable
 | 
									// Check if the expression directly references the item variable
 | 
				
			||||||
				if (itemVar && expression === itemVar) {
 | 
									if (itemVar && expression === itemVar) {
 | 
				
			||||||
| 
						 | 
					@ -751,7 +879,10 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
				const func = new Function(...contextKeys, `return ${expression}`)
 | 
									const func = new Function(...contextKeys, `return ${expression}`)
 | 
				
			||||||
				return func(...contextValues)
 | 
									return func(...contextValues)
 | 
				
			||||||
			} catch (error) {
 | 
								} catch (error) {
 | 
				
			||||||
				console.error(`Error evaluating expression with item context: ${expression}`, error)
 | 
									console.error(
 | 
				
			||||||
 | 
										`Error evaluating expression with item context: ${expression}`,
 | 
				
			||||||
 | 
										error,
 | 
				
			||||||
 | 
									)
 | 
				
			||||||
				return undefined
 | 
									return undefined
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -765,10 +896,14 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
			const shouldShow = Boolean(result)
 | 
								const shouldShow = Boolean(result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (shouldShow !== info.isPresent) {
 | 
								if (shouldShow !== info.isPresent) {
 | 
				
			||||||
				if (shouldShow) // Insert the element back into the DOM
 | 
									if (shouldShow)
 | 
				
			||||||
					info.placeholder.parentNode?.insertBefore(element, info.placeholder.nextSibling)
 | 
										// Insert the element back into the DOM
 | 
				
			||||||
				else // Remove the element from the DOM
 | 
										info.placeholder.parentNode?.insertBefore(
 | 
				
			||||||
					element.parentNode?.removeChild(element)
 | 
											element,
 | 
				
			||||||
 | 
											info.placeholder.nextSibling,
 | 
				
			||||||
 | 
										)
 | 
				
			||||||
 | 
									// Remove the element from the DOM
 | 
				
			||||||
 | 
									else element.parentNode?.removeChild(element)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// Update the state
 | 
									// Update the state
 | 
				
			||||||
				info.isPresent = shouldShow
 | 
									info.isPresent = shouldShow
 | 
				
			||||||
| 
						 | 
					@ -789,7 +924,9 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
				const func = new Function(...stateKeys, `return ${expression}`)
 | 
									const func = new Function(...stateKeys, `return ${expression}`)
 | 
				
			||||||
				const execRes = func(...stateValues)
 | 
									const execRes = func(...stateValues)
 | 
				
			||||||
				if (typeof execRes !== 'boolean')
 | 
									if (typeof execRes !== 'boolean')
 | 
				
			||||||
					throw new Error(`The expression "${expression}" must return a boolean value.`)
 | 
										throw new Error(
 | 
				
			||||||
 | 
											`The expression "${expression}" must return a boolean value.`,
 | 
				
			||||||
 | 
										)
 | 
				
			||||||
				return execRes
 | 
									return execRes
 | 
				
			||||||
			} catch (error) {
 | 
								} catch (error) {
 | 
				
			||||||
				console.error(`Error evaluating expression: ${expression}`, error)
 | 
									console.error(`Error evaluating expression: ${expression}`, error)
 | 
				
			||||||
| 
						 | 
					@ -799,13 +936,18 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		private _extractStatePathsFromExpression(expression: string): string[] {
 | 
							private _extractStatePathsFromExpression(expression: string): string[] {
 | 
				
			||||||
			const matches = expression.match(/[a-zA-Z_$][a-zA-Z0-9_$]*/g) || []
 | 
								const matches = expression.match(/[a-zA-Z_$][a-zA-Z0-9_$]*/g) || []
 | 
				
			||||||
			return matches.filter(match =>
 | 
								return matches.filter(
 | 
				
			||||||
				!['true', 'false', 'null', 'undefined', 'this'].includes(match)
 | 
									(match) =>
 | 
				
			||||||
 | 
										!['true', 'false', 'null', 'undefined', 'this'].includes(match),
 | 
				
			||||||
			)
 | 
								)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Handle arrow function
 | 
							// Handle arrow function
 | 
				
			||||||
		private _setupArrowFunctionHandler(element: Element, eventName: string, handlerValue: string) {
 | 
							private _setupArrowFunctionHandler(
 | 
				
			||||||
 | 
								element: Element,
 | 
				
			||||||
 | 
								eventName: string,
 | 
				
			||||||
 | 
								handlerValue: string,
 | 
				
			||||||
 | 
							) {
 | 
				
			||||||
			element.addEventListener(eventName, (event: Event) => {
 | 
								element.addEventListener(eventName, (event: Event) => {
 | 
				
			||||||
				try {
 | 
									try {
 | 
				
			||||||
					// Arrow function parsing
 | 
										// Arrow function parsing
 | 
				
			||||||
| 
						 | 
					@ -860,7 +1002,10 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
						handlerFn.apply(context, [event])
 | 
											handlerFn.apply(context, [event])
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				} catch (err) {
 | 
									} catch (err) {
 | 
				
			||||||
					console.error(`Error executing arrow function handler: ${handlerValue}`, err)
 | 
										console.error(
 | 
				
			||||||
 | 
											`Error executing arrow function handler: ${handlerValue}`,
 | 
				
			||||||
 | 
											err,
 | 
				
			||||||
 | 
										)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -881,21 +1026,30 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
				$el: element,
 | 
									$el: element,
 | 
				
			||||||
				this: this, // Provide reference to the component instance
 | 
									this: this, // Provide reference to the component instance
 | 
				
			||||||
				setState: this.setState.bind(this),
 | 
									setState: this.setState.bind(this),
 | 
				
			||||||
				getState: this.getState.bind(this)
 | 
									getState: this.getState.bind(this),
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Add all methods of the component
 | 
								// Add all methods of the component
 | 
				
			||||||
			Object.getOwnPropertyNames(Object.getPrototypeOf(this)).forEach(name => {
 | 
								Object.getOwnPropertyNames(Object.getPrototypeOf(this)).forEach(
 | 
				
			||||||
				if (typeof (this as any)[name] === 'function' && name !== 'constructor') {
 | 
									(name) => {
 | 
				
			||||||
 | 
										if (
 | 
				
			||||||
 | 
											typeof (this as any)[name] === 'function' &&
 | 
				
			||||||
 | 
											name !== 'constructor'
 | 
				
			||||||
 | 
										) {
 | 
				
			||||||
						context[name] = (this as any)[name].bind(this)
 | 
											context[name] = (this as any)[name].bind(this)
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
			})
 | 
									},
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			return context
 | 
								return context
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Handle function call, such as @click="increment(5)"
 | 
							// Handle function call, such as @click="increment(5)"
 | 
				
			||||||
		private _setupFunctionCallHandler(element: Element, eventName: string, handlerValue: string) {
 | 
							private _setupFunctionCallHandler(
 | 
				
			||||||
 | 
								element: Element,
 | 
				
			||||||
 | 
								eventName: string,
 | 
				
			||||||
 | 
								handlerValue: string,
 | 
				
			||||||
 | 
							) {
 | 
				
			||||||
			element.addEventListener(eventName, (event: Event) => {
 | 
								element.addEventListener(eventName, (event: Event) => {
 | 
				
			||||||
				try {
 | 
									try {
 | 
				
			||||||
					// Create context object
 | 
										// Create context object
 | 
				
			||||||
| 
						 | 
					@ -910,13 +1064,20 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					new Function(fnStr).call(context)
 | 
										new Function(fnStr).call(context)
 | 
				
			||||||
				} catch (err) {
 | 
									} catch (err) {
 | 
				
			||||||
					console.error(`Error executing function call handler: ${handlerValue}`, err)
 | 
										console.error(
 | 
				
			||||||
 | 
											`Error executing function call handler: ${handlerValue}`,
 | 
				
			||||||
 | 
											err,
 | 
				
			||||||
 | 
										)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Handle simple expression, such as @click="count++" or @input="name = $event.target.value"
 | 
							// Handle simple expression, such as @click="count++" or @input="name = $event.target.value"
 | 
				
			||||||
		private _setupExpressionHandler(element: Element, eventName: string, handlerValue: string) {
 | 
							private _setupExpressionHandler(
 | 
				
			||||||
 | 
								element: Element,
 | 
				
			||||||
 | 
								eventName: string,
 | 
				
			||||||
 | 
								handlerValue: string,
 | 
				
			||||||
 | 
							) {
 | 
				
			||||||
			element.addEventListener(eventName, (event: Event) => {
 | 
								element.addEventListener(eventName, (event: Event) => {
 | 
				
			||||||
				try {
 | 
									try {
 | 
				
			||||||
					// Create context object
 | 
										// Create context object
 | 
				
			||||||
| 
						 | 
					@ -935,7 +1096,10 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
					// If the expression returns a value, it can be used for two-way binding
 | 
										// If the expression returns a value, it can be used for two-way binding
 | 
				
			||||||
					return result
 | 
										return result
 | 
				
			||||||
				} catch (err) {
 | 
									} catch (err) {
 | 
				
			||||||
					console.error(`Error executing expression handler: ${handlerValue}`, err)
 | 
										console.error(
 | 
				
			||||||
 | 
											`Error executing expression handler: ${handlerValue}`,
 | 
				
			||||||
 | 
											err,
 | 
				
			||||||
 | 
										)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -959,7 +1123,12 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Set up attribute binding
 | 
							// Set up attribute binding
 | 
				
			||||||
		private _setupAttributeBinding(element: Element, attrName: string, expr: string, template: string) {
 | 
							private _setupAttributeBinding(
 | 
				
			||||||
 | 
								element: Element,
 | 
				
			||||||
 | 
								attrName: string,
 | 
				
			||||||
 | 
								expr: string,
 | 
				
			||||||
 | 
								template: string,
 | 
				
			||||||
 | 
							) {
 | 
				
			||||||
			// Initialize attribute value
 | 
								// Initialize attribute value
 | 
				
			||||||
			const value = this._getNestedState(expr)
 | 
								const value = this._getNestedState(expr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -977,7 +1146,7 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
				element,
 | 
									element,
 | 
				
			||||||
				attrName,
 | 
									attrName,
 | 
				
			||||||
				expr,
 | 
									expr,
 | 
				
			||||||
				template
 | 
									template,
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1009,7 +1178,11 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
			return ['data-attribute']
 | 
								return ['data-attribute']
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		attributeChangedCallback(attrName: string, oldValue: string, newValue: string) {
 | 
							attributeChangedCallback(
 | 
				
			||||||
 | 
								attrName: string,
 | 
				
			||||||
 | 
								oldValue: string,
 | 
				
			||||||
 | 
								newValue: string,
 | 
				
			||||||
 | 
							) {
 | 
				
			||||||
			if (onAttributeChanged) onAttributeChanged(attrName, oldValue, newValue)
 | 
								if (onAttributeChanged) onAttributeChanged(attrName, oldValue, newValue)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1032,8 +1205,7 @@ export default (options: ComponentOptions) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// function trigger
 | 
							// function trigger
 | 
				
			||||||
		triggerFunc(eventName: string, ...args: any[]) {
 | 
							triggerFunc(eventName: string, ...args: any[]) {
 | 
				
			||||||
			if (funcs && funcs[eventName])
 | 
								if (funcs && funcs[eventName]) funcs[eventName].call(this, ...args)
 | 
				
			||||||
				funcs[eventName].call(this, ...args)
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user