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 +10 -10
- package/package.json +1 -1
- package/src/communication/live-sync.js +70 -129
- package/src/core/autosave.js +14 -0
- package/src/core/tailwindInject.js +43 -2
- package/src/dom-utilities/insertStyleTag.js +14 -4
- package/src/hyperclay.js +35 -2
- package/src/ui/toast-hyperclay.js +14 -6
- package/src/ui/toast.js +18 -5
- package/src/utilities/mutation.js +30 -7
- package/src/vendor/hyper-morph.vendor.js +1 -1
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ import 'hyperclayjs/presets/standard.js';
|
|
|
57
57
|
|
|
58
58
|
| Module | Size | Description |
|
|
59
59
|
|--------|------|-------------|
|
|
60
|
-
| autosave |
|
|
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 |
|
|
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 |
|
|
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.
|
|
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 |
|
|
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 |
|
|
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 |
|
|
131
|
+
| hyper-morph | 17.2KB | DOM morphing with content-based element matching |
|
|
132
132
|
|
|
133
133
|
## Presets
|
|
134
134
|
|
|
135
|
-
### Minimal (~46.
|
|
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 (~
|
|
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.
|
|
145
|
+
### Everything (~201.8KB)
|
|
146
146
|
All available features
|
|
147
147
|
|
|
148
148
|
Includes all available modules across all categories.
|
package/package.json
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
*
|
|
6
6
|
* ┌─────────────────────────────────────────────────────────┐
|
|
7
7
|
* │ 1. LISTEN snapshot-ready event from save │
|
|
8
|
-
* │ (
|
|
8
|
+
* │ (full document with form values) │
|
|
9
9
|
* └─────────────────────────────────────────────────────────┘
|
|
10
10
|
* │
|
|
11
11
|
* ▼
|
|
12
12
|
* ┌─────────────────────────────────────────────────────────┐
|
|
13
|
-
* │ 2. SEND POST
|
|
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
|
|
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.
|
|
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
|
|
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 =
|
|
75
|
+
id = sessionStorage.getItem('livesync-client-id');
|
|
64
76
|
} catch (e) {
|
|
65
|
-
//
|
|
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
|
-
|
|
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.
|
|
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 {
|
|
200
|
+
const { html, sender } = data;
|
|
190
201
|
|
|
191
202
|
// Ignore own changes
|
|
192
|
-
if (sender === this.clientId)
|
|
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
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
213
|
-
this.applyUpdate(
|
|
214
|
-
if (this.onUpdate) this.onUpdate({
|
|
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
|
|
234
|
+
* Receives the full cloned documentElement and sends it.
|
|
233
235
|
*/
|
|
234
236
|
listenForSnapshots() {
|
|
235
|
-
this._snapshotHandler =
|
|
236
|
-
if (this.isPaused)
|
|
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
|
-
|
|
242
|
-
const
|
|
243
|
-
|
|
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
|
|
257
|
-
* Only updates
|
|
255
|
+
* Send full HTML to the server (debounced)
|
|
256
|
+
* Only updates lastHtml after successful save
|
|
258
257
|
*/
|
|
259
|
-
sendUpdate(
|
|
258
|
+
sendUpdate(html) {
|
|
260
259
|
clearTimeout(this.debounceTimer);
|
|
261
260
|
|
|
262
261
|
this.debounceTimer = setTimeout(() => {
|
|
263
262
|
// Skip if unchanged
|
|
264
|
-
if (
|
|
265
|
-
|
|
266
|
-
|
|
263
|
+
if (html === this.lastHtml) {
|
|
264
|
+
this._log('Skipping send - HTML unchanged');
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
267
|
|
|
268
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
293
|
+
* Morphs the entire document (head and body)
|
|
326
294
|
*/
|
|
327
|
-
applyUpdate(
|
|
328
|
-
|
|
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.
|
|
298
|
+
this.lastHtml = html;
|
|
336
299
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
temp.innerHTML = bodyHtml;
|
|
300
|
+
// Pause mutation observer so morph doesn't trigger autosave
|
|
301
|
+
Mutation.pause();
|
|
340
302
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
});
|
|
303
|
+
// Parse as full document
|
|
304
|
+
const parser = new DOMParser();
|
|
305
|
+
const newDoc = parser.parseFromString(html, 'text/html');
|
|
345
306
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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
|
/**
|
package/src/core/autosave.js
CHANGED
|
@@ -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('
|
|
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
|
-
//
|
|
66
|
-
const
|
|
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
|
|
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.
|
|
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 -
|
|
2
|
+
* Toast Hyperclay - Configure toast() to use Hyperclay platform styling
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
*
|
|
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
|
-
//
|
|
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
|
|
258
|
+
// Main toast function - uses configured theme (default: modern, or hyperclay if toast-hyperclay loaded)
|
|
246
259
|
function toast(message, messageType = "success") {
|
|
247
|
-
injectToastStyles(
|
|
260
|
+
injectToastStyles(toastConfig.styles, toastConfig.theme);
|
|
248
261
|
toastCore(message, messageType, {
|
|
249
|
-
templates:
|
|
250
|
-
icons:
|
|
251
|
-
theme:
|
|
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(
|
|
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
|
-
|
|
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 && !
|
|
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 = {}) {
|