: 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
tfminicolors.parentNode.removeChild(tfminicolors);
that._controlChange(input, opacity, data);
}, {once: true, passive: true});
input.tfOn('focusin', () => {
}, {once: true, passive: true});
opacity.tfOn('focusin', function () {
if (!input.dataset.tfminicolorsInitialized) {
input.dataset.opacity = this.value;
$(input).tfminicolors('show');
if (!v && data.default) {
this.setColor(input, span, opacity, v);
if (data.after !== undefined) {
f.appendChild(self.after(data));
if (data.description !== undefined) {
f.appendChild(self.description(data.description));
if (data.tooltip !== undefined) {
f.appendChild(self.hint(data.tooltip));
if (data.label === undefined) {
else if (data.label === false) {
const prefix = data.prefix || '_tooltip',
group = data.group ?? true;
control: false, /* disable live preview refresh */
empty : { hide : [ prefix + '_bg', prefix + '_c', prefix + '_w' ] },
not_empty : { show : [ prefix + '_bg', prefix + '_c', prefix + '_w' ] }
wrap_class : 'tb_disable_dc'
let f = self.create( options );
f = this._bindEvents(f, data);
/* setup live preview events */
prefix = data.prefix ? data.prefix : '_tooltip',
_tooltip = el.querySelector('#' + prefix),
color_fields = [el.querySelector('#' + prefix + '_bg'), el.querySelector('#' + prefix + '_c')],
events = ['focus', 'keyup', 'blur', 'change'],
tooltip_w = el.querySelector('#' + prefix + '_w');
tooltip_w.tfOn(events, e => {
self._addOrRemoveTooltip(e.type !== 'blur', data);
_tooltip.tfOn(events, e => {
self._addOrRemoveTooltip(e.type !== 'blur', data);
for (let i = color_fields.length - 1; i > -1; --i) {
color_fields[i].tfOn('themify_builder_color_picker_show', function () {
self._addOrRemoveTooltip(true, data);
this.tfOn('themify_builder_color_picker_hide', () => {
self._addOrRemoveTooltip(false, data);
}, {once: true, passive: true});
.tfOn('themify_builder_color_picker_change', () => {
self._addOrRemoveTooltip(true, data);
/* creates tooltip preview element */
_addOrRemoveTooltip(show, data) {
let el = api.liveStylingInstance.el,
/* change element to display the tooltip inside */
el = el.querySelector( data.scope );
tooltip = el.querySelector( ':scope > .tf_tooltip' );
Themify.loadCss('tooltip');
const self = ThemifyConstructor,
prefix = data.prefix || '_tooltip',
val = self.getEl(prefix).value; /* Tooltip Text field */
tooltip = createElement('','tf_tooltip');
let width = self.getEl( prefix + '_w' ).value;
tooltip.classList.add('tf_abs_c');
tooltip.style.background = api.Helper.getColor( self.getEl( prefix + '_bg' ) );
tooltip.style.color = api.Helper.getColor( self.getEl( prefix + '_c' ) );
width += self.getEl( prefix + '_w_unit' ).value;
tooltip.style.width = width;
tooltip.classList.remove('tf_hide');
const item = self.getEl(id);
const f = createDocumentFragment(),
input = createElement('input'),
v = self.getStyleVal(data.id) ?? data.default;
input.type = data.input_type || 'text'; // custom input types
if (self.is_repeat === true) {
input.className = self.is_sort === true ? 'tb_lb_sort_child' : 'tb_lb_option_child';
input.dataset.inputId = data.id;
input.className = 'tb_lb_option';
if (data.placeholder !== undefined) {
input.placeholder = data.placeholder;
if (data.custom_args !== undefined) {
for (let i in data.custom_args) {
input.setAttribute(i, data.custom_args[i]);
if (data.class !== undefined) {
input.className += ' ' + data.class;
f.appendChild(self._initControl(input, data));
if (data.unit !== undefined) {
f.appendChild(self.select.render(data.unit, self));
if (data.after !== undefined) {
f.appendChild(self.after(data));
if (data.description !== undefined) {
f.appendChild(self.description(data.description));
if (data.tooltip !== undefined) {
f.appendChild(self.hint(data.tooltip));
data.input_type = 'number';
if (data.custom_args === undefined) {
data.custom_args = {min: data.min || 0};
if (data.max !== undefined) {
data.custom_args.max = data.max;
if (data.step !== undefined) {
data.custom_args.step = data.step;
return self.text.render(data, self);
self.range.update(id, v, self);
data.input_type = 'number';
data.custom_args = {min: 0, max: 360};
const wrap = createElement('',{class:'tb_angle_container tf_rel',tabindex:-1}),
css_class = 'tb_lb_option tb_angle_input';
data.class = data.class !== undefined ? data.class + ' ' + css_class : css_class;
wrap.appendChild(self.range.render(data, self));
const angle = wrap.querySelector('#' + data.id),
event = data.event|| (self.clicked === 'styling' ? 'keyup' : 'change');
angle.tfOn('pointerdown', function (e) {
e.stopImmediatePropagation();
let _circle = this.parentNode.tfClass('tb_angle_circle')[0];
const tmp1 = createElement('','tb_angle_dot'),
tmp2 = createElement('','tb_angle_circle_wrapper');
_circle = createElement('','tb_angle_circle');
tmp1.style.transform = 'rotate(' + v + 'deg)';
_circle.appendChild(tmp1);
tmp2.appendChild(_circle);
this.parentNode.appendChild(tmp2);
_circle.tfOn(e.type, function (e) {
let box = this.getBoundingClientRect(),
center_x = (this.offsetWidth / 2) + box.left,
center_y = (this.offsetHeight / 2) + box.top,
_dot = this.parentNode.tfClass('tb_angle_dot')[0];
const PI = 180 / Math.PI,
e.stopImmediatePropagation();
topBodyCl.add('tb_start_animate');
e.stopImmediatePropagation();
timer = requestAnimationFrame(() => {
let delta_y = center_y - e.clientY,
delta_x = center_x - e.clientX,
ang = Math.atan2(delta_y, delta_x) * PI; // Calculate Angle between circle center and mouse pos
ang += 360; // Always show angle positive
_dot.style.transform = 'rotate(' + ang + 'deg)';
Themify.triggerEvent(angle, event);
cancelAnimationFrame(timer);
this.tfOff('pointermove', _start, {passive: true, once: true})
.tfOff('pointermove', _move, {passive: true})
.tfOff('lostpointercapture pointerup', _up, {passive: true, once: true});
topBodyCl.remove('tb_start_animate');
requestAnimationFrame(() => {
_dot = center_x = timer = center_y = null;
this.tfOn('lostpointercapture pointerup', _up, {passive: true, once: true})
.tfOn('pointermove', _start, {passive: true, once: true})
.tfOn('pointermove', _move, {passive: true})
.setPointerCapture(e.pointerId);
}, {passive: true, once: true});
const d = self.text.render(data, self);
if (data.dataset === undefined) {
input = d.querySelector('input',{autocomplete:'off'}),
container = createElement('','tb_autocomplete_container');
d.appendChild(container);
input.tfOn('input', async function () {
// remove all elements in container
const wrapper = this.nextElementSibling,
wrapper.replaceChildren();
let resp = _this._cache.get(k);
const parent = this.parentNode;
controller = new AbortController();
parent.classList.add('tb_autocomplete_loading', 'tf_loader');
resp = await api.LocalFetch({action: 'tb_get_ajax_data', mode: 'autocomplete', dataset: type, value: value}, false, {signal: controller.signal});
resp = resp.success ? resp.data : '';
_this._cache.set(k, resp);
parent.classList.remove('tb_autocomplete_loading', 'tf_loader');
const d = createDocumentFragment();
d.appendChild(createElement('',{class:'tb_autocomplete_item','data-value':i},resp[i]));
wrapper.classList.add('tf_scrollbar');
container.tfOn('pointerdown', function (e) {
if (e.button === 0 && e.target.classList.contains('tb_autocomplete_item')) {
const field = this.previousElementSibling;
field.value = e.target.dataset.value;
Themify.triggerEvent(field, 'change');
browse(uploader, input, self, type) {
uploader.tfOn(_CLICK_, e => {
if (this._frames[type] !== undefined) {
file_frame = this._frames[type];
file_frame = wp.media.frames.file_frame = wp.media({
title: input.dataset.title || i18n['upload_' + type] || i18n.upload_image,
type: type === 'json' ? 'text/plain,application/json' : type
this._frames[type] = file_frame;
file_frame.off('select').on('select', () => {
api.ActionBar.disable = true;
const attachment = file_frame.state().get('selection').first().toJSON();
input.value = attachment.url;
Themify.triggerEvent(input, 'change');
$(input).trigger('change');
const attach_id = input.getRootNode().querySelector('#' + input.id + '_id');
attach_id.value = attachment.id;
file_frame.on('close', () => {
api.ActionBar.disable = true;
api.ActionBar.disable = null;
// Finally, open the modal
file_frame.content.mode('browse');
input.tfOn('change', e => {
this.setImage(uploader, e.currentTarget.value.trim());
placeholder = new Image(w, h);
placeholder.decoding = 'async';
placeholder.src = 'https://placehold.co/' + w + 'x' + h + '.png';
placeholder.replaceWith(img);
prev.appendChild(placeholder);
const item = self.getEl(id);
this.setImage(item.parentNode.tfClass('thumb_preview')[0], v);
render(type, data, self) {
const wr = createElement('','tb_uploader_wrapper tf_rel'),
input = createElement('input',{class:'tb_uploader_input',type:'text',required:'required',pattern: /.*\S.*/.source,autocomplete:'off'}),
upload_btn = createElement('a',{class:'tb_media_uploader tb_upload_btn thumb_preview tf_plus_icon tf_rel',href:'#',title:i18n.browse_image,'data-library-type':type === 'json' ? 'text/plain,application/json' : type}),
btn_delete =createElement('span','tb_clear_input tf_close'),
v = self.getStyleVal(data.id);
input.dataset.title = data.title;
if (self.is_repeat === true) {
input.className += self.is_sort === true ? ' tb_lb_sort_child' : ' tb_lb_option_child';
id = 'tb_' + Math.random().toString(36).substr(2, 7);
input.dataset.inputId = data.id;
input.className += ' tb_lb_option';
btn_delete.tfOn(_CLICK_, e => {
Themify.triggerEvent(input, 'change');
wr.append(self._initControl(input, data), btn_delete, upload_btn);
this.setImage(upload_btn, v);
this.browse(upload_btn, input, self, type);
if (data.after !== undefined) {
wr.appendChild(self.after(data));
if (data.description !== undefined) {
wr.appendChild(self.description(data.description));
if (data.tooltip !== undefined) {