@dialpad/dialtone
Version:
Dialpad's Dialtone design system monorepo
768 lines (767 loc) • 24.9 kB
JavaScript
import { reactive as ce, ref as U, computed as E, watch as fe, onMounted as xe, nextTick as Fe, createElementBlock as de, openBlock as P, normalizeClass as ue, createBlock as L, resolveDynamicComponent as X, normalizeStyle as he, unref as Ae, withCtx as V, Fragment as Oe, renderList as Ue, mergeProps as pe, toHandlers as Pe, renderSlot as G, markRaw as Re, shallowReactive as Be, h as Ve, resolveComponent as me, createVNode as Me, normalizeProps as ve, guardReactiveProps as ye, provide as He } from "vue";
import { returnFirstEl as w } from "../../common/utils/index.js";
import { _ as qe } from "../../_plugin-vue_export-helper-CHgC5LLL.js";
const ze = {
__name: "core_scroller",
props: {
/**
* List of items you want to display in the scroller.
*/
items: {
type: Array,
required: !0
},
/**
*
* Field used to identify items and optimize managing rendered views
*/
keyField: {
type: String,
default: "id"
},
/**
* Direction of the scroller. Can be either `vertical` or `horizontal`.
*/
direction: {
type: String,
default: "vertical",
validator: (e) => ["vertical", "horizontal"].includes(e)
},
/**
* Size of the items in the list.
* If it is set to null (the default value), it will use variable size mode.
*/
itemSize: {
type: Number,
default: null
},
/**
* Minimum size used if the height (or width in horizontal mode) of an item is unknown.
*/
minItemSize: {
type: [Number, String],
default: null
},
/**
* Field used to get the item's size in variable size mode.
*/
sizeField: {
type: String,
default: "size"
},
/**
* Amount of pixel to add to edges of the scrolling visible area to start rendering items further away.
*/
buffer: {
type: Number,
default: 200
},
/**
* If true, the hover state will be skipped.
* This can be useful if you want to use the hover state for other purposes.
*/
skipHover: {
type: Boolean,
default: !1
},
/**
* The element to render as the list's wrapper.
*/
listTag: {
type: String,
default: "div"
},
/**
* The element to render as the list item.
*/
itemTag: {
type: String,
default: "div"
},
/**
* The custom classes added to the item list wrapper.
*/
listClass: {
type: [String, Object, Array],
default: ""
},
/**
* The custom classes added to each item.
*/
itemClass: {
type: [String, Object, Array],
default: ""
}
},
emits: ["user-position"],
setup(e, { expose: i, emit: o }) {
const t = e, z = o, u = ce(/* @__PURE__ */ new Map()), v = ce(/* @__PURE__ */ new Map()), f = U([]), y = U(null), S = U(!1), h = U(null), F = U("top");
let x = 0, A = !1, O = 0, M = null, H = null, J = 0, Se = 0;
const K = E(() => {
if (t.itemSize === null) {
const r = {
"-1": { accumulator: 0 }
}, n = t.items, s = t.sizeField, T = t.minItemSize;
let p = 1e4, D = 0, d;
for (let b = 0, R = n.length; b < R; b++)
d = n[b][s] || T, d < p && (p = d), D += d, r[b] = { accumulator: D, size: d };
return H = p, r;
}
return [];
}), ge = E(() => t.items.length && typeof t.items[0] != "object"), $e = E(() => {
const r = {};
for (let n = 0, s = t.items.length; n < s; n++)
r[t.items[n][t.keyField]] = n;
return r;
});
fe(K, () => {
q(!1);
}, { deep: !0 }), xe(() => {
Fe(() => {
q(!0), S.value = !0;
});
});
const Q = (r, n, s, T, p) => {
const D = Re({
id: Se++,
index: n,
used: !0,
key: T,
type: p
}), d = Be({
item: s,
position: 0,
nr: D
});
return r.value.push(d), d;
}, Y = (r, n = !1) => {
const s = v, T = r.nr.type;
let p = s.get(T);
p || (p = [], s.set(T, p)), p.push(r), n || (r.nr.used = !1, r.position = -9999);
}, _e = () => {
const r = t.direction === "vertical";
let n;
return r ? n = {
start: h.value.scrollTop,
end: h.value.scrollTop + h.value.clientHeight
} : n = {
start: h.value.scrollLeft,
end: h.value.scrollLeft + h.value.clientWidth
}, n;
}, be = () => {
throw setTimeout(() => {
console.error("It seems the scroller element isn't scrolling, so it tries to render all the items at once.", "Scroller:", h), console.error("Make sure the scroller has a fixed height (or width) and 'overflow-y' (or 'overflow-x') set to 'auto' so it can scroll correctly and only render the items visible in the scroll viewport.");
}), new Error("Rendered items limit reached");
}, Te = () => {
f.value.sort((r, n) => r.nr.index - n.nr.index);
}, q = (r, n = !1) => {
var te, ie, se, le, re, oe;
const s = t.itemSize, T = H, p = ge.value ? null : t.keyField, D = t.items, d = D.length, b = K.value, R = u, Z = v, N = f, we = $e;
let g, m, W;
if (!d)
g = m = W = 0;
else {
const a = _e();
if (n) {
let c = a.start - O.value;
if (c < 0 && (c = -c), s === null && c < T.value || c < s)
return {
continuous: !0
};
}
O = a.start;
const $ = t.buffer;
if (a.start -= $, a.end += $, s === null) {
let c, B = 0, ne = d - 1, _ = ~~(d / 2), ae;
do
ae = _, c = (te = b[_]) == null ? void 0 : te.accumulator, c < a.start ? B = _ : _ < d - 1 && ((ie = b[_ + 1]) == null ? void 0 : ie.accumulator) > a.start && (ne = _), _ = ~~((B + ne) / 2);
while (_ !== ae);
for (_ < 0 && (_ = 0), g = _, W = (se = b[d - 1]) == null ? void 0 : se.accumulator, m = _; m < d && ((le = b[m]) == null ? void 0 : le.accumulator) < a.end; m++) ;
m === -1 ? m = D.length - 1 : (m++, m > d && (m = d));
} else {
g = ~~(a.start / s);
const c = g % 1;
g -= c, m = Math.ceil(a.end / s), g < 0 && (g = 0), m > d && (m = d), W = Math.ceil(d / 1) * s;
}
}
m - g > 1e3 && be(), J = W;
let l;
const C = g <= x && m >= g;
if (C)
for (let a = 0, $ = N.value.length; a < $; a++)
l = N.value[a], l != null && l.nr.used && (r && (l.nr.index = we[l.item[p]]), (l.nr.index == null || l.nr.index < g || l.nr.index >= m) && Y(l));
const ee = C ? null : /* @__PURE__ */ new Map();
let k, I, j;
for (let a = g; a < m; a++) {
k = D[a];
const $ = p ? k == null ? void 0 : k[p] : k;
if ($ == null)
throw new Error(`Key is ${$} on item (keyField is '${p}')`);
if (l = R.get($), !s && !((re = b[a]) != null && re.size)) {
l && Y(l);
continue;
}
I = k.type;
let c = Z.get(I);
if (!l)
C ? c && c.length ? l = c.pop() : l = Q(N, a, k, $, I) : (j = ee.get(I) || 0, (!c || j >= c.length) && (l = Q(N, a, k, $, I), Y(l, !0), c = Z.get(I)), l = c[j], ee.set(I, j + 1)), R.delete(l.nr.key), l.nr.used = !0, l.nr.index = a, l.nr.key = $, l.nr.type = I, R.set($, l);
else if (!l.nr.used && (l.nr.used = !0, c)) {
const B = c.indexOf(l);
B !== -1 && c.splice(B, 1);
}
l.item = k, s === null ? (l.position = (oe = b[a - 1]) == null ? void 0 : oe.accumulator, l.offset = 0) : (l.position = Math.floor(a) * s, l.offset = a % 1 * s);
}
return x = m, clearTimeout(M), M = setTimeout(Te, 300), {
continuous: C
};
}, ke = (r) => {
const n = t.direction === "vertical" ? { scroll: "scrollTop" } : { scroll: "scrollLeft" }, s = h.value, T = n.scroll;
s[T] = r;
}, De = (r) => {
var s;
let n;
t.itemSize === null ? n = r > 0 ? (s = K.value[r - 1]) == null ? void 0 : s.accumulator : 0 : n = Math.floor(r) * t.itemSize, ke(n);
}, Ie = () => {
const r = h.value;
F.value !== "middle" && (F.value = "middle", z("user-position", "middle")), r.scrollTop === 0 && (F.value = "top", z("user-position", "top")), r.scrollTop + r.clientHeight === r.scrollHeight && (F.value = "bottom", z("user-position", "bottom")), A || (A = !0, requestAnimationFrame(() => {
A = !1, q(!1, !0);
}));
};
return i({
scrollToItem: De,
_updateVisibleItems: q
}), (r, n) => (P(), de("div", {
ref_key: "scroller",
ref: h,
class: ue(["vue-recycle-scroller", {
ready: S.value,
[`direction-${e.direction}`]: !0
}]),
onScrollPassive: Ie
}, [
(P(), L(X(e.listTag), {
ref: "wrapper",
style: he({ [e.direction === "vertical" ? "minHeight" : "minWidth"]: `${Ae(J)}px` }),
class: ue(["vue-recycle-scroller__item-wrapper", e.listClass])
}, {
default: V(() => [
(P(!0), de(Oe, null, Ue(f.value, (s) => (P(), L(X(e.itemTag), pe({
key: s.nr.id,
style: S.value ? {
transform: `translate${e.direction === "vertical" ? "Y" : "X"}(${s.position}px) translate${e.direction === "vertical" ? "X" : "Y"}(${s.offset}px)`,
width: void 0,
height: void 0
} : null,
class: ["vue-recycle-scroller__item-view", [
e.itemClass,
{
hover: !e.skipHover && y.value === s.nr.key
}
]]
}, Pe(e.skipHover ? {} : {
mouseenter: () => {
y.value = s.nr.key;
},
mouseleave: () => {
y.value = null;
}
})), {
default: V(() => [
G(r.$slots, "default", {
item: s.item,
index: s.nr.index,
active: s.nr.used
})
]),
_: 2
}, 1040, ["style", "class"]))), 128))
]),
_: 3
}, 8, ["style", "class"]))
], 34));
}
}, Ne = {
name: "DtScrollerItem",
inject: [
"vscrollData",
"vscrollParent",
"vscrollResizeObserver"
],
props: {
// eslint-disable-next-line vue/require-prop-types
item: {
required: !0
},
watchData: {
type: Boolean,
default: !1
},
/**
* Indicates if the view is actively used to display an item.
*/
active: {
type: Boolean,
required: !0
},
index: {
type: Number,
default: void 0
},
sizeDependencies: {
type: [Array, Object],
default: null
},
tag: {
type: String,
default: "div"
}
},
computed: {
id() {
if (this.vscrollData.simpleArray) return this.index;
if (this.vscrollData.keyField in this.item) return this.item[this.vscrollData.keyField];
throw new Error(`keyField '${this.vscrollData.keyField}' not found in your item. You should set a valid keyField prop on your Scroller`);
},
size() {
return this.vscrollData.sizes[this.id] || 0;
},
finalActive() {
return this.active && this.vscrollData.active;
}
},
watch: {
watchData: "updateWatchData",
id(e, i) {
if (w(this.$el).$_vs_id = this.id, this.size || this.onDataUpdate(), this.$_sizeObserved) {
const o = this.vscrollData.sizes[i], t = this.vscrollData.sizes[e];
o != null && o !== t && this.applySize(o);
}
},
finalActive(e) {
this.size || (e ? this.vscrollParent.$_undefinedMap[this.id] || (this.vscrollParent.$_undefinedSizes++, this.vscrollParent.$_undefinedMap[this.id] = !0) : this.vscrollParent.$_undefinedMap[this.id] && (this.vscrollParent.$_undefinedSizes--, this.vscrollParent.$_undefinedMap[this.id] = !1)), this.vscrollResizeObserver ? e ? this.observeSize() : this.unobserveSize() : e && this.$_pendingVScrollUpdate === this.id && this.updateSize();
}
},
created() {
if (!this.$isServer && (this.$_forceNextVScrollUpdate = null, this.updateWatchData(), !this.vscrollResizeObserver))
for (const e in this.sizeDependencies)
this.$watch(() => this.sizeDependencies[e], this.onDataUpdate);
},
mounted() {
this.finalActive && (this.updateSize(), this.observeSize());
},
beforeUnmount() {
this.unobserveSize();
},
methods: {
updateSize() {
this.finalActive ? this.$_pendingSizeUpdate !== this.id && (this.$_pendingSizeUpdate = this.id, this.$_forceNextVScrollUpdate = null, this.$_pendingVScrollUpdate = null, this.computeSize(this.id)) : this.$_forceNextVScrollUpdate = this.id;
},
updateWatchData() {
this.watchData && !this.vscrollResizeObserver ? this.$_watchData = this.$watch("item", () => {
this.onDataUpdate();
}, {
deep: !0
}) : this.$_watchData && (this.$_watchData(), this.$_watchData = null);
},
onVscrollUpdate({ force: e }) {
!this.finalActive && e && (this.$_pendingVScrollUpdate = this.id), (this.$_forceNextVScrollUpdate === this.id || e || !this.size) && this.updateSize();
},
onDataUpdate() {
this.updateSize();
},
computeSize(e) {
this.$nextTick(() => {
if (this.id === e) {
const i = w(this.$el).offsetWidth, o = w(this.$el).offsetHeight;
this.applyWidthHeight(i, o);
}
this.$_pendingSizeUpdate = null;
});
},
applyWidthHeight(e, i) {
const o = ~~(this.vscrollParent.direction === "vertical" ? i : e);
o && this.size !== o && this.applySize(o);
},
applySize(e) {
this.vscrollParent.$_undefinedMap[this.id] && (this.vscrollParent.$_undefinedSizes--, this.vscrollParent.$_undefinedMap[this.id] = void 0), this.vscrollData.sizes[this.id] = e;
},
observeSize() {
this.vscrollResizeObserver && (this.$_sizeObserved || (this.vscrollResizeObserver.observe(w(this.$el)), this.$el.$_vs_id = this.id, this.$el.$_vs_onResize = this.onResize, this.$_sizeObserved = !0));
},
unobserveSize() {
this.vscrollResizeObserver && this.$_sizeObserved && (this.vscrollResizeObserver.unobserve(w(this.$el)), this.$el.$_vs_onResize = void 0, this.$_sizeObserved = !1);
},
onResize(e, i, o) {
this.id === e && this.applyWidthHeight(i, o);
}
},
render() {
return Ve(this.tag, this.$slots.default());
}
}, We = {
name: "DynamicScroller",
components: {
CoreScroller: ze,
DtScrollerItem: Ne
},
provide() {
return typeof ResizeObserver < "u" && (this.$_resizeObserver = new ResizeObserver((e) => {
requestAnimationFrame(() => {
if (Array.isArray(e)) {
for (const i of e)
if (i.target && i.target.$_vs_onResize) {
let o, t;
if (i.borderBoxSize) {
const z = i.borderBoxSize[0];
o = z.inlineSize, t = z.blockSize;
} else
o = i.contentRect.width, t = i.contentRect.height;
i.target.$_vs_onResize(i.target.$_vs_id, o, t);
}
}
});
})), {
vscrollData: this.vscrollData,
vscrollParent: this,
vscrollResizeObserver: this.$_resizeObserver
};
},
inheritAttrs: !1,
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: !0
},
/*
* 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: !1
},
/*
* 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: (e) => ["vertical", "horizontal"].includes(e)
},
/*
* 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: !0,
sizes: {},
keyField: this.keyField,
simpleArray: !1
}
};
},
computed: {
simpleArray() {
return this.items.length && typeof this.items[0] != "object";
},
itemsWithSize() {
const e = [], { items: i, keyField: o, simpleArray: t } = this, z = this.vscrollData.sizes, u = i.length;
for (let v = 0; v < u; v++) {
const f = i[v], y = t ? v : f[o];
let S = z[y];
typeof S > "u" && !this.$_undefinedMap[y] && (S = 0), e.push({
item: f,
[o]: y,
size: S
});
}
return e;
}
},
watch: {
simpleArray: {
handler(e) {
this.vscrollData.simpleArray = e;
},
immediate: !0
},
itemsWithSize(e, i) {
const o = w(this.$el).scrollTop;
let t = 0, z = 0;
const u = Math.min(e.length, i.length);
for (let f = 0; f < u && !(t >= o); f++)
t += i[f].size || this.minItemSize, z += e[f].size || this.minItemSize;
const v = z - t;
v !== 0 && (w(this.$el).scrollTop += v);
}
},
beforeCreate() {
this.$_updates = [], this.$_undefinedSizes = 0, this.$_undefinedMap = {};
},
activated() {
this.vscrollData.active = !0;
},
deactivated() {
this.vscrollData.active = !1;
},
methods: {
dynamicScrollerUpdateItems() {
const e = this.$refs.scroller;
e && e._updateVisibleItems(!0);
},
dynamicScrollerUpdateItemsFromBottom() {
const e = this.$refs.scroller;
e && e._updateVisibleItems(!1, !0);
},
scrollToItem(e) {
const i = this.$refs.scroller;
i && i.scrollToItem(e);
},
scrollToBottom() {
if (this.$_scrollingToBottom) return;
this.$_scrollingToBottom = !0;
const e = w(this.$el);
this.$nextTick(() => {
e.scrollTop = e.scrollHeight + 5e3;
const i = () => {
e.scrollTop = e.scrollHeight + 5e3, requestAnimationFrame(() => {
e.scrollTop = e.scrollHeight + 5e3, this.$_undefinedSizes === 0 ? this.$_scrollingToBottom = !1 : requestAnimationFrame(i);
});
};
requestAnimationFrame(i);
});
}
}
};
function Ce(e, i, o, t, z, u) {
const v = me("dt-scroller-item"), f = me("core-scroller");
return P(), L(f, pe({
ref: "scroller",
items: u.itemsWithSize,
"min-item-size": o.minItemSize,
direction: o.direction,
"key-field": o.keyField,
"list-tag": o.listTag,
"item-tag": o.itemTag
}, e.$attrs), {
default: V(({ item: y, index: S, active: h }) => [
Me(v, {
item: y,
active: h,
"size-dependencies": [
y.message
],
"data-index": S
}, {
default: V(() => [
G(e.$slots, "default", ve(ye({
item: y.item,
index: S,
active: h,
itemWithSize: y
})))
]),
_: 2
}, 1032, ["item", "active", "size-dependencies", "data-index"])
]),
_: 3
}, 16, ["items", "min-item-size", "direction", "key-field", "list-tag", "item-tag"]);
}
const je = /* @__PURE__ */ qe(We, [["render", Ce]]), Ye = /* @__PURE__ */ Object.assign({
name: "DtScroller"
}, {
__name: "scroller",
props: {
/**
* The direction of the scroller.
* @values vertical, horizontal
*/
direction: {
type: String,
default: "vertical",
validator: (e) => ["vertical", "horizontal"].includes(e)
},
/**
* Indicates if the items need to react to changes in their size.
* If disabled the itemSize prop is required and you will get improved performance.
* If enabled the minItemSize prop is required and you
* will have reduced performance but the ability to reactively size list items
* @values true, false
*/
dynamic: {
type: Boolean,
default: !1
},
/**
* Display height (or width in horizontal mode) of the items in pixels
* used to calculate the scroll size and position.
* Required if DYNAMIC is false
*/
itemSize: {
type: Number,
default: null
},
/**
* The tag to use for the items.
*/
itemTag: {
type: String,
default: "div"
},
/**
* 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.
* @example items: [ 'item1', 'item2', 'item3' ]
* @example items: [ { id: 1, name: 'item1' }, { id: 2, name: 'item2' }, { id: 3, name: 'item3' } ]
*/
items: {
type: Array,
required: !0
},
/**
* The key field to use for the items.
* If the items are objects, the scroller needs to be able to identify them.
* By default it will look for an id field on the items.
* This can be configured with this prop if you are using another field name.
*/
keyField: {
type: String,
default: "id"
},
/**
* The tag to use for the list.
*/
listTag: {
type: String,
default: "div"
},
/**
* Minimum size used if the height (or width in horizontal mode) of a item is unknown.
* Is required for the initial render of items in DYNAMIC size mode.
*/
minItemSize: {
type: [Number, String],
default: null
},
/**
* The height of the scroller.
* Can be a number (in pixels) or a string (in CSS units).
*/
scrollerHeight: {
type: [String, Number],
default: "100%"
},
/**
* The width of the scroller.
* Can be a number (in pixels) or a string (in CSS units).
*/
scrollerWidth: {
type: [String, Number],
default: "100%"
}
},
emits: [
/**
* Describe when the scroller changes from start/middle/end
* @param {string} position The position of the scroller.
* @values start, middle, end
*/
"user-position"
],
setup(e, { expose: i, emit: o }) {
const t = e;
He("emit", o);
const u = U(null), v = E(() => ({
width: typeof t.scrollerWidth == "number" ? `${t.scrollerWidth}px` : t.scrollerWidth,
height: typeof t.scrollerHeight == "number" ? `${t.scrollerHeight}px` : t.scrollerHeight
}));
fe(t, () => {
F();
}, { deep: !0, immediate: !0 });
function f() {
u.value && u.value.scrollToBottom();
}
function y(x) {
u.value && u.value.scrollToItem(x);
}
function S() {
u.value && (t.dynamic ? u.value.dynamicScrollerUpdateItems() : u.value._updateVisibleItems(!0));
}
function h() {
u.value && (t.dynamic ? u.value.dynamicScrollerUpdateItemsFromBottom() : u.value._updateVisibleItems(!1, !0));
}
function F() {
t.dynamic && !t.minItemSize && console.error("scroller error: 'minItemSize' is required on 'dynamic' mode."), !t.dynamic && !t.itemSize && console.error("scroller error: 'itemSize' is required.");
}
return i({
scrollToBottom: f,
scrollToItem: y,
updateItems: S,
updateItemsFromBottom: h
}), (x, A) => (P(), L(X(e.dynamic ? je : ze), {
ref_key: "scroller",
ref: u,
"data-qa": "dt-scroller",
items: e.items,
"item-size": e.itemSize,
"min-item-size": e.minItemSize,
direction: e.direction,
"key-field": e.keyField,
"list-tag": e.listTag,
"item-tag": e.itemTag,
style: he(v.value),
tabindex: "0",
onUserPosition: A[0] || (A[0] = (O) => x.$emit("user-position", O))
}, {
default: V(({ item: O, index: M, active: H }) => [
G(x.$slots, "default", ve(ye({
item: O,
index: M,
active: H
})))
]),
_: 3
}, 40, ["items", "item-size", "min-item-size", "direction", "key-field", "list-tag", "item-tag", "style"]));
}
});
export {
Ye as default
};
//# sourceMappingURL=scroller.js.map