hyperclayjs 1.16.0 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -57,7 +57,7 @@ import 'hyperclayjs/presets/standard.js';
57
57
 
58
58
  | Module | Size | Description |
59
59
  |--------|------|-------------|
60
- | autosave | 0.9KB | Auto-save on DOM changes |
60
+ | autosave | 1.4KB | Auto-save on DOM changes |
61
61
  | edit-mode | 1.8KB | Toggle edit mode on hyperclay on/off |
62
62
  | edit-mode-helpers | 7.5KB | Admin-only functionality: [edit-mode-input], [edit-mode-resource], [edit-mode-onclick] |
63
63
  | option-visibility | 7.8KB | Dynamic show/hide based on ancestor state with option:attribute="value" |
@@ -66,7 +66,7 @@ import 'hyperclayjs/presets/standard.js';
66
66
  | save-system | 12.1KB | CMD+S, [trigger-save] button, savestatus attribute |
67
67
  | save-toast | 0.9KB | Toast notifications for save events |
68
68
  | snapshot | 10.2KB | Source of truth for page state - captures DOM snapshots for save and sync |
69
- | tailwind-inject | 0.4KB | Injects tailwind CSS link with cache-bust on save |
69
+ | tailwind-inject | 1.4KB | Injects tailwind CSS link with cache-bust on save |
70
70
  | unsaved-warning | 1.3KB | Warn before leaving page with unsaved changes |
71
71
 
72
72
  ### Custom Attributes (HTML enhancements)
@@ -86,7 +86,7 @@ import 'hyperclayjs/presets/standard.js';
86
86
  |--------|------|-------------|
87
87
  | dialogs | 7.7KB | ask(), consent(), tell(), snippet() dialog functions |
88
88
  | the-modal | 21KB | Full modal window creation system - window.theModal |
89
- | toast | 7.9KB | Success/error message notifications, toast(msg, msgType) |
89
+ | toast | 8.3KB | Success/error message notifications, toast(msg, msgType) |
90
90
 
91
91
  ### Utilities (Core utilities (often auto-included))
92
92
 
@@ -95,7 +95,7 @@ import 'hyperclayjs/presets/standard.js';
95
95
  | cache-bust | 0.6KB | Cache-bust href/src attributes |
96
96
  | cookie | 1.4KB | Cookie management (often auto-included) |
97
97
  | debounce | 0.4KB | Function debouncing |
98
- | mutation | 13.1KB | DOM mutation observation (often auto-included) |
98
+ | mutation | 13.5KB | DOM mutation observation (often auto-included) |
99
99
  | nearest | 3.4KB | Find nearest elements (often auto-included) |
100
100
  | throttle | 0.8KB | Function throttling |
101
101
 
@@ -106,7 +106,7 @@ import 'hyperclayjs/presets/standard.js';
106
106
  | all-js | 14.4KB | Full DOM manipulation library |
107
107
  | dom-ready | 0.4KB | DOM ready callback |
108
108
  | form-data | 2KB | Extract form data as an object |
109
- | style-injection | 4KB | Dynamic stylesheet injection |
109
+ | style-injection | 4.2KB | Dynamic stylesheet injection |
110
110
 
111
111
  ### String Utilities (String manipulation helpers)
112
112
 
@@ -121,28 +121,28 @@ import 'hyperclayjs/presets/standard.js';
121
121
  | Module | Size | Description |
122
122
  |--------|------|-------------|
123
123
  | file-upload | 10.7KB | File upload with progress |
124
- | live-sync | 12.5KB | Real-time DOM sync across browsers |
124
+ | live-sync | 10.4KB | Real-time DOM sync across browsers |
125
125
  | send-message | 1.3KB | Message sending utility |
126
126
 
127
127
  ### Vendor Libraries (Third-party libraries)
128
128
 
129
129
  | Module | Size | Description |
130
130
  |--------|------|-------------|
131
- | hyper-morph | 16.3KB | DOM morphing with content-based element matching |
131
+ | hyper-morph | 17.2KB | DOM morphing with content-based element matching |
132
132
 
133
133
  ## Presets
134
134
 
135
- ### Minimal (~46.4KB)
135
+ ### Minimal (~46.8KB)
136
136
  Essential features for basic editing
137
137
 
138
138
  **Modules:** `save-core`, `snapshot`, `save-system`, `edit-mode-helpers`, `toast`, `save-toast`, `export-to-window`, `view-mode-excludes-edit-modules`
139
139
 
140
- ### Standard (~68.7KB)
140
+ ### Standard (~69.1KB)
141
141
  Standard feature set for most use cases
142
142
 
143
143
  **Modules:** `save-core`, `snapshot`, `save-system`, `unsaved-warning`, `edit-mode-helpers`, `persist`, `option-visibility`, `event-attrs`, `dom-helpers`, `toast`, `save-toast`, `export-to-window`, `view-mode-excludes-edit-modules`
144
144
 
145
- ### Everything (~201.5KB)
145
+ ### Everything (~201.8KB)
146
146
  All available features
147
147
 
148
148
  Includes all available modules across all categories.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperclayjs",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
4
4
  "description": "Modular JavaScript library for building interactive HTML applications with Hyperclay",
5
5
  "type": "module",
6
6
  "main": "src/hyperclay.js",
@@ -5,12 +5,12 @@
5
5
  *
6
6
  * ┌─────────────────────────────────────────────────────────┐
7
7
  * │ 1. LISTEN snapshot-ready event from save │
8
- * │ (body with form values, no strip)
8
+ * │ (full document with form values)
9
9
  * └─────────────────────────────────────────────────────────┘
10
10
  * │
11
11
  * ▼
12
12
  * ┌─────────────────────────────────────────────────────────┐
13
- * │ 2. SEND POST body to /live-sync/save │
13
+ * │ 2. SEND POST html to /live-sync/save │
14
14
  * │ (debounced, skip if unchanged) │
15
15
  * └─────────────────────────────────────────────────────────┘
16
16
  * │
@@ -22,7 +22,7 @@
22
22
  * │
23
23
  * ▼
24
24
  * ┌─────────────────────────────────────────────────────────┐
25
- * │ 4. MORPH HyperMorph to update DOM
25
+ * │ 4. MORPH HyperMorph full documentElement
26
26
  * │ (preserves focus, input values) │
27
27
  * └─────────────────────────────────────────────────────────┘
28
28
  *
@@ -30,18 +30,19 @@
30
30
  */
31
31
 
32
32
  import { HyperMorph } from "../vendor/hyper-morph.vendor.js";
33
+ import Mutation from "../utilities/mutation.js";
33
34
 
