@simplysm/angular 14.0.42 → 14.0.43

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 (241) hide show
  1. package/dist/controls/button/sd-additional-button.js +1 -1
  2. package/dist/controls/button/sd-anchor.js +1 -1
  3. package/dist/controls/button/sd-button.js +1 -1
  4. package/dist/controls/button/sd-modal-select-button.d.ts +1 -1
  5. package/dist/controls/button/sd-modal-select-button.d.ts.map +1 -1
  6. package/dist/controls/button/sd-modal-select-button.js +8 -6
  7. package/dist/controls/checkbox/sd-checkbox-group-item.js +1 -1
  8. package/dist/controls/checkbox/sd-checkbox-group.js +1 -1
  9. package/dist/controls/checkbox/sd-checkbox.js +1 -1
  10. package/dist/controls/checkbox/sd-switch.js +1 -1
  11. package/dist/controls/collapse/sd-collapse-icon.d.ts.map +1 -1
  12. package/dist/controls/collapse/sd-collapse-icon.js +3 -3
  13. package/dist/controls/collapse/sd-collapse.d.ts +1 -1
  14. package/dist/controls/collapse/sd-collapse.d.ts.map +1 -1
  15. package/dist/controls/collapse/sd-collapse.js +8 -7
  16. package/dist/controls/dropdown/sd-dropdown-popup.d.ts +2 -0
  17. package/dist/controls/dropdown/sd-dropdown-popup.d.ts.map +1 -1
  18. package/dist/controls/dropdown/sd-dropdown-popup.js +21 -8
  19. package/dist/controls/dropdown/sd-dropdown.d.ts.map +1 -1
  20. package/dist/controls/dropdown/sd-dropdown.js +5 -16
  21. package/dist/controls/form/sd-form.js +1 -1
  22. package/dist/controls/gap/sd-gap.js +5 -5
  23. package/dist/controls/input/sd-date-range-picker.d.ts +1 -0
  24. package/dist/controls/input/sd-date-range-picker.d.ts.map +1 -1
  25. package/dist/controls/input/sd-date-range-picker.js +14 -19
  26. package/dist/controls/input/sd-numpad.js +3 -3
  27. package/dist/controls/input/sd-range.js +1 -1
  28. package/dist/controls/input/sd-textarea.js +2 -2
  29. package/dist/controls/input/sd-textfield-type-handlers.js +2 -2
  30. package/dist/controls/input/sd-textfield.js +9 -9
  31. package/dist/controls/list/sd-list-item.d.ts +0 -1
  32. package/dist/controls/list/sd-list-item.d.ts.map +1 -1
  33. package/dist/controls/list/sd-list-item.js +38 -38
  34. package/dist/controls/list/sd-list.d.ts.map +1 -1
  35. package/dist/controls/list/sd-list.js +3 -3
  36. package/dist/controls/pagination/sd-pagination.js +1 -1
  37. package/dist/controls/select/sd-select-button.d.ts.map +1 -1
  38. package/dist/controls/select/sd-select-button.js +4 -8
  39. package/dist/controls/select/sd-select-item.d.ts +3 -3
  40. package/dist/controls/select/sd-select-item.d.ts.map +1 -1
  41. package/dist/controls/select/sd-select-item.js +27 -20
  42. package/dist/controls/select/sd-select.d.ts +5 -4
  43. package/dist/controls/select/sd-select.d.ts.map +1 -1
  44. package/dist/controls/select/sd-select.js +20 -17
  45. package/dist/controls/tab/sd-tab-item.js +1 -1
  46. package/dist/controls/tab/sd-tab.js +1 -1
  47. package/dist/core/app-structure/sd-app-structure.utils.js +1 -1
  48. package/dist/core/busy/sd-busy-container.d.ts.map +1 -1
  49. package/dist/core/busy/sd-busy-container.js +62 -68
  50. package/dist/core/busy/sd-busy.provider.js +1 -1
  51. package/dist/core/commands/sd-command.d.ts +13 -0
  52. package/dist/core/commands/sd-command.d.ts.map +1 -0
  53. package/dist/core/commands/sd-command.js +52 -0
  54. package/dist/core/error-handler/sd-global-error-handler.plugin.js +1 -1
  55. package/dist/core/events/sd-events.d.ts +1 -6
  56. package/dist/core/events/sd-events.d.ts.map +1 -1
  57. package/dist/core/events/sd-events.js +3 -11
  58. package/dist/core/events/sd-intersection.d.ts +13 -0
  59. package/dist/core/events/sd-intersection.d.ts.map +1 -0
  60. package/dist/core/events/sd-intersection.js +27 -0
  61. package/dist/core/events/sd-resize.d.ts +16 -0
  62. package/dist/core/events/sd-resize.d.ts.map +1 -0
  63. package/dist/core/events/{sd-resize-event.plugin.js → sd-resize.js} +18 -20
  64. package/dist/core/file-dialog/sd-file-dialog.provider.js +1 -1
  65. package/dist/core/modal/injectDragResize.js +9 -9
  66. package/dist/core/modal/injectFocusTrap.js +1 -1
  67. package/dist/core/modal/sd-confirm-modal.js +1 -1
  68. package/dist/core/modal/sd-modal.d.ts +8 -1
  69. package/dist/core/modal/sd-modal.d.ts.map +1 -1
  70. package/dist/core/modal/sd-modal.js +135 -39
  71. package/dist/core/modal/sd-modal.provider.js +5 -5
  72. package/dist/core/modal/sd-prompt-modal.js +1 -1
  73. package/dist/core/provideSdAngular.d.ts.map +1 -1
  74. package/dist/core/provideSdAngular.js +0 -10
  75. package/dist/core/ripple/setupRipple.js +5 -5
  76. package/dist/core/routing/menu-utils.js +1 -1
  77. package/dist/core/routing/sd-navigate-window.provider.js +1 -1
  78. package/dist/core/routing/sd-router-link.js +1 -1
  79. package/dist/core/selection/setupCumulateSelectedKeys.js +3 -3
  80. package/dist/core/setupModelHook.js +1 -1
  81. package/dist/core/toast/sd-toast-container.d.ts +1 -1
  82. package/dist/core/toast/sd-toast-container.js +3 -7
  83. package/dist/core/toast/sd-toast.d.ts.map +1 -1
  84. package/dist/core/toast/sd-toast.js +27 -24
  85. package/dist/core/toast/sd-toast.provider.js +6 -6
  86. package/dist/core/validation/setupInvalid.js +2 -1
  87. package/dist/data/data-detail/sd-data-detail.d.ts +2 -1
  88. package/dist/data/data-detail/sd-data-detail.d.ts.map +1 -1
  89. package/dist/data/data-detail/sd-data-detail.js +37 -33
  90. package/dist/data/data-select-button/sd-data-select-button.js +1 -1
  91. package/dist/data/data-sheet/sd-data-sheet.d.ts +2 -1
  92. package/dist/data/data-sheet/sd-data-sheet.d.ts.map +1 -1
  93. package/dist/data/data-sheet/sd-data-sheet.js +30 -22
  94. package/dist/data/kanban/sd-kanban-board.js +1 -1
  95. package/dist/data/kanban/sd-kanban-lane.js +1 -1
  96. package/dist/data/kanban/sd-kanban.d.ts +1 -1
  97. package/dist/data/kanban/sd-kanban.d.ts.map +1 -1
  98. package/dist/data/kanban/sd-kanban.js +4 -4
  99. package/dist/data/permission-table/sd-permission-table.js +6 -6
  100. package/dist/data/shared-data/sd-shared-data-select-button.js +1 -1
  101. package/dist/data/shared-data/sd-shared-data-select-list.js +3 -3
  102. package/dist/data/shared-data/sd-shared-data-select.d.ts +1 -1
  103. package/dist/data/shared-data/sd-shared-data-select.d.ts.map +1 -1
  104. package/dist/data/shared-data/sd-shared-data-select.js +29 -54
  105. package/dist/data/sheet/injectSheetDomAccessor.d.ts +6 -2
  106. package/dist/data/sheet/injectSheetDomAccessor.d.ts.map +1 -1
  107. package/dist/data/sheet/injectSheetDomAccessor.js +27 -22
  108. package/dist/data/sheet/injectSheetSelectRowIndicator.d.ts +10 -0
  109. package/dist/data/sheet/injectSheetSelectRowIndicator.d.ts.map +1 -0
  110. package/dist/data/sheet/injectSheetSelectRowIndicator.js +55 -0
  111. package/dist/data/sheet/sd-sheet-config.modal.d.ts +18 -19
  112. package/dist/data/sheet/sd-sheet-config.modal.d.ts.map +1 -1
  113. package/dist/data/sheet/sd-sheet-config.modal.js +357 -217
  114. package/dist/data/sheet/sd-sheet.d.ts +27 -13
  115. package/dist/data/sheet/sd-sheet.d.ts.map +1 -1
  116. package/dist/data/sheet/sd-sheet.js +609 -291
  117. package/dist/data/sheet/useSheetCellAgent.js +6 -6
  118. package/dist/data/sheet/useSheetCellStyling.d.ts.map +1 -1
  119. package/dist/data/sheet/useSheetCellStyling.js +4 -4
  120. package/dist/data/sheet/useSheetFocusIndicator.d.ts +7 -0
  121. package/dist/data/sheet/useSheetFocusIndicator.d.ts.map +1 -0
  122. package/dist/data/sheet/useSheetFocusIndicator.js +53 -0
  123. package/dist/data/sheet/useSheetLayoutEngine.js +1 -1
  124. package/dist/data/state-preset/sd-state-preset.d.ts.map +1 -1
  125. package/dist/data/state-preset/sd-state-preset.js +14 -14
  126. package/dist/features/address/sd-address-search.modal.js +3 -3
  127. package/dist/features/editor/sd-tiptap-editor.js +6 -6
  128. package/dist/features/editor/useTiptapToolbar.js +2 -2
  129. package/dist/features/theme/sd-theme-selector.js +1 -1
  130. package/dist/features/visual/sd-barcode.js +1 -1
  131. package/dist/features/visual/sd-calendar.js +2 -2
  132. package/dist/features/visual/sd-echarts.d.ts +2 -1
  133. package/dist/features/visual/sd-echarts.d.ts.map +1 -1
  134. package/dist/features/visual/sd-echarts.js +5 -3
  135. package/dist/features/visual/sd-label.js +1 -1
  136. package/dist/features/visual/sd-note.js +1 -1
  137. package/dist/features/visual/sd-progress.js +1 -1
  138. package/dist/index.d.ts +3 -5
  139. package/dist/index.d.ts.map +1 -1
  140. package/dist/index.js +3 -5
  141. package/dist/layout/base-container/sd-base-container.js +3 -3
  142. package/dist/layout/dock/sd-dock-container.js +1 -1
  143. package/dist/layout/dock/sd-dock.d.ts +3 -2
  144. package/dist/layout/dock/sd-dock.d.ts.map +1 -1
  145. package/dist/layout/dock/sd-dock.js +6 -4
  146. package/dist/layout/sidebar/sd-sidebar-container.js +5 -5
  147. package/dist/layout/sidebar/sd-sidebar-menu.d.ts.map +1 -1
  148. package/dist/layout/sidebar/sd-sidebar-menu.js +7 -6
  149. package/dist/layout/sidebar/sd-sidebar-user.js +1 -1
  150. package/dist/layout/sidebar/sd-sidebar.js +1 -1
  151. package/dist/layout/topbar/sd-topbar-container.js +1 -1
  152. package/dist/layout/topbar/sd-topbar-menu.d.ts.map +1 -1
  153. package/dist/layout/topbar/sd-topbar-menu.js +7 -7
  154. package/dist/layout/topbar/sd-topbar-user.js +1 -1
  155. package/dist/layout/topbar/sd-topbar.js +3 -3
  156. package/package.json +5 -5
  157. package/src/controls/button/sd-modal-select-button.ts +7 -2
  158. package/src/controls/collapse/sd-collapse-icon.ts +2 -2
  159. package/src/controls/collapse/sd-collapse.ts +4 -5
  160. package/src/controls/dropdown/sd-dropdown-popup.ts +15 -2
  161. package/src/controls/dropdown/sd-dropdown.ts +4 -16
  162. package/src/controls/gap/sd-gap.ts +4 -4
  163. package/src/controls/input/sd-date-range-picker.ts +13 -16
  164. package/src/controls/input/sd-numpad.ts +1 -1
  165. package/src/controls/input/sd-textarea.ts +1 -1
  166. package/src/controls/input/sd-textfield-type-handlers.ts +2 -2
  167. package/src/controls/input/sd-textfield.ts +6 -6
  168. package/src/controls/list/sd-list-item.ts +40 -26
  169. package/src/controls/list/sd-list.ts +2 -1
  170. package/src/controls/select/sd-select-button.ts +8 -11
  171. package/src/controls/select/sd-select-item.ts +35 -27
  172. package/src/controls/select/sd-select.ts +111 -41
  173. package/src/core/app-structure/sd-app-structure.utils.ts +1 -1
  174. package/src/core/busy/sd-busy-container.ts +235 -123
  175. package/src/core/busy/sd-busy.provider.ts +1 -1
  176. package/src/core/commands/sd-command.ts +46 -0
  177. package/src/core/error-handler/sd-global-error-handler.plugin.ts +1 -1
  178. package/src/core/events/sd-events.ts +1 -12
  179. package/src/core/events/sd-intersection.ts +29 -0
  180. package/src/core/events/{sd-resize-event.plugin.ts → sd-resize.ts} +17 -24
  181. package/src/core/file-dialog/sd-file-dialog.provider.ts +1 -1
  182. package/src/core/modal/injectDragResize.ts +9 -9
  183. package/src/core/modal/injectFocusTrap.ts +1 -1
  184. package/src/core/modal/sd-modal.provider.ts +5 -5
  185. package/src/core/modal/sd-modal.ts +119 -41
  186. package/src/core/provideSdAngular.ts +0 -10
  187. package/src/core/ripple/setupRipple.ts +5 -5
  188. package/src/core/routing/menu-utils.ts +1 -1
  189. package/src/core/routing/sd-navigate-window.provider.ts +1 -1
  190. package/src/core/routing/sd-router-link.ts +1 -1
  191. package/src/core/selection/setupCumulateSelectedKeys.ts +3 -3
  192. package/src/core/setupModelHook.ts +1 -1
  193. package/src/core/toast/sd-toast-container.ts +1 -1
  194. package/src/core/toast/sd-toast.provider.ts +6 -6
  195. package/src/core/toast/sd-toast.ts +25 -22
  196. package/src/core/validation/setupInvalid.ts +2 -1
  197. package/src/data/data-detail/sd-data-detail.ts +15 -11
  198. package/src/data/data-sheet/sd-data-sheet.ts +13 -15
  199. package/src/data/kanban/sd-kanban.ts +4 -5
  200. package/src/data/permission-table/sd-permission-table.ts +4 -4
  201. package/src/data/shared-data/sd-shared-data-select-list.ts +1 -1
  202. package/src/data/shared-data/sd-shared-data-select.ts +8 -27
  203. package/src/data/sheet/injectSheetDomAccessor.ts +37 -25
  204. package/src/data/sheet/injectSheetSelectRowIndicator.ts +69 -0
  205. package/src/data/sheet/sd-sheet-config.modal.ts +215 -191
  206. package/src/data/sheet/sd-sheet.ts +529 -209
  207. package/src/data/sheet/useSheetCellAgent.ts +8 -8
  208. package/src/data/sheet/useSheetCellStyling.ts +4 -8
  209. package/src/data/sheet/useSheetFocusIndicator.ts +66 -0
  210. package/src/data/sheet/useSheetLayoutEngine.ts +1 -1
  211. package/src/data/state-preset/sd-state-preset.ts +26 -13
  212. package/src/features/address/sd-address-search.modal.ts +1 -1
  213. package/src/features/editor/sd-tiptap-editor.ts +4 -4
  214. package/src/features/editor/useTiptapToolbar.ts +2 -2
  215. package/src/features/visual/sd-calendar.ts +1 -1
  216. package/src/features/visual/sd-echarts.ts +2 -0
  217. package/src/index.ts +4 -6
  218. package/src/layout/base-container/sd-base-container.ts +1 -1
  219. package/src/layout/dock/sd-dock.ts +3 -2
  220. package/src/layout/sidebar/sd-sidebar-container.ts +2 -2
  221. package/src/layout/sidebar/sd-sidebar-menu.ts +1 -3
  222. package/src/layout/topbar/sd-topbar-menu.ts +11 -15
  223. package/src/layout/topbar/sd-topbar.ts +1 -1
  224. package/dist/core/commands/sd-insert-command-event.plugin.d.ts +0 -11
  225. package/dist/core/commands/sd-insert-command-event.plugin.d.ts.map +0 -1
  226. package/dist/core/commands/sd-insert-command-event.plugin.js +0 -35
  227. package/dist/core/commands/sd-refresh-command-event.plugin.d.ts +0 -11
  228. package/dist/core/commands/sd-refresh-command-event.plugin.d.ts.map +0 -1
  229. package/dist/core/commands/sd-refresh-command-event.plugin.js +0 -38
  230. package/dist/core/commands/sd-save-command-event.plugin.d.ts +0 -11
  231. package/dist/core/commands/sd-save-command-event.plugin.d.ts.map +0 -1
  232. package/dist/core/commands/sd-save-command-event.plugin.js +0 -38
  233. package/dist/core/events/sd-intersection-event.plugin.d.ts +0 -13
  234. package/dist/core/events/sd-intersection-event.plugin.d.ts.map +0 -1
  235. package/dist/core/events/sd-intersection-event.plugin.js +0 -29
  236. package/dist/core/events/sd-resize-event.plugin.d.ts +0 -16
  237. package/dist/core/events/sd-resize-event.plugin.d.ts.map +0 -1
  238. package/src/core/commands/sd-insert-command-event.plugin.ts +0 -39
  239. package/src/core/commands/sd-refresh-command-event.plugin.ts +0 -44
  240. package/src/core/commands/sd-save-command-event.plugin.ts +0 -44
  241. package/src/core/events/sd-intersection-event.plugin.ts +0 -35
