whatwg / html

HTML Standard
https://html.spec.whatwg.org/multipage/
Other
8k stars 2.62k forks source link

Proposal: Add a way to open programmatically a date picker #6909

Closed beaufortfrancois closed 2 years ago

beaufortfrancois commented 3 years ago

Background:

Developers have been asking for years for a way to programmatically open a browser date picker. See https://www.google.com/search?q=programmatically+open+date+picker+site:stackoverflow.com Because of that, they had to rely on custom widget libraries and CSS hacks for specific browsers.

Date pickers in Chrome Desktop, Chrome Mobile, Safari Desktop, Safari Mobile, and Firefox Desktop (July 2021).

Proposal

A new showPicker() method to the HTMLInputElement interface would show a browser picker (if supported) for <input type="date">, <input type="month">, <input type="week">, <input type="datetime-local">, and <input type="time"> HTML elements . This method is handy, easy to use, and friendly to discover.

As it's already possible to listen tochange events to know when the HTML element value changes, I believe it's OK to make this method synchronous.

Example usage

// Show a date picker
const inputDateElement = document.querySelector('input[type="date"]');
inputDateElement.showPicker();

Feature detection

if ("showPicker" in HTMLInputElement.prototype) {
  ... 
}

Open questions

  1. Should we restrict the number of date pickers opened?
  2. Date pickers are modal on Mobile. They're not modal on Desktop. Should we require a user activation?

Alternative considered

Resources

tomayac commented 3 years ago

Should this also come along with an open attribute, similar to dialog?

domenic commented 3 years ago

show a browser picker (if supported) for <input type="date">, <input type="month">, <input type="week">, <input type="datetime-local">, and <input type="time">

Should this explicitly only work for these inputs? Other inputs which have pickers in some browsers are tel/email (contact picker), url (history picker), password (password manager), number, color, and file.

annevk commented 3 years ago

Also <select>.

(The other thing I was thinking about, which is somewhat off-topic, is that you might want to display the picker exclusively as some sites do (i.e., without the input box), but perhaps that's best left to libraries as there's often additional requirements.)

beaufortfrancois commented 3 years ago

Should this also come along with an open attribute, similar to dialog?

I'm not sure. According to the spec, a dialog element without an open attribute specified should not be shown to the user. Shall we then have an "open" attribute for HTMLInputElement that prevents user to show a picker? I don't think so. Thoughts?

beaufortfrancois commented 3 years ago

show a browser picker (if supported) for <input type="date">, <input type="month">, <input type="week">, <input type="datetime-local">, and <input type="time">

Should this explicitly only work for these inputs? Other inputs which have pickers in some browsers are tel/email (contact picker), url (history picker), password (password manager), number, color, and file.

FYI There's a JS API already for Contact picker

Currently tel, email, url, password, and number have no pickers from what I can see. color however seems like a good candidate that would benefit from this.

Having showPicker() for file may not be appropriate as we already have showOpenFilePicker().

beaufortfrancois commented 3 years ago

Also <select>.

We'd have to find out if that's something developers need.

domenic commented 3 years ago

In Chrome I agree those do not have pickers. In other browsers they do. (In some cases historical browsers like Opera.)

Yay295 commented 3 years ago

Having showPicker() for file may not be appropriate as we already have showOpenFilePicker().

Not supported in Firefox or Safari though. And we could still have a more general method even with an existing method for a specific case. showOpenFilePicker() is async though, and it returns the file that was picked. So if we want the generic method to work like showOpenFilePicker(), it should also be async and return whatever was picked.

domenic commented 3 years ago

I was given an action item to see what click() does today to guide this discussion. It turns out interop here is a bit messy. Test page: https://boom-bath.glitch.me/pickers.html . Results:

Element Firefox (desktop) Firefox (mobile) Chrome (desktop) Chrome (mobile) Safari (desktop) Safari (mobile)
<input type="date"> * * *
<input type="month"> * *
<input type="week"> *
<input type="time"> * *
<input type="datetime-local"> * *
<input type="color"> *
<input type="file">
<select> * *
<select multiple> * *

❗ means the picker does open, on both programmatic click() and when the user clicks on the <label>'s text.

* means the picker does not open via programmatic click(), but it does open by the user click on the <label>'s text.

Blank means neither clicking on the label nor programmatic click() will open the picker.

