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