@hyperfixi/core 2.6.0 → 2.7.1

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 (54) hide show
  1. package/dist/api/hyperscript-api.d.ts +6 -0
  2. package/dist/ast-utils/index.js +8 -8
  3. package/dist/ast-utils/index.mjs +8 -8
  4. package/dist/bundle-generator/index.js +12 -4
  5. package/dist/bundle-generator/index.mjs +12 -4
  6. package/dist/chunks/{bridge-fFLEvCQs.js → bridge-lZbOVRDD.js} +2 -2
  7. package/dist/chunks/browser-modular-DegkWQ8d.js +2 -0
  8. package/dist/chunks/{index-BRgVy8nY.js → index-i_j9Z-1e.js} +2 -2
  9. package/dist/commands/data/set.d.ts +1 -0
  10. package/dist/commands/index.js +156 -47
  11. package/dist/commands/index.mjs +156 -47
  12. package/dist/core/context.d.ts +4 -0
  13. package/dist/dom/attribute-processor.d.ts +1 -0
  14. package/dist/hyperfixi-browser-classic-i18n.js +1 -1
  15. package/dist/hyperfixi-browser-minimal.js +1 -1
  16. package/dist/hyperfixi-browser-standard.js +1 -1
  17. package/dist/hyperfixi-browser.js +1 -1
  18. package/dist/hyperfixi-classic-i18n.js +1 -1
  19. package/dist/hyperfixi-hx-v4.js +1 -1
  20. package/dist/hyperfixi-hx.js +1 -1
  21. package/dist/hyperfixi-hybrid-complete.js +1 -1
  22. package/dist/hyperfixi-hybrid-hx.js +1 -1
  23. package/dist/hyperfixi-lite-plus.js +1 -1
  24. package/dist/hyperfixi-lite.js +1 -1
  25. package/dist/hyperfixi-minimal.js +1 -1
  26. package/dist/hyperfixi-multilingual.js +1 -1
  27. package/dist/hyperfixi-standard.js +1 -1
  28. package/dist/hyperfixi.js +1 -1
  29. package/dist/hyperfixi.mjs +1 -1
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.js +694 -459
  32. package/dist/index.min.js +1 -1
  33. package/dist/index.mjs +691 -460
  34. package/dist/lokascript-browser-classic-i18n.js +1 -1
  35. package/dist/lokascript-browser-minimal.js +1 -1
  36. package/dist/lokascript-browser-standard.js +1 -1
  37. package/dist/lokascript-browser.js +1 -1
  38. package/dist/lokascript-hybrid-complete.js +1 -1
  39. package/dist/lokascript-hybrid-hx.js +1 -1
  40. package/dist/lokascript-lite-plus.js +1 -1
  41. package/dist/lokascript-lite.js +1 -1
  42. package/dist/lokascript-multilingual.js +1 -1
  43. package/dist/parser/full-parser.js +70 -35
  44. package/dist/parser/full-parser.mjs +70 -35
  45. package/dist/parser/parser-types.d.ts +1 -1
  46. package/dist/parser/parser.d.ts +2 -0
  47. package/dist/parser/regex-parser.js +23 -14
  48. package/dist/parser/regex-parser.mjs +23 -14
  49. package/dist/registry/index.js +178 -45
  50. package/dist/registry/index.mjs +178 -45
  51. package/dist/types/core-context.d.ts +1 -0
  52. package/package.json +6 -6
  53. package/dist/chunks/browser-modular-BK3IOo93.js +0 -2
  54. package/dist/compatibility/browser-bundle.d.ts +0 -164