@@ -8,6 +8,7 @@ import {
8
8
  input,
9
9
  model,
10
10
  output,
11
+ signal,
11
12
  ViewEncapsulation,
12
13
  } from "@angular/core";
13
14
  import { NgTemplateOutlet } from "@angular/common";
@@ -15,18 +16,17 @@ import { SdSheetColumn } from "./sd-sheet-column";
15
16
  import { SdCheckbox } from "../../controls/checkbox/sd-checkbox";
16
17
  import { NgIcon } from "@ng-icons/core";
17
18
  import {
18
- tablerArrowUp,
19
- tablerArrowDown,
20
- tablerChevronRight,
21
- tablerChevronDown,
22
- tablerChevronsRight,
23
- tablerChevronsDown,
19
+ tablerArrowRight,
20
+ tablerArrowsSort,
21
+ tablerCaretRight,
24
22
  tablerSettings,
23
+ tablerSortAscending,
24
+ tablerSortDescending,
25
25
  } from "@ng-icons/tabler-icons";
26
26
  import { useSheetLayoutEngine } from "./useSheetLayoutEngine";
27
27
  import { useSheetColumnFixing } from "./useSheetColumnFixing";
28
28
  import { useSelectionManager } from "../../core/selection/useSelectionManager";
29
- import { useSortingManager, type SortingDef } from "../../core/selection/useSortingManager";
29
+ import { type SortingDef, useSortingManager } from "../../core/selection/useSortingManager";
30
30
  import { SdPagination } from "../../controls/pagination/sd-pagination";
