@ui5/webcomponents-base 0.0.0-c6d8872f3 → 0.0.0-cf069fb29

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 (351) hide show
  1. package/.eslintignore +5 -0
  2. package/CHANGELOG.md +293 -0
  3. package/README.md +26 -2
  4. package/bundle.esm.js +17 -7
  5. package/dist/AssetRegistry.js +8 -6
  6. package/dist/Boot.js +61 -0
  7. package/dist/CSP.js +59 -0
  8. package/dist/CustomElementsRegistry.js +95 -0
  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 +45 -9
  16. package/dist/Keys.js +112 -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 -145
  23. package/dist/Runtimes.js +107 -0
  24. package/dist/StaticArea.js +1 -39
  25. package/dist/StaticAreaItem.js +61 -50
  26. package/dist/SystemCSSVars.js +10 -0
  27. package/dist/Theming.js +2 -5
  28. package/dist/UI5Element.js +448 -297
  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 +100 -68
  33. package/dist/asset-registries/Themes.js +35 -32
  34. package/dist/asset-registries/i18n.js +76 -29
  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 +101 -19
  45. package/dist/features/F6Navigation.js +106 -0
  46. package/dist/features/OpenUI5Support.js +56 -7
  47. package/dist/generated/AssetParameters.js +13 -0
  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 +59 -8
  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/locale/nextFallbackLocale.js +6 -4
  62. package/dist/locale/normalizeLocale.js +7 -1
  63. package/dist/renderer/LitRenderer.js +26 -7
  64. package/dist/renderer/directives/style-map.js +72 -0
  65. package/dist/renderer/executeTemplate.js +17 -0
  66. package/dist/resources/bundle.esm.js +20 -106
  67. package/dist/resources/bundle.esm.js.map +1 -1
  68. package/dist/sap/base/Log.js +241 -0
  69. package/dist/sap/base/assert.js +8 -0
  70. package/dist/sap/base/security/URLListValidator.js +148 -0
  71. package/dist/sap/base/security/encodeCSS.js +14 -0
  72. package/dist/sap/base/security/encodeXML.js +23 -0
  73. package/dist/sap/base/security/sanitizeHTML.js +16 -0
  74. package/dist/sap/base/strings/toHex.js +8 -0
  75. package/dist/sap/base/util/now.js +7 -0
  76. package/dist/sap/ui/thirdparty/caja-html-sanitizer.js +3585 -0
  77. package/dist/test-resources/assets/Themes.js +10 -6
  78. package/dist/test-resources/elements/Parent.js +6 -2
  79. package/dist/test-resources/elements/WithStaticArea.js +77 -0
  80. package/dist/test-resources/pages/AllTestElements.html +5 -3
  81. package/dist/test-resources/pages/Configuration.html +0 -2
  82. package/dist/test-resources/pages/ConfigurationScript.html +1 -3
  83. package/dist/test-resources/pages/assets/messagebundle_de.properties +1 -0
  84. package/dist/test-resources/pages/assets/messagebundle_en.properties +1 -0
  85. package/dist/test-resources/pages/assets/messagebundle_es.properties +1 -0
  86. package/dist/test-resources/pages/assets/messagebundle_fr.properties +1 -0
  87. package/dist/test-resources/pages/i18n.html +33 -0
  88. package/dist/test-resources/specs/ConfigurationChange.spec.js +11 -9
  89. package/dist/test-resources/specs/ConfigurationScript.spec.js +25 -23
  90. package/dist/test-resources/specs/ConfigurationURL.spec.js +65 -17
  91. package/dist/test-resources/specs/CustomTheme.spec.js +9 -7
  92. package/dist/test-resources/specs/EventProvider.spec.js +63 -0
  93. package/dist/test-resources/specs/StaticArea.spec.js +78 -0
  94. package/dist/test-resources/specs/Theming.spec.js +12 -10
  95. package/dist/test-resources/specs/UI5ElementInvalidation.js +54 -70
  96. package/dist/test-resources/specs/UI5ElementLifecycle.js +19 -14
  97. package/dist/test-resources/specs/UI5ElementListenForChildPropChanges.spec.js +25 -51
  98. package/dist/test-resources/specs/UI5ElementMetadataExt.js +9 -7
  99. package/dist/test-resources/specs/UI5ElementPropertyValidation.js +7 -5
  100. package/dist/test-resources/specs/UI5ElementPropsAndAttrs.spec.js +37 -35
  101. package/dist/test-resources/specs/UI5ElementShadowDOM.js +10 -8
  102. package/dist/test-resources/specs/UI5ElementSlots.js +10 -8
  103. package/dist/theming/CustomStyle.js +47 -6
  104. package/dist/theming/ThemeLoaded.js +22 -0
  105. package/dist/theming/applyTheme.js +69 -9
  106. package/dist/theming/getConstructableStyle.js +15 -10
  107. package/dist/theming/getEffectiveLinksHrefs.js +20 -0
  108. package/dist/theming/getEffectiveStyle.js +23 -8
  109. package/dist/theming/getStylesString.js +15 -0
  110. package/dist/theming/getThemeDesignerTheme.js +87 -0
  111. package/dist/theming/preloadLinks.js +23 -0
  112. package/dist/types/CSSColor.js +9 -0
  113. package/dist/types/CalendarType.js +1 -1
  114. package/dist/types/DataType.js +13 -1
  115. package/dist/types/Float.js +14 -0
  116. package/dist/types/Integer.js +4 -0
  117. package/dist/types/InvisibleMessageMode.js +30 -0
  118. package/dist/types/ItemNavigationBehavior.js +2 -7
  119. package/dist/types/NavigationMode.js +1 -0
  120. package/dist/types/PopupState.js +1 -1
  121. package/dist/types/ValueState.js +1 -1
  122. package/dist/updateShadowRoot.js +30 -0
  123. package/dist/util/AriaLabelHelper.js +41 -0
  124. package/dist/util/Caret.js +45 -0
  125. package/dist/util/ColorConversion.js +370 -0
  126. package/dist/util/FocusableElements.js +30 -11
  127. package/dist/util/HTMLSanitizer.js +7 -0
  128. package/dist/util/InvisibleMessage.js +58 -0
  129. package/dist/util/PopupUtils.js +93 -0
  130. package/dist/util/SlotsHelper.js +43 -0
  131. package/dist/util/TabbableElements.js +5 -1
  132. package/dist/util/arraysAreEqual.js +15 -0
  133. package/dist/util/clamp.js +12 -0
  134. package/dist/util/createLinkInHead.js +19 -0
  135. package/dist/util/debounce.js +17 -0
  136. package/dist/util/detectNavigatorLanguage.js +3 -1
  137. package/dist/util/escapeRegex.js +10 -0
  138. package/dist/util/findNodeOwner.js +35 -0
  139. package/dist/util/generateHighlightedMarkup.js +42 -0
  140. package/dist/util/getActiveElement.js +11 -0
  141. package/dist/util/getClassCopy.js +10 -0
  142. package/dist/util/getEffectiveContentDensity.js +5 -0
  143. package/dist/util/getFileExtension.js +21 -0
  144. package/dist/util/getSingletonElementInstance.js +13 -0
  145. package/dist/util/isElementInView.js +15 -0
  146. package/dist/util/isNodeHidden.js +1 -5
  147. package/dist/util/isNodeTabbable.js +4 -4
  148. package/dist/util/isValidPropertyName.js +11 -2
  149. package/dist/util/setToArray.js +10 -0
  150. package/hash.txt +1 -0
  151. package/index.js +1 -1
  152. package/lib/generate-asset-parameters/index.js +21 -0
  153. package/lib/generate-styles/index.js +18 -0
  154. package/lib/generate-version-info/index.js +27 -0
  155. package/package-scripts.js +43 -17
  156. package/package.json +19 -12
  157. package/src/AssetRegistry.js +8 -6
  158. package/src/Boot.js +61 -0
  159. package/src/CSP.js +59 -0
  160. package/src/CustomElementsRegistry.js +95 -0
  161. package/src/CustomElementsScope.js +108 -0
  162. package/src/DOMObserver.js +65 -0
  163. package/src/Device.js +69 -776
  164. package/src/EventProvider.js +38 -26
  165. package/src/FeaturesRegistry.js +4 -1
  166. package/src/FontFace.js +19 -62
  167. package/src/InitialConfiguration.js +45 -9
  168. package/src/Keys.js +112 -2
  169. package/src/ManagedStyles.js +83 -0
  170. package/src/MediaRange.js +109 -0
  171. package/src/PropertiesFileFormat.js +95 -0
  172. package/src/Render.js +175 -0
  173. package/src/RenderQueue.js +41 -17
  174. package/src/RenderScheduler.js +24 -145
  175. package/src/Runtimes.js +107 -0
  176. package/src/StaticArea.js +1 -39
  177. package/src/StaticAreaItem.js +61 -50
  178. package/src/SystemCSSVars.js +10 -0
  179. package/src/Theming.js +2 -5
  180. package/src/UI5Element.js +448 -297
  181. package/src/UI5ElementMetadata.js +173 -19
  182. package/src/asset-registries/Icons.js +129 -14
  183. package/src/asset-registries/Illustrations.js +30 -0
  184. package/src/asset-registries/LocaleData.js +100 -68
  185. package/src/asset-registries/Themes.js +35 -32
  186. package/src/asset-registries/i18n.js +76 -29
  187. package/src/assets-meta/IconCollectionsAlias.js +18 -0
  188. package/src/config/AnimationMode.js +11 -1
  189. package/src/config/Language.js +58 -2
  190. package/src/config/Theme.js +14 -0
  191. package/src/css/FontFace.css +45 -0
  192. package/src/css/OverrideFontFace.css +32 -0
  193. package/src/css/SystemCSSVars.css +17 -0
  194. package/src/delegate/ItemNavigation.js +244 -171
  195. package/src/delegate/ResizeHandler.js +78 -38
  196. package/src/delegate/ScrollEnablement.js +101 -19
  197. package/src/features/F6Navigation.js +106 -0
  198. package/src/features/OpenUI5Support.js +56 -7
  199. package/src/getSharedResource.js +30 -0
  200. package/src/i18nBundle.js +59 -8
  201. package/src/isLegacyBrowser.js +3 -0
  202. package/src/locale/RTLAwareRegistry.js +14 -0
  203. package/src/locale/applyDirection.js +17 -0
  204. package/src/locale/directionChange.js +32 -0
  205. package/src/locale/getEffectiveDir.js +28 -0
  206. package/src/locale/getLocale.js +12 -2
  207. package/src/locale/languageChange.js +22 -0
  208. package/src/locale/nextFallbackLocale.js +6 -4
  209. package/src/locale/normalizeLocale.js +7 -1
  210. package/src/renderer/LitRenderer.js +26 -7
  211. package/src/renderer/directives/style-map.js +72 -0
  212. package/src/renderer/executeTemplate.js +17 -0
  213. package/src/theming/CustomStyle.js +47 -6
  214. package/src/theming/ThemeLoaded.js +22 -0
  215. package/src/theming/applyTheme.js +69 -9
  216. package/src/theming/getConstructableStyle.js +15 -10
  217. package/src/theming/getEffectiveLinksHrefs.js +20 -0
  218. package/src/theming/getEffectiveStyle.js +23 -8
  219. package/src/theming/getStylesString.js +15 -0
  220. package/src/theming/getThemeDesignerTheme.js +87 -0
  221. package/src/theming/preloadLinks.js +23 -0
  222. package/src/types/CSSColor.js +9 -0
  223. package/src/types/CalendarType.js +1 -1
  224. package/src/types/DataType.js +13 -1
  225. package/src/types/Float.js +14 -0
  226. package/src/types/Integer.js +4 -0
  227. package/src/types/InvisibleMessageMode.js +30 -0
  228. package/src/types/ItemNavigationBehavior.js +2 -7
  229. package/src/types/NavigationMode.js +1 -0
  230. package/src/types/PopupState.js +1 -1
  231. package/src/types/ValueState.js +1 -1
  232. package/src/updateShadowRoot.js +30 -0
  233. package/src/util/AriaLabelHelper.js +41 -0
  234. package/src/util/Caret.js +45 -0
  235. package/src/util/ColorConversion.js +370 -0
  236. package/src/util/FocusableElements.js +30 -11
  237. package/src/util/HTMLSanitizer.js +7 -0
  238. package/src/util/InvisibleMessage.js +58 -0
  239. package/src/util/PopupUtils.js +93 -0
  240. package/src/util/SlotsHelper.js +43 -0
  241. package/src/util/TabbableElements.js +5 -1
  242. package/src/util/arraysAreEqual.js +15 -0
  243. package/src/util/clamp.js +12 -0
  244. package/src/util/createLinkInHead.js +19 -0
  245. package/src/util/debounce.js +17 -0
  246. package/src/util/detectNavigatorLanguage.js +3 -1
  247. package/src/util/escapeRegex.js +10 -0
  248. package/src/util/findNodeOwner.js +35 -0
  249. package/src/util/generateHighlightedMarkup.js +42 -0
  250. package/src/util/getActiveElement.js +11 -0
  251. package/src/util/getClassCopy.js +10 -0
  252. package/src/util/getEffectiveContentDensity.js +5 -0
  253. package/src/util/getFileExtension.js +21 -0
  254. package/src/util/getSingletonElementInstance.js +13 -0
  255. package/src/util/isElementInView.js +15 -0
  256. package/src/util/isNodeHidden.js +1 -5
  257. package/src/util/isNodeTabbable.js +4 -4
  258. package/src/util/isValidPropertyName.js +11 -2
  259. package/src/util/setToArray.js +10 -0
  260. package/used-modules.txt +10 -0
  261. package/bundle.es5.js +0 -28
  262. package/dist/SVGIconRegistry.js +0 -60
  263. package/dist/boot.js +0 -32
  264. package/dist/compatibility/DOMObserver.js +0 -62
  265. package/dist/compatibility/patchNodeValue.js +0 -24
  266. package/dist/compatibility/whenPolyfillLoaded.js +0 -26
  267. package/dist/delegate/CustomResize.js +0 -78
  268. package/dist/delegate/NativeResize.js +0 -44
  269. package/dist/features/browsersupport/Edge.js +0 -6
  270. package/dist/features/browsersupport/IE11.js +0 -41
  271. package/dist/renderer/ifDefined.js +0 -21
  272. package/dist/resources/bundle.es5.js +0 -212
  273. package/dist/resources/bundle.es5.js.map +0 -1
  274. package/dist/test-resources/dev-helpers/ExternalThemePresent.js +0 -3
  275. package/dist/theming/CSSVarsPonyfill.js +0 -25
  276. package/dist/theming/ExternalThemePresent.js +0 -12
  277. package/dist/theming/adaptCSSForIE.js +0 -90
  278. package/dist/theming/createComponentStyleTag.js +0 -32
  279. package/dist/theming/createThemePropertiesStyleTag.js +0 -27
  280. package/dist/thirdparty/Array.from.js +0 -16
  281. package/dist/thirdparty/Array.prototype.fill.js +0 -44
  282. package/dist/thirdparty/Array.prototype.find.js +0 -46
  283. package/dist/thirdparty/Array.prototype.includes.js +0 -51
  284. package/dist/thirdparty/Element.prototype.closest.js +0 -11
  285. package/dist/thirdparty/Element.prototype.matches.js +0 -6
  286. package/dist/thirdparty/Map.prototype.keys.js +0 -9
  287. package/dist/thirdparty/Number.isInteger.js +0 -5
  288. package/dist/thirdparty/Number.isNaN.js +0 -1
  289. package/dist/thirdparty/Number.parseInt.js +0 -1
  290. package/dist/thirdparty/Object.assign.js +0 -31
  291. package/dist/thirdparty/Object.entries.js +0 -11
  292. package/dist/thirdparty/Symbol.js +0 -2
  293. package/dist/thirdparty/WeakSet.js +0 -27
  294. package/dist/thirdparty/es6-string-methods.js +0 -182
  295. package/dist/thirdparty/events-polyfills.js +0 -89
  296. package/dist/thirdparty/fetch.js +0 -1
  297. package/dist/thirdparty/template.js +0 -600
  298. package/dist/webcomponentsjs/LICENSE.md +0 -19
  299. package/dist/webcomponentsjs/README.md +0 -229
  300. package/dist/webcomponentsjs/bundles/webcomponents-ce.js +0 -63
  301. package/dist/webcomponentsjs/bundles/webcomponents-ce.js.map +0 -1
  302. package/dist/webcomponentsjs/bundles/webcomponents-sd-ce-pf.js +0 -297
  303. package/dist/webcomponentsjs/bundles/webcomponents-sd-ce-pf.js.map +0 -1
  304. package/dist/webcomponentsjs/bundles/webcomponents-sd-ce.js +0 -208
  305. package/dist/webcomponentsjs/bundles/webcomponents-sd-ce.js.map +0 -1
  306. package/dist/webcomponentsjs/bundles/webcomponents-sd.js +0 -166
  307. package/dist/webcomponentsjs/bundles/webcomponents-sd.js.map +0 -1
  308. package/dist/webcomponentsjs/custom-elements-es5-adapter.js +0 -15
  309. package/dist/webcomponentsjs/package.json +0 -46
  310. package/dist/webcomponentsjs/src/entrypoints/custom-elements-es5-adapter-index.js +0 -16
  311. package/dist/webcomponentsjs/src/entrypoints/webcomponents-bundle-index.js +0 -53
  312. package/dist/webcomponentsjs/src/entrypoints/webcomponents-ce-index.js +0 -17
  313. package/dist/webcomponentsjs/src/entrypoints/webcomponents-sd-ce-index.js +0 -19
  314. package/dist/webcomponentsjs/src/entrypoints/webcomponents-sd-ce-pf-index.js +0 -28
  315. package/dist/webcomponentsjs/src/entrypoints/webcomponents-sd-index.js +0 -18
  316. package/dist/webcomponentsjs/webcomponents-bundle.js +0 -298
  317. package/dist/webcomponentsjs/webcomponents-bundle.js.map +0 -1
  318. package/dist/webcomponentsjs/webcomponents-loader.js +0 -185
  319. package/src/SVGIconRegistry.js +0 -60
  320. package/src/boot.js +0 -32
  321. package/src/compatibility/DOMObserver.js +0 -62
  322. package/src/compatibility/patchNodeValue.js +0 -24
  323. package/src/compatibility/whenPolyfillLoaded.js +0 -26
  324. package/src/delegate/CustomResize.js +0 -78
  325. package/src/delegate/NativeResize.js +0 -44
  326. package/src/features/browsersupport/Edge.js +0 -6
  327. package/src/features/browsersupport/IE11.js +0 -41
  328. package/src/renderer/ifDefined.js +0 -21
  329. package/src/theming/CSSVarsPonyfill.js +0 -25
  330. package/src/theming/ExternalThemePresent.js +0 -12
  331. package/src/theming/adaptCSSForIE.js +0 -90
  332. package/src/theming/createComponentStyleTag.js +0 -32
  333. package/src/theming/createThemePropertiesStyleTag.js +0 -27
  334. package/src/thirdparty/Array.from.js +0 -16
  335. package/src/thirdparty/Array.prototype.fill.js +0 -44
  336. package/src/thirdparty/Array.prototype.find.js +0 -46
  337. package/src/thirdparty/Array.prototype.includes.js +0 -51
  338. package/src/thirdparty/Element.prototype.closest.js +0 -11
  339. package/src/thirdparty/Element.prototype.matches.js +0 -6
  340. package/src/thirdparty/Map.prototype.keys.js +0 -9
  341. package/src/thirdparty/Number.isInteger.js +0 -5
  342. package/src/thirdparty/Number.isNaN.js +0 -1
  343. package/src/thirdparty/Number.parseInt.js +0 -1
  344. package/src/thirdparty/Object.assign.js +0 -31
  345. package/src/thirdparty/Object.entries.js +0 -11
  346. package/src/thirdparty/Symbol.js +0 -2
  347. package/src/thirdparty/WeakSet.js +0 -27
  348. package/src/thirdparty/es6-string-methods.js +0 -182
  349. package/src/thirdparty/events-polyfills.js +0 -89
  350. package/src/thirdparty/fetch.js +0 -1
  351. package/src/thirdparty/template.js +0 -600
