@openwebf/webf 0.1.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.
- package/CLAUDE.md +206 -0
- package/README-zhCN.md +256 -0
- package/README.md +232 -0
- package/bin/webf.js +25 -0
- package/coverage/clover.xml +1295 -0
- package/coverage/coverage-final.json +12 -0
- package/coverage/lcov-report/IDLBlob.ts.html +142 -0
- package/coverage/lcov-report/analyzer.ts.html +2158 -0
- package/coverage/lcov-report/analyzer_original.ts.html +1450 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/commands.ts.html +700 -0
- package/coverage/lcov-report/dart.ts.html +490 -0
- package/coverage/lcov-report/declaration.ts.html +337 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/generator.ts.html +1171 -0
- package/coverage/lcov-report/index.html +266 -0
- package/coverage/lcov-report/logger.ts.html +424 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/react.ts.html +619 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +196 -0
- package/coverage/lcov-report/utils.ts.html +466 -0
- package/coverage/lcov-report/vue.ts.html +613 -0
- package/coverage/lcov.info +2149 -0
- package/global.d.ts +2 -0
- package/jest.config.js +24 -0
- package/package.json +36 -0
- package/src/IDLBlob.ts +20 -0
- package/src/analyzer.ts +692 -0
- package/src/commands.ts +645 -0
- package/src/dart.ts +170 -0
- package/src/declaration.ts +84 -0
- package/src/generator.ts +454 -0
- package/src/logger.ts +114 -0
- package/src/react.ts +186 -0
- package/src/utils.ts +127 -0
- package/src/vue.ts +176 -0
- package/templates/class.dart.tpl +86 -0
- package/templates/gitignore.tpl +2 -0
- package/templates/react.component.tsx.tpl +53 -0
- package/templates/react.createComponent.tpl +286 -0
- package/templates/react.index.ts.tpl +8 -0
- package/templates/react.package.json.tpl +26 -0
- package/templates/react.tsconfig.json.tpl +16 -0
- package/templates/react.tsup.config.ts.tpl +10 -0
- package/templates/tsconfig.json.tpl +8 -0
- package/templates/vue.component.partial.tpl +31 -0
- package/templates/vue.components.d.ts.tpl +49 -0
- package/templates/vue.package.json.tpl +11 -0
- package/templates/vue.tsconfig.json.tpl +15 -0
- package/test/IDLBlob.test.ts +75 -0
- package/test/analyzer.test.ts +370 -0
- package/test/commands.test.ts +1253 -0
- package/test/generator.test.ts +460 -0
- package/test/logger.test.ts +215 -0
- package/test/react.test.ts +49 -0
- package/test/utils.test.ts +316 -0
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Generated by TSDL, don't edit this file directly.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
<% if (events) { %>
|
|
6
|
+
import React, { EventHandler, SyntheticEvent } from "react";
|
|
7
|
+
<% } %>
|
|
8
|
+
import { createComponent } from "<%= utilsPath %>";
|
|
9
|
+
|
|
10
|
+
<%= dependencies %>
|
|
11
|
+
|
|
12
|
+
interface <%= className %>Props {
|
|
13
|
+
<% _.forEach(properties?.props, function(prop, index) { %>
|
|
14
|
+
<% var propName = _.camelCase(prop.name); %>
|
|
15
|
+
<% if (prop.optional) { %>
|
|
16
|
+
<%= propName %>?: <%= generateReturnType(prop.type) %>;
|
|
17
|
+
<% } else { %>
|
|
18
|
+
<%= propName %>: <%= generateReturnType(prop.type) %>;
|
|
19
|
+
<% } %>
|
|
20
|
+
<% }); %>
|
|
21
|
+
<% _.forEach(events?.props, function(prop, index) { %>
|
|
22
|
+
<% var propName = toReactEventName(prop.name); %>
|
|
23
|
+
<%= propName %>?: <%= generateEventHandlerType(prop.type) %>;
|
|
24
|
+
<% }); %>
|
|
25
|
+
children?: React.ReactNode;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface <%= className %>Element extends HTMLElement {
|
|
29
|
+
<% _.forEach(properties?.props, function(prop, index) { %>
|
|
30
|
+
<% var propName = _.camelCase(prop.name); %>
|
|
31
|
+
<% if (prop.optional) { %>
|
|
32
|
+
<%= propName %>?: <%= generateReturnType(prop.type) %>;
|
|
33
|
+
<% } else { %>
|
|
34
|
+
<%= propName %>: <%= generateReturnType(prop.type) %>;
|
|
35
|
+
<% } %>
|
|
36
|
+
<% }); %>
|
|
37
|
+
<% _.forEach(properties?.methods, function(method, index) { %>
|
|
38
|
+
<%= generateMethodDeclaration(method) %>
|
|
39
|
+
<% }); %>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const <%= className %> = createComponent({
|
|
43
|
+
tagName: '<%= _.kebabCase(className) %>',
|
|
44
|
+
displayName: '<%= className %>',
|
|
45
|
+
<% if (events) { %>
|
|
46
|
+
events: {
|
|
47
|
+
<% _.forEach(events?.props, function(prop, index) { %>
|
|
48
|
+
<% var propName = toReactEventName(prop.name); %>
|
|
49
|
+
<%= propName %>: '<%= prop.name %>',
|
|
50
|
+
<% }); %>
|
|
51
|
+
}
|
|
52
|
+
<% } %>
|
|
53
|
+
}) as React.ComponentType<<%= className %>Props & { ref?: React.Ref<HTMLUnknownElement> }>
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/* eslint-disable no-param-reassign */
|
|
3
|
+
/* eslint-disable react-hooks/rules-of-hooks */
|
|
4
|
+
/* eslint-disable @typescript-eslint/ban-types */
|
|
5
|
+
/* eslint-disable no-use-before-define */
|
|
6
|
+
/**
|
|
7
|
+
* @license
|
|
8
|
+
* Copyright 2018 Google LLC
|
|
9
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import React, { forwardRef, useLayoutEffect, createElement, MouseEventHandler, useCallback, useRef } from 'react'
|
|
13
|
+
|
|
14
|
+
const NODE_MODE = false
|
|
15
|
+
// const DEV_MODE = true
|
|
16
|
+
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
|
+
type DistributiveOmit<T, K extends string | number | symbol> = T extends any ? (K extends keyof T ? Omit<T, K> : T) : T
|
|
19
|
+
type PropsWithoutRef<T> = DistributiveOmit<T, 'ref'>
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates a type to be used for the props of a web component used directly in
|
|
23
|
+
* React JSX.
|
|
24
|
+
*
|
|
25
|
+
* Example:
|
|
26
|
+
*
|
|
27
|
+
* ```ts
|
|
28
|
+
* declare module "react" {
|
|
29
|
+
* namespace JSX {
|
|
30
|
+
* interface IntrinsicElements {
|
|
31
|
+
* 'x-foo': WebComponentProps<XFoo>;
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export type WebComponentProps<I extends HTMLElement> = React.DetailedHTMLProps<React.HTMLAttributes<I>, I> &
|
|
38
|
+
ElementProps<I>
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Type of the React component wrapping the web component. This is the return
|
|
42
|
+
* type of `createComponent`.
|
|
43
|
+
*/
|
|
44
|
+
export type ReactWebComponent<I extends HTMLElement, E extends EventNames = {}> = React.ForwardRefExoticComponent<
|
|
45
|
+
// TODO(augustjk): Remove and use `React.PropsWithoutRef` when
|
|
46
|
+
// [preact/compat] missing type `PropsWithoutRef` · Issue #4124 · preactjs/preact is fixed.
|
|
47
|
+
PropsWithoutRef<ComponentProps<I, E>> & React.RefAttributes<I>
|
|
48
|
+
>
|
|
49
|
+
|
|
50
|
+
// Props derived from custom element class. Currently has limitations of making
|
|
51
|
+
// all properties optional and also surfaces life cycle methods in autocomplete.
|
|
52
|
+
// TODO(augustjk) Consider omitting keyof LitElement to remove "internal"
|
|
53
|
+
// lifecycle methods or allow user to explicitly provide props.
|
|
54
|
+
type ElementProps<I> = Partial<Omit<I, keyof HTMLElement>>
|
|
55
|
+
|
|
56
|
+
// Acceptable props to the React component.
|
|
57
|
+
type ComponentProps<I, E extends EventNames = {}> = Omit<
|
|
58
|
+
React.HTMLAttributes<I>,
|
|
59
|
+
// Prefer type of provided event handler props or those on element over
|
|
60
|
+
// built-in HTMLAttributes
|
|
61
|
+
keyof E | keyof ElementProps<I>
|
|
62
|
+
> &
|
|
63
|
+
ElementProps<I>
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Type used to cast an event name with an event type when providing the
|
|
67
|
+
* `events` option to `createComponent` for better typing of the event handler
|
|
68
|
+
* prop.
|
|
69
|
+
*
|
|
70
|
+
* Example:
|
|
71
|
+
*
|
|
72
|
+
* ```ts
|
|
73
|
+
* const FooComponent = createComponent({
|
|
74
|
+
* ...
|
|
75
|
+
* events: {
|
|
76
|
+
* onfoo: 'foo' as EventName<FooEvent>,
|
|
77
|
+
* }
|
|
78
|
+
* });
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* `onfoo` prop will have the type `(e: FooEvent) => void`.
|
|
82
|
+
*/
|
|
83
|
+
export type EventName<T extends Event = Event> = string & {
|
|
84
|
+
__eventType: T
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// A key value map matching React prop names to event names.
|
|
88
|
+
type EventNames = Record<string, EventName | string>
|
|
89
|
+
|
|
90
|
+
// A map of expected event listener types based on EventNames.
|
|
91
|
+
|
|
92
|
+
export interface Options<E extends EventNames = {}> {
|
|
93
|
+
tagName: string
|
|
94
|
+
events?: E
|
|
95
|
+
displayName?: string
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// type Constructor<T> = { new (): T }
|
|
99
|
+
|
|
100
|
+
const reservedReactProperties = new Set(['children', 'localName', 'ref', 'style', 'className'])
|
|
101
|
+
|
|
102
|
+
const listenedEvents = new WeakMap<Element, Map<string, EventListenerObject>>()
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Adds an event listener for the specified event to the given node. In the
|
|
106
|
+
* React setup, there should only ever be one event listener. Thus, for
|
|
107
|
+
* efficiency only one listener is added and the handler for that listener is
|
|
108
|
+
* updated to point to the given listener function.
|
|
109
|
+
*/
|
|
110
|
+
const addOrUpdateEventListener = (node: Element, event: string, listener: (event?: Event) => void) => {
|
|
111
|
+
let events = listenedEvents.get(node)
|
|
112
|
+
if (events === undefined) {
|
|
113
|
+
listenedEvents.set(node, (events = new Map()))
|
|
114
|
+
}
|
|
115
|
+
let handler = events.get(event)
|
|
116
|
+
if (listener !== undefined) {
|
|
117
|
+
// If necessary, add listener and track handler
|
|
118
|
+
if (handler === undefined) {
|
|
119
|
+
events.set(event, (handler = { handleEvent: listener }))
|
|
120
|
+
node.addEventListener(event, handler)
|
|
121
|
+
// Otherwise just update the listener with new value
|
|
122
|
+
} else {
|
|
123
|
+
handler.handleEvent = listener
|
|
124
|
+
}
|
|
125
|
+
// Remove listener if one exists and value is undefined
|
|
126
|
+
} else if (handler !== undefined) {
|
|
127
|
+
events.delete(event)
|
|
128
|
+
node.removeEventListener(event, handler)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Sets properties and events on custom elements. These properties and events
|
|
134
|
+
* have been pre-filtered so we know they should apply to the custom element.
|
|
135
|
+
*/
|
|
136
|
+
const setProperty = <E extends Element>(node: E, name: string, value: unknown, old: unknown, events?: EventNames) => {
|
|
137
|
+
const event = events?.[name]
|
|
138
|
+
// Dirty check event value.
|
|
139
|
+
if (event !== undefined) {
|
|
140
|
+
if (value !== old) {
|
|
141
|
+
addOrUpdateEventListener(node, event, value as (e?: Event) => void)
|
|
142
|
+
}
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
// But don't dirty check properties; elements are assumed to do this.
|
|
146
|
+
node[name as keyof E] = value as E[keyof E]
|
|
147
|
+
|
|
148
|
+
// This block is to replicate React's behavior for attributes of native
|
|
149
|
+
// elements where `undefined` or `null` values result in attributes being
|
|
150
|
+
// removed.
|
|
151
|
+
// react/packages/react-dom-bindings/src/client/DOMPropertyOperations.js at 899cb95f52cc83ab5ca1eb1e268
|
|
152
|
+
//
|
|
153
|
+
// It's only needed here for native HTMLElement properties that reflect
|
|
154
|
+
// attributes of the same name but don't have that behavior like "id" or
|
|
155
|
+
// "draggable".
|
|
156
|
+
if ((value === undefined || value === null) && name in HTMLElement.prototype) {
|
|
157
|
+
node.removeAttribute(name)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Creates a React component for a custom element. Properties are distinguished
|
|
163
|
+
* from attributes automatically, and events can be configured so they are added
|
|
164
|
+
* to the custom element as event listeners.
|
|
165
|
+
*
|
|
166
|
+
* @param options An options bag containing the parameters needed to generate a
|
|
167
|
+
* wrapped web component.
|
|
168
|
+
*
|
|
169
|
+
* @param options.react The React module, typically imported from the `react`
|
|
170
|
+
* npm package.
|
|
171
|
+
* @param options.tagName The custom element tag name registered via
|
|
172
|
+
* `customElements.define`.
|
|
173
|
+
* @param options.elementClass The custom element class registered via
|
|
174
|
+
* `customElements.define`.
|
|
175
|
+
* @param options.events An object listing events to which the component can
|
|
176
|
+
* listen. The object keys are the event property names passed in via React
|
|
177
|
+
* props and the object values are the names of the corresponding events
|
|
178
|
+
* generated by the custom element. For example, given `{onactivate:
|
|
179
|
+
* 'activate'}` an event function may be passed via the component's `onactivate`
|
|
180
|
+
* prop and will be called when the custom element fires its `activate` event.
|
|
181
|
+
* @param options.displayName A React component display name, used in debugging
|
|
182
|
+
* messages. Default value is inferred from the name of custom element class
|
|
183
|
+
* registered via `customElements.define`.
|
|
184
|
+
*/
|
|
185
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint
|
|
186
|
+
export const createComponent = <E extends any, I extends any>({
|
|
187
|
+
tagName,
|
|
188
|
+
events,
|
|
189
|
+
displayName
|
|
190
|
+
}: Options): ReactWebComponent<E, I> => {
|
|
191
|
+
const eventProps = new Set(Object.keys(events ?? {}))
|
|
192
|
+
|
|
193
|
+
type Props = ComponentProps<I>
|
|
194
|
+
const ReactComponent = forwardRef<I, Props>((props, ref) => {
|
|
195
|
+
const prevElemPropsRef = useRef(new Map())
|
|
196
|
+
const elementRef = useRef<E | null>(null)
|
|
197
|
+
|
|
198
|
+
// Props to be passed to React.createElement
|
|
199
|
+
const reactProps: Record<string, unknown> = {}
|
|
200
|
+
// Props to be set on element with setProperty
|
|
201
|
+
const elementProps: Record<string, unknown> = {}
|
|
202
|
+
|
|
203
|
+
for (const [k, v] of Object.entries(props)) {
|
|
204
|
+
if (reservedReactProperties.has(k)) {
|
|
205
|
+
// React does *not* handle `className` for custom elements so
|
|
206
|
+
// coerce it to `class` so it's handled correctly.
|
|
207
|
+
reactProps[k === 'className' ? 'class' : k] = v
|
|
208
|
+
continue
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (eventProps.has(k)) {
|
|
212
|
+
elementProps[k] = v
|
|
213
|
+
continue
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
reactProps[k] = v
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// useLayoutEffect produces warnings during server rendering.
|
|
220
|
+
|
|
221
|
+
// This one has no dependency array so it'll run on every re-render.
|
|
222
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
223
|
+
useLayoutEffect(() => {
|
|
224
|
+
if (elementRef.current === null) {
|
|
225
|
+
return
|
|
226
|
+
}
|
|
227
|
+
const newElemProps = new Map()
|
|
228
|
+
for (const key in elementProps) {
|
|
229
|
+
setProperty(elementRef.current, key, props[key], prevElemPropsRef.current.get(key), events)
|
|
230
|
+
prevElemPropsRef.current.delete(key)
|
|
231
|
+
newElemProps.set(key, props[key])
|
|
232
|
+
}
|
|
233
|
+
// "Unset" any props from previous render that no longer exist.
|
|
234
|
+
// Setting to `undefined` seems like the correct thing to "unset"
|
|
235
|
+
// but currently React will set it as `null`.
|
|
236
|
+
// See Bug: Removal of custom element property sets it to `null` rather than `undefined` · Issue #28203 · f
|
|
237
|
+
for (const [key, value] of prevElemPropsRef.current) {
|
|
238
|
+
setProperty(elementRef.current, key, undefined, value, events)
|
|
239
|
+
}
|
|
240
|
+
prevElemPropsRef.current = newElemProps
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
// Empty dependency array so this will only run once after first render.
|
|
244
|
+
useLayoutEffect(() => {
|
|
245
|
+
elementRef.current?.removeAttribute('defer-hydration')
|
|
246
|
+
}, [])
|
|
247
|
+
// }
|
|
248
|
+
|
|
249
|
+
if (NODE_MODE) {
|
|
250
|
+
// If component is to be server rendered with `@lit/ssr-react`, pass
|
|
251
|
+
// element properties in a special bag to be set by the server-side
|
|
252
|
+
// element renderer.
|
|
253
|
+
if (
|
|
254
|
+
(createElement.name === 'litPatchedCreateElement' || globalThis.litSsrReactEnabled) &&
|
|
255
|
+
Object.keys(elementProps).length
|
|
256
|
+
) {
|
|
257
|
+
// This property needs to remain unminified.
|
|
258
|
+
reactProps['_$litProps$'] = elementProps
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
// Suppress hydration warning for server-rendered attributes.
|
|
262
|
+
// This property needs to remain unminified.
|
|
263
|
+
reactProps['suppressHydrationWarning'] = true
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return createElement(tagName, {
|
|
267
|
+
...reactProps,
|
|
268
|
+
ref: useCallback(
|
|
269
|
+
(node: I) => {
|
|
270
|
+
elementRef.current = node
|
|
271
|
+
if (typeof ref === 'function') {
|
|
272
|
+
ref(node)
|
|
273
|
+
} else if (ref !== null) {
|
|
274
|
+
// eslint-disable-next-line no-param-reassign
|
|
275
|
+
ref.current = node
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
[ref]
|
|
279
|
+
)
|
|
280
|
+
})
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
ReactComponent.displayName = displayName
|
|
284
|
+
|
|
285
|
+
return ReactComponent
|
|
286
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Generated by TSDL, don't edit this file directly.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
<% components.forEach(component => { %>
|
|
6
|
+
export { <%= component.className %>, <%= component.className %>Element } from "./<%= component.relativeDir ? component.relativeDir + '/' : '' %><%= component.fileName %>";
|
|
7
|
+
<% }); %>
|
|
8
|
+
export { createComponent } from "./utils/createComponent";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "<%= packageName %>",
|
|
3
|
+
"version": "<%= version %>",
|
|
4
|
+
"description": "<%= description %>",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": ["dist"],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsup"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [],
|
|
13
|
+
"author": "",
|
|
14
|
+
"license": "ISC",
|
|
15
|
+
"type": "commonjs",
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"react": ">=16.8.0",
|
|
18
|
+
"react-dom": ">=16.8.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/react": "^19.1.0",
|
|
22
|
+
"@types/react-dom": "^19.1.2",
|
|
23
|
+
"tsup": "^8.5.0",
|
|
24
|
+
"typescript": "^5.8.3"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"jsx": "react-jsx",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"declarationDir": "dist",
|
|
8
|
+
"outDir": "dist",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"moduleResolution": "node",
|
|
13
|
+
"allowSyntheticDefaultImports": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src"]
|
|
16
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type <%= className %>Props = {
|
|
2
|
+
<% _.forEach(properties?.props, function(prop, index) { %>
|
|
3
|
+
<% var propName = _.kebabCase(prop.name); %>
|
|
4
|
+
<% if (prop.optional) { %>
|
|
5
|
+
'<%= propName %>'?: <%= generateReturnType(prop.type) %>;
|
|
6
|
+
<% } else { %>
|
|
7
|
+
'<%= propName %>': <%= generateReturnType(prop.type) %>;
|
|
8
|
+
<% } %>
|
|
9
|
+
<% }); %>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface <%= className %>Element {
|
|
13
|
+
<% _.forEach(properties?.props, function(prop, index) { %>
|
|
14
|
+
<% var propName = _.camelCase(prop.name); %>
|
|
15
|
+
<% if (prop.optional) { %>
|
|
16
|
+
<%= propName %>?: <%= generateReturnType(prop.type) %>;
|
|
17
|
+
<% } else { %>
|
|
18
|
+
<%= propName %>: <%= generateReturnType(prop.type) %>;
|
|
19
|
+
<% } %>
|
|
20
|
+
<% }); %>
|
|
21
|
+
<% _.forEach(properties?.methods, function(method, index) { %>
|
|
22
|
+
<%= generateMethodDeclaration(method) %>
|
|
23
|
+
<% }); %>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type <%= className %>Events = {
|
|
27
|
+
<% _.forEach(events?.props, function(prop, index) { %>
|
|
28
|
+
<% var propName = prop.name; %>
|
|
29
|
+
<%= propName %>?: <%= generateEventHandlerType(prop.type) %>;
|
|
30
|
+
<% }); %>
|
|
31
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Generated by TSDL, don't edit this file directly.
|
|
3
|
+
*/
|
|
4
|
+
// Based on the Vue 3 documentation for defining custom elements:
|
|
5
|
+
// https://vuejs.org/guide/extras/web-components
|
|
6
|
+
import { EmitFn, PublicProps, HTMLAttributes } from 'vue';
|
|
7
|
+
|
|
8
|
+
type EventMap = {
|
|
9
|
+
[event: string]: Event
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// This maps an EventMap to the format that Vue's $emit type expects.
|
|
13
|
+
type VueEmit<T extends EventMap> = EmitFn<{
|
|
14
|
+
[K in keyof T]: (event: T[K]) => void
|
|
15
|
+
}>
|
|
16
|
+
|
|
17
|
+
type DefineCustomElement<
|
|
18
|
+
ElementType,
|
|
19
|
+
Events extends EventMap = {},
|
|
20
|
+
SelectedAttributes extends keyof ElementType = keyof ElementType
|
|
21
|
+
> = new () => ElementType & {
|
|
22
|
+
// Use $props to define the properties exposed to template type checking. Vue
|
|
23
|
+
// specifically reads prop definitions from the `$props` type. Note that we
|
|
24
|
+
// combine the element's props with the global HTML props and Vue's special
|
|
25
|
+
// props.
|
|
26
|
+
/** @deprecated Do not use the $props property on a Custom Element ref,
|
|
27
|
+
this is for template prop types only. */
|
|
28
|
+
$props: Partial<Pick<ElementType, SelectedAttributes>> & PublicProps
|
|
29
|
+
|
|
30
|
+
// Use $emit to specifically define event types. Vue specifically reads event
|
|
31
|
+
// types from the `$emit` type. Note that `$emit` expects a particular format
|
|
32
|
+
// that we map `Events` to.
|
|
33
|
+
/** @deprecated Do not use the $emit property on a Custom Element ref,
|
|
34
|
+
this is for template prop types only. */
|
|
35
|
+
$emit: VueEmit<Events>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
<%= components %>
|
|
39
|
+
|
|
40
|
+
declare module 'vue' {
|
|
41
|
+
interface GlobalComponents {
|
|
42
|
+
<% componentNames.forEach(name => { %>
|
|
43
|
+
'<%= _.kebabCase(name) %>': DefineCustomElement<
|
|
44
|
+
<%= name %>Props,
|
|
45
|
+
<%= name %>Events
|
|
46
|
+
>
|
|
47
|
+
<% }) %>
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"declarationDir": "dist",
|
|
7
|
+
"outDir": "dist",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"noImplicitAny": true
|
|
13
|
+
},
|
|
14
|
+
"exclude": ["node_modules", "dist"]
|
|
15
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { IDLBlob } from '../src/IDLBlob';
|
|
3
|
+
import { ClassObject, FunctionObject } from '../src/declaration';
|
|
4
|
+
|
|
5
|
+
jest.mock('fs');
|
|
6
|
+
|
|
7
|
+
const mockFs = fs as jest.Mocked<typeof fs>;
|
|
8
|
+
|
|
9
|
+
describe('IDLBlob', () => {
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
jest.clearAllMocks();
|
|
12
|
+
mockFs.readFileSync.mockReturnValue('interface Test { prop: string; }');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe('constructor', () => {
|
|
16
|
+
it('should initialize with provided parameters', () => {
|
|
17
|
+
const blob = new IDLBlob('/source/test.d.ts', '/target', 'test', 'src/test');
|
|
18
|
+
|
|
19
|
+
expect(blob.source).toBe('/source/test.d.ts');
|
|
20
|
+
expect(blob.dist).toBe('/target');
|
|
21
|
+
expect(blob.filename).toBe('test');
|
|
22
|
+
expect(blob.implement).toBe('src/test');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should initialize raw as empty string', () => {
|
|
26
|
+
const blob = new IDLBlob('/source/test.d.ts', '/target', 'test', 'src/test');
|
|
27
|
+
|
|
28
|
+
expect(blob.raw).toBe('');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should initialize objects array as empty', () => {
|
|
32
|
+
const blob = new IDLBlob('/source/test.d.ts', '/target', 'test', 'src/test');
|
|
33
|
+
|
|
34
|
+
expect(blob.objects).toEqual([]);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('properties', () => {
|
|
39
|
+
it('should allow setting and getting raw content', () => {
|
|
40
|
+
const blob = new IDLBlob('/source/test.d.ts', '/target', 'test', 'src/test');
|
|
41
|
+
|
|
42
|
+
blob.raw = 'new content';
|
|
43
|
+
expect(blob.raw).toBe('new content');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should allow setting and getting objects', () => {
|
|
47
|
+
const blob = new IDLBlob('/source/test.d.ts', '/target', 'test', 'src/test');
|
|
48
|
+
|
|
49
|
+
const classObj = new ClassObject();
|
|
50
|
+
classObj.name = 'TestClass';
|
|
51
|
+
|
|
52
|
+
const funcObj = new FunctionObject();
|
|
53
|
+
|
|
54
|
+
blob.objects = [classObj, funcObj];
|
|
55
|
+
expect(blob.objects).toHaveLength(2);
|
|
56
|
+
expect(blob.objects[0]).toBe(classObj);
|
|
57
|
+
expect(blob.objects[1]).toBe(funcObj);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should allow modifying dist path', () => {
|
|
61
|
+
const blob = new IDLBlob('/source/test.d.ts', '/target', 'test', 'src/test');
|
|
62
|
+
|
|
63
|
+
blob.dist = '/new/target';
|
|
64
|
+
expect(blob.dist).toBe('/new/target');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('file operations', () => {
|
|
69
|
+
it('should not read file on construction', () => {
|
|
70
|
+
new IDLBlob('/source/test.d.ts', '/target', 'test', 'src/test');
|
|
71
|
+
|
|
72
|
+
expect(mockFs.readFileSync).not.toHaveBeenCalled();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|