Duet Date Picker is an open source version of Duet Design System’s accessible date picker. Try live example at https://duetds.github.io/date-picker/

Overview

CI Status NPM Version MIT License code style: prettier

Duet Date Picker

Duet Date Picker is an open source version of Duet Design System’s accessible date picker. Duet Date Picker can be implemented and used across any JavaScript framework or no framework at all. We accomplish this by using standardized web platform APIs and Web Components.

Why yet another date picker? Our team working on Duet Design System couldn’t find an existing date picker that would’ve ticked all the requirements we had for accessibility (supporting WCAG 2.1 as well as we can), so we decided to build one and open source it so that others could benefit from this work as well.

Duet Date Picker comes with built-in functionality that allows you to set a minimum and a maximum allowed date. These settings can be combined or used alone, depending on the need. Please note that the date values must be passed in IS0-8601 format: YYYY-MM-DD.

Read getting started instructions ›
Learn more about Duet ›

Duet Date Picker

Sections in this documentation:

  1. Introduction
  2. Live demo
  3. Features
  4. Browser support
  5. Screen reader support
  6. Keyboard support
  7. Getting started
  8. Properties
  9. Events
  10. Methods
  11. Installation
  12. Usage with basic HTML
  13. Usage with Angular
  14. Usage with Vue.js
  15. Usage with React
  16. Usage with Ember
  17. IE11 and Edge 17/18 polyfills
  18. Using events
  19. Theming
  20. Localization
  21. Server side rendering
  22. Single file bundle
  23. Optimizing CDN performance
  24. Contributing
  25. Changelog
  26. Roadmap
  27. License

Live demo

Features

  • Can be used with any JavaScript framework.
  • No external dependencies.
  • Weighs only ~10kb minified and Gzip’ed (this includes all styles and icons).
  • Built with accessibility in mind.
  • Supports all modern browsers and screen readers.
  • Additionally, limited support offered for IE11 and Edge 17+.
  • Allows theming using CSS Custom Properties.
  • Support for localization.
  • Customizable date parsing and formatting.
  • Support for changing the first day of the week.
  • Comes with modified interface for mobile devices to provide better user experience.
  • Supports touch gestures for changing months and closing the picker.
  • Built using Stencil.js and Web Components.
  • Free to use under the MIT license.

Browser support

  • Google Chrome 61+
  • Apple Safari 10+
  • Firefox 63+
  • Microsoft Edge 17+
  • Opera 63+
  • Samsung Browser 8.2+
  • Internet Explorer 11

Screen Reader support

We offer support for the following screen readers. For more information about the level of support and possible issues with the implementation, please refer to the included accessibility audit.

  • VoiceOver on macOS and iOS
  • TalkBack on Android
  • NVDA on Windows
  • Jaws on Windows

Keyboard support

Duet Date Picker’s keyboard support is built to closely follow W3C Date Picker Dialog example with some small exceptions to e.g. better support iOS VoiceOver and Android TalkBack.

Choose date button

  • Space, Enter: Opens the date picker dialog and moves focus to the first select menu in the dialog.

Date picker dialog

  • Esc: Closes the date picker dialog and moves focus back to the “choose date” button.
  • Tab: Moves focus to the next element in the dialog. Please note since the calendar uses role="grid", only one button in the calendar grid is in the tab sequence. Additionally, if focus is on the last focusable element, focus is next moved back to the first focusable element inside the date picker dialog.
  • Shift + Tab: Same as above, but in reverse order.

Date picker dialog: Month/year buttons

  • Space, Enter: Changes the month and/or year displayed.

Date picker dialog: Date grid

  • Space, Enter: Selects a date, closes the dialog, and moves focus back to the “Choose Date” button. Additionally updates the value of the Duet Date Picker input with the selected date, and adds selected date to “Choose Date” button label.
  • Arrow up: Moves focus to the same day of the previous week.
  • Arrow down: Moves focus to the same day of the next week.
  • Arrow right: Moves focus to the next day.
  • Arrow left: Moves focus to the previous day.
  • Home: Moves focus to the first day (e.g Monday) of the current week.
  • End: Moves focus to the last day (e.g. Sunday) of the current week.
  • Page Up: Changes the grid of dates to the previous month and sets focus on the same day of the same week.
  • Shift + Page Up: Changes the grid of dates to the previous year and sets focus on the same day of the same week.
  • Page Down: Changes the grid of dates to the next month and sets focus on the same day of the same week.
  • Shift + Page Down: Changes the grid of dates to the next year and sets focus on the same day of the same week.

Date picker dialog: Close button

  • Space, Enter: Closes the dialog, moves focus to “choose date” button, but does not update the date in input.

Getting started

Integrating Duet Date Picker to a project without a JavaScript framework is very straight forward. If you’re working on a simple HTML page, you can start using Duet Date Picker immediately by adding these tags to the <head>:

<script type="module" src="https://cdn.jsdelivr.net/npm/@duetds/[email protected]/dist/duet/duet.esm.js"></script>
<script nomodule src="https://cdn.jsdelivr.net/npm/@duetds/[email protected]/dist/duet/duet.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@duetds/[email protected]/dist/duet/themes/default.css" />

Once included, Duet Date Picker can be used in your markup like any other regular HTML element:

<label for="date">Choose a date</label>
<duet-date-picker identifier="date"></duet-date-picker>

Please note: Importing the CSS file is optional and only needed if you’re planning on using the default theme. See theming section for more information. Additionally, while the above method is the easiest and fastest way to get started, you can also install Duet Date Picker via NPM. Scroll down for the installation instructions.

Properties