31
31
  import { SdAnchor } from "../../controls/button/sd-anchor";
32
32
  import { SdButton } from "../../controls/button/sd-button";
@@ -36,94 +36,186 @@ import type {
36
36
  SdSheetHeaderDef,
37
37
  SdSheetItemKeydownEventParam,
38
38
  } from "./types";
39
+ import type { SdResizeEvent } from "../../core/events/sd-resize";
40
+ import { SdResizeDirective } from "../../core/events/sd-resize";
39
41
  import { injectSdSystemConfigResource } from "../../core/config/injectSdSystemConfigResource";
40
42
  import { injectSheetDomAccessor } from "./injectSheetDomAccessor";
41
43
  import { useSheetCellAgent } from "./useSheetCellAgent";
42
44
  import { injectSheetColumnResizing } from "./injectSheetColumnResizing";
43
45
  import { useSheetDisplayPipeline } from "./useSheetDisplayPipeline";
44
46
  import { useSheetCellStyling } from "./useSheetCellStyling";
47
+ import { useSheetFocusIndicator } from "./useSheetFocusIndicator";
48
+ import { injectSheetSelectRowIndicator } from "./injectSheetSelectRowIndicator";
45
49
  import { SdModalProvider } from "../../core/modal/sd-modal.provider";
46
50
  import { SdSheetConfigModal } from "./sd-sheet-config.modal";
51
+ import { SdEvents } from "../../core/events/sd-events";
47
52
 
