@ui5/webcomponents-base 0.0.0-934b4df24 → 0.0.0-b053ad005

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 (331) hide show
  1. package/.eslintignore +3 -0
  2. package/CHANGELOG.md +137 -0
  3. package/README.md +26 -2
  4. package/bundle.esm.js +16 -7
  5. package/dist/AssetRegistry.js +8 -7
  6. package/dist/Boot.js +43 -0
  7. package/dist/CSP.js +59 -0
  8. package/dist/CustomElementsRegistry.js +24 -8
  9. package/dist/CustomElementsScope.js +108 -0
  10. package/dist/DOMObserver.js +65 -0
  11. package/dist/Device.js +62 -778
  12. package/dist/EventProvider.js +38 -26
  13. package/dist/FontFace.js +19 -62
  14. package/dist/InitialConfiguration.js +43 -8
  15. package/dist/Keys.js +89 -2
  16. package/dist/ManagedStyles.js +83 -0
  17. package/dist/MediaRange.js +109 -0
  18. package/dist/PropertiesFileFormat.js +95 -0
  19. package/dist/Render.js +175 -0
  20. package/dist/RenderQueue.js +41 -17
  21. package/dist/RenderScheduler.js +24 -126
  22. package/dist/StaticArea.js +1 -39
  23. package/dist/StaticAreaItem.js +56 -63
  24. package/dist/SystemCSSVars.js +10 -0
  25. package/dist/Theming.js +2 -1
  26. package/dist/UI5Element.js +411 -319
  27. package/dist/UI5ElementMetadata.js +165 -17
  28. package/dist/asset-registries/Icons.js +129 -14
  29. package/dist/asset-registries/Illustrations.js +30 -0
  30. package/dist/asset-registries/LocaleData.js +99 -65
  31. package/dist/asset-registries/Themes.js +23 -32
  32. package/dist/asset-registries/i18n.js +72 -30
  33. package/dist/assets-meta/IconCollectionsAlias.js +18 -0
  34. package/dist/config/AnimationMode.js +11 -1
  35. package/dist/config/Language.js +58 -2
  36. package/dist/config/Theme.js +14 -0
  37. package/dist/css/FontFace.css +45 -0
  38. package/dist/css/OverrideFontFace.css +32 -0
  39. package/dist/css/SystemCSSVars.css +17 -0
  40. package/dist/delegate/ItemNavigation.js +244 -171
  41. package/dist/delegate/ResizeHandler.js +78 -38
  42. package/dist/delegate/ScrollEnablement.js +49 -20
  43. package/dist/features/OpenUI5Support.js +42 -7
  44. package/dist/generated/AssetParameters.js +1 -1
  45. package/dist/generated/css/FontFace.css.js +5 -0
  46. package/dist/generated/css/OverrideFontFace.css.js +5 -0
  47. package/dist/generated/css/SystemCSSVars.css.js +5 -0
  48. package/dist/getSharedResource.js +30 -0
  49. package/dist/i18nBundle.js +58 -5
  50. package/dist/isLegacyBrowser.js +3 -0
  51. package/dist/locale/RTLAwareRegistry.js +14 -0
  52. package/dist/locale/applyDirection.js +17 -0
  53. package/dist/locale/directionChange.js +32 -0
  54. package/dist/locale/getEffectiveDir.js +28 -0
  55. package/dist/locale/getLocale.js +12 -2
  56. package/dist/locale/languageChange.js +22 -0
  57. package/dist/renderer/LitRenderer.js +26 -7
  58. package/dist/renderer/directives/style-map.js +72 -0
  59. package/dist/renderer/executeTemplate.js +17 -0
  60. package/dist/resources/bundle.esm.js +17 -109
  61. package/dist/resources/bundle.esm.js.map +1 -1
  62. package/dist/sap/base/Log.js +241 -0
  63. package/dist/sap/base/assert.js +8 -0
  64. package/dist/sap/base/security/URLListValidator.js +148 -0
  65. package/dist/sap/base/security/sanitizeHTML.js +16 -0
  66. package/dist/sap/base/util/now.js +7 -0
  67. package/dist/sap/ui/thirdparty/caja-html-sanitizer.js +3585 -0
  68. package/dist/test-resources/assets/Themes.js +10 -6
  69. package/dist/test-resources/elements/Parent.js +6 -2
  70. package/dist/test-resources/elements/WithStaticArea.js +2 -1
  71. package/dist/test-resources/pages/AllTestElements.html +3 -3
  72. package/dist/test-resources/pages/Configuration.html +0 -2
  73. package/dist/test-resources/pages/ConfigurationScript.html +1 -3
  74. package/dist/test-resources/pages/assets/messagebundle_de.properties +1 -0
  75. package/dist/test-resources/pages/assets/messagebundle_en.properties +1 -0
  76. package/dist/test-resources/pages/assets/messagebundle_es.properties +1 -0
  77. package/dist/test-resources/pages/assets/messagebundle_fr.properties +1 -0
  78. package/dist/test-resources/pages/i18n.html +33 -0
  79. package/dist/test-resources/specs/ConfigurationChange.spec.js +11 -9
  80. package/dist/test-resources/specs/ConfigurationScript.spec.js +25 -23
  81. package/dist/test-resources/specs/ConfigurationURL.spec.js +65 -17
  82. package/dist/test-resources/specs/CustomTheme.spec.js +9 -7
  83. package/dist/test-resources/specs/EventProvider.spec.js +63 -0
  84. package/dist/test-resources/specs/StaticArea.spec.js +56 -12
  85. package/dist/test-resources/specs/Theming.spec.js +12 -10
  86. package/dist/test-resources/specs/UI5ElementInvalidation.js +54 -70
  87. package/dist/test-resources/specs/UI5ElementLifecycle.js +19 -14
  88. package/dist/test-resources/specs/UI5ElementListenForChildPropChanges.spec.js +25 -51
  89. package/dist/test-resources/specs/UI5ElementMetadataExt.js +9 -7
  90. package/dist/test-resources/specs/UI5ElementPropertyValidation.js +7 -5
  91. package/dist/test-resources/specs/UI5ElementPropsAndAttrs.spec.js +37 -35
  92. package/dist/test-resources/specs/UI5ElementShadowDOM.js +10 -8
  93. package/dist/test-resources/specs/UI5ElementSlots.js +10 -8
  94. package/dist/theming/CustomStyle.js +28 -7
  95. package/dist/theming/ThemeLoaded.js +22 -0
  96. package/dist/theming/applyTheme.js +7 -13
  97. package/dist/theming/getConstructableStyle.js +15 -10
  98. package/dist/theming/getEffectiveLinksHrefs.js +20 -0
  99. package/dist/theming/getEffectiveStyle.js +23 -8
  100. package/dist/theming/getStylesString.js +15 -0
  101. package/dist/theming/getThemeDesignerTheme.js +2 -1
  102. package/dist/theming/preloadLinks.js +23 -0
  103. package/dist/types/CSSColor.js +9 -0
  104. package/dist/types/CalendarType.js +1 -1
  105. package/dist/types/DataType.js +13 -1
  106. package/dist/types/Float.js +14 -0
  107. package/dist/types/Integer.js +4 -0
  108. package/dist/types/InvisibleMessageMode.js +30 -0
  109. package/dist/types/ItemNavigationBehavior.js +2 -7
  110. package/dist/types/NavigationMode.js +1 -0
  111. package/dist/types/PopupState.js +1 -1
  112. package/dist/types/ValueState.js +1 -1
  113. package/dist/updateShadowRoot.js +30 -0
  114. package/dist/util/AriaLabelHelper.js +41 -0
  115. package/dist/util/Caret.js +45 -0
  116. package/dist/util/ColorConversion.js +370 -0
  117. package/dist/util/FocusableElements.js +25 -8
  118. package/dist/util/HTMLSanitizer.js +7 -0
  119. package/dist/util/InvisibleMessage.js +58 -0
  120. package/dist/util/PopupUtils.js +93 -0
  121. package/dist/util/SlotsHelper.js +43 -0
  122. package/dist/util/TabbableElements.js +1 -1
  123. package/dist/util/arraysAreEqual.js +15 -0
  124. package/dist/util/clamp.js +12 -0
  125. package/dist/util/createLinkInHead.js +19 -0
  126. package/dist/util/debounce.js +17 -0
  127. package/dist/util/encodeCSS.js +24 -0
  128. package/dist/util/getActiveElement.js +11 -0
  129. package/dist/util/getClassCopy.js +10 -0
  130. package/dist/util/getEffectiveContentDensity.js +5 -0
  131. package/dist/util/getSingletonElementInstance.js +13 -0
  132. package/dist/util/isElementInView.js +15 -0
  133. package/dist/util/isNodeHidden.js +1 -5
  134. package/dist/util/isNodeTabbable.js +4 -4
  135. package/dist/util/isValidPropertyName.js +9 -3
  136. package/dist/util/setToArray.js +10 -0
  137. package/hash.txt +1 -0
  138. package/index.js +1 -1
  139. package/lib/generate-asset-parameters/index.js +0 -1
  140. package/lib/generate-styles/index.js +18 -0
  141. package/package-scripts.js +37 -19
  142. package/package.json +19 -13
  143. package/src/AssetRegistry.js +8 -7
  144. package/src/Boot.js +43 -0
  145. package/src/CSP.js +59 -0
  146. package/src/CustomElementsRegistry.js +24 -8
  147. package/src/CustomElementsScope.js +108 -0
  148. package/src/DOMObserver.js +65 -0
  149. package/src/Device.js +62 -778
  150. package/src/EventProvider.js +38 -26
  151. package/src/FontFace.js +19 -62
  152. package/src/InitialConfiguration.js +43 -8
  153. package/src/Keys.js +89 -2
  154. package/src/ManagedStyles.js +83 -0
  155. package/src/MediaRange.js +109 -0
  156. package/src/PropertiesFileFormat.js +95 -0
  157. package/src/Render.js +175 -0
  158. package/src/RenderQueue.js +41 -17
  159. package/src/RenderScheduler.js +24 -126
  160. package/src/StaticArea.js +1 -39
  161. package/src/StaticAreaItem.js +56 -63
  162. package/src/SystemCSSVars.js +10 -0
  163. package/src/Theming.js +2 -1
  164. package/src/UI5Element.js +411 -319
  165. package/src/UI5ElementMetadata.js +165 -17
  166. package/src/asset-registries/Icons.js +129 -14
  167. package/src/asset-registries/Illustrations.js +30 -0
  168. package/src/asset-registries/LocaleData.js +99 -65
  169. package/src/asset-registries/Themes.js +23 -32
  170. package/src/asset-registries/i18n.js +72 -30
  171. package/src/assets-meta/IconCollectionsAlias.js +18 -0
  172. package/src/config/AnimationMode.js +11 -1
  173. package/src/config/Language.js +58 -2
  174. package/src/config/Theme.js +14 -0
  175. package/src/css/FontFace.css +45 -0
  176. package/src/css/OverrideFontFace.css +32 -0
  177. package/src/css/SystemCSSVars.css +17 -0
  178. package/src/delegate/ItemNavigation.js +244 -171
  179. package/src/delegate/ResizeHandler.js +78 -38
  180. package/src/delegate/ScrollEnablement.js +49 -20
  181. package/src/features/OpenUI5Support.js +42 -7
  182. package/src/getSharedResource.js +30 -0
  183. package/src/i18nBundle.js +58 -5
  184. package/src/isLegacyBrowser.js +3 -0
  185. package/src/locale/RTLAwareRegistry.js +14 -0
  186. package/src/locale/applyDirection.js +17 -0
  187. package/src/locale/directionChange.js +32 -0
  188. package/src/locale/getEffectiveDir.js +28 -0
  189. package/src/locale/getLocale.js +12 -2
  190. package/src/locale/languageChange.js +22 -0
  191. package/src/renderer/LitRenderer.js +26 -7
  192. package/src/renderer/directives/style-map.js +72 -0
  193. package/src/renderer/executeTemplate.js +17 -0
  194. package/src/theming/CustomStyle.js +28 -7
  195. package/src/theming/ThemeLoaded.js +22 -0
  196. package/src/theming/applyTheme.js +7 -13
  197. package/src/theming/getConstructableStyle.js +15 -10
  198. package/src/theming/getEffectiveLinksHrefs.js +20 -0
  199. package/src/theming/getEffectiveStyle.js +23 -8
  200. package/src/theming/getStylesString.js +15 -0
  201. package/src/theming/getThemeDesignerTheme.js +2 -1
  202. package/src/theming/preloadLinks.js +23 -0
  203. package/src/types/CSSColor.js +9 -0
  204. package/src/types/CalendarType.js +1 -1
  205. package/src/types/DataType.js +13 -1
  206. package/src/types/Float.js +14 -0
  207. package/src/types/Integer.js +4 -0
  208. package/src/types/InvisibleMessageMode.js +30 -0
  209. package/src/types/ItemNavigationBehavior.js +2 -7
  210. package/src/types/NavigationMode.js +1 -0
  211. package/src/types/PopupState.js +1 -1
  212. package/src/types/ValueState.js +1 -1
  213. package/src/updateShadowRoot.js +30 -0
  214. package/src/util/AriaLabelHelper.js +41 -0
  215. package/src/util/Caret.js +45 -0
  216. package/src/util/ColorConversion.js +370 -0
  217. package/src/util/FocusableElements.js +25 -8
  218. package/src/util/HTMLSanitizer.js +7 -0
  219. package/src/util/InvisibleMessage.js +58 -0
  220. package/src/util/PopupUtils.js +93 -0
  221. package/src/util/SlotsHelper.js +43 -0
  222. package/src/util/TabbableElements.js +1 -1
  223. package/src/util/arraysAreEqual.js +15 -0
  224. package/src/util/clamp.js +12 -0
  225. package/src/util/createLinkInHead.js +19 -0
  226. package/src/util/debounce.js +17 -0
  227. package/src/util/encodeCSS.js +24 -0
  228. package/src/util/getActiveElement.js +11 -0
  229. package/src/util/getClassCopy.js +10 -0
  230. package/src/util/getEffectiveContentDensity.js +5 -0
  231. package/src/util/getSingletonElementInstance.js +13 -0
  232. package/src/util/isElementInView.js +15 -0
  233. package/src/util/isNodeHidden.js +1 -5
  234. package/src/util/isNodeTabbable.js +4 -4
  235. package/src/util/isValidPropertyName.js +9 -3
  236. package/src/util/setToArray.js +10 -0
  237. package/used-modules.txt +7 -0
  238. package/bundle.es5.js +0 -28
  239. package/dist/SVGIconRegistry.js +0 -60
  240. package/dist/boot.js +0 -32
  241. package/dist/compatibility/DOMObserver.js +0 -62
  242. package/dist/compatibility/patchNodeValue.js +0 -24
  243. package/dist/compatibility/whenPolyfillLoaded.js +0 -26
  244. package/dist/delegate/CustomResize.js +0 -78
  245. package/dist/delegate/NativeResize.js +0 -44
  246. package/dist/features/browsersupport/Edge.js +0 -6
  247. package/dist/features/browsersupport/IE11.js +0 -41
  248. package/dist/features/browsersupport/IE11WithWebComponentsPolyfill.js +0 -5
  249. package/dist/renderer/ifDefined.js +0 -21
  250. package/dist/resources/bundle.es5.js +0 -212
  251. package/dist/resources/bundle.es5.js.map +0 -1
  252. package/dist/theming/CSSVarsPonyfill.js +0 -24
  253. package/dist/theming/adaptCSSForIE.js +0 -90
  254. package/dist/theming/createComponentStyleTag.js +0 -49
  255. package/dist/theming/createThemePropertiesStyleTag.js +0 -20
  256. package/dist/thirdparty/Array.from.js +0 -16
  257. package/dist/thirdparty/Array.prototype.fill.js +0 -44
  258. package/dist/thirdparty/Array.prototype.find.js +0 -46
  259. package/dist/thirdparty/Array.prototype.includes.js +0 -51
  260. package/dist/thirdparty/Element.prototype.closest.js +0 -11
  261. package/dist/thirdparty/Element.prototype.matches.js +0 -6
  262. package/dist/thirdparty/Map.prototype.keys.js +0 -9
  263. package/dist/thirdparty/Number.isInteger.js +0 -5
  264. package/dist/thirdparty/Number.isNaN.js +0 -1
  265. package/dist/thirdparty/Number.parseInt.js +0 -1
  266. package/dist/thirdparty/Object.assign.js +0 -31
  267. package/dist/thirdparty/Object.entries.js +0 -11
  268. package/dist/thirdparty/Symbol.js +0 -2
  269. package/dist/thirdparty/WeakSet.js +0 -27
  270. package/dist/thirdparty/es6-string-methods.js +0 -182
  271. package/dist/thirdparty/events-polyfills.js +0 -89
  272. package/dist/thirdparty/fetch.js +0 -1
  273. package/dist/thirdparty/template.js +0 -600
  274. package/dist/thirdparty/webcomponents-sd-ce-pf.js +0 -318
  275. package/dist/util/isSlot.js +0 -3
  276. package/dist/webcomponentsjs/LICENSE.md +0 -19
  277. package/dist/webcomponentsjs/README.md +0 -229
  278. package/dist/webcomponentsjs/bundles/webcomponents-ce.js +0 -63
  279. package/dist/webcomponentsjs/bundles/webcomponents-ce.js.map +0 -1
  280. package/dist/webcomponentsjs/bundles/webcomponents-sd-ce-pf.js +0 -297
  281. package/dist/webcomponentsjs/bundles/webcomponents-sd-ce-pf.js.map +0 -1
  282. package/dist/webcomponentsjs/bundles/webcomponents-sd-ce.js +0 -208
  283. package/dist/webcomponentsjs/bundles/webcomponents-sd-ce.js.map +0 -1
  284. package/dist/webcomponentsjs/bundles/webcomponents-sd.js +0 -166
  285. package/dist/webcomponentsjs/bundles/webcomponents-sd.js.map +0 -1
  286. package/dist/webcomponentsjs/custom-elements-es5-adapter.js +0 -15
  287. package/dist/webcomponentsjs/package.json +0 -46
  288. package/dist/webcomponentsjs/src/entrypoints/custom-elements-es5-adapter-index.js +0 -16
  289. package/dist/webcomponentsjs/src/entrypoints/webcomponents-bundle-index.js +0 -53
  290. package/dist/webcomponentsjs/src/entrypoints/webcomponents-ce-index.js +0 -17
  291. package/dist/webcomponentsjs/src/entrypoints/webcomponents-sd-ce-index.js +0 -19
  292. package/dist/webcomponentsjs/src/entrypoints/webcomponents-sd-ce-pf-index.js +0 -28
  293. package/dist/webcomponentsjs/src/entrypoints/webcomponents-sd-index.js +0 -18
  294. package/dist/webcomponentsjs/webcomponents-bundle.js +0 -298
  295. package/dist/webcomponentsjs/webcomponents-bundle.js.map +0 -1
  296. package/dist/webcomponentsjs/webcomponents-loader.js +0 -185
  297. package/src/SVGIconRegistry.js +0 -60
  298. package/src/boot.js +0 -32
  299. package/src/compatibility/DOMObserver.js +0 -62
  300. package/src/compatibility/patchNodeValue.js +0 -24
  301. package/src/compatibility/whenPolyfillLoaded.js +0 -26
  302. package/src/delegate/CustomResize.js +0 -78
  303. package/src/delegate/NativeResize.js +0 -44
  304. package/src/features/browsersupport/Edge.js +0 -6
  305. package/src/features/browsersupport/IE11.js +0 -41
  306. package/src/features/browsersupport/IE11WithWebComponentsPolyfill.js +0 -5
  307. package/src/renderer/ifDefined.js +0 -21
  308. package/src/theming/CSSVarsPonyfill.js +0 -24
  309. package/src/theming/adaptCSSForIE.js +0 -90
  310. package/src/theming/createComponentStyleTag.js +0 -49
  311. package/src/theming/createThemePropertiesStyleTag.js +0 -20
  312. package/src/thirdparty/Array.from.js +0 -16
  313. package/src/thirdparty/Array.prototype.fill.js +0 -44
  314. package/src/thirdparty/Array.prototype.find.js +0 -46
  315. package/src/thirdparty/Array.prototype.includes.js +0 -51
  316. package/src/thirdparty/Element.prototype.closest.js +0 -11
  317. package/src/thirdparty/Element.prototype.matches.js +0 -6
  318. package/src/thirdparty/Map.prototype.keys.js +0 -9
  319. package/src/thirdparty/Number.isInteger.js +0 -5
  320. package/src/thirdparty/Number.isNaN.js +0 -1
  321. package/src/thirdparty/Number.parseInt.js +0 -1
  322. package/src/thirdparty/Object.assign.js +0 -31
  323. package/src/thirdparty/Object.entries.js +0 -11
  324. package/src/thirdparty/Symbol.js +0 -2
  325. package/src/thirdparty/WeakSet.js +0 -27
  326. package/src/thirdparty/es6-string-methods.js +0 -182
  327. package/src/thirdparty/events-polyfills.js +0 -89
  328. package/src/thirdparty/fetch.js +0 -1
  329. package/src/thirdparty/template.js +0 -600
  330. package/src/thirdparty/webcomponents-sd-ce-pf.js +0 -318
  331. package/src/util/isSlot.js +0 -3
