@n8n/composables 1.22.0 → 1.24.0

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.
Files changed (58) hide show
  1. package/dist/notification.d.cts +24 -0
  2. package/dist/notification.d.mts +24 -0
  3. package/dist/registries/telemetryRegistry.cjs +5 -0
  4. package/dist/registries/telemetryRegistry.d.cts +2 -0
  5. package/dist/registries/telemetryRegistry.d.mts +2 -0
  6. package/dist/registries/telemetryRegistry.mjs +3 -0
  7. package/dist/telemetryRegistry.cjs +42 -0
  8. package/dist/telemetryRegistry.cjs.map +1 -0
  9. package/dist/telemetryRegistry.d.cts +42 -0
  10. package/dist/telemetryRegistry.d.mts +42 -0
  11. package/dist/telemetryRegistry.mjs +24 -0
  12. package/dist/telemetryRegistry.mjs.map +1 -0
  13. package/dist/types/notification.cjs +0 -0
  14. package/dist/types/notification.d.cts +2 -0
  15. package/dist/types/notification.d.mts +2 -0
  16. package/dist/types/notification.mjs +1 -0
  17. package/dist/useClipboard.cjs +2 -1
  18. package/dist/useClipboard.cjs.map +1 -1
  19. package/dist/useClipboard.d.cts +1 -1
  20. package/dist/useClipboard.d.mts +1 -1
  21. package/dist/useClipboard.mjs +2 -1
  22. package/dist/useClipboard.mjs.map +1 -1
  23. package/dist/useDeviceSupport.d.cts +2 -2
  24. package/dist/useExternalHooks.cjs +5 -0
  25. package/dist/useExternalHooks.d.cts +12 -0
  26. package/dist/useExternalHooks.d.mts +12 -0
  27. package/dist/useExternalHooks.mjs +3 -0
  28. package/dist/useExternalHooks2.cjs +56 -0
  29. package/dist/useExternalHooks2.cjs.map +1 -0
  30. package/dist/useExternalHooks2.mjs +38 -0
  31. package/dist/useExternalHooks2.mjs.map +1 -0
  32. package/dist/useStorage.cjs +13 -0
  33. package/dist/useStorage.cjs.map +1 -0
  34. package/dist/useStorage.d.cts +7 -0
  35. package/dist/useStorage.d.mts +7 -0
  36. package/dist/useStorage.mjs +12 -0
  37. package/dist/useStorage.mjs.map +1 -0
  38. package/dist/useStyles.cjs +19 -0
  39. package/dist/useStyles.cjs.map +1 -0
  40. package/dist/useStyles.d.cts +31 -0
  41. package/dist/useStyles.d.mts +31 -0
  42. package/dist/useStyles.mjs +18 -0
  43. package/dist/useStyles.mjs.map +1 -0
  44. package/dist/useTelemetry.cjs +4 -0
  45. package/dist/useTelemetry.d.cts +7 -0
  46. package/dist/useTelemetry.d.mts +7 -0
  47. package/dist/useTelemetry.mjs +4 -0
  48. package/dist/useTelemetry2.cjs +50 -0
  49. package/dist/useTelemetry2.cjs.map +1 -0
  50. package/dist/useTelemetry2.mjs +44 -0
  51. package/dist/useTelemetry2.mjs.map +1 -0
  52. package/dist/useToast.cjs +170 -0
  53. package/dist/useToast.cjs.map +1 -0
  54. package/dist/useToast.d.cts +29 -0
  55. package/dist/useToast.d.mts +29 -0
  56. package/dist/useToast.mjs +168 -0
  57. package/dist/useToast.mjs.map +1 -0
  58. package/package.json +17 -7
