@hyperfixi/core 3.1.1 → 3.2.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.
Files changed (97) hide show
  1. package/README.md +28 -36
  2. package/dist/ast/command-slots.d.ts +1 -1
  3. package/dist/ast/nodes.d.ts +2 -0
  4. package/dist/ast-utils/index.cjs +193 -48
  5. package/dist/ast-utils/index.mjs +193 -48
  6. package/dist/ast-utils/interchange/types.d.ts +19 -1
  7. package/dist/behaviors/index.cjs +19 -0
  8. package/dist/behaviors/index.mjs +19 -0
  9. package/dist/bundle-generator/index.cjs +24 -9
  10. package/dist/bundle-generator/index.mjs +24 -9
  11. package/dist/bundle-generator/parser-templates.d.ts +1 -1
  12. package/dist/chunks/bridge-Cnj8dQFV.js +2 -0
  13. package/dist/chunks/browser-modular-BNDYF6Dq.js +2 -0
  14. package/dist/chunks/index-DQi2o8Lk.js +2 -0
  15. package/dist/chunks/index-LLuwEQp0.js +2 -0
  16. package/dist/chunks/index-uCOQ8Qn0.js +2 -0
  17. package/dist/commands/animation/transition.d.ts +12 -6
  18. package/dist/commands/helpers/loop-executor.d.ts +2 -2
  19. package/dist/commands/helpers/numeric-target-parser.d.ts +1 -1
  20. package/dist/commands/helpers/variable-access.d.ts +5 -2
  21. package/dist/commands/helpers/write-target.d.ts +5 -0
  22. package/dist/commands/index.cjs +929 -425
  23. package/dist/commands/index.mjs +929 -425
  24. package/dist/commands/templates/render.d.ts +4 -4
  25. package/dist/commands/utility/beep.d.ts +0 -3
  26. package/dist/expressions/index.cjs +7 -13
  27. package/dist/expressions/index.mjs +7 -13
  28. package/dist/htmx/i18n-hooks.d.ts +4 -1
  29. package/dist/hyperfixi-browser-classic-i18n.js +1 -1
  30. package/dist/hyperfixi-browser.js +1 -1
  31. package/dist/hyperfixi-classic-i18n.js +1 -1
  32. package/dist/hyperfixi-hx-v4.js +1 -1
  33. package/dist/hyperfixi-hx.js +1 -1
  34. package/dist/hyperfixi-hybrid-complete.js +1 -1
  35. package/dist/hyperfixi-hybrid-hx.js +1 -1
  36. package/dist/hyperfixi-multilingual.js +1 -1
  37. package/dist/hyperfixi.js +1 -1
  38. package/dist/hyperfixi.mjs +1 -1
  39. package/dist/index.cjs +1140 -485
  40. package/dist/index.min.js +1 -1
  41. package/dist/index.mjs +1140 -485
  42. package/dist/lib/swap-executor.d.ts +1 -1
  43. package/dist/lokascript-browser-classic-i18n.js +1 -1
  44. package/dist/lokascript-browser.js +1 -1
  45. package/dist/lokascript-hybrid-complete.js +1 -1
  46. package/dist/lokascript-hybrid-hx.js +1 -1
  47. package/dist/lokascript-multilingual.js +1 -1
  48. package/dist/metadata.cjs +9 -9
  49. package/dist/metadata.d.ts +1 -1
  50. package/dist/metadata.mjs +9 -9
  51. package/dist/parser/command-grammar.d.ts +1 -0
  52. package/dist/parser/full-parser.cjs +387 -153
  53. package/dist/parser/full-parser.mjs +387 -153
  54. package/dist/parser/helpers/parsing-helpers.d.ts +3 -0
  55. package/dist/parser/hybrid/index.cjs +3 -0
  56. package/dist/parser/hybrid/index.mjs +3 -0
  57. package/dist/parser/hybrid/parser-core.cjs +3 -0
  58. package/dist/parser/hybrid/parser-core.mjs +3 -0
  59. package/dist/parser/hybrid-parser.cjs +3 -0
  60. package/dist/parser/hybrid-parser.mjs +3 -0
  61. package/dist/parser/parser-types.d.ts +2 -0
  62. package/dist/parser/parser.d.ts +9 -0
  63. package/dist/parser/pratt-parser.d.ts +2 -0
  64. package/dist/registry/index.cjs +526 -210
  65. package/dist/registry/index.mjs +526 -210
  66. package/dist/utils/beep.d.ts +8 -0
  67. package/dist/version.d.ts +1 -1
  68. package/package.json +21 -18
  69. package/vocab/htmx/ar.js +22 -20
  70. package/vocab/htmx/bn.js +21 -5
  71. package/vocab/htmx/de.js +33 -10
  72. package/vocab/htmx/en.js +4 -1
  73. package/vocab/htmx/es.js +28 -9
  74. package/vocab/htmx/fr.js +28 -8
  75. package/vocab/htmx/he.js +22 -1
  76. package/vocab/htmx/hi.js +28 -7
  77. package/vocab/htmx/id.js +29 -10
  78. package/vocab/htmx/it.js +26 -5
  79. package/vocab/htmx/ja.js +36 -11
  80. package/vocab/htmx/ko.js +27 -6
  81. package/vocab/htmx/ms.js +24 -1
  82. package/vocab/htmx/pl.js +27 -7
  83. package/vocab/htmx/pt.js +30 -9
  84. package/vocab/htmx/qu.js +32 -9
  85. package/vocab/htmx/ru.js +28 -6
  86. package/vocab/htmx/sw.js +28 -8
  87. package/vocab/htmx/th.js +19 -1
  88. package/vocab/htmx/tl.js +24 -1
  89. package/vocab/htmx/tr.js +34 -8
  90. package/vocab/htmx/uk.js +28 -6
  91. package/vocab/htmx/vi.js +27 -19
  92. package/vocab/htmx/zh.js +28 -9
  93. package/dist/chunks/bridge-D7qrHhV0.js +0 -2
  94. package/dist/chunks/browser-modular-C4LFxfoS.js +0 -2
  95. package/dist/chunks/index-C7lqD4mo.js +0 -2
  96. package/dist/chunks/index-CAPhqA6k.js +0 -2
  97. package/dist/chunks/index-CLdRyZaP.js +0 -2
package/dist/index.mjs CHANGED
@@ -2073,6 +2073,39 @@ function makeTypeCheckHandler(negated) {
2073
2073
  };
2074
2074
  };
2075
2075
  }
