@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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
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
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
2
513
0
22825
77
4
Yes
1692196
10
3
+ Added@lit-labs/ssr-dom-shim@1.2.1(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