@ui5/webcomponents-base 0.0.0-306572ffa → 0.0.0-35e2c9666

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