48
53
  @Component({
49
54
  selector: "sd-sheet",
50
55
  changeDetection: ChangeDetectionStrategy.OnPush,
51
56
  encapsulation: ViewEncapsulation.None,
52
57
  standalone: true,
53
- imports: [NgTemplateOutlet, SdCheckbox, NgIcon, SdPagination, SdAnchor, SdButton],
58
+ imports: [NgTemplateOutlet, SdCheckbox, NgIcon, SdPagination, SdAnchor, SdButton, SdResizeDirective],
59
+ hostDirectives: [
60
+ { directive: SdEvents, outputs: ["keydown.capture", "focus.capture", "blur.capture"] },
61
+ ],
62
+ host: {
63
+ "class": "flex-column fill",
64
+ "[attr.data-sd-inset]": "inset()",
65
+ "[attr.data-sd-focus-mode]": "focusMode()",
66
+ "(keydown.capture)": "onKeydownCapture($event)",
67
+ "(focus.capture)": "onFocusCapture($event)",
68
+ "(dblclick)": "onDblClick($event)",
69
+ "(blur.capture)": "onBlurCapture($event)",
70
+ },
54
71
  template: `
55
- @if ((key() || effectivePageCount() > 1) && !hideConfigBar()) {
56
- <div class="_tool">
72
+ @if ((key() || effectivePageCount() > 0) && !hideConfigBar()) {
73
+ <div class="_tool flex-row gap-sm p-xs">
57
74
  @if (key()) {
58
- <sd-button (click)="onConfigButtonClick()">
75
+ <sd-button [theme]="'link-primary'" [size]="'sm'" (click)="onConfigButtonClick()">
59
76
  <ng-icon [svg]="icons.tablerSettings" />
60
77
  </sd-button>
61
78
  }
62
79
  @if (effectivePageCount() > 1) {
63
- <sd-pagination [(currentPage)]="currentPage" [totalPageCount]="effectivePageCount()" [visiblePageCount]="visiblePageCount()" />
80
+ <sd-pagination
81
+ class="flex-fill"
82
+ [(currentPage)]="currentPage"
83
+ [totalPageCount]="effectivePageCount()"
84
+ [visiblePageCount]="visiblePageCount()"
85
+ />
64
86
  }
65
87
  </div>
66
88
  }
67
- <div class="_container" [style]="contentStyle()">
68
- <table>
89
+
90
+ <div
91
+ class="_sheet-container flex-fill"
92
+ (scroll.passive)="onContainerScroll()"
93
+ [style]="contentStyle()"
94
+ >
95
+ <table (sdResize)="onTableResize($event)">
69
96
  <thead>
70
97
  @for (row of layout.headerDefTable(); track $index; let rowIdx = $index) {
71
98
  <tr>
72
- @if (expanding.hasExpandable() && rowIdx === 0) {
73
- <th
74
- class="_expand-col"
75
- [attr.rowspan]="layout.headerFeatureRowSpan() > 1 ? layout.headerFeatureRowSpan() : null"
76
- >
77
- <sd-anchor (click)="expanding.toggleAll()">
78
- <ng-icon [svg]="expanding.isAllExpanded() ? icons.tablerChevronsDown : icons.tablerChevronsRight" />
79
- </sd-anchor>
80
- </th>
81
- }
82
- @if (selectMode() && rowIdx === 0) {
99
+ @if (rowIdx === 0) {
100
+ @let _fc = expanding.hasExpandable() ? -2 : -1;
83
101
  <th
84
- class="_select-col"
85
- [attr.rowspan]="layout.headerFeatureRowSpan() > 1 ? layout.headerFeatureRowSpan() : null"
102
+ class="_fixed _feature-cell _last-depth"
103
+ [attr.rowspan]="
104
+ layout.headerFeatureRowSpan() > 1 ? layout.headerFeatureRowSpan() : null
105
+ "
106
+ [attr.data-c]="_fc"
107
+ [style.left.px]="featureFixedLeftMap().get(_fc)"
108
+ (sdResize)="onFeatureCellResize($event, _fc)"
86
109
  >
87
- @if (selectMode() === "multi") {
110
+ @if (selection.hasSelectable() && selectMode() === "multi") {
88
111
  <sd-checkbox
89
112
  [value]="selection.isAllSelected()"
90
- (click)="selection.toggleAll()"
113
+ (valueChange)="selection.toggleAll()"
91
114
  [inline]="true"
92
- [inset]="true"
115
+ [theme]="'white'"
93
116
  />
94
117
  }
95
118
  </th>
96
- }
97
- @for (cell of row; track $index) {
98
- <th
99
- [attr.colspan]="cell.colspan > 1 ? cell.colspan : null"
100
- [attr.rowspan]="cell.rowspan > 1 ? cell.rowspan : null"
101
- [attr.aria-sort]="getAriaSortValue(cell)"
102
- [attr.title]="cell.colDef?.tooltip ?? cell.text"
103
- [class.help]="cell.colDef?.tooltip"
104
- [style]="getHeaderCellStyle(cell)"
105
- (click)="onHeaderClick($event, cell)"
106
- >
107
- @if (cell.colDef && getColumnHeaderTpl(cell.colDef.key); as headerTpl) {
108
- <ng-template [ngTemplateOutlet]="headerTpl" />
109
- } @else {
110
- <span>{{ cell.text }}</span>
111
- }
112
- @if (cell.colDef && getSortDef(cell.colDef.key); as sortDef) {
119
+ @if (expanding.hasExpandable()) {
120
+ <th
121
+ class="_fixed _feature-cell _last-depth"
122
+ [attr.rowspan]="
123
+ layout.headerFeatureRowSpan() > 1 ? layout.headerFeatureRowSpan() : null
124
+ "
125
+ [attr.data-c]="-1"
126
+ [style.left.px]="featureFixedLeftMap().get(-1)"
127
+ (sdResize)="onFeatureCellResize($event, -1)"
128
+ >
113
129
  <ng-icon
114
- class="_sort-icon"
115
- [svg]="sortDef.desc ? icons.tablerArrowDown : icons.tablerArrowUp"
130
+ [svg]="icons.tablerCaretRight"
131
+ [class.tx-theme-primary-default]="expanding.isAllExpanded()"
132
+ [style.transform]="expanding.isAllExpanded() ? 'rotate(90deg)' : undefined"
133
+ (click)="expanding.toggleAll()"
116
134
  />
117
- @if (sortDef.indexText) {
118
- <span class="_sort-index">{{ sortDef.indexText }}</span>
135
+ </th>
136
+ }
137
+ }
138
+ @for (cell of row; track $index; let c = $index) {
139
+ @if (!cell.isLastRow) {
140
+ <th
141
+ [class._fixed]="cell.colDef?.fixed"
142
+ [attr.colspan]="cell.colspan > 1 ? cell.colspan : null"
143
+ [attr.rowspan]="cell.rowspan > 1 ? cell.rowspan : null"
144
+ [attr.data-c]="c"
145
+ [attr.title]="cell.text"
146
+ [style.left.px]="
147
+ cell.colDef?.fixed ? fixing.fixedLeftMap().get(cell.colDef!.key) : null
148
+ "
149
+ >
150
+ <div class="_p-sheet">
151
+ <pre>{{ cell.text }}</pre>
152
+ </div>
153
+ </th>
154
+ } @else {
155
+ <th
156
+ [class._fixed]="cell.colDef?.fixed"
157
+ class="_last-depth"
158
+ [class._sort]="cell.colDef && !cell.colDef.disableSorting"
159
+ [attr.colspan]="cell.colspan > 1 ? cell.colspan : null"
160
+ [attr.rowspan]="cell.rowspan > 1 ? cell.rowspan : null"
161
+ [attr.data-c]="c"
162
+ [attr.title]="cell.colDef?.tooltip ?? cell.text"
163
+ [class.help]="cell.colDef?.tooltip"
164
+ [style]="getHeaderCellStyle(cell)"
165
+ (click)="onHeaderClick($event, cell)"
166
+ >
167
+ <div class="_headerContent flex-row">
168
+ @if (cell.colDef && getColumnHeaderTpl(cell.colDef.key); as headerTpl) {
169
+ <div class="flex-fill" [attr.style]="cell.colDef.headerStyle">
170
+ <ng-template [ngTemplateOutlet]="headerTpl" />
171
+ </div>
172
+ } @else {
173
+ <div class="flex-fill _p-sheet" [attr.style]="cell.colDef?.headerStyle">
174
+ <pre>{{ cell.text }}</pre>
175
+ </div>
176
+ }
177
+ @if (cell.colDef && !cell.colDef.disableSorting) {
178
+ @let _sortDef = getSortDef(cell.colDef.key);
179
+ <div class="_sort-icon">
180
+ @if (_sortDef?.desc === false) {
181
+ <ng-icon [svg]="icons.tablerSortAscending" />
182
+ } @else if (_sortDef?.desc === true) {
183
+ <ng-icon [svg]="icons.tablerSortDescending" />
184
+ } @else {
185
+ <ng-icon [svg]="icons.tablerArrowsSort" class="tx-trans-lightest" />
186
+ }
187
+ @if (_sortDef?.indexText) {
188
+ <sub>{{ _sortDef?.indexText }}</sub>
189
+ }
190
+ </div>
191
+ }
192
+ </div>
193
+ @if (cell.colDef && !cell.colDef.disableResizing) {
194
+ <div
195
+ class="_resizer"
196
+ (mousedown)="onResizerMousedown($event, cell.colDef)"
197
+ (dblclick)="onResizerDblClick($event, cell.colDef)"
198
+ ></div>
119
199
  }
120
- }
121
- @if (cell.isLastRow && cell.colDef && !cell.colDef.disableResizing) {
122
- <div
123
- class="_resizer"
124
- (mousedown)="onResizerMousedown($event, cell.colDef)"
125
- (dblclick)="onResizerDblClick($event, cell.colDef)"
126
- ></div>
200
+ </th>
201
+ }
202
+ }
203
+ </tr>
204
+ }
205
+ @if (layout.hasSummary()) {
206
+ <tr class="_summary-row">
207
+ <th class="_fixed _feature-cell"></th>
208
+ @if (expanding.hasExpandable()) {
209
+ <th class="_fixed _feature-cell"></th>
210
+ }
211
+ @for (colDef of layout.columnDefs(); track colDef.key; let c = $index) {
212
+ <th
213
+ [class._fixed]="colDef.fixed"
214
+ [attr.data-c]="c"
215
+ [style.left.px]="colDef.fixed ? fixing.fixedLeftMap().get(colDef.key) : null"
216
+ >
217
+ @if (getColumnSummaryTpl(colDef.key); as tpl) {
218
+ <ng-template [ngTemplateOutlet]="tpl" />
127
219
  }
128
220
  </th>
129
221
  }
@@ -131,39 +223,73 @@ import { SdSheetConfigModal } from "./sd-sheet-config.modal";
131
223
  }
132
224
  </thead>
133
225
  <tbody>
134
- @for (item of displayItems(); track trackByFn() ? trackByFn()!(item, $index) : $index; let rowIdx = $index) {
226
+ @for (item of displayItems(); track trackByFn()(item, $index); let rowIdx = $index) {
135
227
  <tr
136
- [attr.aria-selected]="selection.isSelected(item) ? 'true' : null"
137
- [attr.aria-expanded]="getAriaExpanded(item)"
138
- [style.background]="selection.isSelected(item) ? 'var(--trans-lighter)' : null"
228
+ [attr.data-r]="rowIdx"
139
229
  (click)="onRowClick(item)"
140
230
  (keydown)="onItemKeydown($event, item)"
141
231
  >
142
- @if (expanding.hasExpandable()) {
143
- <td class="_expand-col">
144
- @if (getItemDef(item).hasChildren) {
145
- <sd-anchor (click)="onExpandClick($event, item)">
146
- <ng-icon [svg]="isExpanded(item) ? icons.tablerChevronDown : icons.tablerChevronRight" />
147
- </sd-anchor>
148
- }
149
- </td>
150
- }
151
- @if (selectMode()) {
152
- <td class="_select-col">
232
+ @let _fc = expanding.hasExpandable() ? -2 : -1;
233
+ <td
234
+ class="_fixed _feature-cell"
235
+ [attr.data-r]="rowIdx"
236
+ [attr.data-c]="_fc"
237
+ [style.left.px]="featureFixedLeftMap().get(_fc)"
238
+ >
239
+ @if (selectMode() === "multi") {
240
+ @let _selectable = selection.getSelectable(item);
153
241
  <sd-checkbox
154
242
  [value]="selection.isSelected(item)"
155
243
  [canChangeFn]="selection.getCanChangeFn(item)"
156
- (click)="onSelectCheckboxClick($event, item)"
244
+ (valueChange)="selection.toggle(item)"
245
+ (mousedown)="onSelectorMouseDown($event, rowIdx)"
157
246
  [inline]="true"
158
- [inset]="true"
159
- [attr.title]="getSelectableTooltip(item)"
247
+ [theme]="'white'"
248
+ [disabled]="_selectable !== true"
249
+ [attr.title]="_selectable"
160
250
  />
251
+ } @else if (selectMode() === "single") {
252
+ @let _selectable = selection.getSelectable(item);
253
+ @if (_selectable === true && selection.getCanChangeFn(item)) {
254
+ <sd-anchor
255
+ [theme]="selection.isSelected(item) ? 'primary' : 'gray'"
256
+ (pointerdown)="selection.toggle(item)"
257
+ [attr.title]="_selectable"
258
+ >
259
+ <ng-icon [svg]="icons.tablerArrowRight" />
260
+ </sd-anchor>
261
+ }
262
+ }
263
+ </td>
264
+ @if (expanding.hasExpandable()) {
265
+ <td
266
+ class="_fixed _feature-cell"
267
+ [attr.data-r]="rowIdx"
268
+ [attr.data-c]="-1"
269
+ [style.left.px]="featureFixedLeftMap().get(-1)"
270
+ >
271
+ @let itemDef = getItemDef(item);
272
+ @if (itemDef.depth > 0) {
273
+ <div
274
+ class="_depth-indicator"
275
+ [style.margin-left.em]="itemDef.depth - 0.5"
276
+ ></div>
277
+ }
278
+ @if (itemDef.hasChildren) {
279
+ <ng-icon
280
+ [svg]="icons.tablerCaretRight"
281
+ [style.transform]="isExpanded(item) ? 'rotate(90deg)' : undefined"
282
+ [class.tx-theme-primary-default]="isExpanded(item)"
283
+ (click)="onExpandClick($event, item)"
284
+ />
285
+ }
161
286
  </td>
162
287
  }
163
288
  @for (colDef of layout.columnDefs(); track colDef.key; let colIdx = $index) {
164
289
  <td
165
290
  [attr.data-r]="rowIdx"
166
291
  [attr.data-c]="colIdx"
292
+ [class._fixed]="colDef.fixed"
167
293
  [class]="getDataCellClass(item, colDef, rowIdx, colIdx)"
168
294
  [style]="getCellStyleWithIndent(item, colDef, colIdx)"
169
295
  (click)="onCellClick($event, item)"
@@ -178,8 +304,8 @@ import { SdSheetConfigModal } from "./sd-sheet-config.modal";
178
304
  $implicit: item,
179
305
  item: item,
180
306
  index: rowIdx,
181
- depth: getChildrenFn() !== undefined ? getItemDef(item).depth : 0,
182
- edit: cellAgent.isCellEditMode({ r: rowIdx, c: colIdx })
307
+ depth: getChildrenFn() != null ? getItemDef(item).depth : 0,
308
+ edit: cellAgent.isCellEditMode({ r: rowIdx, c: colIdx }),
183
309
  }"
184
310
  />
185
311
  }
@@ -188,163 +314,256 @@ import { SdSheetConfigModal } from "./sd-sheet-config.modal";
188
314
  </tr>
189
315
  }
190
316
  </tbody>
191
- @if (layout.hasSummary()) {
192
- <tfoot>
193
- <tr>
194
- @if (expanding.hasExpandable()) {
195
- <td class="_expand-col"></td>
196
- }
197
- @if (selectMode()) {
198
- <td class="_select-col"></td>
199
- }
200
- @for (colDef of layout.columnDefs(); track colDef.key) {
201
- <td [style]="getFixedCellStyle(colDef)">
202
- @if (getColumnSummaryTpl(colDef.key); as tpl) {
203
- <ng-template [ngTemplateOutlet]="tpl" />
204
- }
205
- </td>
206
- }
207
- </tr>
208
- </tfoot>
209
- }
210
317
  </table>
211
- <div class="_resize-indicator" [style.display]="_isResizing() ? 'block' : 'none'" [style.left.px]="_resizeIndicatorLeft()"></div>
318
+
319
+ <div class="_focus-row-indicator">
320
+ <div class="_focus-cell-indicator"></div>
321
+ </div>
322
+ <div
323
+ class="_resize-indicator"
324
+ [style.display]="_isResizing() ? 'block' : 'none'"
325
+ [style.left.px]="_resizeIndicatorLeft()"
326
+ ></div>
327
+ <div class="_select-row-indicator-container"></div>
212
328
  </div>
213
329
  `,
214
330
  styles: [
215
331
  /* language=SCSS */ `
216
- sd-sheet {
217
- display: block;
218
- position: relative;
219
- overflow: hidden;
220
- border: 1px solid var(--trans-lighter);
332
+ @use "../../../scss/commons/mixins";
221
333
 
222
- &[data-sd-inset="true"] {
223
- border: none;
224
- }
334
+ $z-index-fixed: 2;
335
+ $z-index-head: 3;
336
+ $z-index-head-fixed: 4;
337
+ $z-index-select-row-indicator: 5;
338
+ $z-index-focus-row-indicator: 6;
339
+ $z-index-resize-indicator: 7;
340
+
341
+ $border-color: var(--theme-gray-lighter);
342
+ $border-color-dark: var(--theme-gray-lighter);
343
+ $border-color-darker: var(--theme-gray-light);
344
+
345
+ $border-radius: var(--border-radius-default);
346
+
347
+ sd-sheet {
348
+ border: 1px solid $border-color-dark;
349
+ border-radius: $border-radius;
225
350
 
226
351
  > ._tool {
227
- display: flex;
228
- align-items: center;
229
- gap: var(--gap-sm);
230
- padding: var(--gap-sm) var(--gap-default);
231
- border-bottom: 1px solid var(--trans-lighter);
232
- background: var(--theme-secondary-lightest);
352
+ background: var(--control-color);
353
+ border-top-left-radius: $border-radius;
354
+ border-top-right-radius: $border-radius;
355
+ border-bottom: 1px solid $border-color-dark;
233
356
  }
234
357
 
235
- > ._container {
358
+ > ._sheet-container {
359
+ position: relative;
360
+ background: var(--sheet-bg);
361
+ border-bottom-left-radius: $border-radius;
362
+ border-bottom-right-radius: $border-radius;
236
363
  overflow: auto;
237
- width: 100%;
238
- height: 100%;
239
364
 
240
365
  > table {
241
- width: 100%;
242
- border-collapse: collapse;
366
+ border-spacing: 0;
243
367
  table-layout: fixed;
368
+ margin-right: 2px;
369
+ margin-bottom: 2px;
370
+ border-bottom-right-radius: $border-radius;
244
371
 
245
- > thead > tr > th {
246
- position: sticky;
247
- top: 0;
248
- z-index: 2;
249
- background: var(--theme-secondary-lightest);
250
- border: 1px solid var(--trans-lighter);
251
- padding: var(--gap-sm) var(--gap-default);
252
- text-align: left;
253
- font-weight: bold;
372
+ > * > tr > *:last-child {
373
+ border-right: 1px solid $border-color-dark;
374
+ }
375
+
376
+ > * > tr:last-child > * {
377
+ border-bottom: 1px solid $border-color-dark;
378
+ }
379
+
380
+ > *:last-child > tr:last-child > td:last-child {
381
+ border-bottom-right-radius: $border-radius;
382
+ overflow: hidden;
383
+ }
384
+
385
+ > * > tr > * {
386
+ border-right: 1px solid $border-color;
387
+ border-bottom: 1px solid $border-color;
254
388
  white-space: nowrap;
255
- cursor: pointer;
256
- user-select: none;
389
+ overflow: hidden;
390
+ padding: 0;
391
+ position: relative;
257
392
 
258
- &:not(._select-col):not(._expand-col) {
259
- position: relative;
393
+ &._fixed:has(+ :not(._fixed)) + :not(._fixed):not([data-c="0"]) {
394
+ border-left: 1px solid $border-color;
260
395
  }
261
396
 
262
- > ._sort-icon {
263
- font-size: 0.85em;
264
- vertical-align: middle;
265
- margin-left: 0.25em;
397
+ &._feature-cell {
398
+ background: var(--theme-gray-lightest);
399
+ min-width: calc(var(--font-size-default) + 2px + var(--sheet-ph) * 2);
400
+ padding: var(--sheet-pv) var(--sheet-ph);
401
+ text-align: left;
402
+
403
+ > ng-icon {
404
+ cursor: pointer;
405
+ color: var(--text-trans-lightest);
406
+ }
407
+ }
408
+
409
+ &._fixed {
410
+ position: sticky;
411
+ left: 0;
412
+
413
+ &:has(+ :not(._fixed)) {
414
+ border-right: 1px solid $border-color-dark;
415
+ }
266
416
  }
417
+ }
418
+
419
+ > thead {
420
+ position: sticky;
421
+ top: 0;
422
+ z-index: $z-index-head;
267
423
 
268
- > ._sort-index {
269
- font-size: 0.75em;
270
- vertical-align: super;
424
+ > tr > th {
425
+ position: relative;
426
+ background: var(--theme-gray-lightest);
427
+ vertical-align: middle;
428
+
429
+ &._fixed {
430
+ z-index: $z-index-head-fixed;
431
+ }
432
+
433
+ &._last-depth {
434
+ border-bottom: 1px solid $border-color-dark;
435
+ }
436
+
437
+ &._feature-cell {
438
+ border-bottom: 1px solid $border-color-dark;
439
+ }
440
+
441
+ &._sort {
442
+ cursor: pointer;
443
+
444
+ &:hover {
445
+ text-decoration: underline;
446
+ }
447
+ }
448
+
449
+ > ._headerContent {
450
+ > ._sort-icon {
451
+ padding: var(--gap-xs) var(--gap-xs) var(--gap-xs) 0;
452
+ background-color: var(--theme-gray-lightest);
453
+ }
454
+ }
455
+
456
+ > ._resizer {
457
+ position: absolute;
458
+ top: 0;
459
+ right: 0;
460
+ bottom: 0;
461
+ width: 2px;
462
+ cursor: ew-resize;
463
+ }
271
464
  }
272
465
 
273
- > ._resizer {
274
- position: absolute;
275
- top: 0;
276
- right: -2px;
277
- width: 5px;
278
- height: 100%;
279
- cursor: col-resize;
280
- z-index: 3;
466
+ &:has(> tr._summary-row) {
467
+ > tr > th._last-depth {
468
+ border-bottom: 1px solid $border-color;
469
+ }
470
+
471
+ > tr._summary-row > th {
472
+ background: var(--theme-warning-lightest);
473
+ text-align: left;
474
+ border-bottom: 1px solid $border-color-dark;
475
+ }
281
476
  }
282
477
  }
283
478
 
284
479
  > tbody > tr > td {
285
- border: 1px solid var(--trans-lighter);
286
- padding: var(--gap-sm) var(--gap-default);
287
- white-space: nowrap;
480
+ background: var(--control-color);
481
+ vertical-align: top;
288
482
 
289
- &:focus {
290
- outline: 2px solid var(--theme-primary-default);
291
- outline-offset: -2px;
483
+ &._fixed {
484
+ z-index: $z-index-fixed;
292
485
  }
293
- }
294
486
 
295
- > thead > tr > th._select-col,
296
- > tbody > tr > td._select-col {
297
- width: 2em;
298
- min-width: 2em;
299
- max-width: 2em;
300
- text-align: center;
301
- padding: 0;
487
+ > ._depth-indicator {
488
+ display: inline-block;
489
+ margin-top: 0.4em;
490
+ width: 0.5em;
491
+ height: 0.5em;
492
+ border-left: 1px solid var(--text-trans-default);
493
+ border-bottom: 1px solid var(--text-trans-default);
494
+ vertical-align: top;
495
+ }
302
496
  }
497
+ }
303
498
 
304
- > thead > tr > th._expand-col,
305
- > tbody > tr > td._expand-col {
306
- width: 2em;
307
- min-width: 2em;
308
- max-width: 2em;
309
- text-align: center;
310
- padding: 0;
311
- }
499
+ > ._focus-row-indicator {
500
+ display: none;
501
+ position: absolute;
502
+ pointer-events: none;
503
+ background: rgba(158, 158, 158, 0.1);
504
+ z-index: $z-index-focus-row-indicator;
312
505
 
313
- > tfoot > tr > td {
314
- position: sticky;
315
- bottom: 0;
316
- z-index: 2;
317
- background: var(--theme-secondary-lightest);
318
- border: 1px solid var(--trans-lighter);
319
- padding: var(--gap-sm) var(--gap-default);
506
+ > ._focus-cell-indicator {
507
+ position: absolute;
508
+ border: 2px solid var(--theme-primary-default);
509
+ border-radius: $border-radius;
320
510
  }
321
511
  }
322
512
 
323
513
  > ._resize-indicator {
514
+ display: none;
324
515
  position: absolute;
516
+ pointer-events: none;
325
517
  top: 0;
326
- width: 2px;
327
518
  height: 100%;
328
- background: var(--theme-primary-default);
329
- z-index: 10;
519
+ border: 1px dotted $border-color-darker;
520
+ z-index: $z-index-resize-indicator;
521
+ }
522
+
523
+ > ._select-row-indicator-container {
524
+ display: none;
525
+ position: absolute;
330
526
  pointer-events: none;
527
+ top: 0;
528
+ left: 0;
529
+ width: 100%;
530
+ height: 100%;
531
+ z-index: $z-index-select-row-indicator;
532
+
533
+ > ._select-row-indicator {
534
+ display: block;
535
+ left: 0;
536
+ position: absolute;
537
+ pointer-events: none;
538
+ background: var(--theme-primary-default);
539
+ opacity: 0.1;
540
+ }
541
+ }
542
+ }
543
+
544
+ ._p-sheet {
545
+ padding: var(--sheet-pv) var(--sheet-ph);
546
+ }
547
+
548
+ &[data-sd-focus-mode="row"] {
549
+ > ._sheet-container > ._focus-row-indicator > ._focus-cell-indicator {
550
+ display: none !important;
331
551
  }
332
552
  }
553
+
554
+ &[data-sd-inset="true"] {
555
+ border: none;
556
+ border-radius: 0;
557
+ }
333
558
  }
334
559
  `,
335
560
  ],
336
- host: {
337
- "[attr.data-sd-inset]": "inset()",
338
- "(keydown.capture)": "onKeydownCapture($event)",
339
- "(dblclick)": "onDblClick($event)",
340
- "(blur.capture)": "onBlurCapture($event)",
341
- },
342
561
  })
