@rt-tools/ui-kit-v2 0.29.1 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/fesm2022/rt-tools-ui-kit-v2-autocomplete.mjs +23 -29
  2. package/fesm2022/rt-tools-ui-kit-v2-autocomplete.mjs.map +1 -1
  3. package/fesm2022/rt-tools-ui-kit-v2-calendar.mjs +2 -2
  4. package/fesm2022/rt-tools-ui-kit-v2-calendar.mjs.map +1 -1
  5. package/fesm2022/rt-tools-ui-kit-v2-core.mjs +223 -3
  6. package/fesm2022/rt-tools-ui-kit-v2-core.mjs.map +1 -1
  7. package/fesm2022/rt-tools-ui-kit-v2-data-list.mjs +2 -2
  8. package/fesm2022/rt-tools-ui-kit-v2-data-list.mjs.map +1 -1
  9. package/fesm2022/rt-tools-ui-kit-v2-draggable-tree.mjs +1 -2
  10. package/fesm2022/rt-tools-ui-kit-v2-draggable-tree.mjs.map +1 -1
  11. package/fesm2022/rt-tools-ui-kit-v2-dynamic-list.mjs +2 -2
  12. package/fesm2022/rt-tools-ui-kit-v2-dynamic-list.mjs.map +1 -1
  13. package/fesm2022/rt-tools-ui-kit-v2-dynamic-selector.mjs +277 -904
  14. package/fesm2022/rt-tools-ui-kit-v2-dynamic-selector.mjs.map +1 -1
  15. package/fesm2022/rt-tools-ui-kit-v2-filter-control.mjs +2 -2
  16. package/fesm2022/rt-tools-ui-kit-v2-filter-control.mjs.map +1 -1
  17. package/fesm2022/rt-tools-ui-kit-v2-hybrid-tree.mjs +1 -1
  18. package/fesm2022/rt-tools-ui-kit-v2-hybrid-tree.mjs.map +1 -1
  19. package/fesm2022/rt-tools-ui-kit-v2-multiselect.mjs +35 -48
  20. package/fesm2022/rt-tools-ui-kit-v2-multiselect.mjs.map +1 -1
  21. package/fesm2022/rt-tools-ui-kit-v2-page-header.mjs +2 -2
  22. package/fesm2022/rt-tools-ui-kit-v2-pdf-viewer.mjs +2 -2
  23. package/fesm2022/rt-tools-ui-kit-v2-pdf-viewer.mjs.map +1 -1
  24. package/fesm2022/rt-tools-ui-kit-v2-rich-editor.mjs +4 -4
  25. package/fesm2022/rt-tools-ui-kit-v2-rich-editor.mjs.map +1 -1
  26. package/fesm2022/rt-tools-ui-kit-v2-select.mjs +37 -181
  27. package/fesm2022/rt-tools-ui-kit-v2-select.mjs.map +1 -1
  28. package/fesm2022/rt-tools-ui-kit-v2-side-menu.mjs +5 -36
  29. package/fesm2022/rt-tools-ui-kit-v2-side-menu.mjs.map +1 -1
  30. package/fesm2022/rt-tools-ui-kit-v2-tag.mjs +2 -3
  31. package/fesm2022/rt-tools-ui-kit-v2-tag.mjs.map +1 -1
  32. package/fesm2022/rt-tools-ui-kit-v2-toolbar.mjs +2 -2
  33. package/fesm2022/rt-tools-ui-kit-v2-toolbar.mjs.map +1 -1
  34. package/fesm2022/rt-tools-ui-kit-v2-tree-selector.mjs +8 -13
  35. package/fesm2022/rt-tools-ui-kit-v2-tree-selector.mjs.map +1 -1
  36. package/fesm2022/rt-tools-ui-kit-v2-tree.mjs +12 -7
  37. package/fesm2022/rt-tools-ui-kit-v2-tree.mjs.map +1 -1
  38. package/fesm2022/rt-tools-ui-kit-v2-workspace.mjs +2 -2
  39. package/fesm2022/rt-tools-ui-kit-v2-workspace.mjs.map +1 -1
  40. package/package.json +1 -1
  41. package/src/lib/components/dynamic-selector/stories/component/test-dynamic-selector-popup-frame.component.scss +6 -0
  42. package/src/lib/components/page-header/rt-page-header.component.scss +4 -2
  43. package/src/lib/components/tree/CONTEXT.md +1 -1
  44. package/src/rich-editor/lib/components/ai-chat/CONTEXT.md +1 -1
  45. package/src/styles/_breakpoints.scss +37 -4
  46. package/src/styles/_index.scss +0 -4
  47. package/styles/tokens.css +0 -8
  48. package/types/rt-tools-ui-kit-v2-autocomplete.d.ts +4 -3
  49. package/types/rt-tools-ui-kit-v2-core.d.ts +155 -1
  50. package/types/rt-tools-ui-kit-v2-dynamic-selector.d.ts +51 -269
  51. package/types/rt-tools-ui-kit-v2-multiselect.d.ts +4 -3
  52. package/types/rt-tools-ui-kit-v2-select.d.ts +12 -74
  53. package/types/rt-tools-ui-kit-v2-side-menu.d.ts +5 -20
  54. package/types/rt-tools-ui-kit-v2-tag.d.ts +2 -3
  55. package/types/rt-tools-ui-kit-v2-tree-selector.d.ts +0 -2
  56. package/types/rt-tools-ui-kit-v2-tree.d.ts +7 -9
  57. package/src/styles/_place.scss +0 -7
