Repository: primefaces/primefaces (java)

Issue:
Tooltip: for accessibility provide multiple showEvent as default mouseover, focus and touchstart and ability to close by Esc keypress
### Description

If, in addition to hover, you also want to have the tooltip for the focus, for example, you have to define a second p:tooltip with the identical text for the same target. Disadvantage: the aria-describedby attribute of the target element then already contains two IDs for both target elements and reads out the same tooltip text twice. The same phenomenon occurs if you create a third p:tooltip for showEvent=“touchstart”. The IDs for the aria-describedby attribute of the target element are collected with each additional p:tooltip for the same target element and all texts with identical content are read out repeatedly.

In addition, the tooltip cannot be closed by pressing the Esc key. Both aspects needed for accessibility reasons.

### Describe the solution you would like

- default showEvent: mouseover focus touchstart (currently only default mouseover event displaying the tooltip)
  -> no need to define multiple p:tooltip for different showEvent having indentical value to be shown readed repeated by screenreader
- dito default hideEvent: mouseout blur touchend
- Esc keypress should close the tooltip for the target element by default

### Additional context

_No response_

Pull request description:
Fix #13521: 15.0.2 Tooltip allow multiple events and ESC to close
Fix #13521: 15.0.2 Tooltip allow multiple events and ESC to close

Patch:
diff --git a/primefaces-showcase/src/main/java/org/primefaces/showcase/view/input/CalendarJava8View.java b/primefaces-showcase/src/main/java/org/primefaces/showcase/view/input/CalendarJava8View.java
index 7ede301165..5df7f7ffea 100644
--- a/primefaces-showcase/src/main/java/org/primefaces/showcase/view/input/CalendarJava8View.java
+++ b/primefaces-showcase/src/main/java/org/primefaces/showcase/view/input/CalendarJava8View.java
@@ -139,6 +139,15 @@ public class CalendarJava8View implements Serializable {
         facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, "Date Selected", event.getObject().format(formatter)));
     }
 
