Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion .storybook/custom-element-manifests/fiori.json
Original file line number Diff line number Diff line change
Expand Up @@ -1721,7 +1721,44 @@
{
"kind": "javascript-module",
"path": "dist/types/TimelineGrowingMode.js",
"declarations": [],
"declarations": [
{
"kind": "enum",
"name": "TimelineGrowingMode",
"description": "Timeline growing modes.",
"_ui5privacy": "public",
"_ui5since": "2.7.0",
"members": [
{
"kind": "field",
"static": true,
"privacy": "public",
"description": "Event `load-more` is fired\nupon pressing a \"More\" button at the end.",
"default": "Button",
"name": "Button",
"readonly": true
},
{
"kind": "field",
"static": true,
"privacy": "public",
"description": "Event `load-more` is fired upon scroll.",
"default": "Scroll",
"name": "Scroll",
"readonly": true
},
{
"kind": "field",
"static": true,
"privacy": "public",
"description": "The growing feature is not enabled.",
"default": "None",
"name": "None",
"readonly": true
}
]
}
],
"exports": [
{
"kind": "js",
Expand Down
12 changes: 6 additions & 6 deletions .storybook/custom-element-manifests/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -754,7 +754,7 @@
"kind": "field",
"static": true,
"privacy": "public",
"description": "The badge is displayed at the top-end corner of the button.\n\n**Note:** It's highly recommended to use the OverlayText design mode only in cozy density.",
"description": "The badge is displayed at the top-end corner of the button.\n\n**Note:** According to design guidance, the OverlayText design mode is best used in cozy density to avoid potential visual issues in compact.",
"default": "OverlayText",
"name": "OverlayText",
"readonly": true
Expand Down Expand Up @@ -5251,7 +5251,7 @@
"declarations": [
{
"kind": "class",
"description": "The `ui5-button-badge` component defines a badge that appears in the `ui5-button`.\n\n * ### ES6 Module Import\n\n`import \"@ui5/webcomponents/dist/ButtonBadge.js\";`",
"description": "The `ui5-button-badge` component defines a badge that appears in the `ui5-button`.\n\n### ES6 Module Import\n\n`import \"@ui5/webcomponents/dist/ButtonBadge.js\";`",
"name": "ButtonBadge",
"members": [
{
Expand All @@ -5268,7 +5268,7 @@
]
},
"default": "\"AttentionDot\"",
"description": "Determines where the badge should be placed and how it should be styled.",
"description": "Defines the badge placement and appearance.\n- **InlineText** - displayed inside the button after its text, and recommended for **compact** density.\n- **OverlayText** - displayed at the top-end corner of the button, and recommended for **cozy** density.\n- **AttentionDot** - displayed at the top-end corner of the button as a dot, and suitable for both **cozy** and **compact** densities.",
"privacy": "public",
"_ui5since": "2.7.0"
},
Expand All @@ -5279,14 +5279,14 @@
"text": "string"
},
"default": "\"\"",
"description": "Defines the text of the component.\n\n**Note:** Text is not needed when the `design` property is set to `AttentionDot`.",
"description": "Defines the text of the component.\n\n**Note:** Text is not applied when the `design` property is set to `AttentionDot`.",
"privacy": "public",
"_ui5since": "2.7.0"
}
],
"attributes": [
{
"description": "Determines where the badge should be placed and how it should be styled.",
"description": "Defines the badge placement and appearance.\n- **InlineText** - displayed inside the button after its text, and recommended for **compact** density.\n- **OverlayText** - displayed at the top-end corner of the button, and recommended for **cozy** density.\n- **AttentionDot** - displayed at the top-end corner of the button as a dot, and suitable for both **cozy** and **compact** densities.",
"name": "design",
"default": "\"AttentionDot\"",
"fieldName": "design",
Expand All @@ -5295,7 +5295,7 @@
}
},
{
"description": "Defines the text of the component.\n\n**Note:** Text is not needed when the `design` property is set to `AttentionDot`.",
"description": "Defines the text of the component.\n\n**Note:** Text is not applied when the `design` property is set to `AttentionDot`.",
"name": "text",
"default": "\"\"",
"fieldName": "text",
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
"prettier:all": "prettier --write --config ./prettier.config.cjs \"packages/**/*.{js,jsx,ts,tsx,mdx,json,md}\"",
"lint": "eslint packages",
"lerna:version-dryrun": "lerna version --conventional-graduate --no-git-tag-version --no-push",
"wrappers:main": "WITH_WEB_COMPONENT_IMPORT_PATH='../../internal/withWebComponent.js' INTERFACES_IMPORT_PATH='../../types/index.js' node packages/cli/dist/bin/index.js create-wrappers --packageName @ui5/webcomponents --out ./packages/main/src/webComponents --additionalComponentNote 'This is a UI5 Web Component! [Repository](https://github.com/SAP/ui5-webcomponents) | [Documentation](https://sap.github.io/ui5-webcomponents/)'",
"wrappers:fiori": "WITH_WEB_COMPONENT_IMPORT_PATH='../../internal/withWebComponent.js' INTERFACES_IMPORT_PATH='../../types/index.js' node packages/cli/dist/bin/index.js create-wrappers --packageName @ui5/webcomponents-fiori --out ./packages/main/src/webComponents --additionalComponentNote 'This is a UI5 Web Component! [Repository](https://github.com/SAP/ui5-webcomponents) | [Documentation](https://sap.github.io/ui5-webcomponents/)'",
"wrappers:compat": "WITH_WEB_COMPONENT_IMPORT_PATH='@ui5/webcomponents-react/dist/internal/withWebComponent.js' node packages/cli/dist/bin/index.js create-wrappers --packageName @ui5/webcomponents-compat --out ./packages/compat/src/components --additionalComponentNote 'This is a UI5 Web Component! [Repository](https://github.com/SAP/ui5-webcomponents) | [Documentation](https://sap.github.io/ui5-webcomponents/)' && prettier --log-level silent --write ./packages/compat/src/components",
"wrappers:main": "node packages/cli/dist/bin/index.js create-wrappers --packageName @ui5/webcomponents --out ./packages/main/src/webComponents --additionalComponentNote 'This is a UI5 Web Component! [Repository](https://github.com/SAP/ui5-webcomponents) | [Documentation](https://sap.github.io/ui5-webcomponents/)'",
"wrappers:fiori": "node packages/cli/dist/bin/index.js create-wrappers --packageName @ui5/webcomponents-fiori --out ./packages/main/src/webComponents --additionalComponentNote 'This is a UI5 Web Component! [Repository](https://github.com/SAP/ui5-webcomponents) | [Documentation](https://sap.github.io/ui5-webcomponents/)'",
"wrappers:compat": "WITH_WEB_COMPONENT_IMPORT_PATH='@ui5/webcomponents-react-base/dist/wrapper/withWebComponent.js' node packages/cli/dist/bin/index.js create-wrappers --packageName @ui5/webcomponents-compat --out ./packages/compat/src/components --additionalComponentNote 'This is a UI5 Web Component! [Repository](https://github.com/SAP/ui5-webcomponents) | [Documentation](https://sap.github.io/ui5-webcomponents/)' && prettier --log-level silent --write ./packages/compat/src/components",
"create-webcomponents-wrapper": "(cd packages/cli && tsc) && yarn run wrappers:main && yarn run wrappers:fiori && prettier --log-level silent --write ./packages/main/src/webComponents && eslint --fix ./packages/main/src/webComponents/*/index.tsx && yarn run sb:prepare-cem",
"create-webcomponents-wrapper-compat": "(cd packages/cli && tsc) && yarn run wrappers:compat && yarn run sb:prepare-cem && eslint --fix ./packages/compat/src/components/*/index.tsx",
"chromatic": "cross-env STORYBOOK_ENV=chromatic npx chromatic --build-script-name build:storybook",
Expand Down
1 change: 0 additions & 1 deletion packages/base/src/Device/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ const handleMobileTimeout = () => {

const handleMobileOrientationResizeChange = (evt) => {
if (evt.type === 'resize') {
// @ts-expect-error: undefined is fine here
if (rInputTagRegex.test(document.activeElement?.tagName) && !bOrientationChange) {
return;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/base/src/hooks/useStylesheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function useStylesheet(styles: string, componentName: string, options?: U
return () => {
if (shouldInject) {
StyleStore.unmountComponent(componentName);
const numberOfMountedComponents = componentsMap.get(componentName)!;
const numberOfMountedComponents = componentsMap.get(componentName);
if (numberOfMountedComponents <= 0) {
removeStyle('data-ui5wcr-component', scopedComponentName);
}
Expand Down
6 changes: 5 additions & 1 deletion packages/base/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import * as hooks from './hooks/index.js';
import { I18nStore } from './stores/I18nStore.js';
import { StyleStore } from './stores/StyleStore.js';
import { ThemingParameters } from './styling/ThemingParameters.js';
import { withWebComponent } from './wrapper/withWebComponent.js';
import type { WithWebComponentPropTypes } from './wrapper/withWebComponent.js';

export * from './styling/CssSizeVariables.js';
export * from './utils/index.js';
export * from './hooks/index.js';
export type * from './types/index.js';

export { I18nStore, StyleStore, ThemingParameters, Device, hooks };
export { I18nStore, StyleStore, ThemingParameters, Device, hooks, withWebComponent };
export type { WithWebComponentPropTypes };
export const version = VersionInfo.version;
4 changes: 2 additions & 2 deletions packages/base/src/stores/StyleStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export const StyleStore = {
mountComponent: (componentName: string) => {
const { componentsMap } = getSnapshot();
if (componentsMap.has(componentName)) {
componentsMap.set(componentName, componentsMap.get(componentName)! + 1);
componentsMap.set(componentName, componentsMap.get(componentName) + 1);
} else {
componentsMap.set(componentName, 1);
}
Expand All @@ -76,7 +76,7 @@ export const StyleStore = {
unmountComponent: (componentName: string) => {
const { componentsMap } = getSnapshot();
if (componentsMap.has(componentName)) {
componentsMap.set(componentName, componentsMap.get(componentName)! - 1);
componentsMap.set(componentName, componentsMap.get(componentName) - 1);
}
emitChange();
}
Expand Down
14 changes: 14 additions & 0 deletions packages/base/src/types/CommonProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { CSSProperties, HTMLAttributes } from 'react';

export interface CommonProps<T = HTMLElement> extends Omit<HTMLAttributes<T>, 'dangerouslySetInnerHTML'> {
/**
* Element style which will be appended to the most outer element of a component.
* Use this prop carefully, some css properties might break the component.
*/
style?: CSSProperties;
/**
* CSS Class Name which will be appended to the most outer element of a component.
* Use this prop carefully, overwriting CSS rules might break the component.
*/
className?: string;
}
5 changes: 5 additions & 0 deletions packages/base/src/types/Ui5CustomEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface Ui5CustomEvent<EventTarget = HTMLElement, Detail = never>
extends Omit<CustomEvent<Detail>, 'target' | 'currentTarget'> {
target: EventTarget;
currentTarget: EventTarget | null;
}
151 changes: 151 additions & 0 deletions packages/base/src/types/Ui5DomRef.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import type { PropertyValue, SlotValue } from '@ui5/webcomponents-base/dist/UI5ElementMetadata.js';

type ChangeInfo = {
type: 'property' | 'slot';
name: string;
reason?: string;
child?: SlotValue;
target?: Ui5DomRef;
newValue?: PropertyValue;
oldValue?: PropertyValue;
};

type InvalidationInfo = ChangeInfo & { target: Ui5DomRef };

export interface Ui5DomRef extends Omit<HTMLElement, 'focus'> {
/**
* Called every time before the component renders.
* @public
*/
onBeforeRendering(): void;
/**
* Called every time after the component renders.
* @public
*/
onAfterRendering(): void;
/**
* Called on connectedCallback - added to the DOM.
* @public
*/
onEnterDOM(): void;
/**
* Called on disconnectedCallback - removed from the DOM.
* @public
*/
onExitDOM(): void;
/**
* Attach a callback that will be executed whenever the component is invalidated
*
* @param callback
* @public
*/
attachInvalidate(callback: (param: InvalidationInfo) => void): void;
/**
* Detach the callback that is executed whenever the component is invalidated
*
* @param callback
* @public
*/
detachInvalidate(callback: (param: InvalidationInfo) => void): void;
/**
* 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: ChangeInfo): void;
/**
* 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(): HTMLElement | undefined;

/**
* 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(): HTMLElement | undefined;

/**
* 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
*/
getFocusDomRefAsync(): Promise<HTMLElement | undefined>;
/**
* Set the focus to the element, returned by "getFocusDomRef()" (marked by "data-sap-focus-ref")
* @param focusOptions additional options for the focus
* @public
*/
focus(focusOptions?: FocusOptions): Promise<void>;
/**
*
* @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<T>(name: string, data?: T, cancelable?: boolean, bubbles?: boolean): boolean;
/**
* 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<T = Node>(slotName: string): Array<T>;
/**
* Attach a callback that will be executed whenever the component's state is finalized
*
* @param callback
* @public
*/
attachComponentStateFinalized(callback: () => void): void;
/**
* Detach the callback that is executed whenever the component's state is finalized
*
* @param callback
* @public
*/
detachComponentStateFinalized(callback: () => void): void;
/**
* Determines whether the component should be rendered in RTL mode or not.
* Returns: "rtl", "ltr" or undefined
*
* @public
* @default undefined
*/
readonly effectiveDir: string | undefined;

/**
* Used to duck-type UI5 elements without using instanceof
* @public
* @default true
*/
readonly isUI5Element: boolean;
}
18 changes: 18 additions & 0 deletions packages/base/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { ReactElement, ReactNode, ReactPortal } from 'react';

export type ReducedReactNode = Exclude<ReactNode, string | number | boolean | ReactPortal | Iterable<ReactNode>>;
export type ReducedReactNodeWithBoolean = Exclude<ReactNode, string | number | ReactPortal | Iterable<ReactNode>>;

type InternalUI5WCSlotsNode =
| ReducedReactNode
| Iterable<ReducedReactNode>
| false
| ReactElement /* necessary for React v16 & v17 ReactNode type*/;

export type UI5WCSlotsNode = InternalUI5WCSlotsNode | InternalUI5WCSlotsNode[];

export type Nullable<T> = T | null;

export type { CommonProps } from './CommonProps.js';
export type { Ui5CustomEvent } from './Ui5CustomEvent.js';
export type { Ui5DomRef } from './Ui5DomRef.js';
26 changes: 23 additions & 3 deletions packages/base/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const enrichEventWithDetails = <
payload: Detail
): EnrichedEventType<Event, Detail> => {
if (!event) {
return event;
return event as EnrichedEventType<Event, Detail>;
}

// Determine if we need to create a new details object
Expand All @@ -41,9 +41,9 @@ export const enrichEventWithDetails = <
});

if (nativeDetail) {
Object.assign(event.detail!, { nativeDetail });
Object.assign(event.detail, { nativeDetail });
}
Object.assign(event.detail!, payload);
Object.assign(event.detail, payload);

return event as EnrichedEventType<Event, Detail>;
};
Expand All @@ -55,3 +55,23 @@ export function getUi5TagWithSuffix(baseTagName: string) {

export { debounce } from './debounce.js';
export { throttle } from './throttle.js';

export const capitalizeFirstLetter = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
export const lowercaseFirstLetter = (s: string) => s.charAt(0).toLowerCase() + s.slice(1);
export const camelToKebabCase = (s: string) => s.replace(/([A-Z])/g, (a, b: string) => `-${b.toLowerCase()}`);
export const kebabToCamelCase = (str: string) => str.replace(/([-_]\w)/g, (g) => g[1].toUpperCase());

const SEMVER_REGEX =
/^(?<major>0|[1-9]\d*)\.(?<minor>0|[1-9]\d*)\.(?<patch>0|[1-9]\d*)(?:-(?<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;

export function parseSemVer(version: string) {
const parsed = SEMVER_REGEX.exec(version).groups;
return {
version,
major: parseInt(parsed.major),
minor: parseInt(parsed.minor),
patch: parseInt(parsed.patch),
prerelease: parsed.prerelease,
buildMetadata: parsed.buildmetadata
};
}
Loading
Loading