@ui5/webcomponents-base
Advanced tools
Comparing version 0.0.0-306572ffa to 0.0.0-31ad69296
@@ -1,40 +0,43 @@ | ||
import { registerThemeProperties } from "./dist/AssetRegistry.js"; | ||
import { registerThemePropertiesLoader } from "./dist/AssetRegistry.js"; | ||
import EventProvider from "./dist/EventProvider.js"; | ||
// ESM bundle targets Edge + browsers with native support | ||
import "./dist/features/browsersupport/Edge.js"; | ||
// ESM bundle targets browsers with native support | ||
import "./dist/features/OpenUI5Support.js"; | ||
// Test components | ||
import "./dist/test-resources/elements/Generic.js"; | ||
import "./dist/test-resources/elements/NoShadowDOM.js"; | ||
import "./dist/test-resources/elements/Parent.js"; | ||
import "./dist/test-resources/elements/Child.js"; | ||
import "./dist/test-resources/elements/WithStaticArea.js"; | ||
import "./dist/test-resources/elements/GenericExt.js"; | ||
import "./test/elements/Accessor.js"; | ||
import "./test/elements/Generic.js"; | ||
import "./test/elements/NoShadowDOM.js"; | ||
import "./test/elements/Parent.js"; | ||
import "./test/elements/Child.js"; | ||
import "./test/elements/WithStaticArea.js"; | ||
import "./test/elements/WithComplexTemplate.js"; | ||
import "./test/elements/GenericExt.js"; | ||
// Test themes - CSS Vars for the sap_fiori_3, sap_fiori_3_dark, sap_belize and sap_belize_hcb themes | ||
import "./dist/test-resources/assets/Themes.js"; | ||
import "./test/assets/Themes.js"; | ||
// used in test pages | ||
import RenderScheduler from "./dist/RenderScheduler.js"; | ||
window.RenderScheduler = RenderScheduler; | ||
import { isIE } from "./dist/Device.js"; | ||
window.isIE = isIE; // attached to the window object for testing purposes | ||
import { renderFinished } from "./dist/Render.js"; | ||
// used for tests - to register a custom theme | ||
window.registerThemeProperties = registerThemeProperties; | ||
window.registerThemePropertiesLoader = registerThemePropertiesLoader; | ||
// i18n | ||
import "./dist/features/PropertiesFormatSupport.js"; | ||
import { registerI18nBundle, fetchI18nBundle, getI18nBundle } from "./dist/i18nBundle.js"; | ||
import { registerI18nLoader, getI18nBundle } from "./dist/i18nBundle.js"; | ||
import parseProperties from "./dist/PropertiesFileFormat.js"; | ||
// Note: keep in sync with rollup.config value for IIFE | ||
import { getAnimationMode } from "./dist/config/AnimationMode.js"; | ||
import { getLanguage } from "./dist/config/Language.js"; | ||
import { getLanguage, setLanguage } from "./dist/config/Language.js"; | ||
import { getCalendarType } from "./dist/config/CalendarType.js"; | ||
import { getTheme, setTheme } from "./dist/config/Theme.js"; | ||
import { getThemeRoot, setThemeRoot } from "./dist/config/ThemeRoot.js"; | ||
import { getNoConflict, setNoConflict } from "./dist/config/NoConflict.js"; | ||
import { getRTL } from "./dist/config/RTL.js"; | ||
import { getFirstDayOfWeek } from "./dist/config/FormatSettings.js"; | ||
import { getRegisteredNames as getIconNames } from "./dist/SVGIconRegistry.js" | ||
import { getFirstDayOfWeek, getLegacyDateCalendarCustomizing } from "./dist/config/FormatSettings.js"; | ||
import { _getRegisteredNames as getIconNames } from "./dist/asset-registries/Icons.js" | ||
import applyDirection from "./dist/locale/applyDirection.js"; | ||
import { getCurrentRuntimeIndex } from "./dist/Runtimes.js"; | ||
import LegacyDateFormats from "./dist/features/LegacyDateFormats.js"; | ||
window["sap-ui-webcomponents-bundle"] = { | ||
@@ -44,3 +47,6 @@ configuration : { | ||
getLanguage, | ||
setLanguage, | ||
getTheme, | ||
getThemeRoot, | ||
setThemeRoot, | ||
setTheme, | ||
@@ -50,9 +56,13 @@ getNoConflict, | ||
getCalendarType, | ||
getRTL, | ||
getFirstDayOfWeek, | ||
getLegacyDateCalendarCustomizing, | ||
}, | ||
getCurrentRuntimeIndex, | ||
getIconNames, | ||
registerI18nBundle, | ||
fetchI18nBundle, | ||
parseProperties, | ||
registerI18nLoader, | ||
getI18nBundle, | ||
renderFinished, | ||
applyDirection, | ||
EventProvider, | ||
}; |
import AnimationQueue from "./AnimationQueue.js"; | ||
import animationConfig from "./config.js"; | ||
export default ({ | ||
beforeStart = animationConfig.identity, | ||
duration = animationConfig.defaultDuration, | ||
element = animationConfig.element, | ||
progress: progressCallback = animationConfig.identity, | ||
}) => { | ||
let start = null; | ||
let stopped = false; | ||
let animationFrame; | ||
let stop; | ||
let animate; | ||
const promise = new Promise((resolve, reject) => { | ||
animate = timestamp => { | ||
start = start || timestamp; | ||
const timeElapsed = timestamp - start; | ||
const remaining = duration - timeElapsed; | ||
if (timeElapsed <= duration) { | ||
const progress = 1 - remaining / duration; // easing formula (currently linear) | ||
progressCallback(progress); | ||
animationFrame = !stopped && requestAnimationFrame(animate); | ||
} else { | ||
progressCallback(1); | ||
resolve(); | ||
} | ||
}; | ||
stop = () => { | ||
stopped = true; | ||
cancelAnimationFrame(animationFrame); | ||
reject(new Error("animation stopped")); | ||
}; | ||
}).catch(oReason => oReason); | ||
AnimationQueue.push(element, () => { | ||
beforeStart(); | ||
requestAnimationFrame(animate); | ||
return new Promise(resolve => { | ||
promise.then(() => resolve()); | ||
}); | ||
}); | ||
return { | ||
promise: () => promise, | ||
stop: () => stop, | ||
}; | ||
const animate = (options) => { | ||
let start = null; | ||
let stopped = false; | ||
let animationFrame; | ||
let stop; | ||
let advanceAnimation; | ||
const promise = new Promise((resolve, reject) => { | ||
advanceAnimation = timestamp => { | ||
start = start || timestamp; | ||
const timeElapsed = timestamp - start; | ||
const remaining = options.duration - timeElapsed; | ||
if (timeElapsed <= options.duration) { | ||
const currentAdvance = 1 - remaining / options.duration; // easing formula (currently linear) | ||
options.advance(currentAdvance); | ||
if (!stopped) { | ||
animationFrame = requestAnimationFrame(advanceAnimation); | ||
} | ||
} | ||
else { | ||
options.advance(1); | ||
resolve(); | ||
} | ||
}; | ||
stop = () => { | ||
stopped = true; | ||
cancelAnimationFrame(animationFrame); | ||
reject(new Error("animation stopped")); | ||
}; | ||
}).catch((reason) => reason); | ||
AnimationQueue.push(options.element, () => { | ||
if (typeof options.beforeStart === "function") { | ||
options.beforeStart(); | ||
} | ||
requestAnimationFrame(advanceAnimation); | ||
return new Promise(resolve => { | ||
promise.then(() => resolve()); | ||
}); | ||
}); | ||
return { | ||
promise: () => promise, | ||
stop: () => stop, | ||
}; | ||
}; | ||
const duration = 400; | ||
export { duration }; | ||
export default animate; | ||
//# sourceMappingURL=animate.js.map |
const tasks = new WeakMap(); | ||
class AnimationQueue { | ||
static get tasks() { | ||
return tasks; | ||
} | ||
static enqueue(element, task) { | ||
if (!tasks.has(element)) { | ||
tasks.set(element, []); | ||
} | ||
tasks.get(element).push(task); | ||
} | ||
static run(element, task) { | ||
if (!tasks.has(element)) { | ||
tasks.set(element, []); | ||
} | ||
return task().then(() => { | ||
const elementTasks = tasks.get(element); | ||
if (elementTasks.length > 0) { | ||
return AnimationQueue.run(element, elementTasks.shift()); | ||
} | ||
tasks.delete(element); | ||
}); | ||
} | ||
static push(element, task) { | ||
const elementTasks = tasks.get(element); | ||
if (elementTasks) { | ||
AnimationQueue.enqueue(element, task); | ||
} else { | ||
AnimationQueue.run(element, task); | ||
} | ||
} | ||
static get tasks() { | ||
return tasks; | ||
} | ||
static enqueue(element, task) { | ||
if (!tasks.has(element)) { | ||
tasks.set(element, []); | ||
} | ||
tasks.get(element).push(task); | ||
} | ||
static run(element, task) { | ||
if (!tasks.has(element)) { | ||
tasks.set(element, []); | ||
} | ||
return task().then(() => { | ||
const elementTasks = tasks.get(element); | ||
if (elementTasks.length > 0) { | ||
return AnimationQueue.run(element, elementTasks.shift()); | ||
} | ||
tasks.delete(element); | ||
}); | ||
} | ||
static push(element, task) { | ||
const elementTasks = tasks.get(element); | ||
if (elementTasks) { | ||
AnimationQueue.enqueue(element, task); | ||
} | ||
else { | ||
AnimationQueue.run(element, task); | ||
} | ||
} | ||
} | ||
export default AnimationQueue; | ||
//# sourceMappingURL=AnimationQueue.js.map |
@@ -1,28 +0,19 @@ | ||
import animate from "./animate.js"; | ||
import animationConfig from "./config.js"; | ||
export default ({ | ||
element = animationConfig.element, | ||
duration = animationConfig.duration, | ||
progress: progressCallback = animationConfig.identity, | ||
dx = 0, | ||
dy = 0, | ||
}) => { | ||
let scrollLeft; | ||
let scrollTop; | ||
return animate({ | ||
beforeStart: () => { | ||
scrollLeft = element.scrollLeft; | ||
scrollTop = element.scrollTop; | ||
}, | ||
duration, | ||
element, | ||
progress: progress => { | ||
progressCallback(progress); | ||
element.scrollLeft = scrollLeft + (progress * dx); // easing - linear | ||
element.scrollTop = scrollTop + (progress * dy); // easing - linear | ||
}, | ||
}); | ||
import animate, { duration } from "./animate.js"; | ||
const scroll = (element, dx, dy) => { | ||
let scrollLeft; | ||
let scrollTop; | ||
return animate({ | ||
beforeStart: () => { | ||
scrollLeft = element.scrollLeft; | ||
scrollTop = element.scrollTop; | ||
}, | ||
duration, | ||
element, | ||
advance: progress => { | ||
element.scrollLeft = scrollLeft + (progress * dx); // easing - linear | ||
element.scrollTop = scrollTop + (progress * dy); // easing - linear | ||
}, | ||
}); | ||
}; | ||
export default scroll; | ||
//# sourceMappingURL=scroll.js.map |
@@ -1,79 +0,54 @@ | ||
import animationConfig from "./config.js"; | ||
import animate from "./animate.js"; | ||
export default ({ | ||
element = animationConfig.element, | ||
duration = animationConfig.defaultDuration, | ||
progress: progressCallback = animationConfig.identity, | ||
}) => { | ||
let computedStyles, | ||
paddingTop, | ||
paddingBottom, | ||
marginTop, | ||
marginBottom, | ||
height; | ||
let storedOverflow, | ||
storedPaddingTop, | ||
storedPaddingBottom, | ||
storedMarginTop, | ||
storedMarginBottom, | ||
storedHeight; | ||
const animation = animate({ | ||
beforeStart: () => { | ||
// Show the element to measure its properties | ||
element.style.display = "block"; | ||
// Get Computed styles | ||
computedStyles = getComputedStyle(element); | ||
paddingTop = parseFloat(computedStyles.paddingTop); | ||
paddingBottom = parseFloat(computedStyles.paddingBottom); | ||
marginTop = parseFloat(computedStyles.marginTop); | ||
marginBottom = parseFloat(computedStyles.marginBottom); | ||
height = parseFloat(computedStyles.height); | ||
// Store inline styles | ||
storedOverflow = element.style.overflow; | ||
storedPaddingTop = element.style.paddingTop; | ||
storedPaddingBottom = element.style.paddingBottom; | ||
storedMarginTop = element.style.marginTop; | ||
storedMarginBottom = element.style.marginBottom; | ||
storedHeight = element.style.height; | ||
element.style.overflow = "hidden"; | ||
element.style.paddingTop = 0; | ||
element.style.paddingBottom = 0; | ||
element.style.marginTop = 0; | ||
element.style.marginBottom = 0; | ||
element.style.height = 0; | ||
}, | ||
duration, | ||
element, | ||
progress(progress) { | ||
progressCallback(progress); | ||
// WORKAROUND | ||
element.style.display = "block"; | ||
// END OF WORKAROUND | ||
/* eslint-disable */ | ||
element.style.paddingTop = 0 + (paddingTop * progress) + "px"; | ||
element.style.paddingBottom = 0 + (paddingBottom * progress) + "px"; | ||
element.style.marginTop = 0 + (marginTop * progress) + "px"; | ||
element.style.marginBottom = 0 + (marginBottom * progress) + "px"; | ||
element.style.height = 0 + (height * progress) + "px"; | ||
/* eslint-enable */ | ||
}, | ||
}); | ||
animation.promise().then(() => { | ||
element.style.overflow = storedOverflow; | ||
element.style.paddingTop = storedPaddingTop; | ||
element.style.paddingBottom = storedPaddingBottom; | ||
element.style.marginTop = storedMarginTop; | ||
element.style.marginBottom = storedMarginBottom; | ||
element.style.height = storedHeight; | ||
}); | ||
return animation; | ||
import animate, { duration } from "./animate.js"; | ||
const slideDown = (element) => { | ||
let computedStyles, paddingTop, paddingBottom, marginTop, marginBottom, height; | ||
let storedOverflow, storedPaddingTop, storedPaddingBottom, storedMarginTop, storedMarginBottom, storedHeight; | ||
const animation = animate({ | ||
beforeStart: () => { | ||
// Show the element to measure its properties | ||
element.style.display = "block"; | ||
// Get Computed styles | ||
computedStyles = getComputedStyle(element); | ||
paddingTop = parseFloat(computedStyles.paddingTop); | ||
paddingBottom = parseFloat(computedStyles.paddingBottom); | ||
marginTop = parseFloat(computedStyles.marginTop); | ||
marginBottom = parseFloat(computedStyles.marginBottom); | ||
height = parseFloat(computedStyles.height); | ||
// Store inline styles | ||
storedOverflow = element.style.overflow; | ||
storedPaddingTop = element.style.paddingTop; | ||
storedPaddingBottom = element.style.paddingBottom; | ||
storedMarginTop = element.style.marginTop; | ||
storedMarginBottom = element.style.marginBottom; | ||
storedHeight = element.style.height; | ||
element.style.overflow = "hidden"; | ||
element.style.paddingTop = "0"; | ||
element.style.paddingBottom = "0"; | ||
element.style.marginTop = "0"; | ||
element.style.marginBottom = "0"; | ||
element.style.height = "0"; | ||
}, | ||
duration, | ||
element, | ||
advance: progress => { | ||
// WORKAROUND | ||
element.style.display = "block"; | ||
// END OF WORKAROUND | ||
element.style.paddingTop = `${(paddingTop * progress)}px`; | ||
element.style.paddingBottom = `${(paddingBottom * progress)}px`; | ||
element.style.marginTop = `${(marginTop * progress)}px`; | ||
element.style.marginBottom = `${(marginBottom * progress)}px`; | ||
element.style.height = `${(height * progress)}px`; | ||
}, | ||
}); | ||
animation.promise().then(() => { | ||
element.style.overflow = storedOverflow; | ||
element.style.paddingTop = storedPaddingTop; | ||
element.style.paddingBottom = storedPaddingBottom; | ||
element.style.marginTop = storedMarginTop; | ||
element.style.marginBottom = storedMarginBottom; | ||
element.style.height = storedHeight; | ||
}); | ||
return animation; | ||
}; | ||
export default slideDown; | ||
//# sourceMappingURL=slideDown.js.map |
@@ -1,71 +0,50 @@ | ||
import animationConfig from "./config.js"; | ||
import animate from "./animate.js"; | ||
export default ({ | ||
element = animationConfig.element, | ||
duration = animationConfig.defaultDuration, | ||
progress: progressCallback = animationConfig.identity, | ||
}) => { | ||
// Get Computed styles | ||
let computedStyles, | ||
paddingTop, | ||
paddingBottom, | ||
marginTop, | ||
marginBottom, | ||
height; | ||
// Store inline styles | ||
let storedOverflow, | ||
storedPaddingTop, | ||
storedPaddingBottom, | ||
storedMarginTop, | ||
storedMarginBottom, | ||
storedHeight; | ||
const animation = animate({ | ||
beforeStart: () => { | ||
// Get Computed styles | ||
computedStyles = getComputedStyle(element); | ||
paddingTop = parseFloat(computedStyles.paddingTop); | ||
paddingBottom = parseFloat(computedStyles.paddingBottom); | ||
marginTop = parseFloat(computedStyles.marginTop); | ||
marginBottom = parseFloat(computedStyles.marginBottom); | ||
height = parseFloat(computedStyles.height); | ||
// Store inline styles | ||
storedOverflow = element.style.overflow; | ||
storedPaddingTop = element.style.paddingTop; | ||
storedPaddingBottom = element.style.paddingBottom; | ||
storedMarginTop = element.style.marginTop; | ||
storedMarginBottom = element.style.marginBottom; | ||
storedHeight = element.style.height; | ||
element.style.overflow = "hidden"; | ||
}, | ||
duration, | ||
element, | ||
progress(progress) { | ||
progressCallback(progress); | ||
element.style.paddingTop = `${paddingTop - (paddingTop * progress)}px`; | ||
element.style.paddingBottom = `${paddingBottom - (paddingBottom * progress)}px`; | ||
element.style.marginTop = `${marginTop - (marginTop * progress)}px`; | ||
element.style.marginBottom = `${marginBottom - (marginBottom * progress)}px`; | ||
element.style.height = `${height - (height * progress)}px`; | ||
}, | ||
}); | ||
animation.promise().then(oReason => { | ||
if (!(oReason instanceof Error)) { | ||
element.style.overflow = storedOverflow; | ||
element.style.paddingTop = storedPaddingTop; | ||
element.style.paddingBottom = storedPaddingBottom; | ||
element.style.marginTop = storedMarginTop; | ||
element.style.marginBottom = storedMarginBottom; | ||
element.style.height = storedHeight; | ||
element.style.display = "none"; | ||
} | ||
}); | ||
return animation; | ||
import animate, { duration } from "./animate.js"; | ||
const slideUp = (element) => { | ||
// Get Computed styles | ||
let computedStyles, paddingTop, paddingBottom, marginTop, marginBottom, height; | ||
// Store inline styles | ||
let storedOverflow, storedPaddingTop, storedPaddingBottom, storedMarginTop, storedMarginBottom, storedHeight; | ||
const animation = animate({ | ||
beforeStart: () => { | ||
// Get Computed styles | ||
const el = element; | ||
computedStyles = getComputedStyle(el); | ||
paddingTop = parseFloat(computedStyles.paddingTop); | ||
paddingBottom = parseFloat(computedStyles.paddingBottom); | ||
marginTop = parseFloat(computedStyles.marginTop); | ||
marginBottom = parseFloat(computedStyles.marginBottom); | ||
height = parseFloat(computedStyles.height); | ||
// Store inline styles | ||
storedOverflow = el.style.overflow; | ||
storedPaddingTop = el.style.paddingTop; | ||
storedPaddingBottom = el.style.paddingBottom; | ||
storedMarginTop = el.style.marginTop; | ||
storedMarginBottom = el.style.marginBottom; | ||
storedHeight = el.style.height; | ||
el.style.overflow = "hidden"; | ||
}, | ||
duration, | ||
element, | ||
advance: progress => { | ||
element.style.paddingTop = `${paddingTop - (paddingTop * progress)}px`; | ||
element.style.paddingBottom = `${paddingBottom - (paddingBottom * progress)}px`; | ||
element.style.marginTop = `${marginTop - (marginTop * progress)}px`; | ||
element.style.marginBottom = `${marginBottom - (marginBottom * progress)}px`; | ||
element.style.height = `${height - (height * progress)}px`; | ||
}, | ||
}); | ||
animation.promise().then(reason => { | ||
if (!(reason instanceof Error)) { | ||
element.style.overflow = storedOverflow; | ||
element.style.paddingTop = storedPaddingTop; | ||
element.style.paddingBottom = storedPaddingBottom; | ||
element.style.marginTop = storedMarginTop; | ||
element.style.marginBottom = storedMarginBottom; | ||
element.style.height = storedHeight; | ||
element.style.display = "none"; | ||
} | ||
}); | ||
return animation; | ||
}; | ||
export default slideUp; | ||
//# sourceMappingURL=slideUp.js.map |
@@ -1,41 +0,54 @@ | ||
import { getFeature } from "../FeaturesRegistry.js"; | ||
import getLocale from "../locale/getLocale.js"; | ||
import { attachLanguageChange } from "../locale/languageChange.js"; | ||
import { fetchTextOnce } from "../util/FetchHelper.js"; | ||
import normalizeLocale from "../locale/normalizeLocale.js"; | ||
import nextFallbackLocale from "../locale/nextFallbackLocale.js"; | ||
import { DEFAULT_LANGUAGE } from "../generated/AssetParameters.js"; | ||
import { getFetchDefaultLanguage } from "../config/Language.js"; | ||
// contains package names for which the warning has been shown | ||
const warningShown = new Set(); | ||
const reportedErrors = new Set(); | ||
const bundleData = new Map(); | ||
const bundleURLs = new Map(); | ||
const bundlePromises = new Map(); | ||
const loaders = new Map(); | ||
/** | ||
* Sets a map with texts and ID the are related to. | ||
* @param {string} packageName package ID that the i18n bundle will be related to | ||
* @param {Object} data an object with string locales as keys and text translataions as values | ||
* Registers i18n loader function for given package and locale. | ||
* | ||
* @public | ||
* @param {string} packageName for which package this loader can fetch data | ||
* @param {string} localeId locale that this loader can handle | ||
* @param {function} loader async function that will be passed a localeId and should return a JSON object | ||
*/ | ||
const setI18nBundleData = (packageName, data) => { | ||
bundleData.set(packageName, data); | ||
const registerI18nLoader = (packageName, localeId, loader) => { | ||
// register loader by key | ||
const bundleKey = `${packageName}/${localeId}`; | ||
loaders.set(bundleKey, loader); | ||
}; | ||
const getI18nBundleData = packageName => { | ||
return bundleData.get(packageName); | ||
const _setI18nBundleData = (packageName, data) => { | ||
bundleData.set(packageName, data); | ||
}; | ||
/** | ||
* Registers a map of locale/url information, to be used by the <code>fetchI18nBundle</code> method. | ||
* Note: In order to be able to register ".properties" files, you must import the following module: | ||
* import "@ui5/webcomponents-base/dist/features/PropertiesFormatSupport.js"; | ||
* | ||
* @param {string} packageName package ID that the i18n bundle will be related to | ||
* @param {Object} bundle an object with string locales as keys and the URLs (in .json or .properties format - see the note above) where the corresponding locale can be fetched from, f.e {"en": "path/en.json", ...} | ||
* | ||
* @public | ||
*/ | ||
const registerI18nBundle = (packageName, bundle) => { | ||
const oldBundle = bundleURLs.get(packageName) || {}; | ||
bundleURLs.set(packageName, Object.assign({}, oldBundle, bundle)); | ||
const getI18nBundleData = (packageName) => { | ||
return bundleData.get(packageName); | ||
}; | ||
const _hasLoader = (packageName, localeId) => { | ||
const bundleKey = `${packageName}/${localeId}`; | ||
return loaders.has(bundleKey); | ||
}; | ||
// load bundle over the network once | ||
const _loadMessageBundleOnce = (packageName, localeId) => { | ||
const bundleKey = `${packageName}/${localeId}`; | ||
const loadMessageBundle = loaders.get(bundleKey); | ||
if (loadMessageBundle && !bundlePromises.get(bundleKey)) { | ||
bundlePromises.set(bundleKey, loadMessageBundle(localeId)); | ||
} | ||
return bundlePromises.get(bundleKey); // Investigate if i18n loader exists and this won't return undefined. | ||
}; | ||
const _showAssetsWarningOnce = (packageName) => { | ||
if (!warningShown.has(packageName)) { | ||
console.warn(`[${packageName}]: Message bundle assets are not configured. Falling back to English texts.`, /* eslint-disable-line */ ` Add \`import "${packageName}/dist/Assets.js"\` in your bundle and make sure your build tool supports dynamic imports and JSON imports. See section "Assets" in the documentation for more information.`); /* eslint-disable-line */ | ||
warningShown.add(packageName); | ||
} | ||
}; | ||
const useFallbackBundle = (packageName, localeId) => { | ||
return localeId !== DEFAULT_LANGUAGE && !_hasLoader(packageName, localeId); | ||
}; | ||
/** | ||
@@ -45,64 +58,46 @@ * This method preforms the asynchronous task of fetching the actual text resources. It will fetch | ||
* It should be fully finished before the i18nBundle class is created in the webcomponents. | ||
* This method uses the bundle URLs that are populated by the <code>registerI18nBundle</code> method. | ||
* To simplify the usage, the synchronization of both methods happens internally for the same <code>bundleId</code> | ||
* This method uses the bundle URLs that are populated by the `registerI18nBundle` method. | ||
* To simplify the usage, the synchronization of both methods happens internally for the same `bundleId` | ||
* @param {packageName} packageName the NPM package name | ||
* @public | ||
*/ | ||
const fetchI18nBundle = async packageName => { | ||
const bundlesForPackage = bundleURLs.get(packageName); | ||
if (!bundlesForPackage) { | ||
console.warn(`Message bundle assets are not configured. Falling back to English texts.`, /* eslint-disable-line */ | ||
` You need to import ${packageName}/dist/Assets.js with a build tool that supports JSON imports.`); /* eslint-disable-line */ | ||
return; | ||
} | ||
const language = getLocale().getLanguage(); | ||
const region = getLocale().getRegion(); | ||
let localeId = normalizeLocale(language + (region ? `-${region}` : ``)); | ||
while (localeId !== DEFAULT_LANGUAGE && !bundlesForPackage[localeId]) { | ||
localeId = nextFallbackLocale(localeId); | ||
} | ||
if (!bundlesForPackage[localeId]) { | ||
setI18nBundleData(packageName, null); // reset for the default language (if data was set for a previous language) | ||
return; | ||
} | ||
const bundleURL = bundlesForPackage[localeId]; | ||
if (typeof bundleURL === "object") { // inlined from build | ||
setI18nBundleData(packageName, bundleURL); | ||
return; | ||
} | ||
const content = await fetchTextOnce(bundleURL); | ||
let parser; | ||
if (content.startsWith("{")) { | ||
parser = JSON.parse; | ||
} else { | ||
const PropertiesFormatSupport = getFeature("PropertiesFormatSupport"); | ||
if (!PropertiesFormatSupport) { | ||
throw new Error(`In order to support .properties files, please: import "@ui5/webcomponents-base/dist/features/PropertiesFormatSupport.js";`); | ||
} | ||
parser = PropertiesFormatSupport.parser; | ||
} | ||
const data = parser(content); | ||
setI18nBundleData(packageName, data); | ||
const fetchI18nBundle = async (packageName) => { | ||
const language = getLocale().getLanguage(); | ||
const region = getLocale().getRegion(); | ||
const variant = getLocale().getVariant(); | ||
let localeId = language + (region ? `-${region}` : ``) + (variant ? `-${variant}` : ``); | ||
if (useFallbackBundle(packageName, localeId)) { | ||
localeId = normalizeLocale(localeId); | ||
while (useFallbackBundle(packageName, localeId)) { | ||
localeId = nextFallbackLocale(localeId); | ||
} | ||
} | ||
// use default language unless configured to always fetch it from the network | ||
const fetchDefaultLanguage = getFetchDefaultLanguage(); | ||
if (localeId === DEFAULT_LANGUAGE && !fetchDefaultLanguage) { | ||
_setI18nBundleData(packageName, null); // reset for the default language (if data was set for a previous language) | ||
return; | ||
} | ||
if (!_hasLoader(packageName, localeId)) { | ||
_showAssetsWarningOnce(packageName); | ||
return; | ||
} | ||
try { | ||
const data = await _loadMessageBundleOnce(packageName, localeId); | ||
_setI18nBundleData(packageName, data); | ||
} | ||
catch (error) { | ||
const e = error; | ||
if (!reportedErrors.has(e.message)) { | ||
reportedErrors.add(e.message); | ||
console.error(e.message); /* eslint-disable-line */ | ||
} | ||
} | ||
}; | ||
// When the language changes dynamically (the user calls setLanguage), re-fetch all previously fetched bundles | ||
attachLanguageChange(() => { | ||
const allPackages = [...bundleData.keys()]; | ||
return Promise.all(allPackages.map(fetchI18nBundle)); | ||
attachLanguageChange((lang /* eslint-disable-line */) => { | ||
const allPackages = [...bundleData.keys()]; | ||
return Promise.all(allPackages.map(fetchI18nBundle)); | ||
}); | ||
export { | ||
fetchI18nBundle, | ||
registerI18nBundle, | ||
setI18nBundleData, | ||
getI18nBundleData, | ||
}; | ||
export { registerI18nLoader, fetchI18nBundle, getI18nBundleData, }; | ||
//# sourceMappingURL=i18n.js.map |
@@ -1,31 +0,136 @@ | ||
import { registerIcon, registerCollectionPromise } from "../SVGIconRegistry.js"; | ||
import { fetchJsonOnce } from "../util/FetchHelper.js"; | ||
const registerIconBundle = async (collectionName, bundleData) => { | ||
let resolveFn; | ||
const collectionFetched = new Promise(resolve => { | ||
resolveFn = resolve; | ||
}); | ||
registerCollectionPromise(collectionName, collectionFetched); | ||
if (typeof bundleData !== "object") { // not inlined from build -> fetch it | ||
bundleData = await fetchJsonOnce(bundleData); | ||
} | ||
fillRegistry(bundleData); | ||
resolveFn(); | ||
import getSharedResource from "../getSharedResource.js"; | ||
import { getIconCollectionByAlias } from "./util/IconCollectionsAlias.js"; | ||
import { registerIconCollectionForTheme } from "./util/IconCollectionsByTheme.js"; | ||
import getEffectiveIconCollection from "./util/getIconCollectionByTheme.js"; | ||
import { getI18nBundle } from "../i18nBundle.js"; | ||
const DEFAULT_THEME_FAMILY = "legacy"; // includes sap_belize_* and sap_fiori_* | ||
const loaders = new Map(); | ||
const registry = getSharedResource("SVGIcons.registry", new Map()); | ||
const iconCollectionPromises = getSharedResource("SVGIcons.promises", new Map()); | ||
const ICON_NOT_FOUND = "ICON_NOT_FOUND"; | ||
const registerIconLoader = (collectionName, loader) => { | ||
loaders.set(collectionName, loader); | ||
}; | ||
const fillRegistry = bundleData => { | ||
Object.keys(bundleData.data).forEach(iconName => { | ||
const iconData = bundleData.data[iconName]; | ||
registerIcon(iconName, { | ||
pathData: iconData.path, | ||
ltr: iconData.ltr, | ||
accData: iconData.acc, | ||
collection: bundleData.collection, | ||
}); | ||
}); | ||
const _loadIconCollectionOnce = async (collectionName) => { | ||
if (!iconCollectionPromises.has(collectionName)) { | ||
if (!loaders.has(collectionName)) { | ||
throw new Error(`No loader registered for the ${collectionName} icons collection. Probably you forgot to import the "AllIcons.js" module for the respective package.`); | ||
} | ||
const loadIcons = loaders.get(collectionName); | ||
iconCollectionPromises.set(collectionName, loadIcons(collectionName)); | ||
} | ||
return iconCollectionPromises.get(collectionName); | ||
}; | ||
export { registerIconBundle }; // eslint-disable-line | ||
const _fillRegistry = (bundleData) => { | ||
Object.keys(bundleData.data).forEach(iconName => { | ||
const iconData = bundleData.data[iconName]; | ||
registerIcon(iconName, { | ||
pathData: (iconData.path || iconData.paths), | ||
ltr: iconData.ltr, | ||
accData: iconData.acc, | ||
collection: bundleData.collection, | ||
packageName: bundleData.packageName, | ||
}); | ||
}); | ||
}; | ||
// set | ||
const registerIcon = (name, iconData) => { | ||
const key = `${iconData.collection}/${name}`; | ||
registry.set(key, { | ||
pathData: iconData.pathData, | ||
ltr: iconData.ltr, | ||
accData: iconData.accData, | ||
packageName: iconData.packageName, | ||
customTemplate: iconData.customTemplate, | ||
viewBox: iconData.viewBox, | ||
collection: iconData.collection, | ||
}); | ||
}; | ||
/** | ||
* Processes the full icon name and splits it into - "name", "collection". | ||
* - removes legacy protocol ("sap-icon://") | ||
* - resolves aliases (f.e "SAP-icons-TNT/actor" => "tnt/actor") | ||
* | ||
* @param { string } name | ||
* @return { object } | ||
*/ | ||
const processName = (name) => { | ||
// silently support ui5-compatible URIs | ||
if (name.startsWith("sap-icon://")) { | ||
name = name.replace("sap-icon://", ""); | ||
} | ||
let collection; | ||
[name, collection] = name.split("/").reverse(); | ||
name = name.replace("icon-", ""); | ||
if (collection) { | ||
collection = getIconCollectionByAlias(collection); | ||
} | ||
return { name, collection }; | ||
}; | ||
const getIconDataSync = (iconName) => { | ||
const { name, collection } = processName(iconName); | ||
return getRegisteredIconData(collection, name); | ||
}; | ||
const getIconData = async (iconName) => { | ||
const { name, collection } = processName(iconName); | ||
let iconData = ICON_NOT_FOUND; | ||
try { | ||
iconData = (await _loadIconCollectionOnce(getEffectiveIconCollection(collection))); | ||
} | ||
catch (error) { | ||
const e = error; | ||
console.error(e.message); /* eslint-disable-line */ | ||
} | ||
if (iconData === ICON_NOT_FOUND) { | ||
return iconData; | ||
} | ||
const registeredIconData = getRegisteredIconData(collection, name); | ||
if (registeredIconData) { | ||
return registeredIconData; | ||
} | ||
// not filled by another await. many getters will await on the same loader, but fill only once | ||
if (Array.isArray(iconData)) { | ||
iconData.forEach(data => { | ||
_fillRegistry(data); | ||
registerIconCollectionForTheme(collection, { [data.themeFamily || DEFAULT_THEME_FAMILY]: data.collection }); | ||
}); | ||
} | ||
else { | ||
_fillRegistry(iconData); | ||
} | ||
return getRegisteredIconData(collection, name); | ||
}; | ||
const getRegisteredIconData = (collection, name) => { | ||
const registryKey = `${getEffectiveIconCollection(collection)}/${name}`; | ||
return registry.get(registryKey); | ||
}; | ||
/** | ||
* Returns the accessible name for the given icon, | ||
* or undefined if accessible name is not present. | ||
* | ||
* @param { string } name | ||
* @return { Promise } | ||
*/ | ||
const getIconAccessibleName = async (name) => { | ||
if (!name) { | ||
return; | ||
} | ||
let iconData = getIconDataSync(name); | ||
if (!iconData) { | ||
iconData = await getIconData(name); | ||
} | ||
if (iconData && iconData !== ICON_NOT_FOUND && iconData.accData) { | ||
const i18nBundle = await getI18nBundle(iconData.packageName); | ||
return i18nBundle.getText(iconData.accData); | ||
} | ||
}; | ||
// test page usage only | ||
const _getRegisteredNames = async () => { | ||
// fetch one icon of each collection to trigger the bundle load | ||
await getIconData("edit"); | ||
await getIconData("tnt/arrow"); | ||
await getIconData("business-suite/3d"); | ||
return Array.from(registry.keys()); | ||
}; | ||
export { registerIconLoader, getIconData, getIconDataSync, getIconAccessibleName, registerIcon, _getRegisteredNames, }; | ||
//# sourceMappingURL=Icons.js.map |
@@ -1,125 +0,136 @@ | ||
import { fetchJsonOnce } from "../util/FetchHelper.js"; | ||
import { attachLanguageChange } from "../locale/languageChange.js"; | ||
import getLocale from "../locale/getLocale.js"; | ||
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from "../generated/AssetParameters.js"; | ||
import { getFeature } from "../FeaturesRegistry.js"; | ||
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from "../generated/AssetParameters.js"; | ||
const resources = new Map(); | ||
const cldrData = {}; | ||
const cldrUrls = {}; | ||
// externally configurable mapping function for resolving (localeId -> URL) | ||
// default implementation - ui5 CDN | ||
let cldrMappingFn = locale => `https://ui5.sap.com/1.60.2/resources/sap/ui/core/cldr/${locale}.json`; | ||
const localeDataMap = new Map(); | ||
const loaders = new Map(); | ||
const cldrPromises = new Map(); | ||
const reportedErrors = new Set(); | ||
let warningShown = false; | ||
const M_ISO639_OLD_TO_NEW = { | ||
"iw": "he", | ||
"ji": "yi", | ||
"in": "id", | ||
"sh": "sr", | ||
"iw": "he", | ||
"ji": "yi", | ||
"in": "id", | ||
}; | ||
const _showAssetsWarningOnce = (localeId) => { | ||
if (warningShown) { | ||
return; | ||
} | ||
console.warn(`[LocaleData] Supported locale "${localeId}" not configured, import the "Assets.js" module from the webcomponents package you are using.`); /* eslint-disable-line */ | ||
warningShown = true; | ||
}; | ||
const calcLocale = (language, region, script) => { | ||
// normalize language and handle special cases | ||
language = (language && M_ISO639_OLD_TO_NEW[language]) || language; | ||
// Special case 1: in an SAP context, the inclusive language code "no" always means Norwegian Bokmal ("nb") | ||
if (language === "no") { | ||
language = "nb"; | ||
} | ||
// Special case 2: for Chinese, derive a default region from the script (this behavior is inherited from Java) | ||
if (language === "zh" && !region) { | ||
if (script === "Hans") { | ||
region = "CN"; | ||
} else if (script === "Hant") { | ||
region = "TW"; | ||
} | ||
} | ||
// try language + region | ||
let localeId = `${language}_${region}`; | ||
if (!SUPPORTED_LOCALES.includes(localeId)) { | ||
// fallback to language only | ||
localeId = language; | ||
} | ||
if (!SUPPORTED_LOCALES.includes(localeId)) { | ||
// fallback to english | ||
localeId = DEFAULT_LOCALE; | ||
} | ||
return localeId; | ||
// normalize language and handle special cases | ||
language = (language && M_ISO639_OLD_TO_NEW[language]) || language; | ||
// Special case 1: in an SAP context, the inclusive language code "no" always means Norwegian Bokmal ("nb") | ||
if (language === "no") { | ||
language = "nb"; | ||
} | ||
// Special case 2: for Chinese, derive a default region from the script (this behavior is inherited from Java) | ||
if (language === "zh" && !region) { | ||
if (script === "Hans") { | ||
region = "CN"; | ||
} | ||
else if (script === "Hant") { | ||
region = "TW"; | ||
} | ||
} | ||
// Special case 3: for Serbian, there are cyrillic and latin scripts, "sh" and "sr-latn" map to "latin", "sr" maps to cyrillic. | ||
if (language === "sh" || (language === "sr" && script === "Latn")) { | ||
language = "sr"; | ||
region = "Latn"; | ||
} | ||
// try language + region | ||
let localeId = `${language}_${region}`; | ||
if (SUPPORTED_LOCALES.includes(localeId)) { | ||
if (loaders.has(localeId)) { | ||
// supported and has loader | ||
return localeId; | ||
} | ||
// supported, no loader - fallback to default and warn | ||
_showAssetsWarningOnce(localeId); | ||
return DEFAULT_LOCALE; | ||
} | ||
// not supported, try language only | ||
localeId = language; | ||
if (SUPPORTED_LOCALES.includes(localeId)) { | ||
if (loaders.has(localeId)) { | ||
// supported and has loader | ||
return localeId; | ||
} | ||
// supported, no loader - fallback to default and warn | ||
_showAssetsWarningOnce(localeId); | ||
return DEFAULT_LOCALE; | ||
} | ||
// not supported - fallback to default locale | ||
return DEFAULT_LOCALE; | ||
}; | ||
const resolveMissingMappings = () => { | ||
if (!cldrMappingFn) { | ||
return; | ||
} | ||
const missingLocales = SUPPORTED_LOCALES.filter(locale => !cldrData[locale] && !cldrUrls[locale]); | ||
missingLocales.forEach(locale => { | ||
cldrUrls[locale] = cldrMappingFn(locale); | ||
}); | ||
// internal set data | ||
const setLocaleData = (localeId, content) => { | ||
localeDataMap.set(localeId, content); | ||
}; | ||
const registerModuleContent = (moduleName, content) => { | ||
resources.set(moduleName, content); | ||
// external getSync | ||
const getLocaleData = (localeId) => { | ||
// if there is no loader, the default fallback was fetched and a warning was given - use default locale instead | ||
if (!loaders.has(localeId)) { | ||
localeId = DEFAULT_LOCALE; | ||
} | ||
const content = localeDataMap.get(localeId); | ||
if (!content) { | ||
throw new Error(`CLDR data for locale ${localeId} is not loaded!`); | ||
} | ||
return content; | ||
}; | ||
const getModuleContent = moduleName => { | ||
const moduleContent = resources.get(moduleName); | ||
if (moduleContent) { | ||
return moduleContent; | ||
} | ||
const missingModule = moduleName.match(/sap\/ui\/core\/cldr\/(\w+)\.json/); | ||
if (missingModule) { | ||
throw new Error(`CLDR data for locale ${missingModule[1]} is not loaded!`); | ||
} | ||
throw new Error(`Unknown module ${moduleName}`); | ||
// load bundle over the network once | ||
const _loadCldrOnce = (localeId) => { | ||
if (!cldrPromises.get(localeId)) { | ||
const loadCldr = loaders.get(localeId); | ||
if (!loadCldr) { | ||
throw new Error(`CLDR data for locale ${localeId} is not loaded!`); | ||
} | ||
cldrPromises.set(localeId, loadCldr(localeId)); | ||
} | ||
return cldrPromises.get(localeId); | ||
}; | ||
// external getAsync | ||
const fetchCldr = async (language, region, script) => { | ||
resolveMissingMappings(); | ||
const localeId = calcLocale(language, region, script); | ||
let cldrObj = cldrData[localeId]; | ||
const url = cldrUrls[localeId]; | ||
const OpenUI5Support = getFeature("OpenUI5Support"); | ||
if (!cldrObj && OpenUI5Support) { | ||
cldrObj = OpenUI5Support.getLocaleDataObject(); | ||
} | ||
if (cldrObj) { | ||
// inlined from build or fetched independently | ||
registerModuleContent(`sap/ui/core/cldr/${localeId}.json`, cldrObj); | ||
} else if (url) { | ||
// fetch it | ||
const cldrContent = await fetchJsonOnce(url); | ||
registerModuleContent(`sap/ui/core/cldr/${localeId}.json`, cldrContent); | ||
} | ||
const localeId = calcLocale(language, region, script); | ||
// reuse OpenUI5 CLDR if present | ||
const openUI5Support = getFeature("OpenUI5Support"); | ||
if (openUI5Support) { | ||
const cldrContent = openUI5Support.getLocaleDataObject(); | ||
if (cldrContent) { | ||
// only if openui5 actually returned valid content | ||
setLocaleData(localeId, cldrContent); | ||
return; | ||
} | ||
} | ||
// fetch it | ||
try { | ||
const cldrContent = await _loadCldrOnce(localeId); | ||
setLocaleData(localeId, cldrContent); | ||
} | ||
catch (error) { | ||
const e = error; | ||
if (!reportedErrors.has(e.message)) { | ||
reportedErrors.add(e.message); | ||
console.error(e.message); /* eslint-disable-line */ | ||
} | ||
} | ||
}; | ||
const registerCldr = (locale, url) => { | ||
cldrUrls[locale] = url; | ||
const registerLocaleDataLoader = (localeId, loader) => { | ||
loaders.set(localeId, loader); | ||
}; | ||
const setCldrData = (locale, data) => { | ||
cldrData[locale] = data; | ||
}; | ||
const getCldrData = locale => { | ||
return cldrData[locale]; | ||
}; | ||
const _registerMappingFunction = mappingFn => { | ||
cldrMappingFn = mappingFn; | ||
}; | ||
export { | ||
fetchCldr, | ||
registerCldr, | ||
setCldrData, | ||
getCldrData, | ||
getModuleContent, | ||
_registerMappingFunction, | ||
}; | ||
// register default loader for "en" from ui5 CDN (dev workflow without assets) | ||
registerLocaleDataLoader("en", async () => { | ||
const cldrContent = await fetch(`https://sdk.openui5.org/1.120.5/resources/sap/ui/core/cldr/en.json`); | ||
return cldrContent.json(); | ||
}); | ||
// When the language changes dynamically (the user calls setLanguage), | ||
// re-fetch the required CDRD data. | ||
attachLanguageChange(() => { | ||
const locale = getLocale(); | ||
return fetchCldr(locale.getLanguage(), locale.getRegion(), locale.getScript()); | ||
}); | ||
export { registerLocaleDataLoader, fetchCldr, getLocaleData, }; | ||
//# sourceMappingURL=LocaleData.js.map |
@@ -1,87 +0,68 @@ | ||
import { fetchJsonOnce, fetchTextOnce } from "../util/FetchHelper.js"; | ||
import { DEFAULT_THEME } from "../generated/AssetParameters.js"; | ||
import getFileExtension from "../util/getFileExtension.js"; | ||
const themeURLs = new Map(); | ||
import { mergeStyles } from "../ManagedStyles.js"; | ||
import { fireThemeRegistered } from "../theming/ThemeRegistered.js"; | ||
const themeStyles = new Map(); | ||
const loaders = new Map(); | ||
const customLoaders = new Map(); | ||
const registeredPackages = new Set(); | ||
const registeredThemes = new Set(); | ||
/** | ||
* Used to provide CSS Vars for a specific theme for a specific package. | ||
* The CSS Vars can be passed directly as a string (containing them), as an object with a "_" property(containing them in the "_" property), or as a URL. | ||
* This URL must point to a JSON file, containing a "_" property. | ||
* | ||
* Example usage: | ||
* 1) Pass the CSS Vars as a string directly. | ||
* registerThemeProperties("my-package", "my_theme", ":root{--var1: red;}"); | ||
* 2) Pass the CSS Vars as an object directly | ||
* registerThemeProperties("my-package", "my_theme", {"_": ":root{--var1: red;}"}); | ||
* 3) Pass a URL to a CSS file, containing the CSS Vars. Will be fetched on demand, not upon registration. | ||
* registerThemeProperties("my-package", "my_theme", "http://url/to/my/theme.css"); | ||
* 4) Pass a URL to a JSON file, containing the CSS Vars in its "_" property. Will be fetched on demand, not upon registration. | ||
* registerThemeProperties("my-package", "my_theme", "http://url/to/my/theme.json"); | ||
* | ||
* @public | ||
* @param packageName - the NPM package for which CSS Vars are registered | ||
* @param themeName - the theme which the CSS Vars implement | ||
* @param style - can be one of four options: a string, an object with a "_" property, URL to a CSS file, or URL to a JSON file with a "_" property | ||
*/ | ||
const registerThemeProperties = (packageName, themeName, style) => { | ||
if (style._) { | ||
// JSON object like ({"_": ":root"}) | ||
themeStyles.set(`${packageName}_${themeName}`, style._); | ||
} else if (style.includes(":root") || style === "") { | ||
// pure string, including empty string | ||
themeStyles.set(`${packageName}_${themeName}`, style); | ||
} else { | ||
// url for fetching | ||
themeURLs.set(`${packageName}_${themeName}`, style); | ||
} | ||
registeredPackages.add(packageName); | ||
registeredThemes.add(themeName); | ||
const registerThemePropertiesLoader = (packageName, themeName, loader) => { | ||
loaders.set(`${packageName}/${themeName}`, loader); | ||
registeredPackages.add(packageName); | ||
registeredThemes.add(themeName); | ||
fireThemeRegistered(themeName); | ||
}; | ||
const getThemeProperties = async (packageName, themeName) => { | ||
const style = themeStyles.get(`${packageName}_${themeName}`); | ||
if (style !== undefined) { // it's valid for style to be an empty string | ||
return style; | ||
} | ||
if (!registeredThemes.has(themeName)) { | ||
const regThemesStr = [...registeredThemes.values()].join(", "); | ||
console.warn(`You have requested a non-registered theme - falling back to ${DEFAULT_THEME}. Registered themes are: ${regThemesStr}`); /* eslint-disable-line */ | ||
return themeStyles.get(`${packageName}_${DEFAULT_THEME}`); | ||
} | ||
const data = await fetchThemeProperties(packageName, themeName); | ||
const themeProps = data._ || data; | ||
themeStyles.set(`${packageName}_${themeName}`, themeProps); | ||
return themeProps; | ||
const registerCustomThemePropertiesLoader = (packageName, themeName, loader) => { | ||
customLoaders.set(`${packageName}/${themeName}`, loader); | ||
}; | ||
const fetchThemeProperties = async (packageName, themeName) => { | ||
const url = themeURLs.get(`${packageName}_${themeName}`); | ||
if (!url) { | ||
throw new Error(`You have to import the ${packageName}/dist/Assets.js module to switch to additional themes`); | ||
} | ||
return getFileExtension(url) === ".css" ? fetchTextOnce(url) : fetchJsonOnce(url); | ||
const getThemeProperties = async (packageName, themeName, externalThemeName) => { | ||
const cacheKey = `${packageName}_${themeName}_${externalThemeName || ""}`; | ||
const cachedStyleData = themeStyles.get(cacheKey); | ||
if (cachedStyleData !== undefined) { // it's valid for style to be an empty string | ||
return cachedStyleData; | ||
} | ||
if (!registeredThemes.has(themeName)) { | ||
const regThemesStr = [...registeredThemes.values()].join(", "); | ||
console.warn(`You have requested a non-registered theme ${themeName} - falling back to ${DEFAULT_THEME}. Registered themes are: ${regThemesStr}`); /* eslint-disable-line */ | ||
return _getThemeProperties(packageName, DEFAULT_THEME); | ||
} | ||
const [style, customStyle] = await Promise.all([ | ||
_getThemeProperties(packageName, themeName), | ||
externalThemeName ? _getThemeProperties(packageName, externalThemeName, true) : undefined, | ||
]); | ||
const styleData = mergeStyles(style, customStyle); | ||
if (styleData) { | ||
themeStyles.set(cacheKey, styleData); | ||
} | ||
return styleData; | ||
}; | ||
const _getThemeProperties = async (packageName, themeName, forCustomTheme = false) => { | ||
const loadersMap = forCustomTheme ? customLoaders : loaders; | ||
const loader = loadersMap.get(`${packageName}/${themeName}`); | ||
if (!loader) { | ||
// no themes for package | ||
if (!forCustomTheme) { | ||
console.error(`Theme [${themeName}] not registered for package [${packageName}]`); /* eslint-disable-line */ | ||
} | ||
return; | ||
} | ||
let data; | ||
try { | ||
data = await loader(themeName); | ||
} | ||
catch (error) { | ||
const e = error; | ||
console.error(packageName, e.message); /* eslint-disable-line */ | ||
return; | ||
} | ||
const themeProps = data._ || data; // Refactor: remove _ everywhere | ||
return themeProps; | ||
}; | ||
const getRegisteredPackages = () => { | ||
return registeredPackages; | ||
return registeredPackages; | ||
}; | ||
const isThemeRegistered = theme => { | ||
return registeredThemes.has(theme); | ||
const isThemeRegistered = (theme) => { | ||
return registeredThemes.has(theme); | ||
}; | ||
export { | ||
registerThemeProperties, | ||
getThemeProperties, | ||
getRegisteredPackages, | ||
isThemeRegistered, | ||
}; | ||
export { registerThemePropertiesLoader, registerCustomThemePropertiesLoader, getThemeProperties, getRegisteredPackages, isThemeRegistered, }; | ||
//# sourceMappingURL=Themes.js.map |
@@ -1,10 +0,6 @@ | ||
import { registerI18nBundle } from "./asset-registries/i18n.js"; | ||
import { registerCldr, _registerMappingFunction as registerCldrMappingFunction } from "./asset-registries/LocaleData.js"; | ||
import { registerThemeProperties } from "./asset-registries/Themes.js"; | ||
export { | ||
registerCldr, | ||
registerCldrMappingFunction, | ||
registerThemeProperties, | ||
registerI18nBundle, | ||
}; | ||
import { registerI18nLoader } from "./asset-registries/i18n.js"; | ||
import { registerLocaleDataLoader } from "./asset-registries/LocaleData.js"; | ||
import { registerThemePropertiesLoader, registerCustomThemePropertiesLoader } from "./asset-registries/Themes.js"; | ||
import { registerIconLoader } from "./asset-registries/Icons.js"; | ||
export { registerI18nLoader, registerLocaleDataLoader, registerThemePropertiesLoader, registerCustomThemePropertiesLoader, registerIconLoader, }; | ||
//# sourceMappingURL=AssetRegistry.js.map |
import { getAnimationMode as getConfiguredAnimationMode } from "../InitialConfiguration.js"; | ||
import AnimationMode from "../types/AnimationMode.js"; | ||
let animationMode; | ||
let curAnimationMode; | ||
/** | ||
* Returns the animation mode - "full", "basic", "minimal" or "none". | ||
* @public | ||
* @returns { AnimationMode } | ||
*/ | ||
const getAnimationMode = () => { | ||
if (animationMode === undefined) { | ||
animationMode = getConfiguredAnimationMode(); | ||
} | ||
return animationMode; | ||
if (curAnimationMode === undefined) { | ||
curAnimationMode = getConfiguredAnimationMode(); | ||
} | ||
return curAnimationMode; | ||
}; | ||
const setAnimationMode = newAnimationMode => { | ||
if (Object.values(AnimationMode).includes(newAnimationMode)) { | ||
animationMode = newAnimationMode; | ||
} | ||
/** | ||
* Sets the animation mode - "full", "basic", "minimal" or "none". | ||
* @public | ||
* @param { AnimationMode } animationMode | ||
*/ | ||
const setAnimationMode = (animationMode) => { | ||
if (animationMode in AnimationMode) { | ||
curAnimationMode = animationMode; | ||
} | ||
}; | ||
export { | ||
getAnimationMode, | ||
setAnimationMode, | ||
}; | ||
export { getAnimationMode, setAnimationMode, }; | ||
//# sourceMappingURL=AnimationMode.js.map |
import CalendarType from "../types/CalendarType.js"; | ||
import { getCalendarType as getConfiguredCalendarType } from "../InitialConfiguration.js"; | ||
import { getCalendarType as getConfiguredCalendarType, getSecondaryCalendarType as getConfiguredSecondaryCalendarType, } from "../InitialConfiguration.js"; | ||
let calendarType; | ||
let secondaryCalendarType; | ||
/** | ||
* Returns the configured or default calendar type. | ||
* @public | ||
* @returns { CalendarType } the effective calendar type | ||
*/ | ||
const getCalendarType = () => { | ||
if (calendarType === undefined) { | ||
calendarType = getConfiguredCalendarType(); | ||
} | ||
if (CalendarType.isValid(calendarType)) { | ||
return calendarType; | ||
} | ||
return CalendarType.Gregorian; | ||
if (calendarType === undefined) { | ||
calendarType = getConfiguredCalendarType(); | ||
} | ||
if (calendarType && calendarType in CalendarType) { | ||
return calendarType; | ||
} | ||
return CalendarType.Gregorian; | ||
}; | ||
export { getCalendarType }; // eslint-disable-line | ||
/** | ||
* Returns the configured secondary calendar type. | ||
* @public | ||
* @returns { CalendarType | undefined } the effective calendar type | ||
* @since 1.18.0 | ||
*/ | ||
const getSecondaryCalendarType = () => { | ||
if (secondaryCalendarType === undefined) { | ||
secondaryCalendarType = getConfiguredSecondaryCalendarType(); | ||
} | ||
if (secondaryCalendarType && secondaryCalendarType in CalendarType) { | ||
return secondaryCalendarType; | ||
} | ||
return secondaryCalendarType; | ||
}; | ||
export { getCalendarType, getSecondaryCalendarType, }; | ||
//# sourceMappingURL=CalendarType.js.map |
@@ -0,13 +1,19 @@ | ||
import LegacyDateFormats from "../features/LegacyDateFormats.js"; | ||
import { getFormatSettings } from "../InitialConfiguration.js"; | ||
import { getFeature } from "../FeaturesRegistry.js"; | ||
let formatSettings; | ||
/** | ||
* Returns the first day of the week from the configured format settings or based on the current locale. | ||
* @public | ||
* @returns {Number} 0 (Sunday) through 6 (Saturday) | ||
*/ | ||
const getFirstDayOfWeek = () => { | ||
if (formatSettings === undefined) { | ||
formatSettings = getFormatSettings(); | ||
} | ||
return formatSettings.firstDayOfWeek; | ||
if (formatSettings === undefined) { | ||
formatSettings = getFormatSettings(); | ||
} | ||
return formatSettings.firstDayOfWeek; | ||
}; | ||
export { getFirstDayOfWeek }; // eslint-disable-line | ||
const legacyDateFormats = getFeature("LegacyDateFormats"); | ||
const getLegacyDateCalendarCustomizing = legacyDateFormats ? LegacyDateFormats.getLegacyDateCalendarCustomizing : () => { return []; }; | ||
export { getFirstDayOfWeek, getLegacyDateCalendarCustomizing, }; | ||
//# sourceMappingURL=FormatSettings.js.map |
@@ -1,41 +0,71 @@ | ||
import { getLanguage as getConfiguredLanguage } from "../InitialConfiguration.js"; | ||
import { getLanguage as getConfiguredLanguage, getFetchDefaultLanguage as getConfiguredFetchDefaultLanguage, } from "../InitialConfiguration.js"; | ||
import { fireLanguageChange } from "../locale/languageChange.js"; | ||
import RenderScheduler from "../RenderScheduler.js"; | ||
let language; | ||
import { reRenderAllUI5Elements } from "../Render.js"; | ||
import { DEFAULT_LANGUAGE } from "../generated/AssetParameters.js"; | ||
import { isBooted } from "../Boot.js"; | ||
let curLanguage; | ||
let fetchDefaultLanguage; | ||
/** | ||
* Returns the currently configured language, or the browser language as a fallback | ||
* @returns {String} | ||
* Returns the currently configured language, or the browser language as a fallback. | ||
* @public | ||
* @returns {string} | ||
*/ | ||
const getLanguage = () => { | ||
if (language === undefined) { | ||
language = getConfiguredLanguage(); | ||
} | ||
return language; | ||
if (curLanguage === undefined) { | ||
curLanguage = getConfiguredLanguage(); | ||
} | ||
return curLanguage; | ||
}; | ||
/** | ||
* Changes the current language, re-fetches all message bundles, updates all language-aware components | ||
* and returns a promise that resolves when all rendering is done | ||
* and returns a promise that resolves when all rendering is done. | ||
* | ||
* @param newLanguage | ||
* @param {string} language | ||
* @public | ||
* @returns {Promise<void>} | ||
*/ | ||
const setLanguage = async newLanguage => { | ||
if (language === newLanguage) { | ||
return; | ||
} | ||
language = newLanguage; | ||
const listenersResults = fireLanguageChange(newLanguage); | ||
await Promise.all(listenersResults); | ||
RenderScheduler.reRenderAllUI5Elements({ languageAware: true }); | ||
return RenderScheduler.whenFinished(); | ||
const setLanguage = async (language) => { | ||
if (curLanguage === language) { | ||
return; | ||
} | ||
curLanguage = language; | ||
if (isBooted()) { | ||
await fireLanguageChange(language); | ||
await reRenderAllUI5Elements({ languageAware: true }); | ||
} | ||
}; | ||
export { | ||
getLanguage, | ||
setLanguage, | ||
/** | ||
* Returns the default languague. | ||
* | ||
* Note: Default language might be different than the configurated one. | ||
* | ||
* @public | ||
* @returns {string} | ||
*/ | ||
const getDefaultLanguage = () => { | ||
return DEFAULT_LANGUAGE; | ||
}; | ||
/** | ||
* Defines if the default language, that is inlined, should be | ||
* fetched over the network instead of using the inlined one. | ||
* **Note:** By default the language will not be fetched. | ||
* | ||
* @public | ||
* @param {boolean} fetchDefaultLang | ||
*/ | ||
const setFetchDefaultLanguage = (fetchDefaultLang) => { | ||
fetchDefaultLanguage = fetchDefaultLang; | ||
}; | ||
/** | ||
* Returns if the default language, that is inlined, should be fetched over the network. | ||
* @public | ||
* @returns {boolean} | ||
*/ | ||
const getFetchDefaultLanguage = () => { | ||
if (fetchDefaultLanguage === undefined) { | ||
setFetchDefaultLanguage(getConfiguredFetchDefaultLanguage()); | ||
} | ||
return fetchDefaultLanguage; | ||
}; | ||
export { getLanguage, setLanguage, getDefaultLanguage, setFetchDefaultLanguage, getFetchDefaultLanguage, }; | ||
//# sourceMappingURL=Language.js.map |
import { getNoConflict as getConfiguredNoConflict } from "../InitialConfiguration.js"; | ||
// Fire these events even with noConflict: true | ||
const excludeList = [ | ||
"value-changed", | ||
"value-changed", | ||
"click", | ||
]; | ||
const shouldFireOriginalEvent = eventName => { | ||
return excludeList.includes(eventName); | ||
}; | ||
let noConflict; | ||
const shouldNotFireOriginalEvent = eventName => { | ||
const nc = getNoConflict(); | ||
return !(nc.events && nc.events.includes && nc.events.includes(eventName)); | ||
const shouldFireOriginalEvent = (eventName) => { | ||
return excludeList.includes(eventName); | ||
}; | ||
const shouldNotFireOriginalEvent = (eventName) => { | ||
const nc = getNoConflict(); | ||
// return !(nc.events && nc.events.includes && nc.events.includes(eventName)); | ||
return !(typeof nc !== "boolean" && nc.events && nc.events.includes && nc.events.includes(eventName)); | ||
}; | ||
/** | ||
* Returns if the "noConflict" configuration is set. | ||
* @public | ||
* @returns { NoConflictData } | ||
*/ | ||
const getNoConflict = () => { | ||
if (noConflict === undefined) { | ||
noConflict = getConfiguredNoConflict(); | ||
} | ||
return noConflict; | ||
if (noConflict === undefined) { | ||
noConflict = getConfiguredNoConflict(); | ||
} | ||
return noConflict; | ||
}; | ||
const skipOriginalEvent = eventName => { | ||
const nc = getNoConflict(); | ||
// Always fire these events | ||
if (shouldFireOriginalEvent(eventName)) { | ||
return false; | ||
} | ||
// Read from the configuration | ||
if (nc === true) { | ||
return true; | ||
} | ||
return !shouldNotFireOriginalEvent(eventName); | ||
/** | ||
* Sets the "noConflict" mode. | ||
* - When "false" (default value), all custom events are fired with and without the "ui5-" prefix. | ||
* - When "true", all custom events are fired with the "ui5-" prefix only. | ||
* - When an object is supplied, just the specified events will be fired with the "ui5-" prefix. | ||
* @public | ||
* @param { NoConflictData } noConflictData | ||
*/ | ||
const setNoConflict = (noConflictData) => { | ||
noConflict = noConflictData; | ||
}; | ||
const setNoConflict = noConflictData => { | ||
noConflict = noConflictData; | ||
const skipOriginalEvent = (eventName) => { | ||
const nc = getNoConflict(); | ||
// Always fire these events | ||
if (shouldFireOriginalEvent(eventName)) { | ||
return false; | ||
} | ||
// Read from the configuration | ||
if (nc === true) { | ||
return true; | ||
} | ||
return !shouldNotFireOriginalEvent(eventName); | ||
}; | ||
export { | ||
getNoConflict, | ||
setNoConflict, | ||
skipOriginalEvent, | ||
}; | ||
export { getNoConflict, setNoConflict, skipOriginalEvent, }; | ||
//# sourceMappingURL=NoConflict.js.map |
import { getTheme as getConfiguredTheme } from "../InitialConfiguration.js"; | ||
import { reRenderAllUI5Elements } from "../Render.js"; | ||
import applyTheme from "../theming/applyTheme.js"; | ||
let theme; | ||
import getThemeDesignerTheme from "../theming/getThemeDesignerTheme.js"; | ||
import { DEFAULT_THEME, SUPPORTED_THEMES } from "../generated/AssetParameters.js"; | ||
import { isBooted } from "../Boot.js"; | ||
let curTheme; | ||
/** | ||
* Returns the current theme. | ||
* @public | ||
* @returns {string} the current theme name | ||
*/ | ||
const getTheme = () => { | ||
if (theme === undefined) { | ||
theme = getConfiguredTheme(); | ||
} | ||
return theme; | ||
if (curTheme === undefined) { | ||
curTheme = getConfiguredTheme(); | ||
} | ||
return curTheme; | ||
}; | ||
const setTheme = async newTheme => { | ||
if (theme === newTheme) { | ||
return; | ||
} | ||
theme = newTheme; | ||
// Update CSS Custom Properties | ||
await applyTheme(theme); | ||
/** | ||
* Applies a new theme after fetching its assets from the network. | ||
* @public | ||
* @param {string} theme the name of the new theme | ||
* @returns {Promise<void>} a promise that is resolved when the new theme assets have been fetched and applied to the DOM | ||
*/ | ||
const setTheme = async (theme) => { | ||
if (curTheme === theme) { | ||
return; | ||
} | ||
curTheme = theme; | ||
if (isBooted()) { | ||
// Update CSS Custom Properties | ||
await applyTheme(curTheme); | ||
await reRenderAllUI5Elements({ themeAware: true }); | ||
} | ||
}; | ||
export { | ||
getTheme, | ||
setTheme, | ||
/** | ||
* Returns the default theme. | ||
* | ||
* Note: Default theme might be different than the configurated one. | ||
* | ||
* @public | ||
* @returns {string} | ||
*/ | ||
const getDefaultTheme = () => { | ||
return DEFAULT_THEME; | ||
}; | ||
/** | ||
* Returns if the given theme name is the one currently applied. | ||
* @private | ||
* @param {string} theme | ||
* @returns {boolean} | ||
*/ | ||
const isTheme = (theme) => { | ||
const currentTheme = getTheme(); | ||
return currentTheme === theme || currentTheme === `${theme}_exp`; | ||
}; | ||
/** | ||
* Returns if the currently set theme is part of legacy theme families ("sap_belize" or "sap_fiori_3"). | ||
* **Note**: in addition, the method checks the base theme of a custom theme, built via the ThemeDesigner. | ||
* | ||
* @private | ||
* @returns { boolean } | ||
*/ | ||
const isLegacyThemeFamily = () => { | ||
const currentTheme = getTheme(); | ||
if (!isKnownTheme(currentTheme)) { | ||
return !getThemeDesignerTheme()?.baseThemeName?.startsWith("sap_horizon"); | ||
} | ||
return !currentTheme.startsWith("sap_horizon"); | ||
}; | ||
const isKnownTheme = (theme) => SUPPORTED_THEMES.includes(theme); | ||
export { getTheme, setTheme, isTheme, isLegacyThemeFamily, getDefaultTheme, }; | ||
//# sourceMappingURL=Theme.js.map |
@@ -1,39 +0,78 @@ | ||
import setToArray from "./util/setToArray.js"; | ||
import getSharedResource from "./getSharedResource.js"; | ||
import { getCurrentRuntimeIndex, compareRuntimes, getAllRuntimes } from "./Runtimes.js"; | ||
const Tags = getSharedResource("Tags", new Map()); | ||
const Definitions = new Set(); | ||
const Failures = new Set(); | ||
let Failures = new Map(); | ||
let failureTimeout; | ||
const registerTag = tag => { | ||
Definitions.add(tag); | ||
const UNKNOWN_RUNTIME = -1; | ||
const registerTag = (tag) => { | ||
Definitions.add(tag); | ||
Tags.set(tag, getCurrentRuntimeIndex()); | ||
}; | ||
const isTagRegistered = tag => { | ||
return Definitions.has(tag); | ||
const isTagRegistered = (tag) => { | ||
return Definitions.has(tag); | ||
}; | ||
const getAllRegisteredTags = () => { | ||
return setToArray(Definitions); | ||
return [...Definitions.values()]; | ||
}; | ||
const recordTagRegistrationFailure = tag => { | ||
Failures.add(tag); | ||
if (!failureTimeout) { | ||
failureTimeout = setTimeout(() => { | ||
displayFailedRegistrations(); | ||
failureTimeout = undefined; | ||
}, 1000); | ||
} | ||
const recordTagRegistrationFailure = (tag) => { | ||
let tagRegRuntimeIndex = Tags.get(tag); | ||
if (tagRegRuntimeIndex === undefined) { | ||
tagRegRuntimeIndex = UNKNOWN_RUNTIME; // If the tag is taken, but not registered in Tags, then a version before 1.1.0 defined it => use the "unknown" key | ||
} | ||
if (!Failures.has(tagRegRuntimeIndex)) { | ||
Failures.set(tagRegRuntimeIndex, new Set()); | ||
} | ||
Failures.get(tagRegRuntimeIndex).add(tag); | ||
if (!failureTimeout) { | ||
failureTimeout = setTimeout(() => { | ||
displayFailedRegistrations(); | ||
Failures = new Map(); | ||
failureTimeout = undefined; | ||
}, 1000); | ||
} | ||
}; | ||
const displayFailedRegistrations = () => { | ||
console.warn(`The following tags have already been defined by a different UI5 Web Components version: ${setToArray(Failures).join(", ")}`); // eslint-disable-line | ||
Failures.clear(); | ||
const allRuntimes = getAllRuntimes(); | ||
const currentRuntimeIndex = getCurrentRuntimeIndex(); | ||
const currentRuntime = allRuntimes[currentRuntimeIndex]; | ||
let message = `Multiple UI5 Web Components instances detected.`; | ||
if (allRuntimes.length > 1) { | ||
message = `${message}\nLoading order (versions before 1.1.0 not listed): ${allRuntimes.map(runtime => `\n${runtime.description}`).join("")}`; | ||
} | ||
[...Failures.keys()].forEach(otherRuntimeIndex => { | ||
let comparison; | ||
let otherRuntime; | ||
if (otherRuntimeIndex === UNKNOWN_RUNTIME) { // version < 1.1.0 defined the tag | ||
comparison = 1; // the current runtime is considered newer | ||
otherRuntime = { | ||
description: `Older unknown runtime`, | ||
}; | ||
} | ||
else { | ||
comparison = compareRuntimes(currentRuntimeIndex, otherRuntimeIndex); | ||
otherRuntime = allRuntimes[otherRuntimeIndex]; | ||
} | ||
let compareWord; | ||
if (comparison > 0) { | ||
compareWord = "an older"; | ||
} | ||
else if (comparison < 0) { | ||
compareWord = "a newer"; | ||
} | ||
else { | ||
compareWord = "the same"; | ||
} | ||
message = `${message}\n\n"${currentRuntime.description}" failed to define ${Failures.get(otherRuntimeIndex).size} tag(s) as they were defined by a runtime of ${compareWord} version "${otherRuntime.description}": ${([...Failures.get(otherRuntimeIndex)]).sort().join(", ")}.`; | ||
if (comparison > 0) { | ||
message = `${message}\nWARNING! If your code uses features of the above web components, unavailable in ${otherRuntime.description}, it might not work as expected!`; | ||
} | ||
else { | ||
message = `${message}\nSince the above web components were defined by the same or newer version runtime, they should be compatible with your code.`; | ||
} | ||
}); | ||
message = `${message}\n\nTo prevent other runtimes from defining tags that you use, consider using scoping or have third-party libraries use scoping: https://github.com/SAP/ui5-webcomponents/blob/main/docs/2-advanced/03-scoping.md.`; | ||
console.warn(message); // eslint-disable-line | ||
}; | ||
export { | ||
registerTag, | ||
isTagRegistered, | ||
getAllRegisteredTags, | ||
recordTagRegistrationFailure, | ||
}; | ||
export { registerTag, isTagRegistered, getAllRegisteredTags, recordTagRegistrationFailure, }; | ||
//# sourceMappingURL=CustomElementsRegistry.js.map |
@@ -1,108 +0,11 @@ | ||
let suf; | ||
let rulesObj = { | ||
include: [/^ui5-/], | ||
exclude: [], | ||
}; | ||
const tagsCache = new Map(); // true/false means the tag should/should not be cached, undefined means not known yet. | ||
/** | ||
* Sets the suffix to be used for custom elements scoping, f.e. pass "demo" to get tags such as "ui5-button-demo". | ||
* Note: by default all tags starting with "ui5-" will be scoped, unless you change this by calling "setCustomElementsScopingRules" | ||
* | ||
* @public | ||
* @param suffix The scoping suffix | ||
*/ | ||
const setCustomElementsScopingSuffix = suffix => { | ||
if (!suffix.match(/^[a-zA-Z0-9_-]+$/)) { | ||
throw new Error("Only alphanumeric characters and dashes allowed for the scoping suffix"); | ||
} | ||
suf = suffix; | ||
}; | ||
/** | ||
* Returns the currently set scoping suffix, or undefined if not set. | ||
* | ||
* @public | ||
* @returns {String|undefined} | ||
*/ | ||
const getCustomElementsScopingSuffix = () => { | ||
return suf; | ||
}; | ||
/** | ||
* Sets the rules, governing which custom element tags to scope and which not, f.e. | ||
* setCustomElementsScopingRules({include: [/^ui5-/]}, exclude: [/^ui5-mylib-/, /^ui5-carousel$/]); | ||
* will scope all elements starting with "ui5-" but not the ones starting with "ui5-mylib-" and not "ui5-carousel". | ||
* | ||
* @public | ||
* @param rules Object with "include" and "exclude" properties, both arrays of regular expressions. Note that "include" | ||
* rules are applied first and "exclude" rules second. | ||
*/ | ||
const setCustomElementsScopingRules = rules => { | ||
if (!rules || !rules.include) { | ||
throw new Error(`"rules" must be an object with at least an "include" property`); | ||
} | ||
if (!Array.isArray(rules.include) || rules.include.some(rule => !(rule instanceof RegExp))) { | ||
throw new Error(`"rules.include" must be an array of regular expressions`); | ||
} | ||
if (rules.exclude && (!Array.isArray(rules.exclude) || rules.exclude.some(rule => !(rule instanceof RegExp)))) { | ||
throw new Error(`"rules.exclude" must be an array of regular expressions`); | ||
} | ||
rules.exclude = rules.exclude || []; | ||
rulesObj = rules; | ||
tagsCache.clear(); // reset the cache upon setting new rules | ||
}; | ||
/** | ||
* Returns the rules, governing which custom element tags to scope and which not. By default, all elements | ||
* starting with "ui5-" are scoped. The default rules are: {include: [/^ui5-/]}. | ||
* | ||
* @public | ||
* @returns {Object} | ||
*/ | ||
const getCustomElementsScopingRules = () => { | ||
return rulesObj; | ||
}; | ||
/** | ||
* Determines whether custom elements with the given tag should be scoped or not. | ||
* The tag is first matched against the "include" rules and then against the "exclude" rules and the | ||
* result is cached until new rules are set. | ||
* | ||
* @public | ||
* @param tag | ||
*/ | ||
const shouldScopeCustomElement = tag => { | ||
if (!tagsCache.has(tag)) { | ||
const result = rulesObj.include.some(rule => tag.match(rule)) && !rulesObj.exclude.some(rule => tag.match(rule)); | ||
tagsCache.set(tag, result); | ||
} | ||
return tagsCache.get(tag); | ||
}; | ||
/** | ||
* Returns the currently set scoping suffix, if any and if the tag should be scoped, or undefined otherwise. | ||
* | ||
* @public | ||
* @param tag | ||
* @returns {String} | ||
*/ | ||
const getEffectiveScopingSuffixForTag = tag => { | ||
if (shouldScopeCustomElement(tag)) { | ||
return getCustomElementsScopingSuffix(); | ||
} | ||
}; | ||
export { | ||
setCustomElementsScopingSuffix, | ||
getCustomElementsScopingSuffix, | ||
setCustomElementsScopingRules, | ||
getCustomElementsScopingRules, | ||
shouldScopeCustomElement, | ||
getEffectiveScopingSuffixForTag, | ||
}; | ||
import { html, svg, unsafeStatic, } from "lit-html/static.js"; | ||
import { setCustomElementsScopingSuffix, getCustomElementsScopingSuffix, setCustomElementsScopingRules, getCustomElementsScopingRules, shouldScopeCustomElement, getEffectiveScopingSuffixForTag, getScopedVarName, } from "./CustomElementsScopeUtils.js"; | ||
import { registerFeature } from "./FeaturesRegistry.js"; | ||
class LitStatic { | ||
} | ||
LitStatic.html = html; | ||
LitStatic.svg = svg; | ||
LitStatic.unsafeStatic = unsafeStatic; | ||
registerFeature("LitStatic", LitStatic); | ||
export { LitStatic, setCustomElementsScopingSuffix, getCustomElementsScopingSuffix, setCustomElementsScopingRules, getCustomElementsScopingRules, shouldScopeCustomElement, getEffectiveScopingSuffixForTag, getScopedVarName, }; | ||
//# sourceMappingURL=CustomElementsScope.js.map |
@@ -1,282 +0,315 @@ | ||
import RenderScheduler from "../RenderScheduler.js"; | ||
import { | ||
isDown, | ||
isUp, | ||
isLeft, | ||
isRight, | ||
isHome, | ||
isEnd, | ||
} from "../Keys.js"; | ||
import EventProvider from "../EventProvider.js"; | ||
import { isDown, isUp, isLeft, isRight, isHome, isEnd, isPageDown, isPageUp, } from "../Keys.js"; | ||
import getActiveElement from "../util/getActiveElement.js"; | ||
import NavigationMode from "../types/NavigationMode.js"; | ||
import ItemNavigationBehavior from "../types/ItemNavigationBehavior.js"; | ||
// navigatable items must have id and tabindex | ||
class ItemNavigation extends EventProvider { | ||
constructor(rootWebComponent, options = {}) { | ||
super(); | ||
this.currentIndex = options.currentIndex || 0; | ||
this.rowSize = options.rowSize || 1; | ||
this.behavior = options.behavior || ItemNavigationBehavior.Static; | ||
this.hasNextPage = true; // used in Paging mode and controlled from the rootWebComponent | ||
this.hasPrevPage = true; // used in Paging mode and controlled from the rootWebComponent | ||
const navigationMode = options.navigationMode; | ||
const autoNavigation = !navigationMode || navigationMode === NavigationMode.Auto; | ||
this.horizontalNavigationOn = autoNavigation || navigationMode === NavigationMode.Horizontal; | ||
this.verticalNavigationOn = autoNavigation || navigationMode === NavigationMode.Vertical; | ||
this.pageSize = options.pageSize; | ||
this.rootWebComponent = rootWebComponent; | ||
this.rootWebComponent.addEventListener("keydown", this.onkeydown.bind(this)); | ||
this.rootWebComponent._onComponentStateFinalized = () => { | ||
this._init(); | ||
}; | ||
} | ||
_init() { | ||
this._getItems().forEach((item, idx) => { | ||
item._tabIndex = (idx === this.currentIndex) ? "0" : "-1"; | ||
}); | ||
} | ||
_horizontalNavigationOn() { | ||
return this.horizontalNavigationOn; | ||
} | ||
_verticalNavigationOn() { | ||
return this.verticalNavigationOn; | ||
} | ||
async _onKeyPress(event) { | ||
if (this.currentIndex >= this._getItems().length) { | ||
this.onOverflowBottomEdge(); | ||
} else if (this.currentIndex < 0) { | ||
this.onOverflowTopEdge(); | ||
} | ||
event.preventDefault(); | ||
await RenderScheduler.whenFinished(); | ||
this.update(); | ||
this.focusCurrent(); | ||
} | ||
onkeydown(event) { | ||
if (isUp(event) && this._verticalNavigationOn()) { | ||
return this._handleUp(event); | ||
} | ||
if (isDown(event) && this._verticalNavigationOn()) { | ||
return this._handleDown(event); | ||
} | ||
if (isLeft(event) && this._horizontalNavigationOn()) { | ||
return this._handleLeft(event); | ||
} | ||
if (isRight(event) && this._horizontalNavigationOn()) { | ||
return this._handleRight(event); | ||
} | ||
if (isHome(event)) { | ||
return this._handleHome(event); | ||
} | ||
if (isEnd(event)) { | ||
return this._handleEnd(event); | ||
} | ||
} | ||
_handleUp(event) { | ||
if (this._canNavigate()) { | ||
this.currentIndex -= this.rowSize; | ||
this._onKeyPress(event); | ||
} | ||
} | ||
_handleDown(event) { | ||
if (this._canNavigate()) { | ||
this.currentIndex += this.rowSize; | ||
this._onKeyPress(event); | ||
} | ||
} | ||
_handleLeft(event) { | ||
if (this._canNavigate()) { | ||
this.currentIndex -= 1; | ||
this._onKeyPress(event); | ||
} | ||
} | ||
_handleRight(event) { | ||
if (this._canNavigate()) { | ||
this.currentIndex += 1; | ||
this._onKeyPress(event); | ||
} | ||
} | ||
_handleHome(event) { | ||
if (this._canNavigate()) { | ||
const homeEndRange = this.rowSize > 1 ? this.rowSize : this._getItems().length; | ||
this.currentIndex -= this.currentIndex % homeEndRange; | ||
this._onKeyPress(event); | ||
} | ||
} | ||
_handleEnd(event) { | ||
if (this._canNavigate()) { | ||
const homeEndRange = this.rowSize > 1 ? this.rowSize : this._getItems().length; | ||
this.currentIndex += (homeEndRange - 1 - this.currentIndex % homeEndRange); // eslint-disable-line | ||
this._onKeyPress(event); | ||
} | ||
} | ||
update(current) { | ||
const origItems = this._getItems(); | ||
if (current) { | ||
this.currentIndex = this._getItems().indexOf(current); | ||
} | ||
if (!origItems[this.currentIndex] | ||
|| (origItems[this.currentIndex]._tabIndex && origItems[this.currentIndex]._tabIndex === "0")) { | ||
return; | ||
} | ||
const items = origItems.slice(0); | ||
for (let i = 0; i < items.length; i++) { | ||
items[i]._tabIndex = (i === this.currentIndex ? "0" : "-1"); | ||
} | ||
this.rootWebComponent._invalidate(); | ||
} | ||
focusCurrent() { | ||
const currentItem = this._getCurrentItem(); | ||
if (currentItem) { | ||
currentItem.focus(); | ||
} | ||
} | ||
_canNavigate() { | ||
const currentItem = this._getCurrentItem(); | ||
let activeElement = document.activeElement; | ||
while (activeElement.shadowRoot && activeElement.shadowRoot.activeElement) { | ||
activeElement = activeElement.shadowRoot.activeElement; | ||
} | ||
return currentItem && currentItem === activeElement; | ||
} | ||
_getCurrentItem() { | ||
const items = this._getItems(); | ||
if (!items.length) { | ||
return null; | ||
} | ||
// normalize the index | ||
while (this.currentIndex >= items.length) { | ||
this.currentIndex -= this.rowSize; | ||
} | ||
if (this.currentIndex < 0) { | ||
this.currentIndex = 0; | ||
} | ||
const currentItem = items[this.currentIndex]; | ||
if (!currentItem) { | ||
return; | ||
} | ||
if (currentItem.isUI5Element) { | ||
return currentItem.getFocusDomRef(); | ||
} | ||
if (!this.rootWebComponent.getDomRef()) { | ||
return; | ||
} | ||
return this.rootWebComponent.getDomRef().querySelector(`#${currentItem.id}`); | ||
} | ||
set getItemsCallback(fn) { | ||
this._getItems = fn; | ||
} | ||
set current(val) { | ||
this.currentIndex = val; | ||
} | ||
onOverflowBottomEdge() { | ||
const items = this._getItems(); | ||
const offset = this.currentIndex - items.length; | ||
if (this.behavior === ItemNavigationBehavior.Cyclic) { | ||
this.currentIndex = 0; | ||
return; | ||
} | ||
if (this.behavior === ItemNavigationBehavior.Paging) { | ||
this._handleNextPage(); | ||
} else { | ||
this.currentIndex = items.length - 1; | ||
} | ||
this.fireEvent(ItemNavigation.BORDER_REACH, { start: false, end: true, offset }); | ||
} | ||
onOverflowTopEdge() { | ||
const items = this._getItems(); | ||
const offset = this.currentIndex + this.rowSize; | ||
if (this.behavior === ItemNavigationBehavior.Cyclic) { | ||
this.currentIndex = items.length - 1; | ||
return; | ||
} | ||
if (this.behavior === ItemNavigationBehavior.Paging) { | ||
this._handlePrevPage(); | ||
} else { | ||
this.currentIndex = 0; | ||
} | ||
this.fireEvent(ItemNavigation.BORDER_REACH, { start: true, end: false, offset }); | ||
} | ||
_handleNextPage() { | ||
this.fireEvent(ItemNavigation.PAGE_BOTTOM); | ||
const items = this._getItems(); | ||
if (!this.hasNextPage) { | ||
this.currentIndex = items.length - 1; | ||
} else { | ||
this.currentIndex -= this.pageSize; | ||
} | ||
} | ||
_handlePrevPage() { | ||
this.fireEvent(ItemNavigation.PAGE_TOP); | ||
if (!this.hasPrevPage) { | ||
this.currentIndex = 0; | ||
} else { | ||
this.currentIndex = this.pageSize + this.currentIndex; | ||
} | ||
} | ||
import { instanceOfUI5Element } from "../UI5Element.js"; | ||
/** | ||
* The ItemNavigation class manages the calculations to determine the correct "tabindex" for a group of related items inside a root component. | ||
* Important: ItemNavigation only does the calculations and does not change "tabindex" directly, this is a responsibility of the developer. | ||
* | ||
* The keys that trigger ItemNavigation are: | ||
* - Up/down | ||
* - Left/right | ||
* - Home/End | ||
* | ||
* Usage: | ||
* 1) Use the "getItemsCallback" constructor property to pass a callback to ItemNavigation, which, whenever called, will return the list of items to navigate among. | ||
* | ||
* Each item passed to ItemNavigation via "getItemsCallback" must be: | ||
* - A) either a UI5Element with a "forcedTabIndex" property | ||
* - B) or an Object with "id" and "forcedTabIndex" properties which represents a part of the root component's shadow DOM. | ||
* The "id" must be a valid ID within the shadow root of the component ItemNavigation operates on. | ||
* This object must not be a DOM object because, as said, ItemNavigation will not set "tabindex" on it. It must be a representation of a DOM object only | ||
* and the developer has the responsibility to update the "tabindex" in the component's DOM. | ||
* - C) a combination of the above | ||
* | ||
* Whenever the user navigates with the keyboard, ItemNavigation will modify the "forcedTabIndex" properties of the items. | ||
* It is the items' responsibilities to re-render themselves and apply the correct value of "tabindex" (i.e. to map the "forcedTabIndex" ItemNavigation set to them to the "tabindex" property). | ||
* If the items of the ItemNavigation are UI5Elements themselves, this can happen naturally since they will be invalidated by their "forcedTabIndex" property. | ||
* If the items are Objects with "id" and "forcedTabIndex" however, it is the developer's responsibility to apply these and the easiest way is to have the root component invalidated by ItemNavigation. | ||
* To do so, set the "affectedPropertiesNames" constructor property to point to one or more of the root component's properties that need refreshing when "forcedTabIndex" is changed deeply. | ||
* | ||
* 2) Call the "setCurrentItem" method of ItemNavigation whenever you want to change the current item. | ||
* This is most commonly required if the user for example clicks on an item and thus selects it directly. | ||
* Pass as the only argument to "setCurrentItem" the item that becomes current (must be one of the items, returned by "getItemsCallback"). | ||
* | ||
* @class | ||
* @public | ||
*/ | ||
class ItemNavigation { | ||
/** | ||
* | ||
* @param rootWebComponent the component to operate on (component that slots or contains within its shadow root the items the user navigates among) | ||
* @param {ItemNavigationOptions} options Object with configuration options: | ||
* - currentIndex: the index of the item that will be initially selected (from which navigation will begin) | ||
* - navigationMode (Auto|Horizontal|Vertical): whether the items are displayed horizontally (Horizontal), vertically (Vertical) or as a matrix (Auto) meaning the user can navigate in both directions (up/down and left/right) | ||
* - rowSize: tells how many items per row there are when the items are not rendered as a flat list but rather as a matrix. Relevant for navigationMode=Auto | ||
* - skipItemsSize: tells how many items upon PAGE_UP and PAGE_DOWN should be skipped to applying the focus on the next item | ||
* - behavior (Static|Cycling): tells what to do when trying to navigate beyond the first and last items | ||
* Static means that nothing happens if the user tries to navigate beyond the first/last item. | ||
* Cycling means that when the user navigates beyond the last item they go to the first and vice versa. | ||
* - getItemsCallback: function that, when called, returns an array with all items the user can navigate among | ||
* - affectedPropertiesNames: a list of metadata properties on the root component which, upon user navigation, will be reassigned by address thus causing the root component to invalidate | ||
*/ | ||
constructor(rootWebComponent, options) { | ||
if (!rootWebComponent.isUI5Element) { | ||
throw new Error("The root web component must be a UI5 Element instance"); | ||
} | ||
this.rootWebComponent = rootWebComponent; | ||
this.rootWebComponent.addEventListener("keydown", this._onkeydown.bind(this)); | ||
this._initBound = this._init.bind(this); | ||
this.rootWebComponent.attachComponentStateFinalized(this._initBound); | ||
if (typeof options.getItemsCallback !== "function") { | ||
throw new Error("getItemsCallback is required"); | ||
} | ||
this._getItems = options.getItemsCallback; | ||
this._currentIndex = options.currentIndex || 0; | ||
this._rowSize = options.rowSize || 1; | ||
this._behavior = options.behavior || ItemNavigationBehavior.Static; | ||
this._navigationMode = options.navigationMode || NavigationMode.Auto; | ||
this._affectedPropertiesNames = options.affectedPropertiesNames || []; | ||
this._skipItemsSize = options.skipItemsSize || null; | ||
} | ||
/** | ||
* Call this method to set a new "current" (selected) item in the item navigation | ||
* Note: the item passed to this function must be one of the items, returned by the getItemsCallback function | ||
* | ||
* @public | ||
* @param current the new selected item | ||
*/ | ||
setCurrentItem(current) { | ||
const currentItemIndex = this._getItems().indexOf(current); | ||
if (currentItemIndex === -1) { | ||
console.warn(`The provided item is not managed by ItemNavigation`, current); // eslint-disable-line | ||
return; | ||
} | ||
this._currentIndex = currentItemIndex; | ||
this._applyTabIndex(); | ||
} | ||
/** | ||
* Call this method to dynamically change the row size | ||
* | ||
* @public | ||
* @param newRowSize | ||
*/ | ||
setRowSize(newRowSize) { | ||
this._rowSize = newRowSize; | ||
} | ||
_init() { | ||
this._getItems().forEach((item, idx) => { | ||
item.forcedTabIndex = (idx === this._currentIndex) ? "0" : "-1"; | ||
}); | ||
} | ||
_onkeydown(event) { | ||
if (!this._canNavigate()) { | ||
return; | ||
} | ||
const horizontalNavigationOn = this._navigationMode === NavigationMode.Horizontal || this._navigationMode === NavigationMode.Auto; | ||
const verticalNavigationOn = this._navigationMode === NavigationMode.Vertical || this._navigationMode === NavigationMode.Auto; | ||
const isRTL = this.rootWebComponent.effectiveDir === "rtl"; | ||
if (isRTL && isLeft(event) && horizontalNavigationOn) { | ||
this._handleRight(); | ||
} | ||
else if (isRTL && isRight(event) && horizontalNavigationOn) { | ||
this._handleLeft(); | ||
} | ||
else if (isLeft(event) && horizontalNavigationOn) { | ||
this._handleLeft(); | ||
} | ||
else if (isRight(event) && horizontalNavigationOn) { | ||
this._handleRight(); | ||
} | ||
else if (isUp(event) && verticalNavigationOn) { | ||
this._handleUp(); | ||
} | ||
else if (isDown(event) && verticalNavigationOn) { | ||
this._handleDown(); | ||
} | ||
else if (isHome(event)) { | ||
this._handleHome(); | ||
} | ||
else if (isEnd(event)) { | ||
this._handleEnd(); | ||
} | ||
else if (isPageUp(event)) { | ||
this._handlePageUp(); | ||
} | ||
else if (isPageDown(event)) { | ||
this._handlePageDown(); | ||
} | ||
else { | ||
return; // if none of the supported keys is pressed, we don't want to prevent the event or update the item navigation | ||
} | ||
event.preventDefault(); | ||
this._applyTabIndex(); | ||
this._focusCurrentItem(); | ||
} | ||
_handleUp() { | ||
const itemsLength = this._getItems().length; | ||
if (this._currentIndex - this._rowSize >= 0) { // no border reached, just decrease the index by a row | ||
this._currentIndex -= this._rowSize; | ||
return; | ||
} | ||
if (this._behavior === ItemNavigationBehavior.Cyclic) { // if cyclic, go to the **last** item in the **previous** column | ||
const firstItemInThisColumnIndex = this._currentIndex % this._rowSize; | ||
const firstItemInPreviousColumnIndex = firstItemInThisColumnIndex === 0 ? this._rowSize - 1 : firstItemInThisColumnIndex - 1; // find the first item in the previous column (if the current column is the first column -> move to the last column) | ||
const rows = Math.ceil(itemsLength / this._rowSize); // how many rows there are (even if incomplete, f.e. for 14 items and _rowSize=4 -> 4 rows total, although only 2 items on the last row) | ||
let lastItemInPreviousColumnIndex = firstItemInPreviousColumnIndex + (rows - 1) * this._rowSize; // multiply rows by columns, and add the column's first item's index | ||
if (lastItemInPreviousColumnIndex > itemsLength - 1) { // for incomplete rows, use the previous row's last item, as for them the last item is missing | ||
lastItemInPreviousColumnIndex -= this._rowSize; | ||
} | ||
this._currentIndex = lastItemInPreviousColumnIndex; | ||
} | ||
else { // not cyclic, so just go to the first item | ||
this._currentIndex = 0; | ||
} | ||
} | ||
_handleDown() { | ||
const itemsLength = this._getItems().length; | ||
if (this._currentIndex + this._rowSize < itemsLength) { // no border reached, just increase the index by a row | ||
this._currentIndex += this._rowSize; | ||
return; | ||
} | ||
if (this._behavior === ItemNavigationBehavior.Cyclic) { // if cyclic, go to the **first** item in the **next** column | ||
const firstItemInThisColumnIndex = this._currentIndex % this._rowSize; // find the first item in the current column first | ||
const firstItemInNextColumnIndex = (firstItemInThisColumnIndex + 1) % this._rowSize; // to get the first item in the next column, just increase the index by 1. The modulo by rows is for the case when we are at the last column | ||
this._currentIndex = firstItemInNextColumnIndex; | ||
} | ||
else { // not cyclic, so just go to the last item | ||
this._currentIndex = itemsLength - 1; | ||
} | ||
} | ||
_handleLeft() { | ||
const itemsLength = this._getItems().length; | ||
if (this._currentIndex > 0) { | ||
this._currentIndex -= 1; | ||
return; | ||
} | ||
if (this._behavior === ItemNavigationBehavior.Cyclic) { // go to the first item in the next column | ||
this._currentIndex = itemsLength - 1; | ||
} | ||
} | ||
_handleRight() { | ||
const itemsLength = this._getItems().length; | ||
if (this._currentIndex < itemsLength - 1) { | ||
this._currentIndex += 1; | ||
return; | ||
} | ||
if (this._behavior === ItemNavigationBehavior.Cyclic) { // go to the first item in the next column | ||
this._currentIndex = 0; | ||
} | ||
} | ||
_handleHome() { | ||
const homeEndRange = this._rowSize > 1 ? this._rowSize : this._getItems().length; | ||
this._currentIndex -= this._currentIndex % homeEndRange; | ||
} | ||
_handleEnd() { | ||
const homeEndRange = this._rowSize > 1 ? this._rowSize : this._getItems().length; | ||
this._currentIndex += (homeEndRange - 1 - this._currentIndex % homeEndRange); // eslint-disable-line | ||
} | ||
_handlePageUp() { | ||
if (this._rowSize > 1) { | ||
// eslint-disable-next-line | ||
// TODO: handle page up on matrix (grid) layout - ColorPalette, ProductSwitch. | ||
return; | ||
} | ||
this._handlePageUpFlat(); | ||
} | ||
_handlePageDown() { | ||
if (this._rowSize > 1) { | ||
// eslint-disable-next-line | ||
// TODO: handle page up on matrix (grid) layout - ColorPalette, ProductSwitch. | ||
return; | ||
} | ||
this._handlePageDownFlat(); | ||
} | ||
/** | ||
* Handles PAGE_UP in a flat list-like structure, both vertically and horizontally. | ||
*/ | ||
_handlePageUpFlat() { | ||
if (this._skipItemsSize === null) { | ||
// Move the focus to the very top (as Home). | ||
this._currentIndex -= this._currentIndex; | ||
return; | ||
} | ||
if (this._currentIndex + 1 > this._skipItemsSize) { | ||
// When there are more than "skipItemsSize" number of items to the top, | ||
// move the focus up/left with the predefined number. | ||
this._currentIndex -= this._skipItemsSize; | ||
} | ||
else { | ||
// Otherwise, move the focus to the very top (as Home). | ||
this._currentIndex -= this._currentIndex; | ||
} | ||
} | ||
/** | ||
* Handles PAGE_DOWN in a flat list-like structure, both vertically and horizontally. | ||
*/ | ||
_handlePageDownFlat() { | ||
if (this._skipItemsSize === null) { | ||
// Move the focus to the very bottom (as End). | ||
this._currentIndex = this._getItems().length - 1; | ||
return; | ||
} | ||
const currentToEndRange = this._getItems().length - this._currentIndex - 1; | ||
if (currentToEndRange > this._skipItemsSize) { | ||
// When there are more than "skipItemsSize" number of items until the bottom, | ||
// move the focus down/right with the predefined number. | ||
this._currentIndex += this._skipItemsSize; | ||
} | ||
else { | ||
// Otherwise, move the focus to the very bottom (as End). | ||
this._currentIndex = this._getItems().length - 1; | ||
} | ||
} | ||
_applyTabIndex() { | ||
const items = this._getItems(); | ||
for (let i = 0; i < items.length; i++) { | ||
items[i].forcedTabIndex = i === this._currentIndex ? "0" : "-1"; | ||
} | ||
this._affectedPropertiesNames.forEach(propName => { | ||
const prop = this.rootWebComponent[propName]; | ||
this.rootWebComponent[propName] = Array.isArray(prop) ? [...prop] : { ...prop }; | ||
}); | ||
} | ||
_focusCurrentItem() { | ||
const currentItem = this._getCurrentItem(); | ||
if (currentItem) { | ||
currentItem.focus(); | ||
} | ||
} | ||
_canNavigate() { | ||
const currentItem = this._getCurrentItem(); | ||
const activeElement = getActiveElement(); | ||
return currentItem && currentItem === activeElement; | ||
} | ||
_getCurrentItem() { | ||
const items = this._getItems(); | ||
if (!items.length) { | ||
return; | ||
} | ||
// normalize the index | ||
while (this._currentIndex >= items.length) { | ||
this._currentIndex -= this._rowSize; | ||
} | ||
if (this._currentIndex < 0) { | ||
this._currentIndex = 0; | ||
} | ||
const currentItem = items[this._currentIndex]; | ||
if (!currentItem) { | ||
return; | ||
} | ||
if (instanceOfUI5Element(currentItem)) { | ||
return currentItem.getFocusDomRef(); | ||
} | ||
const currentItemDOMRef = this.rootWebComponent.getDomRef(); | ||
if (!currentItemDOMRef) { | ||
return; | ||
} | ||
if (currentItem.id) { | ||
return currentItemDOMRef.querySelector(`[id="${currentItem.id}"]`); | ||
} | ||
} | ||
} | ||
ItemNavigation.PAGE_TOP = "PageTop"; | ||
ItemNavigation.PAGE_BOTTOM = "PageBottom"; | ||
ItemNavigation.BORDER_REACH = "_borderReach"; | ||
export default ItemNavigation; | ||
//# sourceMappingURL=ItemNavigation.js.map |
@@ -1,65 +0,85 @@ | ||
import NativeResize from "./NativeResize.js"; | ||
import CustomResize from "./CustomResize.js"; | ||
import { instanceOfUI5Element } from "../UI5Element.js"; | ||
let resizeObserver; | ||
const observedElements = new Map(); | ||
const getResizeObserver = () => { | ||
if (!resizeObserver) { | ||
resizeObserver = new window.ResizeObserver(entries => { | ||
window.requestAnimationFrame(() => { | ||
entries.forEach(entry => { | ||
const callbacks = observedElements.get(entry.target); | ||
// Callbacks could be async and we need to handle returned promises to comply with the eslint "no-misused-promises" rule. | ||
// Although Promise.all awaits all, we don't await the additional task after calling the callbacks and should not make any difference. | ||
callbacks && Promise.all(callbacks.map((callback) => callback())); | ||
}); | ||
}); | ||
}); | ||
} | ||
return resizeObserver; | ||
}; | ||
const observe = (element, callback) => { | ||
const callbacks = observedElements.get(element) || []; | ||
// if no callbacks have been added for this element - start observing it | ||
if (!callbacks.length) { | ||
getResizeObserver().observe(element); | ||
} | ||
// save the callbacks in an array | ||
observedElements.set(element, [...callbacks, callback]); | ||
}; | ||
const unobserve = (element, callback) => { | ||
const callbacks = observedElements.get(element) || []; | ||
if (callbacks.length === 0) { | ||
return; | ||
} | ||
const filteredCallbacks = callbacks.filter((fn) => fn !== callback); | ||
if (filteredCallbacks.length === 0) { | ||
getResizeObserver().unobserve(element); | ||
observedElements.delete(element); | ||
} | ||
else { | ||
observedElements.set(element, filteredCallbacks); | ||
} | ||
}; | ||
/** | ||
* Allows to register/deregister resize observers for a DOM element | ||
* | ||
* @public | ||
* @class | ||
*/ | ||
class ResizeHandler { | ||
static initialize() { | ||
ResizeHandler.Implementation = window.ResizeObserver ? NativeResize : CustomResize; | ||
ResizeHandler.Implementation.initialize(); | ||
} | ||
/** | ||
* @static | ||
* @private | ||
* @param {*} ref Reference to be observed | ||
* @param {*} callback Callback to be executed | ||
* @memberof ResizeHandler | ||
*/ | ||
static attachListener(ref, callback) { | ||
ResizeHandler.Implementation.attachListener.call(ResizeHandler.Implementation, ref, callback); | ||
} | ||
/** | ||
* @static | ||
* @private | ||
* @param {*} ref Reference to be unobserved | ||
* @memberof ResizeHandler | ||
*/ | ||
static detachListener(ref, callback) { | ||
ResizeHandler.Implementation.detachListener.call(ResizeHandler.Implementation, ref, callback); | ||
} | ||
/** | ||
* @static | ||
* @public | ||
* @param {*} ref Reference to a UI5 Web Component or DOM Element to be observed | ||
* @param {*} callback Callback to be executed | ||
* @memberof ResizeHandler | ||
*/ | ||
static register(ref, callback) { | ||
if (ref.isUI5Element) { | ||
ref = ref.getDomRef(); | ||
} | ||
ResizeHandler.attachListener(ref, callback); | ||
} | ||
/** | ||
* @static | ||
* @public | ||
* @param {*} ref Reference to UI5 Web Component or DOM Element to be unobserved | ||
* @memberof ResizeHandler | ||
*/ | ||
static deregister(ref, callback) { | ||
if (ref.isUI5Element) { | ||
ref = ref.getDomRef(); | ||
} | ||
ResizeHandler.detachListener(ref, callback); | ||
} | ||
/** | ||
* @public | ||
* @param element UI5 Web Component or DOM Element to be observed | ||
* @param callback Callback to be executed | ||
*/ | ||
static register(element, callback) { | ||
let effectiveElement = element; | ||
if (instanceOfUI5Element(effectiveElement)) { | ||
effectiveElement = effectiveElement.getDomRef(); | ||
} | ||
if (effectiveElement instanceof HTMLElement) { | ||
observe(effectiveElement, callback); | ||
} | ||
else { | ||
console.warn("Cannot register ResizeHandler for element", element); // eslint-disable-line | ||
} | ||
} | ||
/** | ||
* @public | ||
* @param element UI5 Web Component or DOM Element to be unobserved | ||
* @param callback Callback to be removed | ||
*/ | ||
static deregister(element, callback) { | ||
let effectiveElement = element; | ||
if (instanceOfUI5Element(effectiveElement)) { | ||
effectiveElement = effectiveElement.getDomRef(); | ||
} | ||
if (effectiveElement instanceof HTMLElement) { | ||
unobserve(effectiveElement, callback); | ||
} | ||
else { | ||
console.warn("Cannot deregister ResizeHandler for element", element); // eslint-disable-line | ||
} | ||
} | ||
} | ||
ResizeHandler.initialize(); | ||
export default ResizeHandler; | ||
//# sourceMappingURL=ResizeHandler.js.map |
@@ -1,159 +0,165 @@ | ||
import { isPhone } from "../Device.js"; | ||
import { supportsTouch } from "../Device.js"; | ||
import EventProvider from "../EventProvider.js"; | ||
import scroll from "../animations/scroll.js"; | ||
const scrollEventName = "scroll"; | ||
const touchEndEventName = isPhone() ? "touchend" : "mouseup"; | ||
const touchEndEventName = supportsTouch() ? "touchend" : "mouseup"; | ||
class ScrollEnablement extends EventProvider { | ||
constructor(containerComponent) { | ||
super(); | ||
this.containerComponent = containerComponent; | ||
this.mouseMove = this.ontouchmove.bind(this); | ||
this.mouseUp = this.ontouchend.bind(this); | ||
this.touchStart = this.ontouchstart.bind(this); | ||
this.isPhone = isPhone(); | ||
// On Android devices touchmove is thrown one more time than neccessary (together with touchend) | ||
// so we have to cache the previus coordinates in order to provide correct parameters in the | ||
// event for Android | ||
this.cachedValue = {}; | ||
// In components like Carousel you need to know if the user has clicked on something or swiped | ||
// in order to throw the needed event or not | ||
this.startX = 0; | ||
this.startY = 0; | ||
if (this.isPhone) { | ||
containerComponent.addEventListener("touchstart", this.touchStart, { passive: true }); | ||
containerComponent.addEventListener("touchmove", this.mouseMove, { passive: true }); | ||
containerComponent.addEventListener("touchend", this.mouseUp, { passive: true }); | ||
} else { | ||
containerComponent.addEventListener("mousedown", this.touchStart, { passive: true }); | ||
} | ||
} | ||
set scrollContainer(container) { | ||
this._container = container; | ||
} | ||
get scrollContainer() { | ||
return this._container; | ||
} | ||
scrollTo(left, top) { | ||
this._container.scrollLeft = left; | ||
this._container.scrollTop = top; | ||
} | ||
move(dx, dy) { | ||
return scroll({ | ||
element: this._container, | ||
dx, | ||
dy, | ||
}); | ||
} | ||
getScrollLeft() { | ||
return this._container.scrollLeft; | ||
} | ||
getScrollTop() { | ||
return this._container.scrollTop; | ||
} | ||
_isTouchInside(touch) { | ||
const rect = this._container.getBoundingClientRect(); | ||
const x = this.isPhone ? touch.clientX : touch.x; | ||
const y = this.isPhone ? touch.clientY : touch.y; | ||
return x >= rect.left && x <= rect.right | ||
&& y >= rect.top && y <= rect.bottom; | ||
} | ||
ontouchstart(event) { | ||
const touch = this.isPhone ? event.touches[0] : null; | ||
if (!this.isPhone) { | ||
document.addEventListener("mouseup", this.mouseUp, { passive: true }); | ||
document.addEventListener("mousemove", this.mouseMove, { passive: true }); | ||
} else { | ||
// Needed only on mobile | ||
this.startX = touch.pageX; | ||
this.startY = touch.pageY; | ||
} | ||
this._prevDragX = this.isPhone ? touch.pageX : event.x; | ||
this._prevDragY = this.isPhone ? touch.pageY : event.y; | ||
this._canScroll = this._isTouchInside(this.isPhone ? touch : event); | ||
} | ||
ontouchmove(event) { | ||
if (!this._canScroll) { | ||
return; | ||
} | ||
const container = this._container; | ||
const touch = this.isPhone ? event.touches[0] : null; | ||
const dragX = this.isPhone ? touch.pageX : event.x; | ||
const dragY = this.isPhone ? touch.pageY : event.y; | ||
container.scrollLeft += this._prevDragX - dragX; | ||
container.scrollTop += this._prevDragY - dragY; | ||
this.fireEvent(scrollEventName, { | ||
isLeft: dragX > this._prevDragX, | ||
isRight: dragX < this._prevDragX, | ||
}); | ||
this.cachedValue.dragX = this._prevDragX; | ||
this.cachedValue.dragY = this._prevDragY; | ||
this._prevDragX = dragX; | ||
this._prevDragY = dragY; | ||
} | ||
ontouchend(event) { | ||
if (this.isPhone) { | ||
const deltaX = Math.abs(event.changedTouches[0].pageX - this.startX); | ||
const deltaY = Math.abs(event.changedTouches[0].pageY - this.startY); | ||
if (deltaX < 10 && deltaY < 10) { | ||
return; | ||
} | ||
} | ||
if (!this._canScroll) { | ||
return; | ||
} | ||
const container = this._container; | ||
const dragX = this.isPhone ? event.changedTouches[0].pageX : event.x; | ||
const dragY = this.isPhone ? event.changedTouches[0].pageY : event.y; | ||
container.scrollLeft += this._prevDragX - dragX; | ||
container.scrollTop += this._prevDragY - dragY; | ||
const useCachedValues = dragX === this._prevDragX; | ||
const _dragX = useCachedValues ? this.cachedValue.dragX : dragX; | ||
// const _dragY = useCachedValues ? this.cachedValue.dragY : dragY; add if needed | ||
this.fireEvent(touchEndEventName, { | ||
isLeft: _dragX < this._prevDragX, | ||
isRight: _dragX > this._prevDragX, | ||
}); | ||
this._prevDragX = dragX; | ||
this._prevDragY = dragY; | ||
if (!this.isPhone) { | ||
document.removeEventListener("mousemove", this.mouseMove, { passive: true }); | ||
document.removeEventListener("mouseup", this.mouseUp); | ||
} | ||
} | ||
constructor(containerComponent) { | ||
super(); | ||
this.supportsTouch = supportsTouch(); | ||
this.containerComponent = containerComponent; | ||
this.mouseMove = this.ontouchmove.bind(this); | ||
this.mouseUp = this.ontouchend.bind(this); | ||
this.touchStart = this.ontouchstart.bind(this); | ||
this.supportsTouch = supportsTouch(); | ||
// On Android devices touchmove is thrown one more time than neccessary (together with touchend) | ||
// so we have to cache the previus coordinates in order to provide correct parameters in the | ||
// event for Android | ||
this.cachedValue = { dragX: 0, dragY: 0 }; | ||
// In components like Carousel you need to know if the user has clicked on something or swiped | ||
// in order to throw the needed event or not | ||
this.startX = 0; | ||
this.startY = 0; | ||
if (this.supportsTouch) { | ||
containerComponent.addEventListener("touchstart", this.touchStart, { passive: true }); | ||
containerComponent.addEventListener("touchmove", this.mouseMove, { passive: true }); | ||
containerComponent.addEventListener("touchend", this.mouseUp, { passive: true }); | ||
} | ||
else { | ||
containerComponent.addEventListener("mousedown", this.touchStart, { passive: true }); | ||
} | ||
} | ||
set scrollContainer(container) { | ||
this._container = container; | ||
} | ||
get scrollContainer() { | ||
return this._container; | ||
} | ||
/** | ||
* Scrolls the container to the left/top position, retrying retryCount times, if the container is not yet painted | ||
* | ||
* @param left | ||
* @param top | ||
* @param retryCount | ||
* @param retryInterval | ||
* @returns {Promise<void>} resolved when scrolled successfully | ||
*/ | ||
async scrollTo(left, top, retryCount = 0, retryInterval = 0) { | ||
let containerPainted = this.scrollContainer.clientHeight > 0 && this.scrollContainer.clientWidth > 0; | ||
/* eslint-disable no-loop-func, no-await-in-loop */ | ||
while (!containerPainted && retryCount > 0) { | ||
await new Promise(resolve => { | ||
setTimeout(() => { | ||
containerPainted = this.scrollContainer.clientHeight > 0 && this.scrollContainer.clientWidth > 0; | ||
retryCount--; | ||
resolve(); | ||
}, retryInterval); | ||
}); | ||
} | ||
/* eslint-disable no-loop-func, no-await-in-loop */ | ||
this._container.scrollLeft = left; | ||
this._container.scrollTop = top; | ||
} | ||
move(dx, dy, disableAnimation) { | ||
if (disableAnimation) { | ||
this._container.scrollLeft += dx; | ||
this._container.scrollTop += dy; | ||
return; | ||
} | ||
if (this._container) { | ||
return scroll(this._container, dx, dy); | ||
} | ||
} | ||
getScrollLeft() { | ||
return this._container.scrollLeft; | ||
} | ||
getScrollTop() { | ||
return this._container.scrollTop; | ||
} | ||
_isTouchInside(event) { | ||
let touch = null; | ||
if (this.supportsTouch && event instanceof TouchEvent) { | ||
touch = event.touches[0]; | ||
} | ||
const rect = this._container.getBoundingClientRect(); | ||
const x = this.supportsTouch ? touch.clientX : event.x; | ||
const y = this.supportsTouch ? touch.clientY : event.y; | ||
return x >= rect.left && x <= rect.right | ||
&& y >= rect.top && y <= rect.bottom; | ||
} | ||
ontouchstart(event) { | ||
let touch = null; | ||
if (this.supportsTouch && event instanceof TouchEvent) { | ||
touch = event.touches[0]; | ||
} | ||
if (!touch) { | ||
document.addEventListener("mouseup", this.mouseUp, { passive: true }); | ||
document.addEventListener("mousemove", this.mouseMove, { passive: true }); | ||
} | ||
else { | ||
// Needed only on mobile | ||
this.startX = touch.pageX; | ||
this.startY = touch.pageY; | ||
} | ||
if (touch) { | ||
this._prevDragX = touch.pageX; | ||
this._prevDragY = touch.pageY; | ||
} | ||
if (event instanceof MouseEvent) { | ||
this._prevDragX = event.x; | ||
this._prevDragY = event.y; | ||
} | ||
this._canScroll = this._isTouchInside(event); | ||
} | ||
ontouchmove(event) { | ||
if (!this._canScroll) { | ||
return; | ||
} | ||
const container = this._container; | ||
const touch = this.supportsTouch ? event.touches[0] : null; | ||
const dragX = this.supportsTouch ? touch.pageX : event.x; | ||
const dragY = this.supportsTouch ? touch.pageY : event.y; | ||
container.scrollLeft += this._prevDragX - dragX; | ||
container.scrollTop += this._prevDragY - dragY; | ||
this.fireEvent(scrollEventName, { | ||
isLeft: dragX > this._prevDragX, | ||
isRight: dragX < this._prevDragX, | ||
}); | ||
this.cachedValue.dragX = this._prevDragX; | ||
this.cachedValue.dragY = this._prevDragY; | ||
this._prevDragX = dragX; | ||
this._prevDragY = dragY; | ||
} | ||
ontouchend(event) { | ||
if (this.supportsTouch) { | ||
const deltaX = Math.abs(event.changedTouches[0].pageX - this.startX); | ||
const deltaY = Math.abs(event.changedTouches[0].pageY - this.startY); | ||
if (deltaX < 10 && deltaY < 10) { | ||
return; | ||
} | ||
} | ||
if (!this._canScroll) { | ||
return; | ||
} | ||
const container = this._container; | ||
const dragX = this.supportsTouch ? event.changedTouches[0].pageX : event.x; | ||
const dragY = this.supportsTouch ? event.changedTouches[0].pageY : event.y; | ||
container.scrollLeft += this._prevDragX - dragX; | ||
container.scrollTop += this._prevDragY - dragY; | ||
const useCachedValues = dragX === this._prevDragX; | ||
const _dragX = useCachedValues ? this.cachedValue.dragX : dragX; | ||
// const _dragY = useCachedValues ? this.cachedValue.dragY : dragY; add if needed | ||
this.fireEvent(touchEndEventName, { | ||
isLeft: _dragX < this._prevDragX, | ||
isRight: _dragX > this._prevDragX, | ||
}); | ||
this._prevDragX = dragX; | ||
this._prevDragY = dragY; | ||
if (!this.supportsTouch) { | ||
document.removeEventListener("mousemove", this.mouseMove); | ||
document.removeEventListener("mouseup", this.mouseUp); | ||
} | ||
} | ||
} | ||
export default ScrollEnablement; | ||
//# sourceMappingURL=ScrollEnablement.js.map |
@@ -1,823 +0,175 @@ | ||
/** | ||
* Device and Feature Detection API: Provides information about the used browser / device and cross platform support for certain events | ||
* like media queries, orientation change or resizing. | ||
* | ||
* This API is independent from any other part of the UI5 framework. This allows it to be loaded beforehand, if it is needed, to create the UI5 bootstrap | ||
* dynamically depending on the capabilities of the browser or device. | ||
* | ||
* @namespace | ||
* @name Device | ||
*/ | ||
const Device = {}; | ||
//* ******* OS Detection ******** | ||
/** | ||
* Contains information about the operating system of the Device. | ||
* @name Device.os | ||
*/ | ||
/** | ||
* Enumeration containing the names of known operating systems. | ||
* @name Device.os.OS | ||
*/ | ||
/** | ||
* The name of the operating system. | ||
* @name Device.os.name | ||
* @type String | ||
*/ | ||
/** | ||
* The version of the operating system as <code>string</code>. Might be empty if no version can be determined. | ||
* @name Device.os.versionStr | ||
* @type String | ||
*/ | ||
/** | ||
* The version of the operating system as <code>float</code>. Might be <code>-1</code> if no version can be determined. | ||
* @name Device.os.version | ||
* @type float | ||
*/ | ||
/** | ||
* If this flag is set to <code>true</code>, a Windows operating system is used. | ||
* @name Device.os.windows | ||
* @type boolean | ||
*/ | ||
/** | ||
* If this flag is set to <code>true</code>, a Mac operating system is used. | ||
* @name Device.os.macintosh | ||
* @type boolean | ||
*/ | ||
/* | ||
* If this flag is set to <code>true</code>, an iOS operating system is used. | ||
* @name Device.os.ios | ||
* @type boolean | ||
*/ | ||
/** | ||
* If this flag is set to <code>true</code>, an Android operating system is used. | ||
* @name Device.os.android | ||
* @type boolean | ||
*/ | ||
/* | ||
* Windows operating system name. | ||
* @see Device.os.name | ||
* @name Device.os.OS.WINDOWS | ||
*/ | ||
/** | ||
* MAC operating system name. | ||
* @see Device.os.name | ||
* @name Device.os.OS.MACINTOSH | ||
*/ | ||
/** | ||
* iOS operating system name. | ||
* @see Device.os.name | ||
* @name Device.os.OS.IOS | ||
*/ | ||
/** | ||
* Android operating system name. | ||
* @see Device.os.name | ||
* @name Device.os.OS.ANDROID | ||
*/ | ||
const OS = { | ||
"WINDOWS": "win", | ||
"MACINTOSH": "mac", | ||
"IOS": "iOS", | ||
"ANDROID": "Android", | ||
const isSSR = typeof document === "undefined"; | ||
const internals = { | ||
get userAgent() { | ||
if (isSSR) { | ||
return ""; | ||
} | ||
return navigator.userAgent; | ||
}, | ||
get touch() { | ||
if (isSSR) { | ||
return false; | ||
} | ||
return "ontouchstart" in window || navigator.maxTouchPoints > 0; | ||
}, | ||
get ie() { | ||
if (isSSR) { | ||
return false; | ||
} | ||
return /(msie|trident)/i.test(internals.userAgent); | ||
}, | ||
get chrome() { | ||
if (isSSR) { | ||
return false; | ||
} | ||
return !internals.ie && /(Chrome|CriOS)/.test(internals.userAgent); | ||
}, | ||
get firefox() { | ||
if (isSSR) { | ||
return false; | ||
} | ||
return /Firefox/.test(internals.userAgent); | ||
}, | ||
get safari() { | ||
if (isSSR) { | ||
return false; | ||
} | ||
return !internals.ie && !internals.chrome && /(Version|PhantomJS)\/(\d+\.\d+).*Safari/.test(internals.userAgent); | ||
}, | ||
get webkit() { | ||
if (isSSR) { | ||
return false; | ||
} | ||
return !internals.ie && /webkit/.test(internals.userAgent); | ||
}, | ||
get windows() { | ||
if (isSSR) { | ||
return false; | ||
} | ||
return navigator.platform.indexOf("Win") !== -1; | ||
}, | ||
get macOS() { | ||
if (isSSR) { | ||
return false; | ||
} | ||
return !!navigator.userAgent.match(/Macintosh|Mac OS X/i); | ||
}, | ||
get iOS() { | ||
if (isSSR) { | ||
return false; | ||
} | ||
return !!(navigator.platform.match(/iPhone|iPad|iPod/)) || !!(internals.userAgent.match(/Mac/) && "ontouchend" in document); | ||
}, | ||
get android() { | ||
if (isSSR) { | ||
return false; | ||
} | ||
return !internals.windows && /Android/.test(internals.userAgent); | ||
}, | ||
get androidPhone() { | ||
if (isSSR) { | ||
return false; | ||
} | ||
return internals.android && /(?=android)(?=.*mobile)/i.test(internals.userAgent); | ||
}, | ||
get ipad() { | ||
if (isSSR) { | ||
return false; | ||
} | ||
// With iOS 13 the string 'iPad' was removed from the user agent string through a browser setting, which is applied on all sites by default: | ||
// "Request Desktop Website -> All websites" (for more infos see: https://forums.developer.apple.com/thread/119186). | ||
// Therefore the OS is detected as MACINTOSH instead of iOS and the device is a tablet if the Device.support.touch is true. | ||
return /ipad/i.test(internals.userAgent) || (/Macintosh/i.test(internals.userAgent) && "ontouchend" in document); | ||
}, | ||
}; | ||
const _getMobileOS = () => { | ||
const userAgent = navigator.userAgent; | ||
let rPlatform, // regular expression for platform | ||
aMatches; | ||
// iOS, Android | ||
rPlatform = /\(([a-zA-Z ]+);\s(?:[U]?[;]?)([\D]+)((?:[\d._]*))(?:.*[)][^\d]*)([\d.]*)\s/; | ||
aMatches = userAgent.match(rPlatform); | ||
if (aMatches) { | ||
const rAppleDevices = /iPhone|iPad|iPod/; | ||
if (aMatches[0].match(rAppleDevices)) { | ||
aMatches[3] = aMatches[3].replace(/_/g, "."); | ||
return ({ | ||
"name": OS.IOS, | ||
"versionStr": aMatches[3], | ||
}); | ||
} | ||
if (aMatches[2].match(/Android/)) { | ||
aMatches[2] = aMatches[2].replace(/\s/g, ""); | ||
return ({ | ||
"name": OS.ANDROID, | ||
"versionStr": aMatches[3], | ||
}); | ||
} | ||
} | ||
// Firefox on Android | ||
rPlatform = /\((Android)[\s]?([\d][.\d]*)?;.*Firefox\/[\d][.\d]*/; | ||
aMatches = userAgent.match(rPlatform); | ||
if (aMatches) { | ||
return ({ | ||
"name": OS.ANDROID, | ||
"versionStr": aMatches.length === 3 ? aMatches[2] : "", | ||
}); | ||
} | ||
let windowsVersion; | ||
let webkitVersion; | ||
let tablet; | ||
const isWindows8OrAbove = () => { | ||
if (isSSR) { | ||
return false; | ||
} | ||
if (!internals.windows) { | ||
return false; | ||
} | ||
if (windowsVersion === undefined) { | ||
const matches = internals.userAgent.match(/Windows NT (\d+).(\d)/); | ||
windowsVersion = matches ? parseFloat(matches[1]) : 0; | ||
} | ||
return windowsVersion >= 8; | ||
}; | ||
const _getDesktopOS = () => { | ||
const sPlatform = navigator.platform; | ||
if (sPlatform.indexOf("Win") !== -1) { | ||
const rVersion = /Windows NT (\d+).(\d)/i; // userAgent since windows 10: Windows NT 10[...] | ||
const uaResult = navigator.userAgent.match(rVersion); | ||
return { | ||
"name": OS.WINDOWS, | ||
"versionStr": uaResult[1], | ||
}; | ||
} | ||
if (sPlatform.indexOf("Mac") !== -1) { | ||
return { | ||
"name": OS.MACINTOSH, | ||
"versionStr": "", | ||
}; | ||
} | ||
return null; | ||
const isWebkit537OrAbove = () => { | ||
if (isSSR) { | ||
return false; | ||
} | ||
if (!internals.webkit) { | ||
return false; | ||
} | ||
if (webkitVersion === undefined) { | ||
const matches = internals.userAgent.match(/(webkit)[ /]([\w.]+)/); | ||
webkitVersion = matches ? parseFloat(matches[1]) : 0; | ||
} | ||
return webkitVersion >= 537.10; | ||
}; | ||
const _getOS = () => { | ||
return _getMobileOS() || _getDesktopOS(); | ||
const detectTablet = () => { | ||
if (isSSR) { | ||
return false; | ||
} | ||
if (tablet !== undefined) { | ||
return; | ||
} | ||
if (internals.ipad) { | ||
tablet = true; | ||
return; | ||
} | ||
if (internals.touch) { | ||
if (isWindows8OrAbove()) { | ||
tablet = true; | ||
return; | ||
} | ||
if (internals.chrome && internals.android) { | ||
tablet = !/Mobile Safari\/[.0-9]+/.test(internals.userAgent); | ||
return; | ||
} | ||
let densityFactor = window.devicePixelRatio ? window.devicePixelRatio : 1; // may be undefined in Windows Phone devices | ||
if (internals.android && isWebkit537OrAbove()) { | ||
densityFactor = 1; | ||
} | ||
tablet = (Math.min(window.screen.width / densityFactor, window.screen.height / densityFactor) >= 600); | ||
return; | ||
} | ||
tablet = (internals.ie && internals.userAgent.indexOf("Touch") !== -1) || (internals.android && !internals.androidPhone); | ||
}; | ||
const _setOS = () => { | ||
if (Device.os) { | ||
return; | ||
} | ||
Device.os = _getOS() || {}; | ||
Device.os.OS = OS; | ||
Device.os.version = Device.os.versionStr ? parseFloat(Device.os.versionStr) : -1; | ||
if (Device.os.name) { | ||
Object.keys(OS).forEach(name => { | ||
if (OS[name] === Device.os.name) { | ||
Device.os[name.toLowerCase()] = true; | ||
} | ||
}); | ||
} | ||
const supportsTouch = () => internals.touch; | ||
const isIE = () => internals.ie; | ||
const isSafari = () => internals.safari; | ||
const isChrome = () => internals.chrome; | ||
const isFirefox = () => internals.firefox; | ||
const isTablet = () => { | ||
detectTablet(); | ||
return (internals.touch || isWindows8OrAbove()) && tablet; | ||
}; | ||
const getOS = () => { | ||
if (!Device.os) { | ||
_setOS(); | ||
} | ||
return Device.os; | ||
const isPhone = () => { | ||
detectTablet(); | ||
return internals.touch && !tablet; | ||
}; | ||
const isAndroid = () => { | ||
if (!Device.os) { | ||
_setOS(); | ||
} | ||
return !!Device.os.android; | ||
}; | ||
//* ******* Browser Detection ******** | ||
/** | ||
* Contains information about the used browser. | ||
* @name Device.browser | ||
*/ | ||
/** | ||
* Enumeration containing the names of known browsers. | ||
* @name Device.browser.BROWSER | ||
* | ||
* The name of the browser. | ||
* @name Device.browser.name | ||
* @type String | ||
*/ | ||
/** | ||
* The version of the browser as <code>string</code>. Might be empty if no version can be determined. | ||
* @name Device.browser.versionStr | ||
* @type String | ||
*/ | ||
/** | ||
* The version of the browser as <code>float</code>. Might be <code>-1</code> if no version can be determined. | ||
* @name Device.browser.version | ||
* @type float | ||
*/ | ||
/** | ||
* If this flag is set to <code>true</code>, the mobile variant of the browser is used or | ||
* a tablet or phone device is detected. This information might not be available for all browsers. | ||
* @name Device.browser.mobile | ||
* @type boolean | ||
*/ | ||
/** | ||
* If this flag is set to <code>true</code>, the Microsoft Internet Explorer browser is used. | ||
* @name Device.browser.internet_explorer | ||
* @type boolean | ||
* @deprecated since 1.20, use {@link Device.browser.msie} instead. | ||
*/ | ||
/** | ||
* If this flag is set to <code>true</code>, the Microsoft Internet Explorer browser is used. | ||
* @name Device.browser.msie | ||
* @type boolean | ||
*/ | ||
/** | ||
* If this flag is set to <code>true</code>, the Microsoft Edge browser is used. | ||
* @name Device.browser.edge | ||
* @type boolean | ||
*/ | ||
/** | ||
* If this flag is set to <code>true</code>, the Mozilla Firefox browser is used. | ||
* @name Device.browser.firefox | ||
*/ | ||
/** | ||
* If this flag is set to <code>true</code>, the Google Chrome browser is used. | ||
* @name Device.browser.chrome | ||
* @type boolean | ||
* | ||
* If this flag is set to <code>true</code>, the Apple Safari browser is used. | ||
* | ||
* <b>Note:</b> | ||
* This flag is also <code>true</code> when the standalone (fullscreen) mode or webview is used on iOS devices. | ||
* Please also note the flags {@link Device.browser.fullscreen} and {@link Device.browser.webview}. | ||
* | ||
* @name Device.browser.safari | ||
* @type boolean | ||
*/ | ||
/** | ||
* If this flag is set to <code>true</code>, a browser featuring a Webkit engine is used. | ||
* | ||
* <b>Note:</b> | ||
* This flag is also <code>true</code> when the used browser was based on the Webkit engine, but | ||
* uses another rendering engine in the meantime. For example the Chrome browser started from version 28 and above | ||
* uses the Blink rendering engine. | ||
* | ||
* @name Device.browser.webkit | ||
* @type boolean | ||
*/ | ||
/** | ||
* If this flag is set to <code>true</code>, the Safari browser runs in standalone fullscreen mode on iOS. | ||
* | ||
* <b>Note:</b> This flag is only available if the Safari browser was detected. Furthermore, if this mode is detected, | ||
* technically not a standard Safari is used. There might be slight differences in behavior and detection, e.g. | ||
* the availability of {@link Device.browser.version}. | ||
* | ||
* @name Device.browser.fullscreen | ||
* @type boolean | ||
*/ | ||
/** | ||
* If this flag is set to <code>true</code>, the Safari browser runs in webview mode on iOS. | ||
* | ||
* <b>Note:</b> This flag is only available if the Safari browser was detected. Furthermore, if this mode is detected, | ||
* technically not a standard Safari is used. There might be slight differences in behavior and detection, e.g. | ||
* the availability of {@link Device.browser.version}. | ||
* | ||
* @name Device.browser.webview | ||
* @type boolean | ||
*/ | ||
/** | ||
* If this flag is set to <code>true</code>, the Phantom JS browser is used. | ||
* @name Device.browser.phantomJS | ||
* @type boolean | ||
*/ | ||
/** | ||
* The version of the used Webkit engine, if available. | ||
* @name Device.browser.webkitVersion | ||
* @type String | ||
*/ | ||
/** | ||
* If this flag is set to <code>true</code>, a browser featuring a Mozilla engine is used. | ||
* @name Device.browser.mozilla | ||
* @type boolean | ||
*/ | ||
/** | ||
* Internet Explorer browser name. | ||
* @name Device.browser.BROWSER.INTERNET_EXPLORER | ||
*/ | ||
/** | ||
* Edge browser name. | ||
* @name Device.browser.BROWSER.EDGE | ||
*/ | ||
/** | ||
* Firefox browser name. | ||
* @name Device.browser.BROWSER.FIREFOX | ||
*/ | ||
/** | ||
* Chrome browser name. | ||
* @name Device.browser.BROWSER.CHROME | ||
*/ | ||
/** | ||
* Safari browser name. | ||
* @name Device.browser.BROWSER.SAFARI | ||
*/ | ||
/** | ||
* Android stock browser name. | ||
* @name Device.browser.BROWSER.ANDROID | ||
*/ | ||
const BROWSER = { | ||
"INTERNET_EXPLORER": "ie", | ||
"EDGE": "ed", | ||
"FIREFOX": "ff", | ||
"CHROME": "cr", | ||
"SAFARI": "sf", | ||
"ANDROID": "an", | ||
}; | ||
/*! | ||
* Taken from jQuery JavaScript Library v1.7.1 | ||
* http://jquery.com/ | ||
* | ||
* Copyright 2011, John Resig | ||
* Dual licensed under the MIT or GPL Version 2 licenses. | ||
* http://jquery.org/license | ||
* | ||
* Includes Sizzle.js | ||
* http://sizzlejs.com/ | ||
* Copyright 2011, The Dojo Foundation | ||
* Released under the MIT, BSD, and GPL Licenses. | ||
* | ||
* Date: Mon Nov 21 21:11:03 2011 -0500 | ||
*/ | ||
const _calcBrowser = () => { | ||
const sUserAgent = navigator.userAgent.toLowerCase(); | ||
const rwebkit = /(webkit)[ /]([\w.]+)/; | ||
const rmsie = /(msie) ([\w.]+)/; | ||
const rmsie11 = /(trident)\/[\w.]+;.*rv:([\w.]+)/; | ||
const redge = /(edge)[ /]([\w.]+)/; | ||
const rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/; | ||
// WinPhone IE11 and MS Edge userAgents contain "WebKit" and "Mozilla" and therefore must be checked first | ||
const browserMatch = redge.exec(sUserAgent) | ||
|| rmsie11.exec(sUserAgent) | ||
|| rwebkit.exec(sUserAgent) | ||
|| rmsie.exec(sUserAgent) | ||
|| (sUserAgent.indexOf("compatible") < 0 && rmozilla.exec(sUserAgent)) || []; | ||
const oRes = { | ||
browser: browserMatch[1] || "", | ||
version: browserMatch[2] || "0", | ||
}; | ||
oRes[oRes.browser] = true; | ||
return oRes; | ||
}; | ||
const _getBrowser = () => { | ||
const oBrowser = _calcBrowser(); | ||
const sUserAgent = navigator.userAgent; | ||
const oNavigator = window.navigator; | ||
// jQuery checks for user agent strings. We differentiate between browsers | ||
let oExpMobile; | ||
let oResult; | ||
let fVersion; | ||
// Mozilla | ||
if (oBrowser.mozilla) { | ||
oExpMobile = /Mobile/; | ||
if (sUserAgent.match(/Firefox\/(\d+\.\d+)/)) { | ||
fVersion = parseFloat(RegExp.$1); | ||
oResult = { | ||
name: BROWSER.FIREFOX, | ||
versionStr: `${fVersion}`, | ||
version: fVersion, | ||
mozilla: true, | ||
mobile: oExpMobile.test(sUserAgent), | ||
}; | ||
} else { | ||
// unknown mozilla browser | ||
oResult = { | ||
mobile: oExpMobile.test(sUserAgent), | ||
mozilla: true, | ||
version: -1, | ||
}; | ||
} | ||
} else if (oBrowser.webkit) { | ||
// webkit version is needed for calculation if the mobile android device is a tablet (calculation of other mobile devices work without) | ||
const regExpWebkitVersion = sUserAgent.toLowerCase().match(/webkit[/]([\d.]+)/); | ||
let webkitVersion; | ||
if (regExpWebkitVersion) { | ||
webkitVersion = regExpWebkitVersion[1]; | ||
} | ||
oExpMobile = /Mobile/; | ||
const aChromeMatch = sUserAgent.match(/(Chrome|CriOS)\/(\d+\.\d+).\d+/); | ||
const aFirefoxMatch = sUserAgent.match(/FxiOS\/(\d+\.\d+)/); | ||
const aAndroidMatch = sUserAgent.match(/Android .+ Version\/(\d+\.\d+)/); | ||
if (aChromeMatch || aFirefoxMatch || aAndroidMatch) { | ||
let sName, | ||
sVersion, | ||
bMobile; | ||
if (aChromeMatch) { | ||
sName = BROWSER.CHROME; | ||
bMobile = oExpMobile.test(sUserAgent); | ||
sVersion = parseFloat(aChromeMatch[2]); | ||
} else if (aFirefoxMatch) { | ||
sName = BROWSER.FIREFOX; | ||
bMobile = true; | ||
sVersion = parseFloat(aFirefoxMatch[1]); | ||
} else if (aAndroidMatch) { | ||
sName = BROWSER.ANDROID; | ||
bMobile = oExpMobile.test(sUserAgent); | ||
sVersion = parseFloat(aAndroidMatch[1]); | ||
} | ||
oResult = { | ||
name: sName, | ||
mobile: bMobile, | ||
versionStr: `${sVersion}`, | ||
version: sVersion, | ||
webkit: true, | ||
webkitVersion, | ||
}; | ||
} else { // Safari might have an issue with sUserAgent.match(...); thus changing | ||
const oExp = /(Version|PhantomJS)\/(\d+\.\d+).*Safari/; | ||
const bStandalone = oNavigator.standalone; | ||
if (oExp.test(sUserAgent)) { | ||
const aParts = oExp.exec(sUserAgent); | ||
fVersion = parseFloat(aParts[2]); | ||
oResult = { | ||
name: BROWSER.SAFARI, | ||
versionStr: `${fVersion}`, | ||
fullscreen: false, | ||
webview: false, | ||
version: fVersion, | ||
mobile: oExpMobile.test(sUserAgent), | ||
webkit: true, | ||
webkitVersion, | ||
phantomJS: aParts[1] === "PhantomJS", | ||
}; | ||
} else if (/iPhone|iPad|iPod/.test(sUserAgent) && !(/CriOS/.test(sUserAgent)) && !(/FxiOS/.test(sUserAgent)) && (bStandalone === true || bStandalone === false)) { | ||
// WebView or Standalone mode on iOS | ||
oResult = { | ||
name: BROWSER.SAFARI, | ||
version: -1, | ||
fullscreen: bStandalone, | ||
webview: !bStandalone, | ||
mobile: oExpMobile.test(sUserAgent), | ||
webkit: true, | ||
webkitVersion, | ||
}; | ||
} else { // other webkit based browser | ||
oResult = { | ||
mobile: oExpMobile.test(sUserAgent), | ||
webkit: true, | ||
webkitVersion, | ||
version: -1, | ||
}; | ||
} | ||
} | ||
} else if (oBrowser.msie || oBrowser.trident) { | ||
fVersion = parseFloat(oBrowser.version); | ||
oResult = { | ||
name: BROWSER.INTERNET_EXPLORER, | ||
versionStr: `${fVersion}`, | ||
version: fVersion, | ||
msie: true, | ||
mobile: false, | ||
}; | ||
} else if (oBrowser.edge) { | ||
fVersion = parseFloat(oBrowser.version); | ||
oResult = { | ||
name: BROWSER.EDGE, | ||
versionStr: `${fVersion}`, | ||
version: fVersion, | ||
edge: true, | ||
}; | ||
} else { | ||
oResult = { | ||
name: "", | ||
versionStr: "", | ||
version: -1, | ||
mobile: false, | ||
}; | ||
} | ||
return oResult; | ||
}; | ||
const _setBrowser = () => { | ||
Device.browser = _getBrowser(); | ||
Device.browser.BROWSER = BROWSER; | ||
if (Device.browser.name) { | ||
Object.keys(BROWSER).forEach(b => { | ||
if (BROWSER[b] === Device.browser.name) { | ||
Device.browser[b.toLowerCase()] = true; | ||
} | ||
}); | ||
} | ||
}; | ||
const getBrowser = () => { | ||
if (!Device.browser) { | ||
_setBrowser(); | ||
} | ||
return Device.browser; | ||
}; | ||
const isIE = () => { | ||
if (!Device.browser) { | ||
_setBrowser(); | ||
} | ||
return !!Device.browser.msie; | ||
}; | ||
const isEdge = () => { | ||
if (!Device.browser) { | ||
_setBrowser(); | ||
} | ||
return !!Device.browser.edge; | ||
}; | ||
const isChrome = () => { | ||
if (!Device.browser) { | ||
_setBrowser(); | ||
} | ||
return !!Device.browser.chrome; | ||
}; | ||
const isFF = () => { | ||
if (!Device.browser) { | ||
_setBrowser(); | ||
} | ||
return !!Device.browser.firefox; | ||
}; | ||
const isSafari = () => { | ||
if (!Device.browser) { | ||
_setBrowser(); | ||
} | ||
return !!Device.browser.safari; | ||
}; | ||
//* ******* Support Detection ******** | ||
const _setSupport = () => { | ||
if (Device.support) { | ||
return; | ||
} | ||
if (!Device.browser) { | ||
_setBrowser(); | ||
} | ||
Device.support = {}; | ||
Device.support.touch = !!(("ontouchstart" in window) || (navigator.maxTouchPoints > 0) || (window.DocumentTouch && document instanceof window.DocumentTouch)); | ||
}; | ||
const supportTouch = () => { | ||
if (!Device.support) { | ||
_setSupport(); | ||
} | ||
return !!Device.support.touch; | ||
}; | ||
//* ******* System Detection ******** | ||
/** | ||
* Provides a basic categorization of the used device based on various indicators. | ||
* | ||
* <b>Note:</b> Depending on the capabilities of the device it is also possible that multiple flags are set to <code>true</code>. | ||
* | ||
* @namespace | ||
* @name Device.system | ||
*/ | ||
/** | ||
* If this flag is set to <code>true</code>, the device is recognized as a tablet. | ||
* | ||
* <b>Note:</b> This flag is also true for some browsers on desktop devices running on Windows 8 or higher. | ||
* Also see the documentation for {@link Device.system.combi} devices. | ||
* You can use the following logic to ensure that the current device is a tablet device: | ||
* | ||
* <pre> | ||
* if(Device.system.tablet && !Device.system.desktop){ | ||
* ...tablet related commands... | ||
* } | ||
* </pre> | ||
* | ||
* @name Device.system.tablet | ||
* @type boolean | ||
*/ | ||
/** | ||
* If this flag is set to <code>true</code>, the device is recognized as a phone. | ||
* | ||
* @name Device.system.phone | ||
* @type boolean | ||
*/ | ||
/** | ||
* If this flag is set to <code>true</code>, the device is recognized as a desktop system. | ||
* | ||
* @name Device.system.desktop | ||
* @type boolean | ||
*/ | ||
/** | ||
* Indicates if the device is recognized as a combination of a desktop system and tablet. | ||
* | ||
* <b>Note:</b> This property is mainly for Microsoft Windows 8 (and following) devices where the mouse and touch event may be supported | ||
* natively by the browser being used. This property is set to <code>true</code> only when both mouse and touch event are natively supported. | ||
* | ||
* @name Device.system.combi | ||
* @type boolean | ||
*/ | ||
/** | ||
* @name Device.system.SYSTEMTYPE | ||
* Enumeration containing the names of known types of the devices. | ||
*/ | ||
const SYSTEMTYPE = { | ||
"TABLET": "tablet", | ||
"PHONE": "phone", | ||
"DESKTOP": "desktop", | ||
"COMBI": "combi", | ||
}; | ||
const _isTablet = () => { | ||
const sUserAgent = navigator.userAgent; | ||
if (Device.os.name === Device.os.OS.IOS) { | ||
return /ipad/i.test(sUserAgent); | ||
} | ||
// in real mobile device | ||
if (supportTouch()) { | ||
if (Device.os.windows && Device.os.version >= 8) { | ||
return true; | ||
} | ||
if (Device.browser.chrome && Device.os.android && Device.os.version >= 4.4) { | ||
// From Android version 4.4, WebView also uses Chrome as Kernel. | ||
// We can use the user agent pattern defined in Chrome to do phone/tablet detection | ||
// According to the information here: https://developer.chrome.com/multidevice/user-agent#chrome_for_android_user_agent, | ||
// the existence of "Mobile" indicates it's a phone. But because the crosswalk framework which is used in Fiori Client | ||
// inserts another "Mobile" to the user agent for both tablet and phone, we need to check whether "Mobile Safari/<Webkit Rev>" exists. | ||
return !/Mobile Safari\/[.0-9]+/.test(sUserAgent); | ||
} | ||
let densityFactor = window.devicePixelRatio ? window.devicePixelRatio : 1; // may be undefined in Windows Phone devices | ||
// On Android sometimes window.screen.width returns the logical CSS pixels, sometimes the physical device pixels; | ||
// Tests on multiple devices suggest this depends on the Webkit version. | ||
// The Webkit patch which changed the behavior was done here: https://bugs.webkit.org/show_bug.cgi?id=106460 | ||
// Chrome 27 with Webkit 537.36 returns the logical pixels, | ||
// Chrome 18 with Webkit 535.19 returns the physical pixels. | ||
// The BlackBerry 10 browser with Webkit 537.10+ returns the physical pixels. | ||
// So it appears like somewhere above Webkit 537.10 we do not hve to divide by the devicePixelRatio anymore. | ||
if (Device.os.android && Device.browser.webkit && (parseFloat(Device.browser.webkitVersion) > 537.10)) { | ||
densityFactor = 1; | ||
} | ||
// this is how android distinguishes between tablet and phone | ||
// http://android-developers.blogspot.de/2011/07/new-tools-for-managing-screen-sizes.html | ||
const bTablet = (Math.min(window.screen.width / densityFactor, window.screen.height / densityFactor) >= 600); | ||
// special workaround for Nexus 7 where the window.screen.width is 600px or 601px in portrait mode (=> tablet) | ||
// but window.screen.height 552px in landscape mode (=> phone), because the browser UI takes some space on top. | ||
// So the detected device type depends on the orientation :-( | ||
// actually this is a Chrome bug, as "width"/"height" should return the entire screen's dimensions and | ||
// "availWidth"/"availHeight" should return the size available after subtracting the browser UI | ||
/* | ||
if (isLandscape() && | ||
(window.screen.height === 552 || window.screen.height === 553) // old/new Nexus 7 | ||
&& | ||
(/Nexus 7/i.test(sUserAgent))) { | ||
bTablet = true; | ||
} | ||
*/ | ||
return bTablet; | ||
} | ||
// This simple android phone detection can be used here because this is the mobile emulation mode in desktop browser | ||
const bAndroidPhone = (/(?=android)(?=.*mobile)/i.test(sUserAgent)); | ||
// in desktop browser, it's detected as tablet when | ||
// 1. Windows 8 device with a touch screen where "Touch" is contained in the userAgent | ||
// 2. Android emulation and it's not an Android phone | ||
return (Device.browser.msie && sUserAgent.indexOf("Touch") !== -1) || (Device.os.android && !bAndroidPhone); | ||
}; | ||
const _getSystem = () => { | ||
const bTabletDetected = _isTablet(); | ||
const isWin8Upwards = Device.os.windows && Device.os.version >= 8; | ||
const oSystem = {}; | ||
oSystem.tablet = !!((Device.support.touch || isWin8Upwards) && bTabletDetected); | ||
oSystem.phone = !!((Device.os.windows_phone || (Device.support.touch)) && !bTabletDetected); | ||
oSystem.desktop = !!((!oSystem.tablet && !oSystem.phone) || isWin8Upwards); | ||
oSystem.combi = oSystem.desktop && oSystem.tablet; | ||
oSystem.SYSTEMTYPE = SYSTEMTYPE; | ||
return oSystem; | ||
}; | ||
const _setSystem = () => { | ||
_setSupport(); | ||
_setOS(); | ||
Device.system = {}; | ||
Device.system = _getSystem(); | ||
if (Device.system.tablet || Device.system.phone) { | ||
Device.browser.mobile = true; | ||
} | ||
}; | ||
const getSystem = () => { | ||
if (!Device.system) { | ||
_setSystem(); | ||
} | ||
return Device.system; | ||
}; | ||
const isDesktop = () => { | ||
if (!Device.system) { | ||
_setSystem(); | ||
} | ||
return Device.system.desktop; | ||
if (isSSR) { | ||
return false; | ||
} | ||
return (!isTablet() && !isPhone()) || isWindows8OrAbove(); | ||
}; | ||
const isTablet = () => { | ||
if (!Device.system) { | ||
_setSystem(); | ||
} | ||
return Device.system.tablet; | ||
const isCombi = () => { | ||
return isTablet() && isDesktop(); | ||
}; | ||
const isPhone = () => { | ||
if (!Device.system) { | ||
_setSystem(); | ||
} | ||
return Device.system.phone; | ||
const isIOS = () => { | ||
return internals.iOS; | ||
}; | ||
const isMobile = () => { | ||
if (!Device.system) { | ||
_setSystem(); | ||
} | ||
return Device.browser.mobile; | ||
const isMac = () => { | ||
return internals.macOS; | ||
}; | ||
export { | ||
isIE, | ||
isEdge, | ||
isChrome, | ||
isFF, | ||
isSafari, | ||
isMobile, | ||
isDesktop, | ||
isTablet, | ||
isPhone, | ||
isAndroid, | ||
getOS, | ||
getSystem, | ||
getBrowser, | ||
supportTouch, | ||
const isAndroid = () => { | ||
return internals.android || internals.androidPhone; | ||
}; | ||
export { supportsTouch, isIE, isSafari, isChrome, isFirefox, isPhone, isTablet, isDesktop, isCombi, isIOS, isAndroid, isMac, }; | ||
//# sourceMappingURL=Device.js.map |
class EventProvider { | ||
constructor() { | ||
this._eventRegistry = {}; | ||
} | ||
attachEvent(eventName, fnFunction) { | ||
const eventRegistry = this._eventRegistry; | ||
let eventListeners = eventRegistry[eventName]; | ||
if (!Array.isArray(eventListeners)) { | ||
eventRegistry[eventName] = []; | ||
eventListeners = eventRegistry[eventName]; | ||
} | ||
eventListeners.push({ | ||
"function": fnFunction, | ||
}); | ||
} | ||
detachEvent(eventName, fnFunction) { | ||
const eventRegistry = this._eventRegistry; | ||
let eventListeners = eventRegistry[eventName]; | ||
if (!eventListeners) { | ||
return; | ||
} | ||
eventListeners = eventListeners.filter(event => { | ||
return event["function"] !== fnFunction; // eslint-disable-line | ||
}); | ||
if (eventListeners.length === 0) { | ||
delete eventRegistry[eventName]; | ||
} | ||
} | ||
/** | ||
* Fires an event and returns the results of all event listeners as an array. | ||
* Example: If listeners return promises, you can: await fireEvent("myEvent") to know when all listeners have finished. | ||
* | ||
* @param eventName the event to fire | ||
* @param data optional data to pass to each event listener | ||
* @returns {Array} an array with the results of all event listeners | ||
*/ | ||
fireEvent(eventName, data) { | ||
const eventRegistry = this._eventRegistry; | ||
const eventListeners = eventRegistry[eventName]; | ||
if (!eventListeners) { | ||
return []; | ||
} | ||
return eventListeners.map(event => { | ||
return event["function"].call(this, data); // eslint-disable-line | ||
}); | ||
} | ||
isHandlerAttached(eventName, fnFunction) { | ||
const eventRegistry = this._eventRegistry; | ||
const eventListeners = eventRegistry[eventName]; | ||
if (!eventListeners) { | ||
return false; | ||
} | ||
for (let i = 0; i < eventListeners.length; i++) { | ||
const event = eventListeners[i]; | ||
if (event["function"] === fnFunction) { // eslint-disable-line | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
hasListeners(eventName) { | ||
return !!this._eventRegistry[eventName]; | ||
} | ||
constructor() { | ||
this._eventRegistry = new Map(); | ||
} | ||
attachEvent(eventName, fnFunction) { | ||
const eventRegistry = this._eventRegistry; | ||
const eventListeners = eventRegistry.get(eventName); | ||
if (!Array.isArray(eventListeners)) { | ||
eventRegistry.set(eventName, [fnFunction]); | ||
return; | ||
} | ||
if (!eventListeners.includes(fnFunction)) { | ||
eventListeners.push(fnFunction); | ||
} | ||
} | ||
detachEvent(eventName, fnFunction) { | ||
const eventRegistry = this._eventRegistry; | ||
const eventListeners = eventRegistry.get(eventName); | ||
if (!eventListeners) { | ||
return; | ||
} | ||
const indexOfFnToDetach = eventListeners.indexOf(fnFunction); | ||
if (indexOfFnToDetach !== -1) { | ||
eventListeners.splice(indexOfFnToDetach, 1); | ||
} | ||
if (eventListeners.length === 0) { | ||
eventRegistry.delete(eventName); | ||
} | ||
} | ||
/** | ||
* Fires an event and returns the results of all event listeners as an array. | ||
* | ||
* @param eventName the event to fire | ||
* @param data optional data to pass to each event listener | ||
* @returns {Array} an array with the results of all event listeners | ||
*/ | ||
fireEvent(eventName, data) { | ||
const eventRegistry = this._eventRegistry; | ||
const eventListeners = eventRegistry.get(eventName); | ||
if (!eventListeners) { | ||
return []; | ||
} | ||
return eventListeners.map(fn => { | ||
return fn.call(this, data); | ||
}); | ||
} | ||
/** | ||
* Fires an event and returns a promise that will resolve once all listeners have resolved. | ||
* | ||
* @param eventName the event to fire | ||
* @param data optional data to pass to each event listener | ||
* @returns {Promise} a promise that will resolve when all listeners have resolved | ||
*/ | ||
fireEventAsync(eventName, data) { | ||
return Promise.all(this.fireEvent(eventName, data)); | ||
} | ||
isHandlerAttached(eventName, fnFunction) { | ||
const eventRegistry = this._eventRegistry; | ||
const eventListeners = eventRegistry.get(eventName); | ||
if (!eventListeners) { | ||
return false; | ||
} | ||
return eventListeners.includes(fnFunction); | ||
} | ||
hasListeners(eventName) { | ||
return !!this._eventRegistry.get(eventName); | ||
} | ||
} | ||
export default EventProvider; | ||
//# sourceMappingURL=EventProvider.js.map |
import { registerFeature } from "../FeaturesRegistry.js"; | ||
import { setTheme } from "../config/Theme.js"; | ||
const sap = window.sap; | ||
const core = sap && sap.ui && typeof sap.ui.getCore === "function" && sap.ui.getCore(); | ||
const isLoaded = () => { | ||
return !!core; | ||
}; | ||
const init = () => { | ||
if (!core) { | ||
return Promise.resolve(); | ||
} | ||
return new Promise(resolve => { | ||
core.attachInit(() => { | ||
sap.ui.require(["sap/ui/core/LocaleData"], resolve); | ||
}); | ||
}); | ||
}; | ||
const getConfigurationSettingsObject = () => { | ||
if (!core) { | ||
return; | ||
} | ||
const config = core.getConfiguration(); | ||
const LocaleData = sap.ui.require("sap/ui/core/LocaleData"); | ||
return { | ||
animationMode: config.getAnimationMode(), | ||
language: config.getLanguage(), | ||
theme: config.getTheme(), | ||
rtl: config.getRTL(), | ||
calendarType: config.getCalendarType(), | ||
formatSettings: { | ||
firstDayOfWeek: LocaleData ? LocaleData.getInstance(config.getLocale()).getFirstDayOfWeek() : undefined, | ||
}, | ||
}; | ||
}; | ||
const getLocaleDataObject = () => { | ||
if (!core) { | ||
return; | ||
} | ||
const config = core.getConfiguration(); | ||
const LocaleData = sap.ui.require("sap/ui/core/LocaleData"); | ||
return LocaleData.getInstance(config.getLocale())._get(); | ||
}; | ||
const listenForThemeChange = () => { | ||
const config = core.getConfiguration(); | ||
core.attachThemeChanged(async () => { | ||
await setTheme(config.getTheme()); | ||
}); | ||
}; | ||
const attachListeners = () => { | ||
if (!core) { | ||
return; | ||
} | ||
listenForThemeChange(); | ||
}; | ||
const cssVariablesLoaded = () => { | ||
if (!core) { | ||
return; | ||
} | ||
const link = [...document.head.children].find(el => el.id === "sap-ui-theme-sap.ui.core"); // more reliable than querySelector early | ||
if (!link) { | ||
return; | ||
} | ||
return !!link.href.match(/\/css(-|_)variables\.css/); | ||
}; | ||
const OpenUI5Support = { | ||
isLoaded, | ||
init, | ||
getConfigurationSettingsObject, | ||
getLocaleDataObject, | ||
attachListeners, | ||
cssVariablesLoaded, | ||
}; | ||
import { getCurrentZIndex } from "../util/PopupUtils.js"; | ||
import patchPopup from "./patchPopup.js"; | ||
class OpenUI5Support { | ||
static isAtLeastVersion116() { | ||
if (!window.sap.ui.version) { | ||
return true; // sap.ui.version will be removed in newer OpenUI5 versions | ||
} | ||
const version = window.sap.ui.version; | ||
const parts = version.split("."); | ||
if (!parts || parts.length < 2) { | ||
return false; | ||
} | ||
return parseInt(parts[0]) > 1 || parseInt(parts[1]) >= 116; | ||
} | ||
static isOpenUI5Detected() { | ||
return typeof window.sap?.ui?.require === "function"; | ||
} | ||
static init() { | ||
if (!OpenUI5Support.isOpenUI5Detected()) { | ||
return Promise.resolve(); | ||
} | ||
return new Promise(resolve => { | ||
window.sap.ui.require(["sap/ui/core/Core"], async (Core) => { | ||
const callback = () => { | ||
let deps = ["sap/ui/core/Popup", "sap/ui/core/LocaleData"]; | ||
if (OpenUI5Support.isAtLeastVersion116()) { // for versions since 1.116.0 and onward, use the modular core | ||
deps = [ | ||
...deps, | ||
"sap/base/i18n/Formatting", | ||
"sap/base/i18n/Localization", | ||
"sap/ui/core/ControlBehavior", | ||
"sap/ui/core/Theming", | ||
"sap/ui/core/date/CalendarUtils", | ||
]; | ||
} | ||
window.sap.ui.require(deps, (Popup) => { | ||
Popup.setInitialZIndex(getCurrentZIndex()); | ||
patchPopup(Popup); | ||
resolve(); | ||
}); | ||
}; | ||
if (OpenUI5Support.isAtLeastVersion116()) { | ||
await Core.ready(); | ||
callback(); | ||
} | ||
else { | ||
Core.attachInit(callback); | ||
} | ||
}); | ||
}); | ||
} | ||
static getConfigurationSettingsObject() { | ||
if (!OpenUI5Support.isOpenUI5Detected()) { | ||
return {}; | ||
} | ||
if (OpenUI5Support.isAtLeastVersion116()) { | ||
const ControlBehavior = window.sap.ui.require("sap/ui/core/ControlBehavior"); | ||
const Localization = window.sap.ui.require("sap/base/i18n/Localization"); | ||
const Theming = window.sap.ui.require("sap/ui/core/Theming"); | ||
const Formatting = window.sap.ui.require("sap/base/i18n/Formatting"); | ||
const CalendarUtils = window.sap.ui.require("sap/ui/core/date/CalendarUtils"); | ||
return { | ||
animationMode: ControlBehavior.getAnimationMode(), | ||
language: Localization.getLanguage(), | ||
theme: Theming.getTheme(), | ||
themeRoot: Theming.getThemeRoot(), | ||
rtl: Localization.getRTL(), | ||
timezone: Localization.getTimezone(), | ||
calendarType: Formatting.getCalendarType(), | ||
formatSettings: { | ||
firstDayOfWeek: CalendarUtils.getWeekConfigurationValues().firstDayOfWeek, | ||
legacyDateCalendarCustomizing: Formatting.getCustomIslamicCalendarData?.() | ||
?? Formatting.getLegacyDateCalendarCustomizing?.(), | ||
}, | ||
}; | ||
} | ||
const Core = window.sap.ui.require("sap/ui/core/Core"); | ||
const config = Core.getConfiguration(); | ||
const LocaleData = window.sap.ui.require("sap/ui/core/LocaleData"); | ||
return { | ||
animationMode: config.getAnimationMode(), | ||
language: config.getLanguage(), | ||
theme: config.getTheme(), | ||
themeRoot: config.getThemeRoot(), | ||
rtl: config.getRTL(), | ||
timezone: config.getTimezone(), | ||
calendarType: config.getCalendarType(), | ||
formatSettings: { | ||
firstDayOfWeek: LocaleData ? LocaleData.getInstance(config.getLocale()).getFirstDayOfWeek() : undefined, | ||
legacyDateCalendarCustomizing: config.getFormatSettings().getLegacyDateCalendarCustomizing(), | ||
}, | ||
}; | ||
} | ||
static getLocaleDataObject() { | ||
if (!OpenUI5Support.isOpenUI5Detected()) { | ||
return; | ||
} | ||
const LocaleData = window.sap.ui.require("sap/ui/core/LocaleData"); | ||
if (OpenUI5Support.isAtLeastVersion116()) { | ||
const Localization = window.sap.ui.require("sap/base/i18n/Localization"); | ||
return LocaleData.getInstance(Localization.getLanguageTag())._get(); | ||
} | ||
const Core = window.sap.ui.require("sap/ui/core/Core"); | ||
const config = Core.getConfiguration(); | ||
return LocaleData.getInstance(config.getLocale())._get(); | ||
} | ||
static _listenForThemeChange() { | ||
if (OpenUI5Support.isAtLeastVersion116()) { | ||
const Theming = window.sap.ui.require("sap/ui/core/Theming"); | ||
Theming.attachApplied(() => { | ||
setTheme(Theming.getTheme()); | ||
}); | ||
} | ||
else { | ||
const Core = window.sap.ui.require("sap/ui/core/Core"); | ||
const config = Core.getConfiguration(); | ||
Core.attachThemeChanged(() => { | ||
setTheme(config.getTheme()); | ||
}); | ||
} | ||
} | ||
static attachListeners() { | ||
if (!OpenUI5Support.isOpenUI5Detected()) { | ||
return; | ||
} | ||
OpenUI5Support._listenForThemeChange(); | ||
} | ||
static cssVariablesLoaded() { | ||
if (!OpenUI5Support.isOpenUI5Detected()) { | ||
return; | ||
} | ||
const link = [...document.head.children].find(el => el.id === "sap-ui-theme-sap.ui.core"); // more reliable than querySelector early | ||
if (!link) { | ||
return false; | ||
} | ||
return !!link.href.match(/\/css(-|_)variables\.css/); | ||
} | ||
static getNextZIndex() { | ||
if (!OpenUI5Support.isOpenUI5Detected()) { | ||
return; | ||
} | ||
const Popup = window.sap.ui.require("sap/ui/core/Popup"); | ||
if (!Popup) { | ||
console.warn(`The OpenUI5Support feature hasn't been initialized properly. Make sure you import the "@ui5/webcomponents-base/dist/features/OpenUI5Support.js" module before all components' modules.`); // eslint-disable-line | ||
} | ||
return Popup.getNextZIndex(); | ||
} | ||
static setInitialZIndex() { | ||
if (!OpenUI5Support.isOpenUI5Detected()) { | ||
return; | ||
} | ||
const Popup = window.sap.ui.require("sap/ui/core/Popup"); | ||
Popup.setInitialZIndex(getCurrentZIndex()); | ||
} | ||
} | ||
registerFeature("OpenUI5Support", OpenUI5Support); | ||
export default OpenUI5Support; | ||
//# sourceMappingURL=OpenUI5Support.js.map |
const features = new Map(); | ||
const registerFeature = (name, feature) => { | ||
features.set(name, feature); | ||
features.set(name, feature); | ||
}; | ||
const getFeature = name => { | ||
return features.get(name); | ||
const getFeature = (name) => { | ||
return features.get(name); | ||
}; | ||
export { registerFeature, getFeature }; | ||
export { registerFeature, getFeature, }; | ||
//# sourceMappingURL=FeaturesRegistry.js.map |
@@ -1,73 +0,25 @@ | ||
/** | ||
* CSS font face used for the texts provided by SAP. | ||
*/ | ||
import createStyleInHead from "./util/createStyleInHead.js"; | ||
import { hasStyle, createStyle } from "./ManagedStyles.js"; | ||
import { getFeature } from "./FeaturesRegistry.js"; | ||
/* CDN Locations */ | ||
const font72RegularWoff = `https://ui5.sap.com/sdk/resources/sap/ui/core/themes/sap_fiori_3/fonts/72-Regular.woff?ui5-webcomponents`; | ||
const font72RegularWoff2 = `https://ui5.sap.com/sdk/resources/sap/ui/core/themes/sap_fiori_3/fonts/72-Regular.woff2?ui5-webcomponents`; | ||
const font72RegularFullWoff = `https://ui5.sap.com/sdk/resources/sap/ui/core/themes/sap_fiori_3/fonts/72-Regular-full.woff?ui5-webcomponents`; | ||
const font72RegularFullWoff2 = `https://ui5.sap.com/sdk/resources/sap/ui/core/themes/sap_fiori_3/fonts/72-Regular-full.woff2?ui5-webcomponents`; | ||
const font72BoldWoff = `https://ui5.sap.com/sdk/resources/sap/ui/core/themes/sap_fiori_3/fonts/72-Bold.woff?ui5-webcomponents`; | ||
const font72BoldWoff2 = `https://ui5.sap.com/sdk/resources/sap/ui/core/themes/sap_fiori_3/fonts/72-Bold.woff2?ui5-webcomponents`; | ||
const font72BoldFullWoff = `https://ui5.sap.com/sdk/resources/sap/ui/core/themes/sap_fiori_3/fonts/72-Bold-full.woff?ui5-webcomponents`; | ||
const font72BoldFullWoff2 = `https://ui5.sap.com/sdk/resources/sap/ui/core/themes/sap_fiori_3/fonts/72-Bold-full.woff2?ui5-webcomponents`; | ||
const fontFaceCSS = ` | ||
@font-face { | ||
font-family: "72"; | ||
font-style: normal; | ||
font-weight: 400; | ||
src: local("72"), | ||
url(${font72RegularWoff2}) format("woff2"), | ||
url(${font72RegularWoff}) format("woff"); | ||
} | ||
@font-face { | ||
font-family: "72full"; | ||
font-style: normal; | ||
font-weight: 400; | ||
src: local('72-full'), | ||
url(${font72RegularFullWoff2}) format("woff2"), | ||
url(${font72RegularFullWoff}) format("woff"); | ||
} | ||
@font-face { | ||
font-family: "72"; | ||
font-style: normal; | ||
font-weight: 700; | ||
src: local('72-Bold'), | ||
url(${font72BoldWoff2}) format("woff2"), | ||
url(${font72BoldWoff}) format("woff"); | ||
} | ||
@font-face { | ||
font-family: "72full"; | ||
font-style: normal; | ||
font-weight: 700; | ||
src: local('72-Bold-full'), | ||
url(${font72BoldFullWoff2}) format("woff2"), | ||
url(${font72BoldFullWoff}) format("woff"); | ||
} | ||
`; | ||
import fontFaceCSS from "./generated/css/FontFace.css.js"; | ||
import overrideFontFaceCSS from "./generated/css/OverrideFontFace.css.js"; | ||
const insertFontFace = () => { | ||
if (document.querySelector(`head>style[data-ui5-font-face]`)) { | ||
return; | ||
} | ||
// If OpenUI5 is found, let it set the font | ||
const OpenUI5Support = getFeature("OpenUI5Support"); | ||
if (OpenUI5Support && OpenUI5Support.isLoaded()) { | ||
return; | ||
} | ||
createStyleInHead(fontFaceCSS, { "data-ui5-font-face": "" }); | ||
const openUI5Support = getFeature("OpenUI5Support"); | ||
// Only set the main font if there is no OpenUI5 support, or there is, but OpenUI5 is not loaded | ||
if (!openUI5Support || !openUI5Support.isOpenUI5Detected()) { | ||
insertMainFontFace(); | ||
} | ||
// Always set the override font - OpenUI5 in CSS Vars mode does not set it, unlike the main font | ||
insertOverrideFontFace(); | ||
}; | ||
const insertMainFontFace = () => { | ||
if (!hasStyle("data-ui5-font-face")) { | ||
createStyle(fontFaceCSS, "data-ui5-font-face"); | ||
} | ||
}; | ||
const insertOverrideFontFace = () => { | ||
if (!hasStyle("data-ui5-font-face-override")) { | ||
createStyle(overrideFontFaceCSS, "data-ui5-font-face-override"); | ||
} | ||
}; | ||
export default insertFontFace; | ||
//# sourceMappingURL=FontFace.js.map |
@@ -1,13 +0,8 @@ | ||
const assetParameters = {"themes":{"default":"sap_fiori_3","all":["sap_fiori_3","sap_fiori_3_dark","sap_belize","sap_belize_hcb","sap_belize_hcw","sap_fiori_3_hcb","sap_fiori_3_hcw"]},"languages":{"default":"en","all":["ar","bg","ca","cs","da","de","el","en","es","et","fi","fr","hi","hr","hu","it","iw","ja","kk","ko","lt","lv","ms","nl","no","pl","pt","ro","ru","sh","sk","sl","sv","th","tr","uk","vi","zh_CN","zh_TW"]},"locales":{"default":"en","all":["ar","ar_EG","ar_SA","bg","ca","cs","da","de","de_AT","de_CH","el","el_CY","en","en_AU","en_GB","en_HK","en_IE","en_IN","en_NZ","en_PG","en_SG","en_ZA","es","es_AR","es_BO","es_CL","es_CO","es_MX","es_PE","es_UY","es_VE","et","fa","fi","fr","fr_BE","fr_CA","fr_CH","fr_LU","he","hi","hr","hu","id","it","it_CH","ja","kk","ko","lt","lv","ms","nb","nl","nl_BE","pl","pt","pt_PT","ro","ru","ru_UA","sk","sl","sr","sv","th","tr","uk","vi","zh_CN","zh_HK","zh_SG","zh_TW"]}}; | ||
const assetParameters = { "themes": { "default": "sap_horizon", "all": ["sap_fiori_3", "sap_fiori_3_dark", "sap_belize", "sap_belize_hcb", "sap_belize_hcw", "sap_fiori_3_hcb", "sap_fiori_3_hcw", "sap_horizon", "sap_horizon_dark", "sap_horizon_hcb", "sap_horizon_hcw", "sap_horizon_exp", "sap_horizon_dark_exp", "sap_horizon_hcb_exp", "sap_horizon_hcw_exp"] }, "languages": { "default": "en", "all": ["ar", "bg", "ca", "cnr", "cs", "cy", "da", "de", "el", "en", "en_GB", "en_US_sappsd", "en_US_saprigi", "en_US_saptrc", "es", "es_MX", "et", "fi", "fr", "fr_CA", "hi", "hr", "hu", "in", "it", "iw", "ja", "kk", "ko", "lt", "lv", "mk", "ms", "nl", "no", "pl", "pt_PT", "pt", "ro", "ru", "sh", "sk", "sl", "sr", "sv", "th", "tr", "uk", "vi", "zh_CN", "zh_TW"] }, "locales": { "default": "en", "all": ["ar", "ar_EG", "ar_SA", "bg", "ca", "cnr", "cs", "da", "de", "de_AT", "de_CH", "el", "el_CY", "en", "en_AU", "en_GB", "en_HK", "en_IE", "en_IN", "en_NZ", "en_PG", "en_SG", "en_ZA", "es", "es_AR", "es_BO", "es_CL", "es_CO", "es_MX", "es_PE", "es_UY", "es_VE", "et", "fa", "fi", "fr", "fr_BE", "fr_CA", "fr_CH", "fr_LU", "he", "hi", "hr", "hu", "id", "it", "it_CH", "ja", "kk", "ko", "lt", "lv", "ms", "mk", "nb", "nl", "nl_BE", "pl", "pt", "pt_PT", "ro", "ru", "ru_UA", "sk", "sl", "sr", "sr_Latn", "sv", "th", "tr", "uk", "vi", "zh_CN", "zh_HK", "zh_SG", "zh_TW"] } }; | ||
const DEFAULT_THEME = assetParameters.themes.default; | ||
const SUPPORTED_THEMES = assetParameters.themes.all; | ||
const DEFAULT_LANGUAGE = assetParameters.languages.default; | ||
const DEFAULT_LOCALE = assetParameters.locales.default; | ||
const SUPPORTED_LOCALES = assetParameters.locales.all; | ||
export { | ||
DEFAULT_THEME, | ||
DEFAULT_LANGUAGE, | ||
DEFAULT_LOCALE, | ||
SUPPORTED_LOCALES, | ||
}; | ||
export { DEFAULT_THEME, SUPPORTED_THEMES, DEFAULT_LANGUAGE, DEFAULT_LOCALE, SUPPORTED_LOCALES, }; | ||
//# sourceMappingURL=AssetParameters.js.map |
import getSingletonElementInstance from "./util/getSingletonElementInstance.js"; | ||
const getSharedResourcesInstance = () => getSingletonElementInstance("ui5-shared-resources", document.head); | ||
const getMetaDomEl = () => { | ||
const el = document.createElement("meta"); | ||
el.setAttribute("name", "ui5-shared-resources"); | ||
el.setAttribute("content", ""); // attribute "content" should be present when "name" is set. | ||
return el; | ||
}; | ||
const getSharedResourcesInstance = () => { | ||
if (typeof document === "undefined") { | ||
return null; | ||
} | ||
return getSingletonElementInstance(`meta[name="ui5-shared-resources"]`, document.head, getMetaDomEl); | ||
}; | ||
/** | ||
* Use this method to initialize/get resources that you would like to be shared among UI5 Web Components runtime instances. | ||
* The data will be accessed via a singleton "ui5-shared-resources" HTML element in the "head" element of the page. | ||
* The data will be accessed via a singleton "ui5-shared-resources" HTML element in the "body" element of the page. | ||
* | ||
@@ -15,17 +24,18 @@ * @public | ||
const getSharedResource = (namespace, initialValue) => { | ||
const parts = namespace.split("."); | ||
let current = getSharedResourcesInstance(); | ||
for (let i = 0; i < parts.length; i++) { | ||
const part = parts[i]; | ||
const lastPart = i === parts.length - 1; | ||
if (!Object.prototype.hasOwnProperty.call(current, part)) { | ||
current[part] = lastPart ? initialValue : {}; | ||
} | ||
current = current[part]; | ||
} | ||
return current; | ||
const parts = namespace.split("."); | ||
let current = getSharedResourcesInstance(); | ||
if (!current) { | ||
return initialValue; | ||
} | ||
for (let i = 0; i < parts.length; i++) { | ||
const part = parts[i]; | ||
const lastPart = i === parts.length - 1; | ||
if (!Object.prototype.hasOwnProperty.call(current, part)) { | ||
current[part] = lastPart ? initialValue : {}; | ||
} | ||
current = current[part]; | ||
} | ||
return current; | ||
}; | ||
export default getSharedResource; | ||
//# sourceMappingURL=getSharedResource.js.map |
@@ -1,6 +0,5 @@ | ||
import { registerI18nBundle, fetchI18nBundle, getI18nBundleData } from "./asset-registries/i18n.js"; | ||
import { registerI18nLoader, fetchI18nBundle, getI18nBundleData } from "./asset-registries/i18n.js"; | ||
import formatMessage from "./util/formatMessage.js"; | ||
const I18nBundleInstances = new Map(); | ||
let customGetI18nBundle; | ||
/** | ||
@@ -11,43 +10,68 @@ * @class | ||
class I18nBundle { | ||
constructor(packageName) { | ||
this.packageName = packageName; | ||
} | ||
/** | ||
* Returns a text in the currently loaded language | ||
* | ||
* @param {Object|String} textObj key/defaultText pair or just the key | ||
* @param params Values for the placeholders | ||
* @returns {*} | ||
*/ | ||
getText(textObj, ...params) { | ||
if (typeof textObj === "string") { | ||
textObj = { key: textObj, defaultText: textObj }; | ||
} | ||
if (!textObj || !textObj.key) { | ||
return ""; | ||
} | ||
const bundle = getI18nBundleData(this.packageName); | ||
const messageText = bundle && bundle[textObj.key] ? bundle[textObj.key] : (textObj.defaultText || textObj.key); | ||
return formatMessage(messageText, params); | ||
} | ||
constructor(packageName) { | ||
this.packageName = packageName; | ||
} | ||
/** | ||
* Returns a text in the currently loaded language | ||
* | ||
* @public | ||
* @param textObj key/defaultText pair or just the key | ||
* @param params Values for the placeholders | ||
*/ | ||
getText(textObj, ...params) { | ||
if (typeof textObj === "string") { | ||
textObj = { key: textObj, defaultText: textObj }; | ||
} | ||
if (!textObj || !textObj.key) { | ||
return ""; | ||
} | ||
const bundle = getI18nBundleData(this.packageName); | ||
if (bundle && !bundle[textObj.key]) { | ||
// eslint-disable-next-line no-console | ||
console.warn(`Key ${textObj.key} not found in the i18n bundle, the default text will be used`); | ||
} | ||
const messageText = bundle && bundle[textObj.key] ? bundle[textObj.key] : (textObj.defaultText || textObj.key); | ||
return formatMessage(messageText, params); | ||
} | ||
} | ||
const getI18nBundle = packageName => { | ||
if (I18nBundleInstances.has(packageName)) { | ||
return I18nBundleInstances.get(packageName); | ||
} | ||
const i18nBundle = new I18nBundle(packageName); | ||
I18nBundleInstances.set(packageName, i18nBundle); | ||
return i18nBundle; | ||
/** | ||
* Returns the I18nBundle instance for the given package synchronously. | ||
* | ||
* @public | ||
* @param packageName | ||
*/ | ||
const getI18nBundleSync = (packageName) => { | ||
if (I18nBundleInstances.has(packageName)) { | ||
return I18nBundleInstances.get(packageName); | ||
} | ||
const i18nBundle = new I18nBundle(packageName); | ||
I18nBundleInstances.set(packageName, i18nBundle); | ||
return i18nBundle; | ||
}; | ||
export { | ||
registerI18nBundle, | ||
fetchI18nBundle, | ||
getI18nBundle, | ||
/** | ||
* Fetches and returns the I18nBundle instance for the given package. | ||
* | ||
* @public | ||
* @param packageName | ||
*/ | ||
const getI18nBundle = async (packageName) => { | ||
if (customGetI18nBundle) { | ||
return customGetI18nBundle(packageName); | ||
} | ||
await fetchI18nBundle(packageName); | ||
return getI18nBundleSync(packageName); | ||
}; | ||
/** | ||
* Allows developers to provide a custom getI18nBundle implementation | ||
* If this function is called, the custom implementation will be used for all components and will completely | ||
* replace the default implementation. | ||
* | ||
* @public | ||
* @param customGet the function to use instead of the standard getI18nBundle implementation | ||
*/ | ||
const registerCustomI18nBundleGetter = (customGet) => { | ||
customGetI18nBundle = customGet; | ||
}; | ||
export default I18nBundle; | ||
export { registerI18nLoader, getI18nBundle, registerCustomI18nBundleGetter, }; | ||
//# sourceMappingURL=i18nBundle.js.map |
import merge from "./thirdparty/merge.js"; | ||
import { getFeature } from "./FeaturesRegistry.js"; | ||
import { DEFAULT_THEME } from "./generated/AssetParameters.js"; | ||
import validateThemeRoot from "./validateThemeRoot.js"; | ||
import AnimationMode from "./types/AnimationMode.js"; | ||
let initialized = false; | ||
let initialConfig = { | ||
animationMode: "full", | ||
theme: DEFAULT_THEME, | ||
rtl: null, | ||
language: null, | ||
calendarType: null, | ||
noConflict: false, // no URL | ||
formatSettings: {}, | ||
animationMode: AnimationMode.Full, | ||
theme: DEFAULT_THEME, | ||
themeRoot: undefined, | ||
rtl: undefined, | ||
language: undefined, | ||
timezone: undefined, | ||
calendarType: undefined, | ||
secondaryCalendarType: undefined, | ||
noConflict: false, | ||
formatSettings: {}, | ||
fetchDefaultLanguage: false, | ||
}; | ||
/* General settings */ | ||
const getAnimationMode = () => { | ||
initConfiguration(); | ||
return initialConfig.animationMode; | ||
initConfiguration(); | ||
return initialConfig.animationMode; | ||
}; | ||
const getTheme = () => { | ||
initConfiguration(); | ||
return initialConfig.theme; | ||
initConfiguration(); | ||
return initialConfig.theme; | ||
}; | ||
const getRTL = () => { | ||
initConfiguration(); | ||
return initialConfig.rtl; | ||
const getThemeRoot = () => { | ||
initConfiguration(); | ||
return initialConfig.themeRoot; | ||
}; | ||
const getLanguage = () => { | ||
initConfiguration(); | ||
return initialConfig.language; | ||
initConfiguration(); | ||
return initialConfig.language; | ||
}; | ||
/** | ||
* Returns if the default language, that is inlined at build time, | ||
* should be fetched over the network instead. | ||
* @returns {Boolean} | ||
*/ | ||
const getFetchDefaultLanguage = () => { | ||
initConfiguration(); | ||
return initialConfig.fetchDefaultLanguage; | ||
}; | ||
const getNoConflict = () => { | ||
initConfiguration(); | ||
return initialConfig.noConflict; | ||
initConfiguration(); | ||
return initialConfig.noConflict; | ||
}; | ||
/** | ||
* Get the configured calendar type | ||
* @returns { String } the name of the configured calendar type | ||
*/ | ||
const getCalendarType = () => { | ||
initConfiguration(); | ||
return initialConfig.calendarType; | ||
initConfiguration(); | ||
return initialConfig.calendarType; | ||
}; | ||
const getSecondaryCalendarType = () => { | ||
initConfiguration(); | ||
return initialConfig.secondaryCalendarType; | ||
}; | ||
/** | ||
* Returns the configured IANA timezone ID. | ||
* @returns { String } the configured IANA timezone ID, e.g. "America/New_York" | ||
*/ | ||
const getTimezone = () => { | ||
initConfiguration(); | ||
return initialConfig.timezone; | ||
}; | ||
const getFormatSettings = () => { | ||
initConfiguration(); | ||
return initialConfig.formatSettings; | ||
initConfiguration(); | ||
return initialConfig.formatSettings; | ||
}; | ||
const booleanMapping = new Map(); | ||
booleanMapping.set("true", true); | ||
booleanMapping.set("false", false); | ||
const parseConfigurationScript = () => { | ||
const configScript = document.querySelector("[data-ui5-config]") || document.querySelector("[data-id='sap-ui-config']"); // for backward compatibility | ||
let configJSON; | ||
if (configScript) { | ||
try { | ||
configJSON = JSON.parse(configScript.innerHTML); | ||
} catch (err) { | ||
console.warn("Incorrect data-sap-ui-config format. Please use JSON"); /* eslint-disable-line */ | ||
} | ||
if (configJSON) { | ||
initialConfig = merge(initialConfig, configJSON); | ||
} | ||
} | ||
const configScript = document.querySelector("[data-ui5-config]") || document.querySelector("[data-id='sap-ui-config']"); // for backward compatibility | ||
let configJSON; | ||
if (configScript) { | ||
try { | ||
configJSON = JSON.parse(configScript.innerHTML); | ||
} | ||
catch (err) { | ||
console.warn("Incorrect data-sap-ui-config format. Please use JSON"); /* eslint-disable-line */ | ||
} | ||
if (configJSON) { | ||
initialConfig = merge(initialConfig, configJSON); | ||
} | ||
} | ||
}; | ||
const parseURLParameters = () => { | ||
const params = new URLSearchParams(window.location.search); | ||
params.forEach((value, key) => { | ||
if (!key.startsWith("sap-ui")) { | ||
return; | ||
} | ||
const lowerCaseValue = value.toLowerCase(); | ||
const param = key.split("sap-ui-")[1]; | ||
if (booleanMapping.has(value)) { | ||
value = booleanMapping.get(lowerCaseValue); | ||
} | ||
initialConfig[param] = value; | ||
}); | ||
const params = new URLSearchParams(window.location.search); | ||
// Process "sap-*" params first | ||
params.forEach((value, key) => { | ||
const parts = key.split("sap-").length; | ||
if (parts === 0 || parts === key.split("sap-ui-").length) { | ||
return; | ||
} | ||
applyURLParam(key, value, "sap"); | ||
}); | ||
// Process "sap-ui-*" params | ||
params.forEach((value, key) => { | ||
if (!key.startsWith("sap-ui")) { | ||
return; | ||
} | ||
applyURLParam(key, value, "sap-ui"); | ||
}); | ||
}; | ||
const normalizeThemeRootParamValue = (value) => { | ||
const themeRoot = value.split("@")[1]; | ||
return validateThemeRoot(themeRoot); | ||
}; | ||
const normalizeThemeParamValue = (param, value) => { | ||
if (param === "theme" && value.includes("@")) { // the theme parameter might have @<URL-TO-THEME> in the value - strip this | ||
return value.split("@")[0]; | ||
} | ||
return value; | ||
}; | ||
const applyURLParam = (key, value, paramType) => { | ||
const lowerCaseValue = value.toLowerCase(); | ||
const param = key.split(`${paramType}-`)[1]; | ||
if (booleanMapping.has(value)) { | ||
value = booleanMapping.get(lowerCaseValue); | ||
} | ||
if (param === "theme") { | ||
initialConfig.theme = normalizeThemeParamValue(param, value); | ||
if (value && value.includes("@")) { | ||
initialConfig.themeRoot = normalizeThemeRootParamValue(value); | ||
} | ||
} | ||
else { | ||
initialConfig[param] = value; | ||
} | ||
}; | ||
const applyOpenUI5Configuration = () => { | ||
const OpenUI5Support = getFeature("OpenUI5Support"); | ||
if (!OpenUI5Support || !OpenUI5Support.isLoaded()) { | ||
return; | ||
} | ||
const OpenUI5Config = OpenUI5Support.getConfigurationSettingsObject(); | ||
initialConfig = merge(initialConfig, OpenUI5Config); | ||
const openUI5Support = getFeature("OpenUI5Support"); | ||
if (!openUI5Support || !openUI5Support.isOpenUI5Detected()) { | ||
return; | ||
} | ||
const OpenUI5Config = openUI5Support.getConfigurationSettingsObject(); | ||
initialConfig = merge(initialConfig, OpenUI5Config); | ||
}; | ||
const initConfiguration = () => { | ||
if (initialized) { | ||
return; | ||
} | ||
// 1. Lowest priority - configuration script | ||
parseConfigurationScript(); | ||
// 2. URL parameters overwrite configuration script parameters | ||
parseURLParameters(); | ||
// 3. If OpenUI5 is detected, it has the highest priority | ||
applyOpenUI5Configuration(); | ||
initialized = true; | ||
if (typeof document === "undefined" || initialized) { | ||
return; | ||
} | ||
// 1. Lowest priority - configuration script | ||
parseConfigurationScript(); | ||
// 2. URL parameters overwrite configuration script parameters | ||
parseURLParameters(); | ||
// 3. If OpenUI5 is detected, it has the highest priority | ||
applyOpenUI5Configuration(); | ||
initialized = true; | ||
}; | ||
export { | ||
getAnimationMode, | ||
getTheme, | ||
getRTL, | ||
getLanguage, | ||
getNoConflict, | ||
getCalendarType, | ||
getFormatSettings, | ||
}; | ||
export { getAnimationMode, getTheme, getThemeRoot, getLanguage, getFetchDefaultLanguage, getNoConflict, getCalendarType, getSecondaryCalendarType, getTimezone, getFormatSettings, }; | ||
//# sourceMappingURL=InitialConfiguration.js.map |
357
dist/Keys.js
const KeyCodes = { | ||
BACKSPACE: 8, | ||
TAB: 9, | ||
ENTER: 13, | ||
SHIFT: 16, | ||
CONTROL: 17, | ||
ALT: 18, | ||
BREAK: 19, | ||
CAPS_LOCK: 20, | ||
ESCAPE: 27, | ||
SPACE: 32, | ||
PAGE_UP: 33, | ||
PAGE_DOWN: 34, | ||
END: 35, | ||
HOME: 36, | ||
ARROW_LEFT: 37, | ||
ARROW_UP: 38, | ||
ARROW_RIGHT: 39, | ||
ARROW_DOWN: 40, | ||
PRINT: 44, | ||
INSERT: 45, | ||
DELETE: 46, | ||
DIGIT_0: 48, | ||
DIGIT_1: 49, | ||
DIGIT_2: 50, | ||
DIGIT_3: 51, | ||
DIGIT_4: 52, | ||
DIGIT_5: 53, | ||
DIGIT_6: 54, | ||
DIGIT_7: 55, | ||
DIGIT_8: 56, | ||
DIGIT_9: 57, | ||
A: 65, | ||
B: 66, | ||
C: 67, | ||
D: 68, | ||
E: 69, | ||
F: 70, | ||
G: 71, | ||
H: 72, | ||
I: 73, | ||
J: 74, | ||
K: 75, | ||
L: 76, | ||
M: 77, | ||
N: 78, | ||
O: 79, | ||
P: 80, | ||
Q: 81, | ||
R: 82, | ||
S: 83, | ||
T: 84, | ||
U: 85, | ||
V: 86, | ||
W: 87, | ||
X: 88, | ||
Y: 89, | ||
Z: 90, | ||
WINDOWS: 91, | ||
CONTEXT_MENU: 93, | ||
TURN_OFF: 94, | ||
SLEEP: 95, | ||
NUMPAD_0: 96, | ||
NUMPAD_1: 97, | ||
NUMPAD_2: 98, | ||
NUMPAD_3: 99, | ||
NUMPAD_4: 100, | ||
NUMPAD_5: 101, | ||
NUMPAD_6: 102, | ||
NUMPAD_7: 103, | ||
NUMPAD_8: 104, | ||
NUMPAD_9: 105, | ||
NUMPAD_ASTERISK: 106, | ||
NUMPAD_PLUS: 107, | ||
NUMPAD_MINUS: 109, | ||
NUMPAD_COMMA: 110, | ||
NUMPAD_SLASH: 111, | ||
F1: 112, | ||
F2: 113, | ||
F3: 114, | ||
F4: 115, | ||
F5: 116, | ||
F6: 117, | ||
F7: 118, | ||
F8: 119, | ||
F9: 120, | ||
F10: 121, | ||
F11: 122, | ||
F12: 123, | ||
NUM_LOCK: 144, | ||
SCROLL_LOCK: 145, | ||
OPEN_BRACKET: 186, | ||
PLUS: 187, | ||
COMMA: 188, | ||
SLASH: 189, | ||
DOT: 190, | ||
PIPE: 191, | ||
SEMICOLON: 192, | ||
MINUS: 219, | ||
GREAT_ACCENT: 220, | ||
EQUALS: 221, | ||
SINGLE_QUOTE: 222, | ||
BACKSLASH: 226, | ||
BACKSPACE: 8, | ||
TAB: 9, | ||
ENTER: 13, | ||
SHIFT: 16, | ||
CONTROL: 17, | ||
ALT: 18, | ||
BREAK: 19, | ||
CAPS_LOCK: 20, | ||
ESCAPE: 27, | ||
SPACE: 32, | ||
PAGE_UP: 33, | ||
PAGE_DOWN: 34, | ||
END: 35, | ||
HOME: 36, | ||
ARROW_LEFT: 37, | ||
ARROW_UP: 38, | ||
ARROW_RIGHT: 39, | ||
ARROW_DOWN: 40, | ||
PRINT: 44, | ||
INSERT: 45, | ||
DELETE: 46, | ||
DIGIT_0: 48, | ||
DIGIT_1: 49, | ||
DIGIT_2: 50, | ||
DIGIT_3: 51, | ||
DIGIT_4: 52, | ||
DIGIT_5: 53, | ||
DIGIT_6: 54, | ||
DIGIT_7: 55, | ||
DIGIT_8: 56, | ||
DIGIT_9: 57, | ||
A: 65, | ||
B: 66, | ||
C: 67, | ||
D: 68, | ||
E: 69, | ||
F: 70, | ||
G: 71, | ||
H: 72, | ||
I: 73, | ||
J: 74, | ||
K: 75, | ||
L: 76, | ||
M: 77, | ||
N: 78, | ||
O: 79, | ||
P: 80, | ||
Q: 81, | ||
R: 82, | ||
S: 83, | ||
T: 84, | ||
U: 85, | ||
V: 86, | ||
W: 87, | ||
X: 88, | ||
Y: 89, | ||
Z: 90, | ||
WINDOWS: 91, | ||
CONTEXT_MENU: 93, | ||
TURN_OFF: 94, | ||
SLEEP: 95, | ||
NUMPAD_0: 96, | ||
NUMPAD_1: 97, | ||
NUMPAD_2: 98, | ||
NUMPAD_3: 99, | ||
NUMPAD_4: 100, | ||
NUMPAD_5: 101, | ||
NUMPAD_6: 102, | ||
NUMPAD_7: 103, | ||
NUMPAD_8: 104, | ||
NUMPAD_9: 105, | ||
NUMPAD_ASTERISK: 106, | ||
NUMPAD_PLUS: 107, | ||
NUMPAD_MINUS: 109, | ||
NUMPAD_COMMA: 110, | ||
NUMPAD_SLASH: 111, | ||
F1: 112, | ||
F2: 113, | ||
F3: 114, | ||
F4: 115, | ||
F5: 116, | ||
F6: 117, | ||
F7: 118, | ||
F8: 119, | ||
F9: 120, | ||
F10: 121, | ||
F11: 122, | ||
F12: 123, | ||
NUM_LOCK: 144, | ||
SCROLL_LOCK: 145, | ||
COLON: 186, | ||
PLUS: 187, | ||
COMMA: 188, | ||
SLASH: 189, | ||
DOT: 190, | ||
PIPE: 191, | ||
SEMICOLON: 192, | ||
MINUS: 219, | ||
GREAT_ACCENT: 220, | ||
EQUALS: 221, | ||
SINGLE_QUOTE: 222, | ||
BACKSLASH: 226, | ||
}; | ||
const isEnter = event => (event.key ? event.key === "Enter" : event.keyCode === KeyCodes.ENTER) && !hasModifierKeys(event); | ||
const isSpace = event => (event.key ? (event.key === "Spacebar" || event.key === " ") : event.keyCode === KeyCodes.SPACE) && !hasModifierKeys(event); | ||
const isLeft = event => (event.key ? (event.key === "ArrowLeft" || event.key === "Left") : event.keyCode === KeyCodes.ARROW_LEFT) && !hasModifierKeys(event); | ||
const isRight = event => (event.key ? (event.key === "ArrowRight" || event.key === "Right") : event.keyCode === KeyCodes.ARROW_RIGHT) && !hasModifierKeys(event); | ||
const isUp = event => (event.key ? (event.key === "ArrowUp" || event.key === "Up") : event.keyCode === KeyCodes.ARROW_UP) && !hasModifierKeys(event); | ||
const isDown = event => (event.key ? (event.key === "ArrowDown" || event.key === "Down") : event.keyCode === KeyCodes.ARROW_DOWN) && !hasModifierKeys(event); | ||
const isHome = event => (event.key ? event.key === "Home" : event.keyCode === KeyCodes.HOME) && !hasModifierKeys(event); | ||
const isEnd = event => (event.key ? event.key === "End" : event.keyCode === KeyCodes.END) && !hasModifierKeys(event); | ||
const isEscape = event => (event.key ? event.key === "Escape" || event.key === "Esc" : event.keyCode === KeyCodes.ESCAPE) && !hasModifierKeys(event); | ||
const isTabNext = event => (event.key ? event.key === "Tab" : event.keyCode === KeyCodes.TAB) && !hasModifierKeys(event); | ||
const isTabPrevious = event => (event.key ? event.key === "Tab" : event.keyCode === KeyCodes.TAB) && checkModifierKeys(event, /* Ctrl */ false, /* Alt */ false, /* Shift */ true); | ||
const isBackSpace = event => (event.key ? event.key === "Backspace" : event.keyCode === KeyCodes.BACKSPACE) && !hasModifierKeys(event); | ||
const isDelete = event => (event.key ? event.key === "Delete" : event.keyCode === KeyCodes.DELETE) && !hasModifierKeys(event); | ||
const isPageUp = event => (event.key ? event.key === "PageUp" : event.keyCode === KeyCodes.PAGE_UP) && !hasModifierKeys(event); | ||
const isPageDown = event => (event.key ? event.key === "PageDown" : event.keyCode === KeyCodes.PAGE_DOWN) && !hasModifierKeys(event); | ||
const isPageUpShift = event => (event.key ? event.key === "PageUp" : event.keyCode === KeyCodes.PAGE_UP) && checkModifierKeys(event, false, false, true); | ||
const isPageDownShift = event => (event.key ? event.key === "PageDown" : event.keyCode === KeyCodes.PAGE_DOWN) && checkModifierKeys(event, false, false, true); | ||
const isPageUpShiftCtrl = event => (event.key ? event.key === "PageUp" : event.keyCode === KeyCodes.PAGE_UP) && checkModifierKeys(event, true, false, true); | ||
const isPageDownShiftCtrl = event => (event.key ? event.key === "PageDown" : event.keyCode === KeyCodes.PAGE_DOWN) && checkModifierKeys(event, true, false, true); | ||
const isShow = event => { | ||
if (event.key) { | ||
return isF4(event) || isShowByArrows(event); | ||
} | ||
return (event.keyCode === KeyCodes.F4 && !hasModifierKeys(event)) || (event.keyCode === KeyCodes.ARROW_DOWN && checkModifierKeys(event, /* Ctrl */ false, /* Alt */ true, /* Shift */ false)); | ||
const isEnter = (event) => (event.key ? event.key === "Enter" : event.keyCode === KeyCodes.ENTER) && !hasModifierKeys(event); | ||
const isEnterShift = (event) => (event.key ? event.key === "Enter" : event.keyCode === KeyCodes.ENTER) && checkModifierKeys(event, false, false, true); | ||
const isSpace = (event) => (event.key ? (event.key === "Spacebar" || event.key === " ") : event.keyCode === KeyCodes.SPACE) && !hasModifierKeys(event); | ||
const isSpaceShift = (event) => (event.key ? (event.key === "Spacebar" || event.key === " ") : event.keyCode === KeyCodes.SPACE) && checkModifierKeys(event, false, false, true); | ||
const isSpaceCtrl = (event) => (event.key ? (event.key === "Spacebar" || event.key === " ") : event.keyCode === KeyCodes.SPACE) && checkModifierKeys(event, true, false, false); | ||
const isLeft = (event) => (event.key ? (event.key === "ArrowLeft" || event.key === "Left") : event.keyCode === KeyCodes.ARROW_LEFT) && !hasModifierKeys(event); | ||
const isRight = (event) => (event.key ? (event.key === "ArrowRight" || event.key === "Right") : event.keyCode === KeyCodes.ARROW_RIGHT) && !hasModifierKeys(event); | ||
const isUp = (event) => (event.key ? (event.key === "ArrowUp" || event.key === "Up") : event.keyCode === KeyCodes.ARROW_UP) && !hasModifierKeys(event); | ||
const isDown = (event) => (event.key ? (event.key === "ArrowDown" || event.key === "Down") : event.keyCode === KeyCodes.ARROW_DOWN) && !hasModifierKeys(event); | ||
const isLeftCtrl = (event) => (event.key ? (event.key === "ArrowLeft" || event.key === "Left") : event.keyCode === KeyCodes.ARROW_LEFT) && checkModifierKeys(event, true, false, false); | ||
const isRightCtrl = (event) => (event.key ? (event.key === "ArrowRight" || event.key === "Right") : event.keyCode === KeyCodes.ARROW_RIGHT) && checkModifierKeys(event, true, false, false); | ||
const isUpCtrl = (event) => (event.key ? (event.key === "ArrowUp" || event.key === "Up") : event.keyCode === KeyCodes.ARROW_UP) && checkModifierKeys(event, true, false, false); | ||
const isDownCtrl = (event) => (event.key ? (event.key === "ArrowDown" || event.key === "Down") : event.keyCode === KeyCodes.ARROW_DOWN) && checkModifierKeys(event, true, false, false); | ||
const isUpShift = (event) => (event.key ? (event.key === "ArrowUp" || event.key === "Up") : event.keyCode === KeyCodes.ARROW_UP) && checkModifierKeys(event, false, false, true); | ||
const isDownShift = (event) => (event.key ? (event.key === "ArrowDown" || event.key === "Down") : event.keyCode === KeyCodes.ARROW_DOWN) && checkModifierKeys(event, false, false, true); | ||
const isUpAlt = (event) => (event.key ? (event.key === "ArrowUp" || event.key === "Up") : event.keyCode === KeyCodes.ARROW_UP) && checkModifierKeys(event, false, true, false); | ||
const isDownAlt = (event) => (event.key ? (event.key === "ArrowDown" || event.key === "Down") : event.keyCode === KeyCodes.ARROW_DOWN) && checkModifierKeys(event, false, true, false); | ||
const isLeftShift = (event) => (event.key ? (event.key === "ArrowLeft" || event.key === "Left") : event.keyCode === KeyCodes.ARROW_LEFT) && checkModifierKeys(event, false, false, true); | ||
const isRightShift = (event) => (event.key ? (event.key === "ArrowRight" || event.key === "Right") : event.keyCode === KeyCodes.ARROW_RIGHT) && checkModifierKeys(event, false, false, true); | ||
const isLeftShiftCtrl = (event) => (event.key ? (event.key === "ArrowLeft" || event.key === "Left") : event.keyCode === KeyCodes.ARROW_LEFT) && checkModifierKeys(event, true, false, true); | ||
const isRightShiftCtrl = (event) => (event.key ? (event.key === "ArrowRight" || event.key === "Right") : event.keyCode === KeyCodes.ARROW_RIGHT) && checkModifierKeys(event, true, false, true); | ||
const isUpShiftCtrl = (event) => (event.key ? (event.key === "ArrowUp" || event.key === "Up") : event.keyCode === KeyCodes.ARROW_UP) && checkModifierKeys(event, true, false, true); | ||
const isDownShiftCtrl = (event) => (event.key ? (event.key === "ArrowDown" || event.key === "Down") : event.keyCode === KeyCodes.ARROW_DOWN) && checkModifierKeys(event, true, false, true); | ||
const isHome = (event) => (event.key ? event.key === "Home" : event.keyCode === KeyCodes.HOME) && !hasModifierKeys(event); | ||
const isEnd = (event) => (event.key ? event.key === "End" : event.keyCode === KeyCodes.END) && !hasModifierKeys(event); | ||
const isHomeCtrl = (event) => (event.key ? event.key === "Home" : event.keyCode === KeyCodes.HOME) && checkModifierKeys(event, true, false, false); | ||
const isHomeShift = (event) => (event.key ? event.key === "Home" : event.keyCode === KeyCodes.HOME) && checkModifierKeys(event, false, false, true); | ||
const isEndCtrl = (event) => (event.key ? event.key === "End" : event.keyCode === KeyCodes.END) && checkModifierKeys(event, true, false, false); | ||
const isEndShift = (event) => (event.key ? event.key === "End" : event.keyCode === KeyCodes.END) && checkModifierKeys(event, false, false, true); | ||
const isEscape = (event) => (event.key ? event.key === "Escape" || event.key === "Esc" : event.keyCode === KeyCodes.ESCAPE) && !hasModifierKeys(event); | ||
const isTabNext = (event) => (event.key ? event.key === "Tab" : event.keyCode === KeyCodes.TAB) && !hasModifierKeys(event); | ||
const isTabPrevious = (event) => (event.key ? event.key === "Tab" : event.keyCode === KeyCodes.TAB) && checkModifierKeys(event, /* Ctrl */ false, /* Alt */ false, /* Shift */ true); | ||
const isBackSpace = (event) => (event.key ? event.key === "Backspace" : event.keyCode === KeyCodes.BACKSPACE) && !hasModifierKeys(event); | ||
const isDelete = (event) => (event.key ? event.key === "Delete" : event.keyCode === KeyCodes.DELETE) && !hasModifierKeys(event); | ||
const isDeleteShift = (event) => (event.key ? event.key === "Delete" : event.keyCode === KeyCodes.DELETE) && checkModifierKeys(event, false, false, true); | ||
const isInsertShift = (event) => (event.key ? event.key === "Insert" : event.keyCode === KeyCodes.DELETE) && checkModifierKeys(event, false, false, true); | ||
const isInsertCtrl = (event) => (event.key ? event.key === "Insert" : event.keyCode === KeyCodes.DELETE) && checkModifierKeys(event, true, false, false); | ||
const isPageUp = (event) => (event.key ? event.key === "PageUp" : event.keyCode === KeyCodes.PAGE_UP) && !hasModifierKeys(event); | ||
const isPageDown = (event) => (event.key ? event.key === "PageDown" : event.keyCode === KeyCodes.PAGE_DOWN) && !hasModifierKeys(event); | ||
const isPageUpShift = (event) => (event.key ? event.key === "PageUp" : event.keyCode === KeyCodes.PAGE_UP) && checkModifierKeys(event, false, false, true); | ||
const isPageUpAlt = (event) => (event.key ? event.key === "PageUp" : event.keyCode === KeyCodes.PAGE_UP) && checkModifierKeys(event, false, true, false); | ||
const isPageDownShift = (event) => (event.key ? event.key === "PageDown" : event.keyCode === KeyCodes.PAGE_DOWN) && checkModifierKeys(event, false, false, true); | ||
const isPageDownAlt = (event) => (event.key ? event.key === "PageDown" : event.keyCode === KeyCodes.PAGE_DOWN) && checkModifierKeys(event, false, true, false); | ||
const isPageUpShiftCtrl = (event) => (event.key ? event.key === "PageUp" : event.keyCode === KeyCodes.PAGE_UP) && checkModifierKeys(event, true, false, true); | ||
const isPageDownShiftCtrl = (event) => (event.key ? event.key === "PageDown" : event.keyCode === KeyCodes.PAGE_DOWN) && checkModifierKeys(event, true, false, true); | ||
const isPlus = (event) => (event.key ? event.key === "+" : event.keyCode === KeyCodes.PLUS) || (event.keyCode === KeyCodes.NUMPAD_PLUS && !hasModifierKeys(event)); | ||
const isMinus = (event) => (event.key ? event.key === "-" : event.keyCode === KeyCodes.MINUS) || (event.keyCode === KeyCodes.NUMPAD_MINUS && !hasModifierKeys(event)); | ||
const isShow = (event) => { | ||
if (event.key) { | ||
return isF4(event) || isShowByArrows(event); | ||
} | ||
return (event.keyCode === KeyCodes.F4 && !hasModifierKeys(event)) || (event.keyCode === KeyCodes.ARROW_DOWN && checkModifierKeys(event, /* Ctrl */ false, /* Alt */ true, /* Shift */ false)); | ||
}; | ||
const isF4 = event => { | ||
return event.key === "F4" && !hasModifierKeys(event); | ||
const isF4 = (event) => { | ||
return event.key === "F4" && !hasModifierKeys(event); | ||
}; | ||
const isShowByArrows = event => { | ||
return ((event.key === "ArrowDown" || event.key === "Down") || (event.key === "ArrowUp" || event.key === "Up")) && checkModifierKeys(event, /* Ctrl */ false, /* Alt */ true, /* Shift */ false); | ||
const isF4Shift = (event) => (event.key ? event.key === "F4" : event.keyCode === KeyCodes.F4) && checkModifierKeys(event, false, false, true); | ||
const isF6Next = (event) => ((event.key ? event.key === "F6" : event.keyCode === KeyCodes.F6) && checkModifierKeys(event, false, false, false)) | ||
|| ((event.key ? (event.key === "ArrowDown" || event.key === "Down") : event.keyCode === KeyCodes.ARROW_DOWN) && checkModifierKeys(event, true, true, false)); | ||
const isF6Previous = (event) => ((event.key ? event.key === "F6" : event.keyCode === KeyCodes.F6) && checkModifierKeys(event, false, false, true)) | ||
|| ((event.key ? (event.key === "ArrowUp" || event.key === "Up") : event.keyCode === KeyCodes.ARROW_UP) && checkModifierKeys(event, true, true, false)); | ||
const isF7 = (event) => (event.key ? event.key === "F7" : event.keyCode === KeyCodes.F7) && !hasModifierKeys(event); | ||
const isShowByArrows = (event) => { | ||
return ((event.key === "ArrowDown" || event.key === "Down") || (event.key === "ArrowUp" || event.key === "Up")) && checkModifierKeys(event, /* Ctrl */ false, /* Alt */ true, /* Shift */ false); | ||
}; | ||
const hasModifierKeys = event => event.shiftKey || event.altKey || getCtrlKey(event); | ||
const getCtrlKey = event => !!(event.metaKey || event.ctrlKey); // double negation doesn't have effect on boolean but ensures null and undefined are equivalent to false. | ||
const isShift = (event) => event.key === "Shift" || event.keyCode === KeyCodes.SHIFT; | ||
const isCtrlA = (event) => ((event.key === "A" || event.key === "a") || event.which === KeyCodes.A) && checkModifierKeys(event, true, false, false); | ||
const isCtrlV = (event) => ((event.key === "V" || event.key === "v") || event.which === KeyCodes.V) && checkModifierKeys(event, true, false, false); | ||
const isKeyA = (event) => ((event.key === "A" || event.key === "a") || event.which === KeyCodes.A) && checkModifierKeys(event, false, false, false); | ||
const isKeyP = (event) => ((event.key === "P" || event.key === "p") || event.which === KeyCodes.P) && checkModifierKeys(event, false, false, false); | ||
const hasModifierKeys = (event) => event.shiftKey || event.altKey || getCtrlKey(event); | ||
const getCtrlKey = (event) => !!(event.metaKey || event.ctrlKey); // double negation doesn't have effect on boolean but ensures null and undefined are equivalent to false. | ||
const checkModifierKeys = (event, bCtrlKey, bAltKey, bShiftKey) => event.shiftKey === bShiftKey && event.altKey === bAltKey && getCtrlKey(event) === bCtrlKey; | ||
export { | ||
isEnter, | ||
isSpace, | ||
isLeft, | ||
isRight, | ||
isUp, | ||
isDown, | ||
isHome, | ||
isEnd, | ||
isEscape, | ||
isTabNext, | ||
isTabPrevious, | ||
isBackSpace, | ||
isDelete, | ||
isShow, | ||
isF4, | ||
isPageUp, | ||
isPageDown, | ||
isPageUpShift, | ||
isPageDownShift, | ||
isPageUpShiftCtrl, | ||
isPageDownShiftCtrl, | ||
}; | ||
const isNumber = (event) => ((event.key ? "0123456789".indexOf(event.key) !== -1 : event.keyCode >= KeyCodes.DIGIT_0 && event.keyCode <= KeyCodes.DIGIT_9) && checkModifierKeys(event, false, false, false)); | ||
const isColon = (event) => ((event.key ? event.key === ":" : event.keyCode === KeyCodes.COLON) && checkModifierKeys(event, false, false, true)); | ||
export { isEnter, isEnterShift, isSpace, isSpaceShift, isSpaceCtrl, isLeft, isRight, isUp, isDown, isLeftCtrl, isRightCtrl, isUpCtrl, isDownCtrl, isUpShift, isDownShift, isUpAlt, isDownAlt, isLeftShift, isRightShift, isLeftShiftCtrl, isRightShiftCtrl, isUpShiftCtrl, isDownShiftCtrl, isHome, isEnd, isPlus, isMinus, isHomeCtrl, isEndCtrl, isHomeShift, isEndShift, isEscape, isTabNext, isTabPrevious, isBackSpace, isDelete, isShow, isF4, isF4Shift, isF6Previous, isF6Next, isF7, isPageUp, isPageDown, isPageUpShift, isPageUpAlt, isPageDownShift, isPageDownAlt, isPageUpShiftCtrl, isPageDownShiftCtrl, isShift, isCtrlA, isCtrlV, isKeyA, isKeyP, isDeleteShift, isInsertShift, isInsertCtrl, isNumber, isColon, }; | ||
//# sourceMappingURL=Keys.js.map |
@@ -1,15 +0,18 @@ | ||
import RenderScheduler from "../RenderScheduler.js"; | ||
import { reRenderAllUI5Elements } from "../Render.js"; | ||
import { fireDirectionChange } from "./directionChange.js"; | ||
/** | ||
* Re-renders all RTL-aware UI5 Elements. | ||
* Call this method whenever you change the "dir" property anywhere in your HTML page | ||
* Example: document.body.dir = "rtl"; applyDirection(); | ||
* | ||
* **Note:** Call this method whenever you change the "dir" property anywhere in your HTML page. | ||
* | ||
* **Example:** `document.body.dir = "rtl"; applyDirection();` | ||
* @public | ||
* @returns {Promise<void>} | ||
*/ | ||
const applyDirection = () => { | ||
RenderScheduler.reRenderAllUI5Elements({ rtlAware: true }); | ||
return RenderScheduler.whenFinished(); | ||
const applyDirection = async () => { | ||
const listenersResults = fireDirectionChange(); | ||
await Promise.all(listenersResults); | ||
await reRenderAllUI5Elements({ rtlAware: true }); | ||
}; | ||
export default applyDirection; | ||
//# sourceMappingURL=applyDirection.js.map |
import detectNavigatorLanguage from "../util/detectNavigatorLanguage.js"; | ||
import { getLanguage as getConfigLanguage } from "../config/Language.js"; | ||
import Locale from "./Locale.js"; | ||
const convertToLocaleOrNull = lang => { | ||
try { | ||
if (lang && typeof lang === "string") { | ||
return new Locale(lang); | ||
} | ||
} catch (e) { | ||
// ignore | ||
} | ||
import { DEFAULT_LOCALE } from "../generated/AssetParameters.js"; | ||
const cache = new Map(); | ||
const getLocaleInstance = (lang) => { | ||
if (!cache.has(lang)) { | ||
cache.set(lang, new Locale(lang)); | ||
} | ||
return cache.get(lang); | ||
}; | ||
const convertToLocaleOrNull = (lang) => { | ||
try { | ||
if (lang && typeof lang === "string") { | ||
return getLocaleInstance(lang); | ||
} | ||
} | ||
catch (e) { | ||
// ignore | ||
} | ||
return new Locale(DEFAULT_LOCALE); | ||
}; | ||
/** | ||
@@ -19,14 +27,13 @@ * Returns the locale based on the parameter or configured language Configuration#getLanguage | ||
*/ | ||
const getLocale = lang => { | ||
if (lang) { | ||
return convertToLocaleOrNull(lang); | ||
} | ||
if (getConfigLanguage()) { | ||
return new Locale(getConfigLanguage()); | ||
} | ||
return convertToLocaleOrNull(detectNavigatorLanguage()); | ||
const getLocale = (lang) => { | ||
if (lang) { | ||
return convertToLocaleOrNull(lang); | ||
} | ||
const configLanguage = getConfigLanguage(); | ||
if (configLanguage) { | ||
return getLocaleInstance(configLanguage); | ||
} | ||
return convertToLocaleOrNull(detectNavigatorLanguage()); | ||
}; | ||
export default getLocale; | ||
//# sourceMappingURL=getLocale.js.map |
import EventProvider from "../EventProvider.js"; | ||
const eventProvider = new EventProvider(); | ||
const LANG_CHANGE = "languageChange"; | ||
const attachLanguageChange = listener => { | ||
eventProvider.attachEvent(LANG_CHANGE, listener); | ||
const attachLanguageChange = (listener) => { | ||
eventProvider.attachEvent(LANG_CHANGE, listener); | ||
}; | ||
const detachLanguageChange = listener => { | ||
eventProvider.detachEvent(LANG_CHANGE, listener); | ||
const detachLanguageChange = (listener) => { | ||
eventProvider.detachEvent(LANG_CHANGE, listener); | ||
}; | ||
const fireLanguageChange = lang => { | ||
return eventProvider.fireEvent(LANG_CHANGE, lang); | ||
const fireLanguageChange = (lang) => { | ||
return eventProvider.fireEventAsync(LANG_CHANGE, lang); | ||
}; | ||
export { | ||
attachLanguageChange, | ||
detachLanguageChange, | ||
fireLanguageChange, | ||
}; | ||
export { attachLanguageChange, detachLanguageChange, fireLanguageChange, }; | ||
//# sourceMappingURL=languageChange.js.map |
@@ -0,91 +1,79 @@ | ||
import { DEFAULT_LANGUAGE } from "../generated/AssetParameters.js"; | ||
const rLocale = /^((?:[A-Z]{2,3}(?:-[A-Z]{3}){0,3})|[A-Z]{4}|[A-Z]{5,8})(?:-([A-Z]{4}))?(?:-([A-Z]{2}|[0-9]{3}))?((?:-[0-9A-Z]{5,8}|-[0-9][0-9A-Z]{3})*)((?:-[0-9A-WYZ](?:-[0-9A-Z]{2,8})+)*)(?:-(X(?:-[0-9A-Z]{1,8})+))?$/i; | ||
class Locale { | ||
constructor(sLocaleId) { | ||
const aResult = rLocale.exec(sLocaleId.replace(/_/g, "-")); | ||
if (aResult === null) { | ||
throw new Error(`The given language ${sLocaleId} does not adhere to BCP-47.`); | ||
} | ||
this.sLocaleId = sLocaleId; | ||
this.sLanguage = aResult[1] || null; | ||
this.sScript = aResult[2] || null; | ||
this.sRegion = aResult[3] || null; | ||
this.sVariant = (aResult[4] && aResult[4].slice(1)) || null; | ||
this.sExtension = (aResult[5] && aResult[5].slice(1)) || null; | ||
this.sPrivateUse = aResult[6] || null; | ||
if (this.sLanguage) { | ||
this.sLanguage = this.sLanguage.toLowerCase(); | ||
} | ||
if (this.sScript) { | ||
this.sScript = this.sScript.toLowerCase().replace(/^[a-z]/, s => { | ||
return s.toUpperCase(); | ||
}); | ||
} | ||
if (this.sRegion) { | ||
this.sRegion = this.sRegion.toUpperCase(); | ||
} | ||
} | ||
getLanguage() { | ||
return this.sLanguage; | ||
} | ||
getScript() { | ||
return this.sScript; | ||
} | ||
getRegion() { | ||
return this.sRegion; | ||
} | ||
getVariant() { | ||
return this.sVariant; | ||
} | ||
getVariantSubtags() { | ||
return this.sVariant ? this.sVariant.split("-") : []; | ||
} | ||
getExtension() { | ||
return this.sExtension; | ||
} | ||
getExtensionSubtags() { | ||
return this.sExtension ? this.sExtension.slice(2).split("-") : []; | ||
} | ||
getPrivateUse() { | ||
return this.sPrivateUse; | ||
} | ||
getPrivateUseSubtags() { | ||
return this.sPrivateUse ? this.sPrivateUse.slice(2).split("-") : []; | ||
} | ||
hasPrivateUseSubtag(sSubtag) { | ||
return this.getPrivateUseSubtags().indexOf(sSubtag) >= 0; | ||
} | ||
toString() { | ||
const r = [this.sLanguage]; | ||
if (this.sScript) { | ||
r.push(this.sScript); | ||
} | ||
if (this.sRegion) { | ||
r.push(this.sRegion); | ||
} | ||
if (this.sVariant) { | ||
r.push(this.sVariant); | ||
} | ||
if (this.sExtension) { | ||
r.push(this.sExtension); | ||
} | ||
if (this.sPrivateUse) { | ||
r.push(this.sPrivateUse); | ||
} | ||
return r.join("-"); | ||
} | ||
constructor(sLocaleId) { | ||
const aResult = rLocale.exec(sLocaleId.replace(/_/g, "-")); | ||
if (aResult === null) { | ||
throw new Error(`The given language ${sLocaleId} does not adhere to BCP-47.`); | ||
} | ||
this.sLocaleId = sLocaleId; | ||
this.sLanguage = aResult[1] || DEFAULT_LANGUAGE; | ||
this.sScript = aResult[2] || ""; | ||
this.sRegion = aResult[3] || ""; | ||
this.sVariant = (aResult[4] && aResult[4].slice(1)) || null; | ||
this.sExtension = (aResult[5] && aResult[5].slice(1)) || null; | ||
this.sPrivateUse = aResult[6] || null; | ||
if (this.sLanguage) { | ||
this.sLanguage = this.sLanguage.toLowerCase(); | ||
} | ||
if (this.sScript) { | ||
this.sScript = this.sScript.toLowerCase().replace(/^[a-z]/, s => { | ||
return s.toUpperCase(); | ||
}); | ||
} | ||
if (this.sRegion) { | ||
this.sRegion = this.sRegion.toUpperCase(); | ||
} | ||
} | ||
getLanguage() { | ||
return this.sLanguage; | ||
} | ||
getScript() { | ||
return this.sScript; | ||
} | ||
getRegion() { | ||
return this.sRegion; | ||
} | ||
getVariant() { | ||
return this.sVariant; | ||
} | ||
getVariantSubtags() { | ||
return this.sVariant ? this.sVariant.split("-") : []; | ||
} | ||
getExtension() { | ||
return this.sExtension; | ||
} | ||
getExtensionSubtags() { | ||
return this.sExtension ? this.sExtension.slice(2).split("-") : []; | ||
} | ||
getPrivateUse() { | ||
return this.sPrivateUse; | ||
} | ||
getPrivateUseSubtags() { | ||
return this.sPrivateUse ? this.sPrivateUse.slice(2).split("-") : []; | ||
} | ||
hasPrivateUseSubtag(sSubtag) { | ||
return this.getPrivateUseSubtags().indexOf(sSubtag) >= 0; | ||
} | ||
toString() { | ||
const r = [this.sLanguage]; | ||
if (this.sScript) { | ||
r.push(this.sScript); | ||
} | ||
if (this.sRegion) { | ||
r.push(this.sRegion); | ||
} | ||
if (this.sVariant) { | ||
r.push(this.sVariant); | ||
} | ||
if (this.sExtension) { | ||
r.push(this.sExtension); | ||
} | ||
if (this.sPrivateUse) { | ||
r.push(this.sPrivateUse); | ||
} | ||
return r.join("-"); | ||
} | ||
} | ||
export default Locale; | ||
//# sourceMappingURL=Locale.js.map |
import { DEFAULT_LOCALE } from "../generated/AssetParameters.js"; | ||
/** | ||
@@ -9,21 +8,18 @@ * Calculates the next fallback locale for the given locale. | ||
*/ | ||
const nextFallbackLocale = locale => { | ||
if (!locale) { | ||
return DEFAULT_LOCALE; | ||
} | ||
if (locale === "zh_HK") { | ||
return "zh_TW"; | ||
} | ||
// if there are multiple segments (separated by underscores), remove the last one | ||
const p = locale.lastIndexOf("_"); | ||
if (p >= 0) { | ||
return locale.slice(0, p); | ||
} | ||
// for any language but the default, fallback to the default first before falling back to the 'raw' language (empty string) | ||
return locale !== DEFAULT_LOCALE ? DEFAULT_LOCALE : ""; | ||
const nextFallbackLocale = (locale) => { | ||
if (!locale) { | ||
return DEFAULT_LOCALE; | ||
} | ||
if (locale === "zh_HK") { | ||
return "zh_TW"; | ||
} | ||
// if there are multiple segments (separated by underscores), remove the last one | ||
const p = locale.lastIndexOf("_"); | ||
if (p >= 0) { | ||
return locale.slice(0, p); | ||
} | ||
// for any language but the default, fallback to the default first before falling back to the 'raw' language (empty string) | ||
return locale !== DEFAULT_LOCALE ? DEFAULT_LOCALE : ""; | ||
}; | ||
export default nextFallbackLocale; | ||
//# sourceMappingURL=nextFallbackLocale.js.map |
import { DEFAULT_LOCALE } from "../generated/AssetParameters.js"; | ||
const localeRegEX = /^((?:[A-Z]{2,3}(?:-[A-Z]{3}){0,3})|[A-Z]{4}|[A-Z]{5,8})(?:-([A-Z]{4}))?(?:-([A-Z]{2}|[0-9]{3}))?((?:-[0-9A-Z]{5,8}|-[0-9][0-9A-Z]{3})*)((?:-[0-9A-WYZ](?:-[0-9A-Z]{2,8})+)*)(?:-(X(?:-[0-9A-Z]{1,8})+))?$/i; | ||
const SAPSupportabilityLocales = /(?:^|-)(saptrc|sappsd)(?:-|$)/i; | ||
/* Map for old language names for a few ISO639 codes. */ | ||
const M_ISO639_NEW_TO_OLD = { | ||
"he": "iw", | ||
"yi": "ji", | ||
"id": "in", | ||
"sr": "sh", | ||
"he": "iw", | ||
"yi": "ji", | ||
"nb": "no", | ||
"sr": "sh", | ||
}; | ||
/** | ||
@@ -19,37 +16,33 @@ * Normalizes the given locale in BCP-47 syntax. | ||
*/ | ||
const normalizeLocale = locale => { | ||
let m; | ||
if (!locale) { | ||
return DEFAULT_LOCALE; | ||
} | ||
if (typeof locale === "string" && (m = localeRegEX.exec(locale.replace(/_/g, "-")))) {/* eslint-disable-line */ | ||
let language = m[1].toLowerCase(); | ||
let region = m[3] ? m[3].toUpperCase() : undefined; | ||
const script = m[2] ? m[2].toLowerCase() : undefined; | ||
const variants = m[4] ? m[4].slice(1) : undefined; | ||
const isPrivate = m[6]; | ||
language = M_ISO639_NEW_TO_OLD[language] || language; | ||
// recognize and convert special SAP supportability locales (overwrites m[]!) | ||
if ((isPrivate && (m = SAPSupportabilityLocales.exec(isPrivate))) /* eslint-disable-line */ || | ||
(variants && (m = SAPSupportabilityLocales.exec(variants)))) {/* eslint-disable-line */ | ||
return `en_US_${m[1].toLowerCase()}`; // for now enforce en_US (agreed with SAP SLS) | ||
} | ||
// Chinese: when no region but a script is specified, use default region for each script | ||
if (language === "zh" && !region) { | ||
if (script === "hans") { | ||
region = "CN"; | ||
} else if (script === "hant") { | ||
region = "TW"; | ||
} | ||
} | ||
return language + (region ? "_" + region + (variants ? "_" + variants.replace("-", "_") : "") : ""); /* eslint-disable-line */ | ||
} | ||
const normalizeLocale = (locale) => { | ||
let m; | ||
if (!locale) { | ||
return DEFAULT_LOCALE; | ||
} | ||
if (typeof locale === "string" && (m = localeRegEX.exec(locale.replace(/_/g, "-")))) { /* eslint-disable-line */ | ||
let language = m[1].toLowerCase(); | ||
let region = m[3] ? m[3].toUpperCase() : undefined; | ||
const script = m[2] ? m[2].toLowerCase() : undefined; | ||
const variants = m[4] ? m[4].slice(1) : undefined; | ||
const isPrivate = m[6]; | ||
language = M_ISO639_NEW_TO_OLD[language] || language; | ||
// recognize and convert special SAP supportability locales (overwrites m[]!) | ||
if ((isPrivate && (m = SAPSupportabilityLocales.exec(isPrivate))) /* eslint-disable-line */ || | ||
(variants && (m = SAPSupportabilityLocales.exec(variants)))) { /* eslint-disable-line */ | ||
return `en_US_${m[1].toLowerCase()}`; // for now enforce en_US (agreed with SAP SLS) | ||
} | ||
// Chinese: when no region but a script is specified, use default region for each script | ||
if (language === "zh" && !region) { | ||
if (script === "hans") { | ||
region = "CN"; | ||
} | ||
else if (script === "hant") { | ||
region = "TW"; | ||
} | ||
} | ||
return language + (region ? "_" + region + (variants ? "_" + variants.replace("-", "_") : "") : ""); /* eslint-disable-line */ | ||
} | ||
return DEFAULT_LOCALE; | ||
}; | ||
export default normalizeLocale; | ||
//# sourceMappingURL=normalizeLocale.js.map |
const rtlAwareSet = new Set(); | ||
const markAsRtlAware = klass => { | ||
rtlAwareSet.add(klass); | ||
const markAsRtlAware = (klass) => { | ||
rtlAwareSet.add(klass); | ||
}; | ||
const isRtlAware = klass => { | ||
return rtlAwareSet.has(klass); | ||
const isRtlAware = (klass) => { | ||
return rtlAwareSet.has(klass); | ||
}; | ||
export { | ||
markAsRtlAware, | ||
isRtlAware, | ||
}; | ||
export { markAsRtlAware, isRtlAware, }; | ||
//# sourceMappingURL=RTLAwareRegistry.js.map |
@@ -1,3 +0,2 @@ | ||
import { getCustomElementsScopingSuffix, shouldScopeCustomElement } from "../CustomElementsScope.js"; | ||
import { getCustomElementsScopingSuffix, shouldScopeCustomElement } from "../CustomElementsScopeUtils.js"; | ||
/** | ||
@@ -9,10 +8,24 @@ * Runs a component's template with the component's current state, while also scoping HTML | ||
* @public | ||
* @returns {*} | ||
*/ | ||
const executeTemplate = (template, component) => { | ||
const tagsToScope = component.constructor.getUniqueDependencies().map(dep => dep.getMetadata().getPureTag()).filter(shouldScopeCustomElement); | ||
const scope = getCustomElementsScopingSuffix(); | ||
return template(component, tagsToScope, scope); | ||
const tagsToScope = getTagsToScope(component); | ||
const scope = getCustomElementsScopingSuffix(); | ||
return template.call(component, component, tagsToScope, scope); | ||
}; | ||
/** | ||
* Returns all tags, used inside component's template subject to scoping. | ||
* @param component - the component | ||
* @returns {Array[]} | ||
* @private | ||
*/ | ||
const getTagsToScope = (component) => { | ||
const ctor = component.constructor; | ||
const componentTag = ctor.getMetadata().getPureTag(); | ||
const tagsToScope = ctor.getUniqueDependencies().map((dep) => dep.getMetadata().getPureTag()).filter(shouldScopeCustomElement); | ||
if (shouldScopeCustomElement(componentTag)) { | ||
tagsToScope.push(componentTag); | ||
} | ||
return tagsToScope; | ||
}; | ||
export default executeTemplate; | ||
//# sourceMappingURL=executeTemplate.js.map |
@@ -1,31 +0,41 @@ | ||
import { html, svg, render } from "lit-html/lit-html.js"; | ||
import scopeHTML from "./scopeHTML.js"; | ||
let tags; | ||
let suffix; | ||
const setTags = t => { | ||
tags = t; | ||
import { render, html, svg, } from "lit-html"; | ||
import { getFeature } from "../FeaturesRegistry.js"; | ||
const effectiveHtml = (strings, ...values) => { | ||
const litStatic = getFeature("LitStatic"); | ||
const fn = litStatic ? litStatic.html : html; | ||
return fn(strings, ...values); | ||
}; | ||
const setSuffix = s => { | ||
suffix = s; | ||
const effectiveSvg = (strings, ...values) => { | ||
const litStatic = getFeature("LitStatic"); | ||
const fn = litStatic ? litStatic.svg : svg; | ||
return fn(strings, ...values); | ||
}; | ||
const litRender = (templateResult, domNode, styles, { eventContext } = {}) => { | ||
if (styles) { | ||
templateResult = html`<style>${styles}</style>${templateResult}`; | ||
} | ||
render(templateResult, domNode, { eventContext }); | ||
const litRender = (templateResult, container, styleStrOrHrefsArr, forStaticArea, options) => { | ||
const openUI5Enablement = getFeature("OpenUI5Enablement"); | ||
if (openUI5Enablement && !forStaticArea) { | ||
templateResult = openUI5Enablement.wrapTemplateResultInBusyMarkup(effectiveHtml, options.host, templateResult); | ||
} | ||
if (typeof styleStrOrHrefsArr === "string") { | ||
templateResult = effectiveHtml `<style>${styleStrOrHrefsArr}</style>${templateResult}`; | ||
} | ||
else if (Array.isArray(styleStrOrHrefsArr) && styleStrOrHrefsArr.length) { | ||
templateResult = effectiveHtml `${styleStrOrHrefsArr.map(href => effectiveHtml `<link type="text/css" rel="stylesheet" href="${href}">`)}${templateResult}`; | ||
} | ||
render(templateResult, container, options); | ||
}; | ||
const scopedHtml = (strings, ...values) => html(scopeHTML(strings, tags, suffix), ...values); | ||
const scopedSvg = (strings, ...values) => svg(scopeHTML(strings, tags, suffix), ...values); | ||
export { setTags, setSuffix }; | ||
export { scopedHtml as html, scopedSvg as svg }; | ||
const scopeTag = (tag, tags, suffix) => { | ||
const litStatic = getFeature("LitStatic"); | ||
if (litStatic) { | ||
return litStatic.unsafeStatic((tags || []).includes(tag) ? `${tag}-${suffix}` : tag); | ||
} | ||
}; | ||
export { effectiveHtml as html, effectiveSvg as svg, }; | ||
export { scopeTag }; | ||
export { repeat } from "lit-html/directives/repeat.js"; | ||
export { classMap } from "lit-html/directives/class-map.js"; | ||
export { styleMap } from "lit-html/directives/style-map.js"; | ||
// @ts-ignore style-map is a JS file | ||
export { styleMap } from "./directives/style-map.js"; | ||
export { ifDefined } from "lit-html/directives/if-defined.js"; | ||
export { unsafeHTML } from "lit-html/directives/unsafe-html.js"; | ||
export default litRender; | ||
//# sourceMappingURL=LitRenderer.js.map |
const MAX_PROCESS_COUNT = 10; | ||
class RenderQueue { | ||
constructor() { | ||
this.list = []; // Used to store the web components in order | ||
this.lookup = new Set(); // Used for faster search | ||
} | ||
add(webComponent) { | ||
if (this.lookup.has(webComponent)) { | ||
return; | ||
} | ||
this.list.push(webComponent); | ||
this.lookup.add(webComponent); | ||
} | ||
remove(webComponent) { | ||
if (!this.lookup.has(webComponent)) { | ||
return; | ||
} | ||
this.list = this.list.filter(item => item !== webComponent); | ||
this.lookup.delete(webComponent); | ||
} | ||
shift() { | ||
const webComponent = this.list.shift(); | ||
if (webComponent) { | ||
this.lookup.delete(webComponent); | ||
return webComponent; | ||
} | ||
} | ||
isEmpty() { | ||
return this.list.length === 0; | ||
} | ||
isAdded(webComponent) { | ||
return this.lookup.has(webComponent); | ||
} | ||
/** | ||
* Processes the whole queue by executing the callback on each component, | ||
* while also imposing restrictions on how many times a component may be processed. | ||
* | ||
* @param callback - function with one argument (the web component to be processed) | ||
*/ | ||
process(callback) { | ||
let webComponent; | ||
const stats = new Map(); | ||
webComponent = this.shift(); | ||
while (webComponent) { | ||
const timesProcessed = stats.get(webComponent) || 0; | ||
if (timesProcessed > MAX_PROCESS_COUNT) { | ||
throw new Error(`Web component processed too many times this task, max allowed is: ${MAX_PROCESS_COUNT}`); | ||
} | ||
callback(webComponent); | ||
stats.set(webComponent, timesProcessed + 1); | ||
webComponent = this.shift(); | ||
} | ||
} | ||
constructor() { | ||
this.list = []; // Used to store the web components in order | ||
this.lookup = new Set(); // Used for faster search | ||
} | ||
add(webComponent) { | ||
if (this.lookup.has(webComponent)) { | ||
return; | ||
} | ||
this.list.push(webComponent); | ||
this.lookup.add(webComponent); | ||
} | ||
remove(webComponent) { | ||
if (!this.lookup.has(webComponent)) { | ||
return; | ||
} | ||
this.list = this.list.filter(item => item !== webComponent); | ||
this.lookup.delete(webComponent); | ||
} | ||
shift() { | ||
const webComponent = this.list.shift(); | ||
if (webComponent) { | ||
this.lookup.delete(webComponent); | ||
return webComponent; | ||
} | ||
} | ||
isEmpty() { | ||
return this.list.length === 0; | ||
} | ||
isAdded(webComponent) { | ||
return this.lookup.has(webComponent); | ||
} | ||
/** | ||
* Processes the whole queue by executing the callback on each component, | ||
* while also imposing restrictions on how many times a component may be processed. | ||
* | ||
* @param callback - function with one argument (the web component to be processed) | ||
*/ | ||
process(callback) { | ||
let webComponent; | ||
const stats = new Map(); | ||
webComponent = this.shift(); | ||
while (webComponent) { | ||
const timesProcessed = stats.get(webComponent) || 0; | ||
if (timesProcessed > MAX_PROCESS_COUNT) { | ||
throw new Error(`Web component processed too many times this task, max allowed is: ${MAX_PROCESS_COUNT}`); | ||
} | ||
callback(webComponent); | ||
stats.set(webComponent, timesProcessed + 1); | ||
webComponent = this.shift(); | ||
} | ||
} | ||
} | ||
export default RenderQueue; | ||
//# sourceMappingURL=RenderQueue.js.map |
@@ -1,31 +0,7 @@ | ||
import getSingletonElementInstance from "./util/getSingletonElementInstance.js"; | ||
const getStaticAreaInstance = () => getSingletonElementInstance("ui5-static-area"); | ||
const removeStaticArea = () => { | ||
getStaticAreaInstance().destroy(); | ||
}; | ||
class StaticAreaElement extends HTMLElement { | ||
constructor() { | ||
super(); | ||
} | ||
get isUI5Element() { | ||
return true; | ||
} | ||
destroy() { | ||
const staticAreaDomRef = document.querySelector(this.tagName.toLowerCase()); | ||
staticAreaDomRef.parentElement.removeChild(staticAreaDomRef); | ||
} | ||
class StaticArea extends HTMLElement { | ||
} | ||
if (!customElements.get("ui5-static-area")) { | ||
customElements.define("ui5-static-area", StaticAreaElement); | ||
customElements.define("ui5-static-area", StaticArea); | ||
} | ||
export { | ||
getStaticAreaInstance, | ||
removeStaticArea, | ||
}; | ||
export default StaticArea; | ||
//# sourceMappingURL=StaticArea.js.map |
@@ -1,108 +0,100 @@ | ||
import { getStaticAreaInstance, removeStaticArea } from "./StaticArea.js"; | ||
import RenderScheduler from "./RenderScheduler.js"; | ||
import getStylesString from "./theming/getStylesString.js"; | ||
import executeTemplate from "./renderer/executeTemplate.js"; | ||
import "./StaticArea.js"; | ||
import updateShadowRoot from "./updateShadowRoot.js"; | ||
import { renderFinished } from "./Render.js"; | ||
import getEffectiveContentDensity from "./util/getEffectiveContentDensity.js"; | ||
import { getEffectiveScopingSuffixForTag } from "./CustomElementsScopeUtils.js"; | ||
import getEffectiveDir from "./locale/getEffectiveDir.js"; | ||
const pureTagName = "ui5-static-area-item"; | ||
const popupIntegrationAttr = "data-sap-ui-integration-popup-content"; | ||
/** | ||
* @class | ||
* @author SAP SE | ||
* @private | ||
* Defines and takes care of ui5-static-are-item items | ||
*/ | ||
class StaticAreaItem { | ||
constructor(_ui5ElementContext) { | ||
this.ui5ElementContext = _ui5ElementContext; | ||
this._rendered = false; | ||
} | ||
isRendered() { | ||
return this._rendered; | ||
} | ||
/** | ||
* @protected | ||
*/ | ||
_updateFragment() { | ||
const renderResult = executeTemplate(this.ui5ElementContext.constructor.staticAreaTemplate, this.ui5ElementContext), | ||
stylesToAdd = window.ShadyDOM ? false : getStylesString(this.ui5ElementContext.constructor.staticAreaStyles); | ||
if (!this.staticAreaItemDomRef) { | ||
// Initial rendering of fragment | ||
this.staticAreaItemDomRef = document.createElement("ui5-static-area-item"); | ||
this.staticAreaItemDomRef.attachShadow({ mode: "open" }); | ||
this.staticAreaItemDomRef.classList.add(this.ui5ElementContext._id); // used for getting the popover in the tests | ||
getStaticAreaInstance().appendChild(this.staticAreaItemDomRef); | ||
this._rendered = true; | ||
} | ||
this._updateContentDensity(this.ui5ElementContext.isCompact); | ||
this.ui5ElementContext.constructor.render(renderResult, this.staticAreaItemDomRef.shadowRoot, stylesToAdd, { eventContext: this.ui5ElementContext }); | ||
} | ||
/** | ||
* @protected | ||
*/ | ||
_removeFragmentFromStaticArea() { | ||
if (!this.staticAreaItemDomRef) { | ||
return; | ||
} | ||
const staticArea = getStaticAreaInstance(); | ||
staticArea.removeChild(this.staticAreaItemDomRef); | ||
this.staticAreaItemDomRef = null; | ||
// remove static area | ||
if (staticArea.childElementCount < 1) { | ||
removeStaticArea(); | ||
} | ||
} | ||
/** | ||
* @protected | ||
*/ | ||
_updateContentDensity(isCompact) { | ||
if (!this.staticAreaItemDomRef) { | ||
return; | ||
} | ||
if (isCompact) { | ||
this.staticAreaItemDomRef.classList.add("sapUiSizeCompact"); | ||
this.staticAreaItemDomRef.classList.add("ui5-content-density-compact"); | ||
} else { | ||
this.staticAreaItemDomRef.classList.remove("sapUiSizeCompact"); | ||
this.staticAreaItemDomRef.classList.remove("ui5-content-density-compact"); | ||
} | ||
} | ||
/** | ||
* @protected | ||
* Returns reference to the DOM element where the current fragment is added. | ||
*/ | ||
async getDomRef() { | ||
if (!this._rendered || !this.staticAreaItemDomRef) { | ||
this._updateFragment(); | ||
} | ||
await RenderScheduler.whenDOMUpdated(); // Wait for the content of the ui5-static-area-item to be rendered | ||
return this.staticAreaItemDomRef.shadowRoot; | ||
} | ||
class StaticAreaItem extends HTMLElement { | ||
constructor() { | ||
super(); | ||
this._rendered = false; | ||
this.attachShadow({ mode: "open" }); | ||
} | ||
/** | ||
* @param ownerElement the UI5Element instance that owns this static area item | ||
*/ | ||
setOwnerElement(ownerElement) { | ||
this.ownerElement = ownerElement; | ||
this.classList.add(this.ownerElement._id); // used for getting the popover in the tests | ||
if (this.ownerElement.hasAttribute("data-ui5-static-stable")) { | ||
this.setAttribute("data-ui5-stable", this.ownerElement.getAttribute("data-ui5-static-stable")); // stable selector | ||
} | ||
} | ||
/** | ||
* Updates the shadow root of the static area item with the latest state, if rendered | ||
*/ | ||
update() { | ||
if (this._rendered) { | ||
this.updateAdditionalProperties(); | ||
updateShadowRoot(this.ownerElement, true); | ||
} | ||
} | ||
updateAdditionalProperties() { | ||
this._updateAdditionalAttrs(); | ||
this._updateContentDensity(); | ||
this._updateDirection(); | ||
} | ||
/** | ||
* Sets the correct content density based on the owner element's state | ||
* @private | ||
*/ | ||
_updateContentDensity() { | ||
if (getEffectiveContentDensity(this.ownerElement) === "compact") { | ||
this.classList.add("sapUiSizeCompact"); | ||
this.classList.add("ui5-content-density-compact"); | ||
} | ||
else { | ||
this.classList.remove("sapUiSizeCompact"); | ||
this.classList.remove("ui5-content-density-compact"); | ||
} | ||
} | ||
_updateDirection() { | ||
if (this.ownerElement) { | ||
const dir = getEffectiveDir(this.ownerElement); | ||
if (dir === "rtl") { | ||
this.setAttribute("dir", dir); | ||
} | ||
else { | ||
this.removeAttribute("dir"); | ||
} | ||
} | ||
} | ||
_updateAdditionalAttrs() { | ||
this.setAttribute(pureTagName, ""); | ||
this.setAttribute(popupIntegrationAttr, ""); | ||
} | ||
/** | ||
* Returns reference to the DOM element where the current fragment is added. | ||
* @protected | ||
*/ | ||
async getDomRef() { | ||
this.updateAdditionalProperties(); | ||
if (!this._rendered) { | ||
this._rendered = true; | ||
updateShadowRoot(this.ownerElement, true); | ||
} | ||
await renderFinished(); // Wait for the content of the ui5-static-area-item to be rendered | ||
return this.shadowRoot; | ||
} | ||
static getTag() { | ||
const suffix = getEffectiveScopingSuffixForTag(pureTagName); | ||
if (!suffix) { | ||
return pureTagName; | ||
} | ||
return `${pureTagName}-${suffix}`; | ||
} | ||
static createInstance() { | ||
if (!customElements.get(StaticAreaItem.getTag())) { | ||
customElements.define(StaticAreaItem.getTag(), StaticAreaItem); | ||
} | ||
return document.createElement(this.getTag()); | ||
} | ||
} | ||
class StaticAreaItemElement extends HTMLElement { | ||
constructor() { | ||
super(); | ||
} | ||
get isUI5Element() { | ||
return true; | ||
} | ||
} | ||
if (!customElements.get("ui5-static-area-item")) { | ||
customElements.define("ui5-static-area-item", StaticAreaItemElement); | ||
} | ||
export default StaticAreaItem; | ||
//# sourceMappingURL=StaticAreaItem.js.map |
@@ -1,31 +0,9 @@ | ||
import createStyleInHead from "./util/createStyleInHead.js"; | ||
const systemCSSVars = ` | ||
:root { | ||
--_ui5_content_density:cozy; | ||
} | ||
[data-ui5-compact-size], | ||
.ui5-content-density-compact, | ||
.sapUiSizeCompact { | ||
--_ui5_content_density:compact; | ||
} | ||
[dir="rtl"] { | ||
--_ui5_dir:rtl; | ||
} | ||
[dir="ltr"] { | ||
--_ui5_dir:ltr; | ||
} | ||
`; | ||
import { hasStyle, createStyle } from "./ManagedStyles.js"; | ||
import systemCSSVars from "./generated/css/SystemCSSVars.css.js"; | ||
const insertSystemCSSVars = () => { | ||
if (document.querySelector(`head>style[data-ui5-system-css-vars]`)) { | ||
return; | ||
} | ||
createStyleInHead(systemCSSVars, { "data-ui5-system-css-vars": "" }); | ||
if (!hasStyle("data-ui5-system-css-vars")) { | ||
createStyle(systemCSSVars, "data-ui5-system-css-vars"); | ||
} | ||
}; | ||
export default insertSystemCSSVars; | ||
//# sourceMappingURL=SystemCSSVars.js.map |
import { addCustomCSS } from "./theming/CustomStyle.js"; | ||
export { addCustomCSS }; // eslint-disable-line | ||
import { attachThemeLoaded, detachThemeLoaded } from "./theming/ThemeLoaded.js"; | ||
export { addCustomCSS, attachThemeLoaded, detachThemeLoaded }; | ||
//# sourceMappingURL=Theming.js.map |
import { getThemeProperties, getRegisteredPackages, isThemeRegistered } from "../asset-registries/Themes.js"; | ||
import createThemePropertiesStyleTag from "./createThemePropertiesStyleTag.js"; | ||
import { removeStyle, createOrUpdateStyle } from "../ManagedStyles.js"; | ||
import getThemeDesignerTheme from "./getThemeDesignerTheme.js"; | ||
import { ponyfillNeeded, runPonyfill } from "./CSSVarsPonyfill.js"; | ||
import { fireThemeLoaded } from "./ThemeLoaded.js"; | ||
import { getFeature } from "../FeaturesRegistry.js"; | ||
const BASE_THEME_PACKAGE = "@ui5/webcomponents-theme-base"; | ||
import { attachCustomThemeStylesToHead, getThemeRoot } from "../config/ThemeRoot.js"; | ||
import { DEFAULT_THEME } from "../generated/AssetParameters.js"; | ||
import { getCurrentRuntimeIndex } from "../Runtimes.js"; | ||
const BASE_THEME_PACKAGE = "@ui5/webcomponents-theming"; | ||
const isThemeBaseRegistered = () => { | ||
const registeredPackages = getRegisteredPackages(); | ||
return registeredPackages.has(BASE_THEME_PACKAGE); | ||
const registeredPackages = getRegisteredPackages(); | ||
return registeredPackages.has(BASE_THEME_PACKAGE); | ||
}; | ||
const loadThemeBase = async theme => { | ||
if (!isThemeBaseRegistered()) { | ||
return; | ||
} | ||
const cssText = await getThemeProperties(BASE_THEME_PACKAGE, theme); | ||
createThemePropertiesStyleTag(cssText, BASE_THEME_PACKAGE); | ||
const loadThemeBase = async (theme) => { | ||
if (!isThemeBaseRegistered()) { | ||
return; | ||
} | ||
const cssData = await getThemeProperties(BASE_THEME_PACKAGE, theme); | ||
if (cssData) { | ||
createOrUpdateStyle(cssData, "data-ui5-theme-properties", BASE_THEME_PACKAGE, theme); | ||
} | ||
}; | ||
const deleteThemeBase = () => { | ||
const styleElement = document.head.querySelector(`style[data-ui5-theme-properties="${BASE_THEME_PACKAGE}"]`); | ||
if (styleElement) { | ||
styleElement.parentElement.removeChild(styleElement); | ||
} | ||
removeStyle("data-ui5-theme-properties", BASE_THEME_PACKAGE); | ||
}; | ||
const loadComponentPackages = async theme => { | ||
const registeredPackages = getRegisteredPackages(); | ||
registeredPackages.forEach(async packageName => { | ||
if (packageName === BASE_THEME_PACKAGE) { | ||
return; | ||
} | ||
const cssText = await getThemeProperties(packageName, theme); | ||
createThemePropertiesStyleTag(cssText, packageName); | ||
}); | ||
const loadComponentPackages = async (theme, externalThemeName) => { | ||
const registeredPackages = getRegisteredPackages(); | ||
const packagesStylesPromises = [...registeredPackages].map(async (packageName) => { | ||
if (packageName === BASE_THEME_PACKAGE) { | ||
return; | ||
} | ||
const cssData = await getThemeProperties(packageName, theme, externalThemeName); | ||
if (cssData) { | ||
createOrUpdateStyle(cssData, `data-ui5-component-properties-${getCurrentRuntimeIndex()}`, packageName); | ||
} | ||
}); | ||
return Promise.all(packagesStylesPromises); | ||
}; | ||
const detectExternalTheme = () => { | ||
// If theme designer theme is detected, use this | ||
const extTheme = getThemeDesignerTheme(); | ||
if (extTheme) { | ||
return extTheme; | ||
} | ||
// If OpenUI5Support is enabled, try to find out if it loaded variables | ||
const OpenUI5Support = getFeature("OpenUI5Support"); | ||
if (OpenUI5Support) { | ||
const varsLoaded = OpenUI5Support.cssVariablesLoaded(); | ||
if (varsLoaded) { | ||
return { | ||
themeName: OpenUI5Support.getConfigurationSettingsObject().theme, // just themeName, baseThemeName is only relevant for custom themes | ||
}; | ||
} | ||
} | ||
const detectExternalTheme = async (theme) => { | ||
// If theme designer theme is detected, use this | ||
const extTheme = getThemeDesignerTheme(); | ||
if (extTheme) { | ||
return extTheme; | ||
} | ||
// If OpenUI5Support is enabled, try to find out if it loaded variables | ||
const openUI5Support = getFeature("OpenUI5Support"); | ||
if (openUI5Support && openUI5Support.isOpenUI5Detected()) { | ||
const varsLoaded = openUI5Support.cssVariablesLoaded(); | ||
if (varsLoaded) { | ||
return { | ||
themeName: openUI5Support.getConfigurationSettingsObject()?.theme, | ||
baseThemeName: "", // baseThemeName is only relevant for custom themes | ||
}; | ||
} | ||
} | ||
else if (getThemeRoot()) { | ||
await attachCustomThemeStylesToHead(theme); | ||
return getThemeDesignerTheme(); | ||
} | ||
}; | ||
const applyTheme = async theme => { | ||
const extTheme = detectExternalTheme(); | ||
// Only load theme_base properties if there is no externally loaded theme, or there is, but it is not being loaded | ||
if (!extTheme || theme !== extTheme.themeName) { | ||
await loadThemeBase(theme); | ||
} else { | ||
deleteThemeBase(); | ||
} | ||
// Always load component packages properties. For non-registered themes, try with the base theme, if any | ||
const packagesTheme = isThemeRegistered(theme) ? theme : extTheme && extTheme.baseThemeName; | ||
await loadComponentPackages(packagesTheme); | ||
// When changing the theme, run the ponyfill immediately | ||
if (ponyfillNeeded()) { | ||
runPonyfill(); | ||
} | ||
const applyTheme = async (theme) => { | ||
const extTheme = await detectExternalTheme(theme); | ||
// Only load theme_base properties if there is no externally loaded theme, or there is, but it is not being loaded | ||
if (!extTheme || theme !== extTheme.themeName) { | ||
await loadThemeBase(theme); | ||
} | ||
else { | ||
deleteThemeBase(); | ||
} | ||
// Always load component packages properties. For non-registered themes, try with the base theme, if any | ||
const packagesTheme = isThemeRegistered(theme) ? theme : extTheme && extTheme.baseThemeName; | ||
await loadComponentPackages(packagesTheme || DEFAULT_THEME, extTheme && extTheme.themeName === theme ? theme : undefined); | ||
fireThemeLoaded(theme); | ||
}; | ||
export default applyTheme; | ||
//# sourceMappingURL=applyTheme.js.map |
@@ -1,40 +0,48 @@ | ||
import RenderScheduler from "../RenderScheduler.js"; | ||
import { reRenderAllUI5Elements } from "../Render.js"; | ||
import getSharedResource from "../getSharedResource.js"; | ||
import EventProvider from "../EventProvider.js"; | ||
const eventProvider = new EventProvider(); | ||
const getEventProvider = () => getSharedResource("CustomStyle.eventProvider", new EventProvider()); | ||
const CUSTOM_CSS_CHANGE = "CustomCSSChange"; | ||
const attachCustomCSSChange = listener => { | ||
eventProvider.attachEvent(CUSTOM_CSS_CHANGE, listener); | ||
const attachCustomCSSChange = (listener) => { | ||
getEventProvider().attachEvent(CUSTOM_CSS_CHANGE, listener); | ||
}; | ||
const detachCustomCSSChange = listener => { | ||
eventProvider.detachEvent(CUSTOM_CSS_CHANGE, listener); | ||
const detachCustomCSSChange = (listener) => { | ||
getEventProvider().detachEvent(CUSTOM_CSS_CHANGE, listener); | ||
}; | ||
const fireCustomCSSChange = tag => { | ||
return eventProvider.fireEvent(CUSTOM_CSS_CHANGE, tag); | ||
const fireCustomCSSChange = (tag) => { | ||
return getEventProvider().fireEvent(CUSTOM_CSS_CHANGE, tag); | ||
}; | ||
const customCSSFor = {}; | ||
const getCustomCSSFor = () => getSharedResource("CustomStyle.customCSSFor", {}); | ||
// Listen to the eventProvider, in case other copies of this CustomStyle module fire this | ||
// event, and this copy would therefore need to reRender the ui5 webcomponents; but | ||
// don't reRender if it was this copy that fired the event to begin with. | ||
let skipRerender; | ||
attachCustomCSSChange((tag) => { | ||
if (!skipRerender) { | ||
reRenderAllUI5Elements({ tag }); | ||
} | ||
}); | ||
const addCustomCSS = (tag, css) => { | ||
if (!customCSSFor[tag]) { | ||
customCSSFor[tag] = []; | ||
} | ||
customCSSFor[tag].push(css); | ||
fireCustomCSSChange(tag); | ||
RenderScheduler.reRenderAllUI5Elements({ tag }); | ||
const customCSSFor = getCustomCSSFor(); | ||
if (!customCSSFor[tag]) { | ||
customCSSFor[tag] = []; | ||
} | ||
customCSSFor[tag].push(css); | ||
skipRerender = true; | ||
try { | ||
// The event is fired and the attached event listeners are all called synchronously | ||
// The skipRerender flag will be used to avoid calling reRenderAllUI5Elements twice when it is this copy | ||
// of CustomStyle.js which is firing the `CustomCSSChange` event. | ||
fireCustomCSSChange(tag); | ||
} | ||
finally { | ||
skipRerender = false; | ||
} | ||
return reRenderAllUI5Elements({ tag }); | ||
}; | ||
const getCustomCSS = tag => { | ||
return customCSSFor[tag] ? customCSSFor[tag].join("") : ""; | ||
const getCustomCSS = (tag) => { | ||
const customCSSFor = getCustomCSSFor(); | ||
return customCSSFor[tag] ? customCSSFor[tag].join("") : ""; | ||
}; | ||
export { | ||
addCustomCSS, | ||
getCustomCSS, | ||
attachCustomCSSChange, | ||
detachCustomCSSChange, | ||
}; | ||
export { addCustomCSS, getCustomCSS, attachCustomCSSChange, detachCustomCSSChange, }; | ||
//# sourceMappingURL=CustomStyle.js.map |
import getEffectiveStyle from "./getEffectiveStyle.js"; | ||
import { attachCustomCSSChange } from "./CustomStyle.js"; | ||
const constructableStyleMap = new Map(); | ||
attachCustomCSSChange(tag => { | ||
constructableStyleMap.delete(tag); | ||
attachCustomCSSChange((tag) => { | ||
constructableStyleMap.delete(`${tag}_normal`); // there is custom CSS only for the component itself, not for its static area part | ||
}); | ||
/** | ||
@@ -16,15 +13,14 @@ * Returns (and caches) a constructable style sheet for a web component class | ||
*/ | ||
const getConstructableStyle = ElementClass => { | ||
const tag = ElementClass.getMetadata().getTag(); | ||
if (!constructableStyleMap.has(tag)) { | ||
const styleContent = getEffectiveStyle(ElementClass); | ||
const style = new CSSStyleSheet(); | ||
style.replaceSync(styleContent); | ||
constructableStyleMap.set(tag, [style]); | ||
} | ||
return constructableStyleMap.get(tag); | ||
const getConstructableStyle = (ElementClass, forStaticArea = false) => { | ||
const tag = ElementClass.getMetadata().getTag(); | ||
const key = `${tag}_${forStaticArea ? "static" : "normal"}`; | ||
if (!constructableStyleMap.has(key)) { | ||
const styleContent = getEffectiveStyle(ElementClass, forStaticArea); | ||
const style = new CSSStyleSheet(); | ||
style.replaceSync(styleContent); | ||
constructableStyleMap.set(key, [style]); | ||
} | ||
return constructableStyleMap.get(key); | ||
}; | ||
export default getConstructableStyle; | ||
//# sourceMappingURL=getConstructableStyle.js.map |
import { getCustomCSS, attachCustomCSSChange } from "./CustomStyle.js"; | ||
import getStylesString from "./getStylesString.js"; | ||
import { getFeature } from "../FeaturesRegistry.js"; | ||
const effectiveStyleMap = new Map(); | ||
attachCustomCSSChange(tag => { | ||
effectiveStyleMap.delete(tag); | ||
attachCustomCSSChange((tag) => { | ||
effectiveStyleMap.delete(`${tag}_normal`); // there is custom CSS only for the component itself, not for its static area part | ||
}); | ||
const getEffectiveStyle = ElementClass => { | ||
const tag = ElementClass.getMetadata().getTag(); | ||
if (!effectiveStyleMap.has(tag)) { | ||
const customStyle = getCustomCSS(tag) || ""; | ||
const builtInStyles = getStylesString(ElementClass.styles); | ||
const effectiveStyle = `${builtInStyles} ${customStyle}`; | ||
effectiveStyleMap.set(tag, effectiveStyle); | ||
} | ||
return effectiveStyleMap.get(tag); | ||
const getEffectiveStyle = (ElementClass, forStaticArea = false) => { | ||
const tag = ElementClass.getMetadata().getTag(); | ||
const key = `${tag}_${forStaticArea ? "static" : "normal"}`; | ||
const openUI5Enablement = getFeature("OpenUI5Enablement"); | ||
if (!effectiveStyleMap.has(key)) { | ||
let effectiveStyle; | ||
let busyIndicatorStyles = ""; | ||
if (openUI5Enablement) { | ||
busyIndicatorStyles = getStylesString(openUI5Enablement.getBusyIndicatorStyles()); | ||
} | ||
if (forStaticArea) { | ||
effectiveStyle = getStylesString(ElementClass.staticAreaStyles); | ||
} | ||
else { | ||
const customStyle = getCustomCSS(tag) || ""; | ||
const builtInStyles = getStylesString(ElementClass.styles); | ||
effectiveStyle = `${builtInStyles} ${customStyle}`; | ||
} | ||
effectiveStyle = `${effectiveStyle} ${busyIndicatorStyles}`; | ||
effectiveStyleMap.set(key, effectiveStyle); | ||
} | ||
return effectiveStyleMap.get(key); // The key is guaranteed to exist | ||
}; | ||
export default getEffectiveStyle; | ||
//# sourceMappingURL=getEffectiveStyle.js.map |
@@ -1,13 +0,11 @@ | ||
const getStylesString = styles => { | ||
if (Array.isArray(styles)) { | ||
return flatten(styles).join(" "); | ||
} | ||
return styles; | ||
const MAX_DEPTH_INHERITED_CLASSES = 10; // TypeScript complains about Infinity and big numbers | ||
const getStylesString = (styles) => { | ||
if (Array.isArray(styles)) { | ||
return styles.filter(style => !!style).flat(MAX_DEPTH_INHERITED_CLASSES).map((style) => { | ||
return typeof style === "string" ? style : style.content; | ||
}).join(" "); | ||
} | ||
return typeof styles === "string" ? styles : styles.content; | ||
}; | ||
const flatten = arr => { | ||
return arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? flatten(val) : val), []); | ||
}; | ||
export default getStylesString; | ||
//# sourceMappingURL=getStylesString.js.map |
@@ -0,67 +1,80 @@ | ||
const warnings = new Set(); | ||
const getThemeMetadata = () => { | ||
// Check if the class was already applied, most commonly to the link/style tag with the CSS Variables | ||
let el = document.querySelector(".sapThemeMetaData-Base-baseLib"); | ||
if (el) { | ||
return getComputedStyle(el).backgroundImage; | ||
} | ||
el = document.createElement("span"); | ||
el.style.display = "none"; | ||
el.classList.add("sapThemeMetaData-Base-baseLib"); | ||
document.body.appendChild(el); | ||
const metadata = getComputedStyle(el).backgroundImage; | ||
document.body.removeChild(el); | ||
return metadata; | ||
// Check if the class was already applied, most commonly to the link/style tag with the CSS Variables | ||
let el = document.querySelector(".sapThemeMetaData-Base-baseLib") || document.querySelector(".sapThemeMetaData-UI5-sap-ui-core"); | ||
if (el) { | ||
return getComputedStyle(el).backgroundImage; | ||
} | ||
el = document.createElement("span"); | ||
el.style.display = "none"; | ||
// Try with sapThemeMetaData-Base-baseLib first | ||
el.classList.add("sapThemeMetaData-Base-baseLib"); | ||
document.body.appendChild(el); | ||
let metadata = getComputedStyle(el).backgroundImage; | ||
// Try with sapThemeMetaData-UI5-sap-ui-core only if the previous selector was not found | ||
if (metadata === "none") { | ||
el.classList.add("sapThemeMetaData-UI5-sap-ui-core"); | ||
metadata = getComputedStyle(el).backgroundImage; | ||
} | ||
document.body.removeChild(el); | ||
return metadata; | ||
}; | ||
const parseThemeMetadata = metadataString => { | ||
const params = /\(["']?data:text\/plain;utf-8,(.*?)['"]?\)$/i.exec(metadataString); | ||
if (params && params.length >= 2) { | ||
let paramsString = params[1]; | ||
paramsString = paramsString.replace(/\\"/g, `"`); | ||
if (paramsString.charAt(0) !== "{" && paramsString.charAt(paramsString.length - 1) !== "}") { | ||
try { | ||
paramsString = decodeURIComponent(paramsString); | ||
} catch (ex) { | ||
console.warn("Malformed theme metadata string, unable to decodeURIComponent"); // eslint-disable-line | ||
return; | ||
} | ||
} | ||
try { | ||
return JSON.parse(paramsString); | ||
} catch (ex) { | ||
console.warn("Malformed theme metadata string, unable to parse JSON"); // eslint-disable-line | ||
} | ||
} | ||
const parseThemeMetadata = (metadataString) => { | ||
const params = /\(["']?data:text\/plain;utf-8,(.*?)['"]?\)$/i.exec(metadataString); | ||
if (params && params.length >= 2) { | ||
let paramsString = params[1]; | ||
paramsString = paramsString.replace(/\\"/g, `"`); | ||
if (paramsString.charAt(0) !== "{" && paramsString.charAt(paramsString.length - 1) !== "}") { | ||
try { | ||
paramsString = decodeURIComponent(paramsString); | ||
} | ||
catch (ex) { | ||
if (!warnings.has("decode")) { | ||
console.warn("Malformed theme metadata string, unable to decodeURIComponent"); // eslint-disable-line | ||
warnings.add("decode"); | ||
} | ||
return; | ||
} | ||
} | ||
try { | ||
return JSON.parse(paramsString); | ||
} | ||
catch (ex) { | ||
if (!warnings.has("parse")) { | ||
console.warn("Malformed theme metadata string, unable to parse JSON"); // eslint-disable-line | ||
warnings.add("parse"); | ||
} | ||
} | ||
} | ||
}; | ||
const processThemeMetadata = metadata => { | ||
let themeName; | ||
let baseThemeName; | ||
try { | ||
themeName = metadata.Path.match(/\.([^.]+)\.css_variables$/)[1]; | ||
baseThemeName = metadata.Extends[0]; | ||
} catch (ex) { | ||
console.warn("Malformed theme metadata Object", metadata); // eslint-disable-line | ||
return; | ||
} | ||
return { | ||
themeName, | ||
baseThemeName, | ||
}; | ||
const processThemeMetadata = (metadata) => { | ||
let themeName; | ||
let baseThemeName; | ||
try { | ||
themeName = metadata.Path.match(/\.([^.]+)\.css_variables$/)[1]; | ||
baseThemeName = metadata.Extends[0]; | ||
} | ||
catch (ex) { | ||
if (!warnings.has("object")) { | ||
console.warn("Malformed theme metadata Object", metadata); // eslint-disable-line | ||
warnings.add("object"); | ||
} | ||
return; | ||
} | ||
return { | ||
themeName, | ||
baseThemeName, | ||
}; | ||
}; | ||
const getThemeDesignerTheme = () => { | ||
const metadataString = getThemeMetadata(); | ||
if (!metadataString || metadataString === "none") { | ||
return; | ||
} | ||
const metadata = parseThemeMetadata(metadataString); | ||
return processThemeMetadata(metadata); | ||
const metadataString = getThemeMetadata(); | ||
if (!metadataString || metadataString === "none") { | ||
return; | ||
} | ||
const metadata = parseThemeMetadata(metadataString); | ||
if (metadata) { | ||
return processThemeMetadata(metadata); | ||
} | ||
}; | ||
export default getThemeDesignerTheme; | ||
//# sourceMappingURL=getThemeDesignerTheme.js.map |
import isPlainObject from './isPlainObject.js'; | ||
var oToken = Object.create(null); | ||
var fnMerge = function () { | ||
var fnMerge = function (arg1, arg2, arg3, arg4) { | ||
var src, copyIsArray, copy, name, options, clone, target = arguments[2] || {}, i = 3, length = arguments.length, deep = arguments[0] || false, skipToken = arguments[1] ? undefined : oToken; | ||
@@ -20,7 +20,9 @@ if (typeof target !== 'object' && typeof target !== 'function') { | ||
clone = src && Array.isArray(src) ? src : []; | ||
} else { | ||
} | ||
else { | ||
clone = src && isPlainObject(src) ? src : {}; | ||
} | ||
target[name] = fnMerge(deep, arguments[1], clone, copy); | ||
} else if (copy !== skipToken) { | ||
} | ||
else if (copy !== skipToken) { | ||
target[name] = copy; | ||
@@ -33,2 +35,3 @@ } | ||
}; | ||
export default fnMerge; | ||
export default fnMerge; | ||
//# sourceMappingURL=_merge.js.map |
@@ -7,13 +7,14 @@ var class2type = {}; | ||
var fnIsPlainObject = function (obj) { | ||
var proto, Ctor; | ||
if (!obj || toString.call(obj) !== "[object Object]") { | ||
return false; | ||
} | ||
proto = Object.getPrototypeOf(obj); | ||
if (!proto) { | ||
return true; | ||
} | ||
Ctor = hasOwn.call(proto, "constructor") && proto.constructor; | ||
return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString; | ||
var proto, Ctor; | ||
if (!obj || toString.call(obj) !== "[object Object]") { | ||
return false; | ||
} | ||
proto = Object.getPrototypeOf(obj); | ||
if (!proto) { | ||
return true; | ||
} | ||
Ctor = hasOwn.call(proto, "constructor") && proto.constructor; | ||
return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString; | ||
}; | ||
export default fnIsPlainObject; | ||
//# sourceMappingURL=isPlainObject.js.map |
import _merge from './_merge.js'; | ||
var fnMerge = function () { | ||
var args = [ | ||
true, | ||
false | ||
]; | ||
args.push.apply(args, arguments); | ||
return _merge.apply(null, args); | ||
const fnMerge = function (arg1, arg2) { | ||
return _merge(true, false, ...arguments); | ||
}; | ||
export default fnMerge; | ||
export default fnMerge; | ||
//# sourceMappingURL=merge.js.map |
@@ -1,7 +0,26 @@ | ||
const AnimationMode = { | ||
Full: "full", | ||
Basic: "basic", | ||
Minimal: "minimal", | ||
None: "none", | ||
}; | ||
/** | ||
* Different types of AnimationMode. | ||
* | ||
* @public | ||
*/ | ||
var AnimationMode; | ||
(function (AnimationMode) { | ||
/** | ||
* @public | ||
*/ | ||
AnimationMode["Full"] = "full"; | ||
/** | ||
* @public | ||
*/ | ||
AnimationMode["Basic"] = "basic"; | ||
/** | ||
* @public | ||
*/ | ||
AnimationMode["Minimal"] = "minimal"; | ||
/** | ||
* @public | ||
*/ | ||
AnimationMode["None"] = "none"; | ||
})(AnimationMode || (AnimationMode = {})); | ||
export default AnimationMode; | ||
//# sourceMappingURL=AnimationMode.js.map |
@@ -1,22 +0,30 @@ | ||
import DataType from "./DataType.js"; | ||
/** | ||
* Different calendar types. | ||
* | ||
* @public | ||
*/ | ||
const CalendarTypes = { | ||
Gregorian: "Gregorian", | ||
Islamic: "Islamic", | ||
Japanese: "Japanese", | ||
Buddhist: "Buddhist", | ||
Persian: "Persian", | ||
}; | ||
class CalendarType extends DataType { | ||
static isValid(value) { | ||
return !!CalendarTypes[value]; | ||
} | ||
} | ||
CalendarType.generataTypeAcessors(CalendarTypes); | ||
var CalendarType; | ||
(function (CalendarType) { | ||
/** | ||
* @public | ||
*/ | ||
CalendarType["Gregorian"] = "Gregorian"; | ||
/** | ||
* @public | ||
*/ | ||
CalendarType["Islamic"] = "Islamic"; | ||
/** | ||
* @public | ||
*/ | ||
CalendarType["Japanese"] = "Japanese"; | ||
/** | ||
* @public | ||
*/ | ||
CalendarType["Buddhist"] = "Buddhist"; | ||
/** | ||
* @public | ||
*/ | ||
CalendarType["Persian"] = "Persian"; | ||
})(CalendarType || (CalendarType = {})); | ||
export default CalendarType; | ||
//# sourceMappingURL=CalendarType.js.map |
import DataType from "./DataType.js"; | ||
/** | ||
* @class | ||
* CSSSize data type. | ||
* | ||
* @public | ||
*/ | ||
class CSSSize extends DataType { | ||
static isValid(value) { | ||
return /^(auto|inherit|[-+]?(0*|([0-9]+|[0-9]*\.[0-9]+)([rR][eE][mM]|[eE][mM]|[eE][xX]|[pP][xX]|[cC][mM]|[mM][mM]|[iI][nN]|[pP][tT]|[pP][cC]|%))|calc\(\s*(\(\s*)*[-+]?(([0-9]+|[0-9]*\.[0-9]+)([rR][eE][mM]|[eE][mM]|[eE][xX]|[pP][xX]|[cC][mM]|[mM][mM]|[iI][nN]|[pP][tT]|[pP][cC]|%)?)(\s*(\)\s*)*(\s[-+]\s|[*\/])\s*(\(\s*)*([-+]?(([0-9]+|[0-9]*\.[0-9]+)([rR][eE][mM]|[eE][mM]|[eE][xX]|[pP][xX]|[cC][mM]|[mM][mM]|[iI][nN]|[pP][tT]|[pP][cC]|%)?)))*\s*(\)\s*)*\))$/.test(value); // eslint-disable-line | ||
} | ||
static isValid(value) { | ||
return /^(auto|inherit|[-+]?(0*|([0-9]+|[0-9]*\.[0-9]+)([rR][eE][mM]|[eE][mM]|[eE][xX]|[pP][xX]|[cC][mM]|[mM][mM]|[iI][nN]|[pP][tT]|[pP][cC]|%))|calc\(\s*(\(\s*)*[-+]?(([0-9]+|[0-9]*\.[0-9]+)([rR][eE][mM]|[eE][mM]|[eE][xX]|[pP][xX]|[cC][mM]|[mM][mM]|[iI][nN]|[pP][tT]|[pP][cC]|%)?)(\s*(\)\s*)*(\s[-+]\s|[*\/])\s*(\(\s*)*([-+]?(([0-9]+|[0-9]*\.[0-9]+)([rR][eE][mM]|[eE][mM]|[eE][xX]|[pP][xX]|[cC][mM]|[mM][mM]|[iI][nN]|[pP][tT]|[pP][cC]|%)?)))*\s*(\)\s*)*\))$/.test(value); // eslint-disable-line | ||
} | ||
} | ||
export default CSSSize; | ||
//# sourceMappingURL=CSSSize.js.map |
/** | ||
* @class | ||
* Base class for all data types. | ||
* | ||
* @class | ||
* @constructor | ||
* @author SAP SE | ||
* @alias sap.ui.webcomponents.base.types.DataType | ||
* @public | ||
*/ | ||
class DataType { | ||
static isValid(value) { | ||
} | ||
static generataTypeAcessors(types) { | ||
Object.keys(types).forEach(type => { | ||
Object.defineProperty(this, type, { | ||
get() { | ||
return types[type]; | ||
}, | ||
}); | ||
}); | ||
} | ||
/** | ||
* Checks if the value is valid for its data type. | ||
* @public | ||
*/ | ||
// eslint-disable-next-line | ||
static isValid(value) { | ||
return false; | ||
} | ||
static attributeToProperty(attributeValue) { | ||
return attributeValue; | ||
} | ||
static propertyToAttribute(propertyValue) { | ||
return propertyValue === null ? null : String(propertyValue); | ||
} | ||
static valuesAreEqual(value1, value2) { | ||
return value1 === value2; | ||
} | ||
static generateTypeAccessors(types) { | ||
Object.keys(types).forEach(type => { | ||
Object.defineProperty(this, type, { | ||
get() { | ||
return types[type]; | ||
}, | ||
}); | ||
}); | ||
} | ||
static get isDataTypeClass() { | ||
return true; | ||
} | ||
} | ||
export default DataType; | ||
//# sourceMappingURL=DataType.js.map |
import DataType from "./DataType.js"; | ||
/** | ||
* @class | ||
* Float data type. | ||
* | ||
* @constructor | ||
* @public | ||
*/ | ||
class Float extends DataType { | ||
static isValid(value) { | ||
// Assuming that integers are floats as well! | ||
return Number(value) === value; | ||
} | ||
static isValid(value) { | ||
return Number(value) === value; | ||
} | ||
static attributeToProperty(attributeValue) { | ||
return parseFloat(attributeValue); | ||
} | ||
} | ||
export default Float; | ||
//# sourceMappingURL=Float.js.map |
import DataType from "./DataType.js"; | ||
/** | ||
* @class | ||
* Integer data type. | ||
* | ||
* @constructor | ||
* @public | ||
*/ | ||
class Integer extends DataType { | ||
static isValid(value) { | ||
return Number.isInteger(value); | ||
} | ||
static isValid(value) { | ||
return Number.isInteger(value); | ||
} | ||
static attributeToProperty(attributeValue) { | ||
return parseInt(attributeValue); | ||
} | ||
} | ||
export default Integer; | ||
//# sourceMappingURL=Integer.js.map |
/** | ||
* @private | ||
* Different behavior for ItemNavigation. | ||
* | ||
* @public | ||
*/ | ||
const ItemNavigationBehavior = { | ||
/** | ||
* Static behavior: when border of the items is reached, you can't go out of the cage. | ||
*/ | ||
Static: "Static", | ||
/** | ||
* Cycling behavior: when border of the items is reached, you can cycle through the items. | ||
*/ | ||
Cyclic: "Cyclic", | ||
/** | ||
* Paging behavior: when border of the items is reached, tou can go up/down based on the rowsize(e.g. DayPicker) | ||
*/ | ||
Paging: "Paging", | ||
}; | ||
var ItemNavigationBehavior; | ||
(function (ItemNavigationBehavior) { | ||
/** | ||
* Static behavior: navigations stops at the first or last item. | ||
* @public | ||
*/ | ||
ItemNavigationBehavior["Static"] = "Static"; | ||
/** | ||
* Cycling behavior: navigating past the last item continues with the first and vice versa. | ||
* @public | ||
*/ | ||
ItemNavigationBehavior["Cyclic"] = "Cyclic"; | ||
})(ItemNavigationBehavior || (ItemNavigationBehavior = {})); | ||
export default ItemNavigationBehavior; | ||
//# sourceMappingURL=ItemNavigationBehavior.js.map |
@@ -1,7 +0,26 @@ | ||
const NavigationMode = { | ||
Auto: "Auto", | ||
Vertical: "Vertical", | ||
Horizontal: "Horizontal", | ||
Paging: "Paging", | ||
}; | ||
/** | ||
* Different navigation modes for ItemNavigation. | ||
* | ||
* @public | ||
*/ | ||
var NavigationMode; | ||
(function (NavigationMode) { | ||
/** | ||
* @public | ||
*/ | ||
NavigationMode["Auto"] = "Auto"; | ||
/** | ||
* @public | ||
*/ | ||
NavigationMode["Vertical"] = "Vertical"; | ||
/** | ||
* @public | ||
*/ | ||
NavigationMode["Horizontal"] = "Horizontal"; | ||
/** | ||
* @public | ||
*/ | ||
NavigationMode["Paging"] = "Paging"; | ||
})(NavigationMode || (NavigationMode = {})); | ||
export default NavigationMode; | ||
//# sourceMappingURL=NavigationMode.js.map |
@@ -1,22 +0,30 @@ | ||
import DataType from "./DataType.js"; | ||
/** | ||
* Different states. | ||
* Different types of ValueStates. | ||
* | ||
* @public | ||
*/ | ||
const ValueStates = { | ||
None: "None", | ||
Success: "Success", | ||
Warning: "Warning", | ||
Error: "Error", | ||
Information: "Information", | ||
}; | ||
class ValueState extends DataType { | ||
static isValid(value) { | ||
return !!ValueStates[value]; | ||
} | ||
} | ||
ValueState.generataTypeAcessors(ValueStates); | ||
var ValueState; | ||
(function (ValueState) { | ||
/** | ||
* @public | ||
*/ | ||
ValueState["None"] = "None"; | ||
/** | ||
* @public | ||
*/ | ||
ValueState["Success"] = "Success"; | ||
/** | ||
* @public | ||
*/ | ||
ValueState["Warning"] = "Warning"; | ||
/** | ||
* @public | ||
*/ | ||
ValueState["Error"] = "Error"; | ||
/** | ||
* @public | ||
*/ | ||
ValueState["Information"] = "Information"; | ||
})(ValueState || (ValueState = {})); | ||
export default ValueState; | ||
//# sourceMappingURL=ValueState.js.map |
@@ -0,42 +1,63 @@ | ||
// eslint-disable-next-line import/no-extraneous-dependencies | ||
import "@ui5/webcomponents-base/dist/ssr-dom.js"; | ||
import merge from "./thirdparty/merge.js"; | ||
import boot from "./boot.js"; | ||
import { boot } from "./Boot.js"; | ||
import UI5ElementMetadata from "./UI5ElementMetadata.js"; | ||
import executeTemplate from "./renderer/executeTemplate.js"; | ||
import EventProvider from "./EventProvider.js"; | ||
import getSingletonElementInstance from "./util/getSingletonElementInstance.js"; | ||
import StaticAreaItem from "./StaticAreaItem.js"; | ||
import RenderScheduler from "./RenderScheduler.js"; | ||
import updateShadowRoot from "./updateShadowRoot.js"; | ||
import { shouldIgnoreCustomElement } from "./IgnoreCustomElements.js"; | ||
import { renderDeferred, renderImmediately, cancelRender } from "./Render.js"; | ||
import { registerTag, isTagRegistered, recordTagRegistrationFailure } from "./CustomElementsRegistry.js"; | ||
import DOMObserver from "./compatibility/DOMObserver.js"; | ||
import { observeDOMNode, unobserveDOMNode } from "./DOMObserver.js"; | ||
import { skipOriginalEvent } from "./config/NoConflict.js"; | ||
import { getRTL } from "./config/RTL.js"; | ||
import getConstructableStyle from "./theming/getConstructableStyle.js"; | ||
import createComponentStyleTag from "./theming/createComponentStyleTag.js"; | ||
import getEffectiveStyle from "./theming/getEffectiveStyle.js"; | ||
import Integer from "./types/Integer.js"; | ||
import Float from "./types/Float.js"; | ||
import getEffectiveDir from "./locale/getEffectiveDir.js"; | ||
import { kebabToCamelCase, camelToKebabCase } from "./util/StringHelper.js"; | ||
import isValidPropertyName from "./util/isValidPropertyName.js"; | ||
import isSlot from "./util/isSlot.js"; | ||
import { getSlotName, getSlottedNodesList } from "./util/SlotsHelper.js"; | ||
import arraysAreEqual from "./util/arraysAreEqual.js"; | ||
import { markAsRtlAware } from "./locale/RTLAwareRegistry.js"; | ||
const metadata = { | ||
events: { | ||
"_property-change": {}, | ||
}, | ||
}; | ||
import preloadLinks from "./theming/preloadLinks.js"; | ||
import executeTemplate from "./renderer/executeTemplate.js"; | ||
let autoId = 0; | ||
const elementTimeouts = new Map(); | ||
const uniqueDependenciesCache = new Map(); | ||
const GLOBAL_CONTENT_DENSITY_CSS_VAR = "--_ui5_content_density"; | ||
const GLOBAL_DIR_CSS_VAR = "--_ui5_dir"; | ||
/** | ||
* Triggers re-rendering of a UI5Element instance due to state change. | ||
* @param {ChangeInfo} changeInfo An object with information about the change that caused invalidation. | ||
* @private | ||
*/ | ||
function _invalidate(changeInfo) { | ||
// Invalidation should be suppressed: 1) before the component is rendered for the first time 2) and during the execution of onBeforeRendering | ||
// This is necessary not only as an optimization, but also to avoid infinite loops on invalidation between children and parents (when invalidateOnChildChange is used) | ||
if (this._suppressInvalidation) { | ||
return; | ||
} | ||
// Call the onInvalidation hook | ||
this.onInvalidation(changeInfo); | ||
this._changedState.push(changeInfo); | ||
renderDeferred(this); | ||
this._invalidationEventProvider.fireEvent("invalidate", { ...changeInfo, target: this }); | ||
} | ||
/** | ||
* looks up a property descsriptor including in the prototype chain | ||
* @param proto the starting prototype | ||
* @param name the property to look for | ||
* @returns the property descriptor if found directly or in the prototype chaing, undefined if not found | ||
*/ | ||
function getPropertyDescriptor(proto, name) { | ||
do { | ||
const descriptor = Object.getOwnPropertyDescriptor(proto, name); | ||
if (descriptor) { | ||
return descriptor; | ||
} | ||
// go up the prototype chain | ||
proto = Object.getPrototypeOf(proto); | ||
} while (proto && proto !== HTMLElement.prototype); | ||
} | ||
/** | ||
* @class | ||
* Base class for all UI5 Web Components | ||
* | ||
* @class | ||
* @constructor | ||
* @author SAP SE | ||
* @alias sap.ui.webcomponents.base.UI5Element | ||
* @extends HTMLElement | ||
@@ -46,1034 +67,954 @@ * @public | ||
class UI5Element extends HTMLElement { | ||
constructor() { | ||
super(); | ||
this._initializeState(); | ||
this._upgradeAllProperties(); | ||
this._initializeContainers(); | ||
this._upToDate = false; | ||
this._inDOM = false; | ||
this._fullyConnected = false; | ||
constructor() { | ||
super(); | ||
const ctor = this.constructor; | ||
this._changedState = []; // Filled on each invalidation, cleared on re-render (used for debugging) | ||
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 | ||
this._inDOM = false; // A flag telling whether the UI5Element is currently in the DOM tree of the document or not | ||
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) | ||
this._childChangeListeners = new Map(); // used to store lazy listeners per slot for the child change event of every child inside that slot | ||
this._slotChangeListeners = new Map(); // used to store lazy listeners per slot for the slotchange event of all slot children inside that slot | ||
this._invalidationEventProvider = new EventProvider(); // used by parent components for listening to changes to child components | ||
this._componentStateFinalizedEventProvider = new EventProvider(); // used by friend classes for synchronization | ||
let deferredResolve; | ||
this._domRefReadyPromise = new Promise(resolve => { | ||
deferredResolve = resolve; | ||
}); | ||
this._domRefReadyPromise._deferredResolve = deferredResolve; | ||
this._doNotSyncAttributes = new Set(); // attributes that are excluded from attributeChangedCallback synchronization | ||
this._state = { ...ctor.getMetadata().getInitialState() }; | ||
this._upgradeAllProperties(); | ||
if (ctor._needsShadowDOM()) { | ||
this.attachShadow({ mode: "open" }); | ||
} | ||
} | ||
/** | ||
* Returns a unique ID for this UI5 Element | ||
* | ||
* @deprecated - This property is not guaranteed in future releases | ||
* @protected | ||
*/ | ||
get _id() { | ||
if (!this.__id) { | ||
this.__id = `ui5wc_${++autoId}`; | ||
} | ||
return this.__id; | ||
} | ||
render() { | ||
const template = this.constructor.template; | ||
return executeTemplate(template, this); | ||
} | ||
renderStatic() { | ||
const template = this.constructor.staticAreaTemplate; | ||
return executeTemplate(template, this); | ||
} | ||
/** | ||
* Do not call this method from derivatives of UI5Element, use "onEnterDOM" only | ||
* @private | ||
*/ | ||
async connectedCallback() { | ||
const ctor = this.constructor; | ||
this.setAttribute(ctor.getMetadata().getPureTag(), ""); | ||
if (ctor.getMetadata().supportsF6FastNavigation()) { | ||
this.setAttribute("data-sap-ui-fastnavgroup", "true"); | ||
} | ||
const slotsAreManaged = ctor.getMetadata().slotsAreManaged(); | ||
this._inDOM = true; | ||
if (slotsAreManaged) { | ||
// always register the observer before yielding control to the main thread (await) | ||
this._startObservingDOMChildren(); | ||
await this._processChildren(); | ||
} | ||
if (!this._inDOM) { // Component removed from DOM while _processChildren was running | ||
return; | ||
} | ||
renderImmediately(this); | ||
this._domRefReadyPromise._deferredResolve(); | ||
this._fullyConnected = true; | ||
this.onEnterDOM(); | ||
} | ||
/** | ||
* Do not call this method from derivatives of UI5Element, use "onExitDOM" only | ||
* @private | ||
*/ | ||
disconnectedCallback() { | ||
const ctor = this.constructor; | ||
const slotsAreManaged = ctor.getMetadata().slotsAreManaged(); | ||
this._inDOM = false; | ||
if (slotsAreManaged) { | ||
this._stopObservingDOMChildren(); | ||
} | ||
if (this._fullyConnected) { | ||
this.onExitDOM(); | ||
this._fullyConnected = false; | ||
} | ||
if (this.staticAreaItem && this.staticAreaItem.parentElement) { | ||
this.staticAreaItem.parentElement.removeChild(this.staticAreaItem); | ||
} | ||
cancelRender(this); | ||
} | ||
/** | ||
* Called every time before the component renders. | ||
* @public | ||
*/ | ||
onBeforeRendering() { } | ||
/** | ||
* Called every time after the component renders. | ||
* @public | ||
*/ | ||
onAfterRendering() { } | ||
/** | ||
* Called on connectedCallback - added to the DOM. | ||
* @public | ||
*/ | ||
onEnterDOM() { } | ||
/** | ||
* Called on disconnectedCallback - removed from the DOM. | ||
* @public | ||
*/ | ||
onExitDOM() { } | ||
/** | ||
* @private | ||
*/ | ||
_startObservingDOMChildren() { | ||
const ctor = this.constructor; | ||
const metadata = ctor.getMetadata(); | ||
const shouldObserveChildren = metadata.hasSlots(); | ||
if (!shouldObserveChildren) { | ||
return; | ||
} | ||
const canSlotText = metadata.canSlotText(); | ||
const hasClonedSlot = Object.keys(metadata.getSlots()).some(slotName => metadata.getSlots()[slotName].cloned); | ||
const mutationObserverOptions = { | ||
childList: true, | ||
subtree: canSlotText || hasClonedSlot, | ||
characterData: canSlotText, | ||
}; | ||
observeDOMNode(this, this._processChildren.bind(this), mutationObserverOptions); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_stopObservingDOMChildren() { | ||
unobserveDOMNode(this); | ||
} | ||
/** | ||
* Note: this method is also manually called by "compatibility/patchNodeValue.js" | ||
* @private | ||
*/ | ||
async _processChildren() { | ||
const hasSlots = this.constructor.getMetadata().hasSlots(); | ||
if (hasSlots) { | ||
await this._updateSlots(); | ||
} | ||
} | ||
/** | ||
* @private | ||
*/ | ||
async _updateSlots() { | ||
const ctor = this.constructor; | ||
const slotsMap = ctor.getMetadata().getSlots(); | ||
const canSlotText = ctor.getMetadata().canSlotText(); | ||
const domChildren = Array.from(canSlotText ? this.childNodes : this.children); | ||
const slotsCachedContentMap = new Map(); // Store here the content of each slot before the mutation occurred | ||
const propertyNameToSlotMap = new Map(); // Used for reverse lookup to determine to which slot the property name corresponds | ||
// Init the _state object based on the supported slots and store the previous values | ||
for (const [slotName, slotData] of Object.entries(slotsMap)) { // eslint-disable-line | ||
const propertyName = slotData.propertyName || slotName; | ||
propertyNameToSlotMap.set(propertyName, slotName); | ||
slotsCachedContentMap.set(propertyName, [...this._state[propertyName]]); | ||
this._clearSlot(slotName, slotData); | ||
} | ||
const autoIncrementMap = new Map(); | ||
const slottedChildrenMap = new Map(); | ||
const allChildrenUpgraded = domChildren.map(async (child, idx) => { | ||
// Determine the type of the child (mainly by the slot attribute) | ||
const slotName = getSlotName(child); | ||
const slotData = slotsMap[slotName]; | ||
// Check if the slotName is supported | ||
if (slotData === undefined) { | ||
if (slotName !== "default") { | ||
const validValues = Object.keys(slotsMap).join(", "); | ||
console.warn(`Unknown slotName: ${slotName}, ignoring`, child, `Valid values are: ${validValues}`); // eslint-disable-line | ||
} | ||
return; | ||
} | ||
// For children that need individual slots, calculate them | ||
if (slotData.individualSlots) { | ||
const nextIndex = (autoIncrementMap.get(slotName) || 0) + 1; | ||
autoIncrementMap.set(slotName, nextIndex); | ||
child._individualSlot = `${slotName}-${nextIndex}`; | ||
} | ||
// Await for not-yet-defined custom elements | ||
if (child instanceof HTMLElement) { | ||
const localName = child.localName; | ||
const shouldWaitForCustomElement = localName.includes("-") && !shouldIgnoreCustomElement(localName); | ||
if (shouldWaitForCustomElement) { | ||
const isDefined = customElements.get(localName); | ||
if (!isDefined) { | ||
const whenDefinedPromise = customElements.whenDefined(localName); // Class registered, but instances not upgraded yet | ||
let timeoutPromise = elementTimeouts.get(localName); | ||
if (!timeoutPromise) { | ||
timeoutPromise = new Promise(resolve => setTimeout(resolve, 1000)); | ||
elementTimeouts.set(localName, timeoutPromise); | ||
} | ||
await Promise.race([whenDefinedPromise, timeoutPromise]); | ||
} | ||
customElements.upgrade(child); | ||
} | ||
} | ||
child = ctor.getMetadata().constructor.validateSlotValue(child, slotData); | ||
// Listen for any invalidation on the child if invalidateOnChildChange is true or an object (ignore when false or not set) | ||
if (instanceOfUI5Element(child) && slotData.invalidateOnChildChange) { | ||
const childChangeListener = this._getChildChangeListener(slotName); | ||
if (childChangeListener) { | ||
child.attachInvalidate.call(child, childChangeListener); | ||
} | ||
} | ||
// Listen for the slotchange event if the child is a slot itself | ||
if (child instanceof HTMLSlotElement) { | ||
this._attachSlotChange(child, slotName); | ||
} | ||
const propertyName = slotData.propertyName || slotName; | ||
if (slottedChildrenMap.has(propertyName)) { | ||
slottedChildrenMap.get(propertyName).push({ child, idx }); | ||
} | ||
else { | ||
slottedChildrenMap.set(propertyName, [{ child, idx }]); | ||
} | ||
}); | ||
await Promise.all(allChildrenUpgraded); | ||
// Distribute the child in the _state object, keeping the Light DOM order, | ||
// not the order elements are defined. | ||
slottedChildrenMap.forEach((children, propertyName) => { | ||
this._state[propertyName] = children.sort((a, b) => a.idx - b.idx).map(_ => _.child); | ||
}); | ||
// Compare the content of each slot with the cached values and invalidate for the ones that changed | ||
let invalidated = false; | ||
for (const [slotName, slotData] of Object.entries(slotsMap)) { // eslint-disable-line | ||
const propertyName = slotData.propertyName || slotName; | ||
if (!arraysAreEqual(slotsCachedContentMap.get(propertyName), this._state[propertyName])) { | ||
_invalidate.call(this, { | ||
type: "slot", | ||
name: propertyNameToSlotMap.get(propertyName), | ||
reason: "children", | ||
}); | ||
invalidated = true; | ||
} | ||
} | ||
// If none of the slots had an invalidation due to changes to immediate children, | ||
// the change is considered to be text content of the default slot | ||
if (!invalidated) { | ||
_invalidate.call(this, { | ||
type: "slot", | ||
name: "default", | ||
reason: "textcontent", | ||
}); | ||
} | ||
} | ||
/** | ||
* Removes all children from the slot and detaches listeners, if any | ||
* @private | ||
*/ | ||
_clearSlot(slotName, slotData) { | ||
const propertyName = slotData.propertyName || slotName; | ||
const children = this._state[propertyName]; | ||
children.forEach(child => { | ||
if (instanceOfUI5Element(child)) { | ||
const childChangeListener = this._getChildChangeListener(slotName); | ||
if (childChangeListener) { | ||
child.detachInvalidate.call(child, childChangeListener); | ||
} | ||
} | ||
if (child instanceof HTMLSlotElement) { | ||
this._detachSlotChange(child, slotName); | ||
} | ||
}); | ||
this._state[propertyName] = []; | ||
} | ||
/** | ||
* Attach a callback that will be executed whenever the component is invalidated | ||
* | ||
* @param callback | ||
* @public | ||
*/ | ||
attachInvalidate(callback) { | ||
this._invalidationEventProvider.attachEvent("invalidate", callback); | ||
} | ||
/** | ||
* Detach the callback that is executed whenever the component is invalidated | ||
* | ||
* @param callback | ||
* @public | ||
*/ | ||
detachInvalidate(callback) { | ||
this._invalidationEventProvider.detachEvent("invalidate", callback); | ||
} | ||
/** | ||
* Callback that is executed whenever a monitored child changes its state | ||
* | ||
* @param slotName the slot in which a child was invalidated | ||
* @param childChangeInfo the changeInfo object for the child in the given slot | ||
* @private | ||
*/ | ||
_onChildChange(slotName, childChangeInfo) { | ||
if (!this.constructor.getMetadata().shouldInvalidateOnChildChange(slotName, childChangeInfo.type, childChangeInfo.name)) { | ||
return; | ||
} | ||
// The component should be invalidated as this type of change on the child is listened for | ||
// However, no matter what changed on the child (property/slot), the invalidation is registered as "type=slot" for the component itself | ||
_invalidate.call(this, { | ||
type: "slot", | ||
name: slotName, | ||
reason: "childchange", | ||
child: childChangeInfo.target, | ||
}); | ||
} | ||
/** | ||
* Do not override this method in derivatives of UI5Element | ||
* @private | ||
*/ | ||
attributeChangedCallback(name, oldValue, newValue) { | ||
let newPropertyValue; | ||
if (this._doNotSyncAttributes.has(name)) { // This attribute is mutated internally, not by the user | ||
return; | ||
} | ||
const properties = this.constructor.getMetadata().getProperties(); | ||
const realName = name.replace(/^ui5-/, ""); | ||
const nameInCamelCase = kebabToCamelCase(realName); | ||
if (properties.hasOwnProperty(nameInCamelCase)) { // eslint-disable-line | ||
const propData = properties[nameInCamelCase]; | ||
const propertyType = propData.type; | ||
let propertyValidator = propData.validator; | ||
if (propertyType && propertyType.isDataTypeClass) { | ||
propertyValidator = propertyType; | ||
} | ||
if (propertyValidator) { | ||
newPropertyValue = propertyValidator.attributeToProperty(newValue); | ||
} | ||
else if (propertyType === Boolean) { | ||
newPropertyValue = newValue !== null; | ||
} | ||
else { | ||
newPropertyValue = newValue; | ||
} | ||
this[nameInCamelCase] = newPropertyValue; | ||
} | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_updateAttribute(name, newValue) { | ||
const ctor = this.constructor; | ||
if (!ctor.getMetadata().hasAttribute(name)) { | ||
return; | ||
} | ||
const properties = ctor.getMetadata().getProperties(); | ||
const propData = properties[name]; | ||
const propertyType = propData.type; | ||
let propertyValidator = propData.validator; | ||
const attrName = camelToKebabCase(name); | ||
const attrValue = this.getAttribute(attrName); | ||
if (propertyType && propertyType.isDataTypeClass) { | ||
propertyValidator = propertyType; | ||
} | ||
if (propertyValidator) { | ||
const newAttrValue = propertyValidator.propertyToAttribute(newValue); | ||
if (newAttrValue === null) { // null means there must be no attribute for the current value of the property | ||
this._doNotSyncAttributes.add(attrName); // skip the attributeChangedCallback call for this attribute | ||
this.removeAttribute(attrName); // remove the attribute safely (will not trigger synchronization to the property value due to the above line) | ||
this._doNotSyncAttributes.delete(attrName); // enable synchronization again for this attribute | ||
} | ||
else { | ||
this.setAttribute(attrName, newAttrValue); | ||
} | ||
} | ||
else if (propertyType === Boolean) { | ||
if (newValue === true && attrValue === null) { | ||
this.setAttribute(attrName, ""); | ||
} | ||
else if (newValue === false && attrValue !== null) { | ||
this.removeAttribute(attrName); | ||
} | ||
} | ||
else if (typeof newValue !== "object") { | ||
if (attrValue !== newValue) { | ||
this.setAttribute(attrName, newValue); | ||
} | ||
} // else { return; } // old object handling | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_upgradeProperty(propertyName) { | ||
if (this.hasOwnProperty(propertyName)) { // eslint-disable-line | ||
const value = this[propertyName]; | ||
delete this[propertyName]; | ||
this[propertyName] = value; | ||
} | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_upgradeAllProperties() { | ||
const allProps = this.constructor.getMetadata().getPropertiesList(); | ||
allProps.forEach(this._upgradeProperty.bind(this)); | ||
} | ||
/** | ||
* Returns a singleton event listener for the "change" event of a child in a given slot | ||
* | ||
* @param slotName the name of the slot, where the child is | ||
* @private | ||
*/ | ||
_getChildChangeListener(slotName) { | ||
if (!this._childChangeListeners.has(slotName)) { | ||
this._childChangeListeners.set(slotName, this._onChildChange.bind(this, slotName)); | ||
} | ||
return this._childChangeListeners.get(slotName); | ||
} | ||
/** | ||
* Returns a singleton slotchange event listener that invalidates the component due to changes in the given slot | ||
* | ||
* @param slotName the name of the slot, where the slot element (whose slotchange event we're listening to) is | ||
* @private | ||
*/ | ||
_getSlotChangeListener(slotName) { | ||
if (!this._slotChangeListeners.has(slotName)) { | ||
this._slotChangeListeners.set(slotName, this._onSlotChange.bind(this, slotName)); | ||
} | ||
return this._slotChangeListeners.get(slotName); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_attachSlotChange(child, slotName) { | ||
const slotChangeListener = this._getSlotChangeListener(slotName); | ||
if (slotChangeListener) { | ||
child.addEventListener("slotchange", slotChangeListener); | ||
} | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_detachSlotChange(child, slotName) { | ||
child.removeEventListener("slotchange", this._getSlotChangeListener(slotName)); | ||
} | ||
/** | ||
* Whenever a slot element is slotted inside a UI5 Web Component, its slotchange event invalidates the component | ||
* | ||
* @param slotName the name of the slot, where the slot element (whose slotchange event we're listening to) is | ||
* @private | ||
*/ | ||
_onSlotChange(slotName) { | ||
_invalidate.call(this, { | ||
type: "slot", | ||
name: slotName, | ||
reason: "slotchange", | ||
}); | ||
} | ||
/** | ||
* A callback that is executed each time an already rendered component is invalidated (scheduled for re-rendering) | ||
* | ||
* @param changeInfo An object with information about the change that caused invalidation. | ||
* The object can have the following properties: | ||
* - type: (property|slot) tells what caused the invalidation | ||
* 1) property: a property value was changed either directly or as a result of changing the corresponding attribute | ||
* 2) slot: a slotted node(nodes) changed in one of several ways (see "reason") | ||
* | ||
* - name: the name of the property or slot that caused the invalidation | ||
* | ||
* - reason: (children|textcontent|childchange|slotchange) relevant only for type="slot" only and tells exactly what changed in the slot | ||
* 1) children: immediate children (HTML elements or text nodes) were added, removed or reordered in the slot | ||
* 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" | ||
* 3) slotchange: a slot element, slotted inside that slot had its "slotchange" event listener called. This practically means that transitively slotted children changed. | ||
* Can only trigger if the child of a slot is a slot element itself. | ||
* 4) childchange: indicates that a UI5Element child in that slot was invalidated and in turn invalidated the component. | ||
* Can only trigger for slots with "invalidateOnChildChange" metadata descriptor | ||
* | ||
* - newValue: the new value of the property (for type="property" only) | ||
* | ||
* - oldValue: the old value of the property (for type="property" only) | ||
* | ||
* - child the child that was changed (for type="slot" and reason="childchange" only) | ||
* | ||
* @public | ||
*/ | ||
onInvalidation(changeInfo) { } // eslint-disable-line | ||
/** | ||
* Do not call this method directly, only intended to be called by js | ||
* @protected | ||
*/ | ||
_render() { | ||
const ctor = this.constructor; | ||
const hasIndividualSlots = ctor.getMetadata().hasIndividualSlots(); | ||
// suppress invalidation to prevent state changes scheduling another rendering | ||
this._suppressInvalidation = true; | ||
this.onBeforeRendering(); | ||
// Intended for framework usage only. Currently ItemNavigation updates tab indexes after the component has updated its state but before the template is rendered | ||
this._componentStateFinalizedEventProvider.fireEvent("componentStateFinalized"); | ||
// resume normal invalidation handling | ||
this._suppressInvalidation = false; | ||
// Update the shadow root with the render result | ||
/* | ||
if (this._changedState.length) { | ||
let element = this.localName; | ||
if (this.id) { | ||
element = `${element}#${this.id}`; | ||
} | ||
console.log("Re-rendering:", element, this._changedState.map(x => { // eslint-disable-line | ||
let res = `${x.type}`; | ||
if (x.reason) { | ||
res = `${res}(${x.reason})`; | ||
} | ||
res = `${res}: ${x.name}`; | ||
if (x.type === "property") { | ||
res = `${res} ${JSON.stringify(x.oldValue)} => ${JSON.stringify(x.newValue)}`; | ||
} | ||
let deferredResolve; | ||
this._domRefReadyPromise = new Promise(resolve => { | ||
deferredResolve = resolve; | ||
}); | ||
this._domRefReadyPromise._deferredResolve = deferredResolve; | ||
this._monitoredChildProps = new Map(); | ||
this._firePropertyChange = false; | ||
this._shouldInvalidateParent = false; | ||
} | ||
/** | ||
* Returns a unique ID for this UI5 Element | ||
* | ||
* @deprecated - This property is not guaranteed in future releases | ||
* @protected | ||
*/ | ||
get _id() { | ||
if (!this.__id) { | ||
this.__id = `ui5wc_${++autoId}`; | ||
} | ||
return this.__id; | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_initializeContainers() { | ||
const needsShadowDOM = this.constructor._needsShadowDOM(); | ||
const needsStaticArea = this.constructor._needsStaticArea(); | ||
// Init Shadow Root | ||
if (needsShadowDOM) { | ||
this.attachShadow({ mode: "open" }); | ||
} | ||
// Init StaticAreaItem only if needed | ||
if (needsStaticArea) { | ||
this.staticAreaItem = new StaticAreaItem(this); | ||
} | ||
} | ||
/** | ||
* Do not call this method from derivatives of UI5Element, use "onEnterDOM" only | ||
* @private | ||
*/ | ||
async connectedCallback() { | ||
this.setAttribute(this.constructor.getMetadata().getPureTag(), ""); | ||
const needsShadowDOM = this.constructor._needsShadowDOM(); | ||
const slotsAreManaged = this.constructor.getMetadata().slotsAreManaged(); | ||
this._inDOM = true; | ||
if (slotsAreManaged) { | ||
// always register the observer before yielding control to the main thread (await) | ||
this._startObservingDOMChildren(); | ||
await this._processChildren(); | ||
} | ||
// Render the Shadow DOM | ||
if (needsShadowDOM) { | ||
if (!this.shadowRoot) { // Workaround for Firefox74 bug | ||
await Promise.resolve(); | ||
} | ||
if (!this._inDOM) { // Component removed from DOM while _processChildren was running | ||
return; | ||
} | ||
RenderScheduler.register(this); | ||
RenderScheduler.renderImmediately(this); | ||
this._domRefReadyPromise._deferredResolve(); | ||
this._fullyConnected = true; | ||
if (typeof this.onEnterDOM === "function") { | ||
this.onEnterDOM(); | ||
} | ||
} | ||
} | ||
/** | ||
* Do not call this method from derivatives of UI5Element, use "onExitDOM" only | ||
* @private | ||
*/ | ||
disconnectedCallback() { | ||
const needsShadowDOM = this.constructor._needsShadowDOM(); | ||
const needsStaticArea = this.constructor._needsStaticArea(); | ||
const slotsAreManaged = this.constructor.getMetadata().slotsAreManaged(); | ||
this._inDOM = false; | ||
if (slotsAreManaged) { | ||
this._stopObservingDOMChildren(); | ||
} | ||
if (needsShadowDOM) { | ||
RenderScheduler.deregister(this); | ||
if (this._fullyConnected) { | ||
if (typeof this.onExitDOM === "function") { | ||
this.onExitDOM(); | ||
} | ||
this._fullyConnected = false; | ||
} | ||
} | ||
if (needsStaticArea) { | ||
this.staticAreaItem._removeFragmentFromStaticArea(); | ||
} | ||
RenderScheduler.cancelRender(this); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_startObservingDOMChildren() { | ||
const shouldObserveChildren = this.constructor.getMetadata().hasSlots(); | ||
if (!shouldObserveChildren) { | ||
return; | ||
} | ||
const canSlotText = this.constructor.getMetadata().canSlotText(); | ||
const mutationObserverOptions = { | ||
childList: true, | ||
subtree: canSlotText, | ||
characterData: true, | ||
}; | ||
DOMObserver.observeDOMNode(this, this._processChildren.bind(this), mutationObserverOptions); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_stopObservingDOMChildren() { | ||
DOMObserver.unobserveDOMNode(this); | ||
} | ||
/** | ||
* Note: this method is also manually called by "compatibility/patchNodeValue.js" | ||
* @private | ||
*/ | ||
async _processChildren() { | ||
const hasSlots = this.constructor.getMetadata().hasSlots(); | ||
if (hasSlots) { | ||
await this._updateSlots(); | ||
} | ||
} | ||
/** | ||
* @private | ||
*/ | ||
async _updateSlots() { | ||
const slotsMap = this.constructor.getMetadata().getSlots(); | ||
const canSlotText = this.constructor.getMetadata().canSlotText(); | ||
const domChildren = Array.from(canSlotText ? this.childNodes : this.children); | ||
// Init the _state object based on the supported slots | ||
for (const [slotName, slotData] of Object.entries(slotsMap)) { // eslint-disable-line | ||
this._clearSlot(slotName, slotData); | ||
} | ||
const autoIncrementMap = new Map(); | ||
const slottedChildrenMap = new Map(); | ||
const allChildrenUpgraded = domChildren.map(async (child, idx) => { | ||
// Determine the type of the child (mainly by the slot attribute) | ||
const slotName = this.constructor._getSlotName(child); | ||
const slotData = slotsMap[slotName]; | ||
// Check if the slotName is supported | ||
if (slotData === undefined) { | ||
const validValues = Object.keys(slotsMap).join(", "); | ||
console.warn(`Unknown slotName: ${slotName}, ignoring`, child, `Valid values are: ${validValues}`); // eslint-disable-line | ||
return; | ||
} | ||
// For children that need individual slots, calculate them | ||
if (slotData.individualSlots) { | ||
const nextIndex = (autoIncrementMap.get(slotName) || 0) + 1; | ||
autoIncrementMap.set(slotName, nextIndex); | ||
child._individualSlot = `${slotName}-${nextIndex}`; | ||
} | ||
// Await for not-yet-defined custom elements | ||
if (child instanceof HTMLElement) { | ||
const localName = child.localName; | ||
const isCustomElement = localName.includes("-"); | ||
if (isCustomElement) { | ||
const isDefined = window.customElements.get(localName); | ||
if (!isDefined) { | ||
const whenDefinedPromise = window.customElements.whenDefined(localName); // Class registered, but instances not upgraded yet | ||
let timeoutPromise = elementTimeouts.get(localName); | ||
if (!timeoutPromise) { | ||
timeoutPromise = new Promise(resolve => setTimeout(resolve, 1000)); | ||
elementTimeouts.set(localName, timeoutPromise); | ||
} | ||
await Promise.race([whenDefinedPromise, timeoutPromise]); | ||
} | ||
window.customElements.upgrade(child); | ||
} | ||
} | ||
child = this.constructor.getMetadata().constructor.validateSlotValue(child, slotData); | ||
if (child.isUI5Element && slotData.listenFor) { | ||
this._attachChildPropertyUpdated(child, slotData.listenFor); | ||
} | ||
if (child.isUI5Element && slotData.invalidateParent) { | ||
child._shouldInvalidateParent = true; | ||
} | ||
if (isSlot(child)) { | ||
this._attachSlotChange(child); | ||
} | ||
const propertyName = slotData.propertyName || slotName; | ||
if (slottedChildrenMap.has(propertyName)) { | ||
slottedChildrenMap.get(propertyName).push({ child, idx }); | ||
} else { | ||
slottedChildrenMap.set(propertyName, [{ child, idx }]); | ||
} | ||
}); | ||
await Promise.all(allChildrenUpgraded); | ||
// Distribute the child in the _state object, keeping the Light DOM order, | ||
// not the order elements are defined. | ||
slottedChildrenMap.forEach((children, slot) => { | ||
this._state[slot] = children.sort((a, b) => a.idx - b.idx).map(_ => _.child); | ||
}); | ||
this._invalidate("slots"); | ||
} | ||
/** | ||
* Removes all children from the slot and detaches listeners, if any | ||
* @private | ||
*/ | ||
_clearSlot(slotName, slotData) { | ||
const propertyName = slotData.propertyName || slotName; | ||
let children = this._state[propertyName]; | ||
if (!Array.isArray(children)) { | ||
children = [children]; | ||
} | ||
children.forEach(child => { | ||
if (child && child.isUI5Element) { | ||
this._detachChildPropertyUpdated(child); | ||
child._shouldInvalidateParent = false; | ||
} | ||
if (isSlot(child)) { | ||
this._detachSlotChange(child); | ||
} | ||
}); | ||
this._state[propertyName] = []; | ||
this._invalidate(propertyName, []); | ||
} | ||
/** | ||
* Do not override this method in derivatives of UI5Element | ||
* @private | ||
*/ | ||
attributeChangedCallback(name, oldValue, newValue) { | ||
const properties = this.constructor.getMetadata().getProperties(); | ||
const realName = name.replace(/^ui5-/, ""); | ||
const nameInCamelCase = kebabToCamelCase(realName); | ||
if (properties.hasOwnProperty(nameInCamelCase)) { // eslint-disable-line | ||
const propertyTypeClass = properties[nameInCamelCase].type; | ||
if (propertyTypeClass === Boolean) { | ||
newValue = newValue !== null; | ||
} | ||
if (propertyTypeClass === Integer) { | ||
newValue = parseInt(newValue); | ||
} | ||
if (propertyTypeClass === Float) { | ||
newValue = parseFloat(newValue); | ||
} | ||
this[nameInCamelCase] = newValue; | ||
} | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_updateAttribute(name, newValue) { | ||
if (!this.constructor.getMetadata().hasAttribute(name)) { | ||
return; | ||
} | ||
if (typeof newValue === "object") { | ||
return; | ||
} | ||
const attrName = camelToKebabCase(name); | ||
const attrValue = this.getAttribute(attrName); | ||
if (typeof newValue === "boolean") { | ||
if (newValue === true && attrValue === null) { | ||
this.setAttribute(attrName, ""); | ||
} else if (newValue === false && attrValue !== null) { | ||
this.removeAttribute(attrName); | ||
} | ||
} else if (attrValue !== newValue) { | ||
this.setAttribute(attrName, newValue); | ||
} | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_upgradeProperty(prop) { | ||
if (this.hasOwnProperty(prop)) { // eslint-disable-line | ||
const value = this[prop]; | ||
delete this[prop]; | ||
this[prop] = value; | ||
} | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_upgradeAllProperties() { | ||
const allProps = this.constructor.getMetadata().getPropertiesList(); | ||
allProps.forEach(this._upgradeProperty, this); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_initializeState() { | ||
const defaultState = this.constructor._getDefaultState(); | ||
this._state = Object.assign({}, defaultState); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_attachChildPropertyUpdated(child, listenFor) { | ||
const childMetadata = child.constructor.getMetadata(), | ||
slotName = this.constructor._getSlotName(child), // all slotted children have the same configuration | ||
childProperties = childMetadata.getProperties(); | ||
let observedProps = [], | ||
notObservedProps = []; | ||
if (Array.isArray(listenFor)) { | ||
observedProps = listenFor; | ||
} else { | ||
observedProps = Array.isArray(listenFor.props) ? listenFor.props : Object.keys(childProperties); | ||
notObservedProps = Array.isArray(listenFor.exclude) ? listenFor.exclude : []; | ||
} | ||
if (!this._monitoredChildProps.has(slotName)) { | ||
this._monitoredChildProps.set(slotName, { observedProps, notObservedProps }); | ||
} | ||
child.addEventListener("_property-change", this._invalidateParentOnPropertyUpdate); | ||
child._firePropertyChange = true; | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_detachChildPropertyUpdated(child) { | ||
child.removeEventListener("_property-change", this._invalidateParentOnPropertyUpdate); | ||
child._firePropertyChange = false; | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_propertyChange(name, value) { | ||
this._updateAttribute(name, value); | ||
if (this._firePropertyChange) { | ||
this.dispatchEvent(new CustomEvent("_property-change", { | ||
detail: { name, newValue: value }, | ||
composed: false, | ||
bubbles: true, | ||
})); | ||
} | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_invalidateParentOnPropertyUpdate(prop) { | ||
// The web component to be invalidated | ||
const parentNode = this.parentNode; | ||
if (!parentNode) { | ||
return; | ||
} | ||
const slotName = parentNode.constructor._getSlotName(this); | ||
const propsMetadata = parentNode._monitoredChildProps.get(slotName); | ||
if (!propsMetadata) { | ||
return; | ||
} | ||
const { observedProps, notObservedProps } = propsMetadata; | ||
if (observedProps.includes(prop.detail.name) && !notObservedProps.includes(prop.detail.name)) { | ||
parentNode._invalidate("_parent_", this); | ||
} | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_attachSlotChange(child) { | ||
if (!this._invalidateOnSlotChange) { | ||
this._invalidateOnSlotChange = () => { | ||
this._invalidate("slotchange"); | ||
}; | ||
} | ||
child.addEventListener("slotchange", this._invalidateOnSlotChange); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_detachSlotChange(child) { | ||
child.removeEventListener("slotchange", this._invalidateOnSlotChange); | ||
} | ||
/** | ||
* Asynchronously re-renders an already rendered web component | ||
* @private | ||
*/ | ||
_invalidate() { | ||
if (this._shouldInvalidateParent) { | ||
this.parentNode._invalidate(); | ||
} | ||
if (!this._upToDate) { | ||
// console.log("already invalidated", this, ...arguments); | ||
return; | ||
} | ||
if (this.getDomRef() && !this._suppressInvalidation) { | ||
this._upToDate = false; | ||
// console.log("INVAL", this, ...arguments); | ||
RenderScheduler.renderDeferred(this); | ||
} | ||
} | ||
/** | ||
* Do not call this method directly, only intended to be called by RenderScheduler.js | ||
* @protected | ||
*/ | ||
_render() { | ||
const hasIndividualSlots = this.constructor.getMetadata().hasIndividualSlots(); | ||
// suppress invalidation to prevent state changes scheduling another rendering | ||
this._suppressInvalidation = true; | ||
if (typeof this.onBeforeRendering === "function") { | ||
this.onBeforeRendering(); | ||
} | ||
// Intended for framework usage only. Currently ItemNavigation updates tab indexes after the component has updated its state but before the template is rendered | ||
if (this._onComponentStateFinalized) { | ||
this._onComponentStateFinalized(); | ||
} | ||
// resume normal invalidation handling | ||
delete this._suppressInvalidation; | ||
// Update the shadow root with the render result | ||
// console.log(this.getDomRef() ? "RE-RENDER" : "FIRST RENDER", this); | ||
this._upToDate = true; | ||
this._updateShadowRoot(); | ||
if (this._shouldUpdateFragment()) { | ||
this.staticAreaItem._updateFragment(this); | ||
} | ||
// Safari requires that children get the slot attribute only after the slot tags have been rendered in the shadow DOM | ||
if (hasIndividualSlots) { | ||
this._assignIndividualSlotsToChildren(); | ||
} | ||
// Call the onAfterRendering hook | ||
if (typeof this.onAfterRendering === "function") { | ||
this.onAfterRendering(); | ||
} | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_updateShadowRoot() { | ||
if (!this.constructor._needsShadowDOM()) { | ||
return; | ||
} | ||
let styleToPrepend; | ||
const renderResult = executeTemplate(this.constructor.template, this); | ||
// IE11, Edge | ||
if (window.ShadyDOM) { | ||
createComponentStyleTag(this.constructor); | ||
} | ||
// Chrome | ||
if (document.adoptedStyleSheets) { | ||
this.shadowRoot.adoptedStyleSheets = getConstructableStyle(this.constructor); | ||
} | ||
// FF, Safari | ||
if (!document.adoptedStyleSheets && !window.ShadyDOM) { | ||
styleToPrepend = getEffectiveStyle(this.constructor); | ||
} | ||
this.constructor.render(renderResult, this.shadowRoot, styleToPrepend, { eventContext: this }); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_assignIndividualSlotsToChildren() { | ||
const domChildren = Array.from(this.children); | ||
domChildren.forEach(child => { | ||
if (child._individualSlot) { | ||
child.setAttribute("slot", child._individualSlot); | ||
} | ||
}); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_waitForDomRef() { | ||
return this._domRefReadyPromise; | ||
} | ||
/** | ||
* Returns the DOM Element inside the Shadow Root that corresponds to the opening tag in the UI5 Web Component's template | ||
* Use this method instead of "this.shadowRoot" to read the Shadow DOM, if ever necessary | ||
* @public | ||
*/ | ||
getDomRef() { | ||
if (!this.shadowRoot || this.shadowRoot.children.length === 0) { | ||
return; | ||
} | ||
return this.shadowRoot.children.length === 1 | ||
? this.shadowRoot.children[0] : this.shadowRoot.children[1]; | ||
} | ||
/** | ||
* Returns the DOM Element marked with "data-sap-focus-ref" inside the template. | ||
* This is the element that will receive the focus by default. | ||
* @public | ||
*/ | ||
getFocusDomRef() { | ||
const domRef = this.getDomRef(); | ||
if (domRef) { | ||
const focusRef = domRef.querySelector("[data-sap-focus-ref]"); | ||
return focusRef || domRef; | ||
} | ||
} | ||
/** | ||
* Use this method in order to get a reference to element in the shadow root of a web component | ||
* @public | ||
* @param {String} refName Defines the name of the stable DOM ref | ||
*/ | ||
getStableDomRef(refName) { | ||
return this.getDomRef().querySelector(`[data-ui5-stable=${refName}]`); | ||
} | ||
/** | ||
* Set the focus to the element, returned by "getFocusDomRef()" (marked by "data-sap-focus-ref") | ||
* @public | ||
*/ | ||
async focus() { | ||
await this._waitForDomRef(); | ||
const focusDomRef = this.getFocusDomRef(); | ||
if (focusDomRef && typeof focusDomRef.focus === "function") { | ||
focusDomRef.focus(); | ||
} | ||
} | ||
/** | ||
* | ||
* @public | ||
* @param name - name of the event | ||
* @param data - additional data for the event | ||
* @param cancelable - true, if the user can call preventDefault on the event object | ||
* @param bubbles - true, if the event bubbles | ||
* @returns {boolean} false, if the event was cancelled (preventDefault called), true otherwise | ||
*/ | ||
fireEvent(name, data, cancelable = false, bubbles = true) { | ||
const eventResult = this._fireEvent(name, data, cancelable, bubbles); | ||
const camelCaseEventName = kebabToCamelCase(name); | ||
if (camelCaseEventName !== name) { | ||
return eventResult && this._fireEvent(camelCaseEventName, data, cancelable); | ||
} | ||
return eventResult; | ||
} | ||
_fireEvent(name, data, cancelable = false, bubbles = true) { | ||
let compatEventResult = true; // Initialized to true, because if the event is not fired at all, it should be considered "not-prevented" | ||
const noConflictEvent = new CustomEvent(`ui5-${name}`, { | ||
detail: data, | ||
composed: false, | ||
bubbles, | ||
cancelable, | ||
}); | ||
// This will be false if the compat event is prevented | ||
compatEventResult = this.dispatchEvent(noConflictEvent); | ||
if (skipOriginalEvent(name)) { | ||
return compatEventResult; | ||
} | ||
const customEvent = new CustomEvent(name, { | ||
detail: data, | ||
composed: false, | ||
bubbles, | ||
cancelable, | ||
}); | ||
// This will be false if the normal event is prevented | ||
const normalEventResult = this.dispatchEvent(customEvent); | ||
// Return false if any of the two events was prevented (its result was false). | ||
return normalEventResult && compatEventResult; | ||
} | ||
/** | ||
* Returns the actual children, associated with a slot. | ||
* Useful when there are transitive slots in nested component scenarios and you don't want to get a list of the slots, but rather of their content. | ||
* @public | ||
*/ | ||
getSlottedNodes(slotName) { | ||
const reducer = (acc, curr) => { | ||
if (!isSlot(curr)) { | ||
return acc.concat([curr]); | ||
} | ||
return acc.concat(curr.assignedNodes({ flatten: true }).filter(item => item instanceof HTMLElement)); | ||
}; | ||
return this[slotName].reduce(reducer, []); | ||
} | ||
get isCompact() { | ||
return getComputedStyle(this).getPropertyValue(GLOBAL_CONTENT_DENSITY_CSS_VAR) === "compact"; | ||
} | ||
/** | ||
* Determines whether the component should be rendered in RTL mode or not. | ||
* Returns: "rtl", "ltr" or undefined | ||
* | ||
* @public | ||
* @returns {String|undefined} | ||
*/ | ||
get effectiveDir() { | ||
markAsRtlAware(this.constructor); // if a UI5 Element calls this method, it's considered to be rtl-aware | ||
const doc = window.document; | ||
const dirValues = ["ltr", "rtl"]; // exclude "auto" and "" from all calculations | ||
const locallyAppliedDir = getComputedStyle(this).getPropertyValue(GLOBAL_DIR_CSS_VAR); | ||
// In that order, inspect the CSS Var (for modern browsers), the element itself, html and body (for IE fallback) | ||
if (dirValues.includes(locallyAppliedDir)) { | ||
return locallyAppliedDir; | ||
} | ||
if (dirValues.includes(this.dir)) { | ||
return this.dir; | ||
} | ||
if (dirValues.includes(doc.documentElement.dir)) { | ||
return doc.documentElement.dir; | ||
} | ||
if (dirValues.includes(doc.body.dir)) { | ||
return doc.body.dir; | ||
} | ||
// Finally, check the configuration for explicitly set RTL or language-implied RTL | ||
return getRTL() ? "rtl" : undefined; | ||
} | ||
updateStaticAreaItemContentDensity() { | ||
if (this.staticAreaItem) { | ||
this.staticAreaItem._updateContentDensity(this.isCompact); | ||
} | ||
} | ||
/** | ||
* Used to duck-type UI5 elements without using instanceof | ||
* @returns {boolean} | ||
* @public | ||
*/ | ||
get isUI5Element() { | ||
return true; | ||
} | ||
/** | ||
* Do not override this method in derivatives of UI5Element, use metadata properties instead | ||
* @private | ||
*/ | ||
static get observedAttributes() { | ||
return this.getMetadata().getAttributesList(); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
static _getSlotName(child) { | ||
// Text nodes can only go to the default slot | ||
if (!(child instanceof HTMLElement)) { | ||
return "default"; | ||
} | ||
// Discover the slot based on the real slot name (f.e. footer => footer, or content-32 => content) | ||
const slot = child.getAttribute("slot"); | ||
if (slot) { | ||
const match = slot.match(/^(.+?)-\d+$/); | ||
return match ? match[1] : slot; | ||
} | ||
// Use default slot as a fallback | ||
return "default"; | ||
} | ||
/** | ||
* @private | ||
*/ | ||
static _needsShadowDOM() { | ||
return !!this.template; | ||
} | ||
_shouldUpdateFragment() { | ||
return this.constructor._needsStaticArea() && this.staticAreaItem.isRendered(); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
static _needsStaticArea() { | ||
return typeof this.staticAreaTemplate === "function"; | ||
} | ||
/** | ||
* @public | ||
*/ | ||
getStaticAreaItemDomRef() { | ||
return this.staticAreaItem.getDomRef(); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
static _getDefaultState() { | ||
if (this._defaultState) { | ||
return this._defaultState; | ||
} | ||
const MetadataClass = this.getMetadata(); | ||
const defaultState = {}; | ||
const slotsAreManaged = MetadataClass.slotsAreManaged(); | ||
// 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] = "defaultValue" in props[propName] ? props[propName].defaultValue : ""; | ||
} else { | ||
defaultState[propName] = propDefaultValue; | ||
} | ||
} | ||
// Initialize slots | ||
if (slotsAreManaged) { | ||
const slots = MetadataClass.getSlots(); | ||
for (const [slotName, slotData] of Object.entries(slots)) { // eslint-disable-line | ||
const propertyName = slotData.propertyName || slotName; | ||
defaultState[propertyName] = []; | ||
} | ||
} | ||
this._defaultState = defaultState; | ||
return defaultState; | ||
} | ||
/** | ||
* @private | ||
*/ | ||
static _generateAccessors() { | ||
const proto = this.prototype; | ||
const slotsAreManaged = this.getMetadata().slotsAreManaged(); | ||
// Properties | ||
const properties = this.getMetadata().getProperties(); | ||
for (const [prop, propData] of Object.entries(properties)) { // eslint-disable-line | ||
if (!isValidPropertyName(prop)) { | ||
throw new Error(`"${prop}" is not a valid property name. Use a name that does not collide with DOM APIs`); | ||
} | ||
if (propData.type === Boolean && propData.defaultValue) { | ||
throw new Error(`Cannot set a default value for property "${prop}". All booleans are false by default.`); | ||
} | ||
if (propData.type === Array) { | ||
throw new Error(`Wrong type for property "${prop}". Properties cannot be of type Array - use "multiple: true" and set "type" to the single value type, such as "String", "Object", etc...`); | ||
} | ||
if (propData.type === Object && propData.defaultValue) { | ||
throw new Error(`Cannot set a default value for property "${prop}". All properties of type "Object" are empty objects by default.`); | ||
} | ||
if (propData.multiple && propData.defaultValue) { | ||
throw new Error(`Cannot set a default value for property "${prop}". All multiple properties are empty arrays by default.`); | ||
} | ||
Object.defineProperty(proto, prop, { | ||
get() { | ||
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) { | ||
value = this.constructor.getMetadata().constructor.validatePropertyValue(value, propData); | ||
const oldState = this._state[prop]; | ||
if (oldState !== value) { | ||
this._state[prop] = value; | ||
this._invalidate(prop, value); | ||
this._propertyChange(prop, value); | ||
} | ||
}, | ||
}); | ||
} | ||
// Slots | ||
if (slotsAreManaged) { | ||
const slots = this.getMetadata().getSlots(); | ||
for (const [slotName, slotData] of Object.entries(slots)) { // eslint-disable-line | ||
if (!isValidPropertyName(slotName)) { | ||
throw new Error(`"${slotName}" is not a valid property name. Use a name that does not collide with DOM APIs`); | ||
} | ||
const propertyName = slotData.propertyName || slotName; | ||
Object.defineProperty(proto, propertyName, { | ||
get() { | ||
if (this._state[propertyName] !== undefined) { | ||
return this._state[propertyName]; | ||
} | ||
return []; | ||
}, | ||
set() { | ||
throw new Error("Cannot set slots directly, use the DOM APIs"); | ||
}, | ||
}); | ||
} | ||
} | ||
} | ||
/** | ||
* Returns the metadata object for this UI5 Web Component Class | ||
* @protected | ||
*/ | ||
static get metadata() { | ||
return metadata; | ||
} | ||
/** | ||
* Returns the CSS for this UI5 Web Component Class | ||
* @protected | ||
*/ | ||
static get styles() { | ||
return ""; | ||
} | ||
/** | ||
* Returns the Static Area CSS for this UI5 Web Component Class | ||
* @protected | ||
*/ | ||
static get staticAreaStyles() { | ||
return ""; | ||
} | ||
/** | ||
* Returns an array with the dependencies for this UI5 Web Component, which could be: | ||
* - composed components (used in its shadow root or static area item) | ||
* - slotted components that the component may need to communicate with | ||
* | ||
* @protected | ||
*/ | ||
static get dependencies() { | ||
return []; | ||
} | ||
/** | ||
* Returns a list of the unique dependencies for this UI5 Web Component | ||
* | ||
* @public | ||
*/ | ||
static getUniqueDependencies() { | ||
if (!uniqueDependenciesCache.has(this)) { | ||
const filtered = this.dependencies.filter((dep, index, deps) => deps.indexOf(dep) === index); | ||
uniqueDependenciesCache.set(this, filtered); | ||
} | ||
return uniqueDependenciesCache.get(this); | ||
} | ||
/** | ||
* Returns a promise that resolves whenever all dependencies for this UI5 Web Component have resolved | ||
* | ||
* @returns {Promise<any[]>} | ||
*/ | ||
static whenDependenciesDefined() { | ||
return Promise.all(this.getUniqueDependencies().map(dep => dep.define())); | ||
} | ||
/** | ||
* Hook that will be called upon custom element definition | ||
* | ||
* @protected | ||
* @returns {Promise<void>} | ||
*/ | ||
static async onDefine() { | ||
return Promise.resolve(); | ||
} | ||
/** | ||
* Registers a UI5 Web Component in the browser window object | ||
* @public | ||
* @returns {Promise<UI5Element>} | ||
*/ | ||
static async define() { | ||
await boot(); | ||
await Promise.all([ | ||
this.whenDependenciesDefined(), | ||
this.onDefine(), | ||
]); | ||
const tag = this.getMetadata().getTag(); | ||
const altTag = this.getMetadata().getAltTag(); | ||
const definedLocally = isTagRegistered(tag); | ||
const definedGlobally = customElements.get(tag); | ||
if (definedGlobally && !definedLocally) { | ||
recordTagRegistrationFailure(tag); | ||
} else if (!definedGlobally) { | ||
this._generateAccessors(); | ||
registerTag(tag); | ||
window.customElements.define(tag, this); | ||
if (altTag && !customElements.get(altTag)) { | ||
class oldClassName extends this {} | ||
registerTag(altTag); | ||
window.customElements.define(altTag, oldClassName); | ||
} | ||
} | ||
return this; | ||
} | ||
/** | ||
* Returns an instance of UI5ElementMetadata.js representing this UI5 Web Component's full metadata (its and its parents') | ||
* Note: not to be confused with the "get metadata()" method, which returns an object for this class's metadata only | ||
* @public | ||
* @returns {UI5ElementMetadata} | ||
*/ | ||
static getMetadata() { | ||
if (this.hasOwnProperty("_metadata")) { // eslint-disable-line | ||
return this._metadata; | ||
} | ||
const metadataObjects = [this.metadata]; | ||
let klass = this; // eslint-disable-line | ||
while (klass !== UI5Element) { | ||
klass = Object.getPrototypeOf(klass); | ||
metadataObjects.unshift(klass.metadata); | ||
} | ||
const mergedMetadata = merge({}, ...metadataObjects); | ||
this._metadata = new UI5ElementMetadata(mergedMetadata); | ||
return this._metadata; | ||
} | ||
return res; | ||
})); | ||
} | ||
*/ | ||
this._changedState = []; | ||
// Update shadow root and static area item | ||
if (ctor._needsShadowDOM()) { | ||
updateShadowRoot(this); | ||
} | ||
if (this.staticAreaItem) { | ||
this.staticAreaItem.update(); | ||
} | ||
// Safari requires that children get the slot attribute only after the slot tags have been rendered in the shadow DOM | ||
if (hasIndividualSlots) { | ||
this._assignIndividualSlotsToChildren(); | ||
} | ||
// Call the onAfterRendering hook | ||
this.onAfterRendering(); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_assignIndividualSlotsToChildren() { | ||
const domChildren = Array.from(this.children); | ||
domChildren.forEach((child) => { | ||
if (child._individualSlot) { | ||
child.setAttribute("slot", child._individualSlot); | ||
} | ||
}); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_waitForDomRef() { | ||
return this._domRefReadyPromise; | ||
} | ||
/** | ||
* Returns the DOM Element inside the Shadow Root that corresponds to the opening tag in the UI5 Web Component's template | ||
* *Note:* For logical (abstract) elements (items, options, etc...), returns the part of the parent's DOM that represents this option | ||
* Use this method instead of "this.shadowRoot" to read the Shadow DOM, if ever necessary | ||
* | ||
* @public | ||
*/ | ||
getDomRef() { | ||
// If a component set _getRealDomRef to its children, use the return value of this function | ||
if (typeof this._getRealDomRef === "function") { | ||
return this._getRealDomRef(); | ||
} | ||
if (!this.shadowRoot || this.shadowRoot.children.length === 0) { | ||
return; | ||
} | ||
const children = [...this.shadowRoot.children].filter(child => !["link", "style"].includes(child.localName)); | ||
if (children.length !== 1) { | ||
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 | ||
} | ||
return children[0]; | ||
} | ||
/** | ||
* Returns the DOM Element marked with "data-sap-focus-ref" inside the template. | ||
* This is the element that will receive the focus by default. | ||
* @public | ||
*/ | ||
getFocusDomRef() { | ||
const domRef = this.getDomRef(); | ||
if (domRef) { | ||
const focusRef = domRef.querySelector("[data-sap-focus-ref]"); | ||
return focusRef || domRef; | ||
} | ||
} | ||
/** | ||
* Waits for dom ref and then returns the DOM Element marked with "data-sap-focus-ref" inside the template. | ||
* This is the element that will receive the focus by default. | ||
* @public | ||
*/ | ||
async getFocusDomRefAsync() { | ||
await this._waitForDomRef(); | ||
return this.getFocusDomRef(); | ||
} | ||
/** | ||
* Set the focus to the element, returned by "getFocusDomRef()" (marked by "data-sap-focus-ref") | ||
* @param focusOptions additional options for the focus | ||
* @public | ||
*/ | ||
async focus(focusOptions) { | ||
await this._waitForDomRef(); | ||
const focusDomRef = this.getFocusDomRef(); | ||
if (focusDomRef && typeof focusDomRef.focus === "function") { | ||
focusDomRef.focus(focusOptions); | ||
} | ||
} | ||
/** | ||
* | ||
* @public | ||
* @param name - name of the event | ||
* @param data - additional data for the event | ||
* @param cancelable - true, if the user can call preventDefault on the event object | ||
* @param bubbles - true, if the event bubbles | ||
* @returns false, if the event was cancelled (preventDefault called), true otherwise | ||
*/ | ||
fireEvent(name, data, cancelable = false, bubbles = true) { | ||
const eventResult = this._fireEvent(name, data, cancelable, bubbles); | ||
const camelCaseEventName = kebabToCamelCase(name); | ||
if (camelCaseEventName !== name) { | ||
return eventResult && this._fireEvent(camelCaseEventName, data, cancelable, bubbles); | ||
} | ||
return eventResult; | ||
} | ||
_fireEvent(name, data, cancelable = false, bubbles = true) { | ||
const noConflictEvent = new CustomEvent(`ui5-${name}`, { | ||
detail: data, | ||
composed: false, | ||
bubbles, | ||
cancelable, | ||
}); | ||
// This will be false if the no-conflict event is prevented | ||
const noConflictEventResult = this.dispatchEvent(noConflictEvent); | ||
if (skipOriginalEvent(name)) { | ||
return noConflictEventResult; | ||
} | ||
const normalEvent = new CustomEvent(name, { | ||
detail: data, | ||
composed: false, | ||
bubbles, | ||
cancelable, | ||
}); | ||
// This will be false if the normal event is prevented | ||
const normalEventResult = this.dispatchEvent(normalEvent); | ||
// Return false if any of the two events was prevented (its result was false). | ||
return normalEventResult && noConflictEventResult; | ||
} | ||
/** | ||
* Returns the actual children, associated with a slot. | ||
* Useful when there are transitive slots in nested component scenarios and you don't want to get a list of the slots, but rather of their content. | ||
* @public | ||
*/ | ||
getSlottedNodes(slotName) { | ||
return getSlottedNodesList(this[slotName]); | ||
} | ||
/** | ||
* Attach a callback that will be executed whenever the component's state is finalized | ||
* | ||
* @param callback | ||
* @public | ||
*/ | ||
attachComponentStateFinalized(callback) { | ||
this._componentStateFinalizedEventProvider.attachEvent("componentStateFinalized", callback); | ||
} | ||
/** | ||
* Detach the callback that is executed whenever the component's state is finalized | ||
* | ||
* @param callback | ||
* @public | ||
*/ | ||
detachComponentStateFinalized(callback) { | ||
this._componentStateFinalizedEventProvider.detachEvent("componentStateFinalized", callback); | ||
} | ||
/** | ||
* Determines whether the component should be rendered in RTL mode or not. | ||
* Returns: "rtl", "ltr" or undefined | ||
* | ||
* @public | ||
* @default undefined | ||
*/ | ||
get effectiveDir() { | ||
markAsRtlAware(this.constructor); // if a UI5 Element calls this method, it's considered to be rtl-aware | ||
return getEffectiveDir(this); | ||
} | ||
/** | ||
* Used to duck-type UI5 elements without using instanceof | ||
* @public | ||
* @default true | ||
*/ | ||
get isUI5Element() { | ||
return true; | ||
} | ||
get classes() { | ||
return {}; | ||
} | ||
/** | ||
* Returns the component accessibility info. | ||
* @private | ||
*/ | ||
get accessibilityInfo() { | ||
return {}; | ||
} | ||
/** | ||
* Do not override this method in derivatives of UI5Element, use metadata properties instead | ||
* @private | ||
*/ | ||
static get observedAttributes() { | ||
return this.getMetadata().getAttributesList(); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
static _needsShadowDOM() { | ||
return !!this.template || Object.prototype.hasOwnProperty.call(this.prototype, "render"); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
static _needsStaticArea() { | ||
return !!this.staticAreaTemplate || Object.prototype.hasOwnProperty.call(this.prototype, "renderStatic"); | ||
} | ||
/** | ||
* @public | ||
*/ | ||
getStaticAreaItemDomRef() { | ||
if (!this.constructor._needsStaticArea()) { | ||
throw new Error("This component does not use the static area"); | ||
} | ||
if (!this.staticAreaItem) { | ||
this.staticAreaItem = StaticAreaItem.createInstance(); | ||
this.staticAreaItem.setOwnerElement(this); | ||
} | ||
if (!this.staticAreaItem.parentElement) { | ||
getSingletonElementInstance("ui5-static-area").appendChild(this.staticAreaItem); | ||
} | ||
return this.staticAreaItem.getDomRef(); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
static _generateAccessors() { | ||
const proto = this.prototype; | ||
const slotsAreManaged = this.getMetadata().slotsAreManaged(); | ||
// Properties | ||
const properties = this.getMetadata().getProperties(); | ||
for (const [prop, propData] of Object.entries(properties)) { // eslint-disable-line | ||
if (!isValidPropertyName(prop)) { | ||
console.warn(`"${prop}" is not a valid property name. Use a name that does not collide with DOM APIs`); /* eslint-disable-line */ | ||
} | ||
if (propData.type === Boolean && propData.defaultValue) { | ||
throw new Error(`Cannot set a default value for property "${prop}". All booleans are false by default.`); | ||
} | ||
if (propData.type === Array) { | ||
throw new Error(`Wrong type for property "${prop}". Properties cannot be of type Array - use "multiple: true" and set "type" to the single value type, such as "String", "Object", etc...`); | ||
} | ||
if (propData.type === Object && propData.defaultValue) { | ||
throw new Error(`Cannot set a default value for property "${prop}". All properties of type "Object" are empty objects by default.`); | ||
} | ||
if (propData.multiple && propData.defaultValue) { | ||
throw new Error(`Cannot set a default value for property "${prop}". All multiple properties are empty arrays by default.`); | ||
} | ||
const descriptor = getPropertyDescriptor(proto, prop); | ||
// if the decorator is on a setter, proxy the new setter to it | ||
let origSet; | ||
if (descriptor?.set) { | ||
// eslint-disable-next-line @typescript-eslint/unbound-method | ||
origSet = descriptor.set; | ||
} | ||
// if the decorator is on a setter, there will be a corresponding getter - proxy the new getter to it | ||
let origGet; | ||
if (descriptor?.get) { | ||
// eslint-disable-next-line @typescript-eslint/unbound-method | ||
origGet = descriptor.get; | ||
} | ||
Object.defineProperty(proto, prop, { | ||
get() { | ||
// proxy the getter to the original accessor if there was one | ||
if (origGet) { | ||
return origGet.call(this); | ||
} | ||
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) { | ||
let isDifferent; | ||
const ctor = this.constructor; | ||
const metadataCtor = ctor.getMetadata().constructor; | ||
value = metadataCtor.validatePropertyValue(value, propData); | ||
const propertyType = propData.type; | ||
let propertyValidator = propData.validator; | ||
const oldState = origGet ? origGet.call(this) : this._state[prop]; | ||
if (propertyType && propertyType.isDataTypeClass) { | ||
propertyValidator = propertyType; | ||
} | ||
if (propertyValidator) { | ||
isDifferent = !propertyValidator.valuesAreEqual(oldState, value); | ||
} | ||
else if (Array.isArray(oldState) && Array.isArray(value) && propData.multiple && propData.compareValues) { // compareValues is added for IE, test if needed now | ||
isDifferent = !arraysAreEqual(oldState, value); | ||
} | ||
else { | ||
isDifferent = oldState !== value; | ||
} | ||
if (isDifferent) { | ||
// if the decorator is on a setter, use it for storage | ||
if (origSet) { | ||
origSet.call(this, value); | ||
} | ||
else { | ||
this._state[prop] = value; | ||
} | ||
_invalidate.call(this, { | ||
type: "property", | ||
name: prop, | ||
newValue: value, | ||
oldValue: oldState, | ||
}); | ||
this._updateAttribute(prop, value); | ||
} | ||
}, | ||
}); | ||
} | ||
// Slots | ||
if (slotsAreManaged) { | ||
const slots = this.getMetadata().getSlots(); | ||
for (const [slotName, slotData] of Object.entries(slots)) { // eslint-disable-line | ||
if (!isValidPropertyName(slotName)) { | ||
console.warn(`"${slotName}" is not a valid property name. Use a name that does not collide with DOM APIs`); /* eslint-disable-line */ | ||
} | ||
const propertyName = slotData.propertyName || slotName; | ||
Object.defineProperty(proto, propertyName, { | ||
get() { | ||
if (this._state[propertyName] !== undefined) { | ||
return this._state[propertyName]; | ||
} | ||
return []; | ||
}, | ||
set() { | ||
throw new Error("Cannot set slot content directly, use the DOM APIs (appendChild, removeChild, etc...)"); | ||
}, | ||
}); | ||
} | ||
} | ||
} | ||
/** | ||
* Returns the Static Area CSS for this UI5 Web Component Class | ||
* @protected | ||
*/ | ||
static get staticAreaStyles() { | ||
return ""; | ||
} | ||
/** | ||
* Returns an array with the dependencies for this UI5 Web Component, which could be: | ||
* - composed components (used in its shadow root or static area item) | ||
* - slotted components that the component may need to communicate with | ||
* | ||
* @protected | ||
*/ | ||
static get dependencies() { | ||
return []; | ||
} | ||
/** | ||
* Returns a list of the unique dependencies for this UI5 Web Component | ||
* | ||
* @public | ||
*/ | ||
static getUniqueDependencies() { | ||
if (!uniqueDependenciesCache.has(this)) { | ||
const filtered = this.dependencies.filter((dep, index, deps) => deps.indexOf(dep) === index); | ||
uniqueDependenciesCache.set(this, filtered); | ||
} | ||
return uniqueDependenciesCache.get(this) || []; | ||
} | ||
/** | ||
* Returns a promise that resolves whenever all dependencies for this UI5 Web Component have resolved | ||
*/ | ||
static whenDependenciesDefined() { | ||
return Promise.all(this.getUniqueDependencies().map(dep => dep.define())); | ||
} | ||
/** | ||
* Hook that will be called upon custom element definition | ||
* | ||
* @protected | ||
*/ | ||
static async onDefine() { | ||
return Promise.resolve(); | ||
} | ||
/** | ||
* Registers a UI5 Web Component in the browser window object | ||
* @public | ||
*/ | ||
static async define() { | ||
await boot(); | ||
await Promise.all([ | ||
this.whenDependenciesDefined(), | ||
this.onDefine(), | ||
]); | ||
const tag = this.getMetadata().getTag(); | ||
const definedLocally = isTagRegistered(tag); | ||
const definedGlobally = customElements.get(tag); | ||
if (definedGlobally && !definedLocally) { | ||
recordTagRegistrationFailure(tag); | ||
} | ||
else if (!definedGlobally) { | ||
this._generateAccessors(); | ||
registerTag(tag); | ||
customElements.define(tag, this); | ||
preloadLinks(this); | ||
} | ||
return this; | ||
} | ||
/** | ||
* Returns an instance of UI5ElementMetadata.js representing this UI5 Web Component's full metadata (its and its parents') | ||
* Note: not to be confused with the "get metadata()" method, which returns an object for this class's metadata only | ||
* @public | ||
*/ | ||
static getMetadata() { | ||
if (this.hasOwnProperty("_metadata")) { // eslint-disable-line | ||
return this._metadata; | ||
} | ||
const metadataObjects = [this.metadata]; | ||
let klass = this; // eslint-disable-line | ||
while (klass !== UI5Element) { | ||
klass = Object.getPrototypeOf(klass); | ||
metadataObjects.unshift(klass.metadata); | ||
} | ||
const mergedMetadata = merge({}, ...metadataObjects); | ||
this._metadata = new UI5ElementMetadata(mergedMetadata); | ||
return this._metadata; | ||
} | ||
} | ||
/** | ||
* Returns the metadata object for this UI5 Web Component Class | ||
* @protected | ||
*/ | ||
UI5Element.metadata = {}; | ||
/** | ||
* Returns the CSS for this UI5 Web Component Class | ||
* @protected | ||
*/ | ||
UI5Element.styles = ""; | ||
/** | ||
* Always use duck-typing to cover all runtimes on the page. | ||
*/ | ||
const instanceOfUI5Element = (object) => { | ||
return "isUI5Element" in object; | ||
}; | ||
export default UI5Element; | ||
export { instanceOfUI5Element }; | ||
//# sourceMappingURL=UI5Element.js.map |
@@ -1,9 +0,5 @@ | ||
import DataType from "./types/DataType.js"; | ||
import isDescendantOf from "./util/isDescendantOf.js"; | ||
import { camelToKebabCase } from "./util/StringHelper.js"; | ||
import isSlot from "./util/isSlot.js"; | ||
import { getEffectiveScopingSuffixForTag } from "./CustomElementsScope.js"; | ||
import { getSlottedNodes } from "./util/SlotsHelper.js"; | ||
import { getEffectiveScopingSuffixForTag } from "./CustomElementsScopeUtils.js"; | ||
/** | ||
* | ||
* @class | ||
@@ -13,201 +9,286 @@ * @public | ||
class UI5ElementMetadata { | ||
constructor(metadata) { | ||
this.metadata = metadata; | ||
} | ||
/** | ||
* Only intended for use by UI5Element.js | ||
* @protected | ||
*/ | ||
static validatePropertyValue(value, propData) { | ||
const isMultiple = propData.multiple; | ||
if (isMultiple) { | ||
return value.map(propValue => validateSingleProperty(propValue, propData)); | ||
} | ||
return validateSingleProperty(value, propData); | ||
} | ||
/** | ||
* Only intended for use by UI5Element.js | ||
* @protected | ||
*/ | ||
static validateSlotValue(value, slotData) { | ||
return validateSingleSlot(value, slotData); | ||
} | ||
/** | ||
* Returns the tag of the UI5 Element without the scope | ||
* @public | ||
*/ | ||
getPureTag() { | ||
return this.metadata.tag; | ||
} | ||
/** | ||
* Returns the tag of the UI5 Element | ||
* @public | ||
*/ | ||
getTag() { | ||
const pureTag = this.metadata.tag; | ||
const suffix = getEffectiveScopingSuffixForTag(pureTag); | ||
if (!suffix) { | ||
return pureTag; | ||
} | ||
return `${pureTag}-${suffix}`; | ||
} | ||
/** | ||
* Used to get the tag we need to register for backwards compatibility | ||
* @public | ||
*/ | ||
getAltTag() { | ||
const pureAltTag = this.metadata.altTag; | ||
if (!pureAltTag) { | ||
return; | ||
} | ||
const suffix = getEffectiveScopingSuffixForTag(pureAltTag); | ||
if (!suffix) { | ||
return pureAltTag; | ||
} | ||
return `${pureAltTag}-${suffix}`; | ||
} | ||
/** | ||
* Determines whether a property should have an attribute counterpart | ||
* @public | ||
* @param propName | ||
* @returns {boolean} | ||
*/ | ||
hasAttribute(propName) { | ||
const propData = this.getProperties()[propName]; | ||
return propData.type !== Object && !propData.noAttribute; | ||
} | ||
/** | ||
* Returns an array with the properties of the UI5 Element (in camelCase) | ||
* @public | ||
* @returns {string[]} | ||
*/ | ||
getPropertiesList() { | ||
return Object.keys(this.getProperties()); | ||
} | ||
/** | ||
* Returns an array with the attributes of the UI5 Element (in kebab-case) | ||
* @public | ||
* @returns {string[]} | ||
*/ | ||
getAttributesList() { | ||
return this.getPropertiesList().filter(this.hasAttribute, this).map(camelToKebabCase); | ||
} | ||
/** | ||
* Returns an object with key-value pairs of slots and their metadata definitions | ||
* @public | ||
*/ | ||
getSlots() { | ||
return this.metadata.slots || {}; | ||
} | ||
/** | ||
* Determines whether this UI5 Element has a default slot of type Node, therefore can slot text | ||
* @returns {boolean} | ||
*/ | ||
canSlotText() { | ||
const defaultSlot = this.getSlots().default; | ||
return defaultSlot && defaultSlot.type === Node; | ||
} | ||
/** | ||
* Determines whether this UI5 Element supports any slots | ||
* @public | ||
*/ | ||
hasSlots() { | ||
return !!Object.entries(this.getSlots()).length; | ||
} | ||
/** | ||
* Determines whether this UI5 Element supports any slots with "individualSlots: true" | ||
* @public | ||
*/ | ||
hasIndividualSlots() { | ||
return this.slotsAreManaged() && Object.entries(this.getSlots()).some(([_slotName, slotData]) => slotData.individualSlots); | ||
} | ||
/** | ||
* Determines whether this UI5 Element needs to invalidate if children are added/removed/changed | ||
* @public | ||
*/ | ||
slotsAreManaged() { | ||
return !!this.metadata.managedSlots; | ||
} | ||
/** | ||
* Returns an object with key-value pairs of properties and their metadata definitions | ||
* @public | ||
*/ | ||
getProperties() { | ||
return this.metadata.properties || {}; | ||
} | ||
/** | ||
* Returns an object with key-value pairs of events and their metadata definitions | ||
* @public | ||
*/ | ||
getEvents() { | ||
return this.metadata.events || {}; | ||
} | ||
/** | ||
* Determines whether this UI5 Element has any translatable texts (needs to be invalidated upon language change) | ||
* @returns {boolean} | ||
*/ | ||
isLanguageAware() { | ||
return !!this.metadata.languageAware; | ||
} | ||
constructor(metadata) { | ||
this.metadata = metadata; | ||
} | ||
getInitialState() { | ||
if (Object.prototype.hasOwnProperty.call(this, "_initialState")) { | ||
return this._initialState; | ||
} | ||
const initialState = {}; | ||
const slotsAreManaged = this.slotsAreManaged(); | ||
// Initialize properties | ||
const props = this.getProperties(); | ||
for (const propName in props) { // eslint-disable-line | ||
const propType = props[propName].type; | ||
const propDefaultValue = props[propName].defaultValue; | ||
if (propType === Boolean) { | ||
initialState[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) { | ||
Object.defineProperty(initialState, propName, { | ||
enumerable: true, | ||
get() { | ||
return []; | ||
}, | ||
}); | ||
} | ||
else if (propType === Object) { | ||
Object.defineProperty(initialState, propName, { | ||
enumerable: true, | ||
get() { | ||
return "defaultValue" in props[propName] ? props[propName].defaultValue : {}; | ||
}, | ||
}); | ||
} | ||
else if (propType === String) { | ||
initialState[propName] = "defaultValue" in props[propName] ? props[propName].defaultValue : ""; | ||
} | ||
else { | ||
initialState[propName] = propDefaultValue; | ||
} | ||
} | ||
// Initialize slots | ||
if (slotsAreManaged) { | ||
const slots = this.getSlots(); | ||
for (const [slotName, slotData] of Object.entries(slots)) { // eslint-disable-line | ||
const propertyName = slotData.propertyName || slotName; | ||
initialState[propertyName] = []; | ||
} | ||
} | ||
this._initialState = initialState; | ||
return initialState; | ||
} | ||
/** | ||
* Validates the property's value and returns it if correct | ||
* or returns the default value if not. | ||
* **Note:** Only intended for use by UI5Element.js | ||
* @public | ||
*/ | ||
static validatePropertyValue(value, propData) { | ||
const isMultiple = propData.multiple; | ||
if (isMultiple && value) { | ||
return value.map((propValue) => validateSingleProperty(propValue, propData)); | ||
} | ||
return validateSingleProperty(value, propData); | ||
} | ||
/** | ||
* Validates the slot's value and returns it if correct | ||
* or throws an exception if not. | ||
* **Note:** Only intended for use by UI5Element.js | ||
* @public | ||
*/ | ||
static validateSlotValue(value, slotData) { | ||
return validateSingleSlot(value, slotData); | ||
} | ||
/** | ||
* Returns the tag of the UI5 Element without the scope | ||
* @public | ||
*/ | ||
getPureTag() { | ||
return this.metadata.tag || ""; | ||
} | ||
/** | ||
* Returns the tag of the UI5 Element | ||
* @public | ||
*/ | ||
getTag() { | ||
const pureTag = this.metadata.tag; | ||
if (!pureTag) { | ||
return ""; | ||
} | ||
const suffix = getEffectiveScopingSuffixForTag(pureTag); | ||
if (!suffix) { | ||
return pureTag; | ||
} | ||
return `${pureTag}-${suffix}`; | ||
} | ||
/** | ||
* Determines whether a property should have an attribute counterpart | ||
* @public | ||
* @param propName | ||
*/ | ||
hasAttribute(propName) { | ||
const propData = this.getProperties()[propName]; | ||
return propData.type !== Object && !propData.noAttribute && !propData.multiple; | ||
} | ||
/** | ||
* Returns an array with the properties of the UI5 Element (in camelCase) | ||
* @public | ||
*/ | ||
getPropertiesList() { | ||
return Object.keys(this.getProperties()); | ||
} | ||
/** | ||
* Returns an array with the attributes of the UI5 Element (in kebab-case) | ||
* @public | ||
*/ | ||
getAttributesList() { | ||
return this.getPropertiesList().filter(this.hasAttribute.bind(this)).map(camelToKebabCase); | ||
} | ||
/** | ||
* Determines whether this UI5 Element has a default slot of type Node, therefore can slot text | ||
*/ | ||
canSlotText() { | ||
return (this.getSlots().default)?.type === Node; | ||
} | ||
/** | ||
* Determines whether this UI5 Element supports any slots | ||
* @public | ||
*/ | ||
hasSlots() { | ||
return !!Object.entries(this.getSlots()).length; | ||
} | ||
/** | ||
* Determines whether this UI5 Element supports any slots with "individualSlots: true" | ||
* @public | ||
*/ | ||
hasIndividualSlots() { | ||
return this.slotsAreManaged() && Object.values(this.getSlots()).some(slotData => slotData.individualSlots); | ||
} | ||
/** | ||
* Determines whether this UI5 Element needs to invalidate if children are added/removed/changed | ||
* @public | ||
*/ | ||
slotsAreManaged() { | ||
return !!this.metadata.managedSlots; | ||
} | ||
/** | ||
* Determines whether this control supports F6 fast navigation | ||
* @public | ||
*/ | ||
supportsF6FastNavigation() { | ||
return !!this.metadata.fastNavigation; | ||
} | ||
/** | ||
* Returns an object with key-value pairs of properties and their metadata definitions | ||
* @public | ||
*/ | ||
getProperties() { | ||
if (!this.metadata.properties) { | ||
this.metadata.properties = {}; | ||
} | ||
return this.metadata.properties; | ||
} | ||
/** | ||
* Returns an object with key-value pairs of events and their metadata definitions | ||
* @public | ||
*/ | ||
getEvents() { | ||
if (!this.metadata.events) { | ||
this.metadata.events = {}; | ||
} | ||
return this.metadata.events; | ||
} | ||
/** | ||
* Returns an object with key-value pairs of slots and their metadata definitions | ||
* @public | ||
*/ | ||
getSlots() { | ||
if (!this.metadata.slots) { | ||
this.metadata.slots = {}; | ||
} | ||
return this.metadata.slots; | ||
} | ||
/** | ||
* Determines whether this UI5 Element has any translatable texts (needs to be invalidated upon language change) | ||
*/ | ||
isLanguageAware() { | ||
return !!this.metadata.languageAware; | ||
} | ||
/** | ||
* Determines whether this UI5 Element has any theme dependant carachteristics. | ||
*/ | ||
isThemeAware() { | ||
return !!this.metadata.themeAware; | ||
} | ||
/** | ||
* Matches a changed entity (property/slot) with the given name against the "invalidateOnChildChange" configuration | ||
* and determines whether this should cause and invalidation | ||
* | ||
* @param slotName the name of the slot in which a child was changed | ||
* @param type the type of change in the child: "property" or "slot" | ||
* @param name the name of the property/slot that changed | ||
*/ | ||
shouldInvalidateOnChildChange(slotName, type, name) { | ||
const config = this.getSlots()[slotName].invalidateOnChildChange; | ||
// invalidateOnChildChange was not set in the slot metadata - by default child changes do not affect the component | ||
if (config === undefined) { | ||
return false; | ||
} | ||
// The simple format was used: invalidateOnChildChange: true/false; | ||
if (typeof config === "boolean") { | ||
return config; | ||
} | ||
// The complex format was used: invalidateOnChildChange: { properties, slots } | ||
if (typeof config === "object") { | ||
// A property was changed | ||
if (type === "property") { | ||
// The config object does not have a properties field | ||
if (config.properties === undefined) { | ||
return false; | ||
} | ||
// The config object has the short format: properties: true/false | ||
if (typeof config.properties === "boolean") { | ||
return config.properties; | ||
} | ||
// The config object has the complex format: properties: [...] | ||
if (Array.isArray(config.properties)) { | ||
return config.properties.includes(name); | ||
} | ||
throw new Error("Wrong format for invalidateOnChildChange.properties: boolean or array is expected"); | ||
} | ||
// A slot was changed | ||
if (type === "slot") { | ||
// The config object does not have a slots field | ||
if (config.slots === undefined) { | ||
return false; | ||
} | ||
// The config object has the short format: slots: true/false | ||
if (typeof config.slots === "boolean") { | ||
return config.slots; | ||
} | ||
// The config object has the complex format: slots: [...] | ||
if (Array.isArray(config.slots)) { | ||
return config.slots.includes(name); | ||
} | ||
throw new Error("Wrong format for invalidateOnChildChange.slots: boolean or array is expected"); | ||
} | ||
} | ||
throw new Error("Wrong format for invalidateOnChildChange: boolean or object is expected"); | ||
} | ||
} | ||
const validateSingleProperty = (value, propData) => { | ||
const propertyType = propData.type; | ||
if (propertyType === Boolean) { | ||
return typeof value === "boolean" ? value : false; | ||
} | ||
if (propertyType === String) { | ||
return (typeof value === "string" || typeof value === "undefined" || value === null) ? value : value.toString(); | ||
} | ||
if (propertyType === Object) { | ||
return typeof value === "object" ? value : propData.defaultValue; | ||
} | ||
if (isDescendantOf(propertyType, DataType)) { | ||
return propertyType.isValid(value) ? value : propData.defaultValue; | ||
} | ||
const propertyType = propData.type; | ||
let propertyValidator = propData.validator; | ||
if (propertyType && propertyType.isDataTypeClass) { | ||
propertyValidator = propertyType; | ||
} | ||
if (propertyValidator) { | ||
return propertyValidator.isValid(value) ? value : propData.defaultValue; | ||
} | ||
if (!propertyType || propertyType === String) { | ||
// eslint-disable-next-line @typescript-eslint/no-base-to-string -- if an object is passed as a value to a string property, this was an error so displaying [object Object] will indicate the issue to the developer | ||
return (typeof value === "string" || typeof value === "undefined" || value === null) ? value : value.toString(); | ||
} | ||
if (propertyType === Boolean) { | ||
return typeof value === "boolean" ? value : false; | ||
} | ||
if (propertyType === Object) { | ||
return typeof value === "object" ? value : propData.defaultValue; | ||
} | ||
// Check if "value" is part of the enum (propertyType) values and return the defaultValue if not found. | ||
return value in propertyType ? value : propData.defaultValue; | ||
}; | ||
const validateSingleSlot = (value, slotData) => { | ||
if (value === null) { | ||
return value; | ||
} | ||
const getSlottedNodes = el => { | ||
if (isSlot(el)) { | ||
return el.assignedNodes({ flatten: true }).filter(item => item instanceof HTMLElement); | ||
} | ||
return [el]; | ||
}; | ||
const slottedNodes = getSlottedNodes(value); | ||
slottedNodes.forEach(el => { | ||
if (!(el instanceof slotData.type)) { | ||
throw new Error(`${el} is not of type ${slotData.type}`); | ||
} | ||
}); | ||
return value; | ||
value && getSlottedNodes(value).forEach(el => { | ||
if (!(el instanceof slotData.type)) { | ||
throw new Error(`The element is not of type ${slotData.type.toString()}`); | ||
} | ||
}); | ||
return value; | ||
}; | ||
export default UI5ElementMetadata; | ||
//# sourceMappingURL=UI5ElementMetadata.js.map |
/** | ||
* Creates a <style> tag in the <head> tag | ||
* Creates a `<style>` tag in the `<head>` tag | ||
* @param cssText - the CSS | ||
@@ -7,13 +7,13 @@ * @param attributes - optional attributes to add to the tag | ||
*/ | ||
const createStyleInHead = (cssText, attributes = {}) => { | ||
const style = document.createElement("style"); | ||
style.type = "text/css"; | ||
Object.entries(attributes).forEach(pair => style.setAttribute(...pair)); | ||
style.textContent = cssText; | ||
document.head.appendChild(style); | ||
return style; | ||
const createStyleInHead = (cssText, attributes) => { | ||
const style = document.createElement("style"); | ||
style.type = "text/css"; | ||
if (attributes) { | ||
Object.entries(attributes).forEach(pair => style.setAttribute(...pair)); | ||
} | ||
style.textContent = cssText; | ||
document.head.appendChild(style); | ||
return style; | ||
}; | ||
export default createStyleInHead; | ||
//# sourceMappingURL=createStyleInHead.js.map |
import { DEFAULT_LANGUAGE } from "../generated/AssetParameters.js"; | ||
export default () => { | ||
const browserLanguages = navigator.languages; | ||
const navigatorLanguage = () => { | ||
return navigator.language; | ||
}; | ||
const rawLocale = (browserLanguages && browserLanguages[0]) || navigatorLanguage() || navigator.userLanguage || navigator.browserLanguage; | ||
return rawLocale || DEFAULT_LANGUAGE; | ||
const isSSR = typeof document === "undefined"; | ||
const detectNavigatorLanguage = () => { | ||
if (isSSR) { | ||
return DEFAULT_LANGUAGE; | ||
} | ||
const browserLanguages = navigator.languages; | ||
const navigatorLanguage = () => { | ||
return navigator.language; | ||
}; | ||
const rawLocale = (browserLanguages && browserLanguages[0]) || navigatorLanguage(); | ||
return rawLocale || DEFAULT_LANGUAGE; | ||
}; | ||
export default detectNavigatorLanguage; | ||
//# sourceMappingURL=detectNavigatorLanguage.js.map |
const fetchPromises = new Map(); | ||
const jsonPromises = new Map(); | ||
const textPromises = new Map(); | ||
const fetchTextOnce = async url => { | ||
if (!fetchPromises.get(url)) { | ||
fetchPromises.set(url, fetch(url)); | ||
} | ||
const response = await fetchPromises.get(url); | ||
if (!textPromises.get(url)) { | ||
textPromises.set(url, response.text()); | ||
} | ||
return textPromises.get(url); | ||
const fetchTextOnce = async (url) => { | ||
if (!fetchPromises.get(url)) { | ||
fetchPromises.set(url, fetch(url)); | ||
} | ||
const response = await fetchPromises.get(url); | ||
if (response && !textPromises.get(url)) { | ||
textPromises.set(url, response.text()); | ||
} | ||
return textPromises.get(url); | ||
}; | ||
const fetchJsonOnce = async url => { | ||
if (!fetchPromises.get(url)) { | ||
fetchPromises.set(url, fetch(url)); | ||
} | ||
const response = await fetchPromises.get(url); | ||
if (!jsonPromises.get(url)) { | ||
jsonPromises.set(url, response.json()); | ||
} | ||
return jsonPromises.get(url); | ||
const fetchJsonOnce = async (url) => { | ||
if (!fetchPromises.get(url)) { | ||
fetchPromises.set(url, fetch(url)); | ||
} | ||
const response = await fetchPromises.get(url); | ||
if (response && !jsonPromises.get(url)) { | ||
jsonPromises.set(url, response.json()); | ||
} | ||
return jsonPromises.get(url); | ||
}; | ||
export { fetchTextOnce, fetchJsonOnce }; | ||
//# sourceMappingURL=FetchHelper.js.map |
@@ -1,66 +0,72 @@ | ||
import isNodeHidden from "./isNodeHidden.js"; | ||
import isNodeClickable from "./isNodeClickable.js"; | ||
const isFocusTrap = el => { | ||
return el.hasAttribute("data-ui5-focus-trap"); | ||
import isElementHidden from "./isElementHidden.js"; | ||
import isElementClickable from "./isElementClickable.js"; | ||
import { instanceOfUI5Element } from "../UI5Element.js"; | ||
const isFocusTrap = (el) => { | ||
return el.hasAttribute("data-ui5-focus-trap"); | ||
}; | ||
const getFirstFocusableElement = container => { | ||
if (!container || isNodeHidden(container)) { | ||
return null; | ||
} | ||
return findFocusableElement(container, true); | ||
const getFirstFocusableElement = async (container, startFromContainer) => { | ||
if (!container || isElementHidden(container)) { | ||
return null; | ||
} | ||
return findFocusableElement(container, true, startFromContainer); | ||
}; | ||
const getLastFocusableElement = container => { | ||
if (!container || isNodeHidden(container)) { | ||
return null; | ||
} | ||
return findFocusableElement(container, false); | ||
const getLastFocusableElement = async (container, startFromContainer) => { | ||
if (!container || isElementHidden(container)) { | ||
return null; | ||
} | ||
return findFocusableElement(container, false, startFromContainer); | ||
}; | ||
const findFocusableElement = (container, forward) => { | ||
let child; | ||
if (container.shadowRoot) { | ||
child = forward ? container.shadowRoot.firstChild : container.shadowRoot.lastChild; | ||
} else if (container.assignedNodes && container.assignedNodes()) { | ||
const assignedElements = container.assignedNodes(); | ||
child = forward ? assignedElements[0] : assignedElements[assignedElements.length - 1]; | ||
} else { | ||
child = forward ? container.firstChild : container.lastChild; | ||
} | ||
let focusableDescendant; | ||
while (child) { | ||
const originalChild = child; | ||
child = child.isUI5Element ? child.getFocusDomRef() : child; | ||
if (!child) { | ||
return null; | ||
} | ||
if (child.nodeType === 1 && !isNodeHidden(child) && !isFocusTrap(child)) { | ||
if (isNodeClickable(child)) { | ||
return (child && typeof child.focus === "function") ? child : null; | ||
} | ||
focusableDescendant = findFocusableElement(child, forward); | ||
if (focusableDescendant) { | ||
return (focusableDescendant && typeof focusableDescendant.focus === "function") ? focusableDescendant : null; | ||
} | ||
} | ||
child = forward ? originalChild.nextSibling : originalChild.previousSibling; | ||
} | ||
return null; | ||
const isElemFocusable = (el) => { | ||
return el.hasAttribute("data-ui5-focus-redirect") || !isElementHidden(el); | ||
}; | ||
export { | ||
getFirstFocusableElement, | ||
getLastFocusableElement, | ||
const findFocusableElement = async (container, forward, startFromContainer) => { | ||
let child; | ||
let assignedElements; | ||
let currentIndex = -1; | ||
if (container.shadowRoot) { | ||
child = forward ? container.shadowRoot.firstChild : container.shadowRoot.lastChild; | ||
} | ||
else if (container instanceof HTMLSlotElement && container.assignedNodes()) { | ||
assignedElements = container.assignedNodes(); | ||
currentIndex = forward ? 0 : assignedElements.length - 1; | ||
child = assignedElements[currentIndex]; | ||
} | ||
else if (startFromContainer) { | ||
child = container; | ||
} | ||
else { | ||
child = forward ? container.firstElementChild : container.lastElementChild; | ||
} | ||
let focusableDescendant; | ||
/* eslint-disable no-await-in-loop */ | ||
while (child) { | ||
const originalChild = child; | ||
if (instanceOfUI5Element(child)) { | ||
child = await child.getFocusDomRefAsync(); | ||
} | ||
if (!child) { | ||
return null; | ||
} | ||
if (child.nodeType === 1 && isElemFocusable(child) && !isFocusTrap(child)) { | ||
if (isElementClickable(child)) { | ||
return (child && typeof child.focus === "function") ? child : null; | ||
} | ||
focusableDescendant = await findFocusableElement(child, forward); | ||
if (focusableDescendant) { | ||
return (focusableDescendant && typeof focusableDescendant.focus === "function") ? focusableDescendant : null; | ||
} | ||
} | ||
child = forward ? originalChild.nextSibling : originalChild.previousSibling; | ||
// If the child element is not part of the currently assigned element, | ||
// we have to check the next/previous element assigned to the slot or continue with the next/previous sibling of the slot, | ||
// otherwise, the nextSibling/previousSibling is the next element inside the light DOM | ||
if (assignedElements && !assignedElements[currentIndex].contains(child)) { | ||
currentIndex = forward ? currentIndex + 1 : currentIndex - 1; | ||
child = assignedElements[currentIndex]; | ||
} | ||
} | ||
/* eslint-enable no-await-in-loop */ | ||
return null; | ||
}; | ||
export { getFirstFocusableElement, getLastFocusableElement, }; | ||
//# sourceMappingURL=FocusableElements.js.map |
const messageFormatRegEX = /('')|'([^']+(?:''[^']*)*)(?:'|$)|\{([0-9]+(?:\s*,[^{}]*)?)\}|[{}]/g; | ||
const formatMessage = (text, values) => { | ||
values = values || []; | ||
return text.replace(messageFormatRegEX, ($0, $1, $2, $3, offset) => { | ||
if ($1) { | ||
return '\''; /* eslint-disable-line */ | ||
} | ||
if ($2) { | ||
return $2.replace(/''/g, '\''); /* eslint-disable-line */ | ||
} | ||
if ($3) { | ||
return String(values[parseInt($3)]); | ||
} | ||
throw new Error(`[i18n]: pattern syntax error at pos ${offset}`); | ||
}); | ||
values = values || []; | ||
return text.replace(messageFormatRegEX, ($0, $1, $2, $3, offset) => { | ||
if ($1) { | ||
return '\''; /* eslint-disable-line */ | ||
} | ||
if ($2) { | ||
return $2.replace(/''/g, '\''); /* eslint-disable-line */ | ||
} | ||
if ($3) { | ||
const ind = typeof $3 === "string" ? parseInt($3) : $3; | ||
return String(values[ind]); | ||
} | ||
throw new Error(`[i18n]: pattern syntax error at pos ${offset}`); | ||
}); | ||
}; | ||
export default formatMessage; | ||
//# sourceMappingURL=formatMessage.js.map |
@@ -1,4 +0,6 @@ | ||
export default value => { | ||
const m = /\$([-a-z0-9A-Z._]+)(?::([^$]*))?\$/.exec(value); | ||
return m && m[2] ? m[2].split(/,/) : null; | ||
const designTimePropertyAsArray = (value) => { | ||
const m = /\$([-a-z0-9A-Z._]+)(?::([^$]*))?\$/.exec(value); | ||
return m && m[2] ? m[2].split(/,/) : null; | ||
}; | ||
export default designTimePropertyAsArray; | ||
//# sourceMappingURL=getDesigntimePropertyAsArray.js.map |
@@ -8,15 +8,13 @@ /** | ||
* | ||
* @param fileName - the file name | ||
* @returns {string} | ||
* @param { string } fileName - the file name | ||
* @returns { string } | ||
*/ | ||
const getFileExtension = fileName => { | ||
const dotPos = fileName.lastIndexOf("."); | ||
if (dotPos < 1) { | ||
return ""; | ||
} | ||
return fileName.slice(dotPos); | ||
const getFileExtension = (fileName) => { | ||
const dotPos = fileName.lastIndexOf("."); | ||
if (dotPos < 1) { | ||
return ""; | ||
} | ||
return fileName.slice(dotPos); | ||
}; | ||
export default getFileExtension; | ||
//# sourceMappingURL=getFileExtension.js.map |
@@ -1,13 +0,19 @@ | ||
const getSingletonElementInstance = (tag, parentElement = document.body) => { | ||
let el = document.querySelector(tag); | ||
if (el) { | ||
return el; | ||
} | ||
el = document.createElement(tag); | ||
return parentElement.insertBefore(el, parentElement.firstChild); | ||
/** | ||
* Returns a singleton HTML element, inserted in given parent element of HTML page, | ||
* used mostly to store and share global resources between multiple UI5 Web Components runtimes. | ||
* | ||
* @param { string } tag the element tag/selector | ||
* @param { HTMLElement } parentElement the parent element to insert the singleton element instance | ||
* @param { Function } createEl a factory function for the element instantiation, by default document.createElement is used | ||
* @returns { Element } | ||
*/ | ||
const getSingletonElementInstance = (tag, parentElement = document.body, createEl) => { | ||
let el = document.querySelector(tag); | ||
if (el) { | ||
return el; | ||
} | ||
el = createEl ? createEl() : document.createElement(tag); | ||
return parentElement.insertBefore(el, parentElement.firstChild); | ||
}; | ||
export default getSingletonElementInstance; | ||
//# sourceMappingURL=getSingletonElementInstance.js.map |
// Note: disabled is present in IE so we explicitly allow it here. | ||
// Others, such as title/hidden, we explicitly override, so valid too | ||
const whitelist = [ | ||
"disabled", | ||
"title", | ||
"hidden", | ||
const allowList = [ | ||
"disabled", | ||
"title", | ||
"hidden", | ||
"role", | ||
"draggable", | ||
]; | ||
/** | ||
@@ -15,14 +16,14 @@ * Checks whether a property name is valid (does not collide with existing DOM API properties) | ||
*/ | ||
const isValidPropertyName = name => { | ||
if (whitelist.includes(name) || name.startsWith("aria")) { | ||
return true; | ||
} | ||
const classes = [ | ||
HTMLElement, | ||
Element, | ||
Node, | ||
]; | ||
return !classes.some(klass => klass.prototype.hasOwnProperty(name)); // eslint-disable-line | ||
const isValidPropertyName = (name) => { | ||
if (allowList.includes(name) || name.startsWith("aria")) { | ||
return true; | ||
} | ||
const classes = [ | ||
HTMLElement, | ||
Element, | ||
Node, | ||
]; | ||
return !classes.some(klass => klass.prototype.hasOwnProperty(name)); // eslint-disable-line | ||
}; | ||
export default isValidPropertyName; | ||
//# sourceMappingURL=isValidPropertyName.js.map |
const kebabToCamelMap = new Map(); | ||
const camelToKebabMap = new Map(); | ||
const kebabToCamelCase = string => { | ||
if (!kebabToCamelMap.has(string)) { | ||
const result = toCamelCase(string.split("-")); | ||
kebabToCamelMap.set(string, result); | ||
} | ||
return kebabToCamelMap.get(string); | ||
const kebabToCamelCase = (string) => { | ||
if (!kebabToCamelMap.has(string)) { | ||
const result = toCamelCase(string.split("-")); | ||
kebabToCamelMap.set(string, result); | ||
} | ||
return kebabToCamelMap.get(string); | ||
}; | ||
const camelToKebabCase = string => { | ||
if (!camelToKebabMap.has(string)) { | ||
const result = string.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); | ||
camelToKebabMap.set(string, result); | ||
} | ||
return camelToKebabMap.get(string); | ||
const camelToKebabCase = (string) => { | ||
if (!camelToKebabMap.has(string)) { | ||
const result = string.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); | ||
camelToKebabMap.set(string, result); | ||
} | ||
return camelToKebabMap.get(string); | ||
}; | ||
const toCamelCase = parts => { | ||
return parts.map((string, index) => { | ||
return index === 0 ? string.toLowerCase() : string.charAt(0).toUpperCase() + string.slice(1).toLowerCase(); | ||
}).join(""); | ||
const toCamelCase = (parts) => { | ||
return parts.map((string, index) => { | ||
return index === 0 ? string.toLowerCase() : string.charAt(0).toUpperCase() + string.slice(1).toLowerCase(); | ||
}).join(""); | ||
}; | ||
export { kebabToCamelCase, camelToKebabCase }; | ||
//# sourceMappingURL=StringHelper.js.map |
@@ -1,47 +0,57 @@ | ||
import isNodeTabbable from "./isNodeTabbable.js"; | ||
const getTabbableElements = node => { | ||
return getTabbables(node.children); | ||
import isElementTabbable from "./isElementTabbable.js"; | ||
/** | ||
* Returns the tabbable elements within the provided HTMLElement. | ||
* | ||
* @public | ||
* @param { HTMLElement } el the component to operate on (component that slots or contains within its shadow root the items the user navigates among) | ||
* @returns { Array<HTMLElement> } the tabbable elements | ||
*/ | ||
const getTabbableElements = (el) => { | ||
return getTabbables([...el.children]); | ||
}; | ||
const getLastTabbableElement = node => { | ||
const tabbables = getTabbables(node.children); | ||
return tabbables.length ? tabbables[tabbables.length - 1] : null; | ||
/** | ||
* Returns the last tabbable element within the provided HTMLElement. | ||
* | ||
* @public | ||
* @param { HTMLElement } el the component to operate on (component that slots or contains within its shadow root the items the user navigates among) | ||
* @returns { HTMLElement | null } the last tabbable element or "null" if not found | ||
*/ | ||
const getLastTabbableElement = (el) => { | ||
const tabbables = getTabbables([...el.children]); | ||
return tabbables.length ? tabbables[tabbables.length - 1] : null; | ||
}; | ||
const getTabbables = (nodes, tabbables) => { | ||
const tabbablesNodes = tabbables || []; | ||
if (!nodes) { | ||
return tabbablesNodes; | ||
} | ||
Array.from(nodes).forEach(currentNode => { | ||
if (currentNode.nodeType === Node.TEXT_NODE || currentNode.nodeType === Node.COMMENT_NODE) { | ||
return; | ||
} | ||
if (currentNode.shadowRoot) { | ||
// get the root node of the ShadowDom (1st none style tag) | ||
const children = currentNode.shadowRoot.children; | ||
currentNode = Array.from(children).find(node => node.tagName !== "STYLE"); | ||
} | ||
if (isNodeTabbable(currentNode)) { | ||
tabbablesNodes.push(currentNode); | ||
} | ||
if (currentNode.tagName === "SLOT") { | ||
getTabbables(currentNode.assignedNodes(), tabbablesNodes); | ||
} else { | ||
getTabbables(currentNode.children, tabbablesNodes); | ||
} | ||
}); | ||
return tabbablesNodes; | ||
const tabbableElements = tabbables || []; | ||
if (!nodes) { | ||
return tabbableElements; | ||
} | ||
nodes.forEach(currentNode => { | ||
if (currentNode.nodeType === Node.TEXT_NODE || currentNode.nodeType === Node.COMMENT_NODE) { | ||
return; | ||
} | ||
let currentElement = currentNode; | ||
if (currentElement.hasAttribute("data-sap-no-tab-ref")) { | ||
return; | ||
} | ||
if (currentElement.shadowRoot) { | ||
// get the root node of the ShadowDom (1st none style tag) | ||
const children = currentElement.shadowRoot.children; | ||
currentElement = Array.from(children).find(node => node.tagName !== "STYLE"); | ||
} | ||
if (!currentElement) { | ||
return; | ||
} | ||
if (isElementTabbable(currentElement)) { | ||
tabbableElements.push(currentElement); | ||
} | ||
if (currentElement.tagName === "SLOT") { | ||
getTabbables(currentElement.assignedNodes(), tabbableElements); | ||
} | ||
else { | ||
getTabbables([...currentElement.children], tabbableElements); | ||
} | ||
}); | ||
return tabbableElements; | ||
}; | ||
export { | ||
getTabbableElements, | ||
getLastTabbableElement, | ||
}; | ||
export { getTabbableElements, getLastTabbableElement, }; | ||
//# sourceMappingURL=TabbableElements.js.map |
const whenDOMReady = () => { | ||
return new Promise(resolve => { | ||
if (document.body) { | ||
resolve(); | ||
} else { | ||
document.addEventListener("DOMContentLoaded", () => { | ||
resolve(); | ||
}); | ||
} | ||
}); | ||
return new Promise(resolve => { | ||
if (document.body) { | ||
resolve(); | ||
} | ||
else { | ||
document.addEventListener("DOMContentLoaded", () => { | ||
resolve(); | ||
}); | ||
} | ||
}); | ||
}; | ||
export default whenDOMReady; | ||
//# sourceMappingURL=whenDOMReady.js.map |
228
index.js
@@ -1,4 +0,228 @@ | ||
import UI5Element from "./src/UI5Element.js"; | ||
// animations/ | ||
import scroll from "./dist/animations/scroll.js"; | ||
import slideDown from "./dist/animations/slideDown.js"; | ||
import slideUp from "./dist/animations/slideUp.js"; | ||
// config/ | ||
import { getAnimationMode, setAnimationMode } from "./dist/config/AnimationMode.js"; | ||
import { getCalendarType } from "./dist/config/CalendarType.js"; | ||
import { getFirstDayOfWeek, getLegacyDateCalendarCustomizing } from "./dist/config/FormatSettings.js"; | ||
import { | ||
setDefaultIconCollection, | ||
getDefaultIconCollection, | ||
} from "./dist/config/Icons.js"; | ||
import { RegisteredIconCollection } from "./dist/asset-registries/util/IconCollectionsByTheme.js"; | ||
import getEffectiveIconCollection from "./dist/asset-registries/util/getIconCollectionByTheme.js"; | ||
import { | ||
getLanguage, | ||
setLanguage, | ||
getDefaultLanguage, | ||
setFetchDefaultLanguage, | ||
getFetchDefaultLanguage, | ||
} from "./dist/config/Language.js"; | ||
import { getNoConflict, setNoConflict } from "./dist/config/NoConflict.js"; | ||
import { | ||
getTheme, | ||
setTheme, | ||
getDefaultTheme, | ||
} from "./dist/config/Theme.js"; | ||
// decorators/ | ||
import customElement from "./dist/decorators/customElement.js"; | ||
import event from "./dist/decorators/event.js"; | ||
import property from "./dist/decorators/property.js"; | ||
import slot from "./dist/decorators/slot.js"; | ||
// delegate/ | ||
import ItemNavigation from "./dist/delegate/ItemNavigation.js"; | ||
import ResizeHandler from "./dist/delegate/ResizeHandler.js"; | ||
import ScrollEnablement from "./dist/delegate/ScrollEnablement.js"; | ||
// locale/ | ||
import applyDirection from "./dist/locale/applyDirection.js"; | ||
import { attachDirectionChange, detachDirectionChange } from "./dist/locale/directionChange.js"; | ||
import getEffectiveDir from "./dist/locale/getEffectiveDir.js"; | ||
import { attachLanguageChange, detachLanguageChange } from "./dist/locale/languageChange.js"; | ||
// util/ | ||
import { URLListValidator, sanitizeHTML } from "./dist/util/HTMLSanitizer.js"; | ||
// Assets.ts | ||
import { registerI18nLoader } from "./dist/asset-registries/i18n.js"; | ||
import { registerLocaleDataLoader } from "./dist/asset-registries/LocaleData.js"; | ||
import { registerThemePropertiesLoader } from "./dist/asset-registries/Themes.js"; | ||
import { registerIconLoader } from "./dist/asset-registries/Icons.js"; | ||
// Boot.ts | ||
import { attachBoot } from "./dist/Boot.js"; | ||
// CSP.ts | ||
import { | ||
setPackageCSSRoot, | ||
setUseLinks, | ||
setPreloadLinks, | ||
} from "./dist/CSP.js"; | ||
// CustomElementsScope.ts | ||
import { | ||
setCustomElementsScopingSuffix, | ||
getCustomElementsScopingSuffix, | ||
setCustomElementsScopingRules, | ||
getCustomElementsScopingRules, | ||
getEffectiveScopingSuffixForTag, | ||
} from "./dist/CustomElementsScope.js"; | ||
// Device.ts | ||
import { | ||
supportsTouch, | ||
isIE, | ||
isSafari, | ||
isChrome, | ||
isFirefox, | ||
isPhone, | ||
isTablet, | ||
isDesktop, | ||
isCombi, | ||
isIOS, | ||
isAndroid, | ||
} from "./dist/Device.js"; | ||
// EventProvider.ts | ||
import EventProvider from "./dist/EventProvider.js"; | ||
// i18nBundle.ts | ||
import I18nBundle, { getI18nBundle, registerCustomI18nBundleGetter } from "./dist/i18nBundle.js"; | ||
// MediaRange.ts | ||
import MediaRange from "./dist/MediaRange.js"; | ||
// PropertiesFileFormat.ts | ||
import parseProperties from "./dist/PropertiesFileFormat.js"; | ||
// Render.ts | ||
import { | ||
renderDeferred, | ||
renderImmediately, | ||
cancelRender, | ||
renderFinished, | ||
} from "./dist/Render.js"; | ||
// Theming.ts | ||
import { addCustomCSS, attachThemeLoaded, detachThemeLoaded } from "./dist/Theming.js"; | ||
// UI5Element.ts | ||
import UI5Element from "./dist/UI5Element.js"; | ||
export default UI5Element; | ||
export { UI5Element }; | ||
export { | ||
// animations/ | ||
scroll, | ||
slideDown, | ||
slideUp, | ||
// config/ | ||
getAnimationMode, | ||
setAnimationMode, | ||
getCalendarType, | ||
getFirstDayOfWeek, | ||
getLegacyDateCalendarCustomizing, | ||
setDefaultIconCollection, | ||
getDefaultIconCollection, | ||
getEffectiveIconCollection, | ||
RegisteredIconCollection, | ||
getLanguage, | ||
setLanguage, | ||
getDefaultLanguage, | ||
setFetchDefaultLanguage, | ||
getFetchDefaultLanguage, | ||
getNoConflict, | ||
setNoConflict, | ||
getTheme, | ||
setTheme, | ||
getDefaultTheme, | ||
// decorators/ | ||
customElement, | ||
event, | ||
property, | ||
slot, | ||
// delegate/ | ||
ItemNavigation, | ||
ResizeHandler, | ||
ScrollEnablement, | ||
// locale/ | ||
applyDirection, | ||
attachDirectionChange, | ||
detachDirectionChange, | ||
getEffectiveDir, | ||
attachLanguageChange, | ||
detachLanguageChange, | ||
// util/ | ||
URLListValidator, | ||
sanitizeHTML, | ||
// Assets.ts | ||
registerI18nLoader, | ||
registerLocaleDataLoader, | ||
registerThemePropertiesLoader, | ||
registerIconLoader, | ||
// Boot.ts | ||
attachBoot, | ||
// CSP.ts | ||
setPackageCSSRoot, | ||
setUseLinks, | ||
setPreloadLinks, | ||
// CustomElementsScope.ts | ||
setCustomElementsScopingSuffix, | ||
getCustomElementsScopingSuffix, | ||
setCustomElementsScopingRules, | ||
getCustomElementsScopingRules, | ||
getEffectiveScopingSuffixForTag, | ||
// Device.ts | ||
supportsTouch, | ||
isIE, | ||
isSafari, | ||
isChrome, | ||
isFirefox, | ||
isPhone, | ||
isTablet, | ||
isDesktop, | ||
isCombi, | ||
isIOS, | ||
isAndroid, | ||
// EventProvider.ts | ||
EventProvider, | ||
// i18nBundle.ts | ||
I18nBundle, | ||
getI18nBundle, | ||
registerCustomI18nBundleGetter, | ||
// MediaRange.ts | ||
MediaRange, | ||
// PropertiesFileFormat.ts | ||
parseProperties, | ||
// Render.ts | ||
renderDeferred, | ||
renderImmediately, | ||
cancelRender, | ||
renderFinished, | ||
// Theming.ts | ||
addCustomCSS, | ||
attachThemeLoaded, | ||
detachThemeLoaded, | ||
// UI5Element.ts | ||
UI5Element, | ||
}; |
@@ -1,5 +0,3 @@ | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const mkdirp = require('mkdirp'); | ||
const assets = require('@ui5/webcomponents-tools/assets-meta.js'); | ||
import fs from "fs/promises"; | ||
import assets from "@ui5/webcomponents-tools/assets-meta.js"; | ||
@@ -9,2 +7,3 @@ const fileContent = `const assetParameters = ${JSON.stringify(assets)}; | ||
const DEFAULT_THEME = assetParameters.themes.default; | ||
const SUPPORTED_THEMES = assetParameters.themes.all; | ||
const DEFAULT_LANGUAGE = assetParameters.languages.default; | ||
@@ -16,2 +15,3 @@ const DEFAULT_LOCALE = assetParameters.locales.default; | ||
DEFAULT_THEME, | ||
SUPPORTED_THEMES, | ||
DEFAULT_LANGUAGE, | ||
@@ -22,4 +22,9 @@ DEFAULT_LOCALE, | ||
mkdirp.sync("dist/generated/"); | ||
fs.writeFileSync("dist/generated/AssetParameters.js", fileContent); | ||
const generate = async () => { | ||
await fs.mkdir("src/generated/", { recursive: true }); | ||
return fs.writeFile("src/generated/AssetParameters.ts", fileContent); | ||
} | ||
generate().then(() => { | ||
console.log("Assets parameters generated."); | ||
}); |
{ | ||
"name": "@ui5/webcomponents-base", | ||
"version": "0.0.0-306572ffa", | ||
"version": "0.0.0-31ad69296", | ||
"description": "UI5 Web Components: webcomponents.base", | ||
"author": "SAP SE (https://www.sap.com)", | ||
"license": "Apache-2.0", | ||
"type": "module", | ||
"module": "index.js", | ||
@@ -18,2 +19,14 @@ "keywords": [ | ||
}, | ||
"exports": { | ||
"./dist/ssr-dom.js": { | ||
"browser": "./dist/ssr-dom.js", | ||
"node": "./dist/ssr-dom-shim.js", | ||
"default": "./dist/ssr-dom.js" | ||
}, | ||
".": "./index.js", | ||
"./dist/*": "./dist/*", | ||
"./package.json": "./package.json", | ||
"./bundle.esm.js": "./bundle.esm.js", | ||
"./*": "./dist/*" | ||
}, | ||
"scripts": { | ||
@@ -24,21 +37,24 @@ "clean": "nps clean", | ||
"build": "nps build", | ||
"generate": "nps generate", | ||
"generateAPI": "nps generateAPI", | ||
"bundle": "nps build.bundle", | ||
"test": "nps test", | ||
"prepublishOnly": "npm run clean && npm run build" | ||
"prepublishOnly": "tsc -b" | ||
}, | ||
"dependencies": { | ||
"css-vars-ponyfill": "^2.1.2", | ||
"lit-html": "^1.0.0", | ||
"regenerator-runtime": "0.12.1", | ||
"url-search-params-polyfill": "^5.0.0" | ||
"@lit-labs/ssr-dom-shim": "^1.1.2", | ||
"lit-html": "^2.0.1" | ||
}, | ||
"devDependencies": { | ||
"@ui5/webcomponents-tools": "0.0.0-306572ffa", | ||
"array-uniq": "^2.0.0", | ||
"chromedriver": "latest", | ||
"copy-and-watch": "^0.1.4", | ||
"eslint": "^5.13.0", | ||
"eslint-config-airbnb-base": "^13.1.0", | ||
"npm-run-all": "^4.1.5", | ||
"path-exists-cli": "^1.0.0" | ||
"@ui5/webcomponents-tools": "0.0.0-31ad69296", | ||
"chromedriver": "^126.0.2", | ||
"@openui5/sap.ui.core": "1.120.17", | ||
"clean-css": "^5.2.2", | ||
"copy-and-watch": "^0.1.5", | ||
"cross-env": "^7.0.3", | ||
"eslint": "^7.22.0", | ||
"mkdirp": "^1.0.4", | ||
"replace-in-file": "^6.3.5", | ||
"resolve": "^1.20.0" | ||
} | ||
} |
@@ -1,6 +0,6 @@ | ||
![UI5 icon](https://raw.githubusercontent.com/SAP/ui5-webcomponents/master/docs/images/UI5_logo_wide.png) | ||
![UI5 icon](https://raw.githubusercontent.com/SAP/ui5-webcomponents/main/docs/images/UI5_logo_wide.png) | ||
# UI5 Web Components - Base | ||
[![Travis CI Build Status](https://travis-ci.org/SAP/ui5-webcomponents.svg?branch=master)](https://travis-ci.org/SAP/ui5-webcomponents) | ||
[![npm Package Version](https://badge.fury.io/js/%40ui5%2Fwebcomponents.svg)](https://www.npmjs.com/package/@ui5/webcomponents) | ||
@@ -10,6 +10,57 @@ | ||
For a complete list of all app development related public module imports from the `base` package, click [here](../../docs/Public%20Module%20Imports.md#base): | ||
## Provided APIs for applications | ||
| Affects | Import | Description | | ||
|--------------|---------------------------------------------------------- |-----------------------------------------------------------------------------------------------------| | ||
Configuration | `@ui5/webcomponents-base/dist/config/Theme.js` | Sets Theme Configuration | | ||
Configuration | `@ui5/webcomponents-base/dist/config/Language.js` | Sets Language Configuration | | ||
Configuration | `@ui5/webcomponents-base/dist/config/AnimationMode.js` | Sets Animation Mode Configuration | | ||
Configuration | `@ui5/webcomponents-base/dist/config/NoConflict.js` | Sets "NoConflict" Mode Configuration - if enabled all custom events are fired with the `ui5-` prefix| | ||
Framework | `@ui5/webcomponents-base/dist/features/OpenUI5Support.js` | Adds integration with the OpenUI5 framework for resources re-use | | ||
Components | `@ui5/webcomponents-base/dist/features/F6Navigation.js` | Adds support for F6 fast group navigation | | ||
Components | `import applyDirection from "@ui5/webcomponents-base/dist/locale/applyDirection.js"`| Applies direction ("ltr"/"rtl") - re-renders all RTL-aware components | | ||
Components | `import { setCustomElementsScopingSuffix } from "@ui5/webcomponents-base/dist/CustomElementsScope.js"`| Adds suffix to the tag names of all components | | ||
Components | `@ui5/webcomponents-base/dist/util/InvisibleMessage.js` | Provides a way to expose dynamic content changes that can be announced by screen readers | | ||
CSP compliance| `import { setPackageCSSRoot } from "@ui5/webcomponents-base/dist/CSP.js"`| Sets directory path where the CSS resources for given package will be served from | | ||
CSP compliance| `import { setUseLinks } from "@ui5/webcomponents-base/dist/CSP.js"` | Enables or disables the usage of `<link>` tags instead of `<style>` tags | | ||
CSP compliance| `import { setPreloadLinks } from "@ui5/webcomponents-base/dist/CSP.js"` | Enables or disables the preloading of `<link>` tags | | ||
### `applyDirection.js` | ||
- `applyDirection` | ||
### `Boot.js` | ||
- `attachBoot` | ||
### `CustomElementsScope.js` | ||
- `setCustomElementsScopingSuffix` | ||
- `getCustomElementsScopingSuffix` | ||
- `setCustomElementsScopingRules` | ||
- `getCustomElementsScopingRules` | ||
### `IgnoreCustomElements.js` | ||
- `ignoreCustomElements` | ||
### `CSP.js` | ||
- `setPackageCSSRoot` | ||
- `setUseLinks` | ||
- `setPreloadLinks` | ||
### `i18nBundle.js` | ||
- `registerI18nLoader` | ||
- `getI18nBundle` | ||
### `PropertiesFileFormat.js` | ||
- `parseProperties` | ||
### `Render.js` | ||
- `renderFinished` | ||
## Resources | ||
- [UI5 Web Components - README.md](https://github.com/SAP/ui5-webcomponents/blob/master/README.md) | ||
- [UI5 Web Components - README.md](https://github.com/SAP/ui5-webcomponents/blob/main/README.md) | ||
- [UI5 Web Components - Home Page](https://sap.github.io/ui5-webcomponents) | ||
@@ -19,9 +70,9 @@ - [UI5 Web Components - Playground and API Reference](https://sap.github.io/ui5-webcomponents/playground/) | ||
## Support | ||
We welcome all comments, suggestions, questions, and bug reports. Please follow our [Support Guidelines](https://github.com/SAP/ui5-webcomponents/blob/master/SUPPORT.md#-content) on how to report an issue, or chat with us in the `#webcomponents` channel of the [OpenUI5 Community Slack](https://join-ui5-slack.herokuapp.com/). | ||
We welcome all comments, suggestions, questions, and bug reports. Please follow our [Support Guidelines](https://github.com/SAP/ui5-webcomponents/blob/main/SUPPORT.md#-content) on how to report an issue, or chat with us in the `#webcomponents` channel of the [OpenUI5 Community Slack](https://ui5-slack-invite.cfapps.eu10.hana.ondemand.com/). | ||
## Contribute | ||
Please check our [Contribution Guidelines](https://github.com/SAP/ui5-webcomponents/blob/master/CONTRIBUTING.md). | ||
Please check our [Contribution Guidelines](https://github.com/SAP/ui5-webcomponents/blob/main/docs/6-contributing/02-conventions-and-guidelines.md). | ||
## License | ||
Copyright (c) 2019 SAP SE or an SAP affiliate company. All rights reserved. | ||
This file is licensed under the Apache Software License, Version 2.0 except as noted otherwise in the [LICENSE](https://github.com/SAP/ui5-webcomponents/blob/master/LICENSE.txt) file. | ||
This file is licensed under the Apache Software License, Version 2.0 except as noted otherwise in the [LICENSE](https://github.com/SAP/ui5-webcomponents/blob/main/LICENSE.txt) file. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
2
513
22825
77
4
Yes
1692196
10
3
+ Added@lit-labs/ssr-dom-shim@1.3.0(transitive)
+ Added@types/trusted-types@2.0.7(transitive)
+ Addedlit-html@2.8.0(transitive)
- Removedcss-vars-ponyfill@^2.1.2
- Removedregenerator-runtime@0.12.1
- Removedurl-search-params-polyfill@^5.0.0
- Removedbalanced-match@1.0.2(transitive)
- Removedcss-vars-ponyfill@2.4.9(transitive)
- Removedget-css-data@2.1.1(transitive)
- Removedlit-html@1.4.1(transitive)
- Removedregenerator-runtime@0.12.1(transitive)
- Removedurl-search-params-polyfill@5.1.0(transitive)
Updatedlit-html@^2.0.1