: 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,bodyCl,_CLICK_)=> {
$.ThemifyGradient = function (element, options) {
gradient: $.ThemifyGradient.default,
type: 'linear', // [linear / radial]
this.initSwatchesFlag = false;
this.__constructor = function () {
this.settings = {...defaults, ...options};
this.updateSettings = function (options) {
this.settings = {...defaults, ...options};
this.update = function () {
this.getCSSvalue = function () {
defDir = this.settings.type === 'radial'?(this.settings.circle ? 'circle,' : ''):this.settings.angle + 'deg,';
for (let i = 0; i < points.length; ++i) {
defCss.push(points[i][1] + ' ' + points[i][0]);
return this.settings.type + '-gradient(' + defDir + defCss.join(', ') + ')';
this.getString = function () {
for (let i = 0; i < points.length; ++i) {
out += points[i][0] + ' ' + points[i][1] + '|';
return out.substr(0, out.length - 1);
this.setType = function (type) {
this.settings.type = type;
this.settings.onChange(this.getString(), this.getCSSvalue());
this.setAngle = function (angle) {
this.settings.angle = angle;
this.settings.onChange(this.getString(), this.getCSSvalue());
this.setRadialCircle = function (circle) {
this.settings.circle = circle;
this.settings.onChange(this.getString(), this.getCSSvalue());
this._setupPoints = function () {
if (Array.isArray(this.settings.gradient)) {
points = this.settings.gradient;
points = this._getGradientFromString(this.settings.gradient);
this._setup = function () {
fragment = createDocumentFragment(),
_container = createElement('',{class: 'themifyGradient tf_rel',tabindex:-1}),
pointsInfos = createElement('','gradient-pointer-info'),
oldGradient=element.tfClass('themifyGradient')[0];
$btnPointDelete = createElement('a',{href:'#',class:'gradient-point-delete tf_close'});
$pointColor = createElement('', 'point-color');
$pointPosition = createElement('input',{type:'text',class:'point-position'});
$pointsContainer = createElement('', 'points');
$pointsInfosContent = createElement('','content');
let _canvas = createElement('canvas',{width:self.settings.width,height:self.settings.height});
tmpDiv1.style.backgroundColor='#00ff00';
$pointColor.appendChild(tmpDiv1);
$pointsInfosContent.append($pointColor,createElement('span','gradient_delimiter'),$pointPosition,createElement('span','gradient_percent','%'),$btnPointDelete);
pointsInfos.append(createElement('','gradient-pointer-arrow'),$pointsInfosContent);
fragment.append($pointsContainer,_canvas,pointsInfos);
_container.appendChild(fragment);
element.prepend(_container);
if(!element.tfClass('tb_gradient_swatches')[0]){
element.appendChild(this.swatchesHTML());
$pointsInfosContent = $($pointsInfosContent);
$pointColor = $($pointColor);
$pointPosition = $($pointPosition);
$btnPointDelete = $($btnPointDelete);
$pointsContainer = $($pointsContainer);
_context = _canvas.getContext('2d');
_canvas.off('click').on('click', function (e) {
const offset = $(this).offset();
let defaultColor = 'rgba(0,0,0, 1)',
clickPosition = e.pageX - offset.left;
clickPosition = Math.round((clickPosition * 100) / self.settings.width);
for (let i = 0; i < points.length; ++i) {
points[i][0] = parseInt(points[i][0]);
if ((points[i][0] < clickPosition) && (clickPosition - points[i][0] < minDist)) {
minDist = clickPosition - points[i][0];
defaultColor = points[i][1];
else if ((points[i][0] > clickPosition) && (points[i][0] - clickPosition < minDist)) {
minDist = points[i][0] - clickPosition;
defaultColor = points[i][1];
points.push([clickPosition + '%', defaultColor]);
points.sort(self._sortByPosition);
for (let i = 0; i < points.length; ++i) {
if (points[i][0] === clickPosition + '%') {
self._selectPoint($pointsContainer.find('.point:eq(' + i + ')')[0]);
setTimeout(self._colorPickerPosition, 315);
this.pointEvents = function () {
if(_this.classList.contains('point-position')){
let v = parseInt(_this.value.trim());
if (e.type !== 'focusout') {
v = Math.round((v * this.settings.width) / 100);
$(_this).closest('.themifyGradient').find('.themify_current_point').css('left', v);
$pointsInfosContent[0].tfOn('focusout keyup',listener,{passive: false});
$pointsContainer[0].tfOn('keyup',e=>{
if (e.code === 'Delete' && doc.activeElement.tagName !== 'INPUT') {
if(e.target.classList.contains('point')){
this._selectPoint(e.target);
if (api.isFrontend) {//fix drag/drop window focus
this._colorPickerPosition();
.tfOn('pointerdown',function(e){
if(e.button === 0 && e.target.classList.contains('point')){
e.stopImmediatePropagation();
p.classList.add('tb_gradient_drag_point');
bodyCl.add('tb_start_animate','tb_move_drag','tb_gradient_drag');
marginLeft=parseFloat(getComputedStyle(p).getPropertyValue('margin-left')) || 0,
dragX=p.offsetLeft-e.clientX,
e.stopImmediatePropagation();
timer=requestAnimationFrame(()=>{
let clientX=dragX+e.clientX-marginLeft;
p.style.left=clientX+'px';
self._selectPoint(p, true);
cancelAnimationFrame(timer);
e.stopImmediatePropagation();
this.tfOff('pointermove', _startDrag,{passive: true,once:true})
.tfOff('pointermove', _move, {passive: true})
.tfOff('lostpointercapture pointerup',_up, {passive: true,once:true});
bodyCl.remove('tb_start_animate','tb_move_drag','tb_gradient_drag');
this.classList.remove('tb_gradient_drag_point');
p.tfOn('lostpointercapture pointerup',_up, {passive: true,once:true})
.tfOn('pointermove', _startDrag,{passive: true,once:true})
.tfOn('pointermove', _move, {passive: true})
.setPointerCapture(e.pointerId);
this._render = function () {
this._initGradientPoints();
this._colorPickerPosition = function () {
const lightbox = $(api.LightBox.el),
p = $pointsInfosContent.find('.tfminicolors'),
el = p.find('.tfminicolors-panel');
if ((lightbox.offset().left + lightbox.width()) <= el.offset().left + el.width()) {
p.addClass('tb_minicolors_right');
p.removeClass('tb_minicolors_right');
this._initGradientPoints = function () {
const fragment = createDocumentFragment();
for (let i = 0, len = points.length; i < len; ++i) {
let p=createElement('', 'point');
p.style.backgroundColor = points[i][1];
p.style.left = ((parseInt(points[i][0]) * this.settings.width) / 100)+'px';
$pointsContainer[0].replaceChildren(fragment);
this.hexToRgb = function (hex) {
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
hex = hex.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i, (_m, r, g, b)=>{
return r + r + g + g + b + b;
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
this._selectPoint = function (el, is_drag) {
let left = parseInt(el.style.left);
$pointPosition.val(Math.round((left / this.settings.width) * 100));
if (left < 0 && bodyCl.contains('tb_module_panel_docked')) {
$pointsInfosContent[0].parentNode.style.marginLeft=left + 'px';
_selPoint.addClass('themify_current_point').siblings().removeClass('themify_current_point');
let bgColor = _selPoint.css('backgroundColor');
$element.find('.point-color .tfminicolors').remove();
// create the color picker element
let $input = $pointColor.find('.themify-color-picker');
if ($input.length === 0) {
$input = $('<input type="text" class="themify-color-picker" />');
$input.appendTo($pointColor).tfminicolors({
let rgb = self.hexToRgb(value);
rgb = {r: 255, g: 255, b: 255};
_selPoint.css('backgroundColor', 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + opacity + ')');
$element.find('.tfminicolors').first().addClass('tfminicolors-focus');
$btnPointDelete.off('click').on('click', this.removePoint.bind(this));
let rgb = bgColor.replace(/^rgba?\(|\s+|\)$/g, '').split(','),
opacity = rgb.length === 4 ? rgb.pop() : 1; // opacity is the last item in the array
rgb = this._rgbToHex(rgb);
// set the color for colorpicker
$input.val(rgb).attr('data-opacity', opacity).data('opacity', opacity).tfminicolors('settings', {value: rgb});
this._renderCanvas = function () {
const items = $pointsContainer[0].tfClass('point');
for (let i = 0; i < items.length; ++i) {
let position = Math.round((parseInt(items[i].style.left) / this.settings.width) * 100);
points.push([position + '%', items[i].style.backgroundColor]);
points.sort(this._sortByPosition);
this.settings.onChange(this.getString(), this.getCSSvalue());
this._renderToCanvas = function () {
const gradient = _context.createLinearGradient(0, 0, this.settings.width, 0);
for (let i = 0; i < points.length; ++i) {
gradient.addColorStop(parseInt(points[i][0]) / 100, points[i][1]);
_context.clearRect(0, 0, this.settings.width, this.settings.height);
_context.fillStyle = gradient;
_context.fillRect(0, 0, this.settings.width, this.settings.height);
this._getGradientFromString = function (gradient) {
points = gradient.split('|');
for (let i = 0, len = points.length; i < len; ++i) {
sub = el.substr(index - 3, index);
if (sub === '100' || sub === '100%') {
position = parseInt(el.substr(index - 2, index));
position = parseInt(el.substr(index - 1, index));
arr.push([position, el.replace(position, '')]);
this._rgbToHex = function (rgb) {
const R = rgb[0], G = rgb[1], B = rgb[2],
n = Math.max(0, Math.min(n, 255));
return '0123456789ABCDEF'.charAt((n - n % 16) / 16) + '0123456789ABCDEF'.charAt(n % 16);
return '#' + toHex(R) + toHex(G) + toHex(B);
this._sortByPosition = function (data_A, data_B) {
data_A = parseInt(data_A[0]);
data_B = parseInt(data_B[0]);
return data_A < data_B ? -1 : (data_A > data_B ? 1 : 0);
this.removePoint = function(e){
points.splice(_selPoint.index(), 1);
const p=$pointsInfosContent[0].parentNode;
this.swatchesHTML = function(){
const fr = createDocumentFragment(),
dropdownIcon = createElement('',{class:'tf_cm_dropdown_icon',tabindex:-1}),
swatchesContainer = createElement('ul','tb_gradient_swatches tf_scrollbar tf_w'),
dropdown = themifyColorManager.makeImportExportDropdown(),
addBtn = createElement('button',{class: 'tb_gradient_add_swatch tf_plus_icon',type:'button'});
addBtn.tfOn(_CLICK_,this.saveSwatch.bind(this))
.appendChild(createElement('span','themify_tooltip',i18n.save_gradient));
dropdown.tfOn(_CLICK_,e=>{
this.swatchesDropdownClicked(e);
dropdownIcon.append(createElement('span','themify_tooltip',i18n.ie_gradient),api.Helper.getIcon('ti-import'),dropdown);
swatchesContainer.tfOn(_CLICK_,e=>{
fr.append(addBtn,dropdownIcon,swatchesContainer);
this.swatchesDropdownClicked = function ( e ) {
classList = target.classList;
if(classList.contains('tb_cm_export')){
target.parentNode.parentNode.parentNode.blur();
themifyColorManager.exportColors( 'gradients' );
else if(classList.contains('tb_cm_import')){
target.parentNode.parentNode.parentNode.blur();
themifyColorManager.importColors('gradients');
this.saveSwatch = function () {
if('' === this.getString() || '' === this.getCSSvalue())return false;
const swatches = Object.keys(themifyCM.gradients),
css = this.getCSSvalue();
for(let i = swatches.length-1; i>-1; --i) {
if ( themifyCM.gradients[swatches[i]].css === css ){
const id = themifyColorManager.UID(),
setting : JSON.parse(JSON.stringify(this.settings)),
gradient : this.getString(),
themifyCM.gradients[id] = swatch;
themifyColorManager.updateColorSwatches('gradients');
this.addSwatch = function ( swatch, init ) {
const sw = createElement('li',{class:'tb_gradient_swatch','data-id':swatch.id});
sw.style.background = swatch.css;
sw.appendChild(createElement('span','tf_delete_swatch tf_close'));
const container = element.parentElement.tfClass('tb_gradient_swatches')[0];
container.insertBefore(sw, container.firstChild);
const gradients = api.LightBox.el.tfClass('tb_gradient_swatches');
for(let i=0; i <gradients.length;++i){
gradients[i].insertBefore(sw.cloneNode(true), gradients[i].firstChild);
this.swatchClicked = function(e){
classList = target.classList;
if(classList.contains('tb_gradient_swatch')){
this.selectSwatch(target.dataset.id);
else if(classList.contains('tf_delete_swatch')){
this.removeSwatch(target.parentNode.dataset.id);
themifyColorManager.updateColorSwatches('gradients');
this.removeSwatch = function(id){
const swatches = api.LightBox.el.querySelectorAll('.tb_gradient_swatch[data-id="'+id+'"]');
for(let i=swatches.length-1;i>-1; --i){
delete themifyCM.gradients[id];