@tacdaed/fragments 1.0.0-beta.0 → 1.0.0-beta.2

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.
Files changed (248) hide show
  1. package/README.md +2 -18
  2. package/ng-package.json +25 -0
  3. package/package.json +22 -29
  4. package/src/lib/components/accordion/accordion.component.html +103 -0
  5. package/src/lib/components/accordion/accordion.component.scss +382 -0
  6. package/src/lib/components/accordion/accordion.component.spec.ts +147 -0
  7. package/src/lib/components/accordion/accordion.component.ts +211 -0
  8. package/src/lib/components/accordion/accordion.type.ts +82 -0
  9. package/src/lib/components/breadcrumb/breadcrumb.component.html +43 -0
  10. package/src/lib/components/breadcrumb/breadcrumb.component.scss +112 -0
  11. package/src/lib/components/breadcrumb/breadcrumb.component.spec.ts +33 -0
  12. package/src/lib/components/breadcrumb/breadcrumb.component.ts +103 -0
  13. package/src/lib/components/breadcrumb/breadcrumb.interface.ts +7 -0
  14. package/src/lib/components/button/button.component.html +57 -0
  15. package/src/lib/components/button/button.component.scss +445 -0
  16. package/src/lib/components/button/button.component.spec.ts +99 -0
  17. package/src/lib/components/button/button.component.ts +143 -0
  18. package/src/lib/components/button/button.type.ts +7 -0
  19. package/src/lib/components/card/card.component.html +44 -0
  20. package/src/lib/components/card/card.component.scss +114 -0
  21. package/src/lib/components/card/card.component.spec.ts +65 -0
  22. package/src/lib/components/card/card.component.ts +21 -0
  23. package/src/lib/components/card/card.type.ts +3 -0
  24. package/src/lib/components/code-block/code-block.component.html +55 -0
  25. package/src/lib/components/code-block/code-block.component.scss +122 -0
  26. package/src/lib/components/code-block/code-block.component.spec.ts +81 -0
  27. package/src/lib/components/code-block/code-block.component.ts +302 -0
  28. package/src/lib/components/code-block/code-block.interface.ts +28 -0
  29. package/src/lib/components/code-block/code-block.type.ts +73 -0
  30. package/src/lib/components/decorative/sparkle-field/sparkle-field.component.html +14 -0
  31. package/src/lib/components/decorative/sparkle-field/sparkle-field.component.scss +20 -0
  32. package/src/lib/components/decorative/sparkle-field/sparkle-field.component.spec.ts +38 -0
  33. package/src/lib/components/decorative/sparkle-field/sparkle-field.component.ts +181 -0
  34. package/src/lib/components/input/input-base.ts +187 -0
  35. package/src/lib/components/input/input-calendar/input-calendar.component.html +76 -0
  36. package/src/lib/components/input/input-calendar/input-calendar.component.scss +179 -0
  37. package/src/lib/components/input/input-calendar/input-calendar.component.spec.ts +44 -0
  38. package/src/lib/components/input/input-calendar/input-calendar.component.ts +299 -0
  39. package/src/lib/components/input/input-checkbox/input-checkbox.component.html +37 -0
  40. package/src/lib/components/input/input-checkbox/input-checkbox.component.scss +128 -0
  41. package/src/lib/components/input/input-checkbox/input-checkbox.component.spec.ts +43 -0
  42. package/src/lib/components/input/input-checkbox/input-checkbox.component.ts +112 -0
  43. package/src/lib/components/input/input-checkbox-group/input-checkbox-group.component.html +43 -0
  44. package/src/lib/components/input/input-checkbox-group/input-checkbox-group.component.scss +140 -0
  45. package/src/lib/components/input/input-checkbox-group/input-checkbox-group.component.spec.ts +62 -0
  46. package/src/lib/components/input/input-checkbox-group/input-checkbox-group.component.ts +136 -0
  47. package/src/lib/components/input/input-clock-picker/input-clock-picker.component.html +81 -0
  48. package/src/lib/components/input/input-clock-picker/input-clock-picker.component.scss +228 -0
  49. package/src/lib/components/input/input-clock-picker/input-clock-picker.component.spec.ts +62 -0
  50. package/src/lib/components/input/input-clock-picker/input-clock-picker.component.ts +178 -0
  51. package/src/lib/components/input/input-consts.ts +132 -0
  52. package/src/lib/components/input/input-date/input-date-validators.ts +41 -0
  53. package/src/lib/components/input/input-date/input-date.component.html +41 -0
  54. package/src/lib/components/input/input-date/input-date.component.scss +95 -0
  55. package/src/lib/components/input/input-date/input-date.component.spec.ts +43 -0
  56. package/src/lib/components/input/input-date/input-date.component.ts +359 -0
  57. package/src/lib/components/input/input-date-time/input-date-time.component.html +70 -0
  58. package/src/lib/components/input/input-date-time/input-date-time.component.scss +133 -0
  59. package/src/lib/components/input/input-date-time/input-date-time.component.spec.ts +36 -0
  60. package/src/lib/components/input/input-date-time/input-date-time.component.ts +387 -0
  61. package/src/lib/components/input/input-file-upload/input-file-upload.component.html +89 -0
  62. package/src/lib/components/input/input-file-upload/input-file-upload.component.scss +171 -0
  63. package/src/lib/components/input/input-file-upload/input-file-upload.component.spec.ts +43 -0
  64. package/src/lib/components/input/input-file-upload/input-file-upload.component.ts +351 -0
  65. package/src/lib/components/input/input-interface.ts +8 -0
  66. package/src/lib/components/input/input-number/input-number-validators.ts +0 -0
  67. package/src/lib/components/input/input-number/input-number.component.html +51 -0
  68. package/src/lib/components/input/input-number/input-number.component.scss +140 -0
  69. package/src/lib/components/input/input-number/input-number.component.spec.ts +44 -0
  70. package/src/lib/components/input/input-number/input-number.component.ts +343 -0
  71. package/src/lib/components/input/input-radio-group/input-radio-group.component.html +44 -0
  72. package/src/lib/components/input/input-radio-group/input-radio-group.component.scss +139 -0
  73. package/src/lib/components/input/input-radio-group/input-radio-group.component.spec.ts +58 -0
  74. package/src/lib/components/input/input-radio-group/input-radio-group.component.ts +132 -0
  75. package/src/lib/components/input/input-slider/input-slider.component.html +111 -0
  76. package/src/lib/components/input/input-slider/input-slider.component.scss +203 -0
  77. package/src/lib/components/input/input-slider/input-slider.component.spec.ts +46 -0
  78. package/src/lib/components/input/input-slider/input-slider.component.ts +410 -0
  79. package/src/lib/components/input/input-text/input-text-validators.ts +67 -0
  80. package/src/lib/components/input/input-text/input-text.component.html +71 -0
  81. package/src/lib/components/input/input-text/input-text.component.scss +118 -0
  82. package/src/lib/components/input/input-text/input-text.component.spec.ts +55 -0
  83. package/src/lib/components/input/input-text/input-text.component.ts +215 -0
  84. package/src/lib/components/input/input-time/input-time-validators.ts +42 -0
  85. package/src/lib/components/input/input-time/input-time.component.html +92 -0
  86. package/src/lib/components/input/input-time/input-time.component.scss +191 -0
  87. package/src/lib/components/input/input-time/input-time.component.spec.ts +39 -0
  88. package/src/lib/components/input/input-time/input-time.component.ts +691 -0
  89. package/src/lib/components/input/input-toggle-switch/input-toggle-switch.component.html +36 -0
  90. package/src/lib/components/input/input-toggle-switch/input-toggle-switch.component.scss +121 -0
  91. package/src/lib/components/input/input-toggle-switch/input-toggle-switch.component.spec.ts +54 -0
  92. package/src/lib/components/input/input-toggle-switch/input-toggle-switch.component.ts +117 -0
  93. package/src/lib/components/input/input-type.ts +18 -0
  94. package/src/lib/components/input/input-validation/input-validation.component.html +19 -0
  95. package/src/lib/components/input/input-validation/input-validation.component.scss +39 -0
  96. package/src/lib/components/input/input-validation/input-validation.component.spec.ts +45 -0
  97. package/src/lib/components/input/input-validation/input-validation.component.ts +13 -0
  98. package/src/lib/components/input/input.pipe.ts +14 -0
  99. package/src/lib/components/layout/container/container.component.html +1 -0
  100. package/src/lib/components/layout/container/container.component.scss +33 -0
  101. package/src/lib/components/layout/container/container.component.ts +32 -0
  102. package/src/lib/components/layout/container/container.type.ts +1 -0
  103. package/src/lib/components/layout/divider/divider.component.html +1 -0
  104. package/src/lib/components/layout/divider/divider.component.scss +60 -0
  105. package/src/lib/components/layout/divider/divider.component.ts +38 -0
  106. package/src/lib/components/layout/divider/divider.type.ts +2 -0
  107. package/src/lib/components/layout/section/section.component.html +21 -0
  108. package/src/lib/components/layout/section/section.component.scss +43 -0
  109. package/src/lib/components/layout/section/section.component.ts +33 -0
  110. package/src/lib/components/layout/section/section.type.ts +2 -0
  111. package/src/lib/components/layout/separator/separator.component.html +9 -0
  112. package/src/lib/components/layout/separator/separator.component.scss +52 -0
  113. package/src/lib/components/layout/separator/separator.component.ts +25 -0
  114. package/src/lib/components/layout/separator/separator.type.ts +1 -0
  115. package/src/lib/components/loader/content-blur/content-blur.component.html +13 -0
  116. package/src/lib/components/loader/content-blur/content-blur.component.scss +43 -0
  117. package/src/lib/components/loader/content-blur/content-blur.component.spec.ts +42 -0
  118. package/src/lib/components/loader/content-blur/content-blur.component.ts +34 -0
  119. package/src/lib/components/loader/loader.type.ts +2 -0
  120. package/src/lib/components/loader/progress-bar/progress-bar.component.html +26 -0
  121. package/src/lib/components/loader/progress-bar/progress-bar.component.scss +151 -0
  122. package/src/lib/components/loader/progress-bar/progress-bar.component.spec.ts +47 -0
  123. package/src/lib/components/loader/progress-bar/progress-bar.component.ts +28 -0
  124. package/src/lib/components/loader/progress-bar/progress-bar.type.ts +8 -0
  125. package/src/lib/components/loader/pulse-loader/pulse-loader.component.html +12 -0
  126. package/src/lib/components/loader/pulse-loader/pulse-loader.component.scss +202 -0
  127. package/src/lib/components/loader/pulse-loader/pulse-loader.component.spec.ts +55 -0
  128. package/src/lib/components/loader/pulse-loader/pulse-loader.component.ts +73 -0
  129. package/src/lib/components/loader/pulse-loader/pulse-loader.type.ts +6 -0
  130. package/src/lib/components/loader/skeleton-loader/skeleton-loader.component.html +13 -0
  131. package/src/lib/components/loader/skeleton-loader/skeleton-loader.component.scss +113 -0
  132. package/src/lib/components/loader/skeleton-loader/skeleton-loader.component.spec.ts +37 -0
  133. package/src/lib/components/loader/skeleton-loader/skeleton-loader.component.ts +51 -0
  134. package/src/lib/components/loader/skeleton-loader/skeleton-loader.type.ts +6 -0
  135. package/src/lib/components/loader/spinner/spinner.component.html +20 -0
  136. package/src/lib/components/loader/spinner/spinner.component.scss +137 -0
  137. package/src/lib/components/loader/spinner/spinner.component.spec.ts +43 -0
  138. package/src/lib/components/loader/spinner/spinner.component.ts +32 -0
  139. package/src/lib/components/loader/spinner/spinner.type.ts +6 -0
  140. package/src/lib/components/modal/modal.component.html +47 -0
  141. package/src/lib/components/modal/modal.component.scss +139 -0
  142. package/src/lib/components/modal/modal.component.spec.ts +60 -0
  143. package/src/lib/components/modal/modal.component.ts +83 -0
  144. package/src/lib/components/modal/modal.type.ts +9 -0
  145. package/src/lib/components/morph/blob-moph/blob-moprh.component.spec.ts +79 -0
  146. package/src/lib/components/morph/blob-moph/blob-moprh.component.ts +96 -0
  147. package/src/lib/components/morph/blob-moph/blob-morph.component.html +34 -0
  148. package/src/lib/components/morph/blob-moph/blob-morph.component.scss +7 -0
  149. package/src/lib/components/morph/morph.abstract.ts +13 -0
  150. package/src/lib/components/pagination/pagination.interface.ts +4 -0
  151. package/src/lib/components/pagination/small-pagination/small-pagination.component.html +61 -0
  152. package/src/lib/components/pagination/small-pagination/small-pagination.component.scss +187 -0
  153. package/src/lib/components/pagination/small-pagination/small-pagination.component.spec.ts +88 -0
  154. package/src/lib/components/pagination/small-pagination/small-pagination.component.ts +177 -0
  155. package/src/lib/components/selection-lists/multi-select/multi-select.component.html +170 -0
  156. package/src/lib/components/selection-lists/multi-select/multi-select.component.scss +312 -0
  157. package/src/lib/components/selection-lists/multi-select/multi-select.component.spec.ts +61 -0
  158. package/src/lib/components/selection-lists/multi-select/multi-select.component.ts +372 -0
  159. package/src/lib/components/selection-lists/selection-list/selection-list.component.html +125 -0
  160. package/src/lib/components/selection-lists/selection-list/selection-list.component.scss +267 -0
  161. package/src/lib/components/selection-lists/selection-list/selection-list.component.spec.ts +66 -0
  162. package/src/lib/components/selection-lists/selection-list/selection-list.component.ts +315 -0
  163. package/src/lib/components/selection-lists/selection-lists-base.ts +35 -0
  164. package/src/lib/components/selection-lists/selection-lists-const.ts +17 -0
  165. package/src/lib/components/selection-lists/selection-lists-interface.ts +7 -0
  166. package/src/lib/components/selection-lists/selection-lists.type.ts +1 -0
  167. package/src/lib/components/side-nav/side-nav.component.html +101 -0
  168. package/src/lib/components/side-nav/side-nav.component.scss +295 -0
  169. package/src/lib/components/side-nav/side-nav.component.spec.ts +0 -0
  170. package/src/lib/components/side-nav/side-nav.component.ts +18 -0
  171. package/src/lib/components/side-nav/side-nav.type.ts +28 -0
  172. package/src/lib/components/snackbar/snackbar.component.html +33 -0
  173. package/src/lib/components/snackbar/snackbar.component.scss +195 -0
  174. package/src/lib/components/snackbar/snackbar.component.ts +112 -0
  175. package/src/lib/components/snackbar/snackbar.type.ts +27 -0
  176. package/src/lib/components/status/chip/chip.component.html +51 -0
  177. package/src/lib/components/status/chip/chip.component.scss +149 -0
  178. package/src/lib/components/status/chip/chip.component.spec.ts +62 -0
  179. package/src/lib/components/status/chip/chip.component.ts +83 -0
  180. package/src/lib/components/status/chip/chip.type.ts +42 -0
  181. package/src/lib/components/status/directives/badge/badge.directive.spec.ts +60 -0
  182. package/src/lib/components/status/directives/badge/badge.directive.ts +190 -0
  183. package/src/lib/components/status/directives/badge/badge.interface.ts +19 -0
  184. package/src/lib/components/status/pill/pill.component.html +40 -0
  185. package/src/lib/components/status/pill/pill.component.scss +113 -0
  186. package/src/lib/components/status/pill/pill.component.spec.ts +47 -0
  187. package/src/lib/components/status/pill/pill.component.ts +83 -0
  188. package/src/lib/components/status/pill/pill.type.ts +42 -0
  189. package/src/lib/components/status/status.interface.ts +57 -0
  190. package/src/lib/components/status/status.type.ts +62 -0
  191. package/src/lib/components/status/tag/tag.component.html +39 -0
  192. package/src/lib/components/status/tag/tag.component.scss +140 -0
  193. package/src/lib/components/status/tag/tag.component.spec.ts +47 -0
  194. package/src/lib/components/status/tag/tag.component.ts +83 -0
  195. package/src/lib/components/status/tag/tag.type.ts +42 -0
  196. package/src/lib/components/stepper/stepper.component.html +83 -0
  197. package/src/lib/components/stepper/stepper.component.scss +196 -0
  198. package/src/lib/components/stepper/stepper.component.ts +482 -0
  199. package/src/lib/components/stepper/stepper.type.ts +60 -0
  200. package/src/lib/components/table/table.component.html +438 -0
  201. package/src/lib/components/table/table.component.scss +259 -0
  202. package/src/lib/components/table/table.component.spec.ts +117 -0
  203. package/src/lib/components/table/table.component.ts +215 -0
  204. package/src/lib/components/table/table.enum.ts +4 -0
  205. package/src/lib/components/table/table.function.ts +47 -0
  206. package/src/lib/components/table/table.interface.ts +143 -0
  207. package/src/lib/components/table/table.pipe.ts +62 -0
  208. package/src/lib/components/table/table.type.ts +15 -0
  209. package/src/lib/components/tabs/tabs.component.html +88 -0
  210. package/src/lib/components/tabs/tabs.component.scss +305 -0
  211. package/src/lib/components/tabs/tabs.component.spec.ts +94 -0
  212. package/src/lib/components/tabs/tabs.component.ts +282 -0
  213. package/src/lib/components/tabs/tabs.type.ts +81 -0
  214. package/src/lib/components/title-bar/title-bar.component.html +21 -0
  215. package/src/lib/components/title-bar/title-bar.component.scss +139 -0
  216. package/src/lib/components/title-bar/title-bar.component.spec.ts +44 -0
  217. package/src/lib/components/title-bar/title-bar.component.ts +13 -0
  218. package/src/lib/components/toast/toast.component.html +36 -0
  219. package/src/lib/components/toast/toast.component.scss +241 -0
  220. package/src/lib/components/toast/toast.component.ts +165 -0
  221. package/src/lib/components/toast/toast.type.ts +37 -0
  222. package/src/lib/components/toast-stack/toast-stack.component.html +30 -0
  223. package/src/lib/components/toast-stack/toast-stack.component.scss +35 -0
  224. package/src/lib/components/toast-stack/toast-stack.component.ts +51 -0
  225. package/src/lib/consts/country-prefix.ts +244 -0
  226. package/src/lib/directives/tooltip/popover.directive.ts +274 -0
  227. package/src/lib/directives/tooltip/tooltip.directive.spec.ts +86 -0
  228. package/src/lib/directives/tooltip/tooltip.directive.ts +234 -0
  229. package/src/lib/directives/tooltip/tooltip.interface.ts +29 -0
  230. package/src/lib/directives/tooltip/tooltip.type.ts +9 -0
  231. package/src/lib/interfaces/common.interfaces.ts +4 -0
  232. package/src/lib/pipes/chunk.pipe.ts +16 -0
  233. package/src/lib/pipes/safe-html.pipe.ts +14 -0
  234. package/src/lib/pipes/sanitize-html.pipe.ts +23 -0
  235. package/src/lib/types/base.types.ts +23 -0
  236. package/src/lib/types/common.types.ts +98 -0
  237. package/src/lib/types/form.types.ts +5 -0
  238. package/src/lib/utils/common.utils.ts +53 -0
  239. package/src/lib/utils/date.utils.ts +474 -0
  240. package/src/lib/utils/number.utils.ts +16 -0
  241. package/src/lib/utils/uuid.utils.ts +39 -0
  242. package/src/public-api.ts +114 -0
  243. package/tsconfig.lib.json +17 -0
  244. package/tsconfig.lib.prod.json +10 -0
  245. package/tsconfig.spec.json +9 -0
  246. package/fesm2022/fragments.mjs +0 -8928
  247. package/fesm2022/fragments.mjs.map +0 -1
  248. package/index.d.ts +0 -3929
