PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.86
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.86
51.1.86 51.1.84 51.1.85 51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 All 40 releases
← All changes | includes/extensions/Custom_Cursor/assets/script.js +189 -17 51.1.44 → 51.1.86 View file →
@@ -54,9 +54,128 @@
54 54 label: null,
55 55 tailContainer: null,
56 56 initialized: false,
57 57
58 + isLivePreviewMatchedType(type) {
59 + return ['dot', 'ring', 'dot-ring', 'outline', 'blend'].includes((type || '').toString());
60 + },
61 +
58 62 /**
63 + * Render core preset visuals to match the Live Preview logic.
64 + * This ensures Dot/Ring/Dot+Ring/Outline look identical on frontend.
65 + *
66 + * @param {Object} options Render overrides.
67 + * @param {string} [options.fill] Fill color.
68 + * @param {string} [options.border] Border color.
69 + * @param {number} [options.scale] Inner scale.
70 + */
71 + renderLikePreview(options = {}) {
72 + if (!this.cursor || !this.inner || !this.outer || !this.config) {
73 + return;
74 + }
75 +
76 + const { preset, states } = this.config;
77 + const type = (preset.type || 'dot').toString();
78 +
79 + if (!this.isLivePreviewMatchedType(type)) {
80 + // For non-core types, keep the existing CSS-based rendering.
81 + this.state.lastFill = (options.fill || preset.fill_color || '#111111').toString();
82 + return;
83 + }
84 +
85 + const size = parseFloat(this.state.runtimeSize || preset.size || 14) || 14;
86 + const borderWidth = parseFloat(preset.border_width || 2) || 2;
87 + const fill = (options.fill || preset.fill_color || '#111111').toString();
88 + const border = (options.border || preset.border_color || '#111111').toString();
89 + const opacity = parseFloat(states?.normal?.opacity ?? 1) || 1;
90 + const scale = parseFloat(options.scale ?? states?.normal?.scale ?? 1) || 1;
91 + const blur = parseFloat(preset.blur || 0) || 0;
92 +
93 + // Track last fill for ripple.
94 + this.state.lastFill = fill;
95 +
96 + // Reset styles
97 + this.inner.style.display = 'none';
98 + this.outer.style.display = 'none';
99 +
100 + this.inner.style.boxShadow = 'none';
101 + this.inner.style.background = fill;
102 + this.inner.style.filter = 'none';
103 + this.inner.style.borderRadius = '50%';
104 + this.inner.style.border = '';
105 +
106 + this.outer.style.background = 'transparent';
107 + this.cursor.style.mixBlendMode = 'normal';
108 +
109 + // IMPORTANT: base CSS uses `inset: 0` for both inner/outer.
110 + // For preview-matched types we must opt out, otherwise width/height/top/left won't behave.
111 + this.inner.style.inset = 'auto';
112 + this.outer.style.inset = 'auto';
113 + this.inner.style.right = 'auto';
114 + this.inner.style.bottom = 'auto';
115 + this.outer.style.right = 'auto';
116 + this.outer.style.bottom = 'auto';
117 + this.inner.style.top = '50%';
118 + this.inner.style.left = '50%';
119 + this.outer.style.top = '50%';
120 + this.outer.style.left = '50%';
121 +
122 + // Base inner
123 + this.inner.style.width = `${size}px`;
124 + this.inner.style.height = `${size}px`;
125 + this.inner.style.opacity = `${opacity}`;
126 + this.inner.style.transform = `translate(-50%, -50%) scale(${scale})`;
127 +
128 + // Base outer (ring)
129 + const outerSize = size + (borderWidth * 2) + 8;
130 + this.outer.style.width = `${outerSize}px`;
131 + this.outer.style.height = `${outerSize}px`;
132 + this.outer.style.border = `${borderWidth}px solid ${border}`;
133 + this.outer.style.marginTop = '0';
134 + this.outer.style.marginLeft = '0';
135 + this.outer.style.opacity = '1';
136 + this.outer.style.transform = 'translate(-50%, -50%)';
137 +
138 + // Type-specific
139 + switch (type) {
140 + case 'dot':
141 + this.inner.style.display = 'block';
142 + break;
143 + case 'ring':
144 + this.outer.style.display = 'block';
145 + break;
146 + case 'dot-ring':
147 + this.inner.style.display = 'block';
148 + this.outer.style.display = 'block';
149 + break;
150 + case 'outline':
151 + this.inner.style.display = 'block';
152 + this.inner.style.background = 'transparent';
153 + this.inner.style.border = `${borderWidth}px solid ${fill}`;
154 + this.inner.style.width = `${Math.max(0, size - borderWidth * 2)}px`;
155 + this.inner.style.height = `${Math.max(0, size - borderWidth * 2)}px`;
156 + break;
157 + case 'blend':
158 + // Keep existing CSS preset for advanced types; match blend-mode.
159 + this.cursor.style.mixBlendMode = 'difference';
160 + this.inner.style.display = 'block';
161 + this.inner.style.background = '#ffffff';
162 + this.inner.style.width = `${size * 1.5}px`;
163 + this.inner.style.height = `${size * 1.5}px`;
164 + break;
165 + default:
166 + // For all other types, fall back to existing CSS preset styles.
167 + // But still apply blur if configured.
168 + this.inner.style.display = 'block';
169 + }
170 +
171 + // Apply blur if set (mimic preview rule)
172 + if (blur > 0 && !['soft-glow', 'blend'].includes(type)) {
173 + this.inner.style.filter = `blur(${blur}px)`;
174 + }
175 + },
176 +
177 + /**
59 178 * Initialize the custom cursor.
60 179 *
61 180 * @param {Object} config Configuration from PHP.
62 181 */
@@ -66,9 +185,9 @@
66 185 return;
67 186 }
68 187
69 188 if (!config) {
70 - console.warn('[KingAddons CustomCursor] No configuration found.');
189 + // console.warn('[KingAddons CustomCursor] No configuration found.');
71 190 return;
72 191 }
73 192
74 193 if (!config.enabled) {
@@ -137,8 +256,15 @@
137 256
138 257 // Add body class
139 258 document.body.classList.add(config.bodyClass || DEFAULT_BODY_CLASS);
140 259
260 + // Sync hide-original cursor class (must also remove when disabled)
261 + if (config.hideOriginalCursor) {
262 + document.body.classList.add('ka-cursor-hide-original');
263 + } else {
264 + document.body.classList.remove('ka-cursor-hide-original');
265 + }
266 +
141 267 // Add preset type class
142 268 this.cursor.classList.add(`ka-custom-cursor--type-${config.preset.type}`);
143 269
144 270 // Add blend mode class if needed
@@ -216,10 +342,16 @@
216 342 setVar('--ka-cursor-size', `${sizeValue}px`);
217 343 setVar('--ka-cursor-border-width', `${preset.border_width}px`);
218 344 setVar('--ka-cursor-fill', preset.fill_color);
219 345 setVar('--ka-cursor-border-color', preset.border_color);
220 - setVar('--ka-cursor-opacity', states.normal.opacity);
221 - setVar('--ka-cursor-scale', states.normal.scale);
346 + if (this.isLivePreviewMatchedType(preset.type)) {
347 + // Live Preview applies opacity/scale to inner only, not the whole cursor.
348 + setVar('--ka-cursor-opacity', '1');
349 + setVar('--ka-cursor-scale', '1');
350 + } else {
351 + setVar('--ka-cursor-opacity', states.normal.opacity);
352 + setVar('--ka-cursor-scale', states.normal.scale);
353 + }
222 354 setVar('--ka-cursor-blur', `${preset.blur}px`);
223 355 setVar('--ka-cursor-mix-blend', preset.blend_mode || 'normal');
224 356
225 357 // Apply image settings for image cursor type
@@ -227,8 +359,11 @@
227 359 setVar('--ka-cursor-image', `url(${image.url})`);
228 360 setVar('--ka-cursor-image-offset-x', `${image.hotspot_x || 0}px`);
229 361 setVar('--ka-cursor-image-offset-y', `${image.hotspot_y || 0}px`);
230 362 }
363 +
364 + // Render visuals to match admin Live Preview.
365 + this.renderLikePreview();
231 366 },
232 367
233 368 /**
234 369 * Build tail dots for trail effect.
@@ -368,19 +503,35 @@
368 503
369 504 /**
370 505 * Handle mouse down event.
371 506 */
372 - handleDown() {
507 + handleDown(event) {
373 508 this.setState('click');
374 509 if (this.config.states.click.ripple) {
375 - this.cursor.classList.add('ka-custom-cursor--ripple');
376 - globalScope.setTimeout(() => {
377 - this.cursor.classList.remove('ka-custom-cursor--ripple');
378 - }, 300);
510 + this.spawnClickRipple(event);
379 511 }
380 512 },
381 513
382 514 /**
515 + * Spawn a click ripple that expands beyond cursor bounds.
516 + */
517 + spawnClickRipple(event) {
518 + try {
519 + const ripple = document.createElement('div');
520 + ripple.className = 'ka-custom-cursor__click-ripple';
521 + const x = event && typeof event.clientX === 'number' ? event.clientX : (parseFloat(this.state.targetX) || 0);
522 + const y = event && typeof event.clientY === 'number' ? event.clientY : (parseFloat(this.state.targetY) || 0);
523 + ripple.style.left = `${x - 30}px`;
524 + ripple.style.top = `${y - 30}px`;
525 + ripple.style.background = (this.state.lastFill || this.config?.preset?.fill_color || '#111111').toString();
526 + document.body.appendChild(ripple);
527 + globalScope.setTimeout(() => ripple.remove(), 650);
528 + } catch (e) {
529 + // noop
530 + }
531 + },
532 +
533 + /**
383 534 * Handle mouse up event.
384 535 */
385 536 handleUp() {
386 537 this.setState('normal');
@@ -503,29 +654,50 @@
503 654 const hoverScale = this.config.states.hover_link.scale || 1.25;
504 655 const clickScale = this.config.states.click.scale || 0.9;
505 656 const multiplier = options.sizeMultiplier || this.state.multiplier || 1;
506 657
658 + const isMatched = this.isLivePreviewMatchedType(this.config?.preset?.type);
507 659 const setVar = (key, value) => this.cursor.style.setProperty(key, value);
508 660
509 661 if (['hover', 'drag', 'zoom'].includes(name)) {
510 - setVar('--ka-cursor-scale', hoverScale * multiplier);
511 - setVar('--ka-cursor-fill', this.state.colorOverride || this.config.states.hover_link.color || this.config.preset.fill_color);
512 - setVar('--ka-cursor-border-color', this.state.borderOverride || this.config.states.hover_link.border_color || this.config.preset.border_color);
662 + const nextFill = this.state.colorOverride || this.config.states.hover_link.color || this.config.preset.fill_color;
663 + const nextBorder = this.state.borderOverride || this.config.states.hover_link.border_color || this.config.preset.border_color;
664 + if (isMatched) {
665 + this.renderLikePreview({ fill: nextFill, border: nextBorder, scale: hoverScale * multiplier });
666 + } else {
667 + setVar('--ka-cursor-scale', hoverScale * multiplier);
668 + setVar('--ka-cursor-fill', nextFill);
669 + setVar('--ka-cursor-border-color', nextBorder);
670 + }
513 671 this.setLabel(options.label || this.config.states.hover_link.label);
514 672 return;
515 673 }
516 674
517 675 if (name === 'click') {
518 - setVar('--ka-cursor-scale', clickScale * multiplier);
519 - setVar('--ka-cursor-fill', this.state.colorOverride || this.config.preset.fill_color);
520 - setVar('--ka-cursor-border-color', this.state.borderOverride || this.config.preset.border_color);
676 + const nextFill = this.state.colorOverride || this.config.preset.fill_color;
677 + const nextBorder = this.state.borderOverride || this.config.preset.border_color;
678 + if (isMatched) {
679 + this.renderLikePreview({ fill: nextFill, border: nextBorder, scale: clickScale * multiplier });
680 + } else {
681 + setVar('--ka-cursor-scale', clickScale * multiplier);
682 + setVar('--ka-cursor-fill', nextFill);
683 + setVar('--ka-cursor-border-color', nextBorder);
684 + }
521 685 return;
522 686 }
523 687
524 688 // Normal state
525 - setVar('--ka-cursor-scale', baseScale * multiplier);
526 - setVar('--ka-cursor-fill', this.config.preset.fill_color);
527 - setVar('--ka-cursor-border-color', this.config.preset.border_color);
689 + if (isMatched) {
690 + this.renderLikePreview({
691 + fill: this.config.preset.fill_color,
692 + border: this.config.preset.border_color,
693 + scale: baseScale * multiplier,
694 + });
695 + } else {
696 + setVar('--ka-cursor-scale', baseScale * multiplier);
697 + setVar('--ka-cursor-fill', this.config.preset.fill_color);
698 + setVar('--ka-cursor-border-color', this.config.preset.border_color);
699 + }
528 700 this.state.multiplier = 1;
529 701 this.state.colorOverride = null;
530 702 this.state.borderOverride = null;
531 703 this.setLabel('');