@libs-ui/services-grid-layout 0.2.357-22 → 0.2.357-24

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
@@ -128,6 +128,11 @@ export declare class LibsUiGridLayoutCanvasComponent implements AfterViewInit, O
128
128
  */
129
129
  protected readonly toolbarContexts: import("@angular/core").Signal<T_gridLayout_toolbarContext[]>;
130
130
  protected readonly isNestedCanvas: import("@angular/core").Signal<boolean>;
131
+ /**
132
+ * Khe nhìn thấy giữa các khối CẤP GỐC. Mặt phẳng lồng trả 0 — khối con là các dòng của cùng một
133
+ * thẻ, tách chúng ra làm chúng trông như thẻ rời.
134
+ */
135
+ protected readonly visualGap: import("@angular/core").Signal<number>;
131
136
  /** trần lồng container — khối đã chạm trần thì không cho chuyển thành container */
132
137
  protected readonly maxContainerDepth: import("@angular/core").Signal<number>;
133
138
  /**
@@ -27,7 +27,7 @@ export declare const findHoveredGroup: (elements: Element[], canvasContainer: HT
27
27
  /**
28
28
  * Calculates snapped spatial coordinates and pixel dimensions for drop ghost preview box.
29
29
  */
30
- export declare const calculateDropGhost: (event: DragEvent, canvasContainer: HTMLElement, root: T_gridLayout_node, payload: T_gridLayout_externalDragPayload, totalCols?: number) => T_gridLayout_dropGhost | undefined;
30
+ export declare const calculateDropGhost: (event: DragEvent, canvasContainer: HTMLElement, root: T_gridLayout_node, payload: T_gridLayout_externalDragPayload, totalCols?: number, rowStep?: number) => T_gridLayout_dropGhost | undefined;
31
31
  /** Tìm khối theo id trong cây, đi sâu vào mọi container. */
32
32
  export declare const findNodeRecursive: (root: T_gridLayout_node, id: string) => T_gridLayout_node | undefined;
33
33
  /** Tìm khối CHA trực tiếp của một khối, đi sâu vào mọi container. */
@@ -28,7 +28,7 @@ export declare class LibsUiGridLayoutDragController {
28
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
29
  * tự bật ô li của lưới đang được kéo tới.
30
30
  */
31
- onDragOver(event: DragEvent, container: HTMLElement | undefined, root: T_gridLayout_node | undefined, totalCols?: number): T_gridLayout_dropGhost | undefined;
31
+ onDragOver(event: DragEvent, container: HTMLElement | undefined, root: T_gridLayout_node | undefined, totalCols?: number, rowStep?: number): T_gridLayout_dropGhost | undefined;
32
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
33
  private showRootGrid;
34
34
  }