forty-cdk 0.2.0 → 0.3.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 (220) hide show
  1. package/accordion/README.md +122 -0
  2. package/aspect-ratio/README.md +76 -0
  3. package/avatar/README.md +100 -0
  4. package/breadcrumbs/README.md +49 -0
  5. package/breakpoints/README.md +81 -0
  6. package/button/README.md +49 -0
  7. package/calendar/README.md +458 -0
  8. package/carousel/README.md +358 -0
  9. package/checkbox/README.md +146 -0
  10. package/combobox/README.md +535 -0
  11. package/context-menu/README.md +139 -0
  12. package/date-field/README.md +184 -0
  13. package/date-picker/README.md +338 -0
  14. package/dialog/README.md +388 -0
  15. package/disclosure/README.md +114 -0
  16. package/drag-drop/README.md +359 -0
  17. package/drawer/README.md +560 -0
  18. package/dropdown-menu/README.md +176 -0
  19. package/fesm2022/forty-cdk-accordion.mjs +348 -0
  20. package/fesm2022/forty-cdk-accordion.mjs.map +1 -0
  21. package/fesm2022/forty-cdk-aspect-ratio.mjs +74 -0
  22. package/fesm2022/forty-cdk-aspect-ratio.mjs.map +1 -0
  23. package/fesm2022/forty-cdk-avatar.mjs +308 -0
  24. package/fesm2022/forty-cdk-avatar.mjs.map +1 -0
  25. package/fesm2022/forty-cdk-breadcrumbs.mjs +125 -0
  26. package/fesm2022/forty-cdk-breadcrumbs.mjs.map +1 -0
  27. package/fesm2022/forty-cdk-breakpoints.mjs +117 -0
  28. package/fesm2022/forty-cdk-breakpoints.mjs.map +1 -0
  29. package/fesm2022/forty-cdk-button.mjs +134 -0
  30. package/fesm2022/forty-cdk-button.mjs.map +1 -0
  31. package/fesm2022/forty-cdk-calendar.mjs +2034 -0
  32. package/fesm2022/forty-cdk-calendar.mjs.map +1 -0
  33. package/fesm2022/forty-cdk-carousel.mjs +968 -0
  34. package/fesm2022/forty-cdk-carousel.mjs.map +1 -0
  35. package/fesm2022/forty-cdk-checkbox.mjs +226 -0
  36. package/fesm2022/forty-cdk-checkbox.mjs.map +1 -0
  37. package/fesm2022/forty-cdk-combobox.mjs +2596 -0
  38. package/fesm2022/forty-cdk-combobox.mjs.map +1 -0
  39. package/fesm2022/forty-cdk-context-menu.mjs +413 -0
  40. package/fesm2022/forty-cdk-context-menu.mjs.map +1 -0
  41. package/fesm2022/forty-cdk-core.mjs +9022 -0
  42. package/fesm2022/forty-cdk-core.mjs.map +1 -0
  43. package/fesm2022/forty-cdk-date-field.mjs +744 -0
  44. package/fesm2022/forty-cdk-date-field.mjs.map +1 -0
  45. package/fesm2022/forty-cdk-date-picker.mjs +1011 -0
  46. package/fesm2022/forty-cdk-date-picker.mjs.map +1 -0
  47. package/fesm2022/forty-cdk-dialog.mjs +707 -0
  48. package/fesm2022/forty-cdk-dialog.mjs.map +1 -0
  49. package/fesm2022/forty-cdk-disclosure.mjs +190 -0
  50. package/fesm2022/forty-cdk-disclosure.mjs.map +1 -0
  51. package/fesm2022/forty-cdk-drag-drop.mjs +1180 -0
  52. package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -0
  53. package/fesm2022/forty-cdk-drawer.mjs +1641 -0
  54. package/fesm2022/forty-cdk-drawer.mjs.map +1 -0
  55. package/fesm2022/forty-cdk-dropdown-menu.mjs +350 -0
  56. package/fesm2022/forty-cdk-dropdown-menu.mjs.map +1 -0
  57. package/fesm2022/forty-cdk-field.mjs +425 -0
  58. package/fesm2022/forty-cdk-field.mjs.map +1 -0
  59. package/fesm2022/forty-cdk-fieldset.mjs +164 -0
  60. package/fesm2022/forty-cdk-fieldset.mjs.map +1 -0
  61. package/fesm2022/forty-cdk-file-upload.mjs +221 -0
  62. package/fesm2022/forty-cdk-file-upload.mjs.map +1 -0
  63. package/fesm2022/forty-cdk-hover-card.mjs +496 -0
  64. package/fesm2022/forty-cdk-hover-card.mjs.map +1 -0
  65. package/fesm2022/forty-cdk-input.mjs +274 -0
  66. package/fesm2022/forty-cdk-input.mjs.map +1 -0
  67. package/fesm2022/forty-cdk-internationalized-date.mjs +1 -1
  68. package/fesm2022/forty-cdk-internationalized-date.mjs.map +1 -1
  69. package/fesm2022/forty-cdk-listbox.mjs +1279 -0
  70. package/fesm2022/forty-cdk-listbox.mjs.map +1 -0
  71. package/fesm2022/forty-cdk-menu.mjs +1439 -0
  72. package/fesm2022/forty-cdk-menu.mjs.map +1 -0
  73. package/fesm2022/forty-cdk-menubar.mjs +787 -0
  74. package/fesm2022/forty-cdk-menubar.mjs.map +1 -0
  75. package/fesm2022/forty-cdk-meter.mjs +211 -0
  76. package/fesm2022/forty-cdk-meter.mjs.map +1 -0
  77. package/fesm2022/forty-cdk-navigation-menu.mjs +1145 -0
  78. package/fesm2022/forty-cdk-navigation-menu.mjs.map +1 -0
  79. package/fesm2022/forty-cdk-number-input.mjs +559 -0
  80. package/fesm2022/forty-cdk-number-input.mjs.map +1 -0
  81. package/fesm2022/forty-cdk-otp-input.mjs +527 -0
  82. package/fesm2022/forty-cdk-otp-input.mjs.map +1 -0
  83. package/fesm2022/forty-cdk-pagination.mjs +323 -0
  84. package/fesm2022/forty-cdk-pagination.mjs.map +1 -0
  85. package/fesm2022/forty-cdk-pane-resizer.mjs +297 -0
  86. package/fesm2022/forty-cdk-pane-resizer.mjs.map +1 -0
  87. package/fesm2022/forty-cdk-popover.mjs +698 -0
  88. package/fesm2022/forty-cdk-popover.mjs.map +1 -0
  89. package/fesm2022/forty-cdk-progress.mjs +226 -0
  90. package/fesm2022/forty-cdk-progress.mjs.map +1 -0
  91. package/fesm2022/forty-cdk-radio-group.mjs +378 -0
  92. package/fesm2022/forty-cdk-radio-group.mjs.map +1 -0
  93. package/fesm2022/forty-cdk-scroll-area.mjs +640 -0
  94. package/fesm2022/forty-cdk-scroll-area.mjs.map +1 -0
  95. package/fesm2022/forty-cdk-search.mjs +205 -0
  96. package/fesm2022/forty-cdk-search.mjs.map +1 -0
  97. package/fesm2022/forty-cdk-select.mjs +1661 -0
  98. package/fesm2022/forty-cdk-select.mjs.map +1 -0
  99. package/fesm2022/forty-cdk-separator.mjs +82 -0
  100. package/fesm2022/forty-cdk-separator.mjs.map +1 -0
  101. package/fesm2022/forty-cdk-signal-forms.mjs +97 -0
  102. package/fesm2022/forty-cdk-signal-forms.mjs.map +1 -0
  103. package/fesm2022/forty-cdk-slider.mjs +803 -0
  104. package/fesm2022/forty-cdk-slider.mjs.map +1 -0
  105. package/fesm2022/forty-cdk-stepper.mjs +886 -0
  106. package/fesm2022/forty-cdk-stepper.mjs.map +1 -0
  107. package/fesm2022/forty-cdk-switch.mjs +137 -0
  108. package/fesm2022/forty-cdk-switch.mjs.map +1 -0
  109. package/fesm2022/forty-cdk-table.mjs +1518 -0
  110. package/fesm2022/forty-cdk-table.mjs.map +1 -0
  111. package/fesm2022/forty-cdk-tabs.mjs +400 -0
  112. package/fesm2022/forty-cdk-tabs.mjs.map +1 -0
  113. package/fesm2022/forty-cdk-time-field.mjs +593 -0
  114. package/fesm2022/forty-cdk-time-field.mjs.map +1 -0
  115. package/fesm2022/forty-cdk-time-picker.mjs +1013 -0
  116. package/fesm2022/forty-cdk-time-picker.mjs.map +1 -0
  117. package/fesm2022/forty-cdk-toast.mjs +1153 -0
  118. package/fesm2022/forty-cdk-toast.mjs.map +1 -0
  119. package/fesm2022/forty-cdk-toggle.mjs +516 -0
  120. package/fesm2022/forty-cdk-toggle.mjs.map +1 -0
  121. package/fesm2022/forty-cdk-toolbar.mjs +374 -0
  122. package/fesm2022/forty-cdk-toolbar.mjs.map +1 -0
  123. package/fesm2022/forty-cdk-tooltip.mjs +672 -0
  124. package/fesm2022/forty-cdk-tooltip.mjs.map +1 -0
  125. package/fesm2022/forty-cdk-tree.mjs +2007 -0
  126. package/fesm2022/forty-cdk-tree.mjs.map +1 -0
  127. package/fesm2022/forty-cdk-virtualization.mjs +1 -1
  128. package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
  129. package/fesm2022/forty-cdk.mjs +0 -43310
  130. package/fesm2022/forty-cdk.mjs.map +1 -1
  131. package/field/README.md +97 -0
  132. package/fieldset/README.md +86 -0
  133. package/file-upload/README.md +73 -0
  134. package/hover-card/README.md +171 -0
  135. package/input/README.md +156 -0
  136. package/listbox/README.md +424 -0
  137. package/menu/README.md +181 -0
  138. package/menubar/README.md +140 -0
  139. package/meter/README.md +128 -0
  140. package/navigation-menu/README.md +253 -0
  141. package/number-input/README.md +171 -0
  142. package/otp-input/README.md +198 -0
  143. package/package.json +213 -1
  144. package/pagination/README.md +61 -0
  145. package/pane-resizer/README.md +136 -0
  146. package/popover/README.md +262 -0
  147. package/progress/README.md +115 -0
  148. package/radio-group/README.md +129 -0
  149. package/scroll-area/README.md +184 -0
  150. package/search/README.md +42 -0
  151. package/select/README.md +488 -0
  152. package/separator/README.md +84 -0
  153. package/signal-forms/README.md +72 -0
  154. package/slider/README.md +152 -0
  155. package/stepper/README.md +292 -0
  156. package/switch/README.md +116 -0
  157. package/table/README.md +769 -0
  158. package/tabs/README.md +130 -0
  159. package/time-field/README.md +157 -0
  160. package/time-picker/README.md +172 -0
  161. package/toast/README.md +398 -0
  162. package/toggle/README.md +224 -0
  163. package/toolbar/README.md +109 -0
  164. package/tooltip/README.md +274 -0
  165. package/tree/README.md +708 -0
  166. package/types/forty-cdk-accordion.d.ts +242 -0
  167. package/types/forty-cdk-aspect-ratio.d.ts +59 -0
  168. package/types/forty-cdk-avatar.d.ts +133 -0
  169. package/types/forty-cdk-breadcrumbs.d.ts +92 -0
  170. package/types/forty-cdk-breakpoints.d.ts +141 -0
  171. package/types/forty-cdk-button.d.ts +80 -0
  172. package/types/forty-cdk-calendar.d.ts +914 -0
  173. package/types/forty-cdk-carousel.d.ts +530 -0
  174. package/types/forty-cdk-checkbox.d.ts +141 -0
  175. package/types/forty-cdk-combobox.d.ts +1259 -0
  176. package/types/forty-cdk-context-menu.d.ts +313 -0
  177. package/types/forty-cdk-core.d.ts +5774 -0
  178. package/types/forty-cdk-date-field.d.ts +307 -0
  179. package/types/forty-cdk-date-picker.d.ts +622 -0
  180. package/types/forty-cdk-dialog.d.ts +546 -0
  181. package/types/forty-cdk-disclosure.d.ts +127 -0
  182. package/types/forty-cdk-drag-drop.d.ts +456 -0
  183. package/types/forty-cdk-drawer.d.ts +871 -0
  184. package/types/forty-cdk-dropdown-menu.d.ts +242 -0
  185. package/types/forty-cdk-field.d.ts +236 -0
  186. package/types/forty-cdk-fieldset.d.ts +119 -0
  187. package/types/forty-cdk-file-upload.d.ts +124 -0
  188. package/types/forty-cdk-hover-card.d.ts +320 -0
  189. package/types/forty-cdk-input.d.ts +169 -0
  190. package/types/forty-cdk-internationalized-date.d.ts +1 -1
  191. package/types/forty-cdk-listbox.d.ts +513 -0
  192. package/types/forty-cdk-menu.d.ts +629 -0
  193. package/types/forty-cdk-menubar.d.ts +451 -0
  194. package/types/forty-cdk-meter.d.ts +122 -0
  195. package/types/forty-cdk-navigation-menu.d.ts +514 -0
  196. package/types/forty-cdk-number-input.d.ts +319 -0
  197. package/types/forty-cdk-otp-input.d.ts +248 -0
  198. package/types/forty-cdk-pagination.d.ts +214 -0
  199. package/types/forty-cdk-pane-resizer.d.ts +145 -0
  200. package/types/forty-cdk-popover.d.ts +509 -0
  201. package/types/forty-cdk-progress.d.ts +143 -0
  202. package/types/forty-cdk-radio-group.d.ts +222 -0
  203. package/types/forty-cdk-scroll-area.d.ts +258 -0
  204. package/types/forty-cdk-search.d.ts +142 -0
  205. package/types/forty-cdk-select.d.ts +899 -0
  206. package/types/forty-cdk-separator.d.ts +59 -0
  207. package/types/forty-cdk-signal-forms.d.ts +58 -0
  208. package/types/forty-cdk-slider.d.ts +379 -0
  209. package/types/forty-cdk-stepper.d.ts +650 -0
  210. package/types/forty-cdk-switch.d.ts +87 -0
  211. package/types/forty-cdk-table.d.ts +723 -0
  212. package/types/forty-cdk-tabs.d.ts +235 -0
  213. package/types/forty-cdk-time-field.d.ts +307 -0
  214. package/types/forty-cdk-time-picker.d.ts +578 -0
  215. package/types/forty-cdk-toast.d.ts +598 -0
  216. package/types/forty-cdk-toggle.d.ts +310 -0
  217. package/types/forty-cdk-toolbar.d.ts +217 -0
  218. package/types/forty-cdk-tooltip.d.ts +436 -0
  219. package/types/forty-cdk-tree.d.ts +688 -0
  220. package/types/forty-cdk.d.ts +1 -19743
