@reckona/mreact-compat 0.0.91 → 0.0.93

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.
Files changed (104) hide show
  1. package/README.md +1 -0
  2. package/dist/class-component.d.ts +22 -6
  3. package/dist/class-component.d.ts.map +1 -1
  4. package/dist/class-component.js +157 -51
  5. package/dist/class-component.js.map +1 -1
  6. package/dist/context.d.ts +19 -3
  7. package/dist/context.d.ts.map +1 -1
  8. package/dist/context.js +55 -6
  9. package/dist/context.js.map +1 -1
  10. package/dist/dom-children.d.ts +2 -0
  11. package/dist/dom-children.d.ts.map +1 -1
  12. package/dist/dom-children.js +103 -1
  13. package/dist/dom-children.js.map +1 -1
  14. package/dist/dom-host-rules.d.ts +10 -0
  15. package/dist/dom-host-rules.d.ts.map +1 -0
  16. package/dist/dom-host-rules.js +86 -0
  17. package/dist/dom-host-rules.js.map +1 -0
  18. package/dist/dom-props.d.ts +3 -2
  19. package/dist/dom-props.d.ts.map +1 -1
  20. package/dist/dom-props.js +229 -33
  21. package/dist/dom-props.js.map +1 -1
  22. package/dist/element.d.ts +9 -4
  23. package/dist/element.d.ts.map +1 -1
  24. package/dist/element.js +101 -26
  25. package/dist/element.js.map +1 -1
  26. package/dist/event-listeners.d.ts +4 -4
  27. package/dist/event-listeners.d.ts.map +1 -1
  28. package/dist/event-listeners.js +1 -1
  29. package/dist/event-listeners.js.map +1 -1
  30. package/dist/event-types.d.ts +10 -0
  31. package/dist/event-types.d.ts.map +1 -1
  32. package/dist/event-types.js.map +1 -1
  33. package/dist/events.js +22 -1
  34. package/dist/events.js.map +1 -1
  35. package/dist/fiber-commit.d.ts +2 -1
  36. package/dist/fiber-commit.d.ts.map +1 -1
  37. package/dist/fiber-commit.js +13 -1
  38. package/dist/fiber-commit.js.map +1 -1
  39. package/dist/fiber-reconciler.d.ts.map +1 -1
  40. package/dist/fiber-reconciler.js +28 -7
  41. package/dist/fiber-reconciler.js.map +1 -1
  42. package/dist/fiber-work-loop.d.ts.map +1 -1
  43. package/dist/fiber-work-loop.js +4 -3
  44. package/dist/fiber-work-loop.js.map +1 -1
  45. package/dist/fiber.d.ts +5 -0
  46. package/dist/fiber.d.ts.map +1 -1
  47. package/dist/fiber.js +9 -0
  48. package/dist/fiber.js.map +1 -1
  49. package/dist/hooks-entry.d.ts +3 -0
  50. package/dist/hooks-entry.d.ts.map +1 -0
  51. package/dist/hooks-entry.js +2 -0
  52. package/dist/hooks-entry.js.map +1 -0
  53. package/dist/hooks.d.ts +39 -5
  54. package/dist/hooks.d.ts.map +1 -1
  55. package/dist/hooks.js +373 -326
  56. package/dist/hooks.js.map +1 -1
  57. package/dist/host-reconciler.d.ts +3 -0
  58. package/dist/host-reconciler.d.ts.map +1 -1
  59. package/dist/host-reconciler.js +1183 -68
  60. package/dist/host-reconciler.js.map +1 -1
  61. package/dist/hydration.d.ts +1 -1
  62. package/dist/hydration.d.ts.map +1 -1
  63. package/dist/hydration.js.map +1 -1
  64. package/dist/index.d.ts +2 -1
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +2 -1
  67. package/dist/index.js.map +1 -1
  68. package/dist/react-default.d.ts +4 -4
  69. package/dist/react-default.d.ts.map +1 -1
  70. package/dist/react-default.js +2 -1
  71. package/dist/react-default.js.map +1 -1
  72. package/dist/reconciler.d.ts.map +1 -1
  73. package/dist/reconciler.js +38 -22
  74. package/dist/reconciler.js.map +1 -1
  75. package/dist/root.d.ts.map +1 -1
  76. package/dist/root.js +48 -13
  77. package/dist/root.js.map +1 -1
  78. package/dist/server-render.d.ts +6 -0
  79. package/dist/server-render.d.ts.map +1 -0
  80. package/dist/server-render.js +307 -0
  81. package/dist/server-render.js.map +1 -0
  82. package/package.json +6 -2
  83. package/src/class-component.ts +313 -51
  84. package/src/context.ts +108 -9
  85. package/src/dom-children.ts +155 -1
  86. package/src/dom-host-rules.ts +115 -0
  87. package/src/dom-props.ts +297 -46
  88. package/src/element.ts +141 -31
  89. package/src/event-listeners.ts +6 -6
  90. package/src/event-types.ts +10 -0
  91. package/src/events.ts +32 -10
  92. package/src/fiber-commit.ts +16 -1
  93. package/src/fiber-reconciler.ts +39 -6
  94. package/src/fiber-work-loop.ts +4 -3
  95. package/src/fiber.ts +14 -0
  96. package/src/hooks-entry.ts +24 -0
  97. package/src/hooks.ts +482 -479
  98. package/src/host-reconciler.ts +1662 -83
  99. package/src/hydration.ts +1 -1
  100. package/src/index.ts +1 -1
  101. package/src/react-default.ts +1 -1
  102. package/src/reconciler.ts +61 -22
  103. package/src/root.ts +55 -12
  104. package/src/server-render.ts +478 -0
