@foxeltech/angular-ui 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,3 @@
1
- # @fxlt/common-ui
1
+ # @foxeltech/angular-ui
2
2
 
3
- Common UI & Service Library
3
+ Common UI & Service Library
@@ -310,9 +310,9 @@ class FxUtils {
310
310
  return 'Session timeout. Please login again.';
311
311
  }
312
312
  if (this.isStringEmpty(_.get(err, 'error.code'))) {
313
- return _.get(err, 'error.message', 'Unknown Error');
313
+ return _.get(err, 'error.errors', 'Unknown Error');
314
314
  }
315
- return `${_.get(err, 'error.code')}: ${_.get(err, 'error.message', 'Unknown Error')}`;
315
+ return `${_.get(err, 'error.code')}: ${_.get(err, 'error.errors', 'Unknown Error')}`;
316
316
  }
317
317
  static convertColorFromVariable(name, alpha = 1) {
318
318
  const value = getComputedStyle(document.body)
@@ -513,6 +513,9 @@ class BaseTableComponent extends BaseComponent {
513
513
  }
514
514
  async refresh() {
515
515
  this.page = 0;
516
+ if (this.paginator) {
517
+ this.paginator.pageIndex = 0;
518
+ }
516
519
  await this.fetch();
517
520
  }
518
521
  setDataSource(result) {
@@ -890,6 +893,122 @@ class BaseResolver {
890
893
  }
891
894
  }
892
895
 
896
+ class FxValidators {
897
+ /**
898
+ * Name fields: firstName, lastName, groupName, projectName, displayName
899
+ * Allows: letters (Unicode), spaces, hyphens, apostrophes, periods
900
+ */
901
+ static validateName(value) {
902
+ if (!value || value.trim() === '')
903
+ return null;
904
+ const regex = /^[\p{L}\s'\-.]{1,100}$/u;
905
+ return regex.test(value) ? null : { invalidName: 'Only letters, spaces, hyphens, apostrophes, and periods are allowed (max 100 characters)' };
906
+ }
907
+ /**
908
+ * Description fields
909
+ * Allows: letters, digits, spaces, basic punctuation
910
+ */
911
+ static validateDescription(value) {
912
+ if (!value || value.trim() === '')
913
+ return null;
914
+ const regex = /^[\p{L}\d\s.,;:!?'/()"&\-\n\r]{1,500}$/u;
915
+ return regex.test(value) ? null : { invalidDescription: 'Contains disallowed characters (max 500 characters)' };
916
+ }
917
+ /**
918
+ * Email fields
919
+ */
920
+ static validateEmail(value) {
921
+ if (!value || value.trim() === '')
922
+ return null;
923
+ const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
924
+ return regex.test(value) ? null : { invalidEmail: 'Please enter a valid email address' };
925
+ }
926
+ /**
927
+ * Phone fields
928
+ * Allows: digits, optional leading +, spaces, hyphens, parentheses (7-15 digits)
929
+ */
930
+ static validatePhone(value) {
931
+ if (!value || value.trim() === '')
932
+ return null;
933
+ const regex = /^\+?[\d\s\-()]{7,20}$/;
934
+ const digitCount = value.replace(/\D/g, '').length;
935
+ if (!regex.test(value) || digitCount < 7 || digitCount > 15) {
936
+ return { invalidPhone: 'Please enter a valid phone number' };
937
+ }
938
+ return null;
939
+ }
940
+ /**
941
+ * Code/identifier fields: server code, template code, templateId
942
+ * Allows: alphanumeric, hyphens, underscores
943
+ */
944
+ static validateCode(value) {
945
+ if (!value || value.trim() === '')
946
+ return null;
947
+ const regex = /^[a-zA-Z0-9_\-]{1,50}$/;
948
+ return regex.test(value) ? null : { invalidCode: 'Only letters, numbers, hyphens, and underscores are allowed (max 50 characters)' };
949
+ }
950
+ /**
951
+ * Domain fields
952
+ * Standard domain pattern with optional port
953
+ */
954
+ static validateDomain(value) {
955
+ if (!value || value.trim() === '')
956
+ return null;
957
+ const regex = /^([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}(:\d{1,5})?$/;
958
+ return regex.test(value) ? null : { invalidDomain: 'Please enter a valid domain (e.g., mail.example.com)' };
959
+ }
960
+ /**
961
+ * Subject fields (email subject)
962
+ * Allows: letters, digits, spaces, common punctuation, template placeholders {{}}
963
+ */
964
+ static validateSubject(value) {
965
+ if (!value || value.trim() === '')
966
+ return null;
967
+ const regex = /^[\p{L}\d\s.,;:!?'/()"&\-{}]{1,200}$/u;
968
+ return regex.test(value) ? null : { invalidSubject: 'Contains disallowed characters (max 200 characters)' };
969
+ }
970
+ /**
971
+ * Tag fields
972
+ * Allows: alphanumeric, hyphens, underscores, dots, spaces
973
+ */
974
+ static validateTag(value) {
975
+ if (!value || value.trim() === '')
976
+ return null;
977
+ const regex = /^[a-zA-Z0-9_\-.\s]{1,50}$/;
978
+ return regex.test(value) ? null : { invalidTag: 'Only letters, numbers, hyphens, underscores, dots, and spaces are allowed' };
979
+ }
980
+ /**
981
+ * URL fields: proxy, masterUrl
982
+ * Standard URL pattern
983
+ */
984
+ static validateUrl(value) {
985
+ if (!value || value.trim() === '')
986
+ return null;
987
+ const regex = /^https?:\/\/[a-zA-Z0-9\-._~:/?#[\]@!$&'()*+,;=%]{1,2000}$/;
988
+ return regex.test(value) ? null : { invalidUrl: 'Please enter a valid URL (http:// or https://)' };
989
+ }
990
+ /**
991
+ * Numeric string fields: timeout, memoryLimit, cpuCores
992
+ * Allows: digits, optional decimal point
993
+ */
994
+ static validateNumeric(value) {
995
+ if (!value || value.toString().trim() === '')
996
+ return null;
997
+ const regex = /^\d+(\.\d+)?$/;
998
+ return regex.test(value.toString()) ? null : { invalidNumeric: 'Please enter a valid number' };
999
+ }
1000
+ /**
1001
+ * Version fields (semver-like)
1002
+ * Allows: digits, dots, hyphens, plus, letters
1003
+ */
1004
+ static validateVersion(value) {
1005
+ if (!value || value.trim() === '')
1006
+ return null;
1007
+ const regex = /^[a-zA-Z0-9.\-+]{1,50}$/;
1008
+ return regex.test(value) ? null : { invalidVersion: 'Please enter a valid version (e.g., 1.0.0)' };
1009
+ }
1010
+ }
1011
+
893
1012
  function HttpLoaderFactory(http) {
894
1013
  return new TranslateHttpLoader(http, '/assets/i18n/', '.json');
895
1014
  }
@@ -1510,6 +1629,7 @@ class ChartComponent {
1510
1629
  intersectionObserver;
1511
1630
  canInit = false;
1512
1631
  isMobile = false;
1632
+ containerWidth = 0;
1513
1633
  constructor(ref, cdr) {
1514
1634
  this.ref = ref;
1515
1635
  this.cdr = cdr;
@@ -1563,6 +1683,7 @@ class ChartComponent {
1563
1683
  updateResponsiveState() {
1564
1684
  const { width } = this.ref.nativeElement.getBoundingClientRect();
1565
1685
  if (width > 0) {
1686
+ this.containerWidth = width;
1566
1687
  this.isMobile = width < this.mobileWidth;
1567
1688
  }
1568
1689
  }
@@ -1574,7 +1695,7 @@ class ChartComponent {
1574
1695
  requestAnimationFrame(() => this.forceMediaRecalc(retry + 1));
1575
1696
  return;
1576
1697
  }
1577
- this.chartInstance.setOption(this.chartOptions, true);
1698
+ this.rebuildChart();
1578
1699
  this.chartInstance.resize();
1579
1700
  }
1580
1701
  rebuildChart() {
@@ -1646,20 +1767,20 @@ class ChartComponent {
1646
1767
  }),
1647
1768
  pie: (data, colors) => {
1648
1769
  const total = data.reduce((sum, d) => sum + d.value, 0);
1649
- const isMobile = this.isMobile;
1770
+ const layout = this.calcPieLayout(data);
1650
1771
  return {
1651
1772
  color: colors,
1652
1773
  tooltip: { trigger: 'item' },
1653
1774
  legend: {
1654
- orient: isMobile ? 'horizontal' : 'vertical',
1775
+ type: 'scroll',
1776
+ orient: layout.legendOrient,
1655
1777
  icon: 'circle',
1656
1778
  itemGap: 12,
1657
- width: 160,
1658
- // 🔑 layout switch
1659
- right: isMobile ? undefined : this.pieLegendAlign,
1660
- top: isMobile ? undefined : this.pieLegendPosition,
1661
- left: isMobile ? 'center' : undefined,
1662
- bottom: isMobile ? 0 : undefined,
1779
+ right: layout.legendRight,
1780
+ top: layout.legendTop,
1781
+ left: layout.legendLeft,
1782
+ bottom: layout.legendBottom,
1783
+ width: layout.legendWidth,
1663
1784
  formatter: (name) => {
1664
1785
  const item = data.find((d) => d.name === name);
1665
1786
  return item ? `${name}: ${item.value}` : name;
@@ -1676,7 +1797,7 @@ class ChartComponent {
1676
1797
  {
1677
1798
  type: 'pie',
1678
1799
  radius: ['40%', '70%'],
1679
- center: isMobile ? ['50%', '40%'] : ['35%', '50%'],
1800
+ center: layout.pieCenter,
1680
1801
  data,
1681
1802
  label: {
1682
1803
  show: this.showPieLabel,
@@ -1697,8 +1818,8 @@ class ChartComponent {
1697
1818
  ],
1698
1819
  graphic: {
1699
1820
  type: 'group',
1700
- left: isMobile ? '50%' : '35%',
1701
- top: isMobile ? '40%' : '50%',
1821
+ left: layout.graphicLeft,
1822
+ top: layout.graphicTop,
1702
1823
  bounding: 'raw',
1703
1824
  children: [
1704
1825
  {
@@ -1740,6 +1861,46 @@ class ChartComponent {
1740
1861
  ],
1741
1862
  }),
1742
1863
  };
1864
+ /* ================= PIE LAYOUT ================= */
1865
+ calcPieLayout(data) {
1866
+ const cw = this.containerWidth || 400;
1867
+ if (this.isMobile) {
1868
+ return {
1869
+ legendOrient: 'horizontal',
1870
+ legendLeft: 'center',
1871
+ legendRight: undefined,
1872
+ legendTop: undefined,
1873
+ legendBottom: 0,
1874
+ legendWidth: undefined,
1875
+ pieCenter: ['50%', '40%'],
1876
+ graphicLeft: '50%',
1877
+ graphicTop: '40%',
1878
+ };
1879
+ }
1880
+ // Estimate legend pixel width from longest formatted label
1881
+ const CHAR_WIDTH = 7.5; // approx px per char at fontSize 13
1882
+ const ICON_GAP = 30; // legend icon + padding
1883
+ const longestLabel = data.reduce((max, d) => {
1884
+ return Math.max(max, `${d.name}: ${d.value}`.length);
1885
+ }, 0);
1886
+ const rawLegendPx = longestLabel * CHAR_WIDTH + ICON_GAP;
1887
+ // Clamp: min 100px, max 45% of container
1888
+ const legendPx = Math.max(100, Math.min(rawLegendPx, cw * 0.45));
1889
+ const legendPct = (legendPx / cw) * 100;
1890
+ // Pie center: middle of the remaining space after legend
1891
+ const pieCenterX = Math.round((100 - legendPct) / 2);
1892
+ return {
1893
+ legendOrient: 'vertical',
1894
+ legendLeft: undefined,
1895
+ legendRight: '2%',
1896
+ legendTop: 'middle',
1897
+ legendBottom: undefined,
1898
+ legendWidth: Math.round(legendPx),
1899
+ pieCenter: [`${pieCenterX}%`, '50%'],
1900
+ graphicLeft: `${pieCenterX}%`,
1901
+ graphicTop: '50%',
1902
+ };
1903
+ }
1743
1904
  /* ================= BUILD ================= */
1744
1905
  detectType() {
1745
1906
  if (this.type !== 'auto')
@@ -3449,5 +3610,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImpor
3449
3610
  * Generated bundle index. Do not edit.
3450
3611
  */
3451
3612
 
3452
- export { AuthInterceptor, AuthStateService, BaseComponent, BaseDialogComponent, BaseResolver, BaseTableComponent, BreadcrumbService, ButtonComponent, ChartComponent, CheckboxComponent, CircleProgressBar, ConfirmationDialogComponent, DatetimePicker, DndUploadComponent, DrawerComponent, FlowConnection, FxLoadingService, FxStorageService, FxToastrService, FxUtils, HasPermissionDirective, HeroIconComponent, HttpLoaderFactory, HttpWrapper, InputComponent, KanbanBoardComponent, LoadingPanel, MY_MOMENT_FORMATS, NotificationService, PermissionGuard, PermissionService, QuillStyleLoaderService, RadioButtonComponent, RadioButtonToggleComponent, RichTextAreaComponent, SearchBarComponent, SelectComponent, SkeletonTableLoadingComponent, SliderComponent, SwitchComponent, TabComponent, TabGroupComponent, TableCell, TagComponent, ThemeService, ToastComponent, ToastContainerComponent, TranslationModule, TranslationService, TreeDiagram, TrimOnBlurDirective, UiModule };
3613
+ export { AuthInterceptor, AuthStateService, BaseComponent, BaseDialogComponent, BaseResolver, BaseTableComponent, BreadcrumbService, ButtonComponent, ChartComponent, CheckboxComponent, CircleProgressBar, ConfirmationDialogComponent, DatetimePicker, DndUploadComponent, DrawerComponent, FlowConnection, FxLoadingService, FxStorageService, FxToastrService, FxUtils, FxValidators, HasPermissionDirective, HeroIconComponent, HttpLoaderFactory, HttpWrapper, InputComponent, KanbanBoardComponent, LoadingPanel, MY_MOMENT_FORMATS, NotificationService, PermissionGuard, PermissionService, QuillStyleLoaderService, RadioButtonComponent, RadioButtonToggleComponent, RichTextAreaComponent, SearchBarComponent, SelectComponent, SkeletonTableLoadingComponent, SliderComponent, SwitchComponent, TabComponent, TabGroupComponent, TableCell, TagComponent, ThemeService, ToastComponent, ToastContainerComponent, TranslationModule, TranslationService, TreeDiagram, TrimOnBlurDirective, UiModule };
3453
3614
  //# sourceMappingURL=foxeltech-angular-ui.mjs.map