@ui5/webcomponents-base 0.0.0-78035f8e7 → 0.0.0-7e7d9ea6f

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 (355) hide show
  1. package/.eslintignore +3 -0
  2. package/CHANGELOG.md +362 -0
  3. package/README.md +53 -7
  4. package/bundle.esm.js +20 -20
  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 +60 -4
  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 +30 -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 +125 -0
  18. package/dist/ManagedStyles.js +83 -0
  19. package/dist/MediaRange.js +109 -0
  20. package/dist/{util/parseProperties.js → PropertiesFileFormat.js} +6 -0
  21. package/dist/Render.js +175 -0
  22. package/dist/RenderQueue.js +41 -17
  23. package/dist/RenderScheduler.js +24 -158
  24. package/dist/Runtimes.js +107 -0
  25. package/dist/StaticArea.js +1 -29
  26. package/dist/StaticAreaItem.js +58 -62
  27. package/dist/SystemCSSVars.js +4 -25
  28. package/dist/Theming.js +2 -1
  29. package/dist/UI5Element.js +386 -354
  30. package/dist/UI5ElementMetadata.js +187 -22
  31. package/dist/asset-registries/Icons.js +117 -13
  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 +64 -47
  36. package/dist/assets/bundle.esm.691f93f5.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 +33 -6
  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 +244 -170
  47. package/dist/delegate/ResizeHandler.js +78 -37
  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/i18nBundle.js +38 -4
  59. package/dist/locale/applyDirection.js +6 -4
  60. package/dist/locale/directionChange.js +32 -0
  61. package/dist/locale/getEffectiveDir.js +28 -0
  62. package/dist/locale/getLocale.js +12 -2
  63. package/dist/locale/languageChange.js +1 -1
  64. package/dist/renderer/LitRenderer.js +43 -7
  65. package/dist/renderer/directives/style-map.js +72 -0
  66. package/dist/renderer/executeTemplate.js +34 -0
  67. package/dist/sap/base/Log.js +241 -0
  68. package/dist/sap/base/assert.js +8 -0
  69. package/dist/sap/base/security/URLListValidator.js +148 -0
  70. package/dist/sap/base/security/encodeCSS.js +14 -0
  71. package/dist/sap/base/security/encodeXML.js +23 -0
  72. package/dist/sap/base/security/sanitizeHTML.js +16 -0
  73. package/dist/sap/base/strings/toHex.js +8 -0
  74. package/dist/sap/base/util/now.js +7 -0
  75. package/dist/sap/ui/thirdparty/caja-html-sanitizer.js +3585 -0
  76. package/dist/theming/CustomStyle.js +47 -6
  77. package/dist/theming/ThemeLoaded.js +22 -0
  78. package/dist/theming/applyTheme.js +13 -15
  79. package/dist/theming/getConstructableStyle.js +15 -10
  80. package/dist/theming/getEffectiveLinksHrefs.js +27 -0
  81. package/dist/theming/getEffectiveStyle.js +32 -8
  82. package/dist/theming/getStylesString.js +15 -0
  83. package/dist/theming/getThemeDesignerTheme.js +25 -5
  84. package/dist/theming/preloadLinks.js +23 -0
  85. package/dist/types/CSSColor.js +9 -0
  86. package/dist/types/CalendarType.js +1 -1
  87. package/dist/types/DataType.js +13 -1
  88. package/dist/types/Float.js +4 -0
  89. package/dist/types/Integer.js +4 -0
  90. package/dist/types/InvisibleMessageMode.js +30 -0
  91. package/dist/types/ItemNavigationBehavior.js +2 -7
  92. package/dist/types/PopupState.js +1 -1
  93. package/dist/types/ValueState.js +1 -1
  94. package/dist/updateShadowRoot.js +29 -0
  95. package/dist/util/AriaLabelHelper.js +41 -0
  96. package/dist/util/Caret.js +32 -0
  97. package/dist/util/ColorConversion.js +398 -0
  98. package/dist/util/FocusableElements.js +22 -9
  99. package/dist/util/HTMLSanitizer.js +7 -0
  100. package/dist/util/InvisibleMessage.js +69 -0
  101. package/dist/util/PopupUtils.js +93 -0
  102. package/dist/util/SlotsHelper.js +43 -0
  103. package/dist/util/TabbableElements.js +5 -1
  104. package/dist/util/arraysAreEqual.js +15 -0
  105. package/dist/util/clamp.js +12 -0
  106. package/dist/util/createLinkInHead.js +19 -0
  107. package/dist/util/debounce.js +17 -0
  108. package/dist/util/escapeRegex.js +10 -0
  109. package/dist/util/generateHighlightedMarkup.js +42 -0
  110. package/dist/util/getActiveElement.js +11 -0
  111. package/dist/util/getClassCopy.js +10 -0
  112. package/dist/util/getEffectiveContentDensity.js +5 -0
  113. package/dist/util/getNormalizedTarget.js +16 -0
  114. package/dist/util/isDefaultSlotProvided.js +11 -0
  115. package/dist/util/isElementInView.js +15 -0
  116. package/dist/util/isNodeHidden.js +1 -1
  117. package/dist/util/isNodeTabbable.js +4 -4
  118. package/dist/util/isValidPropertyName.js +6 -5
  119. package/hash.txt +1 -0
  120. package/index.js +1 -1
  121. package/lib/generate-asset-parameters/index.js +8 -5
  122. package/lib/generate-styles/index.js +26 -0
  123. package/lib/generate-version-info/index.js +32 -0
  124. package/package-scripts.js +37 -28
  125. package/package.json +20 -13
  126. package/src/AssetRegistry.js +8 -7
  127. package/src/Boot.js +59 -0
  128. package/src/CSP.js +59 -0
  129. package/src/CustomElementsRegistry.js +60 -4
  130. package/src/CustomElementsScope.js +30 -0
  131. package/src/CustomElementsScopeUtils.js +108 -0
  132. package/src/DOMObserver.js +65 -0
  133. package/src/Device.js +71 -773
  134. package/src/EventProvider.js +30 -26
  135. package/src/FeaturesRegistry.js +4 -1
  136. package/src/FontFace.js +19 -62
  137. package/src/InitialConfiguration.js +43 -8
  138. package/src/Keys.js +125 -0
  139. package/src/ManagedStyles.js +83 -0
  140. package/src/MediaRange.js +109 -0
  141. package/src/{util/parseProperties.js → PropertiesFileFormat.js} +6 -0
  142. package/src/Render.js +175 -0
  143. package/src/RenderQueue.js +41 -17
  144. package/src/RenderScheduler.js +24 -158
  145. package/src/Runtimes.js +107 -0
  146. package/src/StaticArea.js +1 -29
  147. package/src/StaticAreaItem.js +58 -62
  148. package/src/SystemCSSVars.js +4 -25
  149. package/src/Theming.js +2 -1
  150. package/src/UI5Element.js +386 -354
  151. package/src/UI5ElementMetadata.js +187 -22
  152. package/src/asset-registries/Icons.js +117 -13
  153. package/src/asset-registries/Illustrations.js +30 -0
  154. package/src/asset-registries/LocaleData.js +105 -66
  155. package/src/asset-registries/Themes.js +29 -34
  156. package/src/asset-registries/i18n.js +64 -47
  157. package/src/assets-meta/IconCollectionsAlias.js +18 -0
  158. package/src/config/AnimationMode.js +11 -1
  159. package/src/config/Icons.js +52 -0
  160. package/src/config/Language.js +33 -6
  161. package/src/config/Theme.js +25 -0
  162. package/src/css/BusyIndicator.css +80 -0
  163. package/src/css/FontFace.css +75 -0
  164. package/src/css/OverrideFontFace.css +32 -0
  165. package/src/css/SystemCSSVars.css +17 -0
  166. package/src/delegate/ItemNavigation.js +244 -170
  167. package/src/delegate/ResizeHandler.js +78 -37
  168. package/src/delegate/ScrollEnablement.js +49 -20
  169. package/src/features/F6Navigation.js +110 -0
  170. package/src/features/OpenUI5Enablement.js +119 -0
  171. package/src/features/OpenUI5Support.js +42 -7
  172. package/src/i18nBundle.js +38 -4
  173. package/src/locale/applyDirection.js +6 -4
  174. package/src/locale/directionChange.js +32 -0
  175. package/src/locale/getEffectiveDir.js +28 -0
  176. package/src/locale/getLocale.js +12 -2
  177. package/src/locale/languageChange.js +1 -1
  178. package/src/renderer/LitRenderer.js +43 -7
  179. package/src/renderer/directives/style-map.js +72 -0
  180. package/src/renderer/executeTemplate.js +34 -0
  181. package/src/theming/CustomStyle.js +47 -6
  182. package/src/theming/ThemeLoaded.js +22 -0
  183. package/src/theming/applyTheme.js +13 -15
  184. package/src/theming/getConstructableStyle.js +15 -10
  185. package/src/theming/getEffectiveLinksHrefs.js +27 -0
  186. package/src/theming/getEffectiveStyle.js +32 -8
  187. package/src/theming/getStylesString.js +15 -0
  188. package/src/theming/getThemeDesignerTheme.js +25 -5
  189. package/src/theming/preloadLinks.js +23 -0
  190. package/src/types/CSSColor.js +9 -0
  191. package/src/types/CalendarType.js +1 -1
  192. package/src/types/DataType.js +13 -1
  193. package/src/types/Float.js +4 -0
  194. package/src/types/Integer.js +4 -0
  195. package/src/types/InvisibleMessageMode.js +30 -0
  196. package/src/types/ItemNavigationBehavior.js +2 -7
  197. package/src/types/PopupState.js +1 -1
  198. package/src/types/ValueState.js +1 -1
  199. package/src/updateShadowRoot.js +29 -0
  200. package/src/util/AriaLabelHelper.js +41 -0
  201. package/src/util/Caret.js +32 -0
  202. package/src/util/ColorConversion.js +398 -0
  203. package/src/util/FocusableElements.js +22 -9
  204. package/src/util/HTMLSanitizer.js +7 -0
  205. package/src/util/InvisibleMessage.js +69 -0
  206. package/src/util/PopupUtils.js +93 -0
  207. package/src/util/SlotsHelper.js +43 -0
  208. package/src/util/TabbableElements.js +5 -1
  209. package/src/util/arraysAreEqual.js +15 -0
  210. package/src/util/clamp.js +12 -0
  211. package/src/util/createLinkInHead.js +19 -0
  212. package/src/util/debounce.js +17 -0
  213. package/src/util/escapeRegex.js +10 -0
  214. package/src/util/generateHighlightedMarkup.js +42 -0
  215. package/src/util/getActiveElement.js +11 -0
  216. package/src/util/getClassCopy.js +10 -0
  217. package/src/util/getEffectiveContentDensity.js +5 -0
  218. package/src/util/getNormalizedTarget.js +16 -0
  219. package/src/util/isDefaultSlotProvided.js +11 -0
  220. package/src/util/isElementInView.js +15 -0
  221. package/src/util/isNodeHidden.js +1 -1
  222. package/src/util/isNodeTabbable.js +4 -4
  223. package/src/util/isValidPropertyName.js +6 -5
  224. package/used-modules.txt +10 -0
  225. package/bundle.es5.js +0 -28
  226. package/config/.eslintrc.js +0 -3
  227. package/config/rollup.config.js +0 -1
  228. package/dist/SVGIconRegistry.js +0 -62
  229. package/dist/boot.js +0 -34
  230. package/dist/compatibility/DOMObserver.js +0 -62
  231. package/dist/compatibility/patchNodeValue.js +0 -24
  232. package/dist/compatibility/whenPolyfillLoaded.js +0 -26
  233. package/dist/delegate/CustomResize.js +0 -78
  234. package/dist/delegate/NativeResize.js +0 -44
  235. package/dist/features/PropertiesFormatSupport.js +0 -8
  236. package/dist/features/browsersupport/Edge.js +0 -6
  237. package/dist/features/browsersupport/IE11.js +0 -41
  238. package/dist/features/browsersupport/IE11WithWebComponentsPolyfill.js +0 -5
  239. package/dist/renderer/ifDefined.js +0 -21
  240. package/dist/resources/bundle.es5.js +0 -212
  241. package/dist/resources/bundle.es5.js.map +0 -1
  242. package/dist/resources/bundle.esm.js +0 -129
  243. package/dist/resources/bundle.esm.js.map +0 -1
  244. package/dist/test-resources/assets/Themes.js +0 -13
  245. package/dist/test-resources/elements/Child.js +0 -35
  246. package/dist/test-resources/elements/Generic.js +0 -84
  247. package/dist/test-resources/elements/GenericExt.js +0 -26
  248. package/dist/test-resources/elements/NoShadowDOM.js +0 -13
  249. package/dist/test-resources/elements/Parent.js +0 -37
  250. package/dist/test-resources/elements/WithStaticArea.js +0 -76
  251. package/dist/test-resources/pages/AllTestElements.html +0 -42
  252. package/dist/test-resources/pages/Configuration.html +0 -20
  253. package/dist/test-resources/pages/ConfigurationScript.html +0 -36
  254. package/dist/test-resources/pages/assets/messagebundle_de.properties +0 -1
  255. package/dist/test-resources/pages/assets/messagebundle_en.properties +0 -1
  256. package/dist/test-resources/pages/assets/messagebundle_es.properties +0 -1
  257. package/dist/test-resources/pages/assets/messagebundle_fr.properties +0 -1
  258. package/dist/test-resources/pages/i18n.html +0 -40
  259. package/dist/test-resources/specs/ConfigurationChange.spec.js +0 -25
  260. package/dist/test-resources/specs/ConfigurationScript.spec.js +0 -61
  261. package/dist/test-resources/specs/ConfigurationURL.spec.js +0 -45
  262. package/dist/test-resources/specs/CustomTheme.spec.js +0 -25
  263. package/dist/test-resources/specs/StaticArea.spec.js +0 -34
  264. package/dist/test-resources/specs/Theming.spec.js +0 -31
  265. package/dist/test-resources/specs/UI5ElementInvalidation.js +0 -258
  266. package/dist/test-resources/specs/UI5ElementLifecycle.js +0 -93
  267. package/dist/test-resources/specs/UI5ElementListenForChildPropChanges.spec.js +0 -101
  268. package/dist/test-resources/specs/UI5ElementMetadataExt.js +0 -40
  269. package/dist/test-resources/specs/UI5ElementPropertyValidation.js +0 -12
  270. package/dist/test-resources/specs/UI5ElementPropsAndAttrs.spec.js +0 -65
  271. package/dist/test-resources/specs/UI5ElementShadowDOM.js +0 -22
  272. package/dist/test-resources/specs/UI5ElementSlots.js +0 -34
  273. package/dist/theming/CSSVarsPonyfill.js +0 -24
  274. package/dist/theming/adaptCSSForIE.js +0 -90
  275. package/dist/theming/createComponentStyleTag.js +0 -49
  276. package/dist/theming/createThemePropertiesStyleTag.js +0 -20
  277. package/dist/thirdparty/Array.from.js +0 -16
  278. package/dist/thirdparty/Array.prototype.fill.js +0 -44
  279. package/dist/thirdparty/Array.prototype.find.js +0 -46
  280. package/dist/thirdparty/Array.prototype.includes.js +0 -51
  281. package/dist/thirdparty/Element.prototype.closest.js +0 -11
  282. package/dist/thirdparty/Element.prototype.matches.js +0 -6
  283. package/dist/thirdparty/Map.prototype.keys.js +0 -9
  284. package/dist/thirdparty/Number.isInteger.js +0 -5
  285. package/dist/thirdparty/Number.isNaN.js +0 -1
  286. package/dist/thirdparty/Number.parseInt.js +0 -1
  287. package/dist/thirdparty/Object.assign.js +0 -31
  288. package/dist/thirdparty/Object.entries.js +0 -11
  289. package/dist/thirdparty/Symbol.js +0 -2
  290. package/dist/thirdparty/WeakSet.js +0 -27
  291. package/dist/thirdparty/es6-string-methods.js +0 -182
  292. package/dist/thirdparty/events-polyfills.js +0 -89
  293. package/dist/thirdparty/fetch.js +0 -1
  294. package/dist/thirdparty/template.js +0 -600
  295. package/dist/thirdparty/webcomponents-sd-ce-pf.js +0 -318
  296. package/dist/util/getEffectiveAriaLabelText.js +0 -28
  297. package/dist/util/isSlot.js +0 -3
  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 -62
  320. package/src/boot.js +0 -34
  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/PropertiesFormatSupport.js +0 -8
  327. package/src/features/browsersupport/Edge.js +0 -6
  328. package/src/features/browsersupport/IE11.js +0 -41
  329. package/src/features/browsersupport/IE11WithWebComponentsPolyfill.js +0 -5
  330. package/src/renderer/ifDefined.js +0 -21
  331. package/src/theming/CSSVarsPonyfill.js +0 -24
  332. package/src/theming/adaptCSSForIE.js +0 -90
  333. package/src/theming/createComponentStyleTag.js +0 -49
  334. package/src/theming/createThemePropertiesStyleTag.js +0 -20
  335. package/src/thirdparty/Array.from.js +0 -16
  336. package/src/thirdparty/Array.prototype.fill.js +0 -44
  337. package/src/thirdparty/Array.prototype.find.js +0 -46
  338. package/src/thirdparty/Array.prototype.includes.js +0 -51
  339. package/src/thirdparty/Element.prototype.closest.js +0 -11
  340. package/src/thirdparty/Element.prototype.matches.js +0 -6
  341. package/src/thirdparty/Map.prototype.keys.js +0 -9
  342. package/src/thirdparty/Number.isInteger.js +0 -5
  343. package/src/thirdparty/Number.isNaN.js +0 -1
  344. package/src/thirdparty/Number.parseInt.js +0 -1
  345. package/src/thirdparty/Object.assign.js +0 -31
  346. package/src/thirdparty/Object.entries.js +0 -11
  347. package/src/thirdparty/Symbol.js +0 -2
  348. package/src/thirdparty/WeakSet.js +0 -27
  349. package/src/thirdparty/es6-string-methods.js +0 -182
  350. package/src/thirdparty/events-polyfills.js +0 -89
  351. package/src/thirdparty/fetch.js +0 -1
  352. package/src/thirdparty/template.js +0 -600
  353. package/src/thirdparty/webcomponents-sd-ce-pf.js +0 -318
  354. package/src/util/getEffectiveAriaLabelText.js +0 -28
  355. package/src/util/isSlot.js +0 -3
