hyperclayjs 1.16.0 → 1.18.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,16 +57,16 @@ 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" |
64
64
  | persist | 2.4KB | Persist input/select/textarea values to the DOM with [persist] attribute |
65
- | save-core | 7.4KB | Basic save function only - hyperclay.savePage() |
65
+ | save-core | 8.9KB | Basic save function only - hyperclay.savePage() |
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
- | 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 |
68
+ | snapshot | 10.6KB | Source of truth for page state - captures DOM snapshots for save and sync |
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 | 10.5KB | 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 | 11.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 (~50.9KB)
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 (~73.2KB)
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 (~206.9KB)
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.18.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;
@@ -51,24 +52,36 @@ class LiveSync {
51
52
  this.onDisconnect = null;
52
53
  this.onUpdate = null;
53
54
  this.onError = null;
55
+ this.onNotification = null;
56
+ }
57
+
58
+ _log(message, data = null) {
59
+ if (!this.debug) return;
60
+ const prefix = `[LiveSync ${new Date().toISOString()}]`;
61
+ if (data !== null) {
62
+ console.log(prefix, message, data);
63
+ } else {
64
+ console.log(prefix, message);
65
+ }
54
66
  }
55
67
 
56
68
  /**
57
- * Generate or retrieve a persistent client ID
69
+ * Generate or retrieve a tab-specific client ID
70
+ * Uses sessionStorage (unique per tab) not localStorage (shared across tabs)
58
71
  */