@@ -1,30 +1,51 @@
1
1
  import merge from "./thirdparty/merge.js";
2
- import boot from "./boot.js";
2
+ import { boot } from "./Boot.js";
3
3
  import UI5ElementMetadata from "./UI5ElementMetadata.js";
4
+ import EventProvider from "./EventProvider.js";
5
+ import getSingletonElementInstance from "./util/getSingletonElementInstance.js";
4
6
  import StaticAreaItem from "./StaticAreaItem.js";
5
- import RenderScheduler from "./RenderScheduler.js";
6
- import { registerTag, isTagRegistered } from "./CustomElementsRegistry.js";
7
- import DOMObserver from "./compatibility/DOMObserver.js";
7
+ import updateShadowRoot from "./updateShadowRoot.js";
8
+ import { renderDeferred, renderImmediately, cancelRender } from "./Render.js";
9
+ import { registerTag, isTagRegistered, recordTagRegistrationFailure } from "./CustomElementsRegistry.js";
10
+ import { observeDOMNode, unobserveDOMNode } from "./DOMObserver.js";
8
11
  import { skipOriginalEvent } from "./config/NoConflict.js";
9
- import getConstructableStyle from "./theming/getConstructableStyle.js";
10
- import createComponentStyleTag from "./theming/createComponentStyleTag.js";
11
- import getEffectiveStyle from "./theming/getEffectiveStyle.js";
12
- import Integer from "./types/Integer.js";
12
+ import getEffectiveDir from "./locale/getEffectiveDir.js";
13
+ import DataType from "./types/DataType.js";
13
14
  import { kebabToCamelCase, camelToKebabCase } from "./util/StringHelper.js";
