@@ -98,21 +98,28 @@ if (process.env.NODE_ENV !== 'production') {
9898}
9999
100100/**
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.
102107 */
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 > ;
105113
106114/**
107115 * The custom element returned when calling {@linkcode createElement} with the given component
108116 * constructor.
109117 *
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,
111119 * 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:
114121 *
115- * @example ```
122+ * ```
116123 * class Example extends LightningElement {
117124 * @api exposed = 'hello'
118125 * internal = 'secret'
@@ -124,6 +131,29 @@ type ComponentClassProperties<T> = Omit<T, keyof LightningElement>;
124131 * const internal = example.internal // type is 'string'
125132 * console.log(internal) // prints `undefined`
126133 * ```
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+ * ```
127157 */
128158export type LightningHTMLElement < T > = HTMLElement & ComponentClassProperties < T > ;
129159
0 commit comments