@ui5/webcomponents-base 0.0.0-b4cbc43ba → 0.0.0-b93c2a321

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 (212) hide show
  1. package/.eslintignore +3 -0
  2. package/CHANGELOG.md +214 -0
  3. package/README.md +26 -2
  4. package/bundle.esm.js +10 -7
  5. package/dist/AssetRegistry.js +8 -9
  6. package/dist/Boot.js +17 -6
  7. package/dist/CSP.js +59 -0
  8. package/dist/CustomElementsRegistry.js +60 -4
  9. package/dist/CustomElementsScope.js +20 -98
  10. package/dist/CustomElementsScopeUtils.js +108 -0
  11. package/dist/DOMObserver.js +6 -2
  12. package/dist/Device.js +69 -776
  13. package/dist/EventProvider.js +19 -25
  14. package/dist/FeaturesRegistry.js +4 -1
  15. package/dist/FontFace.js +7 -95
  16. package/dist/InitialConfiguration.js +37 -21
  17. package/dist/Keys.js +74 -0
  18. package/dist/ManagedStyles.js +83 -0
  19. package/dist/MediaRange.js +109 -0
  20. package/dist/{util/parseProperties.js → PropertiesFileFormat.js} +6 -0
  21. package/dist/Render.js +3 -2
  22. package/dist/Runtimes.js +107 -0
  23. package/dist/StaticArea.js +3 -0
  24. package/dist/StaticAreaItem.js +31 -10
  25. package/dist/SystemCSSVars.js +4 -25
  26. package/dist/UI5Element.js +66 -65
  27. package/dist/UI5ElementMetadata.js +18 -2
  28. package/dist/asset-registries/Icons.js +117 -14
  29. package/dist/asset-registries/Illustrations.js +30 -0
  30. package/dist/asset-registries/LocaleData.js +97 -68
  31. package/dist/asset-registries/Themes.js +23 -33
  32. package/dist/asset-registries/i18n.js +61 -50
  33. package/dist/assets-meta/IconCollectionsAlias.js +18 -0
  34. package/dist/config/Icons.js +52 -0
  35. package/dist/config/Language.js +15 -15
  36. package/dist/config/Theme.js +25 -0
  37. package/dist/css/BusyIndicator.css +76 -0
  38. package/dist/css/FontFace.css +75 -0
  39. package/dist/css/OverrideFontFace.css +32 -0
  40. package/dist/css/SystemCSSVars.css +17 -0
  41. package/dist/delegate/ItemNavigation.js +163 -105
  42. package/dist/delegate/ScrollEnablement.js +25 -19
  43. package/dist/features/F6Navigation.js +108 -0
  44. package/dist/features/OpenUI5Enablement.js +119 -0
  45. package/dist/features/OpenUI5Support.js +41 -6
  46. package/dist/generated/AssetParameters.js +1 -1
  47. package/dist/generated/VersionInfo.js +10 -0
  48. package/dist/generated/css/BusyIndicator.css.js +5 -0
  49. package/dist/generated/css/FontFace.css.js +5 -0
  50. package/dist/generated/css/OverrideFontFace.css.js +5 -0
  51. package/dist/generated/css/SystemCSSVars.css.js +5 -0
  52. package/dist/i18nBundle.js +38 -4
  53. package/dist/renderer/LitRenderer.js +39 -18
  54. package/dist/renderer/directives/style-map.js +72 -0
  55. package/dist/renderer/executeTemplate.js +19 -2
  56. package/dist/resources/bundle.esm.js +15 -104
  57. package/dist/resources/bundle.esm.js.map +1 -1
  58. package/dist/sap/base/Log.js +241 -0
  59. package/dist/sap/base/assert.js +8 -0
  60. package/dist/sap/base/security/URLListValidator.js +148 -0
  61. package/dist/sap/base/security/encodeCSS.js +14 -0
  62. package/dist/sap/base/security/encodeXML.js +23 -0
  63. package/dist/sap/base/security/sanitizeHTML.js +16 -0
  64. package/dist/sap/base/strings/toHex.js +8 -0
  65. package/dist/sap/base/util/now.js +7 -0
  66. package/dist/sap/ui/thirdparty/caja-html-sanitizer.js +3585 -0
  67. package/dist/test-resources/assets/Themes.js +8 -8
  68. package/dist/test-resources/elements/WithStaticArea.js +2 -1
  69. package/dist/test-resources/pages/AllTestElements.html +3 -0
  70. package/dist/test-resources/pages/i18n.html +2 -9
  71. package/dist/test-resources/specs/ConfigurationChange.spec.js +10 -8
  72. package/dist/test-resources/specs/ConfigurationScript.spec.js +25 -23
  73. package/dist/test-resources/specs/ConfigurationURL.spec.js +65 -17
  74. package/dist/test-resources/specs/CustomTheme.spec.js +9 -7
  75. package/dist/test-resources/specs/EventProvider.spec.js +63 -0
  76. package/dist/test-resources/specs/StaticArea.spec.js +56 -12
  77. package/dist/test-resources/specs/Theming.spec.js +12 -10
  78. package/dist/test-resources/specs/UI5ElementInvalidation.js +30 -28
  79. package/dist/test-resources/specs/UI5ElementLifecycle.js +12 -10
  80. package/dist/test-resources/specs/UI5ElementListenForChildPropChanges.spec.js +12 -10
  81. package/dist/test-resources/specs/UI5ElementMetadataExt.js +9 -7
  82. package/dist/test-resources/specs/UI5ElementPropertyValidation.js +7 -5
  83. package/dist/test-resources/specs/UI5ElementPropsAndAttrs.spec.js +37 -35
  84. package/dist/test-resources/specs/UI5ElementShadowDOM.js +10 -8
  85. package/dist/test-resources/specs/UI5ElementSlots.js +10 -8
  86. package/dist/theming/CustomStyle.js +23 -3
  87. package/dist/theming/applyTheme.js +11 -10
  88. package/dist/theming/getEffectiveLinksHrefs.js +27 -0
  89. package/dist/theming/getEffectiveStyle.js +9 -0
  90. package/dist/theming/getStylesString.js +4 -2
  91. package/dist/theming/getThemeDesignerTheme.js +25 -5
  92. package/dist/theming/preloadLinks.js +23 -0
  93. package/dist/types/CSSColor.js +9 -0
  94. package/dist/types/DataType.js +12 -0
  95. package/dist/types/Float.js +4 -0
  96. package/dist/types/Integer.js +4 -0
  97. package/dist/types/InvisibleMessageMode.js +30 -0
  98. package/dist/updateShadowRoot.js +8 -4
  99. package/dist/util/AriaLabelHelper.js +4 -4
  100. package/dist/util/ColorConversion.js +370 -0
  101. package/dist/util/FocusableElements.js +13 -7
  102. package/dist/util/HTMLSanitizer.js +7 -0
  103. package/dist/util/InvisibleMessage.js +60 -0
  104. package/dist/util/PopupUtils.js +93 -0
  105. package/dist/util/TabbableElements.js +5 -1
  106. package/dist/util/clamp.js +12 -0
  107. package/dist/util/createLinkInHead.js +19 -0
  108. package/dist/util/escapeRegex.js +10 -0
  109. package/dist/util/generateHighlightedMarkup.js +42 -0
  110. package/dist/util/getClassCopy.js +10 -0
  111. package/dist/util/getNormalizedTarget.js +16 -0
  112. package/dist/util/isElementInView.js +15 -0
  113. package/dist/util/isNodeHidden.js +1 -1
  114. package/dist/util/isNodeTabbable.js +4 -4
  115. package/hash.txt +1 -0
  116. package/index.js +1 -1
  117. package/lib/generate-asset-parameters/index.js +0 -1
  118. package/lib/generate-styles/index.js +18 -0
  119. package/lib/generate-version-info/index.js +27 -0
  120. package/package-scripts.js +31 -15
  121. package/package.json +19 -10
  122. package/src/AssetRegistry.js +8 -9
  123. package/src/Boot.js +17 -6
  124. package/src/CSP.js +59 -0
  125. package/src/CustomElementsRegistry.js +60 -4
  126. package/src/CustomElementsScope.js +20 -98
  127. package/src/CustomElementsScopeUtils.js +108 -0
  128. package/src/DOMObserver.js +6 -2
  129. package/src/Device.js +69 -776
  130. package/src/EventProvider.js +19 -25
  131. package/src/FeaturesRegistry.js +4 -1
  132. package/src/FontFace.js +7 -95
  133. package/src/InitialConfiguration.js +37 -21
  134. package/src/Keys.js +74 -0
  135. package/src/ManagedStyles.js +83 -0
  136. package/src/MediaRange.js +109 -0
  137. package/src/{util/parseProperties.js → PropertiesFileFormat.js} +6 -0
  138. package/src/Render.js +3 -2
  139. package/src/Runtimes.js +107 -0
  140. package/src/StaticArea.js +3 -0
  141. package/src/StaticAreaItem.js +31 -10
  142. package/src/SystemCSSVars.js +4 -25
  143. package/src/UI5Element.js +66 -65
  144. package/src/UI5ElementMetadata.js +18 -2
  145. package/src/asset-registries/Icons.js +117 -14
  146. package/src/asset-registries/Illustrations.js +30 -0
  147. package/src/asset-registries/LocaleData.js +97 -68
  148. package/src/asset-registries/Themes.js +23 -33
  149. package/src/asset-registries/i18n.js +61 -50
  150. package/src/assets-meta/IconCollectionsAlias.js +18 -0
  151. package/src/config/Icons.js +52 -0
  152. package/src/config/Language.js +15 -15
  153. package/src/config/Theme.js +25 -0
  154. package/src/css/BusyIndicator.css +76 -0
  155. package/src/css/FontFace.css +75 -0
  156. package/src/css/OverrideFontFace.css +32 -0
  157. package/src/css/SystemCSSVars.css +17 -0
  158. package/src/delegate/ItemNavigation.js +163 -105
  159. package/src/delegate/ScrollEnablement.js +25 -19
  160. package/src/features/F6Navigation.js +108 -0
  161. package/src/features/OpenUI5Enablement.js +119 -0
  162. package/src/features/OpenUI5Support.js +41 -6
  163. package/src/i18nBundle.js +38 -4
  164. package/src/renderer/LitRenderer.js +39 -18
  165. package/src/renderer/directives/style-map.js +72 -0
  166. package/src/renderer/executeTemplate.js +19 -2
  167. package/src/theming/CustomStyle.js +23 -3
  168. package/src/theming/applyTheme.js +11 -10
  169. package/src/theming/getEffectiveLinksHrefs.js +27 -0
  170. package/src/theming/getEffectiveStyle.js +9 -0
  171. package/src/theming/getStylesString.js +4 -2
  172. package/src/theming/getThemeDesignerTheme.js +25 -5
  173. package/src/theming/preloadLinks.js +23 -0
  174. package/src/types/CSSColor.js +9 -0
  175. package/src/types/DataType.js +12 -0
  176. package/src/types/Float.js +4 -0
  177. package/src/types/Integer.js +4 -0
  178. package/src/types/InvisibleMessageMode.js +30 -0
  179. package/src/updateShadowRoot.js +8 -4
  180. package/src/util/AriaLabelHelper.js +4 -4
  181. package/src/util/ColorConversion.js +370 -0
  182. package/src/util/FocusableElements.js +13 -7
  183. package/src/util/HTMLSanitizer.js +7 -0
  184. package/src/util/InvisibleMessage.js +60 -0
  185. package/src/util/PopupUtils.js +93 -0
  186. package/src/util/TabbableElements.js +5 -1
  187. package/src/util/clamp.js +12 -0
  188. package/src/util/createLinkInHead.js +19 -0
  189. package/src/util/escapeRegex.js +10 -0
  190. package/src/util/generateHighlightedMarkup.js +42 -0
  191. package/src/util/getClassCopy.js +10 -0
  192. package/src/util/getNormalizedTarget.js +16 -0
  193. package/src/util/isElementInView.js +15 -0
  194. package/src/util/isNodeHidden.js +1 -1
  195. package/src/util/isNodeTabbable.js +4 -4
  196. package/used-modules.txt +10 -0
  197. package/dist/SVGIconRegistry.js +0 -73
  198. package/dist/config/AssetsPath.js +0 -17
  199. package/dist/features/PropertiesFormatSupport.js +0 -8
  200. package/dist/renderer/ifDefined.js +0 -21
  201. package/dist/renderer/scopeHTML.js +0 -32
  202. package/dist/theming/createThemePropertiesStyleTag.js +0 -20
  203. package/dist/util/EffectiveAssetPath.js +0 -27
  204. package/dist/util/encodeCSS.js +0 -24
  205. package/src/SVGIconRegistry.js +0 -73
  206. package/src/config/AssetsPath.js +0 -17
  207. package/src/features/PropertiesFormatSupport.js +0 -8
  208. package/src/renderer/ifDefined.js +0 -21
  209. package/src/renderer/scopeHTML.js +0 -32
  210. package/src/theming/createThemePropertiesStyleTag.js +0 -20
  211. package/src/util/EffectiveAssetPath.js +0 -27
  212. package/src/util/encodeCSS.js +0 -24