14
15
  import isValidPropertyName from "./util/isValidPropertyName.js";
15
- import isSlot from "./util/isSlot.js";
16
-
17
- const metadata = {
18
- events: {
19
- _propertyChange: {},
20
- },
21
- };
16
+ import isDescendantOf from "./util/isDescendantOf.js";
17
+ import { isSlot, getSlotName, getSlottedElementsList } from "./util/SlotsHelper.js";
18
+ import arraysAreEqual from "./util/arraysAreEqual.js";
19
+ import getClassCopy from "./util/getClassCopy.js";
20
+ import { markAsRtlAware } from "./locale/RTLAwareRegistry.js";
21
+ import preloadLinks from "./theming/preloadLinks.js";
22
22
 
23
23
  let autoId = 0;
24
24
 
25
25
  const elementTimeouts = new Map();
26
+ const uniqueDependenciesCache = new Map();
27
+
28
+ /**
29
+ * Triggers re-rendering of a UI5Element instance due to state change.
30
+ *
31
+ * @param changeInfo An object with information about the change that caused invalidation.
32
+ * @private
33
+ */
34
+ function _invalidate(changeInfo) {
35
+ // Invalidation should be suppressed: 1) before the component is rendered for the first time 2) and during the execution of onBeforeRendering
36
+ // This is necessary not only as an optimization, but also to avoid infinite loops on invalidation between children and parents (when invalidateOnChildChange is used)
37
+ if (this._suppressInvalidation) {
38
+ return;
39
+ }
40
+
41
+ // Call the onInvalidation hook
42
+ this.onInvalidation(changeInfo);
43
+
44
+ this._changedState.push(changeInfo);
45
+ renderDeferred(this);
46
+ this._eventProvider.fireEvent("invalidate", { ...changeInfo, target: this });
47
+ }
26
48
 
