@ui5/webcomponents-base 0.0.0-aeb93167c → 0.0.0-b053ad005

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