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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,5 @@ dist

# TernJS port file
.tern-port

.DS_Store
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "custom-element-types",
"version": "0.0.2",
"version": "0.0.3",
"description": "A generator to create Framework integrations and types for Custom Elements using the Custom Elements Schema format.",
"packageManager": "pnpm@10.7.0",
"engines": {
Expand Down
4 changes: 2 additions & 2 deletions src/angular.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { Directive, Input, Output, EventEmitter, ElementRef } from '@angular/cor
${elements.map(e => e.importType).join('\n')}

${elements.map(e => getDirective(e)).join('\n')}`.trim();
return [{ src, path: 'custom-element-types.module.ts' }];;
return [{ src, path: 'custom-element-types.module.ts' }];
}

// https://github.com/angular/angular/issues/14761
Expand All @@ -30,7 +30,7 @@ ${getOutputEvents(element)}
}

function getInputProperties(element: CustomElement) {
return element.propeties.map(prop => `
return element.properties.map(prop => `
@Input() set ${prop.name}(value${prop.type === 'boolean' ? `: boolean | ''` : ''}) { this.element.${prop.name} = ${prop.type === 'boolean' ? `value === '' ? true : ` : ''}value; }
get ${prop.name}() { return this.element.${prop.name}; }`).join('\n');
}
Expand Down
13 changes: 1 addition & 12 deletions src/blazor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,4 @@ Object.keys(customEvents).map(event => {
`;

return [{ src: srcCS, path: 'CustomEvents.cs' }, { src: srcJS, path: 'custom-events.js' }];
}


// export function afterStarted() {
// ${Object.keys(eventObject).map(name => ({ name, descriptions: eventObject[name] })).map(e => `
// ${e.descriptions.map(d => ` // ${d.tagName}: ${d.description}`).join('\n')}
// Blazor.registerCustomEventType('${e.name}', {
// browserEventName: '${e.name}',
// createEventArgs: event => {
// return { detail: event.detail };
// }
// });`)}`};
}
10 changes: 4 additions & 6 deletions src/reserved.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,8 @@ const reservedPublicProperties = new Set(
[...ariaProperties, ...htmlElementProperties, ...elementProperties].map(p => p.toLowerCase())
);

export function isReservedProperty(memberName) {
memberName = memberName.toLowerCase();
return reservedPublicProperties.has(memberName);
export function isReservedProperty(memberName: string) {
return reservedPublicProperties.has(memberName.toLowerCase());
}

const htmlElementEvents = [
Expand Down Expand Up @@ -342,7 +341,6 @@ const htmlElementEvents = [

const reservedPublicEvents = new Set([...htmlElementEvents].map(p => p.toLowerCase()));

export function isReservedEvent(memberName) {
memberName = memberName.toLowerCase();
return reservedPublicEvents.has(memberName);
export function isReservedEvent(memberName: string) {
return reservedPublicEvents.has(memberName.toLowerCase());
}
2 changes: 1 addition & 1 deletion src/typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ ${elements.map(e => ` '${e.tagName}': ${e.name}`).join(';\n')}
}
}`.trim();

return [{ src, path: 'types.d.ts' }];;
return [{ src, path: 'types.d.ts' }];
}
12 changes: 5 additions & 7 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export interface CustomElement {
import: string;
importType: string;
description: string;
propeties: { name: string; type: string; }[];
properties: { name: string; type: string; }[];
events: { name: string; }[];
cssProperties: any[];
slots: any[]
Expand All @@ -19,7 +19,7 @@ export interface CustomElementMetadata {
elements: CustomElement[];
}

export function createElementMetadata(customElementsManifest: Package, entrypoint): CustomElement[] {
export function createElementMetadata(customElementsManifest: Package, entrypoint: string): CustomElement[] {
const modules = getCustomElementModules(customElementsManifest);

const elements = modules.flatMap(m => {
Expand All @@ -36,7 +36,7 @@ export function createElementMetadata(customElementsManifest: Package, entrypoin
slots: d.slots ?? [],
cssProperties: d.cssProperties ?? [],
events: getCustomElementEvents(d) ?? [],
propeties: getPublicProperties(d)
properties: getPublicProperties(d)
};

return element;
Expand All @@ -52,7 +52,7 @@ function replaceTsExtentions(filePath: string) {

function changeExt(filePath: string, ext: string) {
const pos = filePath.includes('.') ? filePath.lastIndexOf('.') : filePath.length;
return `${filePath.substr(0, pos)}.${ext}`;
return `${filePath.substring(0, pos)}.${ext}`;
}

function getPublicProperties(element: any) {
Expand All @@ -62,8 +62,6 @@ function getPublicProperties(element: any) {
m.kind === 'field' &&
m.attribute !== undefined &&
m.privacy === undefined &&
m.privacy !== 'private' &&
m.privacy !== 'protected' &&
m.name !== 'accessor' &&
!isReservedProperty(m.name)
) ?? []).map(p => ({ name: p.name, type: p.type?.text }));
Expand All @@ -73,7 +71,7 @@ function getCustomElementModules(customElementsManifest: any) {
return customElementsManifest.modules.filter(m => m.declarations?.length && m.declarations.find(d => d.customElement === true));
}

function getCustomElementEvents(element): any[] {
function getCustomElementEvents(element: any): any[] {
const memberEvents = element.members
.filter(event => event.privacy === undefined) // public
.filter(prop => prop.type && prop.type?.text && prop.type?.text.includes('EventEmitter') && !isReservedEvent(prop.name))
Expand Down
98 changes: 98 additions & 0 deletions test/blazor.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { describe, it } from 'node:test';
import assert from 'node:assert';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import { generate } from '../src/blazor.js';
import type { Package } from 'custom-elements-manifest/schema';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

describe('blazor.ts', () => {
const manifestPath = join(__dirname, 'fixtures', 'sample-manifest.json');
const manifest: Package = JSON.parse(readFileSync(manifestPath, 'utf-8'));

describe('generate', () => {
it('should generate two output files', () => {
const result = generate({
customElementsManifest: manifest,
entrypoint: undefined
});

assert.strictEqual(result.length, 2);
assert.strictEqual(result[0].path, 'CustomEvents.cs');
assert.strictEqual(result[1].path, 'custom-events.js');
});

it('should generate C# EventHandlers with correct namespace', () => {
const result = generate({
customElementsManifest: manifest,
entrypoint: undefined
});

assert.ok(result[0].src.includes('namespace BlazorApp;'));
assert.ok(result[0].src.includes('public static class EventHandlers'));
});

it('should include C# CustomEventArgs class', () => {
const result = generate({
customElementsManifest: manifest,
entrypoint: undefined
});

assert.ok(result[0].src.includes('public class CustomEventArgs : EventArgs'));
assert.ok(result[0].src.includes('public dynamic? Detail { get; set; }'));
assert.ok(result[0].src.includes('public T GetDetail<T>()'));
});

it('should include EventHandler attributes for custom events', () => {
const result = generate({
customElementsManifest: manifest,
entrypoint: undefined
});

assert.ok(result[0].src.includes('[EventHandler("on'));
assert.ok(result[0].src.includes('typeof(CustomEventArgs)'));
});

it('should generate JavaScript custom events registration', () => {
const result = generate({
customElementsManifest: manifest,
entrypoint: undefined
});

assert.ok(result[1].src.includes('const customEvents'));
assert.ok(result[1].src.includes('Blazor.registerCustomEventType'));
});

it('should include CustomEvent bubbling workaround in JS', () => {
const result = generate({
customElementsManifest: manifest,
entrypoint: undefined
});

assert.ok(result[1].src.includes('class Bubbled extends CustomEvent'));
});

it('should include C# using statements', () => {
const result = generate({
customElementsManifest: manifest,
entrypoint: undefined
});

assert.ok(result[0].src.includes('using Microsoft.AspNetCore.Components;'));
assert.ok(result[0].src.includes('using System.Text.Json;'));
});

it('should include generated message comment in both files', () => {
const result = generate({
customElementsManifest: manifest,
entrypoint: undefined
});

assert.ok(result[0].src.includes('Generated with https://github.com/blueprintui/custom-element-types'));
assert.ok(result[1].src.includes('Generated with https://github.com/blueprintui/custom-element-types'));
});
});
});
113 changes: 113 additions & 0 deletions test/jsx.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { describe, it } from 'node:test';
import assert from 'node:assert';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import { generate } from '../src/jsx.js';
import type { Package } from 'custom-elements-manifest/schema';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

describe('jsx.ts', () => {
const manifestPath = join(__dirname, 'fixtures', 'sample-manifest.json');
const manifest: Package = JSON.parse(readFileSync(manifestPath, 'utf-8'));

describe('generate', () => {
it('should generate JSX type declarations', () => {
const result = generate({
customElementsManifest: manifest,
entrypoint: undefined
});

assert.strictEqual(result.length, 1);
assert.strictEqual(result[0].path, 'types.d.ts');
assert.ok(result[0].src.length > 0);
});

it('should use type imports (not value imports)', () => {
const result = generate({
customElementsManifest: manifest,
entrypoint: undefined
});

assert.ok(result[0].src.includes("import type { MyButton } from './/button/element.js'"));
assert.ok(result[0].src.includes("import type { MyInput } from './/input/element.js'"));
});

it('should export CustomElements interface', () => {
const result = generate({
customElementsManifest: manifest,
entrypoint: undefined
});

assert.ok(result[0].src.includes('export interface CustomElements'));
});

it('should include tag names in CustomElements', () => {
const result = generate({
customElementsManifest: manifest,
entrypoint: undefined
});

assert.ok(result[0].src.includes("['my-button']"));
assert.ok(result[0].src.includes("['my-input']"));
});

it('should include custom events in element type', () => {
const result = generate({
customElementsManifest: manifest,
entrypoint: undefined
});

assert.ok(result[0].src.includes("CustomElement<MyButton,'customClick' | 'click'>"));
});

it('should not include event types for elements without events', () => {
const result = generate({
customElementsManifest: manifest,
entrypoint: undefined
});

assert.ok(result[0].src.includes('CustomElement<MyInput>'));
});

it('should define CustomEvents and CustomElement type helpers', () => {
const result = generate({
customElementsManifest: manifest,
entrypoint: undefined
});

assert.ok(result[0].src.includes('type CustomEvents<K extends string>'));
assert.ok(result[0].src.includes('type CustomElement<T, K extends string'));
});

it('should include generated message comment', () => {
const result = generate({
customElementsManifest: manifest,
entrypoint: undefined
});

assert.ok(result[0].src.includes('Generated with https://github.com/blueprintui/custom-element-types'));
});

it('should mark as experimental', () => {
const result = generate({
customElementsManifest: manifest,
entrypoint: undefined
});

assert.ok(result[0].src.includes('@experimental'));
});

it('should use entrypoint when provided', () => {
const result = generate({
customElementsManifest: manifest,
entrypoint: '@mylib/components'
});

assert.ok(result[0].src.includes("import type { MyButton } from '@mylib/components/button/element.js'"));
assert.ok(result[0].src.includes("import type { MyInput } from '@mylib/components/input/element.js'"));
});
});
});
Loading