@simplysm/angular 14.2.14 → 14.2.18
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.
|
@@ -4,20 +4,40 @@ export declare class SdSidebarMenu {
|
|
|
4
4
|
menus: import("@angular/core").InputSignal<SdMenu[]>;
|
|
5
5
|
layout: import("@angular/core").InputSignal<"flat" | "accordion" | "accordion-expanded" | undefined>;
|
|
6
6
|
getMenuIsSelectedFn: import("@angular/core").InputSignal<((menu: SdMenu) => boolean) | undefined>;
|
|
7
|
+
/**
|
|
8
|
+
* 펼쳐진 메뉴 코드(`codeChain.join(".")`) 목록.
|
|
9
|
+
*
|
|
10
|
+
* 미지정(`undefined`)이면 `layout` 이 정하는 기본 펼침 상태를 따름.
|
|
11
|
+
* 사용자가 토글하거나 호스트가 값을 세팅하면 그 값이 단일 진리원이 되며,
|
|
12
|
+
* 이후 `menus` 가 다시 계산돼도 상태가 유지됨.
|
|
13
|
+
*/
|
|
14
|
+
expandedMenuCodes: import("@angular/core").ModelSignal<string[] | undefined>;
|
|
7
15
|
fullPageCode: import("@angular/core").Signal<string>;
|
|
8
16
|
rootLayout: import("@angular/core").Signal<"flat" | "accordion" | "accordion-expanded">;
|
|
9
|
-
|
|
17
|
+
private readonly _expandableCodes;
|
|
18
|
+
private readonly _expandedCodeSet;
|
|
19
|
+
hasExpandable: import("@angular/core").Signal<boolean>;
|
|
20
|
+
isAllExpanded: import("@angular/core").Signal<boolean>;
|
|
21
|
+
isAllCollapsed: import("@angular/core").Signal<boolean>;
|
|
22
|
+
expandAll(): void;
|
|
23
|
+
collapseAll(): void;
|
|
24
|
+
getIsMenuExpanded(menu: SdMenu): boolean;
|
|
25
|
+
onMenuOpenChange(menu: SdMenu, open: boolean): void;
|
|
10
26
|
getMenuRouterLinkOption(menu: SdMenu): {
|
|
11
27
|
link: string;
|
|
12
28
|
queryParams: Record<string, string> | undefined;
|
|
13
29
|
} | undefined;
|
|
14
30
|
getIsMenuSelected(menu: SdMenu): boolean;
|
|
15
31
|
onMenuClick(menu: SdMenu): void;
|
|
32
|
+
protected readonly icons: {
|
|
33
|
+
tablerFoldDown: string;
|
|
34
|
+
tablerFoldUp: string;
|
|
35
|
+
};
|
|
16
36
|
protected readonly itemTemplateType: {
|
|
17
37
|
menus: SdMenu[];
|
|
18
38
|
depth: number;
|
|
19
39
|
};
|
|
20
40
|
static ɵfac: i0.ɵɵFactoryDeclaration<SdSidebarMenu, never>;
|
|
21
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<SdSidebarMenu, "sd-sidebar-menu", never, { "menus": { "alias": "menus"; "required": false; "isSignal": true; }; "layout": { "alias": "layout"; "required": false; "isSignal": true; }; "getMenuIsSelectedFn": { "alias": "getMenuIsSelectedFn"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
41
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<SdSidebarMenu, "sd-sidebar-menu", never, { "menus": { "alias": "menus"; "required": false; "isSignal": true; }; "layout": { "alias": "layout"; "required": false; "isSignal": true; }; "getMenuIsSelectedFn": { "alias": "getMenuIsSelectedFn"; "required": false; "isSignal": true; }; "expandedMenuCodes": { "alias": "expandedMenuCodes"; "required": false; "isSignal": true; }; }, { "expandedMenuCodes": "expandedMenuCodesChange"; }, never, never, true, never>;
|
|
22
42
|
}
|
|
23
43
|
//# sourceMappingURL=sd-sidebar-menu.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sd-sidebar-menu.d.ts","sourceRoot":"","sources":["../../../src/layout/sidebar/sd-sidebar-menu.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sd-sidebar-menu.d.ts","sourceRoot":"","sources":["../../../src/layout/sidebar/sd-sidebar-menu.ts"],"names":[],"mappings":"AASA,OAAO,EACL,KAAK,MAAM,EAGZ,MAAM,+BAA+B,CAAC;;AAUvC,qBAoGa,aAAa;IACxB,KAAK,gDAAuB;IAC5B,MAAM,+FAAwD;IAC9D,mBAAmB,8CAAgB,MAAM,KAAK,OAAO,eAAI;IAEzD;;;;;;OAMG;IACH,iBAAiB,4DAA0C;IAE3D,YAAY,yCAA8B;IAE1C,UAAU,8EAAsF;IAIhG,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAgB9B;IAGH,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAK9B;IAEH,aAAa,0CAAsD;IAEnE,aAAa,0CAKV;IAEH,cAAc,0CAKX;IAEH,SAAS,IAAI,IAAI;IAIjB,WAAW,IAAI,IAAI;IAInB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIxC,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI;IAWnD,uBAAuB,CACrB,IAAI,EAAE,MAAM,GACX;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAA;KAAE,GAAG,SAAS;IAIhF,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIxC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAM/B,SAAS,CAAC,QAAQ,CAAC,KAAK;;;MAGtB;IAEF,SAAS,CAAC,QAAQ,CAAC,gBAAgB,EAAG;QACpC,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;yCA7GS,aAAa;2CAAb,aAAa;CA8GzB"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChangeDetectionStrategy, Component, computed, input, ViewEncapsulation, } from "@angular/core";
|
|
1
|
+
import { ChangeDetectionStrategy, Component, computed, input, model, ViewEncapsulation, } from "@angular/core";
|
|
2
2
|
import { injectFullPageCodeSignal } from "../../core/routing/injectFullPageCodeSignal";
|
|
3
3
|
import { getMenuRouterLinkOption as menuRouterLinkOption, getIsMenuSelected as menuIsSelected, } from "../../core/routing/menu-utils";
|
|
4
4
|
import { NgTemplateOutlet } from "@angular/common";
|
|
@@ -6,57 +6,81 @@ import { SdTypedTemplate } from "../../core/template/sd-typed-template";
|
|
|
6
6
|
import { SdRouterLink } from "../../core/routing/sd-router-link";
|
|
7
7
|
import { SdList } from "../../controls/list/sd-list";
|
|
8
8
|
import { SdListItem } from "../../controls/list/sd-list-item";
|
|
9
|
+
import { SdAnchor } from "../../controls/button/sd-anchor";
|
|
9
10
|
import { NgIcon } from "@ng-icons/core";
|
|
11
|
+
import { tablerFoldDown, tablerFoldUp } from "@ng-icons/tabler-icons";
|
|
10
12
|
import * as i0 from "@angular/core";
|
|
11
13
|
const _c0 = a0 => ({ menus: a0, depth: 0 });
|
|
12
14
|
const _c1 = (a0, a1) => ({ menus: a0, depth: a1 });
|
|
13
15
|
const _forTrack0 = ($index, $item) => $item.codeChain.join(".");
|
|
14
|
-
function
|
|
15
|
-
|
|
16
|
+
function SdSidebarMenu_Conditional_4_Template(rf, ctx) { if (rf & 1) {
|
|
17
|
+
const _r1 = i0.ɵɵgetCurrentView();
|
|
18
|
+
i0.ɵɵelementStart(0, "sd-anchor", 6);
|
|
19
|
+
i0.ɵɵlistener("click", function SdSidebarMenu_Conditional_4_Template_sd_anchor_click_0_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.expandAll()); });
|
|
20
|
+
i0.ɵɵelement(1, "ng-icon", 7);
|
|
21
|
+
i0.ɵɵelementEnd();
|
|
22
|
+
i0.ɵɵelementStart(2, "sd-anchor", 6);
|
|
23
|
+
i0.ɵɵlistener("click", function SdSidebarMenu_Conditional_4_Template_sd_anchor_click_2_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.collapseAll()); });
|
|
24
|
+
i0.ɵɵelement(3, "ng-icon", 7);
|
|
25
|
+
i0.ɵɵelementEnd();
|
|
26
|
+
} if (rf & 2) {
|
|
27
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
28
|
+
i0.ɵɵproperty("theme", "gray")("disabled", ctx_r1.isAllExpanded());
|
|
29
|
+
i0.ɵɵattribute("title", "\uC804\uCCB4 \uD3BC\uCE58\uAE30");
|
|
30
|
+
i0.ɵɵadvance();
|
|
31
|
+
i0.ɵɵproperty("svg", ctx_r1.icons.tablerFoldDown);
|
|
32
|
+
i0.ɵɵadvance();
|
|
33
|
+
i0.ɵɵproperty("theme", "gray")("disabled", ctx_r1.isAllCollapsed());
|
|
34
|
+
i0.ɵɵattribute("title", "\uC804\uCCB4 \uC811\uAE30");
|
|
35
|
+
i0.ɵɵadvance();
|
|
36
|
+
i0.ɵɵproperty("svg", ctx_r1.icons.tablerFoldUp);
|
|
37
|
+
} }
|
|
38
|
+
function SdSidebarMenu_ng_template_6_Template(rf, ctx) { }
|
|
39
|
+
function SdSidebarMenu_ng_template_7_For_1_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
16
40
|
i0.ɵɵelement(0, "ng-icon", 7);
|
|
17
41
|
} if (rf & 2) {
|
|
18
|
-
const
|
|
19
|
-
i0.ɵɵproperty("svg",
|
|
42
|
+
const menu_r4 = i0.ɵɵnextContext().$implicit;
|
|
43
|
+
i0.ɵɵproperty("svg", menu_r4.icon);
|
|
20
44
|
} }
|
|
21
|
-
function
|
|
22
|
-
function
|
|
23
|
-
i0.ɵɵelementStart(0, "sd-list",
|
|
24
|
-
i0.ɵɵtemplate(1,
|
|
45
|
+
function SdSidebarMenu_ng_template_7_For_1_Conditional_3_ng_template_1_Template(rf, ctx) { }
|
|
46
|
+
function SdSidebarMenu_ng_template_7_For_1_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
47
|
+
i0.ɵɵelementStart(0, "sd-list", 10);
|
|
48
|
+
i0.ɵɵtemplate(1, SdSidebarMenu_ng_template_7_For_1_Conditional_3_ng_template_1_Template, 0, 0, "ng-template", 11);
|
|
25
49
|
i0.ɵɵelementEnd();
|
|
26
50
|
} if (rf & 2) {
|
|
27
|
-
const
|
|
28
|
-
const
|
|
51
|
+
const menu_r4 = i0.ɵɵnextContext().$implicit;
|
|
52
|
+
const depth_r5 = i0.ɵɵnextContext().depth;
|
|
29
53
|
i0.ɵɵnextContext();
|
|
30
|
-
const
|
|
54
|
+
const itemTpl_r6 = i0.ɵɵreference(8);
|
|
31
55
|
i0.ɵɵproperty("inset", true);
|
|
32
56
|
i0.ɵɵadvance();
|
|
33
|
-
i0.ɵɵproperty("ngTemplateOutlet",
|
|
57
|
+
i0.ɵɵproperty("ngTemplateOutlet", itemTpl_r6)("ngTemplateOutletInjector", "outlet")("ngTemplateOutletContext", i0.ɵɵpureFunction2(4, _c1, menu_r4.children, depth_r5 + 1));
|
|
34
58
|
} }
|
|
35
|
-
function
|
|
36
|
-
const
|
|
37
|
-
i0.ɵɵelementStart(0, "sd-list-item",
|
|
38
|
-
i0.ɵɵlistener("click", function
|
|
39
|
-
i0.ɵɵconditionalCreate(1,
|
|
59
|
+
function SdSidebarMenu_ng_template_7_For_1_Template(rf, ctx) { if (rf & 1) {
|
|
60
|
+
const _r3 = i0.ɵɵgetCurrentView();
|
|
61
|
+
i0.ɵɵelementStart(0, "sd-list-item", 9);
|
|
62
|
+
i0.ɵɵlistener("click", function SdSidebarMenu_ng_template_7_For_1_Template_sd_list_item_click_0_listener() { const menu_r4 = i0.ɵɵrestoreView(_r3).$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onMenuClick(menu_r4)); })("openChange", function SdSidebarMenu_ng_template_7_For_1_Template_sd_list_item_openChange_0_listener($event) { const menu_r4 = i0.ɵɵrestoreView(_r3).$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onMenuOpenChange(menu_r4, $event)); });
|
|
63
|
+
i0.ɵɵconditionalCreate(1, SdSidebarMenu_ng_template_7_For_1_Conditional_1_Template, 1, 1, "ng-icon", 7);
|
|
40
64
|
i0.ɵɵtext(2);
|
|
41
|
-
i0.ɵɵconditionalCreate(3,
|
|
65
|
+
i0.ɵɵconditionalCreate(3, SdSidebarMenu_ng_template_7_For_1_Conditional_3_Template, 2, 7, "sd-list", 10);
|
|
42
66
|
i0.ɵɵelementEnd();
|
|
43
67
|
} if (rf & 2) {
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
i0.ɵɵproperty("sdRouterLink",
|
|
68
|
+
const menu_r4 = ctx.$implicit;
|
|
69
|
+
const depth_r5 = i0.ɵɵnextContext().depth;
|
|
70
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
71
|
+
i0.ɵɵproperty("sdRouterLink", ctx_r1.getMenuRouterLinkOption(menu_r4))("selected", ctx_r1.getIsMenuSelected(menu_r4))("layout", depth_r5 === 0 && ctx_r1.rootLayout() === "flat" ? "flat" : "accordion")("open", ctx_r1.getIsMenuExpanded(menu_r4));
|
|
48
72
|
i0.ɵɵadvance();
|
|
49
|
-
i0.ɵɵconditional(
|
|
73
|
+
i0.ɵɵconditional(menu_r4.icon ? 1 : -1);
|
|
50
74
|
i0.ɵɵadvance();
|
|
51
|
-
i0.ɵɵtextInterpolate1(" ",
|
|
75
|
+
i0.ɵɵtextInterpolate1(" ", menu_r4.title, " ");
|
|
52
76
|
i0.ɵɵadvance();
|
|
53
|
-
i0.ɵɵconditional(
|
|
77
|
+
i0.ɵɵconditional(menu_r4.children ? 3 : -1);
|
|
54
78
|
} }
|
|
55
|
-
function
|
|
56
|
-
i0.ɵɵrepeaterCreate(0,
|
|
79
|
+
function SdSidebarMenu_ng_template_7_Template(rf, ctx) { if (rf & 1) {
|
|
80
|
+
i0.ɵɵrepeaterCreate(0, SdSidebarMenu_ng_template_7_For_1_Template, 4, 7, "sd-list-item", 8, _forTrack0);
|
|
57
81
|
} if (rf & 2) {
|
|
58
|
-
const
|
|
59
|
-
i0.ɵɵrepeater(
|
|
82
|
+
const currMenus_r7 = ctx.menus;
|
|
83
|
+
i0.ɵɵrepeater(currMenus_r7);
|
|
60
84
|
} }
|
|
61
85
|
export class SdSidebarMenu {
|
|
62
86
|
menus = input([], /* @ts-ignore */
|
|
@@ -65,12 +89,82 @@ export class SdSidebarMenu {
|
|
|
65
89
|
...(ngDevMode ? [undefined, { debugName: "layout" }] : /* istanbul ignore next */ []));
|
|
66
90
|
getMenuIsSelectedFn = input(/* @ts-ignore */
|
|
67
91
|
...(ngDevMode ? [undefined, { debugName: "getMenuIsSelectedFn" }] : /* istanbul ignore next */ []));
|
|
92
|
+
/**
|
|
93
|
+
* 펼쳐진 메뉴 코드(`codeChain.join(".")`) 목록.
|
|
94
|
+
*
|
|
95
|
+
* 미지정(`undefined`)이면 `layout` 이 정하는 기본 펼침 상태를 따름.
|
|
96
|
+
* 사용자가 토글하거나 호스트가 값을 세팅하면 그 값이 단일 진리원이 되며,
|
|
97
|
+
* 이후 `menus` 가 다시 계산돼도 상태가 유지됨.
|
|
98
|
+
*/
|
|
99
|
+
expandedMenuCodes = model(undefined, /* @ts-ignore */
|
|
100
|
+
...(ngDevMode ? [{ debugName: "expandedMenuCodes" }] : /* istanbul ignore next */ []));
|
|
68
101
|
fullPageCode = injectFullPageCodeSignal();
|
|
69
102
|
rootLayout = computed(() => this.layout() ?? (this.menus().length <= 3 ? "flat" : "accordion"), /* @ts-ignore */
|
|
70
103
|
...(ngDevMode ? [{ debugName: "rootLayout" }] : /* istanbul ignore next */ []));
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
104
|
+
// 접기/펼치기가 가능한 메뉴 코드.
|
|
105
|
+
// flat 로 렌더되는 depth-0 그룹은 항상 펼쳐진 구조라 대상에서 제외(SdListItem.childrenOpen).
|
|
106
|
+
_expandableCodes = computed(() => {
|
|
107
|
+
const rootIsFlat = this.rootLayout() === "flat";
|
|
108
|
+
const result = [];
|
|
109
|
+
function walk(menus, depth) {
|
|
110
|
+
for (const menu of menus) {
|
|
111
|
+
if (menu.children == null)
|
|
112
|
+
continue;
|
|
113
|
+
if (!(rootIsFlat && depth === 0)) {
|
|
114
|
+
result.push(menu.codeChain.join("."));
|
|
115
|
+
}
|
|
116
|
+
walk(menu.children, depth + 1);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
walk(this.menus(), 0);
|
|
120
|
+
return result;
|
|
121
|
+
}, /* @ts-ignore */
|
|
122
|
+
...(ngDevMode ? [{ debugName: "_expandableCodes" }] : /* istanbul ignore next */ []));
|
|
123
|
+
// accordion-expanded: 하위 보유 메뉴 전체를 펼친 채로 시작. 그 외는 접힘 시작.
|
|
124
|
+
_expandedCodeSet = computed(() => {
|
|
125
|
+
const codes = this.expandedMenuCodes() ??
|
|
126
|
+
(this.rootLayout() === "accordion-expanded" ? this._expandableCodes() : []);
|
|
127
|
+
return new Set(codes);
|
|
128
|
+
}, /* @ts-ignore */
|
|
129
|
+
...(ngDevMode ? [{ debugName: "_expandedCodeSet" }] : /* istanbul ignore next */ []));
|
|
130
|
+
hasExpandable = computed(() => this._expandableCodes().length > 0, /* @ts-ignore */
|
|
131
|
+
...(ngDevMode ? [{ debugName: "hasExpandable" }] : /* istanbul ignore next */ []));
|
|
132
|
+
isAllExpanded = computed(() => {
|
|
133
|
+
const codes = this._expandableCodes();
|
|
134
|
+
if (codes.length === 0)
|
|
135
|
+
return false;
|
|
136
|
+
const expandedSet = this._expandedCodeSet();
|
|
137
|
+
return codes.every((code) => expandedSet.has(code));
|
|
138
|
+
}, /* @ts-ignore */
|
|
139
|
+
...(ngDevMode ? [{ debugName: "isAllExpanded" }] : /* istanbul ignore next */ []));
|
|
140
|
+
isAllCollapsed = computed(() => {
|
|
141
|
+
const codes = this._expandableCodes();
|
|
142
|
+
if (codes.length === 0)
|
|
143
|
+
return true;
|
|
144
|
+
const expandedSet = this._expandedCodeSet();
|
|
145
|
+
return codes.every((code) => !expandedSet.has(code));
|
|
146
|
+
}, /* @ts-ignore */
|
|
147
|
+
...(ngDevMode ? [{ debugName: "isAllCollapsed" }] : /* istanbul ignore next */ []));
|
|
148
|
+
expandAll() {
|
|
149
|
+
this.expandedMenuCodes.set([...this._expandableCodes()]);
|
|
150
|
+
}
|
|
151
|
+
collapseAll() {
|
|
152
|
+
this.expandedMenuCodes.set([]);
|
|
153
|
+
}
|
|
154
|
+
getIsMenuExpanded(menu) {
|
|
155
|
+
return this._expandedCodeSet().has(menu.codeChain.join("."));
|
|
156
|
+
}
|
|
157
|
+
onMenuOpenChange(menu, open) {
|
|
158
|
+
const code = menu.codeChain.join(".");
|
|
159
|
+
const nextSet = new Set(this._expandedCodeSet());
|
|
160
|
+
if (open) {
|
|
161
|
+
nextSet.add(code);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
nextSet.delete(code);
|
|
165
|
+
}
|
|
166
|
+
this.expandedMenuCodes.set([...nextSet]);
|
|
167
|
+
}
|
|
74
168
|
getMenuRouterLinkOption(menu) {
|
|
75
169
|
return menuRouterLinkOption(menu);
|
|
76
170
|
}
|
|
@@ -82,35 +176,77 @@ export class SdSidebarMenu {
|
|
|
82
176
|
window.open(menu.url, "_blank");
|
|
83
177
|
}
|
|
84
178
|
}
|
|
179
|
+
icons = {
|
|
180
|
+
tablerFoldDown,
|
|
181
|
+
tablerFoldUp,
|
|
182
|
+
};
|
|
85
183
|
itemTemplateType;
|
|
86
184
|
static ɵfac = function SdSidebarMenu_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || SdSidebarMenu)(); };
|
|
87
185
|
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: SdSidebarMenu, selectors: [["sd-sidebar-menu"]], hostAttrs: [1, "flex-column", "fill"], hostVars: 1, hostBindings: function SdSidebarMenu_HostBindings(rf, ctx) { if (rf & 2) {
|
|
88
186
|
i0.ɵɵattribute("data-sd-root-layout", ctx.rootLayout());
|
|
89
|
-
} }, inputs: { menus: [1, "menus"], layout: [1, "layout"], getMenuIsSelectedFn: [1, "getMenuIsSelectedFn"] }, decls:
|
|
90
|
-
i0.ɵɵelementStart(0, "div", 1)(1, "b");
|
|
91
|
-
i0.ɵɵtext(
|
|
187
|
+
} }, inputs: { menus: [1, "menus"], layout: [1, "layout"], getMenuIsSelectedFn: [1, "getMenuIsSelectedFn"], expandedMenuCodes: [1, "expandedMenuCodes"] }, outputs: { expandedMenuCodes: "expandedMenuCodesChange" }, decls: 9, vars: 7, consts: [["itemTpl", ""], [1, "control-header", "p-default", "flex-row"], [1, "flex-fill"], [1, "flex-fill", 3, "inset"], [3, "ngTemplateOutlet", "ngTemplateOutletContext"], [3, "typed"], [3, "click", "theme", "disabled"], [3, "svg"], [3, "sdRouterLink", "selected", "layout", "open"], [3, "click", "openChange", "sdRouterLink", "selected", "layout", "open"], [3, "inset"], [3, "ngTemplateOutlet", "ngTemplateOutletInjector", "ngTemplateOutletContext"]], template: function SdSidebarMenu_Template(rf, ctx) { if (rf & 1) {
|
|
188
|
+
i0.ɵɵelementStart(0, "div", 1)(1, "div", 2)(2, "b");
|
|
189
|
+
i0.ɵɵtext(3, "MENU");
|
|
92
190
|
i0.ɵɵelementEnd()();
|
|
93
|
-
i0.ɵɵ
|
|
94
|
-
i0.ɵɵtemplate(4, SdSidebarMenu_ng_template_4_Template, 0, 0, "ng-template", 3);
|
|
191
|
+
i0.ɵɵconditionalCreate(4, SdSidebarMenu_Conditional_4_Template, 4, 8);
|
|
95
192
|
i0.ɵɵelementEnd();
|
|
96
|
-
i0.ɵɵ
|
|
193
|
+
i0.ɵɵelementStart(5, "sd-list", 3);
|
|
194
|
+
i0.ɵɵtemplate(6, SdSidebarMenu_ng_template_6_Template, 0, 0, "ng-template", 4);
|
|
195
|
+
i0.ɵɵelementEnd();
|
|
196
|
+
i0.ɵɵtemplate(7, SdSidebarMenu_ng_template_7_Template, 2, 0, "ng-template", 5, 0, i0.ɵɵtemplateRefExtractor);
|
|
97
197
|
} if (rf & 2) {
|
|
98
|
-
const
|
|
99
|
-
i0.ɵɵadvance(
|
|
198
|
+
const itemTpl_r6 = i0.ɵɵreference(8);
|
|
199
|
+
i0.ɵɵadvance(4);
|
|
200
|
+
i0.ɵɵconditional(ctx.hasExpandable() ? 4 : -1);
|
|
201
|
+
i0.ɵɵadvance();
|
|
100
202
|
i0.ɵɵproperty("inset", true);
|
|
101
203
|
i0.ɵɵadvance();
|
|
102
|
-
i0.ɵɵproperty("ngTemplateOutlet",
|
|
204
|
+
i0.ɵɵproperty("ngTemplateOutlet", itemTpl_r6)("ngTemplateOutletContext", i0.ɵɵpureFunction1(5, _c0, ctx.menus()));
|
|
103
205
|
i0.ɵɵadvance();
|
|
104
206
|
i0.ɵɵproperty("typed", ctx.itemTemplateType);
|
|
105
|
-
} }, dependencies: [NgTemplateOutlet,
|
|
207
|
+
} }, dependencies: [NgTemplateOutlet,
|
|
208
|
+
SdTypedTemplate,
|
|
209
|
+
SdRouterLink,
|
|
210
|
+
SdList,
|
|
211
|
+
SdListItem,
|
|
212
|
+
SdAnchor,
|
|
213
|
+
NgIcon], styles: ["sd-sidebar-menu > .control-header {\n align-items: center;\n gap: var(--%NS%sd-gap-xs);\n}\nsd-sidebar-menu[data-sd-root-layout=flat] > sd-list[data-sd-inset=true] > sd-list-item > sd-collapse > ._content > ._children > sd-list {\n background-color: transparent;\n}"], encapsulation: 2 });
|
|
106
214
|
}
|
|
107
215
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SdSidebarMenu, [{
|
|
108
216
|
type: Component,
|
|
109
|
-
args: [{ selector: "sd-sidebar-menu", changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, standalone: true, imports: [
|
|
217
|
+
args: [{ selector: "sd-sidebar-menu", changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, standalone: true, imports: [
|
|
218
|
+
NgTemplateOutlet,
|
|
219
|
+
SdTypedTemplate,
|
|
220
|
+
SdRouterLink,
|
|
221
|
+
SdList,
|
|
222
|
+
SdListItem,
|
|
223
|
+
SdAnchor,
|
|
224
|
+
NgIcon,
|
|
225
|
+
], host: {
|
|
110
226
|
"class": "flex-column fill",
|
|
111
227
|
"[attr.data-sd-root-layout]": "rootLayout()",
|
|
112
228
|
}, template: `
|
|
113
|
-
<div class="control-header p-default"
|
|
229
|
+
<div class="control-header p-default flex-row">
|
|
230
|
+
<div class="flex-fill"><b>MENU</b></div>
|
|
231
|
+
@if (hasExpandable()) {
|
|
232
|
+
<sd-anchor
|
|
233
|
+
[theme]="'gray'"
|
|
234
|
+
[disabled]="isAllExpanded()"
|
|
235
|
+
[attr.title]="'전체 펼치기'"
|
|
236
|
+
(click)="expandAll()"
|
|
237
|
+
>
|
|
238
|
+
<ng-icon [svg]="icons.tablerFoldDown" />
|
|
239
|
+
</sd-anchor>
|
|
240
|
+
<sd-anchor
|
|
241
|
+
[theme]="'gray'"
|
|
242
|
+
[disabled]="isAllCollapsed()"
|
|
243
|
+
[attr.title]="'전체 접기'"
|
|
244
|
+
(click)="collapseAll()"
|
|
245
|
+
>
|
|
246
|
+
<ng-icon [svg]="icons.tablerFoldUp" />
|
|
247
|
+
</sd-anchor>
|
|
248
|
+
}
|
|
249
|
+
</div>
|
|
114
250
|
|
|
115
251
|
<sd-list class="flex-fill" [inset]="true">
|
|
116
252
|
<ng-template
|
|
@@ -126,7 +262,8 @@ export class SdSidebarMenu {
|
|
|
126
262
|
(click)="onMenuClick(menu)"
|
|
127
263
|
[selected]="getIsMenuSelected(menu)"
|
|
128
264
|
[layout]="depth === 0 && rootLayout() === 'flat' ? 'flat' : 'accordion'"
|
|
129
|
-
[open]="
|
|
265
|
+
[open]="getIsMenuExpanded(menu)"
|
|
266
|
+
(openChange)="onMenuOpenChange(menu, $event)"
|
|
130
267
|
>
|
|
131
268
|
@if (menu.icon) {
|
|
132
269
|
<ng-icon [svg]="menu.icon" />
|
|
@@ -147,6 +284,6 @@ export class SdSidebarMenu {
|
|
|
147
284
|
</sd-list-item>
|
|
148
285
|
}
|
|
149
286
|
</ng-template>
|
|
150
|
-
`, styles: ["sd-sidebar-menu[data-sd-root-layout=flat] > sd-list[data-sd-inset=true] > sd-list-item > sd-collapse > ._content > ._children > sd-list {\n background-color: transparent;\n}"] }]
|
|
151
|
-
}], null, { menus: [{ type: i0.Input, args: [{ isSignal: true, alias: "menus", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], getMenuIsSelectedFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "getMenuIsSelectedFn", required: false }] }] }); })();
|
|
152
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(SdSidebarMenu, { className: "SdSidebarMenu", filePath: "packages/angular/src/layout/sidebar/sd-sidebar-menu.ts", lineNumber:
|
|
287
|
+
`, styles: ["sd-sidebar-menu > .control-header {\n align-items: center;\n gap: var(--sd-gap-xs);\n}\nsd-sidebar-menu[data-sd-root-layout=flat] > sd-list[data-sd-inset=true] > sd-list-item > sd-collapse > ._content > ._children > sd-list {\n background-color: transparent;\n}"] }]
|
|
288
|
+
}], null, { menus: [{ type: i0.Input, args: [{ isSignal: true, alias: "menus", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], getMenuIsSelectedFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "getMenuIsSelectedFn", required: false }] }], expandedMenuCodes: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedMenuCodes", required: false }] }, { type: i0.Output, args: ["expandedMenuCodesChange"] }] }); })();
|
|
289
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(SdSidebarMenu, { className: "SdSidebarMenu", filePath: "packages/angular/src/layout/sidebar/sd-sidebar-menu.ts", lineNumber: 124 }); })();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplysm/angular",
|
|
3
|
-
"version": "14.2.
|
|
3
|
+
"version": "14.2.18",
|
|
4
4
|
"description": "심플리즘 패키지 - Angular",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "심플리즘",
|
|
@@ -39,6 +39,10 @@
|
|
|
39
39
|
"@angular/service-worker": "^22.1.0",
|
|
40
40
|
"@ng-icons/core": "^34.0.0",
|
|
41
41
|
"@ng-icons/tabler-icons": "^34.0.0",
|
|
42
|
+
"@simplysm/core-browser": "14.2.18",
|
|
43
|
+
"@simplysm/core-common": "14.2.18",
|
|
44
|
+
"@simplysm/service-client": "14.2.18",
|
|
45
|
+
"@simplysm/service-common": "14.2.18",
|
|
42
46
|
"@tiptap/core": "^3.29.2",
|
|
43
47
|
"@tiptap/extension-color": "^3.29.2",
|
|
44
48
|
"@tiptap/extension-document": "^3.29.2",
|
|
@@ -57,11 +61,7 @@
|
|
|
57
61
|
"html-to-image": "^1.11.13",
|
|
58
62
|
"jspdf": "^4.2.1",
|
|
59
63
|
"rxjs": "^7.8.2",
|
|
60
|
-
"tabbable": "^6.5.0"
|
|
61
|
-
"@simplysm/service-client": "14.2.14",
|
|
62
|
-
"@simplysm/core-browser": "14.2.14",
|
|
63
|
-
"@simplysm/core-common": "14.2.14",
|
|
64
|
-
"@simplysm/service-common": "14.2.14"
|
|
64
|
+
"tabbable": "^6.5.0"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@angular/compiler": "^22.1.0"
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
Component,
|
|
4
4
|
computed,
|
|
5
5
|
input,
|
|
6
|
+
model,
|
|
6
7
|
ViewEncapsulation,
|
|
7
8
|
} from "@angular/core";
|
|
8
9
|
import { injectFullPageCodeSignal } from "../../core/routing/injectFullPageCodeSignal";
|
|
@@ -16,20 +17,50 @@ import { SdTypedTemplate } from "../../core/template/sd-typed-template";
|
|
|
16
17
|
import { SdRouterLink } from "../../core/routing/sd-router-link";
|
|
17
18
|
import { SdList } from "../../controls/list/sd-list";
|
|
18
19
|
import { SdListItem } from "../../controls/list/sd-list-item";
|
|
20
|
+
import { SdAnchor } from "../../controls/button/sd-anchor";
|
|
19
21
|
import { NgIcon } from "@ng-icons/core";
|
|
22
|
+
import { tablerFoldDown, tablerFoldUp } from "@ng-icons/tabler-icons";
|
|
20
23
|
|
|
21
24
|
@Component({
|
|
22
25
|
selector: "sd-sidebar-menu",
|
|
23
26
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
24
27
|
encapsulation: ViewEncapsulation.None,
|
|
25
28
|
standalone: true,
|
|
26
|
-
imports: [
|
|
29
|
+
imports: [
|
|
30
|
+
NgTemplateOutlet,
|
|
31
|
+
SdTypedTemplate,
|
|
32
|
+
SdRouterLink,
|
|
33
|
+
SdList,
|
|
34
|
+
SdListItem,
|
|
35
|
+
SdAnchor,
|
|
36
|
+
NgIcon,
|
|
37
|
+
],
|
|
27
38
|
host: {
|
|
28
39
|
"class": "flex-column fill",
|
|
29
40
|
"[attr.data-sd-root-layout]": "rootLayout()",
|
|
30
41
|
},
|
|
31
42
|
template: `
|
|
32
|
-
<div class="control-header p-default"
|
|
43
|
+
<div class="control-header p-default flex-row">
|
|
44
|
+
<div class="flex-fill"><b>MENU</b></div>
|
|
45
|
+
@if (hasExpandable()) {
|
|
46
|
+
<sd-anchor
|
|
47
|
+
[theme]="'gray'"
|
|
48
|
+
[disabled]="isAllExpanded()"
|
|
49
|
+
[attr.title]="'전체 펼치기'"
|
|
50
|
+
(click)="expandAll()"
|
|
51
|
+
>
|
|
52
|
+
<ng-icon [svg]="icons.tablerFoldDown" />
|
|
53
|
+
</sd-anchor>
|
|
54
|
+
<sd-anchor
|
|
55
|
+
[theme]="'gray'"
|
|
56
|
+
[disabled]="isAllCollapsed()"
|
|
57
|
+
[attr.title]="'전체 접기'"
|
|
58
|
+
(click)="collapseAll()"
|
|
59
|
+
>
|
|
60
|
+
<ng-icon [svg]="icons.tablerFoldUp" />
|
|
61
|
+
</sd-anchor>
|
|
62
|
+
}
|
|
63
|
+
</div>
|
|
33
64
|
|
|
34
65
|
<sd-list class="flex-fill" [inset]="true">
|
|
35
66
|
<ng-template
|
|
@@ -45,7 +76,8 @@ import { NgIcon } from "@ng-icons/core";
|
|
|
45
76
|
(click)="onMenuClick(menu)"
|
|
46
77
|
[selected]="getIsMenuSelected(menu)"
|
|
47
78
|
[layout]="depth === 0 && rootLayout() === 'flat' ? 'flat' : 'accordion'"
|
|
48
|
-
[open]="
|
|
79
|
+
[open]="getIsMenuExpanded(menu)"
|
|
80
|
+
(openChange)="onMenuOpenChange(menu, $event)"
|
|
49
81
|
>
|
|
50
82
|
@if (menu.icon) {
|
|
51
83
|
<ng-icon [svg]="menu.icon" />
|
|
@@ -70,6 +102,11 @@ import { NgIcon } from "@ng-icons/core";
|
|
|
70
102
|
styles: [
|
|
71
103
|
/* language=SCSS */ `
|
|
72
104
|
sd-sidebar-menu {
|
|
105
|
+
> .control-header {
|
|
106
|
+
align-items: center;
|
|
107
|
+
gap: var(--sd-gap-xs);
|
|
108
|
+
}
|
|
109
|
+
|
|
73
110
|
&[data-sd-root-layout="flat"] {
|
|
74
111
|
> sd-list[data-sd-inset="true"]
|
|
75
112
|
> sd-list-item
|
|
@@ -89,12 +126,85 @@ export class SdSidebarMenu {
|
|
|
89
126
|
layout = input<"accordion" | "accordion-expanded" | "flat">();
|
|
90
127
|
getMenuIsSelectedFn = input<(menu: SdMenu) => boolean>();
|
|
91
128
|
|
|
129
|
+
/**
|
|
130
|
+
* 펼쳐진 메뉴 코드(`codeChain.join(".")`) 목록.
|
|
131
|
+
*
|
|
132
|
+
* 미지정(`undefined`)이면 `layout` 이 정하는 기본 펼침 상태를 따름.
|
|
133
|
+
* 사용자가 토글하거나 호스트가 값을 세팅하면 그 값이 단일 진리원이 되며,
|
|
134
|
+
* 이후 `menus` 가 다시 계산돼도 상태가 유지됨.
|
|
135
|
+
*/
|
|
136
|
+
expandedMenuCodes = model<string[] | undefined>(undefined);
|
|
137
|
+
|
|
92
138
|
fullPageCode = injectFullPageCodeSignal();
|
|
93
139
|
|
|
94
140
|
rootLayout = computed(() => this.layout() ?? (this.menus().length <= 3 ? "flat" : "accordion"));
|
|
95
141
|
|
|
96
|
-
//
|
|
97
|
-
|
|
142
|
+
// 접기/펼치기가 가능한 메뉴 코드.
|
|
143
|
+
// flat 로 렌더되는 depth-0 그룹은 항상 펼쳐진 구조라 대상에서 제외(SdListItem.childrenOpen).
|
|
144
|
+
private readonly _expandableCodes = computed(() => {
|
|
145
|
+
const rootIsFlat = this.rootLayout() === "flat";
|
|
146
|
+
const result: string[] = [];
|
|
147
|
+
|
|
148
|
+
function walk(menus: SdMenu[], depth: number): void {
|
|
149
|
+
for (const menu of menus) {
|
|
150
|
+
if (menu.children == null) continue;
|
|
151
|
+
if (!(rootIsFlat && depth === 0)) {
|
|
152
|
+
result.push(menu.codeChain.join("."));
|
|
153
|
+
}
|
|
154
|
+
walk(menu.children, depth + 1);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
walk(this.menus(), 0);
|
|
159
|
+
return result;
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// accordion-expanded: 하위 보유 메뉴 전체를 펼친 채로 시작. 그 외는 접힘 시작.
|
|
163
|
+
private readonly _expandedCodeSet = computed(() => {
|
|
164
|
+
const codes =
|
|
165
|
+
this.expandedMenuCodes() ??
|
|
166
|
+
(this.rootLayout() === "accordion-expanded" ? this._expandableCodes() : []);
|
|
167
|
+
return new Set(codes);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
hasExpandable = computed(() => this._expandableCodes().length > 0);
|
|
171
|
+
|
|
172
|
+
isAllExpanded = computed(() => {
|
|
173
|
+
const codes = this._expandableCodes();
|
|
174
|
+
if (codes.length === 0) return false;
|
|
175
|
+
const expandedSet = this._expandedCodeSet();
|
|
176
|
+
return codes.every((code) => expandedSet.has(code));
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
isAllCollapsed = computed(() => {
|
|
180
|
+
const codes = this._expandableCodes();
|
|
181
|
+
if (codes.length === 0) return true;
|
|
182
|
+
const expandedSet = this._expandedCodeSet();
|
|
183
|
+
return codes.every((code) => !expandedSet.has(code));
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
expandAll(): void {
|
|
187
|
+
this.expandedMenuCodes.set([...this._expandableCodes()]);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
collapseAll(): void {
|
|
191
|
+
this.expandedMenuCodes.set([]);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
getIsMenuExpanded(menu: SdMenu): boolean {
|
|
195
|
+
return this._expandedCodeSet().has(menu.codeChain.join("."));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
onMenuOpenChange(menu: SdMenu, open: boolean): void {
|
|
199
|
+
const code = menu.codeChain.join(".");
|
|
200
|
+
const nextSet = new Set(this._expandedCodeSet());
|
|
201
|
+
if (open) {
|
|
202
|
+
nextSet.add(code);
|
|
203
|
+
} else {
|
|
204
|
+
nextSet.delete(code);
|
|
205
|
+
}
|
|
206
|
+
this.expandedMenuCodes.set([...nextSet]);
|
|
207
|
+
}
|
|
98
208
|
|
|
99
209
|
getMenuRouterLinkOption(
|
|
100
210
|
menu: SdMenu,
|
|
@@ -112,6 +222,11 @@ export class SdSidebarMenu {
|
|
|
112
222
|
}
|
|
113
223
|
}
|
|
114
224
|
|
|
225
|
+
protected readonly icons = {
|
|
226
|
+
tablerFoldDown,
|
|
227
|
+
tablerFoldUp,
|
|
228
|
+
};
|
|
229
|
+
|
|
115
230
|
protected readonly itemTemplateType!: {
|
|
116
231
|
menus: SdMenu[];
|
|
117
232
|
depth: number;
|