@@ -1 +1 @@
1
- {"version":3,"file":"rt-tools-ui-kit-v2-pdf-viewer.mjs","names":[],"sources":["../../../projects/ui-kit-v2/src/lib/components/pdf-viewer/rt-pdf-viewer.logic.ts","../../../projects/ui-kit-v2/src/lib/components/pdf-viewer/rt-pdf-viewer.output.ts","../../../projects/ui-kit-v2/src/lib/components/pdf-viewer/rt-pdf-viewer.tokens.ts","../../../projects/ui-kit-v2/src/lib/components/pdf-viewer/rt-pdf-viewer.component.ts","../../../projects/ui-kit-v2/src/lib/components/pdf-viewer/rt-pdf-viewer.component.html"],"sourcesContent":["import { IRtPdfViewer } from './rt-pdf-viewer.model';\n\n/**\n * Чистая логика просмотрщика документа: шаги масштаба, начальный масштаб, номер видимой страницы и\n * то, какие страницы стоит держать нарисованными.\n */\n\n/** Ступени масштаба в процентах. Кнопки «−» и «+» ходят только по ним. */\nexport const RT_PDF_ZOOM_STEPS: readonly number[] = [50, 75, 100, 125, 150, 200, 300];\n\nexport const RT_PDF_ZOOM_MIN: number = RT_PDF_ZOOM_STEPS[0];\n\nexport const RT_PDF_ZOOM_MAX: number = RT_PDF_ZOOM_STEPS[RT_PDF_ZOOM_STEPS.length - 1];\n\n/**\n * Сколько CSS-пикселей в пункте документа. Пункт — 1/72 дюйма, CSS-пиксель — 1/96: при 100%\n * страница A4 на экране той же ширины, что на бумаге.\n */\nexport const RT_PDF_CSS_UNITS: number = 96 / 72;\n\n/** Ширина окна, до которой документ открывается «по ширине» при любом размере страницы. */\nexport const RT_PDF_NARROW_WIDTH: number = 768;\n\n/** Запас на дробную часть: 99.999% от деления ширин — это всё ещё 100%. */\nconst EPSILON: number = 0.01;\n\n/**\n * Следующая ступень масштаба. Шаг идёт от того, что на экране сейчас: из «по ширине» в 83% кнопка\n * «+» ведёт на 100%, а «−» — на 75%, а не на ступень, соседнюю с последней выбранной руками.\n */\nexport function nextZoomStep(current: number, direction: 1 | -1): number {\n if (direction > 0) {\n return RT_PDF_ZOOM_STEPS.find((step: number): boolean => step > current + EPSILON) ?? RT_PDF_ZOOM_MAX;\n }\n\n return [...RT_PDF_ZOOM_STEPS].reverse().find((step: number): boolean => step < current - EPSILON) ?? RT_PDF_ZOOM_MIN;\n}\n\n/** Есть ли куда увеличивать: на верхней границе кнопка «+» выключена. */\nexport function canZoomIn(current: number): boolean {\n return current < RT_PDF_ZOOM_MAX - EPSILON;\n}\n\n/** Есть ли куда уменьшать: на нижней границе кнопка «−» выключена. */\nexport function canZoomOut(current: number): boolean {\n return current > RT_PDF_ZOOM_MIN + EPSILON;\n}\n\n/**\n * Масштаб «по ширине» в процентах: самая широкая страница занимает всё место столбца.\n *\n * Границами ступеней он не зажат. На телефоне страница A4 «по ширине» — около 45%, и зажать её до\n * 50% значило бы вернуть горизонтальную прокрутку, ради которой режим и существует.\n */\nexport function fitWidthPercent(availableWidth: number, pages: readonly IRtPdfViewer.PageSize[]): number {\n const widest: number = Math.max(0, ...pages.map((page: IRtPdfViewer.PageSize): number => page.width));\n if (widest <= 0 || availableWidth <= 0) {\n return 100;\n }\n\n return (availableWidth / (widest * RT_PDF_CSS_UNITS)) * 100;\n}\n\n/**\n * С какого масштаба открыть документ. Узкий экран и страница, которая в 100% не влезает, —\n * «по ширине»: иначе человек сначала видит обрезанный край листа. Остальные — в 100%.\n */\nexport function initialZoom(viewportWidth: number, availableWidth: number, pages: readonly IRtPdfViewer.PageSize[]): IRtPdfViewer.Zoom {\n const widest: number = Math.max(0, ...pages.map((page: IRtPdfViewer.PageSize): number => page.width));\n if (viewportWidth <= RT_PDF_NARROW_WIDTH || widest * RT_PDF_CSS_UNITS > availableWidth) {\n return { kind: 'fit' };\n }\n\n return { kind: 'percent', percent: 100 };\n}\n\n/** Места под страницы на экране: от размера в пунктах и масштаба, в целых пикселях. */\nexport function pageBoxes(pages: readonly IRtPdfViewer.PageSize[], percent: number): IRtPdfViewer.PageBox[] {\n const scale: number = (percent / 100) * RT_PDF_CSS_UNITS;\n\n return pages.map((page: IRtPdfViewer.PageSize, index: number): IRtPdfViewer.PageBox => ({\n index,\n width: Math.floor(page.width * scale),\n height: Math.floor(page.height * scale),\n }));\n}\n\n/**\n * Номер страницы, которая видна сейчас, — с нуля. Это та, что занимает больше всего высоты окна;\n * при прокрутке до конца — последняя, даже если она короче соседки: иначе счётчик никогда не\n * показал бы «M / M» у документа с маленькой последней страницей. Документ, который виден целиком\n * без прокрутки, стоит на первой: до конца его никто не прокручивал.\n */\nexport function visiblePageIndex(tops: readonly number[], heights: readonly number[], scrollTop: number, viewportHeight: number): number {\n const total: number = Math.min(tops.length, heights.length);\n if (total === 0) {\n return 0;\n }\n\n const contentBottom: number = tops[total - 1] + heights[total - 1];\n if (scrollTop > 0 && scrollTop + viewportHeight >= contentBottom - 1) {\n return total - 1;\n }\n\n let best: number = 0;\n let bestArea: number = -1;\n for (let index: number = 0; index < total; index++) {\n const visible: number = Math.min(tops[index] + heights[index], scrollTop + viewportHeight) - Math.max(tops[index], scrollTop);\n if (visible > bestArea) {\n best = index;\n bestArea = visible;\n }\n }\n\n return best;\n}\n\n/**\n * Страницы, которые стоит нарисовать: видимые и по `overscan` соседних с каждой стороны. Документ\n * в сотню страниц разом не рисуется — каждая страница это холст в мегабайты памяти.\n */\nexport function pagesToRender(\n tops: readonly number[],\n heights: readonly number[],\n scrollTop: number,\n viewportHeight: number,\n overscan: number = 1\n): number[] {\n const total: number = Math.min(tops.length, heights.length);\n const visible: number[] = [];\n for (let index: number = 0; index < total; index++) {\n if (tops[index] < scrollTop + viewportHeight && tops[index] + heights[index] > scrollTop) {\n visible.push(index);\n }\n }\n if (visible.length === 0) {\n return [];\n }\n\n const first: number = Math.max(0, visible[0] - overscan);\n const last: number = Math.min(total - 1, visible[visible.length - 1] + overscan);\n\n return Array.from({ length: last - first + 1 }, (_: unknown, offset: number): number => first + offset);\n}\n\n/**\n * Куда прокрутить после смены масштаба, чтобы на экране осталось то же место той же страницы.\n * Без этого увеличение уводило бы человека на несколько страниц вниз от того, что он читал.\n */\nexport function anchoredScrollTop(oldTop: number, oldHeight: number, oldScrollTop: number, newTop: number, newHeight: number): number {\n if (oldHeight <= 0) {\n return newTop;\n }\n const ratio: number = (oldScrollTop - oldTop) / oldHeight;\n\n return Math.max(0, newTop + ratio * newHeight);\n}\n\n/** Подпись масштаба для плашки в процентах: «83%», а не «83.47%». */\nexport function zoomPercentLabel(percent: number): string {\n return `${Math.round(percent)}%`;\n}\n","import { IRtPdfViewer } from './rt-pdf-viewer.model';\n\n/**\n * Печать и скачивание документа — то, что уходит за пределы окна просмотра.\n */\n\n/** Блок печатного листа. Лист лежит в конце `<body>`, вне просмотрщика, и класс у него свой. */\nexport const RT_PDF_PRINT_BLOCK: string = 'rt-pdf-viewer-print';\n\n/** Признак на корне страницы, пока идёт печать: под ним прячется всё, кроме листа. */\nexport const RT_PDF_PRINTING_FLAG: string = 'rt-pdf-viewer-printing';\n\n/** Плотность печати: 150 точек на дюйм — текст чёткий, а лист в сотню страниц не выедает память. */\nconst PRINT_DPI: number = 150;\n\n/** Байты документа в том виде, в каком их принимает `Blob`. */\nfunction blobOf(bytes: Uint8Array | ArrayBuffer): Blob {\n const copy: Uint8Array<ArrayBuffer> = bytes instanceof ArrayBuffer ? new Uint8Array(bytes.slice(0)) : new Uint8Array(bytes);\n\n return new Blob([copy], { type: 'application/pdf' });\n}\n\n/**\n * Сохраняет файл под его именем. Байты уходят ссылкой на `Blob`: так браузер берёт имя из\n * `download`. Адрес без байтов отдаётся ссылкой как есть — чужой домен имя может и не принять, но\n * файл человек получит.\n */\nexport function downloadPdf(\n doc: Document,\n win: Window & typeof globalThis,\n file: Uint8Array | ArrayBuffer | string,\n fileName: string\n): void {\n const anchor: HTMLAnchorElement = doc.createElement('a');\n const ownUrl: string | null = typeof file === 'string' ? null : win.URL.createObjectURL(blobOf(file));\n\n anchor.href = ownUrl ?? (typeof file === 'string' ? file : '');\n anchor.download = fileName;\n anchor.rel = 'noopener';\n anchor.style.display = 'none';\n doc.body.append(anchor);\n anchor.click();\n anchor.remove();\n\n if (ownUrl) {\n // Отзыв сразу после щелчка обрывает скачивание в части браузеров: ссылка живёт ещё ход.\n win.setTimeout((): void => win.URL.revokeObjectURL(ownUrl), 0);\n }\n}\n\nfunction canvasBlob(canvas: HTMLCanvasElement): Promise<Blob> {\n return new Promise<Blob>((resolve: (blob: Blob) => void, reject: (reason: Error) => void): void => {\n canvas.toBlob((blob: Blob | null): void => (blob ? resolve(blob) : reject(new Error('canvas is empty'))), 'image/png');\n });\n}\n\n/**\n * Готовит печатный лист: каждая страница рисуется в картинку и встаёт в разметку в конце `<body>`.\n *\n * Лист собирается из картинок, а не из `<iframe>` с самим файлом: встроенного показа PDF нет у\n * мобильных браузеров, а `print()` у кадра с PDF в части настольных печатает пустую страницу или\n * только первую. Картинки печатает любой браузер.\n */\nexport async function preparePrintSheet(\n pdf: IRtPdfViewer.PdfDocument,\n doc: Document,\n win: Window & typeof globalThis\n): Promise<IRtPdfViewer.PrintSheet> {\n const sheet: HTMLDivElement = doc.createElement('div');\n const urls: string[] = [];\n const images: HTMLImageElement[] = [];\n sheet.classList.add(RT_PDF_PRINT_BLOCK);\n\n const dispose: () => void = (): void => {\n doc.documentElement.classList.remove(RT_PDF_PRINTING_FLAG);\n sheet.remove();\n for (const url of urls) {\n win.URL.revokeObjectURL(url);\n }\n };\n\n try {\n for (let pageNumber: number = 1; pageNumber <= pdf.numPages; pageNumber++) {\n const page: IRtPdfViewer.PdfPage = await pdf.getPage(pageNumber);\n const viewport: IRtPdfViewer.Viewport = page.getViewport({ scale: PRINT_DPI / 72 });\n const canvas: HTMLCanvasElement = doc.createElement('canvas');\n canvas.width = Math.floor(viewport.width);\n canvas.height = Math.floor(viewport.height);\n await page.render({ canvas, viewport, intent: 'print' }).promise;\n\n const url: string = win.URL.createObjectURL(await canvasBlob(canvas));\n urls.push(url);\n // Холст печатной плотности весит десятки мегабайт: память отдаётся сразу, а не по уборке\n canvas.width = 0;\n canvas.height = 0;\n\n const image: HTMLImageElement = doc.createElement('img');\n image.classList.add(`${RT_PDF_PRINT_BLOCK}__page`);\n image.alt = '';\n image.src = url;\n images.push(image);\n sheet.append(image);\n }\n\n doc.body.append(sheet);\n // Печать до раскодирования картинок даёт пустые листы\n await Promise.all(images.map((image: HTMLImageElement): Promise<void> => image.decode().catch((): void => undefined)));\n doc.documentElement.classList.add(RT_PDF_PRINTING_FLAG);\n } catch (error: unknown) {\n dispose();\n throw error;\n }\n\n return { dispose };\n}\n","import { InjectionToken, Provider } from '@angular/core';\n\nimport { IRtPdfViewer } from './rt-pdf-viewer.model';\n\n/**\n * Адрес воркера по умолчанию. Относительный: считается от `<base href>`, поэтому приложение под\n * своим префиксом не правит его руками. Файл туда кладёт сборка приложения — см. обзор компонента.\n */\nexport const RT_PDF_VIEWER_WORKER_SRC: string = 'assets/pdfjs/pdf.worker.min.mjs';\n\n/**\n * Рисовальщик грузится только при открытии окна. Статический импорт положил бы `pdfjs-dist` в\n * бандл каждому, кто импортировал вход, даже если окно так и не открылось.\n */\nfunction loadPdfJs(): Promise<IRtPdfViewer.PdfJs> {\n return import('pdfjs-dist');\n}\n\n/** Настройки рисовальщика просмотрщика. Без провайдера — умолчания выше. */\nexport const RT_PDF_VIEWER_CONFIG: InjectionToken<IRtPdfViewer.Config> = new InjectionToken<IRtPdfViewer.Config>('RT_PDF_VIEWER_CONFIG', {\n providedIn: 'root',\n factory: (): IRtPdfViewer.Config => ({ workerSrc: RT_PDF_VIEWER_WORKER_SRC, load: loadPdfJs }),\n});\n\n/**\n * Настраивает просмотрщик: адрес воркера и каталоги данных `pdfjs-dist`. Незаданное берётся из\n * умолчаний.\n *\n * ```typescript\n * export const appConfig: ApplicationConfig = {\n * providers: [provideRtPdfViewer({ workerSrc: 'static/pdf.worker.min.mjs' })],\n * };\n * ```\n */\nexport function provideRtPdfViewer(config: Partial<IRtPdfViewer.Config>): Provider {\n return {\n provide: RT_PDF_VIEWER_CONFIG,\n useValue: { workerSrc: RT_PDF_VIEWER_WORKER_SRC, load: loadPdfJs, ...config } satisfies IRtPdfViewer.Config,\n };\n}\n","import { CdkTrapFocus } from '@angular/cdk/a11y';\nimport { DOCUMENT } from '@angular/common';\nimport {\n afterNextRender,\n computed,\n inject,\n signal,\n viewChild,\n viewChildren,\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ElementRef,\n Injector,\n Signal,\n ViewEncapsulation,\n WritableSignal,\n} from '@angular/core';\n\n// rt-tools\nimport { BlockDirective, ElemDirective, WINDOW } from '@rt-tools/core';\n\nimport { rtKitLabel, RT_KIT_LABELS, TRtKitLabelMap, TRtKitLabelParams } from '@rt-tools/ui-kit-v2/core';\nimport { RtButtonDirective } from '@rt-tools/ui-kit-v2/button';\nimport { RtDialogRef } from '@rt-tools/ui-kit-v2/dialog';\nimport { RT_DIALOG_DATA } from '@rt-tools/ui-kit-v2/dialog';\nimport { RtEmptyStateComponent } from '@rt-tools/ui-kit-v2/empty-state';\nimport { RtIconComponent } from '@rt-tools/ui-kit-v2/icon';\nimport { RtIconButtonComponent } from '@rt-tools/ui-kit-v2/icon-button';\nimport { RtSpinnerComponent } from '@rt-tools/ui-kit-v2/spinner';\nimport {\n anchoredScrollTop,\n canZoomIn,\n canZoomOut,\n fitWidthPercent,\n initialZoom,\n nextZoomStep,\n pageBoxes,\n pagesToRender,\n visiblePageIndex,\n zoomPercentLabel,\n} from './rt-pdf-viewer.logic';\nimport { IRtPdfViewer } from './rt-pdf-viewer.model';\nimport { downloadPdf, preparePrintSheet } from './rt-pdf-viewer.output';\nimport { RT_PDF_VIEWER_CONFIG } from './rt-pdf-viewer.tokens';\n\nconst BEM_BLOCK: string = 'rt-pdf-viewer';\n\n/** Сколько страниц сверх нарисованных держать холстами: дальше холст очищается ради памяти. */\nconst KEEP_OVERSCAN: number = 3;\n\n/** Нарисованная страница: холст и масштаб, в котором он нарисован. */\ninterface IRenderedPage {\n readonly percent: number;\n readonly task: IRtPdfViewer.RenderTask;\n}\n\n/**\n * Полноэкранный просмотр документа PDF поверх `RtDialogService`.\n *\n * Страницы рисует `pdfjs-dist` — необязательная зависимость кита. Он грузится динамическим импортом\n * только при открытии окна, поэтому вход `@rt-tools/ui-kit-v2/pdf-viewer` не утяжеляет бандл, пока\n * окно не открыто. Рисуются только видимые страницы и их соседи, с плотностью точек экрана.\n *\n * `Esc`, блокировку прокрутки фона и затемнение даёт сам диалог; фокус после закрытия\n * просмотрщик возвращает туда, откуда его открыли.\n */\n@Component({\n selector: 'rt-pdf-viewer',\n templateUrl: './rt-pdf-viewer.component.html',\n styleUrl: './rt-pdf-viewer.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [\n // angular\n CdkTrapFocus,\n\n // rt-tools\n BlockDirective,\n ElemDirective,\n\n // components\n RtButtonDirective,\n RtEmptyStateComponent,\n RtIconButtonComponent,\n RtIconComponent,\n RtSpinnerComponent,\n\n // transloco\n ],\n host: {\n class: BEM_BLOCK,\n role: 'dialog',\n 'aria-modal': 'true',\n '[attr.aria-label]': 'fileName',\n // Клавиши слушает сам просмотрщик, поэтому фокус переводится на него сразу после открытия\n tabindex: '-1',\n '(keydown.escape)': 'onEscape($event)',\n '(window:resize)': 'onResize()',\n },\n})\nexport class RtPdfViewerComponent {\n readonly #data: IRtPdfViewer.Data = inject(RT_DIALOG_DATA) as IRtPdfViewer.Data;\n readonly #dialogRef: RtDialogRef<void> = inject(RtDialogRef<void>);\n readonly #config: IRtPdfViewer.Config = inject(RT_PDF_VIEWER_CONFIG);\n readonly #host: ElementRef<HTMLElement> = inject(ElementRef<HTMLElement>);\n readonly #document: Document = inject(DOCUMENT);\n readonly #window: Window & typeof globalThis = inject(WINDOW) as Window & typeof globalThis;\n readonly #injector: Injector = inject(Injector);\n\n /** Откуда открыли: туда возвращается фокус после закрытия */\n readonly #opener: HTMLElement | null = this.#focusedElement();\n\n readonly #availableWidth: WritableSignal<number> = signal<number>(0);\n readonly #pageSizes: WritableSignal<readonly IRtPdfViewer.PageSize[]> = signal<readonly IRtPdfViewer.PageSize[]>([]);\n readonly #pageOfParams: Signal<TRtKitLabelParams> = computed((): TRtKitLabelParams => ({\n page: this.currentPage() + 1,\n total: this.#pageSizes().length,\n }));\n\n #loadingTask: IRtPdfViewer.LoadingTask | null = null;\n #pdf: IRtPdfViewer.PdfDocument | null = null;\n #pages: Map<number, Promise<IRtPdfViewer.PdfPage>> = new Map<number, Promise<IRtPdfViewer.PdfPage>>();\n #rendered: Map<number, IRenderedPage> = new Map<number, IRenderedPage>();\n #printSheet: IRtPdfViewer.PrintSheet | null = null;\n #destroyed: boolean = false;\n\n protected readonly t: Signal<TRtKitLabelMap> = inject(RT_KIT_LABELS);\n\n protected readonly scroller: Signal<ElementRef<HTMLElement> | undefined> = viewChild<ElementRef<HTMLElement>>('scroller');\n protected readonly column: Signal<ElementRef<HTMLElement> | undefined> = viewChild<ElementRef<HTMLElement>>('column');\n protected readonly pageNodes: Signal<readonly ElementRef<HTMLElement>[]> = viewChildren<ElementRef<HTMLElement>>('page');\n protected readonly canvases: Signal<readonly ElementRef<HTMLCanvasElement>[]> = viewChildren<ElementRef<HTMLCanvasElement>>('canvas');\n\n protected readonly fileName: string = this.#data.fileName;\n\n protected readonly status: WritableSignal<IRtPdfViewer.Status> = signal<IRtPdfViewer.Status>('loading');\n\n protected readonly printing: WritableSignal<boolean> = signal<boolean>(false);\n\n protected readonly zoom: WritableSignal<IRtPdfViewer.Zoom> = signal<IRtPdfViewer.Zoom>({ kind: 'percent', percent: 100 });\n\n /** Масштаб на экране в процентах — и для «по ширине», который хранится режимом */\n protected readonly percent: Signal<number> = computed((): number => {\n const zoom: IRtPdfViewer.Zoom = this.zoom();\n\n return zoom.kind === 'fit' ? fitWidthPercent(this.#availableWidth(), this.#pageSizes()) : zoom.percent;\n });\n\n protected readonly isFit: Signal<boolean> = computed((): boolean => this.zoom().kind === 'fit');\n\n protected readonly zoomLabel: Signal<string> = computed((): string =>\n this.isFit() ? this.t().pdfViewerFitWidth : zoomPercentLabel(this.percent())\n );\n\n protected readonly canZoomIn: Signal<boolean> = computed((): boolean => canZoomIn(this.percent()));\n\n protected readonly canZoomOut: Signal<boolean> = computed((): boolean => canZoomOut(this.percent()));\n\n protected readonly boxes: Signal<IRtPdfViewer.PageBox[]> = computed((): IRtPdfViewer.PageBox[] =>\n pageBoxes(this.#pageSizes(), this.percent())\n );\n\n /** Видимая сейчас страница, с нуля */\n protected readonly currentPage: WritableSignal<number> = signal<number>(0);\n\n protected readonly counter: Signal<string> = computed((): string => `${this.currentPage() + 1} / ${this.#pageSizes().length}`);\n\n protected readonly pageOfLabel: Signal<string> = rtKitLabel('pdfViewerPageOf', this.#pageOfParams);\n\n constructor() {\n inject(DestroyRef).onDestroy((): void => this.#teardown());\n\n afterNextRender((): void => {\n this.#host.nativeElement.focus();\n void this.#open();\n });\n }\n\n public close(): void {\n this.#dialogRef.close();\n }\n\n /** Esc закрывает только окно: окно под ним, например боковая панель, остаётся открытым */\n protected onEscape(event: Event): void {\n event.stopPropagation();\n this.close();\n }\n\n protected zoomIn(): void {\n this.#setZoom({ kind: 'percent', percent: nextZoomStep(this.percent(), 1) });\n }\n\n protected zoomOut(): void {\n this.#setZoom({ kind: 'percent', percent: nextZoomStep(this.percent(), -1) });\n }\n\n protected fitWidth(): void {\n this.#measure();\n this.#setZoom({ kind: 'fit' });\n }\n\n protected onScroll(): void {\n this.#renderVisible();\n }\n\n protected onResize(): void {\n if (this.status() !== 'ready') {\n return;\n }\n this.#measure();\n if (this.isFit()) {\n this.#afterLayout((): void => this.#renderVisible());\n }\n }\n\n /** Байты уходят как пришли, а адрес — байтами документа, если он уже загружен */\n protected async download(): Promise<void> {\n const source: IRtPdfViewer.Source = this.#data.source;\n if (typeof source !== 'string') {\n downloadPdf(this.#document, this.#window, source, this.fileName);\n return;\n }\n const bytes: Uint8Array | null = this.#pdf ? await this.#pdf.getData().catch((): null => null) : null;\n downloadPdf(this.#document, this.#window, bytes ?? source, this.fileName);\n }\n\n /**\n * Печать документа целиком, без полосы и плашки: страницы рисуются картинками в отдельный лист,\n * и на время печати страница прячет всё, кроме него. Лист убирается после печати.\n */\n protected async print(): Promise<void> {\n const pdf: IRtPdfViewer.PdfDocument | null = this.#pdf;\n if (!pdf || this.printing()) {\n return;\n }\n this.printing.set(true);\n try {\n this.#printSheet?.dispose();\n const sheet: IRtPdfViewer.PrintSheet = await preparePrintSheet(pdf, this.#document, this.#window);\n if (this.#destroyed) {\n sheet.dispose();\n return;\n }\n this.#printSheet = sheet;\n this.#window.addEventListener(\n 'afterprint',\n (): void => {\n sheet.dispose();\n if (this.#printSheet === sheet) {\n this.#printSheet = null;\n }\n },\n { once: true }\n );\n this.#window.print();\n } catch {\n this.#printSheet?.dispose();\n this.#printSheet = null;\n } finally {\n this.printing.set(false);\n }\n }\n\n async #open(): Promise<void> {\n try {\n const pdfjs: IRtPdfViewer.PdfJs = await this.#config.load();\n if (this.#destroyed) {\n return;\n }\n pdfjs.GlobalWorkerOptions.workerSrc = this.#config.workerSrc;\n this.#loadingTask = pdfjs.getDocument(this.#documentParams());\n const pdf: IRtPdfViewer.PdfDocument = await this.#loadingTask.promise;\n if (this.#destroyed) {\n return;\n }\n this.#pdf = pdf;\n\n // Размеры всех страниц нужны сразу: без них нет высоты столбца, и прокрутка прыгала бы\n // по мере того, как страницы рисуются\n const sizes: IRtPdfViewer.PageSize[] = await Promise.all(\n Array.from({ length: pdf.numPages }, async (_: unknown, index: number): Promise<IRtPdfViewer.PageSize> => {\n const viewport: IRtPdfViewer.Viewport = (await this.#page(index)).getViewport({ scale: 1 });\n return { width: viewport.width, height: viewport.height };\n })\n );\n if (this.#destroyed) {\n return;\n }\n // Масштаб выбирается до показа страниц: иначе на телефоне на кадр мелькал бы обрезанный\n // лист в 100%, и только потом — «по ширине»\n this.#measure();\n this.zoom.set(initialZoom(this.#host.nativeElement.clientWidth, this.#availableWidth(), sizes));\n this.#pageSizes.set(sizes);\n this.status.set('ready');\n this.#afterLayout((): void => this.#renderVisible());\n } catch {\n if (!this.#destroyed) {\n this.status.set('error');\n }\n }\n }\n\n #documentParams(): IRtPdfViewer.DocumentParams {\n const source: IRtPdfViewer.Source = this.#data.source;\n const data: Pick<IRtPdfViewer.DocumentParams, 'url' | 'data'> =\n typeof source === 'string'\n ? { url: source }\n : // Рисовальщик забирает буфер себе в поток — отдаётся копия, оригинал нужен для скачивания\n { data: source instanceof ArrayBuffer ? new Uint8Array(source.slice(0)) : source.slice() };\n\n return {\n ...data,\n ...(this.#config.cMapUrl ? { cMapUrl: this.#config.cMapUrl, cMapPacked: true } : {}),\n ...(this.#config.standardFontDataUrl ? { standardFontDataUrl: this.#config.standardFontDataUrl } : {}),\n ...(this.#config.wasmUrl ? { wasmUrl: this.#config.wasmUrl } : {}),\n };\n }\n\n #page(index: number): Promise<IRtPdfViewer.PdfPage> {\n const pdf: IRtPdfViewer.PdfDocument | null = this.#pdf;\n if (!pdf) {\n return Promise.reject(new Error('document is not loaded'));\n }\n let page: Promise<IRtPdfViewer.PdfPage> | undefined = this.#pages.get(index);\n if (!page) {\n page = pdf.getPage(index + 1);\n this.#pages.set(index, page);\n }\n\n return page;\n }\n\n /**\n * Место, в которое страница встаёт «по ширине»: ширина окна прокрутки без полей столбца. Сам\n * столбец не меряется — увеличенная страница растягивает его шире окна.\n */\n #measure(): void {\n const scroller: HTMLElement | undefined = this.scroller()?.nativeElement;\n const column: HTMLElement | undefined = this.column()?.nativeElement;\n if (!scroller || !column) {\n return;\n }\n const style: CSSStyleDeclaration = this.#window.getComputedStyle(column);\n const padding: number = (parseFloat(style.paddingLeft) || 0) + (parseFloat(style.paddingRight) || 0);\n this.#availableWidth.set(Math.max(0, scroller.clientWidth - padding));\n }\n\n /**\n * Смена масштаба держит на экране то же место той же страницы: после перестройки столбца\n * прокрутка ставится от новой высоты страницы, а не остаётся на прежнем числе пикселей.\n */\n #setZoom(zoom: IRtPdfViewer.Zoom): void {\n const scroller: HTMLElement | undefined = this.scroller()?.nativeElement;\n const current: number = this.currentPage();\n const node: HTMLElement | undefined = this.pageNodes()[current]?.nativeElement;\n const before: { top: number; height: number; scrollTop: number } | null =\n scroller && node ? { top: node.offsetTop, height: node.offsetHeight, scrollTop: scroller.scrollTop } : null;\n\n this.zoom.set(zoom);\n this.#afterLayout((): void => {\n const after: HTMLElement | undefined = this.pageNodes()[current]?.nativeElement;\n if (scroller && after && before) {\n scroller.scrollTop = anchoredScrollTop(before.top, before.height, before.scrollTop, after.offsetTop, after.offsetHeight);\n }\n this.#renderVisible();\n });\n }\n\n #afterLayout(callback: () => void): void {\n afterNextRender(callback, { injector: this.#injector });\n }\n\n /** Счётчик догоняет прокрутку, а видимые страницы и их соседи дорисовываются */\n #renderVisible(): void {\n const scroller: HTMLElement | undefined = this.scroller()?.nativeElement;\n const nodes: readonly ElementRef<HTMLElement>[] = this.pageNodes();\n if (!scroller || nodes.length === 0 || this.#destroyed) {\n return;\n }\n const tops: number[] = nodes.map((node: ElementRef<HTMLElement>): number => node.nativeElement.offsetTop);\n const heights: number[] = nodes.map((node: ElementRef<HTMLElement>): number => node.nativeElement.offsetHeight);\n const { scrollTop, clientHeight } = scroller;\n\n this.currentPage.set(visiblePageIndex(tops, heights, scrollTop, clientHeight));\n\n const keep: Set<number> = new Set<number>(pagesToRender(tops, heights, scrollTop, clientHeight, KEEP_OVERSCAN));\n for (const index of [...this.#rendered.keys()]) {\n if (!keep.has(index)) {\n this.#clearPage(index);\n }\n }\n for (const index of pagesToRender(tops, heights, scrollTop, clientHeight)) {\n void this.#renderPage(index);\n }\n }\n\n async #renderPage(index: number): Promise<void> {\n const percent: number = this.percent();\n const existing: IRenderedPage | undefined = this.#rendered.get(index);\n if (existing?.percent === percent) {\n return;\n }\n existing?.task.cancel();\n this.#rendered.delete(index);\n\n const canvas: HTMLCanvasElement | undefined = this.canvases()[index]?.nativeElement;\n const box: IRtPdfViewer.PageBox | undefined = this.boxes()[index];\n if (!canvas || !box) {\n return;\n }\n try {\n const page: IRtPdfViewer.PdfPage = await this.#page(index);\n if (this.#destroyed || this.percent() !== percent || this.#rendered.has(index)) {\n return;\n }\n // Холст в точках экрана, а не в CSS-пикселях: иначе на плотном экране текст мылится\n const ratio: number = this.#window.devicePixelRatio || 1;\n const viewport: IRtPdfViewer.Viewport = page.getViewport({ scale: (box.width / page.getViewport({ scale: 1 }).width) * ratio });\n canvas.width = Math.floor(viewport.width);\n canvas.height = Math.floor(viewport.height);\n const task: IRtPdfViewer.RenderTask = page.render({ canvas, viewport });\n this.#rendered.set(index, { percent, task });\n await task.promise;\n } catch {\n // Отменённая отрисовка — обычное дело при прокрутке; страница дорисуется при подходе\n if (this.#rendered.get(index)?.percent === percent) {\n this.#rendered.delete(index);\n }\n }\n }\n\n #clearPage(index: number): void {\n this.#rendered.get(index)?.task.cancel();\n this.#rendered.delete(index);\n const canvas: HTMLCanvasElement | undefined = this.canvases()[index]?.nativeElement;\n if (canvas) {\n canvas.width = 0;\n canvas.height = 0;\n }\n }\n\n #teardown(): void {\n this.#destroyed = true;\n for (const rendered of this.#rendered.values()) {\n rendered.task.cancel();\n }\n this.#rendered.clear();\n this.#printSheet?.dispose();\n this.#printSheet = null;\n void this.#loadingTask?.destroy().catch((): void => undefined);\n this.#pdf = null;\n if (this.#opener?.isConnected) {\n this.#opener.focus();\n }\n }\n\n #focusedElement(): HTMLElement | null {\n const active: Element | null = this.#document.activeElement;\n\n return active instanceof HTMLElement && active !== this.#document.body ? active : null;\n }\n}\n","<ng-container rtBlock=\"rt-pdf-viewer\">\n <!-- Ловушка держит Tab внутри; фокус ставит сам компонент, поэтому авто-захвата здесь нет -->\n <div rtElem=\"surface\" cdkTrapFocus>\n <!-- Полоса видна сразу, ещё до загрузки документа: человек видит, что открыл, и может закрыть -->\n <header rtElem=\"bar\">\n <div rtElem=\"title\">\n <rt-icon rtElem=\"file-icon\" name=\"attach-pdf\" size=\"md\" />\n <span rtElem=\"file-name\" qa-dataid=\"pdf-viewer-file-name\" [attr.title]=\"fileName\">{{ fileName }}</span>\n </div>\n\n <div rtElem=\"actions\">\n <rt-icon-button\n qa-dataid=\"pdf-viewer-print\"\n icon=\"print\"\n variant=\"ghost\"\n [ariaLabel]=\"t().pdfViewerPrint\"\n [tooltip]=\"t().pdfViewerPrint\"\n [disabled]=\"status() !== 'ready'\"\n [loading]=\"printing()\"\n (clicked)=\"print()\" />\n <rt-icon-button\n qa-dataid=\"pdf-viewer-download\"\n icon=\"ico-download\"\n variant=\"ghost\"\n [ariaLabel]=\"t().pdfViewerDownload\"\n [tooltip]=\"t().pdfViewerDownload\"\n (clicked)=\"download()\" />\n <rt-icon-button\n qa-dataid=\"pdf-viewer-close\"\n icon=\"close\"\n variant=\"ghost\"\n [ariaLabel]=\"t().pdfViewerClose\"\n [tooltip]=\"t().pdfViewerClose\"\n (clicked)=\"close()\" />\n </div>\n </header>\n\n <div rtElem=\"scroller\" qa-dataid=\"pdf-viewer-pages\" #scroller (scroll)=\"onScroll()\">\n <!-- Столбец стоит и до загрузки: по его ширине выбирается начальный масштаб -->\n <div rtElem=\"column\" #column>\n @if (status() === 'ready') {\n @for (box of boxes(); track box.index) {\n <div\n rtElem=\"page\"\n qa-dataid=\"pdf-viewer-page\"\n #page\n [attr.data-page-index]=\"box.index\"\n [style.width.px]=\"box.width\"\n [style.height.px]=\"box.height\">\n <canvas rtElem=\"canvas\" #canvas></canvas>\n </div>\n }\n }\n </div>\n </div>\n\n @switch (status()) {\n @case ('loading') {\n <div rtElem=\"center\">\n <rt-spinner qa-dataid=\"pdf-viewer-spinner\" color=\"on-primary\" [diameter]=\"40\" />\n </div>\n }\n\n @case ('error') {\n <div rtElem=\"center\">\n <rt-empty-state\n rtElem=\"error\"\n qa-dataid=\"pdf-viewer-error\"\n [title]=\"t().pdfViewerErrorTitle\"\n [description]=\"t().pdfViewerErrorText\">\n <button\n rtButton\n rtElem=\"error-download\"\n qa-dataid=\"pdf-viewer-error-download\"\n type=\"button\"\n icon=\"ico-download\"\n [label]=\"t().pdfViewerDownload\"\n [attr.aria-label]=\"t().pdfViewerDownload\"\n (click)=\"download()\"></button>\n </rt-empty-state>\n </div>\n }\n\n @case ('ready') {\n <div rtElem=\"toolbar\" qa-dataid=\"pdf-viewer-toolbar\">\n <span rtElem=\"counter\" qa-dataid=\"pdf-viewer-counter\" role=\"status\" aria-live=\"off\" [attr.aria-label]=\"pageOfLabel()\">\n {{ counter() }}\n </span>\n <span rtElem=\"divider\" aria-hidden=\"true\"></span>\n <rt-icon-button\n qa-dataid=\"pdf-viewer-zoom-out\"\n icon=\"minus\"\n variant=\"ghost\"\n size=\"sm\"\n [ariaLabel]=\"t().pdfViewerZoomOut\"\n [disabled]=\"!canZoomOut()\"\n (clicked)=\"zoomOut()\" />\n <span rtElem=\"zoom\" qa-dataid=\"pdf-viewer-zoom\">{{ zoomLabel() }}</span>\n <rt-icon-button\n qa-dataid=\"pdf-viewer-zoom-in\"\n icon=\"ico-plus\"\n variant=\"ghost\"\n size=\"sm\"\n [ariaLabel]=\"t().pdfViewerZoomIn\"\n [disabled]=\"!canZoomIn()\"\n (clicked)=\"zoomIn()\" />\n <span rtElem=\"fit-divider\" aria-hidden=\"true\"></span>\n <button\n rtButton\n rtElem=\"fit\"\n qa-dataid=\"pdf-viewer-fit\"\n type=\"button\"\n appearance=\"text\"\n size=\"sm\"\n [label]=\"t().pdfViewerFitWidth\"\n [attr.aria-label]=\"t().pdfViewerFitWidth\"\n [pressed]=\"isFit()\"\n (click)=\"fitWidth()\"></button>\n </div>\n }\n }\n </div>\n</ng-container>\n"],"mappings":"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"}
1
+ {"version":3,"file":"rt-tools-ui-kit-v2-pdf-viewer.mjs","names":[],"sources":["../../../projects/ui-kit-v2/src/lib/components/pdf-viewer/rt-pdf-viewer.logic.ts","../../../projects/ui-kit-v2/src/lib/components/pdf-viewer/rt-pdf-viewer.output.ts","../../../projects/ui-kit-v2/src/lib/components/pdf-viewer/rt-pdf-viewer.tokens.ts","../../../projects/ui-kit-v2/src/lib/components/pdf-viewer/rt-pdf-viewer.component.ts","../../../projects/ui-kit-v2/src/lib/components/pdf-viewer/rt-pdf-viewer.component.html"],"sourcesContent":["import { IRtPdfViewer } from './rt-pdf-viewer.model';\n\n/**\n * Чистая логика просмотрщика документа: шаги масштаба, начальный масштаб, номер видимой страницы и\n * то, какие страницы стоит держать нарисованными.\n */\n\n/** Ступени масштаба в процентах. Кнопки «−» и «+» ходят только по ним. */\nexport const RT_PDF_ZOOM_STEPS: readonly number[] = [50, 75, 100, 125, 150, 200, 300];\n\nexport const RT_PDF_ZOOM_MIN: number = RT_PDF_ZOOM_STEPS[0];\n\nexport const RT_PDF_ZOOM_MAX: number = RT_PDF_ZOOM_STEPS[RT_PDF_ZOOM_STEPS.length - 1];\n\n/**\n * Сколько CSS-пикселей в пункте документа. Пункт — 1/72 дюйма, CSS-пиксель — 1/96: при 100%\n * страница A4 на экране той же ширины, что на бумаге.\n */\nexport const RT_PDF_CSS_UNITS: number = 96 / 72;\n\n/** Ширина окна, до которой документ открывается «по ширине» при любом размере страницы. */\nexport const RT_PDF_NARROW_WIDTH: number = 768;\n\n/** Запас на дробную часть: 99.999% от деления ширин — это всё ещё 100%. */\nconst EPSILON: number = 0.01;\n\n/**\n * Следующая ступень масштаба. Шаг идёт от того, что на экране сейчас: из «по ширине» в 83% кнопка\n * «+» ведёт на 100%, а «−» — на 75%, а не на ступень, соседнюю с последней выбранной руками.\n */\nexport function nextZoomStep(current: number, direction: 1 | -1): number {\n if (direction > 0) {\n return RT_PDF_ZOOM_STEPS.find((step: number): boolean => step > current + EPSILON) ?? RT_PDF_ZOOM_MAX;\n }\n\n return [...RT_PDF_ZOOM_STEPS].reverse().find((step: number): boolean => step < current - EPSILON) ?? RT_PDF_ZOOM_MIN;\n}\n\n/** Есть ли куда увеличивать: на верхней границе кнопка «+» выключена. */\nexport function canZoomIn(current: number): boolean {\n return current < RT_PDF_ZOOM_MAX - EPSILON;\n}\n\n/** Есть ли куда уменьшать: на нижней границе кнопка «−» выключена. */\nexport function canZoomOut(current: number): boolean {\n return current > RT_PDF_ZOOM_MIN + EPSILON;\n}\n\n/**\n * Масштаб «по ширине» в процентах: самая широкая страница занимает всё место столбца.\n *\n * Границами ступеней он не зажат. На телефоне страница A4 «по ширине» — около 45%, и зажать её до\n * 50% значило бы вернуть горизонтальную прокрутку, ради которой режим и существует.\n */\nexport function fitWidthPercent(availableWidth: number, pages: readonly IRtPdfViewer.PageSize[]): number {\n const widest: number = Math.max(0, ...pages.map((page: IRtPdfViewer.PageSize): number => page.width));\n if (widest <= 0 || availableWidth <= 0) {\n return 100;\n }\n\n return (availableWidth / (widest * RT_PDF_CSS_UNITS)) * 100;\n}\n\n/**\n * С какого масштаба открыть документ. Узкий экран и страница, которая в 100% не влезает, —\n * «по ширине»: иначе человек сначала видит обрезанный край листа. Остальные — в 100%.\n */\nexport function initialZoom(viewportWidth: number, availableWidth: number, pages: readonly IRtPdfViewer.PageSize[]): IRtPdfViewer.Zoom {\n const widest: number = Math.max(0, ...pages.map((page: IRtPdfViewer.PageSize): number => page.width));\n if (viewportWidth <= RT_PDF_NARROW_WIDTH || widest * RT_PDF_CSS_UNITS > availableWidth) {\n return { kind: 'fit' };\n }\n\n return { kind: 'percent', percent: 100 };\n}\n\n/** Места под страницы на экране: от размера в пунктах и масштаба, в целых пикселях. */\nexport function pageBoxes(pages: readonly IRtPdfViewer.PageSize[], percent: number): IRtPdfViewer.PageBox[] {\n const scale: number = (percent / 100) * RT_PDF_CSS_UNITS;\n\n return pages.map((page: IRtPdfViewer.PageSize, index: number): IRtPdfViewer.PageBox => ({\n index,\n width: Math.floor(page.width * scale),\n height: Math.floor(page.height * scale),\n }));\n}\n\n/**\n * Номер страницы, которая видна сейчас, — с нуля. Это та, что занимает больше всего высоты окна;\n * при прокрутке до конца — последняя, даже если она короче соседки: иначе счётчик никогда не\n * показал бы «M / M» у документа с маленькой последней страницей. Документ, который виден целиком\n * без прокрутки, стоит на первой: до конца его никто не прокручивал.\n */\nexport function visiblePageIndex(tops: readonly number[], heights: readonly number[], scrollTop: number, viewportHeight: number): number {\n const total: number = Math.min(tops.length, heights.length);\n if (total === 0) {\n return 0;\n }\n\n const contentBottom: number = tops[total - 1] + heights[total - 1];\n if (scrollTop > 0 && scrollTop + viewportHeight >= contentBottom - 1) {\n return total - 1;\n }\n\n let best: number = 0;\n let bestArea: number = -1;\n for (let index: number = 0; index < total; index++) {\n const visible: number = Math.min(tops[index] + heights[index], scrollTop + viewportHeight) - Math.max(tops[index], scrollTop);\n if (visible > bestArea) {\n best = index;\n bestArea = visible;\n }\n }\n\n return best;\n}\n\n/**\n * Страницы, которые стоит нарисовать: видимые и по `overscan` соседних с каждой стороны. Документ\n * в сотню страниц разом не рисуется — каждая страница это холст в мегабайты памяти.\n */\nexport function pagesToRender(\n tops: readonly number[],\n heights: readonly number[],\n scrollTop: number,\n viewportHeight: number,\n overscan: number = 1\n): number[] {\n const total: number = Math.min(tops.length, heights.length);\n const visible: number[] = [];\n for (let index: number = 0; index < total; index++) {\n if (tops[index] < scrollTop + viewportHeight && tops[index] + heights[index] > scrollTop) {\n visible.push(index);\n }\n }\n if (visible.length === 0) {\n return [];\n }\n\n const first: number = Math.max(0, visible[0] - overscan);\n const last: number = Math.min(total - 1, visible[visible.length - 1] + overscan);\n\n return Array.from({ length: last - first + 1 }, (_: unknown, offset: number): number => first + offset);\n}\n\n/**\n * Куда прокрутить после смены масштаба, чтобы на экране осталось то же место той же страницы.\n * Без этого увеличение уводило бы человека на несколько страниц вниз от того, что он читал.\n */\nexport function anchoredScrollTop(oldTop: number, oldHeight: number, oldScrollTop: number, newTop: number, newHeight: number): number {\n if (oldHeight <= 0) {\n return newTop;\n }\n const ratio: number = (oldScrollTop - oldTop) / oldHeight;\n\n return Math.max(0, newTop + ratio * newHeight);\n}\n\n/** Подпись масштаба для плашки в процентах: «83%», а не «83.47%». */\nexport function zoomPercentLabel(percent: number): string {\n return `${Math.round(percent)}%`;\n}\n","import { IRtPdfViewer } from './rt-pdf-viewer.model';\n\n/**\n * Печать и скачивание документа — то, что уходит за пределы окна просмотра.\n */\n\n/** Блок печатного листа. Лист лежит в конце `<body>`, вне просмотрщика, и класс у него свой. */\nexport const RT_PDF_PRINT_BLOCK: string = 'rt-pdf-viewer-print';\n\n/** Признак на корне страницы, пока идёт печать: под ним прячется всё, кроме листа. */\nexport const RT_PDF_PRINTING_FLAG: string = 'rt-pdf-viewer-printing';\n\n/** Плотность печати: 150 точек на дюйм — текст чёткий, а лист в сотню страниц не выедает память. */\nconst PRINT_DPI: number = 150;\n\n/** Байты документа в том виде, в каком их принимает `Blob`. */\nfunction blobOf(bytes: Uint8Array | ArrayBuffer): Blob {\n const copy: Uint8Array<ArrayBuffer> = bytes instanceof ArrayBuffer ? new Uint8Array(bytes.slice(0)) : new Uint8Array(bytes);\n\n return new Blob([copy], { type: 'application/pdf' });\n}\n\n/**\n * Сохраняет файл под его именем. Байты уходят ссылкой на `Blob`: так браузер берёт имя из\n * `download`. Адрес без байтов отдаётся ссылкой как есть — чужой домен имя может и не принять, но\n * файл человек получит.\n */\nexport function downloadPdf(\n doc: Document,\n win: Window & typeof globalThis,\n file: Uint8Array | ArrayBuffer | string,\n fileName: string\n): void {\n const anchor: HTMLAnchorElement = doc.createElement('a');\n const ownUrl: string | null = typeof file === 'string' ? null : win.URL.createObjectURL(blobOf(file));\n\n anchor.href = ownUrl ?? (typeof file === 'string' ? file : '');\n anchor.download = fileName;\n anchor.rel = 'noopener';\n anchor.style.display = 'none';\n doc.body.append(anchor);\n anchor.click();\n anchor.remove();\n\n if (ownUrl) {\n // Отзыв сразу после щелчка обрывает скачивание в части браузеров: ссылка живёт ещё ход.\n win.setTimeout((): void => win.URL.revokeObjectURL(ownUrl), 0);\n }\n}\n\nfunction canvasBlob(canvas: HTMLCanvasElement): Promise<Blob> {\n return new Promise<Blob>((resolve: (blob: Blob) => void, reject: (reason: Error) => void): void => {\n canvas.toBlob((blob: Blob | null): void => (blob ? resolve(blob) : reject(new Error('canvas is empty'))), 'image/png');\n });\n}\n\n/**\n * Готовит печатный лист: каждая страница рисуется в картинку и встаёт в разметку в конце `<body>`.\n *\n * Лист собирается из картинок, а не из `<iframe>` с самим файлом: встроенного показа PDF нет у\n * мобильных браузеров, а `print()` у кадра с PDF в части настольных печатает пустую страницу или\n * только первую. Картинки печатает любой браузер.\n */\nexport async function preparePrintSheet(\n pdf: IRtPdfViewer.PdfDocument,\n doc: Document,\n win: Window & typeof globalThis\n): Promise<IRtPdfViewer.PrintSheet> {\n const sheet: HTMLDivElement = doc.createElement('div');\n const urls: string[] = [];\n const images: HTMLImageElement[] = [];\n sheet.classList.add(RT_PDF_PRINT_BLOCK);\n\n const dispose: () => void = (): void => {\n doc.documentElement.classList.remove(RT_PDF_PRINTING_FLAG);\n sheet.remove();\n for (const url of urls) {\n win.URL.revokeObjectURL(url);\n }\n };\n\n try {\n for (let pageNumber: number = 1; pageNumber <= pdf.numPages; pageNumber++) {\n const page: IRtPdfViewer.PdfPage = await pdf.getPage(pageNumber);\n const viewport: IRtPdfViewer.Viewport = page.getViewport({ scale: PRINT_DPI / 72 });\n const canvas: HTMLCanvasElement = doc.createElement('canvas');\n canvas.width = Math.floor(viewport.width);\n canvas.height = Math.floor(viewport.height);\n await page.render({ canvas, viewport, intent: 'print' }).promise;\n\n const url: string = win.URL.createObjectURL(await canvasBlob(canvas));\n urls.push(url);\n // Холст печатной плотности весит десятки мегабайт: память отдаётся сразу, а не по уборке\n canvas.width = 0;\n canvas.height = 0;\n\n const image: HTMLImageElement = doc.createElement('img');\n image.classList.add(`${RT_PDF_PRINT_BLOCK}__page`);\n image.alt = '';\n image.src = url;\n images.push(image);\n sheet.append(image);\n }\n\n doc.body.append(sheet);\n // Печать до раскодирования картинок даёт пустые листы\n await Promise.all(images.map((image: HTMLImageElement): Promise<void> => image.decode().catch((): void => undefined)));\n doc.documentElement.classList.add(RT_PDF_PRINTING_FLAG);\n } catch (error: unknown) {\n dispose();\n throw error;\n }\n\n return { dispose };\n}\n","import { InjectionToken, Provider } from '@angular/core';\n\nimport { IRtPdfViewer } from './rt-pdf-viewer.model';\n\n/**\n * Адрес воркера по умолчанию. Относительный: считается от `<base href>`, поэтому приложение под\n * своим префиксом не правит его руками. Файл туда кладёт сборка приложения — см. обзор компонента.\n */\nexport const RT_PDF_VIEWER_WORKER_SRC: string = 'assets/pdfjs/pdf.worker.min.mjs';\n\n/**\n * Рисовальщик грузится только при открытии окна. Статический импорт положил бы `pdfjs-dist` в\n * бандл каждому, кто импортировал вход, даже если окно так и не открылось.\n */\nfunction loadPdfJs(): Promise<IRtPdfViewer.PdfJs> {\n return import('pdfjs-dist');\n}\n\n/** Настройки рисовальщика просмотрщика. Без провайдера — умолчания выше. */\nexport const RT_PDF_VIEWER_CONFIG: InjectionToken<IRtPdfViewer.Config> = new InjectionToken<IRtPdfViewer.Config>('RT_PDF_VIEWER_CONFIG', {\n providedIn: 'root',\n factory: (): IRtPdfViewer.Config => ({ workerSrc: RT_PDF_VIEWER_WORKER_SRC, load: loadPdfJs }),\n});\n\n/**\n * Настраивает просмотрщик: адрес воркера и каталоги данных `pdfjs-dist`. Незаданное берётся из\n * умолчаний.\n *\n * ```typescript\n * export const appConfig: ApplicationConfig = {\n * providers: [provideRtPdfViewer({ workerSrc: 'static/pdf.worker.min.mjs' })],\n * };\n * ```\n */\nexport function provideRtPdfViewer(config: Partial<IRtPdfViewer.Config>): Provider {\n return {\n provide: RT_PDF_VIEWER_CONFIG,\n useValue: { workerSrc: RT_PDF_VIEWER_WORKER_SRC, load: loadPdfJs, ...config } satisfies IRtPdfViewer.Config,\n };\n}\n","import { CdkTrapFocus } from '@angular/cdk/a11y';\nimport { DOCUMENT } from '@angular/common';\nimport {\n afterNextRender,\n computed,\n inject,\n signal,\n viewChild,\n viewChildren,\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ElementRef,\n Injector,\n Signal,\n ViewEncapsulation,\n WritableSignal,\n} from '@angular/core';\n\n// rt-tools\nimport { BlockDirective, ElemDirective, WINDOW } from '@rt-tools/core';\n\nimport { rtKitLabel, RT_KIT_LABELS, TRtKitLabelMap, TRtKitLabelParams } from '@rt-tools/ui-kit-v2/core';\nimport { RtButtonDirective } from '@rt-tools/ui-kit-v2/button';\nimport { RtDialogRef } from '@rt-tools/ui-kit-v2/dialog';\nimport { RT_DIALOG_DATA } from '@rt-tools/ui-kit-v2/dialog';\nimport { RtEmptyStateComponent } from '@rt-tools/ui-kit-v2/empty-state';\nimport { RtIconComponent } from '@rt-tools/ui-kit-v2/icon';\nimport { RtIconButtonComponent } from '@rt-tools/ui-kit-v2/icon-button';\nimport { RtSpinnerComponent } from '@rt-tools/ui-kit-v2/spinner';\nimport {\n anchoredScrollTop,\n canZoomIn,\n canZoomOut,\n fitWidthPercent,\n initialZoom,\n nextZoomStep,\n pageBoxes,\n pagesToRender,\n visiblePageIndex,\n zoomPercentLabel,\n} from './rt-pdf-viewer.logic';\nimport { IRtPdfViewer } from './rt-pdf-viewer.model';\nimport { downloadPdf, preparePrintSheet } from './rt-pdf-viewer.output';\nimport { RT_PDF_VIEWER_CONFIG } from './rt-pdf-viewer.tokens';\n\nconst BEM_BLOCK: string = 'rt-pdf-viewer';\n\n/** Сколько страниц сверх нарисованных держать холстами: дальше холст очищается ради памяти. */\nconst KEEP_OVERSCAN: number = 3;\n\n/** Нарисованная страница: холст и масштаб, в котором он нарисован. */\ninterface IRenderedPage {\n readonly percent: number;\n readonly task: IRtPdfViewer.RenderTask;\n}\n\n/**\n * Полноэкранный просмотр документа PDF поверх `RtDialogService`.\n *\n * Страницы рисует `pdfjs-dist` — необязательная зависимость кита. Он грузится динамическим импортом\n * только при открытии окна, поэтому вход `@rt-tools/ui-kit-v2/pdf-viewer` не утяжеляет бандл, пока\n * окно не открыто. Рисуются только видимые страницы и их соседи, с плотностью точек экрана.\n *\n * `Esc`, блокировку прокрутки фона и затемнение даёт сам диалог; фокус после закрытия\n * просмотрщик возвращает туда, откуда его открыли.\n */\n@Component({\n selector: 'rt-pdf-viewer',\n templateUrl: './rt-pdf-viewer.component.html',\n styleUrl: './rt-pdf-viewer.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [\n // angular\n CdkTrapFocus,\n\n // rt-tools\n BlockDirective,\n ElemDirective,\n\n // components\n RtButtonDirective,\n RtEmptyStateComponent,\n RtIconButtonComponent,\n RtIconComponent,\n RtSpinnerComponent,\n\n // transloco\n ],\n host: {\n class: BEM_BLOCK,\n role: 'dialog',\n 'aria-modal': 'true',\n '[attr.aria-label]': 'fileName',\n // Клавиши слушает сам просмотрщик, поэтому фокус переводится на него сразу после открытия\n tabindex: '-1',\n '(keydown.escape)': 'onEscape($event)',\n '(window:resize)': 'onResize()',\n },\n})\nexport class RtPdfViewerComponent {\n readonly #data: IRtPdfViewer.Data = inject(RT_DIALOG_DATA) as IRtPdfViewer.Data;\n readonly #dialogRef: RtDialogRef<void> = inject(RtDialogRef<void>);\n readonly #config: IRtPdfViewer.Config = inject(RT_PDF_VIEWER_CONFIG);\n readonly #host: ElementRef<HTMLElement> = inject(ElementRef<HTMLElement>);\n readonly #document: Document = inject(DOCUMENT);\n readonly #window: Window & typeof globalThis = inject(WINDOW) as Window & typeof globalThis;\n readonly #injector: Injector = inject(Injector);\n\n /** Откуда открыли: туда возвращается фокус после закрытия */\n readonly #opener: HTMLElement | null = this.#focusedElement();\n\n readonly #availableWidth: WritableSignal<number> = signal<number>(0);\n readonly #pageSizes: WritableSignal<readonly IRtPdfViewer.PageSize[]> = signal<readonly IRtPdfViewer.PageSize[]>([]);\n readonly #pageOfParams: Signal<TRtKitLabelParams> = computed((): TRtKitLabelParams => ({\n page: this.currentPage() + 1,\n total: this.#pageSizes().length,\n }));\n\n #loadingTask: IRtPdfViewer.LoadingTask | null = null;\n #pdf: IRtPdfViewer.PdfDocument | null = null;\n #pages: Map<number, Promise<IRtPdfViewer.PdfPage>> = new Map<number, Promise<IRtPdfViewer.PdfPage>>();\n #rendered: Map<number, IRenderedPage> = new Map<number, IRenderedPage>();\n #printSheet: IRtPdfViewer.PrintSheet | null = null;\n #destroyed: boolean = false;\n\n protected readonly t: Signal<TRtKitLabelMap> = inject(RT_KIT_LABELS);\n\n protected readonly scroller: Signal<ElementRef<HTMLElement> | undefined> = viewChild<ElementRef<HTMLElement>>('scroller');\n protected readonly column: Signal<ElementRef<HTMLElement> | undefined> = viewChild<ElementRef<HTMLElement>>('column');\n protected readonly pageNodes: Signal<readonly ElementRef<HTMLElement>[]> = viewChildren<ElementRef<HTMLElement>>('page');\n protected readonly canvases: Signal<readonly ElementRef<HTMLCanvasElement>[]> = viewChildren<ElementRef<HTMLCanvasElement>>('canvas');\n\n protected readonly fileName: string = this.#data.fileName;\n\n protected readonly status: WritableSignal<IRtPdfViewer.Status> = signal<IRtPdfViewer.Status>('loading');\n\n protected readonly printing: WritableSignal<boolean> = signal<boolean>(false);\n\n protected readonly zoom: WritableSignal<IRtPdfViewer.Zoom> = signal<IRtPdfViewer.Zoom>({ kind: 'percent', percent: 100 });\n\n /** Масштаб на экране в процентах — и для «по ширине», который хранится режимом */\n protected readonly percent: Signal<number> = computed((): number => {\n const zoom: IRtPdfViewer.Zoom = this.zoom();\n\n return zoom.kind === 'fit' ? fitWidthPercent(this.#availableWidth(), this.#pageSizes()) : zoom.percent;\n });\n\n protected readonly isFit: Signal<boolean> = computed((): boolean => this.zoom().kind === 'fit');\n\n protected readonly zoomLabel: Signal<string> = computed((): string =>\n this.isFit() ? this.t().pdfViewerFitWidth : zoomPercentLabel(this.percent())\n );\n\n protected readonly canZoomIn: Signal<boolean> = computed((): boolean => canZoomIn(this.percent()));\n\n protected readonly canZoomOut: Signal<boolean> = computed((): boolean => canZoomOut(this.percent()));\n\n protected readonly boxes: Signal<IRtPdfViewer.PageBox[]> = computed((): IRtPdfViewer.PageBox[] =>\n pageBoxes(this.#pageSizes(), this.percent())\n );\n\n /** Видимая сейчас страница, с нуля */\n protected readonly currentPage: WritableSignal<number> = signal<number>(0);\n\n protected readonly counter: Signal<string> = computed((): string => `${this.currentPage() + 1} / ${this.#pageSizes().length}`);\n\n protected readonly pageOfLabel: Signal<string> = rtKitLabel('pdfViewerPageOf', this.#pageOfParams);\n\n constructor() {\n inject(DestroyRef).onDestroy((): void => this.#teardown());\n\n afterNextRender((): void => {\n this.#host.nativeElement.focus();\n void this.#open();\n });\n }\n\n public close(): void {\n this.#dialogRef.close();\n }\n\n /** Esc закрывает только окно: окно под ним, например боковая панель, остаётся открытым */\n protected onEscape(event: Event): void {\n event.stopPropagation();\n this.close();\n }\n\n protected zoomIn(): void {\n this.#setZoom({ kind: 'percent', percent: nextZoomStep(this.percent(), 1) });\n }\n\n protected zoomOut(): void {\n this.#setZoom({ kind: 'percent', percent: nextZoomStep(this.percent(), -1) });\n }\n\n protected fitWidth(): void {\n this.#measure();\n this.#setZoom({ kind: 'fit' });\n }\n\n protected onScroll(): void {\n this.#renderVisible();\n }\n\n protected onResize(): void {\n if (this.status() !== 'ready') {\n return;\n }\n this.#measure();\n if (this.isFit()) {\n this.#afterLayout((): void => this.#renderVisible());\n }\n }\n\n /** Байты уходят как пришли, а адрес — байтами документа, если он уже загружен */\n protected async download(): Promise<void> {\n const source: IRtPdfViewer.Source = this.#data.source;\n if (typeof source !== 'string') {\n downloadPdf(this.#document, this.#window, source, this.fileName);\n return;\n }\n const bytes: Uint8Array | null = this.#pdf ? await this.#pdf.getData().catch((): null => null) : null;\n downloadPdf(this.#document, this.#window, bytes ?? source, this.fileName);\n }\n\n /**\n * Печать документа целиком, без полосы и плашки: страницы рисуются картинками в отдельный лист,\n * и на время печати страница прячет всё, кроме него. Лист убирается после печати.\n */\n protected async print(): Promise<void> {\n const pdf: IRtPdfViewer.PdfDocument | null = this.#pdf;\n if (!pdf || this.printing()) {\n return;\n }\n this.printing.set(true);\n try {\n this.#printSheet?.dispose();\n const sheet: IRtPdfViewer.PrintSheet = await preparePrintSheet(pdf, this.#document, this.#window);\n if (this.#destroyed) {\n sheet.dispose();\n return;\n }\n this.#printSheet = sheet;\n this.#window.addEventListener(\n 'afterprint',\n (): void => {\n sheet.dispose();\n if (this.#printSheet === sheet) {\n this.#printSheet = null;\n }\n },\n { once: true }\n );\n this.#window.print();\n } catch {\n this.#printSheet?.dispose();\n this.#printSheet = null;\n } finally {\n this.printing.set(false);\n }\n }\n\n async #open(): Promise<void> {\n try {\n const pdfjs: IRtPdfViewer.PdfJs = await this.#config.load();\n if (this.#destroyed) {\n return;\n }\n pdfjs.GlobalWorkerOptions.workerSrc = this.#config.workerSrc;\n this.#loadingTask = pdfjs.getDocument(this.#documentParams());\n const pdf: IRtPdfViewer.PdfDocument = await this.#loadingTask.promise;\n if (this.#destroyed) {\n return;\n }\n this.#pdf = pdf;\n\n // Размеры всех страниц нужны сразу: без них нет высоты столбца, и прокрутка прыгала бы\n // по мере того, как страницы рисуются\n const sizes: IRtPdfViewer.PageSize[] = await Promise.all(\n Array.from({ length: pdf.numPages }, async (_: unknown, index: number): Promise<IRtPdfViewer.PageSize> => {\n const viewport: IRtPdfViewer.Viewport = (await this.#page(index)).getViewport({ scale: 1 });\n return { width: viewport.width, height: viewport.height };\n })\n );\n if (this.#destroyed) {\n return;\n }\n // Масштаб выбирается до показа страниц: иначе на телефоне на кадр мелькал бы обрезанный\n // лист в 100%, и только потом — «по ширине»\n this.#measure();\n this.zoom.set(initialZoom(this.#host.nativeElement.clientWidth, this.#availableWidth(), sizes));\n this.#pageSizes.set(sizes);\n this.status.set('ready');\n this.#afterLayout((): void => this.#renderVisible());\n } catch {\n if (!this.#destroyed) {\n this.status.set('error');\n }\n }\n }\n\n #documentParams(): IRtPdfViewer.DocumentParams {\n const source: IRtPdfViewer.Source = this.#data.source;\n const data: Pick<IRtPdfViewer.DocumentParams, 'url' | 'data'> =\n typeof source === 'string'\n ? { url: source }\n : // Рисовальщик забирает буфер себе в поток — отдаётся копия, оригинал нужен для скачивания\n { data: source instanceof ArrayBuffer ? new Uint8Array(source.slice(0)) : source.slice() };\n\n return {\n ...data,\n ...(this.#config.cMapUrl ? { cMapUrl: this.#config.cMapUrl, cMapPacked: true } : {}),\n ...(this.#config.standardFontDataUrl ? { standardFontDataUrl: this.#config.standardFontDataUrl } : {}),\n ...(this.#config.wasmUrl ? { wasmUrl: this.#config.wasmUrl } : {}),\n };\n }\n\n #page(index: number): Promise<IRtPdfViewer.PdfPage> {\n const pdf: IRtPdfViewer.PdfDocument | null = this.#pdf;\n if (!pdf) {\n return Promise.reject(new Error('document is not loaded'));\n }\n let page: Promise<IRtPdfViewer.PdfPage> | undefined = this.#pages.get(index);\n if (!page) {\n page = pdf.getPage(index + 1);\n this.#pages.set(index, page);\n }\n\n return page;\n }\n\n /**\n * Место, в которое страница встаёт «по ширине»: ширина окна прокрутки без полей столбца. Сам\n * столбец не меряется — увеличенная страница растягивает его шире окна.\n */\n #measure(): void {\n const scroller: HTMLElement | undefined = this.scroller()?.nativeElement;\n const column: HTMLElement | undefined = this.column()?.nativeElement;\n if (!scroller || !column) {\n return;\n }\n const style: CSSStyleDeclaration = this.#window.getComputedStyle(column);\n const padding: number = (parseFloat(style.paddingLeft) || 0) + (parseFloat(style.paddingRight) || 0);\n this.#availableWidth.set(Math.max(0, scroller.clientWidth - padding));\n }\n\n /**\n * Смена масштаба держит на экране то же место той же страницы: после перестройки столбца\n * прокрутка ставится от новой высоты страницы, а не остаётся на прежнем числе пикселей.\n */\n #setZoom(zoom: IRtPdfViewer.Zoom): void {\n const scroller: HTMLElement | undefined = this.scroller()?.nativeElement;\n const current: number = this.currentPage();\n const node: HTMLElement | undefined = this.pageNodes()[current]?.nativeElement;\n const before: { top: number; height: number; scrollTop: number } | null =\n scroller && node ? { top: node.offsetTop, height: node.offsetHeight, scrollTop: scroller.scrollTop } : null;\n\n this.zoom.set(zoom);\n this.#afterLayout((): void => {\n const after: HTMLElement | undefined = this.pageNodes()[current]?.nativeElement;\n if (scroller && after && before) {\n scroller.scrollTop = anchoredScrollTop(before.top, before.height, before.scrollTop, after.offsetTop, after.offsetHeight);\n }\n this.#renderVisible();\n });\n }\n\n #afterLayout(callback: () => void): void {\n afterNextRender(callback, { injector: this.#injector });\n }\n\n /** Счётчик догоняет прокрутку, а видимые страницы и их соседи дорисовываются */\n #renderVisible(): void {\n const scroller: HTMLElement | undefined = this.scroller()?.nativeElement;\n const nodes: readonly ElementRef<HTMLElement>[] = this.pageNodes();\n if (!scroller || nodes.length === 0 || this.#destroyed) {\n return;\n }\n const tops: number[] = nodes.map((node: ElementRef<HTMLElement>): number => node.nativeElement.offsetTop);\n const heights: number[] = nodes.map((node: ElementRef<HTMLElement>): number => node.nativeElement.offsetHeight);\n const { scrollTop, clientHeight } = scroller;\n\n this.currentPage.set(visiblePageIndex(tops, heights, scrollTop, clientHeight));\n\n const keep: Set<number> = new Set<number>(pagesToRender(tops, heights, scrollTop, clientHeight, KEEP_OVERSCAN));\n for (const index of [...this.#rendered.keys()]) {\n if (!keep.has(index)) {\n this.#clearPage(index);\n }\n }\n for (const index of pagesToRender(tops, heights, scrollTop, clientHeight)) {\n void this.#renderPage(index);\n }\n }\n\n async #renderPage(index: number): Promise<void> {\n const percent: number = this.percent();\n const existing: IRenderedPage | undefined = this.#rendered.get(index);\n if (existing?.percent === percent) {\n return;\n }\n existing?.task.cancel();\n this.#rendered.delete(index);\n\n const canvas: HTMLCanvasElement | undefined = this.canvases()[index]?.nativeElement;\n const box: IRtPdfViewer.PageBox | undefined = this.boxes()[index];\n if (!canvas || !box) {\n return;\n }\n try {\n const page: IRtPdfViewer.PdfPage = await this.#page(index);\n if (this.#destroyed || this.percent() !== percent || this.#rendered.has(index)) {\n return;\n }\n // Холст в точках экрана, а не в CSS-пикселях: иначе на плотном экране текст мылится\n const ratio: number = this.#window.devicePixelRatio || 1;\n const viewport: IRtPdfViewer.Viewport = page.getViewport({ scale: (box.width / page.getViewport({ scale: 1 }).width) * ratio });\n canvas.width = Math.floor(viewport.width);\n canvas.height = Math.floor(viewport.height);\n const task: IRtPdfViewer.RenderTask = page.render({ canvas, viewport });\n this.#rendered.set(index, { percent, task });\n await task.promise;\n } catch {\n // Отменённая отрисовка — обычное дело при прокрутке; страница дорисуется при подходе\n if (this.#rendered.get(index)?.percent === percent) {\n this.#rendered.delete(index);\n }\n }\n }\n\n #clearPage(index: number): void {\n this.#rendered.get(index)?.task.cancel();\n this.#rendered.delete(index);\n const canvas: HTMLCanvasElement | undefined = this.canvases()[index]?.nativeElement;\n if (canvas) {\n canvas.width = 0;\n canvas.height = 0;\n }\n }\n\n #teardown(): void {\n this.#destroyed = true;\n for (const rendered of this.#rendered.values()) {\n rendered.task.cancel();\n }\n this.#rendered.clear();\n this.#printSheet?.dispose();\n this.#printSheet = null;\n void this.#loadingTask?.destroy().catch((): void => undefined);\n this.#pdf = null;\n if (this.#opener?.isConnected) {\n this.#opener.focus();\n }\n }\n\n #focusedElement(): HTMLElement | null {\n const active: Element | null = this.#document.activeElement;\n\n return active instanceof HTMLElement && active !== this.#document.body ? active : null;\n }\n}\n","<ng-container rtBlock=\"rt-pdf-viewer\">\n <!-- Ловушка держит Tab внутри; фокус ставит сам компонент, поэтому авто-захвата здесь нет -->\n <div rtElem=\"surface\" cdkTrapFocus>\n <!-- Полоса видна сразу, ещё до загрузки документа: человек видит, что открыл, и может закрыть -->\n <header rtElem=\"bar\">\n <div rtElem=\"title\">\n <rt-icon rtElem=\"file-icon\" name=\"attach-pdf\" size=\"md\" />\n <span rtElem=\"file-name\" qa-dataid=\"pdf-viewer-file-name\" [attr.title]=\"fileName\">{{ fileName }}</span>\n </div>\n\n <div rtElem=\"actions\">\n <rt-icon-button\n qa-dataid=\"pdf-viewer-print\"\n icon=\"print\"\n variant=\"ghost\"\n [ariaLabel]=\"t().pdfViewerPrint\"\n [tooltip]=\"t().pdfViewerPrint\"\n [disabled]=\"status() !== 'ready'\"\n [loading]=\"printing()\"\n (clicked)=\"print()\" />\n <rt-icon-button\n qa-dataid=\"pdf-viewer-download\"\n icon=\"ico-download\"\n variant=\"ghost\"\n [ariaLabel]=\"t().pdfViewerDownload\"\n [tooltip]=\"t().pdfViewerDownload\"\n (clicked)=\"download()\" />\n <rt-icon-button\n qa-dataid=\"pdf-viewer-close\"\n icon=\"close\"\n variant=\"ghost\"\n [ariaLabel]=\"t().pdfViewerClose\"\n [tooltip]=\"t().pdfViewerClose\"\n (clicked)=\"close()\" />\n </div>\n </header>\n\n <div rtElem=\"scroller\" qa-dataid=\"pdf-viewer-pages\" #scroller (scroll)=\"onScroll()\">\n <!-- Столбец стоит и до загрузки: по его ширине выбирается начальный масштаб -->\n <div rtElem=\"column\" #column>\n @if (status() === 'ready') {\n @for (box of boxes(); track box.index) {\n <div\n rtElem=\"page\"\n qa-dataid=\"pdf-viewer-page\"\n #page\n [attr.data-page-index]=\"box.index\"\n [style.width.px]=\"box.width\"\n [style.height.px]=\"box.height\">\n <canvas rtElem=\"canvas\" #canvas></canvas>\n </div>\n }\n }\n </div>\n </div>\n\n @switch (status()) {\n @case ('loading') {\n <div rtElem=\"center\">\n <rt-spinner qa-dataid=\"pdf-viewer-spinner\" color=\"on-primary\" [diameter]=\"40\" />\n </div>\n }\n\n @case ('error') {\n <div rtElem=\"center\">\n <rt-empty-state\n rtElem=\"error\"\n qa-dataid=\"pdf-viewer-error\"\n [title]=\"t().pdfViewerErrorTitle\"\n [description]=\"t().pdfViewerErrorText\">\n <button\n rtButton\n rtElem=\"error-download\"\n qa-dataid=\"pdf-viewer-error-download\"\n type=\"button\"\n icon=\"ico-download\"\n [label]=\"t().pdfViewerDownload\"\n [attr.aria-label]=\"t().pdfViewerDownload\"\n (click)=\"download()\"></button>\n </rt-empty-state>\n </div>\n }\n\n @case ('ready') {\n <div rtElem=\"toolbar\" qa-dataid=\"pdf-viewer-toolbar\">\n <span rtElem=\"counter\" qa-dataid=\"pdf-viewer-counter\" role=\"status\" aria-live=\"off\" [attr.aria-label]=\"pageOfLabel()\">\n {{ counter() }}\n </span>\n <span rtElem=\"divider\" aria-hidden=\"true\"></span>\n <rt-icon-button\n qa-dataid=\"pdf-viewer-zoom-out\"\n icon=\"minus\"\n variant=\"ghost\"\n size=\"sm\"\n [ariaLabel]=\"t().pdfViewerZoomOut\"\n [disabled]=\"!canZoomOut()\"\n (clicked)=\"zoomOut()\" />\n <span rtElem=\"zoom\" qa-dataid=\"pdf-viewer-zoom\">{{ zoomLabel() }}</span>\n <rt-icon-button\n qa-dataid=\"pdf-viewer-zoom-in\"\n icon=\"ico-plus\"\n variant=\"ghost\"\n size=\"sm\"\n [ariaLabel]=\"t().pdfViewerZoomIn\"\n [disabled]=\"!canZoomIn()\"\n (clicked)=\"zoomIn()\" />\n <span rtElem=\"fit-divider\" aria-hidden=\"true\"></span>\n <button\n rtButton\n rtElem=\"fit\"\n qa-dataid=\"pdf-viewer-fit\"\n type=\"button\"\n appearance=\"text\"\n size=\"sm\"\n [label]=\"t().pdfViewerFitWidth\"\n [attr.aria-label]=\"t().pdfViewerFitWidth\"\n [pressed]=\"isFit()\"\n (click)=\"fitWidth()\"></button>\n </div>\n }\n }\n </div>\n</ng-container>\n"],"mappings":";;;;;;;;;;;;AAQA,MAAa,oBAAuC;CAAC;CAAI;CAAI;CAAK;CAAK;CAAK;CAAK;AAAG;AAEpF,MAAa,kBAA0B,kBAAkB;AAEzD,MAAa,kBAA0B,kBAAkB,kBAAkB,SAAS;AAMpF,MAAa,mBAA2B,KAAK;AAG7C,MAAa,sBAA8B;AAG3C,MAAM,UAAkB;AAMxB,SAAgB,aAAa,SAAiB,WAAiB;CAC3D,IAAI,YAAY,GACZ,OAAO,kBAAkB,MAAM,SAA0B,OAAO,UAAU,OAAO,KAAK;CAG1F,OAAO,CAAC,GAAG,iBAAiB,CAAC,CAAC,QAAO,CAAE,CAAC,MAAM,SAA0B,OAAO,UAAU,OAAO,KAAK;AACzG;AAGA,SAAgB,UAAU,SAAe;CACrC,OAAO,UAAU,kBAAkB;AACvC;AAGA,SAAgB,WAAW,SAAe;CACtC,OAAO,UAAU,kBAAkB;AACvC;AAQA,SAAgB,gBAAgB,gBAAwB,OAAuC;CAC3F,MAAM,SAAiB,KAAK,IAAI,GAAG,GAAG,MAAM,KAAK,SAAwC,KAAK,KAAK,CAAC;CACpG,IAAI,UAAU,KAAK,kBAAkB,GACjC,OAAO;CAGX,OAAQ,kBAAkB,SAAS,oBAAqB;AAC5D;AAMA,SAAgB,YAAY,eAAuB,gBAAwB,OAAuC;CAC9G,MAAM,SAAiB,KAAK,IAAI,GAAG,GAAG,MAAM,KAAK,SAAwC,KAAK,KAAK,CAAC;CACpG,IAAI,iBAAA,OAAwC,SAAA,qBAA4B,gBACpE,OAAO,EAAE,MAAM,MAAK;CAGxB,OAAO;EAAE,MAAM;EAAW,SAAS;CAAG;AAC1C;AAGA,SAAgB,UAAU,OAAyC,SAAe;CAC9E,MAAM,QAAiB,UAAU,MAAO;CAExC,OAAO,MAAM,KAAK,MAA6B,WAAyC;EACpF;EACA,OAAO,KAAK,MAAM,KAAK,QAAQ,KAAK;EACpC,QAAQ,KAAK,MAAM,KAAK,SAAS,KAAK;CACzC,EAAC;AACN;AAQA,SAAgB,iBAAiB,MAAyB,SAA4B,WAAmB,gBAAsB;CAC3H,MAAM,QAAgB,KAAK,IAAI,KAAK,QAAQ,QAAQ,MAAM;CAC1D,IAAI,UAAU,GACV,OAAO;CAGX,MAAM,gBAAwB,KAAK,QAAQ,KAAK,QAAQ,QAAQ;CAChE,IAAI,YAAY,KAAK,YAAY,kBAAkB,gBAAgB,GAC/D,OAAO,QAAQ;CAGnB,IAAI,OAAe;CACnB,IAAI,WAAmB;CACvB,KAAK,IAAI,QAAgB,GAAG,QAAQ,OAAO,SAAS;EAChD,MAAM,UAAkB,KAAK,IAAI,KAAK,SAAS,QAAQ,QAAQ,YAAY,cAAc,IAAI,KAAK,IAAI,KAAK,QAAQ,SAAS;EAC5H,IAAI,UAAU,UAAU;GACpB,OAAO;GACP,WAAW;EACf;CACJ;CAEA,OAAO;AACX;AAMA,SAAgB,cACZ,MACA,SACA,WACA,gBACA,WAAmB,GAAC;CAEpB,MAAM,QAAgB,KAAK,IAAI,KAAK,QAAQ,QAAQ,MAAM;CAC1D,MAAM,UAAoB,CAAA;CAC1B,KAAK,IAAI,QAAgB,GAAG,QAAQ,OAAO,SACvC,IAAI,KAAK,SAAS,YAAY,kBAAkB,KAAK,SAAS,QAAQ,SAAS,WAC3E,QAAQ,KAAK,KAAK;CAG1B,IAAI,QAAQ,WAAW,GACnB,OAAO,CAAA;CAGX,MAAM,QAAgB,KAAK,IAAI,GAAG,QAAQ,KAAK,QAAQ;CACvD,MAAM,OAAe,KAAK,IAAI,QAAQ,GAAG,QAAQ,QAAQ,SAAS,KAAK,QAAQ;CAE/E,OAAO,MAAM,KAAK,EAAE,QAAQ,OAAO,QAAQ,EAAC,IAAK,GAAY,WAA2B,QAAQ,MAAM;AAC1G;AAMA,SAAgB,kBAAkB,QAAgB,WAAmB,cAAsB,QAAgB,WAAiB;CACxH,IAAI,aAAa,GACb,OAAO;CAEX,MAAM,SAAiB,eAAe,UAAU;CAEhD,OAAO,KAAK,IAAI,GAAG,SAAS,QAAQ,SAAS;AACjD;AAGA,SAAgB,iBAAiB,SAAe;CAC5C,OAAO,GAAG,KAAK,MAAM,OAAO,EAAC;AACjC;AC1JA,MAAa,qBAA6B;AAG1C,MAAa,uBAA+B;AAG5C,MAAM,YAAoB;AAG1B,SAAS,OAAO,OAA+B;CAC3C,MAAM,OAAgC,iBAAiB,cAAc,IAAI,WAAW,MAAM,MAAM,CAAC,CAAC,IAAI,IAAI,WAAW,KAAK;CAE1H,OAAO,IAAI,KAAK,CAAC,IAAI,GAAG,EAAE,MAAM,kBAAiB,CAAE;AACvD;AAOA,SAAgB,YACZ,KACA,KACA,MACA,UAAgB;CAEhB,MAAM,SAA4B,IAAI,cAAc,GAAG;CACvD,MAAM,SAAwB,OAAO,SAAS,WAAW,OAAO,IAAI,IAAI,gBAAgB,OAAO,IAAI,CAAC;CAEpG,OAAO,OAAO,WAAW,OAAO,SAAS,WAAW,OAAO;CAC3D,OAAO,WAAW;CAClB,OAAO,MAAM;CACb,OAAO,MAAM,UAAU;CACvB,IAAI,KAAK,OAAO,MAAM;CACtB,OAAO,MAAK;CACZ,OAAO,OAAM;CAEb,IAAI,QAEA,IAAI,iBAAuB,IAAI,IAAI,gBAAgB,MAAM,GAAG,CAAC;AAErE;AAEA,SAAS,WAAW,QAAyB;CACzC,OAAO,IAAI,SAAe,SAA+B,WAAyC;EAC9F,OAAO,QAAQ,SAA6B,OAAO,QAAQ,IAAI,IAAI,uBAAO,IAAI,MAAM,iBAAiB,CAAC,GAAI,WAAW;CACzH,CAAC;AACL;AASA,eAAsB,kBAClB,KACA,KACA,KAA+B;CAE/B,MAAM,QAAwB,IAAI,cAAc,KAAK;CACrD,MAAM,OAAiB,CAAA;CACvB,MAAM,SAA6B,CAAA;CACnC,MAAM,UAAU,IAAI,kBAAkB;CAEtC,MAAM,gBAAiC;EACnC,IAAI,gBAAgB,UAAU,OAAO,oBAAoB;EACzD,MAAM,OAAM;EACZ,KAAK,MAAM,OAAO,MACd,IAAI,IAAI,gBAAgB,GAAG;CAEnC;CAEA,IAAI;EACA,KAAK,IAAI,aAAqB,GAAG,cAAc,IAAI,UAAU,cAAc;GACvE,MAAM,OAA6B,MAAM,IAAI,QAAQ,UAAU;GAC/D,MAAM,WAAkC,KAAK,YAAY,EAAE,OAAO,YAAY,GAAE,CAAE;GAClF,MAAM,SAA4B,IAAI,cAAc,QAAQ;GAC5D,OAAO,QAAQ,KAAK,MAAM,SAAS,KAAK;GACxC,OAAO,SAAS,KAAK,MAAM,SAAS,MAAM;GAC1C,MAAM,KAAK,OAAO;IAAE;IAAQ;IAAU,QAAQ;GAAO,CAAE,CAAC,CAAC;GAEzD,MAAM,MAAc,IAAI,IAAI,gBAAgB,MAAM,WAAW,MAAM,CAAC;GACpE,KAAK,KAAK,GAAG;GAEb,OAAO,QAAQ;GACf,OAAO,SAAS;GAEhB,MAAM,QAA0B,IAAI,cAAc,KAAK;GACvD,MAAM,UAAU,IAAI,GAAG,mBAAkB,OAAQ;GACjD,MAAM,MAAM;GACZ,MAAM,MAAM;GACZ,OAAO,KAAK,KAAK;GACjB,MAAM,OAAO,KAAK;EACtB;EAEA,IAAI,KAAK,OAAO,KAAK;EAErB,MAAM,QAAQ,IAAI,OAAO,KAAK,UAA2C,MAAM,OAAM,CAAE,CAAC,YAAkB,MAAS,CAAC,CAAC;EACrH,IAAI,gBAAgB,UAAU,IAAI,oBAAoB;CAC1D,SAAS,OAAgB;EACrB,QAAO;EACP,MAAM;CACV;CAEA,OAAO,EAAE,QAAO;AACpB;AC1GA,MAAa,2BAAmC;AAMhD,SAAS,YAAS;CACd,OAAO,OAAO;AAClB;AAGA,MAAa,uBAA4D,IAAI,eAAoC,wBAAwB;CACrI,YAAY;CACZ,gBAAqC;EAAE,WAAW;EAA0B,MAAM;CAAS;AAC9F,CAAA;AAYD,SAAgB,mBAAmB,QAAoC;CACnE,OAAO;EACH,SAAS;EACT,UAAU;GAAE,WAAW;GAA0B,MAAM;GAAW,GAAG;EAAM;CAC9E;AACL;ACOA,MAAM,YAAoB;AAG1B,MAAM,gBAAwB;AAoD9B,IAAa,uBAAb,MAAa,qBAAoB;CACpB;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CAEA;CACA;CACA;CAKT;CACA;CACA;CACA;CACA;CACA;CA6CA,cAAA;EApES,KAAA,QAA2B,OAAO,cAAc;EAChD,KAAA,aAAgC,OAAO,WAAiB;EACxD,KAAA,UAA+B,OAAO,oBAAoB;EAC1D,KAAA,QAAiC,OAAO,UAAuB;EAC/D,KAAA,YAAsB,OAAO,QAAQ;EACrC,KAAA,UAAsC,OAAO,MAAM;EACnD,KAAA,YAAsB,OAAO,QAAQ;EAGrC,KAAA,UAA8B,KAAK,gBAAe;EAElD,KAAA,kBAA0C,OAAe,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;EAC3D,KAAA,aAA+D,OAAyC,CAAA,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;EAC3G,KAAA,gBAA2C,gBAAmC;GACnF,MAAM,KAAK,YAAW,IAAK;GAC3B,OAAO,KAAK,WAAU,CAAE,CAAC;EAC5B,IAAE,GAAA,YAAA,CAAA,EAAA,WAAA,gBAAA,CAAA,+BAAA,CAAA,CAAA;EAEH,KAAA,eAAgD;EAChD,KAAA,OAAwC;EACxC,KAAA,yBAAqD,IAAI,IAAG;EAC5D,KAAA,4BAAwC,IAAI,IAAG;EAC/C,KAAA,cAA8C;EAC9C,KAAA,aAAsB;EAEH,KAAA,IAA4B,OAAO,aAAa;EAEhD,KAAA,WAAwD,UAAmC,YAAW,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;EACtG,KAAA,SAAsD,UAAmC,UAAS,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;EAClG,KAAA,YAAwD,aAAsC,QAAO,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;EACrG,KAAA,WAA6D,aAA4C,UAAS,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;EAElH,KAAA,WAAmB,KAAK,MAAM;EAE9B,KAAA,SAA8C,OAA4B,WAAU,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;EAEpF,KAAA,WAAoC,OAAgB,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;EAE1D,KAAA,OAA0C,OAA0B;GAAE,MAAM;GAAW,SAAS;EAAG,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;EAGtG,KAAA,UAA0B,eAAsB;GAC/D,MAAM,OAA0B,KAAK,KAAI;GAEzC,OAAO,KAAK,SAAS,QAAQ,gBAAgB,KAAK,gBAAe,GAAI,KAAK,WAAU,CAAE,IAAI,KAAK;EACnG,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;EAEiB,KAAA,QAAyB,eAAwB,KAAK,KAAI,CAAE,CAAC,SAAS,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;EAE5E,KAAA,YAA4B,eAC3C,KAAK,MAAK,IAAK,KAAK,EAAC,CAAE,CAAC,oBAAoB,iBAAiB,KAAK,QAAO,CAAE,GAC9E,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;EAEkB,KAAA,YAA6B,eAAwB,UAAU,KAAK,QAAO,CAAE,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;EAE/E,KAAA,aAA8B,eAAwB,WAAW,KAAK,QAAO,CAAE,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;EAEjF,KAAA,QAAwC,eACvD,UAAU,KAAK,WAAU,GAAI,KAAK,QAAO,CAAE,GAC9C,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;EAGkB,KAAA,cAAsC,OAAe,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;EAEvD,KAAA,UAA0B,eAAuB,GAAG,KAAK,YAAW,IAAK,EAAC,KAAM,KAAK,WAAU,CAAE,CAAC,UAAS,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;EAE3G,KAAA,cAA8B,WAAW,mBAAmB,KAAK,aAAa;EAG7F,OAAO,UAAU,CAAC,CAAC,gBAAsB,KAAK,UAAS,CAAE;EAEzD,sBAA2B;GACvB,KAAK,MAAM,cAAc,MAAK;GAC9B,AAAK,KAAK,MAAK;EACnB,CAAC;CACL;CAEO,QAAK;EACR,KAAK,WAAW,MAAK;CACzB;CAGU,SAAS,OAAY;EAC3B,MAAM,gBAAe;EACrB,KAAK,MAAK;CACd;CAEU,SAAM;EACZ,KAAK,SAAS;GAAE,MAAM;GAAW,SAAS,aAAa,KAAK,QAAO,GAAI,CAAC;EAAC,CAAE;CAC/E;CAEU,UAAO;EACb,KAAK,SAAS;GAAE,MAAM;GAAW,SAAS,aAAa,KAAK,QAAO,GAAI,EAAE;EAAC,CAAE;CAChF;CAEU,WAAQ;EACd,KAAK,SAAQ;EACb,KAAK,SAAS,EAAE,MAAM,MAAK,CAAE;CACjC;CAEU,WAAQ;EACd,KAAK,eAAc;CACvB;CAEU,WAAQ;EACd,IAAI,KAAK,OAAM,MAAO,SAClB;EAEJ,KAAK,SAAQ;EACb,IAAI,KAAK,MAAK,GACV,KAAK,mBAAyB,KAAK,eAAc,CAAE;CAE3D;CAGU,MAAM,WAAQ;EACpB,MAAM,SAA8B,KAAK,MAAM;EAC/C,IAAI,OAAO,WAAW,UAAU;GAC5B,YAAY,KAAK,WAAW,KAAK,SAAS,QAAQ,KAAK,QAAQ;GAC/D;EACJ;EACA,MAAM,QAA2B,KAAK,OAAO,MAAM,KAAK,KAAK,QAAO,CAAE,CAAC,YAAkB,IAAI,IAAI;EACjG,YAAY,KAAK,WAAW,KAAK,SAAS,SAAS,QAAQ,KAAK,QAAQ;CAC5E;CAMU,MAAM,QAAK;EACjB,MAAM,MAAuC,KAAK;EAClD,IAAI,CAAC,OAAO,KAAK,SAAQ,GACrB;EAEJ,KAAK,SAAS,IAAI,IAAI;EACtB,IAAI;GACA,KAAK,aAAa,QAAO;GACzB,MAAM,QAAiC,MAAM,kBAAkB,KAAK,KAAK,WAAW,KAAK,OAAO;GAChG,IAAI,KAAK,YAAY;IACjB,MAAM,QAAO;IACb;GACJ;GACA,KAAK,cAAc;GACnB,KAAK,QAAQ,iBACT,oBACW;IACP,MAAM,QAAO;IACb,IAAI,KAAK,gBAAgB,OACrB,KAAK,cAAc;GAE3B,GACA,EAAE,MAAM,KAAI,CAAE;GAElB,KAAK,QAAQ,MAAK;EACtB,QAAQ;GACJ,KAAK,aAAa,QAAO;GACzB,KAAK,cAAc;EACvB,UAAU;GACN,KAAK,SAAS,IAAI,KAAK;EAC3B;CACJ;CAEA,MAAM,QAAK;EACP,IAAI;GACA,MAAM,QAA4B,MAAM,KAAK,QAAQ,KAAI;GACzD,IAAI,KAAK,YACL;GAEJ,MAAM,oBAAoB,YAAY,KAAK,QAAQ;GACnD,KAAK,eAAe,MAAM,YAAY,KAAK,gBAAe,CAAE;GAC5D,MAAM,MAAgC,MAAM,KAAK,aAAa;GAC9D,IAAI,KAAK,YACL;GAEJ,KAAK,OAAO;GAIZ,MAAM,QAAiC,MAAM,QAAQ,IACjD,MAAM,KAAK,EAAE,QAAQ,IAAI,SAAQ,GAAI,OAAO,GAAY,UAAiD;IACrG,MAAM,YAAmC,MAAM,KAAK,MAAM,KAAK,EAAA,CAAG,YAAY,EAAE,OAAO,EAAC,CAAE;IAC1F,OAAO;KAAE,OAAO,SAAS;KAAO,QAAQ,SAAS;IAAM;GAC3D,CAAC,CAAC;GAEN,IAAI,KAAK,YACL;GAIJ,KAAK,SAAQ;GACb,KAAK,KAAK,IAAI,YAAY,KAAK,MAAM,cAAc,aAAa,KAAK,gBAAe,GAAI,KAAK,CAAC;GAC9F,KAAK,WAAW,IAAI,KAAK;GACzB,KAAK,OAAO,IAAI,OAAO;GACvB,KAAK,mBAAyB,KAAK,eAAc,CAAE;EACvD,QAAQ;GACJ,IAAI,CAAC,KAAK,YACN,KAAK,OAAO,IAAI,OAAO;EAE/B;CACJ;CAEA,kBAAe;EACX,MAAM,SAA8B,KAAK,MAAM;EAO/C,OAAO;GACH,GANA,OAAO,WAAW,WACZ,EAAE,KAAK,OAAM,IAEb,EAAE,MAAM,kBAAkB,cAAc,IAAI,WAAW,OAAO,MAAM,CAAC,CAAC,IAAI,OAAO,MAAK,EAAE;GAI9F,GAAI,KAAK,QAAQ,UAAU;IAAE,SAAS,KAAK,QAAQ;IAAS,YAAY;GAAI,IAAK,CAAA;GACjF,GAAI,KAAK,QAAQ,sBAAsB,EAAE,qBAAqB,KAAK,QAAQ,oBAAmB,IAAK,CAAA;GACnG,GAAI,KAAK,QAAQ,UAAU,EAAE,SAAS,KAAK,QAAQ,QAAO,IAAK,CAAA;EAClE;CACL;CAEA,MAAM,OAAa;EACf,MAAM,MAAuC,KAAK;EAClD,IAAI,CAAC,KACD,OAAO,QAAQ,uBAAO,IAAI,MAAM,wBAAwB,CAAC;EAE7D,IAAI,OAAkD,KAAK,OAAO,IAAI,KAAK;EAC3E,IAAI,CAAC,MAAM;GACP,OAAO,IAAI,QAAQ,QAAQ,CAAC;GAC5B,KAAK,OAAO,IAAI,OAAO,IAAI;EAC/B;EAEA,OAAO;CACX;CAMA,WAAQ;EACJ,MAAM,WAAoC,KAAK,SAAQ,CAAE,EAAE;EAC3D,MAAM,SAAkC,KAAK,OAAM,CAAE,EAAE;EACvD,IAAI,CAAC,YAAY,CAAC,QACd;EAEJ,MAAM,QAA6B,KAAK,QAAQ,iBAAiB,MAAM;EACvE,MAAM,WAAmB,WAAW,MAAM,WAAW,KAAK,MAAM,WAAW,MAAM,YAAY,KAAK;EAClG,KAAK,gBAAgB,IAAI,KAAK,IAAI,GAAG,SAAS,cAAc,OAAO,CAAC;CACxE;CAMA,SAAS,MAAuB;EAC5B,MAAM,WAAoC,KAAK,SAAQ,CAAE,EAAE;EAC3D,MAAM,UAAkB,KAAK,YAAW;EACxC,MAAM,OAAgC,KAAK,UAAS,CAAE,CAAC,QAAQ,EAAE;EACjE,MAAM,SACF,YAAY,OAAO;GAAE,KAAK,KAAK;GAAW,QAAQ,KAAK;GAAc,WAAW,SAAS;EAAS,IAAK;EAE3G,KAAK,KAAK,IAAI,IAAI;EAClB,KAAK,mBAAwB;GACzB,MAAM,QAAiC,KAAK,UAAS,CAAE,CAAC,QAAQ,EAAE;GAClE,IAAI,YAAY,SAAS,QACrB,SAAS,YAAY,kBAAkB,OAAO,KAAK,OAAO,QAAQ,OAAO,WAAW,MAAM,WAAW,MAAM,YAAY;GAE3H,KAAK,eAAc;EACvB,CAAC;CACL;CAEA,aAAa,UAAoB;EAC7B,gBAAgB,UAAU,EAAE,UAAU,KAAK,UAAS,CAAE;CAC1D;CAGA,iBAAc;EACV,MAAM,WAAoC,KAAK,SAAQ,CAAE,EAAE;EAC3D,MAAM,QAA4C,KAAK,UAAS;EAChE,IAAI,CAAC,YAAY,MAAM,WAAW,KAAK,KAAK,YACxC;EAEJ,MAAM,OAAiB,MAAM,KAAK,SAA0C,KAAK,cAAc,SAAS;EACxG,MAAM,UAAoB,MAAM,KAAK,SAA0C,KAAK,cAAc,YAAY;EAC9G,MAAM,EAAE,WAAW,iBAAiB;EAEpC,KAAK,YAAY,IAAI,iBAAiB,MAAM,SAAS,WAAW,YAAY,CAAC;EAE7E,MAAM,OAAoB,IAAI,IAAY,cAAc,MAAM,SAAS,WAAW,cAAc,aAAa,CAAC;EAC9G,KAAK,MAAM,SAAS,CAAC,GAAG,KAAK,UAAU,KAAI,CAAE,GACzC,IAAI,CAAC,KAAK,IAAI,KAAK,GACf,KAAK,WAAW,KAAK;EAG7B,KAAK,MAAM,SAAS,cAAc,MAAM,SAAS,WAAW,YAAY,GACpE,AAAK,KAAK,YAAY,KAAK;CAEnC;CAEA,MAAM,YAAY,OAAa;EAC3B,MAAM,UAAkB,KAAK,QAAO;EACpC,MAAM,WAAsC,KAAK,UAAU,IAAI,KAAK;EACpE,IAAI,UAAU,YAAY,SACtB;EAEJ,UAAU,KAAK,OAAM;EACrB,KAAK,UAAU,OAAO,KAAK;EAE3B,MAAM,SAAwC,KAAK,SAAQ,CAAE,CAAC,MAAM,EAAE;EACtE,MAAM,MAAwC,KAAK,MAAK,CAAE,CAAC;EAC3D,IAAI,CAAC,UAAU,CAAC,KACZ;EAEJ,IAAI;GACA,MAAM,OAA6B,MAAM,KAAK,MAAM,KAAK;GACzD,IAAI,KAAK,cAAc,KAAK,QAAO,MAAO,WAAW,KAAK,UAAU,IAAI,KAAK,GACzE;GAGJ,MAAM,QAAgB,KAAK,QAAQ,oBAAoB;GACvD,MAAM,WAAkC,KAAK,YAAY,EAAE,OAAQ,IAAI,QAAQ,KAAK,YAAY,EAAE,OAAO,EAAC,CAAE,CAAC,CAAC,QAAS,MAAK,CAAE;GAC9H,OAAO,QAAQ,KAAK,MAAM,SAAS,KAAK;GACxC,OAAO,SAAS,KAAK,MAAM,SAAS,MAAM;GAC1C,MAAM,OAAgC,KAAK,OAAO;IAAE;IAAQ;GAAQ,CAAE;GACtE,KAAK,UAAU,IAAI,OAAO;IAAE;IAAS;GAAI,CAAE;GAC3C,MAAM,KAAK;EACf,QAAQ;GAEJ,IAAI,KAAK,UAAU,IAAI,KAAK,CAAC,EAAE,YAAY,SACvC,KAAK,UAAU,OAAO,KAAK;EAEnC;CACJ;CAEA,WAAW,OAAa;EACpB,KAAK,UAAU,IAAI,KAAK,CAAC,EAAE,KAAK,OAAM;EACtC,KAAK,UAAU,OAAO,KAAK;EAC3B,MAAM,SAAwC,KAAK,SAAQ,CAAE,CAAC,MAAM,EAAE;EACtE,IAAI,QAAQ;GACR,OAAO,QAAQ;GACf,OAAO,SAAS;EACpB;CACJ;CAEA,YAAS;EACL,KAAK,aAAa;EAClB,KAAK,MAAM,YAAY,KAAK,UAAU,OAAM,GACxC,SAAS,KAAK,OAAM;EAExB,KAAK,UAAU,MAAK;EACpB,KAAK,aAAa,QAAO;EACzB,KAAK,cAAc;EACnB,AAAK,KAAK,cAAc,QAAO,CAAE,CAAC,YAAkB,MAAS;EAC7D,KAAK,OAAO;EACZ,IAAI,KAAK,SAAS,aACd,KAAK,QAAQ,MAAK;CAE1B;CAEA,kBAAe;EACX,MAAM,SAAyB,KAAK,UAAU;EAE9C,OAAO,kBAAkB,eAAe,WAAW,KAAK,UAAU,OAAO,SAAS;CACtF;CAxWS;EAAA,KAAA,OAAA,GAAA,mBAAA;GAAA,YAAA;GAAA,SAAA;GAAA,UAAA;GAAA,MAAA;GAAoB,MAAA,CAAA;GAAA,QAAA,GAAA,gBAAA;EAAA,CAAA;CAAA;CAApB;EAAA,KAAA,OAAA,GAAA,qBAAA;GAAA,YAAA;GAAA,SAAA;GAAA,MAAA;GAAoB,cAAA;GAAA,UAAA;GAAA,MAAA;IAAA,YAAA;KAAA,QAAA;KAAA,cAAA;KAAA,YAAA;IAAA;IAAA,WAAA;KAAA,kBAAA;KAAA,iBAAA;IAAA;IAAA,YAAA,EAAA,mBAAA,WAAA;IAAA,gBAAA;GAAA;GAAA,aAAA;IAAA;KAAA,cAAA;KAAA,OAAA;KAAA,WAAA,CAAA,UAAA;KAAA,aAAA;KAAA,UAAA;IAAA;IAAA;KAAA,cAAA;KAAA,OAAA;KAAA,WAAA,CAAA,QAAA;KAAA,aAAA;KAAA,UAAA;IAAA;IAAA;KAAA,cAAA;KAAA,WAAA,CAAA,MAAA;KAAA,aAAA;KAAA,UAAA;IAAA;IAAA;KAAA,cAAA;KAAA,WAAA,CAAA,QAAA;KAAA,aAAA;KAAA,UAAA;IAAA;GAAA;GAAA,UAAA;GAAA,UCrGjC;GA2HA,QAAA,CAAA,ovJAAA;GAAA,cAAA;IAAA;KAAA,MAAA;KAAA,MDhDQ;KAAY,UAAA;KAAA,QAAA,CAAA,gBAAA,yBAAA;KAAA,UAAA,CAAA,cAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MAGZ;KAAc,UAAA;KAAA,QAAA,CAAA,OAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MACd;KAAa,UAAA;KAAA,QAAA,CAAA,OAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MAGb;KAAiB,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;KAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MACjB;KAAqB,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;MAAA;MAAA;KAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MACrB;KAAqB,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;KAAA;KAAA,SAAA,CAAA,SAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MACrB;KAAe,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;KAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MACf;KAAkB,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;KAAA;IAAA;GAAA;GAAA,iBAAA,GAAA,wBAAA;GAAA,eAAA,GAAA,kBAAA;EAAA,CAAA;CAAA;;AAeb,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAoB,YAAA,CAAA;EAlChC,MAAA;EACa,MAAA,CAAA;GAAA,UAAA;GAAe,iBAGR,wBAAwB;GAAM,eAChC,kBAAkB;GAAI,SAC5B;IAEL;IAGA;IACA;IAGA;IACA;IACA;IACA;IACA;GAGH;GAAA,MACK;IACF,OAAO;IACP,MAAM;IACN,cAAc;IACd,qBAAqB;IAErB,UAAU;IACV,oBAAoB;IACpB,mBAAmB;GACtB;GAAA,UAAA;GAAA,QAAA,CAAA,ovJAAA;EAAA,CAAA;CA8B6G,CAAA;CAAA,sBAAA,CAAA;CAAA,gBAAA;EAAA,UAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA,YAAU,EAAA,UAAA,KAAA,CAAA;EAAA,CAAA;EAAA,QAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CACZ,UAAQ,EAAA,UAAA,KAAA,CAAA;EAAA,CAAA;EAAA,WAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CACH,QAAM,EAAA,UAAA,KAAA,CAAA;EAAA,CAAA;EAAA,UAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CACK,UAAQ,EAAA,UAAA,KAAA,CAAA;EAAA,CAAA;CAAA;AAAA,CAAA"}
@@ -1,7 +1,7 @@
1
1
  import * as i0 from "@angular/core";
