@ui5/webcomponents-base 0.0.0-0fcda382e → 0.0.0-1929d08ff

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 (342) 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 -6
  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 +426 -301
  29. package/dist/UI5ElementMetadata.js +173 -19
  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 -12
  152. package/src/AssetRegistry.js +8 -6
  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 +426 -301
  176. package/src/UI5ElementMetadata.js +173 -19
  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/webcomponentsjs/LICENSE.md +0 -19
  289. package/dist/webcomponentsjs/README.md +0 -229
  290. package/dist/webcomponentsjs/bundles/webcomponents-ce.js +0 -63
  291. package/dist/webcomponentsjs/bundles/webcomponents-ce.js.map +0 -1
  292. package/dist/webcomponentsjs/bundles/webcomponents-sd-ce-pf.js +0 -297
  293. package/dist/webcomponentsjs/bundles/webcomponents-sd-ce-pf.js.map +0 -1
  294. package/dist/webcomponentsjs/bundles/webcomponents-sd-ce.js +0 -208
  295. package/dist/webcomponentsjs/bundles/webcomponents-sd-ce.js.map +0 -1
  296. package/dist/webcomponentsjs/bundles/webcomponents-sd.js +0 -166
  297. package/dist/webcomponentsjs/bundles/webcomponents-sd.js.map +0 -1
  298. package/dist/webcomponentsjs/custom-elements-es5-adapter.js +0 -15
  299. package/dist/webcomponentsjs/package.json +0 -46
  300. package/dist/webcomponentsjs/src/entrypoints/custom-elements-es5-adapter-index.js +0 -16
  301. package/dist/webcomponentsjs/src/entrypoints/webcomponents-bundle-index.js +0 -53
  302. package/dist/webcomponentsjs/src/entrypoints/webcomponents-ce-index.js +0 -17
  303. package/dist/webcomponentsjs/src/entrypoints/webcomponents-sd-ce-index.js +0 -19
  304. package/dist/webcomponentsjs/src/entrypoints/webcomponents-sd-ce-pf-index.js +0 -28
  305. package/dist/webcomponentsjs/src/entrypoints/webcomponents-sd-index.js +0 -18
  306. package/dist/webcomponentsjs/webcomponents-bundle.js +0 -298
  307. package/dist/webcomponentsjs/webcomponents-bundle.js.map +0 -1
  308. package/dist/webcomponentsjs/webcomponents-loader.js +0 -185
  309. package/src/SVGIconRegistry.js +0 -60
  310. package/src/boot.js +0 -32
  311. package/src/compatibility/DOMObserver.js +0 -62
  312. package/src/compatibility/patchNodeValue.js +0 -24
  313. package/src/compatibility/whenPolyfillLoaded.js +0 -26
  314. package/src/delegate/CustomResize.js +0 -78
  315. package/src/delegate/NativeResize.js +0 -44
  316. package/src/features/browsersupport/Edge.js +0 -6
  317. package/src/features/browsersupport/IE11.js +0 -41
  318. package/src/features/browsersupport/IE11WithWebComponentsPolyfill.js +0 -5
  319. package/src/renderer/ifDefined.js +0 -21
  320. package/src/theming/CSSVarsPonyfill.js +0 -24
  321. package/src/theming/adaptCSSForIE.js +0 -90
  322. package/src/theming/createComponentStyleTag.js +0 -49
  323. package/src/theming/createThemePropertiesStyleTag.js +0 -20
  324. package/src/thirdparty/Array.from.js +0 -16
  325. package/src/thirdparty/Array.prototype.fill.js +0 -44
  326. package/src/thirdparty/Array.prototype.find.js +0 -46
  327. package/src/thirdparty/Array.prototype.includes.js +0 -51
  328. package/src/thirdparty/Element.prototype.closest.js +0 -11
  329. package/src/thirdparty/Element.prototype.matches.js +0 -6
  330. package/src/thirdparty/Map.prototype.keys.js +0 -9
  331. package/src/thirdparty/Number.isInteger.js +0 -5
  332. package/src/thirdparty/Number.isNaN.js +0 -1
  333. package/src/thirdparty/Number.parseInt.js +0 -1
  334. package/src/thirdparty/Object.assign.js +0 -31
  335. package/src/thirdparty/Object.entries.js +0 -11
  336. package/src/thirdparty/Symbol.js +0 -2
  337. package/src/thirdparty/WeakSet.js +0 -27
  338. package/src/thirdparty/es6-string-methods.js +0 -182
  339. package/src/thirdparty/events-polyfills.js +0 -89
  340. package/src/thirdparty/fetch.js +0 -1
  341. package/src/thirdparty/template.js +0 -600
  342. 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,30 @@ 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
