@hyperfixi/core 2.9.1 → 2.9.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 (49) hide show
  1. package/dist/bundle-generator/index.js +6 -0
  2. package/dist/bundle-generator/index.mjs +6 -0
  3. package/dist/bundle-generator/parser-templates.d.ts +1 -1
  4. package/dist/chunks/{bridge-BYhqTOnL.js → bridge-DwtNLKK3.js} +2 -2
  5. package/dist/chunks/browser-modular-6HT1r2AH.js +2 -0
  6. package/dist/chunks/{index-DXgJaDj-.js → index-B8jwvuj_.js} +2 -2
  7. package/dist/commands/index.js +133 -74
  8. package/dist/commands/index.mjs +133 -74
  9. package/dist/hyperfixi-browser-classic-i18n.js +1 -1
  10. package/dist/hyperfixi-browser-minimal.js +1 -1
  11. package/dist/hyperfixi-browser-standard.js +1 -1
  12. package/dist/hyperfixi-browser.js +1 -1
  13. package/dist/hyperfixi-classic-i18n.js +1 -1
  14. package/dist/hyperfixi-hx-v4.js +1 -1
  15. package/dist/hyperfixi-hx.js +1 -1
  16. package/dist/hyperfixi-hybrid-complete.js +1 -1
  17. package/dist/hyperfixi-hybrid-hx.js +1 -1
  18. package/dist/hyperfixi-minimal.js +1 -1
  19. package/dist/hyperfixi-multilingual.js +1 -1
  20. package/dist/hyperfixi-standard.js +1 -1
  21. package/dist/hyperfixi.js +1 -1
  22. package/dist/hyperfixi.mjs +1 -1
  23. package/dist/index.js +233 -79
  24. package/dist/index.min.js +1 -1
  25. package/dist/index.mjs +233 -79
  26. package/dist/lokascript-browser-classic-i18n.js +1 -1
  27. package/dist/lokascript-browser-minimal.js +1 -1
  28. package/dist/lokascript-browser-standard.js +1 -1
  29. package/dist/lokascript-browser.js +1 -1
  30. package/dist/lokascript-hybrid-complete.js +1 -1
  31. package/dist/lokascript-hybrid-hx.js +1 -1
  32. package/dist/lokascript-multilingual.js +1 -1
  33. package/dist/parser/command-parsers/utility-commands.d.ts +1 -1
  34. package/dist/parser/full-parser.js +133 -74
  35. package/dist/parser/full-parser.mjs +133 -74
  36. package/dist/parser/hybrid/index.js +3 -0
  37. package/dist/parser/hybrid/index.mjs +3 -0
  38. package/dist/parser/hybrid/parser-core.js +3 -0
  39. package/dist/parser/hybrid/parser-core.mjs +3 -0
  40. package/dist/parser/hybrid-parser.js +3 -0
  41. package/dist/parser/hybrid-parser.mjs +3 -0
  42. package/dist/reference/index.js +1 -0
  43. package/dist/reference/index.mjs +1 -0
  44. package/dist/registry/index.js +133 -74
  45. package/dist/registry/index.mjs +133 -74
  46. package/dist/runtime/runtime-base.d.ts +2 -1
  47. package/dist/types/core.d.ts +1 -0
  48. package/package.json +6 -6
  49. package/dist/chunks/browser-modular-D2BHzZFB.js +0 -2
package/dist/index.mjs CHANGED
@@ -514,13 +514,13 @@ function tokenize$1(input) {
514
514
  continue;
515
515
  }
516
516
  if (char === '{') {
517
- addToken(tokenizer, TokenKind.OPERATOR, '{');
518
517
  advance(tokenizer);
518
+ addToken(tokenizer, TokenKind.OPERATOR, '{');
519
519
  continue;
520
520
  }
521
521
  if (char === '}') {
522
- addToken(tokenizer, TokenKind.OPERATOR, '}');
523
522
  advance(tokenizer);
523
+ addToken(tokenizer, TokenKind.OPERATOR, '}');
524
524
  continue;
525
525
  }
