@ui5/webcomponents-base 0.0.0-39bd3067f → 0.0.0-4180fe799

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 (329) hide show
  1. package/.eslintignore +3 -0
  2. package/CHANGELOG.md +158 -0
  3. package/README.md +26 -2
  4. package/bundle.esm.js +16 -7
  5. package/dist/AssetRegistry.js +8 -6
  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 +421 -301
  27. package/dist/UI5ElementMetadata.js +165 -19
  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 +24 -33
  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 +5 -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 -12
  143. package/src/AssetRegistry.js +8 -6
  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 +421 -301
  165. package/src/UI5ElementMetadata.js +165 -19
  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 +24 -33
  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 +5 -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/webcomponentsjs/LICENSE.md +0 -19
  276. package/dist/webcomponentsjs/README.md +0 -229
  277. package/dist/webcomponentsjs/bundles/webcomponents-ce.js +0 -63
  278. package/dist/webcomponentsjs/bundles/webcomponents-ce.js.map +0 -1
  279. package/dist/webcomponentsjs/bundles/webcomponents-sd-ce-pf.js +0 -297
  280. package/dist/webcomponentsjs/bundles/webcomponents-sd-ce-pf.js.map +0 -1
  281. package/dist/webcomponentsjs/bundles/webcomponents-sd-ce.js +0 -208
  282. package/dist/webcomponentsjs/bundles/webcomponents-sd-ce.js.map +0 -1
  283. package/dist/webcomponentsjs/bundles/webcomponents-sd.js +0 -166
  284. package/dist/webcomponentsjs/bundles/webcomponents-sd.js.map +0 -1
  285. package/dist/webcomponentsjs/custom-elements-es5-adapter.js +0 -15
  286. package/dist/webcomponentsjs/package.json +0 -46
  287. package/dist/webcomponentsjs/src/entrypoints/custom-elements-es5-adapter-index.js +0 -16
  288. package/dist/webcomponentsjs/src/entrypoints/webcomponents-bundle-index.js +0 -53
  289. package/dist/webcomponentsjs/src/entrypoints/webcomponents-ce-index.js +0 -17
  290. package/dist/webcomponentsjs/src/entrypoints/webcomponents-sd-ce-index.js +0 -19
  291. package/dist/webcomponentsjs/src/entrypoints/webcomponents-sd-ce-pf-index.js +0 -28
  292. package/dist/webcomponentsjs/src/entrypoints/webcomponents-sd-index.js +0 -18
  293. package/dist/webcomponentsjs/webcomponents-bundle.js +0 -298
  294. package/dist/webcomponentsjs/webcomponents-bundle.js.map +0 -1
  295. package/dist/webcomponentsjs/webcomponents-loader.js +0 -185
  296. package/src/SVGIconRegistry.js +0 -60
  297. package/src/boot.js +0 -32
  298. package/src/compatibility/DOMObserver.js +0 -62
  299. package/src/compatibility/patchNodeValue.js +0 -24
  300. package/src/compatibility/whenPolyfillLoaded.js +0 -26
  301. package/src/delegate/CustomResize.js +0 -78
  302. package/src/delegate/NativeResize.js +0 -44
  303. package/src/features/browsersupport/Edge.js +0 -6
  304. package/src/features/browsersupport/IE11.js +0 -41
  305. package/src/features/browsersupport/IE11WithWebComponentsPolyfill.js +0 -5
  306. package/src/renderer/ifDefined.js +0 -21
  307. package/src/theming/CSSVarsPonyfill.js +0 -24
  308. package/src/theming/adaptCSSForIE.js +0 -90
  309. package/src/theming/createComponentStyleTag.js +0 -49
  310. package/src/theming/createThemePropertiesStyleTag.js +0 -20
  311. package/src/thirdparty/Array.from.js +0 -16
  312. package/src/thirdparty/Array.prototype.fill.js +0 -44
  313. package/src/thirdparty/Array.prototype.find.js +0 -46
  314. package/src/thirdparty/Array.prototype.includes.js +0 -51
  315. package/src/thirdparty/Element.prototype.closest.js +0 -11
  316. package/src/thirdparty/Element.prototype.matches.js +0 -6
  317. package/src/thirdparty/Map.prototype.keys.js +0 -9
  318. package/src/thirdparty/Number.isInteger.js +0 -5
  319. package/src/thirdparty/Number.isNaN.js +0 -1
  320. package/src/thirdparty/Number.parseInt.js +0 -1
  321. package/src/thirdparty/Object.assign.js +0 -31
  322. package/src/thirdparty/Object.entries.js +0 -11
  323. package/src/thirdparty/Symbol.js +0 -2
  324. package/src/thirdparty/WeakSet.js +0 -27
  325. package/src/thirdparty/es6-string-methods.js +0 -182
  326. package/src/thirdparty/events-polyfills.js +0 -89
  327. package/src/thirdparty/fetch.js +0 -1
  328. package/src/thirdparty/template.js +0 -600
  329. package/src/thirdparty/webcomponents-sd-ce-pf.js +0 -318