59
72
  generateClientId() {
60
73
  let id = null;
61
74
 
62
75
  try {
63
- id = localStorage.getItem('livesync-client-id');
76
+ id = sessionStorage.getItem('livesync-client-id');
64
77
  } catch (e) {
65
- // localStorage might not be available
78
+ // sessionStorage might not be available
66
79
  }
67
80
 
68
81
  if (!id) {
69
82
  id = Math.random().toString(36).slice(2, 11) + Date.now().toString(36);
70
83
  try {
71
- localStorage.setItem('livesync-client-id', id);
84
+ sessionStorage.setItem('livesync-client-id', id);
72
85
  } catch (e) {
73
86
  // That's okay
74
87
  }
@@ -98,8 +111,7 @@ class LiveSync {
98
111
  }
99
112
 
100
113
  // Reset state for new connection
101
- this.lastHeadHash = null;
102
- this.lastBodyHtml = null;
114
+ this.lastHtml = null;
103
115
 
104
116
  console.log('[LiveSync] Starting for:', this.currentFile);
105
117
  this.connect();
@@ -179,6 +191,12 @@ class LiveSync {
179
191
  this.sse.onmessage = (event) => {
180
192
  const data = JSON.parse(event.data);
181
193
 
194
+ // Handle notifications (show toast, don't morph)
195
+ if (data.type === "notification") {
196
+ this.handleNotification(data);
197
+ return;
198
+ }
199
+
182
200
  // Handle error events from server
183
201
  if (data.error) {
184
202
  console.error('[LiveSync] Server error:', data.error);
@@ -186,32 +204,23 @@ class LiveSync {
186
204
  return;
187
205
  }
188
206
 
189
- const { body, headHash, sender } = data;
207
+ const { html, sender } = data;
190
208
 
191
209
  // 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');
210
+ if (sender === this.clientId) {
211
+ this._log('Ignoring own message (sender matches clientId)');
197
212
  return;
198
213
  }
199
214
 
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;
215
+ // Guard against invalid html
216
+ if (typeof html !== 'string') {
217
+ console.error('[LiveSync] Received invalid html, ignoring');
218
+ return;
210
219
  }
211
220
 
212
- console.log('[LiveSync] Received update from:', sender);
213
- this.applyUpdate(body);
214
- if (this.onUpdate) this.onUpdate({ body, sender });
221
+ this._log(`Received update from: ${sender} (my clientId: ${this.clientId})`);
222
+ this.applyUpdate(html);
223
+ if (this.onUpdate) this.onUpdate({ html, sender });
215
224
  };
216
225
 
217
226
  // Native EventSource auto-reconnects on transient errors
@@ -229,151 +238,116 @@ class LiveSync {
229
238
 
230
239
  /**
231
240
  * Listen for snapshot-ready events from the save system.
232
- * Receives the full cloned documentElement and extracts head/body.
241
+ * Receives the full cloned documentElement and sends it.
233
242
  */
234
243
  listenForSnapshots() {
235
- this._snapshotHandler = async (event) => {
236
- if (this.isPaused) return;
244
+ this._snapshotHandler = (event) => {
245
+ if (this.isPaused) {
246
+ this._log('snapshot-ready received but isPaused, skipping');
247
+ return;
248
+ }
237
249
 
238
250
  const { documentElement } = event.detail;
239
251
  if (!documentElement) return;
240
252
 
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);
253
+ this._log('snapshot-ready received, preparing to send');
254
+ const html = documentElement.outerHTML;
255
+ this.sendUpdate(html);
250
256
  };
251
257
 
252
258
  document.addEventListener('hyperclay:snapshot-ready', this._snapshotHandler);
253
259
  }
254
260
 
255
261
  /**
256
- * Send body and headHash to the server (debounced)
257
- * Only updates lastBodyHtml after successful save
262
+ * Send full HTML to the server (debounced)
263
+ * Only updates lastHtml after successful save
258
264
  */
259
- sendUpdate(body, headHash) {
265
+ sendUpdate(html) {
260
266
  clearTimeout(this.debounceTimer);
261
267
 
262
268
  this.debounceTimer = setTimeout(() => {
263
269
  // Skip if unchanged
264
- if (body === this.lastBodyHtml) return;
265
-
266
- console.log('[LiveSync] Sending update');
270
+ if (html === this.lastHtml) {
271
+ this._log('Skipping send - HTML unchanged');
272
+ return;
273
+ }
267
274
 
268
- // Track local head changes
269
- this.lastHeadHash = headHash;
275
+ this._log(`Sending update (HTML length: ${html.length}, lastHtml length: ${this.lastHtml?.length || 0})`);
270
276
 
271
277
  fetch('/live-sync/save', {
272
278
  method: 'POST',
273
279
  headers: { 'Content-Type': 'application/json' },
274
280
  body: JSON.stringify({
275
281
  file: this.currentFile,
276
- body: body,
277
- sender: this.clientId,
278
- headHash: headHash
282
+ html: html,
283
+ sender: this.clientId
279
284
  })
280
285
  }).then(response => {
281
286
  if (response.ok) {
282
- // Only update lastBodyHtml after successful save
283
- this.lastBodyHtml = body;
287
+ this.lastHtml = html;
284
288
  } else {
285
- // Log non-OK responses but don't suppress future sends
286
289
  console.warn('[LiveSync] Save returned status:', response.status);
287
290
  }
288
291
  }).catch(err => {
289
- // Network error - don't update lastBodyHtml so next mutation will retry
290
292
  console.error('[LiveSync] Save failed:', err);
291
293
  if (this.onError) this.onError(err);
292
294
  });
293
295
  }, this.debounceMs);
294
296
  }
295
297
 
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
298
  /**
324
299
  * Apply an update received from the server
325
- * Guards against non-string values
300
+ * Morphs the entire document (head and body)
326
301
  */
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
-
302
+ applyUpdate(html) {
303
+ this._log('applyUpdate - pausing mutations and morphing');
334
304
  this.isPaused = true;
335
- this.lastBodyHtml = bodyHtml;
305
+ this.lastHtml = html;
336
306
 
337
- try {
338
- const temp = document.createElement('div');
339
- temp.innerHTML = bodyHtml;
307
+ // Pause mutation observer so morph doesn't trigger autosave
308
+ Mutation.pause();
340
309
 
341
- HyperMorph.morph(document.body, temp, {
342
- morphStyle: 'innerHTML',
343
- ignoreActiveValue: true
344
- });
310
+ // Parse as full document
311
+ const parser = new DOMParser();
312
+ const newDoc = parser.parseFromString(html, 'text/html');
345
313
 
346
- this.rehydrateFormState(document.body);
347
- } finally {
348
- this.isPaused = false;
349
- }
314
+ // Morph entire document (html element)
315
+ HyperMorph.morph(document.documentElement, newDoc.documentElement, {
316
+ morphStyle: 'outerHTML',
317
+ ignoreActiveValue: true,
318
+ head: { style: 'merge' },
319
+ scripts: { handle: true, matchMode: 'smart' }
320
+ });
321
+
322
+ this._log('applyUpdate - morph complete, resuming mutations');
323
+ Mutation.resume();
324
+ this.isPaused = false;
350
325
  }
351
326
 
352
327
  /**
353
- * Sync form control attributes to properties after DOM morph
328
+ * Handle a notification message from the server
329
+ * Shows a toast and emits an event for custom handling
330
+ * @param {Object} data - { msgType, msg, action? }
354
331
  */
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') || '';
362
- });
332
+ handleNotification({ msgType, msg, action }) {
333
+ this._log(`Notification received: ${msgType} - ${msg}`);
334
+
335
+ // Show toast if available
336
+ if (window.toast) {
337
+ window.toast(msg, msgType);
338
+ } else {
339
+ console.log(`[LiveSync] Notification: ${msg}`);
340
+ }
363
341
 
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
- });
342
+ // Emit event for custom handling (e.g., reload button)
343
+ document.dispatchEvent(new CustomEvent('hyperclay:notification', {
344
+ detail: { msgType, msg, action }
345
+ }));
369
346
 
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
- });
347
+ // Call notification callback if set
348
+ if (this.onNotification) {
349
+ this.onNotification({ msgType, msg, action });
350
+ }
377
351
  }
