@syncfusion/ej2-navigations 17.3.9-beta → 17.3.14-96615

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 (314) hide show
  1. package/.eslintrc.json +244 -0
  2. package/CHANGELOG.md +898 -800
  3. package/README.md +163 -163
  4. package/dist/ej2-navigations.umd.min.js +1 -10
  5. package/dist/ej2-navigations.umd.min.js.map +1 -1
  6. package/dist/es6/ej2-navigations.es2015.js +325 -170
  7. package/dist/es6/ej2-navigations.es2015.js.map +1 -1
  8. package/dist/es6/ej2-navigations.es5.js +455 -300
  9. package/dist/es6/ej2-navigations.es5.js.map +1 -1
  10. package/dist/global/ej2-navigations.min.js +1 -10
  11. package/dist/global/ej2-navigations.min.js.map +1 -1
  12. package/dist/global/index.d.ts +0 -9
  13. package/dist/ts/accordion/accordion.ts +1312 -0
  14. package/dist/ts/common/h-scroll.ts +459 -0
  15. package/dist/ts/common/menu-base.ts +2131 -0
  16. package/dist/ts/common/v-scroll.ts +430 -0
  17. package/dist/ts/context-menu/context-menu.ts +119 -0
  18. package/dist/ts/menu/menu.ts +270 -0
  19. package/dist/ts/sidebar/sidebar.ts +817 -0
  20. package/dist/ts/tab/tab.ts +1761 -0
  21. package/dist/ts/toolbar/toolbar.ts +2076 -0
  22. package/dist/ts/treeview/treeview.ts +5050 -0
  23. package/helpers/e2e/index.js +3 -3
  24. package/license +10 -10
  25. package/package.json +135 -149
  26. package/src/accordion/accordion-model.d.ts +156 -156
  27. package/src/accordion/accordion.d.ts +1 -0
  28. package/src/accordion/accordion.js +40 -41
  29. package/src/common/h-scroll-model.d.ts +5 -5
  30. package/src/common/h-scroll.js +19 -20
  31. package/src/common/menu-base-model.d.ts +157 -157
  32. package/src/common/menu-base.d.ts +19 -0
  33. package/src/common/menu-base.js +94 -24
  34. package/src/common/v-scroll-model.d.ts +5 -5
  35. package/src/common/v-scroll.js +19 -19
  36. package/src/context-menu/context-menu-model.d.ts +15 -15
  37. package/src/context-menu/context-menu.js +19 -19
  38. package/src/menu/menu-model.d.ts +30 -30
  39. package/src/menu/menu.js +19 -19
  40. package/src/sidebar/sidebar-model.d.ts +136 -136
  41. package/src/sidebar/sidebar.js +19 -19
  42. package/src/tab/tab-model.d.ts +215 -215
  43. package/src/tab/tab.d.ts +3 -0
  44. package/src/tab/tab.js +117 -82
  45. package/src/toolbar/toolbar-model.d.ts +175 -175
  46. package/src/toolbar/toolbar.d.ts +1 -0
  47. package/src/toolbar/toolbar.js +30 -24
  48. package/src/treeview/treeview-model.d.ts +323 -323
  49. package/src/treeview/treeview.js +79 -33
  50. package/styles/accordion/_all.scss +2 -2
  51. package/styles/accordion/_bootstrap-dark-definition.scss +69 -69
  52. package/styles/accordion/_bootstrap-definition.scss +76 -76
  53. package/styles/accordion/_bootstrap4-definition.scss +82 -82
  54. package/styles/accordion/_fabric-dark-definition.scss +74 -74
  55. package/styles/accordion/_fabric-definition.scss +78 -78
  56. package/styles/accordion/_highcontrast-definition.scss +106 -106
  57. package/styles/accordion/_highcontrast-light-definition.scss +104 -104
  58. package/styles/accordion/_layout.scss +447 -447
  59. package/styles/accordion/_material-dark-definition.scss +75 -75
  60. package/styles/accordion/_material-definition.scss +72 -72
  61. package/styles/accordion/_theme.scss +479 -479
  62. package/styles/accordion/icons/_bootstrap-dark.scss +17 -17
  63. package/styles/accordion/icons/_bootstrap.scss +17 -17
  64. package/styles/accordion/icons/_bootstrap4.scss +17 -17
  65. package/styles/accordion/icons/_fabric-dark.scss +17 -17
  66. package/styles/accordion/icons/_fabric.scss +17 -17
  67. package/styles/accordion/icons/_highcontrast-light.scss +17 -17
  68. package/styles/accordion/icons/_highcontrast.scss +17 -17
  69. package/styles/accordion/icons/_material-dark.scss +17 -17
  70. package/styles/accordion/icons/_material.scss +17 -17
  71. package/styles/bootstrap-dark.css +7 -9
  72. package/styles/bootstrap.css +7 -9
  73. package/styles/bootstrap4.css +7 -9
  74. package/styles/bootstrap5-dark.css +0 -0
  75. package/styles/bootstrap5-dark.scss +0 -0
  76. package/styles/bootstrap5.css +0 -0
  77. package/styles/bootstrap5.scss +0 -0
  78. package/styles/context-menu/_all.scss +2 -2
  79. package/styles/context-menu/_bootstrap-dark-definition.scss +54 -54
  80. package/styles/context-menu/_bootstrap-definition.scss +52 -52
  81. package/styles/context-menu/_bootstrap4-definition.scss +52 -52
  82. package/styles/context-menu/_fabric-dark-definition.scss +54 -54
  83. package/styles/context-menu/_fabric-definition.scss +52 -52
  84. package/styles/context-menu/_highcontrast-definition.scss +52 -52
  85. package/styles/context-menu/_highcontrast-light-definition.scss +54 -54
  86. package/styles/context-menu/_layout-mixin.scss +175 -175
  87. package/styles/context-menu/_layout.scss +70 -70
  88. package/styles/context-menu/_material-dark-definition.scss +54 -54
  89. package/styles/context-menu/_material-definition.scss +52 -52
  90. package/styles/context-menu/_theme-mixin.scss +59 -59
  91. package/styles/context-menu/_theme.scss +36 -36
  92. package/styles/context-menu/bootstrap-dark.css +0 -1
  93. package/styles/context-menu/bootstrap.css +0 -1
  94. package/styles/context-menu/bootstrap4.css +0 -1
  95. package/styles/context-menu/fabric-dark.css +0 -1
  96. package/styles/context-menu/fabric.css +0 -1
  97. package/styles/context-menu/highcontrast-light.css +0 -1
  98. package/styles/context-menu/highcontrast.css +0 -1
  99. package/styles/context-menu/icons/_bootstrap-dark.scss +30 -30
  100. package/styles/context-menu/icons/_bootstrap.scss +30 -30
  101. package/styles/context-menu/icons/_bootstrap4.scss +30 -30
  102. package/styles/context-menu/icons/_fabric-dark.scss +30 -30
  103. package/styles/context-menu/icons/_fabric.scss +30 -30
  104. package/styles/context-menu/icons/_highcontrast-light.scss +30 -30
  105. package/styles/context-menu/icons/_highcontrast.scss +30 -30
  106. package/styles/context-menu/icons/_material-dark.scss +30 -30
  107. package/styles/context-menu/icons/_material.scss +30 -30
  108. package/styles/context-menu/material-dark.css +0 -1
  109. package/styles/context-menu/material.css +3 -4
  110. package/styles/fabric-dark.css +7 -9
  111. package/styles/fabric.css +7 -9
  112. package/styles/h-scroll/_all.scss +2 -2
  113. package/styles/h-scroll/_bootstrap-dark-definition.scss +49 -49
  114. package/styles/h-scroll/_bootstrap-definition.scss +50 -50
  115. package/styles/h-scroll/_bootstrap4-definition.scss +49 -49
  116. package/styles/h-scroll/_fabric-dark-definition.scss +50 -50
  117. package/styles/h-scroll/_fabric-definition.scss +48 -48
  118. package/styles/h-scroll/_highcontrast-definition.scss +52 -52
  119. package/styles/h-scroll/_highcontrast-light-definition.scss +54 -54
  120. package/styles/h-scroll/_layout.scss +198 -198
  121. package/styles/h-scroll/_material-dark-definition.scss +77 -77
  122. package/styles/h-scroll/_material-definition.scss +77 -77
  123. package/styles/h-scroll/_theme.scss +157 -157
  124. package/styles/h-scroll/icons/_bootstrap-dark.scss +49 -49
  125. package/styles/h-scroll/icons/_bootstrap.scss +49 -49
  126. package/styles/h-scroll/icons/_bootstrap4.scss +49 -49
  127. package/styles/h-scroll/icons/_fabric-dark.scss +49 -49
  128. package/styles/h-scroll/icons/_fabric.scss +49 -49
  129. package/styles/h-scroll/icons/_highcontrast-light.scss +49 -49
  130. package/styles/h-scroll/icons/_highcontrast.scss +49 -49
  131. package/styles/h-scroll/icons/_material-dark.scss +49 -49
  132. package/styles/h-scroll/icons/_material.scss +49 -49
  133. package/styles/highcontrast-light.css +7 -9
  134. package/styles/highcontrast.css +7 -9
  135. package/styles/material-dark.css +7 -9
  136. package/styles/material.css +21 -14
  137. package/styles/menu/_all.scss +2 -2
  138. package/styles/menu/_bootstrap-dark-definition.scss +63 -63
  139. package/styles/menu/_bootstrap-definition.scss +65 -65
  140. package/styles/menu/_bootstrap4-definition.scss +64 -64
  141. package/styles/menu/_fabric-dark-definition.scss +63 -63
  142. package/styles/menu/_fabric-definition.scss +64 -64
  143. package/styles/menu/_highcontrast-definition.scss +65 -65
  144. package/styles/menu/_highcontrast-light-definition.scss +61 -61
  145. package/styles/menu/_layout.scss +638 -638
  146. package/styles/menu/_material-dark-definition.scss +63 -63
  147. package/styles/menu/_material-definition.scss +64 -64
  148. package/styles/menu/_theme.scss +243 -243
  149. package/styles/menu/bootstrap-dark.css +0 -1
  150. package/styles/menu/bootstrap.css +0 -1
  151. package/styles/menu/bootstrap.scss +1 -0
  152. package/styles/menu/bootstrap4.css +0 -1
  153. package/styles/menu/fabric-dark.css +0 -1
  154. package/styles/menu/fabric.css +0 -1
  155. package/styles/menu/fabric.scss +1 -0
  156. package/styles/menu/highcontrast-light.css +0 -1
  157. package/styles/menu/highcontrast.css +0 -1
  158. package/styles/menu/highcontrast.scss +1 -0
  159. package/styles/menu/icons/_bootstrap-dark.scss +127 -127
  160. package/styles/menu/icons/_bootstrap.scss +127 -127
  161. package/styles/menu/icons/_bootstrap4.scss +127 -127
  162. package/styles/menu/icons/_fabric-dark.scss +127 -127
  163. package/styles/menu/icons/_fabric.scss +127 -127
  164. package/styles/menu/icons/_highcontrast-light.scss +127 -127
  165. package/styles/menu/icons/_highcontrast.scss +127 -127
  166. package/styles/menu/icons/_material-dark.scss +127 -127
  167. package/styles/menu/icons/_material.scss +127 -127
  168. package/styles/menu/material-dark.css +0 -1
  169. package/styles/menu/material.css +2 -3
  170. package/styles/menu/material.scss +1 -0
  171. package/styles/sidebar/_all.scss +3 -3
  172. package/styles/sidebar/_bootstrap-dark-definition.scss +4 -4
  173. package/styles/sidebar/_bootstrap-definition.scss +4 -4
  174. package/styles/sidebar/_bootstrap4-definition.scss +4 -4
  175. package/styles/sidebar/_fabric-dark-definition.scss +4 -4
  176. package/styles/sidebar/_fabric-definition.scss +6 -6
  177. package/styles/sidebar/_highcontrast-definition.scss +4 -4
  178. package/styles/sidebar/_highcontrast-light-definition.scss +4 -4
  179. package/styles/sidebar/_icons.scss +1 -1
  180. package/styles/sidebar/_material-dark-definition.scss +4 -4
  181. package/styles/sidebar/_material-definition.scss +6 -6
  182. package/styles/sidebar/_theme.scss +168 -168
  183. package/styles/sidebar/bootstrap-dark.css +0 -1
  184. package/styles/sidebar/bootstrap.css +0 -1
  185. package/styles/sidebar/bootstrap4.css +0 -1
  186. package/styles/sidebar/fabric-dark.css +0 -1
  187. package/styles/sidebar/fabric.css +0 -1
  188. package/styles/sidebar/highcontrast-light.css +0 -1
  189. package/styles/sidebar/highcontrast.css +0 -1
  190. package/styles/sidebar/material-dark.css +0 -1
  191. package/styles/sidebar/material.css +0 -1
  192. package/styles/tab/_all.scss +2 -2
  193. package/styles/tab/_bootstrap-dark-definition.scss +386 -386
  194. package/styles/tab/_bootstrap-definition.scss +396 -396
  195. package/styles/tab/_bootstrap4-definition.scss +401 -401
  196. package/styles/tab/_fabric-dark-definition.scss +394 -394
  197. package/styles/tab/_fabric-definition.scss +410 -410
  198. package/styles/tab/_highcontrast-definition.scss +434 -434
  199. package/styles/tab/_highcontrast-light-definition.scss +423 -423
  200. package/styles/tab/_icons.scss +43 -43
  201. package/styles/tab/_layout.scss +3528 -3521
  202. package/styles/tab/_material-dark-definition.scss +407 -407
  203. package/styles/tab/_material-definition.scss +416 -416
  204. package/styles/tab/_theme.scss +1751 -1751
  205. package/styles/tab/bootstrap-dark.css +7 -2
  206. package/styles/tab/bootstrap.css +7 -2
  207. package/styles/tab/bootstrap4.css +7 -2
  208. package/styles/tab/fabric-dark.css +7 -2
  209. package/styles/tab/fabric.css +7 -2
  210. package/styles/tab/highcontrast-light.css +7 -2
  211. package/styles/tab/highcontrast.css +7 -2
  212. package/styles/tab/icons/_bootstrap-dark.scss +132 -132
  213. package/styles/tab/icons/_bootstrap.scss +132 -132
  214. package/styles/tab/icons/_bootstrap4.scss +132 -132
  215. package/styles/tab/icons/_fabric-dark.scss +132 -132
  216. package/styles/tab/icons/_fabric.scss +132 -132
  217. package/styles/tab/icons/_highcontrast-light.scss +132 -132
  218. package/styles/tab/icons/_highcontrast.scss +132 -132
  219. package/styles/tab/icons/_material-dark.scss +132 -132
  220. package/styles/tab/icons/_material.scss +132 -132
  221. package/styles/tab/material-dark.css +7 -2
  222. package/styles/tab/material.css +7 -2
  223. package/styles/tailwind-dark.css +0 -0
  224. package/styles/tailwind-dark.scss +0 -0
  225. package/styles/tailwind.css +0 -0
  226. package/styles/tailwind.scss +0 -0
  227. package/styles/toolbar/_all.scss +2 -2
  228. package/styles/toolbar/_bootstrap-dark-definition.scss +135 -135
  229. package/styles/toolbar/_bootstrap-definition.scss +134 -134
  230. package/styles/toolbar/_bootstrap4-definition.scss +139 -139
  231. package/styles/toolbar/_fabric-dark-definition.scss +155 -155
  232. package/styles/toolbar/_fabric-definition.scss +139 -139
  233. package/styles/toolbar/_highcontrast-definition.scss +149 -149
  234. package/styles/toolbar/_highcontrast-light-definition.scss +164 -164
  235. package/styles/toolbar/_layout.scss +1460 -1460
  236. package/styles/toolbar/_material-dark-definition.scss +180 -180
  237. package/styles/toolbar/_material-definition.scss +164 -164
  238. package/styles/toolbar/_theme.scss +451 -451
  239. package/styles/toolbar/bootstrap-dark.css +0 -1
  240. package/styles/toolbar/bootstrap.css +0 -1
  241. package/styles/toolbar/bootstrap.scss +1 -0
  242. package/styles/toolbar/bootstrap4.css +0 -1
  243. package/styles/toolbar/fabric-dark.css +0 -1
  244. package/styles/toolbar/fabric.css +0 -1
  245. package/styles/toolbar/fabric.scss +1 -0
  246. package/styles/toolbar/highcontrast-light.css +0 -1
  247. package/styles/toolbar/highcontrast.css +0 -1
  248. package/styles/toolbar/highcontrast.scss +1 -0
  249. package/styles/toolbar/icons/_bootstrap-dark.scss +16 -16
  250. package/styles/toolbar/icons/_bootstrap.scss +16 -16
  251. package/styles/toolbar/icons/_bootstrap4.scss +16 -16
  252. package/styles/toolbar/icons/_fabric-dark.scss +16 -16
  253. package/styles/toolbar/icons/_fabric.scss +16 -16
  254. package/styles/toolbar/icons/_highcontrast-light.scss +16 -16
  255. package/styles/toolbar/icons/_highcontrast.scss +16 -16
  256. package/styles/toolbar/icons/_material-dark.scss +16 -16
  257. package/styles/toolbar/icons/_material.scss +16 -16
  258. package/styles/toolbar/material-dark.css +0 -1
  259. package/styles/toolbar/material.css +0 -1
  260. package/styles/toolbar/material.scss +1 -0
  261. package/styles/treeview/_all.scss +2 -2
  262. package/styles/treeview/_bootstrap-dark-definition.scss +131 -131
  263. package/styles/treeview/_bootstrap-definition.scss +127 -127
  264. package/styles/treeview/_bootstrap4-definition.scss +153 -153
  265. package/styles/treeview/_fabric-dark-definition.scss +130 -130
  266. package/styles/treeview/_fabric-definition.scss +126 -126
  267. package/styles/treeview/_highcontrast-definition.scss +132 -132
  268. package/styles/treeview/_highcontrast-light-definition.scss +137 -137
  269. package/styles/treeview/_layout.scss +551 -551
  270. package/styles/treeview/_material-dark-definition.scss +126 -126
  271. package/styles/treeview/_material-definition.scss +126 -126
  272. package/styles/treeview/_theme.scss +331 -331
  273. package/styles/treeview/bootstrap-dark.css +0 -3
  274. package/styles/treeview/bootstrap.css +0 -3
  275. package/styles/treeview/bootstrap4.css +0 -3
  276. package/styles/treeview/fabric-dark.css +0 -3
  277. package/styles/treeview/fabric.css +0 -3
  278. package/styles/treeview/highcontrast-light.css +0 -3
  279. package/styles/treeview/highcontrast.css +0 -3
  280. package/styles/treeview/icons/_bootstrap-dark.scss +39 -39
  281. package/styles/treeview/icons/_bootstrap.scss +39 -39
  282. package/styles/treeview/icons/_bootstrap4.scss +39 -39
  283. package/styles/treeview/icons/_fabric-dark.scss +43 -43
  284. package/styles/treeview/icons/_fabric.scss +43 -43
  285. package/styles/treeview/icons/_highcontrast-light.scss +43 -43
  286. package/styles/treeview/icons/_highcontrast.scss +43 -43
  287. package/styles/treeview/icons/_material-dark.scss +43 -43
  288. package/styles/treeview/icons/_material.scss +43 -43
  289. package/styles/treeview/material-dark.css +0 -3
  290. package/styles/treeview/material.css +9 -3
  291. package/styles/v-scroll/_all.scss +2 -2
  292. package/styles/v-scroll/_bootstrap-dark-definition.scss +50 -50
  293. package/styles/v-scroll/_bootstrap-definition.scss +49 -49
  294. package/styles/v-scroll/_bootstrap4-definition.scss +49 -49
  295. package/styles/v-scroll/_fabric-dark-definition.scss +51 -51
  296. package/styles/v-scroll/_fabric-definition.scss +50 -50
  297. package/styles/v-scroll/_highcontrast-definition.scss +51 -51
  298. package/styles/v-scroll/_highcontrast-light-definition.scss +52 -52
  299. package/styles/v-scroll/_layout.scss +162 -162
  300. package/styles/v-scroll/_material-dark-definition.scss +78 -78
  301. package/styles/v-scroll/_material-definition.scss +77 -77
  302. package/styles/v-scroll/_theme.scss +133 -133
  303. package/styles/v-scroll/icons/_bootstrap-dark.scss +26 -26
  304. package/styles/v-scroll/icons/_bootstrap.scss +26 -26
  305. package/styles/v-scroll/icons/_bootstrap4.scss +26 -26
  306. package/styles/v-scroll/icons/_fabric-dark.scss +26 -26
  307. package/styles/v-scroll/icons/_fabric.scss +26 -26
  308. package/styles/v-scroll/icons/_highcontrast-light.scss +26 -26
  309. package/styles/v-scroll/icons/_highcontrast.scss +26 -26
  310. package/styles/v-scroll/icons/_material-dark.scss +26 -26
  311. package/styles/v-scroll/icons/_material.scss +26 -26
  312. package/tslint.json +111 -0
  313. package/.gitlab/merge_request_templates/Bug.md +0 -63
  314. package/.gitlab/merge_request_templates/feature.md +0 -39
