@foxeltech/angular-ui 0.0.4 → 0.0.6

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)
@@ -363,10 +363,6 @@ class FxUtils {
363
363
  const isEmail = emailRegex.test(value);
364
364
  return isEmail;
365
365
  }
366
- static isStrongPassword(password) {
367
- const regex = /^(?=.{6,}$)(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^A-Za-z0-9]).*$/;
368
- return regex.test(password);
369
- }
370
366
  static formatSizeUnits(bytes) {
371
367
  if (bytes >= 1073741824) {
372
368
  return (bytes / 1073741824).toFixed(2) + ' GB';
@@ -513,6 +509,9 @@ class BaseTableComponent extends BaseComponent {
513
509
  }
514
510
  async refresh() {
515
511
  this.page = 0;
512
+ if (this.paginator) {
513
+ this.paginator.pageIndex = 0;
514
+ }
516
515
  await this.fetch();
517
516
  }
518
517
  setDataSource(result) {
@@ -890,6 +889,122 @@ class BaseResolver {
890
889
  }
891
890
  }
892
891
 
892
+ class FxValidators {
893
+ /**
894
+ * Name fields: firstName, lastName, groupName, projectName, displayName
895
+ * Allows: letters (Unicode), spaces, hyphens, apostrophes, periods
896
+ */
897
+ static validateName(value) {
898
+ if (!value || value.trim() === '')
899
+ return null;
900
+ const regex = /^[\p{L}\s'\-.]{1,100}$/u;
901
+ return regex.test(value) ? null : { invalidName: 'Only letters, spaces, hyphens, apostrophes, and periods are allowed (max 100 characters)' };
902
+ }
903
+ /**
904
+ * Description fields
905
+ * Allows: letters, digits, spaces, basic punctuation
906
+ */
907
+ static validateDescription(value) {
908
+ if (!value || value.trim() === '')
909
+ return null;
910
+ const regex = /^[\p{L}\d\s.,;:!?'/()"&\-\n\r]{1,500}$/u;
911
+ return regex.test(value) ? null : { invalidDescription: 'Contains disallowed characters (max 500 characters)' };
912
+ }
913
+ /**
914
+ * Email fields
915
+ */
916
+ static validateEmail(value) {
917
+ if (!value || value.trim() === '')
918
+ return null;
919
+ const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
920
+ return regex.test(value) ? null : { invalidEmail: 'Please enter a valid email address' };
921
+ }
922
+ /**
923
+ * Phone fields
924
+ * Allows: digits, optional leading +, spaces, hyphens, parentheses (7-15 digits)
925
+ */
926
+ static validatePhone(value) {
927
+ if (!value || value.trim() === '')
928
+ return null;
929
+ const regex = /^\+?[\d\s\-()]{7,20}$/;
930
+ const digitCount = value.replace(/\D/g, '').length;
931
+ if (!regex.test(value) || digitCount < 7 || digitCount > 15) {
932
+ return { invalidPhone: 'Please enter a valid phone number' };
933
+ }
934
+ return null;
935
+ }
936
+ /**
937
+ * Code/identifier fields: server code, template code, templateId
938
+ * Allows: alphanumeric, hyphens, underscores
939
+ */
940
+ static validateCode(value) {
941
+ if (!value || value.trim() === '')
942
+ return null;
943
+ const regex = /^[a-zA-Z0-9_\-]{1,50}$/;
944
+ return regex.test(value) ? null : { invalidCode: 'Only letters, numbers, hyphens, and underscores are allowed (max 50 characters)' };
945
+ }
946
+ /**
947
+ * Domain fields
948
+ * Standard domain pattern with optional port
949
+ */
950
+ static validateDomain(value) {
951
+ if (!value || value.trim() === '')
952
+ return null;
953
+ const regex = /^([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}(:\d{1,5})?$/;
954
+ return regex.test(value) ? null : { invalidDomain: 'Please enter a valid domain (e.g., mail.example.com)' };
955
+ }
956
+ /**
957
+ * Subject fields (email subject)
958
+ * Allows: letters, digits, spaces, common punctuation, template placeholders {{}}
959
+ */
960
+ static validateSubject(value) {
961
+ if (!value || value.trim() === '')
962
+ return null;
963
+ const regex = /^[\p{L}\d\s.,;:!?'/()"&\-{}]{1,200}$/u;
964
+ return regex.test(value) ? null : { invalidSubject: 'Contains disallowed characters (max 200 characters)' };
965
+ }
966
+ /**
967
+ * Tag fields
968
+ * Allows: alphanumeric, hyphens, underscores, dots, spaces
969
+ */
970
+ static validateTag(value) {
971
+ if (!value || value.trim() === '')
972
+ return null;
973
+ const regex = /^[a-zA-Z0-9_\-.\s]{1,50}$/;
974
+ return regex.test(value) ? null : { invalidTag: 'Only letters, numbers, hyphens, underscores, dots, and spaces are allowed' };
975
+ }
976
+ /**
977
+ * URL fields: proxy, masterUrl
978
+ * Standard URL pattern
979
+ */
980
+ static validateUrl(value) {
981
+ if (!value || value.trim() === '')
982
+ return null;
983
+ const regex = /^https?:\/\/[a-zA-Z0-9\-._~:/?#[\]@!$&'()*+,;=%]{1,2000}$/;
984
+ return regex.test(value) ? null : { invalidUrl: 'Please enter a valid URL (http:// or https://)' };
985
+ }
986
+ /**
987
+ * Numeric string fields: timeout, memoryLimit, cpuCores
988
+ * Allows: digits, optional decimal point
989
+ */
990
+ static validateNumeric(value) {
991
+ if (!value || value.toString().trim() === '')
992
+ return null;
993
+ const regex = /^\d+(\.\d+)?$/;
994
+ return regex.test(value.toString()) ? null : { invalidNumeric: 'Please enter a valid number' };
995
+ }
996
+ /**
997
+ * Version fields (semver-like)
998
+ * Allows: digits, dots, hyphens, plus, letters
999
+ */
1000
+ static validateVersion(value) {
1001
+ if (!value || value.trim() === '')
1002
+ return null;
1003
+ const regex = /^[a-zA-Z0-9.\-+]{1,50}$/;
1004
+ return regex.test(value) ? null : { invalidVersion: 'Please enter a valid version (e.g., 1.0.0)' };
1005
+ }
1006
+ }
1007
+
893
1008
  function HttpLoaderFactory(http) {
894
1009
  return new TranslateHttpLoader(http, '/assets/i18n/', '.json');
895
1010
  }
@@ -1510,6 +1625,7 @@ class ChartComponent {
1510
1625
  intersectionObserver;
1511
1626
  canInit = false;
1512
1627
  isMobile = false;
1628
+ containerWidth = 0;
1513
1629
  constructor(ref, cdr) {
1514
1630
  this.ref = ref;
1515
1631
  this.cdr = cdr;
@@ -1563,6 +1679,7 @@ class ChartComponent {
1563
1679
  updateResponsiveState() {
1564
1680
  const { width } = this.ref.nativeElement.getBoundingClientRect();
1565
1681
  if (width > 0) {
1682
+ this.containerWidth = width;
1566
1683
  this.isMobile = width < this.mobileWidth;
1567
1684
  }
1568
1685
  }
@@ -1574,7 +1691,7 @@ class ChartComponent {
1574
1691
  requestAnimationFrame(() => this.forceMediaRecalc(retry + 1));
1575
1692
  return;
1576
1693
  }
1577
- this.chartInstance.setOption(this.chartOptions, true);
1694
+ this.rebuildChart();
1578
1695
  this.chartInstance.resize();
1579
1696
  }
1580
1697
  rebuildChart() {
@@ -1646,20 +1763,20 @@ class ChartComponent {
1646
1763
  }),
1647
1764
  pie: (data, colors) => {
1648
1765
  const total = data.reduce((sum, d) => sum + d.value, 0);
1649
- const isMobile = this.isMobile;
1766
+ const layout = this.calcPieLayout(data);
1650
1767
  return {
1651
1768
  color: colors,
1652
1769
  tooltip: { trigger: 'item' },
1653
1770
  legend: {
1654
- orient: isMobile ? 'horizontal' : 'vertical',
1771
+ type: 'scroll',
1772
+ orient: layout.legendOrient,
1655
1773
  icon: 'circle',
1656
1774
  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,
1775
+ right: layout.legendRight,
1776
+ top: layout.legendTop,
1777
+ left: layout.legendLeft,
1778
+ bottom: layout.legendBottom,
1779
+ width: layout.legendWidth,
1663
1780
  formatter: (name) => {
1664
1781
  const item = data.find((d) => d.name === name);
1665
1782
  return item ? `${name}: ${item.value}` : name;
@@ -1676,7 +1793,7 @@ class ChartComponent {
1676
1793
  {
1677
1794
  type: 'pie',
1678
1795
  radius: ['40%', '70%'],
1679
- center: isMobile ? ['50%', '40%'] : ['35%', '50%'],
1796
+ center: layout.pieCenter,
1680
1797
  data,
1681
1798
  label: {
1682
1799
  show: this.showPieLabel,
@@ -1697,8 +1814,8 @@ class ChartComponent {
1697
1814
  ],
1698
1815
  graphic: {
1699
1816
  type: 'group',
1700
- left: isMobile ? '50%' : '35%',
1701
- top: isMobile ? '40%' : '50%',
1817
+ left: layout.graphicLeft,
1818
+ top: layout.graphicTop,
1702
1819
  bounding: 'raw',
1703
1820
  children: [
1704
1821
  {
@@ -1740,6 +1857,46 @@ class ChartComponent {
1740
1857
  ],
1741
1858
  }),
1742
1859
  };
1860
+ /* ================= PIE LAYOUT ================= */
1861
+ calcPieLayout(data) {
1862
+ const cw = this.containerWidth || 400;
1863
+ if (this.isMobile) {
1864
+ return {
1865
+ legendOrient: 'horizontal',
1866
+ legendLeft: 'center',
1867
+ legendRight: undefined,
1868
+ legendTop: undefined,
1869
+ legendBottom: 0,
1870
+ legendWidth: undefined,
1871
+ pieCenter: ['50%', '40%'],
1872
+ graphicLeft: '50%',
1873
+ graphicTop: '40%',
1874
+ };
1875
+ }
1876
+ // Estimate legend pixel width from longest formatted label
1877
+ const CHAR_WIDTH = 7.5; // approx px per char at fontSize 13
1878
+ const ICON_GAP = 30; // legend icon + padding
1879
+ const longestLabel = data.reduce((max, d) => {
1880
+ return Math.max(max, `${d.name}: ${d.value}`.length);
1881
+ }, 0);
1882
+ const rawLegendPx = longestLabel * CHAR_WIDTH + ICON_GAP;
1883
+ // Clamp: min 100px, max 45% of container
1884
+ const legendPx = Math.max(100, Math.min(rawLegendPx, cw * 0.45));
1885
+ const legendPct = (legendPx / cw) * 100;
1886
+ // Pie center: middle of the remaining space after legend
1887
+ const pieCenterX = Math.round((100 - legendPct) / 2);
1888
+ return {
1889
+ legendOrient: 'vertical',
1890
+ legendLeft: undefined,
1891
+ legendRight: '2%',
1892
+ legendTop: 'middle',
1893
+ legendBottom: undefined,
1894
+ legendWidth: Math.round(legendPx),
1895
+ pieCenter: [`${pieCenterX}%`, '50%'],
1896
+ graphicLeft: `${pieCenterX}%`,
1897
+ graphicTop: '50%',
1898
+ };
1899
+ }
1743
1900
  /* ================= BUILD ================= */
1744
1901
  detectType() {
1745
1902
  if (this.type !== 'auto')
@@ -3449,5 +3606,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImpor
3449
3606
  * Generated bundle index. Do not edit.
3450
3607
  */
3451
3608
 
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 };
3609
+ 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
3610
  //# sourceMappingURL=foxeltech-angular-ui.mjs.map