Skip to content

Commit 4b1d0fc

Browse files
feat: create dom ref interfaces for all web components (#2292)
BREAKING CHANGE: **TypeScript only**: The `Ui5` prefix for interfaces for using DOM refs (e.g. `Ui5DialogDomRef`) has been removed (now `DialogDomRef`) and the interfaces are now exported from the respective components themselves. For details please check our [Migration Guide](https://sap.github.io/ui5-webcomponents-react/?path=/docs/migration-guide--page#changed-typescript-interfaces-for-ref). Co-authored-by: Harbarth, Lukas <lukas.harbarth@sap.com>
1 parent e6971af commit 4b1d0fc

File tree

241 files changed

+1758
-1215
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

241 files changed

+1758
-1215
lines changed

.eslintrc.cjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,8 @@ module.exports = {
133133
'@typescript-eslint/semi': ['error', 'always'],
134134
'@typescript-eslint/space-within-parens': ['off', 'never'],
135135
'@typescript-eslint/type-annotation-spacing': 'error',
136-
'@typescript-eslint/unified-signatures': 'error'
136+
'@typescript-eslint/unified-signatures': 'error',
137+
'@typescript-eslint/no-empty-interface': 'warn'
137138
}
138139
}
139140
]

docs/1-Welcome.stories.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,10 @@ Example - `Popover` with `Ref` type declaration:
151151
```tsx
152152
import React, { useRef } from 'react';
153153
import { ThemeProvider, Button, Popover } from '@ui5/webcomponents-react';
154-
import { Ui5PopoverDomRef } from '@ui5/webcomponents-react/interfaces/Ui5PopoverDomRef';
154+
import { PopoverDomRef } from '@ui5/webcomponents-react/dist/Popover';
155155

156156
export default function App() {
157-
const popoverRef = useRef<Ui5PopoverDomRef>(null);
157+
const popoverRef = useRef<PopoverDomRef>(null);
158158
const onClick = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
159159
popoverRef.current?.showAt(e.target);
160160
};

docs/2-MigrationGuide.stories.mdx

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,73 @@ or the [changelog](https://github.com/SAP/ui5-webcomponents-react/blob/main/CHAN
1717

1818
<TableOfContent headingSelector="h2.sbdocs-h2" />
1919

20+
## Migrating from 0.19.x to 0.20.0
21+
22+
### Changed TypeScript `interfaces` for `ref`
23+
24+
TypeScript `interfaces` that can be used for accessing the DOM-`ref` of a component have been updated:
25+
26+
- The `Ui5` prefix has been removed
27+
- The `interfaces` can now be imported from the component itself
28+
29+
Migration Path:
30+
31+
```tsx
32+
import { Dialog } from '@ui5/webcomponents-react';
33+
import { Ui5DialogDomRef } from '@ui5/webcomponents-react/interfaces/Ui5DialogDomRef';
34+
import { useRef } from 'react';
35+
36+
function MyOldComponent() {
37+
const ref = useRef<Ui5DialogDomRef>(null);
38+
return <Dialog ref={ref} />;
39+
}
40+
41+
// becomes
42+
43+
import { Dialog } from '@ui5/webcomponents-react';
44+
import { DialogDomRef } from '@ui5/webcomponents-react/dist/Dialog';
45+
import { useRef } from 'react';
46+
47+
function MyNewComponent() {
48+
const ref = useRef<DialogDomRef>(null);
49+
return <Dialog ref={ref} />;
50+
}
51+
```
52+
53+
Affected Interfaces:
54+
55+
- `Ui5BarcodeScannerDialogDomRef` <br />
56+
new import: `import { BarcodeScannerDialogDomRef } from '@ui5/webcomponents-react/dist/BarcodeScannerDialog';`
57+
- `Ui5CarouselDomRef`<br />
58+
new import: `import { CarouselDomRef } from '@ui5/webcomponents-react/dist/Carousel';`
59+
- `Ui5DatePickerDomRef`<br />
60+
new import: `import { DatePickerDomRef } from '@ui5/webcomponents-react/dist/DatePicker';`
61+
- `Ui5DateRangePickerDomRef`<br />
62+
new import: `import { DateRangePickerDomRef } from '@ui5/webcomponents-react/dist/DateRangePicker';`
63+
- `Ui5DateTimePickerDomRef`<br />
64+
new import: `import { DateTimePickerDomRef } from '@ui5/webcomponents-react/dist/DateTimePicker';`
65+
- `Ui5DialogDomRef`<br />
66+
new import: `import { DialogDomRef } from '@ui5/webcomponents-react/dist/Dialog';`
67+
- `Ui5PopoverDomRef`<br />
68+
new import: `import { PopoverDomRef } from '@ui5/webcomponents-react/dist/Popover';`
69+
- `Ui5ResponsivePopoverDomRef`<br />
70+
new import: `import { ResponsivePopoverDomRef } from '@ui5/webcomponents-react/dist/ResponsivePopover';`
71+
- `Ui5ShellBarDomRef`<br />
72+
new import: `import { ShellBarDomRef } from '@ui5/webcomponents-react/dist/ShellBar';`
73+
- `Ui5TimePickerDomRef`<br />
74+
new import: `import { TimePickerDomRef } from '@ui5/webcomponents-react/dist/TimePicker';`
75+
- `Ui5ToastDomRef`<br />
76+
new import: `import { ToastDomRef } from '@ui5/webcomponents-react/dist/Toast';`
77+
- `Ui5TreeDomRef`<br />
78+
new import: `import { TreeDomRef } from '@ui5/webcomponents-react/dist/Tree';`
79+
- `Ui5TreeItemDomRef`<br />
80+
new import: `import { TreeItemDomRef } from '@ui5/webcomponents-react/dist/TreeItem';`
81+
82+
- `AnalyticalTableDomRef`<br />
83+
new import: `import { AnalyticalTableDomRef } from '@ui5/webcomponents-react/dist/AnalyticalTable';`
84+
- `MessageViewDomRef`<br />
85+
new import: `import { MessageViewDomRef } from '@ui5/webcomponents-react/dist/MessageView';`
86+
2087
## Migrating from 0.18.x to 0.19.0
2188

2289
### VariantManagement

packages/main/scripts/create-web-components-wrapper.mjs

Lines changed: 98 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import Handlebars from 'handlebars';
1111
import * as Utils from '../../../scripts/web-component-wrappers/utils.js';
1212
import {
1313
COMPONENTS_WITHOUT_DEMOS,
14+
KNOWN_ATTRIBUTES,
1415
KNOWN_EVENTS,
1516
PRIVATE_COMPONENTS
1617
} from '../../../scripts/web-component-wrappers/config.js';
@@ -291,58 +292,72 @@ const getEventParameters = (name, parameters) => {
291292
};
292293

293294
const createWebComponentWrapper = async (
294-
name,
295-
tag,
295+
componentSpec,
296296
description,
297-
types,
297+
attributes,
298+
slotsAndEvents,
298299
importStatements,
299-
ref,
300300
defaultProps,
301301
regularProps,
302302
booleanProps,
303303
slotProps,
304304
eventProps
305305
) => {
306-
const eventsToBeOmitted = eventProps.filter((eventName) => KNOWN_EVENTS.has(eventName));
306+
const eventsToBeOmitted = eventProps
307+
.filter((eventName) => KNOWN_EVENTS.has(eventName))
308+
.map((eventName) => `'on${capitalizeFirstLetter(snakeToCamel(eventName))}'`);
309+
const attributesToBeOmitted = [...regularProps, ...booleanProps]
310+
.filter((attribute) => KNOWN_ATTRIBUTES.has(attribute))
311+
.map((a) => `'${a}'`);
312+
313+
let domRefExtends = 'Ui5DomRef';
314+
if (attributesToBeOmitted.length > 0) {
315+
domRefExtends = `Omit<Ui5DomRef, ${attributesToBeOmitted.join(' | ')}>`;
316+
}
317+
307318
let tsExtendsStatement = 'CommonProps';
308-
if (eventsToBeOmitted.length > 0) {
309-
tsExtendsStatement = `Omit<CommonProps, ${eventsToBeOmitted
310-
.map((eventName) => `'on${capitalizeFirstLetter(snakeToCamel(eventName))}'`)
311-
.join(' | ')}>`;
319+
if (eventsToBeOmitted.length > 0 || attributesToBeOmitted.length > 0) {
320+
tsExtendsStatement = `Omit<CommonProps, ${[...attributesToBeOmitted, ...eventsToBeOmitted].join(' | ')}>`;
312321
}
313322
let componentDescription;
314323
try {
315324
componentDescription = turndownService.turndown(description).replace(/\n/g, '\n * ');
316325
} catch (e) {
317326
console.warn(
318-
`----------------------\nHeader description of ${name} couldn't be generated. \nThere is probably a syntax error in the associated description that can't be fixed automatically.\n----------------------`
327+
`----------------------\nHeader description of ${componentSpec.module} couldn't be generated. \nThere is probably a syntax error in the associated description that can't be fixed automatically.\n----------------------`
319328
);
320329
componentDescription = '';
321330
}
322331

332+
const domRef = Utils.createDomRef(componentSpec);
333+
323334
const imports = [
324335
...importStatements,
325336
'', // do not remove this empty line - otherwise the eslint/import-order plugin won't work as expected
326-
`import '@ui5/webcomponents${componentsFromFioriPackage.has(name) ? '-fiori' : ''}/dist/${name}.js';`
337+
`import '@ui5/webcomponents${componentsFromFioriPackage.has(componentSpec.module) ? '-fiori' : ''}/dist/${
338+
componentSpec.module
339+
}.js';`
327340
];
328341