34
35
  class LiveSync {
35
36
  constructor() {
36
37
  this.sse = null;
37
38
  this.currentFile = null;
38
- this.lastHeadHash = null;
39
- this.lastBodyHtml = null;
39
+ this.lastHtml = null;
40
40
  this.clientId = this.generateClientId();
41
41
  this.debounceMs = 150;
42
42
  this.debounceTimer = null;
43
43
  this.isPaused = false;
44
44
  this.isDestroyed = false;
45
+ this.debug = false;
45
46
 
46
47
  // Store handler reference for cleanup
47
48
  this._snapshotHandler = null;
@@ -53,22 +54,33 @@ class LiveSync {
53
54
  this.onError = null;
54
55
  }
55
56
 
57
+ _log(message, data = null) {
58
+ if (!this.debug) return;
59
+ const prefix = `[LiveSync ${new Date().toISOString()}]`;
60
+ if (data !== null) {
61
+ console.log(prefix, message, data);
62
+ } else {
63
+ console.log(prefix, message);
64
+ }
65
+ }
66
+
56
67
  /**
57
- * Generate or retrieve a persistent client ID
68
+ * Generate or retrieve a tab-specific client ID
69
+ * Uses sessionStorage (unique per tab) not localStorage (shared across tabs)
58
70
  */
59
71
  generateClientId() {
60
72
  let id = null;
61
73
 
62
74
  try {
63
- id = localStorage.getItem('livesync-client-id');
75
+ id = sessionStorage.getItem('livesync-client-id');
64
76
  } catch (e) {
65
- // localStorage might not be available
77
+ // sessionStorage might not be available
66
78
  }
67
79
 
68
80
  if (!id) {
69
81
  id = Math.random().toString(36).slice(2, 11) + Date.now().toString(36);
70
82
  try {
71
- localStorage.setItem('livesync-client-id', id);
83
+ sessionStorage.setItem('livesync-client-id', id);
72
84
  } catch (e) {
73
85
  // That's okay
74
86
  }
@@ -98,8 +110,7 @@ class LiveSync {
98
110
  }
99
111
 
100
112
  // Reset state for new connection
101
- this.lastHeadHash = null;
102
- this.lastBodyHtml = null;
113
+ this.lastHtml = null;
103
114
 
104
115
  console.log('[LiveSync] Starting for:', this.currentFile);
105
116
  this.connect();
@@ -186,32 +197,23 @@ class LiveSync {
186
197
  return;
187
198
  }
188
199
 
189
- const { body, headHash, sender } = data;
200
+ const { html, sender } = data;
190
201
 
191
202
  // Ignore own changes
192
- if (sender === this.clientId) return;
193
-
194
- // Guard against invalid body - never apply non-string
195
- if (typeof body !== 'string') {
196
- console.error('[LiveSync] Received invalid body (not a string), ignoring');
203
+ if (sender === this.clientId) {
204
+ this._log('Ignoring own message (sender matches clientId)');
197
205
  return;
198
206
  }
199
207
 
200
- // Check for head changes -> full reload
201
- // Only compare when BOTH hashes exist (server must send headHash)
202
- // Only set lastHeadHash when incoming hash is valid
203
- if (headHash) {
204
- if (this.lastHeadHash && headHash !== this.lastHeadHash) {
205
- console.log('[LiveSync] Head changed, reloading');
206
- location.reload();
207
- return;
208
- }
209
- this.lastHeadHash = headHash;
208
+ // Guard against invalid html
209
+ if (typeof html !== 'string') {
210
+ console.error('[LiveSync] Received invalid html, ignoring');
211
+ return;
210
212
  }
211
213
 
212
- console.log('[LiveSync] Received update from:', sender);
213
- this.applyUpdate(body);
214
- if (this.onUpdate) this.onUpdate({ body, sender });
214
+ this._log(`Received update from: ${sender} (my clientId: ${this.clientId})`);
215
+ this.applyUpdate(html);
216
+ if (this.onUpdate) this.onUpdate({ html, sender });
215
217
  };
216
218
 
217
219
  // Native EventSource auto-reconnects on transient errors
@@ -229,151 +231,90 @@ class LiveSync {
229
231
 
230
232
  /**
231
233
  * Listen for snapshot-ready events from the save system.
232
- * Receives the full cloned documentElement and extracts head/body.
234
+ * Receives the full cloned documentElement and sends it.
233
235
  */
234
236
  listenForSnapshots() {
235
- this._snapshotHandler = async (event) => {
236
- if (this.isPaused) return;
237
+ this._snapshotHandler = (event) => {
238
+ if (this.isPaused) {
239
+ this._log('snapshot-ready received but isPaused, skipping');
240
+ return;
241
+ }
237
242
 
238
243
  const { documentElement } = event.detail;
239
244
  if (!documentElement) return;
240
245
 
241
- // Extract head and body directly from cloned element
242
- const head = documentElement.querySelector('head')?.innerHTML || '';
243
- const body = documentElement.querySelector('body')?.innerHTML || '';
244
-
245
- // Compute headHash using SHA-256 (async)
246
- const headHash = await this.computeHeadHash(head);
247
-
248
- // Send update even if body is empty (allows clearing content)
249
- this.sendUpdate(body, headHash);
246
+ this._log('snapshot-ready received, preparing to send');
247
+ const html = documentElement.outerHTML;
248
+ this.sendUpdate(html);
250
249
  };
251
250
 
252
251
  document.addEventListener('hyperclay:snapshot-ready', this._snapshotHandler);
253
252
  }
254
253
 
255
254
  /**
256
- * Send body and headHash to the server (debounced)
257
- * Only updates lastBodyHtml after successful save
255
+ * Send full HTML to the server (debounced)
256
+ * Only updates lastHtml after successful save
258
257
  */
259
- sendUpdate(body, headHash) {
258
+ sendUpdate(html) {
260
259
  clearTimeout(this.debounceTimer);
261
260
 
262
261
  this.debounceTimer = setTimeout(() => {
263
262
  // Skip if unchanged
264
- if (body === this.lastBodyHtml) return;
265
-
266
- console.log('[LiveSync] Sending update');
263
+ if (html === this.lastHtml) {
264
+ this._log('Skipping send - HTML unchanged');
265
+ return;
266
+ }
267
267
 
268
- // Track local head changes
269
- this.lastHeadHash = headHash;
268
+ this._log(`Sending update (HTML length: ${html.length}, lastHtml length: ${this.lastHtml?.length || 0})`);
270
269
 
271
270
  fetch('/live-sync/save', {
272
271
  method: 'POST',
273
272
  headers: { 'Content-Type': 'application/json' },
274
273
  body: JSON.stringify({
275
274
  file: this.currentFile,
276
- body: body,
277
- sender: this.clientId,
278
- headHash: headHash
275
+ html: html,
276
+ sender: this.clientId
279
277
  })
280
278
  }).then(response => {
281
279
  if (response.ok) {
282
- // Only update lastBodyHtml after successful save
283
- this.lastBodyHtml = body;
280
+ this.lastHtml = html;
284
281
  } else {
285
- // Log non-OK responses but don't suppress future sends
286
282
  console.warn('[LiveSync] Save returned status:', response.status);
287
283
  }
288
284
  }).catch(err => {
289
- // Network error - don't update lastBodyHtml so next mutation will retry
290
285
  console.error('[LiveSync] Save failed:', err);
291
286
  if (this.onError) this.onError(err);
292
287
  });
293
288
  }, this.debounceMs);
294
289
  }
295
290
 
296
- /**
297
- * Compute SHA-256 hash of head content (first 16 hex chars)
298
- * Uses SubtleCrypto API (async)
299
- * @param {string} head - Head innerHTML
300
- * @returns {Promise<string|null>} 16-char hex hash or null if unavailable
301
- */
302
- async computeHeadHash(head) {
303
- if (!head) return null;
304
-
305
- // SubtleCrypto requires secure context (HTTPS or localhost)
306
- if (!crypto?.subtle?.digest) {
307
- console.warn('[LiveSync] SHA-256 unavailable (non-secure context), skipping headHash');
308
- return null;
309
- }
310
-
311
- try {
312
- const encoder = new TextEncoder();
313
- const data = encoder.encode(head);
314
- const hashBuffer = await crypto.subtle.digest('SHA-256', data);
315
- const hashArray = Array.from(new Uint8Array(hashBuffer));
316
- return hashArray.map(b => b.toString(16).padStart(2, '0')).join('').slice(0, 16);
317
- } catch (e) {
318
- console.warn('[LiveSync] SHA-256 hash failed, skipping headHash:', e);
319
- return null;
320
- }
321
- }
322
-
323
291
  /**
324
292
  * Apply an update received from the server
325
- * Guards against non-string values
293
+ * Morphs the entire document (head and body)
326
294
  */
327
- applyUpdate(bodyHtml) {
328
- // Guard against non-string values
329
- if (typeof bodyHtml !== 'string') {
330
- console.error('[LiveSync] applyUpdate called with non-string value, ignoring');
331
- return;
332
- }
333
-
295
+ applyUpdate(html) {
296
+ this._log('applyUpdate - pausing mutations and morphing');
334
297
  this.isPaused = true;
335
- this.lastBodyHtml = bodyHtml;
298
+ this.lastHtml = html;
336
299
 
337
- try {
338
- const temp = document.createElement('div');
339
- temp.innerHTML = bodyHtml;
300
+ // Pause mutation observer so morph doesn't trigger autosave
301
+ Mutation.pause();
340
302
 
341
- HyperMorph.morph(document.body, temp, {
342
- morphStyle: 'innerHTML',
343
- ignoreActiveValue: true
344
- });
303
+ // Parse as full document
304
+ const parser = new DOMParser();
305
+ const newDoc = parser.parseFromString(html, 'text/html');
345
306
 
346
- this.rehydrateFormState(document.body);
347
- } finally {
348
- this.isPaused = false;
349
- }
350
- }
351
-
352
- /**
353
- * Sync form control attributes to properties after DOM morph
354
- */
355
- rehydrateFormState(container) {
356
- const focused = document.activeElement;
357
-
358
- // Text inputs and textareas
359
- container.querySelectorAll('input[value], textarea[value]').forEach(el => {
360
- if (el === focused) return;
361
- el.value = el.getAttribute('value') || '';
307
+ // Morph entire document (html element)
308
+ HyperMorph.morph(document.documentElement, newDoc.documentElement, {
309
+ morphStyle: 'outerHTML',
310
+ ignoreActiveValue: true,
311
+ head: { style: 'merge' },
312
+ scripts: { handle: true, matchMode: 'smart' }
362
313
  });
363
314
 
364
- // Checkboxes and radios
365
- container.querySelectorAll('input[type="checkbox"], input[type="radio"]').forEach(el => {
366
- if (el === focused) return;
367
- el.checked = el.hasAttribute('checked');
368
- });
369
-
370
- // Select dropdowns
371
- container.querySelectorAll('select').forEach(select => {
372
- if (select === focused) return;
373
- select.querySelectorAll('option').forEach(opt => {
374
- opt.selected = opt.hasAttribute('selected');
375
- });
376
- });
315
+ this._log('applyUpdate - morph complete, resuming mutations');
316
+ Mutation.resume();
317
+ this.isPaused = false;
377
318
  }
378
319
 
379
320
  /**
@@ -27,9 +27,23 @@ function initSavePageOnChange() {
27
27
  });
28
28
  }
29
29
 
30
+ /**
31
+ * Initialize auto-save on input events for [persist] elements
32
+ * Form input values don't trigger DOM mutations, so we listen for input events
33
+ */
34
+ let inputSaveTimer = null;
35
+ function initSaveOnPersistInput() {
36
+ document.addEventListener('input', (e) => {
37
+ if (!e.target.closest('[persist]')) return;
38
+ clearTimeout(inputSaveTimer);
39
+ inputSaveTimer = setTimeout(savePageThrottled, 3333);
40
+ }, true);
41
+ }
42
+
30
43
  function init() {
31
44
  if (!isEditMode) return;
32
45
  initSavePageOnChange();
46
+ initSaveOnPersistInput();
33
47
  }
34
48
 
35
49
  // No window exports - savePageThrottled is exported from save-system
@@ -1,15 +1,56 @@
1
1
  import { insertStyles } from '../dom-utilities/insertStyleTag.js';
2
2
  import cookie from '../utilities/cookie.js';
3
3
 
4
+ function findTailwindLink(resourceName) {
5
+ const targetPath = `/tailwindcss/${resourceName}.css`;
6
+ return [...document.querySelectorAll('link[rel="stylesheet"]')]
7
+ .find(el => {
8
+ try {
9
+ const url = new URL(el.getAttribute('href'), location.href);
10
+ return url.pathname === targetPath;
11
+ } catch {
12
+ return false;
13
+ }
14
+ });
15
+ }
16
+
17
+ function swapTailwindLink() {
18
+ const currentResource = cookie.get('currentResource');
19
+ if (!currentResource) return;
20
+
21
+ const oldLink = findTailwindLink(currentResource);
22
+ if (!oldLink) return;
23
+
24
+ const newLink = document.createElement('link');
25
+ newLink.rel = 'stylesheet';
26
+ const url = new URL(oldLink.getAttribute('href'), location.href);
27
+ url.searchParams.set('v', Date.now());
28
+ newLink.href = url.href;
29
+ newLink.setAttribute('save-ignore', '');
30
+
31
+ oldLink.insertAdjacentElement('afterend', newLink);
32
+
33
+ newLink.onload = () => {
34
+ oldLink.remove();
35
+ };
36
+
37
+ setTimeout(() => {
38
+ if (oldLink.parentNode) {
39
+ oldLink.remove();
40
+ }
41
+ }, 2000);
42
+ }
43
+
4
44
  function init() {
5
45
  const currentResource = cookie.get('currentResource');
6
46
  if (!currentResource) return;
7
47
 
8
48
  const href = `/tailwindcss/${currentResource}.css`;
9
49
  insertStyles(href, (link) => {
10
- link.setAttribute('onaftersave', 'cacheBust(this)');
11
- link.setAttribute('mutations-ignore', '');
50
+ link.setAttribute('save-ignore', '');
12
51
  });
52
+
53
+ document.addEventListener('hyperclay:save-saved', swapTailwindLink);
13
54
  }
14
55
 
15
56
  init();
@@ -62,14 +62,24 @@ function insertStyles(nameOrHref, cssOrCallback, callback) {
62
62
  const href = nameOrHref;
63
63
  const cb = typeof cssOrCallback === 'function' ? cssOrCallback : undefined;
64
64
 
65
- // Normalize href to full URL for comparison
66
- const normalizedHref = new URL(href, window.location.href).href;
65
+ // Helper to get base URL without query params (for comparison)
66
+ const getBaseUrl = (url) => {
67
+ try {
68
+ const parsed = new URL(url, window.location.href);
69
+ return parsed.origin + parsed.pathname;
70
+ } catch {
71
+ return url;
72
+ }
73
+ };
74
+
75
+ // Normalize href to full URL path (without query params) for comparison
76
+ const normalizedHref = getBaseUrl(href);
67
77
 
68
- // Find all links with matching normalized path
78
+ // Find all links with matching normalized path (ignoring query params like ?v=)
69
79
  const existingLinks = [...document.querySelectorAll('link[rel="stylesheet"]')]
70
80
  .filter(el => {
71
81
  try {
72
- return new URL(el.getAttribute('href'), window.location.href).href === normalizedHref;
82
+ return getBaseUrl(el.getAttribute('href')) === normalizedHref;
73
83
  } catch {
74
84
  return false;
75
85
  }
package/src/hyperclay.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * HyperclayJS v1.16.0 - Minimal Browser-Native Loader
2
+ * HyperclayJS v1.17.0 - Minimal Browser-Native Loader
3
3
  *
4
4
  * Modules auto-init when imported (no separate init call needed).
5
5
  * Include `export-to-window` feature to export to window.hyperclay.
@@ -123,7 +123,6 @@ const PRESETS = {
123
123
  "onaftersave",
124
124
  "dialogs",
125
125
  "toast",
126
- "toast-hyperclay",
127
126
  "the-modal",
128
127
  "mutation",
129
128
  "nearest",
@@ -288,3 +287,37 @@ export const uploadFile = window.hyperclayModules['file-upload']?.uploadFile ??
288
287
  export const createFile = window.hyperclayModules['file-upload']?.createFile ?? window.hyperclayModules['file-upload']?.default;
289
288
  export const uploadFileBasic = window.hyperclayModules['file-upload']?.uploadFileBasic ?? window.hyperclayModules['file-upload']?.default;
290
289
  export const liveSync = window.hyperclayModules['live-sync']?.liveSync ?? window.hyperclayModules['live-sync']?.default;
290
+
291
+ /**
292
+ * Enable debug logging across all hyperclay modules that support it.
293
+ * @param {boolean} [enabled=true] - Whether to enable or disable debug logging
294
+ */
295
+ export function setDebug(enabled = true) {
296
+ const modules = [
297
+ { name: 'Mutation', obj: window.hyperclayModules['mutation']?.default },
298
+ { name: 'LiveSync', obj: window.hyperclayModules['live-sync']?.liveSync },
299
+ { name: 'OptionVisibility', obj: window.hyperclayModules['option-visibility']?.default },
300
+ ];
301
+
302
+ const enabledModules = [];
303
+ for (const { name, obj } of modules) {
304
+ if (obj && 'debug' in obj) {
305
+ obj.debug = enabled;
306
+ enabledModules.push(name);
307
+ }
308
+ }
309
+
310
+ console.log(`[hyperclay] Debug ${enabled ? 'enabled' : 'disabled'} for:`, enabledModules.join(', ') || 'no modules found');
311
+ }
312
+
313
+ // Export debug to window.hyperclay
314
+ if (!window.__hyperclayNoAutoExport) {
315
+ window.hyperclay = window.hyperclay || {};
316
+ window.hyperclay.debug = setDebug;
317
+ window.h = window.hyperclay;
318
+ }
319
+
320
+ // Auto-enable debug for all modules if ?debug=true was passed
321
+ if (debug) {
322
+ setDebug(true);
323
+ }
@@ -1,22 +1,30 @@
1
1
  /**
2
- * Toast Hyperclay - Toast with Hyperclay platform styling
2
+ * Toast Hyperclay - Configure toast() to use Hyperclay platform styling
3
3
  *
4
- * Provides toastHyperclay() function with Hyperclay platform styling.
5
- * Use this alongside toast() if you need both styles in the same project.
4
+ * When this module is loaded, it overrides the default toast styling so that
5
+ * all toast() calls (including from save-toast) use Hyperclay styling.
6
6
  *
7
- * This is a hidden feature not exposed in the UI - used internally by
8
- * the Hyperclay platform for backward compatibility.
7
+ * Also provides toastHyperclay() for backward compatibility.
9
8
  */
10
9
 
11
10
  import {
12
11
  toastCore,
13
12
  injectToastStyles,
13
+ setToastTheme,
14
14
  hyperclayStyles,
15
15
  hyperclayTemplates,
16
16
  hyperclayIcons
17
17
  } from './toast.js';
18
18
 
19
- // Toast function with Hyperclay styling
19
+ // Configure the base toast() to use hyperclay styling
20
+ setToastTheme({
21
+ styles: hyperclayStyles,
22
+ templates: hyperclayTemplates,
23
+ icons: hyperclayIcons,
24
+ theme: 'hyperclay'
25
+ });
26
+
27
+ // Toast function with Hyperclay styling (kept for backward compatibility)
20
28
  function toastHyperclay(message, messageType = "success") {
21
29
  injectToastStyles(hyperclayStyles, 'hyperclay');
22
30
  toastCore(message, messageType, {
package/src/ui/toast.js CHANGED
@@ -185,6 +185,19 @@ export const hyperclayStyles = `
185
185
  // Track which theme styles have been injected
186
186
  const injectedThemes = new Set();
187
187
 
188
+ // Global toast configuration (can be overridden by toast-hyperclay module)
189
+ let toastConfig = {
190
+ styles: modernStyles,
191
+ templates: defaultTemplates,
192
+ icons: defaultIcons,
193
+ theme: 'modern'
194
+ };
195
+
196
+ // Allow other modules (like toast-hyperclay) to override default toast styling
197
+ export function setToastTheme(config) {
198
+ Object.assign(toastConfig, config);
199
+ }
200
+
188
201
  // Helper function to inject styles for a theme (additive, not replacing)
189
202
  export function injectToastStyles(styles, theme) {
190
203
  if (injectedThemes.has(theme)) return;
@@ -242,13 +255,13 @@ export function toastCore(message, messageType = "success", config = {}) {
242
255
  }, 6600);
243
256
  }
244
257
 
245
- // Main toast function - uses modern styles
258
+ // Main toast function - uses configured theme (default: modern, or hyperclay if toast-hyperclay loaded)
246
259
  function toast(message, messageType = "success") {
247
- injectToastStyles(modernStyles, 'modern');
260
+ injectToastStyles(toastConfig.styles, toastConfig.theme);
248
261
  toastCore(message, messageType, {
249
- templates: defaultTemplates,
250
- icons: defaultIcons,
251
- theme: 'modern'
262
+ templates: toastConfig.templates,
263
+ icons: toastConfig.icons,
264
+ theme: toastConfig.theme
252
265
  });
253
266
  }
254
267
 
@@ -67,8 +67,27 @@ const Mutation = {
67
67
  },
68
68
 
69
69
  _observing: false,
70
+ _paused: false,
70
71
  debug: false,
71
72
 
73
+ /**
74
+ * Pause mutation observation.
75
+ * Use this when making programmatic DOM changes that shouldn't trigger callbacks.
76
+ * Always pair with resume() in a try/finally block.
77
+ */
78
+ pause() {
79
+ this._paused = true;
80
+ this._log('Paused');
81
+ },
82
+
83
+ /**
84
+ * Resume mutation observation after a pause.
85
+ */
86
+ resume() {
87
+ this._paused = false;
88
+ this._log('Resumed');
89
+ },
90
+
72
91
  _log(message, data = null, type = 'log') {
73
92
  if (!this.debug) return;
74
93
 
@@ -173,7 +192,10 @@ const Mutation = {
173
192
  }
174
193
  },
175
194
 
176
- _shouldIgnore(element) {
195
+ _shouldIgnore(node) {
196
+ // For non-element nodes (like text nodes), start from parent
197
+ let element = (node && node.nodeType !== 1) ? node.parentElement : node;
198
+
177
199
  while (element && element.nodeType === 1) {
178
200
  if (element.hasAttribute?.('mutations-ignore') ||
179
201
  element.hasAttribute?.('save-remove') ||
@@ -186,8 +208,12 @@ const Mutation = {
186
208
  },
187
209
 
188
210
  _handleMutations(mutations) {
189
- this._log(`Processing ${mutations.length} mutations`, { mutations });
190
-
211
+ // Skip all mutations while paused (e.g., during live-sync morph)
212
+ if (this._paused) {
213
+ this._log(`Skipping ${mutations.length} mutations (paused)`);
214
+ return;
215
+ }
216
+
191
217
  const changes = [];
192
218
  const changesByType = {
193
219
  add: [],
@@ -199,7 +225,6 @@ const Mutation = {
199
225
  for (const mutation of mutations) {
200
226
  // Check if the target or any parent has mutations-ignore attribute
201
227
  if (this._shouldIgnore(mutation.target)) {
202
- this._log('Ignoring mutation due to mutations-ignore attribute', { mutation });
203
228
  continue;
204
229
  }
205
230
 
@@ -246,7 +271,7 @@ const Mutation = {
246
271
  }
247
272
 
248
273
  for (const node of mutation.removedNodes) {
249
- if (node.nodeType === 1 && !node.hasAttribute?.('save-remove') && !node.hasAttribute?.('save-ignore') && !node.hasAttribute?.('mutations-ignore')) {
274
+ if (node.nodeType === 1 && !this._shouldIgnore(node)) {
250
275
  const removedNodes = [node, ...node.querySelectorAll('*')];
251
276
  this._log(`Processing ${removedNodes.length} removed nodes`, { removedNodes });
252
277
 
@@ -311,8 +336,6 @@ const Mutation = {
311
336
  if (changesByType.attribute.length) {
312
337
  this._notify('attribute', changesByType.attribute);
313
338
  }
314
- } else {
315
- this._log('No changes to process after filtering');
316
339
  }
317
340
  },
318
341
 
@@ -1,4 +1,4 @@
1
- var HyperMorph=(()=>{var q=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var U=Object.getOwnPropertyNames;var G=Object.prototype.hasOwnProperty;var Y=(s,a)=>{for(var u in a)q(s,u,{get:a[u],enumerable:!0})},J=(s,a,u,d)=>{if(a&&typeof a=="object"||typeof a=="function")for(let f of U(a))!G.call(s,f)&&f!==u&&q(s,f,{get:()=>a[f],enumerable:!(d=z(a,f))||d.enumerable});return s};var K=s=>J(q({},"__esModule",{value:!0}),s);var he={};Y(he,{HyperMorph:()=>V,default:()=>fe,defaults:()=>de,morph:()=>ce});var B={includeClasses:!0,includeAttributes:["href","src","name","type","role","aria-label","alt","title"],excludeAttributePrefixes:["data-morph-","data-hyper-","data-im-"],textHintLength:64,excludeIds:!0,maxPathDepth:4,landmarks:["HEADER","NAV","MAIN","ASIDE","FOOTER","SECTION","ARTICLE"],weights:{signature:100,pathSegment:10,textMatch:20,textMismatch:25,uniqueCandidate:50,positionPenalty:1},minConfidence:101};function Q(s){let a=5381;for(let u=0;u<s.length;u++)a=(a<<5)+a^s.charCodeAt(u);return Math.abs(a).toString(36)}function X(s){if(s.classList&&s.classList.length>0)return Array.from(s.classList).sort().join(" ");let a=s.getAttribute?.("class");return a?a.split(/\s+/).filter(Boolean).sort().join(" "):""}function Z(s,a){let u=[];for(let d of s.attributes||[]){let f=d.name;f==="id"||f==="class"||a.excludeAttributePrefixes.some(y=>f.startsWith(y))||a.includeAttributes.includes(f)&&u.push(`${f}=${d.value}`)}return u.sort().join("|")}function ee(s,a){return(s.textContent||"").replace(/\s+/g," ").trim().slice(0,a.textHintLength)}function te(s,a){let u=[s.tagName];return a.includeClasses&&u.push(X(s)),u.push(Z(s,a)),Q(u.join("|"))}function ne(s){let a=s.tagName,u=1,d=s.previousElementSibling;for(;d;)d.tagName===a&&u++,d=d.previousElementSibling;return u}function re(s,a){return s.id||s.getAttribute?.("role")?!0:a.landmarks.includes(s.tagName)}function se(s){if(s.id)return`#${s.id}`;let a=s.getAttribute?.("role");return a?`@${a}`:s.tagName}function ie(s,a){let u=[],d=s;for(;d&&d.tagName&&u.length<a.maxPathDepth;){let f=`${d.tagName}:${ne(d)}`;if(u.unshift(f),d!==s&&re(d,a)){u.unshift(se(d));break}d=d.parentElement}return u}function ae(s,a){let u=0,d=s.length-1,f=a.length-1;for(;d>=0&&f>=0&&s[d]===a[f];)u++,d--,f--;return u}function R(s,a,u){if(u.has(s))return u.get(s);let d={signature:te(s,a),path:ie(s,a),textHint:ee(s,a)};return u.set(s,d),d}function P(s,a,u,d){if(d.has(s))return d.get(s);let f=new Map,y=s.querySelectorAll("*"),H=0;for(let b of y){let k=R(b,a,u);k.domIndex=H++,f.has(k.signature)||f.set(k.signature,[]),f.get(k.signature).push(b)}return d.set(s,f),f}function oe(s,a,u){u.delete(s),a.delete(s);let d=s.querySelectorAll("*");for(let f of d)a.delete(f)}function F(s,a,u,d,f){let y=R(s,u,d),H=R(a,u,d),b=u.weights,k={},L=0;if(y.signature!==H.signature)return{score:0,breakdown:{rejected:"signature mismatch"}};L+=b.signature,k.signature=b.signature;let I=ae(y.path,H.path)*b.pathSegment;L+=I,k.path=I;let E=!0;if(y.textHint&&H.textHint?y.textHint===H.textHint?(L+=b.textMatch,k.text=b.textMatch):(L-=b.textMismatch,k.text=-b.textMismatch,E=!1):y.textHint!==H.textHint&&(L-=b.textMismatch,k.text=-b.textMismatch,E=!1),f.candidateCount===1&&E&&(L+=b.uniqueCandidate,k.unique=b.uniqueCandidate),typeof y.domIndex=="number"&&typeof H.domIndex=="number"){let x=Math.abs(y.domIndex-H.domIndex),c=Math.min(x*b.positionPenalty,20);L-=c,k.drift=-c}return{score:L,breakdown:k}}function D(s,a,u,d,f){if(u.excludeIds&&s.id)return null;let y=P(a,u,d,f),H=R(s,u,d);if(typeof H.domIndex!="number"){let E=0,x=s.previousElementSibling;for(;x;)E++,x=x.previousElementSibling;H.domIndex=E}let b=y.get(H.signature)||[],k=u.excludeIds?b.filter(E=>!E.id):b;if(k.length===0)return null;let L=null,N=0,I=null;for(let E of k){let{score:x,breakdown:c}=F(s,E,u,d,{candidateCount:k.length});x>N&&(N=x,L=E,I=c)}return N<u.minConfidence?null:{element:L,confidence:N,breakdown:I}}function $(s,a,u,d,f){let y=a.querySelectorAll("*"),H=P(s,u,d,f),b=0;for(let I of y){let E=R(I,u,d);E.domIndex=b++}let k=[];for(let I of y){if(u.excludeIds&&I.id)continue;let E=R(I,u,d),x=H.get(E.signature)||[],c=u.excludeIds?x.filter(S=>!S.id):x;for(let S of c){let{score:p,breakdown:M}=F(I,S,u,d,{candidateCount:c.length});p>=u.minConfidence&&k.push({newEl:I,oldEl:S,score:p,breakdown:M})}}k.sort((I,E)=>E.score-I.score);let L=new Map,N=new Set;for(let{newEl:I,oldEl:E}of k)L.has(I)||N.has(E)||(L.set(I,E),N.add(E));return L}function W(s,a,u,d){let f=R(s,u,d),y=R(a,u,d),{score:H,breakdown:b}=F(s,a,u,d,{candidateCount:1});return{matches:H>=u.minConfidence,score:H,breakdown:b,newMeta:{signature:f.signature,path:f.path,textHint:f.textHint},oldMeta:{signature:y.signature,path:y.path,textHint:y.textHint}}}function C(s={}){let a={...B,...s,weights:{...B.weights,...s.weights}},u=new WeakMap,d=new WeakMap;return{findMatch:(f,y)=>D(f,y,a,u,d),computeMatches:(f,y)=>$(f,y,a,u,d),explain:(f,y)=>W(f,y,a,u),invalidate:f=>oe(f,u,d),session:()=>{let f=new WeakMap,y=new WeakMap;return{findMatch:(H,b)=>D(H,b,a,f,y),computeMatches:(H,b)=>$(H,b,a,f,y),explain:(H,b)=>W(H,b,a,f)}},getConfig:()=>({...a})}}var ue={createMatcher:C,DEFAULT_CONFIG:B};typeof exports<"u"&&(typeof module<"u"&&module.exports&&(module.exports=ue),exports.createMatcher=C,exports.DEFAULT_CONFIG=B);var le=C(),V=(function(){"use strict";let s=()=>{},a={morphStyle:"outerHTML",callbacks:{beforeNodeAdded:s,afterNodeAdded:s,beforeNodeMorphed:s,afterNodeMorphed:s,beforeNodeRemoved:s,afterNodeRemoved:s,beforeAttributeUpdated:s},head:{style:"merge",shouldPreserve:c=>c.getAttribute("im-preserve")==="true",shouldReAppend:c=>c.getAttribute("im-re-append")==="true",shouldRemove:s,afterHeadMorphed:s},scripts:{handle:!1,shouldPreserve:c=>c.getAttribute("im-preserve")==="true",shouldReAppend:c=>c.getAttribute("im-re-append")==="true",shouldRemove:s,afterScriptsHandled:s},restoreFocus:!0},u={computeMatches(c,S){let{computeMatches:p}=le.session();return p(c,S)}};function d(c,S,p={}){c=E(c);let M=x(S),v=I(c,M,p),m=new Set(Array.from(c.querySelectorAll("script")).map(e=>e.outerHTML)),n=y(v,()=>k(v,c,M,e=>e.morphStyle==="innerHTML"?(H(e,c,M),Array.from(c.childNodes)):f(e,c,M)));v.pantry.remove();let o=N(c,m,v);return o.length>0?n instanceof Promise?n.then(e=>Promise.all(o).then(()=>e)):Promise.all(o).then(()=>n):n}function f(c,S,p){let M=x(S);return H(c,M,p,S,S.nextSibling),Array.from(M.childNodes)}function y(c,S){if(!c.config.restoreFocus)return S();let p=document.activeElement;if(!(p instanceof HTMLInputElement||p instanceof HTMLTextAreaElement))return S();let{id:M,selectionStart:v,selectionEnd:m}=p,n=S();return M&&M!==document.activeElement?.getAttribute("id")&&(p=c.target.querySelector(`[id="${M}"]`),p?.focus()),p&&!p.selectionEnd&&m!=null&&p.setSelectionRange(v,m),n}let H=(function(){function c(e,t,i,r=null,l=null){t instanceof HTMLTemplateElement&&i instanceof HTMLTemplateElement&&(t=t.content,i=i.content),r||=t.firstChild;for(let h of i.childNodes){if(r&&r!=l){let A=p(e,h,r,l);if(A){A!==r&&v(e,r,A),b(A,h,e),r=A.nextSibling;continue}}if(h instanceof Element){let A=h.getAttribute("id");if(e.persistentIds.has(A)){let T=m(t,A,r,e);b(T,h,e),r=T.nextSibling;continue}if(!e.idMap.has(h)){let T=e.hyperMatches.get(h);if(T&&!e.idMap.has(T)){o(t,T,r),b(T,h,e),r=T.nextSibling;continue}}}let g=S(t,h,r,e);g&&(r=g.nextSibling)}for(;r&&r!=l;){let h=r;r=r.nextSibling,M(e,h)}}function S(e,t,i,r){if(r.callbacks.beforeNodeAdded(t)===!1)return null;if(r.idMap.has(t)){let l=document.createElement(t.tagName);return e.insertBefore(l,i),b(l,t,r),r.callbacks.afterNodeAdded(l),l}else{let l=document.importNode(t,!0);return e.insertBefore(l,i),r.callbacks.afterNodeAdded(l),l}}let p=(function(){function e(r,l,h,g){let A=l instanceof Element&&!r.idMap.has(l)?r.hyperMatches.get(l):null,T=null,O=l.nextSibling,j=0,w=h;for(;w&&w!=g;){if(i(w,l)){if(t(r,w,l)||w===A&&!r.idMap.has(w))return w;if(T===null){let _=w instanceof Element&&r.hyperMatchedOldElements.has(w);!r.idMap.has(w)&&!_&&(T=w)}}if(T===null&&O&&i(w,O)&&(j++,O=O.nextSibling,j>=2&&(T=void 0)),r.activeElementAndParents.includes(w))break;w=w.nextSibling}return T||null}function t(r,l,h){let g=r.idMap.get(l),A=r.idMap.get(h);if(!A||!g)return!1;for(let T of g)if(A.has(T))return!0;return!1}function i(r,l){let h=r,g=l;return h.nodeType===g.nodeType&&h.tagName===g.tagName&&(!h.getAttribute?.("id")||h.getAttribute?.("id")===g.getAttribute?.("id"))}return e})();function M(e,t){let i=t instanceof Element&&e.hyperMatchedOldElements.has(t)&&!e.idMap.has(t);if(e.idMap.has(t)||i)o(e.pantry,t,null);else{if(e.callbacks.beforeNodeRemoved(t)===!1)return;t.parentNode?.removeChild(t),e.callbacks.afterNodeRemoved(t)}}function v(e,t,i){let r=t;for(;r&&r!==i;){let l=r;r=r.nextSibling,M(e,l)}return r}function m(e,t,i,r){let l=r.target.getAttribute?.("id")===t&&r.target||r.target.querySelector(`[id="${t}"]`)||r.pantry.querySelector(`[id="${t}"]`);return n(l,r),o(e,l,i),l}function n(e,t){let i=e.getAttribute("id");for(;e=e.parentNode;){let r=t.idMap.get(e);r&&(r.delete(i),r.size||t.idMap.delete(e))}}function o(e,t,i){if(e.moveBefore)try{e.moveBefore(t,i)}catch{e.insertBefore(t,i)}else e.insertBefore(t,i)}return c})(),b=(function(){function c(n,o,e){return e.ignoreActive&&n===document.activeElement?null:(e.callbacks.beforeNodeMorphed(n,o)===!1||(n instanceof HTMLHeadElement&&e.head.ignore||(n instanceof HTMLHeadElement&&e.head.style!=="morph"?L(n,o,e):(S(n,o,e),m(n,e)||H(e,n,o))),e.callbacks.afterNodeMorphed(n,o)),n)}function S(n,o,e){let t=o.nodeType;if(t===1){let i=n,r=o,l=i.attributes,h=r.attributes;for(let g of h)v(g.name,i,"update",e)||i.getAttribute(g.name)!==g.value&&i.setAttribute(g.name,g.value);for(let g=l.length-1;0<=g;g--){let A=l[g];if(A&&!r.hasAttribute(A.name)){if(v(A.name,i,"remove",e))continue;i.removeAttribute(A.name)}}m(i,e)||p(i,r,e)}(t===8||t===3)&&n.nodeValue!==o.nodeValue&&(n.nodeValue=o.nodeValue)}function p(n,o,e){if(n instanceof HTMLInputElement&&o instanceof HTMLInputElement&&o.type!=="file"){let t=o.value,i=n.value;M(n,o,"checked",e),M(n,o,"disabled",e),o.hasAttribute("value")?i!==t&&(v("value",n,"update",e)||(n.setAttribute("value",t),n.value=t)):v("value",n,"remove",e)||(n.value="",n.removeAttribute("value"))}else if(n instanceof HTMLOptionElement&&o instanceof HTMLOptionElement)M(n,o,"selected",e);else if(n instanceof HTMLTextAreaElement&&o instanceof HTMLTextAreaElement){let t=o.value,i=n.value;if(v("value",n,"update",e))return;t!==i&&(n.value=t),n.firstChild&&n.firstChild.nodeValue!==t&&(n.firstChild.nodeValue=t)}}function M(n,o,e,t){let i=o[e],r=n[e];if(i!==r){let l=v(e,n,"update",t);l||(n[e]=o[e]),i?l||n.setAttribute(e,""):v(e,n,"remove",t)||n.removeAttribute(e)}}function v(n,o,e,t){return n==="value"&&t.ignoreActiveValue&&o===document.activeElement?!0:t.callbacks.beforeAttributeUpdated(n,o,e)===!1}function m(n,o){return!!o.ignoreActiveValue&&n===document.activeElement&&n!==document.body}return c})();function k(c,S,p,M){if(c.head.block){let v=S.querySelector("head"),m=p.querySelector("head");if(v&&m){let n=L(v,m,c);return Promise.all(n).then(()=>{let o=Object.assign(c,{head:{block:!1,ignore:!0}});return M(o)})}}return M(c)}function L(c,S,p){let M=[],v=[],m=[],n=[],o=new Map;for(let t of S.children)o.set(t.outerHTML,t);for(let t of c.children){let i=o.has(t.outerHTML),r=p.head.shouldReAppend(t),l=p.head.shouldPreserve(t);i||l?r?v.push(t):(o.delete(t.outerHTML),m.push(t)):p.head.style==="append"?r&&(v.push(t),n.push(t)):p.head.shouldRemove(t)!==!1&&v.push(t)}n.push(...o.values());let e=[];for(let t of n){let i=document.createRange().createContextualFragment(t.outerHTML).firstChild;if(p.callbacks.beforeNodeAdded(i)!==!1){if("href"in i&&i.href||"src"in i&&i.src){let r,l=new Promise(function(h){r=h});i.addEventListener("load",function(){r()}),e.push(l)}c.appendChild(i),p.callbacks.afterNodeAdded(i),M.push(i)}}for(let t of v)p.callbacks.beforeNodeRemoved(t)!==!1&&(c.removeChild(t),p.callbacks.afterNodeRemoved(t));return p.head.afterHeadMorphed(c,{added:M,kept:m,removed:v}),e}function N(c,S,p){if(!p.scripts.handle)return[];let M=[],v=[],m=[],n=[],o=Array.from(c.querySelectorAll("script"));for(let t of o){let i=t.outerHTML,r=S.has(i),l=p.scripts.shouldPreserve(t),h=p.scripts.shouldReAppend(t);r||l?h?(v.push(t),n.push(t)):m.push(t):n.push(t)}for(let t of S){let i=o.some(r=>r.outerHTML===t)}let e=[];for(let t of n){if(p.callbacks.beforeNodeAdded(t)===!1)continue;let i=document.createRange().createContextualFragment(t.outerHTML).firstChild;if(i.src){let r,l=new Promise(function(h){r=h});i.addEventListener("load",function(){r()}),i.addEventListener("error",function(){r()}),e.push(l)}t.replaceWith(i),p.callbacks.afterNodeAdded(i),M.push(i)}return p.scripts.afterScriptsHandled(c,{added:M,kept:m,removed:v}),e}let I=(function(){function c(e,t,i){let{persistentIds:r,idMap:l}=n(e,t),h=u.computeMatches(e,t),g=new Set;for(let O of h.values())g.add(O);let A=S(i),T=A.morphStyle||"outerHTML";if(!["innerHTML","outerHTML"].includes(T))throw`Do not understand how to morph style ${T}`;return{target:e,newContent:t,config:A,morphStyle:T,ignoreActive:A.ignoreActive,ignoreActiveValue:A.ignoreActiveValue,restoreFocus:A.restoreFocus,idMap:l,persistentIds:r,hyperMatches:h,hyperMatchedOldElements:g,pantry:p(),activeElementAndParents:M(e),callbacks:A.callbacks,head:A.head,scripts:A.scripts}}function S(e){let t=Object.assign({},a);return Object.assign(t,e),t.callbacks=Object.assign({},a.callbacks,e.callbacks),t.head=Object.assign({},a.head,e.head),t.scripts=Object.assign({},a.scripts,e.scripts),t}function p(){let e=document.createElement("div");return e.hidden=!0,document.body.insertAdjacentElement("afterend",e),e}function M(e){let t=[],i=document.activeElement;if(i?.tagName!=="BODY"&&e.contains(i))for(;i&&(t.push(i),i!==e);)i=i.parentElement;return t}function v(e){let t=Array.from(e.querySelectorAll("[id]"));return e.getAttribute?.("id")&&t.push(e),t}function m(e,t,i,r){for(let l of r){let h=l.getAttribute("id");if(t.has(h)){let g=l;for(;g;){let A=e.get(g);if(A==null&&(A=new Set,e.set(g,A)),A.add(h),g===i)break;g=g.parentElement}}}}function n(e,t){let i=v(e),r=v(t),l=o(i,r),h=new Map;m(h,l,e,i);let g=t.__hyperMorphRoot||t;return m(h,l,g,r),{persistentIds:l,idMap:h}}function o(e,t){let i=new Set,r=new Map;for(let{id:h,tagName:g}of e)r.has(h)?i.add(h):r.set(h,g);let l=new Set;for(let{id:h,tagName:g}of t)l.has(h)?i.add(h):r.get(h)===g&&l.add(h);for(let h of i)l.delete(h);return l}return c})(),{normalizeElement:E,normalizeParent:x}=(function(){let c=new WeakSet;function S(m){return m instanceof Document?m.documentElement:m}function p(m){if(m==null)return document.createElement("div");if(typeof m=="string")return p(v(m));if(c.has(m))return m;if(m instanceof Node){if(m.parentNode)return new M(m);{let n=document.createElement("div");return n.append(m),n}}else{let n=document.createElement("div");for(let o of[...m])n.append(o);return n}}class M{constructor(n){this.originalNode=n,this.realParentNode=n.parentNode,this.previousSibling=n.previousSibling,this.nextSibling=n.nextSibling}get childNodes(){let n=[],o=this.previousSibling?this.previousSibling.nextSibling:this.realParentNode.firstChild;for(;o&&o!=this.nextSibling;)n.push(o),o=o.nextSibling;return n}querySelectorAll(n){return this.childNodes.reduce((o,e)=>{if(e instanceof Element){e.matches(n)&&o.push(e);let t=e.querySelectorAll(n);for(let i=0;i<t.length;i++)o.push(t[i])}return o},[])}insertBefore(n,o){return this.realParentNode.insertBefore(n,o)}moveBefore(n,o){return this.realParentNode.moveBefore(n,o)}get __hyperMorphRoot(){return this.originalNode}}function v(m){let n=new DOMParser,o=m.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim,"");if(o.match(/<\/html>/)||o.match(/<\/head>/)||o.match(/<\/body>/)){let e=n.parseFromString(m,"text/html");if(o.match(/<\/html>/))return c.add(e),e;{let t=e.firstChild;return t&&c.add(t),t}}else{let t=n.parseFromString("<body><template>"+m+"</template></body>","text/html").body.querySelector("template").content;return c.add(t),t}}return{normalizeElement:S,normalizeParent:p}})();return{morph:d,defaults:a}})();var ce=V.morph,de=V.defaults,fe=V;return K(he);})();
1
+ var HyperMorph=(()=>{var V=Object.defineProperty;var K=Object.getOwnPropertyDescriptor;var G=Object.getOwnPropertyNames;var Y=Object.prototype.hasOwnProperty;var J=(s,o)=>{for(var u in o)V(s,u,{get:o[u],enumerable:!0})},Q=(s,o,u,d)=>{if(o&&typeof o=="object"||typeof o=="function")for(let h of G(o))!Y.call(s,h)&&h!==u&&V(s,h,{get:()=>o[h],enumerable:!(d=K(o,h))||d.enumerable});return s};var X=s=>Q(V({},"__esModule",{value:!0}),s);var pe={};J(pe,{HyperMorph:()=>$,default:()=>he,defaults:()=>fe,morph:()=>de});var F={includeClasses:!0,includeAttributes:["href","src","name","type","role","aria-label","alt","title"],excludeAttributePrefixes:["data-morph-","data-hyper-","data-im-"],textHintLength:64,excludeIds:!0,maxPathDepth:4,landmarks:["HEADER","NAV","MAIN","ASIDE","FOOTER","SECTION","ARTICLE"],weights:{signature:100,pathSegment:10,textMatch:20,textMismatch:25,uniqueCandidate:50,positionPenalty:1},minConfidence:101};function Z(s){let o=5381;for(let u=0;u<s.length;u++)o=(o<<5)+o^s.charCodeAt(u);return Math.abs(o).toString(36)}function ee(s){if(s.classList&&s.classList.length>0)return Array.from(s.classList).sort().join(" ");let o=s.getAttribute?.("class");return o?o.split(/\s+/).filter(Boolean).sort().join(" "):""}function te(s,o){let u=[];for(let d of s.attributes||[]){let h=d.name;h==="id"||h==="class"||o.excludeAttributePrefixes.some(y=>h.startsWith(y))||o.includeAttributes.includes(h)&&u.push(`${h}=${d.value}`)}return u.sort().join("|")}function ne(s,o){return(s.textContent||"").replace(/\s+/g," ").trim().slice(0,o.textHintLength)}function re(s,o){let u=[s.tagName];return o.includeClasses&&u.push(ee(s)),u.push(te(s,o)),Z(u.join("|"))}function se(s){let o=s.tagName,u=1,d=s.previousElementSibling;for(;d;)d.tagName===o&&u++,d=d.previousElementSibling;return u}function ie(s,o){return s.id||s.getAttribute?.("role")?!0:o.landmarks.includes(s.tagName)}function ae(s){if(s.id)return`#${s.id}`;let o=s.getAttribute?.("role");return o?`@${o}`:s.tagName}function oe(s,o){let u=[],d=s;for(;d&&d.tagName&&u.length<o.maxPathDepth;){let h=`${d.tagName}:${se(d)}`;if(u.unshift(h),d!==s&&ie(d,o)){u.unshift(ae(d));break}d=d.parentElement}return u}function ue(s,o){let u=0,d=s.length-1,h=o.length-1;for(;d>=0&&h>=0&&s[d]===o[h];)u++,d--,h--;return u}function C(s,o,u){if(u.has(s))return u.get(s);let d={signature:re(s,o),path:oe(s,o),textHint:ne(s,o)};return u.set(s,d),d}function z(s,o,u,d){if(d.has(s))return d.get(s);let h=new Map,y=s.querySelectorAll("*"),H=0;for(let S of y){let I=C(S,o,u);I.domIndex=H++,h.has(I.signature)||h.set(I.signature,[]),h.get(I.signature).push(S)}return d.set(s,h),h}function ce(s,o,u){u.delete(s),o.delete(s);let d=s.querySelectorAll("*");for(let h of d)o.delete(h)}function q(s,o,u,d,h){let y=C(s,u,d),H=C(o,u,d),S=u.weights,I={},E=0;if(y.signature!==H.signature)return{score:0,breakdown:{rejected:"signature mismatch"}};E+=S.signature,I.signature=S.signature;let T=ue(y.path,H.path)*S.pathSegment;E+=T,I.path=T;let k=!0;if(y.textHint&&H.textHint?y.textHint===H.textHint?(E+=S.textMatch,I.text=S.textMatch):(E-=S.textMismatch,I.text=-S.textMismatch,k=!1):y.textHint!==H.textHint&&(E-=S.textMismatch,I.text=-S.textMismatch,k=!1),h.candidateCount===1&&k&&(E+=S.uniqueCandidate,I.unique=S.uniqueCandidate),typeof y.domIndex=="number"&&typeof H.domIndex=="number"){let N=Math.abs(y.domIndex-H.domIndex),R=Math.min(N*S.positionPenalty,20);E-=R,I.drift=-R}return{score:E,breakdown:I}}function D(s,o,u,d,h){if(u.excludeIds&&s.id)return null;let y=z(o,u,d,h),H=C(s,u,d);if(typeof H.domIndex!="number"){let k=0,N=s.previousElementSibling;for(;N;)k++,N=N.previousElementSibling;H.domIndex=k}let S=y.get(H.signature)||[],I=u.excludeIds?S.filter(k=>!k.id):S;if(I.length===0)return null;let E=null,x=0,T=null;for(let k of I){let{score:N,breakdown:R}=q(s,k,u,d,{candidateCount:I.length});N>x&&(x=N,E=k,T=R)}return x<u.minConfidence?null:{element:E,confidence:x,breakdown:T}}function W(s,o,u,d,h){let y=o.querySelectorAll("*"),H=z(s,u,d,h),S=0;for(let T of y){let k=C(T,u,d);k.domIndex=S++}let I=[];for(let T of y){if(u.excludeIds&&T.id)continue;let k=C(T,u,d),N=H.get(k.signature)||[],R=u.excludeIds?N.filter(B=>!B.id):N;for(let B of R){let{score:l,breakdown:A}=q(T,B,u,d,{candidateCount:R.length});l>=u.minConfidence&&I.push({newEl:T,oldEl:B,score:l,breakdown:A})}}I.sort((T,k)=>k.score-T.score);let E=new Map,x=new Set;for(let{newEl:T,oldEl:k}of I)E.has(T)||x.has(k)||(E.set(T,k),x.add(k));return E}function P(s,o,u,d){let h=C(s,u,d),y=C(o,u,d),{score:H,breakdown:S}=q(s,o,u,d,{candidateCount:1});return{matches:H>=u.minConfidence,score:H,breakdown:S,newMeta:{signature:h.signature,path:h.path,textHint:h.textHint},oldMeta:{signature:y.signature,path:y.path,textHint:y.textHint}}}function U(s={}){let o={...F,...s,weights:{...F.weights,...s.weights}},u=new WeakMap,d=new WeakMap;return{findMatch:(h,y)=>D(h,y,o,u,d),computeMatches:(h,y)=>W(h,y,o,u,d),explain:(h,y)=>P(h,y,o,u),invalidate:h=>ce(h,u,d),session:()=>{let h=new WeakMap,y=new WeakMap;return{findMatch:(H,S)=>D(H,S,o,h,y),computeMatches:(H,S)=>W(H,S,o,h,y),explain:(H,S)=>P(H,S,o,h)}},getConfig:()=>({...o})}}var le=U(),$=(function(){"use strict";let s=()=>{};function o(l){if(!(l instanceof Element))return!1;if(l.hasAttribute("save-ignore"))return!0;if(l.tagName==="LINK"||l.tagName==="SCRIPT"){let A=l.getAttribute("src")||l.getAttribute("href")||"";if(A.startsWith("chrome-extension://")||A.startsWith("moz-extension://")||A.startsWith("safari-web-extension://"))return!0}return!1}function u(l,A){if(A!=="smart")return l.outerHTML;let p=l.getAttribute("src"),v=l.getAttribute("type")||"text/javascript";if(p)try{let b=new URL(p,window.location.href);return`ext:${v}:${b.origin}${b.pathname}`}catch{return`ext:${v}:${p}`}else{let b=l.textContent.trim(),g=5381;for(let r=0;r<b.length;r++)g=(g<<5)+g^b.charCodeAt(r);return`inline:${v}:${Math.abs(g).toString(36)}`}}let d={morphStyle:"outerHTML",callbacks:{beforeNodeAdded:s,afterNodeAdded:s,beforeNodeMorphed:s,afterNodeMorphed:s,beforeNodeRemoved:s,afterNodeRemoved:s,beforeAttributeUpdated:s},head:{style:"merge",shouldPreserve:l=>l.getAttribute("im-preserve")==="true",shouldReAppend:l=>l.getAttribute("im-re-append")==="true",shouldRemove:s,afterHeadMorphed:s},scripts:{handle:!1,matchMode:"outerHTML",shouldPreserve:l=>l.getAttribute("im-preserve")==="true",shouldReAppend:l=>l.getAttribute("im-re-append")==="true",shouldRemove:s,afterScriptsHandled:s},restoreFocus:!0},h={computeMatches(l,A){let{computeMatches:p}=le.session();return p(l,A)}};function y(l,A,p={}){l=R(l);let v=B(A),b=N(l,v,p),g=b.scripts.matchMode,r=new Set(Array.from(l.querySelectorAll("script")).map(n=>u(n,g))),c=S(b,()=>x(b,l,v,n=>n.morphStyle==="innerHTML"?(I(n,l,v),Array.from(l.childNodes)):H(n,l,v)));b.pantry.remove();let e=k(l,r,b);return e.length>0?c instanceof Promise?c.then(n=>Promise.all(e).then(()=>n)):Promise.all(e).then(()=>c):c}function H(l,A,p){let v=B(A);return I(l,v,p,A,A.nextSibling),Array.from(v.childNodes)}function S(l,A){if(!l.config.restoreFocus)return A();let p=document.activeElement;if(!(p instanceof HTMLInputElement||p instanceof HTMLTextAreaElement))return A();let{id:v,selectionStart:b,selectionEnd:g}=p,r=A();return v&&v!==document.activeElement?.getAttribute("id")&&(p=l.target.querySelector(`[id="${v}"]`),p?.focus()),p&&!p.selectionEnd&&g!=null&&p.setSelectionRange(b,g),r}let I=(function(){function l(e,n,i,t=null,a=null){n instanceof HTMLTemplateElement&&i instanceof HTMLTemplateElement&&(n=n.content,i=i.content),t||=n.firstChild;for(let f of i.childNodes){if(o(f))continue;if(t&&t!=a){let M=p(e,f,t,a);if(M){M!==t&&b(e,t,M),E(M,f,e),t=M.nextSibling;continue}}if(f instanceof Element){let M=f.getAttribute("id");if(e.persistentIds.has(M)){let w=g(n,M,t,e);E(w,f,e),t=w.nextSibling;continue}if(!e.idMap.has(f)){let w=e.hyperMatches.get(f);if(w&&!e.idMap.has(w)){c(n,w,t),E(w,f,e),t=w.nextSibling;continue}}}let m=A(n,f,t,e);m&&(t=m.nextSibling)}for(;t&&t!=a;){let f=t;t=t.nextSibling,o(f)||v(e,f)}}function A(e,n,i,t){if(t.callbacks.beforeNodeAdded(n)===!1)return null;if(t.idMap.has(n)){let a=document.createElement(n.tagName);return e.insertBefore(a,i),E(a,n,t),t.callbacks.afterNodeAdded(a),a}else{let a=document.importNode(n,!0);return e.insertBefore(a,i),t.callbacks.afterNodeAdded(a),a}}let p=(function(){function e(t,a,f,m){let M=a instanceof Element&&!t.idMap.has(a)?t.hyperMatches.get(a):null,w=null,O=a.nextSibling,j=0,L=f;for(;L&&L!=m;){if(i(L,a)){if(n(t,L,a)||L===M&&!t.idMap.has(L))return L;if(w===null){let _=L instanceof Element&&t.hyperMatchedOldElements.has(L);!t.idMap.has(L)&&!_&&(w=L)}}if(w===null&&O&&i(L,O)&&(j++,O=O.nextSibling,j>=2&&(w=void 0)),t.activeElementAndParents.includes(L))break;L=L.nextSibling}return w||null}function n(t,a,f){let m=t.idMap.get(a),M=t.idMap.get(f);if(!M||!m)return!1;for(let w of m)if(M.has(w))return!0;return!1}function i(t,a){let f=t,m=a;return f.nodeType===m.nodeType&&f.tagName===m.tagName&&(!f.getAttribute?.("id")||f.getAttribute?.("id")===m.getAttribute?.("id"))}return e})();function v(e,n){let i=n instanceof Element&&e.hyperMatchedOldElements.has(n)&&!e.idMap.has(n);if(e.idMap.has(n)||i)c(e.pantry,n,null);else{if(e.callbacks.beforeNodeRemoved(n)===!1)return;n.parentNode?.removeChild(n),e.callbacks.afterNodeRemoved(n)}}function b(e,n,i){let t=n;for(;t&&t!==i;){let a=t;t=t.nextSibling,o(a)||v(e,a)}return t}function g(e,n,i,t){let a=t.target.getAttribute?.("id")===n&&t.target||t.target.querySelector(`[id="${n}"]`)||t.pantry.querySelector(`[id="${n}"]`);return r(a,t),c(e,a,i),a}function r(e,n){let i=e.getAttribute("id");for(;e=e.parentNode;){let t=n.idMap.get(e);t&&(t.delete(i),t.size||n.idMap.delete(e))}}function c(e,n,i){if(e.moveBefore)try{e.moveBefore(n,i)}catch{e.insertBefore(n,i)}else e.insertBefore(n,i)}return l})(),E=(function(){function l(r,c,e){return e.ignoreActive&&r===document.activeElement?null:(e.callbacks.beforeNodeMorphed(r,c)===!1||(r instanceof HTMLHeadElement&&e.head.ignore||(r instanceof HTMLHeadElement&&e.head.style!=="morph"?T(r,c,e):(A(r,c,e),g(r,e)||I(e,r,c))),e.callbacks.afterNodeMorphed(r,c)),r)}function A(r,c,e){let n=c.nodeType;if(n===1){let i=r,t=c,a=i.attributes,f=t.attributes;for(let m of f)b(m.name,i,"update",e)||i.getAttribute(m.name)!==m.value&&i.setAttribute(m.name,m.value);for(let m=a.length-1;0<=m;m--){let M=a[m];if(M&&!t.hasAttribute(M.name)){if(b(M.name,i,"remove",e))continue;i.removeAttribute(M.name)}}g(i,e)||p(i,t,e)}(n===8||n===3)&&r.nodeValue!==c.nodeValue&&(r.nodeValue=c.nodeValue)}function p(r,c,e){if(r instanceof HTMLInputElement&&c instanceof HTMLInputElement&&c.type!=="file"){let n=c.value,i=r.value;v(r,c,"checked",e),v(r,c,"disabled",e),c.hasAttribute("value")?i!==n&&(b("value",r,"update",e)||(r.setAttribute("value",n),r.value=n)):b("value",r,"remove",e)||(r.value="",r.removeAttribute("value"))}else if(r instanceof HTMLOptionElement&&c instanceof HTMLOptionElement)v(r,c,"selected",e);else if(r instanceof HTMLTextAreaElement&&c instanceof HTMLTextAreaElement){let n=c.value,i=r.value;if(b("value",r,"update",e))return;n!==i&&(r.value=n),r.firstChild&&r.firstChild.nodeValue!==n&&(r.firstChild.nodeValue=n)}}function v(r,c,e,n){let i=c[e],t=r[e];if(i!==t){let a=b(e,r,"update",n);a||(r[e]=c[e]),i?a||r.setAttribute(e,""):b(e,r,"remove",n)||r.removeAttribute(e)}}function b(r,c,e,n){return r==="value"&&n.ignoreActiveValue&&c===document.activeElement?!0:n.callbacks.beforeAttributeUpdated(r,c,e)===!1}function g(r,c){return!!c.ignoreActiveValue&&r===document.activeElement&&r!==document.body}return l})();function x(l,A,p,v){if(l.head.block){let b=A.querySelector("head"),g=p.querySelector("head");if(b&&g){let r=T(b,g,l);return Promise.all(r).then(()=>{let c=Object.assign(l,{head:{block:!1,ignore:!0}});return v(c)})}}return v(l)}function T(l,A,p){let v=[],b=[],g=[],r=[],c=p.scripts.matchMode,e=t=>{if(t.tagName==="SCRIPT")return u(t,c);if(t.tagName==="LINK"&&c==="smart"){let a=t.getAttribute("href");if(a)try{let f=new URL(a,window.location.href);return`link:${t.getAttribute("rel")||""}:${f.origin}${f.pathname}`}catch{}}return t.outerHTML},n=new Map;for(let t of A.children)o(t)||n.set(e(t),t);for(let t of l.children){let a=e(t),f=n.has(a),m=p.head.shouldReAppend(t),M=p.head.shouldPreserve(t);f||M?m?b.push(t):(n.delete(a),g.push(t)):p.head.style==="append"?m&&(b.push(t),r.push(t)):p.head.shouldRemove(t)!==!1&&!o(t)&&b.push(t)}r.push(...n.values());let i=[];for(let t of r){let a=document.createRange().createContextualFragment(t.outerHTML).firstChild;if(p.callbacks.beforeNodeAdded(a)!==!1){if("href"in a&&a.href||"src"in a&&a.src){let f,m=new Promise(function(M){f=M});a.addEventListener("load",function(){f()}),i.push(m)}l.appendChild(a),p.callbacks.afterNodeAdded(a),v.push(a)}}for(let t of b)p.callbacks.beforeNodeRemoved(t)!==!1&&(l.removeChild(t),p.callbacks.afterNodeRemoved(t));return p.head.afterHeadMorphed(l,{added:v,kept:g,removed:b}),i}function k(l,A,p){if(!p.scripts.handle)return[];let v=[],b=[],g=[],r=[],c=p.scripts.matchMode,e=Array.from(l.querySelectorAll("script"));for(let i of e){let t=u(i,c),a=A.has(t),f=p.scripts.shouldPreserve(i),m=p.scripts.shouldReAppend(i);a||f?m?(b.push(i),r.push(i)):g.push(i):r.push(i)}for(let i of A){let t=e.some(a=>a.outerHTML===i)}let n=[];for(let i of r){if(p.callbacks.beforeNodeAdded(i)===!1)continue;let t=document.createRange().createContextualFragment(i.outerHTML).firstChild;if(t.src){let a,f=new Promise(function(m){a=m});t.addEventListener("load",function(){a()}),t.addEventListener("error",function(){a()}),n.push(f)}i.replaceWith(t),p.callbacks.afterNodeAdded(t),v.push(t)}return p.scripts.afterScriptsHandled(l,{added:v,kept:g,removed:b}),n}let N=(function(){function l(e,n,i){let{persistentIds:t,idMap:a}=r(e,n),f=h.computeMatches(e,n),m=new Set;for(let O of f.values())m.add(O);let M=A(i),w=M.morphStyle||"outerHTML";if(!["innerHTML","outerHTML"].includes(w))throw`Do not understand how to morph style ${w}`;return{target:e,newContent:n,config:M,morphStyle:w,ignoreActive:M.ignoreActive,ignoreActiveValue:M.ignoreActiveValue,restoreFocus:M.restoreFocus,idMap:a,persistentIds:t,hyperMatches:f,hyperMatchedOldElements:m,pantry:p(),activeElementAndParents:v(e),callbacks:M.callbacks,head:M.head,scripts:M.scripts}}function A(e){let n=Object.assign({},d);return Object.assign(n,e),n.callbacks=Object.assign({},d.callbacks,e.callbacks),n.head=Object.assign({},d.head,e.head),n.scripts=Object.assign({},d.scripts,e.scripts),n}function p(){let e=document.createElement("div");return e.hidden=!0,document.body.insertAdjacentElement("afterend",e),e}function v(e){let n=[],i=document.activeElement;if(i?.tagName!=="BODY"&&e.contains(i))for(;i&&(n.push(i),i!==e);)i=i.parentElement;return n}function b(e){let n=Array.from(e.querySelectorAll("[id]"));return e.getAttribute?.("id")&&n.push(e),n}function g(e,n,i,t){for(let a of t){let f=a.getAttribute("id");if(n.has(f)){let m=a;for(;m;){let M=e.get(m);if(M==null&&(M=new Set,e.set(m,M)),M.add(f),m===i)break;m=m.parentElement}}}}function r(e,n){let i=b(e),t=b(n),a=c(i,t),f=new Map;g(f,a,e,i);let m=n.__hyperMorphRoot||n;return g(f,a,m,t),{persistentIds:a,idMap:f}}function c(e,n){let i=new Set,t=new Map;for(let{id:f,tagName:m}of e)t.has(f)?i.add(f):t.set(f,m);let a=new Set;for(let{id:f,tagName:m}of n)a.has(f)?i.add(f):t.get(f)===m&&a.add(f);for(let f of i)a.delete(f);return a}return l})(),{normalizeElement:R,normalizeParent:B}=(function(){let l=new WeakSet;function A(g){return g instanceof Document?g.documentElement:g}function p(g){if(g==null)return document.createElement("div");if(typeof g=="string")return p(b(g));if(l.has(g))return g;if(g instanceof Node){if(g.parentNode)return new v(g);{let r=document.createElement("div");return r.append(g),r}}else{let r=document.createElement("div");for(let c of[...g])r.append(c);return r}}class v{constructor(r){this.originalNode=r,this.realParentNode=r.parentNode,this.previousSibling=r.previousSibling,this.nextSibling=r.nextSibling}get childNodes(){let r=[],c=this.previousSibling?this.previousSibling.nextSibling:this.realParentNode.firstChild;for(;c&&c!=this.nextSibling;)r.push(c),c=c.nextSibling;return r}querySelectorAll(r){return this.childNodes.reduce((c,e)=>{if(e instanceof Element){e.matches(r)&&c.push(e);let n=e.querySelectorAll(r);for(let i=0;i<n.length;i++)c.push(n[i])}return c},[])}insertBefore(r,c){return this.realParentNode.insertBefore(r,c)}moveBefore(r,c){return this.realParentNode.moveBefore(r,c)}get __hyperMorphRoot(){return this.originalNode}}function b(g){let r=new DOMParser,c=g.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim,"");if(c.match(/<\/html>/)||c.match(/<\/head>/)||c.match(/<\/body>/)){let e=r.parseFromString(g,"text/html");if(c.match(/<\/html>/))return l.add(e),e;{let n=e.firstChild;return n&&l.add(n),n}}else{let n=r.parseFromString("<body><template>"+g+"</template></body>","text/html").body.querySelector("template").content;return l.add(n),n}}return{normalizeElement:A,normalizeParent:p}})();return{morph:y,defaults:d}})();var de=$.morph,fe=$.defaults,he=$;return X(pe);})();
2
2
 
3
3
  // Convenience morph wrapper with data-id support
4
4
  var morph = function(oldEl, newEl, options = {}) {