@wix/interact 1.66.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +258 -0
  2. package/dist/cjs/WixInteractElement.js +103 -0
  3. package/dist/cjs/WixInteractElement.js.map +1 -0
  4. package/dist/cjs/__tests__/interact.spec.js +944 -0
  5. package/dist/cjs/__tests__/interact.spec.js.map +1 -0
  6. package/dist/cjs/external-types.d.js +2 -0
  7. package/dist/cjs/external-types.d.js.map +1 -0
  8. package/dist/cjs/handlers/animationEnd.js +33 -0
  9. package/dist/cjs/handlers/animationEnd.js.map +1 -0
  10. package/dist/cjs/handlers/click.js +82 -0
  11. package/dist/cjs/handlers/click.js.map +1 -0
  12. package/dist/cjs/handlers/hover.js +103 -0
  13. package/dist/cjs/handlers/hover.js.map +1 -0
  14. package/dist/cjs/handlers/index.js +21 -0
  15. package/dist/cjs/handlers/index.js.map +1 -0
  16. package/dist/cjs/handlers/pointerMove.js +40 -0
  17. package/dist/cjs/handlers/pointerMove.js.map +1 -0
  18. package/dist/cjs/handlers/utilities.js +51 -0
  19. package/dist/cjs/handlers/utilities.js.map +1 -0
  20. package/dist/cjs/handlers/viewEnter.js +69 -0
  21. package/dist/cjs/handlers/viewEnter.js.map +1 -0
  22. package/dist/cjs/handlers/viewProgress.js +56 -0
  23. package/dist/cjs/handlers/viewProgress.js.map +1 -0
  24. package/dist/cjs/index.js +21 -0
  25. package/dist/cjs/index.js.map +1 -0
  26. package/dist/cjs/interact.js +310 -0
  27. package/dist/cjs/interact.js.map +1 -0
  28. package/dist/cjs/test-types.d.js +2 -0
  29. package/dist/cjs/test-types.d.js.map +1 -0
  30. package/dist/cjs/types.js +2 -0
  31. package/dist/cjs/types.js.map +1 -0
  32. package/dist/cjs/utils.js +66 -0
  33. package/dist/cjs/utils.js.map +1 -0
  34. package/dist/esm/WixInteractElement.js +97 -0
  35. package/dist/esm/WixInteractElement.js.map +1 -0
  36. package/dist/esm/__tests__/interact.spec.js +952 -0
  37. package/dist/esm/__tests__/interact.spec.js.map +1 -0
  38. package/dist/esm/external-types.d.js +2 -0
  39. package/dist/esm/external-types.d.js.map +1 -0
  40. package/dist/esm/handlers/animationEnd.js +29 -0
  41. package/dist/esm/handlers/animationEnd.js.map +1 -0
  42. package/dist/esm/handlers/click.js +82 -0
  43. package/dist/esm/handlers/click.js.map +1 -0
  44. package/dist/esm/handlers/hover.js +103 -0
  45. package/dist/esm/handlers/hover.js.map +1 -0
  46. package/dist/esm/handlers/index.js +16 -0
  47. package/dist/esm/handlers/index.js.map +1 -0
  48. package/dist/esm/handlers/pointerMove.js +39 -0
  49. package/dist/esm/handlers/pointerMove.js.map +1 -0
  50. package/dist/esm/handlers/utilities.js +45 -0
  51. package/dist/esm/handlers/utilities.js.map +1 -0
  52. package/dist/esm/handlers/viewEnter.js +69 -0
  53. package/dist/esm/handlers/viewEnter.js.map +1 -0
  54. package/dist/esm/handlers/viewProgress.js +52 -0
  55. package/dist/esm/handlers/viewProgress.js.map +1 -0
  56. package/dist/esm/index.js +3 -0
  57. package/dist/esm/index.js.map +1 -0
  58. package/dist/esm/interact.js +303 -0
  59. package/dist/esm/interact.js.map +1 -0
  60. package/dist/esm/test-types.d.js +2 -0
  61. package/dist/esm/test-types.d.js.map +1 -0
  62. package/dist/esm/types.js +2 -0
  63. package/dist/esm/types.js.map +1 -0
  64. package/dist/esm/utils.js +61 -0
  65. package/dist/esm/utils.js.map +1 -0
  66. package/dist/types/WixInteractElement.d.ts +324 -0
  67. package/dist/types/__tests__/interact.spec.d.ts +1 -0
  68. package/dist/types/handlers/animationEnd.d.ts +8 -0
  69. package/dist/types/handlers/click.d.ts +8 -0
  70. package/dist/types/handlers/hover.d.ts +8 -0
  71. package/dist/types/handlers/index.d.ts +3 -0
  72. package/dist/types/handlers/pointerMove.d.ts +8 -0
  73. package/dist/types/handlers/utilities.d.ts +4 -0
  74. package/dist/types/handlers/viewEnter.d.ts +8 -0
  75. package/dist/types/handlers/viewProgress.d.ts +8 -0
  76. package/dist/types/index.d.ts +2 -0
  77. package/dist/types/interact.d.ts +25 -0
  78. package/dist/types/types.d.ts +173 -0
  79. package/dist/types/utils.d.ts +4 -0
  80. package/package.json +71 -0