343
562
  export class SdSheet<T> {
344
563
  // Inputs
345
564
  key = input<string>();
346
565
  items = input<T[]>([]);
347
- trackByFn = input<(item: T, index: number) => unknown>();
566
+ trackByFn = input<(item: T, index: number) => unknown>((item) => item);
348
567
  selectMode = input<"single" | "multi">();
349
568
  autoSelect = input<"click" | "focus">();
350
569
  getItemSelectableFn = input<(item: T) => boolean | string>();
@@ -406,6 +625,23 @@ export class SdSheet<T> {
406
625
  columnDefs: this.layout.columnDefs,
407
626
  });
408
627
 
628
+ // Feature cell fixing (select/expand columns)
629
+ private readonly _featureCellWidths = signal(new Map<number, number>());
630
+
631
+ featureFixedLeftMap = computed(() => {
632
+ const map = new Map<number, number>();
633
+ const widths = this._featureCellWidths();
634
+ const hasExpand = this.expanding.hasExpandable();
635
+
636
+ if (hasExpand) {
637
+ map.set(-2, 0);
638
+ map.set(-1, widths.get(-2) ?? 0);
639
+ } else {
640
+ map.set(-1, 0);
641
+ }
642
+ return map;
643
+ });
644
+
409
645
  // Sorting manager
