UNPKG

zp-bee

Version:

zp-bee,是一款基于 Dumi,由 React + TypeScript 开发的组件库 🎉。

825 lines (672 loc) 22.1 kB
var __rest = this && this.__rest || function (s, e) { var t = {}; for (var p in s) { if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; } if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; import React, { useRef, useState, useCallback, useContext, useEffect, useMemo, useImperativeHandle } from 'react'; /** * THE EVENTS OF SCROLLING. */ var SCROLLEVT_NULL = 0 << 0; var SCROLLEVT_INIT = 1 << 0; var SCROLLEVT_RECOMPUTE = 1 << 1; var SCROLLEVT_NATIVE = 1 << 3; var SCROLLEVT_BY_HOOK = 1 << 6; // any events will be `SCROLLEVT_BY_HOOK` if the `ctx.f_top === TOP_CONTINUE`. var TOP_CONTINUE = 0; var TOP_DONE = 1; /** * `INIT` -> `LOADED` -> `RUNNING` */ var e_VT_STATE; (function (e_VT_STATE) { e_VT_STATE[e_VT_STATE["INIT"] = 1] = "INIT"; e_VT_STATE[e_VT_STATE["LOADED"] = 2] = "LOADED"; e_VT_STATE[e_VT_STATE["RUNNING"] = 4] = "RUNNING"; })(e_VT_STATE || (e_VT_STATE = {})); var row_idx = typeof Symbol === 'function' ? Symbol.for('idx') : '$$idx'; function default_context() { return { vt_state: e_VT_STATE.INIT, possible_hight_per_tr: -1, computed_h: 0, re_computed: 0, row_height: [], row_count: 0, prev_row_count: 0, _offset_top: 0 | 0, _offset_head: 0 | 0, _offset_tail: 0 | 1, WH: 0, top: 0, left: 0, evt: SCROLLEVT_NULL, end: false, final_top: 0, f_final_top: TOP_DONE, update_count: 0 }; } /* overload __DIAGNOSIS__. */ function helper_diagnosis(ctx) { if (ctx.hasOwnProperty('CLICK~__DIAGNOSIS__')) return; Object.defineProperty(ctx, 'CLICK~__DIAGNOSIS__', { get: function get() { console.debug('OoOoOoO DIAGNOSIS OoOoOoO'); var expect_height = 0; for (var i = 0; i < ctx.row_count; ++i) { expect_height += ctx.row_height[i]; } var color, explain; if (expect_height > ctx.computed_h) { color = 'color:rgb(15, 179, 9)'; // green explain = 'lower than expected'; } else if (expect_height < ctx.computed_h) { color = 'color:rgb(202, 61, 81)'; // red explain = 'higher than expected'; } else { color = 'color:rgba(0, 0, 0, 0.85)'; explain = 'normal'; } console.debug("%c%d(%d)(".concat(explain, ")"), color, expect_height, ctx.computed_h - expect_height); console.debug('OoOoOoOoOoOoOOoOoOoOoOoOo'); }, configurable: false, enumerable: false }); } function log_debug(ctx, msg) { if (ctx.debug) { var ts = new Date().getTime(); console.debug("%c[".concat(ctx.id, "][").concat(ts, "][").concat(msg, "] vt"), 'color:#a00', ctx); } } // the factory function returns a SimEvent. function make_evt(ne) { var target = ne.target; return { target: { scrollTop: target.scrollTop, scrollLeft: target.scrollLeft }, end: target.scrollHeight - target.clientHeight === Math.round(target.scrollTop), flag: SCROLLEVT_NATIVE }; } /** * Default Implementation Layer. */ /** AntD.TableComponent.table */ var TableImpl = /*#__PURE__*/React.forwardRef(function TableImpl(props, ref) { return /*#__PURE__*/React.createElement("table", Object.assign({ ref: ref }, props)); }); /** AntD.TableComponent.body.wrapper */ function WrapperImpl(props) { return /*#__PURE__*/React.createElement("tbody", Object.assign({}, props)); } /** AntD.TableComponent.body.row */ var RowImpl = /*#__PURE__*/React.forwardRef(function RowImpl(props, ref) { return /*#__PURE__*/React.createElement("tr", Object.assign({ ref: ref }, props)); }); /** * O(n) * returns offset: [head, tail, top] */ function scroll_with_offset(ctx, top) { var _a; var row_height = ctx.row_height, row_count = ctx.row_count, overscanRowCount = ctx.overscanRowCount; var scroll_y = (_a = ctx.scroll) === null || _a === void 0 ? void 0 : _a.y; if (typeof scroll_y === 'number') { ctx._raw_y = scroll_y; ctx._y = ctx._raw_y; } else if (typeof scroll_y === 'string') { /* a string, like "calc(100vh - 300px)" */ ctx._raw_y = scroll_y; ctx._y = ctx.wrap_inst.current.parentElement.offsetHeight; } else { console.warn('VT: did you forget to set `scroll.y`?'); ctx._raw_y = null; ctx._y = ctx.wrap_inst.current.parentElement.offsetHeight; } console.assert(ctx._y >= 0); // to calc `_top` with `row_height` and `overscan`. var _top = 0, i = 0, j = 0; // the height to render. var torender_h = 0; // scroll to the bottom of the table. if (top === -1 && row_count > 0) { i = row_count; while (i > 0 && torender_h < ctx._y) { torender_h += row_height[--i]; } return [0 | i, 0 | row_count, 0 | ctx.computed_h - torender_h]; } for (; i < row_count && _top < top; ++i) { _top += row_height[i]; } // start j from the visible area j = i; for (; j < row_count && torender_h < ctx._y; ++j) { torender_h += row_height[j]; } // keep offset row on top and bottom var overscan = overscanRowCount < 0 ? 0 : overscanRowCount; while (i > 0 && overscan--) { _top -= row_height[--i]; } j += overscanRowCount; if (j > row_count) j = row_count; // returns [head, tail, top]. return [0 | i, 0 | j, 0 | _top]; } // set the variables for offset top/head/tail. function set_offset(ctx, top, head, tail) { ctx._offset_top = 0 | top; ctx._offset_head = 0 | head; ctx._offset_tail = 0 | tail; } function set_scroll(ctx, top, left, evt, end) { ctx.top = top; ctx.left = left; ctx.evt = evt; ctx.end = end; } function update_wrap_style(ctx, h) { if (ctx.WH === h) return; ctx.WH = h; var s = ctx.wrap_inst.current.style; s.height = h ? (s.maxHeight = h + 'px', s.maxHeight) : (s.maxHeight = 'unset', s.maxHeight); } // scrolls the parent element to specified location. function scroll_to(ctx, top, left) { if (!ctx.wrap_inst.current) return; var ele = ctx.wrap_inst.current.parentElement; /** ie */ ele.scrollTop = top; ele.scrollLeft = left; } function _repainting(ctx, ms) { var fn = function fn() { log_debug(ctx, 'REPAINTING'); if (ctx.vt_state === e_VT_STATE.RUNNING && ctx.wrap_inst.current) { // output to the buffer update_wrap_style(ctx, ctx.computed_h); } // free this handle manually. ctx.HND_PAINT = 0; }; return ms < 0 ? window.requestAnimationFrame(fn) : window.setTimeout(fn, ms); } // a wrapper function for `_repainting`. function repainting(ctx) { if (ctx.HND_PAINT > 0) return; ctx.HND_PAINT = _repainting(ctx, -1); } function srs_expand(ctx, len, prev_len, fill_value) { var slen = len - prev_len; var shadow_rows = new Array(slen).fill(fill_value); ctx.row_height = ctx.row_height.concat(shadow_rows); ctx.computed_h += slen * fill_value; } function srs_shrink(ctx, len, prev_len) { if (len === 0) { ctx.computed_h = 0; ctx.row_height.length = 0; return; } var rows = ctx.row_height; var h2shrink = 0; for (var i = len; i < prev_len; ++i) { h2shrink += rows[i]; } ctx.computed_h -= h2shrink; } function set_tr_cnt(ctx, n) { ctx.re_computed = n - ctx.row_count; ctx.prev_row_count = ctx.row_count; ctx.row_count = n; } function VTable(props, ref) { var style = props.style, context = props.context, rest = __rest(props, ["style", "context"]); // force update this vt. var force = useState(0); var ref_func = useRef(); /*********** DOM ************/ var wrap_inst = useMemo(function () { return /*#__PURE__*/React.createRef(); }, []); /*********** context ************/ var ctx = useContext(context); useMemo(function () { Object.assign(ctx, default_context()); if (ctx.wrap_inst && ctx.wrap_inst.current) { ctx.wrap_inst.current.parentElement.onscroll = null; } ctx.wrap_inst = wrap_inst; ctx.top = ctx.initTop; helper_diagnosis(ctx); }, []); /*********** scroll event ************/ var event_queue = useRef([]).current; var HND_RAF = useRef(0); // handle of requestAnimationFrame /* eslint-disable prefer-const */ var RAF_update_self; /*********** scroll hook ************/ var scroll_hook = useCallback(function (e) { if (ctx.vt_state !== e_VT_STATE.RUNNING) return; if (e) { event_queue.push(e); if (ctx.f_final_top === TOP_CONTINUE) { e.flag = SCROLLEVT_BY_HOOK; return RAF_update_self(0); } } if (event_queue.length) { if (HND_RAF.current) cancelAnimationFrame(HND_RAF.current); // requestAnimationFrame, ie >= 10 HND_RAF.current = requestAnimationFrame(RAF_update_self); } }, []); var scroll_hook_native = useCallback(function (e) { scroll_hook(make_evt(e)); }, []); /* requestAnimationFrame callback */ RAF_update_self = useCallback(function (_) { if (ctx.vt_state !== e_VT_STATE.RUNNING) return; var evq = event_queue; var e; // consume the `evq` first. if (evq.length) { e = evq.shift(); } else { return; } var etop = e.target.scrollTop; var eleft = e.target.scrollLeft; var flag = e.flag; if (ctx.debug) { console.debug("[".concat(ctx.id, "][SCROLL] top: %d, left: %d"), etop, eleft); } // checks every tr's height, which will take some time... var offset = scroll_with_offset(ctx, ctx.f_final_top === TOP_CONTINUE ? ctx.final_top : etop); var head = offset[0]; var tail = offset[1]; var top = offset[2]; var prev_head = ctx._offset_head; var prev_tail = ctx._offset_tail; var prev_top = ctx._offset_top; var end; switch (flag) { case SCROLLEVT_INIT: log_debug(ctx, 'SCROLLEVT_INIT'); end = false; break; case SCROLLEVT_BY_HOOK: log_debug(ctx, 'SCROLLEVT_BY_HOOK'); if (head === prev_head && tail === prev_tail && top === prev_top) { ctx.f_final_top = TOP_DONE; if (ctx.final_top === -1) etop = ctx.computed_h - ctx._y; end = true; } else { if (ctx.final_top === -1) etop = top; end = false; } break; case SCROLLEVT_RECOMPUTE: log_debug(ctx, 'SCROLLEVT_RECOMPUTE'); if (head === prev_head && tail === prev_tail && top === prev_top) { HND_RAF.current = 0; if (event_queue.length) scroll_hook(null); // consume the next. return; } end = false; break; case SCROLLEVT_NATIVE: log_debug(ctx, 'SCROLLEVT_NATIVE'); HND_RAF.current = 0; if (ctx.onScroll) { ctx.onScroll({ top: etop, left: eleft, isEnd: e.end }); } if (head === prev_head && tail === prev_tail && top === prev_top) { return; } end = e.end; break; } set_offset(ctx, top, head, tail); set_scroll(ctx, etop, eleft, flag, end); force[1](++ctx.update_count); }, []); // expose to the parent components you are using. useImperativeHandle(ref, function () { // `y === -1` indicates you need to scroll to the bottom of the table. var _scrollTo = function scrollTo(y) { ctx.f_final_top = TOP_CONTINUE; ctx.final_top = y; scroll_hook({ target: { scrollTop: y, scrollLeft: -1 }, flag: SCROLLEVT_BY_HOOK }); }; return { scrollTo: function scrollTo(y) { ref_func.current = function () { return _scrollTo(y); }; ref_func.current(); }, scrollToIndex: function scrollToIndex(idx) { ref_func.current = function () { if (idx > ctx.row_count - 1) idx = ctx.row_count - 1; if (idx < 0) idx = 0; var y = 0; for (var i = 0; i < idx; ++i) { y += ctx.row_height[i]; } _scrollTo(y); }; ref_func.current(); }, /** 获取实际渲染数据的起始范围 */ getDataSourceIndex: function getDataSourceIndex() { return [ctx._offset_head, ctx._offset_tail]; } }; }, []); useEffect(function () { ctx.wrap_inst.current.parentElement.onscroll = scroll_hook_native; }, [wrap_inst]); // update DOM style. useEffect(function () { switch (ctx.evt) { case SCROLLEVT_BY_HOOK: if (ctx.f_final_top === TOP_CONTINUE) { ref_func.current(); } else { scroll_to(ctx, ctx.top, ctx.left); } break; case SCROLLEVT_INIT: case SCROLLEVT_RECOMPUTE: scroll_to(ctx, ctx.top, ctx.left); if (event_queue.length) RAF_update_self(0); // consume the next. break; } }, [force[0] /* for performance. */ ]); useEffect(function () { switch (ctx.vt_state) { case e_VT_STATE.INIT: // init vt without the rows. break; case e_VT_STATE.LOADED: // changed by VTRow only. ctx.vt_state = e_VT_STATE.RUNNING; // force update. scroll_hook({ target: { scrollTop: ctx.top, scrollLeft: 0 }, flag: SCROLLEVT_INIT }); break; case e_VT_STATE.RUNNING: if (ctx.re_computed !== 0) { // rerender ctx.re_computed = 0; scroll_hook({ target: { scrollTop: ctx.top, scrollLeft: ctx.left }, flag: SCROLLEVT_RECOMPUTE }); } break; } }); style.position = 'relative'; style.top = ctx._offset_top; var width = style.width, rest_style = __rest(style, ["width"]); var wrap_style = useMemo(function () { return { width: width, minWidth: '100%', position: 'relative', transform: 'matrix(1, 0, 0, 1, 0, 0)' }; }, [width]); var Table = ctx.components.table; var ctxMemo = useMemo(function () { return Object.assign({}, ctx); }, [ctx._offset_head, ctx._offset_tail]); // TODO context.Provider改造,抽成订阅发布优化 return /*#__PURE__*/React.createElement("div", { ref: wrap_inst, style: wrap_style }, /*#__PURE__*/React.createElement(context.Provider, { value: ctxMemo }, /*#__PURE__*/React.createElement(Table, Object.assign({}, rest, { style: rest_style })))); } function VWrapper(props) { var c = props.children, ctx = props.ctx, restProps = __rest(props, ["children", "ctx"]); var measureRow = c[0]; var rows = c[1]; var Wrapper = ctx.components.body.wrapper; // reference https://github.com/react-component/table/blob/master/src/Body/index.tsx#L6 var len = Array.isArray(rows) ? rows.length : 0; var head = ctx._offset_head, tail = ctx._offset_tail; var trs; switch (ctx.vt_state) { case e_VT_STATE.INIT: if (len >= 0) { console.assert(head === 0); console.assert(tail === 1); if (Array.isArray(rows)) { trs = rows.slice(head, tail); trs[0].props.record[row_idx] = 0; } else { trs = rows; } ctx.re_computed = len; ctx.prev_row_count = len; ctx.row_count = len; } break; case e_VT_STATE.RUNNING: { if (tail > len) { var offset = tail - len; tail -= offset; head -= offset; if (head < 0) head = 0; if (tail < 0) tail = 0; // update the `head` and `tail`. set_offset(ctx, ctx._offset_top /* NOTE: invalided param, just to fill for this param */ , head, tail); } if (ctx.row_count !== len) { set_tr_cnt(ctx, len); } len = ctx.row_count; var prev_len = ctx.prev_row_count; /* shadow-rows rendering phase. */ if (len < prev_len) { srs_shrink(ctx, len, prev_len); } else if (len > prev_len) { var row_h = ctx.row_height; if (len - row_h.length > 0) { srs_expand(ctx, len, row_h.length, ctx.possible_hight_per_tr); } else { // calculate the total height quickly. row_h.fill(ctx.possible_hight_per_tr, prev_len, len); ctx.computed_h += ctx.possible_hight_per_tr * (len - prev_len); } } /** * tree-structure if indent is not 0 * | idx * | 0 || 0a 0 || 0a * | 1 || 0b --collapse occurred-- 1 || 0b * | 2 || - 1 5->2 || 0c * head | 3 || - 1 6->3 || 0d * | 4 || - 2 7->4 || 0e * | 5 || 0c 8->5 || - 1 * | 6 || 0d 9->6 || - 2 * | 7 || 0e 10->7 || - 3 * tail | 8 || - 1 11->8 || 0f * | 9 || - 2 * | 10 || - 3 * | 11 || 0f * | 12 || */ if (len) { var idx = head; trs = rows.slice(idx, tail); trs.forEach(function (el) { return el.props.record[row_idx] = idx++; }); } else { trs = rows; } ctx.prev_row_count = ctx.row_count; } break; case e_VT_STATE.LOADED: console.assert(false); break; } return /*#__PURE__*/React.createElement(Wrapper, Object.assign({}, restProps), measureRow, trs); } function VTRow(props) { var inst = /*#__PURE__*/React.createRef(); var context = props.context, rest = __rest(props, ["context"]); var ctx = context; var children = props.children; var Row = ctx.components.body.row; if (!Array.isArray(children)) { // https://github.com/react-component/table/blob/master/src/Body/BodyRow.tsx#L211 // https://github.com/react-component/table/blob/master/src/Body/index.tsx#L105 // only empty or expanded row... return /*#__PURE__*/React.createElement(Row, Object.assign({}, rest), children); } var row_props = children[0].props; var index = row_props.record[row_idx]; var last_index = useRef(index); var expanded_cls = useMemo(function () { return ".".concat(row_props.prefixCls, "-expanded-row"); }, [row_props.prefixCls]); useEffect(function () { if (ctx.vt_state === e_VT_STATE.RUNNING) { // apply_h(ctx, index, inst.current.offsetHeight, "dom"); repainting(ctx); } else { console.assert(ctx.vt_state === e_VT_STATE.INIT); ctx.vt_state = e_VT_STATE.LOADED; ctx.possible_hight_per_tr = inst.current.offsetHeight; srs_expand(ctx, ctx.row_count, 0, ctx.possible_hight_per_tr); // create a timeout task. _repainting(ctx, 16); } return function () { return repainting(ctx); }; }, []); useEffect(function () { var rowElm = inst.current; // for nested(expanded) elements don't calculate height and add on cache as its already accommodated on parent row // if (!rowElm.matches(".ant-table-row-level-0")) return; var h = rowElm.offsetHeight; var sibling = rowElm.nextSibling; // https://github.com/react-component/table/blob/master/src/Body/BodyRow.tsx#L212 // include heights of all expanded rows, in parent rows while (sibling && sibling.matches(expanded_cls)) { h += sibling.offsetHeight; sibling = sibling.nextSibling; } var curr_h = ctx.row_height[index]; var last_h = ctx.row_height[last_index.current]; ctx.computed_h -= curr_h; ctx.computed_h += last_h; ctx.computed_h += h - last_h; ctx.row_height[index] = h; repainting(ctx); }); return /*#__PURE__*/React.createElement(Row, Object.assign({}, rest, { ref: inst })); } export function _set_components(ctx, components) { var table = components.table, body = components.body, header = components.header; ctx.components.body = Object.assign(Object.assign({}, ctx.components.body), body); if (body && body.cell) { ctx._vtcomponents.body.cell = body.cell; } if (header) { ctx.components.header = header; ctx._vtcomponents.header = header; } if (table) { ctx.components.table = table; } } export function init(fnOpts, deps) { var ctx = useRef( /*#__PURE__*/React.createContext({})).current; var ctx_value = useContext(ctx); var default_ref = useRef(); useMemo(function () { return Object.assign(ctx_value, { id: +new Date(), initTop: 0, overscanRowCount: 5, debug: false, ref: default_ref }, fnOpts()); }, deps); useMemo(function () { var VTable2 = /*#__PURE__*/React.forwardRef(VTable); // set the virtual layer. ctx_value._vtcomponents = { table: function table(props) { return /*#__PURE__*/React.createElement(VTable2, Object.assign({}, props, { context: ctx, ref: ctx_value.ref })); }, body: { wrapper: function wrapper(props) { return /*#__PURE__*/React.createElement(ctx.Consumer, null, function () /* value */ { return /*#__PURE__*/React.createElement(VWrapper, Object.assign({}, props, { ctx: ctx_value })); }); }, row: function row(props) { return /*#__PURE__*/React.createElement(VTRow, Object.assign({}, props, { context: ctx_value })); } } }; // set the default implementation layer. ctx_value.components = {}; _set_components(ctx_value, { table: TableImpl, body: { wrapper: WrapperImpl, row: RowImpl } }); // start -> `INIT` ctx_value.vt_state = e_VT_STATE.INIT; }, []); return ctx_value; }