@simplysm/angular 14.0.6 → 14.0.8

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 (105) hide show
  1. package/dist/core/directives/sd-router-link.directive.d.ts.map +1 -1
  2. package/dist/core/directives/sd-router-link.directive.js +5 -10
  3. package/dist/core/plugins/events/sd-resize-event.plugin.d.ts.map +1 -1
  4. package/dist/core/plugins/events/sd-resize-event.plugin.js +3 -0
  5. package/dist/core/plugins/sd-global-error-handler.plugin.d.ts +1 -0
  6. package/dist/core/plugins/sd-global-error-handler.plugin.d.ts.map +1 -1
  7. package/dist/core/plugins/sd-global-error-handler.plugin.js +4 -0
  8. package/dist/core/providers/sd-app-structure.provider.d.ts.map +1 -1
  9. package/dist/core/providers/sd-app-structure.provider.js +4 -1
  10. package/dist/core/providers/sd-navigate-window.provider.d.ts +2 -1
  11. package/dist/core/providers/sd-navigate-window.provider.d.ts.map +1 -1
  12. package/dist/core/providers/sd-navigate-window.provider.js +13 -10
  13. package/dist/core/utils/injectParent.d.ts +7 -0
  14. package/dist/core/utils/injectParent.d.ts.map +1 -0
  15. package/dist/core/utils/injectParent.js +26 -0
  16. package/dist/core/utils/setups/setupCanDeactivate.d.ts.map +1 -1
  17. package/dist/core/utils/setups/setupCanDeactivate.js +9 -2
  18. package/dist/core/utils/setups/setupInvalid.d.ts.map +1 -1
  19. package/dist/core/utils/setups/setupInvalid.js +4 -0
  20. package/dist/core/utils/setups/setupModelHook.d.ts +1 -1
  21. package/dist/core/utils/setups/setupModelHook.d.ts.map +1 -1
  22. package/dist/core/utils/setups/setupModelHook.js +5 -1
  23. package/dist/core/utils/setups/setupRipple.d.ts.map +1 -1
  24. package/dist/core/utils/setups/setupRipple.js +3 -2
  25. package/dist/core/utils/useExpandingManager.d.ts.map +1 -1
  26. package/dist/core/utils/useExpandingManager.js +2 -1
  27. package/dist/core/utils/useSdSystemConfigResource.d.ts.map +1 -1
  28. package/dist/core/utils/useSdSystemConfigResource.js +6 -3
  29. package/dist/features/base/sd-base-container.control.d.ts +26 -0
  30. package/dist/features/base/sd-base-container.control.d.ts.map +1 -0
  31. package/dist/features/base/sd-base-container.control.js +183 -0
  32. package/dist/index.d.ts +2 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +3 -0
  35. package/dist/ui/data/list/sd-list-item.control.js +1 -1
  36. package/dist/ui/data/sheet/sd-sheet.control.d.ts +2 -1
  37. package/dist/ui/data/sheet/sd-sheet.control.d.ts.map +1 -1
  38. package/dist/ui/data/sheet/sd-sheet.control.js +16 -13
  39. package/dist/ui/data/sheet/useSheetCellAgent.d.ts.map +1 -1
  40. package/dist/ui/data/sheet/useSheetCellAgent.js +1 -2
  41. package/dist/ui/data/sheet/useSheetLayoutEngine.d.ts.map +1 -1
  42. package/dist/ui/data/sheet/useSheetLayoutEngine.js +1 -0
  43. package/dist/ui/form/editor/sd-tiptap-editor.control.d.ts +5 -5
  44. package/dist/ui/form/editor/sd-tiptap-editor.control.d.ts.map +1 -1
  45. package/dist/ui/form/editor/sd-tiptap-editor.control.js +79 -78
  46. package/dist/ui/form/input/sd-date-range.picker.d.ts.map +1 -1
  47. package/dist/ui/form/input/sd-date-range.picker.js +4 -3
  48. package/dist/ui/form/input/sd-textfield-type-handlers.d.ts.map +1 -1
  49. package/dist/ui/form/input/sd-textfield-type-handlers.js +21 -2
  50. package/dist/ui/form/input/sd-textfield.control.d.ts.map +1 -1
  51. package/dist/ui/form/input/sd-textfield.control.js +1 -0
  52. package/dist/ui/layout/dock/sd-dock.control.d.ts +2 -0
  53. package/dist/ui/layout/dock/sd-dock.control.d.ts.map +1 -1
  54. package/dist/ui/layout/dock/sd-dock.control.js +13 -4
  55. package/dist/ui/layout/kanban/sd-kanban-lane.control.d.ts.map +1 -1
  56. package/dist/ui/layout/kanban/sd-kanban-lane.control.js +7 -1
  57. package/dist/ui/layout/sd-gap.control.d.ts +2 -2
  58. package/dist/ui/navigation/pagination/sd-pagination.control.d.ts.map +1 -1
  59. package/dist/ui/navigation/pagination/sd-pagination.control.js +7 -3
  60. package/dist/ui/navigation/sidebar/sd-sidebar-container.control.js +1 -1
  61. package/dist/ui/overlay/dropdown/sd-dropdown-popup.control.js +1 -1
  62. package/dist/ui/overlay/modal/sd-modal.control.d.ts +1 -0
  63. package/dist/ui/overlay/modal/sd-modal.control.d.ts.map +1 -1
  64. package/dist/ui/overlay/modal/sd-modal.control.js +11 -3
  65. package/dist/ui/overlay/modal/sd-modal.provider.d.ts +1 -1
  66. package/dist/ui/overlay/modal/sd-modal.provider.js +1 -1
  67. package/dist/ui/overlay/toast/sd-toast.provider.d.ts.map +1 -1
  68. package/dist/ui/overlay/toast/sd-toast.provider.js +7 -3
  69. package/dist/ui/visual/sd-progress.control.d.ts +1 -0
  70. package/dist/ui/visual/sd-progress.control.d.ts.map +1 -1
  71. package/dist/ui/visual/sd-progress.control.js +5 -4
  72. package/docs/providers.md +1 -1
  73. package/docs/setup-functions.md +1 -1
  74. package/package.json +7 -10
  75. package/src/core/directives/sd-router-link.directive.ts +5 -13
  76. package/src/core/plugins/events/sd-resize-event.plugin.ts +3 -0
  77. package/src/core/plugins/sd-global-error-handler.plugin.ts +4 -0
  78. package/src/core/providers/sd-app-structure.provider.ts +4 -1
  79. package/src/core/providers/sd-navigate-window.provider.ts +12 -13
  80. package/src/core/utils/injectParent.ts +41 -0
  81. package/src/core/utils/setups/setupCanDeactivate.ts +10 -2
  82. package/src/core/utils/setups/setupInvalid.ts +5 -0
  83. package/src/core/utils/setups/setupModelHook.ts +5 -2
  84. package/src/core/utils/setups/setupRipple.ts +3 -2
  85. package/src/core/utils/useExpandingManager.ts +2 -1
  86. package/src/core/utils/useSdSystemConfigResource.ts +6 -3
  87. package/src/features/base/sd-base-container.control.ts +113 -0
  88. package/src/index.ts +4 -0
  89. package/src/ui/data/list/sd-list-item.control.ts +1 -1
  90. package/src/ui/data/sheet/sd-sheet.control.ts +17 -13
  91. package/src/ui/data/sheet/useSheetCellAgent.ts +1 -2
  92. package/src/ui/data/sheet/useSheetLayoutEngine.ts +1 -0
  93. package/src/ui/form/editor/sd-tiptap-editor.control.ts +64 -61
  94. package/src/ui/form/input/sd-date-range.picker.ts +4 -3
  95. package/src/ui/form/input/sd-textfield-type-handlers.ts +25 -5
  96. package/src/ui/form/input/sd-textfield.control.ts +1 -0
  97. package/src/ui/layout/dock/sd-dock.control.ts +15 -2
  98. package/src/ui/layout/kanban/sd-kanban-lane.control.ts +6 -3
  99. package/src/ui/navigation/pagination/sd-pagination.control.ts +5 -3
  100. package/src/ui/navigation/sidebar/sd-sidebar-container.control.ts +1 -1
  101. package/src/ui/overlay/dropdown/sd-dropdown-popup.control.ts +1 -1
  102. package/src/ui/overlay/modal/sd-modal.control.ts +10 -1
  103. package/src/ui/overlay/modal/sd-modal.provider.ts +1 -1
  104. package/src/ui/overlay/toast/sd-toast.provider.ts +9 -3
  105. package/src/ui/visual/sd-progress.control.ts +4 -1
