@reckona/mreact-compat 0.0.140 → 0.0.141

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reckona/mreact-compat",
3
- "version": "0.0.140",
3
+ "version": "0.0.141",
4
4
  "description": "React-compatible runtime implementation for mreact.",
5
5
  "keywords": [
6
6
  "compatibility",
@@ -69,7 +69,7 @@
69
69
  "access": "public"
70
70
  },
71
71
  "dependencies": {
72
- "@reckona/mreact-reactive-core": "0.0.140",
73
- "@reckona/mreact-shared": "0.0.140"
72
+ "@reckona/mreact-reactive-core": "0.0.141",
73
+ "@reckona/mreact-shared": "0.0.141"
74
74
  }
75
75
  }
package/src/dom-props.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import {
2
2
  getAppliedProps,
3
3
  setAppliedProps,
4
- ensureDelegatedEventListener,
5
- toEventNames,
4
+ ensureDelegatedEventListenersForProp,
6
5
  } from "./host-event-binder.js";
7
6
  import { HOST_OWN_PROPS_META } from "./element.js";
8
7
  import { reportRecoverable, type RenderOptions } from "./hydration.js";
@@ -105,9 +104,7 @@ export function applyProps(
105
104
  continue;
106
105
  }
107
106
 
108
- for (const eventName of toEventNames(name)) {
109
- ensureDelegatedEventListener(options.eventRoot ?? element, eventName);
110
- }
107
+ ensureDelegatedEventListenersForProp(options.eventRoot ?? element, name);
111
108
  continue;
112
109
  }
113
110
 
@@ -223,9 +220,7 @@ function applyInitialProps(
223
220
  }
224
221
 
225
222
  if (isReactEventHandlerPropName(name) && typeof value === "function") {
226
- for (const eventName of toEventNames(name)) {
227
- ensureDelegatedEventListener(options.eventRoot ?? element, eventName);
228
- }
223
+ ensureDelegatedEventListenersForProp(options.eventRoot ?? element, name);
229
224
  continue;
230
225
  }
231
226
 
