@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.
- package/.eslintignore +3 -0
- package/CHANGELOG.md +214 -0
- package/README.md +26 -2
- package/bundle.esm.js +10 -7
- package/dist/AssetRegistry.js +8 -9
- package/dist/Boot.js +17 -6
- package/dist/CSP.js +59 -0
- package/dist/CustomElementsRegistry.js +60 -4
- package/dist/CustomElementsScope.js +20 -98
- package/dist/CustomElementsScopeUtils.js +108 -0
- package/dist/DOMObserver.js +6 -2
- package/dist/Device.js +69 -776
- package/dist/EventProvider.js +19 -25
- package/dist/FeaturesRegistry.js +4 -1
- package/dist/FontFace.js +7 -95
- package/dist/InitialConfiguration.js +37 -21
- package/dist/Keys.js +74 -0
- package/dist/ManagedStyles.js +83 -0
- package/dist/MediaRange.js +109 -0
- package/dist/{util/parseProperties.js → PropertiesFileFormat.js} +6 -0
- package/dist/Render.js +3 -2
- package/dist/Runtimes.js +107 -0
- package/dist/StaticArea.js +3 -0
- package/dist/StaticAreaItem.js +31 -10
- package/dist/SystemCSSVars.js +4 -25
- package/dist/UI5Element.js +66 -65
- package/dist/UI5ElementMetadata.js +18 -2
- package/dist/asset-registries/Icons.js +117 -14
- package/dist/asset-registries/Illustrations.js +30 -0
- package/dist/asset-registries/LocaleData.js +97 -68
- package/dist/asset-registries/Themes.js +23 -33
- package/dist/asset-registries/i18n.js +61 -50
- package/dist/assets-meta/IconCollectionsAlias.js +18 -0
- package/dist/config/Icons.js +52 -0
- package/dist/config/Language.js +15 -15
- package/dist/config/Theme.js +25 -0
- package/dist/css/BusyIndicator.css +76 -0
- package/dist/css/FontFace.css +75 -0
- package/dist/css/OverrideFontFace.css +32 -0
- package/dist/css/SystemCSSVars.css +17 -0
- package/dist/delegate/ItemNavigation.js +163 -105
- package/dist/delegate/ScrollEnablement.js +25 -19
- package/dist/features/F6Navigation.js +108 -0
- package/dist/features/OpenUI5Enablement.js +119 -0
- package/dist/features/OpenUI5Support.js +41 -6
- package/dist/generated/AssetParameters.js +1 -1
- package/dist/generated/VersionInfo.js +10 -0
- package/dist/generated/css/BusyIndicator.css.js +5 -0
- package/dist/generated/css/FontFace.css.js +5 -0
- package/dist/generated/css/OverrideFontFace.css.js +5 -0
- package/dist/generated/css/SystemCSSVars.css.js +5 -0
- package/dist/i18nBundle.js +38 -4
- package/dist/renderer/LitRenderer.js +39 -18
- package/dist/renderer/directives/style-map.js +72 -0
- package/dist/renderer/executeTemplate.js +19 -2
- package/dist/resources/bundle.esm.js +15 -104
- package/dist/resources/bundle.esm.js.map +1 -1
- package/dist/sap/base/Log.js +241 -0
- package/dist/sap/base/assert.js +8 -0
- package/dist/sap/base/security/URLListValidator.js +148 -0
- package/dist/sap/base/security/encodeCSS.js +14 -0
- package/dist/sap/base/security/encodeXML.js +23 -0
- package/dist/sap/base/security/sanitizeHTML.js +16 -0
- package/dist/sap/base/strings/toHex.js +8 -0
- package/dist/sap/base/util/now.js +7 -0
- package/dist/sap/ui/thirdparty/caja-html-sanitizer.js +3585 -0
- package/dist/test-resources/assets/Themes.js +8 -8
- package/dist/test-resources/elements/WithStaticArea.js +2 -1
- package/dist/test-resources/pages/AllTestElements.html +3 -0
- package/dist/test-resources/pages/i18n.html +2 -9
- package/dist/test-resources/specs/ConfigurationChange.spec.js +10 -8
- package/dist/test-resources/specs/ConfigurationScript.spec.js +25 -23
- package/dist/test-resources/specs/ConfigurationURL.spec.js +65 -17
- package/dist/test-resources/specs/CustomTheme.spec.js +9 -7
- package/dist/test-resources/specs/EventProvider.spec.js +63 -0
- package/dist/test-resources/specs/StaticArea.spec.js +56 -12
- package/dist/test-resources/specs/Theming.spec.js +12 -10
- package/dist/test-resources/specs/UI5ElementInvalidation.js +30 -28
- package/dist/test-resources/specs/UI5ElementLifecycle.js +12 -10
- package/dist/test-resources/specs/UI5ElementListenForChildPropChanges.spec.js +12 -10
- package/dist/test-resources/specs/UI5ElementMetadataExt.js +9 -7
- package/dist/test-resources/specs/UI5ElementPropertyValidation.js +7 -5
- package/dist/test-resources/specs/UI5ElementPropsAndAttrs.spec.js +37 -35
- package/dist/test-resources/specs/UI5ElementShadowDOM.js +10 -8
- package/dist/test-resources/specs/UI5ElementSlots.js +10 -8
- package/dist/theming/CustomStyle.js +23 -3
- package/dist/theming/applyTheme.js +11 -10
- package/dist/theming/getEffectiveLinksHrefs.js +27 -0
- package/dist/theming/getEffectiveStyle.js +9 -0
- package/dist/theming/getStylesString.js +4 -2
- package/dist/theming/getThemeDesignerTheme.js +25 -5
- package/dist/theming/preloadLinks.js +23 -0
- package/dist/types/CSSColor.js +9 -0
- package/dist/types/DataType.js +12 -0
- package/dist/types/Float.js +4 -0
- package/dist/types/Integer.js +4 -0
- package/dist/types/InvisibleMessageMode.js +30 -0
- package/dist/updateShadowRoot.js +8 -4
- package/dist/util/AriaLabelHelper.js +4 -4
- package/dist/util/ColorConversion.js +370 -0
- package/dist/util/FocusableElements.js +13 -7
- package/dist/util/HTMLSanitizer.js +7 -0
- package/dist/util/InvisibleMessage.js +60 -0
- package/dist/util/PopupUtils.js +93 -0
- package/dist/util/TabbableElements.js +5 -1
- package/dist/util/clamp.js +12 -0
- package/dist/util/createLinkInHead.js +19 -0
- package/dist/util/escapeRegex.js +10 -0
- package/dist/util/generateHighlightedMarkup.js +42 -0
- package/dist/util/getClassCopy.js +10 -0
- package/dist/util/getNormalizedTarget.js +16 -0
- package/dist/util/isElementInView.js +15 -0
- package/dist/util/isNodeHidden.js +1 -1
- package/dist/util/isNodeTabbable.js +4 -4
- package/hash.txt +1 -0
- package/index.js +1 -1
- package/lib/generate-asset-parameters/index.js +0 -1
- package/lib/generate-styles/index.js +18 -0
- package/lib/generate-version-info/index.js +27 -0
- package/package-scripts.js +31 -15
- package/package.json +19 -10
- package/src/AssetRegistry.js +8 -9
- package/src/Boot.js +17 -6
- package/src/CSP.js +59 -0
- package/src/CustomElementsRegistry.js +60 -4
- package/src/CustomElementsScope.js +20 -98
- package/src/CustomElementsScopeUtils.js +108 -0
- package/src/DOMObserver.js +6 -2
- package/src/Device.js +69 -776
- package/src/EventProvider.js +19 -25
- package/src/FeaturesRegistry.js +4 -1
- package/src/FontFace.js +7 -95
- package/src/InitialConfiguration.js +37 -21
- package/src/Keys.js +74 -0
- package/src/ManagedStyles.js +83 -0
- package/src/MediaRange.js +109 -0
- package/src/{util/parseProperties.js → PropertiesFileFormat.js} +6 -0
- package/src/Render.js +3 -2
- package/src/Runtimes.js +107 -0
- package/src/StaticArea.js +3 -0
- package/src/StaticAreaItem.js +31 -10
- package/src/SystemCSSVars.js +4 -25
- package/src/UI5Element.js +66 -65
- package/src/UI5ElementMetadata.js +18 -2
- package/src/asset-registries/Icons.js +117 -14
- package/src/asset-registries/Illustrations.js +30 -0
- package/src/asset-registries/LocaleData.js +97 -68
- package/src/asset-registries/Themes.js +23 -33
- package/src/asset-registries/i18n.js +61 -50
- package/src/assets-meta/IconCollectionsAlias.js +18 -0
- package/src/config/Icons.js +52 -0
- package/src/config/Language.js +15 -15
- package/src/config/Theme.js +25 -0
- package/src/css/BusyIndicator.css +76 -0
- package/src/css/FontFace.css +75 -0
- package/src/css/OverrideFontFace.css +32 -0
- package/src/css/SystemCSSVars.css +17 -0
- package/src/delegate/ItemNavigation.js +163 -105
- package/src/delegate/ScrollEnablement.js +25 -19
- package/src/features/F6Navigation.js +108 -0
- package/src/features/OpenUI5Enablement.js +119 -0
- package/src/features/OpenUI5Support.js +41 -6
- package/src/i18nBundle.js +38 -4
- package/src/renderer/LitRenderer.js +39 -18
- package/src/renderer/directives/style-map.js +72 -0
- package/src/renderer/executeTemplate.js +19 -2
- package/src/theming/CustomStyle.js +23 -3
- package/src/theming/applyTheme.js +11 -10
- package/src/theming/getEffectiveLinksHrefs.js +27 -0
- package/src/theming/getEffectiveStyle.js +9 -0
- package/src/theming/getStylesString.js +4 -2
- package/src/theming/getThemeDesignerTheme.js +25 -5
- package/src/theming/preloadLinks.js +23 -0
- package/src/types/CSSColor.js +9 -0
- package/src/types/DataType.js +12 -0
- package/src/types/Float.js +4 -0
- package/src/types/Integer.js +4 -0
- package/src/types/InvisibleMessageMode.js +30 -0
- package/src/updateShadowRoot.js +8 -4
- package/src/util/AriaLabelHelper.js +4 -4
- package/src/util/ColorConversion.js +370 -0
- package/src/util/FocusableElements.js +13 -7
- package/src/util/HTMLSanitizer.js +7 -0
- package/src/util/InvisibleMessage.js +60 -0
- package/src/util/PopupUtils.js +93 -0
- package/src/util/TabbableElements.js +5 -1
- package/src/util/clamp.js +12 -0
- package/src/util/createLinkInHead.js +19 -0
- package/src/util/escapeRegex.js +10 -0
- package/src/util/generateHighlightedMarkup.js +42 -0
- package/src/util/getClassCopy.js +10 -0
- package/src/util/getNormalizedTarget.js +16 -0
- package/src/util/isElementInView.js +15 -0
- package/src/util/isNodeHidden.js +1 -1
- package/src/util/isNodeTabbable.js +4 -4
- package/used-modules.txt +10 -0
- package/dist/SVGIconRegistry.js +0 -73
- package/dist/config/AssetsPath.js +0 -17
- package/dist/features/PropertiesFormatSupport.js +0 -8
- package/dist/renderer/ifDefined.js +0 -21
- package/dist/renderer/scopeHTML.js +0 -32
- package/dist/theming/createThemePropertiesStyleTag.js +0 -20
- package/dist/util/EffectiveAssetPath.js +0 -27
- package/dist/util/encodeCSS.js +0 -24
- package/src/SVGIconRegistry.js +0 -73
- package/src/config/AssetsPath.js +0 -17
- package/src/features/PropertiesFormatSupport.js +0 -8
- package/src/renderer/ifDefined.js +0 -21
- package/src/renderer/scopeHTML.js +0 -32
- package/src/theming/createThemePropertiesStyleTag.js +0 -20
- package/src/util/EffectiveAssetPath.js +0 -27
- 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 "
|
|
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 "
|
|
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
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
78
|
-
|
|
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.
|
|
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.
|
|
125
|
+
item._tabIndex = (idx === this._currentIndex) ? "0" : "-1";
|
|
91
126
|
});
|
|
92
127
|
}
|
|
93
128
|
|
|
94
|
-
|
|
129
|
+
_onkeydown(event) {
|
|
95
130
|
if (!this._canNavigate()) {
|
|
96
131
|
return;
|
|
97
132
|
}
|
|
98
133
|
|
|
99
|
-
|
|
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) &&
|
|
139
|
+
} else if (isDown(event) && verticalNavigationOn) {
|
|
102
140
|
this._handleDown();
|
|
103
|
-
} else if (isLeft(event) &&
|
|
141
|
+
} else if (isLeft(event) && horizontalNavigationOn) {
|
|
104
142
|
this._handleLeft();
|
|
105
|
-
} else if (isRight(event) &&
|
|
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.
|
|
117
|
-
this.
|
|
158
|
+
this._applyTabIndex();
|
|
159
|
+
this._focusCurrentItem();
|
|
118
160
|
}
|
|
119
161
|
|
|
120
162
|
_handleUp() {
|
|
121
163
|
const itemsLength = this._getItems().length;
|
|
122
|
-
if (this.
|
|
123
|
-
this.
|
|
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.
|
|
128
|
-
const firstItemInThisColumnIndex = this.
|
|
129
|
-
const firstItemInPreviousColumnIndex = firstItemInThisColumnIndex === 0 ? this.
|
|
130
|
-
const rows = Math.ceil(itemsLength / this.
|
|
131
|
-
let lastItemInPreviousColumnIndex = firstItemInPreviousColumnIndex + (rows - 1) * this.
|
|
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.
|
|
175
|
+
lastItemInPreviousColumnIndex -= this._rowSize;
|
|
134
176
|
}
|
|
135
|
-
this.
|
|
177
|
+
this._currentIndex = lastItemInPreviousColumnIndex;
|
|
136
178
|
} else { // not cyclic, so just go to the first item
|
|
137
|
-
this.
|
|
179
|
+
this._currentIndex = 0;
|
|
138
180
|
}
|
|
139
181
|
}
|
|
140
182
|
|
|
141
183
|
_handleDown() {
|
|
142
184
|
const itemsLength = this._getItems().length;
|
|
143
|
-
if (this.
|
|
144
|
-
this.
|
|
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.
|
|
149
|
-
const firstItemInThisColumnIndex = this.
|
|
150
|
-
const firstItemInNextColumnIndex = (firstItemInThisColumnIndex + 1) % this.
|
|
151
|
-
this.
|
|
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.
|
|
195
|
+
this._currentIndex = itemsLength - 1;
|
|
154
196
|
}
|
|
155
197
|
}
|
|
156
198
|
|
|
157
199
|
_handleLeft() {
|
|
158
200
|
const itemsLength = this._getItems().length;
|
|
159
|
-
if (this.
|
|
160
|
-
this.
|
|
201
|
+
if (this._currentIndex > 0) {
|
|
202
|
+
this._currentIndex -= 1;
|
|
161
203
|
return;
|
|
162
204
|
}
|
|
163
205
|
|
|
164
|
-
if (this.
|
|
165
|
-
this.
|
|
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.
|
|
172
|
-
this.
|
|
213
|
+
if (this._currentIndex < itemsLength - 1) {
|
|
214
|
+
this._currentIndex += 1;
|
|
173
215
|
return;
|
|
174
216
|
}
|
|
175
217
|
|
|
176
|
-
if (this.
|
|
177
|
-
this.
|
|
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.
|
|
183
|
-
this.
|
|
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.
|
|
188
|
-
this.
|
|
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
|
-
*
|
|
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
|
-
|
|
199
|
-
|
|
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 (
|
|
202
|
-
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
|
279
|
+
const currentToEndRange = this._getItems().length - this._currentIndex - 1;
|
|
211
280
|
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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.
|
|
250
|
-
this.
|
|
325
|
+
while (this._currentIndex >= items.length) {
|
|
326
|
+
this._currentIndex -= this._rowSize;
|
|
251
327
|
}
|
|
252
328
|
|
|
253
|
-
if (this.
|
|
254
|
-
this.
|
|
329
|
+
if (this._currentIndex < 0) {
|
|
330
|
+
this._currentIndex = 0;
|
|
255
331
|
}
|
|
256
332
|
|
|
257
|
-
const currentItem = items[this.
|
|
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 {
|
|
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 =
|
|
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.
|
|
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.
|
|
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.
|
|
92
|
-
const y = this.
|
|
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.
|
|
105
|
+
const touch = this.supportsTouch ? event.touches[0] : null;
|
|
100
106
|
|
|
101
|
-
if (!this.
|
|
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.
|
|
111
|
-
this._prevDragY = this.
|
|
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.
|
|
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.
|
|
128
|
+
const touch = this.supportsTouch ? event.touches[0] : null;
|
|
123
129
|
|
|
124
|
-
const dragX = this.
|
|
125
|
-
const dragY = this.
|
|
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.
|
|
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.
|
|
158
|
-
const dragY = this.
|
|
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.
|
|
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;
|