@@ -1,34 +1,52 @@
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";
7
+ import updateShadowRoot from "./updateShadowRoot.js";
8
+ import { renderDeferred, renderImmediately, cancelRender } from "./Render.js";
6
9
  import { registerTag, isTagRegistered, recordTagRegistrationFailure } from "./CustomElementsRegistry.js";
7
- import DOMObserver from "./compatibility/DOMObserver.js";
10
+ import { observeDOMNode, unobserveDOMNode } from "./DOMObserver.js";
8
11
  import { skipOriginalEvent } from "./config/NoConflict.js";
9
- import { getRTL } from "./config/RTL.js";
10
- import getConstructableStyle from "./theming/getConstructableStyle.js";
11
- import createComponentStyleTag from "./theming/createComponentStyleTag.js";
12
- import getEffectiveStyle from "./theming/getEffectiveStyle.js";
13
- import Integer from "./types/Integer.js";
14
- import Float from "./types/Float.js";
12
+ import getEffectiveDir from "./locale/getEffectiveDir.js";
13
+ import DataType from "./types/DataType.js";
15
14
  import { kebabToCamelCase, camelToKebabCase } from "./util/StringHelper.js";
16
15
  import isValidPropertyName from "./util/isValidPropertyName.js";
17
- import isSlot from "./util/isSlot.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";
18
20
  import { markAsRtlAware } from "./locale/RTLAwareRegistry.js";