526
526
  if (char === '[') {
@@ -536,24 +536,24 @@ function tokenize$1(input) {
536
536
  prevToken.value === ']') &&
537
537
  !isEventCondition;
538
538
  if (isMemberAccess) {
539
- addToken(tokenizer, TokenKind.OPERATOR, '[');
540
539
  advance(tokenizer);
540
+ addToken(tokenizer, TokenKind.OPERATOR, '[');
541
541
  }
542
542
  else {
543
543
  if (isEventCondition) {
544
- addToken(tokenizer, TokenKind.SYMBOL, '[');
545
544
  advance(tokenizer);
545
+ addToken(tokenizer, TokenKind.SYMBOL, '[');
546
546
  }
547
547
  else {
548
- addToken(tokenizer, TokenKind.OPERATOR, '[');
549
548
  advance(tokenizer);
549
+ addToken(tokenizer, TokenKind.OPERATOR, '[');
550
550
  }
551
551
  }
552
552
  continue;
553
553
  }
554
554
  if (char === ']') {
555
- addToken(tokenizer, TokenKind.OPERATOR, ']');
556
555
  advance(tokenizer);
556
+ addToken(tokenizer, TokenKind.OPERATOR, ']');
557
557
  continue;
558
558
  }
559
559
  if (char === '$') {
@@ -2980,7 +2980,6 @@ const MULTI_WORD_PATTERNS = [
2980
2980
  keywords: ['a', 'an', 'from', 'called'],
2981
2981
  syntax: 'make (a|an) <type> [from <args>] [called <name>]',
2982
2982
  },
2983
- { command: 'send', keywords: ['to'], syntax: 'send <event> to <target>' },
2984
2983
  { command: 'throw', keywords: [], syntax: 'throw <error>' },
2985
2984
  ];
2986
2985
  function getMultiWordPattern(commandName) {
@@ -3281,7 +3280,13 @@ function parseTriggerCommand(ctx, identifierNode) {
3281
3280
  finalArgs.push(ctx.createIdentifier(keyword));
3282
3281
  continue;
3283
3282
  }
3284
- finalArgs.push(ctx.parsePrimary());
3283
+ const before = ctx.savePosition();
3284
+ const expr = ctx.parsePrimary();
3285
+ if (expr)
3286
+ finalArgs.push(expr);
3287
+ if (ctx.savePosition() === before) {
3288
+ ctx.advance();
3289
+ }
3285
3290
  }
3286
3291
  return CommandNodeBuilder.fromIdentifier(identifierNode)
3287
3292
  .withArgs(...finalArgs)
@@ -3500,11 +3505,64 @@ function parseRepeatCommand(ctx, commandToken) {
3500
3505
  }
3501
3506
  return builder.endingAt(ctx.getPosition()).build();
3502
3507
  }
