what-core 0.6.1 → 0.6.3

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 (50) hide show
  1. package/README.md +2 -0
  2. package/compiler.d.ts +30 -0
  3. package/devtools.d.ts +2 -0
  4. package/dist/compiler.js +1787 -0
  5. package/dist/compiler.js.map +7 -0
  6. package/dist/compiler.min.js +2 -0
  7. package/dist/compiler.min.js.map +7 -0
  8. package/dist/devtools.js +10 -0
  9. package/dist/devtools.js.map +7 -0
  10. package/dist/devtools.min.js +2 -0
  11. package/dist/devtools.min.js.map +7 -0
  12. package/dist/index.js +331 -382
  13. package/dist/index.js.map +4 -4
  14. package/dist/index.min.js +62 -62
  15. package/dist/index.min.js.map +4 -4
  16. package/dist/render.js +263 -21
  17. package/dist/render.js.map +4 -4
  18. package/dist/render.min.js +58 -1
  19. package/dist/render.min.js.map +4 -4
  20. package/dist/testing.js +3 -0
  21. package/dist/testing.js.map +2 -2
  22. package/dist/testing.min.js +1 -1
  23. package/dist/testing.min.js.map +2 -2
  24. package/index.d.ts +176 -1
  25. package/jsx-runtime.d.ts +622 -0
  26. package/package.json +20 -2
  27. package/src/agent-context.js +1 -1
  28. package/src/compiler.js +18 -0
  29. package/src/components.js +73 -27
  30. package/src/devtools.js +4 -0
  31. package/src/dom.js +7 -0
  32. package/src/guardrails.js +3 -4
  33. package/src/hooks.js +0 -11
  34. package/src/index.js +5 -9
  35. package/src/render.js +94 -24
  36. package/dist/a11y.js +0 -440
  37. package/dist/animation.js +0 -548
  38. package/dist/components.js +0 -229
  39. package/dist/data.js +0 -638
  40. package/dist/dom.js +0 -439
  41. package/dist/form.js +0 -509
  42. package/dist/h.js +0 -152
  43. package/dist/head.js +0 -51
  44. package/dist/helpers.js +0 -140
  45. package/dist/hooks.js +0 -210
  46. package/dist/reactive.js +0 -432
  47. package/dist/scheduler.js +0 -246
  48. package/dist/skeleton.js +0 -363
  49. package/dist/store.js +0 -83
  50. package/dist/what.js +0 -117