@@ -0,0 +1,31 @@
1
+ //#region src/useStyles.d.ts
2
+ declare const useStyles: () => {
3
+ APP_Z_INDEXES: {
4
+ readonly CONTEXT_MENU: 10;
5
+ readonly APP_HEADER: 99;
6
+ readonly SELECT_BOX: 100;
7
+ readonly CANVAS_ADD_BUTTON: 101;
8
+ readonly APP_SIDEBAR: 999;
9
+ readonly CANVAS_SELECT_BOX: 100;
10
+ readonly TOP_BANNERS: 999;
11
+ readonly NODE_CREATOR: 1700;
12
+ readonly ASK_ASSISTANT_CHAT: 1750;
13
+ readonly NDV: 1800;
14
+ readonly COMMAND_BAR: 1900;
15
+ readonly DIALOGS: 1950;
16
+ readonly MODALS: 2000;
17
+ readonly TOASTS: 2100;
18
+ readonly ASK_ASSISTANT_FLOATING_BUTTON: 3000;
19
+ readonly ASK_ASSISTANT_FLOATING_BUTTON_TOOLTIP: 3000;
20
+ readonly CODEMIRROR_TOOLTIP: 3000;
21
+ readonly FLOATING_UI: 3000;
22
+ readonly DRAGGABLE: 9999999;
23
+ readonly ACTIVE_STICKY: 9999999;
24
+ readonly WORKFLOW_PREVIEW_NDV: 9999999;
25
+ readonly NPS_SURVEY_MODAL: 3001;
26
+ };
27
+ setAppZIndexes: () => void;
28
+ };
29
+ //#endregion
30
+ export { useStyles };
31
+ //# sourceMappingURL=useStyles.d.mts.map
@@ -0,0 +1,18 @@
1
+ import { APP_Z_INDEXES } from "@n8n/frontend-constants/z-indexes";
2
+
3
+ //#region src/useStyles.ts
4
+ const setAppZIndexes = () => {
5
+ Object.keys(APP_Z_INDEXES).forEach((key) => {
6
+ const variableName = `--${key.toLowerCase().replaceAll("_", "-")}--z`;
7
+ const value = APP_Z_INDEXES[key];
8
+ document.documentElement.style.setProperty(variableName, `${value}`);
9
+ });
10
+ };
11
+ const useStyles = () => ({
12
+ APP_Z_INDEXES,
13
+ setAppZIndexes
14
+ });
15
+
16
+ //#endregion
17
+ export { useStyles };
18
+ //# sourceMappingURL=useStyles.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useStyles.mjs","names":[],"sources":["../src/useStyles.ts"],"sourcesContent":["import { APP_Z_INDEXES } from '@n8n/frontend-constants/z-indexes';\n\nconst setAppZIndexes = () => {\n\tObject.keys(APP_Z_INDEXES).forEach((key) => {\n\t\tconst variableName = `--${key.toLowerCase().replaceAll('_', '-')}--z`;\n\t\tconst value = APP_Z_INDEXES[key as keyof typeof APP_Z_INDEXES];\n\t\tdocument.documentElement.style.setProperty(variableName, `${value}`);\n\t});\n};\n\nexport const useStyles = () => ({\n\tAPP_Z_INDEXES,\n\tsetAppZIndexes,\n});\n"],"mappings":";;;AAEA,MAAM,uBAAuB;AAC5B,QAAO,KAAK,cAAc,CAAC,SAAS,QAAQ;EAC3C,MAAM,eAAe,KAAK,IAAI,aAAa,CAAC,WAAW,KAAK,IAAI,CAAC;EACjE,MAAM,QAAQ,cAAc;AAC5B,WAAS,gBAAgB,MAAM,YAAY,cAAc,GAAG,QAAQ;GACnE;;AAGH,MAAa,mBAAmB;CAC/B;CACA;CACA"}
@@ -0,0 +1,4 @@
1
+ require('./telemetryRegistry.cjs');
2
+ const require_useTelemetry = require('./useTelemetry2.cjs');
3
+
4
+ exports.useTelemetry = require_useTelemetry.useTelemetry;
@@ -0,0 +1,7 @@
1
+ import { i as TelemetryNodeParameterChange, n as TelemetryIdentifyOptions, t as Telemetry } from "./telemetryRegistry.cjs";
2
+
3
+ //#region src/useTelemetry.d.ts
4
+ declare function useTelemetry(): Telemetry;
5
+ //#endregion
6
+ export { type Telemetry, type TelemetryIdentifyOptions, type TelemetryNodeParameterChange, useTelemetry };
7
+ //# sourceMappingURL=useTelemetry.d.cts.map
@@ -0,0 +1,7 @@
1
+ import { i as TelemetryNodeParameterChange, n as TelemetryIdentifyOptions, t as Telemetry } from "./telemetryRegistry.mjs";
2
+
3
+ //#region src/useTelemetry.d.ts
4
+ declare function useTelemetry(): Telemetry;
5
+ //#endregion
6
+ export { type Telemetry, type TelemetryIdentifyOptions, type TelemetryNodeParameterChange, useTelemetry };
7
+ //# sourceMappingURL=useTelemetry.d.mts.map
@@ -0,0 +1,4 @@
1
+ import "./telemetryRegistry.mjs";
2
+ import { t as useTelemetry } from "./useTelemetry2.mjs";
3
+
4
+ export { useTelemetry };
@@ -0,0 +1,50 @@
1
+ const require_useDebounce = require('./useDebounce.cjs');
2
+ const require_telemetryRegistry = require('./telemetryRegistry.cjs');
3
+ let vue = require("vue");
4
+
5
+ //#region src/useTelemetry.ts
6
+ /**
7
+ * Null-object telemetry used when no instance has been registered (e.g. in
8
+ * tests that never install the plugin). Telemetry is best-effort and must never
9
+ * throw or break the UI, so every method is a no-op. Any registered instance
10
+ * (via `setTelemetry` or `TelemetryKey`) takes precedence.
11
+ *
12
+ * A plain object literal (not a `Proxy`) so method identity is stable, spies
13
+ * attach, `'track' in noopTelemetry` holds, and there is no accidental `then`
14
+ * that would make `await useTelemetry()` hang.
15
+ */
16
+ const noopTelemetry = {
17
+ init() {},
18
+ identify() {},
19
+ track() {},
20
+ page() {},
21
+ reset() {},
22
+ flushPageEvents() {},
23
+ trackAskAI() {},
24
+ trackAiTransform() {},
25
+ trackNodeParametersValuesChange() {}
26
+ };
27
+ let warnedAboutMissingTelemetry = false;
28
+ /**
29
+ * Returns the active telemetry instance. Resolution order: a component-provided
30
+ * instance (via `TelemetryKey`), then the app-registered singleton (via
31
+ * `setTelemetry`), then a no-op fallback.
32
+ */
33
+ function useTelemetry() {
34
+ const instance = ((0, vue.hasInjectionContext)() ? (0, vue.inject)(require_telemetryRegistry.TelemetryKey, null) : null) ?? require_telemetryRegistry.getRegisteredTelemetry();
35
+ if (instance) return instance;
36
+ if ({}.env.DEV && !warnedAboutMissingTelemetry) {
37
+ warnedAboutMissingTelemetry = true;
38
+ console.warn("[useTelemetry] No telemetry instance registered; using a no-op. Ensure the telemetry plugin is installed at bootstrap.");
39
+ }
40
+ return noopTelemetry;
41
+ }
42
+
43
+ //#endregion
44
+ Object.defineProperty(exports, 'useTelemetry', {
45
+ enumerable: true,
46
+ get: function () {
47
+ return useTelemetry;
48
+ }
49
+ });
50
+ //# sourceMappingURL=useTelemetry2.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTelemetry2.cjs","names":["noopTelemetry: Telemetry","TelemetryKey","getRegisteredTelemetry"],"sources":["../src/useTelemetry.ts"],"sourcesContent":["import { hasInjectionContext, inject } from 'vue';\n\nimport {\n\tgetRegisteredTelemetry,\n\tTelemetryKey,\n\ttype Telemetry,\n} from './registries/telemetryRegistry';\n\n// The telemetry *contract* and the *registration* surface deliberately live in\n// `./registries/telemetryRegistry`: this module is `vi.mock`ed in ~100 test\n// files, and a mock factory replaces the whole module. While `setTelemetry`\n// lived here, any partial factory (the common `{ useTelemetry: () => ... }`\n// shape) also starved the telemetry plugin's own bootstrap import of it.\n// Separate modules make that unreachable — the plugin never imports this one.\nexport type {\n\tTelemetry,\n\tTelemetryIdentifyOptions,\n\tTelemetryNodeParameterChange,\n} from './registries/telemetryRegistry';\n\n/**\n * Null-object telemetry used when no instance has been registered (e.g. in\n * tests that never install the plugin). Telemetry is best-effort and must never\n * throw or break the UI, so every method is a no-op. Any registered instance\n * (via `setTelemetry` or `TelemetryKey`) takes precedence.\n *\n * A plain object literal (not a `Proxy`) so method identity is stable, spies\n * attach, `'track' in noopTelemetry` holds, and there is no accidental `then`\n * that would make `await useTelemetry()` hang.\n */\nconst noopTelemetry: Telemetry = {\n\tinit() {},\n\tidentify() {},\n\ttrack() {},\n\tpage() {},\n\treset() {},\n\tflushPageEvents() {},\n\ttrackAskAI() {},\n\ttrackAiTransform() {},\n\ttrackNodeParametersValuesChange() {},\n};\n\nlet warnedAboutMissingTelemetry = false;\n\n/**\n * Returns the active telemetry instance. Resolution order: a component-provided\n * instance (via `TelemetryKey`), then the app-registered singleton (via\n * `setTelemetry`), then a no-op fallback.\n */\nexport function useTelemetry(): Telemetry {\n\tconst injected = hasInjectionContext() ? inject(TelemetryKey, null) : null;\n\tconst instance = injected ?? getRegisteredTelemetry();\n\tif (instance) return instance;\n\n\t// Falling back to the no-op means the plugin has not registered an instance\n\t// yet. That should never happen in the app (bootstrap registers before any\n\t// consumer runs); warn once in dev so an ordering regression stays visible.\n\tif (import.meta.env.DEV && !warnedAboutMissingTelemetry) {\n\t\twarnedAboutMissingTelemetry = true;\n\t\tconsole.warn(\n\t\t\t'[useTelemetry] No telemetry instance registered; using a no-op. Ensure the telemetry plugin is installed at bootstrap.',\n\t\t);\n\t}\n\treturn noopTelemetry;\n}\n"],"mappings":";;;;;;;;;;;;;;;AA8BA,MAAMA,gBAA2B;CAChC,OAAO;CACP,WAAW;CACX,QAAQ;CACR,OAAO;CACP,QAAQ;CACR,kBAAkB;CAClB,aAAa;CACb,mBAAmB;CACnB,kCAAkC;CAClC;AAED,IAAI,8BAA8B;;;;;;AAOlC,SAAgB,eAA0B;CAEzC,MAAM,0CADgC,mBAAUC,wCAAc,KAAK,GAAG,SACzCC,kDAAwB;AACrD,KAAI,SAAU,QAAO;AAKrB,QAAgB,IAAI,OAAO,CAAC,6BAA6B;AACxD,gCAA8B;AAC9B,UAAQ,KACP,yHACA;;AAEF,QAAO"}
@@ -0,0 +1,44 @@
1
+ import { n as getRegisteredTelemetry, t as TelemetryKey } from "./telemetryRegistry.mjs";
2
+ import { hasInjectionContext, inject } from "vue";
3
+
4
+ //#region src/useTelemetry.ts
5
+ /**
6
+ * Null-object telemetry used when no instance has been registered (e.g. in
7
+ * tests that never install the plugin). Telemetry is best-effort and must never
8
+ * throw or break the UI, so every method is a no-op. Any registered instance
9
+ * (via `setTelemetry` or `TelemetryKey`) takes precedence.
10
+ *
11
+ * A plain object literal (not a `Proxy`) so method identity is stable, spies
12
+ * attach, `'track' in noopTelemetry` holds, and there is no accidental `then`
13
+ * that would make `await useTelemetry()` hang.
14
+ */
15
+ const noopTelemetry = {
16
+ init() {},
17
+ identify() {},
18
+ track() {},
19
+ page() {},
20
+ reset() {},
21
+ flushPageEvents() {},
22
+ trackAskAI() {},
23
+ trackAiTransform() {},
24
+ trackNodeParametersValuesChange() {}
25
+ };
26
+ let warnedAboutMissingTelemetry = false;
27
+ /**
28
+ * Returns the active telemetry instance. Resolution order: a component-provided
29
+ * instance (via `TelemetryKey`), then the app-registered singleton (via
30
+ * `setTelemetry`), then a no-op fallback.
31
+ */
32
+ function useTelemetry() {
33
+ const instance = (hasInjectionContext() ? inject(TelemetryKey, null) : null) ?? getRegisteredTelemetry();
34
+ if (instance) return instance;
35
+ if (import.meta.env.DEV && !warnedAboutMissingTelemetry) {
36
+ warnedAboutMissingTelemetry = true;
37
+ console.warn("[useTelemetry] No telemetry instance registered; using a no-op. Ensure the telemetry plugin is installed at bootstrap.");
38
+ }
39
+ return noopTelemetry;
40
+ }
41
+
42
+ //#endregion
43
+ export { useTelemetry as t };
44
+ //# sourceMappingURL=useTelemetry2.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTelemetry2.mjs","names":["noopTelemetry: Telemetry"],"sources":["../src/useTelemetry.ts"],"sourcesContent":["import { hasInjectionContext, inject } from 'vue';\n\nimport {\n\tgetRegisteredTelemetry,\n\tTelemetryKey,\n\ttype Telemetry,\n} from './registries/telemetryRegistry';\n\n// The telemetry *contract* and the *registration* surface deliberately live in\n// `./registries/telemetryRegistry`: this module is `vi.mock`ed in ~100 test\n// files, and a mock factory replaces the whole module. While `setTelemetry`\n// lived here, any partial factory (the common `{ useTelemetry: () => ... }`\n// shape) also starved the telemetry plugin's own bootstrap import of it.\n// Separate modules make that unreachable — the plugin never imports this one.\nexport type {\n\tTelemetry,\n\tTelemetryIdentifyOptions,\n\tTelemetryNodeParameterChange,\n} from './registries/telemetryRegistry';\n\n/**\n * Null-object telemetry used when no instance has been registered (e.g. in\n * tests that never install the plugin). Telemetry is best-effort and must never\n * throw or break the UI, so every method is a no-op. Any registered instance\n * (via `setTelemetry` or `TelemetryKey`) takes precedence.\n *\n * A plain object literal (not a `Proxy`) so method identity is stable, spies\n * attach, `'track' in noopTelemetry` holds, and there is no accidental `then`\n * that would make `await useTelemetry()` hang.\n */\nconst noopTelemetry: Telemetry = {\n\tinit() {},\n\tidentify() {},\n\ttrack() {},\n\tpage() {},\n\treset() {},\n\tflushPageEvents() {},\n\ttrackAskAI() {},\n\ttrackAiTransform() {},\n\ttrackNodeParametersValuesChange() {},\n};\n\nlet warnedAboutMissingTelemetry = false;\n\n/**\n * Returns the active telemetry instance. Resolution order: a component-provided\n * instance (via `TelemetryKey`), then the app-registered singleton (via\n * `setTelemetry`), then a no-op fallback.\n */\nexport function useTelemetry(): Telemetry {\n\tconst injected = hasInjectionContext() ? inject(TelemetryKey, null) : null;\n\tconst instance = injected ?? getRegisteredTelemetry();\n\tif (instance) return instance;\n\n\t// Falling back to the no-op means the plugin has not registered an instance\n\t// yet. That should never happen in the app (bootstrap registers before any\n\t// consumer runs); warn once in dev so an ordering regression stays visible.\n\tif (import.meta.env.DEV && !warnedAboutMissingTelemetry) {\n\t\twarnedAboutMissingTelemetry = true;\n\t\tconsole.warn(\n\t\t\t'[useTelemetry] No telemetry instance registered; using a no-op. Ensure the telemetry plugin is installed at bootstrap.',\n\t\t);\n\t}\n\treturn noopTelemetry;\n}\n"],"mappings":";;;;;;;;;;;;;;AA8BA,MAAMA,gBAA2B;CAChC,OAAO;CACP,WAAW;CACX,QAAQ;CACR,OAAO;CACP,QAAQ;CACR,kBAAkB;CAClB,aAAa;CACb,mBAAmB;CACnB,kCAAkC;CAClC;AAED,IAAI,8BAA8B;;;;;;AAOlC,SAAgB,eAA0B;CAEzC,MAAM,YADW,qBAAqB,GAAG,OAAO,cAAc,KAAK,GAAG,SACzC,wBAAwB;AACrD,KAAI,SAAU,QAAO;AAKrB,KAAI,OAAO,KAAK,IAAI,OAAO,CAAC,6BAA6B;AACxD,gCAA8B;AAC9B,UAAQ,KACP,yHACA;;AAEF,QAAO"}
@@ -0,0 +1,170 @@
1
+ const require_useDebounce = require('./useDebounce.cjs');
2
+ const require_useExternalHooks = require('./useExternalHooks2.cjs');
3
+ require('./telemetryRegistry.cjs');
4
+ const require_useTelemetry = require('./useTelemetry2.cjs');
5
+ let vue = require("vue");
6
+ let __n8n_frontend_constants_z_indexes = require("@n8n/frontend-constants/z-indexes");
7
+ let __n8n_frontend_constants_views = require("@n8n/frontend-constants/views");
8
+ let __n8n_frontend_utils_htmlUtils = require("@n8n/frontend-utils/htmlUtils");
9
+ let __n8n_i18n = require("@n8n/i18n");
10
+ let vue_router = require("vue-router");
11
+
12
+ //#region src/useToast.ts
13
+ let registeredNotify;
14
+ /**
15
+ * Register the notification function. Called once at bootstrap by
16
+ * `editor-ui` (passing a suppression-aware wrapper around `ElNotification`) so
17
+ * `useToast` can issue notifications without importing element-plus directly —
18
+ * keeping the DTS build lightweight.
19
+ *
20
+ * This is the *only* toast dependency the app registers. Whether a notification
21
+ * is actually shown is the notifier's decision, so if you are chasing a toast
22
+ * that never appeared, look at the registration site, not here.
23
+ */
24
+ function setNotify(fn) {
25
+ registeredNotify = fn;
26
+ }
27
+ const noopHandle = { close() {} };
28
+ const noopNotify = () => noopHandle;
29
+ let warnedAboutMissingNotify = false;
30
+ function resolveNotify() {
31
+ if (registeredNotify) return registeredNotify;
32
+ if ({}.env.DEV && !warnedAboutMissingNotify) {
33
+ warnedAboutMissingNotify = true;
34
+ console.warn("[useToast] No notification function registered; toasts will not render. Call setNotify() at app bootstrap.");
35
+ }
36
+ return noopNotify;
37
+ }
38
+ const stickyNotificationQueue = [];
39
+ function useToast() {
40
+ const telemetry = require_useTelemetry.useTelemetry();
41
+ const route = (0, vue_router.useRoute)();
42
+ const workflowId = (0, vue.computed)(() => {
43
+ if (route?.name === __n8n_frontend_constants_views.VIEWS.DEMO || route?.name === __n8n_frontend_constants_views.VIEWS.DEMO_DIFF) return "demo";
44
+ const id = route?.params?.workflowId;
45
+ return (Array.isArray(id) ? id[0] : id) ?? "";
46
+ });
47
+ const externalHooks = require_useExternalHooks.useExternalHooks();
48
+ const i18n = (0, __n8n_i18n.useI18n)();
49
+ function causedByCredential(message) {
50
+ if (!message || typeof message !== "string") return false;
51
+ return message.includes("Credentials for") && message.includes("are not set");
52
+ }
53
+ function showMessage(messageData, track = true) {
54
+ const notify = resolveNotify();
55
+ const messageDefaults = {
56
+ dangerouslyUseHTMLString: true,
57
+ position: "bottom-right",
58
+ zIndex: __n8n_frontend_constants_z_indexes.APP_Z_INDEXES.TOASTS,
59
+ appendTo: "#n8n-app",
60
+ customClass: "content-toast"
61
+ };
62
+ const { message, title } = messageData;
63
+ const params = {
64
+ ...messageDefaults,
65
+ ...messageData
66
+ };
67
+ if (typeof message === "string") params.message = (0, __n8n_frontend_utils_htmlUtils.sanitizeHtml)(message);
68
+ if (typeof title === "string") params.title = (0, __n8n_frontend_utils_htmlUtils.sanitizeHtml)(title);
69
+ const notification = notify(params);
70
+ if (!notification) return noopHandle;
71
+ if (params.duration === 0) stickyNotificationQueue.push(notification);
72
+ if (params.type === "error" && track) {
73
+ let messageForTelemetry;
74
+ if (typeof params.message === "string") messageForTelemetry = params.message;
75
+ else if (params.message && typeof params.message === "object" && "props" in params.message && params.message.props) {
76
+ const props = params.message.props;
77
+ const hasErrorMessage = typeof props === "object" && props !== null && "errorMessage" in props;
78
+ const hasMessage = typeof props === "object" && props !== null && "message" in props;
79
+ if (hasErrorMessage) messageForTelemetry = String(props.errorMessage);
80
+ else if (hasMessage) messageForTelemetry = String(props.message);
81
+ else messageForTelemetry = "Unknown error";
82
+ } else messageForTelemetry = "Unknown error";
83
+ telemetry.track("Instance FE emitted error", {
84
+ error_title: params.title,
85
+ error_message: messageForTelemetry,
86
+ caused_by_credential: causedByCredential(messageForTelemetry),
87
+ workflow_id: workflowId.value
88
+ });
89
+ }
90
+ return notification;
91
+ }
92
+ function showToast(config) {
93
+ let notification;
94
+ if (config.closeOnClick) {
95
+ const originalOnClick = config.onClick;
96
+ config.onClick = () => {
97
+ if (notification) notification.close();
98
+ if (originalOnClick) originalOnClick();
99
+ };
100
+ }
101
+ notification = showMessage({
102
+ title: config.title,
103
+ message: config.message,
104
+ onClick: config.onClick,
105
+ onClose: config.onClose,
106
+ duration: config.duration,
107
+ customClass: config.customClass,
108
+ type: config.type
109
+ });
110
+ return notification;
111
+ }
112
+ function collapsableDetails(description) {
113
+ const errorDescription = description.length > 500 ? `${description.slice(0, 500)}...` : description;
114
+ return `
115
+ <br>
116
+ <br>
117
+ <details>
118
+ <summary
119
+ style="color: #ff6d5a; font-weight: bold; cursor: pointer;"
120
+ >
121
+ ${i18n.baseText("showMessage.showDetails")}
122
+ </summary>
123
+ <p>${errorDescription}</p>
124
+ </details>
125
+ `;
126
+ }
127
+ function showError(e, title, options) {
128
+ const error = e;
129
+ const message = options?.message;
130
+ const description = options?.description ?? error.description;
131
+ showMessage({
132
+ title,
133
+ message: `
134
+ ${message ? `${message}<br/>` : ""}
135
+ <i>${error.message}</i>
136
+ ${description ? collapsableDetails(description) : ""}`,
137
+ type: "error",
138
+ duration: 0
139
+ }, false);
140
+ externalHooks.run("showMessage.showError", {
141
+ title,
142
+ message,
143
+ errorMessage: error.message
144
+ });
145
+ telemetry.track("Instance FE emitted error", {
146
+ error_title: title,
147
+ error_description: message,
148
+ error_message: error.message,
149
+ caused_by_credential: causedByCredential(error.message),
150
+ workflow_id: workflowId.value
151
+ });
152
+ }
153
+ function clearAllStickyNotifications() {
154
+ stickyNotificationQueue.forEach((notification) => {
155
+ if (notification) notification.close();
156
+ });
157
+ stickyNotificationQueue.length = 0;
158
+ }
159
+ return {
160
+ showMessage,
161
+ showToast,
162
+ showError,
163
+ clearAllStickyNotifications
164
+ };
165
+ }
166
+
167
+ //#endregion
168
+ exports.setNotify = setNotify;
169
+ exports.useToast = useToast;
170
+ //# sourceMappingURL=useToast.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useToast.cjs","names":["registeredNotify: NotifyFn | undefined","noopHandle: NotificationHandle","noopNotify: NotifyFn","stickyNotificationQueue: NotificationHandle[]","useTelemetry","VIEWS","useExternalHooks","messageDefaults: Partial<Omit<NotificationOptions, 'message'>>","APP_Z_INDEXES","messageForTelemetry: string","notification: NotificationHandle"],"sources":["../src/useToast.ts"],"sourcesContent":["import { VIEWS } from '@n8n/frontend-constants/views';\nimport { APP_Z_INDEXES } from '@n8n/frontend-constants/z-indexes';\nimport { sanitizeHtml } from '@n8n/frontend-utils/htmlUtils';\nimport { useI18n } from '@n8n/i18n';\nimport { computed } from 'vue';\nimport { useRoute } from 'vue-router';\n\nimport type { NotificationOptions, NotificationType } from './types/notification';\nimport { useExternalHooks } from './useExternalHooks';\nimport { useTelemetry } from './useTelemetry';\n\nexport type {\n\tNotificationOptions,\n\tNotificationPosition,\n\tNotificationType,\n} from './types/notification';\n\n/**\n * Handle returned by the notification function. Declared locally so the DTS\n * build stays free of the full element-plus type graph.\n */\nexport interface NotificationHandle {\n\tclose: () => void;\n}\n\n/**\n * Notification function contract — the app's wrapper around `ElNotification`.\n * Registered at bootstrap via {@link setNotify}; this package owns only the\n * contract.\n *\n * Returning `undefined` means the app declined to show this notification — today\n * that is notification suppression, which lives at the registration site rather\n * than here. `showMessage` treats it as \"dropped\": no sticky-queue\n * entry, no error telemetry, and a no-op handle to the caller.\n */\ntype NotifyFn = (options: Record<string, unknown>) => NotificationHandle | undefined;\n\nlet registeredNotify: NotifyFn | undefined;\n\n/**\n * Register the notification function. Called once at bootstrap by\n * `editor-ui` (passing a suppression-aware wrapper around `ElNotification`) so\n * `useToast` can issue notifications without importing element-plus directly —\n * keeping the DTS build lightweight.\n *\n * This is the *only* toast dependency the app registers. Whether a notification\n * is actually shown is the notifier's decision, so if you are chasing a toast\n * that never appeared, look at the registration site, not here.\n */\nexport function setNotify(fn: NotifyFn): void {\n\tregisteredNotify = fn;\n}\n\nconst noopHandle: NotificationHandle = { close() {} };\nconst noopNotify: NotifyFn = () => noopHandle;\n\nlet warnedAboutMissingNotify = false;\n\nfunction resolveNotify(): NotifyFn {\n\tif (registeredNotify) return registeredNotify;\n\n\t// Falling back means bootstrap never registered. The toast is dropped rather\n\t// than shown, so warn once in dev instead of failing silent.\n\tif (import.meta.env.DEV && !warnedAboutMissingNotify) {\n\t\twarnedAboutMissingNotify = true;\n\t\tconsole.warn(\n\t\t\t'[useToast] No notification function registered; toasts will not render. Call setNotify() at app bootstrap.',\n\t\t);\n\t}\n\treturn noopNotify;\n}\n\nconst stickyNotificationQueue: NotificationHandle[] = [];\n\nexport function useToast() {\n\tconst telemetry = useTelemetry();\n\tconst route = useRoute();\n\tconst workflowId = computed(() => {\n\t\tif (route?.name === VIEWS.DEMO || route?.name === VIEWS.DEMO_DIFF) return 'demo';\n\t\tconst id = route?.params?.workflowId;\n\t\treturn (Array.isArray(id) ? id[0] : id) ?? '';\n\t});\n\tconst externalHooks = useExternalHooks();\n\tconst i18n = useI18n();\n\n\tfunction causedByCredential(message: string | undefined) {\n\t\tif (!message || typeof message !== 'string') return false;\n\n\t\treturn message.includes('Credentials for') && message.includes('are not set');\n\t}\n\n\tfunction showMessage(messageData: Partial<NotificationOptions>, track = true) {\n\t\t// Resolved per call, not once per `useToast()`: a composable captured before\n\t\t// bootstrap registers would otherwise stay bound to the no-op for its whole\n\t\t// lifetime, silently dropping toasts.\n\t\tconst notify = resolveNotify();\n\n\t\tconst messageDefaults: Partial<Omit<NotificationOptions, 'message'>> = {\n\t\t\tdangerouslyUseHTMLString: true,\n\t\t\tposition: 'bottom-right',\n\t\t\t// Layer above NDV and modal overlays, sourced from the shared scale.\n\t\t\tzIndex: APP_Z_INDEXES.TOASTS,\n\t\t\tappendTo: '#n8n-app',\n\t\t\tcustomClass: 'content-toast',\n\t\t};\n\t\tconst { message, title } = messageData;\n\t\tconst params = { ...messageDefaults, ...messageData };\n\n\t\tif (typeof message === 'string') {\n\t\t\tparams.message = sanitizeHtml(message);\n\t\t}\n\n\t\tif (typeof title === 'string') {\n\t\t\tparams.title = sanitizeHtml(title);\n\t\t}\n\n\t\tconst notification = notify(params as unknown as Record<string, unknown>);\n\n\t\t// The app declined to show it (suppression). Return before the sticky queue\n\t\t// and before error telemetry, so a dropped toast leaves no trace — exactly\n\t\t// what the removed in-package suppression branch did.\n\t\tif (!notification) return noopHandle;\n\n\t\tif (params.duration === 0) {\n\t\t\tstickyNotificationQueue.push(notification);\n\t\t}\n\n\t\tif (params.type === 'error' && track) {\n\t\t\t// Extract string message for telemetry - don't send VNode objects as they have circular refs\n\t\t\tlet messageForTelemetry: string;\n\t\t\tif (typeof params.message === 'string') {\n\t\t\t\tmessageForTelemetry = params.message;\n\t\t\t} else if (\n\t\t\t\tparams.message &&\n\t\t\t\ttypeof params.message === 'object' &&\n\t\t\t\t'props' in params.message &&\n\t\t\t\tparams.message.props\n\t\t\t) {\n\t\t\t\t// Extract error message from VNode props (e.g., NodeExecutionErrorMessage component)\n\t\t\t\tconst props = params.message.props;\n\t\t\t\tconst hasErrorMessage =\n\t\t\t\t\ttypeof props === 'object' && props !== null && 'errorMessage' in props;\n\t\t\t\tconst hasMessage = typeof props === 'object' && props !== null && 'message' in props;\n\n\t\t\t\tif (hasErrorMessage) {\n\t\t\t\t\tmessageForTelemetry = String(props.errorMessage);\n\t\t\t\t} else if (hasMessage) {\n\t\t\t\t\tmessageForTelemetry = String(props.message);\n\t\t\t\t} else {\n\t\t\t\t\tmessageForTelemetry = 'Unknown error';\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tmessageForTelemetry = 'Unknown error';\n\t\t\t}\n\n\t\t\ttelemetry.track('Instance FE emitted error', {\n\t\t\t\terror_title: params.title,\n\t\t\t\terror_message: messageForTelemetry,\n\t\t\t\tcaused_by_credential: causedByCredential(messageForTelemetry),\n\t\t\t\tworkflow_id: workflowId.value,\n\t\t\t});\n\t\t}\n\n\t\treturn notification;\n\t}\n\n\tfunction showToast(config: {\n\t\ttitle: string;\n\t\tmessage: NotificationOptions['message'];\n\t\tonClick?: (event?: MouseEvent) => void;\n\t\tonClose?: () => void;\n\t\tduration?: number;\n\t\tcustomClass?: string;\n\t\tcloseOnClick?: boolean;\n\t\ttype?: NotificationType;\n\t}) {\n\t\t// eslint-disable-next-line prefer-const\n\t\tlet notification: NotificationHandle;\n\t\tif (config.closeOnClick) {\n\t\t\tconst originalOnClick = config.onClick;\n\t\t\tconfig.onClick = () => {\n\t\t\t\tif (notification) {\n\t\t\t\t\tnotification.close();\n\t\t\t\t}\n\n\t\t\t\tif (originalOnClick) {\n\t\t\t\t\toriginalOnClick();\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\tnotification = showMessage({\n\t\t\ttitle: config.title,\n\t\t\tmessage: config.message,\n\t\t\tonClick: config.onClick,\n\t\t\tonClose: config.onClose,\n\t\t\tduration: config.duration,\n\t\t\tcustomClass: config.customClass,\n\t\t\ttype: config.type,\n\t\t});\n\n\t\treturn notification;\n\t}\n\n\tfunction collapsableDetails(description: string) {\n\t\tconst errorDescription =\n\t\t\tdescription.length > 500 ? `${description.slice(0, 500)}...` : description;\n\n\t\treturn `\n\t\t\t\t<br>\n\t\t\t\t<br>\n\t\t\t\t<details>\n\t\t\t\t\t<summary\n\t\t\t\t\t\tstyle=\"color: #ff6d5a; font-weight: bold; cursor: pointer;\"\n\t\t\t\t\t>\n\t\t\t\t\t\t${i18n.baseText('showMessage.showDetails')}\n\t\t\t\t\t</summary>\n\t\t\t\t\t<p>${errorDescription}</p>\n\t\t\t\t</details>\n\t\t\t`;\n\t}\n\n\tfunction showError(\n\t\te: unknown,\n\t\ttitle: string,\n\t\toptions?: { message?: string; description?: string },\n\t) {\n\t\tconst error = e as Error & { description?: string };\n\t\tconst message = options?.message;\n\t\tconst description = options?.description ?? error.description;\n\t\tconst messageLine = message ? `${message}<br/>` : '';\n\t\tshowMessage(\n\t\t\t{\n\t\t\t\ttitle,\n\t\t\t\tmessage: `\n\t\t\t\t\t${messageLine}\n\t\t\t\t\t<i>${error.message}</i>\n\t\t\t\t\t${description ? collapsableDetails(description) : ''}`,\n\t\t\t\ttype: 'error',\n\t\t\t\tduration: 0,\n\t\t\t},\n\t\t\tfalse,\n\t\t);\n\n\t\tvoid externalHooks.run('showMessage.showError', {\n\t\t\ttitle,\n\t\t\tmessage,\n\t\t\terrorMessage: error.message,\n\t\t});\n\n\t\ttelemetry.track('Instance FE emitted error', {\n\t\t\terror_title: title,\n\t\t\terror_description: message,\n\t\t\terror_message: error.message,\n\t\t\tcaused_by_credential: causedByCredential(error.message),\n\t\t\tworkflow_id: workflowId.value,\n\t\t});\n\t}\n\n\tfunction clearAllStickyNotifications() {\n\t\tstickyNotificationQueue.forEach((notification) => {\n\t\t\tif (notification) {\n\t\t\t\tnotification.close();\n\t\t\t}\n\t\t});\n\n\t\tstickyNotificationQueue.length = 0;\n\t}\n\n\treturn {\n\t\tshowMessage,\n\t\tshowToast,\n\t\tshowError,\n\t\tclearAllStickyNotifications,\n\t};\n}\n"],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import { n as NotificationPosition, r as NotificationType, t as NotificationOptions } from "./notification.cjs";
2
+
3
+ //#region src/useToast.d.ts
4
+ interface NotificationHandle {
5
+ close: () => void;
6
+ }
7
+ type NotifyFn = (options: Record<string, unknown>) => NotificationHandle | undefined;
8
+ declare function setNotify(fn: NotifyFn): void;
9
+ declare function useToast(): {
10
+ showMessage: (messageData: Partial<NotificationOptions>, track?: boolean) => NotificationHandle;
11
+ showToast: (config: {
12
+ title: string;
13
+ message: NotificationOptions["message"];
14
+ onClick?: (event?: MouseEvent) => void;
15
+ onClose?: () => void;
16
+ duration?: number;
17
+ customClass?: string;
18
+ closeOnClick?: boolean;
19
+ type?: NotificationType;
20
+ }) => NotificationHandle;
21
+ showError: (e: unknown, title: string, options?: {
22
+ message?: string;
23
+ description?: string;
24
+ }) => void;
25
+ clearAllStickyNotifications: () => void;
26
+ };
27
+ //#endregion
28
+ export { NotificationHandle, type NotificationOptions, type NotificationPosition, type NotificationType, setNotify, useToast };
29
+ //# sourceMappingURL=useToast.d.cts.map
@@ -0,0 +1,29 @@
1
+ import { n as NotificationPosition, r as NotificationType, t as NotificationOptions } from "./notification.mjs";
2
+
3
+ //#region src/useToast.d.ts
4
+ interface NotificationHandle {
5
+ close: () => void;
6
+ }
7
+ type NotifyFn = (options: Record<string, unknown>) => NotificationHandle | undefined;
8
+ declare function setNotify(fn: NotifyFn): void;
9
+ declare function useToast(): {
10
+ showMessage: (messageData: Partial<NotificationOptions>, track?: boolean) => NotificationHandle;
11
+ showToast: (config: {
12
+ title: string;
13
+ message: NotificationOptions["message"];
14
+ onClick?: (event?: MouseEvent) => void;
15
+ onClose?: () => void;
16
+ duration?: number;
17
+ customClass?: string;
18
+ closeOnClick?: boolean;
19
+ type?: NotificationType;
20
+ }) => NotificationHandle;
21
+ showError: (e: unknown, title: string, options?: {
22
+ message?: string;
23
+ description?: string;
24
+ }) => void;
25
+ clearAllStickyNotifications: () => void;
26
+ };
27
+ //#endregion
28
+ export { NotificationHandle, type NotificationOptions, type NotificationPosition, type NotificationType, setNotify, useToast };
29
+ //# sourceMappingURL=useToast.d.mts.map
@@ -0,0 +1,168 @@
1
+ import { r as useExternalHooks } from "./useExternalHooks2.mjs";
2
+ import "./telemetryRegistry.mjs";
3
+ import { t as useTelemetry } from "./useTelemetry2.mjs";
4
+ import { computed } from "vue";
5
+ import { APP_Z_INDEXES } from "@n8n/frontend-constants/z-indexes";
6
+ import { VIEWS } from "@n8n/frontend-constants/views";
7
+ import { sanitizeHtml } from "@n8n/frontend-utils/htmlUtils";
8
+ import { useI18n } from "@n8n/i18n";
9
+ import { useRoute } from "vue-router";
10
+
11
+ //#region src/useToast.ts
12
+ let registeredNotify;
13
+ /**
14
+ * Register the notification function. Called once at bootstrap by
15
+ * `editor-ui` (passing a suppression-aware wrapper around `ElNotification`) so
16
+ * `useToast` can issue notifications without importing element-plus directly —
17
+ * keeping the DTS build lightweight.
18
+ *
19
+ * This is the *only* toast dependency the app registers. Whether a notification
20
+ * is actually shown is the notifier's decision, so if you are chasing a toast
21
+ * that never appeared, look at the registration site, not here.
22
+ */
23
+ function setNotify(fn) {
24
+ registeredNotify = fn;
25
+ }
26
+ const noopHandle = { close() {} };
27
+ const noopNotify = () => noopHandle;
28
+ let warnedAboutMissingNotify = false;
29
+ function resolveNotify() {
30
+ if (registeredNotify) return registeredNotify;
31
+ if (import.meta.env.DEV && !warnedAboutMissingNotify) {
32
+ warnedAboutMissingNotify = true;
33
+ console.warn("[useToast] No notification function registered; toasts will not render. Call setNotify() at app bootstrap.");
34
+ }
35
+ return noopNotify;
36
+ }
37
+ const stickyNotificationQueue = [];
38
+ function useToast() {
39
+ const telemetry = useTelemetry();
40
+ const route = useRoute();
41
+ const workflowId = computed(() => {
42
+ if (route?.name === VIEWS.DEMO || route?.name === VIEWS.DEMO_DIFF) return "demo";
43
+ const id = route?.params?.workflowId;
44
+ return (Array.isArray(id) ? id[0] : id) ?? "";
45
+ });
46
+ const externalHooks = useExternalHooks();
47
+ const i18n = useI18n();
48
+ function causedByCredential(message) {
49
+ if (!message || typeof message !== "string") return false;
50
+ return message.includes("Credentials for") && message.includes("are not set");
51
+ }
52
+ function showMessage(messageData, track = true) {
53
+ const notify = resolveNotify();
54
+ const messageDefaults = {
55
+ dangerouslyUseHTMLString: true,
56
+ position: "bottom-right",
57
+ zIndex: APP_Z_INDEXES.TOASTS,
58
+ appendTo: "#n8n-app",
59
+ customClass: "content-toast"
60
+ };
61
+ const { message, title } = messageData;
62
+ const params = {
63
+ ...messageDefaults,
64
+ ...messageData
65
+ };
66
+ if (typeof message === "string") params.message = sanitizeHtml(message);
67
+ if (typeof title === "string") params.title = sanitizeHtml(title);
68
+ const notification = notify(params);
69
+ if (!notification) return noopHandle;
70
+ if (params.duration === 0) stickyNotificationQueue.push(notification);
71
+ if (params.type === "error" && track) {
72
+ let messageForTelemetry;
73
+ if (typeof params.message === "string") messageForTelemetry = params.message;
74
+ else if (params.message && typeof params.message === "object" && "props" in params.message && params.message.props) {
75
+ const props = params.message.props;
76
+ const hasErrorMessage = typeof props === "object" && props !== null && "errorMessage" in props;
77
+ const hasMessage = typeof props === "object" && props !== null && "message" in props;
78
+ if (hasErrorMessage) messageForTelemetry = String(props.errorMessage);
79
+ else if (hasMessage) messageForTelemetry = String(props.message);
80
+ else messageForTelemetry = "Unknown error";
81
+ } else messageForTelemetry = "Unknown error";
82
+ telemetry.track("Instance FE emitted error", {
83
+ error_title: params.title,
84
+ error_message: messageForTelemetry,
85
+ caused_by_credential: causedByCredential(messageForTelemetry),
86
+ workflow_id: workflowId.value
87
+ });
88
+ }
89
+ return notification;
90
+ }
91
+ function showToast(config) {
92
+ let notification;
93
+ if (config.closeOnClick) {
94
+ const originalOnClick = config.onClick;
95
+ config.onClick = () => {
96
+ if (notification) notification.close();
97
+ if (originalOnClick) originalOnClick();
98
+ };
99
+ }
100
+ notification = showMessage({
101
+ title: config.title,
102
+ message: config.message,
103
+ onClick: config.onClick,
104
+ onClose: config.onClose,
105
+ duration: config.duration,
106
+ customClass: config.customClass,
107
+ type: config.type
108
+ });
109
+ return notification;
110
+ }
111
+ function collapsableDetails(description) {
112
+ const errorDescription = description.length > 500 ? `${description.slice(0, 500)}...` : description;
113
+ return `
114
+ <br>
115
+ <br>
116
+ <details>
117
+ <summary
118
+ style="color: #ff6d5a; font-weight: bold; cursor: pointer;"
119
+ >
120
+ ${i18n.baseText("showMessage.showDetails")}
121
+ </summary>
122
+ <p>${errorDescription}</p>
123
+ </details>
124
+ `;
125
+ }
126
+ function showError(e, title, options) {
127
+ const error = e;
128
+ const message = options?.message;
129
+ const description = options?.description ?? error.description;
130
+ showMessage({
131
+ title,
132
+ message: `
133
+ ${message ? `${message}<br/>` : ""}
134
+ <i>${error.message}</i>
135
+ ${description ? collapsableDetails(description) : ""}`,
136
+ type: "error",
137
+ duration: 0
138
+ }, false);
139
+ externalHooks.run("showMessage.showError", {
140
+ title,
141
+ message,
142
+ errorMessage: error.message
143
+ });
144
+ telemetry.track("Instance FE emitted error", {
145
+ error_title: title,
146
+ error_description: message,
147
+ error_message: error.message,
148
+ caused_by_credential: causedByCredential(error.message),
149
+ workflow_id: workflowId.value
150
+ });
151
+ }
152
+ function clearAllStickyNotifications() {
153
+ stickyNotificationQueue.forEach((notification) => {
154
+ if (notification) notification.close();
155
+ });
156
+ stickyNotificationQueue.length = 0;
157
+ }
158
+ return {
159
+ showMessage,
160
+ showToast,
161
+ showError,
162
+ clearAllStickyNotifications
163
+ };
164
+ }
165
+
166
+ //#endregion
167
+ export { setNotify, useToast };
168
+ //# sourceMappingURL=useToast.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useToast.mjs","names":["registeredNotify: NotifyFn | undefined","noopHandle: NotificationHandle","noopNotify: NotifyFn","stickyNotificationQueue: NotificationHandle[]","messageDefaults: Partial<Omit<NotificationOptions, 'message'>>","messageForTelemetry: string","notification: NotificationHandle"],"sources":["../src/useToast.ts"],"sourcesContent":["import { VIEWS } from '@n8n/frontend-constants/views';\nimport { APP_Z_INDEXES } from '@n8n/frontend-constants/z-indexes';\nimport { sanitizeHtml } from '@n8n/frontend-utils/htmlUtils';\nimport { useI18n } from '@n8n/i18n';\nimport { computed } from 'vue';\nimport { useRoute } from 'vue-router';\n\nimport type { NotificationOptions, NotificationType } from './types/notification';\nimport { useExternalHooks } from './useExternalHooks';\nimport { useTelemetry } from './useTelemetry';\n\nexport type {\n\tNotificationOptions,\n\tNotificationPosition,\n\tNotificationType,\n} from './types/notification';\n\n/**\n * Handle returned by the notification function. Declared locally so the DTS\n * build stays free of the full element-plus type graph.\n */\nexport interface NotificationHandle {\n\tclose: () => void;\n}\n\n/**\n * Notification function contract — the app's wrapper around `ElNotification`.\n * Registered at bootstrap via {@link setNotify}; this package owns only the\n * contract.\n *\n * Returning `undefined` means the app declined to show this notification — today\n * that is notification suppression, which lives at the registration site rather\n * than here. `showMessage` treats it as \"dropped\": no sticky-queue\n * entry, no error telemetry, and a no-op handle to the caller.\n */\ntype NotifyFn = (options: Record<string, unknown>) => NotificationHandle | undefined;\n\nlet registeredNotify: NotifyFn | undefined;\n\n/**\n * Register the notification function. Called once at bootstrap by\n * `editor-ui` (passing a suppression-aware wrapper around `ElNotification`) so\n * `useToast` can issue notifications without importing element-plus directly —\n * keeping the DTS build lightweight.\n *\n * This is the *only* toast dependency the app registers. Whether a notification\n * is actually shown is the notifier's decision, so if you are chasing a toast\n * that never appeared, look at the registration site, not here.\n */\nexport function setNotify(fn: NotifyFn): void {\n\tregisteredNotify = fn;\n}\n\nconst noopHandle: NotificationHandle = { close() {} };\nconst noopNotify: NotifyFn = () => noopHandle;\n\nlet warnedAboutMissingNotify = false;\n\nfunction resolveNotify(): NotifyFn {\n\tif (registeredNotify) return registeredNotify;\n\n\t// Falling back means bootstrap never registered. The toast is dropped rather\n\t// than shown, so warn once in dev instead of failing silent.\n\tif (import.meta.env.DEV && !warnedAboutMissingNotify) {\n\t\twarnedAboutMissingNotify = true;\n\t\tconsole.warn(\n\t\t\t'[useToast] No notification function registered; toasts will not render. Call setNotify() at app bootstrap.',\n\t\t);\n\t}\n\treturn noopNotify;\n}\n\nconst stickyNotificationQueue: NotificationHandle[] = [];\n\nexport function useToast() {\n\tconst telemetry = useTelemetry();\n\tconst route = useRoute();\n\tconst workflowId = computed(() => {\n\t\tif (route?.name === VIEWS.DEMO || route?.name === VIEWS.DEMO_DIFF) return 'demo';\n\t\tconst id = route?.params?.workflowId;\n\t\treturn (Array.isArray(id) ? id[0] : id) ?? '';\n\t});\n\tconst externalHooks = useExternalHooks();\n\tconst i18n = useI18n();\n\n\tfunction causedByCredential(message: string | undefined) {\n\t\tif (!message || typeof message !== 'string') return false;\n\n\t\treturn message.includes('Credentials for') && message.includes('are not set');\n\t}\n\n\tfunction showMessage(messageData: Partial<NotificationOptions>, track = true) {\n\t\t// Resolved per call, not once per `useToast()`: a composable captured before\n\t\t// bootstrap registers would otherwise stay bound to the no-op for its whole\n\t\t// lifetime, silently dropping toasts.\n\t\tconst notify = resolveNotify();\n\n\t\tconst messageDefaults: Partial<Omit<NotificationOptions, 'message'>> = {\n\t\t\tdangerouslyUseHTMLString: true,\n\t\t\tposition: 'bottom-right',\n\t\t\t// Layer above NDV and modal overlays, sourced from the shared scale.\n\t\t\tzIndex: APP_Z_INDEXES.TOASTS,\n\t\t\tappendTo: '#n8n-app',\n\t\t\tcustomClass: 'content-toast',\n\t\t};\n\t\tconst { message, title } = messageData;\n\t\tconst params = { ...messageDefaults, ...messageData };\n\n\t\tif (typeof message === 'string') {\n\t\t\tparams.message = sanitizeHtml(message);\n\t\t}\n\n\t\tif (typeof title === 'string') {\n\t\t\tparams.title = sanitizeHtml(title);\n\t\t}\n\n\t\tconst notification = notify(params as unknown as Record<string, unknown>);\n\n\t\t// The app declined to show it (suppression). Return before the sticky queue\n\t\t// and before error telemetry, so a dropped toast leaves no trace — exactly\n\t\t// what the removed in-package suppression branch did.\n\t\tif (!notification) return noopHandle;\n\n\t\tif (params.duration === 0) {\n\t\t\tstickyNotificationQueue.push(notification);\n\t\t}\n\n\t\tif (params.type === 'error' && track) {\n\t\t\t// Extract string message for telemetry - don't send VNode objects as they have circular refs\n\t\t\tlet messageForTelemetry: string;\n\t\t\tif (typeof params.message === 'string') {\n\t\t\t\tmessageForTelemetry = params.message;\n\t\t\t} else if (\n\t\t\t\tparams.message &&\n\t\t\t\ttypeof params.message === 'object' &&\n\t\t\t\t'props' in params.message &&\n\t\t\t\tparams.message.props\n\t\t\t) {\n\t\t\t\t// Extract error message from VNode props (e.g., NodeExecutionErrorMessage component)\n\t\t\t\tconst props = params.message.props;\n\t\t\t\tconst hasErrorMessage =\n\t\t\t\t\ttypeof props === 'object' && props !== null && 'errorMessage' in props;\n\t\t\t\tconst hasMessage = typeof props === 'object' && props !== null && 'message' in props;\n\n\t\t\t\tif (hasErrorMessage) {\n\t\t\t\t\tmessageForTelemetry = String(props.errorMessage);\n\t\t\t\t} else if (hasMessage) {\n\t\t\t\t\tmessageForTelemetry = String(props.message);\n\t\t\t\t} else {\n\t\t\t\t\tmessageForTelemetry = 'Unknown error';\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tmessageForTelemetry = 'Unknown error';\n\t\t\t}\n\n\t\t\ttelemetry.track('Instance FE emitted error', {\n\t\t\t\terror_title: params.title,\n\t\t\t\terror_message: messageForTelemetry,\n\t\t\t\tcaused_by_credential: causedByCredential(messageForTelemetry),\n\t\t\t\tworkflow_id: workflowId.value,\n\t\t\t});\n\t\t}\n\n\t\treturn notification;\n\t}\n\n\tfunction showToast(config: {\n\t\ttitle: string;\n\t\tmessage: NotificationOptions['message'];\n\t\tonClick?: (event?: MouseEvent) => void;\n\t\tonClose?: () => void;\n\t\tduration?: number;\n\t\tcustomClass?: string;\n\t\tcloseOnClick?: boolean;\n\t\ttype?: NotificationType;\n\t}) {\n\t\t// eslint-disable-next-line prefer-const\n\t\tlet notification: NotificationHandle;\n\t\tif (config.closeOnClick) {\n\t\t\tconst originalOnClick = config.onClick;\n\t\t\tconfig.onClick = () => {\n\t\t\t\tif (notification) {\n\t\t\t\t\tnotification.close();\n\t\t\t\t}\n\n\t\t\t\tif (originalOnClick) {\n\t\t\t\t\toriginalOnClick();\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\tnotification = showMessage({\n\t\t\ttitle: config.title,\n\t\t\tmessage: config.message,\n\t\t\tonClick: config.onClick,\n\t\t\tonClose: config.onClose,\n\t\t\tduration: config.duration,\n\t\t\tcustomClass: config.customClass,\n\t\t\ttype: config.type,\n\t\t});\n\n\t\treturn notification;\n\t}\n\n\tfunction collapsableDetails(description: string) {\n\t\tconst errorDescription =\n\t\t\tdescription.length > 500 ? `${description.slice(0, 500)}...` : description;\n\n\t\treturn `\n\t\t\t\t<br>\n\t\t\t\t<br>\n\t\t\t\t<details>\n\t\t\t\t\t<summary\n\t\t\t\t\t\tstyle=\"color: #ff6d5a; font-weight: bold; cursor: pointer;\"\n\t\t\t\t\t>\n\t\t\t\t\t\t${i18n.baseText('showMessage.showDetails')}\n\t\t\t\t\t</summary>\n\t\t\t\t\t<p>${errorDescription}</p>\n\t\t\t\t</details>\n\t\t\t`;\n\t}\n\n\tfunction showError(\n\t\te: unknown,\n\t\ttitle: string,\n\t\toptions?: { message?: string; description?: string },\n\t) {\n\t\tconst error = e as Error & { description?: string };\n\t\tconst message = options?.message;\n\t\tconst description = options?.description ?? error.description;\n\t\tconst messageLine = message ? `${message}<br/>` : '';\n\t\tshowMessage(\n\t\t\t{\n\t\t\t\ttitle,\n\t\t\t\tmessage: `\n\t\t\t\t\t${messageLine}\n\t\t\t\t\t<i>${error.message}</i>\n\t\t\t\t\t${description ? collapsableDetails(description) : ''}`,\n\t\t\t\ttype: 'error',\n\t\t\t\tduration: 0,\n\t\t\t},\n\t\t\tfalse,\n\t\t);\n\n\t\tvoid externalHooks.run('showMessage.showError', {\n\t\t\ttitle,\n\t\t\tmessage,\n\t\t\terrorMessage: error.message,\n\t\t});\n\n\t\ttelemetry.track('Instance FE emitted error', {\n\t\t\terror_title: title,\n\t\t\terror_description: message,\n\t\t\terror_message: error.message,\n\t\t\tcaused_by_credential: causedByCredential(error.message),\n\t\t\tworkflow_id: workflowId.value,\n\t\t});\n\t}\n\n\tfunction clearAllStickyNotifications() {\n\t\tstickyNotificationQueue.forEach((notification) => {\n\t\t\tif (notification) {\n\t\t\t\tnotification.close();\n\t\t\t}\n\t\t});\n\n\t\tstickyNotificationQueue.length = 0;\n\t}\n\n\treturn {\n\t\tshowMessage,\n\t\tshowToast,\n\t\tshowError,\n\t\tclearAllStickyNotifications,\n\t};\n}\n"],"mappings":"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"}