dan-lee / timescape

A flexible, headless date and time input library for JavaScript. Provides tools for building fully customizable date and time input fields, with support for libraries like React, Preact, Vue, Svelte and Solid.
https://timescape.daniellehr.de
MIT License
159 stars 2 forks source link

[Feature Request] Step option for minutes #3

Closed fweinaug closed 1 year ago

fweinaug commented 1 year ago

It would be great to have an option to increment the minutes in steps when using the arrow up/down keys. For example when setting step=15 the minutes cycle through 15/30/45/00 when pressing the arrow keys.

Using the step attribute of the input for minutes will lead to a different result because it will just add or subtract the given value.

dan-lee commented 1 year ago

The step property is already supported :) See this example: https://stackblitz.com/edit/timescape-react-brnnek?file=src%2FApp.tsx

fweinaug commented 1 year ago

Thank you for the quick reply! Unfortunately it's a litte bit different: When you enter for example 33 minutes and press the up arrow the minutes change to 48. The expected behaviour would be 45 minutes. This is also the default behaviour of a 'datetime-local' input, at least in Chrome.

My use-case is to allow the user to select an appointment date which is more likely to be in 15 minute steps. It would be amazing if Timescape could support it :)

dan-lee commented 1 year ago

Interesting! Didn't know this was the case. Looks like Firefox and Safari don't adhere to this behavior. What do you think about a new option, something like snapToStep to explictely enable this?

fweinaug commented 1 year ago

Sounds good :) it makes total sense to make it an explicit option.

fweinaug commented 1 year ago

should i try to create a PR? @dan-lee

dan-lee commented 1 year ago

That would be appreciated, not sure when I come around to implement it this week

fweinaug commented 1 year ago

I started looking into it. Adding this code to the handler of the ArrowUp/Down keys could be enough.

This expects snapToStep to be in seconds, not sure if minutes would be better.

let step;
if (this.snapToStep && type === 'minutes') {
  const minutes = date.getMinutes()
  const snapMinutes = Math.max(Math.round(this.snapToStep / 60), 1)

  if (e.key === 'ArrowUp') {
    step = Math.ceil((minutes + 1) / snapMinutes) * snapMinutes - minutes
  } else {
    step = Math.floor((minutes - 1) / snapMinutes) * snapMinutes - minutes
  }
} else {
  step = (elementStep || 1) * factor
}

edit: updated the code, arrow down was not handled properly

fweinaug commented 1 year ago

what do you think @dan-lee ?

dan-lee commented 1 year ago

Yeah, I think that works! Why only limit this to minutes? I thought of snapToStep to be a boolean and to snap for every field. Admittedly not every field makes sense, but I don't think we should limit it to that.

fweinaug commented 1 year ago

good point! here is the updated code:

const elementStep = Number(inputElement.step) || 1
let step;
if (this.snapToStep) {
  const getValue = (date: Date, type: DateType) => {
    switch (type) {
      case 'days':
        return date.getDay()
        case 'months':
          return date.getMonth()
        case 'years':
          return date.getFullYear()
        case 'hours':
          return date.getHours()
        case 'minutes':
          return date.getMinutes()
        case 'seconds':
          return date.getSeconds()
    }
  }

  const value = getValue(date, type)

  if (e.key === 'ArrowUp') {
    step = Math.ceil((value + 1) / elementStep) * elementStep - value
  } else {
    step = Math.floor((value - 1) / elementStep) * elementStep - value
  }
} else {
  const factor = e.key === 'ArrowUp' ? 1 : -1

  step = elementStep * factor
}

would you be open to add it or should i create a PR? you would be way faster, especially with storybook 😅

dan-lee commented 1 year ago

Yeah, I can take care of it. Got some spare time today :)