@@ -5,10 +5,11 @@ import {
5
5
  isRight,
6
6
  isHome,
7
7
  isEnd,
8
+ isPageDown,
9
+ isPageUp,
8
10
  } from "../Keys.js";
9
11
  import getActiveElement from "../util/getActiveElement.js";
10
12
 
11
- import EventProvider from "../EventProvider.js";
12
13
  import NavigationMode from "../types/NavigationMode.js";
13
14
  import ItemNavigationBehavior from "../types/ItemNavigationBehavior.js";
14
15
 
@@ -38,14 +39,14 @@ import ItemNavigationBehavior from "../types/ItemNavigationBehavior.js";
38
39
  * If the items are Objects with "id" and "_tabIndex" however, it is the developer's responsibility to apply these and the easiest way is to have the root component invalidated by ItemNavigation.
39
40
  * To do so, set the "affectedPropertiesNames" constructor property to point to one or more of the root component's properties that need refreshing when "_tabIndex" is changed deeply.
40
41
  *
41
- * 2) Call the "update" method of ItemNavigation whenever you want to change the current item.
42
+ * 2) Call the "setCurrentItem" method of ItemNavigation whenever you want to change the current item.
42
43
  * This is most commonly required if the user for example clicks on an item and thus selects it directly.
43
- * Pass as the only argument to "update" the item that becomes current (must be one of the items, returned by "getItemsCallback").
44
+ * Pass as the only argument to "setCurrentItem" the item that becomes current (must be one of the items, returned by "getItemsCallback").
44
45
  *
