@adukiorg/anza 0.3.8 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adukiorg/anza",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "description": "Anza web platform library — reactive state, networking, offline, animations, custom elements. Instant build step. Pure browser ESM.",
5
5
  "author": "fescii",
6
6
  "license": "MIT",
@@ -59,7 +59,7 @@ export function element(tag, spec, base) {
59
59
 
60
60
  // Initiate resource fetching exactly once per component registration (R-06)
61
61
  let resolved = null;
62
- const resourcesPromise = preloadResources(tag, styleUrl, templateUrl, spec.template, spec.style).then(res => {
62
+ let resourcesPromise = preloadResources(tag, styleUrl, templateUrl, spec.template, spec.style).then(res => {
63
63
  resolved = res;
64
64
  return res;
65
65
  });
@@ -87,6 +87,35 @@ export function element(tag, spec, base) {
87
87
  }
88
88
  }
89
89
 
90
+ // Handle hot reloading of HTML templates
91
+ if (templateUrl && typeof window !== 'undefined') {
92
+ if (!window.__native_hmr_html_listeners__) {
93
+ window.__native_hmr_html_listeners__ = new Set();
94
+ }
95
+ if (!window.__native_hmr_html_listeners__.has(templateUrl)) {
96
+ window.__native_hmr_html_listeners__.add(templateUrl);
97
+ const hmrHandler = async (e) => {
98
+ const { path: changedPath, html } = e.detail;
99
+ const absoluteChangedUrl = new URL(changedPath, window.location.origin).href;
100
+
101
+ if (templateUrl === absoluteChangedUrl || templateUrl.endsWith(changedPath)) {
102
+ // Re-parse HTML into the cached resolved object
103
+ resourcesPromise = preloadResources(tag, styleUrl, null, html, spec.style).then(res => {
104
+ resolved = res;
105
+ // Now re-bind instances!
106
+ const instances = window.__native_hmr_instances__?.get(tag) || [];
107
+ for (const instance of instances) {
108
+ if (instance.__hmr_rebind) instance.__hmr_rebind();
109
+ }
110
+ console.log(`[HMR] Template hot-swapped for <${tag}>`);
111
+ return res;
112
+ });
113
+ }
114
+ };
115
+ window.addEventListener('anza:hmr:html', hmrHandler);
116
+ }
117
+ }
118
+
90
119
  class DeclarativeElement extends BaseElement {
91
120
  static observedAttributes = observedAttrs;
92
121
 
@@ -98,6 +127,11 @@ export function element(tag, spec, base) {
98
127
  pendingUpdatesMap.set(this, new Map());
99
128
  updateScheduledMap.set(this, false);
100
129
 
130
+ if (typeof window !== 'undefined') {
131
+ if (!window.__native_hmr_instances__) window.__native_hmr_instances__ = new Map();
132
+ if (!window.__native_hmr_instances__.has(tag)) window.__native_hmr_instances__.set(tag, new Set());
133
+ }
134
+
101
135
  if (spec.form) {
102
136
  const internals = this.attachInternals();
103
137
  internalsMap.set(this, internals);
@@ -126,6 +160,10 @@ export function element(tag, spec, base) {
126
160
  // AbortController bootstrap inside BaseElement
127
161
  super.connectedCallback();
128
162
 
163
+ if (typeof window !== 'undefined') {
164
+ window.__native_hmr_instances__?.get(tag)?.add(this);
165
+ }
166
+
129
167
  // Wait for resolved resources to compile (synchronously if already cached)
130
168
  let res = resolved;
131
169
  if (!res) {
@@ -190,6 +228,9 @@ export function element(tag, spec, base) {
190
228
  }
191
229
 
192
230
  disconnectedCallback() {
231
+ if (typeof window !== 'undefined') {
232
+ window.__native_hmr_instances__?.get(tag)?.delete(this);
233
+ }
193
234
  if (spec.unmount) {
194
235
  spec.unmount({
195
236
  el: this,
@@ -202,6 +243,66 @@ export function element(tag, spec, base) {
202
243
  super.disconnectedCallback();
203
244
  }
204
245
 
246
+ async __hmr_rebind() {
247
+ // 1. Unmount component safely
248
+ if (spec.unmount) {
249
+ spec.unmount({
250
+ el: this,
251
+ tags: this._tags,
252
+ refs: this._refs,
253
+ watch: this._watch,
254
+ internals: internalsMap.get(this)
255
+ });
256
+ }
257
+
258
+ // 2. Clear Shadow DOM
259
+ this.shadowRoot.innerHTML = '';
260
+
261
+ // 3. Clone new template
262
+ let res = resolved;
263
+ if (!res) {
264
+ res = await resourcesPromise;
265
+ }
266
+ const { templateNode, stylesheet, cssText, tagsDescriptor } = res;
267
+
268
+ if (templateNode) {
269
+ this.shadowRoot.appendChild(templateNode.cloneNode(true));
270
+ }
271
+ if (stylesheet) {
272
+ this.shadowRoot.adoptedStyleSheets = [stylesheet];
273
+ } else if (cssText) {
274
+ const style = document.createElement('style');
275
+ style.textContent = cssText;
276
+ this.shadowRoot.prepend(style);
277
+ }
278
+
279
+ // 4. Recreate component context to rebind DOM events and tags
280
+ const context = createComponentContext({
281
+ el: this,
282
+ shadowRoot: this.shadowRoot,
283
+ ctrl: this.ctrl,
284
+ descriptor: tagsDescriptor,
285
+ internals: internalsMap.get(this)
286
+ });
287
+ this._ctx = context;
288
+ this._tags = context.tags;
289
+ this._on = context.on;
290
+ this._refs = context.refs;
291
+ this._watch = context.watch;
292
+
293
+ // 5. Trigger mount again
294
+ if (spec.mount) {
295
+ try {
296
+ const mountRes = spec.mount(context);
297
+ if (mountRes instanceof Promise) {
298
+ mountRes.catch(console.error);
299
+ }
300
+ } catch (err) {
301
+ console.error('[Native UI] mount failed during HMR:', err);
302
+ }
303
+ }
304
+ }
305
+
205
306
  attributeChangedCallback(name, oldVal, newVal) {
206
307
  if (oldVal === newVal) return;
207
308