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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/fesm2022/rt-tools-ui-kit-v2-autocomplete.mjs +23 -29
  2. package/fesm2022/rt-tools-ui-kit-v2-autocomplete.mjs.map +1 -1
  3. package/fesm2022/rt-tools-ui-kit-v2-calendar.mjs +2 -2
  4. package/fesm2022/rt-tools-ui-kit-v2-calendar.mjs.map +1 -1
  5. package/fesm2022/rt-tools-ui-kit-v2-core.mjs +223 -3
  6. package/fesm2022/rt-tools-ui-kit-v2-core.mjs.map +1 -1
  7. package/fesm2022/rt-tools-ui-kit-v2-data-list.mjs +2 -2
  8. package/fesm2022/rt-tools-ui-kit-v2-data-list.mjs.map +1 -1
  9. package/fesm2022/rt-tools-ui-kit-v2-draggable-tree.mjs +1 -2
  10. package/fesm2022/rt-tools-ui-kit-v2-draggable-tree.mjs.map +1 -1
  11. package/fesm2022/rt-tools-ui-kit-v2-dynamic-list.mjs +2 -2
  12. package/fesm2022/rt-tools-ui-kit-v2-dynamic-list.mjs.map +1 -1
  13. package/fesm2022/rt-tools-ui-kit-v2-dynamic-selector.mjs +277 -904
  14. package/fesm2022/rt-tools-ui-kit-v2-dynamic-selector.mjs.map +1 -1
  15. package/fesm2022/rt-tools-ui-kit-v2-filter-control.mjs +2 -2
  16. package/fesm2022/rt-tools-ui-kit-v2-filter-control.mjs.map +1 -1
  17. package/fesm2022/rt-tools-ui-kit-v2-hybrid-tree.mjs +1 -1
  18. package/fesm2022/rt-tools-ui-kit-v2-hybrid-tree.mjs.map +1 -1
  19. package/fesm2022/rt-tools-ui-kit-v2-multiselect.mjs +35 -48
  20. package/fesm2022/rt-tools-ui-kit-v2-multiselect.mjs.map +1 -1
  21. package/fesm2022/rt-tools-ui-kit-v2-page-header.mjs +2 -2
  22. package/fesm2022/rt-tools-ui-kit-v2-pdf-viewer.mjs +2 -2
  23. package/fesm2022/rt-tools-ui-kit-v2-pdf-viewer.mjs.map +1 -1
  24. package/fesm2022/rt-tools-ui-kit-v2-popover.mjs +4 -0
  25. package/fesm2022/rt-tools-ui-kit-v2-popover.mjs.map +1 -1
  26. package/fesm2022/rt-tools-ui-kit-v2-rich-editor.mjs +4 -4
  27. package/fesm2022/rt-tools-ui-kit-v2-rich-editor.mjs.map +1 -1
  28. package/fesm2022/rt-tools-ui-kit-v2-select.mjs +37 -181
  29. package/fesm2022/rt-tools-ui-kit-v2-select.mjs.map +1 -1
  30. package/fesm2022/rt-tools-ui-kit-v2-side-menu.mjs +5 -36
  31. package/fesm2022/rt-tools-ui-kit-v2-side-menu.mjs.map +1 -1
  32. package/fesm2022/rt-tools-ui-kit-v2-table.mjs +4 -1
  33. package/fesm2022/rt-tools-ui-kit-v2-table.mjs.map +1 -1
  34. package/fesm2022/rt-tools-ui-kit-v2-tag.mjs +2 -3
  35. package/fesm2022/rt-tools-ui-kit-v2-tag.mjs.map +1 -1
  36. package/fesm2022/rt-tools-ui-kit-v2-toolbar.mjs +2 -2
  37. package/fesm2022/rt-tools-ui-kit-v2-toolbar.mjs.map +1 -1
  38. package/fesm2022/rt-tools-ui-kit-v2-tree-selector.mjs +8 -13
  39. package/fesm2022/rt-tools-ui-kit-v2-tree-selector.mjs.map +1 -1
  40. package/fesm2022/rt-tools-ui-kit-v2-tree.mjs +12 -7
  41. package/fesm2022/rt-tools-ui-kit-v2-tree.mjs.map +1 -1
  42. package/fesm2022/rt-tools-ui-kit-v2-workspace.mjs +2 -2
  43. package/fesm2022/rt-tools-ui-kit-v2-workspace.mjs.map +1 -1
  44. package/package.json +1 -1
  45. package/src/lib/components/dynamic-selector/stories/component/test-dynamic-selector-popup-frame.component.scss +6 -0
  46. package/src/lib/components/page-header/rt-page-header.component.scss +4 -2
  47. package/src/lib/components/popover/CONTEXT.md +2 -1
  48. package/src/lib/components/tree/CONTEXT.md +1 -1
  49. package/src/rich-editor/lib/components/ai-chat/CONTEXT.md +1 -1
  50. package/src/styles/_breakpoints.scss +37 -4
  51. package/src/styles/_index.scss +0 -4
  52. package/styles/tokens.css +0 -8
  53. package/types/rt-tools-ui-kit-v2-autocomplete.d.ts +4 -3
  54. package/types/rt-tools-ui-kit-v2-core.d.ts +155 -1
  55. package/types/rt-tools-ui-kit-v2-dynamic-selector.d.ts +51 -269
  56. package/types/rt-tools-ui-kit-v2-multiselect.d.ts +4 -3
  57. package/types/rt-tools-ui-kit-v2-select.d.ts +12 -74
  58. package/types/rt-tools-ui-kit-v2-side-menu.d.ts +5 -20
  59. package/types/rt-tools-ui-kit-v2-table.d.ts +4 -0
  60. package/types/rt-tools-ui-kit-v2-tag.d.ts +2 -3
  61. package/types/rt-tools-ui-kit-v2-tree-selector.d.ts +0 -2
  62. package/types/rt-tools-ui-kit-v2-tree.d.ts +7 -9
  63. package/src/styles/_place.scss +0 -7
@@ -108,7 +108,7 @@ var RtFilterControlComponent = class RtFilterControlComponent {
108
108
  host: { classAttribute: "rt-filter-control" },
109
109
  ngImport: i0,
110
110
  template: "@if (isNarrow()) {\n <rt-select\n qa-dataid=\"filter-control-select\"\n size=\"md\"\n [options]=\"selectOptions()\"\n [placeholder]=\"placeholder()\"\n [ariaLabel]=\"ariaLabel()\"\n [clearable]=\"false\"\n [disabled]=\"disabled()\"\n [ngModel]=\"value()\"\n (selectionChange)=\"onSelectionChange($event)\" />\n} @else {\n <rt-toggle-button-group\n qa-dataid=\"filter-control-group\"\n [options]=\"options()\"\n [value]=\"value()\"\n [ariaLabel]=\"ariaLabel()\"\n [size]=\"size()\"\n [disabled]=\"disabled()\"\n [fullWidth]=\"fullWidth()\"\n (valueChange)=\"onValueChange($event)\" />\n}\n",
111
- styles: ["@layer rt-kit.components{.rt-filter-control{display:inline-flex}@container rt-place (width <= 1080px){.rt-filter-control{display:block}.rt-filter-control .rt-select{inline-size:100%;max-inline-size:var(--rt-input-max-width)}}@container rt-place (width <= 768px){.rt-filter-control{inline-size:100%}}}\n"],
111
+ styles: ["@layer rt-kit.components{.rt-filter-control{display:inline-flex}@container rt-place (width <= 1080px){.rt-filter-control{display:block}.rt-filter-control .rt-select{inline-size:100%;max-inline-size:var(--rt-input-max-width)}}@media(width<=1080px){:where(html:not(:has(.rt-container))) .rt-filter-control{display:block}:where(html:not(:has(.rt-container))) .rt-filter-control .rt-select{inline-size:100%;max-inline-size:var(--rt-input-max-width)}}@container rt-place (width <= 768px){.rt-filter-control{inline-size:100%}}@media(width<=768px){:where(html:not(:has(.rt-container))) .rt-filter-control{inline-size:100%}}}\n"],
112
112
  dependencies: [
113
113
  {
114
114
  kind: "ngmodule",
@@ -188,7 +188,7 @@ i0.ɵɵngDeclareClassMetadata({
188
188
  ],
189
189
  host: { class: BEM_BLOCK },
190
190
  template: "@if (isNarrow()) {\n <rt-select\n qa-dataid=\"filter-control-select\"\n size=\"md\"\n [options]=\"selectOptions()\"\n [placeholder]=\"placeholder()\"\n [ariaLabel]=\"ariaLabel()\"\n [clearable]=\"false\"\n [disabled]=\"disabled()\"\n [ngModel]=\"value()\"\n (selectionChange)=\"onSelectionChange($event)\" />\n} @else {\n <rt-toggle-button-group\n qa-dataid=\"filter-control-group\"\n [options]=\"options()\"\n [value]=\"value()\"\n [ariaLabel]=\"ariaLabel()\"\n [size]=\"size()\"\n [disabled]=\"disabled()\"\n [fullWidth]=\"fullWidth()\"\n (valueChange)=\"onValueChange($event)\" />\n}\n",
191
- styles: ["@layer rt-kit.components{.rt-filter-control{display:inline-flex}@container rt-place (width <= 1080px){.rt-filter-control{display:block}.rt-filter-control .rt-select{inline-size:100%;max-inline-size:var(--rt-input-max-width)}}@container rt-place (width <= 768px){.rt-filter-control{inline-size:100%}}}\n"]
191
+ styles: ["@layer rt-kit.components{.rt-filter-control{display:inline-flex}@container rt-place (width <= 1080px){.rt-filter-control{display:block}.rt-filter-control .rt-select{inline-size:100%;max-inline-size:var(--rt-input-max-width)}}@media(width<=1080px){:where(html:not(:has(.rt-container))) .rt-filter-control{display:block}:where(html:not(:has(.rt-container))) .rt-filter-control .rt-select{inline-size:100%;max-inline-size:var(--rt-input-max-width)}}@container rt-place (width <= 768px){.rt-filter-control{inline-size:100%}}@media(width<=768px){:where(html:not(:has(.rt-container))) .rt-filter-control{inline-size:100%}}}\n"]
192
192
  }]
193
193
  }],