2
2
  import { ChangeDetectionStrategy, Component, DestroyRef, Directive, ElementRef, Injector, Pipe, TemplateRef, ViewEncapsulation, afterNextRender, afterRenderEffect, booleanAttribute, computed, contentChild, effect, forwardRef, inject, input, model, numberAttribute, output, signal, viewChild } from "@angular/core";
3
3
  import { BlockDirective, ElemDirective, ModDirective } from "@rt-tools/core";
4
- import { RT_KIT_LABELS, RT_KIT_LOCALE, RtAiRunStatusComponent, RtButtonDirective, RtConfirmDirective, RtCopyValueComponent, RtDeltaViewComponent, RtEmptyStateComponent, RtFileCardComponent, RtFileDropComponent, RtFormControlBase, RtIconButtonComponent, RtIconComponent, RtMarkdownTextComponent, RtMenuComponent, RtMenuItemComponent, RtMessageComponent, RtNoteComponent, RtPromptSuggestionComponent, RtSkeletonWrapperComponent, RtSpinnerComponent, RtThreadListComponent, RtThreadListRowActionsDirective, RtThreadListRowDirective, RtToolbarComponent, RtToolbarLeftDirective, RtToolbarRightDirective, coerceQuillDeltaOutput, fileNameFromUrl, markdownToPlainText, rtKitLabel, splitSideMenuTitle } from "@rt-tools/ui-kit-v2";
4
+ import { RT_KIT_LABELS, RT_KIT_LOCALE, RtAiRunStatusComponent, RtButtonDirective, RtConfirmDirective, RtCopyValueComponent, RtDeltaViewComponent, RtEmptyStateComponent, RtFileCardComponent, RtFileDropComponent, RtFormControlBase, RtIconButtonComponent, RtIconComponent, RtMarkdownTextComponent, RtMenuComponent, RtMenuItemComponent, RtMessageComponent, RtNoteComponent, RtPromptSuggestionComponent, RtSkeletonWrapperComponent, RtSpinnerComponent, RtThreadListComponent, RtThreadListRowActionsDirective, RtThreadListRowDirective, RtToolbarComponent, RtToolbarLeftDirective, RtToolbarRightDirective, coerceQuillDeltaOutput, fileNameFromUrl, markdownToPlainText, rtKitLabel, rtMatchParts } from "@rt-tools/ui-kit-v2";
5
5
  import { CdkTextareaAutosize } from "@angular/cdk/text-field";