27
- const GLOBAL_CONTENT_DENSITY_CSS_VAR = "--_ui5_content_density";
28
49
  /**
29
50
  * Base class for all UI5 Web Components
30
51
  *
@@ -38,56 +59,40 @@ const GLOBAL_CONTENT_DENSITY_CSS_VAR = "--_ui5_content_density";
38
59
  class UI5Element extends HTMLElement {
39
60
  constructor() {
40
61
  super();
41
- this._generateId();
42
- this._initializeState();
43
- this._upgradeAllProperties();
44
- this._initializeContainers();
45
- this._upToDate = false;
46
62
 
63
+ this._changedState = []; // Filled on each invalidation, cleared on re-render (used for debugging)
64
+ this._suppressInvalidation = true; // A flag telling whether all invalidations should be ignored. Initialized with "true" because a UI5Element can not be invalidated until it is rendered for the first time
65
+ this._inDOM = false; // A flag telling whether the UI5Element is currently in the DOM tree of the document or not
66
+ this._fullyConnected = false; // A flag telling whether the UI5Element's onEnterDOM hook was called (since it's possible to have the element removed from DOM before that)
67
+ this._childChangeListeners = new Map(); // used to store lazy listeners per slot for the child change event of every child inside that slot
68
+ this._slotChangeListeners = new Map(); // used to store lazy listeners per slot for the slotchange event of all slot children inside that slot
69
+ this._eventProvider = new EventProvider(); // used by parent components for listening to changes to child components
47
70
  let deferredResolve;
48
71
  this._domRefReadyPromise = new Promise(resolve => {
49
72
  deferredResolve = resolve;
50
73
  });
51
74
  this._domRefReadyPromise._deferredResolve = deferredResolve;
52
75
 
53
- this._monitoredChildProps = new Map();
54
- this._firePropertyChange = false;
55
- }
76
+ this._initializeState();
77
+ this._upgradeAllProperties();
56
78
 
57
- /**
58
- * @private
59
- */
60
- _generateId() {
61
- this._id = `ui5wc_${++autoId}`;
79
+ if (this.constructor._needsShadowDOM()) {
80
+ this.attachShadow({ mode: "open" });
81
+ }
62
82
  }
63
83
 
64
84
  /**
65
- * @private
85
+ * Returns a unique ID for this UI5 Element
86
+ *
87
+ * @deprecated - This property is not guaranteed in future releases
88
+ * @protected
66
89
  */