@@ -1,29 +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
-
16
- const metadata = {
17
- events: {
18
- _propertyChange: {},
19
- },
20
- };
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";
21
22
 
22
23
  let autoId = 0;
23
24
 
24
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
+ }
25
48
 
26
- const GLOBAL_CONTENT_DENSITY_CSS_VAR = "--_ui5_content_density";
27
49
  /**
28
50
  * Base class for all UI5 Web Components
29
51
  *
@@ -37,56 +59,40 @@ const GLOBAL_CONTENT_DENSITY_CSS_VAR = "--_ui5_content_density";
37
59
  class UI5Element extends HTMLElement {
38
60
  constructor() {
39
61
  super();
40
- this._generateId();
41
- this._initializeState();
42
- this._upgradeAllProperties();
43
- this._initializeContainers();
44
- this._upToDate = false;
45
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
46
70
  let deferredResolve;
47
71
  this._domRefReadyPromise = new Promise(resolve => {
48
72
  deferredResolve = resolve;
49
73
  });
50
74
  this._domRefReadyPromise._deferredResolve = deferredResolve;
51
75
 
52
- this._monitoredChildProps = new Map();
53
- this._firePropertyChange = false;
54
- }
76
+ this._initializeState();
77
+ this._upgradeAllProperties();
55
78
 
56
- /**
57
- * @private
58
- */
59
- _generateId() {
60
- this._id = `ui5wc_${++autoId}`;
79
+ if (this.constructor._needsShadowDOM()) {
80
+ this.attachShadow({ mode: "open" });
81
+ }
61
82
  }
62
83
 
63
84
  /**
64
- * @private
85
+ * Returns a unique ID for this UI5 Element
86
+ *
87
+ * @deprecated - This property is not guaranteed in future releases
88
+ * @protected
65
89
  */