@@ -4007,7 +4007,7 @@ function parseColonVariable(ctx) {
4007
4007
  return {
4008
4008
  type: 'identifier',
4009
4009
  name: varToken.value,
4010
- scope: 'local',
4010
+ scope: 'element',
4011
4011
  start: varToken.start - 1,
4012
4012
  end: varToken.end,
4013
4013
  };
@@ -4059,7 +4059,7 @@ function parseTargetFallback(ctx) {
4059
4059
  expression: {
4060
4060
  type: 'identifier',
4061
4061
  name: varToken.value,
4062
- scope: 'local',
4062
+ scope: 'element',
4063
4063
  start: varToken.start,
4064
4064
  end: varToken.end,
4065
4065
  },
@@ -4140,6 +4140,11 @@ function parseSetCommand(ctx, identifierNode) {
4140
4140
  }
4141
4141
  ctx.advance();
4142
4142
  const value = ctx.parseExpression() ?? ctx.parsePrimary();
4143
+ let scopeTarget;
4144
+ if (ctx.check(KEYWORDS.ON) && tokenLooksLikeElementTarget(ctx.peekAt(1))) {
4145
+ ctx.advance();
4146
+ scopeTarget = parseOneArgument(ctx);
4147
+ }
4143
4148
  const finalArgs = [];
4144
4149
  if (targetExpression) {
4145
4150
  finalArgs.push(targetExpression);
@@ -4151,11 +4156,22 @@ function parseSetCommand(ctx, identifierNode) {
4151
4156
  if (value) {
4152
4157
  finalArgs.push(value);
4153
4158
  }
4154
- return CommandNodeBuilder.fromIdentifier(identifierNode)
4155
- .withArgs(...finalArgs)
4156
- .endingAt(ctx.getPosition())
4157
- .build();
4159
+ const builder = CommandNodeBuilder.fromIdentifier(identifierNode).withArgs(...finalArgs);
4160
+ if (scopeTarget) {
4161
+ builder.withModifier('on', scopeTarget);
4162
+ }
4163
+ return builder.endingAt(ctx.getPosition()).build();
4158
4164
  }
4165
+ function tokenLooksLikeElementTarget(token) {
4166
+ if (!token)
4167
+ return false;
4168
+ if (token.kind === 'selector')
4169
+ return true;
4170
+ if (token.value.startsWith('<'))
4171
+ return true;
4172
+ return ELEMENT_PRONOUNS.has(token.value);
4173
+ }
4174
+ const ELEMENT_PRONOUNS = new Set(['me', 'my', 'it', 'its', 'you', 'your', 'yourself']);
4159
4175
  function parseIncrementDecrementCommand(ctx, commandToken) {
4160
4176
  const commandName = commandToken.value;
4161
4177
  const isIncrement = commandName === 'increment';
@@ -4201,6 +4217,7 @@ function parseIncrementDecrementCommand(ctx, commandToken) {
4201
4217
  column: commandToken.column,
4202
4218
  };
4203
4219
  const binaryExpr = createBinaryExpression(operator, target, amount, pos);
4220
+ binaryExpr.coerceNumeric = true;
4204
4221
  const toIdentifier = createIdentifier(KEYWORDS.TO, pos);
4205
4222
  const args = [targetWithScope, toIdentifier, binaryExpr];
4206
4223
  return CommandNodeBuilder.from(commandToken)
@@ -4819,6 +4836,18 @@ class Parser {
4819
4836
  this.advance();
4820
4837
  continue;
4821
4838
  }
4839
+ if (this.check('then')) {
4840
+ const next = this.peekAt(1);
4841
+ const startsFeature = next !== null &&
4842
+ (next.value === 'init' ||
4843
+ next.value === 'on' ||
4844
+ next.value === 'def' ||
4845
+ getRegisteredFeature(next.value) !== undefined);
4846
+ if (startsFeature) {
4847
+ this.advance();
4848
+ continue;
4849
+ }
4850
+ }
4822
4851
  if (this.check('init')) {
4823
4852
  this.advance();
4824
4853
  const initBlock = this.parseTopLevelInitBlock();
@@ -5619,7 +5648,7 @@ class Parser {
5619
5648
  return {
5620
5649
  type: 'identifier',
5621
5650
  name: varToken.value,
5622
- scope: 'local',
5651
+ scope: 'element',
5623
5652
  start: varToken.start - 1,
5624
5653
  end: varToken.end,
5625
5654
  line: varToken.line,
@@ -6246,6 +6275,33 @@ class Parser {
6246
6275
  return null;
6247
6276
  }
6248
6277
  }
6278
+ parseTemporalKeywordModifiers(modifiers) {
6279
+ if (!this.check('debounced') && !this.check('throttled')) {
6280
+ return;
6281
+ }
6282
+ const modToken = this.advance();
6283
+ const modName = modToken.value.toLowerCase();
6284
+ const modifierKey = modName === 'debounced' ? 'debounce' : 'throttle';
6285
+ if (this.match('at')) {
6286
+ if (this.checkTimeExpression()) {
6287
+ const timeToken = this.advance();
6288
+ const delayMs = parseTimeToMs(timeToken.value);
6289
+ modifiers[modifierKey] = delayMs;
6290
+ debug.parse(`🔧 parseEventHandler: Parsed '${modName} at ${timeToken.value}' (${delayMs}ms)`);
6291
+ }
6292
+ else if (this.matchNumber()) {
6293
+ const delayMs = parseInt(this.previous().value, 10);
6294
+ modifiers[modifierKey] = delayMs;
6295
+ debug.parse(`🔧 parseEventHandler: Parsed '${modName} at ${delayMs}'`);
6296
+ }
6297
+ else {
6298
+ this.addError(`Expected time expression after '${modName} at', got: ${this.peek().value}`);
6299
+ }
6300
+ }
6301
+ else {
6302
+ this.addError(`Expected 'at' after '${modName}'`);
6303
+ }
6304
+ }
6249
6305
  parseEventHandler() {
6250
6306
  debug.parse(`🔧 parseEventHandler: ENTRY - parsing event handler`);
6251
6307
  const eventNames = [];
@@ -6333,30 +6389,7 @@ class Parser {
6333
6389
  debug.parse(`🔧 parseEventHandler: Warning - unknown modifier '.${modName}'`);
6334
6390
  }
6335
6391
  }
6336
- if (this.check('debounced') || this.check('throttled')) {
6337
- const modToken = this.advance();
6338
- const modName = modToken.value.toLowerCase();
6339
- const modifierKey = modName === 'debounced' ? 'debounce' : 'throttle';
6340
- if (this.match('at')) {
6341
- if (this.checkTimeExpression()) {
6342
- const timeToken = this.advance();
6343
- const delayMs = parseTimeToMs(timeToken.value);
6344
- modifiers[modifierKey] = delayMs;
6345
- debug.parse(`🔧 parseEventHandler: Parsed '${modName} at ${timeToken.value}' (${delayMs}ms)`);
6346
- }
6347
- else if (this.matchNumber()) {
6348
- const delayMs = parseInt(this.previous().value, 10);
6349
- modifiers[modifierKey] = delayMs;
6350
- debug.parse(`🔧 parseEventHandler: Parsed '${modName} at ${delayMs}'`);
6351
- }
6352
- else {
6353
- this.addError(`Expected time expression after '${modName} at', got: ${this.peek().value}`);
6354
- }
6355
- }
6356
- else {
6357
- this.addError(`Expected 'at' after '${modName}'`);
6358
- }
6359
- }
6392
+ this.parseTemporalKeywordModifiers(modifiers);
6360
6393
  if (firstOnceAlias) {
6361
6394
  modifiers.once = true;
6362
6395
  }
@@ -6373,6 +6406,7 @@ class Parser {
6373
6406
  const targetToken = this.advance();
6374
6407
  target = targetToken.value;
6375
6408
  debug.parse(`🔧 parseEventHandler: Parsed 'from' target: ${target} (kind: ${targetToken.kind})`);
6409
+ this.parseTemporalKeywordModifiers(modifiers);
6376
6410
  }
6377
6411
  let attributeName;
6378
6412
  if (this.match('of')) {
@@ -7343,6 +7377,10 @@ class Parser {
7343
7377
  }
7344
7378
  return this.tokens[this.current];
7345
7379
  }
7380
+ peekAt(offset) {
7381
+ const index = this.current + offset;
7382
+ return index >= 0 && index < this.tokens.length ? this.tokens[index] : null;
7383
+ }
7346
7384
  previous() {
7347
7385
  return this.tokens[this.current - 1];
7348
7386
  }
@@ -7547,10 +7585,7 @@ class Parser {
7547
7585
  restorePosition: (pos) => {
7548
7586
  parser.current = pos;
7549
7587
  },
7550
- peekAt: (offset) => {
7551
- const index = parser.current + offset;
7552
- return index >= 0 && index < parser.tokens.length ? parser.tokens[index] : null;
7553
- },
7588
+ peekAt: (offset) => parser.peekAt(offset),
7554
7589
  getInputSlice: (start, end) => {
7555
7590
  if (!this.originalInput)
7556
7591
  return '';
@@ -4005,7 +4005,7 @@ function parseColonVariable(ctx) {
4005
4005
  return {
4006
4006
  type: 'identifier',
4007
4007
  name: varToken.value,
4008
- scope: 'local',
4008
+ scope: 'element',
4009
4009
  start: varToken.start - 1,
4010
4010
  end: varToken.end,
4011
4011
  };
@@ -4057,7 +4057,7 @@ function parseTargetFallback(ctx) {
4057
4057
  expression: {
4058
4058
  type: 'identifier',
4059
4059
  name: varToken.value,
4060
- scope: 'local',
4060
+ scope: 'element',
4061
4061
  start: varToken.start,
4062
4062
  end: varToken.end,
4063
4063
  },
@@ -4138,6 +4138,11 @@ function parseSetCommand(ctx, identifierNode) {
4138
4138
  }
4139
4139
  ctx.advance();
4140
4140
  const value = ctx.parseExpression() ?? ctx.parsePrimary();
4141
+ let scopeTarget;
4142
+ if (ctx.check(KEYWORDS.ON) && tokenLooksLikeElementTarget(ctx.peekAt(1))) {
4143
+ ctx.advance();
4144
+ scopeTarget = parseOneArgument(ctx);
4145
+ }
4141
4146
  const finalArgs = [];
4142
4147
  if (targetExpression) {
4143
4148
  finalArgs.push(targetExpression);
@@ -4149,11 +4154,22 @@ function parseSetCommand(ctx, identifierNode) {
4149
4154
  if (value) {
4150
4155
  finalArgs.push(value);
4151
4156
  }
4152
- return CommandNodeBuilder.fromIdentifier(identifierNode)
4153
- .withArgs(...finalArgs)
4154
- .endingAt(ctx.getPosition())
4155
- .build();
4157
+ const builder = CommandNodeBuilder.fromIdentifier(identifierNode).withArgs(...finalArgs);
4158
+ if (scopeTarget) {
4159
+ builder.withModifier('on', scopeTarget);
4160
+ }
4161
+ return builder.endingAt(ctx.getPosition()).build();
4156
4162
  }
4163
+ function tokenLooksLikeElementTarget(token) {
4164
+ if (!token)
4165
+ return false;
4166
+ if (token.kind === 'selector')
4167
+ return true;
4168
+ if (token.value.startsWith('<'))
4169
+ return true;
4170
+ return ELEMENT_PRONOUNS.has(token.value);
4171
+ }
4172
+ const ELEMENT_PRONOUNS = new Set(['me', 'my', 'it', 'its', 'you', 'your', 'yourself']);
4157
4173
  function parseIncrementDecrementCommand(ctx, commandToken) {
4158
4174
  const commandName = commandToken.value;
4159
4175
  const isIncrement = commandName === 'increment';
@@ -4199,6 +4215,7 @@ function parseIncrementDecrementCommand(ctx, commandToken) {
4199
4215
  column: commandToken.column,
4200
4216
  };
4201
4217
  const binaryExpr = createBinaryExpression(operator, target, amount, pos);
4218
+ binaryExpr.coerceNumeric = true;
4202
4219
  const toIdentifier = createIdentifier(KEYWORDS.TO, pos);
4203
4220
  const args = [targetWithScope, toIdentifier, binaryExpr];
4204
4221
  return CommandNodeBuilder.from(commandToken)
@@ -4817,6 +4834,18 @@ class Parser {
4817
4834
  this.advance();
4818
4835
  continue;
4819
4836
  }
4837
+ if (this.check('then')) {
4838
+ const next = this.peekAt(1);
4839
+ const startsFeature = next !== null &&
4840
+ (next.value === 'init' ||
4841
+ next.value === 'on' ||
4842
+ next.value === 'def' ||
4843
+ getRegisteredFeature(next.value) !== undefined);
4844
+ if (startsFeature) {
4845
+ this.advance();
4846
+ continue;
4847
+ }
4848
+ }
4820
4849
  if (this.check('init')) {
4821
4850
  this.advance();
4822
4851
  const initBlock = this.parseTopLevelInitBlock();
@@ -5617,7 +5646,7 @@ class Parser {
5617
5646
  return {
5618
5647
  type: 'identifier',
5619
5648
  name: varToken.value,
5620
- scope: 'local',
5649
+ scope: 'element',
5621
5650
  start: varToken.start - 1,
5622
5651
  end: varToken.end,
5623
5652
  line: varToken.line,
@@ -6244,6 +6273,33 @@ class Parser {
6244
6273
  return null;
6245
6274
  }
6246
6275
  }
6276
+ parseTemporalKeywordModifiers(modifiers) {
6277
+ if (!this.check('debounced') && !this.check('throttled')) {
6278
+ return;
6279
+ }
6280
+ const modToken = this.advance();
6281
+ const modName = modToken.value.toLowerCase();
6282
+ const modifierKey = modName === 'debounced' ? 'debounce' : 'throttle';
6283
+ if (this.match('at')) {
6284
+ if (this.checkTimeExpression()) {
6285
+ const timeToken = this.advance();
6286
+ const delayMs = parseTimeToMs(timeToken.value);
6287
+ modifiers[modifierKey] = delayMs;
6288
+ debug.parse(`🔧 parseEventHandler: Parsed '${modName} at ${timeToken.value}' (${delayMs}ms)`);
6289
+ }
6290
+ else if (this.matchNumber()) {
6291
+ const delayMs = parseInt(this.previous().value, 10);
6292
+ modifiers[modifierKey] = delayMs;
6293
+ debug.parse(`🔧 parseEventHandler: Parsed '${modName} at ${delayMs}'`);
6294
+ }
6295
+ else {
6296
+ this.addError(`Expected time expression after '${modName} at', got: ${this.peek().value}`);
6297
+ }
6298
+ }
6299
+ else {
6300
+ this.addError(`Expected 'at' after '${modName}'`);
6301
+ }
6302
+ }
6247
6303
  parseEventHandler() {
6248
6304
  debug.parse(`🔧 parseEventHandler: ENTRY - parsing event handler`);
6249
6305
  const eventNames = [];
@@ -6331,30 +6387,7 @@ class Parser {
6331
6387
  debug.parse(`🔧 parseEventHandler: Warning - unknown modifier '.${modName}'`);
6332
6388
  }
6333
6389
  }
6334
- if (this.check('debounced') || this.check('throttled')) {
6335
- const modToken = this.advance();
6336
- const modName = modToken.value.toLowerCase();
6337
- const modifierKey = modName === 'debounced' ? 'debounce' : 'throttle';
6338
- if (this.match('at')) {
6339
- if (this.checkTimeExpression()) {
6340
- const timeToken = this.advance();
6341
- const delayMs = parseTimeToMs(timeToken.value);
6342
- modifiers[modifierKey] = delayMs;
6343
- debug.parse(`🔧 parseEventHandler: Parsed '${modName} at ${timeToken.value}' (${delayMs}ms)`);
6344
- }
6345
- else if (this.matchNumber()) {
6346
- const delayMs = parseInt(this.previous().value, 10);
6347
- modifiers[modifierKey] = delayMs;
6348
- debug.parse(`🔧 parseEventHandler: Parsed '${modName} at ${delayMs}'`);
6349
- }
6350
- else {
6351
- this.addError(`Expected time expression after '${modName} at', got: ${this.peek().value}`);
6352
- }
6353
- }
6354
- else {
6355
- this.addError(`Expected 'at' after '${modName}'`);
6356
- }
6357
- }
6390
+ this.parseTemporalKeywordModifiers(modifiers);
6358
6391
  if (firstOnceAlias) {
6359
6392
  modifiers.once = true;
6360
6393
  }
@@ -6371,6 +6404,7 @@ class Parser {
6371
6404
  const targetToken = this.advance();
6372
6405
  target = targetToken.value;
6373
6406
  debug.parse(`🔧 parseEventHandler: Parsed 'from' target: ${target} (kind: ${targetToken.kind})`);
6407
+ this.parseTemporalKeywordModifiers(modifiers);
6374
6408
  }
6375
6409
  let attributeName;
6376
6410
  if (this.match('of')) {
@@ -7341,6 +7375,10 @@ class Parser {
7341
7375
  }
7342
7376
  return this.tokens[this.current];
7343
7377
  }
7378
+ peekAt(offset) {
7379
+ const index = this.current + offset;
7380
+ return index >= 0 && index < this.tokens.length ? this.tokens[index] : null;
7381
+ }
7344
7382
  previous() {
7345
7383
  return this.tokens[this.current - 1];
7346
7384
  }
@@ -7545,10 +7583,7 @@ class Parser {
7545
7583
  restorePosition: (pos) => {
7546
7584
  parser.current = pos;
7547
7585
  },
7548
- peekAt: (offset) => {
7549
- const index = parser.current + offset;
7550
- return index >= 0 && index < parser.tokens.length ? parser.tokens[index] : null;
7551
- },
7586
+ peekAt: (offset) => parser.peekAt(offset),
7552
7587
  getInputSlice: (start, end) => {
7553
7588
  if (!this.originalInput)
7554
7589
  return '';
@@ -8,7 +8,7 @@ export interface Position {
8
8
  export interface IdentifierNode extends ASTNode {
9
9
  type: 'identifier';
10
10
  name: string;
11
- scope?: 'local' | 'global';
11
+ scope?: 'local' | 'element' | 'global';
12
12
  }
13
13
  export interface LiteralNode extends ASTNode {
14
14
  type: 'literal';
@@ -67,6 +67,7 @@ export declare class Parser {
67
67
  private createPseudoCommandNode;
68
68
  private tryParsePseudoCommand;
69
69
  private parseCommandWithErrorRecovery;
70
+ private parseTemporalKeywordModifiers;
70
71
  private parseEventHandler;
71
72
  private parseBehaviorDefinition;
72
73
  private parseCommandSequence;
@@ -143,6 +144,7 @@ export declare class Parser {
143
144
  private advance;
144
145
  private isAtEnd;
145
146
  private peek;
147
+ private peekAt;
146
148
  private previous;
147
149
  private consume;
148
150
  private addError;
@@ -95,6 +95,15 @@ function parseCommand(code) {
95
95
  }
96
96
  return null;
97
97
  }
98
+ const elementVars = new WeakMap();
99
+ function elemScope(el) {
100
+ let m = elementVars.get(el);
101
+ if (!m) {
102
+ m = new Map();
103
+ elementVars.set(el, m);
104
+ }
105
+ return m;
106
+ }
98
107
  function resolveTarget(target, me) {
99
108
  if (!target)
100
109
  return me;
@@ -148,7 +157,7 @@ async function executeCommand(cmd, me, locals) {
148
157
  return null;
149
158
  }
150
159
  case 'put': {
151
- const content = evaluateValue(cmd.args[0], me, locals);
160
+ const content = evaluateValue(cmd.args[0], me);
152
161
  for (const el of elements) {
153
162
  switch (cmd.modifier?.toLowerCase()) {
154
163
  case 'into':
@@ -168,9 +177,9 @@ async function executeCommand(cmd, me, locals) {
168
177
  }
169
178
  case 'set': {
170
179
  const [targetExpr, valueExpr] = cmd.args;
171
- const value = evaluateValue(valueExpr, me, locals);
180
+ const value = evaluateValue(valueExpr, me);
172
181
  if (targetExpr.startsWith(':')) {
173
- locals.set(targetExpr.slice(1), value);
182
+ elemScope(me).set(targetExpr.slice(1), value);
174
183
  return value;
175
184
  }
176
185
  const propMatch = targetExpr.match(/^(.+?)(?:'s|\.)\s*(\w+)$/);
@@ -184,7 +193,7 @@ async function executeCommand(cmd, me, locals) {
184
193
  return value;
185
194
  }
186
195
  case 'log': {
187
- const value = evaluateValue(cmd.args.join(' '), me, locals);
196
+ const value = evaluateValue(cmd.args.join(' '), me);
188
197
  console.log(value);
189
198
  return value;
190
199
  }
@@ -223,7 +232,7 @@ function evaluateValue(expr, me, locals) {
223
232
  if (e === 'null')
224
233
  return null;
225
234
  if (e.startsWith(':')) {
226
- return locals.get(e.slice(1));
235
+ return elemScope(me).get(e.slice(1));
227
236
  }
228
237
  if (e === 'me')
229
238
  return me;
@@ -242,13 +251,13 @@ async function executeParsed(parsed, me, locals = new Map()) {
242
251
  let result;
243
252
  for (const cmd of parsed) {
244
253
  if (cmd.modifier === 'if' || cmd.modifier === 'unless') {
245
- const condition = evaluateCondition(cmd.args[0], me, locals);
254
+ const condition = evaluateCondition(cmd.args[0], me);
246
255
  const shouldRun = cmd.modifier === 'if' ? condition : !condition;
247
256
  if (!shouldRun)
248
257
  continue;
249
258
  cmd.args.shift();
250
259
  }
251
- result = await executeCommand(cmd, me, locals);
260
+ result = await executeCommand(cmd, me);
252
261
  }
253
262
  return result;
254
263
  }
@@ -258,17 +267,17 @@ async function executeParsed(parsed, me, locals = new Map()) {
258
267
  if (eventName.startsWith('interval:')) {
259
268
  const ms = parseInt(eventName.split(':')[1]);
260
269
  setInterval(async () => {
261
- const handlerLocals = new Map(locals);
270
+ new Map(locals);
262
271
  for (const cmd of handler.commands) {
263
- await executeCommand(cmd, me, handlerLocals);
272
+ await executeCommand(cmd, me);
264
273
  }
265
274
  }, ms);
266
275
  return me;
267
276
  }
268
277
  if (eventName === 'init') {
269
- const handlerLocals = new Map(locals);
278
+ new Map(locals);
270
279
  for (const cmd of handler.commands) {
271
- await executeCommand(cmd, me, handlerLocals);
280
+ await executeCommand(cmd, me);
272
281
  }
273
282
  return me;
274
283
  }
@@ -277,7 +286,7 @@ async function executeParsed(parsed, me, locals = new Map()) {
277
286
  const handlerLocals = new Map(locals);
278
287
  handlerLocals.set('event', event);
279
288
  for (const cmd of handler.commands) {
280
- await executeCommand(cmd, me, handlerLocals);
289
+ await executeCommand(cmd, me);
281
290
  }
282
291
  });
283
292
  }
@@ -294,9 +303,9 @@ function evaluateCondition(expr, me, locals) {
294
303
  return Boolean(me[propMatch[2]]);
295
304
  }
296
305
  if (e.startsWith(':')) {
297
- return Boolean(locals.get(e.slice(1)));
306
+ return Boolean(elemScope(me).get(e.slice(1)));
298
307
  }
299
- const value = evaluateValue(e, me, locals);
308
+ const value = evaluateValue(e, me);
300
309
  return Boolean(value);
301
310
  }
302
311
  function processElements(root = document) {
@@ -93,6 +93,15 @@ function parseCommand(code) {
93
93
  }
94
94
  return null;
95
95
  }
96
+ const elementVars = new WeakMap();
97
+ function elemScope(el) {
98
+ let m = elementVars.get(el);
99
+ if (!m) {
100
+ m = new Map();
101
+ elementVars.set(el, m);
102
+ }
103
+ return m;
104
+ }
96
105
  function resolveTarget(target, me) {
97
106
  if (!target)
98
107
  return me;
@@ -146,7 +155,7 @@ async function executeCommand(cmd, me, locals) {
146
155
  return null;
147
156
  }
148
157
  case 'put': {
149
- const content = evaluateValue(cmd.args[0], me, locals);
158
+ const content = evaluateValue(cmd.args[0], me);
150
159
  for (const el of elements) {
151
160
  switch (cmd.modifier?.toLowerCase()) {
152
161
  case 'into':
@@ -166,9 +175,9 @@ async function executeCommand(cmd, me, locals) {
166
175
  }
167
176
  case 'set': {
168
177
  const [targetExpr, valueExpr] = cmd.args;
169
- const value = evaluateValue(valueExpr, me, locals);
178
+ const value = evaluateValue(valueExpr, me);
170
179
  if (targetExpr.startsWith(':')) {
171
- locals.set(targetExpr.slice(1), value);
180
+ elemScope(me).set(targetExpr.slice(1), value);
172
181
  return value;
173
182
  }
174
183
  const propMatch = targetExpr.match(/^(.+?)(?:'s|\.)\s*(\w+)$/);
@@ -182,7 +191,7 @@ async function executeCommand(cmd, me, locals) {
182
191
  return value;
183
192
  }
184
193
  case 'log': {
185
- const value = evaluateValue(cmd.args.join(' '), me, locals);
194
+ const value = evaluateValue(cmd.args.join(' '), me);
186
195
  console.log(value);
187
196
  return value;
188
197
  }
@@ -221,7 +230,7 @@ function evaluateValue(expr, me, locals) {
221
230
  if (e === 'null')
222
231
  return null;
223
232
  if (e.startsWith(':')) {
224
- return locals.get(e.slice(1));
233
+ return elemScope(me).get(e.slice(1));
225
234
  }
226
235
  if (e === 'me')
227
236
  return me;
@@ -240,13 +249,13 @@ async function executeParsed(parsed, me, locals = new Map()) {
240
249
  let result;
241
250
  for (const cmd of parsed) {
242
251
  if (cmd.modifier === 'if' || cmd.modifier === 'unless') {
243
- const condition = evaluateCondition(cmd.args[0], me, locals);
252
+ const condition = evaluateCondition(cmd.args[0], me);
244
253
  const shouldRun = cmd.modifier === 'if' ? condition : !condition;
245
254
  if (!shouldRun)
246
255
  continue;
247
256
  cmd.args.shift();
248
257
  }
249
- result = await executeCommand(cmd, me, locals);
258
+ result = await executeCommand(cmd, me);
250
259
  }
251
260
  return result;
252
261
  }
@@ -256,17 +265,17 @@ async function executeParsed(parsed, me, locals = new Map()) {
256
265
  if (eventName.startsWith('interval:')) {
257
266
  const ms = parseInt(eventName.split(':')[1]);
258
267
  setInterval(async () => {
259
- const handlerLocals = new Map(locals);
268
+ new Map(locals);
260
269
  for (const cmd of handler.commands) {
261
- await executeCommand(cmd, me, handlerLocals);
270
+ await executeCommand(cmd, me);
262
271
  }
263
272
  }, ms);
264
273
  return me;
265
274
  }
266
275
  if (eventName === 'init') {
267
- const handlerLocals = new Map(locals);
276
+ new Map(locals);
268
277
  for (const cmd of handler.commands) {
269
- await executeCommand(cmd, me, handlerLocals);
278
+ await executeCommand(cmd, me);
270
279
  }
271
280
  return me;
272
281
  }
@@ -275,7 +284,7 @@ async function executeParsed(parsed, me, locals = new Map()) {
275
284
  const handlerLocals = new Map(locals);
276
285
  handlerLocals.set('event', event);
277
286
  for (const cmd of handler.commands) {
278
- await executeCommand(cmd, me, handlerLocals);
287
+ await executeCommand(cmd, me);
279
288
  }
280
289
  });
281
290
  }
@@ -292,9 +301,9 @@ function evaluateCondition(expr, me, locals) {
292
301
  return Boolean(me[propMatch[2]]);
293
302
  }
294
303
  if (e.startsWith(':')) {
295
- return Boolean(locals.get(e.slice(1)));
304
+ return Boolean(elemScope(me).get(e.slice(1)));
296
305
  }
297
- const value = evaluateValue(e, me, locals);
306
+ const value = evaluateValue(e, me);
298
307
  return Boolean(value);
299
308
  }
300
309
  function processElements(root = document) {