Git Product home page Git Product logo

time-input-polyfill's Introduction

Time Input Polyfill

JS Deliver hits per month

An accessible polyfill for <input type='time'/> elements.

  • ✔️ Modeled after the Chrome 78 and Firefox 70 desktop implementations.
  • ✔️ Fully keyboard and screen reader accessible.
  • ✔️ Submits the same values to servers as real time inputs (24 hour time).
  • ✔️ Only downloads the full polyfill code in the browsers that need it.
  • ✔️ Zero dependencies.

Demo available here: https://dan503.github.io/time-input-polyfill/

The recommended version is 1.0.11 or higher.

If the recommended version in this documentation is out of sync with the npm version, this is because npm only allows readme edits to be committed through full releases. To prevent needless cache invalidation, I'll only update the recommended version number when there are actual changes to the polyfill code. The current recommended version is 1.0.11. As long as you are using a version that is equal to or higher than that, you are using the latest version of the polyfill.

Pre-built components

To make it easier to implement this polyfill into your projects, I have some pre-built component versions of it that you might find easier to use.

Fastest and easiest way to implement

Add the following script element to your page:

<script src="https://cdn.jsdelivr.net/npm/time-input-polyfill"></script>

Alternatively you can download it via npm and use it through commonJS or an ES6 import statement.

npm i time-input-polyfill

Then require it in your main JavaScript file like so:

// ES5
require('time-input-polyfill/auto')

// ES6
import 'time-input-polyfill/auto'

That's all you need to do.

What did I just do?

You didn't load the actual polyfill onto the page, you loaded a much smaller automatic initialiser function instead.

  1. The initialiser checks if the browser supports input[type="time"] elements.
  2. If it does, it skips the rest of the functionality.
  3. If it does not, it will:
    1. load https://cdn.jsdelivr.net/npm/[email protected]/dist/time-input-polyfill.min.js (the actual polyfill).
    2. Collect all existing input[type="time"] elements on the page.
    3. Loop through each input[type="time"] element and apply the polyfill to it.

I need more control

The following downloads the full polyfill in all browsers, take a look at the auto.mjs file if you want to see how it loads the polyfill dynamically.

npm

First check for input[type="time"] support.

import supportsTime from 'time-input-polyfill/supportsTime'

if (!supportsTime) {
	//Apply polyfill here
}

Then gather a list of all input[type="time"] elements on the page, and loop through them to apply the polyfill.

import supportsTime from 'time-input-polyfill/supportsTime'
import TimePolyfill from 'time-input-polyfill'

if (!supportsTime) {
	// Converting to an array for IE support
	const $$inputs = [].slice.call(
		document.querySelectorAll('input[type="time"]')
	)
	$$inputs.forEach(function ($input) {
		new TimePolyfill($input)
	})
}

TimePolyfill in this case will be a function that is only accessible from the file that it was required in.

CDN

First check for input[type="time"] support.

<script src="https://cdn.jsdelivr.net/npm/[email protected]/core/helpers/supportsTime.js"></script>
if (!supportsTime) {
	//Apply polyfill here
}

Then gather a list of all input[type="time"] elements on the page, and loop through them to apply the polyfill.

<script src="https://cdn.jsdelivr.net/npm/[email protected]/core/helpers/supportsTime.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/time-input-polyfill.min.js"></script>
if (!supportsTime) {
	// Converting to an array for IE support
	const $$inputs = [].slice.call(
		document.querySelectorAll('input[type="time"]')
	)
	$$inputs.forEach(function ($input) {
		new TimePolyfill($input)
	})
}

This will add a global TimePolyfill function to the page.

Shadow DOM

When your code is inside a component that resides in the Shadow DOM, the polyfill will not be able to find your label element. For this case, you can pass your label element in directly.

<label id="myLabel" for="timeInput">Label text</label>
<input type="time" id="timeInput" />
import timePolyfill from 'time-input-polyfill'

// The following element must not be in a shadow DOM
const componentRootElem = document.getElementById(
	'idOfYourShadowDomComponentRootElement'
)