+    public void onDateRangeSelect(SelectEvent<List<LocalDate>> event) {
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
+        String range = event.getObject().get(0).format(formatter)
+                + " - "
+                + event.getObject().get(event.getObject().size() - 1).format(formatter);
+        facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, "Range Selected", range));
+    }
+
     public void onDateTimeSelect(SelectEvent<LocalDateTime> event) {
         FacesContext facesContext = FacesContext.getCurrentInstance();
         DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
diff --git a/primefaces-showcase/src/main/webapp/ui/input/datepicker/datePickerJava8.xhtml b/primefaces-showcase/src/main/webapp/ui/input/datepicker/datePickerJava8.xhtml
index 4d7becb426..68c23ed3c8 100644
--- a/primefaces-showcase/src/main/webapp/ui/input/datepicker/datePickerJava8.xhtml
+++ b/primefaces-showcase/src/main/webapp/ui/input/datepicker/datePickerJava8.xhtml
@@ -70,7 +70,9 @@
 
                     <div class="field col-12 md:col-4">
                         <p:outputLabel for="@next" value="Week Picker" />
-                        <p:datePicker id="week" view="week" value="#{calendarJava8View.week}" />
+                        <p:datePicker id="week" view="week" value="#{calendarJava8View.week}">
+                            <p:ajax event="dateSelect" listener="#{calendarJava8View.onDateRangeSelect}" update="msgs" />
+                        </p:datePicker>
                     </div>
 
                     <div class="field col-12 md:col-4">
diff --git a/primefaces-showcase/src/main/webapp/ui/multimedia/barcode.xhtml b/primefaces-showcase/src/main/webapp/ui/multimedia/barcode.xhtml
index c2bc26ef45..eb16ee0e22 100644
--- a/primefaces-showcase/src/main/webapp/ui/multimedia/barcode.xhtml
+++ b/primefaces-showcase/src/main/webapp/ui/multimedia/barcode.xhtml
@@ -16,7 +16,7 @@
             &lt;dependency&gt;
                &lt;groupId>uk.org.okapibarcode&lt;/groupId&gt;
                &lt;artifactId>okapibarcode&lt;/artifactId&gt;
-               &lt;version>0.4.9&lt;/version&gt;
+               &lt;version>0.5.0&lt;/version&gt;
             &lt;/dependency&gt;
             </code></pre>
     </ui:define>
@@ -39,16 +39,16 @@
                 <p:barcode value="0123456789" type="code128"/>
 
                 <h:outputText value="EAN-8"/>
-                <p:barcode value="1234567" type="ean8"/>
+                <p:barcode value="12345670" type="ean8"/>
 
                 <h:outputText value="EAN-13"/>
-                <p:barcode value="123456789012+12345" type="ean13"/>
+                <p:barcode value="1234567890128" type="ean13"/>
 
                 <h:outputText value="UPC-A (PNG)"/>
-                <p:barcode value="01234567895" type="upca" format="png"/>
+                <p:barcode value="012345678958" type="upca" format="png"/>
 
                 <h:outputText value="UPC-E (Vertical)"/>
-                <p:barcode value="0123457" type="upce" orientation="90"/>
+                <p:barcode value="0123456" type="upce" orientation="90"/>
 
                 <h:outputText value="PDF417"/>
                 <p:barcode value="0123456789" type="pdf417"/>
diff --git a/primefaces-showcase/src/main/webapp/ui/overlay/tooltip/tooltipOptions.xhtml b/primefaces-showcase/src/main/webapp/ui/overlay/tooltip/tooltipOptions.xhtml
index ed9ddbd5c4..aa00da2b58 100644
--- a/primefaces-showcase/src/main/webapp/ui/overlay/tooltip/tooltipOptions.xhtml
+++ b/primefaces-showcase/src/main/webapp/ui/overlay/tooltip/tooltipOptions.xhtml
@@ -46,6 +46,10 @@
             <p:inputText id="focus" title="Enter your username"/>
             <p:tooltip for="focus" showEvent="focus" hideEvent="blur"/>
 
+            <h5>Multiple Events</h5>
+            <p:inputText id="multiple" title="Enter your username"/>
+            <p:tooltip for="multiple" showEvent="mouseover focus touchstart" hideEvent="mouseout blur touchend"/>
+
             <h5>Track Mouse</h5>
             <p:graphicImage id="track" name="/images/primefaces-logo.svg" library="showcase"/>
             <p:tooltip for="track" value="Enter your username" trackMouse="true"/>
diff --git a/primefaces/src/main/java/org/primefaces/component/barcode/BarcodeHandler.java b/primefaces/src/main/java/org/primefaces/component/barcode/BarcodeHandler.java
index 0cdeb549b3..879de681d5 100644
--- a/primefaces/src/main/java/org/primefaces/component/barcode/BarcodeHandler.java
+++ b/primefaces/src/main/java/org/primefaces/component/barcode/BarcodeHandler.java
@@ -105,6 +105,25 @@ public class BarcodeHandler extends BaseDynamicContentHandler {
             int quietZoneHorizontal = Integer.parseInt(params.get("mh"));
             int quietZoneVertical = Integer.parseInt(params.get("mv"));
 
+            // #13548 be lenient with EAN13 and EAN8 for check digits
+            if (generator instanceof Ean) {
+                Ean ean = (Ean) generator;
+                if (ean.getMode() == Ean.Mode.EAN13 && value.length() == 13) {
+                    value = value.substring(0, 12);
+                }
+                else if (ean.getMode() == Ean.Mode.EAN8 && value.length() == 8) {
+                    value = value.substring(0, 7);
+                }
+            }
+
+            // #13548 be lenient with UPC-A for check digits
+            if (generator instanceof Upc) {
+                Upc upc = (Upc) generator;
+                if (upc.getMode() == Mode.UPCA && value.length() == 12) {
+                    value = value.substring(0, 11);
+                }
+            }
+
             generator.setHumanReadableLocation(HumanReadableLocation.valueOf(hrp.toUpperCase(Locale.ROOT)));
             generator.setContent(value);
             generator.setQuietZoneHorizontal(quietZoneHorizontal);
diff --git a/primefaces/src/main/java/org/primefaces/component/datepicker/DatePickerRenderer.java b/primefaces/src/main/java/org/primefaces/component/datepicker/DatePickerRenderer.java
index 260ec7e79a..d8e916d811 100644
--- a/primefaces/src/main/java/org/primefaces/component/datepicker/DatePickerRenderer.java
+++ b/primefaces/src/main/java/org/primefaces/component/datepicker/DatePickerRenderer.java
@@ -377,7 +377,8 @@ public class DatePickerRenderer extends BaseCalendarRenderer {
 
                 return multi;
             case "range":
-                List<String> rangeStr = CalendarUtils.splitRange(submittedValue, datePicker.calculatePattern(), datePicker.getRangeSeparator());
+                List<String> rangeStr = CalendarUtils.splitRange(submittedValue, datePicker.calculatePattern(),
+                        datePicker.getRangeSeparator(), "week".equals(datePicker.getView()));
                 List<Object> range = new ArrayList<>();
                 if (rangeStr.size() == 2) {
                     for (int i = 0; i < rangeStr.size(); i++) {
diff --git a/primefaces/src/main/java/org/primefaces/component/splitbutton/SplitButtonRenderer.java b/primefaces/src/main/java/org/primefaces/component/splitbutton/SplitButtonRenderer.java
index 6563fed247..909f79bce9 100644
--- a/primefaces/src/main/java/org/primefaces/component/splitbutton/SplitButtonRenderer.java
+++ b/primefaces/src/main/java/org/primefaces/component/splitbutton/SplitButtonRenderer.java
@@ -106,9 +106,6 @@ public class SplitButtonRenderer extends MenuItemAwareRenderer {
         writer.startElement("div", button);
         writer.writeAttribute("id", clientId, "id");
         writer.writeAttribute("class", styleClass, "id");
-        writer.writeAttribute(HTML.ARIA_HASPOPUP, Boolean.toString(hasOverlay), null);
-        writer.writeAttribute(HTML.ARIA_CONTROLS, menuId, null);
-        writer.writeAttribute(HTML.ARIA_EXPANDED, "false", null);
         if (button.getStyle() != null) {
             writer.writeAttribute("style", button.getStyle(), "id");
         }
@@ -116,7 +113,7 @@ public class SplitButtonRenderer extends MenuItemAwareRenderer {
         encodeDefaultButton(context, button, buttonId);
         OverlayPanel customOverlay = button.getCustomOverlay();
         if (customOverlay != null || button.getElementsCount() > 0) {
-            encodeMenuIcon(context, button, menuButtonId);
+            encodeMenuIcon(context, button, menuButtonId, menuId, hasOverlay);
             if (hasOverlay) {
                 encodeMenu(context, button, menuId);
             }
@@ -187,7 +184,7 @@ public class SplitButtonRenderer extends MenuItemAwareRenderer {
         writer.endElement("button");
     }
 
-    protected void encodeMenuIcon(FacesContext context, SplitButton button, String id) throws IOException {
+    protected void encodeMenuIcon(FacesContext context, SplitButton button, String id, String menuId, boolean hasOverlay) throws IOException {
         ResponseWriter writer = context.getResponseWriter();
         String buttonClass = SplitButton.MENU_ICON_BUTTON_CLASS;
         if (button.isDisabled()) {
@@ -199,6 +196,9 @@ public class SplitButtonRenderer extends MenuItemAwareRenderer {
         writer.writeAttribute("name", id, null);
         writer.writeAttribute("type", "button", null);
         writer.writeAttribute("class", buttonClass, null);
+        writer.writeAttribute(HTML.ARIA_HASPOPUP, hasOverlay ? "menu" : "false", null);
+        writer.writeAttribute(HTML.ARIA_CONTROLS, menuId, null);
+        writer.writeAttribute(HTML.ARIA_EXPANDED, "false", null);
 
         if (button.isDisabled()) {
             writer.writeAttribute("disabled", "disabled", "disabled");
diff --git a/primefaces/src/main/java/org/primefaces/util/CalendarUtils.java b/primefaces/src/main/java/org/primefaces/util/CalendarUtils.java
index 4b52098383..2162b226ab 100644
--- a/primefaces/src/main/java/org/primefaces/util/CalendarUtils.java
+++ b/primefaces/src/main/java/org/primefaces/util/CalendarUtils.java
@@ -41,7 +41,8 @@ import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeParseException;
 import java.time.temporal.Temporal;
-import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 import java.util.Locale;
@@ -637,19 +638,30 @@ public class CalendarUtils {
         return now;
     }
 
-    public static List<String> splitRange(final String dateRange, final String pattern, final String separator) {
-        final String token = Constants.SPACE + separator + Constants.SPACE;
-        if (pattern.contains(token)) {
-            throw new FacesException("Pattern '" + pattern + "' contains separator '" + token + "'");
+    public static List<String> splitRange(String input, String datePattern, String rangeSeparator,
+                                          boolean week) {
+
+        // security check
+        String token = Constants.SPACE + rangeSeparator + Constants.SPACE;
+        if (datePattern.contains(token)) {
+            throw new FacesException("Pattern '" + datePattern + "' contains separator '" + token + "'");
         }
-        final List<String> dates = new ArrayList<>();
-        if (!dateRange.contains(token)) {
-            return dates;
+
+        // fast return
+        if (!input.contains(token)) {
+            return Collections.emptyList();
         }
-        final int index = dateRange.indexOf(token);
-        dates.add(dateRange.substring(0, index));
-        dates.add(dateRange.substring(index + token.length()));
-        return dates;
+
+        int tokenIndex = input.indexOf(token);
+        String from = input.substring(0, tokenIndex);
+        String to = input.substring(tokenIndex + token.length());
+        if (week) {
+            // hardcoded pattern, see JS:
+            // formattedValue += ' (' + this.options.locale.weekHeader + ' ' + week + ')';
+            to = to.substring(0, to.lastIndexOf(" ("));
+        }
+
+        return Arrays.asList(from, to);
     }
 
 }
diff --git a/primefaces/src/main/resources/META-INF/resources/primefaces/accordion/accordion.js b/primefaces/src/main/resources/META-INF/resources/primefaces/accordion/accordion.js
index f13b77b3db..ed469ba434 100644
--- a/primefaces/src/main/resources/META-INF/resources/primefaces/accordion/accordion.js
+++ b/primefaces/src/main/resources/META-INF/resources/primefaces/accordion/accordion.js
@@ -367,7 +367,8 @@ PrimeFaces.widget.AccordionPanel = PrimeFaces.widget.BaseWidget.extend({
                 params: [
                     { name: this.id + '_contentLoad', value: true },
                     { name: this.id + '_currentTab', value: panel.attr('id') },
-                    { name: this.id + '_tabindex', value: parseInt(panel.index() / 2) }
+                    { name: this.id + '_tabindex', value: parseInt(panel.index() / 2) },
+                    { name: this.id + '_active', value: this.cfg.active }
                 ],
                 onsuccess: function(responseXML, status, xhr) {
                     PrimeFaces.ajax.Response.handle(responseXML, status, xhr, {
diff --git a/primefaces/src/main/resources/META-INF/resources/primefaces/core/core.js b/primefaces/src/main/resources/META-INF/resources/primefaces/core/core.js
index 12a7d2996d..db7eb5e2b3 100644
--- a/primefaces/src/main/resources/META-INF/resources/primefaces/core/core.js
+++ b/primefaces/src/main/resources/META-INF/resources/primefaces/core/core.js
@@ -1739,6 +1739,7 @@
                 "jumpToPageDropdownLabel": "Jump to Page Dropdown",
                 "jumpToPageInputLabel": "Jump to Page Input",
                 "lastPageLabel": "Last Page",
+                "listLabel": "Options List",
                 "listView": "List View",
                 "moveAllToSource": "Move All to Source",
                 "moveAllToTarget": "Move All to Target",
diff --git a/primefaces/src/main/resources/META-INF/resources/primefaces/forms/forms.selectoneradio.js b/primefaces/src/main/resources/META-INF/resources/primefaces/forms/forms.selectoneradio.js
index 50489d2105..5ed96965c0 100644
--- a/primefaces/src/main/resources/META-INF/resources/primefaces/forms/forms.selectoneradio.js
+++ b/primefaces/src/main/resources/META-INF/resources/primefaces/forms/forms.selectoneradio.js
@@ -171,7 +171,10 @@ PrimeFaces.widget.SelectOneRadio = PrimeFaces.widget.BaseWidget.extend({
                 else
                     radio = target.children('.ui-radiobutton-box'); //custom layout
 
-                radio.trigger('click.selectOneRadio');
+                // #13546 only trigger radio if its not already active
+                if (!radio.hasClass('ui-state-active')) {
+                    radio.trigger('click.selectOneRadio');
+                }
 
                 e.preventDefault();
             });
diff --git a/primefaces/src/main/resources/META-INF/resources/primefaces/forms/forms.splitbutton.js b/primefaces/src/main/resources/META-INF/resources/primefaces/forms/forms.splitbutton.js
index e0c6c8da67..f1187c7dbd 100644
--- a/primefaces/src/main/resources/META-INF/resources/primefaces/forms/forms.splitbutton.js
+++ b/primefaces/src/main/resources/META-INF/resources/primefaces/forms/forms.splitbutton.js
@@ -120,6 +120,7 @@ PrimeFaces.widget.SplitButton = PrimeFaces.widget.BaseWidget.extend({
         this.button.data('primefaces-overlay-target', true).find('*').data('primefaces-overlay-target', true);
 
         // toggle menu
+        this.menuButton.attr('aria-label', this.getAriaLabel('listLabel'));
         this.menuButton.on('click.splitbutton', function() {
             if(!$this.cfg.disabled && $this.menu.is(':hidden'))
                 $this.show();
@@ -487,7 +488,7 @@ PrimeFaces.widget.SplitButton = PrimeFaces.widget.BaseWidget.extend({
                 onEntered: function() {
                     $this.bindPanelEvents();
 
-                    $this.jq.attr('aria-expanded', true);
+                    $this.menuButton.attr('aria-expanded', true);
 
                     if ($this.cfg.filter && $this.cfg.filterInputAutoFocus) {
                         $this.filterInput.trigger('focus');
@@ -513,7 +514,7 @@ PrimeFaces.widget.SplitButton = PrimeFaces.widget.BaseWidget.extend({
                     $this.unbindPanelEvents();
                 },
                 onExited: function() {
-                    $this.jq.attr('aria-expanded', false);
+                    $this.menuButton.attr('aria-expanded', false);
                     $this.menuitems.filter('.ui-state-hover').removeClass('ui-state-hover');
                     $this.menuButton.removeClass('ui-state-focus');
                 }
diff --git a/primefaces/src/main/resources/META-INF/resources/primefaces/locales/locale-en.js b/primefaces/src/main/resources/META-INF/resources/primefaces/locales/locale-en.js
index 6fa73b3254..64d3d9c538 100644
--- a/primefaces/src/main/resources/META-INF/resources/primefaces/locales/locale-en.js
+++ b/primefaces/src/main/resources/META-INF/resources/primefaces/locales/locale-en.js
@@ -87,6 +87,7 @@ if (window.PrimeFaces) {
       jumpToPageDropdownLabel: "Jump to Page Dropdown",
       jumpToPageInputLabel: "Jump to Page Input",
       lastPageLabel: "Last Page",
+      listLabel: "Options List",
       listView: "List View",
       moveAllToSource: "Move All to Source",
       moveAllToTarget: "Move All to Target",
diff --git a/primefaces/src/main/resources/META-INF/resources/primefaces/tooltip/tooltip.js b/primefaces/src/main/resources/META-INF/resources/primefaces/tooltip/tooltip.js
index a061cb9fc7..a24ac52207 100644
--- a/primefaces/src/main/resources/META-INF/resources/primefaces/tooltip/tooltip.js
+++ b/primefaces/src/main/resources/META-INF/resources/primefaces/tooltip/tooltip.js
@@ -64,8 +64,8 @@ PrimeFaces.widget.Tooltip = PrimeFaces.widget.BaseWidget.extend({
     init: function(cfg) {
         this._super(cfg);
 
-        this.cfg.showEvent = this.cfg.showEvent ? this.cfg.showEvent + '.tooltip' : 'mouseenter.tooltip';
-        this.cfg.hideEvent = this.cfg.hideEvent ? this.cfg.hideEvent + '.tooltip' : 'mouseleave.tooltip';
+        this.cfg.showEvent = this.cfg.showEvent ? this.cfg.showEvent.split(' ').map(e => e + '.tooltip').join(' ') : 'mouseenter.tooltip';
+        this.cfg.hideEvent = this.cfg.hideEvent ? this.cfg.hideEvent.split(' ').map(e => e + '.tooltip').join(' ') : 'mouseleave.tooltip';
         this.cfg.showEffect = this.cfg.showEffect ? this.cfg.showEffect : 'fade';
         this.cfg.hideEffect = this.cfg.hideEffect ? this.cfg.hideEffect : 'fade';
         this.cfg.showDelay = PrimeFaces.utils.defaultNumeric(this.cfg.showDelay, 400);
@@ -130,7 +130,7 @@ PrimeFaces.widget.Tooltip = PrimeFaces.widget.BaseWidget.extend({
         this.cfg.globalSelector = this.cfg.globalSelector || 'a,:input,:button';
         var $this = this;
 
-        var namespace = '.tooltip' + this.id;
+        var namespace = '.' + this.id;
         $(document).off(this.cfg.showEvent + namespace + ' ' + this.cfg.hideEvent + namespace, this.cfg.globalSelector)
             .on(this.cfg.showEvent + namespace, this.cfg.globalSelector, function(e) {
                 $this._hide();
@@ -210,7 +210,7 @@ PrimeFaces.widget.Tooltip = PrimeFaces.widget.BaseWidget.extend({
             var matchResult = this.cfg.target.match('@\\((.+)\\)');
             var targetSelector = (matchResult && matchResult.length > 1) ? matchResult[1] : "*[id='" + this.target.attr('id') + "']";
 
-            var namespace = '.tooltip' + this.id;
+            var namespace = '.' + this.id;
             $(document).off(this.cfg.showEvent + namespace + ' ' + this.cfg.hideEvent + namespace, targetSelector)
                 .on(this.cfg.showEvent + namespace, targetSelector, function(e) {
                     if ($this.cfg.trackMouse) {
@@ -247,7 +247,7 @@ PrimeFaces.widget.Tooltip = PrimeFaces.widget.BaseWidget.extend({
                         $this.show();
                     }
                 })
-                .on(this.cfg.hideEvent + '.tooltip', function(e) {
+                .on(this.cfg.hideEvent, function(e) {
                     $this.shouldAllowHideBasedOnMouseTarget(e);
                 });
 
@@ -289,6 +289,28 @@ PrimeFaces.widget.Tooltip = PrimeFaces.widget.BaseWidget.extend({
                 $this.shouldAllowHideBasedOnMouseTarget(e);
             });
     },
+
+    /**
+     * Sets up the escape key event listener to hide the tooltip when escape is pressed.
+     * @private
+     */
+    bindEscapeKey: function() {
+        var $this = this;
+        $(document).off('keydown.tooltip' + this.id).on('keydown.tooltip' + this.id, function(e) {
+            if(!e.isDefaultPrevented() && e.key === 'Escape' && $this.isVisible()) {
+                $this.hide();
+            }
+        });
+    },
+
+    /**
+     * Removes the escape key event listener.
+     * @private
+     */
+    unbindEscapeKey: function() {
+        $(document).off('keydown.tooltip' + this.id);
+    },
+
     /**
      * Determines if the tooltip should be allowed to hide based on the mouse event's related target.
      * @param {JQuery.TriggeredEvent} e - The jQuery event object that triggered the mouse event.
@@ -444,6 +466,8 @@ PrimeFaces.widget.Tooltip = PrimeFaces.widget.BaseWidget.extend({
             }
         }
 
+        this.bindEscapeKey();
+
         // allow pointer events when autohide is disabled
         var pointerEvents = '';
         if (this.isAutoHide()) {
@@ -506,6 +530,8 @@ PrimeFaces.widget.Tooltip = PrimeFaces.widget.BaseWidget.extend({
                 $this.target = null;
                 $this.jq.children('.ui-tooltip-text').removeClass('ui-state-error');
             }
+
+            this.unbindEscapeKey();
         }
     },
 


Test patch:
diff --git a/primefaces/src/test/java/org/primefaces/util/CalendarUtilsTest.java b/primefaces/src/test/java/org/primefaces/util/CalendarUtilsTest.java
index 12f13be530..cff2f05e74 100644
--- a/primefaces/src/test/java/org/primefaces/util/CalendarUtilsTest.java
+++ b/primefaces/src/test/java/org/primefaces/util/CalendarUtilsTest.java
@@ -217,18 +217,33 @@ class CalendarUtilsTest {
 
     @Test
     void splitRange() {
-        List<String> splitRange = CalendarUtils.splitRange("2021-03-01 - 2021-03-31", "yyyy-MM-dd", "-");
+        List<String> splitRange = CalendarUtils.splitRange("2021-03-01 - 2021-03-31", "yyyy-MM-dd", "-", false);
         assertEquals(2, splitRange.size());
         assertEquals("2021-03-01", splitRange.get(0));
         assertEquals("2021-03-31", splitRange.get(1));
-        splitRange = CalendarUtils.splitRange("2021-03-01", "yyyy-MM-dd", "-");
+        splitRange = CalendarUtils.splitRange("2021-03-01", "yyyy-MM-dd", "-", false);
         assertTrue(splitRange.isEmpty());
-        splitRange = CalendarUtils.splitRange("", "yyyy-MM-dd", "-");
+        splitRange = CalendarUtils.splitRange("", "yyyy-MM-dd", "-", false);
         assertTrue(splitRange.isEmpty());
 
         assertThrows(FacesException.class, () -> {
-            CalendarUtils.splitRange("2021 - 03 - 01 - 2021 - 03 - 31", "yyyy - MM - dd", "-");
+            CalendarUtils.splitRange("2021 - 03 - 01 - 2021 - 03 - 31", "yyyy - MM - dd", "-", false);
         });
     }
 
+    @Test
+    void splitRangeWeek() {
+        List<String> splitRange = CalendarUtils.splitRange("2021-03-01 - 2021-03-31 (Wk 21)", "yyyy-MM-dd", "-", true);
+        assertEquals(2, splitRange.size());
+        assertEquals("2021-03-01", splitRange.get(0));
+        assertEquals("2021-03-31", splitRange.get(1));
+        splitRange = CalendarUtils.splitRange("2021-03-01 (Wk 21)", "yyyy-MM-dd", "-", true);
+        assertTrue(splitRange.isEmpty());
+        splitRange = CalendarUtils.splitRange("", "yyyy-MM-dd (Wk 21)", "-", true);
+        assertTrue(splitRange.isEmpty());
+
+        assertThrows(FacesException.class, () -> {
+            CalendarUtils.splitRange("2021 - 03 - 01 - 2021 - 03 - 31 (Wk 21)", "yyyy - MM - dd", "-", true);
+        });
+    }
 }