@ui5/webcomponents-base
Advanced tools
Comparing version 0.0.0-20acae2ee to 0.0.0-20ff83d14
import { registerThemePropertiesLoader } from "./dist/AssetRegistry.js"; | ||
import EventProvider from "./dist/EventProvider.js"; | ||
@@ -7,16 +8,16 @@ // ESM bundle targets browsers with native support | ||
// 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 { renderFinished } from "./dist/Render.js"; | ||
import { isIE } from "./dist/Device.js"; | ||
window.isIE = isIE; // attached to the window object for testing purposes | ||
@@ -27,13 +28,18 @@ // used for tests - to register a custom theme | ||
// i18n | ||
import { registerI18nLoader, 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 { 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"] = { | ||
@@ -43,3 +49,6 @@ configuration : { | ||
getLanguage, | ||
setLanguage, | ||
getTheme, | ||
getThemeRoot, | ||
setThemeRoot, | ||
setTheme, | ||
@@ -49,10 +58,13 @@ getNoConflict, | ||
getCalendarType, | ||
getRTL, | ||
getFirstDayOfWeek, | ||
getLegacyDateCalendarCustomizing, | ||
}, | ||
getCurrentRuntimeIndex, | ||
getIconNames, | ||
parseProperties, | ||
registerI18nLoader, | ||
fetchI18nBundle, | ||
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 |
@@ -7,64 +7,49 @@ import getLocale from "../locale/getLocale.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 bundlePromises = new Map(); | ||
const loaders = new Map(); | ||
/** | ||
* 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 | ||
* @param {Array} localeIds Array of locale IDs that this loader can handle | ||
*/ | ||
const registerI18nLoader = (packageName, localeId, loader) => { | ||
// register loader by key | ||
const bundleKey = `${packageName}/${localeId}`; | ||
loaders.set(bundleKey, loader); | ||
// register loader by key | ||
const bundleKey = `${packageName}/${localeId}`; | ||
loaders.set(bundleKey, loader); | ||
}; | ||
const _setI18nBundleData = (packageName, data) => { | ||
bundleData.set(packageName, data); | ||
bundleData.set(packageName, data); | ||
}; | ||
const getI18nBundleData = packageName => { | ||
return bundleData.get(packageName); | ||
const getI18nBundleData = (packageName) => { | ||
return bundleData.get(packageName); | ||
}; | ||
/** | ||
* @public | ||
* @deprecated | ||
*/ | ||
const registerI18nBundle = (_packageName, _bundle) => { | ||
throw new Error("This method has been removed. Use `registerI18nLoader` instead."); | ||
}; | ||
const _hasLoader = (packageName, localeId) => { | ||
const bundleKey = `${packageName}/${localeId}`; | ||
return loaders.has(bundleKey); | ||
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 (!bundlePromises.get(bundleKey)) { | ||
bundlePromises.set(bundleKey, loadMessageBundle(localeId)); | ||
} | ||
return bundlePromises.get(bundleKey); | ||
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 _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); | ||
}; | ||
/** | ||
@@ -74,50 +59,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 language = getLocale().getLanguage(); | ||
const region = getLocale().getRegion(); | ||
let localeId = normalizeLocale(language + (region ? `-${region}` : ``)); | ||
while (localeId !== DEFAULT_LANGUAGE && !_hasLoader(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 (e) { | ||
if (!reportedErrors.has(e.message)) { | ||
reportedErrors.add(e.message); | ||
console.error(e.message); /* eslint-disable-line */ | ||
} | ||
} | ||
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 { | ||
registerI18nLoader, | ||
fetchI18nBundle, | ||
registerI18nBundle, | ||
getI18nBundleData, | ||
}; | ||
export { registerI18nLoader, fetchI18nBundle, getI18nBundleData, }; | ||
//# sourceMappingURL=i18n.js.map |
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 DEFAULT_COLLECTION = "SAP-icons"; | ||
/** | ||
* @deprecated | ||
*/ | ||
const registerIconBundle = async (collectionName, bundleData) => { | ||
throw new Error("This method has been removed. Use `registerIconLoader` instead."); | ||
const registerIconLoader = (collectionName, loader) => { | ||
loaders.set(collectionName, loader); | ||
}; | ||
const registerIconLoader = async (collectionName, loader) => { | ||
loaders.set(collectionName, loader); | ||
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); | ||
}; | ||
const _loadIconCollectionOnce = async collectionName => { | ||
if (!iconCollectionPromises.has(collectionName)) { | ||
const loadIcons = loaders.get(collectionName); | ||
iconCollectionPromises.set(collectionName, loadIcons(collectionName)); | ||
} | ||
return iconCollectionPromises.get(collectionName); | ||
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, | ||
}); | ||
}); | ||
}; | ||
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, | ||
packageName: bundleData.packageName, | ||
}); | ||
}); | ||
}; | ||
// set | ||
const registerIcon = (name, { pathData, ltr, accData, collection, packageName } = {}) => { // eslint-disable-line | ||
if (!collection) { | ||
collection = DEFAULT_COLLECTION; | ||
} | ||
const key = `${collection}/${name}`; | ||
registry.set(key, { | ||
pathData, | ||
ltr, | ||
accData, | ||
packageName, | ||
}); | ||
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, | ||
}); | ||
}; | ||
const _parseName = name => { | ||
// silently support ui5-compatible URIs | ||
if (name.startsWith("sap-icon://")) { | ||
name = name.replace("sap-icon://", ""); | ||
} | ||
let collection; | ||
[name, collection] = name.split("/").reverse(); | ||
collection = collection || DEFAULT_COLLECTION; | ||
// hardcoded alias in case icon explorer is used, resolve `SAP-icons-TNT` to `tnt` | ||
// aliases can be made a feature in the future if more collections need it or more aliases are needed. | ||
if (collection === "SAP-icons-TNT") { | ||
collection = "tnt"; | ||
} | ||
const registryKey = `${collection}/${name}`; | ||
return { name, collection, registryKey }; | ||
/** | ||
* 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 = nameProp => { | ||
const { registryKey } = _parseName(nameProp); | ||
return registry.get(registryKey); | ||
const getIconDataSync = (iconName) => { | ||
const { name, collection } = processName(iconName); | ||
return getRegisteredIconData(collection, name); | ||
}; | ||
const getIconData = async nameProp => { | ||
const { collection, registryKey } = _parseName(nameProp); | ||
let iconData = ICON_NOT_FOUND; | ||
try { | ||
iconData = await _loadIconCollectionOnce(collection); | ||
} catch (e) { | ||
console.error(e.message); /* eslint-disable-line */ | ||
} | ||
if (iconData === ICON_NOT_FOUND) { | ||
return iconData; | ||
} | ||
if (!registry.has(registryKey)) { | ||
// not filled by another await. many getters will await on the same loader, but fill only once | ||
_fillRegistry(iconData); | ||
} | ||
return registry.get(registryKey); | ||
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"); | ||
return Array.from(registry.keys()); | ||
// 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 { | ||
registerIconBundle, | ||
registerIconLoader, | ||
getIconData, | ||
getIconDataSync, | ||
registerIcon, | ||
_getRegisteredNames, | ||
}; | ||
export { registerIconLoader, getIconData, getIconDataSync, getIconAccessibleName, registerIcon, _getRegisteredNames, }; | ||
//# sourceMappingURL=Icons.js.map |
@@ -5,3 +5,2 @@ import { attachLanguageChange } from "../locale/languageChange.js"; | ||
import { getFeature } from "../FeaturesRegistry.js"; | ||
const localeDataMap = new Map(); | ||
@@ -12,141 +11,128 @@ const loaders = new Map(); | ||
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) { | ||
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 _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)) { | ||
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; | ||
// 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; | ||
}; | ||
// internal set data | ||
const setLocaleData = (localeId, content) => { | ||
localeDataMap.set(localeId, content); | ||
localeDataMap.set(localeId, 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 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; | ||
}; | ||
// load bundle over the network once | ||
const _loadCldrOnce = localeId => { | ||
const loadCldr = loaders.get(localeId); | ||
if (!cldrPromises.get(localeId)) { | ||
cldrPromises.set(localeId, loadCldr(localeId)); | ||
} | ||
return cldrPromises.get(localeId); | ||
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) => { | ||
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 (e) { | ||
if (!reportedErrors.has(e.message)) { | ||
reportedErrors.add(e.message); | ||
console.error(e.message); /* eslint-disable-line */ | ||
} | ||
} | ||
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 registerLocaleDataLoader = (localeId, loader) => { | ||
loaders.set(localeId, loader); | ||
loaders.set(localeId, loader); | ||
}; | ||
// register default loader for "en" from ui5 CDN (dev workflow without assets) | ||
registerLocaleDataLoader("en", async runtimeLocaleId => { | ||
return (await fetch(`https://ui5.sap.com/1.60.2/resources/sap/ui/core/cldr/en.json`)).json(); | ||
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()); | ||
const locale = getLocale(); | ||
return fetchCldr(locale.getLanguage(), locale.getRegion(), locale.getScript()); | ||
}); | ||
export { | ||
registerLocaleDataLoader, | ||
fetchCldr, | ||
getLocaleData, | ||
}; | ||
export { registerLocaleDataLoader, fetchCldr, getLocaleData, }; | ||
//# sourceMappingURL=LocaleData.js.map |
import { DEFAULT_THEME } from "../generated/AssetParameters.js"; | ||
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;}"); | ||
* | ||
* @public | ||
* @param packageName - the NPM package for which CSS Vars are registered | ||
* @param themeName - the theme which the CSS Vars implement | ||
* @param style - the style content directly | ||
* @deprecated | ||
*/ | ||
const registerThemeProperties = (_packageName, _themeName, _style) => { | ||
throw new Error("`registerThemeProperties` has been depracated. Use `registerThemePropertiesLoader` instead."); | ||
}; | ||
const registerThemePropertiesLoader = (packageName, themeName, loader) => { | ||
loaders.set(`${packageName}/${themeName}`, loader); | ||
registeredPackages.add(packageName); | ||
registeredThemes.add(themeName); | ||
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 loader = loaders.get(`${packageName}/${themeName}`); | ||
if (!loader) { | ||
// no themes for package | ||
console.error(`Theme [${themeName}] not registered for package [${packageName}]`); /* eslint-disable-line */ | ||
return; | ||
} | ||
let data; | ||
try { | ||
data = await loader(themeName); | ||
} catch (e) { | ||
console.error(packageName, e.message); /* eslint-disable-line */ | ||
return; | ||
} | ||
const themeProps = data._ || data; | ||
themeStyles.set(`${packageName}_${themeName}`, themeProps); | ||
return themeProps; | ||
const registerCustomThemePropertiesLoader = (packageName, themeName, loader) => { | ||
customLoaders.set(`${packageName}/${themeName}`, loader); | ||
}; | ||
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 { | ||
registerThemePropertiesLoader, | ||
registerThemeProperties, | ||
getThemeProperties, | ||
getRegisteredPackages, | ||
isThemeRegistered, | ||
}; | ||
export { registerThemePropertiesLoader, registerCustomThemePropertiesLoader, getThemeProperties, getRegisteredPackages, isThemeRegistered, }; | ||
//# sourceMappingURL=Themes.js.map |
import { registerI18nLoader } from "./asset-registries/i18n.js"; | ||
import { registerLocaleDataLoader } from "./asset-registries/LocaleData.js"; | ||
import { registerThemePropertiesLoader } from "./asset-registries/Themes.js"; | ||
import { registerThemePropertiesLoader, registerCustomThemePropertiesLoader } from "./asset-registries/Themes.js"; | ||
import { registerIconLoader } from "./asset-registries/Icons.js"; | ||
export { | ||
registerI18nLoader, | ||
registerLocaleDataLoader, | ||
registerThemePropertiesLoader, | ||
registerIconLoader, | ||
}; | ||
export { registerI18nLoader, registerLocaleDataLoader, registerThemePropertiesLoader, registerCustomThemePropertiesLoader, registerIconLoader, }; | ||
//# sourceMappingURL=AssetRegistry.js.map |
@@ -0,3 +1,3 @@ | ||
import whenDOMReady from "./util/whenDOMReady.js"; | ||
import EventProvider from "./EventProvider.js"; | ||
import whenDOMReady from "./util/whenDOMReady.js"; | ||
import insertFontFace from "./FontFace.js"; | ||
@@ -7,38 +7,69 @@ import insertSystemCSSVars from "./SystemCSSVars.js"; | ||
import applyTheme from "./theming/applyTheme.js"; | ||
import { registerCurrentRuntime } from "./Runtimes.js"; | ||
import { getFeature } from "./FeaturesRegistry.js"; | ||
import { attachThemeRegistered } from "./theming/ThemeRegistered.js"; | ||
let booted = false; | ||
let bootPromise; | ||
const eventProvider = new EventProvider(); | ||
const isBooted = () => { | ||
return booted; | ||
}; | ||
/** | ||
* Attach a callback that will be executed on boot | ||
* Attaches a callback that will be executed after boot finishes. | ||
* **Note:** If the framework already booted, the callback will be immediately executed. | ||
* @public | ||
* @param listener | ||
* @param { Function } listener | ||
*/ | ||
const attachBoot = listener => { | ||
eventProvider.attachEvent("boot", listener); | ||
const attachBoot = (listener) => { | ||
if (!booted) { | ||
eventProvider.attachEvent("boot", listener); | ||
return; | ||
} | ||
listener(); | ||
}; | ||
const boot = async () => { | ||
if (booted) { | ||
return; | ||
} | ||
const OpenUI5Support = getFeature("OpenUI5Support"); | ||
if (OpenUI5Support) { | ||
await OpenUI5Support.init(); | ||
} | ||
await whenDOMReady(); | ||
await applyTheme(getTheme()); | ||
OpenUI5Support && OpenUI5Support.attachListeners(); | ||
insertFontFace(); | ||
insertSystemCSSVars(); | ||
await eventProvider.fireEventAsync("boot"); | ||
booted = true; | ||
if (bootPromise !== undefined) { | ||
return bootPromise; | ||
} | ||
const bootExecutor = async (resolve) => { | ||
registerCurrentRuntime(); | ||
if (typeof document === "undefined") { | ||
resolve(); | ||
return; | ||
} | ||
attachThemeRegistered(onThemeRegistered); | ||
const openUI5Support = getFeature("OpenUI5Support"); | ||
const isOpenUI5Loaded = openUI5Support ? openUI5Support.isOpenUI5Detected() : false; | ||
const f6Navigation = getFeature("F6Navigation"); | ||
if (openUI5Support) { | ||
await openUI5Support.init(); | ||
} | ||
if (f6Navigation && !isOpenUI5Loaded) { | ||
f6Navigation.init(); | ||
} | ||
await whenDOMReady(); | ||
await applyTheme(getTheme()); | ||
openUI5Support && openUI5Support.attachListeners(); | ||
insertFontFace(); | ||
insertSystemCSSVars(); | ||
resolve(); | ||
booted = true; | ||
await eventProvider.fireEventAsync("boot"); | ||
}; | ||
bootPromise = new Promise(bootExecutor); | ||
return bootPromise; | ||
}; | ||
export { | ||
boot, | ||
attachBoot, | ||
/** | ||
* Callback, executed after theme properties registration | ||
* to apply the newly registered theme. | ||
* @private | ||
* @param { string } theme | ||
*/ | ||
const onThemeRegistered = (theme) => { | ||
const currentTheme = getTheme(); | ||
if (booted && theme === currentTheme) { | ||
applyTheme(currentTheme); | ||
} | ||
}; | ||
export { boot, attachBoot, isBooted, }; | ||
//# sourceMappingURL=Boot.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,68 +0,71 @@ | ||
import { | ||
getLanguage as getConfiguredLanguage, | ||
getFetchDefaultLanguage as getConfiguredFetchDefaultLanguage, | ||
} from "../InitialConfiguration.js"; | ||
import { getLanguage as getConfiguredLanguage, getFetchDefaultLanguage as getConfiguredFetchDefaultLanguage, } from "../InitialConfiguration.js"; | ||
import { fireLanguageChange } from "../locale/languageChange.js"; | ||
import { reRenderAllUI5Elements } from "../Render.js"; | ||
let language; | ||
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; | ||
await fireLanguageChange(newLanguage); | ||
await reRenderAllUI5Elements({ languageAware: true }); | ||
const setLanguage = async (language) => { | ||
if (curLanguage === language) { | ||
return; | ||
} | ||
curLanguage = language; | ||
if (isBooted()) { | ||
await fireLanguageChange(language); | ||
await reRenderAllUI5Elements({ languageAware: true }); | ||
} | ||
}; | ||
/** | ||
* 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. | ||
* <b>Note:</b> By default the language will not be fetched. | ||
* **Note:** By default the language will not be fetched. | ||
* | ||
* @param {Boolean} fetchDefaultLanguage | ||
* @public | ||
* @param {boolean} fetchDefaultLang | ||
*/ | ||
const setFetchDefaultLanguage = fetchDefaultLang => { | ||
fetchDefaultLanguage = fetchDefaultLang; | ||
const setFetchDefaultLanguage = (fetchDefaultLang) => { | ||
fetchDefaultLanguage = fetchDefaultLang; | ||
}; | ||
/** | ||
* Returns if the default language, that is inlined, should be fetched over the network. | ||
* @returns {Boolean} | ||
* @public | ||
* @returns {boolean} | ||
*/ | ||
const getFetchDefaultLanguage = () => { | ||
if (fetchDefaultLanguage === undefined) { | ||
setFetchDefaultLanguage(getConfiguredFetchDefaultLanguage()); | ||
} | ||
return fetchDefaultLanguage; | ||
if (fetchDefaultLanguage === undefined) { | ||
setFetchDefaultLanguage(getConfiguredFetchDefaultLanguage()); | ||
} | ||
return fetchDefaultLanguage; | ||
}; | ||
export { | ||
getLanguage, | ||
setLanguage, | ||
setFetchDefaultLanguage, | ||
getFetchDefaultLanguage, | ||
}; | ||
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,14 +0,6 @@ | ||
import { | ||
isDown, | ||
isUp, | ||
isLeft, | ||
isRight, | ||
isHome, | ||
isEnd, | ||
} from "../Keys.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"; | ||
import { instanceOfUI5Element } from "../UI5Element.js"; | ||
/** | ||
@@ -27,4 +19,4 @@ * The ItemNavigation class manages the calculations to determine the correct "tabindex" for a group of related items inside a root component. | ||
* Each item passed to ItemNavigation via "getItemsCallback" must be: | ||
* - A) either a UI5Element with a "_tabIndex" property | ||
* - B) or an Object with "id" and "_tabIndex" properties which represents a part of the root component's shadow DOM. | ||
* - 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. | ||
@@ -35,7 +27,7 @@ * 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 | ||
* | ||
* Whenever the user navigates with the keyboard, ItemNavigation will modify the "_tabIndex" 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 "_tabIndex" 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 "_tabIndex" property. | ||
* If the items are Objects with "id" and "_tabIndex" 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 "_tabIndex" is changed deeply. | ||
* 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. | ||
* | ||
@@ -50,239 +42,277 @@ * 2) Call the "setCurrentItem" method of ItemNavigation whenever you want to change the current item. | ||
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 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 | ||
* - 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 = {}) { | ||
this._setRootComponent(rootWebComponent); | ||
this._initOptions(options); | ||
} | ||
_setRootComponent(rootWebComponent) { | ||
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.rootWebComponent._onComponentStateFinalized = () => { | ||
this._init(); | ||
}; | ||
} | ||
_initOptions(options) { | ||
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 || []; | ||
} | ||
/** | ||
* 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._tabIndex = (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; | ||
if (isUp(event) && verticalNavigationOn) { | ||
this._handleUp(); | ||
} else if (isDown(event) && verticalNavigationOn) { | ||
this._handleDown(); | ||
} else if (isLeft(event) && horizontalNavigationOn) { | ||
this._handleLeft(); | ||
} else if (isRight(event) && horizontalNavigationOn) { | ||
this._handleRight(); | ||
} else if (isHome(event)) { | ||
this._handleHome(); | ||
} else if (isEnd(event)) { | ||
this._handleEnd(); | ||
} 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 | ||
} | ||
_applyTabIndex() { | ||
const items = this._getItems(); | ||
for (let i = 0; i < items.length; i++) { | ||
items[i]._tabIndex = 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 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}`); | ||
} | ||
/** | ||
* | ||
* @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}"]`); | ||
} | ||
} | ||
} | ||
export default ItemNavigation; | ||
//# sourceMappingURL=ItemNavigation.js.map |
@@ -0,43 +1,42 @@ | ||
import { instanceOfUI5Element } from "../UI5Element.js"; | ||
let resizeObserver; | ||
const observedElements = new Map(); | ||
const getResizeObserver = () => { | ||
if (!resizeObserver) { | ||
resizeObserver = new window.ResizeObserver(entries => { | ||
entries.forEach(entry => { | ||
const callbacks = observedElements.get(entry.target); | ||
callbacks.forEach(callback => callback()); | ||
}); | ||
}); | ||
} | ||
return resizeObserver; | ||
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; | ||
}; | ||
let 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 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]); | ||
}; | ||
let 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); | ||
} | ||
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); | ||
} | ||
}; | ||
/** | ||
@@ -50,58 +49,38 @@ * Allows to register/deregister resize observers for a DOM element | ||
class ResizeHandler { | ||
/** | ||
* @static | ||
* @public | ||
* @param {*} element UI5 Web Component or DOM Element to be observed | ||
* @param {*} callback Callback to be executed | ||
*/ | ||
static register(element, callback) { | ||
if (element.isUI5Element) { | ||
element = element.getDomRef(); | ||
} | ||
if (element instanceof HTMLElement) { | ||
observe(element, callback); | ||
} else { | ||
console.warn("Cannot register ResizeHandler for element", element); // eslint-disable-line | ||
} | ||
} | ||
/** | ||
* @static | ||
* @public | ||
* @param {*} element UI5 Web Component or DOM Element to be unobserved | ||
* @param {*} callback Callback to be removed | ||
*/ | ||
static deregister(element, callback) { | ||
if (element.isUI5Element) { | ||
element = element.getDomRef(); | ||
} | ||
if (element instanceof HTMLElement) { | ||
unobserve(element, callback); | ||
} else { | ||
console.warn("Cannot deregister ResizeHandler for element", element); // eslint-disable-line | ||
} | ||
} | ||
/** | ||
* @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 | ||
} | ||
} | ||
} | ||
/** | ||
* Set a function to be executed whenever a DOM node needs to be observed for size change. | ||
* @public | ||
* @param fn | ||
*/ | ||
const setResizeHandlerObserveFn = fn => { | ||
observe = fn; | ||
}; | ||
/** | ||
* Set a function to be executed whenever a DOM node needs to no longer be observed for size changes | ||
* @public | ||
* @param fn | ||
*/ | ||
const setResizeHandlerUnobserveFn = fn => { | ||
unobserve = fn; | ||
}; | ||
export default ResizeHandler; | ||
export { setResizeHandlerObserveFn, setResizeHandlerUnobserveFn }; | ||
//# sourceMappingURL=ResizeHandler.js.map |
import { supportsTouch } from "../Device.js"; | ||
import EventProvider from "../EventProvider.js"; | ||
import scroll from "../animations/scroll.js"; | ||
const scrollEventName = "scroll"; | ||
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.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 = {}; | ||
// 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) { | ||
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.supportsTouch ? touch.clientX : touch.x; | ||
const y = this.supportsTouch ? touch.clientY : touch.y; | ||
return x >= rect.left && x <= rect.right | ||
&& y >= rect.top && y <= rect.bottom; | ||
} | ||
ontouchstart(event) { | ||
const touch = this.supportsTouch ? event.touches[0] : null; | ||
if (!this.supportsTouch) { | ||
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.supportsTouch ? touch.pageX : event.x; | ||
this._prevDragY = this.supportsTouch ? touch.pageY : event.y; | ||
this._canScroll = this._isTouchInside(this.supportsTouch ? touch : 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, { 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,100 +0,175 @@ | ||
const ua = navigator.userAgent; | ||
const touch = "ontouchstart" in window || navigator.maxTouchPoints > 0; | ||
const ie = /(msie|trident)/i.test(ua); | ||
const chrome = !ie && /(Chrome|CriOS)/.test(ua); | ||
const safari = !ie && !chrome && /(Version|PhantomJS)\/(\d+\.\d+).*Safari/.test(ua); | ||
const webkit = !ie && /webkit/.test(ua); | ||
const windows = navigator.platform.indexOf("Win") !== -1; | ||
const android = !windows && /Android/.test(ua); | ||
const androidPhone = android && /(?=android)(?=.*mobile)/i.test(ua); | ||
const ipad = /ipad/i.test(ua); | ||
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); | ||
}, | ||
}; | ||
let windowsVersion; | ||
let webkitVersion; | ||
let tablet; | ||
const isWindows8OrAbove = () => { | ||
if (!windows) { | ||
return false; | ||
} | ||
if (windowsVersion === undefined) { | ||
const matches = ua.match(/Windows NT (\d+).(\d)/); | ||
windowsVersion = matches ? parseFloat(matches[1]) : 0; | ||
} | ||
return windowsVersion >= 8; | ||
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 isWebkit537OrAbove = () => { | ||
if (!webkit) { | ||
return false; | ||
} | ||
if (webkitVersion === undefined) { | ||
const matches = ua.match(/(webkit)[ /]([\w.]+)/); | ||
webkitVersion = matches ? parseFloat(matches[1]) : 0; | ||
} | ||
return webkitVersion >= 537.10; | ||
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 detectTablet = () => { | ||
if (tablet !== undefined) { | ||
return; | ||
} | ||
if (ipad) { | ||
tablet = true; | ||
return; | ||
} | ||
if (touch) { | ||
if (isWindows8OrAbove()) { | ||
tablet = true; | ||
return; | ||
} | ||
if (chrome && android) { | ||
tablet = !/Mobile Safari\/[.0-9]+/.test(ua); | ||
return; | ||
} | ||
let densityFactor = window.devicePixelRatio ? window.devicePixelRatio : 1; // may be undefined in Windows Phone devices | ||
if (android && isWebkit537OrAbove()) { | ||
densityFactor = 1; | ||
} | ||
tablet = (Math.min(window.screen.width / densityFactor, window.screen.height / densityFactor) >= 600); | ||
return; | ||
} | ||
tablet = (ie && ua.indexOf("Touch") !== -1) || (android && !androidPhone); | ||
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 supportsTouch = () => touch; | ||
const isIE = () => ie; | ||
const isSafari = () => safari; | ||
const supportsTouch = () => internals.touch; | ||
const isIE = () => internals.ie; | ||
const isSafari = () => internals.safari; | ||
const isChrome = () => internals.chrome; | ||
const isFirefox = () => internals.firefox; | ||
const isTablet = () => { | ||
detectTablet(); | ||
return (touch || isWindows8OrAbove()) && tablet; | ||
detectTablet(); | ||
return (internals.touch || isWindows8OrAbove()) && tablet; | ||
}; | ||
const isPhone = () => { | ||
detectTablet(); | ||
return touch && !tablet; | ||
detectTablet(); | ||
return internals.touch && !tablet; | ||
}; | ||
const isDesktop = () => { | ||
return (!isTablet() && !isPhone()) || isWindows8OrAbove(); | ||
if (isSSR) { | ||
return false; | ||
} | ||
return (!isTablet() && !isPhone()) || isWindows8OrAbove(); | ||
}; | ||
export { | ||
supportsTouch, | ||
isIE, | ||
isSafari, | ||
isPhone, | ||
isTablet, | ||
isDesktop, | ||
const isCombi = () => { | ||
return isTablet() && isDesktop(); | ||
}; | ||
const isIOS = () => { | ||
return internals.iOS; | ||
}; | ||
const isMac = () => { | ||
return internals.macOS; | ||
}; | ||
const isAndroid = () => { | ||
return internals.android || internals.androidPhone; | ||
}; | ||
export { supportsTouch, isIE, isSafari, isChrome, isFirefox, isPhone, isTablet, isDesktop, isCombi, isIOS, isAndroid, isMac, }; | ||
//# sourceMappingURL=Device.js.map |
@@ -1,40 +0,3 @@ | ||
const observers = new WeakMap(); // We want just one observer per node, store them here -> DOM nodes are keys | ||
const observers = new WeakMap(); | ||
/** | ||
* Default implementation with MutationObserver for browsers with native support | ||
*/ | ||
let _createObserver = (node, callback, options) => { | ||
const observer = new MutationObserver(callback); | ||
observer.observe(node, options); | ||
return observer; | ||
}; | ||
/** | ||
* Default implementation with MutationObserver for browsers with native support | ||
*/ | ||
let _destroyObserver = observer => { | ||
observer.disconnect(); | ||
}; | ||
/** | ||
* Allows to create an alternative DOM observer implementation | ||
* @param createFn | ||
*/ | ||
const setCreateObserverCallback = createFn => { | ||
if (typeof createFn === "function") { | ||
_createObserver = createFn; | ||
} | ||
}; | ||
/** | ||
* Allows to create an alternative DOM observer implementation | ||
* @param destroyFn | ||
*/ | ||
const setDestroyObserverCallback = destroyFn => { | ||
if (typeof destroyFn === "function") { | ||
_destroyObserver = destroyFn; | ||
} | ||
}; | ||
/** | ||
* @param node | ||
@@ -45,22 +8,17 @@ * @param callback | ||
const observeDOMNode = (node, callback, options) => { | ||
const observer = _createObserver(node, callback, options); | ||
observers.set(node, observer); | ||
const observer = new MutationObserver(callback); | ||
observers.set(node, observer); | ||
observer.observe(node, options); | ||
}; | ||
/** | ||
* @param node | ||
*/ | ||
const unobserveDOMNode = node => { | ||
const observer = observers.get(node); | ||
if (observer) { | ||
_destroyObserver(observer); | ||
observers.delete(node); | ||
} | ||
const unobserveDOMNode = (node) => { | ||
const observer = observers.get(node); | ||
if (observer) { | ||
observer.disconnect(); | ||
observers.delete(node); | ||
} | ||
}; | ||
export { | ||
setCreateObserverCallback, | ||
setDestroyObserverCallback, | ||
observeDOMNode, | ||
unobserveDOMNode, | ||
}; | ||
export { observeDOMNode, unobserveDOMNode, }; | ||
//# sourceMappingURL=DOMObserver.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. | ||
* | ||
* @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 | ||
}); | ||
} | ||
/** | ||
* 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[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"; | ||
import { getCurrentZIndex } from "../util/PopupUtils.js"; | ||
const getCore = () => { | ||
const sap = window.sap; | ||
const core = sap && sap.ui && typeof sap.ui.getCore === "function" && sap.ui.getCore(); | ||
return core; | ||
}; | ||
const isLoaded = () => { | ||
return !!getCore(); | ||
}; | ||
const init = () => { | ||
const core = getCore(); | ||
if (!core) { | ||
return Promise.resolve(); | ||
} | ||
return new Promise(resolve => { | ||
core.attachInit(() => { | ||
window.sap.ui.require(["sap/ui/core/LocaleData", "sap/ui/core/Popup"], (LocaleData, Popup) => { | ||
Popup.setInitialZIndex(getCurrentZIndex()); | ||
resolve(); | ||
}); | ||
}); | ||
}); | ||
}; | ||
const getConfigurationSettingsObject = () => { | ||
const core = getCore(); | ||
if (!core) { | ||
return; | ||
} | ||
const config = core.getConfiguration(); | ||
const LocaleData = window.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 = () => { | ||
const core = getCore(); | ||
if (!core) { | ||
return; | ||
} | ||
const config = core.getConfiguration(); | ||
const LocaleData = window.sap.ui.require("sap/ui/core/LocaleData"); | ||
return LocaleData.getInstance(config.getLocale())._get(); | ||
}; | ||
const listenForThemeChange = () => { | ||
const core = getCore(); | ||
const config = core.getConfiguration(); | ||
core.attachThemeChanged(async () => { | ||
await setTheme(config.getTheme()); | ||
}); | ||
}; | ||
const attachListeners = () => { | ||
const core = getCore(); | ||
if (!core) { | ||
return; | ||
} | ||
listenForThemeChange(); | ||
}; | ||
const cssVariablesLoaded = () => { | ||
const core = getCore(); | ||
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 getNextZIndex = () => { | ||
const core = getCore(); | ||
if (!core) { | ||
return; | ||
} | ||
const Popup = window.sap.ui.require("sap/ui/core/Popup"); | ||
return Popup.getNextZIndex(); | ||
}; | ||
const setInitialZIndex = () => { | ||
const core = getCore(); | ||
if (!core) { | ||
return; | ||
} | ||
const Popup = window.sap.ui.require("sap/ui/core/Popup"); | ||
Popup.setInitialZIndex(getCurrentZIndex()); | ||
}; | ||
const OpenUI5Support = { | ||
isLoaded, | ||
init, | ||
getConfigurationSettingsObject, | ||
getLocaleDataObject, | ||
attachListeners, | ||
cssVariablesLoaded, | ||
getNextZIndex, | ||
setInitialZIndex, | ||
}; | ||
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 |
@@ -0,11 +1,45 @@ | ||
import EventProvider from "./EventProvider.js"; | ||
class ComponentFeature { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-empty-function | ||
constructor(...args) { } | ||
} | ||
const features = new Map(); | ||
const componentFeatures = new Map(); | ||
const subscribers = new Map(); | ||
const EVENT_NAME = "componentFeatureLoad"; | ||
const eventProvider = new EventProvider(); | ||
const featureLoadEventName = (name) => `${EVENT_NAME}_${name}`; | ||
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 }; | ||
const registerComponentFeature = async (name, feature) => { | ||
await Promise.all(feature.dependencies?.map(dep => dep.define()) || []); | ||
await feature.define?.(); | ||
componentFeatures.set(name, feature); | ||
notifyForFeatureLoad(name); | ||
}; | ||
const getComponentFeature = (name) => { | ||
return componentFeatures.get(name); | ||
}; | ||
const subscribeForFeatureLoad = (name, klass, callback) => { | ||
const subscriber = subscribers.get(klass); | ||
const isSubscribed = subscriber?.includes(name); | ||
if (isSubscribed) { | ||
return; | ||
} | ||
if (!subscriber) { | ||
subscribers.set(klass, [name]); | ||
} | ||
else { | ||
subscriber.push(name); | ||
} | ||
eventProvider.attachEvent(featureLoadEventName(name), callback); | ||
}; | ||
const notifyForFeatureLoad = (name) => { | ||
eventProvider.fireEvent(featureLoadEventName(name), undefined); | ||
}; | ||
export { registerFeature, getFeature, registerComponentFeature, getComponentFeature, subscribeForFeatureLoad, ComponentFeature, }; | ||
//# sourceMappingURL=FeaturesRegistry.js.map |
@@ -1,118 +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"); | ||
} | ||
`; | ||
/** | ||
* Some diacritics are supported by the 72 font: | ||
* * Grave | ||
* * Acute | ||
* * Circumflex | ||
* * Tilde | ||
* | ||
* However, the following diacritics and the combination of multiple diacritics (including the supported ones) are not supported: | ||
* * Breve | ||
* * Horn | ||
* * Dot below | ||
* * Hook above | ||
* | ||
* | ||
* Override for the characters that aren't covered by the '72' font to other system fonts | ||
* | ||
* U+0102-0103: A and a with Breve | ||
* U+01A0-01A1: O and o with Horn | ||
* U+01AF-01B0: U and u with Horn | ||
* U+1EA0-1EB7: A and a with diacritics that are not supported by the font and combination of multiple diacritics | ||
* U+1EB8-1EC7: E and e with diacritics that are not supported by the font and combination of multiple diacritics | ||
* U+1EC8-1ECB: I and i with diacritics that are not supported by the font and combination of multiple diacritics | ||
* U+1ECC-1EE3: O and o with diacritics that are not supported by the font and combination of multiple diacritics | ||
* U+1EE4-1EF1: U and u with diacritics that are not supported by the font and combination of multiple diacritics | ||
* U+1EF4-1EF7: Y and y with diacritics that are not supported by the font and combination of multiple diacritics | ||
* | ||
*/ | ||
const overrideFontFaceCSS = ` | ||
@font-face { | ||
font-family: '72override'; | ||
unicode-range: U+0102-0103, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EB7, U+1EB8-1EC7, U+1EC8-1ECB, U+1ECC-1EE3, U+1EE4-1EF1, U+1EF4-1EF7; | ||
src: local('Arial'), local('Helvetica'), local('sans-serif'); | ||
} | ||
`; | ||
import fontFaceCSS from "./generated/css/FontFace.css.js"; | ||
import overrideFontFaceCSS from "./generated/css/OverrideFontFace.css.js"; | ||
const insertFontFace = () => { | ||
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.isLoaded()) { | ||
insertMainFontFace(); | ||
} | ||
// Always set the override font - OpenUI5 in CSS Vars mode does not set it, unlike the main font | ||
insertOverrideFontFace(); | ||
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 (!document.querySelector(`head>style[data-ui5-font-face]`)) { | ||
createStyleInHead(fontFaceCSS, { "data-ui5-font-face": "" }); | ||
} | ||
if (!hasStyle("data-ui5-font-face")) { | ||
createStyle(fontFaceCSS, "data-ui5-font-face"); | ||
} | ||
}; | ||
const insertOverrideFontFace = () => { | ||
if (!document.querySelector(`head>style[data-ui5-font-face-override]`)) { | ||
createStyleInHead(overrideFontFaceCSS, { "data-ui5-font-face-override": "" }); | ||
} | ||
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","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","ms","nl","no","pl","pt_PT","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 |
import { registerI18nLoader, fetchI18nBundle, getI18nBundleData } from "./asset-registries/i18n.js"; | ||
import formatMessage from "./util/formatMessage.js"; | ||
const I18nBundleInstances = new Map(); | ||
let customGetI18nBundle; | ||
/** | ||
@@ -11,46 +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); | ||
if (bundle && !bundle[textObj.key]) { | ||
console.warn(`Key ${textObj.key} not found in the i18n bundle, the default text will be used`); // eslint-disable-line | ||
} | ||
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 { | ||
registerI18nLoader, | ||
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: {}, | ||
fetchDefaultLanguage: false, | ||
assetsPath: "", | ||
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; | ||
}; | ||
/** | ||
@@ -46,105 +43,116 @@ * Returns if the default language, that is inlined at build time, | ||
const getFetchDefaultLanguage = () => { | ||
initConfiguration(); | ||
return initialConfig.fetchDefaultLanguage; | ||
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 getAssetsPath = () => { | ||
initConfiguration(); | ||
return initialConfig.assetsPath; | ||
}; | ||
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, | ||
getFetchDefaultLanguage, | ||
getNoConflict, | ||
getCalendarType, | ||
getFormatSettings, | ||
getAssetsPath, | ||
}; | ||
export { getAnimationMode, getTheme, getThemeRoot, getLanguage, getFetchDefaultLanguage, getNoConflict, getCalendarType, getSecondaryCalendarType, getTimezone, getFormatSettings, }; | ||
//# sourceMappingURL=InitialConfiguration.js.map |
402
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 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 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 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 isEndCtrl = event => (event.key ? event.key === "End" : event.keyCode === KeyCodes.END) && checkModifierKeys(event, true, false, false); | ||
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 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 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 isF4Shift = event => (event.key ? event.key === "F4" : event.keyCode === KeyCodes.F4) && checkModifierKeys(event, false, false, true); | ||
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, | ||
isEnterShift, | ||
isSpace, | ||
isSpaceShift, | ||
isLeft, | ||
isRight, | ||
isUp, | ||
isDown, | ||
isLeftCtrl, | ||
isRightCtrl, | ||
isUpCtrl, | ||
isDownCtrl, | ||
isUpShift, | ||
isDownShift, | ||
isUpShiftCtrl, | ||
isDownShiftCtrl, | ||
isHome, | ||
isEnd, | ||
isPlus, | ||
isMinus, | ||
isHomeCtrl, | ||
isEndCtrl, | ||
isEscape, | ||
isTabNext, | ||
isTabPrevious, | ||
isBackSpace, | ||
isDelete, | ||
isShow, | ||
isF4, | ||
isF4Shift, | ||
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 |
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 = async () => { | ||
const listenersResults = fireDirectionChange(); | ||
await Promise.all(listenersResults); | ||
await reRenderAllUI5Elements({ rtlAware: true }); | ||
const listenersResults = fireDirectionChange(); | ||
await Promise.all(listenersResults); | ||
await reRenderAllUI5Elements({ rtlAware: true }); | ||
}; | ||
export default applyDirection; | ||
//# sourceMappingURL=applyDirection.js.map |
import EventProvider from "../EventProvider.js"; | ||
const eventProvider = new EventProvider(); | ||
const DIR_CHANGE = "directionChange"; | ||
/** | ||
@@ -11,6 +9,5 @@ * Attach a callback that will be executed whenever the application calls the "applyDirection" function | ||
*/ | ||
const attachDirectionChange = listener => { | ||
eventProvider.attachEvent(DIR_CHANGE, listener); | ||
const attachDirectionChange = (listener) => { | ||
eventProvider.attachEvent(DIR_CHANGE, listener); | ||
}; | ||
/** | ||
@@ -21,14 +18,9 @@ * Detach a callback that was passed with "attachDirectionChange" | ||
*/ | ||
const detachDirectionChange = listener => { | ||
eventProvider.detachEvent(DIR_CHANGE, listener); | ||
const detachDirectionChange = (listener) => { | ||
eventProvider.detachEvent(DIR_CHANGE, listener); | ||
}; | ||
const fireDirectionChange = () => { | ||
return eventProvider.fireEvent(DIR_CHANGE); | ||
return eventProvider.fireEvent(DIR_CHANGE, undefined); | ||
}; | ||
export { | ||
attachDirectionChange, | ||
detachDirectionChange, | ||
fireDirectionChange, | ||
}; | ||
export { attachDirectionChange, detachDirectionChange, fireDirectionChange, }; | ||
//# sourceMappingURL=directionChange.js.map |
@@ -1,28 +0,8 @@ | ||
import { getRTL } from "../config/RTL.js"; | ||
const GLOBAL_DIR_CSS_VAR = "--_ui5_dir"; | ||
const getEffectiveDir = element => { | ||
const doc = window.document; | ||
const dirValues = ["ltr", "rtl"]; // exclude "auto" and "" from all calculations | ||
const locallyAppliedDir = getComputedStyle(element).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(element.dir)) { | ||
return element.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; | ||
const getEffectiveDir = (element) => { | ||
if (element.matches(":dir(rtl)")) { | ||
return "rtl"; | ||
} | ||
return "ltr"; | ||
}; | ||
export default getEffectiveDir; | ||
//# sourceMappingURL=getEffectiveDir.js.map |
import detectNavigatorLanguage from "../util/detectNavigatorLanguage.js"; | ||
import { getLanguage as getConfigLanguage } from "../config/Language.js"; | ||
import Locale from "./Locale.js"; | ||
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 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 | ||
} | ||
const convertToLocaleOrNull = (lang) => { | ||
try { | ||
if (lang && typeof lang === "string") { | ||
return getLocaleInstance(lang); | ||
} | ||
} | ||
catch (e) { | ||
// ignore | ||
} | ||
return new Locale(DEFAULT_LOCALE); | ||
}; | ||
/** | ||
@@ -29,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 getLocaleInstance(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.fireEventAsync(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,109 +0,88 @@ | ||
const querySets = {}; | ||
const mediaRanges = new Map(); | ||
const DEAFULT_RANGE_SET = new Map(); | ||
DEAFULT_RANGE_SET.set("S", [0, 599]); | ||
DEAFULT_RANGE_SET.set("M", [600, 1023]); | ||
DEAFULT_RANGE_SET.set("L", [1024, 1439]); | ||
DEAFULT_RANGE_SET.set("XL", [1440, Infinity]); | ||
/** | ||
* Enumeration containing the names and settings of predefined screen width media query range sets. | ||
* | ||
* @public | ||
*/ | ||
var RANGESETS; | ||
(function (RANGESETS) { | ||
/** | ||
* A 4-step range set (S-M-L-XL). | ||
* | ||
* The ranges of this set are: | ||
* | ||
* - `"S"`: For screens smaller than 600 pixels. | ||
* - `"M"`: For screens greater than or equal to 600 pixels and smaller than 1024 pixels. | ||
* - `"L"`: For screens greater than or equal to 1024 pixels and smaller than 1440 pixels. | ||
* - `"XL"`: For screens greater than or equal to 1440 pixels. | ||
* | ||
* | ||
* @public | ||
*/ | ||
RANGESETS["RANGE_4STEPS"] = "4Step"; | ||
})(RANGESETS || (RANGESETS = {})); | ||
/** | ||
* Initializes a screen width media query range set. | ||
* | ||
* This initialization step makes the range set ready to be used for one of the other functions in namespace <code>MediaRange</code>. | ||
* This initialization step makes the range set ready to be used for one of the other functions in namespace `MediaRange`. | ||
* | ||
* A range set can be defined as shown in the following example: | ||
* <pre> | ||
* ``` | ||
* MediaRange.initRangeSet("MyRangeSet", [200, 400], ["Small", "Medium", "Large"]); | ||
* </pre> | ||
* ``` | ||
* This example defines the following named ranges: | ||
* <ul> | ||
* <li><code>"Small"</code>: For screens smaller than 200 pixels.</li> | ||
* <li><code>"Medium"</code>: For screens greater than or equal to 200 pixels and smaller than 400 pixels.</li> | ||
* <li><code>"Large"</code>: For screens greater than or equal to 400 pixels.</li> | ||
* </ul> | ||
* | ||
* @param {string} name The name of the range set to be initialized. | ||
* The name must be a valid id and consist only of letters and numeric digits. | ||
* - `"Small"`: For screens smaller than 200 pixels. | ||
* - `"Medium"`: For screens greater than or equal to 200 pixels and smaller than 400 pixels. | ||
* - `"Large"`: For screens greater than or equal to 400 pixels. | ||
* | ||
* @param {int[]} [borders] The range borders | ||
* | ||
* @param {string[]} [names] The names of the ranges. The names must be a valid id and consist only of letters and digits. | ||
* | ||
* @name MediaRange.initRangeSet | ||
* @function | ||
* @public | ||
* @param name The name of the range set to be initialized. | ||
* The name must be a valid id and consist only of letters and numeric digits. | ||
* @param range The given range set. | ||
*/ | ||
const _initRangeSet = (name, borders, names) => { | ||
querySets[name] = { | ||
borders, | ||
names, | ||
}; | ||
const initRangeSet = (name, range) => { | ||
mediaRanges.set(name, range); | ||
}; | ||
/** | ||
* Returns information about the current active range of the range set with the given name. | ||
* | ||
* If the optional parameter <code>width</code> is given, the active range will be determined for that width, | ||
* If the optional parameter `width` is given, the active range will be determined for that width, | ||
* otherwise it is determined for the current window size. | ||
* | ||
* @param {string} name The name of the range set. The range set must be initialized beforehand ({@link MediaRange.initRangeSet}) | ||
* @param {int} [width] An optional width, based on which the range should be determined; | ||
* If <code>width</code> is not provided, the window size will be used. | ||
* @returns {string} The name of the current active interval of the range set. | ||
* | ||
* @name MediaRange.getCurrentRange | ||
* @function | ||
* @param name The name of the range set. The range set must be initialized beforehand ({@link MediaRange.initRangeSet}) | ||
* @param [width] An optional width, based on which the range should be determined; | ||
* If `width` is not provided, the window size will be used. | ||
* @returns The name of the current active interval of the range set. | ||
* @public | ||
*/ | ||
const _getCurrentRange = (name, width = window.innerWidth) => { | ||
const querySet = querySets[name]; | ||
let i = 0; | ||
if (!querySet) { | ||
return null; | ||
} | ||
for (; i < querySet.borders.length; i++) { | ||
if (width < querySet.borders[i]) { | ||
return querySet.names[i]; | ||
} | ||
} | ||
return querySet.names[i]; | ||
const getCurrentRange = (name, width = window.innerWidth) => { | ||
let rangeSet = mediaRanges.get(name); | ||
if (!rangeSet) { | ||
rangeSet = mediaRanges.get(RANGESETS.RANGE_4STEPS); | ||
} | ||
let currentRangeName; | ||
const effectiveWidth = Math.floor(width); | ||
rangeSet.forEach((value, key) => { | ||
if (effectiveWidth >= value[0] && effectiveWidth <= value[1]) { | ||
currentRangeName = key; | ||
} | ||
}); | ||
return currentRangeName || [...rangeSet.keys()][0]; | ||
}; | ||
/** | ||
* Enumeration containing the names and settings of predefined screen width media query range sets. | ||
* | ||
* @namespace | ||
* @name MediaRange.RANGESETS | ||
* @public | ||
*/ | ||
const RANGESETS = { | ||
/** | ||
* A 4-step range set (S-M-L-XL). | ||
* | ||
* The ranges of this set are: | ||
* <ul> | ||
* <li><code>"S"</code>: For screens smaller than 600 pixels.</li> | ||
* <li><code>"M"</code>: For screens greater than or equal to 600 pixels and smaller than 1024 pixels.</li> | ||
* <li><code>"L"</code>: For screens greater than or equal to 1024 pixels and smaller than 1440 pixels.</li> | ||
* <li><code>"XL"</code>: For screens greater than or equal to 1440 pixels.</li> | ||
* </ul> | ||
* | ||
* @name MediaRange.RANGESETS.RANGE_4STEPS | ||
* @public | ||
*/ | ||
RANGE_4STEPS: "4Step", | ||
}; | ||
/** | ||
* API for screen width changes. | ||
* | ||
* @namespace | ||
* @name MediaRange | ||
*/ | ||
const MediaRange = { | ||
RANGESETS, | ||
initRangeSet: _initRangeSet, | ||
getCurrentRange: _getCurrentRange, | ||
RANGESETS, | ||
initRangeSet, | ||
getCurrentRange, | ||
}; | ||
MediaRange.initRangeSet(MediaRange.RANGESETS.RANGE_4STEPS, [600, 1024, 1440], ["S", "M", "L", "XL"]); | ||
MediaRange.initRangeSet(MediaRange.RANGESETS.RANGE_4STEPS, DEAFULT_RANGE_SET); | ||
export default MediaRange; | ||
//# sourceMappingURL=MediaRange.js.map |
@@ -1,8 +0,7 @@ | ||
const flatstr = typeof chrome === "object" || typeof v8 === "object" ? (s, iConcatOps) => { | ||
if (iConcatOps > 2 && 40 * iConcatOps > s.length) { | ||
Number(s); | ||
} | ||
return s; | ||
} : s => s; | ||
const flatstr = typeof window.chrome === "object" || typeof window.v8 === "object" ? (s, iConcatOps) => { | ||
if (iConcatOps > 2 && 40 * iConcatOps > s.length) { | ||
Number(s); | ||
} | ||
return s; | ||
} : (s) => s; | ||
const rLines = /(?:\r\n|\r|\n|^)[ \t\f]*/; | ||
@@ -12,8 +11,7 @@ const rEscapesOrSeparator = /(\\u[0-9a-fA-F]{0,4})|(\\.)|(\\$)|([ \t\f]*[ \t\f:=][ \t\f]*)/g; | ||
const mEscapes = { | ||
"\\f": "\f", | ||
"\\n": "\n", | ||
"\\r": "\r", | ||
"\\t": "\t", | ||
"\\f": "\f", | ||
"\\n": "\n", | ||
"\\r": "\r", | ||
"\\t": "\t", | ||
}; | ||
/** | ||
@@ -25,73 +23,65 @@ * Parses a .properties format | ||
*/ | ||
const parseProperties = sText => { | ||
const properties = {}, | ||
aLines = sText.split(rLines); | ||
let sLine, | ||
rMatcher, | ||
sKey, | ||
sValue, | ||
i, | ||
m, | ||
iLastIndex, | ||
iConcatOps; | ||
const append = s => { | ||
if (sValue) { | ||
sValue = `${sValue}${s}`; | ||
iConcatOps++; | ||
} else { | ||
sValue = s; | ||
iConcatOps = 0; | ||
} | ||
}; | ||
for (i = 0; i < aLines.length; i++) { | ||
sLine = aLines[i]; | ||
const skipLine = sLine === "" || sLine.charAt(0) === "#" || sLine.charAt(0) === "!"; | ||
if (!skipLine) { | ||
rMatcher = rEscapesOrSeparator; | ||
iLastIndex = 0; | ||
rMatcher.lastIndex = iLastIndex; | ||
sKey = null; | ||
sValue = ""; | ||
m = rMatcher.exec(sLine); | ||
while (m !== null) { | ||
if (iLastIndex < m.index) { | ||
append(sLine.slice(iLastIndex, m.index)); | ||
} | ||
iLastIndex = rMatcher.lastIndex; | ||
if (m[1]) { | ||
if (m[1].length !== 6) { | ||
throw new Error(`Incomplete Unicode Escape '${m[1]}'`); | ||
} | ||
append(String.fromCharCode(parseInt(m[1].slice(2), 16))); | ||
} else if (m[2]) { | ||
append(mEscapes[m[2]] || m[2].slice(1)); | ||
} else if (m[3]) { | ||
sLine = aLines[++i]; | ||
iLastIndex = 0; | ||
rMatcher.lastIndex = iLastIndex; | ||
} else if (m[4]) { | ||
sKey = sValue; | ||
sValue = ""; | ||
rMatcher = rEscapes; | ||
rMatcher.lastIndex = iLastIndex; | ||
} | ||
m = rMatcher.exec(sLine); | ||
} | ||
if (iLastIndex < sLine.length) { | ||
append(sLine.slice(iLastIndex)); | ||
} | ||
if (sKey == null) { | ||
sKey = sValue; | ||
sValue = ""; | ||
} | ||
properties[sKey] = flatstr(sValue, sValue ? iConcatOps : 0); | ||
} | ||
} | ||
return properties; | ||
const parseProperties = (sText) => { | ||
const properties = {}, aLines = sText.split(rLines); | ||
let sLine, rMatcher, sKey, sValue, i, m, iLastIndex, iConcatOps; | ||
const append = (s) => { | ||
if (sValue) { | ||
sValue = `${sValue}${s}`; | ||
iConcatOps++; | ||
} | ||
else { | ||
sValue = s; | ||
iConcatOps = 0; | ||
} | ||
}; | ||
for (i = 0; i < aLines.length; i++) { | ||
sLine = aLines[i]; | ||
const skipLine = sLine === "" || sLine.charAt(0) === "#" || sLine.charAt(0) === "!"; | ||
if (!skipLine) { | ||
rMatcher = rEscapesOrSeparator; | ||
iLastIndex = 0; | ||
rMatcher.lastIndex = iLastIndex; | ||
sKey = null; | ||
sValue = ""; | ||
m = rMatcher.exec(sLine); | ||
while (m !== null) { | ||
if (iLastIndex < m.index) { | ||
append(sLine.slice(iLastIndex, m.index)); | ||
} | ||
iLastIndex = rMatcher.lastIndex; | ||
if (m[1]) { | ||
if (m[1].length !== 6) { | ||
throw new Error(`Incomplete Unicode Escape '${m[1]}'`); | ||
} | ||
append(String.fromCharCode(parseInt(m[1].slice(2), 16))); | ||
} | ||
else if (m[2]) { | ||
append(mEscapes[m[2]] || m[2].slice(1)); | ||
} | ||
else if (m[3]) { | ||
sLine = aLines[++i]; | ||
iLastIndex = 0; | ||
rMatcher.lastIndex = iLastIndex; | ||
} | ||
else if (m[4]) { | ||
sKey = sValue; | ||
sValue = ""; | ||
rMatcher = rEscapes; | ||
rMatcher.lastIndex = iLastIndex; | ||
} | ||
m = rMatcher.exec(sLine); | ||
} | ||
if (iLastIndex < sLine.length) { | ||
append(sLine.slice(iLastIndex)); | ||
} | ||
if (sKey == null) { | ||
sKey = sValue; | ||
sValue = ""; | ||
} | ||
properties[sKey] = flatstr(sValue, sValue ? iConcatOps : 0); | ||
} | ||
} | ||
return properties; | ||
}; | ||
export default parseProperties; | ||
//# sourceMappingURL=PropertiesFileFormat.js.map |
@@ -5,15 +5,8 @@ import EventProvider from "./EventProvider.js"; | ||
import { isRtlAware } from "./locale/RTLAwareRegistry.js"; | ||
const registeredElements = new Set(); | ||
const eventProvider = new EventProvider(); | ||
const invalidatedWebComponents = new RenderQueue(); // Queue for invalidated web components | ||
let renderTaskPromise, | ||
renderTaskPromiseResolve; | ||
let renderTaskPromise, renderTaskPromiseResolve; | ||
let mutationObserverTimer; | ||
let queuePromise; | ||
/** | ||
@@ -25,10 +18,8 @@ * Schedules a render task (if not already scheduled) to render the component | ||
*/ | ||
const renderDeferred = async webComponent => { | ||
// Enqueue the web component | ||
invalidatedWebComponents.add(webComponent); | ||
// Schedule a rendering task | ||
await scheduleRenderTask(); | ||
const renderDeferred = async (webComponent) => { | ||
// Enqueue the web component | ||
invalidatedWebComponents.add(webComponent); | ||
// Schedule a rendering task | ||
await scheduleRenderTask(); | ||
}; | ||
/** | ||
@@ -39,8 +30,7 @@ * Renders a component synchronously and adds it to the registry of rendered components | ||
*/ | ||
const renderImmediately = webComponent => { | ||
eventProvider.fireEvent("beforeComponentRender", webComponent); | ||
registeredElements.add(webComponent); | ||
webComponent._render(); | ||
const renderImmediately = (webComponent) => { | ||
eventProvider.fireEvent("beforeComponentRender", webComponent); | ||
registeredElements.add(webComponent); | ||
webComponent._render(); | ||
}; | ||
/** | ||
@@ -51,7 +41,6 @@ * Cancels the rendering of a component, if awaiting to be rendered, and removes it from the registry of rendered components | ||
*/ | ||
const cancelRender = webComponent => { | ||
invalidatedWebComponents.remove(webComponent); | ||
registeredElements.delete(webComponent); | ||
const cancelRender = (webComponent) => { | ||
invalidatedWebComponents.remove(webComponent); | ||
registeredElements.delete(webComponent); | ||
}; | ||
/** | ||
@@ -61,31 +50,26 @@ * Schedules a rendering task, if not scheduled already | ||
const scheduleRenderTask = async () => { | ||
if (!queuePromise) { | ||
queuePromise = new Promise(resolve => { | ||
window.requestAnimationFrame(() => { | ||
// Render all components in the queue | ||
// console.log(`--------------------RENDER TASK START------------------------------`); // eslint-disable-line | ||
invalidatedWebComponents.process(renderImmediately); | ||
// console.log(`--------------------RENDER TASK END------------------------------`); // eslint-disable-line | ||
// Resolve the promise so that callers of renderDeferred can continue | ||
queuePromise = null; | ||
resolve(); | ||
// Wait for Mutation observer before the render task is considered finished | ||
if (!mutationObserverTimer) { | ||
mutationObserverTimer = setTimeout(() => { | ||
mutationObserverTimer = undefined; | ||
if (invalidatedWebComponents.isEmpty()) { | ||
_resolveTaskPromise(); | ||
} | ||
}, 200); | ||
} | ||
}); | ||
}); | ||
} | ||
await queuePromise; | ||
if (!queuePromise) { | ||
queuePromise = new Promise(resolve => { | ||
window.requestAnimationFrame(() => { | ||
// Render all components in the queue | ||
// console.log(`--------------------RENDER TASK START------------------------------`); // eslint-disable-line | ||
invalidatedWebComponents.process(renderImmediately); | ||
// console.log(`--------------------RENDER TASK END------------------------------`); // eslint-disable-line | ||
// Resolve the promise so that callers of renderDeferred can continue | ||
queuePromise = null; | ||
resolve(); | ||
// Wait for Mutation observer before the render task is considered finished | ||
if (!mutationObserverTimer) { | ||
mutationObserverTimer = setTimeout(() => { | ||
mutationObserverTimer = undefined; | ||
if (invalidatedWebComponents.isEmpty()) { | ||
_resolveTaskPromise(); | ||
} | ||
}, 200); | ||
} | ||
}); | ||
}); | ||
} | ||
await queuePromise; | ||
}; | ||
/** | ||
@@ -95,42 +79,35 @@ * return a promise that will be resolved once all invalidated web components are rendered | ||
const whenDOMUpdated = () => { | ||
if (renderTaskPromise) { | ||
return renderTaskPromise; | ||
} | ||
renderTaskPromise = new Promise(resolve => { | ||
renderTaskPromiseResolve = resolve; | ||
window.requestAnimationFrame(() => { | ||
if (invalidatedWebComponents.isEmpty()) { | ||
renderTaskPromise = undefined; | ||
resolve(); | ||
} | ||
}); | ||
}); | ||
return renderTaskPromise; | ||
if (renderTaskPromise) { | ||
return renderTaskPromise; | ||
} | ||
renderTaskPromise = new Promise(resolve => { | ||
renderTaskPromiseResolve = resolve; | ||
window.requestAnimationFrame(() => { | ||
if (invalidatedWebComponents.isEmpty()) { | ||
renderTaskPromise = undefined; | ||
resolve(); | ||
} | ||
}); | ||
}); | ||
return renderTaskPromise; | ||
}; | ||
const whenAllCustomElementsAreDefined = () => { | ||
const definedPromises = getAllRegisteredTags().map(tag => customElements.whenDefined(tag)); | ||
return Promise.all(definedPromises); | ||
const definedPromises = getAllRegisteredTags().map(tag => customElements.whenDefined(tag)); | ||
return Promise.all(definedPromises); | ||
}; | ||
const renderFinished = async () => { | ||
await whenAllCustomElementsAreDefined(); | ||
await whenDOMUpdated(); | ||
await whenAllCustomElementsAreDefined(); | ||
await whenDOMUpdated(); | ||
}; | ||
const _resolveTaskPromise = () => { | ||
if (!invalidatedWebComponents.isEmpty()) { | ||
// More updates are pending. Resolve will be called again | ||
return; | ||
} | ||
if (renderTaskPromiseResolve) { | ||
renderTaskPromiseResolve(); | ||
renderTaskPromiseResolve = undefined; | ||
renderTaskPromise = undefined; | ||
} | ||
if (!invalidatedWebComponents.isEmpty()) { | ||
// More updates are pending. Resolve will be called again | ||
return; | ||
} | ||
if (renderTaskPromiseResolve) { | ||
renderTaskPromiseResolve(); | ||
renderTaskPromiseResolve = undefined; | ||
renderTaskPromise = undefined; | ||
} | ||
}; | ||
/** | ||
@@ -144,2 +121,3 @@ * Re-renders all UI5 Elements on the page, with the option to specify filters to rerender only some components. | ||
* reRenderAllUI5Elements({languageAware: true}) -> re-renders only languageAware components | ||
* reRenderAllUI5Elements({themeAware: true}) -> re-renders only themeAware components | ||
* reRenderAllUI5Elements({rtlAware: true, languageAware: true}) -> re-renders components that are rtlAware or languageAware | ||
@@ -149,33 +127,25 @@ * etc... | ||
* @public | ||
* @param {Object|undefined} filters - Object with keys that can be "rtlAware" or "languageAware" | ||
* @param {object|undefined} filters - Object with keys that can be "rtlAware" or "languageAware" | ||
* @returns {Promise<void>} | ||
*/ | ||
const reRenderAllUI5Elements = async filters => { | ||
registeredElements.forEach(element => { | ||
const tag = element.constructor.getMetadata().getTag(); | ||
const rtlAware = isRtlAware(element.constructor); | ||
const languageAware = element.constructor.getMetadata().isLanguageAware(); | ||
if (!filters || (filters.tag === tag) || (filters.rtlAware && rtlAware) || (filters.languageAware && languageAware)) { | ||
renderDeferred(element); | ||
} | ||
}); | ||
await renderFinished(); | ||
const reRenderAllUI5Elements = async (filters) => { | ||
registeredElements.forEach((element) => { | ||
const ctor = element.constructor; | ||
const tag = ctor.getMetadata().getTag(); | ||
const rtlAware = isRtlAware(ctor); | ||
const languageAware = ctor.getMetadata().isLanguageAware(); | ||
const themeAware = ctor.getMetadata().isThemeAware(); | ||
if (!filters || (filters.tag === tag) || (filters.rtlAware && rtlAware) || (filters.languageAware && languageAware) || (filters.themeAware && themeAware)) { | ||
renderDeferred(element); | ||
} | ||
}); | ||
await renderFinished(); | ||
}; | ||
const attachBeforeComponentRender = listener => { | ||
eventProvider.attachEvent("beforeComponentRender", listener); | ||
const attachBeforeComponentRender = (listener) => { | ||
eventProvider.attachEvent("beforeComponentRender", listener); | ||
}; | ||
const detachBeforeComponentRender = listener => { | ||
eventProvider.detachEvent("beforeComponentRender", listener); | ||
const detachBeforeComponentRender = (listener) => { | ||
eventProvider.detachEvent("beforeComponentRender", listener); | ||
}; | ||
export { | ||
renderDeferred, | ||
renderImmediately, | ||
cancelRender, | ||
renderFinished, | ||
reRenderAllUI5Elements, | ||
attachBeforeComponentRender, | ||
detachBeforeComponentRender, | ||
}; | ||
export { renderDeferred, renderImmediately, cancelRender, renderFinished, reRenderAllUI5Elements, attachBeforeComponentRender, detachBeforeComponentRender, }; | ||
//# sourceMappingURL=Render.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 |
@@ -0,3 +1,7 @@ | ||
class StaticArea extends HTMLElement { | ||
} | ||
if (!customElements.get("ui5-static-area")) { | ||
customElements.define("ui5-static-area", class extends HTMLElement {}); | ||
customElements.define("ui5-static-area", StaticArea); | ||
} | ||
export default StaticArea; | ||
//# sourceMappingURL=StaticArea.js.map |
@@ -5,93 +5,97 @@ import "./StaticArea.js"; | ||
import getEffectiveContentDensity from "./util/getEffectiveContentDensity.js"; | ||
import { getEffectiveScopingSuffixForTag } from "./CustomElementsScope.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 | ||
*/ | ||
class StaticAreaItem extends HTMLElement { | ||
constructor() { | ||
super(); | ||
this._rendered = false; | ||
this.attachShadow({ mode: "open" }); | ||
} | ||
/** | ||
* @protected | ||
* @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 | ||
} | ||
/** | ||
* Updates the shadow root of the static area item with the latest state, if rendered | ||
* @protected | ||
*/ | ||
update() { | ||
if (this._rendered) { | ||
this._updateContentDensity(); | ||
updateShadowRoot(this.ownerElement, true); | ||
} | ||
} | ||
/** | ||
* 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"); | ||
} | ||
} | ||
/** | ||
* @protected | ||
* Returns reference to the DOM element where the current fragment is added. | ||
*/ | ||
async getDomRef() { | ||
this._updateContentDensity(); | ||
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; | ||
} | ||
/** | ||
* @protected | ||
* @param refName | ||
* @returns {Element} | ||
*/ | ||
getStableDomRef(refName) { | ||
return this.shadowRoot.querySelector(`[data-ui5-stable=${refName}]`); | ||
} | ||
static getTag() { | ||
const pureTag = "ui5-static-area-item"; | ||
const suffix = getEffectiveScopingSuffixForTag(pureTag); | ||
if (!suffix) { | ||
return pureTag; | ||
} | ||
return `${pureTag}-${suffix}`; | ||
} | ||
static createInstance() { | ||
if (!customElements.get(StaticAreaItem.getTag())) { | ||
customElements.define(StaticAreaItem.getTag(), StaticAreaItem); | ||
} | ||
return document.createElement(this.getTag()); | ||
} | ||
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()); | ||
} | ||
} | ||
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"; | ||
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 { 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); | ||
fireThemeLoaded(theme); | ||
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 |
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); | ||
return 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}_normal`); // there is custom CSS only for the component itself, not for its static area part | ||
attachCustomCSSChange((tag) => { | ||
constructableStyleMap.delete(`${tag}_normal`); // there is custom CSS only for the component itself, not for its static area part | ||
}); | ||
/** | ||
@@ -17,15 +14,13 @@ * Returns (and caches) a constructable style sheet for a web component class | ||
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); | ||
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}_normal`); // there is custom CSS only for the component itself, not for its static area part | ||
attachCustomCSSChange((tag) => { | ||
effectiveStyleMap.delete(`${tag}_normal`); // there is custom CSS only for the component itself, not for its static area part | ||
}); | ||
const getEffectiveStyle = (ElementClass, forStaticArea = false) => { | ||
const tag = ElementClass.getMetadata().getTag(); | ||
const key = `${tag}_${forStaticArea ? "static" : "normal"}`; | ||
if (!effectiveStyleMap.has(key)) { | ||
let effectiveStyle; | ||
if (forStaticArea) { | ||
effectiveStyle = getStylesString(ElementClass.staticAreaStyles); | ||
} else { | ||
const customStyle = getCustomCSS(tag) || ""; | ||
const builtInStyles = getStylesString(ElementClass.styles); | ||
effectiveStyle = `${builtInStyles} ${customStyle}`; | ||
} | ||
effectiveStyleMap.set(key, effectiveStyle); | ||
} | ||
return effectiveStyleMap.get(key); | ||
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.filter(style => !!style)).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 EventProvider from "../EventProvider.js"; | ||
const eventProvider = new EventProvider(); | ||
const THEME_LOADED = "themeLoaded"; | ||
const attachThemeLoaded = listener => { | ||
eventProvider.attachEvent(THEME_LOADED, listener); | ||
const attachThemeLoaded = (listener) => { | ||
eventProvider.attachEvent(THEME_LOADED, listener); | ||
}; | ||
const detachThemeLoaded = listener => { | ||
eventProvider.detachEvent(THEME_LOADED, listener); | ||
const detachThemeLoaded = (listener) => { | ||
eventProvider.detachEvent(THEME_LOADED, listener); | ||
}; | ||
const fireThemeLoaded = theme => { | ||
return eventProvider.fireEvent(THEME_LOADED, theme); | ||
const fireThemeLoaded = (theme) => { | ||
return eventProvider.fireEvent(THEME_LOADED, theme); | ||
}; | ||
export { | ||
attachThemeLoaded, | ||
detachThemeLoaded, | ||
fireThemeLoaded, | ||
}; | ||
export { attachThemeLoaded, detachThemeLoaded, fireThemeLoaded, }; | ||
//# sourceMappingURL=ThemeLoaded.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.generateTypeAccessors(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 | ||
* CSSColor data type. | ||
* | ||
* @public | ||
*/ | ||
class CSSColor extends DataType { | ||
static isValid(value) { | ||
return /^(#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})|rgb\(\s*((1?[0-9]?[0-9]|2([0-4][0-9]|5[0-5]))|([0-9]?[0-9](\.[0-9]+)?|100(\.0+)?)%)\s*(,\s*((1?[0-9]?[0-9]|2([0-4][0-9]|5[0-5]))|([0-9]?[0-9](\.[0-9]+)?|100(\.0+)?)%)\s*){2}\)|rgba\((\s*((1?[0-9]?[0-9]|2([0-4][0-9]|5[0-5]))|([0-9]?[0-9](\.[0-9]+)?|100(\.0+)?)%)\s*,){3}\s*(0(\.[0-9]+)?|1(\.0+)?)\s*\)|hsl\(\s*([0-2]?[0-9]?[0-9]|3([0-5][0-9]|60))\s*(,\s*(([0-9]?[0-9](\.[0-9]+)?|100(\.0+)?)%)\s*){2}\)|hsla\(\s*([0-2]?[0-9]?[0-9]|3([0-5][0-9]|60))\s*,(\s*(([0-9]?[0-9](\.[0-9]+)?|100(\.0+)?)%)\s*,){2}\s*(0(\.[0-9]+)?|1(\.0+)?)\s*\)|aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgrey|darkgreen|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|gray|grey|green|greenyellow|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgrey|lightgreen|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy|oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|purple|red|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silverskyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|wheat|white|whitesmoke|yellow|yellowgreen|transparent|inherit|)$/.test(value ); // eslint-disable-line | ||
} | ||
static isValid(value) { | ||
return /^(#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})|rgb\(\s*((1?[0-9]?[0-9]|2([0-4][0-9]|5[0-5]))|([0-9]?[0-9](\.[0-9]+)?|100(\.0+)?)%)\s*(,\s*((1?[0-9]?[0-9]|2([0-4][0-9]|5[0-5]))|([0-9]?[0-9](\.[0-9]+)?|100(\.0+)?)%)\s*){2}\)|rgba\((\s*((1?[0-9]?[0-9]|2([0-4][0-9]|5[0-5]))|([0-9]?[0-9](\.[0-9]+)?|100(\.0+)?)%)\s*,){3}\s*(0(\.[0-9]+)?|1(\.0+)?)\s*\)|hsl\(\s*([0-2]?[0-9]?[0-9]|3([0-5][0-9]|60))\s*(,\s*(([0-9]?[0-9](\.[0-9]+)?|100(\.0+)?)%)\s*){2}\)|hsla\(\s*([0-2]?[0-9]?[0-9]|3([0-5][0-9]|60))\s*,(\s*(([0-9]?[0-9](\.[0-9]+)?|100(\.0+)?)%)\s*,){2}\s*(0(\.[0-9]+)?|1(\.0+)?)\s*\)|aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgrey|darkgreen|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|gray|grey|green|greenyellow|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgrey|lightgreen|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy|oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|purple|red|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silverskyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|wheat|white|whitesmoke|yellow|yellowgreen|transparent|inherit|)$/.test(value); // eslint-disable-line | ||
} | ||
} | ||
export default CSSColor; | ||
//# sourceMappingURL=CSSColor.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 generateTypeAccessors(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: navigations stops at the first or last item. | ||
*/ | ||
Static: "Static", | ||
/** | ||
* Cycling behavior: navigating past the last item continues with the first and vice versa. | ||
*/ | ||
Cyclic: "Cyclic", | ||
}; | ||
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.generateTypeAccessors(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,1 +1,3 @@ | ||
// eslint-disable-next-line import/no-extraneous-dependencies | ||
import "@ui5/webcomponents-base/dist/ssr-dom.js"; | ||
import merge from "./thirdparty/merge.js"; | ||
@@ -8,2 +10,3 @@ import { boot } from "./Boot.js"; | ||
import updateShadowRoot from "./updateShadowRoot.js"; | ||
import { shouldIgnoreCustomElement } from "./IgnoreCustomElements.js"; | ||
import { renderDeferred, renderImmediately, cancelRender } from "./Render.js"; | ||
@@ -14,45 +17,50 @@ import { registerTag, isTagRegistered, recordTagRegistrationFailure } from "./CustomElementsRegistry.js"; | ||
import getEffectiveDir from "./locale/getEffectiveDir.js"; | ||
import Integer from "./types/Integer.js"; | ||
import Float from "./types/Float.js"; | ||
import { kebabToCamelCase, camelToKebabCase } from "./util/StringHelper.js"; | ||
import isValidPropertyName from "./util/isValidPropertyName.js"; | ||
import { isSlot, getSlotName, getSlottedElementsList } from "./util/SlotsHelper.js"; | ||
import { getSlotName, getSlottedNodesList } from "./util/SlotsHelper.js"; | ||
import arraysAreEqual from "./util/arraysAreEqual.js"; | ||
import getClassCopy from "./util/getClassCopy.js"; | ||
import { markAsRtlAware } from "./locale/RTLAwareRegistry.js"; | ||
import isLegacyBrowser from "./isLegacyBrowser.js"; | ||
import preloadLinks from "./theming/preloadLinks.js"; | ||
import executeTemplate from "./renderer/executeTemplate.js"; | ||
import { subscribeForFeatureLoad } from "./FeaturesRegistry.js"; | ||
let autoId = 0; | ||
const elementTimeouts = new Map(); | ||
const uniqueDependenciesCache = new Map(); | ||
/** | ||
* Triggers re-rendering of a UI5Element instance due to state change. | ||
* | ||
* @param changeInfo An object with information about the change that caused invalidation. | ||
* @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._eventProvider.fireEvent("change", { ...changeInfo, target: this }); | ||
// 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 | ||
@@ -62,974 +70,961 @@ * @public | ||
class UI5Element extends HTMLElement { | ||
constructor() { | ||
super(); | ||
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)}`; | ||
} | ||
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._eventProvider = new EventProvider(); // used by parent components for listening to changes to child components | ||
let deferredResolve; | ||
this._domRefReadyPromise = new Promise(resolve => { | ||
deferredResolve = resolve; | ||
}); | ||
this._domRefReadyPromise._deferredResolve = deferredResolve; | ||
this._initializeState(); | ||
this._upgradeAllProperties(); | ||
if (this.constructor._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; | ||
} | ||
/** | ||
* 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(); | ||
} | ||
if (needsShadowDOM && !this.shadowRoot) { // Workaround for Firefox74 bug | ||
await Promise.resolve(); | ||
} | ||
if (!this._inDOM) { // Component removed from DOM while _processChildren was running | ||
return; | ||
} | ||
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 slotsAreManaged = this.constructor.getMetadata().slotsAreManaged(); | ||
this._inDOM = false; | ||
if (slotsAreManaged) { | ||
this._stopObservingDOMChildren(); | ||
} | ||
if (needsShadowDOM) { | ||
if (this._fullyConnected) { | ||
if (typeof this.onExitDOM === "function") { | ||
this.onExitDOM(); | ||
} | ||
this._fullyConnected = false; | ||
} | ||
} | ||
if (this.staticAreaItem && this.staticAreaItem.parentElement) { | ||
this.staticAreaItem.parentElement.removeChild(this.staticAreaItem); | ||
} | ||
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: 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 slotsMap = this.constructor.getMetadata().getSlots(); | ||
const canSlotText = this.constructor.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) { | ||
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); | ||
// Listen for any invalidation on the child if invalidateOnChildChange is true or an object (ignore when false or not set) | ||
if (child.isUI5Element && slotData.invalidateOnChildChange) { | ||
child._attachChange(this._getChildChangeListener(slotName)); | ||
} | ||
// Listen for the slotchange event if the child is a slot itself | ||
if (isSlot(child)) { | ||
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 (child && child.isUI5Element) { | ||
child._detachChange(this._getChildChangeListener(slotName)); | ||
} | ||
if (isSlot(child)) { | ||
this._detachSlotChange(child, slotName); | ||
} | ||
}); | ||
this._state[propertyName] = []; | ||
} | ||
/** | ||
* Attach a callback that will be executed whenever the component is invalidated | ||
* | ||
* @param callback | ||
* @protected | ||
*/ | ||
_attachChange(callback) { | ||
this._eventProvider.attachEvent("change", callback); | ||
} | ||
/** | ||
* Detach the callback that is executed whenever the component is invalidated | ||
* | ||
* @param callback | ||
* @protected | ||
*/ | ||
_detachChange(callback) { | ||
this._eventProvider.detachEvent("change", 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) { | ||
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() { | ||
this._state = { ...this.constructor.getMetadata().getInitialState() }; | ||
} | ||
/** | ||
* 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 | ||
* @returns {any} | ||
* @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 | ||
* @returns {any} | ||
* @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) { | ||
child.addEventListener("slotchange", this._getSlotChangeListener(slotName)); | ||
} | ||
/** | ||
* @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) {} | ||
/** | ||
* Do not call this method directly, only intended to be called by 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 | ||
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} ${x.oldValue} => ${x.newValue}`; | ||
} | ||
return res; | ||
})); | ||
} | ||
*/ | ||
this._changedState = []; | ||
// Update shadow root and static area item | ||
if (this.constructor._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 | ||
if (typeof this.onAfterRendering === "function") { | ||
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 | ||
* 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; | ||
} | ||
this._assertShadowRootStructure(); | ||
return this.shadowRoot.children.length === 1 | ||
? this.shadowRoot.children[0] : this.shadowRoot.children[1]; | ||
} | ||
_assertShadowRootStructure() { | ||
const expectedChildrenCount = document.adoptedStyleSheets || isLegacyBrowser() ? 1 : 2; | ||
if (this.shadowRoot.children.length !== expectedChildrenCount) { | ||
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 | ||
} | ||
} | ||
/** | ||
* 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(); | ||
} | ||
/** | ||
* Use this method in order to get a reference to an element in the shadow root of the web component or the static area item of the component | ||
* @public | ||
* @method | ||
* @param {String} refName Defines the name of the stable DOM ref | ||
*/ | ||
getStableDomRef(refName) { | ||
const staticAreaResult = this.staticAreaItem && this.staticAreaItem.getStableDomRef(refName); | ||
return staticAreaResult || 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) { | ||
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 getSlottedElementsList(this[slotName]); | ||
} | ||
/** | ||
* 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 | ||
return getEffectiveDir(this); | ||
} | ||
/** | ||
* 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 _needsShadowDOM() { | ||
return !!this.template; | ||
} | ||
/** | ||
* @private | ||
*/ | ||
static _needsStaticArea() { | ||
return !!this.staticAreaTemplate; | ||
} | ||
/** | ||
* @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.`); | ||
} | ||
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) { | ||
let isDifferent; | ||
value = this.constructor.getMetadata().constructor.validatePropertyValue(value, propData); | ||
const oldState = this._state[prop]; | ||
if (propData.multiple && propData.compareValues) { | ||
isDifferent = !arraysAreEqual(oldState, value); | ||
} else { | ||
isDifferent = oldState !== value; | ||
} | ||
if (isDifferent) { | ||
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 metadata object for this UI5 Web Component Class | ||
* @protected | ||
*/ | ||
static get metadata() { | ||
return {}; | ||
} | ||
/** | ||
* 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)) { | ||
registerTag(altTag); | ||
window.customElements.define(altTag, getClassCopy(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 | ||
* @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 []; | ||
} | ||
static cacheUniqueDependencies() { | ||
const filtered = this.dependencies.filter((dep, index, deps) => deps.indexOf(dep) === index); | ||
uniqueDependenciesCache.set(this, filtered); | ||
} | ||
/** | ||
* Returns a list of the unique dependencies for this UI5 Web Component | ||
* | ||
* @public | ||
*/ | ||
static getUniqueDependencies() { | ||
if (!uniqueDependenciesCache.has(this)) { | ||
this.cacheUniqueDependencies(); | ||
} | ||
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 features = this.getMetadata().getFeatures(); | ||
features.forEach(feature => { | ||
subscribeForFeatureLoad(feature, this, this.cacheUniqueDependencies.bind(this)); | ||
}); | ||
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 { getSlottedElements } from "./util/SlotsHelper.js"; | ||
import { getEffectiveScopingSuffixForTag } from "./CustomElementsScope.js"; | ||
import { getSlottedNodes } from "./util/SlotsHelper.js"; | ||
import { getEffectiveScopingSuffixForTag } from "./CustomElementsScopeUtils.js"; | ||
/** | ||
* | ||
* @class | ||
@@ -13,300 +9,293 @@ * @public | ||
class UI5ElementMetadata { | ||
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) { | ||
initialState[propName] = []; | ||
} else if (propType === Object) { | ||
initialState[propName] = "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; | ||
} | ||
/** | ||
* 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; | ||
} | ||
/** | ||
* 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 | ||
* @returns {boolean} | ||
*/ | ||
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"); | ||
} | ||
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 without the scope | ||
* @private | ||
*/ | ||
getFeatures() { | ||
return this.metadata.features || []; | ||
} | ||
/** | ||
* 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) => { | ||
value && getSlottedElements(value).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 |
@@ -1,6 +0,6 @@ | ||
import executeTemplate from "./renderer/executeTemplate.js"; | ||
import getConstructableStyle from "./theming/getConstructableStyle.js"; | ||
import getEffectiveStyle from "./theming/getEffectiveStyle.js"; | ||
import isLegacyBrowser from "./isLegacyBrowser.js"; | ||
import getEffectiveLinksHrefs from "./theming/getEffectiveLinksHrefs.js"; | ||
import { shouldUseLinks } from "./CSP.js"; | ||
import { isSafari } from "./Device.js"; | ||
/** | ||
@@ -12,16 +12,32 @@ * Updates the shadow root of a UI5Element or its static area item | ||
const updateShadowRoot = (element, forStaticArea = false) => { | ||
let styleToPrepend; | ||
const template = forStaticArea ? "staticAreaTemplate" : "template"; | ||
const shadowRoot = forStaticArea ? element.staticAreaItem.shadowRoot : element.shadowRoot; | ||
const renderResult = executeTemplate(element.constructor[template], element); | ||
if (document.adoptedStyleSheets) { // Chrome | ||
shadowRoot.adoptedStyleSheets = getConstructableStyle(element.constructor, forStaticArea); | ||
} else if (!isLegacyBrowser()) { // FF, Safari | ||
styleToPrepend = getEffectiveStyle(element.constructor, forStaticArea); | ||
} | ||
element.constructor.render(renderResult, shadowRoot, styleToPrepend, { eventContext: element }); | ||
let styleStrOrHrefsArr; | ||
const ctor = element.constructor; | ||
const shadowRoot = forStaticArea ? element.staticAreaItem.shadowRoot : element.shadowRoot; | ||
let renderResult; | ||
if (forStaticArea) { | ||
renderResult = element.renderStatic(); // this is checked before calling updateShadowRoot | ||
} | ||
else { | ||
renderResult = element.render(); // this is checked before calling updateShadowRoot | ||
} | ||
if (!shadowRoot) { | ||
console.warn(`There is no shadow root to update`); // eslint-disable-line | ||
return; | ||
} | ||
if (shouldUseLinks()) { | ||
styleStrOrHrefsArr = getEffectiveLinksHrefs(ctor, forStaticArea); | ||
} | ||
else if (document.adoptedStyleSheets && !isSafari()) { // Chrome | ||
shadowRoot.adoptedStyleSheets = getConstructableStyle(ctor, forStaticArea); | ||
} | ||
else { // FF, Safari | ||
styleStrOrHrefsArr = getEffectiveStyle(ctor, forStaticArea); | ||
} | ||
if (ctor.renderer) { | ||
ctor.renderer(renderResult, shadowRoot, styleStrOrHrefsArr, forStaticArea, { host: element }); | ||
return; | ||
} | ||
ctor.render(renderResult, shadowRoot, styleStrOrHrefsArr, forStaticArea, { host: element }); | ||
}; | ||
export default updateShadowRoot; | ||
//# sourceMappingURL=updateShadowRoot.js.map |
@@ -1,41 +0,171 @@ | ||
import findNodeOwner from "./findNodeOwner.js"; | ||
const getEffectiveAriaLabelText = el => { | ||
if (!el.ariaLabelledby) { | ||
if (el.ariaLabel) { | ||
return el.ariaLabel; | ||
} | ||
return undefined; | ||
} | ||
return getAriaLabelledByTexts(el); | ||
const associatedElements = new WeakMap(); | ||
const registeredElements = new WeakMap(); | ||
const observerOptions = { | ||
attributes: true, | ||
childList: true, | ||
characterData: true, | ||
subtree: true, | ||
}; | ||
const getEffectiveAriaLabelText = (el) => { | ||
const accessibleEl = el; | ||
if (!accessibleEl.accessibleNameRef) { | ||
if (accessibleEl.accessibleName) { | ||
return accessibleEl.accessibleName; | ||
} | ||
return undefined; | ||
} | ||
return getAllAccessibleNameRefTexts(el); | ||
}; | ||
/** | ||
* | ||
* @param {HTMLElement} el Defines the HTMLElement, for which you need to get all related texts | ||
* @param {HTMLElement} ownerDocument (Optional) Defines the HTMLElement(might document or custom element) where the you want to search for the texts. | ||
* @param {String} readyIds (Optional) Defines a string of elements ids. The text of these elements will be returned. If used you should provide either el or ownerDocument | ||
*/ | ||
const getAriaLabelledByTexts = (el, ownerDocument, readyIds = "") => { | ||
const ids = (readyIds && readyIds.split(" ")) || el.ariaLabelledby.split(" "); | ||
const owner = ownerDocument || findNodeOwner(el); | ||
let result = ""; | ||
ids.forEach((elementId, index) => { | ||
const element = owner.querySelector(`[id='${elementId}']`); | ||
result += `${element ? element.textContent : ""}`; | ||
if (index < ids.length - 1) { | ||
result += " "; | ||
} | ||
}); | ||
return result; | ||
const getAllAccessibleNameRefTexts = (el) => { | ||
const ids = el.accessibleNameRef?.split(" ") ?? []; | ||
const owner = el.getRootNode(); | ||
let result = ""; | ||
ids.forEach((elementId, index) => { | ||
const element = owner.querySelector(`[id='${elementId}']`); | ||
const text = `${element && element.textContent ? element.textContent : ""}`; | ||
if (text) { | ||
result += text; | ||
if (index < ids.length - 1) { | ||
result += " "; | ||
} | ||
} | ||
}); | ||
return result; | ||
}; | ||
export { | ||
getEffectiveAriaLabelText, | ||
getAriaLabelledByTexts, | ||
const _getAllAssociatedElementsFromDOM = (el) => { | ||
const set = new Set(); | ||
// adding labels with attribute for matching the el.id | ||
const labelsForAssociated = _getAssociatedLabels(el); | ||
labelsForAssociated.forEach(itm => { | ||
set.add(itm); | ||
}); | ||
// adding other elements that id is the same as accessibleNameRef value | ||
const value = el["accessibleNameRef"]; | ||
const ids = value?.split(" ") ?? []; | ||
ids.forEach(id => { | ||
const refEl = _getReferencedElementById(el, id); | ||
if (refEl) { | ||
set.add(refEl); | ||
} | ||
}); | ||
return Array.from(set); | ||
}; | ||
const _getAssociatedLabels = (el) => { | ||
const labels = el.getRootNode().querySelectorAll(`[for="${el.id}"]`); | ||
return Array.from(labels); | ||
}; | ||
const _getReferencedElementById = (el, elementId) => { | ||
return el.getRootNode().querySelector(`[id='${elementId}']`); | ||
}; | ||
/** | ||
* @param {HTMLElement} el Defines the HTMLElement, for which you need to get all related "label for" texts | ||
*/ | ||
const getAssociatedLabelForTexts = (el) => { | ||
const results = []; | ||
const labels = _getAssociatedLabels(el); | ||
labels.forEach((label) => { | ||
const labelText = label.textContent; | ||
labelText && results.push(labelText); | ||
}); | ||
if (results.length) { | ||
return results.join(" "); | ||
} | ||
return undefined; | ||
}; | ||
const _createInvalidationCallback = (el) => { | ||
const invalidationCallback = (changeInfo) => { | ||
if (!(changeInfo && changeInfo.type === "property" && changeInfo.name === "accessibleNameRef")) { | ||
return; | ||
} | ||
const registeredElement = registeredElements.get(el); | ||
if (!registeredElement) { | ||
return; | ||
} | ||
const oldAssociatedElements = registeredElement.observedElements; | ||
const newAssociatedElements = _getAllAssociatedElementsFromDOM(el); | ||
oldAssociatedElements.forEach(oldElement => { | ||
if (!newAssociatedElements.includes(oldElement)) { | ||
_removeObservedElementFromRegisteredElement(registeredElement, oldElement); | ||
} | ||
}); | ||
newAssociatedElements.forEach(newElement => { | ||
if (!oldAssociatedElements.includes(newElement)) { | ||
_addObservedElementToRegisteredElement(registeredElement, newElement); | ||
registeredElement.observedElements.push(newElement); | ||
} | ||
}); | ||
registeredElement?.callback(); | ||
}; | ||
return invalidationCallback; | ||
}; | ||
const registerUI5Element = (el, callback) => { | ||
if (registeredElements.has(el)) { | ||
return; | ||
} | ||
const allAssociatedElements = _getAllAssociatedElementsFromDOM(el); | ||
const invalidationCallback = _createInvalidationCallback(el); | ||
const registeredElement = { | ||
host: el, | ||
observedElements: allAssociatedElements, | ||
callback, | ||
invalidationCallback, | ||
}; | ||
registeredElements.set(el, registeredElement); | ||
el.attachInvalidate(invalidationCallback); | ||
allAssociatedElements.forEach((element) => { | ||
_addObservedElementToRegisteredElement(registeredElement, element); | ||
}); | ||
callback(); | ||
}; | ||
const _addObservedElementToRegisteredElement = (registeredElement, element) => { | ||
let associatedElement = associatedElements.get(element); | ||
if (!associatedElement) { | ||
associatedElement = { observer: null, callbacks: [] }; | ||
const observer = new MutationObserver(() => { | ||
const callbacks = associatedElement.callbacks; | ||
callbacks.forEach(callback => { | ||
callback(); | ||
}); | ||
const domEl = document.getElementById(element.id); | ||
// if no longer should be observed from this registeredElement, remove it | ||
if (!(registeredElement.host.id === element.getAttribute("for") || domEl)) { | ||
_removeObservedElementFromRegisteredElement(registeredElement, element); | ||
} | ||
}); | ||
associatedElement.observer = observer; | ||
observer.observe(element, observerOptions); | ||
associatedElements.set(element, associatedElement); | ||
} | ||
if (!associatedElement.callbacks.includes(registeredElement.callback)) { | ||
associatedElement.callbacks.push(registeredElement.callback); | ||
} | ||
}; | ||
const _removeObservedElementFromRegisteredElement = (registeredElement, element) => { | ||
const associatedElement = associatedElements.get(element); | ||
if (associatedElement) { | ||
associatedElement.callbacks = associatedElement.callbacks.filter(itm => itm !== registeredElement.callback); | ||
if (!associatedElement.callbacks.length) { | ||
associatedElement.observer?.disconnect(); | ||
associatedElements.delete(element); | ||
} | ||
} | ||
registeredElement.observedElements = registeredElement.observedElements.filter(itm => itm !== element); | ||
}; | ||
const deregisterUI5Element = (el) => { | ||
const registeredElement = registeredElements.get(el); | ||
if (!registeredElement) { | ||
return; | ||
} | ||
const oldObservedElements = [...registeredElement.observedElements]; | ||
oldObservedElements.forEach(observedElement => { | ||
_removeObservedElementFromRegisteredElement(registeredElement, observedElement); | ||
}); | ||
el.detachInvalidate(registeredElement.invalidationCallback); | ||
registeredElements.delete(el); | ||
}; | ||
export { getEffectiveAriaLabelText, getAssociatedLabelForTexts, registerUI5Element, deregisterUI5Element, getAllAccessibleNameRefTexts, }; | ||
//# sourceMappingURL=AriaLabelHelper.js.map |
const arraysAreEqual = (arr1, arr2) => { | ||
if (arr1.length !== arr2.length) { | ||
return false; | ||
} | ||
for (let i = 0; i < arr1.length; i++) { | ||
if (arr1[i] !== arr2[i]) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
if (arr1.length !== arr2.length) { | ||
return false; | ||
} | ||
for (let i = 0; i < arr1.length; i++) { | ||
if (arr1[i] !== arr2[i]) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
}; | ||
export default arraysAreEqual; | ||
//# sourceMappingURL=arraysAreEqual.js.map |
@@ -5,42 +5,20 @@ /** | ||
*/ | ||
const getCaretPosition = field => { | ||
// Initialize | ||
let caretPos = 0; | ||
// IE Support | ||
if (document.selection) { | ||
// Set focus on the element | ||
field.focus(); | ||
// To get cursor position, get empty selection range | ||
const selection = document.selection.createRange(); | ||
// Move selection start to 0 position | ||
selection.moveStart("character", -field.value.length); | ||
// The caret position is selection length | ||
caretPos = selection.text.length; | ||
} else if (field.selectionStart || field.selectionStart === "0") { // Firefox support | ||
caretPos = field.selectionDirection === "backward" ? field.selectionStart : field.selectionEnd; | ||
} | ||
return caretPos; | ||
const getCaretPosition = (field) => { | ||
// Initialize | ||
let caretPos = 0; | ||
if (field.selectionStart || field.selectionStart === 0) { // Firefox support | ||
caretPos = field.selectionDirection === "backward" ? field.selectionStart : field.selectionEnd; | ||
} | ||
return caretPos; | ||
}; | ||
const setCaretPosition = (field, caretPos) => { | ||
if (field.createTextRange) { | ||
const range = field.createTextRange(); | ||
range.move("character", caretPos); | ||
range.select(); | ||
} else if (field.selectionStart) { | ||
field.focus(); | ||
field.setSelectionRange(caretPos, caretPos); | ||
} else { | ||
field.focus(); | ||
} | ||
if (field.selectionStart) { | ||
field.focus(); | ||
field.setSelectionRange(caretPos, caretPos); | ||
} | ||
else { | ||
field.focus(); | ||
} | ||
}; | ||
export { | ||
getCaretPosition, | ||
setCaretPosition, | ||
}; | ||
export { getCaretPosition, setCaretPosition, }; | ||
//# sourceMappingURL=Caret.js.map |
@@ -9,5 +9,6 @@ /** | ||
const clamp = (val, min, max) => { | ||
return Math.min(Math.max(val, min), max); | ||
// handles case when max < min | ||
return Math.min(Math.max(val, min), Math.max(min, max)); | ||
}; | ||
export default clamp; | ||
//# sourceMappingURL=clamp.js.map |
@@ -5,184 +5,178 @@ /** | ||
*/ | ||
/** | ||
* Map of CSS colors to hex representation | ||
*/ | ||
const CSSColors = { | ||
aliceblue: "f0f8ff", | ||
antiquewhite: "faebd7", | ||
aqua: "00ffff", | ||
aquamarine: "7fffd4", | ||
azure: "f0ffff", | ||
beige: "f5f5dc", | ||
bisque: "ffe4c4", | ||
black: "000000", | ||
blanchedalmond: "ffebcd", | ||
blue: "0000ff", | ||
blueviolet: "8a2be2", | ||
brown: "a52a2a", | ||
burlywood: "deb887", | ||
cadetblue: "5f9ea0", | ||
chartreuse: "7fff00", | ||
chocolate: "d2691e", | ||
coral: "ff7f50", | ||
cornflowerblue: "6495ed", | ||
cornsilk: "fff8dc", | ||
crimson: "dc143c", | ||
cyan: "00ffff", | ||
darkblue: "00008b", | ||
darkcyan: "008b8b", | ||
darkgoldenrod: "b8860b", | ||
darkgray: "a9a9a9", | ||
darkgrey: "a9a9a9", | ||
darkgreen: "006400", | ||
darkkhaki: "bdb76b", | ||
darkmagenta: "8b008b", | ||
darkolivegreen: "556b2f", | ||
darkorange: "ff8c00", | ||
darkorchid: "9932cc", | ||
darkred: "8b0000", | ||
darksalmon: "e9967a", | ||
darkseagreen: "8fbc8f", | ||
darkslateblue: "483d8b", | ||
darkslategray: "2f4f4f", | ||
darkslategrey: "2f4f4f", | ||
darkturquoise: "00ced1", | ||
darkviolet: "9400d3", | ||
deeppink: "ff1493", | ||
deepskyblue: "00bfff", | ||
dimgray: "696969", | ||
dimgrey: "696969", | ||
dodgerblue: "1e90ff", | ||
firebrick: "b22222", | ||
floralwhite: "fffaf0", | ||
forestgreen: "228b22", | ||
fuchsia: "ff00ff", | ||
gainsboro: "dcdcdc", | ||
ghostwhite: "f8f8ff", | ||
gold: "ffd700", | ||
goldenrod: "daa520", | ||
gray: "808080", | ||
grey: "808080", | ||
green: "008000", | ||
greenyellow: "adff2f", | ||
honeydew: "f0fff0", | ||
hotpink: "ff69b4", | ||
indianred: "cd5c5c", | ||
indigo: "4b0082", | ||
ivory: "fffff0", | ||
khaki: "f0e68c", | ||
lavender: "e6e6fa", | ||
lavenderblush: "fff0f5", | ||
lawngreen: "7cfc00", | ||
lemonchiffon: "fffacd", | ||
lightblue: "add8e6", | ||
lightcoral: "f08080", | ||
lightcyan: "e0ffff", | ||
lightgoldenrodyellow: "fafad2", | ||
lightgray: "d3d3d3", | ||
lightgrey: "d3d3d3", | ||
lightgreen: "90ee90", | ||
lightpink: "ffb6c1", | ||
lightsalmon: "ffa07a", | ||
lightseagreen: "20b2aa", | ||
lightskyblue: "87cefa", | ||
lightslategray: "778899", | ||
lightslategrey: "778899", | ||
lightsteelblue: "b0c4de", | ||
lightyellow: "ffffe0", | ||
lime: "00ff00", | ||
limegreen: "32cd32", | ||
linen: "faf0e6", | ||
magenta: "ff00ff", | ||
maroon: "800000", | ||
mediumaquamarine: "66cdaa", | ||
mediumblue: "0000cd", | ||
mediumorchid: "ba55d3", | ||
mediumpurple: "9370db", | ||
mediumseagreen: "3cb371", | ||
mediumslateblue: "7b68ee", | ||
mediumspringgreen: "00fa9a", | ||
mediumturquoise: "48d1cc", | ||
mediumvioletred: "c71585", | ||
midnightblue: "191970", | ||
mintcream: "f5fffa", | ||
mistyrose: "ffe4e1", | ||
moccasin: "ffe4b5", | ||
navajowhite: "ffdead", | ||
navy: "000080", | ||
oldlace: "fdf5e6", | ||
olive: "808000", | ||
olivedrab: "6b8e23", | ||
orange: "ffa500", | ||
orangered: "ff4500", | ||
orchid: "da70d6", | ||
palegoldenrod: "eee8aa", | ||
palegreen: "98fb98", | ||
paleturquoise: "afeeee", | ||
palevioletred: "db7093", | ||
papayawhip: "ffefd5", | ||
peachpuff: "ffdab9", | ||
peru: "cd853f", | ||
pink: "ffc0cb", | ||
plum: "dda0dd", | ||
powderblue: "b0e0e6", | ||
purple: "800080", | ||
red: "ff0000", | ||
rosybrown: "bc8f8f", | ||
royalblue: "4169e1", | ||
saddlebrown: "8b4513", | ||
salmon: "fa8072", | ||
sandybrown: "f4a460", | ||
seagreen: "2e8b57", | ||
seashell: "fff5ee", | ||
sienna: "a0522d", | ||
silver: "c0c0c0", | ||
skyblue: "87ceeb", | ||
slateblue: "6a5acd", | ||
slategray: "708090", | ||
slategrey: "708090", | ||
snow: "fffafa", | ||
springgreen: "00ff7f", | ||
steelblue: "4682b4", | ||
tan: "d2b48c", | ||
teal: "008080", | ||
thistle: "d8bfd8", | ||
tomato: "ff6347", | ||
turquoise: "40e0d0", | ||
violet: "ee82ee", | ||
wheat: "f5deb3", | ||
white: "ffffff", | ||
whitesmoke: "f5f5f5", | ||
yellow: "ffff00", | ||
yellowgreen: "9acd32", | ||
transparent: "00000000", | ||
const CSSColors = new Map([ | ||
["aliceblue", "f0f8ff"], | ||
["antiquewhite", "faebd7"], | ||
["aqua", "00ffff"], | ||
["aquamarine", "7fffd4"], | ||
["azure", "f0ffff"], | ||
["beige", "f5f5dc"], | ||
["bisque", "ffe4c4"], | ||
["black", "000000"], | ||
["blanchedalmond", "ffebcd"], | ||
["blue", "0000ff"], | ||
["blueviolet", "8a2be2"], | ||
["brown", "a52a2a"], | ||
["burlywood", "deb887"], | ||
["cadetblue", "5f9ea0"], | ||
["chartreuse", "7fff00"], | ||
["chocolate", "d2691e"], | ||
["coral", "ff7f50"], | ||
["cornflowerblue", "6495ed"], | ||
["cornsilk", "fff8dc"], | ||
["crimson", "dc143c"], | ||
["cyan", "00ffff"], | ||
["darkblue", "00008b"], | ||
["darkcyan", "008b8b"], | ||
["darkgoldenrod", "b8860b"], | ||
["darkgray", "a9a9a9"], | ||
["darkgrey", "a9a9a9"], | ||
["darkgreen", "006400"], | ||
["darkkhaki", "bdb76b"], | ||
["darkmagenta", "8b008b"], | ||
["darkolivegreen", "556b2f"], | ||
["darkorange", "ff8c00"], | ||
["darkorchid", "9932cc"], | ||
["darkred", "8b0000"], | ||
["darksalmon", "e9967a"], | ||
["darkseagreen", "8fbc8f"], | ||
["darkslateblue", "483d8b"], | ||
["darkslategray", "2f4f4f"], | ||
["darkslategrey", "2f4f4f"], | ||
["darkturquoise", "00ced1"], | ||
["darkviolet", "9400d3"], | ||
["deeppink", "ff1493"], | ||
["deepskyblue", "00bfff"], | ||
["dimgray", "696969"], | ||
["dimgrey", "696969"], | ||
["dodgerblue", "1e90ff"], | ||
["firebrick", "b22222"], | ||
["floralwhite", "fffaf0"], | ||
["forestgreen", "228b22"], | ||
["fuchsia", "ff00ff"], | ||
["gainsboro", "dcdcdc"], | ||
["ghostwhite", "f8f8ff"], | ||
["gold", "ffd700"], | ||
["goldenrod", "daa520"], | ||
["gray", "808080"], | ||
["grey", "808080"], | ||
["green", "008000"], | ||
["greenyellow", "adff2f"], | ||
["honeydew", "f0fff0"], | ||
["hotpink", "ff69b4"], | ||
["indianred", "cd5c5c"], | ||
["indigo", "4b0082"], | ||
["ivory", "fffff0"], | ||
["khaki", "f0e68c"], | ||
["lavender", "e6e6fa"], | ||
["lavenderblush", "fff0f5"], | ||
["lawngreen", "7cfc00"], | ||
["lemonchiffon", "fffacd"], | ||
["lightblue", "add8e6"], | ||
["lightcoral", "f08080"], | ||
["lightcyan", "e0ffff"], | ||
["lightgoldenrodyellow", "fafad2"], | ||
["lightgray", "d3d3d3"], | ||
["lightgrey", "d3d3d3"], | ||
["lightgreen", "90ee90"], | ||
["lightpink", "ffb6c1"], | ||
["lightsalmon", "ffa07a"], | ||
["lightseagreen", "20b2aa"], | ||
["lightskyblue", "87cefa"], | ||
["lightslategray", "778899"], | ||
["lightslategrey", "778899"], | ||
["lightsteelblue", "b0c4de"], | ||
["lightyellow", "ffffe0"], | ||
["lime", "00ff00"], | ||
["limegreen", "32cd32"], | ||
["linen", "faf0e6"], | ||
["magenta", "ff00ff"], | ||
["maroon", "800000"], | ||
["mediumaquamarine", "66cdaa"], | ||
["mediumblue", "0000cd"], | ||
["mediumorchid", "ba55d3"], | ||
["mediumpurple", "9370db"], | ||
["mediumseagreen", "3cb371"], | ||
["mediumslateblue", "7b68ee"], | ||
["mediumspringgreen", "00fa9a"], | ||
["mediumturquoise", "48d1cc"], | ||
["mediumvioletred", "c71585"], | ||
["midnightblue", "191970"], | ||
["mintcream", "f5fffa"], | ||
["mistyrose", "ffe4e1"], | ||
["moccasin", "ffe4b5"], | ||
["navajowhite", "ffdead"], | ||
["navy", "000080"], | ||
["oldlace", "fdf5e6"], | ||
["olive", "808000"], | ||
["olivedrab", "6b8e23"], | ||
["orange", "ffa500"], | ||
["orangered", "ff4500"], | ||
["orchid", "da70d6"], | ||
["palegoldenrod", "eee8aa"], | ||
["palegreen", "98fb98"], | ||
["paleturquoise", "afeeee"], | ||
["palevioletred", "db7093"], | ||
["papayawhip", "ffefd5"], | ||
["peachpuff", "ffdab9"], | ||
["peru", "cd853f"], | ||
["pink", "ffc0cb"], | ||
["plum", "dda0dd"], | ||
["powderblue", "b0e0e6"], | ||
["purple", "800080"], | ||
["red", "ff0000"], | ||
["rosybrown", "bc8f8f"], | ||
["royalblue", "4169e1"], | ||
["saddlebrown", "8b4513"], | ||
["salmon", "fa8072"], | ||
["sandybrown", "f4a460"], | ||
["seagreen", "2e8b57"], | ||
["seashell", "fff5ee"], | ||
["sienna", "a0522d"], | ||
["silver", "c0c0c0"], | ||
["skyblue", "87ceeb"], | ||
["slateblue", "6a5acd"], | ||
["slategray", "708090"], | ||
["slategrey", "708090"], | ||
["snow", "fffafa"], | ||
["springgreen", "00ff7f"], | ||
["steelblue", "4682b4"], | ||
["tan", "d2b48c"], | ||
["teal", "008080"], | ||
["thistle", "d8bfd8"], | ||
["tomato", "ff6347"], | ||
["turquoise", "40e0d0"], | ||
["violet", "ee82ee"], | ||
["wheat", "f5deb3"], | ||
["white", "ffffff"], | ||
["whitesmoke", "f5f5f5"], | ||
["yellow", "ffff00"], | ||
["yellowgreen", "9acd32"], | ||
["transparent", "00000000"], | ||
]); | ||
const getRGBColor = (color) => { | ||
if (color.startsWith("rgba")) { | ||
return RGBAToRGB(color); | ||
} | ||
if (color.startsWith("rgb")) { | ||
return RGBStringToRGBObject(color); | ||
} | ||
// HEX | ||
if (color.indexOf("#") === 0) { | ||
// Shorthand Syntax | ||
if (color.length === 4) { | ||
color = `${color[1]}${color[1]}${color[2]}${color[2]}${color[3]}${color[3]}`; | ||
} | ||
else { | ||
color = color.slice(1, color.length); | ||
} | ||
} | ||
// Css Color | ||
if (CSSColors.has(color)) { | ||
color = CSSColors.get(color); | ||
} | ||
return HEXToRGB(color); | ||
}; | ||
const getRGBColor = color => { | ||
if (color.startsWith("rgba")) { | ||
return RGBAToRGB(color); | ||
} | ||
if (color.startsWith("rgb")) { | ||
return RGBToRGB(color); | ||
} | ||
// HEX | ||
if (color.indexOf("#") === 0) { | ||
// Shorthand Syntax | ||
if (color.length === 4) { | ||
color = `${color[1]}${color[1]}${color[2]}${color[2]}${color[3]}${color[3]}`; | ||
} else { | ||
color = color.slice(1, color.length); | ||
} | ||
} | ||
// Css Color | ||
if (Object.keys(CSSColors).includes(color)) { | ||
color = CSSColors[color]; | ||
} | ||
return HEXToRGB(color); | ||
}; | ||
/** | ||
@@ -192,19 +186,15 @@ * Return an object with the properties for each of the main colors(red, green, blue) | ||
*/ | ||
const RGBAToRGB = color => { | ||
const openingBracketIndex = color.indexOf("("), | ||
commasIndexes = []; | ||
for (let i = 0; i < color.length; i++) { | ||
if (color[i] === ",") { | ||
commasIndexes.push(i); | ||
} | ||
} | ||
return { | ||
r: parseInt(color.slice(openingBracketIndex + 1, commasIndexes[0]).trim()), | ||
g: parseInt(color.slice(commasIndexes[0] + 1, commasIndexes[1]).trim()), | ||
b: parseInt(color.slice(commasIndexes[1] + 1, commasIndexes[2]).trim()), | ||
}; | ||
const RGBAToRGB = (color) => { | ||
const openingBracketIndex = color.indexOf("("), commasIndexes = []; | ||
for (let i = 0; i < color.length; i++) { | ||
if (color[i] === ",") { | ||
commasIndexes.push(i); | ||
} | ||
} | ||
return { | ||
r: parseInt(color.slice(openingBracketIndex + 1, commasIndexes[0]).trim()), | ||
g: parseInt(color.slice(commasIndexes[0] + 1, commasIndexes[1]).trim()), | ||
b: parseInt(color.slice(commasIndexes[1] + 1, commasIndexes[2]).trim()), | ||
}; | ||
}; | ||
/** | ||
@@ -214,160 +204,142 @@ * Return an object with the properties for each of the main colors(red, green, blue) | ||
*/ | ||
const RGBToRGB = color => { | ||
const openingBracketIndex = color.indexOf("("), | ||
closingBraketIndex = color.indexOf(")"), | ||
commasIndexes = []; | ||
for (let i = 0; i < color.length; i++) { | ||
if (color[i] === ",") { | ||
commasIndexes.push(i); | ||
} | ||
} | ||
return { | ||
r: parseInt(color.slice(openingBracketIndex + 1, commasIndexes[0]).trim()), | ||
g: parseInt(color.slice(commasIndexes[0] + 1, commasIndexes[1]).trim()), | ||
b: parseInt(color.slice(commasIndexes[1] + 1, closingBraketIndex).trim()), | ||
}; | ||
const RGBStringToRGBObject = (color) => { | ||
const openingBracketIndex = color.indexOf("("), closingBraketIndex = color.indexOf(")"), commasIndexes = []; | ||
for (let i = 0; i < color.length; i++) { | ||
if (color[i] === ",") { | ||
commasIndexes.push(i); | ||
} | ||
} | ||
return { | ||
r: parseInt(color.slice(openingBracketIndex + 1, commasIndexes[0]).trim()), | ||
g: parseInt(color.slice(commasIndexes[0] + 1, commasIndexes[1]).trim()), | ||
b: parseInt(color.slice(commasIndexes[1] + 1, closingBraketIndex).trim()), | ||
}; | ||
}; | ||
const HSLToRGB = (color = { | ||
h: undefined, | ||
s: undefined, | ||
l: undefined, | ||
}) => { | ||
// Formula taken from https://www.rapidtables.com/convert/color/hsl-to-rgb.html | ||
const C = (1 - Math.abs((2 * color.l) - 1)) * color.s, | ||
X = C * (1 - Math.abs(((color.h / 60) % 2) - 1)), | ||
m = color.l - C / 2; | ||
let tempColor = {}; | ||
switch (Math.round(color.h / 60)) { | ||
// 0 ≤ H < 60 | ||
case 0: | ||
tempColor = { | ||
r: C, | ||
g: X, | ||
b: 0, | ||
}; | ||
break; | ||
// 60 ≤ H < 120 | ||
case 1: | ||
tempColor = { | ||
r: X, | ||
g: C, | ||
b: 0, | ||
}; | ||
break; | ||
// 120 ≤ H < 180 | ||
case 2: | ||
tempColor = { | ||
r: 0, | ||
g: C, | ||
b: X, | ||
}; | ||
break; | ||
// 180 ≤ H < 240 | ||
case 3: | ||
tempColor = { | ||
r: 0, | ||
g: X, | ||
b: C, | ||
}; | ||
break; | ||
// 240 ≤ H < 300 | ||
case 4: | ||
tempColor = { | ||
r: X, | ||
g: 0, | ||
b: C, | ||
}; | ||
break; | ||
// 300 ≤ H < 360 | ||
default: | ||
tempColor = { | ||
r: C, | ||
g: 0, | ||
b: X, | ||
}; | ||
} | ||
return { | ||
r: Math.floor((tempColor.r + m) * 255), | ||
g: Math.floor((tempColor.g + m) * 255), | ||
b: Math.floor((tempColor.b + m) * 255), | ||
}; | ||
const HSLToRGB = (color) => { | ||
// Formula taken from https://www.rapidtables.com/convert/color/hsl-to-rgb.html | ||
let saturation = color.s * 100, lightness = color.l * 100, red, green, blue; | ||
if (saturation > 100) { | ||
saturation = 1; | ||
} | ||
else if (saturation < 0) { | ||
saturation = 0; | ||
} | ||
else { | ||
saturation /= 100; | ||
} | ||
if (lightness > 100) { | ||
lightness = 1; | ||
} | ||
else if (lightness < 0) { | ||
lightness = 0; | ||
} | ||
else { | ||
lightness /= 100; | ||
} | ||
const hue = color.h, d = saturation * (1 - Math.abs(2 * lightness - 1)), m = 255 * (lightness - 0.5 * d), x = d * (1 - Math.abs(((hue / 60) % 2) - 1)), i = Math.floor(hue / 60), m255x = m + 255 * x, m255d = m + 255 * d; | ||
switch (i) { | ||
case 0: | ||
red = m255d; | ||
green = m255x; | ||
blue = m; | ||
break; | ||
case 1: | ||
red = m255x; | ||
green = m255d; | ||
blue = m; | ||
break; | ||
case 2: | ||
red = m; | ||
green = m255d; | ||
blue = m255x; | ||
break; | ||
case 3: | ||
red = m; | ||
green = m255x; | ||
blue = m255d; | ||
break; | ||
case 4: | ||
red = m255x; | ||
green = m; | ||
blue = m255d; | ||
break; | ||
case 5: | ||
red = m255d; | ||
green = m; | ||
blue = m255x; | ||
break; | ||
default: | ||
red = 0; | ||
green = 0; | ||
blue = 0; | ||
break; | ||
} | ||
return { | ||
r: Math.round(red), | ||
g: Math.round(green), | ||
b: Math.round(blue), | ||
}; | ||
}; | ||
const HEXToRGB = hex => { | ||
// Please make sure you pass a valid 6 digit hex color | ||
// In the implementation of this method we assume that the hex argument is a 6 digit valid hex color | ||
const rgbValues = { | ||
r: hex.substr(0, 2), | ||
g: hex.substr(2, 2), | ||
b: hex.substr(4, 2), | ||
}; | ||
const rgbKeys = Object.keys(rgbValues); | ||
rgbKeys.forEach(key => { | ||
rgbValues[key] = parseInt(rgbValues[key], 16); | ||
}); | ||
return rgbValues; | ||
const HEXToRGB = (hex) => { | ||
// Please make sure you pass a valid 6 digit hex color | ||
// In the implementation of this method we assume that the hex argument is a 6 digit valid hex color | ||
return { | ||
r: parseInt(hex.substr(0, 2), 16), | ||
g: parseInt(hex.substr(2, 2), 16), | ||
b: parseInt(hex.substr(4, 2), 16), | ||
}; | ||
}; | ||
const RGBToHSL = (color = { | ||
r: undefined, | ||
g: undefined, | ||
b: undefined, | ||
}) => { | ||
const R = color.r / 255, | ||
G = color.g / 255, | ||
B = color.b / 255, | ||
max = Math.max(R, G, B), | ||
min = Math.min(R, G, B), | ||
delta = max - min; | ||
let h, | ||
s; | ||
// Hue calculation | ||
if (delta === 0) { | ||
h = 0; | ||
} else if (max === R) { | ||
h = 60 * (((G - B) / delta) % 6); | ||
} else if (max === G) { | ||
h = 60 * (((B - R) / delta) + 2); | ||
} else if (max === B) { | ||
h = 60 * (((R - G) / delta) + 4); | ||
} | ||
// Lightness calculation | ||
const l = (max + min) / 2; | ||
// Saturation calculation | ||
if (delta === 0) { | ||
s = 0; | ||
} else { | ||
s = delta / (1 - Math.abs(2 * l - 1)); | ||
} | ||
return { | ||
h, | ||
s, | ||
l, | ||
}; | ||
/** | ||
* Returns the hex value of the color as string | ||
* @param {Object} color Receives an object with the properties for each of the main colors(r, g, b) | ||
*/ | ||
const RGBtoHEX = (color) => { | ||
const hexMap = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E"]; | ||
let hexValue = "#"; | ||
let divisionNumber = color.r / 16; | ||
let remainder = color.r % 16; | ||
hexValue += String(hexMap[Math.floor(divisionNumber)]); | ||
hexValue += String(hexMap[remainder]); | ||
divisionNumber = color.g / 16; | ||
remainder = color.g % 16; | ||
hexValue += String(hexMap[Math.floor(divisionNumber)]); | ||
hexValue += String(hexMap[remainder]); | ||
divisionNumber = color.b / 16; | ||
remainder = color.b % 16; | ||
hexValue += String(hexMap[Math.floor(divisionNumber)]); | ||
hexValue += String(hexMap[remainder]); | ||
return hexValue; | ||
}; | ||
export { | ||
getRGBColor, | ||
HSLToRGB, | ||
HEXToRGB, | ||
RGBToHSL, | ||
const RGBToHSL = (color) => { | ||
const R = color.r / 255, G = color.g / 255, B = color.b / 255, max = Math.max(R, G, B), min = Math.min(R, G, B), delta = max - min; | ||
let h = 0, s; | ||
// Hue calculation | ||
if (delta === 0) { | ||
h = 0; | ||
} | ||
else if (max === R) { | ||
h = 60 * (((G - B) / delta) % 6); | ||
} | ||
else if (max === G) { | ||
h = 60 * (((B - R) / delta) + 2); | ||
} | ||
else if (max === B) { | ||
h = 60 * (((R - G) / delta) + 4); | ||
} | ||
// Lightness calculation | ||
const l = (max + min) / 2; | ||
// Saturation calculation | ||
if (delta === 0) { | ||
s = 0; | ||
} | ||
else { | ||
s = delta / (1 - Math.abs(2 * l - 1)); | ||
} | ||
return { | ||
h, | ||
s, | ||
l, | ||
}; | ||
}; | ||
export { getRGBColor, HSLToRGB, HEXToRGB, RGBToHSL, RGBStringToRGBObject, RGBtoHEX, }; | ||
//# sourceMappingURL=ColorConversion.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 |
@@ -6,13 +6,11 @@ /** | ||
*/ | ||
let debounceInterval = null; | ||
const debounce = (fn, delay) => { | ||
clearTimeout(debounceInterval); | ||
debounceInterval = setTimeout(() => { | ||
debounceInterval = null; | ||
fn(); | ||
}, delay); | ||
debounceInterval && clearTimeout(debounceInterval); | ||
debounceInterval = setTimeout(() => { | ||
debounceInterval = null; | ||
fn(); | ||
}, delay); | ||
}; | ||
export default debounce; | ||
//# sourceMappingURL=debounce.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,73 +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 = async 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 = async 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 = async (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; | ||
/* eslint-disable no-await-in-loop */ | ||
while (child) { | ||
const originalChild = child; | ||
if (child.isUI5Element) { | ||
child = await child.getFocusDomRefAsync(); | ||
} | ||
if (!child) { | ||
return null; | ||
} | ||
if (child.nodeType === 1 && !isNodeHidden(child) && !isFocusTrap(child)) { | ||
if (isNodeClickable(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; | ||
} | ||
/* eslint-enable no-await-in-loop */ | ||
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 |
const getActiveElement = () => { | ||
let element = document.activeElement; | ||
while (element && element.shadowRoot && element.shadowRoot.activeElement) { | ||
element = element.shadowRoot.activeElement; | ||
} | ||
return element; | ||
let element = document.activeElement; | ||
while (element && element.shadowRoot && element.shadowRoot.activeElement) { | ||
element = element.shadowRoot.activeElement; | ||
} | ||
return element; | ||
}; | ||
export default getActiveElement; | ||
//# sourceMappingURL=getActiveElement.js.map |
@@ -1,5 +0,10 @@ | ||
const getClassCopy = klass => { | ||
return class classCopy extends klass {}; | ||
const getClassCopy = (klass, constructorCallback) => { | ||
return class classCopy extends klass { | ||
constructor() { | ||
super(); | ||
constructorCallback && constructorCallback(); | ||
} | ||
}; | ||
}; | ||
export default getClassCopy; | ||
//# sourceMappingURL=getClassCopy.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 |
const GLOBAL_CONTENT_DENSITY_CSS_VAR = "--_ui5_content_density"; | ||
const getEffectiveContentDensity = el => getComputedStyle(el).getPropertyValue(GLOBAL_CONTENT_DENSITY_CSS_VAR); | ||
const getEffectiveContentDensity = (el) => getComputedStyle(el).getPropertyValue(GLOBAL_CONTENT_DENSITY_CSS_VAR); | ||
export default getEffectiveContentDensity; | ||
//# sourceMappingURL=getEffectiveContentDensity.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 |
@@ -5,12 +5,9 @@ /** | ||
*/ | ||
const isElementInView = el => { | ||
const rect = el.getBoundingClientRect(); | ||
return ( | ||
rect.top >= 0 && rect.left >= 0 | ||
&& rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) | ||
&& rect.right <= (window.innerWidth || document.documentElement.clientWidth) | ||
); | ||
const isElementInView = (el) => { | ||
const rect = el.getBoundingClientRect(); | ||
return (rect.top >= 0 && rect.left >= 0 | ||
&& rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) | ||
&& rect.right <= (window.innerWidth || document.documentElement.clientWidth)); | ||
}; | ||
export default isElementInView; | ||
//# sourceMappingURL=isElementInView.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 allowList = [ | ||
"disabled", | ||
"title", | ||
"hidden", | ||
"role", | ||
"draggable", | ||
"disabled", | ||
"title", | ||
"hidden", | ||
"role", | ||
"draggable", | ||
]; | ||
/** | ||
@@ -17,14 +16,14 @@ * Checks whether a property name is valid (does not collide with existing DOM API properties) | ||
*/ | ||
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 | ||
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 |
import getSharedResource from "../getSharedResource.js"; | ||
import { getFeature } from "../FeaturesRegistry.js"; | ||
import getActiveElement from "./getActiveElement.js"; | ||
const PopupUtilsData = getSharedResource("PopupUtilsData", {}); | ||
PopupUtilsData.currentZIndex = PopupUtilsData.currentZIndex || 100; | ||
const popupUtilsData = getSharedResource("PopupUtilsData", { currentZIndex: 100 }); | ||
const getFocusedElement = () => { | ||
const element = getActiveElement(); | ||
return (element && typeof element.focus === "function") ? element : null; | ||
const element = getActiveElement(); | ||
return (element && typeof element.focus === "function") ? element : null; | ||
}; | ||
const isFocusedElementWithinNode = node => { | ||
const fe = getFocusedElement(); | ||
if (fe) { | ||
return isNodeContainedWithin(node, fe); | ||
} | ||
return false; | ||
const isFocusedElementWithinNode = (node) => { | ||
const fe = getFocusedElement(); | ||
if (fe) { | ||
return isNodeContainedWithin(node, fe); | ||
} | ||
return false; | ||
}; | ||
const isNodeContainedWithin = (parent, child) => { | ||
let currentNode = parent; | ||
if (currentNode.shadowRoot) { | ||
currentNode = Array.from(currentNode.shadowRoot.children).find(n => n.localName !== "style"); | ||
} | ||
if (currentNode === child) { | ||
return true; | ||
} | ||
const childNodes = currentNode.localName === "slot" ? currentNode.assignedNodes() : currentNode.children; | ||
if (childNodes) { | ||
return Array.from(childNodes).some(n => isNodeContainedWithin(n, child)); | ||
} | ||
let currentNode = parent; | ||
if (currentNode.shadowRoot) { | ||
const children = Array.from(currentNode.shadowRoot.children); | ||
currentNode = children.find(n => n.localName !== "style"); | ||
if (!currentNode) { | ||
return false; | ||
} | ||
} | ||
if (currentNode === child) { | ||
return true; | ||
} | ||
const childNodes = currentNode.localName === "slot" ? currentNode.assignedNodes() : currentNode.children; | ||
if (childNodes) { | ||
return Array.from(childNodes).some(n => isNodeContainedWithin(n, child)); | ||
} | ||
return false; | ||
}; | ||
const isPointInRect = (x, y, rect) => { | ||
return x >= rect.left && x <= rect.right | ||
&& y >= rect.top && y <= rect.bottom; | ||
return x >= rect.left && x <= rect.right | ||
&& y >= rect.top && y <= rect.bottom; | ||
}; | ||
const isClickInRect = (event, rect) => { | ||
let x; | ||
let y; | ||
if (event.touches) { | ||
const touch = event.touches[0]; | ||
x = touch.clientX; | ||
y = touch.clientY; | ||
} else { | ||
x = event.clientX; | ||
y = event.clientY; | ||
} | ||
return isPointInRect(x, y, rect); | ||
const isClickInRect = (e, rect) => { | ||
let x; | ||
let y; | ||
if (e instanceof MouseEvent) { | ||
x = e.clientX; | ||
y = e.clientY; | ||
} | ||
else { | ||
const touch = e.touches[0]; | ||
x = touch.clientX; | ||
y = touch.clientY; | ||
} | ||
return isPointInRect(x, y, rect); | ||
}; | ||
const getClosedPopupParent = el => { | ||
const parent = el.parentElement || (el.getRootNode && el.getRootNode().host); | ||
if (parent && ((parent.openBy && parent.isUI5Element) || (parent.open && parent.isUI5Element) || parent === document.documentElement)) { | ||
return parent; | ||
} | ||
return getClosedPopupParent(parent); | ||
function instanceOfPopup(object) { | ||
return "isUI5Element" in object && "_show" in object; | ||
} | ||
const getClosedPopupParent = (el) => { | ||
const parent = el.parentElement || (el.getRootNode && el.getRootNode().host); | ||
if (parent && ((instanceOfPopup(parent) || parent === document.documentElement))) { | ||
return parent; | ||
} | ||
return getClosedPopupParent(parent); | ||
}; | ||
const getNextZIndex = () => { | ||
const OpenUI5Support = getFeature("OpenUI5Support"); | ||
if (OpenUI5Support && OpenUI5Support.isLoaded()) { // use OpenUI5 for getting z-index values, if loaded | ||
return OpenUI5Support.getNextZIndex(); | ||
} | ||
PopupUtilsData.currentZIndex += 2; | ||
return PopupUtilsData.currentZIndex; | ||
const openUI5Support = getFeature("OpenUI5Support"); | ||
if (openUI5Support && openUI5Support.isOpenUI5Detected()) { // use OpenUI5 for getting z-index values, if loaded | ||
return openUI5Support.getNextZIndex(); | ||
} | ||
popupUtilsData.currentZIndex += 2; | ||
return popupUtilsData.currentZIndex; | ||
}; | ||
const getCurrentZIndex = () => { | ||
return PopupUtilsData.currentZIndex; | ||
return popupUtilsData.currentZIndex; | ||
}; | ||
export { | ||
getFocusedElement, | ||
isClickInRect, | ||
getClosedPopupParent, | ||
getNextZIndex, | ||
getCurrentZIndex, | ||
isFocusedElementWithinNode, | ||
}; | ||
export { getFocusedElement, isClickInRect, getClosedPopupParent, getNextZIndex, getCurrentZIndex, isFocusedElementWithinNode, }; | ||
//# sourceMappingURL=PopupUtils.js.map |
@@ -6,39 +6,26 @@ /** | ||
*/ | ||
const getSlotName = node => { | ||
// Text nodes can only go to the default slot | ||
if (!(node instanceof HTMLElement)) { | ||
return "default"; | ||
} | ||
// Discover the slot based on the real slot name (f.e. footer => footer, or content-32 => content) | ||
const slot = node.getAttribute("slot"); | ||
if (slot) { | ||
const match = slot.match(/^(.+?)-\d+$/); | ||
return match ? match[1] : slot; | ||
} | ||
// Use default slot as a fallback | ||
return "default"; | ||
const getSlotName = (node) => { | ||
// Text nodes can only go to the default slot | ||
if (!(node instanceof HTMLElement)) { | ||
return "default"; | ||
} | ||
// Discover the slot based on the real slot name (f.e. footer => footer, or content-32 => content) | ||
const slot = node.getAttribute("slot"); | ||
if (slot) { | ||
const match = slot.match(/^(.+?)-\d+$/); | ||
return match ? match[1] : slot; | ||
} | ||
// Use default slot as a fallback | ||
return "default"; | ||
}; | ||
const isSlot = el => el && el instanceof HTMLElement && el.localName === "slot"; | ||
const getSlottedElements = el => { | ||
if (isSlot(el)) { | ||
return el.assignedNodes({ flatten: true }).filter(item => item instanceof HTMLElement); | ||
} | ||
return [el]; | ||
const getSlottedNodes = (node) => { | ||
if (node instanceof HTMLSlotElement) { | ||
return node.assignedNodes({ flatten: true }).filter(item => item instanceof HTMLElement); | ||
} | ||
return [node]; | ||
}; | ||
const getSlottedElementsList = elList => { | ||
const reducer = (acc, curr) => acc.concat(getSlottedElements(curr)); | ||
return elList.reduce(reducer, []); | ||
const getSlottedNodesList = (nodeList) => { | ||
return nodeList.reduce((acc, curr) => acc.concat(getSlottedNodes(curr)), []); | ||
}; | ||
export { | ||
getSlotName, | ||
isSlot, | ||
getSlottedElements, | ||
getSlottedElementsList, | ||
}; | ||
export { getSlotName, getSlottedNodes, getSlottedNodesList, }; | ||
//# sourceMappingURL=SlotsHelper.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-20acae2ee", | ||
"version": "0.0.0-20ff83d14", | ||
"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,19 +37,24 @@ "clean": "nps clean", | ||
"build": "nps build", | ||
"generate": "nps generate", | ||
"generateAPI": "nps generateAPI", | ||
"bundle": "nps build.bundle", | ||
"test": "nps test", | ||
"hash": "nps hash", | ||
"prepublishOnly": "npm run clean && npm run build" | ||
"prepublishOnly": "tsc -b" | ||
}, | ||
"dependencies": { | ||
"lit-html": "^1.3.0" | ||
"@lit-labs/ssr-dom-shim": "^1.1.2", | ||
"lit-html": "^2.0.1" | ||
}, | ||
"devDependencies": { | ||
"@ui5/webcomponents-tools": "0.0.0-20acae2ee", | ||
"array-uniq": "^2.0.0", | ||
"chromedriver": "89.0.0", | ||
"@ui5/webcomponents-tools": "0.0.0-20ff83d14", | ||
"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", | ||
"eslint-config-airbnb-base": "^14.2.1", | ||
"npm-run-all": "^4.1.5", | ||
"path-exists-cli": "^1.0.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
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
1699326
513
22897
77
4
Yes
2
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)
- Removedlit-html@1.4.1(transitive)
Updatedlit-html@^2.0.1