Files
kupshop/web/common/static/wpj/wpj.focus.js
2025-08-02 16:30:27 +02:00

288 lines
8.9 KiB
JavaScript

const toggleBackgroundElements = (hide) => {
$('main, header, footer, [data-aria-hide-on-modal]').attr('aria-hidden', hide ? 'true' : 'false');
};
const trapFocus = (modal) => {
const focusableElements = modal.find('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])').get();
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
modal.on('keydown', function (e) {
if (e.key === 'Tab') {
if (e.shiftKey) {
// Shift+Tab
if (document.activeElement === firstElement) {
e.preventDefault();
lastElement.focus();
}
} else {
// Tab
if (document.activeElement === lastElement) {
e.preventDefault();
firstElement.focus();
}
}
}
});
};
wpj.focus = {
focusName: ADMIN ? "focusAdmin" : "focus",
create: (options) => {
let focusName = ADMIN ? "focusAdmin" : "focus";
let $wpjFocuses = wpj.focus.getFocuses();
const template = $(wpj.focus.getFocusLoadingTemplate());
$wpjFocuses.append(template);
const $focus = template[focusName](options);
$focus[focusName]('load');
return $focus[focusName]('instance');
},
createDeduplicated: (endpoint, ajaxSubmit = false, removeOnClose = false, focusCustomClass = null, focusTriggerEventName = null) => {
// zaridim, ze existuje wrapper, ve kterem budu focusy vytvaret
let $wpjFocuses = wpj.focus.getFocuses();
let focusName = ADMIN ? "focusAdmin" : "focus";
// pokud jsem focus uz jednou vytvoril, tak otevru ten uz vytvoreny
const $existingFocus = $wpjFocuses.find('.focus[data-focus-endpoint="'+ endpoint +'"]');
if ($existingFocus.length) {
$existingFocus[focusName]('show');
return;
}
// pridam novy focus s vychozi loading sablonou
$wpjFocuses.append(
wpj.focus.getFocusLoadingTemplate(endpoint)
);
// inicializuju focus a zavolam na nem load, aby se naloadoval
const $focus = $wpjFocuses.find('.focus[data-focus-endpoint="'+ endpoint +'"]')[focusName]({
opened: true,
onDemandEndpoint: endpoint,
ajaxSubmit: ajaxSubmit,
removeOnClose: removeOnClose,
focusCustomClass: focusCustomClass,
focusTriggerEventName: focusTriggerEventName
});
$focus[focusName]('load');
},
getFocusLoadingTemplate: (endpoint) => {
if (endpoint){
return '<div data-focus-endpoint="' + endpoint + '" class="focus"><div class="focus-dialog"><div class="focus-content"><div class="focus-loading"></div></div></div></div>';
}
return '<div class="focus"><div class="focus-dialog"><div class="focus-content"><div class="focus-loading"></div></div></div></div>';
},
getFocuses: () => {
let $wpjFocuses = $('[data-wpj-focuses]');
if (!$wpjFocuses.length) {
// vytvorim si wrapper
$('body').append('<div data-wpj-focuses style="z-index: 999"></div>');
$wpjFocuses = $('[data-wpj-focuses]');
}
return $wpjFocuses;
}
};
$.widget("wpj." + wpj.focus.focusName, {
options: {
opened: false,
closeOnBgClick: true,
addBodyPadding: false,
onDemandEndpoint: null,
ajaxSubmit: false,
removeOnClose: false,
show: null,
hide: null,
},
_create: function() {
var me = this;
this.element.on('click', '[data-focus=close]', function() {
return me.hide();
});
this._super();
if (this.options.opened) {
this.show(true);
}
},
toggle: function(show) {
if (typeof show === 'undefined') {
show = !this.element.is('.active');
}
return show ? this.show() : this.hide();
},
show: function(immediate) {
this.element.addClass('active').attr('tabindex', '-1').focus();
const focusableElements = this.element.find('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])');
var me = this,
$body = $('body');
if (this.options.addBodyPadding && wpj.domUtils.getScrollbarWidth()) {
this.element.add('body').css('padding-right', wpj.domUtils.getScrollbarWidth());
}
$body
.on('mousedown.focus', function(e) {
// pac-item => results in googlemaps input autocomplete
if (me.element.has(e.target).length || !me.options.closeOnBgClick ||
$(e.target).hasClass('pac-item') || $(e.target).hasClass('pac-item-query') || $(e.target).parents('.pac-item').length) {
return;
}
me.hide();
})
.on('keydown.focus', function(e) {
if (e.key !== 'Escape') {
return true;
}
me.hide();
return false;
})
.removeClass('focus-closed')
.addClass('focus-opened');
toggleBackgroundElements(true);
trapFocus(this.element);
setTimeout(() => {
const firstFocusable = focusableElements.length > 0 ? focusableElements.get(0) : this.element.get(0);
if (firstFocusable && typeof firstFocusable.focus === 'function')
firstFocusable.focus();
}, 50);
if (!immediate) {
$body.addClass('focus-transition');
}
if (this.options.ajaxSubmit) {
this.registerAjaxSubmit();
}
this._trigger( "show" );
return false;
},
hide: function() {
this.element.removeClass('active');
$('body')
.off('mousedown.focus')
.off('keydown.focus')
.removeClass('focus-opened')
.addClass('focus-closed');
if (this.options.addBodyPadding && wpj.domUtils.getScrollbarWidth()) {
this.element.add('body').css('padding-right', 0);
}
this.element.trigger('focusClosed');
toggleBackgroundElements(false);
const lastFocused = this.element.data('last-focused');
if (lastFocused && typeof lastFocused.focus === 'function') {
lastFocused.focus();
} else {
document.body.focus();
}
if (this.options.ajaxSubmit) {
this.element.off('submit', 'form');
}
if (this.options.removeOnClose) {
this.element.remove();
}
this._trigger( "hide" );
return false;
},
load: function () {
if (!this.options.onDemandEndpoint) {
return;
}
const me = this;
if (this.options.focusCustomClass) {
me.element.addClass(this.options.focusCustomClass);//('focus-delivery-widgets');
}
// Handle callback
if (typeof this.options.onDemandEndpoint === "function") {
this.options.onDemandEndpoint(me.element.find('.focus-content')[0]);
return;
}
// Handle global function
if (typeof window[this.options.onDemandEndpoint] === "function") {
window[this.options.onDemandEndpoint](me.element.find('.focus-content')[0]);
return;
}
// nacteni ondemand focusu
fetch(this.options.onDemandEndpoint)
.then((response) => response.text())
.then((response) => {
const focusClasses = [...$(response).prop('classList'), ...['active']];
// replacnu obsah focusu naloadovanym obsahem
me.element.html($(response).html());
// nastavim focusu classy podle nacteneho focusu
me.element.attr('class', focusClasses.join(' '))
if (me.options.focusTriggerEventName) {
$('body').trigger(me.options.focusTriggerEventName, me);
}
});
},
registerAjaxSubmit: function () {
const me = this;
this.element.on('submit', 'form', function (e) {
e.preventDefault();
const $this = $(e.target);
const data = $this.serialize();
const url = $this.attr('action');
wpj.domUtils.reloadPartsFromUrl(url, me.element.find('[data-reload]'), data);
});
this.element.on('click', '[data-ondemand]', function(e) {
const $this = $(e.target);
const url = $this.attr('href');
wpj.domUtils.reloadPartsFromUrl(url, me.element.find('[data-reload]'), undefined, () => {});
return false;
});
}
});
$(document).on('click', '[data-wpj-focus]', function () {
const endpoint = $(this).data('wpj-focus');
const ajaxSubmit = $(this).data('wpj-focus-ajax');
const removeOnClose = $(this).data('wpj-focus-remove-on-close');
const focusCustomClass = $(this).data('wpj-focus-custom-class');
const focusTriggerEventName = $(this).data('wpj-focus-trigger-event-name');
if (endpoint) {
wpj.focus.createDeduplicated(endpoint, !!ajaxSubmit, removeOnClose, focusCustomClass, focusTriggerEventName);
}
return false;
});
// Pro nacteni fallback focusu pri nacteni stranky (nikotino - vyber prodejny)
if (MODULES.COMPONENTS) {
window.dispatchEvent(new Event("wpjFocusLoaded"));
}