@@ -0,0 +1,944 @@
1
+ "use strict";
2
+
3
+ var _interact = require("../interact");
4
+ var _utilities = require("../handlers/utilities");
5
+ // Mock @wix/motion module
6
+ jest.mock('@wix/motion', () => ({
7
+ getWebAnimation: jest.fn().mockReturnValue({
8
+ play: jest.fn(),
9
+ cancel: jest.fn()
10
+ }),
11
+ getScrubScene: jest.fn().mockReturnValue({}),
12
+ getEasing: jest.fn().mockImplementation(v => v)
13
+ }));
14
+
15
+ // Mock kuliso module
16
+ jest.mock('kuliso', () => ({
17
+ Pointer: jest.fn().mockImplementation(() => ({
18
+ start: jest.fn(),
19
+ destroy: jest.fn()
20
+ }))
21
+ }));
22
+
23
+ // Mock fizban module
24
+ jest.mock('fizban', () => ({
25
+ Scroll: jest.fn().mockImplementation(() => ({
26
+ start: jest.fn(),
27
+ end: jest.fn()
28
+ }))
29
+ }));
30
+ describe('interact', () => {
31
+ let element;
32
+ const mockConfig = {
33
+ interactions: [{
34
+ trigger: 'viewEnter',
35
+ source: '#logo-entrance',
36
+ params: {
37
+ threshold: 0.2
38
+ },
39
+ effects: [{
40
+ target: '#logo-entrance',
41
+ effectId: 'logo-arc-in'
42
+ }, {
43
+ target: '#logo-click',
44
+ effectId: 'logo-bounce'
45
+ }]
46
+ }, {
47
+ trigger: 'pageVisible',
48
+ source: '#logo-loop',
49
+ effects: [{
50
+ target: '#logo-loop',
51
+ effectId: 'logo-poke'
52
+ }]
53
+ }, {
54
+ trigger: 'animationEnd',
55
+ source: '#logo-animation-end',
56
+ params: {
57
+ effectId: 'logo-arc-in'
58
+ },
59
+ effects: [{
60
+ target: '#logo-animation-end',
61
+ effectId: 'logo-poke'
62
+ }]
63
+ }, {
64
+ trigger: 'pointerMove',
65
+ source: '#logo-mouse',
66
+ params: {
67
+ hitArea: 'root'
68
+ },
69
+ effects: [{
70
+ target: '#logo-mouse',
71
+ effectId: 'logo-track-mouse'
72
+ }]
73
+ }, {
74
+ trigger: 'click',
75
+ source: '#logo-click',
76
+ params: {
77
+ type: 'alternate'
78
+ },
79
+ effects: [{
80
+ target: '#logo-click',
81
+ effectId: 'logo-bounce'
82
+ }]
83
+ }, {
84
+ trigger: 'click',
85
+ source: '#logo-click',
86
+ params: {
87
+ method: 'toggle'
88
+ },
89
+ effects: [{
90
+ target: '#logo-click',
91
+ effectId: 'logo-transition-hover'
92
+ }]
93
+ }, {
94
+ trigger: 'hover',
95
+ source: '#logo-hover',
96
+ params: {
97
+ type: 'alternate'
98
+ },
99
+ effects: [{
100
+ target: '#logo-hover',
101
+ effectId: 'logo-arc-in'
102
+ }, {
103
+ target: '#logo-hover',
104
+ effectId: 'logo-arc-in',
105
+ namedEffect: {
106
+ type: 'ArcIn',
107
+ direction: 'left',
108
+ power: 'hard'
109
+ }
110
+ }]
111
+ }, {
112
+ trigger: 'hover',
113
+ source: '#logo-hover',
114
+ params: {
115
+ method: 'toggle'
116
+ },
117
+ effects: [{
118
+ target: '#logo-hover',
119
+ effectId: 'logo-transition-hover'
120
+ }]
121
+ }, {
122
+ trigger: 'viewProgress',
123
+ source: '#logo-scroll',
124
+ effects: [{
125
+ target: '#logo-scroll',
126
+ effectId: 'logo-fade-scroll'
127
+ }]
128
+ }],
129
+ effects: {
130
+ 'logo-arc-in': {
131
+ namedEffect: {
132
+ type: 'ArcIn',
133
+ direction: 'right',
134
+ power: 'medium'
135
+ },
136
+ duration: 1200
137
+ },
138
+ 'logo-arc-in-with-target': {
139
+ target: '#logo-hover',
140
+ namedEffect: {
141
+ type: 'ArcIn',
142
+ direction: 'right',
143
+ power: 'medium'
144
+ },
145
+ duration: 1200
146
+ },
147
+ 'logo-track-mouse': {
148
+ namedEffect: {
149
+ type: 'TrackMouse',
150
+ distance: {
151
+ value: 20,
152
+ type: 'px'
153
+ },
154
+ axis: 'both',
155
+ power: 'medium'
156
+ },
157
+ transitionDuration: 300,
158
+ transitionEasing: 'easeOut',
159
+ centeredToTarget: true
160
+ },
161
+ 'logo-bounce': {
162
+ namedEffect: {
163
+ type: 'BounceIn',
164
+ power: 'hard',
165
+ direction: 'center',
166
+ distanceFactor: 1.2
167
+ },
168
+ duration: 500
169
+ },
170
+ 'logo-fade-scroll': {
171
+ namedEffect: {
172
+ type: 'FadeScroll',
173
+ range: 'in',
174
+ opacity: 0
175
+ },
176
+ rangeStart: {
177
+ name: 'contain',
178
+ offset: {
179
+ value: -10,
180
+ type: 'percentage'
181
+ }
182
+ },
183
+ rangeEnd: {
184
+ name: 'contain',
185
+ offset: {
186
+ value: 110,
187
+ type: 'percentage'
188
+ }
189
+ }
190
+ },
191
+ 'logo-transition-hover': {
192
+ transition: {
193
+ duration: 300,
194
+ styleProperties: [{
195
+ name: 'opacity',
196
+ value: '0'
197
+ }]
198
+ }
199
+ },
200
+ 'logo-poke': {
201
+ namedEffect: {
202
+ type: 'Poke',
203
+ direction: 'left',
204
+ power: 'medium'
205
+ },
206
+ duration: 500
207
+ }
208
+ }
209
+ };
210
+ beforeEach(() => {
211
+ element = document.createElement('wix-interact-element');
212
+ const div = document.createElement('div');
213
+ element.append(div);
214
+
215
+ // Mock Web Animations API
216
+ window.KeyframeEffect = class KeyframeEffect {
217
+ // eslint-disable-next-line @typescript-eslint/no-shadow
218
+ constructor(element, keyframes, options) {
219
+ return {
220
+ element,
221
+ keyframes,
222
+ options
223
+ };
224
+ }
225
+ };
226
+
227
+ // Mock ViewTimeline
228
+ window.ViewTimeline = class ViewTimeline {
229
+ constructor(options) {
230
+ return {
231
+ ...options
232
+ };
233
+ }
234
+ };
235
+
236
+ // Mock Animation
237
+ window.Animation = class Animation {
238
+ constructor(effect, timeline) {
239
+ return {
240
+ effect,
241
+ timeline,
242
+ play: jest.fn()
243
+ };
244
+ }
245
+ };
246
+
247
+ // Mock IntersectionObserver
248
+ window.IntersectionObserver = class IntersectionObserver {
249
+ constructor(callback, options) {
250
+ return {
251
+ callback,
252
+ options,
253
+ observe: jest.fn(),
254
+ unobserve: jest.fn()
255
+ };
256
+ }
257
+ };
258
+
259
+ // Mock CSSStyleSheet
260
+ window.CSSStyleSheet = class CSSStyleSheet {
261
+ constructor(_) {
262
+ return {
263
+ replace: jest.fn()
264
+ };
265
+ }
266
+ };
267
+
268
+ // Mock adoptedStyleSheets
269
+ if (!document.adoptedStyleSheets) {
270
+ document.adoptedStyleSheets = [];
271
+ }
272
+
273
+ // Mock matchMedia for condition testing
274
+ mockMatchMedia();
275
+ });
276
+ function mockMatchMedia(matchingQueries = []) {
277
+ const queryRule = `(${matchingQueries.join(') and (')})`;
278
+ const mockMQL = query => {
279
+ return {
280
+ matches: queryRule === query,
281
+ media: query,
282
+ onchange: null,
283
+ addEventListener: jest.fn(),
284
+ removeEventListener: jest.fn(),
285
+ dispatchEvent: jest.fn()
286
+ };
287
+ };
288
+ Object.defineProperty(window, 'matchMedia', {
289
+ writable: true,
290
+ value: jest.fn().mockImplementation(mockMQL)
291
+ });
292
+ }
293
+ function createCascadingTestConfig(conditions = {}, matchingConditions = []) {
294
+ mockMatchMedia(matchingConditions);
295
+ return {
296
+ conditions: {
297
+ desktop: {
298
+ type: 'media',
299
+ predicate: 'min-width: 1024px'
300
+ },
301
+ mobile: {
302
+ type: 'media',
303
+ predicate: 'max-width: 767px'
304
+ },
305
+ tablet: {
306
+ type: 'media',
307
+ predicate: '(min-width: 768px) and (max-width: 1023px)'
308
+ },
309
+ ...conditions
310
+ },
311
+ interactions: [{
312
+ trigger: 'click',
313
+ source: '#cascade-source',
314
+ effects: [{
315
+ target: '#cascade-target',
316
+ effectId: 'default-effect'
317
+ }, {
318
+ target: '#cascade-target',
319
+ effectId: 'default-effect',
320
+ conditions: ['desktop'],
321
+ namedEffect: {
322
+ type: 'SlideIn',
323
+ direction: 'right',
324
+ power: 'medium'
325
+ },
326
+ duration: 800
327
+ }, {
328
+ target: '#cascade-target',
329
+ effectId: 'default-effect',
330
+ conditions: ['mobile'],
331
+ namedEffect: {
332
+ type: 'BounceIn',
333
+ direction: 'center',
334
+ power: 'hard'
335
+ },
336
+ duration: 600
337
+ }]
338
+ }],
339
+ effects: {
340
+ 'default-effect': {
341
+ namedEffect: {
342
+ type: 'FadeIn',
343
+ power: 'medium'
344
+ },
345
+ duration: 500
346
+ }
347
+ }
348
+ };
349
+ }
350
+ afterEach(() => {
351
+ jest.clearAllMocks();
352
+ (0, _interact.remove)('#logo-entrance');
353
+ (0, _interact.remove)('#logo-loop');
354
+ (0, _interact.remove)('#logo-animation-end');
355
+ (0, _interact.remove)('#logo-mouse');
356
+ (0, _interact.remove)('#logo-click');
357
+ (0, _interact.remove)('#logo-hover');
358
+ (0, _interact.remove)('#logo-scroll');
359
+ // Clean up cascading test elements
360
+ (0, _interact.remove)('#cascade-source');
361
+ (0, _interact.remove)('#cascade-target');
362
+ (0, _interact.remove)('#cascade-target-1');
363
+ (0, _interact.remove)('#cascade-target-2');
364
+ (0, _interact.remove)('#multi-source-1');
365
+ (0, _interact.remove)('#multi-source-2');
366
+ // Clear Interact instances to ensure test isolation
367
+ _interact.Interact.instances.length = 0;
368
+ _interact.Interact.elementCache.clear();
369
+ });
370
+ describe('init Interact', () => {
371
+ it('should initialize with valid config', () => {
372
+ _interact.Interact.create({});
373
+ expect(customElements.get('wix-interact-element')).toBeDefined();
374
+ });
375
+ });
376
+ describe('add interaction', () => {
377
+ beforeEach(() => {
378
+ _interact.Interact.create(mockConfig);
379
+ });
380
+ describe('hover', () => {
381
+ it('should add handler for hover trigger with alternate type', () => {
382
+ const {
383
+ getWebAnimation
384
+ } = require('@wix/motion');
385
+ element = document.createElement('wix-interact-element');
386
+ const div = document.createElement('div');
387
+ element.append(div);
388
+ const addEventListenerSpy = jest.spyOn(div, 'addEventListener');
389
+ (0, _interact.add)(element, '#logo-hover');
390
+ expect(addEventListenerSpy).toHaveBeenCalledTimes(4);
391
+ expect(addEventListenerSpy).toHaveBeenCalledWith('mouseenter', expect.any(Function), expect.objectContaining({
392
+ passive: true
393
+ }));
394
+ expect(addEventListenerSpy).toHaveBeenCalledWith('mouseleave', expect.any(Function), expect.objectContaining({
395
+ passive: true
396
+ }));
397
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
398
+ expect(getWebAnimation).toHaveBeenCalledWith(div, expect.objectContaining({
399
+ namedEffect: expect.objectContaining({
400
+ type: 'ArcIn',
401
+ direction: 'left',
402
+ power: 'hard'
403
+ })
404
+ }));
405
+ });
406
+ });
407
+ describe('click', () => {
408
+ it('should add handler for click trigger', () => {
409
+ element = document.createElement('wix-interact-element');
410
+ const div = document.createElement('div');
411
+ element.append(div);
412
+ const addEventListenerSpy = jest.spyOn(div, 'addEventListener');
413
+ (0, _interact.add)(element, '#logo-click');
414
+ expect(addEventListenerSpy).toHaveBeenCalledTimes(2);
415
+ expect(addEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function), expect.objectContaining({
416
+ passive: true
417
+ }));
418
+ });
419
+ });
420
+ describe('viewEnter', () => {
421
+ it('should add handler for viewEnter trigger', () => {
422
+ const {
423
+ getWebAnimation
424
+ } = require('@wix/motion');
425
+ element = document.createElement('wix-interact-element');
426
+ const div = document.createElement('div');
427
+ element.append(div);
428
+ (0, _interact.add)(element, '#logo-entrance');
429
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
430
+ expect(getWebAnimation).toHaveBeenCalledWith(expect.any(HTMLElement), expect.any(Object));
431
+ });
432
+ it('should add handler for viewEnter trigger when target is added before source', () => {
433
+ const {
434
+ getWebAnimation
435
+ } = require('@wix/motion');
436
+ element = document.createElement('wix-interact-element');
437
+ const div = document.createElement('div');
438
+ element.append(div);
439
+ const elementClick = document.createElement('wix-interact-element');
440
+ const divClick = document.createElement('div');
441
+ element.append(divClick);
442
+ (0, _interact.add)(elementClick, '#logo-click');
443
+ (0, _interact.add)(element, '#logo-entrance');
444
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
445
+ expect(getWebAnimation).toHaveBeenCalledWith(expect.any(HTMLElement), expect.any(Object));
446
+ });
447
+ });
448
+ describe('pageVisible', () => {
449
+ it('should add handler for pageVisible trigger', () => {
450
+ const {
451
+ getWebAnimation
452
+ } = require('@wix/motion');
453
+ element = document.createElement('wix-interact-element');
454
+ const div = document.createElement('div');
455
+ element.append(div);
456
+ (0, _interact.add)(element, '#logo-loop');
457
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
458
+ expect(getWebAnimation).toHaveBeenCalledWith(expect.any(HTMLElement), expect.any(Object));
459
+ });
460
+ });
461
+ describe('animationEnd', () => {
462
+ it('should add handler for animationEnd trigger', () => {
463
+ const {
464
+ getWebAnimation
465
+ } = require('@wix/motion');
466
+ element = document.createElement('wix-interact-element');
467
+ const div = document.createElement('div');
468
+ element.append(div);
469
+ (0, _interact.add)(element, '#logo-animation-end');
470
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
471
+ expect(getWebAnimation).toHaveBeenCalledWith(expect.any(HTMLElement), expect.any(Object));
472
+ });
473
+ });
474
+ describe('pointerMove', () => {
475
+ it('should add handler for pointerMove trigger', () => {
476
+ const {
477
+ getScrubScene
478
+ } = require('@wix/motion');
479
+ const {
480
+ Pointer
481
+ } = require('kuliso');
482
+ const pointerInstance = {
483
+ start: jest.fn(),
484
+ destroy: jest.fn()
485
+ };
486
+ Pointer.mockImplementation(() => pointerInstance);
487
+ element = document.createElement('wix-interact-element');
488
+ const div = document.createElement('div');
489
+ element.append(div);
490
+ (0, _interact.add)(element, '#logo-mouse');
491
+ expect(getScrubScene).toHaveBeenCalledTimes(1);
492
+ expect(getScrubScene).toHaveBeenCalledWith(expect.any(HTMLElement), expect.objectContaining((0, _utilities.effectToAnimationOptions)(mockConfig.effects['logo-track-mouse'])), expect.objectContaining({
493
+ trigger: 'pointer-move'
494
+ }));
495
+ expect(pointerInstance.start).toHaveBeenCalled();
496
+ });
497
+ });
498
+ describe('viewProgress', () => {
499
+ it('should add handler for viewProgress trigger with native ViewTimeline support', () => {
500
+ const {
501
+ getWebAnimation
502
+ } = require('@wix/motion');
503
+ element = document.createElement('wix-interact-element');
504
+ const div = document.createElement('div');
505
+ element.append(div);
506
+ (0, _interact.add)(element, '#logo-scroll');
507
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
508
+ expect(getWebAnimation).toHaveBeenCalledWith(expect.any(HTMLElement), expect.objectContaining((0, _utilities.effectToAnimationOptions)(mockConfig.effects['logo-fade-scroll'])), expect.objectContaining({
509
+ trigger: 'view-progress'
510
+ }));
511
+ });
512
+ it('should add handler for viewProgress trigger with fizban polyfill', () => {
513
+ // Remove ViewTimeline support
514
+ delete window.ViewTimeline;
515
+ const {
516
+ getScrubScene
517
+ } = require('@wix/motion');
518
+ const {
519
+ Scroll
520
+ } = require('fizban');
521
+ const scrollInstance = {
522
+ start: jest.fn(),
523
+ destroy: jest.fn()
524
+ };
525
+ Scroll.mockImplementation(() => scrollInstance);
526
+ element = document.createElement('wix-interact-element');
527
+ const div = document.createElement('div');
528
+ element.append(div);
529
+ (0, _interact.add)(element, '#logo-scroll');
530
+ expect(getScrubScene).toHaveBeenCalledTimes(1);
531
+ expect(getScrubScene).toHaveBeenCalledWith(expect.any(HTMLElement), expect.objectContaining((0, _utilities.effectToAnimationOptions)(mockConfig.effects['logo-fade-scroll'])), expect.objectContaining({
532
+ trigger: 'view-progress'
533
+ }));
534
+ setTimeout(() => {
535
+ expect(scrollInstance.start).toHaveBeenCalled();
536
+ }, 0);
537
+ });
538
+ });
539
+ });
540
+ describe('remove interaction', () => {
541
+ beforeEach(() => {
542
+ _interact.Interact.create(mockConfig);
543
+ });
544
+ it('should remove event listeners', () => {
545
+ element = document.createElement('wix-interact-element');
546
+ const div = document.createElement('div');
547
+ element.append(div);
548
+ const removeEventListenerSpy = jest.spyOn(div, 'removeEventListener');
549
+ (0, _interact.add)(element, '#logo-click');
550
+ (0, _interact.remove)('#logo-click');
551
+ expect(removeEventListenerSpy).toHaveBeenCalledTimes(2);
552
+ expect(removeEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function));
553
+ });
554
+ it('should do nothing if key does not exist', () => {
555
+ expect(() => (0, _interact.remove)('non-existent-key')).not.toThrow();
556
+ });
557
+ it('should cleanup pointer effects', () => {
558
+ const {
559
+ Pointer
560
+ } = require('kuliso');
561
+ const pointerInstance = {
562
+ start: jest.fn(),
563
+ destroy: jest.fn()
564
+ };
565
+ Pointer.mockImplementation(() => pointerInstance);
566
+ element = document.createElement('wix-interact-element');
567
+ const div = document.createElement('div');
568
+ element.append(div);
569
+ (0, _interact.add)(element, '#logo-mouse');
570
+ (0, _interact.remove)('#logo-mouse');
571
+ expect(pointerInstance.destroy).toHaveBeenCalledTimes(1);
572
+ });
573
+ });
574
+ describe('effect cascading logic', () => {
575
+ describe('basic cascading behavior', () => {
576
+ it('should apply only first matching effect for same target', () => {
577
+ const {
578
+ getWebAnimation
579
+ } = require('@wix/motion');
580
+ const config = createCascadingTestConfig({}, ['min-width: 1024px']);
581
+ _interact.Interact.create(config);
582
+ const sourceElement = document.createElement('wix-interact-element');
583
+ const sourceDiv = document.createElement('div');
584
+ sourceElement.append(sourceDiv);
585
+ const targetElement = document.createElement('wix-interact-element');
586
+ const targetDiv = document.createElement('div');
587
+ targetElement.append(targetDiv);
588
+ const addEventListenerSpy = jest.spyOn(sourceDiv, 'addEventListener');
589
+ (0, _interact.add)(sourceElement, '#cascade-source');
590
+ (0, _interact.add)(targetElement, '#cascade-target');
591
+ expect(addEventListenerSpy).toHaveBeenCalledTimes(1);
592
+ expect(addEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function), expect.objectContaining({
593
+ passive: true
594
+ }));
595
+
596
+ // Should create animation with desktop effect (first matching condition)
597
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
598
+ expect(getWebAnimation).toHaveBeenCalledWith(targetElement.firstElementChild, expect.objectContaining({
599
+ namedEffect: expect.objectContaining({
600
+ type: 'SlideIn',
601
+ direction: 'right'
602
+ })
603
+ }));
604
+ });
605
+ it('should apply default effect when no conditions match', () => {
606
+ const {
607
+ getWebAnimation
608
+ } = require('@wix/motion');
609
+ const config = createCascadingTestConfig({}, []); // No matching conditions
610
+
611
+ _interact.Interact.create(config);
612
+ const sourceElement = document.createElement('wix-interact-element');
613
+ const sourceDiv = document.createElement('div');
614
+ sourceElement.append(sourceDiv);
615
+ const targetElement = document.createElement('wix-interact-element');
616
+ const targetDiv = document.createElement('div');
617
+ targetElement.append(targetDiv);
618
+ (0, _interact.add)(sourceElement, '#cascade-source');
619
+ (0, _interact.add)(targetElement, '#cascade-target');
620
+
621
+ // Should create animation with default effect
622
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
623
+ expect(getWebAnimation).toHaveBeenCalledWith(targetElement.firstElementChild, expect.objectContaining({
624
+ namedEffect: expect.objectContaining({
625
+ type: 'FadeIn',
626
+ power: 'medium'
627
+ })
628
+ }));
629
+ });
630
+ it('should apply mobile effect when mobile condition matches', () => {
631
+ const {
632
+ getWebAnimation
633
+ } = require('@wix/motion');
634
+ const config = createCascadingTestConfig({}, ['max-width: 767px']);
635
+ _interact.Interact.create(config);
636
+ const sourceElement = document.createElement('wix-interact-element');
637
+ const sourceDiv = document.createElement('div');
638
+ sourceElement.append(sourceDiv);
639
+ const targetElement = document.createElement('wix-interact-element');
640
+ const targetDiv = document.createElement('div');
641
+ targetElement.append(targetDiv);
642
+ (0, _interact.add)(sourceElement, '#cascade-source');
643
+ (0, _interact.add)(targetElement, '#cascade-target');
644
+
645
+ // Should create animation with mobile effect
646
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
647
+ expect(getWebAnimation).toHaveBeenCalledWith(targetElement.firstElementChild, expect.objectContaining({
648
+ namedEffect: expect.objectContaining({
649
+ type: 'BounceIn',
650
+ direction: 'center'
651
+ })
652
+ }));
653
+ });
654
+ });
655
+ describe('element addition order', () => {
656
+ it('should work when source is added before target', () => {
657
+ const {
658
+ getWebAnimation
659
+ } = require('@wix/motion');
660
+ const config = createCascadingTestConfig({}, ['min-width: 1024px']);
661
+ _interact.Interact.create(config);
662
+ const sourceElement = document.createElement('wix-interact-element');
663
+ const sourceDiv = document.createElement('div');
664
+ sourceElement.append(sourceDiv);
665
+ const targetElement = document.createElement('wix-interact-element');
666
+ const targetDiv = document.createElement('div');
667
+ targetElement.append(targetDiv);
668
+
669
+ // Add source first
670
+ (0, _interact.add)(sourceElement, '#cascade-source');
671
+ // Add target second
672
+ (0, _interact.add)(targetElement, '#cascade-target');
673
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
674
+ expect(getWebAnimation).toHaveBeenCalledWith(targetElement.firstElementChild, expect.objectContaining({
675
+ namedEffect: expect.objectContaining({
676
+ type: 'SlideIn'
677
+ })
678
+ }));
679
+ });
680
+ it('should work when target is added before source', () => {
681
+ const {
682
+ getWebAnimation
683
+ } = require('@wix/motion');
684
+ const config = createCascadingTestConfig({}, ['min-width: 1024px']);
685
+ _interact.Interact.create(config);
686
+ const sourceElement = document.createElement('wix-interact-element');
687
+ const sourceDiv = document.createElement('div');
688
+ sourceElement.append(sourceDiv);
689
+ const targetElement = document.createElement('wix-interact-element');
690
+ const targetDiv = document.createElement('div');
691
+ targetElement.append(targetDiv);
692
+
693
+ // Add target first
694
+ (0, _interact.add)(targetElement, '#cascade-target');
695
+ // Add source second
696
+ (0, _interact.add)(sourceElement, '#cascade-source');
697
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
698
+ expect(getWebAnimation).toHaveBeenCalledWith(targetElement.firstElementChild, expect.objectContaining({
699
+ namedEffect: expect.objectContaining({
700
+ type: 'SlideIn'
701
+ })
702
+ }));
703
+ });
704
+ });
705
+ describe('complex cascading scenarios', () => {
706
+ it('should handle multiple targets with different conditions', () => {
707
+ const {
708
+ getWebAnimation
709
+ } = require('@wix/motion');
710
+ const complexConfig = {
711
+ conditions: {
712
+ desktop: {
713
+ type: 'media',
714
+ predicate: 'min-width: 1024px'
715
+ },
716
+ mobile: {
717
+ type: 'media',
718
+ predicate: 'max-width: 767px'
719
+ }
720
+ },
721
+ interactions: [{
722
+ trigger: 'click',
723
+ source: '#multi-source-1',
724
+ effects: [{
725
+ target: '#cascade-target-1',
726
+ effectId: 'desktop-effect',
727
+ conditions: ['desktop']
728
+ }, {
729
+ target: '#cascade-target-2',
730
+ effectId: 'mobile-effect',
731
+ conditions: ['mobile']
732
+ }]
733
+ }],
734
+ effects: {
735
+ 'desktop-effect': {
736
+ namedEffect: {
737
+ type: 'SlideIn',
738
+ direction: 'right',
739
+ power: 'medium'
740
+ },
741
+ duration: 800
742
+ },
743
+ 'mobile-effect': {
744
+ namedEffect: {
745
+ type: 'BounceIn',
746
+ direction: 'center',
747
+ power: 'hard'
748
+ },
749
+ duration: 600
750
+ }
751
+ }
752
+ };
753
+ mockMatchMedia(['min-width: 1024px']); // Only desktop matches
754
+ _interact.Interact.create(complexConfig);
755
+ const sourceElement = document.createElement('wix-interact-element');
756
+ const sourceDiv = document.createElement('div');
757
+ sourceElement.append(sourceDiv);
758
+ const target1Element = document.createElement('wix-interact-element');
759
+ const target1Div = document.createElement('div');
760
+ target1Element.append(target1Div);
761
+ const target2Element = document.createElement('wix-interact-element');
762
+ const target2Div = document.createElement('div');
763
+ target2Element.append(target2Div);
764
+ (0, _interact.add)(sourceElement, '#multi-source-1');
765
+ (0, _interact.add)(target1Element, '#cascade-target-1');
766
+ (0, _interact.add)(target2Element, '#cascade-target-2');
767
+
768
+ // Only desktop effect should be applied (mobile condition doesn't match)
769
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
770
+ expect(getWebAnimation).toHaveBeenCalledWith(target1Element.firstElementChild, expect.objectContaining({
771
+ namedEffect: expect.objectContaining({
772
+ type: 'SlideIn'
773
+ })
774
+ }));
775
+
776
+ // Should not be called for mobile effect since condition doesn't match
777
+ expect(getWebAnimation).not.toHaveBeenCalledWith(target2Element.firstElementChild, expect.objectContaining({
778
+ namedEffect: expect.objectContaining({
779
+ type: 'BounceIn'
780
+ })
781
+ }));
782
+ });
783
+ it('should handle effects with multiple conditions', () => {
784
+ const {
785
+ getWebAnimation
786
+ } = require('@wix/motion');
787
+ const multiConditionConfig = {
788
+ conditions: {
789
+ desktop: {
790
+ type: 'media',
791
+ predicate: 'min-width: 1024px'
792
+ },
793
+ 'high-res': {
794
+ type: 'media',
795
+ predicate: 'min-resolution: 2dppx'
796
+ }
797
+ },
798
+ interactions: [{
799
+ trigger: 'click',
800
+ source: '#cascade-source',
801
+ effects: [{
802
+ target: '#cascade-target',
803
+ effectId: 'premium-effect'
804
+ }, {
805
+ target: '#cascade-target',
806
+ effectId: 'premium-effect',
807
+ conditions: ['desktop', 'high-res']
808
+ }]
809
+ }],
810
+ effects: {
811
+ 'premium-effect': {
812
+ namedEffect: {
813
+ type: 'Poke',
814
+ direction: 'left',
815
+ power: 'hard'
816
+ },
817
+ duration: 1000
818
+ }
819
+ }
820
+ };
821
+
822
+ // Both conditions match
823
+ mockMatchMedia(['min-width: 1024px', 'min-resolution: 2dppx']);
824
+ _interact.Interact.create(multiConditionConfig);
825
+ const sourceElement = document.createElement('wix-interact-element');
826
+ const sourceDiv = document.createElement('div');
827
+ sourceElement.append(sourceDiv);
828
+ const targetElement = document.createElement('wix-interact-element');
829
+ const targetDiv = document.createElement('div');
830
+ targetElement.append(targetDiv);
831
+ (0, _interact.add)(sourceElement, '#cascade-source');
832
+ (0, _interact.add)(targetElement, '#cascade-target');
833
+
834
+ // Premium effect should be applied since both conditions match
835
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
836
+ expect(getWebAnimation).toHaveBeenCalledWith(targetElement.firstElementChild, expect.objectContaining({
837
+ namedEffect: expect.objectContaining({
838
+ type: 'Poke'
839
+ })
840
+ }));
841
+ });
842
+ });
843
+ describe('condition matching edge cases', () => {
844
+ it('should handle missing conditions gracefully', () => {
845
+ const {
846
+ getWebAnimation
847
+ } = require('@wix/motion');
848
+ const configWithMissingCondition = {
849
+ conditions: {
850
+ desktop: {
851
+ type: 'media',
852
+ predicate: 'min-width: 1024px'
853
+ }
854
+ },
855
+ interactions: [{
856
+ trigger: 'click',
857
+ source: '#cascade-source',
858
+ effects: [{
859
+ target: '#cascade-target',
860
+ effectId: 'default-effect'
861
+ }, {
862
+ target: '#cascade-target',
863
+ effectId: 'default-effect',
864
+ conditions: ['nonexistent-condition']
865
+ }]
866
+ }],
867
+ effects: {
868
+ 'default-effect': {
869
+ namedEffect: {
870
+ type: 'FadeIn',
871
+ power: 'medium'
872
+ },
873
+ duration: 500
874
+ }
875
+ }
876
+ };
877
+ mockMatchMedia(['min-width: 1024px']);
878
+ _interact.Interact.create(configWithMissingCondition);
879
+ const sourceElement = document.createElement('wix-interact-element');
880
+ const sourceDiv = document.createElement('div');
881
+ sourceElement.append(sourceDiv);
882
+ const targetElement = document.createElement('wix-interact-element');
883
+ const targetDiv = document.createElement('div');
884
+ targetElement.append(targetDiv);
885
+ (0, _interact.add)(sourceElement, '#cascade-source');
886
+ (0, _interact.add)(targetElement, '#cascade-target');
887
+
888
+ // Should fall back to default effect since condition doesn't exist
889
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
890
+ expect(getWebAnimation).toHaveBeenCalledWith(targetElement.firstElementChild, expect.objectContaining({
891
+ namedEffect: expect.objectContaining({
892
+ type: 'FadeIn'
893
+ })
894
+ }));
895
+ });
896
+ it('should handle empty conditions array', () => {
897
+ const {
898
+ getWebAnimation
899
+ } = require('@wix/motion');
900
+ const configWithEmptyConditions = {
901
+ conditions: {},
902
+ interactions: [{
903
+ trigger: 'click',
904
+ source: '#cascade-source',
905
+ effects: [{
906
+ target: '#cascade-target',
907
+ effectId: 'always-applied-effect',
908
+ conditions: []
909
+ }]
910
+ }],
911
+ effects: {
912
+ 'always-applied-effect': {
913
+ namedEffect: {
914
+ type: 'Spin',
915
+ direction: 'clockwise',
916
+ power: 'medium'
917
+ },
918
+ duration: 1000
919
+ }
920
+ }
921
+ };
922
+ mockMatchMedia([]);
923
+ _interact.Interact.create(configWithEmptyConditions);
924
+ const sourceElement = document.createElement('wix-interact-element');
925
+ const sourceDiv = document.createElement('div');
926
+ sourceElement.append(sourceDiv);
927
+ const targetElement = document.createElement('wix-interact-element');
928
+ const targetDiv = document.createElement('div');
929
+ targetElement.append(targetDiv);
930
+ (0, _interact.add)(sourceElement, '#cascade-source');
931
+ (0, _interact.add)(targetElement, '#cascade-target');
932
+
933
+ // Effect should be applied since empty conditions array should always match
934
+ expect(getWebAnimation).toHaveBeenCalledTimes(1);
935
+ expect(getWebAnimation).toHaveBeenCalledWith(expect.any(HTMLElement), expect.objectContaining({
936
+ namedEffect: expect.objectContaining({
937
+ type: 'Spin'
938
+ })
939
+ }));
940
+ });
941
+ });
942
+ });
943
+ });
944
+ //# sourceMappingURL=interact.spec.js.map