@rt-tools/ui-kit-v2 0.33.0 → 0.34.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 (217) hide show
  1. package/README.md +4 -3
  2. package/fesm2022/rt-tools-ui-kit-v2-ai-run-status.mjs +1 -0
  3. package/fesm2022/rt-tools-ui-kit-v2-ai-run-status.mjs.map +1 -1
  4. package/fesm2022/rt-tools-ui-kit-v2-autocomplete.mjs +92 -5
  5. package/fesm2022/rt-tools-ui-kit-v2-autocomplete.mjs.map +1 -1
  6. package/fesm2022/rt-tools-ui-kit-v2-badge.mjs +191 -0
  7. package/fesm2022/rt-tools-ui-kit-v2-badge.mjs.map +1 -0
  8. package/fesm2022/rt-tools-ui-kit-v2-breadcrumbs.mjs +3 -2
  9. package/fesm2022/rt-tools-ui-kit-v2-breadcrumbs.mjs.map +1 -1
  10. package/fesm2022/rt-tools-ui-kit-v2-button.mjs +6 -1
  11. package/fesm2022/rt-tools-ui-kit-v2-button.mjs.map +1 -1
  12. package/fesm2022/rt-tools-ui-kit-v2-checkbox.mjs +35 -4
  13. package/fesm2022/rt-tools-ui-kit-v2-checkbox.mjs.map +1 -1
  14. package/fesm2022/rt-tools-ui-kit-v2-color-palette.mjs +13 -4
  15. package/fesm2022/rt-tools-ui-kit-v2-color-palette.mjs.map +1 -1
  16. package/fesm2022/rt-tools-ui-kit-v2-container.mjs +245 -60
  17. package/fesm2022/rt-tools-ui-kit-v2-container.mjs.map +1 -1
  18. package/fesm2022/rt-tools-ui-kit-v2-copy-value.mjs +30 -5
  19. package/fesm2022/rt-tools-ui-kit-v2-copy-value.mjs.map +1 -1
  20. package/fesm2022/rt-tools-ui-kit-v2-core.mjs +113 -2
  21. package/fesm2022/rt-tools-ui-kit-v2-core.mjs.map +1 -1
  22. package/fesm2022/rt-tools-ui-kit-v2-data-list.mjs +14 -3
  23. package/fesm2022/rt-tools-ui-kit-v2-data-list.mjs.map +1 -1
  24. package/fesm2022/rt-tools-ui-kit-v2-data-table.mjs +17 -3
  25. package/fesm2022/rt-tools-ui-kit-v2-data-table.mjs.map +1 -1
  26. package/fesm2022/rt-tools-ui-kit-v2-date-picker.mjs +6 -3
  27. package/fesm2022/rt-tools-ui-kit-v2-date-picker.mjs.map +1 -1
  28. package/fesm2022/rt-tools-ui-kit-v2-date-range.mjs +2 -2
  29. package/fesm2022/rt-tools-ui-kit-v2-date-range.mjs.map +1 -1
  30. package/fesm2022/rt-tools-ui-kit-v2-divider.mjs +112 -0
  31. package/fesm2022/rt-tools-ui-kit-v2-divider.mjs.map +1 -0
  32. package/fesm2022/rt-tools-ui-kit-v2-draggable-tree.mjs +1 -0
  33. package/fesm2022/rt-tools-ui-kit-v2-draggable-tree.mjs.map +1 -1
  34. package/fesm2022/rt-tools-ui-kit-v2-drawer.mjs +854 -0
  35. package/fesm2022/rt-tools-ui-kit-v2-drawer.mjs.map +1 -0
  36. package/fesm2022/rt-tools-ui-kit-v2-dynamic-list.mjs +16 -5
  37. package/fesm2022/rt-tools-ui-kit-v2-dynamic-list.mjs.map +1 -1
  38. package/fesm2022/rt-tools-ui-kit-v2-dynamic-selector.mjs +580 -65
  39. package/fesm2022/rt-tools-ui-kit-v2-dynamic-selector.mjs.map +1 -1
  40. package/fesm2022/rt-tools-ui-kit-v2-expansion-panel.mjs +203 -15
  41. package/fesm2022/rt-tools-ui-kit-v2-expansion-panel.mjs.map +1 -1
  42. package/fesm2022/rt-tools-ui-kit-v2-field.mjs +40 -8
  43. package/fesm2022/rt-tools-ui-kit-v2-field.mjs.map +1 -1
  44. package/fesm2022/rt-tools-ui-kit-v2-file-card.mjs +1 -0
  45. package/fesm2022/rt-tools-ui-kit-v2-file-card.mjs.map +1 -1
  46. package/fesm2022/rt-tools-ui-kit-v2-filter-control.mjs +7 -3
  47. package/fesm2022/rt-tools-ui-kit-v2-filter-control.mjs.map +1 -1
  48. package/fesm2022/rt-tools-ui-kit-v2-form-control.mjs +53 -2
  49. package/fesm2022/rt-tools-ui-kit-v2-form-control.mjs.map +1 -1
  50. package/fesm2022/rt-tools-ui-kit-v2-hybrid-tree-selector.mjs +14 -4
  51. package/fesm2022/rt-tools-ui-kit-v2-hybrid-tree-selector.mjs.map +1 -1
  52. package/fesm2022/rt-tools-ui-kit-v2-hybrid-tree.mjs +6 -3
  53. package/fesm2022/rt-tools-ui-kit-v2-hybrid-tree.mjs.map +1 -1
  54. package/fesm2022/rt-tools-ui-kit-v2-icon-button.mjs +16 -5
  55. package/fesm2022/rt-tools-ui-kit-v2-icon-button.mjs.map +1 -1
  56. package/fesm2022/rt-tools-ui-kit-v2-image-upload.mjs +1 -0
  57. package/fesm2022/rt-tools-ui-kit-v2-image-upload.mjs.map +1 -1
  58. package/fesm2022/rt-tools-ui-kit-v2-inline-edit.mjs +14 -4
  59. package/fesm2022/rt-tools-ui-kit-v2-inline-edit.mjs.map +1 -1
  60. package/fesm2022/rt-tools-ui-kit-v2-input-number.mjs +3 -2
  61. package/fesm2022/rt-tools-ui-kit-v2-input-number.mjs.map +1 -1
  62. package/fesm2022/rt-tools-ui-kit-v2-input.mjs +207 -7
  63. package/fesm2022/rt-tools-ui-kit-v2-input.mjs.map +1 -1
  64. package/fesm2022/rt-tools-ui-kit-v2-menu.mjs +1 -0
  65. package/fesm2022/rt-tools-ui-kit-v2-menu.mjs.map +1 -1
  66. package/fesm2022/rt-tools-ui-kit-v2-multiselect.mjs +3 -2
  67. package/fesm2022/rt-tools-ui-kit-v2-multiselect.mjs.map +1 -1
  68. package/fesm2022/rt-tools-ui-kit-v2-page-header.mjs +2 -0
  69. package/fesm2022/rt-tools-ui-kit-v2-page-header.mjs.map +1 -1
  70. package/fesm2022/rt-tools-ui-kit-v2-pagination.mjs +3 -2
  71. package/fesm2022/rt-tools-ui-kit-v2-pagination.mjs.map +1 -1
  72. package/fesm2022/rt-tools-ui-kit-v2-profile-menu.mjs +3 -2
  73. package/fesm2022/rt-tools-ui-kit-v2-profile-menu.mjs.map +1 -1
  74. package/fesm2022/rt-tools-ui-kit-v2-progress-bar.mjs +171 -0
  75. package/fesm2022/rt-tools-ui-kit-v2-progress-bar.mjs.map +1 -0
  76. package/fesm2022/rt-tools-ui-kit-v2-radio-button.mjs +176 -6
  77. package/fesm2022/rt-tools-ui-kit-v2-radio-button.mjs.map +1 -1
  78. package/fesm2022/rt-tools-ui-kit-v2-rich-editor.mjs +2 -1
  79. package/fesm2022/rt-tools-ui-kit-v2-rich-editor.mjs.map +1 -1
  80. package/fesm2022/rt-tools-ui-kit-v2-scroll-area.mjs +1 -0
  81. package/fesm2022/rt-tools-ui-kit-v2-scroll-area.mjs.map +1 -1
  82. package/fesm2022/rt-tools-ui-kit-v2-select.mjs +142 -23
  83. package/fesm2022/rt-tools-ui-kit-v2-select.mjs.map +1 -1
  84. package/fesm2022/rt-tools-ui-kit-v2-side-menu.mjs +22 -9
  85. package/fesm2022/rt-tools-ui-kit-v2-side-menu.mjs.map +1 -1
  86. package/fesm2022/rt-tools-ui-kit-v2-speed-menu.mjs +3 -2
  87. package/fesm2022/rt-tools-ui-kit-v2-speed-menu.mjs.map +1 -1
  88. package/fesm2022/rt-tools-ui-kit-v2-stepper.mjs +105 -8
  89. package/fesm2022/rt-tools-ui-kit-v2-stepper.mjs.map +1 -1
  90. package/fesm2022/rt-tools-ui-kit-v2-table.mjs +13 -2
  91. package/fesm2022/rt-tools-ui-kit-v2-table.mjs.map +1 -1
  92. package/fesm2022/rt-tools-ui-kit-v2-tabs.mjs +609 -33
  93. package/fesm2022/rt-tools-ui-kit-v2-tabs.mjs.map +1 -1
  94. package/fesm2022/rt-tools-ui-kit-v2-tag.mjs +1 -0
  95. package/fesm2022/rt-tools-ui-kit-v2-tag.mjs.map +1 -1
  96. package/fesm2022/rt-tools-ui-kit-v2-textarea.mjs +45 -7
  97. package/fesm2022/rt-tools-ui-kit-v2-textarea.mjs.map +1 -1
  98. package/fesm2022/rt-tools-ui-kit-v2-theme-toggle.mjs +2 -2
  99. package/fesm2022/rt-tools-ui-kit-v2-theme-toggle.mjs.map +1 -1
  100. package/fesm2022/rt-tools-ui-kit-v2-thread-list.mjs +11 -2
  101. package/fesm2022/rt-tools-ui-kit-v2-thread-list.mjs.map +1 -1
  102. package/fesm2022/rt-tools-ui-kit-v2-toast.mjs +152 -11
  103. package/fesm2022/rt-tools-ui-kit-v2-toast.mjs.map +1 -1
  104. package/fesm2022/rt-tools-ui-kit-v2-toggle-button-group.mjs +199 -17
  105. package/fesm2022/rt-tools-ui-kit-v2-toggle-button-group.mjs.map +1 -1
  106. package/fesm2022/rt-tools-ui-kit-v2-tooltip.mjs +35 -5
  107. package/fesm2022/rt-tools-ui-kit-v2-tooltip.mjs.map +1 -1
  108. package/fesm2022/rt-tools-ui-kit-v2-tree-selector.mjs +14 -4
  109. package/fesm2022/rt-tools-ui-kit-v2-tree-selector.mjs.map +1 -1
  110. package/fesm2022/rt-tools-ui-kit-v2-tree.mjs +6 -3
  111. package/fesm2022/rt-tools-ui-kit-v2-tree.mjs.map +1 -1
  112. package/fesm2022/rt-tools-ui-kit-v2-workspace-details.mjs +21 -6
  113. package/fesm2022/rt-tools-ui-kit-v2-workspace-details.mjs.map +1 -1
  114. package/fesm2022/rt-tools-ui-kit-v2.mjs +4 -0
  115. package/package.json +17 -1
  116. package/src/assets/icons/volume-off.svg +2 -1
  117. package/src/lib/components/autocomplete/CONTEXT.md +25 -14
  118. package/src/lib/components/autocomplete/rt-autocomplete.component.scss +19 -0
  119. package/src/lib/components/badge/CONTEXT.md +33 -0
  120. package/src/lib/components/badge/rt-badge.directive.scss +104 -0
  121. package/src/lib/components/breadcrumbs/rt-breadcrumbs.component.scss +2 -2
  122. package/src/lib/components/button/CONTEXT.md +5 -0
  123. package/src/lib/components/button/rt-button.directive.scss +3 -1
  124. package/src/lib/components/checkbox/CONTEXT.md +15 -8
  125. package/src/lib/components/color-palette/rt-color-palette.component.scss +2 -2
  126. package/src/lib/components/container/CONTEXT.md +23 -6
  127. package/src/lib/components/container/rt-container.component.scss +90 -1
  128. package/src/lib/components/copy-value/CONTEXT.md +6 -5
  129. package/src/lib/components/copy-value/rt-copy-value.component.scss +16 -0
  130. package/src/lib/components/date-picker/rt-date-picker.component.scss +7 -0
  131. package/src/lib/components/date-range/rt-date-range.component.scss +7 -0
  132. package/src/lib/components/divider/CONTEXT.md +21 -0
  133. package/src/lib/components/divider/rt-divider.component.scss +34 -0
  134. package/src/lib/components/drawer/CONTEXT.md +73 -0
  135. package/src/lib/components/drawer/rt-drawer-container.component.scss +19 -0
  136. package/src/lib/components/drawer/rt-drawer.component.scss +168 -0
  137. package/src/lib/components/drawer/rt-resize-handle.component.scss +42 -0
  138. package/src/lib/components/dynamic-selector/CONTEXT.md +5 -0
  139. package/src/lib/components/expansion-panel/CONTEXT.md +32 -8
  140. package/src/lib/components/expansion-panel/rt-expansion-panel.component.scss +67 -2
  141. package/src/lib/components/field/CONTEXT.md +43 -0
  142. package/src/lib/components/field/rt-field-label-inside.scss +179 -0
  143. package/src/lib/components/field/rt-field.component.scss +51 -2
  144. package/src/lib/components/icon-button/CONTEXT.md +17 -15
  145. package/src/lib/components/icon-button/rt-icon-button.component.scss +3 -1
  146. package/src/lib/components/inline-edit/rt-inline-edit.component.scss +4 -4
  147. package/src/lib/components/input/CONTEXT.md +45 -11
  148. package/src/lib/components/input/rt-input-floating-label.scss +192 -0
  149. package/src/lib/components/input/rt-input-icons.scss +105 -0
  150. package/src/lib/components/input/rt-input.component.scss +91 -13
  151. package/src/lib/components/input-number/CONTEXT.md +7 -0
  152. package/src/lib/components/input-number/rt-input-number.component.scss +58 -1
  153. package/src/lib/components/menu/CONTEXT.md +1 -0
  154. package/src/lib/components/multiselect/CONTEXT.md +2 -1
  155. package/src/lib/components/multiselect/rt-multiselect.component.scss +7 -0
  156. package/src/lib/components/pagination/CONTEXT.md +3 -0
  157. package/src/lib/components/pagination/rt-pagination.component.scss +7 -0
  158. package/src/lib/components/progress-bar/CONTEXT.md +22 -0
  159. package/src/lib/components/progress-bar/rt-progress-bar.component.scss +77 -0
  160. package/src/lib/components/radio-button/CONTEXT.md +9 -0
  161. package/src/lib/components/radio-button/rt-radio-group.component.scss +13 -0
  162. package/src/lib/components/select/CONTEXT.md +28 -0
  163. package/src/lib/components/select/rt-select-clear.scss +88 -0
  164. package/src/lib/components/select/rt-select-floating-label.scss +155 -0
  165. package/src/lib/components/select/rt-select-material.scss +54 -0
  166. package/src/lib/components/select/rt-select.component.scss +54 -16
  167. package/src/lib/components/side-menu/CONTEXT.md +5 -0
  168. package/src/lib/components/speed-menu/rt-speed-menu.component.scss +5 -5
  169. package/src/lib/components/stepper/CONTEXT.md +13 -5
  170. package/src/lib/components/stepper/rt-stepper.component.scss +93 -0
  171. package/src/lib/components/tabs/CONTEXT.md +57 -9
  172. package/src/lib/components/tabs/rt-tabs.component.scss +201 -5
  173. package/src/lib/components/textarea/CONTEXT.md +12 -1
  174. package/src/lib/components/textarea/rt-textarea.component.scss +116 -1
  175. package/src/lib/components/toast/CONTEXT.md +23 -0
  176. package/src/lib/components/toast/rt-toast.component.scss +37 -0
  177. package/src/lib/components/toggle-button-group/CONTEXT.md +24 -4
  178. package/src/lib/components/toggle-button-group/rt-toggle-button-group-chips.scss +151 -0
  179. package/src/lib/components/toggle-button-group/rt-toggle-button-group.component.scss +35 -0
  180. package/src/lib/components/tooltip/CONTEXT.md +7 -1
  181. package/src/lib/components/tree/rt-tree.component.scss +2 -2
  182. package/src/styles/_color-scheme.scss +1 -1
  183. package/src/styles/_density.scss +48 -0
  184. package/src/styles/_index.scss +8 -0
  185. package/src/styles/_preset-material.scss +160 -70
  186. package/src/styles/_primitives.scss +1 -0
  187. package/src/styles/_semantic-navigation.scss +117 -0
  188. package/src/styles/_semantic.scss +29 -98
  189. package/src/styles/_theme-dark.scss +8 -0
  190. package/styles/tokens.css +2066 -1134
  191. package/types/rt-tools-ui-kit-v2-autocomplete.d.ts +34 -0
  192. package/types/rt-tools-ui-kit-v2-badge.d.ts +71 -0
  193. package/types/rt-tools-ui-kit-v2-checkbox.d.ts +17 -3
  194. package/types/rt-tools-ui-kit-v2-container.d.ts +65 -9
  195. package/types/rt-tools-ui-kit-v2-copy-value.d.ts +11 -1
  196. package/types/rt-tools-ui-kit-v2-core.d.ts +109 -4
  197. package/types/rt-tools-ui-kit-v2-divider.d.ts +29 -0
  198. package/types/rt-tools-ui-kit-v2-drawer.d.ts +257 -0
  199. package/types/rt-tools-ui-kit-v2-dynamic-selector.d.ts +240 -25
  200. package/types/rt-tools-ui-kit-v2-expansion-panel.d.ts +87 -3
  201. package/types/rt-tools-ui-kit-v2-field.d.ts +18 -1
  202. package/types/rt-tools-ui-kit-v2-form-control.d.ts +42 -4
  203. package/types/rt-tools-ui-kit-v2-icon-button.d.ts +2 -1
  204. package/types/rt-tools-ui-kit-v2-input-number.d.ts +1 -0
  205. package/types/rt-tools-ui-kit-v2-input.d.ts +98 -4
  206. package/types/rt-tools-ui-kit-v2-progress-bar.d.ts +53 -0
  207. package/types/rt-tools-ui-kit-v2-radio-button.d.ts +80 -1
  208. package/types/rt-tools-ui-kit-v2-select.d.ts +65 -1
  209. package/types/rt-tools-ui-kit-v2-side-menu.d.ts +3 -0
  210. package/types/rt-tools-ui-kit-v2-stepper.d.ts +57 -4
  211. package/types/rt-tools-ui-kit-v2-tabs.d.ts +291 -6
  212. package/types/rt-tools-ui-kit-v2-textarea.d.ts +16 -0
  213. package/types/rt-tools-ui-kit-v2-toast.d.ts +38 -2
  214. package/types/rt-tools-ui-kit-v2-toggle-button-group.d.ts +83 -3
  215. package/types/rt-tools-ui-kit-v2-tooltip.d.ts +17 -2
  216. package/types/rt-tools-ui-kit-v2.d.ts +4 -0
  217. package/src/lib/components/dynamic-selector/stories/component/test-dynamic-selector-popup-frame.component.scss +0 -6