66
- _initializeContainers() {
67
- const needsShadowDOM = this.constructor._needsShadowDOM();
68
- const needsStaticArea = this.constructor._needsStaticArea();
69
-
70
- // Init Shadow Root
71
- if (needsShadowDOM) {
72
- this.attachShadow({ mode: "open" });
73
-
74
- // IE11, Edge
75
- if (window.ShadyDOM) {
76
- createComponentStyleTag(this.constructor);
77
- }
78
-
79
- // Chrome
80
- if (document.adoptedStyleSheets) {
81
- const style = getConstructableStyle(this.constructor);
82
- this.shadowRoot.adoptedStyleSheets = [style];
83
- }
90
+ get _id() {
91
+ if (!this.__id) {
92
+ this.__id = `ui5wc_${++autoId}`;
84
93
  }
85
94
 
86
- // Init StaticAreaItem only if needed
87
- if (needsStaticArea) {
88
- this.staticAreaItem = new StaticAreaItem(this);
89
- }
95
+ return this.__id;
90
96
  }
91
97
 
92
98
  /**
@@ -94,26 +100,27 @@ class UI5Element extends HTMLElement {
94
100
  * @private
95
101
  */
96
102
  async connectedCallback() {
97
- const needsShadowDOM = this.constructor._needsShadowDOM();
103
+ this.setAttribute(this.constructor.getMetadata().getPureTag(), "");
104
+
98
105
  const slotsAreManaged = this.constructor.getMetadata().slotsAreManaged();
99
106
 
100
- // Render the Shadow DOM
101
- if (needsShadowDOM) {
102
- if (slotsAreManaged) {
103
- // always register the observer before yielding control to the main thread (await)
104
- this._startObservingDOMChildren();
105
- await this._processChildren();
106
- }
107
+ this._inDOM = true;
107
108
 
108
- if (!this.shadowRoot) { // Workaround for Firefox74 bug
109
- await Promise.resolve();
110
- }
109
+ if (slotsAreManaged) {
110
+ // always register the observer before yielding control to the main thread (await)
111
+ this._startObservingDOMChildren();
112
+ await this._processChildren();
113
+ }
111
114
 
112
- await RenderScheduler.renderImmediately(this);
113
- this._domRefReadyPromise._deferredResolve();
114
- if (typeof this.onEnterDOM === "function") {
115
- this.onEnterDOM();
116
- }
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();
117
124
  }
118
125
  }
119
126
 
@@ -122,23 +129,26 @@ class UI5Element extends HTMLElement {
122
129
  * @private
123
130
  */
124
131
  disconnectedCallback() {
125
- const needsShadowDOM = this.constructor._needsShadowDOM();
126
- const needsStaticArea = this.constructor._needsStaticArea();
127
132
  const slotsAreManaged = this.constructor.getMetadata().slotsAreManaged();
128
133
 
129
- if (needsShadowDOM) {
130
- if (slotsAreManaged) {
131
- this._stopObservingDOMChildren();
132
- }
134
+ this._inDOM = false;
133
135
 
136
+ if (slotsAreManaged) {
137
+ this._stopObservingDOMChildren();
138
+ }
139
+
140
+ if (this._fullyConnected) {
134
141
  if (typeof this.onExitDOM === "function") {
135
142
  this.onExitDOM();
136
143
  }
144
+ this._fullyConnected = false;
137
145
  }
138
146
 
139
- if (needsStaticArea) {
140
- this.staticAreaItem._removeFragmentFromStaticArea();
147
+ if (this.staticAreaItem && this.staticAreaItem.parentElement) {
148
+ this.staticAreaItem.parentElement.removeChild(this.staticAreaItem);
141
149
  }
150
+
151
+ cancelRender(this);
142
152
  }
143
153
 
144
154
  /**
@@ -154,16 +164,16 @@ class UI5Element extends HTMLElement {
154
164
  const mutationObserverOptions = {
155
165
  childList: true,
156
166
  subtree: canSlotText,
157
- characterData: true,
167
+ characterData: canSlotText,
158
168
  };
159
- DOMObserver.observeDOMNode(this, this._processChildren.bind(this), mutationObserverOptions);
169
+ observeDOMNode(this, this._processChildren.bind(this), mutationObserverOptions);
160
170
  }
161
171
 
162
172
  /**
163
173
  * @private
164
174
  */
165
175
  _stopObservingDOMChildren() {
166
- DOMObserver.unobserveDOMNode(this);
176
+ unobserveDOMNode(this);
167
177
  }
168
178
 
169
179
  /**
@@ -185,8 +195,14 @@ class UI5Element extends HTMLElement {
185
195
  const canSlotText = this.constructor.getMetadata().canSlotText();
186
196
  const domChildren = Array.from(canSlotText ? this.childNodes : this.children);
187
197
 
188
- // 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
189
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]]);
190
206
  this._clearSlot(slotName, slotData);
191
207
  }
192
208
 
@@ -195,7 +211,7 @@ class UI5Element extends HTMLElement {
195
211
 
196
212
  const allChildrenUpgraded = domChildren.map(async (child, idx) => {
197
213
  // Determine the type of the child (mainly by the slot attribute)
198
- const slotName = this.constructor._getSlotName(child);
214
+ const slotName = getSlotName(child);
199
215
  const slotData = slotsMap[slotName];
200
216
 
201
217
  // Check if the slotName is supported
@@ -233,8 +249,14 @@ class UI5Element extends HTMLElement {
233
249
 
234
250
  child = this.constructor.getMetadata().constructor.validateSlotValue(child, slotData);
235
251
 
236
- if (child.isUI5Element && slotData.listenFor) {
237
- 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));
255
+ }
256
+
257
+ // Listen for the slotchange event if the child is a slot itself
258
+ if (isSlot(child)) {
259
+ this._attachSlotChange(child, slotName);
238
260
  }
239
261
 
240
262
  const propertyName = slotData.propertyName || slotName;
@@ -250,10 +272,33 @@ class UI5Element extends HTMLElement {
250
272
 
251
273
  // Distribute the child in the _state object, keeping the Light DOM order,
252
274
  // not the order elements are defined.
253
- slottedChildrenMap.forEach((children, slot) => {
254
- 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);
255
277
  });
256
- 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
+ }
257
302
  }
258
303
 
259
304
  /**
@@ -262,20 +307,61 @@ class UI5Element extends HTMLElement {
262
307
  */
263
308
  _clearSlot(slotName, slotData) {
264
309
  const propertyName = slotData.propertyName || slotName;
265
-
266
- let children = this._state[propertyName];
267
- if (!Array.isArray(children)) {
268
- children = [children];
269
- }
310
+ const children = this._state[propertyName];
270
311
 
271
312
  children.forEach(child => {
272
313
  if (child && child.isUI5Element) {
273
- this._detachChildPropertyUpdated(child);
314
+ child.detachInvalidate(this._getChildChangeListener(slotName));
315
+ }
316
+
317
+ if (isSlot(child)) {
318
+ this._detachSlotChange(child, slotName);
274
319
  }
275
320
  });
276
321
 
277
322
  this._state[propertyName] = [];
278
- 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
+ });
279
365
  }
280
366
 
281
367
  /**
@@ -290,9 +376,8 @@ class UI5Element extends HTMLElement {
290
376
  const propertyTypeClass = properties[nameInCamelCase].type;
291
377
  if (propertyTypeClass === Boolean) {
292
378
  newValue = newValue !== null;
293
- }
294
- if (propertyTypeClass === Integer) {
295
- newValue = parseInt(newValue);
379
+ } else if (isDescendantOf(propertyTypeClass, DataType)) {
380
+ newValue = propertyTypeClass.attributeToProperty(newValue);
296
381
  }
297
382
  this[nameInCamelCase] = newValue;
298
383
  }
@@ -305,22 +390,24 @@ class UI5Element extends HTMLElement {
305
390
  if (!this.constructor.getMetadata().hasAttribute(name)) {
306
391
  return;
307
392
  }
308
-
309
- if (typeof newValue === "object") {
310
- return;
311
- }
312
-
393
+ const properties = this.constructor.getMetadata().getProperties();
394
+ const propertyTypeClass = properties[name].type;
313
395
  const attrName = camelToKebabCase(name);
314
396
  const attrValue = this.getAttribute(attrName);
315
- if (typeof newValue === "boolean") {
397
+
398
+ if (propertyTypeClass === Boolean) {
316
399
  if (newValue === true && attrValue === null) {
317
400
  this.setAttribute(attrName, "");
318
401
  } else if (newValue === false && attrValue !== null) {
319
402
  this.removeAttribute(attrName);
320
403
  }
321
- } else if (attrValue !== newValue) {
322
- this.setAttribute(attrName, newValue);
323
- }
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
324
411
  }
325
412
 
326
413
  /**
@@ -346,101 +433,96 @@ class UI5Element extends HTMLElement {
346
433
  * @private
347
434
  */
348
435
  _initializeState() {
349
- const defaultState = this.constructor._getDefaultState();
350
- this._state = Object.assign({}, defaultState);
436
+ this._state = { ...this.constructor.getMetadata().getInitialState() };
351
437
  }
352
438
 
353
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}
354
444
  * @private
355
445
  */
356
- _attachChildPropertyUpdated(child, listenFor) {
357
- const childMetadata = child.constructor.getMetadata(),
358
- slotName = this.constructor._getSlotName(child), // all slotted children have the same configuration
359
- childProperties = childMetadata.getProperties();
360
-
361
- let observedProps = [],
362
- notObservedProps = [];
363
-
364
- if (Array.isArray(listenFor)) {
365
- observedProps = listenFor;
366
- } else {
367
- observedProps = Array.isArray(listenFor.props) ? listenFor.props : Object.keys(childProperties);
368
- 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));
369
449
  }
370
-
371
- if (!this._monitoredChildProps.has(slotName)) {
372
- this._monitoredChildProps.set(slotName, { observedProps, notObservedProps });
373
- }
374
-
375
- child.addEventListener("_propertyChange", this._invalidateParentOnPropertyUpdate);
376
- child._firePropertyChange = true;
450
+ return this._childChangeListeners.get(slotName);
377
451
  }
