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

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