194
194
  propDecorators: {
@@ -1 +1 @@
1
- {"version":3,"file":"rt-tools-ui-kit-v2-filter-control.mjs","names":[],"sources":["../../../projects/ui-kit-v2/src/lib/components/filter-control/rt-filter-control.component.ts","../../../projects/ui-kit-v2/src/lib/components/filter-control/rt-filter-control.component.html"],"sourcesContent":["import { BooleanInput } from '@angular/cdk/coercion';\nimport {\n booleanAttribute,\n ChangeDetectionStrategy,\n Component,\n computed,\n inject,\n input,\n InputSignal,\n InputSignalWithTransform,\n output,\n OutputEmitterRef,\n Signal,\n ViewEncapsulation,\n} from '@angular/core';\nimport { FormsModule } from '@angular/forms';\n\nimport { BreakpointsService } from '@rt-tools/ui-kit-v2/core';\n\nimport { RtSelectComponent } from '@rt-tools/ui-kit-v2/select';\nimport { IRtSelect } from '@rt-tools/ui-kit-v2/select';\nimport { RtToggleButtonGroupComponent } from '@rt-tools/ui-kit-v2/toggle-button-group';\nimport { IRtFilterControl } from './rt-filter-control.model';\n\nconst BEM_BLOCK: string = 'rt-filter-control';\n\n/**\n * Адаптивный фильтр-контрол для тулбаров list-страниц.\n *\n * Один и тот же набор опций рендерится двумя способами по ширине вьюпорта:\n * - десктоп (> 1080px) — развёрнутые сегменты `rt-toggle-button-group`;\n * - узкий экран (≤ 1080px, `BreakpointsService.narrow`) — компактный\n * `rt-select` с `clearable=false` (сброс — через явную опцию «Все»).\n *\n * API повторяет controlled-контракт rt-toggle-button-group (`[options]` /\n * `[value]` / `(valueChange)`), поэтому миграция потребителя — смена селектора\n * без правки хендлеров. Sentinel «нет фильтра» задаёт сам потребитель реальным\n * значением опции («»/false), а не `null`/`undefined` — rt-select всегда\n * показывает выбранную опцию.\n *\n * Generic `<T = string>` сохраняет типизацию по value (union'ы, enum'ы).\n */\n@Component({\n selector: 'rt-filter-control',\n templateUrl: './rt-filter-control.component.html',\n styleUrls: ['./rt-filter-control.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [\n // Angular\n FormsModule,\n\n // standalone components / directives\n RtSelectComponent,\n RtToggleButtonGroupComponent,\n ],\n host: {\n class: BEM_BLOCK,\n },\n})\nexport class RtFilterControlComponent<T = string> {\n readonly #breakpoints: BreakpointsService = inject(BreakpointsService);\n\n protected readonly isNarrow: Signal<boolean> = this.#breakpoints.narrow;\n\n /** Проекция опций фильтра в форму rt-select (icon/title отбрасываются). */\n protected readonly selectOptions: Signal<ReadonlyArray<IRtSelect.Option<T>>> = computed((): ReadonlyArray<IRtSelect.Option<T>> =>\n this.options().map((option: IRtFilterControl.Option<T>): IRtSelect.Option<T> => ({\n label: option.label,\n value: option.value,\n }))\n );\n\n public readonly options: InputSignal<ReadonlyArray<IRtFilterControl.Option<T>>> =\n input.required<ReadonlyArray<IRtFilterControl.Option<T>>>();\n\n public readonly value: InputSignal<T | undefined> = input<T | undefined>(undefined);\n\n public readonly ariaLabel: InputSignal<string | null> = input<string | null>(null);\n\n public readonly placeholder: InputSignal<string> = input<string>('');\n\n /**\n * Размер сегментов (десктоп). На узком экране select всегда рендерится в md,\n * чтобы совпадать по высоте и ширине со строкой поиска list-страницы.\n */\n public readonly size: InputSignal<IRtFilterControl.Size> = input<IRtFilterControl.Size>('sm');\n\n public readonly disabled: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n /** Растягивает сегменты на всю ширину контейнера (только десктоп-вариант). */\n public readonly fullWidth: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n public readonly valueChange: OutputEmitterRef<T> = output<T>();\n\n protected onValueChange(value: T): void {\n this.valueChange.emit(value);\n }\n\n protected onSelectionChange(value: T | null): void {\n // rt-select с clearable=false и явной опцией «Все» не эмитит null,\n // но guard оставляем на случай программной очистки.\n if (value !== null) {\n this.valueChange.emit(value);\n }\n }\n}\n","@if (isNarrow()) {\n <rt-select\n qa-dataid=\"filter-control-select\"\n size=\"md\"\n [options]=\"selectOptions()\"\n [placeholder]=\"placeholder()\"\n [ariaLabel]=\"ariaLabel()\"\n [clearable]=\"false\"\n [disabled]=\"disabled()\"\n [ngModel]=\"value()\"\n (selectionChange)=\"onSelectionChange($event)\" />\n} @else {\n <rt-toggle-button-group\n qa-dataid=\"filter-control-group\"\n [options]=\"options()\"\n [value]=\"value()\"\n [ariaLabel]=\"ariaLabel()\"\n [size]=\"size()\"\n [disabled]=\"disabled()\"\n [fullWidth]=\"fullWidth()\"\n (valueChange)=\"onValueChange($event)\" />\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"rt-tools-ui-kit-v2-filter-control.mjs","names":[],"sources":["../../../projects/ui-kit-v2/src/lib/components/filter-control/rt-filter-control.component.ts","../../../projects/ui-kit-v2/src/lib/components/filter-control/rt-filter-control.component.html"],"sourcesContent":["import { BooleanInput } from '@angular/cdk/coercion';\nimport {\n booleanAttribute,\n ChangeDetectionStrategy,\n Component,\n computed,\n inject,\n input,\n InputSignal,\n InputSignalWithTransform,\n output,\n OutputEmitterRef,\n Signal,\n ViewEncapsulation,\n} from '@angular/core';\nimport { FormsModule } from '@angular/forms';\n\nimport { BreakpointsService } from '@rt-tools/ui-kit-v2/core';\n\nimport { RtSelectComponent } from '@rt-tools/ui-kit-v2/select';\nimport { IRtSelect } from '@rt-tools/ui-kit-v2/select';\nimport { RtToggleButtonGroupComponent } from '@rt-tools/ui-kit-v2/toggle-button-group';\nimport { IRtFilterControl } from './rt-filter-control.model';\n\nconst BEM_BLOCK: string = 'rt-filter-control';\n\n/**\n * Адаптивный фильтр-контрол для тулбаров list-страниц.\n *\n * Один и тот же набор опций рендерится двумя способами по ширине вьюпорта:\n * - десктоп (> 1080px) — развёрнутые сегменты `rt-toggle-button-group`;\n * - узкий экран (≤ 1080px, `BreakpointsService.narrow`) — компактный\n * `rt-select` с `clearable=false` (сброс — через явную опцию «Все»).\n *\n * API повторяет controlled-контракт rt-toggle-button-group (`[options]` /\n * `[value]` / `(valueChange)`), поэтому миграция потребителя — смена селектора\n * без правки хендлеров. Sentinel «нет фильтра» задаёт сам потребитель реальным\n * значением опции («»/false), а не `null`/`undefined` — rt-select всегда\n * показывает выбранную опцию.\n *\n * Generic `<T = string>` сохраняет типизацию по value (union'ы, enum'ы).\n */\n@Component({\n selector: 'rt-filter-control',\n templateUrl: './rt-filter-control.component.html',\n styleUrls: ['./rt-filter-control.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [\n // Angular\n FormsModule,\n\n // standalone components / directives\n RtSelectComponent,\n RtToggleButtonGroupComponent,\n ],\n host: {\n class: BEM_BLOCK,\n },\n})\nexport class RtFilterControlComponent<T = string> {\n readonly #breakpoints: BreakpointsService = inject(BreakpointsService);\n\n protected readonly isNarrow: Signal<boolean> = this.#breakpoints.narrow;\n\n /** Проекция опций фильтра в форму rt-select (icon/title отбрасываются). */\n protected readonly selectOptions: Signal<ReadonlyArray<IRtSelect.Option<T>>> = computed((): ReadonlyArray<IRtSelect.Option<T>> =>\n this.options().map((option: IRtFilterControl.Option<T>): IRtSelect.Option<T> => ({\n label: option.label,\n value: option.value,\n }))\n );\n\n public readonly options: InputSignal<ReadonlyArray<IRtFilterControl.Option<T>>> =\n input.required<ReadonlyArray<IRtFilterControl.Option<T>>>();\n\n public readonly value: InputSignal<T | undefined> = input<T | undefined>(undefined);\n\n public readonly ariaLabel: InputSignal<string | null> = input<string | null>(null);\n\n public readonly placeholder: InputSignal<string> = input<string>('');\n\n /**\n * Размер сегментов (десктоп). На узком экране select всегда рендерится в md,\n * чтобы совпадать по высоте и ширине со строкой поиска list-страницы.\n */\n public readonly size: InputSignal<IRtFilterControl.Size> = input<IRtFilterControl.Size>('sm');\n\n public readonly disabled: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n /** Растягивает сегменты на всю ширину контейнера (только десктоп-вариант). */\n public readonly fullWidth: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n public readonly valueChange: OutputEmitterRef<T> = output<T>();\n\n protected onValueChange(value: T): void {\n this.valueChange.emit(value);\n }\n\n protected onSelectionChange(value: T | null): void {\n // rt-select с clearable=false и явной опцией «Все» не эмитит null,\n // но guard оставляем на случай программной очистки.\n if (value !== null) {\n this.valueChange.emit(value);\n }\n }\n}\n","@if (isNarrow()) {\n <rt-select\n qa-dataid=\"filter-control-select\"\n size=\"md\"\n [options]=\"selectOptions()\"\n [placeholder]=\"placeholder()\"\n [ariaLabel]=\"ariaLabel()\"\n [clearable]=\"false\"\n [disabled]=\"disabled()\"\n [ngModel]=\"value()\"\n (selectionChange)=\"onSelectionChange($event)\" />\n} @else {\n <rt-toggle-button-group\n qa-dataid=\"filter-control-group\"\n [options]=\"options()\"\n [value]=\"value()\"\n [ariaLabel]=\"ariaLabel()\"\n [size]=\"size()\"\n [disabled]=\"disabled()\"\n [fullWidth]=\"fullWidth()\"\n (valueChange)=\"onValueChange($event)\" />\n}\n"],"mappings":"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"}
@@ -10,7 +10,7 @@ import { RtRadioButtonComponent } from "@rt-tools/ui-kit-v2/radio-button";
10
10
  import { RtTagComponent } from "@rt-tools/ui-kit-v2/tag";
11
11
  import { RtTooltipDirective } from "@rt-tools/ui-kit-v2/tooltip";
12
12
  import { RtTreeComponent, rtTreeAloneBase, rtTreeChoose, rtTreeChooseAlone, rtTreeMark, rtTreeVisibleLeaves } from "@rt-tools/ui-kit-v2/tree";
13
- import { rtTreeLeaves } from "@rt-tools/ui-kit-v2/select";
13
+ import { rtTreeLeaves } from "@rt-tools/ui-kit-v2/core";
14
14
  function childrenOf(node) {
15
15
  return node.children ?? [];
16
16
  }
@@ -1 +1 @@
1
- {"version":3,"file":"rt-tools-ui-kit-v2-hybrid-tree.mjs","names":[],"sources":["../../../projects/ui-kit-v2/src/lib/components/hybrid-tree/rt-hybrid-tree.logic.ts","../../../projects/ui-kit-v2/src/lib/components/hybrid-tree/rt-hybrid-tree.component.ts","../../../projects/ui-kit-v2/src/lib/components/tree/rt-tree.component.html"],"sourcesContent":["import { rtTreeLeaves } from '@rt-tools/ui-kit-v2/select';\nimport { rtTreeAloneBase, rtTreeChoose, rtTreeChooseAlone, rtTreeMark, rtTreeVisibleLeaves } from '@rt-tools/ui-kit-v2/tree';\nimport { IRtTree } from '@rt-tools/ui-kit-v2/tree';\nimport { IRtHybridTree } from './rt-hybrid-tree.model';\n\n/**\n * Расчёт выбора `rt-hybrid-tree` — чистые функции над неизменяемыми узлами.\n *\n * Всё, что у гибридного дерева общее с `rt-tree`, считает модуль `rt-tree`: здесь только правила\n * группы, где выбирается один лист. Её листья не набираются «выбрать всё» и каскадом ветки, а\n * выбор одного снимает соседей по группе. Каждая функция отвечает новым массивом выбора.\n */\n\ntype TNode<TValue> = IRtHybridTree.Node<TValue>;\n\nfunction childrenOf<TValue>(node: TNode<TValue>): ReadonlyArray<TNode<TValue>> {\n return node.children ?? [];\n}\n\nfunction isBranch<TValue>(node: TNode<TValue>): boolean {\n return childrenOf(node).length > 0;\n}\n\n/** Ветка, в которой выбирается один прямой лист. */\nexport function rtHybridTreeIsSingleGroup<TValue>(node: TNode<TValue>): boolean {\n return !!node.single && isBranch(node);\n}\n\nfunction without<TValue>(value: ReadonlyArray<TValue>, drop: ReadonlyArray<TValue>): ReadonlyArray<TValue> {\n return value.filter((item: TValue): boolean => !drop.includes(item));\n}\n\nfunction withAll<TValue>(value: ReadonlyArray<TValue>, add: ReadonlyArray<TValue>): ReadonlyArray<TValue> {\n return [...value, ...add.filter((item: TValue): boolean => !value.includes(item))];\n}\n\n/** Прямые листья группы — и включённые, и выключенные. */\nfunction directLeaves<TValue>(group: TNode<TValue>): ReadonlyArray<TNode<TValue>> {\n return childrenOf(group).filter((child: TNode<TValue>): boolean => !isBranch(child));\n}\n\n/** Все листья под узлом, выключенные тоже: счёт выбранного видит и их. */\nfunction allLeaves<TValue>(node: TNode<TValue>): ReadonlyArray<TValue> {\n return isBranch(node) ? childrenOf(node).flatMap((child: TNode<TValue>): ReadonlyArray<TValue> => allLeaves(child)) : [node.value];\n}\n\nfunction collectSingles<TValue>(nodes: ReadonlyArray<TNode<TValue>>, into: Set<TValue>): void {\n nodes.forEach((node: TNode<TValue>): void => {\n if (rtHybridTreeIsSingleGroup(node)) {\n directLeaves(node).forEach((leaf: TNode<TValue>): void => {\n into.add(leaf.value);\n });\n }\n collectSingles(childrenOf(node), into);\n });\n}\n\n/** Листья групп, где выбирается один: их не набирают ни «выбрать всё», ни каскад ветки. */\nexport function rtHybridTreeSingleLeaves<TValue>(nodes: ReadonlyArray<TNode<TValue>>): ReadonlySet<TValue> {\n const singles: Set<TValue> = new Set<TValue>();\n collectSingles(nodes, singles);\n return singles;\n}\n\n/** Группа «один лист», прямым листом которой стоит значение. */\nexport function rtHybridTreeGroupOf<TValue>(nodes: ReadonlyArray<TNode<TValue>>, value: TValue): TNode<TValue> | null {\n for (const node of nodes) {\n if (rtHybridTreeIsSingleGroup(node) && directLeaves(node).some((leaf: TNode<TValue>): boolean => leaf.value === value)) {\n return node;\n }\n const found: TNode<TValue> | null = rtHybridTreeGroupOf(childrenOf(node), value);\n if (found) {\n return found;\n }\n }\n return null;\n}\n\nfunction freeLeaves<TValue>(node: TNode<TValue>, singles: ReadonlySet<TValue>): ReadonlyArray<TValue> {\n return rtTreeLeaves(node).filter((leaf: TValue): boolean => !singles.has(leaf));\n}\n\n/**\n * Отметка строки. Группа «один лист» отмечена, пока выбран любой её лист. Прочая ветка в каскаде\n * отмечена по своим свободным листьям; без выбранных свободных — частью, пока под ней что-то выбрано.\n */\nexport function rtHybridTreeMark<TValue>(\n node: TNode<TValue>,\n value: ReadonlyArray<TValue>,\n cascade: boolean,\n singles: ReadonlySet<TValue>\n): IRtTree.Mark {\n if (rtHybridTreeIsSingleGroup(node)) {\n return directLeaves(node).some((leaf: TNode<TValue>): boolean => value.includes(leaf.value)) ? 'all' : 'none';\n }\n if (!cascade || !isBranch(node)) {\n return rtTreeMark(node, value, cascade);\n }\n const free: ReadonlyArray<TValue> = freeLeaves(node, singles);\n const count: number = free.filter((leaf: TValue): boolean => value.includes(leaf)).length;\n if (count > 0) {\n return count === free.length ? 'all' : 'some';\n }\n return allLeaves(node).some((leaf: TValue): boolean => value.includes(leaf)) ? 'some' : 'none';\n}\n\n/** Клик по листу группы «один лист»: лист выбирается вместо соседей, выбранный снимается. */\nfunction chooseSingleLeaf<TValue>(group: TNode<TValue>, leaf: TValue, value: ReadonlyArray<TValue>): ReadonlyArray<TValue> {\n if (value.includes(leaf)) {\n return without(value, [leaf]);\n }\n const siblings: ReadonlyArray<TValue> = directLeaves(group)\n .filter((child: TNode<TValue>): boolean => !child.disabled && child.value !== leaf)\n .map((child: TNode<TValue>): TValue => child.value);\n return withAll(without(value, siblings), [leaf]);\n}\n\n/** Радио группы «один лист»: снимает выбор группы или выбирает её первый включённый лист. */\nfunction chooseSingleGroup<TValue>(group: TNode<TValue>, value: ReadonlyArray<TValue>): ReadonlyArray<TValue> {\n const enabled: ReadonlyArray<TValue> = directLeaves(group)\n .filter((child: TNode<TValue>): boolean => !child.disabled)\n .map((child: TNode<TValue>): TValue => child.value);\n if (directLeaves(group).some((child: TNode<TValue>): boolean => value.includes(child.value))) {\n return without(value, enabled);\n }\n return enabled.length > 0 ? withAll(value, [enabled[0]]) : value;\n}\n\n/** Каскад ветки: добавляет свободные листья, а когда добавлять нечего — снимает все, листья групп «один лист» тоже. */\nfunction chooseBranch<TValue>(node: TNode<TValue>, value: ReadonlyArray<TValue>, singles: ReadonlySet<TValue>): ReadonlyArray<TValue> {\n const free: ReadonlyArray<TValue> = freeLeaves(node, singles);\n if (free.length === 0 || rtHybridTreeMark(node, value, true, singles) === 'all') {\n return without(value, rtTreeLeaves(node));\n }\n return withAll(value, free);\n}\n\n/** Выбор после клика в режиме флажков, без правила одного узла на клик. */\nfunction chooseMultiple<TValue>(\n nodes: ReadonlyArray<TNode<TValue>>,\n node: TNode<TValue>,\n value: ReadonlyArray<TValue>,\n cascade: boolean,\n singles: ReadonlySet<TValue>\n): ReadonlyArray<TValue> {\n if (rtHybridTreeIsSingleGroup(node)) {\n return chooseSingleGroup(node, value);\n }\n const group: TNode<TValue> | null = singles.has(node.value) ? rtHybridTreeGroupOf(nodes, node.value) : null;\n if (group) {\n return chooseSingleLeaf(group, node.value, value);\n }\n if (cascade && isBranch(node)) {\n return chooseBranch(node, value, singles);\n }\n return rtTreeChoose(node, value, 'multiple', cascade);\n}\n\n/**\n * Выбор после клика по узлу. В режиме флажков действуют правила групп «один лист»: клик без Ctrl и\n * Cmd в исключающем режиме оставляет свободный узел одним, а лист группы «один лист» выбор вне\n * группы не трогает. Остальные режимы считает `rt-tree`.\n */\nexport function rtHybridTreeChoose<TValue>(\n nodes: ReadonlyArray<TNode<TValue>>,\n node: TNode<TValue>,\n value: ReadonlyArray<TValue>,\n options: IRtHybridTree.ChooseOptions\n): ReadonlyArray<TValue> {\n if (node.disabled) {\n return value;\n }\n if (options.mode !== 'multiple') {\n return rtTreeChoose(node, value, options.mode, options.cascade);\n }\n const singles: ReadonlySet<TValue> = rtHybridTreeSingleLeaves(nodes);\n if (!options.alone || singles.has(node.value)) {\n return chooseMultiple(nodes, node, value, options.cascade, singles);\n }\n if (singles.size === 0) {\n return rtTreeChooseAlone(nodes, node, value, options.cascade);\n }\n return chooseMultiple(nodes, node, rtTreeAloneBase(nodes, node, value), options.cascade, singles);\n}\n\n/** Отметка «выбрать всё» по свободным листьям видимых строк. */\nexport function rtHybridTreeSelectAllMark<TValue>(\n rows: ReadonlyArray<IRtTree.Row<TValue>>,\n value: ReadonlyArray<TValue>,\n singles: ReadonlySet<TValue>\n): IRtTree.Mark {\n const free: ReadonlyArray<TValue> = rtTreeVisibleLeaves(rows).filter((leaf: TValue): boolean => !singles.has(leaf));\n const count: number = free.filter((leaf: TValue): boolean => value.includes(leaf)).length;\n if (count === 0) {\n return 'none';\n }\n return count === free.length ? 'all' : 'some';\n}\n\n/**\n * Выбор после клика по «выбрать всё»: свободные листья видимых строк добавляются, листья групп\n * «один лист» остаются как были. Когда выбраны все свободные — снимаются все видимые листья.\n */\nexport function rtHybridTreeSelectAll<TValue>(\n rows: ReadonlyArray<IRtTree.Row<TValue>>,\n value: ReadonlyArray<TValue>,\n singles: ReadonlySet<TValue>\n): ReadonlyArray<TValue> {\n const leaves: ReadonlyArray<TValue> = rtTreeVisibleLeaves(rows);\n if (rtHybridTreeSelectAllMark(rows, value, singles) === 'all') {\n return without(value, leaves);\n }\n return withAll(\n value,\n leaves.filter((leaf: TValue): boolean => !singles.has(leaf))\n );\n}\n\n/** Сколько листьев под узлом выбрано. У листа счёта нет. */\nexport function rtHybridTreeChosenCount<TValue>(node: TNode<TValue>, value: ReadonlyArray<TValue>): number {\n if (!isBranch(node)) {\n return 0;\n }\n return allLeaves(node).filter((leaf: TValue): boolean => value.includes(leaf)).length;\n}\n","import { NgTemplateOutlet } from '@angular/common';\nimport { computed, ChangeDetectionStrategy, Component, Signal, ViewEncapsulation } from '@angular/core';\n\nimport { FormsModule } from '@angular/forms';\n\nimport { BlockDirective, ElemDirective, ModDirective } from '@rt-tools/core';\n\nimport { RtCheckboxComponent } from '@rt-tools/ui-kit-v2/checkbox';\nimport { RtIconComponent } from '@rt-tools/ui-kit-v2/icon';\nimport { RtRadioButtonComponent } from '@rt-tools/ui-kit-v2/radio-button';\nimport { RtTagComponent } from '@rt-tools/ui-kit-v2/tag';\nimport { RtTooltipDirective } from '@rt-tools/ui-kit-v2/tooltip';\nimport { RtTreeComponent } from '@rt-tools/ui-kit-v2/tree';\nimport { IRtTree } from '@rt-tools/ui-kit-v2/tree';\nimport {\n rtHybridTreeChoose,\n rtHybridTreeChosenCount,\n rtHybridTreeIsSingleGroup,\n rtHybridTreeMark,\n rtHybridTreeSelectAll,\n rtHybridTreeSelectAllMark,\n rtHybridTreeSingleLeaves,\n} from './rt-hybrid-tree.logic';\n\n/** Блок разметки — блок `rt-tree`: разметка и стили у деревьев одни, отличается только расчёт выбора. */\nconst BEM_BLOCK: string = 'rt-tree';\n\n/**\n * Дерево выбора, где часть групп выбирает один лист.\n *\n * Строки, клавиши, поиск, метки и разметку приложения берёт у `rt-tree` — та же разметка и те же\n * стили. Своё здесь только то, как считается выбор: группа с `single` держит один прямой лист, её\n * листья не набираются ни «выбрать всё», ни каскадом ветки, а группа без отметки показывает число\n * выбранного под ней. Узлы — `IRtHybridTree.Node`.\n */\n@Component({\n selector: 'rt-hybrid-tree',\n templateUrl: '../tree/rt-tree.component.html',\n styleUrl: '../tree/rt-tree.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [\n NgTemplateOutlet,\n FormsModule,\n RtCheckboxComponent,\n RtIconComponent,\n RtRadioButtonComponent,\n RtTagComponent,\n RtTooltipDirective,\n BlockDirective,\n ElemDirective,\n ModDirective,\n ],\n host: {\n class: BEM_BLOCK,\n role: 'tree',\n '[attr.tabindex]': 'disabled() ? -1 : 0',\n '[attr.aria-disabled]': 'disabled() || null',\n '[attr.aria-label]': 'ariaLabel()',\n '[attr.aria-multiselectable]': \"mode() === 'multiple'\",\n '(keydown)': 'handleKeydown($event)',\n },\n})\nexport class RtHybridTreeComponent<TValue> extends RtTreeComponent<TValue> {\n /** Листья групп «один лист». */\n protected readonly singles: Signal<ReadonlySet<TValue>> = computed((): ReadonlySet<TValue> => rtHybridTreeSingleLeaves(this.nodes()));\n\n protected override rowState(row: IRtTree.Row<TValue>, value: ReadonlyArray<TValue>, cascade: boolean): IRtTree.RowState {\n const node: IRtTree.Node<TValue> = row.option;\n const single: boolean = this.singles().has(node.value) || rtHybridTreeIsSingleGroup(node);\n const count: number = rtHybridTreeChosenCount(node, value);\n return {\n mark: rtHybridTreeMark(node, value, cascade, this.singles()),\n radio: this.mode() === 'single' || (this.mode() === 'multiple' && single),\n count: count > 0 ? count : null,\n };\n }\n\n protected override selectAllMarkOf(rows: ReadonlyArray<IRtTree.Row<TValue>>, value: ReadonlyArray<TValue>): IRtTree.Mark {\n return rtHybridTreeSelectAllMark(rows, value, this.singles());\n }\n\n protected override selectAllChoice(rows: ReadonlyArray<IRtTree.Row<TValue>>, value: ReadonlyArray<TValue>): ReadonlyArray<TValue> {\n return rtHybridTreeSelectAll(rows, value, this.singles());\n }\n\n protected override nextChoice(node: IRtTree.Node<TValue>, additive: boolean): ReadonlyArray<TValue> {\n return rtHybridTreeChoose(this.nodes(), node, this.value(), {\n mode: this.mode(),\n cascade: this.cascade(),\n alone: this.exclusive() && !additive,\n });\n }\n}\n","<ng-container rtBlock=\"rt-tree\">\n <!--\n Строки выбираются мышью через (click); клавиатура — на самом дереве (handleKeydown), строки\n по отдельности фокус не берут: подсветка — третье состояние рядом с фокусом и выбором.\n -->\n <!-- eslint-disable @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n @if (isSelectAllShown()) {\n <div\n rtElem=\"select-all\"\n qa-dataid=\"tree-select-all\"\n role=\"presentation\"\n [rtMod]=\"{ disabled: disabled(), 'over-leaf': isSelectAllOverLeaf() }\"\n (click)=\"onSelectAll()\">\n @if (isSelectAllUnderMark()) {\n <span rtElem=\"toggle-space\"></span>\n }\n <rt-checkbox\n rtElem=\"check\"\n inert\n [disabled]=\"disabled()\"\n [indeterminate]=\"selectAllMark() === 'some'\"\n [ngModel]=\"selectAllMark() === 'all'\"\n [ngModelOptions]=\"{ standalone: true }\" />\n <span rtElem=\"label\">{{ t().uiSelectAll }}</span>\n </div>\n }\n\n @for (view of views(); track view.row.option.value) {\n <div\n rtElem=\"row\"\n qa-dataid=\"tree-row\"\n role=\"treeitem\"\n [attr.data-value]=\"view.row.option.value\"\n [attr.aria-level]=\"view.row.level + 1\"\n [attr.aria-expanded]=\"view.row.branch ? view.row.open : null\"\n [attr.aria-selected]=\"view.marked ? view.mark === 'all' : null\"\n [attr.aria-disabled]=\"view.disabled\"\n [style.--rt-tree-level]=\"view.row.level\"\n [rtMod]=\"{ highlighted: view.highlighted, disabled: view.disabled, chosen: view.mark === 'all' }\"\n (click)=\"onRowClick(view.row, $event)\">\n @if (view.row.branch) {\n <button\n rtElem=\"toggle\"\n qa-dataid=\"tree-row-toggle\"\n type=\"button\"\n tabindex=\"-1\"\n [attr.aria-label]=\"view.row.open ? t().uiCollapse : t().uiExpand\"\n (click)=\"onToggleClick($event, view.row.option.value)\">\n <rt-icon size=\"sm\" color=\"muted\" [name]=\"view.row.open ? 'chevron-down' : 'chevron-right'\" />\n </button>\n } @else {\n <span rtElem=\"toggle-space\"></span>\n }\n\n @if (view.marked && !view.radio) {\n <rt-checkbox\n rtElem=\"check\"\n qa-dataid=\"tree-row-checkbox\"\n inert\n [disabled]=\"view.disabled\"\n [indeterminate]=\"view.mark === 'some'\"\n [ngModel]=\"view.mark === 'all'\"\n [ngModelOptions]=\"{ standalone: true }\" />\n } @else if (view.marked) {\n <rt-radio-button\n rtElem=\"check\"\n qa-dataid=\"tree-row-radio\"\n inert\n [value]=\"view.row.option.value\"\n [checked]=\"view.mark === 'all'\"\n [disabled]=\"view.disabled\" />\n }\n\n <span rtElem=\"text\">\n <span rtElem=\"label\" qa-dataid=\"tree-row-label\" rtTooltipWhenTruncated [rtTooltip]=\"view.row.option.label\">\n @for (part of view.parts.label; track $index) {\n <span rtElem=\"part\" [rtMod]=\"{ match: part.matched }\">{{ part.text }}</span>\n }\n </span>\n @if (view.parts.description.length > 0) {\n <span\n rtElem=\"description\"\n qa-dataid=\"tree-row-description\"\n rtTooltipWhenTruncated\n [rtTooltip]=\"view.row.option.description\">\n @for (part of view.parts.description; track $index) {\n <span rtElem=\"part\" [rtMod]=\"{ match: part.matched }\">{{ part.text }}</span>\n }\n </span>\n }\n @if (view.row.option.badges?.length || metaTemplate()) {\n <span rtElem=\"meta\" qa-dataid=\"tree-row-meta\">\n @for (badge of view.row.option.badges; track $index) {\n <rt-tag size=\"sm\" [value]=\"badge.text\" [severity]=\"badge.severity ?? 'neutral'\" [highlight]=\"searchTerm()\" />\n }\n @if (metaTemplate(); as meta) {\n <ng-container [ngTemplateOutlet]=\"meta\" [ngTemplateOutletContext]=\"{ $implicit: view.row.option }\" />\n }\n </span>\n }\n </span>\n\n @if (view.count) {\n <span rtElem=\"count\" qa-dataid=\"tree-row-count\">{{ view.count }}</span>\n }\n\n @if (endTemplate(); as end) {\n <span rtElem=\"end\" qa-dataid=\"tree-row-end\">\n <ng-container [ngTemplateOutlet]=\"end\" [ngTemplateOutletContext]=\"{ $implicit: view.row.option }\" />\n </span>\n }\n </div>\n } @empty {\n <div rtElem=\"empty\" qa-dataid=\"tree-empty\" [rtMod]=\"{ 'with-icon': !!emptyIcon() }\">\n @if (emptyIcon(); as icon) {\n <rt-icon rtElem=\"empty-icon\" qa-dataid=\"tree-empty-icon\" size=\"lg\" [name]=\"icon\" />\n }\n {{ emptyText() }}\n </div>\n }\n</ng-container>\n"],"mappings":";;;;;;;;;;;;;AAeA,SAAS,WAAmB,MAAmB;CAC3C,OAAO,KAAK,YAAY,CAAA;AAC5B;AAEA,SAAS,SAAiB,MAAmB;CACzC,OAAO,WAAW,IAAI,CAAC,CAAC,SAAS;AACrC;AAGA,SAAgB,0BAAkC,MAAmB;CACjE,OAAO,CAAC,CAAC,KAAK,UAAU,SAAS,IAAI;AACzC;AAEA,SAAS,QAAgB,OAA8B,MAA2B;CAC9E,OAAO,MAAM,QAAQ,SAA0B,CAAC,KAAK,SAAS,IAAI,CAAC;AACvE;AAEA,SAAS,QAAgB,OAA8B,KAA0B;CAC7E,OAAO,CAAC,GAAG,OAAO,GAAG,IAAI,QAAQ,SAA0B,CAAC,MAAM,SAAS,IAAI,CAAC,CAAC;AACrF;AAGA,SAAS,aAAqB,OAAoB;CAC9C,OAAO,WAAW,KAAK,CAAC,CAAC,QAAQ,UAAkC,CAAC,SAAS,KAAK,CAAC;AACvF;AAGA,SAAS,UAAkB,MAAmB;CAC1C,OAAO,SAAS,IAAI,IAAI,WAAW,IAAI,CAAC,CAAC,SAAS,UAAgD,UAAU,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK;AACrI;AAEA,SAAS,eAAuB,OAAqC,MAAiB;CAClF,MAAM,SAAS,SAA6B;EACxC,IAAI,0BAA0B,IAAI,GAC9B,aAAa,IAAI,CAAC,CAAC,SAAS,SAA6B;GACrD,KAAK,IAAI,KAAK,KAAK;EACvB,CAAC;EAEL,eAAe,WAAW,IAAI,GAAG,IAAI;CACzC,CAAC;AACL;AAGA,SAAgB,yBAAiC,OAAmC;CAChF,MAAM,0BAAuB,IAAI,IAAG;CACpC,eAAe,OAAO,OAAO;CAC7B,OAAO;AACX;AAGA,SAAgB,oBAA4B,OAAqC,OAAa;CAC1F,KAAK,MAAM,QAAQ,OAAO;EACtB,IAAI,0BAA0B,IAAI,KAAK,aAAa,IAAI,CAAC,CAAC,MAAM,SAAiC,KAAK,UAAU,KAAK,GACjH,OAAO;EAEX,MAAM,QAA8B,oBAAoB,WAAW,IAAI,GAAG,KAAK;EAC/E,IAAI,OACA,OAAO;CAEf;CACA,OAAO;AACX;AAEA,SAAS,WAAmB,MAAqB,SAA4B;CACzE,OAAO,aAAa,IAAI,CAAC,CAAC,QAAQ,SAA0B,CAAC,QAAQ,IAAI,IAAI,CAAC;AAClF;AAMA,SAAgB,iBACZ,MACA,OACA,SACA,SAA4B;CAE5B,IAAI,0BAA0B,IAAI,GAC9B,OAAO,aAAa,IAAI,CAAC,CAAC,MAAM,SAAiC,MAAM,SAAS,KAAK,KAAK,CAAC,IAAI,QAAQ;CAE3G,IAAI,CAAC,WAAW,CAAC,SAAS,IAAI,GAC1B,OAAO,WAAW,MAAM,OAAO,OAAO;CAE1C,MAAM,OAA8B,WAAW,MAAM,OAAO;CAC5D,MAAM,QAAgB,KAAK,QAAQ,SAA0B,MAAM,SAAS,IAAI,CAAC,CAAC,CAAC;CACnF,IAAI,QAAQ,GACR,OAAO,UAAU,KAAK,SAAS,QAAQ;CAE3C,OAAO,UAAU,IAAI,CAAC,CAAC,MAAM,SAA0B,MAAM,SAAS,IAAI,CAAC,IAAI,SAAS;AAC5F;AAGA,SAAS,iBAAyB,OAAsB,MAAc,OAA4B;CAC9F,IAAI,MAAM,SAAS,IAAI,GACnB,OAAO,QAAQ,OAAO,CAAC,IAAI,CAAC;CAKhC,OAAO,QAAQ,QAAQ,OAHiB,aAAa,KAAK,CAAC,CACtD,QAAQ,UAAkC,CAAC,MAAM,YAAY,MAAM,UAAU,IAAI,CAAC,CAClF,KAAK,UAAiC,MAAM,KACnB,CAAQ,GAAG,CAAC,IAAI,CAAC;AACnD;AAGA,SAAS,kBAA0B,OAAsB,OAA4B;CACjF,MAAM,UAAiC,aAAa,KAAK,CAAC,CACrD,QAAQ,UAAkC,CAAC,MAAM,QAAQ,CAAC,CAC1D,KAAK,UAAiC,MAAM,KAAK;CACtD,IAAI,aAAa,KAAK,CAAC,CAAC,MAAM,UAAkC,MAAM,SAAS,MAAM,KAAK,CAAC,GACvF,OAAO,QAAQ,OAAO,OAAO;CAEjC,OAAO,QAAQ,SAAS,IAAI,QAAQ,OAAO,CAAC,QAAQ,EAAE,CAAC,IAAI;AAC/D;AAGA,SAAS,aAAqB,MAAqB,OAA8B,SAA4B;CACzG,MAAM,OAA8B,WAAW,MAAM,OAAO;CAC5D,IAAI,KAAK,WAAW,KAAK,iBAAiB,MAAM,OAAO,MAAM,OAAO,MAAM,OACtE,OAAO,QAAQ,OAAO,aAAa,IAAI,CAAC;CAE5C,OAAO,QAAQ,OAAO,IAAI;AAC9B;AAGA,SAAS,eACL,OACA,MACA,OACA,SACA,SAA4B;CAE5B,IAAI,0BAA0B,IAAI,GAC9B,OAAO,kBAAkB,MAAM,KAAK;CAExC,MAAM,QAA8B,QAAQ,IAAI,KAAK,KAAK,IAAI,oBAAoB,OAAO,KAAK,KAAK,IAAI;CACvG,IAAI,OACA,OAAO,iBAAiB,OAAO,KAAK,OAAO,KAAK;CAEpD,IAAI,WAAW,SAAS,IAAI,GACxB,OAAO,aAAa,MAAM,OAAO,OAAO;CAE5C,OAAO,aAAa,MAAM,OAAO,YAAY,OAAO;AACxD;AAOA,SAAgB,mBACZ,OACA,MACA,OACA,SAAoC;CAEpC,IAAI,KAAK,UACL,OAAO;CAEX,IAAI,QAAQ,SAAS,YACjB,OAAO,aAAa,MAAM,OAAO,QAAQ,MAAM,QAAQ,OAAO;CAElE,MAAM,UAA+B,yBAAyB,KAAK;CACnE,IAAI,CAAC,QAAQ,SAAS,QAAQ,IAAI,KAAK,KAAK,GACxC,OAAO,eAAe,OAAO,MAAM,OAAO,QAAQ,SAAS,OAAO;CAEtE,IAAI,QAAQ,SAAS,GACjB,OAAO,kBAAkB,OAAO,MAAM,OAAO,QAAQ,OAAO;CAEhE,OAAO,eAAe,OAAO,MAAM,gBAAgB,OAAO,MAAM,KAAK,GAAG,QAAQ,SAAS,OAAO;AACpG;AAGA,SAAgB,0BACZ,MACA,OACA,SAA4B;CAE5B,MAAM,OAA8B,oBAAoB,IAAI,CAAC,CAAC,QAAQ,SAA0B,CAAC,QAAQ,IAAI,IAAI,CAAC;CAClH,MAAM,QAAgB,KAAK,QAAQ,SAA0B,MAAM,SAAS,IAAI,CAAC,CAAC,CAAC;CACnF,IAAI,UAAU,GACV,OAAO;CAEX,OAAO,UAAU,KAAK,SAAS,QAAQ;AAC3C;AAMA,SAAgB,sBACZ,MACA,OACA,SAA4B;CAE5B,MAAM,SAAgC,oBAAoB,IAAI;CAC9D,IAAI,0BAA0B,MAAM,OAAO,OAAO,MAAM,OACpD,OAAO,QAAQ,OAAO,MAAM;CAEhC,OAAO,QACH,OACA,OAAO,QAAQ,SAA0B,CAAC,QAAQ,IAAI,IAAI,CAAC,CAAC;AAEpE;AAGA,SAAgB,wBAAgC,MAAqB,OAA4B;CAC7F,IAAI,CAAC,SAAS,IAAI,GACd,OAAO;CAEX,OAAO,UAAU,IAAI,CAAC,CAAC,QAAQ,SAA0B,MAAM,SAAS,IAAI,CAAC,CAAC,CAAC;AACnF;ACvMA,MAAM,YAAoB;AAsC1B,IAAa,wBAAb,MAAa,8BAAsC,gBAAuB;CA5B1E,cAAA;;EA8BuB,KAAA,UAAuC,eAAoC,yBAAyB,KAAK,MAAK,CAAE,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CA4BxI;CA1BsB,SAAS,KAA0B,OAA8B,SAAgB;EAChG,MAAM,OAA6B,IAAI;EACvC,MAAM,SAAkB,KAAK,QAAO,CAAE,CAAC,IAAI,KAAK,KAAK,KAAK,0BAA0B,IAAI;EACxF,MAAM,QAAgB,wBAAwB,MAAM,KAAK;EACzD,OAAO;GACH,MAAM,iBAAiB,MAAM,OAAO,SAAS,KAAK,QAAO,CAAE;GAC3D,OAAO,KAAK,KAAI,MAAO,YAAa,KAAK,KAAI,MAAO,cAAc;GAClE,OAAO,QAAQ,IAAI,QAAQ;EAC9B;CACL;CAEmB,gBAAgB,MAA0C,OAA4B;EACrG,OAAO,0BAA0B,MAAM,OAAO,KAAK,QAAO,CAAE;CAChE;CAEmB,gBAAgB,MAA0C,OAA4B;EACrG,OAAO,sBAAsB,MAAM,OAAO,KAAK,QAAO,CAAE;CAC5D;CAEmB,WAAW,MAA4B,UAAiB;EACvE,OAAO,mBAAmB,KAAK,MAAK,GAAI,MAAM,KAAK,MAAK,GAAI;GACxD,MAAM,KAAK,KAAI;GACf,SAAS,KAAK,QAAO;GACrB,OAAO,KAAK,UAAS,KAAM,CAAC;EAC/B,CAAA;CACL;CA7BS;EAAA,KAAA,OAAA,GAAA,mBAAA;GAAA,YAAA;GAAA,SAAA;GAAA,UAAA;GAAA,MAAA;GAAqB,MAAA;GAAA,QAAA,GAAA,gBAAA;EAAA,CAAA;CAAA;CAArB;EAAA,KAAA,OAAA,GAAA,qBAAA;GAAA,YAAA;GAAA,SAAA;GAAA,MAAA;GAAqB,cAAA;GAAA,UAAA;GAAA,MAAA;IAAA,YAAA,EAAA,QAAA,OAAA;IAAA,WAAA,EAAA,WAAA,wBAAA;IAAA,YAAA;KAAA,iBAAA;KAAA,sBAAA;KAAA,mBAAA;KAAA,6BAAA;IAAA;IAAA,gBAAA;GAAA;GAAA,iBAAA;GAAA,UAAA;GAAA,UC/DlC;GAyHA,QAAA,CAAA,49OAAA;GAAA,cAAA;IAAA;KAAA,MAAA;KAAA,MD/EQ;KAAgB,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;KAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MAChB;IAAW;IAAA;KAAA,MAAA;KAAA,MAAA,GAAA;KAAA,UAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MAAA,GAAA;KAAA,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;MAAA;KAAA;KAAA,SAAA,CAAA,eAAA;KAAA,UAAA,CAAA,SAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MACX;KAAmB,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;MAAA;KAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MACnB;KAAe,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;KAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MACf;KAAsB,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;KAAA;KAAA,SAAA,CAAA,eAAA,eAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MACtB;KAAc,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;KAAA;KAAA,SAAA,CAAA,QAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MACd;KAAkB,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;MAAA;KAAA;KAAA,UAAA,CAAA,WAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MAClB;KAAc,UAAA;KAAA,QAAA,CAAA,OAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MACd;KAAa,UAAA;KAAA,QAAA,CAAA,OAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MACb;KAAY,UAAA;IAAA;GAAA;GAAA,iBAAA,GAAA,wBAAA;GAAA,eAAA,GAAA,kBAAA;EAAA,CAAA;CAAA;;AAYP,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAqB,YAAA,CAAA;EA5BjC,MAAA;EACa,MAAA,CAAA;GAAA,UAAA;GAAgB,iBAGT,wBAAwB;GAAM,eAChC,kBAAkB;GAAI,SAC5B;IACL;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACH;GAAA,MACK;IACF,OAAO;IACP,MAAM;IACN,mBAAmB;IACnB,wBAAwB;IACxB,qBAAqB;IACrB,+BAA+B;IAC/B,aAAa;GAChB;GAAA,UAAA;GAAA,QAAA,CAAA,49OAAA;EAAA,CAAA"}
1
+ {"version":3,"file":"rt-tools-ui-kit-v2-hybrid-tree.mjs","names":[],"sources":["../../../projects/ui-kit-v2/src/lib/components/hybrid-tree/rt-hybrid-tree.logic.ts","../../../projects/ui-kit-v2/src/lib/components/hybrid-tree/rt-hybrid-tree.component.ts","../../../projects/ui-kit-v2/src/lib/components/tree/rt-tree.component.html"],"sourcesContent":["import { rtTreeLeaves } from '@rt-tools/ui-kit-v2/core';\nimport { rtTreeAloneBase, rtTreeChoose, rtTreeChooseAlone, rtTreeMark, rtTreeVisibleLeaves } from '@rt-tools/ui-kit-v2/tree';\nimport { IRtTree } from '@rt-tools/ui-kit-v2/tree';\nimport { IRtHybridTree } from './rt-hybrid-tree.model';\n\n/**\n * Расчёт выбора `rt-hybrid-tree` — чистые функции над неизменяемыми узлами.\n *\n * Всё, что у гибридного дерева общее с `rt-tree`, считает модуль `rt-tree`: здесь только правила\n * группы, где выбирается один лист. Её листья не набираются «выбрать всё» и каскадом ветки, а\n * выбор одного снимает соседей по группе. Каждая функция отвечает новым массивом выбора.\n */\n\ntype TNode<TValue> = IRtHybridTree.Node<TValue>;\n\nfunction childrenOf<TValue>(node: TNode<TValue>): ReadonlyArray<TNode<TValue>> {\n return node.children ?? [];\n}\n\nfunction isBranch<TValue>(node: TNode<TValue>): boolean {\n return childrenOf(node).length > 0;\n}\n\n/** Ветка, в которой выбирается один прямой лист. */\nexport function rtHybridTreeIsSingleGroup<TValue>(node: TNode<TValue>): boolean {\n return !!node.single && isBranch(node);\n}\n\nfunction without<TValue>(value: ReadonlyArray<TValue>, drop: ReadonlyArray<TValue>): ReadonlyArray<TValue> {\n return value.filter((item: TValue): boolean => !drop.includes(item));\n}\n\nfunction withAll<TValue>(value: ReadonlyArray<TValue>, add: ReadonlyArray<TValue>): ReadonlyArray<TValue> {\n return [...value, ...add.filter((item: TValue): boolean => !value.includes(item))];\n}\n\n/** Прямые листья группы — и включённые, и выключенные. */\nfunction directLeaves<TValue>(group: TNode<TValue>): ReadonlyArray<TNode<TValue>> {\n return childrenOf(group).filter((child: TNode<TValue>): boolean => !isBranch(child));\n}\n\n/** Все листья под узлом, выключенные тоже: счёт выбранного видит и их. */\nfunction allLeaves<TValue>(node: TNode<TValue>): ReadonlyArray<TValue> {\n return isBranch(node) ? childrenOf(node).flatMap((child: TNode<TValue>): ReadonlyArray<TValue> => allLeaves(child)) : [node.value];\n}\n\nfunction collectSingles<TValue>(nodes: ReadonlyArray<TNode<TValue>>, into: Set<TValue>): void {\n nodes.forEach((node: TNode<TValue>): void => {\n if (rtHybridTreeIsSingleGroup(node)) {\n directLeaves(node).forEach((leaf: TNode<TValue>): void => {\n into.add(leaf.value);\n });\n }\n collectSingles(childrenOf(node), into);\n });\n}\n\n/** Листья групп, где выбирается один: их не набирают ни «выбрать всё», ни каскад ветки. */\nexport function rtHybridTreeSingleLeaves<TValue>(nodes: ReadonlyArray<TNode<TValue>>): ReadonlySet<TValue> {\n const singles: Set<TValue> = new Set<TValue>();\n collectSingles(nodes, singles);\n return singles;\n}\n\n/** Группа «один лист», прямым листом которой стоит значение. */\nexport function rtHybridTreeGroupOf<TValue>(nodes: ReadonlyArray<TNode<TValue>>, value: TValue): TNode<TValue> | null {\n for (const node of nodes) {\n if (rtHybridTreeIsSingleGroup(node) && directLeaves(node).some((leaf: TNode<TValue>): boolean => leaf.value === value)) {\n return node;\n }\n const found: TNode<TValue> | null = rtHybridTreeGroupOf(childrenOf(node), value);\n if (found) {\n return found;\n }\n }\n return null;\n}\n\nfunction freeLeaves<TValue>(node: TNode<TValue>, singles: ReadonlySet<TValue>): ReadonlyArray<TValue> {\n return rtTreeLeaves(node).filter((leaf: TValue): boolean => !singles.has(leaf));\n}\n\n/**\n * Отметка строки. Группа «один лист» отмечена, пока выбран любой её лист. Прочая ветка в каскаде\n * отмечена по своим свободным листьям; без выбранных свободных — частью, пока под ней что-то выбрано.\n */\nexport function rtHybridTreeMark<TValue>(\n node: TNode<TValue>,\n value: ReadonlyArray<TValue>,\n cascade: boolean,\n singles: ReadonlySet<TValue>\n): IRtTree.Mark {\n if (rtHybridTreeIsSingleGroup(node)) {\n return directLeaves(node).some((leaf: TNode<TValue>): boolean => value.includes(leaf.value)) ? 'all' : 'none';\n }\n if (!cascade || !isBranch(node)) {\n return rtTreeMark(node, value, cascade);\n }\n const free: ReadonlyArray<TValue> = freeLeaves(node, singles);\n const count: number = free.filter((leaf: TValue): boolean => value.includes(leaf)).length;\n if (count > 0) {\n return count === free.length ? 'all' : 'some';\n }\n return allLeaves(node).some((leaf: TValue): boolean => value.includes(leaf)) ? 'some' : 'none';\n}\n\n/** Клик по листу группы «один лист»: лист выбирается вместо соседей, выбранный снимается. */\nfunction chooseSingleLeaf<TValue>(group: TNode<TValue>, leaf: TValue, value: ReadonlyArray<TValue>): ReadonlyArray<TValue> {\n if (value.includes(leaf)) {\n return without(value, [leaf]);\n }\n const siblings: ReadonlyArray<TValue> = directLeaves(group)\n .filter((child: TNode<TValue>): boolean => !child.disabled && child.value !== leaf)\n .map((child: TNode<TValue>): TValue => child.value);\n return withAll(without(value, siblings), [leaf]);\n}\n\n/** Радио группы «один лист»: снимает выбор группы или выбирает её первый включённый лист. */\nfunction chooseSingleGroup<TValue>(group: TNode<TValue>, value: ReadonlyArray<TValue>): ReadonlyArray<TValue> {\n const enabled: ReadonlyArray<TValue> = directLeaves(group)\n .filter((child: TNode<TValue>): boolean => !child.disabled)\n .map((child: TNode<TValue>): TValue => child.value);\n if (directLeaves(group).some((child: TNode<TValue>): boolean => value.includes(child.value))) {\n return without(value, enabled);\n }\n return enabled.length > 0 ? withAll(value, [enabled[0]]) : value;\n}\n\n/** Каскад ветки: добавляет свободные листья, а когда добавлять нечего — снимает все, листья групп «один лист» тоже. */\nfunction chooseBranch<TValue>(node: TNode<TValue>, value: ReadonlyArray<TValue>, singles: ReadonlySet<TValue>): ReadonlyArray<TValue> {\n const free: ReadonlyArray<TValue> = freeLeaves(node, singles);\n if (free.length === 0 || rtHybridTreeMark(node, value, true, singles) === 'all') {\n return without(value, rtTreeLeaves(node));\n }\n return withAll(value, free);\n}\n\n/** Выбор после клика в режиме флажков, без правила одного узла на клик. */\nfunction chooseMultiple<TValue>(\n nodes: ReadonlyArray<TNode<TValue>>,\n node: TNode<TValue>,\n value: ReadonlyArray<TValue>,\n cascade: boolean,\n singles: ReadonlySet<TValue>\n): ReadonlyArray<TValue> {\n if (rtHybridTreeIsSingleGroup(node)) {\n return chooseSingleGroup(node, value);\n }\n const group: TNode<TValue> | null = singles.has(node.value) ? rtHybridTreeGroupOf(nodes, node.value) : null;\n if (group) {\n return chooseSingleLeaf(group, node.value, value);\n }\n if (cascade && isBranch(node)) {\n return chooseBranch(node, value, singles);\n }\n return rtTreeChoose(node, value, 'multiple', cascade);\n}\n\n/**\n * Выбор после клика по узлу. В режиме флажков действуют правила групп «один лист»: клик без Ctrl и\n * Cmd в исключающем режиме оставляет свободный узел одним, а лист группы «один лист» выбор вне\n * группы не трогает. Остальные режимы считает `rt-tree`.\n */\nexport function rtHybridTreeChoose<TValue>(\n nodes: ReadonlyArray<TNode<TValue>>,\n node: TNode<TValue>,\n value: ReadonlyArray<TValue>,\n options: IRtHybridTree.ChooseOptions\n): ReadonlyArray<TValue> {\n if (node.disabled) {\n return value;\n }\n if (options.mode !== 'multiple') {\n return rtTreeChoose(node, value, options.mode, options.cascade);\n }\n const singles: ReadonlySet<TValue> = rtHybridTreeSingleLeaves(nodes);\n if (!options.alone || singles.has(node.value)) {\n return chooseMultiple(nodes, node, value, options.cascade, singles);\n }\n if (singles.size === 0) {\n return rtTreeChooseAlone(nodes, node, value, options.cascade);\n }\n return chooseMultiple(nodes, node, rtTreeAloneBase(nodes, node, value), options.cascade, singles);\n}\n\n/** Отметка «выбрать всё» по свободным листьям видимых строк. */\nexport function rtHybridTreeSelectAllMark<TValue>(\n rows: ReadonlyArray<IRtTree.Row<TValue>>,\n value: ReadonlyArray<TValue>,\n singles: ReadonlySet<TValue>\n): IRtTree.Mark {\n const free: ReadonlyArray<TValue> = rtTreeVisibleLeaves(rows).filter((leaf: TValue): boolean => !singles.has(leaf));\n const count: number = free.filter((leaf: TValue): boolean => value.includes(leaf)).length;\n if (count === 0) {\n return 'none';\n }\n return count === free.length ? 'all' : 'some';\n}\n\n/**\n * Выбор после клика по «выбрать всё»: свободные листья видимых строк добавляются, листья групп\n * «один лист» остаются как были. Когда выбраны все свободные — снимаются все видимые листья.\n */\nexport function rtHybridTreeSelectAll<TValue>(\n rows: ReadonlyArray<IRtTree.Row<TValue>>,\n value: ReadonlyArray<TValue>,\n singles: ReadonlySet<TValue>\n): ReadonlyArray<TValue> {\n const leaves: ReadonlyArray<TValue> = rtTreeVisibleLeaves(rows);\n if (rtHybridTreeSelectAllMark(rows, value, singles) === 'all') {\n return without(value, leaves);\n }\n return withAll(\n value,\n leaves.filter((leaf: TValue): boolean => !singles.has(leaf))\n );\n}\n\n/** Сколько листьев под узлом выбрано. У листа счёта нет. */\nexport function rtHybridTreeChosenCount<TValue>(node: TNode<TValue>, value: ReadonlyArray<TValue>): number {\n if (!isBranch(node)) {\n return 0;\n }\n return allLeaves(node).filter((leaf: TValue): boolean => value.includes(leaf)).length;\n}\n","import { NgTemplateOutlet } from '@angular/common';\nimport { computed, ChangeDetectionStrategy, Component, Signal, ViewEncapsulation } from '@angular/core';\n\nimport { FormsModule } from '@angular/forms';\n\nimport { BlockDirective, ElemDirective, ModDirective } from '@rt-tools/core';\n\nimport { RtCheckboxComponent } from '@rt-tools/ui-kit-v2/checkbox';\nimport { RtIconComponent } from '@rt-tools/ui-kit-v2/icon';\nimport { RtRadioButtonComponent } from '@rt-tools/ui-kit-v2/radio-button';\nimport { RtTagComponent } from '@rt-tools/ui-kit-v2/tag';\nimport { RtTooltipDirective } from '@rt-tools/ui-kit-v2/tooltip';\nimport { RtTreeComponent } from '@rt-tools/ui-kit-v2/tree';\nimport { IRtTree } from '@rt-tools/ui-kit-v2/tree';\nimport {\n rtHybridTreeChoose,\n rtHybridTreeChosenCount,\n rtHybridTreeIsSingleGroup,\n rtHybridTreeMark,\n rtHybridTreeSelectAll,\n rtHybridTreeSelectAllMark,\n rtHybridTreeSingleLeaves,\n} from './rt-hybrid-tree.logic';\n\n/** Блок разметки — блок `rt-tree`: разметка и стили у деревьев одни, отличается только расчёт выбора. */\nconst BEM_BLOCK: string = 'rt-tree';\n\n/**\n * Дерево выбора, где часть групп выбирает один лист.\n *\n * Строки, клавиши, поиск, метки и разметку приложения берёт у `rt-tree` — та же разметка и те же\n * стили. Своё здесь только то, как считается выбор: группа с `single` держит один прямой лист, её\n * листья не набираются ни «выбрать всё», ни каскадом ветки, а группа без отметки показывает число\n * выбранного под ней. Узлы — `IRtHybridTree.Node`.\n */\n@Component({\n selector: 'rt-hybrid-tree',\n templateUrl: '../tree/rt-tree.component.html',\n styleUrl: '../tree/rt-tree.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [\n NgTemplateOutlet,\n FormsModule,\n RtCheckboxComponent,\n RtIconComponent,\n RtRadioButtonComponent,\n RtTagComponent,\n RtTooltipDirective,\n BlockDirective,\n ElemDirective,\n ModDirective,\n ],\n host: {\n class: BEM_BLOCK,\n role: 'tree',\n '[attr.tabindex]': 'disabled() ? -1 : 0',\n '[attr.aria-disabled]': 'disabled() || null',\n '[attr.aria-label]': 'ariaLabel()',\n '[attr.aria-multiselectable]': \"mode() === 'multiple'\",\n '(keydown)': 'handleKeydown($event)',\n },\n})\nexport class RtHybridTreeComponent<TValue> extends RtTreeComponent<TValue> {\n /** Листья групп «один лист». */\n protected readonly singles: Signal<ReadonlySet<TValue>> = computed((): ReadonlySet<TValue> => rtHybridTreeSingleLeaves(this.nodes()));\n\n protected override rowState(row: IRtTree.Row<TValue>, value: ReadonlyArray<TValue>, cascade: boolean): IRtTree.RowState {\n const node: IRtTree.Node<TValue> = row.option;\n const single: boolean = this.singles().has(node.value) || rtHybridTreeIsSingleGroup(node);\n const count: number = rtHybridTreeChosenCount(node, value);\n return {\n mark: rtHybridTreeMark(node, value, cascade, this.singles()),\n radio: this.mode() === 'single' || (this.mode() === 'multiple' && single),\n count: count > 0 ? count : null,\n };\n }\n\n protected override selectAllMarkOf(rows: ReadonlyArray<IRtTree.Row<TValue>>, value: ReadonlyArray<TValue>): IRtTree.Mark {\n return rtHybridTreeSelectAllMark(rows, value, this.singles());\n }\n\n protected override selectAllChoice(rows: ReadonlyArray<IRtTree.Row<TValue>>, value: ReadonlyArray<TValue>): ReadonlyArray<TValue> {\n return rtHybridTreeSelectAll(rows, value, this.singles());\n }\n\n protected override nextChoice(node: IRtTree.Node<TValue>, additive: boolean): ReadonlyArray<TValue> {\n return rtHybridTreeChoose(this.nodes(), node, this.value(), {\n mode: this.mode(),\n cascade: this.cascade(),\n alone: this.exclusive() && !additive,\n });\n }\n}\n","<ng-container rtBlock=\"rt-tree\">\n <!--\n Строки выбираются мышью через (click); клавиатура — на самом дереве (handleKeydown), строки\n по отдельности фокус не берут: подсветка — третье состояние рядом с фокусом и выбором.\n -->\n <!-- eslint-disable @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n @if (isSelectAllShown()) {\n <div\n rtElem=\"select-all\"\n qa-dataid=\"tree-select-all\"\n role=\"presentation\"\n [rtMod]=\"{ disabled: disabled(), 'over-leaf': isSelectAllOverLeaf() }\"\n (click)=\"onSelectAll()\">\n @if (isSelectAllUnderMark()) {\n <span rtElem=\"toggle-space\"></span>\n }\n <rt-checkbox\n rtElem=\"check\"\n inert\n [disabled]=\"disabled()\"\n [indeterminate]=\"selectAllMark() === 'some'\"\n [ngModel]=\"selectAllMark() === 'all'\"\n [ngModelOptions]=\"{ standalone: true }\" />\n <span rtElem=\"label\">{{ t().uiSelectAll }}</span>\n </div>\n }\n\n @for (view of views(); track view.row.option.value) {\n <div\n rtElem=\"row\"\n qa-dataid=\"tree-row\"\n role=\"treeitem\"\n [attr.data-value]=\"view.row.option.value\"\n [attr.aria-level]=\"view.row.level + 1\"\n [attr.aria-expanded]=\"view.row.branch ? view.row.open : null\"\n [attr.aria-selected]=\"view.marked ? view.mark === 'all' : null\"\n [attr.aria-disabled]=\"view.disabled\"\n [style.--rt-tree-level]=\"view.row.level\"\n [rtMod]=\"{ highlighted: view.highlighted, disabled: view.disabled, chosen: view.mark === 'all' }\"\n (click)=\"onRowClick(view.row, $event)\">\n @if (view.row.branch) {\n <button\n rtElem=\"toggle\"\n qa-dataid=\"tree-row-toggle\"\n type=\"button\"\n tabindex=\"-1\"\n [attr.aria-label]=\"view.row.open ? t().uiCollapse : t().uiExpand\"\n (click)=\"onToggleClick($event, view.row.option.value)\">\n <rt-icon size=\"sm\" color=\"muted\" [name]=\"view.row.open ? 'chevron-down' : 'chevron-right'\" />\n </button>\n } @else {\n <span rtElem=\"toggle-space\"></span>\n }\n\n @if (view.marked && !view.radio) {\n <rt-checkbox\n rtElem=\"check\"\n qa-dataid=\"tree-row-checkbox\"\n inert\n [disabled]=\"view.disabled\"\n [indeterminate]=\"view.mark === 'some'\"\n [ngModel]=\"view.mark === 'all'\"\n [ngModelOptions]=\"{ standalone: true }\" />\n } @else if (view.marked) {\n <rt-radio-button\n rtElem=\"check\"\n qa-dataid=\"tree-row-radio\"\n inert\n [value]=\"view.row.option.value\"\n [checked]=\"view.mark === 'all'\"\n [disabled]=\"view.disabled\" />\n }\n\n <span rtElem=\"text\">\n <span rtElem=\"label\" qa-dataid=\"tree-row-label\" rtTooltipWhenTruncated [rtTooltip]=\"view.row.option.label\">\n @for (part of view.parts.label; track $index) {\n <span rtElem=\"part\" [rtMod]=\"{ match: part.matched }\">{{ part.text }}</span>\n }\n </span>\n @if (view.parts.description.length > 0) {\n <span\n rtElem=\"description\"\n qa-dataid=\"tree-row-description\"\n rtTooltipWhenTruncated\n [rtTooltip]=\"view.row.option.description\">\n @for (part of view.parts.description; track $index) {\n <span rtElem=\"part\" [rtMod]=\"{ match: part.matched }\">{{ part.text }}</span>\n }\n </span>\n }\n @if (view.row.option.badges?.length || metaTemplate()) {\n <span rtElem=\"meta\" qa-dataid=\"tree-row-meta\">\n @for (badge of view.row.option.badges; track $index) {\n <rt-tag size=\"sm\" [value]=\"badge.text\" [severity]=\"badge.severity ?? 'neutral'\" [highlight]=\"searchTerm()\" />\n }\n @if (metaTemplate(); as meta) {\n <ng-container [ngTemplateOutlet]=\"meta\" [ngTemplateOutletContext]=\"{ $implicit: view.row.option }\" />\n }\n </span>\n }\n </span>\n\n @if (view.count) {\n <span rtElem=\"count\" qa-dataid=\"tree-row-count\">{{ view.count }}</span>\n }\n\n @if (endTemplate(); as end) {\n <span rtElem=\"end\" qa-dataid=\"tree-row-end\">\n <ng-container [ngTemplateOutlet]=\"end\" [ngTemplateOutletContext]=\"{ $implicit: view.row.option }\" />\n </span>\n }\n </div>\n } @empty {\n <div rtElem=\"empty\" qa-dataid=\"tree-empty\" [rtMod]=\"{ 'with-icon': !!emptyIcon() }\">\n @if (emptyIcon(); as icon) {\n <rt-icon rtElem=\"empty-icon\" qa-dataid=\"tree-empty-icon\" size=\"lg\" [name]=\"icon\" />\n }\n {{ emptyText() }}\n </div>\n }\n</ng-container>\n"],"mappings":";;;;;;;;;;;;;AAeA,SAAS,WAAmB,MAAmB;CAC3C,OAAO,KAAK,YAAY,CAAA;AAC5B;AAEA,SAAS,SAAiB,MAAmB;CACzC,OAAO,WAAW,IAAI,CAAC,CAAC,SAAS;AACrC;AAGA,SAAgB,0BAAkC,MAAmB;CACjE,OAAO,CAAC,CAAC,KAAK,UAAU,SAAS,IAAI;AACzC;AAEA,SAAS,QAAgB,OAA8B,MAA2B;CAC9E,OAAO,MAAM,QAAQ,SAA0B,CAAC,KAAK,SAAS,IAAI,CAAC;AACvE;AAEA,SAAS,QAAgB,OAA8B,KAA0B;CAC7E,OAAO,CAAC,GAAG,OAAO,GAAG,IAAI,QAAQ,SAA0B,CAAC,MAAM,SAAS,IAAI,CAAC,CAAC;AACrF;AAGA,SAAS,aAAqB,OAAoB;CAC9C,OAAO,WAAW,KAAK,CAAC,CAAC,QAAQ,UAAkC,CAAC,SAAS,KAAK,CAAC;AACvF;AAGA,SAAS,UAAkB,MAAmB;CAC1C,OAAO,SAAS,IAAI,IAAI,WAAW,IAAI,CAAC,CAAC,SAAS,UAAgD,UAAU,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK;AACrI;AAEA,SAAS,eAAuB,OAAqC,MAAiB;CAClF,MAAM,SAAS,SAA6B;EACxC,IAAI,0BAA0B,IAAI,GAC9B,aAAa,IAAI,CAAC,CAAC,SAAS,SAA6B;GACrD,KAAK,IAAI,KAAK,KAAK;EACvB,CAAC;EAEL,eAAe,WAAW,IAAI,GAAG,IAAI;CACzC,CAAC;AACL;AAGA,SAAgB,yBAAiC,OAAmC;CAChF,MAAM,0BAAuB,IAAI,IAAG;CACpC,eAAe,OAAO,OAAO;CAC7B,OAAO;AACX;AAGA,SAAgB,oBAA4B,OAAqC,OAAa;CAC1F,KAAK,MAAM,QAAQ,OAAO;EACtB,IAAI,0BAA0B,IAAI,KAAK,aAAa,IAAI,CAAC,CAAC,MAAM,SAAiC,KAAK,UAAU,KAAK,GACjH,OAAO;EAEX,MAAM,QAA8B,oBAAoB,WAAW,IAAI,GAAG,KAAK;EAC/E,IAAI,OACA,OAAO;CAEf;CACA,OAAO;AACX;AAEA,SAAS,WAAmB,MAAqB,SAA4B;CACzE,OAAO,aAAa,IAAI,CAAC,CAAC,QAAQ,SAA0B,CAAC,QAAQ,IAAI,IAAI,CAAC;AAClF;AAMA,SAAgB,iBACZ,MACA,OACA,SACA,SAA4B;CAE5B,IAAI,0BAA0B,IAAI,GAC9B,OAAO,aAAa,IAAI,CAAC,CAAC,MAAM,SAAiC,MAAM,SAAS,KAAK,KAAK,CAAC,IAAI,QAAQ;CAE3G,IAAI,CAAC,WAAW,CAAC,SAAS,IAAI,GAC1B,OAAO,WAAW,MAAM,OAAO,OAAO;CAE1C,MAAM,OAA8B,WAAW,MAAM,OAAO;CAC5D,MAAM,QAAgB,KAAK,QAAQ,SAA0B,MAAM,SAAS,IAAI,CAAC,CAAC,CAAC;CACnF,IAAI,QAAQ,GACR,OAAO,UAAU,KAAK,SAAS,QAAQ;CAE3C,OAAO,UAAU,IAAI,CAAC,CAAC,MAAM,SAA0B,MAAM,SAAS,IAAI,CAAC,IAAI,SAAS;AAC5F;AAGA,SAAS,iBAAyB,OAAsB,MAAc,OAA4B;CAC9F,IAAI,MAAM,SAAS,IAAI,GACnB,OAAO,QAAQ,OAAO,CAAC,IAAI,CAAC;CAKhC,OAAO,QAAQ,QAAQ,OAHiB,aAAa,KAAK,CAAC,CACtD,QAAQ,UAAkC,CAAC,MAAM,YAAY,MAAM,UAAU,IAAI,CAAC,CAClF,KAAK,UAAiC,MAAM,KACnB,CAAQ,GAAG,CAAC,IAAI,CAAC;AACnD;AAGA,SAAS,kBAA0B,OAAsB,OAA4B;CACjF,MAAM,UAAiC,aAAa,KAAK,CAAC,CACrD,QAAQ,UAAkC,CAAC,MAAM,QAAQ,CAAC,CAC1D,KAAK,UAAiC,MAAM,KAAK;CACtD,IAAI,aAAa,KAAK,CAAC,CAAC,MAAM,UAAkC,MAAM,SAAS,MAAM,KAAK,CAAC,GACvF,OAAO,QAAQ,OAAO,OAAO;CAEjC,OAAO,QAAQ,SAAS,IAAI,QAAQ,OAAO,CAAC,QAAQ,EAAE,CAAC,IAAI;AAC/D;AAGA,SAAS,aAAqB,MAAqB,OAA8B,SAA4B;CACzG,MAAM,OAA8B,WAAW,MAAM,OAAO;CAC5D,IAAI,KAAK,WAAW,KAAK,iBAAiB,MAAM,OAAO,MAAM,OAAO,MAAM,OACtE,OAAO,QAAQ,OAAO,aAAa,IAAI,CAAC;CAE5C,OAAO,QAAQ,OAAO,IAAI;AAC9B;AAGA,SAAS,eACL,OACA,MACA,OACA,SACA,SAA4B;CAE5B,IAAI,0BAA0B,IAAI,GAC9B,OAAO,kBAAkB,MAAM,KAAK;CAExC,MAAM,QAA8B,QAAQ,IAAI,KAAK,KAAK,IAAI,oBAAoB,OAAO,KAAK,KAAK,IAAI;CACvG,IAAI,OACA,OAAO,iBAAiB,OAAO,KAAK,OAAO,KAAK;CAEpD,IAAI,WAAW,SAAS,IAAI,GACxB,OAAO,aAAa,MAAM,OAAO,OAAO;CAE5C,OAAO,aAAa,MAAM,OAAO,YAAY,OAAO;AACxD;AAOA,SAAgB,mBACZ,OACA,MACA,OACA,SAAoC;CAEpC,IAAI,KAAK,UACL,OAAO;CAEX,IAAI,QAAQ,SAAS,YACjB,OAAO,aAAa,MAAM,OAAO,QAAQ,MAAM,QAAQ,OAAO;CAElE,MAAM,UAA+B,yBAAyB,KAAK;CACnE,IAAI,CAAC,QAAQ,SAAS,QAAQ,IAAI,KAAK,KAAK,GACxC,OAAO,eAAe,OAAO,MAAM,OAAO,QAAQ,SAAS,OAAO;CAEtE,IAAI,QAAQ,SAAS,GACjB,OAAO,kBAAkB,OAAO,MAAM,OAAO,QAAQ,OAAO;CAEhE,OAAO,eAAe,OAAO,MAAM,gBAAgB,OAAO,MAAM,KAAK,GAAG,QAAQ,SAAS,OAAO;AACpG;AAGA,SAAgB,0BACZ,MACA,OACA,SAA4B;CAE5B,MAAM,OAA8B,oBAAoB,IAAI,CAAC,CAAC,QAAQ,SAA0B,CAAC,QAAQ,IAAI,IAAI,CAAC;CAClH,MAAM,QAAgB,KAAK,QAAQ,SAA0B,MAAM,SAAS,IAAI,CAAC,CAAC,CAAC;CACnF,IAAI,UAAU,GACV,OAAO;CAEX,OAAO,UAAU,KAAK,SAAS,QAAQ;AAC3C;AAMA,SAAgB,sBACZ,MACA,OACA,SAA4B;CAE5B,MAAM,SAAgC,oBAAoB,IAAI;CAC9D,IAAI,0BAA0B,MAAM,OAAO,OAAO,MAAM,OACpD,OAAO,QAAQ,OAAO,MAAM;CAEhC,OAAO,QACH,OACA,OAAO,QAAQ,SAA0B,CAAC,QAAQ,IAAI,IAAI,CAAC,CAAC;AAEpE;AAGA,SAAgB,wBAAgC,MAAqB,OAA4B;CAC7F,IAAI,CAAC,SAAS,IAAI,GACd,OAAO;CAEX,OAAO,UAAU,IAAI,CAAC,CAAC,QAAQ,SAA0B,MAAM,SAAS,IAAI,CAAC,CAAC,CAAC;AACnF;ACvMA,MAAM,YAAoB;AAsC1B,IAAa,wBAAb,MAAa,8BAAsC,gBAAuB;CA5B1E,cAAA;;EA8BuB,KAAA,UAAuC,eAAoC,yBAAyB,KAAK,MAAK,CAAE,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CA4BxI;CA1BsB,SAAS,KAA0B,OAA8B,SAAgB;EAChG,MAAM,OAA6B,IAAI;EACvC,MAAM,SAAkB,KAAK,QAAO,CAAE,CAAC,IAAI,KAAK,KAAK,KAAK,0BAA0B,IAAI;EACxF,MAAM,QAAgB,wBAAwB,MAAM,KAAK;EACzD,OAAO;GACH,MAAM,iBAAiB,MAAM,OAAO,SAAS,KAAK,QAAO,CAAE;GAC3D,OAAO,KAAK,KAAI,MAAO,YAAa,KAAK,KAAI,MAAO,cAAc;GAClE,OAAO,QAAQ,IAAI,QAAQ;EAC9B;CACL;CAEmB,gBAAgB,MAA0C,OAA4B;EACrG,OAAO,0BAA0B,MAAM,OAAO,KAAK,QAAO,CAAE;CAChE;CAEmB,gBAAgB,MAA0C,OAA4B;EACrG,OAAO,sBAAsB,MAAM,OAAO,KAAK,QAAO,CAAE;CAC5D;CAEmB,WAAW,MAA4B,UAAiB;EACvE,OAAO,mBAAmB,KAAK,MAAK,GAAI,MAAM,KAAK,MAAK,GAAI;GACxD,MAAM,KAAK,KAAI;GACf,SAAS,KAAK,QAAO;GACrB,OAAO,KAAK,UAAS,KAAM,CAAC;EAC/B,CAAA;CACL;CA7BS;EAAA,KAAA,OAAA,GAAA,mBAAA;GAAA,YAAA;GAAA,SAAA;GAAA,UAAA;GAAA,MAAA;GAAqB,MAAA;GAAA,QAAA,GAAA,gBAAA;EAAA,CAAA;CAAA;CAArB;EAAA,KAAA,OAAA,GAAA,qBAAA;GAAA,YAAA;GAAA,SAAA;GAAA,MAAA;GAAqB,cAAA;GAAA,UAAA;GAAA,MAAA;IAAA,YAAA,EAAA,QAAA,OAAA;IAAA,WAAA,EAAA,WAAA,wBAAA;IAAA,YAAA;KAAA,iBAAA;KAAA,sBAAA;KAAA,mBAAA;KAAA,6BAAA;IAAA;IAAA,gBAAA;GAAA;GAAA,iBAAA;GAAA,UAAA;GAAA,UC/DlC;GAyHA,QAAA,CAAA,49OAAA;GAAA,cAAA;IAAA;KAAA,MAAA;KAAA,MD/EQ;KAAgB,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;KAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MAChB;IAAW;IAAA;KAAA,MAAA;KAAA,MAAA,GAAA;KAAA,UAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MAAA,GAAA;KAAA,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;MAAA;KAAA;KAAA,SAAA,CAAA,eAAA;KAAA,UAAA,CAAA,SAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MACX;KAAmB,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;MAAA;KAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MACnB;KAAe,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;KAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MACf;KAAsB,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;KAAA;KAAA,SAAA,CAAA,eAAA,eAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MACtB;KAAc,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;KAAA;KAAA,SAAA,CAAA,QAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MACd;KAAkB,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;MAAA;KAAA;KAAA,UAAA,CAAA,WAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MAClB;KAAc,UAAA;KAAA,QAAA,CAAA,OAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MACd;KAAa,UAAA;KAAA,QAAA,CAAA,OAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MACb;KAAY,UAAA;IAAA;GAAA;GAAA,iBAAA,GAAA,wBAAA;GAAA,eAAA,GAAA,kBAAA;EAAA,CAAA;CAAA;;AAYP,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAqB,YAAA,CAAA;EA5BjC,MAAA;EACa,MAAA,CAAA;GAAA,UAAA;GAAgB,iBAGT,wBAAwB;GAAM,eAChC,kBAAkB;GAAI,SAC5B;IACL;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACH;GAAA,MACK;IACF,OAAO;IACP,MAAM;IACN,mBAAmB;IACnB,wBAAwB;IACxB,qBAAqB;IACrB,+BAA+B;IAC/B,aAAa;GAChB;GAAA,UAAA;GAAA,QAAA,CAAA,49OAAA;EAAA,CAAA"}
@@ -2,14 +2,14 @@ import { NgTemplateOutlet } from "@angular/common";
2
2
  import * as i0 from "@angular/core";
3
3
  import { ChangeDetectionStrategy, Component, Pipe, ViewEncapsulation, computed, contentChild, forwardRef, inject, input, signal, viewChild } from "@angular/core";
4
4
  import { BlockDirective, ElemDirective, ModDirective } from "@rt-tools/core";
5
- import { RT_KIT_LABELS, rtKitLabel } from "@rt-tools/ui-kit-v2/core";
5
+ import { RT_KIT_LABELS, RtOptionList, rtKitLabel, rtTreeBranchState, rtTreeFind, rtTreeIsTree, rtTreeLeaves, rtTreeOpenFor, rtTreeRows, rtTreeSideKey, rtTreeToggle } from "@rt-tools/ui-kit-v2/core";
6
6
  import { RtFormControlBase } from "@rt-tools/ui-kit-v2/form-control";
7
7
  import { RtIconButtonComponent } from "@rt-tools/ui-kit-v2/icon-button";
8
8
  import { RtIconComponent } from "@rt-tools/ui-kit-v2/icon";
9
9
  import { RtPopoverDirective } from "@rt-tools/ui-kit-v2/popover";
10
10
  import * as i1 from "@rt-tools/ui-kit-v2/radius";
11
11
  import { RtRadiusDirective } from "@rt-tools/ui-kit-v2/radius";
12
- import { RtSelectTriggerDirective, rtScrollActiveOptionIntoView, rtTreeBranchState, rtTreeFind, rtTreeIsTree, rtTreeLeaves, rtTreeOpenFor, rtTreeRows, rtTreeSideKey, rtTreeToggle } from "@rt-tools/ui-kit-v2/select";
12
+ import { RtSelectTriggerDirective } from "@rt-tools/ui-kit-v2/select";
13
13
  import { RtTagComponent } from "@rt-tools/ui-kit-v2/tag";
14
14
  var RtMultiselectLabelPipe = class RtMultiselectLabelPipe {
15
15
  transform(value, options) {
@@ -47,34 +47,36 @@ i0.ɵɵngDeclareClassMetadata({
47
47
  }]
48
48
  });
49
49
  const BEM_BLOCK = "rt-multiselect";
50
- const OPEN_KEYS = /* @__PURE__ */ new Set([
51
- "ArrowDown",
52
- "ArrowUp",
53
- "Enter",
54
- " "
55
- ]);
56
50
  let panelIdSeed = 0;
57
51
  function nextPanelId() {
58
52
  panelIdSeed += 1;
59
53
  return panelIdSeed;
60
54
  }
61
55
  var RtMultiselectComponent = class RtMultiselectComponent extends RtFormControlBase {
62
- #t_uiSelectValues;
63
56
  constructor() {
64
- super();
57
+ super(...arguments);
65
58
  this.#t_uiSelectValues = rtKitLabel("uiSelectValues");
66
59
  this.t = inject(RT_KIT_LABELS);
67
60
  this.panelId = `rt-multiselect-panel-${nextPanelId()}`;
68
61
  this.placeholderText = computed(() => this.placeholder() || this.#t_uiSelectValues(), ...ngDevMode ? [{ debugName: "placeholderText" }] : /* istanbul ignore next */ []);
69
62
  this.popover = viewChild.required(RtPopoverDirective, ...ngDevMode ? [{ debugName: "popover" }] : /* istanbul ignore next */ []);
70
63
  this.triggerEl = viewChild("triggerEl", ...ngDevMode ? [{ debugName: "triggerEl" }] : /* istanbul ignore next */ []);
71
- this.activeIndex = signal(-1, ...ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []);
72
64
  this.openBranches = signal(/* @__PURE__ */ new Set(), ...ngDevMode ? [{ debugName: "openBranches" }] : /* istanbul ignore next */ []);
73
65
  this.isTree = computed(() => rtTreeIsTree(this.options()), ...ngDevMode ? [{ debugName: "isTree" }] : /* istanbul ignore next */ []);
74
66
  this.rows = computed(() => rtTreeRows(this.options(), this.openBranches()), ...ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []);
75
67
  this.rowChecks = computed(() => this.rows().map((row) => this.#checkOf(row)), ...ngDevMode ? [{ debugName: "rowChecks" }] : /* istanbul ignore next */ []);
76
68
  this.isOpen = computed(() => this.popover().isOpen(), ...ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []);
77
- this.activeOptionId = computed(() => this.isOpen() && this.activeIndex() >= 0 ? `${this.panelId}-opt-${this.activeIndex()}` : null, ...ngDevMode ? [{ debugName: "activeOptionId" }] : /* istanbul ignore next */ []);
69
+ this.optionList = new RtOptionList({
70
+ panelId: () => this.panelId,
71
+ isOpen: () => this.isOpen(),
72
+ count: () => this.rows().length,
73
+ isDisabled: (index) => Boolean(this.rows()[index]?.option.disabled),
74
+ chooseKeys: /* @__PURE__ */ new Set(["Enter", " "]),
75
+ sideKeys: true,
76
+ edgeKeys: true
77
+ });
78
+ this.activeIndex = this.optionList.activeIndex;
79
+ this.activeOptionId = this.optionList.activeOptionId;
78
80
  this.triggerTpl = contentChild(RtSelectTriggerDirective, ...ngDevMode ? [{ debugName: "triggerTpl" }] : /* istanbul ignore next */ []);
79
81
  this.hasValue = computed(() => this.value().length > 0, ...ngDevMode ? [{ debugName: "hasValue" }] : /* istanbul ignore next */ []);
80
82
  this.visibleChips = computed(() => this.value().slice(0, this.maxChips()), ...ngDevMode ? [{ debugName: "visibleChips" }] : /* istanbul ignore next */ []);
@@ -92,8 +94,8 @@ var RtMultiselectComponent = class RtMultiselectComponent extends RtFormControlB
92
94
  this.iconLeft = input(null, ...ngDevMode ? [{ debugName: "iconLeft" }] : /* istanbul ignore next */ []);
93
95
  this.panelWidth = input("trigger-min", ...ngDevMode ? [{ debugName: "panelWidth" }] : /* istanbul ignore next */ []);
94
96
  this.panelMaxHeight = input(null, ...ngDevMode ? [{ debugName: "panelMaxHeight" }] : /* istanbul ignore next */ []);
95
- rtScrollActiveOptionIntoView(this.activeOptionId);
96
97
  }
98
+ #t_uiSelectValues;
97
99
  setDisabledState(disabled) {
98
100
  super.setDisabledState(disabled);
99
101
  if (disabled) this.popover().close();
@@ -139,38 +141,39 @@ var RtMultiselectComponent = class RtMultiselectComponent extends RtFormControlB
139
141
  onTriggerKeydown(event) {
140
142
  if (this.isDisabled()) return;
141
143
  if (!this.isOpen()) {
142
- if (OPEN_KEYS.has(event.key)) {
144
+ if (this.optionList.opensBy(event.key)) {
143
145
  event.preventDefault();
144
146
  this.popover().open();
145
- this.#moveActive(1);
147
+ this.optionList.move(1);
146
148
  }
147
149
  return;
148
150
  }
149
151
  this.#handleOpenKeydown(event);
150
152
  }
151
153
  #handleOpenKeydown(event) {
152
- switch (event.key) {
153
- case "ArrowDown":
154
- event.preventDefault();
155
- this.#moveActive(1);
154
+ const action = this.optionList.actionOf(event.key);
155
+ if (action === "none") return;
156
+ event.preventDefault();
157
+ switch (action) {
158
+ case "next":
159
+ this.optionList.move(1);
156
160
  break;
157
- case "ArrowUp":
158
- event.preventDefault();
159
- this.#moveActive(-1);
161
+ case "previous":
162
+ this.optionList.move(-1);
160
163
  break;
161
- case "Enter":
162
- case " ":
163
- event.preventDefault();
164
+ case "first":
165
+ this.optionList.first();
166
+ break;
167
+ case "last":
168
+ this.optionList.last();
169
+ break;
170
+ case "choose":
164
171
  this.#toggleActive();
165
172
  break;
166
- case "Escape":
167
- event.preventDefault();
173
+ case "close":
168
174
  this.popover().close();
169
175
  break;
170
- case "ArrowRight":
171
- case "ArrowLeft":
172
- event.preventDefault();
173
- this.#sideKey(event.key);
176
+ case "side": this.#sideKey(event.key);
174
177
  }
175
178
  }
176
179
  #checkOf(row) {
@@ -183,21 +186,6 @@ var RtMultiselectComponent = class RtMultiselectComponent extends RtFormControlB
183
186
  if (toggle !== null) this.openBranches.update((open) => rtTreeToggle(open, toggle));
184
187
  this.activeIndex.set(answer.index);
185
188
  }
186
- #moveActive(delta) {
187
- const opts = this.rows().map((row) => row.option);
188
- if (opts.length === 0) {
189
- this.activeIndex.set(-1);
190
- return;
191
- }
192
- let index = this.activeIndex();
193
- for (let step = 0; step < opts.length; step += 1) {
194
- index = (index + delta + opts.length) % opts.length;
195
- if (!opts[index].disabled) {
196
- this.activeIndex.set(index);
197
- return;
198
- }
199
- }
200
- }
201
189
  #toggleActive() {
202
190
  const opts = this.rows().map((row) => row.option);
203
191
  const index = this.activeIndex();
@@ -209,7 +197,7 @@ var RtMultiselectComponent = class RtMultiselectComponent extends RtFormControlB
209
197
  version: "22.2.2",
210
198
  ngImport: i0,
211
199
  type: RtMultiselectComponent,
212
- deps: [],
200
+ deps: null,
213
201
  target: i0.ɵɵFactoryTarget.Component
214
202
  });
215
203
  }
@@ -467,7 +455,6 @@ i0.ɵɵngDeclareClassMetadata({
467
455
  styles: ["@charset \"UTF-8\";@layer rt-kit.components{.rt-multiselect{--rt-multiselect-radius: var(--rt-input-radius);--rt-multiselect-caret-transition-duration: var(--rt-duration-fast);--rt-multiselect-own-outline-width: var(--rt-border-width-medium);position:relative;display:block;width:100%}.rt-multiselect--borderless .rt-multiselect__trigger{border-color:transparent}.rt-multiselect__trigger{display:flex;width:100%;min-height:var(--rt-input-height);box-sizing:border-box;align-items:center;padding:var(--rt-input-padding-y) var(--rt-input-padding-x);border:var(--rt-input-border-width) solid var(--rt-input-color-border);border-radius:var(--rt-multiselect-radius);background-color:var(--rt-input-color-bg);color:var(--rt-input-color-text);cursor:pointer;font-family:inherit;font-size:var(--rt-input-font-size);gap:var(--rt-space-sm);line-height:var(--rt-input-line-height);text-align:start;transition:var(--rt-input-transition)}.rt-multiselect__trigger:focus{border-color:var(--rt-input-color-border-focus);box-shadow:var(--rt-input-focus-shadow);outline:2px solid transparent;outline-offset:var(--rt-input-focus-outline-offset)}.rt-multiselect__trigger:disabled{cursor:not-allowed}.rt-multiselect__trigger--own{width:auto;min-height:0;padding:0;border-color:transparent;background-color:transparent}.rt-multiselect__trigger--own:focus{border-color:transparent;box-shadow:none}.rt-multiselect__trigger--own:focus-visible{outline:var(--rt-multiselect-own-outline-width) solid var(--rt-input-color-border-focus);outline-offset:var(--rt-input-focus-outline-offset)}.rt-multiselect--open .rt-multiselect__trigger{border-color:var(--rt-input-color-border-focus)}.rt-multiselect--open .rt-multiselect__trigger--own{border-color:transparent}.rt-multiselect--invalid .rt-multiselect__trigger{border-color:var(--rt-input-color-border-error)}.rt-multiselect--invalid .rt-multiselect__trigger:focus{box-shadow:var(--rt-input-focus-shadow-error)}.rt-multiselect--invalid .rt-multiselect__trigger--own{border-color:transparent}.rt-multiselect--invalid .rt-multiselect__trigger--own:focus{box-shadow:none}.rt-multiselect--disabled{opacity:var(--rt-input-disabled-opacity)}.rt-multiselect--readonly .rt-multiselect__trigger{display:none}.rt-multiselect__readonly{display:inline-flex;min-height:var(--rt-input-height);align-items:center;color:var(--rt-field-value-color);font-size:var(--rt-field-value-font-size)}.rt-multiselect__readonly--empty{color:var(--rt-field-value-empty-color)}.rt-multiselect--size--sm .rt-multiselect__trigger{min-height:var(--rt-input-height-sm)}.rt-multiselect--size--lg .rt-multiselect__trigger{min-height:var(--rt-input-height-lg)}.rt-multiselect__placeholder{overflow:hidden;min-width:0;flex:1 1 auto;color:var(--rt-input-color-text-placeholder);text-overflow:ellipsis;white-space:nowrap}.rt-multiselect__chips{display:flex;min-width:0;flex:1 1 auto;flex-wrap:wrap;gap:var(--rt-space-2xs)}.rt-multiselect__chip{display:inline-flex;align-items:center}.rt-multiselect__chip-more{display:inline-flex;align-items:center;padding:0 var(--rt-space-xs);color:var(--rt-input-color-text-placeholder);font-size:var(--rt-input-font-size)}.rt-multiselect__icon-left,.rt-multiselect__clear{flex-shrink:0}.rt-multiselect__chevron{flex-shrink:0;transition:transform var(--rt-multiselect-caret-transition-duration) ease}.rt-multiselect--open .rt-multiselect__chevron{transform:rotate(180deg)}.rt-multiselect__panel{--rt-multiselect-panel-shadow: var(--rt-shadow-md);--rt-multiselect-tree-indent: var(--rt-space-lg);--rt-multiselect-panel-max-height: var(--rt-input-panel-max-height);width:100%;max-height:var(--rt-multiselect-panel-max-height);box-sizing:border-box;border:var(--rt-input-border-width) solid var(--rt-color-border-subtle);border-radius:var(--rt-input-radius);background-color:var(--rt-color-bg-surface);box-shadow:var(--rt-multiselect-panel-shadow);overflow-y:auto}.rt-multiselect__list{padding:var(--rt-space-2xs) 0;margin:0;list-style:none}.rt-multiselect__option{--rt-multiselect-tree-level: 0;display:flex;align-items:center;padding:var(--rt-space-sm) var(--rt-space-md);color:var(--rt-color-text-primary);cursor:pointer;font-size:var(--rt-input-font-size);gap:var(--rt-space-sm);padding-inline-start:calc(var(--rt-space-md) + var(--rt-multiselect-tree-indent) * var(--rt-multiselect-tree-level))}.rt-multiselect__option:hover,.rt-multiselect__option--active{background-color:var(--rt-color-bg-hover)}.rt-multiselect__option--selected{background-color:var(--rt-color-action-primary-subtle);font-weight:var(--rt-font-weight-semibold)}.rt-multiselect__option--disabled{cursor:not-allowed;opacity:var(--rt-input-disabled-opacity)}.rt-multiselect__option--disabled:hover{background-color:transparent}.rt-multiselect__toggle,.rt-multiselect__toggle-space{display:inline-flex;flex-shrink:0;block-size:var(--rt-input-icon-size);inline-size:var(--rt-input-icon-size)}.rt-multiselect__toggle{align-items:center;justify-content:center;padding:0;border:0;background:transparent;cursor:pointer}.rt-multiselect__checkbox{flex-shrink:0;margin:0;pointer-events:none}.rt-multiselect__option-label{min-width:0;flex:1 1 auto}.rt-multiselect__empty{padding:var(--rt-space-sm) var(--rt-space-md);color:var(--rt-input-color-text-placeholder);font-style:italic}.rt-multiselect[data-rt-radius=none]{--rt-multiselect-radius: var(--rt-radius-none)}.rt-multiselect[data-rt-radius=xs]{--rt-multiselect-radius: var(--rt-radius-xs)}.rt-multiselect[data-rt-radius=sm]{--rt-multiselect-radius: var(--rt-radius-sm)}.rt-multiselect[data-rt-radius=ms]{--rt-multiselect-radius: var(--rt-radius-ms)}.rt-multiselect[data-rt-radius=md]{--rt-multiselect-radius: var(--rt-radius-md)}.rt-multiselect[data-rt-radius=lg]{--rt-multiselect-radius: var(--rt-radius-lg)}.rt-multiselect[data-rt-radius=xl]{--rt-multiselect-radius: var(--rt-radius-xl)}.rt-multiselect[data-rt-radius=\"2xl\"]{--rt-multiselect-radius: var(--rt-radius-2xl)}.rt-multiselect[data-rt-radius=full]{--rt-multiselect-radius: var(--rt-radius-full)}}\n"]
468
456
  }]
469
457
  }],
470
- ctorParameters: () => [],
471
458
  propDecorators: {
472
459
  popover: [{
473
460
  type: i0.ViewChild,
@@ -1 +1 @@
1
- {"version":3,"file":"rt-tools-ui-kit-v2-multiselect.mjs","names":[],"sources":["../../../projects/ui-kit-v2/src/lib/components/multiselect/rt-multiselect-label.pipe.ts","../../../projects/ui-kit-v2/src/lib/components/multiselect/rt-multiselect.component.ts","../../../projects/ui-kit-v2/src/lib/components/multiselect/rt-multiselect.component.html"],"sourcesContent":["import { Pipe, PipeTransform } from '@angular/core';\n\nimport { rtTreeFind } from '@rt-tools/ui-kit-v2/select';\nimport { IRtSelect } from '@rt-tools/ui-kit-v2/select';\n\n/** Лейбл опции по её value для отображения чипа выбранного значения — на любой глубине дерева. */\n@Pipe({ name: 'rtMultiselectLabel' })\nexport class RtMultiselectLabelPipe implements PipeTransform {\n public transform<TValue>(value: TValue, options: ReadonlyArray<IRtSelect.Option<TValue>>): string {\n return rtTreeFind(options, value)?.label ?? String(value);\n }\n}\n","import { NgTemplateOutlet } from '@angular/common';\nimport {\n computed,\n contentChild,\n forwardRef,\n inject,\n input,\n signal,\n viewChild,\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n InputSignal,\n Signal,\n ViewEncapsulation,\n WritableSignal,\n} from '@angular/core';\n\nimport { BlockDirective, ElemDirective, ModDirective } from '@rt-tools/core';\n\nimport { RT_KIT_LABELS, TRtKitLabelMap, rtKitLabel } from '@rt-tools/ui-kit-v2/core';\nimport { RtFormControlBase } from '@rt-tools/ui-kit-v2/form-control';\nimport { RtIconButtonComponent } from '@rt-tools/ui-kit-v2/icon-button';\nimport { RtIconComponent } from '@rt-tools/ui-kit-v2/icon';\nimport { IRtIcon } from '@rt-tools/ui-kit-v2/core';\nimport { RtPopoverDirective } from '@rt-tools/ui-kit-v2/popover';\nimport { IRtPopover } from '@rt-tools/ui-kit-v2/popover';\nimport { RtRadiusDirective } from '@rt-tools/ui-kit-v2/radius';\nimport { rtScrollActiveOptionIntoView } from '@rt-tools/ui-kit-v2/select';\nimport {\n rtTreeBranchState,\n rtTreeFind,\n rtTreeIsTree,\n rtTreeLeaves,\n rtTreeOpenFor,\n rtTreeRows,\n rtTreeSideKey,\n rtTreeToggle,\n} from '@rt-tools/ui-kit-v2/select';\nimport { RtSelectTriggerDirective } from '@rt-tools/ui-kit-v2/select';\nimport { IRtSelect } from '@rt-tools/ui-kit-v2/select';\nimport { RtTagComponent } from '@rt-tools/ui-kit-v2/tag';\nimport { RtMultiselectLabelPipe } from './rt-multiselect-label.pipe';\n\nconst BEM_BLOCK: string = 'rt-multiselect';\n\n/** Клавиши, открывающие закрытый dropdown с фокуса на trigger'е. */\nconst OPEN_KEYS: ReadonlySet<string> = new Set<string>(['ArrowDown', 'ArrowUp', 'Enter', ' ']);\n\nlet panelIdSeed: number = 0;\n\nfunction nextPanelId(): number {\n panelIdSeed += 1;\n return panelIdSeed;\n}\n\n/**\n * Multi-select с chip-rendering выбранных значений в trigger.\n *\n * Panel рендерится через CDK Overlay (`[rtPopover]`) — не обрезается overflow,\n * авто-flip. Панель НЕ закрывается после выбора (multi vs single). CVA через\n * общий `RtFormControlBase`. Reuses `IRtSelect.Option<TValue>` (type-import).\n * Чипы закрываются поштучно; крестик (`clearable`) слева от шеврона очищает весь\n * выбор.\n *\n * Keyboard-nav (activedescendant): фокус на trigger'е, ArrowUp/Down — highlight,\n * Enter — toggle опции (panel остаётся открытой), Escape — закрыть. Home/End —\n * deferred.\n */\n@Component({\n selector: 'rt-multiselect',\n templateUrl: './rt-multiselect.component.html',\n styleUrl: './rt-multiselect.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 RtTagComponent,\n BlockDirective,\n ElemDirective,\n ModDirective,\n\n // pipes\n RtMultiselectLabelPipe,\n ],\n providers: [\n // Алиас базового токена — для contentChild(RtFormControlBase) в rt-field.\n { provide: RtFormControlBase, useExisting: forwardRef(() => RtMultiselectComponent) },\n ],\n hostDirectives: [{ directive: RtRadiusDirective, inputs: ['radius'] }],\n host: {\n class: BEM_BLOCK,\n '[class.rt-multiselect--open]': 'isOpen()',\n '[class.rt-multiselect--disabled]': 'isDisabled()',\n '[class.rt-multiselect--readonly]': 'isReadonly()',\n '[class.rt-multiselect--invalid]': 'isInvalid()',\n '[class.rt-multiselect--borderless]': '!bordered()',\n '[class.rt-multiselect--size--sm]': \"size() === 'sm'\",\n '[class.rt-multiselect--size--lg]': \"size() === 'lg'\",\n },\n})\nexport class RtMultiselectComponent<TValue> extends RtFormControlBase<ReadonlyArray<TValue>> {\n readonly #t_uiSelectValues: Signal<string> = rtKitLabel('uiSelectValues');\n\n protected readonly t: Signal<TRtKitLabelMap> = inject(RT_KIT_LABELS);\n\n protected readonly panelId: string = `rt-multiselect-panel-${nextPanelId()}`;\n\n protected readonly placeholderText: Signal<string> = computed((): string => this.placeholder() || this.#t_uiSelectValues());\n\n protected readonly popover: Signal<RtPopoverDirective> = viewChild.required(RtPopoverDirective);\n\n protected readonly triggerEl: Signal<ElementRef<HTMLButtonElement> | undefined> = viewChild<ElementRef<HTMLButtonElement>>('triggerEl');\n\n protected readonly activeIndex: WritableSignal<number> = signal<number>(-1);\n /** Раскрытые ветки дерева. Живут, пока открыта панель: при открытии раскрыты ветки над выбранным. */\n protected readonly openBranches: WritableSignal<ReadonlySet<TValue>> = signal<ReadonlySet<TValue>>(new Set<TValue>());\n\n /** Дерево ли список: тогда у строк есть место под стрелку и отступ по уровню. */\n protected readonly isTree: Signal<boolean> = computed((): boolean => rtTreeIsTree(this.options()));\n\n /** Видимые строки: плоский список — как прежде, дерево — по раскрытым веткам. */\n protected readonly rows: Signal<ReadonlyArray<IRtSelect.Row<TValue>>> = computed((): ReadonlyArray<IRtSelect.Row<TValue>> =>\n rtTreeRows(this.options(), this.openBranches())\n );\n\n /** Флажок каждой строки: у листа — выбран ли он, у ветки — выведен из её включённых листьев. */\n protected readonly rowChecks: Signal<ReadonlyArray<IRtSelect.TBranchState>> = computed((): ReadonlyArray<IRtSelect.TBranchState> =>\n this.rows().map((row: IRtSelect.Row<TValue>): IRtSelect.TBranchState => this.#checkOf(row))\n );\n\n protected readonly isOpen: Signal<boolean> = computed((): boolean => this.popover().isOpen());\n\n /** Идентификатор подсвеченной опции — тот же, что уходит в `aria-activedescendant`. */\n protected readonly activeOptionId: Signal<string | null> = computed((): string | null =>\n this.isOpen() && this.activeIndex() >= 0 ? `${this.panelId}-opt-${this.activeIndex()}` : null\n );\n\n /**\n * Своя разметка указателя, если потребитель её объявил. Маркер тот же, что у выбора одного\n * значения: семьи отличаются только разметкой внутри кнопки.\n */\n protected readonly triggerTpl: Signal<RtSelectTriggerDirective<ReadonlyArray<TValue>> | undefined> =\n contentChild(RtSelectTriggerDirective);\n\n protected readonly hasValue: Signal<boolean> = computed((): boolean => this.value().length > 0);\n\n protected readonly visibleChips: Signal<ReadonlyArray<TValue>> = computed((): ReadonlyArray<TValue> =>\n this.value().slice(0, this.maxChips())\n );\n\n protected readonly extraChipsCount: Signal<number> = computed((): number => Math.max(0, this.value().length - this.maxChips()));\n\n /** Три значения, которые кит отдаёт своей разметке указателя, и не больше. */\n protected readonly triggerState: Signal<IRtSelect.TriggerState<ReadonlyArray<TValue>>> = computed(\n (): IRtSelect.TriggerState<ReadonlyArray<TValue>> => ({\n isOpen: this.isOpen(),\n value: this.value(),\n label: this.displayText(),\n isDisabled: this.isDisabled(),\n })\n );\n\n public readonly displayText: Signal<string> = computed((): string =>\n this.value()\n .map((v: TValue): string => this.labelOf(v))\n .join(', ')\n );\n\n public readonly options: InputSignal<ReadonlyArray<IRtSelect.Option<TValue>>> = input<ReadonlyArray<IRtSelect.Option<TValue>>>([]);\n\n /** Пусто — берётся переведённая подпись по умолчанию */\n public readonly placeholder: InputSignal<string> = input<string>('');\n public readonly maxChips: InputSignal<number> = input<number>(3);\n\n /** Иконка слева в указателе — та же, что у `rt-select`: перед подсказкой или чипами. */\n public readonly iconLeft: InputSignal<IRtIcon.Name | null> = input<IRtIcon.Name | null>(null);\n\n /**\n * Чем мерится панель. По умолчанию она не уже кнопки и дальше растёт по содержимому:\n * со своим указателем кнопка бывает узкой, и панель по её ширине давила бы содержимое.\n * `trigger` возвращает прежнее — ровно по кнопке, `auto` пускает панель по содержимому\n * целиком. Предел высоты назначается входом `panelMaxHeight`.\n */\n public readonly panelWidth: InputSignal<IRtPopover.Width> = input<IRtPopover.Width>('trigger-min');\n\n /**\n * Предел высоты панели — длина как в стилях, например `20rem`. Пусто — предел кита\n * `--rt-input-panel-max-height`, и длинный список прокручивается внутри панели: без предела\n * он уходил за нижний край экрана, и пункты за краем были недостижимы. `none` снимает предел —\n * для короткого списка, который должен открываться целиком, без прокрутки. Значение ставится\n * на саму панель: она рисуется в наложении, вне поддерева блока.\n */\n public readonly panelMaxHeight: InputSignal<string | null> = input<string | null>(null);\n\n constructor() {\n super();\n // Подсветка, сдвинутая клавишами, не уходит за край панели с пределом высоты.\n rtScrollActiveOptionIntoView(this.activeOptionId);\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(): ReadonlyArray<TValue> {\n return [];\n }\n\n protected focusAfterClear(): void {\n this.triggerEl()?.nativeElement.focus();\n }\n\n protected labelOf(value: TValue): string {\n return rtTreeFind(this.options(), value)?.label ?? String(value);\n }\n\n protected isSelected(value: TValue): boolean {\n return this.value().includes(value);\n }\n\n protected onOpened(): void {\n this.openBranches.set(rtTreeOpenFor(this.options(), this.value()));\n }\n\n protected toggleBranch(event: Event, value: TValue): void {\n event.stopPropagation();\n this.openBranches.update((open: ReadonlySet<TValue>): ReadonlySet<TValue> => rtTreeToggle(open, value));\n }\n\n protected onClosed(): void {\n this.activeIndex.set(-1);\n this.markTouched();\n }\n\n /**\n * Лист переключается сам. Ветка выбирает все свои включённые листья или снимает их, когда\n * выбраны все; своё значение ветки в выбор не пишется.\n */\n protected toggleOption(opt: IRtSelect.Option<TValue>): void {\n if (opt.disabled || this.isDisabled()) {\n return;\n }\n const leaves: ReadonlyArray<TValue> = rtTreeLeaves(opt);\n const current: ReadonlyArray<TValue> = this.value();\n const clearing: boolean = rtTreeBranchState(opt, current) === 'all';\n const next: ReadonlyArray<TValue> = clearing\n ? current.filter((v: TValue): boolean => !leaves.includes(v))\n : [...current, ...leaves.filter((leaf: TValue): boolean => !current.includes(leaf))];\n this.value.set(next);\n this.emitChange([...next]);\n }\n\n protected removeChip(value: TValue, event: MouseEvent): void {\n event.stopPropagation();\n if (this.isDisabled()) {\n return;\n }\n const next: ReadonlyArray<TValue> = this.value().filter((v: TValue): boolean => v !== value);\n this.value.set(next);\n this.emitChange([...next]);\n }\n\n protected onTriggerKeydown(event: KeyboardEvent): void {\n if (this.isDisabled()) {\n return;\n }\n if (!this.isOpen()) {\n if (OPEN_KEYS.has(event.key)) {\n event.preventDefault();\n this.popover().open();\n this.#moveActive(1);\n }\n return;\n }\n this.#handleOpenKeydown(event);\n }\n\n #handleOpenKeydown(event: KeyboardEvent): void {\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n this.#moveActive(1);\n break;\n case 'ArrowUp':\n event.preventDefault();\n this.#moveActive(-1);\n break;\n case 'Enter':\n case ' ':\n event.preventDefault();\n this.#toggleActive();\n break;\n case 'Escape':\n event.preventDefault();\n this.popover().close();\n break;\n case 'ArrowRight':\n case 'ArrowLeft':\n event.preventDefault();\n this.#sideKey(event.key);\n break;\n default:\n break;\n }\n }\n\n #checkOf(row: IRtSelect.Row<TValue>): IRtSelect.TBranchState {\n if (row.branch) {\n return rtTreeBranchState(row.option, this.value());\n }\n return this.value().includes(row.option.value) ? 'all' : 'none';\n }\n\n #sideKey(key: string): void {\n const answer: IRtSelect.SideKeyAnswer<TValue> = rtTreeSideKey(this.rows(), this.activeIndex(), key);\n const toggle: TValue | null = answer.toggle;\n if (toggle !== null) {\n this.openBranches.update((open: ReadonlySet<TValue>): ReadonlySet<TValue> => rtTreeToggle(open, toggle));\n }\n this.activeIndex.set(answer.index);\n }\n\n #moveActive(delta: number): void {\n const opts: ReadonlyArray<IRtSelect.Option<TValue>> = this.rows().map(\n (row: IRtSelect.Row<TValue>): IRtSelect.Option<TValue> => row.option\n );\n if (opts.length === 0) {\n this.activeIndex.set(-1);\n return;\n }\n let index: number = this.activeIndex();\n for (let step: number = 0; step < opts.length; step += 1) {\n index = (index + delta + opts.length) % opts.length;\n if (!opts[index].disabled) {\n this.activeIndex.set(index);\n return;\n }\n }\n }\n\n #toggleActive(): void {\n const opts: ReadonlyArray<IRtSelect.Option<TValue>> = this.rows().map(\n (row: IRtSelect.Row<TValue>): IRtSelect.Option<TValue> => row.option\n );\n const index: number = this.activeIndex();\n if (index >= 0 && index < opts.length) {\n this.toggleOption(opts[index]);\n }\n }\n}\n","<ng-container rtBlock=\"rt-multiselect\">\n @if (isReadonly()) {\n <span rtElem=\"readonly\" qa-dataid=\"multiselect-readonly\" [rtMod]=\"{ empty: !displayText() }\">{{ displayText() || '—' }}</span>\n }\n <button\n #triggerEl\n rtElem=\"trigger\"\n qa-dataid=\"multiselect-trigger\"\n type=\"button\"\n [rtPopoverWidth]=\"panelWidth()\"\n [rtMod]=\"{ own: !!triggerTpl() }\"\n [rtPopover]=\"panelTpl\"\n [rtPopoverDisabled]=\"isDisabled()\"\n [attr.id]=\"effectiveControlId()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-invalid]=\"isInvalid() || null\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-activedescendant]=\"activeOptionId()\"\n [disabled]=\"isDisabled()\"\n (keydown)=\"onTriggerKeydown($event)\"\n (rtPopoverOpened)=\"onOpened()\"\n (rtPopoverClosed)=\"onClosed()\">\n <!--\n Разметка указателя: своя, если потребитель её объявил, иначе зашитая китом. Она встаёт\n внутрь кнопки, а не вместо неё: поведение — открытие панели, приметы доступности,\n отключённость, клавиши и закрытие — остаётся на кнопке кита.\n -->\n @if (triggerTpl(); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl.templateRef\" [ngTemplateOutletContext]=\"{ $implicit: triggerState() }\" />\n } @else {\n @if (iconLeft(); as left) {\n <rt-icon rtElem=\"icon-left\" qa-dataid=\"multiselect-icon-left\" size=\"sm\" color=\"muted\" [name]=\"left\" />\n }\n @if (value().length === 0) {\n <span rtElem=\"placeholder\" qa-dataid=\"multiselect-placeholder\">{{ placeholderText() }}</span>\n } @else {\n <span rtElem=\"chips\">\n @for (val of visibleChips(); track val) {\n <span rtElem=\"chip\" qa-dataid=\"multiselect-chip\" [attr.data-value]=\"val\">\n <rt-tag\n severity=\"info\"\n [value]=\"val | rtMultiselectLabel: options()\"\n [closable]=\"true\"\n (closed)=\"removeChip(val, $event)\" />\n </span>\n }\n @if (extraChipsCount() > 0) {\n <span rtElem=\"chip-more\" qa-dataid=\"multiselect-chip-more\">+{{ extraChipsCount() }}</span>\n }\n </span>\n }\n @if (showClearButton()) {\n <rt-icon-button\n rtElem=\"clear\"\n qa-dataid=\"multiselect-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 <rt-icon rtElem=\"chevron\" name=\"chevron-down\" size=\"sm\" color=\"muted\" />\n }\n </button>\n\n <ng-template #panelTpl>\n <div\n rtElem=\"panel\"\n qa-dataid=\"multiselect-panel\"\n role=\"listbox\"\n [style.--rt-multiselect-panel-max-height]=\"panelMaxHeight()\"\n [attr.aria-label]=\"ariaLabel()\">\n <!--\n Опции выбираются мышью через (click); keyboard-nav — на trigger'е\n (activedescendant), опции не focusable индивидуально.\n -->\n <!-- eslint-disable @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <ul rtElem=\"list\">\n @for (row of rows(); track row.option.value; let i = $index) {\n <li\n rtElem=\"option\"\n qa-dataid=\"multiselect-option\"\n role=\"option\"\n [id]=\"panelId + '-opt-' + i\"\n [attr.data-value]=\"row.option.value\"\n [attr.aria-level]=\"isTree() ? row.level + 1 : null\"\n [style.--rt-multiselect-tree-level]=\"row.level\"\n [rtMod]=\"{\n selected: rowChecks()[i] === 'all',\n active: i === activeIndex(),\n disabled: !!row.option.disabled,\n }\"\n [attr.aria-selected]=\"rowChecks()[i] === 'all'\"\n [attr.aria-disabled]=\"!!row.option.disabled\"\n (click)=\"toggleOption(row.option)\">\n @if (isTree()) {\n @if (row.branch) {\n <button\n rtElem=\"toggle\"\n qa-dataid=\"multiselect-option-toggle\"\n type=\"button\"\n tabindex=\"-1\"\n [attr.aria-label]=\"row.open ? t().uiCollapse : t().uiExpand\"\n [attr.aria-expanded]=\"row.open\"\n (click)=\"toggleBranch($event, row.option.value)\">\n <rt-icon size=\"sm\" color=\"muted\" [name]=\"row.open ? 'chevron-down' : 'chevron-right'\" />\n </button>\n } @else {\n <span rtElem=\"toggle-space\"></span>\n }\n }\n <input\n rtElem=\"checkbox\"\n qa-dataid=\"multiselect-option-checkbox\"\n type=\"checkbox\"\n tabindex=\"-1\"\n [checked]=\"rowChecks()[i] === 'all'\"\n [indeterminate]=\"rowChecks()[i] === 'some'\" />\n <span rtElem=\"option-label\" qa-dataid=\"multiselect-option-label\">{{ row.option.label }}</span>\n </li>\n } @empty {\n <li rtElem=\"empty\" qa-dataid=\"multiselect-empty\">{{ t().uiNoOptions }}</li>\n }\n </ul>\n </div>\n </ng-template>\n</ng-container>\n"],"mappings":"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"}
1
+ {"version":3,"file":"rt-tools-ui-kit-v2-multiselect.mjs","names":[],"sources":["../../../projects/ui-kit-v2/src/lib/components/multiselect/rt-multiselect-label.pipe.ts","../../../projects/ui-kit-v2/src/lib/components/multiselect/rt-multiselect.component.ts","../../../projects/ui-kit-v2/src/lib/components/multiselect/rt-multiselect.component.html"],"sourcesContent":["import { Pipe, PipeTransform } from '@angular/core';\n\nimport { rtTreeFind } from '@rt-tools/ui-kit-v2/core';\nimport { IRtSelect } from '@rt-tools/ui-kit-v2/select';\n\n/** Лейбл опции по её value для отображения чипа выбранного значения — на любой глубине дерева. */\n@Pipe({ name: 'rtMultiselectLabel' })\nexport class RtMultiselectLabelPipe implements PipeTransform {\n public transform<TValue>(value: TValue, options: ReadonlyArray<IRtSelect.Option<TValue>>): string {\n return rtTreeFind(options, value)?.label ?? String(value);\n }\n}\n","import { NgTemplateOutlet } from '@angular/common';\nimport {\n computed,\n contentChild,\n forwardRef,\n inject,\n input,\n signal,\n viewChild,\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n InputSignal,\n Signal,\n ViewEncapsulation,\n WritableSignal,\n} from '@angular/core';\n\nimport { BlockDirective, ElemDirective, ModDirective } from '@rt-tools/core';\n\nimport { RT_KIT_LABELS, TRtKitLabelMap, rtKitLabel } from '@rt-tools/ui-kit-v2/core';\nimport { RtFormControlBase } from '@rt-tools/ui-kit-v2/form-control';\nimport { RtIconButtonComponent } from '@rt-tools/ui-kit-v2/icon-button';\nimport { RtIconComponent } from '@rt-tools/ui-kit-v2/icon';\nimport { IRtIcon } from '@rt-tools/ui-kit-v2/core';\nimport { RtPopoverDirective } from '@rt-tools/ui-kit-v2/popover';\nimport { IRtPopover } 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 {\n rtTreeBranchState,\n rtTreeFind,\n rtTreeIsTree,\n rtTreeLeaves,\n rtTreeOpenFor,\n rtTreeRows,\n rtTreeSideKey,\n rtTreeToggle,\n} from '@rt-tools/ui-kit-v2/core';\nimport { RtSelectTriggerDirective } from '@rt-tools/ui-kit-v2/select';\nimport { IRtSelect } from '@rt-tools/ui-kit-v2/select';\nimport { RtTagComponent } from '@rt-tools/ui-kit-v2/tag';\nimport { RtMultiselectLabelPipe } from './rt-multiselect-label.pipe';\n\nconst BEM_BLOCK: string = 'rt-multiselect';\n\n/** Клавиши, открывающие закрытый dropdown с фокуса на trigger'е. */\n\nlet panelIdSeed: number = 0;\n\nfunction nextPanelId(): number {\n panelIdSeed += 1;\n return panelIdSeed;\n}\n\n/**\n * Multi-select с chip-rendering выбранных значений в trigger.\n *\n * Panel рендерится через CDK Overlay (`[rtPopover]`) — не обрезается overflow,\n * авто-flip. Панель НЕ закрывается после выбора (multi vs single). CVA через\n * общий `RtFormControlBase`. Reuses `IRtSelect.Option<TValue>` (type-import).\n * Чипы закрываются поштучно; крестик (`clearable`) слева от шеврона очищает весь\n * выбор.\n *\n * Keyboard-nav (activedescendant): фокус на trigger'е, ArrowUp/Down — highlight,\n * Enter — toggle опции (panel остаётся открытой), Escape — закрыть. Home/End —\n * deferred.\n */\n@Component({\n selector: 'rt-multiselect',\n templateUrl: './rt-multiselect.component.html',\n styleUrl: './rt-multiselect.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 RtTagComponent,\n BlockDirective,\n ElemDirective,\n ModDirective,\n\n // pipes\n RtMultiselectLabelPipe,\n ],\n providers: [\n // Алиас базового токена — для contentChild(RtFormControlBase) в rt-field.\n { provide: RtFormControlBase, useExisting: forwardRef(() => RtMultiselectComponent) },\n ],\n hostDirectives: [{ directive: RtRadiusDirective, inputs: ['radius'] }],\n host: {\n class: BEM_BLOCK,\n '[class.rt-multiselect--open]': 'isOpen()',\n '[class.rt-multiselect--disabled]': 'isDisabled()',\n '[class.rt-multiselect--readonly]': 'isReadonly()',\n '[class.rt-multiselect--invalid]': 'isInvalid()',\n '[class.rt-multiselect--borderless]': '!bordered()',\n '[class.rt-multiselect--size--sm]': \"size() === 'sm'\",\n '[class.rt-multiselect--size--lg]': \"size() === 'lg'\",\n },\n})\nexport class RtMultiselectComponent<TValue> extends RtFormControlBase<ReadonlyArray<TValue>> {\n readonly #t_uiSelectValues: Signal<string> = rtKitLabel('uiSelectValues');\n\n protected readonly t: Signal<TRtKitLabelMap> = inject(RT_KIT_LABELS);\n\n protected readonly panelId: string = `rt-multiselect-panel-${nextPanelId()}`;\n\n protected readonly placeholderText: Signal<string> = computed((): string => this.placeholder() || this.#t_uiSelectValues());\n\n protected readonly popover: Signal<RtPopoverDirective> = viewChild.required(RtPopoverDirective);\n\n protected readonly triggerEl: Signal<ElementRef<HTMLButtonElement> | undefined> = viewChild<ElementRef<HTMLButtonElement>>('triggerEl');\n\n /** Раскрытые ветки дерева. Живут, пока открыта панель: при открытии раскрыты ветки над выбранным. */\n protected readonly openBranches: WritableSignal<ReadonlySet<TValue>> = signal<ReadonlySet<TValue>>(new Set<TValue>());\n\n /** Дерево ли список: тогда у строк есть место под стрелку и отступ по уровню. */\n protected readonly isTree: Signal<boolean> = computed((): boolean => rtTreeIsTree(this.options()));\n\n /** Видимые строки: плоский список — как прежде, дерево — по раскрытым веткам. */\n protected readonly rows: Signal<ReadonlyArray<IRtSelect.Row<TValue>>> = computed((): ReadonlyArray<IRtSelect.Row<TValue>> =>\n rtTreeRows(this.options(), this.openBranches())\n );\n\n /** Флажок каждой строки: у листа — выбран ли он, у ветки — выведен из её включённых листьев. */\n protected readonly rowChecks: Signal<ReadonlyArray<IRtSelect.TBranchState>> = computed((): ReadonlyArray<IRtSelect.TBranchState> =>\n this.rows().map((row: IRtSelect.Row<TValue>): IRtSelect.TBranchState => this.#checkOf(row))\n );\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.rows().length,\n isDisabled: (index: number): boolean => Boolean(this.rows()[index]?.option.disabled),\n chooseKeys: new Set<string>(['Enter', ' ']),\n sideKeys: true,\n edgeKeys: true,\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 /**\n * Своя разметка указателя, если потребитель её объявил. Маркер тот же, что у выбора одного\n * значения: семьи отличаются только разметкой внутри кнопки.\n */\n protected readonly triggerTpl: Signal<RtSelectTriggerDirective<ReadonlyArray<TValue>> | undefined> =\n contentChild(RtSelectTriggerDirective);\n\n protected readonly hasValue: Signal<boolean> = computed((): boolean => this.value().length > 0);\n\n protected readonly visibleChips: Signal<ReadonlyArray<TValue>> = computed((): ReadonlyArray<TValue> =>\n this.value().slice(0, this.maxChips())\n );\n\n protected readonly extraChipsCount: Signal<number> = computed((): number => Math.max(0, this.value().length - this.maxChips()));\n\n /** Три значения, которые кит отдаёт своей разметке указателя, и не больше. */\n protected readonly triggerState: Signal<IRtSelect.TriggerState<ReadonlyArray<TValue>>> = computed(\n (): IRtSelect.TriggerState<ReadonlyArray<TValue>> => ({\n isOpen: this.isOpen(),\n value: this.value(),\n label: this.displayText(),\n isDisabled: this.isDisabled(),\n })\n );\n\n public readonly displayText: Signal<string> = computed((): string =>\n this.value()\n .map((v: TValue): string => this.labelOf(v))\n .join(', ')\n );\n\n public readonly options: InputSignal<ReadonlyArray<IRtSelect.Option<TValue>>> = input<ReadonlyArray<IRtSelect.Option<TValue>>>([]);\n\n /** Пусто — берётся переведённая подпись по умолчанию */\n public readonly placeholder: InputSignal<string> = input<string>('');\n public readonly maxChips: InputSignal<number> = input<number>(3);\n\n /** Иконка слева в указателе — та же, что у `rt-select`: перед подсказкой или чипами. */\n public readonly iconLeft: InputSignal<IRtIcon.Name | null> = input<IRtIcon.Name | null>(null);\n\n /**\n * Чем мерится панель. По умолчанию она не уже кнопки и дальше растёт по содержимому:\n * со своим указателем кнопка бывает узкой, и панель по её ширине давила бы содержимое.\n * `trigger` возвращает прежнее — ровно по кнопке, `auto` пускает панель по содержимому\n * целиком. Предел высоты назначается входом `panelMaxHeight`.\n */\n public readonly panelWidth: InputSignal<IRtPopover.Width> = input<IRtPopover.Width>('trigger-min');\n\n /**\n * Предел высоты панели — длина как в стилях, например `20rem`. Пусто — предел кита\n * `--rt-input-panel-max-height`, и длинный список прокручивается внутри панели: без предела\n * он уходил за нижний край экрана, и пункты за краем были недостижимы. `none` снимает предел —\n * для короткого списка, который должен открываться целиком, без прокрутки. Значение ставится\n * на саму панель: она рисуется в наложении, вне поддерева блока.\n */\n public readonly panelMaxHeight: InputSignal<string | null> = input<string | null>(null);\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(): ReadonlyArray<TValue> {\n return [];\n }\n\n protected focusAfterClear(): void {\n this.triggerEl()?.nativeElement.focus();\n }\n\n protected labelOf(value: TValue): string {\n return rtTreeFind(this.options(), value)?.label ?? String(value);\n }\n\n protected isSelected(value: TValue): boolean {\n return this.value().includes(value);\n }\n\n protected onOpened(): void {\n this.openBranches.set(rtTreeOpenFor(this.options(), this.value()));\n }\n\n protected toggleBranch(event: Event, value: TValue): void {\n event.stopPropagation();\n this.openBranches.update((open: ReadonlySet<TValue>): ReadonlySet<TValue> => rtTreeToggle(open, value));\n }\n\n protected onClosed(): void {\n this.activeIndex.set(-1);\n this.markTouched();\n }\n\n /**\n * Лист переключается сам. Ветка выбирает все свои включённые листья или снимает их, когда\n * выбраны все; своё значение ветки в выбор не пишется.\n */\n protected toggleOption(opt: IRtSelect.Option<TValue>): void {\n if (opt.disabled || this.isDisabled()) {\n return;\n }\n const leaves: ReadonlyArray<TValue> = rtTreeLeaves(opt);\n const current: ReadonlyArray<TValue> = this.value();\n const clearing: boolean = rtTreeBranchState(opt, current) === 'all';\n const next: ReadonlyArray<TValue> = clearing\n ? current.filter((v: TValue): boolean => !leaves.includes(v))\n : [...current, ...leaves.filter((leaf: TValue): boolean => !current.includes(leaf))];\n this.value.set(next);\n this.emitChange([...next]);\n }\n\n protected removeChip(value: TValue, event: MouseEvent): void {\n event.stopPropagation();\n if (this.isDisabled()) {\n return;\n }\n const next: ReadonlyArray<TValue> = this.value().filter((v: TValue): boolean => v !== value);\n this.value.set(next);\n this.emitChange([...next]);\n }\n\n protected onTriggerKeydown(event: KeyboardEvent): void {\n if (this.isDisabled()) {\n return;\n }\n if (!this.isOpen()) {\n if (this.optionList.opensBy(event.key)) {\n event.preventDefault();\n this.popover().open();\n this.optionList.move(1);\n }\n return;\n }\n this.#handleOpenKeydown(event);\n }\n\n #handleOpenKeydown(event: KeyboardEvent): void {\n const action: TRtOptionListAction = this.optionList.actionOf(event.key);\n if (action === 'none') {\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 'first':\n this.optionList.first();\n break;\n case 'last':\n this.optionList.last();\n break;\n case 'choose':\n this.#toggleActive();\n break;\n case 'close':\n this.popover().close();\n break;\n case 'side':\n this.#sideKey(event.key);\n break;\n }\n }\n\n #checkOf(row: IRtSelect.Row<TValue>): IRtSelect.TBranchState {\n if (row.branch) {\n return rtTreeBranchState(row.option, this.value());\n }\n return this.value().includes(row.option.value) ? 'all' : 'none';\n }\n\n #sideKey(key: string): void {\n const answer: IRtSelect.SideKeyAnswer<TValue> = rtTreeSideKey(this.rows(), this.activeIndex(), key);\n const toggle: TValue | null = answer.toggle;\n if (toggle !== null) {\n this.openBranches.update((open: ReadonlySet<TValue>): ReadonlySet<TValue> => rtTreeToggle(open, toggle));\n }\n this.activeIndex.set(answer.index);\n }\n\n #toggleActive(): void {\n const opts: ReadonlyArray<IRtSelect.Option<TValue>> = this.rows().map(\n (row: IRtSelect.Row<TValue>): IRtSelect.Option<TValue> => row.option\n );\n const index: number = this.activeIndex();\n if (index >= 0 && index < opts.length) {\n this.toggleOption(opts[index]);\n }\n }\n}\n","<ng-container rtBlock=\"rt-multiselect\">\n @if (isReadonly()) {\n <span rtElem=\"readonly\" qa-dataid=\"multiselect-readonly\" [rtMod]=\"{ empty: !displayText() }\">{{ displayText() || '—' }}</span>\n }\n <button\n #triggerEl\n rtElem=\"trigger\"\n qa-dataid=\"multiselect-trigger\"\n type=\"button\"\n [rtPopoverWidth]=\"panelWidth()\"\n [rtMod]=\"{ own: !!triggerTpl() }\"\n [rtPopover]=\"panelTpl\"\n [rtPopoverDisabled]=\"isDisabled()\"\n [attr.id]=\"effectiveControlId()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-invalid]=\"isInvalid() || null\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-activedescendant]=\"activeOptionId()\"\n [disabled]=\"isDisabled()\"\n (keydown)=\"onTriggerKeydown($event)\"\n (rtPopoverOpened)=\"onOpened()\"\n (rtPopoverClosed)=\"onClosed()\">\n <!--\n Разметка указателя: своя, если потребитель её объявил, иначе зашитая китом. Она встаёт\n внутрь кнопки, а не вместо неё: поведение — открытие панели, приметы доступности,\n отключённость, клавиши и закрытие — остаётся на кнопке кита.\n -->\n @if (triggerTpl(); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl.templateRef\" [ngTemplateOutletContext]=\"{ $implicit: triggerState() }\" />\n } @else {\n @if (iconLeft(); as left) {\n <rt-icon rtElem=\"icon-left\" qa-dataid=\"multiselect-icon-left\" size=\"sm\" color=\"muted\" [name]=\"left\" />\n }\n @if (value().length === 0) {\n <span rtElem=\"placeholder\" qa-dataid=\"multiselect-placeholder\">{{ placeholderText() }}</span>\n } @else {\n <span rtElem=\"chips\">\n @for (val of visibleChips(); track val) {\n <span rtElem=\"chip\" qa-dataid=\"multiselect-chip\" [attr.data-value]=\"val\">\n <rt-tag\n severity=\"info\"\n [value]=\"val | rtMultiselectLabel: options()\"\n [closable]=\"true\"\n (closed)=\"removeChip(val, $event)\" />\n </span>\n }\n @if (extraChipsCount() > 0) {\n <span rtElem=\"chip-more\" qa-dataid=\"multiselect-chip-more\">+{{ extraChipsCount() }}</span>\n }\n </span>\n }\n @if (showClearButton()) {\n <rt-icon-button\n rtElem=\"clear\"\n qa-dataid=\"multiselect-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 <rt-icon rtElem=\"chevron\" name=\"chevron-down\" size=\"sm\" color=\"muted\" />\n }\n </button>\n\n <ng-template #panelTpl>\n <div\n rtElem=\"panel\"\n qa-dataid=\"multiselect-panel\"\n role=\"listbox\"\n [style.--rt-multiselect-panel-max-height]=\"panelMaxHeight()\"\n [attr.aria-label]=\"ariaLabel()\">\n <!--\n Опции выбираются мышью через (click); keyboard-nav — на trigger'е\n (activedescendant), опции не focusable индивидуально.\n -->\n <!-- eslint-disable @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <ul rtElem=\"list\">\n @for (row of rows(); track row.option.value; let i = $index) {\n <li\n rtElem=\"option\"\n qa-dataid=\"multiselect-option\"\n role=\"option\"\n [id]=\"panelId + '-opt-' + i\"\n [attr.data-value]=\"row.option.value\"\n [attr.aria-level]=\"isTree() ? row.level + 1 : null\"\n [style.--rt-multiselect-tree-level]=\"row.level\"\n [rtMod]=\"{\n selected: rowChecks()[i] === 'all',\n active: i === activeIndex(),\n disabled: !!row.option.disabled,\n }\"\n [attr.aria-selected]=\"rowChecks()[i] === 'all'\"\n [attr.aria-disabled]=\"!!row.option.disabled\"\n (click)=\"toggleOption(row.option)\">\n @if (isTree()) {\n @if (row.branch) {\n <button\n rtElem=\"toggle\"\n qa-dataid=\"multiselect-option-toggle\"\n type=\"button\"\n tabindex=\"-1\"\n [attr.aria-label]=\"row.open ? t().uiCollapse : t().uiExpand\"\n [attr.aria-expanded]=\"row.open\"\n (click)=\"toggleBranch($event, row.option.value)\">\n <rt-icon size=\"sm\" color=\"muted\" [name]=\"row.open ? 'chevron-down' : 'chevron-right'\" />\n </button>\n } @else {\n <span rtElem=\"toggle-space\"></span>\n }\n }\n <input\n rtElem=\"checkbox\"\n qa-dataid=\"multiselect-option-checkbox\"\n type=\"checkbox\"\n tabindex=\"-1\"\n [checked]=\"rowChecks()[i] === 'all'\"\n [indeterminate]=\"rowChecks()[i] === 'some'\" />\n <span rtElem=\"option-label\" qa-dataid=\"multiselect-option-label\">{{ row.option.label }}</span>\n </li>\n } @empty {\n <li rtElem=\"empty\" qa-dataid=\"multiselect-empty\">{{ t().uiNoOptions }}</li>\n }\n </ul>\n </div>\n </ng-template>\n</ng-container>\n"],"mappings":"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"}