@ui5/webcomponents-base 0.0.0-0fcda382e → 0.0.0-11d529e8a

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