@foxeltech/angular-ui 0.7.83 → 0.7.103

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 (27) hide show
  1. package/components.css +40 -1
  2. package/fesm2022/foxeltech-angular-ui-chart.mjs +127 -44
  3. package/fesm2022/foxeltech-angular-ui-chart.mjs.map +1 -1
  4. package/fesm2022/foxeltech-angular-ui-flow.mjs +42 -5
  5. package/fesm2022/foxeltech-angular-ui-flow.mjs.map +1 -1
  6. package/fesm2022/foxeltech-angular-ui.mjs +559 -42
  7. package/fesm2022/foxeltech-angular-ui.mjs.map +1 -1
  8. package/package.json +3 -2
  9. package/src/lib/styles/components.css +40 -1
  10. package/src/lib/styles/utilities.css +2 -1
  11. package/src/lib/ui/components/breadcrumb/breadcrumb.component.css +262 -0
  12. package/src/lib/ui/components/breadcrumb/breadcrumb.component.html +91 -0
  13. package/src/lib/ui/components/button/button.component.html +1 -0
  14. package/src/lib/ui/components/filter-pills/filter-pills.component.html +1 -1
  15. package/src/lib/ui/components/load-status/load-status.component.css +96 -0
  16. package/src/lib/ui/components/load-status/load-status.component.html +20 -0
  17. package/src/lib/ui/components/master-detail/master-detail.component.css +63 -11
  18. package/src/lib/ui/components/master-detail/master-detail.component.html +71 -42
  19. package/src/lib/ui/components/route-skeleton/route-skeleton.component.css +1 -1
  20. package/src/lib/ui/components/segmented/segmented.component.html +3 -1
  21. package/src/lib/ui/components/severity-badges/severity-badges.component.html +1 -1
  22. package/src/lib/ui/components/stat-cards/stat-cards.component.css +18 -0
  23. package/src/lib/ui/components/stat-cards/stat-cards.component.html +3 -3
  24. package/types/foxeltech-angular-ui-chart.d.ts +56 -22
  25. package/types/foxeltech-angular-ui-flow.d.ts +7 -0
  26. package/types/foxeltech-angular-ui.d.ts +309 -8
  27. package/utilities.css +2 -1
@@ -1,40 +1,67 @@
1
- <!-- 3 cụm CHUNG chiều cao, mỗi cụm SCROLL RIÊNG (anh chốt 03/09).
2
- `items-stretch` + chiều cao đặt trên hàng (xem .fx-md-row trong CSS) để
3
- cả ba pane bằng nhau; mỗi pane flex-col + min-h-0 chỉ phần thân
4
- `overflow-auto flex-1` cuộn header/filter/paginator đứng yên.
5
- ⚠️ Hướng hàng + chiều cao do CONTAINER QUERY trong CSS quyết định (theo
6
- bề rộng của chính hàng), KHÔNG phải `lg:flex-row`. Đừng thêm lại class
7
- `lg:*` ở đây — `lg` trong lib là 1080px và viewport là proxy sai; xem
8
- ghi chú đầu master-detail.component.css. -->
1
+ <!-- ONE frame, three columns (designs 11/14/17): facets rail · list · detail,
2
+ separated by hairlines, sharing one height; only each pane's body scrolls.
3
+ Row direction / height come from the CONTAINER QUERY in the CSS (the row's
4
+ own width), never from `lg:*`see the note at the top of the .css. -->
9
5
  <div
10
- class="fx-md-row flex flex-col items-stretch gap-xl"
6
+ class="fx-md-row flex flex-col items-stretch"
7
+ [class.fx-md-row--rail-open]="railOpen()"
11
8
  [style.--fx-md-list-width]="listWidth()"
12
9
  [style.--fx-md-pane-h]="paneHeight()"
13
10
  >
14
- <!-- Facets rail (V2 3-column lookup) wrapper always rendered so ng-content
15
- stays unconditional; hidden when the host projects nothing. -->
16
- <div
17
- class="fx-md-facets card-common bg-surface flex flex-col min-h-0"
11
+ <!-- Facets rail — collapsible (» / «), collapsed = 40px strip with the label
12
+ set vertically. The [mdFacets] projection is unconditional (hasFacets is
13
+ detected by querying it) and only hidden while collapsed. -->
14
+ <aside
15
+ class="fx-md-facets flex flex-col min-h-0 min-w-0"
18
16
  [class.hidden]="!hasFacets"
