Skip to content

Commit e5c2b3d

Browse files
committed
...
1 parent b4e8c01 commit e5c2b3d

File tree

5 files changed

+68
-55
lines changed

5 files changed

+68
-55
lines changed

packages/cx/src/jsx-runtime.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@ import { Widget } from "./ui/Widget";
44
import { isArray } from "./util/isArray";
55
import { isString } from "./util/isString";
66
import { HtmlElement, HtmlElementConfig } from "./widgets/HtmlElement";
7-
import {
8-
ReactElementWrapper,
9-
ReactElementWrapperConfig,
10-
TransformReactElementProps,
11-
} from "./widgets/ReactElementWrapper";
7+
import { ReactElementWrapper, ReactElementWrapperConfig } from "./widgets/ReactElementWrapper";
128

139
export function jsx(typeName: any, props: any, key?: string): any {
1410
if (isArray(typeName)) return typeName;

packages/cx/src/ui/Instance.ts

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
import { Controller } from "./Controller";
2-
import { debug, renderFlag, processDataFlag, destroyFlag } from "../util/Debug";
1+
import { isAccessorChain } from "../data/createAccessorModelProxy";
2+
import { View } from "../data/View";
3+
import { debug, destroyFlag, processDataFlag, renderFlag } from "../util/Debug";
34
import { GlobalCacheIdentifier } from "../util/GlobalCacheIdentifier";
4-
import { throttle } from "../util/throttle";
5-
import { validatedDebounce } from "../util/validatedDebounce";
6-
import { batchUpdates } from "./batchUpdates";
7-
import { isString } from "../util/isString";
8-
import { isFunction } from "../util/isFunction";
9-
import { isDefined } from "../util/isDefined";
105
import { isArray } from "../util/isArray";
11-
import { isObject } from "../util/isObject";
6+
import { isDefined } from "../util/isDefined";
7+
import { isFunction } from "../util/isFunction";
128
import { isNonEmptyArray } from "../util/isNonEmptyArray";
9+
import { isObject } from "../util/isObject";
10+
import { isString } from "../util/isString";
1311
import { isUndefined } from "../util/isUndefined";
14-
import { isAccessorChain } from "../data/createAccessorModelProxy";
15-
import { CxChild, RenderingContext } from "./RenderingContext";
12+
import { throttle } from "../util/throttle";
13+
import { validatedDebounce } from "../util/validatedDebounce";
14+
import { batchUpdates } from "./batchUpdates";
15+
import { Controller } from "./Controller";
16+
import { RenderingContext } from "./RenderingContext";
1617
import type { Widget } from "./Widget";
17-
import { View } from "../data/View";
1818

1919
/**
2020
* Serializable value types that can be safely passed through the framework
@@ -744,8 +744,7 @@ export class Instance<WidgetType extends Widget<any, any> = Widget<any, any>> {
744744
*/
745745
public getController(predicate: (controller: Controller) => boolean): Controller {
746746
const controller = this.findController(predicate);
747-
if (!controller)
748-
throw new Error("Cannot find a controller matching the given predicate in the instance tree.");
747+
if (!controller) throw new Error("Cannot find a controller matching the given predicate in the instance tree.");
749748
return controller;
750749
}
751750

@@ -757,8 +756,7 @@ export class Instance<WidgetType extends Widget<any, any> = Widget<any, any>> {
757756
*/
758757
public getControllerByType<T extends Controller>(type: new (...args: any[]) => T): T {
759758
const controller = this.findControllerByType(type);
760-
if (!controller)
761-
throw new Error(`Cannot find a controller of type "${type.name}" in the instance tree.`);
759+
if (!controller) throw new Error(`Cannot find a controller of type "${type.name}" in the instance tree.`);
762760
return controller;
763761
}
764762

packages/cx/src/widgets/HtmlElement.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ type CxEventHandler<T> = T extends (event: infer E) => any
5151
// Note: For string literal union props (like SVG's strokeLinecap), we also accept `string`
5252
// because TypeScript widens string literals to `string` in JSX attribute syntax.
5353
// This is a known TypeScript behavior where `<path strokeLinecap="round"/>` infers "round" as string.
54-
export type TransformHtmlElementProps<T> = {
54+
type TransformHtmlElementProps<T> = {
5555
[K in keyof T]: K extends "children"
5656
? ChildNode | ChildNode[]
5757
: K extends "className" | "class"
@@ -98,10 +98,7 @@ export interface HtmlElementConfigBase extends StyledContainerConfig {
9898
}
9999

100100
/** HtmlElement configuration with tag-specific attributes and events */
101-
export type HtmlElementConfig<Tag extends keyof ReactIntrinsicElements = "div"> = Omit<
102-
HtmlElementConfigBase,
103-
"tag"
104-
> &
101+
export type HtmlElementConfig<Tag extends keyof ReactIntrinsicElements = "div"> = Omit<HtmlElementConfigBase, "tag"> &
105102
TransformHtmlElementProps<ReactIntrinsicElements[Tag]> & { tag?: Tag };
106103

107104
export class HtmlElementInstance<E extends HtmlElement<any, any> = HtmlElement<any, any>>

packages/cx/src/widgets/ReactElementWrapper.spec.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,5 +429,24 @@ describe("ReactElementWrapper", () => {
429429
);
430430
assert.ok(widget);
431431
});
432+
433+
it("provides instance through this", () => {
434+
class TestController extends Controller {
435+
change(_v: number) {}
436+
}
437+
const widget = (
438+
<cx>
439+
<RequiredPropsComponent
440+
label={"label"}
441+
value={100}
442+
controller={TestController}
443+
onChange={function (v) {
444+
this.getControllerByType(TestController).change(v);
445+
}}
446+
/>
447+
</cx>
448+
);
449+
assert.ok(widget);
450+
});
432451
});
433452
});

