@simplysm/angular 14.0.90 → 14.0.92

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/controls/button/sd-button.js +2 -2
  2. package/dist/controls/list/sd-list-item.d.ts +4 -0
  3. package/dist/controls/list/sd-list-item.d.ts.map +1 -1
  4. package/dist/controls/list/sd-list-item.js +79 -35
  5. package/dist/controls/list/sd-list.d.ts.map +1 -1
  6. package/dist/controls/list/sd-list.js +3 -3
  7. package/dist/core/modal/sd-modal.d.ts.map +1 -1
  8. package/dist/core/modal/sd-modal.js +3 -3
  9. package/dist/core/routing/injectCurrentPageCodeSignal.js +1 -1
  10. package/dist/core/routing/injectViewTypeSignal.d.ts.map +1 -1
  11. package/dist/data/crud/sd-base-container.js +11 -11
  12. package/dist/data/crud/sd-crud-detail.js +13 -13
  13. package/dist/data/crud/sd-crud-list.d.ts +4 -1
  14. package/dist/data/crud/sd-crud-list.d.ts.map +1 -1
  15. package/dist/data/crud/sd-crud-list.js +39 -35
  16. package/dist/data/shared-data/sd-shared-data-select-list.d.ts.map +1 -1
  17. package/dist/data/shared-data/sd-shared-data-select-list.js +60 -60
  18. package/dist/layout/sidebar/sd-sidebar-menu.d.ts.map +1 -1
  19. package/dist/layout/sidebar/sd-sidebar-menu.js +26 -29
  20. package/dist/layout/sidebar/sd-sidebar-user.d.ts +1 -0
  21. package/dist/layout/sidebar/sd-sidebar-user.d.ts.map +1 -1
  22. package/dist/layout/sidebar/sd-sidebar-user.js +37 -20
  23. package/dist/layout/sidebar/sd-sidebar.d.ts.map +1 -1
  24. package/dist/layout/sidebar/sd-sidebar.js +3 -3
  25. package/dist/layout/topbar/sd-topbar.d.ts.map +1 -1
  26. package/dist/layout/topbar/sd-topbar.js +10 -9
  27. package/package.json +8 -7
  28. package/scss/commons/_styles.scss +430 -428
  29. package/scss/commons/_variables.scss +4 -10
  30. package/scss/controls/_table.scss +2 -2
  31. package/src/controls/button/sd-button.ts +1 -1
  32. package/src/controls/list/sd-list-item.ts +218 -153
  33. package/src/controls/list/sd-list.ts +13 -0
  34. package/src/core/modal/sd-modal.ts +2 -1
  35. package/src/core/routing/injectCurrentPageCodeSignal.ts +1 -1
  36. package/src/core/routing/injectViewTypeSignal.ts +1 -0
  37. package/src/data/crud/sd-base-container.ts +3 -3
  38. package/src/data/crud/sd-crud-detail.ts +2 -2
  39. package/src/data/crud/sd-crud-list.ts +15 -10
  40. package/src/data/shared-data/sd-shared-data-select-list.ts +7 -8
  41. package/src/layout/sidebar/sd-sidebar-menu.ts +2 -5
  42. package/src/layout/sidebar/sd-sidebar-user.ts +15 -3
  43. package/src/layout/sidebar/sd-sidebar.ts +3 -2
  44. package/src/layout/topbar/sd-topbar.ts +7 -5
  45. package/{dist/styles.css → styles.css} +57 -22
@@ -52,7 +52,7 @@ $breakpoint-sm: 1280px;
52
52
 
