@hyperfixi/core 2.2.0 → 2.3.0

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.mjs CHANGED
@@ -453,7 +453,10 @@ function tokenize$1(input) {
453
453
  prevToken.value === '{' ||
454
454
  prevToken.value === ',' ||
455
455
  prevToken.value === ';';
456
- if (isCSSSelectorContext && isAlpha(peek(tokenizer))) {
456
+ const isAdjacentToPrev = prevToken && prevToken.end === tokenizer.position;
457
+ if (isCSSSelectorContext &&
458
+ !isAdjacentToPrev &&
459
+ (isAlpha(peek(tokenizer)) || peek(tokenizer) === '{')) {
457
460
  tokenizeCSSSelector(tokenizer);
458
461
  continue;
459
462
  }
@@ -686,6 +689,17 @@ function tokenizeCSSSelector(tokenizer) {
686
689
  const start = tokenizer.position;
687
690
  const prefix = advance(tokenizer);
688
691
  let value = prefix;
692
+ if (tokenizer.position < tokenizer.input.length && tokenizer.input[tokenizer.position] === '{') {
693
+ value += advance(tokenizer);
694
+ while (tokenizer.position < tokenizer.input.length) {
695
+ const ch = tokenizer.input[tokenizer.position];
696
+ value += advance(tokenizer);
697
+ if (ch === '}')
698
+ break;
699
+ }
700
+ addToken(tokenizer, TokenKind.SELECTOR, value, start);
701
+ return;
702
+ }
689
703
  while (tokenizer.position < tokenizer.input.length) {
690
704
  const char = tokenizer.input[tokenizer.position];
691
705
  if (isAlphaNumeric(char) || char === '-' || char === '_' || char === ':') {
@@ -2376,33 +2390,14 @@ function parseTriggerCommand(ctx, identifierNode) {
2376
2390
  });
2377
2391
  }
2378
2392
  }
2379
- while (!isCommandBoundary(ctx)) {
2380
- allArgs.push(ctx.parsePrimary());
2381
- }
2382
- let operationIndex = -1;
2383
- let operationKeyword = 'on';
2384
- for (let i = 0; i < allArgs.length; i++) {
2385
- const arg = allArgs[i];
2386
- const argRecord = arg;
2387
- const argValue = argRecord.name || argRecord.value;
2388
- if ((arg.type === 'identifier' || arg.type === 'literal' || arg.type === 'keyword') &&
2389
- (argValue === 'on' || argValue === 'to')) {
2390
- operationIndex = i;
2391
- operationKeyword = argValue;
2392
- break;
2393
+ const finalArgs = [...allArgs];
2394
+ if (ctx.check('on') || ctx.check('to')) {
2395
+ const keyword = ctx.advance().value;
2396
+ finalArgs.push(ctx.createIdentifier(keyword));
2397
+ while (!isCommandBoundary(ctx)) {
2398
+ finalArgs.push(ctx.parsePrimary());
2393
2399
  }
2394
2400
  }
2395
- const finalArgs = [];
2396
- if (operationIndex === -1) {
2397
- finalArgs.push(...allArgs);
2398
- }
2399
- else {
2400
- const eventArgs = allArgs.slice(0, operationIndex);
2401
- const targetArgs = allArgs.slice(operationIndex + 1);
2402
- finalArgs.push(...eventArgs);
2403
- finalArgs.push(ctx.createIdentifier(operationKeyword));
2404
- finalArgs.push(...targetArgs);
2405
- }
2406
2401
  return CommandNodeBuilder.fromIdentifier(identifierNode)
2407
2402
  .withArgs(...finalArgs)
2408
2403
  .endingAt(ctx.getPosition())
@@ -3122,8 +3117,13 @@ function parseSwapCommand(ctx, identifierNode) {
3122
3117
 
3123
3118
  function parseWaitCommand(ctx, commandToken) {
3124
3119
  const args = [];
3125
- if (ctx.checkTimeExpression() || ctx.checkLiteral()) {
3126
- const timeExpr = ctx.parsePrimary();
3120
+ const isExpressionStart = ctx.checkTimeExpression() ||
3121
+ ctx.checkLiteral() ||
3122
+ ctx.checkContextVar() ||
3123
+ ctx.check('(') ||
3124
+ (ctx.checkIdentifierLike() && !ctx.check('for') && !isCommandBoundary(ctx));
3125
+ if (isExpressionStart) {
3126
+ const timeExpr = ctx.parseExpression();
3127
3127
  args.push(timeExpr);
3128
3128
  return CommandNodeBuilder.from(commandToken)
3129
3129
  .withArgs(...args)
@@ -3309,43 +3309,24 @@ function parsePropertyOfTarget(ctx, startPosition) {
3309
3309
  return null;
3310
3310
  const thePosition = ctx.savePosition();
3311
3311
  ctx.advance();
3312
- const nextToken = ctx.peek();
3313
- const tokenAfterNext = ctx.peekAt(1);
3314
- if (nextToken && tokenAfterNext && tokenAfterNext.value === KEYWORDS$1.OF) {
3315
- const propertyToken = ctx.advance();
3316
- if (ctx.check(KEYWORDS$1.OF)) {
3317
- ctx.advance();
3318
- const targetToken = ctx.advance();
3319
- const isIdSelector = targetToken.value.startsWith('#');
3320
- return {
3321
- type: 'propertyOfExpression',
3322
- property: {
3323
- type: 'identifier',
3324
- name: propertyToken.value,
3325
- start: propertyToken.start,
3326
- end: propertyToken.end,
3327
- },
3328
- target: {
3329
- type: isIdSelector ? 'idSelector' : 'cssSelector',
3330
- value: targetToken.value,
3331
- start: targetToken.start,
3332
- end: targetToken.end,
3333
- },
3334
- start: startPosition,
3335
- end: ctx.savePosition(),
3336
- };
3337
- }
3338
- ctx.restorePosition(startPosition);
3312
+ const propertyExpr = ctx.parseExpression();
3313
+ if (!propertyExpr) {
3314
+ ctx.restorePosition(thePosition);
3339
3315
  return null;
3340
3316
  }
3341
- if (nextToken && tokenAfterNext && tokenAfterNext.value === KEYWORDS$1.TO) {
3342
- const variableToken = ctx.advance();
3343
- return {
3344
- type: 'identifier',
3345
- name: variableToken.value,
3346
- start: variableToken.start,
3347
- end: variableToken.end,
3348
- };
3317
+ const exprAny = propertyExpr;
3318
+ if (exprAny.type === 'binaryExpression' && exprAny.operator === KEYWORDS$1.OF) {
3319
+ const property = exprAny.left;
3320
+ const target = exprAny.right;
3321
+ return createPropertyOfExpression(property, target, {
3322
+ start: property.start ?? 0,
3323
+ end: target.end ?? 0,
3324
+ line: property.line ?? 1,
3325
+ column: property.column ?? 1,
3326
+ });
3327
+ }
3328
+ if (ctx.check(KEYWORDS$1.TO)) {
3329
+ return propertyExpr;
3349
3330
  }
3350
3331
  ctx.restorePosition(thePosition);
3351
3332
  return null;
@@ -3704,6 +3685,71 @@ function parseFetchNakedNamedArgs(ctx) {
3704
3685
  column: startPos.column,
3705
3686
  };
3706
3687
  }
3688
+ function findJsEndBoundary(ctx, startPos) {
3689
+ const input = ctx.getInputSlice(startPos);
3690
+ if (!input) {
3691
+ return startPos;
3692
+ }
3693
+ let i = 0;
3694
+ while (i < input.length) {
3695
+ const ch = input[i];
3696
+ if (ch === "'" || ch === '\u2019' || ch === '\u2018') {
3697
+ i++;
3698
+ while (i < input.length && input[i] !== ch) {
3699
+ if (input[i] === '\\')
3700
+ i++;
3701
+ i++;
3702
+ }
3703
+ i++;
3704
+ continue;
3705
+ }
3706
+ if (ch === '"') {
3707
+ i++;
3708
+ while (i < input.length && input[i] !== '"') {
3709
+ if (input[i] === '\\')
3710
+ i++;
3711
+ i++;
3712
+ }
3713
+ i++;
3714
+ continue;
3715
+ }
3716
+ if (ch === '`') {
3717
+ i++;
3718
+ while (i < input.length && input[i] !== '`') {
3719
+ if (input[i] === '\\')
3720
+ i++;
3721
+ i++;
3722
+ }
3723
+ i++;
3724
+ continue;
3725
+ }
3726
+ if (ch === '/' && i + 1 < input.length && input[i + 1] === '/') {
3727
+ i += 2;
3728
+ while (i < input.length && input[i] !== '\n')
3729
+ i++;
3730
+ continue;
3731
+ }
3732
+ if (ch === '/' && i + 1 < input.length && input[i + 1] === '*') {
3733
+ i += 2;
3734
+ while (i < input.length &&
3735
+ !(input[i] === '*' && i + 1 < input.length && input[i + 1] === '/'))
3736
+ i++;
3737
+ i += 2;
3738
+ continue;
3739
+ }
3740
+ if ((ch === 'e' || ch === 'E') &&
3741
+ i + 3 <= input.length &&
3742
+ input.slice(i, i + 3).toLowerCase() === 'end') {
3743
+ const before = i === 0 || !/[a-zA-Z0-9_]/.test(input[i - 1]);
3744
+ const after = i + 3 >= input.length || !/[a-zA-Z0-9_]/.test(input[i + 3]);
3745
+ if (before && after) {
3746
+ return startPos + i;
3747
+ }
3748
+ }
3749
+ i++;
3750
+ }
3751
+ return startPos + input.length;
3752
+ }
3707
3753
  function parseJsCommand(ctx, identifierNode) {
3708
3754
  const parameters = [];
3709
3755
  if (ctx.match('(')) {
@@ -3716,11 +3762,12 @@ function parseJsCommand(ctx, identifierNode) {
3716
3762
  ctx.consume(')', 'Expected ) after js parameters');
3717
3763
  }
3718
3764
  const jsCodeStart = ctx.peek().start;
3719
- while (!ctx.check(KEYWORDS$1.END) && !ctx.isAtEnd()) {
3765
+ const jsCodeEnd = findJsEndBoundary(ctx, jsCodeStart);
3766
+ while (!ctx.isAtEnd() && !ctx.check(KEYWORDS$1.END)) {
3767
+ if (ctx.peek().start >= jsCodeEnd)
3768
+ break;
3720
3769
  ctx.advance();
3721
3770
  }
3722
- const endToken = ctx.peek();
3723
- const jsCodeEnd = endToken.start;
3724
3771
  ctx.consume(KEYWORDS$1.END, 'Expected end after js code body');
3725
3772
  const rawSlice = ctx.getInputSlice(jsCodeStart, jsCodeEnd);
3726
3773
  const code = rawSlice.trim();
@@ -4623,7 +4670,12 @@ class Parser {
4623
4670
  }
4624
4671
  else {
4625
4672
  const commandName = expr.name.toLowerCase();
4626
- if (commandName === 'wait' && this.checkTimeExpression()) {
4673
+ if (commandName === 'wait' &&
4674
+ (this.checkTimeExpression() ||
4675
+ this.checkNumber() ||
4676
+ this.checkIdentifier() ||
4677
+ this.checkContextVar() ||
4678
+ this.check('('))) {
4627
4679
  const command = this.createCommandFromIdentifier(expr);
4628
4680
  if (command) {
4629
4681
  expr = command;
@@ -5842,9 +5894,7 @@ class Parser {
5842
5894
  while (!this.isAtEnd() && !this.check('end')) {
5843
5895
  if (this.match('on')) {
5844
5896
  const handlerPos = this.getPosition();
5845
- const eventToken = this.peek();
5846
- const eventName = eventToken.value;
5847
- this.advance();
5897
+ const eventName = this.parseEventNameWithNamespace("Expected event name after 'on'");
5848
5898
  const eventArgs = [];
5849
5899
  if (this.check('(')) {
5850
5900
  this.advance();
@@ -8663,6 +8713,8 @@ class BaseExpressionEvaluator {
8663
8713
  return this.evaluateIdSelector(node, context);
8664
8714
  case 'attributeAccess':
8665
8715
  return this.evaluateAttributeAccess(node, context);
8716
+ case 'asExpression':
8717
+ return this.evaluateAsExpression(node, context);
8666
8718
  default:
8667
8719
  throw new Error(`Unsupported AST node type for evaluation: ${node.type}`);
8668
8720
  }
@@ -8948,6 +9000,14 @@ class BaseExpressionEvaluator {
8948
9000
  }
8949
9001
  return `@${node.attributeName}`;
8950
9002
  }
9003
+ async evaluateAsExpression(node, context) {
9004
+ const value = await this.evaluate(node.expression, context);
9005
+ const asExpr = this.expressionRegistry.get('as');
9006
+ if (asExpr) {
9007
+ return asExpr.evaluate(context, value, node.targetType);
9008
+ }
9009
+ throw new Error(`Conversion type 'as' not registered`);
9010
+ }
8951
9011
  getAvailableExpressions() {
8952
9012
  return Array.from(this.expressionRegistry.keys());
8953
9013
  }
@@ -14528,6 +14588,8 @@ class RuntimeBase {
14528
14588
  this.behaviorAPI = {
14529
14589
  has: (name) => this.behaviorRegistry.has(name),
14530
14590
  get: (name) => this.behaviorRegistry.get(name),
14591
+ set: (name, definition) => this.behaviorRegistry.set(name, definition),
14592
+ resolve: null,
14531
14593
  install: async (behaviorName, element, parameters) => {
14532
14594
  return await this.installBehaviorOnElement(behaviorName, element, parameters);
14533
14595
  },
@@ -15009,9 +15071,14 @@ class RuntimeBase {
15009
15071
  }
15010
15072
  async installBehaviorOnElement(behaviorName, element, parameters) {
15011
15073
  debug.runtime(`BEHAVIOR: installBehaviorOnElement called: ${behaviorName}`);
15012
- const behavior = this.behaviorRegistry.get(behaviorName);
15013
- if (!behavior)
15014
- throw new Error(`Behavior "${behaviorName}" not found`);
15074
+ let behavior = this.behaviorRegistry.get(behaviorName);
15075
+ if (!behavior) {
15076
+ if (this.behaviorAPI.resolve && this.behaviorAPI.resolve(behaviorName)) {
15077
+ behavior = this.behaviorRegistry.get(behaviorName);
15078
+ }
15079
+ if (!behavior)
15080
+ throw new Error(`Behavior "${behaviorName}" not found`);
15081
+ }
15015
15082
  if (behavior.type === 'imperative' && typeof behavior.install === 'function') {
15016
15083
  debug.runtime(`BEHAVIOR: Installing imperative behavior '${behaviorName}'`);
15017
15084
  behavior.install(element, parameters);
@@ -20322,7 +20389,11 @@ let SetCommand = (() => {
20322
20389
  return { type: 'property', element: firstValue[0], property: 'textContent', value };
20323
20390
  }
20324
20391
  if (typeof firstValue !== 'string') {
20325
- throw new Error('set command target must be a string or object literal');
20392
+ const isMember = firstArg?.type === 'memberExpression' || firstArg?.type === 'propertyAccess';
20393
+ const hint = isMember
20394
+ ? ` (a property chain evaluated to ${firstValue === null ? 'null' : typeof firstValue} — check that all intermediate objects exist)`
20395
+ : '';
20396
+ throw new Error(`set command target must be a string or object literal${hint}`);
20326
20397
  }
20327
20398
  const value = await this.extractValue(raw, evaluator, context);
20328
20399
  return { type: 'variable', name: firstValue, value };
@@ -23531,12 +23602,19 @@ class InstallCommand {
23531
23602
  const behaviorRegistry = context.locals.get('_behaviors');
23532
23603
  if (behaviorRegistry && typeof behaviorRegistry === 'object') {
23533
23604
  const registry = behaviorRegistry;
23534
- return registry.has(behaviorName);
23605
+ if (registry.has(behaviorName))
23606
+ return true;
23607
+ if (registry.resolve && registry.resolve(behaviorName))
23608
+ return true;
23535
23609
  }
23536
23610
  if (typeof globalThis !== 'undefined') {
23537
23611
  const hyperscriptGlobal = globalThis._hyperscript;
23538
23612
  if (hyperscriptGlobal?.behaviors) {
23539
- return hyperscriptGlobal.behaviors.has(behaviorName);
23613
+ if (hyperscriptGlobal.behaviors.has(behaviorName))
23614
+ return true;
23615
+ if (hyperscriptGlobal.behaviors.resolve &&
23616
+ hyperscriptGlobal.behaviors.resolve(behaviorName))
23617
+ return true;
23540
23618
  }
23541
23619
  }
23542
23620
  return false;