@@ -0,0 +1,1518 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, booleanAttribute, inject, signal, computed, input, model, ElementRef, Directive, DestroyRef, numberAttribute, output, effect, PLATFORM_ID, afterNextRender, DOCUMENT } from '@angular/core';
3
+ import { SelectionModel, injectTextDirection, injectElementSize, Collection, RovingTabindex, firstEnabledHost, computeFlatHierarchy, reconcileRovingActive, resolveTreegridExpandCollapse, resolveGridNavigation, moveGridIndex, registerHandle, startPointerResize, clampToRange, translateWindowReorder, LiveAnnouncer, createKeyboardDragMediator, createDefaults } from 'forty-cdk/core';
4
+ import * as i1 from 'forty-cdk/drag-drop';
5
+ import { FOR_DRAGGABLE_CONTEXT, ForDropList, moveItemInArray, FOR_DROP_LIST_DEFAULT_ORIENTATION, FOR_DRAG_DROP_DEFAULTS } from 'forty-cdk/drag-drop';
6
+ import { isPlatformBrowser } from '@angular/common';
7
+
8
+ const FOR_TABLE_CONTEXT = new InjectionToken('FOR_TABLE_CONTEXT');
9
+ const FOR_TABLE_ROW_CONTEXT = new InjectionToken('FOR_TABLE_ROW_CONTEXT');
10
+ /**
11
+ * Coerces the `sticky` input value for header and data cells.
12
+ * The string `'end'` pins the cell to the end edge; any other truthy value
13
+ * (including the empty string from a bare `sticky` attribute) pins it to the
14
+ * start edge; a falsy value means not sticky.
15
+ */
16
+ function coerceSticky(value) {
17
+ return value === 'end' ? 'end' : booleanAttribute(value);
18
+ }
19
+ function injectTableContext(piece) {
20
+ const ctx = inject(FOR_TABLE_CONTEXT, { optional: true });
21
+ if (!ctx) {
22
+ throw new Error(`[forty-cdk/table] ${piece} must be used inside a [forTable] element.`);
23
+ }
24
+ return ctx;
25
+ }
26
+ function injectTableRowContext(piece) {
27
+ const ctx = inject(FOR_TABLE_ROW_CONTEXT, { optional: true });
28
+ if (!ctx) {
29
+ throw new Error(`[forty-cdk/table] ${piece} must be used inside a [forTableRow] element.`);
30
+ }
31
+ return ctx;
32
+ }
33
+
34
+ /**
35
+ * Row-selection sub-model for `ForTable`. Owns the `SelectionModel`, the range
36
+ * anchor, and the toggle / replace / range / select-all algorithms, decoupled
37
+ * from the table root.
38
+ *
39
+ * Internal — not re-exported from `table/index.ts` or `public-api.ts`.
40
+ */
41
+ class TableRowSelection {
42
+ #selectionMode;
43
+ #selectionBehavior;
44
+ #compareWith;
45
+ #aggregateValues;
46
+ #model;
47
+ #anchor = signal(undefined, /* @ts-ignore */
48
+ ...(ngDevMode ? [{ debugName: "#anchor" }] : /* istanbul ignore next */ []));
49
+ /** Aggregate selection state across all selectable rows (`'none'` / `'some'` / `'all'`). */
50
+ selectAllState = computed(() => {
51
+ const values = this.#aggregateValues();
52
+ if (values.length === 0) {
53
+ return 'none';
54
+ }
55
+ let count = 0;
56
+ for (const v of values) {
57
+ if (this.#model.isSelected(v)) {
58
+ count += 1;
59
+ }
60
+ }
61
+ if (count === 0) {
62
+ return 'none';
63
+ }
64
+ return count === values.length ? 'all' : 'some';
65
+ }, /* @ts-ignore */
66
+ ...(ngDevMode ? [{ debugName: "selectAllState" }] : /* istanbul ignore next */ []));
67
+ constructor(deps) {
68
+ this.#selectionMode = deps.selectionMode;
69
+ this.#selectionBehavior = deps.selectionBehavior;
70
+ this.#compareWith = deps.compareWith;
71
+ this.#aggregateValues = deps.aggregateValues;
72
+ this.#model = new SelectionModel(deps.selection, {
73
+ multiple: computed(() => deps.selectionMode() === 'multiple'),
74
+ compareWith: (a, b) => deps.compareWith()(a, b),
75
+ });
76
+ }
77
+ /** Whether `value` is currently in the selection. */
78
+ isSelected(value) {
79
+ return this.#model.isSelected(value);
80
+ }
81
+ /** Toggles `value` in or out of the selection and re-anchors. No-op in `'none'` mode. */
82
+ toggle(value) {
83
+ if (this.#selectionMode() === 'none') {
84
+ return;
85
+ }
86
+ this.#model.toggle(value);
87
+ this.#anchor.set(value);
88
+ }
89
+ /**
90
+ * Applies a row selection click with optional modifier keys, honoring
91
+ * `selectionBehavior`: `'toggle'` always flips; `'replace'` replaces (Ctrl/Cmd
92
+ * toggles a single item, Shift extends a range in multiple mode).
93
+ */
94
+ select(value, modifiers) {
95
+ const mode = this.#selectionMode();
96
+ if (mode === 'none') {
97
+ return;
98
+ }
99
+ if (this.#selectionBehavior() === 'toggle') {
100
+ this.#model.toggle(value);
101
+ this.#anchor.set(value);
102
+ return;
103
+ }
104
+ const multiple = mode === 'multiple';
105
+ if (multiple && modifiers?.shiftKey) {
106
+ this.#selectRange(value);
107
+ return;
108
+ }
109
+ if (multiple && (modifiers?.ctrlKey || modifiers?.metaKey)) {
110
+ this.#model.toggle(value);
111
+ this.#anchor.set(value);
112
+ return;
113
+ }
114
+ this.#model.setSelection(value);
115
+ this.#anchor.set(value);
116
+ }
117
+ /** Selects all selectable rows when not all are selected; clears when all are. No-op outside `'multiple'` mode. */
118
+ toggleSelectAll() {
119
+ if (this.#selectionMode() !== 'multiple') {
120
+ return;
121
+ }
122
+ if (this.selectAllState() === 'all') {
123
+ this.#model.clear();
124
+ }
125
+ else {
126
+ this.#model.select(...this.#aggregateValues());
127
+ }
128
+ }
129
+ #selectRange(toValue) {
130
+ const values = this.#aggregateValues();
131
+ const equals = this.#compareWith();
132
+ const toIdx = values.findIndex((v) => equals(v, toValue));
133
+ if (toIdx < 0) {
134
+ return;
135
+ }
136
+ const anchor = this.#anchor();
137
+ const anchorIdx = anchor === undefined ? -1 : values.findIndex((v) => equals(v, anchor));
138
+ const start = anchorIdx < 0 ? toIdx : anchorIdx;
139
+ const [lo, hi] = start <= toIdx ? [start, toIdx] : [toIdx, start];
140
+ this.#model.setSelection(...values.slice(lo, hi + 1));
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Treegrid expansion sub-model for `ForTable`. Owns the membership and mutation
146
+ * algorithm for the `[(expanded)]` open-rows set, decoupled from the table root.
147
+ *
148
+ * Internal — not re-exported from `table/index.ts` or `public-api.ts`.
149
+ */
150
+ class TableExpansion {
151
+ #expanded;
152
+ #compareWith;
153
+ constructor(deps) {
154
+ this.#expanded = deps.expanded;
155
+ this.#compareWith = deps.compareWith;
156
+ }
157
+ /** Whether `value` is currently in the open-rows set. */
158
+ isExpanded(value) {
159
+ return this.#expanded().some((v) => this.#compareWith()(v, value));
160
+ }
161
+ /**
162
+ * Sets a parent row's expansion in or out of the open-rows set. No-op when
163
+ * `value` is undefined or already in the requested state.
164
+ */
165
+ setExpanded(value, open) {
166
+ if (value === undefined) {
167
+ return;
168
+ }
169
+ const current = this.#expanded();
170
+ const has = this.isExpanded(value);
171
+ if (open && !has) {
172
+ this.#expanded.set([...current, value]);
173
+ }
174
+ else if (!open && has) {
175
+ this.#expanded.set(current.filter((v) => !this.#compareWith()(v, value)));
176
+ }
177
+ }
178
+ /** Toggles a parent row's expansion. No-op when `value` is undefined. */
179
+ toggle(value) {
180
+ if (value === undefined) {
181
+ return;
182
+ }
183
+ this.setExpanded(value, !this.isExpanded(value));
184
+ }
185
+ }
186
+
187
+ /** Grid actions whose target may lie on a row outside the rendered virtualized window. */
188
+ const ROW_CROSSING_ACTIONS = new Set([
189
+ 'next-row',
190
+ 'prev-row',
191
+ 'first',
192
+ 'last',
193
+ ]);
194
+ /**
195
+ * Root of the Table primitive. Sets the ARIA `role` from `mode`, reflects
196
+ * writing direction, and publishes the `--for-table-header-height` CSS custom
197
+ * property (driven by a `ResizeObserver` on the first registered header row)
198
+ * so consumers can `position: sticky` header cells without hard-coding offsets.
199
+ *
200
+ * Implements the [WAI-ARIA Table pattern](https://www.w3.org/WAI/ARIA/apg/patterns/table/)
201
+ * and the [WAI-ARIA Grid pattern](https://www.w3.org/WAI/ARIA/apg/patterns/grid/).
202
+ *
203
+ * Use `mode="grid"` or `mode="treegrid"` for interactive grid semantics: a
204
+ * single-tab-stop roving group with 2D arrow navigation over data cells.
205
+ * The default `mode="table"` is the static read-only structure.
206
+ */
207
+ class ForTable {
208
+ /**
209
+ * ARIA role emitted on the host. `'table'` is the default static read-only
210
+ * structure. `'grid'` and `'treegrid'` provide single-tab-stop roving + 2D
211
+ * arrow navigation over data cells.
212
+ */
213
+ mode = input('table', /* @ts-ignore */
214
+ ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
215
+ /**
216
+ * Accessible label for the table. When set, reflected as `aria-label`.
217
+ * Consumers with a visible caption should prefer pointing native
218
+ * `aria-labelledby` at it instead; this input is the reactive convenience
219
+ * hook for cases where no visible label element exists.
220
+ */
221
+ ariaLabel = input(null, /* @ts-ignore */
222
+ ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
223
+ /**
224
+ * Writing direction. When unset (default `null`), the inherited ambient
225
+ * direction is resolved from the nearest ancestor carrying a `dir` attribute
226
+ * (or `<html dir>`), defaulting to `'ltr'`. An explicit `[dir]` always wins.
227
+ * The resolved value is reflected to the host `dir` attribute.
228
+ */
229
+ _dirInput = input(null, { ...(ngDevMode ? { debugName: "_dirInput" } : /* istanbul ignore next */ {}), alias: 'dir' });
230
+ dir = injectTextDirection(this._dirInput);
231
+ /**
232
+ * True total number of data rows for `aria-rowcount`, e.g. for a server-paged or
233
+ * (later) virtualized table that renders only a window of rows. Defaults to the
234
+ * rendered data-row count. Ignored in `mode="table"`.
235
+ */
236
+ rowCount = input(/* @ts-ignore */
237
+ ...(ngDevMode ? [undefined, { debugName: "rowCount" }] : /* istanbul ignore next */ []));
238
+ /**
239
+ * True total number of columns for `aria-colcount`. Defaults to the rendered
240
+ * column count (the cells of the first data row). Ignored in `mode="table"`.
241
+ */
242
+ colCount = input(/* @ts-ignore */
243
+ ...(ngDevMode ? [undefined, { debugName: "colCount" }] : /* istanbul ignore next */ []));
244
+ /** Row selection mode. `'none'` (default) disables selection entirely. */
245
+ selectionMode = input('none', /* @ts-ignore */
246
+ ...(ngDevMode ? [{ debugName: "selectionMode" }] : /* istanbul ignore next */ []));
247
+ /**
248
+ * How a row click changes the selection. `'toggle'` (default) flips the clicked
249
+ * row. `'replace'` replaces the selection with the clicked row; Ctrl/Cmd-click
250
+ * toggles a single row and Shift-click extends a range (multiple mode only).
251
+ */
252
+ selectionBehavior = input('toggle', /* @ts-ignore */
253
+ ...(ngDevMode ? [{ debugName: "selectionBehavior" }] : /* istanbul ignore next */ []));
254
+ /**
255
+ * Two-way bindable selected row values (each row's `[value]`). Single mode keeps
256
+ * 0–1 entries. The implicit `selectionChange` fires only on internal mutations
257
+ * (selector / row click / Space / select-all), never on consumer writes.
258
+ */
259
+ selection = model([], /* @ts-ignore */
260
+ ...(ngDevMode ? [{ debugName: "selection" }] : /* istanbul ignore next */ []));
261
+ /** Equality comparator for row values. Defaults to `===`; supply id-based for objects. */
262
+ compareWith = input((a, b) => a === b, /* @ts-ignore */
263
+ ...(ngDevMode ? [{ debugName: "compareWith" }] : /* istanbul ignore next */ []));
264
+ /**
265
+ * Full ordered set of selectable row values (each row's `[value]`), for a
266
+ * virtualized or server-paged table whose aggregate selection operations must
267
+ * span rows beyond the rendered window. When `null` (default), the select-all
268
+ * tri-state, `toggleSelectAll`, and Shift-click range selection compute against
269
+ * the registered (rendered) rows only. When supplied, they use this set as the
270
+ * universe of selectable values, so a range can span unmounted rows and the
271
+ * tri-state reflects the true dataset. Per-row selection is unaffected.
272
+ */
273
+ selectableValues = input(null, /* @ts-ignore */
274
+ ...(ngDevMode ? [{ debugName: "selectableValues" }] : /* istanbul ignore next */ []));
275
+ /**
276
+ * Two-way bindable open parent-row values (each row's `[value]`), for
277
+ * `mode="treegrid"`. The implicit `expandedChange` fires only on internal
278
+ * expand/collapse (ArrowRight/ArrowLeft, `toggleRowExpansion`), never on
279
+ * consumer writes through `[(expanded)]`. Ignored outside `treegrid` mode.
280
+ */
281
+ expanded = model([], /* @ts-ignore */
282
+ ...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
283
+ #rootEl = inject(ElementRef).nativeElement;
284
+ #headerRowEl = signal(null, /* @ts-ignore */
285
+ ...(ngDevMode ? [{ debugName: "#headerRowEl" }] : /* istanbul ignore next */ []));
286
+ headerSize = injectElementSize(this.#headerRowEl);
287
+ #rows = new Collection();
288
+ #roving = new RovingTabindex();
289
+ /** Live registered data rows, exposed to `[forTableVirtualized]`'s cross-window navigation bridge. */
290
+ rows = this.#rows.items;
291
+ #virtualNav = signal(null, /* @ts-ignore */
292
+ ...(ngDevMode ? [{ debugName: "#virtualNav" }] : /* istanbul ignore next */ []));
293
+ #reorderingRow = signal(null, /* @ts-ignore */
294
+ ...(ngDevMode ? [{ debugName: "#reorderingRow" }] : /* istanbul ignore next */ []));
295
+ /**
296
+ * Absolute index of the row currently being pointer-reordered, or `null`. Set by
297
+ * `[forTableRowReorder]` and read by `[forTableVirtualized]` to keep the lifted
298
+ * row mounted across recycling during a drag.
299
+ */
300
+ reorderingRowIndex = this.#reorderingRow.asReadonly();
301
+ virtualRowNavigation = this.#virtualNav.asReadonly();
302
+ #flatCells = computed(() => this.#rows.items().flatMap((row) => row.cells()), /* @ts-ignore */
303
+ ...(ngDevMode ? [{ debugName: "#flatCells" }] : /* istanbul ignore next */ []));
304
+ #cols = computed(() => this.#rows.items()[0]?.cells().length ?? 0, /* @ts-ignore */
305
+ ...(ngDevMode ? [{ debugName: "#cols" }] : /* istanbul ignore next */ []));
306
+ #firstEnabledCell = computed(() => firstEnabledHost(this.#flatCells()), /* @ts-ignore */
307
+ ...(ngDevMode ? [{ debugName: "#firstEnabledCell" }] : /* istanbul ignore next */ []));
308
+ #registeredValues = computed(() => this.#rows
309
+ .items()
310
+ .map((row) => row.value())
311
+ .filter((v) => v !== undefined), /* @ts-ignore */
312
+ ...(ngDevMode ? [{ debugName: "#registeredValues" }] : /* istanbul ignore next */ []));
313
+ #aggregateValues = computed(() => this.selectableValues() ?? this.#registeredValues(), /* @ts-ignore */
314
+ ...(ngDevMode ? [{ debugName: "#aggregateValues" }] : /* istanbul ignore next */ []));
315
+ #selection = new TableRowSelection({
316
+ selection: this.selection,
317
+ selectionMode: this.selectionMode,
318
+ selectionBehavior: this.selectionBehavior,
319
+ compareWith: this.compareWith,
320
+ aggregateValues: this.#aggregateValues,
321
+ });
322
+ selectAllState = this.#selection.selectAllState;
323
+ #expansion = new TableExpansion({
324
+ expanded: this.expanded,
325
+ compareWith: this.compareWith,
326
+ });
327
+ #rowHierarchy = computed(() => computeFlatHierarchy(this.#rows.items().map((row) => row.level())), /* @ts-ignore */
328
+ ...(ngDevMode ? [{ debugName: "#rowHierarchy" }] : /* istanbul ignore next */ []));
329
+ #rowOfCell(cellHost) {
330
+ return this.#rows.items().find((row) => row.cells().some((cell) => cell.host === cellHost));
331
+ }
332
+ /**
333
+ * Absolute index of the row that owns the currently roving-focused cell, or `null`
334
+ * when no cell is focused (or the focused row carries no `virtualIndex`). Used by
335
+ * `[forTableVirtualized]` to keep the focused row mounted across recycling.
336
+ */
337
+ focusedRowIndex = computed(() => {
338
+ const active = this.#roving.active();
339
+ if (active === null) {
340
+ return null;
341
+ }
342
+ return this.#rowOfCell(active)?.virtualIndex() ?? null;
343
+ }, /* @ts-ignore */
344
+ ...(ngDevMode ? [{ debugName: "focusedRowIndex" }] : /* istanbul ignore next */ []));
345
+ rowCountAttr = computed(() => this.mode() === 'table' ? null : (this.rowCount() ?? this.#rows.items().length), /* @ts-ignore */
346
+ ...(ngDevMode ? [{ debugName: "rowCountAttr" }] : /* istanbul ignore next */ []));
347
+ colCountAttr = computed(() => this.mode() === 'table' ? null : (this.colCount() ?? this.#cols()), /* @ts-ignore */
348
+ ...(ngDevMode ? [{ debugName: "colCountAttr" }] : /* istanbul ignore next */ []));
349
+ constructor() {
350
+ reconcileRovingActive(this.#roving, this.#flatCells);
351
+ }
352
+ registerHeaderRow(el) {
353
+ this.#headerRowEl.set(el);
354
+ }
355
+ setColumnWidth(column, width) {
356
+ this.#rootEl.style.setProperty(`--for-table-col-${column}-width`, `${width}px`);
357
+ }
358
+ isRowExpanded(value) {
359
+ return this.#expansion.isExpanded(value);
360
+ }
361
+ toggleRowExpansion(value) {
362
+ this.#expansion.toggle(value);
363
+ }
364
+ rowPosinset(host) {
365
+ const index = this.#rows.indexOfHost(host);
366
+ return index < 0 ? 1 : (this.#rowHierarchy()[index]?.posinset ?? 1);
367
+ }
368
+ rowSetsize(host) {
369
+ const index = this.#rows.indexOfHost(host);
370
+ return index < 0 ? 1 : (this.#rowHierarchy()[index]?.setsize ?? 1);
371
+ }
372
+ unregisterHeaderRow(el) {
373
+ if (this.#headerRowEl() === el) {
374
+ this.#headerRowEl.set(null);
375
+ }
376
+ }
377
+ registerVirtualNavigation(navigation) {
378
+ this.#virtualNav.set(navigation);
379
+ }
380
+ setReorderingRow(index) {
381
+ this.#reorderingRow.set(index);
382
+ }
383
+ registerRow(handle) {
384
+ this.#rows.register(handle);
385
+ }
386
+ unregisterRow(handle) {
387
+ this.#rows.unregister(handle);
388
+ }
389
+ rowIndexOf(host) {
390
+ return this.#rows.indexOfHost(host);
391
+ }
392
+ cellTabIndex(host) {
393
+ if (this.#roving.hasActive()) {
394
+ return this.#roving.tabindexFor(host);
395
+ }
396
+ return this.#firstEnabledCell() === host ? 0 : -1;
397
+ }
398
+ isCellHighlighted(host) {
399
+ return this.#roving.active() === host;
400
+ }
401
+ activateCell(host) {
402
+ if (this.mode() !== 'table') {
403
+ this.#roving.setActive(host);
404
+ }
405
+ }
406
+ isRowSelected(value) {
407
+ return this.#selection.isSelected(value);
408
+ }
409
+ toggleRowSelection(value) {
410
+ this.#selection.toggle(value);
411
+ }
412
+ selectRow(value, modifiers) {
413
+ this.#selection.select(value, modifiers);
414
+ }
415
+ toggleSelectAll() {
416
+ this.#selection.toggleSelectAll();
417
+ }
418
+ #rowValueOfCell(cellHost) {
419
+ for (const row of this.#rows.items()) {
420
+ if (row.cells().some((cell) => cell.host === cellHost)) {
421
+ return row.value();
422
+ }
423
+ }
424
+ return undefined;
425
+ }
426
+ handleCellKeydown(event, host) {
427
+ if (this.mode() === 'table') {
428
+ return;
429
+ }
430
+ if (this.#handleSelectionKeydown(event, host)) {
431
+ return;
432
+ }
433
+ if (this.#handleExpansionKeydown(event, host)) {
434
+ return;
435
+ }
436
+ this.#handleGridNavigationKeydown(event, host);
437
+ }
438
+ #handleSelectionKeydown(event, host) {
439
+ if (event.key !== ' ' || event.target !== host || this.selectionMode() === 'none') {
440
+ return false;
441
+ }
442
+ const value = this.#rowValueOfCell(host);
443
+ if (value === undefined) {
444
+ return false;
445
+ }
446
+ event.preventDefault();
447
+ this.#selection.toggle(value);
448
+ return true;
449
+ }
450
+ #handleExpansionKeydown(event, host) {
451
+ if (this.mode() !== 'treegrid') {
452
+ return false;
453
+ }
454
+ const intent = resolveTreegridExpandCollapse(event, this.dir());
455
+ if (intent === null) {
456
+ return false;
457
+ }
458
+ const row = this.#rowOfCell(host);
459
+ if (!row?.expandable()) {
460
+ return false;
461
+ }
462
+ const value = row.value();
463
+ const open = this.#expansion.isExpanded(value);
464
+ if (intent === 'expand' && !open) {
465
+ event.preventDefault();
466
+ this.#expansion.setExpanded(value, true);
467
+ return true;
468
+ }
469
+ if (intent === 'collapse' && open) {
470
+ event.preventDefault();
471
+ this.#expansion.setExpanded(value, false);
472
+ return true;
473
+ }
474
+ return false;
475
+ }
476
+ #handleGridNavigationKeydown(event, host) {
477
+ const cols = this.#cols();
478
+ const cells = this.#flatCells();
479
+ if (cols === 0 || cells.length === 0) {
480
+ return;
481
+ }
482
+ const action = resolveGridNavigation(event, {
483
+ cols,
484
+ dir: this.dir(),
485
+ pageKeys: true,
486
+ });
487
+ if (action === null) {
488
+ return;
489
+ }
490
+ event.preventDefault();
491
+ const currentIndex = cells.findIndex((cell) => cell.host === host);
492
+ const navigation = this.#virtualNav();
493
+ const fromRow = this.focusedRowIndex();
494
+ const total = this.rowCount();
495
+ if (navigation !== null &&
496
+ total !== undefined &&
497
+ fromRow !== null &&
498
+ ROW_CROSSING_ACTIONS.has(action)) {
499
+ const col = currentIndex < 0 ? 0 : currentIndex % cols;
500
+ const target = resolveCrossWindowRowTarget(action, fromRow, col, total, cols);
501
+ if (target !== null) {
502
+ navigation.navigateTo(target.row, target.col);
503
+ }
504
+ return;
505
+ }
506
+ const next = moveGridIndex(currentIndex < 0 ? 0 : currentIndex, cells.length, action, {
507
+ cols,
508
+ isDisabled: (i) => cells[i].disabled(),
509
+ });
510
+ if (next === null) {
511
+ return;
512
+ }
513
+ this.#roving.focusActive(cells[next].host);
514
+ }
515
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTable, deps: [], target: i0.ɵɵFactoryTarget.Directive });
516
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForTable, isStandalone: true, selector: "[forTable]", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, _dirInput: { classPropertyName: "_dirInput", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, rowCount: { classPropertyName: "rowCount", publicName: "rowCount", isSignal: true, isRequired: false, transformFunction: null }, colCount: { classPropertyName: "colCount", publicName: "colCount", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, selectionBehavior: { classPropertyName: "selectionBehavior", publicName: "selectionBehavior", isSignal: true, isRequired: false, transformFunction: null }, selection: { classPropertyName: "selection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null }, selectableValues: { classPropertyName: "selectableValues", publicName: "selectableValues", isSignal: true, isRequired: false, transformFunction: null }, expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selection: "selectionChange", expanded: "expandedChange" }, host: { properties: { "attr.role": "mode()", "attr.aria-label": "ariaLabel() || null", "attr.dir": "dir()", "attr.data-mode": "mode()", "style.--for-table-header-height.px": "headerSize()?.height ?? null", "attr.aria-rowcount": "rowCountAttr()", "attr.aria-colcount": "colCountAttr()", "attr.aria-multiselectable": "selectionMode() === \"multiple\" ? \"true\" : null" } }, providers: [{ provide: FOR_TABLE_CONTEXT, useExisting: ForTable }], exportAs: ["forTable"], ngImport: i0 });
517
+ }
518
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTable, decorators: [{
519
+ type: Directive,
520
+ args: [{
521
+ selector: '[forTable]',
522
+ exportAs: 'forTable',
523
+ host: {
524
+ '[attr.role]': 'mode()',
525
+ '[attr.aria-label]': 'ariaLabel() || null',
526
+ '[attr.dir]': 'dir()',
527
+ '[attr.data-mode]': 'mode()',
528
+ '[style.--for-table-header-height.px]': 'headerSize()?.height ?? null',
529
+ '[attr.aria-rowcount]': 'rowCountAttr()',
530
+ '[attr.aria-colcount]': 'colCountAttr()',
531
+ '[attr.aria-multiselectable]': 'selectionMode() === "multiple" ? "true" : null',
532
+ },
533
+ providers: [{ provide: FOR_TABLE_CONTEXT, useExisting: ForTable }],
534
+ }]
535
+ }], ctorParameters: () => [], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], _dirInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }], rowCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowCount", required: false }] }], colCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "colCount", required: false }] }], selectionMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionMode", required: false }] }], selectionBehavior: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionBehavior", required: false }] }], selection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }, { type: i0.Output, args: ["selectionChange"] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], selectableValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectableValues", required: false }] }], expanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "expanded", required: false }] }, { type: i0.Output, args: ["expandedChange"] }] } });
536
+ /**
537
+ * Resolves the absolute `(row, 0-based column)` target for a row-crossing grid
538
+ * action against the true `total` row count. Arrow row-moves preserve the
539
+ * current column; `first` / `last` jump to the first / last cell of the whole
540
+ * grid. Returns `null` when the move would step past the dataset bounds.
541
+ */
542
+ function resolveCrossWindowRowTarget(action, fromRow, col, total, cols) {
543
+ switch (action) {
544
+ case 'next-row':
545
+ return fromRow + 1 < total ? { row: fromRow + 1, col } : null;
546
+ case 'prev-row':
547
+ return fromRow - 1 >= 0 ? { row: fromRow - 1, col } : null;
548
+ case 'first':
549
+ return { row: 0, col: 0 };
550
+ case 'last':
551
+ return { row: total - 1, col: cols - 1 };
552
+ default:
553
+ return null;
554
+ }
555
+ }
556
+
557
+ /**
558
+ * Marks the header row of the table (`role="row"`). Registers its host
559
+ * element with the root so `ForTable` can measure the header's height and
560
+ * publish `--for-table-header-height` for sticky-cell CSS.
561
+ */
562
+ class ForTableHeaderRow {
563
+ ctx = injectTableContext('ForTableHeaderRow');
564
+ #host = inject(ElementRef);
565
+ constructor() {
566
+ const el = this.#host.nativeElement;
567
+ this.ctx.registerHeaderRow(el);
568
+ inject(DestroyRef).onDestroy(() => this.ctx.unregisterHeaderRow(el));
569
+ }
570
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableHeaderRow, deps: [], target: i0.ɵɵFactoryTarget.Directive });
571
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForTableHeaderRow, isStandalone: true, selector: "[forTableHeaderRow]", host: { attributes: { "role": "row" } }, exportAs: ["forTableHeaderRow"], ngImport: i0 });
572
+ }
573
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableHeaderRow, decorators: [{
574
+ type: Directive,
575
+ args: [{
576
+ selector: '[forTableHeaderRow]',
577
+ exportAs: 'forTableHeaderRow',
578
+ host: {
579
+ role: 'row',
580
+ },
581
+ }]
582
+ }], ctorParameters: () => [] });
583
+
584
+ /**
585
+ * Marks a data row (`role="row"`). Owns the registry of its data cells (for
586
+ * `aria-colindex`) and registers itself with the root so it joins the row index
587
+ * space (`aria-rowindex`, 1-based over data rows in `grid`/`treegrid` mode) and
588
+ * the 2D navigation grid.
589
+ */
590
+ class ForTableRow {
591
+ ctx = injectTableContext('ForTableRow');
592
+ #host = inject(ElementRef).nativeElement;
593
+ #cells = new Collection();
594
+ /** This row's selection identity, written into the table's `[(selection)]`. Leave unset for non-selectable rows. */
595
+ value = input(/* @ts-ignore */
596
+ ...(ngDevMode ? [undefined, { debugName: "value" }] : /* istanbul ignore next */ []));
597
+ /** 1-based tree depth for `aria-level` in `mode="treegrid"`. Ignored in other modes. */
598
+ level = input(1, { ...(ngDevMode ? { debugName: "level" } : /* istanbul ignore next */ {}), transform: numberAttribute });
599
+ /** Marks this row as an expandable parent — emits `aria-expanded` + `data-state`. */
600
+ expandable = input(false, { ...(ngDevMode ? { debugName: "expandable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
601
+ /**
602
+ * Absolute 0-based index of this row in the full virtualized dataset. Set by the
603
+ * consumer when rendering a window via `[forTableVirtualized]`; drives the absolute
604
+ * `aria-rowindex` and keeps the focused row mounted across recycling. Leave unset
605
+ * (default `null`) for non-virtualized tables. Ignored in `mode="table"`.
606
+ */
607
+ virtualIndex = input(null, { ...(ngDevMode ? { debugName: "virtualIndex" } : /* istanbul ignore next */ {}), transform: (v) => (v == null ? null : numberAttribute(v)) });
608
+ rowIndex = computed(() => {
609
+ if (this.ctx.mode() === 'table') {
610
+ return null;
611
+ }
612
+ const vi = this.virtualIndex();
613
+ return vi !== null ? vi + 1 : this.ctx.rowIndexOf(this.#host) + 1;
614
+ }, /* @ts-ignore */
615
+ ...(ngDevMode ? [{ debugName: "rowIndex" }] : /* istanbul ignore next */ []));
616
+ selectionMode = this.ctx.selectionMode;
617
+ selected = computed(() => {
618
+ const v = this.value();
619
+ return v !== undefined && this.ctx.isRowSelected(v);
620
+ }, /* @ts-ignore */
621
+ ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
622
+ ariaSelected = computed(() => this.ctx.selectionMode() === 'none' ? null : this.selected() ? 'true' : 'false', /* @ts-ignore */
623
+ ...(ngDevMode ? [{ debugName: "ariaSelected" }] : /* istanbul ignore next */ []));
624
+ /** Whether this expandable row is currently open. False for non-expandable rows. */
625
+ expanded = computed(() => {
626
+ const v = this.value();
627
+ return v !== undefined && this.ctx.isRowExpanded(v);
628
+ }, /* @ts-ignore */
629
+ ...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
630
+ /** Toggles this row's expansion. No-op when the row is not expandable or has no `[value]`. */
631
+ toggleExpanded() {
632
+ if (this.expandable()) {
633
+ this.ctx.toggleRowExpansion(this.value());
634
+ }
635
+ }
636
+ ariaLevel = computed(() => this.ctx.mode() === 'treegrid' ? this.level() : null, /* @ts-ignore */
637
+ ...(ngDevMode ? [{ debugName: "ariaLevel" }] : /* istanbul ignore next */ []));
638
+ posinset = computed(() => this.ctx.mode() === 'treegrid' ? this.ctx.rowPosinset(this.#host) : null, /* @ts-ignore */
639
+ ...(ngDevMode ? [{ debugName: "posinset" }] : /* istanbul ignore next */ []));
640
+ setsize = computed(() => this.ctx.mode() === 'treegrid' ? this.ctx.rowSetsize(this.#host) : null, /* @ts-ignore */
641
+ ...(ngDevMode ? [{ debugName: "setsize" }] : /* istanbul ignore next */ []));
642
+ ariaExpanded = computed(() => this.ctx.mode() === 'treegrid' && this.expandable()
643
+ ? this.expanded()
644
+ ? 'true'
645
+ : 'false'
646
+ : null, /* @ts-ignore */
647
+ ...(ngDevMode ? [{ debugName: "ariaExpanded" }] : /* istanbul ignore next */ []));
648
+ expandState = computed(() => this.ctx.mode() === 'treegrid' && this.expandable()
649
+ ? this.expanded()
650
+ ? 'open'
651
+ : 'closed'
652
+ : null, /* @ts-ignore */
653
+ ...(ngDevMode ? [{ debugName: "expandState" }] : /* istanbul ignore next */ []));
654
+ constructor() {
655
+ const handle = {
656
+ host: this.#host,
657
+ cells: this.#cells.items,
658
+ value: this.value,
659
+ level: this.level,
660
+ expandable: this.expandable,
661
+ virtualIndex: this.virtualIndex,
662
+ };
663
+ registerHandle(handle, (h) => this.ctx.registerRow(h), (h) => this.ctx.unregisterRow(h));
664
+ }
665
+ registerCell(handle) {
666
+ this.#cells.register(handle);
667
+ }
668
+ unregisterCell(handle) {
669
+ this.#cells.unregister(handle);
670
+ }
671
+ cellIndexOf(host) {
672
+ return this.#cells.indexOfHost(host);
673
+ }
674
+ toggleSelected() {
675
+ const v = this.value();
676
+ if (v !== undefined) {
677
+ this.ctx.toggleRowSelection(v);
678
+ }
679
+ }
680
+ onClick(event) {
681
+ const v = this.value();
682
+ if (this.ctx.selectionMode() === 'none' || v === undefined) {
683
+ return;
684
+ }
685
+ this.ctx.selectRow(v, {
686
+ ctrlKey: event.ctrlKey,
687
+ metaKey: event.metaKey,
688
+ shiftKey: event.shiftKey,
689
+ });
690
+ }
691
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableRow, deps: [], target: i0.ɵɵFactoryTarget.Directive });
692
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForTableRow, isStandalone: true, selector: "[forTableRow]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, level: { classPropertyName: "level", publicName: "level", isSignal: true, isRequired: false, transformFunction: null }, expandable: { classPropertyName: "expandable", publicName: "expandable", isSignal: true, isRequired: false, transformFunction: null }, virtualIndex: { classPropertyName: "virtualIndex", publicName: "virtualIndex", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "row" }, listeners: { "click": "onClick($event)" }, properties: { "attr.aria-rowindex": "rowIndex()", "attr.aria-selected": "ariaSelected()", "attr.data-selected": "selected() ? \"\" : null", "attr.aria-level": "ariaLevel()", "attr.aria-posinset": "posinset()", "attr.aria-setsize": "setsize()", "attr.aria-expanded": "ariaExpanded()", "attr.data-state": "expandState()" } }, providers: [{ provide: FOR_TABLE_ROW_CONTEXT, useExisting: ForTableRow }], exportAs: ["forTableRow"], ngImport: i0 });
693
+ }
694
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableRow, decorators: [{
695
+ type: Directive,
696
+ args: [{
697
+ selector: '[forTableRow]',
698
+ exportAs: 'forTableRow',
699
+ host: {
700
+ role: 'row',
701
+ '[attr.aria-rowindex]': 'rowIndex()',
702
+ '[attr.aria-selected]': 'ariaSelected()',
703
+ '[attr.data-selected]': 'selected() ? "" : null',
704
+ '[attr.aria-level]': 'ariaLevel()',
705
+ '[attr.aria-posinset]': 'posinset()',
706
+ '[attr.aria-setsize]': 'setsize()',
707
+ '[attr.aria-expanded]': 'ariaExpanded()',
708
+ '[attr.data-state]': 'expandState()',
709
+ '(click)': 'onClick($event)',
710
+ },
711
+ providers: [{ provide: FOR_TABLE_ROW_CONTEXT, useExisting: ForTableRow }],
712
+ }]
713
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], level: [{ type: i0.Input, args: [{ isSignal: true, alias: "level", required: false }] }], expandable: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandable", required: false }] }], virtualIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtualIndex", required: false }] }] } });
714
+
715
+ /**
716
+ * Marks a header cell (`role="columnheader"`). Requires a `name` input that
717
+ * identifies the column — reflected as `data-column` for later phases (sort,
718
+ * resize, reorder) to key off. Optionally sticky via the `sticky` input.
719
+ */
720
+ class ForTableHeaderCell {
721
+ ctx = injectTableContext('ForTableHeaderCell');
722
+ /** Column identifier, reflected as `data-column`. Required by later phases (sort, resize, reorder). */
723
+ name = input.required(/* @ts-ignore */
724
+ ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
725
+ /**
726
+ * Sticky placement for this header cell. `true` (or the bare `sticky`
727
+ * attribute) pins to the start edge; `'end'` pins to the end edge; `false`
728
+ * (default) is not sticky. The consumer applies `position: sticky` and the
729
+ * appropriate `top` / `left` / `right` offset in CSS — this input only
730
+ * provides the `data-sticky` hook.
731
+ */
732
+ sticky = input(false, { ...(ngDevMode ? { debugName: "sticky" } : /* istanbul ignore next */ {}), transform: coerceSticky });
733
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableHeaderCell, deps: [], target: i0.ɵɵFactoryTarget.Directive });
734
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForTableHeaderCell, isStandalone: true, selector: "[forTableHeaderCell]", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, sticky: { classPropertyName: "sticky", publicName: "sticky", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "columnheader" }, properties: { "attr.data-column": "name()", "attr.data-sticky": "sticky() ? (sticky() === 'end' ? 'end' : '') : null" } }, exportAs: ["forTableHeaderCell"], ngImport: i0 });
735
+ }
736
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableHeaderCell, decorators: [{
737
+ type: Directive,
738
+ args: [{
739
+ selector: '[forTableHeaderCell]',
740
+ exportAs: 'forTableHeaderCell',
741
+ host: {
742
+ role: 'columnheader',
743
+ '[attr.data-column]': 'name()',
744
+ '[attr.data-sticky]': "sticky() ? (sticky() === 'end' ? 'end' : '') : null",
745
+ },
746
+ }]
747
+ }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], sticky: [{ type: i0.Input, args: [{ isSignal: true, alias: "sticky", required: false }] }] } });
748
+
749
+ /**
750
+ * Marks a data cell. The `role` is derived from the root's `mode`: `'cell'` in
751
+ * `table` mode and `'gridcell'` in `grid` / `treegrid` mode. In grid / treegrid
752
+ * mode the cell is a roving-tabindex target — the single active cell carries
753
+ * `tabindex="0"` (others `-1`), reflects `data-highlighted` when focused, and
754
+ * carries a 1-based `aria-colindex`. Arrow / Home / End / Ctrl+Home / Ctrl+End /
755
+ * PageUp / PageDown move focus between cells. Requires a `name` input that
756
+ * identifies the column — reflected as `data-column`. Optionally sticky and
757
+ * optionally disabled (disabled cells are skipped during navigation).
758
+ */
759
+ class ForTableCell {
760
+ ctx = injectTableContext('ForTableCell');
761
+ rowCtx = injectTableRowContext('ForTableCell');
762
+ #host = inject(ElementRef).nativeElement;
763
+ role = computed(() => (this.ctx.mode() === 'table' ? 'cell' : 'gridcell'), /* @ts-ignore */
764
+ ...(ngDevMode ? [{ debugName: "role" }] : /* istanbul ignore next */ []));
765
+ /** Column identifier, reflected as `data-column`. Required by later phases (sort, resize, reorder). */
766
+ name = input.required(/* @ts-ignore */
767
+ ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
768
+ /**
769
+ * Sticky placement for this data cell. `true` (or the bare `sticky` attribute)
770
+ * pins to the start edge; `'end'` pins to the end edge; `false` (default) is not
771
+ * sticky. The consumer applies `position: sticky` and the offsets in CSS — this
772
+ * input only provides the `data-sticky` hook.
773
+ */
774
+ sticky = input(false, { ...(ngDevMode ? { debugName: "sticky" } : /* istanbul ignore next */ {}), transform: coerceSticky });
775
+ /**
776
+ * Whether this data cell is disabled. Disabled cells are skipped during arrow-key
777
+ * navigation, drop out of the tab order, and reflect `aria-disabled` / `data-disabled`.
778
+ * Only meaningful in `grid` / `treegrid` mode.
779
+ */
780
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
781
+ tabindex = computed(() => {
782
+ if (this.ctx.mode() === 'table') {
783
+ return null;
784
+ }
785
+ if (this.disabled()) {
786
+ return -1;
787
+ }
788
+ return this.ctx.cellTabIndex(this.#host);
789
+ }, /* @ts-ignore */
790
+ ...(ngDevMode ? [{ debugName: "tabindex" }] : /* istanbul ignore next */ []));
791
+ colIndex = computed(() => this.ctx.mode() === 'table' ? null : this.rowCtx.cellIndexOf(this.#host) + 1, /* @ts-ignore */
792
+ ...(ngDevMode ? [{ debugName: "colIndex" }] : /* istanbul ignore next */ []));
793
+ highlighted = computed(() => this.ctx.mode() !== 'table' && this.ctx.isCellHighlighted(this.#host), /* @ts-ignore */
794
+ ...(ngDevMode ? [{ debugName: "highlighted" }] : /* istanbul ignore next */ []));
795
+ constructor() {
796
+ const handle = { host: this.#host, disabled: this.disabled };
797
+ registerHandle(handle, (h) => this.rowCtx.registerCell(h), (h) => this.rowCtx.unregisterCell(h));
798
+ }
799
+ onFocus() {
800
+ this.ctx.activateCell(this.#host);
801
+ }
802
+ onKeyDown(event) {
803
+ this.ctx.handleCellKeydown(event, this.#host);
804
+ }
805
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableCell, deps: [], target: i0.ɵɵFactoryTarget.Directive });
806
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForTableCell, isStandalone: true, selector: "[forTableCell]", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, sticky: { classPropertyName: "sticky", publicName: "sticky", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "focus": "onFocus()", "keydown": "onKeyDown($event)" }, properties: { "attr.role": "role()", "attr.tabindex": "tabindex()", "attr.aria-colindex": "colIndex()", "attr.aria-disabled": "disabled() ? \"true\" : null", "attr.data-column": "name()", "attr.data-sticky": "sticky() ? (sticky() === 'end' ? 'end' : '') : null", "attr.data-highlighted": "highlighted() ? \"\" : null", "attr.data-disabled": "disabled() ? \"\" : null" } }, exportAs: ["forTableCell"], ngImport: i0 });
807
+ }
808
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableCell, decorators: [{
809
+ type: Directive,
810
+ args: [{
811
+ selector: '[forTableCell]',
812
+ exportAs: 'forTableCell',
813
+ host: {
814
+ '[attr.role]': 'role()',
815
+ '[attr.tabindex]': 'tabindex()',
816
+ '[attr.aria-colindex]': 'colIndex()',
817
+ '[attr.aria-disabled]': 'disabled() ? "true" : null',
818
+ '[attr.data-column]': 'name()',
819
+ '[attr.data-sticky]': "sticky() ? (sticky() === 'end' ? 'end' : '') : null",
820
+ '[attr.data-highlighted]': 'highlighted() ? "" : null',
821
+ '[attr.data-disabled]': 'disabled() ? "" : null',
822
+ '(focus)': 'onFocus()',
823
+ '(keydown)': 'onKeyDown($event)',
824
+ },
825
+ }]
826
+ }], ctorParameters: () => [], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], sticky: [{ type: i0.Input, args: [{ isSignal: true, alias: "sticky", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
827
+
828
+ /**
829
+ * Decorative per-row selection affordance inside a `[forTableRow]`. Clicking it
830
+ * toggles the row's selection; the enclosing row owns the announced
831
+ * `aria-selected`, so this element is `aria-hidden` and reflects
832
+ * `data-state="checked" | "unchecked"` for styling only. Place a glyph inside.
833
+ */
834
+ class ForTableRowSelector {
835
+ row = injectTableRowContext('ForTableRowSelector');
836
+ dataState = computed(() => (this.row.selected() ? 'checked' : 'unchecked'), /* @ts-ignore */
837
+ ...(ngDevMode ? [{ debugName: "dataState" }] : /* istanbul ignore next */ []));
838
+ onClick(event) {
839
+ event.stopPropagation();
840
+ this.row.toggleSelected();
841
+ }
842
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableRowSelector, deps: [], target: i0.ɵɵFactoryTarget.Directive });
843
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForTableRowSelector, isStandalone: true, selector: "[forTableRowSelector]", host: { attributes: { "aria-hidden": "true" }, listeners: { "click": "onClick($event)" }, properties: { "attr.data-state": "dataState()" } }, exportAs: ["forTableRowSelector"], ngImport: i0 });
844
+ }
845
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableRowSelector, decorators: [{
846
+ type: Directive,
847
+ args: [{
848
+ selector: '[forTableRowSelector]',
849
+ exportAs: 'forTableRowSelector',
850
+ host: {
851
+ 'aria-hidden': 'true',
852
+ '[attr.data-state]': 'dataState()',
853
+ '(click)': 'onClick($event)',
854
+ },
855
+ }]
856
+ }] });
857
+
858
+ /**
859
+ * Header "select all" checkbox for a `[forTable]` in `selectionMode="multiple"`.
860
+ * Reflects `aria-checked="true" | "false" | "mixed"` and
861
+ * `data-state="checked" | "unchecked" | "indeterminate"` derived from how many
862
+ * selectable rows are selected. Clicking (or Space / Enter) selects all rows
863
+ * when none/some are selected, and clears when all are. No-op outside multiple
864
+ * mode. Apply on a focusable element (e.g. a `<span>` you make tabbable, or a
865
+ * `<button type="button">`).
866
+ */
867
+ class ForTableSelectAll {
868
+ ctx = injectTableContext('ForTableSelectAll');
869
+ /** Accessible label for the control (e.g. "Select all rows"). Truthy-only. */
870
+ ariaLabel = input(null, /* @ts-ignore */
871
+ ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
872
+ ariaChecked = computed(() => {
873
+ const state = this.ctx.selectAllState();
874
+ return state === 'all' ? 'true' : state === 'some' ? 'mixed' : 'false';
875
+ }, /* @ts-ignore */
876
+ ...(ngDevMode ? [{ debugName: "ariaChecked" }] : /* istanbul ignore next */ []));
877
+ dataState = computed(() => {
878
+ const state = this.ctx.selectAllState();
879
+ return state === 'all' ? 'checked' : state === 'some' ? 'indeterminate' : 'unchecked';
880
+ }, /* @ts-ignore */
881
+ ...(ngDevMode ? [{ debugName: "dataState" }] : /* istanbul ignore next */ []));
882
+ onClick() {
883
+ this.ctx.toggleSelectAll();
884
+ }
885
+ onKeyDown(event) {
886
+ if (event.key === ' ' || event.key === 'Enter') {
887
+ event.preventDefault();
888
+ this.ctx.toggleSelectAll();
889
+ }
890
+ }
891
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableSelectAll, deps: [], target: i0.ɵɵFactoryTarget.Directive });
892
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForTableSelectAll, isStandalone: true, selector: "[forTableSelectAll]", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "checkbox", "tabindex": "0" }, listeners: { "click": "onClick()", "keydown": "onKeyDown($event)" }, properties: { "attr.aria-checked": "ariaChecked()", "attr.aria-label": "ariaLabel() || null", "attr.data-state": "dataState()" } }, exportAs: ["forTableSelectAll"], ngImport: i0 });
893
+ }
894
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableSelectAll, decorators: [{
895
+ type: Directive,
896
+ args: [{
897
+ selector: '[forTableSelectAll]',
898
+ exportAs: 'forTableSelectAll',
899
+ host: {
900
+ role: 'checkbox',
901
+ tabindex: '0',
902
+ '[attr.aria-checked]': 'ariaChecked()',
903
+ '[attr.aria-label]': 'ariaLabel() || null',
904
+ '[attr.data-state]': 'dataState()',
905
+ '(click)': 'onClick()',
906
+ '(keydown)': 'onKeyDown($event)',
907
+ },
908
+ }]
909
+ }], propDecorators: { ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
910
+
911
+ /**
912
+ * Turns a `[forTableHeaderCell]` into a sortable affordance that emits `aria-sort`
913
+ * and fires `sortChange` on activation (click, Enter, Space). The directive is
914
+ * **self-contained**: it owns only its own `direction` state and does NOT register
915
+ * with the table context or auto-reset sibling headers. The "one sorted column at a
916
+ * time" guarantee is the consumer's responsibility — hold a single sort descriptor
917
+ * signal and derive each header's `direction` from it. Apply this directive on the
918
+ * same element as `[forTableHeaderCell]`.
919
+ *
920
+ * When a `[forDraggable]` (column reorder) shares the same host cell, this directive
921
+ * yields its `tabindex` to the draggable's roving tab stop so the two never collide on
922
+ * the host attribute; `aria-sort` / `data-sorted` and click / keyboard activation stay
923
+ * on the cell.
924
+ *
925
+ * Cycle (default `firstClickDirection='ascending'`): `none → ascending → descending → none`.
926
+ * With `disableClear`: `none → ascending → descending → ascending`.
927
+ *
928
+ * `firstClickDirection='descending'` flips the entry pole, so a freshly activated
929
+ * column starts descending: `none → descending → ascending → none` (and with
930
+ * `disableClear`: `none → descending → ascending → descending`) — the descending-first
931
+ * behavior used by single-always-active sort descriptors.
932
+ */
933
+ class ForTableSortHeader {
934
+ ctx = injectTableContext('ForTableSortHeader');
935
+ #draggable = inject(FOR_DRAGGABLE_CONTEXT, { self: true, optional: true });
936
+ /** Column identity included in the `sortChange` payload. */
937
+ column = input.required(/* @ts-ignore */
938
+ ...(ngDevMode ? [{ debugName: "column" }] : /* istanbul ignore next */ []));
939
+ /**
940
+ * Current sort direction. Acts as both the controlled value and the initial value.
941
+ * Its implicit `directionChange` output fires on every internal update (via
942
+ * `[(direction)]`). `sortChange` is the primary column-aware event consumers bind:
943
+ * it carries a `{ column, direction }` descriptor and fires on every activation,
944
+ * regardless of whether the consumer uses two-way binding.
945
+ */
946
+ direction = model('none', /* @ts-ignore */
947
+ ...(ngDevMode ? [{ debugName: "direction" }] : /* istanbul ignore next */ []));
948
+ /**
949
+ * When `true`, the cycle skips the `'none'` step: `ascending → descending → ascending`.
950
+ * Useful when clearing the sort is not allowed.
951
+ */
952
+ disableClear = input(false, { ...(ngDevMode ? { debugName: "disableClear" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
953
+ /**
954
+ * Direction a previously-unsorted column enters on its first activation (the
955
+ * `'none' → ?` step of the cycle). Defaults to `'ascending'`. Set to `'descending'`
956
+ * for descending-first columns. The toggle between the two sorted directions and the
957
+ * optional `'none'` step (`disableClear`) are unchanged.
958
+ */
959
+ firstClickDirection = input('ascending', /* @ts-ignore */
960
+ ...(ngDevMode ? [{ debugName: "firstClickDirection" }] : /* istanbul ignore next */ []));
961
+ /**
962
+ * When `false`, the header is fully inert: no `tabindex`, no `aria-sort`, and click /
963
+ * keyboard handlers are no-ops. Defaults to `true`.
964
+ */
965
+ sortable = input(true, { ...(ngDevMode ? { debugName: "sortable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
966
+ /**
967
+ * Fires on every activation with the column identity and the new direction.
968
+ * Consumers bind this to update their own sort descriptor and reorder rows.
969
+ */
970
+ sortChange = output();
971
+ /**
972
+ * Truthy-only `aria-sort` / `data-sorted` value: `'ascending'` or `'descending'`
973
+ * while sorted, `null` (absent) when `direction` is `'none'` or `sortable` is `false`.
974
+ */
975
+ activeDirection = computed(() => this.sortable() && this.direction() !== 'none' ? this.direction() : null, /* @ts-ignore */
976
+ ...(ngDevMode ? [{ debugName: "activeDirection" }] : /* istanbul ignore next */ []));
977
+ tabindex = computed(() => !this.#draggable && this.sortable() ? '0' : null, /* @ts-ignore */
978
+ ...(ngDevMode ? [{ debugName: "tabindex" }] : /* istanbul ignore next */ []));
979
+ /** Activates the sort: computes the next direction, updates the model, and emits `sortChange`. */
980
+ activate() {
981
+ if (!this.sortable())
982
+ return;
983
+ const next = this.#next(this.direction());
984
+ this.direction.set(next);
985
+ this.sortChange.emit({ column: this.column(), direction: next });
986
+ }
987
+ /** Handles Enter and Space keyboard events, forwarding to `activate()`. */
988
+ onKeyDown(event) {
989
+ if (event.key === 'Enter' || event.key === ' ') {
990
+ event.preventDefault();
991
+ this.activate();
992
+ }
993
+ }
994
+ #next(current) {
995
+ const first = this.firstClickDirection();
996
+ const second = first === 'ascending' ? 'descending' : 'ascending';
997
+ if (current === 'none')
998
+ return first;
999
+ if (current === first)
1000
+ return second;
1001
+ return this.disableClear() ? first : 'none';
1002
+ }
1003
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableSortHeader, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1004
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForTableSortHeader, isStandalone: true, selector: "[forTableSortHeader]", inputs: { column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, disableClear: { classPropertyName: "disableClear", publicName: "disableClear", isSignal: true, isRequired: false, transformFunction: null }, firstClickDirection: { classPropertyName: "firstClickDirection", publicName: "firstClickDirection", isSignal: true, isRequired: false, transformFunction: null }, sortable: { classPropertyName: "sortable", publicName: "sortable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { direction: "directionChange", sortChange: "sortChange" }, host: { listeners: { "click": "activate()", "keydown": "onKeyDown($event)" }, properties: { "attr.aria-sort": "activeDirection()", "attr.data-sorted": "activeDirection()", "attr.tabindex": "tabindex()" } }, exportAs: ["forTableSortHeader"], ngImport: i0 });
1005
+ }
1006
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableSortHeader, decorators: [{
1007
+ type: Directive,
1008
+ args: [{
1009
+ selector: '[forTableSortHeader]',
1010
+ exportAs: 'forTableSortHeader',
1011
+ host: {
1012
+ '[attr.aria-sort]': 'activeDirection()',
1013
+ '[attr.data-sorted]': 'activeDirection()',
1014
+ '[attr.tabindex]': 'tabindex()',
1015
+ '(click)': 'activate()',
1016
+ '(keydown)': 'onKeyDown($event)',
1017
+ },
1018
+ }]
1019
+ }], propDecorators: { column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], direction: [{ type: i0.Input, args: [{ isSignal: true, alias: "direction", required: false }] }, { type: i0.Output, args: ["directionChange"] }], disableClear: [{ type: i0.Input, args: [{ isSignal: true, alias: "disableClear", required: false }] }], firstClickDirection: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstClickDirection", required: false }] }], sortable: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortable", required: false }] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }] } });
1020
+
1021
+ /**
1022
+ * Turns a focusable element inside a `[forTableHeaderCell]` into a column-resize
1023
+ * handle. Supports pointer drag (with a dead-zone so a plain click is a no-op) and
1024
+ * `ArrowLeft` / `ArrowRight` keyboard resize, both constrained to `[min, max]`.
1025
+ *
1026
+ * On every change it publishes the resolved width as the CSS custom property
1027
+ * `--for-table-col-<column>-width` on the table root, so the consumer can apply it
1028
+ * to their layout (`grid-template-columns` in `<div>` mode, a `<col>` / cell width
1029
+ * in native `<table>` mode). **It never lays out columns itself and never resizes
1030
+ * data** — it owns the affordance, accessibility, and the published width only.
1031
+ *
1032
+ * Reflects `data-resizing` (empty string) while a pointer drag is active. Carries
1033
+ * `role="separator"` with `aria-orientation="vertical"` and live `aria-value*`,
1034
+ * mirroring `[forPaneResizer]`. The consumer supplies `aria-label`. Before the first
1035
+ * gesture, `aria-valuenow` falls back to the header-cell width measured once on mount
1036
+ * (browser-only), so a separator with no `[width]` is never announced without a
1037
+ * current value; an explicit `[width]` always takes precedence.
1038
+ *
1039
+ * @example
1040
+ * ```html
1041
+ * <th forTableHeaderCell name="name">
1042
+ * Name
1043
+ * <button forTableColumnResizer column="name" [(width)]="nameWidth"
1044
+ * aria-label="Resize Name column"></button>
1045
+ * </th>
1046
+ * ```
1047
+ */
1048
+ class ForTableColumnResizer {
1049
+ ctx = injectTableContext('ForTableColumnResizer');
1050
+ #host = inject(ElementRef).nativeElement;
1051
+ /** Column identity; included in the `resizeCommit` payload and the published CSS var name. */
1052
+ column = input.required(/* @ts-ignore */
1053
+ ...(ngDevMode ? [{ debugName: "column" }] : /* istanbul ignore next */ []));
1054
+ /**
1055
+ * Current column width in pixels. Two-way bindable via `[(width)]`. Acts as both
1056
+ * the controlled value and the base a pointer drag / arrow step is applied to.
1057
+ * When unset, the base for the first gesture is measured from the header cell.
1058
+ * Its implicit `widthChange` fires on every live update (drag tick, arrow press);
1059
+ * `resizeCommit` is the distinct column-aware gesture-end event.
1060
+ */
1061
+ width = model(/* @ts-ignore */
1062
+ ...(ngDevMode ? [undefined, { debugName: "width" }] : /* istanbul ignore next */ []));
1063
+ /** Minimum width in pixels. Default `0`. */
1064
+ min = input(0, /* @ts-ignore */
1065
+ ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
1066
+ /** Maximum width in pixels. Default `Infinity` (no upper bound). */
1067
+ max = input(Infinity, /* @ts-ignore */
1068
+ ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
1069
+ /** Pixels applied per `ArrowLeft` / `ArrowRight` press. Default `10`. */
1070
+ step = input(10, /* @ts-ignore */
1071
+ ...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
1072
+ /**
1073
+ * Fires once per resize gesture — at pointer-up after a drag, and on every arrow
1074
+ * press — with the column identity and its committed width. Bind it to persist
1075
+ * the width; live updates during a drag come through `[(width)]` / `widthChange`.
1076
+ */
1077
+ resizeCommit = output();
1078
+ /** `aria-valuemax`, omitted when `max` is non-finite (the default unbounded case). */
1079
+ ariaValueMax = computed(() => Number.isFinite(this.max()) ? this.max() : null, /* @ts-ignore */
1080
+ ...(ngDevMode ? [{ debugName: "ariaValueMax" }] : /* istanbul ignore next */ []));
1081
+ #resizing = signal(false, /* @ts-ignore */
1082
+ ...(ngDevMode ? [{ debugName: "#resizing" }] : /* istanbul ignore next */ []));
1083
+ /** Whether a pointer drag is currently active (drives `data-resizing`). */
1084
+ resizing = this.#resizing.asReadonly();
1085
+ #measuredWidth = signal(null, /* @ts-ignore */
1086
+ ...(ngDevMode ? [{ debugName: "#measuredWidth" }] : /* istanbul ignore next */ []));
1087
+ /**
1088
+ * Header-cell width measured once on mount (browser-only). Backs `aria-valuenow`
1089
+ * before any explicit `[width]` so the focusable separator never ships without a
1090
+ * current value on the measured-fallback path; an explicit `[width]` still wins.
1091
+ */
1092
+ measuredWidth = this.#measuredWidth.asReadonly();
1093
+ #disposePointer = null;
1094
+ constructor() {
1095
+ inject(DestroyRef).onDestroy(() => this.#disposePointer?.());
1096
+ effect(() => {
1097
+ const w = this.width();
1098
+ if (w != null) {
1099
+ this.ctx.setColumnWidth(this.column(), w);
1100
+ }
1101
+ });
1102
+ if (isPlatformBrowser(inject(PLATFORM_ID))) {
1103
+ afterNextRender(() => this.#measuredWidth.set(this.#measureBaseWidth()));
1104
+ }
1105
+ }
1106
+ onPointerDown(event) {
1107
+ if (event.button !== 0) {
1108
+ return;
1109
+ }
1110
+ event.preventDefault();
1111
+ event.stopPropagation();
1112
+ const ltr = this.ctx.dir() !== 'rtl';
1113
+ this.#disposePointer = startPointerResize(event, {
1114
+ host: this.#host,
1115
+ axis: 'x',
1116
+ startValue: this.width() ?? this.#measureBaseWidth(),
1117
+ invert: !ltr,
1118
+ constrain: (n) => clampToRange(n, this.min(), this.max()),
1119
+ onResize: (w) => {
1120
+ this.#resizing.set(true);
1121
+ this.width.set(w);
1122
+ },
1123
+ onCommit: (w) => {
1124
+ this.#resizing.set(false);
1125
+ this.resizeCommit.emit({ column: this.column(), width: w });
1126
+ },
1127
+ });
1128
+ }
1129
+ onKeyDown(event) {
1130
+ const ltr = this.ctx.dir() !== 'rtl';
1131
+ const base = this.width() ?? this.#measureBaseWidth();
1132
+ let next;
1133
+ if (event.key === 'ArrowRight') {
1134
+ next = clampToRange(base + (ltr ? this.step() : -this.step()), this.min(), this.max());
1135
+ }
1136
+ else if (event.key === 'ArrowLeft') {
1137
+ next = clampToRange(base + (ltr ? -this.step() : this.step()), this.min(), this.max());
1138
+ }
1139
+ else {
1140
+ return;
1141
+ }
1142
+ event.preventDefault();
1143
+ if (next === this.width()) {
1144
+ return;
1145
+ }
1146
+ this.width.set(next);
1147
+ this.resizeCommit.emit({ column: this.column(), width: next });
1148
+ }
1149
+ onClick(event) {
1150
+ event.stopPropagation();
1151
+ }
1152
+ #measureBaseWidth() {
1153
+ const cell = this.#host.closest('[forTableHeaderCell]');
1154
+ return (cell ?? this.#host).getBoundingClientRect().width;
1155
+ }
1156
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableColumnResizer, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1157
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForTableColumnResizer, isStandalone: true, selector: "[forTableColumnResizer]", inputs: { column: { classPropertyName: "column", publicName: "column", 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 }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { width: "widthChange", resizeCommit: "resizeCommit" }, host: { attributes: { "role": "separator", "aria-orientation": "vertical" }, listeners: { "pointerdown": "onPointerDown($event)", "keydown": "onKeyDown($event)", "click": "onClick($event)" }, properties: { "attr.tabindex": "\"0\"", "attr.aria-valuenow": "width() ?? measuredWidth() ?? null", "attr.aria-valuemin": "min()", "attr.aria-valuemax": "ariaValueMax()", "attr.data-resizing": "resizing() ? \"\" : null" } }, exportAs: ["forTableColumnResizer"], ngImport: i0 });
1158
+ }
1159
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableColumnResizer, decorators: [{
1160
+ type: Directive,
1161
+ args: [{
1162
+ selector: '[forTableColumnResizer]',
1163
+ exportAs: 'forTableColumnResizer',
1164
+ host: {
1165
+ role: 'separator',
1166
+ 'aria-orientation': 'vertical',
1167
+ '[attr.tabindex]': '"0"',
1168
+ '[attr.aria-valuenow]': 'width() ?? measuredWidth() ?? null',
1169
+ '[attr.aria-valuemin]': 'min()',
1170
+ '[attr.aria-valuemax]': 'ariaValueMax()',
1171
+ '[attr.data-resizing]': 'resizing() ? "" : null',
1172
+ '(pointerdown)': 'onPointerDown($event)',
1173
+ '(keydown)': 'onKeyDown($event)',
1174
+ '(click)': 'onClick($event)',
1175
+ },
1176
+ }]
1177
+ }], ctorParameters: () => [], propDecorators: { column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }, { type: i0.Output, args: ["widthChange"] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], resizeCommit: [{ type: i0.Output, args: ["resizeCommit"] }] } });
1178
+
1179
+ /**
1180
+ * Opt-in **column reordering** for `ForTable`, composed over the drag-drop primitive.
1181
+ *
1182
+ * Apply on the `[forTableHeaderRow]` element. It wraps `[forDropList]` (via
1183
+ * `hostDirectives`) so the header cells become a reorderable list, then translates
1184
+ * drag-drop's generic drop into the table-friendly `columnReorder` output. Mark each
1185
+ * `[forTableHeaderCell]` as `[forDraggable]` with `[dragData]` set to the column name.
1186
+ * On a committed drop (pointer or keyboard) it emits the previous / new index and the
1187
+ * new column-name order; the consumer applies it to their own column array. **It never
1188
+ * reorders columns itself** (BYO-data).
1189
+ *
1190
+ * The wrapped list defaults to `orientation="horizontal"` (a column reorder is always along
1191
+ * the row axis), so no `orientation` binding is needed. Bind `orientation="vertical"` to
1192
+ * override for the rare case.
1193
+ *
1194
+ * @example
1195
+ * ```html
1196
+ * <div forTableHeaderRow forTableColumnReorder (columnReorder)="columns.set($event.columns)">
1197
+ * @for (col of columns(); track col) {
1198
+ * <div forTableHeaderCell [name]="col" forDraggable [dragData]="col">{{ col }}</div>
1199
+ * }
1200
+ * </div>
1201
+ * ```
1202
+ */
1203
+ class ForTableColumnReorder {
1204
+ ctx = injectTableContext('ForTableColumnReorder');
1205
+ #list = inject(ForDropList);
1206
+ /**
1207
+ * Fires once per committed reorder gesture (pointer drop or keyboard drop) with the
1208
+ * previous / new column index and the full column-name order after the move.
1209
+ */
1210
+ columnReorder = output();
1211
+ constructor() {
1212
+ const sub = this.#list.dragDrop.subscribe((event) => this.#emit(event));
1213
+ inject(DestroyRef).onDestroy(() => sub.unsubscribe());
1214
+ }
1215
+ #emit(event) {
1216
+ const names = this.#list.items().map((handle) => String(handle.data()));
1217
+ const columns = moveItemInArray(names, event.previousIndex, event.currentIndex);
1218
+ this.columnReorder.emit({ from: event.previousIndex, to: event.currentIndex, columns });
1219
+ }
1220
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableColumnReorder, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1221
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForTableColumnReorder, isStandalone: true, selector: "[forTableColumnReorder]", outputs: { columnReorder: "columnReorder" }, providers: [{ provide: FOR_DROP_LIST_DEFAULT_ORIENTATION, useValue: 'horizontal' }], exportAs: ["forTableColumnReorder"], hostDirectives: [{ directive: i1.ForDropList, inputs: ["orientation", "orientation", "dir", "dir", "disabled", "disabled", "autoScroll", "autoScroll", "animateReorder", "animateReorder", "liveSort", "liveSort", "boundary", "boundary", "lockAxis", "lockAxis"] }], ngImport: i0 });
1222
+ }
1223
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableColumnReorder, decorators: [{
1224
+ type: Directive,
1225
+ args: [{
1226
+ selector: '[forTableColumnReorder]',
1227
+ exportAs: 'forTableColumnReorder',
1228
+ providers: [{ provide: FOR_DROP_LIST_DEFAULT_ORIENTATION, useValue: 'horizontal' }],
1229
+ hostDirectives: [
1230
+ {
1231
+ directive: ForDropList,
1232
+ inputs: [
1233
+ 'orientation',
1234
+ 'dir',
1235
+ 'disabled',
1236
+ 'autoScroll',
1237
+ 'animateReorder',
1238
+ 'liveSort',
1239
+ 'boundary',
1240
+ 'lockAxis',
1241
+ ],
1242
+ },
1243
+ ],
1244
+ }]
1245
+ }], ctorParameters: () => [], propDecorators: { columnReorder: [{ type: i0.Output, args: ["columnReorder"] }] } });
1246
+
1247
+ /**
1248
+ * Translates a drop-list's window-relative `previousIndex` / `currentIndex` into
1249
+ * absolute dataset indices, so a virtualized table's consumer can apply
1250
+ * `moveItemInArray` over the **full** row array. `windowIndices` holds the
1251
+ * absolute `virtualIndex` of every rendered draggable row, in DOM (ascending)
1252
+ * order. Thin table-facing wrapper over the shared
1253
+ * {@link translateWindowReorder} helper, which owns the post-removal index math.
1254
+ */
1255
+ function translateRowReorderIndices(windowIndices, previousIndex, currentIndex) {
1256
+ return translateWindowReorder(windowIndices, previousIndex, currentIndex);
1257
+ }
1258
+ /**
1259
+ * Opt-in **row reordering** for `ForTable`, composed over the drag-drop primitive.
1260
+ *
1261
+ * Apply on the rowgroup element that wraps the data rows (`<div role="rowgroup">` in
1262
+ * `<div>` mode, `<tbody>` in native `<table>` mode). It wraps `[forDropList]` (via
1263
+ * `hostDirectives`, vertical by default) so the rows become a reorderable list, then
1264
+ * translates drag-drop's generic drop into the table-friendly `rowReorder` output. Mark
1265
+ * each `[forTableRow]` as `[forDraggable]` with a `[dragData]`. On a committed drop it
1266
+ * emits the previous / new index; the consumer applies the move to their own row array
1267
+ * (e.g. `moveItemInArray`). **It never reorders rows itself** (BYO-data).
1268
+ *
1269
+ * Under `[forTableVirtualized]`, `rowReorder` emits **absolute** dataset indices
1270
+ * (derived from each rendered row's `virtualIndex`) so `moveItemInArray` over the full
1271
+ * array moves the right row. Pointer drag works within the rendered window and reaches
1272
+ * rows beyond it via auto-scroll (the lifted row is kept mounted for the drag); keyboard
1273
+ * reorder steps the target across the entire dataset (scrolling unmounted rows into view).
1274
+ * A non-virtualized table emits rendered-order indices unchanged.
1275
+ *
1276
+ * @example
1277
+ * ```html
1278
+ * <div role="rowgroup" forTableRowReorder (rowReorder)="onReorder($event)">
1279
+ * @for (row of rows(); track row.id) {
1280
+ * <div forTableRow [value]="row.id" forDraggable [dragData]="row.id">…</div>
1281
+ * }
1282
+ * </div>
1283
+ * ```
1284
+ */
1285
+ class ForTableRowReorder {
1286
+ ctx = injectTableContext('ForTableRowReorder');
1287
+ #list = inject(ForDropList);
1288
+ #host = inject(ElementRef).nativeElement;
1289
+ #document = inject(DOCUMENT);
1290
+ #isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
1291
+ #announcer = inject(LiveAnnouncer);
1292
+ #dragDefaults = inject(FOR_DRAG_DROP_DEFAULTS);
1293
+ #kbLiftedHost = null;
1294
+ #kbFrom = 0;
1295
+ #kbTarget = 0;
1296
+ /** Fires once per committed reorder gesture with the previous / new row index. */
1297
+ rowReorder = output();
1298
+ constructor() {
1299
+ const destroyRef = inject(DestroyRef);
1300
+ const sub = this.#list.dragDrop.subscribe((event) => this.rowReorder.emit(this.#resolveDescriptor(event)));
1301
+ destroyRef.onDestroy(() => sub.unsubscribe());
1302
+ if (this.#isBrowser) {
1303
+ const onPointerDown = (event) => this.#pinFromPointer(event);
1304
+ const onPointerEnd = () => this.ctx.setReorderingRow(null);
1305
+ this.#host.addEventListener('pointerdown', onPointerDown, { capture: true });
1306
+ this.#document.addEventListener('pointerup', onPointerEnd, { capture: true });
1307
+ this.#document.addEventListener('pointercancel', onPointerEnd, { capture: true });
1308
+ createKeyboardDragMediator({
1309
+ host: this.#host,
1310
+ isBrowser: this.#isBrowser,
1311
+ destroyRef,
1312
+ isLifted: () => this.#kbLiftedHost !== null,
1313
+ onIdleKeydown: (event) => this.#onIdleKeydown(event),
1314
+ onLiftedKeydown: (event) => this.#onLiftedKeydown(event),
1315
+ onFocusOut: (event) => {
1316
+ if (this.#kbLiftedHost !== null && event.target === this.#kbLiftedHost) {
1317
+ this.#kbCancel();
1318
+ }
1319
+ },
1320
+ });
1321
+ destroyRef.onDestroy(() => {
1322
+ this.#host.removeEventListener('pointerdown', onPointerDown, { capture: true });
1323
+ this.#document.removeEventListener('pointerup', onPointerEnd, { capture: true });
1324
+ this.#document.removeEventListener('pointercancel', onPointerEnd, { capture: true });
1325
+ if (this.#kbLiftedHost !== null) {
1326
+ this.#kbCancel();
1327
+ }
1328
+ this.ctx.setReorderingRow(null);
1329
+ });
1330
+ }
1331
+ }
1332
+ #onLiftedKeydown(event) {
1333
+ if (this.ctx.virtualRowNavigation() === null) {
1334
+ return;
1335
+ }
1336
+ const key = event.key;
1337
+ if (key === ' ' || key === 'Enter') {
1338
+ event.preventDefault();
1339
+ event.stopPropagation();
1340
+ this.#kbCommit();
1341
+ }
1342
+ else if (key === 'Escape') {
1343
+ event.preventDefault();
1344
+ event.stopPropagation();
1345
+ this.#kbCancel();
1346
+ }
1347
+ else if (key === 'ArrowDown') {
1348
+ this.#setTarget(this.#kbTarget + 1);
1349
+ event.preventDefault();
1350
+ event.stopPropagation();
1351
+ this.#kbApplyTarget();
1352
+ }
1353
+ else if (key === 'ArrowUp') {
1354
+ this.#setTarget(this.#kbTarget - 1);
1355
+ event.preventDefault();
1356
+ event.stopPropagation();
1357
+ this.#kbApplyTarget();
1358
+ }
1359
+ else if (key === 'Home') {
1360
+ this.#setTarget(0);
1361
+ event.preventDefault();
1362
+ event.stopPropagation();
1363
+ this.#kbApplyTarget();
1364
+ }
1365
+ else if (key === 'End') {
1366
+ this.#setTarget(this.#count() - 1);
1367
+ event.preventDefault();
1368
+ event.stopPropagation();
1369
+ this.#kbApplyTarget();
1370
+ }
1371
+ else if (key === 'PageDown') {
1372
+ this.#setTarget(this.#kbTarget + this.#page());
1373
+ event.preventDefault();
1374
+ event.stopPropagation();
1375
+ this.#kbApplyTarget();
1376
+ }
1377
+ else if (key === 'PageUp') {
1378
+ this.#setTarget(this.#kbTarget - this.#page());
1379
+ event.preventDefault();
1380
+ event.stopPropagation();
1381
+ this.#kbApplyTarget();
1382
+ }
1383
+ }
1384
+ #onIdleKeydown(event) {
1385
+ if (this.ctx.virtualRowNavigation() === null) {
1386
+ return;
1387
+ }
1388
+ const key = event.key;
1389
+ if (key !== ' ' && key !== 'Enter') {
1390
+ return;
1391
+ }
1392
+ const draggable = this.#list.items().find((h) => h.host === event.target);
1393
+ if (draggable === undefined || draggable.disabled()) {
1394
+ return;
1395
+ }
1396
+ const handle = this.ctx.rows().find((r) => r.host === event.target);
1397
+ const vi = handle?.virtualIndex() ?? null;
1398
+ if (vi === null) {
1399
+ return;
1400
+ }
1401
+ event.preventDefault();
1402
+ event.stopPropagation();
1403
+ this.#kbLift(event.target, vi);
1404
+ }
1405
+ #kbLift(host, vi) {
1406
+ this.#kbLiftedHost = host;
1407
+ this.#kbFrom = vi;
1408
+ this.#kbTarget = vi;
1409
+ this.ctx.setReorderingRow(vi);
1410
+ const total = this.#count();
1411
+ this.#announcer.announce(this.#dragDefaults.announceLift(this.#label(), vi + 1, total), 'assertive');
1412
+ }
1413
+ #kbApplyTarget() {
1414
+ this.ctx.virtualRowNavigation()?.scrollToRow(this.#kbTarget);
1415
+ this.#announcer.announce(this.#dragDefaults.announceMove(this.#label(), this.#kbTarget + 1, this.#count()), 'polite');
1416
+ }
1417
+ #kbCommit() {
1418
+ this.rowReorder.emit({ from: this.#kbFrom, to: this.#kbTarget });
1419
+ this.#announcer.announce(this.#dragDefaults.announceDrop(this.#label(), this.#kbTarget + 1, this.#count()), 'assertive');
1420
+ this.#kbTeardown();
1421
+ }
1422
+ #kbCancel() {
1423
+ this.#announcer.announce(this.#dragDefaults.announceCancel(this.#label()), 'assertive');
1424
+ this.#kbTeardown();
1425
+ }
1426
+ #kbTeardown() {
1427
+ this.#kbLiftedHost = null;
1428
+ this.#kbFrom = 0;
1429
+ this.#kbTarget = 0;
1430
+ this.ctx.setReorderingRow(null);
1431
+ }
1432
+ #count() {
1433
+ return this.ctx.rowCount() ?? this.#list.items().length;
1434
+ }
1435
+ #page() {
1436
+ return Math.max(1, this.#list.items().length);
1437
+ }
1438
+ #label() {
1439
+ return (this.#kbLiftedHost?.textContent ?? '').trim();
1440
+ }
1441
+ #setTarget(value) {
1442
+ this.#kbTarget = Math.max(0, Math.min(this.#count() - 1, value));
1443
+ }
1444
+ #pinFromPointer(event) {
1445
+ const target = event.target;
1446
+ if (!(target instanceof HTMLElement)) {
1447
+ return;
1448
+ }
1449
+ const rowHost = target.closest('[forTableRow]');
1450
+ if (rowHost === null) {
1451
+ return;
1452
+ }
1453
+ const handle = this.ctx.rows().find((r) => r.host === rowHost);
1454
+ this.ctx.setReorderingRow(handle?.virtualIndex() ?? null);
1455
+ }
1456
+ #resolveDescriptor(event) {
1457
+ const fallback = {
1458
+ from: event.previousIndex,
1459
+ to: event.currentIndex,
1460
+ };
1461
+ if (event.container !== event.previousContainer) {
1462
+ return fallback;
1463
+ }
1464
+ const rowByHost = new Map(this.ctx.rows().map((r) => [r.host, r]));
1465
+ const windowIndices = [];
1466
+ for (const item of this.#list.items()) {
1467
+ const index = rowByHost.get(item.host)?.virtualIndex() ?? null;
1468
+ if (index === null) {
1469
+ return fallback;
1470
+ }
1471
+ windowIndices.push(index);
1472
+ }
1473
+ return translateRowReorderIndices(windowIndices, event.previousIndex, event.currentIndex);
1474
+ }
1475
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableRowReorder, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1476
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForTableRowReorder, isStandalone: true, selector: "[forTableRowReorder]", outputs: { rowReorder: "rowReorder" }, exportAs: ["forTableRowReorder"], hostDirectives: [{ directive: i1.ForDropList, inputs: ["dir", "dir", "disabled", "disabled", "autoScroll", "autoScroll", "animateReorder", "animateReorder", "liveSort", "liveSort", "boundary", "boundary", "lockAxis", "lockAxis"] }], ngImport: i0 });
1477
+ }
1478
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableRowReorder, decorators: [{
1479
+ type: Directive,
1480
+ args: [{
1481
+ selector: '[forTableRowReorder]',
1482
+ exportAs: 'forTableRowReorder',
1483
+ hostDirectives: [
1484
+ {
1485
+ directive: ForDropList,
1486
+ inputs: [
1487
+ 'dir',
1488
+ 'disabled',
1489
+ 'autoScroll',
1490
+ 'animateReorder',
1491
+ 'liveSort',
1492
+ 'boundary',
1493
+ 'lockAxis',
1494
+ ],
1495
+ },
1496
+ ],
1497
+ }]
1498
+ }], ctorParameters: () => [], propDecorators: { rowReorder: [{ type: i0.Output, args: ["rowReorder"] }] } });
1499
+
1500
+ const FALLBACK = {};
1501
+ const { token, provideDefaults } = createDefaults('FOR_TABLE_DEFAULTS', FALLBACK);
1502
+ /** Token holding the resolved table defaults for the current scope. */
1503
+ const FOR_TABLE_DEFAULTS = token;
1504
+ /**
1505
+ * Configures forty-cdk table defaults for this injector scope. Partial
1506
+ * overrides inherit unspecified keys from the parent scope (or library
1507
+ * defaults at the root).
1508
+ */
1509
+ function provideForTableDefaults(defaults = {}) {
1510
+ return provideDefaults(defaults);
1511
+ }
1512
+
1513
+ /**
1514
+ * Generated bundle index. Do not edit.
1515
+ */
1516
+
1517
+ export { FOR_TABLE_CONTEXT, FOR_TABLE_DEFAULTS, ForTable, ForTableCell, ForTableColumnReorder, ForTableColumnResizer, ForTableHeaderCell, ForTableHeaderRow, ForTableRow, ForTableRowReorder, ForTableRowSelector, ForTableSelectAll, ForTableSortHeader, provideForTableDefaults };
1518
+ //# sourceMappingURL=forty-cdk-table.mjs.map