@libs-ui/services-grid-layout 0.2.357-19 → 0.2.357-21

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/README.md CHANGED
@@ -1,9 +1,10 @@
1
1
  # @libs-ui/services-grid-layout
2
2
 
3
- Mặt phẳng kéo–thả. Thư viện lo toàn bộ việc **vẽ lưới, kéo thả, đổi kích thước, lồng container**;
4
- nơi dùng chỉ đưa **component nội dung** **cây dữ liệu**, thư viện tự gắn component lên từng khối.
3
+ Mặt phẳng kéo–thả. Thư viện lo **toàn bộ** việc vẽ lưới, kéo thả, đổi kích thước, lồng container,
4
+ tay cầm kéo, bộ nút trên khối, viền chế độ sửa, dọn dẹp khi component chết.
5
5
 
6
- Cùng hình với `@libs-ui/services-diagram-draw`: truyền `nodeComponentType`, thư viện `createComponent`.
6
+ Nơi dùng chỉ đưa **cây dữ liệu** **cách phân giải component nội dung**. Component nội dung của bạn
7
+ chỉ vẽ phần nội dung — không phải tự lo kích thước, không phải tự vẽ viền chế độ sửa.
7
8
 
8
9
  ## Cài
9
10
 
@@ -18,70 +19,162 @@ Lib dùng `angular-gridster2` qua **alias npm** để chạy song song nhiều b
18
19
  Nơi dùng đang có `angular-gridster2` bản khác thì **không bị đụng** — hai bản nằm ở hai thư mục
19
20
  `node_modules` riêng.
20
21
 
22
+ Chạy Jest thì phải map **cả hai** tên, vì package tự khai `name: angular-gridster2`:
23
+
24
+ ```ts
25
+ // jest.config.ts của nơi dùng
26
+ moduleNameMapper: {
27
+ '^gridster18$': '<rootDir>/../../node_modules/gridster18/fesm2022/angular-gridster2.mjs',
28
+ '^angular-gridster2$': '<rootDir>/../../node_modules/gridster18/fesm2022/angular-gridster2.mjs',
29
+ }
30
+ ```
31
+
21
32
  ## Dùng
22
33
 
23
34
  ```ts
24
35
  @Component({
25
36
  imports: [LibsUiGridLayoutCanvasComponent],
26
37
  // 🔴 Khai trong providers của CHÍNH component — service theo từng mặt phẳng, không phải root.
27
- providers: [LibsUiGridLayoutService, LibsUiGridLayoutTreeService],
28
- template: `<libs_ui-services-grid_layout-canvas [node]="root()" (outChange)="save($event)" />`,
38
+ providers: [LibsUiGridLayoutService],
39
+ template: `<libs_ui-services-grid_layout-canvas
40
+ (outChange)="handlerChange($event)"
41
+ (outRemoveNode)="handlerRemoveNode($event)"
42
+ (outAddBlockInside)="handlerAddInside($event)"
43
+ (outToggleType)="handlerToggleType($event)" />`,
29
44
  })
30
- export class MyPageComponent implements OnDestroy {
45
+ export class MyPageComponent {
31
46
  private readonly gridLayout = inject(LibsUiGridLayoutService);
32
47
 
33
- protected readonly root = signal<T_gridLayout_node>(initialLayout());
34
-
35
48
  constructor() {
36
49
  this.gridLayout.init({
37
- nodeComponentType: MyCardComponent, // component nội dung của BẠN
38
- data: this.root(),
50
+ data: layoutFromServer(), // cây khối, thường lấy từ backend
51
+ getComponentOutlet: resolveNodeComponent,
39
52
  mode: E_gridLayout_mode_string.grid,
40
53
  isEditMode: true,
41
- allowOverlap: false,
42
54
  });
43
55
  }
56
+ // KHÔNG cần ngOnDestroy — thư viện tự dọn qua DestroyRef.
57
+ }
58
+ ```
59
+
60
+ **Không truyền `[node]`** — `init()` đã giữ cây. Input `node`/`depth` của canvas chỉ dành cho lưới
61
+ lồng bên trong thư viện.
44
62
 
