@simplysm/angular 14.0.53 → 14.0.55
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.
- package/dist/controls/button/sd-modal-select-button.d.ts +9 -10
- package/dist/controls/button/sd-modal-select-button.d.ts.map +1 -1
- package/dist/controls/button/sd-modal-select-button.js +4 -7
- package/dist/core/select-modal-output-result.d.ts +2 -3
- package/dist/core/select-modal-output-result.d.ts.map +1 -1
- package/dist/core/selection/useSelectionManager.d.ts +11 -11
- package/dist/core/selection/useSelectionManager.d.ts.map +1 -1
- package/dist/core/selection/useSelectionManager.js +29 -28
- package/dist/data/crud/sd-base-container.d.ts +21 -0
- package/dist/data/crud/sd-base-container.d.ts.map +1 -0
- package/dist/data/crud/sd-base-container.js +200 -0
- package/dist/data/crud/sd-crud-detail.d.ts +22 -0
- package/dist/data/crud/sd-crud-detail.d.ts.map +1 -0
- package/dist/data/crud/sd-crud-detail.js +267 -0
- package/dist/data/crud/sd-crud-list.d.ts +54 -0
- package/dist/data/crud/sd-crud-list.d.ts.map +1 -0
- package/dist/data/crud/sd-crud-list.js +592 -0
- package/dist/data/shared-data/sd-shared-data-select-button.d.ts.map +1 -1
- package/dist/data/shared-data/sd-shared-data-select-button.js +4 -5
- package/dist/data/shared-data/sd-shared-data-select-list.js +2 -2
- package/dist/data/shared-data/sd-shared-data-select.js +3 -3
- package/dist/data/sheet/injectSheetSelectRowIndicator.d.ts +4 -3
- package/dist/data/sheet/injectSheetSelectRowIndicator.d.ts.map +1 -1
- package/dist/data/sheet/injectSheetSelectRowIndicator.js +12 -4
- package/dist/data/sheet/sd-sheet.d.ts +39 -39
- package/dist/data/sheet/sd-sheet.d.ts.map +1 -1
- package/dist/data/sheet/sd-sheet.js +96 -74
- package/dist/data/sheet/types.d.ts +1 -0
- package/dist/data/sheet/types.d.ts.map +1 -1
- package/dist/data/sheet/useSheetLayoutEngine.d.ts.map +1 -1
- package/dist/data/sheet/useSheetLayoutEngine.js +3 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/styles.css +2 -2
- package/package.json +13 -13
- package/scss/commons/_variables.scss +2 -2
- package/src/controls/button/sd-modal-select-button.ts +9 -13
- package/src/core/select-modal-output-result.ts +2 -3
- package/src/core/selection/useSelectionManager.ts +47 -49
- package/src/data/crud/sd-base-container.ts +115 -0
- package/src/data/crud/sd-crud-detail.ts +122 -0
- package/src/data/crud/sd-crud-list.ts +316 -0
- package/src/data/shared-data/sd-shared-data-select-button.ts +0 -1
- package/src/data/shared-data/sd-shared-data-select-list.ts +2 -2
- package/src/data/shared-data/sd-shared-data-select.ts +3 -3
- package/src/data/sheet/injectSheetSelectRowIndicator.ts +17 -7
- package/src/data/sheet/sd-sheet.ts +59 -42
- package/src/data/sheet/types.ts +1 -0
- package/src/data/sheet/useSheetLayoutEngine.ts +3 -1
- package/src/index.ts +5 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChangeDetectionStrategy,
|
|
3
|
+
Component,
|
|
4
|
+
computed,
|
|
5
|
+
contentChild,
|
|
6
|
+
contentChildren,
|
|
7
|
+
inject,
|
|
8
|
+
input,
|
|
9
|
+
model,
|
|
10
|
+
output,
|
|
11
|
+
TemplateRef,
|
|
12
|
+
viewChild,
|
|
13
|
+
ViewEncapsulation,
|
|
14
|
+
} from "@angular/core";
|
|
15
|
+
import { SdActivatedModalProvider } from "../../core/modal/sd-activated-modal.provider";
|
|
16
|
+
import { SdAnchor } from "../../controls/button/sd-anchor";
|
|
17
|
+
import { SdButton } from "../../controls/button/sd-button";
|
|
18
|
+
import { SdCommandDirective } from "../../core/commands/sd-command";
|
|
19
|
+
import { SdForm } from "../../controls/form/sd-form";
|
|
20
|
+
import { SdSheet } from "../sheet/sd-sheet";
|
|
21
|
+
import { SdSheetColumn } from "../sheet/sd-sheet-column";
|
|
22
|
+
import { SdSheetColumnCellTemplate } from "../sheet/sd-sheet-column-cell-template";
|
|
23
|
+
import type { SdViewType } from "../../core/routing/injectViewTypeSignal";
|
|
24
|
+
import type { SortingDef } from "../../core/selection/useSortingManager";
|
|
25
|
+
import { SdBaseContainer } from "./sd-base-container";
|
|
26
|
+
import { NgIcon } from "@ng-icons/core";
|
|
27
|
+
import { NgTemplateOutlet } from "@angular/common";
|
|
28
|
+
import {
|
|
29
|
+
tablerCirclePlus,
|
|
30
|
+
tablerDeviceFloppy,
|
|
31
|
+
tablerEraser,
|
|
32
|
+
tablerRestore,
|
|
33
|
+
tablerSearch,
|
|
34
|
+
} from "@ng-icons/tabler-icons";
|
|
35
|
+
|
|
36
|
+
@Component({
|
|
37
|
+
selector: "sd-crud-list",
|
|
38
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
39
|
+
encapsulation: ViewEncapsulation.None,
|
|
40
|
+
standalone: true,
|
|
41
|
+
imports: [
|
|
42
|
+
SdBaseContainer,
|
|
43
|
+
SdButton,
|
|
44
|
+
NgIcon,
|
|
45
|
+
NgTemplateOutlet,
|
|
46
|
+
SdSheet,
|
|
47
|
+
SdForm,
|
|
48
|
+
SdAnchor,
|
|
49
|
+
SdSheetColumn,
|
|
50
|
+
SdSheetColumnCellTemplate,
|
|
51
|
+
],
|
|
52
|
+
hostDirectives: [{ directive: SdCommandDirective, outputs: ["sdSaveCommand"] }],
|
|
53
|
+
host: {
|
|
54
|
+
"(sdSaveCommand)": "onSaveButtonClick()",
|
|
55
|
+
},
|
|
56
|
+
template: `
|
|
57
|
+
<sd-base-container
|
|
58
|
+
[(ready)]="ready"
|
|
59
|
+
[initialized]="initialized()"
|
|
60
|
+
[(busyCount)]="busyCount"
|
|
61
|
+
[restricted]="restricted()"
|
|
62
|
+
[viewType]="viewType()"
|
|
63
|
+
>
|
|
64
|
+
<!-- TOP -->
|
|
65
|
+
@if (viewType() === "page" && (!readonly() || commandTplRef())) {
|
|
66
|
+
<ng-template #topbarTpl>
|
|
67
|
+
@if (!readonly()) {
|
|
68
|
+
<sd-button [theme]="'link-primary'" (click)="formCtrl()?.requestSubmit()">
|
|
69
|
+
<ng-icon [svg]="tablerDeviceFloppy" />
|
|
70
|
+
저장
|
|
71
|
+
<small>(CTRL+S)</small>
|
|
72
|
+
</sd-button>
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
<ng-template [ngTemplateOutlet]="commandTplRef()" />
|
|
76
|
+
</ng-template>
|
|
77
|
+
} @else if (!readonly() || commandTplRef()) {
|
|
78
|
+
<ng-template #commandTpl>
|
|
79
|
+
@if (!readonly()) {
|
|
80
|
+
<sd-button [theme]="'primary'" (click)="onSaveButtonClick()">
|
|
81
|
+
<ng-icon [svg]="tablerDeviceFloppy" />
|
|
82
|
+
저장
|
|
83
|
+
<small>(CTRL+S)</small>
|
|
84
|
+
</sd-button>
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
<ng-template [ngTemplateOutlet]="commandTplRef()" />
|
|
88
|
+
</ng-template>
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
<!-- BOTTOM -->
|
|
92
|
+
@if ((viewType() === "modal" && selectMode() != null) || bottomCommandTplRef()) {
|
|
93
|
+
<ng-template #bottomCommandTpl>
|
|
94
|
+
@if (bottomCommandTplRef()) {
|
|
95
|
+
<div class="flex-fill flex-row main-align-start gap-sm">
|
|
96
|
+
<ng-template [ngTemplateOutlet]="bottomCommandTplRef()" />
|
|
97
|
+
</div>
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@if (selectMode() != null) {
|
|
101
|
+
<sd-button
|
|
102
|
+
[size]="'sm'"
|
|
103
|
+
[theme]="'danger'"
|
|
104
|
+
(click)="onModalSelectionCancelClick()"
|
|
105
|
+
[disabled]="selectedKeys().length < 1"
|
|
106
|
+
>
|
|
107
|
+
선택 해제
|
|
108
|
+
</sd-button>
|
|
109
|
+
@if (selectMode() === "multi") {
|
|
110
|
+
<sd-button [size]="'sm'" [theme]="'primary'" (click)="onModalSelectionConfirmClick()">
|
|
111
|
+
확인({{ selectedKeys().length }})
|
|
112
|
+
</sd-button>
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
</ng-template>
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
<!-- CONTENT -->
|
|
119
|
+
<div class="flex-column fill">
|
|
120
|
+
@if (filterTplRef()) {
|
|
121
|
+
<sd-form (formSubmit)="filterSubmit.emit()" class="block p-default">
|
|
122
|
+
<div class="form-box-inline">
|
|
123
|
+
<div>
|
|
124
|
+
<sd-button [type]="'submit'" [theme]="'info'">
|
|
125
|
+
<ng-icon [svg]="tablerSearch" />
|
|
126
|
+
조회
|
|
127
|
+
</sd-button>
|
|
128
|
+
</div>
|
|
129
|
+
<ng-template [ngTemplateOutlet]="filterTplRef()" />
|
|
130
|
+
</div>
|
|
131
|
+
</sd-form>
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@if (!readonly() || toolTplRef()) {
|
|
135
|
+
<div class="flex-row gap-sm p-xs-default">
|
|
136
|
+
@if (!readonly()) {
|
|
137
|
+
<sd-button [size]="'sm'" [theme]="'link-primary'" (click)="create.emit()">
|
|
138
|
+
<ng-icon [svg]="tablerCirclePlus" />
|
|
139
|
+
등록
|
|
140
|
+
</sd-button>
|
|
141
|
+
@if (selectMode() !== "single") {
|
|
142
|
+
<sd-button
|
|
143
|
+
[size]="'sm'"
|
|
144
|
+
[theme]="'link-danger'"
|
|
145
|
+
(click)="delete.emit(currSelectedItems())"
|
|
146
|
+
[disabled]="!hasSelectedNotDeleted()"
|
|
147
|
+
>
|
|
148
|
+
<ng-icon [svg]="tablerEraser" />
|
|
149
|
+
선택 삭제
|
|
150
|
+
</sd-button>
|
|
151
|
+
@if (hasSelectedDeleted()) {
|
|
152
|
+
<sd-button
|
|
153
|
+
[size]="'sm'"
|
|
154
|
+
[theme]="'link-warning'"
|
|
155
|
+
(click)="restore.emit(currSelectedItems())"
|
|
156
|
+
>
|
|
157
|
+
<ng-icon [svg]="tablerRestore" />
|
|
158
|
+
선택 복구
|
|
159
|
+
</sd-button>
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
@if (toolTplRef()) {
|
|
165
|
+
<ng-template [ngTemplateOutlet]="toolTplRef()" />
|
|
166
|
+
}
|
|
167
|
+
</div>
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
@if (readonly()) {
|
|
171
|
+
<div class="flex-fill p-default pt-0">
|
|
172
|
+
<ng-template [ngTemplateOutlet]="sheet" />
|
|
173
|
+
</div>
|
|
174
|
+
} @else {
|
|
175
|
+
<sd-form #formCtrl (formSubmit)="submit.emit()" class="flex-fill p-default pt-0">
|
|
176
|
+
<ng-template [ngTemplateOutlet]="sheet" />
|
|
177
|
+
</sd-form>
|
|
178
|
+
}
|
|
179
|
+
</div>
|
|
180
|
+
</sd-base-container>
|
|
181
|
+
|
|
182
|
+
<ng-template #sheet>
|
|
183
|
+
<sd-sheet
|
|
184
|
+
[key]="key() + '-sheet'"
|
|
185
|
+
[items]="items()"
|
|
186
|
+
[(currentPage)]="currentPage"
|
|
187
|
+
[totalPageCount]="totalPageCount()"
|
|
188
|
+
[(sorts)]="sorts"
|
|
189
|
+
[useAutoSort]="totalPageCount() === 0"
|
|
190
|
+
[selectMode]="selectMode() ?? (readonly() ? undefined : 'multi')"
|
|
191
|
+
[(selectedKeys)]="selectedKeys"
|
|
192
|
+
[trackByFn]="trackByFn()"
|
|
193
|
+
[getItemCellStyleFn]="getItemCellStyleFn"
|
|
194
|
+
[autoSelect]="selectMode() === 'single' ? 'click' : undefined"
|
|
195
|
+
[columnControlsInput]="columnControls()"
|
|
196
|
+
(selectedKeysChange)="onSelectedKeysChange()"
|
|
197
|
+
>
|
|
198
|
+
@if (!readonly()) {
|
|
199
|
+
<sd-sheet-column
|
|
200
|
+
[fixed]="true"
|
|
201
|
+
[key]="'deleteButton'"
|
|
202
|
+
[disableResizing]="true"
|
|
203
|
+
[disableSorting]="true"
|
|
204
|
+
>
|
|
205
|
+
<ng-template #headerTpl>
|
|
206
|
+
<div class="p-xs-sm tx-center">
|
|
207
|
+
<ng-icon [svg]="tablerEraser" />
|
|
208
|
+
</div>
|
|
209
|
+
</ng-template>
|
|
210
|
+
<ng-template [cell]="items()" let-item="item">
|
|
211
|
+
<div class="p-xs-sm">
|
|
212
|
+
<sd-anchor
|
|
213
|
+
[theme]="'danger'"
|
|
214
|
+
(click)="isDeleted(item) ? restore.emit([item]) : delete.emit([item])"
|
|
215
|
+
>
|
|
216
|
+
<ng-icon [svg]="isDeleted(item) ? tablerRestore : tablerEraser" />
|
|
217
|
+
</sd-anchor>
|
|
218
|
+
</div>
|
|
219
|
+
</ng-template>
|
|
220
|
+
</sd-sheet-column>
|
|
221
|
+
}
|
|
222
|
+
</sd-sheet>
|
|
223
|
+
</ng-template>
|
|
224
|
+
`,
|
|
225
|
+
})
|
|
226
|
+
export class SdCrudList<TItem, TKey> {
|
|
227
|
+
private readonly _sdActivatedModal = inject(SdActivatedModalProvider, { optional: true });
|
|
228
|
+
|
|
229
|
+
ready = model(false);
|
|
230
|
+
initialized = input(false);
|
|
231
|
+
busyCount = model(0);
|
|
232
|
+
restricted = input(false);
|
|
233
|
+
readonly = input(false);
|
|
234
|
+
viewType = input.required<SdViewType>();
|
|
235
|
+
selectMode = input<"single" | "multi">();
|
|
236
|
+
key = input.required<string>();
|
|
237
|
+
|
|
238
|
+
formCtrl = viewChild<SdForm>("formCtrl");
|
|
239
|
+
|
|
240
|
+
filterSubmit = output();
|
|
241
|
+
submit = output();
|
|
242
|
+
create = output();
|
|
243
|
+
delete = output<TItem[]>();
|
|
244
|
+
restore = output<TItem[]>();
|
|
245
|
+
|
|
246
|
+
items = input<TItem[]>([]);
|
|
247
|
+
selectedKeys = model<NonNullable<TKey>[]>([]);
|
|
248
|
+
|
|
249
|
+
currDeletedItems = input<TItem[]>([]);
|
|
250
|
+
currDeletedSet = computed(() => new Set(this.currDeletedItems()));
|
|
251
|
+
|
|
252
|
+
currSelectedItems = computed(() =>
|
|
253
|
+
this.items().filter((it) => {
|
|
254
|
+
const key = this.trackByFn()(it);
|
|
255
|
+
return key != null && this.selectedKeys().includes(key);
|
|
256
|
+
}),
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
currentPage = model(0);
|
|
260
|
+
totalPageCount = input(0);
|
|
261
|
+
sorts = model<SortingDef[]>([]);
|
|
262
|
+
|
|
263
|
+
trackByFn = input.required<(item: TItem) => TKey>();
|
|
264
|
+
|
|
265
|
+
commandTplRef = contentChild<TemplateRef<void>>("commandTpl");
|
|
266
|
+
filterTplRef = contentChild<TemplateRef<void>>("filterTpl");
|
|
267
|
+
toolTplRef = contentChild<TemplateRef<void>>("toolTpl");
|
|
268
|
+
bottomCommandTplRef = contentChild<TemplateRef<void>>("bottomCommandTpl");
|
|
269
|
+
|
|
270
|
+
columnControls = contentChildren(SdSheetColumn);
|
|
271
|
+
|
|
272
|
+
hasSelectedDeleted = computed(() => this.currSelectedItems().some((it) => this.isDeleted(it)));
|
|
273
|
+
hasSelectedNotDeleted = computed(() =>
|
|
274
|
+
this.currSelectedItems().some((it) => !this.isDeleted(it)),
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
getItemCellStyleFn = (item: TItem): string | undefined =>
|
|
278
|
+
this.isDeleted(item) ? "text-decoration: line-through;" : undefined;
|
|
279
|
+
|
|
280
|
+
isDeleted(item: TItem) {
|
|
281
|
+
return this.currDeletedSet().has(item);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
onSaveButtonClick() {
|
|
285
|
+
this.formCtrl()?.requestSubmit();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
onModalSelectionCancelClick(): void {
|
|
289
|
+
this.selectedKeys.set([]);
|
|
290
|
+
|
|
291
|
+
if (this.selectMode() === "single") {
|
|
292
|
+
this._sdActivatedModal?.contentComponent().close.emit({ selectedKeys: [] });
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
onModalSelectionConfirmClick() {
|
|
297
|
+
const selectedKeys = this.selectedKeys();
|
|
298
|
+
this._sdActivatedModal?.contentComponent().close.emit({ selectedKeys });
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
onSelectedKeysChange() {
|
|
302
|
+
if (this.viewType() !== "modal") return;
|
|
303
|
+
if (this.selectMode() !== "single") return;
|
|
304
|
+
|
|
305
|
+
const selectedKeys = this.selectedKeys();
|
|
306
|
+
if (selectedKeys.length !== 1) return;
|
|
307
|
+
|
|
308
|
+
this._sdActivatedModal?.contentComponent().close.emit({ selectedKeys });
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
protected readonly tablerDeviceFloppy = tablerDeviceFloppy;
|
|
312
|
+
protected readonly tablerCirclePlus = tablerCirclePlus;
|
|
313
|
+
protected readonly tablerEraser = tablerEraser;
|
|
314
|
+
protected readonly tablerRestore = tablerRestore;
|
|
315
|
+
protected readonly tablerSearch = tablerSearch;
|
|
316
|
+
}
|
|
@@ -234,7 +234,7 @@ export class SdSharedDataSelectList<
|
|
|
234
234
|
...modalInfo,
|
|
235
235
|
inputs: {
|
|
236
236
|
selectMode: "single",
|
|
237
|
-
|
|
237
|
+
selectedKeys: [this.selectedItem()]
|
|
238
238
|
.filter((v): v is NonNullable<typeof v> => v != null)
|
|
239
239
|
.map((item) => item.__valueKey),
|
|
240
240
|
...modalInfo.inputs,
|
|
@@ -243,7 +243,7 @@ export class SdSharedDataSelectList<
|
|
|
243
243
|
|
|
244
244
|
if (result != null) {
|
|
245
245
|
const newSelectedItem = this.items().find(
|
|
246
|
-
(item) => item.__valueKey === result.
|
|
246
|
+
(item) => item.__valueKey === result.selectedKeys[0],
|
|
247
247
|
);
|
|
248
248
|
this.selectedItem.set(newSelectedItem);
|
|
249
249
|
}
|
|
@@ -347,7 +347,7 @@ export class SdSharedDataSelect<
|
|
|
347
347
|
...modalInfo,
|
|
348
348
|
inputs: {
|
|
349
349
|
selectMode: this.selectMode(),
|
|
350
|
-
|
|
350
|
+
selectedKeys: this.selectedKeys(),
|
|
351
351
|
...modalInfo.inputs,
|
|
352
352
|
} as any,
|
|
353
353
|
});
|
|
@@ -355,8 +355,8 @@ export class SdSharedDataSelect<
|
|
|
355
355
|
if (result != null) {
|
|
356
356
|
const newValue =
|
|
357
357
|
this.selectMode() === "multi"
|
|
358
|
-
? result.
|
|
359
|
-
: result.
|
|
358
|
+
? result.selectedKeys
|
|
359
|
+
: result.selectedKeys[0];
|
|
360
360
|
this.value.set(newValue);
|
|
361
361
|
}
|
|
362
362
|
}
|
|
@@ -1,23 +1,33 @@
|
|
|
1
1
|
import { afterEveryRender, type Signal } from "@angular/core";
|
|
2
|
+
import { obj } from "@simplysm/core-common";
|
|
2
3
|
import type { injectSheetDomAccessor } from "./injectSheetDomAccessor";
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
function isKeyEqual(a: unknown, b: unknown): boolean {
|
|
6
|
+
if (a === b) return true;
|
|
7
|
+
return obj.equal(a, b);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function injectSheetSelectRowIndicator<TItem, TKey>(options: {
|
|
5
11
|
domAccessor: ReturnType<typeof injectSheetDomAccessor>;
|
|
6
|
-
|
|
7
|
-
displayItems: Signal<
|
|
12
|
+
selectedKeys: Signal<NonNullable<TKey>[]>;
|
|
13
|
+
displayItems: Signal<TItem[]>;
|
|
14
|
+
trackByFn: Signal<(item: TItem, index: number) => TKey>;
|
|
8
15
|
}) {
|
|
9
16
|
afterEveryRender(() => {
|
|
10
17
|
const containerEl = options.domAccessor.getSelectRowIndicatorContainer();
|
|
11
18
|
|
|
12
|
-
if (options.
|
|
19
|
+
if (options.selectedKeys().length <= 0) {
|
|
13
20
|
containerEl.innerHTML = "";
|
|
14
21
|
containerEl.style.display = "none";
|
|
15
22
|
return;
|
|
16
23
|
}
|
|
17
24
|
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
const trackByFn = options.trackByFn();
|
|
26
|
+
const displayItems = options.displayItems();
|
|
27
|
+
|
|
28
|
+
const selectedTrInfos = options.selectedKeys()
|
|
29
|
+
.map((key) => {
|
|
30
|
+
const r = displayItems.findIndex((item, i) => isKeyEqual(trackByFn(item, i), key));
|
|
21
31
|
return getTrInfo(r);
|
|
22
32
|
})
|
|
23
33
|
.filter((info): info is NonNullable<typeof info> => info != null);
|
|
@@ -55,7 +55,15 @@ import { SdEvents } from "../../core/events/sd-events";
|
|
|
55
55
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
56
56
|
encapsulation: ViewEncapsulation.None,
|
|
57
57
|
standalone: true,
|
|
58
|
-
imports: [
|
|
58
|
+
imports: [
|
|
59
|
+
NgTemplateOutlet,
|
|
60
|
+
SdCheckbox,
|
|
61
|
+
NgIcon,
|
|
62
|
+
SdPagination,
|
|
63
|
+
SdAnchor,
|
|
64
|
+
SdButton,
|
|
65
|
+
SdResizeDirective,
|
|
66
|
+
],
|
|
59
67
|
hostDirectives: [
|
|
60
68
|
{ directive: SdEvents, outputs: ["keydown.capture", "focus.capture", "blur.capture"] },
|
|
61
69
|
],
|
|
@@ -69,7 +77,7 @@ import { SdEvents } from "../../core/events/sd-events";
|
|
|
69
77
|
"(blur.capture)": "onBlurCapture($event)",
|
|
70
78
|
},
|
|
71
79
|
template: `
|
|
72
|
-
@if ((key() || effectivePageCount() >
|
|
80
|
+
@if ((key() || effectivePageCount() > 1) && !hideConfigBar()) {
|
|
73
81
|
<div class="_tool flex-row gap-sm p-xs">
|
|
74
82
|
@if (key()) {
|
|
75
83
|
<sd-button [theme]="'link-primary'" [size]="'sm'" (click)="onConfigButtonClick()">
|
|
@@ -138,13 +146,13 @@ import { SdEvents } from "../../core/events/sd-events";
|
|
|
138
146
|
@for (cell of row; track $index) {
|
|
139
147
|
@if (!cell.isLastRow) {
|
|
140
148
|
<th
|
|
141
|
-
[class._fixed]="cell.
|
|
149
|
+
[class._fixed]="cell.fixed"
|
|
142
150
|
[attr.colspan]="cell.colspan > 1 ? cell.colspan : null"
|
|
143
151
|
[attr.rowspan]="cell.rowspan > 1 ? cell.rowspan : null"
|
|
144
152
|
[attr.data-c]="cell.colIndex"
|
|
145
153
|
[attr.title]="cell.text"
|
|
146
154
|
[style.left.px]="
|
|
147
|
-
cell.
|
|
155
|
+
cell.fixed ? fixing.fixedLeftMap().get(cell.colIndex) : null
|
|
148
156
|
"
|
|
149
157
|
>
|
|
150
158
|
<div class="_p-sheet">
|
|
@@ -153,13 +161,14 @@ import { SdEvents } from "../../core/events/sd-events";
|
|
|
153
161
|
</th>
|
|
154
162
|
} @else {
|
|
155
163
|
<th
|
|
156
|
-
[class._fixed]="cell.
|
|
164
|
+
[class._fixed]="cell.fixed"
|
|
157
165
|
class="_last-depth"
|
|
158
166
|
[class._sort]="cell.colDef && !cell.colDef.disableSorting"
|
|
159
167
|
[attr.colspan]="cell.colspan > 1 ? cell.colspan : null"
|
|
160
168
|
[attr.rowspan]="cell.rowspan > 1 ? cell.rowspan : null"
|
|
161
169
|
[attr.data-c]="cell.colIndex"
|
|
162
170
|
[attr.title]="cell.colDef?.tooltip ?? cell.text"
|
|
171
|
+
[attr.aria-sort]="getAriaSortValue(cell)"
|
|
163
172
|
[class.help]="cell.colDef?.tooltip"
|
|
164
173
|
[style]="getHeaderCellStyle(cell)"
|
|
165
174
|
(sdResize)="onFixedCellResize($event, cell.colIndex)"
|
|
@@ -231,7 +240,11 @@ import { SdEvents } from "../../core/events/sd-events";
|
|
|
231
240
|
}
|
|
232
241
|
</thead>
|
|
233
242
|
<tbody>
|
|
234
|
-
@for (
|
|
243
|
+
@for (
|
|
244
|
+
item of displayItems();
|
|
245
|
+
track trackByFn()(item, $index) ?? item;
|
|
246
|
+
let rowIdx = $index
|
|
247
|
+
) {
|
|
235
248
|
<tr
|
|
236
249
|
[attr.data-r]="rowIdx"
|
|
237
250
|
(click)="onRowClick(item)"
|
|
@@ -254,15 +267,16 @@ import { SdEvents } from "../../core/events/sd-events";
|
|
|
254
267
|
[inline]="true"
|
|
255
268
|
[theme]="'white'"
|
|
256
269
|
[disabled]="_selectable !== true"
|
|
257
|
-
[attr.title]="
|
|
270
|
+
[attr.title]="getSelectableTooltip(item)"
|
|
258
271
|
/>
|
|
259
272
|
} @else if (selectMode() === "single") {
|
|
260
273
|
@let _selectable = selection.getSelectable(item);
|
|
261
274
|
@if (_selectable === true && selection.getCanChangeFn(item)) {
|
|
262
275
|
<sd-anchor
|
|
263
276
|
[theme]="selection.isSelected(item) ? 'primary' : 'gray'"
|
|
264
|
-
(pointerdown)="selection.toggle(item)"
|
|
265
|
-
|
|
277
|
+
(pointerdown)="$event.stopPropagation(); selection.toggle(item)"
|
|
278
|
+
(click)="$event.stopPropagation()"
|
|
279
|
+
[attr.title]="getSelectableTooltip(item)"
|
|
266
280
|
>
|
|
267
281
|
<ng-icon [svg]="icons.tablerArrowRight" />
|
|
268
282
|
</sd-anchor>
|
|
@@ -288,6 +302,7 @@ import { SdEvents } from "../../core/events/sd-events";
|
|
|
288
302
|
[svg]="icons.tablerCaretRight"
|
|
289
303
|
[style.transform]="isExpanded(item) ? 'rotate(90deg)' : undefined"
|
|
290
304
|
[class.tx-theme-primary-default]="isExpanded(item)"
|
|
305
|
+
[attr.aria-expanded]="getAriaExpanded(item)"
|
|
291
306
|
(click)="onExpandClick($event, item)"
|
|
292
307
|
/>
|
|
293
308
|
}
|
|
@@ -567,15 +582,15 @@ import { SdEvents } from "../../core/events/sd-events";
|
|
|
567
582
|
`,
|
|
568
583
|
],
|
|
569
584
|
})
|
|
570
|
-
export class SdSheet<
|
|
585
|
+
export class SdSheet<TItem, TKey> {
|
|
571
586
|
// Inputs
|
|
572
587
|
key = input<string>();
|
|
573
|
-
items = input<
|
|
574
|
-
trackByFn = input<(item:
|
|
588
|
+
items = input<TItem[]>([]);
|
|
589
|
+
trackByFn = input.required<(item: TItem, index: number) => TKey>();
|
|
575
590
|
selectMode = input<"single" | "multi">();
|
|
576
591
|
autoSelect = input<"click" | "focus">();
|
|
577
|
-
getItemSelectableFn = input<(item:
|
|
578
|
-
getChildrenFn = input<(item:
|
|
592
|
+
getItemSelectableFn = input<(item: TItem) => boolean | string>();
|
|
593
|
+
getChildrenFn = input<(item: TItem, index: number) => TItem[] | undefined>();
|
|
579
594
|
useAutoSort = input(false, { transform: booleanAttribute });
|
|
580
595
|
visiblePageCount = input(10);
|
|
581
596
|
totalPageCount = input(0);
|
|
@@ -583,26 +598,27 @@ export class SdSheet<T> {
|
|
|
583
598
|
focusMode = input<"row" | "cell">("cell");
|
|
584
599
|
inset = input(false, { transform: booleanAttribute });
|
|
585
600
|
contentStyle = input<string>();
|
|
586
|
-
getItemCellClassFn = input<(item:
|
|
587
|
-
getItemCellStyleFn = input<(item:
|
|
601
|
+
getItemCellClassFn = input<(item: TItem, colKey: string) => string>();
|
|
602
|
+
getItemCellStyleFn = input<(item: TItem, colKey: string) => string | undefined>();
|
|
588
603
|
hideConfigBar = input(false, { transform: booleanAttribute });
|
|
589
604
|
|
|
590
605
|
// Outputs
|
|
591
|
-
itemKeydown = output<SdSheetItemKeydownEventParam<
|
|
592
|
-
cellKeydown = output<SdSheetCellKeydownEventParam<
|
|
606
|
+
itemKeydown = output<SdSheetItemKeydownEventParam<TItem>>();
|
|
607
|
+
cellKeydown = output<SdSheetCellKeydownEventParam<TItem>>();
|
|
593
608
|
|
|
594
609
|
// Models
|
|
595
|
-
|
|
596
|
-
expandedItems = model<
|
|
610
|
+
selectedKeys = model<NonNullable<TKey>[]>([]);
|
|
611
|
+
expandedItems = model<TItem[]>([]);
|
|
597
612
|
sorts = model<SortingDef[]>([]); // Re-exported from useSortingManager
|
|
598
613
|
currentPage = model(0);
|
|
599
614
|
|
|
600
615
|
// Content query
|
|
601
616
|
columnControls = contentChildren(SdSheetColumn);
|
|
602
|
-
columnControlsInput = input<readonly SdSheetColumn[]>();
|
|
603
|
-
private readonly _effectiveColumnControls = computed(
|
|
604
|
-
|
|
605
|
-
|
|
617
|
+
columnControlsInput = input<readonly SdSheetColumn[]>([]);
|
|
618
|
+
private readonly _effectiveColumnControls = computed(() => [
|
|
619
|
+
...this.columnControls(),
|
|
620
|
+
...this.columnControlsInput(),
|
|
621
|
+
]);
|
|
606
622
|
|
|
607
623
|
// Injected providers
|
|
608
624
|
private readonly _sdModal = inject(SdModalProvider);
|
|
@@ -638,7 +654,7 @@ export class SdSheet<T> {
|
|
|
638
654
|
});
|
|
639
655
|
|
|
640
656
|
// Display pipeline (sort → page → expand → display)
|
|
641
|
-
private readonly _pipeline = useSheetDisplayPipeline<
|
|
657
|
+
private readonly _pipeline = useSheetDisplayPipeline<TItem>({
|
|
642
658
|
items: this.items,
|
|
643
659
|
useAutoSort: this.useAutoSort,
|
|
644
660
|
sortItems: (items) => this.sorting.sort(items),
|
|
@@ -662,7 +678,7 @@ export class SdSheet<T> {
|
|
|
662
678
|
});
|
|
663
679
|
|
|
664
680
|
// Cell styling composable
|
|
665
|
-
private readonly _styling = useSheetCellStyling<
|
|
681
|
+
private readonly _styling = useSheetCellStyling<TItem>({
|
|
666
682
|
columnDefs: this.layout.columnDefs,
|
|
667
683
|
fixedLeftMap: this.fixing.fixedLeftMap,
|
|
668
684
|
getItemCellStyleFn: this.getItemCellStyleFn,
|
|
@@ -683,16 +699,17 @@ export class SdSheet<T> {
|
|
|
683
699
|
});
|
|
684
700
|
|
|
685
701
|
// Select row indicator
|
|
686
|
-
private readonly _selectRowIndicator = injectSheetSelectRowIndicator<
|
|
702
|
+
private readonly _selectRowIndicator = injectSheetSelectRowIndicator<TItem, TKey>({
|
|
687
703
|
domAccessor: this.domAccessor,
|
|
688
|
-
|
|
704
|
+
selectedKeys: this.selectedKeys,
|
|
689
705
|
displayItems: this.displayItems,
|
|
706
|
+
trackByFn: this.trackByFn,
|
|
690
707
|
});
|
|
691
708
|
|
|
692
709
|
// Selection manager
|
|
693
|
-
selection = useSelectionManager<
|
|
710
|
+
selection = useSelectionManager<TItem, TKey>({
|
|
694
711
|
displayItems: this.displayItems,
|
|
695
|
-
|
|
712
|
+
selectedKeys: this.selectedKeys,
|
|
696
713
|
selectMode: this.selectMode,
|
|
697
714
|
getItemSelectableFn: this.getItemSelectableFn,
|
|
698
715
|
trackByFn: this.trackByFn,
|
|
@@ -731,25 +748,25 @@ export class SdSheet<T> {
|
|
|
731
748
|
return col?.summaryTplRef() ?? null;
|
|
732
749
|
}
|
|
733
750
|
|
|
734
|
-
getSelectableTooltip(item:
|
|
751
|
+
getSelectableTooltip(item: TItem): string | undefined {
|
|
735
752
|
const result = this.selection.getSelectable(item);
|
|
736
753
|
if (typeof result === "string") return result;
|
|
737
|
-
return
|
|
754
|
+
return undefined;
|
|
738
755
|
}
|
|
739
756
|
|
|
740
|
-
onRowClick(item:
|
|
757
|
+
onRowClick(item: TItem): void {
|
|
741
758
|
if (this.autoSelect() === "click") {
|
|
742
759
|
this.selection.select(item);
|
|
743
760
|
}
|
|
744
761
|
}
|
|
745
762
|
|
|
746
|
-
onCellClick(event: Event, item:
|
|
763
|
+
onCellClick(event: Event, item: TItem): void {
|
|
747
764
|
if (this.autoSelect() === "click") {
|
|
748
765
|
this.selection.select(item);
|
|
749
766
|
}
|
|
750
767
|
}
|
|
751
768
|
|
|
752
|
-
onCellFocus(item:
|
|
769
|
+
onCellFocus(item: TItem): void {
|
|
753
770
|
if (this.autoSelect() === "focus") {
|
|
754
771
|
this.selection.select(item);
|
|
755
772
|
}
|
|
@@ -766,25 +783,25 @@ export class SdSheet<T> {
|
|
|
766
783
|
return this.sorting.defMap().get(key) ?? null;
|
|
767
784
|
}
|
|
768
785
|
|
|
769
|
-
getItemDef(item:
|
|
786
|
+
getItemDef(item: TItem) {
|
|
770
787
|
return this.expanding.def(item);
|
|
771
788
|
}
|
|
772
789
|
|
|
773
790
|
// PERF-005: Set-based lookup for O(1) isExpanded check
|
|
774
791
|
private readonly _expandedSet = computed(() => new Set(this.expandedItems()));
|
|
775
792
|
|
|
776
|
-
isExpanded(item:
|
|
793
|
+
isExpanded(item: TItem): boolean {
|
|
777
794
|
return this._expandedSet().has(item);
|
|
778
795
|
}
|
|
779
796
|
|
|
780
|
-
getAriaExpanded(item:
|
|
797
|
+
getAriaExpanded(item: TItem): string | null {
|
|
781
798
|
if (this.getChildrenFn() == null) return null;
|
|
782
799
|
const def = this.getItemDef(item);
|
|
783
800
|
if (!def.hasChildren) return null;
|
|
784
801
|
return this.isExpanded(item) ? "true" : "false";
|
|
785
802
|
}
|
|
786
803
|
|
|
787
|
-
onExpandClick(event: Event, item:
|
|
804
|
+
onExpandClick(event: Event, item: TItem): void {
|
|
788
805
|
event.stopPropagation();
|
|
789
806
|
this.expanding.toggle(item);
|
|
790
807
|
}
|
|
@@ -819,7 +836,7 @@ export class SdSheet<T> {
|
|
|
819
836
|
}
|
|
820
837
|
|
|
821
838
|
onTableResize(event: SdResizeEvent): void {
|
|
822
|
-
if (!event.widthChanged) return;
|
|
839
|
+
if (!event.widthChanged && !event.heightChanged) return;
|
|
823
840
|
this._focusIndicator.redraw();
|
|
824
841
|
this._selectRowIndicator.redraw();
|
|
825
842
|
}
|
|
@@ -888,11 +905,11 @@ export class SdSheet<T> {
|
|
|
888
905
|
);
|
|
889
906
|
}
|
|
890
907
|
|
|
891
|
-
onItemKeydown(event: KeyboardEvent, item:
|
|
908
|
+
onItemKeydown(event: KeyboardEvent, item: TItem): void {
|
|
892
909
|
this.itemKeydown.emit({ item, event });
|
|
893
910
|
}
|
|
894
911
|
|
|
895
|
-
onCellKeydown(event: KeyboardEvent, item:
|
|
912
|
+
onCellKeydown(event: KeyboardEvent, item: TItem, colKey: string): void {
|
|
896
913
|
this.cellKeydown.emit({ item, key: colKey, event });
|
|
897
914
|
}
|
|
898
915
|
|