+ if (this.constructor.getMetadata().supportsF6FastNavigation()) {
105
+ this.setAttribute("data-sap-ui-fastnavgroup", "true");
106
+ }
107
+
98
108
  const slotsAreManaged = this.constructor.getMetadata().slotsAreManaged();
99
109
 
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
- }
110
+ this._inDOM = true;
107
111
 
108
- if (!this.shadowRoot) { // Workaround for Firefox74 bug
109
- await Promise.resolve();
110
- }
112
+ if (slotsAreManaged) {
113
+ // always register the observer before yielding control to the main thread (await)
114
+ this._startObservingDOMChildren();
115
+ await this._processChildren();
116
+ }
111
117
 
112
- await RenderScheduler.renderImmediately(this);
113
- this._domRefReadyPromise._deferredResolve();
114
- if (typeof this.onEnterDOM === "function") {
115
- this.onEnterDOM();
116
- }
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();
117
127
  }
118
128
  }
119
129
 
@@ -122,23 +132,26 @@ class UI5Element extends HTMLElement {
122
132
  * @private
123
133
  */
124
134
  disconnectedCallback() {
125
- const needsShadowDOM = this.constructor._needsShadowDOM();
126
- const needsStaticArea = this.constructor._needsStaticArea();
127
135
  const slotsAreManaged = this.constructor.getMetadata().slotsAreManaged();
128
136
 
129
- if (needsShadowDOM) {
130
- if (slotsAreManaged) {
131
- this._stopObservingDOMChildren();
132
- }
137
+ this._inDOM = false;
133
138
 
139
+ if (slotsAreManaged) {
140
+ this._stopObservingDOMChildren();
141
+ }
142
+
143
+ if (this._fullyConnected) {
134
144
  if (typeof this.onExitDOM === "function") {
135
145
  this.onExitDOM();
136
146
  }
147
+ this._fullyConnected = false;
137
148
  }
138
149
 
139
- if (needsStaticArea) {
140
- this.staticAreaItem._removeFragmentFromStaticArea();
150
+ if (this.staticAreaItem && this.staticAreaItem.parentElement) {
151
+ this.staticAreaItem.parentElement.removeChild(this.staticAreaItem);
141
152
  }
153
+
154
+ cancelRender(this);
142
155
  }
143
156
 
144
157
  /**
@@ -154,16 +167,16 @@ class UI5Element extends HTMLElement {
154
167
  const mutationObserverOptions = {
155
168
  childList: true,
156
169
  subtree: canSlotText,
157
- characterData: true,
170
+ characterData: canSlotText,
158
171
  };
159
- DOMObserver.observeDOMNode(this, this._processChildren.bind(this), mutationObserverOptions);
172
+ observeDOMNode(this, this._processChildren.bind(this), mutationObserverOptions);
160
173
  }
161
174
 
162
175
  /**
163
176
  * @private
164
177
  */
165
178
  _stopObservingDOMChildren() {
166
- DOMObserver.unobserveDOMNode(this);
179
+ unobserveDOMNode(this);
167
180
  }
168
181
 
169
182
  /**
@@ -185,8 +198,14 @@ class UI5Element extends HTMLElement {
185
198
  const canSlotText = this.constructor.getMetadata().canSlotText();
186
199
  const domChildren = Array.from(canSlotText ? this.childNodes : this.children);
187
200
 
188
- // 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
189
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]]);
190
209
  this._clearSlot(slotName, slotData);
191
210
  }
192
211
 
@@ -195,7 +214,7 @@ class UI5Element extends HTMLElement {
195
214
 
196
215
  const allChildrenUpgraded = domChildren.map(async (child, idx) => {
197
216
  // Determine the type of the child (mainly by the slot attribute)
198
- const slotName = this.constructor._getSlotName(child);
217
+ const slotName = getSlotName(child);
199
218
  const slotData = slotsMap[slotName];
200
219
 
201
220
  // Check if the slotName is supported
@@ -233,8 +252,15 @@ class UI5Element extends HTMLElement {
233
252
 
234
253
  child = this.constructor.getMetadata().constructor.validateSlotValue(child, slotData);
235
254
 
236
- if (child.isUI5Element && slotData.listenFor) {
237
- 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));
259
+ }
260
+
261
+ // Listen for the slotchange event if the child is a slot itself
262
+ if (isSlot(child)) {
263
+ this._attachSlotChange(child, slotName);
238
264
  }
239
265
 
240
266
  const propertyName = slotData.propertyName || slotName;
@@ -250,10 +276,33 @@ class UI5Element extends HTMLElement {
250
276
 
251
277
  // Distribute the child in the _state object, keeping the Light DOM order,
252
278
  // 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);
279
+ slottedChildrenMap.forEach((children, propertyName) => {
280
+ this._state[propertyName] = children.sort((a, b) => a.idx - b.idx).map(_ => _.child);
255
281
  });
256
- 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
+ }
257
306
  }
258
307
 
259
308
  /**
@@ -262,20 +311,62 @@ class UI5Element extends HTMLElement {
262
311
  */
263
312
  _clearSlot(slotName, slotData) {
264
313
  const propertyName = slotData.propertyName || slotName;
265
-
266
- let children = this._state[propertyName];
267
- if (!Array.isArray(children)) {
268
- children = [children];
269
- }
314
+ const children = this._state[propertyName];
270
315
 
271
316
  children.forEach(child => {
272
317
  if (child && child.isUI5Element) {
273
- this._detachChildPropertyUpdated(child);
318
+ const method = (child.detachInvalidate || child._detachChange).bind(child);
319
+ method(this._getChildChangeListener(slotName));
320
+ }
321
+
322
+ if (isSlot(child)) {
323
+ this._detachSlotChange(child, slotName);
274
324
  }
275
325
  });
276
326
 
277
327
  this._state[propertyName] = [];
278
- 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
+ });
279
370
  }
280
371
 
281
372
  /**
@@ -290,9 +381,8 @@ class UI5Element extends HTMLElement {
290
381
  const propertyTypeClass = properties[nameInCamelCase].type;
291
382
  if (propertyTypeClass === Boolean) {
292
383
  newValue = newValue !== null;
293
- }
294
- if (propertyTypeClass === Integer) {
295
- newValue = parseInt(newValue);
384
+ } else if (isDescendantOf(propertyTypeClass, DataType)) {
385
+ newValue = propertyTypeClass.attributeToProperty(newValue);
296
386
  }
297
387
  this[nameInCamelCase] = newValue;
298
388
  }
@@ -305,22 +395,24 @@ class UI5Element extends HTMLElement {
305
395
  if (!this.constructor.getMetadata().hasAttribute(name)) {
306
396
  return;
307
397
  }
308
-
309
- if (typeof newValue === "object") {
310
- return;
311
- }
312
-
398
+ const properties = this.constructor.getMetadata().getProperties();
399
+ const propertyTypeClass = properties[name].type;
313
400
  const attrName = camelToKebabCase(name);
314
401
  const attrValue = this.getAttribute(attrName);
315
- if (typeof newValue === "boolean") {
402
+
403
+ if (propertyTypeClass === Boolean) {
316
404
  if (newValue === true && attrValue === null) {
317
405
  this.setAttribute(attrName, "");
318
406
  } else if (newValue === false && attrValue !== null) {
319
407
  this.removeAttribute(attrName);
320
408
  }
321
- } else if (attrValue !== newValue) {
322
- this.setAttribute(attrName, newValue);
323
- }
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
324
416
  }
325
417
 
326
418
  /**
@@ -346,101 +438,96 @@ class UI5Element extends HTMLElement {
346
438
  * @private
347
439
  */
348
440
  _initializeState() {
349
- const defaultState = this.constructor._getDefaultState();
350
- this._state = Object.assign({}, defaultState);
441
+ this._state = { ...this.constructor.getMetadata().getInitialState() };
351
442
  }
352
443
 
353
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}
354
449
  * @private