E.g. on mobile Chrome, the user clicking on the label text for <input type="date"> will open the picker, but the user clicking on the label text for <select> will not open the picker. And on desktop Chrome, the user clicking on the label only opens the picker for <input type="file"> and <input type="color">.

None of the elements in "Not very likely to have a picker, but could conceivably", besides sometimes besides <select multiple>, ever show pickers, so I omit them from above.

Thankfully nobody showed pickers on focus().


I can't think of anything unifying these. E.g. you might think Chrome's approach is "display fullscreen pickers, don't display anchored pickers", but <select> on mobile is a fullscreen picker and not displayed, whereas <input type="color"> on desktop is an achored picker and is displayed.

Here's my proposal:

We could also contemplate not special-casing <input type="color">, as I suspect fewer people are relying on it showing the picker than <input type="file">, but probably that's not worth the risk.

beaufortfrancois commented 3 years ago

Thank you @domenic for the thorough investigation.

Here are my results on Safari Mobile. Feel free to edit your post earlier.

Element Safari (mobile)
<input type="date">
<input type="month">
<input type="week">
<input type="time">
<input type="datetime-local">
<input type="color">
<input type="file">
<select>
domenic commented 3 years ago

Thanks @beaufortfrancois! You inspired me to borrow my girlfriend's iPhone and do some of my own testing on the <label> issue. I've updated the above with full results, and also split out Firefox desktop/mobile since they are sufficiently different on the <label> issue as well.

domenic commented 3 years ago

New test page with no user activation: https://boom-bath.glitch.me/pickers-2.html

Results:

Conclusion: we can definitely impose a user-activation restriction on both the activation behavior and the new showPicker() method.

domenic commented 3 years ago

New test page where the pickers are in a cross-origin iframe: https://creative-abrupt-bladder.glitch.me/

Results: on desktop Firefox and desktop Chrome, at least, there is no difference in behavior from the first-party case. I.e. you can open file and color pickers with a user-activation click.

Conclusion: it would be a web compat lift to lock down cross-origin iframe color and file pickers. So although that's a good idea, I think we should pursue it as a separate issue, since I suspect it'd cause a lot of web developer pain (e.g. third-party file picker widgets).

We could make the new showPicker() API be locked down even if click() is not. Since for non-color/file inputs, it is presenting a new capability. But I think we should still allow showPicker() to work in cross-origin iframes for color and file, as otherwise people will just have to use click() more often. We can work on locking down both at once in the future.

annevk commented 2 years ago

I like the idea of restricting the new capability where it makes sense to do so.

beaufortfrancois commented 2 years ago

@domenic Is there anything I can do to help moving this forward?

domenic commented 2 years ago

@beaufortfrancois I'll work on the spec soon, but a few things could help from the implementation side:

beaufortfrancois commented 2 years ago
  • On desktop the picker should open at a sensible location (not (0,0)) for things like color or date, like click() does currently.

If the HTML input element is already in the DOM, it makes sense to open where the element is. However if the element is not in the DOM already, (0,0) is where it opens by default. Do you want to change that behavior?

domenic commented 2 years ago

Interesting! Let me test what all browsers do in that case.

beaufortfrancois commented 2 years ago
  • Work on a prototype for showPicker():

Here's the WIP Chromium CL for the new showPicker() method: https://chromium-review.googlesource.com/c/chromium/src/+/3056920

domenic commented 2 years ago

In the process of prototyping @beaufortfrancois pointed out that <select> uses HTMLSelectElement and not HTMLInputElement. So there's a question as to whether we should define showPicker() for both of these, or just HTMLInputElement.

Although IMO treating <select> and <input> as on equal footing is nice theoretically, and I suspect web developers probably want the ability to open select popups as well, my instinct is to put off work on showPicker() for select for now pending more developer feedback and successfully landing it for HTMLInputElement:

Happy to discuss more here or in the triage call. In particular it'd be interesting to hear peoples thoughts between: do select.showPicker() at the same time as input.showPicker(), do select.showPicker() shortly after input.showPicker() once we have a successful spec/implementation for the former, do select.showPicker() only after gathering web dev sentiment to ensure it's useful, or never do select.showPicker().

beaufortfrancois commented 2 years ago

Interesting! Let me test what all browsers do in that case.

Here are my results:

