@breakside/jskit 2022.53.0 → 2023.3.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 (43) hide show
  1. package/Frameworks/DOM.jsframework/Info.json +2 -2
  2. package/Frameworks/DOM.jsframework/io.breakside.JSKit.DOM-bundle.js +2 -2
  3. package/Frameworks/FontKit.jsframework/Info.json +2 -2
  4. package/Frameworks/FontKit.jsframework/io.breakside.JSKit.FontKit-bundle.js +2 -2
  5. package/Frameworks/Foundation.jsframework/Info.json +2 -2
  6. package/Frameworks/Foundation.jsframework/io.breakside.JSKit.Foundation-bundle.js +2 -2
  7. package/Frameworks/SecurityKit.jsframework/Info.json +2 -2
  8. package/Frameworks/SecurityKit.jsframework/io.breakside.JSKit.SecurityKit-bundle.js +2 -2
  9. package/Info.json +2 -2
  10. package/Node/io.breakside.jskit-bundle.js +2 -2
  11. package/Root/Frameworks/APIKit/Info.yaml +1 -1
  12. package/Root/Frameworks/APIKitTesting/Info.yaml +1 -1
  13. package/Root/Frameworks/AuthKit/Info.yaml +1 -1
  14. package/Root/Frameworks/CSSOM/Info.yaml +1 -1
  15. package/Root/Frameworks/ChartKit/Info.yaml +1 -1
  16. package/Root/Frameworks/ConferenceKit/Info.yaml +1 -1
  17. package/Root/Frameworks/DBKit/Info.yaml +1 -1
  18. package/Root/Frameworks/DOM/Info.yaml +1 -1
  19. package/Root/Frameworks/Dispatch/Info.yaml +1 -1
  20. package/Root/Frameworks/FontKit/Info.yaml +1 -1
  21. package/Root/Frameworks/Foundation/Info.yaml +1 -1
  22. package/Root/Frameworks/ImageKit/Info.yaml +1 -1
  23. package/Root/Frameworks/MediaKit/Info.yaml +1 -1
  24. package/Root/Frameworks/MediaKitUI/Info.yaml +1 -1
  25. package/Root/Frameworks/NotificationKit/Info.yaml +1 -1
  26. package/Root/Frameworks/PDFKit/Info.yaml +1 -1
  27. package/Root/Frameworks/QRKit/Info.yaml +1 -1
  28. package/Root/Frameworks/SearchKit/Info.yaml +1 -1
  29. package/Root/Frameworks/SecurityKit/Info.yaml +1 -1
  30. package/Root/Frameworks/ServerKit/Info.yaml +1 -1
  31. package/Root/Frameworks/ServerKitTesting/Info.yaml +1 -1
  32. package/Root/Frameworks/TestKit/Info.yaml +1 -1
  33. package/Root/Frameworks/UIKit/Info.yaml +1 -1
  34. package/Root/Frameworks/UIKit/UICollectionReusableView.js +13 -0
  35. package/Root/Frameworks/UIKit/UICollectionView.js +620 -67
  36. package/Root/Frameworks/UIKit/UICollectionViewCell.js +39 -2
  37. package/Root/Frameworks/UIKit/UICollectionViewLayout.js +34 -3
  38. package/Root/Frameworks/UIKit/UIListView.js +46 -0
  39. package/Root/Frameworks/UIKit/UIListViewCell.js +33 -1
  40. package/Root/Frameworks/UIKit/UIView.js +15 -0
  41. package/Root/Frameworks/UIKit/UIWindow.js +3 -0
  42. package/Root/Frameworks/UIKitTesting/Info.yaml +1 -1
  43. package/package.json +1 -1
