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 actionsToSend = this.pendingActions.slice(0, 50); 1498 const remainingActions = this.pendingActions.slice(50); 1499 const requestConfig = { 1500 props: this.valueStore.getOriginalProps(), 1501 actions: actionsToSend, 1502 updated: this.valueStore.getDirtyProps(), 1503 children: {}, 1504 updatedPropsFromParent: this.valueStore.getUpdatedPropsFromParent(), 1505 files: filesToSend 1506 }; 1507 this.hooks.triggerHook("request:started", requestConfig); 1508 this.backendRequest = this.backend.makeRequest(requestConfig.props, requestConfig.actions, requestConfig.updated, requestConfig.children, requestConfig.updatedPropsFromParent, requestConfig.files); 1509 this.hooks.triggerHook("loading.state:started", this.element, this.backendRequest); 1510 this.pendingActions = remainingActions; 1511 this.valueStore.flushDirtyPropsToPending(); 1512 this.isRequestPending = remainingActions.length > 0; 1513 this.backendRequest.promise.then(async (response) => { 1514 const backendResponse = new BackendResponse_default(response); 1515 const html = await backendResponse.getBody(); 1516 for (const input of Object.values(this.pendingFiles)) input.value = ""; 1517 const headers = backendResponse.response.headers; 1518 if (!headers.get("Content-Type")?.includes("application/vnd.live-component+html") && !headers.get("X-Live-Redirect")) { 1519 const controls = { displayError: true }; 1520 this.valueStore.pushPendingPropsBackToDirty(); 1521 this.hooks.triggerHook("response:error", backendResponse, controls); 1522 if (controls.displayError) this.renderError(html); 1523 this.backendRequest = null; 1524 thisPromiseResolve(backendResponse); 1525 return response; 1526 } 1527 const liveUrl = backendResponse.getLiveUrl(); 1528 if (liveUrl) history.replaceState(history.state, "", new URL(liveUrl + window.location.hash, window.location.origin)); 1529 this.processRerender(html, backendResponse); 1530 this.backendRequest = null; 1531 thisPromiseResolve(backendResponse); 1532 if (this.isRequestPending) { 1533 this.isRequestPending = false; 1534 this.performRequest(); 1535 } 1536 return response; 1537 }); 1538 } 1539 processRerender(html, backendResponse) { 1540 const controls = { shouldRender: true }; 1541 this.hooks.triggerHook("render:started", html, backendResponse, controls); 1542 if (!controls.shouldRender) return; 1543 if (backendResponse.response.headers.get("Location")) { 1544 if (this.isTurboEnabled()) Turbo.visit(backendResponse.response.headers.get("Location")); 1545 else window.location.href = backendResponse.response.headers.get("Location") || ""; 1546 return; 1547 } 1548 this.hooks.triggerHook("loading.state:finished", this.element); 1549 const modifiedModelValues = {}; 1550 Object.keys(this.valueStore.getDirtyProps()).forEach((modelName) => { 1551 modifiedModelValues[modelName] = this.valueStore.get(modelName); 1552 }); 1553 let newElement; 1554 try { 1555 newElement = htmlToElement(html); 1556 if (!newElement.matches("[data-controller~=live]")) throw new Error("A live component template must contain a single root controller element."); 1557 } catch (error) { 1558 console.error(`There was a problem with the '${this.name}' component HTML returned:`, { id: this.id }); 1559 throw error; 1560 } 1561 this.externalMutationTracker.handlePendingChanges(); 1562 this.externalMutationTracker.stop(); 1563 executeMorphdom(this.element, newElement, this.unsyncedInputsTracker.getUnsyncedInputs(), (element) => getValueFromElement(element, this.valueStore), this.externalMutationTracker); 1564 this.externalMutationTracker.start(); 1565 const newProps = this.elementDriver.getComponentProps(); 1566 this.valueStore.reinitializeAllProps(newProps); 1567 const eventsToEmit = this.elementDriver.getEventsToEmit(); 1568 const browserEventsToDispatch = this.elementDriver.getBrowserEventsToDispatch();
1569 Object.keys(modifiedModelValues).forEach((modelName) => { 1570 this.valueStore.set(modelName, modifiedModelValues[modelName]); 1571 }); 1572 eventsToEmit.forEach(({ event, data, target, componentName }) => { 1573 if (target === "up") { 1574 this.emitUp(event, data, componentName); 1575 return; 1576 } 1577 if (target === "self") { 1578 this.emitSelf(event, data); 1579 return; 1580 } 1581 this.emit(event, data, componentName); 1582 }); 1583 browserEventsToDispatch.forEach(({ event, payload }) => { 1584 this.element.dispatchEvent(new CustomEvent(event, { 1585 detail: payload, 1586 bubbles: true 1587 })); 1588 }); 1589 this.hooks.triggerHook("render:finished", this); 1590 } 1591 calculateDebounce(debounce) { 1592 if (debounce === true) return this.defaultDebounce; 1593 if (debounce === false) return 0; 1594 return debounce; 1595 } 1596 clearRequestDebounceTimeout() { 1597 if (this.requestDebounceTimeout) { 1598 clearTimeout(this.requestDebounceTimeout); 1599 this.requestDebounceTimeout = null; 1600 } 1601 } 1602 debouncedStartRequest(debounce) { 1603 this.clearRequestDebounceTimeout(); 1604 this.requestDebounceTimeout = window.setTimeout(() => { 1605 this.render(); 1606 }, this.calculateDebounce(debounce)); 1607 } 1608 renderError(html) { 1609 let modal = document.getElementById("live-component-error"); 1610 if (modal) modal.innerHTML = ""; 1611 else { 1612 modal = document.createElement("div"); 1613 modal.id = "live-component-error"; 1614 modal.style.padding = "50px"; 1615 modal.style.backgroundColor = "rgba(0, 0, 0, .5)"; 1616 modal.style.zIndex = "100000"; 1617 modal.style.position = "fixed"; 1618 modal.style.top = "0px"; 1619 modal.style.bottom = "0px"; 1620 modal.style.left = "0px"; 1621 modal.style.right = "0px"; 1622 modal.style.display = "flex"; 1623 modal.style.flexDirection = "column"; 1624 } 1625 const iframe = document.createElement("iframe"); 1626 iframe.style.borderRadius = "5px"; 1627 iframe.style.flexGrow = "1"; 1628 modal.appendChild(iframe); 1629 document.body.prepend(modal); 1630 document.body.style.overflow = "hidden"; 1631 if (iframe.contentWindow) { 1632 iframe.contentWindow.document.open(); 1633 iframe.contentWindow.document.write(html); 1634 iframe.contentWindow.document.close(); 1635 } 1636 const closeModal = (modal) => { 1637 if (modal) modal.outerHTML = ""; 1638 document.body.style.overflow = "visible"; 1639 }; 1640 modal.addEventListener("click", () => closeModal(modal)); 1641 modal.setAttribute("tabindex", "0"); 1642 modal.addEventListener("keydown", (e) => { 1643 if (e.key === "Escape") closeModal(modal); 1644 }); 1645 modal.focus(); 1646 } 1647 resetPromise() { 1648 this.nextRequestPromise = new Promise((resolve) => { 1649 this.nextRequestPromiseResolve = resolve; 1650 }); 1651 } 1652 _updateFromParentProps(props) { 1653 if (this.valueStore.storeNewPropsFromParent(props)) this.render(); 1654 } 1655}; 1656function proxifyComponent(component) { 1657 return new Proxy(component, { 1658 get(component, prop) { 1659 if (prop in component || typeof prop !== "string") { 1660 if (typeof component[prop] === "function") { 1661 const callable = component[prop]; 1662 return (...args) => { 1663 return callable.apply(component, args); 1664 }; 1665 } 1666 return Reflect.get(component, prop); 1667 } 1668 if (component.valueStore.has(prop)) return component.getData(prop); 1669 return (args) => { 1670 return component.action.apply(component, [prop, args]); 1671 }; 1672 }, 1673 set(target, property, value) { 1674 if (property in target) { 1675 target[property] = value; 1676 return true; 1677 } 1678 target.set(property, value); 1679 return true; 1680 } 1681 }); 1682} 1683var StimulusElementDriver = class { 1684 constructor(controller) { 1685 this.controller = controller; 1686 } 1687 getModelName(element) { 1688 const modelDirective = getModelDirectiveFromElement(element, false); 1689 if (!modelDirective) return null; 1690 return modelDirective.action; 1691 } 1692 getComponentProps() { 1693 return this.controller.propsValue; 1694 } 1695 getEventsToEmit() { 1696 return this.controller.eventsToEmitValue; 1697 } 1698 getBrowserEventsToDispatch() { 1699 return this.controller.eventsToDispatchValue; 1700 } 1701}; 1702function get_model_binding_default(modelDirective) { 1703 let shouldRender = true; 1704 let targetEventName = null; 1705 let debounce = false; 1706 let minLength = null; 1707 let maxLength = null; 1708 let minValue = null; 1709 let maxValue = null; 1710 modelDirective.modifiers.forEach((modifier) => {
1711 switch (modifier.name) { 1712 case "on": 1713 if (!modifier.value) throw new Error(`The "on" modifier in ${modelDirective.getString()} requires a value - e.g. on(change).`); 1714 if (!["input", "change"].includes(modifier.value)) throw new Error(`The "on" modifier in ${modelDirective.getString()} only accepts the arguments "input" or "change".`); 1715 targetEventName = modifier.value; 1716 break; 1717 case "norender": 1718 shouldRender = false; 1719 break; 1720 case "debounce": 1721 debounce = modifier.value ? Number.parseInt(modifier.value) : true; 1722 break; 1723 case "min_length": 1724 minLength = modifier.value ? Number.parseInt(modifier.value) : null; 1725 break; 1726 case "max_length": 1727 maxLength = modifier.value ? Number.parseInt(modifier.value) : null; 1728 break; 1729 case "min_value": 1730 minValue = modifier.value ? Number.parseFloat(modifier.value) : null; 1731 break; 1732 case "max_value": 1733 maxValue = modifier.value ? Number.parseFloat(modifier.value) : null; 1734 break; 1735 default: throw new Error(`Unknown modifier "${modifier.name}" in data-model="${modelDirective.getString()}".`); 1736 } 1737 }); 1738 const [modelName, innerModelName] = modelDirective.action.split(":"); 1739 return { 1740 modelName, 1741 innerModelName: innerModelName || null, 1742 shouldRender, 1743 debounce, 1744 targetEventName, 1745 minLength, 1746 maxLength, 1747 minValue, 1748 maxValue 1749 }; 1750} 1751var ChildComponentPlugin_default = class { 1752 constructor(component) { 1753 this.parentModelBindings = []; 1754 this.component = component; 1755 this.parentModelBindings = getAllModelDirectiveFromElements(this.component.element).map(get_model_binding_default); 1756 } 1757 attachToComponent(component) { 1758 component.on("request:started", (requestData) => { 1759 requestData.children = this.getChildrenFingerprints(); 1760 }); 1761 component.on("model:set", (model, value) => { 1762 this.notifyParentModelChange(model, value); 1763 }); 1764 } 1765 getChildrenFingerprints() { 1766 const fingerprints = {};
1767 this.getChildren().forEach((child) => { 1768 if (!child.id) throw new Error("missing id"); 1769 fingerprints[child.id] = { 1770 fingerprint: child.fingerprint, 1771 tag: child.element.tagName.toLowerCase() 1772 }; 1773 }); 1774 return fingerprints; 1775 } 1776 notifyParentModelChange(modelName, value) { 1777 const parentComponent = findParent(this.component); 1778 if (!parentComponent) return; 1779 this.parentModelBindings.forEach((modelBinding) => { 1780 if ((modelBinding.innerModelName || "value") !== modelName) return; 1781 parentComponent.set(modelBinding.modelName, value, modelBinding.shouldRender, modelBinding.debounce); 1782 }); 1783 } 1784 getChildren() { 1785 return findChildren(this.component); 1786 } 1787}; 1788var LazyPlugin_default = class { 1789 constructor() { 1790 this.intersectionObserver = null; 1791 } 1792 attachToComponent(component) { 1793 if ("lazy" !== component.element.attributes.getNamedItem("loading")?.value) return; 1794 component.on("connect", () => { 1795 this.getObserver().observe(component.element); 1796 }); 1797 component.on("disconnect", () => { 1798 this.intersectionObserver?.unobserve(component.element); 1799 }); 1800 } 1801 getObserver() { 1802 if (!this.intersectionObserver) this.intersectionObserver = new IntersectionObserver((entries, observer) => { 1803 entries.forEach((entry) => { 1804 if (entry.isIntersecting) { 1805 entry.target.dispatchEvent(new CustomEvent("live:appear")); 1806 observer.unobserve(entry.target); 1807 } 1808 }); 1809 }); 1810 return this.intersectionObserver; 1811 } 1812}; 1813var LoadingPlugin_default = class { 1814 attachToComponent(component) { 1815 component.on("loading.state:started", (element, request) => { 1816 this.startLoading(component, element, request); 1817 }); 1818 component.on("loading.state:finished", (element) => { 1819 this.finishLoading(component, element); 1820 }); 1821 this.finishLoading(component, component.element); 1822 } 1823 startLoading(component, targetElement, backendRequest) { 1824 this.handleLoadingToggle(component, true, targetElement, backendRequest); 1825 } 1826 finishLoading(component, targetElement) { 1827 this.handleLoadingToggle(component, false, targetElement, null); 1828 } 1829 handleLoadingToggle(component, isLoading, targetElement, backendRequest) { 1830 if (isLoading) this.addAttributes(targetElement, ["busy"]); 1831 else this.removeAttributes(targetElement, ["busy"]); 1832 this.getLoadingDirectives(component, targetElement).forEach(({ element, directives }) => { 1833 if (isLoading) this.addAttributes(element, ["data-live-is-loading"]); 1834 else this.removeAttributes(element, ["data-live-is-loading"]); 1835 directives.forEach((directive) => { 1836 this.handleLoadingDirective(element, isLoading, directive, backendRequest); 1837 }); 1838 }); 1839 } 1840 handleLoadingDirective(element, isLoading, directive, backendRequest) { 1841 const finalAction = parseLoadingAction(directive.action, isLoading); 1842 const targetedActions = []; 1843 const targetedModels = []; 1844 let delay = 0; 1845 const validModifiers = /* @__PURE__ */ new Map(); 1846 validModifiers.set("delay", (modifier) => { 1847 if (!isLoading) return; 1848 delay = modifier.value ? Number.parseInt(modifier.value) : 200; 1849 }); 1850 validModifiers.set("action", (modifier) => { 1851 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()}"`); 1852 targetedActions.push(modifier.value); 1853 }); 1854 validModifiers.set("model", (modifier) => { 1855 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()}"`); 1856 targetedModels.push(modifier.value); 1857 }); 1858 directive.modifiers.forEach((modifier) => { 1859 if (validModifiers.has(modifier.name)) { 1860 (validModifiers.get(modifier.name) ?? (() => {}))(modifier); 1861 return; 1862 } 1863 throw new Error(`Unknown modifier "${modifier.name}" used in data-loading="${directive.getString()}". Available modifiers are: ${Array.from(validModifiers.keys()).join(", ")}.`); 1864 }); 1865 if (isLoading && targetedActions.length > 0 && backendRequest && !backendRequest.containsOneOfActions(targetedActions)) return; 1866 if (isLoading && targetedModels.length > 0 && backendRequest && !backendRequest.areAnyModelsUpdated(targetedModels)) return; 1867 let loadingDirective; 1868 switch (finalAction) { 1869 case "show": 1870 loadingDirective = () => this.showElement(element); 1871 break; 1872 case "hide": 1873 loadingDirective = () => this.hideElement(element); 1874 break; 1875 case "addClass": 1876 loadingDirective = () => this.addClass(element, directive.args); 1877 break; 1878 case "removeClass": 1879 loadingDirective = () => this.removeClass(element, directive.args); 1880 break; 1881 case "addAttribute": 1882 loadingDirective = () => this.addAttributes(element, directive.args); 1883 break; 1884 case "removeAttribute": 1885 loadingDirective = () => this.removeAttributes(element, directive.args); 1886 break; 1887 default: throw new Error(`Unknown data-loading action "${finalAction}"`); 1888 } 1889 if (delay) { 1890 window.setTimeout(() => { 1891 if (backendRequest && !backendRequest.isResolved) loadingDirective(); 1892 }, delay); 1893 return; 1894 } 1895 loadingDirective(); 1896 } 1897 getLoadingDirectives(component, element) { 1898 const loadingDirectives = []; 1899 let matchingElements = Array.from(element.querySelectorAll("[data-loading]")); 1900 matchingElements = matchingElements.filter((elt) => elementBelongsToThisComponent(elt, component)); 1901 if (element.hasAttribute("data-loading")) matchingElements = [element, ...matchingElements];
1902 matchingElements.forEach((element) => { 1903 if (!(element instanceof HTMLElement) && !(element instanceof SVGElement)) throw new Error("Invalid Element Type"); 1904 const directives = parseDirectives(element.dataset.loading || "show"); 1905 loadingDirectives.push({ 1906 element, 1907 directives 1908 }); 1909 }); 1910 return loadingDirectives; 1911 } 1912 showElement(element) { 1913 element.style.display = "revert"; 1914 } 1915 hideElement(element) { 1916 element.style.display = "none"; 1917 } 1918 addClass(element, classes) { 1919 element.classList.add(...combineSpacedArray(classes)); 1920 } 1921 removeClass(element, classes) { 1922 element.classList.remove(...combineSpacedArray(classes)); 1923 if (element.classList.length === 0) element.removeAttribute("class"); 1924 } 1925 addAttributes(element, attributes) { 1926 attributes.forEach((attribute) => { 1927 element.setAttribute(attribute, ""); 1928 }); 1929 } 1930 removeAttributes(element, attributes) { 1931 attributes.forEach((attribute) => { 1932 element.removeAttribute(attribute); 1933 }); 1934 } 1935}; 1936const parseLoadingAction = (action, isLoading) => { 1937 switch (action) { 1938 case "show": return isLoading ? "show" : "hide"; 1939 case "hide": return isLoading ? "hide" : "show"; 1940 case "addClass": return isLoading ? "addClass" : "removeClass"; 1941 case "removeClass": return isLoading ? "removeClass" : "addClass"; 1942 case "addAttribute": return isLoading ? "addAttribute" : "removeAttribute"; 1943 case "removeAttribute": return isLoading ? "removeAttribute" : "addAttribute"; 1944 } 1945 throw new Error(`Unknown data-loading action "${action}"`); 1946}; 1947var PageUnloadingPlugin_default = class { 1948 constructor() { 1949 this.isConnected = false; 1950 } 1951 attachToComponent(component) { 1952 component.on("render:started", (html, response, controls) => { 1953 if (!this.isConnected) controls.shouldRender = false;
1954 }); 1955 component.on("connect", () => { 1956 this.isConnected = true; 1957 }); 1958 component.on("disconnect", () => { 1959 this.isConnected = false; 1960 }); 1961 } 1962}; 1963var PollingDirector_default = class { 1964 constructor(component) { 1965 this.isPollingActive = true; 1966 this.pollingIntervals = []; 1967 this.component = component; 1968 } 1969 addPoll(actionName, duration) { 1970 this.polls.push({ 1971 actionName, 1972 duration 1973 }); 1974 if (this.isPollingActive) this.initiatePoll(actionName, duration); 1975 } 1976 startAllPolling() { 1977 if (this.isPollingActive) return; 1978 this.isPollingActive = true; 1979 this.polls.forEach(({ actionName, duration }) => { 1980 this.initiatePoll(actionName, duration); 1981 }); 1982 } 1983 stopAllPolling() { 1984 this.isPollingActive = false; 1985 this.pollingIntervals.forEach((interval) => { 1986 clearInterval(interval); 1987 }); 1988 } 1989 clearPolling() { 1990 this.stopAllPolling(); 1991 this.polls = []; 1992 this.startAllPolling(); 1993 } 1994 initiatePoll(actionName, duration) { 1995 let callback; 1996 if (actionName === "$render") callback = () => { 1997 this.component.render(); 1998 }; 1999 else callback = () => { 2000 this.component.action(actionName, {}, 0); 2001 }; 2002 const timer = window.setInterval(() => { 2003 callback(); 2004 }, duration); 2005 this.pollingIntervals.push(timer); 2006 } 2007}; 2008var PollingPlugin_default = class { 2009 attachToComponent(component) { 2010 this.element = component.element; 2011 this.pollingDirector = new PollingDirector_default(component); 2012 this.initializePolling(); 2013 component.on("connect", () => { 2014 this.pollingDirector.startAllPolling(); 2015 }); 2016 component.on("disconnect", () => { 2017 this.pollingDirector.stopAllPolling(); 2018 }); 2019 component.on("render:finished", () => { 2020 this.initializePolling(); 2021 }); 2022 } 2023 addPoll(actionName, duration) { 2024 this.pollingDirector.addPoll(actionName, duration); 2025 } 2026 clearPolling() { 2027 this.pollingDirector.clearPolling(); 2028 } 2029 initializePolling() { 2030 this.clearPolling(); 2031 if (this.element.dataset.poll === void 0) return; 2032 const rawPollConfig = this.element.dataset.poll; 2033 parseDirectives(rawPollConfig || "$render").forEach((directive) => { 2034 let duration = 2e3; 2035 directive.modifiers.forEach((modifier) => { 2036 switch (modifier.name) { 2037 case "delay": 2038 if (modifier.value) duration = Number.parseInt(modifier.value); 2039 break; 2040 default: console.warn(`Unknown modifier "${modifier.name}" in data-poll "${rawPollConfig}".`); 2041 } 2042 }); 2043 this.addPoll(directive.action, duration); 2044 }); 2045 } 2046}; 2047var SetValueOntoModelFieldsPlugin_default = class { 2048 attachToComponent(component) { 2049 this.synchronizeValueOfModelFields(component); 2050 component.on("render:finished", () => { 2051 this.synchronizeValueOfModelFields(component); 2052 }); 2053 } 2054 synchronizeValueOfModelFields(component) { 2055 component.element.querySelectorAll("[data-model]").forEach((element) => { 2056 if (!(element instanceof HTMLElement)) throw new Error("Invalid element using data-model."); 2057 if (element instanceof HTMLFormElement) return; 2058 if (!elementBelongsToThisComponent(element, component)) return; 2059 const modelDirective = getModelDirectiveFromElement(element); 2060 if (!modelDirective) return; 2061 const modelName = modelDirective.action; 2062 if (component.getUnsyncedModels().includes(modelName)) return; 2063 if (component.valueStore.has(modelName)) setValueOnElement(element, component.valueStore.get(modelName)); 2064 if (element instanceof HTMLSelectElement && !element.multiple) component.valueStore.set(modelName, getValueFromElement(element, component.valueStore)); 2065 }); 2066 } 2067}; 2068var ValidatedFieldsPlugin_default = class { 2069 attachToComponent(component) { 2070 component.on("model:set", (modelName) => { 2071 this.handleModelSet(modelName, component.valueStore); 2072 }); 2073 } 2074 handleModelSet(modelName, valueStore) { 2075 if (valueStore.has("validatedFields")) { 2076 const validatedFields = [...valueStore.get("validatedFields")]; 2077 if (!validatedFields.includes(modelName)) validatedFields.push(modelName); 2078 valueStore.set("validatedFields", validatedFields); 2079 } 2080 } 2081}; 2082var LiveControllerDefault = class LiveControllerDefault extends Controller { 2083 constructor(..._args) { 2084 super(..._args); 2085 this.pendingActionTriggerModelElement = null; 2086 this.elementEventListeners = [{ 2087 event: "input", 2088 callback: (event) => this.handleInputEvent(event) 2089 }, { 2090 event: "change", 2091 callback: (event) => this.handleChangeEvent(event) 2092 }]; 2093 this.pendingFiles = {}; 2094 } 2095 initialize() { 2096 this.mutationObserver = new MutationObserver(this.onMutations.bind(this)); 2097 this.createComponent(); 2098 } 2099 connect() { 2100 this.connectComponent(); 2101 this.mutationObserver.observe(this.element, { attributes: true }); 2102 } 2103 disconnect() { 2104 this.disconnectComponent(); 2105 this.mutationObserver.disconnect(); 2106 } 2107 update(event) { 2108 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)}`); 2109 this.updateModelFromElementEvent(event.currentTarget, null); 2110 } 2111 action(event) { 2112 const params = event.params; 2113 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?`); 2114 const rawAction = params.action; 2115 const actionArgs = { ...params }; 2116 delete actionArgs.action; 2117 const directives = parseDirectives(rawAction); 2118 let debounce = false;
2119 directives.forEach((directive) => { 2120 let pendingFiles = {}; 2121 const validModifiers = /* @__PURE__ */ new Map(); 2122 validModifiers.set("stop", () => { 2123 event.stopPropagation(); 2124 }); 2125 validModifiers.set("self", () => { 2126 if (event.target !== event.currentTarget) return; 2127 }); 2128 validModifiers.set("debounce", (modifier) => { 2129 debounce = modifier.value ? Number.parseInt(modifier.value) : true; 2130 }); 2131 validModifiers.set("files", (modifier) => { 2132 if (!modifier.value) pendingFiles = this.pendingFiles; 2133 else if (this.pendingFiles[modifier.value]) pendingFiles[modifier.value] = this.pendingFiles[modifier.value]; 2134 }); 2135 directive.modifiers.forEach((modifier) => { 2136 if (validModifiers.has(modifier.name)) { 2137 (validModifiers.get(modifier.name) ?? (() => {}))(modifier); 2138 return; 2139 } 2140 console.warn(`Unknown modifier ${modifier.name} in action "${rawAction}". Available modifiers are: ${Array.from(validModifiers.keys()).join(", ")}.`); 2141 }); 2142 for (const [key, input] of Object.entries(pendingFiles)) { 2143 if (input.files) this.component.files(key, input); 2144 delete this.pendingFiles[key]; 2145 } 2146 this.component.action(directive.action, actionArgs, debounce); 2147 if (getModelDirectiveFromElement(event.currentTarget, false)) this.pendingActionTriggerModelElement = event.currentTarget; 2148 }); 2149 } 2150 $render() { 2151 return this.component.render(); 2152 } 2153 emit(event) { 2154 this.getEmitDirectives(event).forEach(({ name, data, nameMatch }) => { 2155 this.component.emit(name, data, nameMatch); 2156 }); 2157 } 2158 emitUp(event) { 2159 this.getEmitDirectives(event).forEach(({ name, data, nameMatch }) => { 2160 this.component.emitUp(name, data, nameMatch); 2161 }); 2162 } 2163 emitSelf(event) { 2164 this.getEmitDirectives(event).forEach(({ name, data }) => { 2165 this.component.emitSelf(name, data); 2166 }); 2167 } 2168 $updateModel(model, value, shouldRender = true, debounce = true) { 2169 return this.component.set(model, value, shouldRender, debounce); 2170 } 2171 propsUpdatedFromParentValueChanged() { 2172 this.component._updateFromParentProps(this.propsUpdatedFromParentValue); 2173 } 2174 fingerprintValueChanged() { 2175 this.component.fingerprint = this.fingerprintValue; 2176 } 2177 getEmitDirectives(event) { 2178 const params = event.params; 2179 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?`); 2180 const eventInfo = params.event; 2181 const eventArgs = { ...params }; 2182 delete eventArgs.event; 2183 const directives = parseDirectives(eventInfo); 2184 const emits = []; 2185 directives.forEach((directive) => { 2186 let nameMatch = null; 2187 directive.modifiers.forEach((modifier) => { 2188 switch (modifier.name) { 2189 case "name": 2190 nameMatch = modifier.value; 2191 break; 2192 default: throw new Error(`Unknown modifier ${modifier.name} in event "${eventInfo}".`); 2193 } 2194 }); 2195 emits.push({ 2196 name: directive.action, 2197 data: eventArgs, 2198 nameMatch 2199 }); 2200 }); 2201 return emits; 2202 } 2203 createComponent() { 2204 const id = this.element.id || null; 2205 this.component = new Component(this.element, this.nameValue, this.propsValue, this.listenersValue, id, LiveControllerDefault.backendFactory(this), new StimulusElementDriver(this)); 2206 this.proxiedComponent = proxifyComponent(this.component); 2207 Object.defineProperty(this.element, "__component", { 2208 value: this.proxiedComponent, 2209 writable: true 2210 }); 2211 if (this.hasDebounceValue) this.component.defaultDebounce = this.debounceValue; 2212 [ 2213 new LoadingPlugin_default(), 2214 new LazyPlugin_default(), 2215 new ValidatedFieldsPlugin_default(), 2216 new PageUnloadingPlugin_default(), 2217 new PollingPlugin_default(), 2218 new SetValueOntoModelFieldsPlugin_default(), 2219 new ChildComponentPlugin_default(this.component) 2220 ].forEach((plugin) => { 2221 this.component.addPlugin(plugin); 2222 }); 2223 } 2224 connectComponent() { 2225 this.component.connect(); 2226 this.mutationObserver.observe(this.element, { attributes: true }); 2227 this.elementEventListeners.forEach(({ event, callback }) => { 2228 this.component.element.addEventListener(event, callback); 2229 }); 2230 this.dispatchEvent("connect"); 2231 } 2232 disconnectComponent() { 2233 this.component.disconnect();
2234 this.elementEventListeners.forEach(({ event, callback }) => { 2235 this.component.element.removeEventListener(event, callback); 2236 }); 2237 this.dispatchEvent("disconnect"); 2238 } 2239 handleInputEvent(event) { 2240 const target = event.target; 2241 if (!target) return; 2242 this.updateModelFromElementEvent(target, "input"); 2243 } 2244 handleChangeEvent(event) { 2245 const target = event.target; 2246 if (!target) return; 2247 this.updateModelFromElementEvent(target, "change"); 2248 } 2249 updateModelFromElementEvent(element, eventName) { 2250 if (!elementBelongsToThisComponent(element, this.component)) return; 2251 if (!(element instanceof HTMLElement)) throw new Error("Could not update model for non HTMLElement"); 2252 if (element instanceof HTMLInputElement && element.type === "file") { 2253 const key = element.name; 2254 if (element.files?.length) this.pendingFiles[key] = element; 2255 else if (this.pendingFiles[key]) delete this.pendingFiles[key]; 2256 } 2257 const modelDirective = getModelDirectiveFromElement(element, false); 2258 if (!modelDirective) return; 2259 const modelBinding = get_model_binding_default(modelDirective); 2260 if (!modelBinding.targetEventName) modelBinding.targetEventName = "input"; 2261 if (this.pendingActionTriggerModelElement === element) modelBinding.shouldRender = false;
2262 if (eventName === "change" && modelBinding.targetEventName === "input") modelBinding.targetEventName = "change"; 2263 if (eventName && modelBinding.targetEventName !== eventName) return; 2264 if (false === modelBinding.debounce) if (modelBinding.targetEventName === "input") modelBinding.debounce = true; 2265 else modelBinding.debounce = 0; 2266 const finalValue = getValueFromElement(element, this.component.valueStore); 2267 const finalValueIsEmpty = finalValue === "" || finalValue === null || finalValue === void 0; 2268 if (isTextualInputElement(element) || isTextareaElement(element)) { 2269 if (!finalValueIsEmpty && modelBinding.minLength !== null && typeof finalValue === "string" && finalValue.length < modelBinding.minLength) return; 2270 if (!finalValueIsEmpty && modelBinding.maxLength !== null && typeof finalValue === "string" && finalValue.length > modelBinding.maxLength) return; 2271 } 2272 if (isNumericalInputElement(element)) { 2273 if (!finalValueIsEmpty) { 2274 const numericValue = Number(finalValue); 2275 if (modelBinding.minValue !== null && numericValue < modelBinding.minValue) return; 2276 if (modelBinding.maxValue !== null && numericValue > modelBinding.maxValue) return; 2277 } 2278 } 2279 this.component.set(modelBinding.modelName, finalValue, modelBinding.shouldRender, modelBinding.debounce); 2280 } 2281 dispatchEvent(name, detail = {}, canBubble = true, cancelable = false) { 2282 detail.controller = this; 2283 detail.component = this.proxiedComponent; 2284 this.dispatch(name, { 2285 detail, 2286 prefix: "live", 2287 cancelable, 2288 bubbles: canBubble 2289 }); 2290 } 2291 onMutations(mutations) { 2292 mutations.forEach((mutation) => { 2293 if (mutation.type === "attributes" && mutation.attributeName === "id" && this.element.id !== this.component.id) { 2294 this.disconnectComponent(); 2295 this.createComponent(); 2296 this.connectComponent(); 2297 } 2298 }); 2299 } 2300}; 2301LiveControllerDefault.values = { 2302 name: String, 2303 url: String, 2304 props: { 2305 type: Object, 2306 default: {} 2307 }, 2308 propsUpdatedFromParent: { 2309 type: Object, 2310 default: {} 2311 }, 2312 listeners: { 2313 type: Array, 2314 default: [] 2315 }, 2316 eventsToEmit: { 2317 type: Array, 2318 default: [] 2319 }, 2320 eventsToDispatch: { 2321 type: Array, 2322 default: [] 2323 }, 2324 debounce: { 2325 type: Number, 2326 default: 150 2327 }, 2328 fingerprint: { 2329 type: String, 2330 default: "" 2331 }, 2332 requestMethod: { 2333 type: String, 2334 default: "post" 2335 }, 2336 fetchCredentials: { 2337 type: String, 2338 default: "same-origin" 2339 } 2340}; 2341LiveControllerDefault.backendFactory = (controller) => new Backend_default(controller.urlValue, controller.requestMethodValue, controller.fetchCredentialsValue); 2342export { 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.