@angular/compiler 21.0.6 → 21.0.8

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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license Angular v21.0.6
2
+ * @license Angular v21.0.8
3
3
  * (c) 2010-2025 Google LLC. https://angular.dev/
4
4
  * License: MIT
5
5
  */
@@ -3988,7 +3988,7 @@ class ImplicitReceiver extends AST {
3988
3988
  return visitor.visitImplicitReceiver(this, context);
3989
3989
  }
3990
3990
  }
3991
- class ThisReceiver extends ImplicitReceiver {
3991
+ class ThisReceiver extends AST {
3992
3992
  visit(visitor, context = null) {
3993
3993
  return visitor.visitThisReceiver?.(this, context);
3994
3994
  }
@@ -7793,6 +7793,7 @@ function createControlOp(op) {
7793
7793
  return {
7794
7794
  kind: OpKind.Control,
7795
7795
  target: op.target,
7796
+ name: op.name,
7796
7797
  expression: op.expression,
7797
7798
  bindingKind: op.bindingKind,
7798
7799
  securityContext: op.securityContext,
@@ -9692,7 +9693,7 @@ function extractAttributes(job) {
9692
9693
  }
9693
9694
  break;
9694
9695
  case OpKind.Control:
9695
- OpList.insertBefore(createExtractedAttributeOp(op.target, BindingKind.Property, null, 'field', null, null, null, op.securityContext), lookupElement$3(elements, op.target));
9696
+ OpList.insertBefore(createExtractedAttributeOp(op.target, BindingKind.Property, null, op.name, null, null, null, op.securityContext), lookupElement$3(elements, op.target));
9696
9697
  break;
9697
9698
  case OpKind.TwoWayProperty:
9698
9699
  OpList.insertBefore(createExtractedAttributeOp(op.target, BindingKind.TwoWayProperty, null, op.name, null, null, null, op.securityContext), lookupElement$3(elements, op.target));
@@ -9802,7 +9803,7 @@ function specializeBindings(job) {
9802
9803
  OpList.replace(op, createAttributeOp(op.target, null, op.name, op.expression, op.securityContext, false, op.isStructuralTemplateAttribute, op.templateKind, op.i18nMessage, op.sourceSpan));
9803
9804
  } else if (job.kind === CompilationJobKind.Host) {
9804
9805
  OpList.replace(op, createDomPropertyOp(op.name, op.expression, op.bindingKind, op.i18nContext, op.securityContext, op.sourceSpan));
9805
- } else if (op.name === 'field') {
9806
+ } else if (op.name === 'field' || op.name === 'formField') {
9806
9807
  OpList.replace(op, createControlOp(op));
9807
9808
  } else {
9808
9809
  OpList.replace(op, createPropertyOp(op.target, op.name, op.expression, op.bindingKind, op.securityContext, op.isStructuralTemplateAttribute, op.templateKind, op.i18nContext, op.i18nMessage, op.sourceSpan));
@@ -16462,14 +16463,14 @@ class _ParseAST {
16462
16463
  return new PrefixNot(this.span(start), this.sourceSpan(start), result);
16463
16464
  }
16464
16465
  } else if (this.next.isKeywordTypeof()) {
16465
- this.advance();
16466
16466
  const start = this.inputIndex;
16467
- let result = this.parsePrefix();
16467
+ this.advance();
16468
+ const result = this.parsePrefix();
16468
16469
  return new TypeofExpression(this.span(start), this.sourceSpan(start), result);
16469
16470
  } else if (this.next.isKeywordVoid()) {
16470
- this.advance();
16471
16471
  const start = this.inputIndex;
16472
- let result = this.parsePrefix();
16472
+ this.advance();
16473
+ const result = this.parsePrefix();
16473
16474
  return new VoidExpression(this.span(start), this.sourceSpan(start), result);
16474
16475
  }
16475
16476
  return this.parseCallChain();
@@ -16587,9 +16588,13 @@ class _ParseAST {
16587
16588
  const keyStart = this.inputIndex;
16588
16589
  const quoted = this.next.isString();
16589
16590
  const key = this.expectIdentifierOrKeywordOrString();
16591
+ const keySpan = this.span(keyStart);
16592
+ const keySourceSpan = this.sourceSpan(keyStart);
16590
16593
  const literalMapKey = {
16591
16594
  key,
16592
- quoted
16595
+ quoted,
16596
+ span: keySpan,
16597
+ sourceSpan: keySourceSpan
16593
16598
  };
16594
16599
  keys.push(literalMapKey);
16595
16600
  if (quoted) {
@@ -16599,9 +16604,7 @@ class _ParseAST {
16599
16604
  values.push(this.parsePipe());
16600
16605
  } else {
16601
16606
  literalMapKey.isShorthandInitialized = true;
16602
- const span = this.span(keyStart);
16603
- const sourceSpan = this.sourceSpan(keyStart);
16604
- values.push(new PropertyRead(span, sourceSpan, sourceSpan, new ImplicitReceiver(span, sourceSpan), key));
16607
+ values.push(new PropertyRead(keySpan, keySourceSpan, keySourceSpan, new ImplicitReceiver(keySpan, keySourceSpan), key));
16605
16608
  }
16606
16609
  } while (this.consumeOptionalCharacter($COMMA) && !this.next.isCharacter($RBRACE));
16607
16610
  this.rbracesExpected--;
@@ -16973,7 +16976,7 @@ class SerializeExpressionVisitor {
16973
16976
  return `${ast.expression.visit(this, context)}!`;
16974
16977
  }
16975
16978
  visitPropertyRead(ast, context) {
16976
- if (ast.receiver instanceof ImplicitReceiver) {
16979
+ if (ast.receiver instanceof ImplicitReceiver || ast.receiver instanceof ThisReceiver) {
16977
16980
  return ast.name;
16978
16981
  } else {
16979
16982
  return `${ast.receiver.visit(this, context)}.${ast.name}`;
@@ -17044,7 +17047,7 @@ function SECURITY_SCHEMA() {
17044
17047
  registerContext(SecurityContext.HTML, ['iframe|srcdoc', '*|innerHTML', '*|outerHTML']);
17045
17048
  registerContext(SecurityContext.STYLE, ['*|style']);
17046
17049
  registerContext(SecurityContext.URL, ['*|formAction', 'area|href', 'a|href', 'a|xlink:href', 'form|action', 'annotation|href', 'annotation|xlink:href', 'annotation-xml|href', 'annotation-xml|xlink:href', 'maction|href', 'maction|xlink:href', 'malignmark|href', 'malignmark|xlink:href', 'math|href', 'math|xlink:href', 'mroot|href', 'mroot|xlink:href', 'msqrt|href', 'msqrt|xlink:href', 'merror|href', 'merror|xlink:href', 'mfrac|href', 'mfrac|xlink:href', 'mglyph|href', 'mglyph|xlink:href', 'msub|href', 'msub|xlink:href', 'msup|href', 'msup|xlink:href', 'msubsup|href', 'msubsup|xlink:href', 'mmultiscripts|href', 'mmultiscripts|xlink:href', 'mprescripts|href', 'mprescripts|xlink:href', 'mi|href', 'mi|xlink:href', 'mn|href', 'mn|xlink:href', 'mo|href', 'mo|xlink:href', 'mpadded|href', 'mpadded|xlink:href', 'mphantom|href', 'mphantom|xlink:href', 'mrow|href', 'mrow|xlink:href', 'ms|href', 'ms|xlink:href', 'mspace|href', 'mspace|xlink:href', 'mstyle|href', 'mstyle|xlink:href', 'mtable|href', 'mtable|xlink:href', 'mtd|href', 'mtd|xlink:href', 'mtr|href', 'mtr|xlink:href', 'mtext|href', 'mtext|xlink:href', 'mover|href', 'mover|xlink:href', 'munder|href', 'munder|xlink:href', 'munderover|href', 'munderover|xlink:href', 'semantics|href', 'semantics|xlink:href', 'none|href', 'none|xlink:href', 'img|src', 'video|src']);
17047
- registerContext(SecurityContext.RESOURCE_URL, ['base|href', 'embed|src', 'frame|src', 'iframe|src', 'link|href', 'object|codebase', 'object|data', 'script|src']);
17050
+ registerContext(SecurityContext.RESOURCE_URL, ['base|href', 'embed|src', 'frame|src', 'iframe|src', 'link|href', 'object|codebase', 'object|data', 'script|src', 'script|href', 'script|xlink:href']);
17048
17051
  registerContext(SecurityContext.ATTRIBUTE_NO_BINDING, ['animate|attributeName', 'set|attributeName', 'animateMotion|attributeName', 'animateTransform|attributeName', 'unknown|attributeName', 'iframe|sandbox', 'iframe|allow', 'iframe|allowFullscreen', 'iframe|referrerPolicy', 'iframe|csp', 'iframe|fetchPriority', 'unknown|sandbox', 'unknown|allow', 'unknown|allowFullscreen', 'unknown|referrerPolicy', 'unknown|csp', 'unknown|fetchPriority']);
17049
17052
  }
17050
17053
  return _SECURITY_SCHEMA;
@@ -19446,13 +19449,14 @@ function ariaProperty(name, expression, sourceSpan) {
19446
19449
  function property(name, expression, sanitizer, sourceSpan) {
19447
19450
  return propertyBase(Identifiers.property, name, expression, sanitizer, sourceSpan);
19448
19451
  }
19449
- function control(expression, sanitizer, sourceSpan) {
19452
+ function control(name, expression, sanitizer, sourceSpan) {
19450
19453
  const args = [];
19451
19454
  if (expression instanceof Interpolation) {
19452
19455
  args.push(interpolationToExpression(expression, sourceSpan));
19453
19456
  } else {
19454
19457
  args.push(expression);
19455
19458
  }
19459
+ args.push(literal(name));
19456
19460
  if (sanitizer !== null) {
19457
19461
  args.push(sanitizer);
19458
19462
  }
@@ -19547,7 +19551,7 @@ function pipeBindV(slot, varOffset, args) {
19547
19551
  }
19548
19552
  function textInterpolate(strings, expressions, sourceSpan) {
19549
19553
  const interpolationArgs = collateInterpolationArgs(strings, expressions);
19550
- return callVariadicInstruction(TEXT_INTERPOLATE_CONFIG, [], interpolationArgs, [], sourceSpan);
19554
+ return callVariadicInstruction(TEXT_INTERPOLATE_CONFIG, [], interpolationArgs, sourceSpan);
19551
19555
  }
19552
19556
  function i18nExp(expr, sourceSpan) {
19553
19557
  return call(Identifiers.i18nExp, [expr], sourceSpan);
@@ -19584,7 +19588,7 @@ function syntheticHostProperty(name, expression, sourceSpan) {
19584
19588
  return call(Identifiers.syntheticHostProperty, [literal(name), expression], sourceSpan);
19585
19589
  }
19586
19590
  function pureFunction(varOffset, fn, args) {
19587
- return callVariadicInstructionExpr(PURE_FUNCTION_CONFIG, [literal(varOffset), fn], args, [], null);
19591
+ return callVariadicInstructionExpr(PURE_FUNCTION_CONFIG, [literal(varOffset), fn], args, null);
19588
19592
  }
19589
19593
  function attachSourceLocation(templatePath, locations) {
19590
19594
  return call(Identifiers.attachSourceLocations, [literal(templatePath), locations], null);
@@ -19607,7 +19611,7 @@ function collateInterpolationArgs(strings, expressions) {
19607
19611
  }
19608
19612
  function interpolationToExpression(interpolation, sourceSpan) {
19609
19613
  const interpolationArgs = collateInterpolationArgs(interpolation.strings, interpolation.expressions);
19610
- return callVariadicInstructionExpr(VALUE_INTERPOLATE_CONFIG, [], interpolationArgs, [], sourceSpan);
19614
+ return callVariadicInstructionExpr(VALUE_INTERPOLATE_CONFIG, [], interpolationArgs, sourceSpan);
19611
19615
  }
19612
19616
  function call(instruction, args, sourceSpan) {
19613
19617
  const expr = importExpr(instruction).callFn(args, sourceSpan);
@@ -19645,22 +19649,22 @@ const PURE_FUNCTION_CONFIG = {
19645
19649
  variable: Identifiers.pureFunctionV,
19646
19650
  mapping: n => n
19647
19651
  };
19648
- function callVariadicInstructionExpr(config, baseArgs, interpolationArgs, extraArgs, sourceSpan) {
19652
+ function callVariadicInstructionExpr(config, baseArgs, interpolationArgs, sourceSpan) {
19649
19653
  const n = config.mapping(interpolationArgs.length);
19650
19654
  const lastInterpolationArg = interpolationArgs.at(-1);
19651
- if (extraArgs.length === 0 && interpolationArgs.length > 1 && lastInterpolationArg instanceof LiteralExpr && lastInterpolationArg.value === '') {
19655
+ if (interpolationArgs.length > 1 && lastInterpolationArg instanceof LiteralExpr && lastInterpolationArg.value === '') {
19652
19656
  interpolationArgs.pop();
19653
19657
  }
19654
19658
  if (n < config.constant.length) {
19655
- return importExpr(config.constant[n]).callFn([...baseArgs, ...interpolationArgs, ...extraArgs], sourceSpan);
19659
+ return importExpr(config.constant[n]).callFn([...baseArgs, ...interpolationArgs], sourceSpan);
19656
19660
  } else if (config.variable !== null) {
19657
- return importExpr(config.variable).callFn([...baseArgs, literalArr(interpolationArgs), ...extraArgs], sourceSpan);
19661
+ return importExpr(config.variable).callFn([...baseArgs, literalArr(interpolationArgs)], sourceSpan);
19658
19662
  } else {
19659
19663
  throw new Error(`AssertionError: unable to call variadic function`);
19660
19664
  }
19661
19665
  }
19662
- function callVariadicInstruction(config, baseArgs, interpolationArgs, extraArgs, sourceSpan) {
19663
- return createStatementOp(callVariadicInstructionExpr(config, baseArgs, interpolationArgs, extraArgs, sourceSpan).toStmt());
19666
+ function callVariadicInstruction(config, baseArgs, interpolationArgs, sourceSpan) {
19667
+ return createStatementOp(callVariadicInstructionExpr(config, baseArgs, interpolationArgs, sourceSpan).toStmt());
19664
19668
  }
19665
19669
 
19666
19670
  const GLOBAL_TARGET_RESOLVERS = new Map([['window', Identifiers.resolveWindow], ['document', Identifiers.resolveDocument], ['body', Identifiers.resolveBody]]);
@@ -20003,7 +20007,7 @@ function reifyProperty(op) {
20003
20007
  return isAriaAttribute(op.name) ? ariaProperty(op.name, op.expression, op.sourceSpan) : property(op.name, op.expression, op.sanitizer, op.sourceSpan);
20004
20008
  }
20005
20009
  function reifyControl(op) {
20006
- return control(op.expression, op.sanitizer, op.sourceSpan);
20010
+ return control(op.name, op.expression, op.sanitizer, op.sourceSpan);
20007
20011
  }
20008
20012
  function reifyIrExpression(expr) {
20009
20013
  if (!isIrExpression(expr)) {
@@ -21800,7 +21804,7 @@ function ingestElement(unit, element) {
21800
21804
  ingestNodes(unit, element.children);
21801
21805
  const endOp = createElementEndOp(id, element.endSourceSpan ?? element.startSourceSpan);
21802
21806
  unit.create.push(endOp);
21803
- const fieldInput = element.inputs.find(input => input.name === 'field' && input.type === BindingType.Property);
21807
+ const fieldInput = element.inputs.find(input => (input.name === 'field' || input.name === 'formField') && input.type === BindingType.Property);
21804
21808
  if (fieldInput) {
21805
21809
  unit.create.push(createControlCreateOp(fieldInput.sourceSpan));
21806
21810
  }
@@ -22161,8 +22165,7 @@ function convertAst(ast, job, baseSourceSpan) {
22161
22165
  if (ast instanceof ASTWithSource) {
22162
22166
  return convertAst(ast.ast, job, baseSourceSpan);
22163
22167
  } else if (ast instanceof PropertyRead) {
22164
- const isImplicitReceiver = ast.receiver instanceof ImplicitReceiver && !(ast.receiver instanceof ThisReceiver);
22165
- if (isImplicitReceiver) {
22168
+ if (ast.receiver instanceof ImplicitReceiver) {
22166
22169
  return new LexicalReadExpr(ast.name);
22167
22170
  } else {
22168
22171
  return new ReadPropExpr(convertAst(ast.receiver, job, baseSourceSpan), ast.name, null, convertSourceSpan(ast.span, baseSourceSpan));
@@ -22931,7 +22934,7 @@ class BindingParser {
22931
22934
  if (ast instanceof NonNullAssert) {
22932
22935
  return this._isAllowedAssignmentEvent(ast.expression);
22933
22936
  }
22934
- if (ast instanceof Call && ast.args.length === 1 && ast.receiver instanceof PropertyRead && ast.receiver.name === '$any' && ast.receiver.receiver instanceof ImplicitReceiver && !(ast.receiver.receiver instanceof ThisReceiver)) {
22937
+ if (ast instanceof Call && ast.args.length === 1 && ast.receiver instanceof PropertyRead && ast.receiver.name === '$any' && ast.receiver.receiver instanceof ImplicitReceiver) {
22935
22938
  return this._isAllowedAssignmentEvent(ast.args[0]);
22936
22939
  }
22937
22940
  if (ast instanceof PropertyRead || ast instanceof KeyedRead) {
@@ -23694,7 +23697,7 @@ function createViewportTrigger(start, isHydrationTrigger, bindingParser, paramet
23694
23697
  } else {
23695
23698
  const value = parsed.ast.values[triggerIndex];
23696
23699
  const triggerFilter = (_, index) => index !== triggerIndex;
23697
- if (!(value instanceof PropertyRead) || !(value.receiver instanceof ImplicitReceiver) || value.receiver instanceof ThisReceiver) {
23700
+ if (!(value instanceof PropertyRead) || !(value.receiver instanceof ImplicitReceiver)) {
23698
23701
  throw new Error(`"trigger" option of the "viewport" trigger must be an identifier`);
23699
23702
  }
23700
23703
  reference = value.name;
@@ -25696,7 +25699,7 @@ class TemplateBinder extends CombinedRecursiveAstVisitor {
25696
25699
  binder.ingest(node);
25697
25700
  }
25698
25701
  maybeMap(ast, name) {
25699
- if (!(ast.receiver instanceof ImplicitReceiver) || ast.receiver instanceof ThisReceiver) {
25702
+ if (!(ast.receiver instanceof ImplicitReceiver)) {
25700
25703
  return;
25701
25704
  }
25702
25705
  const target = this.scope.lookup(name);
@@ -28046,7 +28049,7 @@ const MINIMUM_PARTIAL_LINKER_DEFER_SUPPORT_VERSION = '18.0.0';
28046
28049
  function compileDeclareClassMetadata(metadata) {
28047
28050
  const definitionMap = new DefinitionMap();
28048
28051
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$5));
28049
- definitionMap.set('version', literal('21.0.6'));
28052
+ definitionMap.set('version', literal('21.0.8'));
28050
28053
  definitionMap.set('ngImport', importExpr(Identifiers.core));
28051
28054
  definitionMap.set('type', metadata.type);
28052
28055
  definitionMap.set('decorators', metadata.decorators);
@@ -28064,7 +28067,7 @@ function compileComponentDeclareClassMetadata(metadata, dependencies) {
28064
28067
  callbackReturnDefinitionMap.set('ctorParameters', metadata.ctorParameters ?? literal(null));
28065
28068
  callbackReturnDefinitionMap.set('propDecorators', metadata.propDecorators ?? literal(null));
28066
28069
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_DEFER_SUPPORT_VERSION));
28067
- definitionMap.set('version', literal('21.0.6'));
28070
+ definitionMap.set('version', literal('21.0.8'));
28068
28071
  definitionMap.set('ngImport', importExpr(Identifiers.core));
28069
28072
  definitionMap.set('type', metadata.type);
28070
28073
  definitionMap.set('resolveDeferredDeps', compileComponentMetadataAsyncResolver(dependencies));
@@ -28137,7 +28140,7 @@ function createDirectiveDefinitionMap(meta) {
28137
28140
  const definitionMap = new DefinitionMap();
28138
28141
  const minVersion = getMinimumVersionForPartialOutput(meta);
28139
28142
  definitionMap.set('minVersion', literal(minVersion));
28140
- definitionMap.set('version', literal('21.0.6'));
28143
+ definitionMap.set('version', literal('21.0.8'));
28141
28144
  definitionMap.set('type', meta.type.value);
28142
28145
  if (meta.isStandalone !== undefined) {
28143
28146
  definitionMap.set('isStandalone', literal(meta.isStandalone));
@@ -28469,7 +28472,7 @@ const MINIMUM_PARTIAL_LINKER_VERSION$4 = '12.0.0';
28469
28472
  function compileDeclareFactoryFunction(meta) {
28470
28473
  const definitionMap = new DefinitionMap();
28471
28474
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$4));
28472
- definitionMap.set('version', literal('21.0.6'));
28475
+ definitionMap.set('version', literal('21.0.8'));
28473
28476
  definitionMap.set('ngImport', importExpr(Identifiers.core));
28474
28477
  definitionMap.set('type', meta.type.value);
28475
28478
  definitionMap.set('deps', compileDependencies(meta.deps));
@@ -28495,7 +28498,7 @@ function compileDeclareInjectableFromMetadata(meta) {
28495
28498
  function createInjectableDefinitionMap(meta) {
28496
28499
  const definitionMap = new DefinitionMap();
28497
28500
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$3));
28498
- definitionMap.set('version', literal('21.0.6'));
28501
+ definitionMap.set('version', literal('21.0.8'));
28499
28502
  definitionMap.set('ngImport', importExpr(Identifiers.core));
28500
28503
  definitionMap.set('type', meta.type.value);
28501
28504
  if (meta.providedIn !== undefined) {
@@ -28536,7 +28539,7 @@ function compileDeclareInjectorFromMetadata(meta) {
28536
28539
  function createInjectorDefinitionMap(meta) {
28537
28540
  const definitionMap = new DefinitionMap();
28538
28541
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$2));
28539
- definitionMap.set('version', literal('21.0.6'));
28542
+ definitionMap.set('version', literal('21.0.8'));
28540
28543
  definitionMap.set('ngImport', importExpr(Identifiers.core));
28541
28544
  definitionMap.set('type', meta.type.value);
28542
28545
  definitionMap.set('providers', meta.providers);
@@ -28563,7 +28566,7 @@ function createNgModuleDefinitionMap(meta) {
28563
28566
  throw new Error('Invalid path! Local compilation mode should not get into the partial compilation path');
28564
28567
  }
28565
28568
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$1));
28566
- definitionMap.set('version', literal('21.0.6'));
28569
+ definitionMap.set('version', literal('21.0.8'));
28567
28570
  definitionMap.set('ngImport', importExpr(Identifiers.core));
28568
28571
  definitionMap.set('type', meta.type.value);
28569
28572
  if (meta.bootstrap.length > 0) {
@@ -28601,7 +28604,7 @@ function compileDeclarePipeFromMetadata(meta) {
28601
28604
  function createPipeDefinitionMap(meta) {
28602
28605
  const definitionMap = new DefinitionMap();
28603
28606
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION));
28604
- definitionMap.set('version', literal('21.0.6'));
28607
+ definitionMap.set('version', literal('21.0.8'));
28605
28608
  definitionMap.set('ngImport', importExpr(Identifiers.core));
28606
28609
  definitionMap.set('type', meta.type.value);
28607
28610
  if (meta.isStandalone !== undefined) {
@@ -28675,7 +28678,7 @@ function compileHmrUpdateCallback(definitions, constantStatements, meta) {
28675
28678
  return new DeclareFunctionStmt(`${meta.className}_UpdateMetadata`, params, body, null, StmtModifier.Final);
28676
28679
  }
28677
28680
 
28678
- const VERSION = new Version('21.0.6');
28681
+ const VERSION = new Version('21.0.8');
28679
28682
 
28680
28683
  publishFacade(_global);
28681
28684