@@ -1 +1 @@
1
- {"version":3,"file":"rt-tools-ui-kit-v2-field.mjs","names":[],"sources":["../../../projects/ui-kit-v2/src/lib/components/field/rt-field-hint.directive.ts","../../../projects/ui-kit-v2/src/lib/components/field/rt-field.model.ts","../../../projects/ui-kit-v2/src/lib/components/field/rt-field.component.ts","../../../projects/ui-kit-v2/src/lib/components/field/rt-field.component.html"],"sourcesContent":["import { Directive } from '@angular/core';\n\n/**\n * Маркер проецируемого hint'а для rt-field. Нужен, когда подсказке требуется\n * разметка (`<strong>`, интерполяция значений), которую плоский string-input\n * `[hint]` передать не может. Если проецируемый hint присутствует — rt-field\n * рендерит его вместо строкового `[hint]` (тот остаётся fallback'ом).\n */\n@Directive({\n selector: '[rtFieldHint]',\n})\nexport class RtFieldHintDirective {}\n","import { TRtKitLabelKey } from '@rt-tools/ui-kit-v2/core';\n\n/** Контракт поля-обёртки common/ui. */\nexport namespace IRtField {\n /** Карта validatorKey → человекочитаемое сообщение. */\n export type ErrorMessages = Record<string, string>;\n}\n\n/**\n * Ключи сообщений об ошибках по имени валидатора (перекрываются input'ом\n * [errors] поштучно, уже готовым текстом).\n *\n * Здесь ключи, а не переводы: карта собирается при загрузке модуля, когда язык\n * страницы ещё неизвестен, и готовый текст остался бы на языке по умолчанию\n * даже под переведённой подписью поля. Переводит сам компонент поля.\n */\nexport const RT_FIELD_DEFAULT_ERROR_KEYS: Readonly<Record<string, TRtKitLabelKey>> = {\n required: 'fieldErrorRequired',\n email: 'fieldErrorEmail',\n minlength: 'fieldErrorMinLength',\n maxlength: 'fieldErrorMaxLength',\n min: 'fieldErrorMin',\n max: 'fieldErrorMax',\n pattern: 'fieldErrorPattern',\n};\n\n/** Текст-плейсхолдер read-only поля без значения. */\nexport const RT_FIELD_EMPTY_VALUE: string = '—';\n","import { BooleanInput } from '@angular/cdk/coercion';\nimport {\n booleanAttribute,\n computed,\n contentChild,\n effect,\n inject,\n input,\n AfterContentInit,\n ChangeDetectionStrategy,\n Component,\n InputSignal,\n InputSignalWithTransform,\n Signal,\n ViewEncapsulation,\n} from '@angular/core';\nimport { ValidationErrors } from '@angular/forms';\n\nimport { BlockDirective, ElemDirective, ModDirective } from '@rt-tools/core';\n\nimport { RT_KIT_LABELS, TRtKitLabelKey, TRtKitLabelMap, rtKitLabel } from '@rt-tools/ui-kit-v2/core';\nimport { RtFormControlBase } from '@rt-tools/ui-kit-v2/form-control';\nimport { RtIconComponent } from '@rt-tools/ui-kit-v2/icon';\nimport { RtPopoverDirective } from '@rt-tools/ui-kit-v2/popover';\nimport { RtSkeletonWrapperComponent } from '@rt-tools/ui-kit-v2/skeleton-wrapper';\nimport { RtFieldHintDirective } from './rt-field-hint.directive';\nimport { RT_FIELD_DEFAULT_ERROR_KEYS, RT_FIELD_EMPTY_VALUE, IRtField } from './rt-field.model';\n\nconst BEM_BLOCK: string = 'rt-field';\n\n/** Счётчик для авто-id (связка label[for] ↔ controlId спроецированного контрола). */\nlet uidSeed: number = 0;\n\nfunction nextAutoId(): string {\n uidSeed += 1;\n return `rt-field-${uidSeed}`;\n}\n\n/**\n * Обёртка единой анатомии поля: label (+ опц. help-иконка с popover, + авто «*»\n * для required) сверху, спроецированный rt-контрол посередине, hint/error снизу.\n * `required` объявляет поле обязательным там, где валидатора на контроле нет (шаблонная форма).\n * `hideRequiredMark` глушит авто-«*» — для форм, где все поля обязательны и\n * маркер лишний (логин).\n * `reserveHintSpace` держит под контролом строку высотой в одно сообщение даже\n * когда ни ошибки, ни hint нет — чтобы появление ошибки не сдвигало форму вниз\n * (нужно для полей без постоянного hint, напр. логин).\n *\n * Читает контрол через contentChild(RtFormControlBase): авто-связывает label[for],\n * прокидывает read-only режим, показывает текст ошибки/required-маркер. На loading\n * оборачивает зону контрола в rt-skeleton-wrapper (label/hint остаются видимыми) —\n * нужно для route-aside, где поля рендерятся до прихода данных.\n */\n@Component({\n selector: 'rt-field',\n templateUrl: './rt-field.component.html',\n styleUrls: ['./rt-field.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [\n // standalone components / directives\n RtIconComponent,\n RtPopoverDirective,\n RtSkeletonWrapperComponent,\n BlockDirective,\n ElemDirective,\n ModDirective,\n ],\n host: { class: BEM_BLOCK },\n})\nexport class RtFieldComponent implements AfterContentInit {\n // contentChild нельзя объявлять на ES-private (#) поле — Angular это запрещает,\n // поэтому проекция контрола живёт в protected-поле.\n protected readonly t: Signal<TRtKitLabelMap> = inject(RT_KIT_LABELS);\n\n protected readonly projectedControl: Signal<RtFormControlBase<unknown> | undefined> = contentChild(RtFormControlBase);\n\n // Проецируемый hint (с разметкой) — если присутствует, рендерится вместо\n // строкового [hint].\n protected readonly projectedHint: Signal<RtFieldHintDirective | undefined> = contentChild(RtFieldHintDirective);\n\n readonly #autoId: string = nextAutoId();\n\n /** Итоговый id: явный controlId контрола важнее авто-id. */\n protected readonly controlId: Signal<string> = computed((): string => this.projectedControl()?.effectiveControlId() ?? this.#autoId);\n\n protected readonly isRequired: Signal<boolean> = computed((): boolean => this.projectedControl()?.required() ?? false);\n\n protected readonly showError: Signal<boolean> = computed((): boolean => !!this.projectedControl()?.invalid());\n\n /** Имя первого сработавшего валидатора: сообщений показываем по одному */\n readonly #failedValidator: Signal<string> = computed((): string => {\n const errs: ValidationErrors | null = this.projectedControl()?.errors() ?? null;\n return errs ? (Object.keys(errs)[0] ?? '') : '';\n });\n\n /**\n * Умолчание переводится по ключу.\n *\n * Ключ передаётся сигналом, а не строкой: так подпись пересчитывается и при\n * смене валидатора, и при смене языка — пользователь переключает его\n * на лету, и текст ошибки обязан переключиться вместе с формой.\n */\n readonly #defaultErrorText: Signal<string> = rtKitLabel(\n computed((): TRtKitLabelKey | '' => RT_FIELD_DEFAULT_ERROR_KEYS[this.#failedValidator()] ?? '')\n );\n\n /** Сообщение об ошибке: текст, переданный формой, важнее умолчания */\n protected readonly errorText: Signal<string> = computed((): string => {\n const validator: string = this.#failedValidator();\n if (!validator) {\n return '';\n }\n const own: string | undefined = this.errors()[validator];\n if (own) {\n return own;\n }\n return RT_FIELD_DEFAULT_ERROR_KEYS[validator] ? this.#defaultErrorText() : '';\n });\n\n protected readonly emptyValue: string = RT_FIELD_EMPTY_VALUE;\n\n public readonly label: InputSignal<string> = input<string>('');\n public readonly hint: InputSignal<string> = input<string>('');\n public readonly help: InputSignal<string> = input<string>('');\n public readonly readonly: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n public readonly loading: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n /**\n * Поле обязательно, хотя валидатора на контроле нет: шаблонная форма его не кладёт. Даёт «*» и\n * ошибку под тронутым пустым полем. Форму недействительной не делает — решение о записи\n * остаётся у формы.\n */\n public readonly required: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n public readonly hideRequiredMark: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n public readonly reserveHintSpace: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n public readonly errors: InputSignal<IRtField.ErrorMessages> = input<IRtField.ErrorMessages>({});\n\n constructor() {\n // Реактивно прокидываем read-only режим в спроецированный контрол.\n effect((): void => {\n this.projectedControl()?.setReadonly(this.readonly());\n });\n effect((): void => {\n this.projectedControl()?.setRequired(this.required());\n });\n }\n\n public ngAfterContentInit(): void {\n const ctrl: RtFormControlBase<unknown> | undefined = this.projectedControl();\n // Авто-id только если у контрола нет явного controlId — для label[for].\n if (ctrl && ctrl.effectiveControlId() === null) {\n ctrl.assignControlId(this.#autoId);\n }\n }\n}\n","<ng-container rtBlock=\"rt-field\">\n @if (label()) {\n <span rtElem=\"label-row\">\n <label rtElem=\"label\" qa-dataid=\"field-label\" [attr.for]=\"controlId()\">\n {{ label() }}\n @if (isRequired() && !hideRequiredMark()) {\n <span rtElem=\"required\" qa-dataid=\"field-required\" aria-hidden=\"true\">*</span>\n }\n </label>\n @if (help()) {\n <button\n rtElem=\"help\"\n qa-dataid=\"field-help\"\n type=\"button\"\n rtPopoverTrigger=\"hover\"\n [attr.aria-label]=\"t().uiHint\"\n [rtPopover]=\"helpTpl\">\n <rt-icon name=\"ico-help\" size=\"sm\" color=\"muted\" />\n </button>\n <ng-template #helpTpl>\n <span rtElem=\"help-popover\" qa-dataid=\"field-help-popover\">{{ help() }}</span>\n </ng-template>\n }\n </span>\n }\n\n <rt-skeleton-wrapper rtElem=\"control\" height=\"var(--rt-input-height)\" [isLoading]=\"loading()\">\n <ng-content />\n </rt-skeleton-wrapper>\n\n <!-- С резервом hint остаётся в потоке даже под ошибкой (скрытый), а сама\n ошибка кладётся в ту же ячейку грида: высота зоны равна большему из\n двух сообщений и не меняется при их подмене. Без резерва — как было,\n одно сообщение за раз, чтобы плотные формы не набирали лишний воздух. -->\n <span rtElem=\"messages\" [rtMod]=\"{ reserved: reserveHintSpace() }\">\n @if (projectedHint() && (reserveHintSpace() || !showError())) {\n <small rtElem=\"hint\" qa-dataid=\"field-hint\" [rtMod]=\"{ hidden: showError() }\" [attr.aria-hidden]=\"showError() ? 'true' : null\">\n <ng-content select=\"[rtFieldHint]\" />\n </small>\n } @else if (hint() && (reserveHintSpace() || !showError())) {\n <small rtElem=\"hint\" qa-dataid=\"field-hint\" [rtMod]=\"{ hidden: showError() }\" [attr.aria-hidden]=\"showError() ? 'true' : null\">\n {{ hint() }}\n </small>\n }\n\n @if (showError()) {\n <small rtElem=\"error\" qa-dataid=\"field-error\">{{ errorText() }}</small>\n }\n </span>\n</ng-container>\n"],"mappings":"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"}
1
+ {"version":3,"file":"rt-tools-ui-kit-v2-field.mjs","names":[],"sources":["../../../projects/ui-kit-v2/src/lib/components/field/rt-field-hint.directive.ts","../../../projects/ui-kit-v2/src/lib/components/field/rt-field.model.ts","../../../projects/ui-kit-v2/src/lib/components/field/rt-field.component.ts","../../../projects/ui-kit-v2/src/lib/components/field/rt-field.component.html"],"sourcesContent":["import { Directive } from '@angular/core';\n\n/**\n * Маркер проецируемого hint'а для rt-field. Нужен, когда подсказке требуется\n * разметка (`<strong>`, интерполяция значений), которую плоский string-input\n * `[hint]` передать не может. Если проецируемый hint присутствует — rt-field\n * рендерит его вместо строкового `[hint]` (тот остаётся fallback'ом).\n */\n@Directive({\n selector: '[rtFieldHint]',\n})\nexport class RtFieldHintDirective {}\n","import { TRtKitLabelKey } from '@rt-tools/ui-kit-v2/core';\n\n/** Контракт поля-обёртки common/ui. */\nexport namespace IRtField {\n /** Карта validatorKey → человекочитаемое сообщение. */\n export type ErrorMessages = Record<string, string>;\n}\n\n/**\n * Ключи сообщений об ошибках по имени валидатора (перекрываются input'ом\n * [errors] поштучно, уже готовым текстом).\n *\n * Здесь ключи, а не переводы: карта собирается при загрузке модуля, когда язык\n * страницы ещё неизвестен, и готовый текст остался бы на языке по умолчанию\n * даже под переведённой подписью поля. Переводит сам компонент поля.\n */\nexport const RT_FIELD_DEFAULT_ERROR_KEYS: Readonly<Record<string, TRtKitLabelKey>> = {\n required: 'fieldErrorRequired',\n email: 'fieldErrorEmail',\n minlength: 'fieldErrorMinLength',\n maxlength: 'fieldErrorMaxLength',\n min: 'fieldErrorMin',\n max: 'fieldErrorMax',\n pattern: 'fieldErrorPattern',\n};\n\n/** Текст-плейсхолдер read-only поля без значения. */\nexport const RT_FIELD_EMPTY_VALUE: string = '—';\n","import { BooleanInput } from '@angular/cdk/coercion';\nimport {\n booleanAttribute,\n computed,\n contentChild,\n effect,\n inject,\n input,\n viewChild,\n AfterContentInit,\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n InputSignal,\n InputSignalWithTransform,\n Signal,\n ViewEncapsulation,\n} from '@angular/core';\nimport { ValidationErrors } from '@angular/forms';\n\nimport { BlockDirective, ElemDirective, ModDirective } from '@rt-tools/core';\n\nimport { RT_KIT_LABELS, TRtKitLabelKey, TRtKitLabelMap, rtKitLabel } from '@rt-tools/ui-kit-v2/core';\nimport { rtNotchWidth, RtFormControlBase } from '@rt-tools/ui-kit-v2/form-control';\nimport { RtIconComponent } from '@rt-tools/ui-kit-v2/icon';\nimport { RtPopoverDirective } from '@rt-tools/ui-kit-v2/popover';\nimport { RtSkeletonWrapperComponent } from '@rt-tools/ui-kit-v2/skeleton-wrapper';\nimport { RtFieldHintDirective } from './rt-field-hint.directive';\nimport { RT_FIELD_DEFAULT_ERROR_KEYS, RT_FIELD_EMPTY_VALUE, IRtField } from './rt-field.model';\n\nconst BEM_BLOCK: string = 'rt-field';\n\n/** Счётчик для авто-id (связка label[for] ↔ controlId спроецированного контрола). */\nlet uidSeed: number = 0;\n\nfunction nextAutoId(): string {\n uidSeed += 1;\n return `rt-field-${uidSeed}`;\n}\n\n/**\n * Обёртка единой анатомии поля: label (+ опц. help-иконка с popover, + авто «*»\n * для required) сверху, спроецированный rt-контрол посередине, hint/error снизу.\n * `required` объявляет поле обязательным там, где валидатора на контроле нет (шаблонная форма).\n * `hideRequiredMark` глушит авто-«*» — для форм, где все поля обязательны и\n * маркер лишний (логин).\n * `reserveHintSpace` держит под контролом строку высотой в одно сообщение даже\n * когда ни ошибки, ни hint нет — чтобы появление ошибки не сдвигало форму вниз\n * (нужно для полей без постоянного hint, напр. логин).\n *\n * Читает контрол через contentChild(RtFormControlBase): авто-связывает label[for],\n * прокидывает read-only режим, показывает текст ошибки/required-маркер. На loading\n * оборачивает зону контрола в rt-skeleton-wrapper (label/hint остаются видимыми) —\n * нужно для route-aside, где поля рендерятся до прихода данных.\n */\n@Component({\n selector: 'rt-field',\n templateUrl: './rt-field.component.html',\n styleUrls: ['./rt-field.component.scss', './rt-field-label-inside.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [\n // standalone components / directives\n RtIconComponent,\n RtPopoverDirective,\n RtSkeletonWrapperComponent,\n BlockDirective,\n ElemDirective,\n ModDirective,\n ],\n host: {\n class: BEM_BLOCK,\n '[class.rt-field--filled]': 'filled()',\n '[class.rt-field--invalid]': 'showError()',\n '[class.rt-field--static]': 'isStatic()',\n '[class.rt-field--label-inside]': 'labelInside()',\n '[style.--rt-field-notch-width]': \"notchWidth() + 'px'\",\n },\n})\nexport class RtFieldComponent implements AfterContentInit {\n // contentChild нельзя объявлять на ES-private (#) поле — Angular это запрещает,\n // поэтому проекция контрола живёт в protected-поле.\n protected readonly t: Signal<TRtKitLabelMap> = inject(RT_KIT_LABELS);\n\n protected readonly projectedControl: Signal<RtFormControlBase<unknown> | undefined> = contentChild(RtFormControlBase);\n\n // Проецируемый hint (с разметкой) — если присутствует, рендерится вместо\n // строкового [hint].\n protected readonly projectedHint: Signal<RtFieldHintDirective | undefined> = contentChild(RtFieldHintDirective);\n\n readonly #autoId: string = nextAutoId();\n\n /** Строка подписи: по её ширине материальный набор вырезает разрыв в рамке. `#`-поле `viewChild` не принимает. */\n protected readonly labelRow: Signal<ElementRef<HTMLElement> | undefined> = viewChild<ElementRef<HTMLElement>>('labelRow');\n\n /**\n * Ширина подписи на рамке, px. Разрыв рамки под подписью, как у поля Material, режется по этому\n * числу: ширину подписи стили сами не знают.\n */\n protected readonly notchWidth: Signal<number> = rtNotchWidth(this.labelRow);\n\n /** Итоговый id: явный controlId контрола важнее авто-id. */\n protected readonly controlId: Signal<string> = computed((): string => this.projectedControl()?.effectiveControlId() ?? this.#autoId);\n\n protected readonly isRequired: Signal<boolean> = computed((): boolean => this.projectedControl()?.required() ?? false);\n\n protected readonly showError: Signal<boolean> = computed((): boolean => !!this.projectedControl()?.invalid());\n\n /**\n * У контрола есть значение. Под материальным набором по нему подпись поднимается на рамку:\n * текст значения есть у всех контролов, поэтому одно правило служит полю, области текста,\n * числу и выбору.\n */\n protected readonly filled: Signal<boolean> = computed((): boolean => (this.projectedControl()?.displayText() ?? '') !== '');\n\n /** Поле без рамки ввода — только для чтения или в загрузке: подпись остаётся над значением. */\n protected readonly isStatic: Signal<boolean> = computed(\n (): boolean => this.readonly() || this.loading() || !!this.projectedControl()?.isReadonly()\n );\n\n /** Подпись материального набора ложится внутрь контрола: только у полей с местом подсказки. */\n protected readonly labelInside: Signal<boolean> = computed(\n (): boolean => !this.isStatic() && !!this.projectedControl()?.fieldLabelInside\n );\n\n /** Имя первого сработавшего валидатора: сообщений показываем по одному */\n readonly #failedValidator: Signal<string> = computed((): string => {\n const errs: ValidationErrors | null = this.projectedControl()?.errors() ?? null;\n return errs ? (Object.keys(errs)[0] ?? '') : '';\n });\n\n /**\n * Умолчание переводится по ключу.\n *\n * Ключ передаётся сигналом, а не строкой: так подпись пересчитывается и при\n * смене валидатора, и при смене языка — пользователь переключает его\n * на лету, и текст ошибки обязан переключиться вместе с формой.\n */\n readonly #defaultErrorText: Signal<string> = rtKitLabel(\n computed((): TRtKitLabelKey | '' => RT_FIELD_DEFAULT_ERROR_KEYS[this.#failedValidator()] ?? '')\n );\n\n /** Сообщение об ошибке: текст, переданный формой, важнее умолчания */\n protected readonly errorText: Signal<string> = computed((): string => {\n const validator: string = this.#failedValidator();\n if (!validator) {\n return '';\n }\n const own: string | undefined = this.errors()[validator];\n if (own) {\n return own;\n }\n return RT_FIELD_DEFAULT_ERROR_KEYS[validator] ? this.#defaultErrorText() : '';\n });\n\n protected readonly emptyValue: string = RT_FIELD_EMPTY_VALUE;\n\n public readonly label: InputSignal<string> = input<string>('');\n public readonly hint: InputSignal<string> = input<string>('');\n public readonly help: InputSignal<string> = input<string>('');\n public readonly readonly: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n public readonly loading: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n /**\n * Поле обязательно, хотя валидатора на контроле нет: шаблонная форма его не кладёт. Даёт «*» и\n * ошибку под тронутым пустым полем. Форму недействительной не делает — решение о записи\n * остаётся у формы.\n */\n public readonly required: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n public readonly hideRequiredMark: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n public readonly reserveHintSpace: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n public readonly errors: InputSignal<IRtField.ErrorMessages> = input<IRtField.ErrorMessages>({});\n\n constructor() {\n // Реактивно прокидываем read-only режим в спроецированный контрол.\n effect((): void => {\n this.projectedControl()?.setReadonly(this.readonly());\n });\n effect((): void => {\n this.projectedControl()?.setRequired(this.required());\n });\n }\n\n public ngAfterContentInit(): void {\n const ctrl: RtFormControlBase<unknown> | undefined = this.projectedControl();\n // Авто-id только если у контрола нет явного controlId — для label[for].\n if (ctrl && ctrl.effectiveControlId() === null) {\n ctrl.assignControlId(this.#autoId);\n }\n }\n}\n","<ng-container rtBlock=\"rt-field\">\n @if (label()) {\n <span #labelRow rtElem=\"label-row\">\n <!-- Текст подписи — свой узел: пробел между ним и звёздочкой сборка выбрасывает, и зазор до\n звёздочки задаёт свойство, а не пробел в разметке. -->\n <label rtElem=\"label\" qa-dataid=\"field-label\" [attr.for]=\"controlId()\">\n <span>{{ label() }}</span>\n @if (isRequired() && !hideRequiredMark()) {\n <span rtElem=\"required\" qa-dataid=\"field-required\" aria-hidden=\"true\">*</span>\n }\n </label>\n @if (help()) {\n <button\n rtElem=\"help\"\n qa-dataid=\"field-help\"\n type=\"button\"\n rtPopoverTrigger=\"hover\"\n [attr.aria-label]=\"t().uiHint\"\n [rtPopover]=\"helpTpl\">\n <rt-icon name=\"ico-help\" size=\"sm\" color=\"muted\" />\n </button>\n <ng-template #helpTpl>\n <span rtElem=\"help-popover\" qa-dataid=\"field-help-popover\">{{ help() }}</span>\n </ng-template>\n }\n </span>\n }\n\n <rt-skeleton-wrapper rtElem=\"control\" height=\"var(--rt-input-height)\" [isLoading]=\"loading()\">\n <ng-content />\n </rt-skeleton-wrapper>\n\n <!-- С резервом hint остаётся в потоке даже под ошибкой (скрытый), а сама\n ошибка кладётся в ту же ячейку грида: высота зоны равна большему из\n двух сообщений и не меняется при их подмене. Без резерва — как было,\n одно сообщение за раз, чтобы плотные формы не набирали лишний воздух. -->\n <span rtElem=\"messages\" [rtMod]=\"{ reserved: reserveHintSpace() }\">\n @if (projectedHint() && (reserveHintSpace() || !showError())) {\n <small rtElem=\"hint\" qa-dataid=\"field-hint\" [rtMod]=\"{ hidden: showError() }\" [attr.aria-hidden]=\"showError() ? 'true' : null\">\n <ng-content select=\"[rtFieldHint]\" />\n </small>\n } @else if (hint() && (reserveHintSpace() || !showError())) {\n <small rtElem=\"hint\" qa-dataid=\"field-hint\" [rtMod]=\"{ hidden: showError() }\" [attr.aria-hidden]=\"showError() ? 'true' : null\">\n {{ hint() }}\n </small>\n }\n\n @if (showError()) {\n <small rtElem=\"error\" qa-dataid=\"field-error\">{{ errorText() }}</small>\n }\n </span>\n</ng-container>\n"],"mappings":";;;;;;;;AAWA,IAAa,uBAAb,MAAa,qBAAoB;CAApB;EAAA,KAAA,OAAA,GAAA,mBAAA;GAAA,YAAA;GAAA,SAAA;GAAA,UAAA;GAAA,MAAA;GAAoB,MAAA,CAAA;GAAA,QAAA,GAAA,gBAAA;EAAA,CAAA;CAAA;CAApB;EAAA,KAAA,OAAA,GAAA,qBAAA;GAAA,YAAA;GAAA,SAAA;GAAA,MAAA;GAAoB,cAAA;GAAA,UAAA;GAAA,UAAA;EAAA,CAAA;CAAA;;AAApB,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAoB,YAAA,CAAA;EAHhC,MAAA;EAAU,MAAA,CAAA,EACP,UAAU,gBACb,CAAA;;;ACMD,MAAa,8BAAwE;CACjF,UAAU;CACV,OAAO;CACP,WAAW;CACX,WAAW;CACX,KAAK;CACL,KAAK;CACL,SAAS;AACZ;AAGD,MAAa,uBAA+B;ACG5C,MAAM,YAAoB;AAG1B,IAAI,UAAkB;AAEtB,SAAS,aAAU;CACf,WAAW;CACX,OAAO,YAAY;AACvB;AAyCA,IAAa,mBAAb,MAAa,iBAAgB;CAWhB;CAoCA;CAYA;CA4CT,cAAA;EApGmB,KAAA,IAA4B,OAAO,aAAa;EAEhD,KAAA,mBAAmE,aAAa,mBAAkB,GAAA,YAAA,CAAA,EAAA,WAAA,mBAAA,CAAA,+BAAA,CAAA,CAAA;EAIlG,KAAA,gBAA0D,aAAa,sBAAqB,GAAA,YAAA,CAAA,EAAA,WAAA,gBAAA,CAAA,+BAAA,CAAA,CAAA;EAEtG,KAAA,UAAkB,WAAU;EAGlB,KAAA,WAAwD,UAAmC,YAAW,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;EAMtG,KAAA,aAA6B,aAAa,KAAK,QAAQ;EAGvD,KAAA,YAA4B,eAAuB,KAAK,iBAAgB,CAAE,EAAE,mBAAkB,KAAM,KAAK,SAAQ,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;EAEjH,KAAA,aAA8B,eAAwB,KAAK,iBAAgB,CAAE,EAAE,SAAQ,KAAM,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;EAEnG,KAAA,YAA6B,eAAwB,CAAC,CAAC,KAAK,iBAAgB,CAAE,EAAE,QAAO,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;EAO1F,KAAA,SAA0B,gBAAyB,KAAK,iBAAgB,CAAE,EAAE,YAAW,KAAM,QAAQ,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;EAGxG,KAAA,WAA4B,eAC5B,KAAK,SAAQ,KAAM,KAAK,QAAO,KAAM,CAAC,CAAC,KAAK,iBAAgB,CAAE,EAAE,WAAU,GAC5F,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;EAGkB,KAAA,cAA+B,eAC/B,CAAC,KAAK,SAAQ,KAAM,CAAC,CAAC,KAAK,iBAAgB,CAAE,EAAE,kBACjE,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;EAGQ,KAAA,mBAAmC,eAAsB;GAC9D,MAAM,OAAgC,KAAK,iBAAgB,CAAE,EAAE,OAAM,KAAM;GAC3E,OAAO,OAAQ,OAAO,KAAK,IAAI,CAAC,CAAC,MAAM,KAAM;EACjD,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,mBAAA,CAAA,+BAAA,CAAA,CAAA;EASO,KAAA,oBAAoC,WACzC,eAAoC,4BAA4B,KAAK,iBAAgB,MAAO,EAAE,CAAC;EAIhF,KAAA,YAA4B,eAAsB;GACjE,MAAM,YAAoB,KAAK,iBAAgB;GAC/C,IAAI,CAAC,WACD,OAAO;GAEX,MAAM,MAA0B,KAAK,OAAM,CAAE,CAAC;GAC9C,IAAI,KACA,OAAO;GAEX,OAAO,4BAA4B,aAAa,KAAK,kBAAiB,IAAK;EAC/E,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;EAEiB,KAAA,aAAA;EAEH,KAAA,QAA6B,MAAc,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;EAC9C,KAAA,OAA4B,MAAc,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;EAC7C,KAAA,OAA4B,MAAc,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;EAC7C,KAAA,WAA4D,MAA6B,OAAK;GAAA,GAAA,YAAA,EAAA,WAAA,WAAA,+BAAA,CAAA;GAC1G,WAAW;EAAgB,CAAA;EAEf,KAAA,UAA2D,MAA6B,OAAK;GAAA,GAAA,YAAA,EAAA,WAAA,UAAA,+BAAA,CAAA;GACzG,WAAW;EAAgB,CAAA;EAOf,KAAA,WAA4D,MAA6B,OAAK;GAAA,GAAA,YAAA,EAAA,WAAA,WAAA,+BAAA,CAAA;GAC1G,WAAW;EAAgB,CAAA;EAEf,KAAA,mBAAoE,MAA6B,OAAK;GAAA,GAAA,YAAA,EAAA,WAAA,mBAAA,+BAAA,CAAA;GAClH,WAAW;EAAgB,CAAA;EAEf,KAAA,mBAAoE,MAA6B,OAAK;GAAA,GAAA,YAAA,EAAA,WAAA,mBAAA,+BAAA,CAAA;GAClH,WAAW;EAAgB,CAAA;EAEf,KAAA,SAA8C,MAA8B,CAAA,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;EAI3F,aAAkB;GACd,KAAK,iBAAgB,CAAE,EAAE,YAAY,KAAK,SAAQ,CAAE;EACxD,CAAC;EACD,aAAkB;GACd,KAAK,iBAAgB,CAAE,EAAE,YAAY,KAAK,SAAQ,CAAE;EACxD,CAAC;CACL;CAEO,qBAAkB;EACrB,MAAM,OAA+C,KAAK,iBAAgB;EAE1E,IAAI,QAAQ,KAAK,mBAAkB,MAAO,MACtC,KAAK,gBAAgB,KAAK,OAAO;CAEzC;CAvHS;EAAA,KAAA,OAAA,GAAA,mBAAA;GAAA,YAAA;GAAA,SAAA;GAAA,UAAA;GAAA,MAAA;GAAgB,MAAA,CAAA;GAAA,QAAA,GAAA,gBAAA;EAAA,CAAA;CAAA;CAAhB;EAAA,KAAA,OAAA,GAAA,qBAAA;GAAA,YAAA;GAAA,SAAA;GAAA,MAAA;GAAgB,cAAA;GAAA,UAAA;GAAA,QAAA;IAAA,OAAA;KAAA,mBAAA;KAAA,YAAA;KAAA,UAAA;KAAA,YAAA;KAAA,mBAAA;IAAA;IAAA,MAAA;KAAA,mBAAA;KAAA,YAAA;KAAA,UAAA;KAAA,YAAA;KAAA,mBAAA;IAAA;IAAA,MAAA;KAAA,mBAAA;KAAA,YAAA;KAAA,UAAA;KAAA,YAAA;KAAA,mBAAA;IAAA;IAAA,UAAA;KAAA,mBAAA;KAAA,YAAA;KAAA,UAAA;KAAA,YAAA;KAAA,mBAAA;IAAA;IAAA,SAAA;KAAA,mBAAA;KAAA,YAAA;KAAA,UAAA;KAAA,YAAA;KAAA,mBAAA;IAAA;IAAA,UAAA;KAAA,mBAAA;KAAA,YAAA;KAAA,UAAA;KAAA,YAAA;KAAA,mBAAA;IAAA;IAAA,kBAAA;KAAA,mBAAA;KAAA,YAAA;KAAA,UAAA;KAAA,YAAA;KAAA,mBAAA;IAAA;IAAA,kBAAA;KAAA,mBAAA;KAAA,YAAA;KAAA,UAAA;KAAA,YAAA;KAAA,mBAAA;IAAA;IAAA,QAAA;KAAA,mBAAA;KAAA,YAAA;KAAA,UAAA;KAAA,YAAA;KAAA,mBAAA;IAAA;GAAA;GAAA,MAAA;IAAA,YAAA;KAAA,0BAAA;KAAA,2BAAA;KAAA,0BAAA;KAAA,gCAAA;KAAA,gCAAA;IAAA;IAAA,gBAAA;GAAA;GAAA,SAAA,CAAA;IAAA,cAAA;IAAA,OAAA;IAAA,WAK0E;IAAiB,aAAA;IAAA,UAAA;GAAA,GAAA;IAAA,cAAA;IAAA,OAAA;IAAA,WAI1B;IAAoB,aAAA;IAAA,UAAA;GAAA,CAAA;GAAA,aAAA,CAAA;IAAA,cAAA;IAAA,OAAA;IAAA,WAAA,CAAA,UAAA;IAAA,aAAA;IAAA,UAAA;GAAA,CAAA;GAAA,UAAA;GAAA,UCxFlH;GAoDA,QAAA,CAAA,+nEAAA,o0PAAA;GAAA,cAAA;IAAA;KAAA,MAAA;KAAA,MDWQ;KAAe,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;KAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MACf;KAAkB,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;KAAA;KAAA,SAAA,CAAA,mBAAA,iBAAA;KAAA,UAAA,CAAA,WAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MAClB;KAA0B,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;KAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MAC1B;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;;AAWP,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAgB,YAAA,CAAA;EAxB5B,MAAA;EACa,MAAA,CAAA;GAAA,UAAA;GAAU,iBAGH,wBAAwB;GAAM,eAChC,kBAAkB;GAAI,SAC5B;IAEL;IACA;IACA;IACA;IACA;IACA;GACH;GAAA,MACK;IACF,OAAO;IACP,4BAA4B;IAC5B,6BAA6B;IAC7B,4BAA4B;IAC5B,kCAAkC;IAClC,kCAAkC;GACrC;GAAA,UAAA;GAAA,QAAA,CAAA,+nEAAA,o0PAAA;EAAA,CAAA;CAOkG,CAAA;CAAA,sBAAA,CAAA;CAAA,gBAAA;EAAA,kBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA,GAAA,iBAAA,iBAAiB,GAAA,EAAA,UAAA,KAAA,CAAA;EAAA,CAAA;EAAA,eAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA,GAAA,iBAI1B,oBAAoB,GAAA,EAAA,UAAA,KAAA,CAAA;EAAA,CAAA;EAAA,UAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAKA,YAAU,EAAA,UAAA,KAAA,CAAA;EAAA,CAAA;EAAA,OAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,MAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,MAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,UAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,SAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,UAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,kBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,kBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,QAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;CAAA;AAAA,CAAA"}
@@ -274,6 +274,7 @@ var RtFileCardComponent = class RtFileCardComponent {
274
274
  inputs: [
275
275
  "rtTooltip",
276
276
  "rtTooltipPlacement",
277
+ "rtTooltipShowDelay",
277
278
  "rtTooltipWhenTruncated",
278
279
  "rtTooltipHideOnClick"
279
280
  ],
@@ -1 +1 @@
1
- {"version":3,"file":"rt-tools-ui-kit-v2-file-card.mjs","names":[],"sources":["../../../projects/ui-kit-v2/src/lib/components/file-card/file-card.helper.ts","../../../projects/ui-kit-v2/src/lib/components/file-card/rt-truncate-middle.pipe.ts","../../../projects/ui-kit-v2/src/lib/components/file-card/rt-file-card.component.ts","../../../projects/ui-kit-v2/src/lib/components/file-card/rt-file-card.component.html"],"sourcesContent":["import { IRtIcon } from '@rt-tools/ui-kit-v2/core';\n\n/** Иконка Word-семейства (несколько расширений делят один глиф). */\nconst ICON_WORD: IRtIcon.Name = 'attach-docx-doc';\n\n/** Иконка растровых изображений. */\nconst ICON_IMAGE: IRtIcon.Name = 'attach-jpg-png';\n\n/** Иконка презентаций. */\nconst ICON_PRESENTATION: IRtIcon.Name = 'attach-ppt-pptx';\n\n/** Иконка таблиц Excel-семейства. */\nconst ICON_SPREADSHEET: IRtIcon.Name = 'attach-xlsx-xls';\n\n/** Иконка архивов. */\nconst ICON_ARCHIVE: IRtIcon.Name = 'attach-zip-rar';\n\n/** Иконка web-форматов изображений/видео. */\nconst ICON_WEB_MEDIA: IRtIcon.Name = 'attach-webp';\n\n/**\n * Маппинг расширения файла → имя цветной иконки типа (`attach-*` в rt-icon-реестре).\n */\nconst FILE_TYPE_ICON: Readonly<Record<string, IRtIcon.Name>> = {\n doc: ICON_WORD,\n docx: ICON_WORD,\n odt: ICON_WORD,\n gif: ICON_IMAGE,\n jpg: ICON_IMAGE,\n jpeg: ICON_IMAGE,\n png: ICON_IMAGE,\n pdf: 'attach-pdf',\n ppt: ICON_PRESENTATION,\n pptx: ICON_PRESENTATION,\n txt: 'attach-txt',\n webp: ICON_WEB_MEDIA,\n webm: ICON_WEB_MEDIA,\n xls: ICON_SPREADSHEET,\n xlsx: ICON_SPREADSHEET,\n zip: ICON_ARCHIVE,\n rar: ICON_ARCHIVE,\n msg: 'attach-msg',\n eml: 'attach-eml',\n};\n\n/**\n * Возвращает имя иконки типа файла по его имени (берётся расширение после\n * последней точки). Неизвестное/отсутствующее расширение → нейтральный `attach-other`.\n */\nexport function getFileTypeIcon(fileName: string): IRtIcon.Name {\n const ext: string = fileName.split('.').pop()?.toLowerCase() ?? '';\n return FILE_TYPE_ICON[ext] ?? 'attach-other';\n}\n\n/**\n * Выводит читаемый заголовок из имени файла: отбрасывает расширение и заменяет\n * разделители `_`/`-` на пробелы (CamelCase намеренно НЕ разбивается — это давало\n * бы непредсказуемый результат на произвольных именах). Если после очистки строка\n * пустая (например имя было `.pdf`) — возвращается исходное имя.\n */\nexport function deriveFileTitle(fileName: string): string {\n const withoutExt: string = fileName.replace(/\\.[^./\\\\]+$/, '');\n const cleaned: string = withoutExt.replace(/[_-]+/g, ' ').replace(/\\s+/g, ' ').trim();\n return cleaned || fileName;\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n/** Многоточие, вставляемое в середину строки при усечении. */\nconst ELLIPSIS: string = '...';\n\n/** Длина строки по умолчанию, при которой включается усечение. */\nconst DEFAULT_MAX_LENGTH: number = 32;\n\n/**\n * Усекает длинное имя файла по середине, сохраняя расширение видимым:\n * `10588-1_EvaluationSearchReport.pdf` → `10588-1_EvaluationSearchRe...pdf`.\n *\n * Обычный CSS `text-overflow: ellipsis` режет хвост и теряет расширение —\n * поэтому усечение по середине делается здесь, а не в стилях. Усечение\n * посимвольное (не по ширине контейнера): предсказуемо и без измерений DOM.\n *\n * Pure-pipe: пересчитывается только при смене входа.\n */\n@Pipe({ name: 'rtTruncateMiddle' })\nexport class RtTruncateMiddlePipe implements PipeTransform {\n public transform(value: string | null | undefined, maxLength: number = DEFAULT_MAX_LENGTH): string {\n if (!value) {\n return '';\n }\n if (value.length <= maxLength) {\n return value;\n }\n\n const lastDot: number = value.lastIndexOf('.');\n const hasExt: boolean = lastDot > 0 && lastDot < value.length - 1;\n const ext: string = hasExt ? value.slice(lastDot + 1) : '';\n const base: string = hasExt ? value.slice(0, lastDot) : value;\n\n const headLength: number = maxLength - ext.length - ELLIPSIS.length;\n if (headLength <= 0) {\n // Расширение длиннее лимита — деградируем до простого усечения хвоста.\n return value.slice(0, Math.max(0, maxLength - ELLIPSIS.length)) + ELLIPSIS;\n }\n\n return base.slice(0, headLength) + ELLIPSIS + ext;\n }\n}\n","import { BooleanInput } from '@angular/cdk/coercion';\nimport {\n booleanAttribute,\n computed,\n inject,\n input,\n output,\n ChangeDetectionStrategy,\n Component,\n InputSignal,\n InputSignalWithTransform,\n OutputEmitterRef,\n Signal,\n ViewEncapsulation,\n} from '@angular/core';\n\nimport { BlockDirective, ElemDirective } from '@rt-tools/core';\n\nimport { RT_KIT_LABELS, TRtKitLabelMap } from '@rt-tools/ui-kit-v2/core';\nimport { formatFileSize } from '@rt-tools/ui-kit-v2/core';\n\nimport { RtIconButtonComponent } from '@rt-tools/ui-kit-v2/icon-button';\nimport { IRtIconButton } 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 { RtRadiusDirective } from '@rt-tools/ui-kit-v2/radius';\nimport { RtTooltipDirective } from '@rt-tools/ui-kit-v2/tooltip';\nimport { deriveFileTitle, getFileTypeIcon } from './file-card.helper';\nimport { IRtFileCard } from './rt-file-card.model';\nimport { RtTruncateMiddlePipe } from './rt-truncate-middle.pipe';\n\nconst BEM_BLOCK: string = 'rt-file-card';\n\n/** Иконка типа файла крупнее прочего: по ней карточку находят взглядом. */\nconst ICON_SIZES: Readonly<Record<IRtFileCard.Size, IRtIcon.Size>> = {\n sm: 'sm',\n md: 'xl',\n lg: 'xl',\n};\n\n/** Кнопки действий: в компактной карточке они не должны задавать её высоту. */\nconst ACTION_SIZES: Readonly<Record<IRtFileCard.Size, IRtIconButton.Size>> = {\n sm: 'sm',\n md: 'sm',\n lg: 'md',\n};\n\n/**\n * Карточка загруженного файла: цветная иконка типа, заголовок (выведенный из имени),\n * усечённое по середине имя файла, бейдж размера и опциональные действия справа\n * (скачать / переименовать / удалить).\n *\n * Презентационный компонент: сам ничего не решает, только эмиттит события действий.\n * Кнопки включаются точечно через `showDownload`/`showRemove`/`showRename` — в режиме\n * загрузки обычно нужен только remove, при показе сохранённых файлов — download.\n *\n * `showRename` по умолчанию выключен и сейчас нигде не включается: бэкенд не\n * поддерживает переименование (`original_name` неизменяем после создания). Вход\n * оставлен, чтобы кнопку можно было включить без правки компонента, когда появится API.\n */\n@Component({\n selector: 'rt-file-card',\n templateUrl: './rt-file-card.component.html',\n styleUrls: ['./rt-file-card.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [\n // standalone components / directives\n RtIconComponent,\n RtIconButtonComponent,\n RtTooltipDirective,\n BlockDirective,\n ElemDirective,\n\n // pipes\n RtTruncateMiddlePipe,\n ],\n hostDirectives: [{ directive: RtRadiusDirective, inputs: ['radius'] }],\n host: {\n class: BEM_BLOCK,\n '[class.rt-file-card--sm]': \"size() === 'sm'\",\n '[class.rt-file-card--md]': \"size() === 'md'\",\n '[class.rt-file-card--lg]': \"size() === 'lg'\",\n },\n})\nexport class RtFileCardComponent {\n /** Имя цветной иконки типа файла по расширению. */\n protected readonly t: Signal<TRtKitLabelMap> = inject(RT_KIT_LABELS);\n\n protected readonly iconName: Signal<IRtIcon.Name> = computed((): IRtIcon.Name => getFileTypeIcon(this.name()));\n\n /** Заголовок карточки, выведенный из имени файла (без расширения, разделители → пробелы). */\n protected readonly title: Signal<string> = computed((): string => deriveFileTitle(this.name()));\n\n /** Отформатированный размер; пустая строка → размер неизвестен. */\n protected readonly sizeLabel: Signal<string> = computed((): string => formatFileSize(this.sizeBytes()));\n\n /** Показывать ли бейдж размера. */\n protected readonly hasSize: Signal<boolean> = computed((): boolean => this.sizeLabel() !== '');\n\n /** Размер иконки типа файла — маппится от размера карточки. */\n protected readonly iconSize: Signal<IRtIcon.Size> = computed((): IRtIcon.Size => ICON_SIZES[this.size()]);\n\n /** Размер кнопок действий — маппится от размера карточки. */\n protected readonly actionSize: Signal<IRtIconButton.Size> = computed((): IRtIconButton.Size => ACTION_SIZES[this.size()]);\n\n /** Имя файла с расширением — обязательно. */\n public readonly name: InputSignal<string> = input.required<string>();\n\n /** Размер в байтах. `null` → бейдж размера скрыт. */\n public readonly sizeBytes: InputSignal<number | null> = input<number | null>(null);\n\n /** Плотность карточки: `sm` для тесных мест вроде композера, `lg` — для страниц. */\n public readonly size: InputSignal<IRtFileCard.Size> = input<IRtFileCard.Size>('md');\n\n /** Показывать кнопку скачивания. */\n public readonly showDownload: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n /** Показывать кнопку удаления. */\n public readonly showRemove: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n /** Показывать кнопку переименования (отключена до появления API на бэке). */\n public readonly showRename: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n /** Блокирует кнопки действий (например, во время сабмита формы). */\n public readonly disabled: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n /** Запрошено скачивание файла. */\n public readonly download: OutputEmitterRef<void> = output<void>();\n\n /** Запрошено удаление файла. */\n public readonly removed: OutputEmitterRef<void> = output<void>();\n\n /** Запрошено переименование файла. */\n public readonly rename: OutputEmitterRef<void> = output<void>();\n\n protected onDownload(): void {\n this.download.emit();\n }\n\n protected onRemove(): void {\n this.removed.emit();\n }\n\n protected onRename(): void {\n this.rename.emit();\n }\n}\n","<ng-container rtBlock=\"rt-file-card\">\n <rt-icon rtElem=\"icon\" [size]=\"iconSize()\" [name]=\"iconName()\" />\n\n <span rtElem=\"text\">\n <span rtElem=\"title\" qa-dataid=\"file-card-title\" [rtTooltip]=\"title()\">{{ title() }}</span>\n <span rtElem=\"name\" qa-dataid=\"file-card-name\" [rtTooltip]=\"name()\">{{ name() | rtTruncateMiddle }}</span>\n </span>\n\n <span rtElem=\"meta\">\n @if (hasSize()) {\n <span rtElem=\"size\" qa-dataid=\"file-card-size\">{{ sizeLabel() }}</span>\n }\n\n @if (showDownload()) {\n <rt-icon-button\n rtElem=\"action\"\n qa-dataid=\"file-card-download\"\n icon=\"ico-download\"\n variant=\"ghost\"\n [ariaLabel]=\"t().uiDownload\"\n [tooltip]=\"t().uiDownload\"\n [size]=\"actionSize()\"\n [disabled]=\"disabled()\"\n (clicked)=\"onDownload()\" />\n }\n\n @if (showRename()) {\n <rt-icon-button\n rtElem=\"action\"\n qa-dataid=\"file-card-rename\"\n icon=\"ico-edit\"\n variant=\"ghost\"\n [ariaLabel]=\"t().uiRename\"\n [tooltip]=\"t().uiRename\"\n [size]=\"actionSize()\"\n [disabled]=\"disabled()\"\n (clicked)=\"onRename()\" />\n }\n\n @if (showRemove()) {\n <rt-icon-button\n rtElem=\"action\"\n qa-dataid=\"file-card-remove\"\n icon=\"ico-trash\"\n variant=\"ghost\"\n [ariaLabel]=\"t().uiRemove\"\n [tooltip]=\"t().uiRemove\"\n [size]=\"actionSize()\"\n [disabled]=\"disabled()\"\n (clicked)=\"onRemove()\" />\n }\n </span>\n</ng-container>\n"],"mappings":";;;;;;;;;AAGA,MAAM,YAA0B;AAGhC,MAAM,aAA2B;AAGjC,MAAM,oBAAkC;AAGxC,MAAM,mBAAiC;AAGvC,MAAM,eAA6B;AAGnC,MAAM,iBAA+B;AAKrC,MAAM,iBAAyD;CAC3D,KAAK;CACL,MAAM;CACN,KAAK;CACL,KAAK;CACL,KAAK;CACL,MAAM;CACN,KAAK;CACL,KAAK;CACL,KAAK;CACL,MAAM;CACN,KAAK;CACL,MAAM;CACN,MAAM;CACN,KAAK;CACL,MAAM;CACN,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;AACR;AAMD,SAAgB,gBAAgB,UAAgB;CAC5C,MAAM,MAAc,SAAS,MAAM,GAAG,CAAC,CAAC,IAAG,CAAE,EAAE,YAAW,KAAM;CAChE,OAAO,eAAe,QAAQ;AAClC;AAQA,SAAgB,gBAAgB,UAAgB;CAG5C,OAF2B,SAAS,QAAQ,eAAe,EACnC,CAAA,CAAW,QAAQ,UAAU,GAAG,CAAC,CAAC,QAAQ,QAAQ,GAAG,CAAC,CAAC,KACxE,KAAW;AACtB;AC7DA,MAAM,WAAmB;AAGzB,MAAM,qBAA6B;AAanC,IAAa,uBAAb,MAAa,qBAAoB;CACtB,UAAU,OAAkC,YAAoB,oBAAkB;EACrF,IAAI,CAAC,OACD,OAAO;EAEX,IAAI,MAAM,UAAU,WAChB,OAAO;EAGX,MAAM,UAAkB,MAAM,YAAY,GAAG;EAC7C,MAAM,SAAkB,UAAU,KAAK,UAAU,MAAM,SAAS;EAChE,MAAM,MAAc,SAAS,MAAM,MAAM,UAAU,CAAC,IAAI;EACxD,MAAM,OAAe,SAAS,MAAM,MAAM,GAAG,OAAO,IAAI;EAExD,MAAM,aAAqB,YAAY,IAAI,SAAS;EACpD,IAAI,cAAc,GAEd,OAAO,MAAM,MAAM,GAAG,KAAK,IAAI,GAAG,YAAY,CAAe,CAAC,IAAI;EAGtE,OAAO,KAAK,MAAM,GAAG,UAAU,IAAI,WAAW;CAClD;CArBS;EAAA,KAAA,OAAA,GAAA,mBAAA;GAAA,YAAA;GAAA,SAAA;GAAA,UAAA;GAAA,MAAA;GAAoB,MAAA,CAAA;GAAA,QAAA,GAAA,gBAAA;EAAA,CAAA;CAAA;CAApB;EAAA,KAAA,QAAA,GAAA,gBAAA;GAAA,YAAA;GAAA,SAAA;GAAA,UAAA;GAAA,MAAA;GAAoB,cAAA;GAAA,MAAA;EAAA,CAAA;CAAA;;AAApB,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAoB,YAAA,CAAA;EADhC,MAAA;EAAK,MAAA,CAAA,EAAE,MAAM,mBAAkB,CAAE;;;ACalC,MAAM,YAAoB;AAG1B,MAAM,aAA+D;CACjE,IAAI;CACJ,IAAI;CACJ,IAAI;AACP;AAGD,MAAM,eAAuE;CACzE,IAAI;CACJ,IAAI;CACJ,IAAI;AACP;AAwCD,IAAa,sBAAb,MAAa,oBAAmB;CAzBhC,cAAA;EA2BuB,KAAA,IAA4B,OAAO,aAAa;EAEhD,KAAA,WAAiC,eAA6B,gBAAgB,KAAK,KAAI,CAAE,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;EAG3F,KAAA,QAAwB,eAAuB,gBAAgB,KAAK,KAAI,CAAE,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;EAG5E,KAAA,YAA4B,eAAuB,eAAe,KAAK,UAAS,CAAE,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;EAGpF,KAAA,UAA2B,eAAwB,KAAK,UAAS,MAAO,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;EAG3E,KAAA,WAAiC,eAA6B,WAAW,KAAK,KAAI,IAAI,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;EAGtF,KAAA,aAAyC,eAAmC,aAAa,KAAK,KAAI,IAAI,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;EAGzG,KAAA,OAA4B,MAAM,SAAkB,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;EAGpD,KAAA,YAAwC,MAAqB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;EAGlE,KAAA,OAAsC,MAAwB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;EAGnE,KAAA,eAAgE,MAA6B,OAAK;GAAA,GAAA,YAAA,EAAA,WAAA,eAAA,+BAAA,CAAA;GAC9G,WAAW;EAAgB,CAAA;EAIf,KAAA,aAA8D,MAA6B,OAAK;GAAA,GAAA,YAAA,EAAA,WAAA,aAAA,+BAAA,CAAA;GAC5G,WAAW;EAAgB,CAAA;EAIf,KAAA,aAA8D,MAA6B,OAAK;GAAA,GAAA,YAAA,EAAA,WAAA,aAAA,+BAAA,CAAA;GAC5G,WAAW;EAAgB,CAAA;EAIf,KAAA,WAA4D,MAA6B,OAAK;GAAA,GAAA,YAAA,EAAA,WAAA,WAAA,+BAAA,CAAA;GAC1G,WAAW;EAAgB,CAAA;EAIf,KAAA,WAAmC,OAAM;EAGzC,KAAA,UAAkC,OAAM;EAGxC,KAAA,SAAiC,OAAM;CAa1D;CAXa,aAAU;EAChB,KAAK,SAAS,KAAI;CACtB;CAEU,WAAQ;EACd,KAAK,QAAQ,KAAI;CACrB;CAEU,WAAQ;EACd,KAAK,OAAO,KAAI;CACpB;CArES;EAAA,KAAA,OAAA,GAAA,mBAAA;GAAA,YAAA;GAAA,SAAA;GAAA,UAAA;GAAA,MAAA;GAAmB,MAAA,CAAA;GAAA,QAAA,GAAA,gBAAA;EAAA,CAAA;CAAA;CAAnB;EAAA,KAAA,OAAA,GAAA,qBAAA;GAAA,YAAA;GAAA,SAAA;GAAA,MAAA;GAAmB,cAAA;GAAA,UAAA;GAAA,QAAA;IAAA,MAAA;KAAA,mBAAA;KAAA,YAAA;KAAA,UAAA;KAAA,YAAA;KAAA,mBAAA;IAAA;IAAA,WAAA;KAAA,mBAAA;KAAA,YAAA;KAAA,UAAA;KAAA,YAAA;KAAA,mBAAA;IAAA;IAAA,MAAA;KAAA,mBAAA;KAAA,YAAA;KAAA,UAAA;KAAA,YAAA;KAAA,mBAAA;IAAA;IAAA,cAAA;KAAA,mBAAA;KAAA,YAAA;KAAA,UAAA;KAAA,YAAA;KAAA,mBAAA;IAAA;IAAA,YAAA;KAAA,mBAAA;KAAA,YAAA;KAAA,UAAA;KAAA,YAAA;KAAA,mBAAA;IAAA;IAAA,YAAA;KAAA,mBAAA;KAAA,YAAA;KAAA,UAAA;KAAA,YAAA;KAAA,mBAAA;IAAA;IAAA,UAAA;KAAA,mBAAA;KAAA,YAAA;KAAA,UAAA;KAAA,YAAA;KAAA,mBAAA;IAAA;GAAA;GAAA,SAAA;IAAA,UAAA;IAAA,SAAA;IAAA,QAAA;GAAA;GAAA,MAAA;IAAA,YAAA;KAAA,0BAAA;KAAA,0BAAA;KAAA,0BAAA;IAAA;IAAA,gBAAA;GAAA;GAAA,gBAAA,CAAA;IAAA,WAAA,GAAA;IAAA,QAAA,CAAA,UAAA,QAAA;GAAA,CAAA;GAAA,UAAA;GAAA,UCrFhC;GAqDA,QAAA,CAAA,ytGAAA;GAAA,cAAA;IAAA;KAAA,MAAA;KAAA,MDeQ;KAAe,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;KAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MACf;KAAqB,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;KAAA;KAAA,SAAA,CAAA,SAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MACrB;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,MAGb;KAAoB,MAAA;IAAA;GAAA;GAAA,iBAAA,GAAA,wBAAA;GAAA,eAAA,GAAA,kBAAA;EAAA,CAAA;CAAA;;AAUf,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAmB,YAAA,CAAA;EAzB/B,MAAA;EACa,MAAA,CAAA;GAAA,UAAA;GAAc,iBAGP,wBAAwB;GAAM,eAChC,kBAAkB;GAAI,SAC5B;IAEL;IACA;IACA;IACA;IACA;IAGA;GACH;GAAA,gBACe,CAAC;IAAE,WAAW;IAAmB,QAAQ,CAAC,QAAQ;GAAC,CAAE;GAAC,MAChE;IACF,OAAO;IACP,4BAA4B;IAC5B,4BAA4B;IAC5B,4BAA4B;GAC/B;GAAA,UAAA;GAAA,QAAA,CAAA,ytGAAA;EAAA,CAAA"}
1
+ {"version":3,"file":"rt-tools-ui-kit-v2-file-card.mjs","names":[],"sources":["../../../projects/ui-kit-v2/src/lib/components/file-card/file-card.helper.ts","../../../projects/ui-kit-v2/src/lib/components/file-card/rt-truncate-middle.pipe.ts","../../../projects/ui-kit-v2/src/lib/components/file-card/rt-file-card.component.ts","../../../projects/ui-kit-v2/src/lib/components/file-card/rt-file-card.component.html"],"sourcesContent":["import { IRtIcon } from '@rt-tools/ui-kit-v2/core';\n\n/** Иконка Word-семейства (несколько расширений делят один глиф). */\nconst ICON_WORD: IRtIcon.Name = 'attach-docx-doc';\n\n/** Иконка растровых изображений. */\nconst ICON_IMAGE: IRtIcon.Name = 'attach-jpg-png';\n\n/** Иконка презентаций. */\nconst ICON_PRESENTATION: IRtIcon.Name = 'attach-ppt-pptx';\n\n/** Иконка таблиц Excel-семейства. */\nconst ICON_SPREADSHEET: IRtIcon.Name = 'attach-xlsx-xls';\n\n/** Иконка архивов. */\nconst ICON_ARCHIVE: IRtIcon.Name = 'attach-zip-rar';\n\n/** Иконка web-форматов изображений/видео. */\nconst ICON_WEB_MEDIA: IRtIcon.Name = 'attach-webp';\n\n/**\n * Маппинг расширения файла → имя цветной иконки типа (`attach-*` в rt-icon-реестре).\n */\nconst FILE_TYPE_ICON: Readonly<Record<string, IRtIcon.Name>> = {\n doc: ICON_WORD,\n docx: ICON_WORD,\n odt: ICON_WORD,\n gif: ICON_IMAGE,\n jpg: ICON_IMAGE,\n jpeg: ICON_IMAGE,\n png: ICON_IMAGE,\n pdf: 'attach-pdf',\n ppt: ICON_PRESENTATION,\n pptx: ICON_PRESENTATION,\n txt: 'attach-txt',\n webp: ICON_WEB_MEDIA,\n webm: ICON_WEB_MEDIA,\n xls: ICON_SPREADSHEET,\n xlsx: ICON_SPREADSHEET,\n zip: ICON_ARCHIVE,\n rar: ICON_ARCHIVE,\n msg: 'attach-msg',\n eml: 'attach-eml',\n};\n\n/**\n * Возвращает имя иконки типа файла по его имени (берётся расширение после\n * последней точки). Неизвестное/отсутствующее расширение → нейтральный `attach-other`.\n */\nexport function getFileTypeIcon(fileName: string): IRtIcon.Name {\n const ext: string = fileName.split('.').pop()?.toLowerCase() ?? '';\n return FILE_TYPE_ICON[ext] ?? 'attach-other';\n}\n\n/**\n * Выводит читаемый заголовок из имени файла: отбрасывает расширение и заменяет\n * разделители `_`/`-` на пробелы (CamelCase намеренно НЕ разбивается — это давало\n * бы непредсказуемый результат на произвольных именах). Если после очистки строка\n * пустая (например имя было `.pdf`) — возвращается исходное имя.\n */\nexport function deriveFileTitle(fileName: string): string {\n const withoutExt: string = fileName.replace(/\\.[^./\\\\]+$/, '');\n const cleaned: string = withoutExt.replace(/[_-]+/g, ' ').replace(/\\s+/g, ' ').trim();\n return cleaned || fileName;\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n/** Многоточие, вставляемое в середину строки при усечении. */\nconst ELLIPSIS: string = '...';\n\n/** Длина строки по умолчанию, при которой включается усечение. */\nconst DEFAULT_MAX_LENGTH: number = 32;\n\n/**\n * Усекает длинное имя файла по середине, сохраняя расширение видимым:\n * `10588-1_EvaluationSearchReport.pdf` → `10588-1_EvaluationSearchRe...pdf`.\n *\n * Обычный CSS `text-overflow: ellipsis` режет хвост и теряет расширение —\n * поэтому усечение по середине делается здесь, а не в стилях. Усечение\n * посимвольное (не по ширине контейнера): предсказуемо и без измерений DOM.\n *\n * Pure-pipe: пересчитывается только при смене входа.\n */\n@Pipe({ name: 'rtTruncateMiddle' })\nexport class RtTruncateMiddlePipe implements PipeTransform {\n public transform(value: string | null | undefined, maxLength: number = DEFAULT_MAX_LENGTH): string {\n if (!value) {\n return '';\n }\n if (value.length <= maxLength) {\n return value;\n }\n\n const lastDot: number = value.lastIndexOf('.');\n const hasExt: boolean = lastDot > 0 && lastDot < value.length - 1;\n const ext: string = hasExt ? value.slice(lastDot + 1) : '';\n const base: string = hasExt ? value.slice(0, lastDot) : value;\n\n const headLength: number = maxLength - ext.length - ELLIPSIS.length;\n if (headLength <= 0) {\n // Расширение длиннее лимита — деградируем до простого усечения хвоста.\n return value.slice(0, Math.max(0, maxLength - ELLIPSIS.length)) + ELLIPSIS;\n }\n\n return base.slice(0, headLength) + ELLIPSIS + ext;\n }\n}\n","import { BooleanInput } from '@angular/cdk/coercion';\nimport {\n booleanAttribute,\n computed,\n inject,\n input,\n output,\n ChangeDetectionStrategy,\n Component,\n InputSignal,\n InputSignalWithTransform,\n OutputEmitterRef,\n Signal,\n ViewEncapsulation,\n} from '@angular/core';\n\nimport { BlockDirective, ElemDirective } from '@rt-tools/core';\n\nimport { RT_KIT_LABELS, TRtKitLabelMap } from '@rt-tools/ui-kit-v2/core';\nimport { formatFileSize } from '@rt-tools/ui-kit-v2/core';\n\nimport { RtIconButtonComponent } from '@rt-tools/ui-kit-v2/icon-button';\nimport { IRtIconButton } 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 { RtRadiusDirective } from '@rt-tools/ui-kit-v2/radius';\nimport { RtTooltipDirective } from '@rt-tools/ui-kit-v2/tooltip';\nimport { deriveFileTitle, getFileTypeIcon } from './file-card.helper';\nimport { IRtFileCard } from './rt-file-card.model';\nimport { RtTruncateMiddlePipe } from './rt-truncate-middle.pipe';\n\nconst BEM_BLOCK: string = 'rt-file-card';\n\n/** Иконка типа файла крупнее прочего: по ней карточку находят взглядом. */\nconst ICON_SIZES: Readonly<Record<IRtFileCard.Size, IRtIcon.Size>> = {\n sm: 'sm',\n md: 'xl',\n lg: 'xl',\n};\n\n/** Кнопки действий: в компактной карточке они не должны задавать её высоту. */\nconst ACTION_SIZES: Readonly<Record<IRtFileCard.Size, IRtIconButton.Size>> = {\n sm: 'sm',\n md: 'sm',\n lg: 'md',\n};\n\n/**\n * Карточка загруженного файла: цветная иконка типа, заголовок (выведенный из имени),\n * усечённое по середине имя файла, бейдж размера и опциональные действия справа\n * (скачать / переименовать / удалить).\n *\n * Презентационный компонент: сам ничего не решает, только эмиттит события действий.\n * Кнопки включаются точечно через `showDownload`/`showRemove`/`showRename` — в режиме\n * загрузки обычно нужен только remove, при показе сохранённых файлов — download.\n *\n * `showRename` по умолчанию выключен и сейчас нигде не включается: бэкенд не\n * поддерживает переименование (`original_name` неизменяем после создания). Вход\n * оставлен, чтобы кнопку можно было включить без правки компонента, когда появится API.\n */\n@Component({\n selector: 'rt-file-card',\n templateUrl: './rt-file-card.component.html',\n styleUrls: ['./rt-file-card.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [\n // standalone components / directives\n RtIconComponent,\n RtIconButtonComponent,\n RtTooltipDirective,\n BlockDirective,\n ElemDirective,\n\n // pipes\n RtTruncateMiddlePipe,\n ],\n hostDirectives: [{ directive: RtRadiusDirective, inputs: ['radius'] }],\n host: {\n class: BEM_BLOCK,\n '[class.rt-file-card--sm]': \"size() === 'sm'\",\n '[class.rt-file-card--md]': \"size() === 'md'\",\n '[class.rt-file-card--lg]': \"size() === 'lg'\",\n },\n})\nexport class RtFileCardComponent {\n /** Имя цветной иконки типа файла по расширению. */\n protected readonly t: Signal<TRtKitLabelMap> = inject(RT_KIT_LABELS);\n\n protected readonly iconName: Signal<IRtIcon.Name> = computed((): IRtIcon.Name => getFileTypeIcon(this.name()));\n\n /** Заголовок карточки, выведенный из имени файла (без расширения, разделители → пробелы). */\n protected readonly title: Signal<string> = computed((): string => deriveFileTitle(this.name()));\n\n /** Отформатированный размер; пустая строка → размер неизвестен. */\n protected readonly sizeLabel: Signal<string> = computed((): string => formatFileSize(this.sizeBytes()));\n\n /** Показывать ли бейдж размера. */\n protected readonly hasSize: Signal<boolean> = computed((): boolean => this.sizeLabel() !== '');\n\n /** Размер иконки типа файла — маппится от размера карточки. */\n protected readonly iconSize: Signal<IRtIcon.Size> = computed((): IRtIcon.Size => ICON_SIZES[this.size()]);\n\n /** Размер кнопок действий — маппится от размера карточки. */\n protected readonly actionSize: Signal<IRtIconButton.Size> = computed((): IRtIconButton.Size => ACTION_SIZES[this.size()]);\n\n /** Имя файла с расширением — обязательно. */\n public readonly name: InputSignal<string> = input.required<string>();\n\n /** Размер в байтах. `null` → бейдж размера скрыт. */\n public readonly sizeBytes: InputSignal<number | null> = input<number | null>(null);\n\n /** Плотность карточки: `sm` для тесных мест вроде композера, `lg` — для страниц. */\n public readonly size: InputSignal<IRtFileCard.Size> = input<IRtFileCard.Size>('md');\n\n /** Показывать кнопку скачивания. */\n public readonly showDownload: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n /** Показывать кнопку удаления. */\n public readonly showRemove: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n /** Показывать кнопку переименования (отключена до появления API на бэке). */\n public readonly showRename: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n /** Блокирует кнопки действий (например, во время сабмита формы). */\n public readonly disabled: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n /** Запрошено скачивание файла. */\n public readonly download: OutputEmitterRef<void> = output<void>();\n\n /** Запрошено удаление файла. */\n public readonly removed: OutputEmitterRef<void> = output<void>();\n\n /** Запрошено переименование файла. */\n public readonly rename: OutputEmitterRef<void> = output<void>();\n\n protected onDownload(): void {\n this.download.emit();\n }\n\n protected onRemove(): void {\n this.removed.emit();\n }\n\n protected onRename(): void {\n this.rename.emit();\n }\n}\n","<ng-container rtBlock=\"rt-file-card\">\n <rt-icon rtElem=\"icon\" [size]=\"iconSize()\" [name]=\"iconName()\" />\n\n <span rtElem=\"text\">\n <span rtElem=\"title\" qa-dataid=\"file-card-title\" [rtTooltip]=\"title()\">{{ title() }}</span>\n <span rtElem=\"name\" qa-dataid=\"file-card-name\" [rtTooltip]=\"name()\">{{ name() | rtTruncateMiddle }}</span>\n </span>\n\n <span rtElem=\"meta\">\n @if (hasSize()) {\n <span rtElem=\"size\" qa-dataid=\"file-card-size\">{{ sizeLabel() }}</span>\n }\n\n @if (showDownload()) {\n <rt-icon-button\n rtElem=\"action\"\n qa-dataid=\"file-card-download\"\n icon=\"ico-download\"\n variant=\"ghost\"\n [ariaLabel]=\"t().uiDownload\"\n [tooltip]=\"t().uiDownload\"\n [size]=\"actionSize()\"\n [disabled]=\"disabled()\"\n (clicked)=\"onDownload()\" />\n }\n\n @if (showRename()) {\n <rt-icon-button\n rtElem=\"action\"\n qa-dataid=\"file-card-rename\"\n icon=\"ico-edit\"\n variant=\"ghost\"\n [ariaLabel]=\"t().uiRename\"\n [tooltip]=\"t().uiRename\"\n [size]=\"actionSize()\"\n [disabled]=\"disabled()\"\n (clicked)=\"onRename()\" />\n }\n\n @if (showRemove()) {\n <rt-icon-button\n rtElem=\"action\"\n qa-dataid=\"file-card-remove\"\n icon=\"ico-trash\"\n variant=\"ghost\"\n [ariaLabel]=\"t().uiRemove\"\n [tooltip]=\"t().uiRemove\"\n [size]=\"actionSize()\"\n [disabled]=\"disabled()\"\n (clicked)=\"onRemove()\" />\n }\n </span>\n</ng-container>\n"],"mappings":"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"}
@@ -107,7 +107,7 @@ var RtFilterControlComponent = class RtFilterControlComponent {
107
107
  outputs: { valueChange: "valueChange" },
108
108
  host: { classAttribute: "rt-filter-control" },
109
109
  ngImport: i0,
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",
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 [ngModelOptions]=\"{ standalone: true }\"\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
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
  {
@@ -141,6 +141,7 @@ var RtFilterControlComponent = class RtFilterControlComponent {
141
141
  "options",
142
142
  "placeholder",
143
143
  "iconLeft",
144
+ "floatingLabel",
144
145
  "filter",
145
146
  "filterPlaceholder",
146
147
  "panelWidth",
@@ -160,7 +161,10 @@ var RtFilterControlComponent = class RtFilterControlComponent {
160
161
  "ariaLabel",
161
162
  "size",
162
163
  "disabled",
163
- "fullWidth"
164
+ "fullWidth",
165
+ "layout",
166
+ "appearance",
167
+ "checkmark"
164
168
  ],
165
169
  outputs: ["valueChange", "valuesChange"]
166
170
  }
@@ -187,7 +191,7 @@ i0.ɵɵngDeclareClassMetadata({
187
191
  RtToggleButtonGroupComponent
188
192
  ],
189
193
  host: { class: BEM_BLOCK },
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",
194
+ 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 [ngModelOptions]=\"{ standalone: true }\"\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
195
  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
196
  }]
193
197
  }],