Click() on the element outside of the DOM Safari (desktop) Firefox (desktop) Chrome (desktop) Safari (mobile) Firefox (mobile) Chrome (mobile)
<input type="color"> - Opens at the bottom left corner of the screen Opens at top left of the page - Opens modal dialog Opens modal dialog
<input type="file"> Opens modal dialog at the center of the page Opens modal dialog at the center of the page Opens modal dialog at the center of the page Opens small popup dialog where user clicked Opens bottom modal dialog Open bottom modal dialog
domenic commented 2 years ago

Thanks. I guess those results made me realize it's kind of device- and situation-dependent. So probably the spec will not actually say anything about the location of the picker, or if it does it'll say something vague like "associated with the control, if the control is being rendered".

beaufortfrancois commented 2 years ago

FYI The showPicker() method is now available in Chrome Canary 🐣 so you can try it and file feedback.

<button>Show Date Picker</button>
<script>
  const button = document.querySelector("button");
  button.addEventListener("click", () => {

    try {
      const inputDate = document.createElement("input");
      inputDate.type = "date";

      // Show programmatically the browser date picker.
      inputDate.showPicker();

      // Get date entered by user.
      inputDate.addEventListener("change", event => {
        console.log(`Got ${event.target.value} date`);
      });
    } catch (error) {
      // Use custom library as a fallback...
    }
  });
</script>

See https://twitter.com/quicksave2k/status/1457650699832004609

mathiasbynens commented 2 years ago

Do we expect all use cases to rely on manual closing of the picker? If not, we could consider adding closePicker as well.

beaufortfrancois commented 2 years ago

Thanks for the feedback @mathiasbynens!

The browser closes automatically the picker as soon as this one loses focus (when user clicks outside for instance). I'm not sure then how a closePicker() would be used. Did you have something specific in mind?

mathiasbynens commented 2 years ago

I could e.g. imagine a scenario with multiple <input>s, some of which triggering a browser-native picker, others triggering a custom picker implementation, through interaction with some other DOM element. It seems worthwhile to allow custom and native picker UIs to co-exist, with any native ones getting closed as soon as a custom one opens, regardless of what triggers the opening. It sounds like this is already handled for the common case where the user has to click somewhere outside the native picker to trigger the other one, and maybe that’s good enough!

To be clear, I’m not proposing / advocating for closePicker — I just wanted to make sure its exclusion was not an oversight but an intentional choice. 👍

domenic commented 2 years ago

I've posted https://github.com/whatwg/html/pull/7319 with a spec PR. Review appreciated!

miketaylr commented 2 years ago

Reposting a comment from blink-dev: https://groups.google.com/a/chromium.org/g/blink-dev/c/fEebe5uXQ1I/m/NWvUs_QEAgAJ

tl;dr - given the EyeDropper API proposal, maybe there's a chance to align on method names that open a picker / UI widget? Was open considered instead of showPicker?

domenic commented 2 years ago

We haven't had much discussion about the naming, so thanks for starting it.

Personally, I don't feel strongly about the name. But some background:

The showPicker() name is chosen to align with window.showOpenFilePicker(). I'll also note there's some unresolved feedback on the EyeDropper issue tracker asking if maybe it should become window.showColorPicker(): https://github.com/WICG/eyedropper-api/issues/18

I do think fileInput.open() and dateInput.open() are a bit less clear than fileInput.showPicker() and dateInput.showPicker(), but not by much.

Curious to hear what others think.

smaug---- commented 2 years ago

open() sounds a bit odd for a method on an input element. It isn't immediately clear to the reader what it would do. showPicker() is quite obvious.

(and since EyeDropper API is just a proposal, it could be changed to have showPicker?)

miketaylr commented 2 years ago

I guess it could go the other way, yeah - I also don't have strong feelings on naming, but consistency would be nice since both of these are new.

Yay295 commented 2 years ago

On a file input I could see people thinking .open() opens the file.

miketaylr commented 2 years ago

For an "empty" <input type=file>, there's no file to open. I guess I wouldn't expect any different behavior with a non-empty files/value.

beaufortfrancois commented 2 years ago

I don't feel strongly either about the naming of this method and I agree some consistency would be great as well for web developers.

FYI I've left a comment at https://github.com/WICG/eyedropper-api/issues/18#issuecomment-964866716 to let EyeDropper folks know about this issue.

mnoorenberghe commented 2 years ago

Developers have also been asking for the ability to open suggestions for <input list="…"> with <datalist> and this API could also work for it:

domenic commented 2 years ago

