intersection-observer 0.11.0 → 0.12.1

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.
package/README.md CHANGED
@@ -61,16 +61,16 @@ There are, however, additional use cases that the default configuration will not
61
61
 
62
62
  If you need to handle any of these use-cases, you can configure the polyfill to poll the document by setting the `POLL_INTERVAL` property. This can be set either globally or on a per-instance basis.
63
63
 
64
- **Enabling polling for all instance:**
64
+ **Enabling polling for all instances:**
65
65
 
66
- To enable polling for all instance, set a value for `POLL_INTERVAL` on the `IntersectionObserver` prototype:
66
+ To enable polling for all instances, set a value for `POLL_INTERVAL` on the `IntersectionObserver` prototype:
67
67
 
68
68
 
69
69
  ```js
70
70
  IntersectionObserver.prototype.POLL_INTERVAL = 100; // Time in milliseconds.
71
71
  ```
72
72
 
73
- **Enabling polling for individual instance:**
73
+ **Enabling polling for individual instances:**
74
74
 
75
75
  To enable polling on only specific instances, set a `POLL_INTERVAL` value on the instance itself:
76
76
 
@@ -94,7 +94,7 @@ var io = new IntersectionObserver(callback);
94
94
  io.USE_MUTATION_OBSERVER = false;
95
95
  ```
96
96
 
97
- This is recommended in cases where the DOM will update frequently but you know those updates will have no affect on the position or your target elements.
97
+ This is recommended in cases where the DOM will update frequently but you know those updates will have no effect on the position or your target elements.
98
98
 
99
99
 
100
100
  ## iframe support
@@ -171,7 +171,7 @@ Legacy support is also possible in very old browsers by including a shim for ES5
171
171
  <script src="https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver"></script>
172
172
  ```
173
173
 
174
- With these polyfills, `IntersectionObserver` has been tested an known to work in the following browsers:
174
+ With these polyfills, `IntersectionObserver` has been tested and known to work in the following browsers:
175
175
 
176
176
  <table>
177
177
  <tr>
@@ -210,4 +210,4 @@ With these polyfills, `IntersectionObserver` has been tested an known to work in
210
210
 
211
211
  To run the test suite for the `IntersectionObserver` polyfill, open the [`intersection-observer-test.html`](./intersection-observer-test.html) page in the browser of your choice.
212
212
 
213
- If you run the tests in a browser that support `IntersectionObserver` natively, the tests will be run against the native implementation. If it doesn't the tests will be run against the polyfill.
213
+ If you run the tests in a browser that supports `IntersectionObserver` natively, the tests will be run against the native implementation. If it doesn't, the tests will be run against the polyfill.
@@ -69,12 +69,15 @@ describe('IntersectionObserver', function() {
69
69
  io = new IntersectionObserver(noop);
70
70
  expect(io.root).to.be(null);
71
71
 
72
+ io = new IntersectionObserver(noop, {root: document});
73
+ expect(io.root).to.be(document);
74
+
72
75
  io = new IntersectionObserver(noop, {root: rootEl});
73
76
  expect(io.root).to.be(rootEl);
74
77
  });
75
78
 
76
79
 