355
450
  */
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 : [];
451
+ _getChildChangeListener(slotName) {
452
+ if (!this._childChangeListeners.has(slotName)) {
453
+ this._childChangeListeners.set(slotName, this._onChildChange.bind(this, slotName));
369
454
  }
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;
455
+ return this._childChangeListeners.get(slotName);
377
456
  }
378
457
 
379
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}
380
463
  * @private
381
464
  */
382
- _detachChildPropertyUpdated(child) {
383
- child.removeEventListener("_propertyChange", this._invalidateParentOnPropertyUpdate);
384
- child._firePropertyChange = false;
465
+ _getSlotChangeListener(slotName) {
466
+ if (!this._slotChangeListeners.has(slotName)) {
467
+ this._slotChangeListeners.set(slotName, this._onSlotChange.bind(this, slotName));
468
+ }
469
+ return this._slotChangeListeners.get(slotName);
385
470
  }
386
471
 
387
472
  /**
388
473
  * @private
389
474
  */
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
- }
475
+ _attachSlotChange(child, slotName) {
476
+ child.addEventListener("slotchange", this._getSlotChangeListener(slotName));
400
477
  }
401
478
 
402
479
  /**
403
480
  * @private
404
481
  */
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
- }
482
+ _detachSlotChange(child, slotName) {
483
+ child.removeEventListener("slotchange", this._getSlotChangeListener(slotName));
423
484
  }