6
6
  import { takeUntilDestroyed, toSignal } from "@angular/core/rxjs-interop";
7
7
  import * as i1 from "@angular/forms";
@@ -1417,7 +1417,7 @@ var RtChatComponent = class RtChatComponent {
1417
1417
  }],
1418
1418
  ngImport: i0,
1419
1419
  template: "<ng-container rtBlock=\"rt-chat\">\n <rt-file-drop\n [overlayLabel]=\"t().chatDropOverlay\"\n [accept]=\"accept()\"\n [disabled]=\"dropDisabled()\"\n (filesDropped)=\"onFilesDropped($event)\">\n @if (showHeader()) {\n <rt-toolbar rtElem=\"header\" dense>\n <ng-template rtToolbarLeft>\n <h3 rtElem=\"title\" qa-dataid=\"chat-title\">{{ title() }}</h3>\n </ng-template>\n <ng-template rtToolbarRight>\n @if (showRefresh()) {\n <rt-icon-button\n qa-dataid=\"chat-refresh\"\n icon=\"sync\"\n variant=\"ghost\"\n [ariaLabel]=\"t().chatRefreshAria\"\n [tooltip]=\"t().chatRefreshTooltip\"\n [loading]=\"fetching()\"\n [disabled]=\"!hasThread()\"\n (clicked)=\"refresh.emit()\" />\n }\n @if (showExpand()) {\n <rt-icon-button\n qa-dataid=\"chat-expand\"\n variant=\"ghost\"\n [icon]=\"expanded() ? 'window-minimize' : 'window-maximize'\"\n [ariaLabel]=\"expandLabel()\"\n [tooltip]=\"expandLabel()\"\n (clicked)=\"toggleExpand()\" />\n }\n <ng-content select=\"[rtChatHeaderActions]\" />\n </ng-template>\n </rt-toolbar>\n }\n\n @if (!hasThread()) {\n <rt-note qa-dataid=\"chat-empty-hint\">{{ emptyHint() }}</rt-note>\n } @else {\n <div rtElem=\"thread-viewport\" qa-dataid=\"chat-thread-viewport\">\n @if (loading()) {\n <!-- Скелетон повторяет структуру сообщения (автор → текст → дата),\n чередуя выравнивание, чтобы тред не «прыгал» при подмене на реальные. -->\n <div rtElem=\"thread\" qa-dataid=\"chat-thread-skeleton\">\n @for (row of skeletonRows; track row; let even = $even) {\n <div rtElem=\"message-skeleton\" qa-dataid=\"chat-message-skeleton\" [rtMod]=\"{ own: even }\">\n <rt-skeleton-wrapper rtElem=\"skeleton-line\" width=\"72px\" height=\"10px\" [isLoading]=\"true\" />\n <rt-skeleton-wrapper rtElem=\"skeleton-line\" width=\"100%\" height=\"13px\" [isLoading]=\"true\" />\n <rt-skeleton-wrapper rtElem=\"skeleton-line\" width=\"55%\" height=\"13px\" [isLoading]=\"true\" />\n <rt-skeleton-wrapper rtElem=\"skeleton-line\" width=\"56px\" height=\"10px\" [isLoading]=\"true\" />\n </div>\n }\n </div>\n } @else {\n <div\n #thread\n rtElem=\"thread\"\n qa-dataid=\"chat-thread\"\n [rtMod]=\"{ frozen: fetching() }\"\n [attr.aria-busy]=\"fetching() ? 'true' : null\"\n (scroll)=\"onThreadScroll()\">\n @for (m of messages(); track m.id) {\n <div\n rtElem=\"message-row\"\n qa-dataid=\"chat-message\"\n [attr.data-own]=\"m.own\"\n [attr.data-status]=\"m.status || null\"\n [attr.data-deleted]=\"m.deleted || null\"\n [rtMod]=\"{ own: m.own }\">\n @if (m.canDelete && !m.deleted) {\n @if (m.deleteConfirm) {\n <rt-icon-button\n rtElem=\"message-delete\"\n qa-dataid=\"chat-message-delete\"\n icon=\"trash\"\n variant=\"ghost\"\n [ariaLabel]=\"t().chatDeleteAria\"\n [tooltip]=\"t().chatDeleteTooltip\"\n [rtConfirm]=\"m.deleteConfirm\"\n (confirmed)=\"deleteMessage.emit(m)\" />\n } @else {\n <rt-icon-button\n rtElem=\"message-delete\"\n qa-dataid=\"chat-message-delete\"\n icon=\"trash\"\n variant=\"ghost\"\n [ariaLabel]=\"t().chatDeleteAria\"\n [tooltip]=\"t().chatDeleteTooltip\"\n (clicked)=\"deleteMessage.emit(m)\" />\n }\n }\n @if (messageActions()?.template; as actionsTpl) {\n @if (m | rtChatMessageHasActions: messageHasActions()) {\n <div rtElem=\"message-actions\" qa-dataid=\"chat-message-actions\">\n <rt-menu>\n <ng-container\n [ngTemplateOutlet]=\"actionsTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: m }\" />\n </rt-menu>\n </div>\n }\n }\n <div rtElem=\"message\" [rtMod]=\"{ own: m.own, deleted: m.deleted }\">\n <p rtElem=\"message-author\" qa-dataid=\"chat-message-author\">{{ m.author }}</p>\n @if (m.deleted) {\n <p rtElem=\"message-text\" qa-dataid=\"chat-message-deleted\" rtMod=\"deleted\">\n {{ t().chatMessageDeleted }}\n </p>\n } @else {\n @if (m.delta) {\n <rt-delta-view rtElem=\"message-text\" qa-dataid=\"chat-message-text\" [delta]=\"m.delta\" />\n } @else {\n <p rtElem=\"message-text\" qa-dataid=\"chat-message-text\">{{ m.text }}</p>\n }\n @if (m.attachments?.length) {\n @for (att of m.attachments; track att.id) {\n <span rtElem=\"message-attachment\" qa-dataid=\"chat-message-attachment\">\n <rt-file-card\n showDownload\n size=\"sm\"\n [name]=\"att.name || attachmentFallbackName()\"\n (download)=\"downloadAttachment.emit(att)\" />\n </span>\n }\n } @else if (m.fileUrl) {\n <span rtElem=\"message-attachment\" qa-dataid=\"chat-message-attachment\">\n <rt-file-card\n showDownload\n size=\"sm\"\n [name]=\"m.fileName || attachmentFallbackName()\"\n (download)=\"downloadFile.emit(m)\" />\n </span>\n }\n @if (m.deepLink) {\n <span rtElem=\"message-deeplink\">\n <a\n rtButton\n qa-dataid=\"chat-message-deeplink\"\n appearance=\"outlined\"\n icon=\"send\"\n rel=\"noopener\"\n target=\"_blank\"\n [href]=\"m.deepLink\"\n [label]=\"m.deepLinkLabel || t().uiSubscribeNotifications\"\n [attr.aria-label]=\"m.deepLinkLabel || t().uiSubscribeNotifications\"></a>\n </span>\n }\n }\n <span rtElem=\"message-footer\">\n <span rtElem=\"message-date\" qa-dataid=\"chat-message-date\">\n {{ m.createdAt | date: 'short' : undefined : locale() }}\n </span>\n @if (m.status) {\n <span\n rtElem=\"message-status\"\n qa-dataid=\"chat-message-status\"\n [attr.data-status]=\"m.status\"\n [rtMod]=\"{ status: m.status }\"\n [attr.aria-label]=\"m.status | rtChatStatusLabel\">\n @if (m.status === MessageStatus.Failed) {\n <rt-icon rtElem=\"status-icon\" name=\"exclamation-circle\" size=\"sm\" />\n <rt-icon-button\n qa-dataid=\"chat-message-retry\"\n icon=\"sync\"\n variant=\"ghost\"\n [ariaLabel]=\"t().chatRetryAria\"\n [tooltip]=\"t().chatRetryTooltip\"\n (clicked)=\"retryMessage.emit(m)\" />\n } @else {\n <!-- Часы, пока сообщение в пути; дальше двойной чек, цвет которого\n (серый/синий) различает доставлено и прочитано. -->\n <rt-icon\n rtElem=\"status-icon\"\n size=\"sm\"\n [name]=\"m.status === MessageStatus.Sending ? 'clock' : 'check-double'\" />\n }\n </span>\n }\n </span>\n </div>\n </div>\n } @empty {\n <rt-note qa-dataid=\"chat-empty-thread\">{{ t().chatEmptyThread }}</rt-note>\n }\n </div>\n }\n @if (!loading() && fetching()) {\n <div rtElem=\"thread-overlay\" qa-dataid=\"chat-thread-overlay\" role=\"status\" [attr.aria-label]=\"t().chatRefreshAria\">\n <rt-spinner diameter=\"32\" />\n </div>\n }\n <div rtElem=\"typing\" qa-dataid=\"chat-typing\" aria-live=\"polite\" [rtMod]=\"{ shown: typingText() !== '' }\">\n {{ typingText() }}\n </div>\n </div>\n\n @if (canReply()) {\n @if (richComposer()) {\n <rt-message-composer\n rtElem=\"composer-rich\"\n qa-dataid=\"chat-composer-rich\"\n [placeholder]=\"placeholder()\"\n [accept]=\"accept()\"\n [attachments]=\"attachments()\"\n [formatting]=\"formatting()\"\n [toolbar]=\"formattingToolbar()\"\n [sending]=\"sending()\"\n [disabled]=\"loading() || fetching()\"\n [droppedFiles]=\"droppedFiles()\"\n (submitted)=\"onComposerSubmit($event)\"\n (input)=\"onReplyInput($event)\" />\n } @else {\n <form\n rtElem=\"composer\"\n qa-dataid=\"chat-composer\"\n [formGroup]=\"replyForm\"\n (ngSubmit)=\"onSubmit()\"\n (input)=\"onReplyInput($event)\">\n @if (attachments() && selectedFiles().length > 0) {\n <div rtElem=\"composer-files\" qa-dataid=\"chat-composer-files\">\n @for (file of selectedFiles(); track $index) {\n <rt-file-card\n showRemove\n qa-dataid=\"chat-composer-file\"\n size=\"sm\"\n [name]=\"file.name\"\n [sizeBytes]=\"file.size\"\n [disabled]=\"composerDisabled()\"\n (removed)=\"removeFile($index)\" />\n }\n </div>\n }\n <div rtElem=\"composer-field\" [rtMod]=\"{ attach: attachments() }\">\n @if (attachments()) {\n <input\n #composerFileEl\n qa-dataid=\"chat-composer-file-input\"\n type=\"file\"\n hidden\n multiple\n [attr.accept]=\"accept() || null\"\n (change)=\"onFilesPicked($event)\" />\n <rt-icon-button\n qa-dataid=\"chat-composer-attach\"\n icon=\"paperclip\"\n variant=\"ghost\"\n [ariaLabel]=\"t().chatAttachAria\"\n [tooltip]=\"t().chatAttachTooltip\"\n [disabled]=\"composerDisabled()\"\n (clicked)=\"openFilePicker()\" />\n }\n <textarea\n rtElem=\"composer-input\"\n qa-dataid=\"chat-composer-input\"\n cdkTextareaAutosize\n formControlName=\"message\"\n cdkAutosizeMinRows=\"1\"\n cdkAutosizeMaxRows=\"4\"\n [attr.aria-label]=\"t().chatComposerAria\"\n [placeholder]=\"placeholder()\"\n (keydown)=\"onComposerKeydown($event)\"></textarea>\n </div>\n <button\n rtButton\n rtElem=\"composer-send\"\n qa-dataid=\"chat-composer-send\"\n type=\"submit\"\n theme=\"primary\"\n appearance=\"filled\"\n [label]=\"t().chatSendLabel\"\n [attr.aria-label]=\"t().chatSendAria\"\n [loading]=\"sending()\"\n [disabled]=\"!canSend()\"></button>\n </form>\n }\n } @else if (replyBlockReason()) {\n <rt-note rtElem=\"reply-block\" qa-dataid=\"chat-reply-block\">{{ replyBlockReason() }}</rt-note>\n }\n\n <!-- Слот доменных действий потребителя под композером: в fullscreen\n попадает внутрь overlay, поэтому кнопки остаются видны. -->\n <div rtElem=\"footer-slot\" qa-dataid=\"chat-footer\" [rtMod]=\"{ blocked: fetching() }\" [attr.inert]=\"fetching() ? '' : null\">\n <ng-content select=\"[rtChatFooter]\" />\n </div>\n }\n </rt-file-drop>\n</ng-container>\n",
