@rt-tools/ui-kit-v2 0.25.0 → 0.26.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.
@@ -0,0 +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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rt-tools/ui-kit-v2",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "license": "Apache-2.0",
5
5
  "author": "Yauheni Krumin",
6
6
  "type": "module",
@@ -19,11 +19,15 @@
19
19
  "@angular/forms": "^22.0.0",
20
20
  "@angular/platform-browser": "^22.0.0",
21
21
  "@angular/router": "^22.0.0",
22
+ "pdfjs-dist": "^6.4.299",
22
23
  "quill": "^2.0.3",
23
24
  "rxjs": "^7.8.2",
24
25
  "typescript": "^6.0.0"
25
26
  },
26
27
  "peerDependenciesMeta": {
28
+ "pdfjs-dist": {
29
+ "optional": true
30
+ },
27
31
  "quill": {
28
32
  "optional": true
29
33
  }
@@ -313,6 +317,10 @@
313
317
  "types": "./types/rt-tools-ui-kit-v2-pagination.d.ts",
314
318
  "default": "./fesm2022/rt-tools-ui-kit-v2-pagination.mjs"
315
319
  },
320
+ "./pdf-viewer": {
321
+ "types": "./types/rt-tools-ui-kit-v2-pdf-viewer.d.ts",
322
+ "default": "./fesm2022/rt-tools-ui-kit-v2-pdf-viewer.mjs"
323
+ },
316
324
  "./photo-viewer": {
317
325
  "types": "./types/rt-tools-ui-kit-v2-photo-viewer.d.ts",
318
326
  "default": "./fesm2022/rt-tools-ui-kit-v2-photo-viewer.mjs"
@@ -41,7 +41,7 @@ tag/
41
41
  | списки выбора | `select`, `multiselect`, `autocomplete`, `filter-control`, `dynamic-selector` |
42
42
  | обёртка поля | `field`, `form-control` (основа) |
43
43
  | плавающие панели | `popover`, `tooltip`, `confirm-popover`, `menu`, `toast` |
44
- | окна и панели | `dialog`, `aside`, `aside-section`, `bottom-sheet`, `welcome-dialog`, `photo-viewer` |
44
+ | окна и панели | `dialog`, `aside`, `aside-section`, `bottom-sheet`, `welcome-dialog`, `photo-viewer`, `pdf-viewer` |
45
45
  | каркас страницы | `container`, `workspace`, `workspace-details`, `header`, `page-header`, `toolbar`, `section-nav` |
46
46
  | данные | `table`, `pagination`, `thread-list`, `detail-list`, `money-list`, `bar-list`, `timeline`, `night-grid`, `calendar` |
47
47
  | переписка | `chat`, `message`, `message-composer` |
@@ -0,0 +1,54 @@
1
+ # `rt-pdf-viewer`
2
+
3
+ Полноэкранный просмотр документа PDF. Открывается через [`RtDialogService`](../dialog/CONTEXT.md)
4
+ из отдельного входа `@rt-tools/ui-kit-v2/pdf-viewer`.
5
+
6
+ ```typescript
7
+ this.#dialog.open<RtPdfViewerComponent, IRtPdfViewer.Data, void>(RtPdfViewerComponent, {
8
+ data: { source: bytes, fileName: 'Договор.pdf' },
9
+ });
10
+ ```
11
+
12
+ Данные приходят токеном `RT_DIALOG_DATA`: `source` — байты (`Uint8Array`, `ArrayBuffer`) или
13
+ адрес, `fileName` — имя в полосе и имя скачанного файла.
14
+
15
+ ## Главное, что нужно знать
16
+
17
+ **Рисует `pdfjs-dist`, и он необязателен.** Пакет стоит в `peerDependenciesMeta` как optional и
18
+ грузится динамическим `import()` только при открытии окна. Вход отдельный и корнем пакета не
19
+ переотдаётся: из корня сборщик разрешал бы `import('pdfjs-dist')` каждому приложению, даже тому,
20
+ кто просмотрщик не ставит.
21
+
22
+ **Воркер приложение кладёт себе само.** Адрес по умолчанию — `assets/pdfjs/pdf.worker.min.mjs`
23
+ от `<base href>`, другой задаётся `provideRtPdfViewer({ workerSrc })`. Файл копируется из того же
24
+ `node_modules`: версии воркера и рисовальщика обязаны совпадать, и чужой адрес разошёлся бы с ними
25
+ на первом же обновлении.
26
+
27
+ **Рисуются только видимые страницы и по одной соседней.** Места под все страницы ставятся сразу,
28
+ по размерам из документа, — прокрутка не прыгает. Холсты дальше трёх страниц от видимых очищаются.
29
+ Холст рисуется в точках экрана (`devicePixelRatio`), а не в CSS-пикселях.
30
+
31
+ **Масштаб «по ширине» — режим, а не число.** При смене ширины окна страница подстраивается заново.
32
+ Узкое окно (до 768 включительно) и страница, которая в 100% не влезает, открываются «по ширине»,
33
+ остальные — в 100%. Ступени — 50…300%; «по ширине» ими не зажат: на телефоне это около 45%, и
34
+ «−» там уже выключен.
35
+
36
+ **Печать — картинками, а не кадром с файлом.** Каждая страница рисуется в картинку 150 точек на
37
+ дюйм и встаёт в лист в конце `<body>`; на время печати корень страницы получает признак
38
+ `rt-pdf-viewer-printing`, и печатается только лист. Кадр `<iframe>` с самим файлом отвергнут:
39
+ у мобильных браузеров встроенного показа PDF нет, а часть настольных печатает из такого кадра
40
+ пустую или только первую страницу. Картинки печатает любой браузер. Лист убирается по
41
+ `afterprint` и при закрытии окна.
42
+
43
+ **Байты уходят рисовальщику копией.** Рисовальщик забирает буфер себе в поток, и без копии
44
+ скачать файл после открытия было бы нечем.
45
+
46
+ ## Края
47
+
48
+ - Документ, который не открылся (битый файл, рисовальщик не загрузился, воркер не найден), даёт
49
+ отказ по центру с кнопкой «Скачать»: файл всё равно у человека.
50
+ - Скачивание адреса без загруженного документа отдаёт ссылку как есть — чужой домен может не
51
+ принять имя из `download`.
52
+ - Фокус после закрытия возвращается туда, где стоял при открытии: просмотрщик помнит его сам и
53
+ не зависит от `restoreFocus` в настройке открытия.
54
+ - Номер видимой страницы — та, что занимает больше высоты окна; в конце прокрутки — последняя.
@@ -0,0 +1,231 @@
1
+ @use '../../../styles/breakpoints' as bp;
2
+
3
+ @layer rt-kit.components {
4
+ /* rt-pdf-viewer — ViewEncapsulation.None: правила таргетируют BEM-класс блока.
5
+ `.rt-pdf-viewer` — сам хост (шаблон начинается с ng-container, класс ставит host). */
6
+
7
+ .rt-pdf-viewer {
8
+ // Документ читают на тёмном в обеих темах: белый лист на светлой подложке теряет край.
9
+ // Поэтому цвета взяты со ступеней серого, а не с назначений темы, и стоят своими
10
+ // свойствами — потребитель перекрывает их правилом той же цели.
11
+ --rt-pdf-viewer-bg: var(--rt-neutral-900);
12
+ --rt-pdf-viewer-bar-bg: color-mix(in srgb, var(--rt-neutral-900) 35%, var(--rt-neutral-950));
13
+ --rt-pdf-viewer-bar-height: var(--rt-control-height-2xl);
14
+ --rt-pdf-viewer-text-color: var(--rt-neutral-0);
15
+ --rt-pdf-viewer-muted-color: var(--rt-neutral-200);
16
+ --rt-pdf-viewer-page-bg: var(--rt-neutral-0);
17
+ --rt-pdf-viewer-page-shadow: var(--rt-shadow-md);
18
+ --rt-pdf-viewer-page-gap: var(--rt-space-4);
19
+ --rt-pdf-viewer-toolbar-bg: var(--rt-neutral-800);
20
+ --rt-pdf-viewer-toolbar-radius: var(--rt-radius-full);
21
+ --rt-pdf-viewer-divider-width: var(--rt-border-width-thin);
22
+ --rt-pdf-viewer-divider-color: color-mix(in srgb, var(--rt-neutral-0) 20%, transparent);
23
+ --rt-pdf-viewer-control-radius: var(--rt-radius-md);
24
+ --rt-pdf-viewer-control-bg: color-mix(in srgb, var(--rt-neutral-0) 10%, transparent);
25
+ --rt-pdf-viewer-control-bg-hover: color-mix(in srgb, var(--rt-neutral-0) 24%, transparent);
26
+
27
+ display: block;
28
+ width: 100vw;
29
+ height: 100vh;
30
+ height: 100dvh;
31
+ background: var(--rt-pdf-viewer-bg);
32
+ color: var(--rt-pdf-viewer-text-color);
33
+
34
+ // Обёртка существует ради ловушки фокуса: display: contents ломает ловушку
35
+ &__surface {
36
+ position: absolute;
37
+ display: flex;
38
+ flex-direction: column;
39
+ inset: 0;
40
+ }
41
+
42
+ &__bar {
43
+ display: flex;
44
+ height: var(--rt-pdf-viewer-bar-height);
45
+ flex: none;
46
+ align-items: center;
47
+ justify-content: space-between;
48
+ padding: 0 var(--rt-space-4);
49
+ background: var(--rt-pdf-viewer-bar-bg);
50
+ gap: var(--rt-space-4);
51
+ }
52
+
53
+ // Имя файла сжимается первым: кнопки справа не уходят за край ни при каком имени
54
+ &__title {
55
+ display: flex;
56
+ min-width: 0;
57
+ align-items: center;
58
+ gap: var(--rt-space-2);
59
+ }
60
+
61
+ &__file-icon {
62
+ flex: none;
63
+ color: var(--rt-pdf-viewer-text-color);
64
+ }
65
+
66
+ // Обрезанное имя целиком видно в подсказке: оно же стоит в title
67
+ &__file-name {
68
+ overflow: hidden;
69
+ min-width: 0;
70
+ font-size: var(--rt-text-sm);
71
+ text-overflow: ellipsis;
72
+ white-space: nowrap;
73
+ }
74
+
75
+ &__actions {
76
+ display: flex;
77
+ flex: none;
78
+ align-items: center;
79
+ gap: var(--rt-space-2);
80
+ }
81
+
82
+ // Кнопки полосы — тот же вид управляющего элемента поверх тёмного, что у просмотра фото
83
+ &__actions rt-icon-button {
84
+ --rt-icon-button-color: var(--rt-pdf-viewer-text-color);
85
+ --rt-icon-button-color-bg-hover: var(--rt-pdf-viewer-control-bg-hover);
86
+
87
+ border-radius: var(--rt-pdf-viewer-control-radius);
88
+ background: var(--rt-pdf-viewer-control-bg);
89
+ }
90
+
91
+ &__scroller {
92
+ position: relative;
93
+ overflow: auto;
94
+ flex: 1 1 auto;
95
+ overscroll-behavior: contain;
96
+
97
+ // Место под полосу прокрутки занято с самого начала: «по ширине» меряется до того, как
98
+ // страницы встанут, и полоса, появившись после, давала бы прокрутку вбок
99
+ scrollbar-gutter: stable;
100
+ }
101
+
102
+ // Столбец шире окна, когда страница увеличена: иначе левый край листа уходил бы за край
103
+ // прокрутки и до него нельзя было бы дотянуться
104
+ &__column {
105
+ display: flex;
106
+ width: max-content;
107
+ min-width: 100%;
108
+ box-sizing: border-box;
109
+ flex-direction: column;
110
+ align-items: center;
111
+ padding: var(--rt-space-4) var(--rt-space-4) var(--rt-space-24);
112
+ gap: var(--rt-pdf-viewer-page-gap);
113
+ }
114
+
115
+ &__page {
116
+ flex: none;
117
+ background: var(--rt-pdf-viewer-page-bg);
118
+ box-shadow: var(--rt-pdf-viewer-page-shadow);
119
+ }
120
+
121
+ &__canvas {
122
+ display: block;
123
+ width: 100%;
124
+ height: 100%;
125
+ }
126
+
127
+ // Индикатор и отказ стоят по центру окна под полосой, поверх пустого столбца
128
+ &__center {
129
+ position: absolute;
130
+ display: flex;
131
+ align-items: center;
132
+ justify-content: center;
133
+ padding: var(--rt-space-4);
134
+ inset: var(--rt-pdf-viewer-bar-height) 0 0;
135
+ }
136
+
137
+ // Свойства заглушки объявлены на её же блоке — перекрываются правилом сильнее его
138
+ & &__error {
139
+ --rt-empty-state-title-color: var(--rt-pdf-viewer-text-color);
140
+
141
+ .rt-empty-state__description {
142
+ color: var(--rt-pdf-viewer-muted-color);
143
+ }
144
+ }
145
+
146
+ // Белая кнопка на тёмном: та же кнопка кита, перекрашенная своими свойствами
147
+ & &__error-download {
148
+ --rt-btn-bg: var(--rt-pdf-viewer-page-bg);
149
+ --rt-btn-bg-hover: var(--rt-neutral-100);
150
+ --rt-btn-bg-active: var(--rt-neutral-200);
151
+ --rt-btn-color: var(--rt-neutral-900);
152
+ }
153
+
154
+ &__toolbar {
155
+ position: absolute;
156
+ bottom: var(--rt-space-6);
157
+ left: 50%;
158
+ display: flex;
159
+ align-items: center;
160
+ padding: var(--rt-space-1) var(--rt-space-2);
161
+ border-radius: var(--rt-pdf-viewer-toolbar-radius);
162
+ background: var(--rt-pdf-viewer-toolbar-bg);
163
+ gap: var(--rt-space-1);
164
+ transform: translateX(-50%);
165
+ white-space: nowrap;
166
+ }
167
+
168
+ &__toolbar rt-icon-button {
169
+ --rt-icon-button-color: var(--rt-pdf-viewer-text-color);
170
+ --rt-icon-button-color-bg-hover: var(--rt-pdf-viewer-control-bg-hover);
171
+ }
172
+
173
+ &__counter,
174
+ &__zoom {
175
+ padding: 0 var(--rt-space-2);
176
+ font-size: var(--rt-text-sm);
177
+ font-variant-numeric: tabular-nums;
178
+ }
179
+
180
+ // Подпись масштаба не дёргает кнопки при смене «75%» на «100%»
181
+ &__zoom {
182
+ min-width: var(--rt-size-12);
183
+ text-align: center;
184
+ }
185
+
186
+ &__divider,
187
+ &__fit-divider {
188
+ width: var(--rt-pdf-viewer-divider-width);
189
+ height: var(--rt-size-5);
190
+ margin: 0 var(--rt-space-1);
191
+ background: var(--rt-pdf-viewer-divider-color);
192
+ }
193
+
194
+ & &__fit {
195
+ --rt-btn-color: var(--rt-pdf-viewer-text-color);
196
+ --rt-btn-bg-hover: var(--rt-pdf-viewer-control-bg-hover);
197
+ }
198
+
199
+ @media (width <= #{bp.$rt-bp-sm}) {
200
+ --rt-pdf-viewer-bar-height: var(--rt-control-height-xl);
201
+
202
+ &__fit,
203
+ &__fit-divider {
204
+ display: none;
205
+ }
206
+ }
207
+ }
208
+
209
+ /* Печатный лист: страницы картинками в конце <body>. Вне печати его не видно, а при печати
210
+ не видно ничего, кроме него, — ни полосы, ни плашки, ни страницы приложения под окном. */
211
+ .rt-pdf-viewer-print {
212
+ display: none;
213
+
214
+ &__page {
215
+ display: block;
216
+ width: 100%;
217
+ break-after: page;
218
+ break-inside: avoid;
219
+ }
220
+ }
221
+
222
+ @media print {
223
+ .rt-pdf-viewer-printing body > :not(.rt-pdf-viewer-print) {
224
+ display: none;
225
+ }
226
+
227
+ .rt-pdf-viewer-printing .rt-pdf-viewer-print {
228
+ display: block;
229
+ }
230
+ }
231
+ }
@@ -539,6 +539,15 @@ export declare const RT_KIT_LABELS_EN: {
539
539
  readonly fieldErrorMinLength: "Value is too short";
540
540
  readonly fieldErrorPattern: "Invalid format";
541
541
  readonly fieldErrorRequired: "Required field";
542
+ readonly pdfViewerClose: "Close document";
543
+ readonly pdfViewerDownload: "Download";
544
+ readonly pdfViewerErrorText: "You can download the file and open it on your computer.";
545
+ readonly pdfViewerErrorTitle: "Couldn't show the document";
546
+ readonly pdfViewerFitWidth: "Fit to width";
547
+ readonly pdfViewerPageOf: "Page {{page}} of {{total}}";
548
+ readonly pdfViewerPrint: "Print";
549
+ readonly pdfViewerZoomIn: "Zoom in";
550
+ readonly pdfViewerZoomOut: "Zoom out";
542
551
  readonly photoViewerClose: "Close viewer";
543
552
  readonly photoViewerNext: "Next photo";
544
553
  readonly photoViewerPrev: "Previous photo";