@@ -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 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";
7
11
  import { skipOriginalEvent } from "./config/NoConflict.js";
8
- import getConstructableStyle from "./theming/getConstructableStyle.js";
9
- import createComponentStyleTag from "./theming/createComponentStyleTag.js";
10
- import getEffectiveStyle from "./theming/getEffectiveStyle.js";
11
- import Integer from "./types/Integer.js";
12
+ import getEffectiveDir from "./locale/getEffectiveDir.js";
13
+ import DataType from "./types/DataType.js";
12
14
  import { kebabToCamelCase, camelToKebabCase } from "./util/StringHelper.js";
13
15
  import isValidPropertyName from "./util/isValidPropertyName.js";
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";
14
22
 
15
- const metadata = {
16
- events: {
17
- _propertyChange: {},
18
- },
19
- };
20
-
21
- const DefinitionsSet = new Set();
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,28 +100,30 @@ class UI5Element extends HTMLElement {
94
100
  * @private
95
101
  */
96
102
  async connectedCallback() {
97
- const needsShadowDOM = this.constructor._needsShadowDOM();
98
- const needsStaticArea = this.constructor._needsStaticArea();
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
- await RenderScheduler.renderImmediately(this);
110
- this._domRefReadyPromise._deferredResolve();
111
- if (typeof this.onEnterDOM === "function") {
112
- this.onEnterDOM();
113
- }
112
+ if (slotsAreManaged) {
113
+ // always register the observer before yielding control to the main thread (await)
114
+ this._startObservingDOMChildren();
115
+ await this._processChildren();
114
116
  }
115
117
 
116
- // Render Fragment if neccessary
117
- if (needsStaticArea) {
118
- this.staticAreaItem._updateFragment(this);
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();
119
127
  }
120
128
  }
121
129
 
@@ -124,23 +132,26 @@ class UI5Element extends HTMLElement {
124
132
  * @private
125
133
  */
126
134
  disconnectedCallback() {
127
- const needsShadowDOM = this.constructor._needsShadowDOM();
128
- const needsStaticArea = this.constructor._needsStaticArea();
129
135
  const slotsAreManaged = this.constructor.getMetadata().slotsAreManaged();
130
136
 
131
- if (needsShadowDOM) {
132
- if (slotsAreManaged) {
133
- this._stopObservingDOMChildren();
134
- }
137
+ this._inDOM = false;
135
138
 
139
+ if (slotsAreManaged) {
140
+ this._stopObservingDOMChildren();
141
+ }
142
+
143
+ if (this._fullyConnected) {
136
144
  if (typeof this.onExitDOM === "function") {
137
145
  this.onExitDOM();
138
146
  }
147
+ this._fullyConnected = false;
139
148
  }
140
149
 
141
- if (needsStaticArea) {
142
- this.staticAreaItem._removeFragmentFromStaticArea();
150
+ if (this.staticAreaItem && this.staticAreaItem.parentElement) {
151
+ this.staticAreaItem.parentElement.removeChild(this.staticAreaItem);
143
152
  }
153
+
154
+ cancelRender(this);
144
155
  }
145
156
 
146
157
  /**
@@ -156,16 +167,16 @@ class UI5Element extends HTMLElement {
156
167
  const mutationObserverOptions = {
157
168
  childList: true,
158
169
  subtree: canSlotText,
159
- characterData: true,
170
+ characterData: canSlotText,
160
171
  };
161
- DOMObserver.observeDOMNode(this, this._processChildren.bind(this), mutationObserverOptions);
172
+ observeDOMNode(this, this._processChildren.bind(this), mutationObserverOptions);
162
173
  }
163
174
 
164
175
  /**
165
176
  * @private
166
177
  */
167
178
  _stopObservingDOMChildren() {
168
- DOMObserver.unobserveDOMNode(this);
179
+ unobserveDOMNode(this);
169
180
  }
170
181
 
171
182
  /**
@@ -187,8 +198,14 @@ class UI5Element extends HTMLElement {
187
198
  const canSlotText = this.constructor.getMetadata().canSlotText();
188
199
  const domChildren = Array.from(canSlotText ? this.childNodes : this.children);
189
200
 
190
- // 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
191
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]]);
192
209
  this._clearSlot(slotName, slotData);
193
210
  }
194
211
 
@@ -197,7 +214,7 @@ class UI5Element extends HTMLElement {
197
214
 
198
215
  const allChildrenUpgraded = domChildren.map(async (child, idx) => {
199
216
  // Determine the type of the child (mainly by the slot attribute)
200
- const slotName = this.constructor._getSlotName(child);
217
+ const slotName = getSlotName(child);
201
218
  const slotData = slotsMap[slotName];
202
219
 
203
220
  // Check if the slotName is supported
@@ -235,8 +252,15 @@ class UI5Element extends HTMLElement {
235
252
 
236
253
  child = this.constructor.getMetadata().constructor.validateSlotValue(child, slotData);
237
254
 
238
- if (child.isUI5Element && slotData.listenFor) {
239
- 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);
240
264
  }
241
265
 
242
266
  const propertyName = slotData.propertyName || slotName;
@@ -252,10 +276,33 @@ class UI5Element extends HTMLElement {
252
276
 
253
277
  // Distribute the child in the _state object, keeping the Light DOM order,
254
278
  // not the order elements are defined.
255
- slottedChildrenMap.forEach((children, slot) => {
256
- 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);
257
281
  });
258
- 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
+ }
259
306
  }
260
307
 
261
308
  /**
@@ -264,20 +311,62 @@ class UI5Element extends HTMLElement {
264
311
  */
265
312
  _clearSlot(slotName, slotData) {
266
313
  const propertyName = slotData.propertyName || slotName;
267
-
268
- let children = this._state[propertyName];
269
- if (!Array.isArray(children)) {
270
- children = [children];
271
- }
314
+ const children = this._state[propertyName];
272
315
 
273
316
  children.forEach(child => {
274
317
  if (child && child.isUI5Element) {
275
- 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);
276
324
  }
277
325
  });
278
326
 
279
327
  this._state[propertyName] = [];
280
- 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
+ });
281
370
  }
282
371
 
283
372
  /**
@@ -292,9 +381,8 @@ class UI5Element extends HTMLElement {
292
381
  const propertyTypeClass = properties[nameInCamelCase].type;
293
382
  if (propertyTypeClass === Boolean) {
294
383
  newValue = newValue !== null;
295
- }
296
- if (propertyTypeClass === Integer) {
297
- newValue = parseInt(newValue);
384
+ } else if (isDescendantOf(propertyTypeClass, DataType)) {
385
+ newValue = propertyTypeClass.attributeToProperty(newValue);
298
386
  }
299
387
  this[nameInCamelCase] = newValue;
300
388
  }
@@ -307,22 +395,24 @@ class UI5Element extends HTMLElement {
307
395
  if (!this.constructor.getMetadata().hasAttribute(name)) {
308
396
  return;
309
397
  }
310
-
311
- if (typeof newValue === "object") {
312
- return;
313
- }
314
-
398
+ const properties = this.constructor.getMetadata().getProperties();
399
+ const propertyTypeClass = properties[name].type;
315
400
  const attrName = camelToKebabCase(name);
316
401
  const attrValue = this.getAttribute(attrName);
317
- if (typeof newValue === "boolean") {
402
+
403
+ if (propertyTypeClass === Boolean) {
318
404
  if (newValue === true && attrValue === null) {
319
405
  this.setAttribute(attrName, "");
320
406
  } else if (newValue === false && attrValue !== null) {
321
407
  this.removeAttribute(attrName);
322
408
  }
323
- } else if (attrValue !== newValue) {
324
- this.setAttribute(attrName, newValue);
325
- }
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
326
416
  }
327
417
 
328
418
  /**
@@ -348,101 +438,96 @@ class UI5Element extends HTMLElement {
348
438
  * @private
349
439
  */
350
440
  _initializeState() {
351
- const defaultState = this.constructor._getDefaultState();
352
- this._state = Object.assign({}, defaultState);
441
+ this._state = { ...this.constructor.getMetadata().getInitialState() };
353
442
  }
354
443
 
355
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}
356
449
  * @private