@@ -693,7 +688,7 @@ function sanitizeMetaRefreshElementProps(
693
688
  element: Element,
694
689
  props: Record<string, unknown>,
695
690
  ): Record<string, unknown> {
696
- if (element.tagName.toLowerCase() !== "meta") {
691
+ if (element.tagName !== "META" && element.tagName !== "meta") {
697
692
  return props;
698
693
  }
699
694
 
package/src/element.ts CHANGED
@@ -12,6 +12,9 @@ export const SuspenseList = Symbol.for("react.suspense_list");
12
12
  export const Activity = Symbol.for("react.activity");
13
13
  export const Profiler = Symbol.for("react.profiler");
14
14
  export const HOST_OWN_PROPS_META = Symbol.for("modular.react.host_own_props_meta");
15
+ export const HOST_CHILDREN_ONLY_PROPS_META = Symbol.for(
16
+ "modular.react.host_children_only_props_meta",
17
+ );
15
18
  const hasOwnProperty = Object.prototype.hasOwnProperty;
16
19
 
17
20
  export interface ReactCompatProviderType {
@@ -271,12 +274,25 @@ function copyElementProps(
271
274
  base?: Record<string, unknown>,
272
275
  omitChildren = false,
273
276
  ): Record<string, unknown> {
274
- const props: Record<string, unknown> = base === undefined ? {} : { ...base };
277
+ const props: Record<string, unknown> = {};
278
+
279
+ if (base !== undefined) {
280
+ copyOwnStringElementProps(base, props, omitChildren);
281
+ }
275
282
 
276
283
  if (source === null || source === undefined) {
277
284
  return props;
278
285
  }
279
286
 
287
+ copyOwnStringElementProps(source, props, omitChildren);
288
+ return props;
289
+ }
290
+
291
+ function copyOwnStringElementProps(
292
+ source: Record<string, unknown>,
293
+ target: Record<string, unknown>,
294
+ omitChildren: boolean,
295
+ ): void {
280
296
  for (const name in source) {
281
297
  if (!hasOwnProperty.call(source, name)) {
282
298
  continue;
@@ -289,11 +305,9 @@ function copyElementProps(
289
305
  name !== "__source" &&
290
306
  (!omitChildren || name !== "children")
291
307
  ) {
292
- props[name] = source[name];
308
+ target[name] = source[name];
293
309
  }
294
310
  }
295
-
296
- return props;
297
311
  }
298
312
 
299
313
  function normalizeElementType<P>(type: ElementType<P>): ElementType<P> {
@@ -351,6 +365,11 @@ function setHostOwnPropsMeta(props: Record<string, unknown>): void {
351
365
  const dataKey = props["data-key"];
352
366
 
353
367
  if (typeof dataKey !== "number" || !Number.isSafeInteger(dataKey) || dataKey < 0) {
368
+ if (hostPropsAreChildrenOnly(props)) {
369
+ (props as { [HOST_CHILDREN_ONLY_PROPS_META]?: true })[
370
+ HOST_CHILDREN_ONLY_PROPS_META
371
+ ] = true;
372
+ }
354
373
  return;
355
374
  }
356
375
 
@@ -402,6 +421,16 @@ function setHostOwnPropsMeta(props: Record<string, unknown>): void {
402
421
  dataKey * 4 + selectedState;
403
422
  }
404
423
 
424
+ function hostPropsAreChildrenOnly(props: Record<string, unknown>): boolean {
425
+ for (const name in props) {
426
+ if (hasOwnProperty.call(props, name) && name !== "children") {
427
+ return false;
428
+ }
429
+ }
430
+
431
+ return true;
432
+ }
433
+
405
434
  export const isValidElement = isReactCompatElement;
406
435
 
407
436
  export const Children = {
package/src/events.ts CHANGED
@@ -90,12 +90,109 @@ const nativeEventToReactProps = new Map<string, string[]>([
90
90
  ]);
91
91
 
92
92
  export function toEventNames(propName: string): string[] {
93
+ const eventNames: string[] = [];
94
+ forEachEventName(propName, (eventName) => {
95
+ eventNames.push(eventName);
96
+ });
97
+ return eventNames;
98
+ }
99
+
100
+ export function forEachEventName(
101
+ propName: string,
102
+ callback: (eventName: string) => void,
103
+ ): void {
104
+ const directEventName = directNativeEventName(propName);
105
+
106
+ if (directEventName !== undefined) {
107
+ callback(directEventName);
108
+ return;
109
+ }
110
+
111
+ const basePropName = toBaseEventPropName(propName);
112
+ const mappedEventNames = reactPropToNativeEvent.get(basePropName);
113
+
114
+ if (mappedEventNames === undefined) {
115
+ callback(basePropName.slice(2).toLowerCase());
116
+ return;
117
+ }
118
+
119
+ for (let index = 0; index < mappedEventNames.length; index += 1) {
120
+ callback(mappedEventNames[index]!);
121
+ }
122
+ }
123
+
124
+ export function ensureDelegatedEventListenersForProp(
125
+ root: Element,
126
+ propName: string,
127
+ ): void {
128
+ const directEventName = directNativeEventName(propName);
129
+
130
+ if (directEventName !== undefined) {
131
+ ensureDelegatedEventListener(root, directEventName);
132
+ return;
133
+ }
134
+
135
+ const basePropName = toBaseEventPropName(propName);
136
+ const mappedEventNames = reactPropToNativeEvent.get(basePropName);
137
+
138
+ if (mappedEventNames === undefined) {
139
+ ensureDelegatedEventListener(root, basePropName.slice(2).toLowerCase());
140
+ return;
141
+ }
142
+
143
+ for (let index = 0; index < mappedEventNames.length; index += 1) {
144
+ ensureDelegatedEventListener(root, mappedEventNames[index]!);
145
+ }
146
+ }
147
+
148
+ function directNativeEventName(propName: string): string | undefined {
149
+ switch (propName) {
150
+ case "onClick":
151
+ case "onClickCapture":
152
+ return "click";
153
+ case "onInput":
154
+ case "onInputCapture":
155
+ return "input";
156
+ case "onKeyDown":
157
+ case "onKeyDownCapture":
158
+ return "keydown";
159
+ case "onKeyUp":
160
+ case "onKeyUpCapture":
161
+ return "keyup";
162
+ case "onMouseDown":
163
+ case "onMouseDownCapture":
164
+ return "mousedown";
165
+ case "onMouseMove":
166
+ case "onMouseMoveCapture":
167
+ return "mousemove";
168
+ case "onMouseOut":
169
+ case "onMouseOutCapture":
170
+ return "mouseout";
171
+ case "onMouseOver":
172
+ case "onMouseOverCapture":
173
+ return "mouseover";
174
+ case "onMouseUp":
175
+ case "onMouseUpCapture":
176
+ return "mouseup";
177
+ case "onScroll":
178
+ case "onScrollCapture":
179
+ return "scroll";
180
+ case "onSubmit":
181
+ case "onSubmitCapture":
182
+ return "submit";
183
+ case "onWheel":
184
+ case "onWheelCapture":
185
+ return "wheel";
186
+ default:
187
+ return undefined;
188
+ }
189
+ }
190
+
191
+ function toBaseEventPropName(propName: string): string {
93
192
  const basePropName = propName.endsWith("Capture")
94
193
  ? propName.slice(0, -"Capture".length)
95
194
  : propName;
96
- return reactPropToNativeEvent.get(basePropName) ?? [
97
- basePropName.slice(2).toLowerCase(),
98
- ];
195
+ return basePropName;
99
196
  }
100
197
 
101
198
  export function toEventPropNames(eventName: string): string[] {
@@ -7,6 +7,8 @@ export {
7
7
  } from "./event-listeners.js";
8
8
  export {
9
9
  ensureDelegatedEventListener,
10
+ ensureDelegatedEventListenersForProp,
11
+ forEachEventName,
10
12
  getEventPriority,
11
13
  setLogicalEventParent,
12
14
  toEventNames,
@@ -3,6 +3,7 @@ import {
3
3
  ERROR_BOUNDARY_TYPE,
4
4
  FORWARD_REF_TYPE,
5
5
  Fragment,
6
+ HOST_CHILDREN_ONLY_PROPS_META,
6
7
  HOST_OWN_PROPS_META,
7
8
  LAZY_TYPE,
8
9
  MEMO_TYPE,
@@ -1621,13 +1622,23 @@ function commitHostDirtyFiber(
1621
1622
 
1622
1623
  const props = fiber.pendingProps as Record<string, unknown>;
1623
1624
  const previousProps = fiber.memoizedProps as Record<string, unknown> | undefined;
1625
+ const directTextChild =
1626
+ fiber.child === undefined && fiber.hydrateExisting !== true
1627
+ ? getDirectHostTextChild(props.children)
1628
+ : undefined;
1629
+ const textOnlyChildrenUpdate =
1630
+ directTextChild !== undefined &&
1631
+ hostPropsAreKnownChildrenOnly(fiber.memoizedProps) &&
1632
+ hostPropsAreKnownChildrenOnly(props);
1624
1633
  const propsAreUnchanged =
1625
1634
  fiber.hydrateExisting !== true &&
1635
+ !textOnlyChildrenUpdate &&
1626
1636
  hostPropsEqual(fiber.memoizedProps, props);
1627
1637
  const propsAreChildrenOnly =
1628
- fiber.hydrateExisting !== true &&
1629
- hostPropsAreChildrenOnly(fiber.memoizedProps) &&
1630
- hostPropsAreChildrenOnly(props);
1638
+ textOnlyChildrenUpdate ||
1639
+ (fiber.hydrateExisting !== true &&
1640
+ hostPropsAreChildrenOnly(fiber.memoizedProps) &&
1641
+ hostPropsAreChildrenOnly(props));
1631
1642
  const textOnlyRowUpdate =
1632
1643
  fiber.hydrateExisting !== true &&
1633
1644
  isRowTextOnlyUpdate(fiber.memoizedProps, props);
@@ -1641,11 +1652,6 @@ function commitHostDirtyFiber(
1641
1652
  applyChangedRef(previousProps?.ref, props.ref, element);
1642
1653
  }
1643
1654
 
1644
- const directTextChild =
1645
- fiber.child === undefined && fiber.hydrateExisting !== true
1646
- ? getDirectHostTextChild(props.children)
1647
- : undefined;
1648
-
1649
1655
  if (directTextChild !== undefined) {
1650
1656
  syncDirectHostTextChild(element, directTextChild);
1651
1657
  } else if (fiber.subtreeFlags !== NoFlags) {
@@ -2007,13 +2013,23 @@ function commitHostFiber(
2007
2013
 
2008
2014
  const props = fiber.pendingProps as Record<string, unknown>;
2009
2015
  const previousProps = fiber.memoizedProps as Record<string, unknown> | undefined;
2016
+ const directTextChild =
2017
+ fiber.child === undefined && fiber.hydrateExisting !== true
2018
+ ? getDirectHostTextChild(props.children)
2019
+ : undefined;
2020
+ const textOnlyChildrenUpdate =
2021
+ directTextChild !== undefined &&
2022
+ hostPropsAreKnownChildrenOnly(fiber.memoizedProps) &&
2023
+ hostPropsAreKnownChildrenOnly(props);
2010
2024
  const propsAreUnchanged =
2011
2025
  fiber.hydrateExisting !== true &&
2026
+ !textOnlyChildrenUpdate &&
2012
2027
  hostPropsEqual(fiber.memoizedProps, props);
2013
2028
  const propsAreChildrenOnly =
2014
- fiber.hydrateExisting !== true &&
2015
- hostPropsAreChildrenOnly(fiber.memoizedProps) &&
2016
- hostPropsAreChildrenOnly(props);
2029
+ textOnlyChildrenUpdate ||
2030
+ (fiber.hydrateExisting !== true &&
2031
+ hostPropsAreChildrenOnly(fiber.memoizedProps) &&
2032
+ hostPropsAreChildrenOnly(props));
2017
2033
  const textOnlyRowUpdate =
2018
2034
  fiber.hydrateExisting !== true &&
2019
2035
  isRowTextOnlyUpdate(fiber.memoizedProps, props);
@@ -2026,11 +2042,6 @@ function commitHostFiber(
2026
2042
  });
2027
2043
  applyChangedRef(previousProps?.ref, props.ref, element);
2028
2044
  }
2029
- const directTextChild =
2030
- fiber.child === undefined && fiber.hydrateExisting !== true
2031
- ? getDirectHostTextChild(props.children)
2032
- : undefined;
2033
-
2034
2045
  if (directTextChild !== undefined) {
2035
2046
  syncDirectHostTextChild(element, directTextChild);
2036
2047
  } else if (
@@ -2352,6 +2363,16 @@ function hostPropsAreChildrenOnly(props: unknown): boolean {
2352
2363
  return true;
2353
2364
  }
2354
2365
 
2366
+ function hostPropsAreKnownChildrenOnly(props: unknown): boolean {
2367
+ return (
2368
+ typeof props === "object" &&
2369
+ props !== null &&
2370
+ (props as { [HOST_CHILDREN_ONLY_PROPS_META]?: true })[
2371
+ HOST_CHILDREN_ONLY_PROPS_META
2372
+ ] === true
2373
+ );
2374
+ }
2375
+
2355
2376
  function isRowTextOnlyUpdate(previous: unknown, next: Record<string, unknown>): boolean {
2356
2377
  if (typeof previous !== "object" || previous === null) {
2357
2378
  return false;