1420
- styles: ["@layer rt-kit.components{.rt-chat{--rt-chat-bubble-radius: var(--rt-radius-lg);--rt-chat-message-border-width: var(--rt-border-width-thin);--rt-chat-transition-duration: var(--rt-duration-fast);--rt-chat-typing-radius: var(--rt-radius-full);--rt-chat-typing-shadow: var(--rt-shadow-sm);display:flex;flex-direction:column}.rt-chat>.rt-file-drop{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--rt-space-4);min-block-size:0}.rt-chat--expanded{position:fixed;z-index:var(--rt-z-modal);box-sizing:border-box;padding:var(--rt-space-md);background:var(--rt-color-bg-surface);inset:0}.rt-chat--expanded>.rt-file-drop{block-size:100%}@container rt-place (width > 1440px){.rt-chat--expanded>.rt-file-drop{inline-size:1000px;margin-inline:auto;max-inline-size:100%}}.rt-chat--expanded .rt-chat__thread-viewport{flex:1 1 auto;min-block-size:0}.rt-chat--expanded .rt-chat__thread{max-height:none}.rt-chat--fill{block-size:100%}.rt-chat--fill .rt-chat__thread-viewport{flex:1 1 auto;min-block-size:0}.rt-chat--fill .rt-chat__thread{max-height:none}.rt-chat__title{margin:0;color:var(--rt-color-text-primary);font-family:var(--rt-font-family-sans);font-size:var(--rt-text-md);font-weight:var(--rt-font-weight-bold);letter-spacing:var(--rt-letter-spacing-wide);line-height:var(--rt-leading-snug)}.rt-chat__thread-viewport{position:relative;display:flex;flex-direction:column;contain:inline-size;min-block-size:0}.rt-chat__thread{display:flex;max-height:280px;flex:1 1 auto;flex-direction:column;gap:var(--rt-space-4);min-block-size:0;overflow-y:auto}.rt-chat__thread--frozen{overflow-y:hidden}.rt-chat__thread-overlay{position:absolute;z-index:1;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--rt-color-bg-surface) 60%,transparent);inset:0}.rt-chat__typing{position:absolute;z-index:1;bottom:var(--rt-space-1);left:var(--rt-space-2);box-sizing:border-box;color:var(--rt-color-text-muted);font-size:var(--rt-text-xs);max-inline-size:calc(100% - 2 * var(--rt-space-2));overflow-wrap:anywhere;pointer-events:none}.rt-chat__typing--shown{padding:var(--rt-space-0-5) var(--rt-space-2);border-radius:var(--rt-chat-typing-radius);background:var(--rt-color-bg-surface);box-shadow:var(--rt-chat-typing-shadow)}.rt-chat__footer-slot{display:flex;flex-direction:column}.rt-chat__footer-slot:empty{display:none}.rt-chat__footer-slot--blocked{opacity:.6;pointer-events:none}.rt-chat__message-row{display:flex;max-width:min(80%,480px);align-items:center;align-self:flex-start;gap:var(--rt-space-2)}.rt-chat__message-row--own{align-self:flex-end}.rt-chat__message-row:hover .rt-chat__message-delete,.rt-chat__message-row:focus-within .rt-chat__message-delete,.rt-chat__message-row:hover .rt-chat__message-actions,.rt-chat__message-row:focus-within .rt-chat__message-actions{opacity:1;pointer-events:auto}.rt-chat__message{display:flex;min-width:0;flex:0 1 auto;flex-direction:column;padding:var(--rt-space-2);border-radius:var(--rt-chat-bubble-radius) var(--rt-chat-bubble-radius) var(--rt-chat-bubble-radius) 0;background:var(--rt-chat-bubble-in-bg);gap:var(--rt-space-2)}.rt-chat__message--own{border-radius:var(--rt-chat-bubble-radius) var(--rt-chat-bubble-radius) 0 var(--rt-chat-bubble-radius);background:var(--rt-chat-bubble-own-bg)}.rt-chat__message--deleted{border:var(--rt-chat-message-border-width) dashed var(--rt-color-border-default);background:transparent}.rt-chat__message-author{margin:0;color:var(--rt-chat-bubble-in-author);font-size:var(--rt-text-2xs);font-weight:var(--rt-font-weight-bold);letter-spacing:var(--rt-letter-spacing-wide);line-height:var(--rt-leading-snug)}.rt-chat__message--own .rt-chat__message-author{color:var(--rt-chat-bubble-own-author)}.rt-chat__message-text{margin:0;color:var(--rt-color-text-primary);font-size:var(--rt-text-xs);font-weight:var(--rt-font-weight-regular);line-height:var(--rt-leading-snug);white-space:pre-wrap}.rt-chat__message-text--deleted{color:var(--rt-color-text-muted);font-style:italic}.rt-chat__message-delete,.rt-chat__message-actions{flex:0 0 auto;opacity:0;pointer-events:none;transition:opacity var(--rt-chat-transition-duration) ease}.rt-chat__message-attachment{display:block}.rt-chat__message-footer{display:flex;align-items:center;gap:var(--rt-space-1)}.rt-chat__message-date{color:var(--rt-chat-bubble-date);font-size:var(--rt-text-xs);line-height:var(--rt-leading-none)}.rt-chat__message-status{display:inline-flex;align-items:center;color:var(--rt-color-text-muted);gap:var(--rt-space-0-5)}.rt-chat__message-status--status--read{color:var(--rt-chat-status-read)}.rt-chat__message-status--status--failed{color:var(--rt-color-state-danger)}.rt-chat__status-icon{font-size:var(--rt-text-sm)}.rt-chat__message-skeleton{display:flex;max-width:min(80%,480px);flex-direction:column;align-self:flex-start;padding:var(--rt-space-2);border-radius:var(--rt-chat-bubble-radius) var(--rt-chat-bubble-radius) var(--rt-chat-bubble-radius) 0;background:var(--rt-chat-bubble-in-bg);gap:var(--rt-space-1)}.rt-chat__message-skeleton--own{align-self:flex-end;border-radius:var(--rt-chat-bubble-radius) var(--rt-chat-bubble-radius) 0 var(--rt-chat-bubble-radius);background:var(--rt-chat-bubble-own-bg)}.rt-chat__skeleton-line{display:block}.rt-chat__message-deeplink{display:flex;justify-content:flex-start;margin-top:var(--rt-space-1)}.rt-chat__composer,.rt-chat__composer-rich,.rt-chat__reply-block{margin-top:var(--rt-space-4)}.rt-chat__composer{display:flex;flex-direction:column;gap:var(--rt-space-2)}.rt-chat__composer-files{display:flex;flex-wrap:wrap;gap:var(--rt-space-2)}.rt-chat__composer-field{display:flex;box-sizing:border-box;align-items:center;border:var(--rt-input-border-width) solid var(--rt-input-color-border);border-radius:var(--rt-input-radius);background-color:var(--rt-input-color-bg);gap:var(--rt-space-1);min-block-size:var(--rt-input-height);padding-inline:var(--rt-input-padding-x);transition:var(--rt-input-transition)}.rt-chat__composer-field:focus-within{border-color:var(--rt-input-color-border-focus);box-shadow:var(--rt-input-focus-shadow)}.rt-chat__composer-field--attach{padding-inline-start:var(--rt-space-1)}.rt-chat__composer-input{box-sizing:content-box;flex:1 1 auto;border:0;background:transparent;color:var(--rt-input-color-text);font-family:inherit;font-size:var(--rt-input-font-size);inline-size:100%;line-height:var(--rt-textarea-line-height);padding-block:var(--rt-space-2);resize:none}.rt-chat__composer-input::placeholder{color:var(--rt-input-color-text-placeholder);opacity:1}.rt-chat__composer-input:focus-visible{outline:none}.rt-chat__composer-input:disabled{cursor:not-allowed}.rt-chat__composer-send{inline-size:100%}}\n"],
1420
+ styles: ["@layer rt-kit.components{.rt-chat{--rt-chat-bubble-radius: var(--rt-radius-lg);--rt-chat-message-border-width: var(--rt-border-width-thin);--rt-chat-transition-duration: var(--rt-duration-fast);--rt-chat-typing-radius: var(--rt-radius-full);--rt-chat-typing-shadow: var(--rt-shadow-sm);display:flex;flex-direction:column}.rt-chat>.rt-file-drop{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--rt-space-4);min-block-size:0}.rt-chat--expanded{position:fixed;z-index:var(--rt-z-modal);box-sizing:border-box;padding:var(--rt-space-md);background:var(--rt-color-bg-surface);inset:0}.rt-chat--expanded>.rt-file-drop{block-size:100%}@container rt-place (width > 1440px){.rt-chat--expanded>.rt-file-drop{inline-size:1000px;margin-inline:auto;max-inline-size:100%}}@media(width>1440px){:where(html:not(:has(.rt-container))) .rt-chat--expanded>.rt-file-drop{inline-size:1000px;margin-inline:auto;max-inline-size:100%}}.rt-chat--expanded .rt-chat__thread-viewport{flex:1 1 auto;min-block-size:0}.rt-chat--expanded .rt-chat__thread{max-height:none}.rt-chat--fill{block-size:100%}.rt-chat--fill .rt-chat__thread-viewport{flex:1 1 auto;min-block-size:0}.rt-chat--fill .rt-chat__thread{max-height:none}.rt-chat__title{margin:0;color:var(--rt-color-text-primary);font-family:var(--rt-font-family-sans);font-size:var(--rt-text-md);font-weight:var(--rt-font-weight-bold);letter-spacing:var(--rt-letter-spacing-wide);line-height:var(--rt-leading-snug)}.rt-chat__thread-viewport{position:relative;display:flex;flex-direction:column;contain:inline-size;min-block-size:0}.rt-chat__thread{display:flex;max-height:280px;flex:1 1 auto;flex-direction:column;gap:var(--rt-space-4);min-block-size:0;overflow-y:auto}.rt-chat__thread--frozen{overflow-y:hidden}.rt-chat__thread-overlay{position:absolute;z-index:1;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--rt-color-bg-surface) 60%,transparent);inset:0}.rt-chat__typing{position:absolute;z-index:1;bottom:var(--rt-space-1);left:var(--rt-space-2);box-sizing:border-box;color:var(--rt-color-text-muted);font-size:var(--rt-text-xs);max-inline-size:calc(100% - 2 * var(--rt-space-2));overflow-wrap:anywhere;pointer-events:none}.rt-chat__typing--shown{padding:var(--rt-space-0-5) var(--rt-space-2);border-radius:var(--rt-chat-typing-radius);background:var(--rt-color-bg-surface);box-shadow:var(--rt-chat-typing-shadow)}.rt-chat__footer-slot{display:flex;flex-direction:column}.rt-chat__footer-slot:empty{display:none}.rt-chat__footer-slot--blocked{opacity:.6;pointer-events:none}.rt-chat__message-row{display:flex;max-width:min(80%,480px);align-items:center;align-self:flex-start;gap:var(--rt-space-2)}.rt-chat__message-row--own{align-self:flex-end}.rt-chat__message-row:hover .rt-chat__message-delete,.rt-chat__message-row:focus-within .rt-chat__message-delete,.rt-chat__message-row:hover .rt-chat__message-actions,.rt-chat__message-row:focus-within .rt-chat__message-actions{opacity:1;pointer-events:auto}.rt-chat__message{display:flex;min-width:0;flex:0 1 auto;flex-direction:column;padding:var(--rt-space-2);border-radius:var(--rt-chat-bubble-radius) var(--rt-chat-bubble-radius) var(--rt-chat-bubble-radius) 0;background:var(--rt-chat-bubble-in-bg);gap:var(--rt-space-2)}.rt-chat__message--own{border-radius:var(--rt-chat-bubble-radius) var(--rt-chat-bubble-radius) 0 var(--rt-chat-bubble-radius);background:var(--rt-chat-bubble-own-bg)}.rt-chat__message--deleted{border:var(--rt-chat-message-border-width) dashed var(--rt-color-border-default);background:transparent}.rt-chat__message-author{margin:0;color:var(--rt-chat-bubble-in-author);font-size:var(--rt-text-2xs);font-weight:var(--rt-font-weight-bold);letter-spacing:var(--rt-letter-spacing-wide);line-height:var(--rt-leading-snug)}.rt-chat__message--own .rt-chat__message-author{color:var(--rt-chat-bubble-own-author)}.rt-chat__message-text{margin:0;color:var(--rt-color-text-primary);font-size:var(--rt-text-xs);font-weight:var(--rt-font-weight-regular);line-height:var(--rt-leading-snug);white-space:pre-wrap}.rt-chat__message-text--deleted{color:var(--rt-color-text-muted);font-style:italic}.rt-chat__message-delete,.rt-chat__message-actions{flex:0 0 auto;opacity:0;pointer-events:none;transition:opacity var(--rt-chat-transition-duration) ease}.rt-chat__message-attachment{display:block}.rt-chat__message-footer{display:flex;align-items:center;gap:var(--rt-space-1)}.rt-chat__message-date{color:var(--rt-chat-bubble-date);font-size:var(--rt-text-xs);line-height:var(--rt-leading-none)}.rt-chat__message-status{display:inline-flex;align-items:center;color:var(--rt-color-text-muted);gap:var(--rt-space-0-5)}.rt-chat__message-status--status--read{color:var(--rt-chat-status-read)}.rt-chat__message-status--status--failed{color:var(--rt-color-state-danger)}.rt-chat__status-icon{font-size:var(--rt-text-sm)}.rt-chat__message-skeleton{display:flex;max-width:min(80%,480px);flex-direction:column;align-self:flex-start;padding:var(--rt-space-2);border-radius:var(--rt-chat-bubble-radius) var(--rt-chat-bubble-radius) var(--rt-chat-bubble-radius) 0;background:var(--rt-chat-bubble-in-bg);gap:var(--rt-space-1)}.rt-chat__message-skeleton--own{align-self:flex-end;border-radius:var(--rt-chat-bubble-radius) var(--rt-chat-bubble-radius) 0 var(--rt-chat-bubble-radius);background:var(--rt-chat-bubble-own-bg)}.rt-chat__skeleton-line{display:block}.rt-chat__message-deeplink{display:flex;justify-content:flex-start;margin-top:var(--rt-space-1)}.rt-chat__composer,.rt-chat__composer-rich,.rt-chat__reply-block{margin-top:var(--rt-space-4)}.rt-chat__composer{display:flex;flex-direction:column;gap:var(--rt-space-2)}.rt-chat__composer-files{display:flex;flex-wrap:wrap;gap:var(--rt-space-2)}.rt-chat__composer-field{display:flex;box-sizing:border-box;align-items:center;border:var(--rt-input-border-width) solid var(--rt-input-color-border);border-radius:var(--rt-input-radius);background-color:var(--rt-input-color-bg);gap:var(--rt-space-1);min-block-size:var(--rt-input-height);padding-inline:var(--rt-input-padding-x);transition:var(--rt-input-transition)}.rt-chat__composer-field:focus-within{border-color:var(--rt-input-color-border-focus);box-shadow:var(--rt-input-focus-shadow)}.rt-chat__composer-field--attach{padding-inline-start:var(--rt-space-1)}.rt-chat__composer-input{box-sizing:content-box;flex:1 1 auto;border:0;background:transparent;color:var(--rt-input-color-text);font-family:inherit;font-size:var(--rt-input-font-size);inline-size:100%;line-height:var(--rt-textarea-line-height);padding-block:var(--rt-space-2);resize:none}.rt-chat__composer-input::placeholder{color:var(--rt-input-color-text-placeholder);opacity:1}.rt-chat__composer-input:focus-visible{outline:none}.rt-chat__composer-input:disabled{cursor:not-allowed}.rt-chat__composer-send{inline-size:100%}}\n"],
1421
1421
  dependencies: [
1422
1422
  {
1423
1423
  kind: "directive",
@@ -1766,7 +1766,7 @@ i0.ɵɵngDeclareClassMetadata({
1766
1766
  "[class.rt-chat--fill]": "fill()"
1767
1767
  },
1768
1768
  template: "<ng-container rtBlock=\"rt-chat\">\n <rt-file-drop\n [overlayLabel]=\"t().chatDropOverlay\"\n [accept]=\"accept()\"\n [disabled]=\"dropDisabled()\"\n (filesDropped)=\"onFilesDropped($event)\">\n @if (showHeader()) {\n <rt-toolbar rtElem=\"header\" dense>\n <ng-template rtToolbarLeft>\n <h3 rtElem=\"title\" qa-dataid=\"chat-title\">{{ title() }}</h3>\n </ng-template>\n <ng-template rtToolbarRight>\n @if (showRefresh()) {\n <rt-icon-button\n qa-dataid=\"chat-refresh\"\n icon=\"sync\"\n variant=\"ghost\"\n [ariaLabel]=\"t().chatRefreshAria\"\n [tooltip]=\"t().chatRefreshTooltip\"\n [loading]=\"fetching()\"\n [disabled]=\"!hasThread()\"\n (clicked)=\"refresh.emit()\" />\n }\n @if (showExpand()) {\n <rt-icon-button\n qa-dataid=\"chat-expand\"\n variant=\"ghost\"\n [icon]=\"expanded() ? 'window-minimize' : 'window-maximize'\"\n [ariaLabel]=\"expandLabel()\"\n [tooltip]=\"expandLabel()\"\n (clicked)=\"toggleExpand()\" />\n }\n <ng-content select=\"[rtChatHeaderActions]\" />\n </ng-template>\n </rt-toolbar>\n }\n\n @if (!hasThread()) {\n <rt-note qa-dataid=\"chat-empty-hint\">{{ emptyHint() }}</rt-note>\n } @else {\n <div rtElem=\"thread-viewport\" qa-dataid=\"chat-thread-viewport\">\n @if (loading()) {\n <!-- Скелетон повторяет структуру сообщения (автор → текст → дата),\n чередуя выравнивание, чтобы тред не «прыгал» при подмене на реальные. -->\n <div rtElem=\"thread\" qa-dataid=\"chat-thread-skeleton\">\n @for (row of skeletonRows; track row; let even = $even) {\n <div rtElem=\"message-skeleton\" qa-dataid=\"chat-message-skeleton\" [rtMod]=\"{ own: even }\">\n <rt-skeleton-wrapper rtElem=\"skeleton-line\" width=\"72px\" height=\"10px\" [isLoading]=\"true\" />\n <rt-skeleton-wrapper rtElem=\"skeleton-line\" width=\"100%\" height=\"13px\" [isLoading]=\"true\" />\n <rt-skeleton-wrapper rtElem=\"skeleton-line\" width=\"55%\" height=\"13px\" [isLoading]=\"true\" />\n <rt-skeleton-wrapper rtElem=\"skeleton-line\" width=\"56px\" height=\"10px\" [isLoading]=\"true\" />\n </div>\n }\n </div>\n } @else {\n <div\n #thread\n rtElem=\"thread\"\n qa-dataid=\"chat-thread\"\n [rtMod]=\"{ frozen: fetching() }\"\n [attr.aria-busy]=\"fetching() ? 'true' : null\"\n (scroll)=\"onThreadScroll()\">\n @for (m of messages(); track m.id) {\n <div\n rtElem=\"message-row\"\n qa-dataid=\"chat-message\"\n [attr.data-own]=\"m.own\"\n [attr.data-status]=\"m.status || null\"\n [attr.data-deleted]=\"m.deleted || null\"\n [rtMod]=\"{ own: m.own }\">\n @if (m.canDelete && !m.deleted) {\n @if (m.deleteConfirm) {\n <rt-icon-button\n rtElem=\"message-delete\"\n qa-dataid=\"chat-message-delete\"\n icon=\"trash\"\n variant=\"ghost\"\n [ariaLabel]=\"t().chatDeleteAria\"\n [tooltip]=\"t().chatDeleteTooltip\"\n [rtConfirm]=\"m.deleteConfirm\"\n (confirmed)=\"deleteMessage.emit(m)\" />\n } @else {\n <rt-icon-button\n rtElem=\"message-delete\"\n qa-dataid=\"chat-message-delete\"\n icon=\"trash\"\n variant=\"ghost\"\n [ariaLabel]=\"t().chatDeleteAria\"\n [tooltip]=\"t().chatDeleteTooltip\"\n (clicked)=\"deleteMessage.emit(m)\" />\n }\n }\n @if (messageActions()?.template; as actionsTpl) {\n @if (m | rtChatMessageHasActions: messageHasActions()) {\n <div rtElem=\"message-actions\" qa-dataid=\"chat-message-actions\">\n <rt-menu>\n <ng-container\n [ngTemplateOutlet]=\"actionsTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: m }\" />\n </rt-menu>\n </div>\n }\n }\n <div rtElem=\"message\" [rtMod]=\"{ own: m.own, deleted: m.deleted }\">\n <p rtElem=\"message-author\" qa-dataid=\"chat-message-author\">{{ m.author }}</p>\n @if (m.deleted) {\n <p rtElem=\"message-text\" qa-dataid=\"chat-message-deleted\" rtMod=\"deleted\">\n {{ t().chatMessageDeleted }}\n </p>\n } @else {\n @if (m.delta) {\n <rt-delta-view rtElem=\"message-text\" qa-dataid=\"chat-message-text\" [delta]=\"m.delta\" />\n } @else {\n <p rtElem=\"message-text\" qa-dataid=\"chat-message-text\">{{ m.text }}</p>\n }\n @if (m.attachments?.length) {\n @for (att of m.attachments; track att.id) {\n <span rtElem=\"message-attachment\" qa-dataid=\"chat-message-attachment\">\n <rt-file-card\n showDownload\n size=\"sm\"\n [name]=\"att.name || attachmentFallbackName()\"\n (download)=\"downloadAttachment.emit(att)\" />\n </span>\n }\n } @else if (m.fileUrl) {\n <span rtElem=\"message-attachment\" qa-dataid=\"chat-message-attachment\">\n <rt-file-card\n showDownload\n size=\"sm\"\n [name]=\"m.fileName || attachmentFallbackName()\"\n (download)=\"downloadFile.emit(m)\" />\n </span>\n }\n @if (m.deepLink) {\n <span rtElem=\"message-deeplink\">\n <a\n rtButton\n qa-dataid=\"chat-message-deeplink\"\n appearance=\"outlined\"\n icon=\"send\"\n rel=\"noopener\"\n target=\"_blank\"\n [href]=\"m.deepLink\"\n [label]=\"m.deepLinkLabel || t().uiSubscribeNotifications\"\n [attr.aria-label]=\"m.deepLinkLabel || t().uiSubscribeNotifications\"></a>\n </span>\n }\n }\n <span rtElem=\"message-footer\">\n <span rtElem=\"message-date\" qa-dataid=\"chat-message-date\">\n {{ m.createdAt | date: 'short' : undefined : locale() }}\n </span>\n @if (m.status) {\n <span\n rtElem=\"message-status\"\n qa-dataid=\"chat-message-status\"\n [attr.data-status]=\"m.status\"\n [rtMod]=\"{ status: m.status }\"\n [attr.aria-label]=\"m.status | rtChatStatusLabel\">\n @if (m.status === MessageStatus.Failed) {\n <rt-icon rtElem=\"status-icon\" name=\"exclamation-circle\" size=\"sm\" />\n <rt-icon-button\n qa-dataid=\"chat-message-retry\"\n icon=\"sync\"\n variant=\"ghost\"\n [ariaLabel]=\"t().chatRetryAria\"\n [tooltip]=\"t().chatRetryTooltip\"\n (clicked)=\"retryMessage.emit(m)\" />\n } @else {\n <!-- Часы, пока сообщение в пути; дальше двойной чек, цвет которого\n (серый/синий) различает доставлено и прочитано. -->\n <rt-icon\n rtElem=\"status-icon\"\n size=\"sm\"\n [name]=\"m.status === MessageStatus.Sending ? 'clock' : 'check-double'\" />\n }\n </span>\n }\n </span>\n </div>\n </div>\n } @empty {\n <rt-note qa-dataid=\"chat-empty-thread\">{{ t().chatEmptyThread }}</rt-note>\n }\n </div>\n }\n @if (!loading() && fetching()) {\n <div rtElem=\"thread-overlay\" qa-dataid=\"chat-thread-overlay\" role=\"status\" [attr.aria-label]=\"t().chatRefreshAria\">\n <rt-spinner diameter=\"32\" />\n </div>\n }\n <div rtElem=\"typing\" qa-dataid=\"chat-typing\" aria-live=\"polite\" [rtMod]=\"{ shown: typingText() !== '' }\">\n {{ typingText() }}\n </div>\n </div>\n\n @if (canReply()) {\n @if (richComposer()) {\n <rt-message-composer\n rtElem=\"composer-rich\"\n qa-dataid=\"chat-composer-rich\"\n [placeholder]=\"placeholder()\"\n [accept]=\"accept()\"\n [attachments]=\"attachments()\"\n [formatting]=\"formatting()\"\n [toolbar]=\"formattingToolbar()\"\n [sending]=\"sending()\"\n [disabled]=\"loading() || fetching()\"\n [droppedFiles]=\"droppedFiles()\"\n (submitted)=\"onComposerSubmit($event)\"\n (input)=\"onReplyInput($event)\" />\n } @else {\n <form\n rtElem=\"composer\"\n qa-dataid=\"chat-composer\"\n [formGroup]=\"replyForm\"\n (ngSubmit)=\"onSubmit()\"\n (input)=\"onReplyInput($event)\">\n @if (attachments() && selectedFiles().length > 0) {\n <div rtElem=\"composer-files\" qa-dataid=\"chat-composer-files\">\n @for (file of selectedFiles(); track $index) {\n <rt-file-card\n showRemove\n qa-dataid=\"chat-composer-file\"\n size=\"sm\"\n [name]=\"file.name\"\n [sizeBytes]=\"file.size\"\n [disabled]=\"composerDisabled()\"\n (removed)=\"removeFile($index)\" />\n }\n </div>\n }\n <div rtElem=\"composer-field\" [rtMod]=\"{ attach: attachments() }\">\n @if (attachments()) {\n <input\n #composerFileEl\n qa-dataid=\"chat-composer-file-input\"\n type=\"file\"\n hidden\n multiple\n [attr.accept]=\"accept() || null\"\n (change)=\"onFilesPicked($event)\" />\n <rt-icon-button\n qa-dataid=\"chat-composer-attach\"\n icon=\"paperclip\"\n variant=\"ghost\"\n [ariaLabel]=\"t().chatAttachAria\"\n [tooltip]=\"t().chatAttachTooltip\"\n [disabled]=\"composerDisabled()\"\n (clicked)=\"openFilePicker()\" />\n }\n <textarea\n rtElem=\"composer-input\"\n qa-dataid=\"chat-composer-input\"\n cdkTextareaAutosize\n formControlName=\"message\"\n cdkAutosizeMinRows=\"1\"\n cdkAutosizeMaxRows=\"4\"\n [attr.aria-label]=\"t().chatComposerAria\"\n [placeholder]=\"placeholder()\"\n (keydown)=\"onComposerKeydown($event)\"></textarea>\n </div>\n <button\n rtButton\n rtElem=\"composer-send\"\n qa-dataid=\"chat-composer-send\"\n type=\"submit\"\n theme=\"primary\"\n appearance=\"filled\"\n [label]=\"t().chatSendLabel\"\n [attr.aria-label]=\"t().chatSendAria\"\n [loading]=\"sending()\"\n [disabled]=\"!canSend()\"></button>\n </form>\n }\n } @else if (replyBlockReason()) {\n <rt-note rtElem=\"reply-block\" qa-dataid=\"chat-reply-block\">{{ replyBlockReason() }}</rt-note>\n }\n\n <!-- Слот доменных действий потребителя под композером: в fullscreen\n попадает внутрь overlay, поэтому кнопки остаются видны. -->\n <div rtElem=\"footer-slot\" qa-dataid=\"chat-footer\" [rtMod]=\"{ blocked: fetching() }\" [attr.inert]=\"fetching() ? '' : null\">\n <ng-content select=\"[rtChatFooter]\" />\n </div>\n }\n </rt-file-drop>\n</ng-container>\n",
1769
- styles: ["@layer rt-kit.components{.rt-chat{--rt-chat-bubble-radius: var(--rt-radius-lg);--rt-chat-message-border-width: var(--rt-border-width-thin);--rt-chat-transition-duration: var(--rt-duration-fast);--rt-chat-typing-radius: var(--rt-radius-full);--rt-chat-typing-shadow: var(--rt-shadow-sm);display:flex;flex-direction:column}.rt-chat>.rt-file-drop{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--rt-space-4);min-block-size:0}.rt-chat--expanded{position:fixed;z-index:var(--rt-z-modal);box-sizing:border-box;padding:var(--rt-space-md);background:var(--rt-color-bg-surface);inset:0}.rt-chat--expanded>.rt-file-drop{block-size:100%}@container rt-place (width > 1440px){.rt-chat--expanded>.rt-file-drop{inline-size:1000px;margin-inline:auto;max-inline-size:100%}}.rt-chat--expanded .rt-chat__thread-viewport{flex:1 1 auto;min-block-size:0}.rt-chat--expanded .rt-chat__thread{max-height:none}.rt-chat--fill{block-size:100%}.rt-chat--fill .rt-chat__thread-viewport{flex:1 1 auto;min-block-size:0}.rt-chat--fill .rt-chat__thread{max-height:none}.rt-chat__title{margin:0;color:var(--rt-color-text-primary);font-family:var(--rt-font-family-sans);font-size:var(--rt-text-md);font-weight:var(--rt-font-weight-bold);letter-spacing:var(--rt-letter-spacing-wide);line-height:var(--rt-leading-snug)}.rt-chat__thread-viewport{position:relative;display:flex;flex-direction:column;contain:inline-size;min-block-size:0}.rt-chat__thread{display:flex;max-height:280px;flex:1 1 auto;flex-direction:column;gap:var(--rt-space-4);min-block-size:0;overflow-y:auto}.rt-chat__thread--frozen{overflow-y:hidden}.rt-chat__thread-overlay{position:absolute;z-index:1;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--rt-color-bg-surface) 60%,transparent);inset:0}.rt-chat__typing{position:absolute;z-index:1;bottom:var(--rt-space-1);left:var(--rt-space-2);box-sizing:border-box;color:var(--rt-color-text-muted);font-size:var(--rt-text-xs);max-inline-size:calc(100% - 2 * var(--rt-space-2));overflow-wrap:anywhere;pointer-events:none}.rt-chat__typing--shown{padding:var(--rt-space-0-5) var(--rt-space-2);border-radius:var(--rt-chat-typing-radius);background:var(--rt-color-bg-surface);box-shadow:var(--rt-chat-typing-shadow)}.rt-chat__footer-slot{display:flex;flex-direction:column}.rt-chat__footer-slot:empty{display:none}.rt-chat__footer-slot--blocked{opacity:.6;pointer-events:none}.rt-chat__message-row{display:flex;max-width:min(80%,480px);align-items:center;align-self:flex-start;gap:var(--rt-space-2)}.rt-chat__message-row--own{align-self:flex-end}.rt-chat__message-row:hover .rt-chat__message-delete,.rt-chat__message-row:focus-within .rt-chat__message-delete,.rt-chat__message-row:hover .rt-chat__message-actions,.rt-chat__message-row:focus-within .rt-chat__message-actions{opacity:1;pointer-events:auto}.rt-chat__message{display:flex;min-width:0;flex:0 1 auto;flex-direction:column;padding:var(--rt-space-2);border-radius:var(--rt-chat-bubble-radius) var(--rt-chat-bubble-radius) var(--rt-chat-bubble-radius) 0;background:var(--rt-chat-bubble-in-bg);gap:var(--rt-space-2)}.rt-chat__message--own{border-radius:var(--rt-chat-bubble-radius) var(--rt-chat-bubble-radius) 0 var(--rt-chat-bubble-radius);background:var(--rt-chat-bubble-own-bg)}.rt-chat__message--deleted{border:var(--rt-chat-message-border-width) dashed var(--rt-color-border-default);background:transparent}.rt-chat__message-author{margin:0;color:var(--rt-chat-bubble-in-author);font-size:var(--rt-text-2xs);font-weight:var(--rt-font-weight-bold);letter-spacing:var(--rt-letter-spacing-wide);line-height:var(--rt-leading-snug)}.rt-chat__message--own .rt-chat__message-author{color:var(--rt-chat-bubble-own-author)}.rt-chat__message-text{margin:0;color:var(--rt-color-text-primary);font-size:var(--rt-text-xs);font-weight:var(--rt-font-weight-regular);line-height:var(--rt-leading-snug);white-space:pre-wrap}.rt-chat__message-text--deleted{color:var(--rt-color-text-muted);font-style:italic}.rt-chat__message-delete,.rt-chat__message-actions{flex:0 0 auto;opacity:0;pointer-events:none;transition:opacity var(--rt-chat-transition-duration) ease}.rt-chat__message-attachment{display:block}.rt-chat__message-footer{display:flex;align-items:center;gap:var(--rt-space-1)}.rt-chat__message-date{color:var(--rt-chat-bubble-date);font-size:var(--rt-text-xs);line-height:var(--rt-leading-none)}.rt-chat__message-status{display:inline-flex;align-items:center;color:var(--rt-color-text-muted);gap:var(--rt-space-0-5)}.rt-chat__message-status--status--read{color:var(--rt-chat-status-read)}.rt-chat__message-status--status--failed{color:var(--rt-color-state-danger)}.rt-chat__status-icon{font-size:var(--rt-text-sm)}.rt-chat__message-skeleton{display:flex;max-width:min(80%,480px);flex-direction:column;align-self:flex-start;padding:var(--rt-space-2);border-radius:var(--rt-chat-bubble-radius) var(--rt-chat-bubble-radius) var(--rt-chat-bubble-radius) 0;background:var(--rt-chat-bubble-in-bg);gap:var(--rt-space-1)}.rt-chat__message-skeleton--own{align-self:flex-end;border-radius:var(--rt-chat-bubble-radius) var(--rt-chat-bubble-radius) 0 var(--rt-chat-bubble-radius);background:var(--rt-chat-bubble-own-bg)}.rt-chat__skeleton-line{display:block}.rt-chat__message-deeplink{display:flex;justify-content:flex-start;margin-top:var(--rt-space-1)}.rt-chat__composer,.rt-chat__composer-rich,.rt-chat__reply-block{margin-top:var(--rt-space-4)}.rt-chat__composer{display:flex;flex-direction:column;gap:var(--rt-space-2)}.rt-chat__composer-files{display:flex;flex-wrap:wrap;gap:var(--rt-space-2)}.rt-chat__composer-field{display:flex;box-sizing:border-box;align-items:center;border:var(--rt-input-border-width) solid var(--rt-input-color-border);border-radius:var(--rt-input-radius);background-color:var(--rt-input-color-bg);gap:var(--rt-space-1);min-block-size:var(--rt-input-height);padding-inline:var(--rt-input-padding-x);transition:var(--rt-input-transition)}.rt-chat__composer-field:focus-within{border-color:var(--rt-input-color-border-focus);box-shadow:var(--rt-input-focus-shadow)}.rt-chat__composer-field--attach{padding-inline-start:var(--rt-space-1)}.rt-chat__composer-input{box-sizing:content-box;flex:1 1 auto;border:0;background:transparent;color:var(--rt-input-color-text);font-family:inherit;font-size:var(--rt-input-font-size);inline-size:100%;line-height:var(--rt-textarea-line-height);padding-block:var(--rt-space-2);resize:none}.rt-chat__composer-input::placeholder{color:var(--rt-input-color-text-placeholder);opacity:1}.rt-chat__composer-input:focus-visible{outline:none}.rt-chat__composer-input:disabled{cursor:not-allowed}.rt-chat__composer-send{inline-size:100%}}\n"]
1769
+ styles: ["@layer rt-kit.components{.rt-chat{--rt-chat-bubble-radius: var(--rt-radius-lg);--rt-chat-message-border-width: var(--rt-border-width-thin);--rt-chat-transition-duration: var(--rt-duration-fast);--rt-chat-typing-radius: var(--rt-radius-full);--rt-chat-typing-shadow: var(--rt-shadow-sm);display:flex;flex-direction:column}.rt-chat>.rt-file-drop{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--rt-space-4);min-block-size:0}.rt-chat--expanded{position:fixed;z-index:var(--rt-z-modal);box-sizing:border-box;padding:var(--rt-space-md);background:var(--rt-color-bg-surface);inset:0}.rt-chat--expanded>.rt-file-drop{block-size:100%}@container rt-place (width > 1440px){.rt-chat--expanded>.rt-file-drop{inline-size:1000px;margin-inline:auto;max-inline-size:100%}}@media(width>1440px){:where(html:not(:has(.rt-container))) .rt-chat--expanded>.rt-file-drop{inline-size:1000px;margin-inline:auto;max-inline-size:100%}}.rt-chat--expanded .rt-chat__thread-viewport{flex:1 1 auto;min-block-size:0}.rt-chat--expanded .rt-chat__thread{max-height:none}.rt-chat--fill{block-size:100%}.rt-chat--fill .rt-chat__thread-viewport{flex:1 1 auto;min-block-size:0}.rt-chat--fill .rt-chat__thread{max-height:none}.rt-chat__title{margin:0;color:var(--rt-color-text-primary);font-family:var(--rt-font-family-sans);font-size:var(--rt-text-md);font-weight:var(--rt-font-weight-bold);letter-spacing:var(--rt-letter-spacing-wide);line-height:var(--rt-leading-snug)}.rt-chat__thread-viewport{position:relative;display:flex;flex-direction:column;contain:inline-size;min-block-size:0}.rt-chat__thread{display:flex;max-height:280px;flex:1 1 auto;flex-direction:column;gap:var(--rt-space-4);min-block-size:0;overflow-y:auto}.rt-chat__thread--frozen{overflow-y:hidden}.rt-chat__thread-overlay{position:absolute;z-index:1;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--rt-color-bg-surface) 60%,transparent);inset:0}.rt-chat__typing{position:absolute;z-index:1;bottom:var(--rt-space-1);left:var(--rt-space-2);box-sizing:border-box;color:var(--rt-color-text-muted);font-size:var(--rt-text-xs);max-inline-size:calc(100% - 2 * var(--rt-space-2));overflow-wrap:anywhere;pointer-events:none}.rt-chat__typing--shown{padding:var(--rt-space-0-5) var(--rt-space-2);border-radius:var(--rt-chat-typing-radius);background:var(--rt-color-bg-surface);box-shadow:var(--rt-chat-typing-shadow)}.rt-chat__footer-slot{display:flex;flex-direction:column}.rt-chat__footer-slot:empty{display:none}.rt-chat__footer-slot--blocked{opacity:.6;pointer-events:none}.rt-chat__message-row{display:flex;max-width:min(80%,480px);align-items:center;align-self:flex-start;gap:var(--rt-space-2)}.rt-chat__message-row--own{align-self:flex-end}.rt-chat__message-row:hover .rt-chat__message-delete,.rt-chat__message-row:focus-within .rt-chat__message-delete,.rt-chat__message-row:hover .rt-chat__message-actions,.rt-chat__message-row:focus-within .rt-chat__message-actions{opacity:1;pointer-events:auto}.rt-chat__message{display:flex;min-width:0;flex:0 1 auto;flex-direction:column;padding:var(--rt-space-2);border-radius:var(--rt-chat-bubble-radius) var(--rt-chat-bubble-radius) var(--rt-chat-bubble-radius) 0;background:var(--rt-chat-bubble-in-bg);gap:var(--rt-space-2)}.rt-chat__message--own{border-radius:var(--rt-chat-bubble-radius) var(--rt-chat-bubble-radius) 0 var(--rt-chat-bubble-radius);background:var(--rt-chat-bubble-own-bg)}.rt-chat__message--deleted{border:var(--rt-chat-message-border-width) dashed var(--rt-color-border-default);background:transparent}.rt-chat__message-author{margin:0;color:var(--rt-chat-bubble-in-author);font-size:var(--rt-text-2xs);font-weight:var(--rt-font-weight-bold);letter-spacing:var(--rt-letter-spacing-wide);line-height:var(--rt-leading-snug)}.rt-chat__message--own .rt-chat__message-author{color:var(--rt-chat-bubble-own-author)}.rt-chat__message-text{margin:0;color:var(--rt-color-text-primary);font-size:var(--rt-text-xs);font-weight:var(--rt-font-weight-regular);line-height:var(--rt-leading-snug);white-space:pre-wrap}.rt-chat__message-text--deleted{color:var(--rt-color-text-muted);font-style:italic}.rt-chat__message-delete,.rt-chat__message-actions{flex:0 0 auto;opacity:0;pointer-events:none;transition:opacity var(--rt-chat-transition-duration) ease}.rt-chat__message-attachment{display:block}.rt-chat__message-footer{display:flex;align-items:center;gap:var(--rt-space-1)}.rt-chat__message-date{color:var(--rt-chat-bubble-date);font-size:var(--rt-text-xs);line-height:var(--rt-leading-none)}.rt-chat__message-status{display:inline-flex;align-items:center;color:var(--rt-color-text-muted);gap:var(--rt-space-0-5)}.rt-chat__message-status--status--read{color:var(--rt-chat-status-read)}.rt-chat__message-status--status--failed{color:var(--rt-color-state-danger)}.rt-chat__status-icon{font-size:var(--rt-text-sm)}.rt-chat__message-skeleton{display:flex;max-width:min(80%,480px);flex-direction:column;align-self:flex-start;padding:var(--rt-space-2);border-radius:var(--rt-chat-bubble-radius) var(--rt-chat-bubble-radius) var(--rt-chat-bubble-radius) 0;background:var(--rt-chat-bubble-in-bg);gap:var(--rt-space-1)}.rt-chat__message-skeleton--own{align-self:flex-end;border-radius:var(--rt-chat-bubble-radius) var(--rt-chat-bubble-radius) 0 var(--rt-chat-bubble-radius);background:var(--rt-chat-bubble-own-bg)}.rt-chat__skeleton-line{display:block}.rt-chat__message-deeplink{display:flex;justify-content:flex-start;margin-top:var(--rt-space-1)}.rt-chat__composer,.rt-chat__composer-rich,.rt-chat__reply-block{margin-top:var(--rt-space-4)}.rt-chat__composer{display:flex;flex-direction:column;gap:var(--rt-space-2)}.rt-chat__composer-files{display:flex;flex-wrap:wrap;gap:var(--rt-space-2)}.rt-chat__composer-field{display:flex;box-sizing:border-box;align-items:center;border:var(--rt-input-border-width) solid var(--rt-input-color-border);border-radius:var(--rt-input-radius);background-color:var(--rt-input-color-bg);gap:var(--rt-space-1);min-block-size:var(--rt-input-height);padding-inline:var(--rt-input-padding-x);transition:var(--rt-input-transition)}.rt-chat__composer-field:focus-within{border-color:var(--rt-input-color-border-focus);box-shadow:var(--rt-input-focus-shadow)}.rt-chat__composer-field--attach{padding-inline-start:var(--rt-space-1)}.rt-chat__composer-input{box-sizing:content-box;flex:1 1 auto;border:0;background:transparent;color:var(--rt-input-color-text);font-family:inherit;font-size:var(--rt-input-font-size);inline-size:100%;line-height:var(--rt-textarea-line-height);padding-block:var(--rt-space-2);resize:none}.rt-chat__composer-input::placeholder{color:var(--rt-input-color-text-placeholder);opacity:1}.rt-chat__composer-input:focus-visible{outline:none}.rt-chat__composer-input:disabled{cursor:not-allowed}.rt-chat__composer-send{inline-size:100%}}\n"]
1770
1770
  }]
1771
1771
  }],
1772
1772
  ctorParameters: () => [],
@@ -2440,7 +2440,7 @@ var RtAiChatComponent = class RtAiChatComponent {
2440
2440
  id: thread.id,
2441
2441
  hasUnread: (thread.unreadCount ?? 0) > 0,
2442
2442
  title: thread.title,
2443
- titleParts: splitSideMenuTitle(thread.title, query),
2443
+ titleParts: rtMatchParts(thread.title, query),
2444
2444
  time: thread.time ?? "",
2445
2445
  unreadCount: thread.unreadCount ?? 0
2446
2446
  }));