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

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
@@ -125,6 +125,10 @@ protected handlerAddInside(event: T_gridLayout_addEvent): void {
125
125
  | `outRemoveNode` | bấm nút xoá | `() => boolean` |
126
126
  | `outToggleType` | bấm gộp (khối đơn → container) hoặc tách | `() => boolean` |
127
127
  | `outAddBlockInside` | bấm + trên container | `(item) => node \| undefined` |
128
+ | `outDropNode` | vừa **thả** một thẻ kéo từ ngoài vào | — (khối đã chèn rồi) |
129
+ | `outSelectNode` | bấm chọn một khối | — |
130
+ | `outConfigNode` | bấm nút bánh răng trên khối | `() => true` |
131
+ | `outZoomChange` | tỷ lệ thu phóng vừa đổi | — |
128
132
 
129
133
  `outChange` **không** bắn khi chính bạn gọi `addBlock`/`removeBlock`/`compact` — bạn đã biết mình vừa
130
134
  làm gì, khỏi phải chống dội.
@@ -152,7 +156,13 @@ làm gì, khỏi phải chống dội.
152
156
  | `compact()` | dồn khối lên, bỏ khoảng trống. Đệ quy vào container |
153
157
  | `findBlock(id)` · `findParentBlock(id)` | tra cây |
154
158
  | `exportLayout()` | cây hiện tại đã bỏ hàm, `cloneDeep` — gửi thẳng lên backend được |
155
- | `Root` · `IsEditMode` · `Version` | signal chỉ đọc |
159
+ | `insertBlockWithCollision(item, col, row, parentId?)` | chèn vào ĐÚNG ô và đẩy khối va chạm xuống. `addBlock` thì tự tìm hàng trống kế tiếp |
160
+ | `duplicateBlock(id, transformData?)` | nhân bản; `transformData` để bạn tự đổi dữ liệu của mình (thư viện không đoán trường nào là tiêu đề) |
161
+ | `resizeBlock(id, { cols, rows })` | đổi kích thước bằng code; rộng quá mép phải thì tự dịch sang trái |
162
+ | `setDragPayload(p)` · `clearDragPayload()` | báo cho canvas biết đang kéo thẻ gì từ palette ngoài vào |
163
+ | `setZoom(v)` · `zoomIn()` · `zoomOut()` · `resetZoom()` · `fitToScreen(w)` | thu phóng |
164
+ | `Root` · `IsEditMode` · `Version` · `Zoom` | signal chỉ đọc |
165
+ | `totalColumns` | số cột thật của lưới cấp trang (mặc định 12) |
156
166
 
157
167
  Mọi hàm sửa cây **tự vẽ lại** — không phải gọi `refresh()` theo sau.
158
168
 
@@ -176,6 +186,111 @@ this.gridLayout.init({ data: UtilsCache.Get(KEY) ?? defaultLayout(), getComponen
176
186
  `exportLayout()` đã lược `getComponentOutlet` (hàm không tuần tự hoá được) nên `JSON.stringify` được
177
187
  ngay. Mở lại chỉ cần đưa `getComponentOutlet` vào `init` — cây không mang hàm.
178
188
 
189
+ ## Kéo thẻ từ palette bên ngoài vào
190
+
191
+ Nơi dùng chỉ làm **hai việc ở thẻ palette**; canvas lo phần còn lại.
192
+
193
+ ```ts
194
+ protected handlerPaletteDragStart(event: DragEvent, item: T_paletteItem): void {
195
+ this.gridLayout.setDragPayload({
196
+ type: item.type,
197
+ title: item.label, // chữ trên khung xem trước
198
+ cols: item.cols, // thư viện đo khung xem trước theo hai số này
199
+ rows: item.rows,
200
+ // Khối THẬT sẽ tạo. `isInside` = con trỏ đang trong một khối ghép ⇒ thu cho vừa ruột
201
+ // container (lưới con luôn 12 cột).
202
+ resolveNode: (isInside: boolean) => ({
203
+ cols: isInside ? 12 : item.cols,
204
+ rows: isInside ? 4 : item.rows,
205
+ data: { ...buildBlockData(item), label: '' }, // nhãn TRỐNG — đánh số ở `outDropNode`
206
+ }),
207
+ });
208
+ event.dataTransfer?.setData('text/plain', item.label); // thiếu dòng này Firefox không kéo được
209
+ }
210
+
211
+ protected handlerPaletteDragEnd(): void {
212
+ this.gridLayout.clearDragPayload(); // người dùng thả RA NGOÀI canvas thì thư viện không bắn gì
213
+ }
214
+ ```
215
+
216
+ ```html
217
+ <div draggable="true"
218
+ (dragstart)="handlerPaletteDragStart($event, item)"
219
+ (dragend)="handlerPaletteDragEnd()">…</div>
220
+
221
+ <libs_ui-services-grid_layout-canvas (outDropNode)="handlerDropNode($event)" />
222
+ ```
223
+
224
+ 🔴 **CẤM tự viết `(dragover)` / `(drop)` trên canvas.** Canvas đã làm: vẽ khung xem trước bám ô lưới,
225
+ bật ô li của container khi con trỏ đi vào trong rồi tắt đúng lúc, chèn khối qua
226
+ `insertBlockWithCollision` và đẩy khối va chạm xuống.
227
+
228
+ 🔴 **`resolveNode` phải THUẦN.** Thư viện gọi nó ở **mỗi nhịp `dragover`** chỉ để ĐO khung xem trước.
229
+ Cấm tăng bộ đếm, sinh id, gọi API bên trong — đánh số khối thì đánh ở `outDropNode`.
230
+
231
+ 🔴 **`outDropNode` không có `confirm()`** — lúc nó bắn thì khối đã chèn rồi. Chặn trước thì chặn ở
232
+ `dragstart`; gỡ sau thì `removeBlock(event.node.id)`. `event.parentId` khác `undefined` ⇒ người dùng
233
+ thả vào ruột một khối ghép.
234
+
235
+ `LibsUiGridLayoutDragController` là lớp **tuỳ chọn** cho trường hợp bạn tự dựng bề mặt thả riêng
236
+ ngoài canvas. Dùng canvas sẵn có thì không cần đụng tới.
237
+
238
+ ## Thu phóng và Stage
239
+
240
+ ```ts
241
+ this.gridLayout.init({
242
+ getComponentOutlet,
243
+ data: layoutFromServer(),
244
+ enableZoom: true,
245
+ stageWidth: 1400, // 12 cột chia trên con số NÀY, không phải bề rộng cửa sổ
246
+ stageMinHeight: 900,
247
+ zoomMin: 0.4,
248
+ zoomMax: 1.5,
249
+ storageKey: 'bao_cao_ban_hang_zoom', // 🔴 RIÊNG từng màn
250
+ autoFitOnLoad: true,
251
+ });
252
+ ```
253
+
254
+ Bật `enableZoom` là canvas dựng một **stage** khổ cố định rồi phóng to/thu nhỏ cả stage — bố cục
255
+ nhìn giống nhau trên mọi khổ màn. Không bật thì 12 cột chia theo bề rộng cửa sổ, mỗi máy một kích
256
+ thước khối.
257
+
258
+ 🔴 **`storageKey` phải riêng từng màn** — dùng chung thì hai màn ghi đè tỷ lệ của nhau.
259
+
260
+ Canvas có sẵn cụm nút thu phóng ở góc; muốn đặt nút chỗ khác thì `[showZoomDock]="false"` rồi gọi
261
+ `zoomIn()` · `zoomOut()` · `resetZoom()` · `setZoom(v)` · `fitToScreen(width)`.
262
+
263
+ ## Input của canvas
264
+
265
+ | Input | Mặc định | Việc |
266
+ |---|---|---|
267
+ | `[(selectedNodeId)]` | `undefined` | khối đang chọn — **hai chiều**, thư viện ghi id vào signal của bạn |
268
+ | `[(zoom)]` | `undefined` | tỷ lệ thu phóng — **hai chiều** |
269
+ | `[showGridLines]` | `false` | vẽ ô li của lưới (lúc KÉO thư viện tự bật rồi tự tắt) |
270
+ | `[showZoomDock]` | `true` | cụm nút thu phóng ở góc canvas |
271
+ | `[enableExternalDrop]` | `true` | cho thả thẻ kéo từ ngoài vào |
272
+ | `[hiddenToolbar]` | `false` | ẩn hẳn bộ nút mặc định trên khối |
273
+ | `[templateToolbar]` | `undefined` | tự vẽ thanh công cụ của khối |
274
+
275
+ 🔴 **`node` và `depth` là input NỘI BỘ** — thư viện tự truyền khi vẽ lưới con. Mặt phẳng gốc đọc cây
276
+ từ service (đã đưa vào qua `init()`), truyền thêm là khai hai lần cùng một thứ.
277
+
278
+ ```html
279
+ <libs_ui-services-grid_layout-canvas [templateToolbar]="toolbar" />
280
+
281
+ <ng-template #toolbar let-node
282
+ let-isContainer="isContainer" let-canAddInside="canAddInside"
283
+ let-remove="remove" let-addInside="addInside" let-toggleType="toggleType">
284
+ @if (canAddInside) { <button (click)="addInside()">+</button> }
285
+ <button (click)="toggleType()">{{ isContainer ? 'Tách' : 'Gộp' }}</button>
286
+ <button (click)="remove()">Xoá</button>
287
+ </ng-template>
288
+ ```
289
+
290
+ 🔴 `templateToolbar` đổi **giao diện**, không đổi **luật**: ba hàm trong context chỉ phát đúng sự
291
+ kiện (`outRemoveNode` · `outAddBlockInside` · `outToggleType`), y như bấm nút mặc định. Vẫn phải bắt
292
+ sự kiện rồi gọi `event.confirm()`.
293
+
179
294
  ## Vì sao cấu hình gridster lại như vậy
180
295
 
181
296
  `defines/gridster-config.define.ts` giữ toàn bộ cấu hình đã vá. Mỗi dòng có dấu 🔴 tương ứng một lỗi
@@ -152,6 +152,7 @@ export declare class LibsUiGridLayoutCanvasComponent implements AfterViewInit, O
152
152
  resetZoom: () => void;
153
153
  fitToScreen: () => void;
154
154
  setZoom: (val: number) => void;
155
+ scrollArea: () => HTMLElement | undefined;
155
156
  };
156
157
  protected handlerZoomIn(): void;
157
158
  protected handlerZoomOut(): void;
@@ -1,4 +1,4 @@
1
- import { T_gridLayout_dropGhost, T_gridLayout_externalDragPayload, T_gridLayout_node } from '../interfaces/grid-layout.interface';
1
+ import { T_gridLayout_dropGhost, T_gridLayout_externalDragPayload, T_gridLayout_newNode, T_gridLayout_node } from '../interfaces/grid-layout.interface';
2
2
  /**
3
3
  * Checks whether the rectangular region [x, x + cols) x [y, y + rows)
4
4
  * is completely vacant among existing items.
@@ -28,4 +28,29 @@ export declare const findHoveredGroup: (elements: Element[], canvasContainer: HT
28
28
  * Calculates snapped spatial coordinates and pixel dimensions for drop ghost preview box.
29
29
  */
30
30
  export declare const calculateDropGhost: (event: DragEvent, canvasContainer: HTMLElement, root: T_gridLayout_node, payload: T_gridLayout_externalDragPayload, totalCols?: number) => T_gridLayout_dropGhost | undefined;
31
+ /** Tìm khối theo id trong cây, đi sâu vào mọi container. */
32
+ export declare const findNodeRecursive: (root: T_gridLayout_node, id: string) => T_gridLayout_node | undefined;
33
+ /** Tìm khối CHA trực tiếp của một khối, đi sâu vào mọi container. */
34
+ export declare const findParentRecursive: (root: T_gridLayout_node, id: string) => T_gridLayout_node | undefined;
35
+ /**
36
+ * Thả khối vào đúng ô mà ghost đang chỉ, đẩy các khối va chạm xuống dưới.
37
+ *
38
+ * Ghost đang nằm trong một container (`isInsideGroup`) thì thả vào ruột container đó; không tìm thấy
39
+ * container thì trả `false` chứ KHÔNG rơi về lưới gốc — thả nhầm cấp khó nhận ra hơn là không thả.
40
+ */
41
+ export declare const dropNodeAtCoordinates: (root: T_gridLayout_node, ghost: T_gridLayout_dropGhost, newNode: T_gridLayout_newNode) => boolean;
42
+ /** Khối có phải CONTAINER không — tức có khối con bên trong. */
43
+ export declare const isContainerNode: (node: T_gridLayout_node) => boolean;
44
+ /** Khối có nằm LỒNG bên trong một container không (không phải con trực tiếp của gốc). */
45
+ export declare const isNodeNestedInside: (root: T_gridLayout_node | undefined, nodeId: string) => boolean;
46
+ /**
47
+ * Có được đổi khối đơn ↔ container không.
48
+ *
49
+ * 🔴 Ba hàm cờ này PHẢI là nguồn duy nhất cho cả thanh công cụ của thư viện lẫn menu do nơi dùng tự
50
+ * dựng. Trước đây nơi dùng phải chép lại quy tắc để dựng menu trước lúc render, và hai bản lệch nhau
51
+ * mỗi khi thư viện đổi trần lồng cấp mà không có gì báo.
52
+ */
53
+ export declare const canToggleTypeNode: (root: T_gridLayout_node | undefined, node: T_gridLayout_node) => boolean;
54
+ /** Có được thêm khối vào BÊN TRONG khối này không. */
55
+ export declare const canAddInsideNode: (root: T_gridLayout_node | undefined, node: T_gridLayout_node) => boolean;
31
56
  export {};
@@ -0,0 +1,34 @@
1
+ import { T_gridLayout_dropGhost, T_gridLayout_externalDragPayload, T_gridLayout_node } from './interfaces/grid-layout.interface';
2
+ /**
3
+ * Giữ trạng thái một lượt kéo khối từ palette bên ngoài vào mặt phẳng.
4
+ *
5
+ * Việc nó lo: bật ô li của lưới đang được kéo tới (lưới gốc, và lưới của container khi con trỏ đi
6
+ * vào trong), tắt đúng lúc, và trả về ghost để nơi dùng vẽ khung xem trước.
7
+ *
8
+ * 🔴 Tự bật/tắt ô li bằng tay ở nơi dùng là chỗ rất dễ sót: kéo ra khỏi container rồi mà quên tắt
9
+ * thì ô li của nó nằm lại trên màn, kéo sang lưới khác lại chồng thêm một lớp nữa.
10
+ */
11
+ export declare class LibsUiGridLayoutDragController {
12
+ private payload?;
13
+ private hoveredElement?;
14
+ private rootGridsterElement?;
15
+ /** Payload của khối đang được kéo; `undefined` khi không có lượt kéo nào. */
16
+ get ActivePayload(): T_gridLayout_externalDragPayload | undefined;
17
+ /** Gọi ở `dragstart` của thẻ trong palette. */
18
+ onDragStart(payload: T_gridLayout_externalDragPayload): void;
19
+ /** Gọi ở `dragend` — dọn sạch mọi ô li đang bật. */
20
+ onDragEnd(container?: HTMLElement): void;
21
+ /** Tắt ô li của container đang hover. */
22
+ cleanupHoveredGroup(): void;
23
+ /** Tắt ô li của lưới gốc, và quét sạch lớp còn sót trong khung chứa. */
24
+ cleanupRootGrid(container?: HTMLElement): void;
25
+ /** Tắt toàn bộ ô li đang bật. */
26
+ cleanupAll(container?: HTMLElement): void;
27
+ /**
28
+ * Gọi ở `dragover` của khung chứa mặt phẳng. Trả ghost để nơi dùng vẽ khung xem trước, đồng thời
29
+ * tự bật ô li của lưới đang được kéo tới.
30
+ */
31
+ onDragOver(event: DragEvent, container: HTMLElement | undefined, root: T_gridLayout_node | undefined, totalCols?: number): T_gridLayout_dropGhost | undefined;
32
+ /** Bật ô li của lưới CẤP TRANG trong suốt lượt kéo, tắt lưới cũ nếu khung chứa đã đổi. */
33
+ private showRootGrid;
34
+ }