77
- it('throws when root is not an Element', function() {
80
+ it('throws when root is not a Document or Element', function() {
78
81
  expect(function() {
79
82
  io = new IntersectionObserver(noop, {root: 'foo'});
80
83
  }).to.throwException();
@@ -1398,6 +1401,89 @@ describe('IntersectionObserver', function() {
1398
1401
  io.observe(iframeTargetEl2);
1399
1402
  });
1400
1403
 
1404
+ it('handles tracking iframe viewport', function(done) {
1405
+ iframe.style.height = '100px';
1406
+ iframe.style.top = '100px';
1407
+ iframeWin.scrollTo(0, 110);
1408
+ // {root:iframeDoc} means to track the iframe viewport.
1409
+ var io = new IntersectionObserver(
1410
+ function (records) {
1411
+ io.unobserve(iframeTargetEl1);
1412
+
1413
+ var intersectionRect = rect({
1414
+ top: 0, // if root=null, then this would be 100.
1415
+ left: 0,
1416
+ height: 90,
1417
+ width: bodyWidth
1418
+ });
1419
+ expect(records.length).to.be(1);
1420
+ expect(rect(records[0].rootBounds)).to.eql(getRootRect(iframeDoc));
1421
+ expect(rect(records[0].intersectionRect)).to.eql(intersectionRect);
1422
+ done();
1423
+ },
1424
+ { root: iframeDoc }
1425
+ );
1426
+
1427
+ io.observe(iframeTargetEl1);
1428
+ });
1429
+
1430
+ it('handles tracking iframe viewport with rootMargin', function(done) {
1431
+ iframe.style.height = '100px';
1432
+
1433
+ var io = new IntersectionObserver(
1434
+ function (records) {
1435
+ io.unobserve(iframeTargetEl1);
1436
+ var intersectionRect = rect({
1437
+ top: 0, // if root=null, then this would be 100.
1438
+ left: 0,
1439
+ height: 200,
1440
+ width: bodyWidth
1441
+ });
1442
+
1443
+ // rootMargin: 100% --> 3x width + 3x height.
1444
+ var expectedRootBounds = rect({
1445
+ top: -100,
1446
+ left: -bodyWidth,
1447
+ width: bodyWidth * 3,
1448
+ height: 100 * 3
1449
+ });
1450
+ expect(records.length).to.be(1);
1451
+ expect(rect(records[0].rootBounds)).to.eql(expectedRootBounds);
1452
+ expect(rect(records[0].intersectionRect)).to.eql(intersectionRect);
1453
+ done();
1454
+ },
1455
+ { root: iframeDoc, rootMargin: '100%' }
1456
+ );
1457
+
1458
+ io.observe(iframeTargetEl1);
1459
+ });
1460
+
1461
+ // Current spec indicates that cross-document tracking yields
1462
+ // an essentially empty IntersectionObserverEntry.
1463
+ // See: https://github.com/w3c/IntersectionObserver/issues/87
1464
+ it('does not track cross-document elements', function(done) {
1465
+ var io = new IntersectionObserver(
1466
+ function (records) {
1467
+ io.unobserve(iframeTargetEl1)
1468
+
1469
+ expect(records.length).to.be(1);
1470
+ const zeroesRect = rect({
1471
+ top: 0,
1472
+ left: 0,
1473
+ width: 0,
1474
+ height: 0
1475
+ });
1476
+ expect(rect(records[0].rootBounds)).to.eql(zeroesRect);
1477
+ expect(rect(records[0].intersectionRect)).to.eql(zeroesRect);
1478
+ expect(records.isIntersecting).false;
1479
+ done();
1480
+ },
1481
+ { root: document }
1482
+ );
1483
+
1484
+ io.observe(iframeTargetEl1);
1485
+ });
1486
+
1401
1487
  it('handles style changes', function(done) {
1402
1488
  var spy = sinon.spy();
1403
1489
 
@@ -3022,6 +3108,62 @@ describe('IntersectionObserver', function() {
3022
3108
  }
3023
3109
  ], done);
3024
3110
  });
3111
+
3112
+ it('handles tracking iframe viewport', function(done) {
3113
+ iframe.style.height = '100px';
3114
+ iframe.style.top = '100px';
3115
+ iframeWin.scrollTo(0, 110);
3116
+ // {root:iframeDoc} means to track the iframe viewport.
3117
+ var io = createObserver(
3118
+ function (records) {
3119
+ io.unobserve(iframeTargetEl1);
3120
+ var intersectionRect = rect({
3121
+ top: 0, // if root=null, then this would be 100.
3122
+ left: 0,
3123
+ height: 90,
3124
+ width: bodyWidth
3125
+ });
3126
+ expect(records.length).to.be(1);
3127
+ expect(rect(records[0].rootBounds)).to.eql(getRootRect(iframeDoc));
3128
+ expect(rect(records[0].intersectionRect)).to.eql(intersectionRect);
3129
+ done();
3130
+ },
3131
+ { root: iframeDoc }
3132
+ );
3133
+
3134
+ io.observe(iframeTargetEl1);
3135
+ });
3136
+
3137
+ it('handles tracking iframe viewport with rootMargin', function(done) {
3138
+ iframe.style.height = '100px';
3139
+
3140
+ var io = createObserver(
3141
+ function (records) {
3142
+ io.unobserve(iframeTargetEl1);
3143
+ var intersectionRect = rect({
3144
+ top: 0, // if root=null, then this would be 100.
3145
+ left: 0,
3146
+ height: 200,
3147
+ width: bodyWidth
3148
+ });
3149
+
3150
+ // rootMargin: 100% --> 3x width + 3x height.
3151
+ var expectedRootBounds = rect({
3152
+ top: -100,
3153
+ left: -bodyWidth,
3154
+ width: bodyWidth * 3,
3155
+ height: 100 * 3
3156
+ });
3157
+ expect(records.length).to.be(1);
3158
+ expect(rect(records[0].rootBounds)).to.eql(expectedRootBounds);
3159
+ expect(rect(records[0].intersectionRect)).to.eql(intersectionRect);
3160
+ done();
3161
+ },
3162
+ { root: iframeDoc, rootMargin: '100%' }
3163
+ );
3164
+
3165
+ io.observe(iframeTargetEl1);
3166
+ });
3025
3167
  });
3026
3168
  });
3027
3169
  });
@@ -131,8 +131,12 @@ function IntersectionObserver(callback, opt_options) {
131
131
  throw new Error('callback must be a function');
132
132
  }
133
133
 
134
- if (options.root && options.root.nodeType != 1) {
135
- throw new Error('root must be an Element');
134
+ if (
135
+ options.root &&
136
+ options.root.nodeType != 1 &&
137
+ options.root.nodeType != 9
138
+ ) {
139
+ throw new Error('root must be a Document or Element');
136
140
  }
137
141
 
138
142
  // Binds and throttles `this._checkForIntersections`.
@@ -395,7 +399,9 @@ IntersectionObserver.prototype._monitorIntersections = function(doc) {
395
399
  });
396
400
 
397
401
  // Also monitor the parent.