19
-
20
- const metadata = {
21
- events: {
22
- "_property-change": {},
23
- },
24
- };
21
+ import preloadLinks from "./theming/preloadLinks.js";
25
22
 
26
23
  let autoId = 0;
27
24
 
28
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
+ }
29
40
 
30
- const GLOBAL_CONTENT_DENSITY_CSS_VAR = "--_ui5_content_density";
31
- const GLOBAL_DIR_CSS_VAR = "--_ui5_dir";
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 = {};
32
50
 
33
51
  /**
34
52
  * Base class for all UI5 Web Components
@@ -43,19 +61,26 @@ const GLOBAL_DIR_CSS_VAR = "--_ui5_dir";
43
61
  class UI5Element extends HTMLElement {
44
62
  constructor() {
45
63
  super();
46
- this._initializeState();
47
- this._upgradeAllProperties();
48
- this._initializeContainers();
49
- this._upToDate = false;
50
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
51
72
  let deferredResolve;
52
73
  this._domRefReadyPromise = new Promise(resolve => {
53
74
  deferredResolve = resolve;
54
75
  });
55
76
  this._domRefReadyPromise._deferredResolve = deferredResolve;
56
77
 
57
- this._monitoredChildProps = new Map();
58
- this._firePropertyChange = false;
78
+ this._initializeState();
79
+ this._upgradeAllProperties();
80
+
81
+ if (this.constructor._needsShadowDOM()) {
82
+ this.attachShadow({ mode: "open" });
83
+ }
59
84
  }
60
85
 
61
86
  /**
@@ -73,60 +98,34 @@ class UI5Element extends HTMLElement {
73
98
  }
74
99
 
75
100
  /**
101
+ * Do not call this method from derivatives of UI5Element, use "onEnterDOM" only
76
102
  * @private
77
103
  */
