@simplysm/angular 14.0.100 → 14.0.102
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-additional-button.js +2 -2
- package/dist/controls/button/sd-anchor.js +2 -2
- package/dist/controls/button/sd-button.js +2 -2
- package/dist/controls/button/sd-modal-select-button.js +2 -2
- package/dist/controls/checkbox/sd-checkbox.d.ts.map +1 -1
- package/dist/controls/checkbox/sd-checkbox.js +3 -3
- package/dist/controls/checkbox/sd-switch.js +2 -2
- package/dist/controls/collapse/sd-collapse-icon.d.ts +1 -1
- package/dist/controls/collapse/sd-collapse-icon.d.ts.map +1 -1
- package/dist/controls/collapse/sd-collapse-icon.js +3 -1
- package/dist/controls/collapse/sd-collapse.d.ts +1 -1
- package/dist/controls/collapse/sd-collapse.d.ts.map +1 -1
- package/dist/controls/collapse/sd-collapse.js +3 -1
- package/dist/controls/dropdown/sd-dropdown-popup.js +2 -2
- package/dist/controls/gap/sd-gap.js +2 -2
- package/dist/controls/input/sd-date-range-picker.js +2 -2
- package/dist/controls/input/sd-range.js +2 -2
- package/dist/controls/input/sd-textarea.d.ts.map +1 -1
- package/dist/controls/input/sd-textarea.js +3 -3
- package/dist/controls/input/sd-textfield.d.ts +2 -0
- package/dist/controls/input/sd-textfield.d.ts.map +1 -1
- package/dist/controls/input/sd-textfield.js +44 -16
- package/dist/controls/list/sd-list-item.js +2 -2
- package/dist/controls/select/sd-select-button.js +2 -2
- package/dist/controls/select/sd-select-item.d.ts.map +1 -1
- package/dist/controls/select/sd-select-item.js +3 -3
- package/dist/controls/select/sd-select.d.ts.map +1 -1
- package/dist/controls/select/sd-select.js +3 -3
- package/dist/core/busy/sd-busy-container.js +2 -2
- package/dist/core/directive-input-signals.d.ts +0 -9
- package/dist/core/directive-input-signals.d.ts.map +1 -1
- package/dist/core/modal/sd-modal.js +2 -2
- package/dist/core/provideSdAngular.d.ts.map +1 -1
- package/dist/core/provideSdAngular.js +12 -0
- package/dist/core/ripple/setupRipple.js +1 -1
- package/dist/core/toast/sd-toast-container.js +2 -2
- package/dist/core/toast/sd-toast.js +2 -2
- package/dist/data/crud/sd-base-container.js +4 -4
- package/dist/data/crud/sd-crud-list.d.ts +4 -2
- package/dist/data/crud/sd-crud-list.d.ts.map +1 -1
- package/dist/data/crud/sd-crud-list.js +60 -58
- package/dist/data/kanban/sd-kanban-board.js +2 -2
- package/dist/data/kanban/sd-kanban-lane.js +2 -2
- package/dist/data/kanban/sd-kanban.js +2 -2
- package/dist/data/sheet/sd-sheet.d.ts.map +1 -1
- package/dist/data/sheet/sd-sheet.js +3 -3
- package/dist/features/editor/sd-tiptap-editor.d.ts.map +1 -1
- package/dist/features/editor/sd-tiptap-editor.js +3 -3
- package/dist/features/theme/sd-theme-provider.d.ts +1 -0
- package/dist/features/theme/sd-theme-provider.d.ts.map +1 -1
- package/dist/features/theme/sd-theme-provider.js +5 -0
- package/dist/features/theme/sd-theme-selector.d.ts.map +1 -1
- package/dist/features/theme/sd-theme-selector.js +14 -2
- package/dist/features/visual/sd-calendar.js +2 -2
- package/dist/features/visual/sd-label.js +2 -2
- package/dist/features/visual/sd-note.js +2 -2
- package/dist/features/visual/sd-progress.js +2 -2
- package/dist/layout/sidebar/sd-sidebar-container.js +2 -2
- package/dist/layout/sidebar/sd-sidebar.js +2 -2
- package/dist/layout/topbar/sd-topbar.js +2 -2
- package/package.json +5 -5
- package/scss/commons/_colors.scss +326 -0
- package/scss/commons/_mixins.scss +129 -129
- package/scss/commons/_theme-variables.scss +8 -1
- package/scss/commons/_variables.scss +37 -33
- package/scss/controls/_card.scss +1 -1
- package/scss/styles.scss +3 -0
- package/scss/themes/_blueprint-surfaces.scss +89 -0
- package/scss/themes/_variables-blueprint.scss +95 -0
- package/scss/themes/_variables-dark.scss +9 -22
- package/src/controls/button/sd-button.ts +2 -2
- package/src/controls/checkbox/sd-checkbox.ts +13 -11
- package/src/controls/collapse/sd-collapse-icon.ts +3 -1
- package/src/controls/collapse/sd-collapse.ts +3 -1
- package/src/controls/dropdown/sd-dropdown-popup.ts +2 -2
- package/src/controls/input/sd-textarea.ts +5 -0
- package/src/controls/input/sd-textfield.ts +40 -4
- package/src/controls/list/sd-list-item.ts +5 -5
- package/src/controls/select/sd-select-button.ts +1 -1
- package/src/controls/select/sd-select-item.ts +4 -3
- package/src/controls/select/sd-select.ts +2 -1
- package/src/core/busy/sd-busy-container.ts +3 -3
- package/src/core/directive-input-signals.ts +0 -9
- package/src/core/provideSdAngular.ts +14 -0
- package/src/core/ripple/setupRipple.ts +1 -1
- package/src/data/crud/sd-base-container.ts +3 -3
- package/src/data/crud/sd-crud-list.ts +24 -22
- package/src/data/kanban/sd-kanban-lane.ts +1 -1
- package/src/data/kanban/sd-kanban.ts +1 -1
- package/src/data/sheet/sd-sheet.ts +4 -3
- package/src/features/editor/sd-tiptap-editor.ts +1 -0
- package/src/features/theme/sd-theme-provider.ts +7 -0
- package/src/features/theme/sd-theme-selector.ts +4 -0
- package/src/layout/sidebar/sd-sidebar-container.ts +3 -3
- package/src/layout/sidebar/sd-sidebar.ts +5 -5
- package/src/layout/topbar/sd-topbar.ts +1 -1
- package/styles.css +487 -103
|
@@ -62,7 +62,7 @@ import { tablerCheck } from "@ng-icons/tabler-icons";
|
|
|
62
62
|
|
|
63
63
|
width: calc(var(--font-size-default) + 2px);
|
|
64
64
|
height: calc(var(--font-size-default) + 2px);
|
|
65
|
-
border: 1px solid var(--
|
|
65
|
+
border: 1px solid var(--border-color-light);
|
|
66
66
|
background: var(--theme-secondary-lightest);
|
|
67
67
|
|
|
68
68
|
> ._indicator {
|
|
@@ -114,14 +114,9 @@ import { tablerCheck } from "@ng-icons/tabler-icons";
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
&:focus {
|
|
118
|
-
> ._indicator_rect {
|
|
119
|
-
border-color: var(--theme-#{$key}-default);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
117
|
&[data-sd-checked="true"] {
|
|
124
118
|
> ._indicator_rect {
|
|
119
|
+
border-color: var(--theme-#{$key}-default);
|
|
125
120
|
background: var(--theme-#{$key}-default);
|
|
126
121
|
|
|
127
122
|
> ._indicator {
|
|
@@ -129,6 +124,12 @@ import { tablerCheck } from "@ng-icons/tabler-icons";
|
|
|
129
124
|
}
|
|
130
125
|
}
|
|
131
126
|
}
|
|
127
|
+
|
|
128
|
+
&:focus {
|
|
129
|
+
> ._indicator_rect {
|
|
130
|
+
border-color: var(--theme-#{$key}-dark);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
132
133
|
}
|
|
133
134
|
}
|
|
134
135
|
|
|
@@ -138,15 +139,16 @@ import { tablerCheck } from "@ng-icons/tabler-icons";
|
|
|
138
139
|
border-color: var(--text-trans-lightest);
|
|
139
140
|
}
|
|
140
141
|
|
|
141
|
-
|
|
142
|
+
&[data-sd-checked="true"] {
|
|
142
143
|
> ._indicator_rect {
|
|
143
|
-
border-color: var(--
|
|
144
|
+
border-color: var(--theme-primary-default);
|
|
145
|
+
background: var(--theme-primary-default);
|
|
144
146
|
}
|
|
145
147
|
}
|
|
146
148
|
|
|
147
|
-
|
|
149
|
+
&:focus {
|
|
148
150
|
> ._indicator_rect {
|
|
149
|
-
|
|
151
|
+
border-color: var(--theme-primary-dark);
|
|
150
152
|
}
|
|
151
153
|
}
|
|
152
154
|
}
|
|
@@ -45,6 +45,8 @@ export class SdCollapseIcon {
|
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
transitionStyle = computed(() => {
|
|
48
|
-
return this.open()
|
|
48
|
+
return this.open()
|
|
49
|
+
? "transform var(--animation-duration) ease-out"
|
|
50
|
+
: "transform var(--animation-duration) ease-in";
|
|
49
51
|
});
|
|
50
52
|
}
|
|
@@ -54,7 +54,9 @@ export class SdCollapse {
|
|
|
54
54
|
|
|
55
55
|
contentTransition = computed(() => {
|
|
56
56
|
if (!this._initialized()) return "";
|
|
57
|
-
return this.open()
|
|
57
|
+
return this.open()
|
|
58
|
+
? "margin-top var(--animation-duration) ease-out"
|
|
59
|
+
: "margin-top var(--animation-duration) ease-in";
|
|
58
60
|
});
|
|
59
61
|
|
|
60
62
|
constructor() {
|
|
@@ -33,7 +33,7 @@ import { SdResizeDirective } from "../../core/events/sd-resize";
|
|
|
33
33
|
z-index: var(--z-index-dropdown);
|
|
34
34
|
opacity: 0;
|
|
35
35
|
transform: translateY(-0.625rem);
|
|
36
|
-
transition:
|
|
36
|
+
transition: var(--animation-duration) linear;
|
|
37
37
|
transition-property: transform, opacity;
|
|
38
38
|
pointer-events: none;
|
|
39
39
|
background: var(--control-color);
|
|
@@ -70,7 +70,7 @@ import { SdResizeDirective } from "../../core/events/sd-resize";
|
|
|
70
70
|
opacity: 1;
|
|
71
71
|
pointer-events: auto;
|
|
72
72
|
transform: none;
|
|
73
|
-
animation: sd-dropdown-slide-up
|
|
73
|
+
animation: sd-dropdown-slide-up var(--animation-duration) ease-out;
|
|
74
74
|
@include mixins.elevation(8);
|
|
75
75
|
}
|
|
76
76
|
}
|
|
@@ -56,6 +56,7 @@ import { setupInvalid } from "../../core/validation/setupInvalid";
|
|
|
56
56
|
> textarea,
|
|
57
57
|
> ._contents {
|
|
58
58
|
@include mixins.form-control-base();
|
|
59
|
+
font-family: var(--font-family-field);
|
|
59
60
|
resize: none;
|
|
60
61
|
|
|
61
62
|
overflow: auto;
|
|
@@ -79,6 +80,10 @@ import { setupInvalid } from "../../core/validation/setupInvalid";
|
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
> ._contents > pre {
|
|
84
|
+
font-family: inherit;
|
|
85
|
+
}
|
|
86
|
+
|
|
82
87
|
&:not([data-sd-inset="true"]):not([data-sd-disabled="true"]) > ._contents {
|
|
83
88
|
height: 0;
|
|
84
89
|
max-height: 0;
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
import { isPlatformBrowser } from "@angular/common";
|
|
1
2
|
import {
|
|
2
3
|
booleanAttribute,
|
|
3
4
|
ChangeDetectionStrategy,
|
|
4
5
|
Component,
|
|
5
6
|
computed,
|
|
7
|
+
effect,
|
|
8
|
+
ElementRef,
|
|
9
|
+
inject,
|
|
6
10
|
input,
|
|
7
11
|
model,
|
|
12
|
+
PLATFORM_ID,
|
|
13
|
+
viewChild,
|
|
8
14
|
ViewEncapsulation,
|
|
9
15
|
} from "@angular/core";
|
|
10
16
|
import { setupInvalid } from "../../core/validation/setupInvalid";
|
|
@@ -40,6 +46,7 @@ import {
|
|
|
40
46
|
</div>
|
|
41
47
|
@if (!readonly() && !disabled()) {
|
|
42
48
|
<input
|
|
49
|
+
#inputEl
|
|
43
50
|
[style]="inputStyle()"
|
|
44
51
|
[class]="inputClass()"
|
|
45
52
|
[attr.title]="title() ?? placeholder()"
|
|
@@ -48,11 +55,11 @@ import {
|
|
|
48
55
|
[attr.max]="max()"
|
|
49
56
|
[type]="controlType()"
|
|
50
57
|
[attr.inputmode]="type() === 'number' ? 'numeric' : undefined"
|
|
51
|
-
[value]="controlValue()"
|
|
52
58
|
[attr.autocomplete]="autocomplete()"
|
|
53
59
|
[attr.step]="controlStep()"
|
|
54
60
|
(input)="onInput($event)"
|
|
55
61
|
(paste)="onInputPaste($event)"
|
|
62
|
+
(blur)="onBlur($event)"
|
|
56
63
|
/>
|
|
57
64
|
}
|
|
58
65
|
`,
|
|
@@ -70,17 +77,18 @@ import {
|
|
|
70
77
|
> input,
|
|
71
78
|
> ._contents {
|
|
72
79
|
@include mixins.form-control-base();
|
|
80
|
+
font-family: var(--font-family-field);
|
|
73
81
|
|
|
74
82
|
overflow: auto;
|
|
75
83
|
width: 100%;
|
|
76
84
|
|
|
77
|
-
border: 1px solid var(--
|
|
85
|
+
border: 1px solid var(--border-color-light);
|
|
78
86
|
border-radius: var(--border-radius-default);
|
|
79
87
|
background: var(--theme-secondary-lightest);
|
|
80
88
|
|
|
81
89
|
&:focus {
|
|
82
90
|
outline: none;
|
|
83
|
-
border-color: var(--theme-
|
|
91
|
+
border-color: var(--theme-primary-default);
|
|
84
92
|
}
|
|
85
93
|
|
|
86
94
|
&[type="date"],
|
|
@@ -114,6 +122,10 @@ import {
|
|
|
114
122
|
display: none;
|
|
115
123
|
}
|
|
116
124
|
|
|
125
|
+
> ._contents > pre {
|
|
126
|
+
font-family: inherit;
|
|
127
|
+
}
|
|
128
|
+
|
|
117
129
|
&[data-sd-type="number"] {
|
|
118
130
|
> input,
|
|
119
131
|
> ._contents {
|
|
@@ -253,7 +265,7 @@ import {
|
|
|
253
265
|
&[data-sd-disabled="true"] {
|
|
254
266
|
> ._contents {
|
|
255
267
|
display: block;
|
|
256
|
-
background: var(--
|
|
268
|
+
background: var(--trans-lightest);
|
|
257
269
|
color: var(--text-trans-light);
|
|
258
270
|
}
|
|
259
271
|
|
|
@@ -316,6 +328,8 @@ export class SdTextfield<K extends keyof SdTextfieldTypes> {
|
|
|
316
328
|
"primary" | "secondary" | "info" | "success" | "warning" | "danger" | "gray" | "blue-gray"
|
|
317
329
|
>();
|
|
318
330
|
|
|
331
|
+
private readonly _inputElRef = viewChild<ElementRef<HTMLInputElement>>("inputEl");
|
|
332
|
+
|
|
319
333
|
private readonly _handler = computed(() => textfieldTypeHandlers[this.type()]);
|
|
320
334
|
|
|
321
335
|
controlType = computed(() => this._handler().controlType);
|
|
@@ -340,6 +354,21 @@ export class SdTextfield<K extends keyof SdTextfieldTypes> {
|
|
|
340
354
|
});
|
|
341
355
|
|
|
342
356
|
constructor() {
|
|
357
|
+
if (isPlatformBrowser(inject(PLATFORM_ID))) {
|
|
358
|
+
effect(() => {
|
|
359
|
+
const controlValue = this.controlValue();
|
|
360
|
+
const inputEl = this._inputElRef()?.nativeElement;
|
|
361
|
+
if (inputEl == null) return;
|
|
362
|
+
|
|
363
|
+
// 편집(포커스) 중에는 DOM 을 되쓰면 사용자 입력(세그먼트·캐럿·IME 조합)이 깨지므로 생략
|
|
364
|
+
if (document.activeElement === inputEl) return;
|
|
365
|
+
|
|
366
|
+
if (inputEl.value !== controlValue) {
|
|
367
|
+
inputEl.value = controlValue;
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
|
|
343
372
|
setupInvalid(() => {
|
|
344
373
|
const value = this.value();
|
|
345
374
|
const handlerErrors = this._handler().validate(value, {
|
|
@@ -379,6 +408,13 @@ export class SdTextfield<K extends keyof SdTextfieldTypes> {
|
|
|
379
408
|
this.value.set(parsed as SdTextfieldTypes[K]);
|
|
380
409
|
}
|
|
381
410
|
|
|
411
|
+
onBlur(event: FocusEvent): void {
|
|
412
|
+
// 편집 종료 시 모델 기준값으로 동기화 (number 콤마 포맷 반영, date 미완성 입력 정리)
|
|
413
|
+
// date 미완성 입력은 input.value 가 "" 라 비교 없이 무조건 되써야 남은 세그먼트가 정리됨
|
|
414
|
+
const inputEl = event.target as HTMLInputElement;
|
|
415
|
+
inputEl.value = this.controlValue();
|
|
416
|
+
}
|
|
417
|
+
|
|
382
418
|
onInputPaste(event: ClipboardEvent): void {
|
|
383
419
|
event.preventDefault();
|
|
384
420
|
const text = event.clipboardData?.getData("text/plain").trim();
|
|
@@ -84,12 +84,12 @@ import { SdRipple } from "../../core/ripple/sd-ripple";
|
|
|
84
84
|
&:focus-visible {
|
|
85
85
|
outline: none;
|
|
86
86
|
transition: background var(--animation-duration) ease-out;
|
|
87
|
-
background: var(--trans-
|
|
87
|
+
background: var(--trans-light);
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
&[data-sd-selected="true"] > ._content {
|
|
92
|
-
background: var(--trans-
|
|
92
|
+
background: var(--trans-light);
|
|
93
93
|
font-weight: bold;
|
|
94
94
|
}
|
|
95
95
|
|
|
@@ -97,7 +97,7 @@ import { SdRipple } from "../../core/ripple/sd-ripple";
|
|
|
97
97
|
color: var(--text-trans-default);
|
|
98
98
|
|
|
99
99
|
&:hover {
|
|
100
|
-
background: var(--trans-
|
|
100
|
+
background: var(--trans-light);
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
|
|
@@ -128,13 +128,13 @@ import { SdRipple } from "../../core/ripple/sd-ripple";
|
|
|
128
128
|
&:not([data-sd-readonly="true"]) {
|
|
129
129
|
> ._content:hover {
|
|
130
130
|
transition: background var(--animation-duration) ease-out;
|
|
131
|
-
background: var(--trans-
|
|
131
|
+
background: var(--trans-light);
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
// 깊이별 들여쓰기: accordion 항목의 자식 리스트를 한 칸씩 누적 들여씀.
|
|
136
136
|
// (flat 항목의 자식 리스트는 들여쓰지 않음)
|
|
137
|
-
> sd-collapse > ._content > ._children > sd-list {
|
|
137
|
+
> sd-collapse > ._content > ._children > sd-list > sd-list-item > ._content {
|
|
138
138
|
padding-left: 1.5em;
|
|
139
139
|
}
|
|
140
140
|
}
|
|
@@ -22,7 +22,7 @@ import { setupRipple } from "../../core/ripple/setupRipple";
|
|
|
22
22
|
font-weight: bold;
|
|
23
23
|
cursor: pointer;
|
|
24
24
|
color: var(--theme-primary-default);
|
|
25
|
-
transition: background
|
|
25
|
+
transition: background var(--animation-duration) linear;
|
|
26
26
|
|
|
27
27
|
&:hover {
|
|
28
28
|
color: var(--theme-primary-darker);
|
|
@@ -40,17 +40,18 @@ import { SdGap } from "../gap/sd-gap";
|
|
|
40
40
|
display: block;
|
|
41
41
|
padding: var(--gap-sm) var(--gap-default);
|
|
42
42
|
cursor: pointer;
|
|
43
|
-
|
|
43
|
+
font-family: var(--font-family-field);
|
|
44
|
+
transition: background var(--animation-duration) ease-in;
|
|
44
45
|
background: var(--control-color);
|
|
45
46
|
|
|
46
47
|
&:hover {
|
|
47
|
-
transition: background
|
|
48
|
+
transition: background var(--animation-duration) ease-out;
|
|
48
49
|
background: rgba(0, 0, 0, 0.07);
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
&:focus {
|
|
52
53
|
outline: none;
|
|
53
|
-
transition: background
|
|
54
|
+
transition: background var(--animation-duration) ease-out;
|
|
54
55
|
background: rgba(0, 0, 0, 0.07);
|
|
55
56
|
}
|
|
56
57
|
|
|
@@ -113,7 +113,7 @@ export type SelectModeValue<T> = {
|
|
|
113
113
|
display: flex;
|
|
114
114
|
overflow: hidden;
|
|
115
115
|
|
|
116
|
-
border: 1px solid var(--
|
|
116
|
+
border: 1px solid var(--border-color-light);
|
|
117
117
|
border-radius: var(--border-radius-default);
|
|
118
118
|
background: var(--theme-secondary-lightest);
|
|
119
119
|
|
|
@@ -129,6 +129,7 @@ export type SelectModeValue<T> = {
|
|
|
129
129
|
flex-grow: 1;
|
|
130
130
|
padding: var(--gap-sm) var(--gap-default);
|
|
131
131
|
cursor: pointer;
|
|
132
|
+
font-family: var(--font-family-field);
|
|
132
133
|
|
|
133
134
|
> ._sd-select-control-content {
|
|
134
135
|
flex: 1;
|
|
@@ -103,7 +103,7 @@ import { SdBusyProvider, type SdBusyType } from "./sd-busy.provider";
|
|
|
103
103
|
content: "";
|
|
104
104
|
height: 4px;
|
|
105
105
|
width: 100%;
|
|
106
|
-
transition:
|
|
106
|
+
transition: var(--animation-duration) ease-in;
|
|
107
107
|
transition-property: transform;
|
|
108
108
|
transform-origin: left;
|
|
109
109
|
transform: scaleX(0);
|
|
@@ -124,7 +124,7 @@ import { SdBusyProvider, type SdBusyType } from "./sd-busy.provider";
|
|
|
124
124
|
&[data-sd-type="spinner"] {
|
|
125
125
|
> ._screen > ._rect {
|
|
126
126
|
transform: translateY(-100%);
|
|
127
|
-
transition:
|
|
127
|
+
transition: var(--animation-duration) ease-in;
|
|
128
128
|
transition-property: transform;
|
|
129
129
|
|
|
130
130
|
> ._indicator {
|
|
@@ -157,7 +157,7 @@ import { SdBusyProvider, type SdBusyType } from "./sd-busy.provider";
|
|
|
157
157
|
&[data-sd-busy="true"] {
|
|
158
158
|
> ._screen > ._rect {
|
|
159
159
|
transform: none;
|
|
160
|
-
transition:
|
|
160
|
+
transition: var(--animation-duration) ease-out;
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
}
|
|
@@ -15,15 +15,6 @@ export type UndefToOptional<T> = {
|
|
|
15
15
|
* 컴포넌트/디렉티브의 InputSignal 프로퍼티에서 값 타입을 추출하는 유틸리티 타입
|
|
16
16
|
*
|
|
17
17
|
* InputSignal이 아닌 프로퍼티는 제외되며, undefined를 포함하는 필드는 optional로 변환된다.
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```typescript
|
|
21
|
-
* class MyComponent {
|
|
22
|
-
* name = input.required<string>();
|
|
23
|
-
* age = input(0);
|
|
24
|
-
* }
|
|
25
|
-
* // DirectiveInputSignals<MyComponent> = { name: string; age: number }
|
|
26
|
-
* ```
|
|
27
18
|
*/
|
|
28
19
|
export type DirectiveInputSignals<T> = UndefToOptional<{
|
|
29
20
|
[P in keyof T as T[P] extends InputSignal<any> ? P : never]: T[P] extends InputSignal<
|
|
@@ -67,6 +67,20 @@ export function provideSdAngular(opt: { clientName: string }): EnvironmentProvid
|
|
|
67
67
|
}
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
+
const savedBlueprint = sdLocalStorage.get("sd-theme-blueprint");
|
|
71
|
+
if (savedBlueprint != null) {
|
|
72
|
+
sdTheme.blueprint.set(savedBlueprint);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let prevBlueprint = sdTheme.blueprint();
|
|
76
|
+
effect(() => {
|
|
77
|
+
const blueprint = sdTheme.blueprint();
|
|
78
|
+
if (blueprint !== prevBlueprint) {
|
|
79
|
+
sdLocalStorage.set("sd-theme-blueprint", blueprint);
|
|
80
|
+
prevBlueprint = blueprint;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
70
84
|
const savedFontSize = sdLocalStorage.get("sd-theme-font-size");
|
|
71
85
|
if (savedFontSize != null) {
|
|
72
86
|
sdTheme.fontSize.set(savedFontSize);
|
|
@@ -42,7 +42,7 @@ export function setupRipple(enableFn?: () => boolean): void {
|
|
|
42
42
|
top: y - size + "px",
|
|
43
43
|
left: x - size + "px",
|
|
44
44
|
|
|
45
|
-
transition: "var(--animation-duration) linear",
|
|
45
|
+
transition: "calc(2 * var(--animation-duration)) linear",
|
|
46
46
|
transitionProperty: "transform, opacity",
|
|
47
47
|
transform: "scale(0.1)",
|
|
48
48
|
});
|
|
@@ -55,9 +55,9 @@ import { NgTemplateOutlet } from "@angular/common";
|
|
|
55
55
|
</sd-busy-container>
|
|
56
56
|
|
|
57
57
|
<ng-template #content>
|
|
58
|
-
<div class="flex-column fill">
|
|
58
|
+
<div class="flex-column fill bg-control">
|
|
59
59
|
@if (commandTplRef()) {
|
|
60
|
-
<div class="p-sm-default flex-row gap-default bdb bdb-color-lighter
|
|
60
|
+
<div class="p-sm-default flex-row gap-default bdb bdb-color-lighter">
|
|
61
61
|
<ng-template [ngTemplateOutlet]="commandTplRef()" />
|
|
62
62
|
</div>
|
|
63
63
|
}
|
|
@@ -70,7 +70,7 @@ import { NgTemplateOutlet } from "@angular/common";
|
|
|
70
70
|
|
|
71
71
|
@if (bottomCommandTplRef()) {
|
|
72
72
|
<div
|
|
73
|
-
class="p-sm-default flex-row main-align-end gap-sm bdt bdt-theme-gray-lighter
|
|
73
|
+
class="p-sm-default flex-row main-align-end gap-sm bdt bdt-theme-gray-lighter"
|
|
74
74
|
>
|
|
75
75
|
<ng-template [ngTemplateOutlet]="bottomCommandTplRef()" />
|
|
76
76
|
</div>
|
|
@@ -131,33 +131,33 @@ import {
|
|
|
131
131
|
</sd-form>
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
@if (
|
|
134
|
+
@if (canCreate() || canDelete() || toolTplRef()) {
|
|
135
135
|
<div class="flex-row gap-sm p-xs-default">
|
|
136
|
-
@if (
|
|
136
|
+
@if (canCreate()) {
|
|
137
137
|
<sd-button [size]="'sm'" [theme]="'link-primary'" (click)="create.emit()">
|
|
138
138
|
<ng-icon [svg]="tablerCirclePlus" />
|
|
139
139
|
등록
|
|
140
140
|
</sd-button>
|
|
141
|
-
|
|
141
|
+
}
|
|
142
|
+
@if (canDelete() && selectMode() !== "single") {
|
|
143
|
+
<sd-button
|
|
144
|
+
[size]="'sm'"
|
|
145
|
+
[theme]="'link-danger'"
|
|
146
|
+
(click)="delete.emit(currSelectedItems())"
|
|
147
|
+
[disabled]="!hasSelectedNotDeleted()"
|
|
148
|
+
>
|
|
149
|
+
<ng-icon [svg]="tablerEraser" />
|
|
150
|
+
선택 삭제
|
|
151
|
+
</sd-button>
|
|
152
|
+
@if (hasSelectedDeleted()) {
|
|
142
153
|
<sd-button
|
|
143
154
|
[size]="'sm'"
|
|
144
|
-
[theme]="'link-
|
|
145
|
-
(click)="
|
|
146
|
-
[disabled]="!hasSelectedNotDeleted()"
|
|
155
|
+
[theme]="'link-warning'"
|
|
156
|
+
(click)="restore.emit(currSelectedItems())"
|
|
147
157
|
>
|
|
148
|
-
<ng-icon [svg]="
|
|
149
|
-
선택
|
|
158
|
+
<ng-icon [svg]="tablerRestore" />
|
|
159
|
+
선택 복구
|
|
150
160
|
</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
161
|
}
|
|
162
162
|
}
|
|
163
163
|
|
|
@@ -190,7 +190,7 @@ import {
|
|
|
190
190
|
[visiblePageCount]="visiblePageCount()"
|
|
191
191
|
[(sorts)]="sorts"
|
|
192
192
|
[useAutoSort]="totalPageCount() === 0"
|
|
193
|
-
[selectMode]="selectMode() ?? (
|
|
193
|
+
[selectMode]="selectMode() ?? (canDelete() ? 'multi' : undefined)"
|
|
194
194
|
[(selectedKeys)]="selectedKeys"
|
|
195
195
|
[getItemSelectableFn]="getItemSelectableFn()"
|
|
196
196
|
[trackByFn]="trackByFn()"
|
|
@@ -199,7 +199,7 @@ import {
|
|
|
199
199
|
[columnControlsInput]="columnControls()"
|
|
200
200
|
(selectedKeysChange)="onSelectedKeysChange()"
|
|
201
201
|
>
|
|
202
|
-
@if (
|
|
202
|
+
@if (canDelete() && inlineEdit()) {
|
|
203
203
|
<sd-sheet-column
|
|
204
204
|
[fixed]="true"
|
|
205
205
|
[key]="'deleteButton'"
|
|
@@ -234,7 +234,9 @@ export class SdCrudList<TItem, TKey> {
|
|
|
234
234
|
initialized = input(false);
|
|
235
235
|
busyCount = model(0);
|
|
236
236
|
restricted = input(false);
|
|
237
|
-
|
|
237
|
+
canCreate = input(true);
|
|
238
|
+
canEdit = input(true);
|
|
239
|
+
canDelete = input(true);
|
|
238
240
|
inlineEdit = input(true);
|
|
239
241
|
viewType = input.required<SdViewType>();
|
|
240
242
|
selectMode = input<"single" | "multi">();
|
|
@@ -277,7 +279,7 @@ export class SdCrudList<TItem, TKey> {
|
|
|
277
279
|
|
|
278
280
|
columnControls = contentChildren(SdSheetColumn);
|
|
279
281
|
|
|
280
|
-
inlineEditEnabled = computed(() =>
|
|
282
|
+
inlineEditEnabled = computed(() => this.canEdit() && this.inlineEdit());
|
|
281
283
|
|
|
282
284
|
hasSelectedDeleted = computed(() => this.currSelectedItems().some((it) => this.isDeleted(it)));
|
|
283
285
|
hasSelectedNotDeleted = computed(() =>
|
|
@@ -102,7 +102,7 @@ import { tablerEye, tablerEyeOff } from "@ng-icons/tabler-icons";
|
|
|
102
102
|
height: 0;
|
|
103
103
|
margin-bottom: 0;
|
|
104
104
|
visibility: hidden;
|
|
105
|
-
transition:
|
|
105
|
+
transition: var(--animation-duration) linear;
|
|
106
106
|
transition-property: height, margin-bottom, visibility;
|
|
107
107
|
}
|
|
108
108
|
}
|
|
@@ -91,7 +91,7 @@ import { SdResizeDirective, type SdResizeEvent } from "../../core/events/sd-resi
|
|
|
91
91
|
height: 0;
|
|
92
92
|
margin-bottom: 0;
|
|
93
93
|
visibility: hidden;
|
|
94
|
-
transition:
|
|
94
|
+
transition: var(--animation-duration) linear;
|
|
95
95
|
transition-property: height, margin-bottom, visibility;
|
|
96
96
|
}
|
|
97
97
|
|
|
@@ -361,9 +361,9 @@ import { SdEvents } from "../../core/events/sd-events";
|
|
|
361
361
|
$z-index-focus-row-indicator: 6;
|
|
362
362
|
$z-index-resize-indicator: 7;
|
|
363
363
|
|
|
364
|
-
$border-color: var(--
|
|
365
|
-
$border-color-dark: var(--
|
|
366
|
-
$border-color-darker: var(--
|
|
364
|
+
$border-color: var(--border-color-lighter);
|
|
365
|
+
$border-color-dark: var(--border-color-lighter);
|
|
366
|
+
$border-color-darker: var(--border-color-light);
|
|
367
367
|
|
|
368
368
|
$border-radius: var(--border-radius-default);
|
|
369
369
|
|
|
@@ -500,6 +500,7 @@ import { SdEvents } from "../../core/events/sd-events";
|
|
|
500
500
|
}
|
|
501
501
|
|
|
502
502
|
> tbody > tr > td {
|
|
503
|
+
font-family: var(--font-family-field);
|
|
503
504
|
background: var(--control-color);
|
|
504
505
|
vertical-align: top;
|
|
505
506
|
|
|
@@ -5,6 +5,9 @@ import { isPlatformBrowser } from "@angular/common";
|
|
|
5
5
|
export class SdThemeProvider {
|
|
6
6
|
dark = signal<boolean>(false);
|
|
7
7
|
|
|
8
|
+
// 블루프린트(엔지니어링 도면) 테마 — dark 토글과 직교. dark 와 조합해 4면을 만든다.
|
|
9
|
+
blueprint = signal<boolean>(false);
|
|
10
|
+
|
|
8
11
|
readonly fontSizePresets: readonly number[] = [12, 14, 16, 20, 24, 28];
|
|
9
12
|
|
|
10
13
|
fontSize = signal<number>(12);
|
|
@@ -17,6 +20,10 @@ export class SdThemeProvider {
|
|
|
17
20
|
document.body.classList.toggle("sd-theme-dark", this.dark());
|
|
18
21
|
});
|
|
19
22
|
|
|
23
|
+
effect(() => {
|
|
24
|
+
document.body.classList.toggle("sd-theme-blueprint", this.blueprint());
|
|
25
|
+
});
|
|
26
|
+
|
|
20
27
|
effect(() => {
|
|
21
28
|
document.documentElement.style.fontSize = `${this.fontSize()}px`;
|
|
22
29
|
});
|
|
@@ -54,6 +54,10 @@ import { tablerMinus, tablerPalette, tablerPlus } from "@ng-icons/tabler-icons";
|
|
|
54
54
|
<span>다크 모드</span>
|
|
55
55
|
<sd-switch [(value)]="_sdTheme.dark" />
|
|
56
56
|
</div>
|
|
57
|
+
<div class="flex-row gap-sm cross-align-center">
|
|
58
|
+
<span>블루프린트</span>
|
|
59
|
+
<sd-switch [(value)]="_sdTheme.blueprint" />
|
|
60
|
+
</div>
|
|
57
61
|
</div>
|
|
58
62
|
</sd-dropdown-popup>
|
|
59
63
|
</sd-dropdown>
|
|
@@ -25,11 +25,11 @@ import { filter } from "rxjs";
|
|
|
25
25
|
position: relative;
|
|
26
26
|
height: 100%;
|
|
27
27
|
padding-left: var(--sidebar-width);
|
|
28
|
-
transition: padding-left
|
|
28
|
+
transition: padding-left var(--animation-duration) ease-out;
|
|
29
29
|
|
|
30
30
|
&[data-sd-toggle="true"] {
|
|
31
31
|
padding-left: 0;
|
|
32
|
-
transition: padding-left
|
|
32
|
+
transition: padding-left var(--animation-duration) ease-in;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
> ._backdrop {
|
|
@@ -52,7 +52,7 @@ import { filter } from "rxjs";
|
|
|
52
52
|
background: var(--background-rev-color);
|
|
53
53
|
opacity: 0;
|
|
54
54
|
pointer-events: none;
|
|
55
|
-
transition: opacity
|
|
55
|
+
transition: opacity var(--animation-duration) ease-in-out;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
&[data-sd-toggle="true"] {
|
|
@@ -33,25 +33,25 @@ import { SdSidebarContainer } from "./sd-sidebar-container";
|
|
|
33
33
|
width: var(--sidebar-width);
|
|
34
34
|
height: 100%;
|
|
35
35
|
//background: var(--control-color);
|
|
36
|
-
background: var(--theme-gray-lightest);
|
|
36
|
+
//background: var(--theme-gray-lightest);
|
|
37
37
|
//border-right: 1px solid var(--border-color-lighter);
|
|
38
38
|
|
|
39
39
|
@media not all and (max-width: variables.$breakpoint-mobile) {
|
|
40
|
-
transition: transform
|
|
40
|
+
transition: transform var(--animation-duration) ease-out;
|
|
41
41
|
|
|
42
42
|
&[data-sd-toggle="true"] {
|
|
43
43
|
transform: translateX(-100%);
|
|
44
|
-
transition: transform
|
|
44
|
+
transition: transform var(--animation-duration) ease-in;
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
@media all and (max-width: variables.$breakpoint-mobile) {
|
|
49
|
-
transition: transform
|
|
49
|
+
transition: transform var(--animation-duration) ease-in;
|
|
50
50
|
transform: translateX(-100%);
|
|
51
51
|
|
|
52
52
|
&[data-sd-toggle="true"] {
|
|
53
53
|
transform: none;
|
|
54
|
-
transition: transform
|
|
54
|
+
transition: transform var(--animation-duration) ease-out;
|
|
55
55
|
@include mixins.elevation(16);
|
|
56
56
|
}
|
|
57
57
|
}
|