@@ -0,0 +1,2131 @@
1
+ import { Component, Property, ChildProperty, NotifyPropertyChanges, INotifyPropertyChanged, AnimationModel } from '@syncfusion/ej2-base';
2
+ import { Event, EventHandler, EmitType, BaseEventArgs, KeyboardEvents, KeyboardEventArgs, Touch, TapEventArgs } from '@syncfusion/ej2-base';
3
+ import { attributes, Animation, AnimationOptions, TouchEventArgs, MouseEventArgs } from '@syncfusion/ej2-base';
4
+ import { Browser, Collection, setValue, getValue, getUniqueID, getInstance, isNullOrUndefined } from '@syncfusion/ej2-base';
5
+ import { select, selectAll, closest, detach, append, rippleEffect, isVisible, Complex, addClass, removeClass } from '@syncfusion/ej2-base';
6
+ import { ListBase, ListBaseOptions } from '@syncfusion/ej2-lists';
7
+ import { getZindexPartial, calculatePosition, OffsetPosition, isCollide, flip, fit, Popup } from '@syncfusion/ej2-popups';
8
+ import { updateBlazorTemplate, resetBlazorTemplate, blazorTemplates, extend } from '@syncfusion/ej2-base';
9
+ import { getScrollableParent } from '@syncfusion/ej2-popups';
10
+ import { MenuItemModel, MenuBaseModel, FieldSettingsModel, MenuAnimationSettingsModel } from './menu-base-model';
11
+ import { HScroll } from '../common/h-scroll';
12
+ import { VScroll } from '../common/v-scroll';
13
+
14
+ type objColl = { [key: string]: Object }[];
15
+ type obj = { [key: string]: Object };
16
+
17
+ const ENTER: string = 'enter';
18
+
19
+ const ESCAPE: string = 'escape';
20
+
21
+ const FOCUSED: string = 'e-focused';
22
+
23
+ const HEADER: string = 'e-menu-header';
24
+
25
+ const SELECTED: string = 'e-selected';
26
+
27
+ const SEPARATOR: string = 'e-separator';
28
+
29
+ const UPARROW: string = 'uparrow';
30
+
31
+ const DOWNARROW: string = 'downarrow';
32
+
33
+ const LEFTARROW: string = 'leftarrow';
34
+
35
+ const RIGHTARROW: string = 'rightarrow';
36
+
37
+ const HOME: string = 'home';
38
+
39
+ const END: string = 'end';
40
+
41
+ const CARET: string = 'e-caret';
42
+
43
+ const ITEM: string = 'e-menu-item';
44
+
45
+ const DISABLED: string = 'e-disabled';
46
+
47
+ const HIDE: string = 'e-menu-hide';
48
+
49
+ const ICONS: string = 'e-icons';
50
+
51
+ const RTL: string = 'e-rtl';
52
+
53
+ const POPUP: string = 'e-menu-popup';
54
+
55
+ const TEMPLATE_PROPERTY: string = 'Template';
56
+ /**
57
+ * Menu animation effects
58
+ */
59
+ export type MenuEffect = 'None' | 'SlideDown' | 'ZoomIn' | 'FadeIn';
60
+
61
+ /**
62
+ * Configures the field options of the Menu.
63
+ */
64
+ export class FieldSettings extends ChildProperty<FieldSettings> {
65
+
66
+ /**
67
+ * Specifies the itemId field for Menu item.
68
+ * @default 'id'
69
+ */
70
+ @Property('id')
71
+ public itemId: string | string[];
72
+
73
+ /**
74
+ * Specifies the parentId field for Menu item.
75
+ * @default 'parentId'
76
+ */
77
+ @Property('parentId')
78
+ public parentId: string | string[];
79
+
80
+ /**
81
+ * Specifies the text field for Menu item.
82
+ * @default 'text'
83
+ */
84
+ @Property('text')
85
+ public text: string | string[];
86
+
87
+ /**
88
+ * Specifies the css icon field for Menu item.
89
+ * @default 'iconCss'
90
+ */
91
+ @Property('iconCss')
92
+ public iconCss: string | string[];
93
+
94
+ /**
95
+ * Specifies the Url field for Menu item.
96
+ * @default 'url'
97
+ */
98
+ @Property('url')
99
+ public url: string | string[];
100
+
101
+ /**
102
+ * Specifies the separator field for Menu item.
103
+ * @default 'separator'
104
+ */
105
+ @Property('separator')
106
+ public separator: string | string[];
107
+
108
+ /**
109
+ * Specifies the children field for Menu item.
110
+ * @default 'items'
111
+ */
112
+ @Property('items')
113
+ public children: string | string[];
114
+ }
115
+
116
+ export interface BrowserDetails {
117
+ isAndroid?: boolean;
118
+ isDevice?: boolean;
119
+ isIE?: boolean;
120
+ isIos?: boolean;
121
+ isIos7?: boolean;
122
+ isMSPointer?: boolean;
123
+ isPointer?: boolean;
124
+ isTouch?: boolean;
125
+ isWebView?: boolean;
126
+ isWindows?: boolean;
127
+ touchStartEvent?: string;
128
+ touchMoveEvent?: string;
129
+ touchEndEvent?: string;
130
+ touchCancelEvent?: string;
131
+ }
132
+ interface MyWindow extends Window {
133
+ browserDetails: BrowserDetails;
134
+ cordova: Object;
135
+ PhoneGap: Object;
136
+ phonegap: Object;
137
+ forge: Object;
138
+ }
139
+ declare let window: MyWindow;
140
+ const REGX_IOS: RegExp = /(ipad|iphone|ipod touch)/i;
141
+ const REGX_BROWSER: { [key: string]: RegExp } = {
142
+ OPERA: /(opera|opr)(?:.*version|)[ /]([\w.]+)/i,
143
+ EDGE: /(edge)(?:.*version|)[ /]([\w.]+)/i,
144
+ CHROME: /(chrome|crios)[ /]([\w.]+)/i,
145
+ PANTHOMEJS: /(phantomjs)[ /]([\w.]+)/i,
146
+ SAFARI: /(safari)[ /]([\w.]+)/i,
147
+ WEBKIT: /(webkit)[ /]([\w.]+)/i,
148
+ MSIE: /(msie|trident) ([\w.]+)/i,
149
+ MOZILLA: /(mozilla)(?:.*? rv:([\w.]+)|)/i
150
+ };
151
+
152
+ /**
153
+ * Specifies menu items.
154
+ */
155
+ export class MenuItem extends ChildProperty<MenuItem> {
156
+ /**
157
+ * Defines class/multiple classes separated by a space for the menu Item that is used to include an icon.
158
+ * Menu Item can include font icon and sprite image.
159
+ * @default null
160
+ */
161
+ @Property(null)
162
+ public iconCss: string;
163
+
164
+ /**
165
+ * Specifies the id for menu item.
166
+ * @default ''
167
+ */
168
+ @Property('')
169
+ public id: string;
170
+
171
+ /**
172
+ * Specifies separator between the menu items. Separator are either horizontal or vertical lines used to group menu items.
173
+ * @default false
174
+ */
175
+ @Property(false)
176
+ public separator: boolean;
177
+
178
+ /**
179
+ * Specifies the sub menu items that is the array of MenuItem model.
180
+ * @default []
181
+ */
182
+ @Collection<MenuItemModel>([], MenuItem)
183
+ public items: MenuItemModel[];
184
+
185
+ /**
186
+ * Specifies text for menu item.
187
+ * @default ''
188
+ */
189
+ @Property('')
190
+ public text: string;
191
+
192
+ /**
193
+ * Specifies url for menu item that creates the anchor link to navigate to the url provided.
194
+ * @default ''
195
+ */
196
+ @Property('')
197
+ public url: string;
198
+ }
199
+
200
+ /**
201
+ * Animation configuration settings.
202
+ */
203
+ export class MenuAnimationSettings extends ChildProperty<MenuAnimationSettings> {
204
+ /**
205
+ * Specifies the effect that shown in the sub menu transform.
206
+ * The possible effects are:
207
+ * * None: Specifies the sub menu transform with no animation effect.
208
+ * * SlideDown: Specifies the sub menu transform with slide down effect.
209
+ * * ZoomIn: Specifies the sub menu transform with zoom in effect.
210
+ * * FadeIn: Specifies the sub menu transform with fade in effect.
211
+ * @default 'SlideDown'
212
+ * @aspType Syncfusion.EJ2.Navigations.MenuEffect
213
+ * @blazorType Syncfusion.EJ2.Navigations.MenuEffect
214
+ * @isEnumeration true
215
+ */
216
+ @Property('SlideDown')
217
+ public effect: MenuEffect;
218
+ /**
219
+ * Specifies the time duration to transform object.
220
+ * @default 400
221
+ */
222
+ @Property(400)
223
+ public duration: number;
224
+ /**
225
+ * Specifies the easing effect applied while transform.
226
+ * @default 'ease'
227
+ */
228
+ @Property('ease')
229
+ public easing: string;
230
+ }
231
+
232
+ /**
233
+ * @private
234
+ * Base class for Menu and ContextMenu components.
235
+ */
236
+ @NotifyPropertyChanges
237
+ export abstract class MenuBase extends Component<HTMLUListElement> implements INotifyPropertyChanged {
238
+ private clonedElement: HTMLElement;
239
+ private targetElement: HTMLElement;
240
+ private delegateClickHandler: Function;
241
+ private delegateMoverHandler: Function;
242
+ private delegateMouseDownHandler: Function;
243
+ private navIdx: number[] = [];
244
+ private animation: Animation = new Animation({});
245
+ private isTapHold: boolean = false;
246
+ protected isMenu: boolean;
247
+ protected hamburgerMode: boolean;
248
+ protected title: string;
249
+ private rippleFn: Function;
250
+ private uList: HTMLElement;
251
+ private lItem: Element;
252
+ private popupObj: Popup;
253
+ private popupWrapper: HTMLElement;
254
+ private isNestedOrVertical: boolean;
255
+ private top: number;
256
+ private left: number;
257
+ private keyType: string;
258
+ private showSubMenu: boolean;
259
+ private action: string;
260
+ private cli: Element;
261
+ private cliIdx: number;
262
+ private isClosed: boolean;
263
+ private liTrgt: Element;
264
+ private isMenusClosed: boolean;
265
+ private isCMenu: boolean;
266
+ private pageX: number;
267
+ private pageY: number;
268
+ private tempItem: objColl = [];
269
+ /**
270
+      * Triggers while rendering each menu item.
271
+      * @event
272
+ * @blazorProperty 'OnItemRender'
273
+      */
274
+ @Event()
275
+ public beforeItemRender: EmitType<MenuEventArgs>;
276
+
277
+ /**
278
+      * Triggers before opening the menu item.
279
+      * @event
280
+ * @blazorProperty 'OnOpen'
281
+      */
282
+ @Event()
283
+ public beforeOpen: EmitType<BeforeOpenCloseMenuEventArgs>;
284
+
285
+ /**
286
+      * Triggers while opening the menu item.
287
+      * @event
288
+ * @blazorProperty 'Opened'
289
+      */
290
+ @Event()
291
+ public onOpen: EmitType<OpenCloseMenuEventArgs>;
292
+
293
+ /**
294
+      * Triggers before closing the menu.
295
+      * @event
296
+ * @blazorProperty 'OnClose'
297
+      */
298
+ @Event()
299
+ public beforeClose: EmitType<BeforeOpenCloseMenuEventArgs>;
300
+
301
+ /**
302
+      * Triggers while closing the menu.
303
+      * @event
304
+ * @blazorProperty 'Closed'
305
+      */
306
+ @Event()
307
+ public onClose: EmitType<OpenCloseMenuEventArgs>;
308
+
309
+ /**
310
+      * Triggers while selecting menu item.
311
+      * @event
312
+ * @blazorProperty 'ItemSelected'
313
+      */
314
+ @Event()
315
+ public select: EmitType<MenuEventArgs>;
316
+
317
+ /**
318
+ * Triggers once the component rendering is completed.
319
+ * @event
320
+ * @blazorProperty 'Created'
321
+ */
322
+ @Event()
323
+ public created: EmitType<Event>;
324
+
325
+ /**
326
+ * Defines class/multiple classes separated by a space in the Menu wrapper.
327
+ * @default ''
328
+ */
329
+ @Property('')
330
+ public cssClass: string;
331
+
332
+ /**
333
+ * Specifies whether to show the sub menu or not on click.
334
+ * When set to true, the sub menu will open only on mouse click.
335
+ * @default false
336
+ */
337
+ @Property(false)
338
+ public showItemOnClick: boolean;
339
+
340
+ /**
341
+ * Specifies target element selector in which the ContextMenu should be opened.
342
+ * Specifies target element to open/close Menu while click in Hamburger mode.
343
+ * @default ''
344
+ * @private
345
+ */
346
+ @Property('')
347
+ public target: string;
348
+
349
+ /**
350
+ * Specifies the filter selector for elements inside the target in that the context menu will be opened.
351
+ * Not applicable to Menu component.
352
+ * @default ''
353
+ * @private
354
+ */
355
+ @Property('')
356
+ public filter: string;
357
+
358
+ /**
359
+ * Specifies the template for Menu item.
360
+ * Not applicable to ContextMenu component.
361
+ * @default null
362
+ * @private
363
+ */
364
+ @Property(null)
365
+ public template: string;
366
+
367
+ /**
368
+ * Specifies whether to enable / disable the scrollable option in Menu.
369
+ * Not applicable to ContextMenu component.
370
+ * @default false
371
+ * @private
372
+ */
373
+ @Property(false)
374
+ public enableScrolling: boolean;
375
+
376
+ /**
377
+ * Specifies mapping fields from the dataSource.
378
+ * Not applicable to ContextMenu component.
379
+ * @default { itemId: "id", text: "text", parentId: "parentId", iconCss: "iconCss", url: "url", separator: "separator",
380
+ * children: "items" }
381
+ * @private
382
+ */
383
+ @Complex<FieldSettingsModel>({}, FieldSettings)
384
+ public fields: FieldSettingsModel;
385
+
386
+ /**
387
+ * Specifies menu items with its properties which will be rendered as Menu.
388
+ * @default []
389
+ */
390
+ @Collection<MenuItemModel>([], MenuItem)
391
+ public items: MenuItemModel[] | { [key: string]: Object }[];
392
+
393
+ /**
394
+ * Specifies the animation settings for the sub menu open.
395
+ * @default { duration: 400, easing: 'ease', effect: 'SlideDown' }
396
+ */
397
+ @Complex<MenuAnimationSettingsModel>({}, MenuAnimationSettings)
398
+ public animationSettings: MenuAnimationSettingsModel;
399
+
400
+ /**
401
+ * Constructor for creating the widget.
402
+ * @private
403
+ */
404
+ constructor(options?: MenuBaseModel, element?: string | HTMLUListElement) {
405
+ super(options, <HTMLUListElement | string>element);
406
+ }
407
+
408
+ /**
409
+ * Initialized third party configuration settings.
410
+ * @private
411
+ */
412
+ protected preRender(): void {
413
+ if (!this.isMenu) {
414
+ let ul: HTMLUListElement;
415
+ if (this.element.tagName === 'EJS-CONTEXTMENU') {
416
+ ul = this.createElement('ul', {
417
+ id: getUniqueID(this.getModuleName()), className: 'e-control e-lib e-' + this.getModuleName() }) as HTMLUListElement;
418
+ let ejInst: Object = getValue('ej2_instances', this.element);
419
+ removeClass([this.element], ['e-control', 'e-lib', 'e-' + this.getModuleName()]);
420
+ this.clonedElement = this.element; this.element = ul;
421
+ setValue('ej2_instances', ejInst, this.element);
422
+ } else {
423
+ ul = this.createElement('ul', { id: getUniqueID(this.getModuleName()) }) as HTMLUListElement;
424
+ append([].slice.call((this.element.cloneNode(true) as Element).children), ul);
425
+ let refEle: Element = this.element.nextElementSibling;
426
+ refEle ? this.element.parentElement.insertBefore(ul, refEle) : this.element.parentElement.appendChild(ul);
427
+ this.clonedElement = ul;
428
+ }
429
+ this.clonedElement.style.display = 'none';
430
+ }
431
+ if (this.element.tagName === 'EJS-MENU') {
432
+ let ele: Element = this.element;
433
+ let ejInstance: Object = getValue('ej2_instances', ele);
434
+ let ul: Element = this.createElement('ul');
435
+ let wrapper: HTMLElement = this.createElement('EJS-MENU', { className: 'e-' + this.getModuleName() + '-wrapper' });
436
+ for (let idx: number = 0, len: number = ele.attributes.length; idx < len; idx++) {
437
+ ul.setAttribute(ele.attributes[idx].nodeName, ele.attributes[idx].nodeValue);
438
+ }
439
+ ele.parentNode.insertBefore(wrapper, ele);
440
+ detach(ele);
441
+ ele = ul;
442
+ wrapper.appendChild(ele);
443
+ setValue('ej2_instances', ejInstance, ele);
444
+ this.clonedElement = wrapper;
445
+ this.element = ele as HTMLUListElement;
446
+ if (!this.element.id) {
447
+ this.element.id = getUniqueID(this.getModuleName());
448
+ }
449
+ }
450
+ }
451
+
452
+ /**
453
+ * Initialize the control rendering
454
+ * @private
455
+ */
456
+ protected render(): void {
457
+ this.initialize();
458
+ this.renderItems();
459
+ if (this.isMenu && this.template && this.isBlazor()) {
460
+ let menuTemplateId: string = this.element.id + TEMPLATE_PROPERTY;
461
+ resetBlazorTemplate(menuTemplateId, TEMPLATE_PROPERTY);
462
+ if (Object.keys(blazorTemplates).length) {
463
+ extend(this.tempItem, (<{ [key: string]: Object }>blazorTemplates)[menuTemplateId], [], true);
464
+ }
465
+ updateBlazorTemplate(menuTemplateId, TEMPLATE_PROPERTY, this);
466
+ }
467
+ this.wireEvents();
468
+ this.renderComplete();
469
+ }
470
+
471
+ protected initialize(): void {
472
+ let wrapper: Element = this.getWrapper();
473
+ if (!wrapper) {
474
+ wrapper = this.createElement('div', { className: 'e-' + this.getModuleName() + '-wrapper' });
475
+ if (this.isMenu) {
476
+ this.element.parentElement.insertBefore(wrapper, this.element);
477
+ } else {
478
+ document.body.appendChild(wrapper);
479
+ }
480
+ }
481
+ if (this.cssClass) {
482
+ addClass([wrapper], this.cssClass.split(' '));
483
+ }
484
+ if (this.enableRtl) {
485
+ wrapper.classList.add(RTL);
486
+ }
487
+ wrapper.appendChild(this.element);
488
+ if (this.isMenu && this.hamburgerMode) {
489
+ if (!this.target) {
490
+ this.createHeaderContainer(wrapper);
491
+ }
492
+ }
493
+ }
494
+
495
+ private renderItems(): void {
496
+ if (!(this.items as objColl).length) {
497
+ let items: { [key: string]: Object; }[] = ListBase.createJsonFromElement(this.element, { fields: { child: 'items' } });
498
+ this.setProperties({ items: items }, true);
499
+ if (this.isBlazor()) {
500
+ this.element = this.removeChildElement(this.element);
501
+ } else {
502
+ this.element.innerHTML = '';
503
+ }
504
+ }
505
+ let ul: Element = this.createItems(this.items as objColl);
506
+ append(Array.prototype.slice.call(ul.children), this.element);
507
+ this.element.classList.add('e-menu-parent');
508
+ let wrapper: HTMLElement = this.getWrapper() as HTMLElement;
509
+ this.element.classList.contains('e-vertical') ?
510
+ this.addScrolling(wrapper, this.element, 'vscroll', wrapper.offsetHeight, this.element.offsetHeight)
511
+ : this.addScrolling(wrapper, this.element, 'hscroll', wrapper.offsetWidth, this.element.offsetWidth);
512
+ }
513
+
514
+ protected wireEvents(): void {
515
+ let wrapper: HTMLElement = this.getWrapper() as HTMLElement;
516
+ if (this.target) {
517
+ let target: HTMLElement;
518
+ let targetElems: HTMLElement[] = selectAll(this.target);
519
+ for (let i: number = 0, len: number = targetElems.length; i < len; i++) {
520
+ target = targetElems[i];
521
+ if (this.isMenu) {
522
+ EventHandler.add(target, 'click', this.menuHeaderClickHandler, this);
523
+ } else {
524
+ if (this.menuIos()) {
525
+ new Touch(target, { tapHold: this.touchHandler.bind(this) });
526
+ } else {
527
+ EventHandler.add(target, 'contextmenu', this.cmenuHandler, this);
528
+ }
529
+ }
530
+ }
531
+ this.targetElement = target;
532
+ if (!this.isMenu) {
533
+ EventHandler.add(this.targetElement, 'scroll', this.scrollHandler, this);
534
+ for (let parent of getScrollableParent(this.targetElement)) {
535
+ EventHandler.add(parent, 'scroll', this.scrollHandler, this);
536
+ }
537
+ }
538
+ }
539
+ if (!Browser.isDevice) {
540
+ this.delegateMoverHandler = this.moverHandler.bind(this);
541
+ this.delegateMouseDownHandler = this.mouseDownHandler.bind(this);
542
+ EventHandler.add(this.isMenu ? document : wrapper, 'mouseover', this.delegateMoverHandler, this);
543
+ EventHandler.add(document, 'mousedown', this.delegateMouseDownHandler, this);
544
+ }
545
+ this.delegateClickHandler = this.clickHandler.bind(this);
546
+ EventHandler.add(document, 'click', this.delegateClickHandler, this);
547
+ this.wireKeyboardEvent(wrapper);
548
+ this.rippleFn = rippleEffect(wrapper, { selector: '.' + ITEM });
549
+ }
550
+
551
+ private menuIos(): boolean {
552
+ return <boolean>this.getValue('isIos', REGX_IOS);
553
+ }
554
+
555
+ private getValue(key: string, regX: RegExp): Object {
556
+ const browserDetails: any = typeof window !== 'undefined' ? window.browserDetails : {};
557
+ if (typeof navigator !== 'undefined' && navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1 && Browser.isTouch === true && !REGX_BROWSER.CHROME.test(navigator.userAgent)) {
558
+ browserDetails['isIos'] = true;
559
+ browserDetails['isDevice'] = true;
560
+ browserDetails['isTouch'] = true;
561
+ browserDetails['isPointer'] = true;
562
+ }
563
+ if ('undefined' === typeof (<{ [key: string]: Object }>browserDetails)[`${key}`]) {
564
+ return (<{ [key: string]: Object }>browserDetails)[`${key}`] = regX.test(Browser.userAgent);
565
+ }
566
+ return (<{ [key: string]: Object }>browserDetails)[`${key}`];
567
+ }
568
+
569
+ private wireKeyboardEvent(element: HTMLElement): void {
570
+ let keyConfigs: { [key: string]: string; } = {
571
+ downarrow: DOWNARROW,
572
+ uparrow: UPARROW,
573
+ enter: ENTER,
574
+ leftarrow: LEFTARROW,
575
+ rightarrow: RIGHTARROW,
576
+ escape: ESCAPE
577
+ };
578
+ if (this.isMenu) {
579
+ keyConfigs.home = HOME;
580
+ keyConfigs.end = END;
581
+ }
582
+ new KeyboardEvents(element, {
583
+ keyAction: this.keyBoardHandler.bind(this),
584
+ keyConfigs: keyConfigs
585
+ });
586
+ }
587
+
588
+ private mouseDownHandler(e: MouseEvent): void {
589
+ if (closest(e.target as Element, '.e-' + this.getModuleName() + '-wrapper') !== this.getWrapper()
590
+ && (!closest(e.target as Element, '.e-' + this.getModuleName() + '-popup'))) {
591
+ this.closeMenu(this.isMenu ? null : this.navIdx.length, e);
592
+ }
593
+ }
594
+
595
+ private keyBoardHandler(e: KeyboardEventArgs): void {
596
+ let actionName: string = '';
597
+ let trgt: Element = e.target as Element;
598
+ let actionNeeded: boolean = this.isMenu && !this.hamburgerMode && !this.element.classList.contains('e-vertical')
599
+ && this.navIdx.length < 1;
600
+ e.preventDefault();
601
+ if (this.enableScrolling && e.keyCode === 13 && trgt.classList.contains('e-scroll-nav')) {
602
+ this.removeLIStateByClass([FOCUSED, SELECTED], [closest(trgt, '.e-' + this.getModuleName() + '-wrapper')]);
603
+ }
604
+ if (actionNeeded) {
605
+ switch (e.action) {
606
+ case RIGHTARROW:
607
+ actionName = RIGHTARROW;
608
+ e.action = DOWNARROW;
609
+ break;
610
+ case LEFTARROW:
611
+ actionName = LEFTARROW;
612
+ e.action = UPARROW;
613
+ break;
614
+ case DOWNARROW:
615
+ actionName = DOWNARROW;
616
+ e.action = RIGHTARROW;
617
+ break;
618
+ case UPARROW:
619
+ actionName = UPARROW;
620
+ e.action = '';
621
+ break;
622
+ }
623
+ } else if (this.enableRtl) {
624
+ switch (e.action) {
625
+ case LEFTARROW:
626
+ actionNeeded = true;
627
+ actionName = LEFTARROW;
628
+ e.action = RIGHTARROW;
629
+ break;
630
+ case RIGHTARROW:
631
+ actionNeeded = true;
632
+ actionName = RIGHTARROW;
633
+ e.action = LEFTARROW;
634
+ break;
635
+ }
636
+ }
637
+ switch (e.action) {
638
+ case DOWNARROW:
639
+ case UPARROW:
640
+ case END:
641
+ case HOME:
642
+ this.upDownKeyHandler(e);
643
+ break;
644
+ case RIGHTARROW:
645
+ this.rightEnterKeyHandler(e);
646
+ break;
647
+ case LEFTARROW:
648
+ this.leftEscKeyHandler(e);
649
+ break;
650
+ case ENTER:
651
+ if (this.hamburgerMode && trgt.tagName === 'SPAN' && trgt.classList.contains('e-menu-icon')) {
652
+ this.menuHeaderClickHandler(e);
653
+ } else {
654
+ this.rightEnterKeyHandler(e);
655
+ }
656
+ break;
657
+ case ESCAPE:
658
+ this.leftEscKeyHandler(e);
659
+ break;
660
+ }
661
+ if (actionNeeded) {
662
+ e.action = actionName;
663
+ }
664
+ }
665
+
666
+ private upDownKeyHandler(e: KeyboardEventArgs): void {
667
+ let cul: Element = this.getUlByNavIdx();
668
+ let defaultIdx: number = (e.action === DOWNARROW || e.action === HOME) ? 0 : cul.childElementCount - 1;
669
+ let fliIdx: number = defaultIdx;
670
+ let fli: Element = this.getLIByClass(cul, FOCUSED);
671
+ if (fli) {
672
+ if (e.action !== END && e.action !== HOME) {
673
+ fliIdx = this.getIdx(cul, fli);
674
+ }
675
+ fli.classList.remove(FOCUSED);
676
+ if (e.action !== END && e.action !== HOME) {
677
+ e.action === DOWNARROW ? fliIdx++ : fliIdx--;
678
+ if (fliIdx === (e.action === DOWNARROW ? cul.childElementCount : -1)) {
679
+ fliIdx = defaultIdx;
680
+ }
681
+ }
682
+ }
683
+ let cli: Element = cul.children[fliIdx];
684
+ fliIdx = this.isValidLI(cli, fliIdx, e.action);
685
+ cul.children[fliIdx].classList.add(FOCUSED);
686
+ (cul.children[fliIdx] as HTMLElement).focus();
687
+ }
688
+
689
+ private isValidLI(cli: Element, index: number, action: string): number {
690
+ let wrapper: Element = this.getWrapper();
691
+ let cul: Element = this.getUlByNavIdx();
692
+ if (cli.classList.contains(SEPARATOR) || cli.classList.contains(DISABLED) || cli.classList.contains(HIDE)) {
693
+ ((action === DOWNARROW) || (action === RIGHTARROW)) ? index++ : index--;
694
+ }
695
+ cli = cul.children[index];
696
+ if (cli.classList.contains(SEPARATOR) || cli.classList.contains(DISABLED) || cli.classList.contains(HIDE)) {
697
+ index = this.isValidLI(cli, index, action);
698
+ }
699
+ return index;
700
+ }
701
+
702
+ private getUlByNavIdx(navIdxLen: number = this.navIdx.length): HTMLElement {
703
+ if (this.isMenu) {
704
+ let popup: Element = [this.getWrapper()].concat([].slice.call(selectAll('.' + POPUP)))[navIdxLen];
705
+ return isNullOrUndefined(popup) ? null : select('.e-menu-parent', popup) as HTMLElement;
706
+ } else {
707
+ return this.getWrapper().children[navIdxLen] as HTMLElement;
708
+ }
709
+ }
710
+
711
+ private rightEnterKeyHandler(e: KeyboardEventArgs): void {
712
+ let eventArgs: MenuEventArgs;
713
+ let cul: Element = this.getUlByNavIdx();
714
+ let fli: Element = this.getLIByClass(cul, FOCUSED);
715
+ if (fli) {
716
+ let fliIdx: number = this.getIdx(cul, fli);
717
+ let navIdx: number[] = this.navIdx.concat(fliIdx);
718
+ let index: number;
719
+ let item: MenuItemModel = this.getItem(navIdx);
720
+ if (item.items.length) {
721
+ this.navIdx.push(fliIdx);
722
+ this.keyType = 'right';
723
+ this.action = e.action;
724
+ this.openMenu(fli, item, null, null, e);
725
+ } else {
726
+ if (e.action === ENTER) {
727
+ if (this.isMenu && this.navIdx.length === 0) {
728
+ this.removeLIStateByClass([SELECTED], [this.getWrapper()]);
729
+ } else {
730
+ fli.classList.remove(FOCUSED);
731
+ }
732
+ fli.classList.add(SELECTED);
733
+ eventArgs = { element: fli as HTMLElement, item: item, event: e };
734
+ this.trigger('select', eventArgs);
735
+ this.closeMenu(null, e);
736
+ }
737
+ }
738
+ }
739
+ }
740
+
741
+ private leftEscKeyHandler(e: KeyboardEventArgs): void {
742
+ if (this.navIdx.length) {
743
+ this.keyType = 'left';
744
+ this.closeMenu(this.navIdx.length, e);
745
+ } else {
746
+ if (e.action === ESCAPE) {
747
+ this.closeMenu(null, e);
748
+ }
749
+ }
750
+ }
751
+
752
+ private scrollHandler(e: MouseEvent): void {
753
+ this.closeMenu(null, e);
754
+ }
755
+
756
+ private touchHandler(e: TapEventArgs): void {
757
+ this.isTapHold = true;
758
+ this.cmenuHandler(e.originalEvent);
759
+ }
760
+
761
+ private cmenuHandler(e: MouseEvent & (TouchEventArgs | MouseEventArgs)): void {
762
+ e.preventDefault();
763
+ this.isCMenu = true;
764
+ this.pageX = e.changedTouches ? e.changedTouches[0].pageX + 1 : e.pageX + 1;
765
+ this.pageY = e.changedTouches ? e.changedTouches[0].pageY + 1 : e.pageY + 1;
766
+ this.closeMenu(null, e);
767
+ if (this.isCMenu) {
768
+ if (this.canOpen(e.target as Element)) {
769
+ this.openMenu(null, null, this.pageY, this.pageX, e);
770
+ }
771
+ this.isCMenu = false;
772
+ }
773
+ }
774
+
775
+ protected closeMenu(ulIndex: number = 0, e: MouseEvent | KeyboardEvent = null): void {
776
+ if (this.isMenuVisible()) {
777
+ let sli: Element;
778
+ let ul: HTMLElement;
779
+ let item: MenuItemModel;
780
+ let items: MenuItemModel[];
781
+ let beforeCloseArgs: BeforeOpenCloseMenuEventArgs;
782
+ let wrapper: Element = this.getWrapper();
783
+ let popups: Element[] = this.getPopups();
784
+ let isClose: boolean = false;
785
+ let cnt: number = this.isMenu ? popups.length + 1 : wrapper.childElementCount;
786
+ ul = this.isMenu && cnt !== 1 ? select('.e-ul', popups[cnt - 2]) as HTMLElement
787
+ : selectAll('.e-menu-parent', wrapper)[cnt - 1] as HTMLElement;
788
+ if (this.isMenu && ul.classList.contains('e-menu')) {
789
+ sli = this.getLIByClass(ul, SELECTED);
790
+ if (sli) {
791
+ sli.classList.remove(SELECTED);
792
+ }
793
+ isClose = true;
794
+ }
795
+ if (!isClose) {
796
+ item = this.navIdx.length ? this.getItem(this.navIdx) : null;
797
+ items = item ? item.items : this.items as objColl;
798
+ beforeCloseArgs = { element: ul, parentItem: item, items: items, event: e, cancel: false };
799
+ this.trigger('beforeClose', beforeCloseArgs, (observedCloseArgs: BeforeOpenCloseMenuEventArgs) => {
800
+ let popupEle: HTMLElement; let closeArgs: OpenCloseMenuEventArgs;
801
+ let popupObj: Popup; let isOpen: boolean = !observedCloseArgs.cancel;
802
+ if (isOpen || this.isCMenu) {
803
+ if (this.isMenu) {
804
+ popupEle = closest(ul, '.' + POPUP) as HTMLElement;
805
+ if (this.hamburgerMode) {
806
+ popupEle.parentElement.style.minHeight = '';
807
+ }
808
+ this.unWireKeyboardEvent(popupEle);
809
+ this.destroyScrollObj(
810
+ getInstance(popupEle.children[0] as HTMLElement, VScroll) as VScroll, popupEle.children[0]);
811
+ popupObj = getInstance(popupEle, Popup) as Popup;
812
+ popupObj.hide();
813
+ popupObj.destroy();
814
+ detach(popupEle);
815
+ } else {
816
+ this.toggleAnimation(ul, false);
817
+ }
818
+ closeArgs = { element: ul, parentItem: item, items: items };
819
+ this.trigger('onClose', closeArgs);
820
+ this.navIdx.pop();
821
+ }
822
+ if (this.isCMenu) {
823
+ if (this.canOpen(e.target as Element)) {
824
+ this.openMenu(null, null, this.pageY, this.pageX, e);
825
+ }
826
+ this.isCMenu = false;
827
+ } else if (isOpen && this.hamburgerMode && ulIndex !== null) {
828
+ this.afterCloseMenu(e as MouseEvent);
829
+ } else if (isOpen && !ulIndex && this.navIdx.length) {
830
+ this.closeMenu(null, e);
831
+ } else if (isOpen && !this.isMenu && !ulIndex && this.navIdx.length === 0 && !this.isMenusClosed) {
832
+ this.isMenusClosed = true;
833
+ this.closeMenu(0, e);
834
+ } else if (isOpen && this.isMenu && e && e.target &&
835
+ this.navIdx.length !== 0 && closest(e.target as Element, '.e-menu-parent.e-control')) {
836
+ this.closeMenu(0, e);
837
+ } else {
838
+ if (isOpen && (this.keyType === 'right' || this.keyType === 'click')) {
839
+ this.afterCloseMenu(e as MouseEvent);
840
+ } else {
841
+ let cul: Element = this.getUlByNavIdx();
842
+ let sli: Element = this.getLIByClass(cul, SELECTED);
843
+ if (sli) {
844
+ sli.setAttribute('aria-expanded', 'false');
845
+ sli.classList.remove(SELECTED);
846
+ sli.classList.add(FOCUSED);
847
+ (sli as HTMLElement).focus();
848
+ }
849
+ }
850
+ }
851
+ this.removeStateWrapper();
852
+ });
853
+ }
854
+ }
855
+ }
856
+ private destroyScrollObj(scrollObj: VScroll | HScroll, scrollEle: Element): void {
857
+ if (scrollObj) {
858
+ scrollObj.destroy();
859
+ scrollEle.parentElement.appendChild(select('.e-menu-parent', scrollEle));
860
+ detach(scrollEle);
861
+ }
862
+ }
863
+
864
+ private getPopups(): Element[] {
865
+ let popups: Element[] = [];
866
+ [].slice.call(document.querySelectorAll('.' + POPUP)).forEach((elem: Element) => {
867
+ if (this.getIndex(elem.querySelector('.' + ITEM).id, true).length) {
868
+ popups.push(elem);
869
+ }
870
+ });
871
+ return popups;
872
+ }
873
+
874
+ private isMenuVisible(): boolean {
875
+ return (this.navIdx.length > 0 || (this.element.classList.contains('e-contextmenu') && isVisible(this.element).valueOf()));
876
+ }
877
+
878
+ private canOpen(target: Element): boolean {
879
+ let canOpen: boolean = true;
880
+ if (this.filter) {
881
+ canOpen = false;
882
+ let filter: string[] = this.filter.split(' ');
883
+ for (let i: number = 0, len: number = filter.length; i < len; i++) {
884
+ if (closest(target, '.' + filter[i])) {
885
+ canOpen = true;
886
+ break;
887
+ }
888
+ }
889
+ }
890
+ return canOpen;
891
+ }
892
+
893
+ protected openMenu(
894
+ li: Element, item: MenuItemModel | { [key: string]: Object }, top: number = 0, left: number = 0,
895
+ e: MouseEvent | KeyboardEvent = null, target: HTMLElement = this.targetElement): void {
896
+ let eventArgs: BeforeOpenCloseMenuEventArgs; let wrapper: Element = this.getWrapper();
897
+ this.lItem = li; let elemId: string = this.element.id !== '' ? this.element.id : 'menu';
898
+ this.isMenusClosed = false;
899
+ if (li) {
900
+ this.uList = this.createItems((<obj>item)[this.getField('children', this.navIdx.length - 1)] as objColl);
901
+ if (!this.isMenu && Browser.isDevice) {
902
+ (wrapper.lastChild as HTMLElement).style.display = 'none';
903
+ let data: { [key: string]: string } = {
904
+ text: (<obj>item)[this.getField('text')].toString(), iconCss: ICONS + ' e-previous'
905
+ };
906
+ let hdata: MenuItem = new MenuItem(this.items[0] as MenuItem, 'items', data, true);
907
+ let hli: Element = this.createItems([hdata] as MenuItemModel[]).children[0];
908
+ hli.classList.add(HEADER);
909
+ this.uList.insertBefore(hli, this.uList.children[0]);
910
+ }
911
+ if (this.isMenu) {
912
+ this.popupWrapper = this.createElement('div', {
913
+ className: 'e-' + this.getModuleName() + '-wrapper ' + POPUP, id: li.id + '-ej2menu-' + elemId + '-popup' });
914
+ if (this.hamburgerMode) {
915
+ top = (li as HTMLElement).offsetHeight;
916
+ li.appendChild(this.popupWrapper);
917
+ } else {
918
+ document.body.appendChild(this.popupWrapper);
919
+ }
920
+ this.isNestedOrVertical = this.element.classList.contains('e-vertical') || this.navIdx.length !== 1;
921
+ this.popupObj = this.generatePopup(this.popupWrapper, this.uList, li as HTMLElement, this.isNestedOrVertical);
922
+ if (this.hamburgerMode) {
923
+ this.calculateIndentSize(this.uList, li);
924
+ } else {
925
+ if (this.cssClass) {
926
+ addClass([this.popupWrapper], this.cssClass.split(' '));
927
+ }
928
+ this.popupObj.hide();
929
+ }
930
+ this.triggerBeforeOpen(li, this.uList, item, e, 0, 0, 'menu');
931
+ } else {
932
+ this.uList.style.zIndex = this.element.style.zIndex; wrapper.appendChild(this.uList);
933
+ this.triggerBeforeOpen(li, this.uList, item, e, top, left, 'none');
934
+ }
935
+ } else {
936
+ this.uList = this.element;
937
+ this.uList.style.zIndex = getZindexPartial(target ? target : this.element).toString();
938
+ this.triggerBeforeOpen(li, this.uList, item, e, top, left, 'none');
939
+ }
940
+ if (this.isMenu && this.template && this.isBlazor()) {
941
+ let menuTemplateId: string = this.element.id + TEMPLATE_PROPERTY;
942
+ if (Object.keys(blazorTemplates).length) {
943
+ let itemFromBlazorTemplate: objColl = (<obj>blazorTemplates)[menuTemplateId] as objColl;
944
+ this.tempItem = this.tempItem.concat(itemFromBlazorTemplate);
945
+ (<{ [key: string]: Object }>blazorTemplates)[menuTemplateId] = this.tempItem;
946
+ }
947
+ updateBlazorTemplate(menuTemplateId, TEMPLATE_PROPERTY, this);
948
+ }
949
+ }
950
+
951
+ private calculateIndentSize(ul: HTMLElement, li: Element): void {
952
+ let liStyle: CSSStyleDeclaration = getComputedStyle(li);
953
+ let liIndent: number = parseInt(liStyle.textIndent, 10);
954
+ if (this.navIdx.length < 2 && !li.classList.contains('e-blankicon')) {
955
+ liIndent *= 2;
956
+ } else {
957
+ liIndent += (liIndent / 4);
958
+ }
959
+ ul.style.textIndent = liIndent + 'px';
960
+ let blankIconElem: NodeList = ul.querySelectorAll('.e-blankicon');
961
+ if (blankIconElem && blankIconElem.length) {
962
+ let menuIconElem: HTMLElement = ul.querySelector('.e-menu-icon');
963
+ let menuIconElemStyle: CSSStyleDeclaration = getComputedStyle(menuIconElem);
964
+ let blankIconIndent: number = (parseInt(menuIconElemStyle.marginRight, 10) + menuIconElem.offsetWidth + liIndent);
965
+ blankIconElem.forEach((element: HTMLElement) => element.style.textIndent = blankIconIndent + 'px');
966
+ }
967
+ }
968
+
969
+ private generatePopup(
970
+ popupWrapper: HTMLElement, ul: HTMLElement, li: HTMLElement, isNestedOrVertical: boolean): Popup {
971
+ let popupObj: Popup = new Popup(popupWrapper, {
972
+ actionOnScroll: this.hamburgerMode ? 'none' : 'reposition',
973
+ relateTo: li,
974
+ collision: this.hamburgerMode ? { X: 'none', Y: 'none' } : { X: isNestedOrVertical ||
975
+ this.enableRtl ? 'none' : 'flip', Y: 'fit' },
976
+ position: (isNestedOrVertical && !this.hamburgerMode) ? { X: 'right', Y: 'top' } : { X: 'left', Y: 'bottom' },
977
+ targetType: 'relative',
978
+ enableRtl: this.enableRtl,
979
+ content: ul,
980
+ open: (): void => {
981
+ let scrollEle: HTMLElement = select('.e-menu-vscroll', popupObj.element) as HTMLElement;
982
+ if (scrollEle) {
983
+ scrollEle.style.height = 'inherit';
984
+ scrollEle.style.maxHeight = '';
985
+ }
986
+ let ul: HTMLElement = select('.e-ul', popupObj.element) as HTMLElement;
987
+ popupObj.element.style.maxHeight = '';
988
+ ul.focus();
989
+ this.triggerOpen(ul);
990
+ }
991
+ });
992
+ return popupObj;
993
+ }
994
+
995
+ protected createHeaderContainer(wrapper?: Element): void {
996
+ wrapper = wrapper || this.getWrapper();
997
+ let spanElem: HTMLElement = this.createElement('span', { className: 'e-' + this.getModuleName() + '-header' });
998
+ let spanTitle: HTMLElement = this.createElement('span', {
999
+ className: 'e-' + this.getModuleName() + '-title', innerHTML: this.title });
1000
+ let spanIcon: HTMLElement = this.createElement('span', {
1001
+ className: 'e-icons e-' + this.getModuleName() + '-icon', attrs: { 'tabindex': '0' } });
1002
+ spanElem.appendChild(spanTitle);
1003
+ spanElem.appendChild(spanIcon);
1004
+ wrapper.insertBefore(spanElem, this.element);
1005
+ }
1006
+
1007
+ protected openHamburgerMenu(e?: MouseEvent | KeyboardEvent) : void {
1008
+ if (this.hamburgerMode) {
1009
+ let eventArgs: BeforeOpenCloseMenuEventArgs;
1010
+ this.triggerBeforeOpen(null, this.element, null, e, 0, 0, 'hamburger');
1011
+ }
1012
+ }
1013
+
1014
+ protected closeHamburgerMenu(e?: MouseEvent | KeyboardEvent) : void {
1015
+ if (this.hamburgerMode) {
1016
+ let beforeCloseArgs: BeforeOpenCloseMenuEventArgs;
1017
+ beforeCloseArgs = { element: this.element, parentItem: null, event: e, items: this.items, cancel: false };
1018
+ this.trigger('beforeClose', beforeCloseArgs, (observedHamburgerCloseArgs: BeforeOpenCloseMenuEventArgs) => {
1019
+ if (!observedHamburgerCloseArgs.cancel) {
1020
+ this.closeMenu(null, e);
1021
+ this.element.classList.add('e-hide-menu');
1022
+ this.trigger('onClose', { element: this.element, parentItem: null, items: this.items });
1023
+ }
1024
+ });
1025
+ }
1026
+ }
1027
+
1028
+ private callFit(element: HTMLElement, x: boolean, y: boolean, top: number, left: number): OffsetPosition {
1029
+ return fit(element, null, { X: x, Y: y }, { top: top, left: left });
1030
+ }
1031
+
1032
+ private triggerBeforeOpen(
1033
+ li: Element, ul: HTMLElement, item: MenuItemModel, e: MouseEvent | KeyboardEvent,
1034
+ top: number, left: number, type: string): void {
1035
+ let navIdx: number[] = this.getIndex(li ? li.id : null, true);
1036
+ let items: MenuItemModel[] = li ? (<obj>item)[this.getField('children', this.navIdx.length - 1)] as objColl : this.items as objColl;
1037
+ let eventArgs: BeforeOpenCloseMenuEventArgs = {
1038
+ element: ul, items: items, parentItem: item, event: e, cancel: false, top: top, left: left
1039
+ };
1040
+ let menuType: string = type;
1041
+ this.trigger('beforeOpen', eventArgs, (observedOpenArgs: BeforeOpenCloseMenuEventArgs) => {
1042
+ switch (menuType) {
1043
+ case 'menu':
1044
+ if (!this.hamburgerMode) {
1045
+ this.top = observedOpenArgs.top; this.left = observedOpenArgs.left;
1046
+ }
1047
+ this.popupWrapper.style.display = 'block';
1048
+ if (!this.hamburgerMode) {
1049
+ this.popupWrapper.style.maxHeight = this.popupWrapper.getBoundingClientRect().height + 'px';
1050
+ this.addScrolling(
1051
+ this.popupWrapper, this.uList, 'vscroll', this.popupWrapper.offsetHeight, this.uList.offsetHeight);
1052
+ this.checkScrollOffset(e);
1053
+ }
1054
+ let collide: string[];
1055
+ if (!this.hamburgerMode && !this.left && !this.top) {
1056
+ this.popupObj.refreshPosition(this.lItem as HTMLElement, true);
1057
+ this.left = parseInt(this.popupWrapper.style.left, 10); this.top = parseInt(this.popupWrapper.style.top, 10);
1058
+ if (this.enableRtl) {
1059
+ this.left =
1060
+ this.isNestedOrVertical ? this.left - this.popupWrapper.offsetWidth - this.lItem.parentElement.offsetWidth
1061
+ : this.left - this.popupWrapper.offsetWidth + (this.lItem as HTMLElement).offsetWidth;
1062
+ }
1063
+ collide = isCollide(this.popupWrapper, null, this.left, this.top);
1064
+ if ((this.isNestedOrVertical || this.enableRtl) && (collide.indexOf('right') > -1
1065
+ || collide.indexOf('left') > -1)) {
1066
+ this.popupObj.collision.X = 'none';
1067
+ let offWidth: number =
1068
+ (closest(this.lItem, '.e-' + this.getModuleName() + '-wrapper') as HTMLElement).offsetWidth;
1069
+ this.left =
1070
+ this.enableRtl ? calculatePosition(this.lItem, this.isNestedOrVertical ? 'right' : 'left', 'top').left
1071
+ : this.left - this.popupWrapper.offsetWidth - offWidth;
1072
+ }
1073
+ collide = isCollide(this.popupWrapper, null, this.left, this.top);
1074
+ if (collide.indexOf('left') > -1 || collide.indexOf('right') > -1) {
1075
+ this.left = this.callFit(this.popupWrapper, true, false, this.top, this.left).left;
1076
+ }
1077
+ this.popupWrapper.style.left = this.left + 'px';
1078
+ } else {
1079
+ this.popupObj.collision = { X: 'none', Y: 'none' };
1080
+ }
1081
+ this.popupWrapper.style.display = '';
1082
+ break;
1083
+ case 'none':
1084
+ this.top = observedOpenArgs.top; this.left = observedOpenArgs.left;
1085
+ break;
1086
+ case 'hamburger':
1087
+ if (!observedOpenArgs.cancel) {
1088
+ this.element.classList.remove('e-hide-menu');
1089
+ this.triggerOpen(this.element);
1090
+ }
1091
+ break;
1092
+ }
1093
+ if (menuType !== 'hamburger') {
1094
+ if (observedOpenArgs.cancel) {
1095
+ if (this.isMenu) { this.popupObj.destroy(); detach(this.popupWrapper); }
1096
+ this.navIdx.pop();
1097
+ } else {
1098
+ if (this.isMenu) {
1099
+ if (this.hamburgerMode) {
1100
+ this.popupWrapper.style.top = this.top + 'px'; this.popupWrapper.style.left = 0 + 'px';
1101
+ this.toggleAnimation(this.popupWrapper);
1102
+ } else {
1103
+ this.wireKeyboardEvent(this.popupWrapper);
1104
+ rippleEffect(this.popupWrapper, { selector: '.' + ITEM });
1105
+ this.popupWrapper.style.left = this.left + 'px'; this.popupWrapper.style.top = this.top + 'px';
1106
+ let animationOptions: AnimationModel = this.animationSettings.effect !== 'None' ? {
1107
+ name: this.animationSettings.effect, duration: this.animationSettings.duration,
1108
+ timingFunction: this.animationSettings.easing
1109
+ } : null;
1110
+ this.popupObj.show(animationOptions, this.lItem as HTMLElement);
1111
+ }
1112
+ } else {
1113
+ this.setPosition(this.lItem, this.uList, this.top, this.left); this.toggleAnimation(this.uList);
1114
+ }
1115
+ }
1116
+ }
1117
+ if (this.keyType === 'right') {
1118
+ let cul: Element = this.getUlByNavIdx(); li.classList.remove(FOCUSED);
1119
+ if (this.isMenu && this.navIdx.length === 1) {
1120
+ this.removeLIStateByClass([SELECTED], [this.getWrapper()]);
1121
+ }
1122
+ li.classList.add(SELECTED);
1123
+ if (this.action === ENTER) {
1124
+ let eventArgs: MenuEventArgs = { element: li as HTMLElement, item: item, event: e };
1125
+ this.trigger('select', eventArgs);
1126
+ }
1127
+ (li as HTMLElement).focus(); cul = this.getUlByNavIdx();
1128
+ let index: number = this.isValidLI(cul.children[0], 0, this.action);
1129
+ cul.children[index].classList.add(FOCUSED);
1130
+ (cul.children[index] as HTMLElement).focus();
1131
+ }
1132
+ });
1133
+ }
1134
+
1135
+ private checkScrollOffset(e: MouseEvent | KeyboardEvent): void {
1136
+ let wrapper: Element = this.getWrapper();
1137
+ if (wrapper.children[0].classList.contains('e-menu-hscroll') && this.navIdx.length === 1) {
1138
+ let trgt: HTMLElement = isNullOrUndefined(e) ? this.element : closest(e.target as Element, '.' + ITEM) as HTMLElement;
1139
+ let offsetEle: HTMLElement = (select('.e-hscroll-bar', wrapper) as HTMLElement);
1140
+ let offsetLeft: number; let offsetRight: number;
1141
+ if (offsetEle.scrollLeft > trgt.offsetLeft) {
1142
+ offsetEle.scrollLeft -= (offsetEle.scrollLeft - trgt.offsetLeft);
1143
+ }
1144
+ offsetLeft = offsetEle.scrollLeft + offsetEle.offsetWidth;
1145
+ offsetRight = trgt.offsetLeft + trgt.offsetWidth;
1146
+ if (offsetLeft < offsetRight) {
1147
+ offsetEle.scrollLeft += (offsetRight - offsetLeft);
1148
+ }
1149
+ }
1150
+ }
1151
+
1152
+ private addScrolling(wrapper: HTMLElement, ul: HTMLElement, scrollType: string, wrapperOffset: number, contentOffset: number): void {
1153
+ if (this.enableScrolling && wrapperOffset < contentOffset) {
1154
+ let scrollEle: HTMLElement = this.createElement('div', { className: 'e-menu-' + scrollType });
1155
+ wrapper.appendChild(scrollEle);
1156
+ scrollEle.appendChild(ul);
1157
+ scrollEle.style.maxHeight = wrapper.style.maxHeight;
1158
+ let scrollObj: VScroll | HScroll;
1159
+ wrapper.style.overflow = 'hidden';
1160
+ if (scrollType === 'vscroll') {
1161
+ scrollObj = new VScroll({ enableRtl: this.enableRtl }, scrollEle);
1162
+ scrollObj.scrollStep = (select('.e-' + scrollType + '-bar', wrapper) as HTMLElement).offsetHeight / 2;
1163
+ } else {
1164
+ scrollObj = new HScroll({ enableRtl: this.enableRtl }, scrollEle);
1165
+ scrollObj.scrollStep = (select('.e-' + scrollType + '-bar', wrapper) as HTMLElement).offsetWidth;
1166
+ }
1167
+ }
1168
+ }
1169
+
1170
+ private setPosition(li: Element, ul: HTMLElement, top: number, left: number): void {
1171
+ let px: string = 'px';
1172
+ this.toggleVisiblity(ul);
1173
+ if (ul === this.element || (!isNullOrUndefined(left) && !isNullOrUndefined(top))) {
1174
+ let collide: string[] = isCollide(ul, null, left, top);
1175
+ if (collide.indexOf('right') > -1) {
1176
+ left = left - ul.offsetWidth;
1177
+ }
1178
+ if (collide.indexOf('bottom') > -1) {
1179
+ let offset: OffsetPosition = this.callFit(ul, false, true, top, left);
1180
+ top = offset.top - 20;
1181
+ }
1182
+ collide = isCollide(ul, null, left, top);
1183
+ if (collide.indexOf('left') > -1) {
1184
+ let offset: OffsetPosition = this.callFit(ul, true, false, top, left);
1185
+ left = offset.left;
1186
+ }
1187
+ } else {
1188
+ if (Browser.isDevice) {
1189
+ top = Number(this.element.style.top.replace(px, ''));
1190
+ left = Number(this.element.style.left.replace(px, ''));
1191
+ } else {
1192
+ let x: string = this.enableRtl ? 'left' : 'right';
1193
+ let offset: OffsetPosition = calculatePosition(li, x, 'top');
1194
+ top = offset.top;
1195
+ left = offset.left;
1196
+ let collide: string[] = isCollide(ul, null, this.enableRtl ? left - ul.offsetWidth : left, top);
1197
+ let xCollision: boolean = collide.indexOf('left') > -1 || collide.indexOf('right') > -1;
1198
+ if (xCollision) {
1199
+ offset = calculatePosition(li, this.enableRtl ? 'right' : 'left', 'top');
1200
+ left = offset.left;
1201
+ }
1202
+ if (this.enableRtl || xCollision) {
1203
+ left = (this.enableRtl && xCollision) ? left : left - ul.offsetWidth;
1204
+ }
1205
+ if (collide.indexOf('bottom') > -1) {
1206
+ offset = this.callFit(ul, false, true, top, left);
1207
+ top = offset.top;
1208
+ }
1209
+ }
1210
+ }
1211
+ this.toggleVisiblity(ul, false);
1212
+ ul.style.top = top + px;
1213
+ ul.style.left = left + px;
1214
+ }
1215
+
1216
+ private toggleVisiblity(ul: HTMLElement, isVisible: boolean = true): void {
1217
+ ul.style.visibility = isVisible ? 'hidden' : '';
1218
+ ul.style.display = isVisible ? 'block' : 'none';
1219
+ }
1220
+
1221
+ private createItems(items: MenuItemModel[] | objColl): HTMLElement {
1222
+ let level: number = this.navIdx ? this.navIdx.length : 0;
1223
+ let showIcon: boolean = this.hasField(items, this.getField('iconCss', level));
1224
+ let id: string = 'id'; let iconCss: string = 'iconCss';
1225
+ let listBaseOptions: ListBaseOptions = {
1226
+ showIcon: showIcon,
1227
+ moduleName: 'menu',
1228
+ fields: this.getFields(level),
1229
+ template: this.template,
1230
+ itemCreating: (args: { curData: obj, fields: obj }): void => {
1231
+ if (!args.curData[(<obj>args.fields)[id] as string]) {
1232
+ args.curData[(<obj>args.fields)[id] as string] = getUniqueID('menuitem');
1233
+ }
1234
+ args.curData.htmlAttributes = {
1235
+ role: 'menuitem',
1236
+ tabindex: '-1'
1237
+ };
1238
+ if (this.isMenu && !(<obj>args.curData)[this.getField('separator', level)]) {
1239
+ (<obj>args.curData.htmlAttributes)['aria-label'] = (<obj>args.curData)[args.fields.text as string];
1240
+ }
1241
+ if (args.curData[(<obj>args.fields)[iconCss] as string] === '') {
1242
+ args.curData[(<obj>args.fields)[iconCss] as string] = null;
1243
+ }
1244
+ },
1245
+ itemCreated: (args: { curData: MenuItemModel | obj, item: Element, fields: obj }): void => {
1246
+ if ((<obj>args.curData)[this.getField('separator', level)]) {
1247
+ args.item.classList.add(SEPARATOR);
1248
+ args.item.removeAttribute('role');
1249
+ }
1250
+ if (showIcon && !(<obj>args.curData)[args.fields.iconCss as string]
1251
+ && !(<obj>args.curData)[this.getField('separator', level)]) {
1252
+ (args.item as HTMLElement).classList.add('e-blankicon');
1253
+ }
1254
+ if ((<obj>args.curData)[args.fields.child as string]
1255
+ && (<objColl>(<obj>args.curData)[args.fields.child as string]).length) {
1256
+ let span: Element = this.createElement('span', { className: ICONS + ' ' + CARET });
1257
+ args.item.appendChild(span);
1258
+ args.item.setAttribute('aria-haspopup', 'true');
1259
+ args.item.setAttribute('aria-expanded', 'false');
1260
+ if (!this.isMenu) {
1261
+ args.item.removeAttribute('role');
1262
+ }
1263
+ (args.item as HTMLElement).classList.add('e-menu-caret-icon');
1264
+ }
1265
+ if (this.isMenu && this.template) {
1266
+ args.item.setAttribute('id', (<obj>args.curData)[args.fields.id as string].toString());
1267
+ args.item.removeAttribute('data-uid');
1268
+ }
1269
+ let eventArgs: MenuEventArgs = { item: args.curData, element: args.item as HTMLElement };
1270
+ this.trigger('beforeItemRender', eventArgs);
1271
+ }
1272
+ };
1273
+ this.setProperties({'items': this.items}, true);
1274
+ if (this.isMenu) {
1275
+ listBaseOptions.templateID = this.element.id + TEMPLATE_PROPERTY;
1276
+ }
1277
+
1278
+ let ul: HTMLElement = ListBase.createList(
1279
+ this.createElement, items as objColl, listBaseOptions, !this.template);
1280
+ ul.setAttribute('tabindex', '0');
1281
+ if (this.isMenu) {
1282
+ ul.setAttribute('role', 'menu');
1283
+ }
1284
+ return ul;
1285
+ }
1286
+
1287
+ private moverHandler(e: MouseEvent): void {
1288
+ let trgt: Element = e.target as Element;
1289
+ this.liTrgt = trgt;
1290
+ let cli: Element = this.getLI(trgt);
1291
+ let wrapper: Element = cli ? closest(cli, '.e-' + this.getModuleName() + '-wrapper') : this.getWrapper();
1292
+ let hdrWrapper: Element = this.getWrapper(); let regex: RegExp = new RegExp('-ej2menu-(.*)-popup'); let ulId: string;
1293
+ let isDifferentElem: boolean = false;
1294
+ if (!wrapper) {
1295
+ return;
1296
+ }
1297
+ if (wrapper.id !== '') {
1298
+ ulId = regex.exec(wrapper.id)[1];
1299
+ } else {
1300
+ ulId = wrapper.querySelector('ul').id;
1301
+ }
1302
+ if (ulId !== this.element.id) {
1303
+ this.removeLIStateByClass([FOCUSED, SELECTED], [this.getWrapper()]);
1304
+ if (this.navIdx.length) {
1305
+ isDifferentElem = true;
1306
+ } else {
1307
+ return;
1308
+ }
1309
+ }
1310
+ if (cli && closest(cli, '.e-' + this.getModuleName() + '-wrapper') && !isDifferentElem) {
1311
+ this.removeLIStateByClass([FOCUSED], this.isMenu ? [wrapper].concat(this.getPopups()) : [wrapper]);
1312
+ this.removeLIStateByClass([FOCUSED], this.isMenu ? [hdrWrapper].concat(this.getPopups()) : [hdrWrapper]);
1313
+ cli.classList.add(FOCUSED);
1314
+ if (!this.showItemOnClick) {
1315
+ this.clickHandler(e);
1316
+ }
1317
+ } else if (this.isMenu && this.showItemOnClick) {
1318
+ this.removeLIStateByClass([FOCUSED], [wrapper].concat(this.getPopups()));
1319
+ }
1320
+ if (this.isMenu) {
1321
+ if (!this.showItemOnClick && (trgt.parentElement !== wrapper && !closest(trgt, '.e-' + this.getModuleName() + '-popup'))
1322
+ && (!cli || (cli && !this.getIndex(cli.id, true).length))) {
1323
+ this.removeLIStateByClass([FOCUSED], [wrapper]);
1324
+ if (this.navIdx.length) {
1325
+ this.isClosed = true;
1326
+ this.closeMenu(null, e);
1327
+ }
1328
+ } else if (isDifferentElem) {
1329
+ if (this.navIdx.length) {
1330
+ this.isClosed = true;
1331
+ this.closeMenu(null, e);
1332
+ }
1333
+ }
1334
+ if (!this.isClosed) {
1335
+ this.removeStateWrapper();
1336
+ }
1337
+ this.isClosed = false;
1338
+ }
1339
+ }
1340
+ private removeStateWrapper(): void {
1341
+ if (this.liTrgt) {
1342
+ let wrapper: Element = closest(this.liTrgt, '.e-menu-vscroll');
1343
+ if (this.liTrgt.tagName === 'DIV' && wrapper) {
1344
+ this.removeLIStateByClass([FOCUSED, SELECTED], [wrapper]);
1345
+ }
1346
+ }
1347
+ }
1348
+
1349
+ private removeLIStateByClass(classList: string[], element: Element[]): void {
1350
+ let li: Element;
1351
+ for (let i: number = 0; i < element.length; i++) {
1352
+ classList.forEach((className: string) => {
1353
+ li = select('.' + className, element[i]);
1354
+ if (li) {
1355
+ li.classList.remove(className);
1356
+ }
1357
+ });
1358
+ }
1359
+ }
1360
+
1361
+ protected getField(propName: string, level: number = 0): string {
1362
+ let fieldName: object = (<obj>this.fields)[propName];
1363
+ return typeof fieldName === 'string' ? fieldName :
1364
+ (!(<obj>fieldName)[level] ? (fieldName as obj)[(<objColl>fieldName).length - 1].toString()
1365
+ : (<obj>fieldName)[level].toString());
1366
+ }
1367
+
1368
+ private getFields(level: number = 0): obj {
1369
+ return {
1370
+ id: this.getField('itemId', level),
1371
+ iconCss: this.getField('iconCss', level),
1372
+ text: this.getField('text', level),
1373
+ url: this.getField('url', level),
1374
+ child: this.getField('children', level),
1375
+ separator: this.getField('separator', level)
1376
+ };
1377
+ }
1378
+
1379
+ private hasField(items: MenuItemModel[], field: string): boolean {
1380
+ for (let i: number = 0, len: number = items.length; i < len; i++) {
1381
+ if ((<obj>items[i])[field]) {
1382
+ return true;
1383
+ }
1384
+ }
1385
+ return false;
1386
+ }
1387
+
1388
+ private menuHeaderClickHandler(e: MouseEvent | KeyboardEvent): void {
1389
+ if (closest(e.target as Element, '.e-menu-wrapper').querySelector('ul.e-menu-parent').id !== this.element.id) {
1390
+ return;
1391
+ }
1392
+ this.element.classList.contains('e-hide-menu') ? this.openHamburgerMenu(e) : this.closeHamburgerMenu(e);
1393
+ }
1394
+
1395
+ private clickHandler(e: MouseEvent): void {
1396
+ if (this.isTapHold) {
1397
+ this.isTapHold = false;
1398
+ } else {
1399
+ let wrapper: Element = this.getWrapper();
1400
+ let trgt: Element = e.target as Element;
1401
+ let cli: Element = this.cli = this.getLI(trgt);
1402
+ let regex: RegExp = new RegExp('-ej2menu-(.*)-popup');
1403
+ let cliWrapper: Element = cli ? closest(cli, '.e-' + this.getModuleName() + '-wrapper') : null;
1404
+ let isInstLI: boolean = cli && cliWrapper && (this.isMenu ? this.getIndex(cli.id, true).length > 0
1405
+ : wrapper.firstElementChild.id === cliWrapper.firstElementChild.id);
1406
+ if (cli && cliWrapper && this.isMenu) {
1407
+ let cliWrapperId: string = cliWrapper.id ? regex.exec(cliWrapper.id)[1] : cliWrapper.querySelector('.e-menu-parent').id;
1408
+ if (this.element.id !== cliWrapperId) {
1409
+ return;
1410
+ }
1411
+ }
1412
+ if (isInstLI && e.type === 'click' && !cli.classList.contains(HEADER)) {
1413
+ this.setLISelected(cli);
1414
+ let navIdx: number[] = this.getIndex(cli.id, true);
1415
+ let item: MenuItemModel = this.getItem(navIdx);
1416
+ let eventArgs: MenuEventArgs = { element: cli as HTMLElement, item: item, event: e };
1417
+ this.trigger('select', eventArgs);
1418
+ }
1419
+ if (isInstLI && (e.type === 'mouseover' || Browser.isDevice || this.showItemOnClick)) {
1420
+ let ul: HTMLElement;
1421
+ if (cli.classList.contains(HEADER)) {
1422
+ ul = wrapper.children[this.navIdx.length - 1] as HTMLElement;
1423
+ this.toggleAnimation(ul);
1424
+ let sli: Element = this.getLIByClass(ul, SELECTED);
1425
+ if (sli) {
1426
+ sli.classList.remove(SELECTED);
1427
+ }
1428
+ detach(cli.parentNode);
1429
+ this.navIdx.pop();
1430
+ } else {
1431
+ if (!cli.classList.contains(SEPARATOR)) {
1432
+ this.showSubMenu = true;
1433
+ let cul: Element = cli.parentNode as Element;
1434
+ this.cliIdx = this.getIdx(cul, cli);
1435
+ if (this.isMenu || !Browser.isDevice) {
1436
+ let culIdx: number = this.isMenu ? Array.prototype.indexOf.call(
1437
+ [wrapper].concat(this.getPopups()), closest(cul, '.' + 'e-' + this.getModuleName() + '-wrapper'))
1438
+ : this.getIdx(wrapper, cul);
1439
+ if (this.navIdx[culIdx] === this.cliIdx) {
1440
+ this.showSubMenu = false;
1441
+ }
1442
+ if (culIdx !== this.navIdx.length && (e.type !== 'mouseover' || this.showSubMenu)) {
1443
+ let sli: Element = this.getLIByClass(cul, SELECTED);
1444
+ if (sli) {
1445
+ sli.classList.remove(SELECTED);
1446
+ }
1447
+ this.isClosed = true;
1448
+ this.keyType = 'click';
1449
+ this.closeMenu(culIdx + 1, e);
1450
+ }
1451
+ }
1452
+ if (!this.isClosed) {
1453
+ this.afterCloseMenu(e);
1454
+ }
1455
+ this.isClosed = false;
1456
+ }
1457
+ }
1458
+ } else {
1459
+ if (this.isMenu && trgt.tagName === 'DIV' && this.navIdx.length && closest(trgt, '.e-menu-vscroll')) {
1460
+ let popupEle: Element = closest(trgt, '.' + POPUP);
1461
+ let cIdx: number = Array.prototype.indexOf.call(this.getPopups(), popupEle) + 1;
1462
+ if (cIdx < this.navIdx.length) {
1463
+ this.closeMenu(cIdx + 1, e);
1464
+ this.removeLIStateByClass([FOCUSED, SELECTED], [popupEle]);
1465
+ }
1466
+ } else if (this.isMenu && this.hamburgerMode && trgt.tagName === 'SPAN'
1467
+ && trgt.classList.contains('e-menu-icon')) {
1468
+ this.menuHeaderClickHandler(e);
1469
+ } else {
1470
+ if (trgt.tagName !== 'UL' || (this.isMenu ? trgt.parentElement.classList.contains('e-menu-wrapper') &&
1471
+ !this.getIndex(trgt.querySelector('.' + ITEM).id, true).length : trgt.parentElement !== wrapper)) {
1472
+ if (!cli) {
1473
+ this.removeLIStateByClass([SELECTED], [wrapper]);
1474
+ }
1475
+ if (!cli || !cli.querySelector('.' + CARET)) {
1476
+ this.closeMenu(null, e);
1477
+ }
1478
+ }
1479
+ }
1480
+ }
1481
+ }
1482
+ }
1483
+ private afterCloseMenu(e: MouseEvent): void {
1484
+ if (this.showSubMenu) {
1485
+ let idx: number[] = this.navIdx.concat(this.cliIdx);
1486
+ let item: MenuItemModel = this.getItem(idx);
1487
+ if (item && (<objColl>(<obj>item)[this.getField('children', idx.length - 1)]) &&
1488
+ (<objColl>(<obj>item)[this.getField('children', idx.length - 1)]).length) {
1489
+ if (e.type === 'mouseover' || (Browser.isDevice && this.isMenu)) {
1490
+ this.setLISelected(this.cli);
1491
+ }
1492
+ this.cli.setAttribute('aria-expanded', 'true');
1493
+ this.navIdx.push(this.cliIdx);
1494
+ this.openMenu(this.cli, item, null, null, e);
1495
+ } else {
1496
+ if (e.type !== 'mouseover') {
1497
+ this.closeMenu(null, e);
1498
+ }
1499
+ }
1500
+ }
1501
+ this.keyType = '';
1502
+ }
1503
+ private setLISelected(li: Element): void {
1504
+ let sli: Element = this.getLIByClass(li.parentElement, SELECTED);
1505
+ if (sli) {
1506
+ sli.classList.remove(SELECTED);
1507
+ }
1508
+ if (!this.isMenu) {
1509
+ li.classList.remove(FOCUSED);
1510
+ }
1511
+ li.classList.add(SELECTED);
1512
+ }
1513
+
1514
+ private getLIByClass(ul: Element, classname: string): Element {
1515
+ for (let i: number = 0, len: number = ul.children.length; i < len; i++) {
1516
+ if (ul.children[i].classList.contains(classname)) {
1517
+ return ul.children[i];
1518
+ }
1519
+ }
1520
+ return null;
1521
+ }
1522
+
1523
+ private getItem(navIdx: number[]): MenuItemModel {
1524
+ navIdx = navIdx.slice();
1525
+ let idx: number = navIdx.pop();
1526
+ let items: MenuItemModel[] = this.getItems(navIdx);
1527
+ return items[idx];
1528
+ }
1529
+
1530
+ private getItems(navIdx: number[]): objColl {
1531
+ let items: objColl = this.items as objColl;
1532
+ for (let i: number = 0; i < navIdx.length; i++) {
1533
+ items = (<obj>items[navIdx[i]])[this.getField('children', i)] as objColl;
1534
+ }
1535
+ return items;
1536
+ }
1537
+
1538
+ private setItems(newItems: objColl, navIdx: number[]): void {
1539
+ let items: objColl = this.getItems(navIdx);
1540
+ items.splice(0, items.length);
1541
+ for (let i: number = 0; i < newItems.length; i++) {
1542
+ items.splice(i, 0, newItems[i]);
1543
+ }
1544
+ }
1545
+
1546
+ private getIdx(ul: Element, li: Element, skipHdr: boolean = true): number {
1547
+ let idx: number = Array.prototype.indexOf.call(ul.querySelectorAll('li'), li);
1548
+ if (this.isMenu && this.template && this.isBlazor()) {
1549
+ idx = Array.prototype.indexOf.call(ul.querySelectorAll(li.tagName), li);
1550
+ } else {
1551
+ idx = Array.prototype.indexOf.call(ul.children, li);
1552
+ }
1553
+ if (skipHdr && ul.children[0].classList.contains(HEADER)) {
1554
+ idx--;
1555
+ }
1556
+ return idx;
1557
+ }
1558
+
1559
+ private getLI(elem: Element): Element {
1560
+ if (elem.tagName === 'LI' && elem.classList.contains('e-menu-item')) {
1561
+ return elem;
1562
+ }
1563
+ return closest(elem, 'li.e-menu-item');
1564
+ }
1565
+
1566
+ private isBlazor(): boolean {
1567
+ return ((Object.keys(window).indexOf('ejsInterop') === -1) ? false : true);
1568
+ }
1569
+
1570
+ private removeChildElement(elem: HTMLUListElement): HTMLUListElement {
1571
+ while (elem.firstElementChild) {
1572
+ elem.removeChild(elem.firstElementChild);
1573
+ }
1574
+ return elem;
1575
+ }
1576
+ /**
1577
+ * Called internally if any of the property value changed
1578
+ * @private
1579
+ * @param {MenuBaseModel} newProp
1580
+ * @param {MenuBaseModel} oldProp
1581
+ * @returns void
1582
+ */
1583
+ public onPropertyChanged(newProp: MenuBaseModel, oldProp: MenuBaseModel): void {
1584
+ let wrapper: HTMLElement = this.getWrapper() as HTMLElement;
1585
+ for (let prop of Object.keys(newProp)) {
1586
+ switch (prop) {
1587
+ case 'cssClass':
1588
+ if (oldProp.cssClass) {
1589
+ removeClass([wrapper], oldProp.cssClass.split(' '));
1590
+ }
1591
+ if (newProp.cssClass) {
1592
+ addClass([wrapper], newProp.cssClass.split(' '));
1593
+ }
1594
+ break;
1595
+ case 'enableRtl':
1596
+ wrapper.classList.toggle(RTL);
1597
+ break;
1598
+ case 'showItemOnClick':
1599
+ this.unWireEvents();
1600
+ this.showItemOnClick = newProp.showItemOnClick;
1601
+ this.wireEvents();
1602
+ break;
1603
+ case 'enableScrolling':
1604
+ if (newProp.enableScrolling) {
1605
+ let ul: HTMLElement;
1606
+ this.element.classList.contains('e-vertical') ?
1607
+ this.addScrolling(wrapper, this.element, 'vscroll', wrapper.offsetHeight, this.element.offsetHeight)
1608
+ : this.addScrolling(wrapper, this.element, 'hscroll', wrapper.offsetWidth, this.element.offsetWidth);
1609
+ (this.getPopups() as HTMLElement[]).forEach((wrapper: HTMLElement) => {
1610
+ ul = select('.e-ul', wrapper) as HTMLElement;
1611
+ this.addScrolling(wrapper, ul, 'vscroll', wrapper.offsetHeight, ul.offsetHeight);
1612
+ });
1613
+ } else {
1614
+ let ul: HTMLElement = wrapper.children[0] as HTMLElement;
1615
+ this.element.classList.contains('e-vertical') ? this.destroyScrollObj(getInstance(ul, VScroll) as VScroll, ul)
1616
+ : this.destroyScrollObj(getInstance(ul, HScroll) as HScroll, ul);
1617
+ wrapper.style.overflow = '';
1618
+ wrapper.appendChild(this.element);
1619
+ (this.getPopups() as HTMLElement[]).forEach((wrapper: HTMLElement) => {
1620
+ ul = wrapper.children[0] as HTMLElement;
1621
+ this.destroyScrollObj(getInstance(ul, VScroll) as VScroll, ul);
1622
+ wrapper.style.overflow = '';
1623
+ });
1624
+ }
1625
+ break;
1626
+ case 'items':
1627
+ let idx: number;
1628
+ let navIdx: number[];
1629
+ let item: MenuItemModel[];
1630
+ if (!Object.keys(oldProp.items).length) {
1631
+ let ul: HTMLElement = this.element;
1632
+ if (this.isBlazor()) {
1633
+ ul = this.removeChildElement(this.element);
1634
+ } else {
1635
+ ul.innerHTML = '';
1636
+ }
1637
+ let lis: HTMLElement[] = [].slice.call(this.createItems(this.items).children);
1638
+ lis.forEach((li: HTMLElement): void => {
1639
+ ul.appendChild(li);
1640
+ });
1641
+ for (let i: number = 1, count: number = wrapper.childElementCount; i < count; i++) {
1642
+ detach(wrapper.lastElementChild);
1643
+ }
1644
+ this.navIdx = [];
1645
+ } else {
1646
+ let keys: string[] = Object.keys(newProp.items);
1647
+ for (let i: number = 0; i < keys.length; i++) {
1648
+ navIdx = this.getChangedItemIndex(newProp, [], Number(keys[i]));
1649
+ if (navIdx.length <= this.getWrapper().children.length) {
1650
+ idx = navIdx.pop();
1651
+ item = this.getItems(navIdx);
1652
+ this.insertAfter([item[idx]], item[idx].text);
1653
+ this.removeItem(item, navIdx, idx);
1654
+ this.setItems(item as objColl, navIdx);
1655
+ }
1656
+ navIdx.length = 0;
1657
+ }
1658
+ }
1659
+ break;
1660
+ }
1661
+ }
1662
+ }
1663
+
1664
+ private getChangedItemIndex(newProp: MenuBaseModel, index: number[], idx: number): number[] {
1665
+ index.push(idx);
1666
+ let key: string = Object.keys((<objColl>newProp.items)[idx]).pop();
1667
+ if (key === 'items') {
1668
+ let item: MenuItemModel = (<objColl>newProp.items)[idx];
1669
+ this.getChangedItemIndex(item, index, Number(Object.keys(item.items).pop()));
1670
+ } else {
1671
+ if (key === 'isParentArray' && index.length > 1) {
1672
+ index.pop();
1673
+ }
1674
+ }
1675
+ return index;
1676
+ }
1677
+
1678
+ private removeItem(item: MenuItemModel[], navIdx: number[], idx: number): void {
1679
+ item.splice(idx, 1);
1680
+ let uls: HTMLCollection = this.getWrapper().children;
1681
+ if (navIdx.length < uls.length) {
1682
+ detach(uls[navIdx.length].children[idx]);
1683
+ }
1684
+ }
1685
+
1686
+ /**
1687
+ * Used to unwire the bind events.
1688
+ * @private
1689
+ */
1690
+ protected unWireEvents(targetSelctor: string = this.target): void {
1691
+ let wrapper: HTMLElement = this.getWrapper() as HTMLElement;
1692
+ if (targetSelctor) {
1693
+ let target: HTMLElement;
1694
+ let touchModule: Touch;
1695
+ let targetElems: HTMLElement[] = selectAll(targetSelctor);
1696
+ for (let i: number = 0, len: number = targetElems.length; i < len; i++) {
1697
+ target = targetElems[i];
1698
+ if (this.isMenu) {
1699
+ EventHandler.remove(target, 'click', this.menuHeaderClickHandler);
1700
+ } else {
1701
+ if (Browser.isIos) {
1702
+ touchModule = getInstance(target, Touch) as Touch;
1703
+ if (touchModule) {
1704
+ touchModule.destroy();
1705
+ }
1706
+ } else {
1707
+ EventHandler.remove(target, 'contextmenu', this.cmenuHandler);
1708
+ }
1709
+ }
1710
+ }
1711
+ if (!this.isMenu) {
1712
+ EventHandler.remove(this.targetElement, 'scroll', this.scrollHandler);
1713
+ for (let parent of getScrollableParent(this.targetElement)) {
1714
+ EventHandler.remove(parent, 'scroll', this.scrollHandler);
1715
+ }
1716
+ }
1717
+ }
1718
+ if (!Browser.isDevice) {
1719
+ EventHandler.remove(this.isMenu ? document : wrapper, 'mouseover', this.delegateMoverHandler);
1720
+ EventHandler.remove(document, 'mousedown', this.delegateMouseDownHandler);
1721
+ }
1722
+ EventHandler.remove(document, 'click', this.delegateClickHandler);
1723
+ this.unWireKeyboardEvent(wrapper);
1724
+ this.rippleFn();
1725
+ }
1726
+
1727
+ private unWireKeyboardEvent(element: HTMLElement): void {
1728
+ let keyboardModule: KeyboardEvents = getInstance(element, KeyboardEvents) as KeyboardEvents;
1729
+ if (keyboardModule) {
1730
+ keyboardModule.destroy();
1731
+ }
1732
+ }
1733
+
1734
+ private toggleAnimation(ul: HTMLElement, isMenuOpen: boolean = true): void {
1735
+ let pUlHeight: number;
1736
+ let pElement: HTMLElement;
1737
+ if (this.animationSettings.effect === 'None' || !isMenuOpen) {
1738
+ this.end(ul, isMenuOpen);
1739
+ } else {
1740
+ this.animation.animate(ul, {
1741
+ name: this.animationSettings.effect,
1742
+ duration: this.animationSettings.duration,
1743
+ timingFunction: this.animationSettings.easing,
1744
+ begin: (options: AnimationOptions) => {
1745
+ if (this.hamburgerMode) {
1746
+ pElement = options.element.parentElement;
1747
+ options.element.style.position = 'absolute';
1748
+ pUlHeight = pElement.offsetHeight;
1749
+ options.element.style.maxHeight = options.element.offsetHeight + 'px';
1750
+ pElement.style.maxHeight = '';
1751
+ } else {
1752
+ options.element.style.display = 'block';
1753
+ options.element.style.maxHeight = options.element.getBoundingClientRect().height + 'px';
1754
+ }
1755
+ },
1756
+ progress: (options: AnimationOptions) => {
1757
+ if (this.hamburgerMode) {
1758
+ pElement.style.minHeight = (pUlHeight + options.element.offsetHeight) + 'px';
1759
+ }
1760
+ },
1761
+ end: (options: AnimationOptions) => {
1762
+ if (this.hamburgerMode) {
1763
+ options.element.style.position = '';
1764
+ options.element.style.maxHeight = '';
1765
+ pElement.style.minHeight = '';
1766
+ options.element.style.top = 0 + 'px';
1767
+ (options.element.children[0] as HTMLElement).focus();
1768
+ this.triggerOpen(options.element.children[0] as HTMLElement);
1769
+ } else {
1770
+ this.end(options.element, isMenuOpen);
1771
+ }
1772
+ }
1773
+ });
1774
+ }
1775
+ }
1776
+
1777
+ private triggerOpen(ul: HTMLElement): void {
1778
+ let item: MenuItemModel = this.navIdx.length ? this.getItem(this.navIdx) : null;
1779
+ let eventArgs: OpenCloseMenuEventArgs = {
1780
+ element: ul, parentItem: item, items: item ? item.items : this.items as objColl
1781
+ };
1782
+ this.trigger('onOpen', eventArgs);
1783
+ }
1784
+
1785
+ private end(ul: HTMLElement, isMenuOpen: boolean): void {
1786
+ if (isMenuOpen) {
1787
+ ul.style.display = 'block';
1788
+ ul.style.maxHeight = '';
1789
+ this.triggerOpen(ul);
1790
+ if (ul.querySelector('.' + FOCUSED)) {
1791
+ (ul.querySelector('.' + FOCUSED) as HTMLElement).focus();
1792
+ } else {
1793
+ let ele: HTMLElement;
1794
+ ele = this.getWrapper().children[this.getIdx(this.getWrapper(), ul) - 1] as HTMLElement;
1795
+ if (ele) {
1796
+ (ele.querySelector('.' + SELECTED) as HTMLElement).focus();
1797
+ } else {
1798
+ this.element.focus();
1799
+ }
1800
+ }
1801
+ } else {
1802
+ if (ul === this.element) {
1803
+ let fli: Element = this.getLIByClass(this.element, FOCUSED);
1804
+ if (fli) {
1805
+ fli.classList.remove(FOCUSED);
1806
+ }
1807
+ let sli: Element = this.getLIByClass(this.element, SELECTED);
1808
+ if (sli) {
1809
+ sli.classList.remove(SELECTED);
1810
+ }
1811
+ ul.style.display = 'none';
1812
+ } else {
1813
+ detach(ul);
1814
+ }
1815
+ }
1816
+ }
1817
+
1818
+ /**
1819
+ * Get the properties to be maintained in the persisted state.
1820
+ * @returns string
1821
+ */
1822
+ protected getPersistData(): string {
1823
+ return '';
1824
+ }
1825
+
1826
+ /**
1827
+ * Get wrapper element.
1828
+ * @returns Element
1829
+ * @private
1830
+ */
1831
+ private getWrapper(): Element {
1832
+ return closest(this.element, '.e-' + this.getModuleName() + '-wrapper');
1833
+ }
1834
+
1835
+ protected getIndex(
1836
+ data: string, isUniqueId?: boolean, items: MenuItemModel[] | { [key: string]: Object }[] = this.items as objColl,
1837
+ nIndex: number[] = [], isCallBack: boolean = false, level: number = 0): number[] {
1838
+ let item: MenuItemModel | obj;
1839
+ level = isCallBack ? level + 1 : 0;
1840
+ for (let i: number = 0, len: number = items.length; i < len; i++) {
1841
+ item = items[i];
1842
+ if ((isUniqueId ? (<obj>item)[this.getField('itemId', level)] : (<obj>item)[this.getField('text', level)]) === data) {
1843
+ nIndex.push(i);
1844
+ break;
1845
+ } else if ((<objColl>(<obj>item)[this.getField('children', level)])
1846
+ && (<objColl>(<obj>item)[this.getField('children', level)]).length) {
1847
+ nIndex = this.getIndex(data, isUniqueId, (<objColl>(<obj>item)[this.getField('children', level)]), nIndex, true, level);
1848
+ if (nIndex[nIndex.length - 1] === -1) {
1849
+ if (i !== len - 1) {
1850
+ nIndex.pop();
1851
+ }
1852
+ } else {
1853
+ nIndex.unshift(i);
1854
+ break;
1855
+ }
1856
+ } else {
1857
+ if (i === len - 1) {
1858
+ nIndex.push(-1);
1859
+ }
1860
+ }
1861
+ }
1862
+ return (!isCallBack && nIndex[0] === -1) ? [] : nIndex;
1863
+ }
1864
+
1865
+ /**
1866
+ * This method is used to enable or disable the menu items in the Menu based on the items and enable argument.
1867
+ * @param items Text items that needs to be enabled/disabled.
1868
+ * @param enable Set `true`/`false` to enable/disable the list items.
1869
+ * @param isUniqueId - Set `true` if it is a unique id.
1870
+ * @returns void
1871
+ */
1872
+ public enableItems(items: string[], enable: boolean = true, isUniqueId?: boolean): void {
1873
+ let ul: Element;
1874
+ let idx: number;
1875
+ let navIdx: number[];
1876
+ let disabled: string = DISABLED; let skipItem: boolean;
1877
+ for (let i: number = 0; i < items.length; i++) {
1878
+ navIdx = this.getIndex(items[i], isUniqueId);
1879
+ if (this.navIdx.length) {
1880
+ if (navIdx.length !== 1) {
1881
+ skipItem = false;
1882
+ for (let i: number = 0, len: number = navIdx.length - 1; i < len; i++) {
1883
+ if (navIdx[i] !== this.navIdx[i]) {
1884
+ skipItem = true; break;
1885
+ }
1886
+ }
1887
+ if (skipItem) { continue; }
1888
+ }
1889
+ } else {
1890
+ if (navIdx.length !== 1) { continue; }
1891
+ }
1892
+ idx = navIdx.pop();
1893
+ ul = this.getUlByNavIdx(navIdx.length);
1894
+ if (ul) {
1895
+ if (enable) {
1896
+ if (this.isMenu) {
1897
+ ul.children[idx].classList.remove(disabled);
1898
+ ul.children[idx].removeAttribute('aria-disabled');
1899
+ } else {
1900
+ if (Browser.isDevice && !ul.classList.contains('e-contextmenu')) {
1901
+ ul.children[idx + 1].classList.remove(disabled);
1902
+ } else {
1903
+ ul.children[idx].classList.remove(disabled);
1904
+ }
1905
+ }
1906
+ } else {
1907
+ if (this.isMenu) {
1908
+ ul.children[idx].classList.add(disabled);
1909
+ ul.children[idx].setAttribute('aria-disabled', 'true');
1910
+ } else {
1911
+ if (Browser.isDevice && !ul.classList.contains('e-contextmenu')) {
1912
+ ul.children[idx + 1].classList.add(disabled);
1913
+ } else {
1914
+ ul.children[idx].classList.add(disabled);
1915
+ }
1916
+ }
1917
+ }
1918
+ }
1919
+ }
1920
+ }
1921
+
1922
+ /**
1923
+ * This method is used to show the menu items in the Menu based on the items text.
1924
+ * @param items Text items that needs to be shown.
1925
+ * @param isUniqueId - Set `true` if it is a unique id.
1926
+ * @returns void
1927
+ */
1928
+ public showItems(items: string[], isUniqueId?: boolean): void {
1929
+ this.showHideItems(items, false, isUniqueId);
1930
+ }
1931
+
1932
+ /**
1933
+ * This method is used to hide the menu items in the Menu based on the items text.
1934
+ * @param items Text items that needs to be hidden.
1935
+ * @returns void
1936
+ */
1937
+ public hideItems(items: string[], isUniqueId?: boolean): void {
1938
+ this.showHideItems(items, true, isUniqueId);
1939
+ }
1940
+
1941
+ private showHideItems(items: string[], ishide: boolean, isUniqueId?: boolean): void {
1942
+ let ul: Element;
1943
+ let index: number;
1944
+ let navIdx: number[];
1945
+ for (let i: number = 0; i < items.length; i++) {
1946
+ navIdx = this.getIndex(items[i], isUniqueId);
1947
+ index = navIdx.pop();
1948
+ ul = this.getUlByNavIdx(navIdx.length);
1949
+ if (ul) {
1950
+ if (ishide) {
1951
+ if (Browser.isDevice && !ul.classList.contains('e-contextmenu')) {
1952
+ ul.children[index + 1].classList.add(HIDE);
1953
+ } else {
1954
+ ul.children[index].classList.add(HIDE);
1955
+ }
1956
+ } else {
1957
+ if (Browser.isDevice && !ul.classList.contains('e-contextmenu')) {
1958
+ ul.children[index + 1].classList.remove(HIDE);
1959
+ } else {
1960
+ ul.children[index].classList.remove(HIDE);
1961
+ }
1962
+ }
1963
+ }
1964
+ }
1965
+ }
1966
+
1967
+ /**
1968
+ * It is used to remove the menu items from the Menu based on the items text.
1969
+ * @param items Text items that needs to be removed.
1970
+ * @returns void
1971
+ */
1972
+ public removeItems(items: string[], isUniqueId?: boolean): void {
1973
+ let idx: number;
1974
+ let navIdx: number[];
1975
+ let iitems: MenuItemModel[];
1976
+ for (let i: number = 0; i < items.length; i++) {
1977
+ navIdx = this.getIndex(items[i], isUniqueId);
1978
+ idx = navIdx.pop();
1979
+ iitems = this.getItems(navIdx);
1980
+ this.removeItem(iitems, navIdx, idx);
1981
+ }
1982
+ }
1983
+
1984
+ /**
1985
+ * It is used to insert the menu items after the specified menu item text.
1986
+ * @param items Items that needs to be inserted.
1987
+ * @param text Text item after that the element to be inserted.
1988
+ * @returns void
1989
+ */
1990
+ public insertAfter(items: MenuItemModel[], text: string, isUniqueId?: boolean): void {
1991
+ this.insertItems(items, text, isUniqueId);
1992
+ }
1993
+
1994
+ /**
1995
+ * It is used to insert the menu items before the specified menu item text.
1996
+ * @param items Items that needs to be inserted.
1997
+ * @param text Text item before that the element to be inserted.
1998
+ * @param isUniqueId - Set `true` if it is a unique id.
1999
+ * @returns void
2000
+ */
2001
+ public insertBefore(items: MenuItemModel[], text: string, isUniqueId?: boolean): void {
2002
+ this.insertItems(items, text, isUniqueId, false);
2003
+ }
2004
+
2005
+ private insertItems(items: MenuItemModel[] | objColl, text: string, isUniqueId?: boolean, isAfter: boolean = true): void {
2006
+ let li: Element;
2007
+ let idx: number;
2008
+ let navIdx: number[];
2009
+ let iitems: MenuItemModel[];
2010
+ let menuitem: MenuItem;
2011
+ let showIcon: boolean;
2012
+ for (let i: number = 0; i < items.length; i++) {
2013
+ navIdx = this.getIndex(text, isUniqueId);
2014
+ idx = navIdx.pop();
2015
+ iitems = this.getItems(navIdx);
2016
+ menuitem = new MenuItem(iitems[0] as MenuItem, 'items', items[i], true);
2017
+ iitems.splice(isAfter ? idx + 1 : idx, 0, menuitem);
2018
+ let uls: Element[] = this.isMenu ? [this.getWrapper()].concat(this.getPopups()) : [].slice.call(this.getWrapper().children);
2019
+ if (navIdx.length < uls.length) {
2020
+ idx = isAfter ? idx + 1 : idx;
2021
+ showIcon = this.hasField(iitems, this.getField('iconCss', navIdx.length - 1));
2022
+ li = this.createItems(iitems).children[idx];
2023
+ let ul: Element = this.isMenu ? select('.e-menu-parent', uls[navIdx.length]) : uls[navIdx.length];
2024
+ ul.insertBefore(li, ul.children[idx]);
2025
+ }
2026
+ }
2027
+ }
2028
+
2029
+ private removeAttributes(): void {
2030
+ ['top', 'left', 'display', 'z-index'].forEach((key: string) => {
2031
+ this.element.style.removeProperty(key);
2032
+ });
2033
+ ['role', 'tabindex', 'class', 'style'].forEach((key: string) => {
2034
+ if (key === 'class' && this.element.classList.contains('e-menu-parent')) {
2035
+ this.element.classList.remove('e-menu-parent');
2036
+ }
2037
+ if (['class', 'style'].indexOf(key) === -1 || !this.element.getAttribute(key)) {
2038
+ this.element.removeAttribute(key);
2039
+ }
2040
+ if (this.isMenu && key === 'class' && this.element.classList.contains('e-vertical')) {
2041
+ this.element.classList.remove('e-vertical');
2042
+ }
2043
+ });
2044
+ }
2045
+
2046
+ /**
2047
+ * Destroys the widget.
2048
+ * @returns void
2049
+ */
2050
+
2051
+ public destroy(): void {
2052
+ let wrapper: Element = this.getWrapper();
2053
+ if (wrapper) {
2054
+ this.unWireEvents();
2055
+ if (!this.isMenu) {
2056
+ this.clonedElement.style.display = '';
2057
+ if (this.clonedElement.tagName === 'EJS-CONTEXTMENU') {
2058
+ addClass([this.clonedElement], ['e-control', 'e-lib', 'e-' + this.getModuleName()]);
2059
+ this.element = this.clonedElement as HTMLUListElement;
2060
+ } else {
2061
+ if (this.refreshing && this.clonedElement.childElementCount && this.clonedElement.children[0].tagName === 'LI') {
2062
+ this.setProperties({ 'items': [] }, true);
2063
+ }
2064
+ if (document.getElementById(this.clonedElement.id)) {
2065
+ let refEle: Element = this.clonedElement.nextElementSibling;
2066
+ refEle && refEle !== wrapper ? this.clonedElement.parentElement.insertBefore(this.element, refEle) :
2067
+ this.clonedElement.parentElement.appendChild(this.element);
2068
+ if (this.isBlazor()) {
2069
+ this.element = this.removeChildElement(this.element);
2070
+ } else {
2071
+ this.element.innerHTML = '';
2072
+ }
2073
+ append([].slice.call(this.clonedElement.children), this.element);
2074
+ detach(this.clonedElement);
2075
+ this.removeAttributes();
2076
+ }
2077
+ }
2078
+ this.clonedElement = null;
2079
+ } else {
2080
+ this.closeMenu();
2081
+ if (this.isBlazor()) {
2082
+ this.element = this.removeChildElement(this.element);
2083
+ } else {
2084
+ this.element.innerHTML = '';
2085
+ }
2086
+ this.removeAttributes();
2087
+ wrapper.parentNode.insertBefore(this.element, wrapper);
2088
+ }
2089
+ if (this.isMenu && this.clonedElement) {
2090
+ detach(this.element);
2091
+ (wrapper as HTMLElement).style.display = '';
2092
+ wrapper.classList.remove('e-' + this.getModuleName() + '-wrapper');
2093
+ wrapper.removeAttribute('data-ripple');
2094
+ } else {
2095
+ detach(wrapper);
2096
+ }
2097
+ super.destroy();
2098
+ }
2099
+ }
2100
+ }
2101
+
2102
+ /**
2103
+ * Interface for before item render/select event.
2104
+ */
2105
+ export interface MenuEventArgs extends BaseEventArgs {
2106
+ element: HTMLElement;
2107
+ item: MenuItemModel;
2108
+ event?: Event;
2109
+ }
2110
+
2111
+ /**
2112
+ * Interface for before open/close event.
2113
+ */
2114
+ export interface BeforeOpenCloseMenuEventArgs extends BaseEventArgs {
2115
+ element: HTMLElement;
2116
+ items: MenuItemModel[];
2117
+ parentItem: MenuItemModel;
2118
+ event: Event;
2119
+ cancel: boolean;
2120
+ top?: number;
2121
+ left?: number;
2122
+ }
2123
+
2124
+ /**
2125
+ * Interface for open/close event.
2126
+ */
2127
+ export interface OpenCloseMenuEventArgs extends BaseEventArgs {
2128
+ element: HTMLElement;
2129
+ items: MenuItemModel[] | { [key: string]: Object }[];
2130
+ parentItem: MenuItemModel;
2131
+ }