@libs-ui/components-skeleton 0.2.357-1 → 0.2.357-3

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
@@ -10,6 +10,7 @@
10
10
 
11
11
  - ✅ Hiệu ứng shimmer loading mượt mà (CSS animation)
12
12
  - ✅ Tự động tính toán số lượng items dựa trên chiều cao container cha
13
+ - ✅ Tự động tính toán lại khi cửa sổ thay đổi kích thước (responsive, debounce 200ms)
13
14
  - ✅ Hỗ trợ cấu hình layout phức tạp: rows và cols lồng nhau nhiều cấp
14
15
  - ✅ Custom class cho từng cấp: container, row, col, item
15
16
  - ✅ Hỗ trợ repeat ở mỗi cấp (container / row / col)
@@ -370,7 +371,7 @@ interface ISkeletonItem {
370
371
 
371
372
  ### Auto-calculation trong ngAfterViewInit
372
373
 
373
- Component thực hiện tính toán tự động sau khi view khởi tạo (`ngAfterViewInit` + `setTimeout`):
374
+ Component thực hiện tính toán tự động sau khi view khởi tạo (`ngAfterViewInit` + `setTimeout`) và **tự chạy lại khi cửa sổ resize** (lắng nghe `window:resize`, debounce 200ms, tự cleanup bằng `takeUntilDestroyed`):
374
375
 
375
376
  1. **Khi `config.rows` rỗng hoặc không có**: Tự tạo 1 row mặc định với item rỗng.
376
377
  2. **Khi `heightContainer` không được set**: Đọc `offsetHeight` thực tế của container DOM để làm chiều cao mỗi block.
@@ -391,6 +392,8 @@ Khi một row hoặc col có `repeat > 1`, phần tử cuối cùng trong vòng
391
392
 
392
393
  ⚠️ **setTimeout trong ngAfterViewInit**: Việc tính toán được bọc trong `setTimeout` (0ms) để đảm bảo DOM đã render xong và `offsetHeight` trả về giá trị chính xác. Điều này là bình thường và có chủ ý.
393
394
 
395
+ ⚠️ **Tự tính lại khi resize**: Component lắng nghe `window:resize` (debounce 200ms) để tính lại số dòng skeleton theo chiều cao container mới — giữ skeleton lấp đầy đúng khi layout co giãn. Subscription tự cleanup qua `takeUntilDestroyed`, không cần xử lý thủ công.
396
+
394
397
  ⚠️ **classRowLast vs classRow**: Khi `repeat = 1` (mặc định), phần tử duy nhất được coi là "cả first lẫn last", tức là `classRowLast` sẽ được áp dụng. Thiết kế này có nghĩa là với row không repeat, bạn chỉ cần dùng `classRowLast` để set class cho row đó.
395
398
 
396
399
  ⚠️ **Input `config` có transform**: Input `config` được khai báo với transform `(value) => value || {}`. Truyền `undefined` hoặc `null` cho `[config]` sẽ được tự động chuyển thành `{}` (config mặc định), component sẽ không crash.
@@ -1,28 +1,35 @@
1
1
  import { NgTemplateOutlet } from '@angular/common';
2
- import { ChangeDetectionStrategy, Component, input, signal, viewChild } from '@angular/core';
2
+ import { ChangeDetectionStrategy, Component, DestroyRef, inject, input, signal, viewChild } from '@angular/core';
3
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
+ import { debounceTime, fromEvent, merge, timer } from 'rxjs';
3
5
  import * as i0 from "@angular/core";
4
6
  export class LibsUiComponentsSkeletonComponent {
5
7
  configRender = signal({});
6
8
  config = input({}, { transform: (value) => value || {} });
7
9
  skeletonRef = viewChild.required('skeletonRef');
10
+ destroyRef = inject(DestroyRef);
8
11
  ngAfterViewInit() {
9
- setTimeout(() => {
10
- this.configRender.update((config) => {
11
- config = this.config();
12
- let offsetHeight = this.skeletonRef().nativeElement.offsetHeight;
13
- if (!config.rows?.length) {
14
- config.rows = [{ item: {} }];
15
- }
16
- if (!config.heightContainer) {
17
- config.heightContainer = offsetHeight - (config.styleMarginBottom || 0);
18
- }
19
- if (config.repeat) {
20
- offsetHeight = offsetHeight || config.heightContainer || config.styleMarginBottom || 0;
21
- config.heightContainer = offsetHeight / config.repeat - (config.styleMarginBottom || 0);
22
- }
23
- config.repeat = Math.ceil(offsetHeight / ((config.heightContainer || 0) + (config.styleMarginBottom || 0)));
24
- return { ...config };
25
- });
12
+ // Tính 1 lần sau 100ms (chờ layout ổn định để đọc offsetHeight đúng) + tính lại mỗi khi resize.
13
+ merge(timer(100), fromEvent(window, 'resize').pipe(debounceTime(200)))
14
+ .pipe(takeUntilDestroyed(this.destroyRef))
15
+ .subscribe(() => this.calculator());
16
+ }
17
+ calculator() {
18
+ this.configRender.update((config) => {
19
+ config = this.config();
20
+ let offsetHeight = this.skeletonRef().nativeElement.offsetHeight;
21
+ if (!config.rows?.length) {
22
+ config.rows = [{ item: {} }];
23
+ }
24
+ if (!config.heightContainer) {
25
+ config.heightContainer = offsetHeight - (config.styleMarginBottom || 0);
26
+ }
27
+ if (config.repeat) {
28
+ offsetHeight = offsetHeight || config.heightContainer || config.styleMarginBottom || 0;
29
+ config.heightContainer = offsetHeight / config.repeat - (config.styleMarginBottom || 0);
30
+ }
31
+ config.repeat = Math.ceil(offsetHeight / ((config.heightContainer || 0) + (config.styleMarginBottom || 0)));
32
+ return { ...config };
26
33
  });
27
34
  }
28
35
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiComponentsSkeletonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
@@ -32,4 +39,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
32
39
  type: Component,
33
40
  args: [{ selector: 'libs_ui-components-skeleton', standalone: true, imports: [NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n #skeletonRef\n class=\"w-full h-full\">\n @if (configRender(); as config) {\n @for (configRepeat of [].constructor(config.repeat || 1); track $index; let lastItem = $last) {\n <div\n [style.marginBottom.px]=\"config.styleMarginBottom && !lastItem ? config.styleMarginBottom : 0\"\n [style.height]=\"config.heightContainer ? config.heightContainer + 'px' : '100%'\"\n [class]=\"(config.classRows || '') + ' flex flex-col'\">\n @for (row of config.rows; track $index) {\n @for (rowRepeat of [].constructor(row.repeat || 1); track $index; let firstRow = $first; let lastRow = $last) {\n <div [class]=\"((lastRow || (firstRow && lastRow) ? row.classRowLast : row.classRow) || '') + ' flex flex-col w-full h-full'\">\n @if (row.item) {\n <ng-container *ngTemplateOutlet=\"templateSkeleton; context: { config: row.item }\" />\n }\n <div [class]=\"((lastRow || (firstRow && lastRow) ? row.classColsLast : row.classCols) || '') + 'flex w-full'\">\n @for (col of row.cols; track $index) {\n @for (colRepeat of [].constructor(col.repeat || 1); track $index; let firstCol = $first; let lastCol = $last) {\n <div [class]=\"((lastCol || (firstCol && lastCol) ? col.classColLast : col.classCol) || '') + ' flex w-full'\">\n @if (col.item) {\n <ng-container *ngTemplateOutlet=\"templateSkeleton; context: { config: col.item }\" />\n }\n </div>\n }\n }\n </div>\n </div>\n }\n }\n </div>\n }\n }\n</div>\n\n<ng-template\n #templateSkeleton\n let-config=\"config\">\n <div [class]=\"config?.classInclude || 'w-full h-full'\">\n @for (_ of [].constructor(config.repeat || 1); track $index; let last = $last) {\n <div\n class=\"w-full h-full skeleton rounded-[8px] {{ config?.classIncludeItem || '' }}\"\n [class.mb-[16px]]=\"config?.styleDefault && !last\"></div>\n }\n </div>\n</ng-template>\n", styles: ["@keyframes mo-skeleton-loading{0%{background-position:100% 50%}to{background-position:0 50%}}.skeleton{animation:mo-skeleton-loading 1.4s ease infinite;background:linear-gradient(90deg,#bebebe33 25%,#8181813d 37%,#bebebe33 63%);background-size:400% 100%}\n"] }]
34
41
  }] });
35
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2tlbGV0b24uY29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vbGlicy11aS9jb21wb25lbnRzL3NrZWxldG9uL3NyYy9za2VsZXRvbi5jb21wb25lbnQudHMiLCIuLi8uLi8uLi8uLi8uLi9saWJzLXVpL2NvbXBvbmVudHMvc2tlbGV0b24vc3JjL3NrZWxldG9uLmNvbXBvbmVudC5odG1sIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQ25ELE9BQU8sRUFBaUIsdUJBQXVCLEVBQUUsU0FBUyxFQUFjLEtBQUssRUFBRSxNQUFNLEVBQUUsU0FBUyxFQUFFLE1BQU0sZUFBZSxDQUFDOztBQVl4SCxNQUFNLE9BQU8saUNBQWlDO0lBQ2xDLFlBQVksR0FBRyxNQUFNLENBQWtCLEVBQUUsQ0FBQyxDQUFDO0lBRTVDLE1BQU0sR0FBRyxLQUFLLENBQStDLEVBQUUsRUFBRSxFQUFFLFNBQVMsRUFBRSxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsS0FBSyxJQUFJLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFFeEcsV0FBVyxHQUFHLFNBQVMsQ0FBQyxRQUFRLENBQWEsYUFBYSxDQUFDLENBQUM7SUFFckUsZUFBZTtRQUNiLFVBQVUsQ0FBQyxHQUFHLEVBQUU7WUFDZCxJQUFJLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxDQUFDLE1BQU0sRUFBRSxFQUFFO2dCQUNsQyxNQUFNLEdBQUcsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO2dCQUN2QixJQUFJLFlBQVksR0FBRyxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUMsYUFBYSxDQUFDLFlBQVksQ0FBQztnQkFDakUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLEVBQUUsTUFBTSxFQUFFLENBQUM7b0JBQ3pCLE1BQU0sQ0FBQyxJQUFJLEdBQUcsQ0FBQyxFQUFFLElBQUksRUFBRSxFQUFFLEVBQUUsQ0FBQyxDQUFDO2dCQUMvQixDQUFDO2dCQUNELElBQUksQ0FBQyxNQUFNLENBQUMsZUFBZSxFQUFFLENBQUM7b0JBQzVCLE1BQU0sQ0FBQyxlQUFlLEdBQUcsWUFBWSxHQUFHLENBQUMsTUFBTSxDQUFDLGlCQUFpQixJQUFJLENBQUMsQ0FBQyxDQUFDO2dCQUMxRSxDQUFDO2dCQUNELElBQUksTUFBTSxDQUFDLE1BQU0sRUFBRSxDQUFDO29CQUNsQixZQUFZLEdBQUcsWUFBWSxJQUFJLE1BQU0sQ0FBQyxlQUFlLElBQUksTUFBTSxDQUFDLGlCQUFpQixJQUFJLENBQUMsQ0FBQztvQkFDdkYsTUFBTSxDQUFDLGVBQWUsR0FBRyxZQUFZLEdBQUcsTUFBTSxDQUFDLE1BQU0sR0FBRyxDQUFDLE1BQU0sQ0FBQyxpQkFBaUIsSUFBSSxDQUFDLENBQUMsQ0FBQztnQkFDMUYsQ0FBQztnQkFDRCxNQUFNLENBQUMsTUFBTSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsWUFBWSxHQUFHLENBQUMsQ0FBQyxNQUFNLENBQUMsZUFBZSxJQUFJLENBQUMsQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLGlCQUFpQixJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztnQkFFNUcsT0FBTyxFQUFFLEdBQUcsTUFBTSxFQUFFLENBQUM7WUFDdkIsQ0FBQyxDQUFDLENBQUM7UUFDTCxDQUFDLENBQUMsQ0FBQztJQUNMLENBQUM7d0dBM0JVLGlDQUFpQzs0RkFBakMsaUNBQWlDLDBWQ2I5QywrbEVBNkNBLDBURG5DWSxnQkFBZ0I7OzRGQUdmLGlDQUFpQztrQkFUN0MsU0FBUzsrQkFFRSw2QkFBNkIsY0FHM0IsSUFBSSxXQUNQLENBQUMsZ0JBQWdCLENBQUMsbUJBQ1YsdUJBQXVCLENBQUMsTUFBTSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IE5nVGVtcGxhdGVPdXRsZXQgfSBmcm9tICdAYW5ndWxhci9jb21tb24nO1xuaW1wb3J0IHsgQWZ0ZXJWaWV3SW5pdCwgQ2hhbmdlRGV0ZWN0aW9uU3RyYXRlZ3ksIENvbXBvbmVudCwgRWxlbWVudFJlZiwgaW5wdXQsIHNpZ25hbCwgdmlld0NoaWxkIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBJU2tlbGV0b25Db25maWcgfSBmcm9tICcuL2ludGVyZmFjZXMvc2tlbGV0b24taXRlbS5pbnRlcmZhY2UnO1xuXG5AQ29tcG9uZW50KHtcbiAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEBhbmd1bGFyLWVzbGludC9jb21wb25lbnQtc2VsZWN0b3JcbiAgc2VsZWN0b3I6ICdsaWJzX3VpLWNvbXBvbmVudHMtc2tlbGV0b24nLFxuICB0ZW1wbGF0ZVVybDogJ3NrZWxldG9uLmNvbXBvbmVudC5odG1sJyxcbiAgc3R5bGVVcmxzOiBbJy4vc2tlbGV0b24uY29tcG9uZW50LnNjc3MnXSxcbiAgc3RhbmRhbG9uZTogdHJ1ZSxcbiAgaW1wb3J0czogW05nVGVtcGxhdGVPdXRsZXRdLFxuICBjaGFuZ2VEZXRlY3Rpb246IENoYW5nZURldGVjdGlvblN0cmF0ZWd5Lk9uUHVzaCxcbn0pXG5leHBvcnQgY2xhc3MgTGlic1VpQ29tcG9uZW50c1NrZWxldG9uQ29tcG9uZW50IGltcGxlbWVudHMgQWZ0ZXJWaWV3SW5pdCB7XG4gIHByb3RlY3RlZCBjb25maWdSZW5kZXIgPSBzaWduYWw8SVNrZWxldG9uQ29uZmlnPih7fSk7XG5cbiAgcmVhZG9ubHkgY29uZmlnID0gaW5wdXQ8SVNrZWxldG9uQ29uZmlnLCBJU2tlbGV0b25Db25maWcgfCB1bmRlZmluZWQ+KHt9LCB7IHRyYW5zZm9ybTogKHZhbHVlKSA9PiB2YWx1ZSB8fCB7fSB9KTtcblxuICByZWFkb25seSBza2VsZXRvblJlZiA9IHZpZXdDaGlsZC5yZXF1aXJlZDxFbGVtZW50UmVmPignc2tlbGV0b25SZWYnKTtcblxuICBuZ0FmdGVyVmlld0luaXQoKTogdm9pZCB7XG4gICAgc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICB0aGlzLmNvbmZpZ1JlbmRlci51cGRhdGUoKGNvbmZpZykgPT4ge1xuICAgICAgICBjb25maWcgPSB0aGlzLmNvbmZpZygpO1xuICAgICAgICBsZXQgb2Zmc2V0SGVpZ2h0ID0gdGhpcy5za2VsZXRvblJlZigpLm5hdGl2ZUVsZW1lbnQub2Zmc2V0SGVpZ2h0O1xuICAgICAgICBpZiAoIWNvbmZpZy5yb3dzPy5sZW5ndGgpIHtcbiAgICAgICAgICBjb25maWcucm93cyA9IFt7IGl0ZW06IHt9IH1dO1xuICAgICAgICB9XG4gICAgICAgIGlmICghY29uZmlnLmhlaWdodENvbnRhaW5lcikge1xuICAgICAgICAgIGNvbmZpZy5oZWlnaHRDb250YWluZXIgPSBvZmZzZXRIZWlnaHQgLSAoY29uZmlnLnN0eWxlTWFyZ2luQm90dG9tIHx8IDApO1xuICAgICAgICB9XG4gICAgICAgIGlmIChjb25maWcucmVwZWF0KSB7XG4gICAgICAgICAgb2Zmc2V0SGVpZ2h0ID0gb2Zmc2V0SGVpZ2h0IHx8IGNvbmZpZy5oZWlnaHRDb250YWluZXIgfHwgY29uZmlnLnN0eWxlTWFyZ2luQm90dG9tIHx8IDA7XG4gICAgICAgICAgY29uZmlnLmhlaWdodENvbnRhaW5lciA9IG9mZnNldEhlaWdodCAvIGNvbmZpZy5yZXBlYXQgLSAoY29uZmlnLnN0eWxlTWFyZ2luQm90dG9tIHx8IDApO1xuICAgICAgICB9XG4gICAgICAgIGNvbmZpZy5yZXBlYXQgPSBNYXRoLmNlaWwob2Zmc2V0SGVpZ2h0IC8gKChjb25maWcuaGVpZ2h0Q29udGFpbmVyIHx8IDApICsgKGNvbmZpZy5zdHlsZU1hcmdpbkJvdHRvbSB8fCAwKSkpO1xuXG4gICAgICAgIHJldHVybiB7IC4uLmNvbmZpZyB9O1xuICAgICAgfSk7XG4gICAgfSk7XG4gIH1cbn1cbiIsIjxkaXZcbiAgI3NrZWxldG9uUmVmXG4gIGNsYXNzPVwidy1mdWxsIGgtZnVsbFwiPlxuICBAaWYgKGNvbmZpZ1JlbmRlcigpOyBhcyBjb25maWcpIHtcbiAgICBAZm9yIChjb25maWdSZXBlYXQgb2YgW10uY29uc3RydWN0b3IoY29uZmlnLnJlcGVhdCB8fCAxKTsgdHJhY2sgJGluZGV4OyBsZXQgbGFzdEl0ZW0gPSAkbGFzdCkge1xuICAgICAgPGRpdlxuICAgICAgICBbc3R5bGUubWFyZ2luQm90dG9tLnB4XT1cImNvbmZpZy5zdHlsZU1hcmdpbkJvdHRvbSAmJiAhbGFzdEl0ZW0gPyBjb25maWcuc3R5bGVNYXJnaW5Cb3R0b20gOiAwXCJcbiAgICAgICAgW3N0eWxlLmhlaWdodF09XCJjb25maWcuaGVpZ2h0Q29udGFpbmVyID8gY29uZmlnLmhlaWdodENvbnRhaW5lciArICdweCcgOiAnMTAwJSdcIlxuICAgICAgICBbY2xhc3NdPVwiKGNvbmZpZy5jbGFzc1Jvd3MgfHwgJycpICsgJyBmbGV4IGZsZXgtY29sJ1wiPlxuICAgICAgICBAZm9yIChyb3cgb2YgY29uZmlnLnJvd3M7IHRyYWNrICRpbmRleCkge1xuICAgICAgICAgIEBmb3IgKHJvd1JlcGVhdCBvZiBbXS5jb25zdHJ1Y3Rvcihyb3cucmVwZWF0IHx8IDEpOyB0cmFjayAkaW5kZXg7IGxldCBmaXJzdFJvdyA9ICRmaXJzdDsgbGV0IGxhc3RSb3cgPSAkbGFzdCkge1xuICAgICAgICAgICAgPGRpdiBbY2xhc3NdPVwiKChsYXN0Um93IHx8IChmaXJzdFJvdyAmJiBsYXN0Um93KSA/IHJvdy5jbGFzc1Jvd0xhc3QgOiByb3cuY2xhc3NSb3cpIHx8ICcnKSArICcgZmxleCBmbGV4LWNvbCB3LWZ1bGwgaC1mdWxsJ1wiPlxuICAgICAgICAgICAgICBAaWYgKHJvdy5pdGVtKSB7XG4gICAgICAgICAgICAgICAgPG5nLWNvbnRhaW5lciAqbmdUZW1wbGF0ZU91dGxldD1cInRlbXBsYXRlU2tlbGV0b247IGNvbnRleHQ6IHsgY29uZmlnOiByb3cuaXRlbSB9XCIgLz5cbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICA8ZGl2IFtjbGFzc109XCIoKGxhc3RSb3cgfHwgKGZpcnN0Um93ICYmIGxhc3RSb3cpID8gcm93LmNsYXNzQ29sc0xhc3QgOiByb3cuY2xhc3NDb2xzKSB8fCAnJykgKyAnZmxleCB3LWZ1bGwnXCI+XG4gICAgICAgICAgICAgICAgQGZvciAoY29sIG9mIHJvdy5jb2xzOyB0cmFjayAkaW5kZXgpIHtcbiAgICAgICAgICAgICAgICAgIEBmb3IgKGNvbFJlcGVhdCBvZiBbXS5jb25zdHJ1Y3Rvcihjb2wucmVwZWF0IHx8IDEpOyB0cmFjayAkaW5kZXg7IGxldCBmaXJzdENvbCA9ICRmaXJzdDsgbGV0IGxhc3RDb2wgPSAkbGFzdCkge1xuICAgICAgICAgICAgICAgICAgICA8ZGl2IFtjbGFzc109XCIoKGxhc3RDb2wgfHwgKGZpcnN0Q29sICYmIGxhc3RDb2wpID8gY29sLmNsYXNzQ29sTGFzdCA6IGNvbC5jbGFzc0NvbCkgfHwgJycpICsgJyBmbGV4IHctZnVsbCdcIj5cbiAgICAgICAgICAgICAgICAgICAgICBAaWYgKGNvbC5pdGVtKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICA8bmctY29udGFpbmVyICpuZ1RlbXBsYXRlT3V0bGV0PVwidGVtcGxhdGVTa2VsZXRvbjsgY29udGV4dDogeyBjb25maWc6IGNvbC5pdGVtIH1cIiAvPlxuICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgPC9kaXY+XG4gICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICA8L2Rpdj5cbiAgICAgICAgICAgIDwvZGl2PlxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgPC9kaXY+XG4gICAgfVxuICB9XG48L2Rpdj5cblxuPG5nLXRlbXBsYXRlXG4gICN0ZW1wbGF0ZVNrZWxldG9uXG4gIGxldC1jb25maWc9XCJjb25maWdcIj5cbiAgPGRpdiBbY2xhc3NdPVwiY29uZmlnPy5jbGFzc0luY2x1ZGUgfHwgJ3ctZnVsbCBoLWZ1bGwnXCI+XG4gICAgQGZvciAoXyBvZiBbXS5jb25zdHJ1Y3Rvcihjb25maWcucmVwZWF0IHx8IDEpOyB0cmFjayAkaW5kZXg7IGxldCBsYXN0ID0gJGxhc3QpIHtcbiAgICAgIDxkaXZcbiAgICAgICAgY2xhc3M9XCJ3LWZ1bGwgaC1mdWxsIHNrZWxldG9uIHJvdW5kZWQtWzhweF0ge3sgY29uZmlnPy5jbGFzc0luY2x1ZGVJdGVtIHx8ICcnIH19XCJcbiAgICAgICAgW2NsYXNzLm1iLVsxNnB4XV09XCJjb25maWc/LnN0eWxlRGVmYXVsdCAmJiAhbGFzdFwiPjwvZGl2PlxuICAgIH1cbiAgPC9kaXY+XG48L25nLXRlbXBsYXRlPlxuIl19
42
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2tlbGV0b24uY29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vbGlicy11aS9jb21wb25lbnRzL3NrZWxldG9uL3NyYy9za2VsZXRvbi5jb21wb25lbnQudHMiLCIuLi8uLi8uLi8uLi8uLi9saWJzLXVpL2NvbXBvbmVudHMvc2tlbGV0b24vc3JjL3NrZWxldG9uLmNvbXBvbmVudC5odG1sIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQ25ELE9BQU8sRUFBaUIsdUJBQXVCLEVBQUUsU0FBUyxFQUFFLFVBQVUsRUFBYyxNQUFNLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxTQUFTLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDNUksT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDaEUsT0FBTyxFQUFFLFlBQVksRUFBRSxTQUFTLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxNQUFNLE1BQU0sQ0FBQzs7QUFZN0QsTUFBTSxPQUFPLGlDQUFpQztJQUNsQyxZQUFZLEdBQUcsTUFBTSxDQUFrQixFQUFFLENBQUMsQ0FBQztJQUU1QyxNQUFNLEdBQUcsS0FBSyxDQUErQyxFQUFFLEVBQUUsRUFBRSxTQUFTLEVBQUUsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLEtBQUssSUFBSSxFQUFFLEVBQUUsQ0FBQyxDQUFDO0lBRXhHLFdBQVcsR0FBRyxTQUFTLENBQUMsUUFBUSxDQUFhLGFBQWEsQ0FBQyxDQUFDO0lBRXBELFVBQVUsR0FBRyxNQUFNLENBQUMsVUFBVSxDQUFDLENBQUM7SUFFakQsZUFBZTtRQUNiLGdHQUFnRztRQUNoRyxLQUFLLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxFQUFFLFNBQVMsQ0FBQyxNQUFNLEVBQUUsUUFBUSxDQUFDLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO2FBQ25FLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUM7YUFDekMsU0FBUyxDQUFDLEdBQUcsRUFBRSxDQUFDLElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FBQyxDQUFDO0lBQ3hDLENBQUM7SUFFTyxVQUFVO1FBQ2hCLElBQUksQ0FBQyxZQUFZLENBQUMsTUFBTSxDQUFDLENBQUMsTUFBTSxFQUFFLEVBQUU7WUFDbEMsTUFBTSxHQUFHLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUN2QixJQUFJLFlBQVksR0FBRyxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUMsYUFBYSxDQUFDLFlBQVksQ0FBQztZQUNqRSxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksRUFBRSxNQUFNLEVBQUUsQ0FBQztnQkFDekIsTUFBTSxDQUFDLElBQUksR0FBRyxDQUFDLEVBQUUsSUFBSSxFQUFFLEVBQUUsRUFBRSxDQUFDLENBQUM7WUFDL0IsQ0FBQztZQUNELElBQUksQ0FBQyxNQUFNLENBQUMsZUFBZSxFQUFFLENBQUM7Z0JBQzVCLE1BQU0sQ0FBQyxlQUFlLEdBQUcsWUFBWSxHQUFHLENBQUMsTUFBTSxDQUFDLGlCQUFpQixJQUFJLENBQUMsQ0FBQyxDQUFDO1lBQzFFLENBQUM7WUFDRCxJQUFJLE1BQU0sQ0FBQyxNQUFNLEVBQUUsQ0FBQztnQkFDbEIsWUFBWSxHQUFHLFlBQVksSUFBSSxNQUFNLENBQUMsZUFBZSxJQUFJLE1BQU0sQ0FBQyxpQkFBaUIsSUFBSSxDQUFDLENBQUM7Z0JBQ3ZGLE1BQU0sQ0FBQyxlQUFlLEdBQUcsWUFBWSxHQUFHLE1BQU0sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxNQUFNLENBQUMsaUJBQWlCLElBQUksQ0FBQyxDQUFDLENBQUM7WUFDMUYsQ0FBQztZQUNELE1BQU0sQ0FBQyxNQUFNLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxZQUFZLEdBQUcsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxlQUFlLElBQUksQ0FBQyxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsaUJBQWlCLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBRTVHLE9BQU8sRUFBRSxHQUFHLE1BQU0sRUFBRSxDQUFDO1FBQ3ZCLENBQUMsQ0FBQyxDQUFDO0lBQ0wsQ0FBQzt3R0FsQ1UsaUNBQWlDOzRGQUFqQyxpQ0FBaUMsMFZDZjlDLCtsRUE2Q0EsMFREakNZLGdCQUFnQjs7NEZBR2YsaUNBQWlDO2tCQVQ3QyxTQUFTOytCQUVFLDZCQUE2QixjQUczQixJQUFJLFdBQ1AsQ0FBQyxnQkFBZ0IsQ0FBQyxtQkFDVix1QkFBdUIsQ0FBQyxNQUFNIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgTmdUZW1wbGF0ZU91dGxldCB9IGZyb20gJ0Bhbmd1bGFyL2NvbW1vbic7XG5pbXBvcnQgeyBBZnRlclZpZXdJbml0LCBDaGFuZ2VEZXRlY3Rpb25TdHJhdGVneSwgQ29tcG9uZW50LCBEZXN0cm95UmVmLCBFbGVtZW50UmVmLCBpbmplY3QsIGlucHV0LCBzaWduYWwsIHZpZXdDaGlsZCB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgdGFrZVVudGlsRGVzdHJveWVkIH0gZnJvbSAnQGFuZ3VsYXIvY29yZS9yeGpzLWludGVyb3AnO1xuaW1wb3J0IHsgZGVib3VuY2VUaW1lLCBmcm9tRXZlbnQsIG1lcmdlLCB0aW1lciB9IGZyb20gJ3J4anMnO1xuaW1wb3J0IHsgSVNrZWxldG9uQ29uZmlnIH0gZnJvbSAnLi9pbnRlcmZhY2VzL3NrZWxldG9uLWl0ZW0uaW50ZXJmYWNlJztcblxuQENvbXBvbmVudCh7XG4gIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAYW5ndWxhci1lc2xpbnQvY29tcG9uZW50LXNlbGVjdG9yXG4gIHNlbGVjdG9yOiAnbGlic191aS1jb21wb25lbnRzLXNrZWxldG9uJyxcbiAgdGVtcGxhdGVVcmw6ICdza2VsZXRvbi5jb21wb25lbnQuaHRtbCcsXG4gIHN0eWxlVXJsczogWycuL3NrZWxldG9uLmNvbXBvbmVudC5zY3NzJ10sXG4gIHN0YW5kYWxvbmU6IHRydWUsXG4gIGltcG9ydHM6IFtOZ1RlbXBsYXRlT3V0bGV0XSxcbiAgY2hhbmdlRGV0ZWN0aW9uOiBDaGFuZ2VEZXRlY3Rpb25TdHJhdGVneS5PblB1c2gsXG59KVxuZXhwb3J0IGNsYXNzIExpYnNVaUNvbXBvbmVudHNTa2VsZXRvbkNvbXBvbmVudCBpbXBsZW1lbnRzIEFmdGVyVmlld0luaXQge1xuICBwcm90ZWN0ZWQgY29uZmlnUmVuZGVyID0gc2lnbmFsPElTa2VsZXRvbkNvbmZpZz4oe30pO1xuXG4gIHJlYWRvbmx5IGNvbmZpZyA9IGlucHV0PElTa2VsZXRvbkNvbmZpZywgSVNrZWxldG9uQ29uZmlnIHwgdW5kZWZpbmVkPih7fSwgeyB0cmFuc2Zvcm06ICh2YWx1ZSkgPT4gdmFsdWUgfHwge30gfSk7XG5cbiAgcmVhZG9ubHkgc2tlbGV0b25SZWYgPSB2aWV3Q2hpbGQucmVxdWlyZWQ8RWxlbWVudFJlZj4oJ3NrZWxldG9uUmVmJyk7XG5cbiAgcHJpdmF0ZSByZWFkb25seSBkZXN0cm95UmVmID0gaW5qZWN0KERlc3Ryb3lSZWYpO1xuXG4gIG5nQWZ0ZXJWaWV3SW5pdCgpOiB2b2lkIHtcbiAgICAvLyBUw61uaCAxIGzhuqduIHNhdSAxMDBtcyAoY2jhu50gbGF5b3V0IOG7lW4gxJHhu4tuaCDEkeG7gyDEkeG7jWMgb2Zmc2V0SGVpZ2h0IMSRw7puZykgKyB0w61uaCBs4bqhaSBt4buXaSBraGkgcmVzaXplLlxuICAgIG1lcmdlKHRpbWVyKDEwMCksIGZyb21FdmVudCh3aW5kb3csICdyZXNpemUnKS5waXBlKGRlYm91bmNlVGltZSgyMDApKSlcbiAgICAgIC5waXBlKHRha2VVbnRpbERlc3Ryb3llZCh0aGlzLmRlc3Ryb3lSZWYpKVxuICAgICAgLnN1YnNjcmliZSgoKSA9PiB0aGlzLmNhbGN1bGF0b3IoKSk7XG4gIH1cblxuICBwcml2YXRlIGNhbGN1bGF0b3IoKTogdm9pZCB7XG4gICAgdGhpcy5jb25maWdSZW5kZXIudXBkYXRlKChjb25maWcpID0+IHtcbiAgICAgIGNvbmZpZyA9IHRoaXMuY29uZmlnKCk7XG4gICAgICBsZXQgb2Zmc2V0SGVpZ2h0ID0gdGhpcy5za2VsZXRvblJlZigpLm5hdGl2ZUVsZW1lbnQub2Zmc2V0SGVpZ2h0O1xuICAgICAgaWYgKCFjb25maWcucm93cz8ubGVuZ3RoKSB7XG4gICAgICAgIGNvbmZpZy5yb3dzID0gW3sgaXRlbToge30gfV07XG4gICAgICB9XG4gICAgICBpZiAoIWNvbmZpZy5oZWlnaHRDb250YWluZXIpIHtcbiAgICAgICAgY29uZmlnLmhlaWdodENvbnRhaW5lciA9IG9mZnNldEhlaWdodCAtIChjb25maWcuc3R5bGVNYXJnaW5Cb3R0b20gfHwgMCk7XG4gICAgICB9XG4gICAgICBpZiAoY29uZmlnLnJlcGVhdCkge1xuICAgICAgICBvZmZzZXRIZWlnaHQgPSBvZmZzZXRIZWlnaHQgfHwgY29uZmlnLmhlaWdodENvbnRhaW5lciB8fCBjb25maWcuc3R5bGVNYXJnaW5Cb3R0b20gfHwgMDtcbiAgICAgICAgY29uZmlnLmhlaWdodENvbnRhaW5lciA9IG9mZnNldEhlaWdodCAvIGNvbmZpZy5yZXBlYXQgLSAoY29uZmlnLnN0eWxlTWFyZ2luQm90dG9tIHx8IDApO1xuICAgICAgfVxuICAgICAgY29uZmlnLnJlcGVhdCA9IE1hdGguY2VpbChvZmZzZXRIZWlnaHQgLyAoKGNvbmZpZy5oZWlnaHRDb250YWluZXIgfHwgMCkgKyAoY29uZmlnLnN0eWxlTWFyZ2luQm90dG9tIHx8IDApKSk7XG5cbiAgICAgIHJldHVybiB7IC4uLmNvbmZpZyB9O1xuICAgIH0pO1xuICB9XG59XG4iLCI8ZGl2XG4gICNza2VsZXRvblJlZlxuICBjbGFzcz1cInctZnVsbCBoLWZ1bGxcIj5cbiAgQGlmIChjb25maWdSZW5kZXIoKTsgYXMgY29uZmlnKSB7XG4gICAgQGZvciAoY29uZmlnUmVwZWF0IG9mIFtdLmNvbnN0cnVjdG9yKGNvbmZpZy5yZXBlYXQgfHwgMSk7IHRyYWNrICRpbmRleDsgbGV0IGxhc3RJdGVtID0gJGxhc3QpIHtcbiAgICAgIDxkaXZcbiAgICAgICAgW3N0eWxlLm1hcmdpbkJvdHRvbS5weF09XCJjb25maWcuc3R5bGVNYXJnaW5Cb3R0b20gJiYgIWxhc3RJdGVtID8gY29uZmlnLnN0eWxlTWFyZ2luQm90dG9tIDogMFwiXG4gICAgICAgIFtzdHlsZS5oZWlnaHRdPVwiY29uZmlnLmhlaWdodENvbnRhaW5lciA/IGNvbmZpZy5oZWlnaHRDb250YWluZXIgKyAncHgnIDogJzEwMCUnXCJcbiAgICAgICAgW2NsYXNzXT1cIihjb25maWcuY2xhc3NSb3dzIHx8ICcnKSArICcgZmxleCBmbGV4LWNvbCdcIj5cbiAgICAgICAgQGZvciAocm93IG9mIGNvbmZpZy5yb3dzOyB0cmFjayAkaW5kZXgpIHtcbiAgICAgICAgICBAZm9yIChyb3dSZXBlYXQgb2YgW10uY29uc3RydWN0b3Iocm93LnJlcGVhdCB8fCAxKTsgdHJhY2sgJGluZGV4OyBsZXQgZmlyc3RSb3cgPSAkZmlyc3Q7IGxldCBsYXN0Um93ID0gJGxhc3QpIHtcbiAgICAgICAgICAgIDxkaXYgW2NsYXNzXT1cIigobGFzdFJvdyB8fCAoZmlyc3RSb3cgJiYgbGFzdFJvdykgPyByb3cuY2xhc3NSb3dMYXN0IDogcm93LmNsYXNzUm93KSB8fCAnJykgKyAnIGZsZXggZmxleC1jb2wgdy1mdWxsIGgtZnVsbCdcIj5cbiAgICAgICAgICAgICAgQGlmIChyb3cuaXRlbSkge1xuICAgICAgICAgICAgICAgIDxuZy1jb250YWluZXIgKm5nVGVtcGxhdGVPdXRsZXQ9XCJ0ZW1wbGF0ZVNrZWxldG9uOyBjb250ZXh0OiB7IGNvbmZpZzogcm93Lml0ZW0gfVwiIC8+XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgPGRpdiBbY2xhc3NdPVwiKChsYXN0Um93IHx8IChmaXJzdFJvdyAmJiBsYXN0Um93KSA/IHJvdy5jbGFzc0NvbHNMYXN0IDogcm93LmNsYXNzQ29scykgfHwgJycpICsgJ2ZsZXggdy1mdWxsJ1wiPlxuICAgICAgICAgICAgICAgIEBmb3IgKGNvbCBvZiByb3cuY29sczsgdHJhY2sgJGluZGV4KSB7XG4gICAgICAgICAgICAgICAgICBAZm9yIChjb2xSZXBlYXQgb2YgW10uY29uc3RydWN0b3IoY29sLnJlcGVhdCB8fCAxKTsgdHJhY2sgJGluZGV4OyBsZXQgZmlyc3RDb2wgPSAkZmlyc3Q7IGxldCBsYXN0Q29sID0gJGxhc3QpIHtcbiAgICAgICAgICAgICAgICAgICAgPGRpdiBbY2xhc3NdPVwiKChsYXN0Q29sIHx8IChmaXJzdENvbCAmJiBsYXN0Q29sKSA/IGNvbC5jbGFzc0NvbExhc3QgOiBjb2wuY2xhc3NDb2wpIHx8ICcnKSArICcgZmxleCB3LWZ1bGwnXCI+XG4gICAgICAgICAgICAgICAgICAgICAgQGlmIChjb2wuaXRlbSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgPG5nLWNvbnRhaW5lciAqbmdUZW1wbGF0ZU91dGxldD1cInRlbXBsYXRlU2tlbGV0b247IGNvbnRleHQ6IHsgY29uZmlnOiBjb2wuaXRlbSB9XCIgLz5cbiAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIDwvZGl2PlxuICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgPC9kaXY+XG4gICAgICAgICAgICA8L2Rpdj5cbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIDwvZGl2PlxuICAgIH1cbiAgfVxuPC9kaXY+XG5cbjxuZy10ZW1wbGF0ZVxuICAjdGVtcGxhdGVTa2VsZXRvblxuICBsZXQtY29uZmlnPVwiY29uZmlnXCI+XG4gIDxkaXYgW2NsYXNzXT1cImNvbmZpZz8uY2xhc3NJbmNsdWRlIHx8ICd3LWZ1bGwgaC1mdWxsJ1wiPlxuICAgIEBmb3IgKF8gb2YgW10uY29uc3RydWN0b3IoY29uZmlnLnJlcGVhdCB8fCAxKTsgdHJhY2sgJGluZGV4OyBsZXQgbGFzdCA9ICRsYXN0KSB7XG4gICAgICA8ZGl2XG4gICAgICAgIGNsYXNzPVwidy1mdWxsIGgtZnVsbCBza2VsZXRvbiByb3VuZGVkLVs4cHhdIHt7IGNvbmZpZz8uY2xhc3NJbmNsdWRlSXRlbSB8fCAnJyB9fVwiXG4gICAgICAgIFtjbGFzcy5tYi1bMTZweF1dPVwiY29uZmlnPy5zdHlsZURlZmF1bHQgJiYgIWxhc3RcIj48L2Rpdj5cbiAgICB9XG4gIDwvZGl2PlxuPC9uZy10ZW1wbGF0ZT5cbiJdfQ==
@@ -1,29 +1,36 @@
1
1
  import { NgTemplateOutlet } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { signal, input, viewChild, Component, ChangeDetectionStrategy } from '@angular/core';
3
+ import { signal, input, viewChild, inject, DestroyRef, Component, ChangeDetectionStrategy } from '@angular/core';
4
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
5
+ import { merge, timer, fromEvent, debounceTime } from 'rxjs';
4
6
 
5
7
  class LibsUiComponentsSkeletonComponent {
6
8
  configRender = signal({});
7
9
  config = input({}, { transform: (value) => value || {} });
8
10
  skeletonRef = viewChild.required('skeletonRef');
11
+ destroyRef = inject(DestroyRef);
9
12
  ngAfterViewInit() {
10
- setTimeout(() => {
11
- this.configRender.update((config) => {
12
- config = this.config();
13
- let offsetHeight = this.skeletonRef().nativeElement.offsetHeight;
14
- if (!config.rows?.length) {
15
- config.rows = [{ item: {} }];
16
- }
17
- if (!config.heightContainer) {
18
- config.heightContainer = offsetHeight - (config.styleMarginBottom || 0);
19
- }
20
- if (config.repeat) {
21
- offsetHeight = offsetHeight || config.heightContainer || config.styleMarginBottom || 0;
22
- config.heightContainer = offsetHeight / config.repeat - (config.styleMarginBottom || 0);
23
- }
24
- config.repeat = Math.ceil(offsetHeight / ((config.heightContainer || 0) + (config.styleMarginBottom || 0)));
25
- return { ...config };
26
- });
13
+ // Tính 1 lần sau 100ms (chờ layout ổn định để đọc offsetHeight đúng) + tính lại mỗi khi resize.
14
+ merge(timer(100), fromEvent(window, 'resize').pipe(debounceTime(200)))
15
+ .pipe(takeUntilDestroyed(this.destroyRef))
16
+ .subscribe(() => this.calculator());
17
+ }
18
+ calculator() {
19
+ this.configRender.update((config) => {
20
+ config = this.config();
21
+ let offsetHeight = this.skeletonRef().nativeElement.offsetHeight;
22
+ if (!config.rows?.length) {
23
+ config.rows = [{ item: {} }];
24
+ }
25
+ if (!config.heightContainer) {
26
+ config.heightContainer = offsetHeight - (config.styleMarginBottom || 0);
27
+ }
28
+ if (config.repeat) {
29
+ offsetHeight = offsetHeight || config.heightContainer || config.styleMarginBottom || 0;
30
+ config.heightContainer = offsetHeight / config.repeat - (config.styleMarginBottom || 0);
31
+ }
32
+ config.repeat = Math.ceil(offsetHeight / ((config.heightContainer || 0) + (config.styleMarginBottom || 0)));
33
+ return { ...config };
27
34
  });
28
35
  }
29
36
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiComponentsSkeletonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
@@ -1 +1 @@
1
- {"version":3,"file":"libs-ui-components-skeleton.mjs","sources":["../../../../../libs-ui/components/skeleton/src/skeleton.component.ts","../../../../../libs-ui/components/skeleton/src/skeleton.component.html","../../../../../libs-ui/components/skeleton/src/libs-ui-components-skeleton.ts"],"sourcesContent":["import { NgTemplateOutlet } from '@angular/common';\nimport { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, input, signal, viewChild } from '@angular/core';\nimport { ISkeletonConfig } from './interfaces/skeleton-item.interface';\n\n@Component({\n // eslint-disable-next-line @angular-eslint/component-selector\n selector: 'libs_ui-components-skeleton',\n templateUrl: 'skeleton.component.html',\n styleUrls: ['./skeleton.component.scss'],\n standalone: true,\n imports: [NgTemplateOutlet],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class LibsUiComponentsSkeletonComponent implements AfterViewInit {\n protected configRender = signal<ISkeletonConfig>({});\n\n readonly config = input<ISkeletonConfig, ISkeletonConfig | undefined>({}, { transform: (value) => value || {} });\n\n readonly skeletonRef = viewChild.required<ElementRef>('skeletonRef');\n\n ngAfterViewInit(): void {\n setTimeout(() => {\n this.configRender.update((config) => {\n config = this.config();\n let offsetHeight = this.skeletonRef().nativeElement.offsetHeight;\n if (!config.rows?.length) {\n config.rows = [{ item: {} }];\n }\n if (!config.heightContainer) {\n config.heightContainer = offsetHeight - (config.styleMarginBottom || 0);\n }\n if (config.repeat) {\n offsetHeight = offsetHeight || config.heightContainer || config.styleMarginBottom || 0;\n config.heightContainer = offsetHeight / config.repeat - (config.styleMarginBottom || 0);\n }\n config.repeat = Math.ceil(offsetHeight / ((config.heightContainer || 0) + (config.styleMarginBottom || 0)));\n\n return { ...config };\n });\n });\n }\n}\n","<div\n #skeletonRef\n class=\"w-full h-full\">\n @if (configRender(); as config) {\n @for (configRepeat of [].constructor(config.repeat || 1); track $index; let lastItem = $last) {\n <div\n [style.marginBottom.px]=\"config.styleMarginBottom && !lastItem ? config.styleMarginBottom : 0\"\n [style.height]=\"config.heightContainer ? config.heightContainer + 'px' : '100%'\"\n [class]=\"(config.classRows || '') + ' flex flex-col'\">\n @for (row of config.rows; track $index) {\n @for (rowRepeat of [].constructor(row.repeat || 1); track $index; let firstRow = $first; let lastRow = $last) {\n <div [class]=\"((lastRow || (firstRow && lastRow) ? row.classRowLast : row.classRow) || '') + ' flex flex-col w-full h-full'\">\n @if (row.item) {\n <ng-container *ngTemplateOutlet=\"templateSkeleton; context: { config: row.item }\" />\n }\n <div [class]=\"((lastRow || (firstRow && lastRow) ? row.classColsLast : row.classCols) || '') + 'flex w-full'\">\n @for (col of row.cols; track $index) {\n @for (colRepeat of [].constructor(col.repeat || 1); track $index; let firstCol = $first; let lastCol = $last) {\n <div [class]=\"((lastCol || (firstCol && lastCol) ? col.classColLast : col.classCol) || '') + ' flex w-full'\">\n @if (col.item) {\n <ng-container *ngTemplateOutlet=\"templateSkeleton; context: { config: col.item }\" />\n }\n </div>\n }\n }\n </div>\n </div>\n }\n }\n </div>\n }\n }\n</div>\n\n<ng-template\n #templateSkeleton\n let-config=\"config\">\n <div [class]=\"config?.classInclude || 'w-full h-full'\">\n @for (_ of [].constructor(config.repeat || 1); track $index; let last = $last) {\n <div\n class=\"w-full h-full skeleton rounded-[8px] {{ config?.classIncludeItem || '' }}\"\n [class.mb-[16px]]=\"config?.styleDefault && !last\"></div>\n }\n </div>\n</ng-template>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"libs-ui-components-skeleton.mjs","sources":["../../../../../libs-ui/components/skeleton/src/skeleton.component.ts","../../../../../libs-ui/components/skeleton/src/skeleton.component.html","../../../../../libs-ui/components/skeleton/src/libs-ui-components-skeleton.ts"],"sourcesContent":["import { NgTemplateOutlet } from '@angular/common';\nimport { AfterViewInit, ChangeDetectionStrategy, Component, DestroyRef, ElementRef, inject, input, signal, viewChild } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { debounceTime, fromEvent, merge, timer } from 'rxjs';\nimport { ISkeletonConfig } from './interfaces/skeleton-item.interface';\n\n@Component({\n // eslint-disable-next-line @angular-eslint/component-selector\n selector: 'libs_ui-components-skeleton',\n templateUrl: 'skeleton.component.html',\n styleUrls: ['./skeleton.component.scss'],\n standalone: true,\n imports: [NgTemplateOutlet],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class LibsUiComponentsSkeletonComponent implements AfterViewInit {\n protected configRender = signal<ISkeletonConfig>({});\n\n readonly config = input<ISkeletonConfig, ISkeletonConfig | undefined>({}, { transform: (value) => value || {} });\n\n readonly skeletonRef = viewChild.required<ElementRef>('skeletonRef');\n\n private readonly destroyRef = inject(DestroyRef);\n\n ngAfterViewInit(): void {\n // Tính 1 lần sau 100ms (chờ layout ổn định để đọc offsetHeight đúng) + tính lại mỗi khi resize.\n merge(timer(100), fromEvent(window, 'resize').pipe(debounceTime(200)))\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe(() => this.calculator());\n }\n\n private calculator(): void {\n this.configRender.update((config) => {\n config = this.config();\n let offsetHeight = this.skeletonRef().nativeElement.offsetHeight;\n if (!config.rows?.length) {\n config.rows = [{ item: {} }];\n }\n if (!config.heightContainer) {\n config.heightContainer = offsetHeight - (config.styleMarginBottom || 0);\n }\n if (config.repeat) {\n offsetHeight = offsetHeight || config.heightContainer || config.styleMarginBottom || 0;\n config.heightContainer = offsetHeight / config.repeat - (config.styleMarginBottom || 0);\n }\n config.repeat = Math.ceil(offsetHeight / ((config.heightContainer || 0) + (config.styleMarginBottom || 0)));\n\n return { ...config };\n });\n }\n}\n","<div\n #skeletonRef\n class=\"w-full h-full\">\n @if (configRender(); as config) {\n @for (configRepeat of [].constructor(config.repeat || 1); track $index; let lastItem = $last) {\n <div\n [style.marginBottom.px]=\"config.styleMarginBottom && !lastItem ? config.styleMarginBottom : 0\"\n [style.height]=\"config.heightContainer ? config.heightContainer + 'px' : '100%'\"\n [class]=\"(config.classRows || '') + ' flex flex-col'\">\n @for (row of config.rows; track $index) {\n @for (rowRepeat of [].constructor(row.repeat || 1); track $index; let firstRow = $first; let lastRow = $last) {\n <div [class]=\"((lastRow || (firstRow && lastRow) ? row.classRowLast : row.classRow) || '') + ' flex flex-col w-full h-full'\">\n @if (row.item) {\n <ng-container *ngTemplateOutlet=\"templateSkeleton; context: { config: row.item }\" />\n }\n <div [class]=\"((lastRow || (firstRow && lastRow) ? row.classColsLast : row.classCols) || '') + 'flex w-full'\">\n @for (col of row.cols; track $index) {\n @for (colRepeat of [].constructor(col.repeat || 1); track $index; let firstCol = $first; let lastCol = $last) {\n <div [class]=\"((lastCol || (firstCol && lastCol) ? col.classColLast : col.classCol) || '') + ' flex w-full'\">\n @if (col.item) {\n <ng-container *ngTemplateOutlet=\"templateSkeleton; context: { config: col.item }\" />\n }\n </div>\n }\n }\n </div>\n </div>\n }\n }\n </div>\n }\n }\n</div>\n\n<ng-template\n #templateSkeleton\n let-config=\"config\">\n <div [class]=\"config?.classInclude || 'w-full h-full'\">\n @for (_ of [].constructor(config.repeat || 1); track $index; let last = $last) {\n <div\n class=\"w-full h-full skeleton rounded-[8px] {{ config?.classIncludeItem || '' }}\"\n [class.mb-[16px]]=\"config?.styleDefault && !last\"></div>\n }\n </div>\n</ng-template>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@libs-ui/components-skeleton",
3
- "version": "0.2.357-1",
3
+ "version": "0.2.357-3",
4
4
  "peerDependencies": {
5
5
  "@angular/common": ">=18.0.0",
6
6
  "@angular/core": ">=18.0.0"
@@ -5,7 +5,9 @@ export declare class LibsUiComponentsSkeletonComponent implements AfterViewInit
5
5
  protected configRender: import("@angular/core").WritableSignal<ISkeletonConfig>;
6
6
  readonly config: import("@angular/core").InputSignalWithTransform<ISkeletonConfig, ISkeletonConfig | undefined>;
7
7
  readonly skeletonRef: import("@angular/core").Signal<ElementRef<any>>;
8
+ private readonly destroyRef;
8
9
  ngAfterViewInit(): void;
10
+ private calculator;
9
11
  static ɵfac: i0.ɵɵFactoryDeclaration<LibsUiComponentsSkeletonComponent, never>;
10
12
  static ɵcmp: i0.ɵɵComponentDeclaration<LibsUiComponentsSkeletonComponent, "libs_ui-components-skeleton", never, { "config": { "alias": "config"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
11
13
  }