@ui5/webcomponents-base
Advanced tools
Comparing version 0.11.1 to 0.12.0
@@ -6,2 +6,20 @@ # Change Log | ||
# [0.12.0](https://github.com/SAP/ui5-webcomponents/compare/v0.11.0...v0.12.0) (2019-06-10) | ||
### Bug Fixes | ||
* access DOM in connectedCallback instead of constructor ([#524](https://github.com/SAP/ui5-webcomponents/issues/524)) ([0f3b8e4](https://github.com/SAP/ui5-webcomponents/commit/0f3b8e4)) | ||
* do not use assignedElements ([#432](https://github.com/SAP/ui5-webcomponents/issues/432)) ([c54c812](https://github.com/SAP/ui5-webcomponents/commit/c54c812)) | ||
### Features | ||
* inline english texts if no translation is fetched ([#479](https://github.com/SAP/ui5-webcomponents/issues/479)) ([abfb221](https://github.com/SAP/ui5-webcomponents/commit/abfb221)) | ||
* **base:** implement late validation ([#522](https://github.com/SAP/ui5-webcomponents/issues/522)) ([c452d60](https://github.com/SAP/ui5-webcomponents/commit/c452d60)) | ||
## [0.11.1](https://github.com/SAP/ui5-webcomponents/compare/v0.11.0...v0.11.1) (2019-05-30) | ||
@@ -8,0 +26,0 @@ |
{ | ||
"name": "@ui5/webcomponents-base", | ||
"version": "0.11.1", | ||
"version": "0.12.0", | ||
"description": "UI5 Web Components: webcomponents.base", | ||
@@ -24,3 +24,3 @@ "author": "SAP SE (https://www.sap.com)", | ||
"dependencies": { | ||
"@ui5/webcomponents-core": "0.11.1", | ||
"@ui5/webcomponents-core": "0.12.0", | ||
"lit-html": "^1.0.0", | ||
@@ -27,0 +27,0 @@ "regenerator-runtime": "0.12.1", |
import { getTheme } from "./Configuration.js"; | ||
import { getEffectiveStyle } from "./Theming.js"; | ||
import { injectWebComponentStyle } from "./theming/StyleInjection.js"; | ||
const styleMap = new Map(); | ||
const createStyleElement = css => { | ||
// Create a local <style> tag for the real shadow DOM | ||
const style = document.createElement("style"); | ||
style.innerHTML = css; | ||
return style; | ||
/** | ||
* Creates the needed CSS for a web component class in the head tag | ||
* Note: IE11, Edge | ||
* @param ElementClass | ||
*/ | ||
const createHeadStyle = ElementClass => { | ||
const tag = ElementClass.getMetadata().getTag(); | ||
const cssContent = getEffectiveStyle(ElementClass); | ||
injectWebComponentStyle(tag, cssContent); | ||
}; | ||
const createConstructableStyleSheet = css => { | ||
const elementStyleSheet = new CSSStyleSheet(); | ||
elementStyleSheet.replaceSync(css); | ||
return elementStyleSheet; | ||
}; | ||
const _createStyle = (tagName, styleContent) => { | ||
/** | ||
* Returns (and caches) a constructable style sheet for a web component class | ||
* Note: Chrome | ||
* @param ElementClass | ||
* @returns {*} | ||
*/ | ||
const getConstructableStyle = ElementClass => { | ||
const tagName = ElementClass.getMetadata().getTag(); | ||
const styleContent = getEffectiveStyle(ElementClass); | ||
const theme = getTheme(); | ||
@@ -26,8 +33,4 @@ const key = theme + tagName; | ||
let style; | ||
if (document.adoptedStyleSheets) { | ||
style = createConstructableStyleSheet(styleContent); | ||
} else { | ||
style = createStyleElement(styleContent); | ||
} | ||
const style = new CSSStyleSheet(); | ||
style.replaceSync(styleContent); | ||
@@ -38,9 +41,18 @@ styleMap.set(key, style); | ||
const createStyle = ElementClass => { | ||
const tagName = ElementClass.getMetadata().getTag(); | ||
/** | ||
* Returns the CSS to be injected inside a web component shadow root, or undefined if not needed | ||
* Note: FF, Safari | ||
* @param ElementClass | ||
* @returns {string} | ||
*/ | ||
const getShadowRootStyle = ElementClass => { | ||
if (document.adoptedStyleSheets || window.ShadyDOM) { | ||
return; | ||
} | ||
const styleContent = getEffectiveStyle(ElementClass); | ||
return _createStyle(tagName, styleContent); | ||
return styleContent; | ||
}; | ||
// eslint-disable-next-line | ||
export { createStyle }; | ||
export { createHeadStyle, getConstructableStyle, getShadowRootStyle}; |
@@ -15,3 +15,3 @@ import { | ||
class ItemNavigation extends EventProvider { | ||
constructor(rootControl, options = {}) { | ||
constructor(rootWebComponent, options = {}) { | ||
super(); | ||
@@ -23,3 +23,3 @@ | ||
this.rootControl = rootControl; | ||
this.rootWebComponent = rootWebComponent; | ||
} | ||
@@ -193,7 +193,7 @@ | ||
if (!this.rootControl.getDomRef()) { | ||
if (!this.rootWebComponent.getDomRef()) { | ||
return; | ||
} | ||
return this.rootControl.getDomRef().querySelector(`#${currentItem.id}`); | ||
return this.rootWebComponent.getDomRef().querySelector(`#${currentItem.id}`); | ||
} | ||
@@ -200,0 +200,0 @@ |
@@ -36,3 +36,3 @@ import UI5Element from "../UI5Element.js"; | ||
* @public | ||
* @param {*} ref Reference to a UI5 Control or DOM Element to be observed | ||
* @param {*} ref Reference to a UI5 Web Component or DOM Element to be observed | ||
* @param {*} callback Callback to be executed | ||
@@ -53,3 +53,3 @@ * @memberof ResizeHandler | ||
* @public | ||
* @param {*} ref Reference to UI5 Control or DOM Element to be unobserved | ||
* @param {*} ref Reference to UI5 Web Component or DOM Element to be unobserved | ||
* @memberof ResizeHandler | ||
@@ -56,0 +56,0 @@ */ |
@@ -1,2 +0,2 @@ | ||
import ControlEvents from "./events/ControlEvents.js"; | ||
import ManagedEvents from "./events/ManagedEvents.js"; | ||
import getOriginalEventTarget from "./events/getOriginalEventTarget.js"; | ||
@@ -61,7 +61,7 @@ import UI5Element from "./UI5Element.js"; | ||
static start() { | ||
ControlEvents.bindAnyEvent(handleEvent); | ||
ManagedEvents.bindAllEvents(handleEvent); | ||
} | ||
static stop() { | ||
ControlEvents.unbindAnyEvent(handleEvent); | ||
ManagedEvents.unbindAllEvents(handleEvent); | ||
} | ||
@@ -68,0 +68,0 @@ } |
@@ -31,3 +31,3 @@ import KeyCodes from "@ui5/webcomponents-core/dist/sap/ui/events/KeyCodes.js"; | ||
if (event.key) { | ||
return (event.key === "F4" && !hasModifierKeys(event)) || ((event.key === "ArrowDown" || event.key === "Down") && checkModifierKeys(event, /* Ctrl */ false, /* Alt */ true, /* Shift */ false)); | ||
return (event.key === "F4" && !hasModifierKeys(event)) || (((event.key === "ArrowDown" || event.key === "Down") || (event.key === "ArrowUp" || event.key === "Up")) && checkModifierKeys(event, /* Ctrl */ false, /* Alt */ true, /* Shift */ false)); | ||
} | ||
@@ -34,0 +34,0 @@ |
@@ -1,6 +0,16 @@ | ||
import { render } from "lit-html"; | ||
import { html, render } from "lit-html"; | ||
class LitRenderer { | ||
static render(renderResult, domNode) { | ||
render(renderResult, domNode); | ||
/** | ||
* Renders "templateResult" by replacing the content of "domNode", and optionally prepends a style tag containing "styles" | ||
* @param templateResult - lit template result object | ||
* @param domNode - the node whose content will be replaced | ||
* @param eventContext - the context of events, bound via the template | ||
* @param styles - if given, will be prepended in a style tag | ||
*/ | ||
static render(templateResult, domNode, eventContext, styles) { | ||
if (styles) { | ||
templateResult = html`<style data-ui5-shadow-root-styles>${styles}</style>${templateResult}`; | ||
} | ||
render(templateResult, domNode, { eventContext }); | ||
} | ||
@@ -11,3 +21,5 @@ } | ||
export { repeat } from "lit-html/directives/repeat"; | ||
export { classMap } from "lit-html/directives/class-map"; | ||
export { styleMap } from "lit-html/directives/style-map"; | ||
export default LitRenderer; |
class RenderQueue { | ||
constructor() { | ||
this.list = []; // Used to store the controls in order | ||
this.promises = new Map(); // Used to store promises for control rendering | ||
this.list = []; // Used to store the web components in order | ||
this.promises = new Map(); // Used to store promises for web component rendering | ||
} | ||
add(control) { | ||
if (this.promises.has(control)) { | ||
return this.promises.get(control); | ||
add(webComponent) { | ||
if (this.promises.has(webComponent)) { | ||
return this.promises.get(webComponent); | ||
} | ||
@@ -18,4 +18,4 @@ | ||
this.list.push(control); | ||
this.promises.set(control, promise); | ||
this.list.push(webComponent); | ||
this.promises.set(webComponent, promise); | ||
@@ -26,7 +26,7 @@ return promise; | ||
shift() { | ||
const control = this.list.shift(); | ||
if (control) { | ||
const promise = this.promises.get(control); | ||
this.promises.delete(control); | ||
return { control, promise }; | ||
const webComponent = this.list.shift(); | ||
if (webComponent) { | ||
const promise = this.promises.get(webComponent); | ||
this.promises.delete(webComponent); | ||
return { webComponent, promise }; | ||
} | ||
@@ -39,4 +39,4 @@ } | ||
isAdded(control) { | ||
return this.promises.has(control); | ||
isAdded(webComponent) { | ||
return this.promises.has(webComponent); | ||
} | ||
@@ -43,0 +43,0 @@ } |
@@ -8,4 +8,4 @@ import RenderQueue from "./RenderQueue.js"; | ||
// Queue for invalidated controls | ||
const invalidatedControls = new RenderQueue(); | ||
// Queue for invalidated web components | ||
const invalidatedWebComponents = new RenderQueue(); | ||
@@ -17,3 +17,3 @@ let renderTaskPromise, | ||
/** | ||
* Class that manages the rendering/re-rendering of controls | ||
* Class that manages the rendering/re-rendering of web components | ||
* This is always asynchronous | ||
@@ -27,8 +27,8 @@ */ | ||
/** | ||
* Queues a control for re-rendering | ||
* @param control | ||
* Queues a web component for re-rendering | ||
* @param webComponent | ||
*/ | ||
static renderDeferred(control) { | ||
// Enqueue the control | ||
const res = invalidatedControls.add(control); | ||
static renderDeferred(webComponent) { | ||
// Enqueue the web component | ||
const res = invalidatedWebComponents.add(webComponent); | ||
@@ -40,5 +40,5 @@ // Schedule a rendering task | ||
static renderImmediately(control) { | ||
// Enqueue the control | ||
const res = invalidatedControls.add(control); | ||
static renderImmediately(webComponent) { | ||
// Enqueue the web component | ||
const res = invalidatedWebComponents.add(webComponent); | ||
@@ -55,5 +55,5 @@ // Immediately start a render task | ||
if (!renderTaskId) { | ||
// renderTaskId = window.setTimeout(RenderScheduler.renderControls, 3000); // Task | ||
// renderTaskId = Promise.resolve().then(RenderScheduler.renderControls); // Micro task | ||
renderTaskId = window.requestAnimationFrame(RenderScheduler.renderControls); // AF | ||
// renderTaskId = window.setTimeout(RenderScheduler.renderWebComponents, 3000); // Task | ||
// renderTaskId = Promise.resolve().then(RenderScheduler.renderWebComponents); // Micro task | ||
renderTaskId = window.requestAnimationFrame(RenderScheduler.renderWebComponents); // AF | ||
} | ||
@@ -64,26 +64,26 @@ } | ||
if (!renderTaskId) { | ||
renderTaskId = 1; // prevent another rendering task from being scheduled, all controls should use this task | ||
RenderScheduler.renderControls(); | ||
renderTaskId = 1; // prevent another rendering task from being scheduled, all web components should use this task | ||
RenderScheduler.renderWebComponents(); | ||
} | ||
} | ||
static renderControls() { | ||
static renderWebComponents() { | ||
// console.log("------------- NEW RENDER TASK ---------------"); | ||
let controlInfo, | ||
control, | ||
let webComponentInfo, | ||
webComponent, | ||
promise; | ||
const renderStats = new Map(); | ||
while (controlInfo = invalidatedControls.shift()) { // eslint-disable-line | ||
control = controlInfo.control; | ||
promise = controlInfo.promise; | ||
while (webComponentInfo = invalidatedWebComponents.shift()) { // eslint-disable-line | ||
webComponent = webComponentInfo.webComponent; | ||
promise = webComponentInfo.promise; | ||
const timesRerendered = renderStats.get(control) || 0; | ||
const timesRerendered = renderStats.get(webComponent) || 0; | ||
if (timesRerendered > MAX_RERENDER_COUNT) { | ||
// console.warn("WARNING RERENDER", control); | ||
throw new Error(`Control re-rendered too many times this task, max allowed is: ${MAX_RERENDER_COUNT}`); | ||
// console.warn("WARNING RERENDER", webComponent); | ||
throw new Error(`Web component re-rendered too many times this task, max allowed is: ${MAX_RERENDER_COUNT}`); | ||
} | ||
control._render(); | ||
webComponent._render(); | ||
promise._deferredResolve(); | ||
renderStats.set(control, timesRerendered + 1); | ||
renderStats.set(webComponent, timesRerendered + 1); | ||
} | ||
@@ -93,3 +93,3 @@ | ||
setTimeout(() => { | ||
if (invalidatedControls.getList().length === 0) { | ||
if (invalidatedWebComponents.getList().length === 0) { | ||
RenderScheduler._resolveTaskPromise(); | ||
@@ -103,3 +103,3 @@ } | ||
/** | ||
* return a promise that will be resolved once all invalidated controls are rendered | ||
* return a promise that will be resolved once all invalidated web components are rendered | ||
*/ | ||
@@ -114,3 +114,3 @@ static whenDOMUpdated() { | ||
window.requestAnimationFrame(() => { | ||
if (invalidatedControls.getList().length === 0) { | ||
if (invalidatedWebComponents.getList().length === 0) { | ||
renderTaskPromise = undefined; | ||
@@ -159,3 +159,3 @@ resolve(); | ||
static _resolveTaskPromise() { | ||
if (invalidatedControls.getList().length > 0) { | ||
if (invalidatedWebComponents.getList().length > 0) { | ||
// More updates are pending. Resolve will be called again | ||
@@ -162,0 +162,0 @@ return; |
import "./shims/jquery-shim.js"; | ||
import ResourceBundle from "@ui5/webcomponents-core/dist/sap/base/i18n/ResourceBundle.js"; | ||
import formatMessage from "@ui5/webcomponents-core/dist/sap/base/strings/formatMessage.js"; | ||
import { getLanguage } from "./LocaleProvider.js"; | ||
@@ -8,29 +9,4 @@ import { registerModuleContent } from "./ResourceLoaderOverrides.js"; | ||
const bundleURLs = new Map(); | ||
const singletonPromises = new Map(); | ||
/** | ||
* Creates a new promise for the specified key (or returns a new one and stores it for next calls). | ||
* The same promise is always returned for multiple calls to this method for the same key. | ||
* This promise can also be resolved so that all usages that await its resolution can continue. | ||
* @param {key} key the unique key identifying the promise | ||
* @private | ||
*/ | ||
const _getSingletonPromise = key => { | ||
const prevPromise = singletonPromises.get(key); | ||
if (prevPromise) { | ||
return prevPromise; | ||
} | ||
let resolveFn; | ||
const newPromise = new Promise(resolve => { | ||
resolveFn = resolve; | ||
}); | ||
// private usage for making a deferred-like API to avoid storing resolve functions in a second map | ||
newPromise._deferredResolve = resolveFn; | ||
singletonPromises.set(key, newPromise); | ||
return newPromise; | ||
}; | ||
/** | ||
* This method preforms the asyncronous task of fething the actual text resources. It will fetch | ||
@@ -45,6 +21,10 @@ * each text resource over the network once (even for multiple calls to the same method). | ||
const fetchResourceBundle = async packageId => { | ||
// depending on the module resolution order, the fetch might run before the bundle URLs are registered - sync them here | ||
await _getSingletonPromise(packageId); | ||
const bundlesForPackage = bundleURLs.get(packageId); | ||
if (!bundlesForPackage) { | ||
console.warn(`Message bundle assets are not configured. Falling back to english texts.`, /* eslint-disable-line */ | ||
` You need to import @ui5/webcomponents/dist/MessageBundleAssets.js with a build tool that supports JSON imports.`); /* eslint-disable-line */ | ||
return; | ||
} | ||
const language = getLanguage(); | ||
@@ -77,11 +57,32 @@ | ||
bundleURLs.set(packageId, bundlesMap); | ||
_getSingletonPromise(packageId)._deferredResolve(); | ||
}; | ||
const getResourceBundle = library => { | ||
return ResourceBundle.create({ | ||
url: `${library}.properties`, | ||
}); | ||
class ResourceBundleFallback { | ||
getText(textObj, ...params) { | ||
return formatMessage(textObj.defaultText, params); | ||
} | ||
} | ||
class ResourceBundleWrapper { | ||
constructor(resouceBundle) { | ||
this._resourceBundle = resouceBundle; | ||
} | ||
getText(textObj, ...params) { | ||
return this._resourceBundle.getText(textObj.key, ...params); | ||
} | ||
} | ||
const getResourceBundle = packageId => { | ||
const bundleLoaded = bundleURLs.has(packageId); | ||
if (bundleLoaded) { | ||
return new ResourceBundleWrapper(ResourceBundle.create({ | ||
url: `${packageId}.properties`, | ||
})); | ||
} | ||
return new ResourceBundleFallback(); | ||
}; | ||
export { fetchResourceBundle, registerMessageBundles, getResourceBundle }; |
import { getWCNoConflict, getCompactSize } from "./Configuration.js"; | ||
import DOMObserver from "./compatibility/DOMObserver.js"; | ||
import ShadowDOM from "./compatibility/ShadowDOM.js"; | ||
import UI5ElementMetadata from "./UI5ElementMetadata.js"; | ||
import Integer from "./types/Integer.js"; | ||
import ControlRenderer from "./ControlRenderer.js"; | ||
import Renderer from "./Renderer.js"; | ||
import RenderScheduler from "./RenderScheduler.js"; | ||
import TemplateContext from "./TemplateContext.js"; | ||
import State from "./State.js"; | ||
import { createStyle } from "./CSS.js"; | ||
import { getConstructableStyle, createHeadStyle } from "./CSS.js"; | ||
import { attachThemeChange } from "./Theming.js"; | ||
const metadata = { | ||
properties: { | ||
/** | ||
* CSS classes that will be applied to the top-level element of the control | ||
*/ | ||
_customClasses: { | ||
type: String, | ||
multiple: true, | ||
}, | ||
/** | ||
* Attributes (most commonly accessibility-related) that will be passed to the control. | ||
* The control has the responsibility to render these attributes | ||
*/ | ||
_customAttributes: { | ||
type: Object, | ||
}, | ||
}, | ||
events: { | ||
@@ -67,3 +47,3 @@ _propertyChange: {}, | ||
} | ||
const newStyle = createStyle(this.constructor); | ||
const newStyle = getConstructableStyle(this.constructor); | ||
if (document.adoptedStyleSheets) { | ||
@@ -82,8 +62,2 @@ this.shadowRoot.adoptedStyleSheets = [newStyle]; | ||
async _initializeShadowRoot() { | ||
const isCompact = getCompactSize(); | ||
if (isCompact) { | ||
this.setAttribute("data-ui5-compact-size", ""); | ||
} | ||
if (this.constructor.getMetadata().getNoShadowDOM()) { | ||
@@ -94,7 +68,11 @@ return Promise.resolve(); | ||
this.attachShadow({ mode: "open" }); | ||
const shadowDOM = await ShadowDOM.prepareShadowDOM(this.constructor); | ||
this.shadowRoot.appendChild(shadowDOM); | ||
// IE11, Edge | ||
if (window.ShadyDOM) { | ||
createHeadStyle(this.constructor); | ||
} | ||
// Chrome | ||
if (document.adoptedStyleSheets) { | ||
const style = createStyle(this.constructor); | ||
const style = getConstructableStyle(this.constructor); | ||
this.shadowRoot.adoptedStyleSheets = [style]; | ||
@@ -105,2 +83,7 @@ } | ||
async connectedCallback() { | ||
const isCompact = getCompactSize(); | ||
if (isCompact) { | ||
this.setAttribute("data-ui5-compact-size", ""); | ||
} | ||
if (this.constructor.getMetadata().getNoShadowDOM()) { | ||
@@ -172,8 +155,4 @@ return; | ||
// Init the _state object based on the supported slots | ||
for (const [prop, propData] of Object.entries(slotsMap)) { // eslint-disable-line | ||
if (propData.multiple) { | ||
this._state[prop] = []; | ||
} else { | ||
this._state[prop] = null; | ||
} | ||
for (const [slot, slotData] of Object.entries(slotsMap)) { // eslint-disable-line | ||
this._clearSlot(slot); | ||
} | ||
@@ -201,4 +180,5 @@ | ||
// Distribute the child in the _state object | ||
child = this._prepareForSlot(slotName, child); | ||
if (slotsMap[slotName].multiple) { | ||
this._state[slotName] = [...this._state[slotName], child]; | ||
this._state[slotName].push(child); | ||
} else { | ||
@@ -208,6 +188,41 @@ this._state[slotName] = child; | ||
}); | ||
this._invalidate(); | ||
} | ||
// Removes all children from the slot and detaches listeners, if any | ||
_clearSlot(slot) { | ||
const slotData = this.constructor.getMetadata().getSlots()[slot]; | ||
let children = this._state[slot]; | ||
if (!Array.isArray(children)) { | ||
children = [children]; | ||
} | ||
children.forEach(child => { | ||
if (child && child._attachChildPropertyUpdated) { | ||
this._detachChildPropertyUpdated(child); | ||
} | ||
}); | ||
if (slotData.multiple) { | ||
this._state[slot] = []; | ||
} else { | ||
this._state[slot] = null; | ||
} | ||
} | ||
_prepareForSlot(slot, child) { | ||
const slotData = this.constructor.getMetadata().getSlots()[slot]; | ||
child = this.constructor.getMetadata().constructor.validateSlotValue(child, slotData); | ||
if (child._attachChildPropertyUpdated) { | ||
this._attachChildPropertyUpdated(child, slotData); | ||
} | ||
return child; | ||
} | ||
static get observedAttributes() { | ||
const observedProps = this.getMetadata().getObservedProps(); | ||
const observedProps = this.getMetadata().getPublicPropsList(); | ||
return observedProps.map(camelToKebabCase); | ||
@@ -263,4 +278,4 @@ } | ||
_upgradeAllProperties() { | ||
const observedProps = this.constructor.getMetadata().getObservedProps(); | ||
observedProps.forEach(this._upgradeProperty.bind(this)); | ||
const allProps = this.constructor.getMetadata().getPropsList(); | ||
allProps.forEach(this._upgradeProperty.bind(this)); | ||
} | ||
@@ -271,5 +286,10 @@ | ||
if (!DefinitionsSet.has(tag)) { | ||
const definedLocally = DefinitionsSet.has(tag); | ||
const definedGlobally = customElements.get(tag); | ||
if (definedGlobally && !definedLocally) { | ||
console.warn(`Skipping definition of tag ${tag}, because it was already defined by another instance of ui5-webcomponents.`); // eslint-disable-line | ||
} else if (!definedGlobally) { | ||
this.generateAccessors(); | ||
DefinitionsSet.add(tag); | ||
this.generateAccessors(); | ||
window.customElements.define(tag, this); | ||
@@ -289,17 +309,7 @@ } | ||
_initializeState() { | ||
const StateClass = this.constructor.StateClass; | ||
this._state = new StateClass(this); | ||
const defaultState = this.constructor._getDefaultState(); | ||
this._state = Object.assign({}, defaultState); | ||
this._delegates = []; | ||
} | ||
static get StateClass() { | ||
if (!this.hasOwnProperty("_StateClass")) { // eslint-disable-line | ||
this._StateClass = class extends State {}; | ||
this._StateClass.generateAccessors(this.getMetadata()); | ||
} | ||
return this._StateClass; | ||
} | ||
static getMetadata() { | ||
@@ -342,8 +352,2 @@ let klass = this; // eslint-disable-line | ||
static calculateTemplateContext(state) { | ||
return { | ||
ctr: state, | ||
}; | ||
} | ||
_attachChildPropertyUpdated(child, propData) { | ||
@@ -401,3 +405,3 @@ const listenFor = propData.listenFor, | ||
/** | ||
* Asynchronously re-renders an already rendered control | ||
* Asynchronously re-renders an already rendered web component | ||
* @private | ||
@@ -428,3 +432,3 @@ */ | ||
delete this._invalidated; | ||
ControlRenderer.render(this); | ||
Renderer.render(this); | ||
@@ -481,6 +485,2 @@ // Safari requires that children get the slot attribute only after the slot tags have been rendered in the shadow DOM | ||
_getTemplateContext() { | ||
return TemplateContext.calculate(this); | ||
} | ||
getDomRef() { | ||
@@ -491,3 +491,4 @@ if (!this.shadowRoot || this.shadowRoot.children.length === 0) { | ||
return this._getRoot().children[0]; | ||
return this.shadowRoot.children.length === 1 | ||
? this.shadowRoot.children[0] : this.shadowRoot.children[1]; | ||
} | ||
@@ -499,6 +500,2 @@ | ||
_getRoot() { | ||
return this.shadowRoot.querySelector("[data-sap-ui-wc-root]"); | ||
} | ||
getFocusDomRef() { | ||
@@ -523,3 +520,3 @@ const domRef = this.getDomRef(); | ||
/** | ||
* Calls the event handler on the control for a native event | ||
* Calls the event handler on the web component for a native event | ||
* | ||
@@ -599,3 +596,3 @@ * @param event The event object | ||
} | ||
return acc.concat(curr.assignedElements({ flatten: true })); | ||
return acc.concat(curr.assignedNodes({ flatten: true }).filter(item => item instanceof HTMLElement)); | ||
}; | ||
@@ -608,3 +605,2 @@ | ||
* Used to generate the next auto-increment id for the current class | ||
* Note: do not call Control._nextID (static) but rather this.constructor._nextID (polymorphic) | ||
* @returns {string} | ||
@@ -646,2 +642,47 @@ * @private | ||
static _getDefaultState() { | ||
if (this._defaultState) { | ||
return this._defaultState; | ||
} | ||
const MetadataClass = this.getMetadata(); | ||
const defaultState = {}; | ||
// Initialize properties | ||
const props = MetadataClass.getProperties(); | ||
for (const propName in props) { // eslint-disable-line | ||
const propType = props[propName].type; | ||
const propDefaultValue = props[propName].defaultValue; | ||
if (propType === Boolean) { | ||
defaultState[propName] = false; | ||
if (propDefaultValue !== undefined) { | ||
console.warn("The 'defaultValue' metadata key is ignored for all booleans properties, they would be initialized with 'false' by default"); // eslint-disable-line | ||
} | ||
} else if (props[propName].multiple) { | ||
defaultState[propName] = []; | ||
} else if (propType === Object) { | ||
defaultState[propName] = "defaultValue" in props[propName] ? props[propName].defaultValue : {}; | ||
} else if (propType === String) { | ||
defaultState[propName] = propDefaultValue || ""; | ||
} else { | ||
defaultState[propName] = propDefaultValue; | ||
} | ||
} | ||
// Initialize slots | ||
const slots = MetadataClass.getSlots(); | ||
for (const slotName in slots) { // eslint-disable-line | ||
if (slots[slotName].multiple) { | ||
defaultState[slotName] = []; | ||
} else { | ||
defaultState[slotName] = null; | ||
} | ||
} | ||
this._defaultState = defaultState; | ||
return defaultState; | ||
} | ||
static generateAccessors() { | ||
@@ -663,6 +704,38 @@ const proto = this.prototype; | ||
get() { | ||
return this._state[prop]; | ||
if (this._state[prop] !== undefined) { | ||
return this._state[prop]; | ||
} | ||
const propDefaultValue = propData.defaultValue; | ||
if (propData.type === Boolean) { | ||
return false; | ||
} else if (propData.type === String) { // eslint-disable-line | ||
return propDefaultValue || ""; | ||
} else if (propData.multiple) { // eslint-disable-line | ||
return []; | ||
} else { | ||
return propDefaultValue; | ||
} | ||
}, | ||
set(value) { | ||
this._state[prop] = value; | ||
let isDifferent = false; | ||
value = this.constructor.getMetadata().constructor.validatePropertyValue(value, propData); | ||
const oldState = this._state[prop]; | ||
if (propData.deepEqual) { | ||
isDifferent = JSON.stringify(oldState) !== JSON.stringify(value); | ||
} else { | ||
isDifferent = oldState !== value; | ||
} | ||
if (isDifferent) { | ||
this._state[prop] = value; | ||
if (propData.nonVisual) { | ||
return; | ||
} | ||
this._invalidate(prop, value); | ||
this._propertyChange(prop, value); | ||
} | ||
}, | ||
@@ -674,3 +747,3 @@ }); | ||
const slots = this.getMetadata().getSlots(); | ||
for (const [slot] of Object.entries(slots)) { // eslint-disable-line | ||
for (const [slot, slotData] of Object.entries(slots)) { // eslint-disable-line | ||
if (nameCollidesWithNative(slot)) { | ||
@@ -682,3 +755,9 @@ throw new Error(`"${slot}" is not a valid property name. Use a name that does not collide with DOM APIs`); | ||
get() { | ||
return this._state[slot]; | ||
if (this._state[slot] !== undefined) { | ||
return this._state[slot]; | ||
} | ||
if (slotData.multiple) { | ||
return []; | ||
} | ||
return null; | ||
}, | ||
@@ -685,0 +764,0 @@ set() { |
import DataType from "./types/DataType.js"; | ||
import Function from "./types/Function.js"; | ||
@@ -21,9 +20,10 @@ class UI5ElementMetadata { | ||
getObservedProps() { | ||
const properties = this.getProperties(); | ||
const allProps = Object.keys(properties); | ||
const observedProps = allProps.filter(UI5ElementMetadata.isPublicProperty); | ||
return observedProps; | ||
getPropsList() { | ||
return Object.keys(this.getProperties()); | ||
} | ||
getPublicPropsList() { | ||
return this.getPropsList().filter(UI5ElementMetadata.isPublicProperty); | ||
} | ||
getSlots() { | ||
@@ -58,6 +58,2 @@ return this.metadata.slots || {}; | ||
static validateSlotValue(value, slotData) { | ||
const isMultiple = slotData.multiple; | ||
if (isMultiple) { | ||
return value.map(propValue => validateSingleSlot(propValue, slotData)); | ||
} | ||
return validateSingleSlot(value, slotData); | ||
@@ -84,5 +80,2 @@ } | ||
} | ||
if (propertyType === Function) { | ||
return typeof value === "function" ? value : undefined; | ||
} | ||
if (isDescendantOf(propertyType, DataType)) { | ||
@@ -93,3 +86,3 @@ return propertyType.isValid(value) ? value : propData.defaultValue; | ||
const validateSingleSlot = (value, propData) => { | ||
const validateSingleSlot = (value, slotData) => { | ||
if (value === null) { | ||
@@ -104,3 +97,3 @@ return value; | ||
if (isSlot) { | ||
return el.assignedElements({ flatten: true }); | ||
return el.assignedNodes({ flatten: true }).filter(item => item instanceof HTMLElement); | ||
} | ||
@@ -110,3 +103,3 @@ | ||
}; | ||
const propertyType = propData.type; | ||
const propertyType = slotData.type; | ||
@@ -116,3 +109,12 @@ const slottedNodes = getSlottedNodes(value); | ||
if (!(el instanceof propertyType)) { | ||
throw new Error(`${el} is not of type ${propertyType}`); | ||
const isHTMLElement = el instanceof HTMLElement; | ||
const tagName = isHTMLElement && el.tagName.toLowerCase(); | ||
const isCustomElement = isHTMLElement && tagName.includes("-"); | ||
if (isCustomElement) { | ||
window.customElements.whenDefined(tagName).then(() => { | ||
if (!(el instanceof propertyType)) { | ||
throw new Error(`${el} is not of type ${propertyType}`); | ||
} | ||
}); | ||
} | ||
} | ||
@@ -119,0 +121,0 @@ }); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
682441
97
12321
+ Added@ui5/webcomponents-core@0.12.0(transitive)
- Removed@ui5/webcomponents-core@0.11.1(transitive)