329342
return prettier.format(
330343
await Utils.runEsLint(
331344
componentTemplate({
332-
name,
345+
name: componentSpec.module,
333346
imports,
334347
propTypesExtends: tsExtendsStatement,
335-
types,
348+
domRefExtends,
349+
attributes,
350+
slotsAndEvents,
336351
description: componentDescription,
337-
tagName: tag,
352+
tagName: componentSpec.tagname,
338353
regularProps,
339354
booleanProps,
340355
slotProps: slotProps.filter((name) => name !== 'children'),
341356
eventProps,
342357
defaultProps,
343-
ref: ref?.tsType
358+
domRef
344359
}),
345-
name
360+
componentSpec.module
346361
),
347362
Utils.prettierConfig
348363
);
@@ -570,59 +585,55 @@ const resolveInheritedAttributes = (componentSpec) => {
570585
);
571586
});
572587

588+
const propDescription = (componentSpec, property) => {
589+
if (!componentSpec.tagname) {
590+
return property.description || '';
591+
}
592+
let formattedDescription = turndownService.turndown((property.description || '').trim()).replace(/\n/g, '\n * ');
593+
594+
const customDescriptionReplace = CUSTOM_DESCRIPTION_REPLACE[componentSpec.module];
595+
if (customDescriptionReplace && customDescriptionReplace[property.name]) {
596+
formattedDescription = customDescriptionReplace[property.name](formattedDescription);
597+
}
598+
599+
const extendedDescription = EXTENDED_PROP_DESCRIPTION[property.name];
600+
601+
if (property.name !== 'children' && componentSpec?.slots?.some((item) => item.name === property.name)) {
602+
formattedDescription += `
603+
*
604+
* __Note:__ When passing a custom React component to this prop, you have to make sure your component reads the \`slot\` prop and appends it to the most outer element of your component.
605+
* Learn more about it [here](https://sap.github.io/ui5-webcomponents-react/?path=/docs/knowledge-base--page#adding-custom-components-to-slots).`;
606+
}
607+
608+
if (extendedDescription) {
609+
return replaceTagNameWithModuleName(`${formattedDescription}${extendedDescription}`);
610+
}
611+
return replaceTagNameWithModuleName(formattedDescription);
612+
};
613+
573614
allWebComponents
574615
.filter((spec) => spec.visibility === 'public')
575616
.filter((spec) => !PRIVATE_COMPONENTS.has(spec.module))
576617
.map(resolveInheritedAttributes)
577618
.forEach(async (componentSpec) => {
578-
const propTypes = [];
619+
const attributes = [];
620+
const slotsAndEvents = [];
579621
const importStatements = [];
580622
const defaultProps = [];
581-
const allComponentProperties = [...(componentSpec.properties || []), ...(componentSpec.slots || [])]
623+
const allComponentProperties = (componentSpec.properties || [])
582624
.filter((prop) => prop.visibility === 'public' && prop.readonly !== 'true' && prop.static !== true)
583625
.map((property) => {
584626
const tsType = Utils.getTypeDefinitionForProperty(property);
585627
if (tsType.importStatement) {
586628
importStatements.push(tsType.importStatement);
587629
}
588630

589-
if (property.name === 'default') {
590-
property.name = 'children';
591-
}
592-
const propDescription = () => {
593-
if (!componentSpec.tagname) {
594-
return property.description || '';
595-
}
596-
let formattedDescription = turndownService
597-
.turndown((property.description || '').trim())
598-
.replace(/\n/g, '\n * ');
599-
600-
const customDescriptionReplace = CUSTOM_DESCRIPTION_REPLACE[componentSpec.module];
601-
if (customDescriptionReplace && customDescriptionReplace[property.name]) {
602-
formattedDescription = customDescriptionReplace[property.name](formattedDescription);
603-
}
604-
605-
const extendedDescription = EXTENDED_PROP_DESCRIPTION[property.name];
606-
607-
if (property.name !== 'children' && componentSpec?.slots?.some((item) => item.name === property.name)) {
608-
formattedDescription += `
609-
*
610-
* __Note:__ When passing a custom React component to this prop, you have to make sure your component reads the \`slot\` prop and appends it to the most outer element of your component.
611-
* Learn more about it [here](https://sap.github.io/ui5-webcomponents-react/?path=/docs/knowledge-base--page#adding-custom-components-to-slots).`;
612-
}
613-
614-
if (extendedDescription) {
615-
return replaceTagNameWithModuleName(`${formattedDescription}${extendedDescription}`);
616-
}
617-
return replaceTagNameWithModuleName(formattedDescription);
618-
};
619-
620-
propTypes.push(dedent`
621-
/**
622-
* ${propDescription()}
623-
*/
624-
${property.name}?: ${tsType.tsType};
625-
`);
631+
attributes.push(dedent`
632+
/**
633+
* ${propDescription(componentSpec, property)}
634+
*/
635+
${property.name}?: ${tsType.tsType};
636+
`);
626637

627638
if (
628639
property.hasOwnProperty('defaultValue') &&
@@ -646,6 +657,33 @@ allWebComponents
646657
};
647658
});
648659

660+
allComponentProperties.push(
661+
...(componentSpec.slots || [])
662+
.filter((prop) => prop.visibility === 'public' && prop.readonly !== 'true' && prop.static !== true)
663+
.map((property) => {
664+
const tsType = Utils.getTypeDefinitionForProperty(property);
665+
if (tsType.importStatement) {
666+
importStatements.push(tsType.importStatement);
667+
}
668+
669+
if (property.name === 'default') {
670+
property.name = 'children';
671+
}
672+
673+
slotsAndEvents.push(dedent`
674+
/**
675+
* ${propDescription(componentSpec, property)}
676+
*/
677+
${property.name}?: ${tsType.tsType};
678+
`);
679+
680+
return {
681+
...property,
682+
...tsType
683+
};
684+
})
685+
);
686+
649687
(componentSpec.events || [])
650688
.filter((eventSpec) => eventSpec.visibility === 'public')
651689
.forEach((eventSpec) => {
@@ -666,7 +704,7 @@ allWebComponents
666704
eventParameters = getEventParameters(componentSpec.module, eventSpec.parameters || []);
667705
}
668706
importStatements.push(...eventParameters.importStatements);
669-
propTypes.push(dedent`
707+
slotsAndEvents.push(dedent`
670708
/**
671709
* ${replaceTagNameWithModuleName(
672710
turndownService.turndown((eventSpec.description || '').trim()).replace(/\n/g, '\n * ')
@@ -676,8 +714,6 @@ allWebComponents
676714
`);
677715
});
678716

679-
const uniqueAdditionalImports = [...new Set(importStatements)];
680-
681717
const formatDescription = () => {
682718
let description = componentSpec.description;
683719
if (!description) {
@@ -718,22 +754,16 @@ allWebComponents
718754
fs.writeFileSync(webComponentWrapperPath, '');
719755
}
720756

721-
const domRef = Utils.createDomRef(componentSpec);
722-
if (domRef) {
723-
uniqueAdditionalImports.push(domRef.importStatement);
724-
}
725-
726757
if (
727758
(CREATE_SINGLE_COMPONENT === componentSpec.module || !CREATE_SINGLE_COMPONENT) &&
728759
!EXCLUDE_LIST.includes(componentSpec.module)
729760
) {
730761
const webComponentWrapper = await createWebComponentWrapper(
731-
componentSpec.module,
732-
componentSpec.tagname,
762+
componentSpec,
733763
mainDescription,
734-
propTypes,
735-
uniqueAdditionalImports,
736-
domRef,
764+
attributes,
765+
slotsAndEvents,
766+
[...new Set(importStatements)],
737767
defaultProps,
738768
(componentSpec.properties || [])
739769
.filter(filterNonPublicAttributes)

packages/main/src/components/ActionSheet/ActionSheet.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { fireEvent, render, screen } from '@shared/tests';
22
import { createPassThroughPropsTest } from '@shared/tests/utils';
33
import { ActionSheet } from '@ui5/webcomponents-react/dist/ActionSheet';
4+
import { PopoverDomRef } from '@ui5/webcomponents-react/dist/Popover';
45
import { Button } from '@ui5/webcomponents-react/dist/Button';
56
import { Label } from '@ui5/webcomponents-react/dist/Label';
67
import React, { createRef, RefObject } from 'react';
7-
import { Ui5PopoverDomRef } from '../../interfaces/Ui5PopoverDomRef';
88

99
describe('ActionSheet', () => {
1010
test('Render without Crashing', () => {
@@ -52,7 +52,7 @@ describe('ActionSheet', () => {
5252
});
5353

5454
test('Ref object', () => {
55-
const ref: RefObject<Ui5PopoverDomRef> = createRef();
55+
const ref: RefObject<PopoverDomRef> = createRef();
5656
render(<ActionSheet ref={ref} />);
5757
expect((ref.current as any).tagName).toEqual('UI5-RESPONSIVE-POPOVER');
5858
});

0 commit comments

Comments
 (0)