@ui5/webcomponents-base 2.20.0 → 2.21.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/.tsbuildinfobuild +1 -1
  3. package/dist/CustomElementsScopeUtils.d.ts +2 -2
  4. package/dist/CustomElementsScopeUtils.js +3 -3
  5. package/dist/CustomElementsScopeUtils.js.map +1 -1
  6. package/dist/Runtimes.d.ts +1 -0
  7. package/dist/Runtimes.js +1 -0
  8. package/dist/Runtimes.js.map +1 -1
  9. package/dist/createReactComponent.d.ts +26 -0
  10. package/dist/createReactComponent.js +51 -0
  11. package/dist/createReactComponent.js.map +1 -0
  12. package/dist/custom-elements-internal.json +15 -0
  13. package/dist/custom-elements.json +15 -0
  14. package/dist/generated/VersionInfo.js +4 -4
  15. package/dist/generated/VersionInfo.js.map +1 -1
  16. package/dist/generated/css/FontFace.css.d.ts +1 -1
  17. package/dist/generated/css/FontFace.css.js +36 -36
  18. package/dist/generated/css/FontFace.css.js.map +1 -1
  19. package/dist/prod/CustomElementsScopeUtils.js +1 -1
  20. package/dist/prod/CustomElementsScopeUtils.js.map +2 -2
  21. package/dist/prod/Runtimes.js +1 -1
  22. package/dist/prod/Runtimes.js.map +2 -2
  23. package/dist/prod/createReactComponent.js +2 -0
  24. package/dist/prod/createReactComponent.js.map +7 -0
  25. package/dist/prod/generated/VersionInfo.js +1 -1
  26. package/dist/prod/generated/VersionInfo.js.map +2 -2
  27. package/dist/prod/generated/css/FontFace.css.js +36 -36
  28. package/dist/prod/generated/css/FontFace.css.js.map +1 -1
  29. package/dist/react18/createReactComponent.d.ts +38 -0
  30. package/dist/react18/createReactComponent.js +112 -0
  31. package/dist/react18/createReactComponent.js.map +1 -0
  32. package/package.json +12 -4
  33. package/src/react18/createReactComponent.tsx +140 -0
