Repository: preactjs/preact-render-to-string
Pull request #393 (2024-09-14 10:24:05)

fix: `spellcheck` prop
Supports `spellcheck={false}` & removes support for `spellCheck` as we don't support it in Preact (see: https://github.com/preactjs/preact/issues/3399)

Related: #388, https://github.com/denoland/fresh/issues/2649, https://github.com/preactjs/preact/pull/4497

This adds a `HTML_ENUMERATED` rgx to stuff enumerated true/false attributes into in the future, as I brought up in #388.


Patch:
diff --git a/.changeset/angry-chairs-marry.md b/.changeset/angry-chairs-marry.md
new file mode 100644
index 0000000..445fac0
--- /dev/null
+++ b/.changeset/angry-chairs-marry.md
@@ -0,0 +1,5 @@
+---
+'preact-render-to-string': patch
+---
+
+Fix `spellcheck={false}` not rendering as `spellcheck="false"`
diff --git a/package-lock.json b/package-lock.json
index 7045b90..59389a1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
 	"name": "preact-render-to-string",
-	"version": "6.5.7",
+	"version": "6.5.10",
 	"lockfileVersion": 3,
 	"requires": true,
 	"packages": {
 		"": {
 			"name": "preact-render-to-string",
-			"version": "6.5.7",
+			"version": "6.5.10",
 			"license": "MIT",
 			"devDependencies": {
 				"@babel/plugin-transform-react-jsx": "^7.12.12",
@@ -14,7 +14,7 @@
 				"@babel/register": "^7.12.10",
 				"@changesets/changelog-github": "^0.4.1",
 				"@changesets/cli": "^2.18.0",
-				"baseline-rts": "npm:preact-render-to-string@6.5.7",
+				"baseline-rts": "npm:preact-render-to-string@latest",
 				"benchmarkjs-pretty": "^2.0.1",
 				"chai": "^4.2.0",
 				"check-export-map": "^1.3.1",
@@ -2567,10 +2567,11 @@
 		},
 		"node_modules/baseline-rts": {
 			"name": "preact-render-to-string",
-			"version": "6.5.7",
-			"resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.7.tgz",
-			"integrity": "sha512-nACZDdv/ZZciuldVYMcfGqr61DKJeaAfPx96hn6OXoBGhgtU2yGQkA0EpTzWH4SvnwF0syLsL4WK7AIp3Ruc1g==",
+			"version": "6.5.10",
+			"resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.10.tgz",
+			"integrity": "sha512-BJdypTQaBA5UbTF9NKZS3zP93Sw33tZOxNXIfuHofqOZFoMdsquNkVebs/HkEw0in/Qbi6Ep/Anngnj+VsHeBQ==",
 			"dev": true,
+			"license": "MIT",
 			"peerDependencies": {
 				"preact": ">=10"
 			}
diff --git a/src/index.js b/src/index.js
index 1977171..c648b4f 100644
--- a/src/index.js
+++ b/src/index.js
@@ -4,6 +4,7 @@ import {
 	UNSAFE_NAME,
 	NAMESPACE_REPLACE_REGEX,
 	HTML_LOWER_CASE,
+	HTML_ENUMERATED,
 	SVG_CAMEL_CASE,
 	createComponent
 } from './lib/util.js';
@@ -623,10 +624,11 @@ function _renderToString(
 					name = name.replace(NAMESPACE_REPLACE_REGEX, '$1:$2').toLowerCase();
 				} else if (UNSAFE_NAME.test(name)) {
 					continue;
-				} else if ((name[4] === '-' || name === 'draggable') && v != null) {
-					// serialize boolean aria-xyz or draggable attribute values as strings
-					// `draggable` is an enumerated attribute and not Boolean. A value of `true` or `false` is mandatory
-					// https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/draggable
+				} else if (
+					(name[4] === '-' || HTML_ENUMERATED.has(name)) &&
+					v != null
+				) {
+					// serialize boolean aria-xyz or enumerated attribute values as strings
 					v = v + EMPTY_STR;
 				} else if (isSvgMode) {
 					if (SVG_CAMEL_CASE.test(name)) {
@@ -637,9 +639,6 @@ function _renderToString(
 					}
 				} else if (HTML_LOWER_CASE.test(name)) {
 					name = name.toLowerCase();
-					if (name === 'spellcheck') {
-						v = '' + v;
-					}
 				}
 			}
 		}
diff --git a/src/lib/util.js b/src/lib/util.js
index 48c02a0..84b348d 100644
--- a/src/lib/util.js
+++ b/src/lib/util.js
@@ -1,8 +1,11 @@
 export const VOID_ELEMENTS = /^(?:area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/;
 export const UNSAFE_NAME = /[\s\n\\/='"\0<>]/;
 export const NAMESPACE_REPLACE_REGEX = /^(xlink|xmlns|xml)([A-Z])/;
-export const HTML_LOWER_CASE = /^accessK|^auto[A-Z]|^cell|^ch|^col|cont|cross|dateT|encT|form[A-Z]|frame|hrefL|inputM|maxL|minL|noV|playsI|popoverT|readO|rowS|spellC|src[A-Z]|tabI|useM|item[A-Z]/;
-export const SVG_CAMEL_CASE = /^ac|^ali|arabic|basel|cap|clipPath$|clipRule$|color|dominant|enable|fill|flood|font|glyph[^R]|horiz|image|letter|lighting|marker[^WUH]|overline|panose|pointe|paint|rendering|shape|stop|strikethrough|stroke|spel|text[^L]|transform|underline|unicode|units|^v[^i]|^w|^xH/;
+export const HTML_LOWER_CASE = /^accessK|^auto[A-Z]|^cell|^ch|^col|cont|cross|dateT|encT|form[A-Z]|frame|hrefL|inputM|maxL|minL|noV|playsI|popoverT|readO|rowS|src[A-Z]|tabI|useM|item[A-Z]/;
+export const SVG_CAMEL_CASE = /^ac|^ali|arabic|basel|cap|clipPath$|clipRule$|color|dominant|enable|fill|flood|font|glyph[^R]|horiz|image|letter|lighting|marker[^WUH]|overline|panose|pointe|paint|rendering|shape|stop|strikethrough|stroke|text[^L]|transform|underline|unicode|units|^v[^i]|^w|^xH/;
+
+// Boolean DOM properties that translate to enumerated ('true'/'false') attributes
+export const HTML_ENUMERATED = new Set(['draggable', 'spellcheck']);
 
 // DOM properties that should NOT have "px" added when numeric
 const ENCODED_ENTITIES = /["&<]/;


Test patch:
diff --git a/test/render.test.jsx b/test/render.test.jsx
index ba8e028..4591569 100644
--- a/test/render.test.jsx
+++ b/test/render.test.jsx
@@ -154,11 +154,11 @@ describe('render', () => {
 			expect(rendered).to.equal(`<div data-checked="false"></div>`);
 		});
 
-		it('should support spellCheck', () => {
-			let rendered = render(<div spellCheck={false} />);
+		it('should support spellcheck', () => {
+			let rendered = render(<div spellcheck={false} />);
 			expect(rendered).to.equal(`<div spellcheck="false"></div>`);
 
-			rendered = render(<div spellCheck />);
+			rendered = render(<div spellcheck />);
 			expect(rendered).to.equal(`<div spellcheck="true"></div>`);
 		});
 
diff --git a/test/utils.jsx b/test/utils.jsx
index 068a114..631f02d 100644
--- a/test/utils.jsx
+++ b/test/utils.jsx
@@ -400,7 +400,6 @@ export const htmlAttributes = {
 	slot: 'slot',
 	span: 'span',
 	spellcheck: 'spellcheck',
-	spellCheck: 'spellcheck',
 	src: 'src',
 	srcset: 'srcset',
 	srcDoc: 'srcdoc',