@mesgflow/ui-kit 0.6.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 (230) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +200 -0
  3. package/fesm2022/mesgflow-ui-kit-accordion.mjs +75 -0
  4. package/fesm2022/mesgflow-ui-kit-accordion.mjs.map +1 -0
  5. package/fesm2022/mesgflow-ui-kit-alert.mjs +165 -0
  6. package/fesm2022/mesgflow-ui-kit-alert.mjs.map +1 -0
  7. package/fesm2022/mesgflow-ui-kit-avatar-group.mjs +88 -0
  8. package/fesm2022/mesgflow-ui-kit-avatar-group.mjs.map +1 -0
  9. package/fesm2022/mesgflow-ui-kit-avatar.mjs +123 -0
  10. package/fesm2022/mesgflow-ui-kit-avatar.mjs.map +1 -0
  11. package/fesm2022/mesgflow-ui-kit-badge.mjs +68 -0
  12. package/fesm2022/mesgflow-ui-kit-badge.mjs.map +1 -0
  13. package/fesm2022/mesgflow-ui-kit-banner.mjs +111 -0
  14. package/fesm2022/mesgflow-ui-kit-banner.mjs.map +1 -0
  15. package/fesm2022/mesgflow-ui-kit-box-input.mjs +78 -0
  16. package/fesm2022/mesgflow-ui-kit-box-input.mjs.map +1 -0
  17. package/fesm2022/mesgflow-ui-kit-brand.mjs +120 -0
  18. package/fesm2022/mesgflow-ui-kit-brand.mjs.map +1 -0
  19. package/fesm2022/mesgflow-ui-kit-breadcrumb.mjs +84 -0
  20. package/fesm2022/mesgflow-ui-kit-breadcrumb.mjs.map +1 -0
  21. package/fesm2022/mesgflow-ui-kit-button-group.mjs +37 -0
  22. package/fesm2022/mesgflow-ui-kit-button-group.mjs.map +1 -0
  23. package/fesm2022/mesgflow-ui-kit-button.mjs +359 -0
  24. package/fesm2022/mesgflow-ui-kit-button.mjs.map +1 -0
  25. package/fesm2022/mesgflow-ui-kit-card.mjs +128 -0
  26. package/fesm2022/mesgflow-ui-kit-card.mjs.map +1 -0
  27. package/fesm2022/mesgflow-ui-kit-chart.mjs +538 -0
  28. package/fesm2022/mesgflow-ui-kit-chart.mjs.map +1 -0
  29. package/fesm2022/mesgflow-ui-kit-checkbox.mjs +45 -0
  30. package/fesm2022/mesgflow-ui-kit-checkbox.mjs.map +1 -0
  31. package/fesm2022/mesgflow-ui-kit-chip.mjs +123 -0
  32. package/fesm2022/mesgflow-ui-kit-chip.mjs.map +1 -0
  33. package/fesm2022/mesgflow-ui-kit-code-block.mjs +71 -0
  34. package/fesm2022/mesgflow-ui-kit-code-block.mjs.map +1 -0
  35. package/fesm2022/mesgflow-ui-kit-collapsible.mjs +89 -0
  36. package/fesm2022/mesgflow-ui-kit-collapsible.mjs.map +1 -0
  37. package/fesm2022/mesgflow-ui-kit-color-picker.mjs +210 -0
  38. package/fesm2022/mesgflow-ui-kit-color-picker.mjs.map +1 -0
  39. package/fesm2022/mesgflow-ui-kit-combobox.mjs +181 -0
  40. package/fesm2022/mesgflow-ui-kit-combobox.mjs.map +1 -0
  41. package/fesm2022/mesgflow-ui-kit-command.mjs +491 -0
  42. package/fesm2022/mesgflow-ui-kit-command.mjs.map +1 -0
  43. package/fesm2022/mesgflow-ui-kit-context-menu.mjs +92 -0
  44. package/fesm2022/mesgflow-ui-kit-context-menu.mjs.map +1 -0
  45. package/fesm2022/mesgflow-ui-kit-copy-button.mjs +167 -0
  46. package/fesm2022/mesgflow-ui-kit-copy-button.mjs.map +1 -0
  47. package/fesm2022/mesgflow-ui-kit-core.mjs +624 -0
  48. package/fesm2022/mesgflow-ui-kit-core.mjs.map +1 -0
  49. package/fesm2022/mesgflow-ui-kit-date-picker.mjs +643 -0
  50. package/fesm2022/mesgflow-ui-kit-date-picker.mjs.map +1 -0
  51. package/fesm2022/mesgflow-ui-kit-description-list.mjs +76 -0
  52. package/fesm2022/mesgflow-ui-kit-description-list.mjs.map +1 -0
  53. package/fesm2022/mesgflow-ui-kit-dialog.mjs +700 -0
  54. package/fesm2022/mesgflow-ui-kit-dialog.mjs.map +1 -0
  55. package/fesm2022/mesgflow-ui-kit-divider.mjs +62 -0
  56. package/fesm2022/mesgflow-ui-kit-divider.mjs.map +1 -0
  57. package/fesm2022/mesgflow-ui-kit-dropzone.mjs +126 -0
  58. package/fesm2022/mesgflow-ui-kit-dropzone.mjs.map +1 -0
  59. package/fesm2022/mesgflow-ui-kit-empty-state.mjs +70 -0
  60. package/fesm2022/mesgflow-ui-kit-empty-state.mjs.map +1 -0
  61. package/fesm2022/mesgflow-ui-kit-field.mjs +42 -0
  62. package/fesm2022/mesgflow-ui-kit-field.mjs.map +1 -0
  63. package/fesm2022/mesgflow-ui-kit-hover-card.mjs +171 -0
  64. package/fesm2022/mesgflow-ui-kit-hover-card.mjs.map +1 -0
  65. package/fesm2022/mesgflow-ui-kit-i18n-tr.mjs +258 -0
  66. package/fesm2022/mesgflow-ui-kit-i18n-tr.mjs.map +1 -0
  67. package/fesm2022/mesgflow-ui-kit-icon.mjs +174 -0
  68. package/fesm2022/mesgflow-ui-kit-icon.mjs.map +1 -0
  69. package/fesm2022/mesgflow-ui-kit-input.mjs +448 -0
  70. package/fesm2022/mesgflow-ui-kit-input.mjs.map +1 -0
  71. package/fesm2022/mesgflow-ui-kit-kbd.mjs +24 -0
  72. package/fesm2022/mesgflow-ui-kit-kbd.mjs.map +1 -0
  73. package/fesm2022/mesgflow-ui-kit-link.mjs +48 -0
  74. package/fesm2022/mesgflow-ui-kit-link.mjs.map +1 -0
  75. package/fesm2022/mesgflow-ui-kit-menu.mjs +105 -0
  76. package/fesm2022/mesgflow-ui-kit-menu.mjs.map +1 -0
  77. package/fesm2022/mesgflow-ui-kit-number-input.mjs +153 -0
  78. package/fesm2022/mesgflow-ui-kit-number-input.mjs.map +1 -0
  79. package/fesm2022/mesgflow-ui-kit-otp-input.mjs +164 -0
  80. package/fesm2022/mesgflow-ui-kit-otp-input.mjs.map +1 -0
  81. package/fesm2022/mesgflow-ui-kit-page-header.mjs +83 -0
  82. package/fesm2022/mesgflow-ui-kit-page-header.mjs.map +1 -0
  83. package/fesm2022/mesgflow-ui-kit-pagination.mjs +99 -0
  84. package/fesm2022/mesgflow-ui-kit-pagination.mjs.map +1 -0
  85. package/fesm2022/mesgflow-ui-kit-password-input.mjs +120 -0
  86. package/fesm2022/mesgflow-ui-kit-password-input.mjs.map +1 -0
  87. package/fesm2022/mesgflow-ui-kit-popover.mjs +213 -0
  88. package/fesm2022/mesgflow-ui-kit-popover.mjs.map +1 -0
  89. package/fesm2022/mesgflow-ui-kit-progress.mjs +82 -0
  90. package/fesm2022/mesgflow-ui-kit-progress.mjs.map +1 -0
  91. package/fesm2022/mesgflow-ui-kit-radio.mjs +123 -0
  92. package/fesm2022/mesgflow-ui-kit-radio.mjs.map +1 -0
  93. package/fesm2022/mesgflow-ui-kit-rating.mjs +116 -0
  94. package/fesm2022/mesgflow-ui-kit-rating.mjs.map +1 -0
  95. package/fesm2022/mesgflow-ui-kit-scroll-area.mjs +84 -0
  96. package/fesm2022/mesgflow-ui-kit-scroll-area.mjs.map +1 -0
  97. package/fesm2022/mesgflow-ui-kit-search-input.mjs +154 -0
  98. package/fesm2022/mesgflow-ui-kit-search-input.mjs.map +1 -0
  99. package/fesm2022/mesgflow-ui-kit-segmented-control.mjs +95 -0
  100. package/fesm2022/mesgflow-ui-kit-segmented-control.mjs.map +1 -0
  101. package/fesm2022/mesgflow-ui-kit-select.mjs +227 -0
  102. package/fesm2022/mesgflow-ui-kit-select.mjs.map +1 -0
  103. package/fesm2022/mesgflow-ui-kit-sidebar.mjs +449 -0
  104. package/fesm2022/mesgflow-ui-kit-sidebar.mjs.map +1 -0
  105. package/fesm2022/mesgflow-ui-kit-skeleton.mjs +74 -0
  106. package/fesm2022/mesgflow-ui-kit-skeleton.mjs.map +1 -0
  107. package/fesm2022/mesgflow-ui-kit-slider.mjs +179 -0
  108. package/fesm2022/mesgflow-ui-kit-slider.mjs.map +1 -0
  109. package/fesm2022/mesgflow-ui-kit-spinner.mjs +67 -0
  110. package/fesm2022/mesgflow-ui-kit-spinner.mjs.map +1 -0
  111. package/fesm2022/mesgflow-ui-kit-splitter.mjs +97 -0
  112. package/fesm2022/mesgflow-ui-kit-splitter.mjs.map +1 -0
  113. package/fesm2022/mesgflow-ui-kit-stat-tile.mjs +98 -0
  114. package/fesm2022/mesgflow-ui-kit-stat-tile.mjs.map +1 -0
  115. package/fesm2022/mesgflow-ui-kit-stepper.mjs +133 -0
  116. package/fesm2022/mesgflow-ui-kit-stepper.mjs.map +1 -0
  117. package/fesm2022/mesgflow-ui-kit-switch.mjs +73 -0
  118. package/fesm2022/mesgflow-ui-kit-switch.mjs.map +1 -0
  119. package/fesm2022/mesgflow-ui-kit-table.mjs +1783 -0
  120. package/fesm2022/mesgflow-ui-kit-table.mjs.map +1 -0
  121. package/fesm2022/mesgflow-ui-kit-tabs.mjs +569 -0
  122. package/fesm2022/mesgflow-ui-kit-tabs.mjs.map +1 -0
  123. package/fesm2022/mesgflow-ui-kit-tag-input.mjs +138 -0
  124. package/fesm2022/mesgflow-ui-kit-tag-input.mjs.map +1 -0
  125. package/fesm2022/mesgflow-ui-kit-theme-switcher.mjs +53 -0
  126. package/fesm2022/mesgflow-ui-kit-theme-switcher.mjs.map +1 -0
  127. package/fesm2022/mesgflow-ui-kit-theming.mjs +1016 -0
  128. package/fesm2022/mesgflow-ui-kit-theming.mjs.map +1 -0
  129. package/fesm2022/mesgflow-ui-kit-time-picker.mjs +138 -0
  130. package/fesm2022/mesgflow-ui-kit-time-picker.mjs.map +1 -0
  131. package/fesm2022/mesgflow-ui-kit-timeline.mjs +91 -0
  132. package/fesm2022/mesgflow-ui-kit-timeline.mjs.map +1 -0
  133. package/fesm2022/mesgflow-ui-kit-toast.mjs +202 -0
  134. package/fesm2022/mesgflow-ui-kit-toast.mjs.map +1 -0
  135. package/fesm2022/mesgflow-ui-kit-toggle.mjs +243 -0
  136. package/fesm2022/mesgflow-ui-kit-toggle.mjs.map +1 -0
  137. package/fesm2022/mesgflow-ui-kit-toolbar.mjs +99 -0
  138. package/fesm2022/mesgflow-ui-kit-toolbar.mjs.map +1 -0
  139. package/fesm2022/mesgflow-ui-kit-tooltip.mjs +160 -0
  140. package/fesm2022/mesgflow-ui-kit-tooltip.mjs.map +1 -0
  141. package/fesm2022/mesgflow-ui-kit-tour.mjs +272 -0
  142. package/fesm2022/mesgflow-ui-kit-tour.mjs.map +1 -0
  143. package/fesm2022/mesgflow-ui-kit-tree-select.mjs +177 -0
  144. package/fesm2022/mesgflow-ui-kit-tree-select.mjs.map +1 -0
  145. package/fesm2022/mesgflow-ui-kit-tree.mjs +82 -0
  146. package/fesm2022/mesgflow-ui-kit-tree.mjs.map +1 -0
  147. package/fesm2022/mesgflow-ui-kit.mjs +12851 -0
  148. package/fesm2022/mesgflow-ui-kit.mjs.map +1 -0
  149. package/package.json +374 -0
  150. package/preset.css +20 -0
  151. package/tokens/color.css +693 -0
  152. package/tokens/contrast.css +68 -0
  153. package/tokens/elevation.css +68 -0
  154. package/tokens/motion.css +277 -0
  155. package/tokens/radius.css +69 -0
  156. package/tokens/typography.css +127 -0
  157. package/tokens.css +41 -0
  158. package/types/mesgflow-ui-kit-accordion.d.ts +42 -0
  159. package/types/mesgflow-ui-kit-alert.d.ts +60 -0
  160. package/types/mesgflow-ui-kit-avatar-group.d.ts +28 -0
  161. package/types/mesgflow-ui-kit-avatar.d.ts +23 -0
  162. package/types/mesgflow-ui-kit-badge.d.ts +39 -0
  163. package/types/mesgflow-ui-kit-banner.d.ts +42 -0
  164. package/types/mesgflow-ui-kit-box-input.d.ts +45 -0
  165. package/types/mesgflow-ui-kit-brand.d.ts +48 -0
  166. package/types/mesgflow-ui-kit-breadcrumb.d.ts +26 -0
  167. package/types/mesgflow-ui-kit-button-group.d.ts +17 -0
  168. package/types/mesgflow-ui-kit-button.d.ts +75 -0
  169. package/types/mesgflow-ui-kit-card.d.ts +71 -0
  170. package/types/mesgflow-ui-kit-chart.d.ts +242 -0
  171. package/types/mesgflow-ui-kit-checkbox.d.ts +17 -0
  172. package/types/mesgflow-ui-kit-chip.d.ts +46 -0
  173. package/types/mesgflow-ui-kit-code-block.d.ts +56 -0
  174. package/types/mesgflow-ui-kit-collapsible.d.ts +39 -0
  175. package/types/mesgflow-ui-kit-color-picker.d.ts +78 -0
  176. package/types/mesgflow-ui-kit-combobox.d.ts +74 -0
  177. package/types/mesgflow-ui-kit-command.d.ts +185 -0
  178. package/types/mesgflow-ui-kit-context-menu.d.ts +40 -0
  179. package/types/mesgflow-ui-kit-copy-button.d.ts +47 -0
  180. package/types/mesgflow-ui-kit-core.d.ts +508 -0
  181. package/types/mesgflow-ui-kit-date-picker.d.ts +215 -0
  182. package/types/mesgflow-ui-kit-description-list.d.ts +23 -0
  183. package/types/mesgflow-ui-kit-dialog.d.ts +342 -0
  184. package/types/mesgflow-ui-kit-divider.d.ts +23 -0
  185. package/types/mesgflow-ui-kit-dropzone.d.ts +47 -0
  186. package/types/mesgflow-ui-kit-empty-state.d.ts +18 -0
  187. package/types/mesgflow-ui-kit-field.d.ts +26 -0
  188. package/types/mesgflow-ui-kit-hover-card.d.ts +52 -0
  189. package/types/mesgflow-ui-kit-i18n-tr.d.ts +14 -0
  190. package/types/mesgflow-ui-kit-icon.d.ts +76 -0
  191. package/types/mesgflow-ui-kit-input.d.ts +186 -0
  192. package/types/mesgflow-ui-kit-kbd.d.ts +9 -0
  193. package/types/mesgflow-ui-kit-link.d.ts +20 -0
  194. package/types/mesgflow-ui-kit-menu.d.ts +53 -0
  195. package/types/mesgflow-ui-kit-number-input.d.ts +55 -0
  196. package/types/mesgflow-ui-kit-otp-input.d.ts +44 -0
  197. package/types/mesgflow-ui-kit-page-header.d.ts +31 -0
  198. package/types/mesgflow-ui-kit-pagination.d.ts +50 -0
  199. package/types/mesgflow-ui-kit-password-input.d.ts +33 -0
  200. package/types/mesgflow-ui-kit-popover.d.ts +62 -0
  201. package/types/mesgflow-ui-kit-progress.d.ts +32 -0
  202. package/types/mesgflow-ui-kit-radio.d.ts +59 -0
  203. package/types/mesgflow-ui-kit-rating.d.ts +56 -0
  204. package/types/mesgflow-ui-kit-scroll-area.d.ts +27 -0
  205. package/types/mesgflow-ui-kit-search-input.d.ts +72 -0
  206. package/types/mesgflow-ui-kit-segmented-control.d.ts +54 -0
  207. package/types/mesgflow-ui-kit-select.d.ts +89 -0
  208. package/types/mesgflow-ui-kit-sidebar.d.ts +224 -0
  209. package/types/mesgflow-ui-kit-skeleton.d.ts +29 -0
  210. package/types/mesgflow-ui-kit-slider.d.ts +40 -0
  211. package/types/mesgflow-ui-kit-spinner.d.ts +31 -0
  212. package/types/mesgflow-ui-kit-splitter.d.ts +40 -0
  213. package/types/mesgflow-ui-kit-stat-tile.d.ts +54 -0
  214. package/types/mesgflow-ui-kit-stepper.d.ts +48 -0
  215. package/types/mesgflow-ui-kit-switch.d.ts +34 -0
  216. package/types/mesgflow-ui-kit-table.d.ts +805 -0
  217. package/types/mesgflow-ui-kit-tabs.d.ts +269 -0
  218. package/types/mesgflow-ui-kit-tag-input.d.ts +33 -0
  219. package/types/mesgflow-ui-kit-theme-switcher.d.ts +27 -0
  220. package/types/mesgflow-ui-kit-theming.d.ts +745 -0
  221. package/types/mesgflow-ui-kit-time-picker.d.ts +54 -0
  222. package/types/mesgflow-ui-kit-timeline.d.ts +27 -0
  223. package/types/mesgflow-ui-kit-toast.d.ts +94 -0
  224. package/types/mesgflow-ui-kit-toggle.d.ts +110 -0
  225. package/types/mesgflow-ui-kit-toolbar.d.ts +52 -0
  226. package/types/mesgflow-ui-kit-tooltip.d.ts +55 -0
  227. package/types/mesgflow-ui-kit-tour.d.ts +66 -0
  228. package/types/mesgflow-ui-kit-tree-select.d.ts +65 -0
  229. package/types/mesgflow-ui-kit-tree.d.ts +44 -0
  230. package/types/mesgflow-ui-kit.d.ts +5444 -0
