diff --git a/package-lock.json b/package-lock.json index fd1ab7cc89b..32b62759f45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,13 +23,17 @@ "@igniteui/material-icons-extended": "^3.1.0", "@lit-labs/ssr-dom-shim": "^1.3.0", "@types/source-map": "0.5.2", + "dompurify": "^3.3.0", "express": "^5.1.0", "fflate": "^0.8.1", "igniteui-theming": "^24.0.0", "igniteui-trial-watermark": "^3.1.0", "jspdf": "^3.0.4", "lodash-es": "^4.17.21", + "marked": "^16.4.0", + "marked-shiki": "^1.2.1", "rxjs": "^7.8.2", + "shiki": "^3.13.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" }, @@ -72,7 +76,7 @@ "ig-typedoc-theme": "^7.0.0", "igniteui-dockmanager": "^1.17.0", "igniteui-sassdoc-theme": "^2.1.0", - "igniteui-webcomponents": "6.2.1", + "igniteui-webcomponents": "^6.3.1", "jasmine": "^5.6.0", "jasmine-core": "^5.6.0", "karma": "^6.4.4", @@ -6068,7 +6072,6 @@ "version": "3.15.0", "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.15.0.tgz", "integrity": "sha512-8TOG6yG557q+fMsSVa8nkEDOZNTSxjbbR8l6lF2gyr6Np+jrPlslqDxQkN6rMXCECQ3isNPZAGszAfYoJOPGlg==", - "dev": true, "license": "MIT", "dependencies": { "@shikijs/types": "3.15.0", @@ -6081,7 +6084,6 @@ "version": "3.15.0", "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.15.0.tgz", "integrity": "sha512-ZedbOFpopibdLmvTz2sJPJgns8Xvyabe2QbmqMTz07kt1pTzfEvKZc5IqPVO/XFiEbbNyaOpjPBkkr1vlwS+qg==", - "dev": true, "license": "MIT", "dependencies": { "@shikijs/types": "3.15.0", @@ -6093,7 +6095,6 @@ "version": "3.15.0", "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.15.0.tgz", "integrity": "sha512-HnqFsV11skAHvOArMZdLBZZApRSYS4LSztk2K3016Y9VCyZISnlYUYsL2hzlS7tPqKHvNqmI5JSUJZprXloMvA==", - "dev": true, "license": "MIT", "dependencies": { "@shikijs/types": "3.15.0", @@ -6104,7 +6105,6 @@ "version": "3.15.0", "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.15.0.tgz", "integrity": "sha512-WpRvEFvkVvO65uKYW4Rzxs+IG0gToyM8SARQMtGGsH4GDMNZrr60qdggXrFOsdfOVssG/QQGEl3FnJ3EZ+8w8A==", - "dev": true, "license": "MIT", "dependencies": { "@shikijs/types": "3.15.0" @@ -6114,7 +6114,6 @@ "version": "3.15.0", "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.15.0.tgz", "integrity": "sha512-8ow2zWb1IDvCKjYb0KiLNrK4offFdkfNVPXb1OZykpLCzRU6j+efkY+Y7VQjNlNFXonSw+4AOdGYtmqykDbRiQ==", - "dev": true, "license": "MIT", "dependencies": { "@shikijs/types": "3.15.0" @@ -6124,7 +6123,6 @@ "version": "3.15.0", "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.15.0.tgz", "integrity": "sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw==", - "dev": true, "license": "MIT", "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", @@ -6135,7 +6133,6 @@ "version": "10.0.2", "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", - "dev": true, "license": "MIT" }, "node_modules/@sigstore/bundle": { @@ -6436,7 +6433,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/unist": "*" @@ -6477,7 +6473,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "dev": true, "license": "MIT", "dependencies": { "@types/unist": "*" @@ -6604,7 +6599,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "dev": true, "license": "MIT" }, "node_modules/@types/webpack-env": { @@ -6863,7 +6857,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, "license": "ISC" }, "node_modules/@vitejs/plugin-basic-ssl": { @@ -8199,22 +8192,6 @@ "regenerator-runtime": "^0.11.0" } }, - "node_modules/babel-runtime/node_modules/core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", - "dev": true, - "hasInstallScript": true, - "license": "MIT" - }, - "node_modules/babel-runtime/node_modules/regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true, - "license": "MIT" - }, "node_modules/bach": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/bach/-/bach-2.0.1.tgz", @@ -9033,11 +9010,29 @@ "node": ">=10.0.0" } }, + "node_modules/canvg/node_modules/core-js": { + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz", + "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/canvg/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT", + "optional": true + }, "node_modules/ccount": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "dev": true, "license": "MIT", "funding": { "type": "github", @@ -9114,7 +9109,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "dev": true, "license": "MIT", "funding": { "type": "github", @@ -9125,7 +9119,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "dev": true, "license": "MIT", "funding": { "type": "github", @@ -9551,7 +9544,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", - "dev": true, "license": "MIT", "funding": { "type": "github", @@ -9856,16 +9848,13 @@ } }, "node_modules/core-js": { - "version": "3.47.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz", - "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==", + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "dev": true, "hasInstallScript": true, - "license": "MIT", - "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } + "license": "MIT" }, "node_modules/core-util-is": { "version": "1.0.3", @@ -10412,7 +10401,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -10481,7 +10469,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", - "dev": true, "license": "MIT", "dependencies": { "dequal": "^2.0.0" @@ -10637,7 +10624,6 @@ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz", "integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==", "license": "(MPL-2.0 OR Apache-2.0)", - "optional": true, "optionalDependencies": { "@types/trusted-types": "^2.0.7" } @@ -11883,6 +11869,12 @@ "pako": "^2.1.0" } }, + "node_modules/fast-png/node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, "node_modules/fast-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", @@ -13871,7 +13863,6 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", - "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -13957,7 +13948,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", - "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" @@ -14138,7 +14128,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", - "dev": true, "license": "MIT", "funding": { "type": "github", @@ -14364,11 +14353,11 @@ } }, "node_modules/igniteui-webcomponents": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/igniteui-webcomponents/-/igniteui-webcomponents-6.2.1.tgz", - "integrity": "sha512-nsErVEF/2nuU76w8pkDzdu+0Xwv25OYWVDdXP5dFoQwvLMusNFju273e8c+DV9LoPtD0nWx6+RzyNaS+ylWXjw==", + "version": "6.3.6", + "resolved": "https://registry.npmjs.org/igniteui-webcomponents/-/igniteui-webcomponents-6.3.6.tgz", + "integrity": "sha512-MRCCD204AE/0H2WRWiZHdRnpuldn/pjzk+3VGAtOvJ03+22HlmE3/7sMhnfNqsxqn/SHFTTFzBV56f3FEFu1+w==", "dev": true, - "license": "SEE LICENSE IN LICENSE", + "license": "MIT", "dependencies": { "@floating-ui/dom": "^1.7.0", "@lit-labs/virtualizer": "^2.1.0", @@ -14377,6 +14366,26 @@ }, "engines": { "node": ">=20" + }, + "peerDependencies": { + "dompurify": "^3.2.0", + "marked": "^16.3.0", + "marked-shiki": "^1.2.0", + "shiki": "^3.12.0" + }, + "peerDependenciesMeta": { + "dompurify": { + "optional": true + }, + "marked": { + "optional": true + }, + "marked-shiki": { + "optional": true + }, + "shiki": { + "optional": true + } } }, "node_modules/ignore": { @@ -15549,6 +15558,18 @@ "html2canvas": "^1.0.0-rc.5" } }, + "node_modules/jspdf/node_modules/core-js": { + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz", + "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/karma": { "version": "6.4.4", "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", @@ -17068,16 +17089,25 @@ } }, "node_modules/marked": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.6.3.tgz", - "integrity": "sha512-Fqa7eq+UaxfMriqzYLayfqAE40WN03jf+zHjT18/uXNuzjq3TY0XTbrAoPeqSJrAmPz11VuUA+kBPYOhHt9oOQ==", - "dev": true, + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", "license": "MIT", "bin": { - "marked": "bin/marked" + "marked": "bin/marked.js" }, "engines": { - "node": ">=0.10.0" + "node": ">= 20" + } + }, + "node_modules/marked-shiki": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/marked-shiki/-/marked-shiki-1.2.1.tgz", + "integrity": "sha512-yHxYQhPY5oYaIRnROn98foKhuClark7M373/VpLxiy5TrDu9Jd/LsMwo8w+U91Up4oDb9IXFrP0N1MFRz8W/DQ==", + "license": "MIT", + "peerDependencies": { + "marked": ">=7.0.0", + "shiki": ">=1.0.0" } }, "node_modules/math-intrinsics": { @@ -17297,7 +17327,6 @@ "version": "13.2.1", "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", - "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -17731,7 +17760,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -17858,7 +17886,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", - "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -17932,7 +17959,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", - "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -17977,7 +18003,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -17994,7 +18019,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", - "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -19585,14 +19609,12 @@ "version": "0.12.1", "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==", - "dev": true, "license": "MIT" }, "node_modules/oniguruma-to-es": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.4.tgz", "integrity": "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==", - "dev": true, "license": "MIT", "dependencies": { "oniguruma-parser": "^0.12.1", @@ -19972,10 +19994,11 @@ } }, "node_modules/pako": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", - "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", - "license": "(MIT AND Zlib)" + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "dev": true, + "license": "MIT" }, "node_modules/param-case": { "version": "2.1.1", @@ -20717,7 +20740,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", - "dev": true, "license": "MIT", "funding": { "type": "github", @@ -21141,17 +21163,16 @@ "license": "Apache-2.0" }, "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "license": "MIT", - "optional": true + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true, + "license": "MIT" }, "node_modules/regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/regex/-/regex-6.0.1.tgz", "integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==", - "dev": true, "license": "MIT", "dependencies": { "regex-utilities": "^2.3.0" @@ -21161,7 +21182,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", - "dev": true, "license": "MIT", "dependencies": { "regex-utilities": "^2.3.0" @@ -21171,7 +21191,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", - "dev": true, "license": "MIT" }, "node_modules/registry-auth-token": { @@ -22592,6 +22611,19 @@ "marked": "^0.6.2" } }, + "node_modules/sassdoc-extras/node_modules/marked": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.6.3.tgz", + "integrity": "sha512-Fqa7eq+UaxfMriqzYLayfqAE40WN03jf+zHjT18/uXNuzjq3TY0XTbrAoPeqSJrAmPz11VuUA+kBPYOhHt9oOQ==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sassdoc-plugin-localization": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/sassdoc-plugin-localization/-/sassdoc-plugin-localization-2.0.0.tgz", @@ -23589,7 +23621,6 @@ "version": "3.15.0", "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.15.0.tgz", "integrity": "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw==", - "dev": true, "license": "MIT", "dependencies": { "@shikijs/core": "3.15.0", @@ -24033,7 +24064,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", - "dev": true, "license": "MIT", "funding": { "type": "github", @@ -24335,7 +24365,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", - "dev": true, "license": "MIT", "dependencies": { "character-entities-html4": "^2.0.0", @@ -25406,7 +25435,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", - "dev": true, "license": "MIT", "funding": { "type": "github", @@ -25798,13 +25826,6 @@ "tiny-inflate": "^1.0.0" } }, - "node_modules/unicode-trie/node_modules/pako": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", - "dev": true, - "license": "MIT" - }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -25906,7 +25927,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", - "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -25935,7 +25955,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", - "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -25964,7 +25983,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -25978,7 +25996,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", - "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -26008,7 +26025,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -26430,7 +26446,6 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", - "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -26460,7 +26475,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", - "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -27937,7 +27951,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", - "dev": true, "license": "MIT", "funding": { "type": "github", diff --git a/package.json b/package.json index 83ebf9e7733..2b0be48844a 100644 --- a/package.json +++ b/package.json @@ -73,13 +73,17 @@ "@igniteui/material-icons-extended": "^3.1.0", "@lit-labs/ssr-dom-shim": "^1.3.0", "@types/source-map": "0.5.2", + "dompurify": "^3.3.0", "express": "^5.1.0", "fflate": "^0.8.1", "igniteui-theming": "^24.0.0", "igniteui-trial-watermark": "^3.1.0", "jspdf": "^3.0.4", "lodash-es": "^4.17.21", + "marked": "^16.4.0", + "marked-shiki": "^1.2.1", "rxjs": "^7.8.2", + "shiki": "^3.13.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" }, @@ -122,7 +126,7 @@ "ig-typedoc-theme": "^7.0.0", "igniteui-dockmanager": "^1.17.0", "igniteui-sassdoc-theme": "^2.1.0", - "igniteui-webcomponents": "6.2.1", + "igniteui-webcomponents": "^6.3.1", "jasmine": "^5.6.0", "jasmine-core": "^5.6.0", "karma": "^6.4.4", diff --git a/projects/igniteui-angular/chat-extras/index.ts b/projects/igniteui-angular/chat-extras/index.ts new file mode 100644 index 00000000000..decc72d85bc --- /dev/null +++ b/projects/igniteui-angular/chat-extras/index.ts @@ -0,0 +1 @@ +export * from './src/public_api'; diff --git a/projects/igniteui-angular/chat-extras/ng-package.json b/projects/igniteui-angular/chat-extras/ng-package.json new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/projects/igniteui-angular/chat-extras/ng-package.json @@ -0,0 +1 @@ +{} diff --git a/projects/igniteui-angular/chat-extras/src/markdown-pipe.spec.ts b/projects/igniteui-angular/chat-extras/src/markdown-pipe.spec.ts new file mode 100644 index 00000000000..cffbb6d5c2b --- /dev/null +++ b/projects/igniteui-angular/chat-extras/src/markdown-pipe.spec.ts @@ -0,0 +1,57 @@ +import { DomSanitizer } from '@angular/platform-browser'; +import { TestBed } from '@angular/core/testing'; +import { IgxChatMarkdownService } from './markdown-service'; +import { MarkdownPipe } from './markdown-pipe'; +import Spy = jasmine.Spy; + +// Mock the Service: We only care that the pipe calls the service and gets an HTML string. +// We provide a *known* unsafe HTML string to ensure sanitization is working. +const mockUnsafeHtml = ` +
unsafe
+ Hello World
\n'; + + const result = await service.parse(markdown); + expect(result).toBe(expectedHtml); + }); + + it('should parse a code block with shiki highlighting', async () => { + const markdown = '```typescript\nconst x = 5;\n```'; + const result = await service.parse(markdown); + + expect(result).toContain(' {
+ const markdown = '[Infragistics](https://www.infragistics.com)';
+ const expectedLink = '';
+
+ const result = await service.parse(markdown);
+ expect(result).toContain(expectedLink);
+ });
+});
diff --git a/projects/igniteui-angular/chat-extras/src/markdown-service.ts b/projects/igniteui-angular/chat-extras/src/markdown-service.ts
new file mode 100644
index 00000000000..4f2edf2a508
--- /dev/null
+++ b/projects/igniteui-angular/chat-extras/src/markdown-service.ts
@@ -0,0 +1,67 @@
+import { Injectable } from '@angular/core';
+import { Marked } from 'marked';
+import markedShiki from 'marked-shiki';
+import { bundledThemes, createHighlighter } from 'shiki/bundle/web';
+
+
+const DEFAULT_LANGUAGES = ['javascript', 'typescript', 'html', 'css'];
+const DEFAULT_THEMES = {
+ light: 'github-light',
+ dark: 'github-dark'
+};
+
+@Injectable({ providedIn: 'root' })
+export class IgxChatMarkdownService {
+
+ private _instance: Marked;
+ private _isInitialized: Promise;
+
+ private _initializeMarked(): void {
+ this._instance = new Marked({
+ breaks: true,
+ gfm: true,
+ extensions: [
+ {
+ name: 'link',
+ renderer({ href, title, text }) {
+ return `${text}`;
+ }
+ }
+ ]
+ });
+ }
+
+ private async _initializeShiki(): Promise {
+ const highlighter = await createHighlighter({
+ langs: DEFAULT_LANGUAGES,
+ themes: Object.keys(bundledThemes)
+ });
+
+ this._instance.use(
+ markedShiki({
+ highlight(code, lang, _) {
+ try {
+ return highlighter.codeToHtml(code, {
+ lang,
+ themes: DEFAULT_THEMES,
+ });
+
+ } catch {
+ return `${code}
`;
+ }
+ }
+ })
+ );
+ }
+
+
+ constructor() {
+ this._initializeMarked();
+ this._isInitialized = this._initializeShiki();
+ }
+
+ public async parse(text: string): Promise {
+ await this._isInitialized;
+ return await this._instance.parse(text);
+ }
+}
diff --git a/projects/igniteui-angular/chat-extras/src/public_api.ts b/projects/igniteui-angular/chat-extras/src/public_api.ts
new file mode 100644
index 00000000000..de599f08302
--- /dev/null
+++ b/projects/igniteui-angular/chat-extras/src/public_api.ts
@@ -0,0 +1 @@
+export { MarkdownPipe } from './markdown-pipe';
diff --git a/projects/igniteui-angular/chat/index.ts b/projects/igniteui-angular/chat/index.ts
new file mode 100644
index 00000000000..decc72d85bc
--- /dev/null
+++ b/projects/igniteui-angular/chat/index.ts
@@ -0,0 +1 @@
+export * from './src/public_api';
diff --git a/projects/igniteui-angular/chat/ng-package.json b/projects/igniteui-angular/chat/ng-package.json
new file mode 100644
index 00000000000..0967ef424bc
--- /dev/null
+++ b/projects/igniteui-angular/chat/ng-package.json
@@ -0,0 +1 @@
+{}
diff --git a/projects/igniteui-angular/chat/src/chat.component.html b/projects/igniteui-angular/chat/src/chat.component.html
new file mode 100644
index 00000000000..896e36340b4
--- /dev/null
+++ b/projects/igniteui-angular/chat/src/chat.component.html
@@ -0,0 +1,16 @@
+
+
+
diff --git a/projects/igniteui-angular/chat/src/chat.component.ts b/projects/igniteui-angular/chat/src/chat.component.ts
new file mode 100644
index 00000000000..97aa851aeff
--- /dev/null
+++ b/projects/igniteui-angular/chat/src/chat.component.ts
@@ -0,0 +1,329 @@
+import {
+ ChangeDetectionStrategy,
+ Component,
+ CUSTOM_ELEMENTS_SCHEMA,
+ Directive,
+ effect,
+ inject,
+ input,
+ OnInit,
+ output,
+ signal,
+ TemplateRef,
+ ViewContainerRef,
+ OnDestroy,
+ ViewRef,
+ computed,
+} from '@angular/core';
+import {
+ IgcChatComponent,
+ type IgcChatMessageAttachment,
+ type IgcChatMessage,
+ type IgcChatOptions,
+ type ChatRenderContext,
+ type ChatRenderers,
+ type ChatAttachmentRenderContext,
+ type ChatInputRenderContext,
+ type ChatMessageRenderContext,
+ type IgcChatMessageReaction,
+} from 'igniteui-webcomponents';
+
+type ChatContextUnion =
+ | ChatAttachmentRenderContext
+ | ChatMessageRenderContext
+ | ChatInputRenderContext
+ | ChatRenderContext;
+
+type ChatContextType =
+ T extends ChatAttachmentRenderContext
+ ? IgcChatMessageAttachment
+ : T extends ChatMessageRenderContext
+ ? IgcChatMessage
+ : T extends ChatInputRenderContext
+ ? string
+ : T extends ChatRenderContext
+ ? { instance: IgcChatComponent }
+ : never;
+
+type ExtractChatContext = T extends (ctx: infer R) => any ? R : never;
+
+type ChatTemplatesContextMap = {
+ [K in keyof ChatRenderers]: {
+ $implicit: ChatContextType<
+ ExtractChatContext> & ChatContextUnion
+ >;
+ };
+};
+
+/**
+ * Template references for customizing chat component rendering.
+ * Each property corresponds to a specific part of the chat UI that can be customized.
+ *
+ * @example
+ * ```typescript
+ * templates = {
+ * messageContent: this.customMessageTemplate,
+ * attachment: this.customAttachmentTemplate
+ * }
+ * ```
+ */
+export type IgxChatTemplates = {
+ [K in keyof Omit]?: TemplateRef;
+};
+
+/**
+ * Configuration options for the chat component.
+ */
+export type IgxChatOptions = Omit;
+
+
+/**
+ * Angular wrapper component for the Ignite UI Web Components Chat component.
+ *
+ * This component provides an Angular-friendly interface to the igc-chat web component,
+ * including support for Angular templates, signals, and change detection.
+ *
+ * Uses OnPush change detection strategy for optimal performance. All inputs are signals,
+ * so changes are automatically tracked and propagated to the underlying web component.
+ *
+ * @example
+ * ```typescript
+ *
+ * ```
+ */
+@Component({
+ selector: 'igx-chat',
+ standalone: true,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
+ templateUrl: './chat.component.html'
+})
+export class IgxChatComponent implements OnInit, OnDestroy {
+ //#region Internal state
+
+ private readonly _view = inject(ViewContainerRef);
+ private readonly _templateViewRefs = new Map, Set>();
+ private _oldTemplates: IgxChatTemplates = {};
+
+ protected readonly _transformedTemplates = signal({});
+
+ protected readonly _mergedOptions = computed(() => {
+ const options = this.options();
+ const transformedTemplates = this._transformedTemplates();
+ return {
+ ...options,
+ renderers: transformedTemplates
+ };
+ });
+
+ //#endregion
+
+ //#region Inputs
+
+ /** Array of chat messages to display */
+ public readonly messages = input([]);
+
+ /** Draft message with text and optional attachments */
+ public readonly draftMessage = input<
+ { text: string; attachments?: IgcChatMessageAttachment[] } | undefined
+ >({ text: '' });
+
+ /** Configuration options for the chat component */
+ public readonly options = input({});
+
+ /** Custom templates for rendering chat elements */
+ public readonly templates = input({});
+
+ //#endregion
+
+ //#region Outputs
+
+ /** Emitted when a new message is created */
+ public readonly messageCreated = output();
+
+ /** Emitted when a user reacts to a message */
+ public readonly messageReact = output();
+
+ /** Emitted when an attachment is clicked */
+ public readonly attachmentClick = output();
+
+ /** Emitted when attachment drag starts */
+ public readonly attachmentDrag = output();
+
+ /** Emitted when attachment is dropped */
+ public readonly attachmentDrop = output();
+
+ /** Emitted when typing indicator state changes */
+ public readonly typingChange = output();
+
+ /** Emitted when the input receives focus */
+ public readonly inputFocus = output();
+
+ /** Emitted when the input loses focus */
+ public readonly inputBlur = output();
+
+ /** Emitted when the input value changes */
+ public readonly inputChange = output();
+
+ //#endregion
+
+ /** @internal */
+ public ngOnInit(): void {
+ IgcChatComponent.register();
+ }
+
+ /** @internal */
+ public ngOnDestroy(): void {
+ for (const viewSet of this._templateViewRefs.values()) {
+ viewSet.forEach(viewRef => viewRef.destroy());
+ }
+ this._templateViewRefs.clear();
+ }
+
+ constructor() {
+ // Templates changed - update transformed templates and viewRefs
+ effect(() => {
+ const templates = this.templates();
+ this._setTemplates(templates ?? {});
+ });
+ }
+
+ private _setTemplates(newTemplates: IgxChatTemplates): void {
+ const templateCopies: ChatRenderers = {};
+ const newTemplateKeys = Object.keys(newTemplates) as Array;
+
+ const oldTemplates = this._oldTemplates;
+ const oldTemplateKeys = Object.keys(oldTemplates) as Array;
+
+ for (const key of oldTemplateKeys) {
+ const oldRef = oldTemplates[key];
+ const newRef = newTemplates[key];
+
+ if (oldRef && oldRef !== newRef) {
+ const obsolete = this._templateViewRefs.get(oldRef);
+ if (obsolete) {
+ obsolete.forEach(viewRef => viewRef.destroy());
+ this._templateViewRefs.delete(oldRef);
+ }
+ }
+ }
+
+ this._oldTemplates = {};
+
+ for (const key of newTemplateKeys) {
+ const ref = newTemplates[key];
+ if (ref) {
+ (this._oldTemplates as Record>)[key] = ref;
+ templateCopies[key] = this._createTemplateRenderer(ref);
+ }
+ }
+
+ this._transformedTemplates.set(templateCopies);
+ }
+
+ private _createTemplateRenderer(ref: NonNullable) {
+ type ChatContext = ExtractChatContext>;
+
+ if (!this._templateViewRefs.has(ref)) {
+ this._templateViewRefs.set(ref, new Set());
+ }
+
+ const viewSet = this._templateViewRefs.get(ref)!;
+
+ return (ctx: ChatContext) => {
+ const context = ctx as ChatContextUnion;
+ let angularContext: any;
+
+ if ('message' in context && 'attachment' in context) {
+ angularContext = { $implicit: context.attachment };
+ } else if ('message' in context) {
+ angularContext = { $implicit: context.message };
+ } else if ('value' in context) {
+ angularContext = {
+ $implicit: context.value,
+ attachments: context.attachments
+ };
+ } else {
+ angularContext = { $implicit: { instance: context.instance } };
+ }
+
+ const viewRef = this._view.createEmbeddedView(ref, angularContext);
+ viewSet.add(viewRef);
+
+ return viewRef.rootNodes;
+ }
+ }
+}
+
+/**
+ * Context provided to the chat input template.
+ */
+export interface ChatInputContext {
+ /** The current input value */
+ $implicit: string;
+ /** Array of attachments associated with the input */
+ attachments: IgcChatMessageAttachment[];
+}
+
+/**
+ * Directive providing type information for chat message template contexts.
+ * Use this directive on ng-template elements that render chat messages.
+ *
+ * @example
+ * ```html
+ *
+ * {{ message.text }}
+ *
+ * ```
+ */
+@Directive({ selector: '[igxChatMessageContext]', standalone: true })
+export class IgxChatMessageContextDirective {
+
+ public static ngTemplateContextGuard(_: IgxChatMessageContextDirective, ctx: unknown): ctx is { $implicit: IgcChatMessage } {
+ return true;
+ }
+}
+
+/**
+ * Directive providing type information for chat attachment template contexts.
+ * Use this directive on ng-template elements that render message attachments.
+ *
+ * @example
+ * ```html
+ *
+ *
+ *
+ * ```
+ */
+@Directive({ selector: '[igxChatAttachmentContext]', standalone: true })
+export class IgxChatAttachmentContextDirective {
+
+ public static ngTemplateContextGuard(_: IgxChatAttachmentContextDirective, ctx: unknown): ctx is { $implicit: IgcChatMessageAttachment } {
+ return true;
+ }
+}
+
+/**
+ * Directive providing type information for chat input template contexts.
+ * Use this directive on ng-template elements that render the chat input.
+ *
+ * @example
+ * ```html
+ *
+ *
+ *
+ * ```
+ */
+@Directive({ selector: '[igxChatInputContext]', standalone: true })
+export class IgxChatInputContextDirective {
+
+ public static ngTemplateContextGuard(_: IgxChatInputContextDirective, ctx: unknown): ctx is ChatInputContext {
+ return true;
+ }
+}
diff --git a/projects/igniteui-angular/chat/src/chat.spec.ts b/projects/igniteui-angular/chat/src/chat.spec.ts
new file mode 100644
index 00000000000..2977d06f194
--- /dev/null
+++ b/projects/igniteui-angular/chat/src/chat.spec.ts
@@ -0,0 +1,177 @@
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'
+import { IgxChatComponent, IgxChatMessageContextDirective, type IgxChatTemplates } from './chat.component'
+import { Component, signal, TemplateRef, viewChild } from '@angular/core';
+import type { IgcChatComponent, IgcChatMessage, IgcTextareaComponent } from 'igniteui-webcomponents';
+
+describe('Chat wrapper', () => {
+
+ let chatComponent: IgxChatComponent;
+ let chatElement: IgcChatComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ imports: [IgxChatComponent]
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(IgxChatComponent);
+ chatComponent = fixture.componentInstance;
+ chatElement = getChatElement(fixture);
+ fixture.detectChanges();
+ })
+
+ it('is created', () => {
+ expect(chatComponent).toBeDefined();
+ });
+
+ it('has correct initial empty state', () => {
+ const draft = chatComponent.draftMessage();
+
+ expect(chatComponent.messages().length).toEqual(0);
+ expect(draft.text).toEqual('');
+ expect(draft.attachments).toBeUndefined();
+ });
+
+ it('correct bindings for messages', async () => {
+ fixture.componentRef.setInput('messages', [{ id: '1', sender: 'user', text: 'Hello' }]);
+
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+
+ const messageElement = getChatMessages(chatElement)[0];
+ expect(messageElement).toBeDefined();
+ expect(getChatMessageDOM(messageElement).textContent.trim()).toEqual(chatComponent.messages()[0].text);
+ });
+
+ it('correct bindings for draft message', async () => {
+ fixture.componentRef.setInput('draftMessage', { text: 'Hello world' });
+
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ const textarea = getChatInput(chatElement);
+ expect(textarea.value).toEqual(chatComponent.draftMessage().text);
+ });
+});
+
+describe('Chat templates', () => {
+ let fixture: ComponentFixture;
+ let chatElement: IgcChatComponent;
+
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ imports: [IgxChatComponent, IgxChatMessageContextDirective, ChatTemplatesBed]
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ChatTemplatesBed);
+ fixture.detectChanges();
+ chatElement = getChatElement(fixture);
+ });
+
+ it('has correct initially bound template', async () => {
+ await fixture.whenStable();
+
+ // NOTE: This is invoked since in the test bed there is no app ref so fresh embedded view
+ // has no change detection ran on it. In an application scenario this is not the case.
+ // This is so we don't explicitly invoke `viewRef.detectChanges()` inside the returned closure
+ // from the wrapper's `_createTemplateRenderer` call.
+ fixture.detectChanges();
+ expect(getChatMessageDOM(getChatMessages(chatElement)[0]).textContent.trim())
+ .toEqual(`Your message: ${fixture.componentInstance.messages()[0].text}`);
+ });
+});
+
+describe('Chat dynamic templates binding', () => {
+ let fixture: ComponentFixture;
+ let chatElement: IgcChatComponent;
+
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ imports: [IgxChatComponent, IgxChatMessageContextDirective, ChatDynamicTemplatesBed]
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ChatDynamicTemplatesBed);
+ fixture.detectChanges();
+ chatElement = getChatElement(fixture);
+ });
+
+ it('supports late binding', async () => {
+ fixture.componentInstance.bindTemplates();
+ fixture.detectChanges();
+
+ await fixture.whenStable();
+ fixture.detectChanges();
+
+ expect(getChatMessageDOM(getChatMessages(chatElement)[0]).textContent.trim())
+ .toEqual(`Your message: ${fixture.componentInstance.messages()[0].text}`);
+ });
+
+});
+
+
+@Component({
+ template: `
+
+
+ Your message: {{ message.text }}
+
+ `,
+ imports: [IgxChatComponent, IgxChatMessageContextDirective]
+})
+class ChatTemplatesBed {
+ public messages = signal([{
+ id: '1',
+ sender: 'user',
+ text: 'Hello world'
+ }]);
+ public messageTemplate = viewChild.required>('message');
+}
+
+@Component({
+ template: `
+
+
+ Your message: {{ message.text }}
+
+ `,
+ imports: [IgxChatComponent, IgxChatMessageContextDirective]
+})
+class ChatDynamicTemplatesBed {
+ public templates = signal(null);
+ public messages = signal([{
+ id: '1',
+ sender: 'user',
+ text: 'Hello world'
+ }]);
+ public messageTemplate = viewChild.required>('message');
+
+ public bindTemplates(): void {
+ this.templates.set({
+ messageContent: this.messageTemplate()
+ });
+ }
+}
+
+function getChatElement(fixture: ComponentFixture): IgcChatComponent {
+ const nativeElement = fixture.nativeElement as HTMLElement;
+ return nativeElement.querySelector('igc-chat');
+}
+
+function getChatInput(chat: IgcChatComponent): IgcTextareaComponent {
+ return chat.renderRoot.querySelector('igc-chat-input').shadowRoot.querySelector('igc-textarea');
+}
+
+function getChatMessages(chat: IgcChatComponent): HTMLElement[] {
+ return Array.from(chat.renderRoot.querySelectorAll('igc-chat-message'));
+}
+
+function getChatMessageDOM(message: HTMLElement) {
+ return message.shadowRoot;
+}
diff --git a/projects/igniteui-angular/chat/src/public_api.ts b/projects/igniteui-angular/chat/src/public_api.ts
new file mode 100644
index 00000000000..eca793fd7b9
--- /dev/null
+++ b/projects/igniteui-angular/chat/src/public_api.ts
@@ -0,0 +1 @@
+export * from './chat.component';
diff --git a/projects/igniteui-angular/ng-package.json b/projects/igniteui-angular/ng-package.json
index 183215b2851..503e963ec27 100644
--- a/projects/igniteui-angular/ng-package.json
+++ b/projects/igniteui-angular/ng-package.json
@@ -13,6 +13,11 @@
"igniteui-trial-watermark",
"lodash-es",
"@igniteui/material-icons-extended",
- "igniteui-theming"
+ "igniteui-theming",
+ "igniteui-webcomponents",
+ "dompurify",
+ "marked",
+ "marked-shiki",
+ "shiki"
]
}
diff --git a/projects/igniteui-angular/ng-package.prod.json b/projects/igniteui-angular/ng-package.prod.json
index 7af1254752a..10a2234414b 100644
--- a/projects/igniteui-angular/ng-package.prod.json
+++ b/projects/igniteui-angular/ng-package.prod.json
@@ -12,6 +12,11 @@
"igniteui-trial-watermark",
"lodash-es",
"@igniteui/material-icons-extended",
- "igniteui-theming"
+ "igniteui-theming",
+ "igniteui-webcomponents",
+ "dompurify",
+ "marked",
+ "marked-shiki",
+ "shiki"
]
}
diff --git a/projects/igniteui-angular/package.json b/projects/igniteui-angular/package.json
index 264200c1cb9..03225f78e67 100644
--- a/projects/igniteui-angular/package.json
+++ b/projects/igniteui-angular/package.json
@@ -84,7 +84,12 @@
"@angular/animations": "21",
"@angular/forms": "21",
"hammerjs": "^2.0.8",
- "@types/hammerjs": "^2.0.46"
+ "@types/hammerjs": "^2.0.46",
+ "igniteui-webcomponents": "^6.3.0",
+ "dompurify": "^3.2.0",
+ "marked": "^16.3.0",
+ "marked-shiki": "^1.2.0",
+ "shiki": "^3.12.0"
},
"peerDependenciesMeta": {
"hammerjs": {
@@ -92,6 +97,21 @@
},
"@types/hammerjs": {
"optional": true
+ },
+ "igniteui-webcomponents": {
+ "optional": true
+ },
+ "dompurify": {
+ "optional": true
+ },
+ "marked": {
+ "optional": true
+ },
+ "marked-shiki": {
+ "optional": true
+ },
+ "shiki": {
+ "optional": true
}
},
"igxDevDependencies": {
diff --git a/projects/igniteui-angular/schematics/utils/dependency-handler.ts b/projects/igniteui-angular/schematics/utils/dependency-handler.ts
index a7fdb148e62..71e3e0f46df 100644
--- a/projects/igniteui-angular/schematics/utils/dependency-handler.ts
+++ b/projects/igniteui-angular/schematics/utils/dependency-handler.ts
@@ -27,6 +27,11 @@ export const DEPENDENCIES_MAP: PackageEntry[] = [
{ name: 'lodash-es', target: PackageTarget.NONE },
{ name: '@igniteui/material-icons-extended', target: PackageTarget.REGULAR },
{ name: 'igniteui-theming', target: PackageTarget.NONE },
+ { name: 'igniteui-webcomponents', target: PackageTarget.NONE },
+ { name: 'dompurify', target: PackageTarget.NONE },
+ { name: 'marked', target: PackageTarget.NONE },
+ { name: 'marked-shiki', target: PackageTarget.NONE },
+ { name: 'shiki', target: PackageTarget.NONE },
// peerDependencies
{ name: '@angular/forms', target: PackageTarget.NONE },
{ name: '@angular/common', target: PackageTarget.NONE },
diff --git a/projects/igniteui-angular/src/public_api.ts b/projects/igniteui-angular/src/public_api.ts
index 1360707be6f..5352b064fef 100644
--- a/projects/igniteui-angular/src/public_api.ts
+++ b/projects/igniteui-angular/src/public_api.ts
@@ -34,6 +34,7 @@ export * from 'igniteui-angular/button-group';
export * from 'igniteui-angular/calendar';
export * from 'igniteui-angular/card';
export * from 'igniteui-angular/carousel';
+export * from 'igniteui-angular/chat';
export * from 'igniteui-angular/checkbox';
export * from 'igniteui-angular/chips';
export * from 'igniteui-angular/combo';
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index c6cc68f4294..5785f5a989c 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -128,6 +128,11 @@ export class AppComponent implements OnInit {
icon: 'view_carousel',
name: 'Carousel'
},
+ {
+ link: '/chat',
+ icon: 'chat',
+ name: 'Chat'
+ },
{
link: '/chip',
icon: 'android',
diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts
index 1b4ee86a042..62c98189e5c 100644
--- a/src/app/app.routes.ts
+++ b/src/app/app.routes.ts
@@ -6,6 +6,7 @@ import { ButtonSampleComponent } from './button/button.sample';
import { CalendarSampleComponent } from './calendar/calendar.sample';
import { CardSampleComponent } from './card/card.sample';
import { CarouselSampleComponent } from './carousel/carousel.sample';
+import { ChatSampleComponent } from './chat/chat.sample';
import { InputControlsSampleComponent } from './input-controls/input-controls.sample';
import { ChipsSampleComponent } from './chips/chips.sample';
import { CircularProgressSampleComponent } from './circular-progress-showcase/circular-progress-showcase.sample'
@@ -200,6 +201,10 @@ export const appRoutes: Routes = [
path: 'carousel',
component: CarouselSampleComponent
},
+ {
+ path: 'chat',
+ component: ChatSampleComponent
+ },
{
path: 'input-controls',
component: InputControlsSampleComponent
diff --git a/src/app/chat/chat.sample.html b/src/app/chat/chat.sample.html
new file mode 100644
index 00000000000..2e278ad1b91
--- /dev/null
+++ b/src/app/chat/chat.sample.html
@@ -0,0 +1,8 @@
+
+ Prefix
+ Actions
+
+
+
+
+
diff --git a/src/app/chat/chat.sample.scss b/src/app/chat/chat.sample.scss
new file mode 100644
index 00000000000..87a84a0fd2f
--- /dev/null
+++ b/src/app/chat/chat.sample.scss
@@ -0,0 +1,4 @@
+#igniteui-demo-app .sample-wrapper {
+ padding: 0;
+}
+
diff --git a/src/app/chat/chat.sample.ts b/src/app/chat/chat.sample.ts
new file mode 100644
index 00000000000..d4d9058fa34
--- /dev/null
+++ b/src/app/chat/chat.sample.ts
@@ -0,0 +1,115 @@
+
+import { AsyncPipe } from '@angular/common';
+import {
+ ChangeDetectionStrategy,
+ Component,
+ CUSTOM_ELEMENTS_SCHEMA,
+ effect,
+ signal,
+ viewChild,
+} from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import {
+ IgxChatComponent,
+ IgxChatMessageContextDirective,
+ type IgxChatOptions,
+} from 'igniteui-angular/chat';
+import { MarkdownPipe } from 'igniteui-angular/chat-extras';
+
+@Component({
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ selector: 'app-chat-sample',
+ styleUrls: ['chat.sample.scss'],
+ templateUrl: 'chat.sample.html',
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
+ imports: [
+ FormsModule,
+ AsyncPipe,
+ IgxChatComponent,
+ MarkdownPipe,
+ IgxChatMessageContextDirective,
+ ]
+})
+export class ChatSampleComponent {
+ protected _template = viewChild.required('renderer');
+
+ public messages = signal([
+ {
+ id: '1',
+ text: `Hello. How can we assist you today?`,
+ sender: 'support',
+ },
+ {
+ id: '2',
+ text: `Hello. I have problem with styling IgcAvatarComponent. Can you take a look at the attached file and help me?`,
+ sender: 'user',
+ attachments: [
+ {
+ id: 'AvatarStyles.css',
+ name: 'AvatarStyles.css',
+ url: './styles/AvatarStyles.css',
+ type: 'text/css'
+ },
+ ],
+ },
+ {
+ id: '3',
+ text: `Sure, give me a moment to check the file.`,
+ sender: 'support',
+ },
+ {
+ id: '4',
+ text: `
+Thank you for your patience. It seems that the issue is the name of the **CSS part**. Here is the fixed code:
+
+
+\`\`\`css
+igc-avatar::part(base) {
+ --size: 60px;
+ color: var(--ig-success-500-contrast);
+ background: var(--ig-success-500);
+ border-radius: 20px;
+}
+\`\`\``,
+ sender: 'support',
+ },
+ {
+ id: '123213123',
+ sender: 'support',
+ text: `
+Here is some typescript:
+
+
+\`\`\`ts
+
+class User {
+ constructor(public name: string, public age: number) {}
+}
+\`\`\``
+ }
+ ]);
+
+ public options = signal({
+ disableAutoScroll: false,
+ disableInputAttachments: false,
+ suggestions: [`It works. Thanks.`, `It doesn't work.`],
+ inputPlaceholder: 'Type your message here...',
+ headerText: 'Customer Support',
+ });
+
+
+ public templates = signal({});
+
+ constructor() {
+ effect(() => {
+ const template = this._template();
+ if (template) {
+ this.templates.set({ messageContent: template });
+ }
+ });
+ }
+
+ public onMessageReact(event: any) {
+ console.log(event);
+ }
+}