livereload-morph 0.1.3 → 0.1.5

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.
@@ -0,0 +1,1464 @@
1
+ // src/protocol.js
2
+ var PROTOCOL_6 = "http://livereload.com/protocols/official-6";
3
+ var PROTOCOL_7 = "http://livereload.com/protocols/official-7";
4
+
5
+ class ProtocolError {
6
+ constructor(reason, data) {
7
+ this.message = `LiveReload protocol error (${reason}) after receiving data: "${data}".`;
8
+ }
9
+ }
10
+
11
+ class Parser {
12
+ constructor(handlers) {
13
+ this.handlers = handlers;
14
+ this.reset();
15
+ }
16
+ reset() {
17
+ this.protocol = null;
18
+ }
19
+ process(data) {
20
+ try {
21
+ let message;
22
+ if (!this.protocol) {
23
+ if (data.match(new RegExp("^!!ver:([\\d.]+)$"))) {
24
+ this.protocol = 6;
25
+ } else if (message = this._parseMessage(data, ["hello"])) {
26
+ if (!message.protocols.length) {
27
+ throw new ProtocolError("no protocols specified in handshake message");
28
+ } else if (Array.from(message.protocols).includes(PROTOCOL_7)) {
29
+ this.protocol = 7;
30
+ } else if (Array.from(message.protocols).includes(PROTOCOL_6)) {
31
+ this.protocol = 6;
32
+ } else {
33
+ throw new ProtocolError("no supported protocols found");
34
+ }
35
+ }
36
+ return this.handlers.connected(this.protocol);
37
+ }
38
+ if (this.protocol === 6) {
39
+ message = JSON.parse(data);
40
+ if (!message.length) {
41
+ throw new ProtocolError("protocol 6 messages must be arrays");
42
+ }
43
+ const [command, options] = Array.from(message);
44
+ if (command !== "refresh") {
45
+ throw new ProtocolError("unknown protocol 6 command");
46
+ }
47
+ return this.handlers.message({
48
+ command: "reload",
49
+ path: options.path,
50
+ liveCSS: options.apply_css_live != null ? options.apply_css_live : true
51
+ });
52
+ }
53
+ message = this._parseMessage(data, ["reload", "alert"]);
54
+ return this.handlers.message(message);
55
+ } catch (e) {
56
+ if (e instanceof ProtocolError) {
57
+ return this.handlers.error(e);
58
+ }
59
+ throw e;
60
+ }
61
+ }
62
+ _parseMessage(data, validCommands) {
63
+ let message;
64
+ try {
65
+ message = JSON.parse(data);
66
+ } catch (e) {
67
+ throw new ProtocolError("unparsable JSON", data);
68
+ }
69
+ if (!message.command) {
70
+ throw new ProtocolError('missing "command" key', data);
71
+ }
72
+ if (!validCommands.includes(message.command)) {
73
+ throw new ProtocolError(`invalid command '${message.command}', only valid commands are: ${validCommands.join(", ")})`, data);
74
+ }
75
+ return message;
76
+ }
77
+ }
78
+
79
+ // src/connector.js
80
+ var VERSION = "1.0.0";
81
+
82
+ class Connector {
83
+ constructor(options, WebSocket, Timer, handlers) {
84
+ this.options = options;
85
+ this.WebSocket = WebSocket;
86
+ this.Timer = Timer;
87
+ this.handlers = handlers;
88
+ const path = this.options.path ? `${this.options.path}` : "livereload";
89
+ const port = this.options.port ? `:${this.options.port}` : "";
90
+ this._uri = `ws${this.options.https ? "s" : ""}://${this.options.host}${port}/${path}`;
91
+ this._nextDelay = this.options.mindelay;
92
+ this._connectionDesired = false;
93
+ this.protocol = 0;
94
+ this.protocolParser = new Parser({
95
+ connected: (protocol) => {
96
+ this.protocol = protocol;
97
+ this._handshakeTimeout.stop();
98
+ this._nextDelay = this.options.mindelay;
99
+ this._disconnectionReason = "broken";
100
+ return this.handlers.connected(this.protocol);
101
+ },
102
+ error: (e) => {
103
+ this.handlers.error(e);
104
+ return this._closeOnError();
105
+ },
106
+ message: (message) => {
107
+ return this.handlers.message(message);
108
+ }
109
+ });
110
+ this._handshakeTimeout = new this.Timer(() => {
111
+ if (!this._isSocketConnected()) {
112
+ return;
113
+ }
114
+ this._disconnectionReason = "handshake-timeout";
115
+ return this.socket.close();
116
+ });
117
+ this._reconnectTimer = new this.Timer(() => {
118
+ if (!this._connectionDesired) {
119
+ return;
120
+ }
121
+ return this.connect();
122
+ });
123
+ this.connect();
124
+ }
125
+ _isSocketConnected() {
126
+ return this.socket && this.socket.readyState === this.WebSocket.OPEN;
127
+ }
128
+ connect() {
129
+ this._connectionDesired = true;
130
+ if (this._isSocketConnected()) {
131
+ return;
132
+ }
133
+ this._reconnectTimer.stop();
134
+ this._disconnectionReason = "cannot-connect";
135
+ this.protocolParser.reset();
136
+ this.handlers.connecting();
137
+ this.socket = new this.WebSocket(this._uri);
138
+ this.socket.onopen = (e) => this._onopen(e);
139
+ this.socket.onclose = (e) => this._onclose(e);
140
+ this.socket.onmessage = (e) => this._onmessage(e);
141
+ this.socket.onerror = (e) => this._onerror(e);
142
+ }
143
+ disconnect() {
144
+ this._connectionDesired = false;
145
+ this._reconnectTimer.stop();
146
+ if (!this._isSocketConnected()) {
147
+ return;
148
+ }
149
+ this._disconnectionReason = "manual";
150
+ return this.socket.close();
151
+ }
152
+ _scheduleReconnection() {
153
+ if (!this._connectionDesired) {
154
+ return;
155
+ }
156
+ if (!this._reconnectTimer.running) {
157
+ this._reconnectTimer.start(this._nextDelay);
158
+ this._nextDelay = Math.min(this.options.maxdelay, this._nextDelay * 2);
159
+ }
160
+ }
161
+ sendCommand(command) {
162
+ if (!this.protocol) {
163
+ return;
164
+ }
165
+ return this._sendCommand(command);
166
+ }
167
+ _sendCommand(command) {
168
+ return this.socket.send(JSON.stringify(command));
169
+ }
170
+ _closeOnError() {
171
+ this._handshakeTimeout.stop();
172
+ this._disconnectionReason = "error";
173
+ return this.socket.close();
174
+ }
175
+ _onopen(e) {
176
+ this.handlers.socketConnected();
177
+ this._disconnectionReason = "handshake-failed";
178
+ const hello = {
179
+ command: "hello",
180
+ protocols: [PROTOCOL_6, PROTOCOL_7]
181
+ };
182
+ hello.ver = VERSION;
183
+ this._sendCommand(hello);
184
+ return this._handshakeTimeout.start(this.options.handshake_timeout);
185
+ }
186
+ _onclose(e) {
187
+ this.protocol = 0;
188
+ this.handlers.disconnected(this._disconnectionReason, this._nextDelay);
189
+ return this._scheduleReconnection();
190
+ }
191
+ _onerror(e) {}
192
+ _onmessage(e) {
193
+ return this.protocolParser.process(e.data);
194
+ }
195
+ }
196
+
197
+ // src/timer.js
198
+ class Timer {
199
+ constructor(func) {
200
+ this.func = func;
201
+ this.running = false;
202
+ this.id = null;
203
+ this._handler = () => {
204
+ this.running = false;
205
+ this.id = null;
206
+ return this.func();
207
+ };
208
+ }
209
+ start(timeout) {
210
+ if (this.running) {
211
+ clearTimeout(this.id);
212
+ }
213
+ this.id = setTimeout(this._handler, timeout);
214
+ this.running = true;
215
+ }
216
+ stop() {
217
+ if (this.running) {
218
+ clearTimeout(this.id);
219
+ this.running = false;
220
+ this.id = null;
221
+ }
222
+ }
223
+ }
224
+ Timer.start = (timeout, func) => setTimeout(func, timeout);
225
+
226
+ // src/options.js
227
+ class Options {
228
+ constructor() {
229
+ this.https = false;
230
+ this.host = null;
231
+ let port = 35729;
232
+ Object.defineProperty(this, "port", {
233
+ get() {
234
+ return port;
235
+ },
236
+ set(v) {
237
+ port = v ? isNaN(v) ? v : +v : "";
238
+ }
239
+ });
240
+ this.mindelay = 1000;
241
+ this.maxdelay = 60000;
242
+ this.handshake_timeout = 5000;
243
+ this.morphHTML = true;
244
+ this.verbose = false;
245
+ this.importCacheWaitPeriod = 200;
246
+ }
247
+ set(name, value) {
248
+ if (typeof value === "undefined") {
249
+ return;
250
+ }
251
+ if (!isNaN(+value)) {
252
+ value = +value;
253
+ }
254
+ if (value === "true") {
255
+ value = true;
256
+ } else if (value === "false") {
257
+ value = false;
258
+ }
259
+ this[name] = value;
260
+ }
261
+ }
262
+ Options.extract = function(document2) {
263
+ const win = document2.defaultView || window;
264
+ if (win && win.LiveMorphOptions) {
265
+ const options = new Options;
266
+ for (const [key, value] of Object.entries(win.LiveMorphOptions)) {
267
+ options.set(key, value);
268
+ }
269
+ return options;
270
+ }
271
+ const scripts = Array.from(document2.getElementsByTagName("script"));
272
+ for (const script of scripts) {
273
+ const host = script.getAttribute("data-livereload-morph-host");
274
+ if (host) {
275
+ const options = new Options;
276
+ options.host = host;
277
+ const port = script.getAttribute("data-livereload-morph-port");
278
+ if (port)
279
+ options.port = parseInt(port, 10);
280
+ const verbose = script.getAttribute("data-livereload-morph-verbose");
281
+ if (verbose !== null)
282
+ options.verbose = verbose === "true";
283
+ return options;
284
+ }
285
+ }
286
+ return null;
287
+ };
288
+
289
+ // node_modules/idiomorph/dist/idiomorph.esm.js
290
+ var Idiomorph = function() {
291
+ const noOp = () => {};
292
+ const defaults = {
293
+ morphStyle: "outerHTML",
294
+ callbacks: {
295
+ beforeNodeAdded: noOp,
296
+ afterNodeAdded: noOp,
297
+ beforeNodeMorphed: noOp,
298
+ afterNodeMorphed: noOp,
299
+ beforeNodeRemoved: noOp,
300
+ afterNodeRemoved: noOp,
301
+ beforeAttributeUpdated: noOp
302
+ },
303
+ head: {
304
+ style: "merge",
305
+ shouldPreserve: (elt) => elt.getAttribute("im-preserve") === "true",
306
+ shouldReAppend: (elt) => elt.getAttribute("im-re-append") === "true",
307
+ shouldRemove: noOp,
308
+ afterHeadMorphed: noOp
309
+ },
310
+ restoreFocus: true
311
+ };
312
+ function morph(oldNode, newContent, config = {}) {
313
+ oldNode = normalizeElement(oldNode);
314
+ const newNode = normalizeParent(newContent);
315
+ const ctx = createMorphContext(oldNode, newNode, config);
316
+ const morphedNodes = saveAndRestoreFocus(ctx, () => {
317
+ return withHeadBlocking(ctx, oldNode, newNode, (ctx2) => {
318
+ if (ctx2.morphStyle === "innerHTML") {
319
+ morphChildren(ctx2, oldNode, newNode);
320
+ return Array.from(oldNode.childNodes);
321
+ } else {
322
+ return morphOuterHTML(ctx2, oldNode, newNode);
323
+ }
324
+ });
325
+ });
326
+ ctx.pantry.remove();
327
+ return morphedNodes;
328
+ }
329
+ function morphOuterHTML(ctx, oldNode, newNode) {
330
+ const oldParent = normalizeParent(oldNode);
331
+ morphChildren(ctx, oldParent, newNode, oldNode, oldNode.nextSibling);
332
+ return Array.from(oldParent.childNodes);
333
+ }
334
+ function saveAndRestoreFocus(ctx, fn) {
335
+ if (!ctx.config.restoreFocus)
336
+ return fn();
337
+ let activeElement = document.activeElement;
338
+ if (!(activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement)) {
339
+ return fn();
340
+ }
341
+ const { id: activeElementId, selectionStart, selectionEnd } = activeElement;
342
+ const results = fn();
343
+ if (activeElementId && activeElementId !== document.activeElement?.getAttribute("id")) {
344
+ activeElement = ctx.target.querySelector(`[id="${activeElementId}"]`);
345
+ activeElement?.focus();
346
+ }
347
+ if (activeElement && !activeElement.selectionEnd && selectionEnd) {
348
+ activeElement.setSelectionRange(selectionStart, selectionEnd);
349
+ }
350
+ return results;
351
+ }
352
+ const morphChildren = function() {
353
+ function morphChildren2(ctx, oldParent, newParent, insertionPoint = null, endPoint = null) {
354
+ if (oldParent instanceof HTMLTemplateElement && newParent instanceof HTMLTemplateElement) {
355
+ oldParent = oldParent.content;
356
+ newParent = newParent.content;
357
+ }
358
+ insertionPoint ||= oldParent.firstChild;
359
+ for (const newChild of newParent.childNodes) {
360
+ if (insertionPoint && insertionPoint != endPoint) {
361
+ const bestMatch = findBestMatch(ctx, newChild, insertionPoint, endPoint);
362
+ if (bestMatch) {
363
+ if (bestMatch !== insertionPoint) {
364
+ removeNodesBetween(ctx, insertionPoint, bestMatch);
365
+ }
366
+ morphNode(bestMatch, newChild, ctx);
367
+ insertionPoint = bestMatch.nextSibling;
368
+ continue;
369
+ }
370
+ }
371
+ if (newChild instanceof Element) {
372
+ const newChildId = newChild.getAttribute("id");
373
+ if (ctx.persistentIds.has(newChildId)) {
374
+ const movedChild = moveBeforeById(oldParent, newChildId, insertionPoint, ctx);
375
+ morphNode(movedChild, newChild, ctx);
376
+ insertionPoint = movedChild.nextSibling;
377
+ continue;
378
+ }
379
+ }
380
+ const insertedNode = createNode(oldParent, newChild, insertionPoint, ctx);
381
+ if (insertedNode) {
382
+ insertionPoint = insertedNode.nextSibling;
383
+ }
384
+ }
385
+ while (insertionPoint && insertionPoint != endPoint) {
386
+ const tempNode = insertionPoint;
387
+ insertionPoint = insertionPoint.nextSibling;
388
+ removeNode(ctx, tempNode);
389
+ }
390
+ }
391
+ function createNode(oldParent, newChild, insertionPoint, ctx) {
392
+ if (ctx.callbacks.beforeNodeAdded(newChild) === false)
393
+ return null;
394
+ if (ctx.idMap.has(newChild)) {
395
+ const newEmptyChild = document.createElement(newChild.tagName);
396
+ oldParent.insertBefore(newEmptyChild, insertionPoint);
397
+ morphNode(newEmptyChild, newChild, ctx);
398
+ ctx.callbacks.afterNodeAdded(newEmptyChild);
399
+ return newEmptyChild;
400
+ } else {
401
+ const newClonedChild = document.importNode(newChild, true);
402
+ oldParent.insertBefore(newClonedChild, insertionPoint);
403
+ ctx.callbacks.afterNodeAdded(newClonedChild);
404
+ return newClonedChild;
405
+ }
406
+ }
407
+ const findBestMatch = function() {
408
+ function findBestMatch2(ctx, node, startPoint, endPoint) {
409
+ let softMatch = null;
410
+ let nextSibling = node.nextSibling;
411
+ let siblingSoftMatchCount = 0;
412
+ let cursor = startPoint;
413
+ while (cursor && cursor != endPoint) {
414
+ if (isSoftMatch(cursor, node)) {
415
+ if (isIdSetMatch(ctx, cursor, node)) {
416
+ return cursor;
417
+ }
418
+ if (softMatch === null) {
419
+ if (!ctx.idMap.has(cursor)) {
420
+ softMatch = cursor;
421
+ }
422
+ }
423
+ }
424
+ if (softMatch === null && nextSibling && isSoftMatch(cursor, nextSibling)) {
425
+ siblingSoftMatchCount++;
426
+ nextSibling = nextSibling.nextSibling;
427
+ if (siblingSoftMatchCount >= 2) {
428
+ softMatch = undefined;
429
+ }
430
+ }
431
+ if (ctx.activeElementAndParents.includes(cursor))
432
+ break;
433
+ cursor = cursor.nextSibling;
434
+ }
435
+ return softMatch || null;
436
+ }
437
+ function isIdSetMatch(ctx, oldNode, newNode) {
438
+ let oldSet = ctx.idMap.get(oldNode);
439
+ let newSet = ctx.idMap.get(newNode);
440
+ if (!newSet || !oldSet)
441
+ return false;
442
+ for (const id of oldSet) {
443
+ if (newSet.has(id)) {
444
+ return true;
445
+ }
446
+ }
447
+ return false;
448
+ }
449
+ function isSoftMatch(oldNode, newNode) {
450
+ const oldElt = oldNode;
451
+ const newElt = newNode;
452
+ return oldElt.nodeType === newElt.nodeType && oldElt.tagName === newElt.tagName && (!oldElt.getAttribute?.("id") || oldElt.getAttribute?.("id") === newElt.getAttribute?.("id"));
453
+ }
454
+ return findBestMatch2;
455
+ }();
456
+ function removeNode(ctx, node) {
457
+ if (ctx.idMap.has(node)) {
458
+ moveBefore(ctx.pantry, node, null);
459
+ } else {
460
+ if (ctx.callbacks.beforeNodeRemoved(node) === false)
461
+ return;
462
+ node.parentNode?.removeChild(node);
463
+ ctx.callbacks.afterNodeRemoved(node);
464
+ }
465
+ }
466
+ function removeNodesBetween(ctx, startInclusive, endExclusive) {
467
+ let cursor = startInclusive;
468
+ while (cursor && cursor !== endExclusive) {
469
+ let tempNode = cursor;
470
+ cursor = cursor.nextSibling;
471
+ removeNode(ctx, tempNode);
472
+ }
473
+ return cursor;
474
+ }
475
+ function moveBeforeById(parentNode, id, after, ctx) {
476
+ const target = ctx.target.getAttribute?.("id") === id && ctx.target || ctx.target.querySelector(`[id="${id}"]`) || ctx.pantry.querySelector(`[id="${id}"]`);
477
+ removeElementFromAncestorsIdMaps(target, ctx);
478
+ moveBefore(parentNode, target, after);
479
+ return target;
480
+ }
481
+ function removeElementFromAncestorsIdMaps(element, ctx) {
482
+ const id = element.getAttribute("id");
483
+ while (element = element.parentNode) {
484
+ let idSet = ctx.idMap.get(element);
485
+ if (idSet) {
486
+ idSet.delete(id);
487
+ if (!idSet.size) {
488
+ ctx.idMap.delete(element);
489
+ }
490
+ }
491
+ }
492
+ }
493
+ function moveBefore(parentNode, element, after) {
494
+ if (parentNode.moveBefore) {
495
+ try {
496
+ parentNode.moveBefore(element, after);
497
+ } catch (e) {
498
+ parentNode.insertBefore(element, after);
499
+ }
500
+ } else {
501
+ parentNode.insertBefore(element, after);
502
+ }
503
+ }
504
+ return morphChildren2;
505
+ }();
506
+ const morphNode = function() {
507
+ function morphNode2(oldNode, newContent, ctx) {
508
+ if (ctx.ignoreActive && oldNode === document.activeElement) {
509
+ return null;
510
+ }
511
+ if (ctx.callbacks.beforeNodeMorphed(oldNode, newContent) === false) {
512
+ return oldNode;
513
+ }
514
+ if (oldNode instanceof HTMLHeadElement && ctx.head.ignore) {} else if (oldNode instanceof HTMLHeadElement && ctx.head.style !== "morph") {
515
+ handleHeadElement(oldNode, newContent, ctx);
516
+ } else {
517
+ morphAttributes(oldNode, newContent, ctx);
518
+ if (!ignoreValueOfActiveElement(oldNode, ctx)) {
519
+ morphChildren(ctx, oldNode, newContent);
520
+ }
521
+ }
522
+ ctx.callbacks.afterNodeMorphed(oldNode, newContent);
523
+ return oldNode;
524
+ }
525
+ function morphAttributes(oldNode, newNode, ctx) {
526
+ let type = newNode.nodeType;
527
+ if (type === 1) {
528
+ const oldElt = oldNode;
529
+ const newElt = newNode;
530
+ const oldAttributes = oldElt.attributes;
531
+ const newAttributes = newElt.attributes;
532
+ for (const newAttribute of newAttributes) {
533
+ if (ignoreAttribute(newAttribute.name, oldElt, "update", ctx)) {
534
+ continue;
535
+ }
536
+ if (oldElt.getAttribute(newAttribute.name) !== newAttribute.value) {
537
+ oldElt.setAttribute(newAttribute.name, newAttribute.value);
538
+ }
539
+ }
540
+ for (let i = oldAttributes.length - 1;0 <= i; i--) {
541
+ const oldAttribute = oldAttributes[i];
542
+ if (!oldAttribute)
543
+ continue;
544
+ if (!newElt.hasAttribute(oldAttribute.name)) {
545
+ if (ignoreAttribute(oldAttribute.name, oldElt, "remove", ctx)) {
546
+ continue;
547
+ }
548
+ oldElt.removeAttribute(oldAttribute.name);
549
+ }
550
+ }
551
+ if (!ignoreValueOfActiveElement(oldElt, ctx)) {
552
+ syncInputValue(oldElt, newElt, ctx);
553
+ }
554
+ }
555
+ if (type === 8 || type === 3) {
556
+ if (oldNode.nodeValue !== newNode.nodeValue) {
557
+ oldNode.nodeValue = newNode.nodeValue;
558
+ }
559
+ }
560
+ }
561
+ function syncInputValue(oldElement, newElement, ctx) {
562
+ if (oldElement instanceof HTMLInputElement && newElement instanceof HTMLInputElement && newElement.type !== "file") {
563
+ let newValue = newElement.value;
564
+ let oldValue = oldElement.value;
565
+ syncBooleanAttribute(oldElement, newElement, "checked", ctx);
566
+ syncBooleanAttribute(oldElement, newElement, "disabled", ctx);
567
+ if (!newElement.hasAttribute("value")) {
568
+ if (!ignoreAttribute("value", oldElement, "remove", ctx)) {
569
+ oldElement.value = "";
570
+ oldElement.removeAttribute("value");
571
+ }
572
+ } else if (oldValue !== newValue) {
573
+ if (!ignoreAttribute("value", oldElement, "update", ctx)) {
574
+ oldElement.setAttribute("value", newValue);
575
+ oldElement.value = newValue;
576
+ }
577
+ }
578
+ } else if (oldElement instanceof HTMLOptionElement && newElement instanceof HTMLOptionElement) {
579
+ syncBooleanAttribute(oldElement, newElement, "selected", ctx);
580
+ } else if (oldElement instanceof HTMLTextAreaElement && newElement instanceof HTMLTextAreaElement) {
581
+ let newValue = newElement.value;
582
+ let oldValue = oldElement.value;
583
+ if (ignoreAttribute("value", oldElement, "update", ctx)) {
584
+ return;
585
+ }
586
+ if (newValue !== oldValue) {
587
+ oldElement.value = newValue;
588
+ }
589
+ if (oldElement.firstChild && oldElement.firstChild.nodeValue !== newValue) {
590
+ oldElement.firstChild.nodeValue = newValue;
591
+ }
592
+ }
593
+ }
594
+ function syncBooleanAttribute(oldElement, newElement, attributeName, ctx) {
595
+ const newLiveValue = newElement[attributeName], oldLiveValue = oldElement[attributeName];
596
+ if (newLiveValue !== oldLiveValue) {
597
+ const ignoreUpdate = ignoreAttribute(attributeName, oldElement, "update", ctx);
598
+ if (!ignoreUpdate) {
599
+ oldElement[attributeName] = newElement[attributeName];
600
+ }
601
+ if (newLiveValue) {
602
+ if (!ignoreUpdate) {
603
+ oldElement.setAttribute(attributeName, "");
604
+ }
605
+ } else {
606
+ if (!ignoreAttribute(attributeName, oldElement, "remove", ctx)) {
607
+ oldElement.removeAttribute(attributeName);
608
+ }
609
+ }
610
+ }
611
+ }
612
+ function ignoreAttribute(attr, element, updateType, ctx) {
613
+ if (attr === "value" && ctx.ignoreActiveValue && element === document.activeElement) {
614
+ return true;
615
+ }
616
+ return ctx.callbacks.beforeAttributeUpdated(attr, element, updateType) === false;
617
+ }
618
+ function ignoreValueOfActiveElement(possibleActiveElement, ctx) {
619
+ return !!ctx.ignoreActiveValue && possibleActiveElement === document.activeElement && possibleActiveElement !== document.body;
620
+ }
621
+ return morphNode2;
622
+ }();
623
+ function withHeadBlocking(ctx, oldNode, newNode, callback) {
624
+ if (ctx.head.block) {
625
+ const oldHead = oldNode.querySelector("head");
626
+ const newHead = newNode.querySelector("head");
627
+ if (oldHead && newHead) {
628
+ const promises = handleHeadElement(oldHead, newHead, ctx);
629
+ return Promise.all(promises).then(() => {
630
+ const newCtx = Object.assign(ctx, {
631
+ head: {
632
+ block: false,
633
+ ignore: true
634
+ }
635
+ });
636
+ return callback(newCtx);
637
+ });
638
+ }
639
+ }
640
+ return callback(ctx);
641
+ }
642
+ function handleHeadElement(oldHead, newHead, ctx) {
643
+ let added = [];
644
+ let removed = [];
645
+ let preserved = [];
646
+ let nodesToAppend = [];
647
+ let srcToNewHeadNodes = new Map;
648
+ for (const newHeadChild of newHead.children) {
649
+ srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
650
+ }
651
+ for (const currentHeadElt of oldHead.children) {
652
+ let inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);
653
+ let isReAppended = ctx.head.shouldReAppend(currentHeadElt);
654
+ let isPreserved = ctx.head.shouldPreserve(currentHeadElt);
655
+ if (inNewContent || isPreserved) {
656
+ if (isReAppended) {
657
+ removed.push(currentHeadElt);
658
+ } else {
659
+ srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
660
+ preserved.push(currentHeadElt);
661
+ }
662
+ } else {
663
+ if (ctx.head.style === "append") {
664
+ if (isReAppended) {
665
+ removed.push(currentHeadElt);
666
+ nodesToAppend.push(currentHeadElt);
667
+ }
668
+ } else {
669
+ if (ctx.head.shouldRemove(currentHeadElt) !== false) {
670
+ removed.push(currentHeadElt);
671
+ }
672
+ }
673
+ }
674
+ }
675
+ nodesToAppend.push(...srcToNewHeadNodes.values());
676
+ let promises = [];
677
+ for (const newNode of nodesToAppend) {
678
+ let newElt = document.createRange().createContextualFragment(newNode.outerHTML).firstChild;
679
+ if (ctx.callbacks.beforeNodeAdded(newElt) !== false) {
680
+ if ("href" in newElt && newElt.href || "src" in newElt && newElt.src) {
681
+ let resolve;
682
+ let promise = new Promise(function(_resolve) {
683
+ resolve = _resolve;
684
+ });
685
+ newElt.addEventListener("load", function() {
686
+ resolve();
687
+ });
688
+ promises.push(promise);
689
+ }
690
+ oldHead.appendChild(newElt);
691
+ ctx.callbacks.afterNodeAdded(newElt);
692
+ added.push(newElt);
693
+ }
694
+ }
695
+ for (const removedElement of removed) {
696
+ if (ctx.callbacks.beforeNodeRemoved(removedElement) !== false) {
697
+ oldHead.removeChild(removedElement);
698
+ ctx.callbacks.afterNodeRemoved(removedElement);
699
+ }
700
+ }
701
+ ctx.head.afterHeadMorphed(oldHead, {
702
+ added,
703
+ kept: preserved,
704
+ removed
705
+ });
706
+ return promises;
707
+ }
708
+ const createMorphContext = function() {
709
+ function createMorphContext2(oldNode, newContent, config) {
710
+ const { persistentIds, idMap } = createIdMaps(oldNode, newContent);
711
+ const mergedConfig = mergeDefaults(config);
712
+ const morphStyle = mergedConfig.morphStyle || "outerHTML";
713
+ if (!["innerHTML", "outerHTML"].includes(morphStyle)) {
714
+ throw `Do not understand how to morph style ${morphStyle}`;
715
+ }
716
+ return {
717
+ target: oldNode,
718
+ newContent,
719
+ config: mergedConfig,
720
+ morphStyle,
721
+ ignoreActive: mergedConfig.ignoreActive,
722
+ ignoreActiveValue: mergedConfig.ignoreActiveValue,
723
+ restoreFocus: mergedConfig.restoreFocus,
724
+ idMap,
725
+ persistentIds,
726
+ pantry: createPantry(),
727
+ activeElementAndParents: createActiveElementAndParents(oldNode),
728
+ callbacks: mergedConfig.callbacks,
729
+ head: mergedConfig.head
730
+ };
731
+ }
732
+ function mergeDefaults(config) {
733
+ let finalConfig = Object.assign({}, defaults);
734
+ Object.assign(finalConfig, config);
735
+ finalConfig.callbacks = Object.assign({}, defaults.callbacks, config.callbacks);
736
+ finalConfig.head = Object.assign({}, defaults.head, config.head);
737
+ return finalConfig;
738
+ }
739
+ function createPantry() {
740
+ const pantry = document.createElement("div");
741
+ pantry.hidden = true;
742
+ document.body.insertAdjacentElement("afterend", pantry);
743
+ return pantry;
744
+ }
745
+ function createActiveElementAndParents(oldNode) {
746
+ let activeElementAndParents = [];
747
+ let elt = document.activeElement;
748
+ if (elt?.tagName !== "BODY" && oldNode.contains(elt)) {
749
+ while (elt) {
750
+ activeElementAndParents.push(elt);
751
+ if (elt === oldNode)
752
+ break;
753
+ elt = elt.parentElement;
754
+ }
755
+ }
756
+ return activeElementAndParents;
757
+ }
758
+ function findIdElements(root) {
759
+ let elements = Array.from(root.querySelectorAll("[id]"));
760
+ if (root.getAttribute?.("id")) {
761
+ elements.push(root);
762
+ }
763
+ return elements;
764
+ }
765
+ function populateIdMapWithTree(idMap, persistentIds, root, elements) {
766
+ for (const elt of elements) {
767
+ const id = elt.getAttribute("id");
768
+ if (persistentIds.has(id)) {
769
+ let current = elt;
770
+ while (current) {
771
+ let idSet = idMap.get(current);
772
+ if (idSet == null) {
773
+ idSet = new Set;
774
+ idMap.set(current, idSet);
775
+ }
776
+ idSet.add(id);
777
+ if (current === root)
778
+ break;
779
+ current = current.parentElement;
780
+ }
781
+ }
782
+ }
783
+ }
784
+ function createIdMaps(oldContent, newContent) {
785
+ const oldIdElements = findIdElements(oldContent);
786
+ const newIdElements = findIdElements(newContent);
787
+ const persistentIds = createPersistentIds(oldIdElements, newIdElements);
788
+ let idMap = new Map;
789
+ populateIdMapWithTree(idMap, persistentIds, oldContent, oldIdElements);
790
+ const newRoot = newContent.__idiomorphRoot || newContent;
791
+ populateIdMapWithTree(idMap, persistentIds, newRoot, newIdElements);
792
+ return { persistentIds, idMap };
793
+ }
794
+ function createPersistentIds(oldIdElements, newIdElements) {
795
+ let duplicateIds = new Set;
796
+ let oldIdTagNameMap = new Map;
797
+ for (const { id, tagName } of oldIdElements) {
798
+ if (oldIdTagNameMap.has(id)) {
799
+ duplicateIds.add(id);
800
+ } else {
801
+ oldIdTagNameMap.set(id, tagName);
802
+ }
803
+ }
804
+ let persistentIds = new Set;
805
+ for (const { id, tagName } of newIdElements) {
806
+ if (persistentIds.has(id)) {
807
+ duplicateIds.add(id);
808
+ } else if (oldIdTagNameMap.get(id) === tagName) {
809
+ persistentIds.add(id);
810
+ }
811
+ }
812
+ for (const id of duplicateIds) {
813
+ persistentIds.delete(id);
814
+ }
815
+ return persistentIds;
816
+ }
817
+ return createMorphContext2;
818
+ }();
819
+ const { normalizeElement, normalizeParent } = function() {
820
+ const generatedByIdiomorph = new WeakSet;
821
+ function normalizeElement2(content) {
822
+ if (content instanceof Document) {
823
+ return content.documentElement;
824
+ } else {
825
+ return content;
826
+ }
827
+ }
828
+ function normalizeParent2(newContent) {
829
+ if (newContent == null) {
830
+ return document.createElement("div");
831
+ } else if (typeof newContent === "string") {
832
+ return normalizeParent2(parseContent(newContent));
833
+ } else if (generatedByIdiomorph.has(newContent)) {
834
+ return newContent;
835
+ } else if (newContent instanceof Node) {
836
+ if (newContent.parentNode) {
837
+ return new SlicedParentNode(newContent);
838
+ } else {
839
+ const dummyParent = document.createElement("div");
840
+ dummyParent.append(newContent);
841
+ return dummyParent;
842
+ }
843
+ } else {
844
+ const dummyParent = document.createElement("div");
845
+ for (const elt of [...newContent]) {
846
+ dummyParent.append(elt);
847
+ }
848
+ return dummyParent;
849
+ }
850
+ }
851
+
852
+ class SlicedParentNode {
853
+ constructor(node) {
854
+ this.originalNode = node;
855
+ this.realParentNode = node.parentNode;
856
+ this.previousSibling = node.previousSibling;
857
+ this.nextSibling = node.nextSibling;
858
+ }
859
+ get childNodes() {
860
+ const nodes = [];
861
+ let cursor = this.previousSibling ? this.previousSibling.nextSibling : this.realParentNode.firstChild;
862
+ while (cursor && cursor != this.nextSibling) {
863
+ nodes.push(cursor);
864
+ cursor = cursor.nextSibling;
865
+ }
866
+ return nodes;
867
+ }
868
+ querySelectorAll(selector) {
869
+ return this.childNodes.reduce((results, node) => {
870
+ if (node instanceof Element) {
871
+ if (node.matches(selector))
872
+ results.push(node);
873
+ const nodeList = node.querySelectorAll(selector);
874
+ for (let i = 0;i < nodeList.length; i++) {
875
+ results.push(nodeList[i]);
876
+ }
877
+ }
878
+ return results;
879
+ }, []);
880
+ }
881
+ insertBefore(node, referenceNode) {
882
+ return this.realParentNode.insertBefore(node, referenceNode);
883
+ }
884
+ moveBefore(node, referenceNode) {
885
+ return this.realParentNode.moveBefore(node, referenceNode);
886
+ }
887
+ get __idiomorphRoot() {
888
+ return this.originalNode;
889
+ }
890
+ }
891
+ function parseContent(newContent) {
892
+ let parser = new DOMParser;
893
+ let contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, "");
894
+ if (contentWithSvgsRemoved.match(/<\/html>/) || contentWithSvgsRemoved.match(/<\/head>/) || contentWithSvgsRemoved.match(/<\/body>/)) {
895
+ let content = parser.parseFromString(newContent, "text/html");
896
+ if (contentWithSvgsRemoved.match(/<\/html>/)) {
897
+ generatedByIdiomorph.add(content);
898
+ return content;
899
+ } else {
900
+ let htmlElement = content.firstChild;
901
+ if (htmlElement) {
902
+ generatedByIdiomorph.add(htmlElement);
903
+ }
904
+ return htmlElement;
905
+ }
906
+ } else {
907
+ let responseDoc = parser.parseFromString("<body><template>" + newContent + "</template></body>", "text/html");
908
+ let content = responseDoc.body.querySelector("template").content;
909
+ generatedByIdiomorph.add(content);
910
+ return content;
911
+ }
912
+ }
913
+ return { normalizeElement: normalizeElement2, normalizeParent: normalizeParent2 };
914
+ }();
915
+ return {
916
+ morph,
917
+ defaults
918
+ };
919
+ }();
920
+
921
+ // src/utils.js
922
+ function splitUrl(url) {
923
+ let hash = "";
924
+ let params = "";
925
+ let index = url.indexOf("#");
926
+ if (index >= 0) {
927
+ hash = url.slice(index);
928
+ url = url.slice(0, index);
929
+ }
930
+ const comboSign = url.indexOf("??");
931
+ if (comboSign >= 0) {
932
+ if (comboSign + 1 !== url.lastIndexOf("?")) {
933
+ index = url.lastIndexOf("?");
934
+ }
935
+ } else {
936
+ index = url.indexOf("?");
937
+ }
938
+ if (index >= 0) {
939
+ params = url.slice(index);
940
+ url = url.slice(0, index);
941
+ }
942
+ return { url, params, hash };
943
+ }
944
+ function pathFromUrl(url) {
945
+ if (!url) {
946
+ return "";
947
+ }
948
+ let path;
949
+ ({ url } = splitUrl(url));
950
+ if (url.indexOf("file://") === 0) {
951
+ path = url.replace(new RegExp("^file://(localhost)?"), "");
952
+ } else {
953
+ path = url.replace(new RegExp("^([^:]+:)?//([^:/]+)(:\\d*)?/"), "/");
954
+ }
955
+ return decodeURIComponent(path);
956
+ }
957
+ function numberOfMatchingSegments(left, right) {
958
+ left = left.replace(/^\/+/, "").toLowerCase();
959
+ right = right.replace(/^\/+/, "").toLowerCase();
960
+ if (left === right) {
961
+ return 1e4;
962
+ }
963
+ const comps1 = left.split(/\/|\\/).reverse();
964
+ const comps2 = right.split(/\/|\\/).reverse();
965
+ const len = Math.min(comps1.length, comps2.length);
966
+ let eqCount = 0;
967
+ while (eqCount < len && comps1[eqCount] === comps2[eqCount]) {
968
+ ++eqCount;
969
+ }
970
+ return eqCount;
971
+ }
972
+ function pickBestMatch(path, objects, pathFunc = (s) => s) {
973
+ let bestMatch = { score: 0 };
974
+ for (const object of objects) {
975
+ const score = numberOfMatchingSegments(path, pathFunc(object));
976
+ if (score > bestMatch.score) {
977
+ bestMatch = { object, score };
978
+ }
979
+ }
980
+ if (bestMatch.score === 0) {
981
+ return null;
982
+ }
983
+ return bestMatch;
984
+ }
985
+ function generateCacheBustUrl(url) {
986
+ const { url: cleanUrl, params, hash } = splitUrl(url);
987
+ const expando = `livereload=${Date.now()}`;
988
+ if (!params) {
989
+ return `${cleanUrl}?${expando}${hash}`;
990
+ }
991
+ if (params.includes("livereload=")) {
992
+ const newParams = params.replace(/([?&])livereload=\d+/, `$1${expando}`);
993
+ return `${cleanUrl}${newParams}${hash}`;
994
+ }
995
+ return `${cleanUrl}${params}&${expando}${hash}`;
996
+ }
997
+ function waitForStylesheetLoad(linkElement, timeout = 15000) {
998
+ return new Promise((resolve) => {
999
+ let resolved = false;
1000
+ const finish = () => {
1001
+ if (resolved)
1002
+ return;
1003
+ resolved = true;
1004
+ resolve();
1005
+ };
1006
+ linkElement.onload = () => {
1007
+ finish();
1008
+ };
1009
+ const pollInterval = 50;
1010
+ const poll = () => {
1011
+ if (resolved)
1012
+ return;
1013
+ if (linkElement.sheet) {
1014
+ finish();
1015
+ return;
1016
+ }
1017
+ setTimeout(poll, pollInterval);
1018
+ };
1019
+ setTimeout(poll, pollInterval);
1020
+ setTimeout(finish, timeout);
1021
+ });
1022
+ }
1023
+
1024
+ // src/morpher.js
1025
+ class Morpher {
1026
+ constructor(window2, console2, Timer2, importCacheWaitPeriod = 200) {
1027
+ this.window = window2;
1028
+ this.console = console2;
1029
+ this.Timer = Timer2;
1030
+ this.document = window2.document;
1031
+ this.importCacheWaitPeriod = importCacheWaitPeriod;
1032
+ }
1033
+ reload(path, options = {}) {
1034
+ const isCSSFile = path.match(/\.css(?:\.map)?$/i);
1035
+ const isImageFile = path.match(/\.(jpe?g|png|gif|svg|webp|ico)$/i);
1036
+ const isJSFile = path.match(/\.m?js$/i);
1037
+ if (isCSSFile && options.liveCSS) {
1038
+ return this.reloadStylesheet(path, options);
1039
+ }
1040
+ if (isImageFile && options.liveImg) {
1041
+ return this.reloadImages(path);
1042
+ }
1043
+ if (isJSFile) {
1044
+ return this.reloadPage();
1045
+ }
1046
+ if (options.morphHTML) {
1047
+ return this.morphHTML(path, options);
1048
+ }
1049
+ this.reloadPage();
1050
+ }
1051
+ async morphHTML(path, options = {}) {
1052
+ try {
1053
+ const response = await fetch(this.window.location.href, {
1054
+ cache: "no-cache",
1055
+ headers: { "X-Live-Morph": "true" }
1056
+ });
1057
+ if (!response.ok) {
1058
+ throw new Error(`Fetch failed: ${response.status} ${response.statusText}`);
1059
+ }
1060
+ let html = await response.text();
1061
+ html = html.replace(/<!DOCTYPE[^>]*>/i, "").trim();
1062
+ Idiomorph.morph(this.document.documentElement, html, {
1063
+ head: {
1064
+ style: "merge",
1065
+ shouldPreserve: (elt) => {
1066
+ if (elt.tagName === "SCRIPT" && elt.src) {
1067
+ return elt.src.toLowerCase().includes("livereload-morph");
1068
+ }
1069
+ return false;
1070
+ }
1071
+ },
1072
+ callbacks: {
1073
+ beforeAttributeUpdated: (attributeName, node, mutationType) => {
1074
+ if (node.tagName === "INPUT" || node.tagName === "TEXTAREA" || node.tagName === "SELECT") {
1075
+ if (attributeName === "value" || attributeName === "checked") {
1076
+ return false;
1077
+ }
1078
+ }
1079
+ if (node.tagName === "DETAILS" && attributeName === "open") {
1080
+ return false;
1081
+ }
1082
+ return true;
1083
+ }
1084
+ }
1085
+ });
1086
+ this.console.log("HTML morphed successfully");
1087
+ } catch (error) {
1088
+ this.console.error(`Morph failed: ${error.message}`);
1089
+ if (options.fallbackToReload !== false) {
1090
+ this.console.log("Falling back to full page reload");
1091
+ this.reloadPage();
1092
+ }
1093
+ }
1094
+ }
1095
+ async reloadStylesheet(path, options = {}) {
1096
+ try {
1097
+ const links = Array.from(this.document.getElementsByTagName("link")).filter((link) => link.rel && link.rel.match(/^stylesheet$/i) && !link.__LiveReload_pendingRemoval);
1098
+ const imported = [];
1099
+ for (const style of Array.from(this.document.getElementsByTagName("style"))) {
1100
+ if (style.sheet) {
1101
+ this.collectImportedStylesheets(style, style.sheet, imported);
1102
+ }
1103
+ }
1104
+ for (const link of links) {
1105
+ if (link.sheet) {
1106
+ this.collectImportedStylesheets(link, link.sheet, imported);
1107
+ }
1108
+ }
1109
+ if (this.window.StyleFix && this.document.querySelectorAll) {
1110
+ for (const style of Array.from(this.document.querySelectorAll("style[data-href]"))) {
1111
+ links.push(style);
1112
+ }
1113
+ }
1114
+ this.console.log(`CSS reload: found ${links.length} LINKed stylesheets, ${imported.length} @imported stylesheets`);
1115
+ const match = pickBestMatch(path, links.concat(imported), (item) => pathFromUrl(item.href || this.linkHref(item)));
1116
+ if (!match) {
1117
+ if (options.reloadMissingCSS !== false) {
1118
+ this.console.log(`CSS reload: no match found for '${path}', reloading all stylesheets`);
1119
+ for (const link of links) {
1120
+ await this.reattachStylesheetLink(link);
1121
+ }
1122
+ } else {
1123
+ this.console.log(`CSS reload: no match found for '${path}', skipping (reloadMissingCSS=false)`);
1124
+ }
1125
+ return;
1126
+ }
1127
+ if (match.object.rule) {
1128
+ this.console.log(`CSS reload: reloading @imported stylesheet: ${match.object.href}`);
1129
+ await this.reattachImportedRule(match.object);
1130
+ } else {
1131
+ this.console.log(`CSS reload: reloading stylesheet: ${this.linkHref(match.object)}`);
1132
+ await this.reattachStylesheetLink(match.object);
1133
+ }
1134
+ } catch (error) {
1135
+ this.console.error(`Stylesheet reload failed: ${error.message}`);
1136
+ this.console.error("Stack:", error.stack);
1137
+ }
1138
+ }
1139
+ async reattachStylesheetLink(link) {
1140
+ if (link.__LiveReload_pendingRemoval) {
1141
+ return;
1142
+ }
1143
+ link.__LiveReload_pendingRemoval = true;
1144
+ let clone;
1145
+ if (link.tagName === "STYLE") {
1146
+ clone = this.document.createElement("link");
1147
+ clone.rel = "stylesheet";
1148
+ clone.media = link.media;
1149
+ clone.disabled = link.disabled;
1150
+ } else {
1151
+ clone = link.cloneNode(false);
1152
+ }
1153
+ clone.href = generateCacheBustUrl(this.linkHref(link));
1154
+ const parent = link.parentNode;
1155
+ if (parent.lastChild === link) {
1156
+ parent.appendChild(clone);
1157
+ } else {
1158
+ parent.insertBefore(clone, link.nextSibling);
1159
+ }
1160
+ await waitForStylesheetLoad(clone);
1161
+ const additionalWait = /AppleWebKit/.test(this.window.navigator.userAgent) ? 5 : 200;
1162
+ await new Promise((resolve) => this.Timer.start(additionalWait, resolve));
1163
+ if (link.parentNode) {
1164
+ link.parentNode.removeChild(link);
1165
+ }
1166
+ if (this.window.StyleFix) {
1167
+ this.window.StyleFix.link(clone);
1168
+ }
1169
+ }
1170
+ reloadPage() {
1171
+ this.window.location.reload();
1172
+ }
1173
+ reloadImages(path) {
1174
+ for (const img of Array.from(this.document.images)) {
1175
+ if (this.pathsMatch(path, pathFromUrl(img.src))) {
1176
+ img.src = generateCacheBustUrl(img.src);
1177
+ }
1178
+ }
1179
+ const bgSelectors = ["background", "border"];
1180
+ const bgStyleNames = ["backgroundImage", "borderImage", "webkitBorderImage", "MozBorderImage"];
1181
+ for (const selector of bgSelectors) {
1182
+ for (const el of Array.from(this.document.querySelectorAll(`[style*=${selector}]`))) {
1183
+ this.reloadStyleImages(el.style, bgStyleNames, path);
1184
+ }
1185
+ }
1186
+ for (const sheet of Array.from(this.document.styleSheets)) {
1187
+ this.reloadStylesheetImages(sheet, path);
1188
+ }
1189
+ this.console.log(`Image reload: ${path}`);
1190
+ }
1191
+ reloadStylesheetImages(styleSheet, path) {
1192
+ let rules;
1193
+ try {
1194
+ rules = (styleSheet || {}).cssRules;
1195
+ } catch (e) {
1196
+ return;
1197
+ }
1198
+ if (!rules)
1199
+ return;
1200
+ const bgStyleNames = ["backgroundImage", "borderImage", "webkitBorderImage", "MozBorderImage"];
1201
+ for (const rule of Array.from(rules)) {
1202
+ switch (rule.type) {
1203
+ case CSSRule.IMPORT_RULE:
1204
+ this.reloadStylesheetImages(rule.styleSheet, path);
1205
+ break;
1206
+ case CSSRule.STYLE_RULE:
1207
+ this.reloadStyleImages(rule.style, bgStyleNames, path);
1208
+ break;
1209
+ case CSSRule.MEDIA_RULE:
1210
+ this.reloadStylesheetImages(rule, path);
1211
+ break;
1212
+ }
1213
+ }
1214
+ }
1215
+ reloadStyleImages(style, styleNames, path) {
1216
+ for (const styleName of styleNames) {
1217
+ const value = style[styleName];
1218
+ if (typeof value === "string") {
1219
+ const newValue = value.replace(/\burl\s*\(([^)]*)\)/g, (match, src) => {
1220
+ const cleanSrc = src.replace(/^['"]|['"]$/g, "");
1221
+ if (this.pathsMatch(path, pathFromUrl(cleanSrc))) {
1222
+ return `url(${generateCacheBustUrl(cleanSrc)})`;
1223
+ }
1224
+ return match;
1225
+ });
1226
+ if (newValue !== value) {
1227
+ style[styleName] = newValue;
1228
+ }
1229
+ }
1230
+ }
1231
+ }
1232
+ pathsMatch(path1, path2) {
1233
+ const segs1 = path1.replace(/^\//, "").split("/").reverse();
1234
+ const segs2 = path2.replace(/^\//, "").split("/").reverse();
1235
+ const len = Math.min(segs1.length, segs2.length);
1236
+ for (let i = 0;i < len; i++) {
1237
+ if (segs1[i] !== segs2[i])
1238
+ return false;
1239
+ }
1240
+ return len > 0;
1241
+ }
1242
+ linkHref(link) {
1243
+ return link.href || link.getAttribute && link.getAttribute("data-href");
1244
+ }
1245
+ collectImportedStylesheets(link, styleSheet, result) {
1246
+ let rules;
1247
+ try {
1248
+ rules = (styleSheet || {}).cssRules;
1249
+ } catch (e) {
1250
+ return;
1251
+ }
1252
+ if (rules && rules.length) {
1253
+ for (let index = 0;index < rules.length; index++) {
1254
+ const rule = rules[index];
1255
+ switch (rule.type) {
1256
+ case CSSRule.CHARSET_RULE:
1257
+ continue;
1258
+ case CSSRule.IMPORT_RULE:
1259
+ result.push({ link, rule, index, href: rule.href });
1260
+ this.collectImportedStylesheets(link, rule.styleSheet, result);
1261
+ break;
1262
+ default:
1263
+ break;
1264
+ }
1265
+ }
1266
+ }
1267
+ }
1268
+ async reattachImportedRule({ rule, index, link }) {
1269
+ const parent = rule.parentStyleSheet;
1270
+ const href = generateCacheBustUrl(rule.href);
1271
+ let media = "";
1272
+ try {
1273
+ media = rule.media.length ? [].join.call(rule.media, ", ") : "";
1274
+ } catch (e) {
1275
+ if (e.name !== "SecurityError") {
1276
+ this.console.error(`Unexpected error accessing @import media: ${e.name}: ${e.message}`);
1277
+ }
1278
+ }
1279
+ const newRule = `@import url("${href}") ${media};`;
1280
+ rule.__LiveReload_newHref = href;
1281
+ if (this.importCacheWaitPeriod > 0) {
1282
+ const tempLink = this.document.createElement("link");
1283
+ tempLink.rel = "stylesheet";
1284
+ tempLink.href = href;
1285
+ tempLink.__LiveReload_pendingRemoval = true;
1286
+ if (link.parentNode) {
1287
+ link.parentNode.insertBefore(tempLink, link);
1288
+ }
1289
+ await new Promise((resolve) => this.Timer.start(this.importCacheWaitPeriod, resolve));
1290
+ if (tempLink.parentNode) {
1291
+ tempLink.parentNode.removeChild(tempLink);
1292
+ }
1293
+ if (rule.__LiveReload_newHref !== href) {
1294
+ return;
1295
+ }
1296
+ }
1297
+ parent.insertRule(newRule, index);
1298
+ parent.deleteRule(index + 1);
1299
+ if (this.importCacheWaitPeriod > 0) {
1300
+ const updatedRule = parent.cssRules[index];
1301
+ updatedRule.__LiveReload_newHref = href;
1302
+ await new Promise((resolve) => this.Timer.start(this.importCacheWaitPeriod, resolve));
1303
+ if (updatedRule.__LiveReload_newHref !== href) {
1304
+ return;
1305
+ }
1306
+ parent.insertRule(newRule, index);
1307
+ parent.deleteRule(index + 1);
1308
+ }
1309
+ }
1310
+ }
1311
+
1312
+ // src/live-morph.js
1313
+ class LiveMorph {
1314
+ constructor(window2) {
1315
+ this.window = window2;
1316
+ this.listeners = {};
1317
+ if (!(this.WebSocket = this.window.WebSocket || this.window.MozWebSocket)) {
1318
+ console.error("[LiveMorph] Disabled because the browser does not support WebSockets");
1319
+ return;
1320
+ }
1321
+ this.options = Options.extract(this.window.document);
1322
+ if (!this.options) {
1323
+ console.error("[LiveMorph] Disabled - no configuration found");
1324
+ console.error('[LiveMorph] Set window.LiveMorphOptions = { host: "localhost", port: 35729 }');
1325
+ return;
1326
+ }
1327
+ console.log("[LiveMorph] Options loaded:", JSON.stringify({
1328
+ host: this.options.host,
1329
+ port: this.options.port,
1330
+ morphHTML: this.options.morphHTML,
1331
+ verbose: this.options.verbose
1332
+ }));
1333
+ this.console = this._setupConsole();
1334
+ this.morpher = new Morpher(this.window, this.console, Timer, this.options.importCacheWaitPeriod);
1335
+ this.connector = new Connector(this.options, this.WebSocket, Timer, {
1336
+ connecting: () => {},
1337
+ socketConnected: () => {},
1338
+ connected: (protocol) => {
1339
+ if (typeof this.listeners.connect === "function") {
1340
+ this.listeners.connect();
1341
+ }
1342
+ const { host } = this.options;
1343
+ const port = this.options.port ? `:${this.options.port}` : "";
1344
+ this.log(`Connected to ${host}${port} (protocol v${protocol})`);
1345
+ return this.sendInfo();
1346
+ },
1347
+ error: (e) => {
1348
+ if (e instanceof ProtocolError) {
1349
+ return console.log(`[LiveMorph] ${e.message}`);
1350
+ } else {
1351
+ return console.log(`[LiveMorph] Internal error: ${e.message}`);
1352
+ }
1353
+ },
1354
+ disconnected: (reason, nextDelay) => {
1355
+ if (typeof this.listeners.disconnect === "function") {
1356
+ this.listeners.disconnect();
1357
+ }
1358
+ const { host } = this.options;
1359
+ const port = this.options.port ? `:${this.options.port}` : "";
1360
+ const delaySec = (nextDelay / 1000).toFixed(0);
1361
+ switch (reason) {
1362
+ case "cannot-connect":
1363
+ return this.log(`Cannot connect to ${host}${port}, will retry in ${delaySec} sec`);
1364
+ case "broken":
1365
+ return this.log(`Disconnected from ${host}${port}, reconnecting in ${delaySec} sec`);
1366
+ case "handshake-timeout":
1367
+ return this.log(`Cannot connect to ${host}${port} (handshake timeout), will retry in ${delaySec} sec`);
1368
+ case "handshake-failed":
1369
+ return this.log(`Cannot connect to ${host}${port} (handshake failed), will retry in ${delaySec} sec`);
1370
+ case "manual":
1371
+ case "error":
1372
+ default:
1373
+ return this.log(`Disconnected from ${host}${port} (${reason}), reconnecting in ${delaySec} sec`);
1374
+ }
1375
+ },
1376
+ message: (message) => {
1377
+ switch (message.command) {
1378
+ case "reload":
1379
+ return this.performReload(message);
1380
+ case "alert":
1381
+ return this.performAlert(message);
1382
+ }
1383
+ }
1384
+ });
1385
+ this.initialized = true;
1386
+ }
1387
+ _setupConsole() {
1388
+ const hasConsole = this.window.console && this.window.console.log && this.window.console.error;
1389
+ if (!hasConsole) {
1390
+ return { log() {}, error() {} };
1391
+ }
1392
+ if (this.options.verbose) {
1393
+ return this.window.console;
1394
+ }
1395
+ return {
1396
+ log() {},
1397
+ error: this.window.console.error.bind(this.window.console)
1398
+ };
1399
+ }
1400
+ on(eventName, handler) {
1401
+ this.listeners[eventName] = handler;
1402
+ }
1403
+ log(message) {
1404
+ return this.console.log(`[LiveMorph] ${message}`);
1405
+ }
1406
+ performReload(message) {
1407
+ this.log(`Received reload request for: ${message.path}`);
1408
+ const options = {
1409
+ liveCSS: message.liveCSS != null ? message.liveCSS : true,
1410
+ liveImg: message.liveImg != null ? message.liveImg : true,
1411
+ reloadMissingCSS: message.reloadMissingCSS != null ? message.reloadMissingCSS : true,
1412
+ morphHTML: this.options.morphHTML
1413
+ };
1414
+ this.log(`Reload options: ${JSON.stringify(options)}`);
1415
+ return this.morpher.reload(message.path, options);
1416
+ }
1417
+ performAlert(message) {
1418
+ return alert(message.message);
1419
+ }
1420
+ sendInfo() {
1421
+ if (!this.initialized) {
1422
+ return;
1423
+ }
1424
+ if (!(this.connector.protocol >= 7)) {
1425
+ return;
1426
+ }
1427
+ this.connector.sendCommand({
1428
+ command: "info",
1429
+ plugins: {},
1430
+ url: this.window.location.href
1431
+ });
1432
+ }
1433
+ shutDown() {
1434
+ if (!this.initialized) {
1435
+ return;
1436
+ }
1437
+ this.connector.disconnect();
1438
+ this.log("Disconnected");
1439
+ if (typeof this.listeners.shutdown === "function") {
1440
+ this.listeners.shutdown();
1441
+ }
1442
+ }
1443
+ }
1444
+
1445
+ // src/index.js
1446
+ var liveMorph = new LiveMorph(window);
1447
+ window.LiveMorph = liveMorph;
1448
+ if (typeof document !== "undefined") {
1449
+ document.addEventListener("LiveMorphShutDown", () => {
1450
+ liveMorph.shutDown();
1451
+ });
1452
+ liveMorph.on("connect", () => {
1453
+ const event = new CustomEvent("LiveMorphConnect");
1454
+ document.dispatchEvent(event);
1455
+ });
1456
+ liveMorph.on("disconnect", () => {
1457
+ const event = new CustomEvent("LiveMorphDisconnect");
1458
+ document.dispatchEvent(event);
1459
+ });
1460
+ }
1461
+ var src_default = liveMorph;
1462
+ export {
1463
+ src_default as default
1464
+ };