@@ -0,0 +1,1783 @@
1
+ import { NgTemplateOutlet } from '@angular/common';
2
+ import * as i1 from '@angular/cdk/overlay';
3
+ import { OverlayModule } from '@angular/cdk/overlay';
4
+ import * as i0 from '@angular/core';
5
+ import { input, output, inject, ElementRef, Directive, computed, Component, signal, viewChild, effect, untracked, afterRenderEffect, model, booleanAttribute, DestroyRef, afterNextRender } from '@angular/core';
6
+ import { Button } from '@mesgflow/ui-kit/button';
7
+ import { Checkbox } from '@mesgflow/ui-kit/checkbox';
8
+ import { MF_TABLE_LABELS, cn, mergeLabels, isRtl } from '@mesgflow/ui-kit/core';
9
+ import { EmptyState } from '@mesgflow/ui-kit/empty-state';
10
+ import { Icon } from '@mesgflow/ui-kit/icon';
11
+ import { SearchInput } from '@mesgflow/ui-kit/search-input';
12
+ import { Skeleton } from '@mesgflow/ui-kit/skeleton';
13
+ import { Tooltip } from '@mesgflow/ui-kit/tooltip';
14
+ import { Popover } from '@mesgflow/ui-kit/popover';
15
+ import { Chip } from '@mesgflow/ui-kit/chip';
16
+ import { Select } from '@mesgflow/ui-kit/select';
17
+ import { startOfDay, startOfMonth } from 'date-fns';
18
+ import { Input } from '@mesgflow/ui-kit/input';
19
+ import { dateRangeForPreset, Calendar } from '@mesgflow/ui-kit/date-picker';
20
+
21
+ var SortDirection;
22
+ (function (SortDirection) {
23
+ SortDirection[SortDirection["Ascending"] = 0] = "Ascending";
24
+ SortDirection[SortDirection["Descending"] = 1] = "Descending";
25
+ })(SortDirection || (SortDirection = {}));
26
+ var FilterOperator;
27
+ (function (FilterOperator) {
28
+ FilterOperator[FilterOperator["Equal"] = 0] = "Equal";
29
+ FilterOperator[FilterOperator["NotEqual"] = 1] = "NotEqual";
30
+ FilterOperator[FilterOperator["GreaterThan"] = 2] = "GreaterThan";
31
+ FilterOperator[FilterOperator["GreaterOrEqual"] = 3] = "GreaterOrEqual";
32
+ FilterOperator[FilterOperator["LessThan"] = 4] = "LessThan";
33
+ FilterOperator[FilterOperator["LessOrEqual"] = 5] = "LessOrEqual";
34
+ FilterOperator[FilterOperator["Contains"] = 6] = "Contains";
35
+ FilterOperator[FilterOperator["StartsWith"] = 7] = "StartsWith";
36
+ FilterOperator[FilterOperator["EndsWith"] = 8] = "EndsWith";
37
+ FilterOperator[FilterOperator["In"] = 9] = "In";
38
+ FilterOperator[FilterOperator["IsNull"] = 10] = "IsNull";
39
+ FilterOperator[FilterOperator["IsNotNull"] = 11] = "IsNotNull";
40
+ })(FilterOperator || (FilterOperator = {}));
41
+ var FilterLogic;
42
+ (function (FilterLogic) {
43
+ FilterLogic[FilterLogic["And"] = 0] = "And";
44
+ FilterLogic[FilterLogic["Or"] = 1] = "Or";
45
+ })(FilterLogic || (FilterLogic = {}));
46
+ const DEFAULT_PAGE_SIZE = 20;
47
+ const MAX_PAGE_SIZE = 100;
48
+ /**
49
+ * `PagedRequest`'i backend'e giden JSON'un HER ZAMAN aynı şekilde serileştiği
50
+ * bir forma indirger. Neden önemli: birçok backend (ör. bu kütüphanenin
51
+ * geliştirildiği projedeki caching davranışı) sorgu cache anahtarını
52
+ * `JsonSerializer.Serialize(request)` ÜZERİNDEN kuruyor — yani anlamca özdeş
53
+ * ama söz dizimsel farklı iki payload (`sort: []` ile `sort` hiç yok; `filter`
54
+ * içi boş `conditions: []` ile `filter` hiç yok; property sırası) SUNUCUDA
55
+ * AYRI cache girdisi üretir ve invalidation'ın getirisini boşa çıkarır. Bu
56
+ * fonksiyon üç şeyi garanti eder: (1) boş `sort`/`filter` her zaman
57
+ * `undefined`, dizi değil, (2) `FilterGroup` içindeki boş `conditions`/`groups`
58
+ * de `undefined`'a iner (özyinelemeli), (3) tüm nesneler SABİT anahtar
59
+ * sırasıyla yeniden kurulur (JS'te string anahtarlı nesnelerde ekleme sırası
60
+ * korunur, `JSON.stringify` bunu birebir yansıtır).
61
+ */
62
+ function canonicalPagedRequest(request) {
63
+ return {
64
+ page: { pageNumber: request.page.pageNumber, pageSize: request.page.pageSize },
65
+ filter: canonicalFilterGroup(request.filter),
66
+ sort: request.sort?.length
67
+ ? request.sort.map((s) => ({ field: s.field, direction: s.direction }))
68
+ : undefined,
69
+ };
70
+ }
71
+ function canonicalFilterGroup(group) {
72
+ if (!group)
73
+ return undefined;
74
+ const conditions = group.conditions?.length
75
+ ? group.conditions.map((c) => ({ field: c.field, operator: c.operator, value: c.value }))
76
+ : undefined;
77
+ const groups = group.groups?.map(canonicalFilterGroup).filter((g) => !!g);
78
+ if (!conditions && (!groups || groups.length === 0))
79
+ return undefined;
80
+ const result = { logic: group.logic };
81
+ if (conditions)
82
+ result.conditions = conditions;
83
+ if (groups?.length)
84
+ result.groups = groups;
85
+ return result;
86
+ }
87
+
88
+ /**
89
+ * `Table`'ın `[clientSide]="true"` modunun sorgu motoru — "backend'in
90
+ * `FilterGroup`/`SortDescriptor` modelini elimde tutuyorum, gerçek sunucu
91
+ * yerine bir diziye uyguluyorum" işini yapıyor; tüketicinin backend'iyle AYNI
92
+ * operatör sözleşmesini konuşacak şekilde tasarlandı — bu yüzden burada
93
+ * `'between'` gibi UI'a özel sentetik bir operatör YOK: `table-filter.model.ts`'teki
94
+ * `buildFilterGroup` çağrıldığı an tarih aralığını zaten gerçek
95
+ * `GreaterOrEqual`/`LessOrEqual` çiftine çevirmiş oluyor.
96
+ */
97
+ function matchesCondition(row, condition) {
98
+ const value = row[condition.field];
99
+ const target = condition.value;
100
+ if (condition.operator === FilterOperator.IsNull)
101
+ return value === null || value === undefined || value === '';
102
+ if (condition.operator === FilterOperator.IsNotNull)
103
+ return !(value === null || value === undefined || value === '');
104
+ if (condition.operator === FilterOperator.In)
105
+ return Array.isArray(target) && target.includes(value);
106
+ // Backend string karşılaştırmalarını Postgres ILIKE (büyük/küçük harf
107
+ // duyarsız) ile yapıyor — istemci tarafı aynı davranışı
108
+ // `toLocaleLowerCase('tr')` ile taklit ediyor.
109
+ if (typeof value === 'string' || typeof target === 'string') {
110
+ const v = String(value ?? '').toLocaleLowerCase('tr');
111
+ const t = String(target ?? '').toLocaleLowerCase('tr');
112
+ switch (condition.operator) {
113
+ case FilterOperator.Equal:
114
+ return v === t;
115
+ case FilterOperator.NotEqual:
116
+ return v !== t;
117
+ case FilterOperator.Contains:
118
+ return v.includes(t);
119
+ case FilterOperator.StartsWith:
120
+ return v.startsWith(t);
121
+ case FilterOperator.EndsWith:
122
+ return v.endsWith(t);
123
+ default:
124
+ break;
125
+ }
126
+ }
127
+ switch (condition.operator) {
128
+ case FilterOperator.Equal:
129
+ return value === target;
130
+ case FilterOperator.NotEqual:
131
+ return value !== target;
132
+ case FilterOperator.GreaterThan:
133
+ return value > target;
134
+ case FilterOperator.GreaterOrEqual:
135
+ return value >= target;
136
+ case FilterOperator.LessThan:
137
+ return value < target;
138
+ case FilterOperator.LessOrEqual:
139
+ return value <= target;
140
+ default:
141
+ return true;
142
+ }
143
+ }
144
+ function matchesFilterGroup(row, group) {
145
+ if (!group)
146
+ return true;
147
+ const results = [
148
+ ...(group.conditions ?? []).map((c) => matchesCondition(row, c)),
149
+ ...(group.groups ?? []).map((g) => matchesFilterGroup(row, g)),
150
+ ];
151
+ if (results.length === 0)
152
+ return true;
153
+ return group.logic === FilterLogic.Or ? results.some(Boolean) : results.every(Boolean);
154
+ }
155
+ /** Backend `SortExpressionBuilder` gibi ilk alan `OrderBy`, gerisi `ThenBy` — çoklu sıralamayı basamak basamak uygular. */
156
+ function sortRows(rows, sort) {
157
+ if (!sort?.length)
158
+ return [...rows];
159
+ return [...rows].sort((a, b) => {
160
+ for (const { field, direction } of sort) {
161
+ const av = a[field];
162
+ const bv = b[field];
163
+ if (av === bv)
164
+ continue;
165
+ const cmp = av > bv ? 1 : -1;
166
+ return direction === SortDirection.Descending ? -cmp : cmp;
167
+ }
168
+ return 0;
169
+ });
170
+ }
171
+
172
+ const KEY_STEP = 16;
173
+ const KEY_STEP_LARGE = 64;
174
+ /**
175
+ * `<span mfTableResizeHandle [label]="..." (widthChange)="..." />` — başlık hücresinin sağ kenarındaki
176
+ * sürükleme tutamacı. WAI-ARIA "window splitter" deseni: `role="separator"` + odaklanabilir +
177
+ * `aria-valuenow`; klavyede ←/→ 16px (Shift ile 64px), çift tık / Enter "içeriğe sığdır".
178
+ *
179
+ * Başlangıç genişliği HER hareketin başında ebeveyn `<th>`'nin GERÇEK ölçüsünden okunur —
180
+ * tutamaç tablonun genişlik durumunu bilmek zorunda değil. Pointer capture sayesinde imleç
181
+ * hücreden çıksa da sürükleme sürer. RTL'de yatay yön ters çevrilir.
182
+ */
183
+ class TableResizeHandle {
184
+ label = input.required(/* @ts-ignore */
185
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
186
+ width = input(/* @ts-ignore */
187
+ ...(ngDevMode ? [undefined, { debugName: "width" }] : /* istanbul ignore next */ []));
188
+ min = input(80, /* @ts-ignore */
189
+ ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
190
+ /** ARIA üst sınırı — separator varsayılanı 100 olduğundan px değerleri için açıkça verilir. */
191
+ max = input(2000, /* @ts-ignore */
192
+ ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
193
+ /** İlk hareketten ÖNCE — tablo diğer kolonların genişliğini dondurur (bkz. `Table.freezeWidths`). */
194
+ resizeStart = output();
195
+ widthChange = output();
196
+ autofit = output();
197
+ host = inject(ElementRef);
198
+ drag = null;
199
+ cellWidth() {
200
+ return this.host.nativeElement.parentElement?.getBoundingClientRect().width ?? 0;
201
+ }
202
+ direction() {
203
+ return getComputedStyle(this.host.nativeElement).direction === 'rtl' ? -1 : 1;
204
+ }
205
+ onPointerDown(event) {
206
+ if (event.button !== 0)
207
+ return;
208
+ event.preventDefault();
209
+ this.resizeStart.emit();
210
+ this.drag = { pointerId: event.pointerId, startX: event.clientX, startWidth: this.cellWidth() };
211
+ this.host.nativeElement.setPointerCapture?.(event.pointerId);
212
+ }
213
+ onPointerMove(event) {
214
+ if (!this.drag || event.pointerId !== this.drag.pointerId)
215
+ return;
216
+ const delta = (event.clientX - this.drag.startX) * this.direction();
217
+ this.widthChange.emit(Math.max(this.min(), Math.round(this.drag.startWidth + delta)));
218
+ }
219
+ onPointerUp(event) {
220
+ if (!this.drag || event.pointerId !== this.drag.pointerId)
221
+ return;
222
+ this.host.nativeElement.releasePointerCapture?.(event.pointerId);
223
+ this.drag = null;
224
+ }
225
+ onKeydown(event) {
226
+ if (event.key === 'Enter') {
227
+ event.preventDefault();
228
+ this.autofit.emit();
229
+ return;
230
+ }
231
+ if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight')
232
+ return;
233
+ event.preventDefault();
234
+ const step = (event.shiftKey ? KEY_STEP_LARGE : KEY_STEP) * this.direction();
235
+ const delta = event.key === 'ArrowRight' ? step : -step;
236
+ this.resizeStart.emit();
237
+ const start = this.width() ?? this.cellWidth();
238
+ this.widthChange.emit(Math.max(this.min(), Math.round(start + delta)));
239
+ }
240
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: TableResizeHandle, deps: [], target: i0.ɵɵFactoryTarget.Directive });
241
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.6", type: TableResizeHandle, isStandalone: true, selector: "[mfTableResizeHandle]", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { resizeStart: "resizeStart", widthChange: "widthChange", autofit: "autofit" }, host: { attributes: { "role": "separator", "aria-orientation": "vertical", "tabindex": "0" }, listeners: { "pointerdown": "onPointerDown($event)", "pointermove": "onPointerMove($event)", "pointerup": "onPointerUp($event)", "pointercancel": "onPointerUp($event)", "keydown": "onKeydown($event)", "dblclick": "autofit.emit()", "click": "$event.stopPropagation()" }, properties: { "attr.aria-label": "label()", "attr.aria-valuenow": "width() ?? null", "attr.aria-valuemin": "min()", "attr.aria-valuemax": "max()" }, classAttribute: "absolute inset-y-0 end-0 z-[3] w-2 cursor-col-resize touch-none select-none outline-none before:absolute before:inset-y-1.5 before:end-0 before:w-0.5 before:rounded-full before:bg-transparent before:transition-colors before:duration-fast hover:before:bg-border-strong focus-visible:before:bg-focus-ring active:before:bg-accent-fill" }, ngImport: i0 });
242
+ }
243
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: TableResizeHandle, decorators: [{
244
+ type: Directive,
245
+ args: [{
246
+ selector: '[mfTableResizeHandle]',
247
+ host: {
248
+ role: 'separator',
249
+ 'aria-orientation': 'vertical',
250
+ tabindex: '0',
251
+ '[attr.aria-label]': 'label()',
252
+ '[attr.aria-valuenow]': 'width() ?? null',
253
+ '[attr.aria-valuemin]': 'min()',
254
+ '[attr.aria-valuemax]': 'max()',
255
+ class: 'absolute inset-y-0 end-0 z-[3] w-2 cursor-col-resize touch-none select-none outline-none before:absolute before:inset-y-1.5 before:end-0 before:w-0.5 before:rounded-full before:bg-transparent before:transition-colors before:duration-fast hover:before:bg-border-strong focus-visible:before:bg-focus-ring active:before:bg-accent-fill',
256
+ '(pointerdown)': 'onPointerDown($event)',
257
+ '(pointermove)': 'onPointerMove($event)',
258
+ '(pointerup)': 'onPointerUp($event)',
259
+ '(pointercancel)': 'onPointerUp($event)',
260
+ '(keydown)': 'onKeydown($event)',
261
+ '(dblclick)': 'autofit.emit()',
262
+ '(click)': '$event.stopPropagation()',
263
+ },
264
+ }]
265
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], resizeStart: [{ type: i0.Output, args: ["resizeStart"] }], widthChange: [{ type: i0.Output, args: ["widthChange"] }], autofit: [{ type: i0.Output, args: ["autofit"] }] } });
266
+
267
+ /**
268
+ * Toolbar'daki "Kolonlar" düğmesine bağlı basit bir görünürlük paneli.
269
+ * Sunucuya HİÇBİR ŞEY göndermez — `Table` `hiddenColumns` sinyalini kendi
270
+ * içinde tutar (bkz. table.ts), bu yalnızca o sinyali değiştiren bir UI.
271
+ * `primary` işaretli kolon listede hiç görünmez — kimlik kolonu kapatılamaz.
272
+ * Panel iskeleti `Popover`'a devredildi (bkz. plan Faz 1c).
273
+ */
274
+ class TableColumnsMenu {
275
+ columns = input.required(/* @ts-ignore */
276
+ ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
277
+ hidden = input.required(/* @ts-ignore */
278
+ ...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
279
+ origin = input.required(/* @ts-ignore */
280
+ ...(ngDevMode ? [{ debugName: "origin" }] : /* istanbul ignore next */ []));
281
+ open = input.required(/* @ts-ignore */
282
+ ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
283
+ label = input(/* @ts-ignore */
284
+ ...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
285
+ tableLabels = inject(MF_TABLE_LABELS);
286
+ effectiveLabel = computed(() => this.label() ?? this.tableLabels().columns, /* @ts-ignore */
287
+ ...(ngDevMode ? [{ debugName: "effectiveLabel" }] : /* istanbul ignore next */ []));
288
+ hiddenChange = output();
289
+ closed = output();
290
+ toggleable = computed(() => this.columns().filter((c) => !c.primary && c.hideable !== false), /* @ts-ignore */
291
+ ...(ngDevMode ? [{ debugName: "toggleable" }] : /* istanbul ignore next */ []));
292
+ isHidden(field) {
293
+ return this.hidden().has(field);
294
+ }
295
+ toggle(field) {
296
+ const next = new Set(this.hidden());
297
+ if (next.has(field))
298
+ next.delete(field);
299
+ else
300
+ next.add(field);
301
+ this.hiddenChange.emit(next);
302
+ }
303
+ close() {
304
+ this.closed.emit();
305
+ }
306
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: TableColumnsMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
307
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: TableColumnsMenu, isStandalone: true, selector: "mf-table-columns-menu", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: true, transformFunction: null }, origin: { classPropertyName: "origin", publicName: "origin", isSignal: true, isRequired: true, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { hiddenChange: "hiddenChange", closed: "closed" }, host: { classAttribute: "contents" }, ngImport: i0, template: "<mf-popover\r\n [origin]=\"origin()\"\r\n [open]=\"open()\"\r\n placement=\"bottom-end\"\r\n padding=\"md\"\r\n panelClass=\"w-56\"\r\n (closed)=\"close()\"\r\n>\r\n <p class=\"mb-1 px-1.5 py-1 text-eyebrow text-fg-subtle\">{{ effectiveLabel() }}</p>\r\n <ul class=\"flex list-none flex-col gap-0.5\">\r\n @for (column of toggleable(); track column.field) {\r\n <li>\r\n <label\r\n class=\"flex cursor-pointer items-center gap-2 rounded-sm px-2.5 py-1.5 text-sm text-fg-muted transition-colors duration-fast ease-emphasis hover:bg-surface-alt\"\r\n >\r\n <input\r\n type=\"checkbox\"\r\n mfCheckbox\r\n [checked]=\"!isHidden(column.field)\"\r\n (change)=\"toggle(column.field)\"\r\n />\r\n <span class=\"truncate\">{{ column.header }}</span>\r\n </label>\r\n </li>\r\n }\r\n </ul>\r\n</mf-popover>\r\n", dependencies: [{ kind: "directive", type: Checkbox, selector: "input[type=checkbox][mfCheckbox]", inputs: ["size"] }, { kind: "component", type: Popover, selector: "mf-popover", inputs: ["origin", "open", "placement", "padding", "hasBackdrop", "matchTriggerWidth", "panelClass"], outputs: ["openChange", "closed"], exportAs: ["mfPopover"] }] });
308
+ }
309
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: TableColumnsMenu, decorators: [{
310
+ type: Component,
311
+ args: [{ selector: 'mf-table-columns-menu', imports: [Checkbox, Popover], host: { class: 'contents' }, template: "<mf-popover\r\n [origin]=\"origin()\"\r\n [open]=\"open()\"\r\n placement=\"bottom-end\"\r\n padding=\"md\"\r\n panelClass=\"w-56\"\r\n (closed)=\"close()\"\r\n>\r\n <p class=\"mb-1 px-1.5 py-1 text-eyebrow text-fg-subtle\">{{ effectiveLabel() }}</p>\r\n <ul class=\"flex list-none flex-col gap-0.5\">\r\n @for (column of toggleable(); track column.field) {\r\n <li>\r\n <label\r\n class=\"flex cursor-pointer items-center gap-2 rounded-sm px-2.5 py-1.5 text-sm text-fg-muted transition-colors duration-fast ease-emphasis hover:bg-surface-alt\"\r\n >\r\n <input\r\n type=\"checkbox\"\r\n mfCheckbox\r\n [checked]=\"!isHidden(column.field)\"\r\n (change)=\"toggle(column.field)\"\r\n />\r\n <span class=\"truncate\">{{ column.header }}</span>\r\n </label>\r\n </li>\r\n }\r\n </ul>\r\n</mf-popover>\r\n" }]
312
+ }], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], hidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidden", required: true }] }], origin: [{ type: i0.Input, args: [{ isSignal: true, alias: "origin", required: true }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hiddenChange: [{ type: i0.Output, args: ["hiddenChange"] }], closed: [{ type: i0.Output, args: ["closed"] }] } });
313
+
314
+ const OPERATOR_KEY = {
315
+ [FilterOperator.Equal]: 'equal',
316
+ [FilterOperator.NotEqual]: 'notEqual',
317
+ [FilterOperator.GreaterThan]: 'greaterThan',
318
+ [FilterOperator.GreaterOrEqual]: 'greaterOrEqual',
319
+ [FilterOperator.LessThan]: 'lessThan',
320
+ [FilterOperator.LessOrEqual]: 'lessOrEqual',
321
+ [FilterOperator.Contains]: 'contains',
322
+ [FilterOperator.StartsWith]: 'startsWith',
323
+ [FilterOperator.EndsWith]: 'endsWith',
324
+ [FilterOperator.In]: 'in',
325
+ [FilterOperator.IsNull]: 'isNull',
326
+ [FilterOperator.IsNotNull]: 'isNotNull',
327
+ };
328
+ function operatorLabel(labels, operator) {
329
+ if (operator === 'between')
330
+ return labels.operators.between;
331
+ const key = OPERATOR_KEY[operator];
332
+ return key ? labels.operators[key] : String(operator);
333
+ }
334
+
335
+ /** Tip başına filtre menüsünde sunulacak operatörler. `In` burada YOK — çoklu seçim ayrı bir jest (checkbox listesi), operatör dropdown'ında bir satır değil. */
336
+ const OPERATORS_BY_TYPE = {
337
+ text: [
338
+ FilterOperator.Contains,
339
+ FilterOperator.Equal,
340
+ FilterOperator.NotEqual,
341
+ FilterOperator.StartsWith,
342
+ FilterOperator.EndsWith,
343
+ FilterOperator.IsNull,
344
+ FilterOperator.IsNotNull,
345
+ ],
346
+ select: [FilterOperator.Equal, FilterOperator.NotEqual],
347
+ boolean: [FilterOperator.Equal],
348
+ date: [
349
+ 'between',
350
+ FilterOperator.GreaterOrEqual,
351
+ FilterOperator.LessOrEqual,
352
+ FilterOperator.Equal,
353
+ ],
354
+ };
355
+ function defaultOperatorFor(type) {
356
+ return OPERATORS_BY_TYPE[type][0];
357
+ }
358
+ /** `IsNull`/`IsNotNull` değer almaz — seçilir seçilmez uygulanır, değer kutusu gösterilmez. */
359
+ function operatorNeedsValue(operator) {
360
+ return operator !== FilterOperator.IsNull && operator !== FilterOperator.IsNotNull;
361
+ }
362
+ /**
363
+ * Chip metninde operatör YAZILMAZ eğer o tip için varsayılan operatörse.
364
+ * "Rol: Yönetici" okunur; "Rol eşittir Yönetici" aynı bilgiyi taşıyıp
365
+ * üç kat yer kaplar. Varsayılandan sapma ise BİLGİ taşır ve gösterilir.
366
+ */
367
+ function isDefaultOperator(operator, type) {
368
+ return operator === defaultOperatorFor(type);
369
+ }
370
+ /**
371
+ * `2026-09-01T00:00:00+03:00` — YEREL gün sınırı. `toISOString()` KULLANMA:
372
+ * UTC'ye kaydırır, "bugün" filtresi saat farkı kadar yanlış güne düşer.
373
+ * `export` — `dateToIso` (aşağıda) ve testler dışında artık DatePicker/
374
+ * Calendar'ın ürettiği `Date` nesnelerini backend'in ISO metnine çeviren TEK
375
+ * yer burası (bkz. `table-filter-menu.ts`).
376
+ */
377
+ function localIso(date, endOfDay) {
378
+ const pad = (n, width = 2) => String(n).padStart(width, '0');
379
+ const time = endOfDay ? '23:59:59.999' : '00:00:00.000';
380
+ const offsetMinutes = -date.getTimezoneOffset();
381
+ const sign = offsetMinutes >= 0 ? '+' : '-';
382
+ const absolute = Math.abs(offsetMinutes);
383
+ const offset = `${sign}${pad(Math.floor(absolute / 60))}:${pad(absolute % 60)}`;
384
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${time}${offset}`;
385
+ }
386
+ /** `Calendar`'ın seçtiği bir günü backend'in beklediği ISO metnine çevirir — `localIso`'nun `Date`-girdili biçimi. */
387
+ function dateToIso(date, endOfDay) {
388
+ return localIso(date, endOfDay);
389
+ }
390
+ /** ISO metnini (ya da `null`/tanımsız değeri) `Calendar`'ın çalıştığı `Date`'e çevirir — gün sınırına (`startOfDay`) normalize edilir, saat bileşeni Calendar'ı ilgilendirmiyor. */
391
+ function isoToDate(value) {
392
+ if (typeof value !== 'string' || !value)
393
+ return null;
394
+ const date = new Date(value);
395
+ return Number.isNaN(date.getTime()) ? null : startOfDay(date);
396
+ }
397
+ function toNode(filter) {
398
+ if (filter.operator === 'between') {
399
+ const [from, to] = filter.value ?? [null, null];
400
+ const conditions = [];
401
+ if (from)
402
+ conditions.push({
403
+ field: filter.field,
404
+ operator: FilterOperator.GreaterOrEqual,
405
+ value: from,
406
+ });
407
+ if (to)
408
+ conditions.push({ field: filter.field, operator: FilterOperator.LessOrEqual, value: to });
409
+ return { logic: FilterLogic.And, conditions };
410
+ }
411
+ if (!operatorNeedsValue(filter.operator)) {
412
+ return { field: filter.field, operator: filter.operator };
413
+ }
414
+ // Çoklu seçim: TEK değer kaldıysa `In` yerine `Equal` gönderiyoruz — backend
415
+ // ikisini de destekliyor ama `Equal` hem sorgu planı hem okunabilirlik için
416
+ // daha dürüst.
417
+ if (Array.isArray(filter.value)) {
418
+ return filter.value.length === 1
419
+ ? { field: filter.field, operator: FilterOperator.Equal, value: filter.value[0] }
420
+ : { field: filter.field, operator: FilterOperator.In, value: filter.value };
421
+ }
422
+ return { field: filter.field, operator: filter.operator, value: filter.value };
423
+ }
424
+ function isGroup(node) {
425
+ return 'logic' in node;
426
+ }
427
+ /**
428
+ * Chip listesini backend'in `FilterGroup` ağacına çevirir. Tarih aralığı
429
+ * chip'i kendi iç `And` grubunu getirdiği için ağaç derinliği en fazla
430
+ * 3 olur — tipik backend sınırları (`MaxDepth = 5`, `MaxNodesPerGroup = 25`)
431
+ * içinde rahat rahat kalıyoruz.
432
+ */
433
+ function buildFilterGroup(filters, logic) {
434
+ if (filters.length === 0)
435
+ return undefined;
436
+ const nodes = filters.map(toNode);
437
+ const conditions = nodes.filter((node) => !isGroup(node));
438
+ const groups = nodes.filter(isGroup).filter((group) => (group.conditions?.length ?? 0) > 0);
439
+ if (conditions.length === 0 && groups.length === 0)
440
+ return undefined;
441
+ // Tek bir alt grup varsa gereksiz sarmalayıcı üretme — payload'ı kanonik
442
+ // tutuyoruz (backend cache anahtarı ham JSON string'i, bkz. query.model.ts).
443
+ if (conditions.length === 0 && groups.length === 1)
444
+ return groups[0];
445
+ const result = { logic };
446
+ if (conditions.length)
447
+ result.conditions = conditions;
448
+ if (groups.length)
449
+ result.groups = groups;
450
+ return result;
451
+ }
452
+
453
+ /**
454
+ * Bir filtre kosulunun duzenleyicisi — IKI modda calisir, TEK bilesen:
455
+ *
456
+ * - **Ekleme** (`initial` = `null`): iki adim. Once alan arama listesi, alan
457
+ * secilince ayni overlay icinde deger adimina gecer.
458
+ * - **Duzenleme** (`initial` doldurulmus, bkz. `TableFilterBar` — her chip
459
+ * kendi overlay'ini bu bilesenle acar): dogrudan deger adimiyla baslar,
460
+ * alan sabit kalir (bir filtrenin ALANINI degistirmek olagan disi bir jest
461
+ * — kullanici bunun yerine chip'i kaldirip yenisini ekler).
462
+ *
463
+ * Onay butonu YOK felsefesi tipe gore farkli uygulanir: `select`/`boolean`
464
+ * checkbox'i her tik ANINDA `apply` emit eder ve overlay ACIK kalir (coklu
465
+ * secim dogal akisi — kullanici birden cok kutuyu art arda isaretler).
466
+ * `text`/`date` bir "taahhut" ani gerektirir (Enter / preset tik / Uygula) —
467
+ * o anda hem `apply` HEM `closed` emit edilir.
468
+ */
469
+ class TableFilterMenu {
470
+ columns = input.required(/* @ts-ignore */
471
+ ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
472
+ origin = input.required(/* @ts-ignore */
473
+ ...(ngDevMode ? [{ debugName: "origin" }] : /* istanbul ignore next */ []));
474
+ open = input.required(/* @ts-ignore */
475
+ ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
476
+ /** Doluysa duzenleme modu (alan sabit, adim='value'); `null` ise ekleme modu (adim='field'). */
477
+ initial = input(null, /* @ts-ignore */
478
+ ...(ngDevMode ? [{ debugName: "initial" }] : /* istanbul ignore next */ []));
479
+ labels = input(/* @ts-ignore */
480
+ ...(ngDevMode ? [undefined, { debugName: "labels" }] : /* istanbul ignore next */ []));
481
+ tableLabels = inject(MF_TABLE_LABELS);
482
+ effectiveLabels = computed(() => this.labels() ?? this.tableLabels().filter, /* @ts-ignore */
483
+ ...(ngDevMode ? [{ debugName: "effectiveLabels" }] : /* istanbul ignore next */ []));
484
+ /** Her zaman TAM bir `AppliedFilter` — ekleme modunda taze `id`, duzenlemede `initial.id` korunur. Ust bilesen (`TableFilterBar`) id'ye gore upsert yapar. */
485
+ apply = output();
486
+ /** Kaldirilan filtrenin id'si — checkbox listesi sifir secime inince (ekleme YA DA duzenleme modunda) emit edilir, ust bilesen o id'yi chip listesinden cikarir. */
487
+ remove = output();
488
+ closed = output();
489
+ step = signal('field', /* @ts-ignore */
490
+ ...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
491
+ fieldQuery = signal('', /* @ts-ignore */
492
+ ...(ngDevMode ? [{ debugName: "fieldQuery" }] : /* istanbul ignore next */ []));
493
+ fieldHighlighted = signal(0, /* @ts-ignore */
494
+ ...(ngDevMode ? [{ debugName: "fieldHighlighted" }] : /* istanbul ignore next */ []));
495
+ selectedField = signal(null, /* @ts-ignore */
496
+ ...(ngDevMode ? [{ debugName: "selectedField" }] : /* istanbul ignore next */ []));
497
+ operator = signal(FilterOperator.Equal, /* @ts-ignore */
498
+ ...(ngDevMode ? [{ debugName: "operator" }] : /* istanbul ignore next */ []));
499
+ /** `select`/`boolean` icin HER ZAMAN dizi (uzunluk 0/1/N) — tekil/coklu ayrimi yalnizca `toggleSelection`'da, emit aninda yapilir. */
500
+ selection = signal([], /* @ts-ignore */
501
+ ...(ngDevMode ? [{ debugName: "selection" }] : /* istanbul ignore next */ []));
502
+ textValue = signal('', /* @ts-ignore */
503
+ ...(ngDevMode ? [{ debugName: "textValue" }] : /* istanbul ignore next */ []));
504
+ customDateOpen = signal(false, /* @ts-ignore */
505
+ ...(ngDevMode ? [{ debugName: "customDateOpen" }] : /* istanbul ignore next */ []));
506
+ dateFrom = signal(null, /* @ts-ignore */
507
+ ...(ngDevMode ? [{ debugName: "dateFrom" }] : /* istanbul ignore next */ []));
508
+ dateTo = signal(null, /* @ts-ignore */
509
+ ...(ngDevMode ? [{ debugName: "dateTo" }] : /* istanbul ignore next */ []));
510
+ /** `'start'` bir sonraki takvim tıklaması `dateFrom`'u YENİDEN başlatır (yeni oturum); `'end'` ikinci ucu belirler. */
511
+ datePickStage = signal('start', /* @ts-ignore */
512
+ ...(ngDevMode ? [{ debugName: "datePickStage" }] : /* istanbul ignore next */ []));
513
+ /** Fare ikinci ucun üzerinde gezinirken önizleme — bkz. `calendar.ts` `hoverEnd`. */
514
+ dateHoverEnd = signal(null, /* @ts-ignore */
515
+ ...(ngDevMode ? [{ debugName: "dateHoverEnd" }] : /* istanbul ignore next */ []));
516
+ calendarMonth = signal(startOfMonth(new Date()), /* @ts-ignore */
517
+ ...(ngDevMode ? [{ debugName: "calendarMonth" }] : /* istanbul ignore next */ []));
518
+ /**
519
+ * Ekleme modunda `initial()` HER ZAMAN `null` kalir — bu yuzden ayni "ekleme
520
+ * oturumu" icinde art arda checkbox tiklamalari (bkz. `toggleSelection`)
521
+ * kendi baslarina birden fazla YENI filtre olustururdu, ayni chip'i
522
+ * GUNCELLEMEK yerine (gercek bulgu: "Yonetici" + "Editor" iki AYRI chip
523
+ * olarak beliriyordu). Bu sinyal, bu oturumda uretilen ilk id'yi hatirlayip
524
+ * sonraki her `emitApply`'de AYNI id'yi kullanmayi saglar — `reset()`
525
+ * (yeni oturum) ve `selectField()` (farkli bir alana gecis) sifirlar.
526
+ */
527
+ draftId = signal(null, /* @ts-ignore */
528
+ ...(ngDevMode ? [{ debugName: "draftId" }] : /* istanbul ignore next */ []));
529
+ fieldSearchEl = viewChild('fieldSearchEl', /* @ts-ignore */
530
+ ...(ngDevMode ? [{ debugName: "fieldSearchEl" }] : /* istanbul ignore next */ []));
531
+ filterableColumns = computed(() => this.columns().filter((c) => !!c.filter), /* @ts-ignore */
532
+ ...(ngDevMode ? [{ debugName: "filterableColumns" }] : /* istanbul ignore next */ []));
533
+ filteredColumns = computed(() => {
534
+ const q = this.fieldQuery().trim().toLocaleLowerCase('tr');
535
+ const all = this.filterableColumns();
536
+ if (!q)
537
+ return all;
538
+ return all.filter((c) => c.header.toLocaleLowerCase('tr').includes(q));
539
+ }, /* @ts-ignore */
540
+ ...(ngDevMode ? [{ debugName: "filteredColumns" }] : /* istanbul ignore next */ []));
541
+ constructor() {
542
+ // Overlay her acilista taze baslar — duzenleme modunda `initial`'dan,
543
+ // ekleme modunda bostan. `open()` bir kez true'ya donduginde okunur;
544
+ // sonraki her checkbox/metin degisikligi ARTIK kendi sinyallerinde yasar
545
+ // (bu effect'in tekrar tetiklenip taslagi sifirlamamasi icin `open()`
546
+ // DISINDA hicbir seye bagimli degil — `untracked` ile korunuyor).
547
+ effect(() => {
548
+ if (!this.open())
549
+ return;
550
+ untracked(() => this.reset());
551
+ });
552
+ }
553
+ reset() {
554
+ const init = this.initial();
555
+ this.fieldQuery.set('');
556
+ this.fieldHighlighted.set(0);
557
+ this.customDateOpen.set(false);
558
+ this.dateFrom.set(null);
559
+ this.dateTo.set(null);
560
+ this.datePickStage.set('start');
561
+ this.dateHoverEnd.set(null);
562
+ this.calendarMonth.set(startOfMonth(new Date()));
563
+ if (!init) {
564
+ this.step.set('field');
565
+ this.selectedField.set(null);
566
+ this.selection.set([]);
567
+ this.textValue.set('');
568
+ this.draftId.set(null);
569
+ queueMicrotask(() => this.fieldSearchEl()?.nativeElement.focus());
570
+ return;
571
+ }
572
+ this.step.set('value');
573
+ this.selectedField.set(init.field);
574
+ this.operator.set(init.operator);
575
+ const type = this.filterTypeFor(init.field);
576
+ if (type === 'select' || type === 'boolean') {
577
+ this.selection.set(Array.isArray(init.value)
578
+ ? init.value
579
+ : init.value === null || init.value === undefined
580
+ ? []
581
+ : [init.value]);
582
+ }
583
+ else if (type === 'date' && init.operator === 'between') {
584
+ const [from, to] = init.value ?? [null, null];
585
+ const fromDate = isoToDate(from);
586
+ const toDate = isoToDate(to);
587
+ this.dateFrom.set(fromDate);
588
+ this.dateTo.set(toDate);
589
+ this.datePickStage.set(fromDate && !toDate ? 'end' : 'start');
590
+ this.calendarMonth.set(startOfMonth(fromDate ?? new Date()));
591
+ this.customDateOpen.set(true);
592
+ }
593
+ else {
594
+ this.textValue.set(typeof init.value === 'string' ? init.value : '');
595
+ }
596
+ }
597
+ columnFor(field) {
598
+ return this.columns().find((c) => c.field === field);
599
+ }
600
+ filterTypeFor(field) {
601
+ return this.columnFor(field)?.filter?.type ?? 'text';
602
+ }
603
+ fieldHeader(field) {
604
+ return this.columnFor(field)?.header ?? field;
605
+ }
606
+ /** `cn()` (twMerge) — base `bg-transparent` ve vurgulu `bg-surface-alt` AYNI class dizesinde çakışırsa (düz string birleştirmede olduğu gibi) hangisinin kazanacağı Tailwind'in derleme sırasına kalır; twMerge çakışan `bg-*` çiftini güvenle çözer. */
607
+ fieldOptionClasses(index) {
608
+ return cn('flex w-full cursor-pointer appearance-none items-center justify-between gap-2 border-0 bg-transparent rounded-sm px-2.5 py-1.5 text-start text-sm text-fg-muted transition-colors duration-fast ease-emphasis', index === this.fieldHighlighted() && 'bg-surface-alt');
609
+ }
610
+ operatorOptions() {
611
+ const field = this.selectedField();
612
+ if (!field)
613
+ return [];
614
+ const column = this.columnFor(field);
615
+ const type = this.filterTypeFor(field);
616
+ const operators = column?.filter?.operators ?? OPERATORS_BY_TYPE[type];
617
+ return operators
618
+ .filter((op) => op !== 'between')
619
+ .map((op) => ({ label: operatorLabel(this.effectiveLabels(), op), value: op }));
620
+ }
621
+ selectOptions() {
622
+ const field = this.selectedField();
623
+ if (!field)
624
+ return [];
625
+ return (this.columnFor(field)?.filter?.options ?? []).map((o) => ({
626
+ label: o.label,
627
+ value: o.value,
628
+ }));
629
+ }
630
+ isSelected(value) {
631
+ return this.selection().includes(value);
632
+ }
633
+ /** Template'te magic-number karsilastirmasi (`operator() !== 10`) yerine — `IsNull`/`IsNotNull` deger kutusu gostermez. */
634
+ currentOperatorNeedsValue() {
635
+ return operatorNeedsValue(this.operator());
636
+ }
637
+ onFieldQueryKeydown(event) {
638
+ const items = this.filteredColumns();
639
+ if (event.key === 'ArrowDown') {
640
+ event.preventDefault();
641
+ this.fieldHighlighted.update((i) => Math.min(items.length - 1, i + 1));
642
+ }
643
+ else if (event.key === 'ArrowUp') {
644
+ event.preventDefault();
645
+ this.fieldHighlighted.update((i) => Math.max(0, i - 1));
646
+ }
647
+ else if (event.key === 'Enter') {
648
+ event.preventDefault();
649
+ const item = items[this.fieldHighlighted()];
650
+ if (item)
651
+ this.selectField(item.field);
652
+ }
653
+ else if (event.key === 'Escape') {
654
+ event.preventDefault();
655
+ this.closed.emit();
656
+ }
657
+ }
658
+ selectField(field) {
659
+ this.selectedField.set(field);
660
+ const type = this.filterTypeFor(field);
661
+ this.operator.set(defaultOperatorFor(type));
662
+ this.selection.set([]);
663
+ this.textValue.set('');
664
+ // Farkli bir alana gecis = yeni bir filtre taslagi — onceki alanda
665
+ // uretilmis olabilecek id'yi TASIMA (bkz. draftId yorumu).
666
+ this.draftId.set(null);
667
+ this.step.set('value');
668
+ }
669
+ backToField() {
670
+ this.step.set('field');
671
+ this.fieldQuery.set('');
672
+ this.fieldHighlighted.set(0);
673
+ queueMicrotask(() => this.fieldSearchEl()?.nativeElement.focus());
674
+ }
675
+ /** `select`/`boolean` — her tik aninda uygular, overlay ACIK kalir (bkz. sinif yorumu). */
676
+ toggleSelection(value) {
677
+ const field = this.selectedField();
678
+ if (!field)
679
+ return;
680
+ const current = this.selection();
681
+ const next = current.includes(value) ? current.filter((v) => v !== value) : [...current, value];
682
+ this.selection.set(next);
683
+ if (next.length === 0) {
684
+ const id = this.initial()?.id ?? this.draftId();
685
+ if (id)
686
+ this.remove.emit(id);
687
+ this.draftId.set(null);
688
+ return;
689
+ }
690
+ const operator = next.length === 1 ? FilterOperator.Equal : FilterOperator.In;
691
+ const value2 = next.length === 1 ? next[0] : next;
692
+ this.emitApply(field, operator, value2, false);
693
+ }
694
+ onOperatorChange(operator) {
695
+ if (operator === null)
696
+ return;
697
+ this.operator.set(operator);
698
+ if (!operatorNeedsValue(operator))
699
+ this.commitText();
700
+ }
701
+ onTextInput(raw) {
702
+ this.textValue.set(raw);
703
+ }
704
+ commitText() {
705
+ const field = this.selectedField();
706
+ if (!field)
707
+ return;
708
+ const operator = this.operator();
709
+ if (operatorNeedsValue(operator) && !this.textValue().trim())
710
+ return;
711
+ this.emitApply(field, operator, operatorNeedsValue(operator) ? this.textValue().trim() : undefined, true);
712
+ }
713
+ applyPreset(preset) {
714
+ const field = this.selectedField();
715
+ if (!field)
716
+ return;
717
+ const range = dateRangeForPreset(preset);
718
+ this.emitApply(field, 'between', [dateToIso(range.start, false), dateToIso(range.end, true)], true);
719
+ }
720
+ openCustomRange() {
721
+ this.customDateOpen.set(true);
722
+ }
723
+ /** İki tıklamalı aralık seçimi — emsal: `date-range-picker.ts` `onDayPick` (aynı mantık, burada tek gömülü takvim). */
724
+ onCalendarDayPick(day) {
725
+ if (this.datePickStage() === 'start' || (this.dateFrom() && this.dateTo())) {
726
+ this.dateFrom.set(day);
727
+ this.dateTo.set(null);
728
+ this.datePickStage.set('end');
729
+ this.dateHoverEnd.set(null);
730
+ return;
731
+ }
732
+ const start = this.dateFrom();
733
+ if (day < start) {
734
+ this.dateFrom.set(day);
735
+ this.dateTo.set(start);
736
+ }
737
+ else {
738
+ this.dateTo.set(day);
739
+ }
740
+ this.datePickStage.set('start');
741
+ this.dateHoverEnd.set(null);
742
+ }
743
+ onCalendarDayHover(day) {
744
+ this.dateHoverEnd.set(day);
745
+ }
746
+ commitCustomRange() {
747
+ const field = this.selectedField();
748
+ if (!field)
749
+ return;
750
+ const from = this.dateFrom();
751
+ const to = this.dateTo();
752
+ if (!from && !to)
753
+ return;
754
+ this.emitApply(field, 'between', [from ? dateToIso(from, false) : null, to ? dateToIso(to, true) : null], true);
755
+ }
756
+ emitApply(field, operator, value, closeAfter) {
757
+ const id = this.initial()?.id ?? this.draftId() ?? crypto.randomUUID();
758
+ if (!this.initial() && !this.draftId())
759
+ this.draftId.set(id);
760
+ this.apply.emit({ id, field, operator, value });
761
+ if (closeAfter)
762
+ this.closed.emit();
763
+ }
764
+ close() {
765
+ this.closed.emit();
766
+ }
767
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: TableFilterMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
768
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: TableFilterMenu, isStandalone: true, selector: "mf-table-filter-menu", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, origin: { classPropertyName: "origin", publicName: "origin", isSignal: true, isRequired: true, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: true, transformFunction: null }, initial: { classPropertyName: "initial", publicName: "initial", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { apply: "apply", remove: "remove", closed: "closed" }, host: { classAttribute: "contents" }, viewQueries: [{ propertyName: "fieldSearchEl", first: true, predicate: ["fieldSearchEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<mf-popover\r\n [origin]=\"origin()\"\r\n [open]=\"open()\"\r\n placement=\"bottom-start\"\r\n padding=\"md\"\r\n panelClass=\"w-72\"\r\n (closed)=\"close()\"\r\n>\r\n @if (step() === 'field') {\r\n <div class=\"relative mb-1.5\">\r\n <span\r\n mfIcon\r\n [size]=\"16\"\r\n class=\"pointer-events-none absolute top-1/2 start-2 -translate-y-1/2 text-fg-subtle\"\r\n >search</span\r\n >\r\n <input\r\n mfInput\r\n #fieldSearchEl\r\n type=\"text\"\r\n class=\"ps-7 text-sm\"\r\n [placeholder]=\"effectiveLabels().searchField\"\r\n [value]=\"fieldQuery()\"\r\n (input)=\"fieldQuery.set($any($event.target).value)\"\r\n (keydown)=\"onFieldQueryKeydown($event)\"\r\n />\r\n </div>\r\n @if (filteredColumns().length === 0) {\r\n <p class=\"px-1.5 py-2 text-sm text-fg-subtle\">{{ effectiveLabels().noField }}</p>\r\n } @else {\r\n <ul class=\"flex max-h-64 list-none flex-col gap-0.5 overflow-auto\">\r\n @for (column of filteredColumns(); track column.field; let i = $index) {\r\n <li>\r\n <button\r\n type=\"button\"\r\n [class]=\"fieldOptionClasses(i)\"\r\n (mouseenter)=\"fieldHighlighted.set(i)\"\r\n (click)=\"selectField(column.field)\"\r\n >\r\n {{ column.header }}\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n } @else {\r\n <div class=\"mb-1.5 flex items-center gap-1 px-0.5\">\r\n @if (!initial()) {\r\n <button\r\n type=\"button\"\r\n class=\"appearance-none border-0 bg-transparent p-0 text-fg-subtle transition-colors duration-fast ease-emphasis hover:text-fg\"\r\n [attr.aria-label]=\"effectiveLabels().back\"\r\n (click)=\"backToField()\"\r\n >\r\n <span mfIcon [size]=\"16\" class=\"rtl:-scale-x-100\">arrow_back</span>\r\n </button>\r\n }\r\n <span class=\"text-sm font-medium text-fg\">{{ fieldHeader(selectedField() ?? '') }}</span>\r\n </div>\r\n\r\n @switch (filterTypeFor(selectedField() ?? '')) {\r\n @case ('select') {\r\n <ul class=\"flex max-h-64 list-none flex-col gap-0.5 overflow-auto\">\r\n @for (option of selectOptions(); track option.label) {\r\n <li>\r\n <label\r\n class=\"flex cursor-pointer items-center gap-2 rounded-sm px-2.5 py-1.5 text-sm text-fg-muted transition-colors duration-fast ease-emphasis hover:bg-surface-alt\"\r\n >\r\n <input\r\n type=\"checkbox\"\r\n mfCheckbox\r\n [checked]=\"isSelected(option.value)\"\r\n (change)=\"toggleSelection(option.value)\"\r\n />\r\n <span class=\"truncate\">{{ option.label }}</span>\r\n </label>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n @case ('boolean') {\r\n <ul class=\"flex list-none flex-col gap-0.5\">\r\n @for (option of selectOptions(); track option.label) {\r\n <li>\r\n <label\r\n class=\"flex cursor-pointer items-center gap-2 rounded-sm px-2.5 py-1.5 text-sm text-fg-muted transition-colors duration-fast ease-emphasis hover:bg-surface-alt\"\r\n >\r\n <input\r\n type=\"checkbox\"\r\n mfCheckbox\r\n [checked]=\"isSelected(option.value)\"\r\n (change)=\"toggleSelection(option.value)\"\r\n />\r\n <span class=\"truncate\">{{ option.label }}</span>\r\n </label>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n @case ('date') {\r\n <div class=\"flex flex-col gap-1\">\r\n <button\r\n type=\"button\"\r\n class=\"appearance-none border-0 bg-transparent rounded-sm px-2.5 py-1.5 text-start text-sm text-fg-muted transition-colors duration-fast ease-emphasis hover:bg-surface-alt\"\r\n (click)=\"applyPreset('today')\"\r\n >\r\n {{ effectiveLabels().dateToday }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"appearance-none border-0 bg-transparent rounded-sm px-2.5 py-1.5 text-start text-sm text-fg-muted transition-colors duration-fast ease-emphasis hover:bg-surface-alt\"\r\n (click)=\"applyPreset('last7')\"\r\n >\r\n {{ effectiveLabels().dateLast7 }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"appearance-none border-0 bg-transparent rounded-sm px-2.5 py-1.5 text-start text-sm text-fg-muted transition-colors duration-fast ease-emphasis hover:bg-surface-alt\"\r\n (click)=\"applyPreset('last30')\"\r\n >\r\n {{ effectiveLabels().dateLast30 }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"appearance-none border-0 bg-transparent rounded-sm px-2.5 py-1.5 text-start text-sm text-fg-muted transition-colors duration-fast ease-emphasis hover:bg-surface-alt\"\r\n (click)=\"applyPreset('thisMonth')\"\r\n >\r\n {{ effectiveLabels().dateThisMonth }}\r\n </button>\r\n @if (!customDateOpen()) {\r\n <button\r\n type=\"button\"\r\n class=\"appearance-none border-0 bg-transparent rounded-sm px-2.5 py-1.5 text-start text-sm text-accent transition-colors duration-fast ease-emphasis hover:bg-accent/5\"\r\n (click)=\"openCustomRange()\"\r\n >\r\n {{ effectiveLabels().dateCustom }}\r\n </button>\r\n } @else {\r\n <div class=\"mt-1 flex flex-col gap-2 border-t border-border pt-2\">\r\n <mf-calendar\r\n mode=\"range\"\r\n [(month)]=\"calendarMonth\"\r\n [rangeStart]=\"dateFrom()\"\r\n [rangeEnd]=\"dateTo()\"\r\n [hoverEnd]=\"dateHoverEnd()\"\r\n (dayPick)=\"onCalendarDayPick($event)\"\r\n (dayHover)=\"onCalendarDayHover($event)\"\r\n />\r\n <div class=\"flex justify-end\">\r\n <button\r\n mfButton\r\n tone=\"primary\"\r\n appearance=\"filled\"\r\n size=\"sm\"\r\n type=\"button\"\r\n [disabled]=\"!dateFrom()\"\r\n (click)=\"commitCustomRange()\"\r\n >\r\n {{ effectiveLabels().apply }}\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n @default {\r\n <div class=\"flex flex-col gap-2\">\r\n <mf-select\r\n [options]=\"operatorOptions()\"\r\n [value]=\"operator()\"\r\n (valueChange)=\"onOperatorChange($event)\"\r\n size=\"sm\"\r\n [ariaLabel]=\"effectiveLabels().operator\"\r\n />\r\n @if (currentOperatorNeedsValue()) {\r\n <input\r\n mfInput\r\n type=\"text\"\r\n class=\"text-sm\"\r\n [attr.aria-label]=\"effectiveLabels().value\"\r\n [placeholder]=\"effectiveLabels().value\"\r\n [value]=\"textValue()\"\r\n (input)=\"onTextInput($any($event.target).value)\"\r\n (keydown.enter)=\"commitText()\"\r\n />\r\n <div class=\"flex items-center justify-between\">\r\n <span class=\"text-xs text-fg-subtle\">{{ effectiveLabels().enterHint }}</span>\r\n <button\r\n mfButton\r\n tone=\"primary\"\r\n appearance=\"filled\"\r\n size=\"sm\"\r\n type=\"button\"\r\n (click)=\"commitText()\"\r\n >\r\n {{ effectiveLabels().apply }}\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n }\r\n }\r\n }\r\n</mf-popover>\r\n", dependencies: [{ kind: "component", type: Button, selector: "button[mfButton], a[mfButton]", inputs: ["tone", "appearance", "size", "shape", "type", "loading", "iconStart", "iconEnd"], exportAs: ["mfButton"] }, { kind: "directive", type: Icon, selector: "span[mfIcon]", inputs: ["size", "filled"] }, { kind: "directive", type: Checkbox, selector: "input[type=checkbox][mfCheckbox]", inputs: ["size"] }, { kind: "directive", type: Input, selector: "input[mfInput]:not([type=checkbox]):not([type=radio]), textarea[mfInput]", inputs: ["size", "hasError", "autosize", "minRows", "maxRows"] }, { kind: "component", type: Popover, selector: "mf-popover", inputs: ["origin", "open", "placement", "padding", "hasBackdrop", "matchTriggerWidth", "panelClass"], outputs: ["openChange", "closed"], exportAs: ["mfPopover"] }, { kind: "component", type: Select, selector: "mf-select", inputs: ["options", "value", "multiple", "values", "maxVisibleChips", "placeholder", "disabled", "size", "hasError", "ariaLabel"], outputs: ["valueChange", "valuesChange"], exportAs: ["mfSelect"] }, { kind: "component", type: Calendar, selector: "mf-calendar", inputs: ["month", "mode", "selected", "rangeStart", "rangeEnd", "hoverEnd", "min", "max", "locale", "labels", "navigable"], outputs: ["monthChange", "dayPick", "dayHover"] }] });
769
+ }
770
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: TableFilterMenu, decorators: [{
771
+ type: Component,
772
+ args: [{ selector: 'mf-table-filter-menu', imports: [Button, Icon, Checkbox, Input, Popover, Select, Calendar], host: { class: 'contents' }, template: "<mf-popover\r\n [origin]=\"origin()\"\r\n [open]=\"open()\"\r\n placement=\"bottom-start\"\r\n padding=\"md\"\r\n panelClass=\"w-72\"\r\n (closed)=\"close()\"\r\n>\r\n @if (step() === 'field') {\r\n <div class=\"relative mb-1.5\">\r\n <span\r\n mfIcon\r\n [size]=\"16\"\r\n class=\"pointer-events-none absolute top-1/2 start-2 -translate-y-1/2 text-fg-subtle\"\r\n >search</span\r\n >\r\n <input\r\n mfInput\r\n #fieldSearchEl\r\n type=\"text\"\r\n class=\"ps-7 text-sm\"\r\n [placeholder]=\"effectiveLabels().searchField\"\r\n [value]=\"fieldQuery()\"\r\n (input)=\"fieldQuery.set($any($event.target).value)\"\r\n (keydown)=\"onFieldQueryKeydown($event)\"\r\n />\r\n </div>\r\n @if (filteredColumns().length === 0) {\r\n <p class=\"px-1.5 py-2 text-sm text-fg-subtle\">{{ effectiveLabels().noField }}</p>\r\n } @else {\r\n <ul class=\"flex max-h-64 list-none flex-col gap-0.5 overflow-auto\">\r\n @for (column of filteredColumns(); track column.field; let i = $index) {\r\n <li>\r\n <button\r\n type=\"button\"\r\n [class]=\"fieldOptionClasses(i)\"\r\n (mouseenter)=\"fieldHighlighted.set(i)\"\r\n (click)=\"selectField(column.field)\"\r\n >\r\n {{ column.header }}\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n } @else {\r\n <div class=\"mb-1.5 flex items-center gap-1 px-0.5\">\r\n @if (!initial()) {\r\n <button\r\n type=\"button\"\r\n class=\"appearance-none border-0 bg-transparent p-0 text-fg-subtle transition-colors duration-fast ease-emphasis hover:text-fg\"\r\n [attr.aria-label]=\"effectiveLabels().back\"\r\n (click)=\"backToField()\"\r\n >\r\n <span mfIcon [size]=\"16\" class=\"rtl:-scale-x-100\">arrow_back</span>\r\n </button>\r\n }\r\n <span class=\"text-sm font-medium text-fg\">{{ fieldHeader(selectedField() ?? '') }}</span>\r\n </div>\r\n\r\n @switch (filterTypeFor(selectedField() ?? '')) {\r\n @case ('select') {\r\n <ul class=\"flex max-h-64 list-none flex-col gap-0.5 overflow-auto\">\r\n @for (option of selectOptions(); track option.label) {\r\n <li>\r\n <label\r\n class=\"flex cursor-pointer items-center gap-2 rounded-sm px-2.5 py-1.5 text-sm text-fg-muted transition-colors duration-fast ease-emphasis hover:bg-surface-alt\"\r\n >\r\n <input\r\n type=\"checkbox\"\r\n mfCheckbox\r\n [checked]=\"isSelected(option.value)\"\r\n (change)=\"toggleSelection(option.value)\"\r\n />\r\n <span class=\"truncate\">{{ option.label }}</span>\r\n </label>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n @case ('boolean') {\r\n <ul class=\"flex list-none flex-col gap-0.5\">\r\n @for (option of selectOptions(); track option.label) {\r\n <li>\r\n <label\r\n class=\"flex cursor-pointer items-center gap-2 rounded-sm px-2.5 py-1.5 text-sm text-fg-muted transition-colors duration-fast ease-emphasis hover:bg-surface-alt\"\r\n >\r\n <input\r\n type=\"checkbox\"\r\n mfCheckbox\r\n [checked]=\"isSelected(option.value)\"\r\n (change)=\"toggleSelection(option.value)\"\r\n />\r\n <span class=\"truncate\">{{ option.label }}</span>\r\n </label>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n @case ('date') {\r\n <div class=\"flex flex-col gap-1\">\r\n <button\r\n type=\"button\"\r\n class=\"appearance-none border-0 bg-transparent rounded-sm px-2.5 py-1.5 text-start text-sm text-fg-muted transition-colors duration-fast ease-emphasis hover:bg-surface-alt\"\r\n (click)=\"applyPreset('today')\"\r\n >\r\n {{ effectiveLabels().dateToday }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"appearance-none border-0 bg-transparent rounded-sm px-2.5 py-1.5 text-start text-sm text-fg-muted transition-colors duration-fast ease-emphasis hover:bg-surface-alt\"\r\n (click)=\"applyPreset('last7')\"\r\n >\r\n {{ effectiveLabels().dateLast7 }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"appearance-none border-0 bg-transparent rounded-sm px-2.5 py-1.5 text-start text-sm text-fg-muted transition-colors duration-fast ease-emphasis hover:bg-surface-alt\"\r\n (click)=\"applyPreset('last30')\"\r\n >\r\n {{ effectiveLabels().dateLast30 }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"appearance-none border-0 bg-transparent rounded-sm px-2.5 py-1.5 text-start text-sm text-fg-muted transition-colors duration-fast ease-emphasis hover:bg-surface-alt\"\r\n (click)=\"applyPreset('thisMonth')\"\r\n >\r\n {{ effectiveLabels().dateThisMonth }}\r\n </button>\r\n @if (!customDateOpen()) {\r\n <button\r\n type=\"button\"\r\n class=\"appearance-none border-0 bg-transparent rounded-sm px-2.5 py-1.5 text-start text-sm text-accent transition-colors duration-fast ease-emphasis hover:bg-accent/5\"\r\n (click)=\"openCustomRange()\"\r\n >\r\n {{ effectiveLabels().dateCustom }}\r\n </button>\r\n } @else {\r\n <div class=\"mt-1 flex flex-col gap-2 border-t border-border pt-2\">\r\n <mf-calendar\r\n mode=\"range\"\r\n [(month)]=\"calendarMonth\"\r\n [rangeStart]=\"dateFrom()\"\r\n [rangeEnd]=\"dateTo()\"\r\n [hoverEnd]=\"dateHoverEnd()\"\r\n (dayPick)=\"onCalendarDayPick($event)\"\r\n (dayHover)=\"onCalendarDayHover($event)\"\r\n />\r\n <div class=\"flex justify-end\">\r\n <button\r\n mfButton\r\n tone=\"primary\"\r\n appearance=\"filled\"\r\n size=\"sm\"\r\n type=\"button\"\r\n [disabled]=\"!dateFrom()\"\r\n (click)=\"commitCustomRange()\"\r\n >\r\n {{ effectiveLabels().apply }}\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n @default {\r\n <div class=\"flex flex-col gap-2\">\r\n <mf-select\r\n [options]=\"operatorOptions()\"\r\n [value]=\"operator()\"\r\n (valueChange)=\"onOperatorChange($event)\"\r\n size=\"sm\"\r\n [ariaLabel]=\"effectiveLabels().operator\"\r\n />\r\n @if (currentOperatorNeedsValue()) {\r\n <input\r\n mfInput\r\n type=\"text\"\r\n class=\"text-sm\"\r\n [attr.aria-label]=\"effectiveLabels().value\"\r\n [placeholder]=\"effectiveLabels().value\"\r\n [value]=\"textValue()\"\r\n (input)=\"onTextInput($any($event.target).value)\"\r\n (keydown.enter)=\"commitText()\"\r\n />\r\n <div class=\"flex items-center justify-between\">\r\n <span class=\"text-xs text-fg-subtle\">{{ effectiveLabels().enterHint }}</span>\r\n <button\r\n mfButton\r\n tone=\"primary\"\r\n appearance=\"filled\"\r\n size=\"sm\"\r\n type=\"button\"\r\n (click)=\"commitText()\"\r\n >\r\n {{ effectiveLabels().apply }}\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n }\r\n }\r\n }\r\n</mf-popover>\r\n" }]
773
+ }], ctorParameters: () => [], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], origin: [{ type: i0.Input, args: [{ isSignal: true, alias: "origin", required: true }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: true }] }], initial: [{ type: i0.Input, args: [{ isSignal: true, alias: "initial", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], apply: [{ type: i0.Output, args: ["apply"] }], remove: [{ type: i0.Output, args: ["remove"] }], closed: [{ type: i0.Output, args: ["closed"] }], fieldSearchEl: [{ type: i0.ViewChild, args: ['fieldSearchEl', { isSignal: true }] }] } });
774
+
775
+ /**
776
+ * Toolbar'ın ALTINDA kalıcı bir satır — uygulanmış filtreler artık ikonun
777
+ * köşesindeki bir sayı rozetinde değil, doğrudan burada, birer chip olarak
778
+ * görünür (bkz. plan — kullanıcının onayladığı Linear/Attio dili). Chip'e
779
+ * tıklamak değerini düzenler, `×` anında kaldırır. `TableFilterMenu` HEM
780
+ * "+ Filtre" tetikleyicisinde (ekleme, `initial=null`) HEM her chip'te
781
+ * (düzenleme, `initial=filter`) aynı bileşen — bkz. o dosyanın başındaki not.
782
+ */
783
+ class TableFilterBar {
784
+ columns = input.required(/* @ts-ignore */
785
+ ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
786
+ filters = input.required(/* @ts-ignore */
787
+ ...(ngDevMode ? [{ debugName: "filters" }] : /* istanbul ignore next */ []));
788
+ logic = input.required(/* @ts-ignore */
789
+ ...(ngDevMode ? [{ debugName: "logic" }] : /* istanbul ignore next */ []));
790
+ labels = input(/* @ts-ignore */
791
+ ...(ngDevMode ? [undefined, { debugName: "labels" }] : /* istanbul ignore next */ []));
792
+ tableLabels = inject(MF_TABLE_LABELS);
793
+ effectiveLabels = computed(() => this.labels() ?? this.tableLabels().filter, /* @ts-ignore */
794
+ ...(ngDevMode ? [{ debugName: "effectiveLabels" }] : /* istanbul ignore next */ []));
795
+ filtersChange = output();
796
+ logicChange = output();
797
+ addOpen = signal(false, /* @ts-ignore */
798
+ ...(ngDevMode ? [{ debugName: "addOpen" }] : /* istanbul ignore next */ []));
799
+ editingId = signal(null, /* @ts-ignore */
800
+ ...(ngDevMode ? [{ debugName: "editingId" }] : /* istanbul ignore next */ []));
801
+ FilterLogic = FilterLogic;
802
+ filterableColumns = computed(() => this.columns().filter((c) => !!c.filter), /* @ts-ignore */
803
+ ...(ngDevMode ? [{ debugName: "filterableColumns" }] : /* istanbul ignore next */ []));
804
+ logicOptions = computed(() => [
805
+ { label: this.effectiveLabels().and, value: FilterLogic.And },
806
+ { label: this.effectiveLabels().or, value: FilterLogic.Or },
807
+ ], /* @ts-ignore */
808
+ ...(ngDevMode ? [{ debugName: "logicOptions" }] : /* istanbul ignore next */ []));
809
+ columnFor(field) {
810
+ return this.columns().find((c) => c.field === field);
811
+ }
812
+ fieldHeader(field) {
813
+ return this.columnFor(field)?.header ?? field;
814
+ }
815
+ filterTypeFor(field) {
816
+ return this.columnFor(field)?.filter?.type ?? 'text';
817
+ }
818
+ /**
819
+ * Chip metni: "Alan: değer" — operatör SADECE o tip için varsayılan
820
+ * değilse eklenir ("Rol: Yönetici" okunur, "Rol eşittir Yönetici" aynı
821
+ * bilgiyi taşıyıp gürültü ekler). Çoklu seçimde 3'ten fazla değer
822
+ * "A, B +2" şeklinde kısaltılır — chip'in kendisi bir özet, tam liste
823
+ * düzenleyicide zaten görünür.
824
+ */
825
+ chipText(filter) {
826
+ const header = this.fieldHeader(filter.field);
827
+ const type = this.filterTypeFor(filter.field);
828
+ const value = this.displayValue(filter, type);
829
+ if (isDefaultOperator(filter.operator, type))
830
+ return `${header}: ${value}`;
831
+ return `${header} ${operatorLabel(this.effectiveLabels(), filter.operator)} ${value}`;
832
+ }
833
+ displayValue(filter, type) {
834
+ if (filter.operator === 'between') {
835
+ const [from, to] = filter.value ?? [
836
+ null,
837
+ null,
838
+ ];
839
+ const f = from ? from.slice(0, 10) : '…';
840
+ const t = to ? to.slice(0, 10) : '…';
841
+ return `${f} – ${t}`;
842
+ }
843
+ if (filter.value === null || filter.value === undefined)
844
+ return '';
845
+ const optionLabel = (raw) => {
846
+ if (type !== 'select' && type !== 'boolean')
847
+ return String(raw);
848
+ return (this.columnFor(filter.field)?.filter?.options?.find((o) => o.value === raw)?.label ??
849
+ String(raw));
850
+ };
851
+ if (Array.isArray(filter.value)) {
852
+ const labels = filter.value.map(optionLabel);
853
+ if (labels.length <= 2)
854
+ return labels.join(', ');
855
+ return `${labels.slice(0, 2).join(', ')} +${labels.length - 2}`;
856
+ }
857
+ return optionLabel(filter.value);
858
+ }
859
+ onMenuApply(applied) {
860
+ const current = this.filters();
861
+ const index = current.findIndex((f) => f.id === applied.id);
862
+ const next = index === -1 ? [...current, applied] : current.map((f, i) => (i === index ? applied : f));
863
+ this.filtersChange.emit(next);
864
+ }
865
+ removeFilter(id) {
866
+ this.filtersChange.emit(this.filters().filter((f) => f.id !== id));
867
+ if (this.editingId() === id)
868
+ this.editingId.set(null);
869
+ }
870
+ onLogicChange(logic) {
871
+ if (logic === null)
872
+ return;
873
+ this.logicChange.emit(logic);
874
+ }
875
+ clearAll() {
876
+ this.filtersChange.emit([]);
877
+ this.editingId.set(null);
878
+ }
879
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: TableFilterBar, deps: [], target: i0.ɵɵFactoryTarget.Component });
880
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: TableFilterBar, isStandalone: true, selector: "mf-table-filter-bar", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, filters: { classPropertyName: "filters", publicName: "filters", isSignal: true, isRequired: true, transformFunction: null }, logic: { classPropertyName: "logic", publicName: "logic", isSignal: true, isRequired: true, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { filtersChange: "filtersChange", logicChange: "logicChange" }, host: { classAttribute: "block" }, ngImport: i0, template: "<div class=\"flex flex-wrap items-center gap-1.5 border-b border-border bg-surface-sunken px-4 py-2\">\r\n @if (filters().length > 1) {\r\n <mf-select\r\n [options]=\"logicOptions()\"\r\n [value]=\"logic()\"\r\n (valueChange)=\"onLogicChange($event)\"\r\n size=\"sm\"\r\n class=\"w-20\"\r\n />\r\n }\r\n\r\n @for (filter of filters(); track filter.id) {\r\n <mf-chip\r\n [removable]=\"true\"\r\n [removeLabel]=\"effectiveLabels().remove\"\r\n (removed)=\"removeFilter(filter.id)\"\r\n >\r\n <button\r\n type=\"button\"\r\n cdkOverlayOrigin\r\n #chipOrigin=\"cdkOverlayOrigin\"\r\n class=\"max-w-[16rem] cursor-pointer appearance-none truncate border-0 bg-transparent p-0 text-start transition-colors duration-fast ease-emphasis hover:text-fg\"\r\n (click)=\"editingId.set(filter.id)\"\r\n >\r\n {{ chipText(filter) }}\r\n </button>\r\n </mf-chip>\r\n <mf-table-filter-menu\r\n [columns]=\"columns()\"\r\n [origin]=\"chipOrigin\"\r\n [open]=\"editingId() === filter.id\"\r\n [initial]=\"filter\"\r\n [labels]=\"effectiveLabels()\"\r\n (apply)=\"onMenuApply($event)\"\r\n (remove)=\"removeFilter($event)\"\r\n (closed)=\"editingId.set(null)\"\r\n />\r\n }\r\n\r\n <button\r\n type=\"button\"\r\n cdkOverlayOrigin\r\n #addOrigin=\"cdkOverlayOrigin\"\r\n class=\"inline-flex cursor-pointer appearance-none items-center gap-1 rounded-full border border-dashed border-border bg-transparent py-1 pe-2.5 ps-2 text-xs font-medium text-fg-muted transition-colors duration-fast ease-emphasis hover:border-border-strong hover:text-fg\"\r\n (click)=\"addOpen.set(!addOpen())\"\r\n >\r\n <span mfIcon [size]=\"14\">add</span>\r\n {{ effectiveLabels().addFilter }}\r\n </button>\r\n <mf-table-filter-menu\r\n [columns]=\"columns()\"\r\n [origin]=\"addOrigin\"\r\n [open]=\"addOpen()\"\r\n [labels]=\"effectiveLabels()\"\r\n (apply)=\"onMenuApply($event)\"\r\n (remove)=\"removeFilter($event)\"\r\n (closed)=\"addOpen.set(false)\"\r\n />\r\n\r\n @if (filters().length > 0) {\r\n <button\r\n type=\"button\"\r\n class=\"ms-auto appearance-none border-0 bg-transparent p-0 text-xs font-medium text-fg-subtle transition-colors duration-fast ease-emphasis hover:text-fg\"\r\n (click)=\"clearAll()\"\r\n >\r\n {{ effectiveLabels().clearAll }}\r\n </button>\r\n }\r\n</div>\r\n", dependencies: [{ kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: Chip, selector: "mf-chip", inputs: ["tone", "size", "selected", "disabled", "removable", "removeLabel"], outputs: ["removed"], exportAs: ["mfChip"] }, { kind: "directive", type: Icon, selector: "span[mfIcon]", inputs: ["size", "filled"] }, { kind: "component", type: Select, selector: "mf-select", inputs: ["options", "value", "multiple", "values", "maxVisibleChips", "placeholder", "disabled", "size", "hasError", "ariaLabel"], outputs: ["valueChange", "valuesChange"], exportAs: ["mfSelect"] }, { kind: "component", type: TableFilterMenu, selector: "mf-table-filter-menu", inputs: ["columns", "origin", "open", "initial", "labels"], outputs: ["apply", "remove", "closed"] }] });
881
+ }
882
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: TableFilterBar, decorators: [{
883
+ type: Component,
884
+ args: [{ selector: 'mf-table-filter-bar', host: { class: 'block' }, imports: [OverlayModule, Chip, Icon, Select, TableFilterMenu], template: "<div class=\"flex flex-wrap items-center gap-1.5 border-b border-border bg-surface-sunken px-4 py-2\">\r\n @if (filters().length > 1) {\r\n <mf-select\r\n [options]=\"logicOptions()\"\r\n [value]=\"logic()\"\r\n (valueChange)=\"onLogicChange($event)\"\r\n size=\"sm\"\r\n class=\"w-20\"\r\n />\r\n }\r\n\r\n @for (filter of filters(); track filter.id) {\r\n <mf-chip\r\n [removable]=\"true\"\r\n [removeLabel]=\"effectiveLabels().remove\"\r\n (removed)=\"removeFilter(filter.id)\"\r\n >\r\n <button\r\n type=\"button\"\r\n cdkOverlayOrigin\r\n #chipOrigin=\"cdkOverlayOrigin\"\r\n class=\"max-w-[16rem] cursor-pointer appearance-none truncate border-0 bg-transparent p-0 text-start transition-colors duration-fast ease-emphasis hover:text-fg\"\r\n (click)=\"editingId.set(filter.id)\"\r\n >\r\n {{ chipText(filter) }}\r\n </button>\r\n </mf-chip>\r\n <mf-table-filter-menu\r\n [columns]=\"columns()\"\r\n [origin]=\"chipOrigin\"\r\n [open]=\"editingId() === filter.id\"\r\n [initial]=\"filter\"\r\n [labels]=\"effectiveLabels()\"\r\n (apply)=\"onMenuApply($event)\"\r\n (remove)=\"removeFilter($event)\"\r\n (closed)=\"editingId.set(null)\"\r\n />\r\n }\r\n\r\n <button\r\n type=\"button\"\r\n cdkOverlayOrigin\r\n #addOrigin=\"cdkOverlayOrigin\"\r\n class=\"inline-flex cursor-pointer appearance-none items-center gap-1 rounded-full border border-dashed border-border bg-transparent py-1 pe-2.5 ps-2 text-xs font-medium text-fg-muted transition-colors duration-fast ease-emphasis hover:border-border-strong hover:text-fg\"\r\n (click)=\"addOpen.set(!addOpen())\"\r\n >\r\n <span mfIcon [size]=\"14\">add</span>\r\n {{ effectiveLabels().addFilter }}\r\n </button>\r\n <mf-table-filter-menu\r\n [columns]=\"columns()\"\r\n [origin]=\"addOrigin\"\r\n [open]=\"addOpen()\"\r\n [labels]=\"effectiveLabels()\"\r\n (apply)=\"onMenuApply($event)\"\r\n (remove)=\"removeFilter($event)\"\r\n (closed)=\"addOpen.set(false)\"\r\n />\r\n\r\n @if (filters().length > 0) {\r\n <button\r\n type=\"button\"\r\n class=\"ms-auto appearance-none border-0 bg-transparent p-0 text-xs font-medium text-fg-subtle transition-colors duration-fast ease-emphasis hover:text-fg\"\r\n (click)=\"clearAll()\"\r\n >\r\n {{ effectiveLabels().clearAll }}\r\n </button>\r\n }\r\n</div>\r\n" }]
885
+ }], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], filters: [{ type: i0.Input, args: [{ isSignal: true, alias: "filters", required: true }] }], logic: [{ type: i0.Input, args: [{ isSignal: true, alias: "logic", required: true }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], filtersChange: [{ type: i0.Output, args: ["filtersChange"] }], logicChange: [{ type: i0.Output, args: ["logicChange"] }] } });
886
+
887
+ /**
888
+ * Sayfalamadaki tıklanabilir "…" için küçük bir sayfaya-git popover'ı —
889
+ * panel iskeleti `Popover`'a devredildi (bkz. plan Faz 1c) — kendi
890
+ * `[origin]`/`[open]` input'ları, `(apply)`/`(closed)` output'ları aynı
891
+ * kaldı, sadece CDK connected overlay/backdrop/ESC/animasyon artık
892
+ * `Popover`'ın işi. Onay YALNIZCA submit'te (Enter / Git) — her tuş
893
+ * vuruşunda backend'e istek atmaz (bkz. plan, rate limit notu).
894
+ */
895
+ class TablePageJump {
896
+ origin = input.required(/* @ts-ignore */
897
+ ...(ngDevMode ? [{ debugName: "origin" }] : /* istanbul ignore next */ []));
898
+ open = input.required(/* @ts-ignore */
899
+ ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
900
+ totalPages = input.required(/* @ts-ignore */
901
+ ...(ngDevMode ? [{ debugName: "totalPages" }] : /* istanbul ignore next */ []));
902
+ labels = input(/* @ts-ignore */
903
+ ...(ngDevMode ? [undefined, { debugName: "labels" }] : /* istanbul ignore next */ []));
904
+ tableLabels = inject(MF_TABLE_LABELS);
905
+ effectiveLabels = computed(() => this.labels() ?? this.tableLabels().pagination, /* @ts-ignore */
906
+ ...(ngDevMode ? [{ debugName: "effectiveLabels" }] : /* istanbul ignore next */ []));
907
+ apply = output();
908
+ closed = output();
909
+ value = signal('', /* @ts-ignore */
910
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
911
+ valueEl = viewChild('valueEl', /* @ts-ignore */
912
+ ...(ngDevMode ? [{ debugName: "valueEl" }] : /* istanbul ignore next */ []));
913
+ constructor() {
914
+ // Overlay her açılışta taze başlar — bkz. table-filter-menu.ts'teki aynı desen.
915
+ effect(() => {
916
+ if (!this.open())
917
+ return;
918
+ untracked(() => {
919
+ this.value.set('');
920
+ queueMicrotask(() => this.valueEl()?.nativeElement.focus());
921
+ });
922
+ });
923
+ }
924
+ onValueInput(raw) {
925
+ this.value.set(raw);
926
+ }
927
+ commit() {
928
+ const parsed = Number(this.value());
929
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > this.totalPages())
930
+ return;
931
+ this.apply.emit(parsed);
932
+ }
933
+ close() {
934
+ this.closed.emit();
935
+ }
936
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: TablePageJump, deps: [], target: i0.ɵɵFactoryTarget.Component });
937
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.6", type: TablePageJump, isStandalone: true, selector: "mf-table-page-jump", inputs: { origin: { classPropertyName: "origin", publicName: "origin", isSignal: true, isRequired: true, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: true, transformFunction: null }, totalPages: { classPropertyName: "totalPages", publicName: "totalPages", isSignal: true, isRequired: true, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { apply: "apply", closed: "closed" }, host: { classAttribute: "contents" }, viewQueries: [{ propertyName: "valueEl", first: true, predicate: ["valueEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<mf-popover\r\n [origin]=\"origin()\"\r\n [open]=\"open()\"\r\n placement=\"bottom\"\r\n padding=\"md\"\r\n panelClass=\"w-44\"\r\n (closed)=\"close()\"\r\n>\r\n <p class=\"mb-1.5 px-0.5 text-eyebrow text-fg-subtle\">{{ effectiveLabels().jumpToPage }}</p>\r\n <div class=\"flex items-center gap-1.5\">\r\n <input\r\n mfInput\r\n #valueEl\r\n type=\"number\"\r\n class=\"text-sm\"\r\n min=\"1\"\r\n [max]=\"totalPages()\"\r\n [placeholder]=\"'1\u2013' + totalPages()\"\r\n [value]=\"value()\"\r\n (input)=\"onValueInput($any($event.target).value)\"\r\n (keydown.enter)=\"commit()\"\r\n />\r\n <button mfButton tone=\"primary\" appearance=\"filled\" size=\"sm\" type=\"button\" (click)=\"commit()\">\r\n {{ effectiveLabels().go }}\r\n </button>\r\n </div>\r\n</mf-popover>\r\n", dependencies: [{ kind: "component", type: Button, selector: "button[mfButton], a[mfButton]", inputs: ["tone", "appearance", "size", "shape", "type", "loading", "iconStart", "iconEnd"], exportAs: ["mfButton"] }, { kind: "directive", type: Input, selector: "input[mfInput]:not([type=checkbox]):not([type=radio]), textarea[mfInput]", inputs: ["size", "hasError", "autosize", "minRows", "maxRows"] }, { kind: "component", type: Popover, selector: "mf-popover", inputs: ["origin", "open", "placement", "padding", "hasBackdrop", "matchTriggerWidth", "panelClass"], outputs: ["openChange", "closed"], exportAs: ["mfPopover"] }] });
938
+ }
939
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: TablePageJump, decorators: [{
940
+ type: Component,
941
+ args: [{ selector: 'mf-table-page-jump', imports: [Button, Input, Popover], host: { class: 'contents' }, template: "<mf-popover\r\n [origin]=\"origin()\"\r\n [open]=\"open()\"\r\n placement=\"bottom\"\r\n padding=\"md\"\r\n panelClass=\"w-44\"\r\n (closed)=\"close()\"\r\n>\r\n <p class=\"mb-1.5 px-0.5 text-eyebrow text-fg-subtle\">{{ effectiveLabels().jumpToPage }}</p>\r\n <div class=\"flex items-center gap-1.5\">\r\n <input\r\n mfInput\r\n #valueEl\r\n type=\"number\"\r\n class=\"text-sm\"\r\n min=\"1\"\r\n [max]=\"totalPages()\"\r\n [placeholder]=\"'1\u2013' + totalPages()\"\r\n [value]=\"value()\"\r\n (input)=\"onValueInput($any($event.target).value)\"\r\n (keydown.enter)=\"commit()\"\r\n />\r\n <button mfButton tone=\"primary\" appearance=\"filled\" size=\"sm\" type=\"button\" (click)=\"commit()\">\r\n {{ effectiveLabels().go }}\r\n </button>\r\n </div>\r\n</mf-popover>\r\n" }]
942
+ }], ctorParameters: () => [], propDecorators: { origin: [{ type: i0.Input, args: [{ isSignal: true, alias: "origin", required: true }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: true }] }], totalPages: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalPages", required: true }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], apply: [{ type: i0.Output, args: ["apply"] }], closed: [{ type: i0.Output, args: ["closed"] }], valueEl: [{ type: i0.ViewChild, args: ['valueEl', { isSignal: true }] }] } });
943
+
944
+ /** GitHub PR listesi tarzı akıllı elips: toplam ≤7 sayfa tümü gösterilir, aksi halde 1, 2, current±1, son-1, son sabit + boşluklar elipsli. */
945
+ function buildPageList(current, total) {
946
+ if (total <= 7)
947
+ return Array.from({ length: total }, (_, i) => i + 1);
948
+ const keep = new Set([1, 2, total - 1, total, current - 1, current, current + 1]);
949
+ const sorted = [...keep].filter((p) => p >= 1 && p <= total).sort((a, b) => a - b);
950
+ const result = [];
951
+ let previous = 0;
952
+ for (const page of sorted) {
953
+ if (previous && page - previous > 1)
954
+ result.push('ellipsis');
955
+ result.push(page);
956
+ previous = page;
957
+ }
958
+ return result;
959
+ }
960
+ /**
961
+ * Her hücre AYNI dili konuşuyor: kendi `rounded-md` hit alanına sahip,
962
+ * tek bir `focus-visible` mekanizmalı, bağımsız bir buton (bkz.
963
+ * projects/ui-kit/README.md "Focus — tek mekanizma"). `h-9 w-9` mobilde dört okun
964
+ * da 36px dokunma hedefi olmasını sağlıyor; `sm:h-8 sm:min-w-8` masaüstünde
965
+ * yandaki `mf-select size="sm"` ile hizalanıyor (`select.variants.ts`).
966
+ */
967
+ const CELL_BASE = 'relative flex h-9 w-9 sm:h-8 sm:min-w-8 shrink-0 appearance-none items-center justify-center rounded-md border-0 bg-transparent px-2 ' +
968
+ 'text-sm font-medium tabular-nums transition-colors duration-fast ease-emphasis active:scale-[0.99] ' +
969
+ 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring ' +
970
+ 'disabled:pointer-events-none disabled:opacity-50 disabled:active:scale-100';
971
+ /**
972
+ * Table'ın footer'ı — sol "X–Y / toplam Z" özet + sayfa-boyutu `Select`i,
973
+ * sağda AYRIK/havadar bir gezinme grubu: ⏮ ‹ · numaralar · › ⏭. Eskiden
974
+ * tek kenarlıklı bir segment grubuydu (bkz. git geçmişi) — artık dış çerçeve
975
+ * yok, her hücre kendi bağımsız butonu. Aktif sayfa dolu yeşil blok DEĞİL,
976
+ * `surface-alt` yüzey + alt kenarda **ray** — bu tasarım sisteminin
977
+ * "buradasın" motifi (sidebar aktif öğe, aktif sekme, seçili tablo satırı ile
978
+ * aynı dil).
979
+ */
980
+ class TablePagination {
981
+ page = input.required(/* @ts-ignore */
982
+ ...(ngDevMode ? [{ debugName: "page" }] : /* istanbul ignore next */ []));
983
+ pageSize = input.required(/* @ts-ignore */
984
+ ...(ngDevMode ? [{ debugName: "pageSize" }] : /* istanbul ignore next */ []));
985
+ total = input.required(/* @ts-ignore */
986
+ ...(ngDevMode ? [{ debugName: "total" }] : /* istanbul ignore next */ []));
987
+ pageSizeOptions = input([10, 20, 50, 100], /* @ts-ignore */
988
+ ...(ngDevMode ? [{ debugName: "pageSizeOptions" }] : /* istanbul ignore next */ []));
989
+ labels = input(/* @ts-ignore */
990
+ ...(ngDevMode ? [undefined, { debugName: "labels" }] : /* istanbul ignore next */ []));
991
+ tableLabels = inject(MF_TABLE_LABELS);
992
+ effectiveLabels = computed(() => this.labels() ?? this.tableLabels().pagination, /* @ts-ignore */
993
+ ...(ngDevMode ? [{ debugName: "effectiveLabels" }] : /* istanbul ignore next */ []));
994
+ pageChange = output();
995
+ pageSizeChange = output();
996
+ totalPages = computed(() => Math.max(1, Math.ceil(this.total() / this.pageSize())), /* @ts-ignore */
997
+ ...(ngDevMode ? [{ debugName: "totalPages" }] : /* istanbul ignore next */ []));
998
+ rangeStart = computed(() => this.total() === 0 ? 0 : (this.page() - 1) * this.pageSize() + 1, /* @ts-ignore */
999
+ ...(ngDevMode ? [{ debugName: "rangeStart" }] : /* istanbul ignore next */ []));
1000
+ rangeEnd = computed(() => Math.min(this.page() * this.pageSize(), this.total()), /* @ts-ignore */
1001
+ ...(ngDevMode ? [{ debugName: "rangeEnd" }] : /* istanbul ignore next */ []));
1002
+ pageItems = computed(() => buildPageList(this.page(), this.totalPages()), /* @ts-ignore */
1003
+ ...(ngDevMode ? [{ debugName: "pageItems" }] : /* istanbul ignore next */ []));
1004
+ pageSizeSelectOptions = computed(() => this.pageSizeOptions().map((size) => ({ label: String(size), value: size })), /* @ts-ignore */
1005
+ ...(ngDevMode ? [{ debugName: "pageSizeSelectOptions" }] : /* istanbul ignore next */ []));
1006
+ /** Aynı anda iki "…" olabilir (baştaki + sondaki) — açık olanın loop index'i, `null` = hiçbiri açık değil. */
1007
+ jumpOpenIndex = signal(null, /* @ts-ignore */
1008
+ ...(ngDevMode ? [{ debugName: "jumpOpenIndex" }] : /* istanbul ignore next */ []));
1009
+ navEl = viewChild('nav', /* @ts-ignore */
1010
+ ...(ngDevMode ? [{ debugName: "navEl" }] : /* istanbul ignore next */ []));
1011
+ /** Signal DEĞİL — sadece odağın şu an `<nav>` içinde olup olmadığını izleyen düz bir bayrak (bkz. constructor'daki geri-odaklama notu). */
1012
+ focusWithinNav = false;
1013
+ constructor() {
1014
+ /*
1015
+ * Ok tuşlarıyla ardışık gezinme: sayfa değişince o anki `[aria-current]`
1016
+ * butonu DOM'dan kalkabiliyor (pencere current±1'e göre kayıyor), odak
1017
+ * `<body>`'ye düşüyor ve bir sonraki ok tuşu artık `<nav>`'a ulaşmıyordu
1018
+ * (gerçek bulgu). Odak `<nav>` içindeyken her sayfa değişiminden sonra
1019
+ * yeni aktif sayfa butonuna (mobilde numaralar gizliyse `<nav>`'ın
1020
+ * kendisine) geri veriliyor.
1021
+ */
1022
+ afterRenderEffect(() => {
1023
+ this.page();
1024
+ if (!this.focusWithinNav)
1025
+ return;
1026
+ const nav = this.navEl()?.nativeElement;
1027
+ if (!nav || nav.contains(document.activeElement))
1028
+ return;
1029
+ const activeBtn = nav.querySelector('[aria-current="page"]');
1030
+ (activeBtn ?? nav).focus();
1031
+ });
1032
+ }
1033
+ goTo(page) {
1034
+ if (page < 1 || page > this.totalPages() || page === this.page())
1035
+ return;
1036
+ this.pageChange.emit(page);
1037
+ }
1038
+ onPageSizeChange(size) {
1039
+ if (size === null)
1040
+ return;
1041
+ this.pageSizeChange.emit(size);
1042
+ }
1043
+ cellClasses(active) {
1044
+ return cn(CELL_BASE, active
1045
+ ? 'bg-surface-alt text-fg font-semibold'
1046
+ : 'text-fg-muted hover:bg-surface-alt hover:text-fg',
1047
+ // Ray: aktif sayfanın alt kenarında 2px dolu marka rengi — dolu yeşil
1048
+ // BLOK değil (bu sistemin "buradasın" motifi, bkz. sınıf yorumu).
1049
+ // `inset-x-2` (8px), hücrenin `rounded-md` (8px) köşe yarıçapını tam
1050
+ // kurtarıyor — ray düz kenarda kalıyor.
1051
+ active &&
1052
+ "after:absolute after:inset-x-2 after:bottom-0 after:h-0.5 after:rounded-full after:bg-accent-fill after:content-['']");
1053
+ }
1054
+ onNavKeydown(event) {
1055
+ switch (event.key) {
1056
+ case 'ArrowLeft':
1057
+ event.preventDefault();
1058
+ this.goTo(this.page() - 1);
1059
+ break;
1060
+ case 'ArrowRight':
1061
+ event.preventDefault();
1062
+ this.goTo(this.page() + 1);
1063
+ break;
1064
+ case 'Home':
1065
+ event.preventDefault();
1066
+ this.goTo(1);
1067
+ break;
1068
+ case 'End':
1069
+ event.preventDefault();
1070
+ this.goTo(this.totalPages());
1071
+ break;
1072
+ }
1073
+ }
1074
+ onNavFocusIn() {
1075
+ this.focusWithinNav = true;
1076
+ }
1077
+ onNavFocusOut() {
1078
+ this.focusWithinNav = false;
1079
+ }
1080
+ toggleJump(index) {
1081
+ this.jumpOpenIndex.update((current) => (current === index ? null : index));
1082
+ }
1083
+ onJumpApply(page) {
1084
+ this.jumpOpenIndex.set(null);
1085
+ this.goTo(page);
1086
+ }
1087
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: TablePagination, deps: [], target: i0.ɵɵFactoryTarget.Component });
1088
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: TablePagination, isStandalone: true, selector: "mf-table-pagination", inputs: { page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: true, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: true, transformFunction: null }, total: { classPropertyName: "total", publicName: "total", isSignal: true, isRequired: true, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pageChange: "pageChange", pageSizeChange: "pageSizeChange" }, host: { classAttribute: "block" }, viewQueries: [{ propertyName: "navEl", first: true, predicate: ["nav"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"flex flex-wrap items-center justify-between gap-3 px-4 py-3\">\r\n <div class=\"flex items-center gap-2 text-sm text-fg-subtle\">\r\n <span class=\"tabular-nums\"\r\n >{{ rangeStart() }}\u2013{{ rangeEnd() }} / {{ total() }} {{ effectiveLabels().records }}</span\r\n >\r\n <span class=\"text-fg-subtle\">{{ effectiveLabels().perPage }}</span>\r\n <mf-select\r\n [options]=\"pageSizeSelectOptions()\"\r\n [value]=\"pageSize()\"\r\n (valueChange)=\"onPageSizeChange($event)\"\r\n size=\"sm\"\r\n [ariaLabel]=\"effectiveLabels().pageSizeAriaLabel\"\r\n class=\"w-20\"\r\n />\r\n </div>\r\n\r\n <nav\r\n #nav\r\n tabindex=\"-1\"\r\n class=\"flex shrink-0 flex-nowrap items-center gap-1.5 outline-none\"\r\n [attr.aria-label]=\"effectiveLabels().navAriaLabel\"\r\n (keydown)=\"onNavKeydown($event)\"\r\n (focusin)=\"onNavFocusIn()\"\r\n (focusout)=\"onNavFocusOut()\"\r\n >\r\n <div class=\"flex items-center gap-0.5\">\r\n <button\r\n type=\"button\"\r\n [class]=\"cellClasses(false)\"\r\n [disabled]=\"page() === 1\"\r\n [attr.aria-label]=\"effectiveLabels().first\"\r\n (click)=\"goTo(1)\"\r\n >\r\n <span mfIcon [size]=\"16\" class=\"rtl:-scale-x-100\">first_page</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n [class]=\"cellClasses(false)\"\r\n [disabled]=\"page() === 1\"\r\n [attr.aria-label]=\"effectiveLabels().previous\"\r\n (click)=\"goTo(page() - 1)\"\r\n >\r\n <span mfIcon [size]=\"16\" class=\"rtl:-scale-x-100\">chevron_left</span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"hidden items-center gap-0.5 sm:flex\">\r\n @for (item of pageItems(); track $index) {\r\n @if (item === 'ellipsis') {\r\n <button\r\n type=\"button\"\r\n cdkOverlayOrigin\r\n #jumpOrigin=\"cdkOverlayOrigin\"\r\n [class]=\"cellClasses(false)\"\r\n [attr.aria-label]=\"effectiveLabels().jumpToPage\"\r\n (click)=\"toggleJump($index)\"\r\n >\r\n \u2026\r\n </button>\r\n <mf-table-page-jump\r\n [origin]=\"jumpOrigin\"\r\n [open]=\"jumpOpenIndex() === $index\"\r\n [totalPages]=\"totalPages()\"\r\n [labels]=\"effectiveLabels()\"\r\n (apply)=\"onJumpApply($event)\"\r\n (closed)=\"jumpOpenIndex.set(null)\"\r\n />\r\n } @else {\r\n <button\r\n type=\"button\"\r\n [class]=\"cellClasses(item === page())\"\r\n [attr.aria-current]=\"item === page() ? 'page' : null\"\r\n (click)=\"goTo(item)\"\r\n >\r\n {{ item }}\r\n </button>\r\n }\r\n }\r\n </div>\r\n\r\n <span class=\"flex h-9 shrink-0 items-center px-3 text-sm tabular-nums text-fg-muted sm:hidden\">\r\n {{ effectiveLabels().page }} {{ page() }} / {{ totalPages() }}\r\n </span>\r\n\r\n <div class=\"flex items-center gap-0.5\">\r\n <button\r\n type=\"button\"\r\n [class]=\"cellClasses(false)\"\r\n [disabled]=\"page() === totalPages()\"\r\n [attr.aria-label]=\"effectiveLabels().next\"\r\n (click)=\"goTo(page() + 1)\"\r\n >\r\n <span mfIcon [size]=\"16\" class=\"rtl:-scale-x-100\">chevron_right</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n [class]=\"cellClasses(false)\"\r\n [disabled]=\"page() === totalPages()\"\r\n [attr.aria-label]=\"effectiveLabels().last\"\r\n (click)=\"goTo(totalPages())\"\r\n >\r\n <span mfIcon [size]=\"16\" class=\"rtl:-scale-x-100\">last_page</span>\r\n </button>\r\n </div>\r\n </nav>\r\n</div>\r\n", dependencies: [{ kind: "component", type: Select, selector: "mf-select", inputs: ["options", "value", "multiple", "values", "maxVisibleChips", "placeholder", "disabled", "size", "hasError", "ariaLabel"], outputs: ["valueChange", "valuesChange"], exportAs: ["mfSelect"] }, { kind: "directive", type: Icon, selector: "span[mfIcon]", inputs: ["size", "filled"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: TablePageJump, selector: "mf-table-page-jump", inputs: ["origin", "open", "totalPages", "labels"], outputs: ["apply", "closed"] }] });
1089
+ }
1090
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: TablePagination, decorators: [{
1091
+ type: Component,
1092
+ args: [{ selector: 'mf-table-pagination', host: { class: 'block' }, imports: [Select, Icon, OverlayModule, TablePageJump], template: "<div class=\"flex flex-wrap items-center justify-between gap-3 px-4 py-3\">\r\n <div class=\"flex items-center gap-2 text-sm text-fg-subtle\">\r\n <span class=\"tabular-nums\"\r\n >{{ rangeStart() }}\u2013{{ rangeEnd() }} / {{ total() }} {{ effectiveLabels().records }}</span\r\n >\r\n <span class=\"text-fg-subtle\">{{ effectiveLabels().perPage }}</span>\r\n <mf-select\r\n [options]=\"pageSizeSelectOptions()\"\r\n [value]=\"pageSize()\"\r\n (valueChange)=\"onPageSizeChange($event)\"\r\n size=\"sm\"\r\n [ariaLabel]=\"effectiveLabels().pageSizeAriaLabel\"\r\n class=\"w-20\"\r\n />\r\n </div>\r\n\r\n <nav\r\n #nav\r\n tabindex=\"-1\"\r\n class=\"flex shrink-0 flex-nowrap items-center gap-1.5 outline-none\"\r\n [attr.aria-label]=\"effectiveLabels().navAriaLabel\"\r\n (keydown)=\"onNavKeydown($event)\"\r\n (focusin)=\"onNavFocusIn()\"\r\n (focusout)=\"onNavFocusOut()\"\r\n >\r\n <div class=\"flex items-center gap-0.5\">\r\n <button\r\n type=\"button\"\r\n [class]=\"cellClasses(false)\"\r\n [disabled]=\"page() === 1\"\r\n [attr.aria-label]=\"effectiveLabels().first\"\r\n (click)=\"goTo(1)\"\r\n >\r\n <span mfIcon [size]=\"16\" class=\"rtl:-scale-x-100\">first_page</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n [class]=\"cellClasses(false)\"\r\n [disabled]=\"page() === 1\"\r\n [attr.aria-label]=\"effectiveLabels().previous\"\r\n (click)=\"goTo(page() - 1)\"\r\n >\r\n <span mfIcon [size]=\"16\" class=\"rtl:-scale-x-100\">chevron_left</span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"hidden items-center gap-0.5 sm:flex\">\r\n @for (item of pageItems(); track $index) {\r\n @if (item === 'ellipsis') {\r\n <button\r\n type=\"button\"\r\n cdkOverlayOrigin\r\n #jumpOrigin=\"cdkOverlayOrigin\"\r\n [class]=\"cellClasses(false)\"\r\n [attr.aria-label]=\"effectiveLabels().jumpToPage\"\r\n (click)=\"toggleJump($index)\"\r\n >\r\n \u2026\r\n </button>\r\n <mf-table-page-jump\r\n [origin]=\"jumpOrigin\"\r\n [open]=\"jumpOpenIndex() === $index\"\r\n [totalPages]=\"totalPages()\"\r\n [labels]=\"effectiveLabels()\"\r\n (apply)=\"onJumpApply($event)\"\r\n (closed)=\"jumpOpenIndex.set(null)\"\r\n />\r\n } @else {\r\n <button\r\n type=\"button\"\r\n [class]=\"cellClasses(item === page())\"\r\n [attr.aria-current]=\"item === page() ? 'page' : null\"\r\n (click)=\"goTo(item)\"\r\n >\r\n {{ item }}\r\n </button>\r\n }\r\n }\r\n </div>\r\n\r\n <span class=\"flex h-9 shrink-0 items-center px-3 text-sm tabular-nums text-fg-muted sm:hidden\">\r\n {{ effectiveLabels().page }} {{ page() }} / {{ totalPages() }}\r\n </span>\r\n\r\n <div class=\"flex items-center gap-0.5\">\r\n <button\r\n type=\"button\"\r\n [class]=\"cellClasses(false)\"\r\n [disabled]=\"page() === totalPages()\"\r\n [attr.aria-label]=\"effectiveLabels().next\"\r\n (click)=\"goTo(page() + 1)\"\r\n >\r\n <span mfIcon [size]=\"16\" class=\"rtl:-scale-x-100\">chevron_right</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n [class]=\"cellClasses(false)\"\r\n [disabled]=\"page() === totalPages()\"\r\n [attr.aria-label]=\"effectiveLabels().last\"\r\n (click)=\"goTo(totalPages())\"\r\n >\r\n <span mfIcon [size]=\"16\" class=\"rtl:-scale-x-100\">last_page</span>\r\n </button>\r\n </div>\r\n </nav>\r\n</div>\r\n" }]
1093
+ }], ctorParameters: () => [], propDecorators: { page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: true }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: true }] }], total: [{ type: i0.Input, args: [{ isSignal: true, alias: "total", required: true }] }], pageSizeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizeOptions", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], pageSizeChange: [{ type: i0.Output, args: ["pageSizeChange"] }], navEl: [{ type: i0.ViewChild, args: ['nav', { isSignal: true }] }] } });
1094
+
1095
+ /**
1096
+ * Sabit satır yüksekliğiyle pencereleme — görünür aralık ± `overscan` satır. Saf fonksiyon;
1097
+ * `Table`'ın `virtual` modu bunu `scrollTop`/görünüm yüksekliği sinyalleriyle çağırır.
1098
+ * `<cdk-virtual-scroll-viewport>` yerine: tablo `<tr>`'leri bir viewport sarmalayıcısına
1099
+ * girince yapışkan başlık, `border-collapse` ve `colspan`'lı boş durum satırı bozuluyordu;
1100
+ * boşluk satırı (`<tr><td [style.height]>`) yaklaşımı tablo semantiğini aynen korur.
1101
+ */
1102
+ function virtualWindow(total, scrollTop, viewportHeight, rowHeight, overscan = 8) {
1103
+ if (total === 0 || rowHeight <= 0)
1104
+ return { start: 0, end: 0, top: 0, bottom: 0 };
1105
+ const first = Math.floor(Math.max(0, scrollTop) / rowHeight);
1106
+ const visible = Math.ceil(Math.max(0, viewportHeight) / rowHeight);
1107
+ const start = Math.min(total, Math.max(0, first - overscan));
1108
+ const end = Math.min(total, first + visible + overscan);
1109
+ return { start, end, top: start * rowHeight, bottom: (total - end) * rowHeight };
1110
+ }
1111
+
1112
+ /** Seçim/genişletme kolonlarının sabit genişliği (`w-10`) ve aksiyon kolonu (`w-20`), px. */
1113
+ const LEADING_COL_PX = 40;
1114
+ const ACTIONS_COL_PX = 80;
1115
+ const FALLBACK_COL_PX = 160;
1116
+ /** Ölçülemeyen ortamda (SSR/jsdom) varsayılan satır yüksekliği — `bodyCellPad` ile uyumlu. */
1117
+ const FALLBACK_ROW_PX = { comfortable: 57, compact: 45 };
1118
+ let tableIdSeq = 0;
1119
+ function sameRecord(a, b) {
1120
+ const keys = Object.keys(a);
1121
+ return keys.length === Object.keys(b).length && keys.every((key) => a[key] === b[key]);
1122
+ }
1123
+ /**
1124
+ * Bu kitin premium data-table'ı.
1125
+ *
1126
+ * İKİ satırlık üst yapı:
1127
+ * 1. **Toolbar** — başlık + kayıt sayacı · her zaman açık arama (bir veri
1128
+ * tablosunda arama gizlenmez, bu bir navbar deseni) · Kolonlar/Yoğunluk
1129
+ * ikon kümesi · `[tableActions]` slotu (sayfanın GERÇEK aksiyonu, tek dolu
1130
+ * buton — Kolonlar/Yoğunluk kasıtlı olarak hayalet kalır, göz doğru yere
1131
+ * gitsin diye). Seçim modunda DA aynı kalır — eskiden seçim toolbar'ın
1132
+ * TAMAMINI yok ediyordu (gerçek bug, kullanıcı arama/filtre bağlamını
1133
+ * kaybediyordu).
1134
+ * 2. **Filtre/seçim şeridi** — filtrelenebilir kolon varsa HER ZAMAN görünür
1135
+ * (`TableFilterBar`, chip + "+ Filtre" komut menüsü — bkz. o dosya). Bir
1136
+ * satır seçilince bu şerit YERİNİ seçim aksiyonlarına bırakır (toolbar'ı
1137
+ * değil) — sağda "N filtre" rozeti filtre bağlamının kaybolmadığını
1138
+ * gösterir.
1139
+ *
1140
+ * `[clientSide]="true"` verildiğinde Table gelen TAM diziyi kendisi
1141
+ * filtreler/sıralar/sayfalar (bkz. table-client-query.ts) ve `queryChange`
1142
+ * emit ETMEZ — backend'i sayfalanmayan uçlar için (tüketici artık kendi
1143
+ * `sortedRows`/`rows`/`total` boilerplate'ini yazmak zorunda kalmaz).
1144
+ */
1145
+ class Table {
1146
+ /** Template'te `[class]="cn(...)"` ile koşullu class birleştirmek için. */
1147
+ cn = cn;
1148
+ title = input('', /* @ts-ignore */
1149
+ ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
1150
+ columns = input.required(/* @ts-ignore */
1151
+ ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
1152
+ /** Sunucu modunda SADECE geçerli sayfa; `[clientSide]="true"` modunda TAM dizi (bkz. sınıf yorumu). */
1153
+ data = input.required(/* @ts-ignore */
1154
+ ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
1155
+ rowKey = input.required(/* @ts-ignore */
1156
+ ...(ngDevMode ? [{ debugName: "rowKey" }] : /* istanbul ignore next */ []));
1157
+ loading = input(false, /* @ts-ignore */
1158
+ ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
1159
+ /** Sunucu modunda zorunlu (toplam kayıt). `clientSide` modunda YOK SAYILIR — toplam `data().length`'ten türetilir. */
1160
+ total = input(0, /* @ts-ignore */
1161
+ ...(ngDevMode ? [{ debugName: "total" }] : /* istanbul ignore next */ []));
1162
+ pageSizeOptions = input([10, 20, 50, 100], /* @ts-ignore */
1163
+ ...(ngDevMode ? [{ debugName: "pageSizeOptions" }] : /* istanbul ignore next */ []));
1164
+ selectable = input(false, /* @ts-ignore */
1165
+ ...(ngDevMode ? [{ debugName: "selectable" }] : /* istanbul ignore next */ []));
1166
+ searchable = input(true, /* @ts-ignore */
1167
+ ...(ngDevMode ? [{ debugName: "searchable" }] : /* istanbul ignore next */ []));
1168
+ stickyHeader = input(false, /* @ts-ignore */
1169
+ ...(ngDevMode ? [{ debugName: "stickyHeader" }] : /* istanbul ignore next */ []));
1170
+ /** `compact` yoğun listeler için satır başına dikey boşluğu daraltır. `model()` — toolbar'daki düğme TOGGLE eder, ama ekran isterse `[(density)]` ile de kontrol edebilir. */
1171
+ density = model('comfortable', /* @ts-ignore */
1172
+ ...(ngDevMode ? [{ debugName: "density" }] : /* istanbul ignore next */ []));
1173
+ rowActions = input({}, /* @ts-ignore */
1174
+ ...(ngDevMode ? [{ debugName: "rowActions" }] : /* istanbul ignore next */ []));
1175
+ /** true ise Table gelen `data()`'yı TAM liste sayar, kendi içinde filtreler/sıralar/sayfalar; `queryChange` emit edilmez (bkz. sınıf yorumu). */
1176
+ clientSide = input(false, /* @ts-ignore */
1177
+ ...(ngDevMode ? [{ debugName: "clientSide" }] : /* istanbul ignore next */ []));
1178
+ /**
1179
+ * Başlık kenarlarında sürükleme tutamacı (klavye: ←/→, çift tık/Enter: içeriğe sığdır).
1180
+ * İlk boyutlandırmada tüm görünür kolonların ölçülen genişliği dondurulur ve tablo
1181
+ * `table-layout: fixed`e geçer — aksi halde otomatik düzen, sürüklenen kolonun
1182
+ * kazandığını komşulardan geri alıp tutamacı imlecin altından kaçırıyordu.
1183
+ */
1184
+ resizableColumns = input(false, { ...(ngDevMode ? { debugName: "resizableColumns" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1185
+ /** Kolon genişlikleri (px, `field` → genişlik). `[(columnWidths)]` ile kalıcılaştırılabilir. */
1186
+ columnWidths = model({}, /* @ts-ignore */
1187
+ ...(ngDevMode ? [{ debugName: "columnWidths" }] : /* istanbul ignore next */ []));
1188
+ /**
1189
+ * Verilirse her satırın başında bir aç/kapa düğmesi çıkar; açık satırın altında bu şablon
1190
+ * tam genişlikte render edilir (context: `{ $implicit: row }`). `virtual` ile birlikte
1191
+ * KULLANILMAZ — değişken yükseklikli satır sabit satır yüksekliği varsayımını bozar.
1192
+ */
1193
+ expandedRowTemplate = input(/* @ts-ignore */
1194
+ ...(ngDevMode ? [undefined, { debugName: "expandedRowTemplate" }] : /* istanbul ignore next */ []));
1195
+ /**
1196
+ * Sayfalama yerine sanal kaydırma: TÜM satırlar tek listede, yalnızca görünen aralık
1197
+ * (± 8 satır) DOM'da. Başlık yapışkan, `aria-rowcount`/`aria-rowindex` tam listeyi
1198
+ * duyurur. Binlerce satırlık `clientSide` listeler için.
1199
+ */
1200
+ virtual = input(false, { ...(ngDevMode ? { debugName: "virtual" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1201
+ /** `virtual` modunda kaydırma alanının yüksekliği (CSS uzunluğu). */
1202
+ virtualHeight = input('32rem', /* @ts-ignore */
1203
+ ...(ngDevMode ? [{ debugName: "virtualHeight" }] : /* istanbul ignore next */ []));
1204
+ /**
1205
+ * Table'ın TÜM chrome metinleri (arama placeholder'ı, boş/sonuç-yok
1206
+ * durumları, satır aksiyonları, yoğunluk düğmesi, filtre çubuğu + sayfalama
1207
+ * dahil — `filter`/`pagination` iç içe alt-alanlar) TEK bir grup input —
1208
+ * eskiden 16 ayrı `input()` + `filterLabels`/`paginationLabels` idi (bkz.
1209
+ * git geçmişi); `DeepPartial` olduğu için sadece değiştirmek istediğin
1210
+ * alanı geçebilirsin, kalanı `MF_TABLE_LABELS`'tan (bkz.
1211
+ * `provideMesgflowUi`) gelir.
1212
+ */
1213
+ labels = input(/* @ts-ignore */
1214
+ ...(ngDevMode ? [undefined, { debugName: "labels" }] : /* istanbul ignore next */ []));
1215
+ tableLabelsToken = inject(MF_TABLE_LABELS);
1216
+ effectiveLabels = computed(() => mergeLabels(this.tableLabelsToken(), this.labels()), /* @ts-ignore */
1217
+ ...(ngDevMode ? [{ debugName: "effectiveLabels" }] : /* istanbul ignore next */ []));
1218
+ queryChange = output();
1219
+ selectionChange = output();
1220
+ edit = output();
1221
+ delete = output();
1222
+ rowDblClick = output();
1223
+ /** Bir satır açıldı/kapandı — ayrıntıyı tembel yüklemek için. */
1224
+ rowExpandedChange = output();
1225
+ host = inject(ElementRef);
1226
+ scroller = viewChild.required('scroller', /* @ts-ignore */
1227
+ ...(ngDevMode ? [{ debugName: "scroller" }] : /* istanbul ignore next */ []));
1228
+ idPrefix = `mf-table-${++tableIdSeq}`;
1229
+ pageState = signal({
1230
+ pageNumber: 1,
1231
+ pageSize: DEFAULT_PAGE_SIZE,
1232
+ }, /* @ts-ignore */
1233
+ ...(ngDevMode ? [{ debugName: "pageState" }] : /* istanbul ignore next */ []));
1234
+ sortState = signal([], /* @ts-ignore */
1235
+ ...(ngDevMode ? [{ debugName: "sortState" }] : /* istanbul ignore next */ []));
1236
+ searchTerm = signal('', /* @ts-ignore */
1237
+ ...(ngDevMode ? [{ debugName: "searchTerm" }] : /* istanbul ignore next */ []));
1238
+ appliedFilters = signal([], /* @ts-ignore */
1239
+ ...(ngDevMode ? [{ debugName: "appliedFilters" }] : /* istanbul ignore next */ []));
1240
+ filterLogic = signal(FilterLogic.And, /* @ts-ignore */
1241
+ ...(ngDevMode ? [{ debugName: "filterLogic" }] : /* istanbul ignore next */ []));
1242
+ selectedRows = signal(new Map(), /* @ts-ignore */
1243
+ ...(ngDevMode ? [{ debugName: "selectedRows" }] : /* istanbul ignore next */ []));
1244
+ hiddenColumns = signal(new Set(), /* @ts-ignore */
1245
+ ...(ngDevMode ? [{ debugName: "hiddenColumns" }] : /* istanbul ignore next */ []));
1246
+ columnsMenuOpen = signal(false, /* @ts-ignore */
1247
+ ...(ngDevMode ? [{ debugName: "columnsMenuOpen" }] : /* istanbul ignore next */ []));
1248
+ FilterLogic = FilterLogic;
1249
+ /** Sabitlenen kolonlar (`pinned: 'left'`) verildikleri sırayla öne alınır. */
1250
+ visibleColumns = computed(() => {
1251
+ const visible = this.columns().filter((c) => !this.hiddenColumns().has(c.field));
1252
+ return [
1253
+ ...visible.filter((c) => c.pinned === 'left'),
1254
+ ...visible.filter((c) => c.pinned !== 'left'),
1255
+ ];
1256
+ }, /* @ts-ignore */
1257
+ ...(ngDevMode ? [{ debugName: "visibleColumns" }] : /* istanbul ignore next */ []));
1258
+ hasPinned = computed(() => this.visibleColumns().some((c) => c.pinned === 'left'), /* @ts-ignore */
1259
+ ...(ngDevMode ? [{ debugName: "hasPinned" }] : /* istanbul ignore next */ []));
1260
+ /** Sabit bölgenin sağ kenarı — ayırıcı gölge bu hücrede. Sabit kolon yoksa `null`. */
1261
+ lastPinnedKey = computed(() => this.visibleColumns()
1262
+ .filter((c) => c.pinned === 'left')
1263
+ .at(-1)?.field ?? null, /* @ts-ignore */
1264
+ ...(ngDevMode ? [{ debugName: "lastPinnedKey" }] : /* istanbul ignore next */ []));
1265
+ /** Ölçülen satır-başı ofsetleri (px, `inset-inline-start`) — anahtar: `field`, `__expand`, `__select`. */
1266
+ pinOffsets = signal({}, /* @ts-ignore */
1267
+ ...(ngDevMode ? [{ debugName: "pinOffsets" }] : /* istanbul ignore next */ []));
1268
+ /** Başlık hücrelerinin ölçülen genişliği — tutamacın `aria-valuenow`u dondurulmadan önce buradan. */
1269
+ measuredWidths = signal({}, /* @ts-ignore */
1270
+ ...(ngDevMode ? [{ debugName: "measuredWidths" }] : /* istanbul ignore next */ []));
1271
+ expandable = computed(() => !!this.expandedRowTemplate() && !this.virtual(), /* @ts-ignore */
1272
+ ...(ngDevMode ? [{ debugName: "expandable" }] : /* istanbul ignore next */ []));
1273
+ expandedKeys = signal(new Set(), /* @ts-ignore */
1274
+ ...(ngDevMode ? [{ debugName: "expandedKeys" }] : /* istanbul ignore next */ []));
1275
+ fixedLayout = computed(() => Object.keys(this.columnWidths()).length > 0, /* @ts-ignore */
1276
+ ...(ngDevMode ? [{ debugName: "fixedLayout" }] : /* istanbul ignore next */ []));
1277
+ /** Sabit düzende tablonun toplam genişliği — kolonlar kapsayıcıdan genişse yatay kaydırma doğar. */
1278
+ tableWidth = computed(() => {
1279
+ if (!this.fixedLayout())
1280
+ return null;
1281
+ const widths = this.columnWidths();
1282
+ const columns = this.visibleColumns().reduce((sum, col) => sum + (widths[col.field] ?? FALLBACK_COL_PX), 0);
1283
+ const leading = (this.selectable() ? LEADING_COL_PX : 0) + (this.expandable() ? LEADING_COL_PX : 0);
1284
+ return columns + leading + (this.hasRowActions() ? ACTIONS_COL_PX : 0);
1285
+ }, /* @ts-ignore */
1286
+ ...(ngDevMode ? [{ debugName: "tableWidth" }] : /* istanbul ignore next */ []));
1287
+ scrollTop = signal(0, /* @ts-ignore */
1288
+ ...(ngDevMode ? [{ debugName: "scrollTop" }] : /* istanbul ignore next */ []));
1289
+ viewportHeight = signal(0, /* @ts-ignore */
1290
+ ...(ngDevMode ? [{ debugName: "viewportHeight" }] : /* istanbul ignore next */ []));
1291
+ /** Kaydırma alanının görünür genişliği — ayrıntı satırı içeriği yatay kaydırmada bu genişlikte sabit kalır. */
1292
+ viewportWidth = signal(0, /* @ts-ignore */
1293
+ ...(ngDevMode ? [{ debugName: "viewportWidth" }] : /* istanbul ignore next */ []));
1294
+ measuredRowHeight = signal(0, /* @ts-ignore */
1295
+ ...(ngDevMode ? [{ debugName: "measuredRowHeight" }] : /* istanbul ignore next */ []));
1296
+ /** Tablo/kapsayıcı yeniden boyutlandığında sabit kolon ofsetleri yeniden ölçülür. */
1297
+ layoutTick = signal(0, /* @ts-ignore */
1298
+ ...(ngDevMode ? [{ debugName: "layoutTick" }] : /* istanbul ignore next */ []));
1299
+ hasFilterableColumns = computed(() => this.columns().some((c) => c.filter), /* @ts-ignore */
1300
+ ...(ngDevMode ? [{ debugName: "hasFilterableColumns" }] : /* istanbul ignore next */ []));
1301
+ activeFilterCount = computed(() => this.appliedFilters().length, /* @ts-ignore */
1302
+ ...(ngDevMode ? [{ debugName: "activeFilterCount" }] : /* istanbul ignore next */ []));
1303
+ isFiltered = computed(() => !!this.searchTerm().trim() || this.appliedFilters().length > 0, /* @ts-ignore */
1304
+ ...(ngDevMode ? [{ debugName: "isFiltered" }] : /* istanbul ignore next */ []));
1305
+ hasRowActions = computed(() => !!(this.rowActions().edit || this.rowActions().delete), /* @ts-ignore */
1306
+ ...(ngDevMode ? [{ debugName: "hasRowActions" }] : /* istanbul ignore next */ []));
1307
+ columnSpan = computed(() => this.visibleColumns().length +
1308
+ (this.selectable() ? 1 : 0) +
1309
+ (this.expandable() ? 1 : 0) +
1310
+ (this.hasRowActions() ? 1 : 0), /* @ts-ignore */
1311
+ ...(ngDevMode ? [{ debugName: "columnSpan" }] : /* istanbul ignore next */ []));
1312
+ /** `clientSide` modunda `data()` TAM listedir — burada filtrelenip/sıralanıp/sayfalanır (bkz. table-client-query.ts). Sunucu modunda kullanılmaz. */
1313
+ clientResult = computed(() => {
1314
+ const rows = this.data();
1315
+ const filtered = this.isFiltered()
1316
+ ? rows.filter((row) => matchesFilterGroup(row, this.buildFilter()))
1317
+ : [...rows];
1318
+ const sorted = sortRows(filtered, this.sortState());
1319
+ if (this.virtual())
1320
+ return { items: sorted, total: filtered.length };
1321
+ const { pageNumber, pageSize } = this.pageState();
1322
+ const start = (pageNumber - 1) * pageSize;
1323
+ return { items: sorted.slice(start, start + pageSize), total: filtered.length };
1324
+ }, /* @ts-ignore */
1325
+ ...(ngDevMode ? [{ debugName: "clientResult" }] : /* istanbul ignore next */ []));
1326
+ rows = computed(() => this.clientSide() ? this.clientResult().items : this.data(), /* @ts-ignore */
1327
+ ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
1328
+ rowsTotal = computed(() => this.clientSide() ? this.clientResult().total : this.total(), /* @ts-ignore */
1329
+ ...(ngDevMode ? [{ debugName: "rowsTotal" }] : /* istanbul ignore next */ []));
1330
+ /** `virtual` modunda görünen aralık; değilse `null` (tüm `rows()` render edilir). */
1331
+ virtualRange = computed(() => {
1332
+ if (!this.virtual())
1333
+ return null;
1334
+ const rowHeight = this.measuredRowHeight() || FALLBACK_ROW_PX[this.density()];
1335
+ return virtualWindow(this.rows().length, this.scrollTop(), this.viewportHeight() || 600, rowHeight);
1336
+ }, /* @ts-ignore */
1337
+ ...(ngDevMode ? [{ debugName: "virtualRange" }] : /* istanbul ignore next */ []));
1338
+ displayRows = computed(() => {
1339
+ const range = this.virtualRange();
1340
+ return range ? this.rows().slice(range.start, range.end) : this.rows();
1341
+ }, /* @ts-ignore */
1342
+ ...(ngDevMode ? [{ debugName: "displayRows" }] : /* istanbul ignore next */ []));
1343
+ rowIndexOffset = computed(() => this.virtualRange()?.start ?? 0, /* @ts-ignore */
1344
+ ...(ngDevMode ? [{ debugName: "rowIndexOffset" }] : /* istanbul ignore next */ []));
1345
+ isStickyHeader = computed(() => this.stickyHeader() || this.virtual(), /* @ts-ignore */
1346
+ ...(ngDevMode ? [{ debugName: "isStickyHeader" }] : /* istanbul ignore next */ []));
1347
+ skeletonIndexes = computed(() => Array.from({ length: Math.min(this.pageState().pageSize, 8) }, (_, i) => i), /* @ts-ignore */
1348
+ ...(ngDevMode ? [{ debugName: "skeletonIndexes" }] : /* istanbul ignore next */ []));
1349
+ /** `overflow-x-auto` HER ZAMAN gerekli (dar viewport'ta yatay kaydırma olmadan tablo taşıp sağdaki satır aksiyonlarını görünmez kılıyordu). `stickyHeader` ek olarak dikey kaydırma+sabit başlık ekliyor. */
1350
+ scrollWrapperClasses = computed(() => cn('overflow-x-auto', this.virtual() && 'overflow-y-auto overscroll-contain', this.stickyHeader() && !this.virtual() && 'max-h-[28rem] overflow-y-auto'), /* @ts-ignore */
1351
+ ...(ngDevMode ? [{ debugName: "scrollWrapperClasses" }] : /* istanbul ignore next */ []));
1352
+ /*
1353
+ * `compact` eskiden `py-2`/`py-2`ydi — `comfortable`'a (py-2.5/py-3) çok
1354
+ * yakın durduğundan geçiş neredeyse fark edilmiyordu (gerçek kullanıcı
1355
+ * bulgusu: "1px anca oynuyor, sık gibi durmuyor"). Fark artık satır
1356
+ * başına ~16px'e çıkarıldı (60px → ~45px) — ikon/checkbox boyutları
1357
+ * (18-20px) hâlâ rahat sığıyor, taşma/kırpılma YOK (bkz. dev sunucuda
1358
+ * canlı ölçüm).
1359
+ */
1360
+ headerCellPad = computed(() => this.density() === 'compact' ? 'py-1.5' : 'py-2.5', /* @ts-ignore */
1361
+ ...(ngDevMode ? [{ debugName: "headerCellPad" }] : /* istanbul ignore next */ []));
1362
+ bodyCellPad = computed(() => (this.density() === 'compact' ? 'py-1' : 'py-3'), /* @ts-ignore */
1363
+ ...(ngDevMode ? [{ debugName: "bodyCellPad" }] : /* istanbul ignore next */ []));
1364
+ allChecked = computed(() => {
1365
+ const rows = this.rows();
1366
+ return rows.length > 0 && rows.every((row) => this.selectedRows().has(row[this.rowKey()]));
1367
+ }, /* @ts-ignore */
1368
+ ...(ngDevMode ? [{ debugName: "allChecked" }] : /* istanbul ignore next */ []));
1369
+ indeterminate = computed(() => this.selectedRows().size > 0 && !this.allChecked(), /* @ts-ignore */
1370
+ ...(ngDevMode ? [{ debugName: "indeterminate" }] : /* istanbul ignore next */ []));
1371
+ selectedCount = computed(() => this.selectedRows().size, /* @ts-ignore */
1372
+ ...(ngDevMode ? [{ debugName: "selectedCount" }] : /* istanbul ignore next */ []));
1373
+ /** İlk yüklemede satır yoksa iskelet gösterilir. Sonraki her sorguda (arama/filtre/sayfa) satırlar YERİNDE kalır, sadece soluklaşır — eskiden HER sorguda tüm tablo iskelete dönüp "yanıp sönüyordu" (gerçek bulgu). */
1374
+ showSkeleton = computed(() => this.loading() && this.rows().length === 0, /* @ts-ignore */
1375
+ ...(ngDevMode ? [{ debugName: "showSkeleton" }] : /* istanbul ignore next */ []));
1376
+ showStaleOverlay = computed(() => this.loading() && this.rows().length > 0, /* @ts-ignore */
1377
+ ...(ngDevMode ? [{ debugName: "showStaleOverlay" }] : /* istanbul ignore next */ []));
1378
+ constructor() {
1379
+ // Sayfa/sıralama/filtre değişince `rows()` yeni bir referans alır —
1380
+ // önceki seçim artık görünürdeki satırlarla eşleşmeyebileceğinden temizlenir.
1381
+ effect(() => {
1382
+ this.rows();
1383
+ untracked(() => {
1384
+ this.selectedRows.set(new Map());
1385
+ // Yeni sonuç kümesi (arama/filtre/sıralama) — sanal listede eski kaydırma konumu anlamsız.
1386
+ if (this.virtual() && this.scrollTop() !== 0) {
1387
+ this.scroller().nativeElement.scrollTop = 0;
1388
+ this.scrollTop.set(0);
1389
+ }
1390
+ });
1391
+ });
1392
+ // Ölçüm: sabit kolon ofsetleri + sanal satır yüksekliği. Değer değişmedikçe yazılmaz
1393
+ // (render → ölçüm → set → render döngüsü tek turda durulur).
1394
+ afterRenderEffect({
1395
+ read: () => {
1396
+ this.visibleColumns();
1397
+ this.columnWidths();
1398
+ this.density();
1399
+ this.layoutTick();
1400
+ this.displayRows();
1401
+ const root = this.host.nativeElement;
1402
+ if (this.hasPinned()) {
1403
+ const next = {};
1404
+ const rtl = isRtl(root);
1405
+ root.querySelectorAll('thead th[data-pin]').forEach((th) => {
1406
+ // Satır başından uzaklık — RTL'de sağ kenardan (sabit kolonlar sağa yapışır).
1407
+ const table = th.offsetParent;
1408
+ next[th.dataset['pin']] = rtl
1409
+ ? (table?.offsetWidth ?? 0) - th.offsetLeft - th.offsetWidth
1410
+ : th.offsetLeft;
1411
+ });
1412
+ if (!sameRecord(next, untracked(this.pinOffsets)))
1413
+ this.pinOffsets.set(next);
1414
+ }
1415
+ if (this.resizableColumns()) {
1416
+ const widths = {};
1417
+ root.querySelectorAll('thead th[data-col]').forEach((th) => {
1418
+ widths[th.dataset['col']] = Math.round(th.getBoundingClientRect().width);
1419
+ });
1420
+ if (!sameRecord(widths, untracked(this.measuredWidths)))
1421
+ this.measuredWidths.set(widths);
1422
+ }
1423
+ if (this.virtual()) {
1424
+ const row = root.querySelector('tbody tr[data-row]');
1425
+ const height = row?.getBoundingClientRect().height ?? 0;
1426
+ if (height > 0 && Math.abs(height - untracked(this.measuredRowHeight)) > 0.5)
1427
+ this.measuredRowHeight.set(height);
1428
+ }
1429
+ },
1430
+ });
1431
+ const destroyRef = inject(DestroyRef);
1432
+ afterNextRender(() => {
1433
+ if (typeof ResizeObserver === 'undefined')
1434
+ return;
1435
+ const scroller = this.scroller().nativeElement;
1436
+ const observer = new ResizeObserver(() => {
1437
+ this.viewportHeight.set(scroller.clientHeight);
1438
+ this.viewportWidth.set(scroller.clientWidth);
1439
+ this.layoutTick.update((tick) => tick + 1);
1440
+ });
1441
+ observer.observe(scroller);
1442
+ const table = scroller.querySelector('table');
1443
+ if (table)
1444
+ observer.observe(table);
1445
+ destroyRef.onDestroy(() => observer.disconnect());
1446
+ });
1447
+ }
1448
+ onScroll(event) {
1449
+ if (!this.virtual())
1450
+ return;
1451
+ this.scrollTop.set(event.target.scrollTop);
1452
+ }
1453
+ // ---- Kolon genişliği ----
1454
+ widthStyle(col) {
1455
+ const px = this.columnWidths()[col.field];
1456
+ return px ? `${px}px` : (col.width ?? null);
1457
+ }
1458
+ /** Tutamacın `aria-valuenow`u: kaydedilmiş > ölçülen > alt sınır. */
1459
+ currentWidth(col) {
1460
+ return (this.columnWidths()[col.field] || this.measuredWidths()[col.field] || (col.minWidth ?? 80));
1461
+ }
1462
+ isResizable(col) {
1463
+ return this.resizableColumns() && col.resizable !== false;
1464
+ }
1465
+ resizeAriaLabel(col) {
1466
+ return `${this.effectiveLabels().resizeColumn}: ${col.header}`;
1467
+ }
1468
+ /** Henüz genişliği kaydedilmemiş görünür kolonları ölçülen genişlikte dondurur. */
1469
+ freezeWidths() {
1470
+ const current = { ...this.columnWidths() };
1471
+ let changed = false;
1472
+ this.host.nativeElement.querySelectorAll('thead th[data-col]').forEach((th) => {
1473
+ const field = th.dataset['col'];
1474
+ if (current[field] !== undefined)
1475
+ return;
1476
+ current[field] = Math.round(th.getBoundingClientRect().width) || FALLBACK_COL_PX;
1477
+ changed = true;
1478
+ });
1479
+ if (changed)
1480
+ this.columnWidths.set(current);
1481
+ }
1482
+ onColumnResize(col, width) {
1483
+ this.columnWidths.update((widths) => ({ ...widths, [col.field]: width }));
1484
+ }
1485
+ /** İçeriğe sığdır: kolondaki en geniş hücre içeriği + yatay dolgu (`px-4` × 2). */
1486
+ autofitColumn(col) {
1487
+ this.freezeWidths();
1488
+ let widest = 0;
1489
+ this.host.nativeElement.querySelectorAll('[data-col]').forEach((cell) => {
1490
+ if (cell.dataset['col'] !== col.field)
1491
+ return;
1492
+ const content = cell.firstElementChild;
1493
+ widest = Math.max(widest, content?.getBoundingClientRect().width ?? 0);
1494
+ });
1495
+ if (widest > 0)
1496
+ this.onColumnResize(col, Math.max(col.minWidth ?? 80, Math.ceil(widest + 32)));
1497
+ }
1498
+ // ---- Sabit kolonlar ----
1499
+ /** Sabitlenecek bir hücre mi? Öndeki seçim/genişletme kolonları, sabit kolon varsa onlarla gider. */
1500
+ isPinnedKey(key) {
1501
+ if (!this.hasPinned())
1502
+ return false;
1503
+ if (key === '__select' || key === '__expand')
1504
+ return true;
1505
+ return this.visibleColumns().some((c) => c.field === key && c.pinned === 'left');
1506
+ }
1507
+ pinStart(key) {
1508
+ return this.isPinnedKey(key) ? (this.pinOffsets()[key] ?? 0) : null;
1509
+ }
1510
+ /** Başlık hücresinin sabitleme sınıfları (`thead` zaten opak `bg-surface-sunken`). */
1511
+ pinHeadClasses(key) {
1512
+ if (!this.isPinnedKey(key))
1513
+ return '';
1514
+ return cn('sticky z-[2] bg-surface-sunken', this.edgeShadow(key));
1515
+ }
1516
+ /** Gövde hücresi: opak zemin şart — altından kayan hücreler sızmasın (bkz. `rowBg`). */
1517
+ pinBodyClasses(key, row) {
1518
+ if (!this.isPinnedKey(key))
1519
+ return '';
1520
+ return cn('sticky z-[1]', this.rowBg(row), this.edgeShadow(key));
1521
+ }
1522
+ /** `border-collapse` altında kenarlıklar yapışkan hücreyle gitmez — içe gölge gider. */
1523
+ edgeShadow(key) {
1524
+ return key === this.lastPinnedKey()
1525
+ ? 'shadow-[inset_-1px_0_0_var(--mf-border)] rtl:shadow-[inset_1px_0_0_var(--mf-border)]'
1526
+ : '';
1527
+ }
1528
+ // ---- Satır genişletme ----
1529
+ isExpanded(row) {
1530
+ return this.expandedKeys().has(row[this.rowKey()]);
1531
+ }
1532
+ toggleExpanded(row) {
1533
+ const key = row[this.rowKey()];
1534
+ const next = new Set(this.expandedKeys());
1535
+ const expanded = !next.has(key);
1536
+ if (expanded)
1537
+ next.add(key);
1538
+ else
1539
+ next.delete(key);
1540
+ this.expandedKeys.set(next);
1541
+ this.rowExpandedChange.emit({ row, expanded });
1542
+ }
1543
+ expandedRowId(index) {
1544
+ return `${this.idPrefix}-detail-${index}`;
1545
+ }
1546
+ expandAriaLabel(row) {
1547
+ const labels = this.effectiveLabels();
1548
+ return this.withRowIdentity(row, this.isExpanded(row) ? labels.collapseRow : labels.expandRow);
1549
+ }
1550
+ trackByRow = (_, row) => row[this.rowKey()];
1551
+ /** `type: 'numeric'` açıkça `align` verilmemişse sağa yaslanır; diğer tüm kolonlar `col.align`'a düşer (varsayılan sol). */
1552
+ resolvedAlign(col) {
1553
+ return col.align ?? (col.type === 'numeric' ? 'right' : 'left');
1554
+ }
1555
+ alignClass(align) {
1556
+ return align === 'center' ? 'text-center' : align === 'right' ? 'text-end' : 'text-start';
1557
+ }
1558
+ /** Kimlik kolonu (`primary`) `text-fg font-medium` — göz bir satırda tutunacak bir çapa buluyor; diğer her hücre `text-fg-muted` kalır (eskiden HER hücre aynı ağırlıktaydı). */
1559
+ cellClasses(col) {
1560
+ return cn(col.primary ? 'text-fg font-medium' : 'text-fg-muted', col.type === 'numeric' && 'tabular-nums', !col.cellTemplate && 'truncate');
1561
+ }
1562
+ /** `[title]` (kesilmiş metin için native tooltip) ve boş/`null` değerleri güvenle metne çevirir — Angular şablonları global `String()`'e erişemiyor. */
1563
+ cellText(row, col) {
1564
+ const value = row[col.field];
1565
+ return value === null || value === undefined ? '' : String(value);
1566
+ }
1567
+ /** Satır checkbox'ının erişilebilir adı — kimlik kolonundan (`primary`, yoksa ilk kolon) türetilir, tüm satırların AYNI "Select row" metnini taşımasını önler. */
1568
+ rowSelectAriaLabel(row) {
1569
+ return this.withRowIdentity(row, this.effectiveLabels().selectRowAriaLabel);
1570
+ }
1571
+ /** "Etiket: kimlik" — kimlik kolonundan (`primary`, yoksa ilk kolon) türetilir. */
1572
+ withRowIdentity(row, label) {
1573
+ const col = this.columns().find((c) => c.primary) ?? this.columns()[0];
1574
+ const value = col ? this.cellText(row, col) : '';
1575
+ return value ? `${label}: ${value}` : label;
1576
+ }
1577
+ /**
1578
+ * Bu tazelemenin imza motiflerinden "ray": seçili satırın sol kenarında
1579
+ * 2px'lik dolu marka rengi çizgi. Kenarlık HER ZAMAN vardır (`border-s-2
1580
+ * border-s-transparent`), sadece rengi değişir — eskiden kenarlık sadece
1581
+ * SEÇİLİYKEN ekleniyordu, `border-collapse: collapse` altında bu 2px
1582
+ * gerçek yer kaplıyor ve her tıklamada ilk hücrenin içeriği 2px kayıyordu
1583
+ * (gerçek layout bug'ı, tarayıcıda `getBoundingClientRect` ile doğrulandı).
1584
+ * `group/row`: sağdaki sabit aksiyon hücresinin hover rengini satırla
1585
+ * senkron tutmak için (bkz. stickyActionClasses).
1586
+ */
1587
+ rowClasses(row) {
1588
+ const selected = this.isChecked(row);
1589
+ return cn('group/row border-b border-b-hairline border-s-2 transition-colors duration-fast ease-emphasis', selected
1590
+ ? 'border-s-accent-fill bg-primary-tint'
1591
+ : 'border-s-transparent hover:bg-surface-alt', this.showStaleOverlay() && 'opacity-60');
1592
+ }
1593
+ /**
1594
+ * Sabit (sticky) aksiyon hücresi için — `rowClasses()`'ın aksine `bg-primary-tint`
1595
+ * DOĞRUDAN kullanılamaz: bu hücre yatay kaydırmada AYNI satırın diğer
1596
+ * hücrelerinin ÜSTÜNÜ kapatıyor, saydam bir zemin altındaki kaydırılmış
1597
+ * içeriği sızdırırdı (gerçek bir composite hatası). Bunun yerine OPAK
1598
+ * `bg-surface` + üstüne tint rengiyle boyanmış bir `background-image`
1599
+ * gradyanı — iki katman üst üste bindiğinde görsel sonuç `bg-primary-tint`
1600
+ * ile BİREBİR aynı, ama alttan hiçbir şey sızmaz. `--mf-primary-tint`
1601
+ * (`--color-primary-tint` DEĞİL) — arbitrary value içindeki `var()`
1602
+ * referansları Tailwind'in "@theme inline" tarama/eleme sezgisine bağlı,
1603
+ * `--mf-*` rol katmanı KENDİSİ her zaman `@layer base`'de KOŞULSUZ yazılır
1604
+ * (bkz. `tabs.variants.ts`'teki aynı karar).
1605
+ */
1606
+ rowBg(row) {
1607
+ return this.isChecked(row)
1608
+ ? 'bg-surface bg-[linear-gradient(var(--mf-primary-tint),var(--mf-primary-tint))]'
1609
+ : 'bg-surface group-hover/row:bg-surface-alt';
1610
+ }
1611
+ stickyActionClasses(row) {
1612
+ return cn('sticky end-0 z-[1] border-s border-border text-end', this.bodyCellPad(), this.rowBg(row));
1613
+ }
1614
+ /** `SearchInput` kendi debounce'unu zaten uyguladı — burada gelen değer commit edilmiş (nihai). */
1615
+ onSearchCommit(value) {
1616
+ this.searchTerm.set(value);
1617
+ this.pageState.update((p) => ({ ...p, pageNumber: 1 }));
1618
+ this.emitQuery();
1619
+ }
1620
+ /**
1621
+ * Düz tık: bu alanı TEK sıralama kriteri yapar (zaten TEK/birincil kriterse
1622
+ * yön döngüsü asc→desc→yok). Shift+tık: İKİNCİL sıralama ekler/döndürür/
1623
+ * kaldırır — backend en fazla 3 alan kabul ediyor (`SortExpressionBuilder.
1624
+ * MaxSortFields`). Eskiden çoklu sıralama YOKTU (yeni Table'ın eski
1625
+ * `shared/components/data-table`'a göre kaybettiği iki yetenekten biri).
1626
+ */
1627
+ onSortClick(column, event) {
1628
+ if (!column.sortable)
1629
+ return;
1630
+ const current = this.sortState();
1631
+ const index = current.findIndex((s) => s.field === column.field);
1632
+ if (!event?.shiftKey) {
1633
+ if (current.length === 1 && index === 0) {
1634
+ const next = current[0].direction === SortDirection.Ascending
1635
+ ? [{ field: column.field, direction: SortDirection.Descending }]
1636
+ : [];
1637
+ this.sortState.set(next);
1638
+ }
1639
+ else {
1640
+ this.sortState.set([{ field: column.field, direction: SortDirection.Ascending }]);
1641
+ }
1642
+ }
1643
+ else if (index === -1) {
1644
+ if (current.length >= 3)
1645
+ return;
1646
+ this.sortState.set([...current, { field: column.field, direction: SortDirection.Ascending }]);
1647
+ }
1648
+ else {
1649
+ const entry = current[index];
1650
+ this.sortState.set(entry.direction === SortDirection.Ascending
1651
+ ? current.map((s, i) => (i === index ? { ...s, direction: SortDirection.Descending } : s))
1652
+ : current.filter((_, i) => i !== index));
1653
+ }
1654
+ this.emitQuery();
1655
+ }
1656
+ sortDirectionFor(field) {
1657
+ return this.sortState().find((s) => s.field === field)?.direction ?? null;
1658
+ }
1659
+ /** 1 tabanlı sıra konumu — yalnızca 2+ alan aktifken gösterilir (tekil sıralamada numara gürültü olurdu). */
1660
+ sortOrderFor(field) {
1661
+ if (this.sortState().length < 2)
1662
+ return null;
1663
+ const index = this.sortState().findIndex((s) => s.field === field);
1664
+ return index === -1 ? null : index + 1;
1665
+ }
1666
+ onFiltersChange(filters) {
1667
+ this.appliedFilters.set(filters);
1668
+ this.pageState.update((p) => ({ ...p, pageNumber: 1 }));
1669
+ this.emitQuery();
1670
+ }
1671
+ onFilterLogicChange(logic) {
1672
+ this.filterLogic.set(logic);
1673
+ if (this.appliedFilters().length > 1)
1674
+ this.emitQuery();
1675
+ }
1676
+ clearAllFilters() {
1677
+ this.searchTerm.set('');
1678
+ this.appliedFilters.set([]);
1679
+ this.pageState.update((p) => ({ ...p, pageNumber: 1 }));
1680
+ this.emitQuery();
1681
+ }
1682
+ onPageChange(page) {
1683
+ this.pageState.update((p) => ({ ...p, pageNumber: page }));
1684
+ this.emitQuery();
1685
+ }
1686
+ onPageSizeChange(size) {
1687
+ this.pageState.set({ pageNumber: 1, pageSize: size });
1688
+ this.emitQuery();
1689
+ }
1690
+ toggleDensity() {
1691
+ this.density.update((d) => (d === 'comfortable' ? 'compact' : 'comfortable'));
1692
+ }
1693
+ densityToggleTooltip() {
1694
+ const labels = this.effectiveLabels();
1695
+ return `${labels.density}: ${this.density() === 'comfortable' ? labels.densityCompact : labels.densityComfortable}`;
1696
+ }
1697
+ onHiddenColumnsChange(hidden) {
1698
+ this.hiddenColumns.set(new Set(hidden));
1699
+ }
1700
+ isChecked(row) {
1701
+ return this.selectedRows().has(row[this.rowKey()]);
1702
+ }
1703
+ onCheckAll(checked) {
1704
+ const next = new Map();
1705
+ if (checked)
1706
+ for (const row of this.rows())
1707
+ next.set(row[this.rowKey()], row);
1708
+ this.selectedRows.set(next);
1709
+ this.selectionChange.emit([...next.values()]);
1710
+ }
1711
+ onCheckRow(row, checked) {
1712
+ const next = new Map(this.selectedRows());
1713
+ if (checked)
1714
+ next.set(row[this.rowKey()], row);
1715
+ else
1716
+ next.delete(row[this.rowKey()]);
1717
+ this.selectedRows.set(next);
1718
+ this.selectionChange.emit([...next.values()]);
1719
+ }
1720
+ clearSelection() {
1721
+ this.selectedRows.set(new Map());
1722
+ this.selectionChange.emit([]);
1723
+ }
1724
+ buildFilter() {
1725
+ const term = this.searchTerm().trim();
1726
+ const searchGroup = term ? this.searchGroupFor(term) : undefined;
1727
+ const chipGroup = buildFilterGroup(this.appliedFilters(), this.filterLogic());
1728
+ if (searchGroup && chipGroup)
1729
+ return { logic: FilterLogic.And, groups: [searchGroup, chipGroup] };
1730
+ return searchGroup ?? chipGroup;
1731
+ }
1732
+ searchGroupFor(term) {
1733
+ const fields = this.columns()
1734
+ .filter((c) => c.searchable)
1735
+ .map((c) => c.field);
1736
+ if (!fields.length)
1737
+ return undefined;
1738
+ return {
1739
+ logic: FilterLogic.Or,
1740
+ conditions: fields.map((field) => ({
1741
+ field,
1742
+ operator: FilterOperator.Contains,
1743
+ value: term,
1744
+ })),
1745
+ };
1746
+ }
1747
+ emitQuery() {
1748
+ if (this.clientSide())
1749
+ return;
1750
+ this.queryChange.emit(canonicalPagedRequest({
1751
+ page: this.pageState(),
1752
+ sort: this.sortState(),
1753
+ filter: this.buildFilter(),
1754
+ }));
1755
+ }
1756
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: Table, deps: [], target: i0.ɵɵFactoryTarget.Component });
1757
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: Table, isStandalone: true, selector: "mf-table", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null }, rowKey: { classPropertyName: "rowKey", publicName: "rowKey", isSignal: true, isRequired: true, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, total: { classPropertyName: "total", publicName: "total", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, stickyHeader: { classPropertyName: "stickyHeader", publicName: "stickyHeader", isSignal: true, isRequired: false, transformFunction: null }, density: { classPropertyName: "density", publicName: "density", isSignal: true, isRequired: false, transformFunction: null }, rowActions: { classPropertyName: "rowActions", publicName: "rowActions", isSignal: true, isRequired: false, transformFunction: null }, clientSide: { classPropertyName: "clientSide", publicName: "clientSide", isSignal: true, isRequired: false, transformFunction: null }, resizableColumns: { classPropertyName: "resizableColumns", publicName: "resizableColumns", isSignal: true, isRequired: false, transformFunction: null }, columnWidths: { classPropertyName: "columnWidths", publicName: "columnWidths", isSignal: true, isRequired: false, transformFunction: null }, expandedRowTemplate: { classPropertyName: "expandedRowTemplate", publicName: "expandedRowTemplate", isSignal: true, isRequired: false, transformFunction: null }, virtual: { classPropertyName: "virtual", publicName: "virtual", isSignal: true, isRequired: false, transformFunction: null }, virtualHeight: { classPropertyName: "virtualHeight", publicName: "virtualHeight", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { density: "densityChange", columnWidths: "columnWidthsChange", queryChange: "queryChange", selectionChange: "selectionChange", edit: "edit", delete: "delete", rowDblClick: "rowDblClick", rowExpandedChange: "rowExpandedChange" }, host: { classAttribute: "block" }, viewQueries: [{ propertyName: "scroller", first: true, predicate: ["scroller"], descendants: true, isSignal: true }], exportAs: ["mfTable"], ngImport: i0, template: "<div class=\"relative overflow-hidden rounded-lg border border-hairline bg-surface\">\r\n @if (showStaleOverlay()) {\r\n <div\r\n class=\"pointer-events-none absolute inset-x-0 top-0 z-20 h-0.5 overflow-hidden bg-primary-tint\"\r\n aria-hidden=\"true\"\r\n >\r\n <div class=\"h-full w-1/4 animate-progress-slide bg-accent-fill\"></div>\r\n </div>\r\n }\r\n\r\n <!--\r\n Sat\u0131r 1 \u2014 toolbar. Se\u00E7im modunda da AYNI kal\u0131r: eskiden bir sat\u0131r\r\n se\u00E7ilince ba\u015Fl\u0131k+arama+filtre tamamen kaybolup yerine \"N se\u00E7ildi\"\r\n geliyordu (ger\u00E7ek bug, kullan\u0131c\u0131 arama/filtre ba\u011Flam\u0131n\u0131 kaybediyordu).\r\n Art\u0131k sadece Sat\u0131r 2 (a\u015Fa\u011F\u0131da) se\u00E7im aksiyonlar\u0131na devrediliyor.\r\n -->\r\n <div class=\"flex flex-wrap items-center justify-between gap-3 border-b border-border px-4 py-3\">\r\n <div class=\"flex min-w-0 items-center gap-2.5\">\r\n @if (title()) {\r\n <h3 class=\"truncate font-display text-title text-fg\">{{ title() }}</h3>\r\n }\r\n </div>\r\n\r\n <div class=\"flex flex-1 flex-wrap items-center justify-end gap-2\">\r\n @if (searchable()) {\r\n <mf-search-input\r\n [collapsible]=\"false\"\r\n [value]=\"searchTerm()\"\r\n [placeholder]=\"effectiveLabels().searchPlaceholder\"\r\n [ariaLabel]=\"effectiveLabels().searchPlaceholder\"\r\n [loading]=\"loading()\"\r\n (valueChange)=\"onSearchCommit($event)\"\r\n />\r\n }\r\n\r\n <!-- Kolonlar + Yo\u011Funluk: B\u0130LEREK hayalet (text/icon) kal\u0131yor \u2014 g\u00F6z [tableActions]'daki ger\u00E7ek CTA'ya gitsin diye (bkz. table.ts s\u0131n\u0131f yorumu, toolbar hiyerar\u015Fisi bulgusu). -->\r\n <div class=\"flex items-center gap-0.5\">\r\n <button\r\n mfButton\r\n tone=\"neutral\"\r\n appearance=\"text\"\r\n size=\"icon\"\r\n shape=\"square\"\r\n type=\"button\"\r\n cdkOverlayOrigin\r\n #columnsOrigin=\"cdkOverlayOrigin\"\r\n [mfTooltip]=\"effectiveLabels().columns\"\r\n [attr.aria-label]=\"effectiveLabels().columns\"\r\n (click)=\"columnsMenuOpen.set(!columnsMenuOpen())\"\r\n >\r\n <span mfIcon [size]=\"18\">view_column</span>\r\n </button>\r\n <mf-table-columns-menu\r\n [columns]=\"columns()\"\r\n [hidden]=\"hiddenColumns()\"\r\n [origin]=\"columnsOrigin\"\r\n [open]=\"columnsMenuOpen()\"\r\n [label]=\"effectiveLabels().columns\"\r\n (hiddenChange)=\"onHiddenColumnsChange($event)\"\r\n (closed)=\"columnsMenuOpen.set(false)\"\r\n />\r\n\r\n <button\r\n mfButton\r\n tone=\"neutral\"\r\n appearance=\"text\"\r\n size=\"icon\"\r\n shape=\"square\"\r\n type=\"button\"\r\n [mfTooltip]=\"densityToggleTooltip()\"\r\n [attr.aria-label]=\"densityToggleTooltip()\"\r\n (click)=\"toggleDensity()\"\r\n >\r\n <span mfIcon [size]=\"18\">{{\r\n density() === 'comfortable' ? 'density_medium' : 'density_small'\r\n }}</span>\r\n </button>\r\n </div>\r\n\r\n <ng-content select=\"[tableActions]\" />\r\n </div>\r\n </div>\r\n\r\n <!-- Sat\u0131r 2 \u2014 ya filtre \u00E7ubu\u011Fu ya se\u00E7im \u00E7ubu\u011Fu, ASLA ikisi birden. -->\r\n @if (selectedCount() > 0) {\r\n <div\r\n class=\"flex flex-wrap items-center justify-between gap-3 border-b border-border bg-surface-sunken px-4 py-2\"\r\n >\r\n <div class=\"flex items-center gap-3\">\r\n <button\r\n type=\"button\"\r\n class=\"appearance-none border-0 bg-transparent p-0 text-fg-subtle transition-colors duration-fast ease-emphasis hover:text-fg\"\r\n [attr.aria-label]=\"effectiveLabels().clearSelection\"\r\n (click)=\"clearSelection()\"\r\n >\r\n <span mfIcon [size]=\"18\">close</span>\r\n </button>\r\n <span class=\"text-sm font-medium text-fg-muted tabular-nums\"\r\n >{{ selectedCount() }} {{ effectiveLabels().selectedCount }}</span\r\n >\r\n </div>\r\n <div class=\"flex items-center gap-2\">\r\n <ng-content select=\"[tableBulkActions]\" />\r\n @if (activeFilterCount() > 0) {\r\n <span\r\n class=\"inline-flex items-center gap-1 rounded-full bg-surface-alt px-2 py-0.5 text-xs text-fg-subtle\"\r\n >\r\n <span mfIcon [size]=\"12\">filter_list</span>\r\n {{ activeFilterCount() }} {{ effectiveLabels().filterCount }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n } @else if (hasFilterableColumns()) {\r\n <mf-table-filter-bar\r\n [columns]=\"columns()\"\r\n [filters]=\"appliedFilters()\"\r\n [logic]=\"filterLogic()\"\r\n [labels]=\"effectiveLabels().filter\"\r\n (filtersChange)=\"onFiltersChange($event)\"\r\n (logicChange)=\"onFilterLogicChange($event)\"\r\n />\r\n }\r\n\r\n <div\r\n #scroller\r\n [class]=\"scrollWrapperClasses()\"\r\n [style.max-height]=\"virtual() ? virtualHeight() : null\"\r\n (scroll)=\"onScroll($event)\"\r\n >\r\n <table\r\n [class]=\"cn('border-collapse text-sm', fixedLayout() ? 'min-w-full table-fixed' : 'w-full')\"\r\n [style.width.px]=\"tableWidth()\"\r\n [attr.aria-rowcount]=\"virtual() ? rows().length + 1 : null\"\r\n >\r\n <thead\r\n [class]=\"\r\n cn(\r\n 'bg-surface-sunken',\r\n isStickyHeader() ? 'sticky top-0 z-10 border-b border-border-strong' : ''\r\n )\r\n \"\r\n >\r\n <tr class=\"border-b border-hairline\" [attr.aria-rowindex]=\"virtual() ? 1 : null\">\r\n @if (selectable()) {\r\n <th\r\n data-pin=\"__select\"\r\n [class]=\"cn('w-10 px-4', headerCellPad(), pinHeadClasses('__select'))\"\r\n [style.inset-inline-start.px]=\"pinStart('__select')\"\r\n >\r\n <input\r\n type=\"checkbox\"\r\n mfCheckbox\r\n [checked]=\"allChecked()\"\r\n [indeterminate]=\"indeterminate()\"\r\n [attr.aria-label]=\"effectiveLabels().selectAllAriaLabel\"\r\n (change)=\"onCheckAll($any($event.target).checked)\"\r\n />\r\n </th>\r\n }\r\n @if (expandable()) {\r\n <th\r\n data-pin=\"__expand\"\r\n [class]=\"cn('w-10 px-1.5', headerCellPad(), pinHeadClasses('__expand'))\"\r\n [style.inset-inline-start.px]=\"pinStart('__expand')\"\r\n >\r\n <span class=\"sr-only\">{{ effectiveLabels().expandRow }}</span>\r\n </th>\r\n }\r\n @for (col of visibleColumns(); track col.field) {\r\n <th\r\n [attr.data-col]=\"col.field\"\r\n [attr.data-pin]=\"col.pinned === 'left' ? col.field : null\"\r\n [class]=\"\r\n cn(\r\n 'group/th relative truncate px-4 text-eyebrow text-fg-subtle',\r\n headerCellPad(),\r\n alignClass(resolvedAlign(col)),\r\n pinHeadClasses(col.field)\r\n )\r\n \"\r\n [style.width]=\"widthStyle(col)\"\r\n [style.min-width.px]=\"col.minWidth ?? null\"\r\n [style.inset-inline-start.px]=\"pinStart(col.field)\"\r\n >\r\n @if (col.sortable) {\r\n <button\r\n type=\"button\"\r\n class=\"inline-flex appearance-none items-center gap-1 border-0 bg-transparent p-0 transition-colors duration-fast ease-emphasis hover:text-fg\"\r\n (click)=\"onSortClick(col, $event)\"\r\n >\r\n <span>{{ col.header }}</span>\r\n @if (sortOrderFor(col.field); as order) {\r\n <span class=\"text-caption font-bold text-accent tabular-nums\">{{ order }}</span>\r\n }\r\n <span\r\n mfIcon\r\n [size]=\"14\"\r\n [class]=\"\r\n sortDirectionFor(col.field) !== null\r\n ? 'text-accent'\r\n : 'text-fg-subtle opacity-0 transition-opacity duration-fast group-hover/th:opacity-100'\r\n \"\r\n >\r\n @switch (sortDirectionFor(col.field)) {\r\n @case (0) {\r\n arrow_upward\r\n }\r\n @case (1) {\r\n arrow_downward\r\n }\r\n @default {\r\n unfold_more\r\n }\r\n }\r\n </span>\r\n </button>\r\n } @else {\r\n <span>{{ col.header }}</span>\r\n }\r\n @if (isResizable(col)) {\r\n <span\r\n mfTableResizeHandle\r\n [label]=\"resizeAriaLabel(col)\"\r\n [width]=\"currentWidth(col)\"\r\n [min]=\"col.minWidth ?? 80\"\r\n (resizeStart)=\"freezeWidths()\"\r\n (widthChange)=\"onColumnResize(col, $event)\"\r\n (autofit)=\"autofitColumn(col)\"\r\n ></span>\r\n }\r\n </th>\r\n }\r\n @if (hasRowActions()) {\r\n <th [class]=\"cn('sticky end-0 z-[1] w-20 bg-surface-sunken px-4', headerCellPad())\">\r\n <span class=\"sr-only\">{{ effectiveLabels().actionsColumnLabel }}</span>\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @if (showSkeleton()) {\r\n @for (i of skeletonIndexes(); track i) {\r\n <tr class=\"border-b border-hairline\">\r\n @if (selectable()) {\r\n <td [class]=\"cn('px-4', bodyCellPad())\"><div mfSkeleton class=\"h-4 w-4\"></div></td>\r\n }\r\n @if (expandable()) {\r\n <td [class]=\"cn('px-1.5', bodyCellPad())\"></td>\r\n }\r\n @for (col of visibleColumns(); track col.field) {\r\n <td [class]=\"cn('px-4', bodyCellPad())\">\r\n <div mfSkeleton class=\"h-4 w-full max-w-[10rem]\"></div>\r\n </td>\r\n }\r\n @if (hasRowActions()) {\r\n <td [class]=\"cn('px-4', bodyCellPad())\"></td>\r\n }\r\n </tr>\r\n }\r\n } @else if (rows().length === 0) {\r\n <tr>\r\n <td [attr.colspan]=\"columnSpan()\" class=\"px-4 py-6\">\r\n <mf-empty-state\r\n class=\"block\"\r\n [title]=\"\r\n isFiltered() ? effectiveLabels().noResultsTitle : effectiveLabels().emptyTitle\r\n \"\r\n [description]=\"\r\n isFiltered()\r\n ? effectiveLabels().noResultsDescription\r\n : effectiveLabels().emptyDescription\r\n \"\r\n >\r\n <span icon mfIcon [size]=\"28\">{{ isFiltered() ? 'search_off' : 'inbox' }}</span>\r\n @if (isFiltered()) {\r\n <button\r\n action\r\n mfButton\r\n appearance=\"text\"\r\n tone=\"primary\"\r\n type=\"button\"\r\n (click)=\"clearAllFilters()\"\r\n >\r\n {{ effectiveLabels().clearFilters }}\r\n </button>\r\n }\r\n </mf-empty-state>\r\n </td>\r\n </tr>\r\n } @else {\r\n @if (virtualRange(); as range) {\r\n @if (range.top > 0) {\r\n <tr aria-hidden=\"true\">\r\n <td\r\n class=\"border-0 p-0\"\r\n [attr.colspan]=\"columnSpan()\"\r\n [style.height.px]=\"range.top\"\r\n ></td>\r\n </tr>\r\n }\r\n }\r\n @for (row of displayRows(); track trackByRow($index, row)) {\r\n <tr\r\n data-row\r\n [class]=\"rowClasses(row)\"\r\n [attr.aria-rowindex]=\"virtual() ? rowIndexOffset() + $index + 2 : null\"\r\n (dblclick)=\"rowDblClick.emit(row)\"\r\n >\r\n @if (selectable()) {\r\n <td\r\n [class]=\"cn('px-4', bodyCellPad(), pinBodyClasses('__select', row))\"\r\n [style.inset-inline-start.px]=\"pinStart('__select')\"\r\n >\r\n <input\r\n type=\"checkbox\"\r\n mfCheckbox\r\n [checked]=\"isChecked(row)\"\r\n [attr.aria-label]=\"rowSelectAriaLabel(row)\"\r\n (change)=\"onCheckRow(row, $any($event.target).checked)\"\r\n />\r\n </td>\r\n }\r\n @if (expandable()) {\r\n <td\r\n [class]=\"cn('px-1.5', bodyCellPad(), pinBodyClasses('__expand', row))\"\r\n [style.inset-inline-start.px]=\"pinStart('__expand')\"\r\n >\r\n <button\r\n type=\"button\"\r\n class=\"inline-flex size-7 appearance-none items-center justify-center rounded-md border-0 bg-transparent p-0 text-fg-subtle transition-colors duration-fast ease-emphasis hover:bg-state-hover hover:text-fg focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring\"\r\n [attr.aria-expanded]=\"isExpanded(row)\"\r\n [attr.aria-controls]=\"isExpanded(row) ? expandedRowId($index) : null\"\r\n [attr.aria-label]=\"expandAriaLabel(row)\"\r\n (click)=\"toggleExpanded(row)\"\r\n >\r\n <span\r\n mfIcon\r\n [size]=\"18\"\r\n class=\"transition-transform duration-base ease-emphasis rtl:-scale-x-100\"\r\n [class.rotate-90]=\"isExpanded(row)\"\r\n [class.rtl:-rotate-90]=\"isExpanded(row)\"\r\n >chevron_right</span\r\n >\r\n </button>\r\n </td>\r\n }\r\n @for (col of visibleColumns(); track col.field) {\r\n <td\r\n [attr.data-col]=\"col.field\"\r\n [class]=\"\r\n cn(\r\n 'max-w-0 px-4',\r\n bodyCellPad(),\r\n cellClasses(col),\r\n alignClass(resolvedAlign(col)),\r\n pinBodyClasses(col.field, row)\r\n )\r\n \"\r\n [style.inset-inline-start.px]=\"pinStart(col.field)\"\r\n [title]=\"!col.cellTemplate ? cellText(row, col) : null\"\r\n >\r\n @if (col.cellTemplate) {\r\n <ng-container\r\n *ngTemplateOutlet=\"col.cellTemplate; context: { $implicit: row }\"\r\n />\r\n } @else {\r\n <span>{{ row[col.field] }}</span>\r\n }\r\n </td>\r\n }\r\n @if (hasRowActions()) {\r\n <td [class]=\"stickyActionClasses(row)\">\r\n <div\r\n class=\"flex items-center justify-end gap-1 opacity-100 [@media(hover:hover)]:opacity-0 [@media(hover:hover)]:group-hover/row:opacity-100 group-focus-within/row:opacity-100\"\r\n >\r\n @if (rowActions().edit) {\r\n <!--\r\n `tone=\"info\"` (mavi) B\u0130LEREK `neutral` DE\u011E\u0130L \u2014 sat\u0131r\r\n hover'\u0131 zaten `bg-surface-alt` boyuyor (bkz. rowBg()),\r\n `neutral`'\u0131n kendi `hover:bg-surface-alt`'\u0131 AYNI renk\r\n oldu\u011Fundan buton \u00FCzerine gelince g\u00F6rsel hi\u00E7bir \u015Fey\r\n de\u011Fi\u015Fmiyordu (ger\u00E7ek kullan\u0131c\u0131 bulgusu). Delete'in\r\n `danger` (k\u0131rm\u0131z\u0131 ton) hover'\u0131 bu sorunu ya\u015Fam\u0131yordu \u2014\r\n ayn\u0131 mant\u0131k: Edit'e de arka planla \u00E7ak\u0131\u015Fmayan kendi\r\n renk tonu (mavi `info/5`) verildi.\r\n -->\r\n <button\r\n mfButton\r\n tone=\"info\"\r\n appearance=\"text\"\r\n size=\"icon\"\r\n shape=\"square\"\r\n type=\"button\"\r\n [attr.aria-label]=\"effectiveLabels().edit\"\r\n [mfTooltip]=\"effectiveLabels().edit\"\r\n (click)=\"edit.emit(row)\"\r\n >\r\n <span mfIcon [size]=\"18\">edit</span>\r\n </button>\r\n }\r\n @if (rowActions().delete) {\r\n <button\r\n mfButton\r\n tone=\"danger\"\r\n appearance=\"text\"\r\n size=\"icon\"\r\n shape=\"square\"\r\n type=\"button\"\r\n [attr.aria-label]=\"effectiveLabels().delete\"\r\n [mfTooltip]=\"effectiveLabels().delete\"\r\n (click)=\"delete.emit(row)\"\r\n >\r\n <span mfIcon [size]=\"18\">delete</span>\r\n </button>\r\n }\r\n </div>\r\n </td>\r\n }\r\n </tr>\r\n @if (expandable() && isExpanded(row)) {\r\n <tr [id]=\"expandedRowId($index)\" class=\"border-b border-b-hairline bg-surface-sunken\">\r\n <td [attr.colspan]=\"columnSpan()\" class=\"p-0\">\r\n <!-- Yatay kayd\u0131rmada ayr\u0131nt\u0131 g\u00F6r\u00FCn\u00FCr alanda sabit kal\u0131r (tablo geni\u015Fli\u011Fiyle kaymaz). -->\r\n <div\r\n class=\"sticky start-0 box-border px-4 py-3\"\r\n [style.width.px]=\"viewportWidth() || null\"\r\n >\r\n <ng-container\r\n *ngTemplateOutlet=\"expandedRowTemplate()!; context: { $implicit: row }\"\r\n />\r\n </div>\r\n </td>\r\n </tr>\r\n }\r\n }\r\n @if (virtualRange(); as range) {\r\n @if (range.bottom > 0) {\r\n <tr aria-hidden=\"true\">\r\n <td\r\n class=\"border-0 p-0\"\r\n [attr.colspan]=\"columnSpan()\"\r\n [style.height.px]=\"range.bottom\"\r\n ></td>\r\n </tr>\r\n }\r\n }\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n <div class=\"border-t border-border bg-surface-sunken\">\r\n @if (virtual()) {\r\n <p class=\"px-4 py-2.5 text-sm text-fg-muted tabular-nums\">\r\n {{ rowsTotal() }} {{ effectiveLabels().pagination.records }}\r\n </p>\r\n } @else {\r\n <mf-table-pagination\r\n [page]=\"pageState().pageNumber\"\r\n [pageSize]=\"pageState().pageSize\"\r\n [total]=\"rowsTotal()\"\r\n [pageSizeOptions]=\"pageSizeOptions()\"\r\n [labels]=\"effectiveLabels().pagination\"\r\n (pageChange)=\"onPageChange($event)\"\r\n (pageSizeChange)=\"onPageSizeChange($event)\"\r\n />\r\n }\r\n </div>\r\n</div>\r\n", dependencies: [{ kind: "component", type: Button, selector: "button[mfButton], a[mfButton]", inputs: ["tone", "appearance", "size", "shape", "type", "loading", "iconStart", "iconEnd"], exportAs: ["mfButton"] }, { kind: "directive", type: Icon, selector: "span[mfIcon]", inputs: ["size", "filled"] }, { kind: "directive", type: Checkbox, selector: "input[type=checkbox][mfCheckbox]", inputs: ["size"] }, { kind: "component", type: Skeleton, selector: "[mfSkeleton]", inputs: ["variant", "lines"] }, { kind: "component", type: EmptyState, selector: "mf-empty-state", inputs: ["title", "description"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: TableFilterBar, selector: "mf-table-filter-bar", inputs: ["columns", "filters", "logic", "labels"], outputs: ["filtersChange", "logicChange"] }, { kind: "component", type: TableColumnsMenu, selector: "mf-table-columns-menu", inputs: ["columns", "hidden", "origin", "open", "label"], outputs: ["hiddenChange", "closed"] }, { kind: "component", type: TablePagination, selector: "mf-table-pagination", inputs: ["page", "pageSize", "total", "pageSizeOptions", "labels"], outputs: ["pageChange", "pageSizeChange"] }, { kind: "directive", type: TableResizeHandle, selector: "[mfTableResizeHandle]", inputs: ["label", "width", "min", "max"], outputs: ["resizeStart", "widthChange", "autofit"] }, { kind: "component", type: SearchInput, selector: "mf-search-input", inputs: ["value", "placeholder", "ariaLabel", "tooltipLabel", "loading", "debounceMs", "collapsible", "fullWidth"], outputs: ["valueChange"], exportAs: ["mfSearchInput"] }, { kind: "directive", type: Tooltip, selector: "[mfTooltip], [mfTooltipContent]", inputs: ["mfTooltip", "mfTooltipContent", "tooltipPosition"], exportAs: ["mfTooltip"] }] });
1758
+ }
1759
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: Table, decorators: [{
1760
+ type: Component,
1761
+ args: [{ selector: 'mf-table', exportAs: 'mfTable', imports: [
1762
+ Button,
1763
+ Icon,
1764
+ Checkbox,
1765
+ Skeleton,
1766
+ EmptyState,
1767
+ NgTemplateOutlet,
1768
+ OverlayModule,
1769
+ TableFilterBar,
1770
+ TableColumnsMenu,
1771
+ TablePagination,
1772
+ TableResizeHandle,
1773
+ SearchInput,
1774
+ Tooltip,
1775
+ ], host: { class: 'block' }, template: "<div class=\"relative overflow-hidden rounded-lg border border-hairline bg-surface\">\r\n @if (showStaleOverlay()) {\r\n <div\r\n class=\"pointer-events-none absolute inset-x-0 top-0 z-20 h-0.5 overflow-hidden bg-primary-tint\"\r\n aria-hidden=\"true\"\r\n >\r\n <div class=\"h-full w-1/4 animate-progress-slide bg-accent-fill\"></div>\r\n </div>\r\n }\r\n\r\n <!--\r\n Sat\u0131r 1 \u2014 toolbar. Se\u00E7im modunda da AYNI kal\u0131r: eskiden bir sat\u0131r\r\n se\u00E7ilince ba\u015Fl\u0131k+arama+filtre tamamen kaybolup yerine \"N se\u00E7ildi\"\r\n geliyordu (ger\u00E7ek bug, kullan\u0131c\u0131 arama/filtre ba\u011Flam\u0131n\u0131 kaybediyordu).\r\n Art\u0131k sadece Sat\u0131r 2 (a\u015Fa\u011F\u0131da) se\u00E7im aksiyonlar\u0131na devrediliyor.\r\n -->\r\n <div class=\"flex flex-wrap items-center justify-between gap-3 border-b border-border px-4 py-3\">\r\n <div class=\"flex min-w-0 items-center gap-2.5\">\r\n @if (title()) {\r\n <h3 class=\"truncate font-display text-title text-fg\">{{ title() }}</h3>\r\n }\r\n </div>\r\n\r\n <div class=\"flex flex-1 flex-wrap items-center justify-end gap-2\">\r\n @if (searchable()) {\r\n <mf-search-input\r\n [collapsible]=\"false\"\r\n [value]=\"searchTerm()\"\r\n [placeholder]=\"effectiveLabels().searchPlaceholder\"\r\n [ariaLabel]=\"effectiveLabels().searchPlaceholder\"\r\n [loading]=\"loading()\"\r\n (valueChange)=\"onSearchCommit($event)\"\r\n />\r\n }\r\n\r\n <!-- Kolonlar + Yo\u011Funluk: B\u0130LEREK hayalet (text/icon) kal\u0131yor \u2014 g\u00F6z [tableActions]'daki ger\u00E7ek CTA'ya gitsin diye (bkz. table.ts s\u0131n\u0131f yorumu, toolbar hiyerar\u015Fisi bulgusu). -->\r\n <div class=\"flex items-center gap-0.5\">\r\n <button\r\n mfButton\r\n tone=\"neutral\"\r\n appearance=\"text\"\r\n size=\"icon\"\r\n shape=\"square\"\r\n type=\"button\"\r\n cdkOverlayOrigin\r\n #columnsOrigin=\"cdkOverlayOrigin\"\r\n [mfTooltip]=\"effectiveLabels().columns\"\r\n [attr.aria-label]=\"effectiveLabels().columns\"\r\n (click)=\"columnsMenuOpen.set(!columnsMenuOpen())\"\r\n >\r\n <span mfIcon [size]=\"18\">view_column</span>\r\n </button>\r\n <mf-table-columns-menu\r\n [columns]=\"columns()\"\r\n [hidden]=\"hiddenColumns()\"\r\n [origin]=\"columnsOrigin\"\r\n [open]=\"columnsMenuOpen()\"\r\n [label]=\"effectiveLabels().columns\"\r\n (hiddenChange)=\"onHiddenColumnsChange($event)\"\r\n (closed)=\"columnsMenuOpen.set(false)\"\r\n />\r\n\r\n <button\r\n mfButton\r\n tone=\"neutral\"\r\n appearance=\"text\"\r\n size=\"icon\"\r\n shape=\"square\"\r\n type=\"button\"\r\n [mfTooltip]=\"densityToggleTooltip()\"\r\n [attr.aria-label]=\"densityToggleTooltip()\"\r\n (click)=\"toggleDensity()\"\r\n >\r\n <span mfIcon [size]=\"18\">{{\r\n density() === 'comfortable' ? 'density_medium' : 'density_small'\r\n }}</span>\r\n </button>\r\n </div>\r\n\r\n <ng-content select=\"[tableActions]\" />\r\n </div>\r\n </div>\r\n\r\n <!-- Sat\u0131r 2 \u2014 ya filtre \u00E7ubu\u011Fu ya se\u00E7im \u00E7ubu\u011Fu, ASLA ikisi birden. -->\r\n @if (selectedCount() > 0) {\r\n <div\r\n class=\"flex flex-wrap items-center justify-between gap-3 border-b border-border bg-surface-sunken px-4 py-2\"\r\n >\r\n <div class=\"flex items-center gap-3\">\r\n <button\r\n type=\"button\"\r\n class=\"appearance-none border-0 bg-transparent p-0 text-fg-subtle transition-colors duration-fast ease-emphasis hover:text-fg\"\r\n [attr.aria-label]=\"effectiveLabels().clearSelection\"\r\n (click)=\"clearSelection()\"\r\n >\r\n <span mfIcon [size]=\"18\">close</span>\r\n </button>\r\n <span class=\"text-sm font-medium text-fg-muted tabular-nums\"\r\n >{{ selectedCount() }} {{ effectiveLabels().selectedCount }}</span\r\n >\r\n </div>\r\n <div class=\"flex items-center gap-2\">\r\n <ng-content select=\"[tableBulkActions]\" />\r\n @if (activeFilterCount() > 0) {\r\n <span\r\n class=\"inline-flex items-center gap-1 rounded-full bg-surface-alt px-2 py-0.5 text-xs text-fg-subtle\"\r\n >\r\n <span mfIcon [size]=\"12\">filter_list</span>\r\n {{ activeFilterCount() }} {{ effectiveLabels().filterCount }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n } @else if (hasFilterableColumns()) {\r\n <mf-table-filter-bar\r\n [columns]=\"columns()\"\r\n [filters]=\"appliedFilters()\"\r\n [logic]=\"filterLogic()\"\r\n [labels]=\"effectiveLabels().filter\"\r\n (filtersChange)=\"onFiltersChange($event)\"\r\n (logicChange)=\"onFilterLogicChange($event)\"\r\n />\r\n }\r\n\r\n <div\r\n #scroller\r\n [class]=\"scrollWrapperClasses()\"\r\n [style.max-height]=\"virtual() ? virtualHeight() : null\"\r\n (scroll)=\"onScroll($event)\"\r\n >\r\n <table\r\n [class]=\"cn('border-collapse text-sm', fixedLayout() ? 'min-w-full table-fixed' : 'w-full')\"\r\n [style.width.px]=\"tableWidth()\"\r\n [attr.aria-rowcount]=\"virtual() ? rows().length + 1 : null\"\r\n >\r\n <thead\r\n [class]=\"\r\n cn(\r\n 'bg-surface-sunken',\r\n isStickyHeader() ? 'sticky top-0 z-10 border-b border-border-strong' : ''\r\n )\r\n \"\r\n >\r\n <tr class=\"border-b border-hairline\" [attr.aria-rowindex]=\"virtual() ? 1 : null\">\r\n @if (selectable()) {\r\n <th\r\n data-pin=\"__select\"\r\n [class]=\"cn('w-10 px-4', headerCellPad(), pinHeadClasses('__select'))\"\r\n [style.inset-inline-start.px]=\"pinStart('__select')\"\r\n >\r\n <input\r\n type=\"checkbox\"\r\n mfCheckbox\r\n [checked]=\"allChecked()\"\r\n [indeterminate]=\"indeterminate()\"\r\n [attr.aria-label]=\"effectiveLabels().selectAllAriaLabel\"\r\n (change)=\"onCheckAll($any($event.target).checked)\"\r\n />\r\n </th>\r\n }\r\n @if (expandable()) {\r\n <th\r\n data-pin=\"__expand\"\r\n [class]=\"cn('w-10 px-1.5', headerCellPad(), pinHeadClasses('__expand'))\"\r\n [style.inset-inline-start.px]=\"pinStart('__expand')\"\r\n >\r\n <span class=\"sr-only\">{{ effectiveLabels().expandRow }}</span>\r\n </th>\r\n }\r\n @for (col of visibleColumns(); track col.field) {\r\n <th\r\n [attr.data-col]=\"col.field\"\r\n [attr.data-pin]=\"col.pinned === 'left' ? col.field : null\"\r\n [class]=\"\r\n cn(\r\n 'group/th relative truncate px-4 text-eyebrow text-fg-subtle',\r\n headerCellPad(),\r\n alignClass(resolvedAlign(col)),\r\n pinHeadClasses(col.field)\r\n )\r\n \"\r\n [style.width]=\"widthStyle(col)\"\r\n [style.min-width.px]=\"col.minWidth ?? null\"\r\n [style.inset-inline-start.px]=\"pinStart(col.field)\"\r\n >\r\n @if (col.sortable) {\r\n <button\r\n type=\"button\"\r\n class=\"inline-flex appearance-none items-center gap-1 border-0 bg-transparent p-0 transition-colors duration-fast ease-emphasis hover:text-fg\"\r\n (click)=\"onSortClick(col, $event)\"\r\n >\r\n <span>{{ col.header }}</span>\r\n @if (sortOrderFor(col.field); as order) {\r\n <span class=\"text-caption font-bold text-accent tabular-nums\">{{ order }}</span>\r\n }\r\n <span\r\n mfIcon\r\n [size]=\"14\"\r\n [class]=\"\r\n sortDirectionFor(col.field) !== null\r\n ? 'text-accent'\r\n : 'text-fg-subtle opacity-0 transition-opacity duration-fast group-hover/th:opacity-100'\r\n \"\r\n >\r\n @switch (sortDirectionFor(col.field)) {\r\n @case (0) {\r\n arrow_upward\r\n }\r\n @case (1) {\r\n arrow_downward\r\n }\r\n @default {\r\n unfold_more\r\n }\r\n }\r\n </span>\r\n </button>\r\n } @else {\r\n <span>{{ col.header }}</span>\r\n }\r\n @if (isResizable(col)) {\r\n <span\r\n mfTableResizeHandle\r\n [label]=\"resizeAriaLabel(col)\"\r\n [width]=\"currentWidth(col)\"\r\n [min]=\"col.minWidth ?? 80\"\r\n (resizeStart)=\"freezeWidths()\"\r\n (widthChange)=\"onColumnResize(col, $event)\"\r\n (autofit)=\"autofitColumn(col)\"\r\n ></span>\r\n }\r\n </th>\r\n }\r\n @if (hasRowActions()) {\r\n <th [class]=\"cn('sticky end-0 z-[1] w-20 bg-surface-sunken px-4', headerCellPad())\">\r\n <span class=\"sr-only\">{{ effectiveLabels().actionsColumnLabel }}</span>\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @if (showSkeleton()) {\r\n @for (i of skeletonIndexes(); track i) {\r\n <tr class=\"border-b border-hairline\">\r\n @if (selectable()) {\r\n <td [class]=\"cn('px-4', bodyCellPad())\"><div mfSkeleton class=\"h-4 w-4\"></div></td>\r\n }\r\n @if (expandable()) {\r\n <td [class]=\"cn('px-1.5', bodyCellPad())\"></td>\r\n }\r\n @for (col of visibleColumns(); track col.field) {\r\n <td [class]=\"cn('px-4', bodyCellPad())\">\r\n <div mfSkeleton class=\"h-4 w-full max-w-[10rem]\"></div>\r\n </td>\r\n }\r\n @if (hasRowActions()) {\r\n <td [class]=\"cn('px-4', bodyCellPad())\"></td>\r\n }\r\n </tr>\r\n }\r\n } @else if (rows().length === 0) {\r\n <tr>\r\n <td [attr.colspan]=\"columnSpan()\" class=\"px-4 py-6\">\r\n <mf-empty-state\r\n class=\"block\"\r\n [title]=\"\r\n isFiltered() ? effectiveLabels().noResultsTitle : effectiveLabels().emptyTitle\r\n \"\r\n [description]=\"\r\n isFiltered()\r\n ? effectiveLabels().noResultsDescription\r\n : effectiveLabels().emptyDescription\r\n \"\r\n >\r\n <span icon mfIcon [size]=\"28\">{{ isFiltered() ? 'search_off' : 'inbox' }}</span>\r\n @if (isFiltered()) {\r\n <button\r\n action\r\n mfButton\r\n appearance=\"text\"\r\n tone=\"primary\"\r\n type=\"button\"\r\n (click)=\"clearAllFilters()\"\r\n >\r\n {{ effectiveLabels().clearFilters }}\r\n </button>\r\n }\r\n </mf-empty-state>\r\n </td>\r\n </tr>\r\n } @else {\r\n @if (virtualRange(); as range) {\r\n @if (range.top > 0) {\r\n <tr aria-hidden=\"true\">\r\n <td\r\n class=\"border-0 p-0\"\r\n [attr.colspan]=\"columnSpan()\"\r\n [style.height.px]=\"range.top\"\r\n ></td>\r\n </tr>\r\n }\r\n }\r\n @for (row of displayRows(); track trackByRow($index, row)) {\r\n <tr\r\n data-row\r\n [class]=\"rowClasses(row)\"\r\n [attr.aria-rowindex]=\"virtual() ? rowIndexOffset() + $index + 2 : null\"\r\n (dblclick)=\"rowDblClick.emit(row)\"\r\n >\r\n @if (selectable()) {\r\n <td\r\n [class]=\"cn('px-4', bodyCellPad(), pinBodyClasses('__select', row))\"\r\n [style.inset-inline-start.px]=\"pinStart('__select')\"\r\n >\r\n <input\r\n type=\"checkbox\"\r\n mfCheckbox\r\n [checked]=\"isChecked(row)\"\r\n [attr.aria-label]=\"rowSelectAriaLabel(row)\"\r\n (change)=\"onCheckRow(row, $any($event.target).checked)\"\r\n />\r\n </td>\r\n }\r\n @if (expandable()) {\r\n <td\r\n [class]=\"cn('px-1.5', bodyCellPad(), pinBodyClasses('__expand', row))\"\r\n [style.inset-inline-start.px]=\"pinStart('__expand')\"\r\n >\r\n <button\r\n type=\"button\"\r\n class=\"inline-flex size-7 appearance-none items-center justify-center rounded-md border-0 bg-transparent p-0 text-fg-subtle transition-colors duration-fast ease-emphasis hover:bg-state-hover hover:text-fg focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring\"\r\n [attr.aria-expanded]=\"isExpanded(row)\"\r\n [attr.aria-controls]=\"isExpanded(row) ? expandedRowId($index) : null\"\r\n [attr.aria-label]=\"expandAriaLabel(row)\"\r\n (click)=\"toggleExpanded(row)\"\r\n >\r\n <span\r\n mfIcon\r\n [size]=\"18\"\r\n class=\"transition-transform duration-base ease-emphasis rtl:-scale-x-100\"\r\n [class.rotate-90]=\"isExpanded(row)\"\r\n [class.rtl:-rotate-90]=\"isExpanded(row)\"\r\n >chevron_right</span\r\n >\r\n </button>\r\n </td>\r\n }\r\n @for (col of visibleColumns(); track col.field) {\r\n <td\r\n [attr.data-col]=\"col.field\"\r\n [class]=\"\r\n cn(\r\n 'max-w-0 px-4',\r\n bodyCellPad(),\r\n cellClasses(col),\r\n alignClass(resolvedAlign(col)),\r\n pinBodyClasses(col.field, row)\r\n )\r\n \"\r\n [style.inset-inline-start.px]=\"pinStart(col.field)\"\r\n [title]=\"!col.cellTemplate ? cellText(row, col) : null\"\r\n >\r\n @if (col.cellTemplate) {\r\n <ng-container\r\n *ngTemplateOutlet=\"col.cellTemplate; context: { $implicit: row }\"\r\n />\r\n } @else {\r\n <span>{{ row[col.field] }}</span>\r\n }\r\n </td>\r\n }\r\n @if (hasRowActions()) {\r\n <td [class]=\"stickyActionClasses(row)\">\r\n <div\r\n class=\"flex items-center justify-end gap-1 opacity-100 [@media(hover:hover)]:opacity-0 [@media(hover:hover)]:group-hover/row:opacity-100 group-focus-within/row:opacity-100\"\r\n >\r\n @if (rowActions().edit) {\r\n <!--\r\n `tone=\"info\"` (mavi) B\u0130LEREK `neutral` DE\u011E\u0130L \u2014 sat\u0131r\r\n hover'\u0131 zaten `bg-surface-alt` boyuyor (bkz. rowBg()),\r\n `neutral`'\u0131n kendi `hover:bg-surface-alt`'\u0131 AYNI renk\r\n oldu\u011Fundan buton \u00FCzerine gelince g\u00F6rsel hi\u00E7bir \u015Fey\r\n de\u011Fi\u015Fmiyordu (ger\u00E7ek kullan\u0131c\u0131 bulgusu). Delete'in\r\n `danger` (k\u0131rm\u0131z\u0131 ton) hover'\u0131 bu sorunu ya\u015Fam\u0131yordu \u2014\r\n ayn\u0131 mant\u0131k: Edit'e de arka planla \u00E7ak\u0131\u015Fmayan kendi\r\n renk tonu (mavi `info/5`) verildi.\r\n -->\r\n <button\r\n mfButton\r\n tone=\"info\"\r\n appearance=\"text\"\r\n size=\"icon\"\r\n shape=\"square\"\r\n type=\"button\"\r\n [attr.aria-label]=\"effectiveLabels().edit\"\r\n [mfTooltip]=\"effectiveLabels().edit\"\r\n (click)=\"edit.emit(row)\"\r\n >\r\n <span mfIcon [size]=\"18\">edit</span>\r\n </button>\r\n }\r\n @if (rowActions().delete) {\r\n <button\r\n mfButton\r\n tone=\"danger\"\r\n appearance=\"text\"\r\n size=\"icon\"\r\n shape=\"square\"\r\n type=\"button\"\r\n [attr.aria-label]=\"effectiveLabels().delete\"\r\n [mfTooltip]=\"effectiveLabels().delete\"\r\n (click)=\"delete.emit(row)\"\r\n >\r\n <span mfIcon [size]=\"18\">delete</span>\r\n </button>\r\n }\r\n </div>\r\n </td>\r\n }\r\n </tr>\r\n @if (expandable() && isExpanded(row)) {\r\n <tr [id]=\"expandedRowId($index)\" class=\"border-b border-b-hairline bg-surface-sunken\">\r\n <td [attr.colspan]=\"columnSpan()\" class=\"p-0\">\r\n <!-- Yatay kayd\u0131rmada ayr\u0131nt\u0131 g\u00F6r\u00FCn\u00FCr alanda sabit kal\u0131r (tablo geni\u015Fli\u011Fiyle kaymaz). -->\r\n <div\r\n class=\"sticky start-0 box-border px-4 py-3\"\r\n [style.width.px]=\"viewportWidth() || null\"\r\n >\r\n <ng-container\r\n *ngTemplateOutlet=\"expandedRowTemplate()!; context: { $implicit: row }\"\r\n />\r\n </div>\r\n </td>\r\n </tr>\r\n }\r\n }\r\n @if (virtualRange(); as range) {\r\n @if (range.bottom > 0) {\r\n <tr aria-hidden=\"true\">\r\n <td\r\n class=\"border-0 p-0\"\r\n [attr.colspan]=\"columnSpan()\"\r\n [style.height.px]=\"range.bottom\"\r\n ></td>\r\n </tr>\r\n }\r\n }\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n <div class=\"border-t border-border bg-surface-sunken\">\r\n @if (virtual()) {\r\n <p class=\"px-4 py-2.5 text-sm text-fg-muted tabular-nums\">\r\n {{ rowsTotal() }} {{ effectiveLabels().pagination.records }}\r\n </p>\r\n } @else {\r\n <mf-table-pagination\r\n [page]=\"pageState().pageNumber\"\r\n [pageSize]=\"pageState().pageSize\"\r\n [total]=\"rowsTotal()\"\r\n [pageSizeOptions]=\"pageSizeOptions()\"\r\n [labels]=\"effectiveLabels().pagination\"\r\n (pageChange)=\"onPageChange($event)\"\r\n (pageSizeChange)=\"onPageSizeChange($event)\"\r\n />\r\n }\r\n </div>\r\n</div>\r\n" }]
1776
+ }], ctorParameters: () => [], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }], rowKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowKey", required: true }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], total: [{ type: i0.Input, args: [{ isSignal: true, alias: "total", required: false }] }], pageSizeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizeOptions", required: false }] }], selectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectable", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], stickyHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "stickyHeader", required: false }] }], density: [{ type: i0.Input, args: [{ isSignal: true, alias: "density", required: false }] }, { type: i0.Output, args: ["densityChange"] }], rowActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowActions", required: false }] }], clientSide: [{ type: i0.Input, args: [{ isSignal: true, alias: "clientSide", required: false }] }], resizableColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizableColumns", required: false }] }], columnWidths: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnWidths", required: false }] }, { type: i0.Output, args: ["columnWidthsChange"] }], expandedRowTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedRowTemplate", required: false }] }], virtual: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtual", required: false }] }], virtualHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtualHeight", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], queryChange: [{ type: i0.Output, args: ["queryChange"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], edit: [{ type: i0.Output, args: ["edit"] }], delete: [{ type: i0.Output, args: ["delete"] }], rowDblClick: [{ type: i0.Output, args: ["rowDblClick"] }], rowExpandedChange: [{ type: i0.Output, args: ["rowExpandedChange"] }], scroller: [{ type: i0.ViewChild, args: ['scroller', { isSignal: true }] }] } });
1777
+
1778
+ /**
1779
+ * Generated bundle index. Do not edit.
1780
+ */
1781
+
1782
+ export { DEFAULT_PAGE_SIZE, FilterLogic, FilterOperator, MAX_PAGE_SIZE, OPERATORS_BY_TYPE, SortDirection, Table, TableColumnsMenu, TableFilterBar, TableFilterMenu, TablePageJump, TablePagination, TableResizeHandle, buildFilterGroup, canonicalPagedRequest, dateToIso, defaultOperatorFor, isDefaultOperator, isoToDate, localIso, matchesFilterGroup, operatorLabel, operatorNeedsValue, sortRows, virtualWindow };
1783
+ //# sourceMappingURL=mesgflow-ui-kit-table.mjs.map