turbo-spark 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3701 @@
1
+ var HotwireSpark = (function () {
2
+ 'use strict';
3
+
4
+ var adapters = {
5
+ logger: typeof console !== "undefined" ? console : undefined,
6
+ WebSocket: typeof WebSocket !== "undefined" ? WebSocket : undefined
7
+ };
8
+
9
+ var logger = {
10
+ log(...messages) {
11
+ if (this.enabled) {
12
+ messages.push(Date.now());
13
+ adapters.logger.log("[ActionCable]", ...messages);
14
+ }
15
+ }
16
+ };
17
+
18
+ const now = () => (new Date).getTime();
19
+
20
+ const secondsSince = time => (now() - time) / 1e3;
21
+
22
+ class ConnectionMonitor {
23
+ constructor(connection) {
24
+ this.visibilityDidChange = this.visibilityDidChange.bind(this);
25
+ this.connection = connection;
26
+ this.reconnectAttempts = 0;
27
+ }
28
+ start() {
29
+ if (!this.isRunning()) {
30
+ this.startedAt = now();
31
+ delete this.stoppedAt;
32
+ this.startPolling();
33
+ addEventListener("visibilitychange", this.visibilityDidChange);
34
+ logger.log(`ConnectionMonitor started. stale threshold = ${this.constructor.staleThreshold} s`);
35
+ }
36
+ }
37
+ stop() {
38
+ if (this.isRunning()) {
39
+ this.stoppedAt = now();
40
+ this.stopPolling();
41
+ removeEventListener("visibilitychange", this.visibilityDidChange);
42
+ logger.log("ConnectionMonitor stopped");
43
+ }
44
+ }
45
+ isRunning() {
46
+ return this.startedAt && !this.stoppedAt;
47
+ }
48
+ recordMessage() {
49
+ this.pingedAt = now();
50
+ }
51
+ recordConnect() {
52
+ this.reconnectAttempts = 0;
53
+ delete this.disconnectedAt;
54
+ logger.log("ConnectionMonitor recorded connect");
55
+ }
56
+ recordDisconnect() {
57
+ this.disconnectedAt = now();
58
+ logger.log("ConnectionMonitor recorded disconnect");
59
+ }
60
+ startPolling() {
61
+ this.stopPolling();
62
+ this.poll();
63
+ }
64
+ stopPolling() {
65
+ clearTimeout(this.pollTimeout);
66
+ }
67
+ poll() {
68
+ this.pollTimeout = setTimeout((() => {
69
+ this.reconnectIfStale();
70
+ this.poll();
71
+ }), this.getPollInterval());
72
+ }
73
+ getPollInterval() {
74
+ const {staleThreshold: staleThreshold, reconnectionBackoffRate: reconnectionBackoffRate} = this.constructor;
75
+ const backoff = Math.pow(1 + reconnectionBackoffRate, Math.min(this.reconnectAttempts, 10));
76
+ const jitterMax = this.reconnectAttempts === 0 ? 1 : reconnectionBackoffRate;
77
+ const jitter = jitterMax * Math.random();
78
+ return staleThreshold * 1e3 * backoff * (1 + jitter);
79
+ }
80
+ reconnectIfStale() {
81
+ if (this.connectionIsStale()) {
82
+ logger.log(`ConnectionMonitor detected stale connection. reconnectAttempts = ${this.reconnectAttempts}, time stale = ${secondsSince(this.refreshedAt)} s, stale threshold = ${this.constructor.staleThreshold} s`);
83
+ this.reconnectAttempts++;
84
+ if (this.disconnectedRecently()) {
85
+ logger.log(`ConnectionMonitor skipping reopening recent disconnect. time disconnected = ${secondsSince(this.disconnectedAt)} s`);
86
+ } else {
87
+ logger.log("ConnectionMonitor reopening");
88
+ this.connection.reopen();
89
+ }
90
+ }
91
+ }
92
+ get refreshedAt() {
93
+ return this.pingedAt ? this.pingedAt : this.startedAt;
94
+ }
95
+ connectionIsStale() {
96
+ return secondsSince(this.refreshedAt) > this.constructor.staleThreshold;
97
+ }
98
+ disconnectedRecently() {
99
+ return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold;
100
+ }
101
+ visibilityDidChange() {
102
+ if (document.visibilityState === "visible") {
103
+ setTimeout((() => {
104
+ if (this.connectionIsStale() || !this.connection.isOpen()) {
105
+ logger.log(`ConnectionMonitor reopening stale connection on visibilitychange. visibilityState = ${document.visibilityState}`);
106
+ this.connection.reopen();
107
+ }
108
+ }), 200);
109
+ }
110
+ }
111
+ }
112
+
113
+ ConnectionMonitor.staleThreshold = 6;
114
+
115
+ ConnectionMonitor.reconnectionBackoffRate = .15;
116
+
117
+ var INTERNAL = {
118
+ message_types: {
119
+ welcome: "welcome",
120
+ disconnect: "disconnect",
121
+ ping: "ping",
122
+ confirmation: "confirm_subscription",
123
+ rejection: "reject_subscription"
124
+ },
125
+ disconnect_reasons: {
126
+ unauthorized: "unauthorized",
127
+ invalid_request: "invalid_request",
128
+ server_restart: "server_restart",
129
+ remote: "remote"
130
+ },
131
+ default_mount_path: "/cable",
132
+ protocols: [ "actioncable-v1-json", "actioncable-unsupported" ]
133
+ };
134
+
135
+ const {message_types: message_types, protocols: protocols} = INTERNAL;
136
+
137
+ const supportedProtocols = protocols.slice(0, protocols.length - 1);
138
+
139
+ const indexOf = [].indexOf;
140
+
141
+ class Connection {
142
+ constructor(consumer) {
143
+ this.open = this.open.bind(this);
144
+ this.consumer = consumer;
145
+ this.subscriptions = this.consumer.subscriptions;
146
+ this.monitor = new ConnectionMonitor(this);
147
+ this.disconnected = true;
148
+ }
149
+ send(data) {
150
+ if (this.isOpen()) {
151
+ this.webSocket.send(JSON.stringify(data));
152
+ return true;
153
+ } else {
154
+ return false;
155
+ }
156
+ }
157
+ open() {
158
+ if (this.isActive()) {
159
+ logger.log(`Attempted to open WebSocket, but existing socket is ${this.getState()}`);
160
+ return false;
161
+ } else {
162
+ const socketProtocols = [ ...protocols, ...this.consumer.subprotocols || [] ];
163
+ logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${socketProtocols}`);
164
+ if (this.webSocket) {
165
+ this.uninstallEventHandlers();
166
+ }
167
+ this.webSocket = new adapters.WebSocket(this.consumer.url, socketProtocols);
168
+ this.installEventHandlers();
169
+ this.monitor.start();
170
+ return true;
171
+ }
172
+ }
173
+ close({allowReconnect: allowReconnect} = {
174
+ allowReconnect: true
175
+ }) {
176
+ if (!allowReconnect) {
177
+ this.monitor.stop();
178
+ }
179
+ if (this.isOpen()) {
180
+ return this.webSocket.close();
181
+ }
182
+ }
183
+ reopen() {
184
+ logger.log(`Reopening WebSocket, current state is ${this.getState()}`);
185
+ if (this.isActive()) {
186
+ try {
187
+ return this.close();
188
+ } catch (error) {
189
+ logger.log("Failed to reopen WebSocket", error);
190
+ } finally {
191
+ logger.log(`Reopening WebSocket in ${this.constructor.reopenDelay}ms`);
192
+ setTimeout(this.open, this.constructor.reopenDelay);
193
+ }
194
+ } else {
195
+ return this.open();
196
+ }
197
+ }
198
+ getProtocol() {
199
+ if (this.webSocket) {
200
+ return this.webSocket.protocol;
201
+ }
202
+ }
203
+ isOpen() {
204
+ return this.isState("open");
205
+ }
206
+ isActive() {
207
+ return this.isState("open", "connecting");
208
+ }
209
+ triedToReconnect() {
210
+ return this.monitor.reconnectAttempts > 0;
211
+ }
212
+ isProtocolSupported() {
213
+ return indexOf.call(supportedProtocols, this.getProtocol()) >= 0;
214
+ }
215
+ isState(...states) {
216
+ return indexOf.call(states, this.getState()) >= 0;
217
+ }
218
+ getState() {
219
+ if (this.webSocket) {
220
+ for (let state in adapters.WebSocket) {
221
+ if (adapters.WebSocket[state] === this.webSocket.readyState) {
222
+ return state.toLowerCase();
223
+ }
224
+ }
225
+ }
226
+ return null;
227
+ }
228
+ installEventHandlers() {
229
+ for (let eventName in this.events) {
230
+ const handler = this.events[eventName].bind(this);
231
+ this.webSocket[`on${eventName}`] = handler;
232
+ }
233
+ }
234
+ uninstallEventHandlers() {
235
+ for (let eventName in this.events) {
236
+ this.webSocket[`on${eventName}`] = function() {};
237
+ }
238
+ }
239
+ }
240
+
241
+ Connection.reopenDelay = 500;
242
+
243
+ Connection.prototype.events = {
244
+ message(event) {
245
+ if (!this.isProtocolSupported()) {
246
+ return;
247
+ }
248
+ const {identifier: identifier, message: message, reason: reason, reconnect: reconnect, type: type} = JSON.parse(event.data);
249
+ this.monitor.recordMessage();
250
+ switch (type) {
251
+ case message_types.welcome:
252
+ if (this.triedToReconnect()) {
253
+ this.reconnectAttempted = true;
254
+ }
255
+ this.monitor.recordConnect();
256
+ return this.subscriptions.reload();
257
+
258
+ case message_types.disconnect:
259
+ logger.log(`Disconnecting. Reason: ${reason}`);
260
+ return this.close({
261
+ allowReconnect: reconnect
262
+ });
263
+
264
+ case message_types.ping:
265
+ return null;
266
+
267
+ case message_types.confirmation:
268
+ this.subscriptions.confirmSubscription(identifier);
269
+ if (this.reconnectAttempted) {
270
+ this.reconnectAttempted = false;
271
+ return this.subscriptions.notify(identifier, "connected", {
272
+ reconnected: true
273
+ });
274
+ } else {
275
+ return this.subscriptions.notify(identifier, "connected", {
276
+ reconnected: false
277
+ });
278
+ }
279
+
280
+ case message_types.rejection:
281
+ return this.subscriptions.reject(identifier);
282
+
283
+ default:
284
+ return this.subscriptions.notify(identifier, "received", message);
285
+ }
286
+ },
287
+ open() {
288
+ logger.log(`WebSocket onopen event, using '${this.getProtocol()}' subprotocol`);
289
+ this.disconnected = false;
290
+ if (!this.isProtocolSupported()) {
291
+ logger.log("Protocol is unsupported. Stopping monitor and disconnecting.");
292
+ return this.close({
293
+ allowReconnect: false
294
+ });
295
+ }
296
+ },
297
+ close(event) {
298
+ logger.log("WebSocket onclose event");
299
+ if (this.disconnected) {
300
+ return;
301
+ }
302
+ this.disconnected = true;
303
+ this.monitor.recordDisconnect();
304
+ return this.subscriptions.notifyAll("disconnected", {
305
+ willAttemptReconnect: this.monitor.isRunning()
306
+ });
307
+ },
308
+ error() {
309
+ logger.log("WebSocket onerror event");
310
+ }
311
+ };
312
+
313
+ const extend$1 = function(object, properties) {
314
+ if (properties != null) {
315
+ for (let key in properties) {
316
+ const value = properties[key];
317
+ object[key] = value;
318
+ }
319
+ }
320
+ return object;
321
+ };
322
+
323
+ class Subscription {
324
+ constructor(consumer, params = {}, mixin) {
325
+ this.consumer = consumer;
326
+ this.identifier = JSON.stringify(params);
327
+ extend$1(this, mixin);
328
+ }
329
+ perform(action, data = {}) {
330
+ data.action = action;
331
+ return this.send(data);
332
+ }
333
+ send(data) {
334
+ return this.consumer.send({
335
+ command: "message",
336
+ identifier: this.identifier,
337
+ data: JSON.stringify(data)
338
+ });
339
+ }
340
+ unsubscribe() {
341
+ return this.consumer.subscriptions.remove(this);
342
+ }
343
+ }
344
+
345
+ class SubscriptionGuarantor {
346
+ constructor(subscriptions) {
347
+ this.subscriptions = subscriptions;
348
+ this.pendingSubscriptions = [];
349
+ }
350
+ guarantee(subscription) {
351
+ if (this.pendingSubscriptions.indexOf(subscription) == -1) {
352
+ logger.log(`SubscriptionGuarantor guaranteeing ${subscription.identifier}`);
353
+ this.pendingSubscriptions.push(subscription);
354
+ } else {
355
+ logger.log(`SubscriptionGuarantor already guaranteeing ${subscription.identifier}`);
356
+ }
357
+ this.startGuaranteeing();
358
+ }
359
+ forget(subscription) {
360
+ logger.log(`SubscriptionGuarantor forgetting ${subscription.identifier}`);
361
+ this.pendingSubscriptions = this.pendingSubscriptions.filter((s => s !== subscription));
362
+ }
363
+ startGuaranteeing() {
364
+ this.stopGuaranteeing();
365
+ this.retrySubscribing();
366
+ }
367
+ stopGuaranteeing() {
368
+ clearTimeout(this.retryTimeout);
369
+ }
370
+ retrySubscribing() {
371
+ this.retryTimeout = setTimeout((() => {
372
+ if (this.subscriptions && typeof this.subscriptions.subscribe === "function") {
373
+ this.pendingSubscriptions.map((subscription => {
374
+ logger.log(`SubscriptionGuarantor resubscribing ${subscription.identifier}`);
375
+ this.subscriptions.subscribe(subscription);
376
+ }));
377
+ }
378
+ }), 500);
379
+ }
380
+ }
381
+
382
+ class Subscriptions {
383
+ constructor(consumer) {
384
+ this.consumer = consumer;
385
+ this.guarantor = new SubscriptionGuarantor(this);
386
+ this.subscriptions = [];
387
+ }
388
+ create(channelName, mixin) {
389
+ const channel = channelName;
390
+ const params = typeof channel === "object" ? channel : {
391
+ channel: channel
392
+ };
393
+ const subscription = new Subscription(this.consumer, params, mixin);
394
+ return this.add(subscription);
395
+ }
396
+ add(subscription) {
397
+ this.subscriptions.push(subscription);
398
+ this.consumer.ensureActiveConnection();
399
+ this.notify(subscription, "initialized");
400
+ this.subscribe(subscription);
401
+ return subscription;
402
+ }
403
+ remove(subscription) {
404
+ this.forget(subscription);
405
+ if (!this.findAll(subscription.identifier).length) {
406
+ this.sendCommand(subscription, "unsubscribe");
407
+ }
408
+ return subscription;
409
+ }
410
+ reject(identifier) {
411
+ return this.findAll(identifier).map((subscription => {
412
+ this.forget(subscription);
413
+ this.notify(subscription, "rejected");
414
+ return subscription;
415
+ }));
416
+ }
417
+ forget(subscription) {
418
+ this.guarantor.forget(subscription);
419
+ this.subscriptions = this.subscriptions.filter((s => s !== subscription));
420
+ return subscription;
421
+ }
422
+ findAll(identifier) {
423
+ return this.subscriptions.filter((s => s.identifier === identifier));
424
+ }
425
+ reload() {
426
+ return this.subscriptions.map((subscription => this.subscribe(subscription)));
427
+ }
428
+ notifyAll(callbackName, ...args) {
429
+ return this.subscriptions.map((subscription => this.notify(subscription, callbackName, ...args)));
430
+ }
431
+ notify(subscription, callbackName, ...args) {
432
+ let subscriptions;
433
+ if (typeof subscription === "string") {
434
+ subscriptions = this.findAll(subscription);
435
+ } else {
436
+ subscriptions = [ subscription ];
437
+ }
438
+ return subscriptions.map((subscription => typeof subscription[callbackName] === "function" ? subscription[callbackName](...args) : undefined));
439
+ }
440
+ subscribe(subscription) {
441
+ if (this.sendCommand(subscription, "subscribe")) {
442
+ this.guarantor.guarantee(subscription);
443
+ }
444
+ }
445
+ confirmSubscription(identifier) {
446
+ logger.log(`Subscription confirmed ${identifier}`);
447
+ this.findAll(identifier).map((subscription => this.guarantor.forget(subscription)));
448
+ }
449
+ sendCommand(subscription, command) {
450
+ const {identifier: identifier} = subscription;
451
+ return this.consumer.send({
452
+ command: command,
453
+ identifier: identifier
454
+ });
455
+ }
456
+ }
457
+
458
+ class Consumer {
459
+ constructor(url) {
460
+ this._url = url;
461
+ this.subscriptions = new Subscriptions(this);
462
+ this.connection = new Connection(this);
463
+ this.subprotocols = [];
464
+ }
465
+ get url() {
466
+ return createWebSocketURL(this._url);
467
+ }
468
+ send(data) {
469
+ return this.connection.send(data);
470
+ }
471
+ connect() {
472
+ return this.connection.open();
473
+ }
474
+ disconnect() {
475
+ return this.connection.close({
476
+ allowReconnect: false
477
+ });
478
+ }
479
+ ensureActiveConnection() {
480
+ if (!this.connection.isActive()) {
481
+ return this.connection.open();
482
+ }
483
+ }
484
+ addSubProtocol(subprotocol) {
485
+ this.subprotocols = [ ...this.subprotocols, subprotocol ];
486
+ }
487
+ }
488
+
489
+ function createWebSocketURL(url) {
490
+ if (typeof url === "function") {
491
+ url = url();
492
+ }
493
+ if (url && !/^wss?:/i.test(url)) {
494
+ const a = document.createElement("a");
495
+ a.href = url;
496
+ a.href = a.href;
497
+ a.protocol = a.protocol.replace("http", "ws");
498
+ return a.href;
499
+ } else {
500
+ return url;
501
+ }
502
+ }
503
+
504
+ function createConsumer(url = getConfig("url") || INTERNAL.default_mount_path) {
505
+ return new Consumer(url);
506
+ }
507
+
508
+ function getConfig(name) {
509
+ const element = document.head.querySelector(`meta[name='action-cable-${name}']`);
510
+ if (element) {
511
+ return element.getAttribute("content");
512
+ }
513
+ }
514
+
515
+ var consumer = createConsumer();
516
+
517
+ function nameFromFilePath(path) {
518
+ return path.split("/").pop().split(".")[0];
519
+ }
520
+ function urlWithParams(urlString, params) {
521
+ const url = new URL(urlString, window.location.origin);
522
+ Object.entries(params).forEach(_ref => {
523
+ let [key, value] = _ref;
524
+ url.searchParams.set(key, value);
525
+ });
526
+ return url.toString();
527
+ }
528
+ function cacheBustedUrl(urlString) {
529
+ return urlWithParams(urlString, {
530
+ reload: Date.now()
531
+ });
532
+ }
533
+ async function reloadHtmlDocument() {
534
+ let currentUrl = cacheBustedUrl(urlWithParams(window.location.href, {
535
+ hotwire_spark: "true"
536
+ }));
537
+ const response = await fetch(currentUrl);
538
+ if (!response.ok) {
539
+ throw new Error(`${response.status} when fetching ${currentUrl}`);
540
+ }
541
+ const fetchedHTML = await response.text();
542
+ const parser = new DOMParser();
543
+ return parser.parseFromString(fetchedHTML, "text/html");
544
+ }
545
+
546
+ // base IIFE to define idiomorph
547
+ var Idiomorph = (function () {
548
+
549
+ //=============================================================================
550
+ // AND NOW IT BEGINS...
551
+ //=============================================================================
552
+ let EMPTY_SET = new Set();
553
+
554
+ // default configuration values, updatable by users now
555
+ let defaults = {
556
+ morphStyle: "outerHTML",
557
+ callbacks : {
558
+ beforeNodeAdded: noOp,
559
+ afterNodeAdded: noOp,
560
+ beforeNodeMorphed: noOp,
561
+ afterNodeMorphed: noOp,
562
+ beforeNodeRemoved: noOp,
563
+ afterNodeRemoved: noOp,
564
+ beforeAttributeUpdated: noOp,
565
+
566
+ },
567
+ head: {
568
+ style: 'merge',
569
+ shouldPreserve: function (elt) {
570
+ return elt.getAttribute("im-preserve") === "true";
571
+ },
572
+ shouldReAppend: function (elt) {
573
+ return elt.getAttribute("im-re-append") === "true";
574
+ },
575
+ shouldRemove: noOp,
576
+ afterHeadMorphed: noOp,
577
+ }
578
+ };
579
+
580
+ //=============================================================================
581
+ // Core Morphing Algorithm - morph, morphNormalizedContent, morphOldNodeTo, morphChildren
582
+ //=============================================================================
583
+ function morph(oldNode, newContent, config = {}) {
584
+
585
+ if (oldNode instanceof Document) {
586
+ oldNode = oldNode.documentElement;
587
+ }
588
+
589
+ if (typeof newContent === 'string') {
590
+ newContent = parseContent(newContent);
591
+ }
592
+
593
+ let normalizedContent = normalizeContent(newContent);
594
+
595
+ let ctx = createMorphContext(oldNode, normalizedContent, config);
596
+
597
+ return morphNormalizedContent(oldNode, normalizedContent, ctx);
598
+ }
599
+
600
+ function morphNormalizedContent(oldNode, normalizedNewContent, ctx) {
601
+ if (ctx.head.block) {
602
+ let oldHead = oldNode.querySelector('head');
603
+ let newHead = normalizedNewContent.querySelector('head');
604
+ if (oldHead && newHead) {
605
+ let promises = handleHeadElement(newHead, oldHead, ctx);
606
+ // when head promises resolve, call morph again, ignoring the head tag
607
+ Promise.all(promises).then(function () {
608
+ morphNormalizedContent(oldNode, normalizedNewContent, Object.assign(ctx, {
609
+ head: {
610
+ block: false,
611
+ ignore: true
612
+ }
613
+ }));
614
+ });
615
+ return;
616
+ }
617
+ }
618
+
619
+ if (ctx.morphStyle === "innerHTML") {
620
+
621
+ // innerHTML, so we are only updating the children
622
+ morphChildren(normalizedNewContent, oldNode, ctx);
623
+ return oldNode.children;
624
+
625
+ } else if (ctx.morphStyle === "outerHTML" || ctx.morphStyle == null) {
626
+ // otherwise find the best element match in the new content, morph that, and merge its siblings
627
+ // into either side of the best match
628
+ let bestMatch = findBestNodeMatch(normalizedNewContent, oldNode, ctx);
629
+
630
+ // stash the siblings that will need to be inserted on either side of the best match
631
+ let previousSibling = bestMatch?.previousSibling;
632
+ let nextSibling = bestMatch?.nextSibling;
633
+
634
+ // morph it
635
+ let morphedNode = morphOldNodeTo(oldNode, bestMatch, ctx);
636
+
637
+ if (bestMatch) {
638
+ // if there was a best match, merge the siblings in too and return the
639
+ // whole bunch
640
+ return insertSiblings(previousSibling, morphedNode, nextSibling);
641
+ } else {
642
+ // otherwise nothing was added to the DOM
643
+ return []
644
+ }
645
+ } else {
646
+ throw "Do not understand how to morph style " + ctx.morphStyle;
647
+ }
648
+ }
649
+
650
+
651
+ /**
652
+ * @param possibleActiveElement
653
+ * @param ctx
654
+ * @returns {boolean}
655
+ */
656
+ function ignoreValueOfActiveElement(possibleActiveElement, ctx) {
657
+ return ctx.ignoreActiveValue && possibleActiveElement === document.activeElement;
658
+ }
659
+
660
+ /**
661
+ * @param oldNode root node to merge content into
662
+ * @param newContent new content to merge
663
+ * @param ctx the merge context
664
+ * @returns {Element} the element that ended up in the DOM
665
+ */
666
+ function morphOldNodeTo(oldNode, newContent, ctx) {
667
+ if (ctx.ignoreActive && oldNode === document.activeElement) ; else if (newContent == null) {
668
+ if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode;
669
+
670
+ oldNode.remove();
671
+ ctx.callbacks.afterNodeRemoved(oldNode);
672
+ return null;
673
+ } else if (!isSoftMatch(oldNode, newContent)) {
674
+ if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode;
675
+ if (ctx.callbacks.beforeNodeAdded(newContent) === false) return oldNode;
676
+
677
+ oldNode.parentElement.replaceChild(newContent, oldNode);
678
+ ctx.callbacks.afterNodeAdded(newContent);
679
+ ctx.callbacks.afterNodeRemoved(oldNode);
680
+ return newContent;
681
+ } else {
682
+ if (ctx.callbacks.beforeNodeMorphed(oldNode, newContent) === false) return oldNode;
683
+
684
+ if (oldNode instanceof HTMLHeadElement && ctx.head.ignore) ; else if (oldNode instanceof HTMLHeadElement && ctx.head.style !== "morph") {
685
+ handleHeadElement(newContent, oldNode, ctx);
686
+ } else {
687
+ syncNodeFrom(newContent, oldNode, ctx);
688
+ if (!ignoreValueOfActiveElement(oldNode, ctx)) {
689
+ morphChildren(newContent, oldNode, ctx);
690
+ }
691
+ }
692
+ ctx.callbacks.afterNodeMorphed(oldNode, newContent);
693
+ return oldNode;
694
+ }
695
+ }
696
+
697
+ /**
698
+ * This is the core algorithm for matching up children. The idea is to use id sets to try to match up
699
+ * nodes as faithfully as possible. We greedily match, which allows us to keep the algorithm fast, but
700
+ * by using id sets, we are able to better match up with content deeper in the DOM.
701
+ *
702
+ * Basic algorithm is, for each node in the new content:
703
+ *
704
+ * - if we have reached the end of the old parent, append the new content
705
+ * - if the new content has an id set match with the current insertion point, morph
706
+ * - search for an id set match
707
+ * - if id set match found, morph
708
+ * - otherwise search for a "soft" match
709
+ * - if a soft match is found, morph
710
+ * - otherwise, prepend the new node before the current insertion point
711
+ *
712
+ * The two search algorithms terminate if competing node matches appear to outweigh what can be achieved
713
+ * with the current node. See findIdSetMatch() and findSoftMatch() for details.
714
+ *
715
+ * @param {Element} newParent the parent element of the new content
716
+ * @param {Element } oldParent the old content that we are merging the new content into
717
+ * @param ctx the merge context
718
+ */
719
+ function morphChildren(newParent, oldParent, ctx) {
720
+
721
+ let nextNewChild = newParent.firstChild;
722
+ let insertionPoint = oldParent.firstChild;
723
+ let newChild;
724
+
725
+ // run through all the new content
726
+ while (nextNewChild) {
727
+
728
+ newChild = nextNewChild;
729
+ nextNewChild = newChild.nextSibling;
730
+
731
+ // if we are at the end of the exiting parent's children, just append
732
+ if (insertionPoint == null) {
733
+ if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
734
+
735
+ oldParent.appendChild(newChild);
736
+ ctx.callbacks.afterNodeAdded(newChild);
737
+ removeIdsFromConsideration(ctx, newChild);
738
+ continue;
739
+ }
740
+
741
+ // if the current node has an id set match then morph
742
+ if (isIdSetMatch(newChild, insertionPoint, ctx)) {
743
+ morphOldNodeTo(insertionPoint, newChild, ctx);
744
+ insertionPoint = insertionPoint.nextSibling;
745
+ removeIdsFromConsideration(ctx, newChild);
746
+ continue;
747
+ }
748
+
749
+ // otherwise search forward in the existing old children for an id set match
750
+ let idSetMatch = findIdSetMatch(newParent, oldParent, newChild, insertionPoint, ctx);
751
+
752
+ // if we found a potential match, remove the nodes until that point and morph
753
+ if (idSetMatch) {
754
+ insertionPoint = removeNodesBetween(insertionPoint, idSetMatch, ctx);
755
+ morphOldNodeTo(idSetMatch, newChild, ctx);
756
+ removeIdsFromConsideration(ctx, newChild);
757
+ continue;
758
+ }
759
+
760
+ // no id set match found, so scan forward for a soft match for the current node
761
+ let softMatch = findSoftMatch(newParent, oldParent, newChild, insertionPoint, ctx);
762
+
763
+ // if we found a soft match for the current node, morph
764
+ if (softMatch) {
765
+ insertionPoint = removeNodesBetween(insertionPoint, softMatch, ctx);
766
+ morphOldNodeTo(softMatch, newChild, ctx);
767
+ removeIdsFromConsideration(ctx, newChild);
768
+ continue;
769
+ }
770
+
771
+ // abandon all hope of morphing, just insert the new child before the insertion point
772
+ // and move on
773
+ if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
774
+
775
+ oldParent.insertBefore(newChild, insertionPoint);
776
+ ctx.callbacks.afterNodeAdded(newChild);
777
+ removeIdsFromConsideration(ctx, newChild);
778
+ }
779
+
780
+ // remove any remaining old nodes that didn't match up with new content
781
+ while (insertionPoint !== null) {
782
+
783
+ let tempNode = insertionPoint;
784
+ insertionPoint = insertionPoint.nextSibling;
785
+ removeNode(tempNode, ctx);
786
+ }
787
+ }
788
+
789
+ //=============================================================================
790
+ // Attribute Syncing Code
791
+ //=============================================================================
792
+
793
+ /**
794
+ * @param attr {String} the attribute to be mutated
795
+ * @param to {Element} the element that is going to be updated
796
+ * @param updateType {("update"|"remove")}
797
+ * @param ctx the merge context
798
+ * @returns {boolean} true if the attribute should be ignored, false otherwise
799
+ */
800
+ function ignoreAttribute(attr, to, updateType, ctx) {
801
+ if(attr === 'value' && ctx.ignoreActiveValue && to === document.activeElement){
802
+ return true;
803
+ }
804
+ return ctx.callbacks.beforeAttributeUpdated(attr, to, updateType) === false;
805
+ }
806
+
807
+ /**
808
+ * syncs a given node with another node, copying over all attributes and
809
+ * inner element state from the 'from' node to the 'to' node
810
+ *
811
+ * @param {Element} from the element to copy attributes & state from
812
+ * @param {Element} to the element to copy attributes & state to
813
+ * @param ctx the merge context
814
+ */
815
+ function syncNodeFrom(from, to, ctx) {
816
+ let type = from.nodeType;
817
+
818
+ // if is an element type, sync the attributes from the
819
+ // new node into the new node
820
+ if (type === 1 /* element type */) {
821
+ const fromAttributes = from.attributes;
822
+ const toAttributes = to.attributes;
823
+ for (const fromAttribute of fromAttributes) {
824
+ if (ignoreAttribute(fromAttribute.name, to, 'update', ctx)) {
825
+ continue;
826
+ }
827
+ if (to.getAttribute(fromAttribute.name) !== fromAttribute.value) {
828
+ to.setAttribute(fromAttribute.name, fromAttribute.value);
829
+ }
830
+ }
831
+ // iterate backwards to avoid skipping over items when a delete occurs
832
+ for (let i = toAttributes.length - 1; 0 <= i; i--) {
833
+ const toAttribute = toAttributes[i];
834
+ if (ignoreAttribute(toAttribute.name, to, 'remove', ctx)) {
835
+ continue;
836
+ }
837
+ if (!from.hasAttribute(toAttribute.name)) {
838
+ to.removeAttribute(toAttribute.name);
839
+ }
840
+ }
841
+ }
842
+
843
+ // sync text nodes
844
+ if (type === 8 /* comment */ || type === 3 /* text */) {
845
+ if (to.nodeValue !== from.nodeValue) {
846
+ to.nodeValue = from.nodeValue;
847
+ }
848
+ }
849
+
850
+ if (!ignoreValueOfActiveElement(to, ctx)) {
851
+ // sync input values
852
+ syncInputValue(from, to, ctx);
853
+ }
854
+ }
855
+
856
+ /**
857
+ * @param from {Element} element to sync the value from
858
+ * @param to {Element} element to sync the value to
859
+ * @param attributeName {String} the attribute name
860
+ * @param ctx the merge context
861
+ */
862
+ function syncBooleanAttribute(from, to, attributeName, ctx) {
863
+ if (from[attributeName] !== to[attributeName]) {
864
+ let ignoreUpdate = ignoreAttribute(attributeName, to, 'update', ctx);
865
+ if (!ignoreUpdate) {
866
+ to[attributeName] = from[attributeName];
867
+ }
868
+ if (from[attributeName]) {
869
+ if (!ignoreUpdate) {
870
+ to.setAttribute(attributeName, from[attributeName]);
871
+ }
872
+ } else {
873
+ if (!ignoreAttribute(attributeName, to, 'remove', ctx)) {
874
+ to.removeAttribute(attributeName);
875
+ }
876
+ }
877
+ }
878
+ }
879
+
880
+ /**
881
+ * NB: many bothans died to bring us information:
882
+ *
883
+ * https://github.com/patrick-steele-idem/morphdom/blob/master/src/specialElHandlers.js
884
+ * https://github.com/choojs/nanomorph/blob/master/lib/morph.jsL113
885
+ *
886
+ * @param from {Element} the element to sync the input value from
887
+ * @param to {Element} the element to sync the input value to
888
+ * @param ctx the merge context
889
+ */
890
+ function syncInputValue(from, to, ctx) {
891
+ if (from instanceof HTMLInputElement &&
892
+ to instanceof HTMLInputElement &&
893
+ from.type !== 'file') {
894
+
895
+ let fromValue = from.value;
896
+ let toValue = to.value;
897
+
898
+ // sync boolean attributes
899
+ syncBooleanAttribute(from, to, 'checked', ctx);
900
+ syncBooleanAttribute(from, to, 'disabled', ctx);
901
+
902
+ if (!from.hasAttribute('value')) {
903
+ if (!ignoreAttribute('value', to, 'remove', ctx)) {
904
+ to.value = '';
905
+ to.removeAttribute('value');
906
+ }
907
+ } else if (fromValue !== toValue) {
908
+ if (!ignoreAttribute('value', to, 'update', ctx)) {
909
+ to.setAttribute('value', fromValue);
910
+ to.value = fromValue;
911
+ }
912
+ }
913
+ } else if (from instanceof HTMLOptionElement) {
914
+ syncBooleanAttribute(from, to, 'selected', ctx);
915
+ } else if (from instanceof HTMLTextAreaElement && to instanceof HTMLTextAreaElement) {
916
+ let fromValue = from.value;
917
+ let toValue = to.value;
918
+ if (ignoreAttribute('value', to, 'update', ctx)) {
919
+ return;
920
+ }
921
+ if (fromValue !== toValue) {
922
+ to.value = fromValue;
923
+ }
924
+ if (to.firstChild && to.firstChild.nodeValue !== fromValue) {
925
+ to.firstChild.nodeValue = fromValue;
926
+ }
927
+ }
928
+ }
929
+
930
+ //=============================================================================
931
+ // the HEAD tag can be handled specially, either w/ a 'merge' or 'append' style
932
+ //=============================================================================
933
+ function handleHeadElement(newHeadTag, currentHead, ctx) {
934
+
935
+ let added = [];
936
+ let removed = [];
937
+ let preserved = [];
938
+ let nodesToAppend = [];
939
+
940
+ let headMergeStyle = ctx.head.style;
941
+
942
+ // put all new head elements into a Map, by their outerHTML
943
+ let srcToNewHeadNodes = new Map();
944
+ for (const newHeadChild of newHeadTag.children) {
945
+ srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
946
+ }
947
+
948
+ // for each elt in the current head
949
+ for (const currentHeadElt of currentHead.children) {
950
+
951
+ // If the current head element is in the map
952
+ let inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);
953
+ let isReAppended = ctx.head.shouldReAppend(currentHeadElt);
954
+ let isPreserved = ctx.head.shouldPreserve(currentHeadElt);
955
+ if (inNewContent || isPreserved) {
956
+ if (isReAppended) {
957
+ // remove the current version and let the new version replace it and re-execute
958
+ removed.push(currentHeadElt);
959
+ } else {
960
+ // this element already exists and should not be re-appended, so remove it from
961
+ // the new content map, preserving it in the DOM
962
+ srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
963
+ preserved.push(currentHeadElt);
964
+ }
965
+ } else {
966
+ if (headMergeStyle === "append") {
967
+ // we are appending and this existing element is not new content
968
+ // so if and only if it is marked for re-append do we do anything
969
+ if (isReAppended) {
970
+ removed.push(currentHeadElt);
971
+ nodesToAppend.push(currentHeadElt);
972
+ }
973
+ } else {
974
+ // if this is a merge, we remove this content since it is not in the new head
975
+ if (ctx.head.shouldRemove(currentHeadElt) !== false) {
976
+ removed.push(currentHeadElt);
977
+ }
978
+ }
979
+ }
980
+ }
981
+
982
+ // Push the remaining new head elements in the Map into the
983
+ // nodes to append to the head tag
984
+ nodesToAppend.push(...srcToNewHeadNodes.values());
985
+
986
+ let promises = [];
987
+ for (const newNode of nodesToAppend) {
988
+ let newElt = document.createRange().createContextualFragment(newNode.outerHTML).firstChild;
989
+ if (ctx.callbacks.beforeNodeAdded(newElt) !== false) {
990
+ if (newElt.href || newElt.src) {
991
+ let resolve = null;
992
+ let promise = new Promise(function (_resolve) {
993
+ resolve = _resolve;
994
+ });
995
+ newElt.addEventListener('load', function () {
996
+ resolve();
997
+ });
998
+ promises.push(promise);
999
+ }
1000
+ currentHead.appendChild(newElt);
1001
+ ctx.callbacks.afterNodeAdded(newElt);
1002
+ added.push(newElt);
1003
+ }
1004
+ }
1005
+
1006
+ // remove all removed elements, after we have appended the new elements to avoid
1007
+ // additional network requests for things like style sheets
1008
+ for (const removedElement of removed) {
1009
+ if (ctx.callbacks.beforeNodeRemoved(removedElement) !== false) {
1010
+ currentHead.removeChild(removedElement);
1011
+ ctx.callbacks.afterNodeRemoved(removedElement);
1012
+ }
1013
+ }
1014
+
1015
+ ctx.head.afterHeadMorphed(currentHead, {added: added, kept: preserved, removed: removed});
1016
+ return promises;
1017
+ }
1018
+
1019
+ function noOp() {
1020
+ }
1021
+
1022
+ /*
1023
+ Deep merges the config object and the Idiomoroph.defaults object to
1024
+ produce a final configuration object
1025
+ */
1026
+ function mergeDefaults(config) {
1027
+ let finalConfig = {};
1028
+ // copy top level stuff into final config
1029
+ Object.assign(finalConfig, defaults);
1030
+ Object.assign(finalConfig, config);
1031
+
1032
+ // copy callbacks into final config (do this to deep merge the callbacks)
1033
+ finalConfig.callbacks = {};
1034
+ Object.assign(finalConfig.callbacks, defaults.callbacks);
1035
+ Object.assign(finalConfig.callbacks, config.callbacks);
1036
+
1037
+ // copy head config into final config (do this to deep merge the head)
1038
+ finalConfig.head = {};
1039
+ Object.assign(finalConfig.head, defaults.head);
1040
+ Object.assign(finalConfig.head, config.head);
1041
+ return finalConfig;
1042
+ }
1043
+
1044
+ function createMorphContext(oldNode, newContent, config) {
1045
+ config = mergeDefaults(config);
1046
+ return {
1047
+ target: oldNode,
1048
+ newContent: newContent,
1049
+ config: config,
1050
+ morphStyle: config.morphStyle,
1051
+ ignoreActive: config.ignoreActive,
1052
+ ignoreActiveValue: config.ignoreActiveValue,
1053
+ idMap: createIdMap(oldNode, newContent),
1054
+ deadIds: new Set(),
1055
+ callbacks: config.callbacks,
1056
+ head: config.head
1057
+ }
1058
+ }
1059
+
1060
+ function isIdSetMatch(node1, node2, ctx) {
1061
+ if (node1 == null || node2 == null) {
1062
+ return false;
1063
+ }
1064
+ if (node1.nodeType === node2.nodeType && node1.tagName === node2.tagName) {
1065
+ if (node1.id !== "" && node1.id === node2.id) {
1066
+ return true;
1067
+ } else {
1068
+ return getIdIntersectionCount(ctx, node1, node2) > 0;
1069
+ }
1070
+ }
1071
+ return false;
1072
+ }
1073
+
1074
+ function isSoftMatch(node1, node2) {
1075
+ if (node1 == null || node2 == null) {
1076
+ return false;
1077
+ }
1078
+ return node1.nodeType === node2.nodeType && node1.tagName === node2.tagName
1079
+ }
1080
+
1081
+ function removeNodesBetween(startInclusive, endExclusive, ctx) {
1082
+ while (startInclusive !== endExclusive) {
1083
+ let tempNode = startInclusive;
1084
+ startInclusive = startInclusive.nextSibling;
1085
+ removeNode(tempNode, ctx);
1086
+ }
1087
+ removeIdsFromConsideration(ctx, endExclusive);
1088
+ return endExclusive.nextSibling;
1089
+ }
1090
+
1091
+ //=============================================================================
1092
+ // Scans forward from the insertionPoint in the old parent looking for a potential id match
1093
+ // for the newChild. We stop if we find a potential id match for the new child OR
1094
+ // if the number of potential id matches we are discarding is greater than the
1095
+ // potential id matches for the new child
1096
+ //=============================================================================
1097
+ function findIdSetMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
1098
+
1099
+ // max id matches we are willing to discard in our search
1100
+ let newChildPotentialIdCount = getIdIntersectionCount(ctx, newChild, oldParent);
1101
+
1102
+ let potentialMatch = null;
1103
+
1104
+ // only search forward if there is a possibility of an id match
1105
+ if (newChildPotentialIdCount > 0) {
1106
+ let potentialMatch = insertionPoint;
1107
+ // if there is a possibility of an id match, scan forward
1108
+ // keep track of the potential id match count we are discarding (the
1109
+ // newChildPotentialIdCount must be greater than this to make it likely
1110
+ // worth it)
1111
+ let otherMatchCount = 0;
1112
+ while (potentialMatch != null) {
1113
+
1114
+ // If we have an id match, return the current potential match
1115
+ if (isIdSetMatch(newChild, potentialMatch, ctx)) {
1116
+ return potentialMatch;
1117
+ }
1118
+
1119
+ // computer the other potential matches of this new content
1120
+ otherMatchCount += getIdIntersectionCount(ctx, potentialMatch, newContent);
1121
+ if (otherMatchCount > newChildPotentialIdCount) {
1122
+ // if we have more potential id matches in _other_ content, we
1123
+ // do not have a good candidate for an id match, so return null
1124
+ return null;
1125
+ }
1126
+
1127
+ // advanced to the next old content child
1128
+ potentialMatch = potentialMatch.nextSibling;
1129
+ }
1130
+ }
1131
+ return potentialMatch;
1132
+ }
1133
+
1134
+ //=============================================================================
1135
+ // Scans forward from the insertionPoint in the old parent looking for a potential soft match
1136
+ // for the newChild. We stop if we find a potential soft match for the new child OR
1137
+ // if we find a potential id match in the old parents children OR if we find two
1138
+ // potential soft matches for the next two pieces of new content
1139
+ //=============================================================================
1140
+ function findSoftMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
1141
+
1142
+ let potentialSoftMatch = insertionPoint;
1143
+ let nextSibling = newChild.nextSibling;
1144
+ let siblingSoftMatchCount = 0;
1145
+
1146
+ while (potentialSoftMatch != null) {
1147
+
1148
+ if (getIdIntersectionCount(ctx, potentialSoftMatch, newContent) > 0) {
1149
+ // the current potential soft match has a potential id set match with the remaining new
1150
+ // content so bail out of looking
1151
+ return null;
1152
+ }
1153
+
1154
+ // if we have a soft match with the current node, return it
1155
+ if (isSoftMatch(newChild, potentialSoftMatch)) {
1156
+ return potentialSoftMatch;
1157
+ }
1158
+
1159
+ if (isSoftMatch(nextSibling, potentialSoftMatch)) {
1160
+ // the next new node has a soft match with this node, so
1161
+ // increment the count of future soft matches
1162
+ siblingSoftMatchCount++;
1163
+ nextSibling = nextSibling.nextSibling;
1164
+
1165
+ // If there are two future soft matches, bail to allow the siblings to soft match
1166
+ // so that we don't consume future soft matches for the sake of the current node
1167
+ if (siblingSoftMatchCount >= 2) {
1168
+ return null;
1169
+ }
1170
+ }
1171
+
1172
+ // advanced to the next old content child
1173
+ potentialSoftMatch = potentialSoftMatch.nextSibling;
1174
+ }
1175
+
1176
+ return potentialSoftMatch;
1177
+ }
1178
+
1179
+ function parseContent(newContent) {
1180
+ let parser = new DOMParser();
1181
+
1182
+ // remove svgs to avoid false-positive matches on head, etc.
1183
+ let contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, '');
1184
+
1185
+ // if the newContent contains a html, head or body tag, we can simply parse it w/o wrapping
1186
+ if (contentWithSvgsRemoved.match(/<\/html>/) || contentWithSvgsRemoved.match(/<\/head>/) || contentWithSvgsRemoved.match(/<\/body>/)) {
1187
+ let content = parser.parseFromString(newContent, "text/html");
1188
+ // if it is a full HTML document, return the document itself as the parent container
1189
+ if (contentWithSvgsRemoved.match(/<\/html>/)) {
1190
+ content.generatedByIdiomorph = true;
1191
+ return content;
1192
+ } else {
1193
+ // otherwise return the html element as the parent container
1194
+ let htmlElement = content.firstChild;
1195
+ if (htmlElement) {
1196
+ htmlElement.generatedByIdiomorph = true;
1197
+ return htmlElement;
1198
+ } else {
1199
+ return null;
1200
+ }
1201
+ }
1202
+ } else {
1203
+ // if it is partial HTML, wrap it in a template tag to provide a parent element and also to help
1204
+ // deal with touchy tags like tr, tbody, etc.
1205
+ let responseDoc = parser.parseFromString("<body><template>" + newContent + "</template></body>", "text/html");
1206
+ let content = responseDoc.body.querySelector('template').content;
1207
+ content.generatedByIdiomorph = true;
1208
+ return content
1209
+ }
1210
+ }
1211
+
1212
+ function normalizeContent(newContent) {
1213
+ if (newContent == null) {
1214
+ // noinspection UnnecessaryLocalVariableJS
1215
+ const dummyParent = document.createElement('div');
1216
+ return dummyParent;
1217
+ } else if (newContent.generatedByIdiomorph) {
1218
+ // the template tag created by idiomorph parsing can serve as a dummy parent
1219
+ return newContent;
1220
+ } else if (newContent instanceof Node) {
1221
+ // a single node is added as a child to a dummy parent
1222
+ const dummyParent = document.createElement('div');
1223
+ dummyParent.append(newContent);
1224
+ return dummyParent;
1225
+ } else {
1226
+ // all nodes in the array or HTMLElement collection are consolidated under
1227
+ // a single dummy parent element
1228
+ const dummyParent = document.createElement('div');
1229
+ for (const elt of [...newContent]) {
1230
+ dummyParent.append(elt);
1231
+ }
1232
+ return dummyParent;
1233
+ }
1234
+ }
1235
+
1236
+ function insertSiblings(previousSibling, morphedNode, nextSibling) {
1237
+ let stack = [];
1238
+ let added = [];
1239
+ while (previousSibling != null) {
1240
+ stack.push(previousSibling);
1241
+ previousSibling = previousSibling.previousSibling;
1242
+ }
1243
+ while (stack.length > 0) {
1244
+ let node = stack.pop();
1245
+ added.push(node); // push added preceding siblings on in order and insert
1246
+ morphedNode.parentElement.insertBefore(node, morphedNode);
1247
+ }
1248
+ added.push(morphedNode);
1249
+ while (nextSibling != null) {
1250
+ stack.push(nextSibling);
1251
+ added.push(nextSibling); // here we are going in order, so push on as we scan, rather than add
1252
+ nextSibling = nextSibling.nextSibling;
1253
+ }
1254
+ while (stack.length > 0) {
1255
+ morphedNode.parentElement.insertBefore(stack.pop(), morphedNode.nextSibling);
1256
+ }
1257
+ return added;
1258
+ }
1259
+
1260
+ function findBestNodeMatch(newContent, oldNode, ctx) {
1261
+ let currentElement;
1262
+ currentElement = newContent.firstChild;
1263
+ let bestElement = currentElement;
1264
+ let score = 0;
1265
+ while (currentElement) {
1266
+ let newScore = scoreElement(currentElement, oldNode, ctx);
1267
+ if (newScore > score) {
1268
+ bestElement = currentElement;
1269
+ score = newScore;
1270
+ }
1271
+ currentElement = currentElement.nextSibling;
1272
+ }
1273
+ return bestElement;
1274
+ }
1275
+
1276
+ function scoreElement(node1, node2, ctx) {
1277
+ if (isSoftMatch(node1, node2)) {
1278
+ return .5 + getIdIntersectionCount(ctx, node1, node2);
1279
+ }
1280
+ return 0;
1281
+ }
1282
+
1283
+ function removeNode(tempNode, ctx) {
1284
+ removeIdsFromConsideration(ctx, tempNode);
1285
+ if (ctx.callbacks.beforeNodeRemoved(tempNode) === false) return;
1286
+
1287
+ tempNode.remove();
1288
+ ctx.callbacks.afterNodeRemoved(tempNode);
1289
+ }
1290
+
1291
+ //=============================================================================
1292
+ // ID Set Functions
1293
+ //=============================================================================
1294
+
1295
+ function isIdInConsideration(ctx, id) {
1296
+ return !ctx.deadIds.has(id);
1297
+ }
1298
+
1299
+ function idIsWithinNode(ctx, id, targetNode) {
1300
+ let idSet = ctx.idMap.get(targetNode) || EMPTY_SET;
1301
+ return idSet.has(id);
1302
+ }
1303
+
1304
+ function removeIdsFromConsideration(ctx, node) {
1305
+ let idSet = ctx.idMap.get(node) || EMPTY_SET;
1306
+ for (const id of idSet) {
1307
+ ctx.deadIds.add(id);
1308
+ }
1309
+ }
1310
+
1311
+ function getIdIntersectionCount(ctx, node1, node2) {
1312
+ let sourceSet = ctx.idMap.get(node1) || EMPTY_SET;
1313
+ let matchCount = 0;
1314
+ for (const id of sourceSet) {
1315
+ // a potential match is an id in the source and potentialIdsSet, but
1316
+ // that has not already been merged into the DOM
1317
+ if (isIdInConsideration(ctx, id) && idIsWithinNode(ctx, id, node2)) {
1318
+ ++matchCount;
1319
+ }
1320
+ }
1321
+ return matchCount;
1322
+ }
1323
+
1324
+ /**
1325
+ * A bottom up algorithm that finds all elements with ids inside of the node
1326
+ * argument and populates id sets for those nodes and all their parents, generating
1327
+ * a set of ids contained within all nodes for the entire hierarchy in the DOM
1328
+ *
1329
+ * @param node {Element}
1330
+ * @param {Map<Node, Set<String>>} idMap
1331
+ */
1332
+ function populateIdMapForNode(node, idMap) {
1333
+ let nodeParent = node.parentElement;
1334
+ // find all elements with an id property
1335
+ let idElements = node.querySelectorAll('[id]');
1336
+ for (const elt of idElements) {
1337
+ let current = elt;
1338
+ // walk up the parent hierarchy of that element, adding the id
1339
+ // of element to the parent's id set
1340
+ while (current !== nodeParent && current != null) {
1341
+ let idSet = idMap.get(current);
1342
+ // if the id set doesn't exist, create it and insert it in the map
1343
+ if (idSet == null) {
1344
+ idSet = new Set();
1345
+ idMap.set(current, idSet);
1346
+ }
1347
+ idSet.add(elt.id);
1348
+ current = current.parentElement;
1349
+ }
1350
+ }
1351
+ }
1352
+
1353
+ /**
1354
+ * This function computes a map of nodes to all ids contained within that node (inclusive of the
1355
+ * node). This map can be used to ask if two nodes have intersecting sets of ids, which allows
1356
+ * for a looser definition of "matching" than tradition id matching, and allows child nodes
1357
+ * to contribute to a parent nodes matching.
1358
+ *
1359
+ * @param {Element} oldContent the old content that will be morphed
1360
+ * @param {Element} newContent the new content to morph to
1361
+ * @returns {Map<Node, Set<String>>} a map of nodes to id sets for the
1362
+ */
1363
+ function createIdMap(oldContent, newContent) {
1364
+ let idMap = new Map();
1365
+ populateIdMapForNode(oldContent, idMap);
1366
+ populateIdMapForNode(newContent, idMap);
1367
+ return idMap;
1368
+ }
1369
+
1370
+ //=============================================================================
1371
+ // This is what ends up becoming the Idiomorph global object
1372
+ //=============================================================================
1373
+ return {
1374
+ morph,
1375
+ defaults
1376
+ }
1377
+ })();
1378
+
1379
+ function log() {
1380
+ if (HotwireSpark.config.loggingEnabled) {
1381
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
1382
+ args[_key] = arguments[_key];
1383
+ }
1384
+ console.log(`[hotwire_spark]`, ...args);
1385
+ }
1386
+ }
1387
+
1388
+ /*
1389
+ Stimulus 3.2.1
1390
+ Copyright © 2023 Basecamp, LLC
1391
+ */
1392
+ class EventListener {
1393
+ constructor(eventTarget, eventName, eventOptions) {
1394
+ this.eventTarget = eventTarget;
1395
+ this.eventName = eventName;
1396
+ this.eventOptions = eventOptions;
1397
+ this.unorderedBindings = new Set();
1398
+ }
1399
+ connect() {
1400
+ this.eventTarget.addEventListener(this.eventName, this, this.eventOptions);
1401
+ }
1402
+ disconnect() {
1403
+ this.eventTarget.removeEventListener(this.eventName, this, this.eventOptions);
1404
+ }
1405
+ bindingConnected(binding) {
1406
+ this.unorderedBindings.add(binding);
1407
+ }
1408
+ bindingDisconnected(binding) {
1409
+ this.unorderedBindings.delete(binding);
1410
+ }
1411
+ handleEvent(event) {
1412
+ const extendedEvent = extendEvent(event);
1413
+ for (const binding of this.bindings) {
1414
+ if (extendedEvent.immediatePropagationStopped) {
1415
+ break;
1416
+ }
1417
+ else {
1418
+ binding.handleEvent(extendedEvent);
1419
+ }
1420
+ }
1421
+ }
1422
+ hasBindings() {
1423
+ return this.unorderedBindings.size > 0;
1424
+ }
1425
+ get bindings() {
1426
+ return Array.from(this.unorderedBindings).sort((left, right) => {
1427
+ const leftIndex = left.index, rightIndex = right.index;
1428
+ return leftIndex < rightIndex ? -1 : leftIndex > rightIndex ? 1 : 0;
1429
+ });
1430
+ }
1431
+ }
1432
+ function extendEvent(event) {
1433
+ if ("immediatePropagationStopped" in event) {
1434
+ return event;
1435
+ }
1436
+ else {
1437
+ const { stopImmediatePropagation } = event;
1438
+ return Object.assign(event, {
1439
+ immediatePropagationStopped: false,
1440
+ stopImmediatePropagation() {
1441
+ this.immediatePropagationStopped = true;
1442
+ stopImmediatePropagation.call(this);
1443
+ },
1444
+ });
1445
+ }
1446
+ }
1447
+
1448
+ class Dispatcher {
1449
+ constructor(application) {
1450
+ this.application = application;
1451
+ this.eventListenerMaps = new Map();
1452
+ this.started = false;
1453
+ }
1454
+ start() {
1455
+ if (!this.started) {
1456
+ this.started = true;
1457
+ this.eventListeners.forEach((eventListener) => eventListener.connect());
1458
+ }
1459
+ }
1460
+ stop() {
1461
+ if (this.started) {
1462
+ this.started = false;
1463
+ this.eventListeners.forEach((eventListener) => eventListener.disconnect());
1464
+ }
1465
+ }
1466
+ get eventListeners() {
1467
+ return Array.from(this.eventListenerMaps.values()).reduce((listeners, map) => listeners.concat(Array.from(map.values())), []);
1468
+ }
1469
+ bindingConnected(binding) {
1470
+ this.fetchEventListenerForBinding(binding).bindingConnected(binding);
1471
+ }
1472
+ bindingDisconnected(binding, clearEventListeners = false) {
1473
+ this.fetchEventListenerForBinding(binding).bindingDisconnected(binding);
1474
+ if (clearEventListeners)
1475
+ this.clearEventListenersForBinding(binding);
1476
+ }
1477
+ handleError(error, message, detail = {}) {
1478
+ this.application.handleError(error, `Error ${message}`, detail);
1479
+ }
1480
+ clearEventListenersForBinding(binding) {
1481
+ const eventListener = this.fetchEventListenerForBinding(binding);
1482
+ if (!eventListener.hasBindings()) {
1483
+ eventListener.disconnect();
1484
+ this.removeMappedEventListenerFor(binding);
1485
+ }
1486
+ }
1487
+ removeMappedEventListenerFor(binding) {
1488
+ const { eventTarget, eventName, eventOptions } = binding;
1489
+ const eventListenerMap = this.fetchEventListenerMapForEventTarget(eventTarget);
1490
+ const cacheKey = this.cacheKey(eventName, eventOptions);
1491
+ eventListenerMap.delete(cacheKey);
1492
+ if (eventListenerMap.size == 0)
1493
+ this.eventListenerMaps.delete(eventTarget);
1494
+ }
1495
+ fetchEventListenerForBinding(binding) {
1496
+ const { eventTarget, eventName, eventOptions } = binding;
1497
+ return this.fetchEventListener(eventTarget, eventName, eventOptions);
1498
+ }
1499
+ fetchEventListener(eventTarget, eventName, eventOptions) {
1500
+ const eventListenerMap = this.fetchEventListenerMapForEventTarget(eventTarget);
1501
+ const cacheKey = this.cacheKey(eventName, eventOptions);
1502
+ let eventListener = eventListenerMap.get(cacheKey);
1503
+ if (!eventListener) {
1504
+ eventListener = this.createEventListener(eventTarget, eventName, eventOptions);
1505
+ eventListenerMap.set(cacheKey, eventListener);
1506
+ }
1507
+ return eventListener;
1508
+ }
1509
+ createEventListener(eventTarget, eventName, eventOptions) {
1510
+ const eventListener = new EventListener(eventTarget, eventName, eventOptions);
1511
+ if (this.started) {
1512
+ eventListener.connect();
1513
+ }
1514
+ return eventListener;
1515
+ }
1516
+ fetchEventListenerMapForEventTarget(eventTarget) {
1517
+ let eventListenerMap = this.eventListenerMaps.get(eventTarget);
1518
+ if (!eventListenerMap) {
1519
+ eventListenerMap = new Map();
1520
+ this.eventListenerMaps.set(eventTarget, eventListenerMap);
1521
+ }
1522
+ return eventListenerMap;
1523
+ }
1524
+ cacheKey(eventName, eventOptions) {
1525
+ const parts = [eventName];
1526
+ Object.keys(eventOptions)
1527
+ .sort()
1528
+ .forEach((key) => {
1529
+ parts.push(`${eventOptions[key] ? "" : "!"}${key}`);
1530
+ });
1531
+ return parts.join(":");
1532
+ }
1533
+ }
1534
+
1535
+ const defaultActionDescriptorFilters = {
1536
+ stop({ event, value }) {
1537
+ if (value)
1538
+ event.stopPropagation();
1539
+ return true;
1540
+ },
1541
+ prevent({ event, value }) {
1542
+ if (value)
1543
+ event.preventDefault();
1544
+ return true;
1545
+ },
1546
+ self({ event, value, element }) {
1547
+ if (value) {
1548
+ return element === event.target;
1549
+ }
1550
+ else {
1551
+ return true;
1552
+ }
1553
+ },
1554
+ };
1555
+ const descriptorPattern = /^(?:(?:([^.]+?)\+)?(.+?)(?:\.(.+?))?(?:@(window|document))?->)?(.+?)(?:#([^:]+?))(?::(.+))?$/;
1556
+ function parseActionDescriptorString(descriptorString) {
1557
+ const source = descriptorString.trim();
1558
+ const matches = source.match(descriptorPattern) || [];
1559
+ let eventName = matches[2];
1560
+ let keyFilter = matches[3];
1561
+ if (keyFilter && !["keydown", "keyup", "keypress"].includes(eventName)) {
1562
+ eventName += `.${keyFilter}`;
1563
+ keyFilter = "";
1564
+ }
1565
+ return {
1566
+ eventTarget: parseEventTarget(matches[4]),
1567
+ eventName,
1568
+ eventOptions: matches[7] ? parseEventOptions(matches[7]) : {},
1569
+ identifier: matches[5],
1570
+ methodName: matches[6],
1571
+ keyFilter: matches[1] || keyFilter,
1572
+ };
1573
+ }
1574
+ function parseEventTarget(eventTargetName) {
1575
+ if (eventTargetName == "window") {
1576
+ return window;
1577
+ }
1578
+ else if (eventTargetName == "document") {
1579
+ return document;
1580
+ }
1581
+ }
1582
+ function parseEventOptions(eventOptions) {
1583
+ return eventOptions
1584
+ .split(":")
1585
+ .reduce((options, token) => Object.assign(options, { [token.replace(/^!/, "")]: !/^!/.test(token) }), {});
1586
+ }
1587
+ function stringifyEventTarget(eventTarget) {
1588
+ if (eventTarget == window) {
1589
+ return "window";
1590
+ }
1591
+ else if (eventTarget == document) {
1592
+ return "document";
1593
+ }
1594
+ }
1595
+
1596
+ function camelize(value) {
1597
+ return value.replace(/(?:[_-])([a-z0-9])/g, (_, char) => char.toUpperCase());
1598
+ }
1599
+ function namespaceCamelize(value) {
1600
+ return camelize(value.replace(/--/g, "-").replace(/__/g, "_"));
1601
+ }
1602
+ function capitalize(value) {
1603
+ return value.charAt(0).toUpperCase() + value.slice(1);
1604
+ }
1605
+ function dasherize(value) {
1606
+ return value.replace(/([A-Z])/g, (_, char) => `-${char.toLowerCase()}`);
1607
+ }
1608
+ function tokenize(value) {
1609
+ return value.match(/[^\s]+/g) || [];
1610
+ }
1611
+ function hasProperty(object, property) {
1612
+ return Object.prototype.hasOwnProperty.call(object, property);
1613
+ }
1614
+
1615
+ const allModifiers = ["meta", "ctrl", "alt", "shift"];
1616
+ class Action {
1617
+ constructor(element, index, descriptor, schema) {
1618
+ this.element = element;
1619
+ this.index = index;
1620
+ this.eventTarget = descriptor.eventTarget || element;
1621
+ this.eventName = descriptor.eventName || getDefaultEventNameForElement(element) || error("missing event name");
1622
+ this.eventOptions = descriptor.eventOptions || {};
1623
+ this.identifier = descriptor.identifier || error("missing identifier");
1624
+ this.methodName = descriptor.methodName || error("missing method name");
1625
+ this.keyFilter = descriptor.keyFilter || "";
1626
+ this.schema = schema;
1627
+ }
1628
+ static forToken(token, schema) {
1629
+ return new this(token.element, token.index, parseActionDescriptorString(token.content), schema);
1630
+ }
1631
+ toString() {
1632
+ const eventFilter = this.keyFilter ? `.${this.keyFilter}` : "";
1633
+ const eventTarget = this.eventTargetName ? `@${this.eventTargetName}` : "";
1634
+ return `${this.eventName}${eventFilter}${eventTarget}->${this.identifier}#${this.methodName}`;
1635
+ }
1636
+ shouldIgnoreKeyboardEvent(event) {
1637
+ if (!this.keyFilter) {
1638
+ return false;
1639
+ }
1640
+ const filters = this.keyFilter.split("+");
1641
+ if (this.keyFilterDissatisfied(event, filters)) {
1642
+ return true;
1643
+ }
1644
+ const standardFilter = filters.filter((key) => !allModifiers.includes(key))[0];
1645
+ if (!standardFilter) {
1646
+ return false;
1647
+ }
1648
+ if (!hasProperty(this.keyMappings, standardFilter)) {
1649
+ error(`contains unknown key filter: ${this.keyFilter}`);
1650
+ }
1651
+ return this.keyMappings[standardFilter].toLowerCase() !== event.key.toLowerCase();
1652
+ }
1653
+ shouldIgnoreMouseEvent(event) {
1654
+ if (!this.keyFilter) {
1655
+ return false;
1656
+ }
1657
+ const filters = [this.keyFilter];
1658
+ if (this.keyFilterDissatisfied(event, filters)) {
1659
+ return true;
1660
+ }
1661
+ return false;
1662
+ }
1663
+ get params() {
1664
+ const params = {};
1665
+ const pattern = new RegExp(`^data-${this.identifier}-(.+)-param$`, "i");
1666
+ for (const { name, value } of Array.from(this.element.attributes)) {
1667
+ const match = name.match(pattern);
1668
+ const key = match && match[1];
1669
+ if (key) {
1670
+ params[camelize(key)] = typecast(value);
1671
+ }
1672
+ }
1673
+ return params;
1674
+ }
1675
+ get eventTargetName() {
1676
+ return stringifyEventTarget(this.eventTarget);
1677
+ }
1678
+ get keyMappings() {
1679
+ return this.schema.keyMappings;
1680
+ }
1681
+ keyFilterDissatisfied(event, filters) {
1682
+ const [meta, ctrl, alt, shift] = allModifiers.map((modifier) => filters.includes(modifier));
1683
+ return event.metaKey !== meta || event.ctrlKey !== ctrl || event.altKey !== alt || event.shiftKey !== shift;
1684
+ }
1685
+ }
1686
+ const defaultEventNames = {
1687
+ a: () => "click",
1688
+ button: () => "click",
1689
+ form: () => "submit",
1690
+ details: () => "toggle",
1691
+ input: (e) => (e.getAttribute("type") == "submit" ? "click" : "input"),
1692
+ select: () => "change",
1693
+ textarea: () => "input",
1694
+ };
1695
+ function getDefaultEventNameForElement(element) {
1696
+ const tagName = element.tagName.toLowerCase();
1697
+ if (tagName in defaultEventNames) {
1698
+ return defaultEventNames[tagName](element);
1699
+ }
1700
+ }
1701
+ function error(message) {
1702
+ throw new Error(message);
1703
+ }
1704
+ function typecast(value) {
1705
+ try {
1706
+ return JSON.parse(value);
1707
+ }
1708
+ catch (o_O) {
1709
+ return value;
1710
+ }
1711
+ }
1712
+
1713
+ class Binding {
1714
+ constructor(context, action) {
1715
+ this.context = context;
1716
+ this.action = action;
1717
+ }
1718
+ get index() {
1719
+ return this.action.index;
1720
+ }
1721
+ get eventTarget() {
1722
+ return this.action.eventTarget;
1723
+ }
1724
+ get eventOptions() {
1725
+ return this.action.eventOptions;
1726
+ }
1727
+ get identifier() {
1728
+ return this.context.identifier;
1729
+ }
1730
+ handleEvent(event) {
1731
+ const actionEvent = this.prepareActionEvent(event);
1732
+ if (this.willBeInvokedByEvent(event) && this.applyEventModifiers(actionEvent)) {
1733
+ this.invokeWithEvent(actionEvent);
1734
+ }
1735
+ }
1736
+ get eventName() {
1737
+ return this.action.eventName;
1738
+ }
1739
+ get method() {
1740
+ const method = this.controller[this.methodName];
1741
+ if (typeof method == "function") {
1742
+ return method;
1743
+ }
1744
+ throw new Error(`Action "${this.action}" references undefined method "${this.methodName}"`);
1745
+ }
1746
+ applyEventModifiers(event) {
1747
+ const { element } = this.action;
1748
+ const { actionDescriptorFilters } = this.context.application;
1749
+ const { controller } = this.context;
1750
+ let passes = true;
1751
+ for (const [name, value] of Object.entries(this.eventOptions)) {
1752
+ if (name in actionDescriptorFilters) {
1753
+ const filter = actionDescriptorFilters[name];
1754
+ passes = passes && filter({ name, value, event, element, controller });
1755
+ }
1756
+ else {
1757
+ continue;
1758
+ }
1759
+ }
1760
+ return passes;
1761
+ }
1762
+ prepareActionEvent(event) {
1763
+ return Object.assign(event, { params: this.action.params });
1764
+ }
1765
+ invokeWithEvent(event) {
1766
+ const { target, currentTarget } = event;
1767
+ try {
1768
+ this.method.call(this.controller, event);
1769
+ this.context.logDebugActivity(this.methodName, { event, target, currentTarget, action: this.methodName });
1770
+ }
1771
+ catch (error) {
1772
+ const { identifier, controller, element, index } = this;
1773
+ const detail = { identifier, controller, element, index, event };
1774
+ this.context.handleError(error, `invoking action "${this.action}"`, detail);
1775
+ }
1776
+ }
1777
+ willBeInvokedByEvent(event) {
1778
+ const eventTarget = event.target;
1779
+ if (event instanceof KeyboardEvent && this.action.shouldIgnoreKeyboardEvent(event)) {
1780
+ return false;
1781
+ }
1782
+ if (event instanceof MouseEvent && this.action.shouldIgnoreMouseEvent(event)) {
1783
+ return false;
1784
+ }
1785
+ if (this.element === eventTarget) {
1786
+ return true;
1787
+ }
1788
+ else if (eventTarget instanceof Element && this.element.contains(eventTarget)) {
1789
+ return this.scope.containsElement(eventTarget);
1790
+ }
1791
+ else {
1792
+ return this.scope.containsElement(this.action.element);
1793
+ }
1794
+ }
1795
+ get controller() {
1796
+ return this.context.controller;
1797
+ }
1798
+ get methodName() {
1799
+ return this.action.methodName;
1800
+ }
1801
+ get element() {
1802
+ return this.scope.element;
1803
+ }
1804
+ get scope() {
1805
+ return this.context.scope;
1806
+ }
1807
+ }
1808
+
1809
+ class ElementObserver {
1810
+ constructor(element, delegate) {
1811
+ this.mutationObserverInit = { attributes: true, childList: true, subtree: true };
1812
+ this.element = element;
1813
+ this.started = false;
1814
+ this.delegate = delegate;
1815
+ this.elements = new Set();
1816
+ this.mutationObserver = new MutationObserver((mutations) => this.processMutations(mutations));
1817
+ }
1818
+ start() {
1819
+ if (!this.started) {
1820
+ this.started = true;
1821
+ this.mutationObserver.observe(this.element, this.mutationObserverInit);
1822
+ this.refresh();
1823
+ }
1824
+ }
1825
+ pause(callback) {
1826
+ if (this.started) {
1827
+ this.mutationObserver.disconnect();
1828
+ this.started = false;
1829
+ }
1830
+ callback();
1831
+ if (!this.started) {
1832
+ this.mutationObserver.observe(this.element, this.mutationObserverInit);
1833
+ this.started = true;
1834
+ }
1835
+ }
1836
+ stop() {
1837
+ if (this.started) {
1838
+ this.mutationObserver.takeRecords();
1839
+ this.mutationObserver.disconnect();
1840
+ this.started = false;
1841
+ }
1842
+ }
1843
+ refresh() {
1844
+ if (this.started) {
1845
+ const matches = new Set(this.matchElementsInTree());
1846
+ for (const element of Array.from(this.elements)) {
1847
+ if (!matches.has(element)) {
1848
+ this.removeElement(element);
1849
+ }
1850
+ }
1851
+ for (const element of Array.from(matches)) {
1852
+ this.addElement(element);
1853
+ }
1854
+ }
1855
+ }
1856
+ processMutations(mutations) {
1857
+ if (this.started) {
1858
+ for (const mutation of mutations) {
1859
+ this.processMutation(mutation);
1860
+ }
1861
+ }
1862
+ }
1863
+ processMutation(mutation) {
1864
+ if (mutation.type == "attributes") {
1865
+ this.processAttributeChange(mutation.target, mutation.attributeName);
1866
+ }
1867
+ else if (mutation.type == "childList") {
1868
+ this.processRemovedNodes(mutation.removedNodes);
1869
+ this.processAddedNodes(mutation.addedNodes);
1870
+ }
1871
+ }
1872
+ processAttributeChange(element, attributeName) {
1873
+ if (this.elements.has(element)) {
1874
+ if (this.delegate.elementAttributeChanged && this.matchElement(element)) {
1875
+ this.delegate.elementAttributeChanged(element, attributeName);
1876
+ }
1877
+ else {
1878
+ this.removeElement(element);
1879
+ }
1880
+ }
1881
+ else if (this.matchElement(element)) {
1882
+ this.addElement(element);
1883
+ }
1884
+ }
1885
+ processRemovedNodes(nodes) {
1886
+ for (const node of Array.from(nodes)) {
1887
+ const element = this.elementFromNode(node);
1888
+ if (element) {
1889
+ this.processTree(element, this.removeElement);
1890
+ }
1891
+ }
1892
+ }
1893
+ processAddedNodes(nodes) {
1894
+ for (const node of Array.from(nodes)) {
1895
+ const element = this.elementFromNode(node);
1896
+ if (element && this.elementIsActive(element)) {
1897
+ this.processTree(element, this.addElement);
1898
+ }
1899
+ }
1900
+ }
1901
+ matchElement(element) {
1902
+ return this.delegate.matchElement(element);
1903
+ }
1904
+ matchElementsInTree(tree = this.element) {
1905
+ return this.delegate.matchElementsInTree(tree);
1906
+ }
1907
+ processTree(tree, processor) {
1908
+ for (const element of this.matchElementsInTree(tree)) {
1909
+ processor.call(this, element);
1910
+ }
1911
+ }
1912
+ elementFromNode(node) {
1913
+ if (node.nodeType == Node.ELEMENT_NODE) {
1914
+ return node;
1915
+ }
1916
+ }
1917
+ elementIsActive(element) {
1918
+ if (element.isConnected != this.element.isConnected) {
1919
+ return false;
1920
+ }
1921
+ else {
1922
+ return this.element.contains(element);
1923
+ }
1924
+ }
1925
+ addElement(element) {
1926
+ if (!this.elements.has(element)) {
1927
+ if (this.elementIsActive(element)) {
1928
+ this.elements.add(element);
1929
+ if (this.delegate.elementMatched) {
1930
+ this.delegate.elementMatched(element);
1931
+ }
1932
+ }
1933
+ }
1934
+ }
1935
+ removeElement(element) {
1936
+ if (this.elements.has(element)) {
1937
+ this.elements.delete(element);
1938
+ if (this.delegate.elementUnmatched) {
1939
+ this.delegate.elementUnmatched(element);
1940
+ }
1941
+ }
1942
+ }
1943
+ }
1944
+
1945
+ class AttributeObserver {
1946
+ constructor(element, attributeName, delegate) {
1947
+ this.attributeName = attributeName;
1948
+ this.delegate = delegate;
1949
+ this.elementObserver = new ElementObserver(element, this);
1950
+ }
1951
+ get element() {
1952
+ return this.elementObserver.element;
1953
+ }
1954
+ get selector() {
1955
+ return `[${this.attributeName}]`;
1956
+ }
1957
+ start() {
1958
+ this.elementObserver.start();
1959
+ }
1960
+ pause(callback) {
1961
+ this.elementObserver.pause(callback);
1962
+ }
1963
+ stop() {
1964
+ this.elementObserver.stop();
1965
+ }
1966
+ refresh() {
1967
+ this.elementObserver.refresh();
1968
+ }
1969
+ get started() {
1970
+ return this.elementObserver.started;
1971
+ }
1972
+ matchElement(element) {
1973
+ return element.hasAttribute(this.attributeName);
1974
+ }
1975
+ matchElementsInTree(tree) {
1976
+ const match = this.matchElement(tree) ? [tree] : [];
1977
+ const matches = Array.from(tree.querySelectorAll(this.selector));
1978
+ return match.concat(matches);
1979
+ }
1980
+ elementMatched(element) {
1981
+ if (this.delegate.elementMatchedAttribute) {
1982
+ this.delegate.elementMatchedAttribute(element, this.attributeName);
1983
+ }
1984
+ }
1985
+ elementUnmatched(element) {
1986
+ if (this.delegate.elementUnmatchedAttribute) {
1987
+ this.delegate.elementUnmatchedAttribute(element, this.attributeName);
1988
+ }
1989
+ }
1990
+ elementAttributeChanged(element, attributeName) {
1991
+ if (this.delegate.elementAttributeValueChanged && this.attributeName == attributeName) {
1992
+ this.delegate.elementAttributeValueChanged(element, attributeName);
1993
+ }
1994
+ }
1995
+ }
1996
+
1997
+ function add(map, key, value) {
1998
+ fetch$1(map, key).add(value);
1999
+ }
2000
+ function del(map, key, value) {
2001
+ fetch$1(map, key).delete(value);
2002
+ prune(map, key);
2003
+ }
2004
+ function fetch$1(map, key) {
2005
+ let values = map.get(key);
2006
+ if (!values) {
2007
+ values = new Set();
2008
+ map.set(key, values);
2009
+ }
2010
+ return values;
2011
+ }
2012
+ function prune(map, key) {
2013
+ const values = map.get(key);
2014
+ if (values != null && values.size == 0) {
2015
+ map.delete(key);
2016
+ }
2017
+ }
2018
+
2019
+ class Multimap {
2020
+ constructor() {
2021
+ this.valuesByKey = new Map();
2022
+ }
2023
+ get keys() {
2024
+ return Array.from(this.valuesByKey.keys());
2025
+ }
2026
+ get values() {
2027
+ const sets = Array.from(this.valuesByKey.values());
2028
+ return sets.reduce((values, set) => values.concat(Array.from(set)), []);
2029
+ }
2030
+ get size() {
2031
+ const sets = Array.from(this.valuesByKey.values());
2032
+ return sets.reduce((size, set) => size + set.size, 0);
2033
+ }
2034
+ add(key, value) {
2035
+ add(this.valuesByKey, key, value);
2036
+ }
2037
+ delete(key, value) {
2038
+ del(this.valuesByKey, key, value);
2039
+ }
2040
+ has(key, value) {
2041
+ const values = this.valuesByKey.get(key);
2042
+ return values != null && values.has(value);
2043
+ }
2044
+ hasKey(key) {
2045
+ return this.valuesByKey.has(key);
2046
+ }
2047
+ hasValue(value) {
2048
+ const sets = Array.from(this.valuesByKey.values());
2049
+ return sets.some((set) => set.has(value));
2050
+ }
2051
+ getValuesForKey(key) {
2052
+ const values = this.valuesByKey.get(key);
2053
+ return values ? Array.from(values) : [];
2054
+ }
2055
+ getKeysForValue(value) {
2056
+ return Array.from(this.valuesByKey)
2057
+ .filter(([_key, values]) => values.has(value))
2058
+ .map(([key, _values]) => key);
2059
+ }
2060
+ }
2061
+
2062
+ class SelectorObserver {
2063
+ constructor(element, selector, delegate, details) {
2064
+ this._selector = selector;
2065
+ this.details = details;
2066
+ this.elementObserver = new ElementObserver(element, this);
2067
+ this.delegate = delegate;
2068
+ this.matchesByElement = new Multimap();
2069
+ }
2070
+ get started() {
2071
+ return this.elementObserver.started;
2072
+ }
2073
+ get selector() {
2074
+ return this._selector;
2075
+ }
2076
+ set selector(selector) {
2077
+ this._selector = selector;
2078
+ this.refresh();
2079
+ }
2080
+ start() {
2081
+ this.elementObserver.start();
2082
+ }
2083
+ pause(callback) {
2084
+ this.elementObserver.pause(callback);
2085
+ }
2086
+ stop() {
2087
+ this.elementObserver.stop();
2088
+ }
2089
+ refresh() {
2090
+ this.elementObserver.refresh();
2091
+ }
2092
+ get element() {
2093
+ return this.elementObserver.element;
2094
+ }
2095
+ matchElement(element) {
2096
+ const { selector } = this;
2097
+ if (selector) {
2098
+ const matches = element.matches(selector);
2099
+ if (this.delegate.selectorMatchElement) {
2100
+ return matches && this.delegate.selectorMatchElement(element, this.details);
2101
+ }
2102
+ return matches;
2103
+ }
2104
+ else {
2105
+ return false;
2106
+ }
2107
+ }
2108
+ matchElementsInTree(tree) {
2109
+ const { selector } = this;
2110
+ if (selector) {
2111
+ const match = this.matchElement(tree) ? [tree] : [];
2112
+ const matches = Array.from(tree.querySelectorAll(selector)).filter((match) => this.matchElement(match));
2113
+ return match.concat(matches);
2114
+ }
2115
+ else {
2116
+ return [];
2117
+ }
2118
+ }
2119
+ elementMatched(element) {
2120
+ const { selector } = this;
2121
+ if (selector) {
2122
+ this.selectorMatched(element, selector);
2123
+ }
2124
+ }
2125
+ elementUnmatched(element) {
2126
+ const selectors = this.matchesByElement.getKeysForValue(element);
2127
+ for (const selector of selectors) {
2128
+ this.selectorUnmatched(element, selector);
2129
+ }
2130
+ }
2131
+ elementAttributeChanged(element, _attributeName) {
2132
+ const { selector } = this;
2133
+ if (selector) {
2134
+ const matches = this.matchElement(element);
2135
+ const matchedBefore = this.matchesByElement.has(selector, element);
2136
+ if (matches && !matchedBefore) {
2137
+ this.selectorMatched(element, selector);
2138
+ }
2139
+ else if (!matches && matchedBefore) {
2140
+ this.selectorUnmatched(element, selector);
2141
+ }
2142
+ }
2143
+ }
2144
+ selectorMatched(element, selector) {
2145
+ this.delegate.selectorMatched(element, selector, this.details);
2146
+ this.matchesByElement.add(selector, element);
2147
+ }
2148
+ selectorUnmatched(element, selector) {
2149
+ this.delegate.selectorUnmatched(element, selector, this.details);
2150
+ this.matchesByElement.delete(selector, element);
2151
+ }
2152
+ }
2153
+
2154
+ class StringMapObserver {
2155
+ constructor(element, delegate) {
2156
+ this.element = element;
2157
+ this.delegate = delegate;
2158
+ this.started = false;
2159
+ this.stringMap = new Map();
2160
+ this.mutationObserver = new MutationObserver((mutations) => this.processMutations(mutations));
2161
+ }
2162
+ start() {
2163
+ if (!this.started) {
2164
+ this.started = true;
2165
+ this.mutationObserver.observe(this.element, { attributes: true, attributeOldValue: true });
2166
+ this.refresh();
2167
+ }
2168
+ }
2169
+ stop() {
2170
+ if (this.started) {
2171
+ this.mutationObserver.takeRecords();
2172
+ this.mutationObserver.disconnect();
2173
+ this.started = false;
2174
+ }
2175
+ }
2176
+ refresh() {
2177
+ if (this.started) {
2178
+ for (const attributeName of this.knownAttributeNames) {
2179
+ this.refreshAttribute(attributeName, null);
2180
+ }
2181
+ }
2182
+ }
2183
+ processMutations(mutations) {
2184
+ if (this.started) {
2185
+ for (const mutation of mutations) {
2186
+ this.processMutation(mutation);
2187
+ }
2188
+ }
2189
+ }
2190
+ processMutation(mutation) {
2191
+ const attributeName = mutation.attributeName;
2192
+ if (attributeName) {
2193
+ this.refreshAttribute(attributeName, mutation.oldValue);
2194
+ }
2195
+ }
2196
+ refreshAttribute(attributeName, oldValue) {
2197
+ const key = this.delegate.getStringMapKeyForAttribute(attributeName);
2198
+ if (key != null) {
2199
+ if (!this.stringMap.has(attributeName)) {
2200
+ this.stringMapKeyAdded(key, attributeName);
2201
+ }
2202
+ const value = this.element.getAttribute(attributeName);
2203
+ if (this.stringMap.get(attributeName) != value) {
2204
+ this.stringMapValueChanged(value, key, oldValue);
2205
+ }
2206
+ if (value == null) {
2207
+ const oldValue = this.stringMap.get(attributeName);
2208
+ this.stringMap.delete(attributeName);
2209
+ if (oldValue)
2210
+ this.stringMapKeyRemoved(key, attributeName, oldValue);
2211
+ }
2212
+ else {
2213
+ this.stringMap.set(attributeName, value);
2214
+ }
2215
+ }
2216
+ }
2217
+ stringMapKeyAdded(key, attributeName) {
2218
+ if (this.delegate.stringMapKeyAdded) {
2219
+ this.delegate.stringMapKeyAdded(key, attributeName);
2220
+ }
2221
+ }
2222
+ stringMapValueChanged(value, key, oldValue) {
2223
+ if (this.delegate.stringMapValueChanged) {
2224
+ this.delegate.stringMapValueChanged(value, key, oldValue);
2225
+ }
2226
+ }
2227
+ stringMapKeyRemoved(key, attributeName, oldValue) {
2228
+ if (this.delegate.stringMapKeyRemoved) {
2229
+ this.delegate.stringMapKeyRemoved(key, attributeName, oldValue);
2230
+ }
2231
+ }
2232
+ get knownAttributeNames() {
2233
+ return Array.from(new Set(this.currentAttributeNames.concat(this.recordedAttributeNames)));
2234
+ }
2235
+ get currentAttributeNames() {
2236
+ return Array.from(this.element.attributes).map((attribute) => attribute.name);
2237
+ }
2238
+ get recordedAttributeNames() {
2239
+ return Array.from(this.stringMap.keys());
2240
+ }
2241
+ }
2242
+
2243
+ class TokenListObserver {
2244
+ constructor(element, attributeName, delegate) {
2245
+ this.attributeObserver = new AttributeObserver(element, attributeName, this);
2246
+ this.delegate = delegate;
2247
+ this.tokensByElement = new Multimap();
2248
+ }
2249
+ get started() {
2250
+ return this.attributeObserver.started;
2251
+ }
2252
+ start() {
2253
+ this.attributeObserver.start();
2254
+ }
2255
+ pause(callback) {
2256
+ this.attributeObserver.pause(callback);
2257
+ }
2258
+ stop() {
2259
+ this.attributeObserver.stop();
2260
+ }
2261
+ refresh() {
2262
+ this.attributeObserver.refresh();
2263
+ }
2264
+ get element() {
2265
+ return this.attributeObserver.element;
2266
+ }
2267
+ get attributeName() {
2268
+ return this.attributeObserver.attributeName;
2269
+ }
2270
+ elementMatchedAttribute(element) {
2271
+ this.tokensMatched(this.readTokensForElement(element));
2272
+ }
2273
+ elementAttributeValueChanged(element) {
2274
+ const [unmatchedTokens, matchedTokens] = this.refreshTokensForElement(element);
2275
+ this.tokensUnmatched(unmatchedTokens);
2276
+ this.tokensMatched(matchedTokens);
2277
+ }
2278
+ elementUnmatchedAttribute(element) {
2279
+ this.tokensUnmatched(this.tokensByElement.getValuesForKey(element));
2280
+ }
2281
+ tokensMatched(tokens) {
2282
+ tokens.forEach((token) => this.tokenMatched(token));
2283
+ }
2284
+ tokensUnmatched(tokens) {
2285
+ tokens.forEach((token) => this.tokenUnmatched(token));
2286
+ }
2287
+ tokenMatched(token) {
2288
+ this.delegate.tokenMatched(token);
2289
+ this.tokensByElement.add(token.element, token);
2290
+ }
2291
+ tokenUnmatched(token) {
2292
+ this.delegate.tokenUnmatched(token);
2293
+ this.tokensByElement.delete(token.element, token);
2294
+ }
2295
+ refreshTokensForElement(element) {
2296
+ const previousTokens = this.tokensByElement.getValuesForKey(element);
2297
+ const currentTokens = this.readTokensForElement(element);
2298
+ const firstDifferingIndex = zip(previousTokens, currentTokens).findIndex(([previousToken, currentToken]) => !tokensAreEqual(previousToken, currentToken));
2299
+ if (firstDifferingIndex == -1) {
2300
+ return [[], []];
2301
+ }
2302
+ else {
2303
+ return [previousTokens.slice(firstDifferingIndex), currentTokens.slice(firstDifferingIndex)];
2304
+ }
2305
+ }
2306
+ readTokensForElement(element) {
2307
+ const attributeName = this.attributeName;
2308
+ const tokenString = element.getAttribute(attributeName) || "";
2309
+ return parseTokenString(tokenString, element, attributeName);
2310
+ }
2311
+ }
2312
+ function parseTokenString(tokenString, element, attributeName) {
2313
+ return tokenString
2314
+ .trim()
2315
+ .split(/\s+/)
2316
+ .filter((content) => content.length)
2317
+ .map((content, index) => ({ element, attributeName, content, index }));
2318
+ }
2319
+ function zip(left, right) {
2320
+ const length = Math.max(left.length, right.length);
2321
+ return Array.from({ length }, (_, index) => [left[index], right[index]]);
2322
+ }
2323
+ function tokensAreEqual(left, right) {
2324
+ return left && right && left.index == right.index && left.content == right.content;
2325
+ }
2326
+
2327
+ class ValueListObserver {
2328
+ constructor(element, attributeName, delegate) {
2329
+ this.tokenListObserver = new TokenListObserver(element, attributeName, this);
2330
+ this.delegate = delegate;
2331
+ this.parseResultsByToken = new WeakMap();
2332
+ this.valuesByTokenByElement = new WeakMap();
2333
+ }
2334
+ get started() {
2335
+ return this.tokenListObserver.started;
2336
+ }
2337
+ start() {
2338
+ this.tokenListObserver.start();
2339
+ }
2340
+ stop() {
2341
+ this.tokenListObserver.stop();
2342
+ }
2343
+ refresh() {
2344
+ this.tokenListObserver.refresh();
2345
+ }
2346
+ get element() {
2347
+ return this.tokenListObserver.element;
2348
+ }
2349
+ get attributeName() {
2350
+ return this.tokenListObserver.attributeName;
2351
+ }
2352
+ tokenMatched(token) {
2353
+ const { element } = token;
2354
+ const { value } = this.fetchParseResultForToken(token);
2355
+ if (value) {
2356
+ this.fetchValuesByTokenForElement(element).set(token, value);
2357
+ this.delegate.elementMatchedValue(element, value);
2358
+ }
2359
+ }
2360
+ tokenUnmatched(token) {
2361
+ const { element } = token;
2362
+ const { value } = this.fetchParseResultForToken(token);
2363
+ if (value) {
2364
+ this.fetchValuesByTokenForElement(element).delete(token);
2365
+ this.delegate.elementUnmatchedValue(element, value);
2366
+ }
2367
+ }
2368
+ fetchParseResultForToken(token) {
2369
+ let parseResult = this.parseResultsByToken.get(token);
2370
+ if (!parseResult) {
2371
+ parseResult = this.parseToken(token);
2372
+ this.parseResultsByToken.set(token, parseResult);
2373
+ }
2374
+ return parseResult;
2375
+ }
2376
+ fetchValuesByTokenForElement(element) {
2377
+ let valuesByToken = this.valuesByTokenByElement.get(element);
2378
+ if (!valuesByToken) {
2379
+ valuesByToken = new Map();
2380
+ this.valuesByTokenByElement.set(element, valuesByToken);
2381
+ }
2382
+ return valuesByToken;
2383
+ }
2384
+ parseToken(token) {
2385
+ try {
2386
+ const value = this.delegate.parseValueForToken(token);
2387
+ return { value };
2388
+ }
2389
+ catch (error) {
2390
+ return { error };
2391
+ }
2392
+ }
2393
+ }
2394
+
2395
+ class BindingObserver {
2396
+ constructor(context, delegate) {
2397
+ this.context = context;
2398
+ this.delegate = delegate;
2399
+ this.bindingsByAction = new Map();
2400
+ }
2401
+ start() {
2402
+ if (!this.valueListObserver) {
2403
+ this.valueListObserver = new ValueListObserver(this.element, this.actionAttribute, this);
2404
+ this.valueListObserver.start();
2405
+ }
2406
+ }
2407
+ stop() {
2408
+ if (this.valueListObserver) {
2409
+ this.valueListObserver.stop();
2410
+ delete this.valueListObserver;
2411
+ this.disconnectAllActions();
2412
+ }
2413
+ }
2414
+ get element() {
2415
+ return this.context.element;
2416
+ }
2417
+ get identifier() {
2418
+ return this.context.identifier;
2419
+ }
2420
+ get actionAttribute() {
2421
+ return this.schema.actionAttribute;
2422
+ }
2423
+ get schema() {
2424
+ return this.context.schema;
2425
+ }
2426
+ get bindings() {
2427
+ return Array.from(this.bindingsByAction.values());
2428
+ }
2429
+ connectAction(action) {
2430
+ const binding = new Binding(this.context, action);
2431
+ this.bindingsByAction.set(action, binding);
2432
+ this.delegate.bindingConnected(binding);
2433
+ }
2434
+ disconnectAction(action) {
2435
+ const binding = this.bindingsByAction.get(action);
2436
+ if (binding) {
2437
+ this.bindingsByAction.delete(action);
2438
+ this.delegate.bindingDisconnected(binding);
2439
+ }
2440
+ }
2441
+ disconnectAllActions() {
2442
+ this.bindings.forEach((binding) => this.delegate.bindingDisconnected(binding, true));
2443
+ this.bindingsByAction.clear();
2444
+ }
2445
+ parseValueForToken(token) {
2446
+ const action = Action.forToken(token, this.schema);
2447
+ if (action.identifier == this.identifier) {
2448
+ return action;
2449
+ }
2450
+ }
2451
+ elementMatchedValue(element, action) {
2452
+ this.connectAction(action);
2453
+ }
2454
+ elementUnmatchedValue(element, action) {
2455
+ this.disconnectAction(action);
2456
+ }
2457
+ }
2458
+
2459
+ class ValueObserver {
2460
+ constructor(context, receiver) {
2461
+ this.context = context;
2462
+ this.receiver = receiver;
2463
+ this.stringMapObserver = new StringMapObserver(this.element, this);
2464
+ this.valueDescriptorMap = this.controller.valueDescriptorMap;
2465
+ }
2466
+ start() {
2467
+ this.stringMapObserver.start();
2468
+ this.invokeChangedCallbacksForDefaultValues();
2469
+ }
2470
+ stop() {
2471
+ this.stringMapObserver.stop();
2472
+ }
2473
+ get element() {
2474
+ return this.context.element;
2475
+ }
2476
+ get controller() {
2477
+ return this.context.controller;
2478
+ }
2479
+ getStringMapKeyForAttribute(attributeName) {
2480
+ if (attributeName in this.valueDescriptorMap) {
2481
+ return this.valueDescriptorMap[attributeName].name;
2482
+ }
2483
+ }
2484
+ stringMapKeyAdded(key, attributeName) {
2485
+ const descriptor = this.valueDescriptorMap[attributeName];
2486
+ if (!this.hasValue(key)) {
2487
+ this.invokeChangedCallback(key, descriptor.writer(this.receiver[key]), descriptor.writer(descriptor.defaultValue));
2488
+ }
2489
+ }
2490
+ stringMapValueChanged(value, name, oldValue) {
2491
+ const descriptor = this.valueDescriptorNameMap[name];
2492
+ if (value === null)
2493
+ return;
2494
+ if (oldValue === null) {
2495
+ oldValue = descriptor.writer(descriptor.defaultValue);
2496
+ }
2497
+ this.invokeChangedCallback(name, value, oldValue);
2498
+ }
2499
+ stringMapKeyRemoved(key, attributeName, oldValue) {
2500
+ const descriptor = this.valueDescriptorNameMap[key];
2501
+ if (this.hasValue(key)) {
2502
+ this.invokeChangedCallback(key, descriptor.writer(this.receiver[key]), oldValue);
2503
+ }
2504
+ else {
2505
+ this.invokeChangedCallback(key, descriptor.writer(descriptor.defaultValue), oldValue);
2506
+ }
2507
+ }
2508
+ invokeChangedCallbacksForDefaultValues() {
2509
+ for (const { key, name, defaultValue, writer } of this.valueDescriptors) {
2510
+ if (defaultValue != undefined && !this.controller.data.has(key)) {
2511
+ this.invokeChangedCallback(name, writer(defaultValue), undefined);
2512
+ }
2513
+ }
2514
+ }
2515
+ invokeChangedCallback(name, rawValue, rawOldValue) {
2516
+ const changedMethodName = `${name}Changed`;
2517
+ const changedMethod = this.receiver[changedMethodName];
2518
+ if (typeof changedMethod == "function") {
2519
+ const descriptor = this.valueDescriptorNameMap[name];
2520
+ try {
2521
+ const value = descriptor.reader(rawValue);
2522
+ let oldValue = rawOldValue;
2523
+ if (rawOldValue) {
2524
+ oldValue = descriptor.reader(rawOldValue);
2525
+ }
2526
+ changedMethod.call(this.receiver, value, oldValue);
2527
+ }
2528
+ catch (error) {
2529
+ if (error instanceof TypeError) {
2530
+ error.message = `Stimulus Value "${this.context.identifier}.${descriptor.name}" - ${error.message}`;
2531
+ }
2532
+ throw error;
2533
+ }
2534
+ }
2535
+ }
2536
+ get valueDescriptors() {
2537
+ const { valueDescriptorMap } = this;
2538
+ return Object.keys(valueDescriptorMap).map((key) => valueDescriptorMap[key]);
2539
+ }
2540
+ get valueDescriptorNameMap() {
2541
+ const descriptors = {};
2542
+ Object.keys(this.valueDescriptorMap).forEach((key) => {
2543
+ const descriptor = this.valueDescriptorMap[key];
2544
+ descriptors[descriptor.name] = descriptor;
2545
+ });
2546
+ return descriptors;
2547
+ }
2548
+ hasValue(attributeName) {
2549
+ const descriptor = this.valueDescriptorNameMap[attributeName];
2550
+ const hasMethodName = `has${capitalize(descriptor.name)}`;
2551
+ return this.receiver[hasMethodName];
2552
+ }
2553
+ }
2554
+
2555
+ class TargetObserver {
2556
+ constructor(context, delegate) {
2557
+ this.context = context;
2558
+ this.delegate = delegate;
2559
+ this.targetsByName = new Multimap();
2560
+ }
2561
+ start() {
2562
+ if (!this.tokenListObserver) {
2563
+ this.tokenListObserver = new TokenListObserver(this.element, this.attributeName, this);
2564
+ this.tokenListObserver.start();
2565
+ }
2566
+ }
2567
+ stop() {
2568
+ if (this.tokenListObserver) {
2569
+ this.disconnectAllTargets();
2570
+ this.tokenListObserver.stop();
2571
+ delete this.tokenListObserver;
2572
+ }
2573
+ }
2574
+ tokenMatched({ element, content: name }) {
2575
+ if (this.scope.containsElement(element)) {
2576
+ this.connectTarget(element, name);
2577
+ }
2578
+ }
2579
+ tokenUnmatched({ element, content: name }) {
2580
+ this.disconnectTarget(element, name);
2581
+ }
2582
+ connectTarget(element, name) {
2583
+ var _a;
2584
+ if (!this.targetsByName.has(name, element)) {
2585
+ this.targetsByName.add(name, element);
2586
+ (_a = this.tokenListObserver) === null || _a === void 0 ? void 0 : _a.pause(() => this.delegate.targetConnected(element, name));
2587
+ }
2588
+ }
2589
+ disconnectTarget(element, name) {
2590
+ var _a;
2591
+ if (this.targetsByName.has(name, element)) {
2592
+ this.targetsByName.delete(name, element);
2593
+ (_a = this.tokenListObserver) === null || _a === void 0 ? void 0 : _a.pause(() => this.delegate.targetDisconnected(element, name));
2594
+ }
2595
+ }
2596
+ disconnectAllTargets() {
2597
+ for (const name of this.targetsByName.keys) {
2598
+ for (const element of this.targetsByName.getValuesForKey(name)) {
2599
+ this.disconnectTarget(element, name);
2600
+ }
2601
+ }
2602
+ }
2603
+ get attributeName() {
2604
+ return `data-${this.context.identifier}-target`;
2605
+ }
2606
+ get element() {
2607
+ return this.context.element;
2608
+ }
2609
+ get scope() {
2610
+ return this.context.scope;
2611
+ }
2612
+ }
2613
+
2614
+ function readInheritableStaticArrayValues(constructor, propertyName) {
2615
+ const ancestors = getAncestorsForConstructor(constructor);
2616
+ return Array.from(ancestors.reduce((values, constructor) => {
2617
+ getOwnStaticArrayValues(constructor, propertyName).forEach((name) => values.add(name));
2618
+ return values;
2619
+ }, new Set()));
2620
+ }
2621
+ function getAncestorsForConstructor(constructor) {
2622
+ const ancestors = [];
2623
+ while (constructor) {
2624
+ ancestors.push(constructor);
2625
+ constructor = Object.getPrototypeOf(constructor);
2626
+ }
2627
+ return ancestors.reverse();
2628
+ }
2629
+ function getOwnStaticArrayValues(constructor, propertyName) {
2630
+ const definition = constructor[propertyName];
2631
+ return Array.isArray(definition) ? definition : [];
2632
+ }
2633
+
2634
+ class OutletObserver {
2635
+ constructor(context, delegate) {
2636
+ this.started = false;
2637
+ this.context = context;
2638
+ this.delegate = delegate;
2639
+ this.outletsByName = new Multimap();
2640
+ this.outletElementsByName = new Multimap();
2641
+ this.selectorObserverMap = new Map();
2642
+ this.attributeObserverMap = new Map();
2643
+ }
2644
+ start() {
2645
+ if (!this.started) {
2646
+ this.outletDefinitions.forEach((outletName) => {
2647
+ this.setupSelectorObserverForOutlet(outletName);
2648
+ this.setupAttributeObserverForOutlet(outletName);
2649
+ });
2650
+ this.started = true;
2651
+ this.dependentContexts.forEach((context) => context.refresh());
2652
+ }
2653
+ }
2654
+ refresh() {
2655
+ this.selectorObserverMap.forEach((observer) => observer.refresh());
2656
+ this.attributeObserverMap.forEach((observer) => observer.refresh());
2657
+ }
2658
+ stop() {
2659
+ if (this.started) {
2660
+ this.started = false;
2661
+ this.disconnectAllOutlets();
2662
+ this.stopSelectorObservers();
2663
+ this.stopAttributeObservers();
2664
+ }
2665
+ }
2666
+ stopSelectorObservers() {
2667
+ if (this.selectorObserverMap.size > 0) {
2668
+ this.selectorObserverMap.forEach((observer) => observer.stop());
2669
+ this.selectorObserverMap.clear();
2670
+ }
2671
+ }
2672
+ stopAttributeObservers() {
2673
+ if (this.attributeObserverMap.size > 0) {
2674
+ this.attributeObserverMap.forEach((observer) => observer.stop());
2675
+ this.attributeObserverMap.clear();
2676
+ }
2677
+ }
2678
+ selectorMatched(element, _selector, { outletName }) {
2679
+ const outlet = this.getOutlet(element, outletName);
2680
+ if (outlet) {
2681
+ this.connectOutlet(outlet, element, outletName);
2682
+ }
2683
+ }
2684
+ selectorUnmatched(element, _selector, { outletName }) {
2685
+ const outlet = this.getOutletFromMap(element, outletName);
2686
+ if (outlet) {
2687
+ this.disconnectOutlet(outlet, element, outletName);
2688
+ }
2689
+ }
2690
+ selectorMatchElement(element, { outletName }) {
2691
+ const selector = this.selector(outletName);
2692
+ const hasOutlet = this.hasOutlet(element, outletName);
2693
+ const hasOutletController = element.matches(`[${this.schema.controllerAttribute}~=${outletName}]`);
2694
+ if (selector) {
2695
+ return hasOutlet && hasOutletController && element.matches(selector);
2696
+ }
2697
+ else {
2698
+ return false;
2699
+ }
2700
+ }
2701
+ elementMatchedAttribute(_element, attributeName) {
2702
+ const outletName = this.getOutletNameFromOutletAttributeName(attributeName);
2703
+ if (outletName) {
2704
+ this.updateSelectorObserverForOutlet(outletName);
2705
+ }
2706
+ }
2707
+ elementAttributeValueChanged(_element, attributeName) {
2708
+ const outletName = this.getOutletNameFromOutletAttributeName(attributeName);
2709
+ if (outletName) {
2710
+ this.updateSelectorObserverForOutlet(outletName);
2711
+ }
2712
+ }
2713
+ elementUnmatchedAttribute(_element, attributeName) {
2714
+ const outletName = this.getOutletNameFromOutletAttributeName(attributeName);
2715
+ if (outletName) {
2716
+ this.updateSelectorObserverForOutlet(outletName);
2717
+ }
2718
+ }
2719
+ connectOutlet(outlet, element, outletName) {
2720
+ var _a;
2721
+ if (!this.outletElementsByName.has(outletName, element)) {
2722
+ this.outletsByName.add(outletName, outlet);
2723
+ this.outletElementsByName.add(outletName, element);
2724
+ (_a = this.selectorObserverMap.get(outletName)) === null || _a === void 0 ? void 0 : _a.pause(() => this.delegate.outletConnected(outlet, element, outletName));
2725
+ }
2726
+ }
2727
+ disconnectOutlet(outlet, element, outletName) {
2728
+ var _a;
2729
+ if (this.outletElementsByName.has(outletName, element)) {
2730
+ this.outletsByName.delete(outletName, outlet);
2731
+ this.outletElementsByName.delete(outletName, element);
2732
+ (_a = this.selectorObserverMap
2733
+ .get(outletName)) === null || _a === void 0 ? void 0 : _a.pause(() => this.delegate.outletDisconnected(outlet, element, outletName));
2734
+ }
2735
+ }
2736
+ disconnectAllOutlets() {
2737
+ for (const outletName of this.outletElementsByName.keys) {
2738
+ for (const element of this.outletElementsByName.getValuesForKey(outletName)) {
2739
+ for (const outlet of this.outletsByName.getValuesForKey(outletName)) {
2740
+ this.disconnectOutlet(outlet, element, outletName);
2741
+ }
2742
+ }
2743
+ }
2744
+ }
2745
+ updateSelectorObserverForOutlet(outletName) {
2746
+ const observer = this.selectorObserverMap.get(outletName);
2747
+ if (observer) {
2748
+ observer.selector = this.selector(outletName);
2749
+ }
2750
+ }
2751
+ setupSelectorObserverForOutlet(outletName) {
2752
+ const selector = this.selector(outletName);
2753
+ const selectorObserver = new SelectorObserver(document.body, selector, this, { outletName });
2754
+ this.selectorObserverMap.set(outletName, selectorObserver);
2755
+ selectorObserver.start();
2756
+ }
2757
+ setupAttributeObserverForOutlet(outletName) {
2758
+ const attributeName = this.attributeNameForOutletName(outletName);
2759
+ const attributeObserver = new AttributeObserver(this.scope.element, attributeName, this);
2760
+ this.attributeObserverMap.set(outletName, attributeObserver);
2761
+ attributeObserver.start();
2762
+ }
2763
+ selector(outletName) {
2764
+ return this.scope.outlets.getSelectorForOutletName(outletName);
2765
+ }
2766
+ attributeNameForOutletName(outletName) {
2767
+ return this.scope.schema.outletAttributeForScope(this.identifier, outletName);
2768
+ }
2769
+ getOutletNameFromOutletAttributeName(attributeName) {
2770
+ return this.outletDefinitions.find((outletName) => this.attributeNameForOutletName(outletName) === attributeName);
2771
+ }
2772
+ get outletDependencies() {
2773
+ const dependencies = new Multimap();
2774
+ this.router.modules.forEach((module) => {
2775
+ const constructor = module.definition.controllerConstructor;
2776
+ const outlets = readInheritableStaticArrayValues(constructor, "outlets");
2777
+ outlets.forEach((outlet) => dependencies.add(outlet, module.identifier));
2778
+ });
2779
+ return dependencies;
2780
+ }
2781
+ get outletDefinitions() {
2782
+ return this.outletDependencies.getKeysForValue(this.identifier);
2783
+ }
2784
+ get dependentControllerIdentifiers() {
2785
+ return this.outletDependencies.getValuesForKey(this.identifier);
2786
+ }
2787
+ get dependentContexts() {
2788
+ const identifiers = this.dependentControllerIdentifiers;
2789
+ return this.router.contexts.filter((context) => identifiers.includes(context.identifier));
2790
+ }
2791
+ hasOutlet(element, outletName) {
2792
+ return !!this.getOutlet(element, outletName) || !!this.getOutletFromMap(element, outletName);
2793
+ }
2794
+ getOutlet(element, outletName) {
2795
+ return this.application.getControllerForElementAndIdentifier(element, outletName);
2796
+ }
2797
+ getOutletFromMap(element, outletName) {
2798
+ return this.outletsByName.getValuesForKey(outletName).find((outlet) => outlet.element === element);
2799
+ }
2800
+ get scope() {
2801
+ return this.context.scope;
2802
+ }
2803
+ get schema() {
2804
+ return this.context.schema;
2805
+ }
2806
+ get identifier() {
2807
+ return this.context.identifier;
2808
+ }
2809
+ get application() {
2810
+ return this.context.application;
2811
+ }
2812
+ get router() {
2813
+ return this.application.router;
2814
+ }
2815
+ }
2816
+
2817
+ class Context {
2818
+ constructor(module, scope) {
2819
+ this.logDebugActivity = (functionName, detail = {}) => {
2820
+ const { identifier, controller, element } = this;
2821
+ detail = Object.assign({ identifier, controller, element }, detail);
2822
+ this.application.logDebugActivity(this.identifier, functionName, detail);
2823
+ };
2824
+ this.module = module;
2825
+ this.scope = scope;
2826
+ this.controller = new module.controllerConstructor(this);
2827
+ this.bindingObserver = new BindingObserver(this, this.dispatcher);
2828
+ this.valueObserver = new ValueObserver(this, this.controller);
2829
+ this.targetObserver = new TargetObserver(this, this);
2830
+ this.outletObserver = new OutletObserver(this, this);
2831
+ try {
2832
+ this.controller.initialize();
2833
+ this.logDebugActivity("initialize");
2834
+ }
2835
+ catch (error) {
2836
+ this.handleError(error, "initializing controller");
2837
+ }
2838
+ }
2839
+ connect() {
2840
+ this.bindingObserver.start();
2841
+ this.valueObserver.start();
2842
+ this.targetObserver.start();
2843
+ this.outletObserver.start();
2844
+ try {
2845
+ this.controller.connect();
2846
+ this.logDebugActivity("connect");
2847
+ }
2848
+ catch (error) {
2849
+ this.handleError(error, "connecting controller");
2850
+ }
2851
+ }
2852
+ refresh() {
2853
+ this.outletObserver.refresh();
2854
+ }
2855
+ disconnect() {
2856
+ try {
2857
+ this.controller.disconnect();
2858
+ this.logDebugActivity("disconnect");
2859
+ }
2860
+ catch (error) {
2861
+ this.handleError(error, "disconnecting controller");
2862
+ }
2863
+ this.outletObserver.stop();
2864
+ this.targetObserver.stop();
2865
+ this.valueObserver.stop();
2866
+ this.bindingObserver.stop();
2867
+ }
2868
+ get application() {
2869
+ return this.module.application;
2870
+ }
2871
+ get identifier() {
2872
+ return this.module.identifier;
2873
+ }
2874
+ get schema() {
2875
+ return this.application.schema;
2876
+ }
2877
+ get dispatcher() {
2878
+ return this.application.dispatcher;
2879
+ }
2880
+ get element() {
2881
+ return this.scope.element;
2882
+ }
2883
+ get parentElement() {
2884
+ return this.element.parentElement;
2885
+ }
2886
+ handleError(error, message, detail = {}) {
2887
+ const { identifier, controller, element } = this;
2888
+ detail = Object.assign({ identifier, controller, element }, detail);
2889
+ this.application.handleError(error, `Error ${message}`, detail);
2890
+ }
2891
+ targetConnected(element, name) {
2892
+ this.invokeControllerMethod(`${name}TargetConnected`, element);
2893
+ }
2894
+ targetDisconnected(element, name) {
2895
+ this.invokeControllerMethod(`${name}TargetDisconnected`, element);
2896
+ }
2897
+ outletConnected(outlet, element, name) {
2898
+ this.invokeControllerMethod(`${namespaceCamelize(name)}OutletConnected`, outlet, element);
2899
+ }
2900
+ outletDisconnected(outlet, element, name) {
2901
+ this.invokeControllerMethod(`${namespaceCamelize(name)}OutletDisconnected`, outlet, element);
2902
+ }
2903
+ invokeControllerMethod(methodName, ...args) {
2904
+ const controller = this.controller;
2905
+ if (typeof controller[methodName] == "function") {
2906
+ controller[methodName](...args);
2907
+ }
2908
+ }
2909
+ }
2910
+
2911
+ function bless(constructor) {
2912
+ return shadow(constructor, getBlessedProperties(constructor));
2913
+ }
2914
+ function shadow(constructor, properties) {
2915
+ const shadowConstructor = extend(constructor);
2916
+ const shadowProperties = getShadowProperties(constructor.prototype, properties);
2917
+ Object.defineProperties(shadowConstructor.prototype, shadowProperties);
2918
+ return shadowConstructor;
2919
+ }
2920
+ function getBlessedProperties(constructor) {
2921
+ const blessings = readInheritableStaticArrayValues(constructor, "blessings");
2922
+ return blessings.reduce((blessedProperties, blessing) => {
2923
+ const properties = blessing(constructor);
2924
+ for (const key in properties) {
2925
+ const descriptor = blessedProperties[key] || {};
2926
+ blessedProperties[key] = Object.assign(descriptor, properties[key]);
2927
+ }
2928
+ return blessedProperties;
2929
+ }, {});
2930
+ }
2931
+ function getShadowProperties(prototype, properties) {
2932
+ return getOwnKeys(properties).reduce((shadowProperties, key) => {
2933
+ const descriptor = getShadowedDescriptor(prototype, properties, key);
2934
+ if (descriptor) {
2935
+ Object.assign(shadowProperties, { [key]: descriptor });
2936
+ }
2937
+ return shadowProperties;
2938
+ }, {});
2939
+ }
2940
+ function getShadowedDescriptor(prototype, properties, key) {
2941
+ const shadowingDescriptor = Object.getOwnPropertyDescriptor(prototype, key);
2942
+ const shadowedByValue = shadowingDescriptor && "value" in shadowingDescriptor;
2943
+ if (!shadowedByValue) {
2944
+ const descriptor = Object.getOwnPropertyDescriptor(properties, key).value;
2945
+ if (shadowingDescriptor) {
2946
+ descriptor.get = shadowingDescriptor.get || descriptor.get;
2947
+ descriptor.set = shadowingDescriptor.set || descriptor.set;
2948
+ }
2949
+ return descriptor;
2950
+ }
2951
+ }
2952
+ const getOwnKeys = (() => {
2953
+ if (typeof Object.getOwnPropertySymbols == "function") {
2954
+ return (object) => [...Object.getOwnPropertyNames(object), ...Object.getOwnPropertySymbols(object)];
2955
+ }
2956
+ else {
2957
+ return Object.getOwnPropertyNames;
2958
+ }
2959
+ })();
2960
+ const extend = (() => {
2961
+ function extendWithReflect(constructor) {
2962
+ function extended() {
2963
+ return Reflect.construct(constructor, arguments, new.target);
2964
+ }
2965
+ extended.prototype = Object.create(constructor.prototype, {
2966
+ constructor: { value: extended },
2967
+ });
2968
+ Reflect.setPrototypeOf(extended, constructor);
2969
+ return extended;
2970
+ }
2971
+ function testReflectExtension() {
2972
+ const a = function () {
2973
+ this.a.call(this);
2974
+ };
2975
+ const b = extendWithReflect(a);
2976
+ b.prototype.a = function () { };
2977
+ return new b();
2978
+ }
2979
+ try {
2980
+ testReflectExtension();
2981
+ return extendWithReflect;
2982
+ }
2983
+ catch (error) {
2984
+ return (constructor) => class extended extends constructor {
2985
+ };
2986
+ }
2987
+ })();
2988
+
2989
+ function blessDefinition(definition) {
2990
+ return {
2991
+ identifier: definition.identifier,
2992
+ controllerConstructor: bless(definition.controllerConstructor),
2993
+ };
2994
+ }
2995
+
2996
+ class Module {
2997
+ constructor(application, definition) {
2998
+ this.application = application;
2999
+ this.definition = blessDefinition(definition);
3000
+ this.contextsByScope = new WeakMap();
3001
+ this.connectedContexts = new Set();
3002
+ }
3003
+ get identifier() {
3004
+ return this.definition.identifier;
3005
+ }
3006
+ get controllerConstructor() {
3007
+ return this.definition.controllerConstructor;
3008
+ }
3009
+ get contexts() {
3010
+ return Array.from(this.connectedContexts);
3011
+ }
3012
+ connectContextForScope(scope) {
3013
+ const context = this.fetchContextForScope(scope);
3014
+ this.connectedContexts.add(context);
3015
+ context.connect();
3016
+ }
3017
+ disconnectContextForScope(scope) {
3018
+ const context = this.contextsByScope.get(scope);
3019
+ if (context) {
3020
+ this.connectedContexts.delete(context);
3021
+ context.disconnect();
3022
+ }
3023
+ }
3024
+ fetchContextForScope(scope) {
3025
+ let context = this.contextsByScope.get(scope);
3026
+ if (!context) {
3027
+ context = new Context(this, scope);
3028
+ this.contextsByScope.set(scope, context);
3029
+ }
3030
+ return context;
3031
+ }
3032
+ }
3033
+
3034
+ class ClassMap {
3035
+ constructor(scope) {
3036
+ this.scope = scope;
3037
+ }
3038
+ has(name) {
3039
+ return this.data.has(this.getDataKey(name));
3040
+ }
3041
+ get(name) {
3042
+ return this.getAll(name)[0];
3043
+ }
3044
+ getAll(name) {
3045
+ const tokenString = this.data.get(this.getDataKey(name)) || "";
3046
+ return tokenize(tokenString);
3047
+ }
3048
+ getAttributeName(name) {
3049
+ return this.data.getAttributeNameForKey(this.getDataKey(name));
3050
+ }
3051
+ getDataKey(name) {
3052
+ return `${name}-class`;
3053
+ }
3054
+ get data() {
3055
+ return this.scope.data;
3056
+ }
3057
+ }
3058
+
3059
+ class DataMap {
3060
+ constructor(scope) {
3061
+ this.scope = scope;
3062
+ }
3063
+ get element() {
3064
+ return this.scope.element;
3065
+ }
3066
+ get identifier() {
3067
+ return this.scope.identifier;
3068
+ }
3069
+ get(key) {
3070
+ const name = this.getAttributeNameForKey(key);
3071
+ return this.element.getAttribute(name);
3072
+ }
3073
+ set(key, value) {
3074
+ const name = this.getAttributeNameForKey(key);
3075
+ this.element.setAttribute(name, value);
3076
+ return this.get(key);
3077
+ }
3078
+ has(key) {
3079
+ const name = this.getAttributeNameForKey(key);
3080
+ return this.element.hasAttribute(name);
3081
+ }
3082
+ delete(key) {
3083
+ if (this.has(key)) {
3084
+ const name = this.getAttributeNameForKey(key);
3085
+ this.element.removeAttribute(name);
3086
+ return true;
3087
+ }
3088
+ else {
3089
+ return false;
3090
+ }
3091
+ }
3092
+ getAttributeNameForKey(key) {
3093
+ return `data-${this.identifier}-${dasherize(key)}`;
3094
+ }
3095
+ }
3096
+
3097
+ class Guide {
3098
+ constructor(logger) {
3099
+ this.warnedKeysByObject = new WeakMap();
3100
+ this.logger = logger;
3101
+ }
3102
+ warn(object, key, message) {
3103
+ let warnedKeys = this.warnedKeysByObject.get(object);
3104
+ if (!warnedKeys) {
3105
+ warnedKeys = new Set();
3106
+ this.warnedKeysByObject.set(object, warnedKeys);
3107
+ }
3108
+ if (!warnedKeys.has(key)) {
3109
+ warnedKeys.add(key);
3110
+ this.logger.warn(message, object);
3111
+ }
3112
+ }
3113
+ }
3114
+
3115
+ function attributeValueContainsToken(attributeName, token) {
3116
+ return `[${attributeName}~="${token}"]`;
3117
+ }
3118
+
3119
+ class TargetSet {
3120
+ constructor(scope) {
3121
+ this.scope = scope;
3122
+ }
3123
+ get element() {
3124
+ return this.scope.element;
3125
+ }
3126
+ get identifier() {
3127
+ return this.scope.identifier;
3128
+ }
3129
+ get schema() {
3130
+ return this.scope.schema;
3131
+ }
3132
+ has(targetName) {
3133
+ return this.find(targetName) != null;
3134
+ }
3135
+ find(...targetNames) {
3136
+ return targetNames.reduce((target, targetName) => target || this.findTarget(targetName) || this.findLegacyTarget(targetName), undefined);
3137
+ }
3138
+ findAll(...targetNames) {
3139
+ return targetNames.reduce((targets, targetName) => [
3140
+ ...targets,
3141
+ ...this.findAllTargets(targetName),
3142
+ ...this.findAllLegacyTargets(targetName),
3143
+ ], []);
3144
+ }
3145
+ findTarget(targetName) {
3146
+ const selector = this.getSelectorForTargetName(targetName);
3147
+ return this.scope.findElement(selector);
3148
+ }
3149
+ findAllTargets(targetName) {
3150
+ const selector = this.getSelectorForTargetName(targetName);
3151
+ return this.scope.findAllElements(selector);
3152
+ }
3153
+ getSelectorForTargetName(targetName) {
3154
+ const attributeName = this.schema.targetAttributeForScope(this.identifier);
3155
+ return attributeValueContainsToken(attributeName, targetName);
3156
+ }
3157
+ findLegacyTarget(targetName) {
3158
+ const selector = this.getLegacySelectorForTargetName(targetName);
3159
+ return this.deprecate(this.scope.findElement(selector), targetName);
3160
+ }
3161
+ findAllLegacyTargets(targetName) {
3162
+ const selector = this.getLegacySelectorForTargetName(targetName);
3163
+ return this.scope.findAllElements(selector).map((element) => this.deprecate(element, targetName));
3164
+ }
3165
+ getLegacySelectorForTargetName(targetName) {
3166
+ const targetDescriptor = `${this.identifier}.${targetName}`;
3167
+ return attributeValueContainsToken(this.schema.targetAttribute, targetDescriptor);
3168
+ }
3169
+ deprecate(element, targetName) {
3170
+ if (element) {
3171
+ const { identifier } = this;
3172
+ const attributeName = this.schema.targetAttribute;
3173
+ const revisedAttributeName = this.schema.targetAttributeForScope(identifier);
3174
+ this.guide.warn(element, `target:${targetName}`, `Please replace ${attributeName}="${identifier}.${targetName}" with ${revisedAttributeName}="${targetName}". ` +
3175
+ `The ${attributeName} attribute is deprecated and will be removed in a future version of Stimulus.`);
3176
+ }
3177
+ return element;
3178
+ }
3179
+ get guide() {
3180
+ return this.scope.guide;
3181
+ }
3182
+ }
3183
+
3184
+ class OutletSet {
3185
+ constructor(scope, controllerElement) {
3186
+ this.scope = scope;
3187
+ this.controllerElement = controllerElement;
3188
+ }
3189
+ get element() {
3190
+ return this.scope.element;
3191
+ }
3192
+ get identifier() {
3193
+ return this.scope.identifier;
3194
+ }
3195
+ get schema() {
3196
+ return this.scope.schema;
3197
+ }
3198
+ has(outletName) {
3199
+ return this.find(outletName) != null;
3200
+ }
3201
+ find(...outletNames) {
3202
+ return outletNames.reduce((outlet, outletName) => outlet || this.findOutlet(outletName), undefined);
3203
+ }
3204
+ findAll(...outletNames) {
3205
+ return outletNames.reduce((outlets, outletName) => [...outlets, ...this.findAllOutlets(outletName)], []);
3206
+ }
3207
+ getSelectorForOutletName(outletName) {
3208
+ const attributeName = this.schema.outletAttributeForScope(this.identifier, outletName);
3209
+ return this.controllerElement.getAttribute(attributeName);
3210
+ }
3211
+ findOutlet(outletName) {
3212
+ const selector = this.getSelectorForOutletName(outletName);
3213
+ if (selector)
3214
+ return this.findElement(selector, outletName);
3215
+ }
3216
+ findAllOutlets(outletName) {
3217
+ const selector = this.getSelectorForOutletName(outletName);
3218
+ return selector ? this.findAllElements(selector, outletName) : [];
3219
+ }
3220
+ findElement(selector, outletName) {
3221
+ const elements = this.scope.queryElements(selector);
3222
+ return elements.filter((element) => this.matchesElement(element, selector, outletName))[0];
3223
+ }
3224
+ findAllElements(selector, outletName) {
3225
+ const elements = this.scope.queryElements(selector);
3226
+ return elements.filter((element) => this.matchesElement(element, selector, outletName));
3227
+ }
3228
+ matchesElement(element, selector, outletName) {
3229
+ const controllerAttribute = element.getAttribute(this.scope.schema.controllerAttribute) || "";
3230
+ return element.matches(selector) && controllerAttribute.split(" ").includes(outletName);
3231
+ }
3232
+ }
3233
+
3234
+ class Scope {
3235
+ constructor(schema, element, identifier, logger) {
3236
+ this.targets = new TargetSet(this);
3237
+ this.classes = new ClassMap(this);
3238
+ this.data = new DataMap(this);
3239
+ this.containsElement = (element) => {
3240
+ return element.closest(this.controllerSelector) === this.element;
3241
+ };
3242
+ this.schema = schema;
3243
+ this.element = element;
3244
+ this.identifier = identifier;
3245
+ this.guide = new Guide(logger);
3246
+ this.outlets = new OutletSet(this.documentScope, element);
3247
+ }
3248
+ findElement(selector) {
3249
+ return this.element.matches(selector) ? this.element : this.queryElements(selector).find(this.containsElement);
3250
+ }
3251
+ findAllElements(selector) {
3252
+ return [
3253
+ ...(this.element.matches(selector) ? [this.element] : []),
3254
+ ...this.queryElements(selector).filter(this.containsElement),
3255
+ ];
3256
+ }
3257
+ queryElements(selector) {
3258
+ return Array.from(this.element.querySelectorAll(selector));
3259
+ }
3260
+ get controllerSelector() {
3261
+ return attributeValueContainsToken(this.schema.controllerAttribute, this.identifier);
3262
+ }
3263
+ get isDocumentScope() {
3264
+ return this.element === document.documentElement;
3265
+ }
3266
+ get documentScope() {
3267
+ return this.isDocumentScope
3268
+ ? this
3269
+ : new Scope(this.schema, document.documentElement, this.identifier, this.guide.logger);
3270
+ }
3271
+ }
3272
+
3273
+ class ScopeObserver {
3274
+ constructor(element, schema, delegate) {
3275
+ this.element = element;
3276
+ this.schema = schema;
3277
+ this.delegate = delegate;
3278
+ this.valueListObserver = new ValueListObserver(this.element, this.controllerAttribute, this);
3279
+ this.scopesByIdentifierByElement = new WeakMap();
3280
+ this.scopeReferenceCounts = new WeakMap();
3281
+ }
3282
+ start() {
3283
+ this.valueListObserver.start();
3284
+ }
3285
+ stop() {
3286
+ this.valueListObserver.stop();
3287
+ }
3288
+ get controllerAttribute() {
3289
+ return this.schema.controllerAttribute;
3290
+ }
3291
+ parseValueForToken(token) {
3292
+ const { element, content: identifier } = token;
3293
+ return this.parseValueForElementAndIdentifier(element, identifier);
3294
+ }
3295
+ parseValueForElementAndIdentifier(element, identifier) {
3296
+ const scopesByIdentifier = this.fetchScopesByIdentifierForElement(element);
3297
+ let scope = scopesByIdentifier.get(identifier);
3298
+ if (!scope) {
3299
+ scope = this.delegate.createScopeForElementAndIdentifier(element, identifier);
3300
+ scopesByIdentifier.set(identifier, scope);
3301
+ }
3302
+ return scope;
3303
+ }
3304
+ elementMatchedValue(element, value) {
3305
+ const referenceCount = (this.scopeReferenceCounts.get(value) || 0) + 1;
3306
+ this.scopeReferenceCounts.set(value, referenceCount);
3307
+ if (referenceCount == 1) {
3308
+ this.delegate.scopeConnected(value);
3309
+ }
3310
+ }
3311
+ elementUnmatchedValue(element, value) {
3312
+ const referenceCount = this.scopeReferenceCounts.get(value);
3313
+ if (referenceCount) {
3314
+ this.scopeReferenceCounts.set(value, referenceCount - 1);
3315
+ if (referenceCount == 1) {
3316
+ this.delegate.scopeDisconnected(value);
3317
+ }
3318
+ }
3319
+ }
3320
+ fetchScopesByIdentifierForElement(element) {
3321
+ let scopesByIdentifier = this.scopesByIdentifierByElement.get(element);
3322
+ if (!scopesByIdentifier) {
3323
+ scopesByIdentifier = new Map();
3324
+ this.scopesByIdentifierByElement.set(element, scopesByIdentifier);
3325
+ }
3326
+ return scopesByIdentifier;
3327
+ }
3328
+ }
3329
+
3330
+ class Router {
3331
+ constructor(application) {
3332
+ this.application = application;
3333
+ this.scopeObserver = new ScopeObserver(this.element, this.schema, this);
3334
+ this.scopesByIdentifier = new Multimap();
3335
+ this.modulesByIdentifier = new Map();
3336
+ }
3337
+ get element() {
3338
+ return this.application.element;
3339
+ }
3340
+ get schema() {
3341
+ return this.application.schema;
3342
+ }
3343
+ get logger() {
3344
+ return this.application.logger;
3345
+ }
3346
+ get controllerAttribute() {
3347
+ return this.schema.controllerAttribute;
3348
+ }
3349
+ get modules() {
3350
+ return Array.from(this.modulesByIdentifier.values());
3351
+ }
3352
+ get contexts() {
3353
+ return this.modules.reduce((contexts, module) => contexts.concat(module.contexts), []);
3354
+ }
3355
+ start() {
3356
+ this.scopeObserver.start();
3357
+ }
3358
+ stop() {
3359
+ this.scopeObserver.stop();
3360
+ }
3361
+ loadDefinition(definition) {
3362
+ this.unloadIdentifier(definition.identifier);
3363
+ const module = new Module(this.application, definition);
3364
+ this.connectModule(module);
3365
+ const afterLoad = definition.controllerConstructor.afterLoad;
3366
+ if (afterLoad) {
3367
+ afterLoad.call(definition.controllerConstructor, definition.identifier, this.application);
3368
+ }
3369
+ }
3370
+ unloadIdentifier(identifier) {
3371
+ const module = this.modulesByIdentifier.get(identifier);
3372
+ if (module) {
3373
+ this.disconnectModule(module);
3374
+ }
3375
+ }
3376
+ getContextForElementAndIdentifier(element, identifier) {
3377
+ const module = this.modulesByIdentifier.get(identifier);
3378
+ if (module) {
3379
+ return module.contexts.find((context) => context.element == element);
3380
+ }
3381
+ }
3382
+ proposeToConnectScopeForElementAndIdentifier(element, identifier) {
3383
+ const scope = this.scopeObserver.parseValueForElementAndIdentifier(element, identifier);
3384
+ if (scope) {
3385
+ this.scopeObserver.elementMatchedValue(scope.element, scope);
3386
+ }
3387
+ else {
3388
+ console.error(`Couldn't find or create scope for identifier: "${identifier}" and element:`, element);
3389
+ }
3390
+ }
3391
+ handleError(error, message, detail) {
3392
+ this.application.handleError(error, message, detail);
3393
+ }
3394
+ createScopeForElementAndIdentifier(element, identifier) {
3395
+ return new Scope(this.schema, element, identifier, this.logger);
3396
+ }
3397
+ scopeConnected(scope) {
3398
+ this.scopesByIdentifier.add(scope.identifier, scope);
3399
+ const module = this.modulesByIdentifier.get(scope.identifier);
3400
+ if (module) {
3401
+ module.connectContextForScope(scope);
3402
+ }
3403
+ }
3404
+ scopeDisconnected(scope) {
3405
+ this.scopesByIdentifier.delete(scope.identifier, scope);
3406
+ const module = this.modulesByIdentifier.get(scope.identifier);
3407
+ if (module) {
3408
+ module.disconnectContextForScope(scope);
3409
+ }
3410
+ }
3411
+ connectModule(module) {
3412
+ this.modulesByIdentifier.set(module.identifier, module);
3413
+ const scopes = this.scopesByIdentifier.getValuesForKey(module.identifier);
3414
+ scopes.forEach((scope) => module.connectContextForScope(scope));
3415
+ }
3416
+ disconnectModule(module) {
3417
+ this.modulesByIdentifier.delete(module.identifier);
3418
+ const scopes = this.scopesByIdentifier.getValuesForKey(module.identifier);
3419
+ scopes.forEach((scope) => module.disconnectContextForScope(scope));
3420
+ }
3421
+ }
3422
+
3423
+ const defaultSchema = {
3424
+ controllerAttribute: "data-controller",
3425
+ actionAttribute: "data-action",
3426
+ targetAttribute: "data-target",
3427
+ targetAttributeForScope: (identifier) => `data-${identifier}-target`,
3428
+ outletAttributeForScope: (identifier, outlet) => `data-${identifier}-${outlet}-outlet`,
3429
+ keyMappings: Object.assign(Object.assign({ enter: "Enter", tab: "Tab", esc: "Escape", space: " ", up: "ArrowUp", down: "ArrowDown", left: "ArrowLeft", right: "ArrowRight", home: "Home", end: "End", page_up: "PageUp", page_down: "PageDown" }, objectFromEntries("abcdefghijklmnopqrstuvwxyz".split("").map((c) => [c, c]))), objectFromEntries("0123456789".split("").map((n) => [n, n]))),
3430
+ };
3431
+ function objectFromEntries(array) {
3432
+ return array.reduce((memo, [k, v]) => (Object.assign(Object.assign({}, memo), { [k]: v })), {});
3433
+ }
3434
+
3435
+ class Application {
3436
+ constructor(element = document.documentElement, schema = defaultSchema) {
3437
+ this.logger = console;
3438
+ this.debug = false;
3439
+ this.logDebugActivity = (identifier, functionName, detail = {}) => {
3440
+ if (this.debug) {
3441
+ this.logFormattedMessage(identifier, functionName, detail);
3442
+ }
3443
+ };
3444
+ this.element = element;
3445
+ this.schema = schema;
3446
+ this.dispatcher = new Dispatcher(this);
3447
+ this.router = new Router(this);
3448
+ this.actionDescriptorFilters = Object.assign({}, defaultActionDescriptorFilters);
3449
+ }
3450
+ static start(element, schema) {
3451
+ const application = new this(element, schema);
3452
+ application.start();
3453
+ return application;
3454
+ }
3455
+ async start() {
3456
+ await domReady();
3457
+ this.logDebugActivity("application", "starting");
3458
+ this.dispatcher.start();
3459
+ this.router.start();
3460
+ this.logDebugActivity("application", "start");
3461
+ }
3462
+ stop() {
3463
+ this.logDebugActivity("application", "stopping");
3464
+ this.dispatcher.stop();
3465
+ this.router.stop();
3466
+ this.logDebugActivity("application", "stop");
3467
+ }
3468
+ register(identifier, controllerConstructor) {
3469
+ this.load({ identifier, controllerConstructor });
3470
+ }
3471
+ registerActionOption(name, filter) {
3472
+ this.actionDescriptorFilters[name] = filter;
3473
+ }
3474
+ load(head, ...rest) {
3475
+ const definitions = Array.isArray(head) ? head : [head, ...rest];
3476
+ definitions.forEach((definition) => {
3477
+ if (definition.controllerConstructor.shouldLoad) {
3478
+ this.router.loadDefinition(definition);
3479
+ }
3480
+ });
3481
+ }
3482
+ unload(head, ...rest) {
3483
+ const identifiers = Array.isArray(head) ? head : [head, ...rest];
3484
+ identifiers.forEach((identifier) => this.router.unloadIdentifier(identifier));
3485
+ }
3486
+ get controllers() {
3487
+ return this.router.contexts.map((context) => context.controller);
3488
+ }
3489
+ getControllerForElementAndIdentifier(element, identifier) {
3490
+ const context = this.router.getContextForElementAndIdentifier(element, identifier);
3491
+ return context ? context.controller : null;
3492
+ }
3493
+ handleError(error, message, detail) {
3494
+ var _a;
3495
+ this.logger.error(`%s\n\n%o\n\n%o`, message, error, detail);
3496
+ (_a = window.onerror) === null || _a === void 0 ? void 0 : _a.call(window, message, "", 0, 0, error);
3497
+ }
3498
+ logFormattedMessage(identifier, functionName, detail = {}) {
3499
+ detail = Object.assign({ application: this }, detail);
3500
+ this.logger.groupCollapsed(`${identifier} #${functionName}`);
3501
+ this.logger.log("details:", Object.assign({}, detail));
3502
+ this.logger.groupEnd();
3503
+ }
3504
+ }
3505
+ function domReady() {
3506
+ return new Promise((resolve) => {
3507
+ if (document.readyState == "loading") {
3508
+ document.addEventListener("DOMContentLoaded", () => resolve());
3509
+ }
3510
+ else {
3511
+ resolve();
3512
+ }
3513
+ });
3514
+ }
3515
+
3516
+ class StimulusReloader {
3517
+ static async reload(filePattern) {
3518
+ const document = await reloadHtmlDocument();
3519
+ return new StimulusReloader(document, filePattern).reload();
3520
+ }
3521
+ constructor(document) {
3522
+ let filePattern = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : /./;
3523
+ this.document = document;
3524
+ this.filePattern = filePattern;
3525
+ this.application = window.Stimulus || Application.start();
3526
+ }
3527
+ async reload() {
3528
+ log("Reload Stimulus controllers...");
3529
+ this.application.stop();
3530
+ await this.#reloadStimulusControllers();
3531
+ this.application.start();
3532
+ }
3533
+ async #reloadStimulusControllers() {
3534
+ await Promise.all(this.#stimulusControllerPaths.map(async moduleName => this.#reloadStimulusController(moduleName)));
3535
+ }
3536
+ get #stimulusControllerPaths() {
3537
+ return Object.keys(this.#stimulusPathsByModule).filter(path => path.endsWith("_controller") && this.#shouldReloadController(path));
3538
+ }
3539
+ #shouldReloadController(path) {
3540
+ return this.filePattern.test(path);
3541
+ }
3542
+ get #stimulusPathsByModule() {
3543
+ this.pathsByModule = this.pathsByModule || this.#parseImportmapJson();
3544
+ return this.pathsByModule;
3545
+ }
3546
+ #parseImportmapJson() {
3547
+ const importmapScript = this.document.querySelector("script[type=importmap]");
3548
+ return JSON.parse(importmapScript.text).imports;
3549
+ }
3550
+ async #reloadStimulusController(moduleName) {
3551
+ log(`\t${moduleName}`);
3552
+ const controllerName = this.#extractControllerName(moduleName);
3553
+ const path = cacheBustedUrl(this.#pathForModuleName(moduleName));
3554
+ const module = await import(path);
3555
+ this.#registerController(controllerName, module);
3556
+ }
3557
+ #pathForModuleName(moduleName) {
3558
+ return this.#stimulusPathsByModule[moduleName];
3559
+ }
3560
+ #extractControllerName(path) {
3561
+ return path.replace(/^.*\//, "").replace("_controller", "").replace(/\//g, "--").replace(/_/g, "-");
3562
+ }
3563
+ #registerController(name, module) {
3564
+ this.application.unload(name);
3565
+ this.application.register(name, module.default);
3566
+ }
3567
+ }
3568
+
3569
+ class HtmlReloader {
3570
+ static async reload() {
3571
+ return new HtmlReloader().reload();
3572
+ }
3573
+ async reload() {
3574
+ const reloadedDocument = await this.#reloadHtml();
3575
+ await this.#reloadStimulus(reloadedDocument);
3576
+ }
3577
+ async #reloadHtml() {
3578
+ log("Reload html...");
3579
+ const reloadedDocument = await reloadHtmlDocument();
3580
+ this.#updateBody(reloadedDocument.body);
3581
+ return reloadedDocument;
3582
+ }
3583
+ async #reloadStimulus(reloadedDocument) {
3584
+ return new StimulusReloader(reloadedDocument).reload();
3585
+ }
3586
+ #updateBody(newBody) {
3587
+ Idiomorph.morph(document.body, newBody);
3588
+ }
3589
+ }
3590
+
3591
+ class CssReloader {
3592
+ static async reload() {
3593
+ for (var _len = arguments.length, params = new Array(_len), _key = 0; _key < _len; _key++) {
3594
+ params[_key] = arguments[_key];
3595
+ }
3596
+ return new CssReloader(...params).reload();
3597
+ }
3598
+ constructor() {
3599
+ let filePattern = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : /./;
3600
+ this.filePattern = filePattern;
3601
+ }
3602
+ async reload() {
3603
+ log("Reload css...");
3604
+ await Promise.all(await this.#reloadAllLinks());
3605
+ }
3606
+ async #reloadAllLinks() {
3607
+ const newCssLinks = await this.#loadNewCssLinks();
3608
+ return newCssLinks.map(link => this.#reloadLinkIfNeeded(link));
3609
+ }
3610
+ async #loadNewCssLinks() {
3611
+ const reloadedDocument = await reloadHtmlDocument();
3612
+ return Array.from(reloadedDocument.head.querySelectorAll("link[rel='stylesheet']"));
3613
+ }
3614
+ #reloadLinkIfNeeded(link) {
3615
+ console.debug("reload if needed", link);
3616
+ if (this.#shouldReloadLink(link)) {
3617
+ return this.#reloadLink(link);
3618
+ } else {
3619
+ return Promise.resolve();
3620
+ }
3621
+ }
3622
+ #shouldReloadLink(link) {
3623
+ console.debug("CHECKING ", link.getAttribute("href"), this.filePattern);
3624
+ return this.filePattern.test(link.getAttribute("href"));
3625
+ }
3626
+ async #reloadLink(link) {
3627
+ return new Promise(resolve => {
3628
+ const href = link.getAttribute("href");
3629
+ const newLink = this.#findExistingLinkFor(link) || this.#appendNewLink(link);
3630
+ newLink.setAttribute("href", cacheBustedUrl(link.getAttribute("href")));
3631
+ newLink.onload = () => {
3632
+ log(`\t${href}`);
3633
+ resolve();
3634
+ };
3635
+ });
3636
+ }
3637
+ #findExistingLinkFor(link) {
3638
+ return this.#cssLinks.find(newLink => {
3639
+ return this.#withoutAssetDigest(link.href) === this.#withoutAssetDigest(newLink.href);
3640
+ });
3641
+ }
3642
+ get #cssLinks() {
3643
+ return Array.from(document.querySelectorAll("link[rel='stylesheet']"));
3644
+ }
3645
+ #withoutAssetDigest(url) {
3646
+ return url.replace(/-[^-]+\.css.*$/, ".css");
3647
+ }
3648
+ #appendNewLink(link) {
3649
+ document.head.append(link);
3650
+ return link;
3651
+ }
3652
+ }
3653
+
3654
+ consumer.subscriptions.create({
3655
+ channel: "HotwireSpark::Channel"
3656
+ }, {
3657
+ connected() {
3658
+ document.body.setAttribute("data-hotwire-spark-ready", "");
3659
+ },
3660
+ async received(data) {
3661
+ try {
3662
+ await this.dispatchMessage(data);
3663
+ } catch (error) {
3664
+ console.log(`Error on ${data.action}`, error);
3665
+ }
3666
+ },
3667
+ dispatchMessage(_ref) {
3668
+ let {
3669
+ action,
3670
+ path
3671
+ } = _ref;
3672
+ const fileName = nameFromFilePath(path);
3673
+ switch (action) {
3674
+ case "reload_html":
3675
+ return this.reloadHtml();
3676
+ case "reload_css":
3677
+ return this.reloadCss(fileName);
3678
+ case "reload_stimulus":
3679
+ return this.reloadStimulus(fileName);
3680
+ }
3681
+ },
3682
+ reloadHtml() {
3683
+ return HtmlReloader.reload();
3684
+ },
3685
+ reloadCss(path) {
3686
+ return CssReloader.reload(new RegExp(path));
3687
+ },
3688
+ reloadStimulus(path) {
3689
+ return StimulusReloader.reload(new RegExp(path));
3690
+ }
3691
+ });
3692
+
3693
+ const HotwireSpark = {
3694
+ config: {
3695
+ loggingEnabled: true
3696
+ }
3697
+ };
3698
+
3699
+ return HotwireSpark;
3700
+
3701
+ })();