@uni-design-system/uni-angular 1.0.0 → 2.0.0
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/.storybook/main.ts +33 -16
- package/.storybook/manager.ts +24 -0
- package/.storybook/preview.ts +10 -3
- package/.storybook/tsconfig.json +7 -4
- package/.turbo/turbo-build.log +3 -3
- package/CHANGELOG.md +7 -0
- package/dist/fesm2022/uni-design-system-uni-angular.mjs +2 -2
- package/dist/fesm2022/uni-design-system-uni-angular.mjs.map +1 -1
- package/package.json +14 -10
- package/src/lib/button.component.ts +12 -5
- package/src/lib/cdk/datasource/base-datasource.ts +113 -0
- package/src/lib/cdk/datasource/datasource.types.ts +10 -0
- package/src/lib/cdk/datasource/record-datasource.ts +115 -0
- package/src/lib/cdk/datasource/server-side-datasource.ts +172 -0
- package/src/lib/cdk/helpers/memoize.helper.ts +15 -0
- package/src/lib/cdk/helpers/number.helper.ts +2 -0
- package/src/lib/cdk/index.ts +3 -0
- package/src/lib/cdk/local-storage/local-storage.service.ts +124 -0
- package/src/lib/cdk/option/option.model.ts +6 -0
- package/src/lib/cdk/timer/timer.ts +66 -0
- package/src/lib/components/text/text.component.ts +57 -0
- package/src/lib/components/text/text.mdx +15 -0
- package/src/lib/components/text/text.stories.ts +39 -0
- package/src/lib/theming/theme.service.ts +270 -0
- package/src/lib/theming/theme.token.ts +7 -0
- package/src/stories/blocks/StoryUsage.tsx +21 -0
- package/tsconfig.json +6 -4
package/.storybook/main.ts
CHANGED
|
@@ -1,24 +1,41 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { dirname } from 'path';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { mergeConfig } from 'vite';
|
|
4
|
+
import type { StorybookConfig as StorybookConfigBase } from '@storybook/angular';
|
|
5
|
+
import type { StorybookConfigVite } from '@storybook/builder-vite';
|
|
4
6
|
|
|
5
7
|
function getAbsolutePath(value: string) {
|
|
6
|
-
return dirname(fileURLToPath(import.meta.resolve(`${value}/package.json`)))
|
|
8
|
+
return dirname(fileURLToPath(import.meta.resolve(`${value}/package.json`)));
|
|
7
9
|
}
|
|
8
10
|
|
|
11
|
+
type StorybookConfig = StorybookConfigBase & StorybookConfigVite;
|
|
12
|
+
|
|
9
13
|
const config: StorybookConfig = {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
|
15
|
+
addons: [getAbsolutePath('@storybook/addon-a11y'), getAbsolutePath('@storybook/addon-docs')],
|
|
16
|
+
framework: {
|
|
17
|
+
name: getAbsolutePath('@storybook/angular') as '@storybook/angular',
|
|
18
|
+
options: {
|
|
19
|
+
builder: {
|
|
20
|
+
name: getAbsolutePath('@storybook/builder-vite') as '@storybook/builder-vite',
|
|
21
|
+
options: {},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
staticDirs: ['../../../public'],
|
|
26
|
+
managerHead: (head) => `${head}<link rel="icon" href="/favicon.ico" />`,
|
|
27
|
+
async viteFinal(config) {
|
|
28
|
+
return mergeConfig(config, {
|
|
29
|
+
optimizeDeps: {
|
|
30
|
+
exclude: ['@angular/animations', '@angular/animations/browser'],
|
|
31
|
+
},
|
|
32
|
+
build: {
|
|
33
|
+
rollupOptions: {
|
|
34
|
+
external: ['@angular/animations', '@angular/animations/browser'],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
},
|
|
22
39
|
};
|
|
23
40
|
|
|
24
41
|
export default config;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { addons } from 'storybook/manager-api';
|
|
2
|
+
import { create } from 'storybook/theming';
|
|
3
|
+
|
|
4
|
+
const theme = create({
|
|
5
|
+
base: 'light',
|
|
6
|
+
brandTitle: 'UNI Design System',
|
|
7
|
+
brandUrl: 'https://uni-design-system.github.io/uni/docs/angular',
|
|
8
|
+
brandImage: 'uni-storybook-logo.png',
|
|
9
|
+
|
|
10
|
+
appBg: 'white',
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
addons.setConfig({
|
|
14
|
+
theme,
|
|
15
|
+
sidebar: {
|
|
16
|
+
// This filter excludes individual stories from the sidebar list,
|
|
17
|
+
// leaving only the MDX documentation pages visible.
|
|
18
|
+
filters: {
|
|
19
|
+
patterns: (item) => {
|
|
20
|
+
return item.type === 'docs';
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
});
|
package/.storybook/preview.ts
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { applicationConfig, type Preview } from '@storybook/angular';
|
|
2
|
+
import { UNI_THEMES } from '../src/lib/theming/theme.token';
|
|
3
|
+
import { UniThemes } from '@uni-design-system/uni-core';
|
|
2
4
|
|
|
3
5
|
const preview: Preview = {
|
|
4
6
|
parameters: {
|
|
5
7
|
controls: {
|
|
6
8
|
matchers: {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
color: /(background|color)$/i,
|
|
10
|
+
date: /Date$/i,
|
|
9
11
|
},
|
|
10
12
|
},
|
|
11
13
|
},
|
|
14
|
+
decorators: [
|
|
15
|
+
applicationConfig({
|
|
16
|
+
providers: [{ provide: UNI_THEMES, useValue: UniThemes }],
|
|
17
|
+
}),
|
|
18
|
+
],
|
|
12
19
|
};
|
|
13
20
|
|
|
14
21
|
export default preview;
|
package/.storybook/tsconfig.json
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
"moduleResolution": "bundler",
|
|
5
5
|
"module": "ESNext",
|
|
6
6
|
"esModuleInterop": true,
|
|
7
|
-
"types": ["node"],
|
|
7
|
+
"types": ["node", "vite/client"],
|
|
8
|
+
"jsx": "react-jsx",
|
|
8
9
|
"allowSyntheticDefaultImports": true,
|
|
9
10
|
"resolveJsonModule": true,
|
|
10
11
|
"experimentalDecorators": true,
|
|
@@ -12,10 +13,12 @@
|
|
|
12
13
|
},
|
|
13
14
|
"exclude": ["../src/test.ts", "../src/**/*.spec.ts"],
|
|
14
15
|
"include": [
|
|
15
|
-
"../src/**/*.ts",
|
|
16
|
-
"../src/**/*.
|
|
16
|
+
"../src/**/*.ts",
|
|
17
|
+
"../src/**/*.tsx",
|
|
18
|
+
"../src/**/*.stories.ts",
|
|
17
19
|
"./preview.ts",
|
|
18
|
-
"./main.ts"
|
|
20
|
+
"./main.ts",
|
|
21
|
+
"./vite.config.ts"
|
|
19
22
|
],
|
|
20
23
|
"files": ["./typings.d.ts"]
|
|
21
24
|
}
|
package/.turbo/turbo-build.log
CHANGED
|
@@ -12,14 +12,14 @@ Building entry point '@uni-design-system/uni-angular'
|
|
|
12
12
|
- Writing package manifest
|
|
13
13
|
[34mℹ[39m Removing scripts section in package.json as it's considered a potential security vulnerability.
|
|
14
14
|
[34mℹ[39m Removing devDependencies section in package.json.
|
|
15
|
-
|
|
16
|
-
------------------------------------------------------------------------------
|
|
17
15
|
[32m✔[39m Writing package manifest
|
|
18
16
|
[32m✔[39m Built @uni-design-system/uni-angular
|
|
17
|
+
|
|
18
|
+
------------------------------------------------------------------------------
|
|
19
19
|
Built Angular Package
|
|
20
20
|
- from: /home/runner/work/uni/uni/packages/angular
|
|
21
21
|
- to: /home/runner/work/uni/uni/packages/angular/dist
|
|
22
22
|
------------------------------------------------------------------------------
|
|
23
23
|
|
|
24
|
-
Build at: 2026-05-
|
|
24
|
+
Build at: 2026-05-19T14:44:38.737Z - Time: 2418ms
|
|
25
25
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# @uni-design-system/uni-angular
|
|
2
2
|
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [[`4a049de`](https://github.com/uni-design-system/uni/commit/4a049def689d56d6b6cc1d2da73c9facd93ed515)]:
|
|
8
|
+
- @uni-design-system/uni-core@1.1.0
|
|
9
|
+
|
|
3
10
|
## 1.0.0
|
|
4
11
|
|
|
5
12
|
### Major Changes
|
|
@@ -4,8 +4,8 @@ import { CommonModule } from '@angular/common';
|
|
|
4
4
|
|
|
5
5
|
class UniButtonComponent {
|
|
6
6
|
label = 'Button';
|
|
7
|
-
bgColor =
|
|
8
|
-
textColor =
|
|
7
|
+
bgColor = '#000000';
|
|
8
|
+
textColor = '#fff';
|
|
9
9
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
10
10
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.12", type: UniButtonComponent, isStandalone: true, selector: "uni-button", inputs: { label: "label" }, ngImport: i0, template: `
|
|
11
11
|
<button [style.background-color]="bgColor" [style.color]="textColor">
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uni-design-system-uni-angular.mjs","sources":["../../src/lib/button.component.ts","../../src/uni-design-system-uni-angular.ts"],"sourcesContent":["import { Component, Input } from '@angular/core';\nimport { CommonModule } from '@angular/common'; // Still useful for pipes/directives\n\n@Component({\n selector: 'uni-button',\n standalone: true, // Optional but explicit\n imports: [CommonModule],\n template: `\n <button [style.background-color]=\"bgColor\" [style.color]=\"textColor\">\n {{ label }}\n </button>\n `,\n styles: [`\n
|
|
1
|
+
{"version":3,"file":"uni-design-system-uni-angular.mjs","sources":["../../src/lib/button.component.ts","../../src/uni-design-system-uni-angular.ts"],"sourcesContent":["import { Component, Input } from '@angular/core';\nimport { CommonModule } from '@angular/common'; // Still useful for pipes/directives\n\n@Component({\n selector: 'uni-button',\n standalone: true, // Optional but explicit\n imports: [CommonModule],\n template: `\n <button [style.background-color]=\"bgColor\" [style.color]=\"textColor\">\n {{ label }}\n </button>\n `,\n styles: [\n `\n button {\n padding: 12px 24px;\n border-radius: 4px;\n border: none;\n cursor: pointer;\n }\n `,\n ],\n})\nexport class UniButtonComponent {\n @Input() label: string = 'Button';\n bgColor = '#000000';\n textColor = '#fff';\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;MAuBa,kBAAkB,CAAA;IACpB,KAAK,GAAW,QAAQ;IACjC,OAAO,GAAG,SAAS;IACnB,SAAS,GAAG,MAAM;wGAHP,kBAAkB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAlB,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,kBAAkB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,YAAA,EAAA,MAAA,EAAA,EAAA,KAAA,EAAA,OAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAhBnB;;;;AAIT,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,0EAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EALS,YAAY,EAAA,CAAA,EAAA,CAAA;;4FAiBX,kBAAkB,EAAA,UAAA,EAAA,CAAA;kBApB9B,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,YAAY,cACV,IAAI,EAAA,OAAA,EACP,CAAC,YAAY,CAAC,EAAA,QAAA,EACb;;;;AAIT,EAAA,CAAA,EAAA,MAAA,EAAA,CAAA,0EAAA,CAAA,EAAA;;sBAaA;;;ACxBH;;AAEG;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uni-design-system/uni-angular",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/uni-design-system/uni",
|
|
@@ -14,10 +14,11 @@
|
|
|
14
14
|
"peerDependencies": {
|
|
15
15
|
"@angular/common": "21.2.12",
|
|
16
16
|
"@angular/core": "21.2.12",
|
|
17
|
-
"@
|
|
17
|
+
"@emotion/css": "^11.0.0",
|
|
18
|
+
"@uni-design-system/uni-core": "1.1.0"
|
|
18
19
|
},
|
|
19
20
|
"devDependencies": {
|
|
20
|
-
"@analogjs/storybook-angular": "^2.5.
|
|
21
|
+
"@analogjs/storybook-angular": "^2.5.1",
|
|
21
22
|
"@analogjs/vite-plugin-angular": "^2.5.0",
|
|
22
23
|
"@angular-devkit/architect": "^0.2102.10",
|
|
23
24
|
"@angular-devkit/build-angular": "^21.2.10",
|
|
@@ -25,12 +26,15 @@
|
|
|
25
26
|
"@angular/compiler": "21.2.12",
|
|
26
27
|
"@angular/compiler-cli": "21.2.12",
|
|
27
28
|
"@angular/platform-browser-dynamic": "21.2.12",
|
|
28
|
-
"@
|
|
29
|
-
"@storybook/addon-
|
|
30
|
-
"@storybook/addon-
|
|
31
|
-
"@storybook/angular": "^10.
|
|
29
|
+
"@emotion/css": "^11.0.0",
|
|
30
|
+
"@storybook/addon-a11y": "^10.4.0",
|
|
31
|
+
"@storybook/addon-docs": "^10.4.0",
|
|
32
|
+
"@storybook/angular": "^10.4.0",
|
|
33
|
+
"@storybook/builder-vite": "^10.4.0",
|
|
34
|
+
"@types/react": "^18.3.28",
|
|
32
35
|
"ng-packagr": "^21.0.0",
|
|
33
|
-
"
|
|
36
|
+
"react": "^18.2.0",
|
|
37
|
+
"storybook": "^10.4.0",
|
|
34
38
|
"tslib": "^2.8.1",
|
|
35
39
|
"typescript": "5.9.3",
|
|
36
40
|
"vite": "^8.0.10",
|
|
@@ -39,8 +43,8 @@
|
|
|
39
43
|
},
|
|
40
44
|
"scripts": {
|
|
41
45
|
"build": "pnpm ng-packagr -p ng-package.json",
|
|
42
|
-
"storybook": "ng run @uni-design-system/uni-angular:storybook",
|
|
43
|
-
"build-storybook": "
|
|
46
|
+
"storybook": "pnpm ng run @uni-design-system/uni-angular:storybook",
|
|
47
|
+
"build-storybook": "pnpm dlx @angular/cli run @uni-design-system/uni-angular:build-storybook",
|
|
44
48
|
"release": "pnpm build && pnpm changeset publish"
|
|
45
49
|
}
|
|
46
50
|
}
|
|
@@ -10,12 +10,19 @@ import { CommonModule } from '@angular/common'; // Still useful for pipes/direct
|
|
|
10
10
|
{{ label }}
|
|
11
11
|
</button>
|
|
12
12
|
`,
|
|
13
|
-
styles: [
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
styles: [
|
|
14
|
+
`
|
|
15
|
+
button {
|
|
16
|
+
padding: 12px 24px;
|
|
17
|
+
border-radius: 4px;
|
|
18
|
+
border: none;
|
|
19
|
+
cursor: pointer;
|
|
20
|
+
}
|
|
21
|
+
`,
|
|
22
|
+
],
|
|
16
23
|
})
|
|
17
24
|
export class UniButtonComponent {
|
|
18
25
|
@Input() label: string = 'Button';
|
|
19
|
-
bgColor =
|
|
20
|
-
textColor =
|
|
26
|
+
bgColor = '#000000';
|
|
27
|
+
textColor = '#fff';
|
|
21
28
|
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { computed, signal, Signal, WritableSignal } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
export type SortDirection = 'asc' | 'desc' | 'indet';
|
|
4
|
+
|
|
5
|
+
export interface Sort<T> {
|
|
6
|
+
column?: keyof T;
|
|
7
|
+
direction: SortDirection;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export abstract class UniBaseDatasource<T> {
|
|
11
|
+
abstract records: Signal<T[]>;
|
|
12
|
+
abstract recordCount: Signal<number>;
|
|
13
|
+
|
|
14
|
+
selections = signal<T[]>([]);
|
|
15
|
+
|
|
16
|
+
sort: WritableSignal<Sort<T>> = signal({
|
|
17
|
+
column: undefined,
|
|
18
|
+
direction: 'indet',
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
sortColumn = computed(() => this.sort().column);
|
|
22
|
+
sortDirection = computed(() => this.sort().direction);
|
|
23
|
+
|
|
24
|
+
abstract pageIndex: Signal<number>;
|
|
25
|
+
abstract pageSize: Signal<number>;
|
|
26
|
+
abstract pageCount: Signal<number>;
|
|
27
|
+
|
|
28
|
+
pages = computed(() => Array.from({ length: this.pageCount() }, (_, index) => index + 1));
|
|
29
|
+
|
|
30
|
+
startIndex = computed(() => {
|
|
31
|
+
const pageSize = this.pageSize();
|
|
32
|
+
const pageIndex = this.pageIndex();
|
|
33
|
+
return pageSize > 0 ? pageIndex * pageSize : 0;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
endIndex = computed(() => {
|
|
37
|
+
const pageSize = this.pageSize();
|
|
38
|
+
const total = this.recordCount();
|
|
39
|
+
|
|
40
|
+
// If unpaginated (pageSize = 0), return total count
|
|
41
|
+
if (pageSize === 0) {
|
|
42
|
+
return total;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const start = this.startIndex();
|
|
46
|
+
return Math.min(start + pageSize, total);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
disablePrevious = computed(() => {
|
|
50
|
+
const pageSize = this.pageSize();
|
|
51
|
+
return pageSize === 0 ? true : this.pageIndex() === 0;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
disableNext = computed(() => {
|
|
55
|
+
const pageSize = this.pageSize();
|
|
56
|
+
return pageSize === 0 ? true : this.pageIndex() + 1 >= this.pageCount();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
truncatedPages = computed(() => {
|
|
60
|
+
const currentPage = this.pageIndex() + 1;
|
|
61
|
+
const displayRange = 3;
|
|
62
|
+
const totalPages = this.pageCount();
|
|
63
|
+
|
|
64
|
+
const pages: (number | string)[] = [];
|
|
65
|
+
const startPage = Math.max(1, currentPage - displayRange);
|
|
66
|
+
const endPage = Math.min(totalPages, currentPage + displayRange);
|
|
67
|
+
|
|
68
|
+
if (startPage > 1) {
|
|
69
|
+
pages.push(1);
|
|
70
|
+
if (startPage > 2) {
|
|
71
|
+
pages.push('...');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (let i = startPage; i <= endPage; i++) {
|
|
76
|
+
pages.push(i);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (endPage < totalPages) {
|
|
80
|
+
if (endPage < totalPages - 1) {
|
|
81
|
+
pages.push('...');
|
|
82
|
+
}
|
|
83
|
+
pages.push(totalPages);
|
|
84
|
+
}
|
|
85
|
+
return pages;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
isSelected(row: T): boolean {
|
|
89
|
+
return this.selections().some((selection) => selection === row);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
toggleSelection(row: T) {
|
|
93
|
+
this.selections.update((selections) => {
|
|
94
|
+
return selections.includes(row) ? selections.filter((i) => i !== row) : [...selections, row];
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
clearSelections() {
|
|
99
|
+
this.selections.set([]);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
selectAll() {
|
|
103
|
+
this.selections.set([...this.records()]);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
abstract sortRecords(sort: Sort<T>): void;
|
|
107
|
+
abstract firstPage(): void;
|
|
108
|
+
abstract nextPage(): void;
|
|
109
|
+
abstract previousPage(): void;
|
|
110
|
+
abstract lastPage(): void;
|
|
111
|
+
abstract jumpToPage(page: number): void;
|
|
112
|
+
abstract setPageSize(size: number): void;
|
|
113
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { UniBaseDatasource } from './base-datasource';
|
|
2
|
+
import { UniRecordDatasource } from './record-datasource';
|
|
3
|
+
import { UniServerSideDatasource } from './server-side-datasource';
|
|
4
|
+
|
|
5
|
+
export type UniDatasource<T> = UniBaseDatasource<T>;
|
|
6
|
+
|
|
7
|
+
export type { Sort, SortDirection } from './base-datasource';
|
|
8
|
+
export type { DataLoader, PageRequest, PageResponse } from './server-side-datasource';
|
|
9
|
+
|
|
10
|
+
export { UniBaseDatasource, UniRecordDatasource, UniServerSideDatasource };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { computed, signal } from '@angular/core';
|
|
2
|
+
import { UniBaseDatasource, Sort } from './base-datasource';
|
|
3
|
+
|
|
4
|
+
export class UniRecordDatasource<T> extends UniBaseDatasource<T> {
|
|
5
|
+
private _pageNumber = signal(1);
|
|
6
|
+
private _pageSize = signal<number>(0); // 0 = unpaginated (show all)
|
|
7
|
+
|
|
8
|
+
initialRecords = signal<T[]>([]);
|
|
9
|
+
override recordCount = computed((): number => this.initialRecords().filter(this.filter()).length);
|
|
10
|
+
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
12
|
+
filter = signal<(value: T) => boolean>((value) => true);
|
|
13
|
+
|
|
14
|
+
override pageIndex = signal<number>(0);
|
|
15
|
+
override pageSize = computed(() => this._pageSize());
|
|
16
|
+
|
|
17
|
+
override pageCount = computed(() => {
|
|
18
|
+
const pageSize = this.pageSize();
|
|
19
|
+
return pageSize > 0 ? Math.ceil(this.recordCount() / pageSize) : 1;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
override records = computed(() => {
|
|
23
|
+
const { column, direction } = this.sort();
|
|
24
|
+
const filter = this.filter();
|
|
25
|
+
const pageSize = this.pageSize();
|
|
26
|
+
|
|
27
|
+
let filteredRecords = this.initialRecords().filter(filter);
|
|
28
|
+
|
|
29
|
+
// Apply sorting if specified
|
|
30
|
+
if (column && direction !== 'indet') {
|
|
31
|
+
filteredRecords = [...filteredRecords].sort((a, b) => {
|
|
32
|
+
const valueA = a[column];
|
|
33
|
+
const valueB = b[column];
|
|
34
|
+
|
|
35
|
+
const isString = typeof valueA === 'string';
|
|
36
|
+
const isAsc = direction === 'asc';
|
|
37
|
+
|
|
38
|
+
return isString
|
|
39
|
+
? stringCompare(valueA as string, valueB as string, isAsc)
|
|
40
|
+
: numberCompare(valueA as number, valueB as number, isAsc);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Apply pagination only if pageSize > 0
|
|
45
|
+
if (pageSize > 0) {
|
|
46
|
+
return filteredRecords.slice(this.startIndex(), this.endIndex());
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Return all records (unpaginated)
|
|
50
|
+
return filteredRecords;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
constructor(data: T[]) {
|
|
54
|
+
super();
|
|
55
|
+
this.initialRecords.set(data);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
override sortRecords(sort: Sort<T>) {
|
|
59
|
+
this.sort.set(sort);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
override firstPage() {
|
|
63
|
+
this.pageIndex.set(0);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
override nextPage() {
|
|
67
|
+
if (this.pageIndex() < this.pageCount() - 1) {
|
|
68
|
+
this.pageIndex.update((i) => i + 1);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
override previousPage() {
|
|
73
|
+
this.pageIndex.update((i) => (i > 1 ? i - 1 : 0));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
override lastPage() {
|
|
77
|
+
this.pageIndex.set(this.pageCount() - 1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
override jumpToPage(page: number) {
|
|
81
|
+
const i = page - 1;
|
|
82
|
+
if (i >= 0 && i < this.pageCount()) {
|
|
83
|
+
this.pageIndex.set(i);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
override setPageSize(size: number) {
|
|
88
|
+
this._pageSize.set(size);
|
|
89
|
+
if (size > 0) {
|
|
90
|
+
this._pageNumber.set(1);
|
|
91
|
+
this.pageIndex.set(0);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
setFilter(filter: (value: T) => boolean) {
|
|
96
|
+
this.filter.set(filter);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
clearFilter() {
|
|
100
|
+
this.filter.set(() => true);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function stringCompare(a = 'zzz', b = 'zzz', isAsc: boolean): number {
|
|
105
|
+
a = a.toString();
|
|
106
|
+
b = b.toString();
|
|
107
|
+
|
|
108
|
+
return isAsc
|
|
109
|
+
? a.localeCompare(b, undefined, { sensitivity: 'base' })
|
|
110
|
+
: b.localeCompare(a, undefined, { sensitivity: 'base' });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function numberCompare(a = 0, b = 0, isAsc: boolean): number {
|
|
114
|
+
return isAsc ? a - b : b - a;
|
|
115
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
linkedSignal,
|
|
4
|
+
resource,
|
|
5
|
+
ResourceLoaderParams,
|
|
6
|
+
ResourceRef,
|
|
7
|
+
type ResourceStatus,
|
|
8
|
+
signal,
|
|
9
|
+
} from '@angular/core';
|
|
10
|
+
import { UniBaseDatasource, Sort, SortDirection } from './base-datasource';
|
|
11
|
+
|
|
12
|
+
export interface PageRequest {
|
|
13
|
+
pageNumber: number;
|
|
14
|
+
pageSize: number;
|
|
15
|
+
sortColumn?: string;
|
|
16
|
+
sortDirection?: SortDirection;
|
|
17
|
+
filter?: Record<string, any>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface PageResponse<T> {
|
|
21
|
+
data: T[];
|
|
22
|
+
totalRecords: number;
|
|
23
|
+
pageNumber: number;
|
|
24
|
+
pageSize: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type DataLoader<T> = (request: PageRequest) => Promise<PageResponse<T>>;
|
|
28
|
+
|
|
29
|
+
export class UniServerSideDatasource<T> extends UniBaseDatasource<T> {
|
|
30
|
+
private _pageNumber = signal(1);
|
|
31
|
+
private _pageSize = signal(10);
|
|
32
|
+
private _sortColumn = signal<keyof T | undefined>(undefined);
|
|
33
|
+
private _sortDirection = signal<SortDirection>('indet');
|
|
34
|
+
private filter = signal<Record<string, any>>({});
|
|
35
|
+
|
|
36
|
+
override sortColumn = computed(() => this._sortColumn());
|
|
37
|
+
override sortDirection = computed(() => this._sortDirection());
|
|
38
|
+
|
|
39
|
+
private totalRecords = signal(0);
|
|
40
|
+
|
|
41
|
+
private dataResource: ResourceRef<PageResponse<T> | undefined>;
|
|
42
|
+
|
|
43
|
+
override pageIndex = computed(() => this._pageNumber() - 1);
|
|
44
|
+
override pageSize = computed(() => this._pageSize());
|
|
45
|
+
|
|
46
|
+
override pageCount = computed(() => {
|
|
47
|
+
const total = this.totalRecords();
|
|
48
|
+
const size = this.pageSize();
|
|
49
|
+
return total > 0 ? Math.ceil(total / size) : 0;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
private readonly _records = linkedSignal<
|
|
53
|
+
{
|
|
54
|
+
val: PageResponse<T> | undefined;
|
|
55
|
+
status: ResourceStatus;
|
|
56
|
+
},
|
|
57
|
+
T[]
|
|
58
|
+
>({
|
|
59
|
+
source: () => ({
|
|
60
|
+
val: this.dataResource.value(),
|
|
61
|
+
status: this.dataResource.status(),
|
|
62
|
+
}),
|
|
63
|
+
computation: (source, previous) => {
|
|
64
|
+
if (source.status === 'loading' && previous) {
|
|
65
|
+
return previous.value;
|
|
66
|
+
}
|
|
67
|
+
return source.val?.data ?? [];
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
override records = this._records.asReadonly();
|
|
72
|
+
|
|
73
|
+
override recordCount = computed(() => this.totalRecords());
|
|
74
|
+
|
|
75
|
+
isLoading = computed(() => this.dataResource.isLoading());
|
|
76
|
+
|
|
77
|
+
error = computed(() => this.dataResource.error());
|
|
78
|
+
|
|
79
|
+
hasError = computed(() => this.dataResource.hasValue() === false && this.error() !== undefined);
|
|
80
|
+
|
|
81
|
+
constructor(
|
|
82
|
+
private dataLoader: DataLoader<T>,
|
|
83
|
+
initialPageSize = 10
|
|
84
|
+
) {
|
|
85
|
+
super();
|
|
86
|
+
this._pageSize.set(initialPageSize);
|
|
87
|
+
|
|
88
|
+
this.dataResource = resource({
|
|
89
|
+
params: () => ({
|
|
90
|
+
pageNumber: this._pageNumber(),
|
|
91
|
+
pageSize: this._pageSize(),
|
|
92
|
+
sortColumn: this._sortColumn() as string | undefined,
|
|
93
|
+
sortDirection: this._sortDirection(),
|
|
94
|
+
filter: this.filter(),
|
|
95
|
+
}),
|
|
96
|
+
loader: async (params: ResourceLoaderParams<PageRequest>) => {
|
|
97
|
+
const request = params.params;
|
|
98
|
+
const response = await this.dataLoader(request);
|
|
99
|
+
|
|
100
|
+
if (request.pageNumber === 1 || this.totalRecords() === 0) {
|
|
101
|
+
this.totalRecords.set(response.totalRecords);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return response;
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
override sortRecords(sort: Sort<T>) {
|
|
110
|
+
this.sort.set(sort);
|
|
111
|
+
this._sortColumn.set(sort.column);
|
|
112
|
+
this._sortDirection.set(sort.direction);
|
|
113
|
+
this._pageNumber.set(1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
override firstPage() {
|
|
117
|
+
this._pageNumber.set(1);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
override nextPage() {
|
|
121
|
+
if (this._pageNumber() < this.pageCount()) {
|
|
122
|
+
this._pageNumber.update((n) => n + 1);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
override previousPage() {
|
|
127
|
+
if (this._pageNumber() > 1) {
|
|
128
|
+
this._pageNumber.update((n) => n - 1);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
override lastPage() {
|
|
133
|
+
this._pageNumber.set(this.pageCount());
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
override jumpToPage(page: number) {
|
|
137
|
+
if (page >= 1 && page <= this.pageCount()) {
|
|
138
|
+
this._pageNumber.set(page);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
override setPageSize(size: number) {
|
|
143
|
+
if (size > 0) {
|
|
144
|
+
this._pageSize.set(size);
|
|
145
|
+
this._pageNumber.set(1);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
setFilter(filterValues: Record<string, any>) {
|
|
150
|
+
this.filter.set(filterValues);
|
|
151
|
+
this._pageNumber.set(1);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
clearFilter() {
|
|
155
|
+
this.filter.set({});
|
|
156
|
+
this._pageNumber.set(1);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
refresh() {
|
|
160
|
+
this.dataResource.reload();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
getPageRequest(): PageRequest {
|
|
164
|
+
return {
|
|
165
|
+
pageNumber: this._pageNumber(),
|
|
166
|
+
pageSize: this._pageSize(),
|
|
167
|
+
sortColumn: this._sortColumn() as string | undefined,
|
|
168
|
+
sortDirection: this._sortDirection(),
|
|
169
|
+
filter: this.filter(),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|