@@ -98,21 +98,28 @@ if (process.env.NODE_ENV !== 'production') {
98
98
}
99
99
100
100
/**
101
- * Properties defined on the component class, excluding those inherited from `LightningElement`.
101
+ * Gets the public properties of a component class. If the `__lwc_public_property_types__` property
102
+ * is defined, it will be used as the source of truth for the property types. Otherwise, all of the
103
+ * properties defined on the component class are used, excluding those inherited from `LightningElement`.
104
+ *
105
+ * IMPORTANT: If the fallback is used, then _all_ component properties are returned, rather than
106
+ * just the public properties.
102
107
*/
103
- // TODO [#4292]: Restrict this to only @api props
104
- type ComponentClassProperties < T > = Omit < T , keyof LightningElement > ;
108
+ type ComponentClassProperties < T > = T extends {
109
+ readonly __lwc_public_property_types__ ?: infer Props extends object ;
110
+ }
111
+ ? Props
112
+ : Omit < T , keyof LightningElement > ;
105
113
106
114
/**
107
115
* The custom element returned when calling {@linkcode createElement} with the given component
108
116
* constructor.
109
117
*
110
- * NOTE: The returned type incorrectly includes _all_ properties defined on the component class,
118
+ * NOTE: By default, the returned type includes _all_ properties defined on the component class,
111
119
* even though the runtime object only uses those decorated with `@api`. This is due to a
112
- * limitation of TypeScript. To avoid inferring incorrect properties, provide an explicit generic
113
- * parameter, e.g. `createElement<typeof LightningElement>('x-foo', { is: FooCtor })`.
120
+ * limitation of TypeScript. For example:
114
121
*
115
- * @example ```
122
+ * ```
116
123
* class Example extends LightningElement {
117
124
* @api exposed = 'hello'
118
125
* internal = 'secret'
@@ -124,6 +131,29 @@ type ComponentClassProperties<T> = Omit<T, keyof LightningElement>;
124
131
* const internal = example.internal // type is 'string'
125
132
* console.log(internal) // prints `undefined`
126
133
* ```
134
+ *
135
+ * One way to avoid inferring incorrect properties, is to provide an explicit generic parameter.
136
+ * For example:
137
+ * ```
138
+ * const example = createElement<{exposed: string}>('c-example', { is: Example })
139
+ * const exposed = example.exposed // type is 'string'
140
+ * const internal = example.internal // Now a type error! ✅
141
+ * ```
142
+ *
143
+ * Alternatively, you can define a type for property on the component, `__lwc_public_property_types__`,
144
+ * that explicitly lists only the public properties, and the types will be inferred from that prop.
145
+ * The property should be optional, because it does not actually exist at runtime.
146
+ * For example:
147
+ * ```
148
+ * class Example extends LightningElement {
149
+ * @api exposed = 'hello'
150
+ * internal = 'secret'
151
+ * __lwc_public_property_types__?: { exposed: string }
152
+ * }
153
+ * const example = createElement('c-example', { is: Example })
154
+ * const exposed = example.exposed // type is 'string'
155
+ * const internal = example.internal // Now a type error! ✅
156
+ * ```
127
157
*/
128
158
export type LightningHTMLElement < T > = HTMLElement & ComponentClassProperties < T > ;
129
159
0 commit comments