That's a great point. I forgot to test those. IMO showPicker() should definitely open those pickers; I'll see if we can update the spec to be more explicit about that in some way.

beaufortfrancois commented 2 years ago

I have a WIP Chromium CL at https://chromium-review.googlesource.com/c/chromium/src/+/3284804 to open those pickers. Good catch @mnoorenberghe!

beaufortfrancois commented 2 years ago

@mnoorenberghe https://chromiumdash.appspot.com/commit/92e5fe8556d41ba34766129574df624ea2413ffa has landed. Give https://show-picker.glitch.me/ a try in Chrome Canary 98 98.0.4726.0. You'll need to enable the experimental web platform features flag for this.

beaufortfrancois commented 2 years ago

FYI I've just sent an intent to ship at blink-dev: https://groups.google.com/a/chromium.org/g/blink-dev/c/YfnM0ubs53k

ziransun commented 2 years ago

@domenic @beaufortfrancois Any chance to help with clarifying the question at https://bugs.webkit.org/show_bug.cgi?id=237192#c19? This WebKit patch is an import of the Chromium showpicker() CL. Thanks.

beaufortfrancois commented 2 years ago

Is it correct that when called on an HTMLInputElement of a type that has no picker, that showPicker should check cross-frame and user gesture status, throw exceptions if either of those applies, but then if neither applies, silently do nothing?

As you can see below, in Chromium showPicker will throw security error if type is not file nor color. However it will check user gesture status for all types. See https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/html/forms/html_input_element.cc;l=2218;drc=72575413b1662ce93e9137f99303d0d5a3942f0f

void HTMLInputElement::showPicker(ExceptionState& exception_state) {
  LocalFrame* frame = GetDocument().GetFrame();
  if (type() != input_type_names::kFile && type() != input_type_names::kColor &&
      frame) {
    const SecurityOrigin* security_origin =
        frame->GetSecurityContext()->GetSecurityOrigin();
    const SecurityOrigin* top_security_origin =
        frame->Tree().Top().GetSecurityContext()->GetSecurityOrigin();
    if (!security_origin->IsSameOriginWith(top_security_origin)) {
      exception_state.ThrowSecurityError(
          "HTMLInputElement::showPicker() called from cross-origin iframe.");
      return;
    }
  }

  if (!LocalFrame::HasTransientUserActivation(frame)) {
    exception_state.ThrowDOMException(
        DOMExceptionCode::kNotAllowedError,
        "HTMLInputElement::showPicker() requires a user gesture.");
    return;
  }

  input_type_view_->OpenPopupView();
}
ziransun commented 2 years ago

As you can see below, in Chromium showPicker will throw security error if type is not file nor color. However it will check user gesture status for all types.

Thanks @beaufortfrancois for your prompt response. Yes, this is what we have in WebKit imported code as well. What we are trying to clarify here is, we check on cross-frame and user gesture status and throw exceptions if either of those applies. What is neither applies, silently do nothing?

As per https://github.com/whatwg/html/issues/6909#issuecomment-956747809, it mentioned that "It should not consume user activation.". Does it mean that silently doing nothing is intentional?

beaufortfrancois commented 2 years ago

That is my understanding according to https://html.spec.whatwg.org/multipage/input.html#dom-input-showpicker

The showPicker() method steps are:

  1. If this's relevant settings object's origin is not same origin with this's relevant settings object's top-level origin, and this's type attribute is not in the File Upload state or Color state, then throw a "SecurityError" DOMException.
  2. If this's relevant global object does not have transient activation, then throw a "NotAllowedError" DOMException.
  3. Show the picker, if applicable, for this.

Let's wait on @domenic answer as well.

domenic commented 2 years ago

Yes, that is correct.

ziransun commented 2 years ago

3. Show the picker, if applicable, for this.

Thanks both. Just to confirm - If not applicable, do nothing?

domenic commented 2 years ago

Yes, if you read the algorithm for "show the picker, if applicable", step 4 says

Otherwise, the user agent should show any relevant user interface for selecting a value for element, in the way it normally would when the user interacts with the control. (If no such UI applies to element, then this step does nothing.)

ziransun commented 2 years ago

Yes, if you read the algorithm for "show the picker, if applicable", step 4 says

Otherwise, the user agent should show any relevant user interface for selecting a value for element, in the way it normally would when the user interacts with the control. (If no such UI applies to element, then this step does nothing.)

Perfect. Thanks very much!