@lwc/template-compiler 7.0.1-alpha.0 → 7.0.2

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/dist/index.cjs.js CHANGED
@@ -10912,6 +10912,138 @@ function validateExpressionAst(rootNode) {
10912
10912
  });
10913
10913
  }
10914
10914
 
10915
+ /*
10916
+ * Copyright (c) 2018, salesforce.com, inc.
10917
+ * All rights reserved.
10918
+ * SPDX-License-Identifier: MIT
10919
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
10920
+ */
10921
+ // https://262.ecma-international.org/12.0/#sec-keywords-and-reserved-words
10922
+ // prettier-ignore
10923
+ const REVERSED_KEYWORDS = new Set([
10924
+ // Reserved keywords
10925
+ 'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'default', 'delete',
10926
+ 'do', 'else', 'enum', 'export', 'extends', 'false', 'finally', 'for', 'function', 'if', 'import',
10927
+ 'in', 'instanceof', 'new', 'null', 'return', 'super', 'switch', 'this', 'throw', 'true', 'try',
10928
+ 'typeof', 'var', 'void', 'while', 'with', 'yield',
10929
+ // Strict mode only reserved keywords
10930
+ 'let', 'static', 'implements', 'interface', 'package', 'private', 'protected', 'public'
10931
+ ]);
10932
+ function isReservedES6Keyword(str) {
10933
+ return REVERSED_KEYWORDS.has(str);
10934
+ }
10935
+
10936
+ /*
10937
+ * Copyright (c) 2018, salesforce.com, inc.
10938
+ * All rights reserved.
10939
+ * SPDX-License-Identifier: MIT
10940
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
10941
+ */
10942
+ const EXPRESSION_SYMBOL_START = '{';
10943
+ const EXPRESSION_SYMBOL_END = '}';
10944
+ const POTENTIAL_EXPRESSION_RE = /^.?{.+}.*$/;
10945
+ const WHITESPACES_RE = /\s/;
10946
+ function isExpression(source) {
10947
+ // Issue #3418: Legacy behavior, previous regex treated "{}" attribute value as non expression
10948
+ return source[0] === '{' && source.slice(-1) === '}' && source.length > 2;
10949
+ }
10950
+ function isPotentialExpression(source) {
10951
+ return !!source.match(POTENTIAL_EXPRESSION_RE);
10952
+ }
10953
+ function validateExpression(node, config) {
10954
+ // TODO [#3370]: remove experimental template expression flag
10955
+ if (config.experimentalComplexExpressions) {
10956
+ return validateExpressionAst(node);
10957
+ }
10958
+ const isValidNode = isIdentifier(node) || isMemberExpression(node);
10959
+ errors.invariant(isValidNode, errors.ParserDiagnostics.INVALID_NODE, [node.type]);
10960
+ if (isMemberExpression(node)) {
10961
+ errors.invariant(config.experimentalComputedMemberExpression || !node.computed, errors.ParserDiagnostics.COMPUTED_PROPERTY_ACCESS_NOT_ALLOWED);
10962
+ const { object, property } = node;
10963
+ if (!isIdentifier(object)) {
10964
+ validateExpression(object, config);
10965
+ }
10966
+ if (!isIdentifier(property)) {
10967
+ validateExpression(property, config);
10968
+ }
10969
+ }
10970
+ }
10971
+ function validateSourceIsParsedExpression(source, parsedExpression) {
10972
+ if (parsedExpression.end === source.length - 1) {
10973
+ return;
10974
+ }
10975
+ let unclosedParenthesisCount = 0;
10976
+ for (let i = 0, n = parsedExpression.start; i < n; i++) {
10977
+ if (source[i] === '(') {
10978
+ unclosedParenthesisCount++;
10979
+ }
10980
+ }
10981
+ // source[source.length - 1] === '}', n = source.length - 1 is to avoid processing '}'.
10982
+ for (let i = parsedExpression.end, n = source.length - 1; i < n; i++) {
10983
+ const character = source[i];
10984
+ if (character === ')') {
10985
+ unclosedParenthesisCount--;
10986
+ }
10987
+ else if (character === ';') {
10988
+ // acorn parseExpressionAt will stop at the first ";", it may be that the expression is not
10989
+ // a multiple expression ({foo;}), but this is a case that we explicitly do not want to support.
10990
+ // in such case, let's fail with the same error as if it were a multiple expression.
10991
+ errors.invariant(false, errors.ParserDiagnostics.MULTIPLE_EXPRESSIONS);
10992
+ }
10993
+ else {
10994
+ errors.invariant(WHITESPACES_RE.test(character), errors.ParserDiagnostics.TEMPLATE_EXPRESSION_PARSING_ERROR, ['Unexpected end of expression']);
10995
+ }
10996
+ }
10997
+ errors.invariant(unclosedParenthesisCount === 0, errors.ParserDiagnostics.TEMPLATE_EXPRESSION_PARSING_ERROR, [
10998
+ 'Unexpected end of expression',
10999
+ ]);
11000
+ }
11001
+ function validatePreparsedJsExpressions(ctx) {
11002
+ ctx.preparsedJsExpressions?.forEach(({ parsedExpression, rawText }) => {
11003
+ const acornLoc = parsedExpression.loc;
11004
+ const parse5Loc = {
11005
+ startLine: acornLoc.start.line,
11006
+ startCol: acornLoc.start.column,
11007
+ startOffset: parsedExpression.start,
11008
+ endLine: acornLoc.end.line,
11009
+ endCol: acornLoc.end.column,
11010
+ endOffset: parsedExpression.end,
11011
+ };
11012
+ ctx.withErrorWrapping(() => {
11013
+ validateExpressionAst(parsedExpression);
11014
+ }, errors.ParserDiagnostics.TEMPLATE_EXPRESSION_PARSING_ERROR, sourceLocation(parse5Loc), (err) => `Invalid expression ${rawText} - ${err.message}`);
11015
+ });
11016
+ }
11017
+ function parseExpression(ctx, source, location) {
11018
+ const { ecmaVersion } = ctx;
11019
+ return ctx.withErrorWrapping(() => {
11020
+ const parsed = acorn.parseExpressionAt(source, 1, {
11021
+ ecmaVersion,
11022
+ // TODO [#3370]: remove experimental template expression flag
11023
+ allowAwaitOutsideFunction: ctx.config.experimentalComplexExpressions,
11024
+ });
11025
+ validateSourceIsParsedExpression(source, parsed);
11026
+ validateExpression(parsed, ctx.config);
11027
+ return { ...parsed, location };
11028
+ }, errors.ParserDiagnostics.TEMPLATE_EXPRESSION_PARSING_ERROR, location, (err) => `Invalid expression ${source} - ${err.message}`);
11029
+ }
11030
+ function parseIdentifier(ctx, source, location) {
11031
+ let isValid = true;
11032
+ isValid = acorn.isIdentifierStart(source.charCodeAt(0));
11033
+ for (let i = 1; i < source.length && isValid; i++) {
11034
+ isValid = acorn.isIdentifierChar(source.charCodeAt(i));
11035
+ }
11036
+ if (isValid && !isReservedES6Keyword(source)) {
11037
+ return {
11038
+ ...identifier(source),
11039
+ location,
11040
+ };
11041
+ }
11042
+ else {
11043
+ ctx.throwAtLocation(errors.ParserDiagnostics.INVALID_IDENTIFIER, location, [source]);
11044
+ }
11045
+ }
11046
+
10915
11047
  /*
10916
11048
  * Copyright (c) 2023, salesforce.com, inc.
10917
11049
  * All rights reserved.
@@ -11069,17 +11201,19 @@ class TemplateHtmlTokenizer extends Tokenizer {
11069
11201
  }
11070
11202
  }
11071
11203
  }
11072
- function isTemplateExpressionTextNodeValue(value) {
11073
- return value?.[0] === '{';
11074
- }
11075
11204
  function isTemplateElement(node) {
11076
11205
  return node.nodeName === 'template';
11077
11206
  }
11078
11207
  function isTextNode(node) {
11079
11208
  return node?.nodeName === '#text';
11080
11209
  }
11081
- function isTemplateExpressionTextNode(node) {
11082
- return isTextNode(node) && isTemplateExpressionTextNodeValue(node.value);
11210
+ function isTemplateExpressionTextNode(node, html) {
11211
+ return (isTextNode(node) &&
11212
+ isTemplateExpressionTextNodeValue(node.value, node.sourceCodeLocation.startOffset, html));
11213
+ }
11214
+ function isTemplateExpressionTextNodeValue(value, startOffset, html) {
11215
+ return (value.startsWith(EXPRESSION_SYMBOL_START) &&
11216
+ html.startsWith(EXPRESSION_SYMBOL_START, startOffset));
11083
11217
  }
11084
11218
  /**
11085
11219
  * This class extends `parse5`'s internal parser. The heavy lifting is
@@ -11101,11 +11235,12 @@ class TemplateHtmlParser extends Parser {
11101
11235
  _insertCharacters(token) {
11102
11236
  const parentNode = this.openElements.current;
11103
11237
  const previousPeer = parentNode.childNodes.at(-1);
11238
+ const html = this.tokenizer.preprocessor.html;
11104
11239
  if (
11105
11240
  // If we're not dealing with a template expression...
11106
- !isTemplateExpressionTextNodeValue(token.chars) &&
11241
+ !isTemplateExpressionTextNodeValue(token.chars, token.location.startOffset, html) &&
11107
11242
  // ... and the previous node wasn't a text-node-template-expression...
11108
- !isTemplateExpressionTextNode(previousPeer)) {
11243
+ !isTemplateExpressionTextNode(previousPeer, html)) {
11109
11244
  // ... concatenate the provided characters with the previous token's characters.
11110
11245
  return super._insertCharacters(token);
11111
11246
  }
@@ -11225,138 +11360,6 @@ function decodeTextContent(source) {
11225
11360
  return he__namespace.decode(source);
11226
11361
  }
11227
11362
 
11228
- /*
11229
- * Copyright (c) 2018, salesforce.com, inc.
11230
- * All rights reserved.
11231
- * SPDX-License-Identifier: MIT
11232
- * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
11233
- */
11234
- // https://262.ecma-international.org/12.0/#sec-keywords-and-reserved-words
11235
- // prettier-ignore
11236
- const REVERSED_KEYWORDS = new Set([
11237
- // Reserved keywords
11238
- 'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'default', 'delete',
11239
- 'do', 'else', 'enum', 'export', 'extends', 'false', 'finally', 'for', 'function', 'if', 'import',
11240
- 'in', 'instanceof', 'new', 'null', 'return', 'super', 'switch', 'this', 'throw', 'true', 'try',
11241
- 'typeof', 'var', 'void', 'while', 'with', 'yield',
11242
- // Strict mode only reserved keywords
11243
- 'let', 'static', 'implements', 'interface', 'package', 'private', 'protected', 'public'
11244
- ]);
11245
- function isReservedES6Keyword(str) {
11246
- return REVERSED_KEYWORDS.has(str);
11247
- }
11248
-
11249
- /*
11250
- * Copyright (c) 2018, salesforce.com, inc.
11251
- * All rights reserved.
11252
- * SPDX-License-Identifier: MIT
11253
- * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
11254
- */
11255
- const EXPRESSION_SYMBOL_START = '{';
11256
- const EXPRESSION_SYMBOL_END = '}';
11257
- const POTENTIAL_EXPRESSION_RE = /^.?{.+}.*$/;
11258
- const WHITESPACES_RE = /\s/;
11259
- function isExpression(source) {
11260
- // Issue #3418: Legacy behavior, previous regex treated "{}" attribute value as non expression
11261
- return source[0] === '{' && source.slice(-1) === '}' && source.length > 2;
11262
- }
11263
- function isPotentialExpression(source) {
11264
- return !!source.match(POTENTIAL_EXPRESSION_RE);
11265
- }
11266
- function validateExpression(node, config) {
11267
- // TODO [#3370]: remove experimental template expression flag
11268
- if (config.experimentalComplexExpressions) {
11269
- return validateExpressionAst(node);
11270
- }
11271
- const isValidNode = isIdentifier(node) || isMemberExpression(node);
11272
- errors.invariant(isValidNode, errors.ParserDiagnostics.INVALID_NODE, [node.type]);
11273
- if (isMemberExpression(node)) {
11274
- errors.invariant(config.experimentalComputedMemberExpression || !node.computed, errors.ParserDiagnostics.COMPUTED_PROPERTY_ACCESS_NOT_ALLOWED);
11275
- const { object, property } = node;
11276
- if (!isIdentifier(object)) {
11277
- validateExpression(object, config);
11278
- }
11279
- if (!isIdentifier(property)) {
11280
- validateExpression(property, config);
11281
- }
11282
- }
11283
- }
11284
- function validateSourceIsParsedExpression(source, parsedExpression) {
11285
- if (parsedExpression.end === source.length - 1) {
11286
- return;
11287
- }
11288
- let unclosedParenthesisCount = 0;
11289
- for (let i = 0, n = parsedExpression.start; i < n; i++) {
11290
- if (source[i] === '(') {
11291
- unclosedParenthesisCount++;
11292
- }
11293
- }
11294
- // source[source.length - 1] === '}', n = source.length - 1 is to avoid processing '}'.
11295
- for (let i = parsedExpression.end, n = source.length - 1; i < n; i++) {
11296
- const character = source[i];
11297
- if (character === ')') {
11298
- unclosedParenthesisCount--;
11299
- }
11300
- else if (character === ';') {
11301
- // acorn parseExpressionAt will stop at the first ";", it may be that the expression is not
11302
- // a multiple expression ({foo;}), but this is a case that we explicitly do not want to support.
11303
- // in such case, let's fail with the same error as if it were a multiple expression.
11304
- errors.invariant(false, errors.ParserDiagnostics.MULTIPLE_EXPRESSIONS);
11305
- }
11306
- else {
11307
- errors.invariant(WHITESPACES_RE.test(character), errors.ParserDiagnostics.TEMPLATE_EXPRESSION_PARSING_ERROR, ['Unexpected end of expression']);
11308
- }
11309
- }
11310
- errors.invariant(unclosedParenthesisCount === 0, errors.ParserDiagnostics.TEMPLATE_EXPRESSION_PARSING_ERROR, [
11311
- 'Unexpected end of expression',
11312
- ]);
11313
- }
11314
- function validatePreparsedJsExpressions(ctx) {
11315
- ctx.preparsedJsExpressions?.forEach(({ parsedExpression, rawText }) => {
11316
- const acornLoc = parsedExpression.loc;
11317
- const parse5Loc = {
11318
- startLine: acornLoc.start.line,
11319
- startCol: acornLoc.start.column,
11320
- startOffset: parsedExpression.start,
11321
- endLine: acornLoc.end.line,
11322
- endCol: acornLoc.end.column,
11323
- endOffset: parsedExpression.end,
11324
- };
11325
- ctx.withErrorWrapping(() => {
11326
- validateExpressionAst(parsedExpression);
11327
- }, errors.ParserDiagnostics.TEMPLATE_EXPRESSION_PARSING_ERROR, sourceLocation(parse5Loc), (err) => `Invalid expression ${rawText} - ${err.message}`);
11328
- });
11329
- }
11330
- function parseExpression(ctx, source, location) {
11331
- const { ecmaVersion } = ctx;
11332
- return ctx.withErrorWrapping(() => {
11333
- const parsed = acorn.parseExpressionAt(source, 1, {
11334
- ecmaVersion,
11335
- // TODO [#3370]: remove experimental template expression flag
11336
- allowAwaitOutsideFunction: ctx.config.experimentalComplexExpressions,
11337
- });
11338
- validateSourceIsParsedExpression(source, parsed);
11339
- validateExpression(parsed, ctx.config);
11340
- return { ...parsed, location };
11341
- }, errors.ParserDiagnostics.TEMPLATE_EXPRESSION_PARSING_ERROR, location, (err) => `Invalid expression ${source} - ${err.message}`);
11342
- }
11343
- function parseIdentifier(ctx, source, location) {
11344
- let isValid = true;
11345
- isValid = acorn.isIdentifierStart(source.charCodeAt(0));
11346
- for (let i = 1; i < source.length && isValid; i++) {
11347
- isValid = acorn.isIdentifierChar(source.charCodeAt(i));
11348
- }
11349
- if (isValid && !isReservedES6Keyword(source)) {
11350
- return {
11351
- ...identifier(source),
11352
- location,
11353
- };
11354
- }
11355
- else {
11356
- ctx.throwAtLocation(errors.ParserDiagnostics.INVALID_IDENTIFIER, location, [source]);
11357
- }
11358
- }
11359
-
11360
11363
  /*
11361
11364
  * Copyright (c) 2018, salesforce.com, inc.
11362
11365
  * All rights reserved.
@@ -12958,15 +12961,15 @@ function parseStyleText(cssText) {
12958
12961
  }
12959
12962
  return styleMap;
12960
12963
  }
12964
+ const IMPORTANT_FLAG = /\s*!\s*important\s*$/i;
12961
12965
  // Given a map of CSS property keys to values, return an array AST like:
12962
12966
  // ['color', 'blue', false] // { color: 'blue' }
12963
12967
  // ['background', 'red', true] // { background: 'red !important' }
12964
12968
  function styleMapToStyleDeclsAST(styleMap) {
12965
12969
  const styles = Object.entries(styleMap).map(([key, value]) => {
12966
- const important = value.endsWith('!important');
12970
+ const important = IMPORTANT_FLAG.test(value);
12967
12971
  if (important) {
12968
- // trim off the trailing "!important" (10 chars)
12969
- value = value.substring(0, value.length - 10).trim();
12972
+ value = value.replace(IMPORTANT_FLAG, '').trim();
12970
12973
  }
12971
12974
  return [key, value, important];
12972
12975
  });
@@ -13007,10 +13010,6 @@ function isStaticNode(node, apiVersion) {
13007
13010
  result &&= attributes.every(({ name, value }) => {
13008
13011
  const isStaticSafeLiteral = isLiteral(value) &&
13009
13012
  name !== 'slot' &&
13010
- // check for ScopedId
13011
- name !== 'id' &&
13012
- name !== 'spellcheck' && // spellcheck is specially handled by the vnodes.
13013
- !isIdReferencingAttribute(name) &&
13014
13013
  // svg href needs sanitization.
13015
13014
  !isSvgUseHref(nodeName, name, namespace) &&
13016
13015
  // Check for ScopedFragId
@@ -13159,7 +13158,7 @@ function serializeAttrs(element, codeGen) {
13159
13158
  */
