Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/LiveComponent/assets/dist/live_controller.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ declare class export_default{
set(name: string, value: any): boolean;
getOriginalProps(): any;
getDirtyProps(): any;
getPendingProps(): any;
getUpdatedPropsFromParent(): any;
flushDirtyPropsToPending(): void;
reinitializeAllProps(props: any): void;
Expand Down
39 changes: 34 additions & 5 deletions src/LiveComponent/assets/dist/live_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -1119,7 +1119,22 @@ var syncAttributes = (fromEl, toEl) => {
toEl.setAttribute(attr.name, attr.value);
}
};
function executeMorphdom(rootFromElement, rootToElement, modifiedFieldElements, getElementValue, externalMutationTracker) {
var syncAttributesExceptValue = (fromEl, toEl) => {
const valueAttributes = ["value", "checked", "selected"];
for (let i = fromEl.attributes.length - 1; i >= 0; i--) {
const attr = fromEl.attributes[i];
if (!valueAttributes.includes(attr.name) && !toEl.hasAttribute(attr.name)) {
fromEl.removeAttribute(attr.name);
}
}
for (let i = 0; i < toEl.attributes.length; i++) {
const attr = toEl.attributes[i];
if (!valueAttributes.includes(attr.name) && fromEl.getAttribute(attr.name) !== attr.value) {
fromEl.setAttribute(attr.name, attr.value);
}
}
};
function executeMorphdom(rootFromElement, rootToElement, modifiedFieldElements, getElementValue, externalMutationTracker, pendingProps = {}) {
const originalElementIdsToSwapAfter = [];
const originalElementsToPreserve = /* @__PURE__ */ new Map();
const markElementAsNeedingPostMorphSwap = (id, replaceWithClone) => {
Expand Down Expand Up @@ -1186,11 +1201,21 @@ function executeMorphdom(rootFromElement, rootToElement, modifiedFieldElements,
fromEl.insertAdjacentElement("afterend", toEl);
return false;
}
const modelDirective = getModelDirectiveFromElement(fromEl, false);
const currentValue = getElementValue(fromEl);
if (modifiedFieldElements.includes(fromEl)) {
setValueOnElement(toEl, getElementValue(fromEl));
if (modelDirective) {
const sentValue = pendingProps ? pendingProps[modelDirective.action] : void 0;
if (sentValue !== currentValue) {
syncAttributesExceptValue(fromEl, toEl);
return false;
}
} else {
setValueOnElement(toEl, currentValue);
}
}
if (fromEl === document.activeElement && fromEl !== document.body && null !== getModelDirectiveFromElement(fromEl, false)) {
setValueOnElement(toEl, getElementValue(fromEl));
if (fromEl === document.activeElement && fromEl !== document.body && modelDirective !== null) {
setValueOnElement(toEl, currentValue);
}
const elementChanges = externalMutationTracker.getChangedElement(fromEl);
if (elementChanges) {
Expand Down Expand Up @@ -1768,6 +1793,9 @@ var ValueStore_default = class {
getDirtyProps() {
return { ...this.dirtyProps };
}
getPendingProps() {
return { ...this.pendingProps };
}
getUpdatedPropsFromParent() {
return { ...this.updatedPropsFromParent };
}
Expand Down Expand Up @@ -2079,7 +2107,8 @@ var Component = class {
newElement,
this.unsyncedInputsTracker.getUnsyncedInputs(),
(element) => getValueFromElement(element, this.valueStore),
this.externalMutationTracker
this.externalMutationTracker,
this.valueStore.getPendingProps()
);
this.externalMutationTracker.start();
const newProps = this.elementDriver.getComponentProps();
Expand Down
4 changes: 4 additions & 0 deletions src/LiveComponent/assets/src/Component/ValueStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ export default class {
return { ...this.dirtyProps };
}

getPendingProps(): any {
return { ...this.pendingProps };
}

getUpdatedPropsFromParent(): any {
return { ...this.updatedPropsFromParent };
}
Expand Down
3 changes: 2 additions & 1 deletion src/LiveComponent/assets/src/Component/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,8 @@ export default class Component {
newElement,
this.unsyncedInputsTracker.getUnsyncedInputs(),
(element: HTMLElement) => getValueFromElement(element, this.valueStore),
this.externalMutationTracker
this.externalMutationTracker,
this.valueStore.getPendingProps()
);
this.externalMutationTracker.start();

Expand Down
50 changes: 40 additions & 10 deletions src/LiveComponent/assets/src/morphdom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,31 @@ const syncAttributes = (fromEl: Element, toEl: Element): void => {
}
};

const syncAttributesExceptValue = (fromEl: Element, toEl: Element): void => {
const valueAttributes = ['value', 'checked', 'selected'];

for (let i = fromEl.attributes.length - 1; i >= 0; i--) {
const attr = fromEl.attributes[i];
if (!valueAttributes.includes(attr.name) && !toEl.hasAttribute(attr.name)) {
fromEl.removeAttribute(attr.name);
}
}

for (let i = 0; i < toEl.attributes.length; i++) {
const attr = toEl.attributes[i];
if (!valueAttributes.includes(attr.name) && fromEl.getAttribute(attr.name) !== attr.value) {
fromEl.setAttribute(attr.name, attr.value);
}
}
};

export function executeMorphdom(
rootFromElement: HTMLElement,
rootToElement: HTMLElement,
modifiedFieldElements: Array<HTMLElement>,
getElementValue: (element: HTMLElement) => any,
externalMutationTracker: ExternalMutationTracker
externalMutationTracker: ExternalMutationTracker,
pendingProps: Record<string, any> = {}
) {
/*
* Handle "data-live-preserve" elements.
Expand Down Expand Up @@ -146,10 +165,25 @@ export function executeMorphdom(
return false;
}

// if this field's value has been modified since this HTML was
// requested, set the toEl's value to match the fromEl
const modelDirective = getModelDirectiveFromElement(fromEl, false);
const currentValue = getElementValue(fromEl);

// If this field's value has been modified since this HTML was
// requested, preserve the value if it differs from what we sent
if (modifiedFieldElements.includes(fromEl)) {
setValueOnElement(toEl, getElementValue(fromEl));
if (modelDirective) {
// For mapped fields, only preserve if DOM value differs from what we sent
const sentValue = pendingProps ? pendingProps[modelDirective.action] : undefined;
if (sentValue !== currentValue) {
// Sync all attributes except value-related ones, then skip
// morphdom to preserve the input value
syncAttributesExceptValue(fromEl, toEl);
return false;
}
} else {
// Unmapped fields: always preserve (original behavior)
setValueOnElement(toEl, currentValue);
}
}

// Special handling for the active element of a model field.
Expand All @@ -162,12 +196,8 @@ export function executeMorphdom(
// We skip this for non-model elements and allow this to either
// maintain the value if changed (see code above) or for the
// morphing process to update it to the value from the server.
if (
fromEl === document.activeElement &&
fromEl !== document.body &&
null !== getModelDirectiveFromElement(fromEl, false)
) {
setValueOnElement(toEl, getElementValue(fromEl));
if (fromEl === document.activeElement && fromEl !== document.body && modelDirective !== null) {
setValueOnElement(toEl, currentValue);
}

// handle any external changes to this element
Expand Down
Loading