357
450
  */
358
- _attachChildPropertyUpdated(child, listenFor) {
359
- const childMetadata = child.constructor.getMetadata(),
360
- slotName = this.constructor._getSlotName(child), // all slotted children have the same configuration
361
- childProperties = childMetadata.getProperties();
362
-
363
- let observedProps = [],
364
- notObservedProps = [];
365
-
366
- if (Array.isArray(listenFor)) {
367
- observedProps = listenFor;
368
- } else {
369
- observedProps = Array.isArray(listenFor.props) ? listenFor.props : Object.keys(childProperties);
370
- notObservedProps = Array.isArray(listenFor.exclude) ? listenFor.exclude : [];
371
- }
372
-
373
- if (!this._monitoredChildProps.has(slotName)) {
374
- this._monitoredChildProps.set(slotName, { observedProps, notObservedProps });
451
+ _getChildChangeListener(slotName) {
452
+ if (!this._childChangeListeners.has(slotName)) {
453
+ this._childChangeListeners.set(slotName, this._onChildChange.bind(this, slotName));
375
454
  }
376
-
377
- child.addEventListener("_propertyChange", this._invalidateParentOnPropertyUpdate);
378
- child._firePropertyChange = true;
455
+ return this._childChangeListeners.get(slotName);
379
456
  }
380
457
 
381
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}
382
463
  * @private
383
464
  */
384
- _detachChildPropertyUpdated(child) {
385
- child.removeEventListener("_propertyChange", this._invalidateParentOnPropertyUpdate);
386
- 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);
387
470
  }
