w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.46k stars 657 forks source link

[selectors-4] Issue 11: Introduce pseudo-class matching when user changed the value of an input #1533

Open SebastianZ opened 7 years ago

SebastianZ commented 7 years ago

Back in 2014 it was suggested to add a pseudo-class matching when a user changed the value of an element. This topic was also already discussed earlier by the CSSWG.

I want to pick up that discussion again. The idea previously was to check whether the current value differs from the default value, expressed in JavaScript syntax, where el.value !== el.defaultValue, el.checked !== el.defaultChecked, currently selected options different from default selected options, etc., depending on the type of input.

Suggested names so far are :changed, :edited, :dirty, :user-dirtied and :user-interacted.

This would make :user-invalid redundant, as it could be expressed by :changed:invalid.

Sebastian

fantasai commented 5 years ago

@SebastianZ Could you give some specific use cases, other than those covered by :user-invalid? :)

zoliszabo commented 5 years ago

@fantasai

Example (copied over from a recent duplicate of this issue):

input {
    font-weight:normal;
}
input:modified {
    font-weight:bold;
}

The above would highlight modified inputs with bold font.

I do not see it less useful than :checked or :default.

tomhodgins commented 5 years ago

Oooh what a fun idea! Here's a quick demo of what using :modified in CSS stylesheets might feel like by using el.value !== el.defaultValue logic in JavaScript with a made-up [--modified] selector in CSS that we can grab onto to implement the functionality: https://codepen.io/tomhodgins/pen/YdKveq

input[--modified] {
  background: lime;
}

It's kind of fun, and does fill a current gap with [value=""] and <input> elements between the value and the dirty value.

zoliszabo commented 5 years ago

Given the above linked dirty value, IMHO the best name for this pseudo-class would be :dirty.

The HTML Live Standard defines the dirty flag as

input and textarea elements have a dirty value flag. This is used to track the interaction between the value and default value. If it is false, value mirrors the default value. If it is true, the default value is ignored.

while select elements have the concept of dirtiness on their option descendants:

The dirtiness of an option element is a boolean state, initially false. It controls whether adding or removing the selected content attribute has any effect.

In this case dirtiness should bubble up from the option elements to their parent select element.

devmattrick commented 5 years ago

This would be really useful for creating Material-style inputs that move the label of the input above it. Most implementations I've seen either use JavaScript or take advantage of the required attribute along with the :valid selector, but obviously this isn't ideal.

htstudios commented 5 years ago

šŸ‘ for :dirty (aka different from what has been submitted by the http response)

htstudios commented 5 years ago

... and :modified would NOT be the same.

Modified could mean the same as unix "touched" which could mean the value would be the same but you touched it.

SebastianZ commented 2 years ago

As there was a lot of positive feedback and possible use cases mentioned, I set this as Agenda+.

Sebastian

nt1m commented 2 years ago

With :placeholder-shown, :checked, :indeterminate, etc. I'm not sure what's the appeal for this. But I think the bigger issue I have with this, is what the "default" state is.

zoliszabo commented 2 years ago

With :placeholder-shown, :checked, :indeterminate, etc. I'm not sure what's the appeal for this. But I think the bigger issue I have with this, is what the "default" state is.

There are cases when you want to mark/highlight the input fields that were modified since page load. While this is easy to do for checkboxes/radio buttons (e.g. input[checked]:not(:checked) {...} and input:not([checked]):checked {...}), there isn't such a "straightforward" solution in the case of text inputs, textareas or selects.

In the context of :dirty, "default" would mean the value the input field had when the page was loaded (it might be empty or not). It is a good point, however, that in the case of SPAs there should be a way to "mark" a new value as default.

emilio commented 2 years ago

Wouldn't this be equivalent to :is(:user-valid, :user-invalid) or somesuch?

zoliszabo commented 2 years ago

Wouldn't this be equivalent to :is(:user-valid, :user-invalid) or somesuch?

Would you like to shortly explain how this would work?

emilio commented 2 years ago

https://drafts.csswg.org/selectors-4/#user-pseudos says:

The :user-invalid and the :user-valid pseudo-classes represent an element with incorrect or correct input, respectively, but only after the user has significantly interacted with it.

So :is(:user-valid, :user-invalid) should cover all elements (both correct and incorrect) that the user has interacted with. So it's not exactly a != default, but it seems to address the relevant use-cases which are more on the line of "things the user has interacted with" rather than the specific "it's not the default value".

zoliszabo commented 2 years ago

Thanks! On a quick read I failed to see that :is(:user-valid, :user-invalid) was an actual selector and not an enumeration of the :is(:user-valid) and :is(:user-invalid) selectors.

You are right that :is(:user-valid, :user-invalid) covers all "significantly interacted with" elements, but there is at least one drawback which makes it incompatible with :dirty:

https://drafts.csswg.org/selectors-4/#user-pseudos also says:

