@simplysm/angular 14.0.10 → 14.0.12
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/README.md +8 -5
- package/dist/core/directives/sd-router-link.directive.js +2 -2
- package/dist/core/pipes/format.pipe.d.ts.map +1 -1
- package/dist/core/pipes/format.pipe.js +2 -0
- package/dist/core/plugins/commands/findTopOpenModalEl.d.ts +1 -1
- package/dist/core/plugins/commands/findTopOpenModalEl.d.ts.map +1 -1
- package/dist/core/plugins/commands/findTopOpenModalEl.js +2 -2
- package/dist/core/plugins/commands/sd-insert-command-event.plugin.d.ts +1 -0
- package/dist/core/plugins/commands/sd-insert-command-event.plugin.d.ts.map +1 -1
- package/dist/core/plugins/commands/sd-insert-command-event.plugin.js +4 -3
- package/dist/core/plugins/commands/sd-refresh-command-event.plugin.d.ts +1 -0
- package/dist/core/plugins/commands/sd-refresh-command-event.plugin.d.ts.map +1 -1
- package/dist/core/plugins/commands/sd-refresh-command-event.plugin.js +4 -3
- package/dist/core/plugins/commands/sd-save-command-event.plugin.d.ts +1 -0
- package/dist/core/plugins/commands/sd-save-command-event.plugin.d.ts.map +1 -1
- package/dist/core/plugins/commands/sd-save-command-event.plugin.js +4 -3
- package/dist/core/plugins/events/sd-intersection-event.plugin.d.ts.map +1 -1
- package/dist/core/plugins/events/sd-intersection-event.plugin.js +2 -3
- package/dist/core/plugins/events/sd-option-event.plugin.d.ts +1 -0
- package/dist/core/plugins/events/sd-option-event.plugin.d.ts.map +1 -1
- package/dist/core/plugins/events/sd-option-event.plugin.js +2 -1
- package/dist/core/plugins/sd-global-error-handler.plugin.d.ts.map +1 -1
- package/dist/core/plugins/sd-global-error-handler.plugin.js +3 -1
- package/dist/core/provideSdAngular.d.ts.map +1 -1
- package/dist/core/provideSdAngular.js +14 -4
- package/dist/core/providers/sd-app-structure.provider.d.ts.map +1 -1
- package/dist/core/providers/sd-app-structure.provider.js +8 -7
- package/dist/core/providers/sd-file-dialog.provider.d.ts.map +1 -1
- package/dist/core/providers/sd-file-dialog.provider.js +15 -7
- package/dist/core/providers/sd-local-storage.provider.d.ts.map +1 -1
- package/dist/core/providers/sd-local-storage.provider.js +6 -1
- package/dist/core/providers/sd-navigate-window.provider.js +4 -1
- package/dist/core/providers/sd-print.provider.js +2 -2
- package/dist/core/providers/sd-shared-data.provider.d.ts.map +1 -1
- package/dist/core/providers/sd-shared-data.provider.js +14 -3
- package/dist/core/utils/injectParent.js +9 -5
- package/dist/core/utils/setups/setupModelHook.d.ts.map +1 -1
- package/dist/core/utils/setups/setupModelHook.js +4 -1
- package/dist/core/utils/setups/setupRevealOnShow.d.ts.map +1 -1
- package/dist/core/utils/setups/setupRevealOnShow.js +3 -2
- package/dist/core/utils/useExpandingManager.d.ts.map +1 -1
- package/dist/core/utils/useExpandingManager.js +3 -2
- package/dist/core/utils/useSdSystemConfigResource.d.ts +1 -1
- package/dist/core/utils/useSdSystemConfigResource.d.ts.map +1 -1
- package/dist/core/utils/useSdSystemConfigResource.js +13 -12
- package/dist/core/utils/withBusy.d.ts +3 -0
- package/dist/core/utils/withBusy.d.ts.map +1 -0
- package/dist/core/utils/withBusy.js +9 -0
- package/dist/features/address/sd-address-search.modal.d.ts.map +1 -1
- package/dist/features/address/sd-address-search.modal.js +5 -2
- package/dist/features/data-view/sd-data-detail.control.d.ts +7 -4
- package/dist/features/data-view/sd-data-detail.control.d.ts.map +1 -1
- package/dist/features/data-view/sd-data-detail.control.js +75 -68
- package/dist/features/data-view/sd-data-sheet.control.d.ts +2 -0
- package/dist/features/data-view/sd-data-sheet.control.d.ts.map +1 -1
- package/dist/features/data-view/sd-data-sheet.control.js +134 -110
- package/dist/features/permission-table/sd-permission-table.control.d.ts +32 -0
- package/dist/features/permission-table/sd-permission-table.control.d.ts.map +1 -0
- package/dist/features/permission-table/sd-permission-table.control.js +467 -0
- package/dist/features/shared-data/matchesSearchText.d.ts +2 -0
- package/dist/features/shared-data/matchesSearchText.d.ts.map +1 -0
- package/dist/features/shared-data/matchesSearchText.js +11 -0
- package/dist/features/shared-data/sd-shared-data-select-button.control.d.ts +2 -2
- package/dist/features/shared-data/sd-shared-data-select-button.control.d.ts.map +1 -1
- package/dist/features/shared-data/sd-shared-data-select-list.control.d.ts +1 -0
- package/dist/features/shared-data/sd-shared-data-select-list.control.d.ts.map +1 -1
- package/dist/features/shared-data/sd-shared-data-select-list.control.js +16 -11
- package/dist/features/shared-data/sd-shared-data-select.control.d.ts +2 -1
- package/dist/features/shared-data/sd-shared-data-select.control.d.ts.map +1 -1
- package/dist/features/shared-data/sd-shared-data-select.control.js +45 -30
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/ui/data/sheet/sd-sheet.control.d.ts +5 -2
- package/dist/ui/data/sheet/sd-sheet.control.d.ts.map +1 -1
- package/dist/ui/data/sheet/sd-sheet.control.js +48 -47
- package/dist/ui/data/sheet/types.d.ts +5 -1
- package/dist/ui/data/sheet/types.d.ts.map +1 -1
- package/dist/ui/data/sheet/useSheetCellAgent.d.ts.map +1 -1
- package/dist/ui/data/sheet/useSheetCellAgent.js +8 -4
- package/dist/ui/data/sheet/useSheetColumnFixing.d.ts +6 -0
- package/dist/ui/data/sheet/useSheetColumnFixing.d.ts.map +1 -1
- package/dist/ui/data/sheet/useSheetColumnFixing.js +6 -0
- package/dist/ui/form/editor/sd-tiptap-editor.control.d.ts +2 -2
- package/dist/ui/form/editor/sd-tiptap-editor.control.d.ts.map +1 -1
- package/dist/ui/form/editor/sd-tiptap-editor.control.js +15 -13
- package/dist/ui/form/input/sd-date-range.picker.d.ts.map +1 -1
- package/dist/ui/form/input/sd-date-range.picker.js +8 -1
- package/dist/ui/form/select/sd-select.control.d.ts.map +1 -1
- package/dist/ui/form/select/sd-select.control.js +20 -16
- package/dist/ui/layout/dock/sd-dock.control.js +4 -4
- package/dist/ui/navigation/pagination/sd-pagination.control.js +1 -1
- package/dist/ui/navigation/sidebar/sd-sidebar-menu.control.d.ts +7 -13
- package/dist/ui/navigation/sidebar/sd-sidebar-menu.control.d.ts.map +1 -1
- package/dist/ui/navigation/sidebar/sd-sidebar-menu.control.js +1 -1
- package/dist/ui/navigation/topbar/sd-topbar-menu.control.d.ts +7 -13
- package/dist/ui/navigation/topbar/sd-topbar-menu.control.d.ts.map +1 -1
- package/dist/ui/navigation/topbar/sd-topbar-menu.control.js +1 -1
- package/dist/ui/overlay/dropdown/sd-dropdown-popup.control.d.ts +0 -3
- package/dist/ui/overlay/dropdown/sd-dropdown-popup.control.d.ts.map +1 -1
- package/dist/ui/overlay/dropdown/sd-dropdown-popup.control.js +8 -21
- package/dist/ui/overlay/dropdown/sd-dropdown.control.d.ts +1 -1
- package/dist/ui/overlay/dropdown/sd-dropdown.control.d.ts.map +1 -1
- package/dist/ui/overlay/dropdown/sd-dropdown.control.js +19 -20
- package/dist/ui/overlay/modal/sd-modal.control.d.ts.map +1 -1
- package/dist/ui/overlay/modal/sd-modal.control.js +21 -5
- package/dist/ui/overlay/modal/sd-modal.provider.d.ts.map +1 -1
- package/dist/ui/overlay/modal/sd-modal.provider.js +12 -0
- package/dist/ui/overlay/toast/sd-toast.provider.d.ts +1 -0
- package/dist/ui/overlay/toast/sd-toast.provider.d.ts.map +1 -1
- package/dist/ui/overlay/toast/sd-toast.provider.js +16 -2
- package/dist/ui/visual/sd-barcode.control.d.ts +2 -2
- package/dist/ui/visual/sd-barcode.control.d.ts.map +1 -1
- package/dist/ui/visual/sd-barcode.control.js +25 -18
- package/dist/ui/visual/sd-progress.control.d.ts.map +1 -1
- package/dist/ui/visual/sd-progress.control.js +1 -1
- package/docs/core.md +19 -0
- package/docs/features.md +25 -0
- package/docs/ui-data.md +20 -4
- package/docs/ui-navigation.md +8 -44
- package/package.json +25 -25
- package/src/core/directives/sd-router-link.directive.ts +1 -1
- package/src/core/pipes/format.pipe.ts +1 -0
- package/src/core/plugins/commands/findTopOpenModalEl.ts +2 -2
- package/src/core/plugins/commands/sd-insert-command-event.plugin.ts +5 -3
- package/src/core/plugins/commands/sd-refresh-command-event.plugin.ts +5 -3
- package/src/core/plugins/commands/sd-save-command-event.plugin.ts +5 -3
- package/src/core/plugins/events/sd-intersection-event.plugin.ts +2 -3
- package/src/core/plugins/events/sd-option-event.plugin.ts +3 -1
- package/src/core/plugins/sd-global-error-handler.plugin.ts +4 -1
- package/src/core/provideSdAngular.ts +17 -4
- package/src/core/providers/sd-app-structure.provider.ts +7 -7
- package/src/core/providers/sd-file-dialog.provider.ts +18 -9
- package/src/core/providers/sd-local-storage.provider.ts +5 -1
- package/src/core/providers/sd-navigate-window.provider.ts +3 -3
- package/src/core/providers/sd-print.provider.ts +2 -2
- package/src/core/providers/sd-shared-data.provider.ts +14 -3
- package/src/core/utils/injectParent.ts +10 -6
- package/src/core/utils/setups/setupModelHook.ts +6 -1
- package/src/core/utils/setups/setupRevealOnShow.ts +3 -2
- package/src/core/utils/useExpandingManager.ts +4 -2
- package/src/core/utils/useSdSystemConfigResource.ts +13 -11
- package/src/core/utils/withBusy.ts +13 -0
- package/src/features/address/sd-address-search.modal.ts +5 -2
- package/src/features/data-view/sd-data-detail.control.ts +74 -51
- package/src/features/data-view/sd-data-sheet.control.ts +84 -60
- package/src/features/permission-table/sd-permission-table.control.ts +461 -0
- package/src/features/shared-data/matchesSearchText.ts +16 -0
- package/src/features/shared-data/sd-shared-data-select-button.control.ts +4 -4
- package/src/features/shared-data/sd-shared-data-select-list.control.ts +19 -11
- package/src/features/shared-data/sd-shared-data-select.control.ts +51 -31
- package/src/index.ts +7 -8
- package/src/ui/data/sheet/sd-sheet.control.ts +51 -48
- package/src/ui/data/sheet/types.ts +6 -1
- package/src/ui/data/sheet/useSheetCellAgent.ts +5 -3
- package/src/ui/data/sheet/useSheetColumnFixing.ts +6 -0
- package/src/ui/form/editor/sd-tiptap-editor.control.ts +14 -12
- package/src/ui/form/input/sd-date-range.picker.ts +7 -1
- package/src/ui/form/select/sd-select.control.ts +18 -14
- package/src/ui/layout/dock/sd-dock.control.ts +4 -4
- package/src/ui/navigation/pagination/sd-pagination.control.ts +1 -1
- package/src/ui/navigation/sidebar/sd-sidebar-menu.control.ts +7 -14
- package/src/ui/navigation/topbar/sd-topbar-menu.control.ts +7 -14
- package/src/ui/overlay/dropdown/sd-dropdown-popup.control.ts +2 -17
- package/src/ui/overlay/dropdown/sd-dropdown.control.ts +19 -19
- package/src/ui/overlay/modal/sd-modal.control.ts +21 -5
- package/src/ui/overlay/modal/sd-modal.provider.ts +13 -0
- package/src/ui/overlay/toast/sd-toast.provider.ts +14 -1
- package/src/ui/visual/sd-barcode.control.ts +18 -16
- package/src/ui/visual/sd-progress.control.ts +1 -1
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function matchesSearchText(
|
|
2
|
+
itemText: string,
|
|
3
|
+
searchQuery: string | undefined,
|
|
4
|
+
): boolean {
|
|
5
|
+
const terms =
|
|
6
|
+
searchQuery
|
|
7
|
+
?.trim()
|
|
8
|
+
.split(" ")
|
|
9
|
+
.map((t) => t.trim())
|
|
10
|
+
.filter((t) => t !== "") ?? [];
|
|
11
|
+
|
|
12
|
+
if (terms.length === 0) return true;
|
|
13
|
+
|
|
14
|
+
const lowerItemText = itemText.toLowerCase();
|
|
15
|
+
return terms.every((term) => lowerItemText.includes(term.toLowerCase()));
|
|
16
|
+
}
|
|
@@ -46,10 +46,10 @@ import type { TSelectModeValue } from "../../ui/form/select/sd-select.control";
|
|
|
46
46
|
`,
|
|
47
47
|
})
|
|
48
48
|
export class SdSharedDataSelectButtonControl<
|
|
49
|
-
TItem extends ISharedDataBase<number>,
|
|
50
|
-
TMode extends keyof TSelectModeValue<number>,
|
|
49
|
+
TItem extends ISharedDataBase<string | number>,
|
|
50
|
+
TMode extends keyof TSelectModeValue<string | number>,
|
|
51
51
|
TModal extends ISdSelectModal<any>,
|
|
52
|
-
> extends AbsSdDataSelectButton<TItem, number, TMode> {
|
|
52
|
+
> extends AbsSdDataSelectButton<TItem, string | number, TMode> {
|
|
53
53
|
items = input<TItem[]>([]);
|
|
54
54
|
modal = input.required<TSdSelectModalInfo<TModal>>();
|
|
55
55
|
|
|
@@ -58,7 +58,7 @@ export class SdSharedDataSelectButtonControl<
|
|
|
58
58
|
{ read: TemplateRef },
|
|
59
59
|
);
|
|
60
60
|
|
|
61
|
-
override load(keys: number[]): TItem[] {
|
|
61
|
+
override load(keys: (string | number)[]): TItem[] {
|
|
62
62
|
return this.items().filter((item) => keys.includes(item.__valueKey));
|
|
63
63
|
}
|
|
64
64
|
}
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
ViewEncapsulation,
|
|
16
16
|
} from "@angular/core";
|
|
17
17
|
import { str } from "@simplysm/core-common";
|
|
18
|
+
import { matchesSearchText } from "./matchesSearchText";
|
|
18
19
|
import type { ISharedDataBase } from "../../core/providers/sd-shared-data.provider";
|
|
19
20
|
import {
|
|
20
21
|
SdItemOfTemplateDirective,
|
|
@@ -163,20 +164,13 @@ export class SdSharedDataSelectListControl<
|
|
|
163
164
|
|
|
164
165
|
pageItemCount = input<number>();
|
|
165
166
|
page = signal(0);
|
|
166
|
-
pageLength = computed(() => {
|
|
167
|
-
const pic = this.pageItemCount();
|
|
168
|
-
if (pic != null && pic > 0) {
|
|
169
|
-
return Math.ceil(this.items().length / pic);
|
|
170
|
-
}
|
|
171
|
-
return 0;
|
|
172
|
-
});
|
|
173
167
|
|
|
174
|
-
|
|
168
|
+
private readonly _filteredItems = computed(() => {
|
|
175
169
|
let result = this.items().filter((item) => !item.__isHidden);
|
|
176
170
|
|
|
177
171
|
if (!str.isNullOrEmpty(this.searchText())) {
|
|
178
172
|
result = result.filter((item) =>
|
|
179
|
-
item.__searchText
|
|
173
|
+
matchesSearchText(item.__searchText, this.searchText()),
|
|
180
174
|
);
|
|
181
175
|
}
|
|
182
176
|
|
|
@@ -184,12 +178,26 @@ export class SdSharedDataSelectListControl<
|
|
|
184
178
|
result = result.filter((item, i) => this.filterFn()!(item, i));
|
|
185
179
|
}
|
|
186
180
|
|
|
181
|
+
return result;
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
pageLength = computed(() => {
|
|
185
|
+
const pic = this.pageItemCount();
|
|
186
|
+
if (pic != null && pic > 0) {
|
|
187
|
+
return Math.ceil(this._filteredItems().length / pic);
|
|
188
|
+
}
|
|
189
|
+
return 0;
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
displayItems = computed(() => {
|
|
193
|
+
const filtered = this._filteredItems();
|
|
194
|
+
|
|
187
195
|
const pic = this.pageItemCount();
|
|
188
196
|
if (pic != null && pic > 0) {
|
|
189
|
-
|
|
197
|
+
return filtered.slice(pic * this.page(), pic * (this.page() + 1));
|
|
190
198
|
}
|
|
191
199
|
|
|
192
|
-
return
|
|
200
|
+
return filtered;
|
|
193
201
|
});
|
|
194
202
|
|
|
195
203
|
constructor() {
|
|
@@ -35,6 +35,7 @@ import type {
|
|
|
35
35
|
} from "../../ui/form/button/sd-modal-select-button.control";
|
|
36
36
|
import { NgIcon } from "@ng-icons/core";
|
|
37
37
|
import { tablerEdit, tablerSearch } from "@ng-icons/tabler-icons";
|
|
38
|
+
import { matchesSearchText } from "./matchesSearchText";
|
|
38
39
|
|
|
39
40
|
@Component({
|
|
40
41
|
selector: "sd-shared-data-select",
|
|
@@ -235,6 +236,46 @@ export class SdSharedDataSelectControl<
|
|
|
235
236
|
return result;
|
|
236
237
|
});
|
|
237
238
|
|
|
239
|
+
private readonly _searchTextMatchCache = computed(() => {
|
|
240
|
+
const cache = new Map<TItem["__valueKey"], boolean>();
|
|
241
|
+
const searchText = this.searchText();
|
|
242
|
+
const getSearchTextFn = this.getSearchTextFn();
|
|
243
|
+
const hasParent = this.hasParentKey();
|
|
244
|
+
const parentMap = this.itemByParentKeyMap();
|
|
245
|
+
const items = this.items();
|
|
246
|
+
|
|
247
|
+
const check = (item: TItem, index: number): boolean => {
|
|
248
|
+
const key = item.__valueKey;
|
|
249
|
+
const cached = cache.get(key);
|
|
250
|
+
if (cached !== undefined) return cached;
|
|
251
|
+
|
|
252
|
+
const itemText = getSearchTextFn(item, index);
|
|
253
|
+
if (matchesSearchText(itemText, searchText)) {
|
|
254
|
+
cache.set(key, true);
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (hasParent && parentMap != null) {
|
|
259
|
+
const children = parentMap.get(key as TItem["__valueKey"]) ?? [];
|
|
260
|
+
for (let i = 0; i < children.length; i++) {
|
|
261
|
+
if (check(children[i], i)) {
|
|
262
|
+
cache.set(key, true);
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
cache.set(key, false);
|
|
269
|
+
return false;
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
for (let i = 0; i < items.length; i++) {
|
|
273
|
+
check(items[i], i);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return cache;
|
|
277
|
+
});
|
|
278
|
+
|
|
238
279
|
selectedKeys = computed((): any[] => {
|
|
239
280
|
const val = this.value();
|
|
240
281
|
if (val == null) return [];
|
|
@@ -243,19 +284,22 @@ export class SdSharedDataSelectControl<
|
|
|
243
284
|
});
|
|
244
285
|
|
|
245
286
|
constructor() {
|
|
246
|
-
// 드롭다운 닫힘 시 검색어 초기화
|
|
287
|
+
// 드롭다운 닫힘 시 검색어 초기화 (open → closed 전환 시에만)
|
|
288
|
+
let prevOpen = false;
|
|
247
289
|
effect(() => {
|
|
248
290
|
const ctrl = this._selectCtrl();
|
|
249
|
-
|
|
250
|
-
|
|
291
|
+
const currentOpen = ctrl != null ? ctrl.dropdownOpen() : false;
|
|
292
|
+
|
|
293
|
+
if (prevOpen && !currentOpen) {
|
|
294
|
+
untracked(() => this.searchText.set(undefined));
|
|
251
295
|
}
|
|
252
|
-
|
|
296
|
+
prevOpen = currentOpen;
|
|
253
297
|
});
|
|
254
298
|
}
|
|
255
299
|
|
|
256
300
|
getItemSelectable(item: TItem, _index: number, depth: number): boolean {
|
|
257
301
|
if (!this.hasParentKey()) return true;
|
|
258
|
-
// depth가 0이면서
|
|
302
|
+
// 트리 구조에서 depth가 0이면서 __parentKey가 있는 항목은 선택 불가
|
|
259
303
|
return depth !== 0 || item.__parentKey == null;
|
|
260
304
|
}
|
|
261
305
|
|
|
@@ -273,32 +317,8 @@ export class SdSharedDataSelectControl<
|
|
|
273
317
|
return false;
|
|
274
318
|
}
|
|
275
319
|
|
|
276
|
-
isIncludeSearchText(item: TItem,
|
|
277
|
-
|
|
278
|
-
this.searchText()
|
|
279
|
-
?.trim()
|
|
280
|
-
.split(" ")
|
|
281
|
-
.map((t) => t.trim())
|
|
282
|
-
.filter((t) => t !== "") ?? [];
|
|
283
|
-
|
|
284
|
-
if (splitSearchTexts.length === 0) return true;
|
|
285
|
-
|
|
286
|
-
const itemText = this.getSearchTextFn()(item, index);
|
|
287
|
-
for (const term of splitSearchTexts) {
|
|
288
|
-
if (!itemText.toLowerCase().includes(term.toLowerCase())) {
|
|
289
|
-
// 트리 구조에서 자식 중 매칭 항목 확인
|
|
290
|
-
if (this.hasParentKey()) {
|
|
291
|
-
const children = this.getChildren(item);
|
|
292
|
-
for (let i = 0; i < children.length; i++) {
|
|
293
|
-
if (this.isIncludeSearchText(children[i], i)) {
|
|
294
|
-
return true;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
return false;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
return true;
|
|
320
|
+
isIncludeSearchText(item: TItem, _index: number): boolean {
|
|
321
|
+
return this._searchTextMatchCache().get(item.__valueKey) ?? false;
|
|
302
322
|
}
|
|
303
323
|
|
|
304
324
|
getChildren = (item: ISharedDataBase<string | number>): TItem[] => {
|
package/src/index.ts
CHANGED
|
@@ -65,6 +65,7 @@ export {
|
|
|
65
65
|
} from "./core/utils/useExpandingManager";
|
|
66
66
|
export { useSelectionManager } from "./core/utils/useSelectionManager";
|
|
67
67
|
export { injectParent } from "./core/utils/injectParent";
|
|
68
|
+
export { withBusy } from "./core/utils/withBusy";
|
|
68
69
|
|
|
69
70
|
// features/address
|
|
70
71
|
export {
|
|
@@ -72,6 +73,9 @@ export {
|
|
|
72
73
|
type IAddress,
|
|
73
74
|
} from "./features/address/sd-address-search.modal";
|
|
74
75
|
|
|
76
|
+
// features/permission-table
|
|
77
|
+
export { SdPermissionTableControl } from "./features/permission-table/sd-permission-table.control";
|
|
78
|
+
|
|
75
79
|
// features
|
|
76
80
|
export { SdBaseContainerControl } from "./features/base/sd-base-container.control";
|
|
77
81
|
export {
|
|
@@ -172,10 +176,7 @@ export { SdPaginationControl } from "./ui/navigation/pagination/sd-pagination.co
|
|
|
172
176
|
// ui/navigation/sidebar
|
|
173
177
|
export { SdSidebarContainerControl } from "./ui/navigation/sidebar/sd-sidebar-container.control";
|
|
174
178
|
export { SdSidebarControl } from "./ui/navigation/sidebar/sd-sidebar.control";
|
|
175
|
-
export {
|
|
176
|
-
SdSidebarMenuControl,
|
|
177
|
-
type ISdSidebarMenu,
|
|
178
|
-
} from "./ui/navigation/sidebar/sd-sidebar-menu.control";
|
|
179
|
+
export { SdSidebarMenuControl } from "./ui/navigation/sidebar/sd-sidebar-menu.control";
|
|
179
180
|
export {
|
|
180
181
|
SdSidebarUserControl,
|
|
181
182
|
type ISidebarUserMenu,
|
|
@@ -184,10 +185,7 @@ export {
|
|
|
184
185
|
// ui/navigation/topbar
|
|
185
186
|
export { SdTopbarContainerControl } from "./ui/navigation/topbar/sd-topbar-container.control";
|
|
186
187
|
export { SdTopbarControl } from "./ui/navigation/topbar/sd-topbar.control";
|
|
187
|
-
export {
|
|
188
|
-
SdTopbarMenuControl,
|
|
189
|
-
type ISdTopbarMenu,
|
|
190
|
-
} from "./ui/navigation/topbar/sd-topbar-menu.control";
|
|
188
|
+
export { SdTopbarMenuControl } from "./ui/navigation/topbar/sd-topbar-menu.control";
|
|
191
189
|
export {
|
|
192
190
|
SdTopbarUserControl,
|
|
193
191
|
type ISdTopbarUserMenu,
|
|
@@ -206,6 +204,7 @@ export type {
|
|
|
206
204
|
ISdSheetConfig,
|
|
207
205
|
ISdSheetHeaderDef,
|
|
208
206
|
ISdSheetItemKeydownEventParam,
|
|
207
|
+
ISdSheetCellKeydownEventParam,
|
|
209
208
|
} from "./ui/data/sheet/types";
|
|
210
209
|
|
|
211
210
|
// ui/visual
|
|
@@ -35,6 +35,7 @@ import { SdAnchorControl } from "../../form/button/sd-anchor.control";
|
|
|
35
35
|
import { SdButtonControl } from "../../form/button/sd-button.control";
|
|
36
36
|
import type {
|
|
37
37
|
ISdSheetColumnDef,
|
|
38
|
+
ISdSheetCellKeydownEventParam,
|
|
38
39
|
ISdSheetConfig,
|
|
39
40
|
ISdSheetHeaderDef,
|
|
40
41
|
ISdSheetItemKeydownEventParam,
|
|
@@ -356,7 +357,7 @@ export class SdSheetControl<T> {
|
|
|
356
357
|
|
|
357
358
|
// Outputs
|
|
358
359
|
itemKeydown = output<ISdSheetItemKeydownEventParam<T>>();
|
|
359
|
-
cellKeydown = output<
|
|
360
|
+
cellKeydown = output<ISdSheetCellKeydownEventParam<T>>();
|
|
360
361
|
|
|
361
362
|
// Models
|
|
362
363
|
selectedItems = model<T[]>([]);
|
|
@@ -496,41 +497,49 @@ export class SdSheetControl<T> {
|
|
|
496
497
|
return col?.summaryTplRef() ?? null;
|
|
497
498
|
}
|
|
498
499
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
const fixedStyle = this._getFixedStyle(
|
|
507
|
-
if (fixedStyle != null)
|
|
508
|
-
|
|
509
|
-
}
|
|
500
|
+
// Pre-computed column styles: header/footer (fixed z-index:3)
|
|
501
|
+
private readonly _headerColumnStyles = computed(() => {
|
|
502
|
+
const map = new Map<string, string | null>();
|
|
503
|
+
for (const colDef of this.layout.columnDefs()) {
|
|
504
|
+
const parts: string[] = [];
|
|
505
|
+
const colStyle = this._getColDefStyle(colDef);
|
|
506
|
+
if (colStyle != null) parts.push(colStyle);
|
|
507
|
+
const fixedStyle = this._getFixedStyle(colDef, 3, "var(--theme-secondary-lightest)");
|
|
508
|
+
if (fixedStyle != null) parts.push(fixedStyle);
|
|
509
|
+
map.set(colDef.key, parts.length > 0 ? parts.join("; ") : null);
|
|
510
510
|
}
|
|
511
|
-
return
|
|
511
|
+
return map;
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
// Pre-computed column styles: body (fixed z-index:1)
|
|
515
|
+
private readonly _dataColumnBaseStyles = computed(() => {
|
|
516
|
+
const map = new Map<string, string | null>();
|
|
517
|
+
for (const colDef of this.layout.columnDefs()) {
|
|
518
|
+
const parts: string[] = [];
|
|
519
|
+
const colStyle = this._getColDefStyle(colDef);
|
|
520
|
+
if (colStyle != null) parts.push(colStyle);
|
|
521
|
+
const fixedStyle = this._getFixedStyle(colDef);
|
|
522
|
+
if (fixedStyle != null) parts.push(fixedStyle);
|
|
523
|
+
map.set(colDef.key, parts.length > 0 ? parts.join("; ") : null);
|
|
524
|
+
}
|
|
525
|
+
return map;
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
getHeaderCellStyle(cell: ISdSheetHeaderDef) {
|
|
529
|
+
if (cell.colDef == null) return null;
|
|
530
|
+
return this._headerColumnStyles().get(cell.colDef.key) ?? null;
|
|
512
531
|
}
|
|
513
532
|
|
|
514
533
|
getCellStyle(item: T, colDef: ISdSheetColumnDef) {
|
|
515
|
-
const
|
|
516
|
-
const baseStyle = this._getColDefStyle(colDef);
|
|
517
|
-
if (baseStyle != null) {
|
|
518
|
-
parts.push(baseStyle);
|
|
519
|
-
}
|
|
520
|
-
const fixedStyle = this._getFixedStyle(colDef);
|
|
521
|
-
if (fixedStyle != null) {
|
|
522
|
-
parts.push(fixedStyle);
|
|
523
|
-
}
|
|
534
|
+
const baseStyle = this._dataColumnBaseStyles().get(colDef.key) ?? null;
|
|
524
535
|
const styleFn = this.getItemCellStyleFn();
|
|
525
536
|
const customStyle = styleFn != null ? styleFn(item, colDef.key) : undefined;
|
|
526
|
-
if (customStyle != null) {
|
|
527
|
-
|
|
528
|
-
}
|
|
529
|
-
return parts.length > 0 ? parts.join("; ") : null;
|
|
537
|
+
if (baseStyle != null && customStyle != null) return `${baseStyle}; ${customStyle}`;
|
|
538
|
+
return customStyle ?? baseStyle ?? null;
|
|
530
539
|
}
|
|
531
540
|
|
|
532
541
|
getFixedCellStyle(colDef: ISdSheetColumnDef) {
|
|
533
|
-
return this._getFixedStyle(colDef);
|
|
542
|
+
return this._getFixedStyle(colDef, 3);
|
|
534
543
|
}
|
|
535
544
|
|
|
536
545
|
getSelectableTooltip(item: T): string | null {
|
|
@@ -593,8 +602,11 @@ export class SdSheetControl<T> {
|
|
|
593
602
|
return this.expanding.def(item);
|
|
594
603
|
}
|
|
595
604
|
|
|
605
|
+
// PERF-005: Set-based lookup for O(1) isExpanded check
|
|
606
|
+
private readonly _expandedSet = computed(() => new Set(this.expandedItems()));
|
|
607
|
+
|
|
596
608
|
isExpanded(item: T): boolean {
|
|
597
|
-
return this.
|
|
609
|
+
return this._expandedSet().has(item);
|
|
598
610
|
}
|
|
599
611
|
|
|
600
612
|
getAriaExpanded(item: T): string | null {
|
|
@@ -617,34 +629,25 @@ export class SdSheetControl<T> {
|
|
|
617
629
|
}
|
|
618
630
|
|
|
619
631
|
private _getColDefStyle(colDef: { width: string | undefined; collapse: boolean }): string | null {
|
|
620
|
-
const parts: string[] = [];
|
|
621
|
-
if (colDef.width != null) {
|
|
622
|
-
parts.push(`width: ${colDef.width}`);
|
|
623
|
-
parts.push(`min-width: ${colDef.width}`);
|
|
624
|
-
parts.push(`max-width: ${colDef.width}`);
|
|
625
|
-
}
|
|
626
632
|
if (colDef.collapse) {
|
|
627
|
-
|
|
628
|
-
parts.push("width: 0");
|
|
629
|
-
parts.push("min-width: 0");
|
|
630
|
-
parts.push("max-width: 0");
|
|
631
|
-
parts.push("overflow: hidden");
|
|
632
|
-
parts.push("border: none");
|
|
633
|
+
return "padding: 0; width: 0; min-width: 0; max-width: 0; overflow: hidden; border: none";
|
|
633
634
|
}
|
|
634
|
-
|
|
635
|
+
if (colDef.width != null) {
|
|
636
|
+
return `width: ${colDef.width}; min-width: ${colDef.width}; max-width: ${colDef.width}`;
|
|
637
|
+
}
|
|
638
|
+
return null;
|
|
635
639
|
}
|
|
636
640
|
|
|
637
|
-
private _getFixedStyle(
|
|
641
|
+
private _getFixedStyle(
|
|
642
|
+
colDef: ISdSheetColumnDef,
|
|
643
|
+
zIndex: number = 1,
|
|
644
|
+
background: string = "var(--control-color)",
|
|
645
|
+
): string | null {
|
|
638
646
|
const fixedLeftMap = this.fixing.fixedLeftMap();
|
|
639
647
|
const leftValue = fixedLeftMap.get(colDef.key);
|
|
640
648
|
if (leftValue == null) return null;
|
|
641
649
|
|
|
642
|
-
|
|
643
|
-
parts.push("position: sticky");
|
|
644
|
-
parts.push(`left: ${leftValue}px`);
|
|
645
|
-
parts.push("z-index: 1");
|
|
646
|
-
parts.push("background: var(--control-color)");
|
|
647
|
-
return parts.join("; ");
|
|
650
|
+
return `position: sticky; left: ${leftValue}px; z-index: ${zIndex}; background: ${background}`;
|
|
648
651
|
}
|
|
649
652
|
|
|
650
653
|
getDataCellClass(item: T, colDef: ISdSheetColumnDef, r: number, c: number): string | null {
|
|
@@ -32,6 +32,11 @@ export interface ISdSheetConfig {
|
|
|
32
32
|
|
|
33
33
|
export interface ISdSheetItemKeydownEventParam<T> {
|
|
34
34
|
item: T;
|
|
35
|
-
|
|
35
|
+
event: KeyboardEvent;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ISdSheetCellKeydownEventParam<T> {
|
|
39
|
+
item: T;
|
|
40
|
+
key: string;
|
|
36
41
|
event: KeyboardEvent;
|
|
37
42
|
}
|
|
@@ -20,11 +20,11 @@ export function useSheetCellAgent(options: {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
function _enterEditMode(r: number, c: number): void {
|
|
23
|
-
const cell = options.domAccessor.getCell(r, c);
|
|
24
|
-
if (cell == null) return;
|
|
25
23
|
editModeCellAddr.set({ r, c });
|
|
26
|
-
//
|
|
24
|
+
// Re-query DOM inside queueMicrotask to avoid stale reference after Angular re-render
|
|
27
25
|
queueMicrotask(() => {
|
|
26
|
+
const cell = options.domAccessor.getCell(r, c);
|
|
27
|
+
if (cell == null) return;
|
|
28
28
|
const focusable = cell.findFirstFocusableChild();
|
|
29
29
|
if (focusable !== undefined) {
|
|
30
30
|
focusable.focus();
|
|
@@ -181,6 +181,7 @@ export function useSheetCellAgent(options: {
|
|
|
181
181
|
|
|
182
182
|
// Ctrl+C (copy)
|
|
183
183
|
if (event.key === "c" && event.ctrlKey && !event.altKey && !event.shiftKey) {
|
|
184
|
+
if (!("clipboard" in navigator)) return;
|
|
184
185
|
const td = _getClosestDataCell(target);
|
|
185
186
|
if (td == null) return;
|
|
186
187
|
if (td !== target) return; // Only when td itself is focused
|
|
@@ -198,6 +199,7 @@ export function useSheetCellAgent(options: {
|
|
|
198
199
|
|
|
199
200
|
// Ctrl+V (paste)
|
|
200
201
|
if (event.key === "v" && event.ctrlKey && !event.altKey && !event.shiftKey) {
|
|
202
|
+
if (!("clipboard" in navigator)) return;
|
|
201
203
|
const td = _getClosestDataCell(target);
|
|
202
204
|
if (td == null) return;
|
|
203
205
|
if (td !== target) return; // Only when td itself is focused
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { computed, type Signal } from "@angular/core";
|
|
2
2
|
import type { ISdSheetColumnDef } from "./types";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Fixed column의 left offset을 계산한다.
|
|
6
|
+
*
|
|
7
|
+
* **주의:** fixed column의 `width`는 반드시 px 단위여야 정확한 offset이 계산된다.
|
|
8
|
+
* em, rem, % 등 non-px 단위의 width는 offset 누적에 반영되지 않는다 (0으로 처리).
|
|
9
|
+
*/
|
|
4
10
|
export function useSheetColumnFixing(options: {
|
|
5
11
|
columnDefs: Signal<ISdSheetColumnDef[]>;
|
|
6
12
|
}) {
|
|
@@ -132,7 +132,7 @@ const DEFAULT_EXTENSIONS: AnyExtension[] = [
|
|
|
132
132
|
<button type="button" data-cmd="clean" (click)="execCmd('clean')">Tx</button>
|
|
133
133
|
</div>
|
|
134
134
|
</div>
|
|
135
|
-
@if (colorPickerMode !== undefined) {
|
|
135
|
+
@if (colorPickerMode() !== undefined) {
|
|
136
136
|
<div class="_color-picker">
|
|
137
137
|
@for (color of colorPresets; track color) {
|
|
138
138
|
<button type="button" class="_color-swatch"
|
|
@@ -288,11 +288,11 @@ export class SdTiptapEditorControl {
|
|
|
288
288
|
activeStates: WritableSignal<TiptapActiveStates> = signal(DEFAULT_ACTIVE_STATES);
|
|
289
289
|
activeColor = signal("");
|
|
290
290
|
activeBgColor = signal("");
|
|
291
|
-
colorPickerMode
|
|
291
|
+
colorPickerMode = signal<"text" | "bg" | undefined>(undefined);
|
|
292
292
|
|
|
293
293
|
/** @internal -- TipTap Editor 인스턴스. 테스트 및 고급 사용자용 */
|
|
294
294
|
editor: WritableSignal<Editor | undefined> = signal(undefined);
|
|
295
|
-
private
|
|
295
|
+
private lastEditorHtml: string | undefined;
|
|
296
296
|
private lastExtensions: AnyExtension[] | undefined;
|
|
297
297
|
|
|
298
298
|
private readonly resolvedExtensions = computed(() => {
|
|
@@ -312,9 +312,6 @@ export class SdTiptapEditorControl {
|
|
|
312
312
|
const extensions = this.resolvedExtensions();
|
|
313
313
|
const val = this.value();
|
|
314
314
|
|
|
315
|
-
// Skip if value change originated from editor input
|
|
316
|
-
if (this.updatingFromEditor) return;
|
|
317
|
-
|
|
318
315
|
// Recreate editor if extensions changed
|
|
319
316
|
if (this.lastExtensions !== extensions) {
|
|
320
317
|
this.lastExtensions = extensions;
|
|
@@ -323,12 +320,16 @@ export class SdTiptapEditorControl {
|
|
|
323
320
|
return;
|
|
324
321
|
}
|
|
325
322
|
|
|
323
|
+
// Skip if value matches last editor output (editor-originated change)
|
|
324
|
+
if (val === this.lastEditorHtml) return;
|
|
325
|
+
|
|
326
326
|
// Sync value to existing editor
|
|
327
327
|
const currentEditor = untracked(() => this.editor());
|
|
328
328
|
if (currentEditor == null) return;
|
|
329
329
|
const currentHtml = this.getEditorHtmlFrom(currentEditor);
|
|
330
330
|
if (currentHtml === val) return;
|
|
331
331
|
currentEditor.commands.setContent(val ?? "", { emitUpdate: false });
|
|
332
|
+
this.lastEditorHtml = undefined;
|
|
332
333
|
});
|
|
333
334
|
|
|
334
335
|
// disabled/readonly → editor.setEditable()
|
|
@@ -374,9 +375,8 @@ export class SdTiptapEditorControl {
|
|
|
374
375
|
editable: untracked(() => !this.disabled() && !this.readonly()),
|
|
375
376
|
onUpdate: ({ editor }) => {
|
|
376
377
|
const html = this.getEditorHtmlFrom(editor);
|
|
377
|
-
this.
|
|
378
|
+
this.lastEditorHtml = html;
|
|
378
379
|
this.value.set(html);
|
|
379
|
-
this.updatingFromEditor = false;
|
|
380
380
|
},
|
|
381
381
|
onTransaction: () => {
|
|
382
382
|
this.refreshActiveStates();
|
|
@@ -390,6 +390,7 @@ export class SdTiptapEditorControl {
|
|
|
390
390
|
ed.destroy();
|
|
391
391
|
this.editor.set(undefined);
|
|
392
392
|
}
|
|
393
|
+
this.lastEditorHtml = undefined;
|
|
393
394
|
}
|
|
394
395
|
|
|
395
396
|
private getEditorHtmlFrom(editor: Editor): string | undefined {
|
|
@@ -459,7 +460,7 @@ export class SdTiptapEditorControl {
|
|
|
459
460
|
}
|
|
460
461
|
|
|
461
462
|
toggleColorPicker(mode: "text" | "bg"): void {
|
|
462
|
-
this.colorPickerMode
|
|
463
|
+
this.colorPickerMode.set(this.colorPickerMode() === mode ? undefined : mode);
|
|
463
464
|
}
|
|
464
465
|
|
|
465
466
|
applyColor(color: string | undefined): void {
|
|
@@ -467,20 +468,21 @@ export class SdTiptapEditorControl {
|
|
|
467
468
|
if (ed == null) return;
|
|
468
469
|
|
|
469
470
|
const chain = ed.chain().focus();
|
|
470
|
-
|
|
471
|
+
const mode = this.colorPickerMode();
|
|
472
|
+
if (mode === "text") {
|
|
471
473
|
if (color !== undefined) {
|
|
472
474
|
chain.setColor(color).run();
|
|
473
475
|
} else {
|
|
474
476
|
chain.unsetColor().run();
|
|
475
477
|
}
|
|
476
|
-
} else if (
|
|
478
|
+
} else if (mode === "bg") {
|
|
477
479
|
if (color !== undefined) {
|
|
478
480
|
chain.setHighlight({ color }).run();
|
|
479
481
|
} else {
|
|
480
482
|
chain.unsetHighlight().run();
|
|
481
483
|
}
|
|
482
484
|
}
|
|
483
|
-
this.colorPickerMode
|
|
485
|
+
this.colorPickerMode.set(undefined);
|
|
484
486
|
}
|
|
485
487
|
|
|
486
488
|
private refreshActiveStates(): void {
|
|
@@ -84,7 +84,13 @@ export class SdDateRangePicker {
|
|
|
84
84
|
handleFromDateChanged(): void {
|
|
85
85
|
if (this.periodType() === "월") {
|
|
86
86
|
const fromDate = this.from();
|
|
87
|
-
|
|
87
|
+
if (fromDate) {
|
|
88
|
+
const firstOfMonth = fromDate.setDay(1);
|
|
89
|
+
this.from.set(firstOfMonth);
|
|
90
|
+
this.to.set(firstOfMonth.addMonths(1).addDays(-1));
|
|
91
|
+
} else {
|
|
92
|
+
this.to.set(undefined);
|
|
93
|
+
}
|
|
88
94
|
} else if (this.periodType() === "일") {
|
|
89
95
|
this.to.set(this.from());
|
|
90
96
|
} else if (
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
model,
|
|
11
11
|
signal,
|
|
12
12
|
TemplateRef,
|
|
13
|
+
untracked,
|
|
13
14
|
viewChild,
|
|
14
15
|
ViewEncapsulation,
|
|
15
16
|
} from "@angular/core";
|
|
@@ -319,6 +320,8 @@ export class SdSelectControl<M extends "single" | "multi", T> {
|
|
|
319
320
|
});
|
|
320
321
|
|
|
321
322
|
// Mirror selected item's contentHTML to the trigger display area
|
|
323
|
+
// PERF-004: item.value() reads are untracked to reduce signal subscriptions from O(N) to O(K).
|
|
324
|
+
// _itemControls() already tracks item additions/removals, value() tracks selection changes.
|
|
322
325
|
effect(() => {
|
|
323
326
|
const items = this._itemControls();
|
|
324
327
|
const currentValue = this.value();
|
|
@@ -335,14 +338,14 @@ export class SdSelectControl<M extends "single" | "multi", T> {
|
|
|
335
338
|
return;
|
|
336
339
|
}
|
|
337
340
|
|
|
341
|
+
const selectedItems = untracked(() => items.filter((item) => arr.includes(item.value())));
|
|
342
|
+
|
|
338
343
|
const separator = this.multiSelectionDisplayDirection() === "vertical" ? "<br>" : ", ";
|
|
339
344
|
const htmlParts: string[] = [];
|
|
340
|
-
for (const item of
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
htmlParts.push(html);
|
|
345
|
-
}
|
|
345
|
+
for (const item of selectedItems) {
|
|
346
|
+
const html = item.contentHTML();
|
|
347
|
+
if (html !== "") {
|
|
348
|
+
htmlParts.push(html);
|
|
346
349
|
}
|
|
347
350
|
}
|
|
348
351
|
if (htmlParts.length > 0) {
|
|
@@ -353,16 +356,17 @@ export class SdSelectControl<M extends "single" | "multi", T> {
|
|
|
353
356
|
return;
|
|
354
357
|
}
|
|
355
358
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
359
|
+
const selectedItem = untracked(() => items.find((item) => item.value() === currentValue));
|
|
360
|
+
if (selectedItem != null) {
|
|
361
|
+
const html = selectedItem.contentHTML();
|
|
362
|
+
if (html !== "") {
|
|
363
|
+
this._selectedItemContentHTML.set(html);
|
|
364
|
+
} else {
|
|
365
|
+
this._selectedItemContentHTML.set(undefined);
|
|
363
366
|
}
|
|
367
|
+
} else {
|
|
368
|
+
this._selectedItemContentHTML.set(undefined);
|
|
364
369
|
}
|
|
365
|
-
this._selectedItemContentHTML.set(undefined);
|
|
366
370
|
});
|
|
367
371
|
}
|
|
368
372
|
|