@rt-tools/ui-kit-v2 0.34.3 → 0.34.5
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.
- package/fesm2022/rt-tools-ui-kit-v2-action-bar.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-action-bar.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-aside.mjs +78 -10
- package/fesm2022/rt-tools-ui-kit-v2-aside.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-autocomplete.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-autocomplete.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-button.mjs +18 -9
- package/fesm2022/rt-tools-ui-kit-v2-button.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-calendar.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-calendar.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-confirm-popover.mjs +2 -2
- package/fesm2022/rt-tools-ui-kit-v2-confirm-popover.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-copy-value.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-copy-value.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-core.mjs +4 -1
- package/fesm2022/rt-tools-ui-kit-v2-core.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-counter.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-counter.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-data-list.mjs +4 -0
- package/fesm2022/rt-tools-ui-kit-v2-data-list.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-data-table.mjs +4 -0
- package/fesm2022/rt-tools-ui-kit-v2-data-table.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-date-picker.mjs +4 -0
- package/fesm2022/rt-tools-ui-kit-v2-date-picker.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-date-range.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-date-range.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-dialog.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-dialog.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-dynamic-list.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-dynamic-list.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-dynamic-selector.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-dynamic-selector.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-file-card.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-file-card.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-header.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-header.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-icon-button.mjs +103 -6
- package/fesm2022/rt-tools-ui-kit-v2-icon-button.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-image-upload.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-image-upload.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-inline-edit.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-inline-edit.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-input-number.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-input-number.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-input.mjs +6 -1
- package/fesm2022/rt-tools-ui-kit-v2-input.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-menu.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-menu.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-message.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-message.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-multiselect.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-multiselect.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-pagination.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-pagination.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-pdf-viewer.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-pdf-viewer.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-photo-viewer.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-photo-viewer.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-rich-editor.mjs +10 -0
- package/fesm2022/rt-tools-ui-kit-v2-rich-editor.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-select.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-select.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-side-menu.mjs +6 -0
- package/fesm2022/rt-tools-ui-kit-v2-side-menu.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-speed-menu.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-speed-menu.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-table.mjs +8 -0
- package/fesm2022/rt-tools-ui-kit-v2-table.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-tag.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-tag.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-theme-toggle.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-theme-toggle.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-thread-list.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-thread-list.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-toast.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-toast.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-workspace-details.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-workspace-details.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-workspace.mjs +2 -0
- package/fesm2022/rt-tools-ui-kit-v2-workspace.mjs.map +1 -1
- package/package.json +1 -1
- package/src/lib/components/aside/footer/rt-aside-footer.component.scss +3 -1
- package/src/lib/components/aside/unsaved-dialog/rt-aside-unsaved-dialog.component.scss +10 -3
- package/src/lib/components/icon-button/CONTEXT.md +32 -16
- package/src/lib/components/icon-button/rt-icon-button.component.scss +17 -9
- package/src/lib/components/input/CONTEXT.md +6 -0
- package/src/styles/_form.scss +14 -1
- package/styles/tokens.css +13 -1
- package/types/rt-tools-ui-kit-v2-aside.d.ts +17 -7
- package/types/rt-tools-ui-kit-v2-button.d.ts +6 -1
- package/types/rt-tools-ui-kit-v2-core.d.ts +18 -0
- package/types/rt-tools-ui-kit-v2-icon-button.d.ts +38 -2
- package/types/rt-tools-ui-kit-v2-input.d.ts +2 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rt-tools-ui-kit-v2-aside.mjs","names":["BEM_BLOCK","BEM_BLOCK","BEM_BLOCK","BEM_BLOCK"],"sources":["../../../projects/ui-kit-v2/src/lib/components/aside/error-box/rt-aside-error-box.logic.ts","../../../projects/ui-kit-v2/src/lib/components/aside/error-box/rt-aside-error-box.component.ts","../../../projects/ui-kit-v2/src/lib/components/aside/error-box/rt-aside-error-box.component.html","../../../projects/ui-kit-v2/src/lib/components/aside/rt-aside.component.ts","../../../projects/ui-kit-v2/src/lib/components/aside/rt-aside.component.html","../../../projects/ui-kit-v2/src/lib/components/aside/rt-aside-ref.ts","../../../projects/ui-kit-v2/src/lib/components/aside/rt-aside.tokens.ts","../../../projects/ui-kit-v2/src/lib/components/aside/rt-aside.service.ts","../../../projects/ui-kit-v2/src/lib/components/aside/header/rt-aside-header.component.ts","../../../projects/ui-kit-v2/src/lib/components/aside/header/rt-aside-header.component.html","../../../projects/ui-kit-v2/src/lib/components/aside/footer/rt-aside-footer.component.ts","../../../projects/ui-kit-v2/src/lib/components/aside/footer/rt-aside-footer.component.html","../../../projects/ui-kit-v2/src/lib/components/aside/unsaved-dialog/rt-aside-unsaved.logic.ts","../../../projects/ui-kit-v2/src/lib/components/aside/unsaved-dialog/rt-aside-unsaved-dialog.component.ts","../../../projects/ui-kit-v2/src/lib/components/aside/unsaved-dialog/rt-aside-unsaved-dialog.component.html"],"sourcesContent":["function errorAsText(error: unknown): string {\n try {\n return JSON.stringify(error) ?? String(error);\n } catch {\n return String(error);\n }\n}\n\n/**\n * Текст, который кнопка блока ошибки кладёт в буфер обмена.\n *\n * Вид повторяет первый кит — `Error time: <дата>_<время>;Error info: <JSON>`: кто разбирал такие\n * копии раньше, читает их так же. Ошибку с петлёй внутри `JSON.stringify` не пишет и бросает —\n * тогда в копию идёт её строковый вид, и нажатие не падает.\n */\nexport function rtAsideErrorCopyText(error: unknown, now: Date): string {\n return `Error time: ${now.toDateString()}_${now.toTimeString()};Error info: ${errorAsText(error)}`;\n}\n","import { Clipboard } from '@angular/cdk/clipboard';\nimport {\n ChangeDetectionStrategy,\n Component,\n computed,\n DestroyRef,\n inject,\n input,\n InputSignal,\n signal,\n Signal,\n ViewEncapsulation,\n WritableSignal,\n} from '@angular/core';\n\nimport { BlockDirective, ElemDirective } from '@rt-tools/core';\n\nimport { rtKitLabel } from '@rt-tools/ui-kit-v2/core';\nimport { RtButtonDirective } from '@rt-tools/ui-kit-v2/button';\nimport { rtAsideErrorCopyText } from './rt-aside-error-box.logic';\n\nconst BEM_BLOCK: string = 'rt-aside-error-box';\n\n/** Сколько кнопка держит «скопировано» — секунда, как у первого кита. */\nconst CONFIRM_DELAY_MS: number = 1000;\n\n/**\n * Блок ошибки запроса боковой панели: надпись и кнопка, которая кладёт в буфер обмена время и\n * ошибку. Перенос `rtui-aside-error-box` первого кита без Material.\n *\n * Обычно его рисует сама панель по входу `requestError`; отдельно он нужен приложению, которое\n * показывает ошибку в другом месте.\n */\n@Component({\n selector: 'rt-aside-error-box',\n templateUrl: './rt-aside-error-box.component.html',\n styleUrl: './rt-aside-error-box.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [RtButtonDirective, BlockDirective, ElemDirective],\n host: {\n class: BEM_BLOCK,\n },\n})\nexport class RtAsideErrorBoxComponent {\n readonly #clipboard: Clipboard = inject(Clipboard);\n readonly #destroyRef: DestroyRef = inject(DestroyRef);\n\n #confirmTimer: ReturnType<typeof setTimeout> | null = null;\n\n readonly #copyLabel: Signal<string> = rtKitLabel('asideCopyErrorInfo');\n readonly #copiedLabel: Signal<string> = rtKitLabel('uiCopied');\n\n protected readonly title: Signal<string> = rtKitLabel('asideRequestError');\n\n protected readonly copied: WritableSignal<boolean> = signal(false);\n\n protected readonly buttonLabel: Signal<string> = computed((): string => (this.copied() ? this.#copiedLabel() : this.#copyLabel()));\n\n protected readonly buttonIcon: Signal<string | null> = computed((): string | null => (this.copied() ? 'check' : null));\n\n /** Ошибка, пришедшая от неудавшегося запроса: уходит в копию как JSON. */\n public readonly error: InputSignal<unknown> = input.required<unknown>();\n\n constructor() {\n this.#destroyRef.onDestroy((): void => this.#clearTimer());\n }\n\n protected onCopy(): void {\n this.#clipboard.copy(rtAsideErrorCopyText(this.error(), new Date()));\n this.copied.set(true);\n\n this.#clearTimer();\n this.#confirmTimer = setTimeout((): void => this.copied.set(false), CONFIRM_DELAY_MS);\n }\n\n #clearTimer(): void {\n if (this.#confirmTimer !== null) {\n clearTimeout(this.#confirmTimer);\n this.#confirmTimer = null;\n }\n }\n}\n","<ng-container rtBlock=\"rt-aside-error-box\">\n <div rtElem=\"frame\" qa-dataid=\"aside-error-box\">\n <span rtElem=\"title\" qa-dataid=\"aside-error-title\">{{ title() }}</span>\n\n <button\n rtButton\n rtElem=\"copy\"\n qa-dataid=\"aside-error-copy\"\n type=\"button\"\n theme=\"secondary\"\n appearance=\"text\"\n size=\"sm\"\n [label]=\"buttonLabel()\"\n [icon]=\"buttonIcon()\"\n [attr.aria-label]=\"buttonLabel()\"\n (click)=\"onCopy()\"></button>\n </div>\n</ng-container>\n","import { FocusTrap, FocusTrapFactory } from '@angular/cdk/a11y';\nimport { BooleanInput } from '@angular/cdk/coercion';\nimport { DOCUMENT } from '@angular/common';\nimport {\n afterRenderEffect,\n booleanAttribute,\n computed,\n inject,\n input,\n untracked,\n viewChild,\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ElementRef,\n InputSignal,\n InputSignalWithTransform,\n Signal,\n ViewEncapsulation,\n} from '@angular/core';\n\nimport { BlockDirective, ElemDirective, ModDirective } from '@rt-tools/core';\n\nimport { RtSpinnerComponent } from '@rt-tools/ui-kit-v2/spinner';\nimport { RtAsideErrorBoxComponent } from './error-box/rt-aside-error-box.component';\n\nconst BEM_BLOCK: string = 'rt-aside';\n\n/**\n * Варианты ширины side-sheet:\n * - `sm` — 400px (`--rt-aside-width-sm`)\n * - `md` — 540px (`--rt-aside-width-md`, дефолт)\n * - `lg` — 720px (`--rt-aside-width-lg`)\n */\nexport type TRtAsideSize = 'sm' | 'md' | 'lg';\n\n/**\n * Раскладка контентной зоны side-sheet:\n * - `default` — контентная зона сама скроллится при overflow (штатный режим);\n * - `tabs` — контент — это `rt-tabs`: внешняя зона перестаёт быть скроллером,\n * полоса вкладок прибита, скроллится только контент активной вкладки\n * (`rt-tabs__content`).\n */\nexport type TRtAsideContentLayout = 'default' | 'tabs';\n\n/**\n * Презентационный styled-frame для side-sheet.\n *\n * Видимость / backdrop / scroll-block / ESC / slide-in анимация — НЕ внутренняя\n * ответственность компонента. Это обеспечивает CDK Overlay через\n * `RtAsideService.open()`. rt-aside здесь — стилизованная \"рамка\" с size-вариантами\n * и тремя слотами: `rt-aside-header`, содержимое и `rt-aside-footer`.\n *\n * Содержимое лежит в одной зоне `.rt-aside__content` — она и держит инсет панели,\n * и прокручивается. Пока зоны не было, каждый корневой узел содержимого получал\n * инсет и прокрутку сам по себе: разделы делили высоту панели между собой, а\n * обводка фокуса поля срезалась краем такого мини-скроллера.\n *\n * Семантика — `role=\"complementary\"` (mirror rt-dialog имеет `role=\"dialog\"`,\n * aside — другая роль, side-sheet не обязательно блокирующий).\n *\n * `ViewEncapsulation.None` — стили префиксованы `.rt-aside`, чтобы соседние\n * rt-aside-header / rt-aside-footer работали с одним BEM-блоком без\n * cross-encapsulation хака.\n */\n@Component({\n selector: 'rt-aside',\n templateUrl: './rt-aside.component.html',\n styleUrl: './rt-aside.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [BlockDirective, ElemDirective, ModDirective, RtAsideErrorBoxComponent, RtSpinnerComponent],\n host: {\n class: BEM_BLOCK,\n },\n})\nexport class RtAsideComponent {\n readonly #focusTrapFactory: FocusTrapFactory = inject(FocusTrapFactory);\n readonly #document: Document = inject(DOCUMENT);\n\n #focusTrap: FocusTrap | null = null;\n #restoreFocusTo: HTMLElement | null = null;\n\n protected readonly frame: Signal<ElementRef<HTMLElement>> = viewChild.required<ElementRef<HTMLElement>>('frame');\n\n protected readonly hasRequestError: Signal<boolean> = computed(\n (): boolean => this.requestError() !== null && this.requestError() !== undefined\n );\n\n public readonly size: InputSignal<TRtAsideSize> = input<TRtAsideSize>('md');\n public readonly contentLayout: InputSignal<TRtAsideContentLayout> = input<TRtAsideContentLayout>('default');\n public readonly width: InputSignal<string | null> = input<string | null>(null);\n public readonly ariaLabel: InputSignal<string | null> = input<string | null>(null);\n\n /**\n * Ошибка неудавшегося запроса. Пока она есть, между шапкой и содержимым стоит блок ошибки с\n * кнопкой копирования; `null` и `undefined` — ошибки нет, любое другое значение её показывает.\n */\n public readonly requestError: InputSignal<unknown> = input<unknown>(null);\n\n /**\n * Держать Tab внутри панели. При включении фокус уходит на `[cdkFocusInitial]` или первый\n * элемент под Tab, а когда панель уходит или вход гаснет, возвращается туда, где стоял.\n */\n public readonly trapFocus: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n /** Идёт запрос: панель накрыта слоем с крутилкой и помечена занятой. */\n public readonly pending: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n constructor() {\n // После отрисовки: до неё у рамки нет ни полей, ни кнопок, и ловить фокус не на чем.\n afterRenderEffect((): void => {\n const enabled: boolean = this.trapFocus();\n const frame: HTMLElement = this.frame().nativeElement;\n untracked((): void => (enabled ? this.#holdFocus(frame) : this.#releaseFocus()));\n });\n inject(DestroyRef).onDestroy((): void => this.#releaseFocus());\n }\n\n #holdFocus(frame: HTMLElement): void {\n if (this.#focusTrap !== null) {\n return;\n }\n const focused: Element | null = this.#document.activeElement;\n this.#restoreFocusTo = focused instanceof HTMLElement ? focused : null;\n this.#focusTrap = this.#focusTrapFactory.create(frame);\n void this.#focusTrap.focusInitialElementWhenReady();\n }\n\n #releaseFocus(): void {\n if (this.#focusTrap === null) {\n return;\n }\n this.#focusTrap.destroy();\n this.#focusTrap = null;\n const restoreTo: HTMLElement | null = this.#restoreFocusTo;\n this.#restoreFocusTo = null;\n if (restoreTo?.isConnected === true) {\n restoreTo.focus();\n }\n }\n}\n","<div\n #frame\n rtBlock=\"rt-aside\"\n qa-dataid=\"aside\"\n role=\"complementary\"\n [rtMod]=\"{ size: size(), content: contentLayout() }\"\n [style.--rt-aside-width]=\"width()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-busy]=\"pending() || null\">\n <ng-content select=\"rt-aside-header\" />\n\n @if (hasRequestError()) {\n <rt-aside-error-box rtElem=\"error\" [error]=\"requestError()\" />\n }\n\n <div rtElem=\"content\" qa-dataid=\"aside-content\">\n <ng-content />\n </div>\n\n <ng-content select=\"rt-aside-footer\" />\n\n @if (pending()) {\n <div rtElem=\"pending\" qa-dataid=\"aside-pending\">\n <rt-spinner />\n </div>\n }\n</div>\n","import { OverlayRef } from '@angular/cdk/overlay';\nimport { Renderer2, signal, WritableSignal } from '@angular/core';\n\nimport { Observable, Subject, take } from 'rxjs';\n\n/** Длительность slide-out анимации; синхронизирована с `--rt-aside-animation-duration` в SCSS. */\nconst LEAVE_ANIMATION_DURATION_MS: number = 200;\n\n/**\n * Жест закрытия, на который панель не закрылась: `Escape` или нажатие по подложке. Панель\n * отказывает в нём под `disableClose()` и когда жест выключен настройкой открытия.\n */\nexport type TRtAsideCloseRequest = 'escape' | 'backdrop';\n\n/**\n * Хэндл к программно открытой side-sheet (через `RtAsideService.open()`).\n *\n * Контент-компонент инжектит `RtAsideRef` и закрывает aside через `.close(result?)`.\n * Родитель ждёт результата через `.afterClosed()`.\n *\n * Generic `<T>` — тип результата `close(result)`. Дефолт `unknown` — если результат\n * не нужен, оставляй generic пустым.\n *\n * Lifecycle close():\n * 1. Race-guard `#isClosing` гарантирует один эмит даже при двойном вызове.\n * 2. Снимает `rt-aside-overlay--open` и ставит `rt-aside-overlay--leaving` на host —\n * CSS-transition (200ms) играет slide-out.\n * 3. Эмиттит result в `afterClosed()` СРАЗУ (consumer не ждёт анимацию).\n * 4. Через `setTimeout(200ms)` зовёт `overlayRef.dispose()` — overlay убран после анимации.\n *\n * Слой, снятый без `close()` — переходом по адресу (`disposeOnNavigation`) или уничтожением\n * хозяина, — тоже завершает `afterClosed()`: подписчик получает `undefined`.\n *\n * @example\n * \\`\\`\\`ts\n * // в контент-компоненте\n * private readonly asideRef = inject(RtAsideRef<{ saved: boolean }>);\n * onSave(): void { this.asideRef.close({ saved: true }); }\n *\n * // в родителе\n * asideService.open(EditAsideComponent).afterClosed().subscribe(result => {...});\n * \\`\\`\\`\n */\nexport class RtAsideRef<T = unknown> {\n /**\n * Runtime-флаг, блокирующий close() через ESC / backdrop click.\n *\n * Default `false`. Включай (`asideRef.disableClose.set(true)`) на время\n * in-flight submit'а, чтобы юзер не потерял введённые в форму данные.\n * Программный `close(result)` гейтом не задет — он всегда закрывает.\n */\n public readonly disableClose: WritableSignal<boolean> = signal(false);\n\n readonly #overlayRef: OverlayRef;\n readonly #renderer: Renderer2;\n readonly #afterClosedSource: Subject<T | undefined> = new Subject<T | undefined>();\n readonly #closeRequestsSource: Subject<TRtAsideCloseRequest>;\n\n #isClosing: boolean = false;\n\n /**\n * `closeRequestsSource` наполняет служба: она слушает жесты и знает, отказала ли в них панель.\n * Хэндл только завершает поток вместе с панелью.\n */\n constructor(\n overlayRef: OverlayRef,\n renderer: Renderer2,\n closeRequestsSource: Subject<TRtAsideCloseRequest> = new Subject<TRtAsideCloseRequest>()\n ) {\n this.#overlayRef = overlayRef;\n this.#renderer = renderer;\n this.#closeRequestsSource = closeRequestsSource;\n this.#overlayRef\n .detachments()\n .pipe(take(1))\n .subscribe((): void => this.#finish(undefined));\n }\n\n /** Закрывает overlay со slide-out анимацией; эмиттит result в `afterClosed()` синхронно. */\n public close(result?: T): void {\n if (this.#isClosing) {\n return;\n }\n this.#isClosing = true;\n\n this.#renderer.removeClass(this.#overlayRef.overlayElement, 'rt-aside-overlay--open');\n this.#renderer.addClass(this.#overlayRef.overlayElement, 'rt-aside-overlay--leaving');\n\n // Эмитим результат сразу — consumer не ждёт slide-out.\n this.#emit(result);\n\n // Дефер dispose'а на длительность анимации — overlay остаётся в DOM,\n // пока CSS-transition не отыграет slide-out.\n setTimeout((): void => {\n this.#overlayRef.dispose();\n }, LEAVE_ANIMATION_DURATION_MS);\n }\n\n /** Эмиттит один раз — результат `close(result)` или `undefined` при снятом слое — и завершается. */\n public afterClosed(): Observable<T | undefined> {\n return this.#afterClosedSource.asObservable();\n }\n\n /**\n * Жесты закрытия, в которых панель отказала: `Escape` и нажатие по подложке под\n * `disableClose()` или выключенные настройкой. Завершается вместе с панелью.\n */\n public closeRequests(): Observable<TRtAsideCloseRequest> {\n return this.#closeRequestsSource.asObservable();\n }\n\n /** Слой снят без `close()`: анимации играть не на чем, остаётся отдать результат. */\n #finish(result: T | undefined): void {\n if (this.#isClosing) {\n return;\n }\n this.#isClosing = true;\n this.#emit(result);\n }\n\n #emit(result: T | undefined): void {\n this.#afterClosedSource.next(result);\n this.#afterClosedSource.complete();\n this.#closeRequestsSource.complete();\n }\n}\n","import { InjectionToken } from '@angular/core';\n\n/**\n * Injection token для передачи данных в side-sheet, открытую через `RtAsideService.open()`.\n *\n * Пользовательский компонент-контент aside'а инжектит токен и получает данные,\n * переданные в `IRtAsideConfig.data`. Тип значения задаётся generic'ом — `inject(RT_ASIDE_DATA)`\n * вернёт `unknown`, прокастуй через `inject(RT_ASIDE_DATA) as MyDataType` ИЛИ\n * объяви токен с конкретным generic'ом в feature-либе.\n *\n * Намеренно отдельный токен от `RT_DIALOG_DATA`, чтобы type-checker не путал контексты\n * (rt-dialog vs rt-aside) — оба сервиса могут существовать в одном DI-дереве.\n *\n * @example\n * \\`\\`\\`ts\n * @Component({...})\n * export class GuestProfileAsideComponent {\n * readonly data = inject(RT_ASIDE_DATA) as { userId: number };\n * }\n *\n * // parent\n * asideService.open(GuestProfileAsideComponent, { data: { userId: 42 } });\n * \\`\\`\\`\n */\nexport const RT_ASIDE_DATA: InjectionToken<unknown> = new InjectionToken<unknown>('RT_ASIDE_DATA');\n","import { ComponentType, Overlay, OverlayConfig, OverlayRef, PositionStrategy } from '@angular/cdk/overlay';\nimport { ComponentPortal } from '@angular/cdk/portal';\nimport { DOCUMENT } from '@angular/common';\nimport { inject, DestroyRef, Injectable, Injector, Renderer2, RendererFactory2, Signal } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\n\nimport { EMPTY, map, merge, mergeMap, Observable, Subject, Subscriber, takeUntil } from 'rxjs';\n\nimport { IRtKitConfig } from '@rt-tools/ui-kit-v2/core';\nimport { rtKitDefault } from '@rt-tools/ui-kit-v2/core';\nimport { carryThemeScopeOfFocus, materialPresetClassesOfFocus } from '@rt-tools/ui-kit-v2/core';\nimport { RtAsideRef, TRtAsideCloseRequest } from './rt-aside-ref';\nimport { RT_ASIDE_DATA } from './rt-aside.tokens';\n\n/**\n * Позиционирование side-sheet — у какого края viewport \"приклеена\" панель.\n */\nexport type TRtAsidePosition = 'right' | 'left';\n\n/**\n * Конфигурация открытия side-sheet через `RtAsideService.open()`.\n */\nexport interface IRtAsideConfig<TData = unknown> {\n /** Данные, доступные внутри aside'а через `inject(RT_ASIDE_DATA)`. */\n data?: TData;\n\n /** Дополнительный CSS-класс для backdrop (поверх стандартного `rt-aside-backdrop`). */\n backdropClass?: string | string[];\n\n /** Дополнительный CSS-класс для panel-обёртки overlay (поверх `rt-aside-overlay`). */\n panelClass?: string | string[];\n\n /** Закрывать aside по клику на backdrop. По дефолту `true`. */\n closeOnBackdropClick?: boolean;\n\n /** Закрывать aside по ESC. По дефолту `true`. */\n closeOnEscape?: boolean;\n\n /** К какому краю viewport \"приклеить\" панель. По дефолту `\"right\"`. */\n position?: TRtAsidePosition;\n\n /**\n * Injector хозяина панели — обычно `inject(Injector)` открывающего компонента. Он становится\n * родителем портала: содержимое видит провайдеры хозяина. Уничтожение хозяина закрывает\n * панель через `close()`. Без него родитель — корневой injector, и панель живёт сама по себе.\n */\n injector?: Injector;\n\n /**\n * Снимать панель при переходе по адресу. По умолчанию `true`; `false` оставляет её открытой —\n * для панели, которая сама ведёт навигацию и должна пережить смену адреса.\n */\n disposeOnNavigation?: boolean;\n}\n\n/**\n * Контекст одного открытия aside'а — payload для per-open close-подписок в конструкторном\n * стриме. `asideRef` сужен до используемых handler'ами членов (`RtAsideRef<T>` инвариантен\n * по `T`, целиком в generic-agnostic контекст не влезает).\n */\ninterface IRtAsideOpenContext {\n overlayRef: OverlayRef;\n asideRef: {\n disableClose: Signal<boolean>;\n close: () => void;\n };\n closeRequests: Subject<TRtAsideCloseRequest>;\n /** DestroyRef хозяина из `config.injector`; его уничтожение закрывает панель. */\n ownerDestroyRef: DestroyRef | null;\n closeOnBackdropClick: boolean;\n closeOnEscape: boolean;\n}\n\n/**\n * Программный API для открытия side-sheet поверх CDK Overlay.\n *\n * Архитектурно зеркалит `RtDialogService`, отличаясь только positionStrategy\n * (edge-anchored: `right('0')` / `left('0')` + `top('0')`) и slide-in анимацией,\n * которая триггерится через CSS-классы на overlay host (`Renderer2` инжектится\n * через `RendererFactory2`, потому что в service нет ComponentRef).\n *\n * Lifecycle:\n * 1. `overlay.create({ panelClass: [..., '--entering'] })` — host получает initial\n * CSS-стейт (translateX(±100%)).\n * 2. `overlayRef.attach(portal)` — контент-компонент монтируется в host.\n * 3. На следующий paint (`requestAnimationFrame`) сервис снимает `--entering` и\n * добавляет `--open` — CSS-transition сделает slide-in.\n * 4. На `ref.close(result)` ref добавляет `--leaving`, эмиттит result синхронно,\n * deferred `dispose()` через 200ms (длительность анимации).\n *\n * Альтернатива rt-dialog — для длинных форм / мастеров / side-panels, где\n * центрированная модалка ощущается тесной. Не предназначено для подтверждений\n * или коротких диалогов (для этого — `RtDialogService`).\n *\n * @example\n * \\`\\`\\`ts\n * private readonly asideService = inject(RtAsideService);\n *\n * openProfile(userId: number): void {\n * const ref = this.asideService.open<GuestProfileAsideComponent, { userId: number }, void>(\n * GuestProfileAsideComponent,\n * { data: { userId }, position: \"right\" }\n * );\n * ref.afterClosed().subscribe(() => this.reloadList());\n * }\n *\n * // контент-компонент\n * @Component({ ... })\n * export class GuestProfileAsideComponent {\n * readonly data = inject(RT_ASIDE_DATA) as { userId: number };\n * readonly #ref = inject(RtAsideRef);\n * close(): void { this.#ref.close(); }\n * }\n * \\`\\`\\`\n */\n@Injectable({ providedIn: 'root' })\nexport class RtAsideService {\n readonly #overlay: Overlay = inject(Overlay);\n readonly #injector: Injector = inject(Injector);\n readonly #document: Document = inject(DOCUMENT);\n // Renderer2 нужен для манипуляции CSS-классами на overlay.hostElement.\n // В service нет ComponentRef, поэтому Renderer2 берём через factory.\n readonly #renderer: Renderer2 = inject(RendererFactory2).createRenderer(null, null);\n\n readonly #openSource: Subject<IRtAsideOpenContext> = new Subject<IRtAsideOpenContext>();\n\n /* Закрывает ли `Escape` штору, когда вызов об этом промолчал. Умолчание кита — закрывает;\n приложению, которому это мешает, иначе пришлось бы писать отказ на каждом вызове, и\n забытый вызов отличался бы от остальных. */\n readonly #closeOnEscape: boolean = rtKitDefault('aside', (it: IRtKitConfig.Aside): boolean | undefined => it.closeOnEscape, true);\n\n constructor() {\n // Per-open close-подписки (backdrop / ESC) живут в одном постоянном\n // конструкторном стриме: open() эмитит контекст открытия, mergeMap\n // подписывает event-стримы конкретного OverlayRef до его dispose\n // (`detachments()` терминирует внутренний поток). mergeMap, а не\n // switchMap — одновременно может быть открыто несколько aside'ов.\n //\n // Runtime-guard через asideRef.disableClose() — те же правила, что в\n // rt-dialog: ESC / backdrop НЕ закрывают overlay, если disableClose=true;\n // программный ref.close() гейтом не задет.\n this.#openSource\n .pipe(\n mergeMap((openContext: IRtAsideOpenContext): Observable<() => void> => {\n // Оба жеста слушаются всегда: отказ в закрытии — тоже ответ, и он уходит в\n // `closeRequests()` хэндла, будь то `disableClose` или выключенный жест.\n const backdropClose$: Observable<() => void> = openContext.overlayRef.backdropClick().pipe(\n map((): (() => void) => (): void => {\n this.#answerCloseGesture(openContext, 'backdrop', openContext.closeOnBackdropClick);\n })\n );\n const escapeClose$: Observable<() => void> = openContext.overlayRef.keydownEvents().pipe(\n map((event: KeyboardEvent): (() => void) => (): void => {\n if (event.key !== 'Escape') {\n return;\n }\n if (openContext.closeOnEscape) {\n event.preventDefault();\n }\n this.#answerCloseGesture(openContext, 'escape', openContext.closeOnEscape);\n })\n );\n const ownerClose$: Observable<() => void> = this.#destroyed(openContext.ownerDestroyRef).pipe(\n map((): (() => void) => (): void => openContext.asideRef.close())\n );\n return merge(backdropClose$, escapeClose$, ownerClose$).pipe(takeUntil(openContext.overlayRef.detachments()));\n }),\n takeUntilDestroyed()\n )\n .subscribe((handleCloseIntent: () => void): void => handleCloseIntent());\n }\n\n public open<TComponent, TData = unknown, TResult = unknown>(\n component: ComponentType<TComponent>,\n config?: IRtAsideConfig<TData>\n ): RtAsideRef<TResult> {\n const position: TRtAsidePosition = config?.position ?? 'right';\n const positionStrategy: PositionStrategy =\n position === 'right'\n ? this.#overlay.position().global().right('0').top('0')\n : this.#overlay.position().global().left('0').top('0');\n\n const panelClasses: string[] = [\n 'rt-aside-overlay',\n `rt-aside-overlay--position-${position}`,\n 'rt-aside-overlay--entering',\n ...materialPresetClassesOfFocus(this.#document),\n ];\n\n const overlayConfig: OverlayConfig = {\n positionStrategy,\n scrollStrategy: this.#overlay.scrollStrategies.block(),\n hasBackdrop: true,\n backdropClass: config?.backdropClass ?? 'rt-aside-backdrop',\n panelClass: config?.panelClass !== undefined ? [...panelClasses, ...this.#toArray(config.panelClass)] : panelClasses,\n disposeOnNavigation: config?.disposeOnNavigation ?? true,\n };\n\n const overlayRef: OverlayRef = this.#overlay.create(overlayConfig);\n // До attach: содержимое панели может забрать фокус, и кусок темы у кнопки будет потерян.\n carryThemeScopeOfFocus(overlayRef.overlayElement, this.#document);\n const closeRequests: Subject<TRtAsideCloseRequest> = new Subject<TRtAsideCloseRequest>();\n const asideRef: RtAsideRef<TResult> = new RtAsideRef<TResult>(overlayRef, this.#renderer, closeRequests);\n\n // Сами close-подписки объявлены один раз в конструкторе — здесь только\n // эмит контекста открытия (см. #openSource-стрим).\n this.#openSource.next({\n overlayRef,\n asideRef,\n closeRequests,\n ownerDestroyRef: config?.injector?.get(DestroyRef, null) ?? null,\n closeOnBackdropClick: config?.closeOnBackdropClick !== false,\n closeOnEscape: config?.closeOnEscape ?? this.#closeOnEscape,\n });\n\n const injector: Injector = Injector.create({\n parent: config?.injector ?? this.#injector,\n providers: [\n { provide: RtAsideRef, useValue: asideRef },\n { provide: RT_ASIDE_DATA, useValue: config?.data ?? null },\n ],\n });\n\n const portal: ComponentPortal<TComponent> = new ComponentPortal<TComponent>(component, null, injector);\n overlayRef.attach(portal);\n\n // Slide-in: на следующий paint снимаем --entering и добавляем --open.\n // CSS transition (см. rt-aside.component.scss) сделает анимацию.\n // Backdrop default hidden — toggle --visible одновременно со слайдом.\n // Панель могли снять до кадра — `close()`, переходом или уничтожением хозяина в том же такте.\n requestAnimationFrame((): void => {\n if (!overlayRef.hasAttached()) {\n return;\n }\n this.#renderer.removeClass(overlayRef.overlayElement, 'rt-aside-overlay--entering');\n this.#renderer.addClass(overlayRef.overlayElement, 'rt-aside-overlay--open');\n const backdropEl: HTMLElement | null = overlayRef.backdropElement;\n if (backdropEl !== null) {\n this.#renderer.addClass(backdropEl, 'rt-aside-backdrop--visible');\n }\n });\n\n return asideRef;\n }\n\n /** Жест закрытия: закрывает панель, если жест включён и не держит `disableClose`, иначе сообщает об отказе. */\n #answerCloseGesture(openContext: IRtAsideOpenContext, gesture: TRtAsideCloseRequest, enabled: boolean): void {\n if (enabled && !openContext.asideRef.disableClose()) {\n openContext.asideRef.close();\n return;\n }\n openContext.closeRequests.next(gesture);\n }\n\n /**\n * Уничтожение хозяина как поток. Отписка — уход панели — снимает слушателя с хозяина, и его\n * позднее уничтожение панель уже не трогает.\n */\n #destroyed(ownerDestroyRef: DestroyRef | null): Observable<void> {\n if (ownerDestroyRef === null) {\n return EMPTY;\n }\n return new Observable<void>((subscriber: Subscriber<void>): (() => void) =>\n ownerDestroyRef.onDestroy((): void => subscriber.next())\n );\n }\n\n /** Нормализует `string | string[]` к массиву (panelClass поддерживает оба формата). */\n #toArray(value: string | string[]): string[] {\n return Array.isArray(value) ? value : [value];\n }\n}\n","import { BooleanInput } from '@angular/cdk/coercion';\nimport {\n booleanAttribute,\n inject,\n input,\n output,\n ChangeDetectionStrategy,\n Component,\n InputSignal,\n InputSignalWithTransform,\n OutputEmitterRef,\n Signal,\n ViewEncapsulation,\n} from '@angular/core';\n\nimport { BlockDirective, ElemDirective } from '@rt-tools/core';\n\nimport { RT_KIT_LABELS, TRtKitLabelMap } from '@rt-tools/ui-kit-v2/core';\nimport { RtIconButtonComponent } from '@rt-tools/ui-kit-v2/icon-button';\nimport { RtSkeletonWrapperComponent } from '@rt-tools/ui-kit-v2/skeleton-wrapper';\nimport { RtTagComponent } from '@rt-tools/ui-kit-v2/tag';\nimport { RtAsideRef } from '../rt-aside-ref';\nimport { IRtAsideHeader } from './rt-aside-header.model';\n\nconst BEM_BLOCK: string = 'rt-aside-header';\n\n/**\n * Заголовок side-sheet'а; работает в двух modes:\n *\n * 1. **Programmatic** (через `RtAsideService.open()`) — `RtAsideRef` провайдится\n * оверлей-инжектором; стрелка «назад» дёргает `asideRef.close()` напрямую,\n * consumer подписан на `afterClosed()`.\n * 2. **Route-driven** (через `[rtContainerRightSidenavPanel]` driver в\n * rt-container'е) — `RtAsideRef` НЕ провайдится (overlay принадлежит\n * rt-container'у, не RtAsideService). Consumer слушает `(dismiss)` output и\n * сам зовёт `panel.close()`.\n *\n * Оба пути живут одновременно: `onDismiss()` сперва эмитит output, затем (если\n * есть) дёргает optional `asideRef.close()`.\n *\n * Доменные действия (отклонить / удалить / снять с публикации и т.п.)\n * проецируются в слот `[asideActions]` как ряд `rt-icon-button` справа от title.\n *\n * Слот `[asideHeaderContent]` — строка под заголовком на всю ширину шапки: поиск, вкладки,\n * сводка. Пустой слот места не занимает.\n *\n * Слот `[asideHeadingContent]` — содержимое в колонке заголовка, под подписью и бэйджами, по её\n * левому краю. Пустой слот ничего не рисует.\n *\n * `ViewEncapsulation.None` — для единообразия с rt-dialog-header (стили\n * префиксованы `.rt-aside-header`).\n *\n * @example\n * ```html\n * <rt-aside-header title=\"Профиль пользователя\" />\n * <rt-aside-header title=\"Создание записи\" [closable]=\"false\" />\n * <rt-aside-header title=\"Детали\" (dismiss)=\"onClose()\">\n * <rt-icon-button asideActions icon=\"ico-error\" variant=\"danger\"\n * ariaLabel=\"Отклонить\" (clicked)=\"reject()\" />\n * </rt-aside-header>\n * ```\n */\n@Component({\n selector: 'rt-aside-header',\n templateUrl: './rt-aside-header.component.html',\n styleUrl: './rt-aside-header.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [\n // standalone components / directives\n RtIconButtonComponent,\n RtSkeletonWrapperComponent,\n RtTagComponent,\n BlockDirective,\n ElemDirective,\n ],\n host: {\n class: BEM_BLOCK,\n },\n})\nexport class RtAsideHeaderComponent {\n readonly #asideRef: RtAsideRef | null = inject(RtAsideRef, { optional: true });\n\n /** Текст заголовка — рендерится в `<h2>`. Null/пустая строка вместе с\n * `[loading]=true` показывают skeleton вместо title. */\n protected readonly t: Signal<TRtKitLabelMap> = inject(RT_KIT_LABELS);\n\n public readonly title: InputSignal<string | null> = input<string | null>(null);\n\n /**\n * Мелкий приглушённый надзаголовок НАД основным title (контекст действия:\n * «Создание записи», «Запись № 6»). Null (дефолт) — обычный\n * однострочный хедер. Overline не участвует в skeleton-режиме — это\n * статичный текст, известный до загрузки данных.\n */\n public readonly overline: InputSignal<string | null> = input<string | null>(null);\n\n /**\n * Приглушённая подпись ПОД заголовком: что делается в панели. Null (дефолт) —\n * строки нет. Как надзаголовок, в skeleton-режиме не участвует.\n */\n public readonly subtitle: InputSignal<string | null> = input<string | null>(null);\n\n /**\n * Ряд бэйджей (`rt-tag`) под заголовком — контекст сущности (статус, метка\n * записи и т.п.). Пустой массив (дефолт) — ряд не рендерится. Async-данные:\n * потребитель отдаёт `[]` пока сущность не зарезолвилась.\n */\n public readonly badges: InputSignal<readonly IRtAsideHeader.Badge[]> = input<readonly IRtAsideHeader.Badge[]>([]);\n\n /** Показывать кнопку закрытия в углу заголовка. Дефолт `true`. */\n public readonly closable: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(true, {\n transform: booleanAttribute,\n });\n\n /** Показывать skeleton вместо title (loading-state aside'а пока данные\n * резолвятся через async fasade). Дефолт `false`. */\n public readonly loading: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n /**\n * Эмитится при клике по стрелке «назад» — намерение пользователя закрыть\n * aside (intent, не «закрыто»). Route-driven consumer слушает этот output и\n * дёргает `panel.close()` сам. Имя `dismiss` вместо `close` — ESLint\n * `@angular-eslint/no-output-native` блокирует output с именем стандартного\n * DOM-события (`close`).\n */\n public readonly dismiss: OutputEmitterRef<void> = output<void>();\n\n protected onDismiss(): void {\n // Сначала output — route-driven consumer обработает intent.\n this.dismiss.emit();\n // Затем (если есть) AsideRef — programmatic mode через RtAsideService.\n this.#asideRef?.close();\n }\n}\n","<ng-container rtBlock=\"rt-aside-header\">\n <header rtElem=\"content\" qa-dataid=\"aside-header\">\n @if (closable()) {\n <rt-icon-button\n rtElem=\"back\"\n qa-dataid=\"aside-back\"\n icon=\"arrow-left\"\n size=\"sm\"\n variant=\"ghost\"\n [ariaLabel]=\"t().uiBack\"\n [tooltip]=\"t().uiBack\"\n (clicked)=\"onDismiss()\" />\n }\n\n <div rtElem=\"heading\">\n @if (overline(); as overlineText) {\n <p rtElem=\"overline\" qa-dataid=\"aside-overline\">{{ overlineText }}</p>\n }\n <h2 rtElem=\"title\" qa-dataid=\"aside-title\">\n <rt-skeleton-wrapper width=\"180px\" height=\"20px\" [isLoading]=\"loading()\">\n {{ title() }}\n </rt-skeleton-wrapper>\n </h2>\n\n @if (subtitle(); as subtitleText) {\n <p rtElem=\"subtitle\" qa-dataid=\"aside-subtitle\">{{ subtitleText }}</p>\n }\n\n @if (badges().length > 0) {\n <div rtElem=\"badges\">\n @for (badge of badges(); track badge.value) {\n @if (badge.href; as href) {\n <a rtElem=\"badge-link\" qa-dataid=\"aside-badge-link\" target=\"_blank\" rel=\"noopener\" [href]=\"href\">\n <rt-tag\n iconEnd=\"external-link\"\n [value]=\"badge.value\"\n [severity]=\"badge.severity ?? 'neutral'\"\n [appearance]=\"badge.appearance ?? 'solid'\"\n [radius]=\"badge.radius ?? 'md'\"\n [icon]=\"badge.icon ?? null\" />\n </a>\n } @else {\n <rt-tag\n [value]=\"badge.value\"\n [severity]=\"badge.severity ?? 'neutral'\"\n [appearance]=\"badge.appearance ?? 'solid'\"\n [radius]=\"badge.radius ?? 'md'\"\n [icon]=\"badge.icon ?? null\" />\n }\n }\n </div>\n }\n\n <ng-content select=\"[asideHeadingContent]\" />\n </div>\n\n <div rtElem=\"actions\" qa-dataid=\"aside-actions\">\n <ng-content select=\"[asideActions]\" />\n </div>\n </header>\n\n <div rtElem=\"row\" qa-dataid=\"aside-header-row\">\n <ng-content select=\"[asideHeaderContent]\" />\n </div>\n</ng-container>\n","import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';\n\nimport { BlockDirective, ElemDirective } from '@rt-tools/core';\n\nconst BEM_BLOCK: string = 'rt-aside-footer';\n\n/**\n * Footer-слот для композиции внутри `<rt-aside>`. Две зоны через именованные\n * слоты: `[asideDismiss]` (слева — «Закрыть», `appearance=\"text\"`, всегда) и\n * `[asidePrimary]` (справа — единственный позитивный глагол: Сохранить /\n * Отправить / Опубликовать, если есть). Раскладка `justify-content:\n * space-between`. Доменные/деструктивные действия в футер не кладём — они\n * проецируются иконками в `<rt-aside-header>` слот `[asideActions]`.\n *\n * Только две именованные зоны — безатрибутный контент НЕ проецируется (контент\n * без `asideDismiss`/`asidePrimary` не отрендерится).\n *\n * `ViewEncapsulation.None` — для единообразия с rt-aside.\n *\n * @example\n * \\`\\`\\`html\n * <rt-aside-footer>\n * <button rtButton asideDismiss appearance=\"text\" (click)=\"onClose()\">Закрыть</button>\n * <button rtButton asidePrimary theme=\"primary\" (click)=\"save()\">Сохранить</button>\n * </rt-aside-footer>\n * \\`\\`\\`\n */\n@Component({\n selector: 'rt-aside-footer',\n templateUrl: './rt-aside-footer.component.html',\n styleUrl: './rt-aside-footer.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [BlockDirective, ElemDirective],\n host: {\n class: BEM_BLOCK,\n },\n})\nexport class RtAsideFooterComponent {}\n","<ng-container rtBlock=\"rt-aside-footer\">\n <footer rtElem=\"content\" qa-dataid=\"aside-footer\">\n <ng-content select=\"[asideDismiss]\" />\n <ng-content select=\"[asidePrimary]\" />\n </footer>\n</ng-container>\n","import { Signal } from '@angular/core';\n\n/** Что делает панель, когда её просят закрыться. */\nexport enum ERtAsideCloseIntent {\n /** Ничего: идёт запись, и закрытие посреди неё потеряло бы её результат. */\n Ignore = 'ignore',\n /** Закрыться сразу: терять нечего. */\n Close = 'close',\n /** Спросить, что сделать с несохранёнными правками. */\n Ask = 'ask',\n}\n\n/** Исход окна с несохранёнными правками. */\nexport enum ERtAsideUnsavedOutcome {\n /** Закрыть без сохранения. */\n Discard = 'discard',\n /** Закрыть с сохранением. */\n Save = 'save',\n /** Остаться в панели. */\n Stay = 'stay',\n}\n\n/**\n * Гард закрытия, который панель ставит на себя: признак нетронутости формы и\n * запись, которую зовёт исход «закрыть с сохранением».\n */\nexport interface IRtAsideUnsavedGuard {\n pristine: Signal<boolean>;\n save: () => void;\n}\n\n/** Состояние панели на момент намерения закрыть её. */\nexport interface IRtAsideCloseState {\n submitting: boolean;\n guarded: boolean;\n pristine: boolean;\n}\n\n/**\n * Решение по намерению закрыть панель. Одно на все четыре пути закрытия: кнопку\n * в шапке, кнопку в футере, нажатие мимо панели и клавишу Esc — иначе Esc обошёл\n * бы то, что ловит кнопка.\n */\nexport function resolveCloseIntent(state: IRtAsideCloseState): ERtAsideCloseIntent {\n if (state.submitting) {\n return ERtAsideCloseIntent.Ignore;\n }\n\n return state.guarded && !state.pristine ? ERtAsideCloseIntent.Ask : ERtAsideCloseIntent.Close;\n}\n","import { inject, ChangeDetectionStrategy, Component, Signal, ViewEncapsulation } from '@angular/core';\n\nimport { BlockDirective, ElemDirective } from '@rt-tools/core';\n\nimport { RT_KIT_LABELS, TRtKitLabelMap } 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 { RtDialogComponent } from '@rt-tools/ui-kit-v2/dialog';\nimport { ERtAsideUnsavedOutcome } from './rt-aside-unsaved.logic';\n\nconst BEM_BLOCK: string = 'rt-aside-unsaved-dialog';\n\n/**\n * Окно, которое видит пользователь, закрывая панель с несохранёнными правками. Три\n * исхода: закрыть без сохранения, закрыть с сохранением, остаться. Открывает его\n * общая основа асайда, поэтому наружу компонент не экспортируется.\n *\n * Отказ от правок подан красной кнопкой: это единственный исход, после которого\n * введённое не вернуть.\n */\n@Component({\n selector: 'rt-aside-unsaved-dialog',\n templateUrl: './rt-aside-unsaved-dialog.component.html',\n styleUrl: './rt-aside-unsaved-dialog.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [\n // standalone components / directives\n RtButtonDirective,\n RtDialogComponent,\n BlockDirective,\n ElemDirective,\n ],\n host: {\n class: BEM_BLOCK,\n },\n})\nexport class RtAsideUnsavedDialogComponent {\n readonly #dialogRef: RtDialogRef<ERtAsideUnsavedOutcome> = inject(RtDialogRef);\n\n protected readonly t: Signal<TRtKitLabelMap> = inject(RT_KIT_LABELS);\n\n protected onDiscard(): void {\n this.#dialogRef.close(ERtAsideUnsavedOutcome.Discard);\n }\n\n protected onSave(): void {\n this.#dialogRef.close(ERtAsideUnsavedOutcome.Save);\n }\n\n protected onStay(): void {\n this.#dialogRef.close(ERtAsideUnsavedOutcome.Stay);\n }\n}\n","<rt-dialog width=\"440px\" qa-dataid=\"aside-unsaved-dialog\" [ariaLabel]=\"t().uiUnsavedTitle\">\n <div rtBlock=\"rt-aside-unsaved-dialog\">\n <div rtElem=\"text\">\n <h2 rtElem=\"title\" qa-dataid=\"aside-unsaved-title\">{{ t().uiUnsavedTitle }}</h2>\n <p rtElem=\"message\" qa-dataid=\"aside-unsaved-message\">{{ t().uiUnsavedMessage }}</p>\n </div>\n\n <div rtElem=\"footer\">\n <button\n rtButton\n qa-dataid=\"aside-unsaved-discard\"\n type=\"button\"\n theme=\"danger\"\n appearance=\"outlined\"\n [label]=\"t().uiUnsavedDiscard\"\n [attr.aria-label]=\"t().uiUnsavedDiscard\"\n (click)=\"onDiscard()\"></button>\n <button\n rtButton\n qa-dataid=\"aside-unsaved-stay\"\n type=\"button\"\n theme=\"secondary\"\n appearance=\"text\"\n [label]=\"t().uiUnsavedStay\"\n [attr.aria-label]=\"t().uiUnsavedStay\"\n (click)=\"onStay()\"></button>\n <button\n rtButton\n qa-dataid=\"aside-unsaved-save\"\n type=\"button\"\n appearance=\"filled\"\n [label]=\"t().uiUnsavedSave\"\n [attr.aria-label]=\"t().uiUnsavedSave\"\n (click)=\"onSave()\"></button>\n </div>\n </div>\n</rt-dialog>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rt-tools-ui-kit-v2-aside.mjs","names":["BEM_BLOCK","BEM_BLOCK","BEM_BLOCK","BEM_BLOCK"],"sources":["../../../projects/ui-kit-v2/src/lib/components/aside/error-box/rt-aside-error-box.logic.ts","../../../projects/ui-kit-v2/src/lib/components/aside/error-box/rt-aside-error-box.component.ts","../../../projects/ui-kit-v2/src/lib/components/aside/error-box/rt-aside-error-box.component.html","../../../projects/ui-kit-v2/src/lib/components/aside/rt-aside.component.ts","../../../projects/ui-kit-v2/src/lib/components/aside/rt-aside.component.html","../../../projects/ui-kit-v2/src/lib/components/aside/rt-aside-ref.ts","../../../projects/ui-kit-v2/src/lib/components/aside/rt-aside.tokens.ts","../../../projects/ui-kit-v2/src/lib/components/aside/rt-aside.service.ts","../../../projects/ui-kit-v2/src/lib/components/aside/header/rt-aside-header.component.ts","../../../projects/ui-kit-v2/src/lib/components/aside/header/rt-aside-header.component.html","../../../projects/ui-kit-v2/src/lib/components/aside/footer/rt-aside-footer.component.ts","../../../projects/ui-kit-v2/src/lib/components/aside/footer/rt-aside-footer.component.html","../../../projects/ui-kit-v2/src/lib/components/aside/unsaved-dialog/rt-aside-unsaved.logic.ts","../../../projects/ui-kit-v2/src/lib/components/aside/unsaved-dialog/rt-aside-unsaved-dialog.component.ts","../../../projects/ui-kit-v2/src/lib/components/aside/unsaved-dialog/rt-aside-unsaved-dialog.component.html"],"sourcesContent":["function errorAsText(error: unknown): string {\n try {\n return JSON.stringify(error) ?? String(error);\n } catch {\n return String(error);\n }\n}\n\n/**\n * Текст, который кнопка блока ошибки кладёт в буфер обмена.\n *\n * Вид повторяет первый кит — `Error time: <дата>_<время>;Error info: <JSON>`: кто разбирал такие\n * копии раньше, читает их так же. Ошибку с петлёй внутри `JSON.stringify` не пишет и бросает —\n * тогда в копию идёт её строковый вид, и нажатие не падает.\n */\nexport function rtAsideErrorCopyText(error: unknown, now: Date): string {\n return `Error time: ${now.toDateString()}_${now.toTimeString()};Error info: ${errorAsText(error)}`;\n}\n","import { Clipboard } from '@angular/cdk/clipboard';\nimport {\n ChangeDetectionStrategy,\n Component,\n computed,\n DestroyRef,\n inject,\n input,\n InputSignal,\n signal,\n Signal,\n ViewEncapsulation,\n WritableSignal,\n} from '@angular/core';\n\nimport { BlockDirective, ElemDirective } from '@rt-tools/core';\n\nimport { rtKitLabel } from '@rt-tools/ui-kit-v2/core';\nimport { RtButtonDirective } from '@rt-tools/ui-kit-v2/button';\nimport { rtAsideErrorCopyText } from './rt-aside-error-box.logic';\n\nconst BEM_BLOCK: string = 'rt-aside-error-box';\n\n/** Сколько кнопка держит «скопировано» — секунда, как у первого кита. */\nconst CONFIRM_DELAY_MS: number = 1000;\n\n/**\n * Блок ошибки запроса боковой панели: надпись и кнопка, которая кладёт в буфер обмена время и\n * ошибку. Перенос `rtui-aside-error-box` первого кита без Material.\n *\n * Обычно его рисует сама панель по входу `requestError`; отдельно он нужен приложению, которое\n * показывает ошибку в другом месте.\n */\n@Component({\n selector: 'rt-aside-error-box',\n templateUrl: './rt-aside-error-box.component.html',\n styleUrl: './rt-aside-error-box.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [RtButtonDirective, BlockDirective, ElemDirective],\n host: {\n class: BEM_BLOCK,\n },\n})\nexport class RtAsideErrorBoxComponent {\n readonly #clipboard: Clipboard = inject(Clipboard);\n readonly #destroyRef: DestroyRef = inject(DestroyRef);\n\n #confirmTimer: ReturnType<typeof setTimeout> | null = null;\n\n readonly #copyLabel: Signal<string> = rtKitLabel('asideCopyErrorInfo');\n readonly #copiedLabel: Signal<string> = rtKitLabel('uiCopied');\n\n protected readonly title: Signal<string> = rtKitLabel('asideRequestError');\n\n protected readonly copied: WritableSignal<boolean> = signal(false);\n\n protected readonly buttonLabel: Signal<string> = computed((): string => (this.copied() ? this.#copiedLabel() : this.#copyLabel()));\n\n protected readonly buttonIcon: Signal<string | null> = computed((): string | null => (this.copied() ? 'check' : null));\n\n /** Ошибка, пришедшая от неудавшегося запроса: уходит в копию как JSON. */\n public readonly error: InputSignal<unknown> = input.required<unknown>();\n\n constructor() {\n this.#destroyRef.onDestroy((): void => this.#clearTimer());\n }\n\n protected onCopy(): void {\n this.#clipboard.copy(rtAsideErrorCopyText(this.error(), new Date()));\n this.copied.set(true);\n\n this.#clearTimer();\n this.#confirmTimer = setTimeout((): void => this.copied.set(false), CONFIRM_DELAY_MS);\n }\n\n #clearTimer(): void {\n if (this.#confirmTimer !== null) {\n clearTimeout(this.#confirmTimer);\n this.#confirmTimer = null;\n }\n }\n}\n","<ng-container rtBlock=\"rt-aside-error-box\">\n <div rtElem=\"frame\" qa-dataid=\"aside-error-box\">\n <span rtElem=\"title\" qa-dataid=\"aside-error-title\">{{ title() }}</span>\n\n <button\n rtButton\n rtElem=\"copy\"\n qa-dataid=\"aside-error-copy\"\n type=\"button\"\n theme=\"secondary\"\n appearance=\"text\"\n size=\"sm\"\n [label]=\"buttonLabel()\"\n [icon]=\"buttonIcon()\"\n [attr.aria-label]=\"buttonLabel()\"\n (click)=\"onCopy()\"></button>\n </div>\n</ng-container>\n","import { FocusTrap, FocusTrapFactory } from '@angular/cdk/a11y';\nimport { BooleanInput } from '@angular/cdk/coercion';\nimport { DOCUMENT } from '@angular/common';\nimport {\n afterRenderEffect,\n booleanAttribute,\n computed,\n inject,\n input,\n untracked,\n viewChild,\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ElementRef,\n InputSignal,\n InputSignalWithTransform,\n Signal,\n ViewEncapsulation,\n} from '@angular/core';\n\nimport { BlockDirective, ElemDirective, ModDirective } from '@rt-tools/core';\n\nimport { RtSpinnerComponent } from '@rt-tools/ui-kit-v2/spinner';\nimport { RtAsideErrorBoxComponent } from './error-box/rt-aside-error-box.component';\n\nconst BEM_BLOCK: string = 'rt-aside';\n\n/**\n * Варианты ширины side-sheet:\n * - `sm` — 400px (`--rt-aside-width-sm`)\n * - `md` — 540px (`--rt-aside-width-md`, дефолт)\n * - `lg` — 720px (`--rt-aside-width-lg`)\n */\nexport type TRtAsideSize = 'sm' | 'md' | 'lg';\n\n/**\n * Раскладка контентной зоны side-sheet:\n * - `default` — контентная зона сама скроллится при overflow (штатный режим);\n * - `tabs` — контент — это `rt-tabs`: внешняя зона перестаёт быть скроллером,\n * полоса вкладок прибита, скроллится только контент активной вкладки\n * (`rt-tabs__content`).\n */\nexport type TRtAsideContentLayout = 'default' | 'tabs';\n\n/**\n * Презентационный styled-frame для side-sheet.\n *\n * Видимость / backdrop / scroll-block / ESC / slide-in анимация — НЕ внутренняя\n * ответственность компонента. Это обеспечивает CDK Overlay через\n * `RtAsideService.open()`. rt-aside здесь — стилизованная \"рамка\" с size-вариантами\n * и тремя слотами: `rt-aside-header`, содержимое и `rt-aside-footer`.\n *\n * Содержимое лежит в одной зоне `.rt-aside__content` — она и держит инсет панели,\n * и прокручивается. Пока зоны не было, каждый корневой узел содержимого получал\n * инсет и прокрутку сам по себе: разделы делили высоту панели между собой, а\n * обводка фокуса поля срезалась краем такого мини-скроллера.\n *\n * Семантика — `role=\"complementary\"` (mirror rt-dialog имеет `role=\"dialog\"`,\n * aside — другая роль, side-sheet не обязательно блокирующий).\n *\n * `ViewEncapsulation.None` — стили префиксованы `.rt-aside`, чтобы соседние\n * rt-aside-header / rt-aside-footer работали с одним BEM-блоком без\n * cross-encapsulation хака.\n */\n@Component({\n selector: 'rt-aside',\n templateUrl: './rt-aside.component.html',\n styleUrl: './rt-aside.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [BlockDirective, ElemDirective, ModDirective, RtAsideErrorBoxComponent, RtSpinnerComponent],\n host: {\n class: BEM_BLOCK,\n },\n})\nexport class RtAsideComponent {\n readonly #focusTrapFactory: FocusTrapFactory = inject(FocusTrapFactory);\n readonly #document: Document = inject(DOCUMENT);\n\n #focusTrap: FocusTrap | null = null;\n #restoreFocusTo: HTMLElement | null = null;\n\n protected readonly frame: Signal<ElementRef<HTMLElement>> = viewChild.required<ElementRef<HTMLElement>>('frame');\n\n protected readonly hasRequestError: Signal<boolean> = computed(\n (): boolean => this.requestError() !== null && this.requestError() !== undefined\n );\n\n public readonly size: InputSignal<TRtAsideSize> = input<TRtAsideSize>('md');\n public readonly contentLayout: InputSignal<TRtAsideContentLayout> = input<TRtAsideContentLayout>('default');\n public readonly width: InputSignal<string | null> = input<string | null>(null);\n public readonly ariaLabel: InputSignal<string | null> = input<string | null>(null);\n\n /**\n * Ошибка неудавшегося запроса. Пока она есть, между шапкой и содержимым стоит блок ошибки с\n * кнопкой копирования; `null` и `undefined` — ошибки нет, любое другое значение её показывает.\n */\n public readonly requestError: InputSignal<unknown> = input<unknown>(null);\n\n /**\n * Держать Tab внутри панели. При включении фокус уходит на `[cdkFocusInitial]` или первый\n * элемент под Tab, а когда панель уходит или вход гаснет, возвращается туда, где стоял.\n */\n public readonly trapFocus: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n /** Идёт запрос: панель накрыта слоем с крутилкой и помечена занятой. */\n public readonly pending: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n constructor() {\n // После отрисовки: до неё у рамки нет ни полей, ни кнопок, и ловить фокус не на чем.\n afterRenderEffect((): void => {\n const enabled: boolean = this.trapFocus();\n const frame: HTMLElement = this.frame().nativeElement;\n untracked((): void => (enabled ? this.#holdFocus(frame) : this.#releaseFocus()));\n });\n inject(DestroyRef).onDestroy((): void => this.#releaseFocus());\n }\n\n #holdFocus(frame: HTMLElement): void {\n if (this.#focusTrap !== null) {\n return;\n }\n const focused: Element | null = this.#document.activeElement;\n this.#restoreFocusTo = focused instanceof HTMLElement ? focused : null;\n this.#focusTrap = this.#focusTrapFactory.create(frame);\n void this.#focusTrap.focusInitialElementWhenReady();\n }\n\n #releaseFocus(): void {\n if (this.#focusTrap === null) {\n return;\n }\n this.#focusTrap.destroy();\n this.#focusTrap = null;\n const restoreTo: HTMLElement | null = this.#restoreFocusTo;\n this.#restoreFocusTo = null;\n if (restoreTo?.isConnected === true) {\n restoreTo.focus();\n }\n }\n}\n","<div\n #frame\n rtBlock=\"rt-aside\"\n qa-dataid=\"aside\"\n role=\"complementary\"\n [rtMod]=\"{ size: size(), content: contentLayout() }\"\n [style.--rt-aside-width]=\"width()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-busy]=\"pending() || null\">\n <ng-content select=\"rt-aside-header\" />\n\n @if (hasRequestError()) {\n <rt-aside-error-box rtElem=\"error\" [error]=\"requestError()\" />\n }\n\n <div rtElem=\"content\" qa-dataid=\"aside-content\">\n <ng-content />\n </div>\n\n <ng-content select=\"rt-aside-footer\" />\n\n @if (pending()) {\n <div rtElem=\"pending\" qa-dataid=\"aside-pending\">\n <rt-spinner />\n </div>\n }\n</div>\n","import { OverlayRef } from '@angular/cdk/overlay';\nimport { Renderer2, signal, WritableSignal } from '@angular/core';\n\nimport { Observable, Subject, take } from 'rxjs';\n\n/** Длительность slide-out анимации; синхронизирована с `--rt-aside-animation-duration` в SCSS. */\nconst LEAVE_ANIMATION_DURATION_MS: number = 200;\n\n/**\n * Жест закрытия, на который панель не закрылась: `Escape` или нажатие по подложке. Панель\n * отказывает в нём под `disableClose()` и когда жест выключен настройкой открытия.\n */\nexport type TRtAsideCloseRequest = 'escape' | 'backdrop';\n\n/**\n * Хэндл к программно открытой side-sheet (через `RtAsideService.open()`).\n *\n * Контент-компонент инжектит `RtAsideRef` и закрывает aside через `.close(result?)`.\n * Родитель ждёт результата через `.afterClosed()`.\n *\n * Generic `<T>` — тип результата `close(result)`. Дефолт `unknown` — если результат\n * не нужен, оставляй generic пустым.\n *\n * Lifecycle close():\n * 1. Race-guard `#isClosing` гарантирует один эмит даже при двойном вызове.\n * 2. Снимает `rt-aside-overlay--open` и ставит `rt-aside-overlay--leaving` на host —\n * CSS-transition (200ms) играет slide-out.\n * 3. Эмиттит result в `afterClosed()` СРАЗУ (consumer не ждёт анимацию).\n * 4. Через `setTimeout(200ms)` зовёт `overlayRef.dispose()` — overlay убран после анимации.\n *\n * Слой, снятый без `close()` — переходом по адресу (`disposeOnNavigation`) или уничтожением\n * хозяина, — тоже завершает `afterClosed()`: подписчик получает `undefined`.\n *\n * @example\n * \\`\\`\\`ts\n * // в контент-компоненте\n * private readonly asideRef = inject(RtAsideRef<{ saved: boolean }>);\n * onSave(): void { this.asideRef.close({ saved: true }); }\n *\n * // в родителе\n * asideService.open(EditAsideComponent).afterClosed().subscribe(result => {...});\n * \\`\\`\\`\n */\nexport class RtAsideRef<T = unknown> {\n /**\n * Runtime-флаг, блокирующий close() через ESC / backdrop click.\n *\n * Default `false`. Включай (`asideRef.disableClose.set(true)`) на время\n * in-flight submit'а, чтобы юзер не потерял введённые в форму данные.\n * Программный `close(result)` гейтом не задет — он всегда закрывает.\n */\n public readonly disableClose: WritableSignal<boolean> = signal(false);\n\n readonly #overlayRef: OverlayRef;\n readonly #renderer: Renderer2;\n readonly #afterClosedSource: Subject<T | undefined> = new Subject<T | undefined>();\n readonly #closeRequestsSource: Subject<TRtAsideCloseRequest>;\n\n #isClosing: boolean = false;\n\n /**\n * `closeRequestsSource` наполняет служба: она слушает жесты и знает, отказала ли в них панель.\n * Хэндл только завершает поток вместе с панелью.\n */\n constructor(\n overlayRef: OverlayRef,\n renderer: Renderer2,\n closeRequestsSource: Subject<TRtAsideCloseRequest> = new Subject<TRtAsideCloseRequest>()\n ) {\n this.#overlayRef = overlayRef;\n this.#renderer = renderer;\n this.#closeRequestsSource = closeRequestsSource;\n this.#overlayRef\n .detachments()\n .pipe(take(1))\n .subscribe((): void => this.#finish(undefined));\n }\n\n /** Закрывает overlay со slide-out анимацией; эмиттит result в `afterClosed()` синхронно. */\n public close(result?: T): void {\n if (this.#isClosing) {\n return;\n }\n this.#isClosing = true;\n\n this.#renderer.removeClass(this.#overlayRef.overlayElement, 'rt-aside-overlay--open');\n this.#renderer.addClass(this.#overlayRef.overlayElement, 'rt-aside-overlay--leaving');\n\n // Эмитим результат сразу — consumer не ждёт slide-out.\n this.#emit(result);\n\n // Дефер dispose'а на длительность анимации — overlay остаётся в DOM,\n // пока CSS-transition не отыграет slide-out.\n setTimeout((): void => {\n this.#overlayRef.dispose();\n }, LEAVE_ANIMATION_DURATION_MS);\n }\n\n /** Эмиттит один раз — результат `close(result)` или `undefined` при снятом слое — и завершается. */\n public afterClosed(): Observable<T | undefined> {\n return this.#afterClosedSource.asObservable();\n }\n\n /**\n * Жесты закрытия, в которых панель отказала: `Escape` и нажатие по подложке под\n * `disableClose()` или выключенные настройкой. Завершается вместе с панелью.\n */\n public closeRequests(): Observable<TRtAsideCloseRequest> {\n return this.#closeRequestsSource.asObservable();\n }\n\n /** Слой снят без `close()`: анимации играть не на чем, остаётся отдать результат. */\n #finish(result: T | undefined): void {\n if (this.#isClosing) {\n return;\n }\n this.#isClosing = true;\n this.#emit(result);\n }\n\n #emit(result: T | undefined): void {\n this.#afterClosedSource.next(result);\n this.#afterClosedSource.complete();\n this.#closeRequestsSource.complete();\n }\n}\n","import { InjectionToken } from '@angular/core';\n\n/**\n * Injection token для передачи данных в side-sheet, открытую через `RtAsideService.open()`.\n *\n * Пользовательский компонент-контент aside'а инжектит токен и получает данные,\n * переданные в `IRtAsideConfig.data`. Тип значения задаётся generic'ом — `inject(RT_ASIDE_DATA)`\n * вернёт `unknown`, прокастуй через `inject(RT_ASIDE_DATA) as MyDataType` ИЛИ\n * объяви токен с конкретным generic'ом в feature-либе.\n *\n * Намеренно отдельный токен от `RT_DIALOG_DATA`, чтобы type-checker не путал контексты\n * (rt-dialog vs rt-aside) — оба сервиса могут существовать в одном DI-дереве.\n *\n * @example\n * \\`\\`\\`ts\n * @Component({...})\n * export class GuestProfileAsideComponent {\n * readonly data = inject(RT_ASIDE_DATA) as { userId: number };\n * }\n *\n * // parent\n * asideService.open(GuestProfileAsideComponent, { data: { userId: 42 } });\n * \\`\\`\\`\n */\nexport const RT_ASIDE_DATA: InjectionToken<unknown> = new InjectionToken<unknown>('RT_ASIDE_DATA');\n","import { ComponentType, Overlay, OverlayConfig, OverlayRef, PositionStrategy } from '@angular/cdk/overlay';\nimport { ComponentPortal } from '@angular/cdk/portal';\nimport { DOCUMENT } from '@angular/common';\nimport { inject, DestroyRef, Injectable, Injector, Renderer2, RendererFactory2, Signal } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\n\nimport { EMPTY, map, merge, mergeMap, Observable, Subject, Subscriber, takeUntil } from 'rxjs';\n\nimport { IRtKitConfig } from '@rt-tools/ui-kit-v2/core';\nimport { rtKitDefault } from '@rt-tools/ui-kit-v2/core';\nimport { carryThemeScopeOfFocus, materialPresetClassesOfFocus } from '@rt-tools/ui-kit-v2/core';\nimport { RtAsideRef, TRtAsideCloseRequest } from './rt-aside-ref';\nimport { RT_ASIDE_DATA } from './rt-aside.tokens';\n\n/**\n * Позиционирование side-sheet — у какого края viewport \"приклеена\" панель.\n */\nexport type TRtAsidePosition = 'right' | 'left';\n\n/**\n * Конфигурация открытия side-sheet через `RtAsideService.open()`.\n */\nexport interface IRtAsideConfig<TData = unknown> {\n /** Данные, доступные внутри aside'а через `inject(RT_ASIDE_DATA)`. */\n data?: TData;\n\n /** Дополнительный CSS-класс для backdrop (поверх стандартного `rt-aside-backdrop`). */\n backdropClass?: string | string[];\n\n /** Дополнительный CSS-класс для panel-обёртки overlay (поверх `rt-aside-overlay`). */\n panelClass?: string | string[];\n\n /** Закрывать aside по клику на backdrop. По дефолту `true`. */\n closeOnBackdropClick?: boolean;\n\n /** Закрывать aside по ESC. По дефолту `true`. */\n closeOnEscape?: boolean;\n\n /** К какому краю viewport \"приклеить\" панель. По дефолту `\"right\"`. */\n position?: TRtAsidePosition;\n\n /**\n * Injector хозяина панели — обычно `inject(Injector)` открывающего компонента. Он становится\n * родителем портала: содержимое видит провайдеры хозяина. Уничтожение хозяина закрывает\n * панель через `close()`. Без него родитель — корневой injector, и панель живёт сама по себе.\n */\n injector?: Injector;\n\n /**\n * Снимать панель при переходе по адресу. По умолчанию `true`; `false` оставляет её открытой —\n * для панели, которая сама ведёт навигацию и должна пережить смену адреса.\n */\n disposeOnNavigation?: boolean;\n}\n\n/**\n * Контекст одного открытия aside'а — payload для per-open close-подписок в конструкторном\n * стриме. `asideRef` сужен до используемых handler'ами членов (`RtAsideRef<T>` инвариантен\n * по `T`, целиком в generic-agnostic контекст не влезает).\n */\ninterface IRtAsideOpenContext {\n overlayRef: OverlayRef;\n asideRef: {\n disableClose: Signal<boolean>;\n close: () => void;\n };\n closeRequests: Subject<TRtAsideCloseRequest>;\n /** DestroyRef хозяина из `config.injector`; его уничтожение закрывает панель. */\n ownerDestroyRef: DestroyRef | null;\n closeOnBackdropClick: boolean;\n closeOnEscape: boolean;\n}\n\n/**\n * Программный API для открытия side-sheet поверх CDK Overlay.\n *\n * Архитектурно зеркалит `RtDialogService`, отличаясь только positionStrategy\n * (edge-anchored: `right('0')` / `left('0')` + `top('0')`) и slide-in анимацией,\n * которая триггерится через CSS-классы на overlay host (`Renderer2` инжектится\n * через `RendererFactory2`, потому что в service нет ComponentRef).\n *\n * Lifecycle:\n * 1. `overlay.create({ panelClass: [..., '--entering'] })` — host получает initial\n * CSS-стейт (translateX(±100%)).\n * 2. `overlayRef.attach(portal)` — контент-компонент монтируется в host.\n * 3. На следующий paint (`requestAnimationFrame`) сервис снимает `--entering` и\n * добавляет `--open` — CSS-transition сделает slide-in.\n * 4. На `ref.close(result)` ref добавляет `--leaving`, эмиттит result синхронно,\n * deferred `dispose()` через 200ms (длительность анимации).\n *\n * Альтернатива rt-dialog — для длинных форм / мастеров / side-panels, где\n * центрированная модалка ощущается тесной. Не предназначено для подтверждений\n * или коротких диалогов (для этого — `RtDialogService`).\n *\n * @example\n * \\`\\`\\`ts\n * private readonly asideService = inject(RtAsideService);\n *\n * openProfile(userId: number): void {\n * const ref = this.asideService.open<GuestProfileAsideComponent, { userId: number }, void>(\n * GuestProfileAsideComponent,\n * { data: { userId }, position: \"right\" }\n * );\n * ref.afterClosed().subscribe(() => this.reloadList());\n * }\n *\n * // контент-компонент\n * @Component({ ... })\n * export class GuestProfileAsideComponent {\n * readonly data = inject(RT_ASIDE_DATA) as { userId: number };\n * readonly #ref = inject(RtAsideRef);\n * close(): void { this.#ref.close(); }\n * }\n * \\`\\`\\`\n */\n@Injectable({ providedIn: 'root' })\nexport class RtAsideService {\n readonly #overlay: Overlay = inject(Overlay);\n readonly #injector: Injector = inject(Injector);\n readonly #document: Document = inject(DOCUMENT);\n // Renderer2 нужен для манипуляции CSS-классами на overlay.hostElement.\n // В service нет ComponentRef, поэтому Renderer2 берём через factory.\n readonly #renderer: Renderer2 = inject(RendererFactory2).createRenderer(null, null);\n\n readonly #openSource: Subject<IRtAsideOpenContext> = new Subject<IRtAsideOpenContext>();\n\n /* Закрывает ли `Escape` штору, когда вызов об этом промолчал. Умолчание кита — закрывает;\n приложению, которому это мешает, иначе пришлось бы писать отказ на каждом вызове, и\n забытый вызов отличался бы от остальных. */\n readonly #closeOnEscape: boolean = rtKitDefault('aside', (it: IRtKitConfig.Aside): boolean | undefined => it.closeOnEscape, true);\n\n constructor() {\n // Per-open close-подписки (backdrop / ESC) живут в одном постоянном\n // конструкторном стриме: open() эмитит контекст открытия, mergeMap\n // подписывает event-стримы конкретного OverlayRef до его dispose\n // (`detachments()` терминирует внутренний поток). mergeMap, а не\n // switchMap — одновременно может быть открыто несколько aside'ов.\n //\n // Runtime-guard через asideRef.disableClose() — те же правила, что в\n // rt-dialog: ESC / backdrop НЕ закрывают overlay, если disableClose=true;\n // программный ref.close() гейтом не задет.\n this.#openSource\n .pipe(\n mergeMap((openContext: IRtAsideOpenContext): Observable<() => void> => {\n // Оба жеста слушаются всегда: отказ в закрытии — тоже ответ, и он уходит в\n // `closeRequests()` хэндла, будь то `disableClose` или выключенный жест.\n const backdropClose$: Observable<() => void> = openContext.overlayRef.backdropClick().pipe(\n map((): (() => void) => (): void => {\n this.#answerCloseGesture(openContext, 'backdrop', openContext.closeOnBackdropClick);\n })\n );\n const escapeClose$: Observable<() => void> = openContext.overlayRef.keydownEvents().pipe(\n map((event: KeyboardEvent): (() => void) => (): void => {\n if (event.key !== 'Escape') {\n return;\n }\n if (openContext.closeOnEscape) {\n event.preventDefault();\n }\n this.#answerCloseGesture(openContext, 'escape', openContext.closeOnEscape);\n })\n );\n const ownerClose$: Observable<() => void> = this.#destroyed(openContext.ownerDestroyRef).pipe(\n map((): (() => void) => (): void => openContext.asideRef.close())\n );\n return merge(backdropClose$, escapeClose$, ownerClose$).pipe(takeUntil(openContext.overlayRef.detachments()));\n }),\n takeUntilDestroyed()\n )\n .subscribe((handleCloseIntent: () => void): void => handleCloseIntent());\n }\n\n public open<TComponent, TData = unknown, TResult = unknown>(\n component: ComponentType<TComponent>,\n config?: IRtAsideConfig<TData>\n ): RtAsideRef<TResult> {\n const position: TRtAsidePosition = config?.position ?? 'right';\n const positionStrategy: PositionStrategy =\n position === 'right'\n ? this.#overlay.position().global().right('0').top('0')\n : this.#overlay.position().global().left('0').top('0');\n\n const panelClasses: string[] = [\n 'rt-aside-overlay',\n `rt-aside-overlay--position-${position}`,\n 'rt-aside-overlay--entering',\n ...materialPresetClassesOfFocus(this.#document),\n ];\n\n const overlayConfig: OverlayConfig = {\n positionStrategy,\n scrollStrategy: this.#overlay.scrollStrategies.block(),\n hasBackdrop: true,\n backdropClass: config?.backdropClass ?? 'rt-aside-backdrop',\n panelClass: config?.panelClass !== undefined ? [...panelClasses, ...this.#toArray(config.panelClass)] : panelClasses,\n disposeOnNavigation: config?.disposeOnNavigation ?? true,\n };\n\n const overlayRef: OverlayRef = this.#overlay.create(overlayConfig);\n // До attach: содержимое панели может забрать фокус, и кусок темы у кнопки будет потерян.\n carryThemeScopeOfFocus(overlayRef.overlayElement, this.#document);\n const closeRequests: Subject<TRtAsideCloseRequest> = new Subject<TRtAsideCloseRequest>();\n const asideRef: RtAsideRef<TResult> = new RtAsideRef<TResult>(overlayRef, this.#renderer, closeRequests);\n\n // Сами close-подписки объявлены один раз в конструкторе — здесь только\n // эмит контекста открытия (см. #openSource-стрим).\n this.#openSource.next({\n overlayRef,\n asideRef,\n closeRequests,\n ownerDestroyRef: config?.injector?.get(DestroyRef, null) ?? null,\n closeOnBackdropClick: config?.closeOnBackdropClick !== false,\n closeOnEscape: config?.closeOnEscape ?? this.#closeOnEscape,\n });\n\n const injector: Injector = Injector.create({\n parent: config?.injector ?? this.#injector,\n providers: [\n { provide: RtAsideRef, useValue: asideRef },\n { provide: RT_ASIDE_DATA, useValue: config?.data ?? null },\n ],\n });\n\n const portal: ComponentPortal<TComponent> = new ComponentPortal<TComponent>(component, null, injector);\n overlayRef.attach(portal);\n\n // Slide-in: на следующий paint снимаем --entering и добавляем --open.\n // CSS transition (см. rt-aside.component.scss) сделает анимацию.\n // Backdrop default hidden — toggle --visible одновременно со слайдом.\n // Панель могли снять до кадра — `close()`, переходом или уничтожением хозяина в том же такте.\n requestAnimationFrame((): void => {\n if (!overlayRef.hasAttached()) {\n return;\n }\n this.#renderer.removeClass(overlayRef.overlayElement, 'rt-aside-overlay--entering');\n this.#renderer.addClass(overlayRef.overlayElement, 'rt-aside-overlay--open');\n const backdropEl: HTMLElement | null = overlayRef.backdropElement;\n if (backdropEl !== null) {\n this.#renderer.addClass(backdropEl, 'rt-aside-backdrop--visible');\n }\n });\n\n return asideRef;\n }\n\n /** Жест закрытия: закрывает панель, если жест включён и не держит `disableClose`, иначе сообщает об отказе. */\n #answerCloseGesture(openContext: IRtAsideOpenContext, gesture: TRtAsideCloseRequest, enabled: boolean): void {\n if (enabled && !openContext.asideRef.disableClose()) {\n openContext.asideRef.close();\n return;\n }\n openContext.closeRequests.next(gesture);\n }\n\n /**\n * Уничтожение хозяина как поток. Отписка — уход панели — снимает слушателя с хозяина, и его\n * позднее уничтожение панель уже не трогает.\n */\n #destroyed(ownerDestroyRef: DestroyRef | null): Observable<void> {\n if (ownerDestroyRef === null) {\n return EMPTY;\n }\n return new Observable<void>((subscriber: Subscriber<void>): (() => void) =>\n ownerDestroyRef.onDestroy((): void => subscriber.next())\n );\n }\n\n /** Нормализует `string | string[]` к массиву (panelClass поддерживает оба формата). */\n #toArray(value: string | string[]): string[] {\n return Array.isArray(value) ? value : [value];\n }\n}\n","import { BooleanInput } from '@angular/cdk/coercion';\nimport {\n booleanAttribute,\n inject,\n input,\n output,\n ChangeDetectionStrategy,\n Component,\n InputSignal,\n InputSignalWithTransform,\n OutputEmitterRef,\n Signal,\n ViewEncapsulation,\n} from '@angular/core';\n\nimport { BlockDirective, ElemDirective } from '@rt-tools/core';\n\nimport { RT_KIT_LABELS, TRtKitLabelMap } from '@rt-tools/ui-kit-v2/core';\nimport { RtIconButtonComponent } from '@rt-tools/ui-kit-v2/icon-button';\nimport { RtSkeletonWrapperComponent } from '@rt-tools/ui-kit-v2/skeleton-wrapper';\nimport { RtTagComponent } from '@rt-tools/ui-kit-v2/tag';\nimport { RtAsideRef } from '../rt-aside-ref';\nimport { IRtAsideHeader } from './rt-aside-header.model';\n\nconst BEM_BLOCK: string = 'rt-aside-header';\n\n/**\n * Заголовок side-sheet'а; работает в двух modes:\n *\n * 1. **Programmatic** (через `RtAsideService.open()`) — `RtAsideRef` провайдится\n * оверлей-инжектором; стрелка «назад» дёргает `asideRef.close()` напрямую,\n * consumer подписан на `afterClosed()`.\n * 2. **Route-driven** (через `[rtContainerRightSidenavPanel]` driver в\n * rt-container'е) — `RtAsideRef` НЕ провайдится (overlay принадлежит\n * rt-container'у, не RtAsideService). Consumer слушает `(dismiss)` output и\n * сам зовёт `panel.close()`.\n *\n * Оба пути живут одновременно: `onDismiss()` сперва эмитит output, затем (если\n * есть) дёргает optional `asideRef.close()`.\n *\n * Доменные действия (отклонить / удалить / снять с публикации и т.п.)\n * проецируются в слот `[asideActions]` как ряд `rt-icon-button` справа от title.\n *\n * Слот `[asideHeaderContent]` — строка под заголовком на всю ширину шапки: поиск, вкладки,\n * сводка. Пустой слот места не занимает.\n *\n * Слот `[asideHeadingContent]` — содержимое в колонке заголовка, под подписью и бэйджами, по её\n * левому краю. Пустой слот ничего не рисует.\n *\n * `ViewEncapsulation.None` — для единообразия с rt-dialog-header (стили\n * префиксованы `.rt-aside-header`).\n *\n * @example\n * ```html\n * <rt-aside-header title=\"Профиль пользователя\" />\n * <rt-aside-header title=\"Создание записи\" [closable]=\"false\" />\n * <rt-aside-header title=\"Детали\" (dismiss)=\"onClose()\">\n * <rt-icon-button asideActions icon=\"ico-error\" variant=\"danger\"\n * ariaLabel=\"Отклонить\" (clicked)=\"reject()\" />\n * </rt-aside-header>\n * ```\n */\n@Component({\n selector: 'rt-aside-header',\n templateUrl: './rt-aside-header.component.html',\n styleUrl: './rt-aside-header.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [\n // standalone components / directives\n RtIconButtonComponent,\n RtSkeletonWrapperComponent,\n RtTagComponent,\n BlockDirective,\n ElemDirective,\n ],\n host: {\n class: BEM_BLOCK,\n },\n})\nexport class RtAsideHeaderComponent {\n readonly #asideRef: RtAsideRef | null = inject(RtAsideRef, { optional: true });\n\n /** Текст заголовка — рендерится в `<h2>`. Null/пустая строка вместе с\n * `[loading]=true` показывают skeleton вместо title. */\n protected readonly t: Signal<TRtKitLabelMap> = inject(RT_KIT_LABELS);\n\n public readonly title: InputSignal<string | null> = input<string | null>(null);\n\n /**\n * Мелкий приглушённый надзаголовок НАД основным title (контекст действия:\n * «Создание записи», «Запись № 6»). Null (дефолт) — обычный\n * однострочный хедер. Overline не участвует в skeleton-режиме — это\n * статичный текст, известный до загрузки данных.\n */\n public readonly overline: InputSignal<string | null> = input<string | null>(null);\n\n /**\n * Приглушённая подпись ПОД заголовком: что делается в панели. Null (дефолт) —\n * строки нет. Как надзаголовок, в skeleton-режиме не участвует.\n */\n public readonly subtitle: InputSignal<string | null> = input<string | null>(null);\n\n /**\n * Ряд бэйджей (`rt-tag`) под заголовком — контекст сущности (статус, метка\n * записи и т.п.). Пустой массив (дефолт) — ряд не рендерится. Async-данные:\n * потребитель отдаёт `[]` пока сущность не зарезолвилась.\n */\n public readonly badges: InputSignal<readonly IRtAsideHeader.Badge[]> = input<readonly IRtAsideHeader.Badge[]>([]);\n\n /** Показывать кнопку закрытия в углу заголовка. Дефолт `true`. */\n public readonly closable: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(true, {\n transform: booleanAttribute,\n });\n\n /** Показывать skeleton вместо title (loading-state aside'а пока данные\n * резолвятся через async fasade). Дефолт `false`. */\n public readonly loading: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n /**\n * Эмитится при клике по стрелке «назад» — намерение пользователя закрыть\n * aside (intent, не «закрыто»). Route-driven consumer слушает этот output и\n * дёргает `panel.close()` сам. Имя `dismiss` вместо `close` — ESLint\n * `@angular-eslint/no-output-native` блокирует output с именем стандартного\n * DOM-события (`close`).\n */\n public readonly dismiss: OutputEmitterRef<void> = output<void>();\n\n protected onDismiss(): void {\n // Сначала output — route-driven consumer обработает intent.\n this.dismiss.emit();\n // Затем (если есть) AsideRef — programmatic mode через RtAsideService.\n this.#asideRef?.close();\n }\n}\n","<ng-container rtBlock=\"rt-aside-header\">\n <header rtElem=\"content\" qa-dataid=\"aside-header\">\n @if (closable()) {\n <rt-icon-button\n rtElem=\"back\"\n qa-dataid=\"aside-back\"\n icon=\"arrow-left\"\n size=\"sm\"\n variant=\"ghost\"\n [ariaLabel]=\"t().uiBack\"\n [tooltip]=\"t().uiBack\"\n (clicked)=\"onDismiss()\" />\n }\n\n <div rtElem=\"heading\">\n @if (overline(); as overlineText) {\n <p rtElem=\"overline\" qa-dataid=\"aside-overline\">{{ overlineText }}</p>\n }\n <h2 rtElem=\"title\" qa-dataid=\"aside-title\">\n <rt-skeleton-wrapper width=\"180px\" height=\"20px\" [isLoading]=\"loading()\">\n {{ title() }}\n </rt-skeleton-wrapper>\n </h2>\n\n @if (subtitle(); as subtitleText) {\n <p rtElem=\"subtitle\" qa-dataid=\"aside-subtitle\">{{ subtitleText }}</p>\n }\n\n @if (badges().length > 0) {\n <div rtElem=\"badges\">\n @for (badge of badges(); track badge.value) {\n @if (badge.href; as href) {\n <a rtElem=\"badge-link\" qa-dataid=\"aside-badge-link\" target=\"_blank\" rel=\"noopener\" [href]=\"href\">\n <rt-tag\n iconEnd=\"external-link\"\n [value]=\"badge.value\"\n [severity]=\"badge.severity ?? 'neutral'\"\n [appearance]=\"badge.appearance ?? 'solid'\"\n [radius]=\"badge.radius ?? 'md'\"\n [icon]=\"badge.icon ?? null\" />\n </a>\n } @else {\n <rt-tag\n [value]=\"badge.value\"\n [severity]=\"badge.severity ?? 'neutral'\"\n [appearance]=\"badge.appearance ?? 'solid'\"\n [radius]=\"badge.radius ?? 'md'\"\n [icon]=\"badge.icon ?? null\" />\n }\n }\n </div>\n }\n\n <ng-content select=\"[asideHeadingContent]\" />\n </div>\n\n <div rtElem=\"actions\" qa-dataid=\"aside-actions\">\n <ng-content select=\"[asideActions]\" />\n </div>\n </header>\n\n <div rtElem=\"row\" qa-dataid=\"aside-header-row\">\n <ng-content select=\"[asideHeaderContent]\" />\n </div>\n</ng-container>\n","import { contentChildren, effect, inject, ChangeDetectionStrategy, Component, ElementRef, Signal, ViewEncapsulation } from '@angular/core';\n\nimport { BlockDirective, ElemDirective } from '@rt-tools/core';\n\nimport { BreakpointsService, IButton, rtKitLabel } from '@rt-tools/ui-kit-v2/core';\nimport { RtButtonDirective } from '@rt-tools/ui-kit-v2/button';\n\nconst BEM_BLOCK: string = 'rt-aside-footer';\n\n/** Вид кнопки слота `[asidePrimary]`: залитая, основного цвета, без значка. */\nconst PRIMARY_LOOK: IButton.PlaceLook = { appearance: 'filled', theme: 'primary', icon: null };\n\n/** Вид кнопки по слоту, в котором она стоит сама или внутри его обёртки; вне слотов вида нет. */\nfunction slotLook(host: HTMLElement | undefined, dismissLook: IButton.PlaceLook): IButton.PlaceLook | null {\n if (host?.closest('[asideDismiss]')) {\n return dismissLook;\n }\n if (host?.closest('[asidePrimary]')) {\n return PRIMARY_LOOK;\n }\n\n return null;\n}\n\n/**\n * Footer-слот для композиции внутри `<rt-aside>`. Две зоны через именованные\n * слоты: `[asideDismiss]` (слева — «Закрыть») и `[asidePrimary]` (справа —\n * единственный позитивный глагол: Сохранить / Отправить / Опубликовать, если\n * есть). Раскладка `justify-content: space-between`, кнопки стоят в одну строку\n * на любой ширине. Доменные/деструктивные действия в футер не кладём — они\n * проецируются иконками в `<rt-aside-header>` слот `[asideActions]`.\n *\n * Вид кнопок задаёт подвал, а не разметка потребителя: «Закрыть» в рамке темы\n * secondary, позитивный глагол залитый и без значка. На телефоне первая кнопка\n * подписана коротко — «Закрыть» из подписей кита. Кнопка может стоять в слоте\n * не сама, а внутри обёртки с атрибутом слота: так ставят подсказку на кнопку.\n *\n * Только две именованные зоны — безатрибутный контент НЕ проецируется (контент\n * без `asideDismiss`/`asidePrimary` не отрендерится).\n *\n * `ViewEncapsulation.None` — для единообразия с rt-aside.\n *\n * @example\n * \\`\\`\\`html\n * <rt-aside-footer>\n * <button rtButton asideDismiss label=\"Закрыть и не сохранять\" (click)=\"onClose()\"></button>\n * <button rtButton asidePrimary label=\"Сохранить\" (click)=\"save()\"></button>\n * </rt-aside-footer>\n * \\`\\`\\`\n */\n@Component({\n selector: 'rt-aside-footer',\n templateUrl: './rt-aside-footer.component.html',\n styleUrl: './rt-aside-footer.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [BlockDirective, ElemDirective],\n host: {\n class: BEM_BLOCK,\n },\n})\nexport class RtAsideFooterComponent {\n readonly #phone: Signal<boolean> = inject(BreakpointsService).phone;\n readonly #closeLabel: Signal<string> = rtKitLabel('uiClose');\n\n /* Кнопки ищутся и внутри обёрток слота; оба списка идут в одном порядке документа. */\n protected readonly buttons: Signal<readonly RtButtonDirective[]> = contentChildren(RtButtonDirective, { descendants: true });\n protected readonly hosts: Signal<readonly ElementRef<HTMLElement>[]> = contentChildren(RtButtonDirective, {\n descendants: true,\n read: ElementRef,\n });\n\n constructor() {\n effect((): void => {\n const hosts: readonly ElementRef<HTMLElement>[] = this.hosts();\n const dismissLook: IButton.PlaceLook = {\n appearance: 'outlined',\n theme: 'secondary',\n ...(this.#phone() ? { label: this.#closeLabel() } : {}),\n };\n\n this.buttons().forEach((button: RtButtonDirective, index: number): void => {\n button.placeLook.set(slotLook(hosts[index]?.nativeElement, dismissLook));\n });\n });\n }\n}\n","<ng-container rtBlock=\"rt-aside-footer\">\n <footer rtElem=\"content\" qa-dataid=\"aside-footer\">\n <ng-content select=\"[asideDismiss]\" />\n <ng-content select=\"[asidePrimary]\" />\n </footer>\n</ng-container>\n","import { Signal } from '@angular/core';\n\n/** Что делает панель, когда её просят закрыться. */\nexport enum ERtAsideCloseIntent {\n /** Ничего: идёт запись, и закрытие посреди неё потеряло бы её результат. */\n Ignore = 'ignore',\n /** Закрыться сразу: терять нечего. */\n Close = 'close',\n /** Спросить, что сделать с несохранёнными правками. */\n Ask = 'ask',\n}\n\n/** Исход окна с несохранёнными правками. */\nexport enum ERtAsideUnsavedOutcome {\n /** Закрыть без сохранения. */\n Discard = 'discard',\n /** Закрыть с сохранением. */\n Save = 'save',\n /** Остаться в панели. */\n Stay = 'stay',\n}\n\n/**\n * Гард закрытия, который панель ставит на себя: признак нетронутости формы и\n * запись, которую зовёт исход «закрыть с сохранением».\n */\nexport interface IRtAsideUnsavedGuard {\n pristine: Signal<boolean>;\n save: () => void;\n}\n\n/** Состояние панели на момент намерения закрыть её. */\nexport interface IRtAsideCloseState {\n submitting: boolean;\n guarded: boolean;\n pristine: boolean;\n}\n\n/**\n * Решение по намерению закрыть панель. Одно на все четыре пути закрытия: кнопку\n * в шапке, кнопку в футере, нажатие мимо панели и клавишу Esc — иначе Esc обошёл\n * бы то, что ловит кнопка.\n */\nexport function resolveCloseIntent(state: IRtAsideCloseState): ERtAsideCloseIntent {\n if (state.submitting) {\n return ERtAsideCloseIntent.Ignore;\n }\n\n return state.guarded && !state.pristine ? ERtAsideCloseIntent.Ask : ERtAsideCloseIntent.Close;\n}\n","import { inject, ChangeDetectionStrategy, Component, Signal, ViewEncapsulation } from '@angular/core';\n\nimport { BlockDirective, ElemDirective } from '@rt-tools/core';\n\nimport { RT_KIT_LABELS, TRtKitLabelMap } 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 { RtDialogComponent } from '@rt-tools/ui-kit-v2/dialog';\nimport { ERtAsideUnsavedOutcome } from './rt-aside-unsaved.logic';\n\nconst BEM_BLOCK: string = 'rt-aside-unsaved-dialog';\n\n/**\n * Окно, которое видит пользователь, закрывая панель с несохранёнными правками. Три\n * исхода: закрыть без сохранения, закрыть с сохранением, остаться. Открывает его\n * общая основа асайда, поэтому наружу компонент не экспортируется.\n *\n * Отказ от правок подан красной кнопкой: это единственный исход, после которого\n * введённое не вернуть.\n */\n@Component({\n selector: 'rt-aside-unsaved-dialog',\n templateUrl: './rt-aside-unsaved-dialog.component.html',\n styleUrl: './rt-aside-unsaved-dialog.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [\n // standalone components / directives\n RtButtonDirective,\n RtDialogComponent,\n BlockDirective,\n ElemDirective,\n ],\n host: {\n class: BEM_BLOCK,\n },\n})\nexport class RtAsideUnsavedDialogComponent {\n readonly #dialogRef: RtDialogRef<ERtAsideUnsavedOutcome> = inject(RtDialogRef);\n\n protected readonly t: Signal<TRtKitLabelMap> = inject(RT_KIT_LABELS);\n\n protected onDiscard(): void {\n this.#dialogRef.close(ERtAsideUnsavedOutcome.Discard);\n }\n\n protected onSave(): void {\n this.#dialogRef.close(ERtAsideUnsavedOutcome.Save);\n }\n\n protected onStay(): void {\n this.#dialogRef.close(ERtAsideUnsavedOutcome.Stay);\n }\n}\n","<rt-dialog size=\"md\" qa-dataid=\"aside-unsaved-dialog\" [ariaLabel]=\"t().uiUnsavedTitle\">\n <div rtBlock=\"rt-aside-unsaved-dialog\">\n <div rtElem=\"text\">\n <h2 rtElem=\"title\" qa-dataid=\"aside-unsaved-title\">{{ t().uiUnsavedTitle }}</h2>\n <p rtElem=\"message\" qa-dataid=\"aside-unsaved-message\">{{ t().uiUnsavedMessage }}</p>\n </div>\n\n <div rtElem=\"footer\">\n <button\n rtButton\n qa-dataid=\"aside-unsaved-discard\"\n type=\"button\"\n theme=\"danger\"\n appearance=\"outlined\"\n [label]=\"t().uiUnsavedDiscard\"\n [attr.aria-label]=\"t().uiUnsavedDiscard\"\n (click)=\"onDiscard()\"></button>\n <button\n rtButton\n qa-dataid=\"aside-unsaved-stay\"\n type=\"button\"\n theme=\"secondary\"\n appearance=\"outlined\"\n [label]=\"t().uiUnsavedStay\"\n [attr.aria-label]=\"t().uiUnsavedStay\"\n (click)=\"onStay()\"></button>\n <button\n rtButton\n qa-dataid=\"aside-unsaved-save\"\n type=\"button\"\n appearance=\"filled\"\n [label]=\"t().uiUnsavedSave\"\n [attr.aria-label]=\"t().uiUnsavedSave\"\n (click)=\"onSave()\"></button>\n </div>\n </div>\n</rt-dialog>\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rt-tools-ui-kit-v2-autocomplete.mjs","names":[],"sources":["../../../projects/ui-kit-v2/src/lib/components/autocomplete/rt-autocomplete.component.ts","../../../projects/ui-kit-v2/src/lib/components/autocomplete/rt-autocomplete.component.html"],"sourcesContent":["import { BooleanInput } from '@angular/cdk/coercion';\nimport { NgTemplateOutlet } from '@angular/common';\nimport {\n booleanAttribute,\n computed,\n forwardRef,\n inject,\n input,\n output,\n signal,\n viewChild,\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n InputSignal,\n InputSignalWithTransform,\n OutputEmitterRef,\n Signal,\n TemplateRef,\n ViewEncapsulation,\n WritableSignal,\n} from '@angular/core';\n\nimport { BlockDirective, ElemDirective, ModDirective } from '@rt-tools/core';\n\nimport { RT_KIT_LABELS, TRtKitLabelMap } from '@rt-tools/ui-kit-v2/core';\nimport { RtFormControlBase } from '@rt-tools/ui-kit-v2/form-control';\nimport { IRtIcon } from '@rt-tools/ui-kit-v2/core';\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 { RtPopoverDirective } from '@rt-tools/ui-kit-v2/popover';\nimport { RtRadiusDirective } from '@rt-tools/ui-kit-v2/radius';\nimport { RtOptionList, TRtOptionListAction } from '@rt-tools/ui-kit-v2/core';\nimport { IRtAutocomplete } from './rt-autocomplete.model';\n\n/** Сколько точек до дна панели уже считается концом списка. */\nconst END_THRESHOLD_PX: number = 8;\n\nconst BEM_BLOCK: string = 'rt-autocomplete';\n\nlet panelIdSeed: number = 0;\n\nfunction nextPanelId(): number {\n panelIdSeed += 1;\n return panelIdSeed;\n}\n\n/**\n * Typeahead/autocomplete с server-side filter через `(complete)` event.\n *\n * Panel рендерится через CDK Overlay (`[rtPopover]` trigger=\"manual\" на input'е)\n * — открывается по набору текста (не click/hover), не обрезается overflow.\n * Подписчик получает `(complete)` с текущей строкой и сам грузит подсказки.\n *\n * CVA через общий `RtFormControlBase`. Generic `TItem` — тип подсказки и\n * selected value. Крестик (`clearable`) виден при непустом тексте — очищает и\n * текст, и выбранный объект. Keyboard-nav (activedescendant): ArrowUp/Down —\n * highlight, Enter — выбрать активную, Escape — закрыть. Home/End — deferred.\n */\n@Component({\n selector: 'rt-autocomplete',\n templateUrl: './rt-autocomplete.component.html',\n styleUrl: './rt-autocomplete.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [\n // Angular\n NgTemplateOutlet,\n\n // standalone components / directives\n RtIconButtonComponent,\n RtIconComponent,\n RtPopoverDirective,\n RtSpinnerComponent,\n BlockDirective,\n ElemDirective,\n ModDirective,\n ],\n providers: [\n // Алиас базового токена — для contentChild(RtFormControlBase) в rt-field.\n { provide: RtFormControlBase, useExisting: forwardRef(() => RtAutocompleteComponent) },\n ],\n hostDirectives: [{ directive: RtRadiusDirective, inputs: ['radius'] }],\n host: {\n class: BEM_BLOCK,\n '[class.rt-autocomplete--open]': 'isOpen()',\n '[class.rt-autocomplete--disabled]': 'isDisabled()',\n '[class.rt-autocomplete--readonly]': 'isReadonly()',\n '[class.rt-autocomplete--invalid]': 'isInvalid()',\n '[class.rt-autocomplete--borderless]': '!bordered()',\n '[class.rt-autocomplete--size--sm]': \"size() === 'sm'\",\n '[class.rt-autocomplete--size--lg]': \"size() === 'lg'\",\n },\n})\nexport class RtAutocompleteComponent<TItem> extends RtFormControlBase<TItem | null> {\n /** Длина списка, на которой конец уже сообщён: прокрутка у дна до новой страницы его не повторяет. */\n #endReportedAt: number = -1;\n\n protected readonly t: Signal<TRtKitLabelMap> = inject(RT_KIT_LABELS);\n\n protected readonly panelId: string = `rt-autocomplete-panel-${nextPanelId()}`;\n\n protected readonly popover: Signal<RtPopoverDirective> = viewChild.required(RtPopoverDirective);\n\n protected readonly fieldEl: Signal<ElementRef<HTMLInputElement> | undefined> = viewChild<ElementRef<HTMLInputElement>>('fieldEl');\n\n protected readonly inputText: WritableSignal<string> = signal<string>('');\n\n protected readonly isOpen: Signal<boolean> = computed((): boolean => this.popover().isOpen());\n\n /** Клавиатура списка: подсветка, клавиши открытия, id для `aria-activedescendant`, прокрутка к опции. */\n protected readonly optionList: RtOptionList = new RtOptionList({\n panelId: (): string => this.panelId,\n isOpen: (): boolean => this.isOpen(),\n count: (): number => this.suggestions().length,\n chooseKeys: new Set<string>(['Enter']),\n sideKeys: false,\n edgeKeys: false,\n });\n\n protected readonly activeIndex: WritableSignal<number> = this.optionList.activeIndex;\n\n /** Идентификатор подсвеченной опции — тот же, что уходит в `aria-activedescendant`. */\n protected readonly activeOptionId: Signal<string | null> = this.optionList.activeOptionId;\n\n protected readonly hasValue: Signal<boolean> = computed((): boolean => this.inputText() !== '');\n\n public readonly displayText: Signal<string> = computed((): string => this.displayWith()(this.value()));\n\n public readonly suggestions: InputSignal<ReadonlyArray<TItem>> = input<ReadonlyArray<TItem>>([]);\n\n public readonly displayWith: InputSignal<(item: TItem | null) => string> = input<(item: TItem | null) => string>(\n (item: TItem | null): string => String(item ?? '')\n );\n\n public readonly placeholder: InputSignal<string> = input<string>('');\n public readonly minLength: InputSignal<number> = input<number>(1);\n\n /** Префикс-иконка слева в поле (например, `map` для поиска). */\n public readonly iconLeft: InputSignal<IRtIcon.Name | null> = input<IRtIcon.Name | null>(null);\n\n public readonly openOnFocus: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n public readonly itemTemplate: InputSignal<TemplateRef<{ $implicit: TItem }> | null> = input<TemplateRef<{ $implicit: TItem }> | null>(\n null\n );\n\n /**\n * Подсказки грузятся: в панели строка со спиннером — вместо пустого результата при первой загрузке\n * и под списком при подгрузке следующей страницы.\n */\n public readonly loading: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n /** Свой текст пустого результата; `null` — подпись кита `uiNothingFound`. */\n public readonly emptyText: InputSignal<string | null> = input<string | null>(null);\n\n /** Своя разметка пустого результата; сильнее `emptyText`. Обстановка — набранная строка. */\n public readonly emptyTemplate: InputSignal<TemplateRef<{ $implicit: string }> | null> = input<TemplateRef<{\n $implicit: string;\n }> | null>(null);\n\n /**\n * Панель долистали до конца — пора просить следующую страницу. Не приходит, пока идёт загрузка, и\n * приходит один раз на каждую длину списка: прокрутка у дна до прихода страницы её не повторяет.\n */\n public readonly scrolledToEnd: OutputEmitterRef<void> = output<void>();\n\n public readonly complete: OutputEmitterRef<IRtAutocomplete.CompleteEvent> = output<IRtAutocomplete.CompleteEvent>();\n\n public readonly itemSelect: OutputEmitterRef<TItem> = output<TItem>();\n\n public override writeValue(value: TItem | null): void {\n super.writeValue(value);\n this.inputText.set(this.displayWith()(this.value()));\n }\n\n public override setDisabledState(disabled: boolean): void {\n super.setDisabledState(disabled);\n if (disabled) {\n this.popover().close();\n }\n }\n\n protected getEmptyValue(): TItem | null {\n return null;\n }\n\n protected focusAfterClear(): void {\n this.fieldEl()?.nativeElement.focus();\n }\n\n protected override clearValue(): void {\n super.clearValue();\n this.inputText.set('');\n this.#endReportedAt = -1;\n }\n\n protected onInput(event: Event): void {\n const target: HTMLInputElement = event.target as HTMLInputElement;\n const query: string = target.value;\n this.inputText.set(query);\n this.activeIndex.set(-1);\n // Новый запрос — новый список: его первая страница той же длины, что прошлый список, снова\n // сообщает о дне, иначе подгрузка нового запроса не началась бы вовсе.\n this.#endReportedAt = -1;\n if (query.length >= this.minLength()) {\n this.complete.emit({ query, originalEvent: event });\n this.popover().open();\n } else {\n this.popover().close();\n }\n }\n\n protected onFocus(): void {\n if (!this.openOnFocus()) {\n return;\n }\n this.complete.emit({ query: this.inputText(), originalEvent: new Event('focus') });\n this.popover().open();\n }\n\n protected onBlur(): void {\n this.markTouched();\n }\n\n /** Прокрутка панели: у дна — сообщить, что пора следующая страница. */\n protected onPanelScroll(event: Event): void {\n const panel: HTMLElement = event.target as HTMLElement;\n const atEnd: boolean = panel.scrollTop + panel.clientHeight >= panel.scrollHeight - END_THRESHOLD_PX;\n const length: number = this.suggestions().length;\n if (!atEnd || this.loading() || length === this.#endReportedAt) {\n return;\n }\n this.#endReportedAt = length;\n this.scrolledToEnd.emit();\n }\n\n protected onClosed(): void {\n this.activeIndex.set(-1);\n }\n\n protected onSelect(item: TItem): void {\n this.value.set(item);\n this.inputText.set(this.displayWith()(item));\n this.emitChange(item);\n this.itemSelect.emit(item);\n this.popover().close();\n }\n\n protected onInputKeydown(event: KeyboardEvent): void {\n if (this.isDisabled()) {\n return;\n }\n const key: string = event.key;\n if (!this.isOpen()) {\n return;\n }\n const action: TRtOptionListAction = this.optionList.actionOf(key);\n if (action === 'none' || action === 'side') {\n return;\n }\n event.preventDefault();\n switch (action) {\n case 'next':\n this.optionList.move(1);\n break;\n case 'previous':\n this.optionList.move(-1);\n break;\n case 'choose':\n this.#selectActive();\n break;\n case 'close':\n // Escape закрывает один слой: панель. Без остановки событие всплывает до document,\n // и оверлей под панелью — боковая панель, окно — закрывается вместе с ней.\n event.stopPropagation();\n this.popover().close();\n break;\n }\n }\n\n #selectActive(): void {\n const items: ReadonlyArray<TItem> = this.suggestions();\n const index: number = this.activeIndex();\n if (index >= 0 && index < items.length) {\n this.onSelect(items[index]);\n }\n }\n}\n","<ng-container rtBlock=\"rt-autocomplete\">\n @if (isReadonly()) {\n <span rtElem=\"readonly\" qa-dataid=\"autocomplete-readonly\" [rtMod]=\"{ empty: !displayText() }\">{{ displayText() || '—' }}</span>\n }\n <div rtElem=\"field\" rtPopoverTrigger=\"manual\" rtPopoverWidth=\"trigger\" [rtPopover]=\"panelTpl\" (rtPopoverClosed)=\"onClosed()\">\n @if (iconLeft(); as left) {\n <rt-icon rtElem=\"icon-left\" size=\"sm\" color=\"muted\" [name]=\"left\" />\n }\n <input\n #fieldEl\n rtElem=\"input\"\n qa-dataid=\"autocomplete-input\"\n type=\"text\"\n autocomplete=\"off\"\n role=\"combobox\"\n [attr.id]=\"effectiveControlId()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-invalid]=\"isInvalid() || null\"\n [attr.aria-autocomplete]=\"'list'\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-activedescendant]=\"activeOptionId()\"\n [placeholder]=\"placeholder()\"\n [value]=\"inputText()\"\n [disabled]=\"isDisabled()\"\n (input)=\"onInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n (keydown)=\"onInputKeydown($event)\" />\n @if (showClearButton()) {\n <rt-icon-button\n rtElem=\"clear\"\n qa-dataid=\"autocomplete-clear\"\n icon=\"ico-close\"\n size=\"sm\"\n variant=\"ghost\"\n iconColor=\"muted\"\n [ariaLabel]=\"t().uiClear\"\n [tabIndex]=\"-1\"\n (clicked)=\"clear($event)\" />\n }\n </div>\n\n <ng-template #panelTpl>\n <!--\n Опции выбираются мышью через (click); keyboard-nav — на input'е\n (activedescendant), опции не focusable индивидуально.\n -->\n <!-- eslint-disable @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <ul\n rtElem=\"panel\"\n qa-dataid=\"autocomplete-panel\"\n role=\"listbox\"\n [id]=\"panelId\"\n [attr.aria-busy]=\"loading() || null\"\n (scroll)=\"onPanelScroll($event)\">\n @for (item of suggestions(); track $index; let i = $index) {\n <li\n rtElem=\"option\"\n qa-dataid=\"autocomplete-option\"\n role=\"option\"\n [id]=\"panelId + '-opt-' + i\"\n [rtMod]=\"{ active: i === activeIndex() }\"\n [attr.aria-selected]=\"item === value()\"\n (click)=\"onSelect(item)\">\n @if (itemTemplate(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: item }\" />\n } @else {\n {{ displayWith()(item) }}\n }\n </li>\n } @empty {\n @if (!loading()) {\n <li rtElem=\"empty\" qa-dataid=\"autocomplete-empty\">\n @if (emptyTemplate(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: inputText() }\" />\n } @else {\n {{ emptyText() ?? t().uiNothingFound }}\n }\n </li>\n }\n }\n @if (loading()) {\n <li rtElem=\"loading\" qa-dataid=\"autocomplete-loading\" aria-hidden=\"true\">\n <rt-spinner [diameter]=\"20\" />\n </li>\n }\n </ul>\n </ng-template>\n</ng-container>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rt-tools-ui-kit-v2-autocomplete.mjs","names":[],"sources":["../../../projects/ui-kit-v2/src/lib/components/autocomplete/rt-autocomplete.component.ts","../../../projects/ui-kit-v2/src/lib/components/autocomplete/rt-autocomplete.component.html"],"sourcesContent":["import { BooleanInput } from '@angular/cdk/coercion';\nimport { NgTemplateOutlet } from '@angular/common';\nimport {\n booleanAttribute,\n computed,\n forwardRef,\n inject,\n input,\n output,\n signal,\n viewChild,\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n InputSignal,\n InputSignalWithTransform,\n OutputEmitterRef,\n Signal,\n TemplateRef,\n ViewEncapsulation,\n WritableSignal,\n} from '@angular/core';\n\nimport { BlockDirective, ElemDirective, ModDirective } from '@rt-tools/core';\n\nimport { RT_KIT_LABELS, TRtKitLabelMap } from '@rt-tools/ui-kit-v2/core';\nimport { RtFormControlBase } from '@rt-tools/ui-kit-v2/form-control';\nimport { IRtIcon } from '@rt-tools/ui-kit-v2/core';\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 { RtPopoverDirective } from '@rt-tools/ui-kit-v2/popover';\nimport { RtRadiusDirective } from '@rt-tools/ui-kit-v2/radius';\nimport { RtOptionList, TRtOptionListAction } from '@rt-tools/ui-kit-v2/core';\nimport { IRtAutocomplete } from './rt-autocomplete.model';\n\n/** Сколько точек до дна панели уже считается концом списка. */\nconst END_THRESHOLD_PX: number = 8;\n\nconst BEM_BLOCK: string = 'rt-autocomplete';\n\nlet panelIdSeed: number = 0;\n\nfunction nextPanelId(): number {\n panelIdSeed += 1;\n return panelIdSeed;\n}\n\n/**\n * Typeahead/autocomplete с server-side filter через `(complete)` event.\n *\n * Panel рендерится через CDK Overlay (`[rtPopover]` trigger=\"manual\" на input'е)\n * — открывается по набору текста (не click/hover), не обрезается overflow.\n * Подписчик получает `(complete)` с текущей строкой и сам грузит подсказки.\n *\n * CVA через общий `RtFormControlBase`. Generic `TItem` — тип подсказки и\n * selected value. Крестик (`clearable`) виден при непустом тексте — очищает и\n * текст, и выбранный объект. Keyboard-nav (activedescendant): ArrowUp/Down —\n * highlight, Enter — выбрать активную, Escape — закрыть. Home/End — deferred.\n */\n@Component({\n selector: 'rt-autocomplete',\n templateUrl: './rt-autocomplete.component.html',\n styleUrl: './rt-autocomplete.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [\n // Angular\n NgTemplateOutlet,\n\n // standalone components / directives\n RtIconButtonComponent,\n RtIconComponent,\n RtPopoverDirective,\n RtSpinnerComponent,\n BlockDirective,\n ElemDirective,\n ModDirective,\n ],\n providers: [\n // Алиас базового токена — для contentChild(RtFormControlBase) в rt-field.\n { provide: RtFormControlBase, useExisting: forwardRef(() => RtAutocompleteComponent) },\n ],\n hostDirectives: [{ directive: RtRadiusDirective, inputs: ['radius'] }],\n host: {\n class: BEM_BLOCK,\n '[class.rt-autocomplete--open]': 'isOpen()',\n '[class.rt-autocomplete--disabled]': 'isDisabled()',\n '[class.rt-autocomplete--readonly]': 'isReadonly()',\n '[class.rt-autocomplete--invalid]': 'isInvalid()',\n '[class.rt-autocomplete--borderless]': '!bordered()',\n '[class.rt-autocomplete--size--sm]': \"size() === 'sm'\",\n '[class.rt-autocomplete--size--lg]': \"size() === 'lg'\",\n },\n})\nexport class RtAutocompleteComponent<TItem> extends RtFormControlBase<TItem | null> {\n /** Длина списка, на которой конец уже сообщён: прокрутка у дна до новой страницы его не повторяет. */\n #endReportedAt: number = -1;\n\n protected readonly t: Signal<TRtKitLabelMap> = inject(RT_KIT_LABELS);\n\n protected readonly panelId: string = `rt-autocomplete-panel-${nextPanelId()}`;\n\n protected readonly popover: Signal<RtPopoverDirective> = viewChild.required(RtPopoverDirective);\n\n protected readonly fieldEl: Signal<ElementRef<HTMLInputElement> | undefined> = viewChild<ElementRef<HTMLInputElement>>('fieldEl');\n\n protected readonly inputText: WritableSignal<string> = signal<string>('');\n\n protected readonly isOpen: Signal<boolean> = computed((): boolean => this.popover().isOpen());\n\n /** Клавиатура списка: подсветка, клавиши открытия, id для `aria-activedescendant`, прокрутка к опции. */\n protected readonly optionList: RtOptionList = new RtOptionList({\n panelId: (): string => this.panelId,\n isOpen: (): boolean => this.isOpen(),\n count: (): number => this.suggestions().length,\n chooseKeys: new Set<string>(['Enter']),\n sideKeys: false,\n edgeKeys: false,\n });\n\n protected readonly activeIndex: WritableSignal<number> = this.optionList.activeIndex;\n\n /** Идентификатор подсвеченной опции — тот же, что уходит в `aria-activedescendant`. */\n protected readonly activeOptionId: Signal<string | null> = this.optionList.activeOptionId;\n\n protected readonly hasValue: Signal<boolean> = computed((): boolean => this.inputText() !== '');\n\n public readonly displayText: Signal<string> = computed((): string => this.displayWith()(this.value()));\n\n public readonly suggestions: InputSignal<ReadonlyArray<TItem>> = input<ReadonlyArray<TItem>>([]);\n\n public readonly displayWith: InputSignal<(item: TItem | null) => string> = input<(item: TItem | null) => string>(\n (item: TItem | null): string => String(item ?? '')\n );\n\n public readonly placeholder: InputSignal<string> = input<string>('');\n public readonly minLength: InputSignal<number> = input<number>(1);\n\n /** Префикс-иконка слева в поле (например, `map` для поиска). */\n public readonly iconLeft: InputSignal<IRtIcon.Name | null> = input<IRtIcon.Name | null>(null);\n\n public readonly openOnFocus: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n public readonly itemTemplate: InputSignal<TemplateRef<{ $implicit: TItem }> | null> = input<TemplateRef<{ $implicit: TItem }> | null>(\n null\n );\n\n /**\n * Подсказки грузятся: в панели строка со спиннером — вместо пустого результата при первой загрузке\n * и под списком при подгрузке следующей страницы.\n */\n public readonly loading: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n /** Свой текст пустого результата; `null` — подпись кита `uiNothingFound`. */\n public readonly emptyText: InputSignal<string | null> = input<string | null>(null);\n\n /** Своя разметка пустого результата; сильнее `emptyText`. Обстановка — набранная строка. */\n public readonly emptyTemplate: InputSignal<TemplateRef<{ $implicit: string }> | null> = input<TemplateRef<{\n $implicit: string;\n }> | null>(null);\n\n /**\n * Панель долистали до конца — пора просить следующую страницу. Не приходит, пока идёт загрузка, и\n * приходит один раз на каждую длину списка: прокрутка у дна до прихода страницы её не повторяет.\n */\n public readonly scrolledToEnd: OutputEmitterRef<void> = output<void>();\n\n public readonly complete: OutputEmitterRef<IRtAutocomplete.CompleteEvent> = output<IRtAutocomplete.CompleteEvent>();\n\n public readonly itemSelect: OutputEmitterRef<TItem> = output<TItem>();\n\n public override writeValue(value: TItem | null): void {\n super.writeValue(value);\n this.inputText.set(this.displayWith()(this.value()));\n }\n\n public override setDisabledState(disabled: boolean): void {\n super.setDisabledState(disabled);\n if (disabled) {\n this.popover().close();\n }\n }\n\n protected getEmptyValue(): TItem | null {\n return null;\n }\n\n protected focusAfterClear(): void {\n this.fieldEl()?.nativeElement.focus();\n }\n\n protected override clearValue(): void {\n super.clearValue();\n this.inputText.set('');\n this.#endReportedAt = -1;\n }\n\n protected onInput(event: Event): void {\n const target: HTMLInputElement = event.target as HTMLInputElement;\n const query: string = target.value;\n this.inputText.set(query);\n this.activeIndex.set(-1);\n // Новый запрос — новый список: его первая страница той же длины, что прошлый список, снова\n // сообщает о дне, иначе подгрузка нового запроса не началась бы вовсе.\n this.#endReportedAt = -1;\n if (query.length >= this.minLength()) {\n this.complete.emit({ query, originalEvent: event });\n this.popover().open();\n } else {\n this.popover().close();\n }\n }\n\n protected onFocus(): void {\n if (!this.openOnFocus()) {\n return;\n }\n this.complete.emit({ query: this.inputText(), originalEvent: new Event('focus') });\n this.popover().open();\n }\n\n protected onBlur(): void {\n this.markTouched();\n }\n\n /** Прокрутка панели: у дна — сообщить, что пора следующая страница. */\n protected onPanelScroll(event: Event): void {\n const panel: HTMLElement = event.target as HTMLElement;\n const atEnd: boolean = panel.scrollTop + panel.clientHeight >= panel.scrollHeight - END_THRESHOLD_PX;\n const length: number = this.suggestions().length;\n if (!atEnd || this.loading() || length === this.#endReportedAt) {\n return;\n }\n this.#endReportedAt = length;\n this.scrolledToEnd.emit();\n }\n\n protected onClosed(): void {\n this.activeIndex.set(-1);\n }\n\n protected onSelect(item: TItem): void {\n this.value.set(item);\n this.inputText.set(this.displayWith()(item));\n this.emitChange(item);\n this.itemSelect.emit(item);\n this.popover().close();\n }\n\n protected onInputKeydown(event: KeyboardEvent): void {\n if (this.isDisabled()) {\n return;\n }\n const key: string = event.key;\n if (!this.isOpen()) {\n return;\n }\n const action: TRtOptionListAction = this.optionList.actionOf(key);\n if (action === 'none' || action === 'side') {\n return;\n }\n event.preventDefault();\n switch (action) {\n case 'next':\n this.optionList.move(1);\n break;\n case 'previous':\n this.optionList.move(-1);\n break;\n case 'choose':\n this.#selectActive();\n break;\n case 'close':\n // Escape закрывает один слой: панель. Без остановки событие всплывает до document,\n // и оверлей под панелью — боковая панель, окно — закрывается вместе с ней.\n event.stopPropagation();\n this.popover().close();\n break;\n }\n }\n\n #selectActive(): void {\n const items: ReadonlyArray<TItem> = this.suggestions();\n const index: number = this.activeIndex();\n if (index >= 0 && index < items.length) {\n this.onSelect(items[index]);\n }\n }\n}\n","<ng-container rtBlock=\"rt-autocomplete\">\n @if (isReadonly()) {\n <span rtElem=\"readonly\" qa-dataid=\"autocomplete-readonly\" [rtMod]=\"{ empty: !displayText() }\">{{ displayText() || '—' }}</span>\n }\n <div rtElem=\"field\" rtPopoverTrigger=\"manual\" rtPopoverWidth=\"trigger\" [rtPopover]=\"panelTpl\" (rtPopoverClosed)=\"onClosed()\">\n @if (iconLeft(); as left) {\n <rt-icon rtElem=\"icon-left\" size=\"sm\" color=\"muted\" [name]=\"left\" />\n }\n <input\n #fieldEl\n rtElem=\"input\"\n qa-dataid=\"autocomplete-input\"\n type=\"text\"\n autocomplete=\"off\"\n role=\"combobox\"\n [attr.id]=\"effectiveControlId()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-invalid]=\"isInvalid() || null\"\n [attr.aria-autocomplete]=\"'list'\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-activedescendant]=\"activeOptionId()\"\n [placeholder]=\"placeholder()\"\n [value]=\"inputText()\"\n [disabled]=\"isDisabled()\"\n (input)=\"onInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n (keydown)=\"onInputKeydown($event)\" />\n @if (showClearButton()) {\n <rt-icon-button\n rtElem=\"clear\"\n qa-dataid=\"autocomplete-clear\"\n icon=\"ico-close\"\n size=\"sm\"\n variant=\"ghost\"\n iconColor=\"muted\"\n [ariaLabel]=\"t().uiClear\"\n [tabIndex]=\"-1\"\n (clicked)=\"clear($event)\" />\n }\n </div>\n\n <ng-template #panelTpl>\n <!--\n Опции выбираются мышью через (click); keyboard-nav — на input'е\n (activedescendant), опции не focusable индивидуально.\n -->\n <!-- eslint-disable @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <ul\n rtElem=\"panel\"\n qa-dataid=\"autocomplete-panel\"\n role=\"listbox\"\n [id]=\"panelId\"\n [attr.aria-busy]=\"loading() || null\"\n (scroll)=\"onPanelScroll($event)\">\n @for (item of suggestions(); track $index; let i = $index) {\n <li\n rtElem=\"option\"\n qa-dataid=\"autocomplete-option\"\n role=\"option\"\n [id]=\"panelId + '-opt-' + i\"\n [rtMod]=\"{ active: i === activeIndex() }\"\n [attr.aria-selected]=\"item === value()\"\n (click)=\"onSelect(item)\">\n @if (itemTemplate(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: item }\" />\n } @else {\n {{ displayWith()(item) }}\n }\n </li>\n } @empty {\n @if (!loading()) {\n <li rtElem=\"empty\" qa-dataid=\"autocomplete-empty\">\n @if (emptyTemplate(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: inputText() }\" />\n } @else {\n {{ emptyText() ?? t().uiNothingFound }}\n }\n </li>\n }\n }\n @if (loading()) {\n <li rtElem=\"loading\" qa-dataid=\"autocomplete-loading\" aria-hidden=\"true\">\n <rt-spinner [diameter]=\"20\" />\n </li>\n }\n </ul>\n </ng-template>\n</ng-container>\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FocusMonitor } from "@angular/cdk/a11y";
|
|
2
2
|
import { DOCUMENT } from "@angular/common";
|
|
3
3
|
import * as i0 from "@angular/core";
|
|
4
|
-
import { DestroyRef, Directive, ElementRef, HostBinding, Renderer2, afterNextRender, booleanAttribute, computed, effect, inject, input, untracked } from "@angular/core";
|
|
4
|
+
import { DestroyRef, Directive, ElementRef, HostBinding, Renderer2, afterNextRender, booleanAttribute, computed, effect, inject, input, signal, untracked } from "@angular/core";
|
|
5
5
|
import { RT_RADIUS_DEFAULT, rtKitDefault } from "@rt-tools/ui-kit-v2/core";
|
|
6
6
|
import * as i2 from "@rt-tools/ui-kit-v2/radius";
|
|
7
7
|
import { RtRadiusDirective } from "@rt-tools/ui-kit-v2/radius";
|
|
@@ -24,6 +24,10 @@ var RtButtonDirective = class RtButtonDirective {
|
|
|
24
24
|
#labelEl;
|
|
25
25
|
#spinnerEl;
|
|
26
26
|
#wasLoading;
|
|
27
|
+
#theme;
|
|
28
|
+
#appearanceShown;
|
|
29
|
+
#icon;
|
|
30
|
+
#label;
|
|
27
31
|
constructor() {
|
|
28
32
|
this.#el = inject(ElementRef);
|
|
29
33
|
this.#renderer = inject(Renderer2);
|
|
@@ -38,7 +42,12 @@ var RtButtonDirective = class RtButtonDirective {
|
|
|
38
42
|
this.#labelEl = null;
|
|
39
43
|
this.#spinnerEl = null;
|
|
40
44
|
this.#wasLoading = false;
|
|
41
|
-
this
|
|
45
|
+
this.#theme = computed(() => this.placeLook()?.theme ?? this.theme(), ...ngDevMode ? [{ debugName: "#theme" }] : /* istanbul ignore next */ []);
|
|
46
|
+
this.#appearanceShown = computed(() => this.placeLook()?.appearance ?? this.appearance(), ...ngDevMode ? [{ debugName: "#appearanceShown" }] : /* istanbul ignore next */ []);
|
|
47
|
+
this.#icon = computed(() => this.placeLook()?.icon === null ? null : this.icon(), ...ngDevMode ? [{ debugName: "#icon" }] : /* istanbul ignore next */ []);
|
|
48
|
+
this.#label = computed(() => this.placeLook()?.label ?? this.label(), ...ngDevMode ? [{ debugName: "#label" }] : /* istanbul ignore next */ []);
|
|
49
|
+
this.isIconOnly = computed(() => !!this.#icon() && !this.#label(), ...ngDevMode ? [{ debugName: "isIconOnly" }] : /* istanbul ignore next */ []);
|
|
50
|
+
this.placeLook = signal(null, ...ngDevMode ? [{ debugName: "placeLook" }] : /* istanbul ignore next */ []);
|
|
42
51
|
this.label = input(null, ...ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []);
|
|
43
52
|
this.icon = input(null, ...ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []);
|
|
44
53
|
this.iconPos = input("left", ...ngDevMode ? [{ debugName: "iconPos" }] : /* istanbul ignore next */ []);
|
|
@@ -56,8 +65,8 @@ var RtButtonDirective = class RtButtonDirective {
|
|
|
56
65
|
this.#focusMonitor.monitor(this.#el);
|
|
57
66
|
inject(DestroyRef).onDestroy(() => this.#focusMonitor.stopMonitoring(this.#el));
|
|
58
67
|
effect(() => {
|
|
59
|
-
this
|
|
60
|
-
this
|
|
68
|
+
this.#label();
|
|
69
|
+
this.#icon();
|
|
61
70
|
this.iconPos();
|
|
62
71
|
this.loading();
|
|
63
72
|
this.loadingIcon();
|
|
@@ -71,8 +80,8 @@ var RtButtonDirective = class RtButtonDirective {
|
|
|
71
80
|
return pressed === null ? null : String(pressed);
|
|
72
81
|
}
|
|
73
82
|
get hostClasses() {
|
|
74
|
-
const theme = this
|
|
75
|
-
const appearance = this
|
|
83
|
+
const theme = this.#theme();
|
|
84
|
+
const appearance = this.#appearanceShown();
|
|
76
85
|
const size = this.size();
|
|
77
86
|
return {
|
|
78
87
|
[BEM_BLOCK]: true,
|
|
@@ -151,15 +160,15 @@ var RtButtonDirective = class RtButtonDirective {
|
|
|
151
160
|
this.#spinnerEl = loader;
|
|
152
161
|
this.#renderer.appendChild(button, this.#spinnerEl);
|
|
153
162
|
}
|
|
154
|
-
const label = this
|
|
163
|
+
const label = this.#label();
|
|
155
164
|
if (label) {
|
|
156
165
|
this.#labelEl = this.#createLabel(label);
|
|
157
166
|
this.#renderer.appendChild(button, this.#labelEl);
|
|
158
167
|
}
|
|
159
168
|
}
|
|
160
169
|
#renderIconAndLabel(button) {
|
|
161
|
-
const iconClass = this
|
|
162
|
-
const label = this
|
|
170
|
+
const iconClass = this.#icon();
|
|
171
|
+
const label = this.#label();
|
|
163
172
|
const pos = this.iconPos();
|
|
164
173
|
const iconEl = iconClass ? this.#createIcon(iconClass) : null;
|
|
165
174
|
const labelEl = label ? this.#createLabel(label) : null;
|