@@ -301,6 +301,10 @@ JSClass("UICollectionView", UIScrollView, {
301
301
  if (this._visibleElements.length === 0){
302
302
  return;
303
303
  }
304
+ if (this._edit !== null){
305
+ this._edit.reloadIndexPaths = this._edit.reloadIndexPaths.concat(indexPaths);
306
+ return;
307
+ }
304
308
 
305
309
  indexPaths = JSCopy(indexPaths);
306
310
  indexPaths.sort(function(a, b){
@@ -317,7 +321,7 @@ JSClass("UICollectionView", UIScrollView, {
317
321
 
318
322
  for (i = 0, l = indexPaths.length; i < l; ++i){
319
323
  indexPath = indexPaths[i];
320
- while (elementIndex < elementCount && !this._visibleElements[elementIndex].indexPath.isEqual(indexPath)){
324
+ while (elementIndex < elementCount && !this._visibleElements[elementIndex].attributes.indexPath.isEqual(indexPath)){
321
325
  ++elementIndex;
322
326
  }
323
327
  if (elementIndex < elementCount){
@@ -337,13 +341,26 @@ JSClass("UICollectionView", UIScrollView, {
337
341
  // --------------------------------------------------------------------
338
342
  // MARK: - Inserting and Deleting Rows
339
343
 
344
+ editAnimationDuration: 1.0/6,
345
+
340
346
  insertCellAtIndexPath: function(indexPath, animation){
341
347
  this.insertCellsAtIndexPaths([indexPath], animation);
342
348
  },
343
349
 
344
350
  insertCellsAtIndexPaths: function(indexPaths, animation){
345
- // TODO:
346
- this.reloadData();
351
+ if (animation === undefined || animation == UICollectionView.CellAnimation.default){
352
+ animation = UICollectionView.CellAnimation.scale;
353
+ }
354
+ this._beginEditIfNeeded(animation);
355
+ var i, l;
356
+ var indexPath;
357
+ for (i = 0, l = indexPaths.length; i < l; ++i){
358
+ indexPath = indexPaths[i];
359
+ this._edit.insertedIndexPaths.push({
360
+ indexPath: indexPath,
361
+ animation: animation
362
+ });
363
+ }
347
364
  },
348
365
 
349
366
  deleteCellAtIndexPath: function(indexPath, animation){
@@ -351,10 +368,19 @@ JSClass("UICollectionView", UIScrollView, {
351
368
  },
352
369
 
353
370
  deleteCellsAtIndexPaths: function(indexPaths, animation){
354
- // TODO:
355
- this.reloadData();
356
- if (this.selectedIndexPaths.length > 0){
357
- this._setSelectedIndexPaths([], {notifyDelegate: true});
371
+ if (animation === undefined || animation == UICollectionView.CellAnimation.default){
372
+ animation = UICollectionView.CellAnimation.scale;
373
+ }
374
+ this._beginEditIfNeeded(animation);
375
+ var i, l;
376
+ var indexPath;
377
+ for (i = 0, l = indexPaths.length; i < l; ++i){
378
+ indexPath = indexPaths[i];
379
+ this._edit.deletedIndexPaths.push({
380
+ indexPath: indexPath,
381
+ animation: animation
382
+ });
383
+ this._edit.didDeleteSelectedItem = this._edit.didDeleteSelectedItem || this._selectionContainsIndexPath(indexPath);
358
384
  }
359
385
  },
360
386
 
@@ -363,8 +389,19 @@ JSClass("UICollectionView", UIScrollView, {
363
389
  },
364
390
 
365
391
  insertSections: function(sections, animation){
366
- // TODO:
367
- this.reloadData();
392
+ if (animation === undefined || animation == UICollectionView.CellAnimation.default){
393
+ animation = UICollectionView.CellAnimation.scale;
394
+ }
395
+ this._beginEditIfNeeded(animation);
396
+ var i, l;
397
+ var section;
398
+ for (i = 0, l = sections.length; i < l; ++i){
399
+ section = sections[i];
400
+ this._edit.insertedSections.push({
401
+ section: section,
402
+ animation: animation
403
+ });
404
+ }
368
405
  },
369
406
 
370
407
  deleteSection: function(section, animation){
@@ -372,13 +409,48 @@ JSClass("UICollectionView", UIScrollView, {
372
409
  },
373
410
 
374
411
  deleteSections: function(sections, animation){
375
- // TODO:
376
- this.reloadData();
377
- if (this.selectedIndexPaths.length > 0){
378
- this._setSelectedIndexPaths([], {notifyDelegate: true});
412
+ if (animation === undefined || animation == UICollectionView.CellAnimation.default){
413
+ animation = UICollectionView.CellAnimation.scale;
414
+ }
415
+ this._beginEditIfNeeded(animation);
416
+ var i, l;
417
+ var j, k;
418
+ var section;
419
+ for (i = 0, l = sections.length; i < l; ++i){
420
+ section = sections[i];
421
+ this._edit.deletedSections.push({
422
+ section: section,
423
+ animation: animation
424
+ });
425
+ for (j = 0, k = this._selectedIndexPaths.length; j < k && !this._edit.didDeleteSelectedItem; ++j){
426
+ if (this._selectedIndexPaths[j].section === section){
427
+ this._edit.didDeleteSelectedItem = true;
428
+ }
429
+ }
379
430
  }
380
431
  },
381
432
 
433
+ _edit: null,
434
+
435
+ _beginEditIfNeeded: function(animation){
436
+ if (this._edit === null){
437
+ this._edit = {
438
+ insertedSections: [],
439
+ deletedSections: [],
440
+ insertedIndexPaths: [],
441
+ deletedIndexPaths: [],
442
+ animator: null,
443
+ selectionChanged: false,
444
+ didDeleteSelectedItem: false,
445
+ animated: false,
446
+ scroll: null,
447
+ reloadIndexPaths: [],
448
+ };
449
+ this.setNeedsLayout();
450
+ }
451
+ this._edit.animated = this._edit.animated || UICollectionView.CellAnimation.none && this._editAnimator === null;
452
+ },
453
+
382
454
  // -------------------------------------------------------------------------
383
455
  // MARK: - Layout
384
456
 
@@ -399,22 +471,40 @@ JSClass("UICollectionView", UIScrollView, {
399
471
 
400
472
  layoutSubviews: function(){
401
473
  UICollectionView.$super.layoutSubviews.call(this);
402
- var needsPrepare = this._needsReload || this._hasPreparedLayout;
403
474
  var i, l;
404
475
  if (this._needsReload){
476
+ // A full reload is requested
477
+ // - enqueue all visible elements for reuse
478
+ // - prepare the layout
479
+ // - clear any edit that may have be simultaneously requested
480
+ // - update container size
481
+ // - update visible elements
405
482
  for (i = 0, l = this._visibleElements.length; i < l; ++i){
406
483
  this._enqueueVisibleElement(this._visibleElements[i]);
407
484
  }
408
485
  this._visibleElements = [];
409
486
  this._needsReload = false;
410
- }
411
- if (needsPrepare){
487
+ this._edit = null;
412
488
  this.collectionViewLayout.prepare();
489
+ this._hasPreparedLayout = true;
413
490
  this.contentSize = this.collectionViewLayout.collectionViewContentSize;
491
+ this._elementsContainerView.untransformedFrame = JSRect(JSPoint.Zero, this.contentSize);
492
+ this._updateVisibleElements();
493
+ }else if (this._edit !== null){
494
+ // An edit is requested
495
+ this._updateVisibleElementsForEdit(this._edit);
496
+ this._edit = null;
497
+ }else if (this._hasPreparedLayout){
498
+ // If we've already prepared the layout once, we need to re-prepare
499
+ // - prepare the layout
500
+ // - update container size
501
+ // - update visible elements
502
+ this.collectionViewLayout.prepare();
414
503
  this._hasPreparedLayout = true;
504
+ this.contentSize = this.collectionViewLayout.collectionViewContentSize;
505
+ this._elementsContainerView.untransformedFrame = JSRect(JSPoint.Zero, this.contentSize);
506
+ this._updateVisibleElements();
415
507
  }
416
- this._elementsContainerView.untransformedFrame = JSRect(JSPoint.Zero, this.contentSize);
417
- this._updateVisibleElements();
418
508
  },
419
509
 
420
510
  _didScroll: function(){
@@ -478,6 +568,484 @@ JSClass("UICollectionView", UIScrollView, {
478
568
  this._removeQueuedViews();
479
569
  },
480
570
 
571
+ _editAnimator: null,
572
+
573
+ _updateVisibleElementsForEdit: function(edit){
574
+ // Prepare the edit
575
+ edit.deletedSections.sort(function(a, b){
576
+ return a.section - b.section;
577
+ });
578
+ edit.deletedIndexPaths.sort(function(a, b){
579
+ return a.indexPath.compare(b.indexPath);
580
+ });
581
+ edit.insertedSections.sort(function(a, b){
582
+ return a.section - b.section;
583
+ });
584
+ edit.insertedIndexPaths.sort(function(a, b){
585
+ return a.indexPath.compare(b.indexPath);
586
+ });
587
+
588
+ var deletedElements = [];
589
+ var deletedAnimations = [];
590
+ var invisibleElements = [];
591
+
592
+ var i, l;
593
+ var elementIndex;
594
+ var deletedSection;
595
+ var deletedIndexPath;
596
+ var elements = this._visibleElements;
597
+ var element;
598
+
599
+ // find deleted elements and remove from this._visibleElements
600
+ elementIndex = elements.length - 1;
601
+ for (i = edit.deletedSections.length - 1; i >= 0; --i){
602
+ deletedSection = edit.deletedSections[i];
603
+ while (elementIndex >= 0 && (elements[elementIndex].attributes.indexPath.length === 0 || elements[elementIndex].attributes.indexPath.section > deletedSection.section)){
604
+ --elementIndex;
605
+ }
606
+ while (elementIndex >= 0 && elements[elementIndex].attributes.indexPath.section === deletedSection.section){
607
+ element = elements[elementIndex];
608
+ deletedElements.push(element);
609
+ deletedAnimations.push(deletedSection.animation);
610
+ elements.splice(elementIndex, 1);
611
+ --elementIndex;
612
+ }
613
+ }
614
+ elementIndex = elements.length - 1;
615
+ for (i = edit.deletedIndexPaths.length - 1; i >= 0; --i){
616
+ deletedIndexPath = edit.deletedIndexPaths[i];
617
+ while (elementIndex >= 0 && (elements[elementIndex].attributes.elementCategory !== UICollectionView.ElementCategory.cell || elements[elementIndex].attributes.indexPath.isGreaterThan(deletedIndexPath.indexPath))){
618
+ --elementIndex;
619
+ }
620
+ if (elementIndex >= 0 && elements[elementIndex].attributes.indexPath.isEqual(deletedIndexPath.indexPath)){
621
+ element = elements[elementIndex];
622
+ deletedElements.push(element);
623
+ deletedAnimations.push(deletedIndexPath.animation);
624
+ elements.splice(elementIndex, 1);
625
+ --elementIndex;
626
+ }
627
+ }
628
+
629
+ var visibleRect;
630
+ var dy;
631
+ var attrs;
632
+
633
+ var previousElements = [];
634
+ var previoiusElementsMap = {};
635
+ if (deletedElements.length > 0){
636
+ // We're deleting at least one element, which means we may animate
637
+ // into view a previously invisble element. We'd like it to animate
638
+ // from it's previous position, which is not currently recorded.
639
+ // So, before preparing the new layout, we'll grab attributes for
640
+ // about a page worth of elements before and after the currently
641
+ // visible elements.
642
+ //
643
+ // Note: when deleting a large number of elements, this expanded
644
+ // attribute query won't be enough. For now, we'll say "oh well".
645
+ //
646
+ // Possible alternate solution: require layout implementations to
647
+ // be copyable so we can make a copy of the previously prepared
648
+ // layout and query individually if needed
649
+ visibleRect = JSRect(this.contentView.bounds);
650
+ dy = Math.min(visibleRect.origin.y, visibleRect.size.height);
651
+ visibleRect.origin.y -= dy;
652
+ visibleRect.size.height += dy + visibleRect.size.height;
653
+ attrs = this.collectionViewLayout.layoutAttributesForElementsInRect(visibleRect);
654
+ for (i = 0, l = attrs.length; i < l; ++i){
655
+ element = VisibleElement(attrs[i].copy());
656
+ previousElements.push(element);
657
+ }
658
+ // remove deleted items from previousElements
659
+ elementIndex = previousElements.length - 1;
660
+ for (i = edit.deletedSections.length - 1; i >= 0; --i){
661
+ deletedSection = edit.deletedSections[i];
662
+ while (elementIndex >= 0 && (previousElements[elementIndex].attributes.indexPath.length === 0 || previousElements[elementIndex].attributes.indexPath.section > deletedSection.section)){
663
+ --elementIndex;
664
+ }
665
+ while (elementIndex >= 0 && previousElements[elementIndex].attributes.indexPath.section === deletedSection.section){
666
+ element = previousElements[elementIndex];
667
+ previousElements.splice(elementIndex, 1);
668
+ --elementIndex;
669
+ }
670
+ }
671
+ elementIndex = previousElements.length - 1;
672
+ for (i = edit.deletedIndexPaths.length - 1; i >= 0; --i){
673
+ deletedIndexPath = edit.deletedIndexPaths[i];
674
+ while (elementIndex >= 0 && (previousElements[elementIndex].attributes.elementCategory !== UICollectionView.ElementCategory.cell || previousElements[elementIndex].attributes.indexPath.isGreaterThan(deletedIndexPath.indexPath))){
675
+ --elementIndex;
676
+ }
677
+ if (elementIndex >= 0 && previousElements[elementIndex].attributes.indexPath.isEqual(deletedIndexPath.indexPath)){
678
+ element = previousElements[elementIndex];
679
+ previousElements.splice(elementIndex, 1);
680
+ --elementIndex;
681
+ }
682
+ }
683
+ this._updateVisibleElementIndexPathsForEdit(previousElements, edit);
684
+ for (i = 0, l = previousElements.length; i < l; ++i){
685
+ element = previousElements[i];
686
+ previoiusElementsMap[element.attributes.elementIdentifier] = element;
687
+ }
688
+ }
689
+
690
+ this.collectionViewLayout.prepare();
691
+ this._hasPreparedLayout = true;
692
+
693
+ // Update index paths of remaining visible elements
694
+ this._updateVisibleElementIndexPathsForEdit(elements, edit);
695
+ this._updateSelectedIndexPathsForEdit(edit);
696
+ if (edit.didDeleteSelectedItem){
697
+ this._updateSelectedIndexPaths({notifyDelegate: true});
698
+ }
699
+
700
+ // Try to stay in the same place visually by adjusting the content offset
701
+ var contentOffset = JSPoint(this.contentOffset);
702
+ if (elements.length > 0){
703
+ // If we're at the top, stay there. Otherwise, keep the fist element in view
704
+ if (contentOffset.y > 0){
705
+ element = elements[0];
706
+ attrs = this.collectionViewLayout.layoutAttributesForElement(element);
707
+ contentOffset.y += attrs.frame.origin.y - element.attributes.frame.origin.y;
708
+ }
709
+ }
710
+ // sanitize the offset as UIScrollView will
711
+ var minContentOffset = JSPoint(-this.contentInsets.left, -this.contentInsets.top);
712
+ var maxContentOffset = JSPoint(
713
+ Math.max(minContentOffset.x, this.collectionViewLayout.collectionViewContentSize.width + this.contentInsets.right - this.contentView.bounds.size.width),
714
+ Math.max(minContentOffset.y, this.collectionViewLayout.collectionViewContentSize.height + this.contentInsets.bottom - this.contentView.bounds.size.height)
715
+ );
716
+ contentOffset.x = Math.round(contentOffset.x);
717
+ contentOffset.y = Math.round(contentOffset.y);
718
+ if (contentOffset.x < minContentOffset.x){
719
+ contentOffset.x = minContentOffset.x;
720
+ }else if (contentOffset.x > maxContentOffset.x){
721
+ contentOffset.x = maxContentOffset.x;
722
+ }
723
+ if (contentOffset.y < minContentOffset.y){
724
+ contentOffset.y = minContentOffset.y;
725
+ }else if (contentOffset.y > maxContentOffset.y){
726
+ contentOffset.y = maxContentOffset.y;
727
+ }
728
+
729
+ // Get the attributes for the elements that should be visible in the post-edit view
730
+ visibleRect = JSRect(contentOffset, this.contentView.bounds.size);
731
+
732
+ // Check if a scroll is requested
733
+ var rect;
734
+ if (edit.scroll !== null){
735
+ rect = this.rectForCellAtIndexPath(edit.scroll.indexPath);
736
+ // If we can't handle it, just do a full reload
737
+ if (edit.scroll.position !== UIScrollView.ScrollPosition.auto || !visibleRect.intersectsRect(rect)){
738
+ // bail the edit and just do a regular reload
739
+ for (i = 0, l = deletedElements.length; i < l; ++i){
740
+ this._enqueueVisibleElement(deletedElements[i]);
741
+ }
742
+ for (i = 0, l = elements.length; i < l; ++i){
743
+ this._enqueueVisibleElement(elements[i]);
744
+ }
745
+ this._visibleElements = [];
746
+ this.contentSize = this.collectionViewLayout.collectionViewContentSize;
747
+ this._elementsContainerView.untransformedFrame = JSRect(JSPoint.Zero, this.contentSize);
748
+ this.scrollToRect(rect, edit.scroll.position);
749
+ this._updateVisibleElements();
750
+ return;
751
+ }
752
+ }
753
+
754
+
755
+ var visibleElementMap = {};
756
+ var attributes = this.collectionViewLayout.layoutAttributesForElementsInRect(visibleRect);
757
+ for (i = 0, l = attributes.length; i < l; ++i){
758
+ attrs = attributes[i];
759
+ visibleElementMap[attrs.elementIdentifier] = null;
760
+ }
761
+
762
+ // find any elements that will become invisible and remove them from this._visibleElements,
763
+ // but don't queue them for reuse yet because they need to animate out of view
764
+ for (elementIndex = elements.length - 1; elementIndex >= 0; --elementIndex){
765
+ element = elements[elementIndex];
766
+ if (element.attributes.elementIdentifier in visibleElementMap){
767
+ visibleElementMap[element.attributes.elementIdentifier] = element;
768
+ }else{
769
+ element.attributes = this.collectionViewLayout.layoutAttributesForElement(element);
770
+ invisibleElements.push(element);
771
+ elements.splice(elementIndex, 1);
772
+ }
773
+ }
774
+
775
+ // Create elements that are newly visible
776
+ var insertedSection;
777
+ var insertedIndexPath;
778
+ var insertedSectionSearcher = JSBinarySearcher(edit.insertedSections, function(a, b){
779
+ return a.section - b.section;
780
+ });
781
+ var insertedIndexPathSearcher = JSBinarySearcher(edit.insertedIndexPaths, function(a, b){
782
+ return a.indexPath.compare(b.indexPath);
783
+ });
784
+ var animation;
785
+ elementIndex = 0;
786
+ var insertedElements = [];
787
+ var insertedAnimations = [];
788
+ for (i = 0, l = attributes.length; i < l; ++i){
789
+ attrs = attributes[i];
790
+ element = visibleElementMap[attrs.elementIdentifier];
791
+ if (element === null){
792
+ element = VisibleElement(attrs);
793
+ this._createViewForVisibleElement(element);
794
+ elements.splice(elementIndex, 0, element);
795
+ if (elementIndex < elements.length - 1){
796
+ this._elementsContainerView.insertSubviewBelowSibling(element.view, elements[elementIndex + 1].view);
797
+ }else{
798
+ this._elementsContainerView.addSubview(element.view);
799
+ }
800
+ insertedSection = insertedSectionSearcher.itemMatchingValue({section: attrs.indexPath.section});
801
+ insertedIndexPath = insertedIndexPathSearcher.itemMatchingValue({indexPath: attrs.indexPath});
802
+ if (insertedSection !== null || insertedIndexPath !== null){
803
+ if (insertedSection !== null){
804
+ animation = insertedSection.animation;
805
+ }else{
806
+ animation = insertedIndexPath.animation;
807
+ }
808
+ insertedElements.push(element);
809
+ insertedAnimations.push(animation);
810
+ }else{
811
+ // This was a previously invisible item.
812
+ // Ideally we'd animate it from its previous position,
813
+ // but we if don't have that information, so we'll just fade
814
+ // it in.
815
+ if (element.attributes.elementIdentifier in previoiusElementsMap){
816
+ element.attributes = previoiusElementsMap[element.attributes.elementIdentifier].attributes;
817
+ element.view.applyAttributes(element.attributes);
818
+ element.attributes = attrs;
819
+ }else{
820
+ element.attributes = attrs;
821
+ element.view.applyAttributes(attrs);
822
+ element.view.alpha = 0;
823
+ }
824
+ }
825
+ }else{
826
+ element.attributes = attrs;
827
+ }
828
+ ++elementIndex;
829
+ }
830
+
831
+ // Order deleted views behind all others
832
+ for (i = 0, l = deletedElements.length; i < l; ++i){
833
+ element = deletedElements[i];
834
+ element.view.superview.insertSubviewAtIndex(element.view, i);
835
+ }
836
+
837
+ // calculate transforms for deleted elements
838
+ var deletedPath = JSPath.init();
839
+ for (i = 0, l = deletedElements.length; i < l; ++i){
840
+ element = deletedElements[i];
841
+ animation = deletedAnimations[i];
842
+ if (animation === UICollectionView.CellAnimation.left || animation === UICollectionView.CellAnimation.right){
843
+ deletedPath.addRect(element.attributes.frame);
844
+ }
845
+ }
846
+
847
+ // calculate & apply transforms to inserted elements
848
+ var insertedPath = JSPath.init();
849
+ for (i = 0, l = insertedElements.length; i < l; ++i){
850
+ element = insertedElements[i];
851
+ animation = insertedAnimations[i];
852
+ if (animation === UICollectionView.CellAnimation.left || animation === UICollectionView.CellAnimation.right){
853
+ insertedPath.addRect(element.attributes.frame);
854
+ }
855
+ }
856
+ for (i = 0, l = insertedElements.length; i < l; ++i){
857
+ element = insertedElements[i];
858
+ animation = insertedAnimations[i];
859
+ element.view.alpha = 0;
860
+ if (animation === UICollectionView.CellAnimation.scale){
861
+ element.view.transform = JSAffineTransform.Scaled(0.1);
862
+ }else if (animation === UICollectionView.CellAnimation.left){
863
+ element.view.transform = JSAffineTransform.Translated(-insertedPath.boundingRect.size.width, 0);
864
+ }else if (animation === UICollectionView.CellAnimation.right){
865
+ element.view.transform = JSAffineTransform.Translated(insertedPath.boundingRect.size.width, 0);
866
+ }
867
+ }
868
+
869
+ var animations = function(){
870
+ this.contentSize = this.collectionViewLayout.collectionViewContentSize;
871
+ this.contentOffset = contentOffset;
872
+ this._elementsContainerView.untransformedFrame = JSRect(JSPoint.Zero, this.contentSize);
873
+ var i, l;
874
+ var element;
875
+ var animation;
876
+ // move visible items to new place
877
+ for (i = 0, l = this._visibleElements.length; i < l; ++i){
878
+ element = this._visibleElements[i];
879
+ element.view.alpha = 1;
880
+ element.view.applyAttributes(element.attributes);
881
+ }
882
+ // move invisible elements to new postion (out of view)
883
+ for (i = 0, l = invisibleElements.length; i < l; ++i){
884
+ element = invisibleElements[i];
885
+ element.view.applyAttributes(element.attributes);
886
+ }
887
+ // animate out deleted elements according to requested animation
888
+ for (i = 0, l = deletedElements.length; i < l; ++i){
889
+ element = deletedElements[i];
890
+ animation = deletedAnimations[i];
891
+ element.view.alpha = 0;
892
+ if (animation === UICollectionView.CellAnimation.scale){
893
+ element.view.transform = JSAffineTransform.Scaled(0.1);
894
+ }else if (animation === UICollectionView.CellAnimation.left){
895
+ element.view.transform = JSAffineTransform.Translated(-deletedPath.boundingRect.size.width, 0);
896
+ }else if (animation === UICollectionView.CellAnimation.right){
897
+ element.view.transform = JSAffineTransform.Translated(deletedPath.boundingRect.size.width, 0);
898
+ }
899
+ }
900
+ };
901
+ var completeAnimations = function(){
902
+ var i, l;
903
+ var element;
904
+ for (i = 0, l = invisibleElements.length; i < l; ++i){
905
+ element = invisibleElements[i];
906
+ element.view.alpha = 1;
907
+ this._enqueueVisibleElement(element);
908
+ }
909
+ for (i = 0, l = deletedElements.length; i < l; ++i){
910
+ element = deletedElements[i];
911
+ element.view.alpha = 1;
912
+ this._enqueueVisibleElement(element);
913
+ }
914
+ this._removeQueuedCells();
915
+ this._removeQueuedViews();
916
+ if (edit.reloadIndexPaths.length > 0){
917
+ this.reloadCellsAtIndexPaths(edit.reloadIndexPaths);
918
+ }
919
+ };
920
+ if (edit.animated !== null){
921
+ if (this._editAnimator !== null){
922
+ this._editAnimator.stopAndCallCompletions();
923
+ }
924
+ this._editAnimator = UIViewPropertyAnimator.initWithDuration(this.editAnimationDuration);
925
+ this._editAnimator.addAnimations(animations, this);
926
+ this._editAnimator.addCompletion(completeAnimations, this);
927
+ this._editAnimator.addCompletion(function(){
928
+ this._editAnimator = null;
929
+ }, this);
930
+ this._editAnimator.start();
931
+ }else{
932
+ animations.call(this);
933
+ completeAnimations.call(this);
934
+ }
935
+ },
936
+
937
+ _updateVisibleElementIndexPathsForEdit: function(elements, edit){
938
+ var elementCount = elements.length;
939
+ var elementIndex = 0;
940
+ var i, l;
941
+ var j;
942
+ var indexPath;
943
+ var info;
944
+ var element;
945
+
946
+ // account for deleted index paths
947
+ elementIndex = elements.length - 1;
948
+ for (i = edit.deletedIndexPaths.length - 1; i >= 0; --i){
949
+ info = edit.deletedIndexPaths[i];
950
+ while (elementIndex >= 0 && (elements[elementIndex].attributes.elementCategory !== UICollectionView.ElementCategory.cell || elements[elementIndex].attributes.indexPath.isGreaterThan(info.indexPath))){
951
+ --elementIndex;
952
+ }
953
+ for (j = elementIndex + 1; j < elementCount && (elements[j].attributes.elementCategory !== UICollectionView.ElementCategory.cell || elements[j].attributes.indexPath.section === info.indexPath.section); ++j){
954
+ if (elements[j].attributes.elementCategory === UICollectionView.ElementCategory.cell){
955
+ elements[j].attributes.indexPath.row -= 1;
956
+ }
957
+ }
958
+ }
959
+
960
+ // account for deleted sections
961
+ elementIndex = elements.length - 1;
962
+ for (i = edit.deletedSections.length - 1; i >= 0; --i){
963
+ info = edit.deletedSections[i];
964
+ while (elementIndex >= 0 && (elements[elementIndex].attributes.indexPath.length === 0 || elements[elementIndex].attributes.indexPath.section > info.section)){
965
+ --elementIndex;
966
+ }
967
+ for (j = elementIndex + 1; j < elementCount; ++j){
968
+ elements[j].attributes.indexPath.section -= 1;
969
+ }
970
+ }
971
+
972
+ // account for inserted sections
973
+ elementIndex = 0;
974
+ for (i = 0, l = edit.insertedSections.length; i < l; ++i){
975
+ info = edit.insertedSections[i];
976
+ while (elementIndex < elementCount && (elements[elementIndex].attributes.indexPath.length === 0 || elements[elementIndex].attributes.indexPath.section < info.section)){
977
+ ++elementIndex;
978
+ }
979
+ for (j = elementIndex; j < elementCount; ++j){
980
+ elements[j].attributes.indexPath.section += 1;
981
+ }
982
+ }
983
+
984
+ // account for inserted index paths
985
+ elementIndex = 0;
986
+ for (i = 0, l = edit.insertedIndexPaths.length; i < l; ++i){
987
+ info = edit.insertedIndexPaths[i];
988
+ while (elementIndex < elementCount && (elements[elementIndex].attributes.elementCategory !== UICollectionView.ElementCategory.cell || elements[elementIndex].attributes.indexPath.isLessThan(info.indexPath))){
989
+ ++elementIndex;
990
+ }
991
+ for (j = elementIndex; j < elementCount && (elements[j].attributes.elementCategory !== UICollectionView.ElementCategory.cell || elements[j].attributes.indexPath.section === info.indexPath.section); ++j){
992
+ if (elements[j].attributes.elementCategory === UICollectionView.ElementCategory.cell){
993
+ elements[j].attributes.indexPath.row += 1;
994
+ }
995
+ }
996
+ }
997
+ },
998
+
999
+ _updateSelectedIndexPathsForEdit: function(edit){
1000
+ if (edit.selectionChanged){
1001
+ this._updateVisibleCellStates();
1002
+ return;
1003
+ }
1004
+ var i, l;
1005
+ var section, indexPath;
1006
+ var index;
1007
+ var searcher = JSBinarySearcher(this._selectedIndexPaths, JSIndexPath.compare);
1008
+ var parent;
1009
+ var selectedLength = this._selectedIndexPaths.length;
1010
+ for (i = edit.deletedIndexPaths.length - 1; i >= 0; --i){
1011
+ indexPath = edit.deletedIndexPaths[i].indexPath;
1012
+ parent = indexPath.removingLastIndex();
1013
+ index = searcher.insertionIndexForValue(indexPath);
1014
+ if (index < selectedLength && this._selectedIndexPaths[index].isEqual(indexPath)){
1015
+ this._selectedIndexPaths.splice(index, 1);
1016
+ --selectedLength;
1017
+ }
1018
+ for (; index < selectedLength && this._selectedIndexPaths[index].startsWith(parent); ++index){
1019
+ this._selectedIndexPaths[index][parent.length]--;
1020
+ }
1021
+ }
1022
+ for (i = edit.deletedSections.length - 1; i >= 0; --i){
1023
+ section = edit.deletedSections[i].section;
1024
+ index = searcher.insertionIndexForValue(JSIndexPath(section, 0));
1025
+ for (; index < selectedLength && this._selectedIndexPaths[index].section == section; --selectedLength){
1026
+ this._selectedIndexPaths.splice(index, 1);
1027
+ }
1028
+ for (; index < selectedLength; ++index){
1029
+ this._selectedIndexPaths[index].section--;
1030
+ }
1031
+ }
1032
+ for (i = 0, l = edit.insertedSections.length; i < l; ++i){
1033
+ section = edit.insertedSections[i].section;
1034
+ index = searcher.insertionIndexForValue(JSIndexPath(section, 0));
1035
+ for (; index < selectedLength && this._selectedIndexPaths[index].section >= section; ++index){
1036
+ this._selectedIndexPaths[index].section++;
1037
+ }
1038
+ }
1039
+ for (i = 0, l = edit.insertedIndexPaths.length; i < l; ++i){
1040
+ indexPath = edit.insertedIndexPaths[i].indexPath;
1041
+ parent = indexPath.removingLastIndex();
1042
+ index = searcher.insertionIndexForValue(indexPath);
1043
+ for (; index < selectedLength && this._selectedIndexPaths[index].startsWith(parent); ++index){
1044
+ this._selectedIndexPaths[index][parent.length]++;
1045
+ }
1046
+ }
1047
+ },
1048
+
481
1049
  _createViewForVisibleElement: function(element){
482
1050
  if (element.attributes.elementCategory === UICollectionView.ElementCategory.cell){
483
1051
  element.view = this._createCellWithAttributes(element.attributes);
@@ -494,23 +1062,15 @@ JSClass("UICollectionView", UIScrollView, {
494
1062
  if (cell === null || cell === undefined){
495
1063
  throw new Error("%s.cellForCollectionViewAtIndexPath() returned null/undefined cell for indexPath: %s".sprintf(this.dataSource.$class.className, attributes.indexPath));
496
1064
  }
497
- this._adoptCell(cell, attributes);
1065
+ cell.collectionView = this;
498
1066
  cell.applyAttributes(attributes);
499
- cell.active = false;
1067
+ cell.prepareForReuse();
500
1068
  this._updateCellState(cell);
501
1069
  cell.update();
502
1070
  cell.setNeedsLayout();
503
1071
  return cell;
504
1072
  },
505
1073
 
506
- _adoptCell: function(cell, attributes){
507
- cell.collectionView = this;
508
- cell.indexPath = JSIndexPath(attributes.indexPath);
509
- cell.accessibilityRowIndex = attributes.rowIndex;
510
- cell.accessibilityColumnIndex = attributes.columnIndex;
511
- cell.postAccessibilityElementChangedNotification();
512
- },
513
-
514
1074
  _createSupplimentaryViewWithAttributes: function(attributes){
515
1075
  if (!this.dataSource.supplimentaryViewForCollectionViewAtIndexPath){
516
1076
  throw new Error("%s must implement supplimentaryViewForCollectionViewAtIndexPath()".sprintf(this.dataSource.$class.className));
@@ -521,6 +1081,7 @@ JSClass("UICollectionView", UIScrollView, {
521
1081
  }
522
1082
  view.collectionView = this;
523
1083
  view.applyAttributes(attributes);
1084
+ view.prepareForReuse();
524
1085
  view.setNeedsLayout();
525
1086
  return view;
526
1087
  },
@@ -608,6 +1169,9 @@ JSClass("UICollectionView", UIScrollView, {
608
1169
  _setSelectedIndexPaths: function(selectedIndexPaths, options){
609
1170
  var indexPaths = JSCopy(selectedIndexPaths);
610
1171
  indexPaths.sort(JSIndexPath.compare);
1172
+ if (this._edit !== null){
1173
+ this._edit.selectionChanged = true;
1174
+ }
611
1175
  if (indexPaths.length === this._selectedIndexPaths.length){
612
1176
  for (var i = 0, l = this._selectedIndexPaths.length; i < l; ++i){
613
1177
  if (!indexPaths[i].isEqual(this._selectedIndexPaths[i])){
@@ -622,10 +1186,6 @@ JSClass("UICollectionView", UIScrollView, {
622
1186
  return;
623
1187
  }
624
1188
  this._selectedIndexPaths = indexPaths;
625
- // TODO: editing
626
- // if (this._edit !== null){
627
- // this._edit.selectionChanged = true;
628
- // }
629
1189
  this._updateSelectedIndexPaths(options);
630
1190
  },
631
1191
 
@@ -1148,13 +1708,12 @@ JSClass("UICollectionView", UIScrollView, {
1148
1708
  if (!this.containsPoint(location)){
1149
1709
  return null;
1150
1710
  }
1151
- // While a binary search over the visible items would be a bit faster,
1152
- // the list may contain deleted items that should not be considered
1153
- var locationInContainer = this.convertPointToView(location, this._elementsContainerView);
1154
1711
  var element;
1712
+ var locationInSubview;
1155
1713
  for (var i = 0, l = this._visibleElements.length; i < l; ++i){
1156
1714
  element = this._visibleElements[i];
1157
- if (element.attributes.elementCategory === UICollectionView.ElementCategory.cell && element.state !== UICollectionView.VisibleElementState.deleted && element.view.frame.containsPoint(locationInContainer)){
1715
+ locationInSubview = this.convertPointToView(location, element.view);
1716
+ if (element.attributes.elementCategory === UICollectionView.ElementCategory.cell && element.view.containsPoint(locationInSubview)){
1158
1717
  return element.view;
1159
1718
  }
1160
1719
  }
@@ -1162,12 +1721,10 @@ JSClass("UICollectionView", UIScrollView, {
1162
1721
  },
1163
1722
 
1164
1723
  cellAtIndexPath: function(indexPath){
1165
- // While a binary search over the visible items would be a bit faster,
1166
- // the list may contain deleted items that should not be considered
1167
1724
  var element;
1168
1725
  for (var i = 0, l = this._visibleElements.length; i < l; ++i){
1169
1726
  element = this._visibleElements[i];
1170
- if (element.attributes.elementCategory === UICollectionView.ElementCategory.cell && element.state !== UICollectionView.VisibleElementState.deleted && element.view.indexPath.isEqual(indexPath)){
1727
+ if (element.attributes.elementCategory === UICollectionView.ElementCategory.cell && element.view.indexPath.isEqual(indexPath)){
1171
1728
  return element.view;
1172
1729
  }
1173
1730
  }
@@ -1183,11 +1740,11 @@ JSClass("UICollectionView", UIScrollView, {
1183
1740
 
1184
1741
  getVisibleIndexPaths: function(){
1185
1742
  var indexPaths = [];
1186
- var item;
1743
+ var element;
1187
1744
  for (var i = 0, l = this._visibleElements.length; i < l; ++i){
1188
- item = this._visibleElements[i];
1189
- if (item.attributes.elementCategory === UICollectionView.ElementCategory.cell){
1190
- indexPaths.push(item.indexPath);
1745
+ element = this._visibleElements[i];
1746
+ if (element.attributes.elementCategory === UICollectionView.ElementCategory.cell){
1747
+ indexPaths.push(element.attributes.indexPath);
1191
1748
  }
1192
1749
  }
1193
1750
  return indexPaths;
@@ -1197,11 +1754,11 @@ JSClass("UICollectionView", UIScrollView, {
1197
1754
 
1198
1755
  getVisibleCells: function(){
1199
1756
  var cells = [];
1200
- var item;
1757
+ var element;
1201
1758
  for (var i = 0, l = this._visibleElements.length; i < l; ++i){
1202
- item = this._visibleElements[i];
1203
- if (item.attributes.elementCategory === UICollectionView.ElementCategory.cell){
1204
- cells.push(item.view);
1759
+ element = this._visibleElements[i];
1760
+ if (element.attributes.elementCategory === UICollectionView.ElementCategory.cell){
1761
+ cells.push(element.view);
1205
1762
  }
1206
1763
  }
1207
1764
  return cells;
@@ -1211,13 +1768,17 @@ JSClass("UICollectionView", UIScrollView, {
1211
1768
  // MARK: - Scrolling
1212
1769
 
1213
1770
  scrollToCellAtIndexPath: function(indexPath, position){
1214
- this.layoutIfNeeded();
1215
1771
  if (position === undefined){
1216
1772
  position = UICollectionView.ScrollPosition.auto;
1217
1773
  }
1218
- var rect = this.rectForCellAtIndexPath(indexPath);
1219
- if (rect !== null){
1220
- this.scrollToRect(rect, position);
1774
+ if (this._edit !== null){
1775
+ this._edit.scroll = {indexPath: JSIndexPath(indexPath), position: position};
1776
+ }else{
1777
+ this.layoutIfNeeded();
1778
+ var rect = this.rectForCellAtIndexPath(indexPath);
1779
+ if (rect !== null){
1780
+ this.scrollToRect(rect, position);
1781
+ }
1221
1782
  }
1222
1783
  },
1223
1784
 
@@ -1246,6 +1807,13 @@ JSClass("UICollectionView", UIScrollView, {
1246
1807
 
1247
1808
  });
1248
1809
 
1810
+ UICollectionView.CellAnimation = {
1811
+ none: 0,
1812
+ default: 1,
1813
+ scale: 2,
1814
+ left: 3,
1815
+ right: 4
1816
+ };
1249
1817
 
1250
1818
  UICollectionView.Styler = Object.create({}, {
1251
1819
  default: {
@@ -1311,27 +1879,12 @@ var VisibleElement = function(attributes){
1311
1879
  return new VisibleElement(attributes);
1312
1880
  }
1313
1881
  this.attributes = attributes;
1314
- this.indexPath = JSIndexPath(attributes.indexPath);
1315
- this.state = UICollectionView.VisibleElementState.normal;
1316
1882
  };
1317
1883
 
1318
1884
  VisibleElement.prototype = Object.create(Function.prototype, {
1319
1885
 
1320
- indexPath: { value: null, writable: true },
1321
- rect: { value: null, writable: true, configurable: true },
1322
- kind: { value: null, writable: true },
1886
+ attributes: {value: null, writable: true},
1323
1887
  view: { value: null, writable: true },
1324
- state: { value: null, writable: true },
1325
- animation: { value: null, writable: true },
1326
-
1327
- updateViewIdentity: {
1328
- value: function(){
1329
- if (this.state === UICollectionView.VisibleElementState.deleting){
1330
- return;
1331
- }
1332
- this.view.indexPath = JSIndexPath(this.indexPath);
1333
- }
1334
- }
1335
1888
 
1336
1889
  });
1337
1890