@@ -0,0 +1,482 @@
1
+ import { NgTemplateOutlet } from '@angular/common';
2
+ import {
3
+ Component,
4
+ ContentChild,
5
+ ElementRef,
6
+ EventEmitter,
7
+ Input,
8
+ OnChanges,
9
+ Output,
10
+ QueryList,
11
+ SimpleChanges,
12
+ TemplateRef,
13
+ ViewChildren
14
+ } from '@angular/core';
15
+ import { AbstractControl } from '@angular/forms';
16
+ import {
17
+ StepChangeEvent,
18
+ StepItem,
19
+ StepItemContext,
20
+ StepValidationEvent,
21
+ TStepNavigationSource,
22
+ TStepperMotion,
23
+ TStepperOrientation
24
+ } from './stepper.type';
25
+
26
+ interface StepViewItem<T = unknown> extends StepItem<T> {
27
+ id: string;
28
+ active?: boolean;
29
+ }
30
+
31
+ interface StepValidationState {
32
+ checked: boolean;
33
+ valid: boolean;
34
+ }
35
+
36
+ @Component({
37
+ selector: 'frg-stepper',
38
+ standalone: true,
39
+ imports: [NgTemplateOutlet],
40
+ templateUrl: './stepper.component.html',
41
+ styleUrl: './stepper.component.scss'
42
+ })
43
+ export class StepperComponent implements OnChanges {
44
+ @Input() steps: StepItem[] = [];
45
+ @Input() activeId: string | null = null;
46
+ @Input() linear: boolean = true;
47
+ @Input() animated: boolean = true;
48
+ @Input() orientation: TStepperOrientation = 'horizontal';
49
+ @Input() focusPanelOnChange: boolean = true;
50
+ @Input() validateOnChange: boolean = true;
51
+ @Input() allowStepClick: boolean = true;
52
+ @Input() formGroup?: AbstractControl | null;
53
+
54
+ @Output() activeIdChange = new EventEmitter<string | null>();
55
+ @Output() changed = new EventEmitter<StepChangeEvent>();
56
+ @Output() validationChange = new EventEmitter<StepValidationEvent>();
57
+
58
+ @ContentChild('frgStepLabel', { read: TemplateRef }) labelTemplate?: TemplateRef<StepItemContext>;
59
+ @ContentChild('frgStepContent', { read: TemplateRef }) contentTemplate?: TemplateRef<StepItemContext>;
60
+ @ContentChild('frgStepIndicator', { read: TemplateRef }) indicatorTemplate?: TemplateRef<StepItemContext>;
61
+
62
+ @ViewChildren('stepBtn') stepButtons!: QueryList<ElementRef<HTMLButtonElement>>;
63
+ @ViewChildren('panelBody') panelBodies!: QueryList<ElementRef<HTMLElement>>;
64
+
65
+ viewSteps: StepViewItem[] = [];
66
+ motion: TStepperMotion = 'none';
67
+
68
+ private uidCounter = 0;
69
+ private readonly validationState = new Map<string, StepValidationState>();
70
+
71
+ ngOnChanges(changes: SimpleChanges): void {
72
+ if (changes['steps']) {
73
+ this.validationState.clear();
74
+ this.viewSteps = this.normalizeSteps(this.steps);
75
+ const initialActive = this.resolveActiveId(this.activeId);
76
+ this.setActiveId(initialActive, false, 'none');
77
+ }
78
+
79
+ if (changes['activeId'] && !changes['activeId'].firstChange) {
80
+ this.setActiveId(this.activeId, false, 'none');
81
+ }
82
+ }
83
+
84
+ get activeIndex(): number {
85
+ return this.viewSteps.findIndex(step => step.id === this.activeId);
86
+ }
87
+
88
+ isActive(id?: string | null): boolean {
89
+ return !!id && this.activeId === id;
90
+ }
91
+
92
+ stepId(id: string): string {
93
+ return `${id}-step`;
94
+ }
95
+
96
+ panelId(id: string): string {
97
+ return `${id}-panel`;
98
+ }
99
+
100
+ onStepClick(index: number, event: Event): void {
101
+ event.preventDefault();
102
+ if (!this.allowStepClick) {
103
+ return;
104
+ }
105
+ void this.requestStepChange(index, 'click');
106
+ }
107
+
108
+ onStepKeydown(event: KeyboardEvent, index: number): void {
109
+ const key = event.key;
110
+ const steps = this.stepButtons?.toArray() || [];
111
+ if (!steps.length) {
112
+ return;
113
+ }
114
+
115
+ if (key === 'Enter' || key === ' ') {
116
+ event.preventDefault();
117
+ void this.requestStepChange(index, 'click');
118
+ return;
119
+ }
120
+
121
+ const isHorizontal = this.orientation === 'horizontal';
122
+ const nextKey = isHorizontal ? 'ArrowRight' : 'ArrowDown';
123
+ const prevKey = isHorizontal ? 'ArrowLeft' : 'ArrowUp';
124
+
125
+ if (key === nextKey) {
126
+ event.preventDefault();
127
+ this.focusStep(this.findNextEnabledIndex(index, 1));
128
+ } else if (key === prevKey) {
129
+ event.preventDefault();
130
+ this.focusStep(this.findNextEnabledIndex(index, -1));
131
+ } else if (key === 'Home') {
132
+ event.preventDefault();
133
+ this.focusStep(this.findNextEnabledIndex(-1, 1));
134
+ } else if (key === 'End') {
135
+ event.preventDefault();
136
+ this.focusStep(this.findNextEnabledIndex(this.viewSteps.length, -1));
137
+ }
138
+ }
139
+
140
+ next(): void {
141
+ const currentIndex = this.getResolvedActiveIndex();
142
+ const target = currentIndex + 1;
143
+ if (target < this.viewSteps.length) {
144
+ void this.requestStepChange(target, 'next');
145
+ }
146
+ }
147
+
148
+ previous(): void {
149
+ const currentIndex = this.getResolvedActiveIndex();
150
+ const target = currentIndex - 1;
151
+ if (target >= 0) {
152
+ void this.requestStepChange(target, 'previous');
153
+ }
154
+ }
155
+
156
+ buildContext<T = unknown>(step: StepItem<T>, index: number): StepItemContext<T> {
157
+ return {
158
+ $implicit: step,
159
+ item: step,
160
+ index,
161
+ active: this.isActive(step.id),
162
+ completed: this.isStepComplete<T>(step),
163
+ error: this.isStepError<T>(step),
164
+ select: () => void this.requestStepChange(index, 'click'),
165
+ next: () => this.next(),
166
+ previous: () => this.previous()
167
+ };
168
+ }
169
+
170
+ stepStatus(step: StepItem, index: number): 'completed' | 'active' | 'error' | 'default' {
171
+ if (this.isStepError(step)) {
172
+ return 'error';
173
+ }
174
+ if (this.isActive(step.id)) {
175
+ return 'active';
176
+ }
177
+ if (this.isStepComplete(step)) {
178
+ return 'completed';
179
+ }
180
+ if (index === 0 && this.activeId === null) {
181
+ return 'active';
182
+ }
183
+ return 'default';
184
+ }
185
+
186
+ indicatorText(step: StepItem, index: number): string {
187
+ const status = this.stepStatus(step, index);
188
+ if (status === 'completed') {
189
+ return 'OK';
190
+ }
191
+ if (status === 'error') {
192
+ return '!';
193
+ }
194
+ if (step.icon) {
195
+ return step.icon;
196
+ }
197
+ return `${index + 1}`;
198
+ }
199
+
200
+ indicatorLabel(step: StepItem, index: number): string {
201
+ const status = this.stepStatus(step, index);
202
+ if (status === 'completed') {
203
+ return `${step.label} completed`;
204
+ }
205
+ if (status === 'error') {
206
+ return `${step.label} has errors`;
207
+ }
208
+ if (status === 'active') {
209
+ return `${step.label} active`;
210
+ }
211
+ return `Step ${index + 1}: ${step.label}`;
212
+ }
213
+
214
+ isStepDisabled(step: StepItem, index: number): boolean {
215
+ return !!step.disabled || this.isStepBlocked(index);
216
+ }
217
+
218
+ tabIndexFor(index: number): number {
219
+ if (this.isActive(this.viewSteps[index]?.id)) {
220
+ return 0;
221
+ }
222
+ const firstEnabled = this.findNextEnabledIndex(-1, 1);
223
+ if (this.activeId === null && index === firstEnabled) {
224
+ return 0;
225
+ }
226
+ return -1;
227
+ }
228
+
229
+ private async requestStepChange(index: number, source: TStepNavigationSource): Promise<void> {
230
+ const currentIndex = this.getResolvedActiveIndex();
231
+ const target = this.viewSteps[index];
232
+ if (!target || index === currentIndex) {
233
+ return;
234
+ }
235
+
236
+ if (target.disabled) {
237
+ return;
238
+ }
239
+
240
+ const forward = currentIndex >= 0 ? index > currentIndex : true;
241
+ if (forward && this.validateOnChange) {
242
+ const valid = await this.validateStep(currentIndex, source);
243
+ if (!valid && this.linear) {
244
+ return;
245
+ }
246
+ }
247
+
248
+ if (this.linear && this.isStepBlocked(index)) {
249
+ return;
250
+ }
251
+
252
+ const direction: TStepperMotion = forward ? 'next' : 'previous';
253
+ this.setActiveId(target.id, true, direction, target, index);
254
+ }
255
+
256
+ private normalizeSteps(steps: StepItem[]): StepViewItem[] {
257
+ return (steps || []).map((step, index) => ({
258
+ ...step,
259
+ id: step.id ?? this.generateId(index)
260
+ }));
261
+ }
262
+
263
+ private generateId(index: number): string {
264
+ this.uidCounter += 1;
265
+ return `frg-step-${index}-${this.uidCounter}`;
266
+ }
267
+
268
+ private resolveActiveId(candidate: string | null): string | null {
269
+ const available = (this.viewSteps || []).filter(step => !step.disabled);
270
+ if (!available.length) {
271
+ return null;
272
+ }
273
+ if (candidate && available.some(step => step.id === candidate)) {
274
+ return candidate;
275
+ }
276
+ const preselected = available.find(step => step.active && step.id);
277
+ if (preselected?.id) {
278
+ return preselected.id;
279
+ }
280
+ return available[0].id ?? null;
281
+ }
282
+
283
+ private setActiveId(
284
+ nextId: string | null,
285
+ emit: boolean,
286
+ direction: TStepperMotion,
287
+ item?: StepItem,
288
+ _index?: number
289
+ ): void {
290
+ const resolved = this.resolveActiveId(nextId);
291
+ const resolvedIndex = resolved ? this.viewSteps.findIndex(step => step.id === resolved) : -1;
292
+ const currentIndex = this.activeIndex;
293
+
294
+ this.activeId = resolved;
295
+ this.viewSteps = this.viewSteps.map(viewStep => ({
296
+ ...viewStep,
297
+ active: resolved ? viewStep.id === resolved : false
298
+ }));
299
+ this.motion = direction === 'none' ? this.getDirection(currentIndex, resolvedIndex) : direction;
300
+
301
+ if (emit) {
302
+ this.activeIdChange.emit(this.activeId);
303
+ this.changed.emit({
304
+ id: this.activeId,
305
+ index: resolvedIndex,
306
+ direction: this.motion,
307
+ item
308
+ });
309
+ }
310
+
311
+ if (this.focusPanelOnChange && resolvedIndex >= 0) {
312
+ setTimeout(() => this.focusPanel(resolvedIndex), 0);
313
+ }
314
+ }
315
+
316
+ private getDirection(fromIndex: number, toIndex: number): TStepperMotion {
317
+ if (fromIndex === -1 || toIndex === -1 || fromIndex === toIndex) {
318
+ return 'none';
319
+ }
320
+ return toIndex > fromIndex ? 'next' : 'previous';
321
+ }
322
+
323
+ isStepComplete<T>(step: StepItem<T>): boolean {
324
+ if (step.completed) {
325
+ return true;
326
+ }
327
+ const validation = this.getValidationResult<T>(step);
328
+ return validation === true;
329
+ }
330
+
331
+ isStepError<T>(step: StepItem<T>): boolean {
332
+ if (step.error) {
333
+ return true;
334
+ }
335
+ const control = step.control;
336
+ if (control) {
337
+ if (control.invalid && (control.touched || control.dirty)) {
338
+ return true;
339
+ }
340
+ const validation = this.validationState.get(step.id ?? '');
341
+ return validation ? !validation.valid : false;
342
+ }
343
+ const validation = this.validationState.get(step.id ?? '');
344
+ return validation ? !validation.valid : false;
345
+ }
346
+
347
+ private getValidationResult<T>(step: StepItem<T>): boolean | null {
348
+ if (step.control) {
349
+ if (step.control.pending) {
350
+ return null;
351
+ }
352
+ return step.control.valid;
353
+ }
354
+ const validation = this.validationState.get(step.id ?? '');
355
+ if (validation) {
356
+ return validation.valid;
357
+ }
358
+ return null;
359
+ }
360
+
361
+ private isStepBlocked(index: number): boolean {
362
+ if (!this.linear) {
363
+ return false;
364
+ }
365
+ const currentIndex = this.getResolvedActiveIndex();
366
+ if (currentIndex === -1) {
367
+ return index !== this.findNextEnabledIndex(-1, 1);
368
+ }
369
+ if (index <= currentIndex) {
370
+ return false;
371
+ }
372
+ for (let i = 0; i < index; i += 1) {
373
+ const step = this.viewSteps[i];
374
+ if (!step) {
375
+ continue;
376
+ }
377
+ if (step.optional) {
378
+ continue;
379
+ }
380
+ if (!this.isStepComplete(step)) {
381
+ return true;
382
+ }
383
+ }
384
+ return false;
385
+ }
386
+
387
+ private async validateStep(index: number, source: TStepNavigationSource): Promise<boolean> {
388
+ if (index < 0 || index >= this.viewSteps.length) {
389
+ return true;
390
+ }
391
+ const step = this.viewSteps[index];
392
+ if (!step) {
393
+ return true;
394
+ }
395
+
396
+ const hasControl = !!step.control;
397
+ const hasValidator = !!step.validator;
398
+ if (!hasControl && !hasValidator) {
399
+ return true;
400
+ }
401
+
402
+ let valid = true;
403
+ if (hasControl && step.control) {
404
+ step.control.markAllAsTouched();
405
+ step.control.updateValueAndValidity({ onlySelf: false, emitEvent: false });
406
+ if (step.control.invalid) {
407
+ valid = false;
408
+ }
409
+ }
410
+
411
+ if (hasValidator && step.validator) {
412
+ const context = {
413
+ step,
414
+ index,
415
+ formGroup: this.formGroup ?? null,
416
+ source
417
+ };
418
+ const result = await Promise.resolve(step.validator(context));
419
+ if (!result) {
420
+ valid = false;
421
+ }
422
+ }
423
+
424
+ this.validationState.set(step.id, { checked: true, valid });
425
+ this.viewSteps = this.viewSteps.map(item =>
426
+ item.id === step.id
427
+ ? {
428
+ ...item,
429
+ completed: valid ? true : item.completed,
430
+ error: valid ? false : true
431
+ }
432
+ : item
433
+ );
434
+
435
+ this.validationChange.emit({
436
+ id: step.id,
437
+ index,
438
+ valid,
439
+ source,
440
+ item: step
441
+ });
442
+
443
+ return valid;
444
+ }
445
+
446
+ private focusStep(index: number): void {
447
+ const step = this.stepButtons?.get(index);
448
+ if (step?.nativeElement) {
449
+ step.nativeElement.focus();
450
+ }
451
+ }
452
+
453
+ private focusPanel(index: number): void {
454
+ const panel = this.panelBodies?.get(index);
455
+ if (panel?.nativeElement) {
456
+ panel.nativeElement.focus();
457
+ }
458
+ }
459
+
460
+ private getResolvedActiveIndex(): number {
461
+ const index = this.viewSteps.findIndex(step => step.id === this.activeId);
462
+ if (index >= 0) {
463
+ return index;
464
+ }
465
+ return this.viewSteps.findIndex(step => !step.disabled);
466
+ }
467
+
468
+ private findNextEnabledIndex(startIndex: number, delta: number): number {
469
+ if (!this.viewSteps.length) {
470
+ return -1;
471
+ }
472
+ let index = startIndex + delta;
473
+ while (index >= 0 && index < this.viewSteps.length) {
474
+ const step = this.viewSteps[index];
475
+ if (step && !this.isStepDisabled(step, index)) {
476
+ return index;
477
+ }
478
+ index += delta;
479
+ }
480
+ return startIndex;
481
+ }
482
+ }
@@ -0,0 +1,60 @@
1
+ import { TemplateRef } from '@angular/core';
2
+ import { AbstractControl } from '@angular/forms';
3
+
4
+ export type TStepperOrientation = 'horizontal' | 'vertical';
5
+ export type TStepperMotion = 'next' | 'previous' | 'none';
6
+ export type TStepNavigationSource = 'click' | 'next' | 'previous' | 'programmatic';
7
+
8
+ export interface StepItemContext<T = unknown> {
9
+ $implicit: StepItem<T>;
10
+ item: StepItem<T>;
11
+ index: number;
12
+ active: boolean;
13
+ completed: boolean;
14
+ error: boolean;
15
+ select: () => void;
16
+ next: () => void;
17
+ previous: () => void;
18
+ }
19
+
20
+ export interface StepValidationContext<T = unknown> {
21
+ step: StepItem<T>;
22
+ index: number;
23
+ formGroup?: AbstractControl | null;
24
+ source: TStepNavigationSource;
25
+ }
26
+
27
+ export type StepValidator<T = unknown> = (context: StepValidationContext<T>) => boolean | Promise<boolean>;
28
+
29
+ export interface StepItem<T = unknown> {
30
+ id?: string;
31
+ label: string;
32
+ description?: string;
33
+ disabled?: boolean;
34
+ optional?: boolean;
35
+ completed?: boolean;
36
+ error?: boolean;
37
+ icon?: string;
38
+ data?: T;
39
+ content?: string;
40
+ control?: AbstractControl | null;
41
+ validator?: StepValidator<T>;
42
+ labelTemplate?: TemplateRef<StepItemContext<T>>;
43
+ contentTemplate?: TemplateRef<StepItemContext<T>>;
44
+ indicatorTemplate?: TemplateRef<StepItemContext<T>>;
45
+ }
46
+
47
+ export interface StepChangeEvent<T = unknown> {
48
+ id: string | null;
49
+ index: number;
50
+ direction: TStepperMotion;
51
+ item?: StepItem<T>;
52
+ }
53
+
54
+ export interface StepValidationEvent<T = unknown> {
55
+ id: string | null;
56
+ index: number;
57
+ valid: boolean;
58
+ source: TStepNavigationSource;
59
+ item?: StepItem<T>;
60
+ }