| 124 | } |
| 125 | |
| 126 | function registerComponent(templateEl, componentScript) { |
| 127 | const tagName = templateEl.getAttribute('component'); |
| 128 | if (!tagName.includes('-')) { |
| 129 | console.error("component name must contain a dash: '" + tagName + "'"); |
| 130 | return; |
| 131 | } |
| 132 | |
| 133 | // Extract <style> blocks from the raw text, wrap in @scope (tag-name), |
| 134 | // and inject a single combined <style> into document.head. Using head |
| 135 | // rather than insertAdjacentElement keeps bundle-loaded templates |
| 136 | // (whose templateEl lives in a DOMParser'd foreign document) working. |
| 137 | var raw = templateEl.textContent; |
| 138 | var combined = ''; |
| 139 | var styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi; |
| 140 | var match; |
| 141 | while ((match = styleRegex.exec(raw)) !== null) { |
| 142 | combined += match[1] + '\n'; |
| 143 | } |
| 144 | if (combined) { |
| 145 | raw = raw.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, ''); |
| 146 | templateEl.textContent = raw; |
| 147 | var scopedStyle = document.createElement('style'); |
| 148 | scopedStyle.setAttribute('data-hyperscript-component', tagName); |
| 149 | scopedStyle.textContent = '@scope (' + tagName + ') {\n' + combined + '}'; |
| 150 | document.head.appendChild(scopedStyle); |
| 151 | } |
| 152 | |
| 153 | const templateSource = templateEl.textContent; |
| 154 | |
| 155 | // Parse template once to validate - actual rendering happens per instance |
| 156 | // (We reuse the render command's approach: tokenize in "lines" mode at render time) |
| 157 | |
| 158 | const ComponentClass = class extends HTMLElement { |
| 159 | connectedCallback() { |
| 160 | // Skip if already initialized |
| 161 | if (this._hypercomp_initialized) return; |
| 162 | this._hypercomp_initialized = true; |
| 163 | |
| 164 | // Isolate component scope - ^var resolution stops here |
| 165 | this.setAttribute('dom-scope', 'isolated'); |
| 166 | |
| 167 | // Capture slot content and clear children immediately, |
| 168 | // before processNode can recurse into them |
| 169 | this._slotContent = this.innerHTML; |
| 170 | this.innerHTML = ''; |
| 171 | |
| 172 | // 1. Inject `attrs` proxy into element scope, then apply component-level hyperscript |
| 173 | var internalData = runtime.getInternalData(this); |
| 174 | if (!internalData.elementScope) internalData.elementScope = {}; |
| 175 | internalData.elementScope.attrs = createAttrs(this); |
| 176 | |
| 177 | if (componentScript) { |
| 178 | this.setAttribute('_', componentScript); |
| 179 | _hyperscript.process(this); |
| 180 | } |
| 181 | |
| 182 | // 2. Render template after synchronous init completes |
| 183 | const self = this; |