1// src/live_controller.ts 2import { Controller } from "@hotwired/stimulus"; 3
4// src/Backend/BackendRequest.ts 5var BackendRequest_default = class { 6 constructor(promise, actions, updateModels) { 7 this.isResolved = false; 8 this.promise = promise; 9 this.promise.then((response) => { 10 this.isResolved = true; 11 return response; 12 }); 13 this.actions = actions; 14 this.updatedModels = updateModels; 15 } 16 /** 17 * Does this BackendRequest contain at least on action in targetedActions? 18 */ 19 containsOneOfActions(targetedActions) { 20 return this.actions.filter((action) => targetedActions.includes(action)).length > 0; 21 } 22 /** 23 * Does this BackendRequest includes updates for any of these models? 24 */ 25 areAnyModelsUpdated(targetedModels) { 26 return this.updatedModels.filter((model) => targetedModels.includes(model)).length > 0; 27 } 28}; 29
30// src/Backend/RequestBuilder.ts 31var RequestBuilder_default = class { 32 constructor(url, method = "post") { 33 this.url = url; 34 this.method = method; 35 } 36 buildRequest(props, actions, updated, children, updatedPropsFromParent, files) { 37 const splitUrl = this.url.split("?"); 38 let [url] = splitUrl; 39 const [, queryString] = splitUrl; 40 const params = new URLSearchParams(queryString || ""); 41 const fetchOptions = {}; 42 fetchOptions.headers = { 43 Accept: "application/vnd.live-component+html", 44 "X-Requested-With": "XMLHttpRequest", 45 "X-Live-Url": window.location.pathname + window.location.search 46 }; 47 const totalFiles = Object.entries(files).reduce((total, current) => total + current.length, 0); 48 const hasFingerprints = Object.keys(children).length > 0; 49 if (actions.length === 0 && totalFiles === 0 && this.method === "get" && this.willDataFitInUrl( 50 JSON.stringify(props), 51 JSON.stringify(updated), 52 params, 53 JSON.stringify(children), 54 JSON.stringify(updatedPropsFromParent) 55 )) { 56 params.set("props", JSON.stringify(props)); 57 params.set("updated", JSON.stringify(updated)); 58 if (Object.keys(updatedPropsFromParent).length > 0) { 59 params.set("propsFromParent", JSON.stringify(updatedPropsFromParent)); 60 } 61 if (hasFingerprints) { 62 params.set("children", JSON.stringify(children)); 63 } 64 fetchOptions.method = "GET"; 65 } else { 66 fetchOptions.method = "POST"; 67 const requestData = { props, updated }; 68 if (Object.keys(updatedPropsFromParent).length > 0) { 69 requestData.propsFromParent = updatedPropsFromParent; 70 } 71 if (hasFingerprints) { 72 requestData.children = children; 73 } 74 if (actions.length > 0) { 75 if (actions.length === 1) { 76 requestData.args = actions[0].args; 77 url += `/${encodeURIComponent(actions[0].name)}`; 78 } else { 79 url += "/_batch"; 80 requestData.actions = actions; 81 } 82 } 83 const formData = new FormData(); 84 formData.append("data", JSON.stringify(requestData)); 85 for (const [key, value] of Object.entries(files)) { 86 const length = value.length; 87 for (let i = 0; i < length; ++i) { 88 formData.append(key, value[i]); 89 } 90 } 91 fetchOptions.body = formData; 92 } 93 const paramsString = params.toString(); 94 return { 95 url: `${url}${paramsString.length > 0 ? `?${paramsString}` : ""}`, 96 fetchOptions 97 }; 98 } 99 willDataFitInUrl(propsJson, updatedJson, params, childrenJson, propsFromParentJson) { 100 const urlEncodedJsonData = new URLSearchParams( 101 propsJson + updatedJson + childrenJson + propsFromParentJson 102 ).toString(); 103 return (urlEncodedJsonData + params.toString()).length < 1500; 104 } 105}; 106
107// src/Backend/Backend.ts 108var Backend_default = class { 109 constructor(url, method = "post") { 110 this.requestBuilder = new RequestBuilder_default(url, method); 111 } 112 makeRequest(props, actions, updated, children, updatedPropsFromParent, files) { 113 const { url, fetchOptions } = this.requestBuilder.buildRequest( 114 props, 115 actions, 116 updated, 117 children, 118 updatedPropsFromParent, 119 files 120 ); 121 return new BackendRequest_default( 122 fetch(url, fetchOptions), 123 actions.map((backendAction) => backendAction.name), 124 Object.keys(updated) 125 ); 126 } 127}; 128
129// src/Backend/BackendResponse.ts 130var BackendResponse_default = class { 131 constructor(response) { 132 this.response = response; 133 } 134 async getBody() { 135 if (!this.body) { 136 this.body = await this.response.text(); 137 } 138 return this.body; 139 } 140 getLiveUrl() { 141 if (void 0 === this.liveUrl) { 142 this.liveUrl = this.response.headers.get("X-Live-Url"); 143 } 144 return this.liveUrl; 145 } 146}; 147
148// src/Util/getElementAsTagText.ts 149function getElementAsTagText(element) { 150 return element.innerHTML ? element.outerHTML.slice(0, element.outerHTML.indexOf(element.innerHTML)) : element.outerHTML; 151} 152
153// src/ComponentRegistry.ts 154var componentMapByElement = /* @__PURE__ */ new WeakMap(); 155var componentMapByComponent = /* @__PURE__ */ new Map(); 156var registerComponent = (component) => { 157 componentMapByElement.set(component.element, component); 158 componentMapByComponent.set(component, component.name); 159}; 160var unregisterComponent = (component) => { 161 componentMapByElement.delete(component.element); 162 componentMapByComponent.delete(component); 163}; 164var getComponent = (element) => new Promise((resolve, reject) => { 165 let count = 0; 166 const maxCount = 10; 167 const interval = setInterval(() => { 168 const component = componentMapByElement.get(element); 169 if (component) { 170 clearInterval(interval); 171 resolve(component); 172 } 173 count++; 174 if (count > maxCount) { 175 clearInterval(interval); 176 reject(new Error(`Component not found for element ${getElementAsTagText(element)}`)); 177 } 178 }, 5); 179}); 180var findComponents = (currentComponent, onlyParents, onlyMatchName) => { 181 const components = []; 182 componentMapByComponent.forEach((componentName, component) => { 183 if (onlyParents && (currentComponent === component || !component.element.contains(currentComponent.element))) { 184 return; 185 } 186 if (onlyMatchName && componentName !== onlyMatchName) { 187 return; 188 } 189 components.push(component); 190 }); 191 return components; 192}; 193var findChildren = (currentComponent) => { 194 const children = []; 195 componentMapByComponent.forEach((componentName, component) => { 196 if (currentComponent === component) { 197 return; 198 } 199 if (!currentComponent.element.contains(component.element)) { 200 return; 201 } 202 let foundChildComponent = false; 203 componentMapByComponent.forEach((childComponentName, childComponent) => { 204 if (foundChildComponent) { 205 return; 206 } 207 if (childComponent === component) { 208 return; 209 } 210 if (childComponent.element.contains(component.element)) { 211 foundChildComponent = true; 212 } 213 }); 214 children.push(component); 215 }); 216 return children; 217}; 218var findParent = (currentComponent) => { 219 let parentElement = currentComponent.element.parentElement; 220 while (parentElement) { 221 const component = componentMapByElement.get(parentElement); 222 if (component) { 223 return component; 224 } 225 parentElement = parentElement.parentElement; 226 } 227 return null; 228}; 229
230// src/Directive/directives_parser.ts 231function parseDirectives(content) { 232 const directives = []; 233 if (!content) { 234 return directives; 235 } 236 let currentActionName = ""; 237 let currentArgumentValue = ""; 238 let currentArguments = []; 239 let currentModifiers = []; 240 let state = "action"; 241 const getLastActionName = () => { 242 if (currentActionName) { 243 return currentActionName; 244 } 245 if (directives.length === 0) { 246 throw new Error("Could not find any directives"); 247 } 248 return directives[directives.length - 1].action; 249 }; 250 const pushInstruction = () => { 251 directives.push({ 252 action: currentActionName, 253 args: currentArguments, 254 modifiers: currentModifiers, 255 getString: () => { 256 return content; 257 } 258 }); 259 currentActionName = ""; 260 currentArgumentValue = ""; 261 currentArguments = []; 262 currentModifiers = []; 263 state = "action"; 264 }; 265 const pushArgument = () => { 266 currentArguments.push(currentArgumentValue.trim()); 267 currentArgumentValue = ""; 268 }; 269 const pushModifier = () => { 270 if (currentArguments.length > 1) { 271 throw new Error(`The modifier "${currentActionName}()" does not support multiple arguments.`); 272 } 273 currentModifiers.push({ 274 name: currentActionName, 275 value: currentArguments.length > 0 ? currentArguments[0] : null 276 }); 277 currentActionName = ""; 278 currentArguments = []; 279 state = "action"; 280 }; 281 for (let i = 0; i < content.length; i++) { 282 const char = content[i]; 283 switch (state) { 284 case "action": 285 if (char === "(") { 286 state = "arguments"; 287 break; 288 } 289 if (char === " ") { 290 if (currentActionName) { 291 pushInstruction(); 292 } 293 break; 294 } 295 if (char === "|") { 296 pushModifier(); 297 break; 298 } 299 currentActionName += char; 300 break; 301 case "arguments": 302 if (char === ")") { 303 pushArgument(); 304 state = "after_arguments"; 305 break; 306 } 307 if (char === ",") { 308 pushArgument(); 309 break; 310 } 311 currentArgumentValue += char; 312 break; 313 case "after_arguments": 314 if (char === "|") { 315 pushModifier(); 316 break; 317 } 318 if (char !== " ") { 319 throw new Error(`Missing space after ${getLastActionName()}()`); 320 } 321 pushInstruction(); 322 break; 323 } 324 } 325 switch (state) { 326 case "action": 327 case "after_arguments": 328 if (currentActionName) { 329 pushInstruction(); 330 } 331 break; 332 default: 333 throw new Error(`Did you forget to add a closing ")" after "${currentActionName}"?`); 334 } 335 return directives; 336} 337
338// src/string_utils.ts 339function combineSpacedArray(parts) { 340 const finalParts = []; 341 parts.forEach((part) => { 342 finalParts.push(...trimAll(part).split(" ")); 343 }); 344 return finalParts; 345} 346function trimAll(str) { 347 return str.replace(/[\s]+/g, " ").trim(); 348} 349function normalizeModelName(model) { 350 return model.replace(/\[]$/, "").split("[").map((s) => s.replace("]", "")).join("."); 351} 352
353// src/dom_utils.ts 354function getValueFromElement(element, valueStore) { 355 if (element instanceof HTMLInputElement) { 356 if (element.type === "checkbox") { 357 const modelNameData = getModelDirectiveFromElement(element, false); 358 if (modelNameData !== null) { 359 const modelValue = valueStore.get(modelNameData.action); 360 if (Array.isArray(modelValue)) { 361 return getMultipleCheckboxValue(element, modelValue); 362 } 363 if (Object(modelValue) === modelValue) { 364 return getMultipleCheckboxValue(element, Object.values(modelValue)); 365 } 366 } 367 if (element.hasAttribute("value")) { 368 return element.checked ? element.getAttribute("value") : null; 369 } 370 return element.checked; 371 } 372 return inputValue(element); 373 } 374 if (element instanceof HTMLSelectElement) { 375 if (element.multiple) { 376 return Array.from(element.selectedOptions).map((el) => el.value); 377 } 378 return element.value; 379 } 380 if (element.hasAttribute("data-value")) { 381 return element.dataset.value; 382 } 383 if ("value" in element) { 384 return element.value; 385 } 386 if (element.hasAttribute("value")) { 387 return element.getAttribute("value"); 388 } 389 return null; 390} 391function setValueOnElement(element, value) { 392 if (element instanceof HTMLInputElement) { 393 if (element.type === "file") { 394 return; 395 } 396 if (element.type === "radio") { 397 element.checked = element.value == value; 398 return; 399 } 400 if (element.type === "checkbox") { 401 if (Array.isArray(value)) { 402 element.checked = value.some((val) => val == element.value); 403 } else if (element.hasAttribute("value")) { 404 element.checked = element.value == value; 405 } else { 406 element.checked = value; 407 } 408 return; 409 } 410 } 411 if (element instanceof HTMLSelectElement) { 412 const arrayWrappedValue = [].concat(value).map((value2) => { 413 return `${value2}`; 414 }); 415 Array.from(element.options).forEach((option) => { 416 option.selected = arrayWrappedValue.includes(option.value); 417 }); 418 return; 419 } 420 value = value === void 0 ? "" : value; 421 element.value = value; 422} 423function getAllModelDirectiveFromElements(element) { 424 if (!element.dataset.model) { 425 return []; 426 } 427 const directives = parseDirectives(element.dataset.model); 428 directives.forEach((directive) => { 429 if (directive.args.length > 0) { 430 throw new Error( 431 `The data-model="${element.dataset.model}" format is invalid: it does not support passing arguments to the model.` 432 ); 433 } 434 directive.action = normalizeModelName(directive.action); 435 }); 436 return directives; 437} 438function getModelDirectiveFromElement(element, throwOnMissing = true) { 439 const dataModelDirectives = getAllModelDirectiveFromElements(element); 440 if (dataModelDirectives.length > 0) { 441 return dataModelDirectives[0]; 442 } 443 if (element.getAttribute("name")) { 444 const formElement = element.closest("form"); 445 if (formElement && "model" in formElement.dataset) { 446 const directives = parseDirectives(formElement.dataset.model || "*"); 447 const directive = directives[0]; 448 if (directive.args.length > 0) { 449 throw new Error( 450 `The data-model="${formElement.dataset.model}" format is invalid: it does not support passing arguments to the model.` 451 ); 452 } 453 directive.action = normalizeModelName(element.getAttribute("name")); 454 return directive; 455 } 456 } 457 if (!throwOnMissing) { 458 return null; 459 } 460 throw new Error( 461 `Cannot determine the model name for "${getElementAsTagText( 462 element 463 )}": the element must either have a "data-model" (or "name" attribute living inside a <form data-model="*">).` 464 ); 465} 466function elementBelongsToThisComponent(element, component) { 467 if (component.element === element) { 468 return true; 469 } 470 if (!component.element.contains(element)) { 471 return false; 472 } 473 const closestLiveComponent = element.closest('[data-controller~="live"]'); 474 return closestLiveComponent === component.element; 475} 476function cloneHTMLElement(element) { 477 const newElement = element.cloneNode(true); 478 if (!(newElement instanceof HTMLElement)) { 479 throw new Error("Could not clone element"); 480 } 481 return newElement; 482} 483function htmlToElement(html) { 484 const template = document.createElement("template"); 485 html = html.trim(); 486 template.innerHTML = html; 487 if (template.content.childElementCount > 1) { 488 throw new Error( 489 `Component HTML contains ${template.content.childElementCount} elements, but only 1 root element is allowed.` 490 ); 491 } 492 const child = template.content.firstElementChild; 493 if (!child) { 494 throw new Error("Child not found"); 495 } 496 if (!(child instanceof HTMLElement)) { 497 throw new Error(`Created element is not an HTMLElement: ${html.trim()}`); 498 } 499 return child; 500} 501var getMultipleCheckboxValue = (element, currentValues) => { 502 const finalValues = [...currentValues]; 503 const value = inputValue(element); 504 const index = currentValues.indexOf(value); 505 if (element.checked) { 506 if (index === -1) { 507 finalValues.push(value); 508 } 509 return finalValues; 510 } 511 if (index > -1) { 512 finalValues.splice(index, 1); 513 } 514 return finalValues; 515}; 516var inputValue = (element) => element.dataset.value ? element.dataset.value : element.value; 517function isTextualInputElement(el) { 518 return el instanceof HTMLInputElement && ["text", "email", "password", "search", "tel", "url"].includes(el.type); 519} 520function isTextareaElement(el) { 521 return el instanceof HTMLTextAreaElement; 522} 523function isNumericalInputElement(element) { 524 return element instanceof HTMLInputElement && ["number", "range"].includes(element.type); 525} 526
527// src/HookManager.ts 528var HookManager_default = class { 529 constructor() { 530 this.hooks = /* @__PURE__ */ new Map(); 531 } 532 register(hookName, callback) { 533 const hooks = this.hooks.get(hookName) || []; 534 hooks.push(callback); 535 this.hooks.set(hookName, hooks); 536 } 537 unregister(hookName, callback) { 538 const hooks = this.hooks.get(hookName) || []; 539 const index = hooks.indexOf(callback); 540 if (index === -1) { 541 return; 542 } 543 hooks.splice(index, 1); 544 this.hooks.set(hookName, hooks); 545 } 546 triggerHook(hookName, ...args) { 547 const hooks = this.hooks.get(hookName) || []; 548 hooks.forEach((callback) => callback(...args)); 549 } 550}; 551
vendor: 19,227 bytes, lines 552-1097
552// ../../../node_modules/.pnpm/[email protected]/node_modules/idiomorph/dist/idiomorph.esm.js 553var Idiomorph = function() { 554 "use strict"; 555 let EMPTY_SET = /* @__PURE__ */ new Set(); 556 let defaults = { 557 morphStyle: "outerHTML", 558 callbacks: { 559 beforeNodeAdded: noOp, 560 afterNodeAdded: noOp, 561 beforeNodeMorphed: noOp, 562 afterNodeMorphed: noOp, 563 beforeNodeRemoved: noOp, 564 afterNodeRemoved: noOp, 565 beforeAttributeUpdated: noOp 566 }, 567 head: { 568 style: "merge", 569 shouldPreserve: function(elt) { 570 return elt.getAttribute("im-preserve") === "true"; 571 }, 572 shouldReAppend: function(elt) { 573 return elt.getAttribute("im-re-append") === "true"; 574 }, 575 shouldRemove: noOp, 576 afterHeadMorphed: noOp 577 } 578 }; 579 function morph(oldNode, newContent, config = {}) { 580 if (oldNode instanceof Document) { 581 oldNode = oldNode.documentElement; 582 } 583 if (typeof newContent === "string") { 584 newContent = parseContent(newContent); 585 } 586 let normalizedContent = normalizeContent(newContent); 587 let ctx = createMorphContext(oldNode, normalizedContent, config); 588 return morphNormalizedContent(oldNode, normalizedContent, ctx); 589 } 590 function morphNormalizedContent(oldNode, normalizedNewContent, ctx) { 591 if (ctx.head.block) { 592 let oldHead = oldNode.querySelector("head"); 593 let newHead = normalizedNewContent.querySelector("head"); 594 if (oldHead && newHead) { 595 let promises = handleHeadElement(newHead, oldHead, ctx); 596 Promise.all(promises).then(function() { 597 morphNormalizedContent(oldNode, normalizedNewContent, Object.assign(ctx, { 598 head: { 599 block: false, 600 ignore: true 601 } 602 })); 603 }); 604 return; 605 } 606 } 607 if (ctx.morphStyle === "innerHTML") { 608 morphChildren(normalizedNewContent, oldNode, ctx); 609 return oldNode.children; 610 } else if (ctx.morphStyle === "outerHTML" || ctx.morphStyle == null) { 611 let bestMatch = findBestNodeMatch(normalizedNewContent, oldNode, ctx); 612 let previousSibling = bestMatch?.previousSibling; 613 let nextSibling = bestMatch?.nextSibling; 614 let morphedNode = morphOldNodeTo(oldNode, bestMatch, ctx); 615 if (bestMatch) { 616 return insertSiblings(previousSibling, morphedNode, nextSibling); 617 } else { 618 return []; 619 } 620 } else { 621 throw "Do not understand how to morph style " + ctx.morphStyle; 622 } 623 } 624 function ignoreValueOfActiveElement(possibleActiveElement, ctx) { 625 return ctx.ignoreActiveValue && possibleActiveElement === document.activeElement; 626 } 627 function morphOldNodeTo(oldNode, newContent, ctx) { 628 if (ctx.ignoreActive && oldNode === document.activeElement) { 629 } else if (newContent == null) { 630 if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode; 631 oldNode.remove(); 632 ctx.callbacks.afterNodeRemoved(oldNode); 633 return null; 634 } else if (!isSoftMatch(oldNode, newContent)) { 635 if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode; 636 if (ctx.callbacks.beforeNodeAdded(newContent) === false) return oldNode; 637 oldNode.parentElement.replaceChild(newContent, oldNode); 638 ctx.callbacks.afterNodeAdded(newContent); 639 ctx.callbacks.afterNodeRemoved(oldNode); 640 return newContent; 641 } else { 642 if (ctx.callbacks.beforeNodeMorphed(oldNode, newContent) === false) return oldNode; 643 if (oldNode instanceof HTMLHeadElement && ctx.head.ignore) { 644 } else if (oldNode instanceof HTMLHeadElement && ctx.head.style !== "morph") { 645 handleHeadElement(newContent, oldNode, ctx); 646 } else { 647 syncNodeFrom(newContent, oldNode, ctx); 648 if (!ignoreValueOfActiveElement(oldNode, ctx)) { 649 morphChildren(newContent, oldNode, ctx); 650 } 651 } 652 ctx.callbacks.afterNodeMorphed(oldNode, newContent); 653 return oldNode; 654 } 655 } 656 function morphChildren(newParent, oldParent, ctx) { 657 let nextNewChild = newParent.firstChild; 658 let insertionPoint = oldParent.firstChild; 659 let newChild; 660 while (nextNewChild) { 661 newChild = nextNewChild; 662 nextNewChild = newChild.nextSibling; 663 if (insertionPoint == null) { 664 if (ctx.callbacks.beforeNodeAdded(newChild) === false) return; 665 oldParent.appendChild(newChild); 666 ctx.callbacks.afterNodeAdded(newChild); 667 removeIdsFromConsideration(ctx, newChild); 668 continue; 669 } 670 if (isIdSetMatch(newChild, insertionPoint, ctx)) { 671 morphOldNodeTo(insertionPoint, newChild, ctx); 672 insertionPoint = insertionPoint.nextSibling; 673 removeIdsFromConsideration(ctx, newChild); 674 continue; 675 } 676 let idSetMatch = findIdSetMatch(newParent, oldParent, newChild, insertionPoint, ctx); 677 if (idSetMatch) { 678 insertionPoint = removeNodesBetween(insertionPoint, idSetMatch, ctx); 679 morphOldNodeTo(idSetMatch, newChild, ctx); 680 removeIdsFromConsideration(ctx, newChild); 681 continue; 682 } 683 let softMatch = findSoftMatch(newParent, oldParent, newChild, insertionPoint, ctx); 684 if (softMatch) { 685 insertionPoint = removeNodesBetween(insertionPoint, softMatch, ctx); 686 morphOldNodeTo(softMatch, newChild, ctx); 687 removeIdsFromConsideration(ctx, newChild); 688 continue; 689 } 690 if (ctx.callbacks.beforeNodeAdded(newChild) === false) return; 691 oldParent.insertBefore(newChild, insertionPoint); 692 ctx.callbacks.afterNodeAdded(newChild); 693 removeIdsFromConsideration(ctx, newChild); 694 } 695 while (insertionPoint !== null) { 696 let tempNode = insertionPoint; 697 insertionPoint = insertionPoint.nextSibling; 698 removeNode(tempNode, ctx); 699 } 700 } 701 function ignoreAttribute(attr, to, updateType, ctx) { 702 if (attr === "value" && ctx.ignoreActiveValue && to === document.activeElement) { 703 return true; 704 } 705 return ctx.callbacks.beforeAttributeUpdated(attr, to, updateType) === false; 706 } 707 function syncNodeFrom(from, to, ctx) { 708 let type = from.nodeType; 709 if (type === 1) { 710 const fromAttributes = from.attributes; 711 const toAttributes = to.attributes; 712 for (const fromAttribute of fromAttributes) { 713 if (ignoreAttribute(fromAttribute.name, to, "update", ctx)) { 714 continue; 715 } 716 if (to.getAttribute(fromAttribute.name) !== fromAttribute.value) { 717 to.setAttribute(fromAttribute.name, fromAttribute.value); 718 } 719 } 720 for (let i = toAttributes.length - 1; 0 <= i; i--) { 721 const toAttribute = toAttributes[i]; 722 if (ignoreAttribute(toAttribute.name, to, "remove", ctx)) { 723 continue; 724 } 725 if (!from.hasAttribute(toAttribute.name)) { 726 to.removeAttribute(toAttribute.name); 727 } 728 } 729 } 730 if (type === 8 || type === 3) { 731 if (to.nodeValue !== from.nodeValue) { 732 to.nodeValue = from.nodeValue; 733 } 734 } 735 if (!ignoreValueOfActiveElement(to, ctx)) { 736 syncInputValue(from, to, ctx); 737 } 738 } 739 function syncBooleanAttribute(from, to, attributeName, ctx) { 740 if (from[attributeName] !== to[attributeName]) { 741 let ignoreUpdate = ignoreAttribute(attributeName, to, "update", ctx); 742 if (!ignoreUpdate) { 743 to[attributeName] = from[attributeName]; 744 } 745 if (from[attributeName]) { 746 if (!ignoreUpdate) { 747 to.setAttribute(attributeName, from[attributeName]); 748 } 749 } else { 750 if (!ignoreAttribute(attributeName, to, "remove", ctx)) { 751 to.removeAttribute(attributeName); 752 } 753 } 754 } 755 } 756 function syncInputValue(from, to, ctx) { 757 if (from instanceof HTMLInputElement && to instanceof HTMLInputElement && from.type !== "file") { 758 let fromValue = from.value; 759 let toValue = to.value; 760 syncBooleanAttribute(from, to, "checked", ctx); 761 syncBooleanAttribute(from, to, "disabled", ctx); 762 if (!from.hasAttribute("value")) { 763 if (!ignoreAttribute("value", to, "remove", ctx)) { 764 to.value = ""; 765 to.removeAttribute("value"); 766 } 767 } else if (fromValue !== toValue) { 768 if (!ignoreAttribute("value", to, "update", ctx)) { 769 to.setAttribute("value", fromValue); 770 to.value = fromValue; 771 } 772 } 773 } else if (from instanceof HTMLOptionElement) { 774 syncBooleanAttribute(from, to, "selected", ctx); 775 } else if (from instanceof HTMLTextAreaElement && to instanceof HTMLTextAreaElement) { 776 let fromValue = from.value; 777 let toValue = to.value; 778 if (ignoreAttribute("value", to, "update", ctx)) { 779 return; 780 } 781 if (fromValue !== toValue) { 782 to.value = fromValue; 783 } 784 if (to.firstChild && to.firstChild.nodeValue !== fromValue) { 785 to.firstChild.nodeValue = fromValue; 786 } 787 } 788 } 789 function handleHeadElement(newHeadTag, currentHead, ctx) { 790 let added = []; 791 let removed = []; 792 let preserved = []; 793 let nodesToAppend = []; 794 let headMergeStyle = ctx.head.style; 795 let srcToNewHeadNodes = /* @__PURE__ */ new Map(); 796 for (const newHeadChild of newHeadTag.children) { 797 srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild); 798 } 799 for (const currentHeadElt of currentHead.children) { 800 let inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML); 801 let isReAppended = ctx.head.shouldReAppend(currentHeadElt); 802 let isPreserved = ctx.head.shouldPreserve(currentHeadElt); 803 if (inNewContent || isPreserved) { 804 if (isReAppended) { 805 removed.push(currentHeadElt); 806 } else { 807 srcToNewHeadNodes.delete(currentHeadElt.outerHTML); 808 preserved.push(currentHeadElt); 809 } 810 } else { 811 if (headMergeStyle === "append") { 812 if (isReAppended) { 813 removed.push(currentHeadElt); 814 nodesToAppend.push(currentHeadElt); 815 } 816 } else { 817 if (ctx.head.shouldRemove(currentHeadElt) !== false) { 818 removed.push(currentHeadElt); 819 } 820 } 821 } 822 } 823 nodesToAppend.push(...srcToNewHeadNodes.values()); 824 log("to append: ", nodesToAppend); 825 let promises = []; 826 for (const newNode of nodesToAppend) { 827 log("adding: ", newNode); 828 let newElt = document.createRange().createContextualFragment(newNode.outerHTML).firstChild; 829 log(newElt); 830 if (ctx.callbacks.beforeNodeAdded(newElt) !== false) { 831 if (newElt.href || newElt.src) { 832 let resolve = null; 833 let promise = new Promise(function(_resolve) { 834 resolve = _resolve; 835 }); 836 newElt.addEventListener("load", function() { 837 resolve(); 838 }); 839 promises.push(promise); 840 } 841 currentHead.appendChild(newElt); 842 ctx.callbacks.afterNodeAdded(newElt); 843 added.push(newElt); 844 } 845 } 846 for (const removedElement of removed) { 847 if (ctx.callbacks.beforeNodeRemoved(removedElement) !== false) { 848 currentHead.removeChild(removedElement); 849 ctx.callbacks.afterNodeRemoved(removedElement); 850 } 851 } 852 ctx.head.afterHeadMorphed(currentHead, { added, kept: preserved, removed }); 853 return promises; 854 } 855 function log() { 856 } 857 function noOp() { 858 } 859 function mergeDefaults(config) { 860 let finalConfig = {}; 861 Object.assign(finalConfig, defaults); 862 Object.assign(finalConfig, config); 863 finalConfig.callbacks = {}; 864 Object.assign(finalConfig.callbacks, defaults.callbacks); 865 Object.assign(finalConfig.callbacks, config.callbacks); 866 finalConfig.head = {}; 867 Object.assign(finalConfig.head, defaults.head); 868 Object.assign(finalConfig.head, config.head); 869 return finalConfig; 870 } 871 function createMorphContext(oldNode, newContent, config) { 872 config = mergeDefaults(config); 873 return { 874 target: oldNode, 875 newContent, 876 config, 877 morphStyle: config.morphStyle, 878 ignoreActive: config.ignoreActive, 879 ignoreActiveValue: config.ignoreActiveValue, 880 idMap: createIdMap(oldNode, newContent), 881 deadIds: /* @__PURE__ */ new Set(), 882 callbacks: config.callbacks, 883 head: config.head 884 }; 885 } 886 function isIdSetMatch(node1, node2, ctx) { 887 if (node1 == null || node2 == null) { 888 return false; 889 } 890 if (node1.nodeType === node2.nodeType && node1.tagName === node2.tagName) { 891 if (node1.id !== "" && node1.id === node2.id) { 892 return true; 893 } else { 894 return getIdIntersectionCount(ctx, node1, node2) > 0; 895 } 896 } 897 return false; 898 } 899 function isSoftMatch(node1, node2) { 900 if (node1 == null || node2 == null) { 901 return false; 902 } 903 return node1.nodeType === node2.nodeType && node1.tagName === node2.tagName; 904 } 905 function removeNodesBetween(startInclusive, endExclusive, ctx) { 906 while (startInclusive !== endExclusive) { 907 let tempNode = startInclusive; 908 startInclusive = startInclusive.nextSibling; 909 removeNode(tempNode, ctx); 910 } 911 removeIdsFromConsideration(ctx, endExclusive); 912 return endExclusive.nextSibling; 913 } 914 function findIdSetMatch(newContent, oldParent, newChild, insertionPoint, ctx) { 915 let newChildPotentialIdCount = getIdIntersectionCount(ctx, newChild, oldParent); 916 let potentialMatch = null; 917 if (newChildPotentialIdCount > 0) { 918 let potentialMatch2 = insertionPoint; 919 let otherMatchCount = 0; 920 while (potentialMatch2 != null) { 921 if (isIdSetMatch(newChild, potentialMatch2, ctx)) { 922 return potentialMatch2; 923 } 924 otherMatchCount += getIdIntersectionCount(ctx, potentialMatch2, newContent); 925 if (otherMatchCount > newChildPotentialIdCount) { 926 return null; 927 } 928 potentialMatch2 = potentialMatch2.nextSibling; 929 } 930 } 931 return potentialMatch; 932 } 933 function findSoftMatch(newContent, oldParent, newChild, insertionPoint, ctx) { 934 let potentialSoftMatch = insertionPoint; 935 let nextSibling = newChild.nextSibling; 936 let siblingSoftMatchCount = 0; 937 while (potentialSoftMatch != null) { 938 if (getIdIntersectionCount(ctx, potentialSoftMatch, newContent) > 0) { 939 return null; 940 } 941 if (isSoftMatch(newChild, potentialSoftMatch)) { 942 return potentialSoftMatch; 943 } 944 if (isSoftMatch(nextSibling, potentialSoftMatch)) { 945 siblingSoftMatchCount++; 946 nextSibling = nextSibling.nextSibling; 947 if (siblingSoftMatchCount >= 2) { 948 return null; 949 } 950 } 951 potentialSoftMatch = potentialSoftMatch.nextSibling; 952 } 953 return potentialSoftMatch; 954 } 955 function parseContent(newContent) { 956 let parser = new DOMParser(); 957 let contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, ""); 958 if (contentWithSvgsRemoved.match(/<\/html>/) || contentWithSvgsRemoved.match(/<\/head>/) || contentWithSvgsRemoved.match(/<\/body>/)) { 959 let content = parser.parseFromString(newContent, "text/html"); 960 if (contentWithSvgsRemoved.match(/<\/html>/)) { 961 content.generatedByIdiomorph = true; 962 return content; 963 } else { 964 let htmlElement = content.firstChild; 965 if (htmlElement) { 966 htmlElement.generatedByIdiomorph = true; 967 return htmlElement; 968 } else { 969 return null; 970 } 971 } 972 } else { 973 let responseDoc = parser.parseFromString("<body><template>" + newContent + "</template></body>", "text/html"); 974 let content = responseDoc.body.querySelector("template").content; 975 content.generatedByIdiomorph = true; 976 return content; 977 } 978 } 979 function normalizeContent(newContent) { 980 if (newContent == null) { 981 const dummyParent = document.createElement("div"); 982 return dummyParent; 983 } else if (newContent.generatedByIdiomorph) { 984 return newContent; 985 } else if (newContent instanceof Node) { 986 const dummyParent = document.createElement("div"); 987 dummyParent.append(newContent); 988 return dummyParent; 989 } else { 990 const dummyParent = document.createElement("div"); 991 for (const elt of [...newContent]) { 992 dummyParent.append(elt); 993 } 994 return dummyParent; 995 } 996 } 997 function insertSiblings(previousSibling, morphedNode, nextSibling) { 998 let stack = []; 999 let added = []; 1000 while (previousSibling != null) { 1001 stack.push(previousSibling); 1002 previousSibling = previousSibling.previousSibling; 1003 } 1004 while (stack.length > 0) { 1005 let node = stack.pop(); 1006 added.push(node); 1007 morphedNode.parentElement.insertBefore(node, morphedNode); 1008 } 1009 added.push(morphedNode); 1010 while (nextSibling != null) { 1011 stack.push(nextSibling); 1012 added.push(nextSibling); 1013 nextSibling = nextSibling.nextSibling; 1014 } 1015 while (stack.length > 0) { 1016 morphedNode.parentElement.insertBefore(stack.pop(), morphedNode.nextSibling); 1017 } 1018 return added; 1019 } 1020 function findBestNodeMatch(newContent, oldNode, ctx) { 1021 let currentElement; 1022 currentElement = newContent.firstChild; 1023 let bestElement = currentElement; 1024 let score = 0; 1025 while (currentElement) { 1026 let newScore = scoreElement(currentElement, oldNode, ctx); 1027 if (newScore > score) { 1028 bestElement = currentElement; 1029 score = newScore; 1030 } 1031 currentElement = currentElement.nextSibling; 1032 } 1033 return bestElement; 1034 } 1035 function scoreElement(node1, node2, ctx) { 1036 if (isSoftMatch(node1, node2)) { 1037 return 0.5 + getIdIntersectionCount(ctx, node1, node2); 1038 } 1039 return 0; 1040 } 1041 function removeNode(tempNode, ctx) { 1042 removeIdsFromConsideration(ctx, tempNode); 1043 if (ctx.callbacks.beforeNodeRemoved(tempNode) === false) return; 1044 tempNode.remove(); 1045 ctx.callbacks.afterNodeRemoved(tempNode); 1046 } 1047 function isIdInConsideration(ctx, id) { 1048 return !ctx.deadIds.has(id); 1049 } 1050 function idIsWithinNode(ctx, id, targetNode) { 1051 let idSet = ctx.idMap.get(targetNode) || EMPTY_SET; 1052 return idSet.has(id); 1053 } 1054 function removeIdsFromConsideration(ctx, node) { 1055 let idSet = ctx.idMap.get(node) || EMPTY_SET; 1056 for (const id of idSet) { 1057 ctx.deadIds.add(id); 1058 } 1059 } 1060 function getIdIntersectionCount(ctx, node1, node2) { 1061 let sourceSet = ctx.idMap.get(node1) || EMPTY_SET; 1062 let matchCount = 0; 1063 for (const id of sourceSet) { 1064 if (isIdInConsideration(ctx, id) && idIsWithinNode(ctx, id, node2)) { 1065 ++matchCount; 1066 } 1067 } 1068 return matchCount; 1069 } 1070 function populateIdMapForNode(node, idMap) { 1071 let nodeParent = node.parentElement; 1072 let idElements = node.querySelectorAll("[id]"); 1073 for (const elt of idElements) { 1074 let current = elt; 1075 while (current !== nodeParent && current != null) { 1076 let idSet = idMap.get(current); 1077 if (idSet == null) { 1078 idSet = /* @__PURE__ */ new Set(); 1079 idMap.set(current, idSet); 1080 } 1081 idSet.add(elt.id); 1082 current = current.parentElement; 1083 } 1084 } 1085 } 1086 function createIdMap(oldContent, newContent) { 1087 let idMap = /* @__PURE__ */ new Map(); 1088 populateIdMapForNode(oldContent, idMap); 1089 populateIdMapForNode(newContent, idMap); 1090 return idMap; 1091 } 1092 return { 1093 morph, 1094 defaults 1095 }; 1096}(); 1097
1098// src/normalize_attributes_for_comparison.ts 1099function normalizeAttributesForComparison(element) { 1100 const isFileInput = element instanceof HTMLInputElement && element.type === "file"; 1101 if (!isFileInput) { 1102 if ("value" in element) { 1103 element.setAttribute("value", element.value); 1104 } else if (element.hasAttribute("value")) { 1105 element.setAttribute("value", ""); 1106 } 1107 } 1108 Array.from(element.children).forEach((child) => { 1109 normalizeAttributesForComparison(child); 1110 }); 1111} 1112
1113// src/morphdom.ts 1114var syncAttributes = (fromEl, toEl) => { 1115 for (let i = 0; i < fromEl.attributes.length; i++) { 1116 const attr = fromEl.attributes[i]; 1117 toEl.setAttribute(attr.name, attr.value); 1118 } 1119}; 1120function executeMorphdom(rootFromElement, rootToElement, modifiedFieldElements, getElementValue, externalMutationTracker) { 1121 const originalElementIdsToSwapAfter = []; 1122 const originalElementsToPreserve = /* @__PURE__ */ new Map(); 1123 const markElementAsNeedingPostMorphSwap = (id, replaceWithClone) => { 1124 const oldElement = originalElementsToPreserve.get(id); 1125 if (!(oldElement instanceof HTMLElement)) { 1126 throw new Error(`Original element with id ${id} not found`); 1127 } 1128 originalElementIdsToSwapAfter.push(id); 1129 if (!replaceWithClone) { 1130 return null; 1131 } 1132 const clonedOldElement = cloneHTMLElement(oldElement); 1133 oldElement.replaceWith(clonedOldElement); 1134 return clonedOldElement; 1135 }; 1136 rootToElement.querySelectorAll("[data-live-preserve]").forEach((newElement) => { 1137 const id = newElement.id; 1138 if (!id) { 1139 throw new Error("The data-live-preserve attribute requires an id attribute to be set on the element"); 1140 } 1141 const oldElement = rootFromElement.querySelector(`#${id}`); 1142 if (!(oldElement instanceof HTMLElement)) { 1143 throw new Error(`The element with id "${id}" was not found in the original HTML`); 1144 } 1145 newElement.removeAttribute("data-live-preserve"); 1146 originalElementsToPreserve.set(id, oldElement); 1147 syncAttributes(newElement, oldElement); 1148 }); 1149 Idiomorph.morph(rootFromElement, rootToElement, { 1150 callbacks: { 1151 beforeNodeMorphed: (fromEl, toEl) => { 1152 if (!(fromEl instanceof Element) || !(toEl instanceof Element)) { 1153 return true; 1154 } 1155 if (fromEl === rootFromElement) { 1156 return true; 1157 } 1158 if (fromEl.id && originalElementsToPreserve.has(fromEl.id)) { 1159 if (fromEl.id === toEl.id) { 1160 return false; 1161 } 1162 const clonedFromEl = markElementAsNeedingPostMorphSwap(fromEl.id, true); 1163 if (!clonedFromEl) { 1164 throw new Error("missing clone"); 1165 } 1166 Idiomorph.morph(clonedFromEl, toEl); 1167 return false; 1168 } 1169 if (fromEl instanceof HTMLElement && toEl instanceof HTMLElement) { 1170 if (typeof fromEl.__x !== "undefined") { 1171 if (!window.Alpine) { 1172 throw new Error( 1173 "Unable to access Alpine.js though the global window.Alpine variable. Please make sure Alpine.js is loaded before Symfony UX LiveComponent." 1174 ); 1175 } 1176 if (typeof window.Alpine.morph !== "function") { 1177 throw new Error( 1178 "Unable to access Alpine.js morph function. Please make sure the Alpine.js Morp
1178h plugin is installed and loaded, see https://alpinejs.dev/plugins/morph for more information." 1179 ); 1180 } 1181 window.Alpine.morph(fromEl.__x, toEl); 1182 } 1183 if (externalMutationTracker.wasElementAdded(fromEl)) { 1184 fromEl.insertAdjacentElement("afterend", toEl); 1185 return false; 1186 } 1187 if (modifiedFieldElements.includes(fromEl)) { 1188 setValueOnElement(toEl, getElementValue(fromEl)); 1189 } 1190 if (fromEl === document.activeElement && fromEl !== document.body && null !== getModelDirectiveFromElement(fromEl, false)) { 1191 setValueOnElement(toEl, getElementValue(fromEl)); 1192 } 1193 const elementChanges = externalMutationTracker.getChangedElement(fromEl); 1194 if (elementChanges) { 1195 elementChanges.applyToElement(toEl); 1196 } 1197 if (fromEl.nodeName.toUpperCase() !== "OPTION" && fromEl.isEqualNode(toEl)) { 1198 const normalizedFromEl = cloneHTMLElement(fromEl); 1199 normalizeAttributesForComparison(normalizedFromEl); 1200 const normalizedToEl = cloneHTMLElement(toEl); 1201 normalizeAttributesForComparison(normalizedToEl); 1202 if (normalizedFromEl.isEqualNode(normalizedToEl)) { 1203 return false; 1204 } 1205 } 1206 } 1207 if (fromEl.hasAttribute("data-skip-morph") || fromEl.id && fromEl.id !== toEl.id) { 1208 fromEl.innerHTML = toEl.innerHTML; 1209 return true; 1210 } 1211 if (fromEl.parentElement?.hasAttribute("data-skip-morph")) { 1212 return false; 1213 } 1214 return !fromEl.hasAttribute("data-live-ignore"); 1215 }, 1216 beforeNodeRemoved(node) { 1217 if (!(node instanceof HTMLElement)) { 1218 return true; 1219 } 1220 if (node.id && originalElementsToPreserve.has(node.id)) { 1221 markElementAsNeedingPostMorphSwap(node.id, false); 1222 return true; 1223 } 1224 if (externalMutationTracker.wasElementAdded(node)) { 1225 return false; 1226 } 1227 return !node.hasAttribute("data-live-ignore"); 1228 } 1229 } 1230 }); 1231 originalElementIdsToSwapAfter.forEach((id) => { 1232 const newElement = rootFromElement.querySelector(`#${id}`); 1233 const originalElement = originalElementsToPreserve.get(id); 1234 if (!(newElement instanceof HTMLElement) || !(originalElement instanceof HTMLElement)) { 1235 throw new Error("Missing elements."); 1236 } 1237 newElement.replaceWith(originalElement); 1238 }); 1239} 1240
1241// src/Rendering/ChangingItemsTracker.ts 1242var ChangingItemsTracker_default = class { 1243 constructor() { 1244 // e.g. a Map with key "color" & value { original: 'previousValue', new: 'newValue' }, 1245 this.changedItems = /* @__PURE__ */ new Map(); 1246 this.removedItems = /* @__PURE__ */ new Map(); 1247 } 1248 /** 1249 * A "null" previousValue means the item was NOT previously present. 1250 */ 1251 setItem(itemName, newValue, previousValue) { 1252 if (this.removedItems.has(itemName)) { 1253 const removedRecord = this.removedItems.get(itemName); 1254 this.removedItems.delete(itemName); 1255 if (removedRecord.original === newValue) { 1256 return; 1257 } 1258 } 1259 if (this.changedItems.has(itemName)) { 1260 const originalRecord = this.changedItems.get(itemName); 1261 if (originalRecord.original === newValue) { 1262 this.changedItems.delete(itemName); 1263 return; 1264 } 1265 this.changedItems.set(itemName, { original: originalRecord.original, new: newValue }); 1266 return; 1267 } 1268 this.changedItems.set(itemName, { original: previousValue, new: newValue }); 1269 } 1270 removeItem(itemName, currentValue) { 1271 let trueOriginalValue = currentValue; 1272 if (this.changedItems.has(itemName)) { 1273 const originalRecord = this.changedItems.get(itemName); 1274 trueOriginalValue = originalRecord.original; 1275 this.changedItems.delete(itemName); 1276 if (trueOriginalValue === null) { 1277 return; 1278 } 1279 } 1280 if (!this.removedItems.has(itemName)) { 1281 this.removedItems.set(itemName, { original: trueOriginalValue }); 1282 } 1283 } 1284 getChangedItems() { 1285 return Array.from(this.changedItems, ([name, { new: value }]) => ({ name, value })); 1286 } 1287 getRemovedItems() { 1288 return Array.from(this.removedItems.keys()); 1289 } 1290 isEmpty() { 1291 return this.changedItems.size === 0 && this.removedItems.size === 0; 1292 } 1293}; 1294
1295// src/Rendering/ElementChanges.ts 1296var ElementChanges = class { 1297 constructor() { 1298 this.addedClasses = /* @__PURE__ */ new Set(); 1299 this.removedClasses = /* @__PURE__ */ new Set(); 1300 this.styleChanges = new ChangingItemsTracker_default(); 1301 this.attributeChanges = new ChangingItemsTracker_default(); 1302 } 1303 addClass(className) { 1304 if (!this.removedClasses.delete(className)) { 1305 this.addedClasses.add(className); 1306 } 1307 } 1308 removeClass(className) { 1309 if (!this.addedClasses.delete(className)) { 1310 this.removedClasses.add(className); 1311 } 1312 } 1313 addStyle(styleName, newValue, originalValue) { 1314 this.styleChanges.setItem(styleName, newValue, originalValue); 1315 } 1316 removeStyle(styleName, originalValue) { 1317 this.styleChanges.removeItem(styleName, originalValue); 1318 } 1319 addAttribute(attributeName, newValue, originalValue) { 1320 this.attributeChanges.setItem(attributeName, newValue, originalValue); 1321 } 1322 removeAttribute(attributeName, originalValue) { 1323 this.attributeChanges.removeItem(attributeName, originalValue); 1324 } 1325 getAddedClasses() { 1326 return [...this.addedClasses]; 1327 } 1328 getRemovedClasses() { 1329 return [...this.removedClasses]; 1330 } 1331 getChangedStyles() { 1332 return this.styleChanges.getChangedItems(); 1333 } 1334 getRemovedStyles() { 1335 return this.styleChanges.getRemovedItems(); 1336 } 1337 getChangedAttributes() { 1338 return this.attributeChanges.getChangedItems(); 1339 } 1340 getRemovedAttributes() { 1341 return this.attributeChanges.getRemovedItems(); 1342 } 1343 applyToElement(element) { 1344 element.classList.add(...this.addedClasses); 1345 element.classList.remove(...this.removedClasses);
1346 this.styleChanges.getChangedItems().forEach((change) => { 1347 element.style.setProperty(change.name, change.value); 1348 return; 1349 }); 1350 this.styleChanges.getRemovedItems().forEach((styleName) => { 1351 element.style.removeProperty(styleName); 1352 }); 1353 this.attributeChanges.getChangedItems().forEach((change) => { 1354 element.setAttribute(change.name, change.value); 1355 }); 1356 this.attributeChanges.getRemovedItems().forEach((attributeName) => { 1357 element.removeAttribute(attributeName); 1358 }); 1359 } 1360 isEmpty() { 1361 return this.addedClasses.size === 0 && this.removedClasses.size === 0 && this.styleChanges.isEmpty() && this.attributeChanges.isEmpty(); 1362 } 1363}; 1364
1365// src/Rendering/ExternalMutationTracker.ts 1366var ExternalMutationTracker_default = class { 1367 constructor(element, shouldTrackChangeCallback) { 1368 this.changedElements = /* @__PURE__ */ new WeakMap(); 1369 /** For testing */ 1370 this.changedElementsCount = 0; 1371 this.addedElements = []; 1372 this.removedElements = []; 1373 this.isStarted = false; 1374 this.element = element; 1375 this.shouldTrackChangeCallback = shouldTrackChangeCallback; 1376 this.mutationObserver = new MutationObserver(this.onMutations.bind(this)); 1377 } 1378 start() { 1379 if (this.isStarted) { 1380 return; 1381 } 1382 this.mutationObserver.observe(this.element, { 1383 childList: true, 1384 subtree: true, 1385 attributes: true, 1386 attributeOldValue: true 1387 }); 1388 this.isStarted = true; 1389 } 1390 stop() { 1391 if (this.isStarted) { 1392 this.mutationObserver.disconnect(); 1393 this.isStarted = false; 1394 } 1395 } 1396 getChangedElement(element) { 1397 return this.changedElements.has(element) ? this.changedElements.get(element) : null; 1398 } 1399 getAddedElements() { 1400 return this.addedElements; 1401 } 1402 wasElementAdded(element) { 1403 return this.addedElements.includes(element); 1404 } 1405 /** 1406 * Forces any pending mutations to be handled immediately, then clears the queue. 1407 */ 1408 handlePendingChanges() { 1409 this.onMutations(this.mutationObserver.takeRecords()); 1410 } 1411 onMutations(mutations) { 1412 const handledAttributeMutations = /* @__PURE__ */ new WeakMap(); 1413 for (const mutation of mutations) { 1414 const element = mutation.target; 1415 if (!this.shouldTrackChangeCallback(element)) { 1416 continue; 1417 } 1418 if (this.isElementAddedByTranslation(element)) { 1419 continue; 1420 } 1421 let isChangeInAddedElement = false; 1422 for (const addedElement of this.addedElements) { 1423 if (addedElement.contains(element)) { 1424 isChangeInAddedElement = true; 1425 break; 1426 } 1427 } 1428 if (isChangeInAddedElement) { 1429 continue; 1430 } 1431 switch (mutation.type) { 1432 case "childList": 1433 this.handleChildListMutation(mutation); 1434 break; 1435 case "attributes": 1436 if (!handledAttributeMutations.has(element)) { 1437 handledAttributeMutations.set(element, []); 1438 } 1439 if (!handledAttributeMutations.get(element).includes(mutation.attributeName)) { 1440 this.handleAttributeMutation(mutation); 1441 handledAttributeMutations.set(element, [ 1442 ...handledAttributeMutations.get(element), 1443 mutation.attributeName 1444 ]); 1445 } 1446 break; 1447 } 1448 } 1449 } 1450 handleChildListMutation(mutation) { 1451 mutation.addedNodes.forEach((node) => { 1452 if (!(node instanceof Element)) { 1453 return; 1454 } 1455 if (this.removedElements.includes(node)) { 1456 this.removedElements.splice(this.removedElements.indexOf(node), 1); 1457 return; 1458 } 1459 if (this.isElementAddedByTranslation(node)) { 1460 return; 1461 } 1462 this.addedElements.push(node); 1463 }); 1464 mutation.removedNodes.forEach((node) => { 1465 if (!(node instanceof Element)) { 1466 return; 1467 } 1468 if (this.addedElements.includes(node)) { 1469 this.addedElements.splice(this.addedElements.indexOf(node), 1); 1470 return; 1471 } 1472 this.removedElements.push(node); 1473 }); 1474 } 1475 handleAttributeMutation(mutation) { 1476 const element = mutation.target; 1477 if (!this.changedElements.has(element)) { 1478 this.changedElements.set(element, new ElementChanges()); 1479 this.changedElementsCount++; 1480 } 1481 const changedElement = this.changedElements.get(element); 1482 switch (mutation.attributeName) { 1483 case "class": 1484 this.handleClassAttributeMutation(mutation, changedElement); 1485 break; 1486 case "style": 1487 this.handleStyleAttributeMutation(mutation, changedElement); 1488 break; 1489 default: 1490 this.handleGenericAttributeMutation(mutation, changedElement); 1491 } 1492 if (changedElement.isEmpty()) { 1493 this.changedElements.delete(element); 1494 this.changedElementsCount--; 1495 } 1496 } 1497 handleClassAttributeMutation(mutation, elementChanges) { 1498 const element = mutation.target; 1499 const previousValue = mutation.oldValue || ""; 1500 const previousValues = previousValue.match(/(\S+)/gu) || []; 1501 const newValues = [].slice.call(element.classList); 1502 const addedValues = newValues.filter((value) => !previousValues.includes(value)); 1503 const removedValues = previousValues.filter((value) => !newValues.includes(value));
1504 addedValues.forEach((value) => { 1505 elementChanges.addClass(value); 1506 }); 1507 removedValues.forEach((value) => { 1508 elementChanges.removeClass(value); 1509 }); 1510 } 1511 handleStyleAttributeMutation(mutation, elementChanges) { 1512 const element = mutation.target; 1513 const previousValue = mutation.oldValue || ""; 1514 const previousStyles = this.extractStyles(previousValue); 1515 const newValue = element.getAttribute("style") || ""; 1516 const newStyles = this.extractStyles(newValue); 1517 const addedOrChangedStyles = Object.keys(newStyles).filter( 1518 (key) => previousStyles[key] === void 0 || previousStyles[key] !== newStyles[key] 1519 ); 1520 const removedStyles = Object.keys(previousStyles).filter((key) => !newStyles[key]); 1521 addedOrChangedStyles.forEach((style) => { 1522 elementChanges.addStyle( 1523 style, 1524 newStyles[style], 1525 previousStyles[style] === void 0 ? null : previousStyles[style] 1526 ); 1527 }); 1528 removedStyles.forEach((style) => { 1529 elementChanges.removeStyle(style, previousStyles[style]); 1530 }); 1531 } 1532 handleGenericAttributeMutation(mutation, elementChanges) { 1533 const attributeName = mutation.attributeName; 1534 const element = mutation.target; 1535 let oldValue = mutation.oldValue; 1536 let newValue = element.getAttribute(attributeName); 1537 if (oldValue === attributeName) { 1538 oldValue = ""; 1539 } 1540 if (newValue === attributeName) { 1541 newValue = ""; 1542 } 1543 if (!element.hasAttribute(attributeName)) { 1544 if (oldValue === null) { 1545 return; 1546 } 1547 elementChanges.removeAttribute(attributeName, mutation.oldValue); 1548 return; 1549 } 1550 if (newValue === oldValue) { 1551 return; 1552 } 1553 elementChanges.addAttribute(attributeName, element.getAttribute(attributeName), mutation.oldValue); 1554 } 1555 extractStyles(styles) { 1556 const styleObject = {}; 1557 styles.split(";").forEach((style) => { 1558 const parts = style.split(":"); 1559 if (parts.length === 1) { 1560 return; 1561 } 1562 const property = parts[0].trim(); 1563 styleObject[property] = parts.slice(1).join(":").trim(); 1564 }); 1565 return styleObject; 1566 } 1567 /** 1568 * Helps avoid tracking changes by Chrome's translation feature. 1569 * 1570 * When Chrome translates, it mutates the dom in a way that triggers MutationObserver. 1571 * This includes adding new elements wrapped in a <font> tag. This causes live 1572 * components to incorrectly think that these new elements should persist through 1573 * re-renders, causing duplicate text. 1574 */ 1575 isElementAddedByTranslation(element) { 1576 return element.tagName === "FONT" && element.getAttribute("style") === "vertical-align: inherit;"; 1577 } 1578}; 1579
1580// src/Component/UnsyncedInputsTracker.ts 1581var UnsyncedInputsTracker_default = class { 1582 constructor(component, modelElementResolver) { 1583 this.elementEventListeners = [ 1584 { event: "input", callback: (event) => this.handleInputEvent(event) } 1585 ]; 1586 this.component = component; 1587 this.modelElementResolver = modelElementResolver; 1588 this.unsyncedInputs = new UnsyncedInputContainer(); 1589 } 1590 activate() { 1591 this.elementEventListeners.forEach(({ event, callback }) => { 1592 this.component.element.addEventListener(event, callback); 1593 }); 1594 } 1595 deactivate() { 1596 this.elementEventListeners.forEach(({ event, callback }) => { 1597 this.component.element.removeEventListener(event, callback); 1598 }); 1599 } 1600 markModelAsSynced(modelName) { 1601 this.unsyncedInputs.markModelAsSynced(modelName); 1602 } 1603 handleInputEvent(event) { 1604 const target = event.target; 1605 if (!target) { 1606 return; 1607 } 1608 this.updateModelFromElement(target); 1609 } 1610 updateModelFromElement(element) { 1611 if (!elementBelongsToThisComponent(element, this.component)) { 1612 return; 1613 } 1614 if (!(element instanceof HTMLElement)) { 1615 throw new Error("Could not update model for non HTMLElement"); 1616 } 1617 const modelName = this.modelElementResolver.getModelName(element); 1618 this.unsyncedInputs.add(element, modelName); 1619 } 1620 getUnsyncedInputs() { 1621 return this.unsyncedInputs.allUnsyncedInputs(); 1622 } 1623 getUnsyncedModels() { 1624 return Array.from(this.unsyncedInputs.getUnsyncedModelNames()); 1625 } 1626 resetUnsyncedFields() { 1627 this.unsyncedInputs.resetUnsyncedFields(); 1628 } 1629}; 1630var UnsyncedInputContainer = class { 1631 constructor() { 1632 this.unsyncedNonModelFields = []; 1633 this.unsyncedModelNames = []; 1634 this.unsyncedModelFields = /* @__PURE__ */ new Map(); 1635 } 1636 add(element, modelName = null) { 1637 if (modelName) { 1638 this.unsyncedModelFields.set(modelName, element); 1639 if (!this.unsyncedModelNames.includes(modelName)) { 1640 this.unsyncedModelNames.push(modelName); 1641 } 1642 return; 1643 } 1644 this.unsyncedNonModelFields.push(element); 1645 } 1646 /** 1647 * Mark all fields as synced, except for those not bound to a model or whose 1648 * values are still dirty. 1649 */ 1650 resetUnsyncedFields() { 1651 this.unsyncedModelFields.forEach((value, key) => { 1652 if (!this.unsyncedModelNames.includes(key)) { 1653 this.unsyncedModelFields.delete(key); 1654 } 1655 }); 1656 } 1657 allUnsyncedInputs() { 1658 return [...this.unsyncedNonModelFields, ...this.unsyncedModelFields.values()]; 1659 } 1660 markModelAsSynced(modelName) { 1661 const index = this.unsyncedModelNames.indexOf(modelName); 1662 if (index !== -1) { 1663 this.unsyncedModelNames.splice(index, 1); 1664 } 1665 } 1666 /** 1667 * Returns a list of models whose fields have been modified, but whose values 1668 * have not yet been set onto the data store. 1669 */ 1670 getUnsyncedModelNames() { 1671 return this.unsyncedModelNames; 1672 } 1673}; 1674
1675// src/data_manipulation_utils.ts 1676function getDeepData(data, propertyPath) { 1677 const { currentLevelData, finalKey } = parseDeepData(data, propertyPath); 1678 if (currentLevelData === void 0) { 1679 return void 0; 1680 } 1681 return currentLevelData[finalKey]; 1682} 1683var parseDeepData = (data, propertyPath) => { 1684 const finalData = JSON.parse(JSON.stringify(data)); 1685 let currentLevelData = finalData; 1686 const parts = propertyPath.split("."); 1687 for (let i = 0; i < parts.length - 1; i++) { 1688 currentLevelData = currentLevelData[parts[i]]; 1689 } 1690 const finalKey = parts[parts.length - 1]; 1691 return { 1692 currentLevelData, 1693 finalData, 1694 finalKey, 1695 parts 1696 }; 1697}; 1698
1699// src/Component/ValueStore.ts 1700var ValueStore_default = class { 1701 constructor(props) { 1702 /** 1703 * Original, read-only props that represent the original component state. 1704 * 1705 * @private 1706 */ 1707 this.props = {}; 1708 /** 1709 * A list of props that have been "dirty" (changed) since the last request to the server. 1710 */ 1711 this.dirtyProps = {}; 1712 /** 1713 * A list of dirty props that were sent to the server, but the response has 1714 * not yet been received. 1715 */ 1716 this.pendingProps = {}; 1717 /** 1718 * A list of props that the parent wants us to update. 1719 * 1720 * These will be sent on the next request to the server. 1721 */ 1722 this.updatedPropsFromParent = {}; 1723 this.props = props; 1724 } 1725 /** 1726 * Returns the props with the given name. 1727 * 1728 * This allows for non-normalized model names - e.g. 1729 * user[firstName] -> user.firstName and also will fetch 1730 * deeply (fetching the "firstName" sub-key from the "user" key). 1731 */ 1732 get(name) { 1733 const normalizedName = normalizeModelName(name); 1734 if (this.dirtyProps[normalizedName] !== void 0) { 1735 return this.dirtyProps[normalizedName]; 1736 } 1737 if (this.pendingProps[normalizedName] !== void 0) { 1738 return this.pendingProps[normalizedName]; 1739 } 1740 if (this.props[normalizedName] !== void 0) { 1741 return this.props[normalizedName]; 1742 } 1743 return getDeepData(this.props, normalizedName); 1744 } 1745 has(name) { 1746 return this.get(name) !== void 0; 1747 } 1748 /** 1749 * Sets data back onto the value store. 1750 * 1751 * The name can be in the non-normalized format. 1752 * 1753 * Returns true if the new value is different from the existing value. 1754 */ 1755 set(name, value) { 1756 const normalizedName = normalizeModelName(name); 1757 if (this.get(normalizedName) === value) { 1758 return false; 1759 } 1760 this.dirtyProps[normalizedName] = value; 1761 return true; 1762 } 1763 getOriginalProps() { 1764 return { ...this.props }; 1765 } 1766 getDirtyProps() { 1767 return { ...this.dirtyProps }; 1768 } 1769 getUpdatedPropsFromParent() { 1770 return { ...this.updatedPropsFromParent }; 1771 } 1772 /** 1773 * Called when an update request begins. 1774 */ 1775 flushDirtyPropsToPending() { 1776 this.pendingProps = { ...this.dirtyProps }; 1777 this.dirtyProps = {}; 1778 } 1779 /** 1780 * Called when an update request finishes successfully. 1781 */ 1782 reinitializeAllProps(props) { 1783 this.props = props; 1784 this.updatedPropsFromParent = {}; 1785 this.pendingProps = {}; 1786 } 1787 /** 1788 * Called after an update request failed. 1789 */ 1790 pushPendingPropsBackToDirty() { 1791 this.dirtyProps = { ...this.pendingProps, ...this.dirtyProps }; 1792 this.pendingProps = {}; 1793 } 1794 /** 1795 * This is used when a parent component is rendering, and it includes 1796 * a fresh set of props that should be updated on the child component. 1797 * 1798 * The server manages returning only the props that should be updated onto 1799 * the child, so we don't need to worry about that. 1800 * 1801 * The props are stored in a different place, because the existing props 1802 * have their own checksum and these new props have *their* own checksum. 1803 * So, on the next render, both need to be sent independently. 1804 * 1805 * Returns true if any of the props are different. 1806 */ 1807 storeNewPropsFromParent(props) {
1808 let changed = false; 1809 for (const [key, value] of Object.entries(props)) { 1810 const currentValue = this.get(key); 1811 if (currentValue !== value) { 1812 changed = true; 1813 } 1814 } 1815 if (changed) { 1816 this.updatedPropsFromParent = props; 1817 } 1818 return changed; 1819 } 1820}; 1821
1822// src/Component/index.ts 1823var Component = class { 1824 /** 1825 * @param element The root element 1826 * @param name The name of the component 1827 * @param props Readonly component props 1828 * @param listeners Array of event -> action listeners 1829 * @param id Some unique id to identify this component. Needed to be a child component 1830 * @param backend Backend instance for updating 1831 * @param elementDriver Class to get "model" name from any element. 1832 */ 1833 constructor(element, name, props, listeners, id, backend, elementDriver) { 1834 /** 1835 * A fingerprint that identifies the props/input that was used on 1836 * the server to create this component, especially if it was a 1837 * child component. This is sent back to the server and can be used 1838 * to determine if any "input" to the child component changed and thus, 1839 * if the child component needs to be re-rendered. 1840 */ 1841 this.fingerprint = ""; 1842 this.defaultDebounce = 150; 1843 this.backendRequest = null; 1844 /** Actions that are waiting to be executed */ 1845 this.pendingActions = []; 1846 /** Files that are waiting to be sent */ 1847 this.pendingFiles = {}; 1848 /** Is a request waiting to be made? */ 1849 this.isRequestPending = false; 1850 /** Current "timeout" before the pending request should be sent. */ 1851 this.requestDebounceTimeout = null; 1852 this.element = element; 1853 this.name = name; 1854 this.backend = backend; 1855 this.elementDriver = elementDriver; 1856 this.id = id; 1857 this.listeners = /* @__PURE__ */ new Map(); 1858 listeners.forEach((listener) => { 1859 if (!this.listeners.has(listener.event)) { 1860 this.listeners.set(listener.event, []); 1861 } 1862 this.listeners.get(listener.event)?.push(listener.action); 1863 }); 1864 this.valueStore = new ValueStore_default(props); 1865 this.unsyncedInputsTracker = new UnsyncedInputsTracker_default(this, elementDriver); 1866 this.hooks = new HookManager_default(); 1867 this.resetPromise(); 1868 this.externalMutationTracker = new ExternalMutationTracker_default( 1869 this.element, 1870 (element2) => elementBelongsToThisComponent(element2, this) 1871 ); 1872 this.externalMutationTracker.start(); 1873 } 1874 addPlugin(plugin) { 1875 plugin.attachToComponent(this); 1876 } 1877 connect() { 1878 registerComponent(this); 1879 this.hooks.triggerHook("connect", this); 1880 this.unsyncedInputsTracker.activate(); 1881 this.externalMutationTracker.start(); 1882 } 1883 disconnect() { 1884 unregisterComponent(this); 1885 this.hooks.triggerHook("disconnect", this); 1886 this.clearRequestDebounceTimeout(); 1887 this.unsyncedInputsTracker.deactivate(); 1888 this.externalMutationTracker.stop(); 1889 } 1890 on(hookName, callback) { 1891 this.hooks.register(hookName, callback); 1892 } 1893 off(hookName, callback) { 1894 this.hooks.unregister(hookName, callback); 1895 } 1896 set(model, value, reRender = false, debounce = false) { 1897 const promise = this.nextRequestPromise; 1898 const modelName = normalizeModelName(model); 1899 if (!this.valueStore.has(modelName)) { 1900 throw new Error(`Invalid model name "${model}".`); 1901 } 1902 const isChanged = this.valueStore.set(modelName, value); 1903 this.hooks.triggerHook("model:set", model, value, this); 1904 this.unsyncedInputsTracker.markModelAsSynced(modelName); 1905 if (reRender && isChanged) { 1906 this.debouncedStartRequest(debounce); 1907 } 1908 return promise; 1909 } 1910 getData(model) { 1911 const modelName = normalizeModelName(model); 1912 if (!this.valueStore.has(modelName)) { 1913 throw new Error(`Invalid model "${model}".`); 1914 } 1915 return this.valueStore.get(modelName); 1916 } 1917 action(name, args = {}, debounce = false) { 1918 const promise = this.nextRequestPromise; 1919 this.pendingActions.push({ 1920 name, 1921 args 1922 }); 1923 this.debouncedStartRequest(debounce); 1924 return promise; 1925 } 1926 files(key, input) { 1927 this.pendingFiles[key] = input; 1928 } 1929 render() { 1930 const promise = this.nextRequestPromise; 1931 this.tryStartingRequest(); 1932 return promise; 1933 } 1934 /** 1935 * Returns an array of models the user has modified, but whose model has not 1936 * yet been updated. 1937 */ 1938 getUnsyncedModels() { 1939 return this.unsyncedInputsTracker.getUnsyncedModels(); 1940 } 1941 emit(name, data, onlyMatchingComponentsNamed = null) { 1942 this.performEmit(name, data, false, onlyMatchingComponentsNamed); 1943 } 1944 emitUp(name, data, onlyMatchingComponentsNamed = null) { 1945 this.performEmit(name, data, true, onlyMatchingComponentsNamed); 1946 } 1947 emitSelf(name, data) { 1948 this.doEmit(name, data); 1949 } 1950 performEmit(name, data, emitUp, matchingName) { 1951 const components = findComponents(this, emitUp, matchingName);
1952 components.forEach((component) => { 1953 component.doEmit(name, data); 1954 }); 1955 } 1956 doEmit(name, data) { 1957 if (!this.listeners.has(name)) { 1958 return; 1959 } 1960 const actions = this.listeners.get(name) || []; 1961 actions.forEach((action) => { 1962 this.action(action, data, 1); 1963 }); 1964 } 1965 isTurboEnabled() { 1966 return typeof Turbo !== "undefined" && !this.element.closest('[data-turbo="false"]'); 1967 } 1968 tryStartingRequest() { 1969 if (!this.backendRequest) { 1970 this.performRequest(); 1971 return; 1972 } 1973 this.isRequestPending = true; 1974 } 1975 performRequest() { 1976 const thisPromiseResolve = this.nextRequestPromiseResolve; 1977 this.resetPromise(); 1978 this.unsyncedInputsTracker.resetUnsyncedFields(); 1979 const filesToSend = {}; 1980 for (const [key, value] of Object.entries(this.pendingFiles)) { 1981 if (value.files) { 1982 filesToSend[key] = value.files; 1983 } 1984 } 1985 const requestConfig = { 1986 props: this.valueStore.getOriginalProps(), 1987 actions: this.pendingActions, 1988 updated: this.valueStore.getDirtyProps(), 1989 children: {}, 1990 updatedPropsFromParent: this.valueStore.getUpdatedPropsFromParent(), 1991 files: filesToSend 1992 }; 1993 this.hooks.triggerHook("request:started", requestConfig); 1994 this.backendRequest = this.backend.makeRequest( 1995 requestConfig.props, 1996 requestConfig.actions, 1997 requestConfig.updated, 1998 requestConfig.children, 1999 requestConfig.updatedPropsFromParent, 2000 requestConfig.files 2001 ); 2002 this.hooks.triggerHook("loading.state:started", this.element, this.backendRequest); 2003 this.pendingActions = []; 2004 this.valueStore.flushDirtyPropsToPending(); 2005 this.isRequestPending = false; 2006 this.backendRequest.promise.then(async (response) => { 2007 const backendResponse = new BackendResponse_default(response); 2008 const html = await backendResponse.getBody(); 2009 for (const input of Object.values(this.pendingFiles)) { 2010 input.value = ""; 2011 } 2012 const headers = backendResponse.response.headers; 2013 if (!headers.get("Content-Type")?.includes("application/vnd.live-component+html") && !headers.get("X-Live-Redirect")) { 2014 const controls = { displayError: true }; 2015 this.valueStore.pushPendingPropsBackToDirty(); 2016 this.hooks.triggerHook("response:error", backendResponse, controls); 2017 if (controls.displayError) { 2018 this.renderError(html); 2019 } 2020 this.backendRequest = null; 2021 thisPromiseResolve(backendResponse); 2022 return response; 2023 } 2024 const liveUrl = backendResponse.getLiveUrl(); 2025 if (liveUrl) { 2026 history.replaceState( 2027 history.state, 2028 "", 2029 new URL(liveUrl + window.location.hash, window.location.origin) 2030 ); 2031 } 2032 this.processRerender(html, backendResponse); 2033 this.backendRequest = null; 2034 thisPromiseResolve(backendResponse); 2035 if (this.isRequestPending) { 2036 this.isRequestPending = false; 2037 this.performRequest(); 2038 } 2039 return response; 2040 }); 2041 } 2042 processRerender(html, backendResponse) { 2043 const controls = { shouldRender: true }; 2044 this.hooks.triggerHook("render:started", html, backendResponse, controls); 2045 if (!controls.shouldRender) { 2046 return; 2047 } 2048 if (backendResponse.response.headers.get("Location")) { 2049 if (this.isTurboEnabled()) { 2050 Turbo.visit(backendResponse.response.headers.get("Location")); 2051 } else { 2052 window.location.href = backendResponse.response.headers.get("Location") || ""; 2053 } 2054 return; 2055 } 2056 this.hooks.triggerHook("loading.state:finished", this.element); 2057 const modifiedModelValues = {}; 2058 Object.keys(this.valueStore.getDirtyProps()).forEach((modelName) => { 2059 modifiedModelValues[modelName] = this.valueStore.get(modelName); 2060 }); 2061 let newElement; 2062 try { 2063 newElement = htmlToElement(html); 2064 if (!newElement.matches("[data-controller~=live]")) { 2065 throw new Error("A live component template must contain a single root controller element."); 2066 } 2067 } catch (error) { 2068 console.error(`There was a problem with the '${this.name}' component HTML returned:`, { 2069 id: this.id 2070 }); 2071 throw error; 2072 } 2073 this.externalMutationTracker.handlePendingChanges(); 2074 this.externalMutationTracker.stop(); 2075 executeMorphdom( 2076 this.element, 2077 newElement, 2078 this.unsyncedInputsTracker.getUnsyncedInputs(), 2079 (element) => getValueFromElement(element, this.valueStore), 2080 this.externalMutationTracker 2081 ); 2082 this.externalMutationTracker.start(); 2083 const newProps = this.elementDriver.getComponentProps(); 2084 this.valueStore.reinitializeAllProps(newProps); 2085 const eventsToEmit = this.elementDriver.getEventsToEmit(); 2086 const browserEventsToDispatch = this.elementDriver.getBrowserEventsToDispatch();
2087 Object.keys(modifiedModelValues).forEach((modelName) => { 2088 this.valueStore.set(modelName, modifiedModelValues[modelName]); 2089 }); 2090 eventsToEmit.forEach(({ event, data, target, componentName }) => { 2091 if (target === "up") { 2092 this.emitUp(event, data, componentName); 2093 return; 2094 } 2095 if (target === "self") { 2096 this.emitSelf(event, data); 2097 return; 2098 } 2099 this.emit(event, data, componentName); 2100 }); 2101 browserEventsToDispatch.forEach(({ event, payload }) => { 2102 this.element.dispatchEvent( 2103 new CustomEvent(event, { 2104 detail: payload, 2105 bubbles: true 2106 }) 2107 ); 2108 }); 2109 this.hooks.triggerHook("render:finished", this); 2110 } 2111 calculateDebounce(debounce) { 2112 if (debounce === true) { 2113 return this.defaultDebounce; 2114 } 2115 if (debounce === false) { 2116 return 0; 2117 } 2118 return debounce; 2119 } 2120 clearRequestDebounceTimeout() { 2121 if (this.requestDebounceTimeout) { 2122 clearTimeout(this.requestDebounceTimeout); 2123 this.requestDebounceTimeout = null; 2124 } 2125 } 2126 debouncedStartRequest(debounce) { 2127 this.clearRequestDebounceTimeout(); 2128 this.requestDebounceTimeout = window.setTimeout(() => { 2129 this.render(); 2130 }, this.calculateDebounce(debounce)); 2131 } 2132 // inspired by Livewire! 2133 renderError(html) { 2134 let modal = document.getElementById("live-component-error"); 2135 if (modal) { 2136 modal.innerHTML = ""; 2137 } else { 2138 modal = document.createElement("div"); 2139 modal.id = "live-component-error"; 2140 modal.style.padding = "50px"; 2141 modal.style.backgroundColor = "rgba(0, 0, 0, .5)"; 2142 modal.style.zIndex = "100000"; 2143 modal.style.position = "fixed"; 2144 modal.style.top = "0px"; 2145 modal.style.bottom = "0px"; 2146 modal.style.left = "0px"; 2147 modal.style.right = "0px"; 2148 modal.style.display = "flex"; 2149 modal.style.flexDirection = "column"; 2150 } 2151 const iframe = document.createElement("iframe"); 2152 iframe.style.borderRadius = "5px"; 2153 iframe.style.flexGrow = "1"; 2154 modal.appendChild(iframe); 2155 document.body.prepend(modal); 2156 document.body.style.overflow = "hidden"; 2157 if (iframe.contentWindow) { 2158 iframe.contentWindow.document.open(); 2159 iframe.contentWindow.document.write(html); 2160 iframe.contentWindow.document.close(); 2161 } 2162 const closeModal = (modal2) => { 2163 if (modal2) { 2164 modal2.outerHTML = ""; 2165 } 2166 document.body.style.overflow = "visible"; 2167 }; 2168 modal.addEventListener("click", () => closeModal(modal)); 2169 modal.setAttribute("tabindex", "0"); 2170 modal.addEventListener("keydown", (e) => { 2171 if (e.key === "Escape") { 2172 closeModal(modal); 2173 } 2174 }); 2175 modal.focus(); 2176 } 2177 resetPromise() { 2178 this.nextRequestPromise = new Promise((resolve) => { 2179 this.nextRequestPromiseResolve = resolve; 2180 }); 2181 } 2182 /** 2183 * Called on a child component after the parent component render has requested 2184 * that the child component update its props & re-render if necessary. 2185 */ 2186 _updateFromParentProps(props) { 2187 const isChanged = this.valueStore.storeNewPropsFromParent(props); 2188 if (isChanged) { 2189 this.render(); 2190 } 2191 } 2192}; 2193function proxifyComponent(component) { 2194 return new Proxy(component, { 2195 get(component2, prop) { 2196 if (prop in component2 || typeof prop !== "string") { 2197 if (typeof component2[prop] === "function") { 2198 const callable = component2[prop]; 2199 return (...args) => { 2200 return callable.apply(component2, args); 2201 }; 2202 } 2203 return Reflect.get(component2, prop); 2204 } 2205 if (component2.valueStore.has(prop)) { 2206 return component2.getData(prop); 2207 } 2208 return (args) => { 2209 return component2.action.apply(component2, [prop, args]); 2210 }; 2211 }, 2212 set(target, property, value) { 2213 if (property in target) { 2214 target[property] = value; 2215 return true; 2216 } 2217 target.set(property, value); 2218 return true; 2219 } 2220 }); 2221} 2222
2223// src/Component/ElementDriver.ts 2224var StimulusElementDriver = class { 2225 constructor(controller) { 2226 this.controller = controller; 2227 } 2228 getModelName(element) { 2229 const modelDirective = getModelDirectiveFromElement(element, false); 2230 if (!modelDirective) { 2231 return null; 2232 } 2233 return modelDirective.action; 2234 } 2235 getComponentProps() { 2236 return this.controller.propsValue; 2237 } 2238 getEventsToEmit() { 2239 return this.controller.eventsToEmitValue; 2240 } 2241 getBrowserEventsToDispatch() { 2242 return this.controller.eventsToDispatchValue; 2243 } 2244}; 2245
2246// src/Directive/get_model_binding.ts 2247function get_model_binding_default(modelDirective) { 2248 let shouldRender = true; 2249 let targetEventName = null; 2250 let debounce = false; 2251 let minLength = null; 2252 let maxLength = null; 2253 let minValue = null; 2254 let maxValue = null; 2255 modelDirective.modifiers.forEach((modifier) => { 2256 switch (modifier.name) { 2257 case "on": 2258 if (!modifier.value) { 2259 throw new Error( 2260 `The "on" modifier in ${modelDirective.getString()} requires a value - e.g. on(change).` 2261 ); 2262 } 2263 if (!["input", "change"].includes(modifier.value)) { 2264 throw new Error( 2265 `The "on" modifier in ${modelDirective.getString()} only accepts the arguments "input" or "change".` 2266 ); 2267 } 2268 targetEventName = modifier.value; 2269 break; 2270 case "norender": 2271 shouldRender = false; 2272 break; 2273 case "debounce": 2274 debounce = modifier.value ? Number.parseInt(modifier.value) : true; 2275 break; 2276 case "min_length": 2277 minLength = modifier.value ? Number.parseInt(modifier.value) : null; 2278 break; 2279 case "max_length": 2280 maxLength = modifier.value ? Number.parseInt(modifier.value) : null; 2281 break; 2282 case "min_value": 2283 minValue = modifier.value ? Number.parseFloat(modifier.value) : null; 2284 break; 2285 case "max_value": 2286 maxValue = modifier.value ? Number.parseFloat(modifier.value) : null; 2287 break; 2288 default: 2289 throw new Error(`Unknown modifier "${modifier.name}" in data-model="${modelDirective.getString()}".`); 2290 } 2291 }); 2292 const [modelName, innerModelName] = modelDirective.action.split(":"); 2293 return { 2294 modelName, 2295 innerModelName: innerModelName || null, 2296 shouldRender, 2297 debounce, 2298 targetEventName, 2299 minLength, 2300 maxLength, 2301 minValue, 2302 maxValue 2303 }; 2304} 2305
2306// src/Component/plugins/ChildComponentPlugin.ts 2307var ChildComponentPlugin_default = class { 2308 constructor(component) { 2309 this.parentModelBindings = []; 2310 this.component = component; 2311 const modelDirectives = getAllModelDirectiveFromElements(this.component.element); 2312 this.parentModelBindings = modelDirectives.map(get_model_binding_default); 2313 } 2314 attachToComponent(component) { 2315 component.on("request:started", (requestData) => { 2316 requestData.children = this.getChildrenFingerprints(); 2317 }); 2318 component.on("model:set", (model, value) => { 2319 this.notifyParentModelChange(model, value); 2320 }); 2321 } 2322 getChildrenFingerprints() { 2323 const fingerprints = {}; 2324 this.getChildren().forEach((child) => { 2325 if (!child.id) { 2326 throw new Error("missing id"); 2327 } 2328 fingerprints[child.id] = { 2329 fingerprint: child.fingerprint, 2330 tag: child.element.tagName.toLowerCase() 2331 }; 2332 }); 2333 return fingerprints; 2334 } 2335 /** 2336 * Notifies parent of a model change if desired. 2337 * 2338 * This makes the child "behave" like it's a normal `<input>` element, 2339 * where, when its value changes, the parent is notified. 2340 */ 2341 notifyParentModelChange(modelName, value) { 2342 const parentComponent = findParent(this.component); 2343 if (!parentComponent) { 2344 return; 2345 }
2346 this.parentModelBindings.forEach((modelBinding) => { 2347 const childModelName = modelBinding.innerModelName || "value"; 2348 if (childModelName !== modelName) { 2349 return; 2350 } 2351 parentComponent.set(modelBinding.modelName, value, modelBinding.shouldRender, modelBinding.debounce); 2352 }); 2353 } 2354 getChildren() { 2355 return findChildren(this.component); 2356 } 2357}; 2358
2359// src/Component/plugins/LazyPlugin.ts 2360var LazyPlugin_default = class { 2361 constructor() { 2362 this.intersectionObserver = null; 2363 } 2364 attachToComponent(component) { 2365 if ("lazy" !== component.element.attributes.getNamedItem("loading")?.value) { 2366 return; 2367 } 2368 component.on("connect", () => { 2369 this.getObserver().observe(component.element); 2370 }); 2371 component.on("disconnect", () => { 2372 this.intersectionObserver?.unobserve(component.element); 2373 }); 2374 } 2375 getObserver() { 2376 if (!this.intersectionObserver) { 2377 this.intersectionObserver = new IntersectionObserver((entries, observer) => { 2378 entries.forEach((entry) => { 2379 if (entry.isIntersecting) { 2380 entry.target.dispatchEvent(new CustomEvent("live:appear")); 2381 observer.unobserve(entry.target); 2382 } 2383 }); 2384 }); 2385 } 2386 return this.intersectionObserver; 2387 } 2388}; 2389
2390// src/Component/plugins/LoadingPlugin.ts 2391var LoadingPlugin_default = class { 2392 attachToComponent(component) { 2393 component.on("loading.state:started", (element, request) => { 2394 this.startLoading(component, element, request); 2395 }); 2396 component.on("loading.state:finished", (element) => { 2397 this.finishLoading(component, element); 2398 }); 2399 this.finishLoading(component, component.element); 2400 } 2401 startLoading(component, targetElement, backendRequest) { 2402 this.handleLoadingToggle(component, true, targetElement, backendRequest); 2403 } 2404 finishLoading(component, targetElement) { 2405 this.handleLoadingToggle(component, false, targetElement, null); 2406 } 2407 handleLoadingToggle(component, isLoading, targetElement, backendRequest) { 2408 if (isLoading) { 2409 this.addAttributes(targetElement, ["busy"]); 2410 } else { 2411 this.removeAttributes(targetElement, ["busy"]); 2412 } 2413 this.getLoadingDirectives(component, targetElement).forEach(({ element, directives }) => { 2414 if (isLoading) { 2415 this.addAttributes(element, ["data-live-is-loading"]); 2416 } else { 2417 this.removeAttributes(element, ["data-live-is-loading"]); 2418 } 2419 directives.forEach((directive) => { 2420 this.handleLoadingDirective(element, isLoading, directive, backendRequest); 2421 }); 2422 }); 2423 } 2424 handleLoadingDirective(element, isLoading, directive, backendRequest) { 2425 const finalAction = parseLoadingAction(directive.action, isLoading); 2426 const targetedActions = []; 2427 const targetedModels = []; 2428 let delay = 0; 2429 const validModifiers = /* @__PURE__ */ new Map(); 2430 validModifiers.set("delay", (modifier) => { 2431 if (!isLoading) { 2432 return; 2433 } 2434 delay = modifier.value ? Number.parseInt(modifier.value) : 200; 2435 }); 2436 validModifiers.set("action", (modifier) => { 2437 if (!modifier.value) { 2438 throw new Error( 2439 `The "action" in data-loading must have an action name - e.g. action(foo). It's missing for "${directive.getString()}"` 2440 ); 2441 } 2442 targetedActions.push(modifier.value); 2443 }); 2444 validModifiers.set("model", (modifier) => { 2445 if (!modifier.value) { 2446 throw new Error( 2447 `The "model" in data-loading must have an action name - e.g. model(foo). It's missing for "${directive.getString()}"` 2448 ); 2449 } 2450 targetedModels.push(modifier.value); 2451 }); 2452 directive.modifiers.forEach((modifier) => { 2453 if (validModifiers.has(modifier.name)) { 2454 const callable = validModifiers.get(modifier.name) ?? (() => { 2455 }); 2456 callable(modifier); 2457 return; 2458 } 2459 throw new Error( 2460 `Unknown modifier "${modifier.name}" used in data-loading="${directive.getString()}". Available modifiers are: ${Array.from(validModifiers.keys()).join(", ")}.` 2461 ); 2462 }); 2463 if (isLoading && targetedActions.length > 0 && backendRequest && !backendRequest.containsOneOfActions(targetedActions)) { 2464 return; 2465 } 2466 if (isLoading && targetedModels.length > 0 && backendRequest && !backendRequest.areAnyModelsUpdated(targetedModels)) { 2467 return; 2468 } 2469 let loadingDirective; 2470 switch (finalAction) { 2471 case "show": 2472 loadingDirective = () => this.showElement(element); 2473 break; 2474 case "hide": 2475 loadingDirective = () => this.hideElement(element); 2476 break; 2477 case "addClass": 2478 loadingDirective = () => this.addClass(element, directive.args); 2479 break; 2480 case "removeClass": 2481 loadingDirective = () => this.removeClass(element, directive.args); 2482 break; 2483 case "addAttribute": 2484 loadingDirective = () => this.addAttributes(element, directive.args); 2485 break; 2486 case "removeAttribute": 2487 loadingDirective = () => this.removeAttributes(element, directive.args); 2488 break; 2489 default: 2490 throw new Error(`Unknown data-loading action "${finalAction}"`); 2491 } 2492 if (delay) { 2493 window.setTimeout(() => { 2494 if (backendRequest && !backendRequest.isResolved) { 2495 loadingDirective(); 2496 } 2497 }, delay); 2498 return; 2499 } 2500 loadingDirective(); 2501 } 2502 getLoadingDirectives(component, element) { 2503 const loadingDirectives = []; 2504 let matchingElements = [...Array.from(element.querySelectorAll("[data-loading]"))]; 2505 matchingElements = matchingElements.filter((elt) => elementBelongsToThisComponent(elt, component)); 2506 if (element.hasAttribute("data-loading")) { 2507 matchingElements = [element, ...matchingElements]; 2508 }
2509 matchingElements.forEach((element2) => { 2510 if (!(element2 instanceof HTMLElement) && !(element2 instanceof SVGElement)) { 2511 throw new Error("Invalid Element Type"); 2512 } 2513 const directives = parseDirectives(element2.dataset.loading || "show"); 2514 loadingDirectives.push({ 2515 element: element2, 2516 directives 2517 }); 2518 }); 2519 return loadingDirectives; 2520 } 2521 showElement(element) { 2522 element.style.display = "revert"; 2523 } 2524 hideElement(element) { 2525 element.style.display = "none"; 2526 } 2527 addClass(element, classes) { 2528 element.classList.add(...combineSpacedArray(classes)); 2529 } 2530 removeClass(element, classes) { 2531 element.classList.remove(...combineSpacedArray(classes)); 2532 if (element.classList.length === 0) { 2533 element.removeAttribute("class"); 2534 } 2535 } 2536 addAttributes(element, attributes) { 2537 attributes.forEach((attribute) => { 2538 element.setAttribute(attribute, ""); 2539 }); 2540 } 2541 removeAttributes(element, attributes) { 2542 attributes.forEach((attribute) => { 2543 element.removeAttribute(attribute); 2544 }); 2545 } 2546}; 2547var parseLoadingAction = (action, isLoading) => { 2548 switch (action) { 2549 case "show": 2550 return isLoading ? "show" : "hide"; 2551 case "hide": 2552 return isLoading ? "hide" : "show"; 2553 case "addClass": 2554 return isLoading ? "addClass" : "removeClass"; 2555 case "removeClass": 2556 return isLoading ? "removeClass" : "addClass"; 2557 case "addAttribute": 2558 return isLoading ? "addAttribute" : "removeAttribute"; 2559 case "removeAttribute": 2560 return isLoading ? "removeAttribute" : "addAttribute"; 2561 } 2562 throw new Error(`Unknown data-loading action "${action}"`); 2563}; 2564
2565// src/Component/plugins/PageUnloadingPlugin.ts 2566var PageUnloadingPlugin_default = class { 2567 constructor() { 2568 this.isConnected = false; 2569 } 2570 attachToComponent(component) { 2571 component.on("render:started", (html, response, controls) => { 2572 if (!this.isConnected) { 2573 controls.shouldRender = false; 2574 } 2575 }); 2576 component.on("connect", () => { 2577 this.isConnected = true; 2578 }); 2579 component.on("disconnect", () => { 2580 this.isConnected = false; 2581 }); 2582 } 2583}; 2584
2585// src/PollingDirector.ts 2586var PollingDirector_default = class { 2587 constructor(component) { 2588 this.isPollingActive = true; 2589 this.pollingIntervals = []; 2590 this.component = component; 2591 } 2592 addPoll(actionName, duration) { 2593 this.polls.push({ actionName, duration }); 2594 if (this.isPollingActive) { 2595 this.initiatePoll(actionName, duration); 2596 } 2597 } 2598 startAllPolling() { 2599 if (this.isPollingActive) { 2600 return; 2601 } 2602 this.isPollingActive = true; 2603 this.polls.forEach(({ actionName, duration }) => { 2604 this.initiatePoll(actionName, duration); 2605 }); 2606 } 2607 stopAllPolling() { 2608 this.isPollingActive = false; 2609 this.pollingIntervals.forEach((interval) => { 2610 clearInterval(interval); 2611 }); 2612 } 2613 clearPolling() { 2614 this.stopAllPolling(); 2615 this.polls = []; 2616 this.startAllPolling(); 2617 } 2618 initiatePoll(actionName, duration) { 2619 let callback; 2620 if (actionName === "$render") { 2621 callback = () => { 2622 this.component.render(); 2623 }; 2624 } else { 2625 callback = () => { 2626 this.component.action(actionName, {}, 0); 2627 }; 2628 } 2629 const timer = window.setInterval(() => { 2630 callback(); 2631 }, duration); 2632 this.pollingIntervals.push(timer); 2633 } 2634}; 2635
2636// src/Component/plugins/PollingPlugin.ts 2637var PollingPlugin_default = class { 2638 attachToComponent(component) { 2639 this.element = component.element; 2640 this.pollingDirector = new PollingDirector_default(component); 2641 this.initializePolling(); 2642 component.on("connect", () => { 2643 this.pollingDirector.startAllPolling(); 2644 }); 2645 component.on("disconnect", () => { 2646 this.pollingDirector.stopAllPolling(); 2647 }); 2648 component.on("render:finished", () => { 2649 this.initializePolling(); 2650 }); 2651 } 2652 addPoll(actionName, duration) { 2653 this.pollingDirector.addPoll(actionName, duration); 2654 } 2655 clearPolling() { 2656 this.pollingDirector.clearPolling(); 2657 } 2658 initializePolling() { 2659 this.clearPolling(); 2660 if (this.element.dataset.poll === void 0) { 2661 return; 2662 } 2663 const rawPollConfig = this.element.dataset.poll; 2664 const directives = parseDirectives(rawPollConfig || "$render"); 2665 directives.forEach((directive) => { 2666 let duration = 2e3; 2667 directive.modifiers.forEach((modifier) => { 2668 switch (modifier.name) { 2669 case "delay": 2670 if (modifier.value) { 2671 duration = Number.parseInt(modifier.value); 2672 } 2673 break; 2674 default: 2675 console.warn(`Unknown modifier "${modifier.name}" in data-poll "${rawPollConfig}".`); 2676 } 2677 }); 2678 this.addPoll(directive.action, duration); 2679 }); 2680 } 2681}; 2682
2683// src/Component/plugins/SetValueOntoModelFieldsPlugin.ts 2684var SetValueOntoModelFieldsPlugin_default = class { 2685 attachToComponent(component) { 2686 this.synchronizeValueOfModelFields(component); 2687 component.on("render:finished", () => { 2688 this.synchronizeValueOfModelFields(component); 2689 }); 2690 } 2691 /** 2692 * Sets the "value" of all model fields to the component data. 2693 * 2694 * This is called when the component initializes and after re-render. 2695 * Take the following element: 2696 * 2697 * <input data-model="firstName"> 2698 * 2699 * This method will set the "value" of that element to the value of 2700 * the "firstName" model. 2701 */ 2702 synchronizeValueOfModelFields(component) { 2703 component.element.querySelectorAll("[data-model]").forEach((element) => { 2704 if (!(element instanceof HTMLElement)) { 2705 throw new Error("Invalid element using data-model."); 2706 } 2707 if (element instanceof HTMLFormElement) { 2708 return; 2709 } 2710 if (!elementBelongsToThisComponent(element, component)) { 2711 return; 2712 } 2713 const modelDirective = getModelDirectiveFromElement(element); 2714 if (!modelDirective) { 2715 return; 2716 } 2717 const modelName = modelDirective.action; 2718 if (component.getUnsyncedModels().includes(modelName)) { 2719 return; 2720 } 2721 if (component.valueStore.has(modelName)) { 2722 setValueOnElement(element, component.valueStore.get(modelName)); 2723 } 2724 if (element instanceof HTMLSelectElement && !element.multiple) { 2725 component.valueStore.set(modelName, getValueFromElement(element, component.valueStore)); 2726 } 2727 }); 2728 } 2729}; 2730
2731// src/Component/plugins/ValidatedFieldsPlugin.ts 2732var ValidatedFieldsPlugin_default = class { 2733 attachToComponent(component) { 2734 component.on("model:set", (modelName) => { 2735 this.handleModelSet(modelName, component.valueStore); 2736 }); 2737 } 2738 handleModelSet(modelName, valueStore) { 2739 if (valueStore.has("validatedFields")) { 2740 const validatedFields = [...valueStore.get("validatedFields")]; 2741 if (!validatedFields.includes(modelName)) { 2742 validatedFields.push(modelName); 2743 } 2744 valueStore.set("validatedFields", validatedFields); 2745 } 2746 } 2747}; 2748
2749// src/live_controller.ts 2750var _LiveControllerDefault = class _LiveControllerDefault extends Controller { 2751 constructor() { 2752 super(...arguments); 2753 this.pendingActionTriggerModelElement = null; 2754 this.elementEventListeners = [ 2755 { event: "input", callback: (event) => this.handleInputEvent(event) }, 2756 { event: "change", callback: (event) => this.handleChangeEvent(event) } 2757 ]; 2758 this.pendingFiles = {}; 2759 } 2760 initialize() { 2761 this.mutationObserver = new MutationObserver(this.onMutations.bind(this)); 2762 this.createComponent(); 2763 } 2764 connect() { 2765 this.connectComponent(); 2766 this.mutationObserver.observe(this.element, { 2767 attributes: true 2768 }); 2769 } 2770 disconnect() { 2771 this.disconnectComponent(); 2772 this.mutationObserver.disconnect(); 2773 } 2774 /** 2775 * Called to update one piece of the model. 2776 * 2777 * <button data-action="live#update" data-model="foo" data-value="5"> 2778 */ 2779 update(event) { 2780 if (event.type === "input" || event.type === "change") { 2781 throw new Error( 2782 `Since LiveComponents 2.3, you no longer need data-action="live#update" on form elements. Found on element: ${getElementAsTagText( 2783 event.currentTarget 2784 )}` 2785 ); 2786 } 2787 this.updateModelFromElementEvent(event.currentTarget, null); 2788 } 2789 action(event) { 2790 const params = event.params; 2791 if (!params.action) { 2792 throw new Error( 2793 `No action name provided on element: ${getElementAsTagText( 2794 event.currentTarget 2795 )}. Did you forget to add the "data-live-action-param" attribute?` 2796 ); 2797 } 2798 const rawAction = params.action; 2799 const actionArgs = { ...params }; 2800 delete actionArgs.action; 2801 const directives = parseDirectives(rawAction); 2802 let debounce = false; 2803 directives.forEach((directive) => { 2804 let pendingFiles = {}; 2805 const validModifiers = /* @__PURE__ */ new Map(); 2806 validModifiers.set("stop", () => { 2807 event.stopPropagation(); 2808 }); 2809 validModifiers.set("self", () => { 2810 if (event.target !== event.currentTarget) { 2811 return; 2812 } 2813 }); 2814 validModifiers.set("debounce", (modifier) => { 2815 debounce = modifier.value ? Number.parseInt(modifier.value) : true; 2816 }); 2817 validModifiers.set("files", (modifier) => { 2818 if (!modifier.value) { 2819 pendingFiles = this.pendingFiles; 2820 } else if (this.pendingFiles[modifier.value]) { 2821 pendingFiles[modifier.value] = this.pendingFiles[modifier.value]; 2822 } 2823 }); 2824 directive.modifiers.forEach((modifier) => { 2825 if (validModifiers.has(modifier.name)) { 2826 const callable = validModifiers.get(modifier.name) ?? (() => { 2827 }); 2828 callable(modifier); 2829 return; 2830 } 2831 console.warn( 2832 `Unknown modifier ${modifier.name} in action "${rawAction}". Available modifiers are: ${Array.from( 2833 validModifiers.keys() 2834 ).join(", ")}.` 2835 ); 2836 }); 2837 for (const [key, input] of Object.entries(pendingFiles)) { 2838 if (input.files) { 2839 this.component.files(key, input); 2840 } 2841 delete this.pendingFiles[key]; 2842 } 2843 this.component.action(directive.action, actionArgs, debounce); 2844 if (getModelDirectiveFromElement(event.currentTarget, false)) { 2845 this.pendingActionTriggerModelElement = event.currentTarget; 2846 } 2847 }); 2848 } 2849 $render() { 2850 return this.component.render(); 2851 } 2852 emit(event) { 2853 this.getEmitDirectives(event).forEach(({ name, data, nameMatch }) => { 2854 this.component.emit(name, data, nameMatch); 2855 }); 2856 } 2857 emitUp(event) { 2858 this.getEmitDirectives(event).forEach(({ name, data, nameMatch }) => { 2859 this.component.emitUp(name, data, nameMatch); 2860 }); 2861 } 2862 emitSelf(event) { 2863 this.getEmitDirectives(event).forEach(({ name, data }) => { 2864 this.component.emitSelf(name, data); 2865 }); 2866 } 2867 /** 2868 * Update a model value. 2869 * 2870 * @param {string} model The model to update 2871 * @param {any} value The new value 2872 * @param {boolean} shouldRender Whether a re-render should be triggered 2873 * @param {number|boolean} debounce 2874 */ 2875 $updateModel(model, value, shouldRender = true, debounce = true) { 2876 return this.component.set(model, value, shouldRender, debounce); 2877 } 2878 propsUpdatedFromParentValueChanged() { 2879 this.component._updateFromParentProps(this.propsUpdatedFromParentValue); 2880 } 2881 fingerprintValueChanged() { 2882 this.component.fingerprint = this.fingerprintValue; 2883 } 2884 getEmitDirectives(event) { 2885 const params = event.params; 2886 if (!params.event) { 2887 throw new Error( 2888 `No event name provided on element: ${getElementAsTagText( 2889 event.currentTarget 2890 )}. Did you forget to add the "data-live-event-param" attribute?` 2891 ); 2892 } 2893 const eventInfo = params.event; 2894 const eventArgs = { ...params }; 2895 delete eventArgs.event; 2896 const directives = parseDirectives(eventInfo); 2897 const emits = [];
2898 directives.forEach((directive) => { 2899 let nameMatch = null; 2900 directive.modifiers.forEach((modifier) => { 2901 switch (modifier.name) { 2902 case "name": 2903 nameMatch = modifier.value; 2904 break; 2905 default: 2906 throw new Error(`Unknown modifier ${modifier.name} in event "${eventInfo}".`); 2907 } 2908 }); 2909 emits.push({ 2910 name: directive.action, 2911 data: eventArgs, 2912 nameMatch 2913 }); 2914 }); 2915 return emits; 2916 } 2917 createComponent() { 2918 const id = this.element.id || null; 2919 this.component = new Component( 2920 this.element, 2921 this.nameValue, 2922 this.propsValue, 2923 this.listenersValue, 2924 id, 2925 _LiveControllerDefault.backendFactory(this), 2926 new StimulusElementDriver(this) 2927 ); 2928 this.proxiedComponent = proxifyComponent(this.component); 2929 Object.defineProperty(this.element, "__component", { 2930 value: this.proxiedComponent, 2931 writable: true 2932 }); 2933 if (this.hasDebounceValue) { 2934 this.component.defaultDebounce = this.debounceValue; 2935 } 2936 const plugins = [ 2937 new LoadingPlugin_default(), 2938 new LazyPlugin_default(), 2939 new ValidatedFieldsPlugin_default(), 2940 new PageUnloadingPlugin_default(), 2941 new PollingPlugin_default(), 2942 new SetValueOntoModelFieldsPlugin_default(), 2943 new ChildComponentPlugin_default(this.component) 2944 ]; 2945 plugins.forEach((plugin) => { 2946 this.component.addPlugin(plugin); 2947 }); 2948 } 2949 connectComponent() { 2950 this.component.connect(); 2951 this.mutationObserver.observe(this.element, { 2952 attributes: true 2953 }); 2954 this.elementEventListeners.forEach(({ event, callback }) => { 2955 this.component.element.addEventListener(event, callback); 2956 }); 2957 this.dispatchEvent("connect"); 2958 } 2959 disconnectComponent() { 2960 this.component.disconnect(); 2961 this.elementEventListeners.forEach(({ event, callback }) => { 2962 this.component.element.removeEventListener(event, callback); 2963 }); 2964 this.dispatchEvent("disconnect"); 2965 } 2966 handleInputEvent(event) { 2967 const target = event.target; 2968 if (!target) { 2969 return; 2970 } 2971 this.updateModelFromElementEvent(target, "input"); 2972 } 2973 handleChangeEvent(event) { 2974 const target = event.target; 2975 if (!target) { 2976 return; 2977 } 2978 this.updateModelFromElementEvent(target, "change"); 2979 } 2980 /** 2981 * Sets a model given an element and some event. 2982 * 2983 * This parses the "data-model" from the element and takes 2984 * into account modifiers like "debounce", "norender" and "on()". 2985 * 2986 * This is used, for example, the grab the new value from an input 2987 * on "change" and set that new value onto the model. 2988 * 2989 * It's also used to, on click, set the value from a button 2990 * with data-model="" and data-value"". 2991 * 2992 * @param element 2993 * @param eventName If specified (e.g. "input" or "change"), the model may 2994 * skip updating if the on() modifier is passed (e.g. on(change)). 2995 * If not passed, the model will always be updated. 2996 */ 2997 updateModelFromElementEvent(element, eventName) { 2998 if (!elementBelongsToThisComponent(element, this.component)) { 2999 return; 3000 } 3001 if (!(element instanceof HTMLElement)) { 3002 throw new Error("Could not update model for non HTMLElement"); 3003 } 3004 if (element instanceof HTMLInputElement && element.type === "file") { 3005 const key = element.name; 3006 if (element.files?.length) { 3007 this.pendingFiles[key] = element; 3008 } else if (this.pendingFiles[key]) { 3009 delete this.pendingFiles[key]; 3010 } 3011 } 3012 const modelDirective = getModelDirectiveFromElement(element, false); 3013 if (!modelDirective) { 3014 return; 3015 } 3016 const modelBinding = get_model_binding_default(modelDirective); 3017 if (!modelBinding.targetEventName) { 3018 modelBinding.targetEventName = "input"; 3019 } 3020 if (this.pendingActionTriggerModelElement === element) { 3021 modelBinding.shouldRender = false;
3022 } 3023 if (eventName === "change" && modelBinding.targetEventName === "input") { 3024 modelBinding.targetEventName = "change"; 3025 } 3026 if (eventName && modelBinding.targetEventName !== eventName) { 3027 return; 3028 } 3029 if (false === modelBinding.debounce) { 3030 if (modelBinding.targetEventName === "input") { 3031 modelBinding.debounce = true; 3032 } else { 3033 modelBinding.debounce = 0; 3034 } 3035 } 3036 const finalValue = getValueFromElement(element, this.component.valueStore); 3037 if (isTextualInputElement(element) || isTextareaElement(element)) { 3038 if (modelBinding.minLength !== null && typeof finalValue === "string" && finalValue.length < modelBinding.minLength) { 3039 return; 3040 } 3041 if (modelBinding.maxLength !== null && typeof finalValue === "string" && finalValue.length > modelBinding.maxLength) { 3042 return; 3043 } 3044 } 3045 if (isNumericalInputElement(element)) { 3046 const numericValue = Number(finalValue); 3047 if (modelBinding.minValue !== null && numericValue < modelBinding.minValue) { 3048 return; 3049 } 3050 if (modelBinding.maxValue !== null && numericValue > modelBinding.maxValue) { 3051 return; 3052 } 3053 } 3054 this.component.set(modelBinding.modelName, finalValue, modelBinding.shouldRender, modelBinding.debounce); 3055 } 3056 dispatchEvent(name, detail = {}, canBubble = true, cancelable = false) { 3057 detail.controller = this; 3058 detail.component = this.proxiedComponent; 3059 this.dispatch(name, { detail, prefix: "live", cancelable, bubbles: canBubble }); 3060 } 3061 onMutations(mutations) { 3062 mutations.forEach((mutation) => { 3063 if (mutation.type === "attributes" && mutation.attributeName === "id" && this.element.id !== this.component.id) { 3064 this.disconnectComponent(); 3065 this.createComponent(); 3066 this.connectComponent(); 3067 } 3068 }); 3069 } 3070}; 3071_LiveControllerDefault.values = { 3072 name: String, 3073 url: String, 3074 props: { type: Object, default: {} }, 3075 propsUpdatedFromParent: { type: Object, default: {} }, 3076 listeners: { type: Array, default: [] }, 3077 eventsToEmit: { type: Array, default: [] }, 3078 eventsToDispatch: { type: Array, default: [] }, 3079 debounce: { type: Number, default: 150 }, 3080 fingerprint: { type: String, default: "" }, 3081 requestMethod: { type: String, default: "post" } 3082}; 3083_LiveControllerDefault.backendFactory = (controller) => new Backend_default(controller.urlValue, controller.requestMethodValue); 3084var LiveControllerDefault = _LiveControllerDefault; 3085export { 3086 Component, 3087 LiveControllerDefault as default, 3088 getComponent 3089};
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.