@@ -0,0 +1,140 @@
1
+ /**
2
+ * React wrapper factory for UI5 Web Components.
3
+ *
4
+ * This lightweight factory creates typed React components that wrap UI5 Web Components.
5
+ * It handles:
6
+ * - Event prop conversion (onXxx → ui5-xxx event listeners)
7
+ * - Ref forwarding
8
+ * - Children handling
9
+ *
10
+ * Note: The function supports react >= 18.
11
+ * Note: This is for documentation samples only - for production React apps,
12
+ * use the official @ui5/webcomponents-react library.
13
+ */
14
+
15
+ import * as React from "react";
16
+ import type { ReactNode } from "react";
17
+ import {
18
+ useRef,
19
+ useEffect,
20
+ forwardRef,
21
+ } from "react";
22
+ import type UI5Element from "../UI5Element.js";
23
+
24
+ type EventHandler<E = Event> = (event: E) => void;
25
+
26
+ // Interface for UI5 Web Component classes with _jsxProps support
27
+ interface UI5ComponentClass<T extends UI5Element = UI5Element> {
28
+ new (): T;
29
+ getMetadata(): {
30
+ getTag(): string;
31
+ };
32
+ }
33
+
34
+ // Helper to convert event name
35
+ const toEventName = (propName: string): string => {
36
+ return propName
37
+ .slice(2) // Remove "on"
38
+ .replace(/([A-Z])/g, (match: string, letter: string, index: number): string => {
39
+ return index === 0 ? letter.toLowerCase() : `-${letter.toLowerCase()}`;
40
+ });
41
+ };
42
+
43
+ // Helper to create cleanup function for event listener
44
+ const createEventCleanup = (element: UI5Element, eventName: string, handler: EventHandler): (() => void) => {
45
+ element.addEventListener(eventName, handler);
46
+ return () => element.removeEventListener(eventName, handler);
47
+ };
48
+
49
+ /**
50
+ * Creates a React component wrapper for a UI5 Web Component.
51
+ * Uses the component's _jsxProps type for full TypeScript support.
52
+ *
53
+ * @param ComponentClass - The UI5 Web Component class (e.g., Button from "@ui5/webcomponents/dist/Button.js")
54
+ * @returns A React component that renders the custom element with proper TypeScript types
55
+ * @since 2.21.0
56
+ * @example
57
+ * import Button from "@ui5/webcomponents/dist/Button.js";
58
+ * const ReactButton = createReactComponent(Button);
59
+ * // ReactButton props are typed based on Button's _jsxProps
60
+ */
61
+ function createReactComponent<T extends UI5Element>(
62
+ ComponentClass: UI5ComponentClass<T>,
63
+ ): React.ForwardRefExoticComponent<
64
+ React.PropsWithoutRef<T["_jsxProps"] & { children?: ReactNode }> & React.RefAttributes<T>
65
+ > {
66
+ const tagName = ComponentClass.getMetadata().getTag();
67
+
68
+ const Component = forwardRef<T, T["_jsxProps"] & { children?: ReactNode }>((props, ref) => {
69
+ const { children, ...restProps } = props;
70
+ const elementRef = useRef<T>(null);
71
+
72
+ // Forward ref
73
+ useEffect(() => {
74
+ if (ref) {
75
+ if (typeof ref === "function") {
76
+ ref(elementRef.current);
77
+ } else {
78
+ ref.current = elementRef.current;
79
+ }
80
+ }
81
+ }, [ref]);
82
+
83
+ // Handle event props and boolean props imperatively
84
+ useEffect(() => {
85
+ const element = elementRef.current;
86
+ if (!element) {
87
+ return;
88
+ }
89
+
90
+ const eventCleanups: Array<() => void> = [];
91
+
92
+ Object.keys(restProps).forEach(propName => {
93
+ const propValue = (restProps as Record<string, unknown>)[propName];
94
+ if (propName.startsWith("on") && typeof propValue === "function") {
95
+ // Convert React event naming (onClick, onSelectionChange) to DOM event naming
96
+ // onClick -> click, onSelectionChange -> selection-change
97
+ const eventName = toEventName(propName);
98
+ const handler = propValue as EventHandler;
99
+ eventCleanups.push(createEventCleanup(element, eventName, handler));
100
+ } else if (typeof propValue === "boolean") {
101
+ // React 18 sets false booleans as empty string attributes on custom elements.
102
+ // Set as property directly to avoid this.
103
+ (element as any)[propName] = propValue;
104
+ }
105
+ });
106
+
107
+ return () => {
108
+ eventCleanups.forEach(cleanup => cleanup());
109
+ };
110
+ }, [restProps]);
111
+
112
+ // Filter out event handlers and booleans from DOM props
113
+ const domProps: Record<string, unknown> = {};
114
+ Object.keys(restProps).forEach(propName => {
115
+ const propValue = (restProps as Record<string, unknown>)[propName];
116
+ if (propName.startsWith("on") && typeof propValue === "function") { return; }
117
+ if (typeof propValue === "boolean") { return; } // handled in useEffect
118
+ // className → class for React compatibility
119
+ if (propName === "className") {
120
+ // eslint-disable-next-line dot-notation
121
+ domProps["class"] = propValue;
122
+ return;
123
+ }
124
+ // Convert camelCase to kebab-case for HTML attributes
125
+ const attrName = propName.replace(/([A-Z])/g, "-$1").toLowerCase();
126
+ domProps[attrName] = propValue;
127
+ });
128
+
129
+ return React.createElement(tagName, { ref: elementRef, ...domProps }, children);
130
+ });
131
+
132
+ Component.displayName = tagName
133
+ .split("-")
134
+ .map(part => part.charAt(0).toUpperCase() + part.slice(1))
135
+ .join("");
136
+
137
+ return Component;
138
+ }
139
+
140
+ export default createReactComponent;