424
485
 
425
486
  /**
426
- * Asynchronously re-renders an already rendered web component
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
427
490
  * @private
428
491
  */
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
- }
492
+ _onSlotChange(slotName) {
493
+ _invalidate.call(this, {
494
+ type: "slot",
495
+ name: slotName,
496
+ reason: "slotchange",
497
+ });
440
498
  }
441
499
 
442
500
  /**
443
- * Do not call this method directly, only intended to be called by RenderScheduler.js
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
526
+ */
527
+ onInvalidation(changeInfo) {}
528
+
529
+ /**
530
+ * Do not call this method directly, only intended to be called by js
444
531
  * @protected
445
532
  */
446
533
  _render() {
@@ -459,15 +546,37 @@ class UI5Element extends HTMLElement {
459
546
  }
460
547
 
461
548
  // resume normal invalidation handling
462
- delete this._suppressInvalidation;
549
+ this._suppressInvalidation = false;
463
550
 
464
551
  // 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();
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
+ }
468
567
 
469
- if (this._shouldUpdateFragment()) {
470
- 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();
471
580
  }
472
581
 
473
582
  // Safari requires that children get the slot attribute only after the slot tags have been rendered in the shadow DOM
@@ -481,23 +590,6 @@ class UI5Element extends HTMLElement {
481
590
  }
482
591
  }
483
592
 
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
593
  /**
502
594
  * @private
503
595
  */
@@ -520,16 +612,27 @@ class UI5Element extends HTMLElement {
520
612
 
521
613
  /**
522
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
523
616
  * Use this method instead of "this.shadowRoot" to read the Shadow DOM, if ever necessary
617
+ *
524
618
  * @public
525
619
  */
526
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
+
527
626
  if (!this.shadowRoot || this.shadowRoot.children.length === 0) {
528
627
  return;
529
628
  }
530
629
 
531
- return this.shadowRoot.children.length === 1
532
- ? 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];
533
636
  }
534
637
 