78
- _initializeContainers() {
79
- const needsShadowDOM = this.constructor._needsShadowDOM();
80
- const needsStaticArea = this.constructor._needsStaticArea();
104
+ async connectedCallback() {
105
+ this.setAttribute(this.constructor.getMetadata().getPureTag(), "");
106
+ if (this.constructor.getMetadata().supportsF6FastNavigation()) {
107
+ this.setAttribute("data-sap-ui-fastnavgroup", "true");
108
+ }
81
109
 
82
- // Init Shadow Root
83
- if (needsShadowDOM) {
84
- this.attachShadow({ mode: "open" });
110
+ const slotsAreManaged = this.constructor.getMetadata().slotsAreManaged();
85
111
 
86
- // IE11, Edge
87
- if (window.ShadyDOM) {
88
- createComponentStyleTag(this.constructor);
89
- }
112
+ this._inDOM = true;
90
113
 
91
- // Chrome
92
- if (document.adoptedStyleSheets) {
93
- const style = getConstructableStyle(this.constructor);
94
- this.shadowRoot.adoptedStyleSheets = [style];
95
- }
114
+ if (slotsAreManaged) {
115
+ // always register the observer before yielding control to the main thread (await)
116
+ this._startObservingDOMChildren();
117
+ await this._processChildren();
96
118
  }
97
119
 
98
- // Init StaticAreaItem only if needed
99
- if (needsStaticArea) {
100
- this.staticAreaItem = new StaticAreaItem(this);
120
+ if (!this._inDOM) { // Component removed from DOM while _processChildren was running
121
+ return;
101
122
  }
102
- }
103
-
104
- /**
105
- * Do not call this method from derivatives of UI5Element, use "onEnterDOM" only
106
- * @private
107
- */
108
- async connectedCallback() {
109
- const needsShadowDOM = this.constructor._needsShadowDOM();
110
- const slotsAreManaged = this.constructor.getMetadata().slotsAreManaged();
111
-
112
- // Render the Shadow DOM
113
- if (needsShadowDOM) {
114
- if (slotsAreManaged) {
115
- // always register the observer before yielding control to the main thread (await)
116
- this._startObservingDOMChildren();
117
- await this._processChildren();
118
- }
119
-
120
- if (!this.shadowRoot) { // Workaround for Firefox74 bug
121
- await Promise.resolve();
122
- }
123
123
 
124
- RenderScheduler.register(this);
125
- await RenderScheduler.renderImmediately(this);
126
- this._domRefReadyPromise._deferredResolve();
127
- if (typeof this.onEnterDOM === "function") {
128
- this.onEnterDOM();
129
- }
124
+ renderImmediately(this);
125
+ this._domRefReadyPromise._deferredResolve();
126
+ this._fullyConnected = true;
127
+ if (typeof this.onEnterDOM === "function") {
128
+ this.onEnterDOM();
130
129
  }
131
130
  }
132
131
 
@@ -135,24 +134,26 @@ class UI5Element extends HTMLElement {
135
134
  * @private
136
135
  */
137
136
  disconnectedCallback() {
138
- const needsShadowDOM = this.constructor._needsShadowDOM();
139
- const needsStaticArea = this.constructor._needsStaticArea();
140
137
  const slotsAreManaged = this.constructor.getMetadata().slotsAreManaged();
141
138
 
142
- if (needsShadowDOM) {
143
- if (slotsAreManaged) {
144
- this._stopObservingDOMChildren();
145
- }
139
+ this._inDOM = false;
146
140
 
147
- RenderScheduler.deregister(this);
141
+ if (slotsAreManaged) {
142
+ this._stopObservingDOMChildren();
143
+ }
144
+
145
+ if (this._fullyConnected) {
148
146
  if (typeof this.onExitDOM === "function") {
149
147
  this.onExitDOM();
150
148
  }
149
+ this._fullyConnected = false;
151
150
  }
152
151
 
153
- if (needsStaticArea) {
154
- this.staticAreaItem._removeFragmentFromStaticArea();
152
+ if (this.staticAreaItem && this.staticAreaItem.parentElement) {
153
+ this.staticAreaItem.parentElement.removeChild(this.staticAreaItem);
155
154
  }
155
+
156
+ cancelRender(this);
156
157
  }
157
158
 
158
159
  /**
@@ -168,16 +169,16 @@ class UI5Element extends HTMLElement {
168
169
  const mutationObserverOptions = {
169
170
  childList: true,
170
171
  subtree: canSlotText,
171
- characterData: true,
172
+ characterData: canSlotText,
172
173
  };
173
- DOMObserver.observeDOMNode(this, this._processChildren.bind(this), mutationObserverOptions);
174
+ observeDOMNode(this, this._processChildren.bind(this), mutationObserverOptions);
174
175
  }
175
176
 
176
177
  /**
177
178
  * @private
178
179
  */
179
180
  _stopObservingDOMChildren() {
180
- DOMObserver.unobserveDOMNode(this);
181
+ unobserveDOMNode(this);
181
182
  }
182
183
 
183
184
  /**
@@ -199,8 +200,14 @@ class UI5Element extends HTMLElement {
199
200
  const canSlotText = this.constructor.getMetadata().canSlotText();
200
201
  const domChildren = Array.from(canSlotText ? this.childNodes : this.children);
201
202
 
202
- // 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
203
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]]);
204
211
  this._clearSlot(slotName, slotData);
205
212
  }
206
213
 
@@ -209,7 +216,7 @@ class UI5Element extends HTMLElement {
209
216
 
210
217
  const allChildrenUpgraded = domChildren.map(async (child, idx) => {
211
218
  // Determine the type of the child (mainly by the slot attribute)
212
- const slotName = this.constructor._getSlotName(child);
219
+ const slotName = getSlotName(child);
213
220
  const slotData = slotsMap[slotName];
214
221
 
215
222
  // Check if the slotName is supported
@@ -247,12 +254,15 @@ class UI5Element extends HTMLElement {
247
254
 
248
255
  child = this.constructor.getMetadata().constructor.validateSlotValue(child, slotData);
249
256
 
250
- if (child.isUI5Element && slotData.listenFor) {
251
- 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));
252
261
  }
253
262
 
263
+ // Listen for the slotchange event if the child is a slot itself
254
264
  if (isSlot(child)) {
255
- this._attachSlotChange(child);
265
+ this._attachSlotChange(child, slotName);
256
266
  }
257
267
 
258
268
  const propertyName = slotData.propertyName || slotName;
@@ -268,10 +278,33 @@ class UI5Element extends HTMLElement {
268
278
 
269
279
  // Distribute the child in the _state object, keeping the Light DOM order,
270
280
  // not the order elements are defined.
271
- slottedChildrenMap.forEach((children, slot) => {
272
- 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);
273
283
  });
274
- 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
+ }
275
308
  }
276
309
 
277
310
  /**
@@ -280,24 +313,62 @@ class UI5Element extends HTMLElement {
280
313
  */
281
314
  _clearSlot(slotName, slotData) {
282
315
  const propertyName = slotData.propertyName || slotName;
283
-
284
- let children = this._state[propertyName];
285
- if (!Array.isArray(children)) {
286
- children = [children];
287
- }
316
+ const children = this._state[propertyName];
288
317
 
289
318
  children.forEach(child => {
290
319
  if (child && child.isUI5Element) {
291
- this._detachChildPropertyUpdated(child);
320
+ const method = (child.detachInvalidate || child._detachChange).bind(child);
321
+ method(this._getChildChangeListener(slotName));
292
322
  }
293
323
 
294
324
  if (isSlot(child)) {
295
- this._detachSlotChange(child);
325
+ this._detachSlotChange(child, slotName);
296
326
  }
297
327
  });
298
328
 
299
329
  this._state[propertyName] = [];
300
- 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
+ });
301
372
  }
302
373
 
303
374
  /**
@@ -312,12 +383,8 @@ class UI5Element extends HTMLElement {
312
383
  const propertyTypeClass = properties[nameInCamelCase].type;
313
384
  if (propertyTypeClass === Boolean) {
314
385
  newValue = newValue !== null;
315
- }
316
- if (propertyTypeClass === Integer) {
317
- newValue = parseInt(newValue);
318
- }
319
- if (propertyTypeClass === Float) {
320
- newValue = parseFloat(newValue);
386
+ } else if (isDescendantOf(propertyTypeClass, DataType)) {
387
+ newValue = propertyTypeClass.attributeToProperty(newValue);
321
388
  }
322
389
  this[nameInCamelCase] = newValue;
323
390
  }
@@ -330,22 +397,24 @@ class UI5Element extends HTMLElement {
330
397
  if (!this.constructor.getMetadata().hasAttribute(name)) {
331
398
  return;
332
399
  }
333
-
334
- if (typeof newValue === "object") {
335
- return;
336
- }
337
-
400
+ const properties = this.constructor.getMetadata().getProperties();
401
+ const propertyTypeClass = properties[name].type;
338
402
  const attrName = camelToKebabCase(name);
339
403
  const attrValue = this.getAttribute(attrName);
340
- if (typeof newValue === "boolean") {
404
+
405
+ if (propertyTypeClass === Boolean) {
341
406
  if (newValue === true && attrValue === null) {
342
407
  this.setAttribute(attrName, "");
343
408
  } else if (newValue === false && attrValue !== null) {
344
409
  this.removeAttribute(attrName);
345
410
  }
346
- } else if (attrValue !== newValue) {
347
- this.setAttribute(attrName, newValue);
348
- }
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
349
418
  }
350
419
 
351
420
  /**
@@ -371,120 +440,96 @@ class UI5Element extends HTMLElement {
371
440
  * @private
372
441
  */
373
442
  _initializeState() {
374
- const defaultState = this.constructor._getDefaultState();
375
- this._state = Object.assign({}, defaultState);
443
+ this._state = { ...this.constructor.getMetadata().getInitialState() };
376
444
  }
377
445
 
378
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}
379
451
  * @private
380
452
  */
381
- _attachChildPropertyUpdated(child, listenFor) {
382
- const childMetadata = child.constructor.getMetadata(),
383
- slotName = this.constructor._getSlotName(child), // all slotted children have the same configuration
384
- childProperties = childMetadata.getProperties();
385
-
386
- let observedProps = [],
387
- notObservedProps = [];
388
-
389
- if (Array.isArray(listenFor)) {
390
- observedProps = listenFor;
391
- } else {
392
- observedProps = Array.isArray(listenFor.props) ? listenFor.props : Object.keys(childProperties);
393
- notObservedProps = Array.isArray(listenFor.exclude) ? listenFor.exclude : [];
453
+ _getChildChangeListener(slotName) {
454
+ if (!this._childChangeListeners.has(slotName)) {
455
+ this._childChangeListeners.set(slotName, this._onChildChange.bind(this, slotName));
394
456
  }
395
-
396
- if (!this._monitoredChildProps.has(slotName)) {
397
- this._monitoredChildProps.set(slotName, { observedProps, notObservedProps });
398
- }
399
-
400
- child.addEventListener("_property-change", this._invalidateParentOnPropertyUpdate);
401
- child._firePropertyChange = true;
402
- }
403
-
404
- /**
405
- * @private
406
- */
407
- _detachChildPropertyUpdated(child) {
408
- child.removeEventListener("_property-change", this._invalidateParentOnPropertyUpdate);
409
- child._firePropertyChange = false;
457
+ return this._childChangeListeners.get(slotName);
410
458
  }
411
459
 
412
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}
413
465
  * @private
414
466
  */
415
- _propertyChange(name, value) {
416
- this._updateAttribute(name, value);
417
-
418
- if (this._firePropertyChange) {
419
- this.dispatchEvent(new CustomEvent("_property-change", {
420
- detail: { name, newValue: value },
421
- composed: false,
422
- bubbles: true,
423
- }));
467
+ _getSlotChangeListener(slotName) {
468
+ if (!this._slotChangeListeners.has(slotName)) {
469
+ this._slotChangeListeners.set(slotName, this._onSlotChange.bind(this, slotName));
424
470
  }
471
+ return this._slotChangeListeners.get(slotName);
425
472
  }
426
473
 
427
474
  /**
428
475
  * @private
429
476
  */
430
- _invalidateParentOnPropertyUpdate(prop) {
431
- // The web component to be invalidated
432
- const parentNode = this.parentNode;
433
- if (!parentNode) {
434
- return;
435
- }
436
-
437
- const slotName = parentNode.constructor._getSlotName(this);
438
- const propsMetadata = parentNode._monitoredChildProps.get(slotName);
439
-
440
- if (!propsMetadata) {
441
- return;
442
- }
443
- const { observedProps, notObservedProps } = propsMetadata;
444
-
445
- if (observedProps.includes(prop.detail.name) && !notObservedProps.includes(prop.detail.name)) {
446
- parentNode._invalidate("_parent_", this);
447
- }
477
+ _attachSlotChange(child, slotName) {
478
+ child.addEventListener("slotchange", this._getSlotChangeListener(slotName));
448
479
  }
449
480
 
450
481
  /**
451
482
  * @private
452
483
  */
453
- _attachSlotChange(child) {
454
- if (!this._invalidateOnSlotChange) {
455
- this._invalidateOnSlotChange = () => {
456
- this._invalidate("slotchange");
457
- };
458
- }
459
- child.addEventListener("slotchange", this._invalidateOnSlotChange);
484
+ _detachSlotChange(child, slotName) {
485
+ child.removeEventListener("slotchange", this._getSlotChangeListener(slotName));
460
486
  }
461
487
 
462
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
463
492
  * @private
464
493
  */
465
- _detachSlotChange(child) {
466
- child.removeEventListener("slotchange", this._invalidateOnSlotChange);
494
+ _onSlotChange(slotName) {
495
+ _invalidate.call(this, {
496
+ type: "slot",
497
+ name: slotName,
498
+ reason: "slotchange",
499
+ });
467
500
  }
468
501
 
469
502
  /**
470
- * Asynchronously re-renders an already rendered web component
471
- * @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
472
528
  */
473
- _invalidate() {
474
- if (!this._upToDate) {
475
- // console.log("already invalidated", this, ...arguments);
476
- return;
477
- }
478
-
479
- if (this.getDomRef() && !this._suppressInvalidation) {
480
- this._upToDate = false;
481
- // console.log("INVAL", this, ...arguments);
482
- RenderScheduler.renderDeferred(this);
483
- }
484
- }
529
+ onInvalidation(changeInfo) {}
485
530
 
486
531
  /**
487
- * 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
488
533
  * @protected
489
534
  */
490
535
  _render() {
@@ -503,15 +548,37 @@ class UI5Element extends HTMLElement {
503
548
  }
504
549
 
505
550
  // resume normal invalidation handling
506
- delete this._suppressInvalidation;
551
+ this._suppressInvalidation = false;
507
552
 
508
553
  // Update the shadow root with the render result
509
- // console.log(this.getDomRef() ? "RE-RENDER" : "FIRST RENDER", this);
510
- this._upToDate = true;
511
- 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
+ }
512
569
 
513
- if (this._shouldUpdateFragment()) {
514
- this.staticAreaItem._updateFragment(this);
570
+ return res;
571
+ }));
572
+ }
573
+ */
574
+ this._changedState = [];
575
+
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();
515
582
  }
516
583
 
517
584
  // Safari requires that children get the slot attribute only after the slot tags have been rendered in the shadow DOM
@@ -525,23 +592,6 @@ class UI5Element extends HTMLElement {
525
592
  }
526
593
  }
527
594
 
528
- /**
529
- * @private
530
- */
531
- _updateShadowRoot() {
532
- if (!this.constructor._needsShadowDOM()) {
533
- return;
534
- }
535
-
536
- let styleToPrepend;
537
- const renderResult = this.constructor.template(this);
538
-
539
- if (!document.adoptedStyleSheets && !window.ShadyDOM) {
540
- styleToPrepend = getEffectiveStyle(this.constructor);
541
- }
542
- this.constructor.render(renderResult, this.shadowRoot, styleToPrepend, { eventContext: this });
543
- }
544
-
545
595
  /**
546
596
  * @private
547
597
  */
@@ -564,16 +614,27 @@ class UI5Element extends HTMLElement {
564
614
 
565
615
  /**
566
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
567
618
  * Use this method instead of "this.shadowRoot" to read the Shadow DOM, if ever necessary
619
+ *
568
620
  * @public
569
621
  */
570
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
+
571
628
  if (!this.shadowRoot || this.shadowRoot.children.length === 0) {
572
629
  return;
573
630
  }
574
631
 
575
- return this.shadowRoot.children.length === 1
576
- ? 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];
577
638
  }
578
639
 
579
640
  /**
@@ -590,12 +651,13 @@ class UI5Element extends HTMLElement {
590
651
  }
591
652
 
592
653
  /**
593
- * Use this method in order to get a reference to element in the shadow root of a web component
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.
594
656
  * @public
595
- * @param {String} refName Defines the name of the stable DOM ref
596
657
  */
597
- getStableDomRef(refName) {
598
- return this.getDomRef().querySelector(`[data-ui5-stable=${refName}]`);
658
+ async getFocusDomRefAsync() {
659
+ await this._waitForDomRef();
660
+ return this.getFocusDomRef();
599
661
  }
600
662
 
601
663
  /**
@@ -618,10 +680,11 @@ class UI5Element extends HTMLElement {
618
680
  * @param name - name of the event
619
681
  * @param data - additional data for the event
620
682
  * @param cancelable - true, if the user can call preventDefault on the event object
683
+ * @param bubbles - true, if the event bubbles
621
684
  * @returns {boolean} false, if the event was cancelled (preventDefault called), true otherwise
622
685
  */
623
- fireEvent(name, data, cancelable) {
624
- const eventResult = this._fireEvent(name, data, cancelable);
686
+ fireEvent(name, data, cancelable = false, bubbles = true) {
687
+ const eventResult = this._fireEvent(name, data, cancelable, bubbles);
625
688
  const camelCaseEventName = kebabToCamelCase(name);
626
689
 
627
690
  if (camelCaseEventName !== name) {
@@ -631,35 +694,33 @@ class UI5Element extends HTMLElement {
631
694
  return eventResult;
632
695
  }
633
696
 
634
- _fireEvent(name, data, cancelable) {
635
- let compatEventResult = true; // Initialized to true, because if the event is not fired at all, it should be considered "not-prevented"
636
-
697
+ _fireEvent(name, data, cancelable = false, bubbles = true) {
637
698
  const noConflictEvent = new CustomEvent(`ui5-${name}`, {
638
699
  detail: data,
639
700
  composed: false,
640
- bubbles: true,
701
+ bubbles,
641
702
  cancelable,
642
703
  });
643
704
 
644
- // This will be false if the compat event is prevented
645
- compatEventResult = this.dispatchEvent(noConflictEvent);
705
+ // This will be false if the no-conflict event is prevented
706
+ const noConflictEventResult = this.dispatchEvent(noConflictEvent);
646
707
 
647
708
  if (skipOriginalEvent(name)) {
648
- return compatEventResult;
709
+ return noConflictEventResult;
649
710
  }
650
711
 
651
- const customEvent = new CustomEvent(name, {
712
+ const normalEvent = new CustomEvent(name, {
652
713
  detail: data,
653
714
  composed: false,
654
- bubbles: true,
715
+ bubbles,
655
716
  cancelable,
656
717
  });
657
718
 
658
719
  // This will be false if the normal event is prevented
659
- const normalEventResult = this.dispatchEvent(customEvent);
720
+ const normalEventResult = this.dispatchEvent(normalEvent);
660
721
 
661
722
  // Return false if any of the two events was prevented (its result was false).
662
- return normalEventResult && compatEventResult;
723
+ return normalEventResult && noConflictEventResult;
663
724
  }
664
725
 
665
726
  /**
@@ -668,18 +729,7 @@ class UI5Element extends HTMLElement {
668
729
  * @public
669
730
  */
670
731
  getSlottedNodes(slotName) {
671
- const reducer = (acc, curr) => {
672
- if (!isSlot(curr)) {
673
- return acc.concat([curr]);
674
- }
675
- return acc.concat(curr.assignedNodes({ flatten: true }).filter(item => item instanceof HTMLElement));
676
- };
677
-
678
- return this[slotName].reduce(reducer, []);
679
- }
680
-
681
- get isCompact() {
682
- return getComputedStyle(this).getPropertyValue(GLOBAL_CONTENT_DENSITY_CSS_VAR) === "compact";
732
+ return getSlottedElementsList(this[slotName]);
683
733
  }
684
734
 
685
735
  /**
@@ -691,33 +741,7 @@ class UI5Element extends HTMLElement {
691
741
  */
692
742
  get effectiveDir() {
693
743
  markAsRtlAware(this.constructor); // if a UI5 Element calls this method, it's considered to be rtl-aware
694
-
695
- const doc = window.document;
696
- const dirValues = ["ltr", "rtl"]; // exclude "auto" and "" from all calculations
697
- const locallyAppliedDir = getComputedStyle(this).getPropertyValue(GLOBAL_DIR_CSS_VAR);
698
-
699
- // In that order, inspect the CSS Var (for modern browsers), the element itself, html and body (for IE fallback)
700
- if (dirValues.includes(locallyAppliedDir)) {
701
- return locallyAppliedDir;
702
- }
703
- if (dirValues.includes(this.dir)) {
704
- return this.dir;
705
- }
706
- if (dirValues.includes(doc.documentElement.dir)) {
707
- return doc.documentElement.dir;
708
- }
709
- if (dirValues.includes(doc.body.dir)) {
710
- return doc.body.dir;
711
- }
712
-
713
- // Finally, check the configuration for explicitly set RTL or language-implied RTL
714
- return getRTL() ? "rtl" : undefined;
715
- }
716
-
717
- updateStaticAreaItemContentDensity() {
718
- if (this.staticAreaItem) {
719
- this.staticAreaItem._updateContentDensity(this.isCompact);
720
- }
744
+ return getEffectiveDir(this);
721
745
  }
722
746
 
723
747
  /**
@@ -737,26 +761,6 @@ class UI5Element extends HTMLElement {
737
761
  return this.getMetadata().getAttributesList();
738
762
  }
739
763
 
740
- /**
741
- * @private
742
- */
743
- static _getSlotName(child) {
744
- // Text nodes can only go to the default slot
745
- if (!(child instanceof HTMLElement)) {
746
- return "default";
747
- }
748
-
749
- // Discover the slot based on the real slot name (f.e. footer => footer, or content-32 => content)
750
- const slot = child.getAttribute("slot");
751
- if (slot) {
752
- const match = slot.match(/^(.+?)-\d+$/);
753
- return match ? match[1] : slot;
754
- }
755
-
756
- // Use default slot as a fallback
757
- return "default";
758
- }
759
-
760
764
  /**
761
765
  * @private
762
766
  */
@@ -764,70 +768,30 @@ class UI5Element extends HTMLElement {
764
768
  return !!this.template;
765
769
  }
766
770
 
767
- _shouldUpdateFragment() {
768
- return this.constructor._needsStaticArea() && this.staticAreaItem.isRendered();
769
- }
770
-
771
771
  /**
772
772
  * @private
773
773
  */
774
774
  static _needsStaticArea() {
775
- return typeof this.staticAreaTemplate === "function";
775
+ return !!this.staticAreaTemplate;
776
776
  }
777
777
 
778
778
  /**
779
779
  * @public
780
780
  */
781
781
  getStaticAreaItemDomRef() {
782
- return this.staticAreaItem.getDomRef();
783
- }
784
-
785
- /**
786
- * @private
787
- */
788
- static _getDefaultState() {
789
- if (this._defaultState) {
790
- return this._defaultState;
782
+ if (!this.constructor._needsStaticArea()) {
783
+ throw new Error("This component does not use the static area");
791
784
  }
792
785
 
793
- const MetadataClass = this.getMetadata();
794
- const defaultState = {};
795
- const slotsAreManaged = MetadataClass.slotsAreManaged();
796
-
797
- // Initialize properties
798
- const props = MetadataClass.getProperties();
799
- for (const propName in props) { // eslint-disable-line
800
- const propType = props[propName].type;
801
- const propDefaultValue = props[propName].defaultValue;
802
-
803
- if (propType === Boolean) {
804
- defaultState[propName] = false;
805
-
806
- if (propDefaultValue !== undefined) {
807
- console.warn("The 'defaultValue' metadata key is ignored for all booleans properties, they would be initialized with 'false' by default"); // eslint-disable-line
808
- }
809
- } else if (props[propName].multiple) {
810
- defaultState[propName] = [];
811
- } else if (propType === Object) {
812
- defaultState[propName] = "defaultValue" in props[propName] ? props[propName].defaultValue : {};
813
- } else if (propType === String) {
814
- defaultState[propName] = "defaultValue" in props[propName] ? props[propName].defaultValue : "";
815
- } else {
816
- defaultState[propName] = propDefaultValue;
817
- }
786
+ if (!this.staticAreaItem) {
787
+ this.staticAreaItem = StaticAreaItem.createInstance();
788
+ this.staticAreaItem.setOwnerElement(this);
818
789
  }
819
-
820
- // Initialize slots
821
- if (slotsAreManaged) {
822
- const slots = MetadataClass.getSlots();
823
- for (const [slotName, slotData] of Object.entries(slots)) { // eslint-disable-line
824
- const propertyName = slotData.propertyName || slotName;
825
- defaultState[propertyName] = [];
826
- }
790
+ if (!this.staticAreaItem.parentElement) {
791
+ getSingletonElementInstance("ui5-static-area").appendChild(this.staticAreaItem);
827
792
  }
828
793
 
829
- this._defaultState = defaultState;
830
- return defaultState;
794
+ return this.staticAreaItem.getDomRef();
831
795
  }
832
796
 
833
797
  /**
@@ -841,7 +805,7 @@ class UI5Element extends HTMLElement {
841
805
  const properties = this.getMetadata().getProperties();
842
806
  for (const [prop, propData] of Object.entries(properties)) { // eslint-disable-line
843
807
  if (!isValidPropertyName(prop)) {
844
- 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 */
845
809
  }
846
810
 
847
811
  if (propData.type === Boolean && propData.defaultValue) {
@@ -879,14 +843,27 @@ class UI5Element extends HTMLElement {
879
843
  }
880
844
  },
881
845
  set(value) {
846
+ let isDifferent;
882
847
  value = this.constructor.getMetadata().constructor.validatePropertyValue(value, propData);
883
848
 
884
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
+ }
885
857
 
886
- if (oldState !== value) {
858
+ if (isDifferent) {
887
859
  this._state[prop] = value;
888
- this._invalidate(prop, value);
889
- 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);
890
867
  }
891
868
  },
892
869
  });
@@ -897,7 +874,7 @@ class UI5Element extends HTMLElement {
897
874
  const slots = this.getMetadata().getSlots();
898
875
  for (const [slotName, slotData] of Object.entries(slots)) { // eslint-disable-line
899
876
  if (!isValidPropertyName(slotName)) {
900
- 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 */
901
878
  }
902
879
 
903
880
  const propertyName = slotData.propertyName || slotName;
@@ -909,7 +886,7 @@ class UI5Element extends HTMLElement {
909
886
  return [];
910
887
  },
911
888
  set() {
912
- 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...)");
913
890
  },
914
891
  });
915
892
  }
@@ -924,6 +901,14 @@ class UI5Element extends HTMLElement {
924
901
  return metadata;
925
902
  }
926
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
+
927
912
  /**
928
913
  * Returns the CSS for this UI5 Web Component Class
929
914
  * @protected
@@ -940,6 +925,50 @@ class UI5Element extends HTMLElement {
940
925
  return "";
941
926
  }
942
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
+
943
972
  /**
944
973
  * Registers a UI5 Web Component in the browser window object
945
974
  * @public
@@ -948,9 +977,10 @@ class UI5Element extends HTMLElement {
948
977
  static async define() {
949
978
  await boot();
950
979
 
951
- if (this.onDefine) {
952
- await this.onDefine();
953
- }
980
+ await Promise.all([
981
+ this.whenDependenciesDefined(),
982
+ this.onDefine(),
983
+ ]);
954
984
 
955
985
  const tag = this.getMetadata().getTag();
956
986
  const altTag = this.getMetadata().getAltTag();
@@ -964,11 +994,13 @@ class UI5Element extends HTMLElement {
964
994
  this._generateAccessors();
965
995
  registerTag(tag);
966
996
  window.customElements.define(tag, this);
997
+ preloadLinks(this);
967
998
 
968
999
  if (altTag && !customElements.get(altTag)) {
969
- class oldClassName extends this {}
970
1000
  registerTag(altTag);
971
- window.customElements.define(altTag, oldClassName);
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
+ }));
972
1004
  }
973
1005
  }
974
1006
  return this;