1import { Controller } from "@hotwired/stimulus"; 2var BackendRequest_default = class { 3 promise; 4 actions; 5 updatedModels; 6 isResolved = false; 7 constructor(promise, actions, updateModels) { 8 this.promise = promise; 9 this.promise.then((response) => { 10 this.isResolved = true; 11 return response; 12 }); 13 this.actions = actions; 14 this.updatedModels = updateModels; 15 } 16 containsOneOfActions(targetedActions) { 17 return this.actions.filter((action) => targetedActions.includes(action)).length > 0; 18 } 19 areAnyModelsUpdated(targetedModels) { 20 return this.updatedModels.filter((model) => targetedModels.includes(model)).length > 0; 21 } 22}; 23var RequestBuilder_default = class { 24 url; 25 method; 26 credentials; 27 constructor(url, method = "post", credentials = "same-origin") { 28 this.url = url; 29 this.method = method; 30 this.credentials = credentials; 31 } 32 buildRequest(props, actions, updated, children, updatedPropsFromParent, files) { 33 const splitUrl = this.url.split("?"); 34 let [url] = splitUrl; 35 const [, queryString] = splitUrl; 36 const params = new URLSearchParams(queryString || ""); 37 const fetchOptions = {}; 38 fetchOptions.credentials = this.credentials; 39 fetchOptions.headers = { 40 Accept: "application/vnd.live-component+html", 41 "X-Requested-With": "XMLHttpRequest", 42 "X-Live-Url": window.location.pathname + window.location.search 43 }; 44 const totalFiles = Object.entries(files).reduce((total, current) => total + current.length, 0); 45 const hasFingerprints = Object.keys(children).length > 0; 46 if (actions.length === 0 && totalFiles === 0 && this.method === "get" && this.willDataFitInUrl(JSON.stringify(props), JSON.stringify(updated), params, JSON.stringify(children), JSON.stringify(updatedPropsFromParent))) { 47 params.set("props", JSON.stringify(props)); 48 params.set("updated", JSON.stringify(updated)); 49 if (Object.keys(updatedPropsFromParent).length > 0) params.set("propsFromParent", JSON.stringify(updatedPropsFromParent)); 50 if (hasFingerprints) params.set("children", JSON.stringify(children)); 51 fetchOptions.method = "GET"; 52 } else { 53 fetchOptions.method = "POST"; 54 const requestData = { 55 props, 56 updated 57 }; 58 if (Object.keys(updatedPropsFromParent).length > 0) requestData.propsFromParent = updatedPropsFromParent; 59 if (hasFingerprints) requestData.children = children; 60 if (actions.length > 0) if (actions.length === 1) { 61 requestData.args = actions[0].args; 62 url += `/${encodeURIComponent(actions[0].name)}`; 63 } else { 64 url += "/_batch"; 65 requestData.actions = actions; 66 } 67 const formData = new FormData(); 68 formData.append("data", JSON.stringify(requestData)); 69 for (const [key, value] of Object.entries(files)) { 70 const length = value.length; 71 for (let i = 0; i < length; ++i) formData.append(key, value[i]); 72 } 73 fetchOptions.body = formData; 74 } 75 const paramsString = params.toString(); 76 return { 77 url: `${url}${paramsString.length > 0 ? `?${paramsString}` : ""}`, 78 fetchOptions 79 }; 80 } 81 willDataFitInUrl(propsJson, updatedJson, params, childrenJson, propsFromParentJson) { 82 return (new URLSearchParams(propsJson + updatedJson + childrenJson + propsFromParentJson).toString() + params.toString()).length < 1500; 83 } 84}; 85var Backend_default = class { 86 requestBuilder; 87 constructor(url, method = "post", credentials = "same-origin") { 88 this.requestBuilder = new RequestBuilder_default(url, method, credentials); 89 } 90 makeRequest(props, actions, updated, children, updatedPropsFromParent, files) { 91 const { url, fetchOptions } = this.requestBuilder.buildRequest(props, actions, updated, children, updatedPropsFromParent, files); 92 return new BackendRequest_default(fetch(url, fetchOptions), actions.map((backendAction) => backendAction.name), Object.keys(updated)); 93 } 94}; 95var BackendResponse_default = class { 96 response; 97 body; 98 liveUrl; 99 download = null; 100 parsePromise = null; 101 constructor(response) { 102 this.response = response; 103 } 104 async getBody() { 105 if (!this.parsePromise) this.parsePromise = this.parse(); 106 await this.parsePromise; 107 return this.body; 108 } 109 getDownload() { 110 return this.download; 111 } 112 getLiveUrl() { 113 if (void 0 === this.liveUrl) this.liveUrl = this.response.headers.get("X-Live-Url"); 114 return this.liveUrl; 115 }
116 getDownloadUrl() { 117 return this.response.headers.get("X-Live-Download-Url"); 118 } 119 isRemoved() { 120 return this.response.headers.has("X-Live-Remove"); 121 } 122 async parse() { 123 const htmlLength = this.response.headers.get("X-Live-Html-Length"); 124 if (null === htmlLength) { 125 this.body = await this.response.text(); 126 return; 127 } 128 const buffer = await this.response.arrayBuffer(); 129 const splitAt = Number.parseInt(htmlLength, 10); 130 this.body = new TextDecoder().decode(buffer.slice(0, splitAt)); 131 this.download = { 132 filename: decodeFilename(this.response.headers.get("X-Live-Download-Filename")), 133 blob: new Blob([buffer.slice(splitAt)], { type: this.response.headers.get("X-Live-Download-Type") ?? "application/octet-stream" }) 134 }; 135 } 136}; 137function decodeFilename(value) { 138 if (!value) return "download"; 139 try { 140 return decodeURIComponent(value) || "download"; 141 } catch { 142 return "download"; 143 } 144} 145function getElementAsTagText(element) { 146 return element.innerHTML ? element.outerHTML.slice(0, element.outerHTML.indexOf(element.innerHTML)) : element.outerHTML; 147} 148let componentMapByElement = /* @__PURE__ */ new WeakMap(); 149let componentMapByComponent = /* @__PURE__ */ new Map(); 150const registerComponent = (component) => { 151 componentMapByElement.set(component.element, component); 152 componentMapByComponent.set(component, component.name); 153}; 154const unregisterComponent = (component) => { 155 componentMapByElement.delete(component.element); 156 componentMapByComponent.delete(component); 157}; 158const getComponent = (element) => new Promise((resolve, reject) => { 159 let count = 0; 160 const maxCount = 10; 161 const interval = setInterval(() => { 162 const component = componentMapByElement.get(element); 163 if (component) { 164 clearInterval(interval); 165 resolve(component); 166 } 167 count++; 168 if (count > maxCount) { 169 clearInterval(interval); 170 reject(/* @__PURE__ */ new Error(`Component not found for element ${getElementAsTagText(element)}`)); 171 } 172 }, 5); 173}); 174const findComponents = (currentComponent, onlyParents, onlyMatchName) => { 175 const components = []; 176 componentMapByComponent.forEach((componentName, component) => { 177 if (onlyParents && (currentComponent === component || !component.element.contains(currentComponent.element))) return; 178 if (onlyMatchName && componentName !== onlyMatchName) return; 179 components.push(component); 180 }); 181 return components; 182}; 183const findChildren = (currentComponent) => { 184 const children = []; 185 componentMapByComponent.forEach((componentName, component) => { 186 if (currentComponent === component) return; 187 if (!currentComponent.element.contains(component.element)) return; 188 let foundChildComponent = false; 189 componentMapByComponent.forEach((childComponentName, childComponent) => { 190 if (foundChildComponent) return; 191 if (childComponent === component) return; 192 if (childComponent.element.contains(component.element)) foundChildComponent = true; 193 }); 194 children.push(component); 195 }); 196 return children; 197}; 198const findParent = (currentComponent) => { 199 let parentElement = currentComponent.element.parentElement; 200 while (parentElement) { 201 const component = componentMapByElement.get(parentElement); 202 if (component) return component; 203 parentElement = parentElement.parentElement; 204 } 205 return null; 206}; 207function parseDirectives(content) { 208 const directives = []; 209 if (!content) return directives; 210 let currentActionName = ""; 211 let currentArgumentValue = ""; 212 let currentArguments = []; 213 let currentModifiers = []; 214 let state = "action"; 215 const getLastActionName = () => { 216 if (currentActionName) return currentActionName; 217 if (directives.length === 0) throw new Error("Could not find any directives"); 218 return directives[directives.length - 1].action; 219 }; 220 const pushInstruction = () => { 221 directives.push({ 222 action: currentActionName, 223 args: currentArguments, 224 modifiers: currentModifiers, 225 getString: () => { 226 return content; 227 } 228 }); 229 currentActionName = ""; 230 currentArgumentValue = ""; 231 currentArguments = []; 232 currentModifiers = []; 233 state = "action"; 234 }; 235 const pushArgument = () => { 236 currentArguments.push(currentArgumentValue.trim()); 237 currentArgumentValue = ""; 238 }; 239 const pushModifier = () => { 240 if (currentArguments.length > 1) throw new Error(`The modifier "${currentActionName}()" does not support multiple arguments.`); 241 currentModifiers.push({ 242 name: currentActionName, 243 value: currentArguments.length > 0 ? currentArguments[0] : null 244 }); 245 currentActionName = ""; 246 currentArguments = []; 247 state = "action"; 248 }; 249 for (let i = 0; i < content.length; i++) { 250 const char = content[i]; 251 switch (state) { 252 case "action": 253 if (char === "(") { 254 state = "arguments"; 255 break; 256 } 257 if (char === " ") { 258 if (currentActionName) pushInstruction(); 259 break; 260 } 261 if (char === "|") { 262 pushModifier(); 263 break; 264 } 265 currentActionName += char; 266 break; 267 case "arguments": 268 if (char === ")") { 269 pushArgument(); 270 state = "after_arguments"; 271 break; 272 } 273 if (char === ",") { 274 pushArgument(); 275 break; 276 } 277 currentArgumentValue += char; 278 break; 279 case "after_arguments": 280 if (char === "|") { 281 pushModifier(); 282 break; 283 } 284 if (char !== " ") throw new Error(`Missing space after ${getLastActionName()}()`); 285 pushInstruction(); 286 break; 287 } 288 } 289 switch (state) { 290 case "action": 291 case "after_arguments": 292 if (currentActionName) pushInstruction(); 293 break; 294 default: throw new Error(`Did you forget to add a closing ")" after "${currentActionName}"?`); 295 } 296 return directives; 297}
298function combineSpacedArray(parts) { 299 const finalParts = []; 300 parts.forEach((part) => { 301 finalParts.push(...trimAll(part).split(" ")); 302 }); 303 return finalParts; 304} 305function trimAll(str) { 306 return str.replace(/[\s]+/g, " ").trim(); 307} 308function normalizeModelName(model) { 309 if (!model.includes("[") && !model.includes("]")) return model; 310 return model.replace(/\[]$/, "").split("[").map((s) => s.replace("]", "")).join("."); 311} 312function getValueFromElement(element, valueStore) { 313 if (element instanceof HTMLInputElement) { 314 if (element.type === "checkbox") { 315 const modelNameData = getModelDirectiveFromElement(element, false); 316 if (modelNameData !== null) { 317 const modelValue = valueStore.get(modelNameData.action); 318 if (Array.isArray(modelValue)) return getMultipleCheckboxValue(element, modelValue); 319 if (Object(modelValue) === modelValue) return getMultipleCheckboxValue(element, Object.values(modelValue)); 320 } 321 if (element.hasAttribute("value")) return element.checked ? element.getAttribute("value") : null; 322 return element.checked; 323 } 324 return inputValue(element); 325 } 326 if (element instanceof HTMLSelectElement) { 327 if (element.multiple) return Array.from(element.selectedOptions).map((el) => el.value); 328 return element.value; 329 } 330 if (element.hasAttribute("data-value")) return element.dataset.value; 331 if ("value" in element) return element.value; 332 if (element.hasAttribute("value")) return element.getAttribute("value"); 333 return null; 334} 335function setValueOnElement(element, value) { 336 if (element instanceof HTMLInputElement) { 337 if (element.type === "file") return; 338 if (element.type === "radio") { 339 element.checked = element.value == value; 340 return; 341 } 342 if (element.type === "checkbox") { 343 if (Array.isArray(value)) element.checked = value.some((val) => val == element.value); 344 else if (element.hasAttribute("value")) element.checked = element.value == value; 345 else element.checked = value; 346 return; 347 } 348 } 349 if (element instanceof HTMLSelectElement) { 350 const arrayWrappedValue = [].concat(value).map((value) => { 351 return `${value}`; 352 }); 353 Array.from(element.options).forEach((option) => { 354 option.selected = arrayWrappedValue.includes(option.value); 355 }); 356 return; 357 } 358 value = value === void 0 ? "" : value; 359 element.value = value; 360} 361function getAllModelDirectiveFromElements(element) { 362 if (!element.dataset.model) return []; 363 const directives = parseDirectives(element.dataset.model); 364 directives.forEach((directive) => { 365 if (directive.args.length > 0) throw new Error(`The data-model="${element.dataset.model}" format is invalid: it does not support passing arguments to the model.`); 366 directive.action = normalizeModelName(directive.action); 367 }); 368 return directives; 369} 370function getModelDirectiveFromElement(element, throwOnMissing = true) { 371 const dataModelDirectives = getAllModelDirectiveFromElements(element); 372 if (dataModelDirectives.length > 0) return dataModelDirectives[0]; 373 if (element.getAttribute("name")) { 374 const formElement = element.closest("form"); 375 if (formElement && "model" in formElement.dataset) { 376 const directive = parseDirectives(formElement.dataset.model || "*")[0]; 377 if (directive.args.length > 0) throw new Error(`The data-model="${formElement.dataset.model}" format is invalid: it does not support passing arguments to the model.`); 378 directive.action = normalizeModelName(element.getAttribute("name")); 379 return directive; 380 } 381 } 382 if (!throwOnMissing) return null; 383 throw new Error(`Cannot determine the model name for "${getElementAsTagText(element)}": the element must either have a "data-model" (or "name" attribute living inside a <form data-model="*">).`); 384} 385function elementBelongsToThisComponent(element, component) { 386 if (component.element === element) return true; 387 if (!component.element.contains(element)) return false; 388 return element.closest("[data-controller~=\"live\"]") === component.element; 389} 390function cloneHTMLElement(element) { 391 const newElement = element.cloneNode(true); 392 if (!(newElement instanceof HTMLElement)) throw new Error("Could not clone element"); 393 return newElement; 394} 395function htmlToElement(html) { 396 const template = document.createElement("template"); 397 html = html.trim(); 398 template.innerHTML = html; 399 if (template.content.childElementCount > 1) throw new Error(`Component HTML contains ${template.content.childElementCount} elements, but only 1 root element is allowed.`); 400 const child = template.content.firstElementChild; 401 if (!child) throw new Error("Child not found"); 402 if (!(child instanceof HTMLElement)) throw new Error(`Created element is not an HTMLElement: ${html.trim()}`); 403 return child; 404} 405const getMultipleCheckboxValue = (element, currentValues) => { 406 const finalValues = [...currentValues]; 407 const value = inputValue(element); 408 const index = currentValues.indexOf(value); 409 if (element.checked) { 410 if (index === -1) finalValues.push(value); 411 return finalValues; 412 } 413 if (index > -1) finalValues.splice(index, 1); 414 return finalValues; 415}; 416const inputValue = (element) => element.dataset.value ? element.dataset.value : element.value; 417function isTextualInputElement(el) { 418 return el instanceof HTMLInputElement && [ 419 "text", 420 "email", 421 "password", 422 "search", 423 "tel", 424 "url" 425 ].includes(el.type); 426} 427function isTextareaElement(el) { 428 return el instanceof HTMLTextAreaElement; 429} 430function isNumericalInputElement(element) { 431 return element instanceof HTMLInputElement && ["number", "range"].includes(element.type); 432} 433var HookManager_default = class {
434 hooks = /* @__PURE__ */ new Map(); 435 register(hookName, callback) { 436 const hooks = this.hooks.get(hookName) || []; 437 hooks.push(callback); 438 this.hooks.set(hookName, hooks); 439 } 440 unregister(hookName, callback) { 441 const hooks = this.hooks.get(hookName) || []; 442 const index = hooks.indexOf(callback); 443 if (index === -1) return; 444 hooks.splice(index, 1); 445 this.hooks.set(hookName, hooks); 446 } 447 triggerHook(hookName, ...args) { 448 (this.hooks.get(hookName) || []).forEach((callback) => { 449 callback(...args); 450 }); 451 } 452}; 453var Idiomorph = (function() { 454 "use strict"; 455 const noOp = () => {}; 456 const defaults = { 457 morphStyle: "outerHTML", 458 callbacks: { 459 beforeNodeAdded: noOp, 460 afterNodeAdded: noOp, 461 beforeNodeMorphed: noOp, 462 afterNodeMorphed: noOp, 463 beforeNodeRemoved: noOp, 464 afterNodeRemoved: noOp, 465 beforeAttributeUpdated: noOp 466 }, 467 head: { 468 style: "merge", 469 shouldPreserve: (elt) => elt.getAttribute("im-preserve") === "true", 470 shouldReAppend: (elt) => elt.getAttribute("im-re-append") === "true", 471 shouldRemove: noOp, 472 afterHeadMorphed: noOp 473 }, 474 restoreFocus: true 475 }; 476 function morph(oldNode, newContent, config = {}) { 477 oldNode = normalizeElement(oldNode); 478 const newNode = normalizeParent(newContent); 479 const ctx = createMorphContext(oldNode, newNode, config); 480 const morphedNodes = saveAndRestoreFocus(ctx, () => { 481 return withHeadBlocking(ctx, oldNode, newNode, (ctx) => { 482 if (ctx.morphStyle === "innerHTML") { 483 morphChildren(ctx, oldNode, newNode); 484 return Array.from(oldNode.childNodes); 485 } else return morphOuterHTML(ctx, oldNode, newNode); 486 }); 487 }); 488 ctx.pantry.remove(); 489 return morphedNodes; 490 } 491 function morphOuterHTML(ctx, oldNode, newNode) { 492 const oldParent = normalizeParent(oldNode); 493 morphChildren(ctx, oldParent, newNode, oldNode, oldNode.nextSibling); 494 return Array.from(oldParent.childNodes); 495 } 496 function saveAndRestoreFocus(ctx, fn) { 497 if (!ctx.config.restoreFocus) return fn(); 498 let activeElement = document.activeElement; 499 if (!(activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement)) return fn(); 500 const { id: activeElementId, selectionStart, selectionEnd } = activeElement; 501 const results = fn(); 502 if (activeElementId && activeElementId !== document.activeElement?.getAttribute("id")) { 503 activeElement = ctx.target.querySelector(`[id="${activeElementId}"]`); 504 activeElement?.focus(); 505 } 506 if (activeElement && !activeElement.selectionEnd && selectionEnd) activeElement.setSelectionRange(selectionStart, selectionEnd); 507 return results; 508 } 509 const morphChildren = (function() { 510 function morphChildren(ctx, oldParent, newParent, insertionPoint = null, endPoint = null) { 511 if (oldParent instanceof HTMLTemplateElement && newParent instanceof HTMLTemplateElement) { 512 oldParent = oldParent.content; 513 newParent = newParent.content; 514 } 515 insertionPoint ||= oldParent.firstChild; 516 for (const newChild of newParent.childNodes) { 517 if (insertionPoint && insertionPoint != endPoint) { 518 const bestMatch = findBestMatch(ctx, newChild, insertionPoint, endPoint); 519 if (bestMatch) { 520 if (bestMatch !== insertionPoint) removeNodesBetween(ctx, insertionPoint, bestMatch); 521 morphNode(bestMatch, newChild, ctx); 522 insertionPoint = bestMatch.nextSibling; 523 continue; 524 } 525 } 526 if (newChild instanceof Element) { 527 const newChildId = newChild.getAttribute("id"); 528 if (ctx.persistentIds.has(newChildId)) { 529 const movedChild = moveBeforeById(oldParent, newChildId, insertionPoint, ctx); 530 morphNode(movedChild, newChild, ctx); 531 insertionPoint = movedChild.nextSibling; 532 continue; 533 } 534 } 535 const insertedNode = createNode(oldParent, newChild, insertionPoint, ctx); 536 if (insertedNode) insertionPoint = insertedNode.nextSibling; 537 } 538 while (insertionPoint && insertionPoint != endPoint) { 539 const tempNode = insertionPoint; 540 insertionPoint = insertionPoint.nextSibling; 541 removeNode(ctx, tempNode); 542 } 543 } 544 function createNode(oldParent, newChild, insertionPoint, ctx) { 545 if (ctx.callbacks.beforeNodeAdded(newChild) === false) return null; 546 if (ctx.idMap.has(newChild)) { 547 const newEmptyChild = document.createElement(newChild.tagName); 548 oldParent.insertBefore(newEmptyChild, insertionPoint); 549 morphNode(newEmptyChild, newChild, ctx); 550 ctx.callbacks.afterNodeAdded(newEmptyChild); 551 return newEmptyChild; 552 } else {
553 const newClonedChild = document.importNode(newChild, true); 554 oldParent.insertBefore(newClonedChild, insertionPoint); 555 ctx.callbacks.afterNodeAdded(newClonedChild); 556 return newClonedChild; 557 } 558 } 559 const findBestMatch = (function() { 560 function findBestMatch(ctx, node, startPoint, endPoint) { 561 let softMatch = null; 562 let nextSibling = node.nextSibling; 563 let siblingSoftMatchCount = 0; 564 let cursor = startPoint; 565 while (cursor && cursor != endPoint) { 566 if (isSoftMatch(cursor, node)) { 567 if (isIdSetMatch(ctx, cursor, node)) return cursor; 568 if (softMatch === null) { 569 if (!ctx.idMap.has(cursor)) softMatch = cursor; 570 } 571 } 572 if (softMatch === null && nextSibling && isSoftMatch(cursor, nextSibling)) { 573 siblingSoftMatchCount++; 574 nextSibling = nextSibling.nextSibling; 575 if (siblingSoftMatchCount >= 2) softMatch = void 0; 576 } 577 if (ctx.activeElementAndParents.includes(cursor)) break; 578 cursor = cursor.nextSibling; 579 } 580 return softMatch || null; 581 } 582 function isIdSetMatch(ctx, oldNode, newNode) { 583 let oldSet = ctx.idMap.get(oldNode); 584 let newSet = ctx.idMap.get(newNode);
585 if (!newSet || !oldSet) return false; 586 for (const id of oldSet) if (newSet.has(id)) return true; 587 return false; 588 } 589 function isSoftMatch(oldNode, newNode) { 590 const oldElt = oldNode; 591 const newElt = newNode; 592 return oldElt.nodeType === newElt.nodeType && oldElt.tagName === newElt.tagName && (!oldElt.getAttribute?.("id") || oldElt.getAttribute?.("id") === newElt.getAttribute?.("id")); 593 } 594 return findBestMatch; 595 })(); 596 function removeNode(ctx, node) { 597 if (ctx.idMap.has(node)) moveBefore(ctx.pantry, node, null); 598 else { 599 if (ctx.callbacks.beforeNodeRemoved(node) === false) return; 600 node.parentNode?.removeChild(node); 601 ctx.callbacks.afterNodeRemoved(node); 602 } 603 } 604 function removeNodesBetween(ctx, startInclusive, endExclusive) { 605 let cursor = startInclusive; 606 while (cursor && cursor !== endExclusive) { 607 let tempNode = cursor; 608 cursor = cursor.nextSibling; 609 removeNode(ctx, tempNode); 610 } 611 return cursor; 612 } 613 function moveBeforeById(parentNode, id, after, ctx) { 614 const target = ctx.target.getAttribute?.("id") === id && ctx.target || ctx.target.querySelector(`[id="${id}"]`) || ctx.pantry.querySelector(`[id="${id}"]`); 615 removeElementFromAncestorsIdMaps(target, ctx); 616 moveBefore(parentNode, target, after); 617 return target; 618 } 619 function removeElementFromAncestorsIdMaps(element, ctx) { 620 const id = element.getAttribute("id"); 621 while (element = element.parentNode) { 622 let idSet = ctx.idMap.get(element); 623 if (idSet) { 624 idSet.delete(id); 625 if (!idSet.size) ctx.idMap.delete(element); 626 } 627 } 628 } 629 function moveBefore(parentNode, element, after) { 630 if (parentNode.moveBefore) try { 631 parentNode.moveBefore(element, after); 632 } catch (e) { 633 parentNode.insertBefore(element, after); 634 } 635 else parentNode.insertBefore(element, after); 636 } 637 return morphChildren; 638 })(); 639 const morphNode = (function() { 640 function morphNode(oldNode, newContent, ctx) { 641 if (ctx.ignoreActive && oldNode === document.activeElement) return null; 642 if (ctx.callbacks.beforeNodeMorphed(oldNode, newContent) === false) return oldNode; 643 if (oldNode instanceof HTMLHeadElement && ctx.head.ignore) {} else if (oldNode instanceof HTMLHeadElement && ctx.head.style !== "morph") handleHeadElement(oldNode, newContent, ctx); 644 else { 645 morphAttributes(oldNode, newContent, ctx); 646 if (!ignoreValueOfActiveElement(oldNode, ctx)) morphChildren(ctx, oldNode, newContent); 647 } 648 ctx.callbacks.afterNodeMorphed(oldNode, newContent); 649 return oldNode; 650 } 651 function morphAttributes(oldNode, newNode, ctx) { 652 let type = newNode.nodeType; 653 if (type === 1) { 654 const oldElt = oldNode; 655 const newElt = newNode; 656 const oldAttributes = oldElt.attributes; 657 const newAttributes = newElt.attributes; 658 for (const newAttribute of newAttributes) { 659 if (ignoreAttribute(newAttribute.name, oldElt, "update", ctx)) continue; 660 if (oldElt.getAttribute(newAttribute.name) !== newAttribute.value) oldElt.setAttribute(newAttribute.name, newAttribute.value); 661 } 662 for (let i = oldAttributes.length - 1; 0 <= i; i--) { 663 const oldAttribute = oldAttributes[i]; 664 if (!oldAttribute) continue; 665 if (!newElt.hasAttribute(oldAttribute.name)) { 666 if (ignoreAttribute(oldAttribute.name, oldElt, "remove", ctx)) continue; 667 oldElt.removeAttribute(oldAttribute.name); 668 } 669 } 670 if (!ignoreValueOfActiveElement(oldElt, ctx)) syncInputValue(oldElt, newElt, ctx); 671 } 672 if (type === 8 || type === 3) { 673 if (oldNode.nodeValue !== newNode.nodeValue) oldNode.nodeValue = newNode.nodeValue; 674 } 675 } 676 function syncInputValue(oldElement, newElement, ctx) { 677 if (oldElement instanceof HTMLInputElement && newElement instanceof HTMLInputElement && newElement.type !== "file") { 678 let newValue = newElement.value; 679 let oldValue = oldElement.value; 680 syncBooleanAttribute(oldElement, newElement, "checked", ctx); 681 syncBooleanAttribute(oldElement, newElement, "disabled", ctx); 682 if (!newElement.hasAttribute("value")) { 683 if (!ignoreAttribute("value", oldElement, "remove", ctx)) { 684 oldElement.value = ""; 685 oldElement.removeAttribute("value"); 686 } 687 } else if (oldValue !== newValue) { 688 if (!ignoreAttribute("value", oldElement, "update", ctx)) { 689 oldElement.setAttribute("value", newValue); 690 oldElement.value = newValue; 691 } 692 } 693 } else if (oldElement instanceof HTMLOptionElement && newElement instanceof HTMLOptionElement) syncBooleanAttribute(oldElement, newElement, "selected", ctx); 694 else if (oldElement instanceof HTMLTextAreaElement && newElement instanceof HTMLTextAreaElement) { 695 let newValue = newElement.value; 696 let oldValue = oldElement.value; 697 if (ignoreAttribute("value", oldElement, "update", ctx)) return; 698 if (newValue !== oldValue) oldElement.value = newValue; 699 if (oldElement.firstChild && oldElement.firstChild.nodeValue !== newValue) oldElement.firstChild.nodeValue = newValue; 700 } 701 } 702 function syncBooleanAttribute(oldElement, newElement, attributeName, ctx) { 703 const newLiveValue = newElement[attributeName]; 704 if (newLiveValue !== oldElement[attributeName]) { 705 const ignoreUpdate = ignoreAttribute(attributeName, oldElement, "update", ctx); 706 if (!ignoreUpdate) oldElement[attributeName] = newElement[attributeName]; 707 if (newLiveValue) { 708 if (!ignoreUpdate) oldElement.setAttribute(attributeName, ""); 709 } else if (!ignoreAttribute(attributeName, oldElement, "remove", ctx)) oldElement.removeAttribute(attributeName); 710 } 711 } 712 function ignoreAttribute(attr, element, updateType, ctx) { 713 if (attr === "value" && ctx.ignoreActiveValue && element === document.activeElement) return true; 714 return ctx.callbacks.beforeAttributeUpdated(attr, element, updateType) === false; 715 }
716 function ignoreValueOfActiveElement(possibleActiveElement, ctx) { 717 return !!ctx.ignoreActiveValue && possibleActiveElement === document.activeElement && possibleActiveElement !== document.body; 718 } 719 return morphNode; 720 })(); 721 function withHeadBlocking(ctx, oldNode, newNode, callback) { 722 if (ctx.head.block) { 723 const oldHead = oldNode.querySelector("head"); 724 const newHead = newNode.querySelector("head"); 725 if (oldHead && newHead) { 726 const promises = handleHeadElement(oldHead, newHead, ctx); 727 return Promise.all(promises).then(() => { 728 return callback(Object.assign(ctx, { head: { 729 block: false, 730 ignore: true 731 } })); 732 }); 733 } 734 } 735 return callback(ctx); 736 } 737 function handleHeadElement(oldHead, newHead, ctx) { 738 let added = []; 739 let removed = []; 740 let preserved = []; 741 let nodesToAppend = []; 742 let srcToNewHeadNodes = /* @__PURE__ */ new Map(); 743 for (const newHeadChild of newHead.children) srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild); 744 for (const currentHeadElt of oldHead.children) { 745 let inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML); 746 let isReAppended = ctx.head.shouldReAppend(currentHeadElt); 747 let isPreserved = ctx.head.shouldPreserve(currentHeadElt); 748 if (inNewContent || isPreserved) if (isReAppended) removed.push(currentHeadElt); 749 else { 750 srcToNewHeadNodes.delete(currentHeadElt.outerHTML); 751 preserved.push(currentHeadElt); 752 } 753 else if (ctx.head.style === "append") { 754 if (isReAppended) { 755 removed.push(currentHeadElt); 756 nodesToAppend.push(currentHeadElt); 757 } 758 } else if (ctx.head.shouldRemove(currentHeadElt) !== false) removed.push(currentHeadElt); 759 } 760 nodesToAppend.push(...srcToNewHeadNodes.values()); 761 let promises = []; 762 for (const newNode of nodesToAppend) { 763 let newElt = document.createRange().createContextualFragment(newNode.outerHTML).firstChild; 764 if (ctx.callbacks.beforeNodeAdded(newElt) !== false) { 765 if ("href" in newElt && newElt.href || "src" in newElt && newElt.src) { 766 /** @type {(result?: any) => void} */ let resolve; 767 let promise = new Promise(function(_resolve) { 768 resolve = _resolve; 769 }); 770 newElt.addEventListener("load", function() { 771 resolve(); 772 }); 773 promises.push(promise); 774 } 775 oldHead.appendChild(newElt); 776 ctx.callbacks.afterNodeAdded(newElt); 777 added.push(newElt); 778 } 779 } 780 for (const removedElement of removed) if (ctx.callbacks.beforeNodeRemoved(removedElement) !== false) { 781 oldHead.removeChild(removedElement); 782 ctx.callbacks.afterNodeRemoved(removedElement); 783 } 784 ctx.head.afterHeadMorphed(oldHead, { 785 added, 786 kept: preserved, 787 removed 788 }); 789 return promises; 790 } 791 const createMorphContext = (function() { 792 function createMorphContext(oldNode, newContent, config) { 793 const { persistentIds, idMap } = createIdMaps(oldNode, newContent); 794 const mergedConfig = mergeDefaults(config); 795 const morphStyle = mergedConfig.morphStyle || "outerHTML"; 796 if (!["innerHTML", "outerHTML"].includes(morphStyle)) throw `Do not understand how to morph style ${morphStyle}`; 797 return { 798 target: oldNode, 799 newContent, 800 config: mergedConfig, 801 morphStyle, 802 ignoreActive: mergedConfig.ignoreActive, 803 ignoreActiveValue: mergedConfig.ignoreActiveValue, 804 restoreFocus: mergedConfig.restoreFocus, 805 idMap, 806 persistentIds, 807 pantry: createPantry(), 808 activeElementAndParents: createActiveElementAndParents(oldNode), 809 callbacks: mergedConfig.callbacks, 810 head: mergedConfig.head 811 }; 812 } 813 function mergeDefaults(config) { 814 let finalConfig = Object.assign({}, defaults); 815 Object.assign(finalConfig, config); 816 finalConfig.callbacks = Object.assign({}, defaults.callbacks, config.callbacks); 817 finalConfig.head = Object.assign({}, defaults.head, config.head); 818 return finalConfig; 819 } 820 function createPantry() { 821 const pantry = document.createElement("div"); 822 pantry.hidden = true; 823 document.body.insertAdjacentElement("afterend", pantry); 824 return pantry; 825 } 826 function createActiveElementAndParents(oldNode) { 827 let activeElementAndParents = []; 828 let elt = document.activeElement; 829 if (elt?.tagName !== "BODY" && oldNode.contains(elt)) while (elt) { 830 activeElementAndParents.push(elt); 831 if (elt === oldNode) break; 832 elt = elt.parentElement; 833 } 834 return activeElementAndParents; 835 } 836 function findIdElements(root) { 837 let elements = Array.from(root.querySelectorAll("[id]")); 838 if (root.getAttribute?.("id")) elements.push(root); 839 return elements; 840 } 841 function populateIdMapWithTree(idMap, persistentIds, root, elements) { 842 for (const elt of elements) { 843 const id = elt.getAttribute("id"); 844 if (persistentIds.has(id)) {
845 let current = elt; 846 while (current) { 847 let idSet = idMap.get(current); 848 if (idSet == null) { 849 idSet = /* @__PURE__ */ new Set(); 850 idMap.set(current, idSet); 851 } 852 idSet.add(id); 853 if (current === root) break; 854 current = current.parentElement; 855 } 856 } 857 } 858 } 859 function createIdMaps(oldContent, newContent) { 860 const oldIdElements = findIdElements(oldContent); 861 const newIdElements = findIdElements(newContent); 862 const persistentIds = createPersistentIds(oldIdElements, newIdElements); 863 let idMap = /* @__PURE__ */ new Map(); 864 populateIdMapWithTree(idMap, persistentIds, oldContent, oldIdElements); 865 populateIdMapWithTree(idMap, persistentIds, newContent.__idiomorphRoot || newContent, newIdElements); 866 return { 867 persistentIds, 868 idMap 869 }; 870 } 871 function createPersistentIds(oldIdElements, newIdElements) { 872 let duplicateIds = /* @__PURE__ */ new Set(); 873 let oldIdTagNameMap = /* @__PURE__ */ new Map(); 874 for (const { id, tagName } of oldIdElements) if (oldIdTagNameMap.has(id)) duplicateIds.add(id); 875 else oldIdTagNameMap.set(id, tagName); 876 let persistentIds = /* @__PURE__ */ new Set(); 877 for (const { id, tagName } of newIdElements) if (persistentIds.has(id)) duplicateIds.add(id); 878 else if (oldIdTagNameMap.get(id) === tagName) persistentIds.add(id); 879 for (const id of duplicateIds) persistentIds.delete(id); 880 return persistentIds; 881 } 882 return createMorphContext; 883 })(); 884 const { normalizeElement, normalizeParent } = (function() { 885 const generatedByIdiomorph = /* @__PURE__ */ new WeakSet(); 886 function normalizeElement(content) { 887 if (content instanceof Document) return content.documentElement; 888 else return content; 889 } 890 function normalizeParent(newContent) { 891 if (newContent == null) return document.createElement("div"); 892 else if (typeof newContent === "string") return normalizeParent(parseContent(newContent)); 893 else if (generatedByIdiomorph.has(newContent)) return newContent; 894 else if (newContent instanceof Node) if (newContent.parentNode) return new SlicedParentNode(newContent); 895 else { 896 const dummyParent = document.createElement("div"); 897 dummyParent.append(newContent); 898 return dummyParent; 899 } 900 else { 901 const dummyParent = document.createElement("div"); 902 for (const elt of [...newContent]) dummyParent.append(elt); 903 return dummyParent; 904 } 905 } 906 class SlicedParentNode { 907 constructor(node) { 908 this.originalNode = node; 909 this.realParentNode = node.parentNode; 910 this.previousSibling = node.previousSibling; 911 this.nextSibling = node.nextSibling; 912 } 913 get childNodes() { 914 const nodes = []; 915 let cursor = this.previousSibling ? this.previousSibling.nextSibling : this.realParentNode.firstChild; 916 while (cursor && cursor != this.nextSibling) { 917 nodes.push(cursor); 918 cursor = cursor.nextSibling; 919 } 920 return nodes; 921 } 922 querySelectorAll(selector) { 923 return this.childNodes.reduce((results, node) => { 924 if (node instanceof Element) { 925 if (node.matches(selector)) results.push(node); 926 const nodeList = node.querySelectorAll(selector); 927 for (let i = 0; i < nodeList.length; i++) results.push(nodeList[i]); 928 } 929 return results; 930 }, []); 931 } 932 insertBefore(node, referenceNode) { 933 return this.realParentNode.insertBefore(node, referenceNode); 934 } 935 moveBefore(node, referenceNode) { 936 return this.realParentNode.moveBefore(node, referenceNode); 937 } 938 get __idiomorphRoot() { 939 return this.originalNode; 940 } 941 } 942 function parseContent(newContent) { 943 let parser = new DOMParser(); 944 let contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, ""); 945 if (contentWithSvgsRemoved.match(/<\/html>/) || contentWithSvgsRemoved.match(/<\/head>/) || contentWithSvgsRemoved.match(/<\/body>/)) { 946 let content = parser.parseFromString(newContent, "text/html"); 947 if (contentWithSvgsRemoved.match(/<\/html>/)) { 948 generatedByIdiomorph.add(content); 949 return content; 950 } else { 951 let htmlElement = content.firstChild; 952 if (htmlElement) generatedByIdiomorph.add(htmlElement); 953 return htmlElement; 954 } 955 } else { 956 let content = parser.parseFromString("<body><template>" + newContent + "</template></body>", "text/html").body.querySelector("template").content; 957 generatedByIdiomorph.add(content); 958 return content; 959 } 960 } 961 return { 962 normalizeElement, 963 normalizeParent 964 }; 965 })(); 966 return { 967 morph, 968 defaults 969 }; 970})(); 971function normalizeAttributesForComparison(element) { 972 if (!(element instanceof HTMLInputElement && element.type === "file")) { 973 if ("value" in element) element.setAttribute("value", element.value); 974 else if (element.hasAttribute("value")) element.setAttribute("value", ""); 975 }
976 Array.from(element.children).forEach((child) => { 977 normalizeAttributesForComparison(child); 978 }); 979} 980const syncAttributes = (fromEl, toEl) => { 981 for (let i = 0; i < fromEl.attributes.length; i++) { 982 const attr = fromEl.attributes[i]; 983 toEl.setAttribute(attr.name, attr.value); 984 } 985}; 986const setId = (element, id) => { 987 if (id === null) element.removeAttribute("id"); 988 else element.setAttribute("id", id); 989}; 990function executeMorphdom(rootFromElement, rootToElement, modifiedFieldElements, getElementValue, externalMutationTracker, childComponents = []) { 991 const externallyChangedIds = /* @__PURE__ */ new Map(); 992 try { 993 for (const [element, originalId] of externalMutationTracker.getOriginalIds()) { 994 externallyChangedIds.set(element, element.getAttribute("id")); 995 setId(element, originalId); 996 } 997 for (const { element, id } of childComponents) if (id && element.getAttribute("id") !== id) { 998 externallyChangedIds.set(element, element.getAttribute("id")); 999 setId(element, id); 1000 } 1001 const originalElementIdsToSwapAfter = []; 1002 const originalElementsToPreserve = /* @__PURE__ */ new Map(); 1003 const markElementAsNeedingPostMorphSwap = (id, replaceWithClone) => { 1004 const oldElement = originalElementsToPreserve.get(id); 1005 if (!(oldElement instanceof HTMLElement)) throw new Error(`Original element with id ${id} not found`); 1006 originalElementIdsToSwapAfter.push(id); 1007 if (!replaceWithClone) return null; 1008 const clonedOldElement = cloneHTMLElement(oldElement); 1009 oldElement.replaceWith(clonedOldElement); 1010 return clonedOldElement; 1011 }; 1012 rootToElement.querySelectorAll("[data-live-preserve]").forEach((newElement) => { 1013 const id = newElement.id; 1014 if (!id) throw new Error("The data-live-preserve attribute requires an id attribute to be set on the element"); 1015 const oldElement = rootFromElement.querySelector(`#${id}`); 1016 if (!(oldElement instanceof HTMLElement)) throw new Error(`The element with id "${id}" was not found in the original HTML`); 1017 newElement.removeAttribute("data-live-preserve"); 1018 originalElementsToPreserve.set(id, oldElement); 1019 syncAttributes(newElement, oldElement); 1020 }); 1021 Idiomorph.morph(rootFromElement, rootToElement, { callbacks: { 1022 beforeNodeMorphed: (fromEl, toEl) => { 1023 if (!(fromEl instanceof Element) || !(toEl instanceof Element)) return true; 1024 if (fromEl === rootFromElement) return true; 1025 if (fromEl.id && originalElementsToPreserve.has(fromEl.id)) { 1026 if (fromEl.id === toEl.id) return false; 1027 const clonedFromEl = markElementAsNeedingPostMorphSwap(fromEl.id, true); 1028 if (!clonedFromEl) throw new Error("missing clone"); 1029 Idiomorph.morph(clonedFromEl, toEl); 1030 return false; 1031 } 1032 if (fromEl instanceof HTMLElement && toEl instanceof HTMLElement) { 1033 if (typeof fromEl.__x !== "undefined") { 1034 if (!window.Alpine) throw new Error("Unable to access Alpine.js though the global window.Alpine variable. Please make sure Alpine.js is loaded before Symfony UX LiveComponent."); 1035 if (typeof window.Alpine.morph !== "function") throw new Error("Unable to access Alpine.js morph function. Please make sure the Alpine.js Morp
1035h plugin is installed and loaded, see https://alpinejs.dev/plugins/morph for more information."); 1036 window.Alpine.morph(fromEl.__x, toEl); 1037 } 1038 if (externalMutationTracker.wasElementAdded(fromEl)) { 1039 const id = fromEl.getAttribute("id"); 1040 if (id && id === toEl.getAttribute("id")) return false; 1041 toEl.before(document.createComment("")); 1042 return false; 1043 } 1044 if (modifiedFieldElements.includes(fromEl)) setValueOnElement(toEl, getElementValue(fromEl)); 1045 if (fromEl === document.activeElement && fromEl !== document.body && null !== getModelDirectiveFromElement(fromEl, false)) setValueOnElement(toEl, getElementValue(fromEl)); 1046 const elementChanges = externalMutationTracker.getChangedElement(fromEl); 1047 if (elementChanges) { 1048 const serverId = toEl.getAttribute("id"); 1049 elementChanges.applyToElement(toEl); 1050 if (externallyChangedIds.has(fromEl)) setId(toEl, serverId); 1051 } 1052 if (fromEl.nodeName.toUpperCase() !== "OPTION" && fromEl.isEqualNode(toEl)) { 1053 const normalizedFromEl = cloneHTMLElement(fromEl); 1054 normalizeAttributesForComparison(normalizedFromEl); 1055 const normalizedToEl = cloneHTMLElement(toEl); 1056 normalizeAttributesForComparison(normalizedToEl); 1057 if (normalizedFromEl.isEqualNode(normalizedToEl)) return false; 1058 } 1059 } 1060 if (fromEl.hasAttribute("data-skip-morph") || fromEl.id && fromEl.id !== toEl.id) { 1061 fromEl.innerHTML = toEl.innerHTML; 1062 return true; 1063 } 1064 if (fromEl.parentElement?.hasAttribute("data-skip-morph")) return false; 1065 return !fromEl.hasAttribute("data-live-ignore"); 1066 }, 1067 beforeNodeRemoved(node) { 1068 if (!(node instanceof HTMLElement)) return true; 1069 if (node.id && originalElementsToPreserve.has(node.id)) { 1070 markElementAsNeedingPostMorphSwap(node.id, false); 1071 return true; 1072 } 1073 if (externalMutationTracker.wasElementAdded(node)) return false; 1074 return !node.hasAttribute("data-live-ignore"); 1075 } 1076 } }); 1077 originalElementIdsToSwapAfter.forEach((id) => { 1078 const newElement = rootFromElement.querySelector(`#${id}`); 1079 const originalElement = originalElementsToPreserve.get(id); 1080 if (!(newElement instanceof HTMLElement) || !(originalElement instanceof HTMLElement)) throw new Error("Missing elements."); 1081 newElement.replaceWith(originalElement); 1082 }); 1083 } finally { 1084 for (const [element, id] of externallyChangedIds) setId(element, id); 1085 } 1086} 1087var ChangingItemsTracker_default = class { 1088 changedItems = /* @__PURE__ */ new Map(); 1089 removedItems = /* @__PURE__ */ new Map(); 1090 setItem(itemName, newValue, previousValue) { 1091 if (this.removedItems.has(itemName)) { 1092 const removedRecord = this.removedItems.get(itemName); 1093 this.removedItems.delete(itemName); 1094 if (removedRecord.original === newValue) return; 1095 } 1096 if (this.changedItems.has(itemName)) { 1097 const originalRecord = this.changedItems.get(itemName); 1098 if (originalRecord.original === newValue) { 1099 this.changedItems.delete(itemName); 1100 return; 1101 } 1102 this.changedItems.set(itemName, { 1103 original: originalRecord.original, 1104 new: newValue 1105 }); 1106 return; 1107 } 1108 this.changedItems.set(itemName, { 1109 original: previousValue, 1110 new: newValue 1111 }); 1112 } 1113 removeItem(itemName, currentValue) { 1114 let trueOriginalValue = currentValue; 1115 if (this.changedItems.has(itemName)) { 1116 trueOriginalValue = this.changedItems.get(itemName).original; 1117 this.changedItems.delete(itemName); 1118 if (trueOriginalValue === null) return; 1119 } 1120 if (!this.removedItems.has(itemName)) this.removedItems.set(itemName, { original: trueOriginalValue }); 1121 } 1122 getChangedItems() { 1123 return Array.from(this.changedItems, ([name, { new: value }]) => ({ 1124 name, 1125 value 1126 })); 1127 } 1128 getRemovedItems() { 1129 return Array.from(this.removedItems.keys()); 1130 } 1131 isEmpty() { 1132 return this.changedItems.size === 0 && this.removedItems.size === 0; 1133 } 1134}; 1135var ElementChanges = class { 1136 addedClasses = /* @__PURE__ */ new Set(); 1137 removedClasses = /* @__PURE__ */ new Set(); 1138 styleChanges = new ChangingItemsTracker_default(); 1139 attributeChanges = new ChangingItemsTracker_default(); 1140 addClass(className) {
1141 if (!this.removedClasses.delete(className)) this.addedClasses.add(className); 1142 } 1143 removeClass(className) { 1144 if (!this.addedClasses.delete(className)) this.removedClasses.add(className); 1145 } 1146 addStyle(styleName, newValue, originalValue) { 1147 this.styleChanges.setItem(styleName, newValue, originalValue); 1148 } 1149 removeStyle(styleName, originalValue) { 1150 this.styleChanges.removeItem(styleName, originalValue); 1151 } 1152 addAttribute(attributeName, newValue, originalValue) { 1153 this.attributeChanges.setItem(attributeName, newValue, originalValue); 1154 } 1155 removeAttribute(attributeName, originalValue) { 1156 this.attributeChanges.removeItem(attributeName, originalValue); 1157 } 1158 getAddedClasses() { 1159 return [...this.addedClasses]; 1160 } 1161 getRemovedClasses() { 1162 return [...this.removedClasses]; 1163 } 1164 getChangedStyles() { 1165 return this.styleChanges.getChangedItems(); 1166 } 1167 getRemovedStyles() { 1168 return this.styleChanges.getRemovedItems(); 1169 } 1170 getChangedAttributes() { 1171 return this.attributeChanges.getChangedItems(); 1172 } 1173 getRemovedAttributes() { 1174 return this.attributeChanges.getRemovedItems(); 1175 } 1176 applyToElement(element) { 1177 element.classList.add(...this.addedClasses); 1178 element.classList.remove(...this.removedClasses);
1179 this.styleChanges.getChangedItems().forEach((change) => { 1180 if (/!\s*important/i.test(change.value)) element.style.setProperty(change.name, change.value.replace(/!\s*important/i, "").trim(), "important"); 1181 else element.style.setProperty(change.name, change.value); 1182 }); 1183 this.styleChanges.getRemovedItems().forEach((styleName) => { 1184 element.style.removeProperty(styleName); 1185 }); 1186 this.attributeChanges.getChangedItems().forEach((change) => { 1187 element.setAttribute(change.name, change.value); 1188 }); 1189 this.attributeChanges.getRemovedItems().forEach((attributeName) => { 1190 element.removeAttribute(attributeName); 1191 }); 1192 } 1193 isEmpty() { 1194 return this.addedClasses.size === 0 && this.removedClasses.size === 0 && this.styleChanges.isEmpty() && this.attributeChanges.isEmpty(); 1195 } 1196}; 1197var ExternalMutationTracker_default = class { 1198 element; 1199 shouldTrackChangeCallback; 1200 mutationObserver; 1201 changedElements = /* @__PURE__ */ new WeakMap(); 1202 changedElementsCount = 0; 1203 addedElements = []; 1204 removedElements = []; 1205 isStarted = false; 1206 originalIds = /* @__PURE__ */ new Map(); 1207 constructor(element, shouldTrackChangeCallback) { 1208 this.element = element; 1209 this.shouldTrackChangeCallback = shouldTrackChangeCallback; 1210 this.mutationObserver = new MutationObserver(this.onMutations.bind(this)); 1211 } 1212 start() { 1213 if (this.isStarted) return; 1214 this.mutationObserver.observe(this.element, { 1215 childList: true, 1216 subtree: true, 1217 attributes: true, 1218 attributeOldValue: true 1219 }); 1220 this.isStarted = true; 1221 } 1222 stop() { 1223 if (this.isStarted) { 1224 this.mutationObserver.disconnect(); 1225 this.isStarted = false; 1226 } 1227 } 1228 getChangedElement(element) { 1229 return this.changedElements.has(element) ? this.changedElements.get(element) : null; 1230 } 1231 getOriginalIds() { 1232 for (const element of this.originalIds.keys()) if (!this.element.contains(element)) this.originalIds.delete(element); 1233 return this.originalIds; 1234 } 1235 getAddedElements() { 1236 return this.addedElements; 1237 } 1238 wasElementAdded(element) { 1239 return this.addedElements.includes(element); 1240 } 1241 handlePendingChanges() { 1242 this.onMutations(this.mutationObserver.takeRecords()); 1243 } 1244 onMutations(mutations) { 1245 const handledAttributeMutations = /* @__PURE__ */ new WeakMap(); 1246 for (const mutation of mutations) { 1247 const element = mutation.target; 1248 if (!this.shouldTrackChangeCallback(element)) continue; 1249 if (this.isElementAddedByTranslation(element)) continue; 1250 let isChangeInAddedElement = false; 1251 for (const addedElement of this.addedElements) if (addedElement.contains(element)) { 1252 isChangeInAddedElement = true; 1253 break; 1254 } 1255 if (isChangeInAddedElement) continue; 1256 switch (mutation.type) { 1257 case "childList": 1258 this.handleChildListMutation(mutation); 1259 break; 1260 case "attributes": 1261 if (!handledAttributeMutations.has(element)) handledAttributeMutations.set(element, []); 1262 if (!handledAttributeMutations.get(element).includes(mutation.attributeName)) { 1263 this.handleAttributeMutation(mutation); 1264 handledAttributeMutations.set(element, [...handledAttributeMutations.get(element), mutation.attributeName]); 1265 } 1266 break; 1267 } 1268 } 1269 } 1270 handleChildListMutation(mutation) { 1271 mutation.addedNodes.forEach((node) => { 1272 if (!(node instanceof Element)) return; 1273 if (this.removedElements.includes(node)) { 1274 this.removedElements.splice(this.removedElements.indexOf(node), 1); 1275 return; 1276 } 1277 if (this.isElementAddedByTranslation(node)) return; 1278 this.addedElements.push(node); 1279 }); 1280 mutation.removedNodes.forEach((node) => { 1281 if (!(node instanceof Element)) return; 1282 if (this.addedElements.includes(node)) { 1283 this.addedElements.splice(this.addedElements.indexOf(node), 1); 1284 return; 1285 } 1286 this.removedElements.push(node); 1287 }); 1288 } 1289 handleAttributeMutation(mutation) { 1290 const element = mutation.target; 1291 if (!this.changedElements.has(element)) { 1292 this.changedElements.set(element, new ElementChanges()); 1293 this.changedElementsCount++; 1294 } 1295 const changedElement = this.changedElements.get(element); 1296 switch (mutation.attributeName) { 1297 case "class": 1298 this.handleClassAttributeMutation(mutation, changedElement); 1299 break; 1300 case "style": 1301 this.handleStyleAttributeMutation(mutation, changedElement); 1302 break; 1303 default: this.handleGenericAttributeMutation(mutation, changedElement); 1304 } 1305 if (changedElement.isEmpty()) { 1306 this.changedElements.delete(element); 1307 this.changedElementsCount--; 1308 } 1309 } 1310 handleClassAttributeMutation(mutation, elementChanges) { 1311 const element = mutation.target; 1312 const previousValues = (mutation.oldValue || "").match(/(\S+)/gu) || []; 1313 const newValues = [].slice.call(element.classList); 1314 const addedValues = newValues.filter((value) => !previousValues.includes(value)); 1315 const removedValues = previousValues.filter((value) => !newValues.includes(value));
1316 addedValues.forEach((value) => { 1317 elementChanges.addClass(value); 1318 }); 1319 removedValues.forEach((value) => { 1320 elementChanges.removeClass(value); 1321 }); 1322 } 1323 handleStyleAttributeMutation(mutation, elementChanges) { 1324 const element = mutation.target; 1325 const previousValue = mutation.oldValue || ""; 1326 const previousStyles = this.extractStyles(previousValue); 1327 const newValue = element.getAttribute("style") || ""; 1328 const newStyles = this.extractStyles(newValue); 1329 const addedOrChangedStyles = Object.keys(newStyles).filter((key) => previousStyles[key] === void 0 || previousStyles[key] !== newStyles[key]); 1330 const removedStyles = Object.keys(previousStyles).filter((key) => !newStyles[key]); 1331 addedOrChangedStyles.forEach((style) => { 1332 elementChanges.addStyle(style, newStyles[style], previousStyles[style] === void 0 ? null : previousStyles[style]); 1333 }); 1334 removedStyles.forEach((style) => { 1335 elementChanges.removeStyle(style, previousStyles[style]); 1336 }); 1337 } 1338 handleGenericAttributeMutation(mutation, elementChanges) { 1339 const attributeName = mutation.attributeName; 1340 const element = mutation.target; 1341 if (attributeName === "id") { 1342 if (!this.originalIds.has(element)) this.originalIds.set(element, mutation.oldValue); 1343 if (element.getAttribute("id") === this.originalIds.get(element)) this.originalIds.delete(element); 1344 } 1345 let oldValue = mutation.oldValue; 1346 let newValue = element.getAttribute(attributeName); 1347 if (oldValue === attributeName) oldValue = ""; 1348 if (newValue === attributeName) newValue = ""; 1349 if (!element.hasAttribute(attributeName)) { 1350 if (oldValue === null) return; 1351 elementChanges.removeAttribute(attributeName, mutation.oldValue); 1352 return; 1353 } 1354 if (newValue === oldValue) return; 1355 elementChanges.addAttribute(attributeName, element.getAttribute(attributeName), mutation.oldValue); 1356 } 1357 extractStyles(styles) { 1358 const styleObject = {}; 1359 styles.split(";").forEach((style) => { 1360 const parts = style.split(":"); 1361 if (parts.length === 1) return; 1362 const property = parts[0].trim(); 1363 styleObject[property] = parts.slice(1).join(":").trim(); 1364 }); 1365 return styleObject; 1366 } 1367 isElementAddedByTranslation(element) { 1368 return element.tagName === "FONT" && element.getAttribute("style") === "vertical-align: inherit;"; 1369 } 1370}; 1371var UnsyncedInputsTracker_default = class { 1372 component; 1373 modelElementResolver; 1374 unsyncedInputs; 1375 elementEventListeners = [{ 1376 event: "input", 1377 callback: (event) => this.handleInputEvent(event) 1378 }]; 1379 constructor(component, modelElementResolver) { 1380 this.component = component; 1381 this.modelElementResolver = modelElementResolver; 1382 this.unsyncedInputs = new UnsyncedInputContainer(); 1383 } 1384 activate() { 1385 this.elementEventListeners.forEach(({ event, callback }) => { 1386 this.component.element.addEventListener(event, callback); 1387 }); 1388 } 1389 deactivate() { 1390 this.elementEventListeners.forEach(({ event, callback }) => { 1391 this.component.element.removeEventListener(event, callback); 1392 }); 1393 } 1394 markModelAsSynced(modelName) { 1395 this.unsyncedInputs.markModelAsSynced(modelName); 1396 } 1397 handleInputEvent(event) { 1398 const target = event.target; 1399 if (!target) return; 1400 this.updateModelFromElement(target); 1401 } 1402 updateModelFromElement(element) { 1403 if (!elementBelongsToThisComponent(element, this.component)) return; 1404 if (!(element instanceof HTMLElement)) throw new Error("Could not update model for non HTMLElement"); 1405 const modelName = this.modelElementResolver.getModelName(element); 1406 this.unsyncedInputs.add(element, modelName); 1407 } 1408 getUnsyncedInputs() { 1409 return this.unsyncedInputs.allUnsyncedInputs(); 1410 } 1411 getUnsyncedModels() { 1412 return Array.from(this.unsyncedInputs.getUnsyncedModelNames()); 1413 } 1414 resetUnsyncedFields() { 1415 this.unsyncedInputs.resetUnsyncedFields(); 1416 } 1417}; 1418var UnsyncedInputContainer = class { 1419 unsyncedModelFields; 1420 unsyncedNonModelFields = []; 1421 unsyncedModelNames = []; 1422 constructor() { 1423 this.unsyncedModelFields = /* @__PURE__ */ new Map(); 1424 } 1425 add(element, modelName = null) { 1426 if (modelName) { 1427 this.unsyncedModelFields.set(modelName, element); 1428 if (!this.unsyncedModelNames.includes(modelName)) this.unsyncedModelNames.push(modelName); 1429 return; 1430 } 1431 this.unsyncedNonModelFields.push(element); 1432 } 1433 resetUnsyncedFields() { 1434 this.unsyncedModelFields.forEach((value, key) => { 1435 if (!this.unsyncedModelNames.includes(key)) this.unsyncedModelFields.delete(key); 1436 }); 1437 } 1438 allUnsyncedInputs() { 1439 return [...this.unsyncedNonModelFields, ...this.unsyncedModelFields.values()]; 1440 } 1441 markModelAsSynced(modelName) { 1442 const index = this.unsyncedModelNames.indexOf(modelName); 1443 if (index !== -1) this.unsyncedModelNames.splice(index, 1); 1444 } 1445 getUnsyncedModelNames() { 1446 return this.unsyncedModelNames; 1447 } 1448}; 1449function getDeepData(data, propertyPath) { 1450 const { currentLevelData, finalKey } = parseDeepData(data, propertyPath); 1451 if (currentLevelData === void 0) return; 1452 return currentLevelData[finalKey]; 1453} 1454const parseDeepData = (data, propertyPath) => { 1455 const finalData = JSON.parse(JSON.stringify(data)); 1456 let currentLevelData = finalData; 1457 const parts = propertyPath.split("."); 1458 for (let i = 0; i < parts.length - 1; i++) currentLevelData = currentLevelData[parts[i]]; 1459 const finalKey = parts[parts.length - 1]; 1460 return { 1461 currentLevelData, 1462 finalData, 1463 finalKey, 1464 parts 1465 }; 1466}; 1467var ValueStore_default = class { 1468 props = {}; 1469 dirtyProps = {}; 1470 pendingProps = {}; 1471 updatedPropsFromParent = {}; 1472 constructor(props) { 1473 this.props = props; 1474 } 1475 get(name) {
1476 const normalizedName = normalizeModelName(name); 1477 if (this.dirtyProps[normalizedName] !== void 0) return this.dirtyProps[normalizedName]; 1478 if (this.pendingProps[normalizedName] !== void 0) return this.pendingProps[normalizedName]; 1479 if (this.props[normalizedName] !== void 0) return this.props[normalizedName]; 1480 return getDeepData(this.props, normalizedName); 1481 } 1482 has(name) { 1483 return this.get(name) !== void 0; 1484 } 1485 set(name, value) { 1486 const normalizedName = normalizeModelName(name); 1487 if (this.get(normalizedName) === value) return false; 1488 this.dirtyProps[normalizedName] = value; 1489 return true; 1490 } 1491 getOriginalProps() { 1492 return { ...this.props }; 1493 } 1494 getDirtyProps() { 1495 return { ...this.dirtyProps }; 1496 } 1497 getUpdatedPropsFromParent() { 1498 return { ...this.updatedPropsFromParent }; 1499 } 1500 flushDirtyPropsToPending() { 1501 this.pendingProps = { ...this.dirtyProps }; 1502 this.dirtyProps = {}; 1503 } 1504 reinitializeAllProps(props) { 1505 this.props = props; 1506 this.updatedPropsFromParent = {}; 1507 this.pendingProps = {}; 1508 } 1509 pushPendingPropsBackToDirty() { 1510 this.dirtyProps = { 1511 ...this.pendingProps, 1512 ...this.dirtyProps 1513 }; 1514 this.pendingProps = {}; 1515 } 1516 storeNewPropsFromParent(props) { 1517 let changed = false; 1518 for (const [key, value] of Object.entries(props)) if (this.get(key) !== value) changed = true; 1519 if (changed) this.updatedPropsFromParent = props; 1520 return changed; 1521 } 1522}; 1523var Component = class { 1524 element; 1525 name; 1526 listeners; 1527 backend; 1528 elementDriver; 1529 id; 1530 fingerprint = ""; 1531 valueStore; 1532 unsyncedInputsTracker; 1533 hooks; 1534 defaultDebounce = 150; 1535 backendRequest = null; 1536 pendingActions = []; 1537 pendingFiles = {}; 1538 isRequestPending = false; 1539 isRemoved = false; 1540 requestDebounceTimeout = null; 1541 nextRequestPromise; 1542 nextRequestPromiseResolve; 1543 externalMutationTracker; 1544 constructor(element, name, props, listeners, id, backend, elementDriver) { 1545 this.element = element; 1546 this.name = name; 1547 this.backend = backend; 1548 this.elementDriver = elementDriver; 1549 this.id = id; 1550 this.listeners = /* @__PURE__ */ new Map(); 1551 listeners.forEach((listener) => { 1552 if (!this.listeners.has(listener.event)) this.listeners.set(listener.event, []); 1553 this.listeners.get(listener.event)?.push(listener.action); 1554 }); 1555 this.valueStore = new ValueStore_default(props); 1556 this.unsyncedInputsTracker = new UnsyncedInputsTracker_default(this, elementDriver); 1557 this.hooks = new HookManager_default(); 1558 this.resetPromise(); 1559 this.externalMutationTracker = new ExternalMutationTracker_default(this.element, (element) => elementBelongsToThisComponent(element, this)); 1560 this.externalMutationTracker.start(); 1561 } 1562 getOriginalId() { 1563 this.externalMutationTracker.handlePendingChanges(); 1564 const originalIds = this.externalMutationTracker.getOriginalIds(); 1565 return originalIds.has(this.element) ? originalIds.get(this.element) : this.element.getAttribute("id"); 1566 } 1567 addPlugin(plugin) { 1568 plugin.attachToComponent(this); 1569 } 1570 connect() { 1571 registerComponent(this); 1572 this.hooks.triggerHook("connect", this); 1573 this.unsyncedInputsTracker.activate(); 1574 this.externalMutationTracker.start(); 1575 } 1576 disconnect() { 1577 unregisterComponent(this); 1578 this.hooks.triggerHook("disconnect", this); 1579 this.clearRequestDebounceTimeout(); 1580 this.unsyncedInputsTracker.deactivate(); 1581 this.externalMutationTracker.stop(); 1582 } 1583 on(hookName, callback) { 1584 this.hooks.register(hookName, callback); 1585 } 1586 off(hookName, callback) { 1587 this.hooks.unregister(hookName, callback); 1588 } 1589 set(model, value, reRender = false, debounce = false) { 1590 const promise = this.nextRequestPromise; 1591 const modelName = normalizeModelName(model); 1592 if (!this.valueStore.has(modelName)) throw new Error(`Invalid model name "${model}".`); 1593 const isChanged = this.valueStore.set(modelName, value); 1594 this.hooks.triggerHook("model:set", model, value, this); 1595 this.unsyncedInputsTracker.markModelAsSynced(modelName); 1596 if (reRender && isChanged) this.debouncedStartRequest(debounce); 1597 return promise; 1598 } 1599 getData(model) { 1600 const modelName = normalizeModelName(model); 1601 if (!this.valueStore.has(modelName)) throw new Error(`Invalid model "${model}".`); 1602 return this.valueStore.get(modelName); 1603 } 1604 action(name, args = {}, debounce = false) { 1605 const promise = this.nextRequestPromise; 1606 this.pendingActions.push({ 1607 name, 1608 args 1609 });
1610 this.debouncedStartRequest(debounce); 1611 return promise; 1612 } 1613 files(key, input) { 1614 this.pendingFiles[key] = input; 1615 } 1616 render() { 1617 const promise = this.nextRequestPromise; 1618 this.tryStartingRequest(); 1619 return promise; 1620 } 1621 getUnsyncedModels() { 1622 return this.unsyncedInputsTracker.getUnsyncedModels(); 1623 } 1624 emit(name, data, onlyMatchingComponentsNamed = null) { 1625 this.performEmit(name, data, false, onlyMatchingComponentsNamed); 1626 } 1627 emitUp(name, data, onlyMatchingComponentsNamed = null) { 1628 this.performEmit(name, data, true, onlyMatchingComponentsNamed); 1629 } 1630 emitSelf(name, data) { 1631 this.doEmit(name, data); 1632 } 1633 performEmit(name, data, emitUp, matchingName) { 1634 findComponents(this, emitUp, matchingName).forEach((component) => { 1635 component.doEmit(name, data); 1636 }); 1637 } 1638 doEmit(name, data) { 1639 if (!this.listeners.has(name)) return; 1640 (this.listeners.get(name) || []).forEach((action) => { 1641 this.action(action, data, 1); 1642 }); 1643 } 1644 isTurboEnabled() { 1645 return typeof Turbo !== "undefined" && !this.element.closest("[data-turbo=\"false\"]"); 1646 } 1647 removeFromPage() { 1648 this.isRemoved = true; 1649 this.disconnect(); 1650 const element = this.element; 1651 for (const name of element.getAttributeNames()) if (name.startsWith("data-live-") && name.endsWith("-value")) element.removeAttribute(name); 1652 element.setAttribute("data-live-removing", ""); 1653 requestAnimationFrame(() => { 1654 const animations = (element.getAnimations?.({ subtree: true }) ?? []).filter((animation) => animation.effect?.getComputedTiming().endTime !== Number.POSITIVE_INFINITY); 1655 Promise.allSettled(animations.map((animation) => animation.finished)).then(() => { 1656 element.remove(); 1657 }); 1658 }); 1659 } 1660 tryStartingRequest() { 1661 if (this.isRemoved) return; 1662 if (!this.backendRequest) { 1663 this.performRequest(); 1664 return; 1665 } 1666 this.isRequestPending = true; 1667 } 1668 performRequest() { 1669 const thisPromiseResolve = this.nextRequestPromiseResolve; 1670 this.resetPromise(); 1671 this.unsyncedInputsTracker.resetUnsyncedFields(); 1672 const filesToSend = {}; 1673 for (const [key, value] of Object.entries(this.pendingFiles)) if (value.files) filesToSend[key] = value.files; 1674 const actionsToSend = this.pendingActions.slice(0, 50); 1675 const remainingActions = this.pendingActions.slice(50); 1676 const requestConfig = { 1677 props: this.valueStore.getOriginalProps(), 1678 actions: actionsToSend, 1679 updated: this.valueStore.getDirtyProps(), 1680 children: {}, 1681 updatedPropsFromParent: this.valueStore.getUpdatedPropsFromParent(), 1682 files: filesToSend 1683 }; 1684 this.hooks.triggerHook("request:started", requestConfig); 1685 this.backendRequest = this.backend.makeRequest(requestConfig.props, requestConfig.actions, requestConfig.updated, requestConfig.children, requestConfig.updatedPropsFromParent, requestConfig.files); 1686 this.hooks.triggerHook("loading.state:started", this.element, this.backendRequest); 1687 this.pendingActions = remainingActions; 1688 this.valueStore.flushDirtyPropsToPending(); 1689 this.isRequestPending = remainingActions.length > 0; 1690 this.backendRequest.promise.then(async (response) => { 1691 const backendResponse = new BackendResponse_default(response); 1692 const headers = backendResponse.response.headers; 1693 for (const input of Object.values(this.pendingFiles)) input.value = ""; 1694 const html = await backendResponse.getBody(); 1695 if (!headers.get("Content-Type")?.includes("application/vnd.live-component+html") && !headers.get("X-Live-Redirect") && !headers.has("X-Live-Remove")) { 1696 const controls = { displayError: true }; 1697 this.valueStore.pushPendingPropsBackToDirty(); 1698 this.hooks.triggerHook("response:error", backendResponse, controls); 1699 if (controls.displayError) this.renderError(html); 1700 this.backendRequest = null; 1701 thisPromiseResolve(backendResponse); 1702 return response; 1703 } 1704 if (backendResponse.isRemoved()) { 1705 this.isRemoved = true; 1706 this.processRerender(html, backendResponse); 1707 this.backendRequest = null; 1708 thisPromiseResolve(backendResponse); 1709 this.removeFromPage(); 1710 return response; 1711 } 1712 const liveUrl = backendResponse.getLiveUrl(); 1713 if (liveUrl) history.replaceState(history.state, "", new URL(liveUrl + window.location.hash, window.location.origin)); 1714 this.processRerender(html, backendResponse); 1715 this.backendRequest = null; 1716 thisPromiseResolve(backendResponse); 1717 if (this.isRequestPending) { 1718 this.isRequestPending = false;
1719 this.performRequest(); 1720 } 1721 return response; 1722 }); 1723 } 1724 processRerender(html, backendResponse) { 1725 const controls = { shouldRender: true }; 1726 this.hooks.triggerHook("render:started", html, backendResponse, controls); 1727 if (!controls.shouldRender) return; 1728 if (backendResponse.response.headers.get("Location")) { 1729 if (this.isTurboEnabled()) Turbo.visit(backendResponse.response.headers.get("Location")); 1730 else window.location.href = backendResponse.response.headers.get("Location") || ""; 1731 return; 1732 } 1733 this.hooks.triggerHook("loading.state:finished", this.element); 1734 const modifiedModelValues = {}; 1735 Object.keys(this.valueStore.getDirtyProps()).forEach((modelName) => { 1736 modifiedModelValues[modelName] = this.valueStore.get(modelName); 1737 }); 1738 let newElement; 1739 try { 1740 newElement = htmlToElement(html); 1741 if (!newElement.matches("[data-controller~=live]")) throw new Error("A live component template must contain a single root controller element."); 1742 } catch (error) { 1743 console.error(`There was a problem with the '${this.name}' component HTML returned:`, { id: this.id }); 1744 throw error; 1745 } 1746 this.externalMutationTracker.handlePendingChanges(); 1747 this.externalMutationTracker.stop(); 1748 executeMorphdom(this.element, newElement, this.unsyncedInputsTracker.getUnsyncedInputs(), (element) => getValueFromElement(element, this.valueStore), this.externalMutationTracker, findChildren(this)); 1749 this.externalMutationTracker.start(); 1750 const newProps = this.elementDriver.getComponentProps(); 1751 this.valueStore.reinitializeAllProps(newProps); 1752 const eventsToEmit = this.elementDriver.getEventsToEmit(); 1753 const browserEventsToDispatch = this.elementDriver.getBrowserEventsToDispatch(); 1754 Object.keys(modifiedModelValues).forEach((modelName) => { 1755 this.valueStore.set(modelName, modifiedModelValues[modelName]); 1756 }); 1757 eventsToEmit.forEach(({ event, data, target, componentName }) => { 1758 if (target === "up") { 1759 this.emitUp(event, data, componentName); 1760 return; 1761 } 1762 if (target === "self") { 1763 this.emitSelf(event, data); 1764 return; 1765 } 1766 this.emit(event, data, componentName); 1767 }); 1768 browserEventsToDispatch.forEach(({ event, payload }) => { 1769 this.element.dispatchEvent(new CustomEvent(event, { 1770 detail: payload, 1771 bubbles: true 1772 })); 1773 }); 1774 try {
1775 const downloadUrl = backendResponse.getDownloadUrl(); 1776 const download = backendResponse.getDownload(); 1777 if (downloadUrl) triggerDownload({ url: downloadUrl }); 1778 else if (download) triggerDownload(download); 1779 } catch (error) { 1780 console.error("Could not start the download:", error); 1781 } 1782 this.hooks.triggerHook("render:finished", this); 1783 } 1784 calculateDebounce(debounce) { 1785 if (debounce === true) return this.defaultDebounce; 1786 if (debounce === false) return 0; 1787 return debounce; 1788 } 1789 clearRequestDebounceTimeout() { 1790 if (this.requestDebounceTimeout) { 1791 clearTimeout(this.requestDebounceTimeout); 1792 this.requestDebounceTimeout = null; 1793 } 1794 } 1795 debouncedStartRequest(debounce) { 1796 this.clearRequestDebounceTimeout(); 1797 this.requestDebounceTimeout = window.setTimeout(() => { 1798 this.render(); 1799 }, this.calculateDebounce(debounce)); 1800 } 1801 renderError(html) { 1802 let modal = document.getElementById("live-component-error"); 1803 if (modal) modal.innerHTML = ""; 1804 else { 1805 modal = document.createElement("div"); 1806 modal.id = "live-component-error"; 1807 modal.style.padding = "50px"; 1808 modal.style.backgroundColor = "rgba(0, 0, 0, .5)"; 1809 modal.style.zIndex = "100000"; 1810 modal.style.position = "fixed"; 1811 modal.style.top = "0px"; 1812 modal.style.bottom = "0px"; 1813 modal.style.left = "0px"; 1814 modal.style.right = "0px"; 1815 modal.style.display = "flex"; 1816 modal.style.flexDirection = "column"; 1817 } 1818 const iframe = document.createElement("iframe"); 1819 iframe.style.borderRadius = "5px"; 1820 iframe.style.flexGrow = "1"; 1821 modal.appendChild(iframe); 1822 document.body.prepend(modal); 1823 document.body.style.overflow = "hidden"; 1824 if (iframe.contentWindow) { 1825 iframe.contentWindow.document.open(); 1826 iframe.contentWindow.document.write(html); 1827 iframe.contentWindow.document.close(); 1828 } 1829 const closeModal = (modal) => { 1830 if (modal) modal.outerHTML = ""; 1831 document.body.style.overflow = "visible"; 1832 }; 1833 modal.addEventListener("click", () => closeModal(modal)); 1834 modal.setAttribute("tabindex", "0"); 1835 modal.addEventListener("keydown", (e) => { 1836 if (e.key === "Escape") closeModal(modal); 1837 }); 1838 modal.focus(); 1839 } 1840 resetPromise() { 1841 this.nextRequestPromise = new Promise((resolve) => { 1842 this.nextRequestPromiseResolve = resolve; 1843 }); 1844 } 1845 _updateFromParentProps(props) { 1846 if (this.valueStore.storeNewPropsFromParent(props)) this.render(); 1847 } 1848}; 1849function proxifyComponent(component) { 1850 return new Proxy(component, { 1851 get(component, prop) { 1852 if (prop in component || typeof prop !== "string") { 1853 if (typeof component[prop] === "function") { 1854 const callable = component[prop]; 1855 return (...args) => { 1856 return callable.apply(component, args); 1857 }; 1858 } 1859 return Reflect.get(component, prop); 1860 } 1861 if (component.valueStore.has(prop)) return component.getData(prop); 1862 if ("toJSON" === prop || "then" === prop) return; 1863 return (args) => { 1864 return component.action.apply(component, [prop, args]); 1865 }; 1866 }, 1867 set(target, property, value) { 1868 if (property in target) { 1869 target[property] = value; 1870 return true; 1871 } 1872 target.set(property, value); 1873 return true; 1874 } 1875 }); 1876} 1877function triggerDownload(download) { 1878 const fromUrl = "url" in download; 1879 const href = fromUrl ? download.url : URL.createObjectURL(download.blob); 1880 const link = Object.assign(document.createElement("a"), { 1881 href, 1882 download: fromUrl ? "" : download.filename, 1883 style: "display: none" 1884 }); 1885 document.body.appendChild(link); 1886 link.click(); 1887 setTimeout(() => { 1888 document.body.removeChild(link); 1889 if (!fromUrl) URL.revokeObjectURL(href); 1890 }, 75); 1891} 1892var StimulusElementDriver = class { 1893 controller; 1894 constructor(controller) { 1895 this.controller = controller; 1896 } 1897 getModelName(element) { 1898 const modelDirective = getModelDirectiveFromElement(element, false); 1899 if (!modelDirective) return null; 1900 return modelDirective.action; 1901 } 1902 getComponentProps() { 1903 return this.controller.propsValue; 1904 } 1905 getEventsToEmit() { 1906 return this.controller.eventsToEmitValue; 1907 } 1908 getBrowserEventsToDispatch() { 1909 return this.controller.eventsToDispatchValue; 1910 } 1911}; 1912function get_model_binding_default(modelDirective) { 1913 let shouldRender = true; 1914 let targetEventName = null; 1915 let debounce = false;
1916 let minLength = null; 1917 let maxLength = null; 1918 let minValue = null; 1919 let maxValue = null; 1920 modelDirective.modifiers.forEach((modifier) => { 1921 switch (modifier.name) { 1922 case "on": 1923 if (!modifier.value) throw new Error(`The "on" modifier in ${modelDirective.getString()} requires a value - e.g. on(change).`); 1924 if (!["input", "change"].includes(modifier.value)) throw new Error(`The "on" modifier in ${modelDirective.getString()} only accepts the arguments "input" or "change".`); 1925 targetEventName = modifier.value; 1926 break; 1927 case "norender": 1928 shouldRender = false; 1929 break; 1930 case "debounce": 1931 debounce = modifier.value ? Number.parseInt(modifier.value) : true; 1932 break; 1933 case "min_length": 1934 minLength = modifier.value ? Number.parseInt(modifier.value) : null; 1935 break; 1936 case "max_length": 1937 maxLength = modifier.value ? Number.parseInt(modifier.value) : null; 1938 break; 1939 case "min_value": 1940 minValue = modifier.value ? Number.parseFloat(modifier.value) : null; 1941 break; 1942 case "max_value": 1943 maxValue = modifier.value ? Number.parseFloat(modifier.value) : null; 1944 break; 1945 default: throw new Error(`Unknown modifier "${modifier.name}" in data-model="${modelDirective.getString()}".`); 1946 } 1947 }); 1948 const [modelName, innerModelName] = modelDirective.action.split(":"); 1949 return { 1950 modelName, 1951 innerModelName: innerModelName || null, 1952 shouldRender, 1953 debounce, 1954 targetEventName, 1955 minLength, 1956 maxLength, 1957 minValue, 1958 maxValue 1959 }; 1960} 1961var ChildComponentPlugin_default = class { 1962 component; 1963 parentModelBindings = []; 1964 constructor(component) { 1965 this.component = component; 1966 const modelDirectives = getAllModelDirectiveFromElements(this.component.element); 1967 this.parentModelBindings = modelDirectives.map(get_model_binding_default); 1968 } 1969 attachToComponent(component) { 1970 component.on("request:started", (requestData) => { 1971 requestData.children = this.getChildrenFingerprints(); 1972 }); 1973 component.on("model:set", (model, value) => { 1974 this.notifyParentModelChange(model, value); 1975 }); 1976 } 1977 getChildrenFingerprints() { 1978 const fingerprints = {};
1979 this.getChildren().forEach((child) => { 1980 if (!child.id) throw new Error("missing id"); 1981 fingerprints[child.id] = { 1982 fingerprint: child.fingerprint, 1983 tag: child.element.tagName.toLowerCase() 1984 }; 1985 }); 1986 return fingerprints; 1987 } 1988 notifyParentModelChange(modelName, value) { 1989 const parentComponent = findParent(this.component); 1990 if (!parentComponent) return; 1991 this.parentModelBindings.forEach((modelBinding) => { 1992 if ((modelBinding.innerModelName || "value") !== modelName) return; 1993 parentComponent.set(modelBinding.modelName, value, modelBinding.shouldRender, modelBinding.debounce); 1994 }); 1995 } 1996 getChildren() { 1997 return findChildren(this.component); 1998 } 1999}; 2000var LazyPlugin_default = class { 2001 intersectionObserver = null; 2002 attachToComponent(component) { 2003 if ("lazy" !== component.element.attributes.getNamedItem("loading")?.value) return; 2004 component.on("connect", () => { 2005 this.getObserver().observe(component.element); 2006 }); 2007 component.on("disconnect", () => { 2008 this.intersectionObserver?.unobserve(component.element); 2009 }); 2010 } 2011 getObserver() { 2012 if (!this.intersectionObserver) this.intersectionObserver = new IntersectionObserver((entries, observer) => { 2013 entries.forEach((entry) => { 2014 if (entry.isIntersecting) { 2015 entry.target.dispatchEvent(new CustomEvent("live:appear")); 2016 observer.unobserve(entry.target); 2017 } 2018 }); 2019 }); 2020 return this.intersectionObserver; 2021 } 2022}; 2023var LoadingPlugin_default = class { 2024 attachToComponent(component) { 2025 component.on("loading.state:started", (element, request) => { 2026 this.startLoading(component, element, request); 2027 }); 2028 component.on("loading.state:finished", (element) => { 2029 this.finishLoading(component, element); 2030 }); 2031 this.finishLoading(component, component.element); 2032 } 2033 startLoading(component, targetElement, backendRequest) { 2034 this.handleLoadingToggle(component, true, targetElement, backendRequest); 2035 } 2036 finishLoading(component, targetElement) { 2037 this.handleLoadingToggle(component, false, targetElement, null); 2038 } 2039 handleLoadingToggle(component, isLoading, targetElement, backendRequest) { 2040 if (isLoading) targetElement.setAttribute("aria-busy", "true"); 2041 else targetElement.removeAttribute("aria-busy"); 2042 this.getLoadingDirectives(component, targetElement).forEach(({ element, directives }) => { 2043 if (isLoading) this.addAttributes(element, ["data-live-is-loading"]); 2044 else this.removeAttributes(element, ["data-live-is-loading"]); 2045 directives.forEach((directive) => { 2046 this.handleLoadingDirective(element, isLoading, directive, backendRequest); 2047 }); 2048 }); 2049 } 2050 handleLoadingDirective(element, isLoading, directive, backendRequest) { 2051 const finalAction = parseLoadingAction(directive.action, isLoading); 2052 const targetedActions = []; 2053 const targetedModels = []; 2054 let delay = 0; 2055 const validModifiers = /* @__PURE__ */ new Map(); 2056 validModifiers.set("delay", (modifier) => { 2057 if (!isLoading) return; 2058 delay = modifier.value ? Number.parseInt(modifier.value) : 200; 2059 }); 2060 validModifiers.set("action", (modifier) => { 2061 if (!modifier.value) throw new Error(`The "action" in data-loading must have an action name - e.g. action(foo). It's missing for "${directive.getString()}"`); 2062 targetedActions.push(modifier.value); 2063 }); 2064 validModifiers.set("model", (modifier) => { 2065 if (!modifier.value) throw new Error(`The "model" in data-loading must have an action name - e.g. model(foo). It's missing for "${directive.getString()}"`); 2066 targetedModels.push(modifier.value); 2067 }); 2068 directive.modifiers.forEach((modifier) => { 2069 if (validModifiers.has(modifier.name)) { 2070 (validModifiers.get(modifier.name) ?? (() => {}))(modifier); 2071 return; 2072 } 2073 throw new Error(`Unknown modifier "${modifier.name}" used in data-loading="${directive.getString()}". Available modifiers are: ${Array.from(validModifiers.keys()).join(", ")}.`); 2074 }); 2075 if (isLoading && targetedActions.length > 0 && backendRequest && !backendRequest.containsOneOfActions(targetedActions)) return; 2076 if (isLoading && targetedModels.length > 0 && backendRequest && !backendRequest.areAnyModelsUpdated(targetedModels)) return; 2077 let loadingDirective; 2078 switch (finalAction) { 2079 case "show": 2080 loadingDirective = () => this.showElement(element); 2081 break; 2082 case "hide": 2083 loadingDirective = () => this.hideElement(element); 2084 break; 2085 case "addClass": 2086 loadingDirective = () => this.addClass(element, directive.args); 2087 break; 2088 case "removeClass": 2089 loadingDirective = () => this.removeClass(element, directive.args); 2090 break; 2091 case "addAttribute": 2092 loadingDirective = () => this.addAttributes(element, directive.args); 2093 break; 2094 case "removeAttribute": 2095 loadingDirective = () => this.removeAttributes(element, directive.args); 2096 break; 2097 default: throw new Error(`Unknown data-loading action "${finalAction}"`); 2098 } 2099 if (delay) { 2100 window.setTimeout(() => { 2101 if (backendRequest && !backendRequest.isResolved) loadingDirective(); 2102 }, delay); 2103 return; 2104 } 2105 loadingDirective(); 2106 } 2107 getLoadingDirectives(component, element) { 2108 const loadingDirectives = []; 2109 let matchingElements = Array.from(element.querySelectorAll("[data-loading]")); 2110 matchingElements = matchingElements.filter((elt) => elementBelongsToThisComponent(elt, component)); 2111 matchingElements = matchingElements.filter((elt) => !elt.closest("[data-live-ignore]")); 2112 if (element.hasAttribute("data-loading")) matchingElements = [element, ...matchingElements];
2113 matchingElements.forEach((element) => { 2114 if (!(element instanceof HTMLElement) && !(element instanceof SVGElement)) throw new Error("Invalid Element Type"); 2115 const directives = parseDirectives(element.dataset.loading || "show"); 2116 loadingDirectives.push({ 2117 element, 2118 directives 2119 }); 2120 }); 2121 return loadingDirectives; 2122 } 2123 showElement(element) { 2124 element.style.display = "revert"; 2125 } 2126 hideElement(element) { 2127 element.style.display = "none"; 2128 } 2129 addClass(element, classes) { 2130 element.classList.add(...combineSpacedArray(classes)); 2131 } 2132 removeClass(element, classes) { 2133 element.classList.remove(...combineSpacedArray(classes)); 2134 if (element.classList.length === 0) element.removeAttribute("class"); 2135 } 2136 addAttributes(element, attributes) { 2137 attributes.forEach((attribute) => { 2138 element.setAttribute(attribute, ""); 2139 }); 2140 } 2141 removeAttributes(element, attributes) { 2142 attributes.forEach((attribute) => { 2143 element.removeAttribute(attribute); 2144 }); 2145 } 2146}; 2147const parseLoadingAction = (action, isLoading) => { 2148 switch (action) { 2149 case "show": return isLoading ? "show" : "hide"; 2150 case "hide": return isLoading ? "hide" : "show"; 2151 case "addClass": return isLoading ? "addClass" : "removeClass"; 2152 case "removeClass": return isLoading ? "removeClass" : "addClass"; 2153 case "addAttribute": return isLoading ? "addAttribute" : "removeAttribute"; 2154 case "removeAttribute": return isLoading ? "removeAttribute" : "addAttribute"; 2155 } 2156 throw new Error(`Unknown data-loading action "${action}"`); 2157}; 2158var PageUnloadingPlugin_default = class { 2159 isConnected = false; 2160 attachToComponent(component) { 2161 component.on("render:started", (html, response, controls) => { 2162 if (!this.isConnected) controls.shouldRender = false;
2163 }); 2164 component.on("connect", () => { 2165 this.isConnected = true; 2166 }); 2167 component.on("disconnect", () => { 2168 this.isConnected = false; 2169 }); 2170 } 2171}; 2172var PollingDirector_default = class { 2173 component; 2174 isPollingActive = true; 2175 polls; 2176 pollingIntervals = []; 2177 constructor(component) { 2178 this.component = component; 2179 } 2180 addPoll(actionName, duration) { 2181 this.polls.push({ 2182 actionName, 2183 duration 2184 }); 2185 if (this.isPollingActive) this.initiatePoll(actionName, duration); 2186 } 2187 startAllPolling() { 2188 if (this.isPollingActive) return; 2189 this.isPollingActive = true; 2190 this.polls.forEach(({ actionName, duration }) => { 2191 this.initiatePoll(actionName, duration); 2192 }); 2193 } 2194 stopAllPolling() { 2195 this.isPollingActive = false; 2196 this.pollingIntervals.forEach((interval) => { 2197 clearInterval(interval); 2198 }); 2199 } 2200 clearPolling() { 2201 this.stopAllPolling(); 2202 this.polls = []; 2203 this.startAllPolling(); 2204 } 2205 initiatePoll(actionName, duration) { 2206 let callback; 2207 if (actionName === "$render") callback = () => { 2208 this.component.render(); 2209 }; 2210 else callback = () => { 2211 this.component.action(actionName, {}, 0); 2212 }; 2213 const timer = window.setInterval(() => { 2214 callback(); 2215 }, duration); 2216 this.pollingIntervals.push(timer); 2217 } 2218}; 2219var PollingPlugin_default = class { 2220 element; 2221 pollingDirector; 2222 attachToComponent(component) { 2223 this.element = component.element; 2224 this.pollingDirector = new PollingDirector_default(component); 2225 this.initializePolling(); 2226 component.on("connect", () => { 2227 this.pollingDirector.startAllPolling(); 2228 }); 2229 component.on("disconnect", () => { 2230 this.pollingDirector.stopAllPolling(); 2231 }); 2232 component.on("render:finished", () => { 2233 this.initializePolling(); 2234 }); 2235 } 2236 addPoll(actionName, duration) { 2237 this.pollingDirector.addPoll(actionName, duration); 2238 } 2239 clearPolling() { 2240 this.pollingDirector.clearPolling(); 2241 } 2242 initializePolling() { 2243 this.clearPolling(); 2244 if (this.element.dataset.poll === void 0) return; 2245 const rawPollConfig = this.element.dataset.poll; 2246 parseDirectives(rawPollConfig || "$render").forEach((directive) => { 2247 let duration = 2e3; 2248 directive.modifiers.forEach((modifier) => { 2249 switch (modifier.name) { 2250 case "delay": 2251 if (modifier.value) duration = Number.parseInt(modifier.value); 2252 break; 2253 default: console.warn(`Unknown modifier "${modifier.name}" in data-poll "${rawPollConfig}".`); 2254 } 2255 }); 2256 this.addPoll(directive.action, duration); 2257 }); 2258 } 2259}; 2260var SetValueOntoModelFieldsPlugin_default = class { 2261 attachToComponent(component) { 2262 this.synchronizeValueOfModelFields(component); 2263 component.on("render:finished", () => { 2264 this.synchronizeValueOfModelFields(component); 2265 }); 2266 } 2267 synchronizeValueOfModelFields(component) { 2268 component.element.querySelectorAll("[data-model]").forEach((element) => { 2269 if (!(element instanceof HTMLElement)) throw new Error("Invalid element using data-model."); 2270 if (element instanceof HTMLFormElement) return; 2271 if (!elementBelongsToThisComponent(element, component)) return; 2272 const modelDirective = getModelDirectiveFromElement(element); 2273 if (!modelDirective) return; 2274 const modelName = modelDirective.action; 2275 if (component.getUnsyncedModels().includes(modelName)) return; 2276 if (component.valueStore.has(modelName)) setValueOnElement(element, component.valueStore.get(modelName)); 2277 if (element instanceof HTMLSelectElement && !element.multiple) component.valueStore.set(modelName, getValueFromElement(element, component.valueStore)); 2278 }); 2279 } 2280}; 2281var ValidatedFieldsPlugin_default = class { 2282 attachToComponent(component) { 2283 component.on("model:set", (modelName) => { 2284 this.handleModelSet(modelName, component.valueStore); 2285 }); 2286 } 2287 handleModelSet(modelName, valueStore) { 2288 if (valueStore.has("validatedFields")) { 2289 const validatedFields = [...valueStore.get("validatedFields")]; 2290 if (!validatedFields.includes(modelName)) validatedFields.push(modelName); 2291 valueStore.set("validatedFields", validatedFields); 2292 } 2293 } 2294}; 2295var LiveControllerDefault = class LiveControllerDefault extends Controller { 2296 static values = { 2297 name: String, 2298 url: String, 2299 props: { 2300 type: Object, 2301 default: {} 2302 }, 2303 propsUpdatedFromParent: { 2304 type: Object, 2305 default: {} 2306 }, 2307 listeners: { 2308 type: Array, 2309 default: [] 2310 }, 2311 eventsToEmit: { 2312 type: Array, 2313 default: [] 2314 }, 2315 eventsToDispatch: { 2316 type: Array, 2317 default: [] 2318 }, 2319 debounce: { 2320 type: Number, 2321 default: 150 2322 }, 2323 fingerprint: { 2324 type: String, 2325 default: "" 2326 }, 2327 requestMethod: { 2328 type: String, 2329 default: "post" 2330 }, 2331 fetchCredentials: { 2332 type: String, 2333 default: "same-origin" 2334 } 2335 }; 2336 proxiedComponent; 2337 mutationObserver; 2338 component; 2339 pendingActionTriggerModelElement = null; 2340 elementEventListeners = [{ 2341 event: "input", 2342 callback: (event) => this.handleInputEvent(event) 2343 }, { 2344 event: "change", 2345 callback: (event) => this.handleChangeEvent(event) 2346 }]; 2347 pendingFiles = {}; 2348 static backendFactory = (controller) => new Backend_default(controller.urlValue, controller.requestMethodValue, controller.fetchCredentialsValue); 2349 initialize() { 2350 this.mutationObserver = new MutationObserver(this.onMutations.bind(this)); 2351 this.createComponent(); 2352 } 2353 connect() { 2354 this.connectComponent(); 2355 this.mutationObserver.observe(this.element, { attributes: true }); 2356 } 2357 disconnect() { 2358 this.disconnectComponent(); 2359 this.mutationObserver.disconnect(); 2360 } 2361 update(event) { 2362 if (event.type === "input" || event.type === "change") throw new Error(`Since LiveComponents 2.3, you no longer need data-action="live#update" on form elements. Found on element: ${getElementAsTagText(event.currentTarget)}`); 2363 this.updateModelFromElementEvent(event.currentTarget, null); 2364 } 2365 action(event) { 2366 const params = event.params; 2367 if (!params.action) throw new Error(`No action name provided on element: ${getElementAsTagText(event.currentTarget)}. Did you forget to add the "data-live-action-param" attribute?`); 2368 const rawAction = params.action; 2369 const actionArgs = { ...params }; 2370 delete actionArgs.action; 2371 const directives = parseDirectives(rawAction); 2372 let debounce = false;
2373 directives.forEach((directive) => { 2374 let pendingFiles = {}; 2375 const validModifiers = /* @__PURE__ */ new Map(); 2376 validModifiers.set("stop", () => { 2377 event.stopPropagation(); 2378 }); 2379 validModifiers.set("self", () => { 2380 if (event.target !== event.currentTarget) return; 2381 }); 2382 validModifiers.set("debounce", (modifier) => { 2383 debounce = modifier.value ? Number.parseInt(modifier.value) : true; 2384 }); 2385 validModifiers.set("files", (modifier) => { 2386 if (!modifier.value) pendingFiles = this.pendingFiles; 2387 else if (this.pendingFiles[modifier.value]) pendingFiles[modifier.value] = this.pendingFiles[modifier.value]; 2388 }); 2389 directive.modifiers.forEach((modifier) => { 2390 if (validModifiers.has(modifier.name)) { 2391 (validModifiers.get(modifier.name) ?? (() => {}))(modifier); 2392 return; 2393 } 2394 console.warn(`Unknown modifier ${modifier.name} in action "${rawAction}". Available modifiers are: ${Array.from(validModifiers.keys()).join(", ")}.`); 2395 }); 2396 for (const [key, input] of Object.entries(pendingFiles)) { 2397 if (input.files) this.component.files(key, input); 2398 delete this.pendingFiles[key]; 2399 } 2400 this.component.action(directive.action, actionArgs, debounce); 2401 if (getModelDirectiveFromElement(event.currentTarget, false)) this.pendingActionTriggerModelElement = event.currentTarget; 2402 }); 2403 } 2404 $render() { 2405 return this.component.render(); 2406 } 2407 emit(event) { 2408 this.getEmitDirectives(event).forEach(({ name, data, nameMatch }) => { 2409 this.component.emit(name, data, nameMatch); 2410 }); 2411 } 2412 emitUp(event) { 2413 this.getEmitDirectives(event).forEach(({ name, data, nameMatch }) => { 2414 this.component.emitUp(name, data, nameMatch); 2415 }); 2416 } 2417 emitSelf(event) { 2418 this.getEmitDirectives(event).forEach(({ name, data }) => { 2419 this.component.emitSelf(name, data); 2420 }); 2421 } 2422 $updateModel(model, value, shouldRender = true, debounce = true) { 2423 return this.component.set(model, value, shouldRender, debounce); 2424 } 2425 propsUpdatedFromParentValueChanged() { 2426 this.component._updateFromParentProps(this.propsUpdatedFromParentValue); 2427 } 2428 fingerprintValueChanged() { 2429 this.component.fingerprint = this.fingerprintValue; 2430 } 2431 getEmitDirectives(event) { 2432 const params = event.params; 2433 if (!params.event) throw new Error(`No event name provided on element: ${getElementAsTagText(event.currentTarget)}. Did you forget to add the "data-live-event-param" attribute?`); 2434 const eventInfo = params.event; 2435 const eventArgs = { ...params }; 2436 delete eventArgs.event; 2437 const directives = parseDirectives(eventInfo); 2438 const emits = []; 2439 directives.forEach((directive) => { 2440 let nameMatch = null; 2441 directive.modifiers.forEach((modifier) => { 2442 switch (modifier.name) { 2443 case "name": 2444 nameMatch = modifier.value; 2445 break; 2446 default: throw new Error(`Unknown modifier ${modifier.name} in event "${eventInfo}".`); 2447 } 2448 }); 2449 emits.push({ 2450 name: directive.action, 2451 data: eventArgs, 2452 nameMatch 2453 }); 2454 }); 2455 return emits; 2456 } 2457 createComponent() { 2458 const id = this.element.id || null; 2459 this.component = new Component(this.element, this.nameValue, this.propsValue, this.listenersValue, id, LiveControllerDefault.backendFactory(this), new StimulusElementDriver(this)); 2460 this.proxiedComponent = proxifyComponent(this.component); 2461 Object.defineProperty(this.element, "__component", { 2462 value: this.proxiedComponent, 2463 writable: true 2464 }); 2465 if (this.hasDebounceValue) this.component.defaultDebounce = this.debounceValue; 2466 [ 2467 new LoadingPlugin_default(), 2468 new LazyPlugin_default(), 2469 new ValidatedFieldsPlugin_default(), 2470 new PageUnloadingPlugin_default(), 2471 new PollingPlugin_default(), 2472 new SetValueOntoModelFieldsPlugin_default(), 2473 new ChildComponentPlugin_default(this.component) 2474 ].forEach((plugin) => { 2475 this.component.addPlugin(plugin); 2476 }); 2477 } 2478 connectComponent() { 2479 this.component.connect(); 2480 this.mutationObserver.observe(this.element, { attributes: true }); 2481 this.elementEventListeners.forEach(({ event, callback }) => { 2482 this.component.element.addEventListener(event, callback); 2483 }); 2484 this.dispatchEvent("connect"); 2485 } 2486 disconnectComponent() { 2487 this.component.disconnect();
2488 this.elementEventListeners.forEach(({ event, callback }) => { 2489 this.component.element.removeEventListener(event, callback); 2490 }); 2491 this.dispatchEvent("disconnect"); 2492 } 2493 handleInputEvent(event) { 2494 const target = event.target; 2495 if (!target) return; 2496 this.updateModelFromElementEvent(target, "input"); 2497 } 2498 handleChangeEvent(event) { 2499 const target = event.target; 2500 if (!target) return; 2501 this.updateModelFromElementEvent(target, "change"); 2502 } 2503 updateModelFromElementEvent(element, eventName) { 2504 if (!elementBelongsToThisComponent(element, this.component)) return; 2505 if (!(element instanceof HTMLElement)) throw new Error("Could not update model for non HTMLElement"); 2506 if (element instanceof HTMLInputElement && element.type === "file") { 2507 const key = element.name; 2508 if (element.files?.length) this.pendingFiles[key] = element; 2509 else if (this.pendingFiles[key]) delete this.pendingFiles[key]; 2510 } 2511 const modelDirective = getModelDirectiveFromElement(element, false); 2512 if (!modelDirective) return; 2513 const modelBinding = get_model_binding_default(modelDirective); 2514 if (!modelBinding.targetEventName) modelBinding.targetEventName = "input"; 2515 if (this.pendingActionTriggerModelElement === element) modelBinding.shouldRender = false;
2516 if (eventName === "change" && modelBinding.targetEventName === "input") modelBinding.targetEventName = "change"; 2517 if (eventName && modelBinding.targetEventName !== eventName) return; 2518 if (false === modelBinding.debounce) if (modelBinding.targetEventName === "input") modelBinding.debounce = true; 2519 else modelBinding.debounce = 0; 2520 const finalValue = getValueFromElement(element, this.component.valueStore); 2521 const finalValueIsEmpty = finalValue === "" || finalValue === null || finalValue === void 0; 2522 if (isTextualInputElement(element) || isTextareaElement(element)) { 2523 if (!finalValueIsEmpty && modelBinding.minLength !== null && typeof finalValue === "string" && finalValue.length < modelBinding.minLength) return; 2524 if (!finalValueIsEmpty && modelBinding.maxLength !== null && typeof finalValue === "string" && finalValue.length > modelBinding.maxLength) return; 2525 } 2526 if (isNumericalInputElement(element)) { 2527 if (!finalValueIsEmpty) { 2528 const numericValue = Number(finalValue); 2529 if (modelBinding.minValue !== null && numericValue < modelBinding.minValue) return; 2530 if (modelBinding.maxValue !== null && numericValue > modelBinding.maxValue) return; 2531 } 2532 } 2533 this.component.set(modelBinding.modelName, finalValue, modelBinding.shouldRender, modelBinding.debounce); 2534 } 2535 dispatchEvent(name, detail = {}, canBubble = true, cancelable = false) { 2536 detail.controller = this; 2537 detail.component = this.proxiedComponent; 2538 this.dispatch(name, { 2539 detail, 2540 prefix: "live", 2541 cancelable, 2542 bubbles: canBubble 2543 }); 2544 } 2545 onMutations(mutations) { 2546 mutations.forEach((mutation) => { 2547 if (mutation.type === "attributes" && mutation.attributeName === "id" && this.component.getOriginalId() !== this.component.id) { 2548 this.disconnectComponent(); 2549 this.createComponent(); 2550 this.connectComponent(); 2551 } 2552 }); 2553 } 2554}; 2555export { Component, LiveControllerDefault as default, getComponent };
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.