Skip to content

Commit 1512a52

Browse files
committed
feat: add DatePickerEx component
1 parent f6ceb84 commit 1512a52

File tree

4 files changed

+249
-0
lines changed

4 files changed

+249
-0
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*-
2+
* #%L
3+
* Year Month Calendar Add-on
4+
* %%
5+
* Copyright (C) 2021 - 2025 Flowing Code
6+
* %%
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* #L%
19+
*/
20+
package com.flowingcode.addons.ycalendar;
21+
22+
import com.vaadin.flow.component.Tag;
23+
import com.vaadin.flow.component.datepicker.DatePicker;
24+
import com.vaadin.flow.component.dependency.JsModule;
25+
import com.vaadin.flow.function.ValueProvider;
26+
import java.time.LocalDate;
27+
28+
@SuppressWarnings("serial")
29+
@Tag("fc-date-picker")
30+
@JsModule("./fc-date-picker/fc-date-picker.js")
31+
public class DatePickerEx extends DatePicker {
32+
33+
private ValueProvider<LocalDate, String> classNameGenerator;
34+
35+
/**
36+
* Default constructor.
37+
*/
38+
public DatePickerEx() {
39+
super();
40+
}
41+
42+
/**
43+
* Convenience constructor to create a date picker with a pre-selected date in current UI locale
44+
* format.
45+
*
46+
* @param initialDate the pre-selected date in the picker
47+
*/
48+
public DatePickerEx(LocalDate initialDate) {
49+
this();
50+
setValue(initialDate);
51+
}
52+
53+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { css, html, LitElement } from 'lit';
2+
import { defineCustomElement } from '@vaadin/component-base/src/define.js';
3+
4+
export class FcDatePickerButtons extends LitElement {
5+
static get is() {
6+
return 'fc-date-picker-buttons';
7+
}
8+
render() {
9+
return html`
10+
<button id="up" @click=${this._clickUp}></button>
11+
<button id="down" @click=${this._clickDown}></button>
12+
`;
13+
}
14+
static get styles() {
15+
return css`
16+
:host {
17+
display: flex;
18+
flex-direction: column;
19+
align-self: stretch;
20+
justify-content: space-evenly;
21+
}
22+
button {
23+
padding: 0;
24+
margin: 0;
25+
max-height: 0.9rem;
26+
box-sizing: content-box;
27+
}
28+
button::before {
29+
display: block;
30+
}
31+
#up::before {
32+
content: "\\25B2";
33+
}
34+
#down::before {
35+
content: "\\25BC";
36+
}`;
37+
}
38+
connectedCallback() {
39+
super.connectedCallback();
40+
}
41+
_clickUp() {
42+
this.dispatchEvent(new CustomEvent("click-up"));
43+
}
44+
_clickDown() {
45+
this.dispatchEvent(new CustomEvent("click-down"));
46+
}
47+
}
48+
49+
defineCustomElement(FcDatePickerButtons);
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import {DatePicker} from '@vaadin/date-picker/vaadin-date-picker.js';
2+
import { html } from '@polymer/polymer/polymer-element.js';
3+
import { defineCustomElement } from '@vaadin/component-base/src/define.js';
4+
import './fc-date-picker-buttons.js';
5+
6+
export class FcDatePicker extends DatePicker {
7+
static get is() {
8+
return 'fc-date-picker';
9+
}
10+
11+
static get template() {
12+
return html`
13+
${super.template}
14+
<style>
15+
vaadin-input-container {
16+
padding-left: 0;
17+
}
18+
::slotted(input) {
19+
padding-left: calc(0.575em + var(--lumo-border-radius-m) / 4 - 1px);
20+
}
21+
</style>
22+
`;
23+
}
24+
25+
ready() {
26+
const buttons=document.createElement('fc-date-picker-buttons');
27+
buttons.setAttribute('slot','prefix');
28+
this.appendChild(buttons);
29+
buttons.addEventListener("click-up", ()=>this._onButtonClick(+1));
30+
buttons.addEventListener("click-down", ()=>this._onButtonClick(-1));
31+
super.ready();
32+
}
33+
34+
_onButtonClick(delta) {
35+
// If input element contains a valid date
36+
const date = this.__parseDate(this.inputElement.value);
37+
if (date) {
38+
39+
// Count how many non-digit characters appear before the current cursor position
40+
let index=0;
41+
for (let i=0;i<this.inputElement.selectionStart;i++) {
42+
if (!this.inputElement.value[i].match(/[0-9]/)) index++;
43+
}
44+
45+
// Map the component index (e.g. 0 for first, 1 for second),
46+
// to its corresponding date part character ('M', 'D', or 'Y')
47+
// based on the formatted structure.
48+
const format = this.__formatDate(new Date(3333,10,22))
49+
.replaceAll('1','M')
50+
.replaceAll('2','D')
51+
.replaceAll('3','Y');
52+
const part = format.replace(new RegExp(`^([DMY]+[^DMY]){${index}}`),'')[0];
53+
54+
// Increments a numeric substring by a specified delta, preserving formatting and padding.
55+
const incrementSubstring = (value, start, end, delta) => {
56+
const prefix = value.substring(0,start);
57+
const middle = parseInt(value.substring(start,end))+delta;
58+
const suffix = value.substring(end);
59+
return prefix+(""+middle).padStart(end-start, '0')+suffix;
60+
};
61+
62+
const incrementComponent = (value, part, delta) => {
63+
// Increments the numeric component (year, month, or day) of a date string based on the current component index.
64+
switch (part) {
65+
case 'Y':
66+
return incrementSubstring(value,0,4,delta);
67+
case 'M':
68+
return incrementSubstring(value,5,7,delta);
69+
case 'D':
70+
return incrementSubstring(value,8,10,delta);
71+
}
72+
};
73+
74+
this.value = incrementComponent(this._formatISO(date), part, delta);
75+
76+
// Find the start of the index-th component
77+
let start=0;
78+
for (let i=0;i<index;start++) {
79+
if (!this.inputElement.value[start].match(/[0-9]/)) i++;
80+
}
81+
82+
// Find the end of the index-th component
83+
let end=start;
84+
while (end<this.inputElement.value.length && this.inputElement.value[end].match(/[0-9]/)) {
85+
end++;
86+
}
87+
88+
this.inputElement.selectionStart=start;
89+
this.inputElement.selectionEnd=end;
90+
91+
this.inputElement.focus();
92+
}
93+
}
94+
95+
}
96+
97+
defineCustomElement(FcDatePicker);
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*-
2+
* #%L
3+
* Year Month Calendar Add-on
4+
* %%
5+
* Copyright (C) 2021 - 2025 Flowing Code
6+
* %%
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* #L%
19+
*/
20+
package com.flowingcode.addons.ycalendar;
21+
22+
import com.flowingcode.vaadin.addons.demo.DemoSource;
23+
import com.vaadin.flow.component.html.Div;
24+
import com.vaadin.flow.component.notification.Notification;
25+
import com.vaadin.flow.router.PageTitle;
26+
import com.vaadin.flow.router.Route;
27+
import java.time.LocalDate;
28+
import java.util.Objects;
29+
30+
@DemoSource
31+
@PageTitle("DatePicker")
32+
@Route(value = "year-month-calendar/date-picker", layout = YearMonthCalendarDemoView.class)
33+
public class DatePickerDemo extends Div {
34+
35+
public DatePickerDemo() {
36+
37+
DatePickerEx field = new DatePickerEx(LocalDate.now());
38+
// #if vaadin eq 0
39+
add(new LocaleSelector(field::setI18n));
40+
// #endif
41+
42+
field.addValueChangeListener(ev->{
43+
Notification.show(Objects.toString(ev.getValue()));
44+
});
45+
46+
add(field);
47+
}
48+
49+
}
50+

0 commit comments

Comments
 (0)