package/src/dom-props.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  ensureDelegatedEventListener,
7
7
  toEventNames,
8
8
  } from "./host-event-binder.js";
9
+ import { HOST_OWN_PROPS_META } from "./element.js";
9
10
  import { reportRecoverable, type RenderOptions } from "./hydration.js";
10
11
  import type { SyntheticEvent } from "./event-types.js";
11
12
  import {
@@ -14,39 +15,67 @@ import {
14
15
  isUnsafeUrlAttribute,
15
16
  isUrlAttribute,
16
17
  } from "./url-safety.js";
18
+ import {
19
+ serializeClientStyleValue,
20
+ styleNameToCssName,
21
+ type HostElement,
22
+ } from "./dom-host-rules.js";
17
23
 
18
24
  export function applyProps(
19
- element: HTMLElement,
25
+ element: HostElement,
20
26
  props: Record<string, unknown>,
21
27
  path: string,
22
28
  options: RenderOptions,
23
29
  ): void {
24
- const previous: AppliedProps = getAppliedProps(element) ?? {
25
- props: {},
26
- listeners: new Map<string, AppliedEventListener>(),
27
- };
28
- const nextAttributeNames = collectAttributeNames(props);
29
30
  const preserveHydrationAttributes = options.preserveHydrationAttributes === true;
31
+ const previous = getAppliedProps(element);
32
+
33
+ if (previous === undefined && !preserveHydrationAttributes) {
34
+ if (applyInitialRowProps(element, props)) {
35
+ setAppliedProps(element, { props });
36
+ return;
37
+ }
38
+
39
+ setAppliedProps(element, {
40
+ props,
41
+ ...applyInitialProps(element, props, path, options),
42
+ });
43
+ return;
44
+ }
45
+
46
+ const previousProps = previous?.props ?? {};
47
+ let listeners = previous?.listeners;
48
+ const previousAttributeNames = collectAttributeNames(previousProps);
49
+ const nextAttributeNames = collectAttributeNames(props);
30
50
 
31
51
  if (!preserveHydrationAttributes) {
32
- for (const attribute of Array.from(element.attributes)) {
33
- if (!nextAttributeNames.has(attribute.name)) {
34
- reportRecoverable(
35
- options,
36
- "attribute",
37
- path,
38
- new Error(`Hydration attribute mismatch: ${attribute.name}.`),
39
- );
40
- element.removeAttribute(attribute.name);
52
+ for (const attributeName of previousAttributeNames) {
53
+ if (!nextAttributeNames.has(attributeName)) {
54
+ if (attributeName === "style") {
55
+ removePreviousStyle(element, previousProps.style, path, options);
56
+ continue;
57
+ }
58
+
59
+ if (element.hasAttribute(attributeName)) {
60
+ reportRecoverable(
61
+ options,
62
+ "attribute",
63
+ path,
64
+ new Error(`Hydration attribute mismatch: ${attributeName}.`),
65
+ );
66
+ element.removeAttribute(attributeName);
67
+ }
41
68
  }
42
69
  }
43
70
  }
44
71
 
45
- for (const [name, appliedListener] of previous.listeners) {
46
- const nextValue = props[name];
72
+ if (listeners !== undefined) {
73
+ for (const [name, appliedListener] of listeners) {
74
+ const nextValue = props[name];
47
75
 
48
- if (nextValue !== appliedListener.handler) {
49
- previous.listeners.delete(name);
76
+ if (nextValue !== appliedListener.handler) {
77
+ listeners.delete(name);
78
+ }
50
79
  }
51
80
  }
52
81
 
@@ -70,12 +99,12 @@ export function applyProps(
70
99
  }
71
100
 
72
101
  if (name === "style") {
73
- applyStyle(element, previous.props[name], value, path, options);
102
+ applyStyle(element, previousProps[name], value, path, options);
74
103
  continue;
75
104
  }
76
105
 
77
106
  if (/^on[A-Z]/.test(name) && typeof value === "function") {
78
- if (previous.listeners.get(name)?.handler === value) {
107
+ if (listeners?.get(name)?.handler === value) {
79
108
  continue;
80
109
  }
81
110
 
@@ -83,12 +112,19 @@ export function applyProps(
83
112
  for (const eventName of toEventNames(name)) {
84
113
  ensureDelegatedEventListener(options.eventRoot ?? element, eventName);
85
114
  }
86
- previous.listeners.set(name, { handler });
115
+ listeners ??= new Map<string, AppliedEventListener>();
116
+ listeners.set(name, { handler });
117
+ continue;
118
+ }
119
+
120
+ const attributeName = toDomAttributeName(name);
121
+
122
+ if (typeof value === "boolean" && isBooleanishStringAttribute(attributeName)) {
123
+ applyAttribute(element, attributeName, value ? "true" : "false", path, options);
87
124
  continue;
88
125
  }
89
126
 
90
127
  if (typeof value === "boolean") {
91
- const attributeName = toDomAttributeName(name);
92
128
  if (element.hasAttribute(attributeName) !== value) {
93
129
  if (!preserveHydrationAttributes) {
94
130
  reportRecoverable(
@@ -104,11 +140,15 @@ export function applyProps(
104
140
  continue;
105
141
  }
106
142
 
107
- (element as unknown as Record<string, unknown>)[name] = value;
143
+ if ((element as unknown as Record<string, unknown>)[name] !== value) {
144
+ (element as unknown as Record<string, unknown>)[name] = value;
145
+ }
108
146
 
109
147
  if (value) {
110
- element.setAttribute(attributeName, "");
111
- } else {
148
+ if (!element.hasAttribute(attributeName)) {
149
+ element.setAttribute(attributeName, "");
150
+ }
151
+ } else if (element.hasAttribute(attributeName)) {
112
152
  element.removeAttribute(attributeName);
113
153
  }
114
154
  continue;
@@ -117,11 +157,121 @@ export function applyProps(
117
157
  applyAttribute(element, toDomAttributeName(name), value, path, options);
118
158
  }
119
159
 
120
- setAppliedProps(element, { props: { ...props }, listeners: previous.listeners });
160
+ setAppliedProps(element, {
161
+ props,
162
+ ...(listeners === undefined ? {} : { listeners }),
163
+ });
164
+ }
165
+
166
+ function applyInitialProps(
167
+ element: HostElement,
168
+ props: Record<string, unknown>,
169
+ path: string,
170
+ options: RenderOptions,
171
+ ): Pick<AppliedProps, "listeners"> | {} {
172
+ let listeners: Map<string, AppliedEventListener> | undefined;
173
+
174
+ for (const name in props) {
175
+ if (!Object.prototype.hasOwnProperty.call(props, name)) {
176
+ continue;
177
+ }
178
+
179
+ const value = props[name];
180
+
181
+ if (name === "children" || name === "ref" || name === "key") {
182
+ continue;
183
+ }
184
+
185
+ if (applyFormValueProp(element, name, value, path, options)) {
186
+ continue;
187
+ }
188
+
189
+ if (value === null || value === undefined) {
190
+ continue;
191
+ }
192
+
193
+ if (name === "className") {
194
+ applyAttribute(element, "class", value, path, options);
195
+ continue;
196
+ }
197
+
198
+ if (name === "htmlFor") {
199
+ applyAttribute(element, "for", value, path, options);
200
+ continue;
201
+ }
202
+
203
+ if (name === "style") {
204
+ applyStyle(element, undefined, value, path, options);
205
+ continue;
206
+ }
207
+
208
+ if (/^on[A-Z]/.test(name) && typeof value === "function") {
209
+ const handler = value as (event: SyntheticEvent) => void;
210
+ for (const eventName of toEventNames(name)) {
211
+ ensureDelegatedEventListener(options.eventRoot ?? element, eventName);
212
+ }
213
+ listeners ??= new Map<string, AppliedEventListener>();
214
+ listeners.set(name, { handler });
215
+ continue;
216
+ }
217
+
218
+ const attributeName = toDomAttributeName(name);
219
+
220
+ if (typeof value === "boolean" && isBooleanishStringAttribute(attributeName)) {
221
+ applyAttribute(element, attributeName, value ? "true" : "false", path, options);
222
+ continue;
223
+ }
224
+
225
+ if (value === false) {
226
+ continue;
227
+ }
228
+
229
+ if (typeof value === "boolean") {
230
+ if ((element as unknown as Record<string, unknown>)[name] !== value) {
231
+ (element as unknown as Record<string, unknown>)[name] = value;
232
+ }
233
+
234
+ if (value) {
235
+ if (!element.hasAttribute(attributeName)) {
236
+ element.setAttribute(attributeName, "");
237
+ }
238
+ } else if (element.hasAttribute(attributeName)) {
239
+ element.removeAttribute(attributeName);
240
+ }
241
+ continue;
242
+ }
243
+
244
+ applyAttribute(element, attributeName, value, path, options);
245
+ }
246
+
247
+ return listeners === undefined ? {} : { listeners };
248
+ }
249
+
250
+ function applyInitialRowProps(
251
+ element: HostElement,
252
+ props: Record<string, unknown>,
253
+ ): boolean {
254
+ const meta = (props as { [HOST_OWN_PROPS_META]?: number })[HOST_OWN_PROPS_META];
255
+
256
+ if (meta === undefined) {
257
+ return false;
258
+ }
259
+
260
+ element.setAttribute("data-key", String(props["data-key"]));
261
+
262
+ if ((meta & 1) !== 0) {
263
+ element.setAttribute("class", "selected");
264
+ }
265
+
266
+ if ((meta & 2) !== 0) {
267
+ element.setAttribute("data-selected", "true");
268
+ }
269
+
270
+ return true;
121
271
  }
122
272
 
123
273
  export function applyPostChildFormProps(
124
- element: HTMLElement,
274
+ element: Element,
125
275
  props: Record<string, unknown>,
126
276
  ): void {
127
277
  const value = props.value ?? props.defaultValue;
@@ -151,7 +301,7 @@ export function applyPostChildFormProps(
151
301
  }
152
302
 
153
303
  function applyAttribute(
154
- element: HTMLElement,
304
+ element: Element,
155
305
  name: string,
156
306
  value: unknown,
157
307
  path: string,
@@ -218,7 +368,9 @@ function applyAttribute(
218
368
  return;
219
369
  }
220
370
 
221
- if (element.getAttribute(name) !== stringValue && !preserveHydrationAttributes) {
371
+ const currentValue = element.getAttribute(name);
372
+
373
+ if (currentValue !== stringValue && !preserveHydrationAttributes) {
222
374
  reportRecoverable(
223
375
  options,
224
376
  "attribute",
@@ -231,11 +383,15 @@ function applyAttribute(
231
383
  return;
232
384
  }
233
385
 
386
+ if (currentValue === stringValue) {
387
+ return;
388
+ }
389
+
234
390
  element.setAttribute(name, stringValue);
235
391
  }
236
392
 
237
393
  function applyFormValueProp(
238
- element: HTMLElement,
394
+ element: Element,
239
395
  name: string,
240
396
  value: unknown,
241
397
  path: string,
@@ -309,7 +465,7 @@ function applyFormValueProp(
309
465
  }
310
466
 
311
467
  function applyStyle(
312
- element: HTMLElement,
468
+ element: HostElement,
313
469
  previousStyle: unknown,
314
470
  nextStyle: unknown,
315
471
  path: string,
@@ -319,11 +475,41 @@ function applyStyle(
319
475
  return;
320
476
  }
321
477
 
478
+ removePreviousStyle(element, previousStyle, path, options);
479
+
480
+ if (isStyleObject(nextStyle)) {
481
+ for (const [name, value] of Object.entries(nextStyle)) {
482
+ if (value === null || value === undefined || value === false) {
483
+ continue;
484
+ }
485
+ element.style.setProperty(styleNameToCssName(name), serializeClientStyleValue(name, value));
486
+ }
487
+ return;
488
+ }
489
+
490
+ if (nextStyle !== undefined && nextStyle !== null && nextStyle !== false) {
491
+ element.removeAttribute("style");
492
+ }
493
+ }
494
+
495
+ function removePreviousStyle(
496
+ element: HostElement,
497
+ previousStyle: unknown,
498
+ path: string,
499
+ options: RenderOptions,
500
+ ): void {
501
+ if (options.preserveHydrationAttributes === true) {
502
+ return;
503
+ }
504
+
322
505
  if (isStyleObject(previousStyle)) {
323
506
  for (const name of Object.keys(previousStyle)) {
324
- element.style.removeProperty(name);
507
+ element.style.removeProperty(styleNameToCssName(name));
325
508
  }
326
- } else if (element.hasAttribute("style")) {
509
+ return;
510
+ }
511
+
512
+ if (previousStyle !== undefined && element.hasAttribute("style")) {
327
513
  reportRecoverable(
328
514
  options,
329
515
  "attribute",
@@ -332,13 +518,6 @@ function applyStyle(
332
518
  );
333
519
  element.removeAttribute("style");
334
520
  }
335
-
336
- if (isStyleObject(nextStyle)) {
337
- Object.assign(element.style, nextStyle);
338
- return;
339
- }
340
-
341
- element.removeAttribute("style");
342
521
  }
343
522
 
344
523
  function collectAttributeNames(props: Record<string, unknown>): Set<string> {
@@ -350,13 +529,18 @@ function collectAttributeNames(props: Record<string, unknown>): Set<string> {
350
529
  name === "ref" ||
351
530
  name === "key" ||
352
531
  /^on[A-Z]/.test(name) ||
353
- value === false ||
354
532
  value === null ||
355
533
  value === undefined
356
534
  ) {
357
535
  continue;
358
536
  }
359
537
 
538
+ const attributeName = toDomAttributeName(name);
539
+
540
+ if (value === false && !isBooleanishStringAttribute(attributeName)) {
541
+ continue;
542
+ }
543
+
360
544
  if (name === "defaultValue") {
361
545
  names.add("value");
362
546
  continue;
@@ -367,17 +551,28 @@ function collectAttributeNames(props: Record<string, unknown>): Set<string> {
367
551
  continue;
368
552
  }
369
553
 
370
- names.add(toDomAttributeName(name));
554
+ names.add(attributeName);
371
555
  }
372
556
 
373
557
  return names;
374
558
  }
375
559
 
560
+ function isBooleanishStringAttribute(name: string): boolean {
561
+ const attributeName = toDomAttributeName(name).toLowerCase();
562
+ return attributeName.startsWith("aria-") || BOOLEANISH_STRING_ATTRIBUTES.has(attributeName);
563
+ }
564
+
565
+ const BOOLEANISH_STRING_ATTRIBUTES = new Set<string>([
566
+ "contenteditable",
567
+ "draggable",
568
+ "spellcheck",
569
+ ]);
570
+
376
571
  function toDomAttributeName(name: string): string {
377
- return HTML_ATTRIBUTE_ALIASES[name] ?? name;
572
+ return DOM_ATTRIBUTE_ALIASES[name] ?? name;
378
573
  }
379
574
 
380
- const HTML_ATTRIBUTE_ALIASES: Record<string, string> = {
575
+ const DOM_ATTRIBUTE_ALIASES: Record<string, string> = {
381
576
  acceptCharset: "accept-charset",
382
577
  autoFocus: "autofocus",
383
578
  autoPlay: "autoplay",
@@ -402,8 +597,64 @@ const HTML_ATTRIBUTE_ALIASES: Record<string, string> = {
402
597
  srcSet: "srcset",
403
598
  tabIndex: "tabindex",
404
599
  useMap: "usemap",
600
+ alignmentBaseline: "alignment-baseline",
601
+ baselineShift: "baseline-shift",
602
+ clipPath: "clip-path",
603
+ clipRule: "clip-rule",
604
+ colorInterpolation: "color-interpolation",
605
+ colorInterpolationFilters: "color-interpolation-filters",
606
+ colorProfile: "color-profile",
607
+ colorRendering: "color-rendering",
608
+ dominantBaseline: "dominant-baseline",
609
+ enableBackground: "enable-background",
610
+ fillOpacity: "fill-opacity",
611
+ fillRule: "fill-rule",
612
+ floodColor: "flood-color",
613
+ floodOpacity: "flood-opacity",
614
+ fontFamily: "font-family",
615
+ fontSize: "font-size",
616
+ fontSizeAdjust: "font-size-adjust",
617
+ fontStretch: "font-stretch",
618
+ fontStyle: "font-style",
619
+ fontVariant: "font-variant",
620
+ fontWeight: "font-weight",
621
+ glyphOrientationHorizontal: "glyph-orientation-horizontal",
622
+ glyphOrientationVertical: "glyph-orientation-vertical",
623
+ imageRendering: "image-rendering",
624
+ letterSpacing: "letter-spacing",
625
+ lightingColor: "lighting-color",
626
+ markerEnd: "marker-end",
627
+ markerMid: "marker-mid",
628
+ markerStart: "marker-start",
629
+ overlinePosition: "overline-position",
630
+ overlineThickness: "overline-thickness",
631
+ paintOrder: "paint-order",
632
+ pointerEvents: "pointer-events",
633
+ shapeRendering: "shape-rendering",
634
+ stopColor: "stop-color",
635
+ stopOpacity: "stop-opacity",
636
+ strikethroughPosition: "strikethrough-position",
637
+ strikethroughThickness: "strikethrough-thickness",
638
+ strokeDasharray: "stroke-dasharray",
639
+ strokeDashoffset: "stroke-dashoffset",
640
+ strokeLinecap: "stroke-linecap",
641
+ strokeLinejoin: "stroke-linejoin",
642
+ strokeMiterlimit: "stroke-miterlimit",
643
+ strokeOpacity: "stroke-opacity",
644
+ strokeWidth: "stroke-width",
645
+ textAnchor: "text-anchor",
646
+ textDecoration: "text-decoration",
647
+ textRendering: "text-rendering",
648
+ transformOrigin: "transform-origin",
649
+ underlinePosition: "underline-position",
650
+ underlineThickness: "underline-thickness",
651
+ unicodeBidi: "unicode-bidi",
652
+ vectorEffect: "vector-effect",
653
+ wordSpacing: "word-spacing",
654
+ writingMode: "writing-mode",
655
+ xHeight: "x-height",
405
656
  };
406
657
 
407
- function isStyleObject(value: unknown): value is Partial<CSSStyleDeclaration> {
658
+ function isStyleObject(value: unknown): value is Record<string, unknown> {
408
659
  return typeof value === "object" && value !== null;
409
660
  }