UNPKG

11.4 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', { value: true });
4
5const index = require('./index-a0a08b2a.js');
6const ionicGlobal = require('./ionic-global-06f21c1a.js');
7const index$1 = require('./index-e1bb33c3.js');
8
9const infiniteScrollCss = "ion-infinite-scroll{display:none;width:100%}.infinite-scroll-enabled{display:block}";
10
11const InfiniteScroll = class {
12 constructor(hostRef) {
13 index.registerInstance(this, hostRef);
14 this.ionInfinite = index.createEvent(this, "ionInfinite", 7);
15 this.thrPx = 0;
16 this.thrPc = 0;
17 this.didFire = false;
18 this.isBusy = false;
19 this.isLoading = false;
20 /**
21 * The threshold distance from the bottom
22 * of the content to call the `infinite` output event when scrolled.
23 * The threshold value can be either a percent, or
24 * in pixels. For example, use the value of `10%` for the `infinite`
25 * output event to get called when the user has scrolled 10%
26 * from the bottom of the page. Use the value `100px` when the
27 * scroll is within 100 pixels from the bottom of the page.
28 */
29 this.threshold = '15%';
30 /**
31 * If `true`, the infinite scroll will be hidden and scroll event listeners
32 * will be removed.
33 *
34 * Set this to true to disable the infinite scroll from actively
35 * trying to receive new data while scrolling. This is useful
36 * when it is known that there is no more data that can be added, and
37 * the infinite scroll is no longer needed.
38 */
39 this.disabled = false;
40 /**
41 * The position of the infinite scroll element.
42 * The value can be either `top` or `bottom`.
43 */
44 this.position = 'bottom';
45 this.onScroll = () => {
46 const scrollEl = this.scrollEl;
47 if (!scrollEl || !this.canStart()) {
48 return 1;
49 }
50 const infiniteHeight = this.el.offsetHeight;
51 if (infiniteHeight === 0) {
52 // if there is no height of this element then do nothing
53 return 2;
54 }
55 const scrollTop = scrollEl.scrollTop;
56 const scrollHeight = scrollEl.scrollHeight;
57 const height = scrollEl.offsetHeight;
58 const threshold = this.thrPc !== 0 ? (height * this.thrPc) : this.thrPx;
59 const distanceFromInfinite = (this.position === 'bottom')
60 ? scrollHeight - infiniteHeight - scrollTop - threshold - height
61 : scrollTop - infiniteHeight - threshold;
62 if (distanceFromInfinite < 0) {
63 if (!this.didFire) {
64 this.isLoading = true;
65 this.didFire = true;
66 this.ionInfinite.emit();
67 return 3;
68 }
69 }
70 else {
71 this.didFire = false;
72 }
73 return 4;
74 };
75 }
76 thresholdChanged() {
77 const val = this.threshold;
78 if (val.lastIndexOf('%') > -1) {
79 this.thrPx = 0;
80 this.thrPc = (parseFloat(val) / 100);
81 }
82 else {
83 this.thrPx = parseFloat(val);
84 this.thrPc = 0;
85 }
86 }
87 disabledChanged() {
88 const disabled = this.disabled;
89 if (disabled) {
90 this.isLoading = false;
91 this.isBusy = false;
92 }
93 this.enableScrollEvents(!disabled);
94 }
95 async connectedCallback() {
96 const contentEl = this.el.closest('ion-content');
97 if (!contentEl) {
98 console.error('<ion-infinite-scroll> must be used inside an <ion-content>');
99 return;
100 }
101 this.scrollEl = await contentEl.getScrollElement();
102 this.thresholdChanged();
103 this.disabledChanged();
104 if (this.position === 'top') {
105 index.writeTask(() => {
106 if (this.scrollEl) {
107 this.scrollEl.scrollTop = this.scrollEl.scrollHeight - this.scrollEl.clientHeight;
108 }
109 });
110 }
111 }
112 disconnectedCallback() {
113 this.enableScrollEvents(false);
114 this.scrollEl = undefined;
115 }
116 /**
117 * Call `complete()` within the `ionInfinite` output event handler when
118 * your async operation has completed. For example, the `loading`
119 * state is while the app is performing an asynchronous operation,
120 * such as receiving more data from an AJAX request to add more items
121 * to a data list. Once the data has been received and UI updated, you
122 * then call this method to signify that the loading has completed.
123 * This method will change the infinite scroll's state from `loading`
124 * to `enabled`.
125 */
126 async complete() {
127 const scrollEl = this.scrollEl;
128 if (!this.isLoading || !scrollEl) {
129 return;
130 }
131 this.isLoading = false;
132 if (this.position === 'top') {
133 /**
134 * New content is being added at the top, but the scrollTop position stays the same,
135 * which causes a scroll jump visually. This algorithm makes sure to prevent this.
136 * (Frame 1)
137 * - complete() is called, but the UI hasn't had time to update yet.
138 * - Save the current content dimensions.
139 * - Wait for the next frame using _dom.read, so the UI will be updated.
140 * (Frame 2)
141 * - Read the new content dimensions.
142 * - Calculate the height difference and the new scroll position.
143 * - Delay the scroll position change until other possible dom reads are done using _dom.write to be performant.
144 * (Still frame 2, if I'm correct)
145 * - Change the scroll position (= visually maintain the scroll position).
146 * - Change the state to re-enable the InfiniteScroll.
147 * - This should be after changing the scroll position, or it could
148 * cause the InfiniteScroll to be triggered again immediately.
149 * (Frame 3)
150 * Done.
151 */
152 this.isBusy = true;
153 // ******** DOM READ ****************
154 // Save the current content dimensions before the UI updates
155 const prev = scrollEl.scrollHeight - scrollEl.scrollTop;
156 // ******** DOM READ ****************
157 requestAnimationFrame(() => {
158 index.readTask(() => {
159 // UI has updated, save the new content dimensions
160 const scrollHeight = scrollEl.scrollHeight;
161 // New content was added on top, so the scroll position should be changed immediately to prevent it from jumping around
162 const newScrollTop = scrollHeight - prev;
163 // ******** DOM WRITE ****************
164 requestAnimationFrame(() => {
165 index.writeTask(() => {
166 scrollEl.scrollTop = newScrollTop;
167 this.isBusy = false;
168 });
169 });
170 });
171 });
172 }
173 }
174 canStart() {
175 return (!this.disabled &&
176 !this.isBusy &&
177 !!this.scrollEl &&
178 !this.isLoading);
179 }
180 enableScrollEvents(shouldListen) {
181 if (this.scrollEl) {
182 if (shouldListen) {
183 this.scrollEl.addEventListener('scroll', this.onScroll);
184 }
185 else {
186 this.scrollEl.removeEventListener('scroll', this.onScroll);
187 }
188 }
189 }
190 render() {
191 const mode = ionicGlobal.getIonMode(this);
192 const disabled = this.disabled;
193 return (index.h(index.Host, { class: {
194 [mode]: true,
195 'infinite-scroll-loading': this.isLoading,
196 'infinite-scroll-enabled': !disabled
197 } }));
198 }
199 get el() { return index.getElement(this); }
200 static get watchers() { return {
201 "threshold": ["thresholdChanged"],
202 "disabled": ["disabledChanged"]
203 }; }
204};
205InfiniteScroll.style = infiniteScrollCss;
206
207const infiniteScrollContentIosCss = "ion-infinite-scroll-content{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;min-height:84px;text-align:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.infinite-loading{margin-left:0;margin-right:0;margin-top:0;margin-bottom:32px;display:none;width:100%}.infinite-loading-text{margin-left:32px;margin-right:32px;margin-top:4px;margin-bottom:0}@supports ((-webkit-margin-start: 0) or (margin-inline-start: 0)) or (-webkit-margin-start: 0){.infinite-loading-text{margin-left:unset;margin-right:unset;-webkit-margin-start:32px;margin-inline-start:32px;-webkit-margin-end:32px;margin-inline-end:32px}}.infinite-scroll-loading ion-infinite-scroll-content>.infinite-loading{display:block}.infinite-scroll-content-ios .infinite-loading-text{color:var(--ion-color-step-600, #666666)}.infinite-scroll-content-ios .infinite-loading-spinner .spinner-lines-ios line,.infinite-scroll-content-ios .infinite-loading-spinner .spinner-lines-small-ios line,.infinite-scroll-content-ios .infinite-loading-spinner .spinner-crescent circle{stroke:var(--ion-color-step-600, #666666)}.infinite-scroll-content-ios .infinite-loading-spinner .spinner-bubbles circle,.infinite-scroll-content-ios .infinite-loading-spinner .spinner-circles circle,.infinite-scroll-content-ios .infinite-loading-spinner .spinner-dots circle{fill:var(--ion-color-step-600, #666666)}";
208
209const infiniteScrollContentMdCss = "ion-infinite-scroll-content{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;min-height:84px;text-align:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.infinite-loading{margin-left:0;margin-right:0;margin-top:0;margin-bottom:32px;display:none;width:100%}.infinite-loading-text{margin-left:32px;margin-right:32px;margin-top:4px;margin-bottom:0}@supports ((-webkit-margin-start: 0) or (margin-inline-start: 0)) or (-webkit-margin-start: 0){.infinite-loading-text{margin-left:unset;margin-right:unset;-webkit-margin-start:32px;margin-inline-start:32px;-webkit-margin-end:32px;margin-inline-end:32px}}.infinite-scroll-loading ion-infinite-scroll-content>.infinite-loading{display:block}.infinite-scroll-content-md .infinite-loading-text{color:var(--ion-color-step-600, #666666)}.infinite-scroll-content-md .infinite-loading-spinner .spinner-lines-md line,.infinite-scroll-content-md .infinite-loading-spinner .spinner-lines-small-md line,.infinite-scroll-content-md .infinite-loading-spinner .spinner-crescent circle{stroke:var(--ion-color-step-600, #666666)}.infinite-scroll-content-md .infinite-loading-spinner .spinner-bubbles circle,.infinite-scroll-content-md .infinite-loading-spinner .spinner-circles circle,.infinite-scroll-content-md .infinite-loading-spinner .spinner-dots circle{fill:var(--ion-color-step-600, #666666)}";
210
211const InfiniteScrollContent = class {
212 constructor(hostRef) {
213 index.registerInstance(this, hostRef);
214 }
215 componentDidLoad() {
216 if (this.loadingSpinner === undefined) {
217 const mode = ionicGlobal.getIonMode(this);
218 this.loadingSpinner = ionicGlobal.config.get('infiniteLoadingSpinner', ionicGlobal.config.get('spinner', mode === 'ios' ? 'lines' : 'crescent'));
219 }
220 }
221 render() {
222 const mode = ionicGlobal.getIonMode(this);
223 return (index.h(index.Host, { class: {
224 [mode]: true,
225 // Used internally for styling
226 [`infinite-scroll-content-${mode}`]: true
227 } }, index.h("div", { class: "infinite-loading" }, this.loadingSpinner && (index.h("div", { class: "infinite-loading-spinner" }, index.h("ion-spinner", { name: this.loadingSpinner }))), this.loadingText && (index.h("div", { class: "infinite-loading-text", innerHTML: index$1.sanitizeDOMString(this.loadingText) })))));
228 }
229};
230InfiniteScrollContent.style = {
231 ios: infiniteScrollContentIosCss,
232 md: infiniteScrollContentMdCss
233};
234
235exports.ion_infinite_scroll = InfiniteScroll;
236exports.ion_infinite_scroll_content = InfiniteScrollContent;