@nstudio/ui-collectionview
Version:
Customized NativeScript CollectionView for high performance lists. Supports vertical and horizontal modes, templating, and more.
147 lines • 6.41 kB
JavaScript
import { ContentView, profile } from '@nativescript/core';
import { NativeViewElementNode, TemplateElement, createElement, registerElement } from 'svelte-native/dom';
//@ts-ignore
import { flush } from 'svelte/internal';
import { CollectionView } from '..';
const SVELTE_VIEW = '_svelteViewRef';
class SvelteKeyedTemplate {
constructor(key, templateEl) {
this._key = key;
this._templateEl = templateEl;
}
get component() {
return this._templateEl.component;
}
get key() {
return this._key;
}
createView() {
// create a proxy element to eventually contain our item (once we have one to render)
// TODO is StackLayout the best choice here?
// const wrapper = createElement('StackLayout') as NativeViewElementNode<View>;
const nativeEl = new ContentView();
// because of the way {N} works we cant use that wrapper as the target for the component
// it will trigger uncessary {N} component updates because the parent view is already attached
nativeEl.__SvelteComponentBuilder__ = (parentView, props) => {
nativeEl.__SvelteComponent__ = new this.component({
target: parentView,
props,
});
};
return nativeEl;
}
}
export default class CollectionViewViewElement extends NativeViewElementNode {
constructor() {
super('collectionview', CollectionView);
const nativeView = this.nativeView;
nativeView.itemViewLoader = (viewType) => this.loadView(viewType);
this.nativeView.on(CollectionView.itemLoadingEvent, this.updateListItem, this);
this.nativeView.on(CollectionView.itemDisposingEvent, this.disposeListItem, this);
}
loadView(viewType) {
if (Array.isArray(this.nativeElement.itemTemplates)) {
const keyedTemplate = this.nativeElement.itemTemplates.find((t) => t.key === 'default');
if (keyedTemplate) {
return keyedTemplate.createView();
}
}
const componentClass = this.getComponentForView(viewType);
if (!componentClass)
return null;
const nativeEl = new ContentView();
const builder = (parentView, props) => {
nativeEl.__SvelteComponent__ = new componentClass({
target: parentView,
props,
});
};
// in svelte we want to add the wrapper as a child of the collectionview ourselves
nativeEl.__SvelteComponentBuilder__ = builder;
return nativeEl;
}
// For some reason itemTemplateSelector isn't defined as a "property" on radListView, so when we set the property, it is lowercase (due to svelte's forced downcasing)
// we intercept and fix the case here.
setAttribute(fullkey, value) {
if (fullkey.toLowerCase() === 'itemtemplateselector') {
fullkey = 'itemTemplateSelector';
}
super.setAttribute(fullkey, value);
}
getComponentForView(viewType) {
const normalizedViewType = viewType.toLowerCase();
const templateEl = this.childNodes.find((n) => n.tagName === 'template' && String(n.getAttribute('type')).toLowerCase() === normalizedViewType);
if (!templateEl)
return null;
return templateEl.component;
}
onInsertedChild(childNode, index) {
super.onInsertedChild(childNode, index);
if (childNode instanceof TemplateElement) {
const key = childNode.getAttribute('key') || 'default';
// const templates = !this.nativeView.itemTemplates || typeof this.nativeView.itemTemplates === 'string' ? [] : (this.nativeView.itemTemplates as any[]);
// we need to reassign or the update wont be seen
this.nativeView.addTemplate(key, new SvelteKeyedTemplate(key, childNode));
// = templates.concat([new SvelteKeyedTemplate(key, childNode)]);
}
}
onRemovedChild(childNode) {
super.onRemovedChild(childNode);
if (childNode instanceof TemplateElement) {
const key = childNode.getAttribute('key') || 'default';
this.nativeView.removeTemplate(key);
}
}
disposeListItem(args) {
const _view = args.view;
if (_view.__SvelteComponent__) {
_view.__SvelteComponent__.$destroy();
_view.__SvelteComponent__ = null;
}
}
updateListItem(args) {
const _view = args.view;
const props = { item: args.bindingContext, index: args.index };
const componentInstance = _view.__SvelteComponent__;
if (!componentInstance) {
if (_view.__SvelteComponentBuilder__) {
const dummy = createElement('fragment');
_view.__SvelteComponentBuilder__(dummy, props);
_view.__SvelteComponentBuilder__ = null;
_view.__CollectionViewCurrentIndex__ = args.index;
const nativeEl = dummy.firstElement().nativeView;
_view.content = nativeEl;
}
}
else {
// ensure we dont do unnecessary tasks if index did not change
// console.log('updateListItem', args.index, _view.__CollectionViewCurrentIndex__);
_view.__CollectionViewCurrentIndex__ = args.index;
// _suspendNativeUpdates with special parameters for Akylas fork to preven requestLayout
//@ts-ignore
_view._suspendNativeUpdates(0, true, true);
_view._batchUpdate(() => {
componentInstance.$set(props);
flush(); // we need to flush to make sure update is applied right away
});
//@ts-ignore
_view._resumeNativeUpdates(0, true, true, true);
}
}
static register() {
registerElement('collectionview', () => new CollectionViewViewElement());
}
}
__decorate([
profile,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", void 0)
], CollectionViewViewElement.prototype, "disposeListItem", null);
__decorate([
profile,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", void 0)
], CollectionViewViewElement.prototype, "updateListItem", null);
//# sourceMappingURL=index.js.map