398
- if (doc != (this.root && this.root.ownerDocument || document)) {
402
+ var rootDoc =
403
+ (this.root && (this.root.ownerDocument || this.root)) || document;
404
+ if (doc != rootDoc) {
399
405
  var frame = getFrameElement(doc);
400
406
  if (frame) {
401
407
  this._monitorIntersections(frame.ownerDocument);
@@ -415,7 +421,8 @@ IntersectionObserver.prototype._unmonitorIntersections = function(doc) {
415
421
  return;
416
422
  }
417
423
 
418
- var rootDoc = (this.root && this.root.ownerDocument || document);
424
+ var rootDoc =
425
+ (this.root && (this.root.ownerDocument || this.root)) || document;
419
426
 
420
427
  // Check if any dependent targets are still remaining.
421
428
  var hasDependentTargets =
@@ -493,11 +500,18 @@ IntersectionObserver.prototype._checkForIntersections = function() {
493
500
  var intersectionRect = rootIsInDom && rootContainsTarget &&
494
501
  this._computeTargetAndRootIntersection(target, targetRect, rootRect);
495
502
 
503
+ var rootBounds = null;
504
+ if (!this._rootContainsTarget(target)) {
505
+ rootBounds = getEmptyRect();
506
+ } else if (!crossOriginUpdater || this.root) {
507
+ rootBounds = rootRect;
508
+ }
509
+
496
510
  var newEntry = item.entry = new IntersectionObserverEntry({
497
511
  time: now(),
498
512
  target: target,
499
513
  boundingClientRect: targetRect,
500
- rootBounds: crossOriginUpdater && !this.root ? null : rootRect,
514
+ rootBounds: rootBounds,
501
515
  intersectionRect: intersectionRect
502
516
  });
503
517
 
@@ -618,12 +632,13 @@ IntersectionObserver.prototype._computeTargetAndRootIntersection =
618
632
  */
619
633
  IntersectionObserver.prototype._getRootRect = function() {
620
634
  var rootRect;
621
- if (this.root) {
635
+ if (this.root && !isDoc(this.root)) {
622
636
  rootRect = getBoundingClientRect(this.root);
623
637
  } else {
624
638
  // Use <html>/<body> instead of window since scroll bars affect size.
625
- var html = document.documentElement;
626
- var body = document.body;
639
+ var doc = isDoc(this.root) ? this.root : document;
640
+ var html = doc.documentElement;
641
+ var body = doc.body;
627
642
  rootRect = {
628
643
  top: 0,
629
644
  left: 0,
@@ -714,8 +729,12 @@ IntersectionObserver.prototype._rootIsInDom = function() {
714
729
  * @private
715
730
  */
716
731
  IntersectionObserver.prototype._rootContainsTarget = function(target) {
717
- return containsDeep(this.root || document, target) &&
718
- (!this.root || this.root.ownerDocument == target.ownerDocument);
732
+ var rootDoc =
733
+ (this.root && (this.root.ownerDocument || this.root)) || document;
734
+ return (
735
+ containsDeep(rootDoc, target) &&
736
+ (!this.root || rootDoc == target.ownerDocument)
737
+ );
719
738
  };
720
739
 
721
740
 
@@ -802,8 +821,8 @@ function removeEvent(node, event, fn, opt_useCapture) {
802
821
  if (typeof node.removeEventListener == 'function') {
803
822
  node.removeEventListener(event, fn, opt_useCapture || false);
804
823
  }
805
- else if (typeof node.detatchEvent == 'function') {
806
- node.detatchEvent('on' + event, fn);
824
+ else if (typeof node.detachEvent == 'function') {
825
+ node.detachEvent('on' + event, fn);
807
826
  }
808
827
  }
809
828
 
@@ -965,19 +984,28 @@ function getParentNode(node) {
965
984
  return getFrameElement(node);
966
985
  }
967
986
 
987
+ // If the parent has element that is assigned through shadow root slot
988
+ if (parent && parent.assignedSlot) {
989
+ parent = parent.assignedSlot.parentNode
990
+ }
991
+
968
992
  if (parent && parent.nodeType == 11 && parent.host) {
969
993
  // If the parent is a shadow root, return the host element.
970
994
  return parent.host;
971
995
  }
972
996
 
973
- if (parent && parent.assignedSlot) {
974
- // If the parent is distributed in a <slot>, return the parent of a slot.
975
- return parent.assignedSlot.parentNode;
976
- }
977
-
978
997
  return parent;
979
998
  }
980
999
 
1000
+ /**
1001
+ * Returns true if `node` is a Document.
1002
+ * @param {!Node} node
1003
+ * @returns {boolean}
1004
+ */
1005
+ function isDoc(node) {
1006
+ return node && node.nodeType === 9;
1007
+ }
1008
+
981
1009
 
982
1010
  // Exposes the constructors globally.
983
1011
  window.IntersectionObserver = IntersectionObserver;
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "intersection-observer",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
4
4
  "description": "A polyfill for IntersectionObserver",
5
- "main": "intersection-observer",
5
+ "main": "intersection-observer.js",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git@github.com:w3c/IntersectionObserver.git"