17
+ [class.fx-md-facets--open]="railOpen()"
19
18
  >
20
- <div class="flex-1 min-h-0 overflow-auto pr-xs">
19
+ <div class="fx-md-rail-head flex items-center gap-small px-normal py-semi border-b border-border-subtle shrink-0" [class.hidden]="!railOpen()">
20
+ <button
21
+ type="button"
22
+ class="flex items-center justify-center w-6 h-6 rounded-control text-muted hover:bg-bg-hover shrink-0"
23
+ [attr.aria-label]="collapseFacetsLabel()"
24
+ (click)="railOpen.set(false)"
25
+ >
26
+ <fx-ui-hero-icon icon="chevron-double-left" [size]="14" class="flex" />
27
+ </button>
28
+ <span class="txt-eyebrow truncate">{{ facetsLabel() }}</span>
29
+ </div>
30
+ <div class="flex-1 min-h-0 overflow-auto px-normal py-normal" [class.hidden]="!railOpen()">
21
31
  <ng-content select="[mdFacets]"></ng-content>
22
32
  </div>
23
- </div>
33
+ <button
34
+ type="button"
35
+ class="fx-md-rail-toggle flex items-center gap-normal text-muted hover:bg-bg-hover transition-colors"
36
+ [class.hidden]="railOpen()"
37
+ [attr.aria-label]="expandFacetsLabel()"
38
+ [attr.aria-expanded]="false"
39
+ (click)="railOpen.set(true)"
40
+ >
41
+ <fx-ui-hero-icon icon="chevron-double-right" [size]="14" class="flex shrink-0" />
42
+ <span class="fx-md-rail-label txt-eyebrow whitespace-nowrap">{{ facetsLabel() }}</span>
43
+ </button>
44
+ </aside>
24
45
 
25
46
  <!-- Master: list pane -->
26
- <div class="fx-md-list card-common flex flex-col min-h-0">
27
- <ng-content select="[mdHeader]"></ng-content>
28
- <ng-content select="[mdFilters]"></ng-content>
47
+ <section class="fx-md-list flex flex-col min-h-0 min-w-0">
48
+ <div class="fx-md-list-head px-large pt-large pb-small border-b border-border-subtle shrink-0">
49
+ <ng-content select="[mdHeader]"></ng-content>
50
+ <ng-content select="[mdFilters]"></ng-content>
51
+ </div>
29
52
  <div
30
- class="flex flex-col gap-normal overflow-auto pr-xs flex-1 min-h-0"
53
+ class="flex flex-col overflow-auto flex-1 min-h-0"
31
54
  role="listbox"
32
55
  [attr.aria-label]="listLabel() || null"
33
56
  >
34
57
  @if (table.loading) {
35
- <fx-ui-skeleton-list [rows]="table.pageSize"></fx-ui-skeleton-list>
58
+ <div class="px-large py-normal">
59
+ <fx-ui-skeleton-list [rows]="table.pageSize"></fx-ui-skeleton-list>
60
+ </div>
36
61
  } @else {
37
62
  @for (row of table.dataSource.data; track table.idOf(row); let i = $index) {
63
+ <!-- Hairline rows; the selected one is tinted with an accent bar on
64
+ the left (design 11/14 selected finding / component). -->
38
65
  <button
39
66
  type="button"
40
67
  role="option"
@@ -43,11 +70,11 @@
43
70
  [tabindex]="isSelected(row) ? 0 : -1"
44
71
  (click)="table.selectItem(row)"
45
72
  (keydown)="onKeydown($event, i)"
46
- class="text-left rounded-lg border p-semi transition-colors"
73
+ class="text-left px-large py-semi border-l-2 border-b border-b-border-subtle transition-colors"
47
74
  [ngClass]="
48
75
  isSelected(row)
49
- ? 'border-accent bg-accent-soft/60'
50
- : 'border-border-default hover:border-border-interactive bg-bg-primary'
76
+ ? 'border-l-accent bg-accent-soft/60'
77
+ : 'border-l-transparent hover:bg-surface-sunken'
51
78
  "
52
79
  >
53
80
  <ng-container
@@ -57,7 +84,9 @@
57
84
  </button>
58
85
  }
59
86
  @if (!table.dataSource.data.length) {
60
- <fx-ui-empty-state [message]="emptyMessage()"></fx-ui-empty-state>
87
+ <div class="px-large py-large">
88
+ <fx-ui-empty-state [message]="emptyMessage()"></fx-ui-empty-state>
89
+ </div>
61
90
  }
62
91
  }