388
471
 
389
472
  /**
390
473
  * @private
391
474
  */
392
- _propertyChange(name, value) {
393
- this._updateAttribute(name, value);
394
-
395
- if (this._firePropertyChange) {
396
- this.dispatchEvent(new CustomEvent("_propertyChange", {
397
- detail: { name, newValue: value },
398
- composed: false,
399
- bubbles: true,
400
- }));
401
- }
475
+ _attachSlotChange(child, slotName) {
476
+ child.addEventListener("slotchange", this._getSlotChangeListener(slotName));
402
477
  }
403
478
 
404
479
  /**
405
480
  * @private
406
481
  */
407
- _invalidateParentOnPropertyUpdate(prop) {
408
- // The web component to be invalidated
409
- const parentNode = this.parentNode;
410
- if (!parentNode) {
411
- return;
412
- }
413
-
414
- const slotName = parentNode.constructor._getSlotName(this);
415
- const propsMetadata = parentNode._monitoredChildProps.get(slotName);
416
-
417
- if (!propsMetadata) {
418
- return;
419
- }
420
- const { observedProps, notObservedProps } = propsMetadata;
421
-
422
- if (observedProps.includes(prop.detail.name) && !notObservedProps.includes(prop.detail.name)) {
423
- parentNode._invalidate("_parent_", this);
424
- }
482
+ _detachSlotChange(child, slotName) {
483
+ child.removeEventListener("slotchange", this._getSlotChangeListener(slotName));
425
484
  }
426
485
 
427
486
  /**
428
- * 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
429
490
  * @private
430
491
  */
431
- _invalidate() {
432
- if (!this._upToDate) {
433
- // console.log("already invalidated", this, ...arguments);
434
- return;
435
- }
436
-
437
- if (this.getDomRef() && !this._suppressInvalidation) {
438
- this._upToDate = false;
439
- // console.log("INVAL", this, ...arguments);
440
- RenderScheduler.renderDeferred(this);
441
- }
492
+ _onSlotChange(slotName) {
493
+ _invalidate.call(this, {
494
+ type: "slot",
495
+ name: slotName,
496
+ reason: "slotchange",
497
+ });
442
498
  }
443
499
 
444
500
  /**
445
- * 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
446
531
  * @protected
447
532
  */
448
533
  _render() {
@@ -461,15 +546,37 @@ class UI5Element extends HTMLElement {
461
546
  }
462
547
 
463
548
  // resume normal invalidation handling
464
- delete this._suppressInvalidation;
549
+ this._suppressInvalidation = false;
465
550
 
466
551
  // Update the shadow root with the render result
467
- // console.log(this.getDomRef() ? "RE-RENDER" : "FIRST RENDER", this);
468
- this._upToDate = true;
469
- 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
+ }
470
567
 
471
- if (this.constructor._needsStaticArea()) {
472
- 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();
473
580
  }
474
581
 
475
582
  // Safari requires that children get the slot attribute only after the slot tags have been rendered in the shadow DOM
@@ -483,19 +590,6 @@ class UI5Element extends HTMLElement {
483
590
  }
484
591
  }
485
592
 
486
- /**
487
- * @private
488
- */
489
- _updateShadowRoot() {
490
- let styleToPrepend;
491
- const renderResult = this.constructor.template(this);
492
-
493
- if (!document.adoptedStyleSheets && !window.ShadyDOM) {
494
- styleToPrepend = getEffectiveStyle(this.constructor);
495
- }
496
- this.constructor.render(renderResult, this.shadowRoot, styleToPrepend, { eventContext: this });
497
- }
498
-
499
593
  /**
500
594
  * @private
501
595
  */
@@ -518,16 +612,27 @@ class UI5Element extends HTMLElement {
518
612
 
519
613
  /**
520
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
521
616
  * Use this method instead of "this.shadowRoot" to read the Shadow DOM, if ever necessary
617
+ *
522
618
  * @public
523
619
  */
524
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
+
525
626
  if (!this.shadowRoot || this.shadowRoot.children.length === 0) {
526
627
  return;
527
628
  }
528
629
 
529
- return this.shadowRoot.children.length === 1
530
- ? 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];
531
636
  }
532
637
 
533
638
  /**
@@ -543,6 +648,16 @@ class UI5Element extends HTMLElement {
543
648
  }
544
649
  }
545
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
+
546
661
  /**
547
662
  * Set the focus to the element, returned by "getFocusDomRef()" (marked by "data-sap-focus-ref")
548
663
  * @public
@@ -563,37 +678,47 @@ class UI5Element extends HTMLElement {
563
678
  * @param name - name of the event
564
679
  * @param data - additional data for the event
565
680
  * @param cancelable - true, if the user can call preventDefault on the event object
681
+ * @param bubbles - true, if the event bubbles
566
682
  * @returns {boolean} false, if the event was cancelled (preventDefault called), true otherwise
567
683
  */
568
- fireEvent(name, data, cancelable) {
569
- 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
+ }
691
+
692
+ return eventResult;
693
+ }
570
694
 
