wip: replace flatpickr

This commit is contained in:
Björn Fromme committed 2026-07-16 09:03:42 +02:00
1 parent e902a5e298
commit 2cc9269236
5 files changed
+80 -58

No files matched your search

+44 -30
View File
@@ -1,56 +1,70 @@
import { Controller } from '@hotwired/stimulus'
import flatpickr from 'flatpickr';
import { German } from 'flatpickr/dist/l10n/de';
import { Calendar } from 'vanilla-calendar-pro'
/* stimulusFetch: 'lazy' */
export default class extends Controller {
static targets = [ 'field' ]
static targets = ['field', 'hiddenField']
static values = {
minDate: String,
maxDate: String,
disableWeekends: Boolean,
}
initialize() {
flatpickr.localize(German)
disabledDates: Array,
}
connect() {
const minDate = this.minDateValue
const maxDate = this.maxDateValue
const options = {
dateFormat: 'Y-m-d',
altFormat: 'd.m.Y',
minDate,
maxDate,
altInput: true,
allowInput: true,
onClose(dates, currentdatestring, instance) {
instance.setDate(instance.altInput.value, true, instance.config.altFormat)
this.element.dispatchEvent(new CustomEvent('datepicker:closed'))
},
onChange(dates, currentdatestring, instance) {
inputMode: true,
locale: 'de-DE',
selectedTheme: 'light',
selectedDates: this.hiddenFieldTarget.value ? [this.hiddenFieldTarget.value] : [],
onClickDate: (self) => {
const iso = self.context.selectedDates[0] ?? null
this.hiddenFieldTarget.value = iso ?? ''
if (iso) {
const [year, month, day] = iso.split('-')
this.fieldTarget.value = `${day}.${month}.${year}`
} else {
this.fieldTarget.value = ''
}
this.element.dispatchEvent(new CustomEvent('datepicker:picked', {
detail: {
date: instance.altInput.value
}
detail: { date: this.fieldTarget.value }
}))
},
onHide: () => {
this.element.dispatchEvent(new CustomEvent('datepicker:closed'))
},
}
if (true === this.disableWeekendsValue) {
options['disable'] = [
function(date) {
return (date.getDay() === 0 || date.getDay() === 6);
if (this.minDateValue) options.dateMin = this.minDateValue
if (this.maxDateValue) options.dateMax = this.maxDateValue
if (this.disableWeekendsValue) options.disableWeekdays = [0, 6]
if (this.disabledDatesValue.length) options.disableDates = this.disabledDatesValue
this.picker = new Calendar(this.fieldTarget, options)
this.picker.init()
this.handleManualInput = () => {
const value = this.fieldTarget.value.trim()
if (!value) {
this.hiddenFieldTarget.value = ''
this.picker.set({ selectedDates: [] }, { dates: true })
} else {
const match = value.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/)
if (match) {
const [, day, month, year] = match
const iso = `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`
this.hiddenFieldTarget.value = iso
this.picker.set({ selectedDates: [iso] }, { dates: true })
}
]
}
this.element.dispatchEvent(new CustomEvent('datepicker:closed'))
}
this.picker = flatpickr(this.fieldTarget, options)
this.fieldTarget.addEventListener('change', this.handleManualInput)
}
disconnect() {
this.fieldTarget.removeEventListener('change', this.handleManualInput)
this.picker.destroy()
}
}
+4 -10
View File
@@ -1,13 +1,7 @@
@import "flatpickr/dist/flatpickr.css";
@import "vanilla-calendar-pro/styles/index.css";
@import "vanilla-calendar-pro/styles/themes/light.css";
.flatpickr-day.selected,
.flatpickr-day.selected:hover,
.flatpickr-day.selected:focus {
[data-vc-theme=light] .vc-date[data-vc-date-selected] .vc-date__btn,
[data-vc-theme=light] .vc-date[data-vc-date-selected] .vc-date__btn:hover {
background-color: theme('colors.secondary');
border-color: theme('colors.secondary');
}
.flatpickr-monthDropdown-months,
.flatpickr-current-month .flatpickr-monthDropdown-months {
@apply form-select pt-0 pb-0 focus:outline-none focus:shadow-none;
}
+12 -8
View File
@@ -8,9 +8,9 @@
"license": "WTFPL",
"dependencies": {
"@trevoreyre/autocomplete-js": "^3.0.3",
"flatpickr": "^4.6.13",
"sortablejs": "^1.15.7",
"stimulus-use": "^0.52.3"
"stimulus-use": "^0.52.3",
"vanilla-calendar-pro": "^3.1.0"
},
"devDependencies": {
"@babel/core": "^7.17.0",
@@ -4393,12 +4393,6 @@
"flat": "cli.js"
}
},
"node_modules/flatpickr": {
"version": "4.6.13",
"resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.13.tgz",
"integrity": "sha512-97PMG/aywoYpB4IvbvUJi0RQi8vearvU0oov1WW3k0WZPBMrTQVqekSX5CjSG/M4Q3i6A/0FKXC7RyAoAUUSPw==",
"license": "MIT"
},
"node_modules/forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
@@ -8563,6 +8557,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/vanilla-calendar-pro": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/vanilla-calendar-pro/-/vanilla-calendar-pro-3.1.0.tgz",
"integrity": "sha512-yXDtCaedcKz6i5OOdWGwui0C8MAmjXjj7JzKZyjDlkczSRqnhI8BDGFygqT2K+qL1uY7R2fLYlTlxA6oyFs2yg==",
"license": "MIT",
"funding": {
"type": "individual",
"url": "https://buymeacoffee.com/uvarov"
}
},
"node_modules/verror": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+1 -1
View File
@@ -33,7 +33,7 @@
},
"dependencies": {
"@trevoreyre/autocomplete-js": "^3.0.3",
"flatpickr": "^4.6.13",
"vanilla-calendar-pro": "^3.1.0",
"sortablejs": "^1.15.7",
"stimulus-use": "^0.52.3"
}
+19 -9
View File
@@ -162,21 +162,31 @@
{%- block datepicker_widget -%}
{%- set minDate = form.vars.min_date ? form.vars.min_date | date('Y-m-d') : null -%}
{%- set maxDate = form.vars.max_date ? form.vars.max_date | date('Y-m-d') : null -%}
{%- set attr = attr|merge({'class': (attr.class|default('') ~ ' block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset text-sm')|trim }) -%}
{%- set displayValue = value ? value|date('d.m.Y') : '' -%}
{%- set inputClass = 'block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset text-sm' -%}
{%- if errors|length -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' ring-red-500 placeholder:red-500 focus:ring-red-500' }) -%}
{% else %}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' placeholder:text-gray-900 focus:ring-primary' }) -%}
{%- set inputClass = inputClass ~ ' ring-red-500 placeholder:red-500 focus:ring-red-500' -%}
{%- else -%}
{%- set inputClass = inputClass ~ ' placeholder:text-gray-900 focus:ring-primary' -%}
{%- endif -%}
{%- if disabled is defined and disabled == true -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' cursor-not-allowed' }) -%}
{%- set inputClass = inputClass ~ ' cursor-not-allowed' -%}
{%- endif -%}
<div {{ stimulus_controller('datepicker', { 'minDate': minDate, 'maxDate': maxDate, 'disableWeekends': form.vars.disable_weekends }) }}>
<input type="text" name="{{ form.vars.full_name }}" value="{{ value }}" {{ block('widget_attributes') }} {{ stimulus_target('datepicker', 'field') }} />
<input type="text"
id="{{ id }}"
class="{{ inputClass }}"
value="{{ displayValue }}"
{% if attr.placeholder is defined %}placeholder="{{ attr.placeholder }}"{% endif %}
{% if attr['aria-describedby'] is defined %}aria-describedby="{{ attr['aria-describedby'] }}"{% endif %}
{% if required %}required{% endif %}
{% if disabled is defined and disabled %}disabled{% endif %}
{{ stimulus_target('datepicker', 'field') }} />
<input type="hidden"
name="{{ form.vars.full_name }}"
value="{{ value }}"
{{ stimulus_target('datepicker', 'hiddenField') }} />
</div>
{%- if disabled is defined and disabled == true -%}
<input type="hidden" name="{{ form.vars.full_name }}" value="{{ value }}">
{%- endif -%}
{%- endblock datepicker_widget %}
{%- block autocomplete_widget -%}