@simplysm/angular 14.0.39 → 14.0.41

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 (82) hide show
  1. package/dist/controls/collapse/sd-collapse.d.ts +2 -1
  2. package/dist/controls/collapse/sd-collapse.d.ts.map +1 -1
  3. package/dist/controls/collapse/sd-collapse.js +6 -0
  4. package/dist/controls/dropdown/sd-dropdown.js +6 -6
  5. package/dist/controls/list/sd-list-item.d.ts.map +1 -1
  6. package/dist/controls/list/sd-list-item.js +3 -3
  7. package/dist/controls/select/sd-select.d.ts.map +1 -1
  8. package/dist/controls/select/sd-select.js +7 -11
  9. package/dist/core/app-structure/sd-app-structure.provider.d.ts +2 -2
  10. package/dist/core/app-structure/sd-app-structure.provider.d.ts.map +1 -1
  11. package/dist/core/app-structure/sd-app-structure.provider.js +4 -4
  12. package/dist/core/config/sd-local-storage.provider.d.ts +1 -1
  13. package/dist/core/config/sd-local-storage.provider.d.ts.map +1 -1
  14. package/dist/core/config/sd-local-storage.provider.js +4 -4
  15. package/dist/core/error-handler/sd-global-error-handler.plugin.d.ts +1 -1
  16. package/dist/core/error-handler/sd-global-error-handler.plugin.d.ts.map +1 -1
  17. package/dist/core/error-handler/sd-global-error-handler.plugin.js +3 -3
  18. package/dist/core/modal/injectFocusTrap.js +4 -4
  19. package/dist/core/modal/sd-modal.d.ts +2 -2
  20. package/dist/core/modal/sd-modal.d.ts.map +1 -1
  21. package/dist/core/modal/sd-modal.js +8 -8
  22. package/dist/core/modal/sd-modal.provider.js +4 -4
  23. package/dist/core/routing/injectViewTitleSignal.js +3 -3
  24. package/dist/core/routing/injectViewTypeSignal.js +2 -2
  25. package/dist/core/routing/sd-router-link.d.ts +1 -1
  26. package/dist/core/routing/sd-router-link.d.ts.map +1 -1
  27. package/dist/core/routing/sd-router-link.js +4 -4
  28. package/dist/core/routing/setupCanDeactivate.js +3 -3
  29. package/dist/core/service-client/sd-service-client-factory.provider.d.ts +2 -2
  30. package/dist/core/service-client/sd-service-client-factory.provider.d.ts.map +1 -1
  31. package/dist/core/service-client/sd-service-client-factory.provider.js +4 -4
  32. package/dist/core/shared-data/sd-shared-data.provider.d.ts +4 -3
  33. package/dist/core/shared-data/sd-shared-data.provider.d.ts.map +1 -1
  34. package/dist/core/shared-data/sd-shared-data.provider.js +4 -4
  35. package/dist/core/toast/sd-toast-container.js +2 -2
  36. package/dist/core/toast/sd-toast.d.ts.map +1 -1
  37. package/dist/core/toast/sd-toast.js +30 -25
  38. package/dist/core/toast/sd-toast.provider.d.ts +3 -3
  39. package/dist/core/toast/sd-toast.provider.d.ts.map +1 -1
  40. package/dist/core/toast/sd-toast.provider.js +2 -2
  41. package/dist/core/validation/setupInvalid.js +6 -5
  42. package/dist/data/data-detail/sd-data-detail.base.d.ts +6 -4
  43. package/dist/data/data-detail/sd-data-detail.base.d.ts.map +1 -1
  44. package/dist/data/data-select-button/sd-data-select-button.base.d.ts +2 -2
  45. package/dist/data/data-select-button/sd-data-select-button.base.d.ts.map +1 -1
  46. package/dist/data/sheet/sd-sheet.d.ts +1 -1
  47. package/dist/data/sheet/sd-sheet.d.ts.map +1 -1
  48. package/dist/data/sheet/sd-sheet.js +2 -2
  49. package/dist/data/sheet/useSheetCellAgent.js +6 -6
  50. package/dist/layout/base-container/sd-base-container.d.ts +1 -1
  51. package/dist/layout/base-container/sd-base-container.d.ts.map +1 -1
  52. package/dist/layout/base-container/sd-base-container.js +2 -2
  53. package/dist/layout/sidebar/sd-sidebar-menu.js +3 -3
  54. package/dist/styles.css +1 -1
  55. package/package.json +5 -5
  56. package/scss/commons/_styles.scss +1 -1
  57. package/src/controls/collapse/sd-collapse.ts +6 -0
  58. package/src/controls/dropdown/sd-dropdown.ts +6 -6
  59. package/src/controls/list/sd-list-item.ts +0 -6
  60. package/src/controls/select/sd-select.ts +8 -13
  61. package/src/core/app-structure/sd-app-structure.provider.ts +4 -4
  62. package/src/core/config/sd-local-storage.provider.ts +4 -4
  63. package/src/core/error-handler/sd-global-error-handler.plugin.ts +3 -3
  64. package/src/core/modal/injectFocusTrap.ts +4 -4
  65. package/src/core/modal/sd-modal.provider.ts +4 -4
  66. package/src/core/modal/sd-modal.ts +8 -8
  67. package/src/core/routing/injectViewTitleSignal.ts +3 -3
  68. package/src/core/routing/injectViewTypeSignal.ts +2 -2
  69. package/src/core/routing/sd-router-link.ts +4 -4
  70. package/src/core/routing/setupCanDeactivate.ts +3 -3
  71. package/src/core/service-client/sd-service-client-factory.provider.ts +4 -4
  72. package/src/core/shared-data/sd-shared-data.provider.ts +5 -5
  73. package/src/core/toast/sd-toast-container.ts +8 -8
  74. package/src/core/toast/sd-toast.provider.ts +6 -6
  75. package/src/core/toast/sd-toast.ts +82 -49
  76. package/src/core/validation/setupInvalid.ts +7 -6
  77. package/src/data/data-detail/sd-data-detail.base.ts +3 -3
  78. package/src/data/data-select-button/sd-data-select-button.base.ts +1 -1
  79. package/src/data/sheet/sd-sheet.ts +2 -2
  80. package/src/data/sheet/useSheetCellAgent.ts +6 -6
  81. package/src/layout/base-container/sd-base-container.ts +2 -2
  82. package/src/layout/sidebar/sd-sidebar-menu.ts +1 -1
