Skip to content

Commit

Permalink
feat: typed events
Browse files Browse the repository at this point in the history
Changes the package use to generate the custom-elements.json file used to build React components
and tell Storybook about the web-components definitions.
  • Loading branch information
gavinbarron committed Dec 12, 2022
1 parent adca163 commit 9566106
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 109 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"samples/teamsfx-app/tabs"
],
"scripts": {
"analyze": "custom-elements-manifest analyze --litelement --globs \"./packages/*/src/**/*.ts\"",
"build": "npm run prettier:check && npm run clean && lerna run build --scope @microsoft/*",
"build:compile": "npm run prettier:check && npm run clean && lerna run build:compile --scope @microsoft/*",
"build:mgt": "cd ./packages/mgt && npm run build",
Expand All @@ -39,7 +40,7 @@
"prettier:check": "npm run prettier:base -- --check \"packages/**/*.{ts,tsx}\"",
"prettier:write": "npm run prettier:base -- --write \"packages/**/*.{ts,tsx}\"",
"storybook": "start-storybook -p 6006 -s assets",
"storybook:dev": "npm run build:compile && wca analyze packages --format json --outFile custom-elements.json",
"storybook:dev": "npm run build:compile && npm run analyze",
"storybook:watch": "npm-run-all --parallel watch storybook:bundle:watch storybook",
"storybook:bundle": "rollup -c ./.storybook/rollup.config.js",
"storybook:bundle:watch": "rollup -c ./.storybook/rollup.config.js --watch",
Expand All @@ -61,6 +62,7 @@
"@babel/preset-env": "^7.12.7",
"@babel/preset-react": "^7.12.7",
"@babel/preset-typescript": "^7.12.7",
"@custom-elements-manifest/analyzer": "^0.6.6",
"@octokit/rest": "^18.5.3",
"@storybook/addon-a11y": "^6.4.4",
"@storybook/addon-actions": "^6.4.4",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ interface IFocusable {
* @class MgtPicker
* @extends {MgtTemplatedComponent}
*
* @fires selectionChanged - Fired when selection changes
* @fires {CustomEvent<IDynamicPerson[]>} selectionChanged - Fired when set of selected people changes
*
* @cssprop --color - {Color} Default font color
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ export class MgtTasks extends MgtTemplatedComponent {
*
* @memberof MgtTasks
*/
public get isNewTaskVisible() {
public get isNewTaskVisible(): boolean {
return this._isNewTaskVisible;
}

Expand Down
13 changes: 11 additions & 2 deletions packages/mgt-element/src/components/templatedComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ interface RenderedTemplates {
};
}

// tslint:disable: completed-docs
export interface TemplateRenderedData {
templateType: string;
context: Record<string, unknown>;
element: HTMLElement;
}
// tslint:enable: completed-docs

/**
* An abstract class that defines a templatable web component
*
Expand All @@ -36,7 +44,7 @@ interface RenderedTemplates {
* @class MgtTemplatedComponent
* @extends {MgtBaseComponent}
*
* @fires templateRendered - fires when a template is rendered
* @fires {CustomEvent<MgtElement.TemplateRenderedData>} templateRendered - fires when a template is rendered
*/
export abstract class MgtTemplatedComponent extends MgtBaseComponent {
/**
Expand Down Expand Up @@ -134,7 +142,8 @@ export abstract class MgtTemplatedComponent extends MgtBaseComponent {

this._renderedTemplates[slotName] = { context: dataContext, slot: div };

this.fireCustomEvent('templateRendered', { templateType, context: dataContext, element: div });
const templateRenderedData: TemplateRenderedData = { templateType, context: dataContext, element: div };
this.fireCustomEvent('templateRendered', templateRenderedData);

return template;
}
Expand Down
3 changes: 2 additions & 1 deletion packages/mgt-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"build": "npm run clean && npm run generate && tsc",
"clean": "node ./scripts/clean.js",
"postpack": "cpx *.tgz ../../artifacts",
"generate": "wca analyze ../mgt-components/src --format json --outFile temp/web-components.json && node ./scripts/generate.js"
"analyze": "custom-elements-manifest analyze --litelement --globs \"../*/src/**/*.ts\" --outdir temp",
"generate": "npm run analyze && node ./scripts/generate.js"
},
"dependencies": {
"@microsoft/mgt-components": "*",
Expand Down
93 changes: 62 additions & 31 deletions packages/mgt-react/scripts/generate.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
var fs = require('fs-extra');

let wc = JSON.parse(fs.readFileSync(`${__dirname}/../temp/web-components.json`));
let wc = JSON.parse(fs.readFileSync(`${__dirname}/../temp/custom-elements.json`));

const primitives = new Set(['string', 'boolean', 'number', 'any']);
const primitives = new Set(['string', 'boolean', 'number', 'any', 'void', 'null', 'undefined']);
const mgtComponentImports = new Set();
const mgtElementImports = new Set();

Expand All @@ -25,61 +25,82 @@ let output = '';

const wrappers = [];

for (const tag of wc.tags) {
if (!tags.has(tag.name)) {
continue;
const customTags = [];
for (const module of wc.modules) {
for (const d of module.declarations) {
if (d.customElement && d.tagName && tags.has(d.tagName)) {
customTags.push(d);
}
}
}

const removeGenericTypeDecoration = type => {
if (type.endsWith('[]')) {
return type.substring(0, type.length - 2);
} else if (type.startsWith('Array<')) {
return removeGenericTypeDecoration(type.substring(6, type.length - 1));
} else if (type.startsWith('CustomEvent<')) {
return removeGenericTypeDecoration(type.substring(12, type.length - 1));
}
return type;
};

const addTypeToImports = type => {
if (type === '*') {
return;
}

const className = tag.name
for (let t of type.split('|')) {
t = removeGenericTypeDecoration(t.trim());
if (t.startsWith('MicrosoftGraph.') || t.startsWith('MicrosoftGraphBeta.')) {
return;
}

if (t.startsWith('MgtElement.') && !mgtElementImports.has(t)) {
mgtElementImports.add(t.split('.')[1]);
} else if (!primitives.has(t) && !mgtComponentImports.has(t)) {
mgtComponentImports.add(t);
}
}
};

for (const tag of customTags.sort((a, b) => (a.tagName > b.tagName ? 1 : -1))) {
const className = tag.tagName
.split('-')
.slice(1)
.map(t => t[0].toUpperCase() + t.substring(1))
.join('');

wrappers.push({
tag: tag.name,
tag: tag.tagName,
propsType: className + 'Props',
className: className
});

const props = {};

for (let i = 0; i < tag.properties.length; ++i) {
const prop = tag.properties[i];
let type = prop.type;
for (let i = 0; i < tag.members.length; ++i) {
const prop = tag.members[i];
let type = prop.type?.text;

if (type) {
if (type && prop.kind === 'field' && prop.privacy === 'public' && !prop.static) {
if (prop.name) {
props[prop.name] = type;
}

if (type.includes('|')) {
const types = type.split('|');
for (const t of types) {
tag.properties.push({
type: t.trim()
tag.members.push({
kind: 'field',
privacy: 'public',
type: { text: t.trim() }
});
}
continue;
}

if (type.endsWith('[]')) {
type = type.substring(0, type.length - 2);
} else if (type.startsWith('Array<')) {
type = type.substring(6, type.length - 1);
} else if (type === '*') {
continue;
}

if (type.startsWith('MicrosoftGraph.') || type.startsWith('MicrosoftGraphBeta.')) {
continue;
}

if (type.startsWith('MgtElement.') && !mgtElementImports.has(type)) {
mgtElementImports.add(type.split('.')[1]);
} else if (!primitives.has(type) && !mgtComponentImports.has(type)) {
mgtComponentImports.add(type);
}
addTypeToImports(type);
}
}

Expand All @@ -95,7 +116,17 @@ for (const tag of wc.tags) {

if (tag.events) {
for (const event of tag.events) {
propsType += `\t${event.name}?: (e: Event) => void;\n`;
if (event.type && event.type.text) {
// remove MgtElement. prefix as this it only used to ensure it's imported from the correct package
propsType += `\t${event.name}?: (e: ${event.type.text.replace(
'CustomEvent<MgtElement.',
'CustomEvent<'
)}) => void;\n`;
// also ensure that the necessary import is added to either mgt-element or mgt-component imports
addTypeToImports(event.type.text);
} else {
propsType += `\t${event.name}?: (e: Event) => void;\n`;
}
}
}

Expand Down
Loading

0 comments on commit 9566106

Please sign in to comment.