@barefootjs/shared 0.18.2 → 0.18.4

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.
@@ -42,9 +42,11 @@ export declare function toHTMLAttrName(key: string): string;
42
42
  * Runtime variant of attribute name mapping that additionally applies
43
43
  * camelCase→kebab conversion for `data-*` and `aria-*` convenience
44
44
  * props (e.g. `dataTestId` → `data-test-id`) and preserves SVG XML
45
- * attribute casing. All other non-SVG keys pass through unchanged
46
- * standard HTML attributes like `tabIndex` and `autoFocus` must not
47
- * be kebab-cased (`tab-index` / `auto-focus` are not valid attrs).
45
+ * attribute casing. HTML camelCase aliases resolve through the same
46
+ * `HTML_CAMEL_ALIASES` table as the compile-time variant (`tabIndex`
47
+ * `tabindex`); generic kebab-casing still applies ONLY to `data-*` /
48
+ * `aria-*` — an unknown camelCase key passes through unchanged rather
49
+ * than being guessed into a hyphenated non-attribute.
48
50
  */
49
51
  export declare function toHTMLAttrNameRuntime(key: string): string;
50
52
  /**
package/dist/index.js CHANGED
@@ -84,6 +84,63 @@ var SVG_CAMEL_TO_KEBAB = {
84
84
  markerMid: "marker-mid",
85
85
  markerEnd: "marker-end"
86
86
  };
87
+ var HTML_CAMEL_ALIASES = {
88
+ acceptCharset: "accept-charset",
89
+ accessKey: "accesskey",
90
+ allowFullScreen: "allowfullscreen",
91
+ autoCapitalize: "autocapitalize",
92
+ autoComplete: "autocomplete",
93
+ autoCorrect: "autocorrect",
94
+ autoFocus: "autofocus",
95
+ autoPlay: "autoplay",
96
+ cellPadding: "cellpadding",
97
+ cellSpacing: "cellspacing",
98
+ charSet: "charset",
99
+ colSpan: "colspan",
100
+ contentEditable: "contenteditable",
101
+ controlsList: "controlslist",
102
+ crossOrigin: "crossorigin",
103
+ dateTime: "datetime",
104
+ dirName: "dirname",
105
+ encType: "enctype",
106
+ enterKeyHint: "enterkeyhint",
107
+ fetchPriority: "fetchpriority",
108
+ formAction: "formaction",
109
+ formEncType: "formenctype",
110
+ formMethod: "formmethod",
111
+ formNoValidate: "formnovalidate",
112
+ formTarget: "formtarget",
113
+ frameBorder: "frameborder",
114
+ hrefLang: "hreflang",
115
+ httpEquiv: "http-equiv",
116
+ imageSizes: "imagesizes",
117
+ imageSrcSet: "imagesrcset",
118
+ inputMode: "inputmode",
119
+ itemID: "itemid",
120
+ itemProp: "itemprop",
121
+ itemRef: "itemref",
122
+ itemScope: "itemscope",
123
+ itemType: "itemtype",
124
+ marginHeight: "marginheight",
125
+ marginWidth: "marginwidth",
126
+ maxLength: "maxlength",
127
+ minLength: "minlength",
128
+ noModule: "nomodule",
129
+ noValidate: "novalidate",
130
+ playsInline: "playsinline",
131
+ popoverTarget: "popovertarget",
132
+ popoverTargetAction: "popovertargetaction",
133
+ radioGroup: "radiogroup",
134
+ readOnly: "readonly",
135
+ referrerPolicy: "referrerpolicy",
136
+ rowSpan: "rowspan",
137
+ spellCheck: "spellcheck",
138
+ srcDoc: "srcdoc",
139
+ srcLang: "srclang",
140
+ srcSet: "srcset",
141
+ tabIndex: "tabindex",
142
+ useMap: "usemap"
143
+ };
87
144
  var SVG_XML_CAMEL_ATTRS = new Set([
88
145
  "allowReorder",
89
146
  "attributeName",
@@ -178,6 +235,9 @@ function toHTMLAttrName(key) {
178
235
  return "class";
179
236
  if (key === "htmlFor")
180
237
  return "for";
238
+ const htmlAlias = HTML_CAMEL_ALIASES[key];
239
+ if (htmlAlias !== undefined)
240
+ return htmlAlias;
181
241
  const svgKebab = SVG_CAMEL_TO_KEBAB[key];
182
242
  if (svgKebab !== undefined)
183
243
  return svgKebab;
@@ -188,6 +248,9 @@ function toHTMLAttrNameRuntime(key) {
188
248
  return "class";
189
249
  if (key === "htmlFor")
190
250
  return "for";
251
+ const htmlAlias = HTML_CAMEL_ALIASES[key];
252
+ if (htmlAlias !== undefined)
253
+ return htmlAlias;
191
254
  const svgKebab = SVG_CAMEL_TO_KEBAB[key];
192
255
  if (svgKebab !== undefined)
193
256
  return svgKebab;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/shared",
3
- "version": "0.18.2",
3
+ "version": "0.18.4",
4
4
  "description": "Shared constants for BarefootJS compiler and runtime",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -106,12 +106,27 @@ describe('toHTMLAttrName (compile-time)', () => {
106
106
  expect(toHTMLAttrName('strokeWidth')).toBe('stroke-width')
107
107
  })
108
108
 
109
- test('tabIndex passes through (no generic kebab)', () => {
110
- expect(toHTMLAttrName('tabIndex')).toBe('tabIndex')
109
+ // #2172: HTML camelCase aliases resolve through HTML_CAMEL_ALIASES
110
+ // these are known HTML attributes with a defined lowercase spelling,
111
+ // NOT the generic kebab conversion (which stays data-*/aria-* only).
112
+ test('tabIndex lowers to tabindex (HTML alias table)', () => {
113
+ expect(toHTMLAttrName('tabIndex')).toBe('tabindex')
111
114
  })
