PluginProbe
Filter Everything — WordPress & WooCommerce Filters / 1.9.7
Filter Everything — WordPress & WooCommerce Filters v1.9.7
1.9.7 1.9.6 1.9.5 1.9.4 1.9.3 1.9.2.2 1.9.2.1 trunk 1.2.1 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 1.4.1 1.4.4 1.4.5 1.4.8 1.4.9 1.5.0 1.5.1 1.6.0 1.6.1 1.6.2 All 52 releases
← All changes | assets/js/filter-everything.js +3935 -290 1.5.11.9.7 View file →
@@ -1,11 +1,12 @@
1 1 /*!
2 - * Filter Everything 1.5.1
2 + * Filter Everything 1.9.7
3 3 */
4 4 (function ($) {
5 5 "use strict";
6 6 let wpcAjax = wpcFilterFront.wpcAjaxEnabled;
7 7 let wpcStatusCookieName = wpcFilterFront.wpcStatusCookieName;
8 + let wpcMoreLessCookieName = wpcFilterFront.wpcMoreLessCookieName;
8 9 let wpcWidgetStatusCookieName = wpcFilterFront.wpcWidgetStatusCookieName;
9 10 let wpcHierachyListCookieName = wpcFilterFront.wpcHierarchyListCookieName;
10 11 let wpcMobileWidth = wpcFilterFront.wpcMobileWidth;
11 12 let wpcPostContainers = wpcFilterFront.wpcPostContainers;
@@ -13,14 +14,39 @@
13 14 let wpcAutoScrollOffset = wpcFilterFront.wpcAutoScrollOffset;
14 15 let wpcWaitCursor = wpcFilterFront.wpcWaitCursor;
15 16 let wpcPostsPerPage = wpcFilterFront.wpcPostsPerPage;
16 17 let wpcUseSelect2 = wpcFilterFront.wpcUseSelect2;
18 + let wpcDateFilters = wpcFilterFront.wpcDateFilters;
19 + let wpcDateFiltersLocale = wpcFilterFront.wpcDateFiltersLocale;
20 + let wpcDateFiltersL10n = wpcFilterFront.wpcDateFiltersL10n;
17 21 let wpcPopupCompatMode = wpcFilterFront.wpcPopupCompatMode;
18 22 let wpcApplyButtonSets = wpcFilterFront.wpcApplyButtonSets;
19 23 let wpcQueryOnThePageSets = wpcFilterFront.wpcQueryOnThePageSets;
20 - let wpcWidgetContainer = '.wpc-filters-widget-main-wrapper';
21 - let wpcIsMobile = false;
24 + let noPostsContainerMsg = wpcFilterFront.wpcNoPostsContainerMsg;
25 + let wpcIsPro = Boolean(wpcFilterFront.wpcIsPro);
26 + let permalinksEnabled = Boolean(wpcFilterFront.permalinksEnabled);
27 + let wpcMoreLessCount = wpcFilterFront.wpcMoreLessCount;
28 + let wpcSearchChipsText = wpcFilterFront.wpcSearchChipsText;
29 + let chipsTitle = wpcFilterFront.chipsTitle;
30 + let chipsReset = wpcFilterFront.chipsReset;
31 + // Instant (client-side) Apply-button recount is active when the server printed
32 + // window.wpcFilterJsonData for this page — it does so only when a Set with
33 + // use_apply_button=yes is present AND the "Instant recount" option is enabled.
34 + // With static-file delivery the data arrives asynchronously: the bootstrap
35 + // exposes wpcFilterJsonDataPromise until the blob is fetched and merged.
36 + // Without either, Apply-button Sets fall back to the legacy per-click AJAX recount.
37 + let wpcInstantRecount = ( typeof window.wpcFilterJsonData !== 'undefined' || typeof window.wpcFilterJsonDataPromise !== 'undefined' );
38 + let wpcWidgetContainer = '.wpc-filters-main-wrap';
39 + let wpcIsMobile = false;
40 + let toReplaceSEO = true;
41 + let prevState = false; // Contains SEO Rule availability on a page
42 + let currentState = false; // Contains SEO Rule availability on a page
22 43
44 + let seoRuleId = $('#wpc-seo-rule-id').data( 'seoruleid' );
45 + if ( seoRuleId > 0 ) {
46 + prevState = true;
47 + }
48 +
23 49 function removeElement($el)
24 50 {
25 51 $el.fadeTo(100, 0, function() {
26 52 $el.slideUp(100, function() {
@@ -28,9 +54,51 @@
28 54 });
29 55 });
30 56 }
31 57
32 - $(document).on('click', '.wpc-filter-content input[type="radio"],.wpc-filter-content input[type="checkbox"]', function (e) {
58 + // When a PARENT filter selection changes, its children (and grandchildren)
59 + // keep whatever the visitor picked earlier — e.g. Brand switched from BMW
60 + // to Audi still kept Model = "5 Series", so the next Apply landed on a
61 + // zero-result page. Step-by-step configurators reset the chain instead:
62 + // clear every descendant selection; the recount that follows the parent
63 + // change re-renders placeholders and counters from the fresh DOM state.
64 + // Term selections only — a numeric range child keeps its min/max inputs.
65 + function wpcResetChildFilters( parentFid, $widget, visited ) {
66 + if ( ! parentFid ) return;
67 + visited = visited || {};
68 + if ( visited[parentFid] ) return; // parent chains are acyclic, but stay safe
69 + visited[parentFid] = true;
70 +
71 + $('.wpc-filters-section[data-parent-filter-id="' + parentFid + '"]', $widget).each(function () {
72 + const $childSection = $(this);
73 +
74 + $childSection.find('select.wpc-filters-widget-select').each(function () {
75 + const $select = $(this);
76 + const $default = $select.find('option.wpc-dropdown-default');
77 + const defVal = $default.length ? $default.val() : '';
78 + if ( $select.val() !== defVal ) {
79 + // change.select2 refreshes the rendered selection without
80 + // re-entering the filter change handlers
81 + $select.val( defVal ).trigger('change.select2');
82 + }
83 + });
84 +
85 + // Radios/checkboxes (incl. labels view): the recount collector reads
86 + // :checked and, for radios, the data-wpc-was-checked attribute
87 + $childSection.find('input:checked').not('.wpc-range-list-item').each(function () {
88 + $(this).prop('checked', false).attr('data-wpc-was-checked', false).data('wpc-was-checked', false);
89 + });
90 +
91 + // Grandchildren may carry a selection too (e.g. arrived via URL)
92 + wpcResetChildFilters( $childSection.data('fid'), $widget, visited );
93 + });
94 + }
95 +
96 + // Named so the label-tap fallback below can invoke the same logic when
97 + // the forwarded input click never arrives (iOS Safari, see below)
98 + function wpcTermInputClickHandler(e) {
99 + // Bumped on every run; the label-tap fallback below compares it
100 + this.__wpcClickSeq = (this.__wpcClickSeq || 0) + 1;
33 101 let wpcLink = $(this).data('wpc-link');
34 102 let $el = $(this).parents(wpcWidgetContainer);
35 103 let setId = $el.data('set');
36 104 let applyButtonMode = false;
@@ -38,19 +106,184 @@
38 106 if( setId > 0 && wpcApplyButtonSets.length > 0 && wpcApplyButtonSets.includes(setId) ){
39 107 applyButtonMode = true;
40 108 }
41 109
42 - if( wpcAjax || applyButtonMode ){
110 + if( applyButtonMode && !wpcInstantRecount ){
111 + // Legacy Apply-button mode: recount counters on the server per click
43 112 e.preventDefault();
44 113 wpcSendFilterRequest( wpcLink, $el, applyButtonMode );
114 + }else if( applyButtonMode ){
115 + // The recount collector selects radios by the [data-wpc-was-checked=true]
116 + // ATTRIBUTE — always clear/set BOTH the attribute and jQuery data on the
117 + // whole radio group, otherwise the previously selected term (incl. the
118 + // server-rendered one) keeps counting as selected alongside the new one
119 + const isRadio = $(this).is('input[type="radio"]');
120 + if (isRadio && !$(this).hasClass( 'flrt-star-input' )) {
121 + if ($(this).data('wpc-was-checked')) {
122 + $(this).prop('checked', false).attr('data-wpc-was-checked', false).data('wpc-was-checked', false);
123 + } else {
124 + $('input[type="radio"][name="' + $(this).attr('name') + '"]').attr('data-wpc-was-checked', false).data('wpc-was-checked', false);
125 + $(this).attr('data-wpc-was-checked', true).data('wpc-was-checked', true);
126 + }
127 + }
128 + const isRatingStar = $(this).hasClass('flrt-star-input');
129 + if (isRatingStar && isRadio) {
130 + const $starContent = $(this).closest('.flrt-stars-wpc-filter-content');
131 + $starContent.find('input.flrt-star-input').removeClass('wpc-checked-for-apply-button');
132 + if ($(this).data('wpc-was-checked')) {
133 + $(this).prop('checked', false).attr('data-wpc-was-checked', false).data('wpc-was-checked', false);
134 + } else if($(this).is(':checked')) {
135 + $('input[type="radio"][name="' + $(this).attr('name') + '"]').not(this).attr('data-wpc-was-checked', false).data('wpc-was-checked', false);
136 + $(this).data('wpc-was-checked', true).attr('data-wpc-was-checked', true);
137 + }
138 + // The star fill has TWO writers whose ORDER depends on the click
139 + // target: a real click lands on the <a> inside the label, so the
140 + // '.wpc-filter-content a' handler triggers this input synchronously
141 + // and the label click handler repaints AFTERWARDS from its own
142 + // stale data (a plain label click runs them in the opposite order).
143 + // Repaint once more after the whole click dispatch settles, from
144 + // the input state — the single source of truth.
145 + const selectedAndAbove = $starContent.data('selectedAndAbove');
146 + setTimeout(function () {
147 + const $checkedInput = $starContent.find('input.flrt-star-input[data-wpc-was-checked="true"]');
148 + const n = $checkedInput.length ? Number($checkedInput.data('ratingNum')) : 0;
149 + $starContent.find('label.flrt-star-label').each(function () {
150 + const k = Number($(this).data('ratingNum'));
151 + const on = n > 0 && (selectedAndAbove ? k >= n : k <= n);
152 + $(this).toggleClass('flrt-star-label-hover', on)
153 + .toggleClass('flrt-star-label-checked', on && !selectedAndAbove);
154 + if (selectedAndAbove) {
155 + $(this).toggleClass('flrt-star-label-not-checked', n > 0 && on);
156 + } else {
157 + $(this).removeClass('flrt-star-label-not-checked');
158 + }
159 + $(this).data('wpc-was-checked', n > 0 && k === n);
160 + });
161 + // The count next to the stars has the same two writers. When the
162 + // click target is not an <a> (crawler span-links, a bare label),
163 + // the label handler reads the radio BEFORE the browser toggles it,
164 + // so a deselect kept the old rating's count. Settle it here.
165 + if (n > 0) {
166 + flrtGetRatingTermCount($starContent.find('label.flrt-rating-numb-' + n), true);
167 + } else {
168 + $('#flrt-wpc-term-count').text('');
169 + }
170 + }, 0);
171 + }
172 +
173 + if ($(this).hasClass('wpc-range-list-item')) {
174 + $('.wpc-range-list-item', $(this).parents('.wpc-filters-range-inputs')).removeClass('wpc-range-list-item-checked');
175 +
176 + if(!applyButtonMode) return;
177 +
178 + const elementData = $(this).data();
179 + let wpcEName = (typeof elementData.wpcEName !== 'undefined');
180 + let wpcMin = (typeof elementData.min !== 'undefined');
181 + let wpcMax = (typeof elementData.max !== 'undefined');
182 + let wpcSlugMin= (typeof elementData.wpcSlugMin !== 'undefined');
183 + let wpcSlugMax= (typeof elementData.wpcSlugMax !== 'undefined');
184 +
185 + $(this).addClass('wpc-range-list-item-checked');
186 +
187 + let isChecked = $(this).data('wpc-was-checked');
188 +
189 + if(!wpcEName) return;
190 +
191 +
192 + if(wpcMin){
193 + if(!wpcSlugMin) return;
194 + let inputNameMin = elementData.wpcSlugMin + elementData.wpcEName;
195 + let minVal = elementData.min;
196 + let $inputElementMin = $("input[name=" + inputNameMin + "].wpc-filters-range-min", $el)
197 + if(minVal !== $inputElementMin.data().min){
198 + $inputElementMin.parent().find('.wpc-range-clear').show();
199 + }
200 + /*if(!minVal || minVal === 0){
201 + minVal = $inputElementMin.data().min
202 + }*/
203 +
204 + if(!isChecked){
205 + minVal = $inputElementMin.data().min;
206 + }
207 +
208 + $inputElementMin.attr('value', minVal)
209 + $inputElementMin.val(minVal)
210 +
211 + }
212 +
213 + if(wpcMax){
214 + if(!wpcSlugMax) return;
215 + let inputNameMax = elementData.wpcSlugMax + elementData.wpcEName ;
216 + let maxVal = elementData.max;
217 + let $inputElementMax = $("input[name=" + inputNameMax + "].wpc-filters-range-max", $el)
218 + if(maxVal !== $inputElementMax.data().max){
219 + $inputElementMax.parent().find('.wpc-range-clear').show();
220 + }
221 + /* if(!maxVal || maxVal === 0){
222 + maxVal = $inputElementMax.data().max
223 + }*/
224 +
225 + if(!isChecked){
226 + maxVal = $inputElementMax.data().max
227 + }
228 +
229 + $inputElementMax.attr('value', maxVal)
230 + $inputElementMax.val(maxVal)
231 + }
232 +
233 +
234 + let form = $(this).parents('.wpc-filters-range-inputs').find('form');
235 + if(form.length){
236 + $.fn.wpcInitSlider( form );
237 + }
238 + }
239 + // Single-choice parents cascade-reset their child filters; checkbox
240 + // parents are multi-select, where narrowing is cumulative — keep them
241 + if ( $(this).is('input[type="radio"]') && !$(this).hasClass('flrt-star-input') ) {
242 + wpcResetChildFilters( $(this).closest('.wpc-filters-section').data('fid'), $el );
243 + }
244 + wpcApplyEngine.applyJsMode($el, setId)
245 + }else if(wpcAjax){
246 + e.preventDefault();
247 + wpcSendFilterRequest( wpcLink, $el, applyButtonMode );
45 248 }else{
46 249 location.href = wpcLink;
47 250 }
251 + }
252 + $(document).on('click', '.wpc-filter-content input[type="radio"], .wpc-filter-content input[type="checkbox"]', wpcTermInputClickHandler);
253 +
254 + // iOS Safari never dispatches the forwarded click on a display:none
255 + // input (the custom-checkbox pattern the labels view uses), so a label
256 + // tap toggles the checkbox natively but the delegated input handler
257 + // above is never called — the term looks selected, yet no recount runs
258 + // and a second tap is needed. Desktop browsers and Android DO forward
259 + // the click. Catch the label click itself and, once the native dispatch
260 + // has settled, run the same handler manually unless the input click
261 + // already arrived.
262 + $(document).on('click', '.wpc-filter-content li.wpc-term-item label', function (e) {
263 + if ($(e.target).closest('a').length) return; // <a> terms have their own handler
264 + if ($(this).hasClass('flrt-star-label')) return; // rating stars have their own handler
265 + if ($(e.target).is('input')) return; // the forwarded input click itself, already handled
266 + let input = null;
267 + const forId = $(this).attr('for');
268 + if (forId) { input = document.getElementById(forId); }
269 + if (!input) { input = $(this).closest('.wpc-term-item-content-wrapper').find('input[type="checkbox"], input[type="radio"]')[0]; }
270 + if (!input || (input.type !== 'checkbox' && input.type !== 'radio')) return;
271 + // Where the browser forwards the click, it is dispatched right after this
272 + // label click — before any timer — and bumps the counter. Compare the
273 + // counter, not the time: a 500 ms window measured from the START of the
274 + // handler expired while a large set's synchronous instant recount was
275 + // still running, so the handler ran twice and a radio ended unchecked.
276 + const seq = input.__wpcClickSeq || 0;
277 + setTimeout(function () {
278 + if ((input.__wpcClickSeq || 0) !== seq) return;
279 + wpcTermInputClickHandler.call(input, { preventDefault: function () {} });
280 + }, 0);
48 281 });
49 282
50 283 $(document).on('change', '.wpc-orderby-select', function (){
51 284 let wpcSortingForm = $(this).parents('form.wpc-sorting-form');
52 - let wpcSortingVal = $(this).val();
285 + // let wpcSortingVal = $(this).val();
53 286 let search = '';
54 287 //@todo bug on mobile force AJAX
55 288 search = '?' + wpcSortingForm.serialize();
56 289
@@ -56,9 +289,9 @@
56 289
57 290 let wpcLink = wpcSortingForm.attr('action') + search;
58 291
59 292 if( wpcFilterFront.wpcAjaxEnabled ) {
60 - $('.wpc-filters-widget-main-wrapper').each(function (index, element) {
293 + $('.wpc-filters-main-wrap').each(function (index, element) {
61 294 let $el = $(element);
62 295 wpcSendFilterRequest(wpcLink, $el, false);
63 296 });
64 297 }else{
@@ -68,8 +301,9 @@
68 301 }
69 302 });
70 303
71 304 $(document).on('change', '.wpc-filter-content select', function (e) {
305 +
72 306 var wpcLink = $(this).find('option:selected').data('wpc-link');
73 307 let $el = $(this).parents(wpcWidgetContainer);
74 308 let setId = $el.data('set');
75 309 let applyButtonMode = false;
@@ -77,32 +311,79 @@
77 311 if( setId > 0 && wpcApplyButtonSets.length > 0 && wpcApplyButtonSets.includes(setId) ){
78 312 applyButtonMode = true;
79 313 }
80 314
81 - if( wpcAjax || applyButtonMode ){
315 + if( applyButtonMode && !wpcInstantRecount ){
82 316 e.preventDefault();
83 317 wpcSendFilterRequest( wpcLink, $el, applyButtonMode );
318 + }else if( applyButtonMode ){
319 + // A dropdown is single-choice: its change cascade-resets child filters
320 + wpcResetChildFilters( $(this).closest('.wpc-filters-section').data('fid'), $el );
321 + wpcApplyEngine.applyJsMode($el, setId)
322 + }else if(wpcAjax){
323 + e.preventDefault();
324 + wpcSendFilterRequest( wpcLink, $el, applyButtonMode );
84 325 }else{
85 326 location.href = wpcLink;
86 327 }
87 328 });
88 329
89 - $(document).on('click', '.wpc-filter-chip a', function (e){
90 - let wpcLink = $(this).attr('href');
330 + // Smart spans render non-indexable chips as <span data-wpc-span-link>
331 + $(document).on('click', '.wpc-filter-chip a, .wpc-filter-chip span[data-wpc-span-link], .wpc-filter-chip span.wpc-apply-button-chip', function (e){
332 + let wpcLink = $(this).attr('href') || $(this).attr('data-wpc-span-link');
91 333 let setId = $(this).parents('.wpc-filter-chips-list').data('set');
92 334 let $el = $('.wpc-filter-set-'+setId);
93 335 let applyButtonMode = false;
94 336
95 337 if( setId > 0 && wpcApplyButtonSets.length > 0 && wpcApplyButtonSets.includes(setId) ){
96 - if( $(this).parents('.wpc-filter-set-'+setId).length > 0 ){
338 + // Legacy mode counts only chips INSIDE the filters widget as Apply-button
339 + // clicks (pre-1.9.3 behaviour) — standalone chips above the posts navigate
340 + // like in non-apply mode. Instant mode handles all chips client-side.
341 + if( wpcInstantRecount ? $el.length > 0 : $(this).parents('.wpc-filter-set-'+setId).length > 0 ){
97 342 applyButtonMode = true;
98 343 }
99 344 }
100 345
101 - if( wpcAjax || applyButtonMode ) {
346 + if( applyButtonMode && !wpcInstantRecount ){
102 347 e.preventDefault();
103 348 wpcSendFilterRequest( wpcLink, $el, applyButtonMode );
349 + }else if( applyButtonMode ){
350 + if($(this).hasClass('wpc-apply-button-chips-reset')){
351 + e.preventDefault();
352 + $(`a.wpc-filters-reset-button.wpc-filters-reset-button-${setId}`).first().click();
353 + }
354 +
355 + if($(this).closest('li').hasClass('wpc-chip-search')){
356 + e.preventDefault();
357 + wpcSendFilterRequest( wpcLink, $el, applyButtonMode );
358 + }
359 +
360 + if($(this).hasClass('wpc-apply-button-chip') && !$(this).hasClass('wpc-apply-button-chips-reset') && !$(this).closest('li').hasClass('wpc-chip-search')){
361 + e.preventDefault();
362 + // Removing the LAST chip equals resetting all filters —
363 + // behave exactly like the Reset all button
364 + const $otherChips = $(this).closest('.wpc-filter-chips-list')
365 + .find('li.wpc-filter-chip')
366 + .not('.wpc-chip-reset-all')
367 + .not($(this).closest('li'));
368 + const $resetButton = $(`a.wpc-filters-reset-button.wpc-filters-reset-button-${setId}`).first();
369 +
370 + if( $otherChips.length === 0 && $resetButton.length > 0 ){
371 + $resetButton.click();
372 + }else{
373 + wpcApplyEngine.unsetChip($(this));
374 + }
375 + }
376 + //applyJsMode($el, setId)
377 + }else if(wpcAjax) {
378 + e.preventDefault();
379 + wpcSendFilterRequest( wpcLink, $el, applyButtonMode );
104 380 }else{
381 + // A span chip has no native navigation — follow the link manually
382 + if( ! $(this).is('a') && wpcLink ){
383 + window.location.href = wpcLink;
384 + return false;
385 + }
105 386 return true;
106 387 }
107 388 });
108 389
@@ -108,121 +389,193 @@
108 389
109 390 $(document).on('click', 'a.wpc-filters-submit-button', function (e){
110 391
111 392 if( $(this).hasClass('on-hold') ){
112 - e.preventDefault();
113 - return false;
393 + if( $(this).data('last') !== 'wpc-search-field' ){
394 + e.preventDefault();
395 + return false;
396 + }
114 397 }
115 398
116 399 let wpcLink = $(this).attr('href');
117 - let setId = $(this).parents('.wpc-filters-widget-main-wrapper').data('set');
400 + let setId = $(this).parents('.wpc-filters-main-wrap').data('set');
118 401 let $el = $('.wpc-filter-set-'+setId);
119 402
403 + let applyButtonMode = false;
404 +
405 + if( setId > 0 && wpcApplyButtonSets.length > 0 && wpcApplyButtonSets.includes(setId) ){
406 + applyButtonMode = true;
407 + // return false;
408 + }
409 +
120 410 if( wpcAjax && wpcQueryOnThePageSets.includes( setId ) ) {
121 411 e.preventDefault();
122 - wpcSendFilterRequest( wpcLink, $el, false );
412 + wpcSendFilterRequest( wpcLink, $el, applyButtonMode && wpcInstantRecount );
123 413 }else{
124 414 return true;
125 415 }
126 416 });
127 417
128 - $(document).on('click', 'a.wpc-filters-reset-button', function (e){
129 -
130 - if( $(this).hasClass('on-hold') ){
131 - e.preventDefault();
132 - return false;
133 - }
134 -
418 + $(document).on('click', 'a.wpc-search-clear-icon', function (e){
135 419 let wpcLink = $(this).attr('href');
136 - let setId = $(this).parents('.wpc-filters-widget-main-wrapper').data('set');
420 + let setId = $(this).parents('.wpc-filters-main-wrap').data('set');
137 421 let $el = $('.wpc-filter-set-'+setId);
422 + let applyButtonMode = false;
138 423
139 424 if( wpcAjax ) {
140 425 e.preventDefault();
141 - if( wpcQueryOnThePageSets.includes( setId ) ){
142 - wpcSendFilterRequest( wpcLink, $el, false );
143 - }else{
144 - wpcSendFilterRequest( wpcLink, $el, true );
145 - }
426 + wpcSendFilterRequest( wpcLink, $el, applyButtonMode );
427 + return false;
146 428 }else{
147 429 return true;
148 - // wpcSendFilterRequest( wpcLink, $el, true );
149 430 }
150 431 });
151 432
152 - $(document).on('click', 'i.wpc-toggle-children-list', function (){
153 - let _tid = $(this).data('tid');
154 - let wpcTermContentWrapper = $(this).parent(".wpc-term-item-content-wrapper");
155 - let wpcTargetUl = wpcTermContentWrapper.parent('li').children('ul.children');
433 + $(document).on( 'change', '.wpc-search-field', function (e) {
434 + let form = $(this).parents(".wpc-filter-search-form");
156 435
157 - wpcTermContentWrapper.parent('li').toggleClass('wpc-opened');
158 - // wpcTermContentWrapper.toggleClass('wpc-opened');
436 + let $el = form.parents(wpcWidgetContainer);
437 + let setId = $el.data('set');
438 + let applyButtonMode = false;
159 439
160 - if( wpcTargetUl.is(':visible' ) ){
161 - rememberOpened( _tid, wpcHierachyListCookieName );
162 - }else{
163 - forgetOpened( _tid, wpcHierachyListCookieName );
440 + if( setId > 0 && wpcApplyButtonSets.length > 0 && wpcApplyButtonSets.includes(setId) ){
441 + applyButtonMode = true;
442 + // return false;
164 443 }
444 +
445 + if( wpcAjax || applyButtonMode ){
446 + let search = form.serialize();
447 + let wpcLink = form.attr('action') + '?' + search;
448 + wpcSendFilterRequest( wpcLink, $el, applyButtonMode );
449 + return false;
450 + } else {
451 + form.submit();
452 + }
165 453 });
166 454
167 - $(document).on('click', '.wpc-filters-overlay', function (){
168 - let setId = $('body').data('set');
169 - wpcCloseFiltersContainer(setId);
170 - })
455 + $(document).on('submit', '.wpc-filter-search-form', function (e) {
456 + let form = $(this);
171 457
172 - $(document).on('change', '.wpc-filter-range-form input[type="number"]', function (e) {
173 - let form = $(this).parents('.wpc-filter-range-form');
458 + let $el = form.parents(wpcWidgetContainer);
459 + let setId = $el.data('set');
460 + let applyButtonMode = false;
174 461
175 - let $min = form.find('.wpc-filters-range-min');
176 - let $max = form.find('.wpc-filters-range-max');
462 + if( setId > 0 && wpcApplyButtonSets.length > 0 && wpcApplyButtonSets.includes(setId) ){
463 + applyButtonMode = true;
464 + }
177 465
178 - var curMinVal = parseFloat($min.val());
179 - var curMaxVal = parseFloat($max.val());
466 + if( wpcAjax || applyButtonMode ){
467 + let search = form.serialize();
468 + let wpcLink = form.attr('action') + '?' + search;
469 + wpcSendFilterRequest( wpcLink, $el, applyButtonMode );
470 + return false;
471 + } else {
472 + return true;
473 + // form.submit();
474 + }
180 475
181 - var initialMin = $min.data('min');
182 - var initialMax = $max.data('max');
476 + });
183 477
184 - if( form.hasClass('wpc-form-has-slider') ){
185 - let $slider = form.find('.wpc-filters-range-slider-control');
186 - $slider.slider("option", "values", [curMinVal, curMaxVal]);
187 - }
478 + $(document).on('click', 'a.wpc-filters-reset-button', function (e){
188 479
189 - if (curMinVal === initialMin) {
190 - $min.attr('disabled', true);
480 + if( $(this).hasClass('on-hold') ){
481 + e.preventDefault();
482 + return false;
191 483 }
192 484
193 - if (curMaxVal === initialMax) {
194 - $max.attr('disabled', true);
195 - }
196 -
197 - let $el = form.parents(wpcWidgetContainer);
198 - let setId = $el.data('set');
485 + let wpcLink = $(this).attr('href');
486 + let setId = $(this).parents('.wpc-filters-main-wrap').data('set');
487 + let $el = $('.wpc-filter-set-'+setId);
199 488 let applyButtonMode = false;
200 489
201 490 if( setId > 0 && wpcApplyButtonSets.length > 0 && wpcApplyButtonSets.includes(setId) ){
202 - applyButtonMode = true;
491 + if( $(this).parents('.wpc-filter-set-'+setId).length > 0 ){
492 + applyButtonMode = true;
493 + }
203 494 }
204 495
205 - if( wpcAjax || applyButtonMode ){
206 - let search = form.serialize();
207 - let wpcLink = form.attr('action') + '?' + search;
496 + if (applyButtonMode && wpcInstantRecount){
497 + e.preventDefault();
498 + // With "AJAX for Filters" disabled, Reset all must be a plain page
499 + // reload to the clean URL — same as the Apply button navigates to
500 + // the built URL. Explicit assign() (not `return true`): the Reset
501 + // chip and the last-chip removal trigger this handler with a
502 + // programmatic jQuery .click(), which never follows the href.
503 + if( ! wpcAjax ){
504 + window.location.assign(wpcLink);
505 + return;
506 + }
507 + wpcSendFilterRequest(wpcLink, $el, applyButtonMode);
508 + }else if (wpcAjax) {
509 + e.preventDefault();
510 + if (wpcQueryOnThePageSets.includes(setId)) {
511 + wpcSendFilterRequest(wpcLink, $el, false);
512 + } else {
513 + wpcSendFilterRequest(wpcLink, $el, true);
514 + }
208 515
209 - wpcSendFilterRequest( wpcLink, $el, applyButtonMode );
516 + } else {
517 + return true;
518 + // wpcSendFilterRequest( wpcLink, $el, true );
519 + }
520 + });
210 521
211 - $min.attr('disabled', false);
212 - $max.attr('disabled', false);
522 + $(document).on('click', 'i.wpc-toggle-children-list', function (){
523 + let tid = $(this).data('tid');
524 + let $targetLi = $(this).parent(".wpc-term-item-content-wrapper").parent('li');
525 + let $targetFilter = $(this).parents('.wpc-filters-section');
526 +
527 + if ( $targetLi.hasClass( 'wpc-opened' ) ) {
528 + $targetLi.removeClass( 'wpc-opened' )
529 + .addClass( 'wpc-closed' );
530 + setStatusCookie( -tid, wpcHierachyListCookieName );
531 + } else if ( $targetLi.hasClass( 'wpc-closed' ) ) {
532 + $targetLi.removeClass( 'wpc-closed' )
533 + .addClass( 'wpc-opened' );
534 + setStatusCookie( tid, wpcHierachyListCookieName );
213 535 } else {
214 - form.submit();
536 + if ( $targetFilter.hasClass( 'wpc-filter-hierarchy-reverse' ) ) {
537 + $targetLi.removeClass( 'wpc-opened' ) // For any case
538 + .addClass( 'wpc-closed' );
539 + setStatusCookie( -tid, wpcHierachyListCookieName );
540 + } else {
541 + $targetLi.removeClass( 'wpc-closed' ) // For any case
542 + .addClass( 'wpc-opened' );
543 + setStatusCookie( tid, wpcHierachyListCookieName );
544 + }
215 545 }
546 + });
216 547
548 + $(document).on('click', '.wpc-filters-overlay', function (){
549 + let setId = $('body').data('set');
550 + wpcCloseFiltersContainer(setId);
551 + })
552 +
553 + $(document).on('change', '.wpc-filter-range-form input[type="number"]', function (event) {
554 +
555 + let form = $(this).parents('.wpc-filter-range-form');
556 + processRangeForm( event, form );
217 557 });
218 558
219 559 $(document).on( 'click','.wpc-open-close-filters-button', function (e){
220 560 e.preventDefault();
561 +
221 562 let openCloseButton = $(this);
222 563 let wpcSetId = openCloseButton.data('wid');
564 + let wpcButtonFilterSetError = openCloseButton.data('wpcButtonFilterSetError');
565 + let wpcButtonWidgetError = openCloseButton.data('wpcButtonWidgetError');
223 566 let widgetContent = $('.wpc-filter-set-'+wpcSetId+' .wpc-filters-widget-content');
224 567
568 + if (typeof wpcButtonFilterSetError !== 'undefined') {
569 + alert(wpcButtonFilterSetError);
570 + }
571 +
572 + if (typeof wpcButtonWidgetError !== 'undefined') {
573 + if(typeof window.wpcFilterWidgetActive === 'undefined'){
574 + alert(wpcButtonWidgetError);
575 + }
576 + }
577 +
225 578 if( widgetContent.is(':visible') ){
226 579 widgetContent.slideUp({
227 580 duration: 100,
228 581 complete: function (){
@@ -250,8 +603,9 @@
250 603 let $wrapper = $( this ).parents( wpcWidgetContainer );
251 604 let setId = $wrapper.data( 'set' );
252 605 wpcCloseFiltersContainer(setId);
253 606 });
607 +
254 608 $(document).on('click', '.wpc-filters-apply-button', function (e){
255 609 e.preventDefault();
256 610 let $wrapper = $( this ).parents( wpcWidgetContainer );
257 611 let setId = $wrapper.data( 'set' );
@@ -259,9 +613,25 @@
259 613 let href = $(this).attr( 'href' );
260 614 let wpcReload = ! $(this).hasClass('wpc-posts-loaded');
261 615 let wpcZindex = '';
262 616 let $currentTag = false;
617 + let $el = $('.wpc-filter-set-'+setId);
263 618
619 + let applyButtonMode = false;
620 +
621 + if( setId > 0 && wpcApplyButtonSets.length > 0 && wpcApplyButtonSets.includes(setId) ){
622 + applyButtonMode = true;
623 + }
624 +
625 + if(applyButtonMode && wpcInstantRecount){
626 + if((wpcFilterFront.wpcAjaxEnabled && wpcAjax) && wpcQueryOnThePageSets.includes( setId ) ) {
627 + wpcSendFilterRequest( href, $el, applyButtonMode );
628 + }else{
629 + location.href = href;
630 + }
631 + }
632 +
633 +
264 634 $wrapper.removeClass('wpc-container-opened');
265 635 $('html').removeClass('wpc-overlay-visible');
266 636 $content.removeClass('wpc-filters-widget-opened');
267 637 $('.wpc-open-button-'+setId+' .wpc-filters-open-widget').removeClass('wpc-opened');
@@ -298,8 +668,15 @@
298 668 $(document).on('submit', '.wpc-filter-range-form', function (e) {
299 669 submitSliderForm(e, $(this));
300 670 });
301 671
672 + $(document).on('keydown', '.wpc-filters-range-from,.wpc-filters-range-to', function (event){
673 + if ( event.which == 13 ) {
674 + let fid = $(this).data('fid');
675 + processRangeForm( event, $("#wpc-filter-date-range-form-"+ fid ) );
676 + }
677 + });
678 +
302 679 $(document).on('click', '.wpc-filter-content a', function (e) {
303 680 e.preventDefault();
304 681 let wpcInputId = $(this).closest('label').attr('for');
305 682 $(this).closest('label').parent('.wpc-term-item-content-wrapper').parent('.wpc-term-item').find('#'+wpcInputId).trigger('click');
@@ -328,22 +705,69 @@
328 705
329 706 wpcCloseFiltersContainer(setId);
330 707 });
331 708
332 - $(document).on('click', '.wpc-filter-title button', function (e){
709 + $(document).on('click', 'a.wpc-toggle-a', function (e){
333 710 e.preventDefault();
334 - let buttonHead = $(this).parents('.wpc-filter-collapsible');
335 - let filterId = buttonHead.parents('.wpc-filters-section').data('fid');
711 + let fid = $(this).data('fid');
712 + let $filterSection = $( ".wpc-filters-section-" + fid );
713 + //$( ".wpc-filters-section-" + fid ).toggleClass( 'wpc-show-more' );
336 714
337 - if( $( '.wpc-filters-section-'+filterId+' .wpc-filter-content' ).is( ':visible' ) ){
338 - closeFilterContentBox(buttonHead);
339 - forgetOpened(filterId, wpcStatusCookieName);
340 - }else{
341 - openFilterContentBox(buttonHead);
342 - rememberOpened(filterId, wpcStatusCookieName);
715 + if ( $filterSection.hasClass('wpc-show-more' ) ) {
716 + $filterSection.removeClass( 'wpc-show-more' )
717 + .addClass( 'wpc-show-less' );
718 + setStatusCookie( -fid, wpcMoreLessCookieName );
719 + } else if ( $filterSection.hasClass('wpc-show-less' ) ) {
720 + $filterSection.removeClass( 'wpc-show-less' )
721 + .addClass( 'wpc-show-more' );
722 + setStatusCookie( fid, wpcMoreLessCookieName );
723 + } else {
724 + // No status class detected
725 + if( $filterSection.hasClass( 'wpc-filter-has-selected' ) || $filterSection.hasClass( 'wpc-show-more-reverse' ) ) {
726 + $filterSection.removeClass( 'wpc-show-more' ) // For any case
727 + .addClass( 'wpc-show-less' );
728 + setStatusCookie( -fid, wpcMoreLessCookieName );
729 + } else {
730 + $filterSection.removeClass( 'wpc-show-less' ) // For any case
731 + .addClass( 'wpc-show-more' );
732 + setStatusCookie( fid, wpcMoreLessCookieName );
733 + }
343 734 }
344 735 });
345 736
737 + $(document).on('click', '.wpc-filters-main-wrap input', function (e) {
738 + let lastInputClass = $(this).attr('class');
739 + if ( typeof lastInputClass !== 'undefined' ){
740 + $('.wpc-filters-submit-button').data('last', lastInputClass);
741 + }
742 + });
743 +
744 + $(document).on('click', '.wpc-filter-title button', function (e) {
745 + e.preventDefault();
746 + let $filterSection = $(this).parents('.wpc-filters-section');
747 + let filterId = $filterSection.data( 'fid' );
748 +
749 + if ( $filterSection.hasClass( 'wpc-opened' ) ) {
750 + $filterSection.removeClass( 'wpc-opened' )
751 + .addClass( 'wpc-closed' );
752 + setStatusCookie( -filterId, wpcStatusCookieName );
753 + } else if ( $filterSection.hasClass( 'wpc-closed' ) ) {
754 + $filterSection.removeClass( 'wpc-closed' )
755 + .addClass( 'wpc-opened' );
756 + setStatusCookie( filterId, wpcStatusCookieName );
757 + } else {
758 + if( $filterSection.hasClass( 'wpc-filter-has-selected' ) || $filterSection.hasClass( 'wpc-filter-collapsible-reverse' ) ) {
759 + $filterSection.removeClass( 'wpc-opened' )
760 + .addClass( 'wpc-closed' );
761 + setStatusCookie( -filterId, wpcStatusCookieName );
762 + } else {
763 + $filterSection.removeClass( 'wpc-closed' )
764 + .addClass( 'wpc-opened' );
765 + setStatusCookie( filterId, wpcStatusCookieName );
766 + }
767 + }
768 + });
769 +
346 770 $( window ).resize(function() {
347 771 if( window.innerWidth <= wpcMobileWidth ){
348 772 wpcIsMobile = true;
349 773 if( wpcFilterFront.showBottomWidget === 'yes' ) {
@@ -353,15 +777,18 @@
353 777 wpcAjax = wpcFilterFront.wpcAjaxEnabled;
354 778 wpcIsMobile = false;
355 779 }
356 780
357 - if( wpcUseSelect2 === 'yes' ){
358 - $(wpcWidgetContainer).each( function ( index, widget ){
359 - let widgetSet = $(widget).data('set');
360 - let widgetClass = 'wpc-filter-set-'+widgetSet;
361 - wpcInitSelect2(widgetClass);
362 - });
781 + if ( ! wpcSsMobileBrowser() ){
782 + if( wpcUseSelect2 === 'yes' ){
783 + $(wpcWidgetContainer).each( function ( index, widget ){
784 + let widgetSet = $(widget).data('set');
785 + let widgetClass = 'wpc-filter-set-'+widgetSet;
786 + wpcInitSelect2(widgetClass);
787 + });
788 + }
363 789 }
790 +
364 791 });
365 792
366 793 if ($.support.pjax) {
367 794 $(document).on('pjax:end', function() {
@@ -374,25 +801,55 @@
374 801 $(document).ready(function (){
375 802 wpcInitiateAll();
376 803 });
377 804
805 + $(document).on('input', '.wpc-search-field',function (e){
806 + let $section = $(this).parents('.wpc-filters-section');
807 + let searchOrig = $(this).val();
808 + let $search = searchOrig.toLowerCase();
809 + let $submitBtn = $(".wpc-filters-submit-button");
810 + let theHref = $submitBtn.attr('href');
811 +
812 + if ( typeof theHref !== 'undefined' ){
813 + let url = new URL(theHref);
814 + url.searchParams.set( 'srch', searchOrig);
815 + $submitBtn.attr( 'href', url.href );
816 +
817 + if( $search !== '' ){
818 + $section.addClass('wpc-search-active');
819 + }else{
820 + $section.removeClass('wpc-search-active');
821 + }
822 + }
823 +
824 + });
825 +
378 826 $(document).on('input', '.wpc-filter-search-field',function (e){
379 - let $search = $(this).val().toString().toLowerCase();
380 - let fid = $(this).parents('.wpc-filters-section').data('fid');
827 + let $search = $(this).val().toString().toLowerCase();
828 + let $section = $(this).parents('.wpc-filters-section');
829 + let fid = $section.data('fid');
381 830
382 831 if( $search !== '' ){
383 832 $(".wpc-filter-search-wrapper-"+fid+" .wpc-search-clear").show();
833 + $section.addClass('wpc-search-active');
384 834 }else{
385 835 $(".wpc-filter-search-wrapper-"+fid+" .wpc-search-clear").hide();
836 + $section.removeClass('wpc-search-active');
386 837 }
387 838
388 839 $(".wpc-filters-list-"+fid+" li").each(function( index, value ) {
389 840 let $li = $(value);
390 - let $termName = $(value).find('label a').text().toLowerCase();
841 + // The term name may be rendered as a link, a span (when the
842 + // "Disable filter links for crawlers" option turns <a> into
843 + // <span>), a colour swatch or a brand logo. Reading the whole
844 + // label's text covers every case; 'label a' alone is empty
845 + // whenever the links are replaced with spans, which made the
846 + // search match nothing and hide every term.
847 + let $termName = $(value).find('label').text().toLowerCase();
391 848 if ($termName.indexOf($search) > -1) {
392 - $li.show();
849 + $li.addClass('showli');
393 850 } else {
394 - $li.hide();
851 + $li.removeClass('showli');
395 852 }
396 853 });
397 854 });
398 855
@@ -400,11 +857,15 @@
400 857 e.preventDefault();
401 858 let $searchField = $(this).parent(".wpc-filter-search-wrapper").find(".wpc-filter-search-field");
402 859 $searchField.val('')
403 860 .trigger('input');
404 - // $(this).hide();
405 861 })
406 862
863 +
864 + function isDonePressed( inst ) {
865 + return ( ( $('#ui-datepicker-div .ui-datepicker-close.ui-state-hover').length > 0 ) && !inst._keyEvent );
866 + }
867 +
407 868 function wpcInitiateAll(){
408 869 $('.wpc-filter-range-form').each( function ( index, form ){
409 870 $.fn.wpcInitSlider( $(form) );
410 871 });
@@ -423,9 +884,180 @@
423 884 wpcInitSelect2(widgetClass);
424 885 });
425 886 }
426 887
888 + if ( wpcDateFilters !== '' ) {
889 +
890 + $.datepicker.regional[wpcDateFiltersLocale] = wpcDateFiltersL10n;
891 + $.datepicker.setDefaults(wpcDateFiltersL10n);
892 +
893 + const updatedProperties = {
894 + _selectDate : function( id, dateStr ) {
895 + var onSelect,
896 + target = $( id ),
897 + inst = this._getInst( target[ 0 ] );
898 +
899 + dateStr = ( dateStr != null ? dateStr : this._formatDate( inst ) );
900 + if ( inst.input ) {
901 + inst.input.val( dateStr );
902 + }
903 + this._updateAlternate( inst );
904 +
905 + onSelect = this._get( inst, "onSelect" );
906 + if ( onSelect ) {
907 + onSelect.apply( ( inst.input ? inst.input[ 0 ] : null ), [ dateStr, inst ] ); // trigger custom callback
908 + } else if ( inst.input ) {
909 + inst.input.trigger( "change" ); // fire the change event
910 + }
911 +
912 + if ( inst.inline || this._curInst.id.includes( 'wpc-filters-alt-date' ) ) {
913 + this._updateDatepicker( inst );
914 + } else {
915 + this._hideDatepicker();
916 + this._lastInput = inst.input[ 0 ];
917 + if ( typeof( inst.input[ 0 ] ) !== "object" ) {
918 + inst.input.trigger( "focus" ); // restore focus
919 + }
920 + this._lastInput = null;
921 + }
922 + }
923 + };
924 +
925 + Object.assign( $.datepicker, updatedProperties );
926 +
927 + $.each( wpcDateFilters, function ( fid, dateFilter ) {
928 +
929 + if ( $("#wpc-filters-date-from-"+ fid).length < 1 ) {
930 + return true;
931 + }
932 + let setId = $("#wpc-filters-date-from-"+ fid).data('set');
933 +
934 + let applyButtonMode = false;
935 +
936 + if( setId > 0 && wpcApplyButtonSets.length > 0 && wpcApplyButtonSets.includes(setId) ){
937 + applyButtonMode = true;
938 + }
939 + if(applyButtonMode && wpcInstantRecount){
940 + wpcDateFiltersL10n.closeText = wpcDateFiltersL10n.applyText
941 + }
942 +
943 + let pickerOptions = {};
944 + let timeFormat = dateFilter['time_format'].includes('s') ? 'HH.mm.ss' : 'HH.mm.00';
945 + if ( dateFilter['date_type'] === 'date' ) {
946 + let yearMin = $("#wpc-filters-date-from-"+ fid).data('from').slice(0,4);
947 + let yearMax = $("#wpc-filters-date-to-"+ fid).data('to').slice(0,4);
948 +
949 + pickerOptions = {
950 + dateFormat: dateFilter['date_format'], // will be shown in visible field
951 + altFieldTimeOnly: false,
952 + altField: '#wpc-filters-date-from-' + fid,
953 + altFormat: 'yy-mm-dd',
954 + changeYear: true,
955 + yearRange: yearMin+':'+yearMax,
956 + changeMonth: true,
957 + showButtonPanel: true,
958 + onClose: function( dateText, inst ){
959 + if( isDonePressed( inst ) ) {
960 + if(applyButtonMode && wpcInstantRecount){
961 + updateInputDateData(dateText, inst.input)
962 + }
963 + processRangeForm( event, $("#wpc-filter-date-range-form-"+ fid ) );
964 + }
965 + },
966 + beforeShow: function(input, inst) {
967 + $('#ui-datepicker-div').addClass('wpc-filter-datepicker wpc-filter-datepicker-'+ fid);
968 + },
969 + onUpdateDatepicker: function( inst ) {
970 + let $inp = $(inst.input);
971 + let w = $inp.outerWidth();
972 + $(".wpc-filter-datepicker-"+ fid).css('width', w + 'px');
973 + }
974 + };
975 +
976 + $( "#wpc-filters-alt-date-from-" + fid ).datepicker( pickerOptions );
977 + pickerOptions.altField = '#wpc-filters-date-to-' + fid;
978 + $( "#wpc-filters-alt-date-to-" + fid ).datepicker( pickerOptions );
979 +
980 + } else if ( dateFilter['date_type'] === 'datetime' ) {
981 + $.timepicker.regional[wpcDateFiltersLocale] = wpcDateFiltersL10n;
982 + $.timepicker.setDefaults(wpcDateFiltersL10n);
983 + let yearMin = $("#wpc-filters-date-from-"+ fid).data('from').slice(0,4);
984 + let yearMax = $("#wpc-filters-date-to-"+ fid).data('to').slice(0,4);
985 +
986 + pickerOptions = {
987 + dateFormat: dateFilter['date_format'],
988 + timeFormat: dateFilter['time_format'], // Depends from localization
989 + altFieldTimeOnly: false,
990 + altField: '#wpc-filters-date-from-' + fid,
991 + altFormat: 'yy-mm-dd',
992 + altTimeFormat: timeFormat, // Depends from format HH.mm.ss or HH.mm.00
993 + altSeparator: 't',
994 + changeYear: true,
995 + yearRange: yearMin+':'+yearMax,
996 + changeMonth: true,
997 + showButtonPanel: true,
998 + controlType: 'select',
999 + oneLine: true,
1000 + onClose: function( dateText, inst ){
1001 + if( isDonePressed( inst )) {
1002 + if(applyButtonMode && wpcInstantRecount){
1003 + updateInputDateData(dateText, inst.input)
1004 + }
1005 + processRangeForm( event, $("#wpc-filter-date-range-form-"+ fid ) );
1006 + }
1007 + },
1008 + beforeShow: function(input, inst) {
1009 + $('#ui-datepicker-div').addClass('wpc-filter-datepicker wpc-filter-datepicker-'+ fid);
1010 + },
1011 + onUpdateDatepicker: function( inst ) {
1012 + let $inp = $(inst.input);
1013 + let w = $inp.outerWidth();
1014 + $(".wpc-filter-datepicker-"+ fid).css('width', w + 'px');
1015 + }
1016 + };
1017 +
1018 + $( "#wpc-filters-alt-date-from-" + fid ).datetimepicker( pickerOptions );
1019 + pickerOptions.altField = '#wpc-filters-date-to-' + fid;
1020 + $( "#wpc-filters-alt-date-to-" + fid ).datetimepicker( pickerOptions );
1021 +
1022 + } else if ( dateFilter['date_type'] === 'time' ) {
1023 + $.timepicker.regional[wpcDateFiltersLocale] = wpcDateFiltersL10n;
1024 + $.timepicker.setDefaults(wpcDateFiltersL10n);
1025 +
1026 + pickerOptions = {
1027 + timeFormat: dateFilter['time_format'],
1028 + altField: '#wpc-filters-date-from-' + fid,
1029 + altFieldTimeOnly: false,
1030 + altTimeFormat: timeFormat,
1031 + controlType: 'select',
1032 + oneLine: true,
1033 + onClose: function( dateText, inst ){
1034 + if( isDonePressed( inst ) ) {
1035 + if(applyButtonMode && wpcInstantRecount){
1036 + updateInputDateData(dateText, inst.input)
1037 + }
1038 + processRangeForm( event, $("#wpc-filter-date-range-form-"+ fid ) );
1039 + }
1040 + },
1041 + beforeShow: function(input, inst) {
1042 + $('#ui-datepicker-div').addClass('wpc-filter-datepicker wpc-filter-datepicker-'+ fid);
1043 + },
1044 + onUpdateDatepicker: function( inst ) {
1045 + let $inp = $(inst.input);
1046 + let w = $inp.outerWidth();
1047 + $(".wpc-filter-datepicker-"+ fid).css('width', w + 'px');
1048 + }
1049 + };
1050 +
1051 + $( "#wpc-filters-alt-date-from-" + fid ).timepicker( pickerOptions );
1052 + pickerOptions.altField = '#wpc-filters-date-to-' + fid;
1053 + $( "#wpc-filters-alt-date-to-" + fid ).timepicker( pickerOptions );
1054 + }
1055 + });
1056 + }
1057 +
427 1058 $('.wpc-help-tip').tipTip({
1059 + 'activation': 'hover',
428 1060 'attribute': 'data-tip',
429 1061 'fadeIn': 50,
430 1062 'fadeOut': 50,
431 1063 'delay': 200,
@@ -433,36 +1065,119 @@
433 1065 'maxWidth': "220px",
434 1066 });
435 1067 }
436 1068
437 - function wpcInitSelect2( widgetClass ) {
1069 + function updateInputDateData(dateText, $input){
1070 + if(typeof $input.data('wpcTempFrom') !== 'undefined'){
1071 + $input.data('wpcTempFrom', dateText)
1072 + $input.attr('data-wpc-temp-from', dateText)
1073 + }
1074 + if(typeof $input.data('wpcTempTo') !== 'undefined'){
1075 + $input.data('wpcTempTo', dateText)
1076 + $input.attr('data-wpc-temp-to', dateText)
1077 + }
1078 + }
1079 +
1080 + function wpcSsMobileBrowser() {
1081 + const userAgent = navigator.userAgent || navigator.vendor || window.opera;
1082 + const isMobile = /Mobile|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);
1083 +
1084 + return isMobile;
1085 + }
1086 +
1087 + function wpcIsDesktopSafari() {
1088 + const userAgent = navigator.userAgent;
1089 + const isSafari = /^((?!chrome|android|crios|fxios).)*safari/i.test(userAgent);
1090 + const isNotMobile = !/Mobile|iPhone|iPad|iPod/i.test(userAgent);
1091 +
1092 + return isSafari && isNotMobile;
1093 + }
1094 +
1095 + function wpcInitSelect2( widgetClass, ) {
438 1096 if( typeof $.fn.select2 === 'undefined'){
439 1097 return;
440 1098 }
441 1099
1100 + // Destroy existing select2 instances before re-initializing
1101 + // to prevent duplicate event listeners that block mobile scroll
1102 + $('.wpc-filters-widget-select').each(function() {
1103 + if ($(this).hasClass('select2-hidden-accessible')) {
1104 + $(this).select2('destroy');
1105 + }
1106 + });
1107 + $('.wpc-orderby-select').each(function() {
1108 + if ($(this).hasClass('select2-hidden-accessible')) {
1109 + $(this).select2('destroy');
1110 + }
1111 + });
1112 +
442 1113 let wpcUserAgent = navigator.userAgent.toLowerCase();
443 1114 let wpcIsAndroid = wpcUserAgent.indexOf("android") > -1;
1115 +
444 1116 let wpcAllowSearchField = 0;
445 - if(wpcIsAndroid) {
1117 + if( wpcIsAndroid ) {
446 1118 wpcAllowSearchField = Infinity;
447 1119 }
448 1120
1121 + if( wpcIsDesktopSafari() ){
1122 + wpcAllowSearchField = 10;
1123 + }
1124 +
1125 + const wpcHideEmpty = $(`.${widgetClass}`).data('wpc-hide-empty');
1126 + const useApplyButton = $(`.${widgetClass}`).data('wpc-use-apply-button');
1127 +
1128 + const isHideEmpty = wpcHideEmpty === 'yes';
1129 +
1130 +
1131 +
449 1132 $('.wpc-filters-widget-select').select2({
450 1133 dropdownCssClass: 'wpc-filter-everything-dropdown',
451 1134 dropdownParent: $('.'+widgetClass+' .wpc-filters-widget-content'),
452 - templateResult: function(data) {
453 - // We only really care if there is an element to pull classes from
454 - if (!data.element) {
455 - return data.text;
1135 + templateResult: function( data, container ) {
1136 + let postsCount = $(data.element).data('count');
1137 + let wpcSlug = $(data.element).data('wpcSlug');
1138 + let wpcEName = $(data.element).data('wpcEName');
1139 + if($(data.element).hasClass('wpc-dropdown-default')){
1140 + data.text = $(data.element).text();
456 1141 }
457 - let $dr_element = $(data.element);
458 - let $dr_wrapper = $('<span></span>');
459 - $dr_wrapper.addClass($dr_element[0].className);
460 - $dr_wrapper.text(data.text);
461 -
462 - return $dr_wrapper;
1142 + if(wpcSlug !== undefined && wpcEName !== undefined){
1143 + $(container).addClass(`select2-${wpcEName}-${wpcSlug}`);
1144 + }
1145 + $(container).addClass('wpc-select2-term-id-' + data.id);
1146 + if($(data.element).hasClass('wpc-show-with-parent-false')){
1147 + $(container).addClass('wpc-show-with-parent-false');
1148 + }
1149 + if($(data.element).hasClass('wpc-ask-to-parent-display')){
1150 + $(container).addClass('wpc-ask-to-parent-display');
1151 + }
1152 + if ( isHideEmpty && useApplyButton && postsCount <= 0 ) {
1153 + $(container).addClass('wpc-term-count-hidden-0');
1154 + }
1155 + return wpcSelect2Template( data );
463 1156 },
464 - minimumResultsForSearch: wpcAllowSearchField
1157 + templateSelection: function( data, container ) {
1158 + let postsCount = $(data.element).data('count');
1159 + let wpcSlug = $(data.element).data('wpcSlug');
1160 + let wpcEName = $(data.element).data('wpcEName');;
1161 + if($(data.element).hasClass('wpc-dropdown-default')){
1162 + data.text = $(data.element).text();
1163 + }
1164 + if(wpcSlug !== undefined && wpcEName !== undefined){
1165 + $(container).addClass(`select2-${wpcEName}-${wpcSlug}`);
1166 + }
1167 + $(container).addClass('wpc-select2-term-id-' + data.id);
1168 + if($(data.element).hasClass('wpc-show-with-parent-false')){
1169 + $(container).addClass('wpc-show-with-parent-false');
1170 + }
1171 + if($(data.element).hasClass('wpc-ask-to-parent-display')){
1172 + $(container).addClass('wpc-ask-to-parent-display');
1173 + }
1174 + if ( isHideEmpty && useApplyButton && postsCount <= 0 ) {
1175 + $(container).addClass('wpc-term-count-hidden-0');
1176 + }
1177 + return wpcSelect2Template( data );
1178 + },
1179 + minimumResultsForSearch: wpcAllowSearchField,
465 1180 });
466 1181
467 1182 $('.wpc-orderby-select').select2({
468 1183 dropdownCssClass: 'wpc-filter-everything-dropdown',
@@ -482,9 +1197,65 @@
482 1197 minimumResultsForSearch: Infinity
483 1198 });
484 1199 }
485 1200
1201 + function wpcSelect2Template( data ) {
1202 + // We only really care if there is an element to pull classes from
1203 + if ( ! data.element ) {
1204 + return data.text;
1205 + }
486 1206
1207 + let theImageSrc = $(data.element).data('image');
1208 + let brandImageSrc = $(data.element).data('brand');
1209 + let theColor = $(data.element).data('color');
1210 + let starRating = $(data.element).data('starRating');
1211 + let innerHtml = data.text;
1212 + let postsCount = $(data.element).data('count');
1213 + let additionalClass = '';
1214 +
1215 + if ( typeof theImageSrc !== 'undefined' ) {
1216 +
1217 + additionalClass = 'wpc-item-has-swatch';
1218 + innerHtml = $('<span data-label="' + data.text +'" class="wpc-term-swatch-wrapper wpc-term-swatch-image"><img src="'+theImageSrc+'" class="wpc-term-image" /></span><span class="wpc-term-name">'+data.text+'</span>');
1219 +
1220 + } else if ( typeof theColor !== 'undefined' ) {
1221 +
1222 + additionalClass = 'wpc-item-has-swatch';
1223 +
1224 + let swatch = '<span data-label="' + data.text +'" class="wpc-term-swatch-wrapper">';
1225 + if ( theColor === 'none' ){
1226 + swatch += '<span class="wpc-term-swatch wpc-no-swatch-yet">';
1227 + } else {
1228 + swatch += '<span class="wpc-term-swatch" style="background-color:'+theColor+'">';
1229 + }
1230 + swatch += '</span></span><span class="wpc-term-name">'+data.text+'</span>';
1231 +
1232 + innerHtml = $( swatch );
1233 +
1234 + } else if ( typeof brandImageSrc !== 'undefined' ) {
1235 +
1236 + additionalClass = 'wpc-item-has-brand';
1237 + innerHtml = $('<span data-label="' + data.text +'" class="wpc-term-image-wrapper"><img src="'+brandImageSrc+'"/></span><span class="wpc-term-name">'+data.text+'</span>');
1238 +
1239 + } else if ( typeof starRating !== 'undefined' && starRating > 0) {
1240 + additionalClass = 'wpc-item-has-star-rating';
1241 + innerHtml = $(data.element.innerHTML);
1242 + }
1243 +
1244 + let $dr_element = $(data.element);
1245 + let $dr_wrapper = $('<span></span>');
1246 + $dr_wrapper.addClass($dr_element[0].className);
1247 + if ( additionalClass !== '' ){
1248 + $dr_wrapper.addClass( additionalClass );
1249 + }
1250 + $dr_wrapper.html( innerHtml );
1251 + if ( typeof postsCount !== 'undefined' ){
1252 + $dr_wrapper.append( '<span class="wpc-term-count"><span class="wpc-term-count-brackets-open">(</span><span class="wpc-term-count-value">'+postsCount+'</span><span class="wpc-term-count-brackets-close">)</span></span>' );
1253 + }
1254 +
1255 + return $dr_wrapper;
1256 + }
1257 +
487 1258 function wpcGetCookie(name) {
488 1259 var matches = document.cookie.match(new RegExp(
489 1260 "(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)"
490 1261 ))
@@ -512,52 +1283,13 @@
512 1283 }
513 1284 document.cookie = updatedCookie
514 1285 }
515 1286
516 - function wpcDeleteCookie(name) {
517 - wpcSetCookie(name, null, { expires: -1 })
518 - }
519 -
520 - function openFilterContentBox(buttonHead)
1287 + function setStatusCookie( fid, wpcListCookieName )
521 1288 {
522 - let body = buttonHead.next('.wpc-filter-content');
523 - let setId = buttonHead.parents(wpcWidgetContainer).data('set');
524 - let widgetClass = 'wpc-filter-set-'+setId;
525 -
526 - buttonHead.addClass('wpc-opened')
527 - .removeClass('wpc-closed');
528 -
529 - body.slideDown({
530 - duration: 100,
531 - complete: function (){
532 - $(this).addClass('wpc-opened')
533 - .removeClass('wpc-closed');
534 -
535 - wpcInitSelect2(widgetClass);
536 - }
537 - });
538 - }
539 -
540 - function closeFilterContentBox(buttonHead)
541 - {
542 - let body = buttonHead.next('.wpc-filter-content');
543 -
544 - buttonHead.removeClass('wpc-opened')
545 - .addClass('wpc-closed');
546 -
547 - body.slideUp({
548 - duration: 100,
549 - complete: function (){
550 - $(this).removeClass('wpc-opened')
551 - .addClass('wpc-closed');
552 - }
553 - });
554 - }
555 -
556 - function rememberOpened(fid, wpcListCookieName)
557 - {
558 1289 let status = wpcGetCookie(wpcListCookieName);
559 1290 let _fids = new Array();
1291 +
560 1292 fid = fid.toString();
561 1293
562 1294 // In case there is no Cookies yet
563 1295 if( typeof status === 'undefined' ){
@@ -571,8 +1303,16 @@
571 1303 _fids = _fids.filter(function (el) {
572 1304 return el != '';
573 1305 });
574 1306
1307 + // Remove possible existing closed/opened to avoid double commands e.g. 151 and -151
1308 + let reversal = -fid;
1309 + let pos = _fids.indexOf( reversal.toString() );
1310 +
1311 + if ( pos !== -1 ) {
1312 + _fids.splice(pos, 1);
1313 + }
1314 +
575 1315 if( _fids.indexOf(fid) === -1 ){
576 1316 _fids.push(fid);
577 1317
578 1318 let newStatus = '';
@@ -587,35 +1327,8 @@
587 1327 }
588 1328
589 1329 }
590 1330
591 - function forgetOpened(fid, wpcListCookieName)
592 - {
593 - let status = wpcGetCookie(wpcListCookieName);
594 - fid = fid.toString();
595 -
596 - if( typeof status !== 'undefined' ){
597 - let _fids = status.split(',');
598 - let pos = _fids.indexOf(fid);
599 - if( pos !== -1 ){
600 - _fids.splice(pos, 1);
601 - let newStatus = _fids.join(',');
602 -
603 - wpcSetCookie( wpcListCookieName, newStatus, {path: '/', 'max-age': 2592000} )
604 - }
605 - }
606 - }
607 -
608 - function wpcSetVisibilityCssValues( $tag )
609 - {
610 - let wpcCssDisplay = $tag.css('display');
611 - let wpcCssOpacity = $tag.css('opacity');
612 -
613 - $tag.data('wpc-display', wpcCssDisplay);
614 - $tag.data('wpc-opacity', wpcCssOpacity);
615 -
616 - }
617 -
618 1331 function wpcCloseFiltersContainer(setId)
619 1332 {
620 1333 let $wrapper = $('.wpc-filter-set-'+setId);
621 1334 let $content = $('.wpc-filter-set-'+setId+' .wpc-filters-widget-content');
@@ -683,9 +1396,9 @@
683 1396 // Save current display, opacity and visibility values
684 1397 if (!wpcVisibility || wpcTransform !== 'none') {
685 1398 if (!$currentTag.hasClass('widget_wpc_filters_widget')
686 1399 &&
687 - !$currentTag.hasClass('wpc-filters-widget-main-wrapper')
1400 + !$currentTag.hasClass('wpc-filters-main-wrap')
688 1401 ) {
689 1402 $currentTag.css('opacity', '0');
690 1403 $currentTag.addClass('wpc-force-visibility wpc-was-invisible');
691 1404 }
@@ -724,11 +1437,13 @@
724 1437
725 1438 function wpcLockApplyButton( setId )
726 1439 {
727 1440 $(".wpc-filter-set-"+setId).addClass('is-active');
1441 + // We have only to check what the element was last focused
1442 + // if( $('.wpc-search-field').length < 1 ){
728 1443 $(".wpc-filter-set-"+setId+" .wpc-filters-submit-button").addClass('on-hold');
729 1444 $(".wpc-filter-set-"+setId+" .wpc-filters-reset-button").addClass('on-hold');
730 - // $(".wpc-filters-section-"+setId).addClass('is-active');
1445 + // }
731 1446 }
732 1447
733 1448 function wpcUnlockApplyButton( setId )
734 1449 {
@@ -734,9 +1449,8 @@
734 1449 {
735 1450 $(".wpc-filter-set-"+setId).removeClass('is-active');
736 1451 $(".wpc-filter-set-"+setId+" .wpc-filters-submit-button").removeClass('on-hold');
737 1452 $(".wpc-filter-set-"+setId+" .wpc-filters-reset-button").removeClass('on-hold');
738 - // $(".wpc-filters-section-"+setId).removeClass('is-active');
739 1453 }
740 1454
741 1455 function wpcShowSpinner()
742 1456 {
@@ -749,8 +1463,15 @@
749 1463 }
750 1464
751 1465 $.fn.wpcInitSlider = function ( form ) {
752 1466
1467 + let $el = form.parents(wpcWidgetContainer);
1468 + let setId = $el.data('set');
1469 + let applyButtonMode = false;
1470 +
1471 + if( setId > 0 && wpcApplyButtonSets.length > 0 && wpcApplyButtonSets.includes( setId ) ){
1472 + applyButtonMode = true;
1473 + }
753 1474 // Default valued at start
754 1475 let $min = form.find('.wpc-filters-range-min');
755 1476 let $max = form.find('.wpc-filters-range-max');
756 1477 let $slider = form.find('.wpc-filters-range-slider-control');
@@ -762,8 +1483,21 @@
762 1483 // Values after applying filter
763 1484 let curMinVal = parseFloat( $min.val() );
764 1485 let curMaxVal = parseFloat( $max.val() );
765 1486
1487 + if( curMaxVal !== initialMaxVal ){
1488 + $max.parent().find('.wpc-range-clear').show();
1489 + }else{
1490 + $max.parent().find('.wpc-range-clear').hide();
1491 + }
1492 +
1493 + if( curMinVal !== initialMinVal ){
1494 + $min.parent().find('.wpc-range-clear').show();
1495 + }else{
1496 + $min.parent().find('.wpc-range-clear').hide();
1497 + }
1498 +
1499 +
766 1500 // Setting value into form inputs when slider is moving
767 1501 $slider.slider({
768 1502 min: initialMinVal,
769 1503 max: initialMaxVal,
@@ -792,55 +1526,98 @@
792 1526 });
793 1527 }
794 1528
795 1529 function submitSliderForm(event, form) {
796 -
797 1530 if (event.originalEvent) {
1531 + processRangeForm( event, form );
1532 + }
1533 + }
798 1534
799 - let $min = form.find('.wpc-filters-range-min');
800 - let $max = form.find('.wpc-filters-range-max');
801 - let $slider = form.find('.wpc-filters-range-slider-control');
1535 + $(document).on('click', '.wpc-filters-range-min-column .wpc-range-clear', function(event) {
1536 + let rangeInput = $(this).parent().find('input');
1537 + let minVal = rangeInput.data('min');
1538 + $(this).hide();
1539 + rangeInput.val(minVal).change();
1540 + });
802 1541
803 - var minVal = parseFloat($min.val());
804 - var maxVal = parseFloat($max.val());
1542 + $(document).on('click', '.wpc-filters-range-max-column .wpc-range-clear', function(event) {
1543 + let rangeInput = $(this).parent().find('input');
1544 + let maxVal = rangeInput.data('max');
1545 + $(this).hide();
1546 + rangeInput.val(maxVal).change();
1547 + });
805 1548
806 - var initialMin = $slider.slider('option', 'min');
807 - var initialMax = $slider.slider('option', 'max');
1549 + function processRangeForm( event, form ){
1550 + let $el = form.parents(wpcWidgetContainer);
1551 + let setId = $el.data('set');
1552 + let applyButtonMode = false;
808 1553
809 - if (minVal === initialMin) {
810 - $min.attr('disabled', true);
811 - }
1554 + if( setId > 0 && wpcApplyButtonSets.length > 0 && wpcApplyButtonSets.includes( setId ) ){
1555 + applyButtonMode = true;
1556 + }
1557 + let low_suffix = 'min';
1558 + let high_suffix = 'max';
812 1559
813 - if (maxVal === initialMax) {
814 - $max.attr('disabled', true);
815 - }
1560 + if ( form.hasClass('wpc-filter-date-range-form') ) {
1561 + low_suffix = 'from';
1562 + high_suffix = 'to';
1563 + }
816 1564
817 - let $el = form.parents(wpcWidgetContainer);
818 - let setId = $el.data('set');
819 - let applyButtonMode = false;
1565 + let $min = form.find( '.wpc-filters-range-' + low_suffix );
1566 + let $max = form.find( '.wpc-filters-range-' + high_suffix );
820 1567
821 - if( setId > 0 && wpcApplyButtonSets.length > 0 && wpcApplyButtonSets.includes( setId ) ){
822 - applyButtonMode = true;
823 - }
1568 + if ( low_suffix === 'min' && high_suffix === 'max' ) {
1569 + var curMinVal = parseFloat( $min.val() );
1570 + var curMaxVal = parseFloat( $max.val() );
1571 + } else {
1572 + var curMinVal = $min.val().toString();
1573 + var curMaxVal = $max.val().toString();
1574 + }
824 1575
825 - if ( wpcAjax || applyButtonMode ) {
826 - event.preventDefault();
827 - let search = form.serialize();
828 - let wpcLink = form.attr('action') + '?' + search;
1576 + var initialMin = $min.data( low_suffix );
1577 + var initialMax = $max.data( high_suffix );
829 1578
830 - wpcSendFilterRequest(wpcLink, $el, applyButtonMode);
1579 + // The form has slider
1580 + if( form.hasClass('wpc-form-has-slider') ){
1581 + let $slider = form.find('.wpc-filters-range-slider-control');
1582 + // in Case of e.type === 'change' we have to set slider values
1583 + if ( event.type === 'change' ){
1584 + $slider.slider("option", "values", [curMinVal, curMaxVal]);
1585 + }
1586 + }
831 1587
1588 + if(!applyButtonMode || !wpcInstantRecount){
1589 + if (curMinVal === initialMin) {
832 1590 $min.attr('disabled', true);
1591 + }
1592 +
1593 + if (curMaxVal === initialMax) {
833 1594 $max.attr('disabled', true);
1595 + }
1596 + }
834 1597
835 - } else if( event.originalEvent ) {
836 - form.trigger('submit');
1598 + if (applyButtonMode && wpcInstantRecount) {
1599 + wpcApplyEngine.compareInputWithRangeList(form, applyButtonMode);
1600 + wpcApplyEngine.applyJsMode($el, setId)
1601 + } else if (wpcAjax || applyButtonMode) {
1602 + event.preventDefault();
1603 + let search = form.serialize();
1604 + let questionParam = '?'
1605 + if (!search) {
1606 + questionParam = '';
837 1607 }
1608 + let wpcLink = form.attr('action') + questionParam + search;
838 1609
1610 + wpcSendFilterRequest(wpcLink, $el, applyButtonMode);
1611 +
1612 + $min.attr('disabled', true);
1613 + $max.attr('disabled', true);
1614 +
1615 + } else {
1616 + form.trigger('submit');
839 1617 }
840 1618 }
841 1619
842 - // Jiboshit' jak treba!
843 1620 function wpcSendFilterRequest( link, widget, applyButtonMode, onComplete ){
844 1621
845 1622 onComplete = (typeof onComplete !== 'undefined') ? onComplete : false;
846 1623 removeElement($('.wpc-front-error'));
@@ -850,8 +1627,9 @@
850 1627 requestParams.wpcAjaxAction = 'filter';
851 1628 let setId = widget.data('set');
852 1629 let widgetClass = 'wpc-filter-set-'+setId;
853 1630 let targetPostsContainer = wpcPostContainers['default'];
1631 + let wpcUsedRouter = false;
854 1632
855 1633 if( typeof wpcPostContainers[setId] !== "undefined" ){
856 1634 targetPostsContainer = wpcPostContainers[setId];
857 1635 }
@@ -887,8 +1665,12 @@
887 1665 // $(".wpc-filters-section-"+setId).find(".wpc-filters-submit-button").attr('href', link);
888 1666
889 1667 if( applyButtonMode ){
890 1668 wpcLockApplyButton( setId );
1669 + // Legacy Apply-button mode only locks the button (pre-1.9.3 behaviour)
1670 + if( wpcInstantRecount ){
1671 + wpcShowSpinner();
1672 + }
891 1673 }else{
892 1674 wpcShowSpinner();
893 1675 }
894 1676 },
@@ -901,35 +1683,114 @@
901 1683 }
902 1684
903 1685 wpcInitiateAll();
904 1686
905 - if( applyButtonMode){
1687 + if( applyButtonMode ){
906 1688 wpcUnlockApplyButton(setId);
907 - }else{
1689 + if( wpcInstantRecount ){
1690 + wpcHideSpinner();
1691 + }
1692 + } else if( !wpcUsedRouter ) {
908 1693 wpcHideSpinner();
909 1694 }
910 -
911 1695 },
912 - success: function (response) {
913 - if (typeof response !== 'undefined' ) {
914 -
1696 + success: function ( response ) {
1697 + if ( typeof response !== 'undefined' ) {
915 1698 // Products
916 1699 // Wrap response to allow .find method search inner elements.
917 - response = '<div class="responseWrapper">'+response+'</div>';
918 - let $response = $(response);
1700 + response = '<div class="responseWrapper">'+response+'</div>';
1701 + let $response = $(response);
1702 + let $responsePostsContainer = $response.find(targetPostsContainer);
1703 + let currentSeoRuleId = $response.find('#wpc-seo-rule-id').data('seoruleid');
1704 + let isFilterRequest = $response.find('.wpc-filters-main-wrap').hasClass('wpc-filter-request');
919 1705
1706 + if($('#wpc-filter-everything-js-before').length > 0){
1707 + wpcApplyEngine.updateWpcFilterJsonData(response)
1708 + }
1709 +
1710 +
1711 + if ( currentSeoRuleId > 0 ) {
1712 + currentState = true;
1713 + } else {
1714 + currentState = false;
1715 + }
1716 +
1717 + if ( ! currentState && ! prevState ) {
1718 + toReplaceSEO = false;
1719 + } else {
1720 + toReplaceSEO = true;
1721 + }
1722 +
920 1723 if( applyButtonMode ){
921 1724 // Filters Widget
922 - wpcReloadWidget(response, widgetClass);
923 - return;
1725 + wpcReloadFiltersWidget( $response, widgetClass );
1726 + if(window.innerWidth > wpcMobileWidth ){
1727 + if(isFilterRequest){
1728 + wpcEnableStickyButtons(true);
1729 + wpcUpdateStickyButtons();
1730 + }
1731 +
1732 + if(!isFilterRequest){
1733 + wpcEnableStickyButtons(false);
1734 + wpcUpdateStickyButtons();
1735 + }
1736 + }
1737 + if( !wpcInstantRecount ){
1738 + // Legacy Apply-button recount: a per-click request refreshes
1739 + // only the widget and counters; posts wait for the Apply click
1740 + return;
1741 + }
924 1742 }
925 1743
926 - if( ( $response.find(targetPostsContainer).length > 0 ) && wpcFilterFront.wpcAjaxEnabled ){
927 - let responseTitle = $response.find('title').text();
928 - let responseCanonical = $response.find('link[rel="canonical"]').attr('href');
1744 + if(!applyButtonMode && !isFilterRequest && window.innerWidth > wpcMobileWidth ){
1745 + wpcEnableStickyButtons(false);
1746 + wpcUpdateStickyButtons();
1747 + }
929 1748
1749 + if( ( $responsePostsContainer.length > 0 ) && wpcFilterFront.wpcAjaxEnabled && wpcQueryOnThePageSets.includes( setId ) ){
1750 + if( isFilterRequest ) {
1751 + $("body").addClass('wpc_is_filter_request');
1752 + } else {
1753 + $("body").removeClass('wpc_is_filter_request');
1754 + }
930 1755 // But this works on TV also
931 - $(targetPostsContainer).html( $response.find(targetPostsContainer).html() );
1756 + //Try reinitializing the WooCommerce product-collection block via Interactivity Router
1757 + wpcUsedRouter = false;
1758 + const wpcImportmap = document.querySelector('script[type="importmap"]');
1759 + if( wpcImportmap ){
1760 + try {
1761 + const wpcMap = JSON.parse(wpcImportmap.textContent);
1762 + const wpcRouterUrl = wpcMap.imports && wpcMap.imports['@wordpress/interactivity-router'];
1763 +
1764 + if( wpcRouterUrl && $(targetPostsContainer).find('[data-wp-interactive]').length > 0 ){
1765 + wpcUsedRouter = true;
1766 + import(wpcRouterUrl).then(function(module){
1767 + if( module.actions && typeof module.actions.navigate === 'function' ){
1768 + module.actions.navigate(link, { force: true }).then(function(){
1769 + wpcHideSpinner();
1770 + }).catch(function(){
1771 + $(targetPostsContainer).html( $responsePostsContainer.html() );
1772 + wpcHideSpinner();
1773 + });
1774 + }
1775 + }).catch(function(){
1776 + $(targetPostsContainer).html( $responsePostsContainer.html() );
1777 + wpcHideSpinner();
1778 + });
1779 + }
1780 + } catch(e) {}
1781 + }
1782 +
1783 + if( !wpcUsedRouter ){
1784 + $(targetPostsContainer).html( $responsePostsContainer.html() );
1785 + }
1786 +
1787 + // Re-init Divi modules
1788 + const isDiviTheme = document.body.classList.contains('wp-theme-Divi') ||
1789 + document.body.classList.contains('theme-Divi');
1790 + if( isDiviTheme && typeof window.et_pb_init_modules === 'function' ){
1791 + window.et_pb_init_modules();
1792 + }
932 1793 // wpcPostsWereLoaded = true;
933 1794
934 1795 // Mark the "Show" button to not reload content
935 1796 $(widget).find('.wpc-filters-apply-button').addClass('wpc-posts-loaded');
@@ -935,77 +1796,103 @@
935 1796 $(widget).find('.wpc-filters-apply-button').addClass('wpc-posts-loaded');
936 1797
937 1798 //@todo update selected terms if them outside of posts container
938 1799
939 - // If h1 outside of posts container
940 - if( $response.find(targetPostsContainer).find('h1').length < 1 ){
941 - if($response.find('h1').length > 0){
942 - $('h1')[0].replaceWith( $response.find('h1')[0] );
1800 + if ( toReplaceSEO ) {
1801 + let responseTitle = $response.find('title').text();
1802 + let responseCanonical = $response.find('link[rel="canonical"]').attr('href');
1803 +
1804 + // If h1 outside of posts container
1805 + if( $responsePostsContainer.find('h1').length < 1 ){
1806 + if( $response.find('h1').length > 0){
1807 + $('h1')[0].replaceWith( $response.find('h1')[0] );
1808 + }
943 1809 }
944 - }
945 1810
946 - // If seoText container is outside from posts container
947 - if( $response.find(targetPostsContainer).find('.wpc-page-seo-description').length < 1 ){
948 - let wpcSeoTextContainer = $response.find('.wpc-page-seo-description');
949 - let originalSeoTextContainer = $('.wpc-page-seo-description');
950 - if( wpcSeoTextContainer.length > 0 && originalSeoTextContainer.length > 0){
951 - $('.wpc-page-seo-description')[0].replaceWith( wpcSeoTextContainer[0] );
1811 + // If seoText container is outside from posts container
1812 + if( $responsePostsContainer.find('.wpc-page-seo-description').length < 1 ){
1813 + let wpcSeoTextContainer = $response.find('.wpc-page-seo-description');
1814 + let originalSeoTextContainer = $('.wpc-page-seo-description');
1815 + if( wpcSeoTextContainer.length > 0 && originalSeoTextContainer.length > 0){
1816 + $('.wpc-page-seo-description')[0].replaceWith( wpcSeoTextContainer[0] );
1817 + }
952 1818 }
953 - }
954 1819
955 - // If Filters open button outside of posts container
956 - if( $response.find(targetPostsContainer).find('.wpc-open-button-'+setId).length < 1 ){
957 - if($response.find('.wpc-open-button-'+setId+' .wpc-button-inner').length > 0){
958 - let wpcButtonInnerContent = $response.find('.wpc-open-button-'+setId+' .wpc-button-inner')[0];
959 - $('.wpc-open-button-'+setId+' .wpc-button-inner').replaceWith( wpcButtonInnerContent );
1820 + // Replace title
1821 + if( typeof responseTitle !== 'undefined' && responseTitle !== '' ){
1822 + $(document).attr( 'title', responseTitle );
960 1823 }
961 - }
962 - // Replace title
963 - if( typeof responseTitle !== 'undefined' && responseTitle !== '' ){
964 - $(document).attr( 'title', responseTitle );
965 - }
966 1824
967 - // Handle <meta name="description" /> tag
968 - handleMetaTag('description', response);
1825 + // Handle <meta name="description" /> tag
1826 + handleMetaTag('description', response);
969 1827
970 - // Handle <meta name="robots" /> tag
971 - handleMetaTag('robots', response);
1828 + // Handle <meta name="robots" /> tag
1829 + handleMetaTag('robots', response);
972 1830
973 - // Handle Canonical
974 - if( typeof responseCanonical !== 'undefined' && responseCanonical !== '' ){
975 - // Replace content if tag exists
976 - if( $('link[rel="canonical"]').length > 0 ){
977 - $('link[rel="canonical"]').attr('href', responseCanonical );
978 - } else {
979 - // Append meta tag
980 - $('head').append('<link rel="canonical" href="'+responseCanonical+'" />');
1831 + // Handle Canonical
1832 + if( typeof responseCanonical !== 'undefined' && responseCanonical !== '' ){
1833 + // Replace content if tag exists
1834 + if( $('link[rel="canonical"]').length > 0 ){
1835 + $('link[rel="canonical"]').attr('href', responseCanonical );
1836 + } else {
1837 + // Append meta tag
1838 + $('head').append('<link rel="canonical" href="'+responseCanonical+'" />');
1839 + }
1840 + }else{
1841 + if( $('link[rel="canonical"]').length > 0 ){
1842 + $('link[rel="canonical"]').remove();
1843 + }
981 1844 }
982 - }else{
983 - if( $('link[rel="canonical"]').length > 0 ){
984 - $('link[rel="canonical"]').remove();
1845 + }
1846 +
1847 + // If Filters open button outside of posts container
1848 + if( $responsePostsContainer.find('.wpc-open-button-'+setId).length < 1 ) {
1849 + let wpcButtonInnerContent = $response.find('.wpc-open-button-'+setId+' .wpc-button-inner');
1850 +
1851 + if( wpcButtonInnerContent.length > 0 ) {
1852 + $('.wpc-open-button-'+setId).each( function ( bIndex, bUtton ) {
1853 + if ( $(this).parent('div').hasClass('wpc-filters-main-wrap') ){
1854 + return true;
1855 + }
1856 + $(this).find(".wpc-button-inner").replaceWith( wpcButtonInnerContent[0] );
1857 + } );
985 1858 }
986 1859 }
987 1860
988 1861 window.history.pushState({wpcHandler: 'wpcFilterEverything'}, null, link);
1862 +
1863 + prevState = currentState;
1864 + } else {
1865 + if ( $(targetPostsContainer).length === 0 && wpcFilterFront.wpcAjaxEnabled ) {
1866 + alert( noPostsContainerMsg );
1867 + }
989 1868 }
990 1869
1870 + let wpcPostsFound = $response.find('.'+widgetClass).find('.wpc-posts-found').data('found');
1871 + wpcPostsFound = parseFloat( wpcPostsFound );
1872 +
991 1873 // Chips
992 - wpcReloadChips($response);
1874 + wpcReloadChips( $response );
993 1875
994 - // Filters Widget
995 - wpcReloadWidget(response, widgetClass);
1876 + // Sorting widget
1877 + wpcReloadSorting( $response );
996 1878
997 - wpcReloadSorting($response);
1879 + // Filters Widget. It modifies $response so it is better to fire it in the end
1880 + wpcReloadFiltersWidget( $response, widgetClass );
998 1881
1882 +
999 1883 //trigger events
1000 1884 $(document).trigger( 'ready' );
1001 1885 $(window).trigger( 'scroll' );
1002 1886 $(window).trigger( 'resize' );
1003 1887
1888 + // a3 Lazy Load support
1889 + $(window).trigger( 'lazyshow' );
1890 +
1004 1891 wpcFixWoocommerceOrder();
1005 1892
1006 - let wpcPostsFound = $response.find('.'+widgetClass).find('.wpc-posts-found').data('found');
1007 - wpcPostsFound = parseFloat( wpcPostsFound );
1893 + //check rating stars
1894 + flrtStarCheck();
1008 1895
1009 1896 let applyButtonFilterSet = false;
1010 1897 if( setId > 0 && wpcApplyButtonSets.length > 0 && wpcApplyButtonSets.includes( setId ) ){
1011 1898 applyButtonFilterSet = true;
@@ -1011,9 +1898,9 @@
1011 1898 applyButtonFilterSet = true;
1012 1899 }
1013 1900
1014 1901 if( ! wpcIsMobile && wpcAutoScroll && ( wpcPostsFound < wpcPostsPerPage[setId] || applyButtonFilterSet ) ){
1015 - if( targetPostsContainer.length > 0 ){
1902 + if( $(targetPostsContainer).length > 0 ){
1016 1903 $('body, html').animate({ scrollTop:$(targetPostsContainer).offset().top - wpcAutoScrollOffset });
1017 1904 }
1018 1905 }
1019 1906
@@ -1018,15 +1905,22 @@
1018 1905 }
1019 1906
1020 1907 // Re-init Elementor actions
1021 1908 if( typeof( elementorFrontend ) !== 'undefined' ){
1022 - $(targetPostsContainer+' .elementor-element').each(
1023 - function() {
1024 - elementorFrontend.elementsHandler.runReadyTrigger( $( this ) );
1025 - }
1026 - );
1909 + if( $responsePostsContainer.hasClass('elementor-element') ){
1910 + $(targetPostsContainer+'.elementor-element').each(
1911 + function() {
1912 + elementorFrontend.elementsHandler.runReadyTrigger($(this));
1913 + }
1914 + );
1915 + } else {
1916 + $(targetPostsContainer+' .elementor-element').each(
1917 + function() {
1918 + elementorFrontend.elementsHandler.runReadyTrigger($(this));
1919 + }
1920 + );
1921 + }
1027 1922 }
1028 -
1029 1923 }
1030 1924 },
1031 1925
1032 1926 error: function (response) {
@@ -1032,9 +1926,9 @@
1032 1926 error: function (response) {
1033 1927 wpcHideSpinner();
1034 1928 let $a_el = $(widget).find('.wpc-filters-apply-button');
1035 1929 let oldLink = $a_el.data('href');
1036 - $a_el.attr('href', oldLink);
1930 + $a_el.attr('href', oldLink );
1037 1931 }
1038 1932 });
1039 1933
1040 1934 }
@@ -1063,15 +1957,20 @@
1063 1957 $(this).closest('form').submit();
1064 1958 });
1065 1959 }
1066 1960
1067 - function wpcReloadWidget( response, widgetClass ){
1961 + // The found-posts value must always be bare digits — the button template
1962 + // supplies the literal parentheses around the span
1963 + function wpcBareCount( value ) {
1964 + return String( value === undefined || value === null ? '' : value ).replace( /[()\s]/g, '' );
1965 + }
1966 +
1967 + function wpcReloadFiltersWidget( $response, widgetClass ){
1068 1968 // Replace parts
1069 - let targetWidget = '.'+widgetClass;
1070 -
1071 - let $response = $(response);
1969 + // let targetWidget = '.'+widgetClass;
1970 + // let $response = $response;
1072 1971 // It seems we need to reload all widgets available on the page
1073 - if( wpcIsMobile === true && (wpcFilterFront.showBottomWidget === 'yes') ){
1972 + if( wpcIsMobile === true && ( wpcFilterFront.showBottomWidget === 'yes' ) ){
1074 1973
1075 1974 $(wpcWidgetContainer).each( function ( index, widget ){
1076 1975 let widgetSet = $(widget).data('set');
1077 1976 let widgetClass = '.wpc-filter-set-'+widgetSet;
@@ -1084,11 +1983,15 @@
1084 1983 // Replace all filters and chips
1085 1984 if( newWidget.length > 0 ){
1086 1985 $(widgetClass).find('.wpc-filters-scroll-container').replaceWith( newWidget );
1087 1986 }
1088 - // Replace found posts number
1987 + // Replace found posts number. The template already wraps this
1988 + // span in literal parentheses, so the value written INSIDE must
1989 + // be bare digits — wpcBareCount() strips parens that a stale
1990 + // cached widget response may still carry, which otherwise
1991 + // rendered as "Show ((60))".
1089 1992 if( newPostsFound.length > 0 ){
1090 - $(widgetClass).find('.wpc-filters-found-posts').replaceWith( newPostsFound );
1993 + $(widgetClass).find('.wpc-filters-found-posts').html( wpcBareCount( newPostsFound.html() ) );
1091 1994 }
1092 1995
1093 1996 if( wpcApplyButtonSets.includes( widgetSet ) ){
1094 1997 let applyLink = $(widgetClass+" .wpc-filters-submit-button").attr('href');
@@ -1095,9 +1998,8 @@
1095 1998 if( applyLink !== '' ){
1096 1999 $(".wpc-filters-widget-controls-container .wpc-filters-submit-button").attr('href', applyLink);
1097 2000 }
1098 2001 }
1099 -
1100 2002 });
1101 2003
1102 2004 } else {
1103 2005 $(wpcWidgetContainer).each( function ( index, widget ) {
@@ -1113,12 +2015,15 @@
1113 2015 }
1114 2016
1115 2017 function wpcReloadSorting( $response ){
1116 2018 let wpcSortingForms = $response.find('.wpc-sorting-form');
2019 + if ( wpcSortingForms.length < 1 ) {
2020 + return;
2021 + }
1117 2022 let originalSortingForms = $(".wpc-sorting-form");
1118 2023
1119 2024 if( wpcSortingForms.length > 0 ){
1120 - wpcSortingForms.each(function ( index, elem ){
2025 + wpcSortingForms.each( function ( index, elem ){
1121 2026 originalSortingForms[index].replaceWith(elem);
1122 2027 });
1123 2028 }
1124 2029 }
@@ -1123,24 +2028,31 @@
1123 2028 }
1124 2029 }
1125 2030
1126 2031 function wpcReloadChips( $response ){
1127 - $(".wpc-filter-chips-list").each( function ( index, chipsWidget ) {
1128 - let chipsSet = $(chipsWidget).data('set');
1129 - let chipsWidgetClass = '.wpc-filter-chips-'+chipsSet;
1130 - let newWidgets = $response.find(chipsWidgetClass);
2032 + let $chips = $(".wpc-filter-chips-list");
2033 + if ( $chips.length < 1 ) {
2034 + return;
2035 + }
1131 2036
1132 - $(chipsWidgetClass).each( function ( innerIndex, theChipsWidget ) {
1133 - let $theChipsWidget = $(theChipsWidget);
2037 + $chips.each( function ( index, chipsWidget ) {
1134 2038
1135 - if (newWidgets.length > 0) {
1136 - $theChipsWidget.replaceWith(newWidgets[innerIndex]);
2039 + if( ( wpcIsMobile === true && ( wpcFilterFront.showBottomWidget !== 'yes' ) ) || wpcIsMobile === false ){
2040 + // Do not replace Chips inside Filters widget
2041 + if ( $(this).parent('div').hasClass('wpc-inner-widget-chips-wrapper') ){
2042 + return true;
1137 2043 }
1138 - });
2044 + }
1139 2045
2046 + let chipsSetCount = $(chipsWidget).data('setcount');
2047 + let chipsWidgetClass = '.wpc-filter-chips-'+chipsSetCount;
2048 + let newChipsInstance = $response.find(chipsWidgetClass);
2049 +
2050 + if ( newChipsInstance.length > 0 ) {
2051 + // Do not use $(this) because reloaded widget kills it
2052 + $(chipsWidgetClass).replaceWith( newChipsInstance );
2053 + }
1140 2054 });
1141 -
1142 - $(".wpc-chips-locked").removeClass("wpc-chips-locked");
1143 2055 }
1144 2056
1145 2057 window.addEventListener( 'popstate', function ( e ) {
1146 2058 // @todo the last history step sometimes doesn't reload
@@ -1150,8 +2062,407 @@
1150 2062 }
1151 2063 }
1152 2064 });
1153 2065
2066 + //check rating stars after upload
2067 + function flrtStarCheck(){
2068 + if($('label.flrt-star-label-checked').length > 0){
2069 + $('#flrt-wpc-term-count').removeClass('flrt-change-blocked');
2070 + let ratingNumChecked = $('label.flrt-star-label-checked').data('ratingNum');
2071 + let selectedAndAbove = $('.flrt-stars-wpc-filter-content').data('selectedAndAbove');
2072 + flrtGetRatingTermCount($('label.flrt-star-label-checked'))
2073 + $('label.flrt-star-label').each(function( index ) {
2074 + index += 1;
2075 + if(!selectedAndAbove && index <= ratingNumChecked){
2076 + $('label.flrt-rating-numb-' + index).addClass('flrt-star-label-hover');
2077 + }else if(selectedAndAbove && index >= ratingNumChecked) {
2078 + $('label.flrt-rating-numb-' + index).addClass('flrt-star-label-hover');
2079 + }
2080 + });
2081 + }
2082 + }
2083 + flrtStarCheck();
2084 +
2085 + $(document).on('mouseenter', 'label.flrt-star-label', function() {
2086 + if(!$('#flrt-wpc-term-count').hasClass('flrt-change-blocked')) {
2087 + let ratingNum = $(this).data('ratingNum');
2088 + let selectedAndAbove = $('.flrt-stars-wpc-filter-content').data('selectedAndAbove');
2089 + $('label.flrt-remove-star-check').removeClass('flrt-star-label-not-checked');
2090 + flrtGetRatingTermCount($(this));
2091 + for (let i = 0; i <= 5; i++) {
2092 + if (!selectedAndAbove && i <= ratingNum) {
2093 + $('label.flrt-rating-numb-' + i).addClass('flrt-star-label-hover');
2094 + } else if (selectedAndAbove && i >= ratingNum) {
2095 + $('label.flrt-rating-numb-' + i).addClass('flrt-star-label-hover');
2096 + } else {
2097 + $('label.flrt-rating-numb-' + i).removeClass('flrt-star-label-hover');
2098 + }
2099 + }
2100 + }
2101 + });
2102 +
2103 + $(document).on('click', 'label.flrt-star-label', function() {
2104 +
2105 + let $el = $(this).parents(wpcWidgetContainer);
2106 + let setId = $el.data('set');
2107 + let applyButtonMode = false;
2108 +
2109 + if( setId > 0 && wpcApplyButtonSets.length > 0 && wpcApplyButtonSets.includes(setId) ){
2110 + applyButtonMode = true;
2111 + }
2112 +
2113 + // The blocked flag freezes hover until the widget HTML is reloaded.
2114 + // In instant Apply mode no reload follows the click, so the flag
2115 + // would never be cleared and hover would stay dead until a full
2116 + // page refresh
2117 + if( !( applyButtonMode && wpcInstantRecount ) ){
2118 + $('#flrt-wpc-term-count').addClass('flrt-change-blocked');
2119 + }
2120 +
2121 + let ratingNum = $(this).data('ratingNum');
2122 + let selectedAndAbove = $('.flrt-stars-wpc-filter-content').data('selectedAndAbove');
2123 + $('label.flrt-remove-star-check').removeClass('flrt-star-label-not-checked');
2124 +
2125 +
2126 + flrtGetRatingTermCount($(this));
2127 + for (let i = 0; i <= 5; i++) {
2128 + if(!selectedAndAbove && i <= ratingNum){
2129 + $('label.flrt-rating-numb-' + i).addClass('flrt-star-label-hover');
2130 + }if(selectedAndAbove && i >= ratingNum){
2131 + $('label.flrt-rating-numb-' + i).addClass('flrt-star-label-hover');
2132 + }
2133 + }
2134 +
2135 + if(applyButtonMode && wpcInstantRecount) {
2136 + const inputId = $(this).attr('for')
2137 + const input = $('#'+inputId);
2138 + // Clear state classes from the previous selection before painting the
2139 + // new one, otherwise -checked/-not-checked leak when switching ratings
2140 + $(this).parents('.flrt-stars-wpc-filter-content').find('label.flrt-star-label')
2141 + .removeClass('flrt-star-label-hover flrt-star-label-checked flrt-star-label-not-checked')
2142 + for (let i = 0; i <= 5; i++) {
2143 + if(!selectedAndAbove && i <= ratingNum){
2144 + $('label.flrt-rating-numb-' + i).addClass('flrt-star-label-hover flrt-star-label-checked');
2145 + }if(selectedAndAbove && i >= ratingNum){
2146 + $('label.flrt-rating-numb-' + i).addClass('flrt-star-label-hover flrt-star-label-not-checked');
2147 + }
2148 + }
2149 + for (let i = 0; i <= 5; i++) {
2150 + if(ratingNum != i){
2151 + $('label.flrt-rating-numb-' + i).data('wpc-was-checked', false);
2152 + }
2153 + }
2154 + if ($(this).data('wpc-was-checked')) {
2155 + // Deselect: clear ALL state classes at once — the same result the
2156 + // input-state repaint produces for "nothing selected". Removing them
2157 + // one-by-one by re-querying '.flrt-star-label-hover' left the
2158 + // -checked/-not-checked classes behind after the hover class was gone
2159 + $(this).parents('.flrt-stars-wpc-filter-content').find('label.flrt-star-label')
2160 + .removeClass('flrt-star-label-hover flrt-star-label-checked flrt-star-label-not-checked')
2161 + $(this).data('wpc-was-checked', false)
2162 + } else {
2163 + $(this).data('wpc-was-checked', true);
2164 + }
2165 + flrtGetRatingTermCount($(this), applyButtonMode);
2166 + /*$(this).parents('.flrt-stars-wpc-filter-content')
2167 + .find('.flrt-star-label-hover')
2168 + .removeClass('flrt-star-label-hover');*/
2169 + }
2170 +
2171 + });
2172 +
2173 + $(document).on('mouseleave', '.flrt-stars-filter', function() {
2174 + if(!$('#flrt-wpc-term-count').hasClass('flrt-change-blocked')){
2175 + // The checked input is the single source of truth for the selected
2176 + // rating: in instant Apply mode the client repaint puts
2177 + // .flrt-star-label-checked on EVERY label up to the selected one,
2178 + // so reading ratingNum from the first such label restores a wrong
2179 + // (always one-star) state
2180 + const $checkedInput = $(this).find('input.flrt-star-input:checked');
2181 + if($checkedInput.length > 0){
2182 + let ratingNumChecked = Number($checkedInput.data('ratingNum'));
2183 + let selectedAndAbove = $('.flrt-stars-wpc-filter-content').data('selectedAndAbove');
2184 + flrtGetRatingTermCount($('label.flrt-rating-numb-' + ratingNumChecked))
2185 + $('label.flrt-remove-star-check').addClass('flrt-star-label-not-checked');
2186 + for (let i = 0; i <= 5; i++) {
2187 + if(!selectedAndAbove && i <= ratingNumChecked){
2188 + $('label.flrt-rating-numb-' + i).addClass('flrt-star-label-hover');
2189 + }else if(selectedAndAbove && i >= ratingNumChecked){
2190 + $('label.flrt-rating-numb-' + i).addClass('flrt-star-label-hover');
2191 + }else{
2192 + $('label.flrt-rating-numb-' + i).removeClass('flrt-star-label-hover');
2193 + }
2194 + }
2195 + }else{
2196 + $('#flrt-wpc-term-count').text('');
2197 + for (let i = 1; i <= 5; i++) {
2198 + $('label.flrt-rating-numb-' + i).removeClass('flrt-star-label-hover');
2199 + }
2200 + }
2201 + }
2202 + });
2203 +
2204 +
2205 + function flrtGetRatingTermCount(el, applyButtonMode = false){
2206 +
2207 + if(!$('#flrt-wpc-term-count').hasClass('flrt-change-blocked') || applyButtonMode){
2208 + let wpcTermCount = el.data('wpcTermCount');
2209 + let selectedAndAbove = $('.flrt-stars-wpc-filter-content').data('selectedAndAbove');
2210 + let showTermCount = $('.flrt-stars-wpc-filter-content').data('showTermCount');
2211 + if(showTermCount) {
2212 + if(selectedAndAbove){
2213 + let ratingNum = el.data('ratingNum');
2214 + let totalTerms = 0;
2215 + for (let i = ratingNum; i <= 5; i++) {
2216 + let showTermCount = $('label.flrt-rating-numb-' + i).data('wpcTermCount');
2217 + totalTerms += showTermCount;
2218 + }
2219 + $('#flrt-wpc-term-count').text(totalTerms);
2220 + }else{
2221 + $('#flrt-wpc-term-count').text(wpcTermCount);
2222 + }
2223 + if(applyButtonMode){
2224 + const checked = $('input:checked', el.parents('.flrt-stars-filter'));
2225 + if(!checked.length){
2226 + $('#flrt-wpc-term-count').text('');
2227 + }
2228 + }
2229 + }
2230 + }
2231 + }
2232 +
2233 +
2234 + // Sticky buttons: enabled lazily after first user interaction with filters
2235 + let wpcStickyButtonsActivated = false;
2236 + let wpcStickyForceOnActivate = false; // If true, fix immediately after first click even before chips are rendered
2237 +
2238 + // Checks if any filter is selected (chips present, inputs checked or a
2239 + // dropdown holds a non-default option — incl. its select2 rendering)
2240 + function wpcHasAnyFilterSelected() {
2241 + const $wrapper = $('.wpc-filters-widget-wrapper');
2242 + if (!$wrapper.length) return false;
2243 +
2244 + const hasChecks = $wrapper.find('input[type=checkbox]:checked, input[type=radio]:checked').length > 0;
2245 + // Smart spans render non-indexable chips as spans — they count too
2246 + const hasChips = $wrapper.find('.wpc-filter-chip a, .wpc-filter-chip [data-wpc-span-link], .wpc-filter-chip span.wpc-apply-button-chip').length > 0;
2247 + const hasSelects = $wrapper.find('.wpc-filters-widget-select option:selected')
2248 + .not('.wpc-dropdown-default').length > 0;
2249 +
2250 + if (hasChecks || hasChips || hasSelects) {
2251 + return true;
2252 + }
2253 +
2254 + // Numeric range and date inputs come pre-filled with their (possibly
2255 + // narrowed) bounds — only a value differing from them is a selection.
2256 + // Same rules as the Apply-URL builder in processElements: equality
2257 + // with any bound attribute, or an applied date equal to the absolute
2258 + // bound, means "not filtering".
2259 + let hasRanges = false;
2260 + $wrapper.find('form.wpc-filter-range-form input, form.wpc-filter-date-range-form-visible input')
2261 + .not('.wpc-range-list-item').each(function () {
2262 + const data = $(this).data();
2263 + let val = $(this).val();
2264 + if (typeof data.wpcEName === 'undefined' || val === '') return;
2265 + if (typeof data.wpcTempFrom !== 'undefined' && typeof data.wpcAbsFrom !== 'undefined' && data.wpcTempFrom === data.wpcAbsFrom) return;
2266 + if (typeof data.wpcTempTo !== 'undefined' && typeof data.wpcAbsTo !== 'undefined' && data.wpcTempTo === data.wpcAbsTo) return;
2267 + if (typeof data.wpcAbsFrom !== 'undefined' && data.wpcAbsFrom === val) return;
2268 + if (typeof data.wpcAbsTo !== 'undefined' && data.wpcAbsTo === val) return;
2269 + if (typeof data.min !== 'undefined' || typeof data.max !== 'undefined') {
2270 + val = Number(val);
2271 + }
2272 + if (typeof data.absMin !== 'undefined' && data.absMin === val) return;
2273 + if (typeof data.min !== 'undefined' && data.min === val) return;
2274 + if (typeof data.absMax !== 'undefined' && data.absMax === val) return;
2275 + if (typeof data.max !== 'undefined' && data.max === val) return;
2276 + hasRanges = true;
2277 + return false;
2278 + });
2279 +
2280 + if (hasRanges) {
2281 + return true;
2282 + }
2283 +
2284 + const $search = $wrapper.find('.wpc-filter-search-form input.wpc-search-field');
2285 + return $search.length > 0 && String($search.val() || '') !== '';
2286 + }
2287 +
2288 + // Enable sticky buttons after user interaction; attach handlers once
2289 + function wpcEnableStickyButtons(force = false) {
2290 + if (!wpcStickyButtonsActivated) {
2291 + const handler = wpcDebounce(wpcUpdateStickyButtons, 0);
2292 + $(window).on('scroll.wpcStickyButtons', handler);
2293 + wpcStickyButtonsActivated = true;
2294 + }
2295 +
2296 + // Force immediate fixation on first click (no need to scroll)
2297 + if (force) {
2298 + wpcStickyForceOnActivate = true;
2299 + }else{
2300 + wpcStickyForceOnActivate = false
2301 + }
2302 +
2303 + // Perform initial calculation right away so buttons become fixed immediately after click
2304 + wpcUpdateStickyButtons();
2305 + }
2306 +
2307 +
2308 +
2309 + // Updates fixed positioning of sticky buttons based on scroll position
2310 + function wpcUpdateStickyButtons() {
2311 + const $allButtons = $('.wpc-sticky-buttons');
2312 +
2313 + // If not activated yet, keep buttons unfixed
2314 + if (!wpcStickyButtonsActivated) {
2315 + return;
2316 + }
2317 +
2318 + $allButtons.each(function () {
2319 + const $buttons = $(this);
2320 + const el = $buttons[0];
2321 + if (!el) return;
2322 + const stickyButtonsWidth = el.getBoundingClientRect().width;
2323 +
2324 + const rect = el.getBoundingClientRect();
2325 + const styles = window.getComputedStyle(el);
2326 + const marginTop = parseFloat(styles.marginTop) || 0;
2327 + const marginBottom = parseFloat(styles.marginBottom) || 0;
2328 + const stickyButtonsHeight = rect.height;
2329 +
2330 + if (stickyButtonsWidth === 0 || stickyButtonsHeight === 0) {
2331 + return;
2332 + }
2333 +
2334 +
2335 +
2336 + const stickyButtonsHeightWithMargin = rect.height + marginTop + marginBottom;
2337 + const $wrapper = $buttons.closest('.wpc-filters-scroll-container');
2338 +
2339 + if (!wpcStickyForceOnActivate && !wpcHasAnyFilterSelected()) {
2340 + if ($buttons.hasClass('wpc-is-fixed-apply-button')) {
2341 + $buttons.removeClass('wpc-is-fixed-apply-button').attr('style', '');
2342 + }
2343 + const prevPos = $wrapper.data('wpc-prev-position');
2344 + if (typeof prevPos !== 'undefined') {
2345 + $wrapper.css('position', prevPos);
2346 + $wrapper.removeData('wpc-prev-position');
2347 + }
2348 +
2349 + const $ph = $buttons.prev('.wpc-sticky-placeholder');
2350 + if ($ph.length) $ph.remove();
2351 + return;
2352 + }
2353 +
2354 +
2355 + if (!$wrapper.length) {
2356 + return;
2357 + }
2358 +
2359 + const winTop = $(window).scrollTop();
2360 + const winH = $(window).height();
2361 + const winBottom = winTop + winH;
2362 +
2363 + const wrOffset = $wrapper.offset();
2364 + if (!wrOffset) {
2365 + return;
2366 + }
2367 +
2368 + const wrTop = wrOffset.top;
2369 + const wrHeight = $wrapper.outerHeight();
2370 + const wrBottom = wrTop + wrHeight;
2371 +
2372 +
2373 + const ensurePlaceholder = (h) => {
2374 + let $ph = $buttons.prev('.wpc-sticky-placeholder');
2375 + if (!$ph.length) {
2376 + $ph = $('<div class="wpc-sticky-placeholder" aria-hidden="true"></div>');
2377 + $buttons.before($ph);
2378 + }
2379 + $ph.css('height', stickyButtonsHeightWithMargin + 'px');
2380 + return $ph;
2381 + };
2382 +
2383 +
2384 +
2385 + const btnH = $buttons.outerHeight();
2386 +
2387 + const wrLeft = wrOffset.left;
2388 +
2389 + const buttonsStyles = window.getComputedStyle(el);
2390 + const buttonsTop = ($('#wpadminbar').length > 0 ? $('#wpadminbar').outerHeight() : 0) + (parseFloat(buttonsStyles.getPropertyValue('--sticky-top')) || parseFloat(el.dataset.stickyTop) || 16);
2391 + parseFloat(el.dataset.stickyTop) || 16;
2392 + const buttonsBottom = parseFloat(buttonsStyles.getPropertyValue('--sticky-bottom')) ||
2393 + parseFloat(el.dataset.stickyBottom) || 16;
2394 +
2395 + let currentPosition = $buttons.css('position');
2396 + let currentTop = currentPosition === 'fixed' ? parseFloat($buttons.css('top')) : null;
2397 + let currentBottom = currentPosition === 'fixed' ? parseFloat($buttons.css('bottom')) : null;
2398 +
2399 + let css = {
2400 + position: 'fixed',
2401 + left: wrLeft + 'px',
2402 + bottom: 0,
2403 + top: 'auto',
2404 + width: stickyButtonsWidth + 'px',
2405 + zIndex: 9999,
2406 + }
2407 +
2408 + if (currentPosition === 'fixed') {
2409 + if (currentTop !== null) {
2410 + css.top = currentTop + 'px';
2411 + }
2412 + if (currentBottom !== null) {
2413 + css.bottom = currentBottom + 'px';
2414 + }
2415 + }
2416 +
2417 + const $ph = ensurePlaceholder(btnH);
2418 +
2419 + if ($ph.length) {
2420 + let buttonsPlaceholderOffset = $('.wpc-sticky-placeholder').offset();
2421 + buttonsPlaceholderOffset.bottom = buttonsPlaceholderOffset.top + $('.wpc-sticky-placeholder').outerHeight();
2422 +
2423 + if (winTop > buttonsPlaceholderOffset.top) {
2424 + $buttons.addClass('wpc-is-fixed-apply-button');
2425 + css.top = buttonsTop + 'px';
2426 + css.left = wrLeft + 'px';
2427 + css.bottom = 'auto';
2428 + $buttons.css(css);
2429 + } else if (winBottom < buttonsPlaceholderOffset.bottom) {
2430 + $buttons.addClass('wpc-is-fixed-apply-button');
2431 + css.top = 'auto';
2432 + css.bottom = buttonsBottom + 'px';
2433 + $buttons.css(css);
2434 + }else{
2435 + $('.wpc-sticky-placeholder').remove();
2436 + css.position = 'relative';
2437 + css.top = 0;
2438 + css.left = 0;
2439 + $buttons.css(css);
2440 + $buttons.removeClass('wpc-is-fixed-apply-button');
2441 + }
2442 + }
2443 + });
2444 + }
2445 +
2446 +
2447 + function wpcDebounce(fn, wait) {
2448 + let t;
2449 + return function() {
2450 + clearTimeout(t);
2451 + const args = arguments;
2452 + const ctx = this;
2453 + t = setTimeout(function() { fn.apply(ctx, args); }, wait);
2454 + }
2455 + }
2456 +
2457 + $(function() {
2458 + if (wpcHasAnyFilterSelected() && window.innerWidth > wpcMobileWidth ) {
2459 + wpcEnableStickyButtons(true);
2460 + }
2461 + });
2462 +
2463 +
2464 +
1154 2465 $.fn.tipTip = function(options) {
1155 2466 var defaults = {
1156 2467 activation: "hover",
1157 2468 keepAlive: false,
@@ -1318,6 +2629,2340 @@
1318 2629 }
1319 2630 }
1320 2631 });
1321 2632 }
2633 +
2634 + /* =========================================================================
2635 + * Apply-button recount engine.
2636 + *
2637 + * Client-side counters recount, chips and apply-URL building for Filter
2638 + * Sets with use_apply_button = 'yes'. On such pages the server prints
2639 + * window.wpcFilterJsonData BEFORE this script (Plugin::inlineScriptJsonData,
2640 + * wp_add_inline_script 'before'). On every other page the guard below
2641 + * replaces the whole engine with no-ops, so none of its code can run.
2642 + * ========================================================================= */
2643 + const wpcApplyEngine = (function () {
2644 +
2645 + if (typeof window.wpcFilterJsonData === 'undefined' && typeof window.wpcFilterJsonDataPromise === 'undefined') {
2646 + const noop = function () {};
2647 + return {
2648 + applyJsMode: noop,
2649 + compareInputWithRangeList: noop,
2650 + unsetChip: noop,
2651 + updateWpcFilterJsonData: noop
2652 + };
2653 + }
2654 +
2655 + // Static-file delivery resolves the data asynchronously; if a user
2656 + // interaction beats the blob fetch, park it on the promise (all engine
2657 + // entry points are fire-and-forget, so deferral is transparent and the
2658 + // registration order of .then callbacks preserves the call order)
2659 + function wpcWhenDataReady(fn) {
2660 + if (typeof window.wpcFilterJsonData !== 'undefined') {
2661 + fn();
2662 + return;
2663 + }
2664 + if (window.wpcFilterJsonDataPromise) {
2665 + wpcShowSpinner();
2666 + window.wpcFilterJsonDataPromise.then(function () {
2667 + wpcHideSpinner();
2668 + if (typeof window.wpcFilterJsonData !== 'undefined') fn();
2669 + });
2670 + }
2671 + }
2672 +
2673 + // Array.prototype.push(...arr) / Math.min(...arr) put every element on the
2674 + // CALL STACK — at 100k-product scale the term post lists exceed V8's
2675 + // argument limit and throw "Maximum call stack size exceeded"; loop instead.
2676 + // Min/max coerce like Math.min does: meta_values may carry numeric strings.
2677 + function wpcPushAll(target, items) {
2678 + for (let i = 0; i < items.length; i++) target.push(items[i]);
2679 + return target;
2680 + }
2681 + function wpcArrayMin(items) {
2682 + let min = Infinity;
2683 + for (let i = 0; i < items.length; i++) {
2684 + const v = Number(items[i]);
2685 + if (v < min) min = v;
2686 + }
2687 + return min;
2688 + }
2689 + function wpcArrayMax(items) {
2690 + let max = -Infinity;
2691 + for (let i = 0; i < items.length; i++) {
2692 + const v = Number(items[i]);
2693 + if (v > max) max = v;
2694 + }
2695 + return max;
2696 + }
2697 +
2698 + function updateCounters($el, setId) {
2699 +
2700 + const filterSetData = wpcFilterJsonData[setId];
2701 + filterSetData.chips = [];
2702 + filterSetData.tempFilteredAllPostsIds = {};
2703 + filterSetData.tempFilteredTerms = {};
2704 + delete filterSetData.filteredPostsIds;
2705 +
2706 + // Needed by the collectors below: filtering parent-keyed dates against the
2707 + // expanded universe alone loses every variable product from a date selection
2708 + const { inSetUniverse } = wpcUniverse(setId);
2709 +
2710 +
2711 + const $all = $('input:checked:not(.wpc-range-list-item), option:selected:not(.wpc-range-list-item), form.wpc-filter-range-form input:not(.wpc-range-list-item), form.wpc-filter-date-range-form-visible input, input[data-wpc-was-checked=true]', $el);
2712 +
2713 + //const allPostsSet = new Set(getAllPostsIdsInArray(setId));
2714 +
2715 +
2716 + const getEntityItems = (data) => {
2717 + let wpcSlug = wpcTermSlug(data.wpcSlug);
2718 +
2719 + if (typeof data.min !== 'undefined') wpcSlug = 'min';
2720 + if (typeof data.max !== 'undefined') wpcSlug = 'max';
2721 + if (typeof data.wpcTempFrom !== 'undefined') wpcSlug = 'from';
2722 + if (typeof data.wpcTempTo !== 'undefined') wpcSlug = 'to';
2723 +
2724 + const allEntityItems = filterSetData.allEntities[data.wpcEName].items;
2725 + for (const item of Object.values(allEntityItems)) {
2726 + if (item.slug == wpcSlug) return item.posts;
2727 + }
2728 + return [];
2729 + };
2730 +
2731 + const getAllEntityItemsForRange = (data) => {
2732 + let wpcSlug = wpcTermSlug(data.wpcSlug);
2733 + if(data.min === undefined && data.max === undefined){
2734 + return;
2735 + }
2736 + if (typeof data.min !== 'undefined') wpcSlug = 'min';
2737 + if (typeof data.max !== 'undefined') wpcSlug = 'max';
2738 +
2739 + const allEntityItems = filterSetData.allEntities[data.wpcEName].items;
2740 + for (const [key, item] of Object.entries(allEntityItems)) {
2741 + if (item.slug === wpcSlug){
2742 + wpcFilterJsonData[setId].allEntities[data.wpcEName].items[key].posts = Object.keys(item.meta_values).map(Number);
2743 + return Object.keys(item.meta_values).map(Number);
2744 + }
2745 + }
2746 + return [];
2747 + };
2748 +
2749 + const filterRangeItems = ($currentEl, postsIds, setId) => {
2750 + const data = $currentEl.data();
2751 + const postsArray = Array.isArray(postsIds) ? postsIds : Object.values(postsIds);
2752 + const filteredPostsIds = [];
2753 +
2754 + if (typeof data.min !== 'undefined' || typeof data.max !== 'undefined') {
2755 + const $form = $currentEl.parents('.wpc-filter-range-form');
2756 + const $minEl = $('.wpc-filters-range-min', $form);
2757 + const $maxEl = $('.wpc-filters-range-max', $form);
2758 + const minCurVal = +$minEl.val();
2759 + const maxCurVal = +$maxEl.val();
2760 +
2761 + // An untouched side means NO bound: the server renders the input
2762 + // VALUE and data-min/max as placeholders scoped to the CURRENT
2763 + // result set, so using them as limits kept phantom constraints
2764 + // after another selection changed (numeric twin of the untouched-
2765 + // date fix). Untouched = value equals the Abs bound OR the current
2766 + // placeholder — exactly the checks buildUrlForApplyButton uses
2767 + // before omitting the query param.
2768 + const minUntouched = ($minEl.data().absMin === minCurVal) || (Number($minEl.data().min) === minCurVal);
2769 + const maxUntouched = ($maxEl.data().absMax === maxCurVal) || (Number($maxEl.data().max) === maxCurVal);
2770 +
2771 + if (minUntouched && maxUntouched) {
2772 + return postsIds;
2773 + }
2774 +
2775 + const minBound = minUntouched ? -Infinity : minCurVal;
2776 + const maxBound = maxUntouched ? Infinity : maxCurVal;
2777 +
2778 + let filterPostsIds = [];
2779 + for (const postId of postsArray) {
2780 + if(inSetUniverse(postId)){
2781 + filterPostsIds.push(+postId);
2782 + }
2783 + }
2784 + const entityNumberValues = filterSetData.allEntities[data.wpcEName].items.min.meta_values;
2785 + for (const postId of filterPostsIds) {
2786 + const postValues = entityNumberValues[postId];
2787 + if (postValues !== undefined) {
2788 + for (let postVal of postValues) {
2789 + if (postVal >= minBound && postVal <= maxBound) {
2790 + filteredPostsIds.push(postId);
2791 + }
2792 + }
2793 + }
2794 + }
2795 + return filteredPostsIds;
2796 + }
2797 +
2798 +
2799 + if (typeof data.wpcTempFrom !== 'undefined' || typeof data.wpcTempTo !== 'undefined') {
2800 + const $form = $currentEl.parents('.wpc-filters-range-inputs form');
2801 + const $fromEl = $('.wpc-filters-range-from.hasDatepicker', $form);
2802 + const $toEl = $('.wpc-filters-range-to.hasDatepicker', $form);
2803 + const from = $fromEl.data('wpcTempFrom');
2804 + const to = $toEl.data('wpcTempTo');
2805 +
2806 + if ($fromEl.data('wpcAbsFrom') === from && $toEl.data('wpcAbsTo') === to) {
2807 + return postsIds;
2808 + }
2809 +
2810 + const entityDateValues = filterSetData.allEntities[data.wpcEName].items.from.meta_values;
2811 + const dateType = $fromEl.data('wpcDateType');
2812 + let dateFrom = from ? $fromEl[wpcPickerFn()]('getDate') : null;
2813 + let dateTo = to ? $toEl[wpcPickerFn()]('getDate') : null;
2814 +
2815 + // An untouched field means NO bound, not "clamp to the displayed
2816 + // Abs value": on an applied page the server renders Abs bounds
2817 + // scoped to the CURRENT result set (they are placeholders), so
2818 + // clamping cut off products outside the previous selection when
2819 + // another filter changed. Mirrors buildUrlForApplyButton, which
2820 + // omits the date param for untouched fields.
2821 + if($fromEl.val() === $fromEl.data('wpcAbsFrom')){
2822 + dateFrom = null;
2823 + }
2824 +
2825 + if($toEl.val() === $toEl.data('wpcAbsTo')){
2826 + dateTo = null;
2827 + }
2828 +
2829 + // meta_values dates are minute-precision (seconds zeroed) — keep
2830 + // picked boundaries in the same precision
2831 + if (dateFrom) dateFrom.setSeconds(0, 0);
2832 + if (dateTo) dateTo.setSeconds(0, 0);
2833 +
2834 +
2835 + let todayStr = null;
2836 + if (dateType === 'time') {
2837 + const t = new Date();
2838 + todayStr = `${t.getFullYear()}-${String(t.getMonth() + 1).padStart(2, '0')}-${String(t.getDate()).padStart(2, '0')}`;
2839 + }
2840 +
2841 + //const variationMap = wpcFilterJsonData.product_variations_map;
2842 +
2843 +
2844 + // Candidates come from the meta_values KEYS, not from item.posts:
2845 + // the server intersects item.posts with the variation-expanded
2846 + // universe (variable parents replaced by variation ids there),
2847 + // while post dates are keyed by PARENT ids — using item.posts
2848 + // dropped every variable product from a date selection
2849 + let filterPostsIds = [];
2850 + for (const postId of Object.keys(entityDateValues).map(Number)) {
2851 + if(inSetUniverse(postId)){
2852 + filterPostsIds.push(postId);
2853 + }
2854 + }
2855 +
2856 + for (const postId of filterPostsIds) {
2857 + const dateValStr = entityDateValues[postId];
2858 + if (!dateValStr) continue;
2859 +
2860 + let dateVal;
2861 + if (dateType === 'time') {
2862 + const timePart = dateValStr.substring(dateValStr.indexOf(' ') + 1);
2863 + if (!timePart) continue;
2864 + dateVal = new Date(`${todayStr} ${timePart}`);
2865 + } else {
2866 + dateVal = new Date(dateValStr);
2867 + }
2868 +
2869 + const isAfterFrom = !dateFrom || dateVal >= dateFrom;
2870 + const isBeforeTo = !dateTo || dateVal <= dateTo;
2871 + if (isAfterFrom && isBeforeTo){
2872 + filteredPostsIds.push(postId);
2873 + }
2874 + }
2875 + }
2876 +
2877 + return filteredPostsIds;
2878 + };
2879 +
2880 +
2881 + const filterLogic = (entityName, postIds, logic) => {
2882 + let posts;
2883 +
2884 + if (logic === 'and' && postIds.length > 1) {
2885 +
2886 + let set = new Set(postIds[0]);
2887 + for (let i = 1; i < postIds.length; i++) {
2888 + set = new Set(postIds[i].filter(id => set.has(id)));
2889 + }
2890 + posts = [...set];
2891 + } else {
2892 + posts = [...new Set(postIds.flat())];
2893 + }
2894 +
2895 + return posts;
2896 + };
2897 +
2898 +
2899 +
2900 + const countTerms = ($elements, setId) => {
2901 + const tempFilteredAllPostsIds = {};
2902 + const tempFilteredTerms = {};
2903 + const setData = wpcFilterJsonData[setId];
2904 +
2905 +
2906 + $elements.each((index, el) => {
2907 + const $currentEl = $(el);
2908 + const data = $currentEl.data();
2909 + if (typeof data.wpcEName === 'undefined' || typeof data.wpcSlug === 'undefined') return;
2910 +
2911 + let currentPostsIds = getEntityItems(data);
2912 + if (!tempFilteredTerms[data.wpcEName]) {
2913 + tempFilteredTerms[data.wpcEName] = [];
2914 + }
2915 + tempFilteredTerms[data.wpcEName].push(wpcTermSlug(data.wpcSlug));
2916 +
2917 + const isRange = typeof data.min !== 'undefined' || typeof data.max !== 'undefined';
2918 + const isDate = typeof data.wpcTempFrom !== 'undefined' || typeof data.wpcTempTo !== 'undefined';
2919 + const { used_for_variations: usedForVariations } = setData.allEntities[data.wpcEName].filter;
2920 + const isUsedForVariations = usedForVariations === 'yes' || usedForVariations === true;
2921 +
2922 + if (isRange || isDate) {
2923 + if (isRange) {
2924 + currentPostsIds = getAllEntityItemsForRange(data);
2925 + const $form = $currentEl.parents('.wpc-filter-range-form');
2926 + const $minEl = $('.wpc-filters-range-min', $form);
2927 + const $maxEl = $('.wpc-filters-range-max', $form);
2928 + const min = $minEl.data().min;
2929 + const max = $maxEl.data().max;
2930 +
2931 + if (min !== +$minEl.val() || max !== +$maxEl.val()) {
2932 + currentPostsIds = filterRangeItems($currentEl, currentPostsIds, setId);
2933 + if (currentPostsIds.length) {
2934 + if (!tempFilteredAllPostsIds[data.wpcEName]) tempFilteredAllPostsIds[data.wpcEName] = [];
2935 + tempFilteredAllPostsIds[data.wpcEName][index] = currentPostsIds;
2936 + }
2937 + collectChips($currentEl, setId);
2938 + }
2939 + }
2940 +
2941 +
2942 + if (isDate) {
2943 + const $form = $currentEl.parents('.wpc-filters-range-inputs');
2944 + const $fromEl = $('.wpc-filters-range-from', $form);
2945 + const $toEl = $('.wpc-filters-range-to', $form);
2946 +
2947 + if ($fromEl.data('wpcTempFrom') !== $fromEl.data('wpcAbsFrom') ||
2948 + $toEl.data('wpcTempTo') !== $toEl.data('wpcAbsTo')) {
2949 +
2950 + currentPostsIds = filterRangeItems($currentEl, currentPostsIds, setId);
2951 + if (currentPostsIds.length) {
2952 + if (!tempFilteredAllPostsIds[data.wpcEName]) tempFilteredAllPostsIds[data.wpcEName] = [];
2953 + tempFilteredAllPostsIds[data.wpcEName][index] = currentPostsIds;
2954 + }
2955 + collectChips($currentEl, setId);
2956 + }
2957 + }
2958 +
2959 + } else {
2960 + if (!tempFilteredAllPostsIds[data.wpcEName]) tempFilteredAllPostsIds[data.wpcEName] = [];
2961 + tempFilteredAllPostsIds[data.wpcEName][index] = currentPostsIds;
2962 + collectChips($currentEl, setId);
2963 + }
2964 + });
2965 + filterSetData.tempFilteredTerms = tempFilteredTerms;
2966 + for (const [entityName, postIds] of Object.entries(tempFilteredAllPostsIds)) {
2967 + filterSetData.tempFilteredAllPostsIds[entityName] = filterLogic(entityName, postIds, setId);
2968 + }
2969 + };
2970 +
2971 +
2972 +
2973 + function getAllPostsIdsInArray(setId) {
2974 + const ids = wpcFilterJsonData[setId].allPostsIds;
2975 + if (Array.isArray(ids)) return wpcFilterJsonData.allPostsIds.map(Number);
2976 + return Object.keys(ids).map(Number);
2977 + }
2978 + const changeFiltersWithParents = ($el, setId) => {
2979 + const $filtersWithParent = $('.wpc-has-parent-filter', $el);
2980 + const hideEmpty = (filterSetData.settings.hide_empty !== undefined) ? wpcFilterJsonData[setId].settings.hide_empty : false;
2981 + const isHideEmpty = hideEmpty === 'yes';
2982 + let $is_parent_unchecked = false;
2983 + $filtersWithParent.each((index, filter) => {
2984 + const $filter = $(filter);
2985 + const parentFilterId = $filter.data('parentFilterId');
2986 + if(parentFilterId !== undefined){
2987 + const hideUntilParent = ($filter.data('hideUntilParent') === 1);
2988 + const filterEName = $filter.data('filterEName');
2989 + const $parentFilter = $('.wpc-filters-section-' + parentFilterId, $el);
2990 + const parentFilterEName = $parentFilter.data('filterEName');
2991 + const $select = $filter.find('select');
2992 + const $wpcDropdownDefault = $select.find('option.wpc-dropdown-default');
2993 + const wpcDropdownDefaultData = $wpcDropdownDefault.data();
2994 + const isSelect2 = $select.hasClass('select2-hidden-accessible');
2995 + const moreLess = filterSetData.allEntities[filterEName].filter.more_less;
2996 + const entity = filterSetData.allEntities[filterEName].filter.entity;
2997 + const isMoreLess = moreLess === 'yes';
2998 + const hideEmptyFilter = (filterSetData.settings.hide_empty_filter !== undefined) ? wpcFilterJsonData[setId].settings.hide_empty_filter : false;
2999 + const isHideEmptyFilter = hideEmptyFilter === 'yes';
3000 + if(
3001 + filterSetData.tempFilteredAllPostsIds[parentFilterEName] !== undefined
3002 + &&
3003 + filterSetData.tempFilteredAllPostsIds[parentFilterEName].length > 0
3004 + ){
3005 + const allParentFilterPosts = filterSetData.tempFilteredAllPostsIds[parentFilterEName];
3006 + if ($select.length) {
3007 + $filter.find('option.wpc-term-item').each((index, elem) => {
3008 + const data = $(elem).data();
3009 + const entityItems = getEntityItems(data);
3010 + const hasMatch = entityItems && entityItems.length > 0 &&
3011 + entityItems.some(id => allParentFilterPosts.includes(Number(id)));
3012 + if(hasMatch){
3013 + $(elem).addClass('wpc-show-with-parent-true')
3014 + if ($(elem).hasClass('wpc-has-terms')) {
3015 + if(!$(elem).is(':selected')){
3016 + $(elem).removeClass('wpc-show-with-parent-false');
3017 + }
3018 +
3019 + } else {
3020 + if(isHideEmpty){
3021 + $(elem).addClass('wpc-show-with-parent-false');
3022 + }else{
3023 + $(elem).removeClass('wpc-show-with-parent-false');
3024 + }
3025 + }
3026 + }else{
3027 + if(!$(elem).is(':selected')) {
3028 + $(elem).addClass('wpc-show-with-parent-false').removeClass('wpc-show-with-parent-true');
3029 + }
3030 + }
3031 + });
3032 + $('.wpc-dropdown-default-' + filterEName, $el).text(wpcDropdownDefaultData.wpcDefaultOptionText)
3033 + } else {
3034 + $filter.find('li.wpc-term-item').each((index, elem) => {
3035 + const data = $(elem).find('input').data();
3036 + const entityItems = getEntityItems(data);
3037 + const hasMatch = entityItems && entityItems.length > 0 &&
3038 + entityItems.some(id => allParentFilterPosts.includes(Number(id)));
3039 + if(hasMatch){
3040 + $(elem).addClass('wpc-show-with-parent-true')
3041 + if ($(elem).hasClass('wpc-has-terms')) {
3042 + $(elem).removeClass('wpc-show-with-parent-false');
3043 + } else {
3044 + if(isHideEmpty){
3045 + if(!$(elem).find('input').is(':checked')){
3046 + $(elem).addClass('wpc-show-with-parent-false');
3047 + }
3048 + }else{
3049 + $(elem).removeClass('wpc-show-with-parent-false');
3050 + }
3051 + }
3052 + }else{
3053 + if(!$(elem).find('input').is(':checked')) {
3054 + $(elem).addClass('wpc-show-with-parent-false').removeClass('wpc-show-with-parent-true');
3055 + }
3056 + }
3057 + });
3058 +
3059 +
3060 +
3061 + let $wpcTermsItems = $filter.find(`.wpc-filters-ul-list li.wpc-show-with-parent-true`);
3062 + $wpcTermsItems.removeClass('wpc-not-hidden-term');
3063 + const $wpcHasTermsItems = $filter.find(`.wpc-filters-ul-list li.wpc-has-terms.wpc-show-with-parent-true`)
3064 + const $wpcHasCheckedTermsItems = $filter.find(`.wpc-filters-ul-list input:checked, .wpc-filters-ul-list option:selected`)
3065 + let wpcHasTerms = $wpcHasTermsItems.length || $wpcHasCheckedTermsItems.length
3066 + const isHideFilterElement = (entity === 'post_meta_num' || entity === 'post_date' || entity === 'tax_numeric' || entity === 'post_meta_num');
3067 +
3068 + if (isHideFilterElement && wpcFilterJsonData[setId].entityPostsCount[data.wpcEName] !== undefined) {
3069 + wpcHasTerms = wpcFilterJsonData[setId].entityPostsCount[data.wpcEName] === true ? 1 : 0;
3070 + }
3071 + if(wpcHasTerms <= 0 && isHideEmptyFilter){
3072 + $filter.addClass('wpc-filters-section-0');
3073 + }else{
3074 + $filter.removeClass('wpc-filters-section-0');
3075 + }
3076 +
3077 + // Same as in updateCountersHtml: trust the rendered
3078 + // section class when the (possibly stale) JSON config
3079 + // says the filter has no More/Less
3080 + if(isMoreLess || $filter.hasClass('wpc-filter-more-less')){
3081 + if(wpcHasTerms <= +wpcMoreLessCount){
3082 + $filter.addClass('wpc-filter-few-terms');
3083 + }else{
3084 + $filter.removeClass('wpc-filter-few-terms');
3085 + }
3086 + if(isHideEmpty){
3087 + $wpcTermsItems.filter('li.wpc-has-terms.wpc-show-with-parent-true').slice(0, +wpcMoreLessCount).each(function() {
3088 + $(this).addClass('wpc-not-hidden-term');
3089 + });
3090 + }else{
3091 + $wpcTermsItems.filter('li.wpc-show-with-parent-true').slice(0, +wpcMoreLessCount).each(function() {
3092 + $(this).addClass('wpc-not-hidden-term');
3093 + });
3094 + }
3095 + }
3096 +
3097 + }
3098 +
3099 + if($filter.find('.wpc-has-terms').length > 0 || $filter.hasClass('wpc-range-filter')){
3100 + $filter.find('.wpc-ask-to-parent-display').addClass('wpc-ask-to-parent-display-none');
3101 + $filter.removeClass('wpc-parent-filter-terms-unselected').addClass('wpc-parent-filter-terms-selected')
3102 + if(hideUntilParent){
3103 + $filter.removeClass('wpc-hide-terms-until-parent-unselected')
3104 + }
3105 + }else{
3106 + if(isHideEmpty){
3107 + $filter.find('.wpc-ask-to-parent-display').removeClass('wpc-ask-to-parent-display-none');
3108 + $filter.removeClass('wpc-parent-filter-terms-selected').addClass('wpc-parent-filter-terms-unselected')
3109 + if(hideUntilParent){
3110 + $filter.addClass('wpc-hide-terms-until-parent-unselected')
3111 + }
3112 + }else{
3113 + $filter.find('wpc-ask-to-parent-display-none').removeClass('.wpc-ask-to-parent-display');
3114 + $filter.removeClass('wpc-parent-filter-terms-unselected').addClass('wpc-parent-filter-terms-selected')
3115 + if(hideUntilParent){
3116 + $filter.removeClass('wpc-hide-terms-until-parent-unselected')
3117 + }
3118 + }
3119 + }
3120 + if($filter.hasClass('wpc-range-from-elem')){
3121 + $filter.find('.wpc-ask-to-parent-display').addClass('wpc-ask-to-parent-display-none');
3122 + $filter.removeClass('wpc-show-range-with-parent-false')
3123 + }
3124 + if($filter.hasClass('wpc-stars-rating-block')){
3125 + $filter.find('.wpc-ask-to-parent-display').addClass('wpc-ask-to-parent-display-none');
3126 + $filter.removeClass('wpc-parent-filter-terms-unselected');
3127 + }
3128 + }else{
3129 + if(filterSetData.tempFilteredAllPostsIds[filterEName] !== undefined
3130 + &&
3131 + filterSetData.tempFilteredAllPostsIds[filterEName].length > 0){
3132 + $filter.addClass('wpc-child-selected-no-parent')
3133 + $filter.find('input:checked').prop('checked', false);
3134 + $filter.find('option:selected').prop('selected', false);
3135 + delete filterSetData.tempFilteredAllPostsIds[filterEName];
3136 + $is_parent_unchecked = true;
3137 + }else{
3138 + $filter.removeClass('wpc-child-selected-no-parent')
3139 + }
3140 +
3141 + if(hideUntilParent){
3142 + $filter.addClass('wpc-hide-terms-until-parent-unselected')
3143 + }
3144 +
3145 + $filter.find('.wpc-ask-to-parent-display').removeClass('wpc-ask-to-parent-display-none');
3146 + $filter.removeClass('wpc-parent-filter-terms-selected').addClass('wpc-parent-filter-terms-unselected')
3147 + if($select.length) {
3148 + $filter.find('option.wpc-term-item').each((index, elem) => {
3149 + if(!$(elem).is(':selected')){
3150 + $(elem).addClass('wpc-show-with-parent-false');
3151 + }
3152 + });
3153 + $('.wpc-dropdown-default-' + filterEName, $el).text(wpcDropdownDefaultData.wpcSelectParentText)
3154 + }else{
3155 + $filter.find('li.wpc-term-item').each((index, elem) => {
3156 + if(!$(elem).is(':checked')){
3157 + $(elem).addClass('wpc-show-with-parent-false');
3158 + }
3159 + });
3160 + }
3161 + if($filter.hasClass('wpc-range-from-elem')){
3162 + $filter.addClass('wpc-show-range-with-parent-false')
3163 + $filter.find('.wpc-ask-to-parent-display').removeClass('wpc-ask-to-parent-display-none');
3164 + }
3165 + if($filter.hasClass('wpc-stars-rating-block')){
3166 + $filter.find('.wpc-ask-to-parent-display').removeClass('wpc-ask-to-parent-display-none');
3167 + $filter.addClass('wpc-parent-filter-terms-unselected');
3168 + }
3169 + }
3170 + }
3171 +
3172 + });
3173 + if($is_parent_unchecked){
3174 + applyJsMode($el, setId);
3175 + }
3176 + }
3177 +
3178 + countTerms($all, setId);
3179 + updateChipsList(setId);
3180 + changeCounters($el, setId);
3181 + changeFiltersWithParents($el, setId)
3182 + }
3183 +
3184 + function changeCounters($el, setId){
3185 +
3186 + const $otherFilterSetsOnPage = $(wpcWidgetContainer);
3187 + const hasMultipleWidgets = $otherFilterSetsOnPage.length > 1;
3188 +
3189 + if (hasMultipleWidgets) {
3190 + let tempPostIds = [];
3191 + let relatedSetsIds = [];
3192 + $otherFilterSetsOnPage.each((index, filterSetWidget) => {
3193 + const widgetSetId = $(filterSetWidget).data('set');
3194 +
3195 + const relatedSets = wpcFilterJsonData[widgetSetId].relatedSets;
3196 +
3197 + if(relatedSetsIds[relatedSets] === undefined){
3198 + relatedSetsIds[relatedSets] = [];
3199 + }
3200 + if(relatedSetsIds[relatedSets][widgetSetId] === undefined){
3201 + relatedSetsIds[relatedSets][widgetSetId] = [];
3202 + }
3203 + relatedSetsIds[relatedSets][widgetSetId] = wpcFilterJsonData[widgetSetId].filteredPostsIds
3204 +
3205 +
3206 + });
3207 +
3208 + if (Object.keys(relatedSetsIds).length > 0) {
3209 + let setParams = [];
3210 + Object.entries(relatedSetsIds).forEach(([relatedSets, widgetSetIds]) => {
3211 + const intersectionPosts = Object.values(widgetSetIds).reduce((acc, arr) => {
3212 + const set = new Set(arr);
3213 + return acc.filter(id => set.has(id));
3214 + }, []);
3215 + Object.entries(widgetSetIds).forEach(([relatedSetId, widgetPostIds]) => {
3216 + wpcFilterJsonData[relatedSetId].filteredPostsIds = intersectionPosts;
3217 + Object.entries(widgetSetIds).forEach(([relatedSetIdNext, widgetPostIdsNext]) => {
3218 + if(relatedSetIdNext !== relatedSetId){
3219 + const filteredPostsIds = wpcFilterJsonData[relatedSetId].tempFilteredAllPostsIds;
3220 + if (filteredPostsIds && typeof filteredPostsIds === 'object') {
3221 + Object.entries(filteredPostsIds).forEach(([eName, entityPostIds]) => {
3222 + if(wpcFilterJsonData[relatedSetIdNext].tempFilteredAllPostsIds === undefined){
3223 + wpcFilterJsonData[relatedSetIdNext].tempFilteredAllPostsIds = [];
3224 + }
3225 + if(typeof wpcFilterJsonData[relatedSetIdNext].tempFilteredAllPostsIds[eName] === 'undefined'){
3226 + wpcFilterJsonData[relatedSetIdNext].tempFilteredAllPostsIds[eName] = entityPostIds;
3227 + }
3228 + });
3229 + }
3230 + }
3231 + });
3232 + });
3233 + });
3234 + }
3235 + }
3236 +
3237 + const setData = wpcFilterJsonData[setId];
3238 + let allEntities = JSON.parse(JSON.stringify(wpcFilterJsonData[setId].allEntities));
3239 + const tempFiltered = setData.tempFilteredAllPostsIds;
3240 + const hasTempFilters = Object.keys(tempFiltered).length > 0;
3241 + let entityPostsCount = [];
3242 + let allPostsIds = getVariation(Object.keys(setData.allPostsIds), false);
3243 + const { allPostsUniverseSet, allPostsExpandedSet, inSetUniverse } = wpcUniverse(setId);
3244 +
3245 + // Final displayed count for a candidate id list. On variations-as-products
3246 + // shops item.posts carry ALL variations of the matched parents (the server
3247 + // clamps its final counts against the set universe in calcTermCount) —
3248 + // mirror that clamp here, in variation space. On regular shops the
3249 + // parent-space collapse compensates already (parents are universe-bound),
3250 + // so the behavior there is unchanged.
3251 + const wpcDisplayCount = (ids) => (wpcFilterJsonData.variationsAsProducts
3252 + ? wpcCountSpace(ids.filter(inSetUniverse))
3253 + : wpcCountSpace(ids)).length;
3254 +
3255 + if (hasTempFilters) {
3256 + for (const [tempEntityName, postIds] of Object.entries(tempFiltered)) {
3257 + // Cross-entity intersections run in PARENT (product-level) space,
3258 + // mirroring the server SQL where every filter is an independent
3259 + // product-level clause: a product with a blue variation at 65 and a
3260 + // non-blue variation at 72 DOES match color-blue + price 70..100,
3261 + // even though no single variation satisfies both conditions
3262 + const parentIdSet = new Set(postIds.map(wpcParentOf));
3263 +
3264 + for (const [entityName, entity] of Object.entries(allEntities)) {
3265 + const { logic, range_list_input: rangeList, used_for_variations: usedForVariations } = entity.filter;
3266 + const isAndLogic = logic === 'and';
3267 + const isUsedForVariations = usedForVariations === 'yes' || usedForVariations === true;
3268 + const hasOnlyOneElement = Object.keys(tempFiltered).length === 1 && tempFiltered[entityName] !== undefined;
3269 +
3270 + for (const [index, item] of Object.entries(entity.items)) {
3271 + let tempPostIdsArray;
3272 + if (tempEntityName === entityName) {
3273 + tempPostIdsArray = isAndLogic
3274 + ? item.posts.filter(x => parentIdSet.has(wpcParentOf(x)) && inSetUniverse(x))
3275 + : item.posts.filter(x => inSetUniverse(x));
3276 + } else {
3277 + tempPostIdsArray = item.posts.filter(x => parentIdSet.has(wpcParentOf(x)));
3278 + }
3279 +
3280 + allEntities[entityName].items[index].posts = tempPostIdsArray;
3281 + allEntities[entityName].items[index].count = tempPostIdsArray.length;
3282 +
3283 + // On variations-as-products shops the DISPLAY count must be
3284 + // taken from the pre-normalization list: getVariation() below
3285 + // collapses non-ufv lists to parent ids for the cross-entity
3286 + // intersections, but the visible catalog unit is a variation
3287 + // (e.g. Variable: 467 raw -> universe-clamped 141, while the
3288 + // collapsed list would count 40 parents).
3289 + const wpcDisplayIds = tempPostIdsArray;
3290 +
3291 + tempPostIdsArray = getVariation(tempPostIdsArray, isUsedForVariations);
3292 +
3293 + // Displayed counters are distinct products (parent space), as
3294 + // the server counts them — non-ufv lists also carry variation
3295 + // ids alongside parents, so a plain .length over-counts. On
3296 + // variations-as-products shops counts stay in variation space,
3297 + // clamped to the set universe.
3298 + allEntities[entityName].items[index].cross_count = wpcDisplayCount(
3299 + wpcFilterJsonData.variationsAsProducts ? wpcDisplayIds : tempPostIdsArray
3300 + );
3301 + if(entityPostsCount[entityName] === undefined){
3302 + entityPostsCount[entityName] = [];
3303 + }
3304 + wpcPushAll(entityPostsCount[entityName], tempPostIdsArray);
3305 +
3306 + if (typeof rangeList === 'object') {
3307 + if (hasOnlyOneElement) {
3308 + tempPostIdsArray = setData.allEntities[entityName].items[index].posts;
3309 + tempPostIdsArray = getVariation(tempPostIdsArray, isUsedForVariations);
3310 + }else{
3311 + tempPostIdsArray = setData.allEntities[entityName].items[index].posts;
3312 + const rangeTempFiltered = {...tempFiltered};
3313 +
3314 + delete rangeTempFiltered[entityName];
3315 + for (const [rangeTempEntityName, rangePostIds] of Object.entries(rangeTempFiltered)) {
3316 + const rangeParentSet = new Set(rangePostIds.map(wpcParentOf));
3317 + tempPostIdsArray = tempPostIdsArray.filter(x => rangeParentSet.has(wpcParentOf(x)));
3318 + }
3319 +
3320 + tempPostIdsArray = getVariation(tempPostIdsArray, isUsedForVariations);
3321 + }
3322 +
3323 + for (const [indexRange, range] of Object.entries(rangeList)) {
3324 + const rangeListMinVal = Number(range.range_list_min_val);
3325 + const rangeListMaxVal = Number(range.range_list_max_val);
3326 + let rangePostIds = [];
3327 +
3328 + for (const postId of tempPostIdsArray) {
3329 + const postValues = allEntities[entityName].items[index].meta_values[postId];
3330 + if (postValues !== undefined) {
3331 + for (let postVal of postValues) {
3332 + postVal = Number(postVal);
3333 + // inSetUniverse, not the parent-only set: with used_for_variations
3334 + // the candidate ids (and meta_values keys) are VARIATION ids,
3335 + // which live only in the expanded universe
3336 + if (inSetUniverse(postId) &&
3337 + ((rangeListMaxVal === 0 && rangeListMinVal <= postVal) ||
3338 + (rangeListMinVal <= postVal && rangeListMaxVal >= postVal))) {
3339 + rangePostIds.push(postId);
3340 + }
3341 + }
3342 + }
3343 + }
3344 +
3345 + rangePostIds = getVariation(rangePostIds, isUsedForVariations);
3346 +
3347 + allEntities[entityName].items[index].range_list_input[indexRange] = wpcDisplayCount(rangePostIds);
3348 + }
3349 + }
3350 + }
3351 + }
3352 + }
3353 + }
3354 +
3355 + setData.filteredPostsIds = [];
3356 +
3357 + if (hasTempFilters) {
3358 + for (const postIds of Object.values(tempFiltered)) {
3359 + wpcPushAll(setData.filteredPostsIds, postIds);
3360 + }
3361 + } else {
3362 + for (const [indexEntity, entity] of Object.entries(allEntities)) {
3363 + const { logic, range_list_input: rangeList, used_for_variations: usedForVariations } = entity.filter;
3364 + const isUsedForVariations = usedForVariations === 'yes' || usedForVariations === true;
3365 + for (const [indexItem, item] of Object.entries(entity.items)) {
3366 + let postIds = item.posts;
3367 + wpcPushAll(setData.filteredPostsIds, item.posts);
3368 + postIds = getVariation(postIds, isUsedForVariations);
3369 + // Same pre-normalization display source as the hasTempFilters
3370 + // branch: non-ufv lists get parent-collapsed by getVariation()
3371 + // right above, which under-counts on variations-as-products shops.
3372 + allEntities[indexEntity].items[indexItem].cross_count = wpcDisplayCount(
3373 + wpcFilterJsonData.variationsAsProducts ? item.posts : postIds
3374 + );
3375 +
3376 +
3377 + if(entityPostsCount[indexEntity] === undefined){
3378 + entityPostsCount[indexEntity] = [];
3379 + }
3380 + wpcPushAll(entityPostsCount[indexEntity], postIds);
3381 +
3382 + if (typeof rangeList === 'object') {
3383 +
3384 + postIds = getVariation(postIds, isUsedForVariations);
3385 +
3386 + for (const [indexRange, range] of Object.entries(rangeList)) {
3387 + const rangeListMinVal = Number(range.range_list_min_val);
3388 + const rangeListMaxVal = Number(range.range_list_max_val);
3389 + let rangePostIds = [];
3390 +
3391 + for (const postId of postIds) {
3392 + const postValues = allEntities[indexEntity].items[indexItem].meta_values[postId];
3393 + if (postValues !== undefined) {
3394 + for (let postVal of postValues) {
3395 + postVal = Number(postVal);
3396 + // inSetUniverse — variation ids of ufv filters live only in the
3397 + // expanded universe (see the twin scan above)
3398 + if (inSetUniverse(postId) &&
3399 + ((rangeListMaxVal === 0 && rangeListMinVal <= postVal) ||
3400 + (rangeListMinVal <= postVal && rangeListMaxVal >= postVal))) {
3401 + rangePostIds.push(postId);
3402 + }
3403 + }
3404 + }
3405 + }
3406 +
3407 + rangePostIds = getVariation(rangePostIds, isUsedForVariations);
3408 +
3409 + allEntities[indexEntity].items[indexItem].range_list_input[indexRange] = rangePostIds.length;
3410 + }
3411 + }
3412 + }
3413 + }
3414 + }
3415 +
3416 + setData.countFilteredPostsIds = [];
3417 + let isFiltered = false;
3418 + if (hasTempFilters) {
3419 + for (const [entityName, postsIds] of Object.entries(tempFiltered)) {
3420 +
3421 + // Product-level (parent-space) intersection: every filter is an
3422 + // independent product-level clause in the server query, so a product
3423 + // matches when ANY of its variations satisfies each filter — not
3424 + // necessarily the same variation for all of them. On
3425 + // variations-as-products shops the intersection (and the Apply
3426 + // total derived from it) stays in variation space instead,
3427 + // matching the server's ungated getBetweenFiltersIntersect.
3428 + // Never count posts outside the set universe (see inSetUniverse above)
3429 + const parentIds = new Set(wpcCountSpace(postsIds.filter(inSetUniverse)));
3430 +
3431 + if(setData.countFilteredPostsIds.length === 0 && !isFiltered){
3432 + setData.countFilteredPostsIds = [...parentIds];
3433 + }
3434 +
3435 + setData.countFilteredPostsIds = setData.countFilteredPostsIds.filter(x => parentIds.has(x));
3436 + isFiltered = true;
3437 + }
3438 + }
3439 +
3440 + setData.filteredPostsIds = [...new Set(setData.filteredPostsIds)];
3441 +
3442 + for (const [entityName, postIds] of Object.entries(entityPostsCount)) {
3443 + const uniquePostIds = [...new Set(postIds)];
3444 + entityPostsCount[entityName] = uniquePostIds.some(id => allPostsUniverseSet.has(Number(id)));
3445 + }
3446 + setData.entityPostsCount = entityPostsCount;
3447 +
3448 + updateCountersHtml(allEntities, setId);
3449 + updateRangeInput(allEntities, setId);
3450 + }
3451 +
3452 + const getVariation = (tempPostIdsArray, isUsedForVariations) => {
3453 + if(!wpcIsPro) return tempPostIdsArray;
3454 + let postIdsWithoutVariation = [];
3455 +
3456 + tempPostIdsArray.forEach(function(postId, indexPostId) {
3457 + let isVariation = wpcFilterJsonData.product_variations_map[postId] !== undefined;
3458 + if(!isUsedForVariations && isVariation){
3459 + // Map the variation to its parent product (the Set dedups).
3460 + // Dropping variations here made numeric-range counts lose every
3461 + // variable product: their _price data is represented by
3462 + // variation IDs, while the server counts parent products whose
3463 + // variation price matches the range.
3464 + postIdsWithoutVariation.push(Number(wpcFilterJsonData.product_variations_map[postId]));
3465 + }else{
3466 + postIdsWithoutVariation.push(postId);
3467 + }
3468 + });
3469 +
3470 + return [...new Set(postIdsWithoutVariation)]
3471 + }
3472 +
3473 + // Final DISPLAYED-count space. On shops that list variations as standalone
3474 + // catalog items (XStore's variable_products_detach — the server sets
3475 + // wpcFilterJsonData.variationsAsProducts) the visible item unit IS a
3476 + // variation, so term counters and the Apply total must stay in variation
3477 + // space; everywhere else counts collapse to distinct parent products.
3478 + // Mirrors the server-side wpc_from_variations_to_products counting gate.
3479 + // Space normalization for intersections / universe membership keeps using
3480 + // getVariation() directly and is deliberately NOT affected by the flag.
3481 + const wpcCountSpace = (postIdsArray) =>
3482 + wpcFilterJsonData.variationsAsProducts ? [...new Set(postIdsArray)] : getVariation(postIdsArray, false);
3483 +
3484 +
3485 +
3486 + // The set universe in both spaces: parent products and the variation-expanded
3487 + // one (variable parents replaced by their variations there). meta_values can
3488 + // be keyed by EITHER space — PARENT ids for post dates and product-level
3489 + // values, VARIATION ids for used-for-variations data — so every universe
3490 + // membership check must accept both spaces.
3491 + const wpcUniverse = (setId) => {
3492 + const expandedKeys = Object.keys(wpcFilterJsonData[setId].allPostsIds);
3493 + const allPostsExpandedSet = new Set(expandedKeys.map(Number));
3494 + const allPostsUniverseSet = new Set(getVariation(expandedKeys, false).map(Number));
3495 + const inSetUniverse = (id) => allPostsUniverseSet.has(+id) || allPostsExpandedSet.has(+id);
3496 + return { allPostsExpandedSet, allPostsUniverseSet, inSetUniverse };
3497 + };
3498 +
3499 + // jQuery .data() type-casts attribute values ("0" -> number 0,
3500 + // "false" -> boolean false), while slugs in the inline JSON are always
3501 + // strings — normalize every slug read from .data() before comparing or
3502 + // collecting, or terms named "0"/"false" silently stop matching.
3503 + const wpcTermSlug = (slug) => (slug === undefined ? undefined : String(slug));
3504 +
3505 + // The timepicker addon is enqueued only when a date+time filter exists on
3506 + // the page; date-only pages have just the stock jQuery UI datepicker (with
3507 + // the addon present, datetimepicker get/setDate works on both kinds).
3508 + const wpcPickerFn = () => ($.fn.datetimepicker ? 'datetimepicker' : 'datepicker');
3509 +
3510 + // Terminate a built URL's path exactly like PHP's user_trailingslashit()
3511 + // does for the server-rendered links (wpcFilterJsonData.trailingSlash
3512 + // carries the site convention) — a mismatched slash costs a 301 redirect
3513 + // on every Apply click and breaks the newUrl/applyDataUrl comparisons
3514 + const wpcNormalizeTrailingSlash = (url) => {
3515 + if (typeof wpcFilterJsonData.trailingSlash === 'undefined' || url.pathname === '/') {
3516 + return url;
3517 + }
3518 + if (wpcFilterJsonData.trailingSlash) {
3519 + if (!url.pathname.endsWith('/')) {
3520 + url.pathname += '/';
3521 + }
3522 + } else {
3523 + url.pathname = url.pathname.replace(/\/+$/, '');
3524 + }
3525 + return url;
3526 + };
3527 +
3528 + // Maps a variation id to its parent product id (identity for non-variations).
3529 + // Cross-entity logic must compare PRODUCTS: the server treats every filter as
3530 + // an independent product-level clause, so intersecting raw variation ids would
3531 + // require one variation to satisfy all filters at once and lose products.
3532 + const wpcParentOf = (postId) => {
3533 + if (!wpcIsPro || !wpcFilterJsonData.product_variations_map) return Number(postId);
3534 + const parent = wpcFilterJsonData.product_variations_map[postId];
3535 + return parent !== undefined ? Number(parent) : Number(postId);
3536 + };
3537 +
3538 + // Range bounds must reflect the INTERSECTION of the other selected filters
3539 + // (filteredPostsIds is a UNION across entities). The intersection is taken in
3540 + // parent space and then expanded back to the whole universe families, because
3541 + // the bounds cover ALL price rows of the surviving products — like the server
3542 + const intersectTempFiltered = (jsonData, excludeEname) => {
3543 + let parentInter = null;
3544 + for (const [eName, ids] of Object.entries(jsonData.tempFilteredAllPostsIds)) {
3545 + if (eName === excludeEname) continue;
3546 + const parents = new Set(getVariation(ids, false));
3547 + if (parentInter === null) {
3548 + parentInter = parents;
3549 + } else {
3550 + const prev = parentInter;
3551 + parentInter = new Set();
3552 + for (const p of parents) {
3553 + if (prev.has(p)) {
3554 + parentInter.add(p);
3555 + }
3556 + }
3557 + }
3558 + }
3559 + if (parentInter === null) {
3560 + return Object.values(jsonData.filteredPostsIds);
3561 + }
3562 + // Parent ids first: post dates and product-level meta rows are keyed by
3563 + // them, and the expanded universe below only carries variation ids for
3564 + // variable products. Duplicated simple ids are harmless for min/max.
3565 + const familyIds = [...parentInter];
3566 + for (const key of Object.keys(jsonData.allPostsIds)) {
3567 + const id = Number(key);
3568 + if (parentInter.has(wpcParentOf(id))) {
3569 + familyIds.push(id);
3570 + }
3571 + }
3572 + return familyIds;
3573 + };
3574 +
3575 + const updateRangeInput = (allEntities, setId) => {
3576 + const $filterSetEl = $('.wpc-filter-set-' + setId);
3577 + const $rangeInputs = $('.wpc-filters-range-inputs', $filterSetEl);
3578 + const setData = wpcFilterJsonData[setId];
3579 + const isSearchUrl = wpcIsSearchUrl()
3580 +
3581 + if(isSearchUrl && !setData.allPostsIds.length){
3582 + return;
3583 + }
3584 +
3585 + $rangeInputs.each(function() {
3586 + const $container = $(this);
3587 + const $forms = $('form', $container);
3588 + const $rangeListInputs = $('.wpc-range-list-item:checked', $container);
3589 + const rangeListData = $rangeListInputs.length ? $rangeListInputs.data() : null;
3590 + const rangeListMin = rangeListData ? rangeListData.min : '';
3591 + const rangeListMax = rangeListData ? rangeListData.max : '';
3592 +
3593 + $forms.each(function() {
3594 + const $form = $(this);
3595 +
3596 + // Number range
3597 + const $min = $('.wpc-filters-range-min', $form);
3598 + const $max = $('.wpc-filters-range-max', $form);
3599 +
3600 + if ($min.length && $max.length) {
3601 + const minData = $min.data();
3602 + const maxData = $max.data();
3603 + const min = Number(minData.min);
3604 + const max = Number(maxData.max);
3605 + const minCurVal = Number($min.val());
3606 + const maxCurVal = Number($max.val());
3607 +
3608 + if (minCurVal == min && max == maxCurVal) {
3609 + const $inputs = $('input', $form);
3610 + const jsonData = wpcFilterJsonData[setId];
3611 + const tempFilteredLength = Object.keys(jsonData.tempFilteredAllPostsIds).length;
3612 +
3613 + $inputs.each(function() {
3614 + const $input = $(this);
3615 + const data = $input.data();
3616 + const wpcEname = data.wpcEName;
3617 +
3618 + if (typeof data.min !== "undefined") {
3619 + const hasOnlyOneElement = tempFilteredLength === 1 &&
3620 + typeof jsonData.tempFilteredAllPostsIds[wpcEname] !== 'undefined';
3621 +
3622 + if (rangeListMin !== '') {
3623 + $input.val(rangeListMin);
3624 + } else {
3625 + let dataMin = data.absMin;
3626 + let minVal = data.absMin;
3627 +
3628 + if (!hasOnlyOneElement && jsonData.filteredPostsIds.length) {
3629 + const minValuePostIds = [];
3630 + // items may arrive without the 'min' key (e.g. reindexed to a plain array)
3631 + const minItem = jsonData.allEntities[wpcEname] ? jsonData.allEntities[wpcEname].items['min'] : undefined;
3632 + const metaValues = minItem ? minItem.meta_values : {};
3633 +
3634 + for (const postId of intersectTempFiltered(jsonData, wpcEname)) {
3635 + if (setData.allPostsIds[postId] === undefined) continue;
3636 + const values = metaValues[postId];
3637 + if (typeof values !== 'undefined') {
3638 + for (const val of values) {
3639 + minValuePostIds.push(val);
3640 + }
3641 + }
3642 + }
3643 +
3644 +
3645 + if (minValuePostIds.length) {
3646 + minVal = wpcArrayMin(minValuePostIds);
3647 + dataMin = minVal;
3648 + }
3649 + } else if (hasOnlyOneElement) {
3650 + minVal = $input.val();
3651 + }
3652 +
3653 + $input
3654 + .data('min', dataMin)
3655 + .attr('data-min', dataMin)
3656 + .val(minVal);
3657 + }
3658 + }
3659 +
3660 + if (typeof data.max !== "undefined") {
3661 + const hasOnlyOneElement = tempFilteredLength === 1 &&
3662 + typeof jsonData.tempFilteredAllPostsIds[wpcEname] !== 'undefined';
3663 +
3664 + if (rangeListMax !== '') {
3665 + $input.val(rangeListMax);
3666 + } else {
3667 + let dataMax = data.absMax;
3668 + let maxVal = data.absMax;
3669 +
3670 + if (!hasOnlyOneElement && jsonData.filteredPostsIds.length) {
3671 + const maxValuePostIds = [];
3672 + // items may arrive without the 'max' key (e.g. reindexed to a plain array)
3673 + const maxItem = jsonData.allEntities[wpcEname] ? jsonData.allEntities[wpcEname].items['max'] : undefined;
3674 + const metaValues = maxItem ? maxItem.meta_values : {};
3675 +
3676 + for (const postId of intersectTempFiltered(jsonData, wpcEname)) {
3677 + if (setData.allPostsIds[postId] === undefined) continue;
3678 + const values = metaValues[postId];
3679 + if (typeof values !== 'undefined') {
3680 + for (const val of values) {
3681 + maxValuePostIds.push(val);
3682 + }
3683 + }
3684 + }
3685 +
3686 + if (maxValuePostIds.length) {
3687 + maxVal = wpcArrayMax(maxValuePostIds);
3688 + dataMax = maxVal;
3689 + }else{
3690 + maxVal = dataMax = 0;
3691 + }
3692 + } else if (hasOnlyOneElement) {
3693 + maxVal = $input.val();
3694 + }
3695 +
3696 + $input
3697 + .data('max', dataMax)
3698 + .attr('data-max', dataMax)
3699 + .val(maxVal);
3700 + }
3701 + }
3702 + });
3703 +
3704 + $.fn.wpcInitSlider($form);
3705 + }
3706 + }
3707 +
3708 + // Date range
3709 + const $fromEl = $('.wpc-filters-range-from.hasDatepicker', $container);
3710 + const $toEl = $('.wpc-filters-range-to.hasDatepicker', $container);
3711 +
3712 + if ($fromEl.length && $toEl.length) {
3713 + const fromData = $fromEl.data();
3714 + const toData = $toEl.data();
3715 +
3716 + if (fromData.wpcTempFrom == fromData.wpcAbsFrom && toData.wpcAbsTo == toData.wpcTempTo) {
3717 + const jsonData = wpcFilterJsonData[setId];
3718 + const filteredPostIds = Object.values(jsonData.filteredPostsIds);
3719 + const hasFilteredPosts = filteredPostIds.length > 0;
3720 + const tempFilteredLength = Object.keys(jsonData.tempFilteredAllPostsIds).length;
3721 +
3722 + $('input', $form).each(function() {
3723 + const data = $(this).data();
3724 + const wpcEname = data.wpcEName;
3725 + const hasOnlyOneElement = tempFilteredLength === 1 &&
3726 + wpcEname in jsonData.tempFilteredAllPostsIds;
3727 +
3728 + const shouldFilter = !hasOnlyOneElement && hasFilteredPosts;
3729 + const boundPostIds = shouldFilter ? intersectTempFiltered(jsonData, wpcEname) : filteredPostIds;
3730 +
3731 + const findDateBound = (metaValues, findMin) => {
3732 + let bound = findMin ? Infinity : -Infinity;
3733 + for (let i = 0; i < boundPostIds.length; i++) {
3734 + const val = metaValues[boundPostIds[i]];
3735 + if (val !== undefined) {
3736 + const ts = Date.parse(val);
3737 + if (findMin ? ts < bound : ts > bound) bound = ts;
3738 + }
3739 + }
3740 + return isFinite(bound) ? new Date(bound) : null;
3741 + };
3742 +
3743 + const pickerFn = wpcPickerFn();
3744 +
3745 + if (data.wpcTempFrom !== undefined) {
3746 + let setDate = new Date(data.wpcAbsFromRaw);
3747 + if (shouldFilter) {
3748 + const fromItem = jsonData.allEntities[wpcEname] ? jsonData.allEntities[wpcEname].items['from'] : undefined;
3749 + const metaValues = fromItem ? fromItem.meta_values : {};
3750 + setDate = findDateBound(metaValues, true) ?? setDate;
3751 + }
3752 + $(this)[pickerFn]('setDate', setDate);
3753 + }
3754 +
3755 + if (data.wpcTempTo !== undefined) {
3756 + let setDate = new Date(data.wpcAbsToRaw);
3757 + if (shouldFilter) {
3758 + const toItem = jsonData.allEntities[wpcEname] ? jsonData.allEntities[wpcEname].items['to'] : undefined;
3759 + const metaValues = toItem ? toItem.meta_values : {};
3760 + setDate = findDateBound(metaValues, false) ?? setDate;
3761 + }
3762 + $(this)[pickerFn]('setDate', setDate);
3763 + }
3764 + });
3765 + }
3766 + }
3767 + });
3768 + });
3769 + }
3770 +
3771 + const updateCountersHtml = (allEntities, setId) => {
3772 + const $filterSetEl = $('.wpc-filter-set-' + setId);
3773 + const hideEmpty = (wpcFilterJsonData[setId].settings.hide_empty !== undefined) ? wpcFilterJsonData[setId].settings.hide_empty : false;
3774 + const isHideEmpty = hideEmpty === 'yes';
3775 + const hideEmptyFilter = (wpcFilterJsonData[setId].settings.hide_empty_filter !== undefined) ? wpcFilterJsonData[setId].settings.hide_empty_filter : false;
3776 + const isHideEmptyFilter = hideEmptyFilter === 'yes';
3777 +
3778 + const selectorsCache = {};
3779 +
3780 + Object.entries(allEntities).forEach(([entityName, entityData]) => {
3781 + let rangeList = entityData.filter.range_list_input;
3782 + const entity = entityData.filter.entity;
3783 + const entitySelector = '[data-wpc-e-name="' + entityName + '"]';
3784 + const moreLess = entityData.filter.more_less;
3785 + const isMoreLess = moreLess === 'yes';
3786 +
3787 + Object.entries(entityData.items).forEach(([index, item]) => {
3788 + const itemSlug = item.slug;
3789 + const selectorKey = entityName + '|' + itemSlug;
3790 +
3791 + if (!selectorsCache[selectorKey]) {
3792 + selectorsCache[selectorKey] = {
3793 + $el: $(entitySelector + '[data-wpc-slug="' + itemSlug + '"]', $filterSetEl).not('.wpc-apply-button-chip'),
3794 + $parent: null
3795 + };
3796 + }
3797 +
3798 + const cacheItem = selectorsCache[selectorKey];
3799 + const $el = cacheItem.$el;
3800 +
3801 + if($el.length){
3802 + if (!cacheItem.$parent) {
3803 + cacheItem.$parent = $el.parent();
3804 + }
3805 + const $parent = cacheItem.$parent;
3806 +
3807 + let $counter = $('.wpc-term-count-value', $parent);
3808 + // closest(), NOT parents(): in a hierarchy parents() climbs to
3809 + // ancestor term-items too, so a zero-count child poisoned its
3810 + // parents with the count-0/hidden-0 classes.
3811 + // With "Show counters" disabled the counter spans do not exist,
3812 + // but the empty-term state classes must still be maintained —
3813 + // derive the li from the input itself then. Options and stars
3814 + // keep the counter-based (empty) path: their own branches below
3815 + // manage their state.
3816 + const isGenericInput = !$el.is('option') && !$el.hasClass('flrt-star-input');
3817 + let $parentItem = isGenericInput ? $el.closest('.wpc-term-item') : $counter.closest('.wpc-term-item');
3818 + if($counter.length){
3819 + $counter.text(item.cross_count);
3820 + }
3821 + if($counter.length || isGenericInput){
3822 + if(item.cross_count === 0){
3823 + $parentItem.addClass('wpc-term-count-0')
3824 + } else {
3825 + $parentItem.removeClass('wpc-term-count-0')
3826 + }
3827 + }
3828 +
3829 + if(isHideEmpty){
3830 + if(item.cross_count <= 0){
3831 + $parentItem.addClass('wpc-term-count-hidden-0').removeClass('wpc-has-terms');
3832 + if($el.is(':checked')){
3833 + $parentItem.addClass('wpc-term-count-hidden-checked-0')
3834 + }else{
3835 + $parentItem.removeClass('wpc-term-count-hidden-checked-0')
3836 + }
3837 + } else {
3838 + $parentItem.removeClass('wpc-term-count-hidden-0 wpc-term-count-hidden-checked-0').addClass('wpc-has-terms');
3839 + }
3840 + }else{
3841 + if(item.cross_count <= 0){
3842 + if(!$el.is(':checked')){
3843 + $parentItem.removeClass('wpc-has-terms');
3844 + }
3845 + } else {
3846 + $parentItem.addClass('wpc-has-terms');
3847 + }
3848 + }
3849 +
3850 +
3851 +
3852 + let $starLabel = $('.flrt-star-label', $parent);
3853 + if($starLabel.length && typeof $starLabel.data().wpcTermCount !== 'undefined'){
3854 + $starLabel.attr('data-wpc-term-count', item.cross_count);
3855 + $starLabel.data('wpcTermCount', item.cross_count);
3856 + // Stars have no counter spans, so the generic branch above
3857 + // never toggles the zero-class that drives the disabled look
3858 + const $starItem = $starLabel.closest('.wpc-term-item');
3859 + if(item.cross_count === 0){
3860 + $starItem.addClass('wpc-term-count-0');
3861 + }else{
3862 + $starItem.removeClass('wpc-term-count-0');
3863 + }
3864 + // The visible number next to the stars is written only on
3865 + // hover/click and then frozen via .flrt-change-blocked, so a
3866 + // recount would otherwise leave a stale value there
3867 + if($el.is(':checked')){
3868 + const showTermCount = $starLabel.closest('.flrt-stars-wpc-filter-content').data('showTermCount');
3869 + if(showTermCount){
3870 + $('#flrt-wpc-term-count', $filterSetEl).text(item.cross_count);
3871 + }
3872 + }
3873 + }
3874 +
3875 + if($el.is('option')){
3876 + const isSelect2 = $el.closest('select').hasClass('select2-hidden-accessible');
3877 + const $select = $($el.closest('select'), $filterSetEl);
3878 + const $childOption = $select.find(`.wpc-term-id-${$el.val()}`);
3879 + if (isHideEmpty) {
3880 + if ($childOption.length) {
3881 + if (item.cross_count <= 0) {
3882 + $childOption.addClass('wpc-term-count-hidden-0').removeClass('wpc-has-terms');
3883 + if($el.is(':selected')){
3884 + $parentItem.addClass('wpc-term-count-hidden-checked-0')
3885 + }else{
3886 + $parentItem.removeClass('wpc-term-count-hidden-checked-0')
3887 + }
3888 + } else {
3889 + $childOption.removeClass('wpc-term-count-hidden-0').addClass('wpc-has-terms');
3890 + }
3891 + }
3892 + } else {
3893 + if(item.cross_count <= 0){
3894 + if(!$el.is(':selected')){
3895 + $childOption.removeClass('wpc-has-terms');
3896 + }
3897 + } else {
3898 + $childOption.addClass('wpc-has-terms');
3899 + }
3900 + }
3901 + if(!isSelect2){
3902 + $el[0].textContent = $el[0].textContent.replace(/\(\d+\)/, '(' + item.cross_count + ')');
3903 + }
3904 + if(isSelect2){
3905 + // Non-ASCII term slugs are percent-encoded ("%" breaks Sizzle selectors)
3906 + const $select2El = $('.' + $.escapeSelector(`select2-${entityName}-${itemSlug}`))
3907 + if($select2El.length){
3908 + $select2El.find('.wpc-term-count-value').text(item.cross_count);
3909 + }
3910 + //wpcInitSelect2( 'wpc-filter-set-'+setId );
3911 + }
3912 + }
3913 +
3914 + if(typeof $el.data().count !== 'undefined'){
3915 + $el.attr('data-count', item.cross_count);
3916 + $el.data('count', item.cross_count);
3917 + }
3918 + }
3919 +
3920 + if (typeof rangeList === 'object') {
3921 + Object.entries(entityData.items[index].range_list_input).forEach(([indexRange, count]) => {
3922 + const rangeId = 'wpc-radio-' + entity + '-' + entityName + '-' + indexRange;
3923 + const $rangeEl = $('#' + rangeId + '.wpc-range-list-item', $filterSetEl);
3924 + if ($rangeEl.length) {
3925 + let $counter = $('.wpc-term-count-value', $rangeEl.parent());
3926 + // The li must not depend on the counter span existing —
3927 + // with "Show counters" off the state classes still apply
3928 + let $parentItem = $rangeEl.closest('.wpc-term-item');
3929 + if ($counter.length) {
3930 + $counter.text(count);
3931 + }
3932 + if (count === 0) {
3933 + $parentItem.addClass('wpc-term-count-0')
3934 + } else {
3935 + $parentItem.removeClass('wpc-term-count-0')
3936 + }
3937 + if(isHideEmpty){
3938 + if(count === 0){
3939 + $parentItem.addClass('wpc-term-count-hidden-0')
3940 + if($rangeEl.is(':checked')){
3941 + $parentItem.addClass('wpc-term-count-hidden-checked-0')
3942 + }else{
3943 + $parentItem.removeClass('wpc-term-count-hidden-checked-0')
3944 + }
3945 + } else {
3946 + $parentItem.removeClass('wpc-term-count-hidden-0 wpc-term-count-hidden-checked-0')
3947 + }
3948 + }
3949 + }
3950 + });
3951 + }
3952 + });
3953 +
3954 + const $section = $(`.wpc-filters-section.wpc-filter-${entityName}`, $filterSetEl)
3955 + let $wpcTermsItems = $section.find(`.wpc-filters-ul-list li, .wpc-filters-widget-select option`);
3956 + let $wpcHasTermsItems = $section.find(`.wpc-filters-ul-list li.wpc-has-terms, .wpc-filters-widget-select option.wpc-has-terms`);
3957 + const $wpcHasCheckedTermsItems = $section.find(`.wpc-filters-ul-list input:checked, .wpc-filters-ul-list option:selected`)
3958 + $wpcTermsItems.removeClass('wpc-not-hidden-term');
3959 + // Views without per-term counter spans (e.g. the stars rating) have no
3960 + // li.wpc-has-terms bookkeeping, so a section hidden by hide_empty_filter
3961 + // could never reappear — decide from the fresh recount data as well.
3962 + // Skip the empty-slug pseudo-term: meta entities carry a ''-item for
3963 + // products WITHOUT the field — it never renders as a selectable term,
3964 + // but its cross_count kept whole custom-field sections visible
3965 + const hasAnyTermPosts = Object.values(entityData.items).some(item => (item.slug ?? '') !== '' && (item.cross_count || 0) > 0);
3966 + let wpcHasTerms = $wpcHasTermsItems.length || $wpcHasCheckedTermsItems.length || (hasAnyTermPosts ? 1 : 0)
3967 + const isHideFilterElement = (entity === 'post_meta_num' || entity === 'post_date' || entity === 'tax_numeric' || entity === 'post_meta_num');
3968 +
3969 + if (isHideFilterElement && wpcFilterJsonData[setId].entityPostsCount[entityName] !== undefined) {
3970 + wpcHasTerms = wpcFilterJsonData[setId].entityPostsCount[entityName] === true ? 1 : 0;
3971 + }
3972 +
3973 + if(wpcHasTerms <= 0 && isHideEmptyFilter){
3974 + $section.addClass('wpc-filters-section-0');
3975 + }else{
3976 + $section.removeClass('wpc-filters-section-0');
3977 + }
3978 +
3979 + // The blob can outlive the filter configuration (its file name now
3980 + // fingerprints the configs, but an already-written stale file or a
3981 + // missed invalidation must not blank the whole filter): the
3982 + // server-rendered section class is per-request truth for More/Less
3983 + const isMoreLessSection = isMoreLess || $section.hasClass('wpc-filter-more-less');
3984 + if (isMoreLessSection) {
3985 + if(!isHideEmpty){
3986 + wpcHasTerms = $wpcTermsItems.length;
3987 + }
3988 +
3989 + if (wpcHasTerms <= +wpcMoreLessCount) {
3990 + $section.addClass('wpc-filter-few-terms');
3991 + } else {
3992 + $section.removeClass('wpc-filter-few-terms');
3993 + }
3994 +
3995 + if (isHideEmpty) {
3996 + $wpcTermsItems.filter('li.wpc-has-terms').slice(0, +wpcMoreLessCount).each(function () {
3997 + $(this).addClass('wpc-not-hidden-term');
3998 + });
3999 + } else {
4000 + $wpcTermsItems.filter('li').slice(0, +wpcMoreLessCount).each(function () {
4001 + $(this).addClass('wpc-not-hidden-term');
4002 + });
4003 + }
4004 + }
4005 + });
4006 +
4007 + // The parents' disabled look is gated by :not(.wpc-has-not-empty-children)
4008 + // (see the dynamic CSS in Plugin.php) — recompute the class from the fresh
4009 + // counts, or a hierarchy parent whose children all dropped to 0 keeps the
4010 + // stale server-rendered class and never greys out
4011 + $('li.wpc-has-children', $filterSetEl).each(function () {
4012 + const hasNotEmpty = $(this).children('ul.children')
4013 + .find('li.wpc-term-item:not(.wpc-term-count-0)').length > 0;
4014 + $(this).toggleClass('wpc-has-not-empty-children', hasNotEmpty);
4015 + });
4016 +
4017 + const $otherFilterSetsOnPage = $(wpcWidgetContainer);
4018 + const hasMultipleWidgets = $otherFilterSetsOnPage.length > 1;
4019 +
4020 + if (hasMultipleWidgets) {
4021 + let tempPostIds = [];
4022 + $otherFilterSetsOnPage.each((index, filterSetWidget) => {
4023 + const widgetSetId = $(filterSetWidget).data('set');
4024 + if (wpcFilterJsonData[widgetSetId] && wpcFilterJsonData[widgetSetId].countFilteredPostsIds) {
4025 +
4026 + const currentPostIds = wpcFilterJsonData[widgetSetId].countFilteredPostsIds;
4027 +
4028 + if (index === 0) {
4029 + tempPostIds = new Set(currentPostIds);
4030 +
4031 + } else {
4032 + tempPostIds = new Set(currentPostIds.filter(x => tempPostIds.has(x)));
4033 +
4034 + }
4035 + } else {
4036 + if (index === 0) {
4037 + tempPostIds = new Set();
4038 + } else {
4039 + tempPostIds = new Set();
4040 + }
4041 + }
4042 + wpcFilterJsonData[widgetSetId].countFilteredPostsIds = [...tempPostIds];
4043 + updateApplyButtonData(widgetSetId);
4044 + });
4045 + }else{
4046 + updateApplyButtonData(setId);
4047 + }
4048 +
4049 + function updateApplyButtonData(setId){
4050 + let filteredPostsCount = wpcFilterJsonData[setId].countFilteredPostsIds.length;
4051 + const $applyButton = $('.wpc-filter-set-' + setId + ' .wpc-pc-apply-button');
4052 + const $mobileApplyButton = $('.wpc-filter-set-' + setId + ' .wpc-filters-apply-button');
4053 + if (filteredPostsCount <= 0){
4054 + $applyButton.text('');
4055 + $mobileApplyButton.find('.wpc-filters-found-posts').text(filteredPostsCount);
4056 + wpcEnableStickyButtons(false);
4057 + }else{
4058 + $applyButton.text('(' + filteredPostsCount + ')');
4059 + $mobileApplyButton.find('.wpc-filters-found-posts').text(filteredPostsCount);
4060 + }
4061 +
4062 + if(filteredPostsCount){
4063 + $applyButton.removeClass('wpc-hidden-term-count');
4064 + }else{
4065 + $applyButton.addClass('wpc-hidden-term-count');
4066 + }
4067 +
4068 + let foundKey = null, foundIndex = null;
4069 + let chips = [];
4070 +
4071 + if(wpcFilterJsonData[setId]['chips'] !== undefined){
4072 + Object.assign(chips, wpcFilterJsonData[setId]['chips']);
4073 +
4074 + Object.keys(chips).forEach(key => {
4075 + if (Array.isArray(chips[key])) {
4076 + const idx = chips[key].findIndex(item => {
4077 + return typeof item === 'object' &&
4078 + item !== null &&
4079 + item.entityClass === 'wpc-chip-search';
4080 + });
4081 + if (idx !== -1) {
4082 + foundKey = key;
4083 + foundIndex = idx;
4084 + }
4085 + }
4086 + });
4087 + if (foundKey !== null) {
4088 + delete chips[foundKey];
4089 + }
4090 + }
4091 +
4092 + const isAnyFilter = chips !== undefined && Object.keys(chips).length > 0;
4093 +
4094 + if(!isAnyFilter){
4095 + filteredPostsCount = wpcFilterJsonData[setId].totalAllPostsIds;
4096 + $mobileApplyButton.find('.wpc-filters-found-posts').text(filteredPostsCount);
4097 + }
4098 + }
4099 + }
4100 +
4101 + const changeSearchUrl = (url, $el, setId) => {
4102 + url = new URL(url);
4103 + const $searchInput = $('.wpc-filter-search-form input.wpc-search-field', $el);
4104 + if ($searchInput === undefined) return;
4105 +
4106 +
4107 + const searchSlug = $searchInput.attr('name');
4108 + const searchVal = $searchInput.val();
4109 +
4110 + if (searchVal !== undefined) {
4111 + url.searchParams.delete(searchSlug);
4112 + let newUrl = url.toString();
4113 + $searchInput.closest('.wpc-filter-search-form').find('a.wpc-search-clear-icon').attr('href', newUrl);
4114 + const chips = $('.wpc-filter-chips-' + setId + ' .wpc-chip-search a');
4115 +
4116 + if(chips === undefined || chips.length === 0) return;
4117 +
4118 + chips.each(function() {
4119 + $(this).attr('href', newUrl);
4120 + });
4121 + }
4122 + }
4123 +
4124 + function buildUrlForApplyButton($el, setId) {
4125 + const $inputs = $('input:checked', '.wpc-filters-main-wrap').not('.wpc-range-list-item');
4126 + const $options = $('option:selected', '.wpc-filters-main-wrap').not('.wpc-range-list-item');
4127 + const $rangeForms = $('form.wpc-filter-range-form input', '.wpc-filters-main-wrap').not('.wpc-range-list-item');
4128 + const $dateForms = $('form.wpc-filter-date-range-form-visible input', '.wpc-filters-main-wrap');
4129 + let baseUrl = wpcFilterJsonData.domain;
4130 + const $submitButton = $('.wpc-filters-submit-button');
4131 + const applyUrl = $submitButton.data('wpcApplyUrl');
4132 + // The location permalink WITHOUT current filter segments. data-wpc-apply-url
4133 + // carries the selections-applied URL (no-JS fallback + sticky-state compare),
4134 + // so appending fresh segments to IT duplicated the segments already in the
4135 + // page URL. Fall back to it only when the attribute is absent — a theme
4136 + // may still ship an overridden copy of the apply-button template.
4137 + const applyBaseUrl = $submitButton.data('wpcApplyBaseUrl');
4138 + const applyButtonPage = $submitButton.data('applyButtonPage');
4139 + const applyBase = (applyBaseUrl !== undefined && applyBaseUrl !== '') ? applyBaseUrl : applyUrl;
4140 + if(applyBase !== undefined && applyButtonPage){
4141 + // Alternative Location: filter segments are appended to this base
4142 + // via RELATIVE URL resolution below, which drops the last path
4143 + // segment of a base without a trailing slash ("/shop" -> "/") —
4144 + // the PHP-rendered location permalink is not slash-terminated
4145 + const applyUrlObj = new URL(applyBase, window.location.href);
4146 + if (!applyUrlObj.pathname.endsWith('/')) {
4147 + applyUrlObj.pathname += '/';
4148 + }
4149 + baseUrl = applyUrlObj.href;
4150 + }
4151 +
4152 + let filterSetData = wpcFilterJsonData[setId];
4153 +
4154 + let urlParams = {};
4155 + let urlParamsWithoutSlug = {};
4156 + let urlParamsWithoutPermalinks = {};
4157 +
4158 +
4159 + const processElements = ($elements, setId) => {
4160 + $elements.each(function(index, el) {
4161 + const $currentEl = $(el);
4162 + const data = $currentEl.data();
4163 +
4164 + if (typeof data.wpcEName === "undefined") {
4165 + return;
4166 + }
4167 +
4168 + let entity = filterSetData.allEntities[data.wpcEName]['filter']['entity'];
4169 + if(entity!== undefined && entity === 'taxonomy'){
4170 + if ($currentEl.is(':disabled:checked')) {
4171 + return;
4172 + }
4173 + }
4174 +
4175 + // Permalink maps are keyed by "entity#e_name": e_name alone collides
4176 + // when two filters share it (Sale Price num vs On Sale exists)
4177 + const entityKey = entity + '#' + data.wpcEName;
4178 +
4179 + if(typeof wpcFilterJsonData.wpcFilterEntitiesWithoutSlug[entityKey] !== "undefined"){
4180 +
4181 + let val = $currentEl.val();
4182 +
4183 + if(typeof data.wpcTempFrom !== 'undefined' || typeof data.wpcTempTo !== 'undefined'){
4184 + let wpcTempFrom = (typeof data.wpcTempFrom !== 'undefined');
4185 + let wpcTempTo= (typeof data.wpcTempTo !== 'undefined');
4186 + let wpcAbsFrom= (typeof data.wpcAbsFrom !== 'undefined');
4187 + let wpcAbsTo= (typeof data.wpcAbsTo !== 'undefined');
4188 + let dateTimeStr = false;
4189 +
4190 + if(wpcAbsFrom && data.wpcAbsFrom === val){
4191 + return;
4192 + }
4193 +
4194 + if(wpcTempFrom && wpcAbsFrom && data.wpcTempFrom === data.wpcAbsFrom){
4195 + return;
4196 + }
4197 +
4198 + if(wpcAbsTo && data.wpcAbsTo === val){
4199 + return;
4200 + }
4201 +
4202 +
4203 + if(wpcTempTo && wpcAbsTo && data.wpcTempTo === data.wpcAbsTo){
4204 + return;
4205 + }
4206 +
4207 + if(wpcTempFrom){
4208 + dateTimeStr = $("#wpc-filters-alt-date-from-"+ data.fid)[wpcPickerFn()]('getDate');
4209 + }
4210 +
4211 + if(wpcTempTo){
4212 + dateTimeStr = $("#wpc-filters-alt-date-to-"+ data.fid)[wpcPickerFn()]('getDate');
4213 + }
4214 +
4215 + if(dateTimeStr !== false){
4216 + val = formatDate(dateTimeStr, data.wpcDateType);
4217 + }
4218 + }
4219 +
4220 + if(typeof data.min !== 'undefined' || typeof data.max !== 'undefined'){
4221 + val = Number($currentEl.val());
4222 + }
4223 +
4224 + if(typeof data.absMin !== 'undefined' && data.absMin === val){
4225 + return;
4226 + }
4227 + if(typeof data.min !== 'undefined' && data.min === val){
4228 + return;
4229 + }
4230 + if(typeof data.absMax !== 'undefined' && data.absMax === val){
4231 + return;
4232 + }
4233 + if(typeof data.max !== 'undefined' && data.max === val){
4234 + return;
4235 + }
4236 + const sortKey = Object.keys(wpcFilterJsonData.wpcFilterPermalinksNum)
4237 + .find(k => wpcFilterJsonData.wpcFilterPermalinksNum[k] === entityKey);
4238 +
4239 + if (!sortKey) return;
4240 +
4241 + urlParamsWithoutSlug[sortKey] ??= [];
4242 + const wpcEName = wpcFilterJsonData.wpcFilterPermalinks[entityKey];
4243 + urlParamsWithoutSlug[sortKey][wpcEName] ??= [];
4244 + urlParamsWithoutSlug[sortKey][wpcEName][data.wpcSlug] = val;
4245 + return;
4246 + }
4247 +
4248 + const sortKey = Object.keys(wpcFilterJsonData.wpcFilterPermalinksNum)
4249 + .find(k => wpcFilterJsonData.wpcFilterPermalinksNum[k] === entityKey);
4250 +
4251 + if (!sortKey) return;
4252 +
4253 + urlParams[sortKey] ??= [];
4254 + const wpcEName = wpcFilterJsonData.wpcFilterPermalinks[entityKey];
4255 + urlParams[sortKey][wpcEName] ??= [];
4256 + if (!urlParams[sortKey][wpcEName].includes(wpcTermSlug(data.wpcSlug))) {
4257 + urlParams[sortKey][wpcEName].push(wpcTermSlug(data.wpcSlug));
4258 + }
4259 + });
4260 + };
4261 +
4262 + processElements($inputs, setId);
4263 + processElements($options, setId);
4264 + processElements($rangeForms, setId);
4265 + processElements($dateForms, setId);
4266 +
4267 + const $searchInput = $('.wpc-filter-search-form input.wpc-search-field', $el);
4268 + if($searchInput !== undefined && $searchInput.length){
4269 +
4270 + let sortKey = Object.values(wpcFilterJsonData.wpcFilterPermalinksNum).length + 1;
4271 + const searchSlug = $searchInput.attr('name');
4272 + const searchVal = $searchInput.val();
4273 +
4274 + if (searchVal !== undefined && searchVal !== '') {
4275 + if(wpcFilterJsonData.wpcFilterEntitiesWithoutSlug === undefined){
4276 + wpcFilterJsonData.wpcFilterEntitiesWithoutSlug = {}
4277 + }
4278 + wpcFilterJsonData.wpcFilterEntitiesWithoutSlug.search = 'search';
4279 +
4280 + if (urlParamsWithoutSlug[sortKey]=== undefined) {
4281 + urlParamsWithoutSlug[sortKey] = {};
4282 + }
4283 + if (urlParamsWithoutSlug[sortKey]['search'] === undefined) {
4284 + urlParamsWithoutSlug[sortKey]['search'] = {};
4285 + }
4286 + urlParamsWithoutSlug[sortKey]['search'][searchSlug] = searchVal;
4287 + }
4288 + }
4289 +
4290 + let urlArray = [];
4291 +
4292 + let wpcFilterPermalinksKeys = {};
4293 + for (const [key, value] of Object.entries(wpcFilterJsonData.wpcFilterPermalinks)) {
4294 + wpcFilterPermalinksKeys[value] = key;
4295 + }
4296 +
4297 + if(wpcIsPro) {
4298 + let url = new URL(baseUrl);
4299 + if(permalinksEnabled){
4300 + if (Object.keys(urlParams).length > 0) {
4301 + Object.entries(urlParams).forEach(([key, value]) => {
4302 + Object.entries(value).forEach(([filterName, arr]) => {
4303 + let urlKey = String(wpcFilterPermalinksKeys[filterName] || '').split('#').pop()
4304 + const sorted = arr.sort((a, b) =>
4305 + Object.values(filterSetData['allEntities'][urlKey].items_sort).indexOf(a) - Object.values(filterSetData['allEntities'][urlKey].items_sort).indexOf(b));
4306 + let logic = filterSetData['allEntities'][urlKey]['filter']['logic'];
4307 + let tempUrlString = filterName + "-" + sorted.join('-' + logic + '-');
4308 + urlArray.push(tempUrlString)
4309 + });
4310 + });
4311 + }
4312 + }
4313 + if(!permalinksEnabled){
4314 + if (Object.keys(urlParams).length > 0) {
4315 + if (Object.keys(urlParams).length > 0) {
4316 + Object.entries(urlParams).forEach(([key, value]) => {
4317 + Object.entries(value).forEach(([filterName, arr]) => {
4318 + let urlKey = String(wpcFilterPermalinksKeys[filterName] || '').split('#').pop()
4319 + const sorted = arr.sort((a, b) =>
4320 + Object.values(filterSetData['allEntities'][urlKey].items_sort).indexOf(a) - Object.values(filterSetData['allEntities'][urlKey].items_sort).indexOf(b));
4321 + url.searchParams.set(filterName, sorted.join(';'));
4322 + });
4323 + });
4324 + }
4325 + }
4326 + }
4327 +
4328 + if(permalinksEnabled){
4329 + if (urlArray.length) {
4330 + // Relative resolution against baseUrl (the clean page URL
4331 + // without filter segments, always slash-terminated) appends
4332 + // the filter path: /shop/ + color-blue -> /shop/color-blue
4333 + url = new URL(urlArray.join('/'), baseUrl);
4334 + } else {
4335 + // No path filters selected (e.g. only a numeric range, which
4336 + // travels as a GET param): keep the page base URL as is.
4337 + // new URL("/", baseUrl) wiped the path and produced links
4338 + // like http://site.test/?max_price=84 instead of
4339 + // http://site.test/shop/?max_price=84
4340 + url = new URL(baseUrl);
4341 + }
4342 + }
4343 +
4344 +
4345 + if (Object.keys(urlParamsWithoutSlug).length > 0) {
4346 + Object.entries(urlParamsWithoutSlug).forEach(([key, value]) => {
4347 + Object.entries(value).forEach(([filterName, arr]) => {
4348 + Object.entries(arr).forEach(([item_key, item]) => {
4349 + url.searchParams.set(item_key, item);
4350 + });
4351 + });
4352 + });
4353 + }
4354 + if(url.pathname === '/' && url.search === '' && applyButtonPage) {
4355 + return baseUrl;
4356 + }
4357 + wpcNormalizeTrailingSlash(url);
4358 + changeSearchUrl(url, $el, setId);
4359 + return url.toString();
4360 + }
4361 +
4362 +
4363 + if(!wpcIsPro){
4364 + if (Object.keys(urlParams).length > 0) {
4365 + Object.entries(urlParams).forEach(([key, value]) => {
4366 + Object.entries(value).forEach(([filterName, arr]) => {
4367 + let urlKey = String(wpcFilterPermalinksKeys[filterName] || '').split('#').pop()
4368 + const sorted = arr.sort((a, b) =>
4369 + Object.values(filterSetData['allEntities'][urlKey].items_sort).indexOf(a) - Object.values(filterSetData['allEntities'][urlKey].items_sort).indexOf(b));
4370 + let filterPositon = Number(filterSetData['allEntities'][urlKey]['filter']['menu_order']);
4371 + urlParamsWithoutPermalinks[filterPositon] = {[filterName]: sorted.join(';')}
4372 +
4373 + });
4374 + });
4375 + }
4376 +
4377 + if (Object.keys(urlParamsWithoutSlug).length > 0) {
4378 + Object.entries(urlParamsWithoutSlug).forEach(([key, value]) => {
4379 + Object.entries(value).forEach(([filterName, arr]) => {
4380 + let urlKey = String(wpcFilterPermalinksKeys[filterName] || '').split('#').pop()
4381 + let filterPositon = Number(filterSetData['allEntities'][urlKey]['filter']['menu_order']);
4382 + urlParamsWithoutPermalinks[filterPositon] = arr;
4383 + });
4384 + });
4385 + }
4386 + let url = new URL(baseUrl);
4387 + if (Object.keys(urlParamsWithoutPermalinks).length > 0) {
4388 + Object.values(urlParamsWithoutPermalinks).forEach((item) => {
4389 + Object.entries(item).forEach(([item_key, item]) => {
4390 + url.searchParams.set(item_key, item);
4391 + });
4392 + });
4393 + }
4394 + if(url.pathname === '/' && url.search === '' && applyButtonPage) {
4395 + return baseUrl;
4396 + }
4397 + wpcNormalizeTrailingSlash(url);
4398 + changeSearchUrl(url, $el, setId);
4399 + return url.toString();
4400 + }
4401 + }
4402 +
4403 + function formatDate(value, type) {
4404 + const d = new Date(value);
4405 +
4406 + const pad = n => String(n).padStart(2, '0');
4407 +
4408 + const date = `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`;
4409 + const time = `${pad(d.getHours())}.${pad(d.getMinutes())}.${pad(d.getSeconds())}`;
4410 + const timeColon = `${pad(d.getHours())}.${pad(d.getMinutes())}.${pad(d.getSeconds())}`;
4411 +
4412 + switch (type) {
4413 + case 'date': return date;
4414 + case 'datetime': return `${date}t${time}`;
4415 + case 'time': return timeColon;
4416 + default: return date;
4417 + }
4418 + }
4419 +
4420 + function compareInputWithRangeList(form, applyButtonMode){
4421 + if(!applyButtonMode) return;
4422 + let inputDataMin = $('input.wpc-filters-range-min', form);
4423 +
4424 + if(!inputDataMin.length) return;
4425 +
4426 + let inputDataMax = $('input.wpc-filters-range-max', form);
4427 + let inputMinVal = Number(inputDataMin.val());
4428 + let inputMaxVal = Number(inputDataMax.val());
4429 + let $rangeList = form.parents().find('.wpc-filter-' + inputDataMin.data('wpcEName') + ' .wpc-filters-range-inputs .wpc-filters-radio input');
4430 +
4431 + if (!$rangeList.length) return;
4432 + $rangeList.each(function (i, radioEl){
4433 + let data = $(radioEl).data();
4434 + let elMin = Number(data.min)
4435 + let elMax = Number(data.max)
4436 +
4437 +
4438 + if((elMin !== inputMinVal || elMax !== inputMaxVal)){
4439 + $(radioEl).prop('checked', false).data('wpc-was-checked', false);
4440 + }
4441 +
4442 + if((elMin === inputMinVal && elMax === inputMaxVal)){
4443 + $(radioEl).prop('checked', true).data('wpc-was-checked', true);
4444 + }
4445 + });
4446 + }
4447 +
4448 + function compareWithOtherFilterSets($el, setId){
4449 + let $otherFilterSetsOnPage;
4450 + $otherFilterSetsOnPage = $(wpcWidgetContainer).not(`.wpc-filter-set-${setId}`);
4451 + let changedFilterSets = new Set();
4452 + if ($otherFilterSetsOnPage) {
4453 + $otherFilterSetsOnPage.each((index, filterSetWidget) => {
4454 + const $filterSetWidget = $(filterSetWidget);
4455 + const changedSetId = $filterSetWidget.data('set');
4456 + const $allInputData = $('input, option', $filterSetWidget);
4457 + const $allInputDataThisWidget = $('input, option', `.wpc-filter-set-${setId}`);
4458 +
4459 + $allInputData.each((index, inputEl) => {
4460 + const $inputEl = $(inputEl);
4461 + const data = $inputEl.data();
4462 + const wpcEName = data.wpcEName;
4463 + const wpcSlug = data.wpcSlug;
4464 +
4465 + if(wpcEName !== undefined && wpcSlug !== undefined){
4466 + const $input = $allInputDataThisWidget.filter(function () {
4467 + return $(this).data('wpc-e-name') === data.wpcEName &&
4468 + $(this).data('wpc-slug') === data.wpcSlug
4469 + });
4470 +
4471 + if($input.length){
4472 + const isChecked = $input.is(':checked') || $input.is(':selected')
4473 + const $inputForChange = $filterSetWidget.find($inputEl);
4474 + const isRatingStar = $inputForChange.hasClass('flrt-star-input');
4475 + if ($inputEl.prop('tagName') === 'OPTION') {
4476 + if($input.prop('selected') !== $inputEl.prop('selected')){
4477 + $inputForChange.prop('selected', isChecked);
4478 + }
4479 + }
4480 +
4481 + if ($inputEl.prop('tagName') === 'INPUT') {
4482 + if ($inputEl.attr('type') === 'checkbox' || $inputEl.attr('type') === 'radio') {
4483 + if($input.prop('checked') !== $inputEl.prop('checked')){
4484 + $inputForChange.prop('checked', isChecked);
4485 + }
4486 + }
4487 + if ($inputEl.attr('type') === 'number' || $inputEl.attr('type') === 'text') {
4488 + const isRange = $input.hasClass('wpc-filters-range-min') && $inputEl.hasClass('wpc-filters-range-min') || $input.hasClass('wpc-filters-range-max') && $inputEl.hasClass('wpc-filters-range-max')
4489 + if(isRange){
4490 + if ($input.val() !== $inputEl.val()) {
4491 + $inputForChange.val($input.val());
4492 + if(data.min !== undefined){
4493 + $inputForChange.data('min', $input.data('min')).attr('data-min', $input.data('min'))
4494 + }
4495 + if(data.max !== undefined){
4496 + $inputForChange.data('max', $input.data('max')).attr('data-max', $input.data('max'));
4497 + }
4498 + }
4499 + }
4500 + const isDate = ($input.hasClass('wpc-filters-range-from') && $inputEl.hasClass('wpc-filters-range-from') || $input.hasClass('wpc-filters-range-to') && $inputEl.hasClass('wpc-filters-range-to'))
4501 + && $input.data('wpc-date-type') === data.wpcDateType;
4502 + if(isDate){
4503 + if ($input.val() !== $inputEl.val()) {
4504 + const newDate = $input[wpcPickerFn()]('getDate');
4505 + $inputForChange[wpcPickerFn()]('setDate', newDate);
4506 + }
4507 + }
4508 + }
4509 + }
4510 + changedFilterSets.add(changedSetId)
4511 + }
4512 + }
4513 +
4514 + if(wpcEName !== undefined && data.wpcSlugMin !== undefined && $inputEl.hasClass('wpc-range-list-item')){
4515 + const $input = $allInputDataThisWidget.filter(function () {
4516 + return $(this).data('wpc-slug-min') !== undefined &&
4517 + $(this).prop('id') === $inputEl.prop('id') &&
4518 + $(this).data('wpc-e-name') === data.wpcEName &&
4519 + $(this).data('termId') === data.termId
4520 + });
4521 +
4522 + if($input.length) {
4523 + const isChecked = $input.is(':checked') || $input.is(':selected')
4524 + const $inputForChange = $filterSetWidget.find($inputEl);
4525 + if ($inputEl.prop('tagName') === 'INPUT') {
4526 + if ($inputEl.attr('type') === 'checkbox' || $inputEl.attr('type') === 'radio') {
4527 + if (isChecked) {
4528 + $inputForChange.prop('checked', isChecked).data('wpc-was-checked', true).addClass('wpc-range-list-item-checked');
4529 + }else{
4530 + $inputForChange.prop('checked', isChecked).data('wpc-was-checked', false).removeClass('wpc-range-list-item-checked');
4531 + }
4532 + }
4533 + }
4534 + changedFilterSets.add(changedSetId)
4535 + }
4536 + }
4537 + });
4538 + });
4539 + }
4540 + return changedFilterSets;
4541 + }
4542 +
4543 + function applyJsMode($el, setId){
4544 + updateCounters($el, setId)
4545 + const hasMultipleWidgets = $(wpcWidgetContainer).length > 1;
4546 +
4547 + if (hasMultipleWidgets) {
4548 + const changedFilterSets = compareWithOtherFilterSets($el, setId);
4549 + if (changedFilterSets.size > 0) {
4550 + for (const elementSetId of changedFilterSets) {
4551 + updateCounters($('.wpc-filter-set-' + elementSetId), elementSetId)
4552 + }
4553 + }
4554 + }
4555 +
4556 + const newUrl = buildUrlForApplyButton($el, setId);
4557 + if( wpcUseSelect2 === 'yes' && typeof $.fn.select2 !== 'undefined' ){
4558 + $('.wpc-filters-widget-select, .wpc-orderby-select').each(function() {
4559 + if ($(this).hasClass('select2-hidden-accessible')) {
4560 + $(this).select2('close');
4561 + }
4562 + });
4563 + }
4564 + wpcInitiateAll();
4565 + const resetButton = $('.wpc-sticky-buttons .wpc-filters-reset-button', $el).attr('href');
4566 + const applyDataUrl = $('.wpc-sticky-buttons .wpc-filters-submit-button', $el).data('wpcApplyUrl');
4567 + const applyButtonUrl = $('.wpc-sticky-buttons .wpc-filters-submit-button', $el).attr('href');
4568 + let isSticky = false;
4569 + const isSameUrl = (newUrl + '/' === resetButton || newUrl === resetButton + '/');
4570 + const urlNow = window.location.href;
4571 + if(applyButtonUrl !== newUrl){
4572 + isSticky = true;
4573 + if(hasMultipleWidgets){
4574 + let $otherFilterSetsOnPage;
4575 + $otherFilterSetsOnPage = $(wpcWidgetContainer).not(`.wpc-filter-set-${setId}`);
4576 + if($otherFilterSetsOnPage){
4577 + $otherFilterSetsOnPage.each((index, filterSetWidget) => {
4578 + const $filterSetWidget = $(filterSetWidget);
4579 + $('.wpc-sticky-buttons .wpc-filters-submit-button', $filterSetWidget).attr('href', newUrl)
4580 + $('.wpc-filters-widget-controls-item .wpc-filters-apply-button', $filterSetWidget).attr('href', newUrl)
4581 + });
4582 + }
4583 + }
4584 + $('.wpc-sticky-buttons .wpc-filters-submit-button', $el).attr('href', newUrl)
4585 + $('.wpc-filters-widget-controls-item .wpc-filters-apply-button', $el).attr('href', newUrl)
4586 + }
4587 + if (applyDataUrl === newUrl){
4588 + isSticky = false;
4589 + }
4590 + wpcEnableStickyButtons(isSticky);
4591 + wpcUpdateStickyButtons();
4592 + }
4593 +
4594 + // Mirrors flrt_chips_labels() in PHP: "Labels for Chips" filter option,
4595 + // {value} replaced with the input value, or the value appended.
4596 + function flrtChipLabelFromTemplate(template, value) {
4597 + template = String(template);
4598 + if (template.indexOf('{value}') !== -1) {
4599 + return template.split('{value}').join(value);
4600 + }
4601 + return template + ' ' + value;
4602 + }
4603 +
4604 + function collectChips($currentEl, setId) {
4605 + const data = $currentEl.data();
4606 + const chips = wpcFilterJsonData[setId]['chips'];
4607 +
4608 +
4609 + const sortData = {};
4610 + $('.wpc-filters-section').each((index, el) => {
4611 + const fid = $(el).data('fid');
4612 + if (fid !== undefined) sortData[fid] = index;
4613 + });
4614 +
4615 +
4616 + const pushChip = (slotIndex, chip, isSearch = false) => {
4617 + chips[slotIndex] = chips[slotIndex] || [];
4618 +
4619 + if (!chips[slotIndex].some(c => c.entityClass === chip.entityClass)) {
4620 + chips[slotIndex].push(chip);
4621 + }
4622 +
4623 + if (isSearch) {
4624 + const idx = chips[slotIndex].findIndex(c => c.entityClass === chip.entityClass);
4625 + if (idx !== -1 && idx !== chips[slotIndex].length - 1) {
4626 + chips[slotIndex].push(chips[slotIndex].splice(idx, 1)[0]);
4627 + }
4628 + }
4629 + };
4630 +
4631 +
4632 + const makeChip = (entityClass, chipLabel, isSearch = false) => ({
4633 + entityClass,
4634 + chipLabel,
4635 + wpcEName: (isSearch) ? '' : data.wpcEName,
4636 + wpcSlug: (isSearch) ? '' : data.wpcSlug,
4637 + });
4638 +
4639 + const slotIndex = sortData[data.fid];
4640 +
4641 + if (data.min !== undefined || data.max !== undefined) {
4642 +
4643 + if (slotIndex === undefined) return;
4644 +
4645 + const rangeName = $currentEl
4646 + .closest('.wpc-filters-section-' + data.fid)
4647 + .find('.wpc-filter-title')
4648 + .text();
4649 + const chipLabel = data.wpcChipLabel
4650 + ? flrtChipLabelFromTemplate(data.wpcChipLabel, $currentEl.val())
4651 + : `${data.wpcChipsText} ${rangeName} ${$currentEl.val()}`;
4652 + const entityClass = `wpc-chip-${data.wpcEName}-${data.wpcSlug}`;
4653 + const chip = makeChip(entityClass, chipLabel);
4654 +
4655 + // A chip only for a bound the user actually set: a value equal to
4656 + // the catalog-wide bound (data.absMin/absMax) OR to the current
4657 + // intersection placeholder (data.min/max, refreshed by
4658 + // updateRangeInput) is untouched — the same criterion
4659 + // buildUrlForApplyButton uses, so chips always match the built URL
4660 + const numVal = Number($currentEl.val());
4661 + if (data.absMin !== undefined && numVal !== data.absMin && numVal !== data.min) pushChip(slotIndex, chip);
4662 + if (data.absMax !== undefined && numVal !== data.absMax && numVal !== data.max) pushChip(slotIndex, chip);
4663 +
4664 + } else if (data.wpcTempFrom !== undefined || data.wpcTempTo !== undefined) {
4665 +
4666 + if (slotIndex === undefined) return;
4667 +
4668 + const chipLabel = data.wpcChipLabel
4669 + ? flrtChipLabelFromTemplate(data.wpcChipLabel, $currentEl.val())
4670 + : `${data.wpcChipsText} ${$currentEl.val()}`;
4671 + const entityClass = `wpc-chip-${data.wpcEName}-${data.wpcSlug}`;
4672 + const chip = makeChip(entityClass, chipLabel);
4673 +
4674 + // Untouched date fields keep data-wpc-temp-* equal to the abs bound
4675 + // (updateInputDateData rewrites it only on a real picker pick) —
4676 + // mirror buildUrlForApplyButton so no chip appears for a field the
4677 + // user never opened, even after updateRangeInput narrowed its value
4678 + if (data.wpcAbsFrom !== undefined && $currentEl.val() != data.wpcAbsFrom && data.wpcTempFrom !== data.wpcAbsFrom) pushChip(slotIndex, chip);
4679 + if (data.wpcAbsTo !== undefined && $currentEl.val() != data.wpcAbsTo && data.wpcTempTo !== data.wpcAbsTo) pushChip(slotIndex, chip);
4680 +
4681 + } else {
4682 +
4683 + const $wpcSection = $currentEl.closest(`.wpc-filters-section.wpc-filter-${data.wpcEName}`);
4684 + const fid = $wpcSection.data('fid');
4685 + if (sortData[fid] === undefined) return;
4686 +
4687 + const sectionSlot = sortData[fid];
4688 + const entityClass = `wpc-chip-${data.wpcEName}-${data.termId}`;
4689 +
4690 + let chipLabel = $(`label .wpc-filter-link`, $currentEl.closest(`.wpc-term-id-${data.termId}`)).text();
4691 +
4692 + const $select = $('select', $wpcSection);
4693 + if ($select.length) {
4694 + const isSelect2 = $select.hasClass('select2-hidden-accessible') && wpcUseSelect2 === 'yes';
4695 + const selector = isSelect2 ? 'select.select2-hidden-accessible option:selected' : 'select option:selected';
4696 + chipLabel = $(selector, $wpcSection).data('wpcChip');
4697 + }
4698 +
4699 + chips[sectionSlot] = chips[sectionSlot] || [];
4700 + if (!chips[sectionSlot].some(c => c.entityClass === entityClass)) {
4701 + chips[sectionSlot][data.termId] = makeChip(entityClass, chipLabel);
4702 + }
4703 + }
4704 +
4705 + const searchInput = $('.wpc-filter-set-' + setId + ' form.wpc-filter-search-form input');
4706 + if(searchInput !== undefined){
4707 + let searchVal = searchInput.val();
4708 + if(searchVal !== undefined && searchVal !== ''){
4709 + const chipLabel = wpcSearchChipsText.replace("%s", searchVal);
4710 + const entityClass = `wpc-chip-search`;
4711 + const chip = makeChip(entityClass, chipLabel, true);
4712 + pushChip(Object.values(sortData).length + 1, chip, true);
4713 + }
4714 +
4715 + }
4716 + }
4717 + function updateChipsList(setId) {
4718 + const chips = wpcFilterJsonData[setId]['chips'];
4719 + flrtClearChips(setId);
4720 +
4721 + const entries = Object.entries(chips);
4722 + if (entries.length === 0) return;
4723 +
4724 +
4725 + const $container = $('.wpc-filter-chips-' + setId);
4726 + $container.find('li').remove();
4727 + $container.removeClass('wpc-filter-chips-empty');
4728 +
4729 + const fragment = [chipsResetButtonTemplate()];
4730 +
4731 + entries.forEach(([, chipGroup]) => {
4732 + Object.values(chipGroup).forEach(chip => {
4733 + fragment.push(
4734 + chip.wpcEName === 'product_visibility'
4735 + ? chipsStarsTemplate(chip, setId)
4736 + : chipsTemplate(chip)
4737 + );
4738 + });
4739 + });
4740 +
4741 + $container.append(fragment.join(''));
4742 + }
4743 +
4744 + function chipsResetButtonTemplate(){
4745 + // Client-rebuilt chips describe the PENDING selection and never navigate —
4746 + // render them as spans so no crawlable link appears from client state
4747 + return `<li class="wpc-filter-chip wpc-chip-reset-all"><span class="wpc-apply-button-chip wpc-apply-button-chips-reset" title=""><span class="wpc-chip-content"><span class="wpc-filter-chip-name">${chipsReset}</span><span class="wpc-chip-remove-icon">×</span></span></span></li>`;
4748 + }
4749 +
4750 + function resetButtonInApplyButtonMode($this, e){
4751 +
4752 + const $el = $this.closest(wpcWidgetContainer);
4753 + const setId = $el.data('set');
4754 +
4755 +
4756 + if (!setId || !wpcApplyButtonSets.length || !wpcApplyButtonSets.includes(setId)) return;
4757 +
4758 + e.preventDefault();
4759 + $('html, body').css("cursor", "wait");
4760 + wpcShowSpinner();
4761 + setTimeout(function() {
4762 + $el.find('.wpc-filters-widget-select').each(function () {
4763 + const sel = this;
4764 + const opts = sel.options;
4765 + if (opts.length && sel.selectedIndex !== -1) {
4766 + sel.value = opts[0].value;
4767 + }
4768 + });
4769 +
4770 + $('input:checked', $el).each(function () {
4771 + this.checked = false;
4772 + $(this).data('wpc-was-checked', false);
4773 + });
4774 +
4775 + $('input[type=text], input[type=number]', $el).each(function () {
4776 + resetInputElement($(this));
4777 + });
4778 +
4779 + applyJsMode($el, setId);
4780 +
4781 + delete wpcFilterJsonData[setId]['chips'];
4782 + delete wpcFilterJsonData[setId]['tempFilteredAllPostsIds'];
4783 + flrtClearChips(setId);
4784 + wpcHideSpinner();
4785 + $('html, body').css("cursor", "auto");
4786 + }, 0);
4787 + }
4788 +
4789 + function flrt_rating_star()
4790 + {
4791 + return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 25">
4792 + <polygon class="cls-1" points="19.89 24.5 12.48 19.8 5.06 24.48 7.03 15.62 0.5 9.64 9.12 8.87 12.51 0.5 15.88 8.88 24.5 9.68 17.96 15.63 19.89 24.5"/>
4793 + </svg>`;
4794 + }
4795 +
4796 + function chipsTemplate(chip){
4797 + let title = wpcSprintf(
4798 + chipsTitle, chip.chipLabel
4799 + )
4800 + return `<li class="wpc-filter-chip ${chip.entityClass}"><span data-wpc-e-name="${chip.wpcEName}" data-wpc-slug="${chip.wpcSlug}" class="wpc-apply-button-chip" title="${title}"><span class="wpc-chip-content"><span class="wpc-filter-chip-name">${chip.chipLabel} </span><span class="wpc-chip-remove-icon">×</span></span></span></li>`;
4801 + }
4802 +
4803 + function chipsStarsTemplate(chip, setId) {
4804 + let title = wpcSprintf(
4805 + chipsTitle, chip.chipLabel
4806 + )
4807 + let $starsNumber = $(`input[data-wpc-e-name="${chip.wpcEName}"][data-wpc-slug="${chip.wpcSlug}"]:checked`, $(`.wpc-filter-set-${setId}`));
4808 + if(!$starsNumber.length){
4809 + $starsNumber = $(`option[data-wpc-e-name="${chip.wpcEName}"][data-wpc-slug="${chip.wpcSlug}"]:selected`, $(`.wpc-filter-set-${setId}`));
4810 + }
4811 +
4812 +
4813 + let ratingNum = $starsNumber.data('ratingNum');
4814 + let tempHtml = '';
4815 + for (let i = 1; i <= ratingNum; i++) {
4816 + tempHtml += `<span>${flrt_rating_star()}</span>`;
4817 + }
4818 + return `<li class="wpc-filter-chip ${chip.entityClass}"><span data-wpc-e-name="${chip.wpcEName}" data-wpc-slug="${chip.wpcSlug}" class="wpc-apply-button-chip" title="${title}"><span class="wpc-chip-content"><span class="wpc-chip-stars">${tempHtml} </span><span class="wpc-chip-remove-icon">×</span></span></span></li>`;
4819 +
4820 + }
4821 +
4822 + function flrtClearChips(setId){
4823 + $('.wpc-filter-chips-'+setId + ' li').remove().addClass('wpc-empty-chips-container');
4824 + }
4825 +
4826 + function wpcSprintf(format, ...args) {
4827 + let index = 0;
4828 + return format.replace(/%(\.\d+)?([sdf%])/g, (match, precision, type) => {
4829 + if (type === '%') return '%';
4830 + const arg = args[index++];
4831 + switch (type) {
4832 + case 's': return String(arg ?? '');
4833 + case 'd': return parseInt(arg ?? 0, 10);
4834 + case 'f': return precision
4835 + ? parseFloat(arg ?? 0).toFixed(parseInt(precision.slice(1)))
4836 + : parseFloat(arg ?? 0).toString();
4837 + default: return match;
4838 + }
4839 + });
4840 + }
4841 +
4842 + function unsetChip($chipElement){
4843 + const wpcEName = $chipElement.data('wpcEName');
4844 + const wpcSlug = $chipElement.data('wpcSlug');
4845 + const setId = $chipElement.closest('.wpc-filter-chips-list').data('set')
4846 + const $el = $(`.widget_wpc_filters_widget .wpc-filter-set-${setId}`);
4847 + if(typeof wpcEName !== 'undefined' && typeof wpcSlug !== 'undefined'){
4848 + const $inputElement = $el.find('[data-wpc-e-name="' + wpcEName + '"][data-wpc-slug="' + wpcSlug + '"]').not('.wpc-apply-button-chip');
4849 + if($inputElement.length > 0){
4850 + resetInputElement($inputElement)
4851 + applyJsMode($el, setId)
4852 + }
4853 + }
4854 + }
4855 + function resetInputElement($inputElement) {
4856 + const inputData = $inputElement.data();
4857 + const type = $inputElement.prop('type');
4858 + const isOption = ($inputElement.prop('tagName') === 'OPTION');
4859 +
4860 + const { absMin, absMax, wpcAbsFromRaw, wpcAbsToRaw, wpcAbsFrom, wpcAbsTo } = inputData;
4861 +
4862 + if (absMin !== undefined) {
4863 + $inputElement.val(absMin).data('min', absMin).change();
4864 + } else if (absMax !== undefined) {
4865 + $inputElement.val(absMax).data('max', absMax).change();
4866 + } else if (wpcAbsFromRaw !== undefined) {
4867 + $inputElement.val(wpcAbsFrom)[wpcPickerFn()]('setDate', new Date(wpcAbsFromRaw));
4868 + } else if (wpcAbsToRaw !== undefined) {
4869 + $inputElement.val(wpcAbsTo)[wpcPickerFn()]('setDate', new Date(wpcAbsToRaw));
4870 + } else if (type === 'checkbox' || type === 'radio') {
4871 + // Clear BOTH the attribute and jQuery data: updateCounters selects
4872 + // radios by the [data-wpc-was-checked=true] ATTRIBUTE, and .data()
4873 + // alone does not update it — the term would stay "selected"
4874 + $inputElement.prop('checked', false)
4875 + .attr('data-wpc-was-checked', false)
4876 + .data('wpc-was-checked', false);
4877 + // The old guard compared type to 'react-radio', which never occurs —
4878 + // dead code — and the fill classes span the WHOLE star group, not one li
4879 + if ($inputElement.hasClass('flrt-star-input')) {
4880 + $inputElement
4881 + .closest('.flrt-stars-wpc-filter-content')
4882 + .find('label.flrt-star-label')
4883 + .removeClass('flrt-star-label-hover flrt-star-label-checked flrt-star-label-not-checked')
4884 + .data('wpc-was-checked', false);
4885 + }
4886 + }else if (isOption) {
4887 + const select = $inputElement.closest('select');
4888 + const defaultValue = select.find('.wpc-dropdown-default').val();
4889 + select.val(defaultValue).trigger('change')
4890 + $inputElement.prop('selected', false);
4891 +
4892 + }
4893 + }
4894 +
4895 + // Merges the inline page part (filteredAllPostsIds, domain, permalinks…)
4896 + // into a blob: per-set keys are object-assigned, root keys replaced
4897 + function wpcMergePagePart(target, pagePart) {
4898 + for (const k in pagePart) {
4899 + if (k === 'blobUrl') continue;
4900 + if (target[k] && typeof target[k] === 'object' && !Array.isArray(target[k]) &&
4901 + pagePart[k] && typeof pagePart[k] === 'object' && !Array.isArray(pagePart[k])) {
4902 + Object.assign(target[k], pagePart[k]);
4903 + } else {
4904 + target[k] = pagePart[k];
4905 + }
4906 + }
4907 + return target;
4908 + }
4909 +
4910 + function updateWpcFilterJsonData(responseHtml) {
4911 + const parser = new DOMParser();
4912 + const doc = parser.parseFromString(responseHtml, 'text/html');
4913 +
4914 + // Inert JSON block (Plugin::inlineScriptJsonData): JSON.parse is
4915 + // orders of magnitude faster than eval'ing a JS object literal
4916 + const jsonEl = doc.querySelector('#wpc-filter-json-data');
4917 + if (jsonEl) {
4918 + try {
4919 + const pagePart = JSON.parse(jsonEl.textContent);
4920 +
4921 + if (pagePart.blobUrl) {
4922 + if (pagePart.blobUrl === window.wpcFilterJsonBlobUrl && typeof window.wpcFilterJsonData !== 'undefined') {
4923 + // Same blob — refresh only the page-scoped part
4924 + wpcMergePagePart(window.wpcFilterJsonData, pagePart);
4925 + } else {
4926 + window.wpcFilterJsonBlobUrl = pagePart.blobUrl;
4927 + delete window.wpcFilterJsonData;
4928 + window.wpcFilterJsonDataPromise = fetch(pagePart.blobUrl, { credentials: 'same-origin' })
4929 + .then((r) => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
4930 + .then((blob) => {
4931 + window.wpcFilterJsonData = wpcMergePagePart(blob, pagePart);
4932 + return blob;
4933 + })
4934 + .catch((e) => { console.error('Filter Everything: filter data fetch failed', e); });
4935 + }
4936 + } else {
4937 + window.wpcFilterJsonData = pagePart;
4938 + }
4939 + } catch (e) {
4940 + console.error('Filter Everything: filter data JSON parse failed', e);
4941 + }
4942 + return;
4943 + }
4944 +
4945 + // Legacy responses carry the data as an executable literal
4946 + const scriptEl = doc.querySelector('#wpc-filter-everything-js-before');
4947 +
4948 + if (scriptEl) {
4949 + let code = scriptEl.textContent;
4950 + (0, eval)(code);
4951 + }
4952 + }
4953 +
4954 + function wpcIsSearchUrl(){
4955 + const url = new URL(window.location.href);
4956 + return url.searchParams.has('srch');
4957 + }
4958 +
4959 + return {
4960 + applyJsMode: function ($el, setId) { wpcWhenDataReady(function () { applyJsMode($el, setId); }); },
4961 + compareInputWithRangeList: function (form, applyButtonMode) { wpcWhenDataReady(function () { compareInputWithRangeList(form, applyButtonMode); }); },
4962 + unsetChip: function ($chipElement) { wpcWhenDataReady(function () { unsetChip($chipElement); }); },
4963 + updateWpcFilterJsonData: updateWpcFilterJsonData
4964 + };
4965 +
4966 + })(); // end of the Apply-button recount engine (wpcApplyEngine)
1322 4967
1323 4968 })(jQuery);