2076
+ const OF_OPERAND_BP = 70;
2077
+ function parseOfOperand(ctx, minBp = OF_OPERAND_BP) {
2078
+ let right = ctx.parseExpr(minBp);
2079
+ if (ctx.peek()?.value === 'in') {
2080
+ const inToken = ctx.advance();
2081
+ const scope = ctx.parseExpr(minBp);
2082
+ right = {
2083
+ type: 'binaryExpression',
2084
+ operator: 'in',
2085
+ left: right,
2086
+ right: scope,
2087
+ start: right.start,
2088
+ end: scope.end ?? inToken.end,
2089
+ line: right.line ?? inToken.line,
2090
+ column: right.column ?? inToken.column,
2091
+ };
2092
+ }
2093
+ return right;
2094
+ }
2095
+ const THE_OF_TARGET_BP = OF_OPERAND_BP + 1;
2096
+ function ofHandler(left, token, ctx) {
2097
+ const right = parseOfOperand(ctx);
2098
+ return {
2099
+ type: 'binaryExpression',
2100
+ operator: token.value,
2101
+ left,
2102
+ right,
2103
+ start: left.start,
2104
+ end: right.end ?? token.end,
2105
+ line: left.line ?? token.line,
2106
+ column: left.column ?? token.column,
2107
+ };
2108
+ }
2076
2109
  const PARSER_COMPARISON_FRAGMENT = new Map([
2077
2110
  ['===', leftAssoc(30)],
2078
2111
  ['!==', leftAssoc(30)],
@@ -2117,7 +2150,7 @@ const PARSER_COMPARISON_FRAGMENT = new Map([
2117
2150
  ['is greater than or equal to', leftAssoc(30)],
2118
2151
  ['is less than or equal to', leftAssoc(30)],
2119
2152
  ['in', leftAssoc(30)],
2120
- ['of', rightAssoc(30)],
2153
+ ['of', { infix: { bp: [90, OF_OPERAND_BP], handler: ofHandler } }],
2121
2154
  ['really', leftAssoc(30)],
2122
2155
  [
2123
2156
  'exists',
@@ -2821,6 +2854,48 @@ function parseMaybeNamedArgument(ctx) {
2821
2854
  const value = ctx.parseExpression();
2822
2855
  return name !== undefined ? { name, value } : { value };
2823
2856
  }
2857
+ function isNakedNamedArgStart(ctx) {
2858
+ if (ctx.check('{'))
2859
+ return false;
2860
+ if (!ctx.checkIdentifierLike())
2861
+ return false;
2862
+ const next = ctx.peekAt(1);
2863
+ return next !== null && next.value === ':';
2864
+ }
2865
+ function parseNakedNamedArgs(ctx, parseValue, command) {
2866
+ const properties = [];
2867
+ const startPos = ctx.getPosition();
2868
+ do {
2869
+ if (!ctx.checkIdentifierLike())
2870
+ break;
2871
+ const keyToken = ctx.advance();
2872
+ const key = {
2873
+ type: 'identifier',
2874
+ name: keyToken.value,
2875
+ start: keyToken.start,
2876
+ end: keyToken.end,
2877
+ line: keyToken.line,
2878
+ column: keyToken.column,
2879
+ };
2880
+ ctx.consume(':', `Expected ':' after property name in ${command} named arguments`);
2881
+ const value = parseValue();
2882
+ if (value) {
2883
+ properties.push({ key, value });
2884
+ }
2885
+ } while (ctx.match(',') && !ctx.isAtEnd());
2886
+ const endPos = ctx.getPosition();
2887
+ return {
2888
+ type: 'objectLiteral',
2889
+ properties,
2890
+ start: startPos.start,
2891
+ end: endPos.end,
2892
+ line: startPos.line,
2893
+ column: startPos.column,
2894
+ };
2895
+ }
2896
+ function isGluedBeepBang(beep, bang) {
2897
+ return beep?.value === 'beep' && bang?.value === '!' && bang.start === beep.end;
2898
+ }
2824
2899
 
2825
2900
  class CommandNodeBuilder {
2826
2901
  constructor(name) {
@@ -3022,6 +3097,8 @@ function parseTriggerCommand(ctx, identifierNode) {
3022
3097
  const modifiers = {};
3023
3098
  while (!isCommandBoundary(ctx)) {
3024
3099
  if (ctx.check(KEYWORDS.ON) || ctx.check(KEYWORDS.TO)) {
3100
+ if (modifiers['on'])
3101
+ break;
3025
3102
  ctx.advance();
3026
3103
  const target = parseOneArgument(ctx, [KEYWORDS.WITH]);
3027
3104
  if (target)
@@ -3162,6 +3239,9 @@ function parseRepeatCommand(ctx, commandToken) {
3162
3239
  ctx.advance();
3163
3240
  loopType = KEYWORDS.FOREVER;
3164
3241
  }
3242
+ else if (ctx.checkIsCommand() || ctx.isCommand(ctx.peek().value)) {
3243
+ loopType = KEYWORDS.FOREVER;
3244
+ }
3165
3245
  else {
3166
3246
  times = ctx.parseExpression();
3167
3247
  if (ctx.check(KEYWORDS.TIMES)) {
@@ -3204,10 +3284,12 @@ function parseRepeatCommand(ctx, commandToken) {
3204
3284
  loopType = result.terminator;
3205
3285
  ctx.advance();
3206
3286
  condition = ctx.parseExpression();
3207
- if (!ctx.check('end')) {
3287
+ if (ctx.check('end')) {
3288
+ ctx.advance();
3289
+ }
3290
+ else if (!ctx.atEndOfInput()) {
3208
3291
  throw new Error('Expected "end" to close repeat block');
3209
3292
  }
3210
- ctx.advance();
3211
3293
  }
3212
3294
  }
3213
3295
  else {
@@ -3453,7 +3535,7 @@ function parseIfCommand(ctx, commandToken) {
3453
3535
  }));
3454
3536
  }
3455
3537
  }
3456
- if (!consumedElseIf && !ctx.isAtEnd()) {
3538
+ if (!consumedElseIf && !ctx.atEndOfInput()) {
3457
3539
  ctx.consume(KEYWORDS.END, "Expected 'end' after if block");
3458
3540
  }
3459
3541
  }
@@ -3478,7 +3560,6 @@ function parseIfCommand(ctx, commandToken) {
3478
3560
  .build();
3479
3561
  }
3480
3562
  function parseForCommand(ctx, commandToken) {
3481
- const args = [];
3482
3563
  let variable = null;
3483
3564
  let collection = null;
3484
3565
  if (ctx.check(KEYWORDS.EACH)) {
@@ -3521,31 +3602,22 @@ function parseForCommand(ctx, commandToken) {
3521
3602
  }
3522
3603
  }
3523
3604
  const commands = ctx.parseCommandListUntilEnd('for');
3524
- args.push({
3525
- type: 'identifier',
3526
- name: 'for',
3527
- start: commandToken.start,
3528
- end: commandToken.end,
3529
- line: commandToken.line,
3530
- column: commandToken.column,
3531
- });
3532
- const forPos = {
3605
+ const pos = {
3533
3606
  start: commandToken.start,
3534
3607
  end: commandToken.end,
3535
3608
  line: commandToken.line,
3536
3609
  column: commandToken.column,
3537
3610
  };
3538
- args.push(createStringLiteral(variable, forPos));
3539
- args.push(collection);
3540
- if (indexVariable) {
3541
- args.push(createStringLiteral(indexVariable, forPos));
3542
- }
3543
- args.push(createBlock(commands, { ...forPos, end: forPos.end || 0 }));
3544
- return CommandNodeBuilder.from({
3545
- ...commandToken,
3546
- value: 'repeat',
3547
- })
3548
- .withArgs(...args)
3611
+ const modifiers = {
3612
+ loopType: toLegacyExpression(createStringLiteral(KEYWORDS.FOR, pos)),
3613
+ for: toLegacyExpression(createStringLiteral(variable, pos)),
3614
+ in: collection,
3615
+ };
3616
+ if (indexVariable)
3617
+ modifiers['index'] = toLegacyExpression(createStringLiteral(indexVariable, pos));
3618
+ return CommandNodeBuilder.from({ ...commandToken, value: 'repeat' })
3619
+ .withArgs(createBlock(commands, { ...pos, end: pos.end || 0 }))
3620
+ .withModifiers(modifiers)
3549
3621
  .endingAt(ctx.getPosition())
3550
3622
  .build();
3551
3623
  }
@@ -3601,86 +3673,159 @@ function parseMeasureCommand(ctx, identifierNode) {
3601
3673
  }
3602
3674
  return builder.build();
3603
3675
  }
3604
- const CONTEXT_POSSESSIVES = new Set(['my', 'its', 'your']);
3676
+ const CONTEXT_REFS = new Set(['me', 'it', 'you', 'result']);
3605
3677
  function parseTransitionCommand(ctx, commandToken) {
3606
- const args = [];
3607
- const modifiers = {};
3608
- let property = null;
3609
- let target = null;
3610
- const leadToken = ctx.peek();
3611
- const startsTarget = ctx.checkAnySelector() ||
3612
- ctx.checkContextVar() ||
3613
- ctx.check('<') ||
3614
- (isIdentifierLike(leadToken) && CONTEXT_POSSESSIVES.has(leadToken.value));
3615
- if (startsTarget) {
3616
- const parsed = ctx.parsePrimary();
3617
- if ((parsed.type === 'possessiveExpression' ||
3618
- (parsed.type === 'memberExpression' && !parsed.computed)) &&
3619
- parsed.object &&
3620
- parsed.property?.name) {
3621
- target = parsed.object;
3622
- property = {
3623
- type: 'string',
3624
- value: parsed.property.name,
3625
- start: leadToken.start || 0,
3626
- end: ctx.getPosition().end,
3627
- line: leadToken.line,
3628
- column: leadToken.column,
3629
- };
3678
+ const pairs = [];
3679
+ do {
3680
+ pairs.push(parseTransitionPair(ctx));
3681
+ } while (startsAnotherPair(ctx));
3682
+ const [first, ...rest] = pairs;
3683
+ const args = first.owner ? [first.owner, first.property] : [first.property];
3684
+ const modifiers = { to: first.to };
3685
+ if (first.from)
3686
+ modifiers['from'] = first.from;
3687
+ if (rest.length > 0)
3688
+ modifiers['pairs'] = pairsNode(rest);
3689
+ for (let i = 0; i < 3 && !ctx.isAtEnd(); i++) {
3690
+ if (ctx.check('over') && !modifiers['over']) {
3691
+ ctx.advance();
3692
+ modifiers['over'] = ctx.parseExpression();
3630
3693
  }
3631
- else {
3632
- target = parsed;
3633
- if (ctx.check("'s"))
3634
- ctx.advance();
3694
+ else if (ctx.check('using') && !modifiers['using']) {
3695
+ ctx.advance();
3696
+ modifiers['using'] = ctx.parseExpression();
3635
3697
  }
3636
- }
3637
- const firstToken = ctx.peek();
3638
- if (!property && (isIdentifierLike(firstToken) || firstToken.value === '*')) {
3639
- let propertyValue = '';
3640
- if (ctx.check('*')) {
3641
- propertyValue = '*';
3698
+ else if (ctx.check(KEYWORDS.WITH) && !modifiers['with']) {
3642
3699
  ctx.advance();
3700
+ modifiers['with'] = ctx.parsePrimary();
3643
3701
  }
3644
- const hyphenatedName = parseHyphenatedName(ctx);
3645
- if (hyphenatedName) {
3646
- propertyValue += hyphenatedName;
3647
- property = {
3648
- type: 'string',
3649
- value: propertyValue,
3650
- start: firstToken.start || 0,
3651
- end: ctx.getPosition().end,
3652
- line: firstToken.line,
3653
- column: firstToken.column,
3654
- };
3702
+ else {
3703
+ break;
3655
3704
  }
3656
3705
  }
3657
- if (!property) {
3658
- throw new Error('Transition command requires a CSS property');
3706
+ return CommandNodeBuilder.from(commandToken)
3707
+ .withArgs(...args)
3708
+ .withModifiers(modifiers)
3709
+ .endingAt(ctx.getPosition())
3710
+ .build();
3711
+ }
3712
+ function parseTransitionPair(ctx) {
3713
+ const { owner, property } = parseTransitionProperty(ctx);
3714
+ let from = null;
3715
+ if (ctx.check('from')) {
3716
+ ctx.advance();
3717
+ from = ctx.parseExpression();
3659
3718
  }
3660
- if (target)
3661
- args.push(target);
3662
- args.push(property);
3663
3719
  if (!ctx.check(KEYWORDS.TO)) {
3664
3720
  throw new Error('Expected "to" keyword after property in transition command');
3665
3721
  }
3666
3722
  ctx.advance();
3667
- const value = ctx.parseExpression();
3668
- modifiers['to'] = value;
3669
- if (ctx.check('over')) {
3670
- ctx.advance();
3671
- const duration = ctx.parseExpression();
3672
- modifiers['over'] = duration;
3723
+ const to = ctx.parseExpression();
3724
+ return { owner, property, from, to };
3725
+ }
3726
+ function parseTransitionProperty(ctx) {
3727
+ const lead = ctx.peek();
3728
+ if (!lead || isKeyword(lead, [KEYWORDS.TO, 'from'])) {
3729
+ throw new Error('Transition command requires a CSS property');
3673
3730
  }
3674
- if (ctx.check(KEYWORDS.WITH)) {
3731
+ const expr = ctx.parseExpression();
3732
+ const split = splitStyleExpression(expr);
3733
+ if (split)
3734
+ return { owner: split.owner, property: propertyNode(split.name, lead, ctx) };
3735
+ let name = '';
3736
+ if (ctx.check('*')) {
3737
+ name = '*';
3675
3738
  ctx.advance();
3676
- const timingFunction = ctx.parsePrimary();
3677
- modifiers['with'] = timingFunction;
3678
3739
  }
3679
- return CommandNodeBuilder.from(commandToken)
3680
- .withArgs(...args)
3681
- .withModifiers(modifiers)
3682
- .endingAt(ctx.getPosition())
3683
- .build();
3740
+ const hyphenated = parseHyphenatedName(ctx);
3741
+ if (!hyphenated)
3742
+ throw new Error('Transition command requires a CSS property');
3743
+ return { owner: expr, property: propertyNode(name + hyphenated, lead, ctx) };
3744
+ }
3745
+ function splitStyleExpression(node) {
3746
+ const n = node;
3747
+ switch (n.type) {
3748
+ case 'selector':
3749
+ return typeof n.value === 'string' && n.value.startsWith('*')
3750
+ ? { owner: null, name: n.value }
3751
+ : null;
3752
+ case 'identifier':
3753
+ return typeof n.name === 'string' && !CONTEXT_REFS.has(n.name)
3754
+ ? { owner: null, name: n.name }
3755
+ : null;
3756
+ case 'possessiveExpression':
3757
+ case 'memberExpression':
3758
+ return !n.computed && n.object && typeof n.property?.name === 'string'
3759
+ ? { owner: n.object, name: n.property.name }
3760
+ : null;
3761
+ case 'binaryExpression': {
3762
+ if (n.operator !== 'of' || !n.left || !n.right)
3763
+ return null;
3764
+ const inner = splitStyleExpression(n.left);
3765
+ return inner && !inner.owner ? { owner: n.right, name: inner.name } : null;
3766
+ }
3767
+ case 'propertyOfExpression':
3768
+ return n.target && typeof n.property?.name === 'string'
3769
+ ? { owner: n.target, name: n.property.name }
3770
+ : null;
3771
+ default:
3772
+ return null;
3773
+ }
3774
+ }
3775
+ function propertyNode(name, lead, ctx) {
3776
+ return {
3777
+ type: 'string',
3778
+ value: name,
3779
+ start: lead.start || 0,
3780
+ end: ctx.getPosition().end,
3781
+ line: lead.line,
3782
+ column: lead.column,
3783
+ };
3784
+ }
3785
+ function startsAnotherPair(ctx) {
3786
+ if (ctx.isAtEnd() || isComment(ctx.peek()))
3787
+ return false;
3788
+ if (isKeyword(ctx.peek(), ['over', 'using', KEYWORDS.WITH]))
3789
+ return false;
3790
+ return !isCommandBoundary(ctx, ['catch', 'finally', 'on']);
3791
+ }
3792
+ function pairsNode(pairs) {
3793
+ const field = (name, value) => ({
3794
+ key: {
3795
+ type: 'identifier',
3796
+ name,
3797
+ start: value.start,
3798
+ end: value.end,
3799
+ line: value.line,
3800
+ column: value.column,
3801
+ },
3802
+ value,
3803
+ });
3804
+ const elements = pairs.map(pair => {
3805
+ const properties = [field('property', pair.property), field('to', pair.to)];
3806
+ if (pair.owner)
3807
+ properties.push(field('owner', pair.owner));
3808
+ if (pair.from)
3809
+ properties.push(field('from', pair.from));
3810
+ return {
3811
+ type: 'objectLiteral',
3812
+ properties,
3813
+ start: (pair.owner ?? pair.property).start,
3814
+ end: pair.to.end,
3815
+ line: (pair.owner ?? pair.property).line,
3816
+ column: (pair.owner ?? pair.property).column,
3817
+ };
3818
+ });
3819
+ const firstEl = elements[0];
3820
+ const lastEl = elements[elements.length - 1];
3821
+ return {
3822
+ type: 'arrayLiteral',
3823
+ elements,
3824
+ start: firstEl.start,
3825
+ end: lastEl.end,
3826
+ line: firstEl.line,
3827
+ column: firstEl.column,
3828
+ };
3684
3829
  }
3685
3830
  function parseStartCommand(ctx, identifierNode) {
3686
3831
  if (!ctx.match('view')) {
@@ -3977,11 +4122,17 @@ function parseSwapCommand(ctx, identifierNode) {
3977
4122
  const targetExpr = isDelete ? ctx.parseExpression() : parseOneArgument(ctx, [KEYWORDS.WITH]);
3978
4123
  if (targetExpr)
3979
4124
  args.push(targetExpr);
3980
- if (!isDelete && consumeOptionalKeyword(ctx, KEYWORDS.WITH)) {
4125
+ const isMorph = identifierNode.name === 'morph';
4126
+ if (!isDelete &&
4127
+ (consumeOptionalKeyword(ctx, KEYWORDS.WITH) ||
4128
+ (isMorph && consumeOptionalKeyword(ctx, KEYWORDS.TO)))) {
3981
4129
  const contentExpr = parseOneArgument(ctx, ['using']);
3982
4130
  if (contentExpr)
3983
4131
  modifiers['with'] = contentExpr;
3984
4132
  }
4133
+ if (isMorph && modifiers['with'] === undefined) {
4134
+ ctx.addError("Expected 'to' and the content to morph into");
4135
+ }
3985
4136
  return CommandNodeBuilder.fromIdentifier(identifierNode)
3986
4137
  .withArgs(...args)
3987
4138
  .withModifiers({ ...modifiers, ...parseViewTransitionTail(ctx) })
@@ -4742,6 +4893,8 @@ function atArgumentBoundary(ctx, grammar) {
4742
4893
  const next = ctx.peek();
4743
4894
  if (ctx.checkIsCommand() && ctx.peekAt(1)?.value === '(')
4744
4895
  return false;
4896
+ if (isGluedBeepBang(next, ctx.peekAt(1)))
4897
+ return false;
4745
4898
  if (isCommandBoundary(ctx, ['catch', 'finally', 'on']))
4746
4899
  return true;
4747
4900
  return isKeyword(next, [...grammar.markers]);
@@ -4773,10 +4926,13 @@ function parseDeclaredCommand(ctx, commandToken, name, grammar) {
4773
4926
  }
4774
4927
  }
4775
4928
  const commaList = new Set(grammar.commaList ?? []);
4929
+ const namedArgs = new Set(grammar.namedArgs ?? []);
4776
4930
  while (!ctx.isAtEnd() && isKeyword(ctx.peek(), [...grammar.markers])) {
4777
4931
  const keyword = ctx.advance().value;
4778
4932
  const key = ctx.resolveKeyword(keyword).toLowerCase();
4779
- let value = ctx.parseExpression();
4933
+ let value = namedArgs.has(key) && isNakedNamedArgStart(ctx)
4934
+ ? parseNakedNamedArgs(ctx, () => ctx.parseExpression(), name)
4935
+ : ctx.parseExpression();
4780
4936
  if (value && commaList.has(key) && ctx.check(',')) {
4781
4937
  const elements = [value];
4782
4938
  while (ctx.match(',')) {
@@ -4941,8 +5097,8 @@ function parseFetchCommand(ctx, commandToken) {
4941
5097
  }
4942
5098
  if (ctx.check('with') && !modifiers['with']) {
4943
5099
  ctx.advance();
4944
- if (isFetchNakedNamedArgStart(ctx)) {
4945
- modifiers['with'] = parseFetchNakedNamedArgs(ctx);
5100
+ if (isNakedNamedArgStart(ctx)) {
5101
+ modifiers['with'] = parseNakedNamedArgs(ctx, () => ctx.parsePrimary(), 'fetch');
4946
5102
  }
4947
5103
  else {
4948
5104
  modifiers['with'] = ctx.parsePrimary();
@@ -4977,45 +5133,6 @@ function parseFetchCommand(ctx, commandToken) {
4977
5133
  }
4978
5134
  return builder.build();
4979
5135
  }
4980
- function isFetchNakedNamedArgStart(ctx) {
4981
- if (ctx.check('{'))
4982
- return false;
4983
- if (!ctx.checkIdentifierLike())
4984
- return false;
4985
- const next = ctx.peekAt(1);
4986
- return next !== null && next.value === ':';
4987
- }
4988
- function parseFetchNakedNamedArgs(ctx) {
4989
- const properties = [];
4990
- const startPos = ctx.getPosition();
4991
- do {
4992
- if (!ctx.checkIdentifierLike())
4993
- break;
4994
- const keyToken = ctx.advance();
4995
- const key = {
4996
- type: 'identifier',
4997
- name: keyToken.value,
4998
- start: keyToken.start,
4999
- end: keyToken.end,
5000
- line: keyToken.line,
5001
- column: keyToken.column,
5002
- };
5003
- ctx.consume(':', "Expected ':' after property name in fetch named arguments");
5004
- const value = ctx.parsePrimary();
5005
- if (value) {
5006
- properties.push({ key, value });
5007
- }
5008
- } while (ctx.match(',') && !ctx.isAtEnd());
5009
- const endPos = ctx.getPosition();
5010
- return {
5011
- type: 'objectLiteral',
5012
- properties,
5013
- start: startPos.start,
5014
- end: endPos.end,
5015
- line: startPos.line,
5016
- column: startPos.column,
5017
- };
5018
- }
5019
5136
  function findJsEndBoundary(ctx, startPos) {
5020
5137
  const input = ctx.getInputSlice(startPos);
5021
5138
  if (!input) {
@@ -5135,6 +5252,7 @@ function parseTellCommand(ctx, identifierNode) {
5135
5252
  const commands = [];
5136
5253
  while (!ctx.isAtEnd()) {
5137
5254
  if (ctx.checkIsCommand()) {
5255
+ const word = ctx.peek().value;
5138
5256
  try {
5139
5257
  ctx.advance();
5140
5258
  const cmd = ctx.parseCommand();
@@ -5145,7 +5263,8 @@ function parseTellCommand(ctx, identifierNode) {
5145
5263
  break;
5146
5264
  }
5147
5265
  }
5148
- catch {
5266
+ catch (error) {
5267
+ ctx.addError(`'${word}' in a tell body failed to parse: ${error instanceof Error ? error.message : String(error)}`);
5149
5268
  break;
5150
5269
  }
5151
5270
  if (ctx.match(KEYWORDS.AND) || ctx.match(KEYWORDS.THEN)) {
@@ -5294,7 +5413,6 @@ const ONE_EXPR = (syntax) => ({
5294
5413
  markers: [],
5295
5414
  syntax,
5296
5415
  });
5297
- const BEEP = ONE_EXPR('beep! [<expression>, \u2026]');
5298
5416
  const COMMAND_GRAMMAR = {
5299
5417
  append: { positional: 'expression', markers: ['to'], syntax: 'append <value> [to <target>]' },
5300
5418
  prepend: { positional: 'expression', markers: ['to'], syntax: 'prepend <value> [to <target>]' },
@@ -5305,8 +5423,7 @@ const COMMAND_GRAMMAR = {
5305
5423
  syntax: 'make (a|an) <type> [from <args>] [called <name>]',
5306
5424
  },
5307
5425
  throw: { positional: 'primary', markers: [], syntax: 'throw <error>' },
5308
- 'beep!': BEEP,
5309
- beep: BEEP,
5426
+ beep: ONE_EXPR('beep! [<expression>, \u2026]'),
5310
5427
  blur: ONE_EXPR('blur [<target>]'),
5311
5428
  break: NONE,
5312
5429
  breakpoint: NONE,
@@ -5330,7 +5447,8 @@ const COMMAND_GRAMMAR = {
5330
5447
  render: {
5331
5448
  positional: 'expression',
5332
5449
  markers: ['with'],
5333
- syntax: 'render <template> [with <variables>]',
5450
+ namedArgs: ['with'],
5451
+ syntax: 'render <template> [with <name>: <value>, \u2026]',
5334
5452
  },
5335
5453
  reset: ONE_EXPR('reset [<form>]'),
5336
5454
  return: ONE_EXPR('return [<value>]'),
@@ -5685,7 +5803,10 @@ class Parser {
5685
5803
  const firstToken = this.peek();
5686
5804
  const prefixEntry = Parser.PRATT_TABLE.get(firstToken.value);
5687
5805
  let left;
5688
- if (prefixEntry?.prefix) {
5806
+ if (isGluedBeepBang(firstToken, this.tokens[this.current + 1])) {
5807
+ left = this.parseBeepExpression();
5808
+ }
5809
+ else if (prefixEntry?.prefix) {
5689
5810
  const token = this.advance();
5690
5811
  if (this.isAtEnd()) {
5691
5812
  this.addError(`Expected expression after '${token.value}' operator`);
@@ -5705,6 +5826,9 @@ class Parser {
5705
5826
  continue;
5706
5827
  }
5707
5828
  }
5829
+ if (nextToken.value === '*' && this.isSpacedStyleRef(this.current)) {
5830
+ break;
5831
+ }
5708
5832
  const infixEntry = Parser.PRATT_TABLE.get(nextToken.value);
5709
5833
  if (!infixEntry?.infix) {
5710
5834
  if (STOP_TOKENS.has(nextToken.value) || STOP_DELIMITERS.has(nextToken.value)) {
@@ -5766,6 +5890,35 @@ class Parser {
5766
5890
  this.prattCache.set(cacheKey, { node: left, endPos: this.current });
5767
5891
  return left;
5768
5892
  }
5893
+ parseBeepExpression() {
5894
+ const beepToken = this.advance();
5895
+ this.advance();
5896
+ if (this.isAtEnd()) {
5897
+ this.addError("Expected expression after 'beep!'");
5898
+ return this.createErrorNode();
5899
+ }
5900
+ const operand = this.parseExpressionPratt(Parser.BEEP_OPERAND_BP);
5901
+ return {
5902
+ type: 'unaryExpression',
5903
+ operator: 'beep!',
5904
+ operand,
5905
+ argument: operand,
5906
+ prefix: true,
5907
+ start: beepToken.start,
5908
+ end: this.previous().end,
5909
+ line: beepToken.line,
5910
+ column: beepToken.column,
5911
+ };
5912
+ }
5913
+ isStyleRefStar(i) {
5914
+ const star = this.tokens[i];
5915
+ const name = this.tokens[i + 1];
5916
+ return star?.value === '*' && !!name && name.start === star.end && isIdentifierLike(name);
5917
+ }
5918
+ isSpacedStyleRef(i) {
5919
+ const prev = this.tokens[i - 1];
5920
+ return this.isStyleRefStar(i) && !!prev && prev.end < this.tokens[i].start;
5921
+ }
5769
5922
  tryParseStringPostfix(left, nextToken) {
5770
5923
  const v = nextToken.value;
5771
5924
  let unit = null;
@@ -5968,6 +6121,10 @@ class Parser {
5968
6121
  const isStop = () => this.check('end') || extraStops.some(s => this.check(s));
5969
6122
  debug.parse('\ud83d\udd04 parseCommandListUntilTerminator: Starting (extraStops:', extraStops.join(','), ')');
5970
6123
  while (!this.isAtEnd() && !isStop()) {
6124
+ if (this.checkComment()) {
6125
+ this.advance();
6126
+ continue;
6127
+ }
5971
6128
  debug.parse('\ud83d\udccd Loop iteration, current token:', this.peek().value, 'kind:', this.peek().kind);
5972
6129
  let parsedCommand = false;
5973
6130
  const isCommandToken = this.checkIsCommand();
@@ -6051,7 +6208,7 @@ class Parser {
6051
6208
  debug.parse('\u2705 Found "end", consuming it');
6052
6209
  this.advance();
6053
6210
  }
6054
- else {
6211
+ else if (!this.atEndOfInput()) {
6055
6212
  debug.parse('\u274c ERROR: Expected "end" but got:', this.peek().value, 'at position:', this.peek().start);
6056
6213
  throw new Error(`Expected "end" to close ${construct} block`);
6057
6214
  }
@@ -6064,7 +6221,7 @@ class Parser {
6064
6221
  if (this.check('end')) {
6065
6222
  this.advance();
6066
6223
  }
6067
- else {
6224
+ else if (!this.atEndOfInput()) {
6068
6225
  throw new Error('Expected "end" to close repeat block');
6069
6226
  }
6070
6227
  }
@@ -6080,7 +6237,10 @@ class Parser {
6080
6237
  this.advance();
6081
6238
  }
6082
6239
  else if (terminator === '') {
6083
- throw new Error('Expected "end", "else", "until", or "while" to close repeat block');
6240
+ if (!this.atEndOfInput()) {
6241
+ throw new Error('Expected "end", "else", "until", or "while" to close repeat block');
6242
+ }
6243
+ return { commands, terminator: 'end' };
6084
6244
  }
6085
6245
  return { commands, terminator };
6086
6246
  }
@@ -6664,7 +6824,9 @@ class Parser {
6664
6824
  parseTopLevelInitBlock() {
6665
6825
  const pos = this.getPosition();
6666
6826
  const initCommands = this.parseCommandBlock(['end']);
6667
- this.consume('end', "Expected 'end' after init block");
6827
+ if (!this.match('end') && !this.atEndOfInput() && !this.checkFeatureStart()) {
6828
+ this.consume('end', "Expected 'end' after init block");
6829
+ }
6668
6830
  return {
6669
6831
  type: 'initBlock',
6670
6832
  commands: initCommands,
@@ -6840,10 +7002,29 @@ class Parser {
6840
7002
  ...(sourceExpr.column !== undefined && { column: sourceExpr.column }),
6841
7003
  };
6842
7004
  }
7005
+ isArgumentListOnly(open) {
7006
+ let depth = 0;
7007
+ for (let i = open; i < this.tokens.length; i++) {
7008
+ const token = this.tokens[i];
7009
+ if (token.kind !== 'operator')
7010
+ continue;
7011
+ if (token.value === '(' || token.value === '[' || token.value === '{')
7012
+ depth++;
7013
+ else if (token.value === ')' || token.value === ']' || token.value === '}') {
7014
+ if (--depth === 0)
7015
+ return i === open + 1;
7016
+ }
7017
+ else if (token.value === ',' && depth === 1)
7018
+ return true;
7019
+ }
7020
+ return false;
7021
+ }
6843
7022
  tryParsePseudoCommand(callExpr, sourceExpr) {
6844
7023
  const methodName = callExpr.callee.name;
6845
7024
  const nextToken = this.peek();
6846
- const hasPseudoCommandPattern = Parser.PSEUDO_COMMAND_PREPOSITIONS.includes(nextToken.value.toLowerCase()) ||
7025
+ const nextIsArticle = nextToken.value.toLowerCase() === 'the';
7026
+ const hasPseudoCommandPattern = nextIsArticle ||
7027
+ Parser.PSEUDO_COMMAND_PREPOSITIONS.includes(nextToken.value.toLowerCase()) ||
6847
7028
  (isIdentifier(nextToken) && !this.isCommand(nextToken.value)) ||
6848
7029
  isContextVar(nextToken);
6849
7030
  if (!hasPseudoCommandPattern) {
@@ -6869,7 +7050,10 @@ class Parser {
6869
7050
  });
6870
7051
  }
6871
7052
  let preposition;
6872
- if (Parser.PSEUDO_COMMAND_PREPOSITIONS.includes(this.peek().value.toLowerCase())) {
7053
+ if (nextIsArticle) {
7054
+ this.advance();
7055
+ }
7056
+ else if (Parser.PSEUDO_COMMAND_PREPOSITIONS.includes(this.peek().value.toLowerCase())) {
6873
7057
  preposition = this.advance().value.toLowerCase();
6874
7058
  }
6875
7059
  let targetExpr;
@@ -7033,10 +7217,22 @@ class Parser {
7033
7217
  this.consume(']', "Expected ']' after event condition");
7034
7218
  }
7035
7219
  let target;
7220
+ let targetExpression;
7036
7221
  if (this.match('from')) {
7037
- const targetToken = this.advance();
7038
- target = targetToken.value;
7039
- debug.parse(`\ud83d\udd27 parseEventHandler: Parsed 'from' target: ${target} (kind: ${targetToken.kind})`);
7222
+ if (this.check('the'))
7223
+ this.advance();
7224
+ if (Parser.POSITIONAL_SOURCE_HEADS.has(this.peek().value.toLowerCase())) {
7225
+ const startIndex = this.current;
7226
+ targetExpression = this.parseExpression();
7227
+ target = this.tokens
7228
+ .slice(startIndex, this.current)
7229
+ .map(t => t.value)
7230
+ .join(' ');
7231
+ }
7232
+ else {
7233
+ target = this.advance().value;
7234
+ }
7235
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed 'from' target: ${target}`);
7040
7236
  this.parseTemporalKeywordModifiers(modifiers);
7041
7237
  }
7042
7238
  let attributeName;
@@ -7082,7 +7278,7 @@ class Parser {
7082
7278
  const nextIsOpenParen = this.tokens[this.current + 1]?.value === '(';
7083
7279
  const commandName = this.peek().value.toLowerCase();
7084
7280
  const isSpecialBodyCommand = commandName === 'js' || commandName === 'tell';
7085
- if (nextIsOpenParen && !isSpecialBodyCommand) {
7281
+ if (nextIsOpenParen && !isSpecialBodyCommand && this.isArgumentListOnly(this.current + 1)) {
7086
7282
  let expr;
7087
7283
  const savedError = this.error;
7088
7284
  try {
@@ -7114,7 +7310,7 @@ class Parser {
7114
7310
  }
7115
7311
  }
7116
7312
  }
7117
- else if (this.checkIdentifier()) {
7313
+ else if (this.checkIdentifier() || this.checkEventNamedCall()) {
7118
7314
  const token = this.peek();
7119
7315
  if (this.isCommand(token.value)) {
7120
7316
  const cmd = this.parseCommandWithErrorRecovery();
@@ -7223,6 +7419,7 @@ class Parser {
7223
7419
  ...(eventParams.length > 0 && { args: eventParams }),
7224
7420
  ...(condition && { condition: fromLegacyExpression(condition) }),
7225
7421
  ...(target && { target }),
7422
+ ...(targetExpression && { targetExpression: fromLegacyExpression(targetExpression) }),
7226
7423
  ...(attributeName && { attributeName }),
7227
7424
  ...(watchTarget && { watchTarget: fromLegacyExpression(watchTarget) }),
7228
7425
  ...(customEventSource && { customEventSource }),
@@ -7478,10 +7675,9 @@ class Parser {
7478
7675
  }
7479
7676
  parseCommandCore() {
7480
7677
  const commandToken = this.previous();
7481
- let commandName = commandToken.value;
7678
+ const commandName = commandToken.value;
7482
7679
  if (commandName === 'beep' && this.check('!')) {
7483
7680
  this.advance();
7484
- commandName = 'beep!';
7485
7681
  }
7486
7682
  if (commandName === 'fetch') {
7487
7683
  return parseFetchCommand(this.getContext(), commandToken);
@@ -7580,6 +7776,9 @@ class Parser {
7580
7776
  args.push(this.parsePrimary());
7581
7777
  }
7582
7778
  const callNode = this.createCallExpression(createIdentifier(funcName, namePos), args);
7779
+ if (funcName === 'closest' && args.length > 0 && this.match('to')) {
7780
+ callNode.closestTo = this.parseExpression();
7781
+ }
7583
7782
  if (funcName === 'next' || funcName === 'previous') {
7584
7783
  const relativePositional = this.parseRelativePositionalModifiers();
7585
7784
  if (relativePositional) {
@@ -7619,6 +7818,9 @@ class Parser {
7619
7818
  }
7620
7819
  parseTheXofY() {
7621
7820
  const positionalKeywords = ['first', 'last', 'next', 'previous', 'random', 'closest'];
7821
+ if (this.isStyleRefStar(this.current)) {
7822
+ return this.parsePrimary();
7823
+ }
7622
7824
  if (!this.checkIdentifier()) {
7623
7825
  return this.createIdentifier('the');
7624
7826
  }
@@ -7634,7 +7836,7 @@ class Parser {
7634
7836
  return this.createIdentifier(propertyName);
7635
7837
  }
7636
7838
  this.advance();
7637
- const target = this.parseCall();
7839
+ const target = parseOfOperand(this.makePrattContext(), THE_OF_TARGET_BP);
7638
7840
  return {
7639
7841
  type: 'propertyOfExpression',
7640
7842
  property: { type: 'identifier', name: propertyName },
@@ -7832,6 +8034,13 @@ class Parser {
7832
8034
  checkLiteral() {
7833
8035
  return this.checkPredicate(isLiteral);
7834
8036
  }
8037
+ checkEventNamedCall() {
8038
+ if (this.isAtEnd() || !this.checkIdentifierLike() || this.checkIsCommand())
8039
+ return false;
8040
+ const token = this.peek();
8041
+ const next = this.tokens[this.current + 1];
8042
+ return next?.value === '(' && next.start === token.end;
8043
+ }
7835
8044
  checkIdentifierLike() {
7836
8045
  return this.checkPredicate(isIdentifierLike);
7837
8046
  }
@@ -7940,6 +8149,22 @@ class Parser {
7940
8149
  isAtEnd() {
7941
8150
  return this.current >= this.tokens.length;
7942
8151
  }
8152
+ atEndOfInput() {
8153
+ for (let i = this.current; i < this.tokens.length; i++) {
8154
+ if (!isComment(this.tokens[i]))
8155
+ return false;
8156
+ }
8157
+ return true;
8158
+ }
8159
+ checkFeatureStart() {
8160
+ if (this.isAtEnd())
8161
+ return false;
8162
+ const value = this.peek().value;
8163
+ return (value === 'init' ||
8164
+ value === 'on' ||
8165
+ value === 'def' ||
8166
+ getRegisteredFeature(value) !== undefined);
8167
+ }
7943
8168
  peek() {
7944
8169
  if (this.isAtEnd()) {
7945
8170
  return { kind: 'unknown', value: '', start: 0, end: 0, line: 1, column: 1 };
@@ -8151,6 +8376,7 @@ class Parser {
8151
8376
  match: this.match.bind(this),
8152
8377
  matchOperator: this.matchOperator.bind(this),
8153
8378
  isAtEnd: this.isAtEnd.bind(this),
8379
+ atEndOfInput: this.atEndOfInput.bind(this),
8154
8380
  createIdentifier: this.createIdentifier.bind(this),
8155
8381
  parseExpression: this.parseExpression.bind(this),
8156
8382
  parsePrimary: this.parsePrimary.bind(this),
@@ -8201,7 +8427,15 @@ Parser.POSTFIX_UNARY_OPERATORS = new Set([
8201
8427
  'ignoring case',
8202
8428
  ]);
8203
8429
  Parser.PRATT_TABLE = PARSER_TABLE;
8430
+ Parser.BEEP_OPERAND_BP = 80;
8204
8431
  Parser.PSEUDO_COMMAND_PREPOSITIONS = ['from', 'on', 'with', 'into', 'at', 'to'];
8432
+ Parser.POSITIONAL_SOURCE_HEADS = new Set([
8433
+ 'closest',
8434
+ 'next',
8435
+ 'previous',
8436
+ 'first',
8437
+ 'last',
8438
+ ]);
8205
8439
  function parse(input, options) {
8206
8440
  const tokens = tokenize(input);
8207
8441
  const parser = new Parser(tokens, options, input);
@@ -8406,6 +8640,89 @@ function isDocumentFragment(value) {
8406
8640
  'childNodes' in value);
8407
8641
  }
8408
8642
 
8643
+ function beepValues(me, values) {
8644
+ const outputs = values.map(describeBeepValue);
8645
+ const shown = outputs.filter(output => announce(me, output.value));
8646
+ if (shown.length === 0)
8647
+ return outputs;
8648
+ console.group('\ud83d\udd14 beep! Debug Output');
8649
+ for (const output of shown) {
8650
+ console.log(`Value:`, output.value);
8651
+ console.log(`Type:`, output.type);
8652
+ console.log(`Representation:`, output.representation);
8653
+ console.log('---');
8654
+ }
8655
+ console.groupEnd();
8656
+ return outputs;
8657
+ }
8658
+ function announce(me, value) {
8659
+ const target = me;
8660
+ if (typeof CustomEvent !== 'function' || typeof target?.dispatchEvent !== 'function')
8661
+ return true;
8662
+ return target.dispatchEvent(new CustomEvent('hyperscript:beep', {
8663
+ bubbles: true,
8664
+ cancelable: true,
8665
+ detail: { element: me, value },
8666
+ }));
8667
+ }
8668
+ function describeBeepValue(value) {
8669
+ return { value, type: typeOf(value), representation: representationOf(value) };
8670
+ }
8671
+ function typeOf(value) {
8672
+ if (value === null)
8673
+ return 'null';
8674
+ if (value === undefined)
8675
+ return 'undefined';
8676
+ if (Array.isArray(value))
8677
+ return 'array';
8678
+ if (isHTMLElement$2(value))
8679
+ return 'HTMLElement';
8680
+ if (typeof Element !== 'undefined' && value instanceof Element)
8681
+ return 'Element';
8682
+ if (typeof Node !== 'undefined' && value instanceof Node)
8683
+ return 'Node';
8684
+ if (value instanceof Error)
8685
+ return 'Error';
8686
+ if (value instanceof Date)
8687
+ return 'Date';
8688
+ if (value instanceof RegExp)
8689
+ return 'RegExp';
8690
+ return typeof value;
8691
+ }
8692
+ function representationOf(value) {
8693
+ if (value === null)
8694
+ return 'null';
8695
+ if (value === undefined)
8696
+ return 'undefined';
8697
+ if (Array.isArray(value)) {
8698
+ return `Array(${value.length}) [${value
8699
+ .slice(0, 3)
8700
+ .map(v => representationOf(v))
8701
+ .join(', ')}${value.length > 3 ? '...' : ''}]`;
8702
+ }
8703
+ if (isHTMLElement$2(value)) {
8704
+ const el = value;
8705
+ const tag = el.tagName.toLowerCase();
8706
+ const id = el.id ? `#${el.id}` : '';
8707
+ const classes = el.className ? `.${el.className.split(' ').join('.')}` : '';
8708
+ return `<${tag}${id}${classes}/>`;
8709
+ }
8710
+ if (value instanceof Error)
8711
+ return `Error: ${value.message}`;
8712
+ if (typeof value === 'string')
8713
+ return value.length > 50 ? `"${value.substring(0, 47)}..."` : `"${value}"`;
8714
+ if (typeof value === 'object') {
8715
+ try {
8716
+ const keys = Object.keys(value);
8717
+ return `Object {${keys.slice(0, 3).join(', ')}${keys.length > 3 ? '...' : ''}}`;
8718
+ }
8719
+ catch {
8720
+ return '[Object]';
8721
+ }
8722
+ }
8723
+ return String(value);
8724
+ }
8725
+
8409
8726
  function convertToNumber(value) {
8410
8727
  if (value === null || value === undefined) {
8411
8728
  return 0;
@@ -8437,6 +8754,16 @@ function convertToNumber(value) {
8437
8754
  }
8438
8755
  return 0;
8439
8756
  }
8757
+ function counterAmount(value) {
8758
+ return typeof value === 'number' ? value : parseFloat(String(value));
8759
+ }
8760
+ function counterValue(value) {
8761
+ if (isHTMLElement$2(value)) {
8762
+ const element = value;
8763
+ return counterValue(element.value || element.textContent);
8764
+ }
8765
+ return value ? counterAmount(value) : 0;
8766
+ }
8440
8767
  function getVariableValue(name, context, preferredScope) {
8441
8768
  if (preferredScope === 'element') {
8442
8769
  return getElementVar(context, name);
@@ -8541,35 +8868,39 @@ function setElementPropertyValue(propertyPath, value, context) {
8541
8868
  element[property] = value;
8542
8869
  }
8543
8870
  }
8871
+ function isObjectOwner(value) {
8872
+ return (typeof value === 'object' && value !== null && !Array.isArray(value) && !isHTMLElement$2(value));
8873
+ }
8544
8874
  function getCurrentNumericValue(target, property, scope, context) {
8545
8875
  if (typeof target === 'number') {
8546
8876
  return target;
8547
8877
  }
8548
8878
  if (isHTMLElement$2(target)) {
8549
8879
  const element = target;
8880
+ if (property?.startsWith('@'))
8881
+ return counterValue(element.getAttribute(property.slice(1)));
8550
8882
  if (property) {
8551
8883
  if (property.startsWith('data-') ||
8552
8884
  ['id', 'class', 'title', 'alt', 'src', 'href'].includes(property)) {
8553
- const value = element.getAttribute(property);
8554
- return convertToNumber(value);
8885
+ return counterValue(element.getAttribute(property));
8555
8886
  }
8556
8887
  else {
8557
- const value = element[property];
8558
- return convertToNumber(value);
8888
+ return counterValue(element[property]);
8559
8889
  }
8560
8890
  }
8561
8891
  else {
8562
- const value = element.value || element.textContent;
8563
- return convertToNumber(value);
8892
+ return counterValue(element);
8564
8893
  }
8565
8894
  }
8895
+ if (property && isObjectOwner(target)) {
8896
+ return counterValue(target[property]);
8897
+ }
8566
8898
  if (typeof target === 'string') {
8567
8899
  if (scope === 'element') {
8568
- return convertToNumber(getVariableValue(target, context, 'element'));
8900
+ return counterValue(getVariableValue(target, context, 'element'));
8569
8901
  }
8570
8902
  if (scope === 'global') {
8571
- const value = getVariableValue(target, context, 'global');
8572
- return convertToNumber(value);
8903
+ return counterValue(getVariableValue(target, context, 'global'));
8573
8904
  }
8574
8905
  if (target.includes('.')) {
8575
8906
  return getElementPropertyValue(target, context);
@@ -8583,14 +8914,17 @@ function getCurrentNumericValue(target, property, scope, context) {
8583
8914
  else if (target === 'you' && context.you) {
8584
8915
  return convertToNumber(context.you.value || 0);
8585
8916
  }
8586
- const value = getVariableValue(target, context);
8587
- return convertToNumber(value);
8917
+ return counterValue(getVariableValue(target, context));
8588
8918
  }
8589
- return convertToNumber(target);
8919
+ return counterValue(target);
8590
8920
  }
8591
8921
  function setTargetValue(target, property, scope, newValue, context) {
8592
8922
  if (isHTMLElement$2(target)) {
8593
8923
  const element = target;
8924
+ if (property?.startsWith('@')) {
8925
+ element.setAttribute(property.slice(1), String(newValue));
8926
+ return;
8927
+ }
8594
8928
  if (property) {
8595
8929
  if (property.startsWith('data-') ||
8596
8930
  ['id', 'class', 'title', 'alt', 'src', 'href'].includes(property)) {
@@ -8610,6 +8944,10 @@ function setTargetValue(target, property, scope, newValue, context) {
8610
8944
  }
8611
8945
  return;
8612
8946
  }
8947
+ if (property && isObjectOwner(target)) {
8948
+ target[property] = newValue;
8949
+ return;
8950
+ }
8613
8951
  if (typeof target === 'string') {
8614
8952
  if (scope === 'element') {
8615
8953
  setVariableValue(target, newValue, context, 'element');
@@ -9038,13 +9376,6 @@ function isFunction(value) {
9038
9376
  function isInputElement$1(el) {
9039
9377
  return (el !== null && typeof el === 'object' && 'tagName' in el && el.tagName === 'INPUT');
9040
9378
  }
9041
- function isFormElement$1(el) {
9042
- if (el === null || typeof el !== 'object' || !('tagName' in el)) {
9043
- return false;
9044
- }
9045
- const tag = el.tagName;
9046
- return tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA';
9047
- }
9048
9379
  function isHTMLElement$1(el) {
9049
9380
  return (el !== null &&
9050
9381
  typeof el === 'object' &&
@@ -9104,7 +9435,7 @@ const SPECIAL_DOM_PROPERTIES = {
9104
9435
  innertext: el => el.textContent?.trim(),
9105
9436
  innerHTML: el => el.innerHTML,
9106
9437
  outerhtml: el => el.outerHTML,
9107
- value: el => (isFormElement$1(el) ? el.value : undefined),
9438
+ value: el => ('value' in el ? el.value : undefined),
9108
9439
  checked: el => (isInputElement$1(el) ? el.checked : undefined),
9109
9440
  disabled: el => ('disabled' in el ? el.disabled : undefined),
9110
9441
  selected: el => (isOptionElement$1(el) ? el.selected : undefined),
@@ -9772,14 +10103,15 @@ const closestExpression = {
9772
10103
  if (typeof selector !== 'string') {
9773
10104
  throw new Error('closest requires a string selector');
9774
10105
  }
9775
- if (!context.me) {
10106
+ const start = args.length > 1 ? args[1] : context.me;
10107
+ if (!(start instanceof Element)) {
9776
10108
  return null;
9777
10109
  }
9778
- return context.me.closest(selector);
10110
+ return start.closest(selector);
9779
10111
  },
9780
10112
  validate(args) {
9781
- if (args.length !== 1) {
9782
- return 'closest requires exactly 1 argument (selector)';
10113
+ if (args.length !== 1 && args.length !== 2) {
10114
+ return 'closest requires a selector and, optionally, the element to start from';
9783
10115
  }
9784
10116
  const arg = args[0];
9785
10117
  if (typeof arg === 'string')
@@ -11279,7 +11611,7 @@ const isEmptyExpression = {
11279
11611
  else if (value instanceof Node || value instanceof Element)
11280
11612
  result = false;
11281
11613
  else if (isObject(value))
11282
- result = Object.keys(value).length === 0;
11614
+ result = value.length === 0;
11283
11615
  else
11284
11616
  result = false;
11285
11617
  return result;
@@ -12403,11 +12735,16 @@ async function evaluateBinaryExpression(node, context) {
12403
12735
  }
12404
12736
  }
12405
12737
  }
12406
- if (leftNode?.type === 'selector' && leftNode.fromQuery && typeof leftNode.value === 'string') {
12738
+ if (leftNode?.type === 'selector' &&
12739
+ typeof leftNode.value === 'string' &&
12740
+ (leftNode.fromQuery || (operator === 'in' && leftNode.value.startsWith('.')))) {
12407
12741
  const root = await evaluateAST(node.right, context);
12408
12742
  const scope = root && typeof root.querySelectorAll === 'function' ? root : null;
12409
12743
  if (scope) {
12410
- return Array.from(scope.querySelectorAll(leftNode.value));
12744
+ const query = leftNode.fromQuery
12745
+ ? leftNode.value
12746
+ : escapeSelectorForQuery(leftNode, leftNode.value);
12747
+ return Array.from(scope.querySelectorAll(query));
12411
12748
  }
12412
12749
  }
12413
12750
  }
@@ -12419,6 +12756,12 @@ async function evaluateBinaryExpression(node, context) {
12419
12756
  return getExpr(context, 'styleRef').evaluate(context, leftNode.value.slice(1), el);
12420
12757
  }
12421
12758
  if (operator === 'of') {
12759
+ if (leftNode?.type === 'attributeAccess') {
12760
+ const target = await evaluateAST(node.right, context);
12761
+ if (target == null)
12762
+ return undefined;
12763
+ return getExpr(context, 'possessive').evaluate(context, target, `@${leftNode.attributeName}`);
12764
+ }
12422
12765
  const path = propertyPathOf(node.left);
12423
12766
  if (path) {
12424
12767
  const target = await evaluateAST(node.right, context);
@@ -12426,6 +12769,10 @@ async function evaluateBinaryExpression(node, context) {
12426
12769
  for (const seg of path) {
12427
12770
  if (cur == null)
12428
12771
  return undefined;
12772
+ if (isMappableCollection(cur) && !(seg in cur)) {
12773
+ cur = await getExpr(context, 'possessive').evaluate(context, cur, seg);
12774
+ continue;
12775
+ }
12429
12776
  cur = isElement(cur) ? getElementProperty$1(cur, seg) : cur[seg];
12430
12777
  }
12431
12778
  return typeof cur === 'function' ? cur.bind(target) : cur;
@@ -12450,26 +12797,21 @@ async function evaluateBinaryExpression(node, context) {
12450
12797
  const R = node.ignoringCase ? applyCI(right) : right;
12451
12798
  switch (operator) {
12452
12799
  case '+': {
12453
- let addLeft = left;
12454
- let addRight = right;
12455
12800
  if (node.coerceNumeric) {
12456
- if (typeof addLeft === 'string')
12457
- addLeft = convertToNumber(addLeft);
12458
- if (typeof addRight === 'string')
12459
- addRight = convertToNumber(addRight);
12801
+ return counterValue(left) + counterAmount(right);
12460
12802
  }
12461
- if (typeof addLeft === 'string' || typeof addRight === 'string') {
12462
- return String(addLeft ?? '') + String(addRight ?? '');
12803
+ if (typeof left === 'string' || typeof right === 'string') {
12804
+ return String(left ?? '') + String(right ?? '');
12463
12805
  }
12464
- if (Array.isArray(addLeft)) {
12465
- return Array.isArray(addRight) ? [...addLeft, ...addRight] : [...addLeft, addRight];
12806
+ if (Array.isArray(left)) {
12807
+ return Array.isArray(right) ? [...left, ...right] : [...left, right];
12466
12808
  }
12467
- return unwrapTypedResult(await getExpr(context, 'addition').evaluate(context, {
12468
- left: addLeft,
12469
- right: addRight,
12470
- }));
12809
+ return unwrapTypedResult(await getExpr(context, 'addition').evaluate(context, { left, right }));
12471
12810
  }
12472
12811
  case '-':
12812
+ if (node.coerceNumeric) {
12813
+ return counterValue(left) - counterAmount(right);
12814
+ }
12473
12815
  return unwrapTypedResult(await getExpr(context, 'subtraction').evaluate(context, { left, right }));
12474
12816
  case '*':
12475
12817
  return unwrapTypedResult(await getExpr(context, 'multiplication').evaluate(context, { left, right }));
@@ -12727,9 +13069,8 @@ async function evaluatePropertyOfExpressionNode(node, context) {
12727
13069
  }
12728
13070
  const propertyName = propertyNode.name;
12729
13071
  const target = await evaluateAST(node.target, context);
12730
- if (target == null) {
12731
- throw new Error(`Cannot access property "${propertyName}" of ${target}`);
12732
- }
13072
+ if (target == null)
13073
+ return undefined;
12733
13074
  return getExpr(context, 'possessive').evaluate(context, target, propertyName);
12734
13075
  }
12735
13076
  async function evaluateTemplateLiteralNode(node, context) {
@@ -12847,6 +13188,9 @@ async function evaluateUnaryExpression(node, context) {
12847
13188
  case 'not':
12848
13189
  case '!':
12849
13190
  return logicalExpressions.not.evaluate(context, value);
13191
+ case 'beep!':
13192
+ beepValues(context.me, [value]);
13193
+ return value;
12850
13194
  case 'no':
12851
13195
  return logicalExpressions.no.evaluate(context, value);
12852
13196
  case '-':
@@ -12936,6 +13280,10 @@ async function evaluateCallExpression(node, context) {
12936
13280
  const args = await resolveCallArgs(node.arguments, funcName, context);
12937
13281
  switch (funcName) {
12938
13282
  case 'closest':
13283
+ if (node.closestTo) {
13284
+ const start = unwrapElement(await evaluateAST(node.closestTo, context)) ?? null;
13285
+ return referencesExpressions.closest.evaluate(context, args[0], start);
13286
+ }
12939
13287
  return referencesExpressions.closest.evaluate(context, ...args);
12940
13288
  case 'previous':
12941
13289
  case 'next': {
@@ -14322,12 +14670,23 @@ class RuntimeBase {
14322
14670
  debug.runtime(`BEHAVIOR: Finished installing ${behaviorName}`);
14323
14671
  }
14324
14672
  async executeEventHandler(node, context) {
14325
- const { event, events, commands, target, args, selector, condition, attributeName, watchTarget, modifiers, errorSymbol, errorHandler, finallyHandler, } = node;
14673
+ const { event, events, commands, target, targetExpression, args, selector, condition, attributeName, watchTarget, modifiers, errorSymbol, errorHandler, finallyHandler, } = node;
14326
14674
  const eventNames = events && events.length > 0 ? events : [event];
14327
14675
  debug.runtime(`BEHAVIOR: executeEventHandler: event='${event}', target='${target}'`);
14328
14676
  let targets = [];
14329
14677
  let globalTarget = null;
14330
- if (target) {
14678
+ if (targetExpression) {
14679
+ const resolved = await this.execute(targetExpression, context);
14680
+ if (resolved === window)
14681
+ globalTarget = window;
14682
+ else if (resolved === document)
14683
+ globalTarget = document;
14684
+ else if (this.isElement(resolved))
14685
+ targets = [resolved];
14686
+ else if (Array.isArray(resolved))
14687
+ targets = resolved.filter(el => this.isElement(el));
14688
+ }
14689
+ else if (target) {
14331
14690
  const targetLower = typeof target === 'string' ? target.toLowerCase() : '';
14332
14691
  if (targetLower === 'window' || targetLower === 'the window') {
14333
14692
  globalTarget = window;
@@ -16348,14 +16707,83 @@ function parseAttribute(expression) {
16348
16707
  if (trimmed.startsWith('@')) {
16349
16708
  return { name: trimmed.substring(1).trim() };
16350
16709
  }
16351
- throw new Error(`Invalid attribute syntax: ${expression}`);
16352
- }
16353
- function parseAttributeName(expression) {
16354
- return parseAttribute(expression).name;
16710
+ throw new Error(`Invalid attribute syntax: ${expression}`);
16711
+ }
16712
+ function parseAttributeName(expression) {
16713
+ return parseAttribute(expression).name;
16714
+ }
16715
+ function parseAttributeWithValue(expression) {
16716
+ const result = parseAttribute(expression);
16717
+ return { name: result.name, value: result.value ?? '' };
16718
+ }
16719
+
16720
+ function _parseDurationComponents(value) {
16721
+ const trimmed = value.trim();
16722
+ const match = trimmed.match(/^(\d+(?:\.\d+)?)\s*(s|ms|sec|seconds?|milliseconds?)?$/i);
16723
+ if (!match) {
16724
+ return null;
16725
+ }
16726
+ const number = parseFloat(match[1]);
16727
+ const unit = (match[2] || 'ms').toLowerCase();
16728
+ if (unit === 'ms' || unit === 'millisecond' || unit === 'milliseconds') {
16729
+ return { number, milliseconds: Math.floor(number) };
16730
+ }
16731
+ else if (unit === 's' || unit === 'sec' || unit === 'second' || unit === 'seconds') {
16732
+ return { number, milliseconds: Math.floor(number * 1000) };
16733
+ }
16734
+ return null;
16735
+ }
16736
+ function parseDuration(value, defaultMs = 300) {
16737
+ if (typeof value === 'number') {
16738
+ return Math.max(0, Math.floor(value));
16739
+ }
16740
+ if (typeof value === 'string') {
16741
+ const result = _parseDurationComponents(value);
16742
+ return result ? result.milliseconds : defaultMs;
16743
+ }
16744
+ return defaultMs;
16745
+ }
16746
+ function parseDurationStrict(value) {
16747
+ if (typeof value === 'number') {
16748
+ if (value < 0)
16749
+ throw new Error('Duration must be >= 0');
16750
+ return Math.floor(value);
16751
+ }
16752
+ if (typeof value === 'string') {
16753
+ const result = _parseDurationComponents(value);
16754
+ if (!result) {
16755
+ throw new Error(`Invalid duration format: "${value}"`);
16756
+ }
16757
+ return result.milliseconds;
16758
+ }
16759
+ throw new Error(`Invalid duration type: ${typeof value}`);
16760
+ }
16761
+ function parseCSSDurations(durationString) {
16762
+ if (!durationString || durationString === 'none') {
16763
+ return [0];
16764
+ }
16765
+ return durationString.split(',').map(duration => {
16766
+ const value = parseFloat(duration.trim());
16767
+ if (isNaN(value))
16768
+ return 0;
16769
+ if (duration.includes('s') && !duration.includes('ms')) {
16770
+ return value * 1000;
16771
+ }
16772
+ return value;
16773
+ });
16774
+ }
16775
+ function calculateMaxAnimationTime(durations, delays) {
16776
+ let maxTime = 0;
16777
+ for (let i = 0; i < durations.length; i++) {
16778
+ const duration = durations[i] || 0;
16779
+ const delay = delays[i] || 0;
16780
+ const totalTime = duration + delay;
16781
+ maxTime = Math.max(maxTime, totalTime);
16782
+ }
16783
+ return maxTime;
16355
16784
  }
16356
- function parseAttributeWithValue(expression) {
16357
- const result = parseAttribute(expression);
16358
- return { name: result.name, value: result.value ?? '' };
16785
+ function camelToKebab(str) {
16786
+ return str.replace(/([A-Z])/g, '-$1').toLowerCase();
16359
16787
  }
16360
16788
 
16361
16789
  function batchApply(targets, operation) {
@@ -16423,15 +16851,18 @@ function toggleAttribute(element, name, value) {
16423
16851
  }
16424
16852
  }
16425
16853
  }
16854
+ function toCssPropertyName(property) {
16855
+ return property.startsWith('--') ? property : camelToKebab(property);
16856
+ }
16426
16857
  function batchSetStyles(targets, styles) {
16427
16858
  const entries = Object.entries(styles);
16428
16859
  return batchApplyItems(targets, entries, (element, [property, value]) => {
16429
- element.style.setProperty(property, value);
16860
+ element.style.setProperty(toCssPropertyName(property), value);
16430
16861
  });
16431
16862
  }
16432
16863
  function batchRemoveStyles(targets, properties) {
16433
16864
  return batchApplyItems(targets, properties, (element, property) => {
16434
- element.style.removeProperty(property);
16865
+ element.style.removeProperty(toCssPropertyName(property));
16435
16866
  });
16436
16867
  }
16437
16868
 
@@ -17097,75 +17528,6 @@ let RemoveCommand = (() => {
17097
17528
  })();
17098
17529
  const createRemoveCommand = createFactory(RemoveCommand);
17099
17530
 
17100
- function _parseDurationComponents(value) {
17101
- const trimmed = value.trim();
17102
- const match = trimmed.match(/^(\d+(?:\.\d+)?)\s*(s|ms|sec|seconds?|milliseconds?)?$/i);
17103
- if (!match) {
17104
- return null;
17105
- }
17106
- const number = parseFloat(match[1]);
17107
- const unit = (match[2] || 'ms').toLowerCase();
17108
- if (unit === 'ms' || unit === 'millisecond' || unit === 'milliseconds') {
17109
- return { number, milliseconds: Math.floor(number) };
17110
- }
17111
- else if (unit === 's' || unit === 'sec' || unit === 'second' || unit === 'seconds') {
17112
- return { number, milliseconds: Math.floor(number * 1000) };
17113
- }
17114
- return null;
17115
- }
17116
- function parseDuration(value, defaultMs = 300) {
17117
- if (typeof value === 'number') {
17118
- return Math.max(0, Math.floor(value));
17119
- }
17120
- if (typeof value === 'string') {
17121
- const result = _parseDurationComponents(value);
17122
- return result ? result.milliseconds : defaultMs;
17123
- }
17124
- return defaultMs;
17125
- }
17126
- function parseDurationStrict(value) {
17127
- if (typeof value === 'number') {
17128
- if (value < 0)
17129
- throw new Error('Duration must be >= 0');
17130
- return Math.floor(value);
17131
- }
17132
- if (typeof value === 'string') {
17133
- const result = _parseDurationComponents(value);
17134
- if (!result) {
17135
- throw new Error(`Invalid duration format: "${value}"`);
17136
- }
17137
- return result.milliseconds;
17138
- }
17139
- throw new Error(`Invalid duration type: ${typeof value}`);
17140
- }
17141
- function parseCSSDurations(durationString) {
17142
- if (!durationString || durationString === 'none') {
17143
- return [0];
17144
- }
17145
- return durationString.split(',').map(duration => {
17146
- const value = parseFloat(duration.trim());
17147
- if (isNaN(value))
17148
- return 0;
17149
- if (duration.includes('s') && !duration.includes('ms')) {
17150
- return value * 1000;
17151
- }
17152
- return value;
17153
- });
17154
- }
17155
- function calculateMaxAnimationTime(durations, delays) {
17156
- let maxTime = 0;
17157
- for (let i = 0; i < durations.length; i++) {
17158
- const duration = durations[i] || 0;
17159
- const delay = delays[i] || 0;
17160
- const totalTime = duration + delay;
17161
- maxTime = Math.max(maxTime, totalTime);
17162
- }
17163
- return maxTime;
17164
- }
17165
- function camelToKebab(str) {
17166
- return str.replace(/([A-Z])/g, '-$1').toLowerCase();
17167
- }
17168
-
17169
17531
  function detectSmartElementType(elements) {
17170
17532
  if (elements.length === 0)
17171
17533
  return null;
@@ -18035,7 +18397,11 @@ let PutCommand = (() => {
18035
18397
  }
18036
18398
  if (isInsertableNode(v))
18037
18399
  return v;
18038
- return v == null ? '' : String(v);
18400
+ if (v == null)
18401
+ return 'null';
18402
+ if (Array.isArray(v))
18403
+ return arrayFragment(v);
18404
+ return String(v);
18039
18405
  }
18040
18406
  looksLikeCss(s) {
18041
18407
  if (!s)
@@ -18120,6 +18486,19 @@ let PutCommand = (() => {
18120
18486
  })();
18121
18487
  return PutCommand = _classThis;
18122
18488
  })();
18489
+ function arrayFragment(items) {
18490
+ const fragment = document.createDocumentFragment();
18491
+ for (const item of items) {
18492
+ if (isInsertableNode(item)) {
18493
+ fragment.append(item);
18494
+ continue;
18495
+ }
18496
+ const template = document.createElement('template');
18497
+ template.innerHTML = item === null ? '' : String(item);
18498
+ fragment.append(template.content);
18499
+ }
18500
+ return fragment;
18501
+ }
18123
18502
  const createPutCommand = createFactory(PutCommand);
18124
18503
 
18125
18504
  const HTML_ESCAPE = {
@@ -19337,10 +19716,29 @@ const STRATEGY_KEYWORDS = {
19337
19716
  innermorph: 'morph',
19338
19717
  outermorph: 'morphOuter',
19339
19718
  };
19719
+ function loneRootWithTag(content, tagName) {
19720
+ let root;
19721
+ if (typeof content === 'string') {
19722
+ const template = document.createElement('template');
19723
+ template.innerHTML = content;
19724
+ root = template.content.firstElementChild;
19725
+ if (!root || root.nextElementSibling)
19726
+ return null;
19727
+ }
19728
+ else {
19729
+ root = content.cloneNode(true);
19730
+ }
19731
+ return root.tagName === tagName && isHTMLElement$2(root) ? root : null;
19732
+ }
19340
19733
  function executeSwap(target, content, strategy, morphOptions) {
19341
19734
  const contentStr = content !== null && !isHTMLElement$2(content) ? content : '';
19342
19735
  const contentEl = isHTMLElement$2(content) ? content : null;
19343
19736
  switch (strategy) {
19737
+ case 'morphMerge': {
19738
+ const root = content !== null ? loneRootWithTag(content, target.tagName) : null;
19739
+ executeSwap(target, root ?? content, root ? 'morphOuter' : 'morph', morphOptions);
19740
+ break;
19741
+ }
19344
19742
  case 'morph':
19345
19743
  if (content !== null) {
19346
19744
  try {
@@ -19630,7 +20028,7 @@ let MorphCommand = (() => {
19630
20028
  if (args.length === 0 && modifiers.with === undefined) {
19631
20029
  throw new Error('[HyperFixi] morph: command requires arguments');
19632
20030
  }
19633
- const strategy = literalText(modifiers.strategy)?.toLowerCase() === 'over' ? 'morphOuter' : 'morph';
20031
+ const strategy = literalText(modifiers.strategy)?.toLowerCase() === 'over' ? 'morphOuter' : 'morphMerge';
19634
20032
  const useViewTransition = modifiers.viewTransition !== undefined;
19635
20033
  const targetNode = modifiers.on !== undefined ? modifiers.on : args[0];
19636
20034
  const contentNode = modifiers.with !== undefined
@@ -20936,6 +21334,19 @@ async function resolveWriteTarget(node, evaluator, context, options) {
20936
21334
  }
20937
21335
  return { kind: 'property', target: property };
20938
21336
  }
21337
+ if (options.ofAndObjectProperty) {
21338
+ const access = ofOrPossessiveAccess(n);
21339
+ if (access) {
21340
+ const owner = await evaluator.evaluate(access.owner, context);
21341
+ const element = Array.isArray(owner) ? owner[0] : owner;
21342
+ if (access.isOf && isHTMLElement$2(element)) {
21343
+ return { kind: 'property', target: { element, property: access.name } };
21344
+ }
21345
+ if (isObjectOwner(owner)) {
21346
+ return { kind: 'member', container: owner, property: access.name };
21347
+ }
21348
+ }
21349
+ }
20939
21350
  if (options.bareReference &&
20940
21351
  (nodeType === 'identifier' || nodeType === 'variable' || nodeType === 'symbol') &&
20941
21352
  typeof n.name === 'string') {
@@ -20945,6 +21356,19 @@ async function resolveWriteTarget(node, evaluator, context, options) {
20945
21356
  }
20946
21357
  return null;
20947
21358
  }
21359
+ function ofOrPossessiveAccess(n) {
21360
+ const [owner, property, isOf] = n.type === 'possessiveExpression'
21361
+ ? [n.object, n.property, false]
21362
+ : n.type === 'propertyOfExpression'
21363
+ ? [n.target, n.property, false]
21364
+ : n.type === 'binaryExpression' && n.operator === 'of'
21365
+ ? [n.right, n.left, true]
21366
+ : [undefined, undefined, false];
21367
+ const name = property?.name;
21368
+ if (!owner || typeof name !== 'string' || !/^[A-Za-z_$][\w$]*$/.test(name))
21369
+ return null;
21370
+ return { owner, name, isOf };
21371
+ }
20948
21372
 
20949
21373
  function toSetInput(target, value) {
20950
21374
  switch (target.kind) {
@@ -20966,10 +21390,18 @@ function toSetInput(target, value) {
20966
21390
  property: target.target.property,
20967
21391
  value,
20968
21392
  };
21393
+ case 'member':
21394
+ return {
21395
+ type: 'member-assignment',
21396
+ container: target.container,
21397
+ property: target.property,
21398
+ value,
21399
+ };
20969
21400
  default:
20970
21401
  throw new Error(`set: unrequested write-target rung '${target.kind}'`);
20971
21402
  }
20972
21403
  }
21404
+ const SET_RUNGS = { nodeWriters: true, styleSplit: true, ofAndObjectProperty: true };
20973
21405
  let SetCommand = (() => {
20974
21406
  let _classDecorators = [command({ name: 'set' })];
20975
21407
  let _classDescriptor;
@@ -20988,8 +21420,7 @@ let SetCommand = (() => {
20988
21420
  const argScope = firstArg?.scope;
20989
21421
  const writeTarget = await resolveWriteTarget(firstArg, evaluator, context, {
20990
21422
  scopeElements: () => this.resolveTargets(raw.modifiers.on, evaluator, context),
20991
- nodeWriters: true,
20992
- styleSplit: true,
21423
+ ...SET_RUNGS,
20993
21424
  });
20994
21425
  if (writeTarget) {
20995
21426
  return toSetInput(writeTarget, await this.extractValue(raw, evaluator, context));
@@ -21315,12 +21746,30 @@ function getNodeType(node) {
21315
21746
  return 'unknown';
21316
21747
  return node.type || 'unknown';
21317
21748
  }
21749
+ function propertyTarget(node) {
21750
+ const n = node;
21751
+ const owner = n.type === 'propertyOfExpression'
21752
+ ? n.target
21753
+ : n.type === 'possessiveExpression' ||
21754
+ n.type === 'memberExpression' ||
21755
+ n.type === 'propertyAccess'
21756
+ ? n.object
21757
+ : undefined;
21758
+ if (!owner || n.computed)
21759
+ return null;
21760
+ const p = n.property;
21761
+ const name = typeof p === 'string' ? p : typeof p?.name === 'string' ? p.name : undefined;
21762
+ if (!name || !/^[A-Za-z_$][\w$-]*$/.test(name))
21763
+ return null;
21764
+ return { owner, name };
21765
+ }
21318
21766
  async function parseNumericTargetInput(raw, evaluator, context, commandName) {
21319
21767
  if (!raw.args || raw.args.length === 0) {
21320
21768
  throw new Error(`${commandName} command requires a target`);
21321
21769
  }
21322
21770
  const targetArg = raw.args[0];
21323
21771
  let target;
21772
+ let property;
21324
21773
  let extractedScope;
21325
21774
  let customWrite;
21326
21775
  const nodeType = getNodeType(targetArg);
@@ -21335,7 +21784,25 @@ async function parseNumericTargetInput(raw, evaluator, context, commandName) {
21335
21784
  }
21336
21785
  else {
21337
21786
  const writer = getRegisteredNodeWriter(nodeType);
21338
- if (writer) {
21787
+ const attribute = writer
21788
+ ? null
21789
+ : await resolveAttributeWriteTarget(targetArg, evaluator, context, async () => context.me ? [context.me] : []);
21790
+ const prop = writer || attribute ? null : propertyTarget(targetArg);
21791
+ const owner = prop ? await evaluator.evaluate(prop.owner, context) : undefined;
21792
+ const ownerElement = Array.isArray(owner) ? owner[0] : owner;
21793
+ if (attribute?.elements[0]) {
21794
+ target = attribute.elements[0];
21795
+ property = `@${attribute.name}`;
21796
+ }
21797
+ else if (prop && isHTMLElement$2(ownerElement)) {
21798
+ target = ownerElement;
21799
+ property = prop.name;
21800
+ }
21801
+ else if (prop && isObjectOwner(owner)) {
21802
+ target = owner;
21803
+ property = prop.name;
21804
+ }
21805
+ else if (writer) {
21339
21806
  const currentRaw = await evaluator.evaluate(targetArg, context);
21340
21807
  const currentValue = typeof currentRaw === 'number'
21341
21808
  ? currentRaw
@@ -21373,24 +21840,22 @@ async function parseNumericTargetInput(raw, evaluator, context, commandName) {
21373
21840
  if (typeof evaluated === 'number') {
21374
21841
  amount = evaluated;
21375
21842
  }
21843
+ else if (evaluated == null) {
21844
+ amount = NaN;
21845
+ }
21376
21846
  }
21377
21847
  }
21378
21848
  const byModifier = raw.modifiers?.by;
21379
21849
  if (byModifier) {
21380
- const byValue = byModifier.value;
21381
- if (getNodeType(byModifier) === 'literal' && typeof byValue === 'number') {
21382
- amount = byValue;
21383
- }
21384
- else if (getNodeType(byModifier) !== 'literal') {
21385
- const evaluated = await evaluator.evaluate(byModifier, context);
21386
- if (typeof evaluated === 'number') {
21387
- amount = evaluated;
21388
- }
21389
- }
21850
+ const value = getNodeType(byModifier) === 'literal'
21851
+ ? byModifier.value
21852
+ : await evaluator.evaluate(byModifier, context);
21853
+ amount = counterAmount(value);
21390
21854
  }
21391
21855
  return {
21392
21856
  target,
21393
21857
  amount,
21858
+ ...(property && { property }),
21394
21859
  ...(scope && { scope }),
21395
21860
  ...(customWrite && { customWrite }),
21396
21861
  };
@@ -21413,7 +21878,7 @@ let NumericModifyCommand = (() => {
21413
21878
  async execute(input, context) {
21414
21879
  const { target, property, scope, amount = 1, operation = 'increment', customWrite } = input;
21415
21880
  if (customWrite) {
21416
- const delta = isFinite(amount) ? amount : 1;
21881
+ const delta = amount;
21417
21882
  const newValue = operation === 'increment'
21418
21883
  ? customWrite.currentValue + delta
21419
21884
  : customWrite.currentValue - delta;
@@ -21427,8 +21892,7 @@ let NumericModifyCommand = (() => {
21427
21892
  newValue = NaN;
21428
21893
  }
21429
21894
  else {
21430
- const delta = isFinite(amount) ? amount : 1;
21431
- newValue = operation === 'increment' ? currentValue + delta : currentValue - delta;
21895
+ newValue = operation === 'increment' ? currentValue + amount : currentValue - amount;
21432
21896
  }
21433
21897
  setTargetValue(target, property, scope, newValue, context);
21434
21898
  Object.assign(context, { it: newValue });
@@ -22536,7 +23000,10 @@ function createUntilLoopConfig(conditionExpr, evaluateCondition, context, indexV
22536
23000
  };
22537
23001
  const config = {
22538
23002
  type: 'until',
22539
- shouldContinue: () => !evaluateCondition(conditionExpr, context),
23003
+ shouldContinue: () => {
23004
+ const met = evaluateCondition(conditionExpr, context);
23005
+ return met instanceof Promise ? met.then(value => !value) : !met;
23006
+ },
22540
23007
  };
22541
23008
  return { config, iterCtx };
22542
23009
  }
@@ -22572,7 +23039,7 @@ function createForeverLoopConfig(indexVariable, maxIterations = 10000) {
22572
23039
  return { config, iterCtx };
22573
23040
  }
22574
23041
 
22575
- async function slotText(node, evaluator, context) {
23042
+ async function slotText$1(node, evaluator, context) {
22576
23043
  if (!node)
22577
23044
  return undefined;
22578
23045
  const n = node;
@@ -22585,6 +23052,18 @@ async function slotText(node, evaluator, context) {
22585
23052
  const v = await evaluator.evaluate(node, context);
22586
23053
  return typeof v === 'string' ? v : undefined;
22587
23054
  }
23055
+ function isEventTarget(value) {
23056
+ return typeof value?.addEventListener === 'function';
23057
+ }
23058
+ async function evaluateLoopCondition(condition, context) {
23059
+ if (typeof condition === 'function')
23060
+ return Boolean(await condition());
23061
+ return evaluateCondition(condition, context);
23062
+ }
23063
+ function loopCount(value) {
23064
+ const count = Number(value);
23065
+ return Number.isNaN(count) ? 0 : count;
23066
+ }
22588
23067
  let RepeatCommand = (() => {
22589
23068
  let _classDecorators = [command({ name: 'repeat' })];
22590
23069
  let _classDescriptor;
@@ -22622,7 +23101,7 @@ let RepeatCommand = (() => {
22622
23101
  }
22623
23102
  }
22624
23103
  const m = raw.modifiers ?? {};
22625
- const text = (node) => slotText(node, evaluator, context);
23104
+ const text = (node) => slotText$1(node, evaluator, context);
22626
23105
  const loopType = (await text(m.loopType)) ?? null;
22627
23106
  const bottomTested = m.bottomTested
22628
23107
  ? (await evaluator.evaluate(m.bottomTested, context))
@@ -22642,17 +23121,24 @@ let RepeatCommand = (() => {
22642
23121
  };
22643
23122
  }
22644
23123
  if (loopType === 'times' || m.times) {
22645
- const countValue = m.times && (await evaluator.evaluate(m.times, context));
22646
- const count = typeof countValue === 'number' ? countValue : parseInt(String(countValue), 10);
22647
- if (isNaN(count))
23124
+ if (!m.times)
22648
23125
  throw new Error('times loops require a count number');
23126
+ const count = loopCount(await evaluator.evaluate(m.times, context));
22649
23127
  return { type: 'times', count, indexVariable, commands, elseCommands };
22650
23128
  }
22651
23129
  if (loopType === 'while' || m.while) {
22652
23130
  const condition = m.while;
22653
23131
  if (!condition)
22654
23132
  throw new Error('while loops require a condition');
22655
- return { type: 'while', condition, indexVariable, commands, elseCommands, bottomTested };
23133
+ const check = () => evaluator.evaluate(condition, context);
23134
+ return {
23135
+ type: 'while',
23136
+ condition: check,
23137
+ indexVariable,
23138
+ commands,
23139
+ elseCommands,
23140
+ bottomTested,
23141
+ };
22656
23142
  }
22657
23143
  if (loopType === 'until-event' || m.event) {
22658
23144
  const eventName = await text(m.event);
@@ -22670,7 +23156,7 @@ let RepeatCommand = (() => {
22670
23156
  }
22671
23157
  else {
22672
23158
  const target = await evaluator.evaluate(m.from, context);
22673
- if (target instanceof EventTarget)
23159
+ if (isEventTarget(target))
22674
23160
  eventTarget = target;
22675
23161
  else if (target === 'document')
22676
23162
  eventTarget = document;
@@ -22691,7 +23177,15 @@ let RepeatCommand = (() => {
22691
23177
  const condition = m.until;
22692
23178
  if (!condition)
22693
23179
  throw new Error('until loops require a condition');
22694
- return { type: 'until', condition, indexVariable, commands, elseCommands, bottomTested };
23180
+ const check = () => evaluator.evaluate(condition, context);
23181
+ return {
23182
+ type: 'until',
23183
+ condition: check,
23184
+ indexVariable,
23185
+ commands,
23186
+ elseCommands,
23187
+ bottomTested,
23188
+ };
22695
23189
  }
22696
23190
  if (loopType === 'forever') {
22697
23191
  return { type: 'forever', indexVariable, commands, elseCommands };
@@ -22709,10 +23203,10 @@ let RepeatCommand = (() => {
22709
23203
  ({ config, iterCtx } = createTimesLoopConfig(count, indexVariable));
22710
23204
  break;
22711
23205
  case 'while':
22712
- ({ config, iterCtx } = createWhileLoopConfig(condition, evaluateCondition, context, indexVariable));
23206
+ ({ config, iterCtx } = createWhileLoopConfig(condition, evaluateLoopCondition, context, indexVariable));
22713
23207
  break;
22714
23208
  case 'until':
22715
- ({ config, iterCtx } = createUntilLoopConfig(condition, evaluateCondition, context, indexVariable));
23209
+ ({ config, iterCtx } = createUntilLoopConfig(condition, evaluateLoopCondition, context, indexVariable));
22716
23210
  break;
22717
23211
  case 'until-event':
22718
23212
  ({ config, iterCtx } = createUntilEventLoopConfig(eventName, eventTarget, indexVariable));
@@ -23743,6 +24237,29 @@ function namedPropertyOf(node) {
23743
24237
  }
23744
24238
  return null;
23745
24239
  }
24240
+ const RESTORE_KEYWORDS = new Set(['initial', 'inherit', 'unset', 'revert']);
24241
+ function elementsOf(value) {
24242
+ if (Array.isArray(value) || isNodeList(value)) {
24243
+ return Array.from(value).filter(isHTMLElement$2);
24244
+ }
24245
+ return isHTMLElement$2(value) ? [value] : [];
24246
+ }
24247
+ async function valueOf(node, evaluator, context) {
24248
+ const value = await evaluator.evaluate(node, context);
24249
+ const name = node.name;
24250
+ if (value === undefined && node.type === 'identifier' && typeof name === 'string')
24251
+ return name;
24252
+ return value;
24253
+ }
24254
+ function longestTransitionMs(css) {
24255
+ let longest;
24256
+ for (const item of css.split(',')) {
24257
+ const times = [...item.matchAll(/(\d*\.?\d+)(ms|s)\b/g)].map(m => parseFloat(m[1]) * (m[2] === 's' ? 1000 : 1));
24258
+ if (times.length > 0)
24259
+ longest = Math.max(longest ?? 0, times[0] + (times[1] ?? 0));
24260
+ }
24261
+ return longest;
24262
+ }
23746
24263
  let TransitionCommand = (() => {
23747
24264
  let _classDecorators = [command({ name: 'transition' })];
23748
24265
  let _classDescriptor;
@@ -23758,11 +24275,10 @@ let TransitionCommand = (() => {
23758
24275
  let property;
23759
24276
  let target;
23760
24277
  const firstArg = await evaluator.evaluate(raw.args[0], context);
23761
- const asElement = (value) => Array.isArray(value) || isNodeList(value) ? value[0] : value;
23762
- const firstAsElement = asElement(firstArg);
23763
- if (isHTMLElement$2(firstAsElement) ||
24278
+ const owners = elementsOf(firstArg);
24279
+ if (owners.length > 0 ||
23764
24280
  (typeof firstArg === 'string' && /^[#.]|^(?:me|it|you)$/.test(firstArg))) {
23765
- target = firstAsElement;
24281
+ target = owners.length === 1 ? owners[0] : owners.length > 1 ? owners : firstArg;
23766
24282
  property = String(await evaluator.evaluate(raw.args[1], context));
23767
24283
  }
23768
24284
  else if (raw.args.length >= 2) {
@@ -23774,9 +24290,9 @@ let TransitionCommand = (() => {
23774
24290
  throw new Error('transition requires a CSS property');
23775
24291
  property = named.property;
23776
24292
  if (named.objectNode !== undefined) {
23777
- const owner = asElement(await evaluator.evaluate(named.objectNode, context));
23778
- if (isHTMLElement$2(owner))
23779
- target = owner;
24293
+ const owner = elementsOf(await evaluator.evaluate(named.objectNode, context));
24294
+ if (owner.length > 0)
24295
+ target = owner.length === 1 ? owner[0] : owner;
23780
24296
  }
23781
24297
  }
23782
24298
  else {
@@ -23787,60 +24303,131 @@ let TransitionCommand = (() => {
23787
24303
  }
23788
24304
  if (!raw.modifiers?.to)
23789
24305
  throw new Error('transition requires "to <value>"');
23790
- let value = await evaluator.evaluate(raw.modifiers.to, context);
23791
- if (value === undefined && raw.modifiers.to.type === 'identifier') {
23792
- value = raw.modifiers.to.name;
23793
- }
23794
- const result = { property, value: value };
24306
+ const result = {
24307
+ property,
24308
+ value: await valueOf(raw.modifiers.to, evaluator, context),
24309
+ };
23795
24310
  if (target !== undefined)
23796
24311
  result.target = target;
24312
+ if (raw.modifiers?.from)
24313
+ result.from = await valueOf(raw.modifiers.from, evaluator, context);
24314
+ if (raw.modifiers?.pairs) {
24315
+ result.pairs = await this.laterPairs(raw.modifiers.pairs, evaluator, context);
24316
+ }
23797
24317
  if (raw.modifiers?.over)
23798
24318
  result.duration = await evaluator.evaluate(raw.modifiers.over, context);
23799
24319
  if (raw.modifiers?.with)
23800
24320
  result.timingFunction = String(await evaluator.evaluate(raw.modifiers.with, context));
24321
+ if (raw.modifiers?.using)
24322
+ result.using = String(await evaluator.evaluate(raw.modifiers.using, context));
23801
24323
  return result;
23802
24324
  }
24325
+ async laterPairs(node, evaluator, context) {
24326
+ const pairs = [];
24327
+ for (const pairNode of node.elements ?? []) {
24328
+ const fields = new Map();
24329
+ const properties = pairNode
24330
+ .properties ?? [];
24331
+ for (const { key, value } of properties) {
24332
+ if (typeof key.name === 'string')
24333
+ fields.set(key.name, value);
24334
+ }
24335
+ const propertyNode = fields.get('property');
24336
+ const toNode = fields.get('to');
24337
+ if (!propertyNode || !toNode)
24338
+ throw new Error('transition requires property and value');
24339
+ const pair = {
24340
+ property: String(await evaluator.evaluate(propertyNode, context)),
24341
+ value: await valueOf(toNode, evaluator, context),
24342
+ };
24343
+ const ownerNode = fields.get('owner');
24344
+ if (ownerNode) {
24345
+ const owners = elementsOf(await evaluator.evaluate(ownerNode, context));
24346
+ if (owners.length === 0)
24347
+ throw new Error('transition: target element not found');
24348
+ pair.target = owners.length === 1 ? owners[0] : owners;
24349
+ }
24350
+ const fromNode = fields.get('from');
24351
+ if (fromNode)
24352
+ pair.from = await valueOf(fromNode, evaluator, context);
24353
+ pairs.push(pair);
24354
+ }
24355
+ return pairs;
24356
+ }
23803
24357
  async execute(input, context) {
23804
- let { property } = input;
23805
- const { target, value, duration: durationInput, timingFunction } = input;
23806
- if (property.startsWith('*'))
23807
- property = property.substring(1);
23808
- property = camelToKebab(property);
23809
- const targetElement = resolveElement(target, context, 'transition');
23810
- const duration = parseDuration(durationInput, 300);
23811
- const fromValue = getComputedStyle(targetElement).getPropertyValue(property);
23812
- const originalTransition = targetElement.style.transition;
23813
- const transitionProp = `${property} ${duration}ms ${timingFunction || 'ease'}`;
23814
- targetElement.style.transition = originalTransition
23815
- ? `${originalTransition}, ${transitionProp}`
23816
- : transitionProp;
23817
- let toValue = String(value);
23818
- let removeInlineAfter = false;
23819
- if (toValue === 'initial' ||
23820
- toValue === 'inherit' ||
23821
- toValue === 'unset' ||
23822
- toValue === 'revert') {
23823
- const currentInline = targetElement.style.getPropertyValue(property);
23824
- targetElement.style.removeProperty(property);
23825
- toValue = getComputedStyle(targetElement).getPropertyValue(property);
23826
- if (currentInline) {
23827
- targetElement.style.setProperty(property, currentInline);
23828
- }
23829
- removeInlineAfter = true;
23830
- }
23831
- targetElement.style.setProperty(property, toValue);
23832
- const result = await waitForTransitionEnd(targetElement, property, duration);
23833
- targetElement.style.transition = originalTransition;
23834
- if (removeInlineAfter) {
23835
- targetElement.style.removeProperty(property);
24358
+ const duration = parseDuration(input.duration, (input.using !== undefined && longestTransitionMs(input.using)) || 300);
24359
+ const timing = input.timingFunction || 'ease';
24360
+ const jobs = [];
24361
+ for (const pair of [input, ...(input.pairs ?? [])]) {
24362
+ let property = pair.property;
24363
+ if (property.startsWith('*'))
24364
+ property = property.substring(1);
24365
+ property = camelToKebab(property);
24366
+ const elements = Array.isArray(pair.target)
24367
+ ? pair.target.filter(isHTMLElement$2)
24368
+ : [resolveElement(pair.target, context, 'transition')];
24369
+ for (const element of elements) {
24370
+ jobs.push({
24371
+ element,
24372
+ property,
24373
+ pair,
24374
+ fromValue: getComputedStyle(element).getPropertyValue(property),
24375
+ toValue: '',
24376
+ removeInlineAfter: false,
24377
+ });
24378
+ }
24379
+ }
24380
+ if (jobs.length === 0)
24381
+ throw new Error('transition: target element not found');
24382
+ for (const job of jobs) {
24383
+ if (job.pair.from !== undefined) {
24384
+ job.element.style.setProperty(job.property, String(job.pair.from));
24385
+ }
24386
+ }
24387
+ for (const job of jobs)
24388
+ if (job.pair.from !== undefined)
24389
+ void job.element.offsetWidth;
24390
+ const originals = new Map();
24391
+ for (const job of jobs) {
24392
+ if (!originals.has(job.element))
24393
+ originals.set(job.element, job.element.style.transition);
24394
+ }
24395
+ for (const [element, original] of originals) {
24396
+ const own = jobs
24397
+ .filter(job => job.element === element)
24398
+ .map(job => `${job.property} ${duration}ms ${timing}`);
24399
+ element.style.transition = input.using ?? [original, ...own].filter(Boolean).join(', ');
24400
+ }
24401
+ for (const job of jobs) {
24402
+ const { element, property } = job;
24403
+ let toValue = String(job.pair.value);
24404
+ if (RESTORE_KEYWORDS.has(toValue)) {
24405
+ const currentInline = element.style.getPropertyValue(property);
24406
+ element.style.removeProperty(property);
24407
+ toValue = getComputedStyle(element).getPropertyValue(property);
24408
+ if (currentInline) {
24409
+ element.style.setProperty(property, currentInline);
24410
+ }
24411
+ job.removeInlineAfter = true;
24412
+ }
24413
+ job.toValue = toValue;
24414
+ element.style.setProperty(property, toValue);
24415
+ }
24416
+ const results = await Promise.all(jobs.map(job => waitForTransitionEnd(job.element, job.property, duration)));
24417
+ for (const [element, original] of originals)
24418
+ element.style.transition = original;
24419
+ for (const job of jobs) {
24420
+ if (job.removeInlineAfter)
24421
+ job.element.style.removeProperty(job.property);
23836
24422
  }
24423
+ const first = jobs[0];
23837
24424
  return {
23838
- element: targetElement,
23839
- property,
23840
- fromValue,
23841
- toValue,
24425
+ element: first.element,
24426
+ property: first.property,
24427
+ fromValue: first.fromValue,
24428
+ toValue: first.toValue,
23842
24429
  duration,
23843
- completed: result.completed,
24430
+ completed: results.every(result => result.completed),
23844
24431
  };
23845
24432
  }
23846
24433
  };
@@ -23853,13 +24440,16 @@ let TransitionCommand = (() => {
23853
24440
  })();
23854
24441
  _classThis.metadata = commandMeta({
23855
24442
  description: 'Animate CSS properties using CSS transitions',
23856
- syntax: 'transition [<target>] <property> to <value> [over <duration>] [with <timing>]',
24443
+ syntax: 'transition <property> [from <value>] to <value> [<property> \u2026 to <value>]\u2026 [over <duration>] [using <css> | with <timing>]',
23857
24444
  examples: [
23858
24445
  'transition opacity to 0.5',
23859
24446
  'transition my *opacity to 0 over 200ms',
23860
24447
  "transition #box's *opacity to 0 over 200ms",
23861
24448
  'transition left to 100px over 500ms',
23862
24449
  'transition background-color to red over 1s with ease-in-out',
24450
+ 'transition *width to 100px *height to 50px over 300ms',
24451
+ 'transition *opacity of #panel from 0 to 1 over 200ms',
24452
+ 'transition my *opacity to 0 using "opacity 1s ease-in"',
23863
24453
  ],
23864
24454
  sideEffects: ['style-change', 'timing'],
23865
24455
  category: 'animation',
@@ -25109,17 +25699,7 @@ let BeepCommand = (() => {
25109
25699
  this.debugContext(context);
25110
25700
  return { expressionCount: 0, debugged: true, outputs: [] };
25111
25701
  }
25112
- const outputs = [];
25113
- console.group('\ud83d\udd14 beep! Debug Output');
25114
- for (const expression of expressions) {
25115
- const output = this.debugExpression(expression);
25116
- outputs.push(output);
25117
- console.log(`Value:`, expression);
25118
- console.log(`Type:`, output.type);
25119
- console.log(`Representation:`, output.representation);
25120
- console.log('---');
25121
- }
25122
- console.groupEnd();
25702
+ const outputs = beepValues(context.me, expressions);
25123
25703
  return { expressionCount: expressions.length, debugged: true, outputs };
25124
25704
  }
25125
25705
  debugContext(context) {
@@ -25132,67 +25712,6 @@ let BeepCommand = (() => {
25132
25712
  console.log('variables:', context.variables);
25133
25713
  console.groupEnd();
25134
25714
  }
25135
- debugExpression(expression) {
25136
- return {
25137
- value: expression,
25138
- type: this.getType(expression),
25139
- representation: this.getRepresentation(expression),
25140
- };
25141
- }
25142
- getType(value) {
25143
- if (value === null)
25144
- return 'null';
25145
- if (value === undefined)
25146
- return 'undefined';
25147
- if (Array.isArray(value))
25148
- return 'array';
25149
- if (isHTMLElement$2(value))
25150
- return 'HTMLElement';
25151
- if (value instanceof Element)
25152
- return 'Element';
25153
- if (value instanceof Node)
25154
- return 'Node';
25155
- if (value instanceof Error)
25156
- return 'Error';
25157
- if (value instanceof Date)
25158
- return 'Date';
25159
- if (value instanceof RegExp)
25160
- return 'RegExp';
25161
- return typeof value;
25162
- }
25163
- getRepresentation(value) {
25164
- if (value === null)
25165
- return 'null';
25166
- if (value === undefined)
25167
- return 'undefined';
25168
- if (Array.isArray(value)) {
25169
- return `Array(${value.length}) [${value
25170
- .slice(0, 3)
25171
- .map(v => this.getRepresentation(v))
25172
- .join(', ')}${value.length > 3 ? '...' : ''}]`;
25173
- }
25174
- if (isHTMLElement$2(value)) {
25175
- const el = value;
25176
- const tag = el.tagName.toLowerCase();
25177
- const id = el.id ? `#${el.id}` : '';
25178
- const classes = el.className ? `.${el.className.split(' ').join('.')}` : '';
25179
- return `<${tag}${id}${classes}/>`;
25180
- }
25181
- if (value instanceof Error)
25182
- return `Error: ${value.message}`;
25183
- if (typeof value === 'string')
25184
- return value.length > 50 ? `"${value.substring(0, 47)}..."` : `"${value}"`;
25185
- if (typeof value === 'object') {
25186
- try {
25187
- const keys = Object.keys(value);
25188
- return `Object {${keys.slice(0, 3).join(', ')}${keys.length > 3 ? '...' : ''}}`;
25189
- }
25190
- catch {
25191
- return '[Object]';
25192
- }
25193
- }
25194
- return String(value);
25195
- }
25196
25715
  };
25197
25716
  __setFunctionName(_classThis, "BeepCommand");
25198
25717
  (() => {
@@ -25740,16 +26259,12 @@ class RenderCommand {
25740
26259
  }
25741
26260
  RenderCommand.metadata = commandMeta({
25742
26261
  description: 'Render templates with @if, @else, and @repeat directives',
25743
- syntax: [
25744
- 'render <template>',
25745
- 'render <template> with <variables>',
25746
- 'render <template> with (key: value, ...)',
25747
- ],
26262
+ syntax: ['render <template>', 'render <template> with <name>: <value>, ...'],
25748
26263
  examples: [
25749
26264
  'render myTemplate',
25750
- 'render myTemplate with (name: "Alice")',
25751
- 'render "<template>Hello ${name}!</template>" with (name: "World")',
25752
- 'render template with (items: data)',
26265
+ 'render myTemplate with name: "Alice"',
26266
+ 'render "<template>Hello ${name}!</template>" with name: "World"',
26267
+ 'render template with items: data',
25753
26268
  ],
25754
26269
  category: 'templates',
25755
26270
  compatibility: 'standard',
@@ -25937,7 +26452,7 @@ function updateDebugStats(event) {
25937
26452
  stats.confidenceHistory.reduce((a, b) => a + b, 0) / stats.confidenceHistory.length;
25938
26453
  }
25939
26454
 
25940
- const VERSION = '3.1.1';
26455
+ const VERSION = '3.2.0';
25941
26456
 
25942
26457
  function createHistorySwap(config) {
25943
26458
  const { target, strategy = 'morph', useViewTransition = false, fetchOptions = {}, transformUrl, onBeforeFetch, onAfterSwap, onError, } = config;
@@ -27483,6 +27998,14 @@ function convertNode(node, infer) {
27483
27998
  ...pos(node),
27484
27999
  };
27485
28000
  case 'identifier':
28001
+ if (node.scope === 'element' || node.scope === 'local' || node.scope === 'global') {
28002
+ return {
28003
+ type: 'variable',
28004
+ name: (node.name ?? ''),
28005
+ scope: node.scope,
28006
+ ...pos(node),
28007
+ };
28008
+ }
27486
28009
  return {
27487
28010
  type: 'identifier',
27488
28011
  value: (node.name ?? node.value ?? ''),
@@ -27505,6 +28028,15 @@ function convertNode(node, infer) {
27505
28028
  operand: convertNode(node.argument ?? node.operand, infer),
27506
28029
  ...pos(node),
27507
28030
  };
28031
+ case 'typeCheckExpression':
28032
+ return {
28033
+ type: 'typeCheck',
28034
+ value: convertNode(node.value, infer),
28035
+ typeName: String(node.typeName),
28036
+ nullOk: node.nullOk !== false,
28037
+ negated: node.negated === true,
28038
+ ...pos(node),
28039
+ };
27508
28040
  case 'timeExpression':
27509
28041
  return { type: 'literal', value: node.value, ...pos(node) };
27510
28042
  case 'templateLiteral':
@@ -27516,6 +28048,28 @@ function convertNode(node, infer) {
27516
28048
  scope: (node.scope ?? 'local'),
27517
28049
  ...pos(node),
27518
28050
  };
28051
+ case 'arrayLiteral':
28052
+ return {
28053
+ type: 'array',
28054
+ elements: (node.elements ?? []).map(element => convertNode(element, infer)),
28055
+ ...pos(node),
28056
+ };
28057
+ case 'attributeAccess':
28058
+ return {
28059
+ type: 'possessive',
28060
+ object: { type: 'identifier', value: 'me' },
28061
+ property: `@${node.attributeName}`,
28062
+ ...pos(node),
28063
+ };
28064
+ case 'propertyOfExpression': {
28065
+ const property = node.property;
28066
+ return {
28067
+ type: 'possessive',
28068
+ object: convertNode(node.target, infer),
28069
+ property: (property?.name ?? property?.value ?? ''),
28070
+ ...pos(node),
28071
+ };
28072
+ }
27519
28073
  case 'htmlSelector':
27520
28074
  return {
27521
28075
  type: 'selector',
@@ -27567,6 +28121,21 @@ function convertCommand$1(node, infer) {
27567
28121
  if (name === 'repeat') {
27568
28122
  return convertRepeatCommand(node, infer);
27569
28123
  }
28124
+ const original = node.originalCommand;
28125
+ const sum = node.modifiers?.to;
28126
+ const counted = node.args?.[0];
28127
+ if (name === 'set' &&
28128
+ (original === 'increment' || original === 'decrement') &&
28129
+ sum?.type === 'binaryExpression' &&
28130
+ counted) {
28131
+ return convertCommand$1({
28132
+ ...node,
28133
+ name: original,
28134
+ args: [counted],
28135
+ modifiers: { by: sum.right },
28136
+ originalCommand: undefined,
28137
+ }, infer);
28138
+ }
27570
28139
  const args = (node.args ?? []).map(arg => convertNode(arg, infer));
27571
28140
  const target = node.target ? convertNode(node.target, infer) : undefined;
27572
28141
  const modifiers = node.modifiers
@@ -27615,6 +28184,64 @@ function convertIfCommand(node, infer) {
27615
28184
  };
27616
28185
  }
27617
28186
  function convertRepeatCommand(node, infer) {
28187
+ const slots = (node.modifiers ?? {});
28188
+ const form = slotText(slots.loopType);
28189
+ if (form === undefined)
28190
+ return convertPositionalRepeat(node, infer);
28191
+ const [bodyBlock, elseBlock] = (node.args ?? []);
28192
+ const body = extractBlockCommands(bodyBlock, infer);
28193
+ const indexName = slotText(slots.index);
28194
+ const shared = {
28195
+ ...(indexName ? { indexName } : {}),
28196
+ ...(elseBlock ? { elseBody: extractBlockCommands(elseBlock, infer) } : {}),
28197
+ ...pos(node),
28198
+ };
28199
+ const operand = (slot) => (slot ? convertNode(slot, infer) : undefined);
28200
+ switch (form) {
28201
+ case 'for':
28202
+ return {
28203
+ type: 'foreach',
28204
+ itemName: slotText(slots.for) ?? 'it',
28205
+ collection: operand(slots.in) ?? { type: 'literal', value: null },
28206
+ body,
28207
+ ...shared,
28208
+ };
28209
+ case 'times':
28210
+ return { type: 'repeat', count: operand(slots.times), body, ...shared };
28211
+ case 'while':
28212
+ case 'until': {
28213
+ const test = operand(slots[form]) ?? { type: 'literal', value: form === 'until' };
28214
+ return {
28215
+ type: 'while',
28216
+ condition: form === 'until' ? { type: 'unary', operator: 'not', operand: test } : test,
28217
+ ...(slots.bottomTested?.value === true ? { bottomTested: true } : {}),
28218
+ body,
28219
+ ...shared,
28220
+ };
28221
+ }
28222
+ case 'until-event': {
28223
+ const event = slotText(slots.event);
28224
+ if (!event) {
28225
+ return { type: 'error', message: "'repeat until event' has no event name", ...pos(node) };
28226
+ }
28227
+ const target = operand(slots.from);
28228
+ return {
28229
+ type: 'repeat',
28230
+ untilEvent: event,
28231
+ ...(target ? { untilEventTarget: target } : {}),
28232
+ body,
28233
+ ...shared,
28234
+ };
28235
+ }
28236
+ default:
28237
+ return { type: 'repeat', body, ...shared };
28238
+ }
28239
+ }
28240
+ function slotText(slot) {
28241
+ const text = slot?.value ?? slot?.name;
28242
+ return typeof text === 'string' ? text : undefined;
28243
+ }
28244
+ function convertPositionalRepeat(node, infer) {
27618
28245
  const args = (node.args ?? []);
27619
28246
  if (args.length === 0) {
27620
28247
  return { type: 'repeat', body: [], ...pos(node) };
@@ -27931,6 +28558,15 @@ function toCoreAST(node) {
27931
28558
  prefix: true,
27932
28559
  ...nodePos(node),
27933
28560
  };
28561
+ case 'typeCheck':
28562
+ return {
28563
+ type: 'typeCheckExpression',
28564
+ value: toCoreAST(node.value),
28565
+ typeName: node.typeName,
28566
+ nullOk: node.nullOk,
28567
+ negated: node.negated,
28568
+ ...nodePos(node),
28569
+ };
27934
28570
  case 'member':
27935
28571
  return {
27936
28572
  type: 'memberExpression',
@@ -27941,6 +28577,12 @@ function toCoreAST(node) {
27941
28577
  computed: node.computed ?? false,
27942
28578
  ...nodePos(node),
27943
28579
  };
28580
+ case 'array':
28581
+ return {
28582
+ type: 'arrayLiteral',
28583
+ elements: node.elements.map(element => toCoreAST(element)),
28584
+ ...nodePos(node),
28585
+ };
27944
28586
  case 'possessive':
27945
28587
  return {
27946
28588
  type: 'possessiveExpression',
@@ -28003,72 +28645,85 @@ function convertCommand(node) {
28003
28645
  }
28004
28646
  return result;
28005
28647
  }
28648
+ function block(nodes) {
28649
+ return { type: 'block', commands: nodes.map(n => toCoreAST(n)), ...POS };
28650
+ }
28006
28651
  function convertIf(node) {
28007
- const result = {
28652
+ const [elseIf, ...laterElseIfs] = node.elseIfBranches ?? [];
28653
+ const elseBranch = elseIf
28654
+ ? [
28655
+ {
28656
+ type: 'if',
28657
+ condition: elseIf.condition,
28658
+ thenBranch: elseIf.body,
28659
+ elseIfBranches: laterElseIfs,
28660
+ ...(node.elseBranch ? { elseBranch: node.elseBranch } : {}),
28661
+ },
28662
+ ]
28663
+ : node.elseBranch;
28664
+ return {
28008
28665
  type: 'command',
28009
28666
  name: 'if',
28010
28667
  isBlocking: true,
28011
- condition: toCoreAST(node.condition),
28012
- thenBranch: node.thenBranch.map(n => toCoreAST(n)),
28013
- args: [],
28668
+ args: [
28669
+ toCoreAST(node.condition),
28670
+ block(node.thenBranch),
28671
+ ...(elseBranch ? [block(elseBranch)] : []),
28672
+ ],
28014
28673
  ...nodePos(node),
28015
28674
  };
28016
- if (node.elseBranch) {
28017
- result.elseBranch = node.elseBranch.map(n => toCoreAST(n));
28018
- }
28019
- return result;
28020
28675
  }
28021
- function convertRepeat(node) {
28022
- const result = {
28676
+ function loopCommand(node, form, operands) {
28677
+ const modifiers = { loopType: slotString(form) };
28678
+ for (const [slot, value] of Object.entries(operands)) {
28679
+ if (value)
28680
+ modifiers[slot] = value;
28681
+ }
28682
+ if (node.indexName)
28683
+ modifiers.index = slotString(node.indexName);
28684
+ return {
28023
28685
  type: 'command',
28024
28686
  name: 'repeat',
28025
28687
  isBlocking: true,
28026
- body: node.body.map(n => toCoreAST(n)),
28027
- args: [],
28688
+ args: [block(node.body), ...(node.elseBody ? [block(node.elseBody)] : [])],
28689
+ modifiers,
28028
28690
  ...nodePos(node),
28029
28691
  };
28692
+ }
28693
+ function slotString(value) {
28694
+ return { type: 'string', value, ...POS };
28695
+ }
28696
+ function convertRepeat(node) {
28030
28697
  if (node.count !== undefined) {
28031
- result.count =
28032
- typeof node.count === 'number'
28033
- ? { type: 'literal', value: node.count, raw: String(node.count), ...POS }
28034
- : toCoreAST(node.count);
28035
- result.loopVariant = 'times';
28036
- }
28037
- else if (node.whileCondition !== undefined) {
28038
- result.condition = toCoreAST(node.whileCondition);
28039
- result.loopVariant = 'while';
28040
- }
28041
- else if (node.untilEvent !== undefined) {
28042
- result.untilEvent = node.untilEvent;
28043
- result.loopVariant = 'until';
28698
+ const times = typeof node.count === 'number'
28699
+ ? { type: 'literal', value: node.count, raw: String(node.count), ...POS }
28700
+ : toCoreAST(node.count);
28701
+ return loopCommand(node, 'times', { times });
28702
+ }
28703
+ if (node.whileCondition !== undefined) {
28704
+ return loopCommand(node, 'while', { while: toCoreAST(node.whileCondition) });
28705
+ }
28706
+ if (node.untilEvent !== undefined) {
28707
+ return loopCommand(node, 'until-event', {
28708
+ event: slotString(node.untilEvent),
28709
+ from: node.untilEventTarget ? toCoreAST(node.untilEventTarget) : undefined,
28710
+ });
28044
28711
  }
28045
- return result;
28712
+ return loopCommand(node, 'forever', {});
28046
28713
  }
28047
28714
  function convertForEach(node) {
28048
- return {
28049
- type: 'command',
28050
- name: 'repeat',
28051
- isBlocking: true,
28052
- loopVariant: 'for',
28053
- itemName: node.itemName,
28054
- ...(node.indexName ? { indexName: node.indexName } : {}),
28055
- collection: toCoreAST(node.collection),
28056
- body: node.body.map(n => toCoreAST(n)),
28057
- args: [],
28058
- ...nodePos(node),
28059
- };
28715
+ return loopCommand(node, 'for', {
28716
+ for: slotString(node.itemName),
28717
+ in: toCoreAST(node.collection),
28718
+ });
28060
28719
  }
28061
28720
  function convertWhile(node) {
28062
- return {
28063
- type: 'command',
28064
- name: 'repeat',
28065
- isBlocking: true,
28066
- loopVariant: 'while',
28067
- condition: toCoreAST(node.condition),
28068
- body: node.body.map(n => toCoreAST(n)),
28069
- args: [],
28070
- ...nodePos(node),
28071
- };
28721
+ return loopCommand(node, 'while', {
28722
+ while: toCoreAST(node.condition),
28723
+ bottomTested: node.bottomTested
28724
+ ? { type: 'literal', value: true, raw: 'true', ...POS }
28725
+ : undefined,
28726
+ });
28072
28727
  }
28073
28728
  function flattenEventModifiers(modifiers) {
28074
28729
  if (!modifiers)