@sdcorejs/angular 20.1.2 → 20.1.4
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 +127 -807
- package/components/audit-diff/index.d.ts +74 -0
- package/components/badge/index.d.ts +1 -1
- package/components/breadcrumb/index.d.ts +53 -0
- package/components/data-state/index.d.ts +44 -0
- package/components/document-builder/index.d.ts +5 -5
- package/components/form-generic/index.d.ts +89 -103
- package/components/import-excel/index.d.ts +4 -14
- package/components/index.d.ts +4 -0
- package/components/job-progress/index.d.ts +38 -0
- package/components/mini-editor/index.d.ts +9 -9
- package/components/modal/index.d.ts +7 -1
- package/components/preview/index.d.ts +181 -28
- package/components/side-drawer/index.d.ts +7 -1
- package/components/tab/index.d.ts +9 -2
- package/components/table/index.d.ts +8 -7
- package/components/tree/index.d.ts +24 -2
- package/components/upload-file/index.d.ts +5 -4
- package/directives/index.d.ts +10 -8
- package/fesm2022/sdcorejs-angular-components-anchor.mjs +14 -14
- package/fesm2022/sdcorejs-angular-components-anchor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-audit-diff.mjs +332 -0
- package/fesm2022/sdcorejs-angular-components-audit-diff.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-components-badge.mjs +1 -0
- package/fesm2022/sdcorejs-angular-components-badge.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-breadcrumb.mjs +121 -0
- package/fesm2022/sdcorejs-angular-components-breadcrumb.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-components-data-state.mjs +68 -0
- package/fesm2022/sdcorejs-angular-components-data-state.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-components-document-builder.mjs +18 -23
- package/fesm2022/sdcorejs-angular-components-document-builder.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-editor.mjs +6 -4
- package/fesm2022/sdcorejs-angular-components-editor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-form-generic.mjs +633 -886
- package/fesm2022/sdcorejs-angular-components-form-generic.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-import-excel.mjs +26 -41
- package/fesm2022/sdcorejs-angular-components-import-excel.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-job-progress.mjs +97 -0
- package/fesm2022/sdcorejs-angular-components-job-progress.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-components-mini-editor.mjs +10 -20
- package/fesm2022/sdcorejs-angular-components-mini-editor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-modal.mjs +61 -4
- package/fesm2022/sdcorejs-angular-components-modal.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-preview.mjs +1438 -218
- package/fesm2022/sdcorejs-angular-components-preview.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-query-bar.mjs +9 -17
- package/fesm2022/sdcorejs-angular-components-query-bar.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-query-builder.mjs +1 -1
- package/fesm2022/sdcorejs-angular-components-query-builder.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-side-drawer.mjs +39 -2
- package/fesm2022/sdcorejs-angular-components-side-drawer.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-tab-router.mjs +76 -52
- package/fesm2022/sdcorejs-angular-components-tab-router.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-tab.mjs +48 -4
- package/fesm2022/sdcorejs-angular-components-tab.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-table.mjs +123 -89
- package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-tree.mjs +188 -18
- package/fesm2022/sdcorejs-angular-components-tree.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-upload-file.mjs +17 -23
- package/fesm2022/sdcorejs-angular-components-upload-file.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components.mjs +4 -0
- package/fesm2022/sdcorejs-angular-components.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-directives.mjs +29 -41
- package/fesm2022/sdcorejs-angular-directives.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs +9 -8
- package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-checkbox.mjs +20 -83
- package/fesm2022/sdcorejs-angular-forms-checkbox.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs +8 -4
- package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-chip.mjs +8 -4
- package/fesm2022/sdcorejs-angular-forms-chip.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-date-range.mjs +18 -14
- package/fesm2022/sdcorejs-angular-forms-date-range.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-date.mjs +48 -70
- package/fesm2022/sdcorejs-angular-forms-date.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-datetime.mjs +62 -528
- package/fesm2022/sdcorejs-angular-forms-datetime.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-entity-picker.mjs +384 -0
- package/fesm2022/sdcorejs-angular-forms-entity-picker.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-forms-input-color.mjs +5 -3
- package/fesm2022/sdcorejs-angular-forms-input-color.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-input-number.mjs +16 -11
- package/fesm2022/sdcorejs-angular-forms-input-number.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-input.mjs +297 -59
- package/fesm2022/sdcorejs-angular-forms-input.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-models.mjs +155 -2
- package/fesm2022/sdcorejs-angular-forms-models.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-radio.mjs +44 -78
- package/fesm2022/sdcorejs-angular-forms-radio.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-select.mjs +8 -21
- package/fesm2022/sdcorejs-angular-forms-select.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-switch.mjs +19 -76
- package/fesm2022/sdcorejs-angular-forms-switch.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-textarea.mjs +8 -6
- package/fesm2022/sdcorejs-angular-forms-textarea.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-time-range.mjs +280 -0
- package/fesm2022/sdcorejs-angular-forms-time-range.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-forms-time.mjs +310 -0
- package/fesm2022/sdcorejs-angular-forms-time.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-forms-tree-select.mjs +210 -0
- package/fesm2022/sdcorejs-angular-forms-tree-select.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-forms.mjs +73 -4
- package/fesm2022/sdcorejs-angular-forms.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-i18n.mjs +355 -10
- package/fesm2022/sdcorejs-angular-i18n.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-interceptors.mjs +12 -17
- package/fesm2022/sdcorejs-angular-interceptors.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-icon.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-layout.mjs +861 -80
- package/fesm2022/sdcorejs-angular-modules-layout.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules.mjs +0 -1
- package/fesm2022/sdcorejs-angular-modules.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-pipes.mjs +5 -7
- package/fesm2022/sdcorejs-angular-pipes.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-api.mjs +407 -137
- package/fesm2022/sdcorejs-angular-services-api.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-cache.mjs +515 -105
- package/fesm2022/sdcorejs-angular-services-cache.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-confirm.mjs +5 -7
- package/fesm2022/sdcorejs-angular-services-confirm.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-docx.mjs +7 -10
- package/fesm2022/sdcorejs-angular-services-docx.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-loading.mjs +340 -66
- package/fesm2022/sdcorejs-angular-services-loading.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-notify.mjs +7 -10
- package/fesm2022/sdcorejs-angular-services-notify.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-persistence.mjs +899 -0
- package/fesm2022/sdcorejs-angular-services-persistence.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-services-storage.mjs +324 -122
- package/fesm2022/sdcorejs-angular-services-storage.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-task.mjs +389 -0
- package/fesm2022/sdcorejs-angular-services-task.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-services-unsaved-changes.mjs +255 -0
- package/fesm2022/sdcorejs-angular-services-unsaved-changes.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-services-viewport.mjs +76 -0
- package/fesm2022/sdcorejs-angular-services-viewport.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-services.mjs +4 -0
- package/fesm2022/sdcorejs-angular-services.mjs.map +1 -1
- package/forms/autocomplete/index.d.ts +3 -4
- package/forms/checkbox/index.d.ts +4 -8
- package/forms/date/index.d.ts +3 -3
- package/forms/date-range/index.d.ts +6 -7
- package/forms/datetime/index.d.ts +3 -3
- package/forms/entity-picker/index.d.ts +128 -0
- package/forms/index.d.ts +15 -5
- package/forms/input/index.d.ts +76 -11
- package/forms/input-color/index.d.ts +3 -1
- package/forms/input-number/index.d.ts +4 -2
- package/forms/models/index.d.ts +86 -3
- package/forms/radio/index.d.ts +12 -8
- package/forms/switch/index.d.ts +4 -7
- package/forms/time/index.d.ts +117 -0
- package/forms/time-range/index.d.ts +98 -0
- package/forms/tree-select/index.d.ts +76 -0
- package/i18n/index.d.ts +68 -2
- package/interceptors/index.d.ts +5 -6
- package/modules/icon/index.d.ts +2 -3
- package/modules/index.d.ts +0 -1
- package/modules/layout/index.d.ts +288 -15
- package/package.json +50 -5
- package/pipes/index.d.ts +2 -2
- package/services/api/index.d.ts +68 -24
- package/services/cache/index.d.ts +52 -16
- package/services/confirm/index.d.ts +3 -2
- package/services/docx/index.d.ts +2 -3
- package/services/index.d.ts +4 -0
- package/services/loading/index.d.ts +16 -8
- package/services/persistence/index.d.ts +159 -0
- package/services/storage/index.d.ts +26 -8
- package/services/task/index.d.ts +104 -0
- package/services/unsaved-changes/index.d.ts +83 -0
- package/services/viewport/index.d.ts +42 -0
- package/fesm2022/sdcorejs-angular-modules-authom.mjs +0 -360
- package/fesm2022/sdcorejs-angular-modules-authom.mjs.map +0 -1
- package/modules/authom/index.d.ts +0 -70
package/README.md
CHANGED
|
@@ -1,885 +1,205 @@
|
|
|
1
1
|
# @sdcorejs/angular
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Angular Material-based UI building blocks for data-heavy business applications. The package combines standalone components, consistent form controls, workflow primitives, application services, theming, and localization for Angular 19, 20, and 21.
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/@sdcorejs/angular)
|
|
6
|
+
[](https://www.npmjs.com/package/@sdcorejs/angular)
|
|
7
|
+
[](#compatibility)
|
|
8
|
+
[](https://github.com/sdcorejs/sdcorejs-angular/blob/main/LICENSE)
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
[Showcase](https://sdcorejs.github.io/sdcorejs-angular/) · [Quick start](#quick-start) · [API manifest](https://sdcorejs.github.io/sdcorejs-angular/docs/latest/index.json) · [Source](https://github.com/sdcorejs/sdcorejs-angular) · [Changelog](https://github.com/sdcorejs/sdcorejs-angular/blob/main/CHANGELOG.md) · [Issues](https://github.com/sdcorejs/sdcorejs-angular/issues)
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
## Compatibility
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
Install the package major that matches your Angular application.
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
- [SdBadge](#sdbadge)
|
|
20
|
-
- [SdSection](#sdsection)
|
|
21
|
-
- [SdModal](#sdmodal)
|
|
22
|
-
- [SdTable](#sdtable)
|
|
23
|
-
- [SdAvatar](#sdavatar)
|
|
24
|
-
- [Other Components](#other-components--các-component-khác)
|
|
25
|
-
- [Form Components](#form-components)
|
|
26
|
-
- [CRUD Patterns / Code mẫu CRUD](#crud-patterns--code-mẫu-crud)
|
|
27
|
-
- [Contributing Guide / Hướng dẫn đóng góp](#contributing-guide--hướng-dẫn-đóng-góp)
|
|
16
|
+
| Angular | Package | Recommended install |
|
|
17
|
+
| ------- | ----------------------- | ------------------------------------------------------------------------------------------------ |
|
|
18
|
+
| 19.x | `@sdcorejs/angular@^19` | `npm install @sdcorejs/angular@^19 @angular/material@^19 @angular/material-date-fns-adapter@^19` |
|
|
19
|
+
| 20.x | `@sdcorejs/angular@^20` | `npm install @sdcorejs/angular@^20 @angular/material@^20 @angular/material-date-fns-adapter@^20` |
|
|
20
|
+
| 21.x | `@sdcorejs/angular@^21` | `npm install @sdcorejs/angular@^21 @angular/material@^21 @angular/material-date-fns-adapter@^21` |
|
|
28
21
|
|
|
29
|
-
|
|
22
|
+
The package manifests accept Angular 19–21 peers, while releases provide an Angular-aligned package line for each major. The first version number is reserved for Angular compatibility, so read the [changelog](https://github.com/sdcorejs/sdcorejs-angular/blob/main/CHANGELOG.md) for explicitly labeled breaking changes before upgrading.
|
|
30
23
|
|
|
31
|
-
##
|
|
24
|
+
## Installation and setup
|
|
32
25
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
| Dependency | Version |
|
|
36
|
-
| ------------------------------------ | ----------------------------------- |
|
|
37
|
-
| `@angular/core` | `^19.0.0 \|\| ^20.0.0 \|\| ^21.0.0` |
|
|
38
|
-
| `@angular/material` | `^19.0.0 \|\| ^20.0.0 \|\| ^21.0.0` |
|
|
39
|
-
| `@angular/material-date-fns-adapter` | `^19.0.0 \|\| ^20.0.0 \|\| ^21.0.0` |
|
|
40
|
-
| `date-fns` | `^3 \|\| ^4` |
|
|
41
|
-
|
|
42
|
-
### Installation / Cài đặt
|
|
26
|
+
The examples below use Angular 19; replace `19` with your application's Angular major.
|
|
43
27
|
|
|
44
28
|
```bash
|
|
45
|
-
npm install @sdcorejs/angular
|
|
29
|
+
npm install @sdcorejs/angular@^19 @angular/material@^19 @angular/material-date-fns-adapter@^19
|
|
46
30
|
```
|
|
47
31
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
**1. Import global styles / Import style toàn cục**
|
|
51
|
-
|
|
52
|
-
Thêm vào `angular.json` (hoặc `styles.scss` của app):
|
|
53
|
-
|
|
54
|
-
```json
|
|
55
|
-
// angular.json
|
|
56
|
-
{
|
|
57
|
-
"styles": ["node_modules/@sdcorejs/angular/assets/scss/sd-core.scss"]
|
|
58
|
-
}
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
hoặc trong `styles.scss`:
|
|
32
|
+
Load the global stylesheet once:
|
|
62
33
|
|
|
63
34
|
```scss
|
|
35
|
+
/* styles.scss */
|
|
64
36
|
@use '@sdcorejs/angular/assets/scss/sd-core';
|
|
65
37
|
```
|
|
66
38
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
Thêm vào `index.html`:
|
|
70
|
-
|
|
71
|
-
```html
|
|
72
|
-
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet" />
|
|
73
|
-
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;600&display=swap" rel="stylesheet" />
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
**3. Configure providers / Cấu hình providers**
|
|
77
|
-
|
|
78
|
-
```typescript
|
|
79
|
-
// app.config.ts
|
|
80
|
-
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
|
81
|
-
|
|
82
|
-
export const appConfig: ApplicationConfig = {
|
|
83
|
-
providers: [
|
|
84
|
-
provideAnimationsAsync(),
|
|
85
|
-
// ... các providers khác
|
|
86
|
-
],
|
|
87
|
-
};
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
## Theming / SCSS Customization
|
|
93
|
-
|
|
94
|
-
`sd-core.scss` emits the Angular Material M3 theme with `mat.theme(...)`.
|
|
95
|
-
Prefer Core UI variables such as `--sd-primary`, `--sd-surface`, `--sd-text`,
|
|
96
|
-
and `--sd-border` in application code. Material system variables remain an
|
|
97
|
-
implementation detail for Angular Material overrides.
|
|
98
|
-
|
|
99
|
-
### CSS Variables
|
|
100
|
-
|
|
101
|
-
`@sdcorejs/angular` sử dụng CSS custom properties (variables) để quản lý màu sắc. Mỗi màu được expose dưới dạng `--sd-<color>`.
|
|
39
|
+
The stylesheet includes the Roboto, Material Icons, and Material Symbols font files used by the library. No Google Fonts link is required. `@sdcorejs/utils`, `date-fns`, and other declared implementation dependencies install transitively.
|
|
102
40
|
|
|
103
|
-
|
|
41
|
+
## Quick start
|
|
104
42
|
|
|
105
|
-
|
|
106
|
-
states, plus neutral tokens for surface, text, border, and disabled states.
|
|
107
|
-
|
|
108
|
-
| Variable | Default bridge / fallback | Description |
|
|
109
|
-
| -------- | ------------------------- | ----------- |
|
|
110
|
-
| `--sd-primary` | `var(--mat-sys-primary, #005cbb)` | Main action color |
|
|
111
|
-
| `--sd-primary-light` | `color-mix(in srgb, var(--sd-primary) 14%, white)` | Soft primary background |
|
|
112
|
-
| `--sd-primary-dark` | `color-mix(in srgb, var(--sd-primary) 84%, black)` | Hover/active primary shade |
|
|
113
|
-
| `--sd-primary-contrast` | `#ffffff` | Text/icon on primary |
|
|
114
|
-
| `--sd-secondary` | `var(--mat-sys-secondary, #5c6270)` | Secondary action/accent |
|
|
115
|
-
| `--sd-secondary-light` | `color-mix(in srgb, var(--sd-secondary) 12%, white)` | Soft secondary background |
|
|
116
|
-
| `--sd-secondary-dark` | `color-mix(in srgb, var(--sd-secondary) 84%, black)` | Hover/active secondary shade |
|
|
117
|
-
| `--sd-secondary-contrast` | `#ffffff` | Text/icon on secondary |
|
|
118
|
-
| `--sd-info`, `--sd-success`, `--sd-warning`, `--sd-error` | Semantic base colors | Info, success, warning, and error states |
|
|
119
|
-
| `--sd-*-light` / `--sd-*-dark` / `--sd-*-contrast` | Generated from each semantic base | Soft background, active shade, and readable foreground |
|
|
120
|
-
| `--sd-surface` | `var(--mat-sys-surface, #fdfbff)` | App/page surface |
|
|
121
|
-
| `--sd-surface-muted` | `var(--mat-sys-surface-container-highest, #e7e8ed)` | Muted neutral background |
|
|
122
|
-
| `--sd-text` | `var(--mat-sys-on-surface, #1a1b1f)` | Primary text |
|
|
123
|
-
| `--sd-text-secondary` | `var(--mat-sys-on-surface-variant, #44474f)` | Secondary text |
|
|
124
|
-
| `--sd-text-muted` | `color-mix(in srgb, var(--sd-text) 62%, transparent)` | Muted text |
|
|
125
|
-
| `--sd-border` | `var(--mat-sys-outline-variant, #c4c6d0)` | Divider/subtle border |
|
|
126
|
-
| `--sd-border-strong` | `var(--mat-sys-outline, #74777f)` | Strong border/focus outline |
|
|
127
|
-
| `--sd-disabled-bg` | `color-mix(in srgb, var(--sd-text) 8%, transparent)` | Disabled background |
|
|
128
|
-
| `--sd-disabled-text` | `color-mix(in srgb, var(--sd-text) 60%, transparent)` | Disabled text/icon |
|
|
129
|
-
|
|
130
|
-
### Custom Theme / Tuỳ chỉnh theme
|
|
131
|
-
|
|
132
|
-
Ghi đè theme mặc định bằng cách truyền map SCSS vào mixin `theme()`:
|
|
133
|
-
|
|
134
|
-
```scss
|
|
135
|
-
// styles.scss
|
|
136
|
-
@use '@sdcorejs/angular/assets/scss/themes/default' as default;
|
|
137
|
-
|
|
138
|
-
html {
|
|
139
|
-
@include default.theme(
|
|
140
|
-
(
|
|
141
|
-
primary: #7c3aed,
|
|
142
|
-
primary-light: #ede9fe,
|
|
143
|
-
primary-dark: #5b21b6,
|
|
144
|
-
success: #10b981,
|
|
145
|
-
error: #ef4444,
|
|
146
|
-
)
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
Chỉ cần override các màu muốn thay đổi — các màu còn lại giữ nguyên giá trị mặc định.
|
|
152
|
-
|
|
153
|
-
### Utility Classes / Các class tiện ích
|
|
154
|
-
|
|
155
|
-
Thư viện cung cấp sẵn các utility class:
|
|
156
|
-
|
|
157
|
-
```html
|
|
158
|
-
<!-- Text color -->
|
|
159
|
-
<span class="text-primary">Text màu primary</span>
|
|
160
|
-
<span class="text-error">Text màu error</span>
|
|
161
|
-
|
|
162
|
-
<!-- Background -->
|
|
163
|
-
<div class="bg-primary-light">Background nhạt</div>
|
|
164
|
-
|
|
165
|
-
<!-- Spacing (đơn vị px, từ 0–200) -->
|
|
166
|
-
<div class="mt-16 mb-8 px-24">margin-top: 16px, padding: 0 24px</div>
|
|
167
|
-
|
|
168
|
-
<!-- Gap -->
|
|
169
|
-
<div class="d-flex gap-8">gap: 8px</div>
|
|
170
|
-
|
|
171
|
-
<!-- Grid -->
|
|
172
|
-
<div class="sd-grid-container grid-cols-3">
|
|
173
|
-
<div class="col-span-2">Chiếm 2 cột</div>
|
|
174
|
-
<div class="col-span-1">Chiếm 1 cột</div>
|
|
175
|
-
</div>
|
|
176
|
-
|
|
177
|
-
<!-- Bootstrap grid -->
|
|
178
|
-
<div class="row">
|
|
179
|
-
<div class="col-6">50%</div>
|
|
180
|
-
<div class="col-6">50%</div>
|
|
181
|
-
</div>
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
---
|
|
185
|
-
|
|
186
|
-
## AI Agent Usage Guardrails
|
|
187
|
-
|
|
188
|
-
Use this section as a checklist when generating SDCoreJS Angular code from online docs.
|
|
189
|
-
|
|
190
|
-
### 1. Standalone imports are mandatory
|
|
191
|
-
|
|
192
|
-
Every component, directive, and pipe used in a standalone template must be listed in the host component's `imports`. This includes template-only directives such as `sdTableCellDef`, `sdTableFilterDef`, `sdSuffixDef`, `sdViewDef`, `sdItemDef`, and shared display pipes.
|
|
43
|
+
This standalone component renders a primary action and shows the button's built-in loading state while work is in progress. No SDCoreJS-specific provider is required for this example.
|
|
193
44
|
|
|
194
45
|
```ts
|
|
195
|
-
import { Component } from '@angular/core';
|
|
46
|
+
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
|
|
196
47
|
import { SdButton } from '@sdcorejs/angular/components/button';
|
|
197
|
-
import {
|
|
198
|
-
SdTable,
|
|
199
|
-
SdTableCellDefDirective,
|
|
200
|
-
SdTableFilterDefDirective,
|
|
201
|
-
SdTableFooterDefDirective,
|
|
202
|
-
SdTableTitleDefDirective,
|
|
203
|
-
} from '@sdcorejs/angular/components/table';
|
|
204
|
-
import { SdInput, SdInputNumber, SdSelect } from '@sdcorejs/angular/forms';
|
|
205
|
-
import { SdItemDefDefDirective, SdSuffixDefDirective, SdViewDefDirective } from '@sdcorejs/angular/forms/directives';
|
|
206
|
-
import { SdFormatDatePipe, SdFormatDatetimePipe, SdFormatNumberPipe, SdViewPipe } from '@sdcorejs/angular/pipes';
|
|
207
48
|
|
|
208
49
|
@Component({
|
|
50
|
+
selector: 'app-root',
|
|
209
51
|
standalone: true,
|
|
210
|
-
imports: [
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
SdTableFilterDefDirective,
|
|
215
|
-
SdTableFooterDefDirective,
|
|
216
|
-
SdTableTitleDefDirective,
|
|
217
|
-
SdInput,
|
|
218
|
-
SdInputNumber,
|
|
219
|
-
SdSelect,
|
|
220
|
-
SdItemDefDefDirective,
|
|
221
|
-
SdSuffixDefDirective,
|
|
222
|
-
SdViewDefDirective,
|
|
223
|
-
SdFormatNumberPipe,
|
|
224
|
-
SdFormatDatePipe,
|
|
225
|
-
SdFormatDatetimePipe,
|
|
226
|
-
SdViewPipe,
|
|
227
|
-
],
|
|
228
|
-
templateUrl: './list.component.html',
|
|
229
|
-
})
|
|
230
|
-
export class ListComponent {}
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
Common missed imports:
|
|
234
|
-
|
|
235
|
-
| Template usage | Required import |
|
|
236
|
-
| ----------------------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------- |
|
|
237
|
-
| `<ng-template sdTableCellDef="amount">` | `SdTableCellDefDirective` from `@sdcorejs/angular/components/table` |
|
|
238
|
-
| `<ng-template sdTableFilterDef="status">` | `SdTableFilterDefDirective` from `@sdcorejs/angular/components/table` |
|
|
239
|
-
| `<ng-template sdTableTitleDef="name">` | `SdTableTitleDefDirective` from `@sdcorejs/angular/components/table` |
|
|
240
|
-
| `<ng-template sdTableFooterDef="amount">` | `SdTableFooterDefDirective` from `@sdcorejs/angular/components/table` |
|
|
241
|
-
| `<ng-template sdTableExpandDef>` | `SdTableExpandDefDirective` from `@sdcorejs/angular/components/table` |
|
|
242
|
-
| `<ng-template sdSuffixDef>` | `SdSuffixDefDirective` from `@sdcorejs/angular/forms/directives` |
|
|
243
|
-
| `<ng-template sdViewDef>` | `SdViewDefDirective` from `@sdcorejs/angular/forms/directives` |
|
|
244
|
-
| `<ng-template sdItemDef>` | `SdItemDefDefDirective` from `@sdcorejs/angular/forms/directives` |
|
|
245
|
-
| ` | sdFormatNumber` | `SdFormatNumberPipe` from `@sdcorejs/angular/pipes` |
|
|
246
|
-
| ` | sdFormatDate` | `SdFormatDatePipe` from `@sdcorejs/angular/pipes` |
|
|
247
|
-
| ` | sdFormatDatetime` | `SdFormatDatetimePipe` from `@sdcorejs/angular/pipes` |
|
|
248
|
-
| ` | sdView` | `SdViewPipe` from `@sdcorejs/angular/pipes` |
|
|
249
|
-
|
|
250
|
-
### 2. Table cells use dense form controls
|
|
251
|
-
|
|
252
|
-
When rendering an editable SD form control inside `<sd-table>` cell templates or custom inline filter templates, always use `size="sm"` and `hideInlineError`. This keeps row height stable and surfaces validation through the compact error icon/tooltip instead of inserting inline error text inside the table row.
|
|
253
|
-
|
|
254
|
-
```html
|
|
255
|
-
<sd-table [option]="tableOption">
|
|
256
|
-
<ng-template sdTableCellDef="quantity" let-row>
|
|
257
|
-
<sd-input-number size="sm" hideInlineError type="positive" [precision]="0" [(model)]="row.quantity"> </sd-input-number>
|
|
258
|
-
</ng-template>
|
|
259
|
-
|
|
260
|
-
<ng-template sdTableCellDef="status" let-row>
|
|
261
|
-
<sd-select size="sm" hideInlineError [items]="statusList" valueField="code" displayField="name" [(model)]="row.status"> </sd-select>
|
|
262
|
-
</ng-template>
|
|
263
|
-
|
|
264
|
-
<ng-template sdTableFilterDef="keyword" let-filter let-update="update">
|
|
265
|
-
<sd-input size="sm" hideInlineError [(model)]="filter.keyword" (keyupEnter)="update()"> </sd-input>
|
|
266
|
-
</ng-template>
|
|
267
|
-
</sd-table>
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
### 3. Prefer shared display pipes
|
|
271
|
-
|
|
272
|
-
Do not create app-local pipes for common display formatting.
|
|
273
|
-
|
|
274
|
-
```html
|
|
275
|
-
<!-- Good -->
|
|
276
|
-
{{ row.amount | sdFormatNumber : 0 | sdView }} {{ row.issueDate | sdFormatDate | sdView }} {{ row.updatedAt | sdFormatDatetime : 'dd/MM/yyyy
|
|
277
|
-
HH:mm' | sdView }} {{ row.tags | sdView }}
|
|
278
|
-
|
|
279
|
-
<!-- Avoid -->
|
|
280
|
-
{{ row.amount | customCurrency }} {{ row.issueDate | date : 'dd/MM/yyyy' }} {{ row.tags?.join(', ') || '--' }}
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
- `sdFormatNumber` formats numbers using SDCoreJS locale configuration.
|
|
284
|
-
- `sdFormatDate` formats date-only values with default `dd/MM/yyyy`.
|
|
285
|
-
- `sdFormatDatetime` formats date-time values with default `dd/MM/yyyy HH:mm:ss`.
|
|
286
|
-
- `sdView` renders `--` for `null`, `undefined`, `''`, `NaN`, and empty arrays; primitive arrays render as `A, B`.
|
|
287
|
-
|
|
288
|
-
### 4. Command icons default to Material Symbols Outlined
|
|
289
|
-
|
|
290
|
-
Table row commands and child command menu items default to the outline Material icon font set. Only set `fontSet` when a specific icon family is required.
|
|
291
|
-
|
|
292
|
-
```ts
|
|
293
|
-
command: {
|
|
294
|
-
align: 'right',
|
|
295
|
-
commands: [
|
|
296
|
-
{ icon: 'edit', title: 'Edit', click: row => this.edit(row) },
|
|
297
|
-
{
|
|
298
|
-
icon: 'more_vert',
|
|
299
|
-
title: 'More',
|
|
300
|
-
children: [
|
|
301
|
-
{ icon: 'visibility', title: 'View', click: row => this.view(row) },
|
|
302
|
-
{ icon: 'delete', title: 'Delete', color: 'error', click: row => this.delete(row) },
|
|
303
|
-
],
|
|
304
|
-
},
|
|
305
|
-
],
|
|
306
|
-
}
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
---
|
|
310
|
-
|
|
311
|
-
## Components
|
|
312
|
-
|
|
313
|
-
> Tất cả component đều là **standalone** và sử dụng **Angular Signals** API.
|
|
314
|
-
> All components are **standalone** and use **Angular Signals** API.
|
|
315
|
-
|
|
316
|
-
### SdButton
|
|
317
|
-
|
|
318
|
-
```typescript
|
|
319
|
-
import { SdButton } from '@sdcorejs/angular/components/button';
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
**Inputs:**
|
|
323
|
-
|
|
324
|
-
| Input | Type | Default | Description |
|
|
325
|
-
| ------------ | ------------------------------------------ | ------------- | ---------------------------------- |
|
|
326
|
-
| `type` | `'fill' \| 'light' \| 'outline' \| 'text'` | `'light'` | Kiểu nút |
|
|
327
|
-
| `color` | `Color` | `'secondary'` | Màu sắc |
|
|
328
|
-
| `size` | `'sm' \| 'md' \| 'lg'` | `'sm'` | Kích thước |
|
|
329
|
-
| `title` | `string` | — | Nhãn nút |
|
|
330
|
-
| `prefixIcon` | `string` | — | Icon Material trước text |
|
|
331
|
-
| `suffixIcon` | `string` | — | Icon Material sau text |
|
|
332
|
-
| `disabled` | `boolean` | `false` | Vô hiệu hoá |
|
|
333
|
-
| `loading` | `boolean` | `false` | Trạng thái loading (tự chặn click) |
|
|
334
|
-
| `tooltip` | `string` | — | Tooltip khi hover |
|
|
335
|
-
| `width` | `string` | — | CSS width tuỳ chỉnh |
|
|
336
|
-
|
|
337
|
-
**Output:** `(click): EventEmitter<Event>` — có throttle 300ms, tự chặn khi `disabled` hoặc `loading`.
|
|
338
|
-
|
|
339
|
-
```html
|
|
340
|
-
<sd-button type="fill" color="primary" title="Lưu" prefixIcon="save" (click)="onSave()" />
|
|
341
|
-
<sd-button type="outline" color="error" prefixIcon="delete" tooltip="Xoá" />
|
|
342
|
-
<sd-button type="light" title="Huỷ" (click)="modal.close()" />
|
|
343
|
-
<sd-button type="fill" color="primary" title="Đang xử lý" [loading]="true" />
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
---
|
|
347
|
-
|
|
348
|
-
### SdBadge
|
|
349
|
-
|
|
350
|
-
```typescript
|
|
351
|
-
import { SdBadge } from '@sdcorejs/angular/components/badge';
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
**Inputs:**
|
|
355
|
-
|
|
356
|
-
| Input | Type | Default | Description |
|
|
357
|
-
| --------- | ---------------------------- | ------------- | ----------------- |
|
|
358
|
-
| `type` | `'tag' \| 'round' \| 'icon'` | `'icon'` | Kiểu badge |
|
|
359
|
-
| `color` | `Color` | `'secondary'` | Màu sắc |
|
|
360
|
-
| `title` | `string \| number` | — | Nội dung hiển thị |
|
|
361
|
-
| `icon` | `string` | — | Icon Material |
|
|
362
|
-
| `size` | `Size` | `'sm'` | Kích thước |
|
|
363
|
-
| `tooltip` | `string` | — | Tooltip |
|
|
364
|
-
|
|
365
|
-
Shorthand color inputs (boolean): `primary`, `secondary`, `success`, `info`, `warning`, `error`.
|
|
366
|
-
|
|
367
|
-
```html
|
|
368
|
-
<sd-badge type="tag" color="success" title="Hoạt động" />
|
|
369
|
-
<sd-badge type="round" [warning]="true" title="Chờ duyệt" />
|
|
370
|
-
<sd-badge type="icon" color="error" icon="close" title="Từ chối" />
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
---
|
|
374
|
-
|
|
375
|
-
### SdSection
|
|
376
|
-
|
|
377
|
-
```typescript
|
|
378
|
-
import { SdSection } from '@sdcorejs/angular/components/section';
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
**Inputs:**
|
|
382
|
-
|
|
383
|
-
| Input | Type | Default | Description |
|
|
384
|
-
| ------------- | --------- | ----------- | -------------------------- |
|
|
385
|
-
| `title` | `string` | _required_ | Tiêu đề section |
|
|
386
|
-
| `subTitle` | `string` | — | Tiêu đề phụ |
|
|
387
|
-
| `icon` | `string` | — | Icon Material |
|
|
388
|
-
| `iconColor` | `Color` | `'primary'` | Màu icon |
|
|
389
|
-
| `collapsible` | `boolean` | `false` | Cho phép thu gọn |
|
|
390
|
-
| `collapsed` | `boolean` | `false` | Trạng thái ban đầu thu gọn |
|
|
391
|
-
| `hideHeader` | `boolean` | `false` | Ẩn phần header |
|
|
392
|
-
|
|
393
|
-
```html
|
|
394
|
-
<sd-section title="Thông tin cơ bản" icon="person" iconColor="primary">
|
|
395
|
-
<!-- nội dung -->
|
|
396
|
-
</sd-section>
|
|
397
|
-
|
|
398
|
-
<sd-section title="Cài đặt nâng cao" icon="settings" collapsible [collapsed]="true">
|
|
399
|
-
<!-- nội dung ẩn mặc định -->
|
|
400
|
-
</sd-section>
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
---
|
|
404
|
-
|
|
405
|
-
### SdModal
|
|
406
|
-
|
|
407
|
-
```typescript
|
|
408
|
-
import { SdModal } from '@sdcorejs/angular/components/modal';
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
**Inputs:**
|
|
412
|
-
|
|
413
|
-
| Input | Type | Default | Description |
|
|
414
|
-
| ----------------- | ---------------------------------------- | ----------- | -------------------------------- |
|
|
415
|
-
| `title` | `string` | — | Tiêu đề modal |
|
|
416
|
-
| `color` | `Color` | `'primary'` | Màu header |
|
|
417
|
-
| `width` | `'sx' \| 'sm' \| 'md' \| 'lg' \| string` | `'md'` | Độ rộng (md = 60vw) |
|
|
418
|
-
| `height` | `string` | `'auto'` | Chiều cao |
|
|
419
|
-
| `view` | `'dialog' \| 'bottom-sheet'` | auto | Tự động bottom-sheet trên mobile |
|
|
420
|
-
| `lazyLoadContent` | `boolean` | `true` | Lazy render nội dung |
|
|
421
|
-
|
|
422
|
-
**Output:** `(sdClosed): EventEmitter` — phát ra khi modal đóng.
|
|
423
|
-
|
|
424
|
-
**Methods** (dùng qua `@ViewChild`):
|
|
425
|
-
|
|
426
|
-
- `modal.open()` — mở modal
|
|
427
|
-
- `modal.close()` — đóng modal
|
|
428
|
-
|
|
429
|
-
> ⚠️ Nội dung modal phải đặt trong `<ng-template>`.
|
|
430
|
-
|
|
431
|
-
```html
|
|
432
|
-
<sd-modal #myModal title="Thêm mới" width="md" (sdClosed)="onClosed()">
|
|
433
|
-
<ng-template>
|
|
434
|
-
<div class="modal-body p-16">
|
|
435
|
-
<!-- nội dung form -->
|
|
436
|
-
</div>
|
|
437
|
-
<div class="modal-footer d-flex justify-content-end gap-8 p-16">
|
|
438
|
-
<sd-button title="Huỷ" (click)="myModal.close()" />
|
|
439
|
-
<sd-button type="fill" color="primary" title="Lưu" (click)="onSave()" />
|
|
440
|
-
</div>
|
|
441
|
-
</ng-template>
|
|
442
|
-
</sd-modal>
|
|
443
|
-
|
|
444
|
-
<sd-button title="Mở modal" prefixIcon="add" (click)="myModal.open()" />
|
|
445
|
-
```
|
|
446
|
-
|
|
447
|
-
---
|
|
448
|
-
|
|
449
|
-
### SdTable
|
|
450
|
-
|
|
451
|
-
```typescript
|
|
452
|
-
import { SdTable } from '@sdcorejs/angular/components/table';
|
|
453
|
-
import type { SdTableOption, SdTableColumn } from '@sdcorejs/angular/components/table';
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
SdTable nhận một object `option` duy nhất kiểu `SdTableOption<T>`.
|
|
457
|
-
|
|
458
|
-
**Column types / Kiểu cột:**
|
|
459
|
-
|
|
460
|
-
| `type` | Mô tả |
|
|
461
|
-
| --------------- | ------------------------- |
|
|
462
|
-
| `'string'` | Văn bản |
|
|
463
|
-
| `'number'` | Số (tự format) |
|
|
464
|
-
| `'boolean'` | True/False |
|
|
465
|
-
| `'date'` | Ngày |
|
|
466
|
-
| `'datetime'` | Ngày giờ |
|
|
467
|
-
| `'time'` | Giờ |
|
|
468
|
-
| `'values'` | Enum từ danh sách cố định |
|
|
469
|
-
| `'lazy-values'` | Enum load async |
|
|
470
|
-
| `'children'` | Cột nhóm (multi-header) |
|
|
471
|
-
|
|
472
|
-
**Local table:**
|
|
473
|
-
|
|
474
|
-
```typescript
|
|
475
|
-
option: SdTableOption<Product> = {
|
|
476
|
-
type: 'local',
|
|
477
|
-
items: () => this.products,
|
|
478
|
-
columns: [
|
|
479
|
-
{ field: 'code', type: 'string', title: 'Mã', width: '120px' },
|
|
480
|
-
{ field: 'name', type: 'string', title: 'Tên', sortable: true },
|
|
481
|
-
{ field: 'price', type: 'number', title: 'Đơn giá', align: 'right' },
|
|
482
|
-
{
|
|
483
|
-
field: 'active',
|
|
484
|
-
type: 'boolean',
|
|
485
|
-
title: 'Kích hoạt',
|
|
486
|
-
useBadge: val => ({ color: val ? 'success' : 'secondary', title: val ? 'Có' : 'Không' }),
|
|
487
|
-
},
|
|
488
|
-
],
|
|
489
|
-
paginate: { pageSize: 20 },
|
|
490
|
-
reload: { visible: true },
|
|
491
|
-
};
|
|
492
|
-
```
|
|
493
|
-
|
|
494
|
-
**Server-side table:**
|
|
495
|
-
|
|
496
|
-
```typescript
|
|
497
|
-
option: SdTableOption<Product> = {
|
|
498
|
-
type: 'server',
|
|
499
|
-
items: async (filterRequest, pagingReq) => {
|
|
500
|
-
const res = await this.service.search({
|
|
501
|
-
keyword: filterRequest.keyword,
|
|
502
|
-
page: pagingReq.page,
|
|
503
|
-
pageSize: pagingReq.pageSize,
|
|
504
|
-
});
|
|
505
|
-
return { items: res.data, total: res.total };
|
|
506
|
-
},
|
|
507
|
-
columns: [
|
|
508
|
-
{ field: 'code', type: 'string', title: 'Mã', width: '120px' },
|
|
509
|
-
{ field: 'name', type: 'string', title: 'Tên', sortable: true },
|
|
510
|
-
{
|
|
511
|
-
field: 'status',
|
|
512
|
-
type: 'values',
|
|
513
|
-
title: 'Trạng thái',
|
|
514
|
-
useBadge: val => ({ color: val === 'ACTIVE' ? 'success' : 'secondary', title: val }),
|
|
515
|
-
},
|
|
516
|
-
],
|
|
517
|
-
command: {
|
|
518
|
-
align: 'right',
|
|
519
|
-
commands: [
|
|
520
|
-
{ icon: 'edit', color: 'primary', title: 'Sửa', click: row => this.onEdit(row) },
|
|
521
|
-
{ icon: 'delete', color: 'error', title: 'Xoá', click: row => this.onDelete(row) },
|
|
522
|
-
],
|
|
523
|
-
},
|
|
524
|
-
};
|
|
525
|
-
```
|
|
526
|
-
|
|
527
|
-
```html
|
|
528
|
-
<sd-table #sdTable [option]="option" />
|
|
529
|
-
```
|
|
52
|
+
imports: [SdButton],
|
|
53
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
54
|
+
template: `
|
|
55
|
+
<sd-button type="fill" color="primary" title="Save changes" [loading]="saving()" (click)="save()" />
|
|
530
56
|
|
|
531
|
-
|
|
57
|
+
<p aria-live="polite">{{ status() }}</p>
|
|
58
|
+
`,
|
|
59
|
+
})
|
|
60
|
+
export class AppComponent {
|
|
61
|
+
readonly saving = signal(false);
|
|
62
|
+
readonly status = signal('Ready');
|
|
532
63
|
|
|
533
|
-
|
|
64
|
+
async save(): Promise<void> {
|
|
65
|
+
this.saving.set(true);
|
|
66
|
+
this.status.set('Saving…');
|
|
534
67
|
|
|
535
|
-
|
|
536
|
-
// loadType: 'static' — children embedded sẵn trong mỗi row
|
|
537
|
-
tree: { loadType: 'static', childrenKey: 'children', defaultExpanded: 1 }
|
|
68
|
+
await new Promise<void>(resolve => setTimeout(resolve, 700));
|
|
538
69
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
hasChildren: (row) => row.type === 'Folder', // chỉ Folder mới có icon expand
|
|
543
|
-
onExpandChildren: (row) => api.getChildren(row.id), // () => Promise<T[]>
|
|
70
|
+
this.status.set('Saved');
|
|
71
|
+
this.saving.set(false);
|
|
72
|
+
}
|
|
544
73
|
}
|
|
545
74
|
```
|
|
546
75
|
|
|
547
|
-
|
|
548
|
-
- **Lazy loading**: spinner hiện trong ô chevron khi `onExpandChildren` đang chạy; `hasChildren` quyết định dòng nào hiện icon (không truyền = mọi node đều hiện).
|
|
76
|
+
Replace the timer with your typed service call and reset `saving` in a `finally` block in production code.
|
|
549
77
|
|
|
550
|
-
|
|
78
|
+
## Main capabilities
|
|
551
79
|
|
|
552
|
-
|
|
80
|
+
| Area | Representative APIs |
|
|
81
|
+
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
82
|
+
| UI components | Navigation/data state, PDF preview, job progress, audit diff, modals, drawers, tabs, charts, editors, and document tooling |
|
|
83
|
+
| Data and workflow | Local/server tables, entity/tree pickers, query builders, unsaved-change guards, background tasks, upload, and Excel import |
|
|
84
|
+
| Form controls | Text/mask, number, time/time range, date/date range, datetime, select, autocomplete, checkbox, radio, switch, chip, and color |
|
|
85
|
+
| Services | Typed API/retry/cancel, ref-counted loading, graph-safe persistence/cache/storage, viewport signals, notifications, and exports |
|
|
86
|
+
| Portal modules | Auth, Keycloak, permission, layout, and icon modules |
|
|
87
|
+
| Localization | Built-in `vi`, `en`, `ja`, `ko`, and `zh` catalogs, plus a synchronous custom-catalog provider |
|
|
553
88
|
|
|
554
|
-
|
|
555
|
-
import { SdAvatar } from '@sdcorejs/angular/components/avatar';
|
|
556
|
-
```
|
|
557
|
-
|
|
558
|
-
```html
|
|
559
|
-
<sd-avatar src="/api/avatar/123" name="Nguyễn Văn A" size="md" /> <sd-avatar name="NVA" color="primary" size="lg" />
|
|
560
|
-
```
|
|
561
|
-
|
|
562
|
-
---
|
|
563
|
-
|
|
564
|
-
### Other Components / Các component khác
|
|
565
|
-
|
|
566
|
-
| Component | Import | Mô tả |
|
|
567
|
-
| ------------------- | ----------------------------------------------- | --------------------------------- |
|
|
568
|
-
| `SdTabRouter` | `@sdcorejs/angular/components/tab-router` | Tab navigation với Angular Router |
|
|
569
|
-
| `SdSideDrawer` | `@sdcorejs/angular/components/side-drawer` | Drawer layout trái/phải |
|
|
570
|
-
| `SdUploadFile` | `@sdcorejs/angular/components/upload-file` | Upload file |
|
|
571
|
-
| `SdQuickAction` | `@sdcorejs/angular/components/quick-action` | Nút action dạng icon |
|
|
572
|
-
| `SdHistory` | `@sdcorejs/angular/components/history` | Lịch sử thay đổi |
|
|
573
|
-
| `SdImportExcel` | `@sdcorejs/angular/components/import-excel` | Wizard import Excel |
|
|
574
|
-
| `SdQueryBuilder` | `@sdcorejs/angular/components/query-builder` | Visual query builder |
|
|
575
|
-
| `SdCodeEditor` | `@sdcorejs/angular/components/code-editor` | Code editor (PrismJS) |
|
|
576
|
-
| `SdMiniEditor` | `@sdcorejs/angular/components/mini-editor` | Rich text editor nhỏ |
|
|
577
|
-
| `SdDocumentBuilder` | `@sdcorejs/angular/components/document-builder` | Document builder |
|
|
578
|
-
| `SdAnchorMain` | `@sdcorejs/angular/components/anchor` | Anchor / mục lục cuộn trang |
|
|
579
|
-
| `SdView` | `@sdcorejs/angular/components/view` | View wrapper read-only |
|
|
580
|
-
|
|
581
|
-
---
|
|
582
|
-
|
|
583
|
-
## Form Components
|
|
584
|
-
|
|
585
|
-
```typescript
|
|
586
|
-
import {
|
|
587
|
-
SdInput, // Text input
|
|
588
|
-
SdInputNumber, // Number input
|
|
589
|
-
SdSelect, // Dropdown
|
|
590
|
-
SdAutocomplete, // Autocomplete
|
|
591
|
-
SdDate, // Date picker
|
|
592
|
-
} from '@sdcorejs/angular/forms';
|
|
593
|
-
```
|
|
89
|
+
The [live showcase](https://sdcorejs.github.io/sdcorejs-angular/) demonstrates components, forms, and services. The [latest API manifest](https://sdcorejs.github.io/sdcorejs-angular/docs/latest/index.json) lists every published reference document without duplicating the full API here.
|
|
594
90
|
|
|
595
|
-
|
|
596
|
-
<sd-input [(model)]="form.name" label="Họ tên" [required]="true" />
|
|
91
|
+
## Standalone and subpath imports
|
|
597
92
|
|
|
598
|
-
|
|
93
|
+
Prefer public leaf entry points so dependencies stay explicit and unused entry points can be removed from the application graph. The package declares `sideEffects: false`.
|
|
599
94
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
95
|
+
```ts
|
|
96
|
+
import { SdButton } from '@sdcorejs/angular/components/button';
|
|
97
|
+
import { SdTable, type SdTableOption } from '@sdcorejs/angular/components/table';
|
|
98
|
+
import { SdInput } from '@sdcorejs/angular/forms/input';
|
|
99
|
+
import { SdNotifyService } from '@sdcorejs/angular/services/notify';
|
|
100
|
+
import { I18nService } from '@sdcorejs/angular/i18n';
|
|
603
101
|
```
|
|
604
102
|
|
|
605
|
-
|
|
103
|
+
Import standalone components in the host component's `imports` array and inject services normally.
|
|
606
104
|
|
|
607
|
-
|
|
105
|
+
Form controls use `[(model)]`. For group validation, pass a `FormGroup` through `[form]` and provide a `name`; SDCoreJS controls do not use `formControlName` or `[(ngModel)]` as their integration contract.
|
|
608
106
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
import { Component, OnInit, ViewChild, signal } from '@angular/core';
|
|
614
|
-
import { SdTable, SdTableOption } from '@sdcorejs/angular/components/table';
|
|
615
|
-
import { SdButton } from '@sdcorejs/angular/components/button';
|
|
616
|
-
import { SdModal } from '@sdcorejs/angular/components/modal';
|
|
617
|
-
import { SdSection } from '@sdcorejs/angular/components/section';
|
|
618
|
-
import { SdInput, SdSelect } from '@sdcorejs/angular/forms';
|
|
619
|
-
|
|
620
|
-
interface Product {
|
|
621
|
-
id: number;
|
|
622
|
-
code: string;
|
|
623
|
-
name: string;
|
|
624
|
-
price: number;
|
|
625
|
-
status: 'ACTIVE' | 'INACTIVE';
|
|
626
|
-
}
|
|
107
|
+
```ts
|
|
108
|
+
import { Component } from '@angular/core';
|
|
109
|
+
import { FormGroup } from '@angular/forms';
|
|
110
|
+
import { SdInput } from '@sdcorejs/angular/forms/input';
|
|
627
111
|
|
|
628
112
|
@Component({
|
|
629
|
-
selector: 'app-
|
|
630
|
-
templateUrl: './product-list.component.html',
|
|
113
|
+
selector: 'app-customer-form',
|
|
631
114
|
standalone: true,
|
|
632
|
-
imports: [
|
|
115
|
+
imports: [SdInput],
|
|
116
|
+
template: `
|
|
117
|
+
<sd-input [form]="customerForm" name="customerName" label="Customer name" required maxlength="100" [(model)]="customer.name" />
|
|
118
|
+
`,
|
|
633
119
|
})
|
|
634
|
-
export class
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
selectedItem: Product | null = null;
|
|
639
|
-
formData: Partial<Product> = {};
|
|
640
|
-
isSaving = signal(false);
|
|
641
|
-
|
|
642
|
-
readonly STATUS_LIST = [
|
|
643
|
-
{ value: 'ACTIVE', label: 'Hoạt động' },
|
|
644
|
-
{ value: 'INACTIVE', label: 'Dừng' },
|
|
645
|
-
];
|
|
646
|
-
|
|
647
|
-
option!: SdTableOption<Product>;
|
|
648
|
-
|
|
649
|
-
constructor(private service: ProductService) {}
|
|
650
|
-
|
|
651
|
-
ngOnInit() {
|
|
652
|
-
this.option = {
|
|
653
|
-
type: 'server',
|
|
654
|
-
items: async (filter, paging) => this.service.search(filter, paging),
|
|
655
|
-
columns: [
|
|
656
|
-
{ field: 'code', type: 'string', title: 'Mã', width: '120px' },
|
|
657
|
-
{ field: 'name', type: 'string', title: 'Tên', sortable: true },
|
|
658
|
-
{ field: 'price', type: 'number', title: 'Đơn giá', align: 'right' },
|
|
659
|
-
{
|
|
660
|
-
field: 'status',
|
|
661
|
-
type: 'values',
|
|
662
|
-
title: 'Trạng thái',
|
|
663
|
-
option: { items: this.STATUS_LIST, valueField: 'value', displayField: 'label' },
|
|
664
|
-
useBadge: val => ({
|
|
665
|
-
color: val === 'ACTIVE' ? 'success' : 'secondary',
|
|
666
|
-
title: this.STATUS_LIST.find(s => s.value === val)?.label,
|
|
667
|
-
}),
|
|
668
|
-
},
|
|
669
|
-
],
|
|
670
|
-
command: {
|
|
671
|
-
align: 'right',
|
|
672
|
-
commands: [
|
|
673
|
-
{ icon: 'edit', color: 'primary', title: 'Sửa', click: row => this.openForm(row) },
|
|
674
|
-
{ icon: 'delete', color: 'error', title: 'Xoá', click: row => this.onDelete(row) },
|
|
675
|
-
],
|
|
676
|
-
},
|
|
677
|
-
paginate: { pageSize: 20 },
|
|
678
|
-
reload: { visible: true },
|
|
679
|
-
};
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
openForm(item?: Product) {
|
|
683
|
-
this.selectedItem = item || null;
|
|
684
|
-
this.formData = item ? { ...item } : { status: 'ACTIVE' };
|
|
685
|
-
this.formModal.open();
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
async onSave() {
|
|
689
|
-
this.isSaving.set(true);
|
|
690
|
-
try {
|
|
691
|
-
if (this.selectedItem) {
|
|
692
|
-
await this.service.update(this.selectedItem.id, this.formData);
|
|
693
|
-
} else {
|
|
694
|
-
await this.service.create(this.formData);
|
|
695
|
-
}
|
|
696
|
-
this.formModal.close();
|
|
697
|
-
this.sdTable?.reload?.();
|
|
698
|
-
} finally {
|
|
699
|
-
this.isSaving.set(false);
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
async onDelete(item: Product) {
|
|
704
|
-
if (!confirm(`Xoá "${item.name}"?`)) return;
|
|
705
|
-
await this.service.delete(item.id);
|
|
706
|
-
this.sdTable?.reload?.();
|
|
707
|
-
}
|
|
120
|
+
export class CustomerFormComponent {
|
|
121
|
+
readonly customerForm = new FormGroup({});
|
|
122
|
+
readonly customer = { name: '' };
|
|
708
123
|
}
|
|
709
124
|
```
|
|
710
125
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
```html
|
|
714
|
-
<!-- product-list.component.html -->
|
|
715
|
-
<div class="d-flex justify-content-between align-items-center mb-16">
|
|
716
|
-
<h2>Danh sách sản phẩm</h2>
|
|
717
|
-
<sd-button type="fill" color="primary" title="Thêm mới" prefixIcon="add" (click)="openForm()" />
|
|
718
|
-
</div>
|
|
719
|
-
|
|
720
|
-
<sd-table #sdTable [option]="option" />
|
|
721
|
-
|
|
722
|
-
<sd-modal #formModal [title]="selectedItem ? 'Chỉnh sửa' : 'Thêm mới'" width="md">
|
|
723
|
-
<ng-template>
|
|
724
|
-
<div class="modal-body p-16">
|
|
725
|
-
<sd-section title="Thông tin sản phẩm" icon="inventory">
|
|
726
|
-
<div class="row">
|
|
727
|
-
<div class="col-6">
|
|
728
|
-
<sd-input [(model)]="formData.code" label="Mã" [required]="true" />
|
|
729
|
-
</div>
|
|
730
|
-
<div class="col-6">
|
|
731
|
-
<sd-select [(model)]="formData.status" label="Trạng thái" [items]="STATUS_LIST" valueField="value" displayField="label" />
|
|
732
|
-
</div>
|
|
733
|
-
<div class="col-12">
|
|
734
|
-
<sd-input [(model)]="formData.name" label="Tên sản phẩm" [required]="true" />
|
|
735
|
-
</div>
|
|
736
|
-
<div class="col-6">
|
|
737
|
-
<sd-input-number [(model)]="formData.price" label="Đơn giá" suffix="VNĐ" />
|
|
738
|
-
</div>
|
|
739
|
-
</div>
|
|
740
|
-
</sd-section>
|
|
741
|
-
</div>
|
|
742
|
-
<div class="modal-footer d-flex justify-content-end gap-8 p-16">
|
|
743
|
-
<sd-button title="Huỷ" (click)="formModal.close()" />
|
|
744
|
-
<sd-button type="fill" color="primary" title="Lưu" prefixIcon="save" [loading]="isSaving()" (click)="onSave()" />
|
|
745
|
-
</div>
|
|
746
|
-
</ng-template>
|
|
747
|
-
</sd-modal>
|
|
748
|
-
```
|
|
126
|
+
## Theming
|
|
749
127
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
## Contributing Guide / Hướng dẫn đóng góp
|
|
753
|
-
|
|
754
|
-
### Cấu trúc thư viện / Project structure
|
|
755
|
-
|
|
756
|
-
```
|
|
757
|
-
sdcorejs-angular/
|
|
758
|
-
├── src/
|
|
759
|
-
│ └── public-api.ts # Entry point chính
|
|
760
|
-
├── assets/
|
|
761
|
-
│ └── scss/
|
|
762
|
-
│ ├── sd-core.scss # SCSS entry (import vào app)
|
|
763
|
-
│ ├── core/ # Base utilities (color, grid, form, ...)
|
|
764
|
-
│ └── themes/ # Theme mặc định + Material theme
|
|
765
|
-
├── components/ # UI Components
|
|
766
|
-
│ ├── button/
|
|
767
|
-
│ ├── table/
|
|
768
|
-
│ ├── modal/
|
|
769
|
-
│ └── ...
|
|
770
|
-
├── forms/ # Form components
|
|
771
|
-
│ ├── input/
|
|
772
|
-
│ ├── select/
|
|
773
|
-
│ └── ...
|
|
774
|
-
├── directives/ # Angular directives
|
|
775
|
-
├── pipes/ # Angular pipes
|
|
776
|
-
├── services/ # Shared services
|
|
777
|
-
├── utilities/ # Types, models, helpers
|
|
778
|
-
└── modules/ # Feature modules (layout, permission, ...)
|
|
779
|
-
```
|
|
780
|
-
|
|
781
|
-
### Thêm component mới / Adding a new component
|
|
782
|
-
|
|
783
|
-
**1. Tạo thư mục component:**
|
|
784
|
-
|
|
785
|
-
```
|
|
786
|
-
components/
|
|
787
|
-
└── my-component/
|
|
788
|
-
├── index.ts # Export public API
|
|
789
|
-
├── ng-package.json # ng-packagr entry
|
|
790
|
-
└── src/
|
|
791
|
-
├── my-component.component.ts
|
|
792
|
-
├── my-component.component.html
|
|
793
|
-
└── my-component.component.scss
|
|
794
|
-
```
|
|
128
|
+
`sd-core.scss` loads the reset, utilities, bundled fonts, semantic colors, form styles, and Angular Material theme baseline. Override public semantic colors with `sd.theme()`:
|
|
795
129
|
|
|
796
|
-
|
|
130
|
+
```scss
|
|
131
|
+
@use '@sdcorejs/angular/assets/scss/sd-core';
|
|
132
|
+
@use '@sdcorejs/angular/assets/scss/themes/default' as sd;
|
|
797
133
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
134
|
+
html {
|
|
135
|
+
@include sd.theme(
|
|
136
|
+
(
|
|
137
|
+
primary: #2563eb,
|
|
138
|
+
primary-light: #dbeafe,
|
|
139
|
+
primary-dark: #1d4ed8,
|
|
140
|
+
)
|
|
141
|
+
);
|
|
804
142
|
}
|
|
805
143
|
```
|
|
806
144
|
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
```typescript
|
|
810
|
-
export * from './src/my-component.component';
|
|
811
|
-
```
|
|
145
|
+
See the [assets and SCSS reference](https://sdcorejs.github.io/sdcorejs-angular/docs/latest/assets/STYLE-GUIDE.md) for supported `--sd-*` tokens, Material M3 guidance, utilities, fonts, and image assets.
|
|
812
146
|
|
|
813
|
-
|
|
147
|
+
## Internationalization
|
|
814
148
|
|
|
815
|
-
|
|
816
|
-
// my-component.component.ts
|
|
817
|
-
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
|
|
818
|
-
import { SdBaseSecureComponent } from '@sdcorejs/angular/components/base';
|
|
819
|
-
import { Color } from '@sdcorejs/angular/utilities';
|
|
149
|
+
Set the default Core UI language through `SD_CORE_CONFIGURATION`:
|
|
820
150
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
styleUrl: './my-component.component.scss',
|
|
825
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
826
|
-
standalone: true,
|
|
827
|
-
imports: [],
|
|
828
|
-
})
|
|
829
|
-
export class SdMyComponent extends SdBaseSecureComponent {
|
|
830
|
-
color = input<Color, Color | undefined | null>('primary', {
|
|
831
|
-
transform: value => value || 'primary',
|
|
832
|
-
});
|
|
833
|
-
|
|
834
|
-
title = input<string | undefined | null>(undefined);
|
|
835
|
-
}
|
|
836
|
-
```
|
|
151
|
+
```ts
|
|
152
|
+
import { ApplicationConfig } from '@angular/core';
|
|
153
|
+
import { type ISdCoreConfiguration, SD_CORE_CONFIGURATION } from '@sdcorejs/angular/configurations';
|
|
837
154
|
|
|
838
|
-
|
|
155
|
+
const sdCoreConfig = {
|
|
156
|
+
language: 'en',
|
|
157
|
+
} satisfies ISdCoreConfiguration;
|
|
839
158
|
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
159
|
+
export const appConfig: ApplicationConfig = {
|
|
160
|
+
providers: [{ provide: SD_CORE_CONFIGURATION, useValue: sdCoreConfig }],
|
|
161
|
+
};
|
|
843
162
|
```
|
|
844
163
|
|
|
845
|
-
|
|
164
|
+
`I18nService.setLanguage()` persists a built-in language and reloads by default. A complete custom catalog can be supplied through the synchronous `language: () => catalog` hook. See the [i18n reference](https://sdcorejs.github.io/sdcorejs-angular/docs/latest/i18n/i18n.md) for catalog typing and fallback behavior.
|
|
846
165
|
|
|
847
|
-
|
|
848
|
-
| ---------------- | ------------------------------------------------------------- |
|
|
849
|
-
| Selector | `sd-<tên-component>` |
|
|
850
|
-
| Class name | `Sd<TênComponent>` (Pascal) |
|
|
851
|
-
| Input | Dùng `input<T>()` signal, **không** dùng `@Input()` decorator |
|
|
852
|
-
| Null safety | Input transform phải handle `null/undefined` |
|
|
853
|
-
| Base class | Extend `SdBaseSecureComponent` cho component có permission |
|
|
854
|
-
| Change detection | Luôn dùng `ChangeDetectionStrategy.OnPush` |
|
|
855
|
-
| Standalone | Luôn `standalone: true` |
|
|
856
|
-
| Colors | Dùng `Color` type, không hardcode màu |
|
|
166
|
+
## Documentation and examples
|
|
857
167
|
|
|
858
|
-
|
|
168
|
+
| Resource | Purpose |
|
|
169
|
+
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------- |
|
|
170
|
+
| [Live showcase](https://sdcorejs.github.io/sdcorejs-angular/) | Interactive component, form, and service demos |
|
|
171
|
+
| [Button example source](https://github.com/sdcorejs/sdcorejs-angular/blob/main/versions/v19/projects/showcase/src/app/pages/components/button/button-demo.component.ts) | Action variants, icons, disabled state, and loading state |
|
|
172
|
+
| [Input example source](https://github.com/sdcorejs/sdcorejs-angular/blob/main/versions/v19/projects/showcase/src/app/pages/forms/input/input-demo.component.ts) | Model binding, validation, and viewed states |
|
|
173
|
+
| [Table example source](https://github.com/sdcorejs/sdcorejs-angular/blob/main/versions/v19/projects/showcase/src/app/pages/components/table/table-demo.component.ts) | Data, selection, filters, grouping, paging, and tree workflows |
|
|
174
|
+
| [Persistence example source](https://github.com/sdcorejs/sdcorejs-angular/blob/main/versions/v19/projects/showcase/src/app/pages/services/persistence/persistence-demo.component.ts) | Graph round-trip, identity, envelopes, and error containment |
|
|
175
|
+
| [Latest API manifest](https://sdcorejs.github.io/sdcorejs-angular/docs/latest/index.json) | Discover all Markdown docs for the latest release |
|
|
176
|
+
| [Versions registry](https://sdcorejs.github.io/sdcorejs-angular/docs/versions.json) | Select docs matching an installed package version |
|
|
177
|
+
| [Machine-readable catalog](https://sdcorejs.github.io/sdcorejs-angular/docs/catalog.json) | Discover documentation across maintained package lines |
|
|
178
|
+
| [E2E attributes](https://github.com/sdcorejs/sdcorejs-angular/blob/main/versions/v19/projects/sdcorejs-angular/docs/E2E-ATTRIBUTES.md) | Stable runtime selectors and state attributes |
|
|
179
|
+
| [1.4 migration guide](https://github.com/sdcorejs/sdcorejs-angular/blob/main/docs/migrations/1.4.md) | Dedupe, loading, persistence, connector, responsive, and signal migrations |
|
|
859
180
|
|
|
860
|
-
|
|
861
|
-
# Build toàn bộ thư viện
|
|
862
|
-
ng-packagr -p ng-package.json
|
|
181
|
+
## Versioning
|
|
863
182
|
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
183
|
+
- Use `@sdcorejs/angular@^19`, `@^20`, or `@^21` to match the application's Angular major.
|
|
184
|
+
- Maintained package lines are released from the same feature surface with required Angular-major adaptations.
|
|
185
|
+
- Consumer-breaking changes and migration notes are recorded in the [changelog](https://github.com/sdcorejs/sdcorejs-angular/blob/main/CHANGELOG.md).
|
|
186
|
+
- Before adopting the `1.4` suffix, follow the [1.4 migration guide](https://github.com/sdcorejs/sdcorejs-angular/blob/main/docs/migrations/1.4.md).
|
|
187
|
+
- Version-pinned reference docs remain available under `https://sdcorejs.github.io/sdcorejs-angular/docs/<package-version>/`.
|
|
867
188
|
|
|
868
|
-
|
|
189
|
+
## Contributing
|
|
869
190
|
|
|
870
|
-
|
|
191
|
+
Contributions are welcome through focused pull requests. See the [repository contribution workflow](https://github.com/sdcorejs/sdcorejs-angular#contributing) for setup, validation, and source-workspace guidance.
|
|
871
192
|
|
|
872
|
-
|
|
873
|
-
- Breaking change được ghi rõ ở `CHANGELOG.md`, mục **Changed (BREAKING for consumers)**, kèm migration.
|
|
193
|
+
## Support
|
|
874
194
|
|
|
875
|
-
|
|
195
|
+
Use [GitHub Issues](https://github.com/sdcorejs/sdcorejs-angular/issues) for reproducible bugs and focused feature proposals. Include the Angular major, package version, a minimal reproduction, and the expected behavior.
|
|
876
196
|
|
|
877
|
-
##
|
|
197
|
+
## Maintainer
|
|
878
198
|
|
|
879
|
-
|
|
199
|
+
**Trần Thuận Nghĩa** — Full Stack Developer and maintainer of `@sdcorejs/angular`. He builds practical, strongly typed web applications and reusable tools that make complex business workflows easier to deliver and maintain.
|
|
880
200
|
|
|
881
|
-
|
|
201
|
+
[SDCoreJS on GitHub](https://github.com/sdcorejs) · [LinkedIn](https://www.linkedin.com/in/tran-thuan-nghia/) · [Email](mailto:tran.thuan.nghia@gmail.com)
|
|
882
202
|
|
|
883
203
|
## License
|
|
884
204
|
|
|
885
|
-
MIT
|
|
205
|
+
MIT © Trần Thuận Nghĩa. See the [repository license](https://github.com/sdcorejs/sdcorejs-angular/blob/main/LICENSE).
|