@html-next/vertical-collection 4.0.2 → 5.0.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/dist/-private/index.js +2 -0
- package/dist/-private/index.js.map +1 -0
- package/dist/index.js +513 -0
- package/dist/index.js.map +1 -0
- package/{addon/styles/app.css → dist/occluded-content.css} +11 -1
- package/dist/static-radar-D0EvnYLd.js +1676 -0
- package/dist/static-radar-D0EvnYLd.js.map +1 -0
- package/package.json +64 -79
- package/{addon → src}/-private/data-view/elements/occluded-content.js +1 -1
- package/{addon/-private/data-view → src/-private/data-view/elements}/viewport-container.js +35 -25
- package/{addon → src}/-private/data-view/elements/virtual-component.js +13 -5
- package/{addon → src}/-private/data-view/radar/dynamic-radar.js +41 -40
- package/{addon → src}/-private/data-view/radar/radar.js +211 -112
- package/{addon → src}/-private/data-view/radar/static-radar.js +19 -7
- package/{addon → src}/-private/data-view/skip-list.js +20 -21
- package/{addon → src}/-private/data-view/utils/insert-range-before.js +6 -1
- package/{addon → src}/-private/data-view/utils/mutation-checkers.js +12 -4
- package/src/-private/data-view/utils/object-at.js +10 -0
- package/{addon → src}/-private/data-view/utils/scroll-handler.js +19 -9
- package/{addon → src}/-private/data-view/utils/supports-passive.js +2 -2
- package/{addon/-private/data-view/elements → src/-private/data-view}/viewport-container.js +35 -25
- package/{addon → src}/-private/ember-internals/key-for-item.js +9 -3
- package/{addon → src}/-private/index.js +8 -8
- package/{addon → src}/-private/utils/element/closest.js +8 -2
- package/{addon → src}/-private/utils/element/estimate-element-height.js +11 -5
- package/{addon/components/vertical-collection/component.js → src/components/vertical-collection.gjs} +155 -71
- package/src/index.js +3 -0
- package/src/occluded-content.css +39 -0
- package/.github/workflows/ci.yml +0 -102
- package/CHANGELOG.md +0 -176
- package/README.md +0 -122
- package/RELEASE.md +0 -74
- package/addon/-private/data-view/utils/object-at.js +0 -7
- package/addon/components/vertical-collection/template.hbs +0 -13
- package/app/components/vertical-collection.js +0 -1
- package/bin/restore-env.sh +0 -1
- package/bin/run-tests-with-retry.sh +0 -24
- package/bin/stash-env.sh +0 -1
- package/config/ember-cli-toolbelts.json +0 -1
- package/config/environment.js +0 -5
- package/index.js +0 -157
- package/vendor/debug.css +0 -62
- /package/{addon → src}/-private/data-view/utils/round-to.js +0 -0
- /package/{addon → src}/-private/ember-internals/identity.js +0 -0
- /package/{addon → src}/-private/utils/document-shim.js +0 -0
- /package/{addon → src}/-private/utils/element/get-scaled-client-rect.js +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
import "./occluded-content.css"
|
|
2
|
+
import { DEBUG } from '@glimmer/env';
|
|
3
|
+
import { assert } from '@ember/debug';
|
|
4
|
+
import { readOnly, empty } from '@ember/object/computed';
|
|
5
|
+
import Component, { setComponentTemplate } from '@ember/component';
|
|
6
|
+
import { get, computed } from '@ember/object';
|
|
7
|
+
import { run } from '@ember/runloop';
|
|
8
|
+
import { Token, scheduler } from 'ember-raf-scheduler';
|
|
9
|
+
import { a as StaticRadar, D as DynamicRadar, V as ViewportContainer, o as objectAt, k as keyForItem } from './static-radar-D0EvnYLd.js';
|
|
10
|
+
import { precompileTemplate } from '@ember/template-compilation';
|
|
11
|
+
|
|
12
|
+
;
|
|
13
|
+
|
|
14
|
+
function isNonZero(value) {
|
|
15
|
+
let int = parseInt(value, 10);
|
|
16
|
+
let float = parseFloat(value);
|
|
17
|
+
return !isNaN(int) && (int !== 0 || float !== 0);
|
|
18
|
+
}
|
|
19
|
+
function hasStyleValue(styles, key, value) {
|
|
20
|
+
return styles[key] === value;
|
|
21
|
+
}
|
|
22
|
+
function hasStyleWithNonZeroValue(styles, key) {
|
|
23
|
+
return isNonZero(styles[key]);
|
|
24
|
+
}
|
|
25
|
+
function styleIsOneOf(styles, key, values) {
|
|
26
|
+
return styles[key] && values.indexOf(styles[key]) !== -1;
|
|
27
|
+
}
|
|
28
|
+
function applyVerticalStyles(element, geography) {
|
|
29
|
+
element.style.height = `${geography.height}px`;
|
|
30
|
+
element.style.top = `${geography.top}px`;
|
|
31
|
+
}
|
|
32
|
+
class Visualization {
|
|
33
|
+
constructor(radar) {
|
|
34
|
+
this.radar = radar;
|
|
35
|
+
this.satellites = [];
|
|
36
|
+
this.cache = [];
|
|
37
|
+
this.wrapper = document.createElement('div');
|
|
38
|
+
this.wrapper.className = 'vertical-collection-visual-debugger';
|
|
39
|
+
this.container = document.createElement('div');
|
|
40
|
+
this.container.className = 'vc_visualization-container';
|
|
41
|
+
this.wrapper.appendChild(this.container);
|
|
42
|
+
this.itemContainer = document.createElement('div');
|
|
43
|
+
this.itemContainer.className = 'vc_visualization-item-container';
|
|
44
|
+
this.container.appendChild(this.itemContainer);
|
|
45
|
+
this.scrollContainer = document.createElement('div');
|
|
46
|
+
this.scrollContainer.className = 'vc_visualization-scroll-container';
|
|
47
|
+
this.container.appendChild(this.scrollContainer);
|
|
48
|
+
this.screen = document.createElement('div');
|
|
49
|
+
this.screen.className = 'vc_visualization-screen';
|
|
50
|
+
this.container.appendChild(this.screen);
|
|
51
|
+
document.body.appendChild(this.wrapper);
|
|
52
|
+
}
|
|
53
|
+
render() {
|
|
54
|
+
this.styleViewport();
|
|
55
|
+
this.updateSatellites();
|
|
56
|
+
}
|
|
57
|
+
styleViewport() {
|
|
58
|
+
const {
|
|
59
|
+
_scrollContainer
|
|
60
|
+
} = this.radar;
|
|
61
|
+
this.container.style.height = `${_scrollContainer.getBoundingClientRect().height}px`;
|
|
62
|
+
applyVerticalStyles(this.scrollContainer, _scrollContainer.getBoundingClientRect());
|
|
63
|
+
applyVerticalStyles(this.screen, ViewportContainer.getBoundingClientRect());
|
|
64
|
+
}
|
|
65
|
+
makeSatellite() {
|
|
66
|
+
let satellite;
|
|
67
|
+
if (this.cache.length) {
|
|
68
|
+
satellite = this.cache.pop();
|
|
69
|
+
} else {
|
|
70
|
+
satellite = document.createElement('div');
|
|
71
|
+
satellite.className = 'vc_visualization-virtual-component';
|
|
72
|
+
}
|
|
73
|
+
this.satellites.push(satellite);
|
|
74
|
+
this.itemContainer.append(satellite);
|
|
75
|
+
}
|
|
76
|
+
updateSatellites() {
|
|
77
|
+
const {
|
|
78
|
+
satellites: sats
|
|
79
|
+
} = this;
|
|
80
|
+
let {
|
|
81
|
+
firstItemIndex,
|
|
82
|
+
lastItemIndex,
|
|
83
|
+
totalItems,
|
|
84
|
+
totalBefore,
|
|
85
|
+
totalAfter,
|
|
86
|
+
skipList,
|
|
87
|
+
_calculatedEstimateHeight
|
|
88
|
+
} = this.radar;
|
|
89
|
+
const isDynamic = !!skipList;
|
|
90
|
+
const itemHeights = isDynamic && skipList.values;
|
|
91
|
+
const firstVisualizedIndex = Math.max(firstItemIndex - 10, 0);
|
|
92
|
+
const lastVisualizedIndex = Math.min(lastItemIndex + 10, totalItems - 1);
|
|
93
|
+
const lengthWithBuffer = lastVisualizedIndex - firstVisualizedIndex + 1;
|
|
94
|
+
const isShrinking = sats.length > lengthWithBuffer;
|
|
95
|
+
while (sats.length !== lengthWithBuffer) {
|
|
96
|
+
if (isShrinking) {
|
|
97
|
+
const satellite = sats.pop();
|
|
98
|
+
satellite.parentNode.removeChild(satellite);
|
|
99
|
+
this.cache.push(satellite);
|
|
100
|
+
} else {
|
|
101
|
+
this.makeSatellite();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
for (let itemIndex = firstVisualizedIndex, i = 0; itemIndex <= lastVisualizedIndex; itemIndex++, i++) {
|
|
105
|
+
const element = sats[i];
|
|
106
|
+
const itemHeight = isDynamic ? itemHeights[itemIndex] : _calculatedEstimateHeight;
|
|
107
|
+
element.style.height = `${itemHeight}px`;
|
|
108
|
+
element.setAttribute('index', String(itemIndex));
|
|
109
|
+
element.innerText = String(itemIndex);
|
|
110
|
+
if (itemIndex < firstItemIndex) {
|
|
111
|
+
element.classList.add('culled');
|
|
112
|
+
totalBefore -= itemHeight;
|
|
113
|
+
} else if (itemIndex > lastItemIndex) {
|
|
114
|
+
element.classList.add('culled');
|
|
115
|
+
totalAfter -= itemHeight;
|
|
116
|
+
} else {
|
|
117
|
+
element.classList.remove('culled');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
this.itemContainer.style.paddingTop = `${totalBefore}px`;
|
|
121
|
+
this.itemContainer.style.paddingBottom = `${totalAfter}px`;
|
|
122
|
+
}
|
|
123
|
+
destroy() {
|
|
124
|
+
this.wrapper.parentNode.removeChild(this.wrapper);
|
|
125
|
+
this.wrapper = null;
|
|
126
|
+
this.radar = null;
|
|
127
|
+
this.component = null;
|
|
128
|
+
this.satellites.forEach(satellite => {
|
|
129
|
+
if (satellite.parentNode) {
|
|
130
|
+
satellite.parentNode.removeChild(satellite);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
this.satellites = null;
|
|
134
|
+
this.cache = null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/*
|
|
138
|
+
* END DEBUG HELPERS
|
|
139
|
+
*/
|
|
140
|
+
class VerticalCollection extends Component.extend({
|
|
141
|
+
tagName: '',
|
|
142
|
+
/**
|
|
143
|
+
* Property name used for storing references to each item in items. Accessing this attribute for each item
|
|
144
|
+
* should yield a unique result for every item in the list.
|
|
145
|
+
*
|
|
146
|
+
* @property key
|
|
147
|
+
* @type String
|
|
148
|
+
* @default '@identity'
|
|
149
|
+
*/
|
|
150
|
+
key: '@identity',
|
|
151
|
+
// –––––––––––––– Required Settings
|
|
152
|
+
/**
|
|
153
|
+
* Estimated height of an item to be rendered. Use best guess as this will be used to determine how many items
|
|
154
|
+
* are displayed virtually, before and after the vertical-collection viewport.
|
|
155
|
+
*
|
|
156
|
+
* @property estimateHeight
|
|
157
|
+
* @type Number
|
|
158
|
+
* @required
|
|
159
|
+
*/
|
|
160
|
+
estimateHeight: null,
|
|
161
|
+
/**
|
|
162
|
+
* List of objects to svelte-render.
|
|
163
|
+
* Can be called like `<VerticalCollection @items={{itemsArray}} />`.
|
|
164
|
+
*
|
|
165
|
+
* @property items
|
|
166
|
+
* @type Array
|
|
167
|
+
* @required
|
|
168
|
+
*/
|
|
169
|
+
items: null,
|
|
170
|
+
// –––––––––––––– Optional Settings
|
|
171
|
+
/**
|
|
172
|
+
* Indicates if the occluded items' heights will change or not.
|
|
173
|
+
* If true, the vertical-collection will assume that items' heights are always equal to estimateHeight;
|
|
174
|
+
* this is more performant, but less flexible.
|
|
175
|
+
*
|
|
176
|
+
* @property staticHeight
|
|
177
|
+
* @type Boolean
|
|
178
|
+
*/
|
|
179
|
+
staticHeight: false,
|
|
180
|
+
/**
|
|
181
|
+
* Indicates whether or not list items in the Radar should be reused on update of virtual components (e.g. scroll).
|
|
182
|
+
* This yields performance benefits because it is not necessary to repopulate the component pool of the radar.
|
|
183
|
+
* Set to false when recycling a component instance has undesirable ramifications including:
|
|
184
|
+
* - When using `unbound` in a component or sub-component
|
|
185
|
+
* - When using init for instance state that differs between instances of a component or sub-component
|
|
186
|
+
* (can move to didInitAttrs to fix this)
|
|
187
|
+
* - When templates for individual items vary widely or are based on conditionals that are likely to change
|
|
188
|
+
* (i.e. would defeat any benefits of DOM recycling anyway)
|
|
189
|
+
*
|
|
190
|
+
* @property shouldRecycle
|
|
191
|
+
* @type Boolean
|
|
192
|
+
*/
|
|
193
|
+
shouldRecycle: true,
|
|
194
|
+
/*
|
|
195
|
+
* A selector string that will select the element from
|
|
196
|
+
* which to calculate the viewable height and needed offsets.
|
|
197
|
+
*
|
|
198
|
+
* This element will also have the `scroll` event handler added to it.
|
|
199
|
+
*
|
|
200
|
+
* Usually this element will be the component's immediate parent element,
|
|
201
|
+
* if so, you can leave this null.
|
|
202
|
+
*
|
|
203
|
+
* Set this to "body" to scroll the entire web page.
|
|
204
|
+
*/
|
|
205
|
+
containerSelector: '*',
|
|
206
|
+
// –––––––––––––– Performance Tuning
|
|
207
|
+
/**
|
|
208
|
+
* The amount of extra items to keep visible on either side of the viewport -- must be greater than 0.
|
|
209
|
+
* Increasing this value is useful when doing infinite scrolling and loading data from a remote service,
|
|
210
|
+
* with the desire to allow records to show as the user scrolls and the backend API takes time to respond.
|
|
211
|
+
*
|
|
212
|
+
* @property bufferSize
|
|
213
|
+
* @type Number
|
|
214
|
+
* @default 1
|
|
215
|
+
*/
|
|
216
|
+
bufferSize: 1,
|
|
217
|
+
// –––––––––––––– Initial Scroll State
|
|
218
|
+
/**
|
|
219
|
+
* If set, upon initialization the scroll
|
|
220
|
+
* position will be set such that the item
|
|
221
|
+
* with the provided id is at the top left
|
|
222
|
+
* on screen.
|
|
223
|
+
*
|
|
224
|
+
* If the item cannot be found, scrollTop
|
|
225
|
+
* is set to 0.
|
|
226
|
+
* @property idForFirstItem
|
|
227
|
+
*/
|
|
228
|
+
idForFirstItem: null,
|
|
229
|
+
/**
|
|
230
|
+
* If set, if scrollPosition is empty
|
|
231
|
+
* at initialization, the component will
|
|
232
|
+
* render starting at the bottom.
|
|
233
|
+
* @property renderFromLast
|
|
234
|
+
* @type Boolean
|
|
235
|
+
* @default false
|
|
236
|
+
*/
|
|
237
|
+
renderFromLast: false,
|
|
238
|
+
/**
|
|
239
|
+
* If set to true, the collection will render all of the items passed into the component.
|
|
240
|
+
* This counteracts the performance benefits of using vertical collection, but has several potential applications,
|
|
241
|
+
* including but not limited to:
|
|
242
|
+
*
|
|
243
|
+
* - It allows for improved accessibility since all elements are rendered and can be picked up by a screen reader.
|
|
244
|
+
* - Can be applied in SEO solutions (i.e. fastboot) where rendering every item is desirable.
|
|
245
|
+
* - Can be used to respond to the keyboard input for Find (i.e. ctrl+F/cmd+F) to show all elements, which then
|
|
246
|
+
* allows the list items to be searchable
|
|
247
|
+
*
|
|
248
|
+
* @property renderAll
|
|
249
|
+
* @type Boolean
|
|
250
|
+
* @default false
|
|
251
|
+
*/
|
|
252
|
+
renderAll: false,
|
|
253
|
+
/**
|
|
254
|
+
* The tag name used in DOM elements before and after the rendered list. By default, it is set to
|
|
255
|
+
* 'occluded-content' to avoid any confusion with user's CSS settings. However, it could be
|
|
256
|
+
* overriden to provide custom behavior (for example, in table user wants to set it to 'tr' to
|
|
257
|
+
* comply with table semantics).
|
|
258
|
+
*/
|
|
259
|
+
occlusionTagName: 'occluded-content',
|
|
260
|
+
isEmpty: empty('items'),
|
|
261
|
+
shouldYieldToInverse: readOnly('isEmpty'),
|
|
262
|
+
virtualComponents: computed('items.[]', 'renderAll', 'estimateHeight', 'bufferSize', function () {
|
|
263
|
+
const {
|
|
264
|
+
_radar
|
|
265
|
+
} = this;
|
|
266
|
+
const items = this.items;
|
|
267
|
+
_radar.items = items === null || items === undefined ? [] : items;
|
|
268
|
+
_radar.estimateHeight = this.estimateHeight;
|
|
269
|
+
_radar.renderAll = this.renderAll;
|
|
270
|
+
_radar.bufferSize = this.bufferSize;
|
|
271
|
+
_radar.scheduleUpdate(true);
|
|
272
|
+
this._clearScheduledActions();
|
|
273
|
+
return _radar.virtualComponents;
|
|
274
|
+
}),
|
|
275
|
+
schedule(queueName, job) {
|
|
276
|
+
return scheduler.schedule(queueName, job, this.token);
|
|
277
|
+
},
|
|
278
|
+
_clearScheduledActions() {
|
|
279
|
+
clearTimeout(this._nextSendActions);
|
|
280
|
+
this._nextSendActions = null;
|
|
281
|
+
this._scheduledActions.length = 0;
|
|
282
|
+
},
|
|
283
|
+
_scheduleSendAction(action, index) {
|
|
284
|
+
this._scheduledActions.push([action, index]);
|
|
285
|
+
if (this._nextSendActions === null) {
|
|
286
|
+
this._nextSendActions = setTimeout(() => {
|
|
287
|
+
this._nextSendActions = null;
|
|
288
|
+
run(() => {
|
|
289
|
+
const items = this.items;
|
|
290
|
+
const keyPath = this.key;
|
|
291
|
+
this._scheduledActions.forEach(([action, index]) => {
|
|
292
|
+
const item = objectAt(items, index);
|
|
293
|
+
const key = keyForItem(item, keyPath, index);
|
|
294
|
+
// this.sendAction will be deprecated in ember 4.0
|
|
295
|
+
const _action = get(this, action);
|
|
296
|
+
if (typeof _action == 'function') {
|
|
297
|
+
_action(item, index, key);
|
|
298
|
+
} else if (typeof _action === 'string') {
|
|
299
|
+
this.sendAction(action, item, index, key);
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
this._scheduledActions.length = 0;
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
/* Public API Methods
|
|
308
|
+
@index => number
|
|
309
|
+
This will return offset height of the indexed item.
|
|
310
|
+
*/
|
|
311
|
+
scrollToItem(index) {
|
|
312
|
+
const {
|
|
313
|
+
_radar
|
|
314
|
+
} = this;
|
|
315
|
+
// Getting the offset height from Radar
|
|
316
|
+
let scrollTop = _radar.getOffsetForIndex(index);
|
|
317
|
+
_radar._scrollContainer.scrollTop = scrollTop;
|
|
318
|
+
// To scroll exactly to specified index, we are changing the prevIndex values to specified index
|
|
319
|
+
_radar._prevFirstVisibleIndex = _radar._prevFirstItemIndex = index;
|
|
320
|
+
// Components will be rendered after schedule 'measure' inside 'update' method.
|
|
321
|
+
// In our case, we need to focus the element after component is rendered. So passing the promise.
|
|
322
|
+
return new Promise(resolve => {
|
|
323
|
+
_radar.scheduleUpdate(false, resolve);
|
|
324
|
+
});
|
|
325
|
+
},
|
|
326
|
+
// –––––––––––––– Setup/Teardown
|
|
327
|
+
didInsertElement() {
|
|
328
|
+
this._super();
|
|
329
|
+
this.schedule('sync', () => {
|
|
330
|
+
this._radar.start();
|
|
331
|
+
});
|
|
332
|
+
},
|
|
333
|
+
willDestroy() {
|
|
334
|
+
this.token.cancel();
|
|
335
|
+
this._radar.destroy();
|
|
336
|
+
let registerAPI = this.registerAPI;
|
|
337
|
+
if (registerAPI) {
|
|
338
|
+
registerAPI(null);
|
|
339
|
+
}
|
|
340
|
+
clearTimeout(this._nextSendActions);
|
|
341
|
+
if (DEBUG) {
|
|
342
|
+
if (this.__visualization) {
|
|
343
|
+
console.info('destroying visualization');
|
|
344
|
+
this.__visualization.destroy();
|
|
345
|
+
this.__visualization = null;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
this._super();
|
|
349
|
+
},
|
|
350
|
+
init() {
|
|
351
|
+
this._super();
|
|
352
|
+
this.token = new Token();
|
|
353
|
+
const RadarClass = this.staticHeight ? StaticRadar : DynamicRadar;
|
|
354
|
+
const items = this.items || [];
|
|
355
|
+
const {
|
|
356
|
+
bufferSize,
|
|
357
|
+
containerSelector,
|
|
358
|
+
estimateHeight,
|
|
359
|
+
initialRenderCount,
|
|
360
|
+
renderAll,
|
|
361
|
+
renderFromLast,
|
|
362
|
+
shouldRecycle,
|
|
363
|
+
occlusionTagName,
|
|
364
|
+
idForFirstItem,
|
|
365
|
+
key
|
|
366
|
+
} = this;
|
|
367
|
+
const startingIndex = calculateStartingIndex(items, idForFirstItem, key, renderFromLast);
|
|
368
|
+
this._radar = new RadarClass(this.token, {
|
|
369
|
+
bufferSize,
|
|
370
|
+
containerSelector,
|
|
371
|
+
estimateHeight,
|
|
372
|
+
initialRenderCount,
|
|
373
|
+
items,
|
|
374
|
+
key,
|
|
375
|
+
renderAll,
|
|
376
|
+
renderFromLast,
|
|
377
|
+
shouldRecycle,
|
|
378
|
+
startingIndex,
|
|
379
|
+
occlusionTagName
|
|
380
|
+
});
|
|
381
|
+
this._prevItemsLength = 0;
|
|
382
|
+
this._prevFirstKey = null;
|
|
383
|
+
this._prevLastKey = null;
|
|
384
|
+
this._hasAction = null;
|
|
385
|
+
this._scheduledActions = [];
|
|
386
|
+
this._nextSendActions = null;
|
|
387
|
+
let a = !!this.lastReached;
|
|
388
|
+
let b = !!this.firstReached;
|
|
389
|
+
let c = !!this.lastVisibleChanged;
|
|
390
|
+
let d = !!this.firstVisibleChanged;
|
|
391
|
+
let any = a || b || c || d;
|
|
392
|
+
if (any) {
|
|
393
|
+
this._hasAction = {
|
|
394
|
+
lastReached: a,
|
|
395
|
+
firstReached: b,
|
|
396
|
+
lastVisibleChanged: c,
|
|
397
|
+
firstVisibleChanged: d
|
|
398
|
+
};
|
|
399
|
+
this._radar.sendAction = (action, index) => {
|
|
400
|
+
if (this._hasAction[action]) {
|
|
401
|
+
this._scheduleSendAction(action, index);
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
/* Public methods to Expose to parent
|
|
406
|
+
Usage:
|
|
407
|
+
Template:
|
|
408
|
+
<VerticalCollection @registerAPI={{this.registerAPI}} />
|
|
409
|
+
Component:
|
|
410
|
+
export default class extends Component {
|
|
411
|
+
@action
|
|
412
|
+
registerAPI(api) {
|
|
413
|
+
this.collectionAPI = api;
|
|
414
|
+
}
|
|
415
|
+
@action
|
|
416
|
+
scrollToItem(index) {
|
|
417
|
+
this.collectionAPI.scrollToItem(index);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
Need to pass this property in the vertical-collection template
|
|
421
|
+
Listen in the component actions and do your custom logic
|
|
422
|
+
This API will have below methods.
|
|
423
|
+
1. scrollToItem
|
|
424
|
+
*/
|
|
425
|
+
let registerAPI = get(this, 'registerAPI');
|
|
426
|
+
if (registerAPI) {
|
|
427
|
+
/* List of methods to be exposed to public should be added here */let publicAPI = {
|
|
428
|
+
scrollToItem: this.scrollToItem.bind(this)
|
|
429
|
+
};
|
|
430
|
+
registerAPI(publicAPI);
|
|
431
|
+
}
|
|
432
|
+
if (DEBUG) {
|
|
433
|
+
this.__visualization = null;
|
|
434
|
+
this._radar._debugDidUpdate = () => {
|
|
435
|
+
// Update visualization
|
|
436
|
+
//
|
|
437
|
+
// This debugging mode can be controlled via the argument
|
|
438
|
+
// `@debugVis={{true}}` at component invocation.
|
|
439
|
+
//
|
|
440
|
+
if (this.debugVis !== true) {
|
|
441
|
+
if (this.__visualization !== null) {
|
|
442
|
+
console.info('tearing down existing visualization');
|
|
443
|
+
this.__visualization.destroy();
|
|
444
|
+
this.__visualization = null;
|
|
445
|
+
}
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
if (this.__visualization === null) {
|
|
449
|
+
this.__visualization = new Visualization(this._radar);
|
|
450
|
+
}
|
|
451
|
+
this.__visualization.render();
|
|
452
|
+
// Detect issues with CSS
|
|
453
|
+
//
|
|
454
|
+
// This debugging mode can be controlled via the argument
|
|
455
|
+
// `@debugCSS={{true}}` at component invocation.
|
|
456
|
+
//
|
|
457
|
+
if (this.debugCSS !== true) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
let radar = this._radar;
|
|
461
|
+
let styles;
|
|
462
|
+
// check telescope
|
|
463
|
+
if (radar.scrollContainer !== ViewportContainer) {
|
|
464
|
+
styles = window.getComputedStyle(radar.scrollContainer);
|
|
465
|
+
} else {
|
|
466
|
+
styles = window.getComputedStyle(document.body);
|
|
467
|
+
}
|
|
468
|
+
assert(`scrollContainer cannot be inline.`, styleIsOneOf(styles, 'display', ['block', 'inline-block', 'flex', 'inline-flex']));
|
|
469
|
+
assert(`scrollContainer must define position`, styleIsOneOf(styles, 'position', ['static', 'relative', 'absolute']));
|
|
470
|
+
assert(`scrollContainer must define height or max-height`, hasStyleWithNonZeroValue(styles, 'height') || hasStyleWithNonZeroValue(styles, 'max-height'));
|
|
471
|
+
// conditional perf check for non-body scrolling
|
|
472
|
+
if (radar.scrollContainer !== ViewportContainer) {
|
|
473
|
+
assert(`scrollContainer must define overflow-y`, hasStyleValue(styles, 'overflow-y', 'scroll') || hasStyleValue(styles, 'overflow', 'scroll'));
|
|
474
|
+
}
|
|
475
|
+
// check itemContainer
|
|
476
|
+
styles = window.getComputedStyle(radar.itemContainer);
|
|
477
|
+
assert(`itemContainer cannot be inline.`, styleIsOneOf(styles, 'display', ['block', 'inline-block', 'flex', 'inline-flex']));
|
|
478
|
+
assert(`itemContainer must define position`, styleIsOneOf(styles, 'position', ['static', 'relative', 'absolute']));
|
|
479
|
+
// check item defaults
|
|
480
|
+
assert(`You must supply at least one item to the collection to debug it's CSS.`, this.items.length);
|
|
481
|
+
let element = radar._itemContainer.firstElementChild;
|
|
482
|
+
styles = window.getComputedStyle(element);
|
|
483
|
+
assert(`Item cannot be inline.`, styleIsOneOf(styles, 'display', ['block', 'inline-block', 'flex', 'inline-flex']));
|
|
484
|
+
assert(`Item must define position`, styleIsOneOf(styles, 'position', ['static', 'relative', 'absolute']));
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}) {
|
|
489
|
+
static {
|
|
490
|
+
setComponentTemplate(precompileTemplate("{{#each this.virtualComponents key=\"id\" as |virtualComponent|~}}\n {{~unbound virtualComponent.upperBound~}}\n {{~#if virtualComponent.isOccludedContent~}}\n {{{unbound virtualComponent.element}}}\n {{~else~}}\n {{~yield virtualComponent.content virtualComponent.index~}}\n {{~/if~}}\n {{~unbound virtualComponent.lowerBound~}}\n{{~/each}}\n\n{{#if this.shouldYieldToInverse}}\n {{yield to=\"inverse\"}}\n{{/if}}", {
|
|
491
|
+
strictMode: true
|
|
492
|
+
}), this);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
function calculateStartingIndex(items, idForFirstItem, key, renderFromLast) {
|
|
496
|
+
const totalItems = get(items, 'length');
|
|
497
|
+
let startingIndex = 0;
|
|
498
|
+
if (idForFirstItem !== undefined && idForFirstItem !== null) {
|
|
499
|
+
for (let i = 0; i < totalItems; i++) {
|
|
500
|
+
if (keyForItem(objectAt(items, i), key, i) === idForFirstItem) {
|
|
501
|
+
startingIndex = i;
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
} else if (renderFromLast === true) {
|
|
506
|
+
// If no id was set and `renderFromLast` is true, start from the bottom
|
|
507
|
+
startingIndex = totalItems - 1;
|
|
508
|
+
}
|
|
509
|
+
return startingIndex;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
export { VerticalCollection };
|
|
513
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -7,7 +7,17 @@
|
|
|
7
7
|
min-height: 0.01px;
|
|
8
8
|
|
|
9
9
|
/* hides text visually while still being readable by screen readers */
|
|
10
|
-
color:
|
|
10
|
+
color: rgb(0 0 0 / 0%);
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
* Prevents the debug text ("And X items before/after") from affecting
|
|
14
|
+
* the element's layout height. Without this, inherited line-height values
|
|
15
|
+
* can cause the text to create a line box that inflates the element's
|
|
16
|
+
* actual height above its inline style height, especially when used
|
|
17
|
+
* inside tables with display: table-row.
|
|
18
|
+
*/
|
|
19
|
+
font-size: 0;
|
|
20
|
+
line-height: 0;
|
|
11
21
|
}
|
|
12
22
|
|
|
13
23
|
table .occluded-content,
|