695
+ _fireEvent(name, data, cancelable = false, bubbles = true) {
571
696
  const noConflictEvent = new CustomEvent(`ui5-${name}`, {
572
697
  detail: data,
573
698
  composed: false,
574
- bubbles: true,
699
+ bubbles,
575
700
  cancelable,
576
701
  });
577
702
 
578
- // This will be false if the compat event is prevented
579
- compatEventResult = this.dispatchEvent(noConflictEvent);
703
+ // This will be false if the no-conflict event is prevented
704
+ const noConflictEventResult = this.dispatchEvent(noConflictEvent);
580
705
 
581
706
  if (skipOriginalEvent(name)) {
582
- return compatEventResult;
707
+ return noConflictEventResult;
583
708
  }
584
709
 
585
- const customEvent = new CustomEvent(name, {
710
+ const normalEvent = new CustomEvent(name, {
586
711
  detail: data,
587
712
  composed: false,
588
- bubbles: true,
713
+ bubbles,
589
714
  cancelable,
590
715
  });
591
716
 
592
717
  // This will be false if the normal event is prevented
593
- const normalEventResult = this.dispatchEvent(customEvent);
718
+ const normalEventResult = this.dispatchEvent(normalEvent);
594
719
 
595
720
  // Return false if any of the two events was prevented (its result was false).
596
- return normalEventResult && compatEventResult;
721
+ return normalEventResult && noConflictEventResult;
597
722
  }
598
723
 
599
724
  /**
@@ -602,24 +727,19 @@ class UI5Element extends HTMLElement {
602
727
  * @public
603
728
  */
604
729
  getSlottedNodes(slotName) {
605
- const reducer = (acc, curr) => {
606
- if (curr.localName !== "slot") {
607
- return acc.concat([curr]);
608
- }
609
- return acc.concat(curr.assignedNodes({ flatten: true }).filter(item => item instanceof HTMLElement));
610
- };
611
-
612
- return this[slotName].reduce(reducer, []);
730
+ return getSlottedElementsList(this[slotName]);
613
731
  }
614
732
 
615
- get isCompact() {
616
- return getComputedStyle(this).getPropertyValue(GLOBAL_CONTENT_DENSITY_CSS_VAR) === "compact";
617
- }
618
-
619
- updateStaticAreaItemContentDensity() {
620
- if (this.staticAreaItem) {
621
- this.staticAreaItem._updateContentDensity(this.isCompact);
622
- }
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);
623
743
  }
624
744
 
625
745
  /**
@@ -639,26 +759,6 @@ class UI5Element extends HTMLElement {
639
759
  return this.getMetadata().getAttributesList();
640
760
  }
641
761
 
642
- /**
643
- * @private
644
- */
645
- static _getSlotName(child) {
646
- // Text nodes can only go to the default slot
647
- if (!(child instanceof HTMLElement)) {
648
- return "default";
649
- }
650
-
651
- // Discover the slot based on the real slot name (f.e. footer => footer, or content-32 => content)
652
- const slot = child.getAttribute("slot");
653
- if (slot) {
654
- const match = slot.match(/^(.+?)-\d+$/);
655
- return match ? match[1] : slot;
656
- }
657
-
658
- // Use default slot as a fallback
659
- return "default";
660
- }
661
-
662
762
  /**
663
763
  * @private
664
764
  */
@@ -670,62 +770,26 @@ class UI5Element extends HTMLElement {
670
770
  * @private
671
771
  */
672
772
  static _needsStaticArea() {
673
- return typeof this.staticAreaTemplate === "function";
773
+ return !!this.staticAreaTemplate;
674
774
  }
675
775
 
676
776
  /**
677
777
  * @public
678
778
  */
679
779
  getStaticAreaItemDomRef() {
680
- return this.staticAreaItem.getDomRef();
681
- }
682
-
683
- /**
684
- * @private
685
- */
686
- static _getDefaultState() {
687
- if (this._defaultState) {
688
- return this._defaultState;
780
+ if (!this.constructor._needsStaticArea()) {
781
+ throw new Error("This component does not use the static area");
689
782
  }
690
783
 
691
- const MetadataClass = this.getMetadata();
692
- const defaultState = {};
693
- const slotsAreManaged = MetadataClass.slotsAreManaged();
694
-
695
- // Initialize properties
696
- const props = MetadataClass.getProperties();
697
- for (const propName in props) { // eslint-disable-line
698
- const propType = props[propName].type;
699
- const propDefaultValue = props[propName].defaultValue;
700
-
701
- if (propType === Boolean) {
702
- defaultState[propName] = false;
703
-
704
- if (propDefaultValue !== undefined) {
705
- console.warn("The 'defaultValue' metadata key is ignored for all booleans properties, they would be initialized with 'false' by default"); // eslint-disable-line
706
- }
707
- } else if (props[propName].multiple) {
708
- defaultState[propName] = [];
709
- } else if (propType === Object) {
710
- defaultState[propName] = "defaultValue" in props[propName] ? props[propName].defaultValue : {};
711
- } else if (propType === String) {
712
- defaultState[propName] = "defaultValue" in props[propName] ? props[propName].defaultValue : "";
713
- } else {
714
- defaultState[propName] = propDefaultValue;
715
- }
784
+ if (!this.staticAreaItem) {
785
+ this.staticAreaItem = StaticAreaItem.createInstance();
786
+ this.staticAreaItem.setOwnerElement(this);
716
787
  }
717
-
718
- // Initialize slots
719
- if (slotsAreManaged) {
720
- const slots = MetadataClass.getSlots();
721
- for (const [slotName, slotData] of Object.entries(slots)) { // eslint-disable-line
722
- const propertyName = slotData.propertyName || slotName;
723
- defaultState[propertyName] = [];
724
- }
788
+ if (!this.staticAreaItem.parentElement) {
789
+ getSingletonElementInstance("ui5-static-area").appendChild(this.staticAreaItem);
725
790
  }
726
791
 
727
- this._defaultState = defaultState;
728
- return defaultState;
792
+ return this.staticAreaItem.getDomRef();
729
793
  }
730
794
 
731
795
  /**
@@ -739,13 +803,25 @@ class UI5Element extends HTMLElement {
739
803
  const properties = this.getMetadata().getProperties();
740
804
  for (const [prop, propData] of Object.entries(properties)) { // eslint-disable-line
741
805
  if (!isValidPropertyName(prop)) {
742
- 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 */
743
807
  }
744
808
 
745
- if (propData.type === "boolean" && propData.defaultValue) {
809
+ if (propData.type === Boolean && propData.defaultValue) {
746
810
  throw new Error(`Cannot set a default value for property "${prop}". All booleans are false by default.`);
747
811
  }
748
812
 
813
+ if (propData.type === Array) {
814
+ throw new Error(`Wrong type for property "${prop}". Properties cannot be of type Array - use "multiple: true" and set "type" to the single value type, such as "String", "Object", etc...`);
815
+ }
816
+
817
+ if (propData.type === Object && propData.defaultValue) {
818
+ throw new Error(`Cannot set a default value for property "${prop}". All properties of type "Object" are empty objects by default.`);
819
+ }
820
+
821
+ if (propData.multiple && propData.defaultValue) {
822
+ throw new Error(`Cannot set a default value for property "${prop}". All multiple properties are empty arrays by default.`);
823
+ }
824
+
749
825
  Object.defineProperty(proto, prop, {
750
826
  get() {
751
827
  if (this._state[prop] !== undefined) {
@@ -765,14 +841,27 @@ class UI5Element extends HTMLElement {
765
841
  }
766
842
  },
767
843
  set(value) {
844
+ let isDifferent;
768
845
  value = this.constructor.getMetadata().constructor.validatePropertyValue(value, propData);
769
846
 
770
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
+ }
771
855
 
772
- if (oldState !== value) {
856
+ if (isDifferent) {
773
857
  this._state[prop] = value;
774
- this._invalidate(prop, value);
775
- 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);
776
865
  }
777
866
  },
778
867
  });
@@ -783,7 +872,7 @@ class UI5Element extends HTMLElement {
783
872
  const slots = this.getMetadata().getSlots();
784
873
  for (const [slotName, slotData] of Object.entries(slots)) { // eslint-disable-line
785
874
  if (!isValidPropertyName(slotName)) {
786
- 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 */
787
876
  }
788
877
 
789
878
  const propertyName = slotData.propertyName || slotName;
@@ -795,7 +884,7 @@ class UI5Element extends HTMLElement {
795
884
  return [];
796
885
  },
797
886
  set() {
798
- 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...)");
799
888
  },
800
889
  });
801
890
  }
@@ -807,7 +896,7 @@ class UI5Element extends HTMLElement {
807
896
  * @protected
808
897
  */
809
898
  static get metadata() {
810
- return metadata;
899
+ return {};
811
900
  }
812
901
 
813
902
  /**
@@ -818,6 +907,58 @@ class UI5Element extends HTMLElement {
818
907
  return "";
819
908
  }
820
909
 
910
+ /**
911
+ * Returns the Static Area CSS for this UI5 Web Component Class
912
+ * @protected
913
+ */
914
+ static get staticAreaStyles() {
915
+ return "";
916
+ }
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
+
821
962
  /**
822
963
  * Registers a UI5 Web Component in the browser window object
823
964
  * @public
@@ -826,21 +967,31 @@ class UI5Element extends HTMLElement {
826
967
  static async define() {
827
968
  await boot();
828
969
 
829
- if (this.onDefine) {
830
- await this.onDefine();
831
- }
970
+ await Promise.all([
971
+ this.whenDependenciesDefined(),
972
+ this.onDefine(),
973
+ ]);
832
974
 
833
975
  const tag = this.getMetadata().getTag();
976
+ const altTag = this.getMetadata().getAltTag();
834
977
 
835
- const definedLocally = DefinitionsSet.has(tag);
978
+ const definedLocally = isTagRegistered(tag);
836
979
  const definedGlobally = customElements.get(tag);
837
980
 
838
981
  if (definedGlobally && !definedLocally) {
839
- console.warn(`Skipping definition of tag ${tag}, because it was already defined by another instance of ui5-webcomponents.`); // eslint-disable-line
982
+ recordTagRegistrationFailure(tag);
840
983
  } else if (!definedGlobally) {
841
984
  this._generateAccessors();
842
- DefinitionsSet.add(tag);
985
+ registerTag(tag);
843
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
+ }
844
995
  }
845
996
  return this;
846
997
  }