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: Tooltip allow multiple events and ESC to close
Fix #13521: 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/frontend/packages/components/src/accordion/accordion.widget.js b/primefaces/src/main/frontend/packages/components/src/accordion/accordion.widget.js
index 2d1ba82f4a..92dbaa2a66 100644
--- a/primefaces/src/main/frontend/packages/components/src/accordion/accordion.widget.js
+++ b/primefaces/src/main/frontend/packages/components/src/accordion/accordion.widget.js
@@ -367,7 +367,8 @@ PrimeFaces.widget.AccordionPanel = class AccordionPanel extends PrimeFaces.widge
                 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 } // #13551: normally set through hidden state input but necessary here for accordion not wrapped in form
                 ],
                 onsuccess: function(responseXML, status, xhr) {
                     PrimeFaces.ajax.Response.handle(responseXML, status, xhr, {
diff --git a/primefaces/src/main/frontend/packages/components/src/forms/forms.splitbutton.widget.ts b/primefaces/src/main/frontend/packages/components/src/forms/forms.splitbutton.widget.ts
index 2fe2c405a8..99f83470bb 100644
--- a/primefaces/src/main/frontend/packages/components/src/forms/forms.splitbutton.widget.ts
+++ b/primefaces/src/main/frontend/packages/components/src/forms/forms.splitbutton.widget.ts
@@ -177,6 +177,7 @@ export class SplitButton<Cfg extends SplitButtonCfg = SplitButtonCfg> extends Pr
         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', () => {
             if(!this.cfg.disabled && this.menu.is(':hidden'))
                 this.show();
@@ -544,7 +545,7 @@ export class SplitButton<Cfg extends SplitButtonCfg = SplitButtonCfg> extends Pr
                 onEntered: () => {
                     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');
@@ -567,7 +568,7 @@ export class SplitButton<Cfg extends SplitButtonCfg = SplitButtonCfg> extends Pr
                     this.unbindPanelEvents();
                 },
                 onExited: () => {
-                    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/frontend/packages/components/src/tooltip/tooltip.widget.js b/primefaces/src/main/frontend/packages/components/src/tooltip/tooltip.widget.js
index 72e4b394e5..36d1593dbd 100644
--- a/primefaces/src/main/frontend/packages/components/src/tooltip/tooltip.widget.js
+++ b/primefaces/src/main/frontend/packages/components/src/tooltip/tooltip.widget.js
@@ -64,8 +64,8 @@ PrimeFaces.widget.Tooltip = class Tooltip extends PrimeFaces.widget.BaseWidget {
     init(cfg) {
         super.init(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 = class Tooltip extends PrimeFaces.widget.BaseWidget {
         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 = class Tooltip extends PrimeFaces.widget.BaseWidget {
             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 = class Tooltip extends PrimeFaces.widget.BaseWidget {
                         $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 = class Tooltip extends PrimeFaces.widget.BaseWidget {
                 $this.shouldAllowHideBasedOnMouseTarget(e);
             });
     }
+
+    /**
+     * Sets up the escape key event listener to hide the tooltip when escape is pressed.
+     * @private
+     */
+    bindEscapeKey() {
+        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() {
+        $(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 = class Tooltip extends PrimeFaces.widget.BaseWidget {
             }
         }
 
+        this.bindEscapeKey();
+
         // allow pointer events when autohide is disabled
         var pointerEvents = '';
         if (this.isAutoHide()) {
@@ -506,6 +530,8 @@ PrimeFaces.widget.Tooltip = class Tooltip extends PrimeFaces.widget.BaseWidget {
                 $this.target = null;
                 $this.jq.children('.ui-tooltip-text').removeClass('ui-state-error');
             }
+
+            this.unbindEscapeKey();
         }
     }
 
diff --git a/primefaces/src/main/frontend/packages/core/index.ts b/primefaces/src/main/frontend/packages/core/index.ts
index c4f6eb9301..ddb45aefa3 100644
--- a/primefaces/src/main/frontend/packages/core/index.ts
+++ b/primefaces/src/main/frontend/packages/core/index.ts
@@ -1717,6 +1717,7 @@ declare global {
             "jumpToPageDropdownLabel"?: string;
             "jumpToPageInputLabel"?: string;
             "lastPageLabel"?: string;
+            "listLabel"?: string;
             "listView"?: string;
             "moveAllToSource"?: string;
             "moveAllToTarget"?: string;
diff --git a/primefaces/src/main/frontend/packages/core/src/core/core.ajax.ts b/primefaces/src/main/frontend/packages/core/src/core/core.ajax.ts
index 49d1071364..b7f734bc87 100644
--- a/primefaces/src/main/frontend/packages/core/src/core/core.ajax.ts
+++ b/primefaces/src/main/frontend/packages/core/src/core/core.ajax.ts
@@ -48,6 +48,22 @@ export class AjaxUtils {
         return content;
     }
 
+    /**
+     * Checks if the given form is a Faces form.
+     * @param form The form to check.
+     * @return `true` if the form is a Faces form.
+     */
+    isFacesForm(form: HTMLFormElement): boolean {
+        if (form.method === 'post') {
+            for (let child of form.children) {
+                if (child instanceof HTMLInputElement && child.name && child.name.includes(core.VIEW_STATE)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     /**
      * Resolves the URL which should be used for the POST request.
      * For portlets, a different URL is used.
@@ -651,7 +667,9 @@ export class AjaxRequest {
 
             //source has no parent form so use first form in document
             if (form.length === 0) {
-                form = $('form').eq(0);
+                form = $('form').filter(function(index, element) {
+                    return core.ajax.Utils.isFacesForm(element);
+                }).first();
             }
         }
 
diff --git a/primefaces/src/main/frontend/packages/core/src/core/core.ts b/primefaces/src/main/frontend/packages/core/src/core/core.ts
index 7f52948b5b..524b9ed847 100644
--- a/primefaces/src/main/frontend/packages/core/src/core/core.ts
+++ b/primefaces/src/main/frontend/packages/core/src/core/core.ts
@@ -129,6 +129,7 @@ const LocaleEnUs: PrimeType.Locale = {
         "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/java/org/primefaces/component/barcode/BarcodeHandler.java b/primefaces/src/main/java/org/primefaces/component/barcode/BarcodeHandler.java
index ecbdbc4c7a..ae7513712c 100644
--- a/primefaces/src/main/java/org/primefaces/component/barcode/BarcodeHandler.java
+++ b/primefaces/src/main/java/org/primefaces/component/barcode/BarcodeHandler.java
@@ -106,6 +106,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 93e1af0b4d..536a3d49ec 100644
--- a/primefaces/src/main/java/org/primefaces/component/datepicker/DatePickerRenderer.java
+++ b/primefaces/src/main/java/org/primefaces/component/datepicker/DatePickerRenderer.java
@@ -371,7 +371,8 @@ public class DatePickerRenderer extends BaseCalendarRenderer<DatePicker> {
 
                 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 03fba4b9cf..0ea296f8fb 100644
--- a/primefaces/src/main/java/org/primefaces/component/splitbutton/SplitButtonRenderer.java
+++ b/primefaces/src/main/java/org/primefaces/component/splitbutton/SplitButtonRenderer.java
@@ -104,9 +104,6 @@ public class SplitButtonRenderer extends MenuItemAwareRenderer<SplitButton> {
         writer.startElement("div", component);
         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 (component.getStyle() != null) {
             writer.writeAttribute("style", component.getStyle(), "id");
         }
@@ -114,7 +111,7 @@ public class SplitButtonRenderer extends MenuItemAwareRenderer<SplitButton> {
         encodeDefaultButton(context, component, buttonId);
         OverlayPanel customOverlay = component.getCustomOverlay();
         if (customOverlay != null || component.getElementsCount() > 0) {
-            encodeMenuIcon(context, component, menuButtonId);
+            encodeMenuIcon(context, component, menuButtonId, menuId, hasOverlay);
             if (hasOverlay) {
                 encodeMenu(context, component, menuId);
             }
@@ -185,7 +182,7 @@ public class SplitButtonRenderer extends MenuItemAwareRenderer<SplitButton> {
         writer.endElement("button");
     }
 
-    protected void encodeMenuIcon(FacesContext context, SplitButton component, String id) throws IOException {
+    protected void encodeMenuIcon(FacesContext context, SplitButton component, String id, String menuId, boolean hasOverlay) throws IOException {
         ResponseWriter writer = context.getResponseWriter();
         String buttonClass = SplitButton.MENU_ICON_BUTTON_CLASS;
         if (component.isDisabled()) {
@@ -197,6 +194,9 @@ public class SplitButtonRenderer extends MenuItemAwareRenderer<SplitButton> {
         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 (component.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 d7c3943bda..a8bfed15c6 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);
     }
 
 }


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 44e088c1d0..401933b6c8 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);
+        });
+    }
 }