@@ -15,19 +15,19 @@ export function injectPermsSignal<K extends string>(viewCodes: string[], keys: K
15
15
  // 메뉴는 모듈/권한 모두 체크함
16
16
  @Injectable({ providedIn: "root" })
17
17
  export class SdAppStructureProvider<TModule = unknown> {
18
- private readonly _clientFactory = inject(SdServiceClientFactoryProvider);
19
- private readonly _config = inject(SdAngularConfigProvider);
18
+ private readonly _sdServiceClientFactory = inject(SdServiceClientFactoryProvider);
19
+ private readonly _sdAngularConfig = inject(SdAngularConfigProvider);
20
20
 
21
21
  readonly usableModules = signal<TModule[] | undefined>(undefined);
22
22
  readonly permRecord = signal<Record<string, boolean> | undefined>(undefined);
23
23
  readonly items = signal<AppStructureItem<TModule>[]>([]);
24
24
 
25
25
  async initialize(serviceKey: string): Promise<void> {
26
- const client = this._clientFactory.get(serviceKey);
26
+ const client = this._sdServiceClientFactory.get(serviceKey);
27
27
  const svc = client.getService<AppStructureService>("AppStructure");
28
28
  const itemsMap = await svc.getItems();
29
29
  this.items.set(
30
- (itemsMap[this._config.clientName] ?? []) as AppStructureItem<TModule>[],
30
+ (itemsMap[this._sdAngularConfig.clientName] ?? []) as AppStructureItem<TModule>[],
31
31
  );
32
32
  }
33
33
 
@@ -3,14 +3,14 @@ import { SdAngularConfigProvider } from "./sd-angular-config.provider";
3
3
 
4
4
  @Injectable({ providedIn: "root" })
5
5
  export class SdLocalStorageProvider<T> {
6
- private readonly _sdNgConf = inject(SdAngularConfigProvider);
6
+ private readonly _sdAngularConfig = inject(SdAngularConfigProvider);
7
7
 
8
8
  set<K extends keyof T & string>(key: K, value: T[K]) {
9
- localStorage.setItem(`${this._sdNgConf.clientName}.${key}`, JSON.stringify(value));
9
+ localStorage.setItem(`${this._sdAngularConfig.clientName}.${key}`, JSON.stringify(value));
10
10
  }
11
11
 
12
12
  get<K extends keyof T & string>(key: K): T[K] | undefined {
13
- const json = localStorage.getItem(`${this._sdNgConf.clientName}.${key}`);
13
+ const json = localStorage.getItem(`${this._sdAngularConfig.clientName}.${key}`);
14
14
  if (json == null) return undefined;
15
15
  try {
16
16
  return JSON.parse(json);
@@ -20,6 +20,6 @@ export class SdLocalStorageProvider<T> {
20
20
  }
21
21
 
22
22
  remove(key: keyof T & string) {
23
- localStorage.removeItem(`${this._sdNgConf.clientName}.${key}`);
23
+ localStorage.removeItem(`${this._sdAngularConfig.clientName}.${key}`);
24
24
  }
25
25
  }
@@ -11,7 +11,7 @@ import { SdSystemLogProvider } from "../config/sd-system-log.provider";
11
11
  @Injectable({ providedIn: null })
12
12
  export class SdGlobalErrorHandlerPlugin implements ErrorHandler {
13
13
  private readonly _envInjector = inject(EnvironmentInjector);
14
- private readonly _systemLog = inject(SdSystemLogProvider);
14
+ private readonly _sdSystemLog = inject(SdSystemLogProvider);
15
15
  private _hasDisplayedError = false;
16
16
 
17
17
  handleError(event: any) {
@@ -41,7 +41,7 @@ export class SdGlobalErrorHandlerPlugin implements ErrorHandler {
41
41
  const { message, filename, lineno, colno, error } = event;
42
42
 
43
43
  if (error == null) {
44
- void this._systemLog.writeAsync("warn", message);
44
+ void this._sdSystemLog.writeAsync("warn", message);
45
45
  return;
46
46
  }
47
47
 
@@ -95,7 +95,7 @@ export class SdGlobalErrorHandlerPlugin implements ErrorHandler {
95
95
 
96
96
  const paramLines = Object.keys(param).map((key) => key + ": " + param[key]);
97
97
 
98
- void this._systemLog.writeAsync("error", `[${title}]\n${paramLines.join("\n")}`);
98
+ void this._sdSystemLog.writeAsync("error", `[${title}]\n${paramLines.join("\n")}`);
99
99
 
100
100
  const appRef = this._envInjector.get<ApplicationRef>(ApplicationRef);
101
101
  appRef.destroy();
@@ -21,11 +21,11 @@ export function injectFocusTrap(): {
21
21
 
22
22
  function handleTabTrap(event: KeyboardEvent): void {
23
23
  const hostEl = elRef.nativeElement;
24
- const focusableElements = getTabbableElements(hostEl);
25
- if (focusableElements.length === 0) return;
24
+ const tabbableElements = getTabbableElements(hostEl);
25
+ if (tabbableElements.length === 0) return;
26
26
 
27
- const first = focusableElements[0];
28
- const last = focusableElements[focusableElements.length - 1];
27
+ const first = tabbableElements[0];
28
+ const last = tabbableElements[tabbableElements.length - 1];
29
29
 
30
30
  if (event.shiftKey) {
31
31
  if (document.activeElement === first) {
@@ -170,11 +170,11 @@ export class SdModalProvider {
170
170
  if (options?.noFirstControlFocusing === true) {
171
171
  dialogEl.focus();
172
172
  } else {
173
- // 컨텐츠 영역에서 첫 포커스 가능 요소 검색
173
+ // 컨텐츠 영역에서 첫 이동 가능 요소 검색
174
174
  const contentEl = modalEl.querySelector("._content");
175
- const firstFocusable = contentEl?.findFirstFocusableChild();
176
- if (firstFocusable !== undefined) {
177
- firstFocusable.focus();
175
+ const firstTabbable = contentEl?.findFirstTabbableChild();
176
+ if (firstTabbable !== undefined) {
177
+ firstTabbable.focus();
178
178
  } else {
179
179
  dialogEl.focus();
180
180
  }
@@ -285,8 +285,8 @@ import "@simplysm/core-browser";
285
285
  })
286
286
  export class SdModal {
287
287
  private readonly _elRef = inject(ElementRef<HTMLElement>);
288
- private readonly _activatedModal = inject(SdActivatedModalProvider, { optional: true });
289
- private readonly _systemConfig = inject(SdSystemConfigProvider, { optional: true });
288
+ private readonly _sdActivatedModal = inject(SdActivatedModalProvider, { optional: true });
289
+ private readonly _sdSystemConfig = inject(SdSystemConfigProvider, { optional: true });
290
290
  private readonly _errorHandler = inject(ErrorHandler);
291
291
  private readonly _focusTrap = injectFocusTrap();
292
292
 
@@ -347,7 +347,7 @@ export class SdModal {
347
347
  // key 기반 설정 복원
348
348
  effect(() => {
349
349
  const k = this.key();
350
- if (k === undefined || this._systemConfig == null) return;
350
+ if (k === undefined || this._sdSystemConfig == null) return;
351
351
 
352
352
  void this._restoreConfig(k).catch((err) => this._errorHandler.handleError(err));
353
353
  });
@@ -389,7 +389,7 @@ export class SdModal {
389
389
  }
390
390
 
391
391
  private _requestClose(): void {
392
- if (this._activatedModal !== null && !this._activatedModal.canDeactivateFn()) {
392
+ if (this._sdActivatedModal !== null && !this._sdActivatedModal.canDeactivateFn()) {
393
393
  return;
394
394
  }
395
395
  void this._saveConfig().catch((err) => this._errorHandler.handleError(err));
@@ -418,7 +418,7 @@ export class SdModal {
418
418
 
419
419
  private async _saveConfig(): Promise<void> {
420
420
  const k = this.key();
421
- if (k === undefined || this._systemConfig == null) return;
421
+ if (k === undefined || this._sdSystemConfig == null) return;
422
422
 
423
423
  const dialogEl = this._getDialogEl();
424
424
  if (dialogEl === null) return;
@@ -429,13 +429,13 @@ export class SdModal {
429
429
  if (dialogEl.style.left !== "") config["left"] = dialogEl.style.left;
430
430
  if (dialogEl.style.top !== "") config["top"] = dialogEl.style.top;
431
431
 
432
- await this._systemConfig.setAsync(`sd-modal.${k}` as any, config as any);
432
+ await this._sdSystemConfig.setAsync(`sd-modal.${k}` as any, config as any);
433
433
  }
434
434
 
435
435
  private async _restoreConfig(k: string): Promise<void> {
436
- if (this._systemConfig == null) return;
436
+ if (this._sdSystemConfig == null) return;
437
437
 
438
- const config = (await this._systemConfig.getAsync(`sd-modal.${k}` as any)) as
438
+ const config = (await this._sdSystemConfig.getAsync(`sd-modal.${k}` as any)) as
439
439
  | Record<string, string | undefined>
440
440
  | undefined;
441
441
  if (config == null) return;
@@ -5,15 +5,15 @@ import { injectFullPageCodeSignal } from "./injectFullPageCodeSignal";
5
5
  import { injectCurrentPageCodeSignal } from "./injectCurrentPageCodeSignal";
6
6
 
7
7
  export function injectViewTitleSignal(): Signal<string> {
8
- const activatedModal = inject(SdActivatedModalProvider, { optional: true });
8
+ const sdActivatedModal = inject(SdActivatedModalProvider, { optional: true });
9
9
  const sdAppStructure = inject(SdAppStructureProvider);
10
10
 
11
11
  const fullPageCode = injectFullPageCodeSignal();
12
12
  const currPageCode = injectCurrentPageCodeSignal();
13
13
 
14
14
  return computed(() => {
15
- if (activatedModal != null) {
16
- return activatedModal.modalComponent()?.title() ?? "";
15
+ if (sdActivatedModal != null) {
16
+ return sdActivatedModal.modalComponent()?.title() ?? "";
17
17
  }
18
18
 
19
19
  try {
@@ -5,14 +5,14 @@ import { injectCurrentPageCodeSignal } from "./injectCurrentPageCodeSignal";
5
5
  import { injectFullPageCodeSignal } from "./injectFullPageCodeSignal";
6
6
 
7
7
  export function injectViewTypeSignal(getComp: () => object): Signal<SdViewType> {
8
- const activatedModal = inject(SdActivatedModalProvider, { optional: true });
8
+ const sdActivatedModal = inject(SdActivatedModalProvider, { optional: true });
9
9
  const activatedRoute = inject(ActivatedRoute, { optional: true });
10
10
 
11
11
  const fullPageCode = injectFullPageCodeSignal();
12
12
  const currPageCode = injectCurrentPageCodeSignal();
13
13
 
14
14
  return computed<SdViewType>(() => {
15
- if (activatedModal != null) {
15
+ if (sdActivatedModal != null) {
16
16
  return "modal";
17
17
  }
18
18
 
@@ -12,7 +12,7 @@ import { SdNavigateWindowProvider } from "./sd-navigate-window.provider";
12
12
  })
13
13
  export class SdRouterLink {
14
14
  private readonly _router = inject(Router);
15
- private readonly _navWindow = inject(SdNavigateWindowProvider);
15
+ private readonly _sdNavigateWindow = inject(SdNavigateWindowProvider);
16
16
 
17
17
  option = input<
18
18
  | {
@@ -39,13 +39,13 @@ export class SdRouterLink {
39
39
  event.preventDefault();
40
40
  event.stopPropagation();
41
41
 
42
- if (this._navWindow.isWindow) {
42
+ if (this._sdNavigateWindow.isWindow) {
43
43
  const width = option.window?.width ?? 800;
44
44
  const height = option.window?.height ?? 800;
45
45
  const qp = option.queryParams
46
46
  ? "?" + new URLSearchParams(option.queryParams).toString()
47
47
  : "";
48
- this._navWindow.open(
48
+ this._sdNavigateWindow.open(
49
49
  option.link + qp,
50
50
  option.params,
51
51
  `width=${width},height=${height}`,
@@ -54,7 +54,7 @@ export class SdRouterLink {
54
54
  const qp = option.queryParams
55
55
  ? "?" + new URLSearchParams(option.queryParams).toString()
56
56
  : "";
57
- this._navWindow.open(option.link + qp, option.params);
57
+ this._sdNavigateWindow.open(option.link + qp, option.params);
58
58
  } else if (option.outletName === undefined) {
59
59
  await this._router.navigate(
60
60
  [option.link, ...(option.params ? [option.params] : [])],
@@ -3,12 +3,12 @@ import { ActivatedRoute, type CanDeactivateFn } from "@angular/router";
3
3
  import { SdActivatedModalProvider } from "../modal/sd-activated-modal.provider";
4
4
 
5
5
  export function setupCanDeactivate(fn: () => boolean): void {
6
- const activatedModal = inject(SdActivatedModalProvider, { optional: true });
6
+ const sdActivatedModal = inject(SdActivatedModalProvider, { optional: true });
7
7
  const activatedRoute = inject(ActivatedRoute, { optional: true });
8
8
  const elRef = inject(ElementRef);
9
9
 
10
- if (activatedModal != null) {
11
- activatedModal.canDeactivateFn = fn;
10
+ if (sdActivatedModal != null) {
11
+ sdActivatedModal.canDeactivateFn = fn;
12
12
  return;
13
13
  }
14
14
 
@@ -6,8 +6,8 @@ import { SdToastProvider } from "../toast/sd-toast.provider";
6
6
 
7
7
  @Injectable({ providedIn: "root" })
8
8
  export class SdServiceClientFactoryProvider {
9
- private readonly _toastProvider = inject(SdToastProvider);
10
- private readonly _configProvider = inject(SdAngularConfigProvider);
9
+ private readonly _sdToast = inject(SdToastProvider);
10
+ private readonly _sdAngularConfig = inject(SdAngularConfigProvider);
11
11
  private readonly _destroyRef = inject(DestroyRef);
12
12
 
13
13
  private readonly _clientMap = new Map<string, ServiceClient>();
@@ -43,7 +43,7 @@ export class SdServiceClientFactoryProvider {
43
43
  ...options,
44
44
  };
45
45
 
46
- const client = createServiceClient(this._configProvider.clientName, mergedOptions);
46
+ const client = createServiceClient(this._sdAngularConfig.clientName, mergedOptions);
47
47
 
48
48
  client.on("request-progress", (state: ServiceProgressState) => {
49
49
  this._handleProgress("요청을 전송하는 중입니다.", state);
@@ -85,7 +85,7 @@ export class SdServiceClientFactoryProvider {
85
85
 
86
86
  let progressSignal = this._progressMap.get(uuid);
87
87
  if (progressSignal == null) {
88
- progressSignal = this._toastProvider.info(message, true);
88
+ progressSignal = this._sdToast.info(message, true);
89
89
  this._progressMap.set(uuid, progressSignal);
90
90
  }
91
91
 
@@ -40,8 +40,8 @@ interface SharedDataEntry<T extends SharedDataBase<string | number>> {
40
40
 
41
41
  @Injectable()
42
42
  export abstract class SdSharedDataProvider<T extends Record<string, SharedDataBase<string | number>>> {
43
- private readonly _clientFactory = inject(SdServiceClientFactoryProvider);
44
- private readonly _errorHandler = inject(ErrorHandler);
43
+ protected readonly _sdServiceClientFactory = inject(SdServiceClientFactoryProvider);
44
+ protected readonly _errorHandler = inject(ErrorHandler);
45
45
 
46
46
  readonly loadingCount: WritableSignal<number> = signal(0);
47
47
 
@@ -54,7 +54,7 @@ export abstract class SdSharedDataProvider<T extends Record<string, SharedDataBa
54
54
  if (existing != null) {
55
55
  // 기존 리스너 키 초기화 + generation 증가로 이전 이벤트 무시
56
56
  if (existing.listenerKey != null) {
57
- const client = this._clientFactory.get(existing.info.serviceKey);
57
+ const client = this._sdServiceClientFactory.get(existing.info.serviceKey);
58
58
  void client.removeListener(existing.listenerKey);
59
59
  existing.listenerKey = undefined;
60
60
  }
@@ -109,7 +109,7 @@ export abstract class SdSharedDataProvider<T extends Record<string, SharedDataBa
109
109
  throw new Error(`등록되지 않은 공유 데이터: ${name as string}`);
110
110
  }
111
111
 
112
- const client = this._clientFactory.get(entry.info.serviceKey);
112
+ const client = this._sdServiceClientFactory.get(entry.info.serviceKey);
113
113
  const event = client.getEvent<typeof SdSharedDataChangeEvent>("SdSharedDataChange");
114
114
  await event.emit(
115
115
  (item) => item.name === (name as string) && obj.equal(item.filter, entry.info.filter),
@@ -137,7 +137,7 @@ export abstract class SdSharedDataProvider<T extends Record<string, SharedDataBa
137
137
 
138
138
  // 이벤트 리스너 등록
139
139
  if (entry.listenerKey == null) {
140
- const client = this._clientFactory.get(entry.info.serviceKey);
140
+ const client = this._sdServiceClientFactory.get(entry.info.serviceKey);
141
141
  const event = client.getEvent<typeof SdSharedDataChangeEvent>("SdSharedDataChange");
142
142
  entry.listenerKey = await event.addListener(
143
143
  { name, filter: entry.info.filter },
@@ -18,18 +18,21 @@ import {
18
18
  styles: [
19
19
  /* language=SCSS */ `
20
20
  @use "../../../scss/commons/variables";
21
+ @use "../../../scss/commons/mixins";
21
22
  @use "sass:map";
22
23
 
23
24
  sd-toast-container {
25
+ display: flex;
24
26
  position: fixed;
25
27
  top: 0;
26
28
  left: 0;
27
- right: 0;
28
- display: flex;
29
- flex-direction: column;
30
- align-items: center;
31
- z-index: map.get(variables.$vars, z-index, toast);
29
+ height: 100%;
30
+ width: 100%;
32
31
  pointer-events: none;
32
+ padding: var(--gap-xxl);
33
+ z-index: map.get(variables.$vars, z-index, toast);
34
+
35
+ @include mixins.flex-direction(column);
33
36
 
34
37
  @media all and (max-width: variables.$breakpoint-mobile) {
35
38
  flex-direction: column-reverse;
@@ -37,8 +40,6 @@ import {
37
40
 
38
41
  &[data-sd-overlap] {
39
42
  display: block;
40
- bottom: 0;
41
- height: 100%;
42
43
 
43
44
  > sd-toast {
44
45
  position: absolute;
@@ -46,7 +47,6 @@ import {
46
47
  left: var(--gap-xxl);
47
48
  right: var(--gap-xxl);
48
49
  width: auto;
49
- max-width: none;
50
50
  }
51
51
  }
52
52
  }
@@ -36,7 +36,7 @@ export class SdToastProvider {
36
36
  private readonly _appRef = inject(ApplicationRef);
37
37
  private readonly _envInjector = inject(EnvironmentInjector);
38
38
  private readonly _injector = inject(Injector);
39
- private readonly _systemLog = inject(SdSystemLogProvider);
39
+ private readonly _sdSystemLog = inject(SdSystemLogProvider);
40
40
 
41
41
  alertThemes = signal<SdToastSeverity[]>([]);
42
42
  overlap = signal(false);
@@ -145,11 +145,11 @@ export class SdToastProvider {
145
145
  });
146
146
  }
147
147
 
148
- async try<R>(fn: () => Promise<R>): Promise<R | undefined>;
149
- async try<R>(fn: () => Promise<R>, messageFn: (err: unknown) => string): Promise<R | undefined>;
148
+ async try<R>(fn: () => Promise<R>, messageFn?: (err: Error) => string): Promise<R | undefined>;
149
+ try<R>(fn: () => R, messageFn?: (err: Error) => string): R | undefined;
150
150
  async try<R>(
151
- fn: () => Promise<R>,
152
- messageFn?: (err: unknown) => string,
151
+ fn: () => Promise<R> | R,
152
+ messageFn?: (err: Error) => string,
153
153
  ): Promise<R | undefined> {
154
154
  try {
155
155
  return await fn();
@@ -160,7 +160,7 @@ export class SdToastProvider {
160
160
 
161
161
  const message = messageFn !== undefined ? messageFn(err) : err.message;
162
162
  this.danger(message);
163
- await this._systemLog.writeAsync("error", err.stack ?? err.message);
163
+ await this._sdSystemLog.writeAsync("error", err.stack ?? err.message);
164
164
  return undefined;
165
165
  }
166
166
  }
@@ -18,21 +18,23 @@ const ASSERTIVE_THEMES: ReadonlySet<string> = new Set(["warning", "danger"]);
18
18
  encapsulation: ViewEncapsulation.None,
19
19
  standalone: true,
20
20
  host: {
21
- "[attr.data-sd-open]": "open() || undefined",
21
+ "[attr.data-sd-open]": "open()",
22
22
  "[attr.data-sd-theme]": "theme()",
23
23
  "[attr.role]": "ariaRole()",
24
24
  "[attr.aria-live]": "ariaLive()",
25
25
  },
26
26
  template: `
27
- @if (message() !== undefined) {
28
- <div class="_message">{{ message() }}</div>
29
- }
30
- <ng-content />
31
- @if (useProgress()) {
32
- <div class="_progress">
33
- <div class="_progress-bar" [style.width.%]="progress()"></div>
34
- </div>
35
- }
27
+ <div class="_block">
28
+ @if (message() !== undefined) {
29
+ <div class="_message">{{ message() }}</div>
30
+ }
31
+ <ng-content />
32
+ @if (useProgress()) {
33
+ <div class="_progress">
34
+ <div class="_progress-bar" [style.width.%]="progress()"></div>
35
+ </div>
36
+ }
37
+ </div>
36
38
  `,
37
39
  styles: [
38
40
  /* language=SCSS */ `
@@ -42,59 +44,90 @@ const ASSERTIVE_THEMES: ReadonlySet<string> = new Set(["warning", "danger"]);
42
44
 
43
45
  sd-toast {
44
46
  display: block;
45
- position: relative;
46
- max-width: 32.5rem;
47
+ margin-bottom: var(--gap-sm);
48
+ text-align: center;
47
49
  width: 100%;
48
- margin: var(--gap-sm);
49
- padding: var(--gap-default) var(--gap-lg);
50
- border-radius: var(--border-radius-default);
51
- color: var(--text-trans-rev-default);
52
- pointer-events: auto;
53
- transform: translateY(-100%);
54
- opacity: 0;
55
- transition:
56
- transform var(--animation-duration) ease,
57
- opacity var(--animation-duration) ease;
58
-
59
- @include mixins.elevation(12);
60
-
61
- &[data-sd-open] {
62
- transform: translateY(0);
63
- opacity: 1;
50
+ pointer-events: none;
51
+
52
+ > ._block {
53
+ display: inline-block;
54
+ text-align: left;
55
+ color: var(--text-trans-rev-default);
56
+ transform: translateY(-100%);
57
+ border-radius: var(--border-radius-lg);
58
+ opacity: 0;
59
+ @include mixins.elevation(12);
60
+ pointer-events: auto;
61
+
62
+ > ._message {
63
+ padding: var(--gap-default) var(--gap-lg);
64
+ word-break: break-all;
65
+ white-space: pre-wrap;
66
+ }
67
+
68
+ > ._progress {
69
+ background: var(--theme-gray-default);
70
+ height: 4px;
71
+ border-radius: var(--border-radius-xl);
72
+ margin: 0 4px 4px 4px;
73
+
74
+ > ._progress-bar {
75
+ border-radius: var(--border-radius-xl);
76
+ height: 4px;
77
+ transition: width 1s ease-out;
78
+ }
79
+ }
64
80
  }
65
81
 
66
82
  @each $theme-name, $theme-map in map.get(variables.$vars, theme) {
67
83
  &[data-sd-theme="#{$theme-name}"] {
68
- background: map.get($theme-map, default);
84
+ > ._block {
85
+ background: map.get($theme-map, default);
86
+
87
+ > ._progress {
88
+ background: map.get($theme-map, darker);
89
+
90
+ > ._progress-bar {
91
+ background: map.get($theme-map, lighter);
92
+ }
93
+ }
94
+ }
69
95
  }
70
96
  }
71
97
 
72
- > ._message {
73
- word-break: break-all;
74
- white-space: pre-wrap;
98
+ &[data-sd-open="true"] {
99
+ > ._block {
100
+ transform: none;
101
+ transition: var(--animation-duration) ease-out;
102
+ transition-property: transform, opacity;
103
+ opacity: 1;
104
+ }
75
105
  }
76
106
 
77
- > ._progress {
78
- position: absolute;
79
- bottom: 0;
80
- left: 0;
81
- right: 0;
82
- height: 0.25rem;
83
- background: var(--trans-lighter);
84
- border-radius: 0 0 var(--border-radius-default) var(--border-radius-default);
85
- overflow: hidden;
86
-
87
- > ._progress-bar {
88
- height: 100%;
89
- background: var(--trans-default);
90
- transition: width 0.3s ease;
107
+ &[data-sd-open="false"] {
108
+ > ._block {
109
+ transform: translateY(-100%);
110
+ transition: var(--animation-duration) ease-in;
111
+ transition-property: transform, opacity;
112
+ opacity: 0;
91
113
  }
92
114
  }
93
115
 
94
116
  @media all and (max-width: variables.$breakpoint-mobile) {
95
- max-width: none;
96
- margin: var(--gap-xs);
97
- border-radius: 0;
117
+ > ._block {
118
+ border-radius: calc(var(--line-height) / 2);
119
+ transform: translateY(100%);
120
+
121
+ > ._message {
122
+ padding: var(--gap-xs) var(--gap-default);
123
+ }
124
+ }
125
+
126
+ &[data-sd-open="false"] {
127
+ > ._block {
128
+ transform: translateY(100%);
129
+ }
130
+ }
98
131
  }
99
132
  }
100
133
  `,
@@ -1,7 +1,7 @@
1
1
  import { DestroyRef, effect, ElementRef, inject, Renderer2 } from "@angular/core";
2
2
  import { setSafeStyle } from "../setSafeStyle";
3
3
  import { Uuid } from "@simplysm/core-common";
4
- import { isFocusable } from "tabbable";
4
+ import { isTabbable } from "tabbable";
5
5
 
6
6
  export function setupInvalid(getInvalidMessage: () => string): void {
7
7
  const elRef = inject<ElementRef<HTMLElement>>(ElementRef);
@@ -65,6 +65,7 @@ function createInputHiddenEl(renderer: Renderer2, hostEl: HTMLElement): HTMLInpu
65
65
  newEl.type = "text";
66
66
  newEl.name = Uuid.generate().toString();
67
67
  newEl.className = "sd-invalid-input";
68
+ newEl.tabIndex = -1;
68
69
  setSafeStyle(renderer, newEl, {
69
70
  position: "absolute",
70
71
  left: "2px",
@@ -79,11 +80,11 @@ function createInputHiddenEl(renderer: Renderer2, hostEl: HTMLElement): HTMLInpu
79
80
  });
80
81
 
81
82
  renderer.listen(newEl, "focus", () => {
82
- const focusableElement =
83
- (isFocusable(hostEl) ? hostEl : hostEl.findFirstFocusableChild()) ??
84
- hostEl.findFocusableParent();
85
- if (focusableElement !== undefined) {
86
- focusableElement.focus();
83
+ const tabbableElement =
84
+ (isTabbable(hostEl) ? hostEl : hostEl.findFirstTabbableChild()) ??
85
+ hostEl.findTabbableParent();
86
+ if (tabbableElement !== undefined) {
87
+ tabbableElement.focus();
87
88
  }
88
89
  });
89
90
 
@@ -47,9 +47,9 @@ export abstract class SdDataDetailBase<T extends object, R = boolean>
47
47
 
48
48
  //-- implement
49
49
 
50
- private readonly _sdToast = inject(SdToastProvider);
51
- private readonly _sdSharedData = inject(SdSharedDataProvider);
52
- private readonly _errorHandler = inject(ErrorHandler);
50
+ protected readonly _sdToast = inject(SdToastProvider);
51
+ protected readonly _sdSharedData = inject(SdSharedDataProvider);
52
+ protected readonly _errorHandler = inject(ErrorHandler);
53
53
 
54
54
  viewType = injectViewTypeSignal(() => this);
55
55
 
@@ -32,7 +32,7 @@ export abstract class SdDataSelectButtonBase<
32
32
 
33
33
  //-- implement
34
34
 
35
- private readonly _sdModal = inject(SdModalProvider);
35
+ protected readonly _sdModal = inject(SdModalProvider);
36
36
 
37
37
  value = model<SelectModeValue<TKey>[TMode]>();
38
38
 
@@ -368,7 +368,7 @@ export class SdSheet<T> {
368
368
  columnControls = contentChildren(SdSheetColumn);
369
369
 
370
370
  // Injected providers
371
- private readonly _modalProvider = inject(SdModalProvider);
371
+ private readonly _sdModal = inject(SdModalProvider);
372
372
 
373
373
  // DOM accessor & cell agent
374
374
  domAccessor = injectSheetDomAccessor();
@@ -564,7 +564,7 @@ export class SdSheet<T> {
564
564
  }
565
565
 
566
566
  async onConfigButtonClick(): Promise<void> {
567
- const result = await this._modalProvider.showAsync(
567
+ const result = await this._sdModal.showAsync(
568
568
  {
569
569
  title: "시트 설정",
570
570
  type: SdSheetConfigModal,