45
- ngOnDestroy(): void {
46
- this.gridLayout.destroy(); // 🔴 BẮT BUỘC, nếu không là rò rỉ bộ nhớ
63
+ ### Phân giải component theo từng khối
64
+
65
+ Mỗi khối tự quyết định component của nó, đọc từ `node.data` (dữ liệu hình dạng server). Trả
66
+ `Observable` nên `import()` động dùng được ngay — mỗi loại khối là một chunk riêng:
67
+
68
+ ```ts
69
+ export const resolveNodeComponent = (node: T_gridLayout_node): Observable<Type<unknown>> => {
70
+ switch ((node.data as T_serverElement | undefined)?.type) {
71
+ case E_serverElement_type_string.chart:
72
+ return from(import('./chart-card.component').then((c) => c.ChartCardComponent));
73
+ case E_serverElement_type_string.table:
74
+ return from(import('./table-card.component').then((c) => c.TableCardComponent));
75
+ default:
76
+ return from(import('./sample-card.component').then((c) => c.SampleCardComponent));
47
77
  }
48
- }
78
+ };
49
79
  ```
50
80
 
51
- Component nội dung **bắt buộc** khai đủ ba input — thư viện `setInput` đúng ba tên này:
81
+ Khai `config.getComponentOutlet` cho cả mặt phẳng, hoặc `node.getComponentOutlet` cho riêng một
82
+ khối (khối tự khai thì thắng).
83
+
84
+ ### Component nội dung
85
+
86
+ Khai đủ **bốn** input — thư viện `setInput` đúng bốn tên này, thiếu một cái là lỗi lúc chạy mà build
87
+ không bắt được:
52
88
 
53
89
  ```ts
54
90
  @Component({ changeDetection: ChangeDetectionStrategy.OnPush, /* … */ })
55
91
  export class MyCardComponent {
92
+ readonly data = input.required<T_myData>(); // = node.data — thường chỉ cần cái này
56
93
  readonly node = input.required<T_gridLayout_node>();
57
94
  readonly isEditMode = input<boolean>(false);
58
95
  readonly depth = input<number>(0);
59
96
  }
60
97
  ```
61
98
 
99
+ Type sẵn có: `T_gridLayout_nodeInputs<TData>`.
100
+
101
+ ## Nút sẵn có bắn event kèm callback
102
+
103
+ Xoá · gộp · tách · thêm-khối-bên-trong là nút của thư viện. Bấm vào, thư viện **chưa làm gì** — nó
104
+ bắn event mang theo `confirm()`. Nghiệp vụ cần hỏi xác nhận thì mở modal rồi mới gọi; không cần thì
105
+ gọi luôn; không cho phép thì đừng gọi:
106
+
107
+ ```ts
108
+ protected handlerRemoveNode(event: T_gridLayout_actionEvent): void {
109
+ if (!event.node.children?.length) {
110
+ event.confirm(); // khối đơn — xoá luôn
111
+ return;
112
+ }
113
+ this.pendingRemove.set(event); // khối ghép — hỏi trước, confirm() gọi sau
114
+ }
115
+
116
+ protected handlerAddInside(event: T_gridLayout_addEvent): void {
117
+ // confirm nhận khối mới, trả về khối đã tạo kèm id thư viện sinh
118
+ const added = event.confirm({ cols: 3, rows: 2, data: { type: 'metric' } });
119
+ }
120
+ ```
121
+
122
+ | Output | Khi nào bắn | `confirm()` |
123
+ |---|---|---|
124
+ | `outChange` | người dùng kéo / đổi kích thước — **chỉ** thao tác thật của người dùng | — |
125
+ | `outRemoveNode` | bấm nút xoá | `() => boolean` |
126
+ | `outToggleType` | bấm gộp (khối đơn → container) hoặc tách | `() => boolean` |
127
+ | `outAddBlockInside` | bấm + trên container | `(item) => node \| undefined` |
128
+
129
+ `outChange` **không** bắn khi chính bạn gọi `addBlock`/`removeBlock`/`compact` — bạn đã biết mình vừa
130
+ làm gì, khỏi phải chống dội.
131
+
62
132
  ## Hai chế độ
63
133
 
64
134
  | Chế độ | Khối bám lưới | Chồng lên nhau | Dùng khi |
65
135
  |---|---|---|---|
66
- | `luoi` | có | chỉ khi `allowOverlap` bật **và** hai khối khác `layerIndex` | dashboard, bố cục dạng bảng |
67
- | `tuDo` | không, toạ độ px | tự do | dựng bố cục kiểu canvas |
136
+ | `grid` | có | chỉ khi `allowOverlap` bật **và** hai khối khác `layerIndex` | dashboard, bố cục dạng bảng |
137
+ | `free` | không, toạ độ px | tự do | bố cục kiểu canvas — *engine chưa dựng, hiện vẫn chạy trên lưới* |
68
138
 
69
139
  ## API
70
140
 
71
- **`LibsUiGridLayoutService`** (một thực thể cho mỗi mặt phẳng)
141
+ **`LibsUiGridLayoutService`** một thực thể cho mỗi mặt phẳng.
72
142
 
73
143
  | Thành viên | Việc |
74
144
  |---|---|
75
- | `init(config)` | dựng mặt phẳng; gọi lại sẽ dọn sạch rồi dựng lại |
76
- | `setEditMode(bat)` | bật/tắt chế độ sửa |
77
- | `refresh(event?)` | báo cây vừa đổi từ bên ngoài |
78
- | `destroy()` | dọn mọi component đã dựnggọi trong `ngOnDestroy` |
145
+ | `init(config)` | dựng mặt phẳng; gọi lại sẽ dọn sạch rồi dựng lại. Tự san phẳng container lồng quá trần |
146
+ | `setEditMode(value)` | bật/tắt chế độ sửa |
147
+ | `setAllowOverlap(value)` | cho phép xếp chồng; tự gán `layerIndex`. KHÔNG dựng lại component nào |
148
+ | `refresh(event?)` | báo cây vừa đổi từ bên ngoài hiếm khi cần |
149
+ | `addBlock(item, parentId?)` | thêm khối; bỏ `parentId` là thêm ở gốc. Trả khối vừa tạo (kèm `id`) |
150
+ | `removeBlock(id)` | bỏ khối; xoá container là xoá cả ruột |
151
+ | `toContainer(id)` · `toSingle(id)` | gộp thành container / tách về khối đơn — không mất dữ liệu |
152
+ | `compact()` | dồn khối lên, bỏ khoảng trống. Đệ quy vào container |
153
+ | `findBlock(id)` · `findParentBlock(id)` | tra cây |
154
+ | `exportLayout()` | cây hiện tại đã bỏ hàm, `cloneDeep` — gửi thẳng lên backend được |
79
155
  | `Root` · `IsEditMode` · `Version` | signal chỉ đọc |
80
156
 
81
- **`LibsUiGridLayoutTreeService`**thao tác trên cây
157
+ Mọi hàm sửa cây **tự vẽ lại** không phải gọi `refresh()` theo sau.
158
+
159
+ **Thư viện tự dọn** qua `DestroyRef` — `destroy()` là `private`, **CẤM** gọi trong `ngOnDestroy`.
160
+
161
+ **KHÔNG tự đặt `id`** cho khối: `addBlock` sinh bằng `uuid()` và trả về khối vừa tạo. Tự đặt là mời
162
+ trùng id, mà trùng thì `findBlock` trả nhầm khối và cây hỏng im lặng.
163
+
164
+ `LibsUiGridLayoutTreeService` **cố ý không xuất**: mọi thao tác trên cây đã có trên service với chữ ký
165
+ gọn hơn (không phải truyền `root` — thư viện đang giữ nó). Cấu hình gridster cũng không xuất, xem lý
166
+ do ở mục dưới.
167
+
168
+ ### Lưu và mở lại bố cục
169
+
170
+ ```ts
171
+ UtilsCache.Set(KEY, this.gridLayout.exportLayout(), UtilsCache.CACHE_EXPIRE_NONE);
172
+ // mở lại
173
+ this.gridLayout.init({ data: UtilsCache.Get(KEY) ?? defaultLayout(), getComponentOutlet });
174
+ ```
82
175
 
83
- `findById` · `findParent` · `addChild` · `removeById` · `canBecomeContainer` ·
84
- `flattenOverDepthContainers` · `compact`
176
+ `exportLayout()` đã lược `getComponentOutlet` (hàm không tuần tự hoá được) nên `JSON.stringify` được
177
+ ngay. Mở lại chỉ cần đưa `getComponentOutlet` vào `init` — cây không mang hàm.
85
178
 
86
179
  ## Vì sao cấu hình gridster lại như vậy
87
180
 
@@ -99,6 +192,7 @@ export class MyCardComponent {
99
192
  | tay cầm resize ở `0`, không âm | `overflow: hidden` cắt mất nửa tay cầm → kéo mép không ăn |
100
193
  | `setGridSize: false` ở lưới lồng | vòng lặp đo–ghi, lưới con co dần về 64×64px |
101
194
  | `disableScroll*` ở lưới lồng | khối con bị đẩy ra ngoài container rồi kẹt |
195
+ | `layerIndex` gán vào cây ĐANG VẼ | gán vào `config.data` thì z-index đứng ở 1 — cây đang vẽ là bản clone |
102
196
 
103
197
  ## Kiểm
104
198
 
@@ -1,5 +1,5 @@
1
- import { OnDestroy } from '@angular/core';
2
- import { T_gridLayout_actionEvent, T_gridLayout_addEvent, T_gridLayout_changeEvent, T_gridLayout_node } from '../interfaces/grid-layout.interface';
1
+ import { AfterViewInit, OnDestroy, TemplateRef } from '@angular/core';
2
+ import { T_gridLayout_actionEvent, T_gridLayout_addEvent, T_gridLayout_changeEvent, T_gridLayout_dropEvent, T_gridLayout_dropGhost, T_gridLayout_node, T_gridLayout_toolbarContext, T_gridLayout_zoomPreset } from '../interfaces/grid-layout.interface';
3
3
  import * as i0 from "@angular/core";
4
4
  /**
5
5
  * Mặt phẳng kéo thả. Đệ quy: khối có `children` thì ruột nó lại là một mặt phẳng nữa.
@@ -7,7 +7,7 @@ import * as i0 from "@angular/core";
7
7
  * Nơi dùng KHÔNG dựng component này trực tiếp cho từng khối — chỉ đặt MỘT thẻ ở gốc và truyền
8
8
  * `nodeComponentType` qua `LibsUiGridLayoutService.init()`. Thư viện tự gắn component cho mọi khối.
9
9
  */
10
- export declare class LibsUiGridLayoutCanvasComponent implements OnDestroy {
10
+ export declare class LibsUiGridLayoutCanvasComponent implements AfterViewInit, OnDestroy {
11
11
  /**
12
12
  * @internal Chỉ mặt phẳng LỒNG dùng — thư viện tự truyền khi vẽ ruột container.
13
13
  *
@@ -17,6 +17,23 @@ export declare class LibsUiGridLayoutCanvasComponent implements OnDestroy {
17
17
  readonly node: import("@angular/core").InputSignal<T_gridLayout_node | undefined>;
18
18
  /** @internal Chỉ mặt phẳng LỒNG dùng — thư viện tự truyền. */
19
19
  readonly depth: import("@angular/core").InputSignal<number>;
20
+ /**
21
+ * Ẩn hẳn thanh công cụ mặc định (thêm-vào-trong · gộp/tách · xoá) của MỌI khối.
22
+ *
23
+ * Dùng khi sản phẩm gom các hành động đó vào chỗ khác — vd một nút ⋮ nằm trong chính nội dung
24
+ * khối. Ẩn rồi thì `outRemoveNode` · `outAddBlockInside` · `outToggleType` không còn nguồn phát
25
+ * từ thư viện; nơi dùng tự gọi `removeBlock` · `addBlock` · `toggleType` của service.
26
+ */
27
+ readonly hiddenToolbar: import("@angular/core").InputSignalWithTransform<boolean, boolean | undefined>;
28
+ /**
29
+ * Template tự vẽ thanh công cụ, thay cho bộ nút mặc định.
30
+ *
31
+ * Nhận context `{ $implicit: node, isContainer, canAddInside, canToggleType }` để nơi dùng vẽ đúng
32
+ * nút cho từng khối, rồi gọi ngược qua `outRemoveNode` · `outAddBlockInside` · `outToggleType`
33
+ * bằng chính các hàm trong context. Truyền cả `hiddenToolbar` và `templateToolbar` thì
34
+ * `hiddenToolbar` thắng — không vẽ gì.
35
+ */
36
+ readonly templateToolbar: import("@angular/core").InputSignal<TemplateRef<T_gridLayout_toolbarContext> | undefined>;
20
37
  /**
21
38
  * Bố cục vừa đổi do NGƯỜI DÙNG kéo hoặc đổi kích thước khối.
22
39
  *
@@ -36,12 +53,60 @@ export declare class LibsUiGridLayoutCanvasComponent implements OnDestroy {
36
53
  readonly outAddBlockInside: import("@angular/core").OutputEmitterRef<T_gridLayout_addEvent>;
37
54
  /** Người dùng bấm nút GỘP/TÁCH. Thư viện chưa làm gì — gọi `event.confirm()` để thực hiện. */
38
55
  readonly outToggleType: import("@angular/core").OutputEmitterRef<T_gridLayout_actionEvent>;
56
+ /** Bật tính năng thu phóng (zoom) canvas. Chỉ áp dụng cho canvas gốc (depth 0). */
57
+ readonly enableZoom: import("@angular/core").InputSignalWithTransform<boolean, boolean | undefined>;
58
+ /** Tỷ lệ thu phóng hiện tại (hỗ trợ two-way binding [(zoom)]="myZoom"). */
59
+ readonly zoom: import("@angular/core").ModelSignal<number | undefined>;
60
+ /** Giới hạn thu phóng tối thiểu. Mặc định 0.4 (40%). */
61
+ readonly zoomMin: import("@angular/core").InputSignal<number>;
62
+ /** Giới hạn thu phóng tối đa. Mặc định 1.5 (150%). */
63
+ readonly zoomMax: import("@angular/core").InputSignal<number>;
64
+ /** Bước nhảy thu phóng mỗi lần bấm +/- hoặc lăn chuột. Mặc định 0.05 (5%). */
65
+ readonly zoomStep: import("@angular/core").InputSignal<number>;
66
+ /** Chiều rộng chuẩn của canvas stage tính bằng px. Mặc định 1668. */
67
+ readonly stageWidth: import("@angular/core").InputSignal<number>;
68
+ /** Chiều cao tối thiểu của canvas stage tính bằng px. Mặc định 937. */
69
+ readonly stageMinHeight: import("@angular/core").InputSignal<number>;
70
+ /** Khóa lưu tỷ lệ thu phóng vào localStorage. Mặc định 'ocb_bi_canvas_zoom'. */
71
+ readonly storageKey: import("@angular/core").InputSignal<string | undefined>;
72
+ /** Hiển thị thanh dock điều khiển thu phóng nổi ở góc dưới phải. Mặc định true. */
73
+ readonly showZoomDock: import("@angular/core").InputSignalWithTransform<boolean, boolean | undefined>;
74
+ /** Tự động vừa màn hình khi tải trang lần đầu nếu khung nhìn nhỏ hơn stageWidth. Mặc định true. */
75
+ readonly autoFitOnLoad: import("@angular/core").InputSignalWithTransform<boolean, boolean | undefined>;
76
+ /** Phát ra khi tỷ lệ thu phóng thay đổi. */
77
+ readonly outZoomChange: import("@angular/core").OutputEmitterRef<number>;
78
+ /** Bật tính năng kéo thả thẻ từ bên ngoài vào canvas (mặc định true) */
79
+ readonly enableExternalDrop: import("@angular/core").InputSignalWithTransform<boolean, boolean | undefined>;
80
+ /** Luôn hiển thị đường kẻ lưới 24 cột */
81
+ readonly showGridLines: import("@angular/core").InputSignalWithTransform<boolean, boolean | undefined>;
82
+ /** Mã id của khối đang được chọn */
83
+ readonly selectedNodeId: import("@angular/core").ModelSignal<string | null | undefined>;
84
+ /** Phát ra khi thả một thẻ mới từ bên ngoài vào canvas */
85
+ readonly outDropNode: import("@angular/core").OutputEmitterRef<T_gridLayout_dropEvent>;
86
+ /** Phát ra khi người dùng click chọn một khối */
87
+ readonly outSelectNode: import("@angular/core").OutputEmitterRef<T_gridLayout_node>;
88
+ /** Phát ra khi người dùng bấm nút cấu hình trên thanh công cụ của khối */
89
+ readonly outConfigNode: import("@angular/core").OutputEmitterRef<T_gridLayout_actionEvent>;
39
90
  private readonly gridsterRef;
91
+ private readonly scrollAreaRef;
92
+ private readonly zoomDockRef;
40
93
  /** mỗi ô một chỗ gắn riêng — cùng thứ tự với `children()` */
41
94
  private readonly itemHosts;
95
+ /** Khung xem trước vị trí thả thẻ từ bên ngoài (snapped ghost preview) */
96
+ protected readonly dropGhost: import("@angular/core").WritableSignal<T_gridLayout_dropGhost | undefined>;
97
+ private activeHoveredEl?;
98
+ private rootGridsterEl?;
99
+ /** Menu chọn preset thu phóng đang mở hay đóng */
100
+ protected readonly isMenuOpen: import("@angular/core").WritableSignal<boolean>;
101
+ /** Các mốc thu phóng định sẵn */
102
+ protected readonly zoomPresets: Array<T_gridLayout_zoomPreset>;
42
103
  /** Sáu chấm của tay cầm kéo — chỉ để `@for` dựng đủ 6 thẻ, giá trị không dùng tới. */
43
104
  protected readonly dragBarDots: number[];
44
105
  private readonly service;
106
+ /** Chế độ thu phóng có đang hoạt động trên canvas này không (chỉ root canvas depth 0) */
107
+ protected readonly isZoomActive: import("@angular/core").Signal<boolean>;
108
+ protected readonly currentZoom: import("@angular/core").Signal<number>;
109
+ protected readonly zoomPercent: import("@angular/core").Signal<number>;
45
110
  /**
46
111
  * Node THẬT của mặt phẳng này.
47
112
  *
@@ -55,6 +120,13 @@ export declare class LibsUiGridLayoutCanvasComponent implements OnDestroy {
55
120
  */
56
121
  protected readonly currentNode: import("@angular/core").Signal<T_gridLayout_node>;
57
122
  protected readonly children: import("@angular/core").Signal<T_gridLayout_node[]>;
123
+ /**
124
+ * Bối cảnh cho `templateToolbar`, dựng sẵn theo từng khối.
125
+ *
126
+ * Dựng ở `computed` chứ không gọi hàm trên template: gọi hàm trong template chạy lại mỗi vòng
127
+ * dò thay đổi (rule `fe-coding-convention-templates` mục 6).
128
+ */
129
+ protected readonly toolbarContexts: import("@angular/core").Signal<T_gridLayout_toolbarContext[]>;
58
130
  protected readonly isNestedCanvas: import("@angular/core").Signal<boolean>;
59
131
  /** trần lồng container — khối đã chạm trần thì không cho chuyển thành container */
60
132
  protected readonly maxContainerDepth: import("@angular/core").Signal<number>;
@@ -70,11 +142,28 @@ export declare class LibsUiGridLayoutCanvasComponent implements OnDestroy {
70
142
  protected readonly isEditMode: import("@angular/core").Signal<boolean>;
71
143
  protected readonly gridOptions: import("@angular/core").Signal<import("angular-gridster2").GridsterConfig>;
72
144
  constructor();
145
+ ngAfterViewInit(): void;
73
146
  ngOnDestroy(): void;
74
147
  /** Vẽ lại lưới — gọi khi khung ngoài đổi kích thước hoặc cây vừa đổi diện rộng. */
75
148
  get FunctionControl(): {
76
149
  veLaiLuoi: () => void | undefined;
150
+ zoomIn: () => void;
151
+ zoomOut: () => void;
152
+ resetZoom: () => void;
153
+ fitToScreen: () => void;
154
+ setZoom: (val: number) => void;
77
155
  };
156
+ protected handlerZoomIn(): void;
157
+ protected handlerZoomOut(): void;
158
+ protected handlerResetZoom(): void;
159
+ protected handlerFitScreen(): void;
160
+ protected handlerToggleMenu(): void;
161
+ protected handlerSelectPreset(value: number): void;
162
+ protected isCurrentPreset(value: number): boolean;
163
+ private syncZoomOut;
164
+ protected handlerWheel(event: WheelEvent): void;
165
+ protected handlerDocumentKeyDown(event: KeyboardEvent): void;
166
+ protected handlerDocumentClick(event: MouseEvent): void;
78
167
  protected handlerTrackNode(_index: number, item: T_gridLayout_node): string;
79
168
  /**
80
169
  * 🔴 Chặn `mousedown` nổi lên lưới CHA khi thao tác trong lưới con.
@@ -94,6 +183,17 @@ export declare class LibsUiGridLayoutCanvasComponent implements OnDestroy {
94
183
  protected handlerRemove(event: Event, node: T_gridLayout_node): void;
95
184
  protected handlerAddInside(event: Event, node: T_gridLayout_node): void;
96
185
  protected handlerToggleType(event: Event, node: T_gridLayout_node): void;
186
+ /**
187
+ * Chặn cú bấm trong thanh công cụ nổi lên gridster.
188
+ *
189
+ * Chỉ `stopPropagation`, KHÔNG `preventDefault`: nút bên trong template của nơi dùng vẫn cần
190
+ * hành vi mặc định của mình (mở dropdown, focus…).
191
+ */
192
+ protected handlerStopEvent(event: Event): void;
193
+ /** Phát `outAddBlockInside` — dùng chung cho nút mặc định và `templateToolbar`. */
194
+ private emitAddInside;
195
+ /** Phát `outToggleType` — dùng chung cho nút mặc định và `templateToolbar`. */
196
+ private emitToggleType;
97
197
  /** khối này có phải container không (có khối con) */
98
198
  protected isContainer(item: T_gridLayout_node): boolean;
99
199
  /** container đã chạm trần lồng thì không cho thêm khối vào trong nữa */
@@ -102,6 +202,13 @@ export declare class LibsUiGridLayoutCanvasComponent implements OnDestroy {
102
202
  protected canToggleType(item: T_gridLayout_node): boolean;
103
203
  protected handlerBlockParentDrag(event: Event): void;
104
204
  protected handlerMouseDownTrongLuoi(event: Event): void;
205
+ protected handlerSelectNode(node: T_gridLayout_node, event?: MouseEvent): void;
206
+ protected handlerConfig(event: Event, node: T_gridLayout_node): void;
207
+ protected handlerDragOver(event: DragEvent): void;
208
+ protected handlerDragLeave(event: DragEvent): void;
209
+ protected handlerDrop(event: DragEvent): void;
210
+ private cleanupHoveredGroup;
211
+ private cleanupDragGrid;
105
212
  static ɵfac: i0.ɵɵFactoryDeclaration<LibsUiGridLayoutCanvasComponent, never>;
106
- static ɵcmp: i0.ɵɵComponentDeclaration<LibsUiGridLayoutCanvasComponent, "libs_ui-services-grid_layout-canvas", never, { "node": { "alias": "node"; "required": false; "isSignal": true; }; "depth": { "alias": "depth"; "required": false; "isSignal": true; }; }, { "outChange": "outChange"; "outRemoveNode": "outRemoveNode"; "outAddBlockInside": "outAddBlockInside"; "outToggleType": "outToggleType"; }, never, never, true, never>;
213
+ static ɵcmp: i0.ɵɵComponentDeclaration<LibsUiGridLayoutCanvasComponent, "libs_ui-services-grid_layout-canvas", never, { "node": { "alias": "node"; "required": false; "isSignal": true; }; "depth": { "alias": "depth"; "required": false; "isSignal": true; }; "hiddenToolbar": { "alias": "hiddenToolbar"; "required": false; "isSignal": true; }; "templateToolbar": { "alias": "templateToolbar"; "required": false; "isSignal": true; }; "enableZoom": { "alias": "enableZoom"; "required": false; "isSignal": true; }; "zoom": { "alias": "zoom"; "required": false; "isSignal": true; }; "zoomMin": { "alias": "zoomMin"; "required": false; "isSignal": true; }; "zoomMax": { "alias": "zoomMax"; "required": false; "isSignal": true; }; "zoomStep": { "alias": "zoomStep"; "required": false; "isSignal": true; }; "stageWidth": { "alias": "stageWidth"; "required": false; "isSignal": true; }; "stageMinHeight": { "alias": "stageMinHeight"; "required": false; "isSignal": true; }; "storageKey": { "alias": "storageKey"; "required": false; "isSignal": true; }; "showZoomDock": { "alias": "showZoomDock"; "required": false; "isSignal": true; }; "autoFitOnLoad": { "alias": "autoFitOnLoad"; "required": false; "isSignal": true; }; "enableExternalDrop": { "alias": "enableExternalDrop"; "required": false; "isSignal": true; }; "showGridLines": { "alias": "showGridLines"; "required": false; "isSignal": true; }; "selectedNodeId": { "alias": "selectedNodeId"; "required": false; "isSignal": true; }; }, { "outChange": "outChange"; "outRemoveNode": "outRemoveNode"; "outAddBlockInside": "outAddBlockInside"; "outToggleType": "outToggleType"; "zoom": "zoomChange"; "outZoomChange": "outZoomChange"; "selectedNodeId": "selectedNodeIdChange"; "outDropNode": "outDropNode"; "outSelectNode": "outSelectNode"; "outConfigNode": "outConfigNode"; }, never, never, true, never>;
107
214
  }
@@ -0,0 +1,31 @@
1
+ import { T_gridLayout_dropGhost, T_gridLayout_externalDragPayload, T_gridLayout_node } from '../interfaces/grid-layout.interface';
2
+ /**
3
+ * Checks whether the rectangular region [x, x + cols) x [y, y + rows)
4
+ * is completely vacant among existing items.
5
+ */
6
+ export declare const isRegionFree: (children: Array<T_gridLayout_node>, x: number, y: number, cols: number, rows: number, ignoredId?: string) => boolean;
7
+ /**
8
+ * Computes optimal (col, row) inside a group container.
9
+ * Snaps flush to adjacent card right edge if slot is vacant.
10
+ */
11
+ export declare const findSnapPositionInsideGroup: (children: Array<T_gridLayout_node>, rawCol: number, rawRow: number, innerCols: number, innerRows: number, totalCols?: number) => {
12
+ col: number;
13
+ row: number;
14
+ };
15
+ /**
16
+ * Resolves vertical collisions when inserting a new card at (newNode.x, newNode.y).
17
+ * Any existing card that horizontally overlaps and whose vertical span overlaps
18
+ * or is below the insertion point is pushed down so there is no overlap.
19
+ */
20
+ export declare const resolveCollisionsAndInsert: (children: Array<T_gridLayout_node>, newNode: T_gridLayout_node) => void;
21
+ export declare function parseDropPayload<T>(activePayload: T | undefined, dataTransfer: DataTransfer | null): T | undefined;
22
+ interface I_hoveredGroup {
23
+ groupNode: T_gridLayout_node;
24
+ groupGridster: HTMLElement;
25
+ }
26
+ export declare const findHoveredGroup: (elements: Element[], canvasContainer: HTMLElement, root: T_gridLayout_node) => I_hoveredGroup | undefined;
27
+ /**
28
+ * Calculates snapped spatial coordinates and pixel dimensions for drop ghost preview box.
29
+ */
30
+ export declare const calculateDropGhost: (event: DragEvent, canvasContainer: HTMLElement, root: T_gridLayout_node, payload: T_gridLayout_externalDragPayload, totalCols?: number) => T_gridLayout_dropGhost | undefined;
31
+ export {};