535
638
  /**
@@ -545,6 +648,16 @@ class UI5Element extends HTMLElement {
545
648
  }
546
649
  }
547
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
+
548
661
  /**
549
662
  * Set the focus to the element, returned by "getFocusDomRef()" (marked by "data-sap-focus-ref")
550
663
  * @public
@@ -565,37 +678,47 @@ class UI5Element extends HTMLElement {
565
678
  * @param name - name of the event
566
679
  * @param data - additional data for the event
567
680
  * @param cancelable - true, if the user can call preventDefault on the event object
681
+ * @param bubbles - true, if the event bubbles
568
682
  * @returns {boolean} false, if the event was cancelled (preventDefault called), true otherwise
569
683
  */
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"
684
+ fireEvent(name, data, cancelable = false, bubbles = true) {
685
+ const eventResult = this._fireEvent(name, data, cancelable, bubbles);
686
+ const camelCaseEventName = kebabToCamelCase(name);
687
+
688
+ if (camelCaseEventName !== name) {
689
+ return eventResult && this._fireEvent(camelCaseEventName, data, cancelable);
690
+ }
572
691
 
692
+ return eventResult;
693
+ }
694
+
695
+ _fireEvent(name, data, cancelable = false, bubbles = true) {
573
696
  const noConflictEvent = new CustomEvent(`ui5-${name}`, {
574
697
  detail: data,
575
698
  composed: false,
576
- bubbles: true,
699
+ bubbles,
577
700
  cancelable,
578
701
  });
579
702
 
580
- // This will be false if the compat event is prevented
581
- compatEventResult = this.dispatchEvent(noConflictEvent);
703
+ // This will be false if the no-conflict event is prevented
704
+ const noConflictEventResult = this.dispatchEvent(noConflictEvent);
582
705
 
583
706
  if (skipOriginalEvent(name)) {
584
- return compatEventResult;
707
+ return noConflictEventResult;
585
708
  }
586
709
 
587
- const customEvent = new CustomEvent(name, {
710
+ const normalEvent = new CustomEvent(name, {
588
711
  detail: data,
589
712
  composed: false,
590
- bubbles: true,
713
+ bubbles,
591
714
  cancelable,
592
715
  });
593
716
 
594
717
  // This will be false if the normal event is prevented
595
- const normalEventResult = this.dispatchEvent(customEvent);
718
+ const normalEventResult = this.dispatchEvent(normalEvent);
596
719
 
597
720
  // Return false if any of the two events was prevented (its result was false).
598
- return normalEventResult && compatEventResult;
721
+ return normalEventResult && noConflictEventResult;
599
722
  }
600
723
 
601
724
  /**
@@ -604,24 +727,19 @@ class UI5Element extends HTMLElement {
604
727
  * @public
605
728
  */
606
729
  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, []);
615
- }
616
-
617
- get isCompact() {
618
- return getComputedStyle(this).getPropertyValue(GLOBAL_CONTENT_DENSITY_CSS_VAR) === "compact";
730
+ return getSlottedElementsList(this[slotName]);
619
731
  }
620
732
 
621
- updateStaticAreaItemContentDensity() {
622
- if (this.staticAreaItem) {
623
- this.staticAreaItem._updateContentDensity(this.isCompact);
624
- }
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);
625
743
  }
626
744
 
627
745
  /**
@@ -641,26 +759,6 @@ class UI5Element extends HTMLElement {
641
759
  return this.getMetadata().getAttributesList();
642
760
  }
643
761
 
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
762
  /**
665
763
  * @private
666
764
  */
@@ -668,70 +766,30 @@ class UI5Element extends HTMLElement {
668
766
  return !!this.template;
669
767
  }
670
768
 
671
- _shouldUpdateFragment() {
672
- return this.constructor._needsStaticArea() && this.staticAreaItem.isRendered();
673
- }
674
-
675
769
  /**
676
770
  * @private
677
771
  */
678
772
  static _needsStaticArea() {
679
- return typeof this.staticAreaTemplate === "function";
773
+ return !!this.staticAreaTemplate;
680
774
  }
681
775
 