For example, the input in the following document fragment would match '':invalid'' as soon as the page is loaded (because it the initial value violates the max-constraint), but it won't match '':user-invalid'' until the user significantly interacts with the element, or attempts to submit the form it's part of.

<form>
<label>
Volume:
<input name='vol' type=number min=0 max=10 value=11>
</label>
...
</form>

Basically the :is(:user-valid, :user-invalid) would kick in on form submit even if there was no interaction with the elements.

This being said, there probably is a combination of :user-valid, :user-invalid, :valid, :invalid that would do the job for each of the most common real-life use-cases, but I would still prefer the more straightforward :dirty šŸ˜€

css-meeting-bot commented 2 years ago

The CSS Working Group just discussed [selectors-4] Issue 11: Introduce pseudo-class matching when user changed the value of an input.

The full IRC log of that discussion <emilio> topic: [selectors-4] Issue 11: Introduce pseudo-class matching when user changed the value of an input
<emilio> github: https://github.com/w3c/csswg-drafts/issues/1533
<fantasai> emilio: I think this would mostly be an alias to a simple :is(:user-valid, :user-invalid)
<fantasai> emilio: but maybe best to have this conversation with Sebastien
<fantasai> fantasai: Comment pointing out a few cases where it's not exactly the same
<emilio> fantasai: It's not exactly what emilio is suggesting, see last comments in the issue
<emilio> bramus: so this is to target inputs, proposed aliases :dirty / :changed / :user-edited
<emilio> ... concept is when a user interacts with the input then it would become dirty
<emilio> fantasai: the other thing to note is that `:user-valid` / `:user-invalid` is that they trigger on submission, but this presumably won't
<tantek> I feel like this has come up in the past but I can't recall what we called it.
<argyle> https://angular.io/guide/form-validation#control-status-css-classes
<emilio> astearns: should we resolve to work on this?
<emilio> bramus: there seems to be a minor difference between :dirty and whether the user touched it
<tantek> q+
<emilio> ... e.g., focusing the input would be touched, changing the value would be dirty
<emilio> q-
<astearns> ack tantek
<emilio> tantek: in general looks like a good area to explore
<fantasai> tantek: This in general sounds like a good area to explore
<emilio> ... I think we have talked about in the past about this but I can't recall where
<emilio> ... one question is what happens with autofill and similar interactions
<argyle> i found these very helpful in the past: https://www.irccloud.com/pastebin/mBDWfxFT/
<emilio> ... does autofilling + clearing it out get special treatment?
<emilio> ... there's a bunch of questions about what other states do we need to design for
<emilio> ... we might want to do some more research on that
<bramus> Good suggestion
<emilio> +1, it seems there's a variety of different use cases that don't quite always match
<flackr> q+
<astearns> ack flackr
<emilio> flackr: I thought the autofilled text isn't exposed till the user interacts with the field
<tantek> I would also advise coordinating this exploration with #openui
<emilio> flackr: which would suggest that naively that before you interact with the page it wouldn't be dirty
<fantasai> emilio: You're right we dont' expose it
<fantasai> emilio: but we do match ?? pseudo-class
<dbaron> some of this may differ between password management autofill and more general autofill, too
<emilio> s/??/:autofill
<tantek> +1 dbaron
<emilio> astearns: I'll raise an issue with openui so that they're aware
<emilio> ... but it seems we have more questions than decissions at this point
<emilio> ... so I wonder if we should take it to the issue again for now
<emilio> ... does that sound ok?
<emilio> [silence]
jensimmons commented 2 years ago

I'm really not a fan of the name :dirty. It seems very inconsistent with the kinds of names in CSS. It feels like it carries a politically problematic judgement. And it does not convey the meaning of the term ā€” itā€™s most important job.

scottaohara commented 2 years ago

seems like this would be useful information to expose beyond just through styles. i'd be concerned if a page exposed a bunch of visually changed fields that indicated "these fields have changed", but there is no programmatic exposure of such for people who can't perceive the style changes. Existing pseudo-class styling like :placeholder-shown, :invalid, :user-valid, :empty, etc., seem like they cover a lot of use cases and are associated with a programmatically exposed state - but just "this was changed from one valid value to another", how might that be relayed?

SebastianZ commented 2 years ago

Sorry that I missed the call! Good points were brought up there! So it obviously needs to be precisely defined what actions cause this pseudo-class to match.

As stated in my initial comment, as a user I'd expect it to match whenever I interacted with the form control and changed the value it had when the page loaded.

I am leaning towards counting autofill to that, as long as the user has chosen the autofill option.

Also, we need to consider how this works in context of single-page applications. In that regard, is the state reset - i.e. the pseudo-class does not match (anymore) - when the value is changed programmatically? I'd say yes.

That's also a reason why :is(:user-valid, :user-invalid) isn't sufficient.

One more question is, what should happen if the user changes the value back to the value that was initially set? I'm unsure about that but I tend to think that the pseudo-class should not match anymore in that case.