@@ -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 [ngModelOptions]=\"{ standalone: true }\"\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,5 +1,5 @@
1
1
  import * as i0 from "@angular/core";
2
- import { DestroyRef, Directive, booleanAttribute, computed, inject, input, signal } from "@angular/core";
2
+ import { DestroyRef, Directive, booleanAttribute, computed, effect, inject, input, numberAttribute, output, signal, untracked } from "@angular/core";
3
3
  import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
4
4
  import { NgControl, Validators } from "@angular/forms";
5
5
  const PRISTINE_STATE = {
@@ -54,6 +54,7 @@ var RtFormControlBase = class RtFormControlBase {
54
54
  this.#missingRequired = computed(() => this.#requiredAssigned() && !this.#state().required && this.#state().interacted && !this.hasValue(), ...ngDevMode ? [{ debugName: "#missingRequired" }] : /* istanbul ignore next */ []);
55
55
  this.isInvalid = computed(() => this.#state().invalid || this.#missingRequired(), ...ngDevMode ? [{ debugName: "isInvalid" }] : /* istanbul ignore next */ []);
56
56
  this.showClearButton = computed(() => this.clearable() && this.hasValue() && !this.isDisabled(), ...ngDevMode ? [{ debugName: "showClearButton" }] : /* istanbul ignore next */ []);
57
+ this.clearTabIndex = computed(() => this.clearTabbable() ? 0 : -1, ...ngDevMode ? [{ debugName: "clearTabIndex" }] : /* istanbul ignore next */ []);
57
58
  this.size = input("md", ...ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []);
58
59
  this.disabled = input(false, {
59
60
  ...ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {},
@@ -65,6 +66,11 @@ var RtFormControlBase = class RtFormControlBase {
65
66
  ...ngDevMode ? { debugName: "clearable" } : /* istanbul ignore next */ {},
66
67
  transform: booleanAttribute
67
68
  });
69
+ this.clearTabbable = input(false, {
70
+ ...ngDevMode ? { debugName: "clearTabbable" } : /* istanbul ignore next */ {},
71
+ transform: booleanAttribute
72
+ });
73
+ this.fieldLabelInside = false;
68
74
  this.bordered = input(true, {
69
75
  ...ngDevMode ? { debugName: "bordered" } : /* istanbul ignore next */ {},
70
76
  transform: booleanAttribute
@@ -72,6 +78,7 @@ var RtFormControlBase = class RtFormControlBase {
72
78
  this.effectiveControlId = computed(() => this.controlId() ?? this.#controlIdAssigned(), ...ngDevMode ? [{ debugName: "effectiveControlId" }] : /* istanbul ignore next */ []);
73
79
  this.isReadonly = this.#readonlyAssigned.asReadonly();
74
80
  this.invalid = computed(() => this.isInvalid(), ...ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []);
81
+ this.cleared = output();
75
82
  this.errors = computed(() => this.#state().errors ?? (this.#missingRequired() ? { required: true } : null), ...ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []);
76
83
  this.required = computed(() => this.#state().required || this.#requiredAssigned(), ...ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []);
77
84
  this.value = signal(this.getEmptyValue(), ...ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []);
@@ -117,6 +124,7 @@ var RtFormControlBase = class RtFormControlBase {
117
124
  this.#onChange(this.value());
118
125
  this.#onTouched();
119
126
  this.focusAfterClear();
127
+ this.cleared.emit();
120
128
  }
121
129
  clearValue() {
122
130
  this.value.set(this.getEmptyValue());
@@ -179,6 +187,13 @@ var RtFormControlBase = class RtFormControlBase {
179
187
  isRequired: false,
180
188
  transformFunction: null
181
189
  },
190
+ clearTabbable: {
191
+ classPropertyName: "clearTabbable",
192
+ publicName: "clearTabbable",
193
+ isSignal: true,
194
+ isRequired: false,
195
+ transformFunction: null
196
+ },
182
197
  bordered: {
183
198
  classPropertyName: "bordered",
184
199
  publicName: "bordered",
@@ -187,6 +202,7 @@ var RtFormControlBase = class RtFormControlBase {
187
202
  transformFunction: null
188
203
  }
189
204
  },
205
+ outputs: { cleared: "cleared" },
190
206
  ngImport: i0
191
207
  });
192
208
  }
@@ -239,6 +255,14 @@ i0.ɵɵngDeclareClassMetadata({
239
255
  required: false
240
256
  }]
241
257
  }],
258
+ clearTabbable: [{
259
+ type: i0.Input,
260
+ args: [{
261
+ isSignal: true,
262
+ alias: "clearTabbable",
263
+ required: false
264
+ }]
265
+ }],
242
266
  bordered: [{
243
267
  type: i0.Input,
244
268
  args: [{
@@ -246,9 +270,36 @@ i0.ɵɵngDeclareClassMetadata({
246
270
  alias: "bordered",
247
271
  required: false
248
272
  }]
273
+ }],
274
+ cleared: [{
275
+ type: i0.Output,
276
+ args: ["cleared"]
249
277
  }]
250
278
  }
251
279
  });
252
- export { RtFormControlBase };
280
+ function rtNotchWidth(label) {
281
+ const width = signal(0, ...ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []);
282
+ let watch = null;
283
+ effect(() => {
284
+ const element = label()?.nativeElement;
285
+ watch?.disconnect();
286
+ watch = null;
287
+ if (element === void 0) {
288
+ untracked(() => width.set(0));
289
+ return;
290
+ }
291
+ if (typeof ResizeObserver === "undefined") return;
292
+ watch = new ResizeObserver((entries) => width.set(entries[0]?.borderBoxSize[0]?.inlineSize ?? element.offsetWidth));
293
+ watch.observe(element, { box: "border-box" });
294
+ });
295
+ inject(DestroyRef).onDestroy(() => watch?.disconnect());
296
+ return width.asReadonly();
297
+ }
298
+ function rtMaxlength(value) {
299
+ if (value === null || value === void 0 || value === "") return null;
300
+ const limit = numberAttribute(value, NaN);
301
+ return Number.isInteger(limit) && limit >= 0 ? limit : null;
302
+ }
303
+ export { RtFormControlBase, rtMaxlength, rtNotchWidth };
253
304
 
254
305
  //# sourceMappingURL=rt-tools-ui-kit-v2-form-control.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"rt-tools-ui-kit-v2-form-control.mjs","names":[],"sources":["../../../projects/ui-kit-v2/src/lib/components/form-control/rt-form-control.base.ts"],"sourcesContent":["import { BooleanInput } from '@angular/cdk/coercion';\nimport {\n booleanAttribute,\n computed,\n DestroyRef,\n Directive,\n inject,\n input,\n InputSignal,\n InputSignalWithTransform,\n OnInit,\n Signal,\n signal,\n WritableSignal,\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { AbstractControl, ControlValueAccessor, NgControl, ValidationErrors, Validators } from '@angular/forms';\n\nimport { IRtInput } from '@rt-tools/ui-kit-v2/core';\n\n/**\n * Базовый класс input-семейства common/ui. Владеет единым CVA-ядром:\n * - self-injected `NgControl` + ручная привязка `valueAccessor` (без\n * `NG_VALUE_ACCESSOR` provider — иначе NG0200 cycle при self-reference),\n * что даёт доступ к `control.invalid/touched` для авто-подсветки ошибки;\n * - сигнал значения `value`, состояния `isDisabled`/`isInvalid` (отключение\n * складывается из входа `disabled` и слова формы — см. `isDisabled`);\n * - общие инпуты `size`/`disabled`/`controlId`/`ariaLabel`/`clearable`;\n * - оркестрацию очистки (`clear`) через абстрактные хуки конкретного поля.\n *\n * Конкретные поля реализуют `getEmptyValue`/`hasValue`/`focusAfterClear`. Поля\n * с отдельным display-состоянием (autocomplete, input-number) дополнительно\n * переопределяют `writeValue`/`clearValue`, синхронизируя свой display-сигнал.\n */\n/**\n * Состояние формы, каким его видит поле: негодность, ошибки и обязательность.\n *\n * Собрано одной записью затем, что источников у него два и говорят они по-разному. Прежняя\n * привязка рассылает состояние событиями контрола, сигнальная — не рассылает вовсе и отдаёт\n * его чтениями своего переходника. Порознь эти два пути дали бы полю два набора сигналов, и\n * обёртке пришлось бы знать, какой из них живой.\n */\ninterface IControlState {\n invalid: boolean;\n errors: ValidationErrors | null;\n required: boolean;\n /** Касались ли контрола или правили его: до этого пустое обязательное поле не краснеет. */\n interacted: boolean;\n}\n\n/** Состояние поля, которому форма ещё ничего не сказала. */\nconst PRISTINE_STATE: IControlState = { invalid: false, errors: null, required: false, interacted: false };\n\n/** Рассылает ли контрол своё состояние. Переходник сигнальной формы — не рассылает. */\nfunction emitsEvents(control: AbstractControl): boolean {\n return typeof (control.events as { pipe?: unknown } | undefined)?.pipe === 'function';\n}\n\n/** Состояние формы, снятое с контрола любого рода. */\nfunction stateOf(control: AbstractControl): IControlState {\n return {\n invalid: !!control.invalid && (control.touched || control.dirty),\n errors: control.errors,\n required: control.hasValidator(Validators.required),\n interacted: control.touched || control.dirty,\n };\n}\n\n@Directive()\nexport abstract class RtFormControlBase<TValue> implements ControlValueAccessor, OnInit {\n readonly #ngControl: NgControl | null = inject(NgControl, { self: true, optional: true });\n readonly #destroyRef: DestroyRef = inject(DestroyRef);\n\n #onChange: (value: TValue) => void = (): void => undefined;\n #onTouched: () => void = (): void => undefined;\n\n // Состояние, которым управляет родительский rt-field (через публичные сеттеры):\n // авто-id для связки label[for], read-only режим, зеркало ошибок/required.\n readonly #controlIdAssigned: WritableSignal<string | null> = signal<string | null>(null);\n readonly #readonlyAssigned: WritableSignal<boolean> = signal<boolean>(false);\n /**\n * Обязательность, объявленная полем. Шаблонная форма валидатора на контрол не кладёт, и без\n * этого признака её обязательное поле не получало бы ни звёздочки, ни ошибки.\n */\n readonly #requiredAssigned: WritableSignal<boolean> = signal<boolean>(false);\n /** Состояние, разосланное прежней привязкой: её контрол сигналами не читается. */\n readonly #pushedState: WritableSignal<IControlState> = signal<IControlState>(PRISTINE_STATE);\n /**\n * Контрол сигнальной привязки. Его состояние не рассылается, а читается геттерами, и\n * внутри геттеров стоят вызовы сигналов формы — то есть из `computed` оно реактивно.\n */\n readonly #signalControl: WritableSignal<AbstractControl | null> = signal<AbstractControl | null>(null);\n /** Отключение, назначенное формой через CVA (`setDisabledState`). */\n readonly #disabledByForm: WritableSignal<boolean> = signal<boolean>(false);\n\n protected readonly value: WritableSignal<TValue>;\n /**\n * Отключение: вход и форма — два независимых источника, поле отключено,\n * если так сказал любой из них. Сводить их в один signal нельзя: форма\n * зовёт `setDisabledState` при связывании контрола, то есть раньше, чем\n * поле впервые обновит своё вью, и мирроринг входа затирал бы её слово —\n * контрол, созданный отключённым, оказывался бы редактируемым.\n */\n protected readonly isDisabled: Signal<boolean> = computed((): boolean => this.disabled() || this.#disabledByForm());\n /** Состояние формы: у сигнальной привязки читается, у прежней берётся разосланное. */\n readonly #state: Signal<IControlState> = computed((): IControlState => {\n const control: AbstractControl | null = this.#signalControl();\n return control === null ? this.#pushedState() : stateOf(control);\n });\n\n /**\n * Пустое поле, обязательное по слову rt-field, которого уже коснулись. Валидатор формы здесь\n * не нужен: его ошибку контрол и так получает от формы.\n */\n readonly #missingRequired: Signal<boolean> = computed(\n (): boolean => this.#requiredAssigned() && !this.#state().required && this.#state().interacted && !this.hasValue()\n );\n\n protected readonly isInvalid: Signal<boolean> = computed((): boolean => this.#state().invalid || this.#missingRequired());\n\n /** Видимость кнопки очистки: включена, есть значение, поле активно. */\n protected readonly showClearButton: Signal<boolean> = computed(\n (): boolean => this.clearable() && this.hasValue() && !this.isDisabled()\n );\n\n /** Есть ли в поле значение — гейт видимости крестика. */\n protected abstract readonly hasValue: Signal<boolean>;\n\n public readonly size: InputSignal<IRtInput.Size> = input<IRtInput.Size>('md');\n public readonly disabled: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n public readonly controlId: InputSignal<string | null> = input<string | null>(null);\n public readonly ariaLabel: InputSignal<string | null> = input<string | null>(null);\n public readonly clearable: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(true, {\n transform: booleanAttribute,\n });\n\n /**\n * Видимая resting-рамка контрола. По умолчанию `true` — рамка видна на обеих\n * темах. `false` снимает только resting-рамку; focus/invalid состояния свою\n * рамку показывают всё равно, read-only остаётся плоским.\n */\n public readonly bordered: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(true, {\n transform: booleanAttribute,\n });\n\n /** Итоговый id поля: явный input важнее авто-id, назначенного rt-field. */\n public readonly effectiveControlId: Signal<string | null> = computed(\n (): string | null => this.controlId() ?? this.#controlIdAssigned()\n );\n\n /** Read-only режим (плоский текст вместо контрола); включается rt-field'ом. */\n public readonly isReadonly: Signal<boolean> = this.#readonlyAssigned.asReadonly();\n\n /** Публичный invalid для rt-field (touched/dirty учтён в состоянии). */\n public readonly invalid: Signal<boolean> = computed((): boolean => this.isInvalid());\n\n /** Текущие ошибки связанного контрола — источник текста ошибки в rt-field. */\n public readonly errors: Signal<ValidationErrors | null> = computed(\n (): ValidationErrors | null => this.#state().errors ?? (this.#missingRequired() ? { required: true } : null)\n );\n\n /** Обязателен ли контрол — по валидатору формы или по слову rt-field. Для авто-«*». */\n public readonly required: Signal<boolean> = computed((): boolean => this.#state().required || this.#requiredAssigned());\n\n /** Текст read-only представления. Реализуется конкретным контролом. */\n public abstract readonly displayText: Signal<string>;\n\n constructor() {\n this.value = signal<TValue>(this.getEmptyValue());\n // Self-inject NgControl + ручная привязка valueAccessor: доступ к\n // control.invalid/touched без NG_VALUE_ACCESSOR provider'а (тот\n // спровоцировал бы NG0200 cycle при self-reference).\n if (this.#ngControl !== null) {\n this.#ngControl.valueAccessor = this;\n }\n }\n\n public ngOnInit(): void {\n const control: AbstractControl | null = this.#ngControl?.control ?? null;\n if (control === null) {\n return;\n }\n // Контрол, который состояния не рассылает, — переходник сигнальной формы: событий у\n // него нет вовсе, и подписка на них роняла поле из запуска вместе со всем поддеревом\n // разметки. Состояние у него читается геттерами, поэтому берётся вычислением.\n if (!emitsEvents(control)) {\n this.#signalControl.set(control);\n return;\n }\n // Авто-подсветка invalid + зеркало ошибок/required для rt-field.\n this.#pushedState.set(stateOf(control));\n control.events.pipe(takeUntilDestroyed(this.#destroyRef)).subscribe((): void => {\n this.#pushedState.set(stateOf(control));\n });\n }\n\n public writeValue(value: TValue | null): void {\n this.value.set(value ?? this.getEmptyValue());\n }\n\n public registerOnChange(fn: (value: TValue) => void): void {\n this.#onChange = fn;\n }\n\n public registerOnTouched(fn: () => void): void {\n this.#onTouched = fn;\n }\n\n public setDisabledState(isDisabled: boolean): void {\n this.#disabledByForm.set(isDisabled);\n }\n\n /** Назначить авто-id (rt-field), когда у контрола нет явного controlId. */\n public assignControlId(id: string): void {\n this.#controlIdAssigned.set(id);\n }\n\n /** Включить/выключить read-only режим — вызывает родительский rt-field. */\n public setReadonly(value: boolean): void {\n this.#readonlyAssigned.set(value);\n }\n\n /** Объявить контрол обязательным — вызывает родительский rt-field. */\n public setRequired(value: boolean): void {\n this.#requiredAssigned.set(value);\n }\n\n /** Очистка значения по кнопке-крестику + возврат фокуса в поле. */\n protected clear(event?: Event): void {\n event?.stopPropagation();\n if (this.isDisabled()) {\n return;\n }\n this.clearValue();\n this.#onChange(this.value());\n this.#onTouched();\n this.focusAfterClear();\n }\n\n /** Сбрасывает значение к пустому. Поля с display-сигналом расширяют. */\n protected clearValue(): void {\n this.value.set(this.getEmptyValue());\n }\n\n /** Прокидывает change наружу — для обработчиков пользовательского ввода. */\n protected emitChange(value: TValue): void {\n this.#onChange(value);\n }\n\n /** Помечает контрол touched — для blur/close-обработчиков. */\n protected markTouched(): void {\n this.#onTouched();\n }\n\n /** Пустое значение поля (тип-специфично: \"\", null, []). */\n protected abstract getEmptyValue(): TValue;\n\n /** Возврат фокуса после очистки (в input или на trigger-кнопку). */\n protected abstract focusAfterClear(): void;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"rt-tools-ui-kit-v2-form-control.mjs","names":[],"sources":["../../../projects/ui-kit-v2/src/lib/components/form-control/rt-form-control.base.ts","../../../projects/ui-kit-v2/src/lib/components/form-control/rt-notch-width.ts","../../../projects/ui-kit-v2/src/lib/components/form-control/rt-maxlength.ts"],"sourcesContent":["import { BooleanInput } from '@angular/cdk/coercion';\nimport {\n booleanAttribute,\n computed,\n DestroyRef,\n Directive,\n inject,\n input,\n InputSignal,\n InputSignalWithTransform,\n OnInit,\n output,\n OutputEmitterRef,\n Signal,\n signal,\n WritableSignal,\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { AbstractControl, ControlValueAccessor, NgControl, ValidationErrors, Validators } from '@angular/forms';\n\nimport { IRtInput } from '@rt-tools/ui-kit-v2/core';\n\n/**\n * Базовый класс input-семейства common/ui. Владеет единым CVA-ядром:\n * - self-injected `NgControl` + ручная привязка `valueAccessor` (без\n * `NG_VALUE_ACCESSOR` provider — иначе NG0200 cycle при self-reference),\n * что даёт доступ к `control.invalid/touched` для авто-подсветки ошибки;\n * - сигнал значения `value`, состояния `isDisabled`/`isInvalid` (отключение\n * складывается из входа `disabled` и слова формы — см. `isDisabled`);\n * - общие инпуты `size`/`disabled`/`controlId`/`ariaLabel`/`clearable`/`clearTabbable`;\n * - оркестрацию очистки (`clear`) через абстрактные хуки конкретного поля и выход `cleared`.\n *\n * Конкретные поля реализуют `getEmptyValue`/`hasValue`/`focusAfterClear`. Поля\n * с отдельным display-состоянием (autocomplete, input-number) дополнительно\n * переопределяют `writeValue`/`clearValue`, синхронизируя свой display-сигнал.\n */\n/**\n * Состояние формы, каким его видит поле: негодность, ошибки и обязательность.\n *\n * Собрано одной записью затем, что источников у него два и говорят они по-разному. Прежняя\n * привязка рассылает состояние событиями контрола, сигнальная — не рассылает вовсе и отдаёт\n * его чтениями своего переходника. Порознь эти два пути дали бы полю два набора сигналов, и\n * обёртке пришлось бы знать, какой из них живой.\n */\ninterface IControlState {\n invalid: boolean;\n errors: ValidationErrors | null;\n required: boolean;\n /** Касались ли контрола или правили его: до этого пустое обязательное поле не краснеет. */\n interacted: boolean;\n}\n\n/** Состояние поля, которому форма ещё ничего не сказала. */\nconst PRISTINE_STATE: IControlState = { invalid: false, errors: null, required: false, interacted: false };\n\n/** Рассылает ли контрол своё состояние. Переходник сигнальной формы — не рассылает. */\nfunction emitsEvents(control: AbstractControl): boolean {\n return typeof (control.events as { pipe?: unknown } | undefined)?.pipe === 'function';\n}\n\n/** Состояние формы, снятое с контрола любого рода. */\nfunction stateOf(control: AbstractControl): IControlState {\n return {\n invalid: !!control.invalid && (control.touched || control.dirty),\n errors: control.errors,\n required: control.hasValidator(Validators.required),\n interacted: control.touched || control.dirty,\n };\n}\n\n@Directive()\nexport abstract class RtFormControlBase<TValue> implements ControlValueAccessor, OnInit {\n readonly #ngControl: NgControl | null = inject(NgControl, { self: true, optional: true });\n readonly #destroyRef: DestroyRef = inject(DestroyRef);\n\n #onChange: (value: TValue) => void = (): void => undefined;\n #onTouched: () => void = (): void => undefined;\n\n // Состояние, которым управляет родительский rt-field (через публичные сеттеры):\n // авто-id для связки label[for], read-only режим, зеркало ошибок/required.\n readonly #controlIdAssigned: WritableSignal<string | null> = signal<string | null>(null);\n readonly #readonlyAssigned: WritableSignal<boolean> = signal<boolean>(false);\n /**\n * Обязательность, объявленная полем. Шаблонная форма валидатора на контрол не кладёт, и без\n * этого признака её обязательное поле не получало бы ни звёздочки, ни ошибки.\n */\n readonly #requiredAssigned: WritableSignal<boolean> = signal<boolean>(false);\n /** Состояние, разосланное прежней привязкой: её контрол сигналами не читается. */\n readonly #pushedState: WritableSignal<IControlState> = signal<IControlState>(PRISTINE_STATE);\n /**\n * Контрол сигнальной привязки. Его состояние не рассылается, а читается геттерами, и\n * внутри геттеров стоят вызовы сигналов формы — то есть из `computed` оно реактивно.\n */\n readonly #signalControl: WritableSignal<AbstractControl | null> = signal<AbstractControl | null>(null);\n /** Отключение, назначенное формой через CVA (`setDisabledState`). */\n readonly #disabledByForm: WritableSignal<boolean> = signal<boolean>(false);\n\n protected readonly value: WritableSignal<TValue>;\n /**\n * Отключение: вход и форма — два независимых источника, поле отключено,\n * если так сказал любой из них. Сводить их в один signal нельзя: форма\n * зовёт `setDisabledState` при связывании контрола, то есть раньше, чем\n * поле впервые обновит своё вью, и мирроринг входа затирал бы её слово —\n * контрол, созданный отключённым, оказывался бы редактируемым.\n */\n protected readonly isDisabled: Signal<boolean> = computed((): boolean => this.disabled() || this.#disabledByForm());\n /** Состояние формы: у сигнальной привязки читается, у прежней берётся разосланное. */\n readonly #state: Signal<IControlState> = computed((): IControlState => {\n const control: AbstractControl | null = this.#signalControl();\n return control === null ? this.#pushedState() : stateOf(control);\n });\n\n /**\n * Пустое поле, обязательное по слову rt-field, которого уже коснулись. Валидатор формы здесь\n * не нужен: его ошибку контрол и так получает от формы.\n */\n readonly #missingRequired: Signal<boolean> = computed(\n (): boolean => this.#requiredAssigned() && !this.#state().required && this.#state().interacted && !this.hasValue()\n );\n\n protected readonly isInvalid: Signal<boolean> = computed((): boolean => this.#state().invalid || this.#missingRequired());\n\n /** Видимость кнопки очистки: включена, есть значение, поле активно. */\n protected readonly showClearButton: Signal<boolean> = computed(\n (): boolean => this.clearable() && this.hasValue() && !this.isDisabled()\n );\n\n /** Tabindex крестика очистки: в порядке обхода только по `clearTabbable`. */\n protected readonly clearTabIndex: Signal<number> = computed((): number => (this.clearTabbable() ? 0 : -1));\n\n /** Есть ли в поле значение — гейт видимости крестика. */\n protected abstract readonly hasValue: Signal<boolean>;\n\n public readonly size: InputSignal<IRtInput.Size> = input<IRtInput.Size>('md');\n public readonly disabled: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n public readonly controlId: InputSignal<string | null> = input<string | null>(null);\n public readonly ariaLabel: InputSignal<string | null> = input<string | null>(null);\n public readonly clearable: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(true, {\n transform: booleanAttribute,\n });\n\n /**\n * Ставит крестик очистки в порядок обхода по Tab. По умолчанию `false`: крестик достаётся мышью,\n * а Tab идёт от поля к полю, как прежде. Учитывают его поле ввода и выбор из списка.\n */\n public readonly clearTabbable: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n /**\n * Подпись `rt-field` в материальном наборе ложится внутрь этого контрола, на место подсказки.\n * Ставят поле ввода, область текста, число и выбор: у палитры, списка и выбора файла места\n * подсказки нет, и подпись над ними остаётся над контролом.\n */\n public readonly fieldLabelInside: boolean = false;\n\n /**\n * Видимая resting-рамка контрола. По умолчанию `true` — рамка видна на обеих\n * темах. `false` снимает только resting-рамку; focus/invalid состояния свою\n * рамку показывают всё равно, read-only остаётся плоским.\n */\n public readonly bordered: InputSignalWithTransform<boolean, BooleanInput> = input<boolean, BooleanInput>(true, {\n transform: booleanAttribute,\n });\n\n /** Итоговый id поля: явный input важнее авто-id, назначенного rt-field. */\n public readonly effectiveControlId: Signal<string | null> = computed(\n (): string | null => this.controlId() ?? this.#controlIdAssigned()\n );\n\n /** Read-only режим (плоский текст вместо контрола); включается rt-field'ом. */\n public readonly isReadonly: Signal<boolean> = this.#readonlyAssigned.asReadonly();\n\n /** Публичный invalid для rt-field (touched/dirty учтён в состоянии). */\n public readonly invalid: Signal<boolean> = computed((): boolean => this.isInvalid());\n\n /**\n * Сообщает о сбросе крестиком. Приходит после того, как значение уже пустое и ушло в форму:\n * DOM-событий `input`/`change` сброс не рождает, и хосту, который слушает их, нужен свой сигнал.\n */\n public readonly cleared: OutputEmitterRef<void> = output<void>();\n\n /** Текущие ошибки связанного контрола — источник текста ошибки в rt-field. */\n public readonly errors: Signal<ValidationErrors | null> = computed(\n (): ValidationErrors | null => this.#state().errors ?? (this.#missingRequired() ? { required: true } : null)\n );\n\n /** Обязателен ли контрол — по валидатору формы или по слову rt-field. Для авто-«*». */\n public readonly required: Signal<boolean> = computed((): boolean => this.#state().required || this.#requiredAssigned());\n\n /** Текст read-only представления. Реализуется конкретным контролом. */\n public abstract readonly displayText: Signal<string>;\n\n constructor() {\n this.value = signal<TValue>(this.getEmptyValue());\n // Self-inject NgControl + ручная привязка valueAccessor: доступ к\n // control.invalid/touched без NG_VALUE_ACCESSOR provider'а (тот\n // спровоцировал бы NG0200 cycle при self-reference).\n if (this.#ngControl !== null) {\n this.#ngControl.valueAccessor = this;\n }\n }\n\n public ngOnInit(): void {\n const control: AbstractControl | null = this.#ngControl?.control ?? null;\n if (control === null) {\n return;\n }\n // Контрол, который состояния не рассылает, — переходник сигнальной формы: событий у\n // него нет вовсе, и подписка на них роняла поле из запуска вместе со всем поддеревом\n // разметки. Состояние у него читается геттерами, поэтому берётся вычислением.\n if (!emitsEvents(control)) {\n this.#signalControl.set(control);\n return;\n }\n // Авто-подсветка invalid + зеркало ошибок/required для rt-field.\n this.#pushedState.set(stateOf(control));\n control.events.pipe(takeUntilDestroyed(this.#destroyRef)).subscribe((): void => {\n this.#pushedState.set(stateOf(control));\n });\n }\n\n public writeValue(value: TValue | null): void {\n this.value.set(value ?? this.getEmptyValue());\n }\n\n public registerOnChange(fn: (value: TValue) => void): void {\n this.#onChange = fn;\n }\n\n public registerOnTouched(fn: () => void): void {\n this.#onTouched = fn;\n }\n\n public setDisabledState(isDisabled: boolean): void {\n this.#disabledByForm.set(isDisabled);\n }\n\n /** Назначить авто-id (rt-field), когда у контрола нет явного controlId. */\n public assignControlId(id: string): void {\n this.#controlIdAssigned.set(id);\n }\n\n /** Включить/выключить read-only режим — вызывает родительский rt-field. */\n public setReadonly(value: boolean): void {\n this.#readonlyAssigned.set(value);\n }\n\n /** Объявить контрол обязательным — вызывает родительский rt-field. */\n public setRequired(value: boolean): void {\n this.#requiredAssigned.set(value);\n }\n\n /** Очистка значения по кнопке-крестику + возврат фокуса в поле. */\n protected clear(event?: Event): void {\n event?.stopPropagation();\n if (this.isDisabled()) {\n return;\n }\n this.clearValue();\n this.#onChange(this.value());\n this.#onTouched();\n this.focusAfterClear();\n this.cleared.emit();\n }\n\n /** Сбрасывает значение к пустому. Поля с display-сигналом расширяют. */\n protected clearValue(): void {\n this.value.set(this.getEmptyValue());\n }\n\n /** Прокидывает change наружу — для обработчиков пользовательского ввода. */\n protected emitChange(value: TValue): void {\n this.#onChange(value);\n }\n\n /** Помечает контрол touched — для blur/close-обработчиков. */\n protected markTouched(): void {\n this.#onTouched();\n }\n\n /** Пустое значение поля (тип-специфично: \"\", null, []). */\n protected abstract getEmptyValue(): TValue;\n\n /** Возврат фокуса после очистки (в input или на trigger-кнопку). */\n protected abstract focusAfterClear(): void;\n}\n","import { effect, inject, signal, untracked, DestroyRef, ElementRef, Signal, WritableSignal } from '@angular/core';\n\n/**\n * Внешняя ширина подписи на рамке, px, — по ней материальный набор вырезает разрыв в рамке поля,\n * как у поля Material. Ширину подписи стили сами не знают, поэтому её меряет наблюдатель размера.\n * Подпись появляется и пропадает вместе со входом — наблюдатель переезжает за ней.\n *\n * Зовётся в контексте внедрения: в инициализаторе поля компонента.\n */\nexport function rtNotchWidth(label: Signal<ElementRef<HTMLElement> | undefined>): Signal<number> {\n const width: WritableSignal<number> = signal<number>(0);\n let watch: ResizeObserver | null = null;\n\n effect((): void => {\n const element: HTMLElement | undefined = label()?.nativeElement;\n watch?.disconnect();\n watch = null;\n if (element === undefined) {\n // Подписи больше нет: разрыв закрывается, иначе в рамке осталась бы пустая щель.\n untracked((): void => width.set(0));\n return;\n }\n if (typeof ResizeObserver === 'undefined') {\n return;\n }\n // Внешний размер: поднятая подпись получает внутренние поля, и разрыв режется вместе с ними.\n watch = new ResizeObserver((entries: ResizeObserverEntry[]): void =>\n width.set(entries[0]?.borderBoxSize[0]?.inlineSize ?? element.offsetWidth)\n );\n watch.observe(element, { box: 'border-box' });\n });\n inject(DestroyRef).onDestroy((): void => watch?.disconnect());\n\n return width.asReadonly();\n}\n","import { NumberInput } from '@angular/cdk/coercion';\nimport { numberAttribute } from '@angular/core';\n\n/**\n * Предел длины поля из входа `maxlength`: целое не меньше нуля. Пустой атрибут, `null` и не число —\n * предела нет, атрибут на поле не ставится.\n */\nexport function rtMaxlength(value: NumberInput): number | null {\n if (value === null || value === undefined || value === '') {\n return null;\n }\n const limit: number = numberAttribute(value, Number.NaN);\n return Number.isInteger(limit) && limit >= 0 ? limit : null;\n}\n"],"mappings":"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"}