@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,267 @@
1
+ @use "sass:color";
2
+ @use './../../../../../assets/styles/scss/variables' as *;
3
+
4
+ :host {
5
+ display: block;
6
+ font-family: $input-font-family;
7
+
8
+ label {
9
+ display: flex;
10
+ font-size: $font-size-xs;
11
+ color: $input-text-color;
12
+ padding: $input-padding 0;
13
+ }
14
+
15
+ .frg-selection-list {
16
+ position: relative;
17
+ display: inline-block;
18
+ width: 100%;
19
+
20
+ .options-search {
21
+ position: relative;
22
+ display: flex;
23
+ align-items: center;
24
+ gap: 0;
25
+ width: 100%;
26
+ border-bottom: 2px solid $input-border-color;
27
+ transition: border-color 0.3s;
28
+
29
+ &.focus,
30
+ &:hover {
31
+ border-color: $input-focus-border-color;
32
+ }
33
+
34
+ &__input {
35
+ flex: 1;
36
+ padding: $input-padding;
37
+ padding-right: 3rem;
38
+ border: none;
39
+ background: transparent;
40
+ font-family: $input-font-family;
41
+ font-size: 1rem;
42
+ color: $color-dark-neutral;
43
+ cursor: pointer;
44
+ outline: none;
45
+
46
+ &::placeholder {
47
+ color: $input-text-color;
48
+ opacity: 0.7;
49
+ }
50
+
51
+ &:disabled {
52
+ cursor: not-allowed;
53
+ opacity: 0.6;
54
+ }
55
+ }
56
+
57
+ }
58
+
59
+ &__handle-buttons {
60
+ display: flex;
61
+ align-items: center;
62
+ gap: 0;
63
+ width: 100%;
64
+ border-bottom: 2px solid $input-border-color;
65
+ background: transparent;
66
+ font-family: $input-font-family;
67
+ color: $input-text-color;
68
+ cursor: pointer;
69
+ transition: border-color 0.3s;
70
+ position: relative;
71
+
72
+ &:hover,
73
+ &.focus {
74
+ border-color: $input-focus-border-color;
75
+ }
76
+
77
+ .custom-select {
78
+ flex: 1;
79
+ padding: $input-padding;
80
+ padding-right: 3rem;
81
+ border: none;
82
+ background: transparent;
83
+ text-align: left;
84
+ display: flex;
85
+ align-items: center;
86
+ font-size: 1rem;
87
+
88
+ &:focus-visible {
89
+ outline: none;
90
+ }
91
+
92
+ &:disabled {
93
+ cursor: not-allowed;
94
+ opacity: 0.6;
95
+ }
96
+
97
+ &.open {
98
+ border-bottom-color: $color-primary;
99
+ }
100
+
101
+ .item-label {
102
+ flex: 1;
103
+ overflow: hidden;
104
+ white-space: nowrap;
105
+ text-overflow: ellipsis;
106
+ margin-right: 0.5rem;
107
+ color: $input-text-color;
108
+ opacity: 0.7;
109
+ }
110
+
111
+ .selected {
112
+ flex: 1;
113
+ overflow: hidden;
114
+ white-space: nowrap;
115
+ text-overflow: ellipsis;
116
+ margin-right: 0.5rem;
117
+ opacity: 1;
118
+ color: $color-dark-neutral;
119
+ }
120
+ }
121
+ }
122
+
123
+ .options {
124
+ position: absolute;
125
+ left: 0;
126
+ right: 0;
127
+ background: #fff;
128
+ border-radius: 0.25rem;
129
+ box-shadow: $box-shadow-md;
130
+ z-index: 10;
131
+ max-height: 240px;
132
+ overflow-y: auto;
133
+ animation: dropdown-fade-in 0.2s ease-in-out;
134
+
135
+ &::-webkit-scrollbar {
136
+ width: 0.3rem;
137
+ background-color: transparent;
138
+ }
139
+
140
+ &::-webkit-scrollbar-thumb {
141
+ background-color: $color-primary;
142
+ border-radius: 0.1rem;
143
+ }
144
+
145
+ &::-webkit-scrollbar-thumb:hover {
146
+ background-color: color.adjust($color-primary, $lightness: -10%);
147
+ }
148
+
149
+ .option {
150
+ display: block;
151
+ width: 100%;
152
+ padding: 0.5rem 1rem;
153
+ background: transparent;
154
+ border: none;
155
+ text-align: left;
156
+ color: $input-text-color;
157
+ font-size: 0.95rem;
158
+ cursor: pointer;
159
+ transition: background-color 0.15s ease, color 0.15s ease;
160
+
161
+ &:hover:not(:disabled),
162
+ &:focus-visible:not(:disabled) {
163
+ background-color: color.scale($color-primary, $lightness: 90%);
164
+ outline: none;
165
+ }
166
+
167
+ &.selected {
168
+ background-color: color.scale($color-primary, $lightness: 85%);
169
+ }
170
+
171
+ &:disabled,
172
+ &.disabled {
173
+ opacity: 0.5;
174
+ cursor: not-allowed;
175
+ }
176
+ }
177
+
178
+ &.open-down {
179
+ top: calc(100% + 0.25rem);
180
+ }
181
+
182
+ &.open-up {
183
+ bottom: calc(100% + 0.25rem);
184
+ }
185
+ }
186
+
187
+ .clear-button,
188
+ .arrow-button {
189
+ background: transparent;
190
+ border: none;
191
+ cursor: pointer;
192
+ padding: 0;
193
+ width: 1.25rem;
194
+ height: 1.25rem;
195
+ display: flex;
196
+ align-items: center;
197
+ justify-content: center;
198
+ font-size: 0.75rem;
199
+ line-height: 1;
200
+ color: $color-dark-neutral;
201
+ }
202
+
203
+ .clear-button {
204
+ font-size: 0.75rem;
205
+ }
206
+
207
+ .clear-button .fas {
208
+ line-height: 1;
209
+ }
210
+
211
+ .selection-list__actions {
212
+ position: absolute;
213
+ right: 0;
214
+ top: 50%;
215
+ transform: translateY(-50%);
216
+ display: inline-flex;
217
+ align-items: center;
218
+ gap: 0.125rem;
219
+ padding-right: 0.25rem;
220
+ }
221
+
222
+ .arrow-button {
223
+ display: flex;
224
+ align-items: center;
225
+ justify-content: center;
226
+ background: transparent;
227
+ border: none;
228
+ cursor: pointer;
229
+ transition: transform 0.3s ease;
230
+
231
+ &:disabled {
232
+ cursor: not-allowed;
233
+ opacity: 0.5;
234
+ }
235
+ }
236
+
237
+ .arrow-icon {
238
+ font-size: 0.75rem;
239
+ transition: transform 0.3s ease;
240
+ }
241
+
242
+ .arrow-button.open .arrow-icon {
243
+ transform: rotate(180deg);
244
+ }
245
+
246
+ &.selection-list-error .options-search,
247
+ &.selection-list-error .frg-selection-list__handle-buttons {
248
+ border-bottom-color: $color-danger;
249
+ }
250
+
251
+ &.selection-list-error label {
252
+ color: $color-danger;
253
+ }
254
+
255
+ @keyframes dropdown-fade-in {
256
+ from {
257
+ opacity: 0;
258
+ transform: translateY(-10px);
259
+ }
260
+
261
+ to {
262
+ opacity: 1;
263
+ transform: translateY(0);
264
+ }
265
+ }
266
+ }
267
+ }
@@ -0,0 +1,66 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+ import { By } from '@angular/platform-browser';
3
+ import { SelectionListComponent } from './selection-list.component';
4
+ import { SelectionListItem } from '../selection-lists-interface';
5
+
6
+ describe('SelectionListComponent', () => {
7
+ let fixture: ComponentFixture<SelectionListComponent<string>>;
8
+ let component: SelectionListComponent<string>;
9
+
10
+ const items: SelectionListItem<string>[] = [
11
+ { id: 'a', label: 'Apple', value: 'apple', selected: false },
12
+ { id: 'b', label: 'Banana', value: 'banana', selected: false }
13
+ ];
14
+
15
+ beforeEach(async () => {
16
+ await TestBed.configureTestingModule({
17
+ imports: [SelectionListComponent]
18
+ }).compileComponents();
19
+
20
+ fixture = TestBed.createComponent(SelectionListComponent<string>);
21
+ component = fixture.componentInstance;
22
+ component.items = [...items];
23
+ });
24
+
25
+ it('shows placeholder text when no selection', () => {
26
+ component.placeholder = 'Pick one';
27
+
28
+ fixture.detectChanges();
29
+
30
+ const label = fixture.debugElement.query(By.css('.item-label'));
31
+ expect(label.nativeElement.textContent).toContain('Pick one');
32
+ });
33
+
34
+ it('toggles dropdown on click', () => {
35
+ fixture.detectChanges();
36
+
37
+ const trigger = fixture.debugElement.query(By.css('.custom-select'));
38
+ trigger.nativeElement.click();
39
+
40
+ expect((component as any).isOpen).toBeTrue();
41
+ });
42
+
43
+ it('selects an item and closes dropdown', () => {
44
+ const onChangeSpy = jasmine.createSpy('onChange');
45
+ component.registerOnChange(onChangeSpy);
46
+
47
+ fixture.detectChanges();
48
+
49
+ component.selectItem(items[0], new Event('click'));
50
+
51
+ expect(component.value).toBe('apple');
52
+ expect((component as any).selectedId).toBe('a');
53
+ expect((component as any).isOpen).toBeFalse();
54
+ expect(onChangeSpy).toHaveBeenCalledWith('apple');
55
+ });
56
+
57
+ it('filters items when search term is long enough', () => {
58
+ component.hasSearch = true;
59
+ (component as any).searchTerm = 'app';
60
+
61
+ fixture.detectChanges();
62
+
63
+ expect(component.filteredItems.length).toBe(1);
64
+ expect(component.filteredItems[0].label).toBe('Apple');
65
+ });
66
+ });
@@ -0,0 +1,315 @@
1
+ import { NgTemplateOutlet } from "@angular/common";
2
+ import { Component, ElementRef, HostListener, Input, QueryList, TemplateRef, ViewChild, ViewChildren, inject } from "@angular/core";
3
+ import { SelectionListBase } from "../selection-lists-base";
4
+ import { DEFAULT_SELECTION_LIST_ERROR_MESSAGES } from "../selection-lists-const";
5
+ import { SelectionListItem } from "../selection-lists-interface";
6
+ import { InputValidationComponent } from "../../input/input-validation/input-validation.component";
7
+ import { DEFAULT_INPUT_SELECTION_LIST_WARNINGS_MESSAGES } from "../../input/input-consts";
8
+
9
+ @Component({
10
+ selector: "frg-selection-list",
11
+ templateUrl: "./selection-list.component.html",
12
+ styleUrls: ["./selection-list.component.scss"],
13
+ imports: [NgTemplateOutlet, InputValidationComponent]
14
+ })
15
+ export class SelectionListComponent<T> extends SelectionListBase<T> {
16
+ private readonly elementRef = inject(ElementRef);
17
+
18
+ @Input() items: Array<SelectionListItem<T>> = [];
19
+ @Input() optionTemplate?: TemplateRef<unknown>;
20
+ @Input() hasSearch: boolean = false;
21
+
22
+ @ViewChildren("optionButton") optionButtons!: QueryList<ElementRef<HTMLButtonElement>>;
23
+ @ViewChild("searchInput", { read: ElementRef }) searchInput?: ElementRef<HTMLInputElement>;
24
+
25
+ protected isOpen = false;
26
+ protected searchTerm = "";
27
+ protected selectedId: string = "";
28
+
29
+ private focusedIndex = -1;
30
+
31
+ @HostListener("document:click", ['$event'])
32
+ closeDropdown(event: MouseEvent): void {
33
+ const target = event.target as HTMLElement;
34
+ const nativeElement = this.elementRef.nativeElement;
35
+
36
+ const clickedInside = nativeElement.contains(target);
37
+ const isSearchInput = target.closest('.options-search__input');
38
+
39
+ if (!clickedInside && !isSearchInput) {
40
+ this.isOpen = false;
41
+ }
42
+ }
43
+
44
+ public get filteredItems(): Array<SelectionListItem<T>> {
45
+ if (this.hasSearch && this.searchTerm && this.searchTerm.length > 2) {
46
+ const lower = this.searchTerm.toLowerCase();
47
+ const lowerWithTrim = this.searchTerm.toLowerCase().trim();
48
+
49
+ return this.items.filter(item =>
50
+ (item.label ?? '').toLowerCase().includes(lower)
51
+ || (item.label ?? '').toLowerCase().trim().includes(lowerWithTrim)
52
+ );
53
+ };
54
+
55
+ return this.items;
56
+ }
57
+
58
+ public get selectedItem(): SelectionListItem<T> | null {
59
+ return this.items.find(i => i.id === this.selectedId) ?? null;
60
+ }
61
+
62
+ protected override get errorList(): string[] {
63
+ if (!this.control?.errors) return [];
64
+ const errors = this.control.errors;
65
+ return Object.keys(errors).map(key => {
66
+ if (this.errorMessages[key]) {
67
+ return this.errorMessages[key];
68
+ }
69
+
70
+ if (key === 'minlength' && errors[key]?.requiredLength) {
71
+ return `Minimum length is ${errors[key].requiredLength} characters.`;
72
+ }
73
+
74
+ if (key === 'maxlength' && errors[key]?.requiredLength) {
75
+ return `Maximum length is ${errors[key].requiredLength} characters.`;
76
+ }
77
+
78
+ return DEFAULT_SELECTION_LIST_ERROR_MESSAGES[key] || 'Invalid field.';
79
+ });
80
+ }
81
+
82
+ /**
83
+ * Returns warning messages. Consumers may pass arbitrary warnings via `warningMessages`.
84
+ * Falls back to DEFAULT_INPUT_SELECTION_LIST_WARNINGS_MESSAGES or a generic message.
85
+ */
86
+ protected override get warningList(): string[] {
87
+ return Object.keys(this.warningMessages).map(key => {
88
+ return (
89
+ this.warningMessages[key] ||
90
+ DEFAULT_INPUT_SELECTION_LIST_WARNINGS_MESSAGES[key] ||
91
+ ''
92
+ );
93
+ });
94
+ }
95
+
96
+ /**
97
+ * Returns whether any warnings are active.
98
+ */
99
+ protected get hasWarning(): boolean {
100
+ return this.warningList.length > 0;
101
+ }
102
+
103
+ public toggleDropdown(event: Event): void {
104
+ event.stopPropagation();
105
+ if (this.disabled) return;
106
+
107
+ this.isOpen = !this.isOpen;
108
+
109
+ if (this.isOpen) {
110
+ this.setDropdownDirection();
111
+ setTimeout(() => {
112
+ if (this.hasSearch && this.searchInput) {
113
+ this.searchInput.nativeElement.focus();
114
+ const len = this.searchInput.nativeElement.value.length;
115
+ this.searchInput.nativeElement.setSelectionRange(len, len);
116
+ } else {
117
+ this.focusFirstItem();
118
+ }
119
+ }, 0);
120
+ } else {
121
+ this.searchTerm = "";
122
+ }
123
+ }
124
+
125
+ public selectItem(item: SelectionListItem<T>, event: Event): void {
126
+ event.stopPropagation();
127
+ if (item.disabled) return;
128
+
129
+ this.selectedId = item.id;
130
+ this.value = item.value;
131
+ this.isOpen = false;
132
+ this.focusedIndex = this.items.findIndex(i => i.id === item.id);
133
+
134
+ if (this.hasSearch) {
135
+ this.searchTerm = item.label ?? '';
136
+ }
137
+
138
+ this.onChange(this.value);
139
+ this.onTouched();
140
+ }
141
+
142
+ protected override clearValue(event: MouseEvent) {
143
+ event.stopPropagation();
144
+
145
+ this.selectedId = '';
146
+ this.value = null;
147
+ this.searchTerm = '';
148
+ this.isOpen = false;
149
+
150
+ this.onChange(null);
151
+ this.onTouched();
152
+ }
153
+
154
+ protected onKeyDown(event: KeyboardEvent): void {
155
+ if (this.disabled) return;
156
+
157
+ switch (event.key) {
158
+ case "Enter":
159
+ case " ":
160
+ case "Spacebar":
161
+ event.preventDefault();
162
+ this.toggleDropdown(event);
163
+ break;
164
+ case "ArrowDown":
165
+ event.preventDefault();
166
+ if (!this.isOpen) this.isOpen = true;
167
+ setTimeout(() => {
168
+ if (this.hasSearch && this.searchInput) {
169
+ this.searchInput.nativeElement.focus();
170
+ } else {
171
+ this.focusFirstItem();
172
+ }
173
+ }, 0);
174
+ break;
175
+ case "Escape":
176
+ this.isOpen = false;
177
+ break;
178
+ }
179
+ }
180
+
181
+ protected onOptionKeyDown(event: KeyboardEvent, item: SelectionListItem<T>, index: number): void {
182
+ switch (event.key) {
183
+ case "Enter":
184
+ case " ":
185
+ case "Spacebar":
186
+ event.preventDefault();
187
+ this.selectItem(item, event);
188
+ break;
189
+ case "ArrowDown":
190
+ event.preventDefault();
191
+ this.focusNextItem(index);
192
+ break;
193
+ case "ArrowUp":
194
+ event.preventDefault();
195
+ this.focusPreviousItem(index);
196
+ break;
197
+ case "Escape":
198
+ event.preventDefault();
199
+ this.isOpen = false;
200
+ break;
201
+ }
202
+ }
203
+
204
+ protected override onBlur(event: FocusEvent): void {
205
+ const nativeElement = this.elementRef.nativeElement;
206
+ const relatedTarget = event.relatedTarget as Node;
207
+ if (!relatedTarget || !nativeElement.contains(relatedTarget)) {
208
+ this.isOpen = false;
209
+ if(this.selectedItem){
210
+ this.searchTerm = this.selectedItem.label;
211
+ } else {
212
+ this.searchTerm = '';
213
+ }
214
+ }
215
+ }
216
+
217
+ protected override onFocus(): void {
218
+ if (!this.disabled) {
219
+ this.isOpen = true;
220
+ this.setDropdownDirection();
221
+ }
222
+ }
223
+
224
+ protected onSearch(event: Event): void {
225
+ const target = event.target;
226
+ if (target instanceof HTMLInputElement) {
227
+ this.searchTerm = target.value ?? '';
228
+ this.focusedIndex = 0;
229
+ }
230
+ }
231
+
232
+ protected onSearchKeyDown(event: KeyboardEvent): void {
233
+ if (event.key === "Enter") {
234
+ event.preventDefault();
235
+ const exactMatch = this.filteredItems.find(i => i.label?.toLowerCase() === this.searchTerm.toLowerCase());
236
+ if (exactMatch) {
237
+ this.selectItem(exactMatch, event);
238
+ }
239
+ }
240
+
241
+ switch (event.key) {
242
+ case "ArrowDown":
243
+ event.preventDefault();
244
+ setTimeout(() => this.focusFirstItem(), 0);
245
+ break;
246
+
247
+ case "Escape":
248
+ event.preventDefault();
249
+ this.isOpen = false;
250
+ break;
251
+ }
252
+ }
253
+
254
+ protected override updateDisabledState(_isDisabled: boolean): void {
255
+ //Intentionally left void, to implement in case is needed
256
+ }
257
+ protected override updateView(_value: T | null): void {
258
+ //Intentionally left void, to implement in case is needed
259
+ }
260
+
261
+ private focusFirstItem(): void {
262
+ const buttons = this.getEnabledButtons();
263
+ if (buttons.length > 0) {
264
+ this.focusedIndex = 0;
265
+ buttons[0].focus();
266
+ }
267
+ }
268
+
269
+ private focusNextItem(currentIndex: number = this.focusedIndex): void {
270
+ const buttons = this.getEnabledButtons();
271
+ if (buttons.length === 0) return;
272
+
273
+ const nextIndex = (currentIndex + 1) % buttons.length;
274
+ this.focusedIndex = nextIndex;
275
+ buttons[nextIndex].focus();
276
+ }
277
+
278
+ private focusPreviousItem(currentIndex: number = this.focusedIndex): void {
279
+ const buttons = this.getEnabledButtons();
280
+ if (buttons.length === 0) return;
281
+
282
+ const prevIndex = (currentIndex - 1 + buttons.length) % buttons.length;
283
+ this.focusedIndex = prevIndex;
284
+ buttons[prevIndex].focus();
285
+ }
286
+
287
+ private getEnabledButtons(): HTMLButtonElement[] {
288
+ return this.optionButtons
289
+ ? this.optionButtons.map(ref => ref.nativeElement).filter(btn => !btn.disabled)
290
+ : [];
291
+ }
292
+
293
+ private setDropdownDirection(): void {
294
+ const trigger: HTMLElement | undefined = this.elementRef.nativeElement.querySelector('.custom-select');
295
+ const searchTrigger: HTMLElement | undefined = this.elementRef.nativeElement.querySelector('.options-search__input');
296
+ const dropdownHeight: number = 240;
297
+
298
+ if(trigger || searchTrigger){
299
+ const rect = trigger?.getBoundingClientRect() || searchTrigger?.getBoundingClientRect();
300
+ const viewportHeight = window.innerHeight;
301
+
302
+ const spaceBelow = viewportHeight - (rect?.bottom ?? 0);
303
+ const spaceAbove = (rect?.top ?? 0);
304
+
305
+ if (spaceBelow >= dropdownHeight) {
306
+ this.openDirection = 'down';
307
+ } else if (spaceAbove >= dropdownHeight) {
308
+ this.openDirection = 'top';
309
+ } else {
310
+ this.openDirection = spaceBelow > spaceAbove ? 'down' : 'top';
311
+ }
312
+ }
313
+ }
314
+
315
+ }
@@ -0,0 +1,35 @@
1
+ import { Directive, OnInit } from '@angular/core';
2
+ import { ControlValueAccessor } from '@angular/forms';
3
+ import { InputBase } from '../input/input-base';
4
+ import { SelectionListPosition } from './selection-lists.type';
5
+
6
+ /**
7
+ * Base class for input components, providing common functionality for form controls.
8
+ * This class implements ControlValueAccessor to integrate with Angular forms.
9
+ * Subclasses must implement the abstract methods to update the view and disabled state.
10
+ * It also provides properties for label, placeholder, required state, and error handling.
11
+ * The errorMessages property can be customized to provide specific validation messages.
12
+ * The hasError getter can be used in templates to conditionally display error messages.
13
+ * Subclasses should call super.ngOnInit() if they override ngOnInit().
14
+ *
15
+ * @see ControlValueAccessor
16
+ */
17
+ @Directive()
18
+ export abstract class SelectionListBase<T> extends InputBase<T> implements ControlValueAccessor, OnInit {
19
+ /**
20
+ * Direction of selection list when it opens.
21
+ */
22
+ protected openDirection: SelectionListPosition = 'down';
23
+
24
+ /**
25
+ * Clear the value of input
26
+ */
27
+ protected abstract clearValue(event: Event): void;
28
+
29
+ /**
30
+ * Handles selection change event.
31
+ * Subclasses can override to provide custom behavior.
32
+ * Default implementation does nothing.
33
+ */
34
+ protected onSelectionChange(_: Event): void { }
35
+ }
@@ -0,0 +1,17 @@
1
+ //#region [ERRORS]
2
+ export const DEFAULT_SELECTION_LIST_ERROR_MESSAGES: Record<string, string> = {
3
+ required: 'This field is required.',
4
+ minlength: 'The value is too short.',
5
+ maxlength: 'The value is too long.',
6
+ pattern: 'The value does not match the required pattern.',
7
+ };
8
+ //#endregion
9
+
10
+ //#region [WARNINGS]
11
+ // export const DEFAULT_INPUT_NUMBER_WARNING_MESSAGES: Record<string, string> = {
12
+ // min:'The minimium value allowed is {{limit}}',
13
+ // max: 'The maximum value allowed is {{limit}}',
14
+ // step: 'The value does not match the required step increment value of {{limit}}',
15
+ // precision: 'The value exceeds the allowed number of decimal places value of {{limit}}',
16
+ // };
17
+ //#endregion
@@ -0,0 +1,7 @@
1
+ export interface SelectionListItem<T> {
2
+ id: string;
3
+ label: string;
4
+ value: T | null;
5
+ selected: boolean;
6
+ disabled?: boolean;
7
+ }
@@ -0,0 +1 @@
1
+ export type SelectionListPosition = 'top' | 'down';