packages/cx/src/widgets/ReactElementWrapper.tsx

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,40 @@
1-
import type { ComponentProps } from "react";
1+
import { ContainerBase, ContainerConfig } from "../ui/Container";
22
import { Instance } from "../ui/Instance";
3-
import { ChildNode, ContainerBase, ContainerConfig } from "../ui/Container";
4-
import { ControllerProp, VDOM } from "../ui/Widget";
3+
import { Prop } from "../ui/Prop";
54
import type { RenderingContext } from "../ui/RenderingContext";
5+
import { VDOM, Widget } from "../ui/Widget";
6+
import { isFunction, isNonEmptyArray } from "../util";
67
import { isArray } from "../util/isArray";
7-
import { Prop } from "../ui/Prop";
8-
import { TransformHtmlElementProps } from "./HtmlElement";
98

10-
// CxJS callback type - can be string (controller method) or callback with Instance as this
11-
type CxCallback<T> = T extends (...args: infer A) => infer R
12-
? string | ((this: Instance, ...args: A) => R)
13-
: string | T;
9+
type ReactElementWrapperConfigBase = Omit<ContainerConfig, "items">;
1410

15-
// Required keys in T
16-
type RequiredKeys<T> = {
17-
[K in keyof T]-?: {} extends Pick<T, K> ? never : K;
18-
}[keyof T];
19-
20-
// Optional keys in T
21-
type OptionalKeys<T> = {
22-
[K in keyof T]-?: {} extends Pick<T, K> ? K : never;
23-
}[keyof T];
11+
// CxJS callback type - can be string (controller method) or callback with Instance as this
12+
type CxCallback<T> = T extends (...args: infer A) => infer R ? (this: Instance, ...args: A) => R : T;
2413

2514
// Transform a single prop: functions to CxCallback, data to Prop<T>
26-
type TransformProp<K, V> = K extends "controller"
27-
? ControllerProp
28-
: K extends "children"
29-
? ChildNode | ChildNode[]
30-
: NonNullable<V> extends Function
31-
? CxCallback<V>
32-
: Prop<V>;
15+
type TransformProp<K, V> = K extends keyof ReactElementWrapperConfigBase
16+
? ReactElementWrapperConfigBase[K]
17+
: NonNullable<V> extends Function
18+
? CxCallback<V>
19+
: Prop<V>;
3320

3421
// Transform React component props - functions to CxCallback, data props to Prop<T>
3522
// Preserves required/optional status
36-
export type TransformReactElementProps<T> = {
23+
type TransformReactElementProps<T> = {
3724
[K in keyof T]: TransformProp<K, T[K]>;
3825
};
3926

4027
/** ReactElementWrapper configuration with component-specific props */
4128
// componentType is not included here - it's added by the jsx-runtime and declared in the class
42-
export type ReactElementWrapperConfig<P> = ContainerConfig & TransformReactElementProps<P>;
29+
export type ReactElementWrapperConfig<P> = ReactElementWrapperConfigBase & TransformReactElementProps<P>;
30+
31+
interface ReactElementWrapperInstance extends Instance {
32+
events?: Record<string, Function>;
33+
}
4334

4435
export class ReactElementWrapper<C extends React.ComponentType<any> = React.ComponentType<any>> extends ContainerBase<
45-
ContainerConfig & { componentType: C }
36+
ReactElementWrapperConfigBase & { componentType: C; jsxAttributes?: string[] },
37+
ReactElementWrapperInstance
4638
> {
4739
declare public componentType: React.ComponentType<any>;
4840
declare public jsxAttributes?: string[];
@@ -78,6 +70,16 @@ export class ReactElementWrapper<C extends React.ComponentType<any> = React.Comp
7870
super.init();
7971
}
8072

73+
public initInstance(_context: RenderingContext, _instance: ReactElementWrapperInstance): void {
74+
let events: Record<string, Function> | undefined;
75+
if (!isNonEmptyArray(this.jsxAttributes)) return;
76+
events = {};
77+
for (let prop of this.jsxAttributes) {
78+
let f = this[prop];
79+
if (prop.startsWith("on") && isFunction(f)) events[prop] = (...args: any[]) => f.apply(_instance, args);
80+
}
81+
}
82+
8183
declareData(...args: Record<string, unknown>[]): void {
8284
super.declareData(
8385
{
@@ -87,7 +89,7 @@ export class ReactElementWrapper<C extends React.ComponentType<any> = React.Comp
8789
);
8890
}
8991

90-
render(context: RenderingContext, instance: Instance, key: string): React.ReactNode {
92+
render(context: RenderingContext, instance: ReactElementWrapperInstance, key: string): React.ReactNode {
9193
const { data } = instance;
9294

9395
// Render CxJS children to React elements
@@ -97,6 +99,7 @@ export class ReactElementWrapper<C extends React.ComponentType<any> = React.Comp
9799
const props = {
98100
key,
99101
...data.props,
102+
...instance.events,
100103
children,
101104
};
102105

0 commit comments

Comments
 (0)