mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 04:01:58 +00:00
refactor: refactor forms components and remove legacy Taiga UI package (#3012)
This commit is contained in:
204
web/package-lock.json
generated
204
web/package-lock.json
generated
@@ -25,19 +25,18 @@
|
|||||||
"@noble/hashes": "^1.4.0",
|
"@noble/hashes": "^1.4.0",
|
||||||
"@start9labs/argon2": "^0.3.0",
|
"@start9labs/argon2": "^0.3.0",
|
||||||
"@start9labs/start-sdk": "file:../sdk/baseDist",
|
"@start9labs/start-sdk": "file:../sdk/baseDist",
|
||||||
"@taiga-ui/addon-charts": "4.48.0",
|
"@taiga-ui/addon-charts": "4.51.0",
|
||||||
"@taiga-ui/addon-commerce": "4.48.0",
|
"@taiga-ui/addon-commerce": "4.51.0",
|
||||||
"@taiga-ui/addon-mobile": "4.48.0",
|
"@taiga-ui/addon-mobile": "4.51.0",
|
||||||
"@taiga-ui/addon-table": "4.48.0",
|
"@taiga-ui/addon-table": "4.51.0",
|
||||||
"@taiga-ui/cdk": "4.48.0",
|
"@taiga-ui/cdk": "4.51.0",
|
||||||
"@taiga-ui/core": "4.48.0",
|
"@taiga-ui/core": "4.51.0",
|
||||||
"@taiga-ui/dompurify": "4.1.11",
|
"@taiga-ui/dompurify": "4.1.11",
|
||||||
"@taiga-ui/event-plugins": "4.6.0",
|
"@taiga-ui/event-plugins": "4.6.0",
|
||||||
"@taiga-ui/experimental": "4.48.0",
|
"@taiga-ui/experimental": "4.51.0",
|
||||||
"@taiga-ui/icons": "4.48.0",
|
"@taiga-ui/icons": "4.51.0",
|
||||||
"@taiga-ui/kit": "4.48.0",
|
"@taiga-ui/kit": "4.51.0",
|
||||||
"@taiga-ui/layout": "4.48.0",
|
"@taiga-ui/layout": "4.51.0",
|
||||||
"@taiga-ui/legacy": "4.48.0",
|
|
||||||
"@taiga-ui/polymorpheus": "4.9.0",
|
"@taiga-ui/polymorpheus": "4.9.0",
|
||||||
"ansi-to-html": "^0.7.2",
|
"ansi-to-html": "^0.7.2",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
@@ -2979,9 +2978,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@maskito/angular": {
|
"node_modules/@maskito/angular": {
|
||||||
"version": "3.10.2",
|
"version": "3.10.3",
|
||||||
"resolved": "https://registry.npmjs.org/@maskito/angular/-/angular-3.10.2.tgz",
|
"resolved": "https://registry.npmjs.org/@maskito/angular/-/angular-3.10.3.tgz",
|
||||||
"integrity": "sha512-+CQ7KQGmu35THj/59Uex+GotMFzdLHFUlPj5X5qphl+tHX09atmRzx7SEUCSEErbftTLafAFeR5N5t1fVTJvmw==",
|
"integrity": "sha512-Wu64iLuuMZH/3fXgQSj15i/XRDcGdxIYY1eoq+zEUX0JkN+f1DLYzS4QVUMz/APNb7mnpnmNP0omr0feEWj+Kg==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -2990,35 +2989,35 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/core": ">=16.0.0",
|
"@angular/core": ">=16.0.0",
|
||||||
"@angular/forms": ">=16.0.0",
|
"@angular/forms": ">=16.0.0",
|
||||||
"@maskito/core": "^3.10.2"
|
"@maskito/core": "^3.10.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@maskito/core": {
|
"node_modules/@maskito/core": {
|
||||||
"version": "3.10.2",
|
"version": "3.10.3",
|
||||||
"resolved": "https://registry.npmjs.org/@maskito/core/-/core-3.10.2.tgz",
|
"resolved": "https://registry.npmjs.org/@maskito/core/-/core-3.10.3.tgz",
|
||||||
"integrity": "sha512-LKh/PrG5wtMQ4AFYrWkKVGJUQB2CJcIt59qMPhntYIBpjw/OHWboHD4WWWQ94GvkYKjKQyjMcS/zvx+JaDrx2A==",
|
"integrity": "sha512-4SZeEF6PjDHC+J5ADrJaSrFmgqmGkqfE5Yi6BrNXze9TGvVRy9aHJCizShFvheqCEu6MsK0XprZot28wH9AhjQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@maskito/kit": {
|
"node_modules/@maskito/kit": {
|
||||||
"version": "3.10.2",
|
"version": "3.10.3",
|
||||||
"resolved": "https://registry.npmjs.org/@maskito/kit/-/kit-3.10.2.tgz",
|
"resolved": "https://registry.npmjs.org/@maskito/kit/-/kit-3.10.3.tgz",
|
||||||
"integrity": "sha512-d0YHheVt+DYZDL+A4uwoF0pF/rofczHz0KKYEuQrSdbKlRxOdyckQrj9iMCsmD73Hwne7LbjLL/rViHL4aFL2Q==",
|
"integrity": "sha512-4IAL5WPlz4zi6vCMp8KbSAVh67WT+o0PzQ56dU4E7crN1jzBm1cN7MIbGawefOIXwAiqCb8zOSyTv/qqSL0xGQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@maskito/core": "^3.10.2"
|
"@maskito/core": "^3.10.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@maskito/phone": {
|
"node_modules/@maskito/phone": {
|
||||||
"version": "3.10.2",
|
"version": "3.10.3",
|
||||||
"resolved": "https://registry.npmjs.org/@maskito/phone/-/phone-3.10.2.tgz",
|
"resolved": "https://registry.npmjs.org/@maskito/phone/-/phone-3.10.3.tgz",
|
||||||
"integrity": "sha512-XP/mp7CTHYriy6U+zoIitlJCGCmMr+yxtJ/u5y9+S4H3T1siILU3K8CAqetxpK//8/Zopco8lyz1D7ASKofdRg==",
|
"integrity": "sha512-xt0WLrzLbxiS+0j5QoR6lBjU+FvtqfUL3BOEAKcopgUa8lrswZr3g6fRy0BCcvrzpf4Jdpj3FsZcBRd6ljiEkg==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@maskito/core": "^3.10.2",
|
"@maskito/core": "^3.10.3",
|
||||||
"@maskito/kit": "^3.10.2",
|
"@maskito/kit": "^3.10.3",
|
||||||
"libphonenumber-js": ">=1.0.0"
|
"libphonenumber-js": ">=1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -4714,9 +4713,9 @@
|
|||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/addon-charts": {
|
"node_modules/@taiga-ui/addon-charts": {
|
||||||
"version": "4.48.0",
|
"version": "4.51.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-4.48.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-4.51.0.tgz",
|
||||||
"integrity": "sha512-0oEfjhV+B50ITyS5oXnVAzeclSrAVX9FiEvWkX7zJ92uy7PKzkoGx+wEsKw3m1ax0I+cVYrh+rX6VivpX4dBZw==",
|
"integrity": "sha512-SqNlaljenvsRILpugAAOGxqpoNSy/6YZoq0rvM9Zs1BToOPWbiTj6w2lpezWMxgL0m4ciGGN7Bo9kq5CZu+QaQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": ">=2.8.1"
|
"tslib": ">=2.8.1"
|
||||||
@@ -4725,15 +4724,15 @@
|
|||||||
"@angular/common": ">=16.0.0",
|
"@angular/common": ">=16.0.0",
|
||||||
"@angular/core": ">=16.0.0",
|
"@angular/core": ">=16.0.0",
|
||||||
"@ng-web-apis/common": "^4.12.0",
|
"@ng-web-apis/common": "^4.12.0",
|
||||||
"@taiga-ui/cdk": "^4.48.0",
|
"@taiga-ui/cdk": "^4.51.0",
|
||||||
"@taiga-ui/core": "^4.48.0",
|
"@taiga-ui/core": "^4.51.0",
|
||||||
"@taiga-ui/polymorpheus": "^4.9.0"
|
"@taiga-ui/polymorpheus": "^4.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/addon-commerce": {
|
"node_modules/@taiga-ui/addon-commerce": {
|
||||||
"version": "4.48.0",
|
"version": "4.51.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-4.48.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-4.51.0.tgz",
|
||||||
"integrity": "sha512-IGWBSRlsQmkNQfKFk90N0N7TkPsFBo0pBBuTXeuVGBo9us4AJafUAMnVlS5U77XSL1xK1pGRkazKfLgLz3yMzg==",
|
"integrity": "sha512-2kGwV4FWZ4k6FoSICBmLfqpwZTdpAHLG79VS9gDv7Qd1vMYXRAHxJIi1phARZjl8EXpeAO6isTq+xpcHMdqT8A==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": ">=2.8.1"
|
"tslib": ">=2.8.1"
|
||||||
@@ -4742,22 +4741,22 @@
|
|||||||
"@angular/common": ">=16.0.0",
|
"@angular/common": ">=16.0.0",
|
||||||
"@angular/core": ">=16.0.0",
|
"@angular/core": ">=16.0.0",
|
||||||
"@angular/forms": ">=16.0.0",
|
"@angular/forms": ">=16.0.0",
|
||||||
"@maskito/angular": "^3.10.2",
|
"@maskito/angular": "^3.10.3",
|
||||||
"@maskito/core": "^3.10.2",
|
"@maskito/core": "^3.10.3",
|
||||||
"@maskito/kit": "^3.10.2",
|
"@maskito/kit": "^3.10.3",
|
||||||
"@ng-web-apis/common": "^4.12.0",
|
"@ng-web-apis/common": "^4.12.0",
|
||||||
"@taiga-ui/cdk": "^4.48.0",
|
"@taiga-ui/cdk": "^4.51.0",
|
||||||
"@taiga-ui/core": "^4.48.0",
|
"@taiga-ui/core": "^4.51.0",
|
||||||
"@taiga-ui/i18n": "^4.48.0",
|
"@taiga-ui/i18n": "^4.51.0",
|
||||||
"@taiga-ui/kit": "^4.48.0",
|
"@taiga-ui/kit": "^4.51.0",
|
||||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||||
"rxjs": ">=7.0.0"
|
"rxjs": ">=7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/addon-mobile": {
|
"node_modules/@taiga-ui/addon-mobile": {
|
||||||
"version": "4.48.0",
|
"version": "4.51.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-4.48.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-4.51.0.tgz",
|
||||||
"integrity": "sha512-aGuCkE0T+EaKSr31R2TYuN1h1STi8iATGlNHX4kZ3+Ab/mebER8Xi7uo5gy9olMOGB65syl5Bo4VL02/wc5HKw==",
|
"integrity": "sha512-oEtIrT0mWdiR0QRe9XUhdu+XjsfjPA4zWDjRU4l7mwCjhBqPjTGK5PchjnMpznpu/Y3/5cRR2+xOuEb8NXFd5w==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": ">=2.8.1"
|
"tslib": ">=2.8.1"
|
||||||
@@ -4767,18 +4766,18 @@
|
|||||||
"@angular/common": ">=16.0.0",
|
"@angular/common": ">=16.0.0",
|
||||||
"@angular/core": ">=16.0.0",
|
"@angular/core": ">=16.0.0",
|
||||||
"@ng-web-apis/common": "^4.12.0",
|
"@ng-web-apis/common": "^4.12.0",
|
||||||
"@taiga-ui/cdk": "^4.48.0",
|
"@taiga-ui/cdk": "^4.51.0",
|
||||||
"@taiga-ui/core": "^4.48.0",
|
"@taiga-ui/core": "^4.51.0",
|
||||||
"@taiga-ui/kit": "^4.48.0",
|
"@taiga-ui/kit": "^4.51.0",
|
||||||
"@taiga-ui/layout": "^4.48.0",
|
"@taiga-ui/layout": "^4.51.0",
|
||||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||||
"rxjs": ">=7.0.0"
|
"rxjs": ">=7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/addon-table": {
|
"node_modules/@taiga-ui/addon-table": {
|
||||||
"version": "4.48.0",
|
"version": "4.51.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-table/-/addon-table-4.48.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-table/-/addon-table-4.51.0.tgz",
|
||||||
"integrity": "sha512-omwAOlwxom03jTWECDjSDVTOItHD6ZyiPMB5aY/HI/jjsQIZXDlPJLYLfS0+rBR4mwBWBMCXaLvVPPAPy2U4eA==",
|
"integrity": "sha512-NTt11Yzcjts08qzlTvnFlMG2ANXu0Tk9w6aOnNRpKbZzlkEVGLFdkYazg10RDei1909PnbkF7TgdZ83IM+3c6w==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": ">=2.8.1"
|
"tslib": ">=2.8.1"
|
||||||
@@ -4787,18 +4786,18 @@
|
|||||||
"@angular/common": ">=16.0.0",
|
"@angular/common": ">=16.0.0",
|
||||||
"@angular/core": ">=16.0.0",
|
"@angular/core": ">=16.0.0",
|
||||||
"@ng-web-apis/intersection-observer": "^4.12.0",
|
"@ng-web-apis/intersection-observer": "^4.12.0",
|
||||||
"@taiga-ui/cdk": "^4.48.0",
|
"@taiga-ui/cdk": "^4.51.0",
|
||||||
"@taiga-ui/core": "^4.48.0",
|
"@taiga-ui/core": "^4.51.0",
|
||||||
"@taiga-ui/i18n": "^4.48.0",
|
"@taiga-ui/i18n": "^4.51.0",
|
||||||
"@taiga-ui/kit": "^4.48.0",
|
"@taiga-ui/kit": "^4.51.0",
|
||||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||||
"rxjs": ">=7.0.0"
|
"rxjs": ">=7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/cdk": {
|
"node_modules/@taiga-ui/cdk": {
|
||||||
"version": "4.48.0",
|
"version": "4.51.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-4.48.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-4.51.0.tgz",
|
||||||
"integrity": "sha512-CJdGnLqOmQsLTXDhliriVpvyjTCZNXtfqMpoBBNQwUdRC+2+0mhhltnmE2FnnyvsKYoFoZ87q1NpKkRqotvstA==",
|
"integrity": "sha512-LSUF12F1u0jQgrrImwc1wqZXGgIdNP39MKwUgEhF4qHuBtJGFJzL643Mw/vhX0Tlw5yo+ecVvr76oBAxMkWLww==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "2.8.1"
|
"tslib": "2.8.1"
|
||||||
@@ -4827,9 +4826,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/core": {
|
"node_modules/@taiga-ui/core": {
|
||||||
"version": "4.48.0",
|
"version": "4.51.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-4.48.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-4.51.0.tgz",
|
||||||
"integrity": "sha512-PkPN4gS1Wnf1nB1e0D9kB+wc6GMndjyAZvxntduG1UKGyFAl4rohbAJI5Fh5bjm/Gr4mQUUBX1LzeQFDY+ob6Q==",
|
"integrity": "sha512-S/P7YKfB7hGXDuo3HFYI8GddahMg4tlvO4Owi/P3qFjWw+ifVEbFZNF4A1JPTZUrpq9uIhMj7tPzeF99QRL/GQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": ">=2.8.1"
|
"tslib": ">=2.8.1"
|
||||||
@@ -4843,9 +4842,9 @@
|
|||||||
"@angular/router": ">=16.0.0",
|
"@angular/router": ">=16.0.0",
|
||||||
"@ng-web-apis/common": "^4.12.0",
|
"@ng-web-apis/common": "^4.12.0",
|
||||||
"@ng-web-apis/mutation-observer": "^4.12.0",
|
"@ng-web-apis/mutation-observer": "^4.12.0",
|
||||||
"@taiga-ui/cdk": "^4.48.0",
|
"@taiga-ui/cdk": "^4.51.0",
|
||||||
"@taiga-ui/event-plugins": "^4.6.0",
|
"@taiga-ui/event-plugins": "^4.6.0",
|
||||||
"@taiga-ui/i18n": "^4.48.0",
|
"@taiga-ui/i18n": "^4.51.0",
|
||||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||||
"rxjs": ">=7.0.0"
|
"rxjs": ">=7.0.0"
|
||||||
}
|
}
|
||||||
@@ -4880,9 +4879,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/experimental": {
|
"node_modules/@taiga-ui/experimental": {
|
||||||
"version": "4.48.0",
|
"version": "4.51.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-4.48.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-4.51.0.tgz",
|
||||||
"integrity": "sha512-ZKVNos1nbKo5koh34TBX5AsLRqbDoNn4crFKqyXux1MmmrCLgqYxeou7/u3g9bIqC263n+p3urM/9oFC7jllBw==",
|
"integrity": "sha512-ZbiXNEOGy+F0UaOwm+h4eDvOesh0PtybRp6PmC8OOXPGOu9SHtrcWhVwczhoSXLX+1fhHnt+P3YxWW2B+KqK/w==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": ">=2.8.1"
|
"tslib": ">=2.8.1"
|
||||||
@@ -4890,18 +4889,19 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/common": ">=16.0.0",
|
"@angular/common": ">=16.0.0",
|
||||||
"@angular/core": ">=16.0.0",
|
"@angular/core": ">=16.0.0",
|
||||||
"@taiga-ui/addon-commerce": "^4.48.0",
|
"@taiga-ui/addon-commerce": "^4.51.0",
|
||||||
"@taiga-ui/cdk": "^4.48.0",
|
"@taiga-ui/cdk": "^4.51.0",
|
||||||
"@taiga-ui/core": "^4.48.0",
|
"@taiga-ui/core": "^4.51.0",
|
||||||
"@taiga-ui/kit": "^4.48.0",
|
"@taiga-ui/kit": "^4.51.0",
|
||||||
|
"@taiga-ui/layout": "^4.51.0",
|
||||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||||
"rxjs": ">=7.0.0"
|
"rxjs": ">=7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/i18n": {
|
"node_modules/@taiga-ui/i18n": {
|
||||||
"version": "4.48.0",
|
"version": "4.51.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-4.48.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-4.51.0.tgz",
|
||||||
"integrity": "sha512-E73l8P1YPFSydgDmz0ajn856ee7eDVIJosrgX3vpaAH1m2pICp4PYwZfqCuHwhogk/mKdAtnVZoBaOgr6ybXlg==",
|
"integrity": "sha512-k0hbvNJZRqhLc538utmek9+p2gqG1ZWMm9F/0D8w00EdD8BDxCli/GIcXDjlxeM7HInHfRrlc9kQOsxpJ2wvoQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -4914,18 +4914,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/icons": {
|
"node_modules/@taiga-ui/icons": {
|
||||||
"version": "4.48.0",
|
"version": "4.51.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-4.48.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-4.51.0.tgz",
|
||||||
"integrity": "sha512-TCWAQ2RshcBwgumk7UayYuDwpNQCwP6bDppsn3yz/JcKH1OagDPcLRy3oV15Gpwvi0AcrnrfE74IkeMdClMQUQ==",
|
"integrity": "sha512-YPNUxgb9kKtkvpuFRXrQEExHvFfTutgZvs5lMixPC6V5+ttud0UQzDDdqEAxk4e3z7ET0GbwVNebBnP/jo5ERA==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/kit": {
|
"node_modules/@taiga-ui/kit": {
|
||||||
"version": "4.48.0",
|
"version": "4.51.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-4.48.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-4.51.0.tgz",
|
||||||
"integrity": "sha512-OraV1GAZqmBYwqTrsJPGar6d3Vo0keUhCGzd8rUxeL0ZKtRX+vsRRPtKAQP7B8IYPxnkZRQLZuV1XLZqmwEiaw==",
|
"integrity": "sha512-gJ0TixxXUh8QkKXCA5o9pZF2ENJnUIVHpmiWatrKmxkYzlYBMpP99Iw4vXlj7Ax6j1Or84FAen/0s8IoFGSnOQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": ">=2.8.1"
|
"tslib": ">=2.8.1"
|
||||||
@@ -4935,25 +4935,25 @@
|
|||||||
"@angular/core": ">=16.0.0",
|
"@angular/core": ">=16.0.0",
|
||||||
"@angular/forms": ">=16.0.0",
|
"@angular/forms": ">=16.0.0",
|
||||||
"@angular/router": ">=16.0.0",
|
"@angular/router": ">=16.0.0",
|
||||||
"@maskito/angular": "^3.10.2",
|
"@maskito/angular": "^3.10.3",
|
||||||
"@maskito/core": "^3.10.2",
|
"@maskito/core": "^3.10.3",
|
||||||
"@maskito/kit": "^3.10.2",
|
"@maskito/kit": "^3.10.3",
|
||||||
"@maskito/phone": "^3.10.2",
|
"@maskito/phone": "^3.10.3",
|
||||||
"@ng-web-apis/common": "^4.12.0",
|
"@ng-web-apis/common": "^4.12.0",
|
||||||
"@ng-web-apis/intersection-observer": "^4.12.0",
|
"@ng-web-apis/intersection-observer": "^4.12.0",
|
||||||
"@ng-web-apis/mutation-observer": "^4.12.0",
|
"@ng-web-apis/mutation-observer": "^4.12.0",
|
||||||
"@ng-web-apis/resize-observer": "^4.12.0",
|
"@ng-web-apis/resize-observer": "^4.12.0",
|
||||||
"@taiga-ui/cdk": "^4.48.0",
|
"@taiga-ui/cdk": "^4.51.0",
|
||||||
"@taiga-ui/core": "^4.48.0",
|
"@taiga-ui/core": "^4.51.0",
|
||||||
"@taiga-ui/i18n": "^4.48.0",
|
"@taiga-ui/i18n": "^4.51.0",
|
||||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||||
"rxjs": ">=7.0.0"
|
"rxjs": ">=7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/layout": {
|
"node_modules/@taiga-ui/layout": {
|
||||||
"version": "4.48.0",
|
"version": "4.51.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/layout/-/layout-4.48.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/layout/-/layout-4.51.0.tgz",
|
||||||
"integrity": "sha512-Q4420HZRv4iIuC5kpGuHzbWR+njBusOjUlpKJ5B6coduw6oXP5zr/R7czZmD110+2jdLj2p4owlc0Rr+8LwNBQ==",
|
"integrity": "sha512-Y1k8C9/SpRH3a6X+lCF2nZ+SYgKvwNTuBvFCHIGZDsD6ye2yxS939ae4j+QBF8w16FlcEZi4eNPH6P0qMht4fw==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": ">=2.8.1"
|
"tslib": ">=2.8.1"
|
||||||
@@ -4961,25 +4961,13 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/common": ">=16.0.0",
|
"@angular/common": ">=16.0.0",
|
||||||
"@angular/core": ">=16.0.0",
|
"@angular/core": ">=16.0.0",
|
||||||
"@taiga-ui/cdk": "^4.48.0",
|
"@taiga-ui/cdk": "^4.51.0",
|
||||||
"@taiga-ui/core": "^4.48.0",
|
"@taiga-ui/core": "^4.51.0",
|
||||||
"@taiga-ui/kit": "^4.48.0",
|
"@taiga-ui/kit": "^4.51.0",
|
||||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||||
"rxjs": ">=7.0.0"
|
"rxjs": ">=7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/legacy": {
|
|
||||||
"version": "4.48.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/legacy/-/legacy-4.48.0.tgz",
|
|
||||||
"integrity": "sha512-1zv8oHcOYUs4W9T/ihL0b2psdVB7PFdLcZ6wkPBIaD/luVrdAGI1RUMrrtcm9SU6uo9hpqDkcaaymf9hnS6Itw==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": ">=2.8.1"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@angular/core": ">=16.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@taiga-ui/polymorpheus": {
|
"node_modules/@taiga-ui/polymorpheus": {
|
||||||
"version": "4.9.0",
|
"version": "4.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/polymorpheus/-/polymorpheus-4.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/polymorpheus/-/polymorpheus-4.9.0.tgz",
|
||||||
@@ -8457,9 +8445,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/libphonenumber-js": {
|
"node_modules/libphonenumber-js": {
|
||||||
"version": "1.12.10",
|
"version": "1.12.14",
|
||||||
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.10.tgz",
|
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.14.tgz",
|
||||||
"integrity": "sha512-E91vHJD61jekHHR/RF/E83T/CMoaLXT7cwYA75T4gim4FZjnM6hbJjVIGg7chqlSqRsSvQ3izGmOjHy1SQzcGQ==",
|
"integrity": "sha512-HBAMAV7f3yGYy7ZZN5FxQ1tXJTwC77G5/96Yn/SH/HPyKX2EMLGFuCIYUmdLU7CxxJlQcvJymP/PGLzyapurhQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -46,19 +46,18 @@
|
|||||||
"@noble/hashes": "^1.4.0",
|
"@noble/hashes": "^1.4.0",
|
||||||
"@start9labs/argon2": "^0.3.0",
|
"@start9labs/argon2": "^0.3.0",
|
||||||
"@start9labs/start-sdk": "file:../sdk/baseDist",
|
"@start9labs/start-sdk": "file:../sdk/baseDist",
|
||||||
"@taiga-ui/addon-charts": "4.48.0",
|
"@taiga-ui/addon-charts": "4.51.0",
|
||||||
"@taiga-ui/addon-commerce": "4.48.0",
|
"@taiga-ui/addon-commerce": "4.51.0",
|
||||||
"@taiga-ui/addon-mobile": "4.48.0",
|
"@taiga-ui/addon-mobile": "4.51.0",
|
||||||
"@taiga-ui/addon-table": "4.48.0",
|
"@taiga-ui/addon-table": "4.51.0",
|
||||||
"@taiga-ui/cdk": "4.48.0",
|
"@taiga-ui/cdk": "4.51.0",
|
||||||
"@taiga-ui/core": "4.48.0",
|
"@taiga-ui/core": "4.51.0",
|
||||||
"@taiga-ui/dompurify": "4.1.11",
|
"@taiga-ui/dompurify": "4.1.11",
|
||||||
"@taiga-ui/event-plugins": "4.6.0",
|
"@taiga-ui/event-plugins": "4.6.0",
|
||||||
"@taiga-ui/experimental": "4.48.0",
|
"@taiga-ui/experimental": "4.51.0",
|
||||||
"@taiga-ui/icons": "4.48.0",
|
"@taiga-ui/icons": "4.51.0",
|
||||||
"@taiga-ui/kit": "4.48.0",
|
"@taiga-ui/kit": "4.51.0",
|
||||||
"@taiga-ui/layout": "4.48.0",
|
"@taiga-ui/layout": "4.51.0",
|
||||||
"@taiga-ui/legacy": "4.48.0",
|
|
||||||
"@taiga-ui/polymorpheus": "4.9.0",
|
"@taiga-ui/polymorpheus": "4.9.0",
|
||||||
"ansi-to-html": "^0.7.2",
|
"ansi-to-html": "^0.7.2",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
|
|||||||
@@ -88,7 +88,7 @@
|
|||||||
--start9-base-5: rgba(60, 62, 64, 1);
|
--start9-base-5: rgba(60, 62, 64, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[tuiAppearance][data-appearance^='primary'] {
|
[tuiAppearance][data-appearance^='primary']:not([tuiCheckbox]._readonly) {
|
||||||
@include taiga.appearance-disabled {
|
@include taiga.appearance-disabled {
|
||||||
background: var(--tui-status-neutral);
|
background: var(--tui-status-neutral);
|
||||||
color: #333;
|
color: #333;
|
||||||
@@ -126,11 +126,8 @@ tui-dropdown[data-appearance='start-os'][data-appearance='start-os'] {
|
|||||||
var(--tui-background-elevation-3) 75%,
|
var(--tui-background-elevation-3) 75%,
|
||||||
transparent
|
transparent
|
||||||
);
|
);
|
||||||
background-image: linear-gradient(
|
background-image:
|
||||||
to bottom,
|
linear-gradient(to bottom, rgba(255, 255, 255, 0.15), transparent),
|
||||||
rgba(255, 255, 255, 0.15),
|
|
||||||
transparent
|
|
||||||
),
|
|
||||||
linear-gradient(to bottom, rgba(255, 255, 255, 0.15), transparent);
|
linear-gradient(to bottom, rgba(255, 255, 255, 0.15), transparent);
|
||||||
background-size: 1px 100%;
|
background-size: 1px 100%;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
@@ -162,6 +159,10 @@ tui-badge-notification {
|
|||||||
background: var(--tui-status-negative);
|
background: var(--tui-status-negative);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tui-textfield [tuiTooltip] {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
[tuiCell] {
|
[tuiCell] {
|
||||||
&[data-height='spacious'] {
|
&[data-height='spacious'] {
|
||||||
padding-block: 0.75rem;
|
padding-block: 0.75rem;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
|
import { BrowserModule } from '@angular/platform-browser'
|
||||||
import { ServiceWorkerModule } from '@angular/service-worker'
|
import { ServiceWorkerModule } from '@angular/service-worker'
|
||||||
import { TuiRoot } from '@taiga-ui/core'
|
import { TuiRoot } from '@taiga-ui/core'
|
||||||
import { ToastContainerComponent } from 'src/app/components/toast-container.component'
|
import { ToastContainerComponent } from 'src/app/components/toast-container.component'
|
||||||
@@ -12,7 +12,7 @@ import { RoutingModule } from './routing.module'
|
|||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [AppComponent],
|
declarations: [AppComponent],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserAnimationsModule,
|
BrowserModule,
|
||||||
RoutingModule,
|
RoutingModule,
|
||||||
ToastContainerComponent,
|
ToastContainerComponent,
|
||||||
TuiRoot,
|
TuiRoot,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { inject, provideAppInitializer } from '@angular/core'
|
import { inject, provideAppInitializer } from '@angular/core'
|
||||||
import { UntypedFormBuilder } from '@angular/forms'
|
import { UntypedFormBuilder } from '@angular/forms'
|
||||||
|
import { provideAnimations } from '@angular/platform-browser/animations'
|
||||||
import { Router } from '@angular/router'
|
import { Router } from '@angular/router'
|
||||||
import { WA_LOCATION } from '@ng-web-apis/common'
|
import { WA_LOCATION } from '@ng-web-apis/common'
|
||||||
import initArgon from '@start9labs/argon2'
|
import initArgon from '@start9labs/argon2'
|
||||||
@@ -28,7 +29,6 @@ import {
|
|||||||
TUI_DATE_TIME_VALUE_TRANSFORMER,
|
TUI_DATE_TIME_VALUE_TRANSFORMER,
|
||||||
TUI_DATE_VALUE_TRANSFORMER,
|
TUI_DATE_VALUE_TRANSFORMER,
|
||||||
} from '@taiga-ui/kit'
|
} from '@taiga-ui/kit'
|
||||||
import { tuiTextfieldOptionsProvider } from '@taiga-ui/legacy'
|
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { filter, of, pairwise } from 'rxjs'
|
import { filter, of, pairwise } from 'rxjs'
|
||||||
import { ConfigService } from 'src/app/services/config.service'
|
import { ConfigService } from 'src/app/services/config.service'
|
||||||
@@ -37,6 +37,7 @@ import {
|
|||||||
PatchDbSource,
|
PatchDbSource,
|
||||||
} from 'src/app/services/patch-db/patch-db-source'
|
} from 'src/app/services/patch-db/patch-db-source'
|
||||||
import { StateService } from 'src/app/services/state.service'
|
import { StateService } from 'src/app/services/state.service'
|
||||||
|
import { FilterUpdatesPipe } from './routes/portal/routes/updates/filter-updates.pipe'
|
||||||
import { ApiService } from './services/api/embassy-api.service'
|
import { ApiService } from './services/api/embassy-api.service'
|
||||||
import { LiveApiService } from './services/api/embassy-live-api.service'
|
import { LiveApiService } from './services/api/embassy-live-api.service'
|
||||||
import { MockApiService } from './services/api/embassy-mock-api.service'
|
import { MockApiService } from './services/api/embassy-mock-api.service'
|
||||||
@@ -46,7 +47,6 @@ import { ClientStorageService } from './services/client-storage.service'
|
|||||||
import { DateTransformerService } from './services/date-transformer.service'
|
import { DateTransformerService } from './services/date-transformer.service'
|
||||||
import { DatetimeTransformerService } from './services/datetime-transformer.service'
|
import { DatetimeTransformerService } from './services/datetime-transformer.service'
|
||||||
import { StorageService } from './services/storage.service'
|
import { StorageService } from './services/storage.service'
|
||||||
import { FilterUpdatesPipe } from './routes/portal/routes/updates/filter-updates.pipe'
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
useMocks,
|
useMocks,
|
||||||
@@ -54,6 +54,7 @@ const {
|
|||||||
} = require('../../../../config.json') as WorkspaceConfig
|
} = require('../../../../config.json') as WorkspaceConfig
|
||||||
|
|
||||||
export const APP_PROVIDERS = [
|
export const APP_PROVIDERS = [
|
||||||
|
provideAnimations(),
|
||||||
provideEventPlugins(),
|
provideEventPlugins(),
|
||||||
I18N_PROVIDERS,
|
I18N_PROVIDERS,
|
||||||
FilterPackagesPipe,
|
FilterPackagesPipe,
|
||||||
@@ -61,7 +62,6 @@ export const APP_PROVIDERS = [
|
|||||||
UntypedFormBuilder,
|
UntypedFormBuilder,
|
||||||
tuiNumberFormatProvider({ decimalSeparator: '.', thousandSeparator: '' }),
|
tuiNumberFormatProvider({ decimalSeparator: '.', thousandSeparator: '' }),
|
||||||
tuiButtonOptionsProvider({ size: 'm' }),
|
tuiButtonOptionsProvider({ size: 'm' }),
|
||||||
tuiTextfieldOptionsProvider({ hintOnDisabled: true }),
|
|
||||||
tuiDropdownOptionsProvider({ appearance: 'start-os' }),
|
tuiDropdownOptionsProvider({ appearance: 'start-os' }),
|
||||||
tuiAlertOptionsProvider({
|
tuiAlertOptionsProvider({
|
||||||
autoClose: appearance => (appearance === 'negative' ? 0 : 3000),
|
autoClose: appearance => (appearance === 'negative' ? 0 : 3000),
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import { TuiButton, TuiDialogContext } from '@taiga-ui/core'
|
|||||||
import { TuiConfirmService } from '@taiga-ui/kit'
|
import { TuiConfirmService } from '@taiga-ui/kit'
|
||||||
import { POLYMORPHEUS_CONTEXT } from '@taiga-ui/polymorpheus'
|
import { POLYMORPHEUS_CONTEXT } from '@taiga-ui/polymorpheus'
|
||||||
import { Operation } from 'fast-json-patch'
|
import { Operation } from 'fast-json-patch'
|
||||||
import { FormModule } from 'src/app/routes/portal/components/form/form.module'
|
import { FormGroupComponent } from 'src/app/routes/portal/components/form/containers/group.component'
|
||||||
import { InvalidService } from 'src/app/routes/portal/components/form/invalid.service'
|
import { InvalidService } from 'src/app/routes/portal/components/form/containers/control.directive'
|
||||||
import { FormService } from 'src/app/services/form.service'
|
import { FormService } from 'src/app/services/form.service'
|
||||||
|
|
||||||
export interface ActionButton<T> {
|
export interface ActionButton<T> {
|
||||||
@@ -88,7 +88,7 @@ export interface FormContext<T> {
|
|||||||
RouterModule,
|
RouterModule,
|
||||||
TuiValueChanges,
|
TuiValueChanges,
|
||||||
TuiButton,
|
TuiButton,
|
||||||
FormModule,
|
FormGroupComponent,
|
||||||
],
|
],
|
||||||
providers: [InvalidService],
|
providers: [InvalidService],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
|||||||
@@ -0,0 +1,230 @@
|
|||||||
|
import { AsyncPipe } from '@angular/common'
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
DestroyRef,
|
||||||
|
forwardRef,
|
||||||
|
HostBinding,
|
||||||
|
inject,
|
||||||
|
Input,
|
||||||
|
} from '@angular/core'
|
||||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
|
||||||
|
import {
|
||||||
|
AbstractControl,
|
||||||
|
FormArrayName,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
} from '@angular/forms'
|
||||||
|
import { DialogService, i18nKey, i18nPipe } from '@start9labs/shared'
|
||||||
|
import { IST } from '@start9labs/start-sdk'
|
||||||
|
import {
|
||||||
|
TUI_ANIMATIONS_SPEED,
|
||||||
|
TuiButton,
|
||||||
|
TuiError,
|
||||||
|
tuiFadeIn,
|
||||||
|
tuiHeightCollapse,
|
||||||
|
TuiIcon,
|
||||||
|
TuiLink,
|
||||||
|
tuiParentStop,
|
||||||
|
TuiTextfield,
|
||||||
|
tuiToAnimationOptions,
|
||||||
|
} from '@taiga-ui/core'
|
||||||
|
import { TuiFieldErrorPipe, TuiTooltip } from '@taiga-ui/kit'
|
||||||
|
import { filter } from 'rxjs'
|
||||||
|
import { FormService } from 'src/app/services/form.service'
|
||||||
|
|
||||||
|
import { HintPipe } from '../pipes/hint.pipe'
|
||||||
|
import { MustachePipe } from '../pipes/mustache.pipe'
|
||||||
|
import { ERRORS, FormControlComponent } from './control.component'
|
||||||
|
import { ControlDirective } from './control.directive'
|
||||||
|
import { FormObjectComponent } from './object.component'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'form-array',
|
||||||
|
template: `
|
||||||
|
<div class="label">
|
||||||
|
{{ spec.name }}
|
||||||
|
@if (spec.description || spec.disabled) {
|
||||||
|
<tui-icon [tuiTooltip]="spec | hint" />
|
||||||
|
}
|
||||||
|
<button
|
||||||
|
tuiLink
|
||||||
|
type="button"
|
||||||
|
class="add"
|
||||||
|
[disabled]="!canAdd"
|
||||||
|
(click)="add()"
|
||||||
|
>
|
||||||
|
+ {{ 'Add' | i18n }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<tui-error [error]="order | tuiFieldError | async" />
|
||||||
|
@for (item of array.control.controls; track item) {
|
||||||
|
@if (spec.spec.type === 'object') {
|
||||||
|
<form-object
|
||||||
|
class="object"
|
||||||
|
[class.object_open]="!!open.get(item)"
|
||||||
|
[formGroup]="$any(item)"
|
||||||
|
[spec]="$any(spec.spec)"
|
||||||
|
[@tuiHeightCollapse]="animation"
|
||||||
|
[@tuiFadeIn]="animation"
|
||||||
|
[open]="!!open.get(item)"
|
||||||
|
(openChange)="open.set(item, $event)"
|
||||||
|
>
|
||||||
|
{{ item.value | mustache: $any(spec.spec).displayAs }}
|
||||||
|
<button
|
||||||
|
tuiIconButton
|
||||||
|
type="button"
|
||||||
|
class="remove"
|
||||||
|
iconStart="@tui.trash"
|
||||||
|
appearance="icon"
|
||||||
|
size="m"
|
||||||
|
title="Remove"
|
||||||
|
(click.stop)="removeAt($index)"
|
||||||
|
></button>
|
||||||
|
</form-object>
|
||||||
|
} @else {
|
||||||
|
<form-control
|
||||||
|
class="control"
|
||||||
|
tuiTextfieldSize="m"
|
||||||
|
[formControl]="$any(item)"
|
||||||
|
[spec]="$any(spec.spec)"
|
||||||
|
[@tuiHeightCollapse]="animation"
|
||||||
|
[@tuiFadeIn]="animation"
|
||||||
|
(remove)="removeAt($index)"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
styles: `
|
||||||
|
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
|
||||||
|
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
display: flex;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add {
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 0 1rem;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.object {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&_open::after,
|
||||||
|
&:last-child::after {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
@include taiga.transition(opacity);
|
||||||
|
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -0.5rem;
|
||||||
|
height: 1px;
|
||||||
|
left: 3rem;
|
||||||
|
right: 1rem;
|
||||||
|
background: var(--tui-background-neutral-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove {
|
||||||
|
margin: 0 0.375rem 0 auto;
|
||||||
|
pointer-events: auto;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.control {
|
||||||
|
display: block;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
animations: [tuiFadeIn, tuiHeightCollapse, tuiParentStop],
|
||||||
|
hostDirectives: [ControlDirective],
|
||||||
|
imports: [
|
||||||
|
AsyncPipe,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
TuiIcon,
|
||||||
|
TuiTooltip,
|
||||||
|
TuiLink,
|
||||||
|
TuiError,
|
||||||
|
TuiFieldErrorPipe,
|
||||||
|
TuiButton,
|
||||||
|
TuiTextfield,
|
||||||
|
i18nPipe,
|
||||||
|
HintPipe,
|
||||||
|
MustachePipe,
|
||||||
|
FormControlComponent,
|
||||||
|
forwardRef(() => FormObjectComponent),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class FormArrayComponent {
|
||||||
|
@Input({ required: true })
|
||||||
|
spec!: IST.ValueSpecList
|
||||||
|
|
||||||
|
@HostBinding('@tuiParentStop')
|
||||||
|
readonly animation = tuiToAnimationOptions(inject(TUI_ANIMATIONS_SPEED))
|
||||||
|
readonly order = ERRORS
|
||||||
|
readonly array = inject(FormArrayName)
|
||||||
|
readonly open = new Map<AbstractControl, boolean>()
|
||||||
|
|
||||||
|
private warned = false
|
||||||
|
private readonly formService = inject(FormService)
|
||||||
|
private readonly destroyRef = inject(DestroyRef)
|
||||||
|
private readonly dialog = inject(DialogService)
|
||||||
|
|
||||||
|
get canAdd(): boolean {
|
||||||
|
return (
|
||||||
|
!this.spec.disabled &&
|
||||||
|
(!this.spec.maxLength ||
|
||||||
|
this.spec.maxLength >= this.array.control.controls.length)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
add() {
|
||||||
|
if (!this.warned && this.spec.warning) {
|
||||||
|
this.dialog
|
||||||
|
.openConfirm<boolean>({
|
||||||
|
label: 'Warning',
|
||||||
|
size: 's',
|
||||||
|
data: {
|
||||||
|
content: this.spec.warning as i18nKey,
|
||||||
|
yes: 'Ok',
|
||||||
|
no: 'Cancel',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.pipe(filter(Boolean), takeUntilDestroyed(this.destroyRef))
|
||||||
|
.subscribe(() => {
|
||||||
|
this.addItem()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.addItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.warned = true
|
||||||
|
}
|
||||||
|
|
||||||
|
removeAt(index: number) {
|
||||||
|
this.removeItem(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeItem(index: number) {
|
||||||
|
this.open.delete(this.array.control.at(index))
|
||||||
|
this.array.control.removeAt(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
private addItem() {
|
||||||
|
this.array.control.insert(0, this.formService.getListItem(this.spec))
|
||||||
|
this.open.set(this.array.control.at(0), true)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
import { AsyncPipe } from '@angular/common'
|
||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
DestroyRef,
|
||||||
|
inject,
|
||||||
|
Input,
|
||||||
|
TemplateRef,
|
||||||
|
ViewChild,
|
||||||
|
} from '@angular/core'
|
||||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
|
||||||
|
import { i18nPipe } from '@start9labs/shared'
|
||||||
|
import { IST } from '@start9labs/start-sdk'
|
||||||
|
import { tuiAsControl, TuiControl } from '@taiga-ui/cdk'
|
||||||
|
import {
|
||||||
|
TuiAlertService,
|
||||||
|
TuiButton,
|
||||||
|
TuiDialogContext,
|
||||||
|
TuiError,
|
||||||
|
} from '@taiga-ui/core'
|
||||||
|
import {
|
||||||
|
TUI_FORMAT_ERROR,
|
||||||
|
TUI_VALIDATION_ERRORS,
|
||||||
|
TuiFieldErrorPipe,
|
||||||
|
} from '@taiga-ui/kit'
|
||||||
|
import { PolymorpheusOutlet } from '@taiga-ui/polymorpheus'
|
||||||
|
import { filter } from 'rxjs'
|
||||||
|
|
||||||
|
import { ControlSpec } from '../controls/control'
|
||||||
|
import { CONTROLS } from '../controls/controls'
|
||||||
|
import { ControlDirective } from './control.directive'
|
||||||
|
|
||||||
|
export const ERRORS = [
|
||||||
|
'required',
|
||||||
|
'pattern',
|
||||||
|
'notNumber',
|
||||||
|
'numberNotInteger',
|
||||||
|
'numberNotInRange',
|
||||||
|
'listNotUnique',
|
||||||
|
'listNotInRange',
|
||||||
|
'listItemIssue',
|
||||||
|
]
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'form-control',
|
||||||
|
template: `
|
||||||
|
<ng-container *polymorpheusOutlet="controls[spec.type]" />
|
||||||
|
<tui-error [error]="order | tuiFieldError | async" />
|
||||||
|
@if (spec.warning || immutable) {
|
||||||
|
<ng-template #warning let-completeWith="completeWith">
|
||||||
|
{{ spec.warning }}
|
||||||
|
@if (immutable) {
|
||||||
|
<p>{{ 'This value cannot be changed once set' | i18n }}!</p>
|
||||||
|
}
|
||||||
|
<div [style.margin-top.rem]="0.5">
|
||||||
|
<button
|
||||||
|
tuiButton
|
||||||
|
type="button"
|
||||||
|
appearance="secondary-grayscale"
|
||||||
|
size="s"
|
||||||
|
[style.margin-inline-end.rem]="0.5"
|
||||||
|
(click)="completeWith(false)"
|
||||||
|
>
|
||||||
|
{{ 'Continue' | i18n }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
tuiButton
|
||||||
|
type="button"
|
||||||
|
appearance="flat-grayscale"
|
||||||
|
size="s"
|
||||||
|
(click)="completeWith(true)"
|
||||||
|
>
|
||||||
|
{{ 'Cancel' | i18n }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
providers: [
|
||||||
|
tuiAsControl(FormControlComponent),
|
||||||
|
{
|
||||||
|
provide: TUI_VALIDATION_ERRORS,
|
||||||
|
deps: [FormControlComponent],
|
||||||
|
useFactory: (control: FormControlComponent<ControlSpec, string>) => ({
|
||||||
|
[TUI_FORMAT_ERROR]: 'Invalid file format',
|
||||||
|
required: 'Required',
|
||||||
|
pattern: (context: any) =>
|
||||||
|
'patterns' in control.spec &&
|
||||||
|
getText(control.spec, String(context.requiredPattern)),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
hostDirectives: [ControlDirective],
|
||||||
|
imports: [
|
||||||
|
AsyncPipe,
|
||||||
|
i18nPipe,
|
||||||
|
PolymorpheusOutlet,
|
||||||
|
TuiError,
|
||||||
|
TuiFieldErrorPipe,
|
||||||
|
TuiButton,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class FormControlComponent<
|
||||||
|
T extends ControlSpec,
|
||||||
|
V,
|
||||||
|
> extends TuiControl<V | null> {
|
||||||
|
private readonly destroyRef = inject(DestroyRef)
|
||||||
|
private readonly alerts = inject(TuiAlertService)
|
||||||
|
private readonly i18n = inject(i18nPipe)
|
||||||
|
|
||||||
|
protected readonly controls = CONTROLS
|
||||||
|
|
||||||
|
@Input({ required: true })
|
||||||
|
spec!: T
|
||||||
|
|
||||||
|
@ViewChild('warning')
|
||||||
|
warning?: TemplateRef<TuiDialogContext<boolean>>
|
||||||
|
|
||||||
|
warned = false
|
||||||
|
readonly order = ERRORS
|
||||||
|
|
||||||
|
get immutable(): boolean {
|
||||||
|
return 'immutable' in this.spec && this.spec.immutable
|
||||||
|
}
|
||||||
|
|
||||||
|
onInput(value: V | null) {
|
||||||
|
const previous = this.value()
|
||||||
|
|
||||||
|
if (!this.warned && this.warning) {
|
||||||
|
this.alerts
|
||||||
|
.open<boolean>(this.warning, {
|
||||||
|
label: this.i18n.transform('Warning'),
|
||||||
|
appearance: 'warning',
|
||||||
|
closeable: false,
|
||||||
|
autoClose: 0,
|
||||||
|
})
|
||||||
|
.pipe(filter(Boolean), takeUntilDestroyed(this.destroyRef))
|
||||||
|
.subscribe(() => {
|
||||||
|
this.onChange(previous)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.warned = true
|
||||||
|
this.onChange(value === '' ? null : value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getText({ patterns }: IST.ValueSpecText, pattern: unknown): string {
|
||||||
|
return (
|
||||||
|
patterns?.find(({ regex }) => String(regex) === pattern)?.description ||
|
||||||
|
'Invalid format'
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { Directive, inject, Injectable, OnDestroy, OnInit } from '@angular/core'
|
||||||
|
import { ControlContainer, NgControl } from '@angular/forms'
|
||||||
|
import { tuiInjectElement } from '@taiga-ui/cdk'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class InvalidService {
|
||||||
|
private readonly controls: ControlDirective[] = []
|
||||||
|
|
||||||
|
scrollIntoView() {
|
||||||
|
this.controls.find(d => d.invalid)?.scrollIntoView()
|
||||||
|
}
|
||||||
|
|
||||||
|
add(control: ControlDirective) {
|
||||||
|
this.controls.push(control)
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(control: ControlDirective) {
|
||||||
|
this.controls.splice(this.controls.indexOf(control), 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive()
|
||||||
|
export class ControlDirective implements OnInit, OnDestroy {
|
||||||
|
private readonly service = inject(InvalidService, { optional: true })
|
||||||
|
private readonly element = tuiInjectElement()
|
||||||
|
private readonly control =
|
||||||
|
inject(NgControl, { optional: true }) ||
|
||||||
|
inject(ControlContainer, { optional: true })
|
||||||
|
|
||||||
|
get invalid(): boolean {
|
||||||
|
return !!this.control?.invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollIntoView() {
|
||||||
|
this.element.scrollIntoView({ behavior: 'smooth' })
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.service?.add(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.service?.remove(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import { KeyValuePipe } from '@angular/common'
|
||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
Input,
|
||||||
|
SkipSelf,
|
||||||
|
ViewEncapsulation,
|
||||||
|
} from '@angular/core'
|
||||||
|
import { ControlContainer, ReactiveFormsModule } from '@angular/forms'
|
||||||
|
import { IST } from '@start9labs/start-sdk'
|
||||||
|
import { TUI_DEFAULT_ERROR_MESSAGE } from '@taiga-ui/core'
|
||||||
|
import { identity, of } from 'rxjs'
|
||||||
|
|
||||||
|
import { FilterHiddenPipe } from '../pipes/filter-hidden.pipe'
|
||||||
|
import { FormArrayComponent } from './array.component'
|
||||||
|
import { FormControlComponent } from './control.component'
|
||||||
|
import { FormObjectComponent } from './object.component'
|
||||||
|
import { FormUnionComponent } from './union.component'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'form-group',
|
||||||
|
template: `
|
||||||
|
@for (entry of spec | keyvalue: asIsOrder | filterHidden; track entry) {
|
||||||
|
@switch (entry.value.type) {
|
||||||
|
@case ('object') {
|
||||||
|
<form-object
|
||||||
|
class="g-form-control"
|
||||||
|
[formGroupName]="entry.key"
|
||||||
|
[spec]="$any(entry.value)"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
@case ('union') {
|
||||||
|
<form-union
|
||||||
|
class="g-form-control"
|
||||||
|
[formGroupName]="entry.key"
|
||||||
|
[spec]="$any(entry.value)"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
@case ('list') {
|
||||||
|
<form-array [formArrayName]="entry.key" [spec]="$any(entry.value)" />
|
||||||
|
}
|
||||||
|
@default {
|
||||||
|
<form-control
|
||||||
|
class="g-form-control"
|
||||||
|
[formControlName]="entry.key"
|
||||||
|
[spec]="entry.value"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
styles: `
|
||||||
|
form-group .g-form-control:not(:first-child) {
|
||||||
|
display: block;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
form-group .g-form-group {
|
||||||
|
position: relative;
|
||||||
|
padding-left: var(--tui-height-m);
|
||||||
|
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
background: var(--tui-background-neutral-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
top: 0;
|
||||||
|
left: calc(1rem - 1px);
|
||||||
|
bottom: 0.5rem;
|
||||||
|
width: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
left: 0.75rem;
|
||||||
|
bottom: 0;
|
||||||
|
width: 0.5rem;
|
||||||
|
height: 0.5rem;
|
||||||
|
border-radius: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form-group tui-tooltip {
|
||||||
|
z-index: 1;
|
||||||
|
margin-left: 0.25rem;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
encapsulation: ViewEncapsulation.None,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
viewProviders: [
|
||||||
|
{
|
||||||
|
provide: TUI_DEFAULT_ERROR_MESSAGE,
|
||||||
|
useValue: of('Unknown error'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ControlContainer,
|
||||||
|
deps: [[new SkipSelf(), ControlContainer]],
|
||||||
|
useFactory: identity,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
KeyValuePipe,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
FilterHiddenPipe,
|
||||||
|
FormControlComponent,
|
||||||
|
FormObjectComponent,
|
||||||
|
FormArrayComponent,
|
||||||
|
FormUnionComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class FormGroupComponent {
|
||||||
|
@Input() spec: IST.InputSpec = {}
|
||||||
|
|
||||||
|
asIsOrder() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
EventEmitter,
|
||||||
|
forwardRef,
|
||||||
|
inject,
|
||||||
|
Input,
|
||||||
|
Output,
|
||||||
|
} from '@angular/core'
|
||||||
|
import { ControlContainer } from '@angular/forms'
|
||||||
|
import { IST } from '@start9labs/start-sdk'
|
||||||
|
import { TuiButton, TuiIcon } from '@taiga-ui/core'
|
||||||
|
import { TuiExpand } from '@taiga-ui/experimental'
|
||||||
|
import { TuiTooltip } from '@taiga-ui/kit'
|
||||||
|
|
||||||
|
import { ControlDirective } from './control.directive'
|
||||||
|
import { FormGroupComponent } from './group.component'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'form-object',
|
||||||
|
template: `
|
||||||
|
<h3 class="title" (click)="toggle()">
|
||||||
|
<button
|
||||||
|
tuiIconButton
|
||||||
|
size="s"
|
||||||
|
iconStart="@tui.chevron-down"
|
||||||
|
type="button"
|
||||||
|
class="button"
|
||||||
|
[class.button_open]="open"
|
||||||
|
[style.border-radius.%]="100"
|
||||||
|
[appearance]="invalid ? 'primary-destructive' : 'secondary'"
|
||||||
|
></button>
|
||||||
|
<ng-content />
|
||||||
|
{{ spec.name }}
|
||||||
|
@if (spec.description) {
|
||||||
|
<tui-icon [tuiTooltip]="spec.description" (click.stop)="(0)" />
|
||||||
|
}
|
||||||
|
</h3>
|
||||||
|
<tui-expand class="expand" [expanded]="open">
|
||||||
|
<div class="g-form-group" [class.g-form-group_invalid]="invalid">
|
||||||
|
<form-group [spec]="spec.spec" />
|
||||||
|
</div>
|
||||||
|
</tui-expand>
|
||||||
|
`,
|
||||||
|
styles: `
|
||||||
|
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
|
||||||
|
|
||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
position: relative;
|
||||||
|
height: var(--tui-height-l);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
font: var(--tui-font-text-l);
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0 0 -0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
@include taiga.transition(transform);
|
||||||
|
|
||||||
|
margin-right: 1rem;
|
||||||
|
|
||||||
|
&_open {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand {
|
||||||
|
align-self: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.g-form-group {
|
||||||
|
padding-top: 0.75rem;
|
||||||
|
|
||||||
|
&_invalid::before,
|
||||||
|
&_invalid::after {
|
||||||
|
background: var(--tui-status-negative-pale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
hostDirectives: [ControlDirective],
|
||||||
|
imports: [
|
||||||
|
TuiButton,
|
||||||
|
TuiIcon,
|
||||||
|
TuiTooltip,
|
||||||
|
TuiExpand,
|
||||||
|
forwardRef(() => FormGroupComponent),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class FormObjectComponent {
|
||||||
|
@Input({ required: true })
|
||||||
|
spec!: IST.ValueSpecObject
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
open = false
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
readonly openChange = new EventEmitter<boolean>()
|
||||||
|
|
||||||
|
private readonly container = inject(ControlContainer)
|
||||||
|
|
||||||
|
get invalid() {
|
||||||
|
return !this.container.valid && this.container.touched
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle() {
|
||||||
|
this.open = !this.open
|
||||||
|
this.openChange.emit(this.open)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,49 @@
|
|||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
|
forwardRef,
|
||||||
inject,
|
inject,
|
||||||
Input,
|
Input,
|
||||||
OnChanges,
|
OnChanges,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { ControlContainer, FormGroupName } from '@angular/forms'
|
import {
|
||||||
|
ControlContainer,
|
||||||
|
FormGroupName,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
} from '@angular/forms'
|
||||||
import { IST } from '@start9labs/start-sdk'
|
import { IST } from '@start9labs/start-sdk'
|
||||||
|
import { tuiPure, TuiValueChanges } from '@taiga-ui/cdk'
|
||||||
|
import { TuiElasticContainer } from '@taiga-ui/kit'
|
||||||
import { FormService } from 'src/app/services/form.service'
|
import { FormService } from 'src/app/services/form.service'
|
||||||
import { tuiPure } from '@taiga-ui/cdk'
|
|
||||||
|
import { FormControlComponent } from './control.component'
|
||||||
|
import { FormGroupComponent } from './group.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'form-union',
|
selector: 'form-union',
|
||||||
templateUrl: './form-union.component.html',
|
template: `
|
||||||
styleUrls: ['./form-union.component.scss'],
|
<form-control
|
||||||
|
[spec]="selectSpec"
|
||||||
|
formControlName="selection"
|
||||||
|
(tuiValueChanges)="onUnion($event)"
|
||||||
|
/>
|
||||||
|
<tui-elastic-container class="g-form-group" formGroupName="value">
|
||||||
|
<form-group
|
||||||
|
class="group"
|
||||||
|
[spec]="(union && spec.variants[union]?.spec) || {}"
|
||||||
|
/>
|
||||||
|
</tui-elastic-container>
|
||||||
|
`,
|
||||||
|
styles: `
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group {
|
||||||
|
display: block;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
viewProviders: [
|
viewProviders: [
|
||||||
{
|
{
|
||||||
@@ -21,7 +51,13 @@ import { tuiPure } from '@taiga-ui/cdk'
|
|||||||
useExisting: FormGroupName,
|
useExisting: FormGroupName,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
standalone: false,
|
imports: [
|
||||||
|
ReactiveFormsModule,
|
||||||
|
TuiValueChanges,
|
||||||
|
TuiElasticContainer,
|
||||||
|
FormControlComponent,
|
||||||
|
forwardRef(() => FormGroupComponent),
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class FormUnionComponent implements OnChanges {
|
export class FormUnionComponent implements OnChanges {
|
||||||
@Input({ required: true })
|
@Input({ required: true })
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { Directive, ElementRef, inject, OnDestroy, OnInit } from '@angular/core'
|
|
||||||
import { ControlContainer, NgControl } from '@angular/forms'
|
|
||||||
import { InvalidService } from './invalid.service'
|
|
||||||
|
|
||||||
@Directive({
|
|
||||||
selector: 'form-control, form-array, form-object',
|
|
||||||
standalone: false,
|
|
||||||
})
|
|
||||||
export class ControlDirective implements OnInit, OnDestroy {
|
|
||||||
private readonly invalidService = inject(InvalidService, { optional: true })
|
|
||||||
private readonly element: ElementRef<HTMLElement> = inject(ElementRef)
|
|
||||||
private readonly control =
|
|
||||||
inject(NgControl, { optional: true }) ||
|
|
||||||
inject(ControlContainer, { optional: true })
|
|
||||||
|
|
||||||
get invalid(): boolean {
|
|
||||||
return !!this.control?.invalid
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollIntoView() {
|
|
||||||
this.element.nativeElement.scrollIntoView({ behavior: 'smooth' })
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.invalidService?.add(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy() {
|
|
||||||
this.invalidService?.remove(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import { inject } from '@angular/core'
|
|
||||||
import { FormControlComponent } from './form-control/form-control.component'
|
|
||||||
import { IST } from '@start9labs/start-sdk'
|
|
||||||
|
|
||||||
export abstract class Control<
|
|
||||||
Spec extends Exclude<IST.ValueSpec, IST.ValueSpecHidden>,
|
|
||||||
Value,
|
|
||||||
> {
|
|
||||||
private readonly control: FormControlComponent<Spec, Value> =
|
|
||||||
inject(FormControlComponent)
|
|
||||||
|
|
||||||
get invalid(): boolean {
|
|
||||||
return this.control.touched && this.control.invalid
|
|
||||||
}
|
|
||||||
|
|
||||||
get spec(): Spec {
|
|
||||||
return this.control.spec
|
|
||||||
}
|
|
||||||
|
|
||||||
get readOnly(): boolean {
|
|
||||||
const def =
|
|
||||||
'default' in this.spec &&
|
|
||||||
this.spec.default != null &&
|
|
||||||
this.spec.default !== this.value
|
|
||||||
|
|
||||||
return (
|
|
||||||
!!this.value &&
|
|
||||||
!def &&
|
|
||||||
!!this.control.control?.pristine &&
|
|
||||||
this.control.immutable
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
get value(): Value | null {
|
|
||||||
return this.control.value
|
|
||||||
}
|
|
||||||
|
|
||||||
set value(value: Value | null) {
|
|
||||||
this.control.onInput(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
onFocus(focused: boolean) {
|
|
||||||
this.control.onFocus(focused)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||||
|
import { FormsModule } from '@angular/forms'
|
||||||
|
import { IST } from '@start9labs/start-sdk'
|
||||||
|
import { TuiIcon, TuiTextfield } from '@taiga-ui/core'
|
||||||
|
import { TuiInputColor, TuiTooltip } from '@taiga-ui/kit'
|
||||||
|
|
||||||
|
import { Control } from './control'
|
||||||
|
import { HintPipe } from '../pipes/hint.pipe'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'form-color',
|
||||||
|
template: `
|
||||||
|
<tui-textfield iconStart=" " [tuiTextfieldCleaner]="false">
|
||||||
|
@if (spec.name) {
|
||||||
|
<label tuiLabel>
|
||||||
|
{{ spec.name }}
|
||||||
|
@if (spec.required) {
|
||||||
|
<span>*</span>
|
||||||
|
}
|
||||||
|
</label>
|
||||||
|
}
|
||||||
|
<input
|
||||||
|
placeholder="#000000"
|
||||||
|
tuiInputColor
|
||||||
|
[invalid]="control.invalid()"
|
||||||
|
[readOnly]="readOnly"
|
||||||
|
[disabled]="!!spec.disabled"
|
||||||
|
[(ngModel)]="value"
|
||||||
|
(blur)="control.onTouched()"
|
||||||
|
/>
|
||||||
|
@if (spec | hint; as hint) {
|
||||||
|
<tui-icon [tuiTooltip]="hint" />
|
||||||
|
}
|
||||||
|
</tui-textfield>
|
||||||
|
`,
|
||||||
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
TuiTextfield,
|
||||||
|
TuiInputColor,
|
||||||
|
TuiIcon,
|
||||||
|
TuiTooltip,
|
||||||
|
HintPipe,
|
||||||
|
],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class FormColorComponent extends Control<IST.ValueSpecColor, string> {}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { inject } from '@angular/core'
|
||||||
|
import { IST } from '@start9labs/start-sdk'
|
||||||
|
import { TuiControl } from '@taiga-ui/cdk'
|
||||||
|
|
||||||
|
export type ControlSpec = Exclude<
|
||||||
|
IST.ValueSpec,
|
||||||
|
| IST.ValueSpecHidden
|
||||||
|
| IST.ValueSpecList
|
||||||
|
| IST.ValueSpecUnion
|
||||||
|
| IST.ValueSpecObject
|
||||||
|
>
|
||||||
|
|
||||||
|
export abstract class Control<Spec extends ControlSpec, Value> {
|
||||||
|
public readonly control: any = inject(TuiControl)
|
||||||
|
|
||||||
|
get spec(): Spec {
|
||||||
|
return this.control.spec
|
||||||
|
}
|
||||||
|
|
||||||
|
get readOnly(): boolean {
|
||||||
|
const def =
|
||||||
|
'default' in this.spec &&
|
||||||
|
this.spec.default != null &&
|
||||||
|
this.spec.default !== this.value
|
||||||
|
|
||||||
|
return (
|
||||||
|
!!this.value &&
|
||||||
|
!def &&
|
||||||
|
!!this.control['control']?.pristine &&
|
||||||
|
this.control.immutable
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
get value(): Value | null {
|
||||||
|
return this.control.value()
|
||||||
|
}
|
||||||
|
|
||||||
|
set value(value: Value | null) {
|
||||||
|
this.control.onInput(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||||
|
import { FormColorComponent } from './color.component'
|
||||||
|
import { FormDatetimeComponent } from './datetime.component'
|
||||||
|
import { FormFileComponent } from './file.component'
|
||||||
|
import { FormMultiselectComponent } from './multiselect.component'
|
||||||
|
import { FormNumberComponent } from './number.component'
|
||||||
|
import { FormSelectComponent } from './select.component'
|
||||||
|
import { FormTextComponent } from './text.component'
|
||||||
|
import { FormTextareaComponent } from './textarea.component'
|
||||||
|
import { FormToggleComponent } from './toggle.component'
|
||||||
|
|
||||||
|
export const CONTROLS = {
|
||||||
|
color: new PolymorpheusComponent(FormColorComponent),
|
||||||
|
datetime: new PolymorpheusComponent(FormDatetimeComponent),
|
||||||
|
file: new PolymorpheusComponent(FormFileComponent),
|
||||||
|
number: new PolymorpheusComponent(FormNumberComponent),
|
||||||
|
select: new PolymorpheusComponent(FormSelectComponent),
|
||||||
|
multiselect: new PolymorpheusComponent(FormMultiselectComponent),
|
||||||
|
text: new PolymorpheusComponent(FormTextComponent),
|
||||||
|
textarea: new PolymorpheusComponent(FormTextareaComponent),
|
||||||
|
toggle: new PolymorpheusComponent(FormToggleComponent),
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
import { Component } from '@angular/core'
|
||||||
|
import { FormsModule } from '@angular/forms'
|
||||||
|
import { IST } from '@start9labs/start-sdk'
|
||||||
|
import {
|
||||||
|
TUI_FIRST_DAY,
|
||||||
|
TUI_LAST_DAY,
|
||||||
|
TuiDay,
|
||||||
|
TuiMapperPipe,
|
||||||
|
tuiPure,
|
||||||
|
TuiTime,
|
||||||
|
} from '@taiga-ui/cdk'
|
||||||
|
import { TuiIcon, TuiTextfield } from '@taiga-ui/core'
|
||||||
|
import {
|
||||||
|
TuiInputDate,
|
||||||
|
TuiInputDateTime,
|
||||||
|
TuiInputTime,
|
||||||
|
TuiTooltip,
|
||||||
|
} from '@taiga-ui/kit'
|
||||||
|
|
||||||
|
import { Control } from './control'
|
||||||
|
import { HintPipe } from '../pipes/hint.pipe'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'form-datetime',
|
||||||
|
template: `
|
||||||
|
<!--
|
||||||
|
TODO: Move @switch down to only affect <input ... /> after fix:
|
||||||
|
https://github.com/taiga-family/taiga-ui/issues/11780
|
||||||
|
-->
|
||||||
|
@switch (spec.inputmode) {
|
||||||
|
@case ('time') {
|
||||||
|
<tui-textfield (tuiActiveZoneChange)="!$event && control.onTouched()">
|
||||||
|
@if (spec.name) {
|
||||||
|
<label tuiLabel>
|
||||||
|
{{ spec.name }}
|
||||||
|
@if (spec.required) {
|
||||||
|
<span>*</span>
|
||||||
|
}
|
||||||
|
</label>
|
||||||
|
}
|
||||||
|
<input
|
||||||
|
tuiInputTime
|
||||||
|
type="time"
|
||||||
|
[invalid]="control.invalid()"
|
||||||
|
[readOnly]="readOnly"
|
||||||
|
[disabled]="!!spec.disabled"
|
||||||
|
[ngModel]="getTime(value)"
|
||||||
|
(ngModelChange)="value = $event?.toString() || null"
|
||||||
|
(blur)="control.onTouched()"
|
||||||
|
/>
|
||||||
|
@if (spec | hint; as hint) {
|
||||||
|
<tui-icon [tuiTooltip]="hint" />
|
||||||
|
}
|
||||||
|
</tui-textfield>
|
||||||
|
}
|
||||||
|
@case ('date') {
|
||||||
|
<tui-textfield (tuiActiveZoneChange)="!$event && control.onTouched()">
|
||||||
|
@if (spec.name) {
|
||||||
|
<label tuiLabel>
|
||||||
|
{{ spec.name }}
|
||||||
|
@if (spec.required) {
|
||||||
|
<span>*</span>
|
||||||
|
}
|
||||||
|
</label>
|
||||||
|
}
|
||||||
|
<input
|
||||||
|
tuiInputDate
|
||||||
|
type="date"
|
||||||
|
[invalid]="control.invalid()"
|
||||||
|
[readOnly]="readOnly"
|
||||||
|
[disabled]="!!spec.disabled"
|
||||||
|
[min]="spec.min ? (spec.min | tuiMapper: getLimit)[0] : min"
|
||||||
|
[max]="spec.max ? (spec.max | tuiMapper: getLimit)[0] : max"
|
||||||
|
[(ngModel)]="value"
|
||||||
|
(blur)="control.onTouched()"
|
||||||
|
/>
|
||||||
|
@if (spec | hint; as hint) {
|
||||||
|
<tui-icon [tuiTooltip]="hint" />
|
||||||
|
}
|
||||||
|
<tui-calendar *tuiTextfieldDropdown />
|
||||||
|
</tui-textfield>
|
||||||
|
}
|
||||||
|
@case ('datetime-local') {
|
||||||
|
<tui-textfield (tuiActiveZoneChange)="!$event && control.onTouched()">
|
||||||
|
@if (spec.name) {
|
||||||
|
<label tuiLabel>
|
||||||
|
{{ spec.name }}
|
||||||
|
@if (spec.required) {
|
||||||
|
<span>*</span>
|
||||||
|
}
|
||||||
|
</label>
|
||||||
|
}
|
||||||
|
<input
|
||||||
|
tuiInputDateTime
|
||||||
|
type="datetime-local"
|
||||||
|
[invalid]="control.invalid()"
|
||||||
|
[readOnly]="readOnly"
|
||||||
|
[disabled]="!!spec.disabled"
|
||||||
|
[min]="spec.min ? (spec.min | tuiMapper: getLimit)[0] : min"
|
||||||
|
[max]="spec.max ? (spec.max | tuiMapper: getLimit)[0] : max"
|
||||||
|
[(ngModel)]="value"
|
||||||
|
(blur)="control.onTouched()"
|
||||||
|
/>
|
||||||
|
@if (spec | hint; as hint) {
|
||||||
|
<tui-icon [tuiTooltip]="hint" />
|
||||||
|
}
|
||||||
|
<tui-calendar *tuiTextfieldDropdown />
|
||||||
|
</tui-textfield>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
TuiTextfield,
|
||||||
|
TuiIcon,
|
||||||
|
TuiTooltip,
|
||||||
|
TuiInputTime,
|
||||||
|
TuiInputDate,
|
||||||
|
TuiMapperPipe,
|
||||||
|
TuiInputDateTime,
|
||||||
|
HintPipe,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class FormDatetimeComponent extends Control<
|
||||||
|
IST.ValueSpecDatetime,
|
||||||
|
string
|
||||||
|
> {
|
||||||
|
readonly min = TUI_FIRST_DAY
|
||||||
|
readonly max = TUI_LAST_DAY
|
||||||
|
|
||||||
|
@tuiPure
|
||||||
|
getTime(value: string | null) {
|
||||||
|
return value ? TuiTime.fromString(value) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
getLimit(limit: string): [TuiDay, TuiTime] {
|
||||||
|
return [
|
||||||
|
TuiDay.jsonParse(limit.slice(0, 10)),
|
||||||
|
limit.length === 10
|
||||||
|
? new TuiTime(0, 0)
|
||||||
|
: TuiTime.fromString(limit.slice(-5)),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import { Component } from '@angular/core'
|
||||||
|
import { FormsModule } from '@angular/forms'
|
||||||
|
import { i18nPipe } from '@start9labs/shared'
|
||||||
|
import { IST } from '@start9labs/start-sdk'
|
||||||
|
import { TuiButton, TuiIcon } from '@taiga-ui/core'
|
||||||
|
import { TuiChip, TuiFileLike, TuiFiles, TuiTooltip } from '@taiga-ui/kit'
|
||||||
|
|
||||||
|
import { Control } from './control'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'form-file',
|
||||||
|
template: `
|
||||||
|
<label tuiInputFiles>
|
||||||
|
<input
|
||||||
|
tuiInputFiles
|
||||||
|
[invalid]="control.invalid()"
|
||||||
|
[accept]="spec.extensions.join(',')"
|
||||||
|
[(ngModel)]="value"
|
||||||
|
(blur)="control.onTouched()"
|
||||||
|
/>
|
||||||
|
<ng-template let-drop>
|
||||||
|
<div class="template" [class.template_hidden]="drop">
|
||||||
|
<div class="label">
|
||||||
|
{{ spec.name }}
|
||||||
|
@if (spec.required) {
|
||||||
|
<span>*</span>
|
||||||
|
}
|
||||||
|
@if (spec.description) {
|
||||||
|
<tui-icon [tuiTooltip]="spec.description" />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
@if (value) {
|
||||||
|
<tui-chip>
|
||||||
|
{{ value.name }}
|
||||||
|
<button
|
||||||
|
tuiIconButton
|
||||||
|
type="button"
|
||||||
|
appearance="icon"
|
||||||
|
size="xs"
|
||||||
|
iconStart="@tui.x"
|
||||||
|
(click.stop)="value = null"
|
||||||
|
>
|
||||||
|
{{ 'Delete' | i18n }}
|
||||||
|
</button>
|
||||||
|
</tui-chip>
|
||||||
|
} @else {
|
||||||
|
<small>{{ 'Click or drop file here' | i18n }}</small>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="drop" [class.drop_hidden]="!drop">
|
||||||
|
{{ 'Drop file here' | i18n }}
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</label>
|
||||||
|
`,
|
||||||
|
styles: `
|
||||||
|
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
|
||||||
|
|
||||||
|
.template {
|
||||||
|
@include taiga.transition(opacity);
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
font: var(--tui-font-text-m);
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&_hidden {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop {
|
||||||
|
@include taiga.fullsize();
|
||||||
|
@include taiga.transition(opacity);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
|
||||||
|
&_hidden {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
max-width: 50%;
|
||||||
|
font-weight: normal;
|
||||||
|
color: var(--tui-text-secondary);
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
tui-chip {
|
||||||
|
z-index: 1;
|
||||||
|
margin: -0.25rem -0.25rem -0.25rem auto;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
TuiFiles,
|
||||||
|
TuiIcon,
|
||||||
|
TuiTooltip,
|
||||||
|
TuiChip,
|
||||||
|
TuiButton,
|
||||||
|
i18nPipe,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class FormFileComponent extends Control<
|
||||||
|
IST.ValueSpecFile,
|
||||||
|
TuiFileLike
|
||||||
|
> {}
|
||||||
@@ -1,13 +1,49 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { IST } from '@start9labs/start-sdk'
|
import { FormsModule } from '@angular/forms'
|
||||||
import { Control } from '../control'
|
|
||||||
import { tuiPure } from '@taiga-ui/cdk'
|
|
||||||
import { invert } from '@start9labs/shared'
|
import { invert } from '@start9labs/shared'
|
||||||
|
import { IST } from '@start9labs/start-sdk'
|
||||||
|
import { tuiPure } from '@taiga-ui/cdk'
|
||||||
|
import { TuiIcon, TuiTextfield } from '@taiga-ui/core'
|
||||||
|
import { TuiMultiSelect, TuiTooltip } from '@taiga-ui/kit'
|
||||||
|
|
||||||
|
import { Control } from './control'
|
||||||
|
import { HintPipe } from '../pipes/hint.pipe'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'form-multiselect',
|
selector: 'form-multiselect',
|
||||||
templateUrl: './form-multiselect.component.html',
|
template: `
|
||||||
standalone: false,
|
<tui-textfield multi [disabledItemHandler]="disabledItemHandler">
|
||||||
|
@if (spec.name) {
|
||||||
|
<label tuiLabel>{{ spec.name }}</label>
|
||||||
|
}
|
||||||
|
<select
|
||||||
|
tuiMultiSelect
|
||||||
|
[invalid]="control.invalid()"
|
||||||
|
[disabled]="disabled"
|
||||||
|
[readOnly]="readOnly"
|
||||||
|
[items]="items"
|
||||||
|
[(ngModel)]="selected"
|
||||||
|
(blur)="control.onTouched()"
|
||||||
|
></select>
|
||||||
|
@if (spec | hint; as hint) {
|
||||||
|
<tui-icon [tuiTooltip]="hint" />
|
||||||
|
}
|
||||||
|
</tui-textfield>
|
||||||
|
`,
|
||||||
|
styles: `
|
||||||
|
// TODO: Remove after Taiga UI update
|
||||||
|
:host ::ng-deep .t-input {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
TuiTextfield,
|
||||||
|
TuiMultiSelect,
|
||||||
|
TuiIcon,
|
||||||
|
TuiTooltip,
|
||||||
|
HintPipe,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class FormMultiselectComponent extends Control<
|
export class FormMultiselectComponent extends Control<
|
||||||
IST.ValueSpecMultiselect,
|
IST.ValueSpecMultiselect,
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { Component } from '@angular/core'
|
||||||
|
import { FormsModule } from '@angular/forms'
|
||||||
|
import { IST } from '@start9labs/start-sdk'
|
||||||
|
import { TuiIcon, TuiNumberFormat, TuiTextfield } from '@taiga-ui/core'
|
||||||
|
import { TuiInputNumber, TuiTooltip } from '@taiga-ui/kit'
|
||||||
|
|
||||||
|
import { Control } from './control'
|
||||||
|
import { HintPipe } from '../pipes/hint.pipe'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'form-number',
|
||||||
|
template: `
|
||||||
|
<tui-textfield [tuiNumberFormat]="{ precision, decimalMode: 'not-zero' }">
|
||||||
|
@if (spec.name) {
|
||||||
|
<label tuiLabel>
|
||||||
|
{{ spec.name }}
|
||||||
|
@if (spec.required) {
|
||||||
|
<span>*</span>
|
||||||
|
}
|
||||||
|
</label>
|
||||||
|
}
|
||||||
|
<input
|
||||||
|
tuiInputNumber
|
||||||
|
[postfix]="spec.units ? ' ' + spec.units : ''"
|
||||||
|
[min]="spec.min"
|
||||||
|
[max]="spec.max"
|
||||||
|
[step]="spec.step || 0"
|
||||||
|
[invalid]="control.invalid()"
|
||||||
|
[disabled]="!!spec.disabled"
|
||||||
|
[readOnly]="readOnly"
|
||||||
|
[placeholder]="spec.placeholder || ''"
|
||||||
|
[(ngModel)]="value"
|
||||||
|
(blur)="control.onTouched()"
|
||||||
|
/>
|
||||||
|
@if (spec | hint; as hint) {
|
||||||
|
<tui-icon [tuiTooltip]="hint" />
|
||||||
|
}
|
||||||
|
</tui-textfield>
|
||||||
|
`,
|
||||||
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
TuiTextfield,
|
||||||
|
TuiInputNumber,
|
||||||
|
TuiNumberFormat,
|
||||||
|
TuiIcon,
|
||||||
|
TuiTooltip,
|
||||||
|
HintPipe,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class FormNumberComponent extends Control<IST.ValueSpecNumber, number> {
|
||||||
|
get precision(): number {
|
||||||
|
return this.spec.integer ? 0 : Infinity
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import { Component, inject } from '@angular/core'
|
||||||
|
import { FormsModule } from '@angular/forms'
|
||||||
|
import { invert } from '@start9labs/shared'
|
||||||
|
import { IST } from '@start9labs/start-sdk'
|
||||||
|
import { TUI_IS_MOBILE } from '@taiga-ui/cdk'
|
||||||
|
import { TuiIcon, TuiTextfield } from '@taiga-ui/core'
|
||||||
|
import { TuiDataListWrapper, TuiSelect, TuiTooltip } from '@taiga-ui/kit'
|
||||||
|
|
||||||
|
import { Control } from './control'
|
||||||
|
import { HintPipe } from '../pipes/hint.pipe'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'form-select',
|
||||||
|
template: `
|
||||||
|
<tui-textfield
|
||||||
|
[tuiTextfieldCleaner]="false"
|
||||||
|
[disabledItemHandler]="disabledItemHandler"
|
||||||
|
(tuiActiveZoneChange)="!$event && control.onTouched()"
|
||||||
|
>
|
||||||
|
@if (spec.name) {
|
||||||
|
<label tuiLabel>{{ spec.name }} *</label>
|
||||||
|
}
|
||||||
|
@if (mobile) {
|
||||||
|
<select
|
||||||
|
tuiSelect
|
||||||
|
[disabled]="disabled"
|
||||||
|
[readOnly]="readOnly"
|
||||||
|
[invalid]="control.invalid()"
|
||||||
|
[placeholder]="spec.name"
|
||||||
|
[items]="items"
|
||||||
|
[(ngModel)]="selected"
|
||||||
|
></select>
|
||||||
|
} @else {
|
||||||
|
<input
|
||||||
|
tuiSelect
|
||||||
|
[disabled]="disabled"
|
||||||
|
[readOnly]="readOnly"
|
||||||
|
[invalid]="control.invalid()"
|
||||||
|
[placeholder]="spec.name"
|
||||||
|
[(ngModel)]="selected"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
<tui-data-list-wrapper *tuiTextfieldDropdown new [items]="items" />
|
||||||
|
@if (spec | hint; as hint) {
|
||||||
|
<tui-icon [tuiTooltip]="hint" />
|
||||||
|
}
|
||||||
|
</tui-textfield>
|
||||||
|
`,
|
||||||
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
TuiTextfield,
|
||||||
|
TuiSelect,
|
||||||
|
TuiDataListWrapper,
|
||||||
|
TuiIcon,
|
||||||
|
TuiTooltip,
|
||||||
|
HintPipe,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class FormSelectComponent extends Control<IST.ValueSpecSelect, string> {
|
||||||
|
private readonly inverted = invert(this.spec.values)
|
||||||
|
|
||||||
|
protected readonly mobile = inject(TUI_IS_MOBILE)
|
||||||
|
protected readonly items = Object.values(this.spec.values)
|
||||||
|
protected readonly disabledItemHandler = (item: string) =>
|
||||||
|
Array.isArray(this.spec.disabled) &&
|
||||||
|
!!this.inverted[item] &&
|
||||||
|
this.spec.disabled.includes(this.inverted[item]!)
|
||||||
|
|
||||||
|
get disabled(): boolean {
|
||||||
|
return typeof this.spec.disabled === 'string'
|
||||||
|
}
|
||||||
|
|
||||||
|
get selected(): string | null {
|
||||||
|
return (this.value && this.spec.values[this.value]) || null
|
||||||
|
}
|
||||||
|
|
||||||
|
set selected(value: string | null) {
|
||||||
|
this.value = (value && this.inverted[value]) || null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import { Component } from '@angular/core'
|
||||||
|
import { FormsModule } from '@angular/forms'
|
||||||
|
import { IST, utils } from '@start9labs/start-sdk'
|
||||||
|
import { tuiInjectElement } from '@taiga-ui/cdk'
|
||||||
|
import { TuiButton, TuiIcon, TuiTextfield } from '@taiga-ui/core'
|
||||||
|
import { TuiTooltip } from '@taiga-ui/kit'
|
||||||
|
|
||||||
|
import { Control } from './control'
|
||||||
|
import { HintPipe } from '../pipes/hint.pipe'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'form-text',
|
||||||
|
template: `
|
||||||
|
<tui-textfield>
|
||||||
|
@if (spec.name) {
|
||||||
|
<label tuiLabel>
|
||||||
|
{{ spec.name }}
|
||||||
|
@if (spec.required) {
|
||||||
|
<span>*</span>
|
||||||
|
}
|
||||||
|
</label>
|
||||||
|
}
|
||||||
|
<input
|
||||||
|
tuiTextfield
|
||||||
|
[attr.inputmode]="spec.inputmode"
|
||||||
|
[attr.minLength]="spec.minLength"
|
||||||
|
[attr.maxLength]="spec.maxLength"
|
||||||
|
[style.-webkit-text-security]="spec.masked && masked ? 'disc' : null"
|
||||||
|
[placeholder]="spec.placeholder || ''"
|
||||||
|
[disabled]="!!spec.disabled"
|
||||||
|
[readOnly]="readOnly"
|
||||||
|
[invalid]="control.invalid()"
|
||||||
|
[(ngModel)]="value"
|
||||||
|
(blur)="control.onTouched()"
|
||||||
|
/>
|
||||||
|
@if (spec.generate) {
|
||||||
|
<button
|
||||||
|
tuiIconButton
|
||||||
|
type="button"
|
||||||
|
appearance="icon"
|
||||||
|
title="Generate"
|
||||||
|
size="xs"
|
||||||
|
iconStart="@tui.refresh-ccw"
|
||||||
|
(click)="generate()"
|
||||||
|
></button>
|
||||||
|
}
|
||||||
|
@if (spec.masked) {
|
||||||
|
<button
|
||||||
|
tuiIconButton
|
||||||
|
type="button"
|
||||||
|
appearance="icon"
|
||||||
|
title="Toggle masking"
|
||||||
|
size="xs"
|
||||||
|
[iconStart]="masked ? '@tui.eye' : '@tui.eye-off'"
|
||||||
|
(click)="masked = !masked"
|
||||||
|
></button>
|
||||||
|
}
|
||||||
|
<button
|
||||||
|
tuiIconButton
|
||||||
|
type="button"
|
||||||
|
iconStart="@tui.trash"
|
||||||
|
appearance="icon"
|
||||||
|
size="xs"
|
||||||
|
title="Remove"
|
||||||
|
class="remove"
|
||||||
|
(click)="remove()"
|
||||||
|
></button>
|
||||||
|
@if (spec | hint; as hint) {
|
||||||
|
<tui-icon [tuiTooltip]="hint" />
|
||||||
|
}
|
||||||
|
</tui-textfield>
|
||||||
|
`,
|
||||||
|
styles: `
|
||||||
|
.remove {
|
||||||
|
display: none;
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host-context(form-array > form-control > :host) .remove {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
TuiTextfield,
|
||||||
|
TuiButton,
|
||||||
|
TuiIcon,
|
||||||
|
TuiTooltip,
|
||||||
|
HintPipe,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class FormTextComponent extends Control<IST.ValueSpecText, string> {
|
||||||
|
private readonly el = tuiInjectElement()
|
||||||
|
|
||||||
|
masked = true
|
||||||
|
|
||||||
|
generate() {
|
||||||
|
this.value = utils.getDefaultString(this.spec.generate || '')
|
||||||
|
}
|
||||||
|
|
||||||
|
remove() {
|
||||||
|
this.el.dispatchEvent(new CustomEvent('remove', { bubbles: true }))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { Component } from '@angular/core'
|
||||||
|
import { FormsModule } from '@angular/forms'
|
||||||
|
import { IST } from '@start9labs/start-sdk'
|
||||||
|
import { TuiIcon, TuiTextfield } from '@taiga-ui/core'
|
||||||
|
import { TuiTextarea, TuiTooltip } from '@taiga-ui/kit'
|
||||||
|
|
||||||
|
import { Control } from './control'
|
||||||
|
import { HintPipe } from '../pipes/hint.pipe'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'form-textarea',
|
||||||
|
template: `
|
||||||
|
<tui-textfield>
|
||||||
|
@if (spec.name) {
|
||||||
|
<label tuiLabel>
|
||||||
|
{{ spec.name }}
|
||||||
|
@if (spec.required) {
|
||||||
|
<span>*</span>
|
||||||
|
}
|
||||||
|
</label>
|
||||||
|
}
|
||||||
|
<textarea
|
||||||
|
placeholder="Placeholder"
|
||||||
|
tuiTextarea
|
||||||
|
[max]="6"
|
||||||
|
[min]="3"
|
||||||
|
[attr.maxLength]="spec.maxLength"
|
||||||
|
[disabled]="!!spec.disabled"
|
||||||
|
[readOnly]="readOnly"
|
||||||
|
[placeholder]="spec.placeholder || ''"
|
||||||
|
[invalid]="control.invalid()"
|
||||||
|
[(ngModel)]="value"
|
||||||
|
(blur)="control.onTouched()"
|
||||||
|
></textarea>
|
||||||
|
@if (spec | hint; as hint) {
|
||||||
|
<tui-icon [tuiTooltip]="hint" />
|
||||||
|
}
|
||||||
|
</tui-textfield>
|
||||||
|
`,
|
||||||
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
TuiTextfield,
|
||||||
|
TuiTextarea,
|
||||||
|
TuiIcon,
|
||||||
|
TuiTooltip,
|
||||||
|
HintPipe,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class FormTextareaComponent extends Control<
|
||||||
|
IST.ValueSpecTextarea,
|
||||||
|
string
|
||||||
|
> {}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { Component } from '@angular/core'
|
||||||
|
import { FormsModule } from '@angular/forms'
|
||||||
|
import { IST } from '@start9labs/start-sdk'
|
||||||
|
import { TuiIcon } from '@taiga-ui/core'
|
||||||
|
import { TuiSwitch, TuiTooltip } from '@taiga-ui/kit'
|
||||||
|
|
||||||
|
import { Control } from './control'
|
||||||
|
import { HintPipe } from '../pipes/hint.pipe'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'form-toggle',
|
||||||
|
template: `
|
||||||
|
{{ spec.name }}
|
||||||
|
@if (spec.description || spec.disabled) {
|
||||||
|
<tui-icon [tuiTooltip]="spec | hint" />
|
||||||
|
}
|
||||||
|
<input
|
||||||
|
tuiSwitch
|
||||||
|
type="checkbox"
|
||||||
|
size="m"
|
||||||
|
[disabled]="!!spec.disabled || readOnly"
|
||||||
|
[showIcons]="false"
|
||||||
|
[(ngModel)]="value"
|
||||||
|
(blur)="control.onTouched()"
|
||||||
|
/>
|
||||||
|
`,
|
||||||
|
host: { class: 'g-toggle' },
|
||||||
|
imports: [TuiIcon, TuiTooltip, HintPipe, TuiSwitch, FormsModule],
|
||||||
|
})
|
||||||
|
export class FormToggleComponent extends Control<
|
||||||
|
IST.ValueSpecToggle,
|
||||||
|
boolean
|
||||||
|
> {}
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
<div class="label">
|
|
||||||
{{ spec.name }}
|
|
||||||
@if (spec.description || spec.disabled) {
|
|
||||||
<tui-icon [tuiTooltip]="spec | hint" />
|
|
||||||
}
|
|
||||||
<button
|
|
||||||
tuiLink
|
|
||||||
type="button"
|
|
||||||
class="add"
|
|
||||||
[disabled]="!canAdd"
|
|
||||||
(click)="add()"
|
|
||||||
>
|
|
||||||
+ {{ 'Add' | i18n }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<tui-error [error]="order | tuiFieldError | async" />
|
|
||||||
|
|
||||||
@for (item of array.control.controls; track item) {
|
|
||||||
@if (spec.spec.type === 'object') {
|
|
||||||
<form-object
|
|
||||||
class="object"
|
|
||||||
[class.object_open]="!!open.get(item)"
|
|
||||||
[formGroup]="$any(item)"
|
|
||||||
[spec]="$any(spec.spec)"
|
|
||||||
[@tuiHeightCollapse]="animation"
|
|
||||||
[@tuiFadeIn]="animation"
|
|
||||||
[open]="!!open.get(item)"
|
|
||||||
(openChange)="open.set(item, $event)"
|
|
||||||
>
|
|
||||||
{{ item.value | mustache: $any(spec.spec).displayAs }}
|
|
||||||
<ng-container *ngTemplateOutlet="remove" />
|
|
||||||
</form-object>
|
|
||||||
} @else {
|
|
||||||
<form-control
|
|
||||||
class="control"
|
|
||||||
tuiTextfieldSize="m"
|
|
||||||
[tuiTextfieldLabelOutside]="true"
|
|
||||||
[tuiTextfieldIcon]="remove"
|
|
||||||
[formControl]="$any(item)"
|
|
||||||
[spec]="$any(spec.spec)"
|
|
||||||
[@tuiHeightCollapse]="animation"
|
|
||||||
[@tuiFadeIn]="animation"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
<ng-template #remove>
|
|
||||||
<button
|
|
||||||
tuiIconButton
|
|
||||||
type="button"
|
|
||||||
class="remove"
|
|
||||||
iconStart="@tui.trash"
|
|
||||||
appearance="icon"
|
|
||||||
size="m"
|
|
||||||
title="Remove"
|
|
||||||
(click.stop)="removeAt($index)"
|
|
||||||
></button>
|
|
||||||
</ng-template>
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
|
|
||||||
|
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
margin: 2rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
display: flex;
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add {
|
|
||||||
font-size: 1rem;
|
|
||||||
padding: 0 1rem;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.object {
|
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&_open::after,
|
|
||||||
&:last-child::after {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
@include taiga.transition(opacity);
|
|
||||||
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
bottom: -0.5rem;
|
|
||||||
height: 1px;
|
|
||||||
left: 3rem;
|
|
||||||
right: 1rem;
|
|
||||||
background: var(--tui-background-neutral-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove {
|
|
||||||
margin-left: auto;
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control {
|
|
||||||
display: block;
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
import {
|
|
||||||
Component,
|
|
||||||
DestroyRef,
|
|
||||||
HostBinding,
|
|
||||||
inject,
|
|
||||||
Input,
|
|
||||||
} from '@angular/core'
|
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
|
|
||||||
import { AbstractControl, FormArrayName } from '@angular/forms'
|
|
||||||
import {
|
|
||||||
TUI_ANIMATIONS_SPEED,
|
|
||||||
tuiFadeIn,
|
|
||||||
tuiHeightCollapse,
|
|
||||||
tuiParentStop,
|
|
||||||
tuiToAnimationOptions,
|
|
||||||
} from '@taiga-ui/core'
|
|
||||||
import { filter } from 'rxjs'
|
|
||||||
import { IST } from '@start9labs/start-sdk'
|
|
||||||
import { FormService } from 'src/app/services/form.service'
|
|
||||||
import { ERRORS } from '../form-group/form-group.component'
|
|
||||||
import { DialogService, i18nKey } from '@start9labs/shared'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'form-array',
|
|
||||||
templateUrl: './form-array.component.html',
|
|
||||||
styleUrls: ['./form-array.component.scss'],
|
|
||||||
animations: [tuiFadeIn, tuiHeightCollapse, tuiParentStop],
|
|
||||||
standalone: false,
|
|
||||||
})
|
|
||||||
export class FormArrayComponent {
|
|
||||||
@Input({ required: true })
|
|
||||||
spec!: IST.ValueSpecList
|
|
||||||
|
|
||||||
@HostBinding('@tuiParentStop')
|
|
||||||
readonly animation = tuiToAnimationOptions(inject(TUI_ANIMATIONS_SPEED))
|
|
||||||
readonly order = ERRORS
|
|
||||||
readonly array = inject(FormArrayName)
|
|
||||||
readonly open = new Map<AbstractControl, boolean>()
|
|
||||||
|
|
||||||
private warned = false
|
|
||||||
private readonly formService = inject(FormService)
|
|
||||||
private readonly destroyRef = inject(DestroyRef)
|
|
||||||
private readonly dialog = inject(DialogService)
|
|
||||||
|
|
||||||
get canAdd(): boolean {
|
|
||||||
return (
|
|
||||||
!this.spec.disabled &&
|
|
||||||
(!this.spec.maxLength ||
|
|
||||||
this.spec.maxLength >= this.array.control.controls.length)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
add() {
|
|
||||||
if (!this.warned && this.spec.warning) {
|
|
||||||
this.dialog
|
|
||||||
.openConfirm<boolean>({
|
|
||||||
label: 'Warning',
|
|
||||||
size: 's',
|
|
||||||
data: {
|
|
||||||
content: this.spec.warning as i18nKey,
|
|
||||||
yes: 'Ok',
|
|
||||||
no: 'Cancel',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.pipe(filter(Boolean), takeUntilDestroyed(this.destroyRef))
|
|
||||||
.subscribe(() => {
|
|
||||||
this.addItem()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.addItem()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.warned = true
|
|
||||||
}
|
|
||||||
|
|
||||||
removeAt(index: number) {
|
|
||||||
this.removeItem(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
private removeItem(index: number) {
|
|
||||||
this.open.delete(this.array.control.at(index))
|
|
||||||
this.array.control.removeAt(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
private addItem() {
|
|
||||||
this.array.control.insert(0, this.formService.getListItem(this.spec))
|
|
||||||
this.open.set(this.array.control.at(0), true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<tui-input
|
|
||||||
[maskito]="mask"
|
|
||||||
[tuiTextfieldCustomContent]="color"
|
|
||||||
[tuiTextfieldCleaner]="false"
|
|
||||||
[tuiHintContent]="spec | hint"
|
|
||||||
[readOnly]="readOnly"
|
|
||||||
[disabled]="!!spec.disabled"
|
|
||||||
[pseudoInvalid]="invalid"
|
|
||||||
[(ngModel)]="value"
|
|
||||||
(focusedChange)="onFocus($event)"
|
|
||||||
>
|
|
||||||
{{ spec.name }}
|
|
||||||
@if (spec.required) {
|
|
||||||
<span>*</span>
|
|
||||||
}
|
|
||||||
</tui-input>
|
|
||||||
<ng-template #color>
|
|
||||||
<div class="wrapper" [style.color]="value">
|
|
||||||
@if (!readOnly && !spec.disabled) {
|
|
||||||
<input
|
|
||||||
type="color"
|
|
||||||
class="color"
|
|
||||||
tabindex="-1"
|
|
||||||
[(ngModel)]="value"
|
|
||||||
(click.stop)="(0)"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
<tui-icon icon="@tui.paint-bucket" tuiAppearance="icon" class="icon" />
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
|
|
||||||
|
|
||||||
.wrapper {
|
|
||||||
position: relative;
|
|
||||||
width: 1.5rem;
|
|
||||||
height: 1.5rem;
|
|
||||||
pointer-events: auto;
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
height: 0.3rem;
|
|
||||||
width: 1.4rem;
|
|
||||||
bottom: -0.25rem;
|
|
||||||
background: currentColor;
|
|
||||||
border-radius: 0.125rem;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.color {
|
|
||||||
@include taiga.fullsize();
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
@include taiga.fullsize();
|
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
input:hover + & {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { Component } from '@angular/core'
|
|
||||||
import { IST } from '@start9labs/start-sdk'
|
|
||||||
import { Control } from '../control'
|
|
||||||
import { MaskitoOptions } from '@maskito/core'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'form-color',
|
|
||||||
templateUrl: './form-color.component.html',
|
|
||||||
styleUrls: ['./form-color.component.scss'],
|
|
||||||
standalone: false,
|
|
||||||
})
|
|
||||||
export class FormColorComponent extends Control<IST.ValueSpecColor, string> {
|
|
||||||
readonly mask: MaskitoOptions = {
|
|
||||||
mask: ['#', ...Array(6).fill(/[0-9a-f]/i)],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
@switch (spec.type) {
|
|
||||||
@case ('color') {
|
|
||||||
<form-color />
|
|
||||||
}
|
|
||||||
@case ('datetime') {
|
|
||||||
<form-datetime />
|
|
||||||
}
|
|
||||||
@case ('file') {
|
|
||||||
<form-file />
|
|
||||||
}
|
|
||||||
@case ('multiselect') {
|
|
||||||
<form-multiselect />
|
|
||||||
}
|
|
||||||
@case ('number') {
|
|
||||||
<form-number />
|
|
||||||
}
|
|
||||||
@case ('select') {
|
|
||||||
<form-select />
|
|
||||||
}
|
|
||||||
@case ('text') {
|
|
||||||
<form-text />
|
|
||||||
}
|
|
||||||
@case ('textarea') {
|
|
||||||
<form-textarea />
|
|
||||||
}
|
|
||||||
@case ('toggle') {
|
|
||||||
<form-toggle />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
<tui-error [error]="order | tuiFieldError | async" />
|
|
||||||
@if (spec.warning || immutable) {
|
|
||||||
<ng-template #warning let-completeWith="completeWith">
|
|
||||||
{{ spec.warning }}
|
|
||||||
@if (immutable) {
|
|
||||||
<p>{{ 'This value cannot be changed once set' | i18n }}!</p>
|
|
||||||
}
|
|
||||||
<div class="buttons">
|
|
||||||
<button
|
|
||||||
tuiButton
|
|
||||||
type="button"
|
|
||||||
appearance="secondary"
|
|
||||||
size="s"
|
|
||||||
(click)="completeWith(true)"
|
|
||||||
>
|
|
||||||
{{ 'Cancel' | i18n }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
tuiButton
|
|
||||||
type="button"
|
|
||||||
appearance="flat-grayscale"
|
|
||||||
size="s"
|
|
||||||
(click)="completeWith(false)"
|
|
||||||
>
|
|
||||||
{{ 'Continue' | i18n }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
:host {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons {
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
|
|
||||||
:first-child {
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
import {
|
|
||||||
ChangeDetectionStrategy,
|
|
||||||
Component,
|
|
||||||
inject,
|
|
||||||
Input,
|
|
||||||
TemplateRef,
|
|
||||||
ViewChild,
|
|
||||||
} from '@angular/core'
|
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
|
|
||||||
import { AbstractTuiNullableControl } from '@taiga-ui/legacy'
|
|
||||||
import { filter } from 'rxjs'
|
|
||||||
import { TuiAlertService, TuiDialogContext } from '@taiga-ui/core'
|
|
||||||
import { IST } from '@start9labs/start-sdk'
|
|
||||||
import { ERRORS } from '../form-group/form-group.component'
|
|
||||||
import { FORM_CONTROL_PROVIDERS } from './form-control.providers'
|
|
||||||
import { DialogService, i18nKey, i18nPipe } from '@start9labs/shared'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'form-control',
|
|
||||||
templateUrl: './form-control.component.html',
|
|
||||||
styleUrls: ['./form-control.component.scss'],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
providers: FORM_CONTROL_PROVIDERS,
|
|
||||||
standalone: false,
|
|
||||||
})
|
|
||||||
export class FormControlComponent<
|
|
||||||
T extends Exclude<IST.ValueSpec, IST.ValueSpecHidden>,
|
|
||||||
V,
|
|
||||||
> extends AbstractTuiNullableControl<V> {
|
|
||||||
private readonly alerts = inject(TuiAlertService)
|
|
||||||
private readonly i18n = inject(i18nPipe)
|
|
||||||
|
|
||||||
@Input({ required: true })
|
|
||||||
spec!: T
|
|
||||||
|
|
||||||
@ViewChild('warning')
|
|
||||||
warning?: TemplateRef<TuiDialogContext<boolean>>
|
|
||||||
|
|
||||||
warned = false
|
|
||||||
focused = false
|
|
||||||
readonly order = ERRORS
|
|
||||||
|
|
||||||
get immutable(): boolean {
|
|
||||||
return 'immutable' in this.spec && this.spec.immutable
|
|
||||||
}
|
|
||||||
|
|
||||||
onFocus(focused: boolean) {
|
|
||||||
this.focused = focused
|
|
||||||
this.updateFocused(focused)
|
|
||||||
}
|
|
||||||
|
|
||||||
onInput(value: V | null) {
|
|
||||||
const previous = this.value
|
|
||||||
|
|
||||||
if (!this.warned && this.warning) {
|
|
||||||
this.alerts
|
|
||||||
.open<boolean>(this.warning, {
|
|
||||||
label: this.i18n.transform('Warning'),
|
|
||||||
appearance: 'warning',
|
|
||||||
closeable: false,
|
|
||||||
autoClose: 0,
|
|
||||||
})
|
|
||||||
.pipe(filter(Boolean), takeUntilDestroyed(this.destroyRef))
|
|
||||||
.subscribe(() => {
|
|
||||||
this.value = previous
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.warned = true
|
|
||||||
this.value = value === '' ? null : value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { forwardRef, Provider } from '@angular/core'
|
|
||||||
import { IST } from '@start9labs/start-sdk'
|
|
||||||
import { TUI_FORMAT_ERROR, TUI_VALIDATION_ERRORS } from '@taiga-ui/kit'
|
|
||||||
import { FormControlComponent } from './form-control.component'
|
|
||||||
|
|
||||||
interface ValidatorsPatternError {
|
|
||||||
actualValue: string
|
|
||||||
requiredPattern: string | RegExp
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FORM_CONTROL_PROVIDERS: Provider[] = [
|
|
||||||
{
|
|
||||||
provide: TUI_VALIDATION_ERRORS,
|
|
||||||
deps: [forwardRef(() => FormControlComponent)],
|
|
||||||
useFactory: (
|
|
||||||
control: FormControlComponent<
|
|
||||||
Exclude<IST.ValueSpec, IST.ValueSpecHidden>,
|
|
||||||
string
|
|
||||||
>,
|
|
||||||
) => ({
|
|
||||||
required: 'Required',
|
|
||||||
pattern: ({ requiredPattern }: ValidatorsPatternError) =>
|
|
||||||
('patterns' in control.spec &&
|
|
||||||
control.spec.patterns.find(
|
|
||||||
({ regex }) => String(regex) === String(requiredPattern),
|
|
||||||
)?.description) ||
|
|
||||||
'Invalid format',
|
|
||||||
[TUI_FORMAT_ERROR]: 'Invalid file format',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
<ng-container [tuiHintContent]="spec.description">
|
|
||||||
@switch (spec.inputmode) {
|
|
||||||
@case ('time') {
|
|
||||||
<tui-input-time
|
|
||||||
[tuiHintContent]="spec | hint"
|
|
||||||
[disabled]="!!spec.disabled"
|
|
||||||
[readOnly]="readOnly"
|
|
||||||
[pseudoInvalid]="invalid"
|
|
||||||
[ngModel]="getTime(value)"
|
|
||||||
(ngModelChange)="value = $event?.toString() || null"
|
|
||||||
(focusedChange)="onFocus($event)"
|
|
||||||
>
|
|
||||||
{{ spec.name }}
|
|
||||||
@if (spec.required) {
|
|
||||||
<span>*</span>
|
|
||||||
}
|
|
||||||
</tui-input-time>
|
|
||||||
}
|
|
||||||
@case ('date') {
|
|
||||||
<tui-input-date
|
|
||||||
[tuiHintContent]="spec | hint"
|
|
||||||
[disabled]="!!spec.disabled"
|
|
||||||
[readOnly]="readOnly"
|
|
||||||
[pseudoInvalid]="invalid"
|
|
||||||
[min]="spec.min ? (spec.min | tuiMapper: getLimit)[0] : min"
|
|
||||||
[max]="spec.max ? (spec.max | tuiMapper: getLimit)[0] : max"
|
|
||||||
[(ngModel)]="value"
|
|
||||||
(focusedChange)="onFocus($event)"
|
|
||||||
>
|
|
||||||
{{ spec.name }}
|
|
||||||
@if (spec.required) {
|
|
||||||
<span>*</span>
|
|
||||||
}
|
|
||||||
</tui-input-date>
|
|
||||||
}
|
|
||||||
@case ('datetime-local') {
|
|
||||||
<tui-input-date-time
|
|
||||||
[tuiHintContent]="spec | hint"
|
|
||||||
[disabled]="!!spec.disabled"
|
|
||||||
[readOnly]="readOnly"
|
|
||||||
[pseudoInvalid]="invalid"
|
|
||||||
[min]="spec.min ? (spec.min | tuiMapper: getLimit) : min"
|
|
||||||
[max]="spec.max ? (spec.max | tuiMapper: getLimit) : max"
|
|
||||||
[(ngModel)]="value"
|
|
||||||
(focusedChange)="onFocus($event)"
|
|
||||||
>
|
|
||||||
{{ spec.name }}
|
|
||||||
@if (spec.required) {
|
|
||||||
<span>*</span>
|
|
||||||
}
|
|
||||||
</tui-input-date-time>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</ng-container>
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import { Component } from '@angular/core'
|
|
||||||
import {
|
|
||||||
TUI_FIRST_DAY,
|
|
||||||
TUI_LAST_DAY,
|
|
||||||
TuiDay,
|
|
||||||
tuiPure,
|
|
||||||
TuiTime,
|
|
||||||
} from '@taiga-ui/cdk'
|
|
||||||
import { IST } from '@start9labs/start-sdk'
|
|
||||||
import { Control } from '../control'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'form-datetime',
|
|
||||||
templateUrl: './form-datetime.component.html',
|
|
||||||
standalone: false,
|
|
||||||
})
|
|
||||||
export class FormDatetimeComponent extends Control<
|
|
||||||
IST.ValueSpecDatetime,
|
|
||||||
string
|
|
||||||
> {
|
|
||||||
readonly min = TUI_FIRST_DAY
|
|
||||||
readonly max = TUI_LAST_DAY
|
|
||||||
|
|
||||||
@tuiPure
|
|
||||||
getTime(value: string | null) {
|
|
||||||
return value ? TuiTime.fromString(value) : null
|
|
||||||
}
|
|
||||||
|
|
||||||
getLimit(limit: string): [TuiDay, TuiTime] {
|
|
||||||
return [
|
|
||||||
TuiDay.jsonParse(limit.slice(0, 10)),
|
|
||||||
limit.length === 10
|
|
||||||
? new TuiTime(0, 0)
|
|
||||||
: TuiTime.fromString(limit.slice(-5)),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
<label tuiInputFiles>
|
|
||||||
<input
|
|
||||||
tuiInputFiles
|
|
||||||
[invalid]="invalid"
|
|
||||||
[accept]="spec.extensions.join(',')"
|
|
||||||
[(ngModel)]="value"
|
|
||||||
(blur)="onFocus(false)"
|
|
||||||
/>
|
|
||||||
<ng-template let-drop>
|
|
||||||
<div class="template" [class.template_hidden]="drop">
|
|
||||||
<div class="label">
|
|
||||||
{{ spec.name }}
|
|
||||||
@if (spec.required) {
|
|
||||||
<span>*</span>
|
|
||||||
}
|
|
||||||
@if (spec.description) {
|
|
||||||
<tui-icon [tuiTooltip]="spec.description" />
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
@if (value) {
|
|
||||||
<tui-chip>
|
|
||||||
{{ value.name }}
|
|
||||||
<button
|
|
||||||
tuiIconButton
|
|
||||||
type="button"
|
|
||||||
appearance="icon"
|
|
||||||
size="xs"
|
|
||||||
iconStart="@tui.x"
|
|
||||||
(click.stop)="value = null"
|
|
||||||
>
|
|
||||||
{{ 'Delete' | i18n }}
|
|
||||||
</button>
|
|
||||||
</tui-chip>
|
|
||||||
} @else {
|
|
||||||
<small>{{ 'Click or drop file here' | i18n }}</small>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="drop" [class.drop_hidden]="!drop">
|
|
||||||
{{ 'Drop file here' | i18n }}
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
</label>
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
|
|
||||||
|
|
||||||
.template {
|
|
||||||
@include taiga.transition(opacity);
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0 0.5rem;
|
|
||||||
font: var(--tui-font-text-m);
|
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
&_hidden {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.drop {
|
|
||||||
@include taiga.fullsize();
|
|
||||||
@include taiga.transition(opacity);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-around;
|
|
||||||
|
|
||||||
&_hidden {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
max-width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
small {
|
|
||||||
max-width: 50%;
|
|
||||||
font-weight: normal;
|
|
||||||
color: var(--tui-text-secondary);
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
tui-chip {
|
|
||||||
z-index: 1;
|
|
||||||
margin: -0.25rem -0.25rem -0.25rem auto;
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { Component } from '@angular/core'
|
|
||||||
import { TuiFileLike } from '@taiga-ui/kit'
|
|
||||||
import { IST } from '@start9labs/start-sdk'
|
|
||||||
import { Control } from '../control'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'form-file',
|
|
||||||
templateUrl: './form-file.component.html',
|
|
||||||
styleUrls: ['./form-file.component.scss'],
|
|
||||||
standalone: false,
|
|
||||||
})
|
|
||||||
export class FormFileComponent extends Control<
|
|
||||||
IST.ValueSpecFile,
|
|
||||||
TuiFileLike
|
|
||||||
> {}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
@for (entry of spec | keyvalue: asIsOrder | filterHidden; track entry) {
|
|
||||||
<ng-container [tuiTextfieldCleaner]="true">
|
|
||||||
@switch (entry.value.type) {
|
|
||||||
@case ('object') {
|
|
||||||
<form-object
|
|
||||||
class="g-form-control"
|
|
||||||
[formGroupName]="entry.key"
|
|
||||||
[spec]="$any(entry.value)"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
@case ('union') {
|
|
||||||
<form-union
|
|
||||||
class="g-form-control"
|
|
||||||
[formGroupName]="entry.key"
|
|
||||||
[spec]="$any(entry.value)"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
@case ('list') {
|
|
||||||
<form-array [formArrayName]="entry.key" [spec]="$any(entry.value)" />
|
|
||||||
}
|
|
||||||
@default {
|
|
||||||
<form-control
|
|
||||||
class="g-form-control"
|
|
||||||
[formControlName]="entry.key"
|
|
||||||
[spec]="entry.value"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</ng-container>
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
form-group .g-form-control:not(:first-child) {
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
form-group .g-form-group {
|
|
||||||
position: relative;
|
|
||||||
padding-left: var(--tui-height-m);
|
|
||||||
|
|
||||||
&::before,
|
|
||||||
&::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
background: var(--tui-background-neutral-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
top: 0;
|
|
||||||
left: calc(1rem - 1px);
|
|
||||||
bottom: 0.5rem;
|
|
||||||
width: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
left: 0.75rem;
|
|
||||||
bottom: 0;
|
|
||||||
width: 0.5rem;
|
|
||||||
height: 0.5rem;
|
|
||||||
border-radius: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
form-group tui-tooltip {
|
|
||||||
z-index: 1;
|
|
||||||
margin-left: 0.25rem;
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import {
|
|
||||||
ChangeDetectionStrategy,
|
|
||||||
Component,
|
|
||||||
Input,
|
|
||||||
ViewEncapsulation,
|
|
||||||
} from '@angular/core'
|
|
||||||
import { IST } from '@start9labs/start-sdk'
|
|
||||||
import { FORM_GROUP_PROVIDERS } from './form-group.providers'
|
|
||||||
|
|
||||||
export const ERRORS = [
|
|
||||||
'required',
|
|
||||||
'pattern',
|
|
||||||
'notNumber',
|
|
||||||
'numberNotInteger',
|
|
||||||
'numberNotInRange',
|
|
||||||
'listNotUnique',
|
|
||||||
'listNotInRange',
|
|
||||||
'listItemIssue',
|
|
||||||
]
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'form-group',
|
|
||||||
templateUrl: './form-group.component.html',
|
|
||||||
styleUrls: ['./form-group.component.scss'],
|
|
||||||
encapsulation: ViewEncapsulation.None,
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
viewProviders: [FORM_GROUP_PROVIDERS],
|
|
||||||
standalone: false,
|
|
||||||
})
|
|
||||||
export class FormGroupComponent {
|
|
||||||
@Input() spec: IST.InputSpec = {}
|
|
||||||
|
|
||||||
asIsOrder() {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { Provider, SkipSelf } from '@angular/core'
|
|
||||||
import { ControlContainer } from '@angular/forms'
|
|
||||||
import { TUI_DEFAULT_ERROR_MESSAGE } from '@taiga-ui/core'
|
|
||||||
import { tuiInputDateOptionsProvider } from '@taiga-ui/kit'
|
|
||||||
import { TUI_ARROW_MODE, tuiInputTimeOptionsProvider } from '@taiga-ui/legacy'
|
|
||||||
import { identity, of } from 'rxjs'
|
|
||||||
|
|
||||||
export const FORM_GROUP_PROVIDERS: Provider[] = [
|
|
||||||
{
|
|
||||||
provide: TUI_DEFAULT_ERROR_MESSAGE,
|
|
||||||
useValue: of('Unknown error'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: ControlContainer,
|
|
||||||
deps: [[new SkipSelf(), ControlContainer]],
|
|
||||||
useFactory: identity,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: TUI_ARROW_MODE,
|
|
||||||
useValue: {
|
|
||||||
interactive: null,
|
|
||||||
disabled: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tuiInputDateOptionsProvider({
|
|
||||||
nativePicker: true,
|
|
||||||
}),
|
|
||||||
tuiInputTimeOptionsProvider({
|
|
||||||
nativePicker: true,
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<tui-multi-select
|
|
||||||
[tuiHintContent]="spec | hint"
|
|
||||||
[disabled]="disabled"
|
|
||||||
[readOnly]="readOnly"
|
|
||||||
[pseudoInvalid]="invalid"
|
|
||||||
[editable]="false"
|
|
||||||
[disabledItemHandler]="disabledItemHandler"
|
|
||||||
[(ngModel)]="selected"
|
|
||||||
(focusedChange)="onFocus($event)"
|
|
||||||
>
|
|
||||||
{{ spec.name }}
|
|
||||||
<select
|
|
||||||
tuiSelect
|
|
||||||
multiple
|
|
||||||
[items]="items"
|
|
||||||
[disabledItemHandler]="disabledItemHandler"
|
|
||||||
></select>
|
|
||||||
</tui-multi-select>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<tui-input-number
|
|
||||||
[tuiHintContent]="spec | hint"
|
|
||||||
[disabled]="!!spec.disabled"
|
|
||||||
[readOnly]="readOnly"
|
|
||||||
[tuiTextfieldPostfix]="spec.units || ''"
|
|
||||||
[pseudoInvalid]="invalid"
|
|
||||||
[tuiNumberFormat]="{
|
|
||||||
precision: spec.integer ? 0 : Infinity,
|
|
||||||
decimalMode: 'not-zero',
|
|
||||||
}"
|
|
||||||
[min]="spec.min ?? -Infinity"
|
|
||||||
[max]="spec.max ?? Infinity"
|
|
||||||
[step]="spec.step || 0"
|
|
||||||
[(ngModel)]="value"
|
|
||||||
(focusedChange)="onFocus($event)"
|
|
||||||
>
|
|
||||||
{{ spec.name }}
|
|
||||||
@if (spec.required) {
|
|
||||||
<span>*</span>
|
|
||||||
}
|
|
||||||
<input tuiTextfieldLegacy [placeholder]="spec.placeholder || ''" />
|
|
||||||
</tui-input-number>
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { Component } from '@angular/core'
|
|
||||||
import { IST } from '@start9labs/start-sdk'
|
|
||||||
import { Control } from '../control'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'form-number',
|
|
||||||
templateUrl: './form-number.component.html',
|
|
||||||
standalone: false,
|
|
||||||
})
|
|
||||||
export class FormNumberComponent extends Control<IST.ValueSpecNumber, number> {
|
|
||||||
protected readonly Infinity = Infinity
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<h3 class="title" (click)="toggle()">
|
|
||||||
<button
|
|
||||||
tuiIconButton
|
|
||||||
size="s"
|
|
||||||
iconStart="@tui.chevron-down"
|
|
||||||
type="button"
|
|
||||||
class="button"
|
|
||||||
[class.button_open]="open"
|
|
||||||
[style.border-radius.%]="100"
|
|
||||||
[appearance]="invalid ? 'primary-destructive' : 'secondary'"
|
|
||||||
></button>
|
|
||||||
<ng-content />
|
|
||||||
{{ spec.name }}
|
|
||||||
@if (spec.description) {
|
|
||||||
<tui-icon [tuiTooltip]="spec.description" (click.stop)="(0)" />
|
|
||||||
}
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<tui-expand class="expand" [expanded]="open">
|
|
||||||
<div class="g-form-group" [class.g-form-group_invalid]="invalid">
|
|
||||||
<form-group [spec]="spec.spec" />
|
|
||||||
</div>
|
|
||||||
</tui-expand>
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
|
|
||||||
|
|
||||||
:host {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
position: relative;
|
|
||||||
height: var(--tui-height-l);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
font: var(--tui-font-text-l);
|
|
||||||
font-weight: bold;
|
|
||||||
margin: 0 0 -0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
|
||||||
@include taiga.transition(transform);
|
|
||||||
|
|
||||||
margin-right: 1rem;
|
|
||||||
|
|
||||||
&_open {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.expand {
|
|
||||||
align-self: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.g-form-group {
|
|
||||||
padding-top: 0.75rem;
|
|
||||||
|
|
||||||
&_invalid::before,
|
|
||||||
&_invalid::after {
|
|
||||||
background: var(--tui-status-negative-pale);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import {
|
|
||||||
ChangeDetectionStrategy,
|
|
||||||
Component,
|
|
||||||
EventEmitter,
|
|
||||||
inject,
|
|
||||||
Input,
|
|
||||||
Output,
|
|
||||||
} from '@angular/core'
|
|
||||||
import { ControlContainer } from '@angular/forms'
|
|
||||||
import { IST } from '@start9labs/start-sdk'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'form-object',
|
|
||||||
templateUrl: './form-object.component.html',
|
|
||||||
styleUrls: ['./form-object.component.scss'],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
standalone: false,
|
|
||||||
})
|
|
||||||
export class FormObjectComponent {
|
|
||||||
@Input({ required: true })
|
|
||||||
spec!: IST.ValueSpecObject
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
open = false
|
|
||||||
|
|
||||||
@Output()
|
|
||||||
readonly openChange = new EventEmitter<boolean>()
|
|
||||||
|
|
||||||
private readonly container = inject(ControlContainer)
|
|
||||||
|
|
||||||
get invalid() {
|
|
||||||
return !this.container.valid && this.container.touched
|
|
||||||
}
|
|
||||||
|
|
||||||
toggle() {
|
|
||||||
this.open = !this.open
|
|
||||||
this.openChange.emit(this.open)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<tui-select
|
|
||||||
[tuiHintContent]="spec | hint"
|
|
||||||
[disabled]="disabled"
|
|
||||||
[readOnly]="readOnly"
|
|
||||||
[tuiTextfieldCleaner]="false"
|
|
||||||
[pseudoInvalid]="invalid"
|
|
||||||
[(ngModel)]="selected"
|
|
||||||
(focusedChange)="onFocus($event)"
|
|
||||||
>
|
|
||||||
{{ spec.name }} *
|
|
||||||
<select
|
|
||||||
tuiSelect
|
|
||||||
[placeholder]="spec.name"
|
|
||||||
[items]="items"
|
|
||||||
[disabledItemHandler]="disabledItemHandler"
|
|
||||||
></select>
|
|
||||||
</tui-select>
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import { Component } from '@angular/core'
|
|
||||||
import { IST } from '@start9labs/start-sdk'
|
|
||||||
import { invert } from '@start9labs/shared'
|
|
||||||
import { Control } from '../control'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'form-select',
|
|
||||||
templateUrl: './form-select.component.html',
|
|
||||||
standalone: false,
|
|
||||||
})
|
|
||||||
export class FormSelectComponent extends Control<IST.ValueSpecSelect, string> {
|
|
||||||
private readonly inverted = invert(this.spec.values)
|
|
||||||
|
|
||||||
readonly items = Object.values(this.spec.values)
|
|
||||||
|
|
||||||
readonly disabledItemHandler = (item: string) =>
|
|
||||||
Array.isArray(this.spec.disabled) &&
|
|
||||||
!!this.inverted[item] &&
|
|
||||||
this.spec.disabled.includes(this.inverted[item]!)
|
|
||||||
|
|
||||||
get disabled(): boolean {
|
|
||||||
return typeof this.spec.disabled === 'string'
|
|
||||||
}
|
|
||||||
|
|
||||||
get selected(): string | null {
|
|
||||||
return (this.value && this.spec.values[this.value]) || null
|
|
||||||
}
|
|
||||||
|
|
||||||
set selected(value: string | null) {
|
|
||||||
this.value = (value && this.inverted[value]) || null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
<tui-input
|
|
||||||
[tuiTextfieldCustomContent]="spec.masked || spec.generate ? toggle : ''"
|
|
||||||
[tuiHintContent]="spec | hint"
|
|
||||||
[disabled]="!!spec.disabled"
|
|
||||||
[readOnly]="readOnly"
|
|
||||||
[pseudoInvalid]="invalid"
|
|
||||||
[(ngModel)]="value"
|
|
||||||
(focusedChange)="onFocus($event)"
|
|
||||||
>
|
|
||||||
{{ spec.name }}
|
|
||||||
@if (spec.required) {
|
|
||||||
<span>*</span>
|
|
||||||
}
|
|
||||||
<input
|
|
||||||
tuiTextfieldLegacy
|
|
||||||
[class.masked]="spec.masked && masked"
|
|
||||||
[placeholder]="spec.placeholder || ''"
|
|
||||||
[attr.minLength]="spec.minLength"
|
|
||||||
[attr.maxLength]="spec.maxLength"
|
|
||||||
[attr.inputmode]="spec.inputmode"
|
|
||||||
/>
|
|
||||||
</tui-input>
|
|
||||||
<ng-template #toggle>
|
|
||||||
@if (spec.generate) {
|
|
||||||
<button
|
|
||||||
tuiIconButton
|
|
||||||
type="button"
|
|
||||||
appearance="icon"
|
|
||||||
title="Generate"
|
|
||||||
size="xs"
|
|
||||||
class="button"
|
|
||||||
iconStart="@tui.refresh-ccw"
|
|
||||||
(click)="generate()"
|
|
||||||
></button>
|
|
||||||
}
|
|
||||||
@if (spec.masked) {
|
|
||||||
<button
|
|
||||||
tuiIconButton
|
|
||||||
type="button"
|
|
||||||
appearance="icon"
|
|
||||||
title="Toggle masking"
|
|
||||||
size="xs"
|
|
||||||
class="button"
|
|
||||||
[iconStart]="masked ? '@tui.eye' : '@tui.eye-off'"
|
|
||||||
(click)="masked = !masked"
|
|
||||||
></button>
|
|
||||||
}
|
|
||||||
</ng-template>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
.button {
|
|
||||||
pointer-events: auto;
|
|
||||||
margin-left: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.masked {
|
|
||||||
-webkit-text-security: disc;
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { Component } from '@angular/core'
|
|
||||||
import { IST, utils } from '@start9labs/start-sdk'
|
|
||||||
import { Control } from '../control'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'form-text',
|
|
||||||
templateUrl: './form-text.component.html',
|
|
||||||
styleUrls: ['./form-text.component.scss'],
|
|
||||||
standalone: false,
|
|
||||||
})
|
|
||||||
export class FormTextComponent extends Control<IST.ValueSpecText, string> {
|
|
||||||
masked = true
|
|
||||||
|
|
||||||
generate() {
|
|
||||||
this.value = utils.getDefaultString(this.spec.generate || '')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<tui-textarea
|
|
||||||
[tuiHintContent]="spec | hint"
|
|
||||||
[disabled]="!!spec.disabled"
|
|
||||||
[readOnly]="readOnly"
|
|
||||||
[pseudoInvalid]="invalid"
|
|
||||||
[expandable]="true"
|
|
||||||
[rows]="6"
|
|
||||||
[maxLength]="spec.maxLength"
|
|
||||||
[(ngModel)]="value"
|
|
||||||
(focusedChange)="onFocus($event)"
|
|
||||||
>
|
|
||||||
{{ spec.name }}
|
|
||||||
@if (spec.required) {
|
|
||||||
<span>*</span>
|
|
||||||
}
|
|
||||||
<textarea
|
|
||||||
tuiTextfieldLegacy
|
|
||||||
[placeholder]="spec.placeholder || ''"
|
|
||||||
></textarea>
|
|
||||||
</tui-textarea>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import { Component } from '@angular/core'
|
|
||||||
import { IST } from '@start9labs/start-sdk'
|
|
||||||
import { Control } from '../control'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'form-textarea',
|
|
||||||
templateUrl: './form-textarea.component.html',
|
|
||||||
standalone: false,
|
|
||||||
})
|
|
||||||
export class FormTextareaComponent extends Control<
|
|
||||||
IST.ValueSpecTextarea,
|
|
||||||
string
|
|
||||||
> {}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
{{ spec.name }}
|
|
||||||
@if (spec.description || spec.disabled) {
|
|
||||||
<tui-icon [tuiTooltip]="spec | hint" />
|
|
||||||
}
|
|
||||||
<input
|
|
||||||
tuiSwitch
|
|
||||||
type="checkbox"
|
|
||||||
size="m"
|
|
||||||
[disabled]="!!spec.disabled || readOnly"
|
|
||||||
[showIcons]="false"
|
|
||||||
[(ngModel)]="value"
|
|
||||||
(blur)="onFocus(false)"
|
|
||||||
/>
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { Component } from '@angular/core'
|
|
||||||
import { IST } from '@start9labs/start-sdk'
|
|
||||||
import { Control } from '../control'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'form-toggle',
|
|
||||||
templateUrl: './form-toggle.component.html',
|
|
||||||
host: { class: 'g-toggle' },
|
|
||||||
standalone: false,
|
|
||||||
})
|
|
||||||
export class FormToggleComponent extends Control<
|
|
||||||
IST.ValueSpecToggle,
|
|
||||||
boolean
|
|
||||||
> {}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<form-control
|
|
||||||
[spec]="selectSpec"
|
|
||||||
formControlName="selection"
|
|
||||||
(tuiValueChanges)="onUnion($event)"
|
|
||||||
></form-control>
|
|
||||||
<tui-elastic-container class="g-form-group" formGroupName="value">
|
|
||||||
<form-group
|
|
||||||
class="group"
|
|
||||||
[spec]="(union && spec.variants[union]?.spec) || {}"
|
|
||||||
></form-group>
|
|
||||||
</tui-elastic-container>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
:host {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.group {
|
|
||||||
display: block;
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
|
||||||
import { MaskitoDirective } from '@maskito/angular'
|
|
||||||
import { i18nPipe } from '@start9labs/shared'
|
|
||||||
import { TuiMapperPipe, TuiValueChanges } from '@taiga-ui/cdk'
|
|
||||||
import {
|
|
||||||
TuiAppearance,
|
|
||||||
TuiButton,
|
|
||||||
TuiError,
|
|
||||||
TuiExpand,
|
|
||||||
TuiHint,
|
|
||||||
TuiIcon,
|
|
||||||
TuiLink,
|
|
||||||
TuiNumberFormat,
|
|
||||||
} from '@taiga-ui/core'
|
|
||||||
import {
|
|
||||||
TuiChip,
|
|
||||||
TuiElasticContainer,
|
|
||||||
TuiFieldErrorPipe,
|
|
||||||
TuiFiles,
|
|
||||||
TuiSwitch,
|
|
||||||
TuiTooltip,
|
|
||||||
} from '@taiga-ui/kit'
|
|
||||||
import {
|
|
||||||
TuiInputDateModule,
|
|
||||||
TuiInputDateTimeModule,
|
|
||||||
TuiInputModule,
|
|
||||||
TuiInputNumberModule,
|
|
||||||
TuiInputTimeModule,
|
|
||||||
TuiMultiSelectModule,
|
|
||||||
TuiSelectModule,
|
|
||||||
TuiTextareaModule,
|
|
||||||
TuiTextfieldControllerModule,
|
|
||||||
} from '@taiga-ui/legacy'
|
|
||||||
import { ControlDirective } from './control.directive'
|
|
||||||
import { FilterHiddenPipe } from './filter-hidden.pipe'
|
|
||||||
import { FormArrayComponent } from './form-array/form-array.component'
|
|
||||||
import { FormColorComponent } from './form-color/form-color.component'
|
|
||||||
import { FormControlComponent } from './form-control/form-control.component'
|
|
||||||
import { FormDatetimeComponent } from './form-datetime/form-datetime.component'
|
|
||||||
import { FormFileComponent } from './form-file/form-file.component'
|
|
||||||
import { FormGroupComponent } from './form-group/form-group.component'
|
|
||||||
import { FormMultiselectComponent } from './form-multiselect/form-multiselect.component'
|
|
||||||
import { FormNumberComponent } from './form-number/form-number.component'
|
|
||||||
import { FormObjectComponent } from './form-object/form-object.component'
|
|
||||||
import { FormSelectComponent } from './form-select/form-select.component'
|
|
||||||
import { FormTextComponent } from './form-text/form-text.component'
|
|
||||||
import { FormTextareaComponent } from './form-textarea/form-textarea.component'
|
|
||||||
import { FormToggleComponent } from './form-toggle/form-toggle.component'
|
|
||||||
import { FormUnionComponent } from './form-union/form-union.component'
|
|
||||||
import { HintPipe } from './hint.pipe'
|
|
||||||
import { MustachePipe } from './mustache.pipe'
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
FormsModule,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
TuiInputModule,
|
|
||||||
TuiInputNumberModule,
|
|
||||||
...TuiFiles,
|
|
||||||
TuiTextareaModule,
|
|
||||||
TuiSelectModule,
|
|
||||||
TuiMultiSelectModule,
|
|
||||||
TuiSwitch,
|
|
||||||
TuiTooltip,
|
|
||||||
...TuiHint,
|
|
||||||
TuiChip,
|
|
||||||
TuiButton,
|
|
||||||
...TuiExpand,
|
|
||||||
TuiTextfieldControllerModule,
|
|
||||||
TuiLink,
|
|
||||||
TuiError,
|
|
||||||
TuiFieldErrorPipe,
|
|
||||||
TuiValueChanges,
|
|
||||||
TuiElasticContainer,
|
|
||||||
MaskitoDirective,
|
|
||||||
TuiInputDateModule,
|
|
||||||
TuiInputTimeModule,
|
|
||||||
TuiInputDateTimeModule,
|
|
||||||
TuiMapperPipe,
|
|
||||||
TuiAppearance,
|
|
||||||
TuiIcon,
|
|
||||||
TuiNumberFormat,
|
|
||||||
i18nPipe,
|
|
||||||
],
|
|
||||||
declarations: [
|
|
||||||
FormGroupComponent,
|
|
||||||
FormControlComponent,
|
|
||||||
FormColorComponent,
|
|
||||||
FormDatetimeComponent,
|
|
||||||
FormTextComponent,
|
|
||||||
FormToggleComponent,
|
|
||||||
FormTextareaComponent,
|
|
||||||
FormNumberComponent,
|
|
||||||
FormSelectComponent,
|
|
||||||
FormMultiselectComponent,
|
|
||||||
FormFileComponent,
|
|
||||||
FormUnionComponent,
|
|
||||||
FormObjectComponent,
|
|
||||||
FormArrayComponent,
|
|
||||||
MustachePipe,
|
|
||||||
HintPipe,
|
|
||||||
ControlDirective,
|
|
||||||
FilterHiddenPipe,
|
|
||||||
],
|
|
||||||
exports: [FormGroupComponent],
|
|
||||||
})
|
|
||||||
export class FormModule {}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core'
|
|
||||||
import { ControlDirective } from './control.directive'
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class InvalidService {
|
|
||||||
private readonly controls: ControlDirective[] = []
|
|
||||||
|
|
||||||
scrollIntoView() {
|
|
||||||
this.controls.find(({ invalid }) => invalid)?.scrollIntoView()
|
|
||||||
}
|
|
||||||
|
|
||||||
add(control: ControlDirective) {
|
|
||||||
this.controls.push(control)
|
|
||||||
}
|
|
||||||
|
|
||||||
remove(control: ControlDirective) {
|
|
||||||
this.controls.splice(this.controls.indexOf(control), 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,6 @@ import { KeyValue } from '@angular/common'
|
|||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'filterHidden',
|
name: 'filterHidden',
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class FilterHiddenPipe implements PipeTransform {
|
export class FilterHiddenPipe implements PipeTransform {
|
||||||
transform(value: KeyValue<string, IST.ValueSpec>[]) {
|
transform(value: KeyValue<string, IST.ValueSpec>[]) {
|
||||||
@@ -4,7 +4,6 @@ import { IST } from '@start9labs/start-sdk'
|
|||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'hint',
|
name: 'hint',
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class HintPipe implements PipeTransform {
|
export class HintPipe implements PipeTransform {
|
||||||
private readonly i18n = inject(i18nPipe)
|
private readonly i18n = inject(i18nPipe)
|
||||||
@@ -3,7 +3,6 @@ import Mustache from 'mustache'
|
|||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'mustache',
|
name: 'mustache',
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class MustachePipe implements PipeTransform {
|
export class MustachePipe implements PipeTransform {
|
||||||
transform(value: any, displayAs: string): string {
|
transform(value: any, displayAs: string): string {
|
||||||
@@ -22,12 +22,12 @@ import {
|
|||||||
ActionButton,
|
ActionButton,
|
||||||
FormComponent,
|
FormComponent,
|
||||||
} from 'src/app/routes/portal/components/form.component'
|
} from 'src/app/routes/portal/components/form.component'
|
||||||
|
import { InvalidService } from 'src/app/routes/portal/components/form/containers/control.directive'
|
||||||
import { TaskInfoComponent } from 'src/app/routes/portal/modals/config-dep.component'
|
import { TaskInfoComponent } from 'src/app/routes/portal/modals/config-dep.component'
|
||||||
import { ActionService } from 'src/app/services/action.service'
|
import { ActionService } from 'src/app/services/action.service'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
import { getAllPackages, getManifest } from 'src/app/utils/get-package-data'
|
import { getAllPackages, getManifest } from 'src/app/utils/get-package-data'
|
||||||
import { InvalidService } from '../../../components/form/invalid.service'
|
|
||||||
|
|
||||||
export type PackageActionData = {
|
export type PackageActionData = {
|
||||||
pkgInfo: {
|
pkgInfo: {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { TuiButton, TuiTitle } from '@taiga-ui/core'
|
|||||||
import { TuiHeader } from '@taiga-ui/layout'
|
import { TuiHeader } from '@taiga-ui/layout'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { combineLatest, first, switchMap } from 'rxjs'
|
import { combineLatest, first, switchMap } from 'rxjs'
|
||||||
import { FormModule } from 'src/app/routes/portal/components/form/form.module'
|
import { FormGroupComponent } from 'src/app/routes/portal/components/form/containers/group.component'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { FormService } from 'src/app/services/form.service'
|
import { FormService } from 'src/app/services/form.service'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
@@ -95,7 +95,7 @@ import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
|||||||
CommonModule,
|
CommonModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
FormModule,
|
FormGroupComponent,
|
||||||
TuiButton,
|
TuiButton,
|
||||||
TuiHeader,
|
TuiHeader,
|
||||||
TuiTitle,
|
TuiTitle,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { TuiButton, TuiTextfield, TuiTitle } from '@taiga-ui/core'
|
|||||||
import { TuiHeader } from '@taiga-ui/layout'
|
import { TuiHeader } from '@taiga-ui/layout'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { switchMap, tap } from 'rxjs'
|
import { switchMap, tap } from 'rxjs'
|
||||||
import { FormModule } from 'src/app/routes/portal/components/form/form.module'
|
import { FormGroupComponent } from 'src/app/routes/portal/components/form/containers/group.component'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { FormService } from 'src/app/services/form.service'
|
import { FormService } from 'src/app/services/form.service'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
@@ -122,7 +122,7 @@ import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
|||||||
CommonModule,
|
CommonModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
FormModule,
|
FormGroupComponent,
|
||||||
TuiButton,
|
TuiButton,
|
||||||
TuiTextfield,
|
TuiTextfield,
|
||||||
TuiHeader,
|
TuiHeader,
|
||||||
|
|||||||
Reference in New Issue
Block a user