410
646
  sorting = useSortingManager({
411
647
  sorts: this.sorts,
@@ -442,6 +678,18 @@ export class SdSheet<T> {
442
678
  getCellStyleWithIndent = this._styling.getCellStyleWithIndent;
443
679
  getDataCellClass = this._styling.getDataCellClass;
444
680
 
681
+ // Focus indicator
682
+ private readonly _focusIndicator = useSheetFocusIndicator({
683
+ domAccessor: this.domAccessor,
684
+ });
685
+
686
+ // Select row indicator
687
+ private readonly _selectRowIndicator = injectSheetSelectRowIndicator<T>({
688
+ domAccessor: this.domAccessor,
689
+ selectedItems: this.selectedItems,
690
+ displayItems: this.displayItems,
691
+ });
692
+
445
693
  // Selection manager
446
694
  selection = useSelectionManager<T>({
447
695
  displayItems: this.displayItems,
@@ -452,13 +700,12 @@ export class SdSheet<T> {
452
700
 
453
701
  // Icons
454
702
  icons = {
455
- tablerArrowUp,
456
- tablerArrowDown,
457
- tablerChevronRight,
458
- tablerChevronDown,
459
- tablerChevronsRight,
460
- tablerChevronsDown,
461
703
  tablerSettings,
704
+ tablerCaretRight,
705
+ tablerArrowsSort,
706
+ tablerSortAscending,
707
+ tablerSortDescending,
708
+ tablerArrowRight,
462
709
  };
463
710
 
464
711
  private readonly _columnControlMap = computed(() => {
@@ -496,11 +743,6 @@ export class SdSheet<T> {
496
743
  }
497
744
  }
498
745
 
499
- onSelectCheckboxClick(event: Event, item: T): void {
500
- event.stopPropagation();
501
- this.selection.toggle(item);
502
- }
503
-
504
746
  onCellClick(event: Event, item: T): void {
505
747
  if (this.autoSelect() === "click") {
506
748
  this.selection.select(item);
@@ -554,16 +796,96 @@ export class SdSheet<T> {
554
796
  return sortDef.desc ? "descending" : "ascending";
555
797
  }
556
798
 
557
- async onKeydownCapture(event: Event): Promise<void> {
558
- await this.cellAgent.handleKeydownCapture(event as KeyboardEvent);
799
+ async onKeydownCapture(event: KeyboardEvent): Promise<void> {
800
+ await this.cellAgent.handleKeydownCapture(event);
559
801
  }
560
802
 
561
803
  onDblClick(event: MouseEvent): void {
562
804
  this.cellAgent.handleCellDoubleClick(event);
563
805
  }
564
806
 
565
- onBlurCapture(event: Event): void {
566
- this.cellAgent.handleBlurCapture(event as FocusEvent);
807
+ onFocusCapture(event: FocusEvent): void {
808
+ this._autoScrollOnFocus(event);
809
+ this._focusIndicator.redraw();
810
+ }
811
+
812
+ onBlurCapture(event: FocusEvent): void {
813
+ this.cellAgent.handleBlurCapture(event);
814
+ this._focusIndicator.redraw();
815
+ }
816
+
817
+ onContainerScroll(): void {
818
+ this._focusIndicator.redraw();
819
+ }
820
+
821
+ onTableResize(event: SdResizeEvent): void {
822
+ if (!event.widthChanged) return;
823
+ this._focusIndicator.redraw();
824
+ this._selectRowIndicator.redraw();
825
+ }
826
+
827
+ onFeatureCellResize(event: SdResizeEvent, key: number): void {
828
+ if (!event.widthChanged) return;
829
+ const width = event.target.offsetWidth;
830
+ this._featureCellWidths.update((m) => {
831
+ const newMap = new Map(m);
832
+ newMap.set(key, width);
833
+ return newMap;
834
+ });
835
+ }
836
+
837
+ onSelectorMouseDown(event: MouseEvent, r: number): void {
838
+ if (!event.shiftKey) return;
839
+ event.preventDefault();
840
+ event.stopPropagation();
841
+
842
+ const focusedEl = document.activeElement;
843
+ if (!(focusedEl instanceof HTMLElement)) return;
844
+
845
+ const focusedTrEl =
846
+ focusedEl.tagName.toLowerCase() === "tr" ? focusedEl : focusedEl.closest("tr");
847
+ if (!(focusedTrEl instanceof HTMLTableRowElement)) return;
848
+
849
+ const frAttr = focusedTrEl.getAttribute("data-r");
850
+ if (frAttr == null) return;
851
+ const fr = parseInt(frAttr, 10);
852
+ if (Number.isNaN(fr)) return;
853
+
854
+ const items = this.displayItems();
855
+ const isSelect = this.selection.isSelected(items[fr]);
856
+ queueMicrotask(() => {
857
+ for (let i = Math.min(fr, r); i <= Math.max(fr, r); i++) {
858
+ if (isSelect) {
859
+ this.selection.select(items[i]);
860
+ } else {
861
+ this.selection.deselect(items[i]);
862
+ }
863
+ }
864
+ });
865
+
866
+ const row = this.domAccessor.getRow(r);
867
+ row?.querySelector<HTMLElement>("[tabindex]")?.focus();
868
+ }
869
+
870
+ private _autoScrollOnFocus(event: FocusEvent): void {
871
+ if (!(event.target instanceof HTMLElement)) return;
872
+
873
+ const tdEl =
874
+ event.target.tagName.toLowerCase() === "td" ? event.target : event.target.closest("td");
875
+ if (!(tdEl instanceof HTMLTableCellElement)) return;
876
+ if (tdEl.classList.contains("_fixed")) return;
877
+
878
+ const containerEl = this.domAccessor.getContainer();
879
+ const theadEl = this.domAccessor.getTHead();
880
+ const fixedHeaders = this.domAccessor.getLastDepthFixedHeaders();
881
+
882
+ containerEl.scrollIntoViewIfNeeded(
883
+ { top: tdEl.offsetTop, left: tdEl.offsetLeft },
884
+ {
885
+ top: theadEl.offsetHeight,
886
+ left: fixedHeaders.reduce((sum, el) => sum + el.offsetWidth, 0),
887
+ },
888
+ );
567
889
  }
568
890
 
569
891
  onItemKeydown(event: KeyboardEvent, item: T): void {
@@ -575,21 +897,19 @@ export class SdSheet<T> {
575
897
  }
576
898
 
577
899
  async onConfigButtonClick(): Promise<void> {
578
- const result = await this._sdModal.showAsync(
579
- {
580
- title: "시트 설정",
581
- type: SdSheetConfigModal,
582
- inputs: {
583
- controls: this.columnControls(),
584
- config: this._configResource.value(),
585
- },
900
+ const result = await this._sdModal.showAsync({
901
+ title: "시트 설정",
902
+ type: SdSheetConfigModal,
903
+ inputs: {
904
+ sheetKey: this.key()!,
905
+ controls: this.columnControls(),
906
+ config: this._configResource.value(),
586
907
  },
587
- );
908
+ });
588
909
  if (result != null) {
589
910
  this._configResource.set(result);
590
911
  }
591
912
  }
592
-
593
913
  }
594
914
 
595
915
  // Re-export SortingDef from useSortingManager for convenience