53
53
  $vars: (
54
54
  theme: (
55
- "gray": color-map(map.get($colors, gray), -5%),
55
+ "gray": color-map(map.get($colors, gray)),
56
56
  blue-gray: color-map(map.get($colors, slate)),
57
57
  primary: color-map(map.get($colors, blue)),
58
58
  secondary: color-map(map.get($colors, blue)),
@@ -106,13 +106,7 @@ $vars: (
106
106
  0: 0,
107
107
  auto: "auto",
108
108
  ),
109
- border-color: (
110
- lighter: var(--theme-gray-lightest),
111
- light: var(--theme-gray-lighter),
112
- default: var(--theme-gray-light),
113
- dark: var(--theme-gray-default),
114
- darker: var(--theme-gray-dark),
115
- ),
109
+ border-color: color-map(map.get($colors, gray), 5%),
116
110
  border-radius: (
117
111
  xs: var(--gap-xxs),
118
112
  sm: var(--gap-xs),
@@ -139,10 +133,10 @@ $vars: (
139
133
  animation-duration: 0.2s,
140
134
  elevation-size: 0.0833rem,
141
135
  sidebar: (
142
- width: 15rem,
136
+ width: 14rem,
143
137
  ),
144
138
  topbar: (
145
- height: 3rem,
139
+ height: 2.5rem,
146
140
  ),
147
141
  sheet: (
148
142
  pv: var(--gap-xs),
@@ -1,5 +1,5 @@
1
- $border-color-light: var(--theme-gray-lighter);
2
- $border-color-dark: var(--border-color-default);
1
+ $border-color-light: var(--border-color-lighter);
2
+ $border-color-dark: var(--border-color-light);
3
3
 
4
4
  .table {
5
5
  display: table;
@@ -40,7 +40,7 @@ import { SdRipple } from "../../core/ripple/sd-ripple";
40
40
  width: 100%;
41
41
 
42
42
  background: var(--control-color);
43
- border-color: var(--border-color-default);
43
+ border-color: var(--border-color-light);
44
44
  border-radius: var(--border-radius-default);
45
45
 
46
46
  font-weight: bold;
@@ -1,153 +1,218 @@
1
- import {
2
- booleanAttribute,
3
- ChangeDetectionStrategy,
4
- Component,
5
- computed,
6
- contentChild,
7
- contentChildren,
8
- input,
9
- model,
10
- TemplateRef,
11
- ViewEncapsulation,
12
- } from "@angular/core";
13
- import { NgTemplateOutlet } from "@angular/common";
14
- import { NgIcon } from "@ng-icons/core";
15
- import { SdCollapse } from "../collapse/sd-collapse";
16
- import { SdCollapseIcon } from "../collapse/sd-collapse-icon";
17
- import { SdList } from "./sd-list";
18
- import { SdRipple } from "../../core/ripple/sd-ripple";
19
-
20
- @Component({
21
- selector: "sd-list-item",
22
- changeDetection: ChangeDetectionStrategy.OnPush,
23
- encapsulation: ViewEncapsulation.None,
24
- standalone: true,
25
- imports: [SdCollapse, SdCollapseIcon, NgTemplateOutlet, NgIcon, SdRipple],
26
- template: `
27
- <div class="_content" [style]="contentStyle()" [class]="contentClass()" tabindex="0" (click)="onContentClick()" (keydown.enter)="onContentClick()" [sdRipple]="!readonly() && !(layout() === 'flat' && hasChildren())">
28
- @if (selectedIcon() != null && !hasChildren()) {
29
- <ng-icon
30
- [class.tx-theme-primary-default]="selected()"
31
- [class.tx-trans-lightest]="!selected()"
32
- [svg]="selectedIcon()!"
33
- />
34
- }
35
- <div class="_label">
36
- <ng-content />
37
- </div>
38
- @if (toolTpl()) {
39
- <div class="_tool">
40
- <ng-template [ngTemplateOutlet]="toolTpl()!" />
41
- </div>
42
- }
43
- @if (layout() === "accordion" && hasChildren()) {
44
- <sd-collapse-icon [open]="open()" />
45
- }
46
- </div>
47
- @if (hasChildren()) {
48
- <sd-collapse [open]="childrenOpen()">
49
- <ng-content select="sd-list" />
50
- </sd-collapse>
51
- }
52
- `,
53
- styles: [
54
- /* language=SCSS */ `
55
- sd-list-item {
56
- display: block;
57
-
58
- > ._content {
59
- display: flex;
60
- padding: var(--gap-sm) var(--gap-default);
61
- cursor: pointer;
62
- gap: var(--gap-xs);
63
-
64
- > ._label {
65
- flex: 1 1 auto;
66
- overflow: auto;
67
- }
68
- }
69
-
70
- &[data-sd-selected="true"] > ._content {
71
- background: var(--trans-lighter);
72
- font-weight: bold;
73
- }
74
-
75
- &[data-sd-has-selected-icon="true"][data-sd-selected="true"] > ._content {
76
- color: var(--text-trans-default);
77
-
78
- &:hover {
79
- background: var(--trans-lighter);
80
- }
81
- }
82
-
83
- &[data-sd-readonly="true"] {
84
- > ._content {
85
- cursor: default;
86
- }
87
- }
88
-
89
- &[data-sd-layout="accordion"] {
90
- &:not([data-sd-readonly="true"]) {
91
- > ._content:hover {
92
- background: var(--trans-lighter);
93
- }
94
- }
95
-
96
- > sd-collapse > ._content > sd-list {
97
- padding: var(--gap-xs) 0;
98
- }
99
- }
100
-
101
- &[data-sd-layout="flat"][data-sd-has-children="true"] {
102
- > ._content {
103
- display: block;
104
- background: transparent;
105
- cursor: default;
106
- font-size: var(--font-size-sm);
107
- opacity: 0.7;
108
- }
109
- }
110
- }
111
-
112
- .sd-theme-mobile > sd-list-item {
113
- > ._content:hover {
114
- background: transparent;
115
- }
116
- }
117
- `,
118
- ],
119
- host: {
120
- "[attr.data-sd-layout]": "layout()",
121
- "[attr.data-sd-open]": "open()",
122
- "[attr.data-sd-selected]": "selected()",
123
- "[attr.data-sd-readonly]": "readonly()",
124
- "[attr.data-sd-has-children]": "hasChildren()",
125
- "[attr.data-sd-has-selected-icon]": "selectedIcon() != null",
126
- },
127
- })
128
- export class SdListItem {
129
- layout = input<"accordion" | "flat">("accordion");
130
- open = model(false);
131
- selected = input(false, { transform: booleanAttribute });
132
- selectedIcon = input<string>();
133
- readonly = input(false, { transform: booleanAttribute });
134
- contentStyle = input<string>();
135
- contentClass = input<string>();
136
-
137
- toolTpl = contentChild<TemplateRef<void>>("toolTpl");
138
-
139
- private readonly _childLists = contentChildren(SdList);
140
-
141
- hasChildren = computed(() => this._childLists().length > 0);
142
-
143
- childrenOpen = computed(() => {
144
- return this.layout() === "flat" ? true : this.open();
145
- });
146
-
147
- onContentClick(): void {
148
- if (this.readonly()) return;
149
- if (this.layout() === "accordion" && this.hasChildren()) {
150
- this.open.update((v) => !v);
151
- }
152
- }
153
- }
1
+ import {
2
+ booleanAttribute,
3
+ ChangeDetectionStrategy,
4
+ Component,
5
+ computed,
6
+ contentChild,
7
+ contentChildren,
8
+ forwardRef,
9
+ inject,
10
+ input,
11
+ model,
12
+ TemplateRef,
13
+ ViewEncapsulation,
14
+ } from "@angular/core";
15
+ import { NgTemplateOutlet } from "@angular/common";
16
+ import { NgIcon } from "@ng-icons/core";
17
+ import { SdCollapse } from "../collapse/sd-collapse";
18
+ import { SdCollapseIcon } from "../collapse/sd-collapse-icon";
19
+ import { SdList } from "./sd-list";
20
+ import { SdRipple } from "../../core/ripple/sd-ripple";
21
+
22
+ @Component({
23
+ selector: "sd-list-item",
24
+ changeDetection: ChangeDetectionStrategy.OnPush,
25
+ encapsulation: ViewEncapsulation.None,
26
+ standalone: true,
27
+ imports: [SdCollapse, SdCollapseIcon, NgTemplateOutlet, NgIcon, SdRipple],
28
+ template: `
29
+ <div
30
+ class="_content"
31
+ [style]="contentStyle()"
32
+ [style.padding-left]="indentPadding()"
33
+ [class]="contentClass()"
34
+ tabindex="0"
35
+ (click)="onContentClick()"
36
+ (keydown.enter)="onContentClick()"
37
+ [sdRipple]="!readonly() && !(layout() === 'flat' && hasChildren())"
38
+ >
39
+ @if (selectedIcon() != null && !hasChildren()) {
40
+ <ng-icon
41
+ [class.tx-theme-primary-default]="selected()"
42
+ [class.tx-trans-lightest]="!selected()"
43
+ [svg]="selectedIcon()!"
44
+ />
45
+ }
46
+ <div class="_label">
47
+ <ng-content />
48
+ </div>
49
+ @if (toolTpl()) {
50
+ <div class="_tool">
51
+ <ng-template [ngTemplateOutlet]="toolTpl()!" />
52
+ </div>
53
+ }
54
+ @if (layout() === "accordion" && hasChildren()) {
55
+ <sd-collapse-icon [open]="open()" />
56
+ }
57
+ </div>
58
+ @if (hasChildren()) {
59
+ <sd-collapse [open]="childrenOpen()">
60
+ <div class="_children">
61
+ @if (layout() === "accordion") {
62
+ <div class="_indent-guide" [style.left]="guideLeft()"></div>
63
+ }
64
+ <ng-content select="sd-list" />
65
+ </div>
66
+ </sd-collapse>
67
+ }
68
+ `,
69
+ styles: [
70
+ /* language=SCSS */ `
71
+ sd-list-item {
72
+ display: block;
73
+
74
+ > ._content {
75
+ display: flex;
76
+ padding: var(--gap-sm) var(--gap-default);
77
+ cursor: pointer;
78
+ gap: var(--gap-xs);
79
+ border-radius: var(--border-radius-default);
80
+ transition: background var(--animation-duration) ease-in;
81
+
82
+ > ._label {
83
+ flex: 1 1 auto;
84
+ overflow: auto;
85
+ }
86
+
87
+ &:focus-visible {
88
+ outline: none;
89
+ transition: background var(--animation-duration) ease-out;
90
+ background: var(--trans-lighter);
91
+ }
92
+ }
93
+
94
+ &[data-sd-selected="true"] > ._content {
95
+ background: var(--trans-lighter);
96
+ font-weight: bold;
97
+ }
98
+
99
+ &[data-sd-has-selected-icon="true"][data-sd-selected="true"] > ._content {
100
+ color: var(--text-trans-default);
101
+
102
+ &:hover {
103
+ background: var(--trans-lighter);
104
+ }
105
+ }
106
+
107
+ &[data-sd-readonly="true"] {
108
+ > ._content {
109
+ cursor: default;
110
+ }
111
+ }
112
+
113
+ // 중첩 자식 영역: 세로 가이드선을 그리기 위한 기준 컨테이너
114
+ > sd-collapse > ._content > ._children {
115
+ position: relative;
116
+
117
+ > ._indent-guide {
118
+ position: absolute;
119
+ top: 0;
120
+ bottom: 0;
121
+ width: 0;
122
+ border-left: 1px solid var(--border-color-default);
123
+ pointer-events: none;
124
+ }
125
+ }
126
+
127
+ &[data-sd-layout="accordion"] {
128
+ &:not([data-sd-readonly="true"]) {
129
+ > ._content:hover {
130
+ transition: background var(--animation-duration) ease-out;
131
+ background: var(--trans-lighter);
132
+ }
133
+ }
134
+
135
+ //> sd-collapse > ._content > ._children > sd-list {
136
+ // padding: var(--gap-xs) 0;
137
+ //}
138
+ }
139
+
140
+ &[data-sd-layout="flat"][data-sd-has-children="true"] {
141
+ > ._content {
142
+ display: block;
143
+ background: transparent;
144
+ cursor: default;
145
+ font-size: var(--font-size-sm);
146
+ opacity: 0.7;
147
+ margin: 0;
148
+ }
149
+ }
150
+ }
151
+
152
+ .sd-theme-mobile > sd-list-item {
153
+ > ._content:hover {
154
+ background: transparent;
155
+ }
156
+ }
157
+ `,
158
+ ],
159
+ host: {
160
+ "[attr.data-sd-layout]": "layout()",
161
+ "[attr.data-sd-open]": "open()",
162
+ "[attr.data-sd-selected]": "selected()",
163
+ "[attr.data-sd-readonly]": "readonly()",
164
+ "[attr.data-sd-has-children]": "hasChildren()",
165
+ "[attr.data-sd-has-selected-icon]": "selectedIcon() != null",
166
+ },
167
+ })
168
+ export class SdListItem {
169
+ layout = input<"accordion" | "flat">("accordion");
170
+ open = model(false);
171
+ selected = input(false, { transform: booleanAttribute });
172
+ selectedIcon = input<string>();
173
+ readonly = input(false, { transform: booleanAttribute });
174
+ contentStyle = input<string>();
175
+ contentClass = input<string>();
176
+
177
+ toolTpl = contentChild<TemplateRef<void>>("toolTpl");
178
+
179
+ private readonly _childLists = contentChildren(SdList);
180
+
181
+ // 상위 아이템을 주입해 중첩 깊이(레벨)를 추적
182
+ private readonly _parentItem = inject<SdListItem>(
183
+ forwardRef(() => SdListItem),
184
+ {
185
+ optional: true,
186
+ skipSelf: true,
187
+ },
188
+ );
189
+
190
+ // accordion(트리) 깊이만 셈. flat(섹션 그룹)은 들여쓰기 대상이 아니므로 0,
191
+ // flat 하위의 accordion 트리는 자체 루트(1)부터 새로 깊이를 셈.
192
+ level = computed((): number => {
193
+ if (this.layout() !== "accordion") return 0;
194
+ return (this._parentItem?.level() ?? 0) + 1;
195
+ });
196
+
197
+ // accordion 트리 깊이별 좌측 들여쓰기 (트리 루트·flat은 기본 패딩 유지)
198
+ indentPadding = computed(() => {
199
+ if (this.level() <= 1) return undefined;
200
+ return `calc(var(--gap-default) + ${(this.level() - 1) * 1.5}em)`;
201
+ });
202
+
203
+ // 자식 영역에 그릴 세로 가이드선의 좌측 위치 (현재 레벨 기준 + 반 칸)
204
+ guideLeft = computed(() => `calc(var(--gap-default) + ${(this.level() - 1) * 1.5}em + 0.75em)`);
205
+
206
+ hasChildren = computed(() => this._childLists().length > 0);
207
+
208
+ childrenOpen = computed(() => {
209
+ return this.layout() === "flat" ? true : this.open();
210
+ });
211
+
212
+ onContentClick(): void {
213
+ if (this.readonly()) return;
214
+ if (this.layout() === "accordion" && this.hasChildren()) {
215
+ this.open.update((v) => !v);
216
+ }
217
+ }
218
+ }
@@ -22,14 +22,27 @@ import {
22
22
  flex-direction: column;
23
23
  user-select: none;
24
24
  background: var(--control-color);
25
+ border: 1px solid var(--border-color-default);
26
+ border-radius: var(--border-radius-default);
27
+ padding: var(--gap-sm);
28
+ gap: var(--gap-xs);
25
29
 
26
30
  &[data-sd-inset="true"] {
27
31
  background: transparent;
32
+ border: none;
28
33
 
29
34
  sd-list {
30
35
  background: transparent;
31
36
  }
32
37
  }
38
+
39
+ // 아이템 내부에 중첩된 리스트는 카드 외형(테두리·라운드·배경)을 제거
40
+ sd-list-item sd-list {
41
+ border-color: transparent;
42
+ border-radius: 0;
43
+ padding: 0;
44
+ background: transparent;
45
+ }
33
46
  }
34
47
  `,
35
48
  ],
@@ -159,7 +159,8 @@ import "@simplysm/core-browser";
159
159
  display: flex;
160
160
  align-items: center;
161
161
  user-select: none;
162
- border-bottom: 1px solid var(--theme-gray-lightest);
162
+ border-bottom: 1px solid var(--theme-gray-lighter);
163
+ background: var(--theme-gray-lightest);
163
164
 
164
165
  > ._title {
165
166
  flex: 1;
@@ -9,7 +9,7 @@ export function injectCurrentPageCodeSignal(): Signal<string> | undefined {
9
9
  const activatedUrlSegmentsSignals = activatedRoute.pathFromRoot
10
10
  .slice(2)
11
11
  .map((item) => toSignal(item.url, { initialValue: item.snapshot.url }));
12
- return computed(() => activatedUrlSegmentsSignals.map((item) => item()).join("."));
12
+ return computed(() => activatedUrlSegmentsSignals.map((item) => item().join(".")).join("."));
13
13
  }
14
14
 
15
15
  return undefined;
@@ -28,6 +28,7 @@ export function injectViewTypeSignal(): Signal<SdViewType> {
28
28
  ) {
29
29
  return "control";
30
30
  }
31
+
31
32
  if (fullPageCode() === currPageCode?.()) {
32
33
  return "page";
33
34
  }
@@ -41,7 +41,7 @@ import { NgTemplateOutlet } from "@angular/common";
41
41
  } @else if (viewType() === "page") {
42
42
  <sd-topbar-container>
43
43
  <sd-topbar>
44
- <h4>{{ viewTitle() }}</h4>
44
+ <h5 class="tx-trans-light">{{ viewTitle() }}</h5>
45
45
 
46
46
  <ng-template [ngTemplateOutlet]="topbarTplRef()" />
47
47
  </sd-topbar>
@@ -57,7 +57,7 @@ import { NgTemplateOutlet } from "@angular/common";
57
57
  <ng-template #content>
58
58
  <div class="flex-column fill">
59
59
  @if (commandTplRef()) {
60
- <div class="p-default flex-row gap-default bdb bdb-theme-gray-lightest">
60
+ <div class="p-default flex-row gap-default">
61
61
  <ng-template [ngTemplateOutlet]="commandTplRef()" />
62
62
  </div>
63
63
  }
@@ -69,7 +69,7 @@ import { NgTemplateOutlet } from "@angular/common";
69
69
  </div>
70
70
 
71
71
  @if (bottomCommandTplRef()) {
72
- <div class="p-sm-default flex-row main-align-end gap-sm bdt bdt-theme-gray-lightest">
72
+ <div class="p-sm-default flex-row main-align-end gap-sm bdt bdt-theme-gray-lighter bg-theme-gray-lightest">
73
73
  <ng-template [ngTemplateOutlet]="bottomCommandTplRef()" />
74
74
  </div>
75
75
  }
@@ -40,7 +40,7 @@ import { tablerDeviceFloppy } from "@ng-icons/tabler-icons";
40
40
  @if (viewType() === "page" && (!readonly() || commandTplRef())) {
41
41
  <ng-template #topbarTpl>
42
42
  @if (!readonly()) {
43
- <sd-button [theme]="'link-primary'" (click)="formCtrl()?.requestSubmit()">
43
+ <sd-button [size]="'sm'" [theme]="'link-primary'" (click)="formCtrl()?.requestSubmit()">
44
44
  <ng-icon [svg]="tablerDeviceFloppy" />
45
45
  저장
46
46
  <small>(CTRL+S)</small>
@@ -71,7 +71,7 @@ import { tablerDeviceFloppy } from "@ng-icons/tabler-icons";
71
71
  @if (viewType() === "modal" || bottomCommandTplRef()) {
72
72
  <ng-template #bottomCommandTpl>
73
73
  @if (bottomCommandTplRef()) {
74
- <div class="flex-fill flex-row main-align-start gap-sm">
74
+ <div class="flex-fill">
75
75
  <ng-template [ngTemplateOutlet]="bottomCommandTplRef()" />
76
76
  </div>
77
77
  }
@@ -62,9 +62,9 @@ import {
62
62
  [viewType]="viewType()"
63
63
  >
64
64
  <!-- TOP -->
65
- @if (viewType() === "page" && (!readonly() || commandTplRef())) {
65
+ @if (viewType() === "page" && (inlineEditEnabled() || commandTplRef())) {
66
66
  <ng-template #topbarTpl>
67
- @if (!readonly()) {
67
+ @if (inlineEditEnabled()) {
68
68
  <sd-button [theme]="'link-primary'" (click)="formCtrl()?.requestSubmit()">
69
69
  <ng-icon [svg]="tablerDeviceFloppy" />
70
70
  저장
@@ -74,9 +74,9 @@ import {
74
74
 
75
75
  <ng-template [ngTemplateOutlet]="commandTplRef()" />
76
76
  </ng-template>
77
- } @else if (!readonly() || commandTplRef()) {
77
+ } @else if (inlineEditEnabled() || commandTplRef()) {
78
78
  <ng-template #commandTpl>
79
- @if (!readonly()) {
79
+ @if (inlineEditEnabled()) {
80
80
  <sd-button [theme]="'primary'" (click)="onSaveButtonClick()">
81
81
  <ng-icon [svg]="tablerDeviceFloppy" />
82
82
  저장
@@ -167,14 +167,14 @@ import {
167
167
  </div>
168
168
  }
169
169
 
170
- @if (readonly()) {
171
- <div class="flex-fill p-default pt-0">
172
- <ng-template [ngTemplateOutlet]="sheet" />
173
- </div>
174
- } @else {
170
+ @if (inlineEditEnabled()) {
175
171
  <sd-form #formCtrl (formSubmit)="submit.emit()" class="flex-fill p-default pt-0">
176
172
  <ng-template [ngTemplateOutlet]="sheet" />
177
173
  </sd-form>
174
+ } @else {
175
+ <div class="flex-fill p-default pt-0">
176
+ <ng-template [ngTemplateOutlet]="sheet" />
177
+ </div>
178
178
  }
179
179
  </div>
180
180
  </ng-template>
@@ -192,13 +192,14 @@ import {
192
192
  [useAutoSort]="totalPageCount() === 0"
193
193
  [selectMode]="selectMode() ?? (readonly() ? undefined : 'multi')"
194
194
  [(selectedKeys)]="selectedKeys"
195
+ [getItemSelectableFn]="getItemSelectableFn()"
195
196
  [trackByFn]="trackByFn()"
196
197
  [getItemCellStyleFn]="getItemCellStyleFn"
197
198
  [autoSelect]="selectMode() === 'single' ? 'click' : undefined"
198
199
  [columnControlsInput]="columnControls()"
199
200
  (selectedKeysChange)="onSelectedKeysChange()"
200
201
  >
201
- @if (!readonly()) {
202
+ @if (inlineEditEnabled()) {
202
203
  <sd-sheet-column
203
204
  [fixed]="true"
204
205
  [key]="'deleteButton'"
@@ -234,6 +235,7 @@ export class SdCrudList<TItem, TKey> {
234
235
  busyCount = model(0);
235
236
  restricted = input(false);
236
237
  readonly = input(false);
238
+ inlineEdit = input(true);
237
239
  viewType = input.required<SdViewType>();
238
240
  selectMode = input<"single" | "multi">();
239
241
  key = input.required<string>();
@@ -266,6 +268,7 @@ export class SdCrudList<TItem, TKey> {
266
268
  sorts = model<SortingDef[]>([]);
267
269
 
268
270
  trackByFn = input.required<(item: TItem) => TKey>();
271
+ getItemSelectableFn = input<(item: TItem) => boolean | string>();
269
272
 
270
273
  commandTplRef = contentChild<TemplateRef<void>>("commandTpl");
271
274
  filterTplRef = contentChild<TemplateRef<void>>("filterTpl");
@@ -274,6 +277,8 @@ export class SdCrudList<TItem, TKey> {
274
277
 
275
278
  columnControls = contentChildren(SdSheetColumn);
276
279
 
280
+ inlineEditEnabled = computed(() => !this.readonly() && this.inlineEdit());
281
+
277
282
  hasSelectedDeleted = computed(() => this.currSelectedItems().some((it) => this.isDeleted(it)));
278
283
  hasSelectedNotDeleted = computed(() =>
279
284
  this.currSelectedItems().some((it) => !this.isDeleted(it)),
@@ -53,19 +53,18 @@ import { tablerExternalLink } from "@ng-icons/tabler-icons";
53
53
  class: "flex-column fill",
54
54
  },
55
55
  template: `
56
- @if (header()) {
57
- <div class="control-header p-default">
58
- {{ header() }}
59
- </div>
60
- }
61
-
62
- <div class="flex-column gap-sm p-default pt-0">
63
- @if (headerTplRef() || modal()) {
56
+ <div class="flex-column gap-sm pt-default ph-sm">
57
+ @if (header() || headerTplRef() || modal()) {
64
58
  <div class="flex-row">
65
59
  <div class="flex-fill">
66
60
  @if (headerTplRef()) {
67
61
  <ng-template [ngTemplateOutlet]="headerTplRef()!"></ng-template>
68
62
  }
63
+ @if (header()) {
64
+ <div class="control-header">
65
+ {{ header() }}
66
+ </div>
67
+ }
69
68
  </div>
70
69
  @if (modal()) {
71
70
  <sd-anchor (click)="onModalButtonClick()">