Skip to content

Commit ebfe2f4

Browse files
committed
wip
1 parent 494e799 commit ebfe2f4

File tree

8 files changed

+132
-135
lines changed

8 files changed

+132
-135
lines changed

packages/qwik/src/core/shared/component-execution.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ function addUseOnEvents(
145145
): ValueOrPromise<JSXNodeInternal<string> | null | JSXOutput> {
146146
const jsxElement = findFirstElementNode(jsx);
147147
let jsxResult = jsx;
148-
const qVisibleEvent = 'onQvisible$';
148+
const qVisibleEvent = 'on:qvisible';
149149
return maybeThen(jsxElement, (jsxElement) => {
150150
// headless components are components that don't render a real DOM element
151151
const isHeadless = !jsxElement;
@@ -184,7 +184,7 @@ function addUseOnEvents(
184184
}
185185
if (targetElement) {
186186
if (targetElement.type === 'script' && key === qVisibleEvent) {
187-
eventKey = 'document:onQinit$';
187+
eventKey = 'on-document:qinit';
188188
if (isDev) {
189189
logWarn(
190190
'You are trying to add an event "' +
@@ -235,7 +235,6 @@ function addUseOnEvent(
235235
}
236236
props[key] = undefined;
237237
}
238-
console.log('addUseOnEvent', key, jsxElement);
239238
}
240239

241240
/**

packages/qwik/src/core/shared/jsx/jsx-node.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { createQRL } from '../qrl/qrl-class';
2+
import type { QRL } from '../qrl/qrl.public';
3+
import { jsxEventToHtmlAttribute } from '../utils/event-names';
24
import { EMPTY_OBJ } from '../utils/flyweight';
35
import { logOnceWarn, logWarn } from '../utils/log';
46
import { qDev, seal } from '../utils/qdev';
@@ -46,6 +48,25 @@ export class JSXNodeImpl<T = unknown> implements JSXNodeInternal<T> {
4648
}
4749

4850
if (typeof type === 'string') {
51+
// convert onEvent$ to on:event
52+
for (const k in this.constProps) {
53+
const attr = jsxEventToHtmlAttribute(k);
54+
if (attr) {
55+
mergeHandlers(this.constProps, attr, this.constProps[k] as QRL);
56+
this.constProps[k] = null;
57+
}
58+
}
59+
for (const k in this.varProps) {
60+
const attr = jsxEventToHtmlAttribute(k);
61+
if (attr) {
62+
// constProps always wins
63+
if (!constProps || !(k in constProps)) {
64+
toSort ||= mergeHandlers(this.varProps, attr, this.varProps[k] as QRL);
65+
}
66+
this.varProps[k] = null;
67+
}
68+
}
69+
4970
// bind:*
5071
if (BIND_CHECKED in this.varProps) {
5172
toSort ||= handleBindProp(this.varProps, BIND_CHECKED)!;
@@ -91,6 +112,21 @@ export class JSXNodeImpl<T = unknown> implements JSXNodeInternal<T> {
91112
}
92113
}
93114

115+
/** @returns `true` if the event is new to the object */
116+
export const mergeHandlers = (obj: Props, event: string, handler: QRL) => {
117+
let current = obj[event];
118+
if (current) {
119+
if (Array.isArray(current)) {
120+
current.push(handler);
121+
} else {
122+
current = obj[event] = [current, handler];
123+
}
124+
} else {
125+
obj[event] = handler;
126+
return true;
127+
}
128+
};
129+
94130
/** @internal */
95131
export const isJSXNode = <T>(n: unknown): n is JSXNodeInternal<T> => {
96132
if (qDev) {

packages/qwik/src/core/shared/jsx/props-proxy.ts

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { WrappedSignalImpl } from '../../reactive-primitives/impl/wrapped-signal-impl';
22
import { WrappedSignalFlags } from '../../reactive-primitives/types';
33
import { _CONST_PROPS, _VAR_PROPS, _OWNER } from '../utils/constants';
4+
import { jsxEventToHtmlAttribute } from '../utils/event-names';
45
import { EMPTY_OBJ } from '../utils/flyweight';
56
import type { JSXNodeImpl } from './jsx-node';
67
import type { Props } from './jsx-runtime';
@@ -23,12 +24,18 @@ class PropsProxyHandler implements ProxyHandler<any> {
2324
if (prop === _OWNER) {
2425
return this.owner;
2526
}
26-
const value =
27-
prop === 'children'
28-
? this.owner.children
29-
: this.owner.constProps && prop in this.owner.constProps
30-
? this.owner.constProps[prop as string]
31-
: this.owner.varProps[prop as string];
27+
let value: unknown;
28+
if (prop === 'children') {
29+
value = this.owner.children;
30+
} else {
31+
if (typeof prop === 'string') {
32+
const attr = jsxEventToHtmlAttribute(prop as string);
33+
if (attr) {
34+
prop = attr;
35+
}
36+
}
37+
value = directGetPropsProxyProp(this.owner, prop as string);
38+
}
3239
// a proxied value that the optimizer made
3340
return value instanceof WrappedSignalImpl && value.$flags$ & WrappedSignalFlags.UNWRAP
3441
? value.value
@@ -40,17 +47,29 @@ class PropsProxyHandler implements ProxyHandler<any> {
4047
this.owner = value;
4148
} else if (prop === 'children') {
4249
this.owner.children = value;
43-
} else if (this.owner.constProps && prop in this.owner.constProps) {
44-
this.owner.constProps[prop as string] = value;
4550
} else {
46-
if (this.owner.varProps === EMPTY_OBJ) {
47-
this.owner.varProps = {};
48-
} else {
51+
if (typeof prop === 'string') {
52+
const attr = jsxEventToHtmlAttribute(prop as string);
53+
if (attr) {
54+
prop = attr;
55+
}
56+
}
57+
if (this.owner.constProps && prop in this.owner.constProps) {
58+
this.owner.constProps[prop as string] = undefined;
4959
if (!(prop in this.owner.varProps)) {
5060
this.owner.toSort = true;
5161
}
62+
this.owner.varProps[prop as string] = value;
63+
} else {
64+
if (this.owner.varProps === EMPTY_OBJ) {
65+
this.owner.varProps = {};
66+
} else {
67+
if (!(prop in this.owner.varProps)) {
68+
this.owner.toSort = true;
69+
}
70+
}
71+
this.owner.varProps[prop as string] = value;
5272
}
53-
this.owner.varProps[prop as string] = value;
5473
}
5574
return true;
5675
}
@@ -66,14 +85,21 @@ class PropsProxyHandler implements ProxyHandler<any> {
6685
return didDelete;
6786
}
6887
has(_: any, prop: string | symbol) {
69-
const hasProp =
70-
prop === 'children'
71-
? this.owner.children != null
72-
: prop === _CONST_PROPS ||
73-
prop === _VAR_PROPS ||
74-
prop in this.owner.varProps ||
75-
(this.owner.constProps ? prop in this.owner.constProps : false);
76-
return hasProp;
88+
if (prop === 'children') {
89+
return this.owner.children != null;
90+
} else if (prop === _CONST_PROPS || prop === _VAR_PROPS) {
91+
return true;
92+
}
93+
if (typeof prop === 'string') {
94+
const attr = jsxEventToHtmlAttribute(prop as string);
95+
if (attr) {
96+
prop = attr;
97+
}
98+
}
99+
100+
return (
101+
prop in this.owner.varProps || (this.owner.constProps ? prop in this.owner.constProps : false)
102+
);
77103
}
78104
getOwnPropertyDescriptor(_: any, p: string | symbol): PropertyDescriptor | undefined {
79105
const value =
@@ -115,7 +141,7 @@ export const directGetPropsProxyProp = <T, JSX>(jsx: JSXNodeInternal<JSX>, prop:
115141
};
116142

117143
/** Used by the optimizer for spread props operations @internal */
118-
export const _getVarProps = <T, JSX>(
144+
export const _getVarProps = (
119145
props: PropsProxy | Record<string, unknown> | null | undefined
120146
): Props | null => {
121147
if (!props) {
@@ -128,7 +154,7 @@ export const _getVarProps = <T, JSX>(
128154
: props;
129155
};
130156
/** Used by the optimizer for spread props operations @internal */
131-
export const _getConstProps = <T, JSX>(
157+
export const _getConstProps = (
132158
props: PropsProxy | Record<string, unknown> | null | undefined
133159
): Props | null => {
134160
if (!props) {

packages/qwik/src/core/shared/jsx/types/jsx-node.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,25 @@ export interface JSXNodeInternal<T extends string | FunctionComponent | unknown
5353
toSort: boolean;
5454
/** The key property */
5555
key: string | null;
56-
/** Props that are not guaranteed shallow equal across runs. Does not contain children or key */
56+
/**
57+
* Props that are not guaranteed shallow equal across runs.
58+
*
59+
* Any prop that is in `constProps` takes precedence over `varProps`.
60+
*
61+
* Does not contain `children` or `key`.
62+
*
63+
* `onEvent$` props are normalized to the html `on:event` version
64+
*/
5765
varProps: Props;
58-
/** Props that will be shallow equal across runs. Does not contain children or key */
66+
/**
67+
* Props that will be shallow equal across runs. Does not contain any props that are in varProps.
68+
*
69+
* Any prop that is in `constProps` takes precedence over `varProps`.
70+
*
71+
* Does not contain `children` or `key`.
72+
*
73+
* `onEvent$` props are normalized to the html `on:event` version
74+
*/
5975
constProps: Props | null;
6076
/** The children of the node */
6177
children: JSXChildren;

packages/qwik/src/core/shared/utils/event-names.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,7 @@ export const isJsxPropertyAnEventName = (name: string): boolean => {
3434
};
3535

3636
export const isHtmlAttributeAnEventName = (name: string): boolean => {
37-
return (
38-
name.startsWith(EventNameHtmlScope.on) ||
39-
name.startsWith(EventNameHtmlScope.window) ||
40-
name.startsWith(EventNameHtmlScope.document)
41-
);
37+
return /^on(|-(window|document)):/.test(name);
4238
};
4339

4440
/**

packages/qwik/src/core/ssr/ssr-render-jsx.ts

Lines changed: 15 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,7 @@ import type { QRL } from '../shared/qrl/qrl.public';
1616
import { qrlToString, type SerializationContext } from '../shared/serdes/index';
1717
import { DEBUG_TYPE, VirtualType } from '../shared/types';
1818
import { isAsyncGenerator } from '../shared/utils/async-generator';
19-
import {
20-
getEventNameFromJsxEvent,
21-
isJsxPropertyAnEventName,
22-
isPreventDefault,
23-
jsxEventToHtmlAttribute,
24-
} from '../shared/utils/event-names';
19+
import { isHtmlAttributeAnEventName, isPreventDefault } from '../shared/utils/event-names';
2520
import { EMPTY_ARRAY } from '../shared/utils/flyweight';
2621
import { getFileLocationFromJsx } from '../shared/utils/jsx-filename';
2722
import {
@@ -321,67 +316,33 @@ export function varPropsToSsrAttrs(
321316
constProps: Record<string, unknown> | null,
322317
options: SsrAttrsOptions
323318
): SsrAttrs | null {
324-
return toSsrAttrs(varProps, constProps, false, options);
319+
return toSsrAttrs(varProps, options);
325320
}
326321

327322
export function constPropsToSsrAttrs(
328323
constProps: Record<string, unknown> | null,
329324
varProps: Record<string, unknown>,
330325
options: SsrAttrsOptions
331326
): SsrAttrs | null {
332-
return toSsrAttrs(constProps, varProps, true, options);
327+
return toSsrAttrs(constProps, options);
333328
}
334329

335330
export function toSsrAttrs(
336331
record: Record<string, unknown> | null | undefined,
337-
anotherRecord: Record<string, unknown> | null | undefined,
338-
isConst: boolean,
339332
options: SsrAttrsOptions
340333
): SsrAttrs | null {
341334
if (record == null) {
342335
return null;
343336
}
344-
const pushMergedEventProps = !isConst;
345337
const ssrAttrs: SsrAttrs = [];
346338
const handleProp = (key: string, value: unknown) => {
347339
if (value == null) {
348340
return;
349341
}
350-
if (isJsxPropertyAnEventName(key)) {
351-
if (anotherRecord) {
352-
/**
353-
* If we have two sources of the same event like this:
354-
*
355-
* ```tsx
356-
* const Counter = component$((props: { initial: number }) => {
357-
* const count = useSignal(props.initial);
358-
* useOnWindow(
359-
* 'dblclick',
360-
* $(() => count.value++)
361-
* );
362-
* return <button window:onDblClick$={() => count.value++}>Count: {count.value}!</button>;
363-
* });
364-
* ```
365-
*
366-
* Then we can end with the const and var props with the same (doubled) event. We process
367-
* the const and var props separately, so:
368-
*
369-
* - For the var props we need to merge them into the one value (array)
370-
* - For the const props we need to just skip, because we will handle this in the var props
371-
*/
372-
const anotherValue = getEventProp(anotherRecord, key);
373-
if (anotherValue) {
374-
if (pushMergedEventProps) {
375-
// merge values from the const props with the var props
376-
value = getMergedEventPropValues(value, anotherValue);
377-
} else {
378-
return;
379-
}
380-
}
381-
}
342+
if (isHtmlAttributeAnEventName(key)) {
382343
const eventValue = setEvent(options.serializationCtx, key, value);
383344
if (eventValue) {
384-
ssrAttrs.push(jsxEventToHtmlAttribute(key), eventValue);
345+
ssrAttrs.push(key, eventValue);
385346
}
386347
return;
387348
}
@@ -422,36 +383,6 @@ export function toSsrAttrs(
422383
return ssrAttrs;
423384
}
424385

425-
function getMergedEventPropValues(value: unknown, anotherValue: unknown) {
426-
let mergedValue = value;
427-
// merge values from the const props with the var props
428-
if (Array.isArray(value) && Array.isArray(anotherValue)) {
429-
// both values are arrays
430-
mergedValue = value.concat(anotherValue);
431-
} else if (Array.isArray(mergedValue)) {
432-
// only first value is array
433-
mergedValue.push(anotherValue);
434-
} else if (Array.isArray(anotherValue)) {
435-
// only second value is array
436-
mergedValue = anotherValue;
437-
(mergedValue as unknown[]).push(value);
438-
} else {
439-
// none of these values are array
440-
mergedValue = [value, anotherValue];
441-
}
442-
return mergedValue;
443-
}
444-
445-
function getEventProp(record: Record<string, unknown>, propKey: string): unknown | null {
446-
const eventProp = propKey.toLowerCase();
447-
for (const prop in record) {
448-
if (prop.toLowerCase() === eventProp) {
449-
return record[prop];
450-
}
451-
}
452-
return null;
453-
}
454-
455386
function setEvent(
456387
serializationCtx: SerializationContext,
457388
key: string,
@@ -503,9 +434,17 @@ function addQwikEventToSerializationContext(
503434
key: string,
504435
qrl: QRL
505436
) {
506-
const eventName = getEventNameFromJsxEvent(key);
507-
if (eventName) {
437+
// TODO extract window/document too so qwikloader can precisely listen
438+
const match = /^on(|-(window|document)):(.+)$/.exec(key);
439+
if (match) {
440+
const eventName = match[3];
508441
serializationCtx.$eventNames$.add(eventName);
442+
console.log(
443+
'addQwikEventToSerializationContext',
444+
key,
445+
eventName,
446+
serializationCtx.$eventNames$
447+
);
509448
serializationCtx.$eventQrls$.add(qrl);
510449
}
511450
}

0 commit comments

Comments
 (0)