112
115
 
113
- test('autoFocus passes through (no generic kebab)', () => {
114
- expect(toHTMLAttrName('autoFocus')).toBe('autoFocus')
116
+ test('autoFocus lowers to autofocus (HTML alias table)', () => {
117
+ expect(toHTMLAttrName('autoFocus')).toBe('autofocus')
118
+ })
119
+
120
+ test('readOnly lowers to the BOOLEAN_ATTRS member readonly', () => {
121
+ expect(toHTMLAttrName('readOnly')).toBe('readonly')
122
+ })
123
+
124
+ test('spellCheck lowers to the enumerated (non-boolean) spellcheck', () => {
125
+ expect(toHTMLAttrName('spellCheck')).toBe('spellcheck')
126
+ })
127
+
128
+ test('unknown camelCase names still pass through unchanged', () => {
129
+ expect(toHTMLAttrName('myCustomAttr')).toBe('myCustomAttr')
115
130
  })
116
131
  })
117
132
 
@@ -144,12 +159,24 @@ describe('toHTMLAttrNameRuntime', () => {
144
159
  expect(toHTMLAttrNameRuntime('ariaLabel')).toBe('aria-label')
145
160
  })
146
161
 
147
- test('tabIndex passes through (no kebab)', () => {
148
- expect(toHTMLAttrNameRuntime('tabIndex')).toBe('tabIndex')
162
+ test('tabIndex lowers to tabindex (HTML alias table, #2172)', () => {
163
+ expect(toHTMLAttrNameRuntime('tabIndex')).toBe('tabindex')
164
+ })
165
+
166
+ test('autoFocus lowers to autofocus (HTML alias table, #2172)', () => {
167
+ expect(toHTMLAttrNameRuntime('autoFocus')).toBe('autofocus')
168
+ })
169
+
170
+ test('readOnly lowers to the boolean attr spelling readonly (HTML alias table, #2172)', () => {
171
+ expect(toHTMLAttrNameRuntime('readOnly')).toBe('readonly')
172
+ })
173
+
174
+ test('spellCheck lowers to the enumerated attr spelling spellcheck (HTML alias table, #2172)', () => {
175
+ expect(toHTMLAttrNameRuntime('spellCheck')).toBe('spellcheck')
149
176
  })
150
177
 
151
- test('autoFocus passes through (no kebab)', () => {
152
- expect(toHTMLAttrNameRuntime('autoFocus')).toBe('autoFocus')
178
+ test('unknown camelCase names still pass through unchanged', () => {
179
+ expect(toHTMLAttrNameRuntime('myCustomAttr')).toBe('myCustomAttr')
153
180
  })
154
181
  })
155
182
 
package/src/dom-prop.ts CHANGED
@@ -72,6 +72,79 @@ const SVG_CAMEL_TO_KEBAB: Readonly<Record<string, string>> = {
72
72
  markerEnd: 'marker-end',
73
73
  }
74
74
 
75
+ /**
76
+ * HTML attributes written in React-style camelCase in JSX that map to a
77
+ * lowercase (or hyphenated) HTML attribute name. Mirrors React DOM's
78
+ * HTML attribute aliases. `className` / `htmlFor` are handled before
79
+ * this table (they predate it); everything here is a plain rename —
80
+ * value semantics are untouched (`readOnly` becomes the BOOLEAN_ATTRS
81
+ * member `readonly`; `spellCheck` stays the *enumerated* — NOT boolean
82
+ * — `spellcheck`).
83
+ *
84
+ * Consumed by `toHTMLAttrName` (compile-time Phase 1: `processAttributes`
85
+ * normalizes IRAttribute.name so adapters emit the HTML name as-is) and
86
+ * by `toHTMLAttrNameRuntime` (runtime spread paths). Names NOT in this
87
+ * table pass through unchanged, so `data-*`, `aria-*`, and custom-element
88
+ * attributes are never rewritten.
89
+ */
90
+ const HTML_CAMEL_ALIASES: Readonly<Record<string, string>> = {
91
+ acceptCharset: 'accept-charset',
92
+ accessKey: 'accesskey',
93
+ allowFullScreen: 'allowfullscreen',
94
+ autoCapitalize: 'autocapitalize',
95
+ autoComplete: 'autocomplete',
96
+ autoCorrect: 'autocorrect',
97
+ autoFocus: 'autofocus',
98
+ autoPlay: 'autoplay',
99
+ cellPadding: 'cellpadding',
100
+ cellSpacing: 'cellspacing',
101
+ charSet: 'charset',
102
+ colSpan: 'colspan',
103
+ contentEditable: 'contenteditable',
104
+ controlsList: 'controlslist',
105
+ crossOrigin: 'crossorigin',
106
+ dateTime: 'datetime',
107
+ dirName: 'dirname',
108
+ encType: 'enctype',
109
+ enterKeyHint: 'enterkeyhint',
110
+ fetchPriority: 'fetchpriority',
111
+ formAction: 'formaction',
112
+ formEncType: 'formenctype',
113
+ formMethod: 'formmethod',
114
+ formNoValidate: 'formnovalidate',
115
+ formTarget: 'formtarget',
116
+ frameBorder: 'frameborder',
117
+ hrefLang: 'hreflang',
118
+ httpEquiv: 'http-equiv',
119
+ imageSizes: 'imagesizes',
120
+ imageSrcSet: 'imagesrcset',
121
+ inputMode: 'inputmode',
122
+ itemID: 'itemid',
123
+ itemProp: 'itemprop',
124
+ itemRef: 'itemref',
125
+ itemScope: 'itemscope',
126
+ itemType: 'itemtype',
127
+ marginHeight: 'marginheight',
128
+ marginWidth: 'marginwidth',
129
+ maxLength: 'maxlength',
130
+ minLength: 'minlength',
131
+ noModule: 'nomodule',
132
+ noValidate: 'novalidate',
133
+ playsInline: 'playsinline',
134
+ popoverTarget: 'popovertarget',
135
+ popoverTargetAction: 'popovertargetaction',
136
+ radioGroup: 'radiogroup',
137
+ readOnly: 'readonly',
138
+ referrerPolicy: 'referrerpolicy',
139
+ rowSpan: 'rowspan',
140
+ spellCheck: 'spellcheck',
141
+ srcDoc: 'srcdoc',
142
+ srcLang: 'srclang',
143
+ srcSet: 'srcset',
144
+ tabIndex: 'tabindex',
145
+ useMap: 'usemap',
146
+ }
147
+
75
148
  /**
76
149
  * SVG XML attribute names that are case-sensitive and MUST stay in camelCase.
77
150
  *
@@ -169,6 +242,8 @@ export function classifyDOMProp(key: string): DOMPropClassification {
169
242
  export function toHTMLAttrName(key: string): string {
170
243
  if (key === 'className') return 'class'
171
244
  if (key === 'htmlFor') return 'for'
245
+ const htmlAlias = HTML_CAMEL_ALIASES[key]
246
+ if (htmlAlias !== undefined) return htmlAlias
172
247
  const svgKebab = SVG_CAMEL_TO_KEBAB[key]
173
248
  if (svgKebab !== undefined) return svgKebab
174
249
  return key
@@ -178,13 +253,17 @@ export function toHTMLAttrName(key: string): string {
178
253
  * Runtime variant of attribute name mapping that additionally applies
179
254
  * camelCase→kebab conversion for `data-*` and `aria-*` convenience
180
255
  * props (e.g. `dataTestId` → `data-test-id`) and preserves SVG XML
181
- * attribute casing. All other non-SVG keys pass through unchanged
182
- * standard HTML attributes like `tabIndex` and `autoFocus` must not
183
- * be kebab-cased (`tab-index` / `auto-focus` are not valid attrs).
256
+ * attribute casing. HTML camelCase aliases resolve through the same
257
+ * `HTML_CAMEL_ALIASES` table as the compile-time variant (`tabIndex`
258
+ * `tabindex`); generic kebab-casing still applies ONLY to `data-*` /
259
+ * `aria-*` — an unknown camelCase key passes through unchanged rather
260
+ * than being guessed into a hyphenated non-attribute.
184
261
  */
185
262
  export function toHTMLAttrNameRuntime(key: string): string {
186
263
  if (key === 'className') return 'class'
187
264
  if (key === 'htmlFor') return 'for'
265
+ const htmlAlias = HTML_CAMEL_ALIASES[key]
266
+ if (htmlAlias !== undefined) return htmlAlias
188
267
  const svgKebab = SVG_CAMEL_TO_KEBAB[key]
189
268
  if (svgKebab !== undefined) return svgKebab
190
269
  if (SVG_XML_CAMEL_ATTRS.has(key)) return key