67
- _initializeContainers() {
68
- const needsShadowDOM = this.constructor._needsShadowDOM();
69
- const needsStaticArea = this.constructor._needsStaticArea();
70
-
71
- // Init Shadow Root
72
- if (needsShadowDOM) {
73
- this.attachShadow({ mode: "open" });
74
-
75
- // IE11, Edge
76
- if (window.ShadyDOM) {
77
- createComponentStyleTag(this.constructor);
78
- }
79
-
80
- // Chrome
81
- if (document.adoptedStyleSheets) {
82
- const style = getConstructableStyle(this.constructor);
83
- this.shadowRoot.adoptedStyleSheets = [style];
84
- }
90
+ get _id() {
91
+ if (!this.__id) {
92
+ this.__id = `ui5wc_${++autoId}`;
85
93
  }
86
94
 
87
- // Init StaticAreaItem only if needed
88
- if (needsStaticArea) {
89
- this.staticAreaItem = new StaticAreaItem(this);
90
- }
95
+ return this.__id;
91
96
  }
92
97
 
93
98
  /**
@@ -95,26 +100,27 @@ class UI5Element extends HTMLElement {
95
100
  * @private
96
101
  */
97
102
  async connectedCallback() {
98
- const needsShadowDOM = this.constructor._needsShadowDOM();
103
+ this.setAttribute(this.constructor.getMetadata().getPureTag(), "");
104
+
99
105
  const slotsAreManaged = this.constructor.getMetadata().slotsAreManaged();
100
106
 
101
- // Render the Shadow DOM
102
- if (needsShadowDOM) {
103
- if (slotsAreManaged) {
104
- // always register the observer before yielding control to the main thread (await)
105
- this._startObservingDOMChildren();
106
- await this._processChildren();
107
- }
107
+ this._inDOM = true;
108
108
 
109
- if (!this.shadowRoot) { // Workaround for Firefox74 bug
110
- await Promise.resolve();
111
- }
109
+ if (slotsAreManaged) {
110
+ // always register the observer before yielding control to the main thread (await)
111
+ this._startObservingDOMChildren();
112
+ await this._processChildren();
113
+ }
112
114
 
113
- await RenderScheduler.renderImmediately(this);
114
- this._domRefReadyPromise._deferredResolve();
115
- if (typeof this.onEnterDOM === "function") {
116
- this.onEnterDOM();
117
- }
115
+ if (!this._inDOM) { // Component removed from DOM while _processChildren was running
116
+ return;
117
+ }
118
+
119
+ renderImmediately(this);
120
+ this._domRefReadyPromise._deferredResolve();
121
+ this._fullyConnected = true;
122
+ if (typeof this.onEnterDOM === "function") {
123
+ this.onEnterDOM();
118
124
  }
119
125
  }
120
126
 
@@ -123,23 +129,26 @@ class UI5Element extends HTMLElement {
123
129
  * @private
124
130
  */
125
131
  disconnectedCallback() {
126
- const needsShadowDOM = this.constructor._needsShadowDOM();
127
- const needsStaticArea = this.constructor._needsStaticArea();
128
132
  const slotsAreManaged = this.constructor.getMetadata().slotsAreManaged();
129
133
 
130
- if (needsShadowDOM) {
131
- if (slotsAreManaged) {
132
- this._stopObservingDOMChildren();
133
- }
134
+ this._inDOM = false;
134
135
 
136
+ if (slotsAreManaged) {
137
+ this._stopObservingDOMChildren();
138
+ }
139
+
140
+ if (this._fullyConnected) {
135
141
  if (typeof this.onExitDOM === "function") {
136
142
  this.onExitDOM();
137
143
  }
144
+ this._fullyConnected = false;
138
145
  }
139
146
 
140
- if (needsStaticArea) {
141
- this.staticAreaItem._removeFragmentFromStaticArea();
147
+ if (this.staticAreaItem && this.staticAreaItem.parentElement) {
148
+ this.staticAreaItem.parentElement.removeChild(this.staticAreaItem);
142
149
  }
150
+
151
+ cancelRender(this);
143
152
  }
144
153
 
145
154
  /**
@@ -155,16 +164,16 @@ class UI5Element extends HTMLElement {
155
164
  const mutationObserverOptions = {
156
165
  childList: true,
157
166
  subtree: canSlotText,
158
- characterData: true,
167
+ characterData: canSlotText,
159
168
  };
160
- DOMObserver.observeDOMNode(this, this._processChildren.bind(this), mutationObserverOptions);
169
+ observeDOMNode(this, this._processChildren.bind(this), mutationObserverOptions);
161
170
  }
162
171
 
163
172
  /**
164
173
  * @private
165
174
  */
166
175
  _stopObservingDOMChildren() {
167
- DOMObserver.unobserveDOMNode(this);
176
+ unobserveDOMNode(this);
168
177
  }
169
178
 
170
179
  /**
@@ -186,8 +195,14 @@ class UI5Element extends HTMLElement {
186
195
  const canSlotText = this.constructor.getMetadata().canSlotText();
187
196
  const domChildren = Array.from(canSlotText ? this.childNodes : this.children);
188
197
 
189
- // Init the _state object based on the supported slots
198
+ const slotsCachedContentMap = new Map(); // Store here the content of each slot before the mutation occurred
199
+ const propertyNameToSlotMap = new Map(); // Used for reverse lookup to determine to which slot the property name corresponds
200
+
201
+ // Init the _state object based on the supported slots and store the previous values
190
202
  for (const [slotName, slotData] of Object.entries(slotsMap)) { // eslint-disable-line
203
+ const propertyName = slotData.propertyName || slotName;
204
+ propertyNameToSlotMap.set(propertyName, slotName);
205
+ slotsCachedContentMap.set(propertyName, [...this._state[propertyName]]);
191
206
  this._clearSlot(slotName, slotData);
192
207
  }
193
208
 
@@ -196,7 +211,7 @@ class UI5Element extends HTMLElement {
196
211
 
197
212
  const allChildrenUpgraded = domChildren.map(async (child, idx) => {
198
213
  // Determine the type of the child (mainly by the slot attribute)
199
- const slotName = this.constructor._getSlotName(child);
214
+ const slotName = getSlotName(child);
200
215
  const slotData = slotsMap[slotName];
201
216
 
202
217
  // Check if the slotName is supported
@@ -234,12 +249,14 @@ class UI5Element extends HTMLElement {
234
249
 
235
250
  child = this.constructor.getMetadata().constructor.validateSlotValue(child, slotData);
236
251
 
237
- if (child.isUI5Element && slotData.listenFor) {
238
- this._attachChildPropertyUpdated(child, slotData.listenFor);
252
+ // Listen for any invalidation on the child if invalidateOnChildChange is true or an object (ignore when false or not set)
253
+ if (child.isUI5Element && slotData.invalidateOnChildChange) {
254
+ child.attachInvalidate(this._getChildChangeListener(slotName));
239
255
  }
240
256
 
257
+ // Listen for the slotchange event if the child is a slot itself
241
258
  if (isSlot(child)) {
242
- this._attachSlotChange(child);
259
+ this._attachSlotChange(child, slotName);
243
260
  }
244
261
 
245
262
  const propertyName = slotData.propertyName || slotName;
@@ -255,10 +272,33 @@ class UI5Element extends HTMLElement {
255
272
 
256
273
  // Distribute the child in the _state object, keeping the Light DOM order,
257
274
  // not the order elements are defined.
258
- slottedChildrenMap.forEach((children, slot) => {
259
- this._state[slot] = children.sort((a, b) => a.idx - b.idx).map(_ => _.child);
275
+ slottedChildrenMap.forEach((children, propertyName) => {
276
+ this._state[propertyName] = children.sort((a, b) => a.idx - b.idx).map(_ => _.child);
260
277
  });
261
- this._invalidate("slots");
278
+
279
+ // Compare the content of each slot with the cached values and invalidate for the ones that changed
280
+ let invalidated = false;
281
+ for (const [slotName, slotData] of Object.entries(slotsMap)) { // eslint-disable-line
282
+ const propertyName = slotData.propertyName || slotName;
283
+ if (!arraysAreEqual(slotsCachedContentMap.get(propertyName), this._state[propertyName])) {
284
+ _invalidate.call(this, {
285
+ type: "slot",
286
+ name: propertyNameToSlotMap.get(propertyName),
287
+ reason: "children",
288
+ });
289
+ invalidated = true;
290
+ }
291
+ }
292
+
293
+ // If none of the slots had an invalidation due to changes to immediate children,
294
+ // the change is considered to be text content of the default slot
295
+ if (!invalidated) {
296
+ _invalidate.call(this, {
297
+ type: "slot",
298
+ name: "default",
299
+ reason: "textcontent",
300
+ });
301
+ }
262
302
  }
263
303
 
264
304
  /**
@@ -267,24 +307,61 @@ class UI5Element extends HTMLElement {
267
307
  */
268
308
  _clearSlot(slotName, slotData) {
269
309
  const propertyName = slotData.propertyName || slotName;
270
-
271
- let children = this._state[propertyName];
272
- if (!Array.isArray(children)) {
273
- children = [children];
274
- }
310
+ const children = this._state[propertyName];
275
311
 
276
312
  children.forEach(child => {
277
313
  if (child && child.isUI5Element) {
278
- this._detachChildPropertyUpdated(child);
314
+ child.detachInvalidate(this._getChildChangeListener(slotName));
279
315
  }
280
316
 
281
317
  if (isSlot(child)) {
282
- this._detachSlotChange(child);
318
+ this._detachSlotChange(child, slotName);
283
319
  }
284
320
  });
285
321
 
286
322
  this._state[propertyName] = [];
287
- this._invalidate(propertyName, []);
323
+ }
324
+
325
+ /**
326
+ * Attach a callback that will be executed whenever the component is invalidated
327
+ *
328
+ * @param callback
329
+ * @public
330
+ */
331
+ attachInvalidate(callback) {
332
+ this._eventProvider.attachEvent("invalidate", callback);
333
+ }
334
+
335
+ /**
336
+ * Detach the callback that is executed whenever the component is invalidated
337
+ *
338
+ * @param callback
339
+ * @public
340
+ */
341
+ detachInvalidate(callback) {
342
+ this._eventProvider.detachEvent("invalidate", callback);
343
+ }
344
+
345
+ /**
346
+ * Callback that is executed whenever a monitored child changes its state
347
+ *
348
+ * @param slotName the slot in which a child was invalidated
349
+ * @param childChangeInfo the changeInfo object for the child in the given slot
350
+ * @private
351
+ */
352
+ _onChildChange(slotName, childChangeInfo) {
353
+ if (!this.constructor.getMetadata().shouldInvalidateOnChildChange(slotName, childChangeInfo.type, childChangeInfo.name)) {
354
+ return;
355
+ }
356
+
357
+ // The component should be invalidated as this type of change on the child is listened for
358
+ // However, no matter what changed on the child (property/slot), the invalidation is registered as "type=slot" for the component itself
359
+ _invalidate.call(this, {
360
+ type: "slot",
361
+ name: slotName,
362
+ reason: "childchange",
363
+ child: childChangeInfo.target,
364
+ });
288
365
  }
289
366
 
290
367
  /**
@@ -299,9 +376,8 @@ class UI5Element extends HTMLElement {
299
376
  const propertyTypeClass = properties[nameInCamelCase].type;
300
377
  if (propertyTypeClass === Boolean) {
301
378
  newValue = newValue !== null;
302
- }
303
- if (propertyTypeClass === Integer) {
304
- newValue = parseInt(newValue);
379
+ } else if (isDescendantOf(propertyTypeClass, DataType)) {
380
+ newValue = propertyTypeClass.attributeToProperty(newValue);
305
381
  }
306
382
  this[nameInCamelCase] = newValue;
307
383
  }
@@ -314,22 +390,24 @@ class UI5Element extends HTMLElement {
314
390
  if (!this.constructor.getMetadata().hasAttribute(name)) {
315
391
  return;
316
392
  }
317
-
318
- if (typeof newValue === "object") {
319
- return;
320
- }
321
-
393
+ const properties = this.constructor.getMetadata().getProperties();
394
+ const propertyTypeClass = properties[name].type;
322
395
  const attrName = camelToKebabCase(name);
323
396
  const attrValue = this.getAttribute(attrName);
324
- if (typeof newValue === "boolean") {
397
+
398
+ if (propertyTypeClass === Boolean) {
325
399
  if (newValue === true && attrValue === null) {
326
400
  this.setAttribute(attrName, "");
327
401
  } else if (newValue === false && attrValue !== null) {
328
402
  this.removeAttribute(attrName);
329
403
  }
330
- } else if (attrValue !== newValue) {
331
- this.setAttribute(attrName, newValue);
332
- }
404
+ } else if (isDescendantOf(propertyTypeClass, DataType)) {
405
+ this.setAttribute(attrName, propertyTypeClass.propertyToAttribute(newValue));
406
+ } else if (typeof newValue !== "object") {
407
+ if (attrValue !== newValue) {
408
+ this.setAttribute(attrName, newValue);
409
+ }
410
+ } // else { return; } // old object handling
333
411
  }
334
412
 
335
413
  /**
@@ -355,120 +433,96 @@ class UI5Element extends HTMLElement {
355
433
  * @private
356
434
  */
357
435
  _initializeState() {
358
- const defaultState = this.constructor._getDefaultState();
359
- this._state = Object.assign({}, defaultState);
436
+ this._state = { ...this.constructor.getMetadata().getInitialState() };
360
437
  }
361
438
 
362
439
  /**
440
+ * Returns a singleton event listener for the "change" event of a child in a given slot
441
+ *
442
+ * @param slotName the name of the slot, where the child is
443
+ * @returns {any}
363
444
  * @private
364
445
  */
365
- _attachChildPropertyUpdated(child, listenFor) {
366
- const childMetadata = child.constructor.getMetadata(),
367
- slotName = this.constructor._getSlotName(child), // all slotted children have the same configuration
368
- childProperties = childMetadata.getProperties();
369
-
370
- let observedProps = [],
371
- notObservedProps = [];
372
-
373
- if (Array.isArray(listenFor)) {
374
- observedProps = listenFor;
375
- } else {
376
- observedProps = Array.isArray(listenFor.props) ? listenFor.props : Object.keys(childProperties);
377
- notObservedProps = Array.isArray(listenFor.exclude) ? listenFor.exclude : [];
446
+ _getChildChangeListener(slotName) {
447
+ if (!this._childChangeListeners.has(slotName)) {
448
+ this._childChangeListeners.set(slotName, this._onChildChange.bind(this, slotName));
378
449
  }
379
-
380
- if (!this._monitoredChildProps.has(slotName)) {
381
- this._monitoredChildProps.set(slotName, { observedProps, notObservedProps });
382
- }
383
-
384
- child.addEventListener("_propertyChange", this._invalidateParentOnPropertyUpdate);
385
- child._firePropertyChange = true;
386
- }
387
-
388
- /**
389
- * @private
390
- */
391
- _detachChildPropertyUpdated(child) {
392
- child.removeEventListener("_propertyChange", this._invalidateParentOnPropertyUpdate);
393
- child._firePropertyChange = false;
450
+ return this._childChangeListeners.get(slotName);
394
451
  }
395
452
 
396
453
  /**
454
+ * Returns a singleton slotchange event listener that invalidates the component due to changes in the given slot
455
+ *
456
+ * @param slotName the name of the slot, where the slot element (whose slotchange event we're listening to) is
457
+ * @returns {any}
397
458
  * @private
398
459
  */
399
- _propertyChange(name, value) {
400
- this._updateAttribute(name, value);
401
-
402
- if (this._firePropertyChange) {
403
- this.dispatchEvent(new CustomEvent("_propertyChange", {
404
- detail: { name, newValue: value },
405
- composed: false,
406
- bubbles: true,
407
- }));
460
+ _getSlotChangeListener(slotName) {
461
+ if (!this._slotChangeListeners.has(slotName)) {
462
+ this._slotChangeListeners.set(slotName, this._onSlotChange.bind(this, slotName));
408
463
  }
464
+ return this._slotChangeListeners.get(slotName);
409
465
  }
410
466
 
411
467
  /**
412
468
  * @private
413
469
  */
414
- _invalidateParentOnPropertyUpdate(prop) {
415
- // The web component to be invalidated
416
- const parentNode = this.parentNode;
417
- if (!parentNode) {
418
- return;
419
- }
420
-
421
- const slotName = parentNode.constructor._getSlotName(this);
422
- const propsMetadata = parentNode._monitoredChildProps.get(slotName);
423
-
424
- if (!propsMetadata) {
425
- return;
426
- }
427
- const { observedProps, notObservedProps } = propsMetadata;
428
-
429
- if (observedProps.includes(prop.detail.name) && !notObservedProps.includes(prop.detail.name)) {
430
- parentNode._invalidate("_parent_", this);
431
- }
470
+ _attachSlotChange(child, slotName) {
471
+ child.addEventListener("slotchange", this._getSlotChangeListener(slotName));
432
472
  }
433
473
 
434
474
  /**
435
475
  * @private
436
476
  */
437
- _attachSlotChange(child) {
438
- if (!this._invalidateOnSlotChange) {
439
- this._invalidateOnSlotChange = () => {
440
- this._invalidate("slotchange");
441
- };
442
- }
443
- child.addEventListener("slotchange", this._invalidateOnSlotChange);
477
+ _detachSlotChange(child, slotName) {
478
+ child.removeEventListener("slotchange", this._getSlotChangeListener(slotName));
444
479
  }
445
480
 
446
481
  /**
482
+ * Whenever a slot element is slotted inside a UI5 Web Component, its slotchange event invalidates the component
483
+ *
484
+ * @param slotName the name of the slot, where the slot element (whose slotchange event we're listening to) is
447
485
  * @private
448
486
  */
449
- _detachSlotChange(child) {
450
- child.removeEventListener("slotchange", this._invalidateOnSlotChange);
487
+ _onSlotChange(slotName) {
488
+ _invalidate.call(this, {
489
+ type: "slot",
490
+ name: slotName,
491
+ reason: "slotchange",
492
+ });
451
493
  }
452
494
 
453
495
  /**
454
- * Asynchronously re-renders an already rendered web component
455
- * @private
496
+ * A callback that is executed each time an already rendered component is invalidated (scheduled for re-rendering)
497
+ *
498
+ * @param changeInfo An object with information about the change that caused invalidation.
499
+ * The object can have the following properties:
500
+ * - type: (property|slot) tells what caused the invalidation
501
+ * 1) property: a property value was changed either directly or as a result of changing the corresponding attribute
502
+ * 2) slot: a slotted node(nodes) changed in one of several ways (see "reason")
503
+ *
504
+ * - name: the name of the property or slot that caused the invalidation
505
+ *
506
+ * - reason: (children|textcontent|childchange|slotchange) relevant only for type="slot" only and tells exactly what changed in the slot
507
+ * 1) children: immediate children (HTML elements or text nodes) were added, removed or reordered in the slot
508
+ * 2) textcontent: text nodes in the slot changed value (or nested text nodes were added or changed value). Can only trigger for slots of "type: Node"
509
+ * 3) slotchange: a slot element, slotted inside that slot had its "slotchange" event listener called. This practically means that transitively slotted children changed.
510
+ * Can only trigger if the child of a slot is a slot element itself.
511
+ * 4) childchange: indicates that a UI5Element child in that slot was invalidated and in turn invalidated the component.
512
+ * Can only trigger for slots with "invalidateOnChildChange" metadata descriptor
513
+ *
514
+ * - newValue: the new value of the property (for type="property" only)
515
+ *
516
+ * - oldValue: the old value of the property (for type="property" only)
517
+ *
518
+ * - child the child that was changed (for type="slot" and reason="childchange" only)
519
+ *
520
+ * @public
456
521
  */
457
- _invalidate() {
458
- if (!this._upToDate) {
459
- // console.log("already invalidated", this, ...arguments);
460
- return;
461
- }
462
-
463
- if (this.getDomRef() && !this._suppressInvalidation) {
464
- this._upToDate = false;
465
- // console.log("INVAL", this, ...arguments);
466
- RenderScheduler.renderDeferred(this);
467
- }
468
- }
522
+ onInvalidation(changeInfo) {}
469
523
 
470
524
  /**
471
- * Do not call this method directly, only intended to be called by RenderScheduler.js
525
+ * Do not call this method directly, only intended to be called by js
472
526
  * @protected
473
527
  */
474
528
  _render() {
@@ -487,15 +541,37 @@ class UI5Element extends HTMLElement {
487
541
  }
488
542
 
489
543
  // resume normal invalidation handling
490
- delete this._suppressInvalidation;
544
+ this._suppressInvalidation = false;
491
545
 
492
546
  // Update the shadow root with the render result
493
- // console.log(this.getDomRef() ? "RE-RENDER" : "FIRST RENDER", this);
494
- this._upToDate = true;
495
- this._updateShadowRoot();
547
+ /*
548
+ if (this._changedState.length) {
549
+ let element = this.localName;
550
+ if (this.id) {
551
+ element = `${element}#${this.id}`;
552
+ }
553
+ console.log("Re-rendering:", element, this._changedState.map(x => { // eslint-disable-line
554
+ let res = `${x.type}`;
555
+ if (x.reason) {
556
+ res = `${res}(${x.reason})`;
557
+ }
558
+ res = `${res}: ${x.name}`;
559
+ if (x.type === "property") {
560
+ res = `${res} ${x.oldValue} => ${x.newValue}`;
561
+ }
496
562
 
497
- if (this._shouldUpdateFragment()) {
498
- this.staticAreaItem._updateFragment(this);
563
+ return res;
564
+ }));
565
+ }
566
+ */
567
+ this._changedState = [];
568
+
569
+ // Update shadow root and static area item
570
+ if (this.constructor._needsShadowDOM()) {
571
+ updateShadowRoot(this);
572
+ }
573
+ if (this.staticAreaItem) {
574
+ this.staticAreaItem.update();
499
575
  }
500
576
 
501
577
  // Safari requires that children get the slot attribute only after the slot tags have been rendered in the shadow DOM
@@ -509,23 +585,6 @@ class UI5Element extends HTMLElement {
509
585
  }
510
586
  }
511
587
 
512
- /**
513
- * @private
514
- */
515
- _updateShadowRoot() {
516
- if (!this.constructor._needsShadowDOM()) {
517
- return;
518
- }
519
-
520
- let styleToPrepend;
521
- const renderResult = this.constructor.template(this);
522
-
523
- if (!document.adoptedStyleSheets && !window.ShadyDOM) {
524
- styleToPrepend = getEffectiveStyle(this.constructor);
525
- }
526
- this.constructor.render(renderResult, this.shadowRoot, styleToPrepend, { eventContext: this });
527
- }
528
-
529
588
  /**
530
589
  * @private
531
590
  */
@@ -548,16 +607,27 @@ class UI5Element extends HTMLElement {
548
607
 
549
608
  /**
550
609
  * Returns the DOM Element inside the Shadow Root that corresponds to the opening tag in the UI5 Web Component's template
610
+ * *Note:* For logical (abstract) elements (items, options, etc...), returns the part of the parent's DOM that represents this option
551
611
  * Use this method instead of "this.shadowRoot" to read the Shadow DOM, if ever necessary
612
+ *
552
613
  * @public
553
614
  */
554
615
  getDomRef() {
616
+ // If a component set _getRealDomRef to its children, use the return value of this function
617
+ if (typeof this._getRealDomRef === "function") {
618
+ return this._getRealDomRef();
619
+ }
620
+
555
621
  if (!this.shadowRoot || this.shadowRoot.children.length === 0) {
556
622
  return;
557
623
  }
558
624
 
559
- return this.shadowRoot.children.length === 1
560
- ? this.shadowRoot.children[0] : this.shadowRoot.children[1];
625
+ const children = [...this.shadowRoot.children].filter(child => !["link", "style"].includes(child.localName));
626
+ if (children.length !== 1) {
627
+ console.warn(`The shadow DOM for ${this.constructor.getMetadata().getTag()} does not have a top level element, the getDomRef() method might not work as expected`); // eslint-disable-line
628
+ }
629
+
630
+ return children[0];
561
631
  }
562
632
 
563
633
  /**
@@ -573,6 +643,16 @@ class UI5Element extends HTMLElement {
573
643
  }
574
644
  }
575
645
 
646
+ /**
647
+ * Waits for dom ref and then returns the DOM Element marked with "data-sap-focus-ref" inside the template.
648
+ * This is the element that will receive the focus by default.
649
+ * @public
650
+ */
651
+ async getFocusDomRefAsync() {
652
+ await this._waitForDomRef();
653
+ return this.getFocusDomRef();
654
+ }
655
+
576
656
  /**
577
657
  * Set the focus to the element, returned by "getFocusDomRef()" (marked by "data-sap-focus-ref")
578
658
  * @public
@@ -593,37 +673,47 @@ class UI5Element extends HTMLElement {
593
673
  * @param name - name of the event
594
674
  * @param data - additional data for the event
595
675
  * @param cancelable - true, if the user can call preventDefault on the event object
676
+ * @param bubbles - true, if the event bubbles
596
677
  * @returns {boolean} false, if the event was cancelled (preventDefault called), true otherwise
597
678
  */
598
- fireEvent(name, data, cancelable) {
599
- let compatEventResult = true; // Initialized to true, because if the event is not fired at all, it should be considered "not-prevented"
679
+ fireEvent(name, data, cancelable = false, bubbles = true) {
680
+ const eventResult = this._fireEvent(name, data, cancelable, bubbles);
681
+ const camelCaseEventName = kebabToCamelCase(name);
600
682
 
683
+ if (camelCaseEventName !== name) {
684
+ return eventResult && this._fireEvent(camelCaseEventName, data, cancelable);
685
+ }
686
+
687
+ return eventResult;
688
+ }
689
+
690
+ _fireEvent(name, data, cancelable = false, bubbles = true) {
601
691
  const noConflictEvent = new CustomEvent(`ui5-${name}`, {
602
692
  detail: data,
603
693
  composed: false,
604
- bubbles: true,
694
+ bubbles,
605
695
  cancelable,
606
696
  });
607
697
 
608
- // This will be false if the compat event is prevented
609
- compatEventResult = this.dispatchEvent(noConflictEvent);
698
+ // This will be false if the no-conflict event is prevented
699
+ const noConflictEventResult = this.dispatchEvent(noConflictEvent);
610
700
 
611
701
  if (skipOriginalEvent(name)) {
612
- return compatEventResult;
702
+ return noConflictEventResult;
613
703
  }
614
704
 
615
- const customEvent = new CustomEvent(name, {
705
+ const normalEvent = new CustomEvent(name, {
616
706
  detail: data,
617
707
  composed: false,
618
- bubbles: true,
708
+ bubbles,
619
709
  cancelable,
620
710
  });
621
711
 
622
712
  // This will be false if the normal event is prevented
623
- const normalEventResult = this.dispatchEvent(customEvent);
713
+ const normalEventResult = this.dispatchEvent(normalEvent);
624
714
 
625
715
  // Return false if any of the two events was prevented (its result was false).
626
- return normalEventResult && compatEventResult;
716
+ return normalEventResult && noConflictEventResult;
627
717
  }
628
718
 
629
719
  /**
@@ -632,24 +722,19 @@ class UI5Element extends HTMLElement {
632
722
  * @public
633
723
  */
634
724
  getSlottedNodes(slotName) {
635
- const reducer = (acc, curr) => {
636
- if (!isSlot(curr)) {
637
- return acc.concat([curr]);
638
- }
639
- return acc.concat(curr.assignedNodes({ flatten: true }).filter(item => item instanceof HTMLElement));
640
- };
641
-
642
- return this[slotName].reduce(reducer, []);
725
+ return getSlottedElementsList(this[slotName]);
643
726
  }
644
727
 
645
- get isCompact() {
646
- return getComputedStyle(this).getPropertyValue(GLOBAL_CONTENT_DENSITY_CSS_VAR) === "compact";
647
- }
648
-
649
- updateStaticAreaItemContentDensity() {
650
- if (this.staticAreaItem) {
651
- this.staticAreaItem._updateContentDensity(this.isCompact);
652
- }
728
+ /**
729
+ * Determines whether the component should be rendered in RTL mode or not.
730
+ * Returns: "rtl", "ltr" or undefined
731
+ *
732
+ * @public
733
+ * @returns {String|undefined}
734
+ */
735
+ get effectiveDir() {
736
+ markAsRtlAware(this.constructor); // if a UI5 Element calls this method, it's considered to be rtl-aware
737
+ return getEffectiveDir(this);
653
738
  }
654
739
 
655
740
  /**
@@ -669,26 +754,6 @@ class UI5Element extends HTMLElement {
669
754
  return this.getMetadata().getAttributesList();
670
755
  }
671
756
 
672
- /**
673
- * @private
674
- */
675
- static _getSlotName(child) {
676
- // Text nodes can only go to the default slot
677
- if (!(child instanceof HTMLElement)) {
678
- return "default";
679
- }
680
-
681
- // Discover the slot based on the real slot name (f.e. footer => footer, or content-32 => content)
682
- const slot = child.getAttribute("slot");
683
- if (slot) {
684
- const match = slot.match(/^(.+?)-\d+$/);
685
- return match ? match[1] : slot;
686
- }
687
-
688
- // Use default slot as a fallback
689
- return "default";
690
- }
691
-
692
757
  /**
693
758
  * @private
694
759
  */
@@ -696,70 +761,30 @@ class UI5Element extends HTMLElement {
696
761
  return !!this.template;
697
762
  }
698
763
 
699
- _shouldUpdateFragment() {
700
- return this.constructor._needsStaticArea() && this.staticAreaItem.isRendered();
701
- }
702
-
703
764
  /**
704
765
  * @private
705
766
  */
706
767
  static _needsStaticArea() {
707
- return typeof this.staticAreaTemplate === "function";
768
+ return !!this.staticAreaTemplate;
708
769
  }
709
770
 
710
771
  /**
711
772
  * @public
712
773
  */
713
774
  getStaticAreaItemDomRef() {
714
- return this.staticAreaItem.getDomRef();
715
- }
716
-
717
- /**
718
- * @private
719
- */
720
- static _getDefaultState() {
721
- if (this._defaultState) {
722
- return this._defaultState;
775
+ if (!this.constructor._needsStaticArea()) {
776
+ throw new Error("This component does not use the static area");
723
777
  }
724
778
 
725
- const MetadataClass = this.getMetadata();
726
- const defaultState = {};
727
- const slotsAreManaged = MetadataClass.slotsAreManaged();
728
-
729
- // Initialize properties
730
- const props = MetadataClass.getProperties();
731
- for (const propName in props) { // eslint-disable-line
732
- const propType = props[propName].type;
733
- const propDefaultValue = props[propName].defaultValue;
734
-
735
- if (propType === Boolean) {
736
- defaultState[propName] = false;
737
-
738
- if (propDefaultValue !== undefined) {
739
- console.warn("The 'defaultValue' metadata key is ignored for all booleans properties, they would be initialized with 'false' by default"); // eslint-disable-line
740
- }
741
- } else if (props[propName].multiple) {
742
- defaultState[propName] = [];
743
- } else if (propType === Object) {
744
- defaultState[propName] = "defaultValue" in props[propName] ? props[propName].defaultValue : {};
745
- } else if (propType === String) {
746
- defaultState[propName] = "defaultValue" in props[propName] ? props[propName].defaultValue : "";
747
- } else {
748
- defaultState[propName] = propDefaultValue;
749
- }
779
+ if (!this.staticAreaItem) {
780
+ this.staticAreaItem = StaticAreaItem.createInstance();
781
+ this.staticAreaItem.setOwnerElement(this);
750
782
  }
751
-
752
- // Initialize slots
753
- if (slotsAreManaged) {
754
- const slots = MetadataClass.getSlots();
755
- for (const [slotName, slotData] of Object.entries(slots)) { // eslint-disable-line
756
- const propertyName = slotData.propertyName || slotName;
757
- defaultState[propertyName] = [];
758
- }
783
+ if (!this.staticAreaItem.parentElement) {
784
+ getSingletonElementInstance("ui5-static-area").appendChild(this.staticAreaItem);
759
785
  }
760
786
 
761
- this._defaultState = defaultState;
762
- return defaultState;
787
+ return this.staticAreaItem.getDomRef();
763
788
  }
764
789
 
765
790
  /**
@@ -773,7 +798,7 @@ class UI5Element extends HTMLElement {
773
798
  const properties = this.getMetadata().getProperties();
774
799
  for (const [prop, propData] of Object.entries(properties)) { // eslint-disable-line
775
800
  if (!isValidPropertyName(prop)) {
776
- throw new Error(`"${prop}" is not a valid property name. Use a name that does not collide with DOM APIs`);
801
+ console.warn(`"${prop}" is not a valid property name. Use a name that does not collide with DOM APIs`); /* eslint-disable-line */
777
802
  }
778
803
 
779
804
  if (propData.type === Boolean && propData.defaultValue) {
@@ -811,14 +836,27 @@ class UI5Element extends HTMLElement {
811
836
  }
812
837
  },
813
838
  set(value) {
839
+ let isDifferent;
814
840
  value = this.constructor.getMetadata().constructor.validatePropertyValue(value, propData);
815
841
 
816
842
  const oldState = this._state[prop];
843
+ if (propData.multiple && propData.compareValues) {
844
+ isDifferent = !arraysAreEqual(oldState, value);
845
+ } else if (isDescendantOf(propData.type, DataType)) {
846
+ isDifferent = !propData.type.valuesAreEqual(oldState, value);
847
+ } else {
848
+ isDifferent = oldState !== value;
849
+ }
817
850
 
818
- if (oldState !== value) {
851
+ if (isDifferent) {
819
852
  this._state[prop] = value;
820
- this._invalidate(prop, value);
821
- this._propertyChange(prop, value);
853
+ _invalidate.call(this, {
854
+ type: "property",
855
+ name: prop,
856
+ newValue: value,
857
+ oldValue: oldState,
858
+ });
859
+ this._updateAttribute(prop, value);
822
860
  }
823
861
  },
824
862
  });
@@ -829,7 +867,7 @@ class UI5Element extends HTMLElement {
829
867
  const slots = this.getMetadata().getSlots();
830
868
  for (const [slotName, slotData] of Object.entries(slots)) { // eslint-disable-line
831
869
  if (!isValidPropertyName(slotName)) {
832
- throw new Error(`"${slotName}" is not a valid property name. Use a name that does not collide with DOM APIs`);
870
+ console.warn(`"${slotName}" is not a valid property name. Use a name that does not collide with DOM APIs`); /* eslint-disable-line */
833
871
  }
834
872
 
835
873
  const propertyName = slotData.propertyName || slotName;
@@ -841,7 +879,7 @@ class UI5Element extends HTMLElement {
841
879
  return [];
842
880
  },
843
881
  set() {
844
- throw new Error("Cannot set slots directly, use the DOM APIs");
882
+ throw new Error("Cannot set slot content directly, use the DOM APIs (appendChild, removeChild, etc...)");
845
883
  },
846
884
  });
847
885
  }
@@ -853,7 +891,7 @@ class UI5Element extends HTMLElement {
853
891
  * @protected
854
892
  */
855
893
  static get metadata() {
856
- return metadata;
894
+ return {};
857
895
  }
858
896
 
859
897
  /**
@@ -872,6 +910,50 @@ class UI5Element extends HTMLElement {
872
910
  return "";
873
911
  }
874
912
 
913
+ /**
914
+ * Returns an array with the dependencies for this UI5 Web Component, which could be:
915
+ * - composed components (used in its shadow root or static area item)
916
+ * - slotted components that the component may need to communicate with
917
+ *
918
+ * @protected
919
+ */
920
+ static get dependencies() {
921
+ return [];
922
+ }
923
+
924
+ /**
925
+ * Returns a list of the unique dependencies for this UI5 Web Component
926
+ *
927
+ * @public
928
+ */
929
+ static getUniqueDependencies() {
930
+ if (!uniqueDependenciesCache.has(this)) {
931
+ const filtered = this.dependencies.filter((dep, index, deps) => deps.indexOf(dep) === index);
932
+ uniqueDependenciesCache.set(this, filtered);
933
+ }
934
+
935
+ return uniqueDependenciesCache.get(this);
936
+ }
937
+
938
+ /**
939
+ * Returns a promise that resolves whenever all dependencies for this UI5 Web Component have resolved
940
+ *
941
+ * @returns {Promise<any[]>}
942
+ */
943
+ static whenDependenciesDefined() {
944
+ return Promise.all(this.getUniqueDependencies().map(dep => dep.define()));
945
+ }
946
+
947
+ /**
948
+ * Hook that will be called upon custom element definition
949
+ *
950
+ * @protected
951
+ * @returns {Promise<void>}
952
+ */
953
+ static async onDefine() {
954
+ return Promise.resolve();
955
+ }
956
+
875
957
  /**
876
958
  * Registers a UI5 Web Component in the browser window object
877
959
  * @public
@@ -880,21 +962,31 @@ class UI5Element extends HTMLElement {
880
962
  static async define() {
881
963
  await boot();
882
964
 
883
- if (this.onDefine) {
884
- await this.onDefine();
885
- }
965
+ await Promise.all([
966
+ this.whenDependenciesDefined(),
967
+ this.onDefine(),
968
+ ]);
886
969
 
887
970
  const tag = this.getMetadata().getTag();
971
+ const altTag = this.getMetadata().getAltTag();
888
972
 
889
973
  const definedLocally = isTagRegistered(tag);
890
974
  const definedGlobally = customElements.get(tag);
891
975
 
892
976
  if (definedGlobally && !definedLocally) {
893
- console.warn(`Skipping definition of tag ${tag}, because it was already defined by another instance of ui5-webcomponents.`); // eslint-disable-line
977
+ recordTagRegistrationFailure(tag);
894
978
  } else if (!definedGlobally) {
895
979
  this._generateAccessors();
896
980
  registerTag(tag);
897
981
  window.customElements.define(tag, this);
982
+ preloadLinks(this);
983
+
984
+ if (altTag && !customElements.get(altTag)) {
985
+ registerTag(altTag);
986
+ window.customElements.define(altTag, getClassCopy(this, () => {
987
+ console.log(`The ${altTag} tag is deprecated and will be removed in the next release, please use ${tag} instead.`); // eslint-disable-line
988
+ }));
989
+ }
898
990
  }
899
991
  return this;
900
992
  }