Skip to content

Conversation

@MichaelWest22
Copy link
Collaborator

Here is a possible change to implement a new keepInputValues option while keeping all the old input handling behavior the same

With this setting turned on we are able to optimize the performance of all node updates where there are no innerHTML changes as we can just skip morphing. Before this was not possible because the old sync input value routine would apply changes to reset input and textarea user input state even when there are no no actual node changes to morph. But with the new keepInputValues option this is not an issue anymore. It does mean this currently has to be opt in to not break things. It may be possible to remove the two old input preservation config flags as they may not be needed in a future version as this option mostly replaces their need.

The idea with the keepInputValues is to just skip forcing syncing the input values logic we have now and just now detect if the default value supplied is different than the last one and only update the users input value if this changes. This allows you to push out new identical form updates that will morph without losing any user input. However it has one big downside and this is that forms will not reset on morph so after form submit if you return the same blank form it will retain the old existing input values which may not be what is expected. This is why it is currently an opt in config value so it won't break existing use cases.

@bakura10
Copy link

I would be very interested in such a feature. For now I'm using this callback, but this use case seems to be common enough to have it in core so that more edge cases can be handled:

export const preserveFormInputs: MorphCallback = (oldNode, newNode) => {
  if (oldNode instanceof HTMLInputElement && newNode instanceof HTMLInputElement) {
    // Preserve user-entered values
    if (newNode.value !== oldNode.value) {
      newNode.value = oldNode.value;
      oldNode.dispatchEvent(new Event('change', { bubbles: true }));
    }

    // Preserve checked state
    if ((oldNode.type === 'checkbox' || oldNode.type === 'radio') && newNode.checked !== oldNode.checked) {
      newNode.checked = oldNode.checked;
      oldNode.dispatchEvent(new Event('change', { bubbles: true }));
    }
  }
  
  if (oldNode instanceof HTMLTextAreaElement && newNode instanceof HTMLTextAreaElement) {
    if (newNode.value !== oldNode.value) {
      newNode.value = oldNode.value;
      oldNode.dispatchEvent(new Event('change', { bubbles: true }));
    }
  }
  
  if (oldNode instanceof HTMLSelectElement && newNode instanceof HTMLSelectElement) {
    if (newNode.value !== oldNode.value) {
      newNode.value = oldNode.value;
      oldNode.dispatchEvent(new Event('change', { bubbles: true }));
    }
  }

  return true;
}

@MichaelWest22 in our implementation we also send the change event on the oldNode. The use case here is imagine that you have a web component composing an input:

<quantity-picker>
 <button type="button" disabled>Minus</button>
 <input type="text" value="1" />
 <button type="button">Plus</button>
</quantity-picker>

To synchronize the disabled property of the buttons, the component listens to thechange input on the input. So it was required to ensure that the input properly triggers the change event.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants