: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
((api, topThemify,topWindowDoc,topWindow,topBody,topBodyCl,_CLICK_,Registry,ThemifyConstructor) => {
static #storageKey= api.isFrontend? 'themify_builder_lightbox_frontend_pos_size' : 'themify_builder_lightbox_backend_pos_size';
static #isStandalone= false;
topBody.appendChild(doc.tfId('tmpl-builder_lightbox').content);
this.el = topWindowDoc.tfId('tb_lightbox_parent');
api.Forms._initValidators();
Themify.on('tb_opened_lightbox', () => {
el.tfOn('keydown paste copy', e=>{//to prevent copy/paste or undomanager
.tfClass('tb_close_lightbox')[0].tfOn(_CLICK_, () => {
el.tfClass('builder_cancel_docked_mode')[0].tfOn(_CLICK_, e => {
api.MainPanel.updateStorage();
if (_this.#isStandalone === false) {
_this.setupLightboxSizeClass();
if (!themifyBuilder.disableShortcuts) {
const shortcutListener = e => {
const active = topWindowDoc.activeElement;
if (active.tagName !== 'INPUT' && active.tagName !== 'TEXTAREA' && !topWindowDoc.fullscreenElement && !active.isContentEditable) {
if ((e.key === 'Escape' || e.code==='Escape')&& (api.activeModel || api.LiteLightBox.isOpen())) {
if(api.LiteLightBox.isOpen()){
api.LiteLightBox.close();
else if ('KeyS' === e.code && (true === e.ctrlKey || true === e.metaKey)) {
// Ctrl + s | Cmd + s - Save Builder
api.LiteLightBox.close();
api.Builder.get().save();
doc.tfOn('keydown', shortcutListener);
topWindowDoc.tfOn('keydown', shortcutListener);
static open(options, model) {
return new Promise(resolve => {
const callback = async response => {
lightboxContainer = el.querySelector('#tb_lightbox_container'),
action = el.tfClass('tb_lightbox_actions_wrap')[0],
save = createElement('button',{class:'builder_button builder_save_button',title:i18n.ctr_save,type:'button'},i18n.done),
activeModel=api.activeModel;
el.classList.add('tf_hide');
el.classList.add('tb_lb_'+activeModel.get('mod_name'));
await activeModel.beforeOpenLightbox?.(response);
if (typeof response === 'string') {
lightboxContainer.innerHTML = response;
lightboxContainer.replaceChildren(response);
action.replaceChildren();
if (options.save !== false) {
el.classList.add('tb_lightbox_small');
if (api.GS.activeGS !== null) {
api.liveStylingInstance = api.createStyleInstance();
api.liveStylingInstance.init(null, true);
if (!api.liveStylingInstance) {
api.liveStylingInstance = api.createStyleInstance();
api.liveStylingInstance.init();
const _saveClicked = e => {
e.stopImmediatePropagation();
if (api.isGSPage !== true) {
save.tfOff(e.type, _saveClicked, {passive: true});
topSave.tfOff(e.type, _saveClicked, {passive: true});
topSave = createElement('a',{class:'tb_tooltip',href:'javascript:;'}),
li = createElement('li', 'tb_top_save_btn');
topSave.append(api.Helper.getIcon('ti-check'), createElement('span','',i18n.done));
el.tfClass('tb_options_tab')[0].appendChild(li);
save.tfOn(_CLICK_, _saveClicked, {passive: true});
topSave.tfOn(_CLICK_, _saveClicked, {passive: true});
api.undoManager.start('saveLightbox', activeModel);
api.restoreVals=api.Helper.cloneObject(activeModel.get('mod_settings'));
action.appendChild(save);
if ('html' === options.loadMethod && options.contructor !== true) {
const tabs = lightboxContainer.querySelectorAll('.tb_tab_nav a');
for (let i = tabs.length - 1; i > -1; --i) {
tabs[i].tfOn(_CLICK_, ThemifyConstructor.switchTabs);
await activeModel?.openLightbox?.(lightboxContainer);
el.classList.remove('tf_hide');
lightboxContainer.style.scrollBehavior = 'auto';
lightboxContainer.scrollTop = 0;
lightboxContainer.style.scrollBehavior = '';
Themify.trigger('tb_opened_lightbox');
api.Spinner.showLoader('spinhide');
api.ActionBar.clearClicked();
if (options.loadMethod === 'html') {
if (options.contructor === true) {
callback(ThemifyConstructor.run(options.data));
callback(ThemifyConstructor.run(options));
if (!el.classList.contains('tf_hide') && api.isGSPage !== true) {
Themify.trigger('themify_builder_lightbox_before_close', el);
el.classList.add('tf_hide');
this._cleanLightBoxContent();
api.undoManager.enable();
api.Utils.removeViewPortClass(el);
Themify.trigger('themify_builder_lightbox_close', el);
topThemify.trigger('themify_builder_lightbox_close', el);
el.classList.remove('tb_lb_'+model.get('mod_name'));
api.liveStylingInstance.clear();
if (model.is_new === true) {
else if (!model.is_saved && api.undoManager.has('saveLightbox')) {
api.undoManager.clear('saveLightbox');
model.el?.classList.remove('tb_current_module','tb_outline_anim');
api.activeModel = api.restoreVals=null;
static setStandAlone(left, top) {
Themify.on('tb_opened_lightbox', () => {
_this.#isStandalone = true;
topBodyCl.add('tb_standalone_lightbox');
st.width = st.height = '';
const box = api.ToolBar.el.getBoundingClientRect(),
computed = getComputedStyle(_this.el),
w = parseInt(computed.width),
h = parseInt(computed.height),
topW = topWindow.innerWidth - 10,
topH = topWindow.innerHeight + 10;
} else if ((top + h) > topH) {
} else if ((left + w) > topW) {
st.transform = 'translate(' + left + 'px,' + top + 'px)';
_this.setupLightboxSizeClass(w);
Themify.on('themify_builder_lightbox_close', lb => {
topBodyCl.remove('tb_standalone_lightbox');
lb.style.transform = lb.style.width = lb.style.height = '';
_this.#isStandalone = false;
_this.setupLightboxSizeClass(_this._getStorage().w);
static _cleanLightBoxContent() {
const items = this.el.querySelectorAll('#tb_lightbox_container,.tb_options_tab,.tb_lightbox_actions_wrap,.tb_action_breadcrumb');
for (let i = items.length - 1; i > -1; --i) {
items[i].replaceChildren();
const model = api.activeModel;
if (!this.el.classList.contains('tf_hide')) {
if (api.isGSPage !== true && !api.Forms.isValidate(this.el.querySelector('#tb_options_setting'))) {
ThemifyConstructor.setStylingValues(api.activeBreakPoint);//save current breakpoint style tab
let oldSettings = model.get('mod_settings'),
settings = {...api.Helper.cloneObject(ThemifyConstructor.values),...api.Forms.serialize('tb_options_setting', true)};
if (model.type !== 'column') {
settings={...settings,...api.Forms.serialize('tb_options_animation', true),...api.Forms.serialize('tb_options_visibility', true)};
model.el.classList.toggle('tb_visibility_hidden', settings.visibility_all === 'hide_all' || settings.visibility_desktop === 'hide' || settings.visibility_tablet === 'hide' || settings.visibility_tablet_landscape === 'hide' || settings.visibility_mobile === 'hide');
if (model.type === 'module') {
api.Builder.get().removeLayoutButton();
if (api.ActionBar?.id === model.id) {
api.Base.builderSave(settings, 'empty');
api.Base.builderSave(oldSettings, 'empty');
await model.saveLightbox(settings, oldSettings );
const hasChange = api.Helper.compareObject(oldSettings, settings);
if (hasChange === true) {
await Themify.trigger('themify_builder_save_component', [settings, oldSettings]);
await topThemify.trigger('themify_builder_save_component', [settings, oldSettings]);
model.set('mod_settings', settings);
if (api.isGSPage !== true && hasChange === true) {
api.undoManager.end('saveLightbox');
api.undoManager.clear('saveLightbox');
model.is_new=model.is_saved=null;
else if (topBodyCl.contains('tb_standalone_lightbox')) {
await Themify.trigger('tb_save_lb');
if (api.isGSPage === true) {
await TF_Notification.showHide('done', themifyBuilder.globalStyleData.save_text, 2000);
resizeHandler = self.el.tfClass('tb_resizable');
for (let i = resizeHandler.length - 1; i > -1; --i) {
resizeHandler[i].tfOn('pointerdown', function (e) {
e.stopImmediatePropagation();
let owner = this.ownerDocument,
el.style.willChange = 'transform,width,height';
maxHeight = owner.documentElement.clientHeight * .9,
minHeight = parseInt(getComputedStyle(el).getPropertyValue('min-height')),
axis = this.dataset.axis,
startH = ~~el.offsetHeight,
startW = ~~el.offsetWidth,
{clientX:resizeX,clientY:resizeY} = e,
owner.body.classList.add('tb_start_animate');
e.stopImmediatePropagation();
timer = requestAnimationFrame(() => {
const {clientX,clientY} = e,
matrix = new DOMMatrix(getComputedStyle(el).transform);
w = resizeX + startW - clientX;
if (w >= minWidth && w <= maxWidth) {
matrix.m41 += parseInt(el.style.width) - w;
el.style.width = w + 'px';
self.setupLightboxSizeClass(w);
const h = axis === '-y' || axis === 'ne' || axis === 'nw' ? (resizeY + startH - clientY) : (startH + clientY - resizeY);
w = axis === 'sw' || axis === 'nw' ? (resizeX + startW - clientX) : (startW + clientX - resizeX);
if ((axis === 'se' || axis === 'x' || axis === 'sw' || axis === 'nw' || axis === 'ne') && w >= minWidth && w <= maxWidth) {
if (axis === 'sw' || axis === 'nw') {
matrix.m41 += parseInt(el.style.width) - w;
el.style.width = w + 'px';
self.setupLightboxSizeClass(w);
if ((axis === 'se' || axis === 'y' || axis === '-y' || axis === 'sw' || axis === 'nw' || axis === 'ne') && h >= minHeight && h <= maxHeight) {
if (axis === '-y' || axis === 'nw' || axis === 'ne') {
matrix.m42 += parseInt(el.style.height) - h;
el.style.height = h + 'px';
el.style.transform = 'translate(' + matrix.m41 + 'px,' + matrix.m42 + 'px)';
Themify.trigger('tb_resize_lightbox');
e.stopImmediatePropagation();
cancelAnimationFrame(timer);
this.tfOff('pointermove', _start, {passive: true, once: true})
.tfOff('pointermove', _resize, {passive: true})
.tfOff('lostpointercapture pointerup', _stop, {passive: true, once: true});
el.style.willChange = '';
owner.body.classList.remove('tb_start_animate');
owner = el = timer = null;
this.tfOn('lostpointercapture pointerup', _stop, {
.tfOn('pointermove', _start, {
.tfOn('pointermove', _resize, {
.setPointerCapture(e.pointerId);
dragHandler = self.el.querySelectorAll('.tb_lightbox_top_bar,.tb_action_breadcrumb');
for (let i = dragHandler.length - 1; i > -1; --i) {
dragHandler[i].tfOn('pointerdown', function (e) {
const targetCl = e.target.classList;
if (!targetCl.contains('tb_lightbox_top_bar') && !targetCl.contains('tb_action_breadcrumb')) {
e.stopImmediatePropagation();
owner = this.ownerDocument,
box = el.getBoundingClientRect(),
dragX = box.left - e.clientX,
dragY = box.top - e.clientY,
//topW=topWindow.innerWidth - 10,
draggableCallback = e => {
e.stopImmediatePropagation();
timer = requestAnimationFrame(() => {
let clientX = dragX + e.clientX,
clientY = dragY + e.clientY;
else if((clientX+width)>topW){
el.style.transform = 'translate(' + clientX + 'px,' + clientY + 'px)';
Themify.trigger('tb_panel_drag', [clientX, width]);
e.stopImmediatePropagation();
owner.body.classList.add('tb_start_animate', 'tb_drag_lightbox');
api.ToolBar.el.classList.add('tb_start_animate', 'tb_drag_lightbox');
api.MainPanel.el.classList.add('tb_start_animate', 'tb_drag_lightbox');
Themify.trigger('tb_panel_drag_start');
if (self.#isStandalone === false) {
self.setupLightboxSizeClass();
e.stopImmediatePropagation();
cancelAnimationFrame(timer);
this.tfOff('pointermove', startDrag, {passive: true, once: true})
.tfOff('pointermove', draggableCallback, {passive: true})
.tfOff('lostpointercapture pointerup', up, {passive: true, once: true});
el.style.willChange = '';
Themify.trigger('tb_panel_drag_end');
if (self.#isStandalone === false) {
self.setupLightboxSizeClass();
owner.body.classList.remove('tb_start_animate', 'tb_drag_lightbox');
api.ToolBar.el.classList.remove('tb_start_animate', 'tb_drag_lightbox');
api.MainPanel.el.classList.remove('tb_start_animate', 'tb_drag_lightbox');
timer = el = owner = dragX=width=dragY=null;
el.style.willChange = 'transform';
this.tfOn('lostpointercapture pointerup', up, {passive: true, once: true})
.tfOn('pointermove', startDrag, {passive: true, once: true})
.tfOn('pointermove', draggableCallback, {passive: true})
.setPointerCapture(e.pointerId);
static _responsiveTabs() {
ul = this.el.querySelector('.tb_styling_tab_nav ul');
const li = ul.lastElementChild;
tabsWidth = li.offsetLeft + li.offsetWidth;