@ui5/webcomponents-base 0.0.0-934b4df24 → 0.0.0-9c26e8e4f

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