diff --git a/litmus-ts/TestWidget.tsx b/litmus-ts/TestWidget.tsx
new file mode 100644
index 000000000..e01912f3c
--- /dev/null
+++ b/litmus-ts/TestWidget.tsx
@@ -0,0 +1,16 @@
+/** @jsxImportSource react */
+
+import { Widget } from "cx/widgets";
+import { RenderingContext, Instance } from "cx/ui";
+
+interface TestWidgetProps {}
+
+export class TestWidget extends Widget {
+ constructor(props: TestWidgetProps) {
+ super(props);
+ }
+
+ render(context: RenderingContext, instance: Instance, key: string): React.ReactNode {
+ return
Test Widget
;
+ }
+}
diff --git a/litmus-ts/bugs/GridOnFetchRecords.tsx b/litmus-ts/bugs/GridOnFetchRecords.tsx
new file mode 100644
index 000000000..f3f776bff
--- /dev/null
+++ b/litmus-ts/bugs/GridOnFetchRecords.tsx
@@ -0,0 +1,91 @@
+import {
+ bind,
+ createAccessorModelProxy,
+ createFunctionalComponent,
+ KeySelection,
+} from "cx/ui";
+import {
+ Button,
+ Grid,
+ GridColumnConfig,
+ LookupField,
+ PureContainer,
+} from "cx/widgets";
+
+const tags = ["history", "american", "crime", "tets"].map((tag) => ({
+ name: tag,
+ id: tag,
+}));
+
+interface Model {
+ $page: {
+ showGrid: boolean;
+ tag: string;
+ };
+}
+
+const m = createAccessorModelProxy();
+
+const columns = [
+ {
+ field: "id",
+ header: "ID",
+ },
+ {
+ field: "title",
+ header: "Title",
+ },
+ {
+ field: "body",
+ header: "Body",
+ },
+] as GridColumnConfig[];
+
+export default createFunctionalComponent(() => (
+
+
+));
diff --git a/litmus-ts/index.html b/litmus-ts/index.html
new file mode 100644
index 000000000..e48ad0215
--- /dev/null
+++ b/litmus-ts/index.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+ Cx - Litmus
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/litmus-ts/index.scss b/litmus-ts/index.scss
new file mode 100644
index 000000000..207843205
--- /dev/null
+++ b/litmus-ts/index.scss
@@ -0,0 +1,2 @@
+@import "~cx/src/variables.scss";
+@import "~cx/src/index.scss";
diff --git a/litmus-ts/index.tsx b/litmus-ts/index.tsx
new file mode 100644
index 000000000..1dbaaf725
--- /dev/null
+++ b/litmus-ts/index.tsx
@@ -0,0 +1,31 @@
+import { Store } from "cx/data";
+import "cx/locale/de-de.js";
+import { History, Widget } from "cx/ui";
+import { startHotAppLoop } from "cx/ui/app/startHotAppLoop.js";
+import { Debug, Timing } from "cx/util";
+import { enableMsgBoxAlerts, enableTooltips } from "cx/widgets";
+import Demo from "./bugs/GridOnFetchRecords";
+
+let store = new Store();
+
+Widget.resetCounter();
+//Widget.optimizePrepare = false;
+//Widget.prototype.memoize = false;
+//Timing.enable('vdom-render');
+Timing.enable("app-loop");
+Debug.enable("app-data");
+
+enableTooltips();
+enableMsgBoxAlerts();
+
+History.connect(store, "url");
+
+startHotAppLoop(
+ module,
+ document.getElementById("app"),
+ store,
+
+
+
+ ,
+);
diff --git a/litmus-ts/package.json b/litmus-ts/package.json
new file mode 100644
index 000000000..e6faf9ea1
--- /dev/null
+++ b/litmus-ts/package.json
@@ -0,0 +1,42 @@
+{
+ "name": "litmus-ts",
+ "private": true,
+ "version": "1.0.0",
+ "scripts": {
+ "start": "webpack-dev-server --open",
+ "build": "webpack"
+ },
+ "dependencies": {
+ "cx": "workspace:*",
+ "cx-react": "workspace:*",
+ "react": "^19.2.3",
+ "react-dom": "^19.2.3"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.28.5",
+ "@types/node": "^25.0.3",
+ "@types/react": "^19.2.7",
+ "@types/webpack-env": "^1.18.8",
+ "babel-loader": "^10.0.0",
+ "babel-plugin-transform-cx-imports": "^26.0.1",
+ "copy-webpack-plugin": "^12.0.2",
+ "css-loader": "^7.1.2",
+ "file-loader": "^6.2.0",
+ "html-webpack-plugin": "^5.6.5",
+ "mini-css-extract-plugin": "^2.9.2",
+ "sass": "^1.77.8",
+ "sass-loader": "^16.0.6",
+ "style-loader": "^4.0.0",
+ "svg-url-loader": "^8.0.0",
+ "ts-loader": "9.5.4",
+ "typescript": "^5.7.2",
+ "url-loader": "^4.1.1",
+ "webpack": "^5.103.0",
+ "webpack-bundle-analyzer": "^4.10.2",
+ "webpack-cleanup-plugin": "^0.5.1",
+ "webpack-cli": "^5.1.4",
+ "webpack-combine-loaders": "^2.0.4",
+ "webpack-dev-server": "^5.2.2",
+ "webpack-merge": "^6.0.1"
+ }
+}
diff --git a/litmus-ts/tsconfig.json b/litmus-ts/tsconfig.json
new file mode 100644
index 000000000..11867fe6a
--- /dev/null
+++ b/litmus-ts/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ //"jsxFactory": "cx",
+ "jsxImportSource": "cx",
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "esModuleInterop": true,
+ "allowJs": true,
+ "types": ["webpack-env"],
+ "outDir": "dist",
+ "target": "esnext"
+ },
+ "exclude": ["**.spec.**"]
+}
diff --git a/litmus-ts/webpack.config.js b/litmus-ts/webpack.config.js
new file mode 100644
index 000000000..8f575fe74
--- /dev/null
+++ b/litmus-ts/webpack.config.js
@@ -0,0 +1,182 @@
+const webpack = require("webpack"),
+ HtmlWebpackPlugin = require("html-webpack-plugin"),
+ MiniCssExtractPlugin = require("mini-css-extract-plugin"),
+ { merge } = require("webpack-merge"),
+ path = require("path");
+
+let production = process.env.npm_lifecycle_event && process.env.npm_lifecycle_event.indexOf("build") == 0;
+
+let common = {
+ resolve: {
+ // alias: {
+ // cx: path.resolve(path.join(__dirname, "../packages/cx")),
+ // },
+ extensions: [".js", ".ts", ".tsx", ".json"],
+ },
+
+ module: {
+ rules: [
+ {
+ test: /\.json$/,
+ loader: "json-loader",
+ },
+ {
+ test: /\.(ts|tsx)$/,
+ include: /(litmus-ts)/,
+ exclude: /node_modules/,
+ use: [
+ {
+ loader: "babel-loader",
+ options: {
+ plugins: [["transform-cx-imports", { useSrc: true }]],
+ },
+ },
+ {
+ loader: "ts-loader",
+ options: {
+ colors: false,
+ logLevel: "info",
+ },
+ },
+ ],
+ },
+ ],
+ },
+ entry: {
+ app: [__dirname + "/index.tsx", __dirname + "/index.scss"],
+ },
+ output: {
+ path: __dirname,
+ filename: "[name].js",
+ },
+ plugins: [
+ //new webpack.optimize.CommonsChunkPlugin("vendor"),
+ new HtmlWebpackPlugin({
+ template: path.join(__dirname, "index.html"),
+ }),
+ // new CxScssManifestPlugin({
+ // outputPath: path.join(__dirname, "manifest.scss")
+ // })
+ ],
+ stats: {
+ usedExports: true,
+ },
+ cache: {
+ // 1. Set cache type to filesystem
+ type: "filesystem",
+
+ buildDependencies: {
+ // 2. Add your config as buildDependency to get cache invalidation on config change
+ config: [__filename],
+
+ // 3. If you have other things the build depends on you can add them here
+ // Note that webpack, loaders and all modules referenced from your config are automatically added
+ },
+ },
+};
+
+let specific;
+
+if (production) {
+ specific = {
+ mode: "production",
+ target: ["web", "es2022"], // Modern browsers for better tree-shaking
+ module: {
+ rules: [
+ {
+ test: /\.scss$/,
+ use: [
+ MiniCssExtractPlugin.loader,
+ "css-loader",
+ {
+ loader: "sass-loader",
+ options: {
+ sassOptions: {
+ quietDeps: true,
+ silenceDeprecations: ["legacy-js-api", "import", "global-builtin"],
+ },
+ },
+ },
+ ],
+ },
+ {
+ test: /\.css$/,
+ use: [MiniCssExtractPlugin.loader, "css-loader"],
+ },
+ ],
+ },
+
+ plugins: [
+ new webpack.DefinePlugin({
+ "process.env.NODE_ENV": JSON.stringify("production"),
+ }),
+
+ new MiniCssExtractPlugin({
+ filename: "app.css",
+ }),
+ ],
+
+ output: {
+ path: path.join(__dirname, "dist"),
+ publicPath: ".",
+ },
+
+ optimization: {
+ usedExports: true,
+ sideEffects: false,
+ },
+ };
+} else {
+ specific = {
+ module: {
+ rules: [
+ {
+ test: /\.scss$/,
+ use: [
+ "style-loader",
+ "css-loader",
+ {
+ loader: "sass-loader",
+ options: {
+ sassOptions: {
+ quietDeps: true,
+ silenceDeprecations: ["legacy-js-api", "import", "global-builtin"],
+ },
+ },
+ },
+ ],
+ },
+ {
+ test: /\.css$/,
+ use: ["style-loader", "css-loader"],
+ },
+ ],
+ },
+ mode: "development",
+ //target: ["web", "es5"], //Uncomment for IE testing
+ plugins: [
+ new webpack.DefinePlugin({
+ "process.env.NODE_ENV": JSON.stringify("development"),
+ "process.env.NODE_DEBUG": JSON.stringify(false),
+ }),
+ ],
+ output: {
+ publicPath: "/",
+ },
+ performance: {
+ hints: false,
+ },
+ //devtool: "eval",
+ devServer: {
+ //contentBase: "/",
+ hot: true,
+ port: 8091,
+ //noInfo: false,
+ //inline: true,
+ historyApiFallback: true,
+ //quiet: true
+ },
+ };
+}
+
+module.exports = merge(common, specific);
diff --git a/package.json b/package.json
index 4552de21d..8f6931355 100644
--- a/package.json
+++ b/package.json
@@ -51,7 +51,8 @@
"benchmark",
"fiddle",
"ts-minimal",
- "homedocs"
+ "homedocs",
+ "litmus-ts"
],
"jest": {
"transform": {
diff --git a/packages/cx/package.json b/packages/cx/package.json
index 4965cca9e..35e04f159 100644
--- a/packages/cx/package.json
+++ b/packages/cx/package.json
@@ -1,6 +1,6 @@
{
"name": "cx",
- "version": "26.2.1",
+ "version": "26.2.2",
"description": "Advanced JavaScript UI framework for admin and dashboard applications with ready to use grid, form and chart components.",
"exports": {
"./data": {
diff --git a/packages/cx/src/widgets/grid/Grid.tsx b/packages/cx/src/widgets/grid/Grid.tsx
index 509a47fb2..2b8ceaddd 100644
--- a/packages/cx/src/widgets/grid/Grid.tsx
+++ b/packages/cx/src/widgets/grid/Grid.tsx
@@ -521,7 +521,10 @@ export interface GridConfig extends StyledContainerConfig {
focusable?: boolean;
/** Callback function to retrieve grouping data. */
- onGetGrouping?: (params: any, instance: Instance) => (string | GridGroupingConfig)[];
+ onGetGrouping?: (
+ params: any,
+ instance: Instance,
+ ) => (string | GridGroupingConfig)[];
/** Callback function to dynamically calculate columns. */
onGetColumns?: (
@@ -2988,6 +2991,7 @@ class GridComponent extends VDOM.Component<
pageRecords.forEach((page) => {
if (Array.isArray(page)) {
records.push(...page);
+ lastPage = records.length < pageSize;
} else {
if (!Array.isArray(page.records))
throw new Error(
@@ -3004,13 +3008,12 @@ class GridComponent extends VDOM.Component<
if (!isNumber(totalRecordCount)) {
totalRecordCount = (startPage - 1) * pageSize + records.length;
- if (
- !lastPage &&
- records.length == (endPage - startPage + 1) * pageSize
- )
- totalRecordCount++;
- if (data.totalRecordCount > totalRecordCount)
- totalRecordCount = data.totalRecordCount;
+ if (!lastPage) {
+ if (records.length == (endPage - startPage + 1) * pageSize)
+ totalRecordCount++;
+ if (data.totalRecordCount > totalRecordCount)
+ totalRecordCount = data.totalRecordCount;
+ }
}
instance.buffer.totalRecordCount = data.totalRecordCount =
diff --git a/yarn.lock b/yarn.lock
index 57968b895..7c4b487e5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8923,6 +8923,42 @@ __metadata:
languageName: node
linkType: hard
+"litmus-ts@workspace:litmus-ts":
+ version: 0.0.0-use.local
+ resolution: "litmus-ts@workspace:litmus-ts"
+ dependencies:
+ "@babel/core": "npm:^7.28.5"
+ "@types/node": "npm:^25.0.3"
+ "@types/react": "npm:^19.2.7"
+ "@types/webpack-env": "npm:^1.18.8"
+ babel-loader: "npm:^10.0.0"
+ babel-plugin-transform-cx-imports: "npm:^26.0.1"
+ copy-webpack-plugin: "npm:^12.0.2"
+ css-loader: "npm:^7.1.2"
+ cx: "workspace:*"
+ cx-react: "workspace:*"
+ file-loader: "npm:^6.2.0"
+ html-webpack-plugin: "npm:^5.6.5"
+ mini-css-extract-plugin: "npm:^2.9.2"
+ react: "npm:^19.2.3"
+ react-dom: "npm:^19.2.3"
+ sass: "npm:^1.77.8"
+ sass-loader: "npm:^16.0.6"
+ style-loader: "npm:^4.0.0"
+ svg-url-loader: "npm:^8.0.0"
+ ts-loader: "npm:9.5.4"
+ typescript: "npm:^5.7.2"
+ url-loader: "npm:^4.1.1"
+ webpack: "npm:^5.103.0"
+ webpack-bundle-analyzer: "npm:^4.10.2"
+ webpack-cleanup-plugin: "npm:^0.5.1"
+ webpack-cli: "npm:^5.1.4"
+ webpack-combine-loaders: "npm:^2.0.4"
+ webpack-dev-server: "npm:^5.2.2"
+ webpack-merge: "npm:^6.0.1"
+ languageName: unknown
+ linkType: soft
+
"litmus@workspace:litmus":
version: 0.0.0-use.local
resolution: "litmus@workspace:litmus"