378
352
 
379
353
  /**
@@ -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
@@ -112,12 +112,31 @@ export function savePage(callback = () => {}) {
112
112
  const controller = new AbortController();
113
113
  const timeoutId = setTimeout(() => controller.abort(), 12000);
114
114
 
115
- fetch(saveEndpoint, {
115
+ // Check if running on Hyperclay Local - send JSON with both versions for platform sync
116
+ const isHyperclayLocal = window.location.hostname === 'localhost' ||
117
+ window.location.hostname === '127.0.0.1';
118
+
119
+ const fetchOptions = {
116
120
  method: 'POST',
117
121
  credentials: 'include',
118
- body: currentContents,
119
122
  signal: controller.signal
120
- })
123
+ };
124
+
125
+ if (isHyperclayLocal && window.__hyperclaySnapshotHtml) {
126
+ // Send JSON with both stripped content and full snapshot for platform live sync
127
+ fetchOptions.headers = { 'Content-Type': 'application/json' };
128
+ fetchOptions.body = JSON.stringify({
129
+ content: currentContents,
130
+ snapshotHtml: window.__hyperclaySnapshotHtml
131
+ });
132
+ // Clear after use to avoid stale data
133
+ window.__hyperclaySnapshotHtml = null;
134
+ } else {
135
+ // Platform: send plain text as before
136
+ fetchOptions.body = currentContents;
137
+ }
138
+
139
+ fetch(saveEndpoint, fetchOptions)
121
140
  .then(res => {
122
141
  clearTimeout(timeoutId);
123
142
  return res.json().then(data => {
@@ -187,12 +206,31 @@ export function saveHtml(html, callback = () => {}) {
187
206
  const controller = new AbortController();
188
207
  const timeoutId = setTimeout(() => controller.abort(), 12000);
189
208
 
190
- fetch(saveEndpoint, {
209
+ // Check if running on Hyperclay Local - send JSON with both versions for platform sync
210
+ const isHyperclayLocal = window.location.hostname === 'localhost' ||
211
+ window.location.hostname === '127.0.0.1';
212
+
213
+ const fetchOptions = {
191
214
  method: 'POST',
192
215
  credentials: 'include',
193
- body: html,
194
216
  signal: controller.signal
195
- })
217
+ };
218
+
219
+ if (isHyperclayLocal && window.__hyperclaySnapshotHtml) {
220
+ // Send JSON with both stripped content and full snapshot for platform live sync
221
+ fetchOptions.headers = { 'Content-Type': 'application/json' };
222
+ fetchOptions.body = JSON.stringify({
223
+ content: html,
224
+ snapshotHtml: window.__hyperclaySnapshotHtml
225
+ });
226
+ // Clear after use to avoid stale data
227
+ window.__hyperclaySnapshotHtml = null;
228
+ } else {
229
+ // Platform: send plain text as before
230
+ fetchOptions.body = html;
231
+ }
232
+
233
+ fetch(saveEndpoint, fetchOptions)
196
234
  .then(res => {
197
235
  clearTimeout(timeoutId);
198
236
  return res.json().then(data => {
@@ -172,6 +172,14 @@ export function captureForSaveAndComparison({ emitForSync = true } = {}) {
172
172
  document.dispatchEvent(new CustomEvent('hyperclay:snapshot-ready', {
173
173
  detail: { documentElement: clone }
174
174
  }));
175
+
176
+ // Store snapshot HTML for Hyperclay Local platform sync
177
+ // This allows the save system to send both stripped and full versions
178
+ const isHyperclayLocal = window.location.hostname === 'localhost' ||
179
+ window.location.hostname === '127.0.0.1';
180
+ if (isHyperclayLocal) {
181
+ window.__hyperclaySnapshotHtml = '<!DOCTYPE html>' + clone.outerHTML;
182
+ }
175
183
  }
176
184
 
177
185
  // Run inline [onbeforesave] handlers
@@ -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.18.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
@@ -4,13 +4,17 @@
4
4
  // Default modern icons (normalized to 48x48 viewBox)
5
5
  const defaultIcons = {
6
6
  success: `<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M13.9404 23.9475L21.9099 31.224L35.1906 15.9045M3 4.5H44.9804V44.309H3V4.5Z" stroke="#33D131" stroke-width="4.3"/></svg>`,
7
- error: `<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M32.7383 14.4045L14 33.1429M32.7451 33.1429L14.0068 14.4046M3.01 4H44.99V43.809H3.01V4Z" stroke="#FF4450" stroke-width="4"/></svg>`
7
+ error: `<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M32.7383 14.4045L14 33.1429M32.7451 33.1429L14.0068 14.4046M3.01 4H44.99V43.809H3.01V4Z" stroke="#FF4450" stroke-width="4"/></svg>`,
8
+ warning: `<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M24 8L4 40H44L24 8Z" stroke="#F5A623" stroke-width="4" stroke-linejoin="round"/><path d="M24 20V28M24 34V36" stroke="#F5A623" stroke-width="4" stroke-linecap="round"/></svg>`,
9
+ info: `<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="24" cy="24" r="20" stroke="#4A90D9" stroke-width="4"/><path d="M24 20V32M24 14V16" stroke="#4A90D9" stroke-width="4" stroke-linecap="round"/></svg>`
8
10
  };
9
11
 
10
12
  // Hyperclay icons
11
13
  export const hyperclayIcons = {
12
14
  success: `<svg viewBox="0 0 33 33" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M32 1h-5v3.5h-2.5V8h-2v3.5H20V15h-2.5v3.5h-2V22H13v3.5H9V22H7v-3.5H6V15H1v3.5h1V22h2v3.5h1.5V29H7v3.5h5V29h3.5v-3.5H18V22h2.5v-3.5h2V15H25v-3.5h2.5V8h2V4.5H32V1Z" fill="#76C824"/></svg>`,
13
- error: `<svg viewBox="0 0 33 33" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M33 1h-5v3.5h-3.5V8H21v3.5h-3.5V15h-2v-3.5H12V8H8.5V4.5H5V1H0v3.5h3.5V8H7v3.5h3.5V15H14v3.5h-3.5V22H7v3.5H3.5V29H0v3.5h5V29h3.5v-3.5H12V22h3.5v-3.5h2V22H21v3.5h3.5V29H28v3.5h5V29h-3.5v-3.5H26V22h-3.5v-3.5H19V15h3.5v-3.5H26V8h3.5V4.5H33V1Z" fill="#DD304F"/></svg>`
15
+ error: `<svg viewBox="0 0 33 33" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M33 1h-5v3.5h-3.5V8H21v3.5h-3.5V15h-2v-3.5H12V8H8.5V4.5H5V1H0v3.5h3.5V8H7v3.5h3.5V15H14v3.5h-3.5V22H7v3.5H3.5V29H0v3.5h5V29h3.5v-3.5H12V22h3.5v-3.5h2V22H21v3.5h3.5V29H28v3.5h5V29h-3.5v-3.5H26V22h-3.5v-3.5H19V15h3.5v-3.5H26V8h3.5V4.5H33V1Z" fill="#DD304F"/></svg>`,
16
+ warning: `<svg viewBox="0 0 33 33" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M18 1h-3v3h-2v3h-2v3H9v3H7v3H5v3H3v3H1v6h31v-6h-2v-3h-2v-3h-2v-3h-2v-3h-2V7h-2V4h-2V1zm-2 8h1v10h-1V9zm0 13h1v2h-1v-2z" fill="#F5A623"/></svg>`,
17
+ info: `<svg viewBox="0 0 33 33" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M12 1v3H9v3H6v3H3v6h3v3h3v3h3v3h9v-3h3v-3h3v-3h3v-6h-3V7h-3V4h-3V1H12zm4 7h1v2h-1V8zm0 5h1v9h-1v-9z" fill="#4A90D9"/></svg>`
14
18
  };
15
19
 
16
20
  // Default templates
@@ -27,6 +31,18 @@ const defaultTemplates = {
27
31
  <div class="toast-icon">{icon}</div>
28
32
  <div class="toast-message">{message}</div>
29
33
  </div>
34
+ `,
35
+ warning: `
36
+ <div class="toast hide warning noise-texture">
37
+ <div class="toast-icon">{icon}</div>
38
+ <div class="toast-message">{message}</div>
39
+ </div>
40
+ `,
41
+ info: `
42
+ <div class="toast hide info noise-texture">
43
+ <div class="toast-icon">{icon}</div>
44
+ <div class="toast-message">{message}</div>
45
+ </div>
30
46
  `
31
47
  }
32
48
  };
@@ -45,6 +61,18 @@ export const hyperclayTemplates = {
45
61
  {icon}
46
62
  <span class="message">{message}</span>
47
63
  </div>
64
+ `,
65
+ warning: `
66
+ <div class="toast hide warning">
67
+ {icon}
68
+ <span class="message">{message}</span>
69
+ </div>
70
+ `,
71
+ info: `
72
+ <div class="toast hide info">
73
+ {icon}
74
+ <span class="message">{message}</span>
75
+ </div>
48
76
  `
49
77
  }
50
78
  };
@@ -94,6 +122,16 @@ const modernStyles = `
94
122
  background: radial-gradient(85.86% 68.42% at 50% 68.42%, #240A13 0%, #481826 100%);
95
123
  }
96
124
 
125
+ [data-toast-theme="modern"] .toast.warning {
126
+ border-color: #B8860B;
127
+ background: radial-gradient(85.86% 68.42% at 50% 68.42%, #2A2010 0%, #3D3018 100%);
128
+ }
129
+
130
+ [data-toast-theme="modern"] .toast.info {
131
+ border-color: #2E6B9E;
132
+ background: radial-gradient(85.86% 68.42% at 50% 68.42%, #0A1A24 0%, #162D3D 100%);
133
+ }
134
+
97
135
  [data-toast-theme="modern"] .toast-icon {
98
136
  position: relative;
99
137
  top: -1px;
@@ -180,11 +218,34 @@ export const hyperclayStyles = `
180
218
  color: #DD304F;
181
219
  border: 2px dashed #CD2140;
182
220
  }
221
+
222
+ [data-toast-theme="hyperclay"] .toast.warning {
223
+ color: #F5A623;
224
+ border: 2px dashed #D4900E;
225
+ }
226
+
227
+ [data-toast-theme="hyperclay"] .toast.info {
228
+ color: #4A90D9;
229
+ border: 2px dashed #3A7BBF;
230
+ }
183
231
  `;
184
232
 
185
233
  // Track which theme styles have been injected
186
234
  const injectedThemes = new Set();
187
235
 
236
+ // Global toast configuration (can be overridden by toast-hyperclay module)
237
+ let toastConfig = {
238
+ styles: modernStyles,
239
+ templates: defaultTemplates,
240
+ icons: defaultIcons,
241
+ theme: 'modern'
242
+ };
243
+
244
+ // Allow other modules (like toast-hyperclay) to override default toast styling
245
+ export function setToastTheme(config) {
246
+ Object.assign(toastConfig, config);
247
+ }
248
+
188
249
  // Helper function to inject styles for a theme (additive, not replacing)
189
250
  export function injectToastStyles(styles, theme) {
190
251
  if (injectedThemes.has(theme)) return;
@@ -242,13 +303,13 @@ export function toastCore(message, messageType = "success", config = {}) {
242
303
  }, 6600);
243
304
  }
244
305
 
245
- // Main toast function - uses modern styles
306
+ // Main toast function - uses configured theme (default: modern, or hyperclay if toast-hyperclay loaded)
246
307
  function toast(message, messageType = "success") {
247
- injectToastStyles(modernStyles, 'modern');
308
+ injectToastStyles(toastConfig.styles, toastConfig.theme);
248
309
  toastCore(message, messageType, {
249
- templates: defaultTemplates,
250
- icons: defaultIcons,
251
- theme: 'modern'
310
+ templates: toastConfig.templates,
311
+ icons: toastConfig.icons,
312
+ theme: toastConfig.theme
252
313
  });
253
314
  }
254
315
 
@@ -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 = {}) {