vendor: 339 bytes, lines 1-10
1var __defProp = Object.defineProperty; 2var __getOwnPropNames = Object.getOwnPropertyNames; 3var __esm = (fn, res) => function __init() { 4 return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; 5}; 6var __export = (target, all) => { 7 for (var name in all) 8 __defProp(target, name, { get: all[name], enumerable: true }); 9}; 10
vendor: 334 bytes, lines 11-21
11// node_modules/@rails/actioncable/src/adapters.js 12var adapters_default; 13var init_adapters = __esm({ 14 "node_modules/@rails/actioncable/src/adapters.js"() { 15 adapters_default = { 16 logger: typeof console !== "undefined" ? console : void 0, 17 WebSocket: typeof WebSocket !== "undefined" ? WebSocket : void 0 18 }; 19 } 20}); 21
vendor: 386 bytes, lines 22-37
22// node_modules/@rails/actioncable/src/logger.js 23var logger_default; 24var init_logger = __esm({ 25 "node_modules/@rails/actioncable/src/logger.js"() { 26 init_adapters(); 27 logger_default = { 28 log(...messages) { 29 if (this.enabled) { 30 messages.push(Date.now()); 31 adapters_default.logger.log("[ActionCable]", ...messages); 32 } 33 } 34 }; 35 } 36}); 37
vendor: 4,101 bytes, lines 38-147
38// node_modules/@rails/actioncable/src/connection_monitor.js 39var now, secondsSince, ConnectionMonitor, connection_monitor_default; 40var init_connection_monitor = __esm({ 41 "node_modules/@rails/actioncable/src/connection_monitor.js"() { 42 init_logger(); 43 now = () => (/* @__PURE__ */ new Date()).getTime(); 44 secondsSince = (time) => (now() - time) / 1e3; 45 ConnectionMonitor = class { 46 constructor(connection) { 47 this.visibilityDidChange = this.visibilityDidChange.bind(this); 48 this.connection = connection; 49 this.reconnectAttempts = 0; 50 } 51 start() { 52 if (!this.isRunning()) { 53 this.startedAt = now(); 54 delete this.stoppedAt; 55 this.startPolling(); 56 addEventListener("visibilitychange", this.visibilityDidChange); 57 logger_default.log(`ConnectionMonitor started. stale threshold = ${this.constructor.staleThreshold} s`); 58 } 59 } 60 stop() { 61 if (this.isRunning()) { 62 this.stoppedAt = now(); 63 this.stopPolling(); 64 removeEventListener("visibilitychange", this.visibilityDidChange); 65 logger_default.log("ConnectionMonitor stopped"); 66 } 67 } 68 isRunning() { 69 return this.startedAt && !this.stoppedAt; 70 } 71 recordMessage() { 72 this.pingedAt = now(); 73 } 74 recordConnect() { 75 this.reconnectAttempts = 0; 76 delete this.disconnectedAt; 77 logger_default.log("ConnectionMonitor recorded connect"); 78 } 79 recordDisconnect() { 80 this.disconnectedAt = now(); 81 logger_default.log("ConnectionMonitor recorded disconnect"); 82 } 83 // Private 84 startPolling() { 85 this.stopPolling(); 86 this.poll(); 87 } 88 stopPolling() { 89 clearTimeout(this.pollTimeout); 90 } 91 poll() { 92 this.pollTimeout = setTimeout( 93 () => { 94 this.reconnectIfStale(); 95 this.poll(); 96 }, 97 this.getPollInterval() 98 ); 99 } 100 getPollInterval() { 101 const { staleThreshold, reconnectionBackoffRate } = this.constructor; 102 const backoff = Math.pow(1 + reconnectionBackoffRate, Math.min(this.reconnectAttempts, 10)); 103 const jitterMax = this.reconnectAttempts === 0 ? 1 : reconnectionBackoffRate; 104 const jitter = jitterMax * Math.random(); 105 return staleThreshold * 1e3 * backoff * (1 + jitter); 106 } 107 reconnectIfStale() { 108 if (this.connectionIsStale()) { 109 logger_default.log(`ConnectionMonitor detected stale connection. reconnectAttempts = ${this.reconnectAttempts}, time stale = ${secondsSince(this.refreshedAt)} s, stale threshold = ${this.constructor.staleThreshold} s`); 110 this.reconnectAttempts++; 111 if (this.disconnectedRecently()) { 112 logger_default.log(`ConnectionMonitor skipping reopening recent disconnect. time disconnected = ${secondsSince(this.disconnectedAt)} s`); 113 } else { 114 logger_default.log("ConnectionMonitor reopening"); 115 this.connection.reopen(); 116 } 117 } 118 } 119 get refreshedAt() { 120 return this.pingedAt ? this.pingedAt : this.startedAt; 121 } 122 connectionIsStale() { 123 return secondsSince(this.refreshedAt) > this.constructor.staleThreshold; 124 } 125 disconnectedRecently() { 126 return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold; 127 } 128 visibilityDidChange() { 129 if (document.visibilityState === "visible") { 130 setTimeout( 131 () => { 132 if (this.connectionIsStale() || !this.connection.isOpen()) { 133 logger_default.log(`ConnectionMonitor reopening stale connection on visibilitychange. visibilityState = ${document.visibilityState}`); 134 this.connection.reopen(); 135 } 136 }, 137 200 138 ); 139 } 140 } 141 }; 142 ConnectionMonitor.staleThreshold = 6; 143 ConnectionMonitor.reconnectionBackoffRate = 0.15; 144 connection_monitor_default = ConnectionMonitor; 145 } 146}); 147
vendor: 741 bytes, lines 148-174
148// node_modules/@rails/actioncable/src/internal.js 149var internal_default; 150var init_internal = __esm({ 151 "node_modules/@rails/actioncable/src/internal.js"() { 152 internal_default = { 153 "message_types": { 154 "welcome": "welcome", 155 "disconnect": "disconnect", 156 "ping": "ping", 157 "confirmation": "confirm_subscription", 158 "rejection": "reject_subscription" 159 }, 160 "disconnect_reasons": { 161 "unauthorized": "unauthorized", 162 "invalid_request": "invalid_request", 163 "server_restart": "server_restart", 164 "remote": "remote" 165 }, 166 "default_mount_path": "/cable", 167 "protocols": [ 168 "actioncable-v1-json", 169 "actioncable-unsupported" 170 ] 171 }; 172 } 173}); 174
vendor: 5,887 bytes, lines 175-343
175// node_modules/@rails/actioncable/src/connection.js 176var message_types, protocols, supportedProtocols, indexOf, Connection, connection_default; 177var init_connection = __esm({ 178 "node_modules/@rails/actioncable/src/connection.js"() { 179 init_adapters(); 180 init_connection_monitor(); 181 init_internal(); 182 init_logger(); 183 ({ message_types, protocols } = internal_default); 184 supportedProtocols = protocols.slice(0, protocols.length - 1); 185 indexOf = [].indexOf; 186 Connection = class { 187 constructor(consumer2) { 188 this.open = this.open.bind(this); 189 this.consumer = consumer2; 190 this.subscriptions = this.consumer.subscriptions; 191 this.monitor = new connection_monitor_default(this); 192 this.disconnected = true; 193 } 194 send(data) { 195 if (this.isOpen()) { 196 this.webSocket.send(JSON.stringify(data)); 197 return true; 198 } else { 199 return false; 200 } 201 } 202 open() { 203 if (this.isActive()) { 204 logger_default.log(`Attempted to open WebSocket, but existing socket is ${this.getState()}`); 205 return false; 206 } else { 207 const socketProtocols = [...protocols, ...this.consumer.subprotocols || []]; 208 logger_default.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${socketProtocols}`); 209 if (this.webSocket) { 210 this.uninstallEventHandlers(); 211 } 212 this.webSocket = new adapters_default.WebSocket(this.consumer.url, socketProtocols); 213 this.installEventHandlers(); 214 this.monitor.start(); 215 return true; 216 } 217 } 218 close({ allowReconnect } = { allowReconnect: true }) { 219 if (!allowReconnect) { 220 this.monitor.stop(); 221 } 222 if (this.isOpen()) { 223 return this.webSocket.close(); 224 } 225 } 226 reopen() { 227 logger_default.log(`Reopening WebSocket, current state is ${this.getState()}`); 228 if (this.isActive()) { 229 try { 230 return this.close(); 231 } catch (error2) { 232 logger_default.log("Failed to reopen WebSocket", error2); 233 } finally { 234 logger_default.log(`Reopening WebSocket in ${this.constructor.reopenDelay}ms`); 235 setTimeout(this.open, this.constructor.reopenDelay); 236 } 237 } else { 238 return this.open(); 239 } 240 } 241 getProtocol() { 242 if (this.webSocket) { 243 return this.webSocket.protocol; 244 } 245 } 246 isOpen() { 247 return this.isState("open"); 248 } 249 isActive() { 250 return this.isState("open", "connecting"); 251 } 252 triedToReconnect() { 253 return this.monitor.reconnectAttempts > 0; 254 } 255 // Private 256 isProtocolSupported() { 257 return indexOf.call(supportedProtocols, this.getProtocol()) >= 0; 258 } 259 isState(...states) { 260 return indexOf.call(states, this.getState()) >= 0; 261 } 262 getState() { 263 if (this.webSocket) { 264 for (let state in adapters_default.WebSocket) { 265 if (adapters_default.WebSocket[state] === this.webSocket.readyState) { 266 return state.toLowerCase(); 267 } 268 } 269 } 270 return null; 271 } 272 installEventHandlers() { 273 for (let eventName in this.events) { 274 const handler = this.events[eventName].bind(this); 275 this.webSocket[`on${eventName}`] = handler; 276 } 277 } 278 uninstallEventHandlers() { 279 for (let eventName in this.events) { 280 this.webSocket[`on${eventName}`] = function() { 281 }; 282 } 283 } 284 }; 285 Connection.reopenDelay = 500; 286 Connection.prototype.events = { 287 message(event) { 288 if (!this.isProtocolSupported()) { 289 return; 290 } 291 const { identifier, message, reason, reconnect, type } = JSON.parse(event.data); 292 this.monitor.recordMessage(); 293 switch (type) { 294 case message_types.welcome: 295 if (this.triedToReconnect()) { 296 this.reconnectAttempted = true; 297 } 298 this.monitor.recordConnect(); 299 return this.subscriptions.reload(); 300 case message_types.disconnect: 301 logger_default.log(`Disconnecting. Reason: ${reason}`); 302 return this.close({ allowReconnect: reconnect }); 303 case message_types.ping: 304 return null; 305 case message_types.confirmation: 306 this.subscriptions.confirmSubscription(identifier); 307 if (this.reconnectAttempted) { 308 this.reconnectAttempted = false; 309 return this.subscriptions.notify(identifier, "connected", { reconnected: true }); 310 } else { 311 return this.subscriptions.notify(identifier, "connected", { reconnected: false }); 312 } 313 case message_types.rejection: 314 return this.subscriptions.reject(identifier); 315 default: 316 return this.subscriptions.notify(identifier, "received", message); 317 } 318 }, 319 open() { 320 logger_default.log(`WebSocket onopen event, using '${this.getProtocol()}' subprotocol`); 321 this.disconnected = false; 322 if (!this.isProtocolSupported()) { 323 logger_default.log("Protocol is unsupported. Stopping monitor and disconnecting."); 324 return this.close({ allowReconnect: false }); 325 } 326 }, 327 close(event) { 328 logger_default.log("WebSocket onclose event"); 329 if (this.disconnected) { 330 return; 331 } 332 this.disconnected = true; 333 this.monitor.recordDisconnect(); 334 return this.subscriptions.notifyAll("disconnected", { willAttemptReconnect: this.monitor.isRunning() }); 335 }, 336 error() { 337 logger_default.log("WebSocket onerror event"); 338 } 339 }; 340 connection_default = Connection; 341 } 342}); 343
vendor: 1,036 bytes, lines 344-377
344// node_modules/@rails/actioncable/src/subscription.js 345var extend, Subscription; 346var init_subscription = __esm({ 347 "node_modules/@rails/actioncable/src/subscription.js"() { 348 extend = function(object, properties) { 349 if (properties != null) { 350 for (let key in properties) { 351 const value = properties[key]; 352 object[key] = value; 353 } 354 } 355 return object; 356 }; 357 Subscription = class { 358 constructor(consumer2, params = {}, mixin) { 359 this.consumer = consumer2; 360 this.identifier = JSON.stringify(params); 361 extend(this, mixin); 362 } 363 // Perform a channel action with the optional data passed as an attribute 364 perform(action, data = {}) { 365 data.action = action; 366 return this.send(data); 367 } 368 send(data) { 369 return this.consumer.send({ command: "message", identifier: this.identifier, data: JSON.stringify(data) }); 370 } 371 unsubscribe() { 372 return this.consumer.subscriptions.remove(this); 373 } 374 }; 375 } 376}); 377
vendor: 1,802 bytes, lines 378-425
378// node_modules/@rails/actioncable/src/subscription_guarantor.js 379var SubscriptionGuarantor, subscription_guarantor_default; 380var init_subscription_guarantor = __esm({ 381 "node_modules/@rails/actioncable/src/subscription_guarantor.js"() { 382 init_logger(); 383 SubscriptionGuarantor = class { 384 constructor(subscriptions) { 385 this.subscriptions = subscriptions; 386 this.pendingSubscriptions = []; 387 } 388 guarantee(subscription) { 389 if (this.pendingSubscriptions.indexOf(subscription) == -1) { 390 logger_default.log(`SubscriptionGuarantor guaranteeing ${subscription.identifier}`); 391 this.pendingSubscriptions.push(subscription); 392 } else { 393 logger_default.log(`SubscriptionGuarantor already guaranteeing ${subscription.identifier}`); 394 } 395 this.startGuaranteeing(); 396 } 397 forget(subscription) { 398 logger_default.log(`SubscriptionGuarantor forgetting ${subscription.identifier}`); 399 this.pendingSubscriptions = this.pendingSubscriptions.filter((s) => s !== subscription); 400 } 401 startGuaranteeing() { 402 this.stopGuaranteeing(); 403 this.retrySubscribing(); 404 } 405 stopGuaranteeing() { 406 clearTimeout(this.retryTimeout); 407 } 408 retrySubscribing() { 409 this.retryTimeout = setTimeout( 410 () => { 411 if (this.subscriptions && typeof this.subscriptions.subscribe === "function") { 412 this.pendingSubscriptions.map((subscription) => { 413 logger_default.log(`SubscriptionGuarantor resubscribing ${subscription.identifier}`); 414 this.subscriptions.subscribe(subscription); 415 }); 416 } 417 }, 418 500 419 ); 420 } 421 }; 422 subscription_guarantor_default = SubscriptionGuarantor; 423 } 424}); 425
vendor: 2,968 bytes, lines 426-506
426// node_modules/@rails/actioncable/src/subscriptions.js 427var Subscriptions; 428var init_subscriptions = __esm({ 429 "node_modules/@rails/actioncable/src/subscriptions.js"() { 430 init_subscription(); 431 init_subscription_guarantor(); 432 init_logger(); 433 Subscriptions = class { 434 constructor(consumer2) { 435 this.consumer = consumer2; 436 this.guarantor = new subscription_guarantor_default(this); 437 this.subscriptions = []; 438 } 439 create(channelName, mixin) { 440 const channel = channelName; 441 const params = typeof channel === "object" ? channel : { channel }; 442 const subscription = new Subscription(this.consumer, params, mixin); 443 return this.add(subscription); 444 } 445 // Private 446 add(subscription) { 447 this.subscriptions.push(subscription); 448 this.consumer.ensureActiveConnection(); 449 this.notify(subscription, "initialized"); 450 this.subscribe(subscription); 451 return subscription; 452 } 453 remove(subscription) { 454 this.forget(subscription); 455 if (!this.findAll(subscription.identifier).length) { 456 this.sendCommand(subscription, "unsubscribe"); 457 } 458 return subscription; 459 } 460 reject(identifier) { 461 return this.findAll(identifier).map((subscription) => { 462 this.forget(subscription); 463 this.notify(subscription, "rejected"); 464 return subscription; 465 }); 466 } 467 forget(subscription) { 468 this.guarantor.forget(subscription); 469 this.subscriptions = this.subscriptions.filter((s) => s !== subscription); 470 return subscription; 471 } 472 findAll(identifier) { 473 return this.subscriptions.filter((s) => s.identifier === identifier); 474 } 475 reload() { 476 return this.subscriptions.map((subscription) => this.subscribe(subscription)); 477 } 478 notifyAll(callbackName, ...args) { 479 return this.subscriptions.map((subscription) => this.notify(subscription, callbackName, ...args)); 480 } 481 notify(subscription, callbackName, ...args) { 482 let subscriptions; 483 if (typeof subscription === "string") { 484 subscriptions = this.findAll(subscription); 485 } else { 486 subscriptions = [subscription]; 487 } 488 return subscriptions.map((subscription2) => typeof subscription2[callbackName] === "function" ? subscription2[callbackName](...args) : void 0); 489 } 490 subscribe(subscription) { 491 if (this.sendCommand(subscription, "subscribe")) { 492 this.guarantor.guarantee(subscription); 493 } 494 } 495 confirmSubscription(identifier) { 496 logger_default.log(`Subscription confirmed ${identifier}`); 497 this.findAll(identifier).map((subscription) => this.guarantor.forget(subscription)); 498 } 499 sendCommand(subscription, command) { 500 const { identifier } = subscription; 501 return this.consumer.send({ command, identifier }); 502 } 503 }; 504 } 505}); 506
vendor: 1,295 bytes, lines 507-557
507// node_modules/@rails/actioncable/src/consumer.js 508function createWebSocketURL(url) { 509 if (typeof url === "function") { 510 url = url(); 511 } 512 if (url && !/^wss?:/i.test(url)) { 513 const a = document.createElement("a"); 514 a.href = url; 515 a.href = a.href; 516 a.protocol = a.protocol.replace("http", "ws"); 517 return a.href; 518 } else { 519 return url; 520 } 521} 522var Consumer; 523var init_consumer = __esm({ 524 "node_modules/@rails/actioncable/src/consumer.js"() { 525 init_connection(); 526 init_subscriptions(); 527 Consumer = class { 528 constructor(url) { 529 this._url = url; 530 this.subscriptions = new Subscriptions(this); 531 this.connection = new connection_default(this); 532 this.subprotocols = []; 533 } 534 get url() { 535 return createWebSocketURL(this._url); 536 } 537 send(data) { 538 return this.connection.send(data); 539 } 540 connect() { 541 return this.connection.open(); 542 } 543 disconnect() { 544 return this.connection.close({ allowReconnect: false }); 545 } 546 ensureActiveConnection() { 547 if (!this.connection.isActive()) { 548 return this.connection.open(); 549 } 550 } 551 addSubProtocol(subprotocol) { 552 this.subprotocols = [...this.subprotocols, subprotocol]; 553 } 554 }; 555 } 556}); 557
vendor: 1,183 bytes, lines 558-596
558// node_modules/@rails/actioncable/src/index.js 559var src_exports = {}; 560__export(src_exports, { 561 Connection: () => connection_default, 562 ConnectionMonitor: () => connection_monitor_default, 563 Consumer: () => Consumer, 564 INTERNAL: () => internal_default, 565 Subscription: () => Subscription, 566 SubscriptionGuarantor: () => subscription_guarantor_default, 567 Subscriptions: () => Subscriptions, 568 adapters: () => adapters_default, 569 createConsumer: () => createConsumer, 570 createWebSocketURL: () => createWebSocketURL, 571 getConfig: () => getConfig, 572 logger: () => logger_default 573}); 574function createConsumer(url = getConfig("url") || internal_default.default_mount_path) { 575 return new Consumer(url); 576} 577function getConfig(name) { 578 const element = document.head.querySelector(`meta[name='action-cable-${name}']`); 579 if (element) { 580 return element.getAttribute("content"); 581 } 582} 583var init_src = __esm({ 584 "node_modules/@rails/actioncable/src/index.js"() { 585 init_connection(); 586 init_connection_monitor(); 587 init_consumer(); 588 init_internal(); 589 init_subscription(); 590 init_subscriptions(); 591 init_subscription_guarantor(); 592 init_adapters(); 593 init_logger(); 594 } 595}); 596
vendor: 172,739 bytes, lines 597-5962
597// node_modules/@hotwired/turbo/dist/turbo.es2017-esm.js 598var turbo_es2017_esm_exports = {}; 599__export(turbo_es2017_esm_exports, { 600 FetchEnctype: () => FetchEnctype, 601 FetchMethod: () => FetchMethod, 602 FetchRequest: () => FetchRequest, 603 FetchResponse: () => FetchResponse, 604 FrameElement: () => FrameElement, 605 FrameLoadingStyle: () => FrameLoadingStyle, 606 FrameRenderer: () => FrameRenderer, 607 PageRenderer: () => PageRenderer, 608 PageSnapshot: () => PageSnapshot, 609 StreamActions: () => StreamActions, 610 StreamElement: () => StreamElement, 611 StreamSourceElement: () => StreamSourceElement, 612 cache: () => cache, 613 clearCache: () => clearCache, 614 config: () => config, 615 connectStreamSource: () => connectStreamSource, 616 disconnectStreamSource: () => disconnectStreamSource, 617 fetch: () => fetchWithTurboHeaders, 618 fetchEnctypeFromString: () => fetchEnctypeFromString, 619 fetchMethodFromString: () => fetchMethodFromString, 620 isSafe: () => isSafe, 621 navigator: () => navigator$1, 622 registerAdapter: () => registerAdapter, 623 renderStreamMessage: () => renderStreamMessage, 624 session: () => session, 625 setConfirmMethod: () => setConfirmMethod, 626 setFormMode: () => setFormMode, 627 setProgressBarDelay: () => setProgressBarDelay, 628 start: () => start, 629 visit: () => visit 630}); 631(function(prototype) { 632 if (typeof prototype.requestSubmit == "function") return; 633 prototype.requestSubmit = function(submitter2) { 634 if (submitter2) { 635 validateSubmitter(submitter2, this); 636 submitter2.click(); 637 } else { 638 submitter2 = document.createElement("input"); 639 submitter2.type = "submit"; 640 submitter2.hidden = true; 641 this.appendChild(submitter2); 642 submitter2.click(); 643 this.removeChild(submitter2); 644 } 645 }; 646 function validateSubmitter(submitter2, form) { 647 submitter2 instanceof HTMLElement || raise(TypeError, "parameter 1 is not of type 'HTMLElement'"); 648 submitter2.type == "submit" || raise(TypeError, "The specified element is not a submit button"); 649 submitter2.form == form || raise(DOMException, "The specified element is not owned by this form element", "NotFoundError"); 650 } 651 function raise(errorConstructor, message, name) { 652 throw new errorConstructor("Failed to execute 'requestSubmit' on 'HTMLFormElement': " + message + ".", name); 653 } 654})(HTMLFormElement.prototype); 655var submittersByForm = /* @__PURE__ */ new WeakMap(); 656function findSubmitterFromClickTarget(target) { 657 const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null; 658 const candidate = element ? element.closest("input, button") : null; 659 return candidate?.type == "submit" ? candidate : null; 660} 661function clickCaptured(event) { 662 const submitter2 = findSubmitterFromClickTarget(event.target); 663 if (submitter2 && submitter2.form) { 664 submittersByForm.set(submitter2.form, submitter2); 665 } 666} 667(function() { 668 if ("submitter" in Event.prototype) return; 669 let prototype = window.Event.prototype; 670 if ("SubmitEvent" in window) { 671 const prototypeOfSubmitEvent = window.SubmitEvent.prototype; 672 if (/Apple Computer/.test(navigator.vendor) && !("submitter" in prototypeOfSubmitEvent)) { 673 prototype = prototypeOfSubmitEvent; 674 } else { 675 return; 676 } 677 } 678 addEventListener("click", clickCaptured, true); 679 Object.defineProperty(prototype, "submitter", { 680 get() { 681 if (this.type == "submit" && this.target instanceof HTMLFormElement) { 682 return submittersByForm.get(this.target); 683 } 684 } 685 }); 686})(); 687var FrameLoadingStyle = { 688 eager: "eager", 689 lazy: "lazy" 690}; 691var FrameElement = class _FrameElement extends HTMLElement { 692 static delegateConstructor = void 0; 693 loaded = Promise.resolve(); 694 static get observedAttributes() { 695 return ["disabled", "loading", "src"]; 696 } 697 constructor() { 698 super(); 699 this.delegate = new _FrameElement.delegateConstructor(this); 700 } 701 connectedCallback() { 702 this.delegate.connect(); 703 } 704 disconnectedCallback() { 705 this.delegate.disconnect(); 706 } 707 reload() { 708 return this.delegate.sourceURLReloaded(); 709 } 710 attributeChangedCallback(name) { 711 if (name == "loading") { 712 this.delegate.loadingStyleChanged(); 713 } else if (name == "src") { 714 this.delegate.sourceURLChanged(); 715 } else if (name == "disabled") { 716 this.delegate.disabledChanged(); 717 } 718 } 719 /** 720 * Gets the URL to lazily load source HTML from 721 */ 722 get src() { 723 return this.getAttribute("src"); 724 } 725 /** 726 * Sets the URL to lazily load source HTML from 727 */ 728 set src(value) { 729 if (value) { 730 this.setAttribute("src", value); 731 } else { 732 this.removeAttribute("src"); 733 } 734 } 735 /** 736 * Gets the refresh mode for the frame. 737 */ 738 get refresh() { 739 return this.getAttribute("refresh"); 740 } 741 /** 742 * Sets the refresh mode for the frame. 743 */ 744 set refresh(value) { 745 if (value) { 746 this.setAttribute("refresh", value); 747 } else { 748 this.removeAttribute("refresh"); 749 } 750 } 751 get shouldReloadWithMorph() { 752 return this.src && this.refresh === "morph"; 753 } 754 /** 755 * Determines if the element is loading 756 */ 757 get loading() { 758 return frameLoadingStyleFromString(this.getAttribute("loading") || ""); 759 } 760 /** 761 * Sets the value of if the element is loading 762 */ 763 set loading(value) { 764 if (value) { 765 this.setAttribute("loading", value); 766 } else { 767 this.removeAttribute("loading"); 768 } 769 } 770 /** 771 * Gets the disabled state of the frame. 772 * 773 * If disabled, no requests will be intercepted by the frame. 774 */ 775 get disabled() { 776 return this.hasAttribute("disabled"); 777 } 778 /** 779 * Sets the disabled state of the frame. 780 * 781 * If disabled, no requests will be intercepted by the frame. 782 */ 783 set disabled(value) { 784 if (value) { 785 this.setAttribute("disabled", ""); 786 } else { 787 this.removeAttribute("disabled"); 788 } 789 } 790 /** 791 * Gets the autoscroll state of the frame. 792 * 793 * If true, the frame will be scrolled into view automatically on update. 794 */ 795 get autoscroll() { 796 return this.hasAttribute("autoscroll"); 797 } 798 /** 799 * Sets the autoscroll state of the frame. 800 * 801 * If true, the frame will be scrolled into view automatically on update. 802 */ 803 set autoscroll(value) { 804 if (value) { 805 this.setAttribute("autoscroll", ""); 806 } else { 807 this.removeAttribute("autoscroll"); 808 } 809 } 810 /** 811 * Determines if the element has finished loading 812 */ 813 get complete() { 814 return !this.delegate.isLoading; 815 } 816 /** 817 * Gets the active state of the frame. 818 * 819 * If inactive, source changes will not be observed. 820 */ 821 get isActive() { 822 return this.ownerDocument === document && !this.isPreview; 823 } 824 /** 825 * Sets the active state of the frame. 826 * 827 * If inactive, source changes will not be observed. 828 */ 829 get isPreview() { 830 return this.ownerDocument?.documentElement?.hasAttribute("data-turbo-preview"); 831 } 832}; 833function frameLoadingStyleFromString(style) { 834 switch (style.toLowerCase()) { 835 case "lazy": 836 return FrameLoadingStyle.lazy; 837 default: 838 return FrameLoadingStyle.eager; 839 } 840} 841var drive = { 842 enabled: true, 843 progressBarDelay: 500, 844 unvisitableExtensions: /* @__PURE__ */ new Set( 845 [ 846 ".7z", 847 ".aac", 848 ".apk", 849 ".avi", 850 ".bmp", 851 ".bz2", 852 ".css", 853 ".csv", 854 ".deb", 855 ".dmg", 856 ".doc", 857 ".docx", 858 ".exe", 859 ".gif", 860 ".gz", 861 ".heic", 862 ".heif", 863 ".ico", 864 ".iso", 865 ".jpeg", 866 ".jpg", 867 ".js", 868 ".json", 869 ".m4a", 870 ".mkv", 871 ".mov", 872 ".mp3", 873 ".mp4", 874 ".mpeg", 875 ".mpg", 876 ".msi", 877 ".ogg", 878 ".ogv", 879 ".pdf", 880 ".pkg", 881 ".png", 882 ".ppt", 883 ".pptx", 884 ".rar", 885 ".rtf", 886 ".svg", 887 ".tar", 888 ".tif", 889 ".tiff", 890 ".txt", 891 ".wav", 892 ".webm", 893 ".webp", 894 ".wma", 895 ".wmv", 896 ".xls", 897 ".xlsx", 898 ".xml", 899 ".zip" 900 ] 901 ) 902}; 903function activateScriptElement(element) { 904 if (element.getAttribute("data-turbo-eval") == "false") { 905 return element; 906 } else { 907 const createdScriptElement = document.createElement("script"); 908 const cspNonce = getCspNonce(); 909 if (cspNonce) { 910 createdScriptElement.nonce = cspNonce; 911 } 912 createdScriptElement.textContent = element.textContent; 913 createdScriptElement.async = false; 914 copyElementAttributes(createdScriptElement, element); 915 return createdScriptElement; 916 } 917} 918function copyElementAttributes(destinationElement, sourceElement) { 919 for (const { name, value } of sourceElement.attributes) { 920 destinationElement.setAttribute(name, value); 921 } 922} 923function createDocumentFragment(html) { 924 const template = document.createElement("template"); 925 template.innerHTML = html; 926 return template.content; 927} 928function dispatch(eventName, { target, cancelable, detail } = {}) { 929 const event = new CustomEvent(eventName, { 930 cancelable, 931 bubbles: true, 932 composed: true, 933 detail 934 }); 935 if (target && target.isConnected) { 936 target.dispatchEvent(event); 937 } else { 938 document.documentElement.dispatchEvent(event); 939 } 940 return event; 941} 942function cancelEvent(event) { 943 event.preventDefault(); 944 event.stopImmediatePropagation(); 945} 946function nextRepaint() { 947 if (document.visibilityState === "hidden") { 948 return nextEventLoopTick(); 949 } else { 950 return nextAnimationFrame(); 951 } 952} 953function nextAnimationFrame() { 954 return new Promise((resolve) => requestAnimationFrame(() => resolve())); 955} 956function nextEventLoopTick() { 957 return new Promise((resolve) => setTimeout(() => resolve(), 0)); 958} 959function nextMicrotask() { 960 return Promise.resolve(); 961} 962function parseHTMLDocument(html = "") { 963 return new DOMParser().parseFromString(html, "text/html"); 964} 965function unindent(strings, ...values) { 966 const lines = interpolate(strings, values).replace(/^\n/, "").split("\n"); 967 const match = lines[0].match(/^\s+/); 968 const indent = match ? match[0].length : 0; 969 return lines.map((line) => line.slice(indent)).join("\n"); 970} 971function interpolate(strings, values) { 972 return strings.reduce((result, string, i) => { 973 const value = values[i] == void 0 ? "" : values[i]; 974 return result + string + value; 975 }, ""); 976} 977function uuid() { 978 return Array.from({ length: 36 }).map((_, i) => { 979 if (i == 8 || i == 13 || i == 18 || i == 23) { 980 return "-"; 981 } else if (i == 14) { 982 return "4"; 983 } else if (i == 19) { 984 return (Math.floor(Math.random() * 4) + 8).toString(16); 985 } else { 986 return Math.floor(Math.random() * 15).toString(16); 987 } 988 }).join(""); 989} 990function getAttribute(attributeName, ...elements) { 991 for (const value of elements.map((element) => element?.getAttribute(attributeName))) { 992 if (typeof value == "string") return value; 993 } 994 return null; 995} 996function hasAttribute(attributeName, ...elements) { 997 return elements.some((element) => element && element.hasAttribute(attributeName)); 998} 999function markAsBusy(...elements) { 1000 for (const element of elements) { 1001 if (element.localName == "turbo-frame") { 1002 element.setAttribute("busy", ""); 1003 } 1004 element.setAttribute("aria-busy", "true"); 1005 } 1006} 1007function clearBusyState(...elements) { 1008 for (const element of elements) { 1009 if (element.localName == "turbo-frame") { 1010 element.removeAttribute("busy"); 1011 } 1012 element.removeAttribute("aria-busy"); 1013 } 1014} 1015function waitForLoad(element, timeoutInMilliseconds = 2e3) { 1016 return new Promise((resolve) => { 1017 const onComplete = () => { 1018 element.removeEventListener("error", onComplete); 1019 element.removeEventListener("load", onComplete); 1020 resolve(); 1021 }; 1022 element.addEventListener("load", onComplete, { once: true }); 1023 element.addEventListener("error", onComplete, { once: true }); 1024 setTimeout(resolve, timeoutInMilliseconds); 1025 }); 1026} 1027function getHistoryMethodForAction(action) { 1028 switch (action) { 1029 case "replace": 1030 return history.replaceState; 1031 case "advance": 1032 case "restore": 1033 return history.pushState; 1034 } 1035} 1036function isAction(action) { 1037 return action == "advance" || action == "replace" || action == "restore"; 1038} 1039function getVisitAction(...elements) { 1040 const action = getAttribute("data-turbo-action", ...elements); 1041 return isAction(action) ? action : null; 1042} 1043function getMetaElement(name) { 1044 return document.querySelector(`meta[name="${name}"]`); 1045} 1046function getMetaContent(name) { 1047 const element = getMetaElement(name); 1048 return element && element.content; 1049} 1050function getCspNonce() { 1051 const element = getMetaElement("csp-nonce"); 1052 if (element) { 1053 const { nonce, content } = element; 1054 return nonce == "" ? content : nonce; 1055 } 1056} 1057function setMetaContent(name, content) { 1058 let element = getMetaElement(name); 1059 if (!element) { 1060 element = document.createElement("meta"); 1061 element.setAttribute("name", name); 1062 document.head.appendChild(element); 1063 } 1064 element.setAttribute("content", content); 1065 return element; 1066} 1067function findClosestRecursively(element, selector) { 1068 if (element instanceof Element) { 1069 return element.closest(selector) || findClosestRecursively(element.assignedSlot || element.getRootNode()?.host, selector); 1070 } 1071} 1072function elementIsFocusable(element) { 1073 const inertDisabledOrHidden = "[inert], :disabled, [hidden], details:not([open]), dialog:not([open])"; 1074 return !!element && element.closest(inertDisabledOrHidden) == null && typeof element.focus == "function"; 1075} 1076function queryAutofocusableElement(elementOrDocumentFragment) { 1077 return Array.from(elementOrDocumentFragment.querySelectorAll("[autofocus]")).find(elementIsFocusable); 1078} 1079async function around(callback, reader) { 1080 const before = reader(); 1081 callback(); 1082 await nextAnimationFrame(); 1083 const after = reader(); 1084 return [before, after]; 1085} 1086function doesNotTargetIFrame(name) { 1087 if (name === "_blank") { 1088 return false; 1089 } else if (name) { 1090 for (const element of document.getElementsByName(name)) { 1091 if (element instanceof HTMLIFrameElement) return false; 1092 } 1093 return true; 1094 } else { 1095 return true; 1096 } 1097} 1098function findLinkFromClickTarget(target) { 1099 return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])"); 1100} 1101function getLocationForLink(link) { 1102 return expandURL(link.getAttribute("href") || ""); 1103} 1104function debounce(fn, delay) { 1105 let timeoutId = null; 1106 return (...args) => { 1107 const callback = () => fn.apply(this, args); 1108 clearTimeout(timeoutId); 1109 timeoutId = setTimeout(callback, delay); 1110 }; 1111} 1112var submitter = { 1113 "aria-disabled": { 1114 beforeSubmit: (submitter2) => { 1115 submitter2.setAttribute("aria-disabled", "true"); 1116 submitter2.addEventListener("click", cancelEvent); 1117 }, 1118 afterSubmit: (submitter2) => { 1119 submitter2.removeAttribute("aria-disabled"); 1120 submitter2.removeEventListener("click", cancelEvent); 1121 } 1122 }, 1123 "disabled": { 1124 beforeSubmit: (submitter2) => submitter2.disabled = true, 1125 afterSubmit: (submitter2) => submitter2.disabled = false 1126 } 1127}; 1128var Config = class { 1129 #submitter = null; 1130 constructor(config2) { 1131 Object.assign(this, config2); 1132 } 1133 get submitter() { 1134 return this.#submitter; 1135 } 1136 set submitter(value) { 1137 this.#submitter = submitter[value] || value; 1138 } 1139}; 1140var forms = new Config({ 1141 mode: "on", 1142 submitter: "disabled" 1143}); 1144var config = { 1145 drive, 1146 forms 1147}; 1148function expandURL(locatable) { 1149 return new URL(locatable.toString(), document.baseURI); 1150} 1151function getAnchor(url) { 1152 let anchorMatch; 1153 if (url.hash) { 1154 return url.hash.slice(1); 1155 } else if (anchorMatch = url.href.match(/#(.*)$/)) { 1156 return anchorMatch[1]; 1157 } 1158} 1159function getAction$1(form, submitter2) { 1160 const action = submitter2?.getAttribute("formaction") || form.getAttribute("action") || form.action; 1161 return expandURL(action); 1162} 1163function getExtension(url) { 1164 return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || ""; 1165} 1166function isPrefixedBy(baseURL, url) { 1167 const prefix = getPrefix(url); 1168 return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix); 1169} 1170function locationIsVisitable(location2, rootLocation) { 1171 return isPrefixedBy(location2, rootLocation) && !config.drive.unvisitableExtensions.has(getExtension(location2)); 1172} 1173function getRequestURL(url) { 1174 const anchor = getAnchor(url); 1175 return anchor != null ? url.href.slice(0, -(anchor.length + 1)) : url.href; 1176} 1177function toCacheKey(url) { 1178 return getRequestURL(url); 1179} 1180function urlsAreEqual(left, right) { 1181 return expandURL(left).href == expandURL(right).href; 1182} 1183function getPathComponents(url) { 1184 return url.pathname.split("/").slice(1); 1185} 1186function getLastPathComponent(url) { 1187 return getPathComponents(url).slice(-1)[0]; 1188} 1189function getPrefix(url) { 1190 return addTrailingSlash(url.origin + url.pathname); 1191} 1192function addTrailingSlash(value) { 1193 return value.endsWith("/") ? value : value + "/"; 1194} 1195var FetchResponse = class { 1196 constructor(response) { 1197 this.response = response; 1198 } 1199 get succeeded() { 1200 return this.response.ok; 1201 } 1202 get failed() { 1203 return !this.succeeded; 1204 } 1205 get clientError() { 1206 return this.statusCode >= 400 && this.statusCode <= 499; 1207 } 1208 get serverError() { 1209 return this.statusCode >= 500 && this.statusCode <= 599; 1210 } 1211 get redirected() { 1212 return this.response.redirected; 1213 } 1214 get location() { 1215 return expandURL(this.response.url); 1216 } 1217 get isHTML() { 1218 return this.contentType && this.contentType.match(/^(?:text\/([^\s;,]+\b)?html|application\/xhtml\+xml)\b/); 1219 } 1220 get statusCode() { 1221 return this.response.status; 1222 } 1223 get contentType() { 1224 return this.header("Content-Type"); 1225 } 1226 get responseText() { 1227 return this.response.clone().text(); 1228 } 1229 get responseHTML() { 1230 if (this.isHTML) { 1231 return this.response.clone().text(); 1232 } else { 1233 return Promise.resolve(void 0); 1234 } 1235 } 1236 header(name) { 1237 return this.response.headers.get(name); 1238 } 1239}; 1240var LimitedSet = class extends Set { 1241 constructor(maxSize) { 1242 super(); 1243 this.maxSize = maxSize; 1244 } 1245 add(value) { 1246 if (this.size >= this.maxSize) { 1247 const iterator = this.values(); 1248 const oldestValue = iterator.next().value; 1249 this.delete(oldestValue); 1250 } 1251 super.add(value); 1252 } 1253}; 1254var recentRequests = new LimitedSet(20); 1255var nativeFetch = window.fetch; 1256function fetchWithTurboHeaders(url, options = {}) { 1257 const modifiedHeaders = new Headers(options.headers || {}); 1258 const requestUID = uuid(); 1259 recentRequests.add(requestUID); 1260 modifiedHeaders.append("X-Turbo-Request-Id", requestUID); 1261 return nativeFetch(url, { 1262 ...options, 1263 headers: modifiedHeaders 1264 }); 1265} 1266function fetchMethodFromString(method) { 1267 switch (method.toLowerCase()) { 1268 case "get": 1269 return FetchMethod.get; 1270 case "post": 1271 return FetchMethod.post; 1272 case "put": 1273 return FetchMethod.put; 1274 case "patch": 1275 return FetchMethod.patch; 1276 case "delete": 1277 return FetchMethod.delete; 1278 } 1279} 1280var FetchMethod = { 1281 get: "get", 1282 post: "post", 1283 put: "put", 1284 patch: "patch", 1285 delete: "delete" 1286}; 1287function fetchEnctypeFromString(encoding) { 1288 switch (encoding.toLowerCase()) { 1289 case FetchEnctype.multipart: 1290 return FetchEnctype.multipart; 1291 case FetchEnctype.plain: 1292 return FetchEnctype.plain; 1293 default: 1294 return FetchEnctype.urlEncoded; 1295 } 1296} 1297var FetchEnctype = { 1298 urlEncoded: "application/x-www-form-urlencoded", 1299 multipart: "multipart/form-data", 1300 plain: "text/plain" 1301}; 1302var FetchRequest = class { 1303 abortController = new AbortController(); 1304 #resolveRequestPromise = (_value) => { 1305 }; 1306 constructor(delegate, method, location2, requestBody = new URLSearchParams(), target = null, enctype = FetchEnctype.urlEncoded) { 1307 const [url, body] = buildResourceAndBody(expandURL(location2), method, requestBody, enctype); 1308 this.delegate = delegate; 1309 this.url = url; 1310 this.target = target; 1311 this.fetchOptions = { 1312 credentials: "same-origin", 1313 redirect: "follow", 1314 method: method.toUpperCase(), 1315 headers: { ...this.defaultHeaders }, 1316 body, 1317 signal: this.abortSignal, 1318 referrer: this.delegate.referrer?.href 1319 }; 1320 this.enctype = enctype; 1321 } 1322 get method() { 1323 return this.fetchOptions.method; 1324 } 1325 set method(value) { 1326 const fetchBody = this.isSafe ? this.url.searchParams : this.fetchOptions.body || new FormData(); 1327 const fetchMethod = fetchMethodFromString(value) || FetchMethod.get; 1328 this.url.search = ""; 1329 const [url, body] = buildResourceAndBody(this.url, fetchMethod, fetchBody, this.enctype); 1330 this.url = url; 1331 this.fetchOptions.body = body; 1332 this.fetchOptions.method = fetchMethod.toUpperCase(); 1333 } 1334 get headers() { 1335 return this.fetchOptions.headers; 1336 } 1337 set headers(value) { 1338 this.fetchOptions.headers = value; 1339 } 1340 get body() { 1341 if (this.isSafe) { 1342 return this.url.searchParams; 1343 } else { 1344 return this.fetchOptions.body; 1345 } 1346 } 1347 set body(value) { 1348 this.fetchOptions.body = value; 1349 } 1350 get location() { 1351 return this.url; 1352 } 1353 get params() { 1354 return this.url.searchParams; 1355 } 1356 get entries() { 1357 return this.body ? Array.from(this.body.entries()) : []; 1358 } 1359 cancel() { 1360 this.abortController.abort(); 1361 } 1362 async perform() { 1363 const { fetchOptions } = this; 1364 this.delegate.prepareRequest(this); 1365 const event = await this.#allowRequestToBeIntercepted(fetchOptions); 1366 try { 1367 this.delegate.requestStarted(this); 1368 if (event.detail.fetchRequest) { 1369 this.response = event.detail.fetchRequest.response; 1370 } else { 1371 this.response = fetchWithTurboHeaders(this.url.href, fetchOptions); 1372 } 1373 const response = await this.response; 1374 return await this.receive(response); 1375 } catch (error2) { 1376 if (error2.name !== "AbortError") { 1377 if (this.#willDelegateErrorHandling(error2)) { 1378 this.delegate.requestErrored(this, error2); 1379 } 1380 throw error2; 1381 } 1382 } finally { 1383 this.delegate.requestFinished(this); 1384 } 1385 } 1386 async receive(response) { 1387 const fetchResponse = new FetchResponse(response); 1388 const event = dispatch("turbo:before-fetch-response", { 1389 cancelable: true, 1390 detail: { fetchResponse }, 1391 target: this.target 1392 }); 1393 if (event.defaultPrevented) { 1394 this.delegate.requestPreventedHandlingResponse(this, fetchResponse); 1395 } else if (fetchResponse.succeeded) { 1396 this.delegate.requestSucceededWithResponse(this, fetchResponse); 1397 } else { 1398 this.delegate.requestFailedWithResponse(this, fetchResponse); 1399 } 1400 return fetchResponse; 1401 } 1402 get defaultHeaders() { 1403 return { 1404 Accept: "text/html, application/xhtml+xml" 1405 }; 1406 } 1407 get isSafe() { 1408 return isSafe(this.method); 1409 } 1410 get abortSignal() { 1411 return this.abortController.signal; 1412 } 1413 acceptResponseType(mimeType) { 1414 this.headers["Accept"] = [mimeType, this.headers["Accept"]].join(", "); 1415 } 1416 async #allowRequestToBeIntercepted(fetchOptions) { 1417 const requestInterception = new Promise((resolve) => this.#resolveRequestPromise = resolve); 1418 const event = dispatch("turbo:before-fetch-request", { 1419 cancelable: true, 1420 detail: { 1421 fetchOptions, 1422 url: this.url, 1423 resume: this.#resolveRequestPromise 1424 }, 1425 target: this.target 1426 }); 1427 this.url = event.detail.url; 1428 if (event.defaultPrevented) await requestInterception; 1429 return event; 1430 } 1431 #willDelegateErrorHandling(error2) { 1432 const event = dispatch("turbo:fetch-request-error", { 1433 target: this.target, 1434 cancelable: true, 1435 detail: { request: this, error: error2 } 1436 }); 1437 return !event.defaultPrevented; 1438 } 1439}; 1440function isSafe(fetchMethod) { 1441 return fetchMethodFromString(fetchMethod) == FetchMethod.get; 1442} 1443function buildResourceAndBody(resource, method, requestBody, enctype) { 1444 const searchParams = Array.from(requestBody).length > 0 ? new URLSearchParams(entriesExcludingFiles(requestBody)) : resource.searchParams; 1445 if (isSafe(method)) { 1446 return [mergeIntoURLSearchParams(resource, searchParams), null]; 1447 } else if (enctype == FetchEnctype.urlEncoded) { 1448 return [resource, searchParams]; 1449 } else { 1450 return [resource, requestBody]; 1451 } 1452} 1453function entriesExcludingFiles(requestBody) { 1454 const entries = []; 1455 for (const [name, value] of requestBody) { 1456 if (value instanceof File) continue; 1457 else entries.push([name, value]); 1458 } 1459 return entries; 1460} 1461function mergeIntoURLSearchParams(url, requestBody) { 1462 const searchParams = new URLSearchParams(entriesExcludingFiles(requestBody)); 1463 url.search = searchParams.toString(); 1464 return url; 1465} 1466var AppearanceObserver = class { 1467 started = false; 1468 constructor(delegate, element) { 1469 this.delegate = delegate; 1470 this.element = element; 1471 this.intersectionObserver = new IntersectionObserver(this.intersect); 1472 } 1473 start() { 1474 if (!this.started) { 1475 this.started = true; 1476 this.intersectionObserver.observe(this.element); 1477 } 1478 } 1479 stop() { 1480 if (this.started) { 1481 this.started = false; 1482 this.intersectionObserver.unobserve(this.element); 1483 } 1484 } 1485 intersect = (entries) => { 1486 const lastEntry = entries.slice(-1)[0]; 1487 if (lastEntry?.isIntersecting) { 1488 this.delegate.elementAppearedInViewport(this.element); 1489 } 1490 }; 1491}; 1492var StreamMessage = class { 1493 static contentType = "text/vnd.turbo-stream.html"; 1494 static wrap(message) { 1495 if (typeof message == "string") { 1496 return new this(createDocumentFragment(message)); 1497 } else { 1498 return message; 1499 } 1500 } 1501 constructor(fragment) { 1502 this.fragment = importStreamElements(fragment); 1503 } 1504}; 1505function importStreamElements(fragment) { 1506 for (const element of fragment.querySelectorAll("turbo-stream")) { 1507 const streamElement = document.importNode(element, true); 1508 for (const inertScriptElement of streamElement.templateElement.content.querySelectorAll("script")) { 1509 inertScriptElement.replaceWith(activateScriptElement(inertScriptElement)); 1510 } 1511 element.replaceWith(streamElement); 1512 } 1513 return fragment; 1514} 1515var PREFETCH_DELAY = 100; 1516var PrefetchCache = class { 1517 #prefetchTimeout = null; 1518 #prefetched = null; 1519 get(url) { 1520 if (this.#prefetched && this.#prefetched.url === url && this.#prefetched.expire > Date.now()) { 1521 return this.#prefetched.request; 1522 } 1523 } 1524 setLater(url, request, ttl) { 1525 this.clear(); 1526 this.#prefetchTimeout = setTimeout(() => { 1527 request.perform(); 1528 this.set(url, request, ttl); 1529 this.#prefetchTimeout = null; 1530 }, PREFETCH_DELAY); 1531 } 1532 set(url, request, ttl) { 1533 this.#prefetched = { url, request, expire: new Date((/* @__PURE__ */ new Date()).getTime() + ttl) }; 1534 } 1535 clear() { 1536 if (this.#prefetchTimeout) clearTimeout(this.#prefetchTimeout); 1537 this.#prefetched = null; 1538 } 1539}; 1540var cacheTtl = 10 * 1e3; 1541var prefetchCache = new PrefetchCache(); 1542var FormSubmissionState = { 1543 initialized: "initialized", 1544 requesting: "requesting", 1545 waiting: "waiting", 1546 receiving: "receiving", 1547 stopping: "stopping", 1548 stopped: "stopped" 1549}; 1550var FormSubmission = class _FormSubmission { 1551 state = FormSubmissionState.initialized; 1552 static confirmMethod(message) { 1553 return Promise.resolve(confirm(message)); 1554 } 1555 constructor(delegate, formElement, submitter2, mustRedirect = false) { 1556 const method = getMethod(formElement, submitter2); 1557 const action = getAction(getFormAction(formElement, submitter2), method); 1558 const body = buildFormData(formElement, submitter2); 1559 const enctype = getEnctype(formElement, submitter2); 1560 this.delegate = delegate; 1561 this.formElement = formElement; 1562 this.submitter = submitter2; 1563 this.fetchRequest = new FetchRequest(this, method, action, body, formElement, enctype); 1564 this.mustRedirect = mustRedirect; 1565 } 1566 get method() { 1567 return this.fetchRequest.method; 1568 } 1569 set method(value) { 1570 this.fetchRequest.method = value; 1571 } 1572 get action() { 1573 return this.fetchRequest.url.toString(); 1574 } 1575 set action(value) { 1576 this.fetchRequest.url = expandURL(value); 1577 } 1578 get body() { 1579 return this.fetchRequest.body; 1580 } 1581 get enctype() { 1582 return this.fetchRequest.enctype; 1583 } 1584 get isSafe() { 1585 return this.fetchRequest.isSafe; 1586 } 1587 get location() { 1588 return this.fetchRequest.url; 1589 } 1590 // The submission process 1591 async start() { 1592 const { initialized, requesting } = FormSubmissionState; 1593 const confirmationMessage = getAttribute("data-turbo-confirm", this.submitter, this.formElement); 1594 if (typeof confirmationMessage === "string") { 1595 const confirmMethod = typeof config.forms.confirm === "function" ? config.forms.confirm : _FormSubmission.confirmMethod; 1596 const answer = await confirmMethod(confirmationMessage, this.formElement, this.submitter); 1597 if (!answer) { 1598 return; 1599 } 1600 } 1601 if (this.state == initialized) { 1602 this.state = requesting; 1603 return this.fetchRequest.perform(); 1604 } 1605 } 1606 stop() { 1607 const { stopping, stopped } = FormSubmissionState; 1608 if (this.state != stopping && this.state != stopped) { 1609 this.state = stopping; 1610 this.fetchRequest.cancel(); 1611 return true; 1612 } 1613 } 1614 // Fetch request delegate 1615 prepareRequest(request) { 1616 if (!request.isSafe) { 1617 const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token"); 1618 if (token) { 1619 request.headers["X-CSRF-Token"] = token; 1620 } 1621 } 1622 if (this.requestAcceptsTurboStreamResponse(request)) { 1623 request.acceptResponseType(StreamMessage.contentType); 1624 } 1625 } 1626 requestStarted(_request) { 1627 this.state = FormSubmissionState.waiting; 1628 if (this.submitter) config.forms.submitter.beforeSubmit(this.submitter); 1629 this.setSubmitsWith(); 1630 markAsBusy(this.formElement); 1631 dispatch("turbo:submit-start", { 1632 target: this.formElement, 1633 detail: { formSubmission: this } 1634 }); 1635 this.delegate.formSubmissionStarted(this); 1636 } 1637 requestPreventedHandlingResponse(request, response) { 1638 prefetchCache.clear(); 1639 this.result = { success: response.succeeded, fetchResponse: response }; 1640 } 1641 requestSucceededWithResponse(request, response) { 1642 if (response.clientError || response.serverError) { 1643 this.delegate.formSubmissionFailedWithResponse(this, response); 1644 return; 1645 } 1646 prefetchCache.clear(); 1647 if (this.requestMustRedirect(request) && responseSucceededWithoutRedirect(response)) { 1648 const error2 = new Error("Form responses must redirect to another location"); 1649 this.delegate.formSubmissionErrored(this, error2); 1650 } else { 1651 this.state = FormSubmissionState.receiving; 1652 this.result = { success: true, fetchResponse: response }; 1653 this.delegate.formSubmissionSucceededWithResponse(this, response); 1654 } 1655 } 1656 requestFailedWithResponse(request, response) { 1657 this.result = { success: false, fetchResponse: response }; 1658 this.delegate.formSubmissionFailedWithResponse(this, response); 1659 } 1660 requestErrored(request, error2) { 1661 this.result = { success: false, error: error2 }; 1662 this.delegate.formSubmissionErrored(this, error2); 1663 } 1664 requestFinished(_request) { 1665 this.state = FormSubmissionState.stopped; 1666 if (this.submitter) config.forms.submitter.afterSubmit(this.submitter); 1667 this.resetSubmitterText(); 1668 clearBusyState(this.formElement); 1669 dispatch("turbo:submit-end", { 1670 target: this.formElement, 1671 detail: { formSubmission: this, ...this.result } 1672 }); 1673 this.delegate.formSubmissionFinished(this); 1674 } 1675 // Private 1676 setSubmitsWith() { 1677 if (!this.submitter || !this.submitsWith) return; 1678 if (this.submitter.matches("button")) { 1679 this.originalSubmitText = this.submitter.innerHTML; 1680 this.submitter.innerHTML = this.submitsWith; 1681 } else if (this.submitter.matches("input")) { 1682 const input = this.submitter; 1683 this.originalSubmitText = input.value; 1684 input.value = this.submitsWith; 1685 } 1686 } 1687 resetSubmitterText() { 1688 if (!this.submitter || !this.originalSubmitText) return; 1689 if (this.submitter.matches("button")) { 1690 this.submitter.innerHTML = this.originalSubmitText; 1691 } else if (this.submitter.matches("input")) { 1692 const input = this.submitter; 1693 input.value = this.originalSubmitText; 1694 } 1695 } 1696 requestMustRedirect(request) { 1697 return !request.isSafe && this.mustRedirect; 1698 } 1699 requestAcceptsTurboStreamResponse(request) { 1700 return !request.isSafe || hasAttribute("data-turbo-stream", this.submitter, this.formElement); 1701 } 1702 get submitsWith() { 1703 return this.submitter?.getAttribute("data-turbo-submits-with"); 1704 } 1705}; 1706function buildFormData(formElement, submitter2) { 1707 const formData = new FormData(formElement); 1708 const name = submitter2?.getAttribute("name"); 1709 const value = submitter2?.getAttribute("value"); 1710 if (name) { 1711 formData.append(name, value || ""); 1712 } 1713 return formData; 1714} 1715function getCookieValue(cookieName) { 1716 if (cookieName != null) { 1717 const cookies = document.cookie ? document.cookie.split("; ") : []; 1718 const cookie = cookies.find((cookie2) => cookie2.startsWith(cookieName)); 1719 if (cookie) { 1720 const value = cookie.split("=").slice(1).join("="); 1721 return value ? decodeURIComponent(value) : void 0; 1722 } 1723 } 1724} 1725function responseSucceededWithoutRedirect(response) { 1726 return response.statusCode == 200 && !response.redirected; 1727} 1728function getFormAction(formElement, submitter2) { 1729 const formElementAction = typeof formElement.action === "string" ? formElement.action : null; 1730 if (submitter2?.hasAttribute("formaction")) { 1731 return submitter2.getAttribute("formaction") || ""; 1732 } else { 1733 return formElement.getAttribute("action") || formElementAction || ""; 1734 } 1735} 1736function getAction(formAction, fetchMethod) { 1737 const action = expandURL(formAction); 1738 if (isSafe(fetchMethod)) { 1739 action.search = ""; 1740 } 1741 return action; 1742} 1743function getMethod(formElement, submitter2) { 1744 const method = submitter2?.getAttribute("formmethod") || formElement.getAttribute("method") || ""; 1745 return fetchMethodFromString(method.toLowerCase()) || FetchMethod.get; 1746} 1747function getEnctype(formElement, submitter2) { 1748 return fetchEnctypeFromString(submitter2?.getAttribute("formenctype") || formElement.enctype); 1749} 1750var Snapshot = class { 1751 constructor(element) { 1752 this.element = element; 1753 } 1754 get activeElement() { 1755 return this.element.ownerDocument.activeElement; 1756 } 1757 get children() { 1758 return [...this.element.children]; 1759 } 1760 hasAnchor(anchor) { 1761 return this.getElementForAnchor(anchor) != null; 1762 } 1763 getElementForAnchor(anchor) { 1764 return anchor ? this.element.querySelector(`[id='${anchor}'], a[name='${anchor}']`) : null; 1765 } 1766 get isConnected() { 1767 return this.element.isConnected; 1768 } 1769 get firstAutofocusableElement() { 1770 return queryAutofocusableElement(this.element); 1771 } 1772 get permanentElements() { 1773 return queryPermanentElementsAll(this.element); 1774 } 1775 getPermanentElementById(id) { 1776 return getPermanentElementById(this.element, id); 1777 } 1778 getPermanentElementMapForSnapshot(snapshot) { 1779 const permanentElementMap = {}; 1780 for (const currentPermanentElement of this.permanentElements) { 1781 const { id } = currentPermanentElement; 1782 const newPermanentElement = snapshot.getPermanentElementById(id); 1783 if (newPermanentElement) { 1784 permanentElementMap[id] = [currentPermanentElement, newPermanentElement]; 1785 } 1786 } 1787 return permanentElementMap; 1788 } 1789}; 1790function getPermanentElementById(node, id) { 1791 return node.querySelector(`#${id}[data-turbo-permanent]`); 1792} 1793function queryPermanentElementsAll(node) { 1794 return node.querySelectorAll("[id][data-turbo-permanent]"); 1795} 1796var FormSubmitObserver = class { 1797 started = false; 1798 constructor(delegate, eventTarget) { 1799 this.delegate = delegate; 1800 this.eventTarget = eventTarget; 1801 } 1802 start() { 1803 if (!this.started) { 1804 this.eventTarget.addEventListener("submit", this.submitCaptured, true); 1805 this.started = true; 1806 } 1807 } 1808 stop() { 1809 if (this.started) { 1810 this.eventTarget.removeEventListener("submit", this.submitCaptured, true); 1811 this.started = false; 1812 } 1813 } 1814 submitCaptured = () => { 1815 this.eventTarget.removeEventListener("submit", this.submitBubbled, false); 1816 this.eventTarget.addEventListener("submit", this.submitBubbled, false); 1817 }; 1818 submitBubbled = (event) => { 1819 if (!event.defaultPrevented) { 1820 const form = event.target instanceof HTMLFormElement ? event.target : void 0; 1821 const submitter2 = event.submitter || void 0; 1822 if (form && submissionDoesNotDismissDialog(form, submitter2) && submissionDoesNotTargetIFrame(form, submitter2) && this.delegate.willSubmitForm(form, submitter2)) { 1823 event.preventDefault(); 1824 event.stopImmediatePropagation(); 1825 this.delegate.formSubmitted(form, submitter2); 1826 } 1827 } 1828 }; 1829}; 1830function submissionDoesNotDismissDialog(form, submitter2) { 1831 const method = submitter2?.getAttribute("formmethod") || form.getAttribute("method"); 1832 return method != "dialog"; 1833} 1834function submissionDoesNotTargetIFrame(form, submitter2) { 1835 const target = submitter2?.getAttribute("formtarget") || form.getAttribute("target"); 1836 return doesNotTargetIFrame(target); 1837} 1838var View = class { 1839 #resolveRenderPromise = (_value) => { 1840 }; 1841 #resolveInterceptionPromise = (_value) => { 1842 }; 1843 constructor(delegate, element) { 1844 this.delegate = delegate; 1845 this.element = element; 1846 } 1847 // Scrolling 1848 scrollToAnchor(anchor) { 1849 const element = this.snapshot.getElementForAnchor(anchor); 1850 if (element) { 1851 this.scrollToElement(element); 1852 this.focusElement(element); 1853 } else { 1854 this.scrollToPosition({ x: 0, y: 0 }); 1855 } 1856 } 1857 scrollToAnchorFromLocation(location2) { 1858 this.scrollToAnchor(getAnchor(location2)); 1859 } 1860 scrollToElement(element) { 1861 element.scrollIntoView(); 1862 } 1863 focusElement(element) { 1864 if (element instanceof HTMLElement) { 1865 if (element.hasAttribute("tabindex")) { 1866 element.focus(); 1867 } else { 1868 element.setAttribute("tabindex", "-1"); 1869 element.focus(); 1870 element.removeAttribute("tabindex"); 1871 } 1872 } 1873 } 1874 scrollToPosition({ x, y }) { 1875 this.scrollRoot.scrollTo(x, y); 1876 } 1877 scrollToTop() { 1878 this.scrollToPosition({ x: 0, y: 0 }); 1879 } 1880 get scrollRoot() { 1881 return window; 1882 } 1883 // Rendering 1884 async render(renderer) { 1885 const { isPreview, shouldRender, willRender, newSnapshot: snapshot } = renderer; 1886 const shouldInvalidate = willRender; 1887 if (shouldRender) { 1888 try { 1889 this.renderPromise = new Promise((resolve) => this.#resolveRenderPromise = resolve); 1890 this.renderer = renderer; 1891 await this.prepareToRenderSnapshot(renderer); 1892 const renderInterception = new Promise((resolve) => this.#resolveInterceptionPromise = resolve); 1893 const options = { resume: this.#resolveInterceptionPromise, render: this.renderer.renderElement, renderMethod: this.renderer.renderMethod }; 1894 const immediateRender = this.delegate.allowsImmediateRender(snapshot, options); 1895 if (!immediateRender) await renderInterception; 1896 await this.renderSnapshot(renderer); 1897 this.delegate.viewRenderedSnapshot(snapshot, isPreview, this.renderer.renderMethod); 1898 this.delegate.preloadOnLoadLinksForView(this.element); 1899 this.finishRenderingSnapshot(renderer); 1900 } finally { 1901 delete this.renderer; 1902 this.#resolveRenderPromise(void 0); 1903 delete this.renderPromise; 1904 } 1905 } else if (shouldInvalidate) { 1906 this.invalidate(renderer.reloadReason); 1907 } 1908 } 1909 invalidate(reason) { 1910 this.delegate.viewInvalidated(reason); 1911 } 1912 async prepareToRenderSnapshot(renderer) { 1913 this.markAsPreview(renderer.isPreview); 1914 await renderer.prepareToRender(); 1915 } 1916 markAsPreview(isPreview) { 1917 if (isPreview) { 1918 this.element.setAttribute("data-turbo-preview", ""); 1919 } else { 1920 this.element.removeAttribute("data-turbo-preview"); 1921 } 1922 } 1923 markVisitDirection(direction) { 1924 this.element.setAttribute("data-turbo-visit-direction", direction); 1925 } 1926 unmarkVisitDirection() { 1927 this.element.removeAttribute("data-turbo-visit-direction"); 1928 } 1929 async renderSnapshot(renderer) { 1930 await renderer.render(); 1931 } 1932 finishRenderingSnapshot(renderer) { 1933 renderer.finishRendering(); 1934 } 1935}; 1936var FrameView = class extends View { 1937 missing() { 1938 this.element.innerHTML = `<strong class="turbo-frame-error">Content missing</strong>`; 1939 } 1940 get snapshot() { 1941 return new Snapshot(this.element); 1942 } 1943}; 1944var LinkInterceptor = class { 1945 constructor(delegate, element) { 1946 this.delegate = delegate; 1947 this.element = element; 1948 } 1949 start() { 1950 this.element.addEventListener("click", this.clickBubbled); 1951 document.addEventListener("turbo:click", this.linkClicked); 1952 document.addEventListener("turbo:before-visit", this.willVisit); 1953 } 1954 stop() { 1955 this.element.removeEventListener("click", this.clickBubbled); 1956 document.removeEventListener("turbo:click", this.linkClicked); 1957 document.removeEventListener("turbo:before-visit", this.willVisit); 1958 } 1959 clickBubbled = (event) => { 1960 if (this.clickEventIsSignificant(event)) { 1961 this.clickEvent = event; 1962 } else { 1963 delete this.clickEvent; 1964 } 1965 }; 1966 linkClicked = (event) => { 1967 if (this.clickEvent && this.clickEventIsSignificant(event)) { 1968 if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) { 1969 this.clickEvent.preventDefault(); 1970 event.preventDefault(); 1971 this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent); 1972 } 1973 } 1974 delete this.clickEvent; 1975 }; 1976 willVisit = (_event) => { 1977 delete this.clickEvent; 1978 }; 1979 clickEventIsSignificant(event) { 1980 const target = event.composed ? event.target?.parentElement : event.target; 1981 const element = findLinkFromClickTarget(target) || target; 1982 return element instanceof Element && element.closest("turbo-frame, html") == this.element; 1983 } 1984}; 1985var LinkClickObserver = class { 1986 started = false; 1987 constructor(delegate, eventTarget) { 1988 this.delegate = delegate; 1989 this.eventTarget = eventTarget; 1990 } 1991 start() { 1992 if (!this.started) { 1993 this.eventTarget.addEventListener("click", this.clickCaptured, true); 1994 this.started = true; 1995 } 1996 } 1997 stop() { 1998 if (this.started) { 1999 this.eventTarget.removeEventListener("click", this.clickCaptured, true); 2000 this.started = false; 2001 } 2002 } 2003 clickCaptured = () => { 2004 this.eventTarget.removeEventListener("click", this.clickBubbled, false); 2005 this.eventTarget.addEventListener("click", this.clickBubbled, false); 2006 }; 2007 clickBubbled = (event) => { 2008 if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) { 2009 const target = event.composedPath && event.composedPath()[0] || event.target; 2010 const link = findLinkFromClickTarget(target); 2011 if (link && doesNotTargetIFrame(link.target)) { 2012 const location2 = getLocationForLink(link); 2013 if (this.delegate.willFollowLinkToLocation(link, location2, event)) { 2014 event.preventDefault(); 2015 this.delegate.followedLinkToLocation(link, location2); 2016 } 2017 } 2018 } 2019 }; 2020 clickEventIsSignificant(event) { 2021 return !(event.target && event.target.isContentEditable || event.defaultPrevented || event.which > 1 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey); 2022 } 2023}; 2024var FormLinkClickObserver = class { 2025 constructor(delegate, element) { 2026 this.delegate = delegate; 2027 this.linkInterceptor = new LinkClickObserver(this, element); 2028 } 2029 start() { 2030 this.linkInterceptor.start(); 2031 } 2032 stop() { 2033 this.linkInterceptor.stop(); 2034 } 2035 // Link hover observer delegate 2036 canPrefetchRequestToLocation(link, location2) { 2037 return false; 2038 } 2039 prefetchAndCacheRequestToLocation(link, location2) { 2040 return; 2041 } 2042 // Link click observer delegate 2043 willFollowLinkToLocation(link, location2, originalEvent) { 2044 return this.delegate.willSubmitFormLinkToLocation(link, location2, originalEvent) && (link.hasAttribute("data-turbo-method") || link.hasAttribute("data-turbo-stream")); 2045 } 2046 followedLinkToLocation(link, location2) { 2047 const form = document.createElement("form"); 2048 const type = "hidden"; 2049 for (const [name, value] of location2.searchParams) { 2050 form.append(Object.assign(document.createElement("input"), { type, name, value })); 2051 } 2052 const action = Object.assign(location2, { search: "" }); 2053 form.setAttribute("data-turbo", "true"); 2054 form.setAttribute("action", action.href); 2055 form.setAttribute("hidden", ""); 2056 const method = link.getAttribute("data-turbo-method"); 2057 if (method) form.setAttribute("method", method); 2058 const turboFrame = link.getAttribute("data-turbo-frame"); 2059 if (turboFrame) form.setAttribute("data-turbo-frame", turboFrame); 2060 const turboAction = getVisitAction(link); 2061 if (turboAction) form.setAttribute("data-turbo-action", turboAction); 2062 const turboConfirm = link.getAttribute("data-turbo-confirm"); 2063 if (turboConfirm) form.setAttribute("data-turbo-confirm", turboConfirm); 2064 const turboStream = link.hasAttribute("data-turbo-stream"); 2065 if (turboStream) form.setAttribute("data-turbo-stream", ""); 2066 this.delegate.submittedFormLinkToLocation(link, location2, form); 2067 document.body.appendChild(form); 2068 form.addEventListener("turbo:submit-end", () => form.remove(), { once: true }); 2069 requestAnimationFrame(() => form.requestSubmit()); 2070 } 2071}; 2072var Bardo = class { 2073 static async preservingPermanentElements(delegate, permanentElementMap, callback) { 2074 const bardo = new this(delegate, permanentElementMap); 2075 bardo.enter(); 2076 await callback(); 2077 bardo.leave(); 2078 } 2079 constructor(delegate, permanentElementMap) { 2080 this.delegate = delegate; 2081 this.permanentElementMap = permanentElementMap; 2082 } 2083 enter() { 2084 for (const id in this.permanentElementMap) { 2085 const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id]; 2086 this.delegate.enteringBardo(currentPermanentElement, newPermanentElement); 2087 this.replaceNewPermanentElementWithPlaceholder(newPermanentElement); 2088 } 2089 } 2090 leave() { 2091 for (const id in this.permanentElementMap) { 2092 const [currentPermanentElement] = this.permanentElementMap[id]; 2093 this.replaceCurrentPermanentElementWithClone(currentPermanentElement); 2094 this.replacePlaceholderWithPermanentElement(currentPermanentElement); 2095 this.delegate.leavingBardo(currentPermanentElement); 2096 } 2097 } 2098 replaceNewPermanentElementWithPlaceholder(permanentElement) { 2099 const placeholder = createPlaceholderForPermanentElement(permanentElement); 2100 permanentElement.replaceWith(placeholder); 2101 } 2102 replaceCurrentPermanentElementWithClone(permanentElement) { 2103 const clone = permanentElement.cloneNode(true); 2104 permanentElement.replaceWith(clone); 2105 } 2106 replacePlaceholderWithPermanentElement(permanentElement) { 2107 const placeholder = this.getPlaceholderById(permanentElement.id); 2108 placeholder?.replaceWith(permanentElement); 2109 } 2110 getPlaceholderById(id) { 2111 return this.placeholders.find((element) => element.content == id); 2112 } 2113 get placeholders() { 2114 return [...document.querySelectorAll("meta[name=turbo-permanent-placeholder][content]")]; 2115 } 2116}; 2117function createPlaceholderForPermanentElement(permanentElement) { 2118 const element = document.createElement("meta"); 2119 element.setAttribute("name", "turbo-permanent-placeholder"); 2120 element.setAttribute("content", permanentElement.id); 2121 return element; 2122} 2123var Renderer = class { 2124 #activeElement = null; 2125 static renderElement(currentElement, newElement) { 2126 } 2127 constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) { 2128 this.currentSnapshot = currentSnapshot; 2129 this.newSnapshot = newSnapshot; 2130 this.isPreview = isPreview; 2131 this.willRender = willRender; 2132 this.renderElement = this.constructor.renderElement; 2133 this.promise = new Promise((resolve, reject) => this.resolvingFunctions = { resolve, reject }); 2134 } 2135 get shouldRender() { 2136 return true; 2137 } 2138 get shouldAutofocus() { 2139 return true; 2140 } 2141 get reloadReason() { 2142 return; 2143 } 2144 prepareToRender() { 2145 return; 2146 } 2147 render() { 2148 } 2149 finishRendering() { 2150 if (this.resolvingFunctions) { 2151 this.resolvingFunctions.resolve(); 2152 delete this.resolvingFunctions; 2153 } 2154 } 2155 async preservingPermanentElements(callback) { 2156 await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback); 2157 } 2158 focusFirstAutofocusableElement() { 2159 if (this.shouldAutofocus) { 2160 const element = this.connectedSnapshot.firstAutofocusableElement; 2161 if (element) { 2162 element.focus(); 2163 } 2164 } 2165 } 2166 // Bardo delegate 2167 enteringBardo(currentPermanentElement) { 2168 if (this.#activeElement) return; 2169 if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) { 2170 this.#activeElement = this.currentSnapshot.activeElement; 2171 } 2172 } 2173 leavingBardo(currentPermanentElement) { 2174 if (currentPermanentElement.contains(this.#activeElement) && this.#activeElement instanceof HTMLElement) { 2175 this.#activeElement.focus(); 2176 this.#activeElement = null; 2177 } 2178 } 2179 get connectedSnapshot() { 2180 return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot; 2181 } 2182 get currentElement() { 2183 return this.currentSnapshot.element; 2184 } 2185 get newElement() { 2186 return this.newSnapshot.element; 2187 } 2188 get permanentElementMap() { 2189 return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot); 2190 } 2191 get renderMethod() { 2192 return "replace"; 2193 } 2194}; 2195var FrameRenderer = class extends Renderer { 2196 static renderElement(currentElement, newElement) { 2197 const destinationRange = document.createRange(); 2198 destinationRange.selectNodeContents(currentElement); 2199 destinationRange.deleteContents(); 2200 const frameElement = newElement; 2201 const sourceRange = frameElement.ownerDocument?.createRange(); 2202 if (sourceRange) { 2203 sourceRange.selectNodeContents(frameElement); 2204 currentElement.appendChild(sourceRange.extractContents()); 2205 } 2206 } 2207 constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) { 2208 super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender); 2209 this.delegate = delegate; 2210 } 2211 get shouldRender() { 2212 return true; 2213 } 2214 async render() { 2215 await nextRepaint(); 2216 this.preservingPermanentElements(() => { 2217 this.loadFrameElement(); 2218 }); 2219 this.scrollFrameIntoView(); 2220 await nextRepaint(); 2221 this.focusFirstAutofocusableElement(); 2222 await nextRepaint(); 2223 this.activateScriptElements(); 2224 } 2225 loadFrameElement() { 2226 this.delegate.willRenderFrame(this.currentElement, this.newElement); 2227 this.renderElement(this.currentElement, this.newElement); 2228 } 2229 scrollFrameIntoView() { 2230 if (this.currentElement.autoscroll || this.newElement.autoscroll) { 2231 const element = this.currentElement.firstElementChild; 2232 const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end"); 2233 const behavior = readScrollBehavior(this.currentElement.getAttribute("data-autoscroll-behavior"), "auto"); 2234 if (element) { 2235 element.scrollIntoView({ block, behavior }); 2236 return true; 2237 } 2238 } 2239 return false; 2240 } 2241 activateScriptElements() { 2242 for (const inertScriptElement of this.newScriptElements) { 2243 const activatedScriptElement = activateScriptElement(inertScriptElement); 2244 inertScriptElement.replaceWith(activatedScriptElement); 2245 } 2246 } 2247 get newScriptElements() { 2248 return this.currentElement.querySelectorAll("script"); 2249 } 2250}; 2251function readScrollLogicalPosition(value, defaultValue) { 2252 if (value == "end" || value == "start" || value == "center" || value == "nearest") { 2253 return value; 2254 } else { 2255 return defaultValue; 2256 } 2257} 2258function readScrollBehavior(value, defaultValue) { 2259 if (value == "auto" || value == "smooth") { 2260 return value; 2261 } else { 2262 return defaultValue; 2263 } 2264} 2265var Idiomorph = function() { 2266 const noOp = () => { 2267 }; 2268 const defaults = { 2269 morphStyle: "outerHTML", 2270 callbacks: { 2271 beforeNodeAdded: noOp, 2272 afterNodeAdded: noOp, 2273 beforeNodeMorphed: noOp, 2274 afterNodeMorphed: noOp, 2275 beforeNodeRemoved: noOp, 2276 afterNodeRemoved: noOp, 2277 beforeAttributeUpdated: noOp 2278 }, 2279 head: { 2280 style: "merge", 2281 shouldPreserve: (elt) => elt.getAttribute("im-preserve") === "true", 2282 shouldReAppend: (elt) => elt.getAttribute("im-re-append") === "true", 2283 shouldRemove: noOp, 2284 afterHeadMorphed: noOp 2285 }, 2286 restoreFocus: true 2287 }; 2288 function morph(oldNode, newContent, config2 = {}) { 2289 oldNode = normalizeElement(oldNode); 2290 const newNode = normalizeParent(newContent); 2291 const ctx = createMorphContext(oldNode, newNode, config2); 2292 const morphedNodes = saveAndRestoreFocus(ctx, () => { 2293 return withHeadBlocking( 2294 ctx, 2295 oldNode, 2296 newNode, 2297 /** @param {MorphContext} ctx */ 2298 (ctx2) => { 2299 if (ctx2.morphStyle === "innerHTML") { 2300 morphChildren2(ctx2, oldNode, newNode); 2301 return Array.from(oldNode.childNodes); 2302 } else { 2303 return morphOuterHTML(ctx2, oldNode, newNode); 2304 } 2305 } 2306 ); 2307 }); 2308 ctx.pantry.remove(); 2309 return morphedNodes; 2310 } 2311 function morphOuterHTML(ctx, oldNode, newNode) { 2312 const oldParent = normalizeParent(oldNode); 2313 let childNodes = Array.from(oldParent.childNodes); 2314 const index = childNodes.indexOf(oldNode); 2315 const rightMargin = childNodes.length - (index + 1); 2316 morphChildren2( 2317 ctx, 2318 oldParent, 2319 newNode, 2320 // these two optional params are the secret sauce 2321 oldNode, 2322 // start point for iteration 2323 oldNode.nextSibling 2324 // end point for iteration 2325 ); 2326 childNodes = Array.from(oldParent.childNodes); 2327 return childNodes.slice(index, childNodes.length - rightMargin); 2328 } 2329 function saveAndRestoreFocus(ctx, fn) { 2330 if (!ctx.config.restoreFocus) return fn(); 2331 let activeElement = ( 2332 /** @type {HTMLInputElement|HTMLTextAreaElement|null} */ 2333 document.activeElement 2334 ); 2335 if (!(activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement)) { 2336 return fn(); 2337 } 2338 const { id: activeElementId, selectionStart, selectionEnd } = activeElement; 2339 const results = fn(); 2340 if (activeElementId && activeElementId !== document.activeElement?.id) { 2341 activeElement = ctx.target.querySelector(`#${activeElementId}`); 2342 activeElement?.focus(); 2343 } 2344 if (activeElement && !activeElement.selectionEnd && selectionEnd) { 2345 activeElement.setSelectionRange(selectionStart, selectionEnd); 2346 } 2347 return results; 2348 } 2349 const morphChildren2 = /* @__PURE__ */ function() { 2350 function morphChildren3(ctx, oldParent, newParent, insertionPoint = null, endPoint = null) { 2351 if (oldParent instanceof HTMLTemplateElement && newParent instanceof HTMLTemplateElement) { 2352 oldParent = oldParent.content; 2353 newParent = newParent.content; 2354 } 2355 insertionPoint ||= oldParent.firstChild; 2356 for (const newChild of newParent.childNodes) { 2357 if (insertionPoint && insertionPoint != endPoint) { 2358 const bestMatch = findBestMatch( 2359 ctx, 2360 newChild, 2361 insertionPoint, 2362 endPoint 2363 ); 2364 if (bestMatch) { 2365 if (bestMatch !== insertionPoint) { 2366 removeNodesBetween(ctx, insertionPoint, bestMatch); 2367 } 2368 morphNode(bestMatch, newChild, ctx); 2369 insertionPoint = bestMatch.nextSibling; 2370 continue; 2371 } 2372 } 2373 if (newChild instanceof Element && ctx.persistentIds.has(newChild.id)) { 2374 const movedChild = moveBeforeById( 2375 oldParent, 2376 newChild.id, 2377 insertionPoint, 2378 ctx 2379 ); 2380 morphNode(movedChild, newChild, ctx); 2381 insertionPoint = movedChild.nextSibling; 2382 continue; 2383 } 2384 const insertedNode = createNode( 2385 oldParent, 2386 newChild, 2387 insertionPoint, 2388 ctx 2389 ); 2390 if (insertedNode) { 2391 insertionPoint = insertedNode.nextSibling; 2392 } 2393 } 2394 while (insertionPoint && insertionPoint != endPoint) { 2395 const tempNode = insertionPoint; 2396 insertionPoint = insertionPoint.nextSibling; 2397 removeNode(ctx, tempNode); 2398 } 2399 } 2400 function createNode(oldParent, newChild, insertionPoint, ctx) { 2401 if (ctx.callbacks.beforeNodeAdded(newChild) === false) return null; 2402 if (ctx.idMap.has(newChild)) { 2403 const newEmptyChild = document.createElement( 2404 /** @type {Element} */ 2405 newChild.tagName 2406 ); 2407 oldParent.insertBefore(newEmptyChild, insertionPoint); 2408 morphNode(newEmptyChild, newChild, ctx); 2409 ctx.callbacks.afterNodeAdded(newEmptyChild); 2410 return newEmptyChild; 2411 } else { 2412 const newClonedChild = document.importNode(newChild, true); 2413 oldParent.insertBefore(newClonedChild, insertionPoint); 2414 ctx.callbacks.afterNodeAdded(newClonedChild); 2415 return newClonedChild; 2416 } 2417 } 2418 const findBestMatch = /* @__PURE__ */ function() { 2419 function findBestMatch2(ctx, node, startPoint, endPoint) { 2420 let softMatch = null; 2421 let nextSibling = node.nextSibling; 2422 let siblingSoftMatchCount = 0; 2423 let cursor = startPoint; 2424 while (cursor && cursor != endPoint) { 2425 if (isSoftMatch(cursor, node)) { 2426 if (isIdSetMatch(ctx, cursor, node)) { 2427 return cursor; 2428 } 2429 if (softMatch === null) { 2430 if (!ctx.idMap.has(cursor)) { 2431 softMatch = cursor; 2432 } 2433 } 2434 } 2435 if (softMatch === null && nextSibling && isSoftMatch(cursor, nextSibling)) { 2436 siblingSoftMatchCount++; 2437 nextSibling = nextSibling.nextSibling; 2438 if (siblingSoftMatchCount >= 2) { 2439 softMatch = void 0; 2440 } 2441 } 2442 if (cursor.contains(document.activeElement)) break; 2443 cursor = cursor.nextSibling; 2444 } 2445 return softMatch || null; 2446 } 2447 function isIdSetMatch(ctx, oldNode, newNode) { 2448 let oldSet = ctx.idMap.get(oldNode); 2449 let newSet = ctx.idMap.get(newNode); 2450 if (!newSet || !oldSet) return false; 2451 for (const id of oldSet) { 2452 if (newSet.has(id)) { 2453 return true; 2454 } 2455 } 2456 return false; 2457 } 2458 function isSoftMatch(oldNode, newNode) { 2459 const oldElt = ( 2460 /** @type {Element} */ 2461 oldNode 2462 ); 2463 const newElt = ( 2464 /** @type {Element} */ 2465 newNode 2466 ); 2467 return oldElt.nodeType === newElt.nodeType && oldElt.tagName === newElt.tagName && // If oldElt has an `id` with possible state and it doesn't match newElt.id then avoid morphing. 2468 // We'll still match an anonymous node with an IDed newElt, though, because if it got this far, 2469 // its not persistent, and new nodes can't have any hidden state. 2470 (!oldElt.id || oldElt.id === newElt.id); 2471 } 2472 return findBestMatch2; 2473 }(); 2474 function removeNode(ctx, node) { 2475 if (ctx.idMap.has(node)) { 2476 moveBefore(ctx.pantry, node, null); 2477 } else { 2478 if (ctx.callbacks.beforeNodeRemoved(node) === false) return; 2479 node.parentNode?.removeChild(node); 2480 ctx.callbacks.afterNodeRemoved(node); 2481 } 2482 } 2483 function removeNodesBetween(ctx, startInclusive, endExclusive) { 2484 let cursor = startInclusive; 2485 while (cursor && cursor !== endExclusive) { 2486 let tempNode = ( 2487 /** @type {Node} */ 2488 cursor 2489 ); 2490 cursor = cursor.nextSibling; 2491 removeNode(ctx, tempNode); 2492 } 2493 return cursor; 2494 } 2495 function moveBeforeById(parentNode, id, after, ctx) { 2496 const target = ( 2497 /** @type {Element} - will always be found */ 2498 ctx.target.querySelector(`#${id}`) || ctx.pantry.querySelector(`#${id}`) 2499 ); 2500 removeElementFromAncestorsIdMaps(target, ctx); 2501 moveBefore(parentNode, target, after); 2502 return target; 2503 } 2504 function removeElementFromAncestorsIdMaps(element, ctx) { 2505 const id = element.id; 2506 while (element = element.parentNode) { 2507 let idSet = ctx.idMap.get(element); 2508 if (idSet) { 2509 idSet.delete(id); 2510 if (!idSet.size) { 2511 ctx.idMap.delete(element); 2512 } 2513 } 2514 } 2515 } 2516 function moveBefore(parentNode, element, after) { 2517 if (parentNode.moveBefore) { 2518 try { 2519 parentNode.moveBefore(element, after); 2520 } catch (e) { 2521 parentNode.insertBefore(element, after); 2522 } 2523 } else { 2524 parentNode.insertBefore(element, after); 2525 } 2526 } 2527 return morphChildren3; 2528 }(); 2529 const morphNode = /* @__PURE__ */ function() { 2530 function morphNode2(oldNode, newContent, ctx) { 2531 if (ctx.ignoreActive && oldNode === document.activeElement) { 2532 return null; 2533 } 2534 if (ctx.callbacks.beforeNodeMorphed(oldNode, newContent) === false) { 2535 return oldNode; 2536 } 2537 if (oldNode instanceof HTMLHeadElement && ctx.head.ignore) ; 2538 else if (oldNode instanceof HTMLHeadElement && ctx.head.style !== "morph") { 2539 handleHeadElement( 2540 oldNode, 2541 /** @type {HTMLHeadElement} */ 2542 newContent, 2543 ctx 2544 ); 2545 } else { 2546 morphAttributes(oldNode, newContent, ctx); 2547 if (!ignoreValueOfActiveElement(oldNode, ctx)) { 2548 morphChildren2(ctx, oldNode, newContent); 2549 } 2550 } 2551 ctx.callbacks.afterNodeMorphed(oldNode, newContent); 2552 return oldNode; 2553 } 2554 function morphAttributes(oldNode, newNode, ctx) { 2555 let type = newNode.nodeType; 2556 if (type === 1) { 2557 const oldElt = ( 2558 /** @type {Element} */ 2559 oldNode 2560 ); 2561 const newElt = ( 2562 /** @type {Element} */ 2563 newNode 2564 ); 2565 const oldAttributes = oldElt.attributes; 2566 const newAttributes = newElt.attributes; 2567 for (const newAttribute of newAttributes) { 2568 if (ignoreAttribute(newAttribute.name, oldElt, "update", ctx)) { 2569 continue; 2570 } 2571 if (oldElt.getAttribute(newAttribute.name) !== newAttribute.value) { 2572 oldElt.setAttribute(newAttribute.name, newAttribute.value); 2573 } 2574 } 2575 for (let i = oldAttributes.length - 1; 0 <= i; i--) { 2576 const oldAttribute = oldAttributes[i]; 2577 if (!oldAttribute) continue; 2578 if (!newElt.hasAttribute(oldAttribute.name)) { 2579 if (ignoreAttribute(oldAttribute.name, oldElt, "remove", ctx)) { 2580 continue; 2581 } 2582 oldElt.removeAttribute(oldAttribute.name); 2583 } 2584 } 2585 if (!ignoreValueOfActiveElement(oldElt, ctx)) { 2586 syncInputValue(oldElt, newElt, ctx); 2587 } 2588 } 2589 if (type === 8 || type === 3) { 2590 if (oldNode.nodeValue !== newNode.nodeValue) { 2591 oldNode.nodeValue = newNode.nodeValue; 2592 } 2593 } 2594 } 2595 function syncInputValue(oldElement, newElement, ctx) { 2596 if (oldElement instanceof HTMLInputElement && newElement instanceof HTMLInputElement && newElement.type !== "file") { 2597 let newValue = newElement.value; 2598 let oldValue = oldElement.value; 2599 syncBooleanAttribute(oldElement, newElement, "checked", ctx); 2600 syncBooleanAttribute(oldElement, newElement, "disabled", ctx); 2601 if (!newElement.hasAttribute("value")) { 2602 if (!ignoreAttribute("value", oldElement, "remove", ctx)) { 2603 oldElement.value = ""; 2604 oldElement.removeAttribute("value"); 2605 } 2606 } else if (oldValue !== newValue) { 2607 if (!ignoreAttribute("value", oldElement, "update", ctx)) { 2608 oldElement.setAttribute("value", newValue); 2609 oldElement.value = newValue; 2610 } 2611 } 2612 } else if (oldElement instanceof HTMLOptionElement && newElement instanceof HTMLOptionElement) { 2613 syncBooleanAttribute(oldElement, newElement, "selected", ctx); 2614 } else if (oldElement instanceof HTMLTextAreaElement && newElement instanceof HTMLTextAreaElement) { 2615 let newValue = newElement.value; 2616 let oldValue = oldElement.value; 2617 if (ignoreAttribute("value", oldElement, "update", ctx)) { 2618 return; 2619 } 2620 if (newValue !== oldValue) { 2621 oldElement.value = newValue; 2622 } 2623 if (oldElement.firstChild && oldElement.firstChild.nodeValue !== newValue) { 2624 oldElement.firstChild.nodeValue = newValue; 2625 } 2626 } 2627 } 2628 function syncBooleanAttribute(oldElement, newElement, attributeName, ctx) { 2629 const newLiveValue = newElement[attributeName], oldLiveValue = oldElement[attributeName]; 2630 if (newLiveValue !== oldLiveValue) { 2631 const ignoreUpdate = ignoreAttribute( 2632 attributeName, 2633 oldElement, 2634 "update", 2635 ctx 2636 ); 2637 if (!ignoreUpdate) { 2638 oldElement[attributeName] = newElement[attributeName]; 2639 } 2640 if (newLiveValue) { 2641 if (!ignoreUpdate) { 2642 oldElement.setAttribute(attributeName, ""); 2643 } 2644 } else { 2645 if (!ignoreAttribute(attributeName, oldElement, "remove", ctx)) { 2646 oldElement.removeAttribute(attributeName); 2647 } 2648 } 2649 } 2650 } 2651 function ignoreAttribute(attr, element, updateType, ctx) { 2652 if (attr === "value" && ctx.ignoreActiveValue && element === document.activeElement) { 2653 return true; 2654 } 2655 return ctx.callbacks.beforeAttributeUpdated(attr, element, updateType) === false; 2656 } 2657 function ignoreValueOfActiveElement(possibleActiveElement, ctx) { 2658 return !!ctx.ignoreActiveValue && possibleActiveElement === document.activeElement && possibleActiveElement !== document.body; 2659 } 2660 return morphNode2; 2661 }(); 2662 function withHeadBlocking(ctx, oldNode, newNode, callback) { 2663 if (ctx.head.block) { 2664 const oldHead = oldNode.querySelector("head"); 2665 const newHead = newNode.querySelector("head"); 2666 if (oldHead && newHead) { 2667 const promises = handleHeadElement(oldHead, newHead, ctx); 2668 return Promise.all(promises).then(() => { 2669 const newCtx = Object.assign(ctx, { 2670 head: { 2671 block: false, 2672 ignore: true 2673 } 2674 }); 2675 return callback(newCtx); 2676 }); 2677 } 2678 } 2679 return callback(ctx); 2680 } 2681 function handleHeadElement(oldHead, newHead, ctx) { 2682 let added = []; 2683 let removed = []; 2684 let preserved = []; 2685 let nodesToAppend = []; 2686 let srcToNewHeadNodes = /* @__PURE__ */ new Map(); 2687 for (const newHeadChild of newHead.children) { 2688 srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild); 2689 } 2690 for (const currentHeadElt of oldHead.children) { 2691 let inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML); 2692 let isReAppended = ctx.head.shouldReAppend(currentHeadElt); 2693 let isPreserved = ctx.head.shouldPreserve(currentHeadElt); 2694 if (inNewContent || isPreserved) { 2695 if (isReAppended) { 2696 removed.push(currentHeadElt); 2697 } else { 2698 srcToNewHeadNodes.delete(currentHeadElt.outerHTML); 2699 preserved.push(currentHeadElt); 2700 } 2701 } else { 2702 if (ctx.head.style === "append") { 2703 if (isReAppended) { 2704 removed.push(currentHeadElt); 2705 nodesToAppend.push(currentHeadElt); 2706 } 2707 } else { 2708 if (ctx.head.shouldRemove(currentHeadElt) !== false) { 2709 removed.push(currentHeadElt); 2710 } 2711 } 2712 } 2713 } 2714 nodesToAppend.push(...srcToNewHeadNodes.values()); 2715 let promises = []; 2716 for (const newNode of nodesToAppend) { 2717 let newElt = ( 2718 /** @type {ChildNode} */ 2719 document.createRange().createContextualFragment(newNode.outerHTML).firstChild 2720 ); 2721 if (ctx.callbacks.beforeNodeAdded(newElt) !== false) { 2722 if ("href" in newElt && newElt.href || "src" in newElt && newElt.src) { 2723 let resolve; 2724 let promise = new Promise(function(_resolve) { 2725 resolve = _resolve; 2726 }); 2727 newElt.addEventListener("load", function() { 2728 resolve(); 2729 }); 2730 promises.push(promise); 2731 } 2732 oldHead.appendChild(newElt); 2733 ctx.callbacks.afterNodeAdded(newElt); 2734 added.push(newElt); 2735 } 2736 } 2737 for (const removedElement of removed) { 2738 if (ctx.callbacks.beforeNodeRemoved(removedElement) !== false) { 2739 oldHead.removeChild(removedElement); 2740 ctx.callbacks.afterNodeRemoved(removedElement); 2741 } 2742 } 2743 ctx.head.afterHeadMorphed(oldHead, { 2744 added, 2745 kept: preserved, 2746 removed 2747 }); 2748 return promises; 2749 } 2750 const createMorphContext = /* @__PURE__ */ function() { 2751 function createMorphContext2(oldNode, newContent, config2) { 2752 const { persistentIds, idMap } = createIdMaps(oldNode, newContent); 2753 const mergedConfig = mergeDefaults(config2); 2754 const morphStyle = mergedConfig.morphStyle || "outerHTML"; 2755 if (!["innerHTML", "outerHTML"].includes(morphStyle)) { 2756 throw `Do not understand how to morph style ${morphStyle}`; 2757 } 2758 return { 2759 target: oldNode, 2760 newContent, 2761 config: mergedConfig, 2762 morphStyle, 2763 ignoreActive: mergedConfig.ignoreActive, 2764 ignoreActiveValue: mergedConfig.ignoreActiveValue, 2765 restoreFocus: mergedConfig.restoreFocus, 2766 idMap, 2767 persistentIds, 2768 pantry: createPantry(), 2769 callbacks: mergedConfig.callbacks, 2770 head: mergedConfig.head 2771 }; 2772 } 2773 function mergeDefaults(config2) { 2774 let finalConfig = Object.assign({}, defaults); 2775 Object.assign(finalConfig, config2); 2776 finalConfig.callbacks = Object.assign( 2777 {}, 2778 defaults.callbacks, 2779 config2.callbacks 2780 ); 2781 finalConfig.head = Object.assign({}, defaults.head, config2.head); 2782 return finalConfig; 2783 } 2784 function createPantry() { 2785 const pantry = document.createElement("div"); 2786 pantry.hidden = true; 2787 document.body.insertAdjacentElement("afterend", pantry); 2788 return pantry; 2789 } 2790 function findIdElements(root) { 2791 let elements = Array.from(root.querySelectorAll("[id]")); 2792 if (root.id) { 2793 elements.push(root); 2794 } 2795 return elements; 2796 } 2797 function populateIdMapWithTree(idMap, persistentIds, root, elements) { 2798 for (const elt of elements) { 2799 if (persistentIds.has(elt.id)) { 2800 let current = elt; 2801 while (current) { 2802 let idSet = idMap.get(current); 2803 if (idSet == null) { 2804 idSet = /* @__PURE__ */ new Set(); 2805 idMap.set(current, idSet); 2806 } 2807 idSet.add(elt.id); 2808 if (current === root) break; 2809 current = current.parentElement; 2810 } 2811 } 2812 } 2813 } 2814 function createIdMaps(oldContent, newContent) { 2815 const oldIdElements = findIdElements(oldContent); 2816 const newIdElements = findIdElements(newContent); 2817 const persistentIds = createPersistentIds(oldIdElements, newIdElements); 2818 let idMap = /* @__PURE__ */ new Map(); 2819 populateIdMapWithTree(idMap, persistentIds, oldContent, oldIdElements); 2820 const newRoot = newContent.__idiomorphRoot || newContent; 2821 populateIdMapWithTree(idMap, persistentIds, newRoot, newIdElements); 2822 return { persistentIds, idMap }; 2823 } 2824 function createPersistentIds(oldIdElements, newIdElements) { 2825 let duplicateIds = /* @__PURE__ */ new Set(); 2826 let oldIdTagNameMap = /* @__PURE__ */ new Map(); 2827 for (const { id, tagName } of oldIdElements) { 2828 if (oldIdTagNameMap.has(id)) { 2829 duplicateIds.add(id); 2830 } else { 2831 oldIdTagNameMap.set(id, tagName); 2832 } 2833 } 2834 let persistentIds = /* @__PURE__ */ new Set(); 2835 for (const { id, tagName } of newIdElements) { 2836 if (persistentIds.has(id)) { 2837 duplicateIds.add(id); 2838 } else if (oldIdTagNameMap.get(id) === tagName) { 2839 persistentIds.add(id); 2840 } 2841 } 2842 for (const id of duplicateIds) { 2843 persistentIds.delete(id); 2844 } 2845 return persistentIds; 2846 } 2847 return createMorphContext2; 2848 }(); 2849 const { normalizeElement, normalizeParent } = /* @__PURE__ */ function() { 2850 const generatedByIdiomorph = /* @__PURE__ */ new WeakSet(); 2851 function normalizeElement2(content) { 2852 if (content instanceof Document) { 2853 return content.documentElement; 2854 } else { 2855 return content; 2856 } 2857 } 2858 function normalizeParent2(newContent) { 2859 if (newContent == null) { 2860 return document.createElement("div"); 2861 } else if (typeof newContent === "string") { 2862 return normalizeParent2(parseContent(newContent)); 2863 } else if (generatedByIdiomorph.has( 2864 /** @type {Element} */ 2865 newContent 2866 )) { 2867 return ( 2868 /** @type {Element} */ 2869 newContent 2870 ); 2871 } else if (newContent instanceof Node) { 2872 if (newContent.parentNode) { 2873 return createDuckTypedParent(newContent); 2874 } else { 2875 const dummyParent = document.createElement("div"); 2876 dummyParent.append(newContent); 2877 return dummyParent; 2878 } 2879 } else { 2880 const dummyParent = document.createElement("div"); 2881 for (const elt of [...newContent]) { 2882 dummyParent.append(elt); 2883 } 2884 return dummyParent; 2885 } 2886 } 2887 function createDuckTypedParent(newContent) { 2888 return ( 2889 /** @type {Element} */ 2890 /** @type {unknown} */ 2891 { 2892 childNodes: [newContent], 2893 /** @ts-ignore - cover your eyes for a minute, tsc */ 2894 querySelectorAll: (s) => { 2895 const elements = newContent.querySelectorAll(s); 2896 return newContent.matches(s) ? [newContent, ...elements] : elements; 2897 }, 2898 /** @ts-ignore */ 2899 insertBefore: (n, r) => newContent.parentNode.insertBefore(n, r), 2900 /** @ts-ignore */ 2901 moveBefore: (n, r) => newContent.parentNode.moveBefore(n, r), 2902 // for later use with populateIdMapWithTree to halt upwards iteration 2903 get __idiomorphRoot() { 2904 return newContent; 2905 } 2906 } 2907 ); 2908 } 2909 function parseContent(newContent) { 2910 let parser = new DOMParser(); 2911 let contentWithSvgsRemoved = newContent.replace( 2912 /<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, 2913 "" 2914 ); 2915 if (contentWithSvgsRemoved.match(/<\/html>/) || contentWithSvgsRemoved.match(/<\/head>/) || contentWithSvgsRemoved.match(/<\/body>/)) { 2916 let content = parser.parseFromString(newContent, "text/html"); 2917 if (contentWithSvgsRemoved.match(/<\/html>/)) { 2918 generatedByIdiomorph.add(content); 2919 return content; 2920 } else { 2921 let htmlElement = content.firstChild; 2922 if (htmlElement) { 2923 generatedByIdiomorph.add(htmlElement); 2924 } 2925 return htmlElement; 2926 } 2927 } else { 2928 let responseDoc = parser.parseFromString( 2929 "<body><template>" + newContent + "</template></body>", 2930 "text/html" 2931 ); 2932 let content = ( 2933 /** @type {HTMLTemplateElement} */ 2934 responseDoc.body.querySelector("template").content 2935 ); 2936 generatedByIdiomorph.add(content); 2937 return content; 2938 } 2939 } 2940 return { normalizeElement: normalizeElement2, normalizeParent: normalizeParent2 }; 2941 }(); 2942 return { 2943 morph, 2944 defaults 2945 }; 2946}(); 2947function morphElements(currentElement, newElement, { callbacks, ...options } = {}) { 2948 Idiomorph.morph(currentElement, newElement, { 2949 ...options, 2950 callbacks: new DefaultIdiomorphCallbacks(callbacks) 2951 }); 2952} 2953function morphChildren(currentElement, newElement) { 2954 morphElements(currentElement, newElement.childNodes, { 2955 morphStyle: "innerHTML" 2956 }); 2957} 2958var DefaultIdiomorphCallbacks = class { 2959 #beforeNodeMorphed; 2960 constructor({ beforeNodeMorphed } = {}) { 2961 this.#beforeNodeMorphed = beforeNodeMorphed || (() => true); 2962 } 2963 beforeNodeAdded = (node) => { 2964 return !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id)); 2965 }; 2966 beforeNodeMorphed = (currentElement, newElement) => { 2967 if (currentElement instanceof Element) { 2968 if (!currentElement.hasAttribute("data-turbo-permanent") && this.#beforeNodeMorphed(currentElement, newElement)) { 2969 const event = dispatch("turbo:before-morph-element", { 2970 cancelable: true, 2971 target: currentElement, 2972 detail: { currentElement, newElement } 2973 }); 2974 return !event.defaultPrevented; 2975 } else { 2976 return false; 2977 } 2978 } 2979 }; 2980 beforeAttributeUpdated = (attributeName, target, mutationType) => { 2981 const event = dispatch("turbo:before-morph-attribute", { 2982 cancelable: true, 2983 target, 2984 detail: { attributeName, mutationType } 2985 }); 2986 return !event.defaultPrevented; 2987 }; 2988 beforeNodeRemoved = (node) => { 2989 return this.beforeNodeMorphed(node); 2990 }; 2991 afterNodeMorphed = (currentElement, newElement) => { 2992 if (currentElement instanceof Element) { 2993 dispatch("turbo:morph-element", { 2994 target: currentElement, 2995 detail: { currentElement, newElement } 2996 }); 2997 } 2998 }; 2999}; 3000var MorphingFrameRenderer = class extends FrameRenderer { 3001 static renderElement(currentElement, newElement) { 3002 dispatch("turbo:before-frame-morph", { 3003 target: currentElement, 3004 detail: { currentElement, newElement } 3005 }); 3006 morphChildren(currentElement, newElement); 3007 } 3008 async preservingPermanentElements(callback) { 3009 return await callback(); 3010 } 3011}; 3012var ProgressBar = class _ProgressBar { 3013 static animationDuration = 300; 3014 /*ms*/ 3015 static get defaultCSS() { 3016 return unindent` 3017 .turbo-progress-bar { 3018 position: fixed; 3019 display: block; 3020 top: 0; 3021 left: 0; 3022 height: 3px; 3023 background: #0076ff; 3024 z-index: 2147483647; 3025 transition: 3026 width ${_ProgressBar.animationDuration}ms ease-out, 3027 opacity ${_ProgressBar.animationDuration / 2}ms ${_ProgressBar.animationDuration / 2}ms ease-in; 3028 transform: translate3d(0, 0, 0); 3029 } 3030 `; 3031 } 3032 hiding = false; 3033 value = 0; 3034 visible = false; 3035 constructor() { 3036 this.stylesheetElement = this.createStylesheetElement(); 3037 this.progressElement = this.createProgressElement(); 3038 this.installStylesheetElement(); 3039 this.setValue(0); 3040 } 3041 show() { 3042 if (!this.visible) { 3043 this.visible = true; 3044 this.installProgressElement(); 3045 this.startTrickling(); 3046 } 3047 } 3048 hide() { 3049 if (this.visible && !this.hiding) { 3050 this.hiding = true; 3051 this.fadeProgressElement(() => { 3052 this.uninstallProgressElement(); 3053 this.stopTrickling(); 3054 this.visible = false; 3055 this.hiding = false; 3056 }); 3057 } 3058 } 3059 setValue(value) { 3060 this.value = value; 3061 this.refresh(); 3062 } 3063 // Private 3064 installStylesheetElement() { 3065 document.head.insertBefore(this.stylesheetElement, document.head.firstChild); 3066 } 3067 installProgressElement() { 3068 this.progressElement.style.width = "0"; 3069 this.progressElement.style.opacity = "1"; 3070 document.documentElement.insertBefore(this.progressElement, document.body); 3071 this.refresh(); 3072 } 3073 fadeProgressElement(callback) { 3074 this.progressElement.style.opacity = "0"; 3075 setTimeout(callback, _ProgressBar.animationDuration * 1.5); 3076 } 3077 uninstallProgressElement() { 3078 if (this.progressElement.parentNode) { 3079 document.documentElement.removeChild(this.progressElement); 3080 } 3081 } 3082 startTrickling() { 3083 if (!this.trickleInterval) { 3084 this.trickleInterval = window.setInterval(this.trickle, _ProgressBar.animationDuration); 3085 } 3086 } 3087 stopTrickling() { 3088 window.clearInterval(this.trickleInterval); 3089 delete this.trickleInterval; 3090 } 3091 trickle = () => { 3092 this.setValue(this.value + Math.random() / 100); 3093 }; 3094 refresh() { 3095 requestAnimationFrame(() => { 3096 this.progressElement.style.width = `${10 + this.value * 90}%`; 3097 }); 3098 } 3099 createStylesheetElement() { 3100 const element = document.createElement("style"); 3101 element.type = "text/css"; 3102 element.textContent = _ProgressBar.defaultCSS; 3103 const cspNonce = getCspNonce(); 3104 if (cspNonce) { 3105 element.nonce = cspNonce; 3106 } 3107 return element; 3108 } 3109 createProgressElement() { 3110 const element = document.createElement("div"); 3111 element.className = "turbo-progress-bar"; 3112 return element; 3113 } 3114}; 3115var HeadSnapshot = class extends Snapshot { 3116 detailsByOuterHTML = this.children.filter((element) => !elementIsNoscript(element)).map((element) => elementWithoutNonce(element)).reduce((result, element) => { 3117 const { outerHTML } = element; 3118 const details = outerHTML in result ? result[outerHTML] : { 3119 type: elementType(element), 3120 tracked: elementIsTracked(element), 3121 elements: [] 3122 }; 3123 return { 3124 ...result, 3125 [outerHTML]: { 3126 ...details, 3127 elements: [...details.elements, element] 3128 } 3129 }; 3130 }, {}); 3131 get trackedElementSignature() { 3132 return Object.keys(this.detailsByOuterHTML).filter((outerHTML) => this.detailsByOuterHTML[outerHTML].tracked).join(""); 3133 } 3134 getScriptElementsNotInSnapshot(snapshot) { 3135 return this.getElementsMatchingTypeNotInSnapshot("script", snapshot); 3136 } 3137 getStylesheetElementsNotInSnapshot(snapshot) { 3138 return this.getElementsMatchingTypeNotInSnapshot("stylesheet", snapshot); 3139 } 3140 getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) { 3141 return Object.keys(this.detailsByOuterHTML).filter((outerHTML) => !(outerHTML in snapshot.detailsByOuterHTML)).map((outerHTML) => this.detailsByOuterHTML[outerHTML]).filter(({ type }) => type == matchedType).map(({ elements: [element] }) => element); 3142 } 3143 get provisionalElements() { 3144 return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => { 3145 const { type, tracked, elements } = this.detailsByOuterHTML[outerHTML]; 3146 if (type == null && !tracked) { 3147 return [...result, ...elements]; 3148 } else if (elements.length > 1) { 3149 return [...result, ...elements.slice(1)]; 3150 } else { 3151 return result; 3152 } 3153 }, []); 3154 } 3155 getMetaValue(name) { 3156 const element = this.findMetaElementByName(name); 3157 return element ? element.getAttribute("content") : null; 3158 } 3159 findMetaElementByName(name) { 3160 return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => { 3161 const { 3162 elements: [element] 3163 } = this.detailsByOuterHTML[outerHTML]; 3164 return elementIsMetaElementWithName(element, name) ? element : result; 3165 }, void 0 | void 0); 3166 } 3167}; 3168function elementType(element) { 3169 if (elementIsScript(element)) { 3170 return "script"; 3171 } else if (elementIsStylesheet(element)) { 3172 return "stylesheet"; 3173 } 3174} 3175function elementIsTracked(element) { 3176 return element.getAttribute("data-turbo-track") == "reload"; 3177} 3178function elementIsScript(element) { 3179 const tagName = element.localName; 3180 return tagName == "script"; 3181} 3182function elementIsNoscript(element) { 3183 const tagName = element.localName; 3184 return tagName == "noscript"; 3185} 3186function elementIsStylesheet(element) { 3187 const tagName = element.localName; 3188 return tagName == "style" || tagName == "link" && element.getAttribute("rel") == "stylesheet"; 3189} 3190function elementIsMetaElementWithName(element, name) { 3191 const tagName = element.localName; 3192 return tagName == "meta" && element.getAttribute("name") == name; 3193} 3194function elementWithoutNonce(element) { 3195 if (element.hasAttribute("nonce")) { 3196 element.setAttribute("nonce", ""); 3197 } 3198 return element; 3199} 3200var PageSnapshot = class _PageSnapshot extends Snapshot { 3201 static fromHTMLString(html = "") { 3202 return this.fromDocument(parseHTMLDocument(html)); 3203 } 3204 static fromElement(element) { 3205 return this.fromDocument(element.ownerDocument); 3206 } 3207 static fromDocument({ documentElement, body, head }) { 3208 return new this(documentElement, body, new HeadSnapshot(head)); 3209 } 3210 constructor(documentElement, body, headSnapshot) { 3211 super(body); 3212 this.documentElement = documentElement; 3213 this.headSnapshot = headSnapshot; 3214 } 3215 clone() { 3216 const clonedElement = this.element.cloneNode(true); 3217 const selectElements = this.element.querySelectorAll("select"); 3218 const clonedSelectElements = clonedElement.querySelectorAll("select"); 3219 for (const [index, source] of selectElements.entries()) { 3220 const clone = clonedSelectElements[index]; 3221 for (const option of clone.selectedOptions) option.selected = false; 3222 for (const option of source.selectedOptions) clone.options[option.index].selected = true; 3223 } 3224 for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) { 3225 clonedPasswordInput.value = ""; 3226 } 3227 return new _PageSnapshot(this.documentElement, clonedElement, this.headSnapshot); 3228 } 3229 get lang() { 3230 return this.documentElement.getAttribute("lang"); 3231 } 3232 get headElement() { 3233 return this.headSnapshot.element; 3234 } 3235 get rootLocation() { 3236 const root = this.getSetting("root") ?? "/"; 3237 return expandURL(root); 3238 } 3239 get cacheControlValue() { 3240 return this.getSetting("cache-control"); 3241 } 3242 get isPreviewable() { 3243 return this.cacheControlValue != "no-preview"; 3244 } 3245 get isCacheable() { 3246 return this.cacheControlValue != "no-cache"; 3247 } 3248 get isVisitable() { 3249 return this.getSetting("visit-control") != "reload"; 3250 } 3251 get prefersViewTransitions() { 3252 return this.headSnapshot.getMetaValue("view-transition") === "same-origin"; 3253 } 3254 get shouldMorphPage() { 3255 return this.getSetting("refresh-method") === "morph"; 3256 } 3257 get shouldPreserveScrollPosition() { 3258 return this.getSetting("refresh-scroll") === "preserve"; 3259 } 3260 // Private 3261 getSetting(name) { 3262 return this.headSnapshot.getMetaValue(`turbo-${name}`); 3263 } 3264}; 3265var ViewTransitioner = class { 3266 #viewTransitionStarted = false; 3267 #lastOperation = Promise.resolve(); 3268 renderChange(useViewTransition, render) { 3269 if (useViewTransition && this.viewTransitionsAvailable && !this.#viewTransitionStarted) { 3270 this.#viewTransitionStarted = true; 3271 this.#lastOperation = this.#lastOperation.then(async () => { 3272 await document.startViewTransition(render).finished; 3273 }); 3274 } else { 3275 this.#lastOperation = this.#lastOperation.then(render); 3276 } 3277 return this.#lastOperation; 3278 } 3279 get viewTransitionsAvailable() { 3280 return document.startViewTransition; 3281 } 3282}; 3283var defaultOptions = { 3284 action: "advance", 3285 historyChanged: false, 3286 visitCachedSnapshot: () => { 3287 }, 3288 willRender: true, 3289 updateHistory: true, 3290 shouldCacheSnapshot: true, 3291 acceptsStreamResponse: false 3292}; 3293var TimingMetric = { 3294 visitStart: "visitStart", 3295 requestStart: "requestStart", 3296 requestEnd: "requestEnd", 3297 visitEnd: "visitEnd" 3298}; 3299var VisitState = { 3300 initialized: "initialized", 3301 started: "started", 3302 canceled: "canceled", 3303 failed: "failed", 3304 completed: "completed" 3305}; 3306var SystemStatusCode = { 3307 networkFailure: 0, 3308 timeoutFailure: -1, 3309 contentTypeMismatch: -2 3310}; 3311var Direction = { 3312 advance: "forward", 3313 restore: "back", 3314 replace: "none" 3315}; 3316var Visit = class { 3317 identifier = uuid(); 3318 // Required by turbo-ios 3319 timingMetrics = {}; 3320 followedRedirect = false; 3321 historyChanged = false; 3322 scrolled = false; 3323 shouldCacheSnapshot = true; 3324 acceptsStreamResponse = false; 3325 snapshotCached = false; 3326 state = VisitState.initialized; 3327 viewTransitioner = new ViewTransitioner(); 3328 constructor(delegate, location2, restorationIdentifier, options = {}) { 3329 this.delegate = delegate; 3330 this.location = location2; 3331 this.restorationIdentifier = restorationIdentifier || uuid(); 3332 const { 3333 action, 3334 historyChanged, 3335 referrer, 3336 snapshot, 3337 snapshotHTML, 3338 response, 3339 visitCachedSnapshot, 3340 willRender, 3341 updateHistory, 3342 shouldCacheSnapshot, 3343 acceptsStreamResponse, 3344 direction 3345 } = { 3346 ...defaultOptions, 3347 ...options 3348 }; 3349 this.action = action; 3350 this.historyChanged = historyChanged; 3351 this.referrer = referrer; 3352 this.snapshot = snapshot; 3353 this.snapshotHTML = snapshotHTML; 3354 this.response = response; 3355 this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action); 3356 this.isPageRefresh = this.view.isPageRefresh(this); 3357 this.visitCachedSnapshot = visitCachedSnapshot; 3358 this.willRender = willRender; 3359 this.updateHistory = updateHistory; 3360 this.scrolled = !willRender; 3361 this.shouldCacheSnapshot = shouldCacheSnapshot; 3362 this.acceptsStreamResponse = acceptsStreamResponse; 3363 this.direction = direction || Direction[action]; 3364 } 3365 get adapter() { 3366 return this.delegate.adapter; 3367 } 3368 get view() { 3369 return this.delegate.view; 3370 } 3371 get history() { 3372 return this.delegate.history; 3373 } 3374 get restorationData() { 3375 return this.history.getRestorationDataForIdentifier(this.restorationIdentifier); 3376 } 3377 get silent() { 3378 return this.isSamePage; 3379 } 3380 start() { 3381 if (this.state == VisitState.initialized) { 3382 this.recordTimingMetric(TimingMetric.visitStart); 3383 this.state = VisitState.started; 3384 this.adapter.visitStarted(this); 3385 this.delegate.visitStarted(this); 3386 } 3387 } 3388 cancel() { 3389 if (this.state == VisitState.started) { 3390 if (this.request) { 3391 this.request.cancel(); 3392 } 3393 this.cancelRender(); 3394 this.state = VisitState.canceled; 3395 } 3396 } 3397 complete() { 3398 if (this.state == VisitState.started) { 3399 this.recordTimingMetric(TimingMetric.visitEnd); 3400 this.adapter.visitCompleted(this); 3401 this.state = VisitState.completed; 3402 this.followRedirect(); 3403 if (!this.followedRedirect) { 3404 this.delegate.visitCompleted(this); 3405 } 3406 } 3407 } 3408 fail() { 3409 if (this.state == VisitState.started) { 3410 this.state = VisitState.failed; 3411 this.adapter.visitFailed(this); 3412 this.delegate.visitCompleted(this); 3413 } 3414 } 3415 changeHistory() { 3416 if (!this.historyChanged && this.updateHistory) { 3417 const actionForHistory = this.location.href === this.referrer?.href ? "replace" : this.action; 3418 const method = getHistoryMethodForAction(actionForHistory); 3419 this.history.update(method, this.location, this.restorationIdentifier); 3420 this.historyChanged = true; 3421 } 3422 } 3423 issueRequest() { 3424 if (this.hasPreloadedResponse()) { 3425 this.simulateRequest(); 3426 } else if (this.shouldIssueRequest() && !this.request) { 3427 this.request = new FetchRequest(this, FetchMethod.get, this.location); 3428 this.request.perform(); 3429 } 3430 } 3431 simulateRequest() { 3432 if (this.response) { 3433 this.startRequest(); 3434 this.recordResponse(); 3435 this.finishRequest(); 3436 } 3437 } 3438 startRequest() { 3439 this.recordTimingMetric(TimingMetric.requestStart); 3440 this.adapter.visitRequestStarted(this); 3441 } 3442 recordResponse(response = this.response) { 3443 this.response = response; 3444 if (response) { 3445 const { statusCode } = response; 3446 if (isSuccessful(statusCode)) { 3447 this.adapter.visitRequestCompleted(this); 3448 } else { 3449 this.adapter.visitRequestFailedWithStatusCode(this, statusCode); 3450 } 3451 } 3452 } 3453 finishRequest() { 3454 this.recordTimingMetric(TimingMetric.requestEnd); 3455 this.adapter.visitRequestFinished(this); 3456 } 3457 loadResponse() { 3458 if (this.response) { 3459 const { statusCode, responseHTML } = this.response; 3460 this.render(async () => { 3461 if (this.shouldCacheSnapshot) this.cacheSnapshot(); 3462 if (this.view.renderPromise) await this.view.renderPromise; 3463 if (isSuccessful(statusCode) && responseHTML != null) { 3464 const snapshot = PageSnapshot.fromHTMLString(responseHTML); 3465 await this.renderPageSnapshot(snapshot, false); 3466 this.adapter.visitRendered(this); 3467 this.complete(); 3468 } else { 3469 await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML), this); 3470 this.adapter.visitRendered(this); 3471 this.fail(); 3472 } 3473 }); 3474 } 3475 } 3476 getCachedSnapshot() { 3477 const snapshot = this.view.getCachedSnapshotForLocation(this.location) || this.getPreloadedSnapshot(); 3478 if (snapshot && (!getAnchor(this.location) || snapshot.hasAnchor(getAnchor(this.location)))) { 3479 if (this.action == "restore" || snapshot.isPreviewable) { 3480 return snapshot; 3481 } 3482 } 3483 } 3484 getPreloadedSnapshot() { 3485 if (this.snapshotHTML) { 3486 return PageSnapshot.fromHTMLString(this.snapshotHTML); 3487 } 3488 } 3489 hasCachedSnapshot() { 3490 return this.getCachedSnapshot() != null; 3491 } 3492 loadCachedSnapshot() { 3493 const snapshot = this.getCachedSnapshot(); 3494 if (snapshot) { 3495 const isPreview = this.shouldIssueRequest(); 3496 this.render(async () => { 3497 this.cacheSnapshot(); 3498 if (this.isSamePage || this.isPageRefresh) { 3499 this.adapter.visitRendered(this); 3500 } else { 3501 if (this.view.renderPromise) await this.view.renderPromise; 3502 await this.renderPageSnapshot(snapshot, isPreview); 3503 this.adapter.visitRendered(this); 3504 if (!isPreview) { 3505 this.complete(); 3506 } 3507 } 3508 }); 3509 } 3510 } 3511 followRedirect() { 3512 if (this.redirectedToLocation && !this.followedRedirect && this.response?.redirected) { 3513 this.adapter.visitProposedToLocation(this.redirectedToLocation, { 3514 action: "replace", 3515 response: this.response, 3516 shouldCacheSnapshot: false, 3517 willRender: false 3518 }); 3519 this.followedRedirect = true; 3520 } 3521 } 3522 goToSamePageAnchor() { 3523 if (this.isSamePage) { 3524 this.render(async () => { 3525 this.cacheSnapshot(); 3526 this.performScroll(); 3527 this.changeHistory(); 3528 this.adapter.visitRendered(this); 3529 }); 3530 } 3531 } 3532 // Fetch request delegate 3533 prepareRequest(request) { 3534 if (this.acceptsStreamResponse) { 3535 request.acceptResponseType(StreamMessage.contentType); 3536 } 3537 } 3538 requestStarted() { 3539 this.startRequest(); 3540 } 3541 requestPreventedHandlingResponse(_request, _response) { 3542 } 3543 async requestSucceededWithResponse(request, response) { 3544 const responseHTML = await response.responseHTML; 3545 const { redirected, statusCode } = response; 3546 if (responseHTML == void 0) { 3547 this.recordResponse({ 3548 statusCode: SystemStatusCode.contentTypeMismatch, 3549 redirected 3550 }); 3551 } else { 3552 this.redirectedToLocation = response.redirected ? response.location : void 0; 3553 this.recordResponse({ statusCode, responseHTML, redirected }); 3554 } 3555 } 3556 async requestFailedWithResponse(request, response) { 3557 const responseHTML = await response.responseHTML; 3558 const { redirected, statusCode } = response; 3559 if (responseHTML == void 0) { 3560 this.recordResponse({ 3561 statusCode: SystemStatusCode.contentTypeMismatch, 3562 redirected 3563 }); 3564 } else { 3565 this.recordResponse({ statusCode, responseHTML, redirected }); 3566 } 3567 } 3568 requestErrored(_request, _error) { 3569 this.recordResponse({ 3570 statusCode: SystemStatusCode.networkFailure, 3571 redirected: false 3572 }); 3573 } 3574 requestFinished() { 3575 this.finishRequest(); 3576 } 3577 // Scrolling 3578 performScroll() { 3579 if (!this.scrolled && !this.view.forceReloaded && !this.view.shouldPreserveScrollPosition(this)) { 3580 if (this.action == "restore") { 3581 this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop(); 3582 } else { 3583 this.scrollToAnchor() || this.view.scrollToTop(); 3584 } 3585 if (this.isSamePage) { 3586 this.delegate.visitScrolledToSamePageLocation(this.view.lastRenderedLocation, this.location); 3587 } 3588 this.scrolled = true; 3589 } 3590 } 3591 scrollToRestoredPosition() { 3592 const { scrollPosition } = this.restorationData; 3593 if (scrollPosition) { 3594 this.view.scrollToPosition(scrollPosition); 3595 return true; 3596 } 3597 } 3598 scrollToAnchor() { 3599 const anchor = getAnchor(this.location); 3600 if (anchor != null) { 3601 this.view.scrollToAnchor(anchor); 3602 return true; 3603 } 3604 } 3605 // Instrumentation 3606 recordTimingMetric(metric) { 3607 this.timingMetrics[metric] = (/* @__PURE__ */ new Date()).getTime(); 3608 } 3609 getTimingMetrics() { 3610 return { ...this.timingMetrics }; 3611 } 3612 // Private 3613 hasPreloadedResponse() { 3614 return typeof this.response == "object"; 3615 } 3616 shouldIssueRequest() { 3617 if (this.isSamePage) { 3618 return false; 3619 } else if (this.action == "restore") { 3620 return !this.hasCachedSnapshot(); 3621 } else { 3622 return this.willRender; 3623 } 3624 } 3625 cacheSnapshot() { 3626 if (!this.snapshotCached) { 3627 this.view.cacheSnapshot(this.snapshot).then((snapshot) => snapshot && this.visitCachedSnapshot(snapshot)); 3628 this.snapshotCached = true; 3629 } 3630 } 3631 async render(callback) { 3632 this.cancelRender(); 3633 await new Promise((resolve) => { 3634 this.frame = document.visibilityState === "hidden" ? setTimeout(() => resolve(), 0) : requestAnimationFrame(() => resolve()); 3635 }); 3636 await callback(); 3637 delete this.frame; 3638 } 3639 async renderPageSnapshot(snapshot, isPreview) { 3640 await this.viewTransitioner.renderChange(this.view.shouldTransitionTo(snapshot), async () => { 3641 await this.view.renderPage(snapshot, isPreview, this.willRender, this); 3642 this.performScroll(); 3643 }); 3644 } 3645 cancelRender() { 3646 if (this.frame) { 3647 cancelAnimationFrame(this.frame); 3648 delete this.frame; 3649 } 3650 } 3651}; 3652function isSuccessful(statusCode) { 3653 return statusCode >= 200 && statusCode < 300; 3654} 3655var BrowserAdapter = class { 3656 progressBar = new ProgressBar(); 3657 constructor(session2) { 3658 this.session = session2; 3659 } 3660 visitProposedToLocation(location2, options) { 3661 if (locationIsVisitable(location2, this.navigator.rootLocation)) { 3662 this.navigator.startVisit(location2, options?.restorationIdentifier || uuid(), options); 3663 } else { 3664 window.location.href = location2.toString(); 3665 } 3666 } 3667 visitStarted(visit2) { 3668 this.location = visit2.location; 3669 visit2.loadCachedSnapshot(); 3670 visit2.issueRequest(); 3671 visit2.goToSamePageAnchor(); 3672 } 3673 visitRequestStarted(visit2) { 3674 this.progressBar.setValue(0); 3675 if (visit2.hasCachedSnapshot() || visit2.action != "restore") { 3676 this.showVisitProgressBarAfterDelay(); 3677 } else { 3678 this.showProgressBar(); 3679 } 3680 } 3681 visitRequestCompleted(visit2) { 3682 visit2.loadResponse(); 3683 } 3684 visitRequestFailedWithStatusCode(visit2, statusCode) { 3685 switch (statusCode) { 3686 case SystemStatusCode.networkFailure: 3687 case SystemStatusCode.timeoutFailure: 3688 case SystemStatusCode.contentTypeMismatch: 3689 return this.reload({ 3690 reason: "request_failed", 3691 context: { 3692 statusCode 3693 } 3694 }); 3695 default: 3696 return visit2.loadResponse(); 3697 } 3698 } 3699 visitRequestFinished(_visit) { 3700 } 3701 visitCompleted(_visit) { 3702 this.progressBar.setValue(1); 3703 this.hideVisitProgressBar(); 3704 } 3705 pageInvalidated(reason) { 3706 this.reload(reason); 3707 } 3708 visitFailed(_visit) { 3709 this.progressBar.setValue(1); 3710 this.hideVisitProgressBar(); 3711 } 3712 visitRendered(_visit) { 3713 } 3714 // Link prefetching 3715 linkPrefetchingIsEnabledForLocation(location2) { 3716 return true; 3717 } 3718 // Form Submission Delegate 3719 formSubmissionStarted(_formSubmission) { 3720 this.progressBar.setValue(0); 3721 this.showFormProgressBarAfterDelay(); 3722 } 3723 formSubmissionFinished(_formSubmission) { 3724 this.progressBar.setValue(1); 3725 this.hideFormProgressBar(); 3726 } 3727 // Private 3728 showVisitProgressBarAfterDelay() { 3729 this.visitProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay); 3730 } 3731 hideVisitProgressBar() { 3732 this.progressBar.hide(); 3733 if (this.visitProgressBarTimeout != null) { 3734 window.clearTimeout(this.visitProgressBarTimeout); 3735 delete this.visitProgressBarTimeout; 3736 } 3737 } 3738 showFormProgressBarAfterDelay() { 3739 if (this.formProgressBarTimeout == null) { 3740 this.formProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay); 3741 } 3742 } 3743 hideFormProgressBar() { 3744 this.progressBar.hide(); 3745 if (this.formProgressBarTimeout != null) { 3746 window.clearTimeout(this.formProgressBarTimeout); 3747 delete this.formProgressBarTimeout; 3748 } 3749 } 3750 showProgressBar = () => { 3751 this.progressBar.show(); 3752 }; 3753 reload(reason) { 3754 dispatch("turbo:reload", { detail: reason }); 3755 window.location.href = this.location?.toString() || window.location.href; 3756 } 3757 get navigator() { 3758 return this.session.navigator; 3759 } 3760}; 3761var CacheObserver = class { 3762 selector = "[data-turbo-temporary]"; 3763 deprecatedSelector = "[data-turbo-cache=false]"; 3764 started = false; 3765 start() { 3766 if (!this.started) { 3767 this.started = true; 3768 addEventListener("turbo:before-cache", this.removeTemporaryElements, false); 3769 } 3770 } 3771 stop() { 3772 if (this.started) { 3773 this.started = false; 3774 removeEventListener("turbo:before-cache", this.removeTemporaryElements, false); 3775 } 3776 } 3777 removeTemporaryElements = (_event) => { 3778 for (const element of this.temporaryElements) { 3779 element.remove(); 3780 } 3781 }; 3782 get temporaryElements() { 3783 return [...document.querySelectorAll(this.selector), ...this.temporaryElementsWithDeprecation]; 3784 } 3785 get temporaryElementsWithDeprecation() { 3786 const elements = document.querySelectorAll(this.deprecatedSelector); 3787 if (elements.length) { 3788 console.warn( 3789 `The ${this.deprecatedSelector} selector is deprecated and will be removed in a future version. Use ${this.selector} instead.` 3790 ); 3791 } 3792 return [...elements]; 3793 } 3794}; 3795var FrameRedirector = class { 3796 constructor(session2, element) { 3797 this.session = session2; 3798 this.element = element; 3799 this.linkInterceptor = new LinkInterceptor(this, element); 3800 this.formSubmitObserver = new FormSubmitObserver(this, element); 3801 } 3802 start() { 3803 this.linkInterceptor.start(); 3804 this.formSubmitObserver.start(); 3805 } 3806 stop() { 3807 this.linkInterceptor.stop(); 3808 this.formSubmitObserver.stop(); 3809 } 3810 // Link interceptor delegate 3811 shouldInterceptLinkClick(element, _location, _event) { 3812 return this.#shouldRedirect(element); 3813 } 3814 linkClickIntercepted(element, url, event) { 3815 const frame = this.#findFrameElement(element); 3816 if (frame) { 3817 frame.delegate.linkClickIntercepted(element, url, event); 3818 } 3819 } 3820 // Form submit observer delegate 3821 willSubmitForm(element, submitter2) { 3822 return element.closest("turbo-frame") == null && this.#shouldSubmit(element, submitter2) && this.#shouldRedirect(element, submitter2); 3823 } 3824 formSubmitted(element, submitter2) { 3825 const frame = this.#findFrameElement(element, submitter2); 3826 if (frame) { 3827 frame.delegate.formSubmitted(element, submitter2); 3828 } 3829 } 3830 #shouldSubmit(form, submitter2) { 3831 const action = getAction$1(form, submitter2); 3832 const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`); 3833 const rootLocation = expandURL(meta?.content ?? "/"); 3834 return this.#shouldRedirect(form, submitter2) && locationIsVisitable(action, rootLocation); 3835 } 3836 #shouldRedirect(element, submitter2) { 3837 const isNavigatable = element instanceof HTMLFormElement ? this.session.submissionIsNavigatable(element, submitter2) : this.session.elementIsNavigatable(element); 3838 if (isNavigatable) { 3839 const frame = this.#findFrameElement(element, submitter2); 3840 return frame ? frame != element.closest("turbo-frame") : false; 3841 } else { 3842 return false; 3843 } 3844 } 3845 #findFrameElement(element, submitter2) { 3846 const id = submitter2?.getAttribute("data-turbo-frame") || element.getAttribute("data-turbo-frame"); 3847 if (id && id != "_top") { 3848 const frame = this.element.querySelector(`#${id}:not([disabled])`); 3849 if (frame instanceof FrameElement) { 3850 return frame; 3851 } 3852 } 3853 } 3854}; 3855var History = class { 3856 location; 3857 restorationIdentifier = uuid(); 3858 restorationData = {}; 3859 started = false; 3860 pageLoaded = false; 3861 currentIndex = 0; 3862 constructor(delegate) { 3863 this.delegate = delegate; 3864 } 3865 start() { 3866 if (!this.started) { 3867 addEventListener("popstate", this.onPopState, false); 3868 addEventListener("load", this.onPageLoad, false); 3869 this.currentIndex = history.state?.turbo?.restorationIndex || 0; 3870 this.started = true; 3871 this.replace(new URL(window.location.href)); 3872 } 3873 } 3874 stop() { 3875 if (this.started) { 3876 removeEventListener("popstate", this.onPopState, false); 3877 removeEventListener("load", this.onPageLoad, false); 3878 this.started = false; 3879 } 3880 } 3881 push(location2, restorationIdentifier) { 3882 this.update(history.pushState, location2, restorationIdentifier); 3883 } 3884 replace(location2, restorationIdentifier) { 3885 this.update(history.replaceState, location2, restorationIdentifier); 3886 } 3887 update(method, location2, restorationIdentifier = uuid()) { 3888 if (method === history.pushState) ++this.currentIndex; 3889 const state = { turbo: { restorationIdentifier, restorationIndex: this.currentIndex } }; 3890 method.call(history, state, "", location2.href); 3891 this.location = location2; 3892 this.restorationIdentifier = restorationIdentifier; 3893 } 3894 // Restoration data 3895 getRestorationDataForIdentifier(restorationIdentifier) { 3896 return this.restorationData[restorationIdentifier] || {}; 3897 } 3898 updateRestorationData(additionalData) { 3899 const { restorationIdentifier } = this; 3900 const restorationData = this.restorationData[restorationIdentifier]; 3901 this.restorationData[restorationIdentifier] = { 3902 ...restorationData, 3903 ...additionalData 3904 }; 3905 } 3906 // Scroll restoration 3907 assumeControlOfScrollRestoration() { 3908 if (!this.previousScrollRestoration) { 3909 this.previousScrollRestoration = history.scrollRestoration ?? "auto"; 3910 history.scrollRestoration = "manual"; 3911 } 3912 } 3913 relinquishControlOfScrollRestoration() { 3914 if (this.previousScrollRestoration) { 3915 history.scrollRestoration = this.previousScrollRestoration; 3916 delete this.previousScrollRestoration; 3917 } 3918 } 3919 // Event handlers 3920 onPopState = (event) => { 3921 if (this.shouldHandlePopState()) { 3922 const { turbo } = event.state || {}; 3923 if (turbo) { 3924 this.location = new URL(window.location.href); 3925 const { restorationIdentifier, restorationIndex } = turbo; 3926 this.restorationIdentifier = restorationIdentifier; 3927 const direction = restorationIndex > this.currentIndex ? "forward" : "back"; 3928 this.delegate.historyPoppedToLocationWithRestorationIdentifierAndDirection(this.location, restorationIdentifier, direction); 3929 this.currentIndex = restorationIndex; 3930 } 3931 } 3932 }; 3933 onPageLoad = async (_event) => { 3934 await nextMicrotask(); 3935 this.pageLoaded = true; 3936 }; 3937 // Private 3938 shouldHandlePopState() { 3939 return this.pageIsLoaded(); 3940 } 3941 pageIsLoaded() { 3942 return this.pageLoaded || document.readyState == "complete"; 3943 } 3944}; 3945var LinkPrefetchObserver = class { 3946 started = false; 3947 #prefetchedLink = null; 3948 constructor(delegate, eventTarget) { 3949 this.delegate = delegate; 3950 this.eventTarget = eventTarget; 3951 } 3952 start() { 3953 if (this.started) return; 3954 if (this.eventTarget.readyState === "loading") { 3955 this.eventTarget.addEventListener("DOMContentLoaded", this.#enable, { once: true }); 3956 } else { 3957 this.#enable(); 3958 } 3959 } 3960 stop() { 3961 if (!this.started) return; 3962 this.eventTarget.removeEventListener("mouseenter", this.#tryToPrefetchRequest, { 3963 capture: true, 3964 passive: true 3965 }); 3966 this.eventTarget.removeEventListener("mouseleave", this.#cancelRequestIfObsolete, { 3967 capture: true, 3968 passive: true 3969 }); 3970 this.eventTarget.removeEventListener("turbo:before-fetch-request", this.#tryToUsePrefetchedRequest, true); 3971 this.started = false; 3972 } 3973 #enable = () => { 3974 this.eventTarget.addEventListener("mouseenter", this.#tryToPrefetchRequest, { 3975 capture: true, 3976 passive: true 3977 }); 3978 this.eventTarget.addEventListener("mouseleave", this.#cancelRequestIfObsolete, { 3979 capture: true, 3980 passive: true 3981 }); 3982 this.eventTarget.addEventListener("turbo:before-fetch-request", this.#tryToUsePrefetchedRequest, true); 3983 this.started = true; 3984 }; 3985 #tryToPrefetchRequest = (event) => { 3986 if (getMetaContent("turbo-prefetch") === "false") return; 3987 const target = event.target; 3988 const isLink = target.matches && target.matches("a[href]:not([target^=_]):not([download])"); 3989 if (isLink && this.#isPrefetchable(target)) { 3990 const link = target; 3991 const location2 = getLocationForLink(link); 3992 if (this.delegate.canPrefetchRequestToLocation(link, location2)) { 3993 this.#prefetchedLink = link; 3994 const fetchRequest = new FetchRequest( 3995 this, 3996 FetchMethod.get, 3997 location2, 3998 new URLSearchParams(), 3999 target 4000 ); 4001 prefetchCache.setLater(location2.toString(), fetchRequest, this.#cacheTtl); 4002 } 4003 } 4004 }; 4005 #cancelRequestIfObsolete = (event) => { 4006 if (event.target === this.#prefetchedLink) this.#cancelPrefetchRequest(); 4007 }; 4008 #cancelPrefetchRequest = () => { 4009 prefetchCache.clear(); 4010 this.#prefetchedLink = null; 4011 }; 4012 #tryToUsePrefetchedRequest = (event) => { 4013 if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "GET") { 4014 const cached = prefetchCache.get(event.detail.url.toString()); 4015 if (cached) { 4016 event.detail.fetchRequest = cached; 4017 } 4018 prefetchCache.clear(); 4019 } 4020 }; 4021 prepareRequest(request) { 4022 const link = request.target; 4023 request.headers["X-Sec-Purpose"] = "prefetch"; 4024 const turboFrame = link.closest("turbo-frame"); 4025 const turboFrameTarget = link.getAttribute("data-turbo-frame") || turboFrame?.getAttribute("target") || turboFrame?.id; 4026 if (turboFrameTarget && turboFrameTarget !== "_top") { 4027 request.headers["Turbo-Frame"] = turboFrameTarget; 4028 } 4029 } 4030 // Fetch request interface 4031 requestSucceededWithResponse() { 4032 } 4033 requestStarted(fetchRequest) { 4034 } 4035 requestErrored(fetchRequest) { 4036 } 4037 requestFinished(fetchRequest) { 4038 } 4039 requestPreventedHandlingResponse(fetchRequest, fetchResponse) { 4040 } 4041 requestFailedWithResponse(fetchRequest, fetchResponse) { 4042 } 4043 get #cacheTtl() { 4044 return Number(getMetaContent("turbo-prefetch-cache-time")) || cacheTtl; 4045 } 4046 #isPrefetchable(link) { 4047 const href = link.getAttribute("href"); 4048 if (!href) return false; 4049 if (unfetchableLink(link)) return false; 4050 if (linkToTheSamePage(link)) return false; 4051 if (linkOptsOut(link)) return false; 4052 if (nonSafeLink(link)) return false; 4053 if (eventPrevented(link)) return false; 4054 return true; 4055 } 4056}; 4057var unfetchableLink = (link) => { 4058 return link.origin !== document.location.origin || !["http:", "https:"].includes(link.protocol) || link.hasAttribute("target"); 4059}; 4060var linkToTheSamePage = (link) => { 4061 return link.pathname + link.search === document.location.pathname + document.location.search || link.href.startsWith("#"); 4062}; 4063var linkOptsOut = (link) => { 4064 if (link.getAttribute("data-turbo-prefetch") === "false") return true; 4065 if (link.getAttribute("data-turbo") === "false") return true; 4066 const turboPrefetchParent = findClosestRecursively(link, "[data-turbo-prefetch]"); 4067 if (turboPrefetchParent && turboPrefetchParent.getAttribute("data-turbo-prefetch") === "false") return true; 4068 return false; 4069}; 4070var nonSafeLink = (link) => { 4071 const turboMethod = link.getAttribute("data-turbo-method"); 4072 if (turboMethod && turboMethod.toLowerCase() !== "get") return true; 4073 if (isUJS(link)) return true; 4074 if (link.hasAttribute("data-turbo-confirm")) return true; 4075 if (link.hasAttribute("data-turbo-stream")) return true; 4076 return false; 4077}; 4078var isUJS = (link) => { 4079 return link.hasAttribute("data-remote") || link.hasAttribute("data-behavior") || link.hasAttribute("data-confirm") || link.hasAttribute("data-method"); 4080}; 4081var eventPrevented = (link) => { 4082 const event = dispatch("turbo:before-prefetch", { target: link, cancelable: true }); 4083 return event.defaultPrevented; 4084}; 4085var Navigator = class { 4086 constructor(delegate) { 4087 this.delegate = delegate; 4088 } 4089 proposeVisit(location2, options = {}) { 4090 if (this.delegate.allowsVisitingLocationWithAction(location2, options.action)) { 4091 this.delegate.visitProposedToLocation(location2, options); 4092 } 4093 } 4094 startVisit(locatable, restorationIdentifier, options = {}) { 4095 this.stop(); 4096 this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, { 4097 referrer: this.location, 4098 ...options 4099 }); 4100 this.currentVisit.start(); 4101 } 4102 submitForm(form, submitter2) { 4103 this.stop(); 4104 this.formSubmission = new FormSubmission(this, form, submitter2, true); 4105 this.formSubmission.start(); 4106 } 4107 stop() { 4108 if (this.formSubmission) { 4109 this.formSubmission.stop(); 4110 delete this.formSubmission; 4111 } 4112 if (this.currentVisit) { 4113 this.currentVisit.cancel(); 4114 delete this.currentVisit; 4115 } 4116 } 4117 get adapter() { 4118 return this.delegate.adapter; 4119 } 4120 get view() { 4121 return this.delegate.view; 4122 } 4123 get rootLocation() { 4124 return this.view.snapshot.rootLocation; 4125 } 4126 get history() { 4127 return this.delegate.history; 4128 } 4129 // Form submission delegate 4130 formSubmissionStarted(formSubmission) { 4131 if (typeof this.adapter.formSubmissionStarted === "function") { 4132 this.adapter.formSubmissionStarted(formSubmission); 4133 } 4134 } 4135 async formSubmissionSucceededWithResponse(formSubmission, fetchResponse) { 4136 if (formSubmission == this.formSubmission) { 4137 const responseHTML = await fetchResponse.responseHTML; 4138 if (responseHTML) { 4139 const shouldCacheSnapshot = formSubmission.isSafe; 4140 if (!shouldCacheSnapshot) { 4141 this.view.clearSnapshotCache(); 4142 } 4143 const { statusCode, redirected } = fetchResponse; 4144 const action = this.#getActionForFormSubmission(formSubmission, fetchResponse); 4145 const visitOptions = { 4146 action, 4147 shouldCacheSnapshot, 4148 response: { statusCode, responseHTML, redirected } 4149 }; 4150 this.proposeVisit(fetchResponse.location, visitOptions); 4151 } 4152 } 4153 } 4154 async formSubmissionFailedWithResponse(formSubmission, fetchResponse) { 4155 const responseHTML = await fetchResponse.responseHTML; 4156 if (responseHTML) { 4157 const snapshot = PageSnapshot.fromHTMLString(responseHTML); 4158 if (fetchResponse.serverError) { 4159 await this.view.renderError(snapshot, this.currentVisit); 4160 } else { 4161 await this.view.renderPage(snapshot, false, true, this.currentVisit); 4162 } 4163 if (!snapshot.shouldPreserveScrollPosition) { 4164 this.view.scrollToTop(); 4165 } 4166 this.view.clearSnapshotCache(); 4167 } 4168 } 4169 formSubmissionErrored(formSubmission, error2) { 4170 console.error(error2); 4171 } 4172 formSubmissionFinished(formSubmission) { 4173 if (typeof this.adapter.formSubmissionFinished === "function") { 4174 this.adapter.formSubmissionFinished(formSubmission); 4175 } 4176 } 4177 // Link prefetching 4178 linkPrefetchingIsEnabledForLocation(location2) { 4179 if (typeof this.adapter.linkPrefetchingIsEnabledForLocation === "function") { 4180 return this.adapter.linkPrefetchingIsEnabledForLocation(location2); 4181 } 4182 return true; 4183 } 4184 // Visit delegate 4185 visitStarted(visit2) { 4186 this.delegate.visitStarted(visit2); 4187 } 4188 visitCompleted(visit2) { 4189 this.delegate.visitCompleted(visit2); 4190 delete this.currentVisit; 4191 } 4192 locationWithActionIsSamePage(location2, action) { 4193 const anchor = getAnchor(location2); 4194 const currentAnchor = getAnchor(this.view.lastRenderedLocation); 4195 const isRestorationToTop = action === "restore" && typeof anchor === "undefined"; 4196 return action !== "replace" && getRequestURL(location2) === getRequestURL(this.view.lastRenderedLocation) && (isRestorationToTop || anchor != null && anchor !== currentAnchor); 4197 } 4198 visitScrolledToSamePageLocation(oldURL, newURL) { 4199 this.delegate.visitScrolledToSamePageLocation(oldURL, newURL); 4200 } 4201 // Visits 4202 get location() { 4203 return this.history.location; 4204 } 4205 get restorationIdentifier() { 4206 return this.history.restorationIdentifier; 4207 } 4208 #getActionForFormSubmission(formSubmission, fetchResponse) { 4209 const { submitter: submitter2, formElement } = formSubmission; 4210 return getVisitAction(submitter2, formElement) || this.#getDefaultAction(fetchResponse); 4211 } 4212 #getDefaultAction(fetchResponse) { 4213 const sameLocationRedirect = fetchResponse.redirected && fetchResponse.location.href === this.location?.href; 4214 return sameLocationRedirect ? "replace" : "advance"; 4215 } 4216}; 4217var PageStage = { 4218 initial: 0, 4219 loading: 1, 4220 interactive: 2, 4221 complete: 3 4222}; 4223var PageObserver = class { 4224 stage = PageStage.initial; 4225 started = false; 4226 constructor(delegate) { 4227 this.delegate = delegate; 4228 } 4229 start() { 4230 if (!this.started) { 4231 if (this.stage == PageStage.initial) { 4232 this.stage = PageStage.loading; 4233 } 4234 document.addEventListener("readystatechange", this.interpretReadyState, false); 4235 addEventListener("pagehide", this.pageWillUnload, false); 4236 this.started = true; 4237 } 4238 } 4239 stop() { 4240 if (this.started) { 4241 document.removeEventListener("readystatechange", this.interpretReadyState, false); 4242 removeEventListener("pagehide", this.pageWillUnload, false); 4243 this.started = false; 4244 } 4245 } 4246 interpretReadyState = () => { 4247 const { readyState } = this; 4248 if (readyState == "interactive") { 4249 this.pageIsInteractive(); 4250 } else if (readyState == "complete") { 4251 this.pageIsComplete(); 4252 } 4253 }; 4254 pageIsInteractive() { 4255 if (this.stage == PageStage.loading) { 4256 this.stage = PageStage.interactive; 4257 this.delegate.pageBecameInteractive(); 4258 } 4259 } 4260 pageIsComplete() { 4261 this.pageIsInteractive(); 4262 if (this.stage == PageStage.interactive) { 4263 this.stage = PageStage.complete; 4264 this.delegate.pageLoaded(); 4265 } 4266 } 4267 pageWillUnload = () => { 4268 this.delegate.pageWillUnload(); 4269 }; 4270 get readyState() { 4271 return document.readyState; 4272 } 4273}; 4274var ScrollObserver = class { 4275 started = false; 4276 constructor(delegate) { 4277 this.delegate = delegate; 4278 } 4279 start() { 4280 if (!this.started) { 4281 addEventListener("scroll", this.onScroll, false); 4282 this.onScroll(); 4283 this.started = true; 4284 } 4285 } 4286 stop() { 4287 if (this.started) { 4288 removeEventListener("scroll", this.onScroll, false); 4289 this.started = false; 4290 } 4291 } 4292 onScroll = () => { 4293 this.updatePosition({ x: window.pageXOffset, y: window.pageYOffset }); 4294 }; 4295 // Private 4296 updatePosition(position) { 4297 this.delegate.scrollPositionChanged(position); 4298 } 4299}; 4300var StreamMessageRenderer = class { 4301 render({ fragment }) { 4302 Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), () => { 4303 withAutofocusFromFragment(fragment, () => { 4304 withPreservedFocus(() => { 4305 document.documentElement.appendChild(fragment); 4306 }); 4307 }); 4308 }); 4309 } 4310 // Bardo delegate 4311 enteringBardo(currentPermanentElement, newPermanentElement) { 4312 newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true)); 4313 } 4314 leavingBardo() { 4315 } 4316}; 4317function getPermanentElementMapForFragment(fragment) { 4318 const permanentElementsInDocument = queryPermanentElementsAll(document.documentElement); 4319 const permanentElementMap = {}; 4320 for (const permanentElementInDocument of permanentElementsInDocument) { 4321 const { id } = permanentElementInDocument; 4322 for (const streamElement of fragment.querySelectorAll("turbo-stream")) { 4323 const elementInStream = getPermanentElementById(streamElement.templateElement.content, id); 4324 if (elementInStream) { 4325 permanentElementMap[id] = [permanentElementInDocument, elementInStream]; 4326 } 4327 } 4328 } 4329 return permanentElementMap; 4330} 4331async function withAutofocusFromFragment(fragment, callback) { 4332 const generatedID = `turbo-stream-autofocus-${uuid()}`; 4333 const turboStreams = fragment.querySelectorAll("turbo-stream"); 4334 const elementWithAutofocus = firstAutofocusableElementInStreams(turboStreams); 4335 let willAutofocusId = null; 4336 if (elementWithAutofocus) { 4337 if (elementWithAutofocus.id) { 4338 willAutofocusId = elementWithAutofocus.id; 4339 } else { 4340 willAutofocusId = generatedID; 4341 } 4342 elementWithAutofocus.id = willAutofocusId; 4343 } 4344 callback(); 4345 await nextRepaint(); 4346 const hasNoActiveElement = document.activeElement == null || document.activeElement == document.body; 4347 if (hasNoActiveElement && willAutofocusId) { 4348 const elementToAutofocus = document.getElementById(willAutofocusId); 4349 if (elementIsFocusable(elementToAutofocus)) { 4350 elementToAutofocus.focus(); 4351 } 4352 if (elementToAutofocus && elementToAutofocus.id == generatedID) { 4353 elementToAutofocus.removeAttribute("id"); 4354 } 4355 } 4356} 4357async function withPreservedFocus(callback) { 4358 const [activeElementBeforeRender, activeElementAfterRender] = await around(callback, () => document.activeElement); 4359 const restoreFocusTo = activeElementBeforeRender && activeElementBeforeRender.id; 4360 if (restoreFocusTo) { 4361 const elementToFocus = document.getElementById(restoreFocusTo); 4362 if (elementIsFocusable(elementToFocus) && elementToFocus != activeElementAfterRender) { 4363 elementToFocus.focus(); 4364 } 4365 } 4366} 4367function firstAutofocusableElementInStreams(nodeListOfStreamElements) { 4368 for (const streamElement of nodeListOfStreamElements) { 4369 const elementWithAutofocus = queryAutofocusableElement(streamElement.templateElement.content); 4370 if (elementWithAutofocus) return elementWithAutofocus; 4371 } 4372 return null; 4373} 4374var StreamObserver = class { 4375 sources = /* @__PURE__ */ new Set(); 4376 #started = false; 4377 constructor(delegate) { 4378 this.delegate = delegate; 4379 } 4380 start() { 4381 if (!this.#started) { 4382 this.#started = true; 4383 addEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false); 4384 } 4385 } 4386 stop() { 4387 if (this.#started) { 4388 this.#started = false; 4389 removeEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false); 4390 } 4391 } 4392 connectStreamSource(source) { 4393 if (!this.streamSourceIsConnected(source)) { 4394 this.sources.add(source); 4395 source.addEventListener("message", this.receiveMessageEvent, false); 4396 } 4397 } 4398 disconnectStreamSource(source) { 4399 if (this.streamSourceIsConnected(source)) { 4400 this.sources.delete(source); 4401 source.removeEventListener("message", this.receiveMessageEvent, false); 4402 } 4403 } 4404 streamSourceIsConnected(source) { 4405 return this.sources.has(source); 4406 } 4407 inspectFetchResponse = (event) => { 4408 const response = fetchResponseFromEvent(event); 4409 if (response && fetchResponseIsStream(response)) { 4410 event.preventDefault(); 4411 this.receiveMessageResponse(response); 4412 } 4413 }; 4414 receiveMessageEvent = (event) => { 4415 if (this.#started && typeof event.data == "string") { 4416 this.receiveMessageHTML(event.data); 4417 } 4418 }; 4419 async receiveMessageResponse(response) { 4420 const html = await response.responseHTML; 4421 if (html) { 4422 this.receiveMessageHTML(html); 4423 } 4424 } 4425 receiveMessageHTML(html) { 4426 this.delegate.receivedMessageFromStream(StreamMessage.wrap(html)); 4427 } 4428}; 4429function fetchResponseFromEvent(event) { 4430 const fetchResponse = event.detail?.fetchResponse; 4431 if (fetchResponse instanceof FetchResponse) { 4432 return fetchResponse; 4433 } 4434} 4435function fetchResponseIsStream(response) { 4436 const contentType = response.contentType ?? ""; 4437 return contentType.startsWith(StreamMessage.contentType); 4438} 4439var ErrorRenderer = class extends Renderer { 4440 static renderElement(currentElement, newElement) { 4441 const { documentElement, body } = document; 4442 documentElement.replaceChild(newElement, body); 4443 } 4444 async render() { 4445 this.replaceHeadAndBody(); 4446 this.activateScriptElements(); 4447 } 4448 replaceHeadAndBody() { 4449 const { documentElement, head } = document; 4450 documentElement.replaceChild(this.newHead, head); 4451 this.renderElement(this.currentElement, this.newElement); 4452 } 4453 activateScriptElements() { 4454 for (const replaceableElement of this.scriptElements) { 4455 const parentNode = replaceableElement.parentNode; 4456 if (parentNode) { 4457 const element = activateScriptElement(replaceableElement); 4458 parentNode.replaceChild(element, replaceableElement); 4459 } 4460 } 4461 } 4462 get newHead() { 4463 return this.newSnapshot.headSnapshot.element; 4464 } 4465 get scriptElements() { 4466 return document.documentElement.querySelectorAll("script"); 4467 } 4468}; 4469var PageRenderer = class extends Renderer { 4470 static renderElement(currentElement, newElement) { 4471 if (document.body && newElement instanceof HTMLBodyElement) { 4472 document.body.replaceWith(newElement); 4473 } else { 4474 document.documentElement.appendChild(newElement); 4475 } 4476 } 4477 get shouldRender() { 4478 return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical; 4479 } 4480 get reloadReason() { 4481 if (!this.newSnapshot.isVisitable) { 4482 return { 4483 reason: "turbo_visit_control_is_reload" 4484 }; 4485 } 4486 if (!this.trackedElementsAreIdentical) { 4487 return { 4488 reason: "tracked_element_mismatch" 4489 }; 4490 } 4491 } 4492 async prepareToRender() { 4493 this.#setLanguage(); 4494 await this.mergeHead(); 4495 } 4496 async render() { 4497 if (this.willRender) { 4498 await this.replaceBody(); 4499 } 4500 } 4501 finishRendering() { 4502 super.finishRendering(); 4503 if (!this.isPreview) { 4504 this.focusFirstAutofocusableElement(); 4505 } 4506 } 4507 get currentHeadSnapshot() { 4508 return this.currentSnapshot.headSnapshot; 4509 } 4510 get newHeadSnapshot() { 4511 return this.newSnapshot.headSnapshot; 4512 } 4513 get newElement() { 4514 return this.newSnapshot.element; 4515 } 4516 #setLanguage() { 4517 const { documentElement } = this.currentSnapshot; 4518 const { lang } = this.newSnapshot; 4519 if (lang) { 4520 documentElement.setAttribute("lang", lang); 4521 } else { 4522 documentElement.removeAttribute("lang"); 4523 } 4524 } 4525 async mergeHead() { 4526 const mergedHeadElements = this.mergeProvisionalElements(); 4527 const newStylesheetElements = this.copyNewHeadStylesheetElements(); 4528 this.copyNewHeadScriptElements(); 4529 await mergedHeadElements; 4530 await newStylesheetElements; 4531 if (this.willRender) { 4532 this.removeUnusedDynamicStylesheetElements(); 4533 } 4534 } 4535 async replaceBody() { 4536 await this.preservingPermanentElements(async () => { 4537 this.activateNewBody(); 4538 await this.assignNewBody(); 4539 }); 4540 } 4541 get trackedElementsAreIdentical() { 4542 return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature; 4543 } 4544 async copyNewHeadStylesheetElements() { 4545 const loadingElements = []; 4546 for (const element of this.newHeadStylesheetElements) { 4547 loadingElements.push(waitForLoad(element)); 4548 document.head.appendChild(element); 4549 } 4550 await Promise.all(loadingElements); 4551 } 4552 copyNewHeadScriptElements() { 4553 for (const element of this.newHeadScriptElements) { 4554 document.head.appendChild(activateScriptElement(element)); 4555 } 4556 } 4557 removeUnusedDynamicStylesheetElements() { 4558 for (const element of this.unusedDynamicStylesheetElements) { 4559 document.head.removeChild(element); 4560 } 4561 } 4562 async mergeProvisionalElements() { 4563 const newHeadElements = [...this.newHeadProvisionalElements]; 4564 for (const element of this.currentHeadProvisionalElements) { 4565 if (!this.isCurrentElementInElementList(element, newHeadElements)) { 4566 document.head.removeChild(element); 4567 } 4568 } 4569 for (const element of newHeadElements) { 4570 document.head.appendChild(element); 4571 } 4572 } 4573 isCurrentElementInElementList(element, elementList) { 4574 for (const [index, newElement] of elementList.entries()) { 4575 if (element.tagName == "TITLE") { 4576 if (newElement.tagName != "TITLE") { 4577 continue; 4578 } 4579 if (element.innerHTML == newElement.innerHTML) { 4580 elementList.splice(index, 1); 4581 return true; 4582 } 4583 } 4584 if (newElement.isEqualNode(element)) { 4585 elementList.splice(index, 1); 4586 return true; 4587 } 4588 } 4589 return false; 4590 } 4591 removeCurrentHeadProvisionalElements() { 4592 for (const element of this.currentHeadProvisionalElements) { 4593 document.head.removeChild(element); 4594 } 4595 } 4596 copyNewHeadProvisionalElements() { 4597 for (const element of this.newHeadProvisionalElements) { 4598 document.head.appendChild(element); 4599 } 4600 } 4601 activateNewBody() { 4602 document.adoptNode(this.newElement); 4603 this.activateNewBodyScriptElements(); 4604 } 4605 activateNewBodyScriptElements() { 4606 for (const inertScriptElement of this.newBodyScriptElements) { 4607 const activatedScriptElement = activateScriptElement(inertScriptElement); 4608 inertScriptElement.replaceWith(activatedScriptElement); 4609 } 4610 } 4611 async assignNewBody() { 4612 await this.renderElement(this.currentElement, this.newElement); 4613 } 4614 get unusedDynamicStylesheetElements() { 4615 return this.oldHeadStylesheetElements.filter((element) => { 4616 return element.getAttribute("data-turbo-track") === "dynamic"; 4617 }); 4618 } 4619 get oldHeadStylesheetElements() { 4620 return this.currentHeadSnapshot.getStylesheetElementsNotInSnapshot(this.newHeadSnapshot); 4621 } 4622 get newHeadStylesheetElements() { 4623 return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot); 4624 } 4625 get newHeadScriptElements() { 4626 return this.newHeadSnapshot.getScriptElementsNotInSnapshot(this.currentHeadSnapshot); 4627 } 4628 get currentHeadProvisionalElements() { 4629 return this.currentHeadSnapshot.provisionalElements; 4630 } 4631 get newHeadProvisionalElements() { 4632 return this.newHeadSnapshot.provisionalElements; 4633 } 4634 get newBodyScriptElements() { 4635 return this.newElement.querySelectorAll("script"); 4636 } 4637}; 4638var MorphingPageRenderer = class extends PageRenderer { 4639 static renderElement(currentElement, newElement) { 4640 morphElements(currentElement, newElement, { 4641 callbacks: { 4642 beforeNodeMorphed: (element) => !canRefreshFrame(element) 4643 } 4644 }); 4645 for (const frame of currentElement.querySelectorAll("turbo-frame")) { 4646 if (canRefreshFrame(frame)) frame.reload(); 4647 } 4648 dispatch("turbo:morph", { detail: { currentElement, newElement } }); 4649 } 4650 async preservingPermanentElements(callback) { 4651 return await callback(); 4652 } 4653 get renderMethod() { 4654 return "morph"; 4655 } 4656 get shouldAutofocus() { 4657 return false; 4658 } 4659}; 4660function canRefreshFrame(frame) { 4661 return frame instanceof FrameElement && frame.src && frame.refresh === "morph" && !frame.closest("[data-turbo-permanent]"); 4662} 4663var SnapshotCache = class { 4664 keys = []; 4665 snapshots = {}; 4666 constructor(size) { 4667 this.size = size; 4668 } 4669 has(location2) { 4670 return toCacheKey(location2) in this.snapshots; 4671 } 4672 get(location2) { 4673 if (this.has(location2)) { 4674 const snapshot = this.read(location2); 4675 this.touch(location2); 4676 return snapshot; 4677 } 4678 } 4679 put(location2, snapshot) { 4680 this.write(location2, snapshot); 4681 this.touch(location2); 4682 return snapshot; 4683 } 4684 clear() { 4685 this.snapshots = {}; 4686 } 4687 // Private 4688 read(location2) { 4689 return this.snapshots[toCacheKey(location2)]; 4690 } 4691 write(location2, snapshot) { 4692 this.snapshots[toCacheKey(location2)] = snapshot; 4693 } 4694 touch(location2) { 4695 const key = toCacheKey(location2); 4696 const index = this.keys.indexOf(key); 4697 if (index > -1) this.keys.splice(index, 1); 4698 this.keys.unshift(key); 4699 this.trim(); 4700 } 4701 trim() { 4702 for (const key of this.keys.splice(this.size)) { 4703 delete this.snapshots[key]; 4704 } 4705 } 4706}; 4707var PageView = class extends View { 4708 snapshotCache = new SnapshotCache(10); 4709 lastRenderedLocation = new URL(location.href); 4710 forceReloaded = false; 4711 shouldTransitionTo(newSnapshot) { 4712 return this.snapshot.prefersViewTransitions && newSnapshot.prefersViewTransitions; 4713 } 4714 renderPage(snapshot, isPreview = false, willRender = true, visit2) { 4715 const shouldMorphPage = this.isPageRefresh(visit2) && this.snapshot.shouldMorphPage; 4716 const rendererClass = shouldMorphPage ? MorphingPageRenderer : PageRenderer; 4717 const renderer = new rendererClass(this.snapshot, snapshot, isPreview, willRender); 4718 if (!renderer.shouldRender) { 4719 this.forceReloaded = true; 4720 } else { 4721 visit2?.changeHistory(); 4722 } 4723 return this.render(renderer); 4724 } 4725 renderError(snapshot, visit2) { 4726 visit2?.changeHistory(); 4727 const renderer = new ErrorRenderer(this.snapshot, snapshot, false); 4728 return this.render(renderer); 4729 } 4730 clearSnapshotCache() { 4731 this.snapshotCache.clear(); 4732 } 4733 async cacheSnapshot(snapshot = this.snapshot) { 4734 if (snapshot.isCacheable) { 4735 this.delegate.viewWillCacheSnapshot(); 4736 const { lastRenderedLocation: location2 } = this; 4737 await nextEventLoopTick(); 4738 const cachedSnapshot = snapshot.clone(); 4739 this.snapshotCache.put(location2, cachedSnapshot); 4740 return cachedSnapshot; 4741 } 4742 } 4743 getCachedSnapshotForLocation(location2) { 4744 return this.snapshotCache.get(location2); 4745 } 4746 isPageRefresh(visit2) { 4747 return !visit2 || this.lastRenderedLocation.pathname === visit2.location.pathname && visit2.action === "replace"; 4748 } 4749 shouldPreserveScrollPosition(visit2) { 4750 return this.isPageRefresh(visit2) && this.snapshot.shouldPreserveScrollPosition; 4751 } 4752 get snapshot() { 4753 return PageSnapshot.fromElement(this.element); 4754 } 4755}; 4756var Preloader = class { 4757 selector = "a[data-turbo-preload]"; 4758 constructor(delegate, snapshotCache) { 4759 this.delegate = delegate; 4760 this.snapshotCache = snapshotCache; 4761 } 4762 start() { 4763 if (document.readyState === "loading") { 4764 document.addEventListener("DOMContentLoaded", this.#preloadAll); 4765 } else { 4766 this.preloadOnLoadLinksForView(document.body); 4767 } 4768 } 4769 stop() { 4770 document.removeEventListener("DOMContentLoaded", this.#preloadAll); 4771 } 4772 preloadOnLoadLinksForView(element) { 4773 for (const link of element.querySelectorAll(this.selector)) { 4774 if (this.delegate.shouldPreloadLink(link)) { 4775 this.preloadURL(link); 4776 } 4777 } 4778 } 4779 async preloadURL(link) { 4780 const location2 = new URL(link.href); 4781 if (this.snapshotCache.has(location2)) { 4782 return; 4783 } 4784 const fetchRequest = new FetchRequest(this, FetchMethod.get, location2, new URLSearchParams(), link); 4785 await fetchRequest.perform(); 4786 } 4787 // Fetch request delegate 4788 prepareRequest(fetchRequest) { 4789 fetchRequest.headers["X-Sec-Purpose"] = "prefetch"; 4790 } 4791 async requestSucceededWithResponse(fetchRequest, fetchResponse) { 4792 try { 4793 const responseHTML = await fetchResponse.responseHTML; 4794 const snapshot = PageSnapshot.fromHTMLString(responseHTML); 4795 this.snapshotCache.put(fetchRequest.url, snapshot); 4796 } catch (_) { 4797 } 4798 } 4799 requestStarted(fetchRequest) { 4800 } 4801 requestErrored(fetchRequest) { 4802 } 4803 requestFinished(fetchRequest) { 4804 } 4805 requestPreventedHandlingResponse(fetchRequest, fetchResponse) { 4806 } 4807 requestFailedWithResponse(fetchRequest, fetchResponse) { 4808 } 4809 #preloadAll = () => { 4810 this.preloadOnLoadLinksForView(document.body); 4811 }; 4812}; 4813var Cache = class { 4814 constructor(session2) { 4815 this.session = session2; 4816 } 4817 clear() { 4818 this.session.clearCache(); 4819 } 4820 resetCacheControl() { 4821 this.#setCacheControl(""); 4822 } 4823 exemptPageFromCache() { 4824 this.#setCacheControl("no-cache"); 4825 } 4826 exemptPageFromPreview() { 4827 this.#setCacheControl("no-preview"); 4828 } 4829 #setCacheControl(value) { 4830 setMetaContent("turbo-cache-control", value); 4831 } 4832}; 4833var Session = class { 4834 navigator = new Navigator(this); 4835 history = new History(this); 4836 view = new PageView(this, document.documentElement); 4837 adapter = new BrowserAdapter(this); 4838 pageObserver = new PageObserver(this); 4839 cacheObserver = new CacheObserver(); 4840 linkPrefetchObserver = new LinkPrefetchObserver(this, document); 4841 linkClickObserver = new LinkClickObserver(this, window); 4842 formSubmitObserver = new FormSubmitObserver(this, document); 4843 scrollObserver = new ScrollObserver(this); 4844 streamObserver = new StreamObserver(this); 4845 formLinkClickObserver = new FormLinkClickObserver(this, document.documentElement); 4846 frameRedirector = new FrameRedirector(this, document.documentElement); 4847 streamMessageRenderer = new StreamMessageRenderer(); 4848 cache = new Cache(this); 4849 enabled = true; 4850 started = false; 4851 #pageRefreshDebouncePeriod = 150; 4852 constructor(recentRequests2) { 4853 this.recentRequests = recentRequests2; 4854 this.preloader = new Preloader(this, this.view.snapshotCache); 4855 this.debouncedRefresh = this.refresh; 4856 this.pageRefreshDebouncePeriod = this.pageRefreshDebouncePeriod; 4857 } 4858 start() { 4859 if (!this.started) { 4860 this.pageObserver.start(); 4861 this.cacheObserver.start(); 4862 this.linkPrefetchObserver.start(); 4863 this.formLinkClickObserver.start(); 4864 this.linkClickObserver.start(); 4865 this.formSubmitObserver.start(); 4866 this.scrollObserver.start(); 4867 this.streamObserver.start(); 4868 this.frameRedirector.start(); 4869 this.history.start(); 4870 this.preloader.start(); 4871 this.started = true; 4872 this.enabled = true; 4873 } 4874 } 4875 disable() { 4876 this.enabled = false; 4877 } 4878 stop() { 4879 if (this.started) { 4880 this.pageObserver.stop(); 4881 this.cacheObserver.stop(); 4882 this.linkPrefetchObserver.stop(); 4883 this.formLinkClickObserver.stop(); 4884 this.linkClickObserver.stop(); 4885 this.formSubmitObserver.stop(); 4886 this.scrollObserver.stop(); 4887 this.streamObserver.stop(); 4888 this.frameRedirector.stop(); 4889 this.history.stop(); 4890 this.preloader.stop(); 4891 this.started = false; 4892 } 4893 } 4894 registerAdapter(adapter) { 4895 this.adapter = adapter; 4896 } 4897 visit(location2, options = {}) { 4898 const frameElement = options.frame ? document.getElementById(options.frame) : null; 4899 if (frameElement instanceof FrameElement) { 4900 const action = options.action || getVisitAction(frameElement); 4901 frameElement.delegate.proposeVisitIfNavigatedWithAction(frameElement, action); 4902 frameElement.src = location2.toString(); 4903 } else { 4904 this.navigator.proposeVisit(expandURL(location2), options); 4905 } 4906 } 4907 refresh(url, requestId) { 4908 const isRecentRequest = requestId && this.recentRequests.has(requestId); 4909 const isCurrentUrl = url === document.baseURI; 4910 if (!isRecentRequest && !this.navigator.currentVisit && isCurrentUrl) { 4911 this.visit(url, { action: "replace", shouldCacheSnapshot: false }); 4912 } 4913 } 4914 connectStreamSource(source) { 4915 this.streamObserver.connectStreamSource(source); 4916 } 4917 disconnectStreamSource(source) { 4918 this.streamObserver.disconnectStreamSource(source); 4919 } 4920 renderStreamMessage(message) { 4921 this.streamMessageRenderer.render(StreamMessage.wrap(message)); 4922 } 4923 clearCache() { 4924 this.view.clearSnapshotCache(); 4925 } 4926 setProgressBarDelay(delay) { 4927 console.warn( 4928 "Please replace `session.setProgressBarDelay(delay)` with `session.progressBarDelay = delay`. The function is deprecated and will be removed in a future version of Turbo.`" 4929 ); 4930 this.progressBarDelay = delay; 4931 } 4932 set progressBarDelay(delay) { 4933 config.drive.progressBarDelay = delay; 4934 } 4935 get progressBarDelay() { 4936 return config.drive.progressBarDelay; 4937 } 4938 set drive(value) { 4939 config.drive.enabled = value; 4940 } 4941 get drive() { 4942 return config.drive.enabled; 4943 } 4944 set formMode(value) { 4945 config.forms.mode = value; 4946 } 4947 get formMode() { 4948 return config.forms.mode; 4949 } 4950 get location() { 4951 return this.history.location; 4952 } 4953 get restorationIdentifier() { 4954 return this.history.restorationIdentifier; 4955 } 4956 get pageRefreshDebouncePeriod() { 4957 return this.#pageRefreshDebouncePeriod; 4958 } 4959 set pageRefreshDebouncePeriod(value) { 4960 this.refresh = debounce(this.debouncedRefresh.bind(this), value); 4961 this.#pageRefreshDebouncePeriod = value; 4962 } 4963 // Preloader delegate 4964 shouldPreloadLink(element) { 4965 const isUnsafe = element.hasAttribute("data-turbo-method"); 4966 const isStream = element.hasAttribute("data-turbo-stream"); 4967 const frameTarget = element.getAttribute("data-turbo-frame"); 4968 const frame = frameTarget == "_top" ? null : document.getElementById(frameTarget) || findClosestRecursively(element, "turbo-frame:not([disabled])"); 4969 if (isUnsafe || isStream || frame instanceof FrameElement) { 4970 return false; 4971 } else { 4972 const location2 = new URL(element.href); 4973 return this.elementIsNavigatable(element) && locationIsVisitable(location2, this.snapshot.rootLocation); 4974 } 4975 } 4976 // History delegate 4977 historyPoppedToLocationWithRestorationIdentifierAndDirection(location2, restorationIdentifier, direction) { 4978 if (this.enabled) { 4979 this.navigator.startVisit(location2, restorationIdentifier, { 4980 action: "restore", 4981 historyChanged: true, 4982 direction 4983 }); 4984 } else { 4985 this.adapter.pageInvalidated({ 4986 reason: "turbo_disabled" 4987 }); 4988 } 4989 } 4990 // Scroll observer delegate 4991 scrollPositionChanged(position) { 4992 this.history.updateRestorationData({ scrollPosition: position }); 4993 } 4994 // Form click observer delegate 4995 willSubmitFormLinkToLocation(link, location2) { 4996 return this.elementIsNavigatable(link) && locationIsVisitable(location2, this.snapshot.rootLocation); 4997 } 4998 submittedFormLinkToLocation() { 4999 } 5000 // Link hover observer delegate 5001 canPrefetchRequestToLocation(link, location2) { 5002 return this.elementIsNavigatable(link) && locationIsVisitable(location2, this.snapshot.rootLocation) && this.navigator.linkPrefetchingIsEnabledForLocation(location2); 5003 } 5004 // Link click observer delegate 5005 willFollowLinkToLocation(link, location2, event) { 5006 return this.elementIsNavigatable(link) && locationIsVisitable(location2, this.snapshot.rootLocation) && this.applicationAllowsFollowingLinkToLocation(link, location2, event); 5007 } 5008 followedLinkToLocation(link, location2) { 5009 const action = this.getActionForLink(link); 5010 const acceptsStreamResponse = link.hasAttribute("data-turbo-stream"); 5011 this.visit(location2.href, { action, acceptsStreamResponse }); 5012 } 5013 // Navigator delegate 5014 allowsVisitingLocationWithAction(location2, action) { 5015 return this.locationWithActionIsSamePage(location2, action) || this.applicationAllowsVisitingLocation(location2); 5016 } 5017 visitProposedToLocation(location2, options) { 5018 extendURLWithDeprecatedProperties(location2); 5019 this.adapter.visitProposedToLocation(location2, options); 5020 } 5021 // Visit delegate 5022 visitStarted(visit2) { 5023 if (!visit2.acceptsStreamResponse) { 5024 markAsBusy(document.documentElement); 5025 this.view.markVisitDirection(visit2.direction); 5026 } 5027 extendURLWithDeprecatedProperties(visit2.location); 5028 if (!visit2.silent) { 5029 this.notifyApplicationAfterVisitingLocation(visit2.location, visit2.action); 5030 } 5031 } 5032 visitCompleted(visit2) { 5033 this.view.unmarkVisitDirection(); 5034 clearBusyState(document.documentElement); 5035 this.notifyApplicationAfterPageLoad(visit2.getTimingMetrics()); 5036 } 5037 locationWithActionIsSamePage(location2, action) { 5038 return this.navigator.locationWithActionIsSamePage(location2, action); 5039 } 5040 visitScrolledToSamePageLocation(oldURL, newURL) { 5041 this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL); 5042 } 5043 // Form submit observer delegate 5044 willSubmitForm(form, submitter2) { 5045 const action = getAction$1(form, submitter2); 5046 return this.submissionIsNavigatable(form, submitter2) && locationIsVisitable(expandURL(action), this.snapshot.rootLocation); 5047 } 5048 formSubmitted(form, submitter2) { 5049 this.navigator.submitForm(form, submitter2); 5050 } 5051 // Page observer delegate 5052 pageBecameInteractive() { 5053 this.view.lastRenderedLocation = this.location; 5054 this.notifyApplicationAfterPageLoad(); 5055 } 5056 pageLoaded() { 5057 this.history.assumeControlOfScrollRestoration(); 5058 } 5059 pageWillUnload() { 5060 this.history.relinquishControlOfScrollRestoration(); 5061 } 5062 // Stream observer delegate 5063 receivedMessageFromStream(message) { 5064 this.renderStreamMessage(message); 5065 } 5066 // Page view delegate 5067 viewWillCacheSnapshot() { 5068 if (!this.navigator.currentVisit?.silent) { 5069 this.notifyApplicationBeforeCachingSnapshot(); 5070 } 5071 } 5072 allowsImmediateRender({ element }, options) { 5073 const event = this.notifyApplicationBeforeRender(element, options); 5074 const { 5075 defaultPrevented, 5076 detail: { render } 5077 } = event; 5078 if (this.view.renderer && render) { 5079 this.view.renderer.renderElement = render; 5080 } 5081 return !defaultPrevented; 5082 } 5083 viewRenderedSnapshot(_snapshot, _isPreview, renderMethod) { 5084 this.view.lastRenderedLocation = this.history.location; 5085 this.notifyApplicationAfterRender(renderMethod); 5086 } 5087 preloadOnLoadLinksForView(element) { 5088 this.preloader.preloadOnLoadLinksForView(element); 5089 } 5090 viewInvalidated(reason) { 5091 this.adapter.pageInvalidated(reason); 5092 } 5093 // Frame element 5094 frameLoaded(frame) { 5095 this.notifyApplicationAfterFrameLoad(frame); 5096 } 5097 frameRendered(fetchResponse, frame) { 5098 this.notifyApplicationAfterFrameRender(fetchResponse, frame); 5099 } 5100 // Application events 5101 applicationAllowsFollowingLinkToLocation(link, location2, ev) { 5102 const event = this.notifyApplicationAfterClickingLinkToLocation(link, location2, ev); 5103 return !event.defaultPrevented; 5104 } 5105 applicationAllowsVisitingLocation(location2) { 5106 const event = this.notifyApplicationBeforeVisitingLocation(location2); 5107 return !event.defaultPrevented; 5108 } 5109 notifyApplicationAfterClickingLinkToLocation(link, location2, event) { 5110 return dispatch("turbo:click", { 5111 target: link, 5112 detail: { url: location2.href, originalEvent: event }, 5113 cancelable: true 5114 }); 5115 } 5116 notifyApplicationBeforeVisitingLocation(location2) { 5117 return dispatch("turbo:before-visit", { 5118 detail: { url: location2.href }, 5119 cancelable: true 5120 }); 5121 } 5122 notifyApplicationAfterVisitingLocation(location2, action) { 5123 return dispatch("turbo:visit", { detail: { url: location2.href, action } }); 5124 } 5125 notifyApplicationBeforeCachingSnapshot() { 5126 return dispatch("turbo:before-cache"); 5127 } 5128 notifyApplicationBeforeRender(newBody, options) { 5129 return dispatch("turbo:before-render", { 5130 detail: { newBody, ...options }, 5131 cancelable: true 5132 }); 5133 } 5134 notifyApplicationAfterRender(renderMethod) { 5135 return dispatch("turbo:render", { detail: { renderMethod } }); 5136 } 5137 notifyApplicationAfterPageLoad(timing = {}) { 5138 return dispatch("turbo:load", { 5139 detail: { url: this.location.href, timing } 5140 }); 5141 } 5142 notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) { 5143 dispatchEvent( 5144 new HashChangeEvent("hashchange", { 5145 oldURL: oldURL.toString(), 5146 newURL: newURL.toString() 5147 }) 5148 ); 5149 } 5150 notifyApplicationAfterFrameLoad(frame) { 5151 return dispatch("turbo:frame-load", { target: frame }); 5152 } 5153 notifyApplicationAfterFrameRender(fetchResponse, frame) { 5154 return dispatch("turbo:frame-render", { 5155 detail: { fetchResponse }, 5156 target: frame, 5157 cancelable: true 5158 }); 5159 } 5160 // Helpers 5161 submissionIsNavigatable(form, submitter2) { 5162 if (config.forms.mode == "off") { 5163 return false; 5164 } else { 5165 const submitterIsNavigatable = submitter2 ? this.elementIsNavigatable(submitter2) : true; 5166 if (config.forms.mode == "optin") { 5167 return submitterIsNavigatable && form.closest('[data-turbo="true"]') != null; 5168 } else { 5169 return submitterIsNavigatable && this.elementIsNavigatable(form); 5170 } 5171 } 5172 } 5173 elementIsNavigatable(element) { 5174 const container = findClosestRecursively(element, "[data-turbo]"); 5175 const withinFrame = findClosestRecursively(element, "turbo-frame"); 5176 if (config.drive.enabled || withinFrame) { 5177 if (container) { 5178 return container.getAttribute("data-turbo") != "false"; 5179 } else { 5180 return true; 5181 } 5182 } else { 5183 if (container) { 5184 return container.getAttribute("data-turbo") == "true"; 5185 } else { 5186 return false; 5187 } 5188 } 5189 } 5190 // Private 5191 getActionForLink(link) { 5192 return getVisitAction(link) || "advance"; 5193 } 5194 get snapshot() { 5195 return this.view.snapshot; 5196 } 5197}; 5198function extendURLWithDeprecatedProperties(url) { 5199 Object.defineProperties(url, deprecatedLocationPropertyDescriptors); 5200} 5201var deprecatedLocationPropertyDescriptors = { 5202 absoluteURL: { 5203 get() { 5204 return this.toString(); 5205 } 5206 } 5207}; 5208var session = new Session(recentRequests); 5209var { cache, navigator: navigator$1 } = session; 5210function start() { 5211 session.start(); 5212} 5213function registerAdapter(adapter) { 5214 session.registerAdapter(adapter); 5215} 5216function visit(location2, options) { 5217 session.visit(location2, options); 5218} 5219function connectStreamSource(source) { 5220 session.connectStreamSource(source); 5221} 5222function disconnectStreamSource(source) { 5223 session.disconnectStreamSource(source); 5224} 5225function renderStreamMessage(message) { 5226 session.renderStreamMessage(message); 5227} 5228function clearCache() { 5229 console.warn( 5230 "Please replace `Turbo.clearCache()` with `Turbo.cache.clear()`. The top-level function is deprecated and will be removed in a future version of Turbo.`" 5231 ); 5232 session.clearCache(); 5233} 5234function setProgressBarDelay(delay) { 5235 console.warn( 5236 "Please replace `Turbo.setProgressBarDelay(delay)` with `Turbo.config.drive.progressBarDelay = delay`. The top-level function is deprecated and will be removed in a future version of Turbo.`" 5237 ); 5238 config.drive.progressBarDelay = delay; 5239} 5240function setConfirmMethod(confirmMethod) { 5241 console.warn( 5242 "Please replace `Turbo.setConfirmMethod(confirmMethod)` with `Turbo.config.forms.confirm = confirmMethod`. The top-level function is deprecated and will be removed in a future version of Turbo.`" 5243 ); 5244 config.forms.confirm = confirmMethod; 5245} 5246function setFormMode(mode) { 5247 console.warn( 5248 "Please replace `Turbo.setFormMode(mode)` with `Turbo.config.forms.mode = mode`. The top-level function is deprecated and will be removed in a future version of Turbo.`" 5249 ); 5250 config.forms.mode = mode; 5251} 5252var Turbo2 = /* @__PURE__ */ Object.freeze({ 5253 __proto__: null, 5254 navigator: navigator$1, 5255 session, 5256 cache, 5257 PageRenderer, 5258 PageSnapshot, 5259 FrameRenderer, 5260 fetch: fetchWithTurboHeaders, 5261 config, 5262 start, 5263 registerAdapter, 5264 visit, 5265 connectStreamSource, 5266 disconnectStreamSource, 5267 renderStreamMessage, 5268 clearCache, 5269 setProgressBarDelay, 5270 setConfirmMethod, 5271 setFormMode 5272}); 5273var TurboFrameMissingError = class extends Error { 5274}; 5275var FrameController = class { 5276 fetchResponseLoaded = (_fetchResponse) => Promise.resolve(); 5277 #currentFetchRequest = null; 5278 #resolveVisitPromise = () => { 5279 }; 5280 #connected = false; 5281 #hasBeenLoaded = false; 5282 #ignoredAttributes = /* @__PURE__ */ new Set(); 5283 #shouldMorphFrame = false; 5284 action = null; 5285 constructor(element) { 5286 this.element = element; 5287 this.view = new FrameView(this, this.element); 5288 this.appearanceObserver = new AppearanceObserver(this, this.element); 5289 this.formLinkClickObserver = new FormLinkClickObserver(this, this.element); 5290 this.linkInterceptor = new LinkInterceptor(this, this.element); 5291 this.restorationIdentifier = uuid(); 5292 this.formSubmitObserver = new FormSubmitObserver(this, this.element); 5293 } 5294 // Frame delegate 5295 connect() { 5296 if (!this.#connected) { 5297 this.#connected = true; 5298 if (this.loadingStyle == FrameLoadingStyle.lazy) { 5299 this.appearanceObserver.start(); 5300 } else { 5301 this.#loadSourceURL(); 5302 } 5303 this.formLinkClickObserver.start(); 5304 this.linkInterceptor.start(); 5305 this.formSubmitObserver.start(); 5306 } 5307 } 5308 disconnect() { 5309 if (this.#connected) { 5310 this.#connected = false; 5311 this.appearanceObserver.stop(); 5312 this.formLinkClickObserver.stop(); 5313 this.linkInterceptor.stop(); 5314 this.formSubmitObserver.stop(); 5315 } 5316 } 5317 disabledChanged() { 5318 if (this.loadingStyle == FrameLoadingStyle.eager) { 5319 this.#loadSourceURL(); 5320 } 5321 } 5322 sourceURLChanged() { 5323 if (this.#isIgnoringChangesTo("src")) return; 5324 if (this.element.isConnected) { 5325 this.complete = false; 5326 } 5327 if (this.loadingStyle == FrameLoadingStyle.eager || this.#hasBeenLoaded) { 5328 this.#loadSourceURL(); 5329 } 5330 } 5331 sourceURLReloaded() { 5332 const { refresh, src } = this.element; 5333 this.#shouldMorphFrame = src && refresh === "morph"; 5334 this.element.removeAttribute("complete"); 5335 this.element.src = null; 5336 this.element.src = src; 5337 return this.element.loaded; 5338 } 5339 loadingStyleChanged() { 5340 if (this.loadingStyle == FrameLoadingStyle.lazy) { 5341 this.appearanceObserver.start(); 5342 } else { 5343 this.appearanceObserver.stop(); 5344 this.#loadSourceURL(); 5345 } 5346 } 5347 async #loadSourceURL() { 5348 if (this.enabled && this.isActive && !this.complete && this.sourceURL) { 5349 this.element.loaded = this.#visit(expandURL(this.sourceURL)); 5350 this.appearanceObserver.stop(); 5351 await this.element.loaded; 5352 this.#hasBeenLoaded = true; 5353 } 5354 } 5355 async loadResponse(fetchResponse) { 5356 if (fetchResponse.redirected || fetchResponse.succeeded && fetchResponse.isHTML) { 5357 this.sourceURL = fetchResponse.response.url; 5358 } 5359 try { 5360 const html = await fetchResponse.responseHTML; 5361 if (html) { 5362 const document2 = parseHTMLDocument(html); 5363 const pageSnapshot = PageSnapshot.fromDocument(document2); 5364 if (pageSnapshot.isVisitable) { 5365 await this.#loadFrameResponse(fetchResponse, document2); 5366 } else { 5367 await this.#handleUnvisitableFrameResponse(fetchResponse); 5368 } 5369 } 5370 } finally { 5371 this.#shouldMorphFrame = false; 5372 this.fetchResponseLoaded = () => Promise.resolve(); 5373 } 5374 } 5375 // Appearance observer delegate 5376 elementAppearedInViewport(element) { 5377 this.proposeVisitIfNavigatedWithAction(element, getVisitAction(element)); 5378 this.#loadSourceURL(); 5379 } 5380 // Form link click observer delegate 5381 willSubmitFormLinkToLocation(link) { 5382 return this.#shouldInterceptNavigation(link); 5383 } 5384 submittedFormLinkToLocation(link, _location, form) { 5385 const frame = this.#findFrameElement(link); 5386 if (frame) form.setAttribute("data-turbo-frame", frame.id); 5387 } 5388 // Link interceptor delegate 5389 shouldInterceptLinkClick(element, _location, _event) { 5390 return this.#shouldInterceptNavigation(element); 5391 } 5392 linkClickIntercepted(element, location2) { 5393 this.#navigateFrame(element, location2); 5394 } 5395 // Form submit observer delegate 5396 willSubmitForm(element, submitter2) { 5397 return element.closest("turbo-frame") == this.element && this.#shouldInterceptNavigation(element, submitter2); 5398 } 5399 formSubmitted(element, submitter2) { 5400 if (this.formSubmission) { 5401 this.formSubmission.stop(); 5402 } 5403 this.formSubmission = new FormSubmission(this, element, submitter2); 5404 const { fetchRequest } = this.formSubmission; 5405 this.prepareRequest(fetchRequest); 5406 this.formSubmission.start(); 5407 } 5408 // Fetch request delegate 5409 prepareRequest(request) { 5410 request.headers["Turbo-Frame"] = this.id; 5411 if (this.currentNavigationElement?.hasAttribute("data-turbo-stream")) { 5412 request.acceptResponseType(StreamMessage.contentType); 5413 } 5414 } 5415 requestStarted(_request) { 5416 markAsBusy(this.element); 5417 } 5418 requestPreventedHandlingResponse(_request, _response) { 5419 this.#resolveVisitPromise(); 5420 } 5421 async requestSucceededWithResponse(request, response) { 5422 await this.loadResponse(response); 5423 this.#resolveVisitPromise(); 5424 } 5425 async requestFailedWithResponse(request, response) { 5426 await this.loadResponse(response); 5427 this.#resolveVisitPromise(); 5428 } 5429 requestErrored(request, error2) { 5430 console.error(error2); 5431 this.#resolveVisitPromise(); 5432 } 5433 requestFinished(_request) { 5434 clearBusyState(this.element); 5435 } 5436 // Form submission delegate 5437 formSubmissionStarted({ formElement }) { 5438 markAsBusy(formElement, this.#findFrameElement(formElement)); 5439 } 5440 formSubmissionSucceededWithResponse(formSubmission, response) { 5441 const frame = this.#findFrameElement(formSubmission.formElement, formSubmission.submitter); 5442 frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(formSubmission.submitter, formSubmission.formElement, frame)); 5443 frame.delegate.loadResponse(response); 5444 if (!formSubmission.isSafe) { 5445 session.clearCache(); 5446 } 5447 } 5448 formSubmissionFailedWithResponse(formSubmission, fetchResponse) { 5449 this.element.delegate.loadResponse(fetchResponse); 5450 session.clearCache(); 5451 } 5452 formSubmissionErrored(formSubmission, error2) { 5453 console.error(error2); 5454 } 5455 formSubmissionFinished({ formElement }) { 5456 clearBusyState(formElement, this.#findFrameElement(formElement)); 5457 } 5458 // View delegate 5459 allowsImmediateRender({ element: newFrame }, options) { 5460 const event = dispatch("turbo:before-frame-render", { 5461 target: this.element, 5462 detail: { newFrame, ...options }, 5463 cancelable: true 5464 }); 5465 const { 5466 defaultPrevented, 5467 detail: { render } 5468 } = event; 5469 if (this.view.renderer && render) { 5470 this.view.renderer.renderElement = render; 5471 } 5472 return !defaultPrevented; 5473 } 5474 viewRenderedSnapshot(_snapshot, _isPreview, _renderMethod) { 5475 } 5476 preloadOnLoadLinksForView(element) { 5477 session.preloadOnLoadLinksForView(element); 5478 } 5479 viewInvalidated() { 5480 } 5481 // Frame renderer delegate 5482 willRenderFrame(currentElement, _newElement) { 5483 this.previousFrameElement = currentElement.cloneNode(true); 5484 } 5485 visitCachedSnapshot = ({ element }) => { 5486 const frame = element.querySelector("#" + this.element.id); 5487 if (frame && this.previousFrameElement) { 5488 frame.replaceChildren(...this.previousFrameElement.children); 5489 } 5490 delete this.previousFrameElement; 5491 }; 5492 // Private 5493 async #loadFrameResponse(fetchResponse, document2) { 5494 const newFrameElement = await this.extractForeignFrameElement(document2.body); 5495 const rendererClass = this.#shouldMorphFrame ? MorphingFrameRenderer : FrameRenderer; 5496 if (newFrameElement) { 5497 const snapshot = new Snapshot(newFrameElement); 5498 const renderer = new rendererClass(this, this.view.snapshot, snapshot, false, false); 5499 if (this.view.renderPromise) await this.view.renderPromise; 5500 this.changeHistory(); 5501 await this.view.render(renderer); 5502 this.complete = true; 5503 session.frameRendered(fetchResponse, this.element); 5504 session.frameLoaded(this.element); 5505 await this.fetchResponseLoaded(fetchResponse); 5506 } else if (this.#willHandleFrameMissingFromResponse(fetchResponse)) { 5507 this.#handleFrameMissingFromResponse(fetchResponse); 5508 } 5509 } 5510 async #visit(url) { 5511 const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams(), this.element); 5512 this.#currentFetchRequest?.cancel(); 5513 this.#currentFetchRequest = request; 5514 return new Promise((resolve) => { 5515 this.#resolveVisitPromise = () => { 5516 this.#resolveVisitPromise = () => { 5517 }; 5518 this.#currentFetchRequest = null; 5519 resolve(); 5520 }; 5521 request.perform(); 5522 }); 5523 } 5524 #navigateFrame(element, url, submitter2) { 5525 const frame = this.#findFrameElement(element, submitter2); 5526 frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(submitter2, element, frame)); 5527 this.#withCurrentNavigationElement(element, () => { 5528 frame.src = url; 5529 }); 5530 } 5531 proposeVisitIfNavigatedWithAction(frame, action = null) { 5532 this.action = action; 5533 if (this.action) { 5534 const pageSnapshot = PageSnapshot.fromElement(frame).clone(); 5535 const { visitCachedSnapshot } = frame.delegate; 5536 frame.delegate.fetchResponseLoaded = async (fetchResponse) => { 5537 if (frame.src) { 5538 const { statusCode, redirected } = fetchResponse; 5539 const responseHTML = await fetchResponse.responseHTML; 5540 const response = { statusCode, redirected, responseHTML }; 5541 const options = { 5542 response, 5543 visitCachedSnapshot, 5544 willRender: false, 5545 updateHistory: false, 5546 restorationIdentifier: this.restorationIdentifier, 5547 snapshot: pageSnapshot 5548 }; 5549 if (this.action) options.action = this.action; 5550 session.visit(frame.src, options); 5551 } 5552 }; 5553 } 5554 } 5555 changeHistory() { 5556 if (this.action) { 5557 const method = getHistoryMethodForAction(this.action); 5558 session.history.update(method, expandURL(this.element.src || ""), this.restorationIdentifier); 5559 } 5560 } 5561 async #handleUnvisitableFrameResponse(fetchResponse) { 5562 console.warn( 5563 `The response (${fetchResponse.statusCode}) from <turbo-frame id="${this.element.id}"> is performing a full page visit due to turbo-visit-control.` 5564 ); 5565 await this.#visitResponse(fetchResponse.response); 5566 } 5567 #willHandleFrameMissingFromResponse(fetchResponse) { 5568 this.element.setAttribute("complete", ""); 5569 const response = fetchResponse.response; 5570 const visit2 = async (url, options) => { 5571 if (url instanceof Response) { 5572 this.#visitResponse(url); 5573 } else { 5574 session.visit(url, options); 5575 } 5576 }; 5577 const event = dispatch("turbo:frame-missing", { 5578 target: this.element, 5579 detail: { response, visit: visit2 }, 5580 cancelable: true 5581 }); 5582 return !event.defaultPrevented; 5583 } 5584 #handleFrameMissingFromResponse(fetchResponse) { 5585 this.view.missing(); 5586 this.#throwFrameMissingError(fetchResponse); 5587 } 5588 #throwFrameMissingError(fetchResponse) { 5589 const message = `The response (${fetchResponse.statusCode}) did not contain the expected <turbo-frame id="${this.element.id}"> and will be ignored. To perform a full page visit instead, set turbo-visit-control to reload.`; 5590 throw new TurboFrameMissingError(message); 5591 } 5592 async #visitResponse(response) { 5593 const wrapped = new FetchResponse(response); 5594 const responseHTML = await wrapped.responseHTML; 5595 const { location: location2, redirected, statusCode } = wrapped; 5596 return session.visit(location2, { response: { redirected, statusCode, responseHTML } }); 5597 } 5598 #findFrameElement(element, submitter2) { 5599 const id = getAttribute("data-turbo-frame", submitter2, element) || this.element.getAttribute("target"); 5600 return getFrameElementById(id) ?? this.element; 5601 } 5602 async extractForeignFrameElement(container) { 5603 let element; 5604 const id = CSS.escape(this.id); 5605 try { 5606 element = activateElement(container.querySelector(`turbo-frame#${id}`), this.sourceURL); 5607 if (element) { 5608 return element; 5609 } 5610 element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.sourceURL); 5611 if (element) { 5612 await element.loaded; 5613 return await this.extractForeignFrameElement(element); 5614 } 5615 } catch (error2) { 5616 console.error(error2); 5617 return new FrameElement(); 5618 } 5619 return null; 5620 } 5621 #formActionIsVisitable(form, submitter2) { 5622 const action = getAction$1(form, submitter2); 5623 return locationIsVisitable(expandURL(action), this.rootLocation); 5624 } 5625 #shouldInterceptNavigation(element, submitter2) { 5626 const id = getAttribute("data-turbo-frame", submitter2, element) || this.element.getAttribute("target"); 5627 if (element instanceof HTMLFormElement && !this.#formActionIsVisitable(element, submitter2)) { 5628 return false; 5629 } 5630 if (!this.enabled || id == "_top") { 5631 return false; 5632 } 5633 if (id) { 5634 const frameElement = getFrameElementById(id); 5635 if (frameElement) { 5636 return !frameElement.disabled; 5637 } 5638 } 5639 if (!session.elementIsNavigatable(element)) { 5640 return false; 5641 } 5642 if (submitter2 && !session.elementIsNavigatable(submitter2)) { 5643 return false; 5644 } 5645 return true; 5646 } 5647 // Computed properties 5648 get id() { 5649 return this.element.id; 5650 } 5651 get enabled() { 5652 return !this.element.disabled; 5653 } 5654 get sourceURL() { 5655 if (this.element.src) { 5656 return this.element.src; 5657 } 5658 } 5659 set sourceURL(sourceURL) { 5660 this.#ignoringChangesToAttribute("src", () => { 5661 this.element.src = sourceURL ?? null; 5662 }); 5663 } 5664 get loadingStyle() { 5665 return this.element.loading; 5666 } 5667 get isLoading() { 5668 return this.formSubmission !== void 0 || this.#resolveVisitPromise() !== void 0; 5669 } 5670 get complete() { 5671 return this.element.hasAttribute("complete"); 5672 } 5673 set complete(value) { 5674 if (value) { 5675 this.element.setAttribute("complete", ""); 5676 } else { 5677 this.element.removeAttribute("complete"); 5678 } 5679 } 5680 get isActive() { 5681 return this.element.isActive && this.#connected; 5682 } 5683 get rootLocation() { 5684 const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`); 5685 const root = meta?.content ?? "/"; 5686 return expandURL(root); 5687 } 5688 #isIgnoringChangesTo(attributeName) { 5689 return this.#ignoredAttributes.has(attributeName); 5690 } 5691 #ignoringChangesToAttribute(attributeName, callback) { 5692 this.#ignoredAttributes.add(attributeName); 5693 callback(); 5694 this.#ignoredAttributes.delete(attributeName); 5695 } 5696 #withCurrentNavigationElement(element, callback) { 5697 this.currentNavigationElement = element; 5698 callback(); 5699 delete this.currentNavigationElement; 5700 } 5701}; 5702function getFrameElementById(id) { 5703 if (id != null) { 5704 const element = document.getElementById(id); 5705 if (element instanceof FrameElement) { 5706 return element; 5707 } 5708 } 5709} 5710function activateElement(element, currentURL) { 5711 if (element) { 5712 const src = element.getAttribute("src"); 5713 if (src != null && currentURL != null && urlsAreEqual(src, currentURL)) { 5714 throw new Error(`Matching <turbo-frame id="${element.id}"> element has a source URL which references itself`); 5715 } 5716 if (element.ownerDocument !== document) { 5717 element = document.importNode(element, true); 5718 } 5719 if (element instanceof FrameElement) { 5720 element.connectedCallback(); 5721 element.disconnectedCallback(); 5722 return element; 5723 } 5724 } 5725} 5726var StreamActions = { 5727 after() { 5728 this.targetElements.forEach((e) => e.parentElement?.insertBefore(this.templateContent, e.nextSibling)); 5729 }, 5730 append() { 5731 this.removeDuplicateTargetChildren(); 5732 this.targetElements.forEach((e) => e.append(this.templateContent)); 5733 }, 5734 before() { 5735 this.targetElements.forEach((e) => e.parentElement?.insertBefore(this.templateContent, e)); 5736 }, 5737 prepend() { 5738 this.removeDuplicateTargetChildren(); 5739 this.targetElements.forEach((e) => e.prepend(this.templateContent)); 5740 }, 5741 remove() { 5742 this.targetElements.forEach((e) => e.remove()); 5743 }, 5744 replace() { 5745 const method = this.getAttribute("method"); 5746 this.targetElements.forEach((targetElement) => { 5747 if (method === "morph") { 5748 morphElements(targetElement, this.templateContent); 5749 } else { 5750 targetElement.replaceWith(this.templateContent); 5751 } 5752 }); 5753 }, 5754 update() { 5755 const method = this.getAttribute("method"); 5756 this.targetElements.forEach((targetElement) => { 5757 if (method === "morph") { 5758 morphChildren(targetElement, this.templateContent); 5759 } else { 5760 targetElement.innerHTML = ""; 5761 targetElement.append(this.templateContent); 5762 } 5763 }); 5764 }, 5765 refresh() { 5766 session.refresh(this.baseURI, this.requestId); 5767 } 5768}; 5769var StreamElement = class _StreamElement extends HTMLElement { 5770 static async renderElement(newElement) { 5771 await newElement.performAction(); 5772 } 5773 async connectedCallback() { 5774 try { 5775 await this.render(); 5776 } catch (error2) { 5777 console.error(error2); 5778 } finally { 5779 this.disconnect(); 5780 } 5781 } 5782 async render() { 5783 return this.renderPromise ??= (async () => { 5784 const event = this.beforeRenderEvent; 5785 if (this.dispatchEvent(event)) { 5786 await nextRepaint(); 5787 await event.detail.render(this); 5788 } 5789 })(); 5790 } 5791 disconnect() { 5792 try { 5793 this.remove(); 5794 } catch { 5795 } 5796 } 5797 /** 5798 * Removes duplicate children (by ID) 5799 */ 5800 removeDuplicateTargetChildren() { 5801 this.duplicateChildren.forEach((c) => c.remove()); 5802 } 5803 /** 5804 * Gets the list of duplicate children (i.e. those with the same ID) 5805 */ 5806 get duplicateChildren() { 5807 const existingChildren = this.targetElements.flatMap((e) => [...e.children]).filter((c) => !!c.getAttribute("id")); 5808 const newChildrenIds = [...this.templateContent?.children || []].filter((c) => !!c.getAttribute("id")).map((c) => c.getAttribute("id")); 5809 return existingChildren.filter((c) => newChildrenIds.includes(c.getAttribute("id"))); 5810 } 5811 /** 5812 * Gets the action function to be performed. 5813 */ 5814 get performAction() { 5815 if (this.action) { 5816 const actionFunction = StreamActions[this.action]; 5817 if (actionFunction) { 5818 return actionFunction; 5819 } 5820 this.#raise("unknown action"); 5821 } 5822 this.#raise("action attribute is missing"); 5823 } 5824 /** 5825 * Gets the target elements which the template will be rendered to. 5826 */ 5827 get targetElements() { 5828 if (this.target) { 5829 return this.targetElementsById; 5830 } else if (this.targets) { 5831 return this.targetElementsByQuery; 5832 } else { 5833 this.#raise("target or targets attribute is missing"); 5834 } 5835 } 5836 /** 5837 * Gets the contents of the main `<template>`. 5838 */ 5839 get templateContent() { 5840 return this.templateElement.content.cloneNode(true); 5841 } 5842 /** 5843 * Gets the main `<template>` used for rendering 5844 */ 5845 get templateElement() { 5846 if (this.firstElementChild === null) { 5847 const template = this.ownerDocument.createElement("template"); 5848 this.appendChild(template); 5849 return template; 5850 } else if (this.firstElementChild instanceof HTMLTemplateElement) { 5851 return this.firstElementChild; 5852 } 5853 this.#raise("first child element must be a <template> element"); 5854 } 5855 /** 5856 * Gets the current action. 5857 */ 5858 get action() { 5859 return this.getAttribute("action"); 5860 } 5861 /** 5862 * Gets the current target (an element ID) to which the result will 5863 * be rendered. 5864 */ 5865 get target() { 5866 return this.getAttribute("target"); 5867 } 5868 /** 5869 * Gets the current "targets" selector (a CSS selector) 5870 */ 5871 get targets() { 5872 return this.getAttribute("targets"); 5873 } 5874 /** 5875 * Reads the request-id attribute 5876 */ 5877 get requestId() { 5878 return this.getAttribute("request-id"); 5879 } 5880 #raise(message) { 5881 throw new Error(`${this.description}: ${message}`); 5882 } 5883 get description() { 5884 return (this.outerHTML.match(/<[^>]+>/) ?? [])[0] ?? "<turbo-stream>"; 5885 } 5886 get beforeRenderEvent() { 5887 return new CustomEvent("turbo:before-stream-render", { 5888 bubbles: true, 5889 cancelable: true, 5890 detail: { newStream: this, render: _StreamElement.renderElement } 5891 }); 5892 } 5893 get targetElementsById() { 5894 const element = this.ownerDocument?.getElementById(this.target); 5895 if (element !== null) { 5896 return [element]; 5897 } else { 5898 return []; 5899 } 5900 } 5901 get targetElementsByQuery() { 5902 const elements = this.ownerDocument?.querySelectorAll(this.targets); 5903 if (elements.length !== 0) { 5904 return Array.prototype.slice.call(elements); 5905 } else { 5906 return []; 5907 } 5908 } 5909}; 5910var StreamSourceElement = class extends HTMLElement { 5911 streamSource = null; 5912 connectedCallback() { 5913 this.streamSource = this.src.match(/^ws{1,2}:/) ? new WebSocket(this.src) : new EventSource(this.src); 5914 connectStreamSource(this.streamSource); 5915 } 5916 disconnectedCallback() { 5917 if (this.streamSource) { 5918 this.streamSource.close(); 5919 disconnectStreamSource(this.streamSource); 5920 } 5921 } 5922 get src() { 5923 return this.getAttribute("src") || ""; 5924 } 5925}; 5926FrameElement.delegateConstructor = FrameController; 5927if (customElements.get("turbo-frame") === void 0) { 5928 customElements.define("turbo-frame", FrameElement); 5929} 5930if (customElements.get("turbo-stream") === void 0) { 5931 customElements.define("turbo-stream", StreamElement); 5932} 5933if (customElements.get("turbo-stream-source") === void 0) { 5934 customElements.define("turbo-stream-source", StreamSourceElement); 5935} 5936(() => { 5937 let element = document.currentScript; 5938 if (!element) return; 5939 if (element.hasAttribute("data-turbo-suppress-warning")) return; 5940 element = element.parentElement; 5941 while (element) { 5942 if (element == document.body) { 5943 return console.warn( 5944 unindent` 5945 You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do! 5946 5947 Load your applicationâs JavaScript bundle inside the <head> element instead. <script> elements in <body> are evaluated with each page change. 5948 5949 For more information, see: https://turbo.hotwired.dev/handbook/building#working-with-script-elements 5950 5951 ââ 5952 Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s 5953 `, 5954 element.outerHTML 5955 ); 5956 } 5957 element = element.parentElement; 5958 } 5959})(); 5960window.Turbo = { ...Turbo2, StreamActions }; 5961start(); 5962
vendor: 575 bytes, lines 5963-5979
5963// node_modules/@hotwired/turbo-rails/app/javascript/turbo/cable.js 5964var consumer; 5965async function getConsumer() { 5966 return consumer || setConsumer(createConsumer2().then(setConsumer)); 5967} 5968function setConsumer(newConsumer) { 5969 return consumer = newConsumer; 5970} 5971async function createConsumer2() { 5972 const { createConsumer: createConsumer3 } = await Promise.resolve().then(() => (init_src(), src_exports)); 5973 return createConsumer3(); 5974} 5975async function subscribeTo(channel, mixin) { 5976 const { subscriptions } = await getConsumer(); 5977 return subscriptions.create(channel, mixin); 5978} 5979
vendor: 505 bytes, lines 5980-5993
5980// node_modules/@hotwired/turbo-rails/app/javascript/turbo/snakeize.js 5981function walk(obj) { 5982 if (!obj || typeof obj !== "object") return obj; 5983 if (obj instanceof Date || obj instanceof RegExp) return obj; 5984 if (Array.isArray(obj)) return obj.map(walk); 5985 return Object.keys(obj).reduce(function(acc, key) { 5986 var camel = key[0].toLowerCase() + key.slice(1).replace(/([A-Z]+)/g, function(m, x) { 5987 return "_" + x.toLowerCase(); 5988 }); 5989 acc[camel] = walk(obj[key]); 5990 return acc; 5991 }, {}); 5992} 5993
vendor: 1,471 bytes, lines 5994-6035
5994// node_modules/@hotwired/turbo-rails/app/javascript/turbo/cable_stream_source_element.js 5995var TurboCableStreamSourceElement = class extends HTMLElement { 5996 static observedAttributes = ["channel", "signed-stream-name"]; 5997 async connectedCallback() { 5998 connectStreamSource(this); 5999 this.subscription = await subscribeTo(this.channel, { 6000 received: this.dispatchMessageEvent.bind(this), 6001 connected: this.subscriptionConnected.bind(this), 6002 disconnected: this.subscriptionDisconnected.bind(this) 6003 }); 6004 } 6005 disconnectedCallback() { 6006 disconnectStreamSource(this); 6007 if (this.subscription) this.subscription.unsubscribe(); 6008 this.subscriptionDisconnected(); 6009 } 6010 attributeChangedCallback() { 6011 if (this.subscription) { 6012 this.disconnectedCallback(); 6013 this.connectedCallback(); 6014 } 6015 } 6016 dispatchMessageEvent(data) { 6017 const event = new MessageEvent("message", { data }); 6018 return this.dispatchEvent(event); 6019 } 6020 subscriptionConnected() { 6021 this.setAttribute("connected", ""); 6022 } 6023 subscriptionDisconnected() { 6024 this.removeAttribute("connected"); 6025 } 6026 get channel() { 6027 const channel = this.getAttribute("channel"); 6028 const signed_stream_name = this.getAttribute("signed-stream-name"); 6029 return { channel, signed_stream_name, ...walk({ ...this.dataset }) }; 6030 } 6031}; 6032if (customElements.get("turbo-cable-stream-source") === void 0) { 6033 customElements.define("turbo-cable-stream-source", TurboCableStreamSourceElement); 6034} 6035
vendor: 1,611 bytes, lines 6036-6082
6036// node_modules/@hotwired/turbo-rails/app/javascript/turbo/fetch_requests.js 6037function encodeMethodIntoRequestBody(event) { 6038 if (event.target instanceof HTMLFormElement) { 6039 const { target: form, detail: { fetchOptions } } = event; 6040 form.addEventListener("turbo:submit-start", ({ detail: { formSubmission: { submitter: submitter2 } } }) => { 6041 const body = isBodyInit(fetchOptions.body) ? fetchOptions.body : new URLSearchParams(); 6042 const method = determineFetchMethod(submitter2, body, form); 6043 if (!/get/i.test(method)) { 6044 if (/post/i.test(method)) { 6045 body.delete("_method"); 6046 } else { 6047 body.set("_method", method); 6048 } 6049 fetchOptions.method = "post"; 6050 } 6051 }, { once: true }); 6052 } 6053} 6054function determineFetchMethod(submitter2, body, form) { 6055 const formMethod = determineFormMethod(submitter2); 6056 const overrideMethod = body.get("_method"); 6057 const method = form.getAttribute("method") || "get"; 6058 if (typeof formMethod == "string") { 6059 return formMethod; 6060 } else if (typeof overrideMethod == "string") { 6061 return overrideMethod; 6062 } else { 6063 return method; 6064 } 6065} 6066function determineFormMethod(submitter2) { 6067 if (submitter2 instanceof HTMLButtonElement || submitter2 instanceof HTMLInputElement) { 6068 if (submitter2.name === "_method") { 6069 return submitter2.value; 6070 } else if (submitter2.hasAttribute("formmethod")) { 6071 return submitter2.formMethod; 6072 } else { 6073 return null; 6074 } 6075 } else { 6076 return null; 6077 } 6078} 6079function isBodyInit(body) { 6080 return body instanceof FormData || body instanceof URLSearchParams; 6081} 6082
vendor: 187 bytes, lines 6083-6086
6083// node_modules/@hotwired/turbo-rails/app/javascript/turbo/index.js 6084window.Turbo = turbo_es2017_esm_exports; 6085addEventListener("turbo:before-fetch-request", encodeMethodIntoRequestBody); 6086
vendor: 79,408 bytes, lines 6087-8534
6087// node_modules/@hotwired/stimulus/dist/stimulus.js 6088var EventListener = class { 6089 constructor(eventTarget, eventName, eventOptions) { 6090 this.eventTarget = eventTarget; 6091 this.eventName = eventName; 6092 this.eventOptions = eventOptions; 6093 this.unorderedBindings = /* @__PURE__ */ new Set(); 6094 } 6095 connect() { 6096 this.eventTarget.addEventListener(this.eventName, this, this.eventOptions); 6097 } 6098 disconnect() { 6099 this.eventTarget.removeEventListener(this.eventName, this, this.eventOptions); 6100 } 6101 bindingConnected(binding) { 6102 this.unorderedBindings.add(binding); 6103 } 6104 bindingDisconnected(binding) { 6105 this.unorderedBindings.delete(binding); 6106 } 6107 handleEvent(event) { 6108 const extendedEvent = extendEvent(event); 6109 for (const binding of this.bindings) { 6110 if (extendedEvent.immediatePropagationStopped) { 6111 break; 6112 } else { 6113 binding.handleEvent(extendedEvent); 6114 } 6115 } 6116 } 6117 hasBindings() { 6118 return this.unorderedBindings.size > 0; 6119 } 6120 get bindings() { 6121 return Array.from(this.unorderedBindings).sort((left, right) => { 6122 const leftIndex = left.index, rightIndex = right.index; 6123 return leftIndex < rightIndex ? -1 : leftIndex > rightIndex ? 1 : 0; 6124 }); 6125 } 6126}; 6127function extendEvent(event) { 6128 if ("immediatePropagationStopped" in event) { 6129 return event; 6130 } else { 6131 const { stopImmediatePropagation } = event; 6132 return Object.assign(event, { 6133 immediatePropagationStopped: false, 6134 stopImmediatePropagation() { 6135 this.immediatePropagationStopped = true; 6136 stopImmediatePropagation.call(this); 6137 } 6138 }); 6139 } 6140} 6141var Dispatcher = class { 6142 constructor(application2) { 6143 this.application = application2; 6144 this.eventListenerMaps = /* @__PURE__ */ new Map(); 6145 this.started = false; 6146 } 6147 start() { 6148 if (!this.started) { 6149 this.started = true; 6150 this.eventListeners.forEach((eventListener) => eventListener.connect()); 6151 } 6152 } 6153 stop() { 6154 if (this.started) { 6155 this.started = false; 6156 this.eventListeners.forEach((eventListener) => eventListener.disconnect()); 6157 } 6158 } 6159 get eventListeners() { 6160 return Array.from(this.eventListenerMaps.values()).reduce((listeners, map) => listeners.concat(Array.from(map.values())), []); 6161 } 6162 bindingConnected(binding) { 6163 this.fetchEventListenerForBinding(binding).bindingConnected(binding); 6164 } 6165 bindingDisconnected(binding, clearEventListeners = false) { 6166 this.fetchEventListenerForBinding(binding).bindingDisconnected(binding); 6167 if (clearEventListeners) 6168 this.clearEventListenersForBinding(binding); 6169 } 6170 handleError(error2, message, detail = {}) { 6171 this.application.handleError(error2, `Error ${message}`, detail); 6172 } 6173 clearEventListenersForBinding(binding) { 6174 const eventListener = this.fetchEventListenerForBinding(binding); 6175 if (!eventListener.hasBindings()) { 6176 eventListener.disconnect(); 6177 this.removeMappedEventListenerFor(binding); 6178 } 6179 } 6180 removeMappedEventListenerFor(binding) { 6181 const { eventTarget, eventName, eventOptions } = binding; 6182 const eventListenerMap = this.fetchEventListenerMapForEventTarget(eventTarget); 6183 const cacheKey = this.cacheKey(eventName, eventOptions); 6184 eventListenerMap.delete(cacheKey); 6185 if (eventListenerMap.size == 0) 6186 this.eventListenerMaps.delete(eventTarget); 6187 } 6188 fetchEventListenerForBinding(binding) { 6189 const { eventTarget, eventName, eventOptions } = binding; 6190 return this.fetchEventListener(eventTarget, eventName, eventOptions); 6191 } 6192 fetchEventListener(eventTarget, eventName, eventOptions) { 6193 const eventListenerMap = this.fetchEventListenerMapForEventTarget(eventTarget); 6194 const cacheKey = this.cacheKey(eventName, eventOptions); 6195 let eventListener = eventListenerMap.get(cacheKey); 6196 if (!eventListener) { 6197 eventListener = this.createEventListener(eventTarget, eventName, eventOptions); 6198 eventListenerMap.set(cacheKey, eventListener); 6199 } 6200 return eventListener; 6201 } 6202 createEventListener(eventTarget, eventName, eventOptions) { 6203 const eventListener = new EventListener(eventTarget, eventName, eventOptions); 6204 if (this.started) { 6205 eventListener.connect(); 6206 } 6207 return eventListener; 6208 } 6209 fetchEventListenerMapForEventTarget(eventTarget) { 6210 let eventListenerMap = this.eventListenerMaps.get(eventTarget); 6211 if (!eventListenerMap) { 6212 eventListenerMap = /* @__PURE__ */ new Map(); 6213 this.eventListenerMaps.set(eventTarget, eventListenerMap); 6214 } 6215 return eventListenerMap; 6216 } 6217 cacheKey(eventName, eventOptions) { 6218 const parts = [eventName]; 6219 Object.keys(eventOptions).sort().forEach((key) => { 6220 parts.push(`${eventOptions[key] ? "" : "!"}${key}`); 6221 }); 6222 return parts.join(":"); 6223 } 6224}; 6225var defaultActionDescriptorFilters = { 6226 stop({ event, value }) { 6227 if (value) 6228 event.stopPropagation(); 6229 return true; 6230 }, 6231 prevent({ event, value }) { 6232 if (value) 6233 event.preventDefault(); 6234 return true; 6235 }, 6236 self({ event, value, element }) { 6237 if (value) { 6238 return element === event.target; 6239 } else { 6240 return true; 6241 } 6242 } 6243}; 6244var descriptorPattern = /^(?:(?:([^.]+?)\+)?(.+?)(?:\.(.+?))?(?:@(window|document))?->)?(.+?)(?:#([^:]+?))(?::(.+))?$/; 6245function parseActionDescriptorString(descriptorString) { 6246 const source = descriptorString.trim(); 6247 const matches = source.match(descriptorPattern) || []; 6248 let eventName = matches[2]; 6249 let keyFilter = matches[3]; 6250 if (keyFilter && !["keydown", "keyup", "keypress"].includes(eventName)) { 6251 eventName += `.${keyFilter}`; 6252 keyFilter = ""; 6253 } 6254 return { 6255 eventTarget: parseEventTarget(matches[4]), 6256 eventName, 6257 eventOptions: matches[7] ? parseEventOptions(matches[7]) : {}, 6258 identifier: matches[5], 6259 methodName: matches[6], 6260 keyFilter: matches[1] || keyFilter 6261 }; 6262} 6263function parseEventTarget(eventTargetName) { 6264 if (eventTargetName == "window") { 6265 return window; 6266 } else if (eventTargetName == "document") { 6267 return document; 6268 } 6269} 6270function parseEventOptions(eventOptions) { 6271 return eventOptions.split(":").reduce((options, token) => Object.assign(options, { [token.replace(/^!/, "")]: !/^!/.test(token) }), {}); 6272} 6273function stringifyEventTarget(eventTarget) { 6274 if (eventTarget == window) { 6275 return "window"; 6276 } else if (eventTarget == document) { 6277 return "document"; 6278 } 6279} 6280function camelize(value) { 6281 return value.replace(/(?:[_-])([a-z0-9])/g, (_, char) => char.toUpperCase()); 6282} 6283function namespaceCamelize(value) { 6284 return camelize(value.replace(/--/g, "-").replace(/__/g, "_")); 6285} 6286function capitalize(value) { 6287 return value.charAt(0).toUpperCase() + value.slice(1); 6288} 6289function dasherize(value) { 6290 return value.replace(/([A-Z])/g, (_, char) => `-${char.toLowerCase()}`); 6291} 6292function tokenize(value) { 6293 return value.match(/[^\s]+/g) || []; 6294} 6295function isSomething(object) { 6296 return object !== null && object !== void 0; 6297} 6298function hasProperty(object, property) { 6299 return Object.prototype.hasOwnProperty.call(object, property); 6300} 6301var allModifiers = ["meta", "ctrl", "alt", "shift"]; 6302var Action = class { 6303 constructor(element, index, descriptor, schema) { 6304 this.element = element; 6305 this.index = index; 6306 this.eventTarget = descriptor.eventTarget || element; 6307 this.eventName = descriptor.eventName || getDefaultEventNameForElement(element) || error("missing event name"); 6308 this.eventOptions = descriptor.eventOptions || {}; 6309 this.identifier = descriptor.identifier || error("missing identifier"); 6310 this.methodName = descriptor.methodName || error("missing method name"); 6311 this.keyFilter = descriptor.keyFilter || ""; 6312 this.schema = schema; 6313 } 6314 static forToken(token, schema) { 6315 return new this(token.element, token.index, parseActionDescriptorString(token.content), schema); 6316 } 6317 toString() { 6318 const eventFilter = this.keyFilter ? `.${this.keyFilter}` : ""; 6319 const eventTarget = this.eventTargetName ? `@${this.eventTargetName}` : ""; 6320 return `${this.eventName}${eventFilter}${eventTarget}->${this.identifier}#${this.methodName}`; 6321 } 6322 shouldIgnoreKeyboardEvent(event) { 6323 if (!this.keyFilter) { 6324 return false; 6325 } 6326 const filters = this.keyFilter.split("+"); 6327 if (this.keyFilterDissatisfied(event, filters)) { 6328 return true; 6329 } 6330 const standardFilter = filters.filter((key) => !allModifiers.includes(key))[0]; 6331 if (!standardFilter) { 6332 return false; 6333 } 6334 if (!hasProperty(this.keyMappings, standardFilter)) { 6335 error(`contains unknown key filter: ${this.keyFilter}`); 6336 } 6337 return this.keyMappings[standardFilter].toLowerCase() !== event.key.toLowerCase(); 6338 } 6339 shouldIgnoreMouseEvent(event) { 6340 if (!this.keyFilter) { 6341 return false; 6342 } 6343 const filters = [this.keyFilter]; 6344 if (this.keyFilterDissatisfied(event, filters)) { 6345 return true; 6346 } 6347 return false; 6348 } 6349 get params() { 6350 const params = {}; 6351 const pattern = new RegExp(`^data-${this.identifier}-(.+)-param$`, "i"); 6352 for (const { name, value } of Array.from(this.element.attributes)) { 6353 const match = name.match(pattern); 6354 const key = match && match[1]; 6355 if (key) { 6356 params[camelize(key)] = typecast(value); 6357 } 6358 } 6359 return params; 6360 } 6361 get eventTargetName() { 6362 return stringifyEventTarget(this.eventTarget); 6363 } 6364 get keyMappings() { 6365 return this.schema.keyMappings; 6366 } 6367 keyFilterDissatisfied(event, filters) { 6368 const [meta, ctrl, alt, shift] = allModifiers.map((modifier) => filters.includes(modifier)); 6369 return event.metaKey !== meta || event.ctrlKey !== ctrl || event.altKey !== alt || event.shiftKey !== shift; 6370 } 6371}; 6372var defaultEventNames = { 6373 a: () => "click", 6374 button: () => "click", 6375 form: () => "submit", 6376 details: () => "toggle", 6377 input: (e) => e.getAttribute("type") == "submit" ? "click" : "input", 6378 select: () => "change", 6379 textarea: () => "input" 6380}; 6381function getDefaultEventNameForElement(element) { 6382 const tagName = element.tagName.toLowerCase(); 6383 if (tagName in defaultEventNames) { 6384 return defaultEventNames[tagName](element); 6385 } 6386} 6387function error(message) { 6388 throw new Error(message); 6389} 6390function typecast(value) { 6391 try { 6392 return JSON.parse(value); 6393 } catch (o_O) { 6394 return value; 6395 } 6396} 6397var Binding = class { 6398 constructor(context, action) { 6399 this.context = context; 6400 this.action = action; 6401 } 6402 get index() { 6403 return this.action.index; 6404 } 6405 get eventTarget() { 6406 return this.action.eventTarget; 6407 } 6408 get eventOptions() { 6409 return this.action.eventOptions; 6410 } 6411 get identifier() { 6412 return this.context.identifier; 6413 } 6414 handleEvent(event) { 6415 const actionEvent = this.prepareActionEvent(event); 6416 if (this.willBeInvokedByEvent(event) && this.applyEventModifiers(actionEvent)) { 6417 this.invokeWithEvent(actionEvent); 6418 } 6419 } 6420 get eventName() { 6421 return this.action.eventName; 6422 } 6423 get method() { 6424 const method = this.controller[this.methodName]; 6425 if (typeof method == "function") { 6426 return method; 6427 } 6428 throw new Error(`Action "${this.action}" references undefined method "${this.methodName}"`); 6429 } 6430 applyEventModifiers(event) { 6431 const { element } = this.action; 6432 const { actionDescriptorFilters } = this.context.application; 6433 const { controller } = this.context; 6434 let passes = true; 6435 for (const [name, value] of Object.entries(this.eventOptions)) { 6436 if (name in actionDescriptorFilters) { 6437 const filter = actionDescriptorFilters[name]; 6438 passes = passes && filter({ name, value, event, element, controller }); 6439 } else { 6440 continue; 6441 } 6442 } 6443 return passes; 6444 } 6445 prepareActionEvent(event) { 6446 return Object.assign(event, { params: this.action.params }); 6447 } 6448 invokeWithEvent(event) { 6449 const { target, currentTarget } = event; 6450 try { 6451 this.method.call(this.controller, event); 6452 this.context.logDebugActivity(this.methodName, { event, target, currentTarget, action: this.methodName }); 6453 } catch (error2) { 6454 const { identifier, controller, element, index } = this; 6455 const detail = { identifier, controller, element, index, event }; 6456 this.context.handleError(error2, `invoking action "${this.action}"`, detail); 6457 } 6458 } 6459 willBeInvokedByEvent(event) { 6460 const eventTarget = event.target; 6461 if (event instanceof KeyboardEvent && this.action.shouldIgnoreKeyboardEvent(event)) { 6462 return false; 6463 } 6464 if (event instanceof MouseEvent && this.action.shouldIgnoreMouseEvent(event)) { 6465 return false; 6466 } 6467 if (this.element === eventTarget) { 6468 return true; 6469 } else if (eventTarget instanceof Element && this.element.contains(eventTarget)) { 6470 return this.scope.containsElement(eventTarget); 6471 } else { 6472 return this.scope.containsElement(this.action.element); 6473 } 6474 } 6475 get controller() { 6476 return this.context.controller; 6477 } 6478 get methodName() { 6479 return this.action.methodName; 6480 } 6481 get element() { 6482 return this.scope.element; 6483 } 6484 get scope() { 6485 return this.context.scope; 6486 } 6487}; 6488var ElementObserver = class { 6489 constructor(element, delegate) { 6490 this.mutationObserverInit = { attributes: true, childList: true, subtree: true }; 6491 this.element = element; 6492 this.started = false; 6493 this.delegate = delegate; 6494 this.elements = /* @__PURE__ */ new Set(); 6495 this.mutationObserver = new MutationObserver((mutations) => this.processMutations(mutations)); 6496 } 6497 start() { 6498 if (!this.started) { 6499 this.started = true; 6500 this.mutationObserver.observe(this.element, this.mutationObserverInit); 6501 this.refresh(); 6502 } 6503 } 6504 pause(callback) { 6505 if (this.started) { 6506 this.mutationObserver.disconnect(); 6507 this.started = false; 6508 } 6509 callback(); 6510 if (!this.started) { 6511 this.mutationObserver.observe(this.element, this.mutationObserverInit); 6512 this.started = true; 6513 } 6514 } 6515 stop() { 6516 if (this.started) { 6517 this.mutationObserver.takeRecords(); 6518 this.mutationObserver.disconnect(); 6519 this.started = false; 6520 } 6521 } 6522 refresh() { 6523 if (this.started) { 6524 const matches = new Set(this.matchElementsInTree()); 6525 for (const element of Array.from(this.elements)) { 6526 if (!matches.has(element)) { 6527 this.removeElement(element); 6528 } 6529 } 6530 for (const element of Array.from(matches)) { 6531 this.addElement(element); 6532 } 6533 } 6534 } 6535 processMutations(mutations) { 6536 if (this.started) { 6537 for (const mutation of mutations) { 6538 this.processMutation(mutation); 6539 } 6540 } 6541 } 6542 processMutation(mutation) { 6543 if (mutation.type == "attributes") { 6544 this.processAttributeChange(mutation.target, mutation.attributeName); 6545 } else if (mutation.type == "childList") { 6546 this.processRemovedNodes(mutation.removedNodes); 6547 this.processAddedNodes(mutation.addedNodes); 6548 } 6549 } 6550 processAttributeChange(element, attributeName) { 6551 if (this.elements.has(element)) { 6552 if (this.delegate.elementAttributeChanged && this.matchElement(element)) { 6553 this.delegate.elementAttributeChanged(element, attributeName); 6554 } else { 6555 this.removeElement(element); 6556 } 6557 } else if (this.matchElement(element)) { 6558 this.addElement(element); 6559 } 6560 } 6561 processRemovedNodes(nodes) { 6562 for (const node of Array.from(nodes)) { 6563 const element = this.elementFromNode(node); 6564 if (element) { 6565 this.processTree(element, this.removeElement); 6566 } 6567 } 6568 } 6569 processAddedNodes(nodes) { 6570 for (const node of Array.from(nodes)) { 6571 const element = this.elementFromNode(node); 6572 if (element && this.elementIsActive(element)) { 6573 this.processTree(element, this.addElement); 6574 } 6575 } 6576 } 6577 matchElement(element) { 6578 return this.delegate.matchElement(element); 6579 } 6580 matchElementsInTree(tree = this.element) { 6581 return this.delegate.matchElementsInTree(tree); 6582 } 6583 processTree(tree, processor) { 6584 for (const element of this.matchElementsInTree(tree)) { 6585 processor.call(this, element); 6586 } 6587 } 6588 elementFromNode(node) { 6589 if (node.nodeType == Node.ELEMENT_NODE) { 6590 return node; 6591 } 6592 } 6593 elementIsActive(element) { 6594 if (element.isConnected != this.element.isConnected) { 6595 return false; 6596 } else { 6597 return this.element.contains(element); 6598 } 6599 } 6600 addElement(element) { 6601 if (!this.elements.has(element)) { 6602 if (this.elementIsActive(element)) { 6603 this.elements.add(element); 6604 if (this.delegate.elementMatched) { 6605 this.delegate.elementMatched(element); 6606 } 6607 } 6608 } 6609 } 6610 removeElement(element) { 6611 if (this.elements.has(element)) { 6612 this.elements.delete(element); 6613 if (this.delegate.elementUnmatched) { 6614 this.delegate.elementUnmatched(element); 6615 } 6616 } 6617 } 6618}; 6619var AttributeObserver = class { 6620 constructor(element, attributeName, delegate) { 6621 this.attributeName = attributeName; 6622 this.delegate = delegate; 6623 this.elementObserver = new ElementObserver(element, this); 6624 } 6625 get element() { 6626 return this.elementObserver.element; 6627 } 6628 get selector() { 6629 return `[${this.attributeName}]`; 6630 } 6631 start() { 6632 this.elementObserver.start(); 6633 } 6634 pause(callback) { 6635 this.elementObserver.pause(callback); 6636 } 6637 stop() { 6638 this.elementObserver.stop(); 6639 } 6640 refresh() { 6641 this.elementObserver.refresh(); 6642 } 6643 get started() { 6644 return this.elementObserver.started; 6645 } 6646 matchElement(element) { 6647 return element.hasAttribute(this.attributeName); 6648 } 6649 matchElementsInTree(tree) { 6650 const match = this.matchElement(tree) ? [tree] : []; 6651 const matches = Array.from(tree.querySelectorAll(this.selector)); 6652 return match.concat(matches); 6653 } 6654 elementMatched(element) { 6655 if (this.delegate.elementMatchedAttribute) { 6656 this.delegate.elementMatchedAttribute(element, this.attributeName); 6657 } 6658 } 6659 elementUnmatched(element) { 6660 if (this.delegate.elementUnmatchedAttribute) { 6661 this.delegate.elementUnmatchedAttribute(element, this.attributeName); 6662 } 6663 } 6664 elementAttributeChanged(element, attributeName) { 6665 if (this.delegate.elementAttributeValueChanged && this.attributeName == attributeName) { 6666 this.delegate.elementAttributeValueChanged(element, attributeName); 6667 } 6668 } 6669}; 6670function add(map, key, value) { 6671 fetch2(map, key).add(value); 6672} 6673function del(map, key, value) { 6674 fetch2(map, key).delete(value); 6675 prune(map, key); 6676} 6677function fetch2(map, key) { 6678 let values = map.get(key); 6679 if (!values) { 6680 values = /* @__PURE__ */ new Set(); 6681 map.set(key, values); 6682 } 6683 return values; 6684} 6685function prune(map, key) { 6686 const values = map.get(key); 6687 if (values != null && values.size == 0) { 6688 map.delete(key); 6689 } 6690} 6691var Multimap = class { 6692 constructor() { 6693 this.valuesByKey = /* @__PURE__ */ new Map(); 6694 } 6695 get keys() { 6696 return Array.from(this.valuesByKey.keys()); 6697 } 6698 get values() { 6699 const sets = Array.from(this.valuesByKey.values()); 6700 return sets.reduce((values, set) => values.concat(Array.from(set)), []); 6701 } 6702 get size() { 6703 const sets = Array.from(this.valuesByKey.values()); 6704 return sets.reduce((size, set) => size + set.size, 0); 6705 } 6706 add(key, value) { 6707 add(this.valuesByKey, key, value); 6708 } 6709 delete(key, value) { 6710 del(this.valuesByKey, key, value); 6711 } 6712 has(key, value) { 6713 const values = this.valuesByKey.get(key); 6714 return values != null && values.has(value); 6715 } 6716 hasKey(key) { 6717 return this.valuesByKey.has(key); 6718 } 6719 hasValue(value) { 6720 const sets = Array.from(this.valuesByKey.values()); 6721 return sets.some((set) => set.has(value)); 6722 } 6723 getValuesForKey(key) { 6724 const values = this.valuesByKey.get(key); 6725 return values ? Array.from(values) : []; 6726 } 6727 getKeysForValue(value) { 6728 return Array.from(this.valuesByKey).filter(([_key, values]) => values.has(value)).map(([key, _values]) => key); 6729 } 6730}; 6731var SelectorObserver = class { 6732 constructor(element, selector, delegate, details) { 6733 this._selector = selector; 6734 this.details = details; 6735 this.elementObserver = new ElementObserver(element, this); 6736 this.delegate = delegate; 6737 this.matchesByElement = new Multimap(); 6738 } 6739 get started() { 6740 return this.elementObserver.started; 6741 } 6742 get selector() { 6743 return this._selector; 6744 } 6745 set selector(selector) { 6746 this._selector = selector; 6747 this.refresh(); 6748 } 6749 start() { 6750 this.elementObserver.start(); 6751 } 6752 pause(callback) { 6753 this.elementObserver.pause(callback); 6754 } 6755 stop() { 6756 this.elementObserver.stop(); 6757 } 6758 refresh() { 6759 this.elementObserver.refresh(); 6760 } 6761 get element() { 6762 return this.elementObserver.element; 6763 } 6764 matchElement(element) { 6765 const { selector } = this; 6766 if (selector) { 6767 const matches = element.matches(selector); 6768 if (this.delegate.selectorMatchElement) { 6769 return matches && this.delegate.selectorMatchElement(element, this.details); 6770 } 6771 return matches; 6772 } else { 6773 return false; 6774 } 6775 } 6776 matchElementsInTree(tree) { 6777 const { selector } = this; 6778 if (selector) { 6779 const match = this.matchElement(tree) ? [tree] : []; 6780 const matches = Array.from(tree.querySelectorAll(selector)).filter((match2) => this.matchElement(match2)); 6781 return match.concat(matches); 6782 } else { 6783 return []; 6784 } 6785 } 6786 elementMatched(element) { 6787 const { selector } = this; 6788 if (selector) { 6789 this.selectorMatched(element, selector); 6790 } 6791 } 6792 elementUnmatched(element) { 6793 const selectors = this.matchesByElement.getKeysForValue(element); 6794 for (const selector of selectors) { 6795 this.selectorUnmatched(element, selector); 6796 } 6797 } 6798 elementAttributeChanged(element, _attributeName) { 6799 const { selector } = this; 6800 if (selector) { 6801 const matches = this.matchElement(element); 6802 const matchedBefore = this.matchesByElement.has(selector, element); 6803 if (matches && !matchedBefore) { 6804 this.selectorMatched(element, selector); 6805 } else if (!matches && matchedBefore) { 6806 this.selectorUnmatched(element, selector); 6807 } 6808 } 6809 } 6810 selectorMatched(element, selector) { 6811 this.delegate.selectorMatched(element, selector, this.details); 6812 this.matchesByElement.add(selector, element); 6813 } 6814 selectorUnmatched(element, selector) { 6815 this.delegate.selectorUnmatched(element, selector, this.details); 6816 this.matchesByElement.delete(selector, element); 6817 } 6818}; 6819var StringMapObserver = class { 6820 constructor(element, delegate) { 6821 this.element = element; 6822 this.delegate = delegate; 6823 this.started = false; 6824 this.stringMap = /* @__PURE__ */ new Map(); 6825 this.mutationObserver = new MutationObserver((mutations) => this.processMutations(mutations)); 6826 } 6827 start() { 6828 if (!this.started) { 6829 this.started = true; 6830 this.mutationObserver.observe(this.element, { attributes: true, attributeOldValue: true }); 6831 this.refresh(); 6832 } 6833 } 6834 stop() { 6835 if (this.started) { 6836 this.mutationObserver.takeRecords(); 6837 this.mutationObserver.disconnect(); 6838 this.started = false; 6839 } 6840 } 6841 refresh() { 6842 if (this.started) { 6843 for (const attributeName of this.knownAttributeNames) { 6844 this.refreshAttribute(attributeName, null); 6845 } 6846 } 6847 } 6848 processMutations(mutations) { 6849 if (this.started) { 6850 for (const mutation of mutations) { 6851 this.processMutation(mutation); 6852 } 6853 } 6854 } 6855 processMutation(mutation) { 6856 const attributeName = mutation.attributeName; 6857 if (attributeName) { 6858 this.refreshAttribute(attributeName, mutation.oldValue); 6859 } 6860 } 6861 refreshAttribute(attributeName, oldValue) { 6862 const key = this.delegate.getStringMapKeyForAttribute(attributeName); 6863 if (key != null) { 6864 if (!this.stringMap.has(attributeName)) { 6865 this.stringMapKeyAdded(key, attributeName); 6866 } 6867 const value = this.element.getAttribute(attributeName); 6868 if (this.stringMap.get(attributeName) != value) { 6869 this.stringMapValueChanged(value, key, oldValue); 6870 } 6871 if (value == null) { 6872 const oldValue2 = this.stringMap.get(attributeName); 6873 this.stringMap.delete(attributeName); 6874 if (oldValue2) 6875 this.stringMapKeyRemoved(key, attributeName, oldValue2); 6876 } else { 6877 this.stringMap.set(attributeName, value); 6878 } 6879 } 6880 } 6881 stringMapKeyAdded(key, attributeName) { 6882 if (this.delegate.stringMapKeyAdded) { 6883 this.delegate.stringMapKeyAdded(key, attributeName); 6884 } 6885 } 6886 stringMapValueChanged(value, key, oldValue) { 6887 if (this.delegate.stringMapValueChanged) { 6888 this.delegate.stringMapValueChanged(value, key, oldValue); 6889 } 6890 } 6891 stringMapKeyRemoved(key, attributeName, oldValue) { 6892 if (this.delegate.stringMapKeyRemoved) { 6893 this.delegate.stringMapKeyRemoved(key, attributeName, oldValue); 6894 } 6895 } 6896 get knownAttributeNames() { 6897 return Array.from(new Set(this.currentAttributeNames.concat(this.recordedAttributeNames))); 6898 } 6899 get currentAttributeNames() { 6900 return Array.from(this.element.attributes).map((attribute) => attribute.name); 6901 } 6902 get recordedAttributeNames() { 6903 return Array.from(this.stringMap.keys()); 6904 } 6905}; 6906var TokenListObserver = class { 6907 constructor(element, attributeName, delegate) { 6908 this.attributeObserver = new AttributeObserver(element, attributeName, this); 6909 this.delegate = delegate; 6910 this.tokensByElement = new Multimap(); 6911 } 6912 get started() { 6913 return this.attributeObserver.started; 6914 } 6915 start() { 6916 this.attributeObserver.start(); 6917 } 6918 pause(callback) { 6919 this.attributeObserver.pause(callback); 6920 } 6921 stop() { 6922 this.attributeObserver.stop(); 6923 } 6924 refresh() { 6925 this.attributeObserver.refresh(); 6926 } 6927 get element() { 6928 return this.attributeObserver.element; 6929 } 6930 get attributeName() { 6931 return this.attributeObserver.attributeName; 6932 } 6933 elementMatchedAttribute(element) { 6934 this.tokensMatched(this.readTokensForElement(element)); 6935 } 6936 elementAttributeValueChanged(element) { 6937 const [unmatchedTokens, matchedTokens] = this.refreshTokensForElement(element); 6938 this.tokensUnmatched(unmatchedTokens); 6939 this.tokensMatched(matchedTokens); 6940 } 6941 elementUnmatchedAttribute(element) { 6942 this.tokensUnmatched(this.tokensByElement.getValuesForKey(element)); 6943 } 6944 tokensMatched(tokens) { 6945 tokens.forEach((token) => this.tokenMatched(token)); 6946 } 6947 tokensUnmatched(tokens) { 6948 tokens.forEach((token) => this.tokenUnmatched(token)); 6949 } 6950 tokenMatched(token) { 6951 this.delegate.tokenMatched(token); 6952 this.tokensByElement.add(token.element, token); 6953 } 6954 tokenUnmatched(token) { 6955 this.delegate.tokenUnmatched(token); 6956 this.tokensByElement.delete(token.element, token); 6957 } 6958 refreshTokensForElement(element) { 6959 const previousTokens = this.tokensByElement.getValuesForKey(element); 6960 const currentTokens = this.readTokensForElement(element); 6961 const firstDifferingIndex = zip(previousTokens, currentTokens).findIndex(([previousToken, currentToken]) => !tokensAreEqual(previousToken, currentToken)); 6962 if (firstDifferingIndex == -1) { 6963 return [[], []]; 6964 } else { 6965 return [previousTokens.slice(firstDifferingIndex), currentTokens.slice(firstDifferingIndex)]; 6966 } 6967 } 6968 readTokensForElement(element) { 6969 const attributeName = this.attributeName; 6970 const tokenString = element.getAttribute(attributeName) || ""; 6971 return parseTokenString(tokenString, element, attributeName); 6972 } 6973}; 6974function parseTokenString(tokenString, element, attributeName) { 6975 return tokenString.trim().split(/\s+/).filter((content) => content.length).map((content, index) => ({ element, attributeName, content, index })); 6976} 6977function zip(left, right) { 6978 const length = Math.max(left.length, right.length); 6979 return Array.from({ length }, (_, index) => [left[index], right[index]]); 6980} 6981function tokensAreEqual(left, right) { 6982 return left && right && left.index == right.index && left.content == right.content; 6983} 6984var ValueListObserver = class { 6985 constructor(element, attributeName, delegate) { 6986 this.tokenListObserver = new TokenListObserver(element, attributeName, this); 6987 this.delegate = delegate; 6988 this.parseResultsByToken = /* @__PURE__ */ new WeakMap(); 6989 this.valuesByTokenByElement = /* @__PURE__ */ new WeakMap(); 6990 } 6991 get started() { 6992 return this.tokenListObserver.started; 6993 } 6994 start() { 6995 this.tokenListObserver.start(); 6996 } 6997 stop() { 6998 this.tokenListObserver.stop(); 6999 } 7000 refresh() { 7001 this.tokenListObserver.refresh(); 7002 } 7003 get element() { 7004 return this.tokenListObserver.element; 7005 } 7006 get attributeName() { 7007 return this.tokenListObserver.attributeName; 7008 } 7009 tokenMatched(token) { 7010 const { element } = token; 7011 const { value } = this.fetchParseResultForToken(token); 7012 if (value) { 7013 this.fetchValuesByTokenForElement(element).set(token, value); 7014 this.delegate.elementMatchedValue(element, value); 7015 } 7016 } 7017 tokenUnmatched(token) { 7018 const { element } = token; 7019 const { value } = this.fetchParseResultForToken(token); 7020 if (value) { 7021 this.fetchValuesByTokenForElement(element).delete(token); 7022 this.delegate.elementUnmatchedValue(element, value); 7023 } 7024 } 7025 fetchParseResultForToken(token) { 7026 let parseResult = this.parseResultsByToken.get(token); 7027 if (!parseResult) { 7028 parseResult = this.parseToken(token); 7029 this.parseResultsByToken.set(token, parseResult); 7030 } 7031 return parseResult; 7032 } 7033 fetchValuesByTokenForElement(element) { 7034 let valuesByToken = this.valuesByTokenByElement.get(element); 7035 if (!valuesByToken) { 7036 valuesByToken = /* @__PURE__ */ new Map(); 7037 this.valuesByTokenByElement.set(element, valuesByToken); 7038 } 7039 return valuesByToken; 7040 } 7041 parseToken(token) { 7042 try { 7043 const value = this.delegate.parseValueForToken(token); 7044 return { value }; 7045 } catch (error2) { 7046 return { error: error2 }; 7047 } 7048 } 7049}; 7050var BindingObserver = class { 7051 constructor(context, delegate) { 7052 this.context = context; 7053 this.delegate = delegate; 7054 this.bindingsByAction = /* @__PURE__ */ new Map(); 7055 } 7056 start() { 7057 if (!this.valueListObserver) { 7058 this.valueListObserver = new ValueListObserver(this.element, this.actionAttribute, this); 7059 this.valueListObserver.start(); 7060 } 7061 } 7062 stop() { 7063 if (this.valueListObserver) { 7064 this.valueListObserver.stop(); 7065 delete this.valueListObserver; 7066 this.disconnectAllActions(); 7067 } 7068 } 7069 get element() { 7070 return this.context.element; 7071 } 7072 get identifier() { 7073 return this.context.identifier; 7074 } 7075 get actionAttribute() { 7076 return this.schema.actionAttribute; 7077 } 7078 get schema() { 7079 return this.context.schema; 7080 } 7081 get bindings() { 7082 return Array.from(this.bindingsByAction.values()); 7083 } 7084 connectAction(action) { 7085 const binding = new Binding(this.context, action); 7086 this.bindingsByAction.set(action, binding); 7087 this.delegate.bindingConnected(binding); 7088 } 7089 disconnectAction(action) { 7090 const binding = this.bindingsByAction.get(action); 7091 if (binding) { 7092 this.bindingsByAction.delete(action); 7093 this.delegate.bindingDisconnected(binding); 7094 } 7095 } 7096 disconnectAllActions() { 7097 this.bindings.forEach((binding) => this.delegate.bindingDisconnected(binding, true)); 7098 this.bindingsByAction.clear(); 7099 } 7100 parseValueForToken(token) { 7101 const action = Action.forToken(token, this.schema); 7102 if (action.identifier == this.identifier) { 7103 return action; 7104 } 7105 } 7106 elementMatchedValue(element, action) { 7107 this.connectAction(action); 7108 } 7109 elementUnmatchedValue(element, action) { 7110 this.disconnectAction(action); 7111 } 7112}; 7113var ValueObserver = class { 7114 constructor(context, receiver) { 7115 this.context = context; 7116 this.receiver = receiver; 7117 this.stringMapObserver = new StringMapObserver(this.element, this); 7118 this.valueDescriptorMap = this.controller.valueDescriptorMap; 7119 } 7120 start() { 7121 this.stringMapObserver.start(); 7122 this.invokeChangedCallbacksForDefaultValues(); 7123 } 7124 stop() { 7125 this.stringMapObserver.stop(); 7126 } 7127 get element() { 7128 return this.context.element; 7129 } 7130 get controller() { 7131 return this.context.controller; 7132 } 7133 getStringMapKeyForAttribute(attributeName) { 7134 if (attributeName in this.valueDescriptorMap) { 7135 return this.valueDescriptorMap[attributeName].name; 7136 } 7137 } 7138 stringMapKeyAdded(key, attributeName) { 7139 const descriptor = this.valueDescriptorMap[attributeName]; 7140 if (!this.hasValue(key)) { 7141 this.invokeChangedCallback(key, descriptor.writer(this.receiver[key]), descriptor.writer(descriptor.defaultValue)); 7142 } 7143 } 7144 stringMapValueChanged(value, name, oldValue) { 7145 const descriptor = this.valueDescriptorNameMap[name]; 7146 if (value === null) 7147 return; 7148 if (oldValue === null) { 7149 oldValue = descriptor.writer(descriptor.defaultValue); 7150 } 7151 this.invokeChangedCallback(name, value, oldValue); 7152 } 7153 stringMapKeyRemoved(key, attributeName, oldValue) { 7154 const descriptor = this.valueDescriptorNameMap[key]; 7155 if (this.hasValue(key)) { 7156 this.invokeChangedCallback(key, descriptor.writer(this.receiver[key]), oldValue); 7157 } else { 7158 this.invokeChangedCallback(key, descriptor.writer(descriptor.defaultValue), oldValue); 7159 } 7160 } 7161 invokeChangedCallbacksForDefaultValues() { 7162 for (const { key, name, defaultValue, writer } of this.valueDescriptors) { 7163 if (defaultValue != void 0 && !this.controller.data.has(key)) { 7164 this.invokeChangedCallback(name, writer(defaultValue), void 0); 7165 } 7166 } 7167 } 7168 invokeChangedCallback(name, rawValue, rawOldValue) { 7169 const changedMethodName = `${name}Changed`; 7170 const changedMethod = this.receiver[changedMethodName]; 7171 if (typeof changedMethod == "function") { 7172 const descriptor = this.valueDescriptorNameMap[name]; 7173 try { 7174 const value = descriptor.reader(rawValue); 7175 let oldValue = rawOldValue; 7176 if (rawOldValue) { 7177 oldValue = descriptor.reader(rawOldValue); 7178 } 7179 changedMethod.call(this.receiver, value, oldValue); 7180 } catch (error2) { 7181 if (error2 instanceof TypeError) { 7182 error2.message = `Stimulus Value "${this.context.identifier}.${descriptor.name}" - ${error2.message}`; 7183 } 7184 throw error2; 7185 } 7186 } 7187 } 7188 get valueDescriptors() { 7189 const { valueDescriptorMap } = this; 7190 return Object.keys(valueDescriptorMap).map((key) => valueDescriptorMap[key]); 7191 } 7192 get valueDescriptorNameMap() { 7193 const descriptors = {}; 7194 Object.keys(this.valueDescriptorMap).forEach((key) => { 7195 const descriptor = this.valueDescriptorMap[key]; 7196 descriptors[descriptor.name] = descriptor; 7197 }); 7198 return descriptors; 7199 } 7200 hasValue(attributeName) { 7201 const descriptor = this.valueDescriptorNameMap[attributeName]; 7202 const hasMethodName = `has${capitalize(descriptor.name)}`; 7203 return this.receiver[hasMethodName]; 7204 } 7205}; 7206var TargetObserver = class { 7207 constructor(context, delegate) { 7208 this.context = context; 7209 this.delegate = delegate; 7210 this.targetsByName = new Multimap(); 7211 } 7212 start() { 7213 if (!this.tokenListObserver) { 7214 this.tokenListObserver = new TokenListObserver(this.element, this.attributeName, this); 7215 this.tokenListObserver.start(); 7216 } 7217 } 7218 stop() { 7219 if (this.tokenListObserver) { 7220 this.disconnectAllTargets(); 7221 this.tokenListObserver.stop(); 7222 delete this.tokenListObserver; 7223 } 7224 } 7225 tokenMatched({ element, content: name }) { 7226 if (this.scope.containsElement(element)) { 7227 this.connectTarget(element, name); 7228 } 7229 } 7230 tokenUnmatched({ element, content: name }) { 7231 this.disconnectTarget(element, name); 7232 } 7233 connectTarget(element, name) { 7234 var _a; 7235 if (!this.targetsByName.has(name, element)) { 7236 this.targetsByName.add(name, element); 7237 (_a = this.tokenListObserver) === null || _a === void 0 ? void 0 : _a.pause(() => this.delegate.targetConnected(element, name)); 7238 } 7239 } 7240 disconnectTarget(element, name) { 7241 var _a; 7242 if (this.targetsByName.has(name, element)) { 7243 this.targetsByName.delete(name, element); 7244 (_a = this.tokenListObserver) === null || _a === void 0 ? void 0 : _a.pause(() => this.delegate.targetDisconnected(element, name)); 7245 } 7246 } 7247 disconnectAllTargets() { 7248 for (const name of this.targetsByName.keys) { 7249 for (const element of this.targetsByName.getValuesForKey(name)) { 7250 this.disconnectTarget(element, name); 7251 } 7252 } 7253 } 7254 get attributeName() { 7255 return `data-${this.context.identifier}-target`; 7256 } 7257 get element() { 7258 return this.context.element; 7259 } 7260 get scope() { 7261 return this.context.scope; 7262 } 7263}; 7264function readInheritableStaticArrayValues(constructor, propertyName) { 7265 const ancestors = getAncestorsForConstructor(constructor); 7266 return Array.from(ancestors.reduce((values, constructor2) => { 7267 getOwnStaticArrayValues(constructor2, propertyName).forEach((name) => values.add(name)); 7268 return values; 7269 }, /* @__PURE__ */ new Set())); 7270} 7271function readInheritableStaticObjectPairs(constructor, propertyName) { 7272 const ancestors = getAncestorsForConstructor(constructor); 7273 return ancestors.reduce((pairs, constructor2) => { 7274 pairs.push(...getOwnStaticObjectPairs(constructor2, propertyName)); 7275 return pairs; 7276 }, []); 7277} 7278function getAncestorsForConstructor(constructor) { 7279 const ancestors = []; 7280 while (constructor) { 7281 ancestors.push(constructor); 7282 constructor = Object.getPrototypeOf(constructor); 7283 } 7284 return ancestors.reverse(); 7285} 7286function getOwnStaticArrayValues(constructor, propertyName) { 7287 const definition = constructor[propertyName]; 7288 return Array.isArray(definition) ? definition : []; 7289} 7290function getOwnStaticObjectPairs(constructor, propertyName) { 7291 const definition = constructor[propertyName]; 7292 return definition ? Object.keys(definition).map((key) => [key, definition[key]]) : []; 7293} 7294var OutletObserver = class { 7295 constructor(context, delegate) { 7296 this.started = false; 7297 this.context = context; 7298 this.delegate = delegate; 7299 this.outletsByName = new Multimap(); 7300 this.outletElementsByName = new Multimap(); 7301 this.selectorObserverMap = /* @__PURE__ */ new Map(); 7302 this.attributeObserverMap = /* @__PURE__ */ new Map(); 7303 } 7304 start() { 7305 if (!this.started) { 7306 this.outletDefinitions.forEach((outletName) => { 7307 this.setupSelectorObserverForOutlet(outletName); 7308 this.setupAttributeObserverForOutlet(outletName); 7309 }); 7310 this.started = true; 7311 this.dependentContexts.forEach((context) => context.refresh()); 7312 } 7313 } 7314 refresh() { 7315 this.selectorObserverMap.forEach((observer) => observer.refresh()); 7316 this.attributeObserverMap.forEach((observer) => observer.refresh()); 7317 } 7318 stop() { 7319 if (this.started) { 7320 this.started = false; 7321 this.disconnectAllOutlets(); 7322 this.stopSelectorObservers(); 7323 this.stopAttributeObservers(); 7324 } 7325 } 7326 stopSelectorObservers() { 7327 if (this.selectorObserverMap.size > 0) { 7328 this.selectorObserverMap.forEach((observer) => observer.stop()); 7329 this.selectorObserverMap.clear(); 7330 } 7331 } 7332 stopAttributeObservers() { 7333 if (this.attributeObserverMap.size > 0) { 7334 this.attributeObserverMap.forEach((observer) => observer.stop()); 7335 this.attributeObserverMap.clear(); 7336 } 7337 } 7338 selectorMatched(element, _selector, { outletName }) { 7339 const outlet = this.getOutlet(element, outletName); 7340 if (outlet) { 7341 this.connectOutlet(outlet, element, outletName); 7342 } 7343 } 7344 selectorUnmatched(element, _selector, { outletName }) { 7345 const outlet = this.getOutletFromMap(element, outletName); 7346 if (outlet) { 7347 this.disconnectOutlet(outlet, element, outletName); 7348 } 7349 } 7350 selectorMatchElement(element, { outletName }) { 7351 const selector = this.selector(outletName); 7352 const hasOutlet = this.hasOutlet(element, outletName); 7353 const hasOutletController = element.matches(`[${this.schema.controllerAttribute}~=${outletName}]`); 7354 if (selector) { 7355 return hasOutlet && hasOutletController && element.matches(selector); 7356 } else { 7357 return false; 7358 } 7359 } 7360 elementMatchedAttribute(_element, attributeName) { 7361 const outletName = this.getOutletNameFromOutletAttributeName(attributeName); 7362 if (outletName) { 7363 this.updateSelectorObserverForOutlet(outletName); 7364 } 7365 } 7366 elementAttributeValueChanged(_element, attributeName) { 7367 const outletName = this.getOutletNameFromOutletAttributeName(attributeName); 7368 if (outletName) { 7369 this.updateSelectorObserverForOutlet(outletName); 7370 } 7371 } 7372 elementUnmatchedAttribute(_element, attributeName) { 7373 const outletName = this.getOutletNameFromOutletAttributeName(attributeName); 7374 if (outletName) { 7375 this.updateSelectorObserverForOutlet(outletName); 7376 } 7377 } 7378 connectOutlet(outlet, element, outletName) { 7379 var _a; 7380 if (!this.outletElementsByName.has(outletName, element)) { 7381 this.outletsByName.add(outletName, outlet); 7382 this.outletElementsByName.add(outletName, element); 7383 (_a = this.selectorObserverMap.get(outletName)) === null || _a === void 0 ? void 0 : _a.pause(() => this.delegate.outletConnected(outlet, element, outletName)); 7384 } 7385 } 7386 disconnectOutlet(outlet, element, outletName) { 7387 var _a; 7388 if (this.outletElementsByName.has(outletName, element)) { 7389 this.outletsByName.delete(outletName, outlet); 7390 this.outletElementsByName.delete(outletName, element); 7391 (_a = this.selectorObserverMap.get(outletName)) === null || _a === void 0 ? void 0 : _a.pause(() => this.delegate.outletDisconnected(outlet, element, outletName)); 7392 } 7393 } 7394 disconnectAllOutlets() { 7395 for (const outletName of this.outletElementsByName.keys) { 7396 for (const element of this.outletElementsByName.getValuesForKey(outletName)) { 7397 for (const outlet of this.outletsByName.getValuesForKey(outletName)) { 7398 this.disconnectOutlet(outlet, element, outletName); 7399 } 7400 } 7401 } 7402 } 7403 updateSelectorObserverForOutlet(outletName) { 7404 const observer = this.selectorObserverMap.get(outletName); 7405 if (observer) { 7406 observer.selector = this.selector(outletName); 7407 } 7408 } 7409 setupSelectorObserverForOutlet(outletName) { 7410 const selector = this.selector(outletName); 7411 const selectorObserver = new SelectorObserver(document.body, selector, this, { outletName }); 7412 this.selectorObserverMap.set(outletName, selectorObserver); 7413 selectorObserver.start(); 7414 } 7415 setupAttributeObserverForOutlet(outletName) { 7416 const attributeName = this.attributeNameForOutletName(outletName); 7417 const attributeObserver = new AttributeObserver(this.scope.element, attributeName, this); 7418 this.attributeObserverMap.set(outletName, attributeObserver); 7419 attributeObserver.start(); 7420 } 7421 selector(outletName) { 7422 return this.scope.outlets.getSelectorForOutletName(outletName); 7423 } 7424 attributeNameForOutletName(outletName) { 7425 return this.scope.schema.outletAttributeForScope(this.identifier, outletName); 7426 } 7427 getOutletNameFromOutletAttributeName(attributeName) { 7428 return this.outletDefinitions.find((outletName) => this.attributeNameForOutletName(outletName) === attributeName); 7429 } 7430 get outletDependencies() { 7431 const dependencies = new Multimap(); 7432 this.router.modules.forEach((module) => { 7433 const constructor = module.definition.controllerConstructor; 7434 const outlets = readInheritableStaticArrayValues(constructor, "outlets"); 7435 outlets.forEach((outlet) => dependencies.add(outlet, module.identifier)); 7436 }); 7437 return dependencies; 7438 } 7439 get outletDefinitions() { 7440 return this.outletDependencies.getKeysForValue(this.identifier); 7441 } 7442 get dependentControllerIdentifiers() { 7443 return this.outletDependencies.getValuesForKey(this.identifier); 7444 } 7445 get dependentContexts() { 7446 const identifiers = this.dependentControllerIdentifiers; 7447 return this.router.contexts.filter((context) => identifiers.includes(context.identifier)); 7448 } 7449 hasOutlet(element, outletName) { 7450 return !!this.getOutlet(element, outletName) || !!this.getOutletFromMap(element, outletName); 7451 } 7452 getOutlet(element, outletName) { 7453 return this.application.getControllerForElementAndIdentifier(element, outletName); 7454 } 7455 getOutletFromMap(element, outletName) { 7456 return this.outletsByName.getValuesForKey(outletName).find((outlet) => outlet.element === element); 7457 } 7458 get scope() { 7459 return this.context.scope; 7460 } 7461 get schema() { 7462 return this.context.schema; 7463 } 7464 get identifier() { 7465 return this.context.identifier; 7466 } 7467 get application() { 7468 return this.context.application; 7469 } 7470 get router() { 7471 return this.application.router; 7472 } 7473}; 7474var Context = class { 7475 constructor(module, scope) { 7476 this.logDebugActivity = (functionName, detail = {}) => { 7477 const { identifier, controller, element } = this; 7478 detail = Object.assign({ identifier, controller, element }, detail); 7479 this.application.logDebugActivity(this.identifier, functionName, detail); 7480 }; 7481 this.module = module; 7482 this.scope = scope; 7483 this.controller = new module.controllerConstructor(this); 7484 this.bindingObserver = new BindingObserver(this, this.dispatcher); 7485 this.valueObserver = new ValueObserver(this, this.controller); 7486 this.targetObserver = new TargetObserver(this, this); 7487 this.outletObserver = new OutletObserver(this, this); 7488 try { 7489 this.controller.initialize(); 7490 this.logDebugActivity("initialize"); 7491 } catch (error2) { 7492 this.handleError(error2, "initializing controller"); 7493 } 7494 } 7495 connect() { 7496 this.bindingObserver.start(); 7497 this.valueObserver.start(); 7498 this.targetObserver.start(); 7499 this.outletObserver.start(); 7500 try { 7501 this.controller.connect(); 7502 this.logDebugActivity("connect"); 7503 } catch (error2) { 7504 this.handleError(error2, "connecting controller"); 7505 } 7506 } 7507 refresh() { 7508 this.outletObserver.refresh(); 7509 } 7510 disconnect() { 7511 try { 7512 this.controller.disconnect(); 7513 this.logDebugActivity("disconnect"); 7514 } catch (error2) { 7515 this.handleError(error2, "disconnecting controller"); 7516 } 7517 this.outletObserver.stop(); 7518 this.targetObserver.stop(); 7519 this.valueObserver.stop(); 7520 this.bindingObserver.stop(); 7521 } 7522 get application() { 7523 return this.module.application; 7524 } 7525 get identifier() { 7526 return this.module.identifier; 7527 } 7528 get schema() { 7529 return this.application.schema; 7530 } 7531 get dispatcher() { 7532 return this.application.dispatcher; 7533 } 7534 get element() { 7535 return this.scope.element; 7536 } 7537 get parentElement() { 7538 return this.element.parentElement; 7539 } 7540 handleError(error2, message, detail = {}) { 7541 const { identifier, controller, element } = this; 7542 detail = Object.assign({ identifier, controller, element }, detail); 7543 this.application.handleError(error2, `Error ${message}`, detail); 7544 } 7545 targetConnected(element, name) { 7546 this.invokeControllerMethod(`${name}TargetConnected`, element); 7547 } 7548 targetDisconnected(element, name) { 7549 this.invokeControllerMethod(`${name}TargetDisconnected`, element); 7550 } 7551 outletConnected(outlet, element, name) { 7552 this.invokeControllerMethod(`${namespaceCamelize(name)}OutletConnected`, outlet, element); 7553 } 7554 outletDisconnected(outlet, element, name) { 7555 this.invokeControllerMethod(`${namespaceCamelize(name)}OutletDisconnected`, outlet, element); 7556 } 7557 invokeControllerMethod(methodName, ...args) { 7558 const controller = this.controller; 7559 if (typeof controller[methodName] == "function") { 7560 controller[methodName](...args); 7561 } 7562 } 7563}; 7564function bless(constructor) { 7565 return shadow(constructor, getBlessedProperties(constructor)); 7566} 7567function shadow(constructor, properties) { 7568 const shadowConstructor = extend2(constructor); 7569 const shadowProperties = getShadowProperties(constructor.prototype, properties); 7570 Object.defineProperties(shadowConstructor.prototype, shadowProperties); 7571 return shadowConstructor; 7572} 7573function getBlessedProperties(constructor) { 7574 const blessings = readInheritableStaticArrayValues(constructor, "blessings"); 7575 return blessings.reduce((blessedProperties, blessing) => { 7576 const properties = blessing(constructor); 7577 for (const key in properties) { 7578 const descriptor = blessedProperties[key] || {}; 7579 blessedProperties[key] = Object.assign(descriptor, properties[key]); 7580 } 7581 return blessedProperties; 7582 }, {}); 7583} 7584function getShadowProperties(prototype, properties) { 7585 return getOwnKeys(properties).reduce((shadowProperties, key) => { 7586 const descriptor = getShadowedDescriptor(prototype, properties, key); 7587 if (descriptor) { 7588 Object.assign(shadowProperties, { [key]: descriptor }); 7589 } 7590 return shadowProperties; 7591 }, {}); 7592} 7593function getShadowedDescriptor(prototype, properties, key) { 7594 const shadowingDescriptor = Object.getOwnPropertyDescriptor(prototype, key); 7595 const shadowedByValue = shadowingDescriptor && "value" in shadowingDescriptor; 7596 if (!shadowedByValue) { 7597 const descriptor = Object.getOwnPropertyDescriptor(properties, key).value; 7598 if (shadowingDescriptor) { 7599 descriptor.get = shadowingDescriptor.get || descriptor.get; 7600 descriptor.set = shadowingDescriptor.set || descriptor.set; 7601 } 7602 return descriptor; 7603 } 7604} 7605var getOwnKeys = (() => { 7606 if (typeof Object.getOwnPropertySymbols == "function") { 7607 return (object) => [...Object.getOwnPropertyNames(object), ...Object.getOwnPropertySymbols(object)]; 7608 } else { 7609 return Object.getOwnPropertyNames; 7610 } 7611})(); 7612var extend2 = (() => { 7613 function extendWithReflect(constructor) { 7614 function extended() { 7615 return Reflect.construct(constructor, arguments, new.target); 7616 } 7617 extended.prototype = Object.create(constructor.prototype, { 7618 constructor: { value: extended } 7619 }); 7620 Reflect.setPrototypeOf(extended, constructor); 7621 return extended; 7622 } 7623 function testReflectExtension() { 7624 const a = function() { 7625 this.a.call(this); 7626 }; 7627 const b = extendWithReflect(a); 7628 b.prototype.a = function() { 7629 }; 7630 return new b(); 7631 } 7632 try { 7633 testReflectExtension(); 7634 return extendWithReflect; 7635 } catch (error2) { 7636 return (constructor) => class extended extends constructor { 7637 }; 7638 } 7639})(); 7640function blessDefinition(definition) { 7641 return { 7642 identifier: definition.identifier, 7643 controllerConstructor: bless(definition.controllerConstructor) 7644 }; 7645} 7646var Module = class { 7647 constructor(application2, definition) { 7648 this.application = application2; 7649 this.definition = blessDefinition(definition); 7650 this.contextsByScope = /* @__PURE__ */ new WeakMap(); 7651 this.connectedContexts = /* @__PURE__ */ new Set(); 7652 } 7653 get identifier() { 7654 return this.definition.identifier; 7655 } 7656 get controllerConstructor() { 7657 return this.definition.controllerConstructor; 7658 } 7659 get contexts() { 7660 return Array.from(this.connectedContexts); 7661 } 7662 connectContextForScope(scope) { 7663 const context = this.fetchContextForScope(scope); 7664 this.connectedContexts.add(context); 7665 context.connect(); 7666 } 7667 disconnectContextForScope(scope) { 7668 const context = this.contextsByScope.get(scope); 7669 if (context) { 7670 this.connectedContexts.delete(context); 7671 context.disconnect(); 7672 } 7673 } 7674 fetchContextForScope(scope) { 7675 let context = this.contextsByScope.get(scope); 7676 if (!context) { 7677 context = new Context(this, scope); 7678 this.contextsByScope.set(scope, context); 7679 } 7680 return context; 7681 } 7682}; 7683var ClassMap = class { 7684 constructor(scope) { 7685 this.scope = scope; 7686 } 7687 has(name) { 7688 return this.data.has(this.getDataKey(name)); 7689 } 7690 get(name) { 7691 return this.getAll(name)[0]; 7692 } 7693 getAll(name) { 7694 const tokenString = this.data.get(this.getDataKey(name)) || ""; 7695 return tokenize(tokenString); 7696 } 7697 getAttributeName(name) { 7698 return this.data.getAttributeNameForKey(this.getDataKey(name)); 7699 } 7700 getDataKey(name) { 7701 return `${name}-class`; 7702 } 7703 get data() { 7704 return this.scope.data; 7705 } 7706}; 7707var DataMap = class { 7708 constructor(scope) { 7709 this.scope = scope; 7710 } 7711 get element() { 7712 return this.scope.element; 7713 } 7714 get identifier() { 7715 return this.scope.identifier; 7716 } 7717 get(key) { 7718 const name = this.getAttributeNameForKey(key); 7719 return this.element.getAttribute(name); 7720 } 7721 set(key, value) { 7722 const name = this.getAttributeNameForKey(key); 7723 this.element.setAttribute(name, value); 7724 return this.get(key); 7725 } 7726 has(key) { 7727 const name = this.getAttributeNameForKey(key); 7728 return this.element.hasAttribute(name); 7729 } 7730 delete(key) { 7731 if (this.has(key)) { 7732 const name = this.getAttributeNameForKey(key); 7733 this.element.removeAttribute(name); 7734 return true; 7735 } else { 7736 return false; 7737 } 7738 } 7739 getAttributeNameForKey(key) { 7740 return `data-${this.identifier}-${dasherize(key)}`; 7741 } 7742}; 7743var Guide = class { 7744 constructor(logger) { 7745 this.warnedKeysByObject = /* @__PURE__ */ new WeakMap(); 7746 this.logger = logger; 7747 } 7748 warn(object, key, message) { 7749 let warnedKeys = this.warnedKeysByObject.get(object); 7750 if (!warnedKeys) { 7751 warnedKeys = /* @__PURE__ */ new Set(); 7752 this.warnedKeysByObject.set(object, warnedKeys); 7753 } 7754 if (!warnedKeys.has(key)) { 7755 warnedKeys.add(key); 7756 this.logger.warn(message, object); 7757 } 7758 } 7759}; 7760function attributeValueContainsToken(attributeName, token) { 7761 return `[${attributeName}~="${token}"]`; 7762} 7763var TargetSet = class { 7764 constructor(scope) { 7765 this.scope = scope; 7766 } 7767 get element() { 7768 return this.scope.element; 7769 } 7770 get identifier() { 7771 return this.scope.identifier; 7772 } 7773 get schema() { 7774 return this.scope.schema; 7775 } 7776 has(targetName) { 7777 return this.find(targetName) != null; 7778 } 7779 find(...targetNames) { 7780 return targetNames.reduce((target, targetName) => target || this.findTarget(targetName) || this.findLegacyTarget(targetName), void 0); 7781 } 7782 findAll(...targetNames) { 7783 return targetNames.reduce((targets, targetName) => [ 7784 ...targets, 7785 ...this.findAllTargets(targetName), 7786 ...this.findAllLegacyTargets(targetName) 7787 ], []); 7788 } 7789 findTarget(targetName) { 7790 const selector = this.getSelectorForTargetName(targetName); 7791 return this.scope.findElement(selector); 7792 } 7793 findAllTargets(targetName) { 7794 const selector = this.getSelectorForTargetName(targetName); 7795 return this.scope.findAllElements(selector); 7796 } 7797 getSelectorForTargetName(targetName) { 7798 const attributeName = this.schema.targetAttributeForScope(this.identifier); 7799 return attributeValueContainsToken(attributeName, targetName); 7800 } 7801 findLegacyTarget(targetName) { 7802 const selector = this.getLegacySelectorForTargetName(targetName); 7803 return this.deprecate(this.scope.findElement(selector), targetName); 7804 } 7805 findAllLegacyTargets(targetName) { 7806 const selector = this.getLegacySelectorForTargetName(targetName); 7807 return this.scope.findAllElements(selector).map((element) => this.deprecate(element, targetName)); 7808 } 7809 getLegacySelectorForTargetName(targetName) { 7810 const targetDescriptor = `${this.identifier}.${targetName}`; 7811 return attributeValueContainsToken(this.schema.targetAttribute, targetDescriptor); 7812 } 7813 deprecate(element, targetName) { 7814 if (element) { 7815 const { identifier } = this; 7816 const attributeName = this.schema.targetAttribute; 7817 const revisedAttributeName = this.schema.targetAttributeForScope(identifier); 7818 this.guide.warn(element, `target:${targetName}`, `Please replace ${attributeName}="${identifier}.${targetName}" with ${revisedAttributeName}="${targetName}". The ${attributeName} attribute is deprecated and will be removed in a future version of Stimulus.`); 7819 } 7820 return element; 7821 } 7822 get guide() { 7823 return this.scope.guide; 7824 } 7825}; 7826var OutletSet = class { 7827 constructor(scope, controllerElement) { 7828 this.scope = scope; 7829 this.controllerElement = controllerElement; 7830 } 7831 get element() { 7832 return this.scope.element; 7833 } 7834 get identifier() { 7835 return this.scope.identifier; 7836 } 7837 get schema() { 7838 return this.scope.schema; 7839 } 7840 has(outletName) { 7841 return this.find(outletName) != null; 7842 } 7843 find(...outletNames) { 7844 return outletNames.reduce((outlet, outletName) => outlet || this.findOutlet(outletName), void 0); 7845 } 7846 findAll(...outletNames) { 7847 return outletNames.reduce((outlets, outletName) => [...outlets, ...this.findAllOutlets(outletName)], []); 7848 } 7849 getSelectorForOutletName(outletName) { 7850 const attributeName = this.schema.outletAttributeForScope(this.identifier, outletName); 7851 return this.controllerElement.getAttribute(attributeName); 7852 } 7853 findOutlet(outletName) { 7854 const selector = this.getSelectorForOutletName(outletName); 7855 if (selector) 7856 return this.findElement(selector, outletName); 7857 } 7858 findAllOutlets(outletName) { 7859 const selector = this.getSelectorForOutletName(outletName); 7860 return selector ? this.findAllElements(selector, outletName) : []; 7861 } 7862 findElement(selector, outletName) { 7863 const elements = this.scope.queryElements(selector); 7864 return elements.filter((element) => this.matchesElement(element, selector, outletName))[0]; 7865 } 7866 findAllElements(selector, outletName) { 7867 const elements = this.scope.queryElements(selector); 7868 return elements.filter((element) => this.matchesElement(element, selector, outletName)); 7869 } 7870 matchesElement(element, selector, outletName) { 7871 const controllerAttribute = element.getAttribute(this.scope.schema.controllerAttribute) || ""; 7872 return element.matches(selector) && controllerAttribute.split(" ").includes(outletName); 7873 } 7874}; 7875var Scope = class _Scope { 7876 constructor(schema, element, identifier, logger) { 7877 this.targets = new TargetSet(this); 7878 this.classes = new ClassMap(this); 7879 this.data = new DataMap(this); 7880 this.containsElement = (element2) => { 7881 return element2.closest(this.controllerSelector) === this.element; 7882 }; 7883 this.schema = schema; 7884 this.element = element; 7885 this.identifier = identifier; 7886 this.guide = new Guide(logger); 7887 this.outlets = new OutletSet(this.documentScope, element); 7888 } 7889 findElement(selector) { 7890 return this.element.matches(selector) ? this.element : this.queryElements(selector).find(this.containsElement); 7891 } 7892 findAllElements(selector) { 7893 return [ 7894 ...this.element.matches(selector) ? [this.element] : [], 7895 ...this.queryElements(selector).filter(this.containsElement) 7896 ]; 7897 } 7898 queryElements(selector) { 7899 return Array.from(this.element.querySelectorAll(selector)); 7900 } 7901 get controllerSelector() { 7902 return attributeValueContainsToken(this.schema.controllerAttribute, this.identifier); 7903 } 7904 get isDocumentScope() { 7905 return this.element === document.documentElement; 7906 } 7907 get documentScope() { 7908 return this.isDocumentScope ? this : new _Scope(this.schema, document.documentElement, this.identifier, this.guide.logger); 7909 } 7910}; 7911var ScopeObserver = class { 7912 constructor(element, schema, delegate) { 7913 this.element = element; 7914 this.schema = schema; 7915 this.delegate = delegate; 7916 this.valueListObserver = new ValueListObserver(this.element, this.controllerAttribute, this); 7917 this.scopesByIdentifierByElement = /* @__PURE__ */ new WeakMap(); 7918 this.scopeReferenceCounts = /* @__PURE__ */ new WeakMap(); 7919 } 7920 start() { 7921 this.valueListObserver.start(); 7922 } 7923 stop() { 7924 this.valueListObserver.stop(); 7925 } 7926 get controllerAttribute() { 7927 return this.schema.controllerAttribute; 7928 } 7929 parseValueForToken(token) { 7930 const { element, content: identifier } = token; 7931 return this.parseValueForElementAndIdentifier(element, identifier); 7932 } 7933 parseValueForElementAndIdentifier(element, identifier) { 7934 const scopesByIdentifier = this.fetchScopesByIdentifierForElement(element); 7935 let scope = scopesByIdentifier.get(identifier); 7936 if (!scope) { 7937 scope = this.delegate.createScopeForElementAndIdentifier(element, identifier); 7938 scopesByIdentifier.set(identifier, scope); 7939 } 7940 return scope; 7941 } 7942 elementMatchedValue(element, value) { 7943 const referenceCount = (this.scopeReferenceCounts.get(value) || 0) + 1; 7944 this.scopeReferenceCounts.set(value, referenceCount); 7945 if (referenceCount == 1) { 7946 this.delegate.scopeConnected(value); 7947 } 7948 } 7949 elementUnmatchedValue(element, value) { 7950 const referenceCount = this.scopeReferenceCounts.get(value); 7951 if (referenceCount) { 7952 this.scopeReferenceCounts.set(value, referenceCount - 1); 7953 if (referenceCount == 1) { 7954 this.delegate.scopeDisconnected(value); 7955 } 7956 } 7957 } 7958 fetchScopesByIdentifierForElement(element) { 7959 let scopesByIdentifier = this.scopesByIdentifierByElement.get(element); 7960 if (!scopesByIdentifier) { 7961 scopesByIdentifier = /* @__PURE__ */ new Map(); 7962 this.scopesByIdentifierByElement.set(element, scopesByIdentifier); 7963 } 7964 return scopesByIdentifier; 7965 } 7966}; 7967var Router = class { 7968 constructor(application2) { 7969 this.application = application2; 7970 this.scopeObserver = new ScopeObserver(this.element, this.schema, this); 7971 this.scopesByIdentifier = new Multimap(); 7972 this.modulesByIdentifier = /* @__PURE__ */ new Map(); 7973 } 7974 get element() { 7975 return this.application.element; 7976 } 7977 get schema() { 7978 return this.application.schema; 7979 } 7980 get logger() { 7981 return this.application.logger; 7982 } 7983 get controllerAttribute() { 7984 return this.schema.controllerAttribute; 7985 } 7986 get modules() { 7987 return Array.from(this.modulesByIdentifier.values()); 7988 } 7989 get contexts() { 7990 return this.modules.reduce((contexts, module) => contexts.concat(module.contexts), []); 7991 } 7992 start() { 7993 this.scopeObserver.start(); 7994 } 7995 stop() { 7996 this.scopeObserver.stop(); 7997 } 7998 loadDefinition(definition) { 7999 this.unloadIdentifier(definition.identifier); 8000 const module = new Module(this.application, definition); 8001 this.connectModule(module); 8002 const afterLoad = definition.controllerConstructor.afterLoad; 8003 if (afterLoad) { 8004 afterLoad.call(definition.controllerConstructor, definition.identifier, this.application); 8005 } 8006 } 8007 unloadIdentifier(identifier) { 8008 const module = this.modulesByIdentifier.get(identifier); 8009 if (module) { 8010 this.disconnectModule(module); 8011 } 8012 } 8013 getContextForElementAndIdentifier(element, identifier) { 8014 const module = this.modulesByIdentifier.get(identifier); 8015 if (module) { 8016 return module.contexts.find((context) => context.element == element); 8017 } 8018 } 8019 proposeToConnectScopeForElementAndIdentifier(element, identifier) { 8020 const scope = this.scopeObserver.parseValueForElementAndIdentifier(element, identifier); 8021 if (scope) { 8022 this.scopeObserver.elementMatchedValue(scope.element, scope); 8023 } else { 8024 console.error(`Couldn't find or create scope for identifier: "${identifier}" and element:`, element); 8025 } 8026 } 8027 handleError(error2, message, detail) { 8028 this.application.handleError(error2, message, detail); 8029 } 8030 createScopeForElementAndIdentifier(element, identifier) { 8031 return new Scope(this.schema, element, identifier, this.logger); 8032 } 8033 scopeConnected(scope) { 8034 this.scopesByIdentifier.add(scope.identifier, scope); 8035 const module = this.modulesByIdentifier.get(scope.identifier); 8036 if (module) { 8037 module.connectContextForScope(scope); 8038 } 8039 } 8040 scopeDisconnected(scope) { 8041 this.scopesByIdentifier.delete(scope.identifier, scope); 8042 const module = this.modulesByIdentifier.get(scope.identifier); 8043 if (module) { 8044 module.disconnectContextForScope(scope); 8045 } 8046 } 8047 connectModule(module) { 8048 this.modulesByIdentifier.set(module.identifier, module); 8049 const scopes = this.scopesByIdentifier.getValuesForKey(module.identifier); 8050 scopes.forEach((scope) => module.connectContextForScope(scope)); 8051 } 8052 disconnectModule(module) { 8053 this.modulesByIdentifier.delete(module.identifier); 8054 const scopes = this.scopesByIdentifier.getValuesForKey(module.identifier); 8055 scopes.forEach((scope) => module.disconnectContextForScope(scope)); 8056 } 8057}; 8058var defaultSchema = { 8059 controllerAttribute: "data-controller", 8060 actionAttribute: "data-action", 8061 targetAttribute: "data-target", 8062 targetAttributeForScope: (identifier) => `data-${identifier}-target`, 8063 outletAttributeForScope: (identifier, outlet) => `data-${identifier}-${outlet}-outlet`, 8064 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]))) 8065}; 8066function objectFromEntries(array) { 8067 return array.reduce((memo, [k, v]) => Object.assign(Object.assign({}, memo), { [k]: v }), {}); 8068} 8069var Application = class { 8070 constructor(element = document.documentElement, schema = defaultSchema) { 8071 this.logger = console; 8072 this.debug = false; 8073 this.logDebugActivity = (identifier, functionName, detail = {}) => { 8074 if (this.debug) { 8075 this.logFormattedMessage(identifier, functionName, detail); 8076 } 8077 }; 8078 this.element = element; 8079 this.schema = schema; 8080 this.dispatcher = new Dispatcher(this); 8081 this.router = new Router(this); 8082 this.actionDescriptorFilters = Object.assign({}, defaultActionDescriptorFilters); 8083 } 8084 static start(element, schema) { 8085 const application2 = new this(element, schema); 8086 application2.start(); 8087 return application2; 8088 } 8089 async start() { 8090 await domReady(); 8091 this.logDebugActivity("application", "starting"); 8092 this.dispatcher.start(); 8093 this.router.start(); 8094 this.logDebugActivity("application", "start"); 8095 } 8096 stop() { 8097 this.logDebugActivity("application", "stopping"); 8098 this.dispatcher.stop(); 8099 this.router.stop(); 8100 this.logDebugActivity("application", "stop"); 8101 } 8102 register(identifier, controllerConstructor) { 8103 this.load({ identifier, controllerConstructor }); 8104 } 8105 registerActionOption(name, filter) { 8106 this.actionDescriptorFilters[name] = filter; 8107 } 8108 load(head, ...rest) { 8109 const definitions = Array.isArray(head) ? head : [head, ...rest]; 8110 definitions.forEach((definition) => { 8111 if (definition.controllerConstructor.shouldLoad) { 8112 this.router.loadDefinition(definition); 8113 } 8114 }); 8115 } 8116 unload(head, ...rest) { 8117 const identifiers = Array.isArray(head) ? head : [head, ...rest]; 8118 identifiers.forEach((identifier) => this.router.unloadIdentifier(identifier)); 8119 } 8120 get controllers() { 8121 return this.router.contexts.map((context) => context.controller); 8122 } 8123 getControllerForElementAndIdentifier(element, identifier) { 8124 const context = this.router.getContextForElementAndIdentifier(element, identifier); 8125 return context ? context.controller : null; 8126 } 8127 handleError(error2, message, detail) { 8128 var _a; 8129 this.logger.error(`%s 8130 8131%o 8132 8133%o`, message, error2, detail); 8134 (_a = window.onerror) === null || _a === void 0 ? void 0 : _a.call(window, message, "", 0, 0, error2); 8135 } 8136 logFormattedMessage(identifier, functionName, detail = {}) { 8137 detail = Object.assign({ application: this }, detail); 8138 this.logger.groupCollapsed(`${identifier} #${functionName}`); 8139 this.logger.log("details:", Object.assign({}, detail)); 8140 this.logger.groupEnd(); 8141 } 8142}; 8143function domReady() { 8144 return new Promise((resolve) => { 8145 if (document.readyState == "loading") { 8146 document.addEventListener("DOMContentLoaded", () => resolve()); 8147 } else { 8148 resolve(); 8149 } 8150 }); 8151} 8152function ClassPropertiesBlessing(constructor) { 8153 const classes = readInheritableStaticArrayValues(constructor, "classes"); 8154 return classes.reduce((properties, classDefinition) => { 8155 return Object.assign(properties, propertiesForClassDefinition(classDefinition)); 8156 }, {}); 8157} 8158function propertiesForClassDefinition(key) { 8159 return { 8160 [`${key}Class`]: { 8161 get() { 8162 const { classes } = this; 8163 if (classes.has(key)) { 8164 return classes.get(key); 8165 } else { 8166 const attribute = classes.getAttributeName(key); 8167 throw new Error(`Missing attribute "${attribute}"`); 8168 } 8169 } 8170 }, 8171 [`${key}Classes`]: { 8172 get() { 8173 return this.classes.getAll(key); 8174 } 8175 }, 8176 [`has${capitalize(key)}Class`]: { 8177 get() { 8178 return this.classes.has(key); 8179 } 8180 } 8181 }; 8182} 8183function OutletPropertiesBlessing(constructor) { 8184 const outlets = readInheritableStaticArrayValues(constructor, "outlets"); 8185 return outlets.reduce((properties, outletDefinition) => { 8186 return Object.assign(properties, propertiesForOutletDefinition(outletDefinition)); 8187 }, {}); 8188} 8189function getOutletController(controller, element, identifier) { 8190 return controller.application.getControllerForElementAndIdentifier(element, identifier); 8191} 8192function getControllerAndEnsureConnectedScope(controller, element, outletName) { 8193 let outletController = getOutletController(controller, element, outletName); 8194 if (outletController) 8195 return outletController; 8196 controller.application.router.proposeToConnectScopeForElementAndIdentifier(element, outletName); 8197 outletController = getOutletController(controller, element, outletName); 8198 if (outletController) 8199 return outletController; 8200} 8201function propertiesForOutletDefinition(name) { 8202 const camelizedName = namespaceCamelize(name); 8203 return { 8204 [`${camelizedName}Outlet`]: { 8205 get() { 8206 const outletElement = this.outlets.find(name); 8207 const selector = this.outlets.getSelectorForOutletName(name); 8208 if (outletElement) { 8209 const outletController = getControllerAndEnsureConnectedScope(this, outletElement, name); 8210 if (outletController) 8211 return outletController; 8212 throw new Error(`The provided outlet element is missing an outlet controller "${name}" instance for host controller "${this.identifier}"`); 8213 } 8214 throw new Error(`Missing outlet element "${name}" for host controller "${this.identifier}". Stimulus couldn't find a matching outlet element using selector "${selector}".`); 8215 } 8216 }, 8217 [`${camelizedName}Outlets`]: { 8218 get() { 8219 const outlets = this.outlets.findAll(name); 8220 if (outlets.length > 0) { 8221 return outlets.map((outletElement) => { 8222 const outletController = getControllerAndEnsureConnectedScope(this, outletElement, name); 8223 if (outletController) 8224 return outletController; 8225 console.warn(`The provided outlet element is missing an outlet controller "${name}" instance for host controller "${this.identifier}"`, outletElement); 8226 }).filter((controller) => controller); 8227 } 8228 return []; 8229 } 8230 }, 8231 [`${camelizedName}OutletElement`]: { 8232 get() { 8233 const outletElement = this.outlets.find(name); 8234 const selector = this.outlets.getSelectorForOutletName(name); 8235 if (outletElement) { 8236 return outletElement; 8237 } else { 8238 throw new Error(`Missing outlet element "${name}" for host controller "${this.identifier}". Stimulus couldn't find a matching outlet element using selector "${selector}".`); 8239 } 8240 } 8241 }, 8242 [`${camelizedName}OutletElements`]: { 8243 get() { 8244 return this.outlets.findAll(name); 8245 } 8246 }, 8247 [`has${capitalize(camelizedName)}Outlet`]: { 8248 get() { 8249 return this.outlets.has(name); 8250 } 8251 } 8252 }; 8253} 8254function TargetPropertiesBlessing(constructor) { 8255 const targets = readInheritableStaticArrayValues(constructor, "targets"); 8256 return targets.reduce((properties, targetDefinition) => { 8257 return Object.assign(properties, propertiesForTargetDefinition(targetDefinition)); 8258 }, {}); 8259} 8260function propertiesForTargetDefinition(name) { 8261 return { 8262 [`${name}Target`]: { 8263 get() { 8264 const target = this.targets.find(name); 8265 if (target) { 8266 return target; 8267 } else { 8268 throw new Error(`Missing target element "${name}" for "${this.identifier}" controller`); 8269 } 8270 } 8271 }, 8272 [`${name}Targets`]: { 8273 get() { 8274 return this.targets.findAll(name); 8275 } 8276 }, 8277 [`has${capitalize(name)}Target`]: { 8278 get() { 8279 return this.targets.has(name); 8280 } 8281 } 8282 }; 8283} 8284function ValuePropertiesBlessing(constructor) { 8285 const valueDefinitionPairs = readInheritableStaticObjectPairs(constructor, "values"); 8286 const propertyDescriptorMap = { 8287 valueDescriptorMap: { 8288 get() { 8289 return valueDefinitionPairs.reduce((result, valueDefinitionPair) => { 8290 const valueDescriptor = parseValueDefinitionPair(valueDefinitionPair, this.identifier); 8291 const attributeName = this.data.getAttributeNameForKey(valueDescriptor.key); 8292 return Object.assign(result, { [attributeName]: valueDescriptor }); 8293 }, {}); 8294 } 8295 } 8296 }; 8297 return valueDefinitionPairs.reduce((properties, valueDefinitionPair) => { 8298 return Object.assign(properties, propertiesForValueDefinitionPair(valueDefinitionPair)); 8299 }, propertyDescriptorMap); 8300} 8301function propertiesForValueDefinitionPair(valueDefinitionPair, controller) { 8302 const definition = parseValueDefinitionPair(valueDefinitionPair, controller); 8303 const { key, name, reader: read, writer: write } = definition; 8304 return { 8305 [name]: { 8306 get() { 8307 const value = this.data.get(key); 8308 if (value !== null) { 8309 return read(value); 8310 } else { 8311 return definition.defaultValue; 8312 } 8313 }, 8314 set(value) { 8315 if (value === void 0) { 8316 this.data.delete(key); 8317 } else { 8318 this.data.set(key, write(value)); 8319 } 8320 } 8321 }, 8322 [`has${capitalize(name)}`]: { 8323 get() { 8324 return this.data.has(key) || definition.hasCustomDefaultValue; 8325 } 8326 } 8327 }; 8328} 8329function parseValueDefinitionPair([token, typeDefinition], controller) { 8330 return valueDescriptorForTokenAndTypeDefinition({ 8331 controller, 8332 token, 8333 typeDefinition 8334 }); 8335} 8336function parseValueTypeConstant(constant) { 8337 switch (constant) { 8338 case Array: 8339 return "array"; 8340 case Boolean: 8341 return "boolean"; 8342 case Number: 8343 return "number"; 8344 case Object: 8345 return "object"; 8346 case String: 8347 return "string"; 8348 } 8349} 8350function parseValueTypeDefault(defaultValue) { 8351 switch (typeof defaultValue) { 8352 case "boolean": 8353 return "boolean"; 8354 case "number": 8355 return "number"; 8356 case "string": 8357 return "string"; 8358 } 8359 if (Array.isArray(defaultValue)) 8360 return "array"; 8361 if (Object.prototype.toString.call(defaultValue) === "[object Object]") 8362 return "object"; 8363} 8364function parseValueTypeObject(payload) { 8365 const { controller, token, typeObject } = payload; 8366 const hasType = isSomething(typeObject.type); 8367 const hasDefault = isSomething(typeObject.default); 8368 const fullObject = hasType && hasDefault; 8369 const onlyType = hasType && !hasDefault; 8370 const onlyDefault = !hasType && hasDefault; 8371 const typeFromObject = parseValueTypeConstant(typeObject.type); 8372 const typeFromDefaultValue = parseValueTypeDefault(payload.typeObject.default); 8373 if (onlyType) 8374 return typeFromObject; 8375 if (onlyDefault) 8376 return typeFromDefaultValue; 8377 if (typeFromObject !== typeFromDefaultValue) { 8378 const propertyPath = controller ? `${controller}.${token}` : token; 8379 throw new Error(`The specified default value for the Stimulus Value "${propertyPath}" must match the defined type "${typeFromObject}". The provided default value of "${typeObject.default}" is of type "${typeFromDefaultValue}".`); 8380 } 8381 if (fullObject) 8382 return typeFromObject; 8383} 8384function parseValueTypeDefinition(payload) { 8385 const { controller, token, typeDefinition } = payload; 8386 const typeObject = { controller, token, typeObject: typeDefinition }; 8387 const typeFromObject = parseValueTypeObject(typeObject); 8388 const typeFromDefaultValue = parseValueTypeDefault(typeDefinition); 8389 const typeFromConstant = parseValueTypeConstant(typeDefinition); 8390 const type = typeFromObject || typeFromDefaultValue || typeFromConstant; 8391 if (type) 8392 return type; 8393 const propertyPath = controller ? `${controller}.${typeDefinition}` : token; 8394 throw new Error(`Unknown value type "${propertyPath}" for "${token}" value`); 8395} 8396function defaultValueForDefinition(typeDefinition) { 8397 const constant = parseValueTypeConstant(typeDefinition); 8398 if (constant) 8399 return defaultValuesByType[constant]; 8400 const hasDefault = hasProperty(typeDefinition, "default"); 8401 const hasType = hasProperty(typeDefinition, "type"); 8402 const typeObject = typeDefinition; 8403 if (hasDefault) 8404 return typeObject.default; 8405 if (hasType) { 8406 const { type } = typeObject; 8407 const constantFromType = parseValueTypeConstant(type); 8408 if (constantFromType) 8409 return defaultValuesByType[constantFromType]; 8410 } 8411 return typeDefinition; 8412} 8413function valueDescriptorForTokenAndTypeDefinition(payload) { 8414 const { token, typeDefinition } = payload; 8415 const key = `${dasherize(token)}-value`; 8416 const type = parseValueTypeDefinition(payload); 8417 return { 8418 type, 8419 key, 8420 name: camelize(key), 8421 get defaultValue() { 8422 return defaultValueForDefinition(typeDefinition); 8423 }, 8424 get hasCustomDefaultValue() { 8425 return parseValueTypeDefault(typeDefinition) !== void 0; 8426 }, 8427 reader: readers[type], 8428 writer: writers[type] || writers.default 8429 }; 8430} 8431var defaultValuesByType = { 8432 get array() { 8433 return []; 8434 }, 8435 boolean: false, 8436 number: 0, 8437 get object() { 8438 return {}; 8439 }, 8440 string: "" 8441}; 8442var readers = { 8443 array(value) { 8444 const array = JSON.parse(value); 8445 if (!Array.isArray(array)) { 8446 throw new TypeError(`expected value of type "array" but instead got value "${value}" of type "${parseValueTypeDefault(array)}"`); 8447 } 8448 return array; 8449 }, 8450 boolean(value) { 8451 return !(value == "0" || String(value).toLowerCase() == "false"); 8452 }, 8453 number(value) { 8454 return Number(value.replace(/_/g, "")); 8455 }, 8456 object(value) { 8457 const object = JSON.parse(value); 8458 if (object === null || typeof object != "object" || Array.isArray(object)) { 8459 throw new TypeError(`expected value of type "object" but instead got value "${value}" of type "${parseValueTypeDefault(object)}"`); 8460 } 8461 return object; 8462 }, 8463 string(value) { 8464 return value; 8465 } 8466}; 8467var writers = { 8468 default: writeString, 8469 array: writeJSON, 8470 object: writeJSON 8471}; 8472function writeJSON(value) { 8473 return JSON.stringify(value); 8474} 8475function writeString(value) { 8476 return `${value}`; 8477} 8478var Controller = class { 8479 constructor(context) { 8480 this.context = context; 8481 } 8482 static get shouldLoad() { 8483 return true; 8484 } 8485 static afterLoad(_identifier, _application) { 8486 return; 8487 } 8488 get application() { 8489 return this.context.application; 8490 } 8491 get scope() { 8492 return this.context.scope; 8493 } 8494 get element() { 8495 return this.scope.element; 8496 } 8497 get identifier() { 8498 return this.scope.identifier; 8499 } 8500 get targets() { 8501 return this.scope.targets; 8502 } 8503 get outlets() { 8504 return this.scope.outlets; 8505 } 8506 get classes() { 8507 return this.scope.classes; 8508 } 8509 get data() { 8510 return this.scope.data; 8511 } 8512 initialize() { 8513 } 8514 connect() { 8515 } 8516 disconnect() { 8517 } 8518 dispatch(eventName, { target = this.element, detail = {}, prefix = this.identifier, bubbles = true, cancelable = true } = {}) { 8519 const type = prefix ? `${prefix}:${eventName}` : eventName; 8520 const event = new CustomEvent(type, { detail, bubbles, cancelable }); 8521 target.dispatchEvent(event); 8522 return event; 8523 } 8524}; 8525Controller.blessings = [ 8526 ClassPropertiesBlessing, 8527 TargetPropertiesBlessing, 8528 ValuePropertiesBlessing, 8529 OutletPropertiesBlessing 8530]; 8531Controller.targets = []; 8532Controller.outlets = []; 8533Controller.values = {}; 8534
8535// app/javascript/controllers/infinite_scroll_controller.js 8536var infinite_scroll_controller_default = class extends Controller { 8537 static values = { 8538 url: String, 8539 page: { type: Number, default: 1 } 8540 }; 8541 connect() { 8542 this.hidePagination(); 8543 this.createObserver(); 8544 } 8545 disconnect() { 8546 if (this.observer) { 8547 this.observer.disconnect(); 8548 } 8549 this.hideLoadingSpinner(); 8550 } 8551 hidePagination() { 8552 const paginationElements = document.querySelectorAll('.pagination, [class*="paginate"]'); 8553 paginationElements.forEach((element) => { 8554 if (element.querySelector("a") || element.classList.contains("pagination")) { 8555 element.style.display = "none"; 8556 } 8557 }); 8558 } 8559 createObserver() { 8560 this.observer = new IntersectionObserver( 8561 (entries) => { 8562 entries.forEach((entry) => { 8563 if (entry.isIntersecting && !this.loading) { 8564 this.loadMore(); 8565 } 8566 }); 8567 }, 8568 { 8569 rootMargin: "300px" 8570 } 8571 ); 8572 const sentinel = this.element.parentElement.querySelector("#scroll-sentinel"); 8573 if (sentinel) { 8574 this.observer.observe(sentinel); 8575 } 8576 } 8577 showLoadingSpinner() { 8578 this.hideLoadingSpinner(); 8579 const spinner = document.createElement("div"); 8580 spinner.id = "infinite-scroll-spinner"; 8581 spinner.className = "flex justify-center items-center py-8"; 8582 spinner.innerHTML = ` 8583 <div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div> 8584 <span class="ml-3 text-gray-600">Loading more videos...</span> 8585 `; 8586 this.element.appendChild(spinner); 8587 } 8588 hideLoadingSpinner() { 8589 const spinner = this.element.querySelector("#infinite-scroll-spinner"); 8590 if (spinner) { 8591 spinner.remove(); 8592 } 8593 } 8594 async loadMore() { 8595 if (this.loading) return; 8596 this.loading = true; 8597 this.showLoadingSpinner(); 8598 this.pageValue++; 8599 try { 8600 const url = new URL(this.urlValue, window.location.origin); 8601 url.searchParams.set("page", this.pageValue); 8602 const response = await fetch(url, { 8603 headers: { 8604 "Accept": "text/vnd.turbo-stream.html" 8605 } 8606 }); 8607 if (response.ok) { 8608 const turboStream = await response.text(); 8609 Turbo.renderStreamMessage(turboStream); 8610 } else { 8611 this.disconnect(); 8612 } 8613 } catch (error2) { 8614 console.error("Error loading more videos:", error2); 8615 } finally { 8616 this.loading = false; 8617 this.hideLoadingSpinner(); 8618 } 8619 } 8620}; 8621
8622// app/javascript/controllers/dropdown_controller.js 8623var dropdown_controller_default = class extends Controller { 8624 static targets = ["menu"]; 8625 toggle() { 8626 this.menuTarget.classList.toggle("hidden"); 8627 } 8628 hide(event) { 8629 if (!this.element.contains(event.target)) { 8630 this.menuTarget.classList.add("hidden"); 8631 } 8632 } 8633 connect() { 8634 document.addEventListener("click", this.hide.bind(this)); 8635 } 8636 disconnect() { 8637 document.removeEventListener("click", this.hide.bind(this)); 8638 } 8639}; 8640
8641// app/javascript/controllers/modal_controller.js 8642var modal_controller_default = class extends Controller { 8643 static targets = ["modal"]; 8644 connect() { 8645 this.escapeHandler = this.escape.bind(this); 8646 document.addEventListener("keydown", this.escapeHandler); 8647 } 8648 disconnect() { 8649 document.removeEventListener("keydown", this.escapeHandler); 8650 } 8651 open() { 8652 this.modalTarget.classList.remove("hidden"); 8653 document.body.style.overflow = "hidden"; 8654 } 8655 close() { 8656 this.modalTarget.classList.add("hidden"); 8657 document.body.style.overflow = ""; 8658 } 8659 escape(event) { 8660 if (event.key === "Escape" && !this.modalTarget.classList.contains("hidden")) { 8661 this.close(); 8662 } 8663 } 8664 // Close modal when clicking on backdrop 8665 backdropClose(event) { 8666 if (event.target === this.modalTarget) { 8667 this.close(); 8668 } 8669 } 8670}; 8671
8672// app/javascript/controllers/playlist_filter_controller.js 8673var playlist_filter_controller_default = class extends Controller { 8674 static targets = ["search", "playlist"]; 8675 connect() { 8676 this.filter(); 8677 } 8678 filter() { 8679 const query = this.searchTarget.value.toLowerCase().trim(); 8680 this.playlistTargets.forEach((playlist) => { 8681 const title = playlist.dataset.title; 8682 const shouldShow = query === "" || title.includes(query); 8683 playlist.style.display = shouldShow ? "" : "none"; 8684 }); 8685 } 8686}; 8687
8688// app/javascript/controllers/deep_dive_controller.js 8689var deep_dive_controller_default = class extends Controller { 8690 static targets = ["chatMessages", "elapsedText", "channelName", "progressFill", "drillDown", "breadcrumb", "questionList", "menu", "menuButton"]; 8691 static values = { 8692 videoId: String, 8693 startTime: Number, 8694 stopTime: Number, 8695 duration: Number, 8696 pollUrl: String, 8697 drillDownUrl: String 8698 }; 8699 connect() { 8700 this._startTimer(); 8701 this._startPoller(); 8702 this._drillDownCache = {}; 8703 this._breadcrumbPath = []; 8704 this._handleEscape = this._handleEscape.bind(this); 8705 this._handleClickOutside = this._handleClickOutside.bind(this); 8706 document.addEventListener("keydown", this._handleEscape); 8707 document.addEventListener("click", this._handleClickOutside); 8708 } 8709 disconnect() { 8710 this._stopTimer(); 8711 this._stopPoller(); 8712 document.removeEventListener("keydown", this._handleEscape); 8713 document.removeEventListener("click", this._handleClickOutside); 8714 } 8715 // -- Menu Toggle ------------------------------------------------------------ 8716 toggleMenu() { 8717 if (!this.hasMenuTarget) return; 8718 const isOpen = this.menuTarget.classList.toggle("nf-ss-menu--open"); 8719 this.menuTarget.inert = !isOpen; 8720 if (this.hasMenuButtonTarget) { 8721 this.menuButtonTarget.setAttribute("aria-expanded", String(isOpen)); 8722 } 8723 } 8724 closeMenu() { 8725 if (!this.hasMenuTarget) return; 8726 this.menuTarget.classList.remove("nf-ss-menu--open"); 8727 this.menuTarget.inert = true; 8728 if (this.hasMenuButtonTarget) { 8729 this.menuButtonTarget.setAttribute("aria-expanded", "false"); 8730 } 8731 } 8732 _handleEscape(event) { 8733 if (event.key === "Escape") { 8734 this.closeMenu(); 8735 } 8736 } 8737 _handleClickOutside(event) { 8738 if (!this.hasMenuTarget) return; 8739 const menuOpen = this.menuTarget.classList.contains("nf-ss-menu--open"); 8740 if (!menuOpen) return; 8741 const clickedInsideMenu = this.menuTarget.contains(event.target); 8742 const clickedMenuButton = this.hasMenuButtonTarget && this.menuButtonTarget.contains(event.target); 8743 if (!clickedInsideMenu && !clickedMenuButton) { 8744 this.closeMenu(); 8745 } 8746 } 8747 // -- Drill-Down Q&A -------------------------------------------------------- 8748 expandQuestion(event) { 8749 const button = event.currentTarget; 8750 const pathKey = button.dataset.pathKey; 8751 const question = button.dataset.question; 8752 const preloadedAnswer = button.dataset.answer; 8753 const parentQuestion = button.dataset.parentQuestion || ""; 8754 const parentAnswer = button.dataset.parentAnswer || ""; 8755 const questionTrail = this._parseQuestionTrail(button.dataset.questionTrail); 8756 const existingThread = document.getElementById(`drill-thread-${pathKey}`); 8757 if (existingThread) { 8758 existingThread.remove(); 8759 button.classList.remove("nf-ss-question-chip--expanded"); 8760 const isInBreadcrumb = this._breadcrumbPath.some((entry) => entry.pathKey === pathKey || entry.pathKey.startsWith(pathKey + ".")); 8761 if (isInBreadcrumb) { 8762 const depth = pathKey.split(".").length; 8763 this._breadcrumbPath = this._breadcrumbPath.slice(0, depth - 1); 8764 this._renderBreadcrumb(); 8765 } 8766 return; 8767 } 8768 button.classList.add("nf-ss-question-chip--expanded"); 8769 const thread = this._buildThread(pathKey); 8770 button.insertAdjacentElement("afterend", thread); 8771 if (preloadedAnswer) { 8772 const trailForFetch = [...questionTrail, question]; 8773 this._insertAnswer(thread, preloadedAnswer); 8774 this._addLoadingPlaceholder(thread, pathKey); 8775 this._updateBreadcrumb(pathKey, question); 8776 this._fetchSubQuestions(pathKey, question, parentQuestion || question, parentAnswer || preloadedAnswer, trailForFetch); 8777 } else { 8778 const trailForFetch = [...questionTrail, question]; 8779 this._addLoadingPlaceholder(thread, pathKey); 8780 this._updateBreadcrumb(pathKey, question); 8781 this._fetchDrillDown(pathKey, question, parentQuestion, parentAnswer, trailForFetch); 8782 } 8783 this._scrollToElement(thread); 8784 } 8785 retryDrillDown(event) { 8786 const button = event.currentTarget; 8787 const pathKey = button.dataset.pathKey; 8788 const question = button.dataset.question; 8789 const parentQuestion = button.dataset.parentQuestion || ""; 8790 const parentAnswer = button.dataset.parentAnswer || ""; 8791 const questionTrail = this._parseQuestionTrail(button.dataset.questionTrail); 8792 const error2 = document.getElementById(`drill-error-${pathKey}`); 8793 if (error2) error2.remove(); 8794 const thread = document.getElementById(`drill-thread-${pathKey}`); 8795 if (!thread) return; 8796 const subContainer = thread.querySelector(".nf-ss-drill-sub-container"); 8797 if (subContainer) { 8798 this._addLoadingToContainer(subContainer, pathKey); 8799 } 8800 const answerBubble = thread.querySelector(".nf-ss-bubble-companion"); 8801 if (answerBubble) { 8802 this._fetchSubQuestions(pathKey, question, parentQuestion, parentAnswer, questionTrail); 8803 } else { 8804 this._fetchDrillDown(pathKey, question, parentQuestion, parentAnswer, questionTrail); 8805 } 8806 } 8807 navigateBreadcrumb(event) { 8808 const targetDepth = parseInt(event.currentTarget.dataset.depth, 10); 8809 if (this.hasDrillDownTarget) { 8810 const allThreads = this.drillDownTarget.querySelectorAll(".nf-ss-drill-thread"); 8811 allThreads.forEach((thread) => { 8812 const threadPathKey = thread.dataset.pathKey; 8813 const threadDepth = threadPathKey.split(".").length; 8814 if (threadDepth > targetDepth) { 8815 const parentButton = this.drillDownTarget.querySelector( 8816 `.nf-ss-question-chip--expanded[data-path-key="${threadPathKey}"]` 8817 ); 8818 if (parentButton) { 8819 parentButton.classList.remove("nf-ss-question-chip--expanded"); 8820 } 8821 thread.remove(); 8822 } 8823 }); 8824 } 8825 this._breadcrumbPath = this._breadcrumbPath.slice(0, targetDepth); 8826 this._renderBreadcrumb(); 8827 } 8828 // -- Drill-Down Private Helpers --------------------------------------------- 8829 async _fetchDrillDown(pathKey, question, parentQuestion, parentAnswer, questionTrail) { 8830 const cached = this._drillDownCache[pathKey]; 8831 if (cached) { 8832 this._handleDrillDownResult(pathKey, question, parentQuestion, parentAnswer, cached, questionTrail); 8833 return; 8834 } 8835 try { 8836 const response = await fetch(this.drillDownUrlValue, { 8837 method: "POST", 8838 headers: { 8839 "Content-Type": "application/json", 8840 "Accept": "application/json", 8841 "X-CSRF-Token": this._csrfToken() 8842 }, 8843 body: JSON.stringify({ 8844 video_id: this.videoIdValue, 8845 path_key: pathKey, 8846 question, 8847 parent_question: parentQuestion, 8848 parent_answer: parentAnswer, 8849 question_trail: questionTrail 8850 }) 8851 }); 8852 if (!response.ok) { 8853 this._showDrillDownError(pathKey, question, parentQuestion, parentAnswer, questionTrail); 8854 return; 8855 } 8856 const data = await response.json(); 8857 if (data.error === true) { 8858 this._showDrillDownError(pathKey, question, parentQuestion, parentAnswer, questionTrail); 8859 return; 8860 } 8861 this._drillDownCache[pathKey] = data; 8862 this._handleDrillDownResult(pathKey, question, parentQuestion, parentAnswer, data, questionTrail); 8863 } catch (_error) { 8864 this._showDrillDownError(pathKey, question, parentQuestion, parentAnswer, questionTrail); 8865 } 8866 } 8867 async _fetchSubQuestions(pathKey, question, parentQuestion, parentAnswer, questionTrail) { 8868 const cached = this._drillDownCache[pathKey]; 8869 if (cached && cached.sub_questions && cached.sub_questions.length > 0) { 8870 this._removeLoadingPlaceholder(pathKey); 8871 this._insertSubQuestions(pathKey, cached.sub_questions, question, cached.answer || parentAnswer, questionTrail); 8872 return; 8873 } 8874 try { 8875 const response = await fetch(this.drillDownUrlValue, { 8876 method: "POST", 8877 headers: { 8878 "Content-Type": "application/json", 8879 "Accept": "application/json", 8880 "X-CSRF-Token": this._csrfToken() 8881 }, 8882 body: JSON.stringify({ 8883 video_id: this.videoIdValue, 8884 path_key: pathKey, 8885 question, 8886 parent_question: parentQuestion, 8887 parent_answer: parentAnswer, 8888 question_trail: questionTrail 8889 }) 8890 }); 8891 if (!response.ok) { 8892 this._removeLoadingPlaceholder(pathKey); 8893 this._showDrillDownError(pathKey, question, parentQuestion, parentAnswer, questionTrail); 8894 return; 8895 } 8896 const data = await response.json(); 8897 if (!data.sub_questions || data.sub_questions.length === 0) { 8898 this._removeLoadingPlaceholder(pathKey); 8899 this._showDrillDownError(pathKey, question, parentQuestion, parentAnswer, questionTrail); 8900 return; 8901 } 8902 this._drillDownCache[pathKey] = data; 8903 this._removeLoadingPlaceholder(pathKey); 8904 this._insertSubQuestions(pathKey, data.sub_questions, question, data.answer || parentAnswer, questionTrail); 8905 } catch (_error) { 8906 this._removeLoadingPlaceholder(pathKey); 8907 this._showDrillDownError(pathKey, question, parentQuestion, parentAnswer, questionTrail); 8908 } 8909 } 8910 _handleDrillDownResult(pathKey, question, parentQuestion, parentAnswer, data, questionTrail) { 8911 const thread = document.getElementById(`drill-thread-${pathKey}`); 8912 if (!thread) return; 8913 this._removeLoadingPlaceholder(pathKey); 8914 const existingAnswer = thread.querySelector(".nf-ss-bubble-companion"); 8915 if (!existingAnswer && data.answer) { 8916 this._insertAnswer(thread, data.answer, data.answer_html); 8917 } 8918 if (data.sub_questions && data.sub_questions.length > 0) { 8919 this._insertSubQuestions(pathKey, data.sub_questions, question, data.answer || parentAnswer, questionTrail); 8920 } 8921 } 8922 _buildThread(pathKey) { 8923 const thread = document.createElement("div"); 8924 thread.classList.add("nf-ss-drill-thread", "nf-ss-animate-in"); 8925 thread.id = `drill-thread-${pathKey}`; 8926 thread.dataset.pathKey = pathKey; 8927 const depth = pathKey.split(".").length; 8928 if (depth > 1) {
8929 thread.classList.add("nf-ss-drill-thread--nested"); 8930 thread.style.setProperty("--drill-depth", depth); 8931 } 8932 const subContainer = document.createElement("div"); 8933 subContainer.classList.add("nf-ss-drill-sub-container"); 8934 thread.appendChild(subContainer); 8935 return thread; 8936 } 8937 _insertAnswer(thread, answerText, answerHtml) { 8938 const subContainer = thread.querySelector(".nf-ss-drill-sub-container"); 8939 const bubble = document.createElement("div"); 8940 bubble.classList.add("nf-ss-bubble-companion", "nf-ss-animate-in"); 8941 if (answerHtml) { 8942 bubble.innerHTML = answerHtml; 8943 } else { 8944 const paragraphs = answerText.split(/\n\n+/); 8945 bubble.innerHTML = paragraphs.map((p) => `<p>${this._escapeHtml(p.trim())}</p>`).join(""); 8946 } 8947 if (subContainer) { 8948 subContainer.insertBefore(bubble, subContainer.firstChild); 8949 } else { 8950 thread.appendChild(bubble); 8951 } 8952 } 8953 _addLoadingPlaceholder(thread, pathKey) { 8954 const subContainer = thread.querySelector(".nf-ss-drill-sub-container"); 8955 if (subContainer) { 8956 this._addLoadingToContainer(subContainer, pathKey); 8957 } 8958 } 8959 _addLoadingToContainer(container, pathKey) { 8960 const loading = document.createElement("div"); 8961 loading.classList.add("nf-ss-skeleton-chips"); 8962 loading.id = `drill-loading-${pathKey}`; 8963 loading.setAttribute("aria-label", "Loading follow-up questions"); 8964 for (let i = 0; i < 3; i++) { 8965 const chip = document.createElement("div"); 8966 chip.classList.add("nf-ss-skeleton-chip"); 8967 loading.appendChild(chip); 8968 } 8969 container.appendChild(loading); 8970 } 8971 _removeLoadingPlaceholder(pathKey) { 8972 const loading = document.getElementById(`drill-loading-${pathKey}`); 8973 if (loading) loading.remove(); 8974 } 8975 _insertSubQuestions(pathKey, subQuestions, parentQuestion, parentAnswer, questionTrail) { 8976 const thread = document.getElementById(`drill-thread-${pathKey}`); 8977 if (!thread) return; 8978 const subContainer = thread.querySelector(".nf-ss-drill-sub-container"); 8979 if (!subContainer) return; 8980 if (subContainer.querySelector(".nf-ss-question-list")) return; 8981 const list = document.createElement("div"); 8982 list.classList.add("nf-ss-question-list", "nf-ss-animate-in"); 8983 const childTrail = Array.isArray(questionTrail) ? questionTrail : []; 8984 subQuestions.forEach((subQ, index) => { 8985 const subPathKey = `${pathKey}.${index}`; 8986 const button = document.createElement("button"); 8987 button.classList.add("nf-ss-question-chip"); 8988 button.dataset.action = "click->deep-dive#expandQuestion"; 8989 button.dataset.pathKey = subPathKey; 8990 button.dataset.question = subQ; 8991 button.dataset.answer = ""; 8992 button.dataset.parentQuestion = parentQuestion; 8993 button.dataset.parentAnswer = parentAnswer; 8994 button.dataset.questionTrail = JSON.stringify(childTrail); 8995 const textSpan = document.createElement("span"); 8996 textSpan.classList.add("nf-ss-question-chip-text"); 8997 textSpan.textContent = subQ; 8998 button.appendChild(textSpan); 8999 const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); 9000 svg.classList.add("nf-ss-question-chip-chevron"); 9001 svg.setAttribute("width", "16"); 9002 svg.setAttribute("height", "16"); 9003 svg.setAttribute("viewBox", "0 0 16 16"); 9004 svg.setAttribute("fill", "none"); 9005 svg.setAttribute("aria-hidden", "true"); 9006 const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); 9007 path.setAttribute("d", "M6 4l4 4-4 4"); 9008 path.setAttribute("stroke", "currentColor"); 9009 path.setAttribute("stroke-width", "1.5"); 9010 path.setAttribute("stroke-linecap", "round"); 9011 path.setAttribute("stroke-linejoin", "round"); 9012 svg.appendChild(path); 9013 button.appendChild(svg); 9014 list.appendChild(button); 9015 }); 9016 subContainer.appendChild(list); 9017 } 9018 _showDrillDownError(pathKey, question, parentQuestion, parentAnswer, questionTrail) { 9019 const existing = document.getElementById(`drill-error-${pathKey}`); 9020 if (existing) existing.remove(); 9021 const thread = document.getElementById(`drill-thread-${pathKey}`); 9022 if (!thread) return; 9023 const subContainer = thread.querySelector(".nf-ss-drill-sub-container"); 9024 const target = subContainer || thread; 9025 const error2 = document.createElement("div"); 9026 error2.classList.add("nf-ss-drill-error", "nf-ss-animate-in"); 9027 error2.id = `drill-error-${pathKey}`; 9028 const message = document.createElement("span"); 9029 message.textContent = "Couldn't load follow-up questions \u2014 try again."; 9030 error2.appendChild(message); 9031 const retryBtn = document.createElement("button");
9032 retryBtn.classList.add("nf-ss-drill-retry"); 9033 retryBtn.textContent = "Retry"; 9034 retryBtn.dataset.action = "click->deep-dive#retryDrillDown"; 9035 retryBtn.dataset.pathKey = pathKey; 9036 retryBtn.dataset.question = question; 9037 retryBtn.dataset.parentQuestion = parentQuestion; 9038 retryBtn.dataset.parentAnswer = parentAnswer; 9039 retryBtn.dataset.questionTrail = JSON.stringify(Array.isArray(questionTrail) ? questionTrail : []); 9040 error2.appendChild(retryBtn); 9041 target.appendChild(error2); 9042 } 9043 _updateBreadcrumb(pathKey, question) { 9044 const depth = pathKey.split(".").length; 9045 this._breadcrumbPath = this._breadcrumbPath.slice(0, depth - 1); 9046 this._breadcrumbPath.push({ pathKey, question }); 9047 this._renderBreadcrumb(); 9048 } 9049 _renderBreadcrumb() { 9050 if (!this.hasBreadcrumbTarget) return; 9051 const breadcrumb = this.breadcrumbTarget; 9052 if (this._breadcrumbPath.length === 0) { 9053 breadcrumb.hidden = true; 9054 return; 9055 } 9056 breadcrumb.hidden = false; 9057 breadcrumb.innerHTML = ""; 9058 const root = document.createElement("span"); 9059 root.classList.add("nf-ss-breadcrumb-item"); 9060 root.dataset.action = "click->deep-dive#navigateBreadcrumb"; 9061 root.dataset.depth = "0"; 9062 root.textContent = "Questions"; 9063 breadcrumb.appendChild(root); 9064 this._breadcrumbPath.forEach((entry, index) => { 9065 const sep = document.createElement("span"); 9066 sep.classList.add("nf-ss-breadcrumb-separator"); 9067 sep.textContent = "\u203A"; 9068 sep.setAttribute("aria-hidden", "true"); 9069 breadcrumb.appendChild(sep); 9070 const item = document.createElement("span"); 9071 const isLast = index === this._breadcrumbPath.length - 1; 9072 item.classList.add("nf-ss-breadcrumb-item"); 9073 if (isLast) { 9074 item.classList.add("nf-ss-breadcrumb-item--active"); 9075 } 9076 item.dataset.action = "click->deep-dive#navigateBreadcrumb"; 9077 item.dataset.depth = String(index + 1); 9078 item.textContent = this._truncate(entry.question, 30); 9079 breadcrumb.appendChild(item); 9080 }); 9081 } 9082 _parseQuestionTrail(raw) { 9083 if (!raw) return []; 9084 try { 9085 const parsed = JSON.parse(raw); 9086 return Array.isArray(parsed) ? parsed : []; 9087 } catch (_e) { 9088 return []; 9089 } 9090 } 9091 _truncate(text, maxLength) { 9092 if (!text || text.length <= maxLength) return text; 9093 return text.substring(0, maxLength - 1) + "\u2026"; 9094 } 9095 _scrollToElement(element) { 9096 requestAnimationFrame(() => { 9097 element.scrollIntoView({ behavior: "smooth", block: "nearest" }); 9098 }); 9099 } 9100 _csrfToken() { 9101 const meta = document.querySelector('meta[name="csrf-token"]'); 9102 return meta ? meta.getAttribute("content") : ""; 9103 } 9104 // -- Progress Timer -------------------------------------------------------- 9105 _startTimer() { 9106 if (!this.hasDurationValue || this.durationValue === 0) return; 9107 if (!this.hasStartTimeValue || !this.hasStopTimeValue) return; 9108 this._programmeEnded = false; 9109 this._updateTimer(); 9110 this._timerInterval = setInterval(() => this._updateTimer(), 1e3); 9111 } 9112 _stopTimer() { 9113 if (this._timerInterval) { 9114 clearInterval(this._timerInterval); 9115 this._timerInterval = null; 9116 } 9117 } 9118 _updateTimer() { 9119 const nowSeconds = Math.floor(Date.now() / 1e3); 9120 const elapsed = Math.max(0, nowSeconds - this.startTimeValue); 9121 const remaining = Math.max(0, this.stopTimeValue - nowSeconds); 9122 const duration = this.durationValue; 9123 if (elapsed >= duration || remaining <= 0) { 9124 this._showEnded(); 9125 return; 9126 } 9127 const elapsedMin = Math.round(elapsed / 60); 9128 const totalMin = Math.round(duration / 60); 9129 const percentage = Math.min(100, elapsed / duration * 100); 9130 if (this.hasElapsedTextTarget) { 9131 this.elapsedTextTarget.textContent = `${elapsedMin} min in of ${totalMin}`; 9132 } 9133 if (this.hasProgressFillTarget) { 9134 this.progressFillTarget.style.width = `${percentage.toFixed(1)}%`; 9135 } 9136 } 9137 _showEnded() { 9138 if (this._programmeEnded) return; 9139 this._programmeEnded = true; 9140 this._stopTimer(); 9141 if (this.hasElapsedTextTarget) { 9142 this.elapsedTextTarget.textContent = "Ended"; 9143 } 9144 if (this.hasProgressFillTarget) { 9145 this.progressFillTarget.style.width = "100%"; 9146 } 9147 this._pollForProgrammeChange(); 9148 } 9149 // -- Programme Change Polling ---------------------------------------------- 9150 _startPoller() { 9151 if (!this.hasPollUrlValue || this.pollUrlValue === "") return; 9152 this._pollInterval = setInterval(() => this._pollForProgrammeChange(), 3e4); 9153 } 9154 _stopPoller() { 9155 if (this._pollInterval) { 9156 clearInterval(this._pollInterval); 9157 this._pollInterval = null; 9158 } 9159 } 9160 async _pollForProgrammeChange() { 9161 try { 9162 const response = await fetch(this.pollUrlValue, { 9163 headers: { "Accept": "application/json" } 9164 }); 9165 if (!response.ok) return; 9166 const data = await response.json(); 9167 this._handlePollResponse(data); 9168 } catch (_error) { 9169 } 9170 } 9171 _handlePollResponse(data) { 9172 if (!data.playing) { 9173 if (this.hasVideoIdValue && this.videoIdValue !== "") { 9174 this._showBanner("The current programme has ended.", null, null); 9175 } 9176 return; 9177 } 9178 if (!this.hasVideoIdValue || this.videoIdValue === "") { 9179 this._showBanner(`Now Playing: ${data.title}`, "Watch along", this._buildShowUrl()); 9180 return; 9181 } 9182 if (data.zype_video_id !== this.videoIdValue) { 9183 this._showBanner(`Now Playing: ${data.title}`, "Switch", this._buildShowUrl()); 9184 } 9185 } 9186 _showBanner(message, buttonText, buttonUrl) { 9187 if (!this.hasChatMessagesTarget) return; 9188 const existing = this.chatMessagesTarget.querySelector(".nf-ss-programme-change"); 9189 if (existing) existing.remove(); 9190 const banner = document.createElement("div"); 9191 banner.classList.add("nf-ss-programme-change", "nf-ss-animate-in"); 9192 let html = `<span class="nf-ss-programme-change-title">${this._escapeHtml(message)}</span>`;
9193 if (buttonText && buttonUrl) { 9194 html += `<a href="${this._escapeHtml(buttonUrl)}" class="nf-ss-programme-change-btn" data-turbo-action="replace">${this._escapeHtml(buttonText)}</a>`; 9195 } 9196 banner.innerHTML = html; 9197 this.chatMessagesTarget.insertBefore(banner, this.chatMessagesTarget.firstChild); 9198 } 9199 _buildShowUrl() { 9200 return window.location.pathname; 9201 } 9202 _escapeHtml(text) { 9203 const div = document.createElement("div"); 9204 div.textContent = text; 9205 return div.innerHTML; 9206 } 9207}; 9208
9209// app/javascript/lib/sse_reader.js 9210function parseFrame(rawFrame) { 9211 let event = "message"; 9212 const dataLines = []; 9213 for (const line of rawFrame.split("\n")) { 9214 if (line.startsWith("event:")) { 9215 event = line.slice("event:".length).trim(); 9216 } else if (line.startsWith("data:")) { 9217 dataLines.push(line.slice("data:".length).replace(/^ /, "")); 9218 } 9219 } 9220 if (dataLines.length === 0) return null; 9221 let data = null; 9222 try { 9223 data = JSON.parse(dataLines.join("\n")); 9224 } catch (_e) { 9225 data = null; 9226 } 9227 return { event, data }; 9228} 9229async function readSse(body, onFrame) { 9230 const reader = body.getReader(); 9231 const decoder = new TextDecoder(); 9232 let buffer = ""; 9233 try { 9234 for (; ; ) { 9235 const { value, done } = await reader.read(); 9236 if (done) break; 9237 buffer += decoder.decode(value, { stream: true }); 9238 let delimiter; 9239 while ((delimiter = indexOfFrameBreak(buffer)) !== -1) { 9240 const rawFrame = buffer.slice(0, delimiter.index); 9241 buffer = buffer.slice(delimiter.index + delimiter.length); 9242 const frame = parseFrame(rawFrame); 9243 if (frame) onFrame(frame); 9244 } 9245 } 9246 } finally { 9247 reader.releaseLock(); 9248 } 9249} 9250function indexOfFrameBreak(buffer) { 9251 const lf = buffer.indexOf("\n\n"); 9252 const crlf = buffer.indexOf("\r\n\r\n"); 9253 if (lf === -1 && crlf === -1) return -1; 9254 if (crlf === -1 || lf !== -1 && lf < crlf) return { index: lf, length: 2 }; 9255 return { index: crlf, length: 4 }; 9256} 9257
9258// app/javascript/controllers/deep_dive_stream_controller.js 9259var deep_dive_stream_controller_default = class extends Controller { 9260 static targets = ["streaming"]; 9261 static values = { 9262 mode: String, 9263 url: String, 9264 fallback: String 9265 }; 9266 connect() { 9267 this._abortController = new AbortController(); 9268 this._streamBuffer = ""; 9269 this._open(); 9270 } 9271 disconnect() { 9272 if (this._abortController) { 9273 this._abortController.abort(); 9274 this._abortController = null; 9275 } 9276 } 9277 async _open() { 9278 try { 9279 const response = await fetch(this.urlValue, { 9280 headers: { 9281 "Accept": "text/event-stream", 9282 "X-CSRF-Token": this._csrfToken() 9283 }, 9284 signal: this._abortController.signal 9285 }); 9286 if (!response.ok || !response.body) { 9287 this._handleError(); 9288 return; 9289 } 9290 const contentType = response.headers.get("Content-Type") || ""; 9291 if (response.redirected || !contentType.includes("text/event-stream")) { 9292 this._handleError(); 9293 return; 9294 } 9295 await readSse(response.body, (frame) => this._handleFrame(frame)); 9296 } catch (error2) { 9297 if (error2 && error2.name === "AbortError") return; 9298 this._handleError(); 9299 } 9300 } 9301 _handleFrame({ event, data }) { 9302 switch (event) { 9303 case "delta": 9304 this._appendDelta(data); 9305 break; 9306 case "done": 9307 this._finalise(data); 9308 break; 9309 case "error": 9310 this._handleError(data); 9311 break; 9312 } 9313 } 9314 _appendDelta(data) { 9315 if (!data || typeof data.text !== "string") return; 9316 if (!this.hasStreamingTarget) return; 9317 this._streamBuffer += data.text; 9318 this._renderParagraphs(this._streamBuffer); 9319 } 9320 // Renders the accumulated prose as <p> tags split on blank lines, so the 9321 // progressive preview shows paragraph breaks instead of one run-on block. 9322 // Uses textContent per paragraph (XSS-safe); the `done` event later swaps the 9323 // whole element for the canonical server-sanitized HTML. 9324 _renderParagraphs(text) { 9325 let paragraphs = text.split(/\n{2,}/); 9326 if (paragraphs.length > 1 && paragraphs[paragraphs.length - 1].trim() === "") { 9327 paragraphs = paragraphs.slice(0, -1); 9328 } 9329 const target = this.streamingTarget; 9330 paragraphs.forEach((para, i) => { 9331 let p = target.children[i]; 9332 if (!p) { 9333 p = document.createElement("p"); 9334 target.appendChild(p); 9335 } 9336 if (p.textContent !== para) p.textContent = para; 9337 }); 9338 while (target.children.length > paragraphs.length) { 9339 target.removeChild(target.lastChild); 9340 } 9341 } 9342 // Swaps the live preview for the canonical sanitized HTML and enables the 9343 // collapsible-bubble behaviour against the final DOM. 9344 _finalise(data) { 9345 if (this.modeValue === "brief" && data && typeof data.html === "string") { 9346 this.element.innerHTML = data.html; 9347 this._attachCollapsibleBubble(); 9348 } 9349 } 9350 _handleError(data) { 9351 if (!this.hasStreamingTarget) return; 9352 const message = data && data.message || this.fallbackValue || ""; 9353 this.streamingTarget.textContent = message; 9354 } 9355 // Appends (rather than replaces) so this controller stays attached; Stimulus 9356 // observes the mutated data-controller list and connects collapsible-bubble, 9357 // whose connect() runs once against the now-final HTML. 9358 _attachCollapsibleBubble() { 9359 const controllers = (this.element.getAttribute("data-controller") || "").split(/\s+/); 9360 if (!controllers.includes("collapsible-bubble")) { 9361 controllers.push("collapsible-bubble"); 9362 } 9363 this.element.setAttribute("data-controller", controllers.join(" ").trim()); 9364 this.element.setAttribute( 9365 "data-action", 9366 "click->collapsible-bubble#toggle keydown->collapsible-bubble#toggleOnKey" 9367 ); 9368 } 9369 _csrfToken() { 9370 const meta = document.querySelector('meta[name="csrf-token"]'); 9371 return meta ? meta.getAttribute("content") : ""; 9372 } 9373}; 9374
9375// app/javascript/controllers/collapsible_bubble_controller.js 9376var COLLAPSED_CLASS = "nf-ss-bubble-companion--collapsed"; 9377var EXTRAS_CLASS = "nf-ss-bubble-extra"; 9378var EXTRAS_INNER_CLASS = "nf-ss-bubble-extra-inner"; 9379var CHEVRON_CLASS = "nf-ss-bubble-companion-chevron"; 9380var collapsible_bubble_controller_default = class extends Controller { 9381 static values = { 9382 initial: { type: String, default: "expanded" } 9383 }; 9384 connect() { 9385 this._wrapExtras(); 9386 this._injectChevron(); 9387 this._applyAria(); 9388 this._applyInitialState(); 9389 } 9390 disconnect() { 9391 const chevron = this.element.querySelector(`:scope > .${CHEVRON_CLASS}`); 9392 if (chevron) chevron.remove(); 9393 const extras = this.element.querySelector(`:scope > .${EXTRAS_CLASS}`); 9394 if (extras) { 9395 const inner = extras.querySelector(`:scope > .${EXTRAS_INNER_CLASS}`); 9396 if (inner) { 9397 while (inner.firstChild) { 9398 this.element.insertBefore(inner.firstChild, extras); 9399 } 9400 inner.remove(); 9401 } 9402 extras.remove(); 9403 } 9404 } 9405 toggle(event) { 9406 if (event && event.target.closest("a")) return; 9407 const collapsed = this.element.classList.toggle(COLLAPSED_CLASS); 9408 this.element.setAttribute("aria-expanded", collapsed ? "false" : "true"); 9409 } 9410 toggleOnKey(event) { 9411 if (event.key !== "Enter" && event.key !== " ") return; 9412 if (event.target.closest("a")) return; 9413 event.preventDefault(); 9414 this.toggle(event); 9415 } 9416 _wrapExtras() { 9417 const children = Array.from(this.element.children); 9418 const outer = document.createElement("div"); 9419 outer.classList.add(EXTRAS_CLASS); 9420 const inner = document.createElement("div"); 9421 inner.classList.add(EXTRAS_INNER_CLASS); 9422 outer.appendChild(inner); 9423 children.slice(1).forEach((child) => inner.appendChild(child)); 9424 this.element.appendChild(outer); 9425 } 9426 _injectChevron() { 9427 const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); 9428 svg.classList.add(CHEVRON_CLASS); 9429 svg.setAttribute("width", "16"); 9430 svg.setAttribute("height", "16"); 9431 svg.setAttribute("viewBox", "0 0 16 16"); 9432 svg.setAttribute("fill", "none"); 9433 svg.setAttribute("aria-hidden", "true"); 9434 const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); 9435 path.setAttribute("d", "M4 6l4 4 4-4"); 9436 path.setAttribute("stroke", "currentColor"); 9437 path.setAttribute("stroke-width", "1.5"); 9438 path.setAttribute("stroke-linecap", "round"); 9439 path.setAttribute("stroke-linejoin", "round"); 9440 svg.appendChild(path); 9441 this.element.appendChild(svg); 9442 } 9443 _applyAria() { 9444 if (!this.element.hasAttribute("role")) { 9445 this.element.setAttribute("role", "button"); 9446 } 9447 if (!this.element.hasAttribute("tabindex")) { 9448 this.element.setAttribute("tabindex", "0"); 9449 } 9450 } 9451 _applyInitialState() { 9452 const collapsed = this.initialValue === "collapsed"; 9453 this.element.classList.toggle(COLLAPSED_CLASS, collapsed); 9454 this.element.setAttribute("aria-expanded", collapsed ? "false" : "true"); 9455 } 9456}; 9457
9458// app/javascript/controllers/admin_transcriptions_controller.js 9459var admin_transcriptions_controller_default = class extends Controller { 9460 static targets = [ 9461 "toggleConfirmCheckbox", 9462 "toggleConfirmedField", 9463 "bulkButton", 9464 "bulkForm", 9465 "batchSize", 9466 "rowTriggerButton", 9467 "rowTriggerForm" 9468 ]; 9469 static values = { 9470 canaryFresh: Boolean, 9471 softConfirmThreshold: { type: Number, default: 50 } 9472 }; 9473 connect() { 9474 this.syncControls(); 9475 } 9476 toggleChanged() { 9477 this.syncControls(); 9478 } 9479 syncControls() { 9480 const toggleConfirmed = this.hasToggleConfirmCheckboxTarget ? this.toggleConfirmCheckboxTarget.checked : false; 9481 if (this.hasToggleConfirmedFieldTarget) { 9482 this.toggleConfirmedFieldTarget.value = toggleConfirmed ? "1" : "0"; 9483 } 9484 if (this.hasBulkButtonTarget) { 9485 const bulkEnabled = this.canaryFreshValue && toggleConfirmed; 9486 this.bulkButtonTarget.disabled = !bulkEnabled; 9487 } 9488 if (this.hasRowTriggerFormTarget) { 9489 this.rowTriggerFormTargets.forEach((form) => { 9490 const hidden = form.querySelector('input[name="toggle_confirmed"]'); 9491 if (hidden) hidden.value = toggleConfirmed ? "1" : "0"; 9492 }); 9493 } 9494 if (this.hasRowTriggerButtonTarget) { 9495 this.rowTriggerButtonTargets.forEach((btn) => { 9496 btn.disabled = !toggleConfirmed; 9497 }); 9498 } 9499 } 9500 // Submits the enclosing GET form when an input changes (e.g. the 9501 // "Show failed videos" checkbox), reloading the candidate list with the 9502 // updated query-string params. 9503 submitOnChange(event) { 9504 const form = event.target.closest("form"); 9505 if (form) form.requestSubmit(); 9506 } 9507 confirmBulk(event) { 9508 if (this.hasToggleConfirmedFieldTarget && this.hasToggleConfirmCheckboxTarget) { 9509 this.toggleConfirmedFieldTarget.value = this.toggleConfirmCheckboxTarget.checked ? "1" : "0"; 9510 } 9511 if (!this.hasBatchSizeTarget) return; 9512 const batchSize = parseInt(this.batchSizeTarget.value, 10) || 0; 9513 if (batchSize < this.softConfirmThresholdValue) return; 9514 const estimatedSeconds = Math.round(batchSize * 1.25); 9515 const minutes = Math.max(1, Math.round(estimatedSeconds / 60)); 9516 const message = `You are about to trigger ${batchSize} videos at roughly 1.25s/video \u2248 ${minutes} min wall-clock. Continue?`; 9517 if (!window.confirm(message)) { 9518 event.preventDefault(); 9519 } 9520 } 9521}; 9522
9523// app/javascript/controllers/recaptcha_v3_controller.js 9524var recaptcha_v3_controller_default = class extends Controller { 9525 static values = { siteKey: String, action: { type: String, default: "login" } }; 9526 connect() { 9527 this.submitting = false; 9528 } 9529 execute(event) { 9530 if (this.submitting) return; 9531 const field = this.tokenField(); 9532 if (!field) return; 9533 if (typeof grecaptcha === "undefined") return; 9534 event.preventDefault(); 9535 let done = false; 9536 const submitForm = () => { 9537 if (done) return; 9538 done = true; 9539 this.submitting = true; 9540 this.element.requestSubmit(); 9541 }; 9542 const timeout = setTimeout(submitForm, 3e3); 9543 grecaptcha.ready(() => { 9544 grecaptcha.execute(this.siteKeyValue, { action: this.actionValue }).then((token) => { 9545 field.value = token; 9546 }).catch(() => { 9547 }).finally(() => { 9548 clearTimeout(timeout); 9549 submitForm(); 9550 }); 9551 }); 9552 } 9553 tokenField() { 9554 return this.element.querySelector( 9555 `input[name="g-recaptcha-response-data[${this.actionValue}]"]` 9556 ); 9557 } 9558}; 9559
9560// app/javascript/controllers/video_player_seek_controller.js 9561var POLL_INTERVAL_MS = 200; 9562var MAX_POLL_MS = 15e3; 9563var video_player_seek_controller_default = class extends Controller { 9564 static values = { t: Number }; 9565 connect() { 9566 this._seconds = this.hasTValue ? this.tValue : 0; 9567 if (this._seconds <= 0) return; 9568 this._seeked = false; 9569 this._pollElapsed = 0; 9570 this._pollTimer = setInterval(() => this._pollForPlayer(), POLL_INTERVAL_MS); 9571 } 9572 disconnect() { 9573 this._clearPoll(); 9574 this._detachListeners(); 9575 } 9576 _pollForPlayer() { 9577 this._pollElapsed += POLL_INTERVAL_MS; 9578 if (this._pollElapsed >= MAX_POLL_MS) { 9579 this._clearPoll(); 9580 return; 9581 } 9582 const player = window.zypePlayer; 9583 if (!player || typeof player.on !== "function") return; 9584 this._clearPoll(); 9585 this._attachListeners(player); 9586 } 9587 _attachListeners(player) { 9588 this._player = player; 9589 this._onPlayback = () => this._maybeSeek(player); 9590 try { 9591 player.on("playing", this._onPlayback); 9592 player.on("timechanged", this._onPlayback); 9593 } catch (e) { 9594 } 9595 } 9596 _maybeSeek(player) { 9597 if (this._seeked) return; 9598 if (this._adActive(player)) return; 9599 try { 9600 if (typeof player.seek === "function") { 9601 player.seek(this._seconds); 9602 this._seeked = true; 9603 this._detachListeners(); 9604 } 9605 } catch (e) { 9606 } 9607 } 9608 _adActive(player) { 9609 try { 9610 return !!(player.ads && typeof player.ads.isLinearAdActive === "function" && player.ads.isLinearAdActive()); 9611 } catch (e) { 9612 return false; 9613 } 9614 } 9615 _detachListeners() { 9616 if (!this._player || !this._onPlayback) return; 9617 try { 9618 this._player.off("playing", this._onPlayback); 9619 this._player.off("timechanged", this._onPlayback); 9620 } catch (e) { 9621 } 9622 this._player = null; 9623 this._onPlayback = null; 9624 } 9625 _clearPoll() { 9626 if (this._pollTimer) { 9627 clearInterval(this._pollTimer); 9628 this._pollTimer = null; 9629 } 9630 } 9631}; 9632
9633// app/javascript/controllers/index.js 9634var application = Application.start(); 9635application.debug = false; 9636window.Stimulus = application; 9637application.register("infinite-scroll", infinite_scroll_controller_default); 9638application.register("dropdown", dropdown_controller_default); 9639application.register("modal", modal_controller_default); 9640application.register("playlist-filter", playlist_filter_controller_default); 9641application.register("deep-dive", deep_dive_controller_default); 9642application.register("deep-dive-stream", deep_dive_stream_controller_default); 9643application.register("collapsible-bubble", collapsible_bubble_controller_default); 9644application.register("admin-transcriptions", admin_transcriptions_controller_default); 9645application.register("recaptcha-v3", recaptcha_v3_controller_default); 9646application.register("video-player-seek", video_player_seek_controller_default); 9647
9648// app/javascript/application-eb.js 9649Turbo.config.forms.mode = "optin"; 9650/*! Bundled license information: 9651 9652@hotwired/turbo/dist/turbo.es2017-esm.js: 9653 (*! 9654 Turbo 8.0.13 9655 Copyright © 2025 37signals LLC 9656 *) 9657*/ 9658;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.