682
776
  /**
683
777
  * @public
684
778
  */
685
779
  getStaticAreaItemDomRef() {
686
- return this.staticAreaItem.getDomRef();
687
- }
688
-
689
- /**
690
- * @private
691
- */
692
- static _getDefaultState() {
693
- if (this._defaultState) {
694
- return this._defaultState;
780
+ if (!this.constructor._needsStaticArea()) {
781
+ throw new Error("This component does not use the static area");
695
782
  }
696
783
 
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
- }
784
+ if (!this.staticAreaItem) {
785
+ this.staticAreaItem = StaticAreaItem.createInstance();
786
+ this.staticAreaItem.setOwnerElement(this);
722
787
  }
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
- }
788
+ if (!this.staticAreaItem.parentElement) {
789
+ getSingletonElementInstance("ui5-static-area").appendChild(this.staticAreaItem);
731
790
  }
732
791
 
733
- this._defaultState = defaultState;
734
- return defaultState;
792
+ return this.staticAreaItem.getDomRef();
735
793
  }
736
794
 
737
795
  /**
@@ -745,7 +803,7 @@ class UI5Element extends HTMLElement {
745
803
  const properties = this.getMetadata().getProperties();
746
804
  for (const [prop, propData] of Object.entries(properties)) { // eslint-disable-line
747
805
  if (!isValidPropertyName(prop)) {
748
- 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 */
749
807
  }
750
808
 
751
809
  if (propData.type === Boolean && propData.defaultValue) {
@@ -783,14 +841,27 @@ class UI5Element extends HTMLElement {
783
841
  }
784
842
  },
785
843
  set(value) {
844
+ let isDifferent;
786
845
  value = this.constructor.getMetadata().constructor.validatePropertyValue(value, propData);
787
846
 
788
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
+ }
789
855
 
790
- if (oldState !== value) {
856
+ if (isDifferent) {
791
857
  this._state[prop] = value;
792
- this._invalidate(prop, value);
793
- 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);
794
865
  }
795
866
  },
796
867
  });
@@ -801,7 +872,7 @@ class UI5Element extends HTMLElement {
801
872
  const slots = this.getMetadata().getSlots();
802
873
  for (const [slotName, slotData] of Object.entries(slots)) { // eslint-disable-line
803
874
  if (!isValidPropertyName(slotName)) {
804
- 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 */
805
876
  }
806
877
 
807
878
  const propertyName = slotData.propertyName || slotName;
@@ -813,7 +884,7 @@ class UI5Element extends HTMLElement {
813
884
  return [];
814
885
  },
815
886
  set() {
816
- 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...)");
817
888
  },
818
889
  });
819
890
  }
@@ -825,7 +896,7 @@ class UI5Element extends HTMLElement {
825
896
  * @protected
826
897
  */
827
898
  static get metadata() {
828
- return metadata;
899
+ return {};
829
900
  }
830
901
 
831
902
  /**
@@ -844,6 +915,50 @@ class UI5Element extends HTMLElement {
844
915
  return "";
845
916
  }
846
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
+
847
962
  /**
848
963
  * Registers a UI5 Web Component in the browser window object
849
964
  * @public
@@ -852,21 +967,31 @@ class UI5Element extends HTMLElement {
852
967
  static async define() {
853
968
  await boot();
854
969
 
855
- if (this.onDefine) {
856
- await this.onDefine();
857
- }
970
+ await Promise.all([
971
+ this.whenDependenciesDefined(),
972
+ this.onDefine(),
973
+ ]);
858
974
 
859
975
  const tag = this.getMetadata().getTag();
976
+ const altTag = this.getMetadata().getAltTag();
860
977
 
861
978
  const definedLocally = isTagRegistered(tag);
862
979
  const definedGlobally = customElements.get(tag);
863
980
 
864
981
  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
982
+ recordTagRegistrationFailure(tag);
866
983
  } else if (!definedGlobally) {
867
984
  this._generateAccessors();
868
985
  registerTag(tag);
869
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
+ }
870
995
  }
871
996
  return this;
872
997
  }