const timeLabelElem = componentRootElem.shadowRoot.getElementById('myLabel')
const timeInputElem = componentRootElem.shadowRoot.getElementById('timeInput')
timePolyFill(timeInputElem, timeLabelElem)

Major limitations

Note that I refer to an input[type="time"] element that has had the polyfill initialized on it as an $input in this section.

The value of the polyfill will be different to the value of the real input

In browsers that support the time input natively, they will provide the value of the input in 24 hour time (eg. 20:45). The polyfill will provide the value in 12 hour time (08:45 PM). If the polyfill detects that a form is being submitted, the polyfill will quickly switch to 24 hour time in an attempt to align with standard time input functionality.

If this isn't working for you, you can prevent the auto-swap functionality by setting $input.polyfill.autoSwap = false. You can access the current input value in 24 hour time format by reading the data-value attribute.

You can also switch the $input manually to 24 hour time using $input.polyfill.swap(). The polyfill does not function properly at the moment while running in 24 hour time. 24 hour time is only meant to be used as a means of submitting correct values to forms. It is not intended to be used as a permanent mode.

You must call $input.polyfill.update() on dynamic inputs

I couldn't find any reliable way to track when a user uses $input.value = '13:30'. So instead of tracking the use of $input.value, I have attached a .polyfill.update() method to the $input element.

Any time you update the value of the time input element through JavaScript, check that $input.polyfill exists, and if it does, call $input.polyfill.update().

<input id="example" type="time" />
const $input = document.getElementByID('example')
$input.value = '13:30'
// call the update() method whenever the value is updated through JS
if ($input.polyfill) $input.polyfill.update()

The update() method will return the input element that it was called on so it can be chained if you want.

All $input elements must have a label

The polyfill will fail if the $input is missing a label.