378
452
 
379
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}
380
458
  * @private
381
459
  */
382
- _detachChildPropertyUpdated(child) {
383
- child.removeEventListener("_propertyChange", this._invalidateParentOnPropertyUpdate);
384
- child._firePropertyChange = false;
460
+ _getSlotChangeListener(slotName) {
461
+ if (!this._slotChangeListeners.has(slotName)) {
462
+ this._slotChangeListeners.set(slotName, this._onSlotChange.bind(this, slotName));
463
+ }
464
+ return this._slotChangeListeners.get(slotName);
385
465
  }
386
466
 
387
467
  /**
388
468
  * @private
389
469
  */
390
- _propertyChange(name, value) {
391
- this._updateAttribute(name, value);
392
-
393
- if (this._firePropertyChange) {
394
- this.dispatchEvent(new CustomEvent("_propertyChange", {
395
- detail: { name, newValue: value },
396
- composed: false,
397
- bubbles: true,
398
- }));
399
- }
470
+ _attachSlotChange(child, slotName) {
471
+ child.addEventListener("slotchange", this._getSlotChangeListener(slotName));
400
472
  }
401
473
 
402
474
  /**
403
475
  * @private
404
476
  */
405
- _invalidateParentOnPropertyUpdate(prop) {
406
- // The web component to be invalidated
407
- const parentNode = this.parentNode;
408
- if (!parentNode) {
409
- return;
410
- }
411
-
412
- const slotName = parentNode.constructor._getSlotName(this);
413
- const propsMetadata = parentNode._monitoredChildProps.get(slotName);
414
-
415
- if (!propsMetadata) {
416
- return;
417
- }
418
- const { observedProps, notObservedProps } = propsMetadata;
419
-
420
- if (observedProps.includes(prop.detail.name) && !notObservedProps.includes(prop.detail.name)) {
421
- parentNode._invalidate("_parent_", this);
422
- }
477
+ _detachSlotChange(child, slotName) {
478
+ child.removeEventListener("slotchange", this._getSlotChangeListener(slotName));
423
479
  }
424
480
 
425
481
  /**
426
- * Asynchronously re-renders an already rendered web component
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
427
485
  * @private
428
486
  */
429
- _invalidate() {
430
- if (!this._upToDate) {
431
- // console.log("already invalidated", this, ...arguments);
432
- return;
433
- }
434
-
435
- if (this.getDomRef() && !this._suppressInvalidation) {
436
- this._upToDate = false;
437
- // console.log("INVAL", this, ...arguments);
438
- RenderScheduler.renderDeferred(this);
439
- }
487
+ _onSlotChange(slotName) {
488
+ _invalidate.call(this, {
489
+ type: "slot",
490
+ name: slotName,
491
+ reason: "slotchange",
492
+ });
440
493
  }
441
494
 
442
495
  /**
443
- * Do not call this method directly, only intended to be called by RenderScheduler.js
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
521
+ */
522
+ onInvalidation(changeInfo) {}
523
+
524
+ /**
525
+ * Do not call this method directly, only intended to be called by js
444
526
  * @protected
445
527
  */
446
528
  _render() {
@@ -459,15 +541,37 @@ class UI5Element extends HTMLElement {
459
541
  }
460
542
 
461
543
  // resume normal invalidation handling
462
- delete this._suppressInvalidation;
544
+ this._suppressInvalidation = false;
463
545
 
464
546
  // Update the shadow root with the render result
465
- // console.log(this.getDomRef() ? "RE-RENDER" : "FIRST RENDER", this);
466
- this._upToDate = true;
467
- 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
+ }
468
562
 
469
- if (this._shouldUpdateFragment()) {
470
- 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();
471
575
  }
472
576
 
473
577
  // Safari requires that children get the slot attribute only after the slot tags have been rendered in the shadow DOM
@@ -481,23 +585,6 @@ class UI5Element extends HTMLElement {
481
585
  }
482
586
  }
483
587
 
484
- /**
485
- * @private
486
- */
487
- _updateShadowRoot() {
488
- if (!this.constructor._needsShadowDOM()) {
489
- return;
490
- }
491
-
492
- let styleToPrepend;
493
- const renderResult = this.constructor.template(this);
494
-
495
- if (!document.adoptedStyleSheets && !window.ShadyDOM) {
496
- styleToPrepend = getEffectiveStyle(this.constructor);
497
- }
498
- this.constructor.render(renderResult, this.shadowRoot, styleToPrepend, { eventContext: this });
499
- }
500
-
501
588
  /**
502
589
  * @private
503
590
  */
@@ -520,16 +607,27 @@ class UI5Element extends HTMLElement {
520
607
 
521
608
  /**
522
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
523
611
  * Use this method instead of "this.shadowRoot" to read the Shadow DOM, if ever necessary
612
+ *
524
613
  * @public
525
614
  */
526
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
+
527
621
  if (!this.shadowRoot || this.shadowRoot.children.length === 0) {
528
622
  return;
529
623
  }
530
624
 
531
- return this.shadowRoot.children.length === 1
532
- ? 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];
533
631
  }
534
632
 
535
633
  /**
@@ -545,6 +643,16 @@ class UI5Element extends HTMLElement {
545
643
  }
546
644
  }
547
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
+
548
656
  /**
549
657
  * Set the focus to the element, returned by "getFocusDomRef()" (marked by "data-sap-focus-ref")
550
658
  * @public
@@ -565,37 +673,47 @@ class UI5Element extends HTMLElement {
565
673
  * @param name - name of the event
566
674
  * @param data - additional data for the event
567
675
  * @param cancelable - true, if the user can call preventDefault on the event object
676
+ * @param bubbles - true, if the event bubbles
568
677
  * @returns {boolean} false, if the event was cancelled (preventDefault called), true otherwise
569
678
  */
570
- fireEvent(name, data, cancelable) {
571
- 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);
572
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) {
573
691
  const noConflictEvent = new CustomEvent(`ui5-${name}`, {
574
692
  detail: data,
575
693
  composed: false,
576
- bubbles: true,
694
+ bubbles,
577
695
  cancelable,
578
696
  });
579
697
 
580
- // This will be false if the compat event is prevented
581
- compatEventResult = this.dispatchEvent(noConflictEvent);
698
+ // This will be false if the no-conflict event is prevented
699
+ const noConflictEventResult = this.dispatchEvent(noConflictEvent);
582
700
 
583
701
  if (skipOriginalEvent(name)) {
584
- return compatEventResult;
702
+ return noConflictEventResult;
585
703
  }
586
704
 
587
- const customEvent = new CustomEvent(name, {
705
+ const normalEvent = new CustomEvent(name, {
588
706
  detail: data,
589
707
  composed: false,
590
- bubbles: true,
708
+ bubbles,
591
709
  cancelable,
592
710
  });
593
711
 
594
712
  // This will be false if the normal event is prevented
595
- const normalEventResult = this.dispatchEvent(customEvent);
713
+ const normalEventResult = this.dispatchEvent(normalEvent);
596
714
 
597
715
  // Return false if any of the two events was prevented (its result was false).
598
- return normalEventResult && compatEventResult;
716
+ return normalEventResult && noConflictEventResult;
599
717
  }
600
718
 
601
719
  /**
@@ -604,24 +722,19 @@ class UI5Element extends HTMLElement {
604
722
  * @public
605
723
  */
606
724
  getSlottedNodes(slotName) {
607
- const reducer = (acc, curr) => {
608
- if (curr.localName !== "slot") {
609
- return acc.concat([curr]);
610
- }
611
- return acc.concat(curr.assignedNodes({ flatten: true }).filter(item => item instanceof HTMLElement));
612
- };
613
-
614
- return this[slotName].reduce(reducer, []);
725
+ return getSlottedElementsList(this[slotName]);
615
726
  }
616
727
 
617
- get isCompact() {
618
- return getComputedStyle(this).getPropertyValue(GLOBAL_CONTENT_DENSITY_CSS_VAR) === "compact";
619
- }
620
-
621
- updateStaticAreaItemContentDensity() {
622
- if (this.staticAreaItem) {
623
- this.staticAreaItem._updateContentDensity(this.isCompact);
624
- }
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);
625
738
  }
626
739
 
627
740
  /**
@@ -641,26 +754,6 @@ class UI5Element extends HTMLElement {
641
754
  return this.getMetadata().getAttributesList();
642
755
  }
643
756
 
644
- /**
645
- * @private
646
- */
647
- static _getSlotName(child) {
648
- // Text nodes can only go to the default slot
649
- if (!(child instanceof HTMLElement)) {
650
- return "default";
651
- }
652
-
653
- // Discover the slot based on the real slot name (f.e. footer => footer, or content-32 => content)
654
- const slot = child.getAttribute("slot");
655
- if (slot) {
656
- const match = slot.match(/^(.+?)-\d+$/);
657
- return match ? match[1] : slot;
658
- }
659
-
660
- // Use default slot as a fallback
661
- return "default";
662
- }
663
-
664
757
  /**
665
758
  * @private
666
759
  */
@@ -668,70 +761,30 @@ class UI5Element extends HTMLElement {
668
761
  return !!this.template;
669
762
  }
670
763
 
671
- _shouldUpdateFragment() {
672
- return this.constructor._needsStaticArea() && this.staticAreaItem.isRendered();
673
- }
674
-
675
764
  /**
676
765
  * @private
677
766
  */
678
767
  static _needsStaticArea() {
679
- return typeof this.staticAreaTemplate === "function";
768
+ return !!this.staticAreaTemplate;
680
769
  }
681
770
 
682
771
  /**
683
772
  * @public
684
773
  */
685
774
  getStaticAreaItemDomRef() {
686
- return this.staticAreaItem.getDomRef();
687
- }
688
-
689
- /**
690
- * @private
691
- */
692
- static _getDefaultState() {
693
- if (this._defaultState) {
694
- return this._defaultState;
775
+ if (!this.constructor._needsStaticArea()) {
776
+ throw new Error("This component does not use the static area");
695
777
  }
696
778
 
697
- const MetadataClass = this.getMetadata();
698
- const defaultState = {};
699
- const slotsAreManaged = MetadataClass.slotsAreManaged();
700
-
701
- // Initialize properties
702
- const props = MetadataClass.getProperties();
703
- for (const propName in props) { // eslint-disable-line
704
- const propType = props[propName].type;
705
- const propDefaultValue = props[propName].defaultValue;
706
-
707
- if (propType === Boolean) {
708
- defaultState[propName] = false;
709
-
710
- if (propDefaultValue !== undefined) {
711
- console.warn("The 'defaultValue' metadata key is ignored for all booleans properties, they would be initialized with 'false' by default"); // eslint-disable-line
712
- }
713
- } else if (props[propName].multiple) {
714
- defaultState[propName] = [];
715
- } else if (propType === Object) {
716
- defaultState[propName] = "defaultValue" in props[propName] ? props[propName].defaultValue : {};
717
- } else if (propType === String) {
718
- defaultState[propName] = "defaultValue" in props[propName] ? props[propName].defaultValue : "";
719
- } else {
720
- defaultState[propName] = propDefaultValue;
721
- }
779
+ if (!this.staticAreaItem) {
780
+ this.staticAreaItem = StaticAreaItem.createInstance();
781
+ this.staticAreaItem.setOwnerElement(this);
722
782
  }
723
-
724
- // Initialize slots
725
- if (slotsAreManaged) {
726
- const slots = MetadataClass.getSlots();
727
- for (const [slotName, slotData] of Object.entries(slots)) { // eslint-disable-line
728
- const propertyName = slotData.propertyName || slotName;
729
- defaultState[propertyName] = [];
730
- }
783
+ if (!this.staticAreaItem.parentElement) {
784
+ getSingletonElementInstance("ui5-static-area").appendChild(this.staticAreaItem);
731
785
  }
732
786
 
733
- this._defaultState = defaultState;
734
- return defaultState;
787
+ return this.staticAreaItem.getDomRef();
735
788
  }
736
789
 
737
790
  /**
@@ -745,7 +798,7 @@ class UI5Element extends HTMLElement {
745
798
  const properties = this.getMetadata().getProperties();
746
799
  for (const [prop, propData] of Object.entries(properties)) { // eslint-disable-line
747
800
  if (!isValidPropertyName(prop)) {
748
- 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 */
749
802
  }
750
803
 
751
804
  if (propData.type === Boolean && propData.defaultValue) {
@@ -783,14 +836,27 @@ class UI5Element extends HTMLElement {
783
836
  }
784
837
  },
785
838
  set(value) {
839
+ let isDifferent;
786
840
  value = this.constructor.getMetadata().constructor.validatePropertyValue(value, propData);
787
841
 
788
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
+ }
789
850
 
790
- if (oldState !== value) {
851
+ if (isDifferent) {
791
852
  this._state[prop] = value;
792
- this._invalidate(prop, value);
793
- 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);
794
860
  }
795
861
  },
796
862
  });
@@ -801,7 +867,7 @@ class UI5Element extends HTMLElement {
801
867
  const slots = this.getMetadata().getSlots();
802
868
  for (const [slotName, slotData] of Object.entries(slots)) { // eslint-disable-line
803
869
  if (!isValidPropertyName(slotName)) {
804
- 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 */
805
871
  }
806
872
 
807
873
  const propertyName = slotData.propertyName || slotName;
@@ -813,7 +879,7 @@ class UI5Element extends HTMLElement {
813
879
  return [];
814
880
  },
815
881
  set() {
816
- 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...)");
817
883
  },
818
884
  });