Regarding the name, I prefer one that makes it clear that there was some user interaction. This could simply be :user-changed, or :edited. Therefore, I'd exclude :changed as that sounds like user interaction isn't necessary, :dirty because of the reasons @jensimmons mentioned, :user-dirtied for the same reasons, and :user-interacted because "interacted" doesn't mean changed.

Sebastian

jpzwarte commented 1 year ago

So wanted to add my 2 cents: I just ran into a scenario where I have a FACE web component (Form Associated Custom Element) that is required. I want to change the styling of the component when :invalid, but only after the user has interacted with the component.

:host(:invalid:user-interacted) { 
  border-color: red; 
}

Btw, I am more familiar with the term :touched instead of :user-interacted (Angular & angular.js use the term "touched" for example). But perhaps touched can be confusing: it implies a user has touched (using a finger?) the component.

SebastianZ commented 1 year ago

Btw, I am more familiar with the term :touched instead of :user-interacted (Angular & angular.js use the term "touched" for example). But perhaps touched can be confusing: it implies a user has touched (using a finger?) the component.

There are probably many people out there that understand the concept of :touched, though I'd still rule it out for the reason you provided.

Sebastian

jensimmons commented 1 year ago

:invalid:user-interacted

Isn't this usecase already covered by :user-invalid? And it's twin, :user-valid.

jpzwarte commented 1 year ago

@jensimmons Yes, the more I read about it, the more I think :user-invalid is better suited. One question I still have though: what about if you haven't interacted with the form element, but you've called form.requestSubmit(). How would you style the invalid form element after that? Does :user-invalid still apply in that case? (:user-interacted wouldn't either afaict).

jacobrask commented 1 year ago

What some in this issue seem to argue for is rather the :blank selector. To create the Material style inputs with labels that are first positioned inside the input but then animate away, what youā€™re interested in is if the input is blank or not. If the default value is non-empty you also want to move the label.

Developers currently need to use :placeholder-shown in combination with a placeholder attribute with whitespace , which is not intuitive and is one of the few remaining reasons why we need JS for our inputs

scottaohara commented 1 year ago

Does :user-invalid still apply in that case?

if a form attempts to be submitted but a user hasn't filled out required fields, then i don't see why that wouldn't be considered user-invalid. They did not meet all requirements for form submission.

waterplea commented 2 weeks ago

We need both :dirty and :touched. Currently :user-invalid does not trigger when I focus a required field and blur it. This is suboptimal when just tabbing through a form ā€” user should be notified in this case that they missed out on a required value. Combination of those selectors would make it possible.

bramus commented 2 weeks ago

Angular defines the following:

State Description
pristine A control isĀ pristineĀ if the user has not yet changed the value in the UI.
dirty A control isĀ dirtyĀ if the user has changed the value in the UI.
touched True if the control is marked asĀ touched. A control is markedĀ touchedĀ once the user has triggered aĀ blurĀ event on it.
untouched True if the control has not been marked as touched. A control isĀ untouchedĀ if the user has not yet triggered aĀ blurĀ event on it.

These terms also appear in popular libraries such as React Hook Form, see its useFormState hook.

These all seems like reasonable pseudos to add to CSS.

Regarding the relation to user-valid/user-invalid:

Also note that an input can be both touched and pristine in case the user has edited the value, but then changed it back to its original value.

emilio commented 2 weeks ago

If user-invalid has suboptimal behavior we should fix that, imo

waterplea commented 1 week ago

If user-invalid has suboptimal behavior we should fix that, imo

I'm trying here: https://issues.chromium.org/issues/365802576 https://bugs.webkit.org/show_bug.cgi?id=279503

Haven't filed one for Firefox

Having native CSS selectors for controls states would allow better flexibility though, as you can see Chromium guy who implemented it argues that current behavior is correct and in accordance with the specs. Current behavior is unacceptable per our design guidelines, which is the same as how Angular does it ā€” show invalid controls after user visited them, not edited them.

emilio commented 1 week ago

Having native CSS selectors for controls states would allow better flexibility though, as you can see Chromium guy who implemented it argues that current behavior is correct and in accordance with the specs.

That is true. But the html spec could be changed with agreement from vendors and tests.

Current behavior is unacceptable per our design guidelines, which is the same as how Angular does it ā€” show invalid controls after user visited them, not edited them.

That's how Firefox used to do it iirc, before I changed it to follow the current spec.

waterplea commented 1 week ago

The current spec only says 3 things:

  1. It should match "only after the user has significantly interacted with it" without specifying criteria of significance.
  2. It should match after form submit attempt
  3. And "user-agents may allow them to match such elements at other times, as would be appropriate for highlighting an error to the user"

I believe focusing and leaving an empty required input is exactly "appropriate for highlighting an error to the user" šŸ™‚ I would even go as far as to say focusing and leaving any invalid input should highlight it for user, but I can see how this could be up for a debate so it would be great if we could personalize this to our liking with new selectors.