45
46
  * @class
46
47
  * @public
47
48
  */
48
- class ItemNavigation extends EventProvider {
49
+ class ItemNavigation {
49
50
  /**
50
51
  *
51
52
  * @param rootWebComponent the component to operate on (component that slots or contains within its shadow root the items the user navigates among)
@@ -53,6 +54,7 @@ class ItemNavigation extends EventProvider {
53
54
  * - currentIndex: the index of the item that will be initially selected (from which navigation will begin)
54
55
  * - navigationMode (Auto|Horizontal|Vertical): whether the items are displayed horizontally (Horizontal), vertically (Vertical) or as a matrix (Auto) meaning the user can navigate in both directions (up/down and left/right)
55
56
  * - rowSize: tells how many items per row there are when the items are not rendered as a flat list but rather as a matrix. Relevant for navigationMode=Auto
57
+ * - skipItemsSize: tells how many items upon PAGE_UP and PAGE_DOWN should be skipped to applying the focus on the next item
56
58
  * - behavior (Static|Cycling): tells what to do when trying to navigate beyond the first and last items
57
59
  * Static means that nothing happens if the user tries to navigate beyond the first/last item.
58
60
  * Cycling means that when the user navigates beyond the last item they go to the first and vice versa.
@@ -60,171 +62,245 @@ class ItemNavigation extends EventProvider {
60
62
  * - affectedPropertiesNames: a list of metadata properties on the root component which, upon user navigation, will be reassigned by address thus causing the root component to invalidate
61
63
  */
62
64
  constructor(rootWebComponent, options = {}) {
63
- super();
64
-
65
- this.currentIndex = options.currentIndex || 0;
66
- this.rowSize = options.rowSize || 1;
67
- this.behavior = options.behavior || ItemNavigationBehavior.Static;
68
- const navigationMode = options.navigationMode;
69
- const autoNavigation = !navigationMode || navigationMode === NavigationMode.Auto;
70
- this.horizontalNavigationOn = autoNavigation || navigationMode === NavigationMode.Horizontal;
71
- this.verticalNavigationOn = autoNavigation || navigationMode === NavigationMode.Vertical;
72
-
73
- if (options.affectedPropertiesNames) {
74
- this.affectedPropertiesNames = options.affectedPropertiesNames;
75
- }
65
+ this._setRootComponent(rootWebComponent);
66
+ this._initOptions(options);
67
+ }
76
68
 
77
- if (options.getItemsCallback) {
78
- this._getItems = options.getItemsCallback;
69
+ _setRootComponent(rootWebComponent) {
70
+ if (!rootWebComponent.isUI5Element) {
71
+ throw new Error("The root web component must be a UI5 Element instance");
79
72
  }
80
-
81
73
  this.rootWebComponent = rootWebComponent;
82
- this.rootWebComponent.addEventListener("keydown", this.onkeydown.bind(this));
74
+ this.rootWebComponent.addEventListener("keydown", this._onkeydown.bind(this));
83
75
  this.rootWebComponent._onComponentStateFinalized = () => {
84
76
  this._init();
85
77
  };
86
78
  }
87
79
 
80
+ _initOptions(options) {
81
+ if (typeof options.getItemsCallback !== "function") {
82
+ throw new Error("getItemsCallback is required");
83
+ }
84
+
85
+ this._getItems = options.getItemsCallback;
86
+ this._currentIndex = options.currentIndex || 0;
87
+ this._rowSize = options.rowSize || 1;
88
+ this._behavior = options.behavior || ItemNavigationBehavior.Static;
89
+ this._navigationMode = options.navigationMode || NavigationMode.Auto;
90
+ this._affectedPropertiesNames = options.affectedPropertiesNames || [];
91
+ this._skipItemsSize = options.skipItemsSize || null;
92
+ }
93
+
94
+ /**
95
+ * Call this method to set a new "current" (selected) item in the item navigation
96
+ * Note: the item passed to this function must be one of the items, returned by the getItemsCallback function
97
+ *
98
+ * @public
99
+ * @param current the new selected item
100
+ */
101
+ setCurrentItem(current) {
102
+ const currentItemIndex = this._getItems().indexOf(current);
103
+
104
+ if (currentItemIndex === -1) {
105
+ console.warn(`The provided item is not managed by ItemNavigation`, current); // eslint-disable-line
106
+ return;
107
+ }
108
+
109
+ this._currentIndex = currentItemIndex;
110
+ this._applyTabIndex();
111
+ }
112
+
113
+ /**
114
+ * Call this method to dynamically change the row size
115
+ *
116
+ * @public
117
+ * @param newRowSize
118
+ */
119
+ setRowSize(newRowSize) {
120
+ this._rowSize = newRowSize;
121
+ }
122
+
88
123
  _init() {
89
124
  this._getItems().forEach((item, idx) => {
90
- item._tabIndex = (idx === this.currentIndex) ? "0" : "-1";
125
+ item._tabIndex = (idx === this._currentIndex) ? "0" : "-1";
91
126
  });
92
127
  }
93
128
 
94
- onkeydown(event) {
129
+ _onkeydown(event) {
95
130
  if (!this._canNavigate()) {
96
131
  return;
97
132
  }
98
133
 
99
- if (isUp(event) && this.verticalNavigationOn) {
134
+ const horizontalNavigationOn = this._navigationMode === NavigationMode.Horizontal || this._navigationMode === NavigationMode.Auto;
135
+ const verticalNavigationOn = this._navigationMode === NavigationMode.Vertical || this._navigationMode === NavigationMode.Auto;
136
+
137
+ if (isUp(event) && verticalNavigationOn) {
100
138
  this._handleUp();
101
- } else if (isDown(event) && this.verticalNavigationOn) {
139
+ } else if (isDown(event) && verticalNavigationOn) {
102
140
  this._handleDown();
103
- } else if (isLeft(event) && this.horizontalNavigationOn) {
141
+ } else if (isLeft(event) && horizontalNavigationOn) {
104
142
  this._handleLeft();
105
- } else if (isRight(event) && this.horizontalNavigationOn) {
143
+ } else if (isRight(event) && horizontalNavigationOn) {
106
144
  this._handleRight();
107
145
  } else if (isHome(event)) {
108
146
  this._handleHome();
109
147
  } else if (isEnd(event)) {
110
148
  this._handleEnd();
149
+ } else if (isPageUp(event)) {
150
+ this._handlePageUp();
151
+ } else if (isPageDown(event)) {
152
+ this._handlePageDown();
111
153
  } else {
112
154
  return; // if none of the supported keys is pressed, we don't want to prevent the event or update the item navigation
113
155
  }
114
156
 
115
157
  event.preventDefault();
116
- this.update();
117
- this.focusCurrent();
158
+ this._applyTabIndex();
159
+ this._focusCurrentItem();
118
160
  }
119
161
 
120
162
  _handleUp() {
121
163
  const itemsLength = this._getItems().length;
122
- if (this.currentIndex - this.rowSize >= 0) { // no border reached, just decrease the index by a row
123
- this.currentIndex -= this.rowSize;
164
+ if (this._currentIndex - this._rowSize >= 0) { // no border reached, just decrease the index by a row
165
+ this._currentIndex -= this._rowSize;
124
166
  return;
125
167
  }
126
168
 
127
- if (this.behavior === ItemNavigationBehavior.Cyclic) { // if cyclic, go to the **last** item in the **previous** column
128
- const firstItemInThisColumnIndex = this.currentIndex % this.rowSize;
129
- const firstItemInPreviousColumnIndex = firstItemInThisColumnIndex === 0 ? this.rowSize - 1 : firstItemInThisColumnIndex - 1; // find the first item in the previous column (if the current column is the first column -> move to the last column)
130
- const rows = Math.ceil(itemsLength / this.rowSize); // how many rows there are (even if incomplete, f.e. for 14 items and rowSize=4 -> 4 rows total, although only 2 items on the last row)
131
- let lastItemInPreviousColumnIndex = firstItemInPreviousColumnIndex + (rows - 1) * this.rowSize; // multiply rows by columns, and add the column's first item's index
169
+ if (this._behavior === ItemNavigationBehavior.Cyclic) { // if cyclic, go to the **last** item in the **previous** column
170
+ const firstItemInThisColumnIndex = this._currentIndex % this._rowSize;
171
+ const firstItemInPreviousColumnIndex = firstItemInThisColumnIndex === 0 ? this._rowSize - 1 : firstItemInThisColumnIndex - 1; // find the first item in the previous column (if the current column is the first column -> move to the last column)
172
+ const rows = Math.ceil(itemsLength / this._rowSize); // how many rows there are (even if incomplete, f.e. for 14 items and _rowSize=4 -> 4 rows total, although only 2 items on the last row)
173
+ let lastItemInPreviousColumnIndex = firstItemInPreviousColumnIndex + (rows - 1) * this._rowSize; // multiply rows by columns, and add the column's first item's index
132
174
  if (lastItemInPreviousColumnIndex > itemsLength - 1) { // for incomplete rows, use the previous row's last item, as for them the last item is missing
133
- lastItemInPreviousColumnIndex -= this.rowSize;
175
+ lastItemInPreviousColumnIndex -= this._rowSize;
134
176
  }
135
- this.currentIndex = lastItemInPreviousColumnIndex;
177
+ this._currentIndex = lastItemInPreviousColumnIndex;
136
178
  } else { // not cyclic, so just go to the first item
137
- this.currentIndex = 0;
179
+ this._currentIndex = 0;
138
180
  }
139
181
  }
140
182
 
141
183
  _handleDown() {
142
184
  const itemsLength = this._getItems().length;
143
- if (this.currentIndex + this.rowSize < itemsLength) { // no border reached, just increase the index by a row
144
- this.currentIndex += this.rowSize;
185
+ if (this._currentIndex + this._rowSize < itemsLength) { // no border reached, just increase the index by a row
186
+ this._currentIndex += this._rowSize;
145
187
  return;
146
188
  }
147
189
 
148
- if (this.behavior === ItemNavigationBehavior.Cyclic) { // if cyclic, go to the **first** item in the **next** column
149
- const firstItemInThisColumnIndex = this.currentIndex % this.rowSize; // find the first item in the current column first
150
- const firstItemInNextColumnIndex = (firstItemInThisColumnIndex + 1) % this.rowSize; // to get the first item in the next column, just increase the index by 1. The modulo by rows is for the case when we are at the last column
151
- this.currentIndex = firstItemInNextColumnIndex;
190
+ if (this._behavior === ItemNavigationBehavior.Cyclic) { // if cyclic, go to the **first** item in the **next** column
191
+ const firstItemInThisColumnIndex = this._currentIndex % this._rowSize; // find the first item in the current column first
192
+ const firstItemInNextColumnIndex = (firstItemInThisColumnIndex + 1) % this._rowSize; // to get the first item in the next column, just increase the index by 1. The modulo by rows is for the case when we are at the last column
193
+ this._currentIndex = firstItemInNextColumnIndex;
152
194
  } else { // not cyclic, so just go to the last item
153
- this.currentIndex = itemsLength - 1;
195
+ this._currentIndex = itemsLength - 1;
154
196
  }
155
197
  }
156
198
 
157
199
  _handleLeft() {
158
200
  const itemsLength = this._getItems().length;
159
- if (this.currentIndex > 0) {
160
- this.currentIndex -= 1;
201
+ if (this._currentIndex > 0) {
202
+ this._currentIndex -= 1;
161
203
  return;
162
204
  }
163
205
 
164
- if (this.behavior === ItemNavigationBehavior.Cyclic) { // go to the first item in the next column
165
- this.currentIndex = itemsLength - 1;
206
+ if (this._behavior === ItemNavigationBehavior.Cyclic) { // go to the first item in the next column
207
+ this._currentIndex = itemsLength - 1;
166
208
  }
167
209
  }
168
210
 
169
211
  _handleRight() {
170
212
  const itemsLength = this._getItems().length;
171
- if (this.currentIndex < itemsLength - 1) {
172
- this.currentIndex += 1;
213
+ if (this._currentIndex < itemsLength - 1) {
214
+ this._currentIndex += 1;
173
215
  return;
174
216
  }
175
217
 
176
- if (this.behavior === ItemNavigationBehavior.Cyclic) { // go to the first item in the next column
177
- this.currentIndex = 0;
218
+ if (this._behavior === ItemNavigationBehavior.Cyclic) { // go to the first item in the next column
219
+ this._currentIndex = 0;
178
220
  }
179
221
  }
180
222
 
181
223
  _handleHome() {
182
- const homeEndRange = this.rowSize > 1 ? this.rowSize : this._getItems().length;
183
- this.currentIndex -= this.currentIndex % homeEndRange;
224
+ const homeEndRange = this._rowSize > 1 ? this._rowSize : this._getItems().length;
225
+ this._currentIndex -= this._currentIndex % homeEndRange;
184
226
  }
185
227
 
186
228
  _handleEnd() {
187
- const homeEndRange = this.rowSize > 1 ? this.rowSize : this._getItems().length;
188
- this.currentIndex += (homeEndRange - 1 - this.currentIndex % homeEndRange); // eslint-disable-line
229
+ const homeEndRange = this._rowSize > 1 ? this._rowSize : this._getItems().length;
230
+ this._currentIndex += (homeEndRange - 1 - this._currentIndex % homeEndRange); // eslint-disable-line
231
+ }
232
+
233
+ _handlePageUp() {
234
+ if (this._rowSize > 1) {
235
+ // eslint-disable-next-line
236
+ // TODO: handle page up on matrix (grid) layout - ColorPalette, ProductSwitch.
237
+ return;
238
+ }
239
+ this._handlePageUpFlat();
240
+ }
241
+
242
+ _handlePageDown() {
243
+ if (this._rowSize > 1) {
244
+ // eslint-disable-next-line
245
+ // TODO: handle page up on matrix (grid) layout - ColorPalette, ProductSwitch.
246
+ return;
247
+ }
248
+ this._handlePageDownFlat();
189
249
  }
190
250
 
191
251
  /**
192
- * Call this method to set a new "current" (selected) item in the item navigation
193
- * Note: the item passed to this function must be one of the items, returned by the getItemsCallback function
194
- *
195
- * @public
196
- * @param current the new selected item
252
+ * Handles PAGE_UP in a flat list-like structure, both vertically and horizontally.
197
253
  */
198
- update(current) {
199
- const origItems = this._getItems();
254
+ _handlePageUpFlat() {
255
+ if (this._skipItemsSize === null) {
256
+ // Move the focus to the very top (as Home).
257
+ this._currentIndex -= this._currentIndex;
258
+ }
200
259
 
201
- if (current) {
202
- this.currentIndex = this._getItems().indexOf(current);
260
+ if (this._currentIndex + 1 > this._skipItemsSize) {
261
+ // When there are more than "skipItemsSize" number of items to the top,
262
+ // move the focus up/left with the predefined number.
263
+ this._currentIndex -= this._skipItemsSize;
264
+ } else {
265
+ // Otherwise, move the focus to the very top (as Home).
266
+ this._currentIndex -= this._currentIndex;
203
267
  }
268
+ }
204
269
 
205
- if (!origItems[this.currentIndex]
206
- || (origItems[this.currentIndex]._tabIndex && origItems[this.currentIndex]._tabIndex === "0")) {
207
- return;
270
+ /**
271
+ * Handles PAGE_DOWN in a flat list-like structure, both vertically and horizontally.
272
+ */
273
+ _handlePageDownFlat() {
274
+ if (this._skipItemsSize === null) {
275
+ // Move the focus to the very bottom (as End).
276
+ this._currentIndex = this._getItems().length - 1;
208
277
  }
209
278
 
210
- const items = origItems.slice(0);
279
+ const currentToEndRange = this._getItems().length - this._currentIndex - 1;
211
280
 
212
- for (let i = 0; i < items.length; i++) {
213
- items[i]._tabIndex = (i === this.currentIndex ? "0" : "-1");
281
+ if (currentToEndRange > this._skipItemsSize) {
282
+ // When there are more than "skipItemsSize" number of items until the bottom,
283
+ // move the focus down/right with the predefined number.
284
+ this._currentIndex += this._skipItemsSize;
285
+ } else {
286
+ // Otherwise, move the focus to the very bottom (as End).
287
+ this._currentIndex = this._getItems().length - 1;
214
288
  }
289
+ }
215
290
 
216
- if (Array.isArray(this.affectedPropertiesNames)) {
217
- this.affectedPropertiesNames.forEach(propName => {
218
- const prop = this.rootWebComponent[propName];
219
- this.rootWebComponent[propName] = Array.isArray(prop) ? [...prop] : { ...prop };
220
- });
291
+ _applyTabIndex() {
292
+ const items = this._getItems();
293
+ for (let i = 0; i < items.length; i++) {
294
+ items[i]._tabIndex = i === this._currentIndex ? "0" : "-1";
221
295
  }
296
+
297
+ this._affectedPropertiesNames.forEach(propName => {
298
+ const prop = this.rootWebComponent[propName];
299
+ this.rootWebComponent[propName] = Array.isArray(prop) ? [...prop] : { ...prop };
300
+ });
222
301
  }
223
302
 
224
- /**
225
- * @private
226
- */
227
- focusCurrent() {
303
+ _focusCurrentItem() {
228
304
  const currentItem = this._getCurrentItem();
229
305
  if (currentItem) {
230
306
  currentItem.focus();
@@ -246,15 +322,15 @@ class ItemNavigation extends EventProvider {
246
322
  }
247
323
 
248
324
  // normalize the index
249
- while (this.currentIndex >= items.length) {
250
- this.currentIndex -= this.rowSize;
325
+ while (this._currentIndex >= items.length) {
326
+ this._currentIndex -= this._rowSize;
251
327
  }
252
328
 
253
- if (this.currentIndex < 0) {
254
- this.currentIndex = 0;
329
+ if (this._currentIndex < 0) {
330
+ this._currentIndex = 0;
255
331
  }
256
332
 
257
- const currentItem = items[this.currentIndex];
333
+ const currentItem = items[this._currentIndex];
258
334
 
259
335
  if (!currentItem) {
260
336
  return;
@@ -270,24 +346,6 @@ class ItemNavigation extends EventProvider {
270
346
 
271
347
  return this.rootWebComponent.getDomRef().querySelector(`#${currentItem.id}`);
272
348
  }
273
-
274
- /**
275
- * Set to callback that returns the list of items to navigate among
276
- * @public
277
- * @param callback a function that returns an array of items to navigate among
278
- */
279
- set getItemsCallback(callback) {
280
- this._getItems = callback;
281
- }
282
-
283
- /**
284
- * @public
285
- * @deprecated
286
- * @param val
287
- */
288
- set current(val) {
289
- this.currentIndex = val;
290
- }
291
349
  }
292
350
 
293
351
  export default ItemNavigation;
@@ -1,9 +1,9 @@
1
- import { isPhone } from "../Device.js";
1
+ import { supportsTouch } from "../Device.js";
2
2
  import EventProvider from "../EventProvider.js";
3
3
  import scroll from "../animations/scroll.js";
4
4
 
5
5
  const scrollEventName = "scroll";
6
- const touchEndEventName = isPhone() ? "touchend" : "mouseup";
6
+ const touchEndEventName = supportsTouch() ? "touchend" : "mouseup";
7
7
 
8
8
  class ScrollEnablement extends EventProvider {
9
9
  constructor(containerComponent) {
@@ -13,7 +13,7 @@ class ScrollEnablement extends EventProvider {
13
13
  this.mouseUp = this.ontouchend.bind(this);
14
14
  this.touchStart = this.ontouchstart.bind(this);
15
15
 
16
- this.isPhone = isPhone();
16
+ this.supportsTouch = supportsTouch();
17
17
 
18
18
  // On Android devices touchmove is thrown one more time than neccessary (together with touchend)
19
19
  // so we have to cache the previus coordinates in order to provide correct parameters in the
@@ -25,7 +25,7 @@ class ScrollEnablement extends EventProvider {
25
25
  this.startX = 0;
26
26
  this.startY = 0;
27
27
 
28
- if (this.isPhone) {
28
+ if (this.supportsTouch) {
29
29
  containerComponent.addEventListener("touchstart", this.touchStart, { passive: true });
30
30
  containerComponent.addEventListener("touchmove", this.mouseMove, { passive: true });
31
31
  containerComponent.addEventListener("touchend", this.mouseUp, { passive: true });
@@ -70,7 +70,13 @@ class ScrollEnablement extends EventProvider {
70
70
  this._container.scrollTop = top;
71
71
  }
72
72
 
73
- move(dx, dy) {
73
+ move(dx, dy, disableAnimation) {
74
+ if (disableAnimation) {
75
+ this._container.scrollLeft += dx;
76
+ this._container.scrollTop += dy;
77
+ return;
78
+ }
79
+
74
80
  return scroll({
75
81
  element: this._container,
76
82
  dx,
@@ -88,17 +94,17 @@ class ScrollEnablement extends EventProvider {
88
94
 
89
95
  _isTouchInside(touch) {
90
96
  const rect = this._container.getBoundingClientRect();
91
- const x = this.isPhone ? touch.clientX : touch.x;
92
- const y = this.isPhone ? touch.clientY : touch.y;
97
+ const x = this.supportsTouch ? touch.clientX : touch.x;
98
+ const y = this.supportsTouch ? touch.clientY : touch.y;
93
99
 
94
100
  return x >= rect.left && x <= rect.right
95
101
  && y >= rect.top && y <= rect.bottom;
96
102
  }
97
103
 
98
104
  ontouchstart(event) {
99
- const touch = this.isPhone ? event.touches[0] : null;
105
+ const touch = this.supportsTouch ? event.touches[0] : null;
100
106
 
101
- if (!this.isPhone) {
107
+ if (!this.supportsTouch) {
102
108
  document.addEventListener("mouseup", this.mouseUp, { passive: true });
103
109
  document.addEventListener("mousemove", this.mouseMove, { passive: true });
104
110
  } else {
@@ -107,10 +113,10 @@ class ScrollEnablement extends EventProvider {
107
113
  this.startY = touch.pageY;
108
114
  }
109
115
 
110
- this._prevDragX = this.isPhone ? touch.pageX : event.x;
111
- this._prevDragY = this.isPhone ? touch.pageY : event.y;
116
+ this._prevDragX = this.supportsTouch ? touch.pageX : event.x;
117
+ this._prevDragY = this.supportsTouch ? touch.pageY : event.y;
112
118
 
113
- this._canScroll = this._isTouchInside(this.isPhone ? touch : event);
119
+ this._canScroll = this._isTouchInside(this.supportsTouch ? touch : event);
114
120
  }
115
121
 
116
122
  ontouchmove(event) {
@@ -119,10 +125,10 @@ class ScrollEnablement extends EventProvider {
119
125
  }
120
126
 
121
127
  const container = this._container;
122
- const touch = this.isPhone ? event.touches[0] : null;
128
+ const touch = this.supportsTouch ? event.touches[0] : null;
123
129
 
124
- const dragX = this.isPhone ? touch.pageX : event.x;
125
- const dragY = this.isPhone ? touch.pageY : event.y;
130
+ const dragX = this.supportsTouch ? touch.pageX : event.x;
131
+ const dragY = this.supportsTouch ? touch.pageY : event.y;
126
132
 
127
133
  container.scrollLeft += this._prevDragX - dragX;
128
134
  container.scrollTop += this._prevDragY - dragY;
@@ -140,7 +146,7 @@ class ScrollEnablement extends EventProvider {
140
146
  }
141
147
 
142
148
  ontouchend(event) {
143
- if (this.isPhone) {
149
+ if (this.supportsTouch) {
144
150
  const deltaX = Math.abs(event.changedTouches[0].pageX - this.startX);
145
151
  const deltaY = Math.abs(event.changedTouches[0].pageY - this.startY);
146
152
 
@@ -154,8 +160,8 @@ class ScrollEnablement extends EventProvider {
154
160
  }
155
161
 
156
162
  const container = this._container;
157
- const dragX = this.isPhone ? event.changedTouches[0].pageX : event.x;
158
- const dragY = this.isPhone ? event.changedTouches[0].pageY : event.y;
163
+ const dragX = this.supportsTouch ? event.changedTouches[0].pageX : event.x;
164
+ const dragY = this.supportsTouch ? event.changedTouches[0].pageY : event.y;
159
165
 
160
166
  container.scrollLeft += this._prevDragX - dragX;
161
167
  container.scrollTop += this._prevDragY - dragY;
@@ -172,7 +178,7 @@ class ScrollEnablement extends EventProvider {
172
178
  this._prevDragX = dragX;
173
179
  this._prevDragY = dragY;
174
180
 
175
- if (!this.isPhone) {
181
+ if (!this.supportsTouch) {
176
182
  document.removeEventListener("mousemove", this.mouseMove, { passive: true });
177
183
  document.removeEventListener("mouseup", this.mouseUp);
178
184
  }
@@ -0,0 +1,108 @@
1
+ import { registerFeature } from "../FeaturesRegistry.js";
2
+ import { isF6Next, isF6Previous } from "../Keys.js";
3
+ import { getFirstFocusableElement } from "../util/FocusableElements.js";
4
+
5
+ class F6Navigation {
6
+ init() {
7
+ this.keydownHandler = this._keydownHandler.bind(this);
8
+ this.attachEventListeners();
9
+ this.selectedGroup = null;
10
+ this.groups = [];
11
+ }
12
+
13
+ attachEventListeners() {
14
+ document.addEventListener("keydown", this.keydownHandler);
15
+ }
16
+
17
+ async _keydownHandler(event) {
18
+ event.preventDefault();
19
+
20
+ if (isF6Next(event)) {
21
+ this.updateGroups();
22
+ if (this.groups.length < 1) {
23
+ return;
24
+ }
25
+
26
+ const nextIndex = this.groups.indexOf(this.selectedGroup);
27
+ let nextElement = null;
28
+
29
+ if (nextIndex > -1) {
30
+ if (nextIndex + 1 >= this.groups.length) {
31
+ nextElement = this.groups[0];
32
+ } else {
33
+ nextElement = this.groups[nextIndex + 1];
34
+ }
35
+ } else {
36
+ nextElement = this.groups[0];
37
+ }
38
+
39
+ const elementToFocus = await getFirstFocusableElement(nextElement.isUI5Element ? nextElement.getDomRef() : nextElement, true);
40
+ elementToFocus.focus();
41
+ }
42
+
43
+ if (isF6Previous(event)) {
44
+ this.updateGroups();
45
+ if (this.groups.length < 1) {
46
+ return;
47
+ }
48
+
49
+ const nextIndex = this.groups.indexOf(this.selectedGroup);
50
+ let nextElement = null;
51
+
52
+ if (nextIndex > -1) {
53
+ if (nextIndex - 1 < 0) {
54
+ nextElement = this.groups[this.groups.length - 1];
55
+ } else {
56
+ // Handle the situation where the first focusable element of two neighbor groups is the same
57
+ // For example:
58
+ // <ui5-flexible-column-layout>
59
+ // <ui5-list>
60
+ // <ui5-li>List Item</ui5-li>
61
+ // </ui5-list>
62
+ // </ui5-flexible-column-layout>
63
+ // Here for both FCL & List the firstFoccusableElement is the same (the ui5-li)
64
+
65
+ const firstFocusable = await getFirstFocusableElement(this.groups[nextIndex - 1], true);
66
+ const shouldSkipParent = firstFocusable === await getFirstFocusableElement(this.groups[nextIndex], true);
67
+
68
+ nextElement = this.groups[shouldSkipParent ? nextIndex - 2 : nextIndex - 1];
69
+ }
70
+ } else {
71
+ nextElement = this.groups[this.groups.length - 1];
72
+ }
73
+
74
+ const elementToFocus = await getFirstFocusableElement(nextElement.isUI5Element ? nextElement.getDomRef() : nextElement, true);
75
+ elementToFocus.focus();
76
+ }
77
+ }
78
+
79
+ removeEventListeners() {
80
+ document.removeEventListener("keydown", this.keydownHandler);
81
+ }
82
+
83
+ updateGroups() {
84
+ this.setSelectedGroup(document.activeElement);
85
+ this.setGroups();
86
+ }
87
+
88
+ setGroups() {
89
+ this.groups = Array.from(document.querySelectorAll("[data-sap-ui-fastnavgroup='true']")).filter(group => group.clientWidth && window.getComputedStyle(group).visibility !== "hidden");
90
+ }
91
+
92
+ setSelectedGroup(element) {
93
+ while (element && element.getAttribute("data-sap-ui-fastnavgroup") !== "true" && element !== document.querySelector("html")) {
94
+ element = element.parentElement ? element.parentNode : element.parentNode.host;
95
+ }
96
+
97
+ this.selectedGroup = element;
98
+ }
99
+
100
+ destroy() {
101
+ this.removeEventListeners();
102
+ }
103
+ }
104
+
105
+ const F6HelperInstance = new F6Navigation();
106
+ registerFeature("F6Navigation", F6HelperInstance);
107
+
108
+ export default F6Navigation;