Property Attribute Description Type Default
dateAdapter -- Date adapter, for custom parsing/formatting. Must be object with a parse function which accepts a string and returns a Date, and a format function which accepts a Date and returns a string. Default is IS0-8601 parsing and formatting. DuetDateAdapter isoAdapter
direction direction Forces the opening direction of the calendar modal to be always left or right. This setting can be useful when the input is smaller than the opening date picker would be as by default the picker always opens towards right. "left" | "right" "right"
disabled disabled Makes the date picker input component disabled. This prevents users from being able to interact with the input, and conveys its inactive state to assistive technologies. boolean false
firstDayOfWeek first-day-of-week Which day is considered first day of the week? 0 for Sunday, 1 for Monday, etc. Default is Monday. DaysOfWeek.Friday | DaysOfWeek.Monday | DaysOfWeek.Saturday | DaysOfWeek.Sunday | DaysOfWeek.Thursday | DaysOfWeek.Tuesday | DaysOfWeek.Wednesday DaysOfWeek.Monday
identifier identifier Adds a unique identifier for the date picker input. Use this instead of html id attribute. string ""
localization -- Button labels, day names, month names, etc, used for localization. Default is English. { buttonLabel: string; placeholder: string; selectedDateMessage: string; prevMonthLabel: string; nextMonthLabel: string; monthSelectLabel: string; yearSelectLabel: string; closeLabel: string; keyboardInstruction: string; calendarHeading: string; dayNames: DayNames; monthNames: MonthsNames; monthNamesShort: MonthsNames; } defaultLocalization
max max Maximum date allowed to be picked. Must be in IS0-8601 format: YYYY-MM-DD. This setting can be used alone or together with the min property. string ""
min min Minimum date allowed to be picked. Must be in IS0-8601 format: YYYY-MM-DD. This setting can be used alone or together with the max property. string ""
name name Name of the date picker input. string "date"
role role Defines a specific role attribute for the date picker input. string undefined
required required Should the input be marked as required? boolean false
value value Date value. Must be in IS0-8601 format: YYYY-MM-DD. string ""

Events

Event Description Type
duetBlur Event emitted the date picker input is blurred. CustomEvent<{ component: "duet-date-picker"; }>
duetChange Event emitted when a date is selected. CustomEvent<{ component: "duet-date-picker"; valueAsDate: Date; value: string; }>
duetFocus Event emitted the date picker input is focused. CustomEvent<{ component: "duet-date-picker"; }>
duetOpen Event emitted when the date picker modal is opened. CustomEvent<{ component: "duet-date-picker"; }>
duetClose Event emitted the date picker modal is closed. CustomEvent<{ component: "duet-date-picker"; }>

Methods

hide(moveFocusToButton?: boolean) => Promise<void>

Hide the calendar modal. Set moveFocusToButton to false to prevent focus returning to the date picker's button. Default is true.

Returns

Type: Promise<void>

setFocus() => Promise<void>

Sets focus on the date picker's input. Use this method instead of the global focus().

Returns

Type: Promise<void>

show() => Promise<void>

Show the calendar modal, moving focus to the calendar inside.

Returns

Type: Promise<void>

Installation

Before moving further, please make sure you have Node.js installed on your machine. You can install the latest version through their website. If you’re planning on using Duet Date Picker in a project that doesn’t yet use Node Package Manager, you’ll have to first create a package.json file. To do so, run npm init and follow the steps provided.

Once finished, you can install Duet Date Picker by running:

# WEB COMPONENT for HTML, Ember, Vue.js, React, Angular and Vanilla JS:
npm install @duetds/date-picker

Usage with basic HTML

Please note: We recommend the usage of CDN like JSDelivr over the below approach if you’re not server side rendering Duet Date Picker. See getting started section to find the correct script tags.

Once you’ve installed @duetds/date-picker package into your project, it’s recommended to create a copy task that copies Duet Date Picker component from node_modules to a location you’ve specified. One such tool that can do this is NCP. You can install ncp by running:

npm install ncp --save-dev

Once installed, add a script to your package.json that copies the component library from Duet’s package into a location you’ve specified:

"scripts": {
  "copy:duet-date-picker": "ncp node_modules/@duetds/date-picker/dist src/SPECIFY_PATH"
}

You can call this script while starting up your app to make sure you’ve always got the latest code copied over. If you’re using an UNIX-like environment, you can use & as the separator:

"start": "copy:duet-date-picker & dev"

Otherwise, if you need a cross-platform solution, use npm-run-all module:

"start": "npm-run-all copy:duet-date-picker dev"

Once you have a copy task in place and have copied Duet Date Picker over, you can put tags similar to these in the <head> of your index.html (importing the CSS file is optional and only needed if you’re planning on using the default theme. See theming section for more information):

<script type="module" src="SPECIFY_YOUR_PATH/duet.esm.js"></script>
<script nomodule src="SPECIFY_YOUR_PATH/duet.js"></script>
<link rel="stylesheet" href="SPECIFY_YOUR_PATH/duet.css" />

Once included, Duet Date Picker can be used in your basic HTML markup as in the following example:

<label for="date">Choose a date</label>
<duet-date-picker identifier="date"></duet-date-picker>

To know when this tag name becomes defined, you can use window.customElements.whenDefined(). It returns a Promise that resolves when the element becomes defined:

customElements.whenDefined("duet-date-picker").then(() => {
  document.querySelector("duet-date-picker").show()
});

Usage with Angular

Before you can use Duet Date Picker in Angular, you must import and add Angular’s CUSTOM_ELEMENTS_SCHEMA. This allows the use of Web Components in HTML markup, without the compiler producing errors. The CUSTOM_ELEMENTS_SCHEMA needs to be included in any module that uses custom elements. Typically, this can be added to AppModule:

