@ui5/webcomponents-base 0.0.0-1b568f22b → 0.0.0-1e08ebdef

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