@revolist/revogrid 4.23.3 → 4.23.5
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/cjs/{column.drag.plugin-DJueWxN_.js → column.drag.plugin-yUSx3qoN.js} +2 -2
- package/dist/cjs/{header-cell-renderer-QrcXXSkF.js → header-cell-renderer-vVr4IWNV.js} +7 -18
- package/dist/cjs/index.cjs.js +3 -3
- package/dist/cjs/revo-grid.cjs.entry.js +13 -4
- package/dist/cjs/revogr-attribution_7.cjs.entry.js +1 -1
- package/dist/cjs/revogr-data_4.cjs.entry.js +27 -13
- package/dist/cjs/{viewport.store-BlKQ4x9H.js → viewport.store-BscUCiUk.js} +23 -0
- package/dist/collection/components/header/header-renderer.js +10 -7
- package/dist/collection/components/header/resizable.element.js +7 -18
- package/dist/collection/components/revoGrid/viewport.resize.service.js +1 -0
- package/dist/collection/components/scroll/revogr-viewport-scroll.js +15 -5
- package/dist/collection/services/dimension.provider.js +11 -2
- package/dist/collection/store/vp/viewport.helpers.js +23 -0
- package/dist/{revo-grid/column.drag.plugin-DCZW62Uc.js → esm/column.drag.plugin-Cg2U-91C.js} +2 -2
- package/dist/esm/{header-cell-renderer-BsvUQ8GS.js → header-cell-renderer-B-LX2sgu.js} +7 -18
- package/dist/esm/index.js +4 -4
- package/dist/esm/revo-grid.entry.js +13 -4
- package/dist/esm/revogr-attribution_7.entry.js +1 -1
- package/dist/esm/revogr-data_4.entry.js +27 -13
- package/dist/{revo-grid/viewport.store-COAfzAyu.js → esm/viewport.store-_c579YyM.js} +23 -0
- package/dist/{esm/column.drag.plugin-DCZW62Uc.js → revo-grid/column.drag.plugin-Cg2U-91C.js} +2 -2
- package/dist/revo-grid/{header-cell-renderer-BsvUQ8GS.js → header-cell-renderer-B-LX2sgu.js} +7 -18
- package/dist/revo-grid/index.esm.js +4 -4
- package/dist/revo-grid/revo-grid.entry.js +13 -4
- package/dist/revo-grid/revogr-attribution_7.entry.js +1 -1
- package/dist/revo-grid/revogr-data_4.entry.js +27 -13
- package/dist/{esm/viewport.store-COAfzAyu.js → revo-grid/viewport.store-_c579YyM.js} +23 -0
- package/dist/types/components/scroll/revogr-viewport-scroll.d.ts +1 -0
- package/dist/types/services/scroll.dimension.helpers.d.ts +19 -0
- package/dist/types/store/vp/viewport.helpers.d.ts +23 -0
- package/hydrate/index.js +66 -31
- package/hydrate/index.mjs +66 -31
- package/package.json +1 -1
- package/readme.md +127 -16
- package/standalone/revo-grid.js +1 -1
- package/standalone/revogr-header2.js +1 -1
- package/standalone/revogr-viewport-scroll2.js +1 -1
package/hydrate/index.js
CHANGED
|
@@ -12051,6 +12051,29 @@ function getViewportMaxCoordinate(dimension, viewportSize, frameOffset = 1) {
|
|
|
12051
12051
|
}
|
|
12052
12052
|
return Math.max(0, dimension.realSize - viewportSize - dimension.originItemSize * frameOffset);
|
|
12053
12053
|
}
|
|
12054
|
+
/**
|
|
12055
|
+
* Clamp the viewport coordinate within the valid range.
|
|
12056
|
+
* Given a scroll position, pick a safe starting point for rendering visible items.
|
|
12057
|
+
*
|
|
12058
|
+
* Do not use it when you need the exact scroll position for positioning math.
|
|
12059
|
+
*
|
|
12060
|
+
* It does two things:
|
|
12061
|
+
* 1. If the coordinate is below 0, use 0.
|
|
12062
|
+
* 2. If the coordinate is too close to the very end, pull it back a bit.
|
|
12063
|
+
*
|
|
12064
|
+
* Example:
|
|
12065
|
+
*
|
|
12066
|
+
* content height: 1000px
|
|
12067
|
+
* viewport height: 200px
|
|
12068
|
+
* row height: 30px
|
|
12069
|
+
* The real max scroll is:
|
|
12070
|
+
*
|
|
12071
|
+
* 1000 - 200 = 800
|
|
12072
|
+
* But clampViewportCoordinate may clamp to:
|
|
12073
|
+
*
|
|
12074
|
+
* 1000 - 200 - 30 = 770
|
|
12075
|
+
* Ask for 800 -> it returns 770.
|
|
12076
|
+
*/
|
|
12054
12077
|
function clampViewportCoordinate(coordinate, dimension, viewportSize, frameOffset = 1) {
|
|
12055
12078
|
return Math.min(Math.max(0, coordinate), getViewportMaxCoordinate(dimension, viewportSize, frameOffset));
|
|
12056
12079
|
}
|
|
@@ -14946,7 +14969,16 @@ class DimensionProvider {
|
|
|
14946
14969
|
clientSize,
|
|
14947
14970
|
virtualSize: viewportSize,
|
|
14948
14971
|
});
|
|
14949
|
-
|
|
14972
|
+
// Render offset must use the true logical scroll coordinate
|
|
14973
|
+
// It is the logical scroll position that should be used for compressed-scroll offset math.
|
|
14974
|
+
const renderCoordinate = Math.min(Math.max(0, coordinate), // prevents negative scroll positions
|
|
14975
|
+
scrollDimension.logicalScrollSize); // prevents positions past the logical end
|
|
14976
|
+
/**
|
|
14977
|
+
* If viewport sizing is initialized (clientSize and viewportSize are truthy), calculate the offset needed for compressed scroll.
|
|
14978
|
+
* Otherwise keep renderOffset at 0, because there is not enough measurement data yet.
|
|
14979
|
+
*
|
|
14980
|
+
* In normal scrolling, logical and physical coordinates are the same, so offset is 0.
|
|
14981
|
+
*/
|
|
14950
14982
|
const renderOffset = clientSize && viewportSize
|
|
14951
14983
|
? scrollDimension.getRenderOffset(renderCoordinate)
|
|
14952
14984
|
: 0;
|
|
@@ -18408,24 +18440,13 @@ const ResizableElement = (props, children) => {
|
|
|
18408
18440
|
}
|
|
18409
18441
|
})) ||
|
|
18410
18442
|
null;
|
|
18411
|
-
if (props.active) {
|
|
18412
|
-
|
|
18413
|
-
|
|
18414
|
-
|
|
18415
|
-
|
|
18416
|
-
|
|
18417
|
-
|
|
18418
|
-
}, onMouseDown: (e) => directive === null || directive === void 0 ? void 0 : directive.handleDown(e), onTouchStart: (e) => directive === null || directive === void 0 ? void 0 : directive.handleDown(e), class: `resizable resizable-${props.active[p]}` }));
|
|
18419
|
-
}
|
|
18420
|
-
}
|
|
18421
|
-
else {
|
|
18422
|
-
for (let _p in props.active) {
|
|
18423
|
-
resizeEls.push(hAsync("div", { onClick: e => e.preventDefault(), onTouchStart: (e) => e.preventDefault(), onDblClick: e => {
|
|
18424
|
-
var _a;
|
|
18425
|
-
e.preventDefault();
|
|
18426
|
-
(_a = props.onDblClick) === null || _a === void 0 ? void 0 : _a.call(props, e);
|
|
18427
|
-
}, class: `no-resize` }));
|
|
18428
|
-
}
|
|
18443
|
+
if (props.active && props.canResize) {
|
|
18444
|
+
for (let p in props.active) {
|
|
18445
|
+
resizeEls.push(hAsync("div", { onClick: e => e.preventDefault(), onDblClick: e => {
|
|
18446
|
+
var _a;
|
|
18447
|
+
e.preventDefault();
|
|
18448
|
+
(_a = props.onDblClick) === null || _a === void 0 ? void 0 : _a.call(props, e);
|
|
18449
|
+
}, onMouseDown: (e) => directive === null || directive === void 0 ? void 0 : directive.handleDown(e), onTouchStart: (e) => directive === null || directive === void 0 ? void 0 : directive.handleDown(e), class: `resizable resizable-${props.active[p]}` }));
|
|
18429
18450
|
}
|
|
18430
18451
|
}
|
|
18431
18452
|
return (hAsync("div", Object.assign({}, props, { ref: e => e && (directive === null || directive === void 0 ? void 0 : directive.set(e)) }), children, resizeEls));
|
|
@@ -20759,20 +20780,23 @@ class RevogrFocus {
|
|
|
20759
20780
|
}
|
|
20760
20781
|
|
|
20761
20782
|
const HeaderRenderer = (p) => {
|
|
20762
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
20783
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
20784
|
+
const hasSortingSign = !!(((_a = p.data) === null || _a === void 0 ? void 0 : _a.sortable) ||
|
|
20785
|
+
((_b = p.data) === null || _b === void 0 ? void 0 : _b.order) ||
|
|
20786
|
+
((_c = p.data) === null || _c === void 0 ? void 0 : _c.sortIndex));
|
|
20763
20787
|
const cellClass = {
|
|
20764
20788
|
[HEADER_CLASS]: true,
|
|
20765
|
-
[HEADER_SORTABLE_CLASS]: !!((
|
|
20789
|
+
[HEADER_SORTABLE_CLASS]: !!((_d = p.data) === null || _d === void 0 ? void 0 : _d.sortable),
|
|
20766
20790
|
};
|
|
20767
|
-
if ((
|
|
20791
|
+
if ((_e = p.data) === null || _e === void 0 ? void 0 : _e.order) {
|
|
20768
20792
|
cellClass[p.data.order] = true;
|
|
20769
20793
|
}
|
|
20770
20794
|
const dataProps = {
|
|
20771
|
-
key: String((
|
|
20795
|
+
key: String((_g = (_f = p.data) === null || _f === void 0 ? void 0 : _f.prop) !== null && _g !== void 0 ? _g : p.column.itemIndex),
|
|
20772
20796
|
[DATA_COL]: p.column.itemIndex,
|
|
20773
20797
|
canResize: p.canResize,
|
|
20774
|
-
minWidth: ((
|
|
20775
|
-
maxWidth: (
|
|
20798
|
+
minWidth: ((_h = p.data) === null || _h === void 0 ? void 0 : _h.minSize) || MIN_COL_SIZE,
|
|
20799
|
+
maxWidth: (_j = p.data) === null || _j === void 0 ? void 0 : _j.maxSize,
|
|
20776
20800
|
active: p.active || ['r'],
|
|
20777
20801
|
class: cellClass,
|
|
20778
20802
|
style: {
|
|
@@ -20808,7 +20832,7 @@ const HeaderRenderer = (p) => {
|
|
|
20808
20832
|
}
|
|
20809
20833
|
}
|
|
20810
20834
|
}
|
|
20811
|
-
return (hAsync(HeaderCellRenderer, { data: p.data, props: dataProps, additionalData: p.additionalData }, hAsync(SortingSign, { column: p.data }), p.canFilter && ((
|
|
20835
|
+
return (hAsync(HeaderCellRenderer, { data: p.data, props: dataProps, additionalData: p.additionalData }, hasSortingSign ? hAsync(SortingSign, { column: p.data }) : null, p.canFilter && ((_k = p.data) === null || _k === void 0 ? void 0 : _k.filter) !== false ? (hAsync(FilterButton, { column: p.data })) : ('')));
|
|
20812
20836
|
};
|
|
20813
20837
|
|
|
20814
20838
|
const HeaderGroupRenderer = (p) => {
|
|
@@ -21603,6 +21627,7 @@ class GridResizeService {
|
|
|
21603
21627
|
}
|
|
21604
21628
|
destroy() {
|
|
21605
21629
|
var _a;
|
|
21630
|
+
this.apply.cancel();
|
|
21606
21631
|
(_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
21607
21632
|
this.resizeObserver = null;
|
|
21608
21633
|
}
|
|
@@ -21747,6 +21772,10 @@ class RevogrViewportScroll {
|
|
|
21747
21772
|
scroll: this.horizontalScroll.scrollLeft,
|
|
21748
21773
|
noScroll: this.colType !== 'rgCol',
|
|
21749
21774
|
};
|
|
21775
|
+
this.setScrollParams({
|
|
21776
|
+
rgRow: calculatedHeight,
|
|
21777
|
+
rgCol: calculatedWidth,
|
|
21778
|
+
});
|
|
21750
21779
|
// Process changes in order: width first, then height
|
|
21751
21780
|
const dimensions = ['rgCol', 'rgRow'];
|
|
21752
21781
|
for (const dimension of dimensions) {
|
|
@@ -21803,24 +21832,30 @@ class RevogrViewportScroll {
|
|
|
21803
21832
|
}
|
|
21804
21833
|
async componentDidRender() {
|
|
21805
21834
|
var _a, _b, _c, _d;
|
|
21835
|
+
this.setScrollParams({
|
|
21836
|
+
rgRow: (_b = (_a = this.verticalScroll) === null || _a === void 0 ? void 0 : _a.clientHeight) !== null && _b !== void 0 ? _b : 0,
|
|
21837
|
+
rgCol: this.horizontalScroll.clientWidth,
|
|
21838
|
+
});
|
|
21839
|
+
this.setScrollVisibility('rgRow', (_d = (_c = this.verticalScroll) === null || _c === void 0 ? void 0 : _c.clientHeight) !== null && _d !== void 0 ? _d : 0, this.contentHeight);
|
|
21840
|
+
this.setScrollVisibility('rgCol', this.horizontalScroll.clientWidth, this.contentWidth);
|
|
21841
|
+
}
|
|
21842
|
+
setScrollParams(clientSize) {
|
|
21806
21843
|
this.localScrollService.setParams({
|
|
21807
21844
|
contentSize: this.contentHeight,
|
|
21808
|
-
clientSize:
|
|
21845
|
+
clientSize: clientSize.rgRow,
|
|
21809
21846
|
virtualSize: 0,
|
|
21810
21847
|
}, 'rgRow');
|
|
21811
21848
|
this.localScrollService.setParams({
|
|
21812
21849
|
contentSize: this.contentWidth,
|
|
21813
|
-
clientSize:
|
|
21850
|
+
clientSize: clientSize.rgCol,
|
|
21814
21851
|
virtualSize: 0,
|
|
21815
21852
|
}, 'rgCol');
|
|
21816
|
-
this.setScrollVisibility('rgRow', (_d = (_c = this.verticalScroll) === null || _c === void 0 ? void 0 : _c.clientHeight) !== null && _d !== void 0 ? _d : 0, this.contentHeight);
|
|
21817
|
-
this.setScrollVisibility('rgCol', this.horizontalScroll.clientWidth, this.contentWidth);
|
|
21818
21853
|
}
|
|
21819
21854
|
render() {
|
|
21820
21855
|
var _a, _b;
|
|
21821
21856
|
const physicalContentHeight = getContentSize(this.contentHeight, (_b = (_a = this.verticalScroll) === null || _a === void 0 ? void 0 : _a.clientHeight) !== null && _b !== void 0 ? _b : 0);
|
|
21822
21857
|
const physicalContentWidth = getContentSize(this.contentWidth, 0);
|
|
21823
|
-
return (hAsync(Host, { key: '
|
|
21858
|
+
return (hAsync(Host, { key: 'ec8d907976c1d50f7aab3c263be3f0249a274df6', onWheel: this.horizontalMouseWheel, onScroll: (e) => this.applyScroll('rgCol', e) }, hAsync("div", { key: 'e35696a7993ac94261426b45c28d488cdc42b7f0', class: "inner-content-table", style: { width: `${physicalContentWidth}px` } }, hAsync("div", { key: 'a6997451e01eacda1d27d4efa1d74e1748626218', class: "header-wrapper", ref: e => (this.header = e) }, hAsync("slot", { key: '1d401e87d32d5b1531c2211723b552bbc894f22c', name: HEADER_SLOT })), hAsync("div", { key: 'ceab6f9e812d6ca9a0aa376afcd2562a17f505e0', class: "vertical-inner", ref: el => (this.verticalScroll = el), onWheel: this.verticalMouseWheel, onScroll: (e) => this.applyScroll('rgRow', e) }, hAsync("div", { key: 'a9556578a23d6efddec2e982e863aec064042154', class: "content-wrapper", style: { height: `${physicalContentHeight}px` } }, hAsync("slot", { key: '0ae01f9736b9740612e75261f6e3abebda533377', name: CONTENT_SLOT }))), hAsync("div", { key: '09c2565d4ed449a43820f92d97b6558fca3758e7', class: "footer-wrapper", ref: e => (this.footer = e) }, hAsync("slot", { key: '1ffb08ff8138a560cc09d82e3fe22a53e502aafe', name: FOOTER_SLOT })))));
|
|
21824
21859
|
}
|
|
21825
21860
|
/**
|
|
21826
21861
|
* Extra layer for scroll event monitoring, where MouseWheel event is not passing
|
package/hydrate/index.mjs
CHANGED
|
@@ -12049,6 +12049,29 @@ function getViewportMaxCoordinate(dimension, viewportSize, frameOffset = 1) {
|
|
|
12049
12049
|
}
|
|
12050
12050
|
return Math.max(0, dimension.realSize - viewportSize - dimension.originItemSize * frameOffset);
|
|
12051
12051
|
}
|
|
12052
|
+
/**
|
|
12053
|
+
* Clamp the viewport coordinate within the valid range.
|
|
12054
|
+
* Given a scroll position, pick a safe starting point for rendering visible items.
|
|
12055
|
+
*
|
|
12056
|
+
* Do not use it when you need the exact scroll position for positioning math.
|
|
12057
|
+
*
|
|
12058
|
+
* It does two things:
|
|
12059
|
+
* 1. If the coordinate is below 0, use 0.
|
|
12060
|
+
* 2. If the coordinate is too close to the very end, pull it back a bit.
|
|
12061
|
+
*
|
|
12062
|
+
* Example:
|
|
12063
|
+
*
|
|
12064
|
+
* content height: 1000px
|
|
12065
|
+
* viewport height: 200px
|
|
12066
|
+
* row height: 30px
|
|
12067
|
+
* The real max scroll is:
|
|
12068
|
+
*
|
|
12069
|
+
* 1000 - 200 = 800
|
|
12070
|
+
* But clampViewportCoordinate may clamp to:
|
|
12071
|
+
*
|
|
12072
|
+
* 1000 - 200 - 30 = 770
|
|
12073
|
+
* Ask for 800 -> it returns 770.
|
|
12074
|
+
*/
|
|
12052
12075
|
function clampViewportCoordinate(coordinate, dimension, viewportSize, frameOffset = 1) {
|
|
12053
12076
|
return Math.min(Math.max(0, coordinate), getViewportMaxCoordinate(dimension, viewportSize, frameOffset));
|
|
12054
12077
|
}
|
|
@@ -14944,7 +14967,16 @@ class DimensionProvider {
|
|
|
14944
14967
|
clientSize,
|
|
14945
14968
|
virtualSize: viewportSize,
|
|
14946
14969
|
});
|
|
14947
|
-
|
|
14970
|
+
// Render offset must use the true logical scroll coordinate
|
|
14971
|
+
// It is the logical scroll position that should be used for compressed-scroll offset math.
|
|
14972
|
+
const renderCoordinate = Math.min(Math.max(0, coordinate), // prevents negative scroll positions
|
|
14973
|
+
scrollDimension.logicalScrollSize); // prevents positions past the logical end
|
|
14974
|
+
/**
|
|
14975
|
+
* If viewport sizing is initialized (clientSize and viewportSize are truthy), calculate the offset needed for compressed scroll.
|
|
14976
|
+
* Otherwise keep renderOffset at 0, because there is not enough measurement data yet.
|
|
14977
|
+
*
|
|
14978
|
+
* In normal scrolling, logical and physical coordinates are the same, so offset is 0.
|
|
14979
|
+
*/
|
|
14948
14980
|
const renderOffset = clientSize && viewportSize
|
|
14949
14981
|
? scrollDimension.getRenderOffset(renderCoordinate)
|
|
14950
14982
|
: 0;
|
|
@@ -18406,24 +18438,13 @@ const ResizableElement = (props, children) => {
|
|
|
18406
18438
|
}
|
|
18407
18439
|
})) ||
|
|
18408
18440
|
null;
|
|
18409
|
-
if (props.active) {
|
|
18410
|
-
|
|
18411
|
-
|
|
18412
|
-
|
|
18413
|
-
|
|
18414
|
-
|
|
18415
|
-
|
|
18416
|
-
}, onMouseDown: (e) => directive === null || directive === void 0 ? void 0 : directive.handleDown(e), onTouchStart: (e) => directive === null || directive === void 0 ? void 0 : directive.handleDown(e), class: `resizable resizable-${props.active[p]}` }));
|
|
18417
|
-
}
|
|
18418
|
-
}
|
|
18419
|
-
else {
|
|
18420
|
-
for (let _p in props.active) {
|
|
18421
|
-
resizeEls.push(hAsync("div", { onClick: e => e.preventDefault(), onTouchStart: (e) => e.preventDefault(), onDblClick: e => {
|
|
18422
|
-
var _a;
|
|
18423
|
-
e.preventDefault();
|
|
18424
|
-
(_a = props.onDblClick) === null || _a === void 0 ? void 0 : _a.call(props, e);
|
|
18425
|
-
}, class: `no-resize` }));
|
|
18426
|
-
}
|
|
18441
|
+
if (props.active && props.canResize) {
|
|
18442
|
+
for (let p in props.active) {
|
|
18443
|
+
resizeEls.push(hAsync("div", { onClick: e => e.preventDefault(), onDblClick: e => {
|
|
18444
|
+
var _a;
|
|
18445
|
+
e.preventDefault();
|
|
18446
|
+
(_a = props.onDblClick) === null || _a === void 0 ? void 0 : _a.call(props, e);
|
|
18447
|
+
}, onMouseDown: (e) => directive === null || directive === void 0 ? void 0 : directive.handleDown(e), onTouchStart: (e) => directive === null || directive === void 0 ? void 0 : directive.handleDown(e), class: `resizable resizable-${props.active[p]}` }));
|
|
18427
18448
|
}
|
|
18428
18449
|
}
|
|
18429
18450
|
return (hAsync("div", Object.assign({}, props, { ref: e => e && (directive === null || directive === void 0 ? void 0 : directive.set(e)) }), children, resizeEls));
|
|
@@ -20757,20 +20778,23 @@ class RevogrFocus {
|
|
|
20757
20778
|
}
|
|
20758
20779
|
|
|
20759
20780
|
const HeaderRenderer = (p) => {
|
|
20760
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
20781
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
20782
|
+
const hasSortingSign = !!(((_a = p.data) === null || _a === void 0 ? void 0 : _a.sortable) ||
|
|
20783
|
+
((_b = p.data) === null || _b === void 0 ? void 0 : _b.order) ||
|
|
20784
|
+
((_c = p.data) === null || _c === void 0 ? void 0 : _c.sortIndex));
|
|
20761
20785
|
const cellClass = {
|
|
20762
20786
|
[HEADER_CLASS]: true,
|
|
20763
|
-
[HEADER_SORTABLE_CLASS]: !!((
|
|
20787
|
+
[HEADER_SORTABLE_CLASS]: !!((_d = p.data) === null || _d === void 0 ? void 0 : _d.sortable),
|
|
20764
20788
|
};
|
|
20765
|
-
if ((
|
|
20789
|
+
if ((_e = p.data) === null || _e === void 0 ? void 0 : _e.order) {
|
|
20766
20790
|
cellClass[p.data.order] = true;
|
|
20767
20791
|
}
|
|
20768
20792
|
const dataProps = {
|
|
20769
|
-
key: String((
|
|
20793
|
+
key: String((_g = (_f = p.data) === null || _f === void 0 ? void 0 : _f.prop) !== null && _g !== void 0 ? _g : p.column.itemIndex),
|
|
20770
20794
|
[DATA_COL]: p.column.itemIndex,
|
|
20771
20795
|
canResize: p.canResize,
|
|
20772
|
-
minWidth: ((
|
|
20773
|
-
maxWidth: (
|
|
20796
|
+
minWidth: ((_h = p.data) === null || _h === void 0 ? void 0 : _h.minSize) || MIN_COL_SIZE,
|
|
20797
|
+
maxWidth: (_j = p.data) === null || _j === void 0 ? void 0 : _j.maxSize,
|
|
20774
20798
|
active: p.active || ['r'],
|
|
20775
20799
|
class: cellClass,
|
|
20776
20800
|
style: {
|
|
@@ -20806,7 +20830,7 @@ const HeaderRenderer = (p) => {
|
|
|
20806
20830
|
}
|
|
20807
20831
|
}
|
|
20808
20832
|
}
|
|
20809
|
-
return (hAsync(HeaderCellRenderer, { data: p.data, props: dataProps, additionalData: p.additionalData }, hAsync(SortingSign, { column: p.data }), p.canFilter && ((
|
|
20833
|
+
return (hAsync(HeaderCellRenderer, { data: p.data, props: dataProps, additionalData: p.additionalData }, hasSortingSign ? hAsync(SortingSign, { column: p.data }) : null, p.canFilter && ((_k = p.data) === null || _k === void 0 ? void 0 : _k.filter) !== false ? (hAsync(FilterButton, { column: p.data })) : ('')));
|
|
20810
20834
|
};
|
|
20811
20835
|
|
|
20812
20836
|
const HeaderGroupRenderer = (p) => {
|
|
@@ -21601,6 +21625,7 @@ class GridResizeService {
|
|
|
21601
21625
|
}
|
|
21602
21626
|
destroy() {
|
|
21603
21627
|
var _a;
|
|
21628
|
+
this.apply.cancel();
|
|
21604
21629
|
(_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
21605
21630
|
this.resizeObserver = null;
|
|
21606
21631
|
}
|
|
@@ -21745,6 +21770,10 @@ class RevogrViewportScroll {
|
|
|
21745
21770
|
scroll: this.horizontalScroll.scrollLeft,
|
|
21746
21771
|
noScroll: this.colType !== 'rgCol',
|
|
21747
21772
|
};
|
|
21773
|
+
this.setScrollParams({
|
|
21774
|
+
rgRow: calculatedHeight,
|
|
21775
|
+
rgCol: calculatedWidth,
|
|
21776
|
+
});
|
|
21748
21777
|
// Process changes in order: width first, then height
|
|
21749
21778
|
const dimensions = ['rgCol', 'rgRow'];
|
|
21750
21779
|
for (const dimension of dimensions) {
|
|
@@ -21801,24 +21830,30 @@ class RevogrViewportScroll {
|
|
|
21801
21830
|
}
|
|
21802
21831
|
async componentDidRender() {
|
|
21803
21832
|
var _a, _b, _c, _d;
|
|
21833
|
+
this.setScrollParams({
|
|
21834
|
+
rgRow: (_b = (_a = this.verticalScroll) === null || _a === void 0 ? void 0 : _a.clientHeight) !== null && _b !== void 0 ? _b : 0,
|
|
21835
|
+
rgCol: this.horizontalScroll.clientWidth,
|
|
21836
|
+
});
|
|
21837
|
+
this.setScrollVisibility('rgRow', (_d = (_c = this.verticalScroll) === null || _c === void 0 ? void 0 : _c.clientHeight) !== null && _d !== void 0 ? _d : 0, this.contentHeight);
|
|
21838
|
+
this.setScrollVisibility('rgCol', this.horizontalScroll.clientWidth, this.contentWidth);
|
|
21839
|
+
}
|
|
21840
|
+
setScrollParams(clientSize) {
|
|
21804
21841
|
this.localScrollService.setParams({
|
|
21805
21842
|
contentSize: this.contentHeight,
|
|
21806
|
-
clientSize:
|
|
21843
|
+
clientSize: clientSize.rgRow,
|
|
21807
21844
|
virtualSize: 0,
|
|
21808
21845
|
}, 'rgRow');
|
|
21809
21846
|
this.localScrollService.setParams({
|
|
21810
21847
|
contentSize: this.contentWidth,
|
|
21811
|
-
clientSize:
|
|
21848
|
+
clientSize: clientSize.rgCol,
|
|
21812
21849
|
virtualSize: 0,
|
|
21813
21850
|
}, 'rgCol');
|
|
21814
|
-
this.setScrollVisibility('rgRow', (_d = (_c = this.verticalScroll) === null || _c === void 0 ? void 0 : _c.clientHeight) !== null && _d !== void 0 ? _d : 0, this.contentHeight);
|
|
21815
|
-
this.setScrollVisibility('rgCol', this.horizontalScroll.clientWidth, this.contentWidth);
|
|
21816
21851
|
}
|
|
21817
21852
|
render() {
|
|
21818
21853
|
var _a, _b;
|
|
21819
21854
|
const physicalContentHeight = getContentSize(this.contentHeight, (_b = (_a = this.verticalScroll) === null || _a === void 0 ? void 0 : _a.clientHeight) !== null && _b !== void 0 ? _b : 0);
|
|
21820
21855
|
const physicalContentWidth = getContentSize(this.contentWidth, 0);
|
|
21821
|
-
return (hAsync(Host, { key: '
|
|
21856
|
+
return (hAsync(Host, { key: 'ec8d907976c1d50f7aab3c263be3f0249a274df6', onWheel: this.horizontalMouseWheel, onScroll: (e) => this.applyScroll('rgCol', e) }, hAsync("div", { key: 'e35696a7993ac94261426b45c28d488cdc42b7f0', class: "inner-content-table", style: { width: `${physicalContentWidth}px` } }, hAsync("div", { key: 'a6997451e01eacda1d27d4efa1d74e1748626218', class: "header-wrapper", ref: e => (this.header = e) }, hAsync("slot", { key: '1d401e87d32d5b1531c2211723b552bbc894f22c', name: HEADER_SLOT })), hAsync("div", { key: 'ceab6f9e812d6ca9a0aa376afcd2562a17f505e0', class: "vertical-inner", ref: el => (this.verticalScroll = el), onWheel: this.verticalMouseWheel, onScroll: (e) => this.applyScroll('rgRow', e) }, hAsync("div", { key: 'a9556578a23d6efddec2e982e863aec064042154', class: "content-wrapper", style: { height: `${physicalContentHeight}px` } }, hAsync("slot", { key: '0ae01f9736b9740612e75261f6e3abebda533377', name: CONTENT_SLOT }))), hAsync("div", { key: '09c2565d4ed449a43820f92d97b6558fca3758e7', class: "footer-wrapper", ref: e => (this.footer = e) }, hAsync("slot", { key: '1ffb08ff8138a560cc09d82e3fe22a53e502aafe', name: FOOTER_SLOT })))));
|
|
21822
21857
|
}
|
|
21823
21858
|
/**
|
|
21824
21859
|
* Extra layer for scroll event monitoring, where MouseWheel event is not passing
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
<h3 align="center">Powerful data grid component built with <a href="https://stenciljs.com" target="_blank">StencilJS</a>.</h3>
|
|
24
24
|
<p align="center">
|
|
25
|
-
|
|
25
|
+
Render 1M+ rows, millions of cells, and thousands of columns efficiently with no hard row limit in the grid.
|
|
26
26
|
</p>
|
|
27
27
|
<p align="center">
|
|
28
28
|
Used by some of the largest companies in Europe and the United States.
|
|
@@ -31,6 +31,7 @@ Used by some of the largest companies in Europe and the United States.
|
|
|
31
31
|
<p align="center">
|
|
32
32
|
<a href="https://rv-grid.com/demo/">Demo and API</a> •
|
|
33
33
|
<a href="#key-features">Key Features</a> •
|
|
34
|
+
<a href="#revogrid-pro-features">Pro Features</a> •
|
|
34
35
|
<a href="#basic-usage">How To Use</a> •
|
|
35
36
|
<a href="#installation">Installation</a> •
|
|
36
37
|
<a href="https://rv-grid.com/guide/">Docs</a> •
|
|
@@ -44,7 +45,7 @@ Used by some of the largest companies in Europe and the United States.
|
|
|
44
45
|
|
|
45
46
|
## Key Features
|
|
46
47
|
|
|
47
|
-
- **High Performance**:
|
|
48
|
+
- **High Performance**: Render 1M+ rows and millions of cells with no hard row limit in the grid. Virtualization keeps the DOM focused on the visible viewport.
|
|
48
49
|
|
|
49
50
|
- **[Accessibility](https://rv-grid.com/guide/wcag)**: Follows WAI-ARIA best practices.
|
|
50
51
|
|
|
@@ -59,7 +60,7 @@ Used by some of the largest companies in Europe and the United States.
|
|
|
59
60
|
|
|
60
61
|
- **[Intelligent Virtual DOM](https://rv-grid.com/guide/overview#VNode-Reactive-DOM)**: Smart row recombination to minimize redraws.
|
|
61
62
|
|
|
62
|
-
- **[Virtual Scroll](https://rv-grid.com/guide/viewports)**: Handles large datasets
|
|
63
|
+
- **[Virtual Scroll](https://rv-grid.com/guide/viewports)**: Handles large datasets without rendering every row or column into the DOM.
|
|
63
64
|
|
|
64
65
|
- **[Drag and Drop](https://rv-grid.com/guide/row/order)**: Drag and drop in [rows](https://rv-grid.com/guide/row/order) and [columns](https://rv-grid.com/guide/column/order).
|
|
65
66
|
|
|
@@ -112,28 +113,138 @@ Used by some of the largest companies in Europe and the United States.
|
|
|
112
113
|
|
|
113
114
|
- **[Plugin System](https://rv-grid.com/guide/plugin/)**: Create custom plugins or extend existing ones easily.
|
|
114
115
|
|
|
115
|
-
- **[Formula Support](https://rv-grid.com/guide/cell/formula)**: Evaluate formulas in cell data with Excel-like syntax, including basic arithmetic, statistical functions, and cell references.
|
|
116
|
-
- **[Pivot Table](https://rv-grid.com/demo/pivot)**: Transform and analyze data dynamically with drag-and-drop field arrangement, aggregation functions, and interactive filtering capabilities.
|
|
117
|
-
|
|
118
|
-
- **[Master Detail/Subtables/Forms](https://rv-grid.com/guide/row/master.pro)**: Expand rows to reveal child data.
|
|
119
|
-
- **[Cell/Column/Row Span/Merge](https://rv-grid.com/guide/cell/merge)**: Merge cells to form groups.
|
|
120
|
-
- **Auto Merge**: Automatically merges cells with identical values in a column.
|
|
121
|
-
- **Form editig**: Edit forms directly within the grid, featuring all necessary fields, including custom options and markdown support for a fast and enhanced data entry experience.
|
|
122
|
-
|
|
123
116
|
- **Customizations**:
|
|
124
117
|
- [Column header template](https://rv-grid.com/guide/column/header.template).
|
|
125
118
|
- [Row header template](https://rv-grid.com/guide/row/headers).
|
|
126
119
|
- [Cell properties](https://rv-grid.com/guide/cell/) (define custom properties for rendered cells).
|
|
127
|
-
- Nested grids: Build a grid inside a grid, showcasing advanced editing options and user interactions for a more dynamic data presentation.
|
|
128
|
-
- Context Menu: Build context menus for any grid element - from cells to headers. Cut, copy, paste, add rows, modify columns, and more. Fully customizable with your own actions and behaviors.
|
|
129
|
-
|
|
130
|
-
- **[AI Agents and MCP](https://rv-grid.com/guide/mcp)**: Connect Codex, Cursor, Claude Code, and VS Code to version-aware RevoGrid docs, examples, migrations, feature availability, and typed API context.
|
|
131
|
-
|
|
132
120
|
- [Cell template](https://rv-grid.com/guide/cell/renderer) (create your own cell views).
|
|
133
121
|
- [Cell editor](https://rv-grid.com/guide/cell/editor) (use predefined or apply your own custom editors and cell types).
|
|
134
122
|
|
|
123
|
+
- **[AI Agents and MCP](https://rv-grid.com/guide/mcp)**: Connect Codex, Cursor, Claude Code, and VS Code to version-aware RevoGrid docs, examples, migrations, feature availability, and typed API context.
|
|
124
|
+
|
|
135
125
|
- **Rich API & Additional Improvements**: Explore hundreds of other small customizations and improvements in [RevoGrid](https://rv-grid.com/).
|
|
136
126
|
|
|
127
|
+
|
|
128
|
+
## RevoGrid Pro Features
|
|
129
|
+
|
|
130
|
+
RevoGrid Pro extends the core grid with production plugins for advanced data entry, analytics, layout, validation, remote data workflows, and enterprise planning.
|
|
131
|
+
|
|
132
|
+
- **Advanced Data Structures**:
|
|
133
|
+
- **Hierarchical Data View**: Display tree data with expandable rows, nested relationships, sorting, filtering, editing, and drag-and-drop friendly hierarchy handling.
|
|
134
|
+
- **Row Transpose**: Flip records into a row-oriented view when users need to inspect one entity as a vertical form-like grid.
|
|
135
|
+
|
|
136
|
+
- **Headers, Columns, and Grid Structure**:
|
|
137
|
+
- **Multi-Level Headers / Column Groups**: Build stacked, nested headers so related columns can sit under shared parent groups.
|
|
138
|
+
- **Multi-Row Headers**: Render more than one header row for dense tables, grouped labels, or spreadsheet-like header layouts.
|
|
139
|
+
- **Column Group Panel**: Let users drag columns into a grouping panel to create row groups interactively.
|
|
140
|
+
- **Column Group Render Sync**: Keep grouped header rendering aligned during column moves, resizing, and virtualization updates.
|
|
141
|
+
- **Column Move with Groups**: Move columns while preserving grouped header relationships and valid group boundaries.
|
|
142
|
+
- **Column Collapse & Expand (Drill Down)**: Collapse grouped columns to focus on summary information, then expand when details are needed.
|
|
143
|
+
- **Column Hide**: Hide and reveal columns to create focused views without mutating the underlying dataset.
|
|
144
|
+
- **Column Add Popup**: Provide a UI flow for adding columns from available field definitions.
|
|
145
|
+
- **Column Selection**: Select entire columns from the header for bulk operations, copying, formatting, or analysis.
|
|
146
|
+
- **Column Stretch**: Distribute column widths to fill available grid space while respecting sizing constraints.
|
|
147
|
+
- **Column Autosize**: Measure content and automatically adjust column widths for readability.
|
|
148
|
+
- **Merge Cells**: Merge cells across rows and columns for grouped labels, reports, or spreadsheet-style layouts.
|
|
149
|
+
- **Auto Merge / Same-Value Merge**: Automatically merge neighboring cells with matching values to reduce visual repetition.
|
|
150
|
+
- **Sticky Cells and Rows**: Keep important rows, cells, totals, labels, or action areas visible while scrolling.
|
|
151
|
+
- **Overlay Layers**: Push temporary UI layers above the grid for richer interactions without replacing the main grid.
|
|
152
|
+
|
|
153
|
+
- **Remote Data and Large Dataset Workflows**:
|
|
154
|
+
- **Server Loading with Infinite Scroll**: Load remote data as users scroll, keeping memory and DOM usage controlled for large datasets.
|
|
155
|
+
- **Infinite Scroll**: Support total-based or dynamic scrolling patterns where rows are fetched and released in chunks.
|
|
156
|
+
- **Pagination**: Split large datasets into page-sized views with built-in navigation controls.
|
|
157
|
+
- **Remote Pagination**: Keep page index, page size, total counts, and server-loaded rows synchronized with the grid.
|
|
158
|
+
- **Server-Side Grouping**: Request grouped row blocks from a remote source, expand group paths on demand, and combine grouping with remote filtering, sorting, and export.
|
|
159
|
+
|
|
160
|
+
- **Data Management and Change Tracking**:
|
|
161
|
+
- **Audit Trail History**: Record data-change history for traceability, review, and compliance-oriented workflows.
|
|
162
|
+
- **History**: Track user edits and provide undo/redo controls for grid changes.
|
|
163
|
+
- **History Controls**: Add ready-made UI controls for navigating undo and redo stacks.
|
|
164
|
+
- **Range Apply Preview**: Preview copy, paste, or fill changes before applying them to target cells.
|
|
165
|
+
- **Smart Auto Fill**: Fill ranges from an initial value, series, or pattern to speed repetitive data entry.
|
|
166
|
+
- **Excel Export/Import**: Export and import Excel workbook formats including `xlsx`, `xlsm`, `xlsb`, and `xls`.
|
|
167
|
+
- **Multi-Column Export Headers**: Preserve grouped and multi-level column headers when exporting structured grids.
|
|
168
|
+
- **Clipboard with JSON**: Copy and paste structured JSON/object values while keeping control over parsing and rendering.
|
|
169
|
+
|
|
170
|
+
- **Selection and Range Operations**:
|
|
171
|
+
- **Multi-Range Selection**: Work with multiple selected ranges for spreadsheet-style copy, edit, and interaction flows.
|
|
172
|
+
- **Range Selection Limit**: Restrict selected ranges with configurable limits to protect performance and workflow rules.
|
|
173
|
+
- **Row Checkbox Selection**: Select rows through checkbox controls with bulk selection and keyboard-friendly behavior.
|
|
174
|
+
- **Row Advanced Drag and Drop**: Reorder rows with custom drag handles, multi-row behavior, and controlled drop handling.
|
|
175
|
+
- **Row Expand**: Add expandable row affordances for detail views, children, or custom row content.
|
|
176
|
+
- **Row Custom Heading**: Customize row header content for labels, actions, or contextual row information.
|
|
177
|
+
|
|
178
|
+
- **Filtering, Search, and Grouping**:
|
|
179
|
+
- **Advanced Selection Filtering**: Filter with multi-condition selection controls for categorical data.
|
|
180
|
+
- **Selection Filter Cascade**: Cascade filters so each choice narrows available values in dependent filters.
|
|
181
|
+
- **Advanced Slider Filtering**: Filter numeric values with range sliders.
|
|
182
|
+
- **Header Input Filtering**: Put filter inputs in the header area for fast per-column search.
|
|
183
|
+
- **Date Filter**: Filter temporal data by date-specific conditions and ranges.
|
|
184
|
+
- **Row Grouping Drag and Drop**: Drag fields into a panel to group rows dynamically.
|
|
185
|
+
- **Grouping Aggregation**: Calculate grouped summaries such as sum, average, count, min, and max.
|
|
186
|
+
- **Server-Side Group Aggregation**: Combine remote grouped data with aggregate values returned by the server.
|
|
187
|
+
|
|
188
|
+
- **Calculations and Formulas**:
|
|
189
|
+
- **Formula Engine**: Add Excel-like formulas with cell references, dynamic calculations, and a broad function set.
|
|
190
|
+
- **Formula Bar**: Give users a dedicated place to inspect and edit formulas.
|
|
191
|
+
- **Formula Name Manager**: Define reusable named references for formulas.
|
|
192
|
+
- **Formula Dependency Highlighting**: Highlight related cells so users can understand formula inputs and outputs.
|
|
193
|
+
- **Summary Header**: Render calculated summary values in header-level UI.
|
|
194
|
+
|
|
195
|
+
- **Data Visualization and Cell Rendering**:
|
|
196
|
+
- **Charts in Cells**: Render compact visuals such as progress lines, progress lines with values, sparklines, bar charts, timelines, rating stars, badges, change indicators, thumbs, and pie charts.
|
|
197
|
+
- **Heat and Cold Maps**: Color-code values with gradients and legends so users can compare magnitude quickly.
|
|
198
|
+
- **Conditional Formatting**: Apply styling rules based on cell values, row data, or custom logic.
|
|
199
|
+
- **Multi-Cell Formatting**: Choose different renderers or editors inside the same column based on row-level conditions.
|
|
200
|
+
- **Cell Flash**: Highlight recently changed values so live updates are easy to spot.
|
|
201
|
+
- **Avatar, Badge, Progress, Rate, Link, and Chart Column Types**: Use ready-made renderers for common visual data patterns.
|
|
202
|
+
- **Array Renderer**: Display array-like values inside cells with a purpose-built renderer.
|
|
203
|
+
- **Buttons**: Add action buttons inside grid cells for row-level commands.
|
|
204
|
+
|
|
205
|
+
- **Editing and Data Entry**:
|
|
206
|
+
- **Dynamic Form Editing**: Edit row data through a generated form with custom options and richer inputs.
|
|
207
|
+
- **Full Row Editing**: Edit multiple columns in a row as one coordinated editing flow.
|
|
208
|
+
- **Cell Checkbox Editors**: Use checkbox cells that act as both renderer and editor.
|
|
209
|
+
- **Cell Slider Editor**: Edit bounded numeric values with an inline slider.
|
|
210
|
+
- **Cell Counter Editor**: Adjust numeric values with plus/minus controls and configurable steps.
|
|
211
|
+
- **Textarea Editor**: Edit longer text values without leaving the grid.
|
|
212
|
+
- **Dropdown Editor**: Edit values through a dropdown or custom popup.
|
|
213
|
+
- **Timeline Editor**: Edit date ranges and timeline-like values with visual controls.
|
|
214
|
+
- **Cell Validation**: Highlight invalid cells and block or guide invalid edits with custom rules.
|
|
215
|
+
- **Input Validation**: Validate editor input before it is committed to the grid.
|
|
216
|
+
|
|
217
|
+
- **User Interaction and UX**:
|
|
218
|
+
- **Context Menus**: Build menus for cells, rows, columns, and headers with actions such as cut, copy, paste, insert, delete, and custom commands.
|
|
219
|
+
- **Tooltips**: Show contextual information on hover for cells or custom grid elements.
|
|
220
|
+
- **Next Line Focus (WCAG)**: Move focus automatically to the next row during data entry workflows.
|
|
221
|
+
- **WCAG Helpers**: Improve keyboard and screen-reader-oriented grid workflows.
|
|
222
|
+
- **Cell Focus Helpers**: Extend focus behavior for custom editing and navigation scenarios.
|
|
223
|
+
- **Info Panel**: Show contextual status or helper information around grid interactions.
|
|
224
|
+
- **Loader**: Display loading state while remote data, exports, or long-running operations are in progress.
|
|
225
|
+
|
|
226
|
+
- **Development and Integration**:
|
|
227
|
+
- **Event Manager**: Coordinate grid events through one managed layer for easier customization and cleanup.
|
|
228
|
+
- **Observable Props**: React to property changes and synchronize plugin state with grid configuration.
|
|
229
|
+
- **Plugin Dependencies**: Declare and resolve plugin relationships when features need to work together.
|
|
230
|
+
- **Dimension Animation**: Animate row and column dimension changes for smoother layout transitions.
|
|
231
|
+
- **Dropdown Infrastructure**: Reuse popup/dropdown services for custom editors and plugin UI.
|
|
232
|
+
- **Grid Presets and Utilities**: Compose reusable grid configurations, helper functions, and shared plugin behavior.
|
|
233
|
+
|
|
234
|
+
- **Enterprise Analytics and Planning**:
|
|
235
|
+
- **Pivot Table**: Build multidimensional analytics with dynamic row, column, and value dimensions; built-in and custom aggregations; hierarchical rows; generated column groups; flat headers; grand totals and subtotals; values-on-rows layouts; row and column drill-down; grouped aggregate values; drag-and-drop configuration; compact field panel; server-side engine/store contracts; remote sorting and filtering; drilldown contracts; field registry validation; cache keys; serializable errors; and state save/load.
|
|
236
|
+
- **Pivot Configurator**: Give users a drag-and-drop UI for choosing Pivot rows, columns, values, filters, and field layout.
|
|
237
|
+
- **Gantt & Scheduling**: Plan projects with task, dependency, calendar, resource, assignment, and baseline models; summary tasks and milestones; WBS hierarchy; automatic scheduling; working calendars and holidays; constrained scheduling; dependency validation; FS, SS, FF, and SF dependencies with lead/lag; critical path and slack; baselines; resource filtering; task move, resize, create, and progress controls; indent/outdent; timeline zoom; highlighted ranges; non-working time shading; labels; and custom markers.
|
|
238
|
+
- **Gantt Toolbar**: Provide ready-made timeline navigation, baseline, critical path, and export actions for Gantt views.
|
|
239
|
+
- **Gantt Task Editor Dialog**: Edit task fields, dependencies, resources, and scheduling details in a structured dialog.
|
|
240
|
+
|
|
241
|
+
- **Advanced Support**:
|
|
242
|
+
- **AI Agent Support**: Use Pro AI tooling to generate plugins, renderers, templates, and grid configurations.
|
|
243
|
+
- **RevoGrid MCP - AI-Native Grid Intelligence**: Connect AI coding tools to version-aware docs, examples, migrations, feature resolution, and typed API context.
|
|
244
|
+
- **Support via GitHub**: Get engineering support through GitHub-based workflows.
|
|
245
|
+
- **Support via Email**: Get direct support for Pro and enterprise implementation questions.
|
|
246
|
+
|
|
247
|
+
|
|
137
248
|
<h2 align="center">Framework Friendly</h2>
|
|
138
249
|
|
|
139
250
|
<p align="center">
|