// ...
// Import custom elements schema
import { CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";

@NgModule({
  // ...
  // Add custom elements schema to NgModule
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule { }

The final step is to load and register Duet Date Picker in the browser. @duetds/date-picker includes a main function that handles this. That function is called defineCustomElements() and it needs to be called once during the bootstrapping of your application. One convenient place to do this is in main.ts as such:

// Import Duet Date Picker
import { defineCustomElements } from "@duetds/date-picker/dist/loader";
// ...
// Register Duet Date Picker
defineCustomElements(window);

Once included, Duet Date Picker can be used in your HTML markup as in the following example:

<label for="date">Choose a date</label>
<duet-date-picker identifier="date"></duet-date-picker>

Please note that you need to also import duet.css separately if you want to use the default theme. See theming section for more information.

Accessing using ViewChild and ViewChildren

Once included, components could also be referenced in your code using ViewChild and ViewChildren as shown in the Stencil.js documentation.

Usage with Vue.js

To integrate @duetds/date-picker into a Vue.js application, edit src/main.js to include:

// Import Duet Date Picker
import { defineCustomElements } from "@duetds/date-picker/dist/loader";

// ...
// configure Vue.js to ignore Duet Date Picker
Vue.config.ignoredElements = [/duet-\w*/];

// Register Duet Date Picker
defineCustomElements(window);

new Vue({
    render: h => h(App)
}).$mount("#app");

Once included, Duet Date Picker can be used in your HTML markup as in the following example:

<template>
  <label for="date">Choose a date</label>
  <duet-date-picker
    identifier="date"
    :localization.prop="localisation_uk">
  </duet-date-picker>
</template>

<script>
  const localisation_uk = {
    buttonLabel: 'Choose date',
    placeholder: 'DD/MM/YYYY',
    selectedDateMessage: 'Selected date is',
    prevMonthLabel: 'Previous month',
    nextMonthLabel: 'Next month',
    monthSelectLabel: 'Month',
    yearSelectLabel: 'Year',
    closeLabel: 'Close window',
    keyboardInstruction: 'You can use arrow keys to navigate dates',
    calendarHeading: 'Choose a date',
    dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
    monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
    monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
  }
</script>

Please note that you need to also import duet.css separately if you want to use the default theme. See theming section for more information.

Please also note that in order to use duet-date-picker's own custom properties (as seen on the properties section), vue must recognise that such options are being passed down as properties rather than attributes, hence the .prop at the end.

Usage with React

With an application built using the create-react-app script the easiest way to include Duet Date Picker is to call defineCustomElements(window) from the index.js file:

// Import Duet Date Picker
import { defineCustomElements } from "@duetds/date-picker/dist/loader";

// ...
// Register Duet Date Picker
defineCustomElements(window);

Then you can create a thin React wrapper component to handle listening for events, cleanup, passing properties etc:

import React, { useEffect, useRef } from "react";

function useListener(ref, eventName, handler) {
  useEffect(() => {
    if (ref.current) {
      const element = ref.current;
      element.addEventListener(eventName, handler)
      return () => element.removeEventListener(eventName, handler)
    }
  }, [eventName, handler, ref])
}

export function DatePicker({
  onChange,
  onFocus,
  onBlur,
  onOpen,
  onClose,
  dateAdapter,
  localization,
  ...props
}) {
  const ref = useRef(null)

  useListener(ref, "duetChange", onChange)
  useListener(ref, "duetFocus", onFocus)
  useListener(ref, "duetBlur", onBlur)
  useListener(ref, "duetOpen", onOpen)
  useListener(ref, "duetClose", onClose)

  useEffect(() => {
    ref.current.localization = localization
    ref.current.dateAdapter = dateAdapter
  }, [localization, dateAdapter])

  return <duet-date-picker ref={ref} {...props}></duet-date-picker>
}

Then the wrapper can be used like any other React component:

<DatePicker
  value="2020-08-24"
  onChange={e => console.log(e.detail)}
/>

Please note that you need to also import duet.css separately if you want to use the default theme. See theming section for more information.

Usage with Ember

Duet Date Picker can be easily integrated into Ember thanks to the ember-cli-stencil addon that handles:

  • Importing the required files into your vendor.js
  • Copying the component definitions into your assets directory
  • Optionally generating a wrapper component for improved compatibility with older Ember versions

Start by installing the Ember addon:

ember install ember-cli-stencil ember-auto-import

When you build your application, Stencil collections in your dependencies will be automatically discovered and pulled into your application. You might get a Can't resolve error when building. The easiest way to resolve that issue is by adding an alias to your ember-cli-build.js file.

	autoImport: {
		alias: {
			'@duetds/date-picker/loader':  '@duetds/date-picker/dist/loader/index.cjs',
		},
	},

For more information, see ember-cli-stencil documentation.

Ember octane example:

<label  for="date">Choose a date.</label>
<duet-date-picker identifier="date" {{prop localization=this.localization}} ></duet-date-picker>
import Controller from "@ember/controller";
import { action } from "@ember/object";
import { tracked } from "@glimmer/tracking";

export default class ExampleController extends Controller {
  @tracked localization = {
    buttonLabel: "Choose date",
    placeholder: "mm/dd/yyyy",
    selectedDateMessage: "Selected date is",
    prevMonthLabel: "Previous month",
    nextMonthLabel: "Next month",
    monthSelectLabel: "Month",
    yearSelectLabel: "Year",
    closeLabel: "Close window",
    keyboardInstruction: "You can use arrow keys to navigate dates",
    calendarHeading: "Choose a date",
    dayNames: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
    monthNames: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
    monthNamesShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
  };
}

IE11 and Edge 17/18 polyfills

If you want the Duet Date Picker custom element to work on older browser, you need to add the applyPolyfills() that surround the defineCustomElements() function:

import { applyPolyfills, defineCustomElements } from "@duetds/date-picker/lib/loader";
// ...
applyPolyfills().then(() => {
  defineCustomElements(window)
})

Using events

We encourage the use of DOM events, but additionally provide custom events to make handling of certain event types easier. All custom events are documented in this same readme under the “Events” heading.

Duet Date Picker provides e.g. a custom event called duetChange. This custom event includes an object called detail which includes for example the selected date:

// Select the date picker component
const date = document.querySelector("duet-date-picker")

// Listen for when date is selected
date.addEventListener("duetChange", function(e) {
  console.log("selected date", e.detail.valueAsDate)
})

The console output for the above code looks like this:

selected date Sat Aug 15 2020 00:00:00 GMT+0300 (Eastern European Summer Time)

Theming

Duet Date Picker uses CSS Custom Properties to make it easy to theme the picker. The component ships with a default theme that you can import either from the NPM package or directly from a CDN like JSDelivr:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@duetds/[email protected]/dist/duet/themes/default.css" />

The above CSS file provides the following Custom Properties that you can override with your own properties:

:root {
  --duet-color-primary: #005fcc;
  --duet-color-text: #333;
  --duet-color-text-active: #fff;
  --duet-color-placeholder: #666;
  --duet-color-button: #f5f5f5;
  --duet-color-surface: #fff;
  --duet-color-overlay: rgba(0, 0, 0, 0.8);
  --duet-color-border: #333;

  --duet-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --duet-font-normal: 400;
  --duet-font-bold: 600;

  --duet-radius: 4px;
  --duet-z-index: 600;
}

If you wish to customize any of the default properties shown above, we recommend to NOT import or link to the provided CSS, but instead copying the above code into your own stylesheet and replacing the values used there.

Additionally, you’re able to override Duet Date Picker’s default styles by using e.g. .duet-date__input selector in your own stylesheet. This allows you to give the form input and e.g. date picker toggle button a visual look that matches the rest of your website.

Localization

Duet Date Picker offers full support for localization. This includes the text labels and date formats used. Below is an example of a date picker that is using Finnish date format and localization.

<label for="date">Valitse päivämäärä</label>
<duet-date-picker identifier="date"></duet-date-picker>

<script>
  const picker = document.querySelector("duet-date-picker")
  const DATE_FORMAT = /^(\d{1,2})\.(\d{1,2})\.(\d{4})$/

  picker.dateAdapter = {
    parse(value = "", createDate) {
      const matches = value.match(DATE_FORMAT)

      if (matches) {
        return createDate(matches[3], matches[2], matches[1])
      }
    },
    format(date) {
      return `${date.getDate()}.${date.getMonth() + 1}.${date.getFullYear()}`
    },
  }

  picker.localization = {
    buttonLabel: "Valitse päivämäärä",
    placeholder: "pp.kk.vvvv",
    selectedDateMessage: "Valittu päivämäärä on",
    prevMonthLabel: "Edellinen kuukausi",
    nextMonthLabel: "Seuraava kuukausi",
    monthSelectLabel: "Kuukausi",
    yearSelectLabel: "Vuosi",
    closeLabel: "Sulje ikkuna",
    keyboardInstruction: "Voit navigoida päivämääriä nuolinäppäimillä",
    calendarHeading: "Valitse päivämäärä",
    dayNames: [
      "Sunnuntai", "Maanantai", "Tiistai", "Keskiviikko",
      "Torstai", "Perjantai", "Lauantai"
    ],
    monthNames: [
      "Tammikuu", "Helmikuu", "Maaliskuu", "Huhtikuu",
      "Toukokuu", "Kesäkuu", "Heinäkuu", "Elokuu",
      "Syyskuu", "Lokakuu", "Marraskuu", "Joulukuu"
    ],
    monthNamesShort: [
      "Tammi", "Helmi", "Maalis", "Huhti", "Touko", "Kesä",
      "Heinä", "Elo", "Syys", "Loka", "Marras", "Joulu"
    ],
    locale: "fi-FI",
  }
</script>

Please note that you must provide the entirety of the localization properties in the object when overriding with your coustom localization.

Server side rendering

Duet Date Picker package includes a hydrate app that is a bundle of the same components, but compiled so that they can be hydrated on a NodeJS server and generate static HTML and CSS. To get started, import the hydrate app into your server’s code like so:

import hydrate from "@duetds/date-picker/hydrate"

If you are using for example Eleventy, you could now add a transform into .eleventy.js configuration file that takes content as an input and processes it using Duet’s hydrate app:

eleventyConfig.addTransform("hydrate", async(content, outputPath) => {
  if (process.env.ELEVENTY_ENV == "production") {
    if (outputPath.endsWith(".html")) {
      try {
        const results = await hydrate.renderToString(content, {
          clientHydrateAnnotations: true,
          removeScripts: false,
          removeUnusedStyles: false
        })
        return results.html
      } catch (error) {
        return error
      }
    }
  }
  return content
})

The above transform gives you server side rendered components that function without JavaScript. Please note that you need to separately pre-render the content for each theme you want to support.

Single file bundle

Duet Date Picker also offers a single file bundle without the polyfills and other additional functionality included in the default output. To import that instead of the default output, use:

import { DuetDatePicker } from "@duetds/date-picker/custom-element";

customElements.define("duet-date-picker", DuetDatePicker);

Please note that this custom-element output does not automatically define the custom elements or apply any polyfills which is why we’re defining the custom element above ourselves.

For more details, please see Stencil.js documentation.

Optimizing CDN performance

If you wish to make sure Duet Date Picker shows up as quickly as possible when loading the scripts from JSDelivr CDN, you can preload the key parts using link rel="preload". To do this, add these tags in the <head> of your webpage before any other <script> or <link> tags:

<link rel="preload" href="https://cdn.jsdelivr.net/npm/@duetds/[email protected]/dist/duet/duet.esm.js" as="script" crossorigin="anonymous" />
<link rel="preload" href="https://cdn.jsdelivr.net/npm/@duetds/[email protected]/dist/duet/duet-date-picker.entry.js" as="script" crossorigin="anonymous" />

In case you’re also using one of the included themes, you can preload them the same way using the below tag:

<link rel="preload" href="https://cdn.jsdelivr.net/npm/@duetds/[email protected]/dist/duet/themes/default.css" as="style" />

Contributing

Development server

  • Clone the repository by running git clone [email protected]:duetds/duet-date-picker.git.
  • Once cloned, open the directory and run npm install.
  • Run npm start to get the development server and watch tasks up and running. This will also automatically open a new browser window with an example page.
  • To edit the example page’s source, see ./src/index.html.

Testing and building

  • To run the unit, end-to-end and visual diff tests use npm run test.
  • To build the project use npm run build.

Publishing the package

NOTE: this section is for maintainers and can be ignored by contributors.

The process for publishing a stable or a beta release differs.

To publish a new stable release, use the npm version command. The npm docs for the version command explains in detail how this command works.

E.g. to publish a new minor version:

npm version minor

This will run tests, build the project, bump the minor version in package.json, commit and tag the changes, publish to npm, and finally push any commits to github.

To publish a new beta release, do the following:

  1. Bump version in package.json and elsewhere.
  2. Commit your changes.
  3. Tag new release by running git tag -a 1.2.0-beta.0 -m "1.2.0-beta.0".
  4. Push your changes to Git and then run npm publish --tag beta.
  5. Push to git: git push --tags --no-verify.

Changelog

  • 1.3.0:
    • Add new theme variable --duet-border-color for customising the input's border color. Falls back to previous value --duet-color-text if not set (#70).
    • Improve handling of disallowed characters so that cursor position is maintained.
    • Add new duetOpen and duetClose events to correspond with opening and closing the calendar (#73).
    • Fix click outside logic so that it works when nested in a shadow DOM (#65).
  • 1.2.0:
    • Improvements to screen reader accessibility.
      • Ensure table can be navigated with table navigation commands.
      • Ensure column headers are announced out when navigating table columns.
      • Ensure month/year is announced whenever it changes.
      • Improve how dates are presented to screen readers. Use formats like "17 November 2020" instead of "2020-11-17".
    • Year dropdown now shows every year that satisfies min/max range.
  • 1.1.0: Adds support for required attribute. Ensures date is always submitted in ISO format. Updates @stencil/core to 2.3.0.
  • 1.0.4: Improves stability for NVDA + Chrome on Windows. Also fixes an issue which caused build attempts to fail due to snapshot mismatch.
  • 1.0.2: Documentation improvements.
  • 1.0.1: Hitting arrow keys on year select on Windows without first opening the dropdown previously causes odd results. This is now fixed.
  • 1.0.0: Initial release.

Roadmap

  • Better examples on how to do date ranges, handle validation and so on.
  • Better theming and basic code examples.
  • Making it possible to pass in your own input component.

License

Copyright © 2020 LocalTapiola Services Ltd / Duet Design System.

Licensed under the MIT license.

Comments
  • localized date incorrectly submitted as part of <form>

    localized date incorrectly submitted as part of

    I am trying to use duet-date-picker with Thymeleaf (server-side rendering). It works as long as I don't use the localization support.

    I added this in a <script> tag in my page:

        const picker = document.querySelector('duet-date-picker');
        const DATE_FORMAT = /^(\d{1,2})\.(\d{1,2})\.(\d{4})$/;
    
        if (picker) {
            picker.dateAdapter = {
                parse(value = '', createDate) {
                    try {
                        console.log('parsing' + value);
                        const matches = value.match(DATE_FORMAT);
    
                        if (matches) {
                            return createDate(matches[3], matches[2], matches[1]);
                        }
                    } catch (err) {
                        console.log(err);
                    }
                },
                format(date) {
                    console.log('formatting: ' + date);
                    return `${date.getDate()}.${date.getMonth() + 1}.${date.getFullYear()}`;
                },
            };
        }
    

    I see lot's of formatting... statements, but never parsing... and also no error in the console.

    When I check with dev tools, I see updates to the value attribute of <duet-date-picker>, but the value of the actual <input> is not updated. Due to this, my server-side thinks that nothing was selected. For some reason, that does not seem to be an issue when not using the localization support. Am I doing something wrong?

    bug 
    opened by wimdeblauwe 18
  • Column Headers Not Announced

    Column Headers Not Announced

    Describe the bug When navigating the calendar grid (<table>) in JAWS, the column headers are not announced. All I hear is "two zero two zero dash zero four dash zero two" (or whatever is the date in this number format).

    To Reproduce Steps to reproduce the behavior:

    1. Go to the grid,
    2. Navigate between days within a week,
    3. Observe the screen reader does not announce the day of the week.

    Expected behavior As I navigate from column to column, I should hear the column header. Specifically I should hear the day of week as I move through a week.

    Screenshots I made a video, but GitHub does not allow me to embed it.

    Desktop (please complete the following information):

    • OS: Windows 10
    • Browser Chrome
    • Version 85
    • JAWS 2020

    Additional context Using first example at https://duetds.github.io/date-picker/

    opened by aardrian 16
  • Day of week should be announced to screen readers when navigating the calendar

    Day of week should be announced to screen readers when navigating the calendar

    Is your feature request related to a problem? Please describe. Currently the days of the week aren't announced by a screen reader when navigating the calendar. The <table> structure is correct, so presumably this is due to the use of grid + gridcell roles. Whatever the cause, the result is that screen reader users don't know what day of the week their currently-focused date is.

    Describe the solution you'd like Simplest solution would be to append or prepend the day of the week to the hidden text in each button, i.e.:

    <span class="duet-date__vhidden">Thursday, 2020-09-24</span>
    

    This could be done with Date.toLocaleDateString() to support internationalization.

    Describe alternatives you've considered The only alternative I can think of would be to drop the use of grid and just use a plain table, but this doesn't seem like a good solution—it would mean sighted keyboard users would be forced to TAB through the calendar instead of using arrow keys.

    Additional context Tested with JAWS, NVDA and MacOS Voiceover.

    bug accessibility 
    opened by JamesCatt 11
  • Handling date picker properties in Vue JS

    Handling date picker properties in Vue JS

    Describe the bug Thank you very much for all your hard work, this date picker is incredibly neat!

    I've been trying to implement this date picker into a Vue JS project. I'm a bit new and as I understand from your documentation, in order to use the properties of duet date picker (e.g. Localization, dateAdapter, direction, etc.) I need to override their defaults. I've been trying to add them as props to my component but so far no luck. Is this a bug or just a gap on my own understanding?

    To Reproduce Steps to reproduce the behavior:

    1. Create a Vue component that uses duet date picker
    2. Pass localization object as a prop

    Expected behavior I expect the placeholder to change it's label from "YYYY-MM-DD" to "DD/MM/YYYY"

    Desktop (please complete the following information):

    • OS: Catalina 10.5.15
    • Browser: [Chrome, FF, IE11, Safari]
    • Version: Duet date-picker version 1.0.1

    Additional context Component in case:

      <duet-date-picker 
        ref="date-picker"
        identifier="date"
        direction="left"
        :value="enteredDate"
        v-bind:class="{'has-error': hasErrors}"
        v-bind:required="isRequired"
        :name="name"
        :localization="{placeholder: 'DD/MM/YYYY'}"
        :dateAdapter="format_date()"
        @duetChange="dateChanged"
      />
    
    opened by RiqueBR 9
  • tweaks to disable date functionality to slightly simplify things

    tweaks to disable date functionality to slightly simplify things

    @bseber here are my changes. They seem to work the same in my testing. Does anything stand out to you as wrong, or are there any parts you disagree with? Let me know :)

    opened by WickyNilliams 8
  • Google lighthouse complains that the datepicker uses an eventlistener that is not passive

    Google lighthouse complains that the datepicker uses an eventlistener that is not passive

    Describe the bug Google lighthouse complains that the datepicker uses an eventlistener that is not passive

    To Reproduce Steps to reproduce the behavior:

    1. Use the date picker somewhere
    2. Run Google Lighthouse
    3. Se warning

    Expected behavior Google lighthouse shows no warnings

    Desktop (please complete the following information):

    • Chrome, latest

    Additional context It might be related to https://github.com/ionic-team/stencil/issues/2621

    opened by martinfjant 8
  • Unable to get applyPolyfills && defineCustomElements to import / work properly

    Unable to get applyPolyfills && defineCustomElements to import / work properly

    I'm looking for some advice on where i'm possibly going wrong when trying to apply the polyfills to get the duet date picker working in IE11 using basic HTML.

    I think i am either trying to import it wrong or misunderstanding how i'm supposed to bundle the loader and import it into html.

    This is how i am pulling the dependancies into the html head and my initial attempt at getting the polyfill working.

    <script type="module" src="https://cdn.jsdelivr.net/npm/@duetds/[email protected]/dist/duet/duet.esm.js"></script>
    <script nomodule src="https://cdn.jsdelivr.net/npm/@duetds/[email protected]/dist/duet/duet.js"></script>
    
    <script>
      // applyPolyfills & defineCustomElements are both undefined
      applyPolyfills().then(function () {
        defineCustomElements(window);
      })
    </script>
    

    I have additionally tried to use browserfy to bundle the dependencies into a single standalone UMD js file and import that instead.

    var loader = require("@duetds/date-picker/dist/loader");
    
    module.exports = {
      applyPolyfills: loader.applyPolyfills,
      defineCustomElements: loader.defineCustomElements,
    };
    
    browserify --standalone duetLoader -r ./assets/javascript/duet-non-bundled.js -o assets/javascript/duet-bundled.js
    
    <script type="text/javascript" src="/assets/javascript/duet-bundled.js"></script>
    <script>
      var applyPolyfills = duetLoader.applyPolyfills;
      var defineCustomElements = duetLoader.defineCustomElements;
      applyPolyfills().then(function () {
         defineCustomElements(window);
      })
    </script>
    

    The above approach resolves the issue with the methods being undefined but i get the following stack trace on runtime when the <duet-date-picker> tags are rendered and the date picker becomes unusable / non responsive to on click events.

    duet-bundled.js:1422 Error: Cannot find module './duet-date-picker.cjs.entry.js'
        at o (duet-bundled.js:1)
        at duet-bundled.js:1
        at duet-bundled.js:1432
        at async initializeComponent (duet-bundled.js:1163) undefined
    

    Prior to trying to get the poly fills to work i have encountered no issues with the date picker.

    Any advice or a point in the right direction would be much appreciated. If it's something missing in the docs that could be improved i'd be happy to contribute to improving them once i've managed to find a solution.

    P.S this is a great date picker in terms of UI and accessibility, I look forward to seeing it develop further.

    opened by mikeystewartap 6
  • Live Region Not Announced

    Live Region Not Announced

    Describe the bug I discovered a live region in the code:

    <div class="duet-date__vhidden duet-date__instructions" aria-live="polite">You can use arrow keys to navigate dates</div>
    

    However when I access the control I do not hear the live region announced.

    To Reproduce Steps to reproduce the behavior:

    1. Go to the grid,
    2. Interact with it (typing a date, opening the calendar, navigation it, choosing one, etc),
    3. Observe the screen reader does not announce the content from the live region.

    Expected behavior I expect to hear the live region.

    Desktop (please complete the following information):

    • OS: Windows 10
    • Browser Chrome
    • Version 85
    • JAWS 2020

    Additional context Using first example at https://duetds.github.io/date-picker/

    bug accessibility 
    opened by aardrian 5
  • Selecting a month then selecting days from another month seen in the calendar.

    Selecting a month then selecting days from another month seen in the calendar.

    Is your feature request related to a problem? Please describe.

    One can see days from other months but not select them. First one has to switch months and then select the days.

    Describe the solution you'd like

    Screen Shot 2021-08-27 at 23 03 33

    Above here we see 1-5 days in September. It would be nice to be able to click one of these days in September and have the calendar select the day and at the same time switch months.

    opened by paaljoachim 4
  • Bugfix/inside shadow dom

    Bugfix/inside shadow dom

    Fixes #64

    If an event is fired from within a component with shadow DOM and listened to outside (like document or window), the event.target will not pierce the shadow DOM and show as the top level component. For example, listening to a click event on the document in the following situation (if foo-bar had a shadow dom)

    <foo-bar>
      <duet-date-picker></duet-date-picker>
    </foo-bar>
    

    will have foo-bar as the event target! Therefore the condition if (this.dialogWrapperNode.contains(target) || this.datePickerButton.contains(target)) { will not be met because target always returns a parent node!

    Enter composedPath to the rescue. https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath It returns an array of nodes and will contain shadow dom nodes as long as it is open. I realized that composedPath is not supported in IE (IE doesn't support shadow dom at all), so if composedPath is available it will be used, otherwise it will fallback to target.

    opened by dsappet 4
  • date not showing with these examples

    date not showing with these examples

    Hi, any idea why the date control is not showing a value with these dates:

    2019-11-14T00:55:31.820Z
    2020-11-25T15:18:15Z
    2020-11-25T15:18:15+00:00
    2002-02-02T00:00:00.000Z
    
    

    Here's how I've added the control,

          <duet-date-picker
            :id="inputId"
            type="text"
            :value="value"
            v-on="maskedInputEvents()"
            :localization.prop="localization()"
          />
    
    

    here's the localization & maskedInputEvents function

      public localization(): any {
        return {
          buttonLabel: "Choose date",
          placeholder: "YYYY-MM-DD",
          selectedDateMessage: "Selected date is",
          prevMonthLabel: "Previous month",
          nextMonthLabel: "Next month",
          monthSelectLabel: "Month",
          yearSelectLabel: "Year",
          closeLabel: "Close window",
          keyboardInstruction: "You can use arrow keys to navigate dates",
          calendarHeading: "Choose a date",
          dayNames: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
          monthNames: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
          monthNamesShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
        };
      }
    
      public maskedInputEvents(): any {
        const _vm = this as DateInput;
        return Object.assign({},
          // parent listeners
          this.$listeners,
          {
            // custom listeners
            input(event: any) {
              // Emit updated value
              _vm.$emit('input', event.target.value);
            },
            duetBlur(event: any) {
              const value = _vm.value;
              const matches = value ? value.match(/^(\d{0,4})(\d{0,2})(\d{0,2})$/) : value;
              if (value && matches) {
                _vm.$emit('input', `${matches[1]}-${matches[2]}-${matches[3]}`);
              } else {
                _vm.$emit('input', value);  
              }
            },
            duetChange(event: any) {
              // Emit updated value
              _vm.$emit('input', event.detail.value.length > 0 ? event.detail.value : undefined);
            }
          }
        );
      }
    
    Screen Shot 2020-11-25 at 10 35 16 AM

    It does work with 2019-11-14

    Any ideas what could be wrong here?

    opened by johnantoni 4
  • Custom Element creates opinionated stylesheet with no option to exclude it. Better extract it as an optional import.

    Custom Element creates opinionated stylesheet with no option to exclude it. Better extract it as an optional import.

    Is your feature request related to a problem? Please describe.

    I added this script in the head:

    Datepicker initialized correctly but creates a constructed stylesheet with css selectors which I do not want. For example I think it's a bad idea to have a selector like .is-active .duet-date__dialog-content. It may interfere with other css (for example an open accordion). I have no other choice than to override this css. Another example is that the Datepicker overrides default focus-Style of the browser which I also think it's wrong.

    Describe the solution you'd like Extract the css in own file and make it optional by importing it whenever you want.

    Describe alternatives you've considered Didn't find any solution

    Additional context N/A

    opened by ico85 1
  • isDateDisabled should supply the date in ISO8601 string format

    isDateDisabled should supply the date in ISO8601 string format

    Problem

    When using isDateDisabled I want to check whether the date given in the callback is in a list of dates that I have supplied. My list of dates is in ISO8601 format ("YYYY-MM-DD") which fits nicely with the main inputs and outputs of this date-picker. The problem is that obtaining "YYYY-MM-DD" from this particular Date object is surprisingly awkward, and having only this representation available means it is less possible to share code between isDateDisabled and duetChange callbacks.

    What happens now

    isDateDisabled currently provides the date only as a javascript Date object, representing midnight in local time. Converting that to "YYYY-MM-DD" is awkward, partly because the javascript Date object does not supply adequate methods for doing so. The fact that it's local time makes things harder, because Date.toISOString() returns UTC, leading to the resulting YYYY-MM-DD portion of the string being off-by-one depending on local time zone. One alternative is taking the individual components such as Date.getMonth() and formatting them, which is straightforward but surprisingly verbose for reasons that can be seen in the code below.

    The code I am using now:

      // convert a date to a string 'yyyy-mm-dd'
      // 'date' from this widget is a midnight in local time, which is awkward
      function dateStr(date) {
        const pad2 = (x) => (String(x).padStart(2,'0'))
        return `${date.getFullYear()}-${pad2(date.getMonth()+1)}-${pad2(date.getDate())}`
      }
      picker.isDateDisabled = (date) => ! myDatesList.includes(dateStr(date))
    

    Whereas checking for disabled dates in the duetChange callback is a short one-liner and does not require such helper functions:

      picker.addEventListener("duetChange", function(e) {
        // check for unavailable dates (entered manually) is easy:
        if (! myDatesList.includes(e.detail.value) // { alert(...) }
      }
    

    Suggested solution

    Add to the isDateDisabled callback a new argument giving the same detail that is provided as the argument to the duetChange callback. This detail contains both the Date object and the YYYY-MM-DD string representation.

    I do not wish to recommend here any particular backward compatibility strategy; options include adding an optional second argument, or making a differently named callback while keeping (or deprecating) isDateDisabled. Let's say for example that we make a differently named callback, that supplies a parameter in the same form as duetChange does. Then the user code could be a short one-liner matching the implementation of duetChange:

      picker.isDateDisabled = (e) => ! myDatesList.includes(e.detail.value)
    

    Alternatives considered

    Looked for simpler ways to use the supplied Date object; found no really simple ways to do so without further dependencies.

    opened by julianfoad 0
  • Support 3 letters for weekday names

    Support 3 letters for weekday names

    Is your feature request related to a problem? Please describe. In some languages, two characters is not enough to distinguish between weekday names. A lot of other date pickers, such as jquery date picker, support three characters to resolve this.

    Describe the solution you'd like Allow for weekday names to use three characters.

    Describe alternatives you've considered There really isn't another solution.

    Additional context N/A

    opened by Raol87 0
  • Support for readonly (as opposed to disabled)

    Support for readonly (as opposed to disabled)

    Is your feature request related to a problem? Please describe. In trying to use this control to stand in for a regular text box, and to work like other controls on a page, there are times when a 'readonly' property would be preferred over the 'disabled' property. When a regular input type="text" is disabled, its value will not be sent when submitting the form, and it is not focusable. When it is readonly, its value is sent when submitting the form, and it is focusable, but just not editable. Currently, the duet date picker supports only a 'disabled' property, which behaves mostly like a native 'disabled' property with the exception that there is a hidden field that does get submitted with the value of the control.

    Describe the solution you'd like I suggest having both a 'disabled' and a 'readonly' property that mimic the behavior of the native controls as much as possible

    opened by jessevoogt 0
  • VoiceOver guidance is unclear after date is selected

    VoiceOver guidance is unclear after date is selected

    Describe the bug

    I'm experiencing some strange behavior when using VoiceOver on Safari.

    After I select a date, calendar closes and the browser focus moves back to the calendar button. The VoiceOver cursor stays on the date, which is now hidden. VoiceOver says, "you are currently on a toggle button, inside of a blank."

    To Reproduce Steps to reproduce the behavior:

    1. Open the Duet Date Picker demo in Safari with VoiceOver running
    2. Use the VoiceOver keyboard controls to activate the calendar button. The calendar opens.
    3. Use the VoiceOver keyboard controls to navigate to a date and select it.
    4. The calendar closes.
    5. The message in the screenshot below should appear.

    Expected behavior I'd expect the VoiceOver cursor to return to the calendar button. I'd then expect VoiceOver to announce that I'm on the calendar button. (Adding an aria-live region might help.) Alternately, the calendar could remain open after I make a selection.

    Thank you!

    Screenshots Screenshot of date input with VoiceOver running. VoiceOver is announcing: 'You are currently on a toggle button, inside of a blank. To select or deselect this checkbox, press Control-Option-Space.

    Desktop (please complete the following information):

    • OS: macOS 12.4
    • Browser: Safari 15.5
    opened by rdebeasi 0
Owner
Duet Design System
Duet provides a set of organized tools, patterns and practices that work as the foundation for LocalTapiola and Turva digital products and experiences.
Duet Design System
Reusable date picker component for React

React DayPicker DayPicker is a reusable date picker component for React. $ npm install react-day-picker@next Beta version ⚠️ This branch is for the ne

Giampaolo Bellavite 4.8k Dec 28, 2022
CalendarPickerJS - A minimalistic and modern date-picker component/library 🗓️👨🏽‍💻 Written in Vanilla JS

CalendarPickerJS The simple and pretty way to let a user select a day! Supports all major browser. Entirely written in Vanilla JavaScript with no depe

Mathias Picker 15 Dec 6, 2022
Nepali Date Picker jQuery Plugin 🇳🇵

Nepali Date Picker Nepali Date Picker jQuery Plugin for everyone. ???? Installation npm install nepali-date-picker Demo and Documentation https://leap

Leapfrog Technology 70 Sep 29, 2022
React Native Week Month Date Picker

React Native Week Month Date Picker Date picker with a week and month view Installation npm install react-native-week-month-date-picker Dependencies T

Noona 119 Dec 27, 2022
A tiny and fast zero-dependency date-picker built with vanilla Javascript and CSS.

A tiny zero-dependency and framework-agnostic date picker that is incredibly easy to use! Compatible with any web UI framework, vanilla JS projects, and even HTML-only projects!

Nezar 1 Jan 22, 2021
Create Persian Calendar as html helper with tag builder c# , and convert selected persian date to gregorian date

Persian-Calendar Use JS,Html,CSS,C# White theme for Persian Calendar , easy to use. Create Persian Calendar as html helper. Use Tag builder in c# for

Tareq Awwad 4 Feb 28, 2022
An wide ranged emoji picker extension for firefox based browsers.

Fire-Picker A wide ranged emoji picker extension for firefox based browsers made with pure js. This is still in a prototype phase. Changes will be mad

null 2 Jan 10, 2022
An wide ranged emoji picker extension for firefox based browsers.

Fire-Picker A wide ranged emoji picker extension for firefox based browsers made with pure js. This is still in a prototype phase. Changes will be mad

Furha Lepton 2 Jan 10, 2022
A CJS version of dateformat, forked from node-dateformat

dateformat A node.js package for Steven Levithan's excellent dateFormat() function. This module was forked from https://github.com/felixge/node-datefo

Matteo Collina 14 Sep 25, 2021
⏰ Day.js 2KB immutable date-time library alternative to Moment.js with the same modern API

English | 简体中文 | 日本語 | Português Brasileiro | 한국어 | Español (España) | Русский Fast 2kB alternative to Moment.js with the same modern API Day.js is a

null 41.7k Dec 28, 2022
⏳ Modern JavaScript date utility library ⌛️

date-fns provides the most comprehensive, yet simple and consistent toolset for manipulating JavaScript dates in a browser & Node.js. ?? Documentation

date-fns 30.6k Dec 29, 2022
🕑 js-joda is an immutable date and time library for JavaScript.

js-joda is an immutable date and time library for JavaScript. It provides a simple, domain-driven and clean API based on the ISO8601 calendar.

null 1.5k Dec 27, 2022
DEPRECATED: Timezone-enabled JavaScript Date object. Uses Olson zoneinfo files for timezone data.

TimezoneJS.Date A timezone-enabled, drop-in replacement for the stock JavaScript Date. The timezoneJS.Date object is API-compatible with JS Date, with

Matthew Eernisse 830 Nov 20, 2022
Date() for humans

date Date is an english language date parser for node.js and the browser. For examples and demos, see: http://matthewmueller.github.io/date/ Update: d

Matthew Mueller 1.5k Jan 4, 2023
:clock8: :hourglass: timeago.js is a tiny(2.0 kb) library used to format date with `*** time ago` statement.

timeago.js timeago.js is a nano library(less than 2 kb) used to format datetime with *** time ago statement. eg: '3 hours ago'. i18n supported. Time a

hustcc 4.9k Jan 4, 2023
Lightweight and simple JS date formatting and parsing

fecha Lightweight date formatting and parsing (~2KB). Meant to replace parsing and formatting functionality of moment.js. NPM npm install fecha --save

Taylor Hakes 2k Jan 5, 2023
A lightweight, locale aware formatter for strings containing unicode date tokens.

Date Token Format A lightweight (~2kB), locale aware formatter for strings containing unicode date tokens. Usage Install the package using: yarn add d

Donovan Hutchinson 1 Dec 24, 2021
⏰ Day.js 2kB immutable date-time library alternative to Moment.js with the same modern API

English | 简体中文 | 日本語 | Português Brasileiro | 한국어 | Español (España) | Русский Fast 2kB alternative to Moment.js with the same modern API Day.js is a

null 41.7k Dec 28, 2022
Easy to get a date.

date2data 테이블에 날짜별 데이터 넣을 때마다 새로 객체 만들어서 작업하기가 매우 귀찮아서 만들었다. moment.js를 쓰기에는 구현하고자 하는 내용이 너무 가벼웠음 Install npm i date2data Usage import {getMonthlyDate

Duho Kim 3 Apr 12, 2022