@@ -2,7 +2,8 @@ import { Injectable } from "@angular/core";
2
2
 
3
3
  @Injectable({ providedIn: "root" })
4
4
  export class SdNavigateWindowProvider {
5
- private _beforeUnloadController: AbortController | undefined;
5
+ private readonly _openedWindows = new Set<Window>();
6
+ private _beforeUnloadRegistered = false;
6
7
 
7
8
  get isWindow(): boolean {
8
9
  const urlSearchParams = new URLSearchParams(
@@ -19,21 +20,19 @@ export class SdNavigateWindowProvider {
19
20
  features,
20
21
  );
21
22
 
22
- // 기존 beforeunload 리스너 제거
23
- if (this._beforeUnloadController != null) {
24
- this._beforeUnloadController.abort();
23
+ if (newWindow) {
24
+ this._openedWindows.add(newWindow);
25
25
  }
26
- this._beforeUnloadController = new AbortController();
27
26
 
28
- window.addEventListener(
29
- "beforeunload",
30
- () => {
31
- if (newWindow) {
32
- newWindow.close();
27
+ if (!this._beforeUnloadRegistered) {
28
+ this._beforeUnloadRegistered = true;
29
+ window.addEventListener("beforeunload", () => {
30
+ for (const w of this._openedWindows) {
31
+ w.close();
33
32
  }
34
- },
35
- { signal: this._beforeUnloadController.signal },
36
- );
33
+ this._openedWindows.clear();
34
+ });
35
+ }
37
36
  } else {
38
37
  window.open(
39
38
  `${location.pathname}#${navigate};${new URLSearchParams(params).toString()}`,
@@ -0,0 +1,41 @@
1
+ import { type AbstractType, inject, Injector, ViewContainerRef } from "@angular/core";
2
+
3
+ export function injectParent<T = object>(): T;
4
+ export function injectParent<T = object>(type: AbstractType<T>): T;
5
+ export function injectParent<T = object>(
6
+ type: AbstractType<T>,
7
+ options: { optional: true },
8
+ ): T | undefined;
9
+ export function injectParent<T = object>(
10
+ type?: AbstractType<T>,
11
+ options?: { optional: true },
12
+ ): T | undefined {
13
+ let currentInjector: Injector | undefined = inject(ViewContainerRef).injector;
14
+ while (currentInjector != null) {
15
+ // Angular internal: NodeInjector._lView[CONTEXT=8]에 컴포넌트 인스턴스 저장
16
+ const lView: unknown[] | undefined = (
17
+ currentInjector as unknown as { _lView?: unknown[] }
18
+ )._lView;
19
+ if (lView == null) {
20
+ break;
21
+ }
22
+
23
+ const comp = lView[8] as object;
24
+ if (type == null) {
25
+ return comp as T;
26
+ }
27
+ if (comp instanceof type) {
28
+ return comp as T;
29
+ }
30
+
31
+ currentInjector = currentInjector.get(Injector, undefined, {
32
+ skipSelf: true,
33
+ optional: true,
34
+ }) as Injector | undefined;
35
+ }
36
+
37
+ if (options?.optional) {
38
+ return undefined;
39
+ }
40
+ throw new Error("부모 컴포넌트를 찾을 수 없습니다.");
41
+ }
@@ -1,4 +1,4 @@
1
- import { ElementRef, inject, reflectComponentType } from "@angular/core";
1
+ import { DestroyRef, ElementRef, inject, reflectComponentType } from "@angular/core";
2
2
  import { ActivatedRoute, type CanDeactivateFn } from "@angular/router";
3
3
  import { SdActivatedModalProvider } from "../../../ui/overlay/modal/sd-modal.provider";
4
4
 
@@ -8,7 +8,7 @@ export function setupCanDeactivate(fn: () => boolean): void {
8
8
  const elRef = inject(ElementRef);
9
9
 
10
10
  if (activatedModal != null) {
11
- activatedModal.canDeactivefn = fn;
11
+ activatedModal.canDeactiveFn = fn;
12
12
  return;
13
13
  }
14
14
 
@@ -30,5 +30,13 @@ export function setupCanDeactivate(fn: () => boolean): void {
30
30
  activatedRoute.routeConfig.canDeactivate = [];
31
31
  }
32
32
  activatedRoute.routeConfig.canDeactivate.push(canDeactivateFn);
33
+
34
+ const destroyRef = inject(DestroyRef);
35
+ destroyRef.onDestroy(() => {
36
+ const idx = activatedRoute.routeConfig!.canDeactivate!.indexOf(canDeactivateFn);
37
+ if (idx !== -1) {
38
+ activatedRoute.routeConfig!.canDeactivate!.splice(idx, 1);
39
+ }
40
+ });
33
41
  }
34
42
  }
@@ -26,6 +26,11 @@ export function setupInvalid(getInvalidMessage: () => string): void {
26
26
  setSafeStyle(renderer, indicatorEl, { display: isInvalid ? "block" : "none" });
27
27
  });
28
28
 
29
+ destroyRef.onDestroy(() => {
30
+ indicatorEl.remove();
31
+ inputEl.remove();
32
+ });
33
+
29
34
  const formEl = inputEl.form;
30
35
  if (formEl == null) return;
31
36
 
@@ -1,9 +1,10 @@
1
- import type { Signal, WritableSignal } from "@angular/core";
1
+ import { ErrorHandler, inject, type Signal, type WritableSignal } from "@angular/core";
2
2
 
3
3
  export function setupModelHook<T, S extends WritableSignal<T>>(
4
4
  model: S,
5
5
  canFn: Signal<(item: T) => boolean | Promise<boolean>>,
6
6
  ): void {
7
+ const errorHandler = inject(ErrorHandler);
7
8
  const orgSet = model.set;
8
9
  model.set = (value) => {
9
10
  const canSet = canFn()(value);
@@ -21,6 +22,8 @@ export function setupModelHook<T, S extends WritableSignal<T>>(
21
22
  if (allowed !== false) {
22
23
  orgSet(value);
23
24
  }
24
- }).catch(() => {});
25
+ }).catch((err) => {
26
+ errorHandler.handleError(err);
27
+ });
25
28
  };
26
29
  }
@@ -44,9 +44,10 @@ export function setupRipple(enableFn?: () => boolean): void {
44
44
  });
