diff --git a/README.md b/README.md
index 098c3ee..ee4aacb 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,38 @@
-[](https://vaadin.com/directory/component/template-add-on)
-[](https://vaadin.com/directory/component/template-add-on)
-[](https://jenkins.flowingcode.com/job/template-addon)
-[](https://mvnrepository.com/artifact/com.flowingcode.vaadin.addons/template-addon)
-[](https://javadoc.flowingcode.com/artifact/com.flowingcode.vaadin.addons/template-addon)
+[](https://vaadin.com/directory/component/date-time-range-picker-add-on)
+[](https://vaadin.com/directory/component/date-time-range-picker-add-on)
+[](https://jenkins.flowingcode.com/job/date-time-range-picker-addon)
+[](https://mvnrepository.com/artifact/com.flowingcode.vaadin.addons/date-time-range-picker-addon)
+[](https://javadoc.flowingcode.com/artifact/com.flowingcode.vaadin.addons/date-time-range-picker-addon)
-# Template Add-on
+# DateTimeRangePicker Add-on for Vaadin 24
-This is a template project for building new Vaadin 24 add-ons
+A component to create [Time Intervals](https://en.wikipedia.org/wiki/ISO_8601#Time_intervals) (_a time period defined by start and end points_) constrained by a time frame.
-## Features
+You use the UI to define time and date ranges in which the time intervals should be created, and then call the API to operate them.
+
+As an example, you could set it to create an interval **every weekend** from **8:30 to 12:30 AM** between the
+**1st and 15th of May 2025**.
Then, you can make the following queries:
-* List the features of your add-on in here
+1. How many intervals will be created? (4)
+2. Starting from today, when will the next interval occur?
+3. Is the 7th of May at 9:15 AM included in any interval? (It's not)
+ - How about the 5th of May at 9:00 AM? (Yes)
+4. How many intervals will have passed after the 9th of May? (2)
+5. How many intervals will remain after the 5th of May at 12:45? (3)
+
+... and more
+
+## Features
+- Customizable selection of date, time and days.
+- API to create and query time intervals.
## Online demo
-[Online demo here](http://addonsv24.flowingcode.com/template)
+[Online demo here](http://addonsv24.flowingcode.com/date-time-range-picker)
## Download release
-[Available in Vaadin Directory](https://vaadin.com/directory/component/template-add-on)
+[Available in Vaadin Directory](https://vaadin.com/directory/component/date-time-range-picker-add-on)
### Maven install
@@ -27,7 +41,7 @@ Add the following dependencies in your pom.xml file:
```xml
com.flowingcode.vaadin.addons
- template-addon
+ date-time-range-picker-addon
X.Y.Z
```
@@ -44,7 +58,7 @@ To see the demo, navigate to http://localhost:8080/
## Release notes
-See [here](https://github.com/FlowingCode/TemplateAddon/releases)
+See [here](https://github.com/FlowingCode/date-time-range-picker-addon/releases)
## Issue tracking
@@ -69,13 +83,65 @@ Then, follow these steps for creating a contribution:
This add-on is distributed under Apache License 2.0. For license terms, see LICENSE.txt.
-TEMPLATE_ADDON is written by Flowing Code S.A.
+DateTimeRangePicker is written by Flowing Code S.A.
# Developer Guide
## Getting started
-Add your code samples in this section
+```
+ DateTimeRangePicker addon = new DateTimeRangePicker(); (1)
+ addon.setMinDate(LocalDate.now()); (2)
+ addon.setMaxDate(LocalDate.now().plusDays(15)); (2)
+ addon.setWeekDays(DayOfWeek.MONDAY, DayOfWeek.FRIDAY); (3)
+```
+- (1) Instantiation.
+- (2) Date range selection will be constraint between **today** and **fifteen** days later.
+- (3) Component will have **Monday** and **Friday** selected by default.
+
+## Binding
+
+The API is exposed through the **DateTimeRange** class.
+
+```
+ DateTimeRangePicker addon = new DateTimeRangePicker();
+ Binder binder = new Binder<>(Pojo.class); (1)
+ binder.forField(addon).bind(Pojo::getDateTimeRange, Pojo::setDateTimeRange); (2)
+ binder.setBean(pojo); (3)
+```
+ - (1) The **Pojo** class is binded using its getter and setter methods (2).
+ - (2) The **DateTimeRangePicker** is binded. Its [value](https://vaadin.com/api/platform/current/com/vaadin/flow/component/HasValue.html) can be operated now.
+ - (3) The **DateTimeRange** instance is saved in the **Pojo** class or returned from it.
+
+
+You can operate time intervals with the **DateTimeRange** class, which acts as a holder.
+
+```
+ TimeInterval interval = pojo.getDateTimeRange().getNextInterval(); (1)
+ boolean includes = pojo.getDateTimeRange().includes(LocalDateTime.now()); (2)
+```
+ - (1) "Starting from today, when will the next interval occur?"
+ - (2) "Is today included in any interval?"
+
+You can also use the **TimeInterval** instance directly.
+
+```
+ boolean includes = interval != null && interval.includes(LocalDateTime.now());
+```
+
+## I18n support
+
+Customize a ``DateTimeRangePickerI18n`` instance and pass it to the component (1).
+
+```
+ DateTimeRangePicker addon = new DateTimeRangePicker();
+ addon.setI18n(new DateTimeRangePickerI18n() (1)
+ .setDatesTitle("Your custom title")
+ .setTimeChipsText("AM", "PM", "AM + PM")
+ .setTimesPlaceholder("Start time", "End time")
+ );
+```
+
## Special configuration when using Spring
diff --git a/pom.xml b/pom.xml
index 78099aa..627bce2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,14 +5,14 @@
4.0.0
com.flowingcode.vaadin.addons
- template-addon
+ date-time-range-picker-addon
1.0.0-SNAPSHOT
- Template Add-on
- Template Add-on for Vaadin Flow
+ DateTimeRangePicker
+ DateTimeRangePicker Vaadin Add-on
https://www.flowingcode.com/en/open-source/
- 24.4.6
+ 24.4.7
4.10.0
17
17
@@ -156,6 +156,13 @@
5.9.1
test
+
+
+ com.flowingcode.vaadin.addons
+ day-of-week-selector-addon
+ 1.0.1
+
+
diff --git a/src/main/java/com/flowingcode/vaadin/addons/datetimerangepicker/api/DateTimeRange.java b/src/main/java/com/flowingcode/vaadin/addons/datetimerangepicker/api/DateTimeRange.java
new file mode 100644
index 0000000..ddc5619
--- /dev/null
+++ b/src/main/java/com/flowingcode/vaadin/addons/datetimerangepicker/api/DateTimeRange.java
@@ -0,0 +1,353 @@
+/*-
+ * #%L
+ * DateTimeRangePicker Add-on
+ * %%
+ * Copyright (C) 2025 Flowing Code
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package com.flowingcode.vaadin.addons.datetimerangepicker.api;
+
+import java.io.Serializable;
+import java.time.DayOfWeek;
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Period;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A class to operate {@link TimeInterval} instances based on defined date and time constraints
+ *
+ * @author Izaguirre, Ezequiel
+ * @see TimeInterval
+ */
+public class DateTimeRange implements Serializable {
+
+ private static final DayOfWeek[] defaultWeekDays = DayOfWeek.values();
+ private static final LocalTime defaultStartTime = LocalTime.MIN;
+ private static final LocalTime defaultEndTime = LocalTime.MAX;
+
+ private final LocalDate startDate;
+ private final LocalDate endDate;
+ private final DayOfWeek[] weekDays = {
+ null, null, null, null, null, null, null
+ };
+ private LocalTime startTime = defaultStartTime;
+ private LocalTime endTime = defaultEndTime;
+
+
+ public DateTimeRange(LocalDate startDate, LocalDate endDate, Set weekDays) {
+ this.startDate = startDate;
+ this.endDate = endDate;
+ setWeekDays(weekDays);
+ }
+
+ public DateTimeRange(LocalDate startDate, LocalDate endDate) {
+ this(startDate, endDate, Set.of(defaultWeekDays));
+ }
+
+ public DateTimeRange(LocalDate startDate, LocalDate endDate, LocalTime startTime, LocalTime endTime,
+ Set weekDays) {
+ this(startDate, endDate, weekDays);
+ this.endTime = endTime;
+ this.startTime = startTime;
+ }
+
+ public DateTimeRange(LocalDate startDate, LocalDate endDate, LocalTime startTime, LocalTime endTime) {
+ this(startDate, endDate, startTime, endTime, Set.of(defaultWeekDays));
+ }
+
+ /**
+ * Sets time boundaries for the intervals
+ *
+ * @param startTime the starting point
+ * @param endTime the ending point (exclusive).
+ */
+ public void setDayDuration(LocalTime startTime, LocalTime endTime) {
+ this.startTime = startTime;
+ this.endTime = endTime;
+ }
+
+ /**
+ * Defines on which days of the week an interval should exist
+ *
+ * @param weekDays a list of days
+ */
+ public void setWeekDays(Set weekDays) {
+ DayOfWeek[] allWeekDays = DayOfWeek.values();
+
+ for (int i = 0; i < allWeekDays.length; i++) {
+ if (weekDays.contains(allWeekDays[i])) {
+ this.weekDays[i] = allWeekDays[i];
+ } else {
+ this.weekDays[i] = null;
+ }
+ }
+ }
+
+ /**
+ * Shorthand for setWeekDays
with the entire week set
+ */
+ public void setAllWeekDays() {
+ this.setWeekDays(Set.of(DayOfWeek.values()));
+ }
+
+ /**
+ * Returns the days of the week when intervals exist
+ */
+ public Set getWeekDays() {
+ Set days = new HashSet<>();
+ for (DayOfWeek day : this.weekDays) {
+ if (day != null) {
+ days.add(day);
+ }
+ }
+ return days;
+ }
+
+ /**
+ * Gets the intervals that conform to current constraints
+ */
+ public List getIntervals() {
+ return generateIntervals(this.startDate.atTime(this.startTime), this.endDate.atTime(this.endTime));
+ }
+
+ /**
+ * Checks if the given {@link LocalDate} is between any interval
+ */
+ public boolean includes(LocalDate date) {
+ int index = date.getDayOfWeek().getValue() - 1;
+ return this.weekDays[index] != null && insideRange(date);
+ }
+
+ /**
+ * Checks if the given {@link LocalDateTime} is between any interval
+ */
+ public boolean includes(LocalDateTime dateTime) {
+ boolean contains = false;
+ LocalDate date = dateTime.toLocalDate();
+ if (this.includes(date)) {
+ TimeInterval interval = new TimeInterval(
+ date.atTime(startTime),
+ date.atTime(endTime)
+ );
+ contains = interval.includes(dateTime);
+ }
+
+ return contains;
+ }
+
+ /**
+ * Gets the next interval that ends after given {@link LocalDate}
+ */
+ public TimeInterval getNextInterval(LocalDate from) {
+ LocalDateTime dateTime = LocalDateTime.of(from, this.startTime);
+ return this.getNextInterval(dateTime);
+ }
+
+ /**
+ * Gets the next interval that ends after current time
+ */
+ public TimeInterval getNextInterval() {
+ return this.getNextInterval(LocalDateTime.now());
+ }
+
+ /**
+ * Gets the next interval that ends after given {@link LocalDateTime}
+ */
+ public TimeInterval getNextInterval(LocalDateTime from) {
+ LocalDate date = from.toLocalDate();
+ LocalTime time = from.toLocalTime();
+ TimeInterval interval = null;
+
+ int index = date.getDayOfWeek().getValue() - 1;
+ if (this.weekDays[index] == null) {
+ DayOfWeek nextWeekDay = getNextDay(date.getDayOfWeek());
+ date = date.plusDays(daysBetween(nextWeekDay, date.getDayOfWeek()));
+ }
+ if (insideRange(date)) {
+ if (this.endTime.isAfter(time)) {
+ interval = new TimeInterval(
+ LocalDateTime.of(date, this.startTime),
+ LocalDateTime.of(date, this.endTime)
+ );
+ } else {
+ DayOfWeek nextWeekDay = getNextDay(date.getDayOfWeek());
+ LocalDate nextDay = date.plusDays(daysBetween(nextWeekDay, date.getDayOfWeek()));
+ if (insideRange(nextDay)) {
+ interval = new TimeInterval(
+ LocalDateTime.of(nextDay, this.startTime),
+ LocalDateTime.of(nextDay, this.endTime)
+ );
+ }
+ }
+ }
+
+ return interval;
+ }
+
+ /**
+ * Gets the intervals that end after current time
+ */
+ public List getIntervalsLeft() {
+ return this.getIntervalsLeft(LocalDateTime.now());
+ }
+
+ /**
+ * Gets the intervals that end after {@link LocalDateTime}
+ */
+ public List getIntervalsLeft(LocalDateTime from) {
+ List result = new ArrayList<>();
+ TimeInterval nextInterval = getNextInterval(from);
+
+ if (nextInterval != null) {
+ result.addAll(generateIntervals(nextInterval.getStartDate(), this.endDate.atTime(LocalTime.MAX)));
+ }
+
+ return result;
+ }
+
+ /**
+ * Gets the intervals that end after given {@link LocalDate}
+ */
+ public List getIntervalsLeft(LocalDate from) {
+ return this.getIntervalsLeft(from.atTime(LocalTime.MIN));
+ }
+
+ /**
+ * Gets the intervals that ended before current time
+ */
+ public List getPastIntervals() {
+ return this.getPastIntervals(LocalDateTime.now());
+ }
+
+ /**
+ * Gets the intervals that ended before given {@link LocalDate}
+ */
+ public List getPastIntervals(LocalDate from) {
+ return this.getPastIntervals(from.atTime(LocalTime.MIN));
+ }
+
+ /**
+ * Gets the intervals that ended before given {@link LocalDateTime}
+ */
+ public List getPastIntervals(LocalDateTime from) {
+ TimeInterval nextInterval = getNextInterval(from);
+
+ from = nextInterval == null ? this.getEndDate().atTime(LocalTime.MAX) : nextInterval.getStartDate();
+
+ return new ArrayList<>(generateIntervals(this.startDate.atTime(LocalTime.MIN), from));
+ }
+
+ /**
+ * Gets the time interval duration (or time period)
+ */
+ public Duration getDayDuration() {
+ return Duration.between(startTime, endTime);
+ }
+
+ /**
+ * Gets the amount of days between the start and (exclusive) end dates
+ */
+ public Period getDaysSpan() {
+ return Period.between(startDate, endDate);
+ }
+
+ /**
+ * Gets the start date
+ */
+ public LocalDate getStartDate() {
+ return this.startDate;
+ }
+
+ /**
+ * Gets the end date (exclusive)
+ */
+ public LocalDate getEndDate() {
+ return this.endDate;
+ }
+
+ /**
+ * Gets the {@link LocalTime} when intervals start
+ */
+ public LocalTime getStartTime() {
+ return this.startTime;
+ }
+
+ /**
+ * Gets the {@link LocalTime} when intervals end (exclusive)
+ */
+ public LocalTime getEndTime() {
+ return this.endTime;
+ }
+
+ // Utils
+ private DayOfWeek getNextDay(DayOfWeek previous) {
+ int index = previous.getValue() - 1;
+ DayOfWeek[] allWeekDays = DayOfWeek.values();
+ DayOfWeek next;
+ do {
+ index = (index + 1) % allWeekDays.length;
+ }
+ while ((next = this.weekDays[index]) == null);
+ return next;
+ }
+
+ private int daysBetween(DayOfWeek to, DayOfWeek from) {
+ int offset = to.getValue() - from.getValue();
+ if (offset <= 0) {
+ offset = 7 + offset;
+ }
+ return offset;
+ }
+
+ private List generateIntervals(LocalDateTime from, LocalDateTime to) {
+ LocalDate startDate = from.toLocalDate();
+ LocalTime startTime = from.toLocalTime();
+ LocalDate endDate = to.toLocalDate();
+ int days = Period.between(startDate, endDate).getDays();
+ int i = 0;
+ List entities = new ArrayList<>();
+ DayOfWeek firstDay = startDate.getDayOfWeek();
+ if (this.weekDays[firstDay.getValue() - 1] == null || !this.endTime.isAfter(startTime)) {
+ DayOfWeek nextDay = getNextDay(startDate.getDayOfWeek());
+ i = daysBetween(nextDay, firstDay);
+ }
+
+ while (i < days) {
+ LocalDate current = startDate.plusDays(i);
+ LocalDateTime start = LocalDateTime.of(current, this.startTime);
+ LocalDateTime end = LocalDateTime.of(current, this.endTime);
+ TimeInterval timeInterval = new TimeInterval(start, end);
+ entities.add(timeInterval);
+
+ DayOfWeek lastDay = current.getDayOfWeek();
+ DayOfWeek nextDay = getNextDay(current.getDayOfWeek());
+ i += daysBetween(nextDay, lastDay);
+ }
+
+ return entities;
+ }
+
+ private boolean insideRange(LocalDate date) {
+ return (this.startDate.isEqual(date) || this.startDate.isBefore(date))
+ &&
+ (this.endDate.isAfter(date));
+ }
+}
diff --git a/src/main/java/com/flowingcode/vaadin/addons/datetimerangepicker/api/TimeInterval.java b/src/main/java/com/flowingcode/vaadin/addons/datetimerangepicker/api/TimeInterval.java
new file mode 100644
index 0000000..b1a7ae4
--- /dev/null
+++ b/src/main/java/com/flowingcode/vaadin/addons/datetimerangepicker/api/TimeInterval.java
@@ -0,0 +1,103 @@
+/*-
+ * #%L
+ * DateTimeRangePicker Add-on
+ * %%
+ * Copyright (C) 2025 Flowing Code
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package com.flowingcode.vaadin.addons.datetimerangepicker.api;
+
+import java.io.Serializable;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.util.Objects;
+
+/**
+ * A class that represents a time interval as defined by ISO 8601
+ *
+ * @author Izaguirre, Ezequiel
+ */
+public class TimeInterval implements Serializable, Comparable {
+
+ private final LocalDateTime startDate;
+ private final LocalDateTime endDate;
+
+ public TimeInterval(LocalDateTime start, LocalDateTime end) {
+ this.startDate = start;
+ this.endDate = end;
+ }
+
+ /**
+ * Returns the starting point of this interval
+ */
+ public LocalDateTime getStartDate() {
+ return startDate;
+ }
+
+ /**
+ * Returns the (exclusive) ending point of this interval
+ */
+ public LocalDateTime getEndDate() {
+ return endDate;
+ }
+
+ /**
+ * Checks whether the given {@link LocalDateTime} falls within this time interval
+ */
+ public boolean includes(LocalDateTime date) {
+ boolean startCheck = startDate.isBefore(date) || startDate.equals(date);
+ boolean endCheck = endDate.isAfter(date);
+ return startCheck && endCheck;
+ }
+
+ /**
+ * Checks whether this interval happens before the specified {@link LocalDateTime}
+ */
+ public boolean isBefore(LocalDateTime date) {
+ return endDate.isBefore(date) || endDate.equals(date);
+ }
+
+ /**
+ * Checks whether this interval happens after the specified {@link LocalDateTime}
+ */
+ public boolean isAfter(LocalDateTime date) {
+ return startDate.isAfter(date);
+ }
+
+ /**
+ * Returns the time duration (or time period) of this interval
+ */
+ public Duration getDuration() {
+ return Duration.between(startDate.toLocalTime(), endDate.toLocalTime());
+ }
+
+ @Override
+ public int compareTo(TimeInterval o) {
+ return this.startDate.compareTo(o.getStartDate());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof TimeInterval that)) {
+ return false;
+ }
+ return Objects.equals(startDate, that.startDate) && Objects.equals(endDate, that.endDate);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(startDate, endDate);
+ }
+}
diff --git a/src/main/java/com/flowingcode/vaadin/addons/datetimerangepicker/ui/ChipGroup.java b/src/main/java/com/flowingcode/vaadin/addons/datetimerangepicker/ui/ChipGroup.java
new file mode 100644
index 0000000..79ef98f
--- /dev/null
+++ b/src/main/java/com/flowingcode/vaadin/addons/datetimerangepicker/ui/ChipGroup.java
@@ -0,0 +1,209 @@
+/*-
+ * #%L
+ * DateTimeRangePicker Add-on
+ * %%
+ * Copyright (C) 2025 Flowing Code
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package com.flowingcode.vaadin.addons.datetimerangepicker.ui;
+
+import com.vaadin.flow.component.dependency.CssImport;
+import com.vaadin.flow.component.html.Span;
+import com.vaadin.flow.component.icon.Icon;
+import com.vaadin.flow.component.icon.VaadinIcon;
+import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
+import com.vaadin.flow.function.SerializableConsumer;
+import com.vaadin.flow.theme.lumo.LumoUtility.AlignItems;
+import com.vaadin.flow.theme.lumo.LumoUtility.Border;
+import com.vaadin.flow.theme.lumo.LumoUtility.BorderColor;
+import com.vaadin.flow.theme.lumo.LumoUtility.BorderRadius;
+import com.vaadin.flow.theme.lumo.LumoUtility.Display;
+import com.vaadin.flow.theme.lumo.LumoUtility.FlexWrap;
+import com.vaadin.flow.theme.lumo.LumoUtility.FontSize;
+import com.vaadin.flow.theme.lumo.LumoUtility.FontWeight;
+import com.vaadin.flow.theme.lumo.LumoUtility.Gap;
+import com.vaadin.flow.theme.lumo.LumoUtility.JustifyContent;
+import com.vaadin.flow.theme.lumo.LumoUtility.LineHeight;
+import com.vaadin.flow.theme.lumo.LumoUtility.Margin;
+import com.vaadin.flow.theme.lumo.LumoUtility.Padding;
+import com.vaadin.flow.theme.lumo.LumoUtility.Padding.Horizontal;
+import com.vaadin.flow.theme.lumo.LumoUtility.Padding.Vertical;
+import com.vaadin.flow.theme.lumo.LumoUtility.TextColor;
+import java.util.HashSet;
+import java.util.Set;
+
+// A single selection ChipGroup
+@CssImport("./styles/styles.css")
+class ChipGroup extends HorizontalLayout {
+
+ private final Set chips = new HashSet<>();
+ private Chip checkedChip;
+
+ public ChipGroup() {
+ addClassNames(
+ AlignItems.CENTER,
+ JustifyContent.END,
+ Gap.SMALL,
+ FlexWrap.WRAP,
+ Horizontal.SMALL,
+ Vertical.SMALL
+ );
+ }
+
+ public ChipGroup(Chip... chips) {
+ this();
+ for (Chip chip : chips) {
+ addChip(chip);
+ }
+ }
+
+ public Chip addChip(Chip chip) {
+ chips.add(chip);
+ chip.setParent(this);
+ add(chip);
+ return chip;
+ }
+
+ public boolean deleteChip(Chip chip) {
+ chip.setParent(null);
+ remove(chip);
+ return chips.remove(chip);
+ }
+
+ private void onChipChange(Chip chip) {
+ if (checkedChip != null) {
+ if (!checkedChip.equals(chip)) {
+ checkedChip.setChecked(false);
+ checkedChip = chip;
+ } else {
+ checkedChip = null;
+ }
+ } else {
+ checkedChip = chip;
+ }
+ }
+
+ public void setReadOnly(boolean readOnly) {
+ chips.forEach(c -> c.setReadOnly(readOnly));
+ }
+
+ static class Chip extends HorizontalLayout {
+
+ private final Icon checkIcon;
+ private boolean checked = false;
+ private final Span textField;
+ private boolean readOnly = false;
+ private ChipGroup parent;
+ private SerializableConsumer callback;
+
+ public Chip(String text) {
+ addClassNames(
+ Horizontal.MEDIUM,
+ Vertical.XSMALL,
+ "fc-dtrp-hoverable",
+ AlignItems.CENTER,
+ BorderRadius.LARGE,
+ Border.ALL,
+ BorderColor.CONTRAST_10,
+ Display.INLINE_FLEX,
+ Gap.SMALL
+ );
+ getStyle().setCursor("pointer");
+ checkIcon = VaadinIcon.CHECK.create();
+ checkIcon.setSize("14px");
+
+ textField = new Span(text);
+ textField.addClassNames(
+ FontSize.SMALL,
+ FontWeight.SEMIBOLD,
+ Margin.NONE,
+ Padding.NONE,
+ LineHeight.SMALL
+ );
+ addClickListener(ev -> {
+ if (!readOnly) {
+ this.checked = !this.checked;
+ toggle();
+ if (callback != null) {
+ callback.accept(this.checked);
+ }
+ if (parent != null) {
+ parent.onChipChange(this);
+ }
+ }
+ });
+
+ add(textField);
+ }
+
+ private void toggle() {
+ if (checked) {
+ removeClassName("fc-dtrp-unselected");
+ addClassName("fc-dtrp-selected");
+ addComponentAsFirst(checkIcon);
+ } else {
+ removeClassName("fc-dtrp-selected");
+ addClassName("fc-dtrp-unselected");
+ remove(checkIcon);
+ }
+ }
+
+ private void setParent(ChipGroup parent) {
+ this.parent = parent;
+ }
+
+ public void onPress(SerializableConsumer onClick) {
+ this.callback = onClick;
+ }
+
+ public void setChecked(boolean checked) {
+ this.checked = checked;
+ toggle();
+ }
+
+ public boolean isChecked() {
+ return checked;
+ }
+
+ public String getText() {
+ return textField.getText();
+ }
+
+ public void setText(String text) {
+ textField.setText(text);
+ }
+
+ public void setReadOnly(boolean readOnly) {
+ if(this.readOnly == readOnly) return;
+ this.readOnly = readOnly;
+
+ if (readOnly) {
+ removeClassName("fc-dtrp-hoverable");
+ removeClassName(TextColor.BODY);
+ addClassName(TextColor.SECONDARY);
+ getStyle().setCursor("default");
+ getElement().getStyle().set("border-style", "dashed");
+ } else {
+ addClassName("fc-dtrp-hoverable");
+ removeClassName(TextColor.SECONDARY);
+ addClassName(TextColor.BODY);
+ getElement().getStyle().set("border-style", "solid");
+ getStyle().setCursor("pointer");
+ }
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/flowingcode/vaadin/addons/datetimerangepicker/ui/Circle.java b/src/main/java/com/flowingcode/vaadin/addons/datetimerangepicker/ui/Circle.java
new file mode 100644
index 0000000..b710edf
--- /dev/null
+++ b/src/main/java/com/flowingcode/vaadin/addons/datetimerangepicker/ui/Circle.java
@@ -0,0 +1,55 @@
+/*-
+ * #%L
+ * DateTimeRangePicker Add-on
+ * %%
+ * Copyright (C) 2025 Flowing Code
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package com.flowingcode.vaadin.addons.datetimerangepicker.ui;
+
+import com.vaadin.flow.component.html.Div;
+import com.vaadin.flow.theme.lumo.LumoUtility.AlignItems;
+import com.vaadin.flow.theme.lumo.LumoUtility.Display;
+import com.vaadin.flow.theme.lumo.LumoUtility.JustifyContent;
+import com.vaadin.flow.theme.lumo.LumoUtility.Padding.Vertical;
+import com.vaadin.flow.theme.lumo.LumoUtility.Position;
+import com.vaadin.flow.theme.lumo.LumoUtility.Width;
+
+// Indicator circle
+class Circle extends Div {
+
+ private final Div circle;
+
+ public Circle() {
+ circle = new Div();
+
+ addClassNames(
+ Display.INLINE_FLEX,
+ AlignItems.CENTER,
+ JustifyContent.CENTER,
+ Vertical.XSMALL,
+ Position.ABSOLUTE,
+ Width.AUTO,
+ "fc-dtrp-circle"
+ );
+
+ add(circle);
+ }
+
+ public void setColor(String background) {
+ circle.getStyle().setBackgroundColor(background);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/flowingcode/vaadin/addons/datetimerangepicker/ui/DateTimeRangePicker.java b/src/main/java/com/flowingcode/vaadin/addons/datetimerangepicker/ui/DateTimeRangePicker.java
new file mode 100644
index 0000000..5f77de4
--- /dev/null
+++ b/src/main/java/com/flowingcode/vaadin/addons/datetimerangepicker/ui/DateTimeRangePicker.java
@@ -0,0 +1,652 @@
+/*-
+ * #%L
+ * DateTimeRangePicker Add-on
+ * %%
+ * Copyright (C) 2025 Flowing Code
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package com.flowingcode.vaadin.addons.datetimerangepicker.ui;
+
+import com.flowingcode.vaadin.addons.datetimerangepicker.api.DateTimeRange;
+import com.flowingcode.vaadin.addons.datetimerangepicker.ui.ChipGroup.Chip;
+import com.flowingcode.vaadin.addons.dayofweekselector.DayOfWeekSelector;
+import com.vaadin.flow.component.Component;
+import com.vaadin.flow.component.customfield.CustomField;
+import com.vaadin.flow.component.datepicker.DatePicker;
+import com.vaadin.flow.component.html.Div;
+import com.vaadin.flow.component.html.H5;
+import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
+import com.vaadin.flow.component.orderedlayout.VerticalLayout;
+import com.vaadin.flow.component.timepicker.TimePicker;
+import com.vaadin.flow.data.binder.HasValidator;
+import com.vaadin.flow.data.binder.Validator;
+import com.vaadin.flow.theme.lumo.LumoUtility.AlignItems;
+import com.vaadin.flow.theme.lumo.LumoUtility.AlignSelf;
+import com.vaadin.flow.theme.lumo.LumoUtility.Display;
+import com.vaadin.flow.theme.lumo.LumoUtility.Flex;
+import com.vaadin.flow.theme.lumo.LumoUtility.Gap;
+import com.vaadin.flow.theme.lumo.LumoUtility.Height;
+import com.vaadin.flow.theme.lumo.LumoUtility.JustifyContent;
+import com.vaadin.flow.theme.lumo.LumoUtility.Margin;
+import com.vaadin.flow.theme.lumo.LumoUtility.Padding;
+import com.vaadin.flow.theme.lumo.LumoUtility.Padding.Bottom;
+import com.vaadin.flow.theme.lumo.LumoUtility.Position;
+import com.vaadin.flow.theme.lumo.LumoUtility.Width;
+import java.time.DayOfWeek;
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.Period;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * A component to create Time Intervals based on date and time constraints
+ *
+ * @author Izaguirre, Ezequiel
+ */
+public class DateTimeRangePicker
+ extends CustomField implements HasValidator {
+
+ static final String defaultErrorMessage = "Invalid or incomplete fields remaining";
+
+ // Mandatory attributes for validation
+ DatePicker startDate;
+ DatePicker endDate;
+ TimePicker startTime;
+ TimePicker endTime;
+ DayOfWeekSelector weekDays;
+
+ // UI-only validation attributes (should change after validation check)
+ SpanLine daysDivider;
+ SpanLine timeDivider;
+ Circle dateCircle;
+ Circle timeCircle;
+ Circle daysCircle;
+
+ // Others
+ H5 datesTitle;
+ H5 daysTitle;
+ H5 timesTitle;
+ Component dateSelector;
+ Component daysSelector;
+ Component timeSelector;
+ Div verticalLine;
+ ChipGroup daysChipGroup;
+ Chip weekdaysChip;
+ Chip weekendChip;
+ Chip allDaysChip;
+ ChipGroup timeChipGroup;
+ Chip morningChip;
+ Chip afterNoonChip;
+ Chip allTimeChip;
+ List daysInitials;
+
+ private void setUI() {
+ addClassNames(
+ Width.AUTO,
+ Display.INLINE,
+ Margin.NONE,
+ Padding.SMALL
+ );
+
+ HorizontalLayout rootLayout = new HorizontalLayout();
+ rootLayout.addClassNames(
+ Padding.SMALL,
+ Width.FULL,
+ Height.FULL,
+ AlignItems.STRETCH,
+ Gap.MEDIUM
+ );
+
+ VerticalLayout mainLayout = new VerticalLayout();
+ dateSelector = getDateSelectors();
+ daysSelector = getDaysSelector();
+ timeSelector = getTimeSelectors();
+
+ verticalLine = new Div();
+ verticalLine.getStyle().setBackgroundColor("var(--lumo-contrast-10pct)");
+ verticalLine.setMinWidth("1px");
+ verticalLine.setMaxWidth("1px");
+ verticalLine.setMinHeight("100%");
+ verticalLine.setMaxHeight("100%");
+
+ mainLayout.addClassNames(
+ Gap.MEDIUM,
+ Display.INLINE_FLEX,
+ AlignItems.STRETCH,
+ Padding.NONE,
+ Flex.GROW
+ );
+
+ mainLayout.add(dateSelector, daysSelector, timeSelector);
+ rootLayout.add(verticalLine, mainLayout);
+ add(rootLayout);
+ }
+
+ void refreshUI() {
+ if (this.startDate.getValue() != null && this.endDate.getValue() != null) {
+ Period distance = Period.between(this.startDate.getValue(), this.endDate.getValue());
+ this.daysDivider.setText(formatPeriod(distance));
+ } else {
+ this.daysDivider.setEmptyText();
+ }
+ if (this.startTime.getValue() != null && this.endTime.getValue() != null) {
+ Duration duration = Duration.between(this.startTime.getValue(), this.endTime.getValue());
+ this.timeDivider.setText(formatDuration(duration));
+ } else {
+ this.timeDivider.setEmptyText();
+ }
+ }
+
+ // UI Components
+ private Component getDateSelectors() {
+ VerticalLayout layout = new VerticalLayout();
+ layout.addClassNames(Gap.SMALL, Padding.NONE, Gap.XSMALL);
+
+ Div headerWrapper = new Div();
+ headerWrapper.addClassNames(
+ Display.FLEX,
+ AlignItems.CENTER,
+ Gap.SMALL,
+ Position.RELATIVE, // Required for the circle
+ Bottom.SMALL
+ );
+
+ datesTitle = new H5("Select dates range");
+
+ this.dateCircle = new Circle();
+
+ headerWrapper.add(this.dateCircle, datesTitle);
+
+ this.startDate = new DatePicker();
+ this.startDate.setPlaceholder("Start date");
+ this.startDate.setClearButtonVisible(true);
+
+ this.endDate = new DatePicker();
+ this.endDate.setPlaceholder("End date");
+ this.endDate.setClearButtonVisible(true);
+
+ this.daysDivider = new SpanLine();
+
+ HorizontalLayout selectorLayout = new HorizontalLayout();
+ selectorLayout.addClassNames(Gap.SMALL);
+ selectorLayout.add(this.startDate, this.daysDivider, this.endDate);
+
+ layout.add(headerWrapper, selectorLayout);
+
+ return layout;
+
+ }
+
+ private Component getDaysSelector() {
+ VerticalLayout layout = new VerticalLayout();
+ layout.addClassNames(AlignItems.STRETCH, Bottom.NONE, Padding.NONE, Gap.XSMALL);
+
+ daysTitle = new H5("Select days");
+
+ HorizontalLayout headerLayout = new HorizontalLayout();
+ headerLayout.addClassNames(
+ AlignItems.CENTER,
+ Position.RELATIVE,
+ JustifyContent.BETWEEN
+ );
+
+ weekendChip = new Chip("Weekend");
+ weekdaysChip = new Chip("Weekdays");
+ allDaysChip = new Chip("All");
+
+ daysChipGroup = new ChipGroup(
+ weekendChip,
+ weekdaysChip,
+ allDaysChip
+ );
+
+ this.daysCircle = new Circle();
+
+ headerLayout.add(this.daysCircle, daysTitle, daysChipGroup);
+
+ this.weekDays = new DayOfWeekSelector();
+ this.weekDays.getChildren().forEach(e -> {
+ e.getStyle().setScale("1.15");
+ }
+ );
+ this.weekDays.addValueChangeListener(ev -> revalidate());
+ this.weekDays.setFirstDayOfWeek(DayOfWeek.SUNDAY);
+ this.weekDays.addClassNames(
+ AlignSelf.CENTER,
+ Padding.NONE,
+ Margin.NONE
+ );
+ this.daysInitials = List.of("S","M","T","W","T","F","S");
+ this.weekDays.setWeekDaysShort(this.daysInitials);
+
+ weekendChip.onPress(checked -> {
+ if (checked) {
+ this.weekDays.setValue(DayOfWeek.SUNDAY, DayOfWeek.SATURDAY);
+ }
+ this.weekDays.setReadOnly(checked);
+ revalidate();
+ });
+
+ weekdaysChip.onPress(checked -> {
+ if (checked) {
+ this.weekDays.setValue(
+ DayOfWeek.MONDAY,
+ DayOfWeek.TUESDAY,
+ DayOfWeek.WEDNESDAY,
+ DayOfWeek.THURSDAY,
+ DayOfWeek.FRIDAY
+ );
+ }
+ this.weekDays.setReadOnly(checked);
+ revalidate();
+ });
+
+ allDaysChip.onPress(checked -> {
+ if (checked) {
+ this.weekDays.setValue(
+ DayOfWeek.MONDAY,
+ DayOfWeek.TUESDAY,
+ DayOfWeek.WEDNESDAY,
+ DayOfWeek.THURSDAY,
+ DayOfWeek.FRIDAY,
+ DayOfWeek.SATURDAY,
+ DayOfWeek.SUNDAY
+ );
+ }
+ this.weekDays.setReadOnly(checked);
+ revalidate();
+ });
+
+ layout.add(headerLayout, this.weekDays);
+
+ return layout;
+ }
+
+ private Component getTimeSelectors() {
+ VerticalLayout layout = new VerticalLayout();
+ layout.addClassNames(AlignItems.STRETCH, Padding.NONE, Gap.XSMALL);
+
+ timesTitle = new H5("Select times range");
+
+ morningChip = new Chip("Morning");
+ afterNoonChip = new Chip("Afternoon");
+ allTimeChip = new Chip("All");
+ timeChipGroup = new ChipGroup(
+ morningChip,
+ afterNoonChip,
+ allTimeChip
+ );
+
+ this.startTime = new TimePicker();
+ this.startTime.setPlaceholder("Start time");
+ this.startTime.setStep(Duration.ofMinutes(30));
+ // Sets the TimePicker to use 24-hours format
+ this.startTime.setLocale(Locale.FRENCH);
+ this.startTime.setClearButtonVisible(true);
+
+ this.endTime = new TimePicker();
+ this.endTime.setPlaceholder("End time");
+ this.endTime.setStep(Duration.ofMinutes(30));
+ this.endTime.setLocale(Locale.FRENCH);
+ this.endTime.setClearButtonVisible(true);
+
+ morningChip.onPress(checked -> {
+ if (checked) {
+ LocalTime minDate = this.startTime.getMin() != null ? this.startTime.getMin() : LocalTime.MIN;
+ LocalTime maxDate = this.endTime.getMax() != null ? this.startTime.getMax() : LocalTime.MAX;
+ this.startTime.setValue(minDate);
+ this.endTime.setValue(maxDate.isBefore(LocalTime.NOON) ? maxDate : LocalTime.NOON);
+ this.startTime.setReadOnly(true);
+ this.endTime.setReadOnly(true);
+ revalidate();
+ }
+ this.startTime.setReadOnly(checked);
+ this.endTime.setReadOnly(checked);
+ });
+
+ afterNoonChip.onPress(checked -> {
+ if (checked) {
+ LocalTime minDate = this.startTime.getMin() != null ? this.startTime.getMin() : LocalTime.MIN;
+ LocalTime maxDate = this.endTime.getMax() != null ? this.startTime.getMax() : LocalTime.MAX;
+ this.startTime.setValue(minDate.isAfter(LocalTime.NOON) ? minDate : LocalTime.NOON);
+ this.endTime.setValue(maxDate.isBefore(LocalTime.NOON) ? maxDate : LocalTime.NOON);
+ this.endTime.setValue(maxDate);
+ revalidate();
+ }
+ this.startTime.setReadOnly(checked);
+ this.endTime.setReadOnly(checked);
+ });
+
+ allTimeChip.onPress(checked -> {
+ if (checked) {
+ LocalTime minDate = this.startTime.getMin() != null ? this.startTime.getMin() : LocalTime.MIN;
+ LocalTime maxDate = this.endTime.getMax() != null ? this.startTime.getMax() : LocalTime.MAX;
+ this.startTime.setValue(minDate);
+ this.endTime.setValue(maxDate);
+ revalidate();
+ }
+ this.startTime.setReadOnly(checked);
+ this.endTime.setReadOnly(checked);
+ });
+
+ this.timeDivider = new SpanLine();
+
+ HorizontalLayout headerLayout = new HorizontalLayout();
+ headerLayout.addClassNames(AlignItems.CENTER, Position.RELATIVE, JustifyContent.BETWEEN);
+
+ this.timeCircle = new Circle();
+
+ headerLayout.add(this.timeCircle, timesTitle, timeChipGroup);
+
+ HorizontalLayout selectorLayout = new HorizontalLayout();
+ selectorLayout.addClassNames(Gap.SMALL);
+ selectorLayout.add(this.startTime, this.timeDivider, this.endTime);
+
+ layout.add(headerLayout, selectorLayout);
+
+ return layout;
+ }
+
+ // Fires a revalidation when data changes
+ private void revalidate() {
+ getElement().executeJs("this.dispatchEvent(new Event('change'))");
+ }
+
+ // Custom field
+ @Override
+ protected DateTimeRange generateModelValue() {
+ return new DateTimeRange(
+ startDate.getValue(),
+ endDate.getValue(),
+ startTime.getValue(),
+ endTime.getValue(),
+ weekDays.getValue()
+ );
+ }
+
+ @Override
+ protected void setPresentationValue(DateTimeRange dateTimeRange) {
+
+ startDate.setValue(dateTimeRange.getStartDate());
+ endDate.setValue(dateTimeRange.getEndDate());
+ startTime.setValue(dateTimeRange.getStartTime());
+ endTime.setValue(dateTimeRange.getEndTime());
+ weekDays.setValue(dateTimeRange.getWeekDays());
+
+ daysDivider.setText(formatPeriod(dateTimeRange.getDaysSpan()));
+ Duration timeDuration = dateTimeRange.getDayDuration();
+ timeDivider.setText(formatDuration(timeDuration));
+ }
+
+ @Override
+ public Validator getDefaultValidator() {
+ return new DateTimeRangePickerValidator(this);
+ }
+
+ // api
+
+ /**
+ * Changes this component's visibility state
+ *
+ * @param visible whether this component should be visible
+ */
+ @Override
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+ this.setDatesVisible(visible);
+ this.setDaysVisible(visible);
+ this.setTimesVisible(visible);
+ }
+
+ /**
+ * Changes this component's read-only state
+ *
+ * @param readOnly whether this component should be read-only
+ */
+ @Override
+ public void setReadOnly(boolean readOnly) {
+ getElement().setProperty("readonly", readOnly);
+ this.setDaysReadOnly(readOnly);
+ this.setTimesReadOnly(readOnly);
+ this.setDatesReadOnly(readOnly);
+ this.timeChipGroup.setReadOnly(readOnly);
+ this.daysChipGroup.setReadOnly(readOnly);
+ }
+
+ /**
+ * Sets the maximum days distance between start and end dates
+ *
+ * @param max the maximum distance measured in days
+ */
+ public void setMaxDaysSpan(int max) {
+
+ this.startDate.addValueChangeListener(it -> {
+ LocalDate current = it.getValue();
+ LocalDate maxDate = current != null ? current.plusDays(max) : null;
+ if (max > 0) {
+ this.endDate.setMax(maxDate);
+ }
+ });
+
+ this.endDate.addValueChangeListener(it -> {
+ LocalDate current = it.getValue();
+ LocalDate minDate = current != null ? current.minusDays(max) : null;
+ if (max > 0) {
+ this.startDate.setMin(minDate);
+ }
+ }
+ );
+ }
+
+ /**
+ * Sets the minimum start date
+ *
+ * @param date the minimum date that can be selected
+ */
+ public void setMinDate(LocalDate date) {
+ this.startDate.setMin(date);
+ this.endDate.setMin(date);
+ }
+
+ /**
+ * Sets the maximum end date
+ *
+ * @param date the maximum date that can be selected
+ */
+ public void setMaxDate(LocalDate date) {
+ this.endDate.setMax(date);
+ this.startDate.setMax(date);
+ }
+
+ /**
+ * Sets the minimum start time
+ *
+ * @param time the minimum time that can be selected
+ */
+ public void setMinTime(LocalTime time) {
+ this.startTime.setMin(time);
+ this.endTime.setMin(time);
+ morningChip.setVisible(time.isBefore(LocalTime.NOON));
+ }
+
+ /**
+ * Sets the maximum end time
+ *
+ * @param time the maximum time that can be selected
+ */
+ public void setMaxTime(LocalTime time) {
+ this.endTime.setMax(time);
+ this.startTime.setMax(time);
+ afterNoonChip.setVisible(time.isAfter(LocalTime.NOON));
+ }
+
+ /**
+ * Sets the selected week days
+ *
+ * @param weekDays the days that will be selected
+ *
note that days not included will be deselected
+ */
+ public void setWeekDays(DayOfWeek... weekDays) {
+ this.weekDays.setValue(Set.of(weekDays));
+ }
+
+ /**
+ * Sets which day should be placed at the starting or left-most position
+ *
+ * @param weekDay the starting or left-most day
+ */
+ public void setFirstWeekDay(DayOfWeek weekDay) {
+ this.weekDays.setFirstDayOfWeek(weekDay);
+ }
+
+ /**
+ * Changes the date pickers' read-only state
+ *
+ * @param readOnly whether the date pickers should be read-only
+ */
+ public void setDatesReadOnly(boolean readOnly) {
+ this.startDate.setReadOnly(readOnly);
+ this.endDate.setReadOnly(readOnly);
+ }
+
+ /**
+ * Changes the date pickers' visibility state
+ *
+ * @param visible whether the date pickers should be visible
+ */
+ public void setDatesVisible(boolean visible) {
+ this.dateSelector.setVisible(visible);
+ }
+
+ /**
+ * Changes the days picker's read-only state
+ *
+ * @param readOnly whether the days picker should be read-only
+ */
+ public void setDaysReadOnly(boolean readOnly) {
+ this.weekDays.setReadOnly(readOnly);
+ this.daysChipGroup.setReadOnly(readOnly);
+ }
+
+ /**
+ * Changes the days picker's visibility state
+ *
+ * @param visible whether the days picker should be visible
+ */
+ public void setDaysVisible(boolean visible) {
+ this.daysSelector.setVisible(visible);
+ }
+
+ /**
+ * Changes the time pickers' read-only state
+ *
+ * @param readOnly whether the time pickers should be read-only
+ */
+ public void setTimesReadOnly(boolean readOnly) {
+ this.startTime.setReadOnly(readOnly);
+ this.endTime.setReadOnly(readOnly);
+ this.timeChipGroup.setReadOnly(readOnly);
+ }
+
+ /**
+ * Changes the time pickers' visibility state
+ *
+ * @param visible whether the time pickers should be visible
+ */
+ public void setTimesVisible(boolean visible) {
+ this.timeSelector.setVisible(visible);
+ }
+
+ /**
+ * Changes the left line indicator's visibility state
+ *
+ * @param visible whether the left indicator should be visible
+ */
+ public void setIndicatorVisible(boolean visible) {
+ this.verticalLine.setVisible(visible);
+ this.dateCircle.setVisible(visible);
+ this.daysCircle.setVisible(visible);
+ this.timeCircle.setVisible(visible);
+ }
+
+ /**
+ * Sets the minimum time gap for the time selection lists
+ *
+ * @param step the time difference between adjacent lists' items
+ */
+ public void setTimeStep(Duration step) {
+ this.startTime.setStep(step);
+ this.endTime.setStep(step);
+ }
+
+ /**
+ * Sets the time locale for the time selection lists
+ *
+ * @param locale the {@code Locale} to use for the time lists' items
+ */
+ public void setTimeLocale(Locale locale) {
+ this.startTime.setLocale(locale);
+ this.endTime.setLocale(locale);
+ }
+
+ /**
+ * Sets the custom text properties for internationalization purposes
+ *
+ * @param i18n instance to attach
+ * @see DateTimeRangePickerI18n
+ */
+ public void setI18n(DateTimeRangePickerI18n i18n) {
+ i18n.component = this;
+ for(Runnable action : i18n.actions) {
+ if(action != null) action.run();
+ }
+ }
+
+ public DateTimeRangePicker() {
+ this(defaultErrorMessage);
+ }
+
+ public DateTimeRangePicker(String errorMessage) {
+ super();
+ setErrorMessage(errorMessage);
+ setUI();
+ }
+
+ public DateTimeRangePicker(DateTimeRange defaultValue, String errorMessage) {
+ super(defaultValue);
+ setErrorMessage(errorMessage);
+ setUI();
+ }
+
+ public DateTimeRangePicker(DateTimeRange defaultValue) {
+ this(defaultValue, defaultErrorMessage);
+ }
+
+ public static String formatDuration(Duration duration) {
+ return String.format("%02d:%02d:%02d",
+ duration.toHoursPart(),
+ duration.toMinutesPart(),
+ duration.toSecondsPart()
+ );
+ }
+
+ protected static String formatPeriod(Period period) {
+ return String.format("%dD", period.getDays());
+ }
+
+}
diff --git a/src/main/java/com/flowingcode/vaadin/addons/datetimerangepicker/ui/DateTimeRangePickerI18n.java b/src/main/java/com/flowingcode/vaadin/addons/datetimerangepicker/ui/DateTimeRangePickerI18n.java
new file mode 100644
index 0000000..807cf12
--- /dev/null
+++ b/src/main/java/com/flowingcode/vaadin/addons/datetimerangepicker/ui/DateTimeRangePickerI18n.java
@@ -0,0 +1,259 @@
+/*-
+ * #%L
+ * DateTimeRangePicker Add-on
+ * %%
+ * Copyright (C) 2025 Flowing Code
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package com.flowingcode.vaadin.addons.datetimerangepicker.ui;
+
+import com.vaadin.flow.function.SerializableRunnable;
+import java.io.Serializable;
+import java.time.DayOfWeek;
+import java.util.List;
+
+/**
+ * A class to help internationalize {@link DateTimeRangePicker} instances
+ *
+ * @author Izaguirre, Ezequiel
+ */
+public class DateTimeRangePickerI18n implements Serializable {
+
+ DateTimeRangePicker component;
+ final SerializableRunnable[] actions = {null, null, null, null, null, null, null, null};
+
+ /**
+ * Sets the date pickers' title
+ *
+ * @param text title for the pickers
+ */
+ public DateTimeRangePickerI18n setDatesTitle(String text) {
+ SerializableRunnable action = () -> {
+ component.datesTitle.setText(text);
+ };
+ actions[0] = action;
+ if(component != null) action.run();
+ return this;
+ }
+
+ /**
+ * Gets current date pickers' title
+ *
+ * @return date pickers' title or {@code null} if this object is not attached to a {@code DateTimeRangePicker} instance
+ */
+ public String getDatesTitle() {
+ return component != null ? component.datesTitle.toString() : null;
+ }
+
+ /**
+ * Sets the days picker's title
+ *
+ * @param text title for the days picker
+ */
+ public DateTimeRangePickerI18n setDaysTitle(String text) {
+ SerializableRunnable action = () -> {
+ component.daysTitle.setText(text);
+ };
+ actions[1] = action;
+ if(component != null) action.run();
+ return this;
+ }
+
+ /**
+ * Gets current days picker's title
+ *
+ * @return days picker's title or {@code null} if this object is not attached to a {@code DateTimeRangePicker} instance
+ */
+ public String getDaysTitle() {
+ return component != null ? component.daysTitle.toString() : null;
+ }
+
+ /**
+ * Sets the time pickers' title
+ *
+ * @param text title for the pickers
+ */
+ public DateTimeRangePickerI18n setTimesTitle(String text) {
+ SerializableRunnable action = () -> {
+ component.timesTitle.setText(text);
+ };
+ actions[2] = action;
+ if(component != null) action.run();
+ return this;
+ }
+
+ /**
+ * Gets current time pickers' title
+ *
+ * @return time pickers' title or {@code null} if this object is not attached to a {@code DateTimeRangePicker} instance
+ */
+ public String getTimesTitle() {
+ return component != null ? component.timesTitle.toString() : null;
+ }
+
+ /**
+ * Sets the time pickers' placeholder
+ *
+ * @param startTime placeholder for the start-time picker
+ * @param endTime placeholder for the end-time picker
+ */
+ public DateTimeRangePickerI18n setTimesPlaceholder(String startTime, String endTime) {
+ SerializableRunnable action = () -> {
+ component.startTime.setPlaceholder(startTime);
+ component.endTime.setPlaceholder(endTime);
+ };
+ actions[3] = action;
+ if(component != null) action.run();
+ return this;
+ }
+
+ /**
+ * Gets current time pickers' placeholder
+ *
+ * @return
+ * a list where the first element corresponds to the start-time picker's placeholder and the second to the end-time picker's placeholder
+ *
{@code null} if this object is not attached to a {@code DateTimeRangePicker} instance
+ */
+ public List getTimesPlaceholder() {
+ return component != null ? List.of(component.startTime.getPlaceholder(), component.endTime.getPlaceholder()) : null;
+ }
+
+ /**
+ * Sets the date pickers' placeholder
+ *
+ * @param startDate placeholder for the start-date picker
+ * @param endDate placeholder for the end-date picker
+ */
+ public DateTimeRangePickerI18n setDatesPlaceholder(String startDate, String endDate) {
+ SerializableRunnable action = () -> {
+ component.startDate.setPlaceholder(startDate);
+ component.endDate.setPlaceholder(endDate);
+ };
+ actions[4] = action;
+ if(component != null) action.run();
+ return this;
+ }
+
+ /**
+ * Gets current date pickers' placeholder
+ *
+ * @return
+ * a list where the first element corresponds to the start-date picker's placeholder and the second to the end-date picker's placeholder
+ *
{@code null} if this object is not attached to a {@code DateTimeRangePicker} instance
+ */
+ public List getDatesPlaceholder() {
+ return component != null ? List.of(component.startDate.getPlaceholder(), component.endDate.getPlaceholder()) : null;
+ }
+
+ /**
+ * Sets the week days picker's initials
+ *
+ * @param initials a list of initials for the 7 days of the week
+ *
The order of each depends on the order set on the picker
+ * @see DateTimeRangePicker#setFirstWeekDay(DayOfWeek)
+ */
+ public DateTimeRangePickerI18n setDayInitials(List initials) {
+ SerializableRunnable action = () -> {
+ component.daysInitials = initials;
+ component.weekDays.setWeekDaysShort(initials);
+ };
+ actions[5] = action;
+ if(component != null) action.run();
+ return this;
+ }
+
+ /**
+ * Gets current week days picker's initials
+ *
+ * @return
+ * a list of initials for the 7 days of the week. The order of each depends on the order set on the picker
+ *
{@code null} if this object is not attached to a {@code DateTimeRangePicker} instance
+ * @see DateTimeRangePicker#setFirstWeekDay(DayOfWeek)
+ */
+ public List getDayInitials() {
+ return component != null ? component.daysInitials : null;
+ }
+
+ /**
+ * Sets the time filter chips' text
+ *
+ * @param morning text for the morning-only chip
+ * @param afternoon text for the afternoon-only chip
+ * @param all text for the all-day chip
+ */
+ public DateTimeRangePickerI18n setTimeChipsText(String morning, String afternoon, String all) {
+ SerializableRunnable action = () -> {
+ component.morningChip.setText(morning);
+ component.afterNoonChip.setText(afternoon);
+ component.allTimeChip.setText(all);
+ };
+ actions[6] = action;
+ if(component != null) action.run();
+ return this;
+ }
+
+ /**
+ * Gets current time filter chips' text
+ *
+ * @return
+ * a list where the first element corresponds to the morning-only chip's text,
+ * the second to the afternoon-only chip's text and the third to the all-day chip's text
+ *
+ *
{@code null} if this object is not attached to a {@code DateTimeRangePicker} instance
+ */
+ public List getTimeChipsText() {
+ return component != null ? List.of(
+ component.morningChip.getText(),
+ component.afterNoonChip.getText(),
+ component.allTimeChip.getText()
+ ) : null;
+ }
+
+ /**
+ * Sets the days filter chips' text
+ *
+ * @param weekdays text for the monday-to-friday chip
+ * @param weekend text for the weekends-only chip
+ * @param all text for the all days chip
+ */
+ public DateTimeRangePickerI18n setDaysChipsText(String weekdays, String weekend, String all) {
+ SerializableRunnable action = () -> {
+ component.weekdaysChip.setText(weekdays);
+ component.weekendChip.setText(weekend);
+ component.allDaysChip.setText(all);
+ };
+ actions[7] = action;
+ if(component != null) action.run();
+ return this;
+ }
+
+ /**
+ * Gets current days filter chips' text
+ *
+ * @return
+ * a list where the first element corresponds to the monday-to-friday chip's text,
+ * the second to the weekends-only chip's text and the third to the all-days chip's text
+ *
+ *
{@code null} if this object is not attached to a {@code DateTimeRangePicker} instance
+ */
+ public List getDaysChipsText() {
+ return component != null ? List.of(
+ component.weekdaysChip.getText(),
+ component.weekendChip.getText(),
+ component.allDaysChip.getText()
+ ) : null;
+ }
+
+}
diff --git a/src/main/java/com/flowingcode/vaadin/addons/datetimerangepicker/ui/DateTimeRangePickerValidator.java b/src/main/java/com/flowingcode/vaadin/addons/datetimerangepicker/ui/DateTimeRangePickerValidator.java
new file mode 100644
index 0000000..efa13b7
--- /dev/null
+++ b/src/main/java/com/flowingcode/vaadin/addons/datetimerangepicker/ui/DateTimeRangePickerValidator.java
@@ -0,0 +1,120 @@
+/*-
+ * #%L
+ * DateTimeRangePicker Add-on
+ * %%
+ * Copyright (C) 2025 Flowing Code
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package com.flowingcode.vaadin.addons.datetimerangepicker.ui;
+
+import com.flowingcode.vaadin.addons.datetimerangepicker.api.DateTimeRange;
+import com.vaadin.flow.data.binder.ValidationResult;
+import com.vaadin.flow.data.binder.Validator;
+import com.vaadin.flow.data.binder.ValueContext;
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.util.Set;
+
+public class DateTimeRangePickerValidator implements Validator {
+
+ private final DateTimeRangePicker model;
+
+ private static final String SUCCESS_COLOR = "var(--lumo-primary-color)";
+ private static final String ERROR_COLOR = "var(--lumo-error-color)";
+
+ public DateTimeRangePickerValidator(DateTimeRangePicker model) {
+ this.model = model;
+ setManualValidation();
+ }
+
+ private void setManualValidation() {
+ model.startTime.setManualValidation(true);
+ model.endTime.setManualValidation(true);
+ model.startDate.setManualValidation(true);
+ model.endDate.setManualValidation(true);
+ model.weekDays.setManualValidation(true);
+ }
+
+ @Override
+ public ValidationResult apply(DateTimeRange data, ValueContext valueContext) {
+ ValidationResult result;
+ if (
+ data != null &&
+ dateValidation(data.getStartDate(), data.getEndDate())
+ & timeValidation(data.getStartTime(), data.getEndTime()) //Single & is intentional
+ & daysValidation(data.getWeekDays())
+ ) {
+ result = ValidationResult.ok();
+ } else {
+ result = ValidationResult.error(model.getErrorMessage());
+ }
+
+ this.model.refreshUI();
+ return result;
+ }
+
+ private boolean dateValidation(LocalDate start, LocalDate end) {
+ boolean nullCheck = (start != null && end != null);
+ if (!nullCheck) {
+ model.startDate.setInvalid(false);
+ model.endDate.setInvalid(false);
+ } else {
+ boolean dateCheck = start.isBefore(end);
+ if (!dateCheck) {
+ model.startDate.setInvalid(true);
+ model.endDate.setInvalid(true);
+ model.dateCircle.setColor(ERROR_COLOR);
+ } else {
+ model.startDate.setInvalid(false);
+ model.endDate.setInvalid(false);
+ model.dateCircle.setColor(SUCCESS_COLOR);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean timeValidation(LocalTime start, LocalTime end) {
+ boolean nullCheck = (start != null && end != null);
+ if (!nullCheck) {
+ model.startTime.setInvalid(false);
+ model.endTime.setInvalid(false);
+ } else {
+ boolean dateCheck = start.isBefore(end);
+ if (!dateCheck) {
+ model.startTime.setInvalid(true);
+ model.endTime.setInvalid(true);
+ model.timeCircle.setColor(ERROR_COLOR);
+ } else {
+ model.startTime.setInvalid(false);
+ model.endTime.setInvalid(false);
+ model.timeCircle.setColor(SUCCESS_COLOR);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean daysValidation(Set weekDays) {
+ boolean check = weekDays != null && !weekDays.isEmpty();
+ if (check) {
+ model.daysCircle.setColor(SUCCESS_COLOR);
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/com/flowingcode/vaadin/addons/datetimerangepicker/ui/SpanLine.java b/src/main/java/com/flowingcode/vaadin/addons/datetimerangepicker/ui/SpanLine.java
new file mode 100644
index 0000000..5871dec
--- /dev/null
+++ b/src/main/java/com/flowingcode/vaadin/addons/datetimerangepicker/ui/SpanLine.java
@@ -0,0 +1,89 @@
+/*-
+ * #%L
+ * DateTimeRangePicker Add-on
+ * %%
+ * Copyright (C) 2025 Flowing Code
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package com.flowingcode.vaadin.addons.datetimerangepicker.ui;
+
+import com.vaadin.flow.component.html.Div;
+import com.vaadin.flow.component.html.Paragraph;
+import com.vaadin.flow.theme.lumo.LumoUtility.AlignItems;
+import com.vaadin.flow.theme.lumo.LumoUtility.Display;
+import com.vaadin.flow.theme.lumo.LumoUtility.FlexDirection;
+import com.vaadin.flow.theme.lumo.LumoUtility.FontSize;
+import com.vaadin.flow.theme.lumo.LumoUtility.FontWeight;
+import com.vaadin.flow.theme.lumo.LumoUtility.JustifyContent;
+import com.vaadin.flow.theme.lumo.LumoUtility.LineHeight;
+import com.vaadin.flow.theme.lumo.LumoUtility.Margin;
+import com.vaadin.flow.theme.lumo.LumoUtility.Padding;
+import com.vaadin.flow.theme.lumo.LumoUtility.Padding.Bottom;
+import com.vaadin.flow.theme.lumo.LumoUtility.Padding.Horizontal;
+import com.vaadin.flow.theme.lumo.LumoUtility.TextAlignment;
+import com.vaadin.flow.theme.lumo.LumoUtility.Width;
+
+// Horizontal line between pickers
+class SpanLine extends Div {
+
+ private final Paragraph text;
+ private static final String EMPTY = "\u200E";
+
+ public SpanLine() {
+
+ addClassNames(
+ Display.INLINE_FLEX,
+ FlexDirection.COLUMN,
+ AlignItems.CENTER,
+ Bottom.XSMALL,
+ Width.AUTO,
+ Horizontal.SMALL,
+ JustifyContent.END
+ );
+ setMinHeight("var(--lumo-size-m)");
+ setMaxHeight("var(--lumo-size-m)");
+
+ Div line = new Div();
+ line.setMinWidth("4.5rem");
+ line.setMaxHeight("1px");
+ line.setMinHeight("1px");
+ line.getElement().getStyle().setBackgroundColor("var(--lumo-contrast-10pct)");
+
+ this.text = new Paragraph(EMPTY);
+ text.addClassNames(
+ TextAlignment.CENTER,
+ FontSize.SMALL,
+ Padding.NONE,
+ Margin.NONE,
+ FontWeight.SEMIBOLD,
+ LineHeight.SMALL
+ );
+ text.getStyle().setColor("var(--lumo-secondary-text-color)");
+
+ add(line, text);
+
+ }
+
+ public void setText(String text) {
+ if (text.isEmpty()) {
+ text = EMPTY;
+ }
+ this.text.setText(text);
+ }
+
+ public void setEmptyText() {
+ this.text.setText(EMPTY);
+ }
+}
diff --git a/src/main/java/com/flowingcode/vaadin/addons/template/TemplateAddon.java b/src/main/java/com/flowingcode/vaadin/addons/template/TemplateAddon.java
deleted file mode 100644
index c9ec694..0000000
--- a/src/main/java/com/flowingcode/vaadin/addons/template/TemplateAddon.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*-
- * #%L
- * Template Add-on
- * %%
- * Copyright (C) 2024 Flowing Code
- * %%
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * #L%
- */
-
-package com.flowingcode.vaadin.addons.template;
-
-import com.vaadin.flow.component.Tag;
-import com.vaadin.flow.component.dependency.JsModule;
-import com.vaadin.flow.component.dependency.NpmPackage;
-import com.vaadin.flow.component.html.Div;
-
-@SuppressWarnings("serial")
-@NpmPackage(value = "@polymer/paper-input", version = "3.2.1")
-@JsModule("@polymer/paper-input/paper-input.js")
-@Tag("paper-input")
-public class TemplateAddon extends Div {}
diff --git a/src/main/resources/META-INF/VAADIN/package.properties b/src/main/resources/META-INF/VAADIN/package.properties
index c66616f..b2b7e21 100644
--- a/src/main/resources/META-INF/VAADIN/package.properties
+++ b/src/main/resources/META-INF/VAADIN/package.properties
@@ -1 +1 @@
-vaadin.allowed-packages=com.flowingcode
+vaadin.allowed-packages=com.flowingcode
\ No newline at end of file
diff --git a/src/main/resources/META-INF/frontend/styles/static_addon_styles b/src/main/resources/META-INF/frontend/styles/static_addon_styles
deleted file mode 100644
index c2a6ed1..0000000
--- a/src/main/resources/META-INF/frontend/styles/static_addon_styles
+++ /dev/null
@@ -1 +0,0 @@
-Place add-on shareable styles in this folder
\ No newline at end of file
diff --git a/src/main/resources/META-INF/frontend/styles/styles.css b/src/main/resources/META-INF/frontend/styles/styles.css
new file mode 100644
index 0000000..1fb19ea
--- /dev/null
+++ b/src/main/resources/META-INF/frontend/styles/styles.css
@@ -0,0 +1,25 @@
+
+.fc-dtrp-circle {
+ left : calc((var(--lumo-space-m) + 12px - 6px) * -1);
+ background: var(--lumo-base-color);
+
+ div {
+ border-radius: 50%;
+ background: var(--lumo-primary-color);
+ min-height: 11px;
+ max-height: 11px;
+ min-width: 11px;
+ max-width: 11px;
+ }
+}
+
+.fc-dtrp-hoverable:hover, .fc-dtrp-selected {
+ background: var(--lumo-primary-color-50pct);
+ color: var(--lumo-primary-contrast-color);
+}
+
+.fc-dtrp-unselected {
+ background : initial
+}
+
+
diff --git a/src/test/java/com/flowingcode/vaadin/addons/AppShellConfiguratorImpl.java b/src/test/java/com/flowingcode/vaadin/addons/AppShellConfiguratorImpl.java
new file mode 100644
index 0000000..4be9fe7
--- /dev/null
+++ b/src/test/java/com/flowingcode/vaadin/addons/AppShellConfiguratorImpl.java
@@ -0,0 +1,9 @@
+package com.flowingcode.vaadin.addons;
+
+import com.vaadin.flow.component.page.AppShellConfigurator;
+import com.vaadin.flow.theme.Theme;
+
+@Theme("dtrp")
+public class AppShellConfiguratorImpl implements AppShellConfigurator {
+
+}
diff --git a/src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/BinderDemo.java b/src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/BinderDemo.java
new file mode 100644
index 0000000..72d4dfa
--- /dev/null
+++ b/src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/BinderDemo.java
@@ -0,0 +1,147 @@
+package com.flowingcode.vaadin.addons.datetimerangepicker;
+
+import com.flowingcode.vaadin.addons.demo.DemoSource;
+import com.flowingcode.vaadin.addons.datetimerangepicker.api.DateTimeRange;
+import com.flowingcode.vaadin.addons.datetimerangepicker.api.TimeInterval;
+import com.flowingcode.vaadin.addons.datetimerangepicker.ui.DateTimeRangePicker;
+import com.vaadin.flow.component.button.Button;
+import com.vaadin.flow.component.grid.Grid;
+import com.vaadin.flow.component.grid.dataview.GridListDataView;
+import com.vaadin.flow.component.notification.Notification;
+import com.vaadin.flow.component.notification.Notification.Position;
+import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
+import com.vaadin.flow.component.orderedlayout.VerticalLayout;
+import com.vaadin.flow.data.binder.Binder;
+import com.vaadin.flow.router.PageTitle;
+import com.vaadin.flow.router.Route;
+import com.vaadin.flow.theme.lumo.LumoUtility.AlignItems;
+import com.vaadin.flow.theme.lumo.LumoUtility.Margin.Horizontal;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.util.ArrayList;
+import java.util.List;
+
+@PageTitle("Binding")
+@SuppressWarnings("serial")
+@Route(value = "dtrp/binder", layout = DateTimeRangePickerTabbedView.class)
+@DemoSource
+public class BinderDemo extends VerticalLayout {
+
+ private final Button dateButton = new Button("Show dates range");
+ private final Button daysButton = new Button("Show days");
+ private final Button timeButton = new Button("Show times range");
+ private final Button interButton = new Button("Update intervals");
+ private final Grid grid = new Grid<>(TimeInterval.class, false);
+ private final List intervals = new ArrayList<>();
+
+ /*
+ DateTimeRangePicker::getValue returns a DateTimeRange instance.
+ You operate TimeInterval instances using that class.
+ TimeInterval represents a time interval (ISO 8601), defined by start and end points.
+ */
+ public BinderDemo() {
+ setSizeFull();
+ addClassNames(AlignItems.CENTER);
+
+ // Component creation
+ DateTimeRangePicker addon = new DateTimeRangePicker();
+ // Distance between start and end dates is at most 30 days
+ addon.setMaxDaysSpan(30);
+
+ // An object with getter/setter for DateTimeRange
+ Pojo pojo = new Pojo();
+ Binder binder = new Binder<>(Pojo.class);
+ binder.forField(addon)
+ .bind(Pojo::getDateTimeRange, Pojo::setDateTimeRange);
+ binder.setBean(pojo);
+
+ binder.addStatusChangeListener(ev -> {
+ boolean isValid = binder.isValid();
+ dateButton.setEnabled(isValid);
+ daysButton.setEnabled(isValid);
+ timeButton.setEnabled(isValid);
+ interButton.setEnabled(isValid);
+
+ });
+
+ Grid.Column firstCol = grid.addColumn(i -> i.getStartDate().getDayOfWeek()).setHeader("Week day")
+ .setSortable(true);
+ grid.addColumn(TimeInterval::getStartDate).setHeader("Start")
+ .setSortable(true);
+ grid.addColumn(TimeInterval::getEndDate).setHeader("End").setSortable(true);
+ // You can use this function to get a Duration formatted as hh:mm:ss
+ grid.addColumn(i -> DateTimeRangePicker.formatDuration(i.getDuration())).setHeader("Duration");
+
+ GridListDataView dataView = grid.setItems(intervals);
+ dataView.addItemCountChangeListener(c -> firstCol.setFooter("Total: " + c.getItemCount()));
+ grid.setWidth("75%");
+ grid.addClassName(Horizontal.AUTO);
+
+ HorizontalLayout buttonLayout = new HorizontalLayout();
+ buttonLayout.setAlignItems(Alignment.CENTER);
+ buttonLayout.add(dateButton, daysButton, timeButton);
+
+ dateButton.addClickListener(ev -> {
+ DateTimeRange result = pojo.getDateTimeRange();
+ LocalDate start = result.getStartDate();
+ LocalDate end = result.getEndDate();
+ Notification.show(
+ String.format("From %s %s to: %s %s (exclusive)",
+ start.getDayOfWeek(),
+ start,
+ end.getDayOfWeek(),
+ end
+ ),
+ 5000, Position.BOTTOM_CENTER
+ );
+ });
+ dateButton.setEnabled(false);
+
+ daysButton.addClickListener(ev -> {
+ DateTimeRange result = pojo.getDateTimeRange();
+ Notification.show(result.getWeekDays().toString(),
+ 5000, Position.BOTTOM_CENTER
+ );
+ });
+ daysButton.setEnabled(false);
+
+ timeButton.addClickListener(ev -> {
+ DateTimeRange result = pojo.getDateTimeRange();
+ LocalTime start = result.getStartTime();
+ LocalTime end = result.getEndTime();
+ Notification.show(
+ String.format("From %s to: %s (exclusive)",
+ start,
+ end
+ ),
+ 5000, Position.BOTTOM_CENTER
+ );
+ });
+ timeButton.setEnabled(false);
+
+ interButton.addClickListener(ev -> {
+ intervals.clear();
+ intervals.addAll(pojo.getDateTimeRange().getIntervals());
+ dataView.refreshAll();
+ });
+ interButton.setEnabled(false);
+
+ add(addon, buttonLayout, interButton, grid);
+
+ }
+
+ private static class Pojo {
+
+ private DateTimeRange dateTimeRange;
+
+ public DateTimeRange getDateTimeRange() {
+ return dateTimeRange;
+ }
+
+ public void setDateTimeRange(DateTimeRange dateTimeRange) {
+ this.dateTimeRange = dateTimeRange;
+ }
+ }
+
+
+}
diff --git a/src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/ComponentDemo.java b/src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/ComponentDemo.java
new file mode 100644
index 0000000..1b68868
--- /dev/null
+++ b/src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/ComponentDemo.java
@@ -0,0 +1,24 @@
+package com.flowingcode.vaadin.addons.datetimerangepicker;
+
+import com.flowingcode.vaadin.addons.demo.DemoSource;
+import com.flowingcode.vaadin.addons.datetimerangepicker.ui.DateTimeRangePicker;
+import com.vaadin.flow.component.orderedlayout.VerticalLayout;
+import com.vaadin.flow.router.PageTitle;
+import com.vaadin.flow.router.Route;
+import com.vaadin.flow.theme.lumo.LumoUtility.AlignItems;
+
+@PageTitle("Basic")
+@SuppressWarnings("serial")
+@Route(value = "dtrp/basic", layout = DateTimeRangePickerTabbedView.class)
+@DemoSource
+public class ComponentDemo extends VerticalLayout {
+
+ public ComponentDemo() {
+ setSizeFull();
+ addClassNames(AlignItems.CENTER);
+
+ // Component creation
+ DateTimeRangePicker addon = new DateTimeRangePicker();
+ add(addon);
+ }
+}
diff --git a/src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/ConstrainedDemo.java b/src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/ConstrainedDemo.java
new file mode 100644
index 0000000..232c37d
--- /dev/null
+++ b/src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/ConstrainedDemo.java
@@ -0,0 +1,38 @@
+package com.flowingcode.vaadin.addons.datetimerangepicker;
+
+import com.flowingcode.vaadin.addons.demo.DemoSource;
+import com.flowingcode.vaadin.addons.datetimerangepicker.ui.DateTimeRangePicker;
+import com.vaadin.flow.component.orderedlayout.VerticalLayout;
+import com.vaadin.flow.router.PageTitle;
+import com.vaadin.flow.router.Route;
+import com.vaadin.flow.theme.lumo.LumoUtility.AlignItems;
+import java.time.DayOfWeek;
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalTime;
+
+@PageTitle("Constrained")
+@SuppressWarnings("serial")
+@Route(value = "dtrp/constrained", layout = DateTimeRangePickerTabbedView.class)
+@DemoSource
+public class ConstrainedDemo extends VerticalLayout {
+
+ public ConstrainedDemo() {
+ setSizeFull();
+ addClassNames(AlignItems.CENTER);
+
+ // Component creation
+ DateTimeRangePicker addon = new DateTimeRangePicker();
+ addon.setMinDate(LocalDate.now());
+ addon.setMaxDate(LocalDate.now().plusDays(15));
+ addon.setWeekDays(DayOfWeek.MONDAY, DayOfWeek.FRIDAY);
+ addon.setDaysReadOnly(true);
+ addon.setTimeStep(Duration.ofMinutes(15));
+ addon.setMinTime(LocalTime.of(13, 30));
+ addon.setMaxTime(LocalTime.of(20, 0));
+
+ add(addon);
+
+ }
+
+}
diff --git a/src/test/java/com/flowingcode/vaadin/addons/template/TemplateDemoView.java b/src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/DateTimeRangePickerTabbedView.java
similarity index 71%
rename from src/test/java/com/flowingcode/vaadin/addons/template/TemplateDemoView.java
rename to src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/DateTimeRangePickerTabbedView.java
index 1954535..094c4c3 100644
--- a/src/test/java/com/flowingcode/vaadin/addons/template/TemplateDemoView.java
+++ b/src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/DateTimeRangePickerTabbedView.java
@@ -17,7 +17,7 @@
* limitations under the License.
* #L%
*/
-package com.flowingcode.vaadin.addons.template;
+package com.flowingcode.vaadin.addons.datetimerangepicker;
import com.flowingcode.vaadin.addons.DemoLayout;
import com.flowingcode.vaadin.addons.GithubLink;
@@ -27,12 +27,15 @@
@SuppressWarnings("serial")
@ParentLayout(DemoLayout.class)
-@Route("template")
-@GithubLink("https://github.com/FlowingCode/AddonStarter24")
-public class TemplateDemoView extends TabbedDemo {
+@Route("datetimerange")
+@GithubLink("https://github.com/FlowingCode/DateTimeRangeSelector")
+public class DateTimeRangePickerTabbedView extends TabbedDemo {
- public TemplateDemoView() {
- addDemo(TemplateDemo.class);
+ public DateTimeRangePickerTabbedView() {
+ addDemo(ComponentDemo.class);
+ addDemo(BinderDemo.class);
+ addDemo(StatesDemo.class);
+ addDemo(ConstrainedDemo.class);
setSizeFull();
}
}
diff --git a/src/test/java/com/flowingcode/vaadin/addons/template/DemoView.java b/src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/DemoView.java
similarity index 89%
rename from src/test/java/com/flowingcode/vaadin/addons/template/DemoView.java
rename to src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/DemoView.java
index a600c9d..7ec50d9 100644
--- a/src/test/java/com/flowingcode/vaadin/addons/template/DemoView.java
+++ b/src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/DemoView.java
@@ -18,7 +18,7 @@
* #L%
*/
-package com.flowingcode.vaadin.addons.template;
+package com.flowingcode.vaadin.addons.datetimerangepicker;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.BeforeEnterEvent;
@@ -31,6 +31,6 @@ public class DemoView extends VerticalLayout implements BeforeEnterObserver {
@Override
public void beforeEnter(BeforeEnterEvent event) {
- event.forwardTo(TemplateDemoView.class);
+ event.forwardTo(DateTimeRangePickerTabbedView.class);
}
}
diff --git a/src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/StatesDemo.java b/src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/StatesDemo.java
new file mode 100644
index 0000000..f73f76d
--- /dev/null
+++ b/src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/StatesDemo.java
@@ -0,0 +1,89 @@
+package com.flowingcode.vaadin.addons.datetimerangepicker;
+
+import com.flowingcode.vaadin.addons.datetimerangepicker.ui.DateTimeRangePickerI18n;
+import com.flowingcode.vaadin.addons.demo.DemoSource;
+import com.flowingcode.vaadin.addons.datetimerangepicker.ui.DateTimeRangePicker;
+import com.vaadin.flow.component.button.Button;
+import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
+import com.vaadin.flow.component.orderedlayout.VerticalLayout;
+import com.vaadin.flow.router.PageTitle;
+import com.vaadin.flow.router.Route;
+import com.vaadin.flow.theme.lumo.LumoUtility.AlignItems;
+import java.time.DayOfWeek;
+
+@PageTitle("States")
+@SuppressWarnings("serial")
+@Route(value = "dtrp/states", layout = DateTimeRangePickerTabbedView.class)
+@DemoSource
+public class StatesDemo extends VerticalLayout {
+
+ private boolean indicator = true;
+ private final boolean[] readOnly = {false, false, false};
+ private final boolean[] visible = {true, true, true};
+
+ public StatesDemo() {
+ setSizeFull();
+ addClassNames(AlignItems.CENTER);
+
+ // Component creation
+ DateTimeRangePicker addon = new DateTimeRangePicker();
+ // Set the first or leftmost day
+ addon.setFirstWeekDay(DayOfWeek.THURSDAY);
+ addon.setI18n(new DateTimeRangePickerI18n()
+ .setDatesTitle("Custom date title")
+ .setTimeChipsText("AM", "PM", "AM + PM")
+ .setTimesPlaceholder("Begin", "End")
+ );
+
+ VerticalLayout buttonLayout = new VerticalLayout();
+ buttonLayout.setAlignItems(Alignment.CENTER);
+ HorizontalLayout readOnlyLayout = new HorizontalLayout();
+ readOnlyLayout.setAlignItems(Alignment.CENTER);
+ HorizontalLayout visibleLayout = new HorizontalLayout();
+ visibleLayout.setAlignItems(Alignment.CENTER);
+
+ Button indicatorButton = new Button("Toggle indicator", ev ->
+ {
+ indicator = !indicator;
+ addon.setIndicatorVisible(indicator);
+ });
+
+ for (int i = 0; i < 3; i++) {
+ final int index = i;
+ readOnlyLayout.add(
+ new Button("Toggle " + (i == 0 ? "dates" : i == 1 ? "days" : "times") + " read only",
+ ev -> {
+ readOnly[index] = !readOnly[index];
+ if (index == 0) {
+ addon.setDatesReadOnly(readOnly[index]);
+ } else if (index == 1) {
+ addon.setDaysReadOnly(readOnly[index]);
+ } else {
+ addon.setTimesReadOnly(readOnly[index]);
+ }
+ }
+ )
+ );
+ visibleLayout.add(
+ new Button("Toggle " + (i == 0 ? "dates" : i == 1 ? "days" : "times") + " visible",
+ ev -> {
+ visible[index] = !visible[index];
+ if (index == 0) {
+ addon.setDatesVisible(visible[index]);
+ } else if (index == 1) {
+ addon.setDaysVisible(visible[index]);
+ } else {
+ addon.setTimesVisible(visible[index]);
+ }
+ }
+ )
+ );
+ }
+
+ buttonLayout.add(indicatorButton, readOnlyLayout, visibleLayout);
+
+ add(addon, buttonLayout);
+
+ }
+
+}
diff --git a/src/test/java/com/flowingcode/vaadin/addons/template/it/AbstractViewTest.java b/src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/it/AbstractViewTest.java
similarity index 98%
rename from src/test/java/com/flowingcode/vaadin/addons/template/it/AbstractViewTest.java
rename to src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/it/AbstractViewTest.java
index 1f7749b..d6f8d66 100644
--- a/src/test/java/com/flowingcode/vaadin/addons/template/it/AbstractViewTest.java
+++ b/src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/it/AbstractViewTest.java
@@ -18,7 +18,7 @@
* #L%
*/
-package com.flowingcode.vaadin.addons.template.it;
+package com.flowingcode.vaadin.addons.datetimerangepicker.it;
import com.vaadin.testbench.ScreenshotOnFailureRule;
import com.vaadin.testbench.TestBench;
diff --git a/src/test/java/com/flowingcode/vaadin/addons/template/it/ViewIT.java b/src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/it/ViewIT.java
similarity index 97%
rename from src/test/java/com/flowingcode/vaadin/addons/template/it/ViewIT.java
rename to src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/it/ViewIT.java
index 0e5f164..af82520 100644
--- a/src/test/java/com/flowingcode/vaadin/addons/template/it/ViewIT.java
+++ b/src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/it/ViewIT.java
@@ -18,7 +18,7 @@
* #L%
*/
-package com.flowingcode.vaadin.addons.template.it;
+package com.flowingcode.vaadin.addons.datetimerangepicker.it;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
diff --git a/src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/test/DateTimeRangeTest.java b/src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/test/DateTimeRangeTest.java
new file mode 100644
index 0000000..f8d3684
--- /dev/null
+++ b/src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/test/DateTimeRangeTest.java
@@ -0,0 +1,109 @@
+package com.flowingcode.vaadin.addons.datetimerangepicker.test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+
+import com.flowingcode.vaadin.addons.datetimerangepicker.api.DateTimeRange;
+import com.flowingcode.vaadin.addons.datetimerangepicker.api.TimeInterval;
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.util.Set;
+import org.junit.Test;
+
+public class DateTimeRangeTest {
+
+ @Test
+ public void testRanges() {
+ // Monday 7 to Monday 21 (exclusive) - 12:00 to 20:30 (exclusive)
+ LocalDate startDate = LocalDate.of(2025, 4, 7);
+ LocalDate endDate = LocalDate.of(2025, 4, 21);
+ LocalTime startTime = LocalTime.NOON;
+ LocalTime endTime = LocalTime.of(20, 30);
+
+ DateTimeRange dtr = new DateTimeRange(
+ startDate,
+ endDate,
+ startTime,
+ endTime,
+ Set.of(DayOfWeek.MONDAY, DayOfWeek.FRIDAY)
+ );
+
+ assertThat(dtr.includes(startDate), equalTo(true));
+ assertThat(dtr.includes(startDate.plusDays(4)), equalTo(true));
+ assertThat(dtr.includes(startDate.plusDays(7)), equalTo(true));
+ assertThat(dtr.includes(startDate.plusDays(11)), equalTo(true));
+ assertThat(dtr.includes(startDate.plusDays(14)), equalTo(false));
+ assertThat(dtr.includes(startDate.plusDays(18)), equalTo(false));
+ assertThat(dtr.includes(startDate.plusDays(3)), equalTo(false));
+ assertThat(dtr.includes(endDate), equalTo(false));
+
+ assertThat(dtr.includes(startDate.atTime(startTime)), equalTo(true));
+ assertThat(dtr.includes(startDate.atTime(LocalTime.of(13, 45))), equalTo(true));
+ assertThat(dtr.includes(startDate.atTime(LocalTime.MIN)), equalTo(false));
+ assertThat(dtr.includes(startDate.atTime(LocalTime.MAX)), equalTo(false));
+ assertThat(dtr.includes(startDate.atTime(LocalTime.of(20, 30))), equalTo(false));
+
+ assertThat(dtr.getWeekDays().contains(DayOfWeek.MONDAY), equalTo(true));
+ assertThat(dtr.getWeekDays().contains(DayOfWeek.FRIDAY), equalTo(true));
+ assertThat(dtr.getWeekDays().contains(DayOfWeek.THURSDAY), equalTo(false));
+ assertThat(dtr.getWeekDays().contains(DayOfWeek.SATURDAY), equalTo(false));
+ }
+
+ @Test
+ public void testIntervals() {
+ // Monday 7 to Monday 21 (exclusive) - 12:00 to 20:30 (exclusive)
+ LocalDate startDate = LocalDate.of(2025, 4, 7);
+ LocalDate endDate = LocalDate.of(2025, 4, 21);
+ LocalTime startTime = LocalTime.NOON;
+ LocalTime endTime = LocalTime.of(20, 30);
+
+ DateTimeRange dtr = new DateTimeRange(
+ startDate,
+ endDate,
+ startTime,
+ endTime,
+ Set.of(DayOfWeek.MONDAY, DayOfWeek.FRIDAY)
+ );
+
+ assertThat(dtr.getIntervals().size(), equalTo(4));
+ assertThat(dtr.getNextInterval(startDate), equalTo(new TimeInterval(
+ startDate.atTime(startTime),
+ startDate.atTime(endTime))
+ )
+ );
+ assertThat(dtr.getNextInterval(startDate.atTime(LocalTime.MIN)), equalTo(new TimeInterval(
+ startDate.atTime(startTime),
+ startDate.atTime(endTime))
+ )
+ );
+ assertThat(dtr.getNextInterval(startDate.atTime(LocalTime.NOON)), equalTo(new TimeInterval(
+ startDate.atTime(startTime),
+ startDate.atTime(endTime))
+ )
+ );
+ assertThat(dtr.getNextInterval(startDate.atTime(endTime)), equalTo(new TimeInterval(
+ startDate.plusDays(4).atTime(startTime),
+ startDate.plusDays(4).atTime(endTime))
+ )
+ );
+ assertThat(dtr.getNextInterval(startDate.atTime(LocalTime.MAX)), equalTo(new TimeInterval(
+ startDate.plusDays(4).atTime(startTime),
+ startDate.plusDays(4).atTime(endTime))
+ )
+ );
+ assertThat(dtr.getNextInterval(endDate), equalTo(null));
+ assertThat(dtr.getNextInterval(startDate.plusDays(19)), equalTo(null));
+
+ assertThat(dtr.getIntervalsLeft(startDate).size(), equalTo(4));
+ assertThat(dtr.getIntervalsLeft(startDate.atTime(endTime)).size(), equalTo(3));
+ assertThat(dtr.getIntervalsLeft(startDate.plusDays(5)).size(), equalTo(2));
+ assertThat(dtr.getIntervalsLeft(startDate.plusDays(19)).size(), equalTo(0));
+
+ assertThat(dtr.getPastIntervals(startDate).size(), equalTo(0));
+ assertThat(dtr.getPastIntervals(startDate.atTime(endTime)).size(), equalTo(1));
+ assertThat(dtr.getPastIntervals(startDate.plusDays(5)).size(), equalTo(2));
+ assertThat(dtr.getPastIntervals(startDate.plusDays(19)).size(), equalTo(4));
+ }
+
+}
diff --git a/src/test/java/com/flowingcode/vaadin/addons/template/test/SerializationTest.java b/src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/test/SerializationTest.java
similarity index 88%
rename from src/test/java/com/flowingcode/vaadin/addons/template/test/SerializationTest.java
rename to src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/test/SerializationTest.java
index 1ee78c3..a851a56 100644
--- a/src/test/java/com/flowingcode/vaadin/addons/template/test/SerializationTest.java
+++ b/src/test/java/com/flowingcode/vaadin/addons/datetimerangepicker/test/SerializationTest.java
@@ -17,9 +17,9 @@
* limitations under the License.
* #L%
*/
-package com.flowingcode.vaadin.addons.template.test;
+package com.flowingcode.vaadin.addons.datetimerangepicker.test;
-import com.flowingcode.vaadin.addons.template.TemplateAddon;
+import com.flowingcode.vaadin.addons.datetimerangepicker.ui.DateTimeRangePicker;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -44,7 +44,7 @@ private void testSerializationOf(Object obj) throws IOException, ClassNotFoundEx
@Test
public void testSerialization() throws ClassNotFoundException, IOException {
try {
- testSerializationOf(new TemplateAddon());
+ testSerializationOf(new DateTimeRangePicker());
} catch (Exception e) {
Assert.fail("Problem while testing serialization: " + e.getMessage());
}
diff --git a/src/test/java/com/flowingcode/vaadin/addons/template/TemplateDemo.java b/src/test/java/com/flowingcode/vaadin/addons/template/TemplateDemo.java
deleted file mode 100644
index 5f6e6ee..0000000
--- a/src/test/java/com/flowingcode/vaadin/addons/template/TemplateDemo.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.flowingcode.vaadin.addons.template;
-
-import com.flowingcode.vaadin.addons.demo.DemoSource;
-import com.vaadin.flow.component.html.Div;
-import com.vaadin.flow.router.PageTitle;
-import com.vaadin.flow.router.Route;
-
-@DemoSource
-@PageTitle("Template Add-on Demo")
-@SuppressWarnings("serial")
-@Route(value = "demo", layout = TemplateDemoView.class)
-public class TemplateDemo extends Div {
-
- public TemplateDemo() {
- add(new TemplateAddon());
- }
-}
diff --git a/src/test/resources/META-INF/frontend/styles/shared-styles.css b/src/test/resources/META-INF/frontend/styles/shared-styles.css
deleted file mode 100644
index 6680e2d..0000000
--- a/src/test/resources/META-INF/frontend/styles/shared-styles.css
+++ /dev/null
@@ -1 +0,0 @@
-/*Demo styles*/
\ No newline at end of file
diff --git a/src/test/resources/META-INF/frontend/themes/dtrp/theme.json b/src/test/resources/META-INF/frontend/themes/dtrp/theme.json
new file mode 100644
index 0000000..b007ffd
--- /dev/null
+++ b/src/test/resources/META-INF/frontend/themes/dtrp/theme.json
@@ -0,0 +1,3 @@
+{
+ "lumoImports" : [ "typography", "color", "spacing", "badge", "utility" ]
+}
diff --git a/test b/test
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/test
@@ -0,0 +1 @@
+