3508
+ function parseIfBranchCommands(ctx, stopAtElse) {
3509
+ const commands = [];
3510
+ const atTerminator = () => ctx.check(KEYWORDS$1.END) || (stopAtElse && ctx.check(KEYWORDS$1.ELSE));
3511
+ while (!ctx.isAtEnd() && !atTerminator()) {
3512
+ if (isComment(ctx.peek())) {
3513
+ ctx.advance();
3514
+ continue;
3515
+ }
3516
+ if (!ctx.checkIsCommand() && !ctx.isCommand(ctx.peek().value))
3517
+ break;
3518
+ ctx.advance();
3519
+ const cmd = ctx.parseCommand();
3520
+ if (cmd) {
3521
+ commands.push(cmd);
3522
+ }
3523
+ if (ctx.check(KEYWORDS$1.THEN) || ctx.check(KEYWORDS$1.AND) || ctx.check(',')) {
3524
+ ctx.advance();
3525
+ }
3526
+ }
3527
+ return commands;
3528
+ }
3529
+ const OPERAND_INTRODUCERS = new Set([
3530
+ 'is',
3531
+ 'am',
3532
+ 'are',
3533
+ 'not',
3534
+ 'no',
3535
+ 'and',
3536
+ 'or',
3537
+ '==',
3538
+ '===',
3539
+ '!=',
3540
+ '!==',
3541
+ '<',
3542
+ '>',
3543
+ '<=',
3544
+ '>=',
3545
+ '+',
3546
+ '-',
3547
+ '*',
3548
+ '/',
3549
+ 'mod',
3550
+ ]);
3551
+ function isBodyCommandStart(ctx, prevToken, token) {
3552
+ if (prevToken === null) {
3553
+ return false;
3554
+ }
3555
+ if (OPERAND_INTRODUCERS.has(prevToken.value?.toLowerCase?.() ?? '')) {
3556
+ return false;
3557
+ }
3558
+ return ctx.checkIsCommand() || ctx.isCommand(token.value?.toLowerCase());
3559
+ }
3503
3560
  function parseIfCommand(ctx, commandToken) {
3504
3561
  const args = [];
3505
3562
  let hasThen = false;
3506
3563
  const savedPosForThen = ctx.savePosition();
3507
3564
  const maxThenLookahead = 500;
3565
+ let prevForThen = null;
3508
3566
  for (let i = 0; i < maxThenLookahead && !ctx.isAtEnd(); i++) {
3509
3567
  const token = ctx.peek();
3510
3568
  if (token.value === KEYWORDS$1.THEN) {
@@ -3517,6 +3575,14 @@ function parseIfCommand(ctx, commandToken) {
3517
3575
  token.value === KEYWORDS$1.ON) {
3518
3576
  break;
3519
3577
  }
3578
+ if (token.line !== undefined &&
3579
+ token.line !== commandToken.line &&
3580
+ isBodyCommandStart(ctx, prevForThen, token)) {
3581
+ break;
3582
+ }
3583
+ if (!isComment(token)) {
3584
+ prevForThen = token;
3585
+ }
3520
3586
  ctx.advance();
3521
3587
  }
3522
3588
  ctx.restorePosition(savedPosForThen);
@@ -3525,9 +3591,14 @@ function parseIfCommand(ctx, commandToken) {
3525
3591
  const savedPosition = ctx.savePosition();
3526
3592
  const ifStatementLine = commandToken.line;
3527
3593
  const maxLookahead = 100;
3594
+ let firstCommandOnIfLine = false;
3595
+ let prevToken = null;
3528
3596
  while (!ctx.isAtEnd() && ctx.current - savedPosition < maxLookahead) {
3529
3597
  const token = ctx.peek();
3530
3598
  const tokenValue = token.value?.toLowerCase();
3599
+ if (firstCommandOnIfLine && token.line !== undefined && token.line !== ifStatementLine) {
3600
+ break;
3601
+ }
3531
3602
  if (tokenValue === KEYWORDS$1.BEHAVIOR ||
3532
3603
  tokenValue === KEYWORDS$1.DEF ||
3533
3604
  tokenValue === KEYWORDS$1.ON) {
@@ -3539,11 +3610,15 @@ function parseIfCommand(ctx, commandToken) {
3539
3610
  }
3540
3611
  break;
3541
3612
  }
3542
- if (ctx.checkIsCommand() || ctx.isCommand(tokenValue)) {
3613
+ if (isBodyCommandStart(ctx, prevToken, token)) {
3543
3614
  if (token.line !== undefined && token.line !== ifStatementLine) {
3544
3615
  hasImplicitMultiLineEnd = true;
3545
3616
  break;
3546
3617
  }
3618
+ firstCommandOnIfLine = true;
3619
+ }
3620
+ if (!isComment(token)) {
3621
+ prevToken = token;
3547
3622
  }
3548
3623
  ctx.advance();
3549
3624
  }
@@ -3558,11 +3633,12 @@ function parseIfCommand(ctx, commandToken) {
3558
3633
  const conditionTokens = [];
3559
3634
  const maxIterations = 20;
3560
3635
  let iterations = 0;
3636
+ let firstConditionParse = true;
3561
3637
  while (!ctx.isAtEnd() &&
3562
- !ctx.checkIsCommand() &&
3563
- !ctx.isCommand(ctx.peek().value) &&
3564
3638
  !ctx.check(KEYWORDS$1.THEN) &&
3639
+ (firstConditionParse || (!ctx.checkIsCommand() && !ctx.isCommand(ctx.peek().value))) &&
3565
3640
  iterations < maxIterations) {
3641
+ firstConditionParse = false;
3566
3642
  const beforePos = ctx.savePosition();
3567
3643
  conditionTokens.push(ctx.parseLogicalAnd());
3568
3644
  if (ctx.savePosition() === beforePos) {
@@ -3589,22 +3665,8 @@ function parseIfCommand(ctx, commandToken) {
3589
3665
  }
3590
3666
  args.push(condition);
3591
3667
  if (isMultiLine) {
3592
- if (hasThen) {
3593
- ctx.advance();
3594
- }
3595
- const thenCommands = [];
3596
- while (!ctx.isAtEnd() && !ctx.check(KEYWORDS$1.ELSE) && !ctx.check(KEYWORDS$1.END)) {
3597
- if (ctx.checkIsCommand() || ctx.isCommand(ctx.peek().value)) {
3598
- ctx.advance();
3599
- const cmd = ctx.parseCommand();
3600
- if (cmd) {
3601
- thenCommands.push(cmd);
3602
- }
3603
- }
3604
- else {
3605
- break;
3606
- }
3607
- }
3668
+ consumeOptionalKeyword(ctx, KEYWORDS$1.THEN);
3669
+ const thenCommands = parseIfBranchCommands(ctx, true);
3608
3670
  if (thenCommands.length === 0 && ctx.isAtEnd()) {
3609
3671
  throw new Error("Expected command after 'then' in if statement - incomplete conditional");
3610
3672
  }
@@ -3630,19 +3692,7 @@ function parseIfCommand(ctx, commandToken) {
3630
3692
  consumedElseIf = true;
3631
3693
  }
3632
3694
  else {
3633
- const elseCommands = [];
3634
- while (!ctx.isAtEnd() && !ctx.check(KEYWORDS$1.END)) {
3635
- if (ctx.checkIsCommand() || ctx.isCommand(ctx.peek().value)) {
3636
- ctx.advance();
3637
- const cmd = ctx.parseCommand();
3638
- if (cmd) {
3639
- elseCommands.push(cmd);
3640
- }
3641
- }
3642
- else {
3643
- break;
3644
- }
3645
- }
3695
+ const elseCommands = parseIfBranchCommands(ctx, false);
3646
3696
  args.push(createBlock(elseCommands, {
3647
3697
  start: commandToken.start,
3648
3698
  end: ctx.getPosition().end,
@@ -4464,21 +4514,6 @@ const GO_KEYWORDS = new Set([
4464
4514
  'back',
4465
4515
  'forward',
4466
4516
  ]);
4467
- const GO_URL_STOP = new Set([
4468
- 'in',
4469
- 'then',
4470
- 'and',
4471
- 'else',
4472
- 'end',
4473
- 'otherwise',
4474
- 'when',
4475
- 'where',
4476
- 'catch',
4477
- 'finally',
4478
- ]);
4479
- function isGoURLTerminator(ctx) {
4480
- return ctx.isAtEnd() || GO_URL_STOP.has(ctx.peek().value);
4481
- }
4482
4517
  function stringNode(value, tok) {
4483
4518
  return {
4484
4519
  type: 'string',
@@ -4500,7 +4535,7 @@ function parseGoCommand(ctx, identifierNode) {
4500
4535
  continue;
4501
4536
  }
4502
4537
  if (isNakedURLStart(ctx)) {
4503
- const url = parseBareURLPath(ctx, isGoURLTerminator);
4538
+ const url = parseBareURLPath(ctx);
4504
4539
  if (url) {
4505
4540
  args.push(url);
4506
4541
  continue;
@@ -4640,13 +4675,6 @@ function parseMultiWordCommand(ctx, commandToken, commandName) {
4640
4675
  }
4641
4676
  return builder.build();
4642
4677
  }
4643
- function isFetchURLTerminator(ctx) {
4644
- if (ctx.isAtEnd())
4645
- return true;
4646
- const val = ctx.peek().value;
4647
- return (['as', 'with', '{', 'then', 'end', 'else', 'otherwise', ')'].includes(val) ||
4648
- isCommandBoundary(ctx));
4649
- }
4650
4678
  function isNakedURLStart(ctx) {
4651
4679
  const tok = ctx.peek();
4652
4680
  if (tok.value === '/')
@@ -4657,23 +4685,50 @@ function isNakedURLStart(ctx) {
4657
4685
  }
4658
4686
  return false;
4659
4687
  }
4660
- function parseBareURLPath(ctx, isTerminator = isFetchURLTerminator) {
4688
+ const URL_HARD_STOPS = new Set(['{', ')']);
4689
+ function parseBareURLPath(ctx) {
4661
4690
  const startPos = ctx.savePosition();
4691
+ const firstTok = ctx.peek();
4662
4692
  let path = '';
4663
- while (!ctx.isAtEnd() && !isTerminator(ctx)) {
4664
- path += ctx.advance().value;
4693
+ let prev = null;
4694
+ while (!ctx.isAtEnd()) {
4695
+ const tok = ctx.peek();
4696
+ if (prev && tok.start !== prev.end)
4697
+ break;
4698
+ if (tok.value === '$' && ctx.peekAt(1)?.value === '{') {
4699
+ ctx.advance();
4700
+ let last = ctx.advance();
4701
+ let depth = 1;
4702
+ while (!ctx.isAtEnd() && depth > 0) {
4703
+ last = ctx.advance();
4704
+ if (last.value === '{')
4705
+ depth++;
4706
+ else if (last.value === '}')
4707
+ depth--;
4708
+ }
4709
+ path += ctx.getInputSlice(tok.start, last.end);
4710
+ prev = last;
4711
+ continue;
4712
+ }
4713
+ if (URL_HARD_STOPS.has(tok.value))
4714
+ break;
4715
+ prev = ctx.advance();
4716
+ path += prev.value;
4665
4717
  }
4666
4718
  if (!path || path === '/') {
4667
4719
  ctx.restorePosition(startPos);
4668
4720
  return null;
4669
4721
  }
4670
- return {
4671
- type: 'literal',
4672
- value: path,
4673
- raw: path,
4674
- start: startPos,
4675
- end: ctx.savePosition(),
4722
+ const span = {
4723
+ start: firstTok.start,
4724
+ end: prev ? prev.end : firstTok.end,
4725
+ line: firstTok.line,
4726
+ column: firstTok.column,
4676
4727
  };
4728
+ if (path.includes('${')) {
4729
+ return { type: 'templateLiteral', value: path, ...span };
4730
+ }
4731
+ return { type: 'literal', value: path, raw: path, ...span };
4677
4732
  }
4678
4733
  function parseFetchCommand(ctx, commandToken) {
4679
4734
  const modifiers = {};
@@ -4904,10 +4959,10 @@ function parseTellCommand(ctx, identifierNode) {
4904
4959
  catch {
4905
4960
  break;
4906
4961
  }
4907
- if (ctx.match(KEYWORDS$1.AND)) {
4962
+ if (ctx.match(KEYWORDS$1.AND) || ctx.match(KEYWORDS$1.THEN)) {
4908
4963
  continue;
4909
4964
  }
4910
- if (ctx.check(KEYWORDS$1.THEN) || ctx.check(KEYWORDS$1.ELSE) || ctx.check(KEYWORDS$1.END)) {
4965
+ if (ctx.check(KEYWORDS$1.ELSE) || ctx.check(KEYWORDS$1.END)) {
4911
4966
  break;
4912
4967
  }
4913
4968
  if (ctx.checkIsCommand()) {
@@ -5110,6 +5165,7 @@ class Parser {
5110
5165
  const result = this.parseInternal();
5111
5166
  if (this.errors.length > 0) {
5112
5167
  result.errors = [...this.errors];
5168
+ result.recovered = true;
5113
5169
  }
5114
5170
  return result;
5115
5171
  }
@@ -7141,6 +7197,9 @@ class Parser {
7141
7197
  this.advance();
7142
7198
  const cmd = this.parseCommand();
7143
7199
  commands.push(cmd);
7200
+ if (this.match('then', 'and', ',')) {
7201
+ continue;
7202
+ }
7144
7203
  }
7145
7204
  else {
7146
7205
  break;
@@ -13047,6 +13106,25 @@ function isImplicitCommand(val) {
13047
13106
  return val !== null && typeof val === 'object' && 'command' in val && 'selector' in val;
13048
13107
  }
13049
13108
  const eventRecursionDepth = new WeakMap();
13109
+ function collectIdentifierNames(node, out = new Set()) {
13110
+ if (node === null || typeof node !== 'object')
13111
+ return out;
13112
+ if (Array.isArray(node)) {
13113
+ for (const item of node)
13114
+ collectIdentifierNames(item, out);
13115
+ return out;
13116
+ }
13117
+ const obj = node;
13118
+ if (obj.type === 'identifier' && typeof obj.name === 'string') {
13119
+ out.add(obj.name);
13120
+ }
13121
+ for (const key of Object.keys(obj)) {
13122
+ if (key === 'start' || key === 'end' || key === 'line' || key === 'column')
13123
+ continue;
13124
+ collectIdentifierNames(obj[key], out);
13125
+ }
13126
+ return out;
13127
+ }
13050
13128
  class RuntimeBase {
13051
13129
  constructor(options) {
13052
13130
  this.autoCleanupObserver = null;
@@ -13238,6 +13316,9 @@ class RuntimeBase {
13238
13316
  case 'behavior': {
13239
13317
  return await this.executeBehaviorDefinition(node, context);
13240
13318
  }
13319
+ case 'def': {
13320
+ return this.installFunction(node, context);
13321
+ }
13241
13322
  case 'Program': {
13242
13323
  return await this.executeProgram(node, context);
13243
13324
  }
@@ -13453,17 +13534,64 @@ class RuntimeBase {
13453
13534
  };
13454
13535
  return this.processCommand(commandNode, context);
13455
13536
  }
13537
+ installFunction(node, context) {
13538
+ const runtime = this;
13539
+ const params = node.params ?? [];
13540
+ const body = (node.body ?? []);
13541
+ const errorHandler = node.errorHandler;
13542
+ const finallyHandler = node.finallyHandler;
13543
+ const errorSymbol = node.errorSymbol;
13544
+ const fn = async (...args) => {
13545
+ const fnContext = {
13546
+ ...context,
13547
+ locals: new Map(context.locals),
13548
+ };
13549
+ params.forEach((name, i) => {
13550
+ if (name)
13551
+ fnContext.locals.set(name, args[i]);
13552
+ });
13553
+ const run = async (commands) => {
13554
+ const result = await runtime.executeCommandSequenceWithResult(commands, fnContext);
13555
+ if (!isOk(result))
13556
+ throw asControlFlowError(result.error);
13557
+ return result.value;
13558
+ };
13559
+ if (!errorHandler && !finallyHandler) {
13560
+ return await run(body);
13561
+ }
13562
+ try {
13563
+ return await run(body);
13564
+ }
13565
+ catch (e) {
13566
+ if (!errorHandler || isControlFlowError(e))
13567
+ throw e;
13568
+ if (errorSymbol)
13569
+ fnContext.locals.set(errorSymbol, e);
13570
+ return await run(errorHandler);
13571
+ }
13572
+ finally {
13573
+ if (finallyHandler) {
13574
+ await run(finallyHandler);
13575
+ }
13576
+ }
13577
+ };
13578
+ context.globals.set(node.name, fn);
13579
+ }
13456
13580
  async executeProgram(node, context) {
13457
13581
  if (!node.statements || !Array.isArray(node.statements))
13458
13582
  return;
13459
13583
  let lastResult = undefined;
13460
13584
  const eventHandlers = [];
13585
+ const defs = [];
13461
13586
  const initBlocks = [];
13462
13587
  const otherStatements = [];
13463
13588
  for (const statement of node.statements) {
13464
13589
  if (statement.type === 'eventHandler') {
13465
13590
  eventHandlers.push(statement);
13466
13591
  }
13592
+ else if (statement.type === 'def') {
13593
+ defs.push(statement);
13594
+ }
13467
13595
  else if (statement.type === 'initBlock') {
13468
13596
  initBlocks.push(statement);
13469
13597
  }
@@ -13471,6 +13599,9 @@ class RuntimeBase {
13471
13599
  otherStatements.push(statement);
13472
13600
  }
13473
13601
  }
13602
+ for (const def of defs) {
13603
+ this.execute(def, context);
13604
+ }
13474
13605
  for (const handler of eventHandlers) {
13475
13606
  try {
13476
13607
  await this.execute(handler, context);
@@ -13666,7 +13797,7 @@ class RuntimeBase {
13666
13797
  debug.runtime(`BEHAVIOR: Finished installing ${behaviorName}`);
13667
13798
  }
13668
13799
  async executeEventHandler(node, context) {
13669
- const { event, events, commands, target, args, selector, attributeName, watchTarget, modifiers, errorSymbol, errorHandler, finallyHandler, } = node;
13800
+ const { event, events, commands, target, args, selector, condition, attributeName, watchTarget, modifiers, errorSymbol, errorHandler, finallyHandler, } = node;
13670
13801
  const eventNames = events && events.length > 0 ? events : [event];
13671
13802
  debug.runtime(`BEHAVIOR: executeEventHandler: event='${event}', target='${target}'`);
13672
13803
  let targets = [];
@@ -13749,7 +13880,8 @@ class RuntimeBase {
13749
13880
  }
13750
13881
  return;
13751
13882
  }
13752
- const baseEventHandler = RuntimeBase.createEventHandler(this, commands, context, selector, args, { errorSymbol, errorHandler, finallyHandler });
13883
+ const applicableCondition = eventNames.length === 1 ? condition : undefined;
13884
+ const baseEventHandler = RuntimeBase.createEventHandler(this, commands, context, selector, args, { errorSymbol, errorHandler, finallyHandler }, applicableCondition);
13753
13885
  let eventHandler;
13754
13886
  let debounceCleanup = null;
13755
13887
  if (modifiers) {
@@ -13853,7 +13985,7 @@ class RuntimeBase {
13853
13985
  }
13854
13986
  }
13855
13987
  }
13856
- static createEventHandler(runtime, commands, context, selector, args, errorBlocks) {
13988
+ static createEventHandler(runtime, commands, context, selector, args, errorBlocks, condition) {
13857
13989
  return async (domEvent) => {
13858
13990
  const currentDepth = eventRecursionDepth.get(domEvent) ?? 0;
13859
13991
  if (currentDepth >= 100) {
@@ -13889,6 +14021,25 @@ class RuntimeBase {
13889
14021
  eventContext.locals.set(argName, value);
13890
14022
  }
13891
14023
  }
14024
+ if (condition) {
14025
+ const eventProps = domEvent;
14026
+ for (const name of collectIdentifierNames(condition)) {
14027
+ if (!eventContext.locals.has(name) && name in eventProps) {
14028
+ eventContext.locals.set(name, eventProps[name]);
14029
+ }
14030
+ }
14031
+ let filterResult;
14032
+ try {
14033
+ filterResult = await runtime.evaluateExpression(condition, eventContext);
14034
+ }
14035
+ catch (e) {
14036
+ debug.runtime(`Event filter threw; skipping handler body:`, e);
14037
+ return;
14038
+ }
14039
+ if (!filterResult) {
14040
+ return;
14041
+ }
14042
+ }
13892
14043
  const runCommands = async (toRun) => {
13893
14044
  for (const command of toRun) {
13894
14045
  try {
@@ -80807,9 +80958,11 @@ function compileSync(code, options) {
80807
80958
  const parseResult = parse$1(code, parserOptions);
80808
80959
  const timeMs = performance.now() - startTime;
80809
80960
  if (parseResult.success && parseResult.node) {
80961
+ const recovered = parseResult.errors ?? [];
80810
80962
  const result = {
80811
80963
  ok: true,
80812
80964
  ast: parseResult.node,
80965
+ ...(recovered.length > 0 ? { errors: recovered.map(toCompileError) } : {}),
80813
80966
  meta: {
80814
80967
  parser: usesSemanticParser ? 'semantic' : 'traditional',
80815
80968
  language: lang,
@@ -80935,9 +81088,10 @@ async function evalCode(code, context, options) {
80935
81088
  }
80936
81089
  async function validate(code, options) {
80937
81090
  const result = await compileAsync(code, options);
81091
+ const errors = result.errors ?? [];
80938
81092
  return {
80939
- valid: result.ok,
80940
- errors: result.errors,
81093
+ valid: result.ok && errors.length === 0,
81094
+ errors: errors.length > 0 ? errors : undefined,
80941
81095
  };
80942
81096
  }
80943
81097
  function createContextWithParent(element, parent) {