The following is a list of ways you can add a label to the $input. The list is in order from the best method to the worst method:

  1. Using the for attribute
    <label for="uniqueID">Label text</label> <input type="time" id="uniqueID" />
  2. Using the aria-labelledby attribute
    <p id="uniqueID">Label text</p>
    <input type="time" aria-labelledby="uniqueID" />
  3. Nesting the $input inside a <label> (Doesn't require IDs to work but not supported by all screen readers)
    <label>
    	<span>Label text</span>
    	<input type="time" />
    </label>
  4. Using the title attribute
    <input type="time" title="Label text" />
  5. Using the aria-label attribute
    <input type="time" aria-label="Label text" />

Change log

You can view the Change Log on the GitHub Releases page.

Contributing

Please make pull requests against Develop branch rather than Master.

For testing you will need Gulp cli installed (npm i gulp-cli -g) then run gulp --open from a command line interface.

time-input-polyfill's People

Contributors

dan503 avatar russcarverifs avatar seanhealy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

time-input-polyfill's Issues

Error if labels are not used is unclear

I'm trying to use this library in Safari and running into this error: undefined is not an object (evaluating 'l(e).textContent'). Points me to index.js:29 which says:

Screen Shot 2019-05-06 at 6 25 21 PM

Any thoughts? I've added the package through CDN and imported it into my JS. Would appreciate any help!

value="12:30" results in "12:30 AM" being displayed

Thanks for this polyfill. It is almost perfect, but the one issue I see is a showstopper for me. In Safari, there does not seem to be a way to set a value on an input that will result in times within the 12pm hour. Or perhaps I'm doing it wrong. The result is always an input displaying 12am.

Here is a JSBin demonstrating the issue (when viewed in Safari).
https://jsbin.com/huyowaxeza/edit?html,output

I read in another issue that you don't have access to Safari, so this is probably not going anywhere, but am reporting anyway. Thanks.

The Polyfill doesn't work inside the shadow DOM

I was using this in my Angular project and we've been converting all of our components to use the shadow DOM since we dropped support for IE11. This polyfill doesn't support that. I tried all of the documented ways to combine label & input, but they all assume that the label is accessible via "document.get..." of some type. With the shadow DOM you need "document.getElementById().shadowRoot.getElementById()". The "shadowRoot" here is the key. And if your component is nested inside another component that also uses the shadow DOM, then you have to use "shadowRoot" yet another time.

It would be far easier to simply pass in the label element - like you allow with the input element - via the 'timePolyFill' function. I brought your code directly into my project and modified it to do just that in order to work with Safari.

Here are the relevant bits (code is in TypeScript):

<label id="myLabel"></label>
<input #time type="time" id="timeInput" formControlName="contentTime">
               
               
import * as timePolyFill from 'lib/time-input-polyfill';

public constructor(private elemRef: ElementRef) {

private someMethod(): void {
  const timeLabelElem: HTMLElement = this.elemRef.nativeElement.shadowRoot.getElementById('myLabel');
  const timeInputElem: HTMLElement = this.elemRef.nativeElement.shadowRoot.getElementById('timeInput');
  timePolyFill(timeInputElem, timeLabelElem);
}

And from the polyfill code:
index.js (just the relevant parts):

function TimePolyfill($input, $labelElem) {

...

  var label;
  if ($labelElem) {
    label = $labelElem.textContent;
  } else {
    label = get_label($input)
  }

...

}

Not working in Firefox

Steps to reproduce

  1. Open Firefox 92.0.1 (64-bit)
  2. Go to example page.
  3. Click on "Polyfill time input" or "Polyfill with value" input.

Result

Behaves exactly the same as real inputs above the corresponding polyfill ones.

Expected behaviour

Opens time picker (somewhat like this date picker).

webpack 5 incompatible unless configuring fullyResolved of false

When upgrading from Webpack 4 to 5, I received a number of errors from this library

ERROR in ./node_modules/time-input-polyfill/index.mjs 1:0-56
Module not found: Error: Can't resolve './core/setters/apply_default' in '/Users/REDACTED/code/REDACTED/frontend/node_modules/time-input-polyfill'
Did you mean 'apply_default.js'?
BREAKING CHANGE: The request './core/setters/apply_default' failed to resolve only because it was resolved as fully specified
(probably because the origin is a '*.mjs' file or a '*.js' file where the package.json contains '"type": "module"').
The extension in the request is mandatory for it to be fully specified.
Add the extension to the request.

Other files that had errors (abbreviated, in general similar to above):

Module not found: Error: Can't resolve './core/setters/update_time' in '/Users/REDACTED/code/REDACTED/frontend/node_modules/time-input-polyfill'
Did you mean 'update_time.js'?

Module not found: Error: Can't resolve './core/setters/set_data_attribute' in '/Users/REDACTED/code/REDACTED/frontend/node_modules/time-input-polyfill'
Did you mean 'set_data_attribute.js'?

Module not found: Error: Can't resolve './core/events/bind_events' in '/Users/REDACTED/code/REDACTED/frontend/node_modules/time-input-polyfill'
Did you mean 'bind_events.js'?

Module not found: Error: Can't resolve './core/setters/switch_times' in '/Users/REDACTED/code/REDACTED/frontend/node_modules/time-input-polyfill'
Did you mean 'switch_times.js'?

Module not found: Error: Can't resolve './core/getters/get_label' in '/Users/REDACTED/code/REDACTED/frontend/node_modules/time-input-polyfill'
Did you mean 'get_label.js'?

Module not found: Error: Can't resolve './core/accessibility/create_a11y_block' in '/Users/REDACTED/code/REDACTED/frontend/node_modules/time-input-polyfill'
Did you mean 'create_a11y_block.js'?

For now, this workaround was successful for me on the latest 1.0.10: graphql/graphql-js#2721 (comment)

Note that upgrading from graphql 14 -> 15 removes the requirement to add fullyResolved: false to Webpack config. I believe a similar fix could be made to this library by following the Webpack suggestion to add explicit extensions to the imports.

Selection doesn't work on safari

Hi Dan!

It appears that prevent_user_select also prevents selection highlighting from working in Safari, I was wondering the reasoning behind this function?

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.