819
885
  }
@@ -825,7 +891,7 @@ class UI5Element extends HTMLElement {
825
891
  * @protected
826
892
  */
827
893
  static get metadata() {
828
- return metadata;
894
+ return {};
829
895
  }
830
896
 
831
897
  /**
@@ -844,6 +910,50 @@ class UI5Element extends HTMLElement {
844
910
  return "";
845
911
  }
846
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
+
847
957
  /**
848
958
  * Registers a UI5 Web Component in the browser window object
849
959
  * @public
@@ -852,21 +962,31 @@ class UI5Element extends HTMLElement {
852
962
  static async define() {
853
963
  await boot();
854
964
 
855
- if (this.onDefine) {
856
- await this.onDefine();
857
- }
965
+ await Promise.all([
966
+ this.whenDependenciesDefined(),
967
+ this.onDefine(),
968
+ ]);
858
969
 
859
970
  const tag = this.getMetadata().getTag();
971
+ const altTag = this.getMetadata().getAltTag();
860
972
 
861
973
  const definedLocally = isTagRegistered(tag);
862
974
  const definedGlobally = customElements.get(tag);
863
975
 
864
976
  if (definedGlobally && !definedLocally) {
865
- console.warn(`Skipping definition of tag ${tag}, because it was already defined by another instance of ui5-webcomponents.`); // eslint-disable-line
977
+ recordTagRegistrationFailure(tag);
866
978
  } else if (!definedGlobally) {
867
979
  this._generateAccessors();
868
980
  registerTag(tag);
869
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
+ }
870
990
  }
871
991
  return this;
872
992
  }