45
45
  el.appendChild(indicatorEl);
46
46
 
47
- indicatorEl.ontransitionend = () => {
48
- if (indicatorEl !== undefined && getComputedStyle(indicatorEl).opacity === "0") {
47
+ indicatorEl.ontransitionend = (ev: TransitionEvent) => {
48
+ if (ev.propertyName === "opacity" && indicatorEl !== undefined) {
49
49
  indicatorEl.remove();
50
+ indicatorEl = undefined;
50
51
  }
51
52
  };
52
53
 
@@ -86,9 +86,10 @@ export function useExpandingManager<T>(binding: {
86
86
  const itemDef = map.get(item);
87
87
  if (itemDef == null) return false;
88
88
 
89
+ const expandedSet = new Set(binding.expandedItems());
89
90
  let current = itemDef.parentDef;
90
91
  while (current != null) {
91
- if (!binding.expandedItems().includes(current.item)) {
92
+ if (!expandedSet.has(current.item)) {
92
93
  return false;
93
94
  }
94
95
  current = current.parentDef;
@@ -1,9 +1,10 @@
1
- import { ElementRef, inject, type ResourceRef, resource, type Signal } from "@angular/core";
1
+ import { ElementRef, ErrorHandler, inject, type ResourceRef, resource, type Signal } from "@angular/core";
2
2
  import { SdSystemConfigProvider } from "../providers/sd-system-config.provider";
3
3
 
4
4
  export function useSdSystemConfigResource<T>(options: { key: Signal<string | undefined> }) {
5
5
  const sdSystemConfig = inject<SdSystemConfigProvider<Record<string, T>>>(SdSystemConfigProvider);
6
6
  const elRef = inject(ElementRef);
7
+ const errorHandler = inject(ErrorHandler);
7
8
 
8
9
  const elTag = elRef.nativeElement.tagName.toLowerCase();
9
10
 
@@ -24,8 +25,10 @@ export function useSdSystemConfigResource<T>(options: { key: Signal<string | und
24
25
  res.set(value);
25
26
  const key = options.key();
26
27
  if (key == null) return;
27
- queueMicrotask(async () => {
28
- await sdSystemConfig.setAsync(`${elTag}.${key}`, value as T);
28
+ queueMicrotask(() => {
29
+ sdSystemConfig.setAsync(`${elTag}.${key}`, value as T).catch((err) => {
30
+ errorHandler.handleError(err);
31
+ });
29
32
  });
30
33
  },
31
34
  update(fn: (prev: T | undefined) => T | undefined) {
@@ -0,0 +1,113 @@
1
+ import { NgTemplateOutlet } from "@angular/common";
2
+ import {
3
+ booleanAttribute,
4
+ ChangeDetectionStrategy,
5
+ Component,
6
+ computed,
7
+ contentChild,
8
+ inject,
9
+ input,
10
+ TemplateRef,
11
+ ViewEncapsulation,
12
+ } from "@angular/core";
13
+ import { SdBusyContainerControl } from "../../ui/overlay/busy/sd-busy-container.control";
14
+ import { SdTopbarContainerControl } from "../../ui/navigation/topbar/sd-topbar-container.control";
15
+ import { SdTopbarControl } from "../../ui/navigation/topbar/sd-topbar.control";
16
+ import { SdAppStructureProvider } from "../../core/providers/sd-app-structure.provider";
17
+ import { SdActivatedModalProvider } from "../../ui/overlay/modal/sd-modal.provider";
18
+ import { useCurrentPageCodeSignal } from "../../core/utils/useCurrentPageCodeSignal";
19
+ import { useFullPageCodeSignal } from "../../core/utils/useFullPageCodeSignal";
20
+ import {
21
+ useViewTypeSignal,
22
+ type TSdViewType,
23
+ } from "../../core/utils/useViewTypeSignal";
24
+ import { injectParent } from "../../core/utils/injectParent";
25
+ import { NgIcon } from "@ng-icons/core";
26
+ import { tablerAlertTriangle } from "@ng-icons/tabler-icons";
27
+
28
+ @Component({
29
+ selector: "sd-base-container",
30
+ changeDetection: ChangeDetectionStrategy.OnPush,
31
+ encapsulation: ViewEncapsulation.None,
32
+ standalone: true,
33
+ imports: [
34
+ SdBusyContainerControl,
35
+ SdTopbarContainerControl,
36
+ SdTopbarControl,
37
+ NgTemplateOutlet,
38
+ NgIcon,
39
+ ],
40
+ template: `
41
+ <sd-busy-container [busy]="busy()" [message]="busyMessage()">
42
+ @if (initialized() === undefined || initialized()) {
43
+ @if (restricted()) {
44
+ <div class="fill tx-theme-gray-light p-xxl tx-center">
45
+ <br />
46
+ <ng-icon [svg]="tablerAlertTriangle" [size]="'5em'" />
47
+ <br />
48
+ <br />
49
+ '{{ modalOrPageTitle() }}'에 대한 사용권한이 없습니다. 시스템 관리자에게 문의하세요.
50
+ </div>
51
+ } @else if (currViewType() === "page") {
52
+ <sd-topbar-container>
53
+ <sd-topbar>
54
+ <h4>{{ modalOrPageTitle() }}</h4>
55
+
56
+ <ng-template [ngTemplateOutlet]="pageTopbarTplRef() ?? null" />
57
+ </sd-topbar>
58
+
59
+ <div class="fill">
60
+ <ng-template [ngTemplateOutlet]="contentTplRef()" />
61
+ </div>
62
+ </sd-topbar-container>
63
+ } @else if (currViewType() === "modal") {
64
+ <div class="flex-column fill">
65
+ <div class="flex-fill">
66
+ <ng-template [ngTemplateOutlet]="contentTplRef()" />
67
+ </div>
68
+ @if (modalBottomTplRef()) {
69
+ <div class="bdt bdt-theme-gray-lightest">
70
+ <ng-template [ngTemplateOutlet]="modalBottomTplRef() ?? null" />
71
+ </div>
72
+ }
73
+ </div>
74
+ } @else {
75
+ <ng-template [ngTemplateOutlet]="contentTplRef()" />
76
+ }
77
+ }
78
+ </sd-busy-container>
79
+ `,
80
+ })
81
+ export class SdBaseContainerControl {
82
+ private readonly _sdActivatedModal = inject(SdActivatedModalProvider, { optional: true });
83
+ private readonly _sdAppStructure = inject(SdAppStructureProvider);
84
+
85
+ private readonly _parent = injectParent();
86
+
87
+ private readonly _fullPageCode = useFullPageCodeSignal();
88
+ private readonly _currPageCode = useCurrentPageCodeSignal();
89
+
90
+ contentTplRef = contentChild.required("contentTpl", { read: TemplateRef });
91
+
92
+ pageTopbarTplRef = contentChild("pageTopbarTpl", { read: TemplateRef });
93
+ modalBottomTplRef = contentChild("modalBottomTpl", { read: TemplateRef });
94
+
95
+ private readonly _parentViewType = useViewTypeSignal(() => this._parent);
96
+ viewType = input<TSdViewType>();
97
+ currViewType = computed(() => this.viewType() ?? this._parentViewType());
98
+
99
+ header = input<string>();
100
+ modalOrPageTitle = computed(
101
+ () =>
102
+ this.header() ??
103
+ this._sdActivatedModal?.modalComponent()?.title() ??
104
+ this._sdAppStructure.getTitleByFullCode(this._currPageCode?.() ?? this._fullPageCode()),
105
+ );
106
+
107
+ initialized = input<boolean | undefined>(undefined);
108
+ restricted = input(false, { transform: booleanAttribute });
109
+ busy = input(false, { transform: booleanAttribute });
110
+ busyMessage = input<string>();
111
+
112
+ protected readonly tablerAlertTriangle = tablerAlertTriangle;
113
+ }
package/src/index.ts CHANGED
@@ -62,6 +62,10 @@ export {
62
62
  type IExpandItemDef,
63
63
  } from "./core/utils/useExpandingManager";
64
64
  export { useSelectionManager } from "./core/utils/useSelectionManager";
65
+ export { injectParent } from "./core/utils/injectParent";
66
+
67
+ // features
68
+ export { SdBaseContainerControl } from "./features/base/sd-base-container.control";
65
69
 
66
70
  // ui/layout
67
71
  export { SdDockContainerControl } from "./ui/layout/dock/sd-dock-container.control";
@@ -131,7 +131,7 @@ export class SdListItemControl {
131
131
  });
132
132
 
133
133
  constructor() {
134
- setupRipple(() => !this.readonly() && !(this.layout() === "flat" && this.hasChildren()));
134
+ setupRipple(() => !this.readonly() && this.layout() !== "flat");
135
135
  }
136
136
 
137
137
  onContentClick(): void {
@@ -384,7 +384,7 @@ export class SdSheetControl<T> {
384
384
  // Resizing state
385
385
  _isResizing = signal(false);
386
386
  _resizeIndicatorLeft = signal(0);
387
- private _isOnResizing = false;
387
+ private _lastResizeEndTimeStamp = 0;
388
388
  private _resizingCleanup: (() => void) | null = null;
389
389
 
390
390
  // Config resource
@@ -478,13 +478,21 @@ export class SdSheetControl<T> {
478
478
  getItemSelectableFn: this.getItemSelectableFn,
479
479
  });
480
480
 
481
+ private readonly _columnControlMap = computed(() => {
482
+ const map = new Map<string, SdSheetColumnDirective>();
483
+ for (const col of this.columnControls()) {
484
+ map.set(col.key(), col);
485
+ }
486
+ return map;
487
+ });
488
+
481
489
  getColumnCellTpl(key: string) {
482
- const col = this.columnControls().find((c) => c.key() === key);
490
+ const col = this._columnControlMap().get(key);
483
491
  return col?.cellTplRef() ?? null;
484
492
  }
485
493
 
486
494
  getColumnSummaryTpl(key: string) {
487
- const col = this.columnControls().find((c) => c.key() === key);
495
+ const col = this._columnControlMap().get(key);
488
496
  return col?.summaryTplRef() ?? null;
489
497
  }
490
498
 
@@ -513,9 +521,8 @@ export class SdSheetControl<T> {
513
521
  if (fixedStyle != null) {
514
522
  parts.push(fixedStyle);
515
523
  }
516
- const customStyle = this.getItemCellStyleFn()
517
- ? this.getItemCellStyleFn()!(item, colDef.key)
518
- : undefined;
524
+ const styleFn = this.getItemCellStyleFn();
525
+ const customStyle = styleFn != null ? styleFn(item, colDef.key) : undefined;
519
526
  if (customStyle != null) {
520
527
  parts.push(customStyle);
521
528
  }
@@ -556,7 +563,7 @@ export class SdSheetControl<T> {
556
563
  }
557
564
 
558
565
  onHeaderClick(event: MouseEvent, cell: ISdSheetHeaderDef): void {
559
- if (this._isOnResizing) return;
566
+ if (event.timeStamp - this._lastResizeEndTimeStamp < 50) return;
560
567
  if (cell.colDef == null) return;
561
568
  if (cell.colDef.disableSorting) return;
562
569
  this.sorting.toggle(cell.colDef.key, event.shiftKey);
@@ -642,7 +649,8 @@ export class SdSheetControl<T> {
642
649
 
643
650
  getDataCellClass(item: T, colDef: ISdSheetColumnDef, r: number, c: number): string | null {
644
651
  const parts: string[] = [];
645
- const customClass = this.getItemCellClassFn() ? this.getItemCellClassFn()!(item, colDef.key) : undefined;
652
+ const classFn = this.getItemCellClassFn();
653
+ const customClass = classFn != null ? classFn(item, colDef.key) : undefined;
646
654
  if (customClass != null) {
647
655
  parts.push(customClass);
648
656
  }
@@ -700,7 +708,6 @@ export class SdSheetControl<T> {
700
708
  const startWidth = th.offsetWidth;
701
709
 
702
710
  this._isResizing.set(true);
703
- this._isOnResizing = true;
704
711
  this._resizeIndicatorLeft.set(
705
712
  th.offsetLeft + th.offsetWidth - container.scrollLeft,
706
713
  );
@@ -724,10 +731,7 @@ export class SdSheetControl<T> {
724
731
  this._isResizing.set(false);
725
732
  this._saveColumnConfig(colDef.key, { width: `${newWidth}px` });
726
733
 
727
- // Prevent sorting for 300ms after resize
728
- setTimeout(() => {
729
- this._isOnResizing = false;
730
- }, 300);
734
+ this._lastResizeEndTimeStamp = e.timeStamp;
731
735
  };
732
736
 
733
737
  document.addEventListener("mousemove", onMouseMove);
@@ -192,8 +192,7 @@ export function useSheetCellAgent(options: {
192
192
  }
193
193
 
194
194
  event.preventDefault();
195
- const text = td.textContent;
196
- await navigator.clipboard.writeText(text);
195
+ await navigator.clipboard.writeText(td.textContent);
197
196
  return;
198
197
  }
199
198
 
@@ -112,6 +112,7 @@ export function useSheetLayoutEngine(options: {
112
112
 
113
113
  if (canMerge && lastCell != null) {
114
114
  lastCell.colspan += 1;
115
+ spanStartHeaders[row] = headers;
115
116
  } else {
116
117
  table[row].push({
117
118
  text,