@ui5/webcomponents-base 0.0.0-d849bec2c → 0.0.0-dc3ccac50

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