@dialpad/dialtone
Version:
Dialpad's Dialtone design system monorepo
249 lines (248 loc) • 7.3 kB
JavaScript
import _sfc_main$1 from "./core_scroller.vue.js";
import _sfc_main$2 from "./scroller_item.vue.js";
import { returnFirstEl } from "../../../common/utils.js";
import { resolveComponent, openBlock, createBlock, mergeProps, withCtx, createVNode, renderSlot, normalizeProps, guardReactiveProps } from "vue";
import _export_sfc from "../../../_virtual/_plugin-vue_export-helper.js";
const _sfc_main = {
name: "DynamicScroller",
components: {
CoreScroller: _sfc_main$1,
DtScrollerItem: _sfc_main$2
},
provide() {
if (typeof ResizeObserver !== "undefined") {
this.$_resizeObserver = new ResizeObserver((entries) => {
requestAnimationFrame(() => {
if (!Array.isArray(entries)) {
return;
}
for (const entry of entries) {
if (entry.target && entry.target.$_vs_onResize) {
let width, height;
if (entry.borderBoxSize) {
const resizeObserverSize = entry.borderBoxSize[0];
width = resizeObserverSize.inlineSize;
height = resizeObserverSize.blockSize;
} else {
width = entry.contentRect.width;
height = entry.contentRect.height;
}
entry.target.$_vs_onResize(entry.target.$_vs_id, width, height);
}
}
});
});
}
return {
vscrollData: this.vscrollData,
vscrollParent: this,
vscrollResizeObserver: this.$_resizeObserver
};
},
inheritAttrs: false,
props: {
/*
* The items to render.
* If the items are simple arrays, the index will be used as the key.
* If the items are objects, the keyField will be used as the key.
*/
items: {
type: Array,
required: true
},
/*
* Indicates if the items are dynamic.
* If true, the items will be wrapped in a DtScrollerItem component.
* This is required for dynamic items to be able to react to changes in their size.
*/
dynamic: {
type: Boolean,
default: false
},
/*
* The key field to use for the items.
* Only used if the items are objects.
*/
keyField: {
type: String,
default: "id"
},
/*
* The direction of the scroller.
* Can be either 'vertical' or 'horizontal'.
*/
direction: {
type: String,
default: "vertical",
validator: (value) => ["vertical", "horizontal"].includes(value)
},
/*
* The tag to use for the list.
*/
listTag: {
type: String,
default: "div"
},
/*
* The tag to use for the items.
*/
itemTag: {
type: String,
default: "div"
},
/*
* Display height (or width in horizontal mode) of the items in pixels
* used to calculate the scroll size and position.
* Is required for the initial render of items in DYNAMIC size mode.
*/
minItemSize: {
type: [Number, String]
}
},
data() {
return {
vscrollData: {
active: true,
sizes: {},
keyField: this.keyField,
simpleArray: false
}
};
},
computed: {
simpleArray() {
return this.items.length && typeof this.items[0] !== "object";
},
itemsWithSize() {
const result = [];
const { items, keyField, simpleArray } = this;
const sizes = this.vscrollData.sizes;
const l = items.length;
for (let i = 0; i < l; i++) {
const item = items[i];
const id = simpleArray ? i : item[keyField];
let size = sizes[id];
if (typeof size === "undefined" && !this.$_undefinedMap[id]) {
size = 0;
}
result.push({
item,
[keyField]: id,
size
});
}
return result;
}
},
watch: {
simpleArray: {
handler(value) {
this.vscrollData.simpleArray = value;
},
immediate: true
},
itemsWithSize(next, prev) {
const scrollTop = returnFirstEl(this.$el).scrollTop;
let prevActiveTop = 0;
let activeTop = 0;
const length = Math.min(next.length, prev.length);
for (let i = 0; i < length; i++) {
if (prevActiveTop >= scrollTop) {
break;
}
prevActiveTop += prev[i].size || this.minItemSize;
activeTop += next[i].size || this.minItemSize;
}
const offset = activeTop - prevActiveTop;
if (offset === 0) {
return;
}
returnFirstEl(this.$el).scrollTop += offset;
}
},
beforeCreate() {
this.$_updates = [];
this.$_undefinedSizes = 0;
this.$_undefinedMap = {};
},
activated() {
this.vscrollData.active = true;
},
deactivated() {
this.vscrollData.active = false;
},
methods: {
dynamicScrollerUpdateItems() {
const scroller = this.$refs.scroller;
if (scroller) scroller._updateVisibleItems(true);
},
dynamicScrollerUpdateItemsFromBottom() {
const scroller = this.$refs.scroller;
if (scroller) scroller._updateVisibleItems(false, true);
},
scrollToItem(index) {
const scroller = this.$refs.scroller;
if (scroller) scroller.scrollToItem(index);
},
scrollToBottom() {
if (this.$_scrollingToBottom) return;
this.$_scrollingToBottom = true;
const el = returnFirstEl(this.$el);
this.$nextTick(() => {
el.scrollTop = el.scrollHeight + 5e3;
const cb = () => {
el.scrollTop = el.scrollHeight + 5e3;
requestAnimationFrame(() => {
el.scrollTop = el.scrollHeight + 5e3;
if (this.$_undefinedSizes === 0) {
this.$_scrollingToBottom = false;
} else {
requestAnimationFrame(cb);
}
});
};
requestAnimationFrame(cb);
});
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
const _component_dt_scroller_item = resolveComponent("dt-scroller-item");
const _component_core_scroller = resolveComponent("core-scroller");
return openBlock(), createBlock(_component_core_scroller, mergeProps({
ref: "scroller",
items: $options.itemsWithSize,
"min-item-size": $props.minItemSize,
direction: $props.direction,
"key-field": $props.keyField,
"list-tag": $props.listTag,
"item-tag": $props.itemTag
}, _ctx.$attrs), {
default: withCtx(({ item: itemWithSize, index, active }) => [
createVNode(_component_dt_scroller_item, {
item: itemWithSize,
active,
"size-dependencies": [
itemWithSize.message
],
"data-index": index
}, {
default: withCtx(() => [
renderSlot(_ctx.$slots, "default", normalizeProps(guardReactiveProps({
item: itemWithSize.item,
index,
active,
itemWithSize
})))
]),
_: 2
}, 1032, ["item", "active", "size-dependencies", "data-index"])
]),
_: 3
}, 16, ["items", "min-item-size", "direction", "key-field", "list-tag", "item-tag"]);
}
const DynamicScroller = /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render]]);
export {
DynamicScroller as default
};
//# sourceMappingURL=dynamic_scroller.vue.js.map