63
92
  </div>
@@ -65,14 +94,14 @@
65
94
  whose own BaseTable subscription (running after this child's hook)
66
95
  wires page events — a binding here would double-fetch every change. -->
67
96
  <mat-paginator
68
- class="mt-normal"
97
+ class="fx-md-paginator"
69
98
  [class.fx-md-paginator-hidden]="paginationStyle() === 'prevnext'"
70
99
  [length]="table.total"
71
100
  [pageSize]="table.pageSize"
72
101
  [pageSizeOptions]="pageSizeOptions()"
73
102
  ></mat-paginator>
74
103
  @if (paginationStyle() === 'prevnext') {
75
- <div class="flex items-center justify-between mt-normal">
104
+ <div class="flex items-center justify-between gap-normal px-large py-normal border-t border-border-subtle shrink-0">
76
105
  <span class="txt-utility-description" style="font-family: var(--og-font-figure)">
77
106
  {{ rangeText() }}
78
107
  </span>
@@ -96,25 +125,25 @@
96
125
  </div>
97
126
  </div>
98
127
  }
99
- </div>
128
+ </section>
100
129
 
101
130
  <!-- Detail pane -->
102
- <div class="fx-md-detail w-full min-w-0 flex flex-col min-h-0">
103
- <div class="flex-1 min-h-0 overflow-auto">
104
- @if (table.selected) {
105
- @if (table.detailBusy) {
106
- <fx-ui-skeleton-detail [cards]="skeletonCards()"></fx-ui-skeleton-detail>
131
+ <section class="fx-md-detail w-full min-w-0 flex flex-col min-h-0">
132
+ <div class="flex-1 min-h-0 overflow-auto px-large py-large">
133
+ @if (table.selected) {
134
+ @if (table.detailBusy) {
135
+ <fx-ui-skeleton-detail [cards]="skeletonCards()"></fx-ui-skeleton-detail>
136
+ } @else {
137
+ <ng-container
138
+ [ngTemplateOutlet]="detailTemplate()"
139
+ [ngTemplateOutletContext]="{ $implicit: table.detail, selected: table.selected }"
140
+ ></ng-container>
141
+ }
107
142
  } @else {
108
- <ng-container
109
- [ngTemplateOutlet]="detailTemplate()"
110
- [ngTemplateOutletContext]="{ $implicit: table.detail, selected: table.selected }"
111
- ></ng-container>
143
+ <div class="flex items-center justify-center h-full min-h-[300px]">
144
+ <fx-ui-empty-state [message]="emptyMessage()"></fx-ui-empty-state>
145
+ </div>
112
146
  }
113
- } @else {
114
- <div class="card-common flex items-center justify-center h-full min-h-[300px]">
115
- <fx-ui-empty-state [message]="emptyMessage()"></fx-ui-empty-state>
116
- </div>
117
- }
118
147
  </div>
119
- </div>
148
+ </section>
120
149
  </div>
@@ -16,7 +16,7 @@
16
16
 
17
17
  /* Ngưỡng + bề rộng panes giữ y hệt master-detail để skeleton không nhảy hình
18
18
  khi dữ liệu về. */
19
- @container fx-rs (min-width: 968px) {
19
+ @container fx-rs (min-width: 720px) {
20
20
  .fx-rs-row {
21
21
  flex-direction: row;
22
22
  align-items: stretch;
@@ -14,6 +14,8 @@
14
14
  [style.transform]="thumbTransform()"
15
15
  ></span>
16
16
  }
17
+ <!-- option 30px tall (7px × 18px padding): the text-box trim below collapses the
18
+ glyph box to ~8px, so padding alone left the pill at 22px (anh Tú 04/09) -->
17
19
  @for (option of options(); track option.value) {
18
20
  <button
19
21
  #opt
@@ -21,7 +23,7 @@
21
23
  (click)="select(option)"
22
24
  [disabled]="option.disabled"
23
25
  [attr.aria-pressed]="option.value === value()"
24
- class="relative z-[1] flex min-w-0 items-center justify-center gap-xs px-semi py-[5px] rounded-full text-[13px] leading-none font-medium whitespace-nowrap transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
26
+ class="relative z-[1] flex min-w-0 min-h-[30px] items-center justify-center gap-xs px-[18px] py-[7px] rounded-full text-[13px] leading-none font-medium whitespace-nowrap transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
25
27
  [class]="optionClass(option)"
26
28
  >
27
29
  @if (option.icon) {
@@ -2,7 +2,7 @@
2
2
  @for (badge of badges; track badge.key) {
3
3
  <button
4
4
  type="button"
5
- class="px-2 py-0.5 rounded-full txt-utility-caption font-medium cursor-pointer focus-visible:ring-2 focus-visible:ring-primary/50"
5
+ class="px-2 py-[3px] rounded-full txt-utility-caption font-medium cursor-pointer focus-visible:ring-2 focus-visible:ring-primary/50"
6
6
  [ngClass]="badgeClass(badge)"
7
7
  [attr.aria-label]="badge.key + ': ' + badge.label"
8
8
  (click)="badgeClick.emit(badge.key)"
@@ -0,0 +1,18 @@
1
+ :host {
2
+ display: block;
3
+ container-type: inline-size;
4
+ }
5
+
6
+ /* Explicit `cols` only holds from md (720px) up; narrower containers fall back
7
+ to 2-up, then 1-up — figures stay readable on phones (anh Tú 04/09). The
8
+ inline style carries the wide layout, so the overrides need !important. */
9
+ @container (width < 720px) {
10
+ .fx-stat-grid {
11
+ grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
12
+ }
13
+ }
14
+ @container (width < 360px) {
15
+ .fx-stat-grid {
16
+ grid-template-columns: minmax(0, 1fr) !important;
17
+ }
18
+ }
@@ -1,4 +1,4 @@
1
- <div class="grid gap-normal" [ngStyle]="{ 'grid-template-columns': gridColumns }">
1
+ <div class="fx-stat-grid grid gap-normal" [ngStyle]="{ 'grid-template-columns': gridColumns }">
2
2
  @for (card of cards(); track card.label) {
3
3
  @if (variant() === 'figure') {
4
4
  <div class="card-common flex flex-col gap-small min-w-0">
@@ -7,7 +7,7 @@
7
7
  class="txt-heading-03"
8
8
  [ngClass]="numClass(card)"
9
9
  style="font-family: var(--og-font-figure)"
10
- >{{ card.value }}</span
10
+ ><span [fxCountUp]="card.value"></span></span
11
11
  >
12
12
  @if (card.sub) {
13
13
  <span class="txt-utility-caption" [ngClass]="subClass(card)">{{ card.sub }}</span>
@@ -32,7 +32,7 @@
32
32
  class="txt-heading-04"
33
33
  [ngClass]="numClass(card)"
34
34
  style="font-family: var(--og-font-figure)"
35
- >{{ card.value }}</span
35
+ ><span [fxCountUp]="card.value"></span></span
36
36
  >
37
37
  @if (card.delta) {
38
38
  <span class="text-xs font-semibold" [ngClass]="deltaClass(card)">
@@ -37,6 +37,8 @@ declare class ChartComponent implements OnChanges, AfterViewInit, OnDestroy {
37
37
  * dom.". Chỉ dựng directive khi đã có options thật ⇒ đúng một lần init.
38
38
  */
39
39
  hasOptions: boolean;
40
+ /** Heights (%) of the pre-init placeholder bars — a fixed, calm silhouette. */
41
+ readonly placeholderBars: number[];
40
42
  labelFontSize: number;
41
43
  private chartInstance?;
42
44
  private resizeObserver?;
@@ -62,6 +64,24 @@ declare class ChartComponent implements OnChanges, AfterViewInit, OnDestroy {
62
64
  private pendingFrames;
63
65
  private isMobile;
64
66
  private containerWidth;
67
+ private containerHeight;
68
+ /**
69
+ * Mốc (performance.now) mà animation "xuất hiện" của lần setOption gần nhất
70
+ * chắc chắn đã xong. ECharts mặc định animationDuration 1000ms; trong khoảng
71
+ * này KHÔNG được `resize()` — resize nhảy thẳng tới trạng thái cuối và giết
72
+ * animation (đo 05/09: chart PM bị cắt ở ~800ms khi thanh cuộn xuất hiện
73
+ * lúc lưới card nạp xong làm bề rộng đổi 720→710).
74
+ */
75
+ private appearUntil;
76
+ private static readonly APPEAR_MS;
77
+ private deferTimer?;
78
+ /**
79
+ * Thay đổi kích thước ghi nhận TRONG cửa sổ appear, chờ áp sau. `measure()`
80
+ * đã cập nhật containerWidth/Height ngay lúc đo, nên lần gọi hoãn không thể
81
+ * dựa vào "có đổi không" nữa — phải nhớ bằng cờ.
82
+ */
83
+ private pendingResize;
84
+ private pendingMobile;
65
85
  constructor(ref: ElementRef<HTMLElement>, cdr: ChangeDetectorRef);
66
86
  ngAfterViewInit(): void;
67
87
  ngOnChanges(): void;
@@ -93,30 +113,29 @@ declare class ChartComponent implements OnChanges, AfterViewInit, OnDestroy {
93
113
  onChartClick(event: any): void;
94
114
  onChartInit(raw: any): void;
95
115
  /**
96
- * @returns true nếu trạng thái responsive THỰC SỰ đổi (nên dựng lại options).
97
- *
98
- * Trả về boolean điều then chốt cho animation lúc chart xuất hiện — xem
99
- * ghi chú ở `forceMediaRecalc`.
116
+ * Đo host ghi lại; trả về CÁI đổi so với lần đo trước.
117
+ * Khi bị ẩn (0×0) thì KHÔNG ghi — giữ kích thước hiển thị cuối, để lúc hiện
118
+ * lại cùng cỡ thì coi như không đổi (không resize, không cắt animation).
100
119
  */
101
- private updateResponsiveState;
120
+ private measure;
121
+ /** Animation xuất hiện vừa bắt đầu — khoá resize tới khi nó xong. */
122
+ private markAppear;
123
+ /** Pie/sunburst đặt legend + số giữa theo px của containerWidth → phải dựng lại. */
124
+ private needsLayoutRebuild;
125
+ private deferResize;
102
126
  /**
103
- * ⚠️ CHỈ dựng lại options khi kích thước thật sự đổi nếu không thì
104
- * **animation lúc chart xuất hiện bị giết**.
127
+ * Phản ứng với kích thước host (RO, sau init, sau khi hiện lại).
105
128
  *
106
- * Đường đi: `onChartInit` schedule(forceMediaRecalc) chạy frame KẾ
107
- * TIẾP. Nếu chỗ này điều kiện `rebuildChart()` (clear + setOption notMerge)
108
- * thì xoá sạch vẽ lại từ đầu ~16ms sau lần vẽ đầu, trong khi animation
109
- * xuất hiện dài ~1000ms người dùng thấy chart hiện ra đứng im.
110
- *
111
- * Đo được: lần xuất hiện đầu 1/12 frame khác nhau (đứng yên) trong khi vẽ lại
112
- * bằng `theme-changed` cho 9/12 animation của ECharts vẫn bật, chỉ lần đầu
113
- * bị cắt.
114
- *
115
- * Trước Pha 0 lỗi này bị che: lần init đầu dùng `chartOptions = {}` nên
116
- * animation xuất hiện xảy ra ở lần cập nhật SAU, tức sau khi forceMediaRecalc
117
- * đã chạy xong. Cổng `hasOptions` đưa options thật về ngay lần init nên lộ ra.
118
- *
119
- * `resize()` thì luôn gọi được — nó không cắt animation.
129
+ * Ba quy tắc, đều đo được (0.7.75 05/09):
130
+ * 1. **Ẩn (0×H) thì không làm gì** không resize canvas về 0. Chart hiện
131
+ * lại cùng cỡ thì animation đã bắt đầu lúc ẩn (setOption khi resolver về,
132
+ * ngay trước NavigationEnd) chạy tiếp bình thường.
133
+ * 2. **`resize()` nhảy thẳng tới trạng thái cuối** ⇒ trong cửa sổ appear
134
+ * (`appearUntil`) thì HOÃN, đo lại xử sau khi animation xong. Không
135
+ * dựng lại (clear + setOption) lúc đó đó "vẽ 2 lần" theo cách khác.
136
+ * 3. Ngoài cửa sổ: `resize()` trước, rồi chỉ dựng lại khi preset đổi (mobile)
137
+ * hoặc loại chart đặt layout theo px (pie/sunburst). Thứ tự này quan
138
+ * trọng: resize SAU rebuild sẽ cắt ngay animation vừa bắt đầu.
120
139
  */
121
140
  private forceMediaRecalc;
122
141
  private rebuildChart;
@@ -136,5 +155,20 @@ declare class ChartComponent implements OnChanges, AfterViewInit, OnDestroy {
136
155
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<ChartComponent, "fx-ui-chart", never, { "type": { "alias": "type"; "required": false; "isSignal": true; }; "data": { "alias": "data"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "palette": { "alias": "palette"; "required": false; "isSignal": true; }; "height": { "alias": "height"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "pieLegendAlign": { "alias": "pieLegendAlign"; "required": false; "isSignal": true; }; "pieLegendPosition": { "alias": "pieLegendPosition"; "required": false; "isSignal": true; }; "showPieLabel": { "alias": "showPieLabel"; "required": false; "isSignal": true; }; "showPieLegend": { "alias": "showPieLegend"; "required": false; "isSignal": true; }; "pieCenterLabel": { "alias": "pieCenterLabel"; "required": false; "isSignal": true; }; "mobileWidth": { "alias": "mobileWidth"; "required": false; "isSignal": true; }; "labelFontFamily": { "alias": "labelFontFamily"; "required": false; "isSignal": true; }; "emptyText": { "alias": "emptyText"; "required": false; "isSignal": true; }; }, { "chartClick": "chartClick"; "chartInit": "chartInit"; }, never, never, true, never>;
137
156
  }
138
157
 
139
- export { ChartComponent };
158
+ /**
159
+ * Warm the echarts chunk before any chart is on screen.
160
+ *
161
+ * `fx-ui-chart` loads echarts lazily (`provideEchartsCore({ echarts: () =>
162
+ * import('echarts') })`), so the FIRST chart of a session pays the chunk
163
+ * download right when the user is looking at an empty panel. Call this once
164
+ * from an idle callback when the app's env injector is created (e.g. a
165
+ * `provideEnvironmentInitializer` in the remote's root route) and the dashboard
166
+ * charts appear on the frame they mount. Same module URL as the directive's
167
+ * import ⇒ one shared instance, no double load.
168
+ */
169
+ declare function preloadEcharts(): Promise<unknown>;
170
+ /** Schedule `preloadEcharts()` when the main thread is idle (SSR/tests: no-op). */
171
+ declare function preloadEchartsWhenIdle(timeoutMs?: number): void;
172
+
173
+ export { ChartComponent, preloadEcharts, preloadEchartsWhenIdle };
140
174
  export type { IChartType };
@@ -52,6 +52,13 @@ declare class FlowCanvasComponent {
52
52
  /** Caller-translated hint shown when the graph is empty. */
53
53
  readonly emptyText: _angular_core.InputSignal<string>;
54
54
  readonly nodeClick: _angular_core.OutputEmitterRef<string>;
55
+ private readonly flow;
56
+ private readonly host;
57
+ private readonly destroyRef;
58
+ constructor();
59
+ private fitHandle;
60
+ /** Coalesces fit requests into one frame after vflow has measured its nodes. */
61
+ private scheduleFit;
55
62
  /** Column index per node: pinned value, else longest path from a source. */
56
63
  private readonly columns;
57
64
  /** ngx-vflow nodes: html-template cards positioned by the derived layout. */