{"id":475163,"date":"2025-09-16T06:56:42","date_gmt":"2025-09-16T06:56:42","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=475163"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"pishem-vysokoproizvoditelnyy-vyuport-dlya-messendzhera","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=475163","title":{"rendered":"\u041f\u0438\u0448\u0435\u043c \u0432\u044b\u0441\u043e\u043a\u043e\u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0432\u044c\u044e\u043f\u043e\u0440\u0442 \u0434\u043b\u044f \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440\u0430"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u041f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0430\u044f <a href=\"https:\/\/habr.com\/ru\/articles\/923120\/\" rel=\"noopener noreferrer nofollow\">\u0441\u0442\u0430\u0442\u044c\u044f <\/a>\u0431\u044b\u043b\u0430 \u043f\u043e\u0441\u0432\u044f\u0449\u0435\u043d\u0430 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0443 <a href=\"https:\/\/github.com\/DjonnyX\/ng-virtual-list\/tree\/main\/projects\/ng-virtual-list\" rel=\"noopener noreferrer nofollow\">ng\u2011virtual\u2011list<\/a>. \u0421\u00a0\u0442\u0435\u0445 \u043f\u043e\u0440 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u043e\u0431\u0440\u0435\u043b \u0431\u043e\u0433\u0430\u0442\u044b\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b, \u0432\u043d\u0435\u0441\u0435\u043d \u0440\u044f\u0434 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0445 \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u0439 \u0432\u00a0\u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u044b \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u0442\u0440\u0435\u043a\u0438\u043d\u0433\u0430, \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0430 \u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0438 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c. \u0422\u0430\u043a\u0436\u0435\u00a0\u0431\u044b\u043b \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d \u043f\u043e\u0440\u0442 \u043d\u0430 <a href=\"https:\/\/github.com\/DjonnyX\/rcx-virtual-list\" rel=\"noopener noreferrer nofollow\">React<\/a>. \u041a\u043e\u043c\u0443 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b \u0442\u0435\u0441\u0442\u044b, \u0431\u0435\u043d\u0447\u043c\u0430\u0440\u043a\u0438 \u0438 \u0446\u0438\u0444\u0440\u044b, \u0447\u0435\u043c \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442\u0441\u044f \u0434\u0430\u043d\u043d\u044b\u0439 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u043e\u0442\u00a0\u0430\u043d\u0430\u043b\u043e\u0433\u043e\u0432 \u0438 \u043a\u043e\u0440\u043e\u0431\u043e\u0447\u043d\u044b\u0445 \u0440\u0435\u0448\u0435\u043d\u0438\u0439 \u0434\u043b\u044f <strong>Angular CDKVirtualFor<\/strong>, \u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u044b \u043a\u00a0\u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0435.<\/p>\n<blockquote>\n<p>\u0425\u043e\u0447\u0435\u0442\u0441\u044f \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e <a href=\"https:\/\/github.com\/DjonnyX\/ng-virtual-list\/tree\/main\/projects\/ng-virtual-list\" rel=\"noopener noreferrer nofollow\">ng\u2011virtual\u2011list<\/a> \u043d\u0435\u00a0\u043f\u0440\u043e\u0441\u0442\u043e \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a, \u043e\u043d \u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e \u043c\u043e\u0436\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043a\u0430\u043a\u00a0\u0432\u0438\u0440\u0442\u0443\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 <strong>select <\/strong>\u0438 <strong>multi\u2011select<\/strong>; \u0443\u043c\u0435\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441\u00a0\u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u043c\u0438 \u0441\u043f\u0438\u0441\u043a\u0430\u043c\u0438 \u0438 \u0432\u00a0\u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c <strong>collapsableGroups <\/strong>\u0438 \u0440\u0430\u0431\u043e\u0442\u0430 \u0432\u00a0\u043c\u043d\u043e\u0433\u043e\u043f\u043e\u0442\u043e\u0447\u043d\u043e\u043c \u0440\u0435\u0436\u0438\u043c\u0435.<\/p>\n<\/blockquote>\n<h2>\u041f\u0440\u043e\u0435\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435<\/h2>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u0441\u0442\u0430\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u043e\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u0442\u044c \u0432\u0441\u044e \u0441\u0438\u043b\u0443 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u043f\u0438\u0441\u043a\u043e\u0432 \u043d\u0430\u00a0\u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435.<\/p>\n<p><strong>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c\u0441\u044f \u0441\u00a0\u0443\u0441\u043b\u043e\u0432\u0438\u044f\u043c\u0438 \u0434\u043b\u044f\u00a0\u043f\u0440\u043e\u0435\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u044c\u044e\u043f\u043e\u0440\u0442\u0430:<\/strong><\/p>\n<ul>\n<li>\n<p>\u0421\u043f\u0438\u0441\u043e\u043a \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439. \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0443\u0442 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c\u0441\u044f \u0432\u00a0\u043d\u0430\u0447\u0430\u043b\u0435 \u0438 \u043a\u043e\u043d\u0446\u0435 \u0441\u043f\u0438\u0441\u043a\u0430; \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439.<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u0438\u0441\u043a \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u043e\u00a0\u043f\u043e\u0434\u0441\u0442\u0440\u043e\u043a\u0435.<\/p>\n<\/li>\n<li>\n<p>\u0411\u043e\u043a\u043e\u0432\u043e\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u043e\u0432 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u0432\u00a0\u043b\u0435\u0432\u043e\u043c \u0434\u043e\u043a\u0435. \u0414\u043e\u043a \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u043e\u00a0\u043a\u043b\u0438\u043a\u0443 \u043d\u0430\u00a0\u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e \u043a\u043d\u043e\u043f\u043a\u0443. \u041f\u0440\u0438\u00a0\u0432\u044b\u0431\u043e\u0440\u0435 \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430, \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0438 \u0434\u043e\u043a \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u0442\u0441\u044f.<\/p>\n<\/li>\n<li>\n<p>\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u0432\u044c\u044e\u043f\u043e\u0440\u0442\u0430 \u0432\u00a0\u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0438 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u0438\u0441\u043a\u043e\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0438 \u043a\u043d\u043e\u043f\u043a\u0430 \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u044f \u0434\u043e\u043a\u0430 \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u043e\u0432.<\/p>\n<\/li>\n<\/ul>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/6ae\/cf3\/44e\/6aecf344e4c878cefab92686f053b18f.png\" alt=\"\u041c\u0430\u043a\u0435\u0442 \u0431\u0443\u0434\u0443\u0449\u0435\u0433\u043e \u0432\u044c\u044e\u043f\u043e\u0440\u0442\u0430 \u0434\u043b\u044f \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440\u0430\" title=\"\u041c\u0430\u043a\u0435\u0442 \u0431\u0443\u0434\u0443\u0449\u0435\u0433\u043e \u0432\u044c\u044e\u043f\u043e\u0440\u0442\u0430 \u0434\u043b\u044f \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440\u0430\" width=\"1230\" height=\"706\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/6ae\/cf3\/44e\/6aecf344e4c878cefab92686f053b18f.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/6ae\/cf3\/44e\/6aecf344e4c878cefab92686f053b18f.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u041c\u0430\u043a\u0435\u0442 \u0431\u0443\u0434\u0443\u0449\u0435\u0433\u043e \u0432\u044c\u044e\u043f\u043e\u0440\u0442\u0430 \u0434\u043b\u044f\u00a0\u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440\u0430<\/figcaption><\/div>\n<\/figure>\n<p>\u0411\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <strong>Angular 19.x<\/strong> \u0438 <a href=\"https:\/\/www.npmjs.com\/package\/ng-virtual-list\/v\/19.7.6\" rel=\"noopener noreferrer nofollow\"><strong>ng\u2011virtual\u2011list@19<\/strong><\/a><\/p>\n<h2>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f<\/h2>\n<p>\u0412\u00a0\u0434\u0430\u043d\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043d\u0435\u00a0\u0431\u0443\u0434\u0443 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u0432\u0435\u0441\u044c \u043a\u043e\u0434 \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u0442. \u043a. \u0412\u044b \u0441\u043c\u043e\u0436\u0435\u0442\u0435 \u0435\u0433\u043e \u043d\u0430\u0439\u0442\u0438 \u043d\u0438\u0436\u0435 \u043f\u043e\u00a0\u0441\u0441\u044b\u043b\u043a\u0435. \u0421\u043a\u043e\u043d\u0446\u0435\u043d\u0442\u0440\u0438\u0440\u0443\u044e\u00a0\u043b\u0438\u0448\u044c \u043d\u0430\u00a0\u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0445 \u043c\u043e\u043c\u0435\u043d\u0442\u0430\u0445.<\/p>\n<p>\u0414\u0435\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0448\u0430\u0431\u043b\u043e\u043d\u0430:<\/p>\n<details class=\"spoiler\">\n<summary>\u0421\u043a\u0440\u044b\u0442\u044b\u0439 \u0442\u0435\u043a\u0441\u0442<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"xml\">&lt;div class=\"container\"&gt;    &lt;!-- \u041f\u0430\u043d\u0435\u043b\u044c \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432 --&gt;    &lt;div class=\"toolbar\"&gt;      &lt;div&gt;        &lt;!-- \u041a\u043d\u043e\u043f\u043a\u0430 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u044e\u0449\u0430\u044f \u0434\u043e\u043a \u0441 \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430\u043c\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 --&gt;        &lt;app-menu-button (click)=\"onOpenMenuHandler()\" [opened]=\"menuOpened()\" \/&gt;      &lt;\/div&gt;      &lt;!-- \u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 --&gt;      &lt;div class=\"title\"&gt;{{title()}}&lt;\/div&gt;      &lt;!-- \u042d\u043b\u0435\u043c\u0435\u043d\u0442 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043f\u043e \u043f\u043e\u0434\u0441\u0442\u0440\u043e\u043a\u0435 --&gt;      &lt;app-search (search)=\"onSearchHandler($event)\" \/&gt;    &lt;\/div&gt;    &lt;div class=\"list-container\"&gt;      @let doc = dockMode();      &lt;app-drawer [dock]=\"doc\" [dockLeftSize]=\"240\"&gt;        &lt;!-- \u0414\u043e\u043a \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 --&gt;        &lt;dock-left&gt;          &lt;div class=\"list-rooms__container\"&gt;            &lt;!-- \u0412\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u043e\u0432 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 --&gt;            &lt;ng-virtual-list class=\"list rooms\" [items]=\"items\" [itemRenderer]=\"itemRenderer\" [trackBy]=\"'id'\"              [itemSize]=\"40\" [dynamicSize]=\"true\" [bufferSize]=\"60\"              (onItemClick)=\"onRoomClickHandler($event)\"&gt;&lt;\/ng-virtual-list&gt;          &lt;\/div&gt;        &lt;\/dock-left&gt;        &lt;!-- \u0412\u044c\u044e\u043f\u043e\u0440\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 --&gt;        &lt;div class=\"list-wrapper\"&gt;          &lt;!-- \u0412\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 --&gt;          &lt;ng-virtual-list #dynamicList class=\"list\" [items]=\"groupDynamicItems\" [itemRenderer]=\"groupItemRenderer\"            [trackBy]=\"'id'\" [itemSize]=\"40\" [bufferSize]=\"30\" [bufferSize]=\"120\"            [itemConfigMap]=\"groupDynamicItemsConfigMap\" [dynamicSize]=\"true\" [snap]=\"true\"            (onScroll)=\"onScrollHandler($event)\" snappingMethod=\"advanced\" methodForSelecting=\"multi-select\"            (onScrollEnd)=\"onScrollEndHandler($event)\" [enabledBufferOptimization]=\"false\"            (onItemClick)=\"onClickHandler($event)\"&gt;&lt;\/ng-virtual-list&gt;        &lt;\/div&gt;      &lt;\/app-drawer&gt;    &lt;\/div&gt;  &lt;\/div&gt;&lt;!-- \u0428\u0430\u0431\u043b\u043e\u043d \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f --&gt;  &lt;ng-template #groupItemRenderer let-data=\"data\" let-measures=\"measures\" let-config=\"config\"&gt;    @if (data) {      @switch (data.type) {        &lt;!-- \u0418\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440 \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f --&gt;        @case (\"write-indicator\") {          &lt;div class=\"list__windicator-container\"&gt;            &lt;!-- \u0422\u0443\u0442 \u0440\u0430\u0441\u043f\u043e\u043b\u0430\u0433\u0430\u0435\u0442\u0441\u044f \u043f\u0438\u043a\u0442\u043e\u0433\u0440\u0430\u043c\u043c\u0430 \u0434\u043b\u044f \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440\u0430 \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f --&gt;          &lt;\/div&gt;        }        &lt;!-- \u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u0433\u0440\u0443\u043f\u043f\u044b \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 --&gt;        @case (\"group-header\") {          &lt;div class=\"list__group-container\" [ngClass]=\"{'snapped': config.snapped, 'snapped-out': config.snappedOut}\"&gt;            &lt;span&gt;{{data.name}}&lt;\/span&gt;          &lt;\/div&gt;        }        &lt;!-- C\u043e\u0431\u0449\u0435\u043d\u0438\u0435 --&gt;        @default {          @let isIn = data.incomType === 'in';          @let isOut = data.incomType === 'out';          @let class = {'in': isIn, 'out': isOut, 'edited': data.edited, 'selected': config.selected, focused: config.focus};          &lt;div class=\"list__container\" [ngClass]=\"class\" [longPress]=\"1000\"&gt;            &lt;div class=\"message__container\" [ngClass]=\"class\"&gt;              &lt;div class=\"message\" [ngClass]=\"class\"&gt;                @if (data.edited) {                  &lt;!-- \u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0441 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u043c --&gt;                  @if (data.image) {                    &lt;div class=\"complex-message\"&gt;                      &lt;img [src]=\"data.image\" \/&gt;                      &lt;textarea clickOutside [clickOutsideItem]=\"data\" (keydown)=\"onKeyDownHandler($event)\"                    (onClickOutside)=\"onOutsideClickHandler($event, data, config.selected)\"                    [ngStyle]=\"{height: getContentHeight(measures.height, true) + 'px'}\" [value]=\"data.name\"                    (click)=\"onTAClickHandler($event)\" (onOutsideClose)=\"onEditingCloseHandler($event)\"                    (change)=\"onEditedHandler($event, data)\"&gt;&lt;\/textarea&gt;                    &lt;\/div&gt;                  } @else {                    &lt;!-- \u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0431\u0435\u0437 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f --&gt;                    &lt;textarea clickOutside [clickOutsideItem]=\"data\" (keydown)=\"onKeyDownHandler($event)\"                    (onClickOutside)=\"onOutsideClickHandler($event, data, config.selected)\"                    [ngStyle]=\"{height: getContentHeight(measures.height) + 'px'}\" [value]=\"data.name\"                    (click)=\"onTAClickHandler($event)\" (onOutsideClose)=\"onEditingCloseHandler($event)\"                    (change)=\"onEditedHandler($event, data)\"&gt;&lt;\/textarea&gt;                  }                } @else {                  &lt;!-- \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0441 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u043c --&gt;                  @if (data.image) {                    &lt;div class=\"complex-message\"&gt;                      &lt;img [src]=\"data.image\" \/&gt;                      &lt;span searchHighlight substringClass=\"search-substring\" [text]=\"data.name\" [searchedWords]=\"searchedWords()\"                        (click)=\"onEditItemHandler($event, data, config.selected)\"&gt;{{data.name}}&lt;\/span&gt;                    &lt;\/div&gt;                  } @else {                    &lt;!-- \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0431\u0435\u0437 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f --&gt;                    &lt;span searchHighlight substringClass=\"search-substring\" [text]=\"data.name\" [searchedWords]=\"searchedWords()\"                      (click)=\"onEditItemHandler($event, data, config.selected)\"&gt;{{data.name}}&lt;\/span&gt;                  }                }              &lt;\/div&gt;                &lt;!-- \u041f\u0440\u0438 \u0432\u044b\u0431\u043e\u0440\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0435\u0433\u043e \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f --&gt;                @if (config.selected) {                  &lt;div class=\"flex\"&gt;&lt;\/div&gt;                  &lt;div class=\"message__controls\"&gt;                    &lt;div class=\"ctrl__button del-icon\" (click)=\"onDeleteItemHandler($event, data)\"&gt;                      &lt;!-- \u0418\u043a\u043e\u043d\u043a\u0430 \u0434\u043b\u044f \u043a\u043d\u043e\u043f\u043a\u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f --&gt;                    &lt;\/div&gt;                  &lt;\/div&gt;                }              &lt;\/div&gt;            &lt;\/div&gt;          }        }      }  &lt;\/ng-template&gt;  &lt;!-- \u0428\u0430\u0431\u043b\u043e\u043d \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u0441\u043f\u0438\u0441\u043a\u0430 \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 --&gt;  &lt;ng-template #itemRenderer let-data=\"data\" let-config=\"config\"&gt;    @if (data) {      @switch (data.type) {        @case (\"group-header\") {          &lt;div class=\"list__item-container\"&gt;            &lt;div class=\"content\"&gt;              &lt;span&gt;{{data.name}}&lt;\/span&gt;            &lt;\/div&gt;          &lt;\/div&gt;        }        @default {          &lt;div class=\"list__item-container\"&gt;            &lt;div class=\"message\"&gt;              &lt;span&gt;                &lt;!-- \u0422\u0443\u0442 \u0440\u0430\u0441\u043f\u043e\u043b\u0430\u0433\u0430\u0435\u0442\u0441\u044f \u043f\u0438\u043a\u0442\u043e\u0433\u0440\u0430\u043c\u043c\u0430 \u0434\u043b\u044f \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430 --&gt;              &lt;\/span&gt;              &lt;span&gt;{{data.name}}&lt;\/span&gt;            &lt;\/div&gt;          &lt;\/div&gt;        }      }    }  &lt;\/ng-template&gt;&lt;\/div&gt;<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<\/div>\n<\/details>\n<p>\u0414\u0435\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 App:<\/p>\n<details class=\"spoiler\">\n<summary>\u0421\u043a\u0440\u044b\u0442\u044b\u0439 \u0442\u0435\u043a\u0441\u0442<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"typescript\">constructor(private _service: ClickOutsideService) {    const list = this._listContainerRef;    this.dockMode = computed(() =&gt; {      const menuOpened = this.menuOpened();      return menuOpened ? DockMode.LEFT : DockMode.NONE;    });    const $virtualList = toObservable(list).pipe(      filter(list =&gt; !!list),      switchMap(list =&gt; combineLatest([of(list), list?.$initialized])),      filter(([, init]) =&gt; !!init),      map(([list]) =&gt; list),    );    \/\/ \u0412 \u043c\u043e\u043c\u0435\u043d\u0442 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u043f\u0438\u0441\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439    \/\/ \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u0435\u0441\u043b\u0438 \u043d\u0443\u0436\u043d\u043e \u043f\u0440\u043e\u0441\u043a\u0440\u043e\u043b\u043b\u0438\u0442\u044c \u0434\u043e \u043a\u043e\u043d\u0446\u0430 \u0441\u043f\u0438\u0441\u043a\u0430, \u0442\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0441\u043a\u0440\u043e\u043b\u043b    combineLatest([this.$version, $virtualList]).pipe(      map(([version, list]) =&gt; ({ version, list })),      filter(({ list }) =&gt; !!list),      debounceTime(50),      tap(({ version, list }) =&gt; {        if (version === 0) {          list!.scrollToEnd('instant');        }        if (this._$isEndOfListPosition.getValue()) {          list!.scrollToEnd('instant');        }      }),    ).subscribe();    \/\/ \u041f\u043e\u0438\u0441\u043a \u0438 \u0441\u043a\u0440\u043e\u043b\u043b \u0434\u043e \u0438\u0441\u043a\u043e\u043c\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432\u043e \u0432\u044c\u044e\u043f\u043e\u0440\u0442\u0435    combineLatest([$virtualList, toObservable(this.search)]).pipe(      map(([list, search]) =&gt; ({ list, search })),      filter(({ list }) =&gt; !!list),      debounceTime(0),      tap(({ list, search }) =&gt; {        this.searchedWords.set(search.split(' '));        for (let i = 0, l = this.groupDynamicItems.length; i &lt; l; i++) {          const item = this.groupDynamicItems[i], name: string = item['name'];          if (name) {            const index = name?.indexOf(search);            if (index &gt; -1) {              list!.scrollTo(item.id, 'instant');              break;            }          }        }      }),    ).subscribe();    \/\/ \u041f\u0440\u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u043f\u0435\u0440\u0432\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435    $virtualList.pipe(      delay(100),      mergeMap(() =&gt; this.write()),    ).subscribe();    \/\/ \u0414\u0430\u043b\u0435\u0435 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u043d\u043e\u0432\u044b\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u0441 \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u043e\u043c \u0432 2\u0441\u0435\u043a    from(interval(2000)).pipe(      mergeMap(() =&gt; this.write()),    ).subscribe();    \/\/ \u0412\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0435, \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043b\u0438 \u043f\u043e\u0437\u0438\u0446\u0438\u044f \u0441\u043a\u0440\u043e\u043b\u043b\u0430 \u043a\u043e\u043d\u0435\u0447\u043d\u043e\u0439    combineLatest([toObservable(this._scrollParams), $virtualList, this.$version]).pipe(      delay(10),      switchMap(([{ viewportEndY, scrollWeight }, list]) =&gt; {        let bounds: ISize | undefined;        if (list) {          bounds = list.getItemBounds(this.groupDynamicItems[this.groupDynamicItems.length - 1].id);        }        const height = (bounds?.height ?? 0);        return of((viewportEndY + height + SNAP_HEIGHT) &gt;= scrollWeight);      }),      tap(v =&gt; {        this._$isEndOfListPosition.next(v);      }),    ).subscribe();    const appHeightHandler = () =&gt; document.documentElement.style.setProperty('--app-height', `${window.innerHeight}px`);    window.addEventListener('resize', appHeightHandler);    $virtualList.pipe(      tap(() =&gt; {        appHeightHandler();      }),      delay(100),      tap(() =&gt; {        document.documentElement.style.setProperty('--viewport-alpha', '1');      }),    ).subscribe();  }  \/**   * \u0412\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442 \u0432\u044b\u0441\u043e\u0442\u0443 \u0434\u043b\u044f \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u043c\u043e\u0433\u043e \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u043b\u044f   *\/  getContentHeight(v: number, hasImage: boolean = false) {    return Math.ceil(v) - 34 - (hasImage ? 72 : 0);  }  \/**   * \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043f\u043e\u0438\u0441\u043a\u0430 \u043f\u043e \u043f\u043e\u0434\u0441\u0442\u0440\u043e\u043a\u0435   *\/  onSearchHandler(pattern: string) {    this.search.set(pattern);  }  \/**   * \u0421\u0431\u0440\u043e\u0441 \u0441\u043f\u0438\u0441\u043a\u0430 \u0432 \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435   * \u0412\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u043e\u0441\u043b\u0435 \u0432\u044b\u0431\u043e\u0440\u0430 \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439   *\/  private resetList() {    this.groupDynamicItems = [...GROUP_DYNAMIC_ITEMS];    this.groupDynamicItemsConfigMap = { ...GROUP_DYNAMIC_ITEMS_STICKY_MAP };  }  \/** \u041f\u043e\u0442\u043e\u043a \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f *\/  private write() {    const msg = generateMessage(this._nextIndex);    this._nextIndex++;    return of(msg).pipe(      tap(() =&gt; {        const writeIndicator = generateWriteIndicator(this._nextIndex);        this._nextIndex++;        this.groupDynamicItems = [...this.groupDynamicItems, writeIndicator];        this.groupDynamicItemsConfigMap[writeIndicator.id] = {          sticky: 0,          selectable: false,        };        const writeIndicatorShift = generateWriteIndicator(this._nextIndex);        this._nextIndex++;        this.groupDynamicItems = [writeIndicatorShift, ...this.groupDynamicItems];        this.groupDynamicItemsConfigMap[writeIndicatorShift.id] = {          sticky: 0,          selectable: false,        };        this.increaseVersion();      }),      delay(500),      tap(() =&gt; {        const items = [...this.groupDynamicItems];        items.pop();        items.push(msg);        this.groupDynamicItemsConfigMap[msg.id] = {          sticky: 0,          selectable: true,        };        items.shift();        for (let i = 0, l = 1; i &lt; l; i++) {          const msgStart = generateMessage(this._nextIndex);          this._nextIndex++;          this.groupDynamicItemsConfigMap[msgStart.id] = {            sticky: 0,            selectable: true,          };          items.unshift(msgStart);        }        this.groupDynamicItems = items;        this.increaseVersion();      }),    );  }  \/**   * \u0417\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 \u043c\u0435\u0442\u0440\u0438\u043a\u0438 \u0441\u043a\u0440\u043e\u043b\u043b\u0430   *\/  onScrollHandler(e: IScrollEvent &amp; { [x: string]: any; }) {    this._scrollParams.set({      viewportEndY: e.scrollSize + e.size,      scrollWeight: e.scrollWeight,    });  }  \/**   * \u0417\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 \u043c\u0435\u0442\u0440\u0438\u043a\u0438 \u0441\u043a\u0440\u043e\u043b\u043b\u0430   *\/  onScrollEndHandler(e: IScrollEvent &amp; { [x: string]: any; }) {    this._scrollParams.set({      viewportEndY: e.scrollSize + e.size,      scrollWeight: e.scrollWeight,    });  }  \/**   * \u0422\u0440\u044d\u0439\u0441 \u043a\u043b\u0438\u043a\u0430 \u043f\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044e   *\/  onClickHandler(item: IRenderVirtualListItem | undefined) {    if (item) {      console.info(`Click: (ID: ${item.id}) Item ${item.data.name}`);    }  }  \/**   * \u0411\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043a\u0430 \u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0441\u043e\u0431\u044b\u0442\u0438\u044f `keydown` \u043f\u0440\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u0438 `Space`   * \u042d\u0442\u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0435\u0434\u043e\u0442\u0432\u0440\u0430\u0442\u0438\u0442\u044c \u0441\u043d\u044f\u0442\u0438\u0435 \u0432\u044b\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0441 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f.   * \u0422.\u043a. \u0437\u0430 \u0432\u044b\u0431\u043e\u0440 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432 \u0441\u043f\u0438\u0441\u043a\u0435 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0438\u043c\u0435\u043d\u043d\u043e \u043a\u043b\u0430\u0432\u0438\u0448\u0430 `Space`   *\/  onKeyDownHandler(e: KeyboardEvent) {    if (e.key === ' ') {      e.stopImmediatePropagation();    }  }  \/**   * \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0440\u0435\u0436\u0438\u043c\u0430 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f   *\/  onEditItemHandler(e: Event, item: IRenderVirtualListItem | undefined, selected: boolean) {    if (selected) {      e.stopImmediatePropagation();    }    const index = this.groupDynamicItems.findIndex(({ id }) =&gt; id === item?.id);    if (index &gt; -1) {      const items = [...this.groupDynamicItems], item = items[index];      items[index] = { ...item, edited: selected ? !item.edited : false };      this.groupDynamicItems = items;      this.increaseVersion();    }  }  onTAClickHandler(e: Event) {    e.stopImmediatePropagation();  }  \/**   * \u0417\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u043e \u0441\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u043d\u0438\u044e `outside click`   *\/  onOutsideClickHandler(e: Event, item: IRenderVirtualListItem&lt;any&gt; | undefined, selected: boolean) {    const index = this.groupDynamicItems.findIndex(({ id }) =&gt; id === item?.id);    if (index &gt; -1) {      const items = [...this.groupDynamicItems], item = items[index];      items[index] = { ...item, edited: false };      this.groupDynamicItems = items;      this.increaseVersion();    }    this._service.activeTarget = null;  }  \/**   * \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f   *\/  onEditingCloseHandler(data: { target: any; item: IItemData &amp; { id: Id }; }) {    const index = this.groupDynamicItems.findIndex(({ id }) =&gt; id === data.item.id);    if (index &gt; -1) {      const items = [...this.groupDynamicItems], _item = items[index];      items[index] = { ..._item, edited: false, name: data.target.value };      this.groupDynamicItems = items;      this.increaseVersion();    }  }  \/**   * \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0438\u0437 \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430 \u0432 \u0440\u0435\u0436\u0438\u043c \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u043d\u0430\u043e\u043e\u0431\u043e\u0440\u043e\u0442   *\/  onEditedHandler(e: any, item: IRenderVirtualListItem&lt;any&gt; | undefined) {    const index = this.groupDynamicItems.findIndex(({ id }) =&gt; id === item?.id);    if (index &gt; -1) {      const items = [...this.groupDynamicItems], _item = items[index];      items[index] = { ..._item, edited: !_item.edited, name: e.target.value };      this.groupDynamicItems = items;      this.increaseVersion();    }  }  \/**   * \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f   *\/  onDeleteItemHandler(e: Event, item: IRenderVirtualListItem | undefined) {    e.stopImmediatePropagation();    const index = this.groupDynamicItems.findIndex(({ id }) =&gt; id === item?.id);    if (index &gt; -1) {      const items = [...this.groupDynamicItems];      items.splice(index, 1);      this.groupDynamicItems = items;      this.increaseVersion();    }  }  \/**   * \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043a\u043b\u0438\u043a\u0430 \u043f\u043e \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0443 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439   *\/  onRoomClickHandler(item: IRenderVirtualListItem | undefined) {    this.menuOpened.set(false);    if (item) {      this.title.set(item.data['name']);      this.resetList();      this._listContainerRef()?.scrollToEnd('instant');      setTimeout(() =&gt; {        this._listContainerRef()?.scrollToEnd('instant');      }, 150);    }  }  \/**   * \u041e\u0442\u043a\u0440\u044b\u0442\u0438\u0435\/\u0437\u0430\u043a\u0440\u044b\u0442\u0438\u0435 \u0434\u043e\u043a\u0430 \u0441 \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430\u043c\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439   *\/  onOpenMenuHandler() {    this.menuOpened.update(v =&gt; !v);  }<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<\/div>\n<\/details>\n<p><em>\u041f\u043e\u043c\u0438\u043c\u043e \u044d\u0442\u043e\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442 \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0432\u00a0\u0441\u0435\u0431\u044f \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0434\u0438\u0440\u0435\u043a\u0442\u0438\u0432\u044b, \u0441\u0435\u0440\u0432\u0438\u0441\u044b \u0438 \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b.<\/em><\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/6a4\/c8c\/d0b\/6a4c8cd0bd3d8a410303c5495e4c96bc.gif\" alt=\"Preview \u0432\u044c\u044e\u043f\u043e\u0440\u0442\u0430\" title=\"Preview \u0432\u044c\u044e\u043f\u043e\u0440\u0442\u0430\" width=\"604\" height=\"472\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/6a4\/c8c\/d0b\/6a4c8cd0bd3d8a410303c5495e4c96bc.gif 780w,&#10;       https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/6a4\/c8c\/d0b\/6a4c8cd0bd3d8a410303c5495e4c96bc.gif 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>Preview \u0432\u044c\u044e\u043f\u043e\u0440\u0442\u0430<\/figcaption><\/div>\n<\/figure>\n<p>\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0441\u043c. \u043f\u043e\u00a0\u0441\u0441\u044b\u043b\u043a\u0435 <a href=\"https:\/\/github.com\/DjonnyX\/ng-virtual-list-demo\" rel=\"noopener noreferrer nofollow\">ng\u2011virtual\u2011list\u2011demo<\/a><\/p>\n<p><a href=\"https:\/\/ng-virtual-list-chat-demo.eugene-grebennikov.pro\/\" rel=\"noopener noreferrer nofollow\">Live demo<\/a> \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/p>\n<p>\u041f\u043e\u043d\u0440\u0430\u0432\u0438\u043b\u0441\u044f \u043f\u0440\u043e\u0435\u043a\u0442 \u0438 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442? \u0422\u043e\u0433\u0434\u0430 \u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u2b50 <a href=\"https:\/\/github.com\/DjonnyX\/ng-virtual-list\" rel=\"noopener noreferrer nofollow\">ng\u2011virtual\u2011list<\/a> \u0438 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u0431\u0443\u0434\u0435\u0442 \u0434\u0430\u043b\u044c\u0448\u0435 \u0440\u0430\u0437\u0432\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u0438 \u0443\u043b\u0443\u0447\u0448\u0430\u0442\u044c\u0441\u044f!<\/p>\n<p>\u0410\u00a0\u0442\u0430\u043a\u0436\u0435, \u0435\u0441\u043b\u0438 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0435\u043d \u0434\u0430\u043d\u043d\u044b\u0439 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u043f\u0438\u0441\u043a\u043e\u0432 \u043f\u043e\u0434 <strong>React<\/strong>, \u0442\u043e\u0436\u0435 \u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u2b50 <a href=\"https:\/\/github.com\/DjonnyX\/rcx-virtual-list\" rel=\"noopener noreferrer nofollow\">rcx\u2011virtual\u2011list<\/a> \u0438 \u043f\u0440\u043e\u0435\u043a\u0442 \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d \u0441\u00a0\u043f\u043e\u043b\u043d\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c\u044e \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b\u0430!<\/p>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/947256\/\">https:\/\/habr.com\/ru\/articles\/947256\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u041f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f \u0431\u044b\u043b\u0430 \u043f\u043e\u0441\u0432\u044f\u0449\u0435\u043d\u0430 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0443 ng\u2011virtual\u2011list. \u0421\u00a0\u0442\u0435\u0445 \u043f\u043e\u0440 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u043e\u0431\u0440\u0435\u043b \u0431\u043e\u0433\u0430\u0442\u044b\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b, \u0432\u043d\u0435\u0441\u0435\u043d \u0440\u044f\u0434 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0445 \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u0439 \u0432\u00a0\u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u044b \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u0442\u0440\u0435\u043a\u0438\u043d\u0433\u0430, \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0430 \u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0438 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c. \u0422\u0430\u043a\u0436\u0435\u00a0\u0431\u044b\u043b \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d \u043f\u043e\u0440\u0442 \u043d\u0430 React. \u041a\u043e\u043c\u0443 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b \u0442\u0435\u0441\u0442\u044b, \u0431\u0435\u043d\u0447\u043c\u0430\u0440\u043a\u0438 \u0438 \u0446\u0438\u0444\u0440\u044b, \u0447\u0435\u043c \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442\u0441\u044f \u0434\u0430\u043d\u043d\u044b\u0439 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u043e\u0442\u00a0\u0430\u043d\u0430\u043b\u043e\u0433\u043e\u0432 \u0438 \u043a\u043e\u0440\u043e\u0431\u043e\u0447\u043d\u044b\u0445 \u0440\u0435\u0448\u0435\u043d\u0438\u0439 \u0434\u043b\u044f Angular CDKVirtualFor, \u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u044b \u043a\u00a0\u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0435.\u0425\u043e\u0447\u0435\u0442\u0441\u044f \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e ng\u2011virtual\u2011list \u043d\u0435\u00a0\u043f\u0440\u043e\u0441\u0442\u043e \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a, \u043e\u043d \u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e \u043c\u043e\u0436\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043a\u0430\u043a\u00a0\u0432\u0438\u0440\u0442\u0443\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 select \u0438 multi\u2011select; \u0443\u043c\u0435\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441\u00a0\u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u043c\u0438 \u0441\u043f\u0438\u0441\u043a\u0430\u043c\u0438 \u0438 \u0432\u00a0\u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c collapsableGroups \u0438 \u0440\u0430\u0431\u043e\u0442\u0430 \u0432\u00a0\u043c\u043d\u043e\u0433\u043e\u043f\u043e\u0442\u043e\u0447\u043d\u043e\u043c \u0440\u0435\u0436\u0438\u043c\u0435.\u041f\u0440\u043e\u0435\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u0441\u0442\u0430\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u043e\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u0442\u044c \u0432\u0441\u044e \u0441\u0438\u043b\u0443 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u043f\u0438\u0441\u043a\u043e\u0432 \u043d\u0430\u00a0\u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435.\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c\u0441\u044f \u0441\u00a0\u0443\u0441\u043b\u043e\u0432\u0438\u044f\u043c\u0438 \u0434\u043b\u044f\u00a0\u043f\u0440\u043e\u0435\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u044c\u044e\u043f\u043e\u0440\u0442\u0430:\u0421\u043f\u0438\u0441\u043e\u043a \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439. \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0443\u0442 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c\u0441\u044f \u0432\u00a0\u043d\u0430\u0447\u0430\u043b\u0435 \u0438 \u043a\u043e\u043d\u0446\u0435 \u0441\u043f\u0438\u0441\u043a\u0430; \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439.\u041f\u043e\u0438\u0441\u043a \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u043e\u00a0\u043f\u043e\u0434\u0441\u0442\u0440\u043e\u043a\u0435.\u0411\u043e\u043a\u043e\u0432\u043e\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u043e\u0432 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u0432\u00a0\u043b\u0435\u0432\u043e\u043c \u0434\u043e\u043a\u0435. \u0414\u043e\u043a \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u043e\u00a0\u043a\u043b\u0438\u043a\u0443 \u043d\u0430\u00a0\u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e \u043a\u043d\u043e\u043f\u043a\u0443. \u041f\u0440\u0438\u00a0\u0432\u044b\u0431\u043e\u0440\u0435 \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430, \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0438 \u0434\u043e\u043a \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u0442\u0441\u044f.\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u0432\u044c\u044e\u043f\u043e\u0440\u0442\u0430 \u0432\u00a0\u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0438 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u0438\u0441\u043a\u043e\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0438 \u043a\u043d\u043e\u043f\u043a\u0430 \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u044f \u0434\u043e\u043a\u0430 \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u043e\u0432.\u041c\u0430\u043a\u0435\u0442 \u0431\u0443\u0434\u0443\u0449\u0435\u0433\u043e \u0432\u044c\u044e\u043f\u043e\u0440\u0442\u0430 \u0434\u043b\u044f\u00a0\u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440\u0430\u0411\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Angular 19.x \u0438 ng\u2011virtual\u2011list@19\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f\u0412\u00a0\u0434\u0430\u043d\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043d\u0435\u00a0\u0431\u0443\u0434\u0443 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u0432\u0435\u0441\u044c \u043a\u043e\u0434 \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u0442. \u043a. \u0412\u044b \u0441\u043c\u043e\u0436\u0435\u0442\u0435 \u0435\u0433\u043e \u043d\u0430\u0439\u0442\u0438 \u043d\u0438\u0436\u0435 \u043f\u043e\u00a0\u0441\u0441\u044b\u043b\u043a\u0435. \u0421\u043a\u043e\u043d\u0446\u0435\u043d\u0442\u0440\u0438\u0440\u0443\u044e\u00a0\u043b\u0438\u0448\u044c \u043d\u0430\u00a0\u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0445 \u043c\u043e\u043c\u0435\u043d\u0442\u0430\u0445.\u0414\u0435\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0448\u0430\u0431\u043b\u043e\u043d\u0430:\u0421\u043a\u0440\u044b\u0442\u044b\u0439 \u0442\u0435\u043a\u0441\u0442&lt;div class=&#187;container&#187;&gt;    &lt;!&#8212; \u041f\u0430\u043d\u0435\u043b\u044c \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432 &#8212;&gt;    &lt;div class=&#187;toolbar&#187;&gt;      &lt;div&gt;        &lt;!&#8212; \u041a\u043d\u043e\u043f\u043a\u0430 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u044e\u0449\u0430\u044f \u0434\u043e\u043a \u0441 \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430\u043c\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 &#8212;&gt;        &lt;app-menu-button (click)=&#187;onOpenMenuHandler()&#187; [opened]=&#187;menuOpened()&#187; \/&gt;      &lt;\/div&gt;      &lt;!&#8212; \u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 &#8212;&gt;      &lt;div class=&#187;title&#187;&gt;{{title()}}&lt;\/div&gt;      &lt;!&#8212; \u042d\u043b\u0435\u043c\u0435\u043d\u0442 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043f\u043e \u043f\u043e\u0434\u0441\u0442\u0440\u043e\u043a\u0435 &#8212;&gt;      &lt;app-search (search)=&#187;onSearchHandler($event)&#187; \/&gt;    &lt;\/div&gt;    &lt;div class=&#187;list-container&#187;&gt;      @let doc = dockMode();      &lt;app-drawer [dock]=&#187;doc&#187; [dockLeftSize]=&#187;240&#8243;&gt;        &lt;!&#8212; \u0414\u043e\u043a \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 &#8212;&gt;        &lt;dock-left&gt;          &lt;div class=&#187;list-rooms__container&#187;&gt;            &lt;!&#8212; \u0412\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u043e\u0432 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 &#8212;&gt;            &lt;ng-virtual-list class=&#187;list rooms&#187; [items]=&#187;items&#187; [itemRenderer]=&#187;itemRenderer&#187; [trackBy]=&#187;&#8216;id'&#187;              [itemSize]=&#187;40&#8243; [dynamicSize]=&#187;true&#187; [bufferSize]=&#187;60&#8243;              (onItemClick)=&#187;onRoomClickHandler($event)&#187;&gt;&lt;\/ng-virtual-list&gt;          &lt;\/div&gt;        &lt;\/dock-left&gt;        &lt;!&#8212; \u0412\u044c\u044e\u043f\u043e\u0440\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 &#8212;&gt;        &lt;div class=&#187;list-wrapper&#187;&gt;          &lt;!&#8212; \u0412\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 &#8212;&gt;          &lt;ng-virtual-list #dynamicList class=&#187;list&#187; [items]=&#187;groupDynamicItems&#187; [itemRenderer]=&#187;groupItemRenderer&#187;            [trackBy]=&#187;&#8216;id'&#187; [itemSize]=&#187;40&#8243; [bufferSize]=&#187;30&#8243; [bufferSize]=&#187;120&#8243;            [itemConfigMap]=&#187;groupDynamicItemsConfigMap&#187; [dynamicSize]=&#187;true&#187; [snap]=&#187;true&#187;            (onScroll)=&#187;onScrollHandler($event)&#187; snappingMethod=&#187;advanced&#187; methodForSelecting=&#187;multi-select&#187;            (onScrollEnd)=&#187;onScrollEndHandler($event)&#187; [enabledBufferOptimization]=&#187;false&#187;            (onItemClick)=&#187;onClickHandler($event)&#187;&gt;&lt;\/ng-virtual-list&gt;        &lt;\/div&gt;      &lt;\/app-drawer&gt;    &lt;\/div&gt;  &lt;\/div&gt;&lt;!&#8212; \u0428\u0430\u0431\u043b\u043e\u043d \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f &#8212;&gt;  &lt;ng-template #groupItemRenderer let-data=&#187;data&#187; let-measures=&#187;measures&#187; let-config=&#187;config&#187;&gt;    @if (data) {      @switch (data.type) {        &lt;!&#8212; \u0418\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440 \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f &#8212;&gt;        @case (&#171;write-indicator&#187;) {          &lt;div class=&#187;list__windicator-container&#187;&gt;            &lt;!&#8212; \u0422\u0443\u0442 \u0440\u0430\u0441\u043f\u043e\u043b\u0430\u0433\u0430\u0435\u0442\u0441\u044f \u043f\u0438\u043a\u0442\u043e\u0433\u0440\u0430\u043c\u043c\u0430 \u0434\u043b\u044f \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440\u0430 \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f &#8212;&gt;          &lt;\/div&gt;        }        &lt;!&#8212; \u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u0433\u0440\u0443\u043f\u043f\u044b \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 &#8212;&gt;        @case (&#171;group-header&#187;) {          &lt;div class=&#187;list__group-container&#187; [ngClass]=&#187;{&#8216;snapped&#8217;: config.snapped, &#8216;snapped-out&#8217;: config.snappedOut}&#187;&gt;            &lt;span&gt;{{data.name}}&lt;\/span&gt;          &lt;\/div&gt;        }        &lt;!&#8212; C\u043e\u0431\u0449\u0435\u043d\u0438\u0435 &#8212;&gt;        @default {          @let isIn = data.incomType === &#8216;in&#8217;;          @let isOut = data.incomType === &#8216;out&#8217;;          @let class = {&#8216;in&#8217;: isIn, &#8216;out&#8217;: isOut, &#8216;edited&#8217;: data.edited, &#8216;selected&#8217;: config.selected, focused: config.focus};          &lt;div class=&#187;list__container&#187; [ngClass]=&#187;class&#187; [longPress]=&#187;1000&#8243;&gt;            &lt;div class=&#187;message__container&#187; [ngClass]=&#187;class&#187;&gt;              &lt;div class=&#187;message&#187; [ngClass]=&#187;class&#187;&gt;                @if (data.edited) {                  &lt;!&#8212; \u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0441 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u043c &#8212;&gt;                  @if (data.image) {                    &lt;div class=&#187;complex-message&#187;&gt;                      &lt;img [src]=&#187;data.image&#187; \/&gt;                      &lt;textarea clickOutside [clickOutsideItem]=&#187;data&#187; (keydown)=&#187;onKeyDownHandler($event)&#187;                    (onClickOutside)=&#187;onOutsideClickHandler($event, data, config.selected)&#187;                    [ngStyle]=&#187;{height: getContentHeight(measures.height, true) + &#8216;px&#8217;}&#187; [value]=&#187;data.name&#187;                    (click)=&#187;onTAClickHandler($event)&#187; (onOutsideClose)=&#187;onEditingCloseHandler($event)&#187;                    (change)=&#187;onEditedHandler($event, data)&#187;&gt;&lt;\/textarea&gt;                    &lt;\/div&gt;                  } @else {                    &lt;!&#8212; \u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0431\u0435\u0437 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f &#8212;&gt;                    &lt;textarea clickOutside [clickOutsideItem]=&#187;data&#187; (keydown)=&#187;onKeyDownHandler($event)&#187;                    (onClickOutside)=&#187;onOutsideClickHandler($event, data, config.selected)&#187;                    [ngStyle]=&#187;{height: getContentHeight(measures.height) + &#8216;px&#8217;}&#187; [value]=&#187;data.name&#187;                    (click)=&#187;onTAClickHandler($event)&#187; (onOutsideClose)=&#187;onEditingCloseHandler($event)&#187;                    (change)=&#187;onEditedHandler($event, data)&#187;&gt;&lt;\/textarea&gt;                  }                } @else {                  &lt;!&#8212; \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0441 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u043c &#8212;&gt;                  @if (data.image) {                    &lt;div class=&#187;complex-message&#187;&gt;                      &lt;img [src]=&#187;data.image&#187; \/&gt;                      &lt;span searchHighlight substringClass=&#187;search-substring&#187; [text]=&#187;data.name&#187; [searchedWords]=&#187;searchedWords()&#187;                        (click)=&#187;onEditItemHandler($event, data, config.selected)&#187;&gt;{{data.name}}&lt;\/span&gt;                    &lt;\/div&gt;                  } @else {                    &lt;!&#8212; \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0431\u0435\u0437 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f &#8212;&gt;                    &lt;span searchHighlight substringClass=&#187;search-substring&#187; [text]=&#187;data.name&#187; [searchedWords]=&#187;searchedWords()&#187;                      (click)=&#187;onEditItemHandler($event, data, config.selected)&#187;&gt;{{data.name}}&lt;\/span&gt;                  }                }              &lt;\/div&gt;                &lt;!&#8212; \u041f\u0440\u0438 \u0432\u044b\u0431\u043e\u0440\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0435\u0433\u043e \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f &#8212;&gt;                @if (config.selected) {                  &lt;div class=&#187;flex&#187;&gt;&lt;\/div&gt;                  &lt;div class=&#187;message__controls&#187;&gt;                    &lt;div class=&#187;ctrl__button del-icon&#187; (click)=&#187;onDeleteItemHandler($event, data)&#187;&gt;                      &lt;!&#8212; \u0418\u043a\u043e\u043d\u043a\u0430 \u0434\u043b\u044f \u043a\u043d\u043e\u043f\u043a\u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f &#8212;&gt;                    &lt;\/div&gt;                  &lt;\/div&gt;                }              &lt;\/div&gt;            &lt;\/div&gt;          }        }      }  &lt;\/ng-template&gt;  &lt;!&#8212; \u0428\u0430\u0431\u043b\u043e\u043d \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u0441\u043f\u0438\u0441\u043a\u0430 \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 &#8212;&gt;  &lt;ng-template #itemRenderer let-data=&#187;data&#187; let-config=&#187;config&#187;&gt;    @if (data) {      @switch (data.type) {        @case (&#171;group-header&#187;) {          &lt;div class=&#187;list__item-container&#187;&gt;            &lt;div class=&#187;content&#187;&gt;              &lt;span&gt;{{data.name}}&lt;\/span&gt;            &lt;\/div&gt;          &lt;\/div&gt;        }        @default {          &lt;div class=&#187;list__item-container&#187;&gt;            &lt;div class=&#187;message&#187;&gt;              &lt;span&gt;                &lt;!&#8212; \u0422\u0443\u0442 \u0440\u0430\u0441\u043f\u043e\u043b\u0430\u0433\u0430\u0435\u0442\u0441\u044f \u043f\u0438\u043a\u0442\u043e\u0433\u0440\u0430\u043c\u043c\u0430 \u0434\u043b\u044f \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430 &#8212;&gt;              &lt;\/span&gt;              &lt;span&gt;{{data.name}}&lt;\/span&gt;            &lt;\/div&gt;          &lt;\/div&gt;        }      }    }  &lt;\/ng-template&gt;&lt;\/div&gt;\u0414\u0435\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 App:\u0421\u043a\u0440\u044b\u0442\u044b\u0439 \u0442\u0435\u043a\u0441\u0442constructor(private _service: ClickOutsideService) {    const list = this._listContainerRef;    this.dockMode = computed(() =&gt; {      const menuOpened = this.menuOpened();      return menuOpened ? DockMode.LEFT : DockMode.NONE;    });    const $virtualList = toObservable(list).pipe(      filter(list =&gt; !!list),      switchMap(list =&gt; combineLatest([of(list), list?.$initialized])),      filter(([, init]) =&gt; !!init),      map(([list]) =&gt; list),    );    \/\/ \u0412 \u043c\u043e\u043c\u0435\u043d\u0442 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u043f\u0438\u0441\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439    \/\/ \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u0435\u0441\u043b\u0438 \u043d\u0443\u0436\u043d\u043e \u043f\u0440\u043e\u0441\u043a\u0440\u043e\u043b\u043b\u0438\u0442\u044c \u0434\u043e \u043a\u043e\u043d\u0446\u0430 \u0441\u043f\u0438\u0441\u043a\u0430, \u0442\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0441\u043a\u0440\u043e\u043b\u043b    combineLatest([this.$version, $virtualList]).pipe(      map(([version, list]) =&gt; ({ version, list })),      filter(({ list }) =&gt; !!list),      debounceTime(50),      tap(({ version, list }) =&gt; {        if (version === 0) {          list!.scrollToEnd(&#8216;instant&#8217;);        }        if (this._$isEndOfListPosition.getValue()) {          list!.scrollToEnd(&#8216;instant&#8217;);        }      }),    ).subscribe();    \/\/ \u041f\u043e\u0438\u0441\u043a \u0438 \u0441\u043a\u0440\u043e\u043b\u043b \u0434\u043e \u0438\u0441\u043a\u043e\u043c\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432\u043e \u0432\u044c\u044e\u043f\u043e\u0440\u0442\u0435    combineLatest([$virtualList, toObservable(this.search)]).pipe(      map(([list, search]) =&gt; ({ list, search })),      filter(({ list }) =&gt; !!list),      debounceTime(0),      tap(({ list, search }) =&gt; {        this.searchedWords.set(search.split(&#8216; &#8216;));        for (let i = 0, l = this.groupDynamicItems.length; i &lt; l; i++) {          const item =&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-475163","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/475163","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=475163"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/475163\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=475163"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=475163"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=475163"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}