diff --git a/packages/pluggableWidgets/pusher-web/.prettierrc.js b/packages/pluggableWidgets/pusher-web/.prettierrc.js
new file mode 100644
index 0000000000..13dc01f67f
--- /dev/null
+++ b/packages/pluggableWidgets/pusher-web/.prettierrc.js
@@ -0,0 +1,5 @@
+const base = require("@mendix/prettier-config-web-widgets");
+
+module.exports = {
+ ...base
+};
diff --git a/packages/pluggableWidgets/pusher-web/CHANGELOG.md b/packages/pluggableWidgets/pusher-web/CHANGELOG.md
new file mode 100644
index 0000000000..d3fa2771ff
--- /dev/null
+++ b/packages/pluggableWidgets/pusher-web/CHANGELOG.md
@@ -0,0 +1,11 @@
+# Changelog
+
+All notable changes to this widget will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+### Added
+
+- Initial widget scaffolding
diff --git a/packages/pluggableWidgets/pusher-web/README.md b/packages/pluggableWidgets/pusher-web/README.md
new file mode 100644
index 0000000000..cdcf2addda
--- /dev/null
+++ b/packages/pluggableWidgets/pusher-web/README.md
@@ -0,0 +1 @@
+# Pusher Widget
diff --git a/packages/pluggableWidgets/pusher-web/eslint.config.mjs b/packages/pluggableWidgets/pusher-web/eslint.config.mjs
new file mode 100644
index 0000000000..ed68ae9e78
--- /dev/null
+++ b/packages/pluggableWidgets/pusher-web/eslint.config.mjs
@@ -0,0 +1,3 @@
+import config from "@mendix/eslint-config-web-widgets/widget-ts.mjs";
+
+export default config;
diff --git a/packages/pluggableWidgets/pusher-web/package.json b/packages/pluggableWidgets/pusher-web/package.json
new file mode 100644
index 0000000000..a28408cd8c
--- /dev/null
+++ b/packages/pluggableWidgets/pusher-web/package.json
@@ -0,0 +1,63 @@
+{
+ "name": "@mendix/pusher-web",
+ "widgetName": "Pusher",
+ "version": "2.0.0",
+ "description": "Pusher.com integration widget for real-time communication",
+ "copyright": "© Mendix Technology BV 2026. All rights reserved.",
+ "license": "Apache-2.0",
+ "private": true,
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/mendix/web-widgets.git"
+ },
+ "config": {
+ "developmentPort": 3000,
+ "mendixHost": "http://localhost:8080"
+ },
+ "mxpackage": {
+ "name": "Pusher",
+ "type": "widget",
+ "mpkName": "com.mendix.widget.web.Pusher.mpk"
+ },
+ "packagePath": "com.mendix.widget.web",
+ "marketplace": {
+ "minimumMXVersion": "11.11.0",
+ "appName": "Pusher",
+ "reactReady": true
+ },
+ "testProject": {
+ "githubUrl": "https://github.com/mendix/testProjects",
+ "branchName": "pusher-web"
+ },
+ "scripts": {
+ "build": "pluggable-widgets-tools build:web",
+ "create-gh-release": "rui-create-gh-release",
+ "create-translation": "rui-create-translation",
+ "dev": "pluggable-widgets-tools start:web",
+ "e2e": "echo \"Skipping this e2e test\"",
+ "e2edev": "run-e2e dev --with-preps",
+ "format": "prettier --ignore-path ./node_modules/@mendix/prettier-config-web-widgets/global-prettierignore --write .",
+ "lint": "eslint src/ package.json",
+ "release": "pluggable-widgets-tools release:web",
+ "start": "pluggable-widgets-tools start:server",
+ "test": "pluggable-widgets-tools test:unit:web",
+ "update-changelog": "rui-update-changelog-widget",
+ "verify": "rui-verify-package-format"
+ },
+ "dependencies": {
+ "classnames": "^2.5.1",
+ "pusher-js": "^8.5.0"
+ },
+ "devDependencies": {
+ "@mendix/automation-utils": "workspace:*",
+ "@mendix/eslint-config-web-widgets": "workspace:*",
+ "@mendix/pluggable-widgets-tools": "*",
+ "@mendix/prettier-config-web-widgets": "workspace:*",
+ "@mendix/run-e2e": "workspace:^*",
+ "@mendix/widget-plugin-component-kit": "workspace:*",
+ "@mendix/widget-plugin-hooks": "workspace:*",
+ "@mendix/widget-plugin-platform": "workspace:*",
+ "@mendix/widget-plugin-test-utils": "workspace:*",
+ "cross-env": "^7.0.3"
+ }
+}
diff --git a/packages/pluggableWidgets/pusher-web/src/Pusher.editorConfig.ts b/packages/pluggableWidgets/pusher-web/src/Pusher.editorConfig.ts
new file mode 100644
index 0000000000..ff85ea8768
--- /dev/null
+++ b/packages/pluggableWidgets/pusher-web/src/Pusher.editorConfig.ts
@@ -0,0 +1,31 @@
+import { Properties } from "@mendix/pluggable-widgets-tools";
+import {
+ container,
+ rowLayout,
+ structurePreviewPalette,
+ StructurePreviewProps,
+ text
+} from "@mendix/widget-plugin-platform/preview/structure-preview-api";
+import { PusherPreviewProps } from "../typings/PusherProps";
+
+export function getProperties(_values: PusherPreviewProps, defaultProperties: Properties): Properties {
+ return defaultProperties;
+}
+
+export function getPreview(values: PusherPreviewProps, isDarkMode: boolean): StructurePreviewProps {
+ const palette = structurePreviewPalette[isDarkMode ? "dark" : "light"];
+
+ return rowLayout({ columnSize: "grow", borders: true, backgroundColor: palette.background.containerFill })(
+ container()(),
+ rowLayout({ grow: 2, padding: 8 })(text({ fontColor: palette.text.primary, grow: 10 })(getCaption(values))),
+ container()()
+ );
+}
+
+export function getCustomCaption(values: PusherPreviewProps): string {
+ return getCaption(values);
+}
+
+export function getCaption(values: PusherPreviewProps): string {
+ return `Pusher widget [${values.notifyActionName}]`;
+}
diff --git a/packages/pluggableWidgets/pusher-web/src/Pusher.editorPreview.tsx b/packages/pluggableWidgets/pusher-web/src/Pusher.editorPreview.tsx
new file mode 100644
index 0000000000..9e83c4b766
--- /dev/null
+++ b/packages/pluggableWidgets/pusher-web/src/Pusher.editorPreview.tsx
@@ -0,0 +1,9 @@
+import classNames from "classnames";
+import { ReactElement } from "react";
+import { PusherPreviewProps } from "typings/PusherProps";
+import { getCaption } from "./Pusher.editorConfig";
+import "./ui/PusherPreview.css";
+
+export function preview(props: PusherPreviewProps): ReactElement {
+ return
{getCaption(props)}
;
+}
diff --git a/packages/pluggableWidgets/pusher-web/src/Pusher.tsx b/packages/pluggableWidgets/pusher-web/src/Pusher.tsx
new file mode 100644
index 0000000000..8269158b43
--- /dev/null
+++ b/packages/pluggableWidgets/pusher-web/src/Pusher.tsx
@@ -0,0 +1,50 @@
+import classnames from "classnames";
+import { ReactElement, useCallback, useMemo } from "react";
+import { executeAction } from "@mendix/widget-plugin-platform/framework/execute-action";
+import { PusherContainerProps } from "../typings/PusherProps";
+import { usePusherSubscribe } from "./hooks/usePusherSubscribe";
+import "./ui/Pusher.scss";
+import { useMxObjectInfo } from "./utils/useMxObjectInfo";
+
+export default function Pusher(props: PusherContainerProps): ReactElement {
+ const { class: className, objectSource, notifyActionName, notifyEventAction } = props;
+
+ // Extract object GUID and entity name from data source
+ const mxObjectInfo = useMxObjectInfo(objectSource as any); // TODO: fix typings when PWT updated.
+
+ // Event callback - triggered when Pusher event is received
+ const handleEvent = useCallback(
+ (data: unknown) => {
+ console.debug("[Pusher] Event received:", data);
+
+ // Execute configured action
+ executeAction(notifyEventAction);
+ },
+ [notifyEventAction]
+ );
+
+ // Error callback
+ const handleError = useCallback((error: Error) => {
+ console.error("[Pusher] Subscription error:", error.message);
+ }, []);
+
+ // Setup stable subscription config
+ const subscription = useMemo(() => {
+ if (!mxObjectInfo) {
+ return undefined;
+ }
+
+ return {
+ entityName: mxObjectInfo.entityName,
+ guid: mxObjectInfo.guid,
+ eventName: notifyActionName,
+ onEvent: handleEvent,
+ onError: handleError
+ };
+ }, [mxObjectInfo, handleEvent, handleError, notifyActionName]);
+
+ // Initialize Pusher listener
+ usePusherSubscribe(subscription);
+
+ return ;
+}
diff --git a/packages/pluggableWidgets/pusher-web/src/Pusher.xml b/packages/pluggableWidgets/pusher-web/src/Pusher.xml
new file mode 100644
index 0000000000..f17f0a8f7a
--- /dev/null
+++ b/packages/pluggableWidgets/pusher-web/src/Pusher.xml
@@ -0,0 +1,24 @@
+
+
+ Pusher
+ Listen to Notify server action and perform client side action
+ https://docs.mendix.com/appstore/widgets/pusher
+
+
+
+ Object to listen
+
+
+
+
+ Notify action name
+ The name should match the with the 'Notify' parameter `ActionName`
+
+
+
+ Action
+
+
+
+
+
diff --git a/packages/pluggableWidgets/pusher-web/src/__tests__/Pusher.spec.tsx b/packages/pluggableWidgets/pusher-web/src/__tests__/Pusher.spec.tsx
new file mode 100644
index 0000000000..aafb61a263
--- /dev/null
+++ b/packages/pluggableWidgets/pusher-web/src/__tests__/Pusher.spec.tsx
@@ -0,0 +1,7 @@
+describe("Pusher", () => {
+ // TODO: Add comprehensive unit tests for:
+ // - PusherListener class (connection, subscription, cleanup)
+ // - usePusherConfig hook (fetching config)
+ // - usePusherListener hook (React lifecycle integration)
+ // - Event handling and action execution
+});
diff --git a/packages/pluggableWidgets/pusher-web/src/hooks/useFetchPusherConfig.ts b/packages/pluggableWidgets/pusher-web/src/hooks/useFetchPusherConfig.ts
new file mode 100644
index 0000000000..660220c56c
--- /dev/null
+++ b/packages/pluggableWidgets/pusher-web/src/hooks/useFetchPusherConfig.ts
@@ -0,0 +1,29 @@
+import { useEffect, useState } from "react";
+import { fetchPusherConfig } from "../utils/fetchPusherConfig";
+import { PusherConfig } from "../utils/PusherListener";
+
+/**
+ * Provides Pusher configuration fetched from the backend.
+ * Returns null while loading or on error.
+ */
+export function useFetchPusherConfig(): PusherConfig | null {
+ const [config, setConfig] = useState(null);
+
+ useEffect(() => {
+ let active = true;
+ const controller = new AbortController();
+
+ fetchPusherConfig(controller.signal).then(result => {
+ if (active) {
+ setConfig(result);
+ }
+ });
+
+ return () => {
+ active = false;
+ controller.abort();
+ };
+ }, []);
+
+ return config;
+}
diff --git a/packages/pluggableWidgets/pusher-web/src/hooks/usePusherListener.ts b/packages/pluggableWidgets/pusher-web/src/hooks/usePusherListener.ts
new file mode 100644
index 0000000000..d91d7c4ecf
--- /dev/null
+++ b/packages/pluggableWidgets/pusher-web/src/hooks/usePusherListener.ts
@@ -0,0 +1,35 @@
+import { useEffect, useRef, useState } from "react";
+import { useFetchPusherConfig } from "./useFetchPusherConfig";
+import { PusherListener } from "../utils/PusherListener";
+
+/**
+ * Creates and initializes a PusherListener
+ */
+export function usePusherListener(): PusherListener | null {
+ const instanceRef = useRef(null);
+ const [ready, setReady] = useState(false);
+
+ const pusherConfig = useFetchPusherConfig();
+
+ useEffect(() => {
+ if (!pusherConfig) {
+ return;
+ }
+ try {
+ const instance = new PusherListener(pusherConfig);
+ instance.initialize();
+ instanceRef.current = instance;
+ setReady(true);
+ } catch (error) {
+ console.error("[usePusherListenerInstance] Failed to initialize:", error);
+ return;
+ }
+ return () => {
+ instanceRef.current?.destroy();
+ instanceRef.current = null;
+ setReady(false);
+ };
+ }, [pusherConfig]);
+
+ return ready ? instanceRef.current : null;
+}
diff --git a/packages/pluggableWidgets/pusher-web/src/hooks/usePusherSubscribe.ts b/packages/pluggableWidgets/pusher-web/src/hooks/usePusherSubscribe.ts
new file mode 100644
index 0000000000..52841e4f25
--- /dev/null
+++ b/packages/pluggableWidgets/pusher-web/src/hooks/usePusherSubscribe.ts
@@ -0,0 +1,21 @@
+import { useEffect } from "react";
+import { usePusherListener } from "./usePusherListener";
+import { SubscriptionConfig } from "../utils/PusherListener";
+
+/**
+ * Manages the full Pusher listener lifecycle: config fetching, initialization,
+ * and subscription. Resubscribes automatically when subscription changes.
+ */
+export function usePusherSubscribe(subscription?: SubscriptionConfig): void {
+ const listener = usePusherListener();
+
+ useEffect(() => {
+ if (!listener || !subscription) {
+ return;
+ }
+ listener.subscribe(subscription);
+ return () => {
+ listener.unsubscribe();
+ };
+ }, [listener, subscription]);
+}
diff --git a/packages/pluggableWidgets/pusher-web/src/package.xml b/packages/pluggableWidgets/pusher-web/src/package.xml
new file mode 100644
index 0000000000..414872761b
--- /dev/null
+++ b/packages/pluggableWidgets/pusher-web/src/package.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/pluggableWidgets/pusher-web/src/ui/Pusher.scss b/packages/pluggableWidgets/pusher-web/src/ui/Pusher.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/pluggableWidgets/pusher-web/src/ui/PusherPreview.css b/packages/pluggableWidgets/pusher-web/src/ui/PusherPreview.css
new file mode 100644
index 0000000000..c9a0020357
--- /dev/null
+++ b/packages/pluggableWidgets/pusher-web/src/ui/PusherPreview.css
@@ -0,0 +1,6 @@
+.widget-pusher-preview {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px;
+}
diff --git a/packages/pluggableWidgets/pusher-web/src/utils/PusherListener.ts b/packages/pluggableWidgets/pusher-web/src/utils/PusherListener.ts
new file mode 100644
index 0000000000..0f7d3cda10
--- /dev/null
+++ b/packages/pluggableWidgets/pusher-web/src/utils/PusherListener.ts
@@ -0,0 +1,127 @@
+import Pusher, { Channel } from "pusher-js";
+
+export interface PusherConfig {
+ key: string;
+ cluster: string;
+ authEndpoint: string;
+ csrfToken: string;
+}
+
+export interface SubscriptionConfig {
+ entityName: string;
+ guid: string;
+ eventName: string;
+ onEvent: (data: unknown) => void;
+ onError?: (error: Error) => void;
+}
+
+export class PusherListener {
+ private pusher: Pusher | null = null;
+ private currentChannel: Channel | null = null;
+ private currentChannelName: string | null = null;
+ private currentEventName: string | null = null;
+
+ constructor(private config: PusherConfig) {}
+
+ /**
+ * Initialize Pusher connection
+ * Should be called once on widget mount
+ */
+ initialize(): void {
+ if (this.pusher) {
+ return; // Already initialized
+ }
+
+ this.pusher = new Pusher(this.config.key, {
+ cluster: this.config.cluster,
+ authEndpoint: this.config.authEndpoint,
+ auth: {
+ headers: {
+ "X-Csrf-Token": this.config.csrfToken
+ }
+ }
+ });
+
+ // Setup connection event handlers
+ this.pusher.connection.bind("error", this.handleConnectionError);
+ this.pusher.connection.bind("state_change", this.handleStateChange);
+ }
+
+ /**
+ * Subscribe to channel for specific object and event
+ * Automatically unsubscribes from previous channel if different
+ */
+ subscribe(config: SubscriptionConfig): void {
+ if (!this.pusher) {
+ throw new Error("PusherListener not initialized. Call initialize() first.");
+ }
+
+ const channelName = this.buildChannelName(config.entityName, config.guid);
+
+ // If already subscribed to same channel and event, do nothing
+ if (channelName === this.currentChannelName && config.eventName === this.currentEventName) {
+ return;
+ }
+
+ // Unsubscribe from previous channel if exists
+ this.unsubscribe();
+
+ // Subscribe to new channel
+ this.currentChannelName = channelName;
+ this.currentEventName = config.eventName;
+ this.currentChannel = this.pusher.subscribe(channelName);
+
+ // Bind event handler
+ this.currentChannel.bind(config.eventName, config.onEvent);
+
+ // Bind error handler
+ this.currentChannel.bind("pusher:subscription_error", (error: unknown) => {
+ console.error(error);
+ const errorMsg =
+ error === 515
+ ? "Authentication failed. Please verify Pusher configuration constants."
+ : `Subscription error: ${String(error)}`;
+ config.onError?.(new Error(errorMsg));
+ });
+ }
+
+ /**
+ * Unsubscribe from current channel
+ */
+ unsubscribe(): void {
+ if (this.currentChannel && this.currentChannelName) {
+ // Unbind event handler before unsubscribing
+ if (this.currentEventName) {
+ this.currentChannel.unbind(this.currentEventName);
+ }
+ this.pusher?.unsubscribe(this.currentChannelName);
+ this.currentChannel = null;
+ this.currentChannelName = null;
+ this.currentEventName = null;
+ }
+ }
+
+ /**
+ * Disconnect and cleanup
+ * Should be called on widget unmount
+ */
+ destroy(): void {
+ this.unsubscribe();
+ if (this.pusher) {
+ this.pusher.disconnect();
+ this.pusher = null;
+ }
+ }
+
+ private buildChannelName(entityName: string, guid: string): string {
+ return `private-${entityName}.${guid}`;
+ }
+
+ private handleConnectionError = (error: unknown): void => {
+ console.error("[PusherListener] Connection error:", error);
+ };
+
+ private handleStateChange = (states: { previous: string; current: string }): void => {
+ console.debug(`[PusherListener] State changed: ${states.previous} → ${states.current}`);
+ };
+}
diff --git a/packages/pluggableWidgets/pusher-web/src/utils/fetchPusherConfig.ts b/packages/pluggableWidgets/pusher-web/src/utils/fetchPusherConfig.ts
new file mode 100644
index 0000000000..ed97fbe650
--- /dev/null
+++ b/packages/pluggableWidgets/pusher-web/src/utils/fetchPusherConfig.ts
@@ -0,0 +1,53 @@
+import { PusherConfig } from "./PusherListener";
+
+interface KeyData {
+ key: string;
+ cluster: string;
+}
+
+/**
+ * Fetches Pusher configuration from the backend.
+ * Returns a PusherConfig on success, or null on error / invalid response.
+ */
+export async function fetchPusherConfig(signal: AbortSignal): Promise {
+ const baseUrl = (window as any).mx?.remoteUrl || (window as any).mx?.appUrl || "";
+ const endpoint = `${baseUrl}rest/pusher/key`;
+ const csrfToken = (window as any).mx?.sessionData?.csrftoken || "";
+ const authEndpoint = `${baseUrl}rest/pusher/auth`;
+
+ let response: Response;
+ try {
+ response = await fetch(endpoint, {
+ method: "GET",
+ credentials: "same-origin",
+ headers: { "X-Csrf-Token": csrfToken },
+ signal
+ });
+ } catch (error) {
+ if (error instanceof DOMException && error.name === "AbortError") {
+ return null;
+ }
+ console.error("[fetchPusherConfig] Network error:", error);
+ return null;
+ }
+
+ if (response.status !== 200) {
+ console.error(`[fetchPusherConfig] Unexpected response: HTTP ${response.status}`);
+ return null;
+ }
+
+ let keyData: KeyData;
+ try {
+ keyData = JSON.parse(await response.text()) as KeyData;
+ } catch (error) {
+ console.error("[fetchPusherConfig] Failed to parse response:", error);
+ return null;
+ }
+
+ if (!keyData.key || !keyData.cluster) {
+ console.error("[fetchPusherConfig] Invalid response: missing key or cluster");
+ return null;
+ }
+
+ return { key: keyData.key, cluster: keyData.cluster, authEndpoint, csrfToken };
+}
diff --git a/packages/pluggableWidgets/pusher-web/src/utils/useMxObjectInfo.ts b/packages/pluggableWidgets/pusher-web/src/utils/useMxObjectInfo.ts
new file mode 100644
index 0000000000..9ebc31771e
--- /dev/null
+++ b/packages/pluggableWidgets/pusher-web/src/utils/useMxObjectInfo.ts
@@ -0,0 +1,32 @@
+import { DynamicValue, ObjectItem } from "mendix";
+import { useMemo } from "react";
+
+interface MxObjectInfo {
+ guid: string;
+ entityName: string;
+}
+
+export function useMxObjectInfo(objectSource: DynamicValue): MxObjectInfo | undefined {
+ const object = (objectSource as any)?.value as ObjectItem | undefined;
+
+ const guid = object?.id;
+ const entityName = object ? extractEntityName(object) : undefined;
+ return useMemo(() => {
+ if (!guid || !entityName) {
+ return undefined;
+ }
+
+ return {
+ guid,
+ entityName
+ };
+ }, [guid, entityName]);
+}
+
+function extractEntityName(object: ObjectItem): string {
+ const mxObj = (object as any)[Object.getOwnPropertySymbols(object)[0]];
+ if (!mxObj) {
+ throw new Error("Unable to extract entity name. mxObject was not found.");
+ }
+ return mxObj.getEntity();
+}
diff --git a/packages/pluggableWidgets/pusher-web/tsconfig.json b/packages/pluggableWidgets/pusher-web/tsconfig.json
new file mode 100644
index 0000000000..7aa60df0c9
--- /dev/null
+++ b/packages/pluggableWidgets/pusher-web/tsconfig.json
@@ -0,0 +1,30 @@
+{
+ "include": ["./src", "./typings"],
+ "compilerOptions": {
+ "baseUrl": "./",
+ "noEmitOnError": true,
+ "sourceMap": true,
+ "module": "esnext",
+ "target": "es6",
+ "lib": ["esnext", "dom"],
+ "types": ["jest", "node"],
+ "moduleResolution": "node",
+ "declaration": false,
+ "noLib": false,
+ "forceConsistentCasingInFileNames": true,
+ "noFallthroughCasesInSwitch": true,
+ "strict": true,
+ "strictFunctionTypes": false,
+ "skipLibCheck": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "jsx": "react-jsx",
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true,
+ "useUnknownInCatchVariables": false,
+ "exactOptionalPropertyTypes": false,
+ "paths": {
+ "react-hot-loader/root": ["./hot-typescript.ts"]
+ }
+ }
+}
diff --git a/packages/pluggableWidgets/pusher-web/typings/PusherProps.d.ts b/packages/pluggableWidgets/pusher-web/typings/PusherProps.d.ts
new file mode 100644
index 0000000000..66aa96b78a
--- /dev/null
+++ b/packages/pluggableWidgets/pusher-web/typings/PusherProps.d.ts
@@ -0,0 +1,33 @@
+/**
+ * This file was generated from Pusher.xml
+ * WARNING: All changes made to this file will be overwritten
+ * @author Mendix Widgets Framework Team
+ */
+import { CSSProperties } from "react";
+import { ActionValue, ListValue } from "mendix";
+
+export interface PusherContainerProps {
+ name: string;
+ class: string;
+ style?: CSSProperties;
+ tabIndex?: number;
+ objectSource: ListValue;
+ notifyActionName: string;
+ notifyEventAction?: ActionValue;
+}
+
+export interface PusherPreviewProps {
+ /**
+ * @deprecated Deprecated since version 9.18.0. Please use class property instead.
+ */
+ className: string;
+ class: string;
+ style: string;
+ styleObject?: CSSProperties;
+ readOnly: boolean;
+ renderMode: "design" | "xray" | "structure";
+ translate: (text: string) => string;
+ objectSource: {} | { caption: string } | { type: string } | null;
+ notifyActionName: string;
+ notifyEventAction: {} | null;
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index cb1ef83ce6..9b7875ba7d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -2077,6 +2077,46 @@ importers:
specifier: ^7.0.3
version: 7.0.3
+ packages/pluggableWidgets/pusher-web:
+ dependencies:
+ classnames:
+ specifier: ^2.5.1
+ version: 2.5.1
+ pusher-js:
+ specifier: ^8.5.0
+ version: 8.5.0
+ devDependencies:
+ '@mendix/automation-utils':
+ specifier: workspace:*
+ version: link:../../../automation/utils
+ '@mendix/eslint-config-web-widgets':
+ specifier: workspace:*
+ version: link:../../shared/eslint-config-web-widgets
+ '@mendix/pluggable-widgets-tools':
+ specifier: 11.8.0
+ version: 11.8.0(@jest/transform@29.7.0)(@jest/types@30.2.0)(@swc/core@1.13.5)(@types/babel__core@7.20.5)(@types/node@22.14.1)(eslint@9.39.3(jiti@2.6.1))(jest-util@30.2.0)(prettier@3.8.1)(react-dom@18.3.1(react@18.3.1))(react-native@0.82.0(@babel/core@7.29.0)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1)(tslib@2.8.1)
+ '@mendix/prettier-config-web-widgets':
+ specifier: workspace:*
+ version: link:../../shared/prettier-config-web-widgets
+ '@mendix/run-e2e':
+ specifier: workspace:^*
+ version: link:../../../automation/run-e2e
+ '@mendix/widget-plugin-component-kit':
+ specifier: workspace:*
+ version: link:../../shared/widget-plugin-component-kit
+ '@mendix/widget-plugin-hooks':
+ specifier: workspace:*
+ version: link:../../shared/widget-plugin-hooks
+ '@mendix/widget-plugin-platform':
+ specifier: workspace:*
+ version: link:../../shared/widget-plugin-platform
+ '@mendix/widget-plugin-test-utils':
+ specifier: workspace:*
+ version: link:../../shared/widget-plugin-test-utils
+ cross-env:
+ specifier: ^7.0.3
+ version: 7.0.3
+
packages/pluggableWidgets/range-slider-web:
dependencies:
'@mendix/widget-plugin-component-kit':
@@ -3401,12 +3441,6 @@ packages:
peerDependencies:
'@babel/core': 7.29.0
- '@babel/plugin-syntax-typescript@7.27.1':
- resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==}
- engines: {node: '>=6.9.0'}
- peerDependencies:
- '@babel/core': 7.29.0
-
'@babel/plugin-syntax-typescript@7.28.6':
resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==}
engines: {node: '>=6.9.0'}
@@ -9347,6 +9381,9 @@ packages:
pure-rand@6.1.0:
resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==}
+ pusher-js@8.5.0:
+ resolution: {integrity: sha512-V7uzGi9bqOOOyM/6IkJdpFyjGZj7llz1v0oWnYkZKcYLvbz6VcHVLmzKqkvegjuMumpfIEKGLmWHwFb39XFCpw==}
+
qrcode.react@4.2.0:
resolution: {integrity: sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==}
peerDependencies:
@@ -10472,6 +10509,9 @@ packages:
resolution: {integrity: sha512-u6e9e3cTTpE2adQ1DYm3A3r8y3LAONEx1jYvJx6eIgSY4bMLxIxs0riWzI0Z/IK903ikiUzRPZ2c1Ph5lVLkhA==}
hasBin: true
+ tweetnacl@1.0.3:
+ resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==}
+
type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
@@ -10632,6 +10672,7 @@ packages:
uuid@8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
+ deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).
hasBin: true
v8-compile-cache-lib@3.0.1:
@@ -11012,8 +11053,8 @@ snapshots:
'@babel/generator@7.28.3':
dependencies:
- '@babel/parser': 7.28.4
- '@babel/types': 7.28.4
+ '@babel/parser': 7.29.0
+ '@babel/types': 7.29.0
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31
jsesc: 3.1.0
@@ -11230,22 +11271,22 @@ snapshots:
'@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.29.0)':
dependencies:
@@ -11270,67 +11311,62 @@ snapshots:
'@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.27.1
-
- '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.29.0)':
- dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)':
dependencies:
@@ -11873,8 +11909,8 @@ snapshots:
'@babel/template@7.27.2':
dependencies:
'@babel/code-frame': 7.29.0
- '@babel/parser': 7.28.4
- '@babel/types': 7.28.4
+ '@babel/parser': 7.29.0
+ '@babel/types': 7.29.0
'@babel/template@7.28.6':
dependencies:
@@ -12615,7 +12651,7 @@ snapshots:
identity-obj-proxy: 3.0.0
jasmine: 3.99.0
jasmine-core: 3.99.1
- jest: 29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.14.1)(typescript@5.9.3))
+ jest: 29.7.0(@types/node@22.14.1)
jest-environment-jsdom: 29.7.0
jest-jasmine2: 29.7.0
jest-junit: 13.2.0
@@ -13290,7 +13326,7 @@ snapshots:
react-test-renderer: 19.2.4(react@18.3.1)
redent: 3.0.0
optionalDependencies:
- jest: 29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.14.1)(typescript@5.9.3))
+ jest: 29.7.0(@types/node@22.14.1)
'@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
@@ -13354,24 +13390,24 @@ snapshots:
'@types/babel__core@7.20.5':
dependencies:
- '@babel/parser': 7.28.4
- '@babel/types': 7.28.4
+ '@babel/parser': 7.29.0
+ '@babel/types': 7.29.0
'@types/babel__generator': 7.27.0
'@types/babel__template': 7.4.4
'@types/babel__traverse': 7.28.0
'@types/babel__generator@7.27.0':
dependencies:
- '@babel/types': 7.28.4
+ '@babel/types': 7.29.0
'@types/babel__template@7.4.4':
dependencies:
- '@babel/parser': 7.28.4
- '@babel/types': 7.28.4
+ '@babel/parser': 7.29.0
+ '@babel/types': 7.29.0
'@types/babel__traverse@7.28.0':
dependencies:
- '@babel/types': 7.28.4
+ '@babel/types': 7.29.0
'@types/big.js@6.2.2': {}
@@ -14118,7 +14154,7 @@ snapshots:
babel-plugin-istanbul@6.1.1:
dependencies:
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-plugin-utils': 7.28.6
'@istanbuljs/load-nyc-config': 1.1.0
'@istanbuljs/schema': 0.1.3
istanbul-lib-instrument: 5.2.1
@@ -14128,8 +14164,8 @@ snapshots:
babel-plugin-jest-hoist@29.6.3:
dependencies:
- '@babel/template': 7.27.2
- '@babel/types': 7.28.4
+ '@babel/template': 7.28.6
+ '@babel/types': 7.29.0
'@types/babel__core': 7.20.5
'@types/babel__traverse': 7.28.0
@@ -14331,7 +14367,7 @@ snapshots:
caniuse-api@3.0.0:
dependencies:
- browserslist: 4.26.3
+ browserslist: 4.28.1
caniuse-lite: 1.0.30001750
lodash.memoize: 4.1.2
lodash.uniq: 4.5.0
@@ -16589,7 +16625,7 @@ snapshots:
istanbul-lib-instrument@5.2.1:
dependencies:
'@babel/core': 7.29.0
- '@babel/parser': 7.28.4
+ '@babel/parser': 7.29.0
'@istanbuljs/schema': 0.1.3
istanbul-lib-coverage: 3.2.2
semver: 6.3.1
@@ -16599,7 +16635,7 @@ snapshots:
istanbul-lib-instrument@6.0.3:
dependencies:
'@babel/core': 7.29.0
- '@babel/parser': 7.28.4
+ '@babel/parser': 7.29.0
'@istanbuljs/schema': 0.1.3
istanbul-lib-coverage: 3.2.2
semver: 7.7.4
@@ -16973,10 +17009,10 @@ snapshots:
jest-snapshot@29.7.0:
dependencies:
'@babel/core': 7.29.0
- '@babel/generator': 7.28.3
+ '@babel/generator': 7.29.1
'@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.29.0)
- '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.29.0)
- '@babel/types': 7.28.4
+ '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0)
+ '@babel/types': 7.29.0
'@jest/expect-utils': 29.7.0
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
@@ -17046,6 +17082,18 @@ snapshots:
merge-stream: 2.0.0
supports-color: 8.1.1
+ jest@29.7.0(@types/node@22.14.1):
+ dependencies:
+ '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.14.1)(typescript@5.9.3))
+ '@jest/types': 29.6.3
+ import-local: 3.2.0
+ jest-cli: 29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.14.1)(typescript@5.9.3))
+ transitivePeerDependencies:
+ - '@types/node'
+ - babel-plugin-macros
+ - supports-color
+ - ts-node
+
jest@29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.14.1)(typescript@5.9.3)):
dependencies:
'@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.14.1)(typescript@5.9.3))
@@ -18227,7 +18275,7 @@ snapshots:
postcss-colormin@5.3.1(postcss@8.5.6):
dependencies:
- browserslist: 4.26.3
+ browserslist: 4.28.1
caniuse-api: 3.0.0
colord: 2.9.3
postcss: 8.5.6
@@ -18235,7 +18283,7 @@ snapshots:
postcss-convert-values@5.1.3(postcss@8.5.6):
dependencies:
- browserslist: 4.26.3
+ browserslist: 4.28.1
postcss: 8.5.6
postcss-value-parser: 4.2.0
@@ -18285,7 +18333,7 @@ snapshots:
postcss-merge-rules@5.1.4(postcss@8.5.6):
dependencies:
- browserslist: 4.26.3
+ browserslist: 4.28.1
caniuse-api: 3.0.0
cssnano-utils: 3.1.0(postcss@8.5.6)
postcss: 8.5.6
@@ -18305,7 +18353,7 @@ snapshots:
postcss-minify-params@5.1.4(postcss@8.5.6):
dependencies:
- browserslist: 4.26.3
+ browserslist: 4.28.1
cssnano-utils: 3.1.0(postcss@8.5.6)
postcss: 8.5.6
postcss-value-parser: 4.2.0
@@ -18379,7 +18427,7 @@ snapshots:
postcss-normalize-unicode@5.1.1(postcss@8.5.6):
dependencies:
- browserslist: 4.26.3
+ browserslist: 4.28.1
postcss: 8.5.6
postcss-value-parser: 4.2.0
@@ -18402,7 +18450,7 @@ snapshots:
postcss-reduce-initial@5.1.2(postcss@8.5.6):
dependencies:
- browserslist: 4.26.3
+ browserslist: 4.28.1
caniuse-api: 3.0.0
postcss: 8.5.6
@@ -18576,6 +18624,10 @@ snapshots:
pure-rand@6.1.0: {}
+ pusher-js@8.5.0:
+ dependencies:
+ tweetnacl: 1.0.3
+
qrcode.react@4.2.0(react@18.3.1):
dependencies:
react: 18.3.1
@@ -19683,7 +19735,7 @@ snapshots:
stylehacks@5.1.1(postcss@8.5.6):
dependencies:
- browserslist: 4.26.3
+ browserslist: 4.28.1
postcss: 8.5.6
postcss-selector-parser: 6.1.2
@@ -19882,7 +19934,7 @@ snapshots:
bs-logger: 0.2.6
fast-json-stable-stringify: 2.1.0
handlebars: 4.7.8
- jest: 29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.14.1)(typescript@5.9.3))
+ jest: 29.7.0(@types/node@22.14.1)
json5: 2.2.3
lodash.memoize: 4.1.2
make-error: 1.3.6
@@ -19970,6 +20022,8 @@ snapshots:
turbo-windows-64: 2.8.16
turbo-windows-arm64: 2.8.16
+ tweetnacl@1.0.3: {}
+
type-check@0.4.0:
dependencies:
prelude-ls: 1.2.1