package/src/components.js CHANGED
@@ -154,42 +154,73 @@ export function reportError(error, startCtx) {
154
154
 
155
155
  // --- Show ---
156
156
  // Conditional rendering component. Cleaner than ternaries.
157
+ // Reactively shows/hides children based on the `when` condition.
157
158
 
158
159
  export function Show({ when, fallback = null, children }) {
159
- // when can be a signal or a value
160
- const condition = typeof when === 'function' ? when() : when;
161
- return condition ? children : fallback;
160
+ // If `when` is a signal or function, return a reactive function
161
+ // so the DOM runtime tracks changes via its effect wrapper
162
+ if (typeof when === 'function') {
163
+ return () => when() ? children : fallback;
164
+ }
165
+ // Static value — just return directly
166
+ return when ? children : fallback;
162
167
  }
163
168
 
164
169
  // --- For ---
165
- // Efficient list rendering with keyed reconciliation.
166
-
167
- export function For({ each, fallback = null, children }) {
168
- const list = typeof each === 'function' ? each() : each;
169
- if (!list || list.length === 0) return fallback;
170
-
170
+ // Reactive list rendering with keyed reconciliation.
171
+ // Takes a signal (or function returning an array) as `each` and reactively
172
+ // adds/removes/moves DOM nodes when the array changes.
173
+ // Uses mapArray from render.js for efficient keyed reconciliation with LIS.
174
+ //
175
+ // Usage: <For each={items}>{(item, index) => <div>{item}</div>}</For>
176
+ // - `each`: signal or function returning an array
177
+ // - `children`: render function (item, index) => vnode
178
+ // - `fallback`: shown when array is empty
179
+ // - `key`: optional key function (item) => key for keyed reconciliation
180
+
181
+ export function For({ each, fallback = null, children, key: keyFn }) {
171
182
  // children should be a function (item, index) => vnode
172
183
  const renderFn = Array.isArray(children) ? children[0] : children;
173
184
  if (typeof renderFn !== 'function') {
174
- console.warn('[what] For: children must be a render function, e.g. <For each={items}>{(item) => ...}</For>');
185
+ if (__DEV__) {
186
+ console.warn('[what] For: children must be a render function, e.g. <For each={items}>{(item) => ...}</For>');
187
+ }
175
188
  return fallback;
176
189
  }
177
190
 
178
- return list.map((item, index) => {
191
+ // Normalize `each` to a function that returns the current array
192
+ const source = typeof each === 'function' ? each : () => each;
193
+
194
+ // Build the map function that wraps renderFn with auto-key detection
195
+ const mapFn = (item, index) => {
179
196
  const vnode = renderFn(item, index);
180
197
  // Auto-detect keys for efficient keyed reconciliation
181
198
  if (vnode && typeof vnode === 'object' && vnode.key == null) {
182
- if (item != null && typeof item === 'object') {
183
- // Use item.id or item.key if available
184
- if (item.id != null) vnode.key = item.id;
185
- else if (item.key != null) vnode.key = item.key;
186
- } else if (typeof item === 'string' || typeof item === 'number') {
187
- // Primitive items can be their own key
188
- vnode.key = item;
199
+ const rawItem = typeof item === 'function' && item._signal ? item() : item;
200
+ if (rawItem != null && typeof rawItem === 'object') {
201
+ if (rawItem.id != null) vnode.key = rawItem.id;
202
+ else if (rawItem.key != null) vnode.key = rawItem.key;
203
+ } else if (typeof rawItem === 'string' || typeof rawItem === 'number') {
204
+ vnode.key = rawItem;
189
205
  }
190
206
  }
191
207
  return vnode;
192
- });
208
+ };
209
+
210
+ // Return a reactive function. The DOM runtime (createDOM in dom.js)
211
+ // wraps functions in effects, so this will re-evaluate whenever the
212
+ // source signal changes. For simple cases (no mapArray integration),
213
+ // this provides correct reactivity by re-rendering the list on change.
214
+ //
215
+ // The effect wrapper in createDOM (lines 140-190 in dom.js) handles:
216
+ // - Creating DOM nodes for new vnodes
217
+ // - Removing old DOM nodes
218
+ // - Inserting between comment markers
219
+ return () => {
220
+ const list = source();
221
+ if (!list || list.length === 0) return fallback;
222
+ return list.map((item, i) => mapFn(item, i));
223
+ };
193
224
  }
194
225
 
195
226
  // --- Switch / Match ---
@@ -198,12 +229,29 @@ export function For({ each, fallback = null, children }) {
198
229
  export function Switch({ fallback = null, children }) {
199
230
  const kids = Array.isArray(children) ? children : [children];
200
231
 
232
+ const hasReactiveCondition = kids.some(
233
+ child => child && child.tag === Match && typeof child.props.when === 'function'
234
+ );
235
+
236
+ if (hasReactiveCondition) {
237
+ return () => {
238
+ for (const child of kids) {
239
+ if (child && child.tag === Match) {
240
+ const condition = typeof child.props.when === 'function'
241
+ ? child.props.when()
242
+ : child.props.when;
243
+ if (condition) {
244
+ return child.children;
245
+ }
246
+ }
247
+ }
248
+ return fallback;
249
+ };
250
+ }
251
+
201
252
  for (const child of kids) {
202
253
  if (child && child.tag === Match) {
203
- const condition = typeof child.props.when === 'function'
204
- ? child.props.when()
205
- : child.props.when;
206
- if (condition) {
254
+ if (child.props.when) {
207
255
  return child.children;
208
256
  }
209
257
  }
@@ -223,8 +271,6 @@ export function Match({ when, children }) {
223
271
  // The babel plugin compiles <Counter client:idle /> into this.
224
272
 
225
273
  export function Island({ component: Component, mode, mediaQuery, ...props }) {
226
- const placeholder = h('div', { 'data-island': Component.name || 'Island', 'data-hydrate': mode });
227
-
228
274
  // We need to return a vnode that the reconciler can handle.
229
275
  // The actual hydration scheduling happens after mount via an effect.
230
276
  const wrapper = signal(null);
@@ -304,8 +350,8 @@ export function Island({ component: Component, mode, mediaQuery, ...props }) {
304
350
  if (el) scheduleHydration(el);
305
351
  };
306
352
 
307
- // Return: show placeholder until hydrated, then show the real component
353
+ // Return: reactive function so DOM runtime tracks hydration state changes
308
354
  return h('div', { 'data-island': Component.name || 'Island', 'data-hydrate': mode, ref: refCallback },
309
- hydrated() ? wrapper() : null
355
+ () => hydrated() ? wrapper() : null
310
356
  );
311
357
  }
@@ -0,0 +1,4 @@
1
+ // What Framework - DevTools bridge
2
+ // Internal integration surface consumed by what-devtools.
3
+
4
+ export { __setDevToolsHooks } from './reactive.js';
package/src/dom.js CHANGED
@@ -204,6 +204,13 @@ export function createDOM(vnode, parent, isSvg) {
204
204
  return createComponent(vnode, parent, isSvg);
205
205
  }
206
206
 
207
+ // Special boundary vnodes (returned by ErrorBoundary, Suspense, Portal components)
208
+ // These have string tags like '__errorBoundary' and must be routed to createComponent
209
+ // which contains the special-case handlers for them.
210
+ if (isVNode(vnode) && (vnode.tag === '__errorBoundary' || vnode.tag === '__suspense' || vnode.tag === '__portal')) {
211
+ return createComponent(vnode, parent, isSvg);
212
+ }
213
+
207
214
  // VNode with string tag — create element
208
215
  if (isVNode(vnode)) {
209
216
  return createElementFromVNode(vnode, parent, isSvg);
package/src/guardrails.js CHANGED
@@ -125,11 +125,10 @@ function capitalize(str) {
125
125
  const VALID_EXPORTS = new Set([
126
126
  // Reactive primitives
127
127
  'signal', 'computed', 'effect', 'signalMemo', 'batch', 'untrack', 'flushSync',
128
- 'createRoot', 'getOwner', 'runWithOwner', 'onRootCleanup', '__setDevToolsHooks',
128
+ 'createRoot', 'getOwner', 'runWithOwner', 'onRootCleanup',
129
129
  // Rendering
130
- 'template', '_template', 'svgTemplate', 'insert', 'mapArray', 'spread',
130
+ 'template', 'svgTemplate', 'insert', 'mapArray', 'spread',
131
131
  'setProp', 'delegateEvents', 'on', 'classList', 'hydrate', 'isHydrating',
132
- '_$createComponent',
133
132
  // JSX
134
133
  'h', 'Fragment', 'html',
135
134
  // DOM
@@ -163,7 +162,7 @@ const VALID_EXPORTS = new Set([
163
162
  'IslandSkeleton', 'useSkeleton', 'Placeholder', 'LoadingDots', 'Spinner',
164
163
  // Data fetching
165
164
  'useFetch', 'useSWR', 'useQuery', 'useInfiniteQuery', 'invalidateQueries',
166
- 'prefetchQuery', 'setQueryData', 'getQueryData', 'clearCache', '__getCacheSnapshot',
165
+ 'prefetchQuery', 'setQueryData', 'getQueryData', 'clearCache',
167
166
  // Form
168
167
  'useForm', 'useField', 'rules', 'simpleResolver', 'zodResolver', 'yupResolver',
169
168
  'Input', 'Textarea', 'Select', 'Checkbox', 'Radio', 'ErrorMessage',
package/src/hooks.js CHANGED
@@ -371,14 +371,3 @@ export function createResource(fetcher, options = {}) {
371
371
  return [data, { loading, error, refetch, mutate }];
372
372
  }
373
373
 
374
- // --- Dep comparison (kept for potential external use) ---
375
-
376
- function depsChanged(oldDeps, newDeps) {
377
- if (oldDeps === undefined) return true;
378
- if (!oldDeps || !newDeps) return true;
379
- if (oldDeps.length !== newDeps.length) return true;
380
- for (let i = 0; i < oldDeps.length; i++) {
381
- if (!Object.is(oldDeps[i], newDeps[i])) return true;
382
- }
383
- return false;
384
- }
package/src/index.js CHANGED
@@ -2,10 +2,10 @@
2
2
  // The closest framework to vanilla JS.
3
3
 
4
4
  // Reactive primitives
5
- export { signal, computed, effect, memo as signalMemo, batch, untrack, flushSync, createRoot, getOwner, runWithOwner, onCleanup as onRootCleanup, __setDevToolsHooks } from './reactive.js';
5
+ export { signal, computed, effect, memo as signalMemo, batch, untrack, flushSync, createRoot, getOwner, runWithOwner, onCleanup as onRootCleanup } from './reactive.js';
6
6
 
7
7
  // Fine-grained rendering primitives
8
- export { template, _template, _$template, svgTemplate, insert, mapArray, spread, setProp, delegateEvents, on, classList, hydrate, isHydrating, _$createComponent } from './render.js';
8
+ export { template, svgTemplate, insert, mapArray, spread, setProp, delegateEvents, on, classList, hydrate, isHydrating, getHydrationMismatchCount } from './render.js';
9
9
 
10
10
  // JSX factory — Fragment and html tagged template are public APIs.
11
11
  // h is exported for internal package use only (jsx-runtime, server, router, react-compat).
@@ -15,15 +15,12 @@ export { h, Fragment, html } from './h.js';
15
15
  // DOM mounting & rendering (fine-grained, no VDOM reconciler)
16
16
  export { mount } from './dom.js';
17
17
 
18
- // Hooks (React-compatible API)
18
+ // Hooks — Solid-style API (signal-first)
19
+ // React-style hooks (useState, useEffect, useMemo, useCallback, useRef)
20
+ // are available via 'what-framework/react-compat' only.
19
21
  export {
20
- useState,
21
22
  useSignal,
22
23
  useComputed,
23
- useEffect,
24
- useMemo,
25
- useCallback,
26
- useRef,
27
24
  useContext,
28
25
  useReducer,
29
26
  createContext,
@@ -134,7 +131,6 @@ export {
134
131
  setQueryData,
135
132
  getQueryData,
136
133
  clearCache,
137
- __getCacheSnapshot,
138
134
  } from './data.js';
139
135
 
140
136
  // Form utilities
package/src/render.js CHANGED
@@ -2,8 +2,9 @@
2
2
  // Solid-style rendering: components run once, signals create individual DOM effects.
3
3
  // No VDOM diffing — direct DOM manipulation with surgical signal-driven updates.
4
4
 
5
- import { effect, untrack, createRoot, signal, __DEV__ } from './reactive.js';
5
+ import { effect, untrack, createRoot, signal, __DEV__, __devtools } from './reactive.js';
6
6
  import { createDOM, disposeTree, getCurrentComponent, getComponentStack } from './dom.js';
7
+ import { createWhatError, collectError } from './errors.js';
7
8
 
8
9
  export { effect, untrack };
9
10
 
@@ -865,6 +866,9 @@ export function setProp(el, key, value) {
865
866
  return;
866
867
  }
867
868
 
869
+ // Key prop — no-op, WhatFW has no virtual DOM (defense in depth, issue #6)
870
+ if (key === 'key') return;
871
+
868
872
  // Sanitize URL attributes — reject dangerous protocols
869
873
  if (URL_ATTRS.has(key) || URL_ATTRS.has(key.toLowerCase())) {
870
874
  if (!isSafeUrl(value)) {
@@ -977,9 +981,16 @@ export function isHydrating() {
977
981
  export function hydrate(vnode, container) {
978
982
  _isHydrating = true;
979
983
  _hydrationCursor = { parent: container, index: 0 };
984
+ _hydrationMismatchCount = 0;
980
985
 
981
986
  try {
982
987
  const result = hydrateNode(vnode, container);
988
+ if (__DEV__ && _hydrationMismatchCount > 0) {
989
+ console.warn(
990
+ `[what] Hydration completed with ${_hydrationMismatchCount} mismatch${_hydrationMismatchCount === 1 ? '' : 'es'}. ` +
991
+ 'See previous warnings for details. This usually means server and client render different initial HTML.'
992
+ );
993
+ }
983
994
  return result;
984
995
  } finally {
985
996
  _isHydrating = false;
@@ -1009,11 +1020,46 @@ function claimNode(parent) {
1009
1020
  return null;
1010
1021
  }
1011
1022
 
1012
- function isDevMode() {
1013
- return typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production';
1023
+ // Track hydration mismatch count for diagnostics
1024
+ let _hydrationMismatchCount = 0;
1025
+
1026
+ /** Returns the number of hydration mismatches encountered during the last hydrate() call. */
1027
+ export function getHydrationMismatchCount() {
1028
+ return _hydrationMismatchCount;
1029
+ }
1030
+
1031
+ function reportHydrationMismatch(expected, actual, componentName) {
1032
+ _hydrationMismatchCount++;
1033
+ if (__DEV__) {
1034
+ const context = {
1035
+ component: componentName || 'unknown',
1036
+ serverHTML: actual,
1037
+ clientHTML: expected,
1038
+ };
1039
+ const whatError = createWhatError('HYDRATION_MISMATCH', context);
1040
+ collectError(whatError);
1041
+ // Notify devtools so MCP agents see hydration mismatches as live events
1042
+ if (__devtools?.onHydrationMismatch) {
1043
+ __devtools.onHydrationMismatch({
1044
+ component: componentName || 'unknown',
1045
+ expected,
1046
+ actual,
1047
+ mismatchCount: _hydrationMismatchCount,
1048
+ });
1049
+ }
1050
+ // Also notify the error handler so it appears in devtools error log
1051
+ if (__devtools?.onError) {
1052
+ __devtools.onError(whatError, { type: 'hydration', component: componentName });
1053
+ }
1054
+ console.warn(
1055
+ `[what] Hydration mismatch: expected ${expected}, got ${actual}` +
1056
+ (componentName ? ` (in ${componentName})` : '') +
1057
+ '. Falling back to client render.'
1058
+ );
1059
+ }
1014
1060
  }
1015
1061
 
1016
- function hydrateNode(vnode, parent) {
1062
+ function hydrateNode(vnode, parent, _componentName) {
1017
1063
  if (vnode == null || typeof vnode === 'boolean') {
1018
1064
  return null;
1019
1065
  }
@@ -1025,9 +1071,9 @@ function hydrateNode(vnode, parent) {
1025
1071
 
1026
1072
  if (existing && existing.nodeType === 3) {
1027
1073
  // Reuse text node — check for mismatch in dev
1028
- if (isDevMode() && existing.textContent !== text) {
1029
- console.warn(
1030
- `[what] Hydration mismatch: expected text "${text}", got "${existing.textContent}"`
1074
+ if (__DEV__ && existing.textContent !== text) {
1075
+ reportHydrationMismatch(
1076
+ `text "${text}"`, `text "${existing.textContent}"`, _componentName
1031
1077
  );
1032
1078
  existing.textContent = text;
1033
1079
  }
@@ -1035,11 +1081,9 @@ function hydrateNode(vnode, parent) {
1035
1081
  }
1036
1082
 
1037
1083
  // Mismatch: expected text node, got element or nothing
1038
- if (isDevMode()) {
1039
- console.warn(
1040
- `[what] Hydration mismatch: expected text node "${text}", got ${existing ? existing.nodeName : 'nothing'}. Falling back to client render.`
1041
- );
1042
- }
1084
+ reportHydrationMismatch(
1085
+ `text node "${text}"`, existing ? existing.nodeName : 'nothing', _componentName
1086
+ );
1043
1087
  const textNode = document.createTextNode(text);
1044
1088
  if (existing) {
1045
1089
  parent.replaceChild(textNode, existing);
@@ -1053,7 +1097,7 @@ function hydrateNode(vnode, parent) {
1053
1097
  if (typeof vnode === 'function') {
1054
1098
  // Unwrap to get the initial value for hydration
1055
1099
  const initialValue = vnode();
1056
- let current = hydrateNode(initialValue, parent);
1100
+ let current = hydrateNode(initialValue, parent, _componentName);
1057
1101
 
1058
1102
  // Set up reactive effect for future updates (normal rendering path)
1059
1103
  effect(() => {
@@ -1070,7 +1114,7 @@ function hydrateNode(vnode, parent) {
1070
1114
  if (Array.isArray(vnode)) {
1071
1115
  const nodes = [];
1072
1116
  for (const child of vnode) {
1073
- const node = hydrateNode(child, parent);
1117
+ const node = hydrateNode(child, parent, _componentName);
1074
1118
  if (node) nodes.push(node);
1075
1119
  }
1076
1120
  return nodes.length === 1 ? nodes[0] : nodes;
@@ -1082,6 +1126,7 @@ function hydrateNode(vnode, parent) {
1082
1126
  if (typeof vnode.tag === 'function') {
1083
1127
  const componentStack = getComponentStack();
1084
1128
  const Component = vnode.tag;
1129
+ const compName = Component.displayName || Component.name || 'Anonymous';
1085
1130
  const props = vnode.props || {};
1086
1131
  const children = vnode.children || [];
1087
1132
 
@@ -1108,7 +1153,9 @@ function hydrateNode(vnode, parent) {
1108
1153
  result = Component({ ...props, children: propsChildren });
1109
1154
  } catch (error) {
1110
1155
  componentStack.pop();
1111
- console.error('[what] Error in component during hydration:', Component.name || 'Anonymous', error);
1156
+ if (__DEV__) {
1157
+ console.error('[what] Error in component during hydration:', compName, error);
1158
+ }
1112
1159
  return null;
1113
1160
  }
1114
1161
 
@@ -1125,7 +1172,7 @@ function hydrateNode(vnode, parent) {
1125
1172
  });
1126
1173
  }
1127
1174
 
1128
- return hydrateNode(result, parent);
1175
+ return hydrateNode(result, parent, compName);
1129
1176
  }
1130
1177
 
1131
1178
  // Element — claim existing DOM element
@@ -1136,6 +1183,16 @@ function hydrateNode(vnode, parent) {
1136
1183
  // Match! Reuse this element. Apply props/bindings.
1137
1184
  hydrateElementProps(existing, vnode.props || {});
1138
1185
 
1186
+ // Handle ref callback — this was previously missing during hydration
1187
+ if (vnode.props?.ref) {
1188
+ const ref = vnode.props.ref;
1189
+ if (typeof ref === 'function') {
1190
+ ref(existing);
1191
+ } else if (typeof ref === 'object' && ref !== null) {
1192
+ ref.current = existing;
1193
+ }
1194
+ }
1195
+
1139
1196
  // Hydrate children
1140
1197
  const savedCursor = _hydrationCursor;
1141
1198
  _hydrationCursor = { parent: existing, index: 0 };
@@ -1143,7 +1200,7 @@ function hydrateNode(vnode, parent) {
1143
1200
  const rawInner = vnode.props?.dangerouslySetInnerHTML?.__html;
1144
1201
  if (rawInner == null) {
1145
1202
  for (const child of vnode.children) {
1146
- hydrateNode(child, existing);
1203
+ hydrateNode(child, existing, _componentName);
1147
1204
  }
1148
1205
  }
1149
1206
 
@@ -1152,11 +1209,9 @@ function hydrateNode(vnode, parent) {
1152
1209
  }
1153
1210
 
1154
1211
  // Mismatch — fall back to client render for this subtree
1155
- if (isDevMode()) {
1156
- console.warn(
1157
- `[what] Hydration mismatch: expected <${vnode.tag}>, got ${existing ? existing.nodeName : 'nothing'}. Falling back to client render.`
1158
- );
1159
- }
1212
+ reportHydrationMismatch(
1213
+ `<${vnode.tag}>`, existing ? existing.nodeName : 'nothing', _componentName
1214
+ );
1160
1215
 
1161
1216
  // Create the element from scratch
1162
1217
  const newEl = document.createElement(vnode.tag);
@@ -1227,8 +1282,23 @@ function hydrateElementProps(el, props) {
1227
1282
  continue;
1228
1283
  }
1229
1284
 
1230
- // Static props — skip attributes already set from SSR
1231
- // Only attach non-serializable props or ones that may differ
1285
+ // Static props — verify attributes match in dev mode
1232
1286
  if (key === 'data-hk') continue;
1287
+
1288
+ // In dev mode, check that the server-rendered attribute matches the client value
1289
+ // to catch hydration mismatches early (e.g., class="foo" vs class="bar")
1290
+ if (__DEV__ && typeof value === 'string') {
1291
+ const attrName = key === 'className' ? 'class' : key === 'htmlFor' ? 'for' : key;
1292
+ const serverValue = el.getAttribute(attrName);
1293
+ if (serverValue !== null && serverValue !== value) {
1294
+ reportHydrationMismatch(
1295
+ `${attrName}="${value}"`,
1296
+ `${attrName}="${serverValue}"`,
1297
+ el.tagName.toLowerCase()
1298
+ );
1299
+ // Apply client value to fix the mismatch
1300
+ setProp(el, key, value);
1301
+ }
1302
+ }
1233
1303
  }
1234
1304
  }