13160
13159
  const attrs = [];
13161
13160
  let hasClassAttr = false;
13162
- const collector = ({ name, value, hasExpression, }) => {
13161
+ const collector = ({ name, value, hasExpression, isIdOrIdRef, }) => {
13163
13162
  let v = typeof value === 'string' ? templateStringEscape(value) : value;
13164
13163
  if (name === 'class') {
13165
13164
  hasClassAttr = true;
@@ -13171,10 +13170,24 @@ function serializeAttrs(element, codeGen) {
13171
13170
  v += '${0}';
13172
13171
  }
13173
13172
  }
13173
+ // `spellcheck` string values are specially handled to massage them into booleans.
13174
+ // For backwards compat with non-static-optimized templates, we also treat any non-`"false"`
13175
+ // value other than the valueless format (e.g. `<div spellcheck>`) as `"true"`,
13176
+ // even though per MDN, the empty string and `"true"` are equivalent:
13177
+ // https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/spellcheck
13178
+ if (name === 'spellcheck' && typeof v === 'string' && !hasExpression) {
13179
+ v = String(v.toLowerCase() !== 'false');
13180
+ }
13174
13181
  if (typeof v === 'string') {
13182
+ // IDs/IDRefs must be handled dynamically at runtime due to synthetic shadow scoping.
13183
+ // Skip serializing here and handle it as if it were a dynamic attribute instead.
13184
+ // Note that, to maintain backwards compatibility with the non-static output, we treat the valueless
13185
+ // "boolean" format (e.g. `<div id>`) as the empty string, which is semantically equivalent.
13186
+ // TODO [#3658]: `disableSyntheticShadowSupport` should also disable this dynamic behavior
13187
+ const needsPlaceholder = hasExpression || isIdOrIdRef;
13175
13188
  // Inject a placeholder where the staticPartId will go when an expression occurs.
13176
13189
  // This is only needed for SSR to inject the expression value during serialization.
13177
- attrs.push(hasExpression ? `\${"${v}"}` : ` ${name}="${shared.htmlEscape(v, true)}"`);
13190
+ attrs.push(needsPlaceholder ? `\${"${v}"}` : ` ${name}="${shared.htmlEscape(v, true)}"`);
13178
13191
  }
13179
13192
  else {
13180
13193
  attrs.push(` ${name}`);
@@ -13182,13 +13195,20 @@ function serializeAttrs(element, codeGen) {
13182
13195
  };
13183
13196
  element.attributes
13184
13197
  .map((attr) => {
13185
- const hasExpression = isExpression$1(attr.value);
13198
+ const { name, value } = attr;
13199
+ const hasExpression = isExpression$1(value);
13200
+ // IDs/IDRefs must be handled dynamically at runtime due to synthetic shadow scoping.
13201
+ // Note that for backwards compat we only consider non-booleans to be dynamic IDs/IDRefs
13202
+ // TODO [#3658]: `disableSyntheticShadowSupport` should also disable this dynamic behavior
13203
+ const isIdOrIdRef = (name === 'id' || isIdReferencingAttribute(name)) &&
13204
+ (isExpression$1(value) || isStringLiteral(value));
13186
13205
  return {
13187
13206
  hasExpression,
13188
- name: attr.name,
13189
- value: hasExpression
13207
+ isIdOrIdRef,
13208
+ name,
13209
+ value: hasExpression || isIdOrIdRef
13190
13210
  ? codeGen.getStaticExpressionToken(attr)
13191
- : attr.value.value,
13211
+ : value.value,
13192
13212
  };
13193
13213
  })
13194
13214
  .forEach(collector);
@@ -13601,7 +13621,7 @@ class CodeGen {
13601
13621
  }
13602
13622
  genClassExpression(value) {
13603
13623
  let classExpression = this.bindExpression(value);
13604
- const isClassNameObjectBindingEnabled = shared.isAPIFeatureEnabled(11 /* APIFeature.TEMPLATE_CLASS_NAME_OBJECT_BINDING */, this.state.config.apiVersion);
13624
+ const isClassNameObjectBindingEnabled = shared.isAPIFeatureEnabled(10 /* APIFeature.TEMPLATE_CLASS_NAME_OBJECT_BINDING */, this.state.config.apiVersion);
13605
13625
  if (isClassNameObjectBindingEnabled) {
13606
13626
  classExpression = this.genNormalizeClassName(classExpression);
13607
13627
  }
@@ -13715,6 +13735,7 @@ class CodeGen {
13715
13735
  const callExpression$1 = callExpression(identifier$1, params);
13716
13736
  // Check if the property actually exists before calling, to allow
13717
13737
  // for older engines to work with newer compilers
13738
+ // TODO [#4313]: remove temporary logic to support v7 compiler + v6 engine
13718
13739
  return fallbackForCompilerEngineVersionMismatch
13719
13740
  ? conditionalExpression(identifier$1, callExpression$1, fallbackForCompilerEngineVersionMismatch)
13720
13741
  : callExpression$1;
@@ -13890,7 +13911,12 @@ class CodeGen {
13890
13911
  const attributeExpressions = [];
13891
13912
  for (const attribute of elm.attributes) {
13892
13913
  const { name, value } = attribute;
13893
- if (isExpression$1(value)) {
13914
+ // IDs/IDRefs must be handled dynamically at runtime due to synthetic shadow scoping.
13915
+ // Note that for backwards compat we only consider non-booleans to be dynamic IDs/IDRefs
13916
+ // TODO [#3658]: `disableSyntheticShadowSupport` should also disable this dynamic behavior
13917
+ const isIdOrIdRef = (name === 'id' || isIdReferencingAttribute(name)) &&
13918
+ (isExpression$1(value) || isStringLiteral(value));
13919
+ if (isExpression$1(value) || isIdOrIdRef) {
13894
13920
  let partToken = '';
13895
13921
  if (name === 'style') {
13896
13922
  partToken = `${"s" /* STATIC_PART_TOKEN_ID.STYLE */}${partId}`;
@@ -13901,6 +13927,7 @@ class CodeGen {
13901
13927
  databag.push(property$1(identifier('className'), this.genClassExpression(value)));
13902
13928
  }
13903
13929
  else {
13930
+ // non-class, non-style (i.e. generic attribute or ID/IDRef)
13904
13931
  partToken = `${"a" /* STATIC_PART_TOKEN_ID.ATTRIBUTE */}${partId}:${name}`;
13905
13932
  attributeExpressions.push(property$1(literal$1(name), bindAttributeExpression(attribute, elm, this, false)));
13906
13933
  }
@@ -14402,6 +14429,7 @@ function transform(codeGen) {
14402
14429
  if (attrName === 'id') {
14403
14430
  return codeGen.genScopedId(attrValue.value);
14404
14431
  }
14432
+ // `spellcheck` string values are specially handled to massage them into booleans.
14405
14433
  if (attrName === 'spellcheck') {
14406
14434
  return literal$1(attrValue.value.toLowerCase() !== 'false');
14407
14435
  }
@@ -14668,5 +14696,5 @@ exports.default = compile;
14668
14696
  exports.kebabcaseToCamelcase = kebabcaseToCamelcase;
14669
14697
  exports.parse = parse;
14670
14698
  exports.toPropertyName = toPropertyName;
14671
- /** version: 7.0.0 */
14699
+ /** version: 7.0.2 */
14672
14700
  //# sourceMappingURL=index.cjs.js.map