@hyperfixi/core 3.1.1 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +36 -17
  10. package/dist/bundle-generator/index.mjs +36 -17
  11. package/dist/bundle-generator/parser-templates.d.ts +1 -1
  12. package/dist/chunks/bridge-CI9SOl8u.js +2 -0
  13. package/dist/chunks/browser-modular-7CpI5VF2.js +2 -0
  14. package/dist/chunks/index-BwXDEK0m.js +2 -0
  15. package/dist/chunks/index-DQi2o8Lk.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 +938 -425
  23. package/dist/commands/index.mjs +938 -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 +1149 -485
  40. package/dist/index.min.js +1 -1
  41. package/dist/index.mjs +1149 -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 +396 -153
  53. package/dist/parser/full-parser.mjs +396 -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 +535 -210
  65. package/dist/registry/index.mjs +535 -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')) {
@@ -3800,6 +3945,15 @@ function parseToggleCommand(ctx, identifierNode) {
3800
3945
  args.push(classArg.expression);
3801
3946
  modifiers['as'] = literalModifier(mode);
3802
3947
  }
3948
+ else if (classArg?.type === 'binaryExpression' &&
3949
+ classArg.operator === 'of' &&
3950
+ classArg.left?.type === 'selector' &&
3951
+ typeof classArg.left.value === 'string' &&
3952
+ classArg.left.value.startsWith('*') &&
3953
+ classArg.right) {
3954
+ args.push(classArg.left);
3955
+ modifiers['on'] = classArg.right;
3956
+ }
3803
3957
  else if (classArg) {
3804
3958
  args.push(classArg);
3805
3959
  }
@@ -3977,11 +4131,17 @@ function parseSwapCommand(ctx, identifierNode) {
3977
4131
  const targetExpr = isDelete ? ctx.parseExpression() : parseOneArgument(ctx, [KEYWORDS.WITH]);
3978
4132
  if (targetExpr)
3979
4133
  args.push(targetExpr);
3980
- if (!isDelete && consumeOptionalKeyword(ctx, KEYWORDS.WITH)) {
4134
+ const isMorph = identifierNode.name === 'morph';
4135
+ if (!isDelete &&
4136
+ (consumeOptionalKeyword(ctx, KEYWORDS.WITH) ||
4137
+ (isMorph && consumeOptionalKeyword(ctx, KEYWORDS.TO)))) {
3981
4138
  const contentExpr = parseOneArgument(ctx, ['using']);
3982
4139
  if (contentExpr)
3983
4140
  modifiers['with'] = contentExpr;
3984
4141
  }
4142
+ if (isMorph && modifiers['with'] === undefined) {
4143
+ ctx.addError("Expected 'to' and the content to morph into");
4144
+ }
3985
4145
  return CommandNodeBuilder.fromIdentifier(identifierNode)
3986
4146
  .withArgs(...args)
3987
4147
  .withModifiers({ ...modifiers, ...parseViewTransitionTail(ctx) })
@@ -4742,6 +4902,8 @@ function atArgumentBoundary(ctx, grammar) {
4742
4902
  const next = ctx.peek();
4743
4903
  if (ctx.checkIsCommand() && ctx.peekAt(1)?.value === '(')
4744
4904
  return false;
4905
+ if (isGluedBeepBang(next, ctx.peekAt(1)))
4906
+ return false;
4745
4907
  if (isCommandBoundary(ctx, ['catch', 'finally', 'on']))
4746
4908
  return true;
4747
4909
  return isKeyword(next, [...grammar.markers]);
@@ -4773,10 +4935,13 @@ function parseDeclaredCommand(ctx, commandToken, name, grammar) {
4773
4935
  }
4774
4936
  }
4775
4937
  const commaList = new Set(grammar.commaList ?? []);
4938
+ const namedArgs = new Set(grammar.namedArgs ?? []);
4776
4939
  while (!ctx.isAtEnd() && isKeyword(ctx.peek(), [...grammar.markers])) {
4777
4940
  const keyword = ctx.advance().value;
4778
4941
  const key = ctx.resolveKeyword(keyword).toLowerCase();
4779
- let value = ctx.parseExpression();
4942
+ let value = namedArgs.has(key) && isNakedNamedArgStart(ctx)
4943
+ ? parseNakedNamedArgs(ctx, () => ctx.parseExpression(), name)
4944
+ : ctx.parseExpression();
4780
4945
  if (value && commaList.has(key) && ctx.check(',')) {
4781
4946
  const elements = [value];
4782
4947
  while (ctx.match(',')) {
@@ -4941,8 +5106,8 @@ function parseFetchCommand(ctx, commandToken) {
4941
5106
  }
4942
5107
  if (ctx.check('with') && !modifiers['with']) {
4943
5108
  ctx.advance();
4944
- if (isFetchNakedNamedArgStart(ctx)) {
4945
- modifiers['with'] = parseFetchNakedNamedArgs(ctx);
5109
+ if (isNakedNamedArgStart(ctx)) {
5110
+ modifiers['with'] = parseNakedNamedArgs(ctx, () => ctx.parsePrimary(), 'fetch');
4946
5111
  }
4947
5112
  else {
4948
5113
  modifiers['with'] = ctx.parsePrimary();
@@ -4977,45 +5142,6 @@ function parseFetchCommand(ctx, commandToken) {
4977
5142
  }
4978
5143
  return builder.build();
4979
5144
  }
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
5145
  function findJsEndBoundary(ctx, startPos) {
5020
5146
  const input = ctx.getInputSlice(startPos);
5021
5147
  if (!input) {
@@ -5135,6 +5261,7 @@ function parseTellCommand(ctx, identifierNode) {
5135
5261
  const commands = [];
5136
5262
  while (!ctx.isAtEnd()) {
5137
5263
  if (ctx.checkIsCommand()) {
5264
+ const word = ctx.peek().value;
5138
5265
  try {
5139
5266
  ctx.advance();
5140
5267
  const cmd = ctx.parseCommand();
@@ -5145,7 +5272,8 @@ function parseTellCommand(ctx, identifierNode) {
5145
5272
  break;
5146
5273
  }
5147
5274
  }
5148
- catch {
5275
+ catch (error) {
5276
+ ctx.addError(`'${word}' in a tell body failed to parse: ${error instanceof Error ? error.message : String(error)}`);
5149
5277
  break;
5150
5278
  }
5151
5279
  if (ctx.match(KEYWORDS.AND) || ctx.match(KEYWORDS.THEN)) {
@@ -5294,7 +5422,6 @@ const ONE_EXPR = (syntax) => ({
5294
5422
  markers: [],
5295
5423
  syntax,
5296
5424
  });
5297
- const BEEP = ONE_EXPR('beep! [<expression>, \u2026]');
5298
5425
  const COMMAND_GRAMMAR = {
5299
5426
  append: { positional: 'expression', markers: ['to'], syntax: 'append <value> [to <target>]' },
5300
5427
  prepend: { positional: 'expression', markers: ['to'], syntax: 'prepend <value> [to <target>]' },
@@ -5305,8 +5432,7 @@ const COMMAND_GRAMMAR = {
5305
5432
  syntax: 'make (a|an) <type> [from <args>] [called <name>]',
5306
5433
  },
5307
5434
  throw: { positional: 'primary', markers: [], syntax: 'throw <error>' },
5308
- 'beep!': BEEP,
5309
- beep: BEEP,
5435
+ beep: ONE_EXPR('beep! [<expression>, \u2026]'),
5310
5436
  blur: ONE_EXPR('blur [<target>]'),
5311
5437
  break: NONE,
5312
5438
  breakpoint: NONE,
@@ -5330,7 +5456,8 @@ const COMMAND_GRAMMAR = {
5330
5456
  render: {
5331
5457
  positional: 'expression',
5332
5458
  markers: ['with'],
5333
- syntax: 'render <template> [with <variables>]',
5459
+ namedArgs: ['with'],
5460
+ syntax: 'render <template> [with <name>: <value>, \u2026]',
5334
5461
  },
5335
5462
  reset: ONE_EXPR('reset [<form>]'),
5336
5463
  return: ONE_EXPR('return [<value>]'),
@@ -5685,7 +5812,10 @@ class Parser {
5685
5812
  const firstToken = this.peek();
5686
5813
  const prefixEntry = Parser.PRATT_TABLE.get(firstToken.value);
5687
5814
  let left;
5688
- if (prefixEntry?.prefix) {
5815
+ if (isGluedBeepBang(firstToken, this.tokens[this.current + 1])) {
5816
+ left = this.parseBeepExpression();
5817
+ }
5818
+ else if (prefixEntry?.prefix) {
5689
5819
  const token = this.advance();
5690
5820
  if (this.isAtEnd()) {
5691
5821
  this.addError(`Expected expression after '${token.value}' operator`);
@@ -5705,6 +5835,9 @@ class Parser {
5705
5835
  continue;
5706
5836
  }
5707
5837
  }
5838
+ if (nextToken.value === '*' && this.isSpacedStyleRef(this.current)) {
5839
+ break;
5840
+ }
5708
5841
  const infixEntry = Parser.PRATT_TABLE.get(nextToken.value);
5709
5842
  if (!infixEntry?.infix) {
5710
5843
  if (STOP_TOKENS.has(nextToken.value) || STOP_DELIMITERS.has(nextToken.value)) {
@@ -5766,6 +5899,35 @@ class Parser {
5766
5899
  this.prattCache.set(cacheKey, { node: left, endPos: this.current });
5767
5900
  return left;
5768
5901
  }
5902
+ parseBeepExpression() {
5903
+ const beepToken = this.advance();
5904
+ this.advance();
5905
+ if (this.isAtEnd()) {
5906
+ this.addError("Expected expression after 'beep!'");
5907
+ return this.createErrorNode();
5908
+ }
5909
+ const operand = this.parseExpressionPratt(Parser.BEEP_OPERAND_BP);
5910
+ return {
5911
+ type: 'unaryExpression',
5912
+ operator: 'beep!',
5913
+ operand,
5914
+ argument: operand,
5915
+ prefix: true,
5916
+ start: beepToken.start,
5917
+ end: this.previous().end,
5918
+ line: beepToken.line,
5919
+ column: beepToken.column,
5920
+ };
5921
+ }
5922
+ isStyleRefStar(i) {
5923
+ const star = this.tokens[i];
5924
+ const name = this.tokens[i + 1];
5925
+ return star?.value === '*' && !!name && name.start === star.end && isIdentifierLike(name);
5926
+ }
5927
+ isSpacedStyleRef(i) {
5928
+ const prev = this.tokens[i - 1];
5929
+ return this.isStyleRefStar(i) && !!prev && prev.end < this.tokens[i].start;
5930
+ }
5769
5931
  tryParseStringPostfix(left, nextToken) {
5770
5932
  const v = nextToken.value;
5771
5933
  let unit = null;
@@ -5968,6 +6130,10 @@ class Parser {
5968
6130
  const isStop = () => this.check('end') || extraStops.some(s => this.check(s));
5969
6131
  debug.parse('\ud83d\udd04 parseCommandListUntilTerminator: Starting (extraStops:', extraStops.join(','), ')');
5970
6132
  while (!this.isAtEnd() && !isStop()) {
6133
+ if (this.checkComment()) {
6134
+ this.advance();
6135
+ continue;
6136
+ }
5971
6137
  debug.parse('\ud83d\udccd Loop iteration, current token:', this.peek().value, 'kind:', this.peek().kind);
5972
6138
  let parsedCommand = false;
5973
6139
  const isCommandToken = this.checkIsCommand();
@@ -6051,7 +6217,7 @@ class Parser {
6051
6217
  debug.parse('\u2705 Found "end", consuming it');
6052
6218
  this.advance();
6053
6219
  }
6054
- else {
6220
+ else if (!this.atEndOfInput()) {
6055
6221
  debug.parse('\u274c ERROR: Expected "end" but got:', this.peek().value, 'at position:', this.peek().start);
6056
6222
  throw new Error(`Expected "end" to close ${construct} block`);
6057
6223
  }
@@ -6064,7 +6230,7 @@ class Parser {
6064
6230
  if (this.check('end')) {
6065
6231
  this.advance();
6066
6232
  }
6067
- else {
6233
+ else if (!this.atEndOfInput()) {
6068
6234
  throw new Error('Expected "end" to close repeat block');
6069
6235
  }
6070
6236
  }
@@ -6080,7 +6246,10 @@ class Parser {
6080
6246
  this.advance();
6081
6247
  }
6082
6248
  else if (terminator === '') {
6083
- throw new Error('Expected "end", "else", "until", or "while" to close repeat block');
6249
+ if (!this.atEndOfInput()) {
6250
+ throw new Error('Expected "end", "else", "until", or "while" to close repeat block');
6251
+ }
6252
+ return { commands, terminator: 'end' };
6084
6253
  }
6085
6254
  return { commands, terminator };
6086
6255
  }
@@ -6664,7 +6833,9 @@ class Parser {
6664
6833
  parseTopLevelInitBlock() {
6665
6834
  const pos = this.getPosition();
6666
6835
  const initCommands = this.parseCommandBlock(['end']);
6667
- this.consume('end', "Expected 'end' after init block");
6836
+ if (!this.match('end') && !this.atEndOfInput() && !this.checkFeatureStart()) {
6837
+ this.consume('end', "Expected 'end' after init block");
6838
+ }
6668
6839
  return {
6669
6840
  type: 'initBlock',
6670
6841
  commands: initCommands,
@@ -6840,10 +7011,29 @@ class Parser {
6840
7011
  ...(sourceExpr.column !== undefined && { column: sourceExpr.column }),
6841
7012
  };
6842
7013
  }
7014
+ isArgumentListOnly(open) {
7015
+ let depth = 0;
7016
+ for (let i = open; i < this.tokens.length; i++) {
7017
+ const token = this.tokens[i];
7018
+ if (token.kind !== 'operator')
7019
+ continue;
7020
+ if (token.value === '(' || token.value === '[' || token.value === '{')
7021
+ depth++;
7022
+ else if (token.value === ')' || token.value === ']' || token.value === '}') {
7023
+ if (--depth === 0)
7024
+ return i === open + 1;
7025
+ }
7026
+ else if (token.value === ',' && depth === 1)
7027
+ return true;
7028
+ }
7029
+ return false;
7030
+ }
6843
7031
  tryParsePseudoCommand(callExpr, sourceExpr) {
6844
7032
  const methodName = callExpr.callee.name;
6845
7033
  const nextToken = this.peek();
6846
- const hasPseudoCommandPattern = Parser.PSEUDO_COMMAND_PREPOSITIONS.includes(nextToken.value.toLowerCase()) ||
7034
+ const nextIsArticle = nextToken.value.toLowerCase() === 'the';
7035
+ const hasPseudoCommandPattern = nextIsArticle ||
7036
+ Parser.PSEUDO_COMMAND_PREPOSITIONS.includes(nextToken.value.toLowerCase()) ||
6847
7037
  (isIdentifier(nextToken) && !this.isCommand(nextToken.value)) ||
6848
7038
  isContextVar(nextToken);
6849
7039
  if (!hasPseudoCommandPattern) {
@@ -6869,7 +7059,10 @@ class Parser {
6869
7059
  });
6870
7060
  }
6871
7061
  let preposition;
6872
- if (Parser.PSEUDO_COMMAND_PREPOSITIONS.includes(this.peek().value.toLowerCase())) {
7062
+ if (nextIsArticle) {
7063
+ this.advance();
7064
+ }
7065
+ else if (Parser.PSEUDO_COMMAND_PREPOSITIONS.includes(this.peek().value.toLowerCase())) {
6873
7066
  preposition = this.advance().value.toLowerCase();
6874
7067
  }
6875
7068
  let targetExpr;
@@ -7033,10 +7226,22 @@ class Parser {
7033
7226
  this.consume(']', "Expected ']' after event condition");
7034
7227
  }
7035
7228
  let target;
7229
+ let targetExpression;
7036
7230
  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})`);
7231
+ if (this.check('the'))
7232
+ this.advance();
7233
+ if (Parser.POSITIONAL_SOURCE_HEADS.has(this.peek().value.toLowerCase())) {
7234
+ const startIndex = this.current;
7235
+ targetExpression = this.parseExpression();
7236
+ target = this.tokens
7237
+ .slice(startIndex, this.current)
7238
+ .map(t => t.value)
7239
+ .join(' ');
7240
+ }
7241
+ else {
7242
+ target = this.advance().value;
7243
+ }
7244
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed 'from' target: ${target}`);
7040
7245
  this.parseTemporalKeywordModifiers(modifiers);
7041
7246
  }
7042
7247
  let attributeName;
@@ -7082,7 +7287,7 @@ class Parser {
7082
7287
  const nextIsOpenParen = this.tokens[this.current + 1]?.value === '(';
7083
7288
  const commandName = this.peek().value.toLowerCase();
7084
7289
  const isSpecialBodyCommand = commandName === 'js' || commandName === 'tell';
7085
- if (nextIsOpenParen && !isSpecialBodyCommand) {
7290
+ if (nextIsOpenParen && !isSpecialBodyCommand && this.isArgumentListOnly(this.current + 1)) {
7086
7291
  let expr;
7087
7292
  const savedError = this.error;
7088
7293
  try {
@@ -7114,7 +7319,7 @@ class Parser {
7114
7319
  }
7115
7320
  }
7116
7321
  }
7117
- else if (this.checkIdentifier()) {
7322
+ else if (this.checkIdentifier() || this.checkEventNamedCall()) {
7118
7323
  const token = this.peek();
7119
7324
  if (this.isCommand(token.value)) {
7120
7325
  const cmd = this.parseCommandWithErrorRecovery();
@@ -7223,6 +7428,7 @@ class Parser {
7223
7428
  ...(eventParams.length > 0 && { args: eventParams }),
7224
7429
  ...(condition && { condition: fromLegacyExpression(condition) }),
7225
7430
  ...(target && { target }),
7431
+ ...(targetExpression && { targetExpression: fromLegacyExpression(targetExpression) }),
7226
7432
  ...(attributeName && { attributeName }),
7227
7433
  ...(watchTarget && { watchTarget: fromLegacyExpression(watchTarget) }),
7228
7434
  ...(customEventSource && { customEventSource }),
@@ -7478,10 +7684,9 @@ class Parser {
7478
7684
  }
7479
7685
  parseCommandCore() {
7480
7686
  const commandToken = this.previous();
7481
- let commandName = commandToken.value;
7687
+ const commandName = commandToken.value;
7482
7688
  if (commandName === 'beep' && this.check('!')) {
7483
7689
  this.advance();
7484
- commandName = 'beep!';
7485
7690
  }
7486
7691
  if (commandName === 'fetch') {
7487
7692
  return parseFetchCommand(this.getContext(), commandToken);
@@ -7580,6 +7785,9 @@ class Parser {
7580
7785
  args.push(this.parsePrimary());
7581
7786
  }
7582
7787
  const callNode = this.createCallExpression(createIdentifier(funcName, namePos), args);
7788
+ if (funcName === 'closest' && args.length > 0 && this.match('to')) {
7789
+ callNode.closestTo = this.parseExpression();
7790
+ }
7583
7791
  if (funcName === 'next' || funcName === 'previous') {
7584
7792
  const relativePositional = this.parseRelativePositionalModifiers();
7585
7793
  if (relativePositional) {
@@ -7619,6 +7827,9 @@ class Parser {
7619
7827
  }
7620
7828
  parseTheXofY() {
7621
7829
  const positionalKeywords = ['first', 'last', 'next', 'previous', 'random', 'closest'];
7830
+ if (this.isStyleRefStar(this.current)) {
7831
+ return this.parsePrimary();
7832
+ }
7622
7833
  if (!this.checkIdentifier()) {
7623
7834
  return this.createIdentifier('the');
7624
7835
  }
@@ -7634,7 +7845,7 @@ class Parser {
7634
7845
  return this.createIdentifier(propertyName);
7635
7846
  }
7636
7847
  this.advance();
7637
- const target = this.parseCall();
7848
+ const target = parseOfOperand(this.makePrattContext(), THE_OF_TARGET_BP);
7638
7849
  return {
7639
7850
  type: 'propertyOfExpression',
7640
7851
  property: { type: 'identifier', name: propertyName },
@@ -7832,6 +8043,13 @@ class Parser {
7832
8043
  checkLiteral() {
7833
8044
  return this.checkPredicate(isLiteral);
7834
8045
  }
8046
+ checkEventNamedCall() {
8047
+ if (this.isAtEnd() || !this.checkIdentifierLike() || this.checkIsCommand())
8048
+ return false;
8049
+ const token = this.peek();
8050
+ const next = this.tokens[this.current + 1];
8051
+ return next?.value === '(' && next.start === token.end;
8052
+ }
7835
8053
  checkIdentifierLike() {
7836
8054
  return this.checkPredicate(isIdentifierLike);
7837
8055
  }
@@ -7940,6 +8158,22 @@ class Parser {
7940
8158
  isAtEnd() {
7941
8159
  return this.current >= this.tokens.length;
7942
8160
  }
8161
+ atEndOfInput() {
8162
+ for (let i = this.current; i < this.tokens.length; i++) {
8163
+ if (!isComment(this.tokens[i]))
8164
+ return false;
8165
+ }
8166
+ return true;
8167
+ }
8168
+ checkFeatureStart() {
8169
+ if (this.isAtEnd())
8170
+ return false;
8171
+ const value = this.peek().value;
8172
+ return (value === 'init' ||
8173
+ value === 'on' ||
8174
+ value === 'def' ||
8175
+ getRegisteredFeature(value) !== undefined);
8176
+ }
7943
8177
  peek() {
7944
8178
  if (this.isAtEnd()) {
7945
8179
  return { kind: 'unknown', value: '', start: 0, end: 0, line: 1, column: 1 };
@@ -8151,6 +8385,7 @@ class Parser {
8151
8385
  match: this.match.bind(this),
8152
8386
  matchOperator: this.matchOperator.bind(this),
8153
8387
  isAtEnd: this.isAtEnd.bind(this),
8388
+ atEndOfInput: this.atEndOfInput.bind(this),
8154
8389
  createIdentifier: this.createIdentifier.bind(this),
8155
8390
  parseExpression: this.parseExpression.bind(this),
8156
8391
  parsePrimary: this.parsePrimary.bind(this),
@@ -8201,7 +8436,15 @@ Parser.POSTFIX_UNARY_OPERATORS = new Set([
8201
8436
  'ignoring case',
8202
8437
  ]);
8203
8438
  Parser.PRATT_TABLE = PARSER_TABLE;
8439
+ Parser.BEEP_OPERAND_BP = 80;
8204
8440
  Parser.PSEUDO_COMMAND_PREPOSITIONS = ['from', 'on', 'with', 'into', 'at', 'to'];
8441
+ Parser.POSITIONAL_SOURCE_HEADS = new Set([
8442
+ 'closest',
8443
+ 'next',
8444
+ 'previous',
8445
+ 'first',
8446
+ 'last',
8447
+ ]);
8205
8448
  function parse(input, options) {
8206
8449
  const tokens = tokenize(input);
8207
8450
  const parser = new Parser(tokens, options, input);
@@ -8406,6 +8649,89 @@ function isDocumentFragment(value) {
8406
8649
  'childNodes' in value);
8407
8650
  }
8408
8651
 
8652
+ function beepValues(me, values) {
8653
+ const outputs = values.map(describeBeepValue);
8654
+ const shown = outputs.filter(output => announce(me, output.value));
8655
+ if (shown.length === 0)
8656
+ return outputs;
8657
+ console.group('\ud83d\udd14 beep! Debug Output');
8658
+ for (const output of shown) {
8659
+ console.log(`Value:`, output.value);
8660
+ console.log(`Type:`, output.type);
8661
+ console.log(`Representation:`, output.representation);
8662
+ console.log('---');
8663
+ }
8664
+ console.groupEnd();
8665
+ return outputs;
8666
+ }
8667
+ function announce(me, value) {
8668
+ const target = me;
8669
+ if (typeof CustomEvent !== 'function' || typeof target?.dispatchEvent !== 'function')
8670
+ return true;
8671
+ return target.dispatchEvent(new CustomEvent('hyperscript:beep', {
8672
+ bubbles: true,
8673
+ cancelable: true,
8674
+ detail: { element: me, value },
8675
+ }));
8676
+ }
8677
+ function describeBeepValue(value) {
8678
+ return { value, type: typeOf(value), representation: representationOf(value) };
8679
+ }
8680
+ function typeOf(value) {
8681
+ if (value === null)
8682
+ return 'null';
8683
+ if (value === undefined)
8684
+ return 'undefined';
8685
+ if (Array.isArray(value))
8686
+ return 'array';
8687
+ if (isHTMLElement$2(value))
8688
+ return 'HTMLElement';
8689
+ if (typeof Element !== 'undefined' && value instanceof Element)
8690
+ return 'Element';
8691
+ if (typeof Node !== 'undefined' && value instanceof Node)
8692
+ return 'Node';
8693
+ if (value instanceof Error)
8694
+ return 'Error';
8695
+ if (value instanceof Date)
8696
+ return 'Date';
8697
+ if (value instanceof RegExp)
8698
+ return 'RegExp';
8699
+ return typeof value;
8700
+ }
8701
+ function representationOf(value) {
8702
+ if (value === null)
8703
+ return 'null';
8704
+ if (value === undefined)
8705
+ return 'undefined';
8706
+ if (Array.isArray(value)) {
8707
+ return `Array(${value.length}) [${value
8708
+ .slice(0, 3)
8709
+ .map(v => representationOf(v))
8710
+ .join(', ')}${value.length > 3 ? '...' : ''}]`;
8711
+ }
8712
+ if (isHTMLElement$2(value)) {
8713
+ const el = value;
8714
+ const tag = el.tagName.toLowerCase();
8715
+ const id = el.id ? `#${el.id}` : '';
8716
+ const classes = el.className ? `.${el.className.split(' ').join('.')}` : '';
8717
+ return `<${tag}${id}${classes}/>`;
8718
+ }
8719
+ if (value instanceof Error)
8720
+ return `Error: ${value.message}`;
8721
+ if (typeof value === 'string')
8722
+ return value.length > 50 ? `"${value.substring(0, 47)}..."` : `"${value}"`;
8723
+ if (typeof value === 'object') {
8724
+ try {
8725
+ const keys = Object.keys(value);
8726
+ return `Object {${keys.slice(0, 3).join(', ')}${keys.length > 3 ? '...' : ''}}`;
8727
+ }
8728
+ catch {
8729
+ return '[Object]';
8730
+ }
8731
+ }
8732
+ return String(value);
8733
+ }
8734
+
8409
8735
  function convertToNumber(value) {
8410
8736
  if (value === null || value === undefined) {
8411
8737
  return 0;
@@ -8437,6 +8763,16 @@ function convertToNumber(value) {
8437
8763
  }
8438
8764
  return 0;
8439
8765
  }
8766
+ function counterAmount(value) {
8767
+ return typeof value === 'number' ? value : parseFloat(String(value));
8768
+ }
8769
+ function counterValue(value) {
8770
+ if (isHTMLElement$2(value)) {
8771
+ const element = value;
8772
+ return counterValue(element.value || element.textContent);
8773
+ }
8774
+ return value ? counterAmount(value) : 0;
8775
+ }
8440
8776
  function getVariableValue(name, context, preferredScope) {
8441
8777
  if (preferredScope === 'element') {
8442
8778
  return getElementVar(context, name);
@@ -8541,35 +8877,39 @@ function setElementPropertyValue(propertyPath, value, context) {
8541
8877
  element[property] = value;
8542
8878
  }
8543
8879
  }
8880
+ function isObjectOwner(value) {
8881
+ return (typeof value === 'object' && value !== null && !Array.isArray(value) && !isHTMLElement$2(value));
8882
+ }
8544
8883
  function getCurrentNumericValue(target, property, scope, context) {
8545
8884
  if (typeof target === 'number') {
8546
8885
  return target;
8547
8886
  }
8548
8887
  if (isHTMLElement$2(target)) {
8549
8888
  const element = target;
8889
+ if (property?.startsWith('@'))
8890
+ return counterValue(element.getAttribute(property.slice(1)));
8550
8891
  if (property) {
8551
8892
  if (property.startsWith('data-') ||
8552
8893
  ['id', 'class', 'title', 'alt', 'src', 'href'].includes(property)) {
8553
- const value = element.getAttribute(property);
8554
- return convertToNumber(value);
8894
+ return counterValue(element.getAttribute(property));
8555
8895
  }
8556
8896
  else {
8557
- const value = element[property];
8558
- return convertToNumber(value);
8897
+ return counterValue(element[property]);
8559
8898
  }
8560
8899
  }
8561
8900
  else {
8562
- const value = element.value || element.textContent;
8563
- return convertToNumber(value);
8901
+ return counterValue(element);
8564
8902
  }
8565
8903
  }
8904
+ if (property && isObjectOwner(target)) {
8905
+ return counterValue(target[property]);
8906
+ }
8566
8907
  if (typeof target === 'string') {
8567
8908
  if (scope === 'element') {
8568
- return convertToNumber(getVariableValue(target, context, 'element'));
8909
+ return counterValue(getVariableValue(target, context, 'element'));
8569
8910
  }
8570
8911
  if (scope === 'global') {
8571
- const value = getVariableValue(target, context, 'global');
8572
- return convertToNumber(value);
8912
+ return counterValue(getVariableValue(target, context, 'global'));
8573
8913
  }
8574
8914
  if (target.includes('.')) {
8575
8915
  return getElementPropertyValue(target, context);
@@ -8583,14 +8923,17 @@ function getCurrentNumericValue(target, property, scope, context) {
8583
8923
  else if (target === 'you' && context.you) {
8584
8924
  return convertToNumber(context.you.value || 0);
8585
8925
  }
8586
- const value = getVariableValue(target, context);
8587
- return convertToNumber(value);
8926
+ return counterValue(getVariableValue(target, context));
8588
8927
  }
8589
- return convertToNumber(target);
8928
+ return counterValue(target);
8590
8929
  }
8591
8930
  function setTargetValue(target, property, scope, newValue, context) {
8592
8931
  if (isHTMLElement$2(target)) {
8593
8932
  const element = target;
8933
+ if (property?.startsWith('@')) {
8934
+ element.setAttribute(property.slice(1), String(newValue));
8935
+ return;
8936
+ }
8594
8937
  if (property) {
8595
8938
  if (property.startsWith('data-') ||
8596
8939
  ['id', 'class', 'title', 'alt', 'src', 'href'].includes(property)) {
@@ -8610,6 +8953,10 @@ function setTargetValue(target, property, scope, newValue, context) {
8610
8953
  }
8611
8954
  return;
8612
8955
  }
8956
+ if (property && isObjectOwner(target)) {
8957
+ target[property] = newValue;
8958
+ return;
8959
+ }
8613
8960
  if (typeof target === 'string') {
8614
8961
  if (scope === 'element') {
8615
8962
  setVariableValue(target, newValue, context, 'element');
@@ -9038,13 +9385,6 @@ function isFunction(value) {
9038
9385
  function isInputElement$1(el) {
9039
9386
  return (el !== null && typeof el === 'object' && 'tagName' in el && el.tagName === 'INPUT');
9040
9387
  }
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
9388
  function isHTMLElement$1(el) {
9049
9389
  return (el !== null &&
9050
9390
  typeof el === 'object' &&
@@ -9104,7 +9444,7 @@ const SPECIAL_DOM_PROPERTIES = {
9104
9444
  innertext: el => el.textContent?.trim(),
9105
9445
  innerHTML: el => el.innerHTML,
9106
9446
  outerhtml: el => el.outerHTML,
9107
- value: el => (isFormElement$1(el) ? el.value : undefined),
9447
+ value: el => ('value' in el ? el.value : undefined),
9108
9448
  checked: el => (isInputElement$1(el) ? el.checked : undefined),
9109
9449
  disabled: el => ('disabled' in el ? el.disabled : undefined),
9110
9450
  selected: el => (isOptionElement$1(el) ? el.selected : undefined),
@@ -9772,14 +10112,15 @@ const closestExpression = {
9772
10112
  if (typeof selector !== 'string') {
9773
10113
  throw new Error('closest requires a string selector');
9774
10114
  }
9775
- if (!context.me) {
10115
+ const start = args.length > 1 ? args[1] : context.me;
10116
+ if (!(start instanceof Element)) {
9776
10117
  return null;
9777
10118
  }
9778
- return context.me.closest(selector);
10119
+ return start.closest(selector);
9779
10120
  },
9780
10121
  validate(args) {
9781
- if (args.length !== 1) {
9782
- return 'closest requires exactly 1 argument (selector)';
10122
+ if (args.length !== 1 && args.length !== 2) {
10123
+ return 'closest requires a selector and, optionally, the element to start from';
9783
10124
  }
9784
10125
  const arg = args[0];
9785
10126
  if (typeof arg === 'string')
@@ -11279,7 +11620,7 @@ const isEmptyExpression = {
11279
11620
  else if (value instanceof Node || value instanceof Element)
11280
11621
  result = false;
11281
11622
  else if (isObject(value))
11282
- result = Object.keys(value).length === 0;
11623
+ result = value.length === 0;
11283
11624
  else
11284
11625
  result = false;
11285
11626
  return result;
@@ -12403,11 +12744,16 @@ async function evaluateBinaryExpression(node, context) {
12403
12744
  }
12404
12745
  }
12405
12746
  }
12406
- if (leftNode?.type === 'selector' && leftNode.fromQuery && typeof leftNode.value === 'string') {
12747
+ if (leftNode?.type === 'selector' &&
12748
+ typeof leftNode.value === 'string' &&
12749
+ (leftNode.fromQuery || (operator === 'in' && leftNode.value.startsWith('.')))) {
12407
12750
  const root = await evaluateAST(node.right, context);
12408
12751
  const scope = root && typeof root.querySelectorAll === 'function' ? root : null;
12409
12752
  if (scope) {
12410
- return Array.from(scope.querySelectorAll(leftNode.value));
12753
+ const query = leftNode.fromQuery
12754
+ ? leftNode.value
12755
+ : escapeSelectorForQuery(leftNode, leftNode.value);
12756
+ return Array.from(scope.querySelectorAll(query));
12411
12757
  }
12412
12758
  }
12413
12759
  }
@@ -12419,6 +12765,12 @@ async function evaluateBinaryExpression(node, context) {
12419
12765
  return getExpr(context, 'styleRef').evaluate(context, leftNode.value.slice(1), el);
12420
12766
  }
12421
12767
  if (operator === 'of') {
12768
+ if (leftNode?.type === 'attributeAccess') {
12769
+ const target = await evaluateAST(node.right, context);
12770
+ if (target == null)
12771
+ return undefined;
12772
+ return getExpr(context, 'possessive').evaluate(context, target, `@${leftNode.attributeName}`);
12773
+ }
12422
12774
  const path = propertyPathOf(node.left);
12423
12775
  if (path) {
12424
12776
  const target = await evaluateAST(node.right, context);
@@ -12426,6 +12778,10 @@ async function evaluateBinaryExpression(node, context) {
12426
12778
  for (const seg of path) {
12427
12779
  if (cur == null)
12428
12780
  return undefined;
12781
+ if (isMappableCollection(cur) && !(seg in cur)) {
12782
+ cur = await getExpr(context, 'possessive').evaluate(context, cur, seg);
12783
+ continue;
12784
+ }
12429
12785
  cur = isElement(cur) ? getElementProperty$1(cur, seg) : cur[seg];
12430
12786
  }
12431
12787
  return typeof cur === 'function' ? cur.bind(target) : cur;
@@ -12450,26 +12806,21 @@ async function evaluateBinaryExpression(node, context) {
12450
12806
  const R = node.ignoringCase ? applyCI(right) : right;
12451
12807
  switch (operator) {
12452
12808
  case '+': {
12453
- let addLeft = left;
12454
- let addRight = right;
12455
12809
  if (node.coerceNumeric) {
12456
- if (typeof addLeft === 'string')
12457
- addLeft = convertToNumber(addLeft);
12458
- if (typeof addRight === 'string')
12459
- addRight = convertToNumber(addRight);
12810
+ return counterValue(left) + counterAmount(right);
12460
12811
  }
12461
- if (typeof addLeft === 'string' || typeof addRight === 'string') {
12462
- return String(addLeft ?? '') + String(addRight ?? '');
12812
+ if (typeof left === 'string' || typeof right === 'string') {
12813
+ return String(left ?? '') + String(right ?? '');
12463
12814
  }
12464
- if (Array.isArray(addLeft)) {
12465
- return Array.isArray(addRight) ? [...addLeft, ...addRight] : [...addLeft, addRight];
12815
+ if (Array.isArray(left)) {
12816
+ return Array.isArray(right) ? [...left, ...right] : [...left, right];
12466
12817
  }
12467
- return unwrapTypedResult(await getExpr(context, 'addition').evaluate(context, {
12468
- left: addLeft,
12469
- right: addRight,
12470
- }));
12818
+ return unwrapTypedResult(await getExpr(context, 'addition').evaluate(context, { left, right }));
12471
12819
  }
12472
12820
  case '-':
12821
+ if (node.coerceNumeric) {
12822
+ return counterValue(left) - counterAmount(right);
12823
+ }
12473
12824
  return unwrapTypedResult(await getExpr(context, 'subtraction').evaluate(context, { left, right }));
12474
12825
  case '*':
12475
12826
  return unwrapTypedResult(await getExpr(context, 'multiplication').evaluate(context, { left, right }));
@@ -12727,9 +13078,8 @@ async function evaluatePropertyOfExpressionNode(node, context) {
12727
13078
  }
12728
13079
  const propertyName = propertyNode.name;
12729
13080
  const target = await evaluateAST(node.target, context);
12730
- if (target == null) {
12731
- throw new Error(`Cannot access property "${propertyName}" of ${target}`);
12732
- }
13081
+ if (target == null)
13082
+ return undefined;
12733
13083
  return getExpr(context, 'possessive').evaluate(context, target, propertyName);
12734
13084
  }
12735
13085
  async function evaluateTemplateLiteralNode(node, context) {
@@ -12847,6 +13197,9 @@ async function evaluateUnaryExpression(node, context) {
12847
13197
  case 'not':
12848
13198
  case '!':
12849
13199
  return logicalExpressions.not.evaluate(context, value);
13200
+ case 'beep!':
13201
+ beepValues(context.me, [value]);
13202
+ return value;
12850
13203
  case 'no':
12851
13204
  return logicalExpressions.no.evaluate(context, value);
12852
13205
  case '-':
@@ -12936,6 +13289,10 @@ async function evaluateCallExpression(node, context) {
12936
13289
  const args = await resolveCallArgs(node.arguments, funcName, context);
12937
13290
  switch (funcName) {
12938
13291
  case 'closest':
13292
+ if (node.closestTo) {
13293
+ const start = unwrapElement(await evaluateAST(node.closestTo, context)) ?? null;
13294
+ return referencesExpressions.closest.evaluate(context, args[0], start);
13295
+ }
12939
13296
  return referencesExpressions.closest.evaluate(context, ...args);
12940
13297
  case 'previous':
12941
13298
  case 'next': {
@@ -14322,12 +14679,23 @@ class RuntimeBase {
14322
14679
  debug.runtime(`BEHAVIOR: Finished installing ${behaviorName}`);
14323
14680
  }
14324
14681
  async executeEventHandler(node, context) {
14325
- const { event, events, commands, target, args, selector, condition, attributeName, watchTarget, modifiers, errorSymbol, errorHandler, finallyHandler, } = node;
14682
+ const { event, events, commands, target, targetExpression, args, selector, condition, attributeName, watchTarget, modifiers, errorSymbol, errorHandler, finallyHandler, } = node;
14326
14683
  const eventNames = events && events.length > 0 ? events : [event];
14327
14684
  debug.runtime(`BEHAVIOR: executeEventHandler: event='${event}', target='${target}'`);
14328
14685
  let targets = [];
14329
14686
  let globalTarget = null;
14330
- if (target) {
14687
+ if (targetExpression) {
14688
+ const resolved = await this.execute(targetExpression, context);
14689
+ if (resolved === window)
14690
+ globalTarget = window;
14691
+ else if (resolved === document)
14692
+ globalTarget = document;
14693
+ else if (this.isElement(resolved))
14694
+ targets = [resolved];
14695
+ else if (Array.isArray(resolved))
14696
+ targets = resolved.filter(el => this.isElement(el));
14697
+ }
14698
+ else if (target) {
14331
14699
  const targetLower = typeof target === 'string' ? target.toLowerCase() : '';
14332
14700
  if (targetLower === 'window' || targetLower === 'the window') {
14333
14701
  globalTarget = window;
@@ -16348,14 +16716,83 @@ function parseAttribute(expression) {
16348
16716
  if (trimmed.startsWith('@')) {
16349
16717
  return { name: trimmed.substring(1).trim() };
16350
16718
  }
16351
- throw new Error(`Invalid attribute syntax: ${expression}`);
16352
- }
16353
- function parseAttributeName(expression) {
16354
- return parseAttribute(expression).name;
16719
+ throw new Error(`Invalid attribute syntax: ${expression}`);
16720
+ }
16721
+ function parseAttributeName(expression) {
16722
+ return parseAttribute(expression).name;
16723
+ }
16724
+ function parseAttributeWithValue(expression) {
16725
+ const result = parseAttribute(expression);
16726
+ return { name: result.name, value: result.value ?? '' };
16727
+ }
16728
+
16729
+ function _parseDurationComponents(value) {
16730
+ const trimmed = value.trim();
16731
+ const match = trimmed.match(/^(\d+(?:\.\d+)?)\s*(s|ms|sec|seconds?|milliseconds?)?$/i);
16732
+ if (!match) {
16733
+ return null;
16734
+ }
16735
+ const number = parseFloat(match[1]);
16736
+ const unit = (match[2] || 'ms').toLowerCase();
16737
+ if (unit === 'ms' || unit === 'millisecond' || unit === 'milliseconds') {
16738
+ return { number, milliseconds: Math.floor(number) };
16739
+ }
16740
+ else if (unit === 's' || unit === 'sec' || unit === 'second' || unit === 'seconds') {
16741
+ return { number, milliseconds: Math.floor(number * 1000) };
16742
+ }
16743
+ return null;
16744
+ }
16745
+ function parseDuration(value, defaultMs = 300) {
16746
+ if (typeof value === 'number') {
16747
+ return Math.max(0, Math.floor(value));
16748
+ }
16749
+ if (typeof value === 'string') {
16750
+ const result = _parseDurationComponents(value);
16751
+ return result ? result.milliseconds : defaultMs;
16752
+ }
16753
+ return defaultMs;
16754
+ }
16755
+ function parseDurationStrict(value) {
16756
+ if (typeof value === 'number') {
16757
+ if (value < 0)
16758
+ throw new Error('Duration must be >= 0');
16759
+ return Math.floor(value);
16760
+ }
16761
+ if (typeof value === 'string') {
16762
+ const result = _parseDurationComponents(value);
16763
+ if (!result) {
16764
+ throw new Error(`Invalid duration format: "${value}"`);
16765
+ }
16766
+ return result.milliseconds;
16767
+ }
16768
+ throw new Error(`Invalid duration type: ${typeof value}`);
16769
+ }
16770
+ function parseCSSDurations(durationString) {
16771
+ if (!durationString || durationString === 'none') {
16772
+ return [0];
16773
+ }
16774
+ return durationString.split(',').map(duration => {
16775
+ const value = parseFloat(duration.trim());
16776
+ if (isNaN(value))
16777
+ return 0;
16778
+ if (duration.includes('s') && !duration.includes('ms')) {
16779
+ return value * 1000;
16780
+ }
16781
+ return value;
16782
+ });
16783
+ }
16784
+ function calculateMaxAnimationTime(durations, delays) {
16785
+ let maxTime = 0;
16786
+ for (let i = 0; i < durations.length; i++) {
16787
+ const duration = durations[i] || 0;
16788
+ const delay = delays[i] || 0;
16789
+ const totalTime = duration + delay;
16790
+ maxTime = Math.max(maxTime, totalTime);
16791
+ }
16792
+ return maxTime;
16355
16793
  }
16356
- function parseAttributeWithValue(expression) {
16357
- const result = parseAttribute(expression);
16358
- return { name: result.name, value: result.value ?? '' };
16794
+ function camelToKebab(str) {
16795
+ return str.replace(/([A-Z])/g, '-$1').toLowerCase();
16359
16796
  }
16360
16797
 
16361
16798
  function batchApply(targets, operation) {
@@ -16423,15 +16860,18 @@ function toggleAttribute(element, name, value) {
16423
16860
  }
16424
16861
  }
16425
16862
  }
16863
+ function toCssPropertyName(property) {
16864
+ return property.startsWith('--') ? property : camelToKebab(property);
16865
+ }
16426
16866
  function batchSetStyles(targets, styles) {
16427
16867
  const entries = Object.entries(styles);
16428
16868
  return batchApplyItems(targets, entries, (element, [property, value]) => {
16429
- element.style.setProperty(property, value);
16869
+ element.style.setProperty(toCssPropertyName(property), value);
16430
16870
  });
16431
16871
  }
16432
16872
  function batchRemoveStyles(targets, properties) {
16433
16873
  return batchApplyItems(targets, properties, (element, property) => {
16434
- element.style.removeProperty(property);
16874
+ element.style.removeProperty(toCssPropertyName(property));
16435
16875
  });
16436
16876
  }
16437
16877
 
@@ -17097,75 +17537,6 @@ let RemoveCommand = (() => {
17097
17537
  })();
17098
17538
  const createRemoveCommand = createFactory(RemoveCommand);
17099
17539
 
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
17540
  function detectSmartElementType(elements) {
17170
17541
  if (elements.length === 0)
17171
17542
  return null;
@@ -18035,7 +18406,11 @@ let PutCommand = (() => {
18035
18406
  }
18036
18407
  if (isInsertableNode(v))
18037
18408
  return v;
18038
- return v == null ? '' : String(v);
18409
+ if (v == null)
18410
+ return 'null';
18411
+ if (Array.isArray(v))
18412
+ return arrayFragment(v);
18413
+ return String(v);
18039
18414
  }
18040
18415
  looksLikeCss(s) {
18041
18416
  if (!s)
@@ -18120,6 +18495,19 @@ let PutCommand = (() => {
18120
18495
  })();
18121
18496
  return PutCommand = _classThis;
18122
18497
  })();
18498
+ function arrayFragment(items) {
18499
+ const fragment = document.createDocumentFragment();
18500
+ for (const item of items) {
18501
+ if (isInsertableNode(item)) {
18502
+ fragment.append(item);
18503
+ continue;
18504
+ }
18505
+ const template = document.createElement('template');
18506
+ template.innerHTML = item === null ? '' : String(item);
18507
+ fragment.append(template.content);
18508
+ }
18509
+ return fragment;
18510
+ }
18123
18511
  const createPutCommand = createFactory(PutCommand);
18124
18512
 
18125
18513
  const HTML_ESCAPE = {
@@ -19337,10 +19725,29 @@ const STRATEGY_KEYWORDS = {
19337
19725
  innermorph: 'morph',
19338
19726
  outermorph: 'morphOuter',
19339
19727
  };
19728
+ function loneRootWithTag(content, tagName) {
19729
+ let root;
19730
+ if (typeof content === 'string') {
19731
+ const template = document.createElement('template');
19732
+ template.innerHTML = content;
19733
+ root = template.content.firstElementChild;
19734
+ if (!root || root.nextElementSibling)
19735
+ return null;
19736
+ }
19737
+ else {
19738
+ root = content.cloneNode(true);
19739
+ }
19740
+ return root.tagName === tagName && isHTMLElement$2(root) ? root : null;
19741
+ }
19340
19742
  function executeSwap(target, content, strategy, morphOptions) {
19341
19743
  const contentStr = content !== null && !isHTMLElement$2(content) ? content : '';
19342
19744
  const contentEl = isHTMLElement$2(content) ? content : null;
19343
19745
  switch (strategy) {
19746
+ case 'morphMerge': {
19747
+ const root = content !== null ? loneRootWithTag(content, target.tagName) : null;
19748
+ executeSwap(target, root ?? content, root ? 'morphOuter' : 'morph', morphOptions);
19749
+ break;
19750
+ }
19344
19751
  case 'morph':
19345
19752
  if (content !== null) {
19346
19753
  try {
@@ -19630,7 +20037,7 @@ let MorphCommand = (() => {
19630
20037
  if (args.length === 0 && modifiers.with === undefined) {
19631
20038
  throw new Error('[HyperFixi] morph: command requires arguments');
19632
20039
  }
19633
- const strategy = literalText(modifiers.strategy)?.toLowerCase() === 'over' ? 'morphOuter' : 'morph';
20040
+ const strategy = literalText(modifiers.strategy)?.toLowerCase() === 'over' ? 'morphOuter' : 'morphMerge';
19634
20041
  const useViewTransition = modifiers.viewTransition !== undefined;
19635
20042
  const targetNode = modifiers.on !== undefined ? modifiers.on : args[0];
19636
20043
  const contentNode = modifiers.with !== undefined
@@ -20936,6 +21343,19 @@ async function resolveWriteTarget(node, evaluator, context, options) {
20936
21343
  }
20937
21344
  return { kind: 'property', target: property };
20938
21345
  }
21346
+ if (options.ofAndObjectProperty) {
21347
+ const access = ofOrPossessiveAccess(n);
21348
+ if (access) {
21349
+ const owner = await evaluator.evaluate(access.owner, context);
21350
+ const element = Array.isArray(owner) ? owner[0] : owner;
21351
+ if (access.isOf && isHTMLElement$2(element)) {
21352
+ return { kind: 'property', target: { element, property: access.name } };
21353
+ }
21354
+ if (isObjectOwner(owner)) {
21355
+ return { kind: 'member', container: owner, property: access.name };
21356
+ }
21357
+ }
21358
+ }
20939
21359
  if (options.bareReference &&
20940
21360
  (nodeType === 'identifier' || nodeType === 'variable' || nodeType === 'symbol') &&
20941
21361
  typeof n.name === 'string') {
@@ -20945,6 +21365,19 @@ async function resolveWriteTarget(node, evaluator, context, options) {
20945
21365
  }
20946
21366
  return null;
20947
21367
  }
21368
+ function ofOrPossessiveAccess(n) {
21369
+ const [owner, property, isOf] = n.type === 'possessiveExpression'
21370
+ ? [n.object, n.property, false]
21371
+ : n.type === 'propertyOfExpression'
21372
+ ? [n.target, n.property, false]
21373
+ : n.type === 'binaryExpression' && n.operator === 'of'
21374
+ ? [n.right, n.left, true]
21375
+ : [undefined, undefined, false];
21376
+ const name = property?.name;
21377
+ if (!owner || typeof name !== 'string' || !/^[A-Za-z_$][\w$]*$/.test(name))
21378
+ return null;
21379
+ return { owner, name, isOf };
21380
+ }
20948
21381
 
20949
21382
  function toSetInput(target, value) {
20950
21383
  switch (target.kind) {
@@ -20966,10 +21399,18 @@ function toSetInput(target, value) {
20966
21399
  property: target.target.property,
20967
21400
  value,
20968
21401
  };
21402
+ case 'member':
21403
+ return {
21404
+ type: 'member-assignment',
21405
+ container: target.container,
21406
+ property: target.property,
21407
+ value,
21408
+ };
20969
21409
  default:
20970
21410
  throw new Error(`set: unrequested write-target rung '${target.kind}'`);
20971
21411
  }
20972
21412
  }
21413
+ const SET_RUNGS = { nodeWriters: true, styleSplit: true, ofAndObjectProperty: true };
20973
21414
  let SetCommand = (() => {
20974
21415
  let _classDecorators = [command({ name: 'set' })];
20975
21416
  let _classDescriptor;
@@ -20988,8 +21429,7 @@ let SetCommand = (() => {
20988
21429
  const argScope = firstArg?.scope;
20989
21430
  const writeTarget = await resolveWriteTarget(firstArg, evaluator, context, {
20990
21431
  scopeElements: () => this.resolveTargets(raw.modifiers.on, evaluator, context),
20991
- nodeWriters: true,
20992
- styleSplit: true,
21432
+ ...SET_RUNGS,
20993
21433
  });
20994
21434
  if (writeTarget) {
20995
21435
  return toSetInput(writeTarget, await this.extractValue(raw, evaluator, context));
@@ -21315,12 +21755,30 @@ function getNodeType(node) {
21315
21755
  return 'unknown';
21316
21756
  return node.type || 'unknown';
21317
21757
  }
21758
+ function propertyTarget(node) {
21759
+ const n = node;
21760
+ const owner = n.type === 'propertyOfExpression'
21761
+ ? n.target
21762
+ : n.type === 'possessiveExpression' ||
21763
+ n.type === 'memberExpression' ||
21764
+ n.type === 'propertyAccess'
21765
+ ? n.object
21766
+ : undefined;
21767
+ if (!owner || n.computed)
21768
+ return null;
21769
+ const p = n.property;
21770
+ const name = typeof p === 'string' ? p : typeof p?.name === 'string' ? p.name : undefined;
21771
+ if (!name || !/^[A-Za-z_$][\w$-]*$/.test(name))
21772
+ return null;
21773
+ return { owner, name };
21774
+ }
21318
21775
  async function parseNumericTargetInput(raw, evaluator, context, commandName) {
21319
21776
  if (!raw.args || raw.args.length === 0) {
21320
21777
  throw new Error(`${commandName} command requires a target`);
21321
21778
  }
21322
21779
  const targetArg = raw.args[0];
21323
21780
  let target;
21781
+ let property;
21324
21782
  let extractedScope;
21325
21783
  let customWrite;
21326
21784
  const nodeType = getNodeType(targetArg);
@@ -21335,7 +21793,25 @@ async function parseNumericTargetInput(raw, evaluator, context, commandName) {
21335
21793
  }
21336
21794
  else {
21337
21795
  const writer = getRegisteredNodeWriter(nodeType);
21338
- if (writer) {
21796
+ const attribute = writer
21797
+ ? null
21798
+ : await resolveAttributeWriteTarget(targetArg, evaluator, context, async () => context.me ? [context.me] : []);
21799
+ const prop = writer || attribute ? null : propertyTarget(targetArg);
21800
+ const owner = prop ? await evaluator.evaluate(prop.owner, context) : undefined;
21801
+ const ownerElement = Array.isArray(owner) ? owner[0] : owner;
21802
+ if (attribute?.elements[0]) {
21803
+ target = attribute.elements[0];
21804
+ property = `@${attribute.name}`;
21805
+ }
21806
+ else if (prop && isHTMLElement$2(ownerElement)) {
21807
+ target = ownerElement;
21808
+ property = prop.name;
21809
+ }
21810
+ else if (prop && isObjectOwner(owner)) {
21811
+ target = owner;
21812
+ property = prop.name;
21813
+ }
21814
+ else if (writer) {
21339
21815
  const currentRaw = await evaluator.evaluate(targetArg, context);
21340
21816
  const currentValue = typeof currentRaw === 'number'
21341
21817
  ? currentRaw
@@ -21373,24 +21849,22 @@ async function parseNumericTargetInput(raw, evaluator, context, commandName) {
21373
21849
  if (typeof evaluated === 'number') {
21374
21850
  amount = evaluated;
21375
21851
  }
21852
+ else if (evaluated == null) {
21853
+ amount = NaN;
21854
+ }
21376
21855
  }
21377
21856
  }
21378
21857
  const byModifier = raw.modifiers?.by;
21379
21858
  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
- }
21859
+ const value = getNodeType(byModifier) === 'literal'
21860
+ ? byModifier.value
21861
+ : await evaluator.evaluate(byModifier, context);
21862
+ amount = counterAmount(value);
21390
21863
  }
21391
21864
  return {
21392
21865
  target,
21393
21866
  amount,
21867
+ ...(property && { property }),
21394
21868
  ...(scope && { scope }),
21395
21869
  ...(customWrite && { customWrite }),
21396
21870
  };
@@ -21413,7 +21887,7 @@ let NumericModifyCommand = (() => {
21413
21887
  async execute(input, context) {
21414
21888
  const { target, property, scope, amount = 1, operation = 'increment', customWrite } = input;
21415
21889
  if (customWrite) {
21416
- const delta = isFinite(amount) ? amount : 1;
21890
+ const delta = amount;
21417
21891
  const newValue = operation === 'increment'
21418
21892
  ? customWrite.currentValue + delta
21419
21893
  : customWrite.currentValue - delta;
@@ -21427,8 +21901,7 @@ let NumericModifyCommand = (() => {
21427
21901
  newValue = NaN;
21428
21902
  }
21429
21903
  else {
21430
- const delta = isFinite(amount) ? amount : 1;
21431
- newValue = operation === 'increment' ? currentValue + delta : currentValue - delta;
21904
+ newValue = operation === 'increment' ? currentValue + amount : currentValue - amount;
21432
21905
  }
21433
21906
  setTargetValue(target, property, scope, newValue, context);
21434
21907
  Object.assign(context, { it: newValue });
@@ -22536,7 +23009,10 @@ function createUntilLoopConfig(conditionExpr, evaluateCondition, context, indexV
22536
23009
  };
22537
23010
  const config = {
22538
23011
  type: 'until',
22539
- shouldContinue: () => !evaluateCondition(conditionExpr, context),
23012
+ shouldContinue: () => {
23013
+ const met = evaluateCondition(conditionExpr, context);
23014
+ return met instanceof Promise ? met.then(value => !value) : !met;
23015
+ },
22540
23016
  };
22541
23017
  return { config, iterCtx };
22542
23018
  }
@@ -22572,7 +23048,7 @@ function createForeverLoopConfig(indexVariable, maxIterations = 10000) {
22572
23048
  return { config, iterCtx };
22573
23049
  }
22574
23050
 
22575
- async function slotText(node, evaluator, context) {
23051
+ async function slotText$1(node, evaluator, context) {
22576
23052
  if (!node)
22577
23053
  return undefined;
22578
23054
  const n = node;
@@ -22585,6 +23061,18 @@ async function slotText(node, evaluator, context) {
22585
23061
  const v = await evaluator.evaluate(node, context);
22586
23062
  return typeof v === 'string' ? v : undefined;
22587
23063
  }
23064
+ function isEventTarget(value) {
23065
+ return typeof value?.addEventListener === 'function';
23066
+ }
23067
+ async function evaluateLoopCondition(condition, context) {
23068
+ if (typeof condition === 'function')
23069
+ return Boolean(await condition());
23070
+ return evaluateCondition(condition, context);
23071
+ }
23072
+ function loopCount(value) {
23073
+ const count = Number(value);
23074
+ return Number.isNaN(count) ? 0 : count;
23075
+ }
22588
23076
  let RepeatCommand = (() => {
22589
23077
  let _classDecorators = [command({ name: 'repeat' })];
22590
23078
  let _classDescriptor;
@@ -22622,7 +23110,7 @@ let RepeatCommand = (() => {
22622
23110
  }
22623
23111
  }
22624
23112
  const m = raw.modifiers ?? {};
22625
- const text = (node) => slotText(node, evaluator, context);
23113
+ const text = (node) => slotText$1(node, evaluator, context);
22626
23114
  const loopType = (await text(m.loopType)) ?? null;
22627
23115
  const bottomTested = m.bottomTested
22628
23116
  ? (await evaluator.evaluate(m.bottomTested, context))
@@ -22642,17 +23130,24 @@ let RepeatCommand = (() => {
22642
23130
  };
22643
23131
  }
22644
23132
  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))
23133
+ if (!m.times)
22648
23134
  throw new Error('times loops require a count number');
23135
+ const count = loopCount(await evaluator.evaluate(m.times, context));
22649
23136
  return { type: 'times', count, indexVariable, commands, elseCommands };
22650
23137
  }
22651
23138
  if (loopType === 'while' || m.while) {
22652
23139
  const condition = m.while;
22653
23140
  if (!condition)
22654
23141
  throw new Error('while loops require a condition');
22655
- return { type: 'while', condition, indexVariable, commands, elseCommands, bottomTested };
23142
+ const check = () => evaluator.evaluate(condition, context);
23143
+ return {
23144
+ type: 'while',
23145
+ condition: check,
23146
+ indexVariable,
23147
+ commands,
23148
+ elseCommands,
23149
+ bottomTested,
23150
+ };
22656
23151
  }
22657
23152
  if (loopType === 'until-event' || m.event) {
22658
23153
  const eventName = await text(m.event);
@@ -22670,7 +23165,7 @@ let RepeatCommand = (() => {
22670
23165
  }
22671
23166
  else {
22672
23167
  const target = await evaluator.evaluate(m.from, context);
22673
- if (target instanceof EventTarget)
23168
+ if (isEventTarget(target))
22674
23169
  eventTarget = target;
22675
23170
  else if (target === 'document')
22676
23171
  eventTarget = document;
@@ -22691,7 +23186,15 @@ let RepeatCommand = (() => {
22691
23186
  const condition = m.until;
22692
23187
  if (!condition)
22693
23188
  throw new Error('until loops require a condition');
22694
- return { type: 'until', condition, indexVariable, commands, elseCommands, bottomTested };
23189
+ const check = () => evaluator.evaluate(condition, context);
23190
+ return {
23191
+ type: 'until',
23192
+ condition: check,
23193
+ indexVariable,
23194
+ commands,
23195
+ elseCommands,
23196
+ bottomTested,
23197
+ };
22695
23198
  }
22696
23199
  if (loopType === 'forever') {
22697
23200
  return { type: 'forever', indexVariable, commands, elseCommands };
@@ -22709,10 +23212,10 @@ let RepeatCommand = (() => {
22709
23212
  ({ config, iterCtx } = createTimesLoopConfig(count, indexVariable));
22710
23213
  break;
22711
23214
  case 'while':
22712
- ({ config, iterCtx } = createWhileLoopConfig(condition, evaluateCondition, context, indexVariable));
23215
+ ({ config, iterCtx } = createWhileLoopConfig(condition, evaluateLoopCondition, context, indexVariable));
22713
23216
  break;
22714
23217
  case 'until':
22715
- ({ config, iterCtx } = createUntilLoopConfig(condition, evaluateCondition, context, indexVariable));
23218
+ ({ config, iterCtx } = createUntilLoopConfig(condition, evaluateLoopCondition, context, indexVariable));
22716
23219
  break;
22717
23220
  case 'until-event':
22718
23221
  ({ config, iterCtx } = createUntilEventLoopConfig(eventName, eventTarget, indexVariable));
@@ -23743,6 +24246,29 @@ function namedPropertyOf(node) {
23743
24246
  }
23744
24247
  return null;
23745
24248
  }
24249
+ const RESTORE_KEYWORDS = new Set(['initial', 'inherit', 'unset', 'revert']);
24250
+ function elementsOf(value) {
24251
+ if (Array.isArray(value) || isNodeList(value)) {
24252
+ return Array.from(value).filter(isHTMLElement$2);
24253
+ }
24254
+ return isHTMLElement$2(value) ? [value] : [];
24255
+ }
24256
+ async function valueOf(node, evaluator, context) {
24257
+ const value = await evaluator.evaluate(node, context);
24258
+ const name = node.name;
24259
+ if (value === undefined && node.type === 'identifier' && typeof name === 'string')
24260
+ return name;
24261
+ return value;
24262
+ }
24263
+ function longestTransitionMs(css) {
24264
+ let longest;
24265
+ for (const item of css.split(',')) {
24266
+ const times = [...item.matchAll(/(\d*\.?\d+)(ms|s)\b/g)].map(m => parseFloat(m[1]) * (m[2] === 's' ? 1000 : 1));
24267
+ if (times.length > 0)
24268
+ longest = Math.max(longest ?? 0, times[0] + (times[1] ?? 0));
24269
+ }
24270
+ return longest;
24271
+ }
23746
24272
  let TransitionCommand = (() => {
23747
24273
  let _classDecorators = [command({ name: 'transition' })];
23748
24274
  let _classDescriptor;
@@ -23758,11 +24284,10 @@ let TransitionCommand = (() => {
23758
24284
  let property;
23759
24285
  let target;
23760
24286
  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) ||
24287
+ const owners = elementsOf(firstArg);
24288
+ if (owners.length > 0 ||
23764
24289
  (typeof firstArg === 'string' && /^[#.]|^(?:me|it|you)$/.test(firstArg))) {
23765
- target = firstAsElement;
24290
+ target = owners.length === 1 ? owners[0] : owners.length > 1 ? owners : firstArg;
23766
24291
  property = String(await evaluator.evaluate(raw.args[1], context));
23767
24292
  }
23768
24293
  else if (raw.args.length >= 2) {
@@ -23774,9 +24299,9 @@ let TransitionCommand = (() => {
23774
24299
  throw new Error('transition requires a CSS property');
23775
24300
  property = named.property;
23776
24301
  if (named.objectNode !== undefined) {
23777
- const owner = asElement(await evaluator.evaluate(named.objectNode, context));
23778
- if (isHTMLElement$2(owner))
23779
- target = owner;
24302
+ const owner = elementsOf(await evaluator.evaluate(named.objectNode, context));
24303
+ if (owner.length > 0)
24304
+ target = owner.length === 1 ? owner[0] : owner;
23780
24305
  }
23781
24306
  }
23782
24307
  else {
@@ -23787,60 +24312,131 @@ let TransitionCommand = (() => {
23787
24312
  }
23788
24313
  if (!raw.modifiers?.to)
23789
24314
  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 };
24315
+ const result = {
24316
+ property,
24317
+ value: await valueOf(raw.modifiers.to, evaluator, context),
24318
+ };
23795
24319
  if (target !== undefined)
23796
24320
  result.target = target;
24321
+ if (raw.modifiers?.from)
24322
+ result.from = await valueOf(raw.modifiers.from, evaluator, context);
24323
+ if (raw.modifiers?.pairs) {
24324
+ result.pairs = await this.laterPairs(raw.modifiers.pairs, evaluator, context);
24325
+ }
23797
24326
  if (raw.modifiers?.over)
23798
24327
  result.duration = await evaluator.evaluate(raw.modifiers.over, context);
23799
24328
  if (raw.modifiers?.with)
23800
24329
  result.timingFunction = String(await evaluator.evaluate(raw.modifiers.with, context));
24330
+ if (raw.modifiers?.using)
24331
+ result.using = String(await evaluator.evaluate(raw.modifiers.using, context));
23801
24332
  return result;
23802
24333
  }
24334
+ async laterPairs(node, evaluator, context) {
24335
+ const pairs = [];
24336
+ for (const pairNode of node.elements ?? []) {
24337
+ const fields = new Map();
24338
+ const properties = pairNode
24339
+ .properties ?? [];
24340
+ for (const { key, value } of properties) {
24341
+ if (typeof key.name === 'string')
24342
+ fields.set(key.name, value);
24343
+ }
24344
+ const propertyNode = fields.get('property');
24345
+ const toNode = fields.get('to');
24346
+ if (!propertyNode || !toNode)
24347
+ throw new Error('transition requires property and value');
24348
+ const pair = {
24349
+ property: String(await evaluator.evaluate(propertyNode, context)),
24350
+ value: await valueOf(toNode, evaluator, context),
24351
+ };
24352
+ const ownerNode = fields.get('owner');
24353
+ if (ownerNode) {
24354
+ const owners = elementsOf(await evaluator.evaluate(ownerNode, context));
24355
+ if (owners.length === 0)
24356
+ throw new Error('transition: target element not found');
24357
+ pair.target = owners.length === 1 ? owners[0] : owners;
24358
+ }
24359
+ const fromNode = fields.get('from');
24360
+ if (fromNode)
24361
+ pair.from = await valueOf(fromNode, evaluator, context);
24362
+ pairs.push(pair);
24363
+ }
24364
+ return pairs;
24365
+ }
23803
24366
  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);
24367
+ const duration = parseDuration(input.duration, (input.using !== undefined && longestTransitionMs(input.using)) || 300);
24368
+ const timing = input.timingFunction || 'ease';
24369
+ const jobs = [];
24370
+ for (const pair of [input, ...(input.pairs ?? [])]) {
24371
+ let property = pair.property;
24372
+ if (property.startsWith('*'))
24373
+ property = property.substring(1);
24374
+ property = camelToKebab(property);
24375
+ const elements = Array.isArray(pair.target)
24376
+ ? pair.target.filter(isHTMLElement$2)
24377
+ : [resolveElement(pair.target, context, 'transition')];
24378
+ for (const element of elements) {
24379
+ jobs.push({
24380
+ element,
24381
+ property,
24382
+ pair,
24383
+ fromValue: getComputedStyle(element).getPropertyValue(property),
24384
+ toValue: '',
24385
+ removeInlineAfter: false,
24386
+ });
24387
+ }
24388
+ }
24389
+ if (jobs.length === 0)
24390
+ throw new Error('transition: target element not found');
24391
+ for (const job of jobs) {
24392
+ if (job.pair.from !== undefined) {
24393
+ job.element.style.setProperty(job.property, String(job.pair.from));
24394
+ }
24395
+ }
24396
+ for (const job of jobs)
24397
+ if (job.pair.from !== undefined)
24398
+ void job.element.offsetWidth;
24399
+ const originals = new Map();
24400
+ for (const job of jobs) {
24401
+ if (!originals.has(job.element))
24402
+ originals.set(job.element, job.element.style.transition);
24403
+ }
24404
+ for (const [element, original] of originals) {
24405
+ const own = jobs
24406
+ .filter(job => job.element === element)
24407
+ .map(job => `${job.property} ${duration}ms ${timing}`);
24408
+ element.style.transition = input.using ?? [original, ...own].filter(Boolean).join(', ');
24409
+ }
24410
+ for (const job of jobs) {
24411
+ const { element, property } = job;
24412
+ let toValue = String(job.pair.value);
24413
+ if (RESTORE_KEYWORDS.has(toValue)) {
24414
+ const currentInline = element.style.getPropertyValue(property);
24415
+ element.style.removeProperty(property);
24416
+ toValue = getComputedStyle(element).getPropertyValue(property);
24417
+ if (currentInline) {
24418
+ element.style.setProperty(property, currentInline);
24419
+ }
24420
+ job.removeInlineAfter = true;
24421
+ }
24422
+ job.toValue = toValue;
24423
+ element.style.setProperty(property, toValue);
24424
+ }
24425
+ const results = await Promise.all(jobs.map(job => waitForTransitionEnd(job.element, job.property, duration)));
24426
+ for (const [element, original] of originals)
24427
+ element.style.transition = original;
24428
+ for (const job of jobs) {
24429
+ if (job.removeInlineAfter)
24430
+ job.element.style.removeProperty(job.property);
23836
24431
  }
24432
+ const first = jobs[0];
23837
24433
  return {
23838
- element: targetElement,
23839
- property,
23840
- fromValue,
23841
- toValue,
24434
+ element: first.element,
24435
+ property: first.property,
24436
+ fromValue: first.fromValue,
24437
+ toValue: first.toValue,
23842
24438
  duration,
23843
- completed: result.completed,
24439
+ completed: results.every(result => result.completed),
23844
24440
  };
23845
24441
  }
23846
24442
  };
@@ -23853,13 +24449,16 @@ let TransitionCommand = (() => {
23853
24449
  })();
23854
24450
  _classThis.metadata = commandMeta({
23855
24451
  description: 'Animate CSS properties using CSS transitions',
23856
- syntax: 'transition [<target>] <property> to <value> [over <duration>] [with <timing>]',
24452
+ syntax: 'transition <property> [from <value>] to <value> [<property> \u2026 to <value>]\u2026 [over <duration>] [using <css> | with <timing>]',
23857
24453
  examples: [
23858
24454
  'transition opacity to 0.5',
23859
24455
  'transition my *opacity to 0 over 200ms',
23860
24456
  "transition #box's *opacity to 0 over 200ms",
23861
24457
  'transition left to 100px over 500ms',
23862
24458
  'transition background-color to red over 1s with ease-in-out',
24459
+ 'transition *width to 100px *height to 50px over 300ms',
24460
+ 'transition *opacity of #panel from 0 to 1 over 200ms',
24461
+ 'transition my *opacity to 0 using "opacity 1s ease-in"',
23863
24462
  ],
23864
24463
  sideEffects: ['style-change', 'timing'],
23865
24464
  category: 'animation',
@@ -25109,17 +25708,7 @@ let BeepCommand = (() => {
25109
25708
  this.debugContext(context);
25110
25709
  return { expressionCount: 0, debugged: true, outputs: [] };
25111
25710
  }
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();
25711
+ const outputs = beepValues(context.me, expressions);
25123
25712
  return { expressionCount: expressions.length, debugged: true, outputs };
25124
25713
  }
25125
25714
  debugContext(context) {
@@ -25132,67 +25721,6 @@ let BeepCommand = (() => {
25132
25721
  console.log('variables:', context.variables);
25133
25722
  console.groupEnd();
25134
25723
  }
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
25724
  };
25197
25725
  __setFunctionName(_classThis, "BeepCommand");
25198
25726
  (() => {
@@ -25740,16 +26268,12 @@ class RenderCommand {
25740
26268
  }
25741
26269
  RenderCommand.metadata = commandMeta({
25742
26270
  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
- ],
26271
+ syntax: ['render <template>', 'render <template> with <name>: <value>, ...'],
25748
26272
  examples: [
25749
26273
  'render myTemplate',
25750
- 'render myTemplate with (name: "Alice")',
25751
- 'render "<template>Hello ${name}!</template>" with (name: "World")',
25752
- 'render template with (items: data)',
26274
+ 'render myTemplate with name: "Alice"',
26275
+ 'render "<template>Hello ${name}!</template>" with name: "World"',
26276
+ 'render template with items: data',
25753
26277
  ],
25754
26278
  category: 'templates',
25755
26279
  compatibility: 'standard',
@@ -25937,7 +26461,7 @@ function updateDebugStats(event) {
25937
26461
  stats.confidenceHistory.reduce((a, b) => a + b, 0) / stats.confidenceHistory.length;
25938
26462
  }
25939
26463
 
25940
- const VERSION = '3.1.1';
26464
+ const VERSION = '3.3.0';
25941
26465
 
25942
26466
  function createHistorySwap(config) {
25943
26467
  const { target, strategy = 'morph', useViewTransition = false, fetchOptions = {}, transformUrl, onBeforeFetch, onAfterSwap, onError, } = config;
@@ -27483,6 +28007,14 @@ function convertNode(node, infer) {
27483
28007
  ...pos(node),
27484
28008
  };
27485
28009
  case 'identifier':
28010
+ if (node.scope === 'element' || node.scope === 'local' || node.scope === 'global') {
28011
+ return {
28012
+ type: 'variable',
28013
+ name: (node.name ?? ''),
28014
+ scope: node.scope,
28015
+ ...pos(node),
28016
+ };
28017
+ }
27486
28018
  return {
27487
28019
  type: 'identifier',
27488
28020
  value: (node.name ?? node.value ?? ''),
@@ -27505,6 +28037,15 @@ function convertNode(node, infer) {
27505
28037
  operand: convertNode(node.argument ?? node.operand, infer),
27506
28038
  ...pos(node),
27507
28039
  };
28040
+ case 'typeCheckExpression':
28041
+ return {
28042
+ type: 'typeCheck',
28043
+ value: convertNode(node.value, infer),
28044
+ typeName: String(node.typeName),
28045
+ nullOk: node.nullOk !== false,
28046
+ negated: node.negated === true,
28047
+ ...pos(node),
28048
+ };
27508
28049
  case 'timeExpression':
27509
28050
  return { type: 'literal', value: node.value, ...pos(node) };
27510
28051
  case 'templateLiteral':
@@ -27516,6 +28057,28 @@ function convertNode(node, infer) {
27516
28057
  scope: (node.scope ?? 'local'),
27517
28058
  ...pos(node),
27518
28059
  };
28060
+ case 'arrayLiteral':
28061
+ return {
28062
+ type: 'array',
28063
+ elements: (node.elements ?? []).map(element => convertNode(element, infer)),
28064
+ ...pos(node),
28065
+ };
28066
+ case 'attributeAccess':
28067
+ return {
28068
+ type: 'possessive',
28069
+ object: { type: 'identifier', value: 'me' },
28070
+ property: `@${node.attributeName}`,
28071
+ ...pos(node),
28072
+ };
28073
+ case 'propertyOfExpression': {
28074
+ const property = node.property;
28075
+ return {
28076
+ type: 'possessive',
28077
+ object: convertNode(node.target, infer),
28078
+ property: (property?.name ?? property?.value ?? ''),
28079
+ ...pos(node),
28080
+ };
28081
+ }
27519
28082
  case 'htmlSelector':
27520
28083
  return {
27521
28084
  type: 'selector',
@@ -27567,6 +28130,21 @@ function convertCommand$1(node, infer) {
27567
28130
  if (name === 'repeat') {
27568
28131
  return convertRepeatCommand(node, infer);
27569
28132
  }
28133
+ const original = node.originalCommand;
28134
+ const sum = node.modifiers?.to;
28135
+ const counted = node.args?.[0];
28136
+ if (name === 'set' &&
28137
+ (original === 'increment' || original === 'decrement') &&
28138
+ sum?.type === 'binaryExpression' &&
28139
+ counted) {
28140
+ return convertCommand$1({
28141
+ ...node,
28142
+ name: original,
28143
+ args: [counted],
28144
+ modifiers: { by: sum.right },
28145
+ originalCommand: undefined,
28146
+ }, infer);
28147
+ }
27570
28148
  const args = (node.args ?? []).map(arg => convertNode(arg, infer));
27571
28149
  const target = node.target ? convertNode(node.target, infer) : undefined;
27572
28150
  const modifiers = node.modifiers
@@ -27615,6 +28193,64 @@ function convertIfCommand(node, infer) {
27615
28193
  };
27616
28194
  }
27617
28195
  function convertRepeatCommand(node, infer) {
28196
+ const slots = (node.modifiers ?? {});
28197
+ const form = slotText(slots.loopType);
28198
+ if (form === undefined)
28199
+ return convertPositionalRepeat(node, infer);
28200
+ const [bodyBlock, elseBlock] = (node.args ?? []);
28201
+ const body = extractBlockCommands(bodyBlock, infer);
28202
+ const indexName = slotText(slots.index);
28203
+ const shared = {
28204
+ ...(indexName ? { indexName } : {}),
28205
+ ...(elseBlock ? { elseBody: extractBlockCommands(elseBlock, infer) } : {}),
28206
+ ...pos(node),
28207
+ };
28208
+ const operand = (slot) => (slot ? convertNode(slot, infer) : undefined);
28209
+ switch (form) {
28210
+ case 'for':
28211
+ return {
28212
+ type: 'foreach',
28213
+ itemName: slotText(slots.for) ?? 'it',
28214
+ collection: operand(slots.in) ?? { type: 'literal', value: null },
28215
+ body,
28216
+ ...shared,
28217
+ };
28218
+ case 'times':
28219
+ return { type: 'repeat', count: operand(slots.times), body, ...shared };
28220
+ case 'while':
28221
+ case 'until': {
28222
+ const test = operand(slots[form]) ?? { type: 'literal', value: form === 'until' };
28223
+ return {
28224
+ type: 'while',
28225
+ condition: form === 'until' ? { type: 'unary', operator: 'not', operand: test } : test,
28226
+ ...(slots.bottomTested?.value === true ? { bottomTested: true } : {}),
28227
+ body,
28228
+ ...shared,
28229
+ };
28230
+ }
28231
+ case 'until-event': {
28232
+ const event = slotText(slots.event);
28233
+ if (!event) {
28234
+ return { type: 'error', message: "'repeat until event' has no event name", ...pos(node) };
28235
+ }
28236
+ const target = operand(slots.from);
28237
+ return {
28238
+ type: 'repeat',
28239
+ untilEvent: event,
28240
+ ...(target ? { untilEventTarget: target } : {}),
28241
+ body,
28242
+ ...shared,
28243
+ };
28244
+ }
28245
+ default:
28246
+ return { type: 'repeat', body, ...shared };
28247
+ }
28248
+ }
28249
+ function slotText(slot) {
28250
+ const text = slot?.value ?? slot?.name;
28251
+ return typeof text === 'string' ? text : undefined;
28252
+ }
28253
+ function convertPositionalRepeat(node, infer) {
27618
28254
  const args = (node.args ?? []);
27619
28255
  if (args.length === 0) {
27620
28256
  return { type: 'repeat', body: [], ...pos(node) };
@@ -27931,6 +28567,15 @@ function toCoreAST(node) {
27931
28567
  prefix: true,
27932
28568
  ...nodePos(node),
27933
28569
  };
28570
+ case 'typeCheck':
28571
+ return {
28572
+ type: 'typeCheckExpression',
28573
+ value: toCoreAST(node.value),
28574
+ typeName: node.typeName,
28575
+ nullOk: node.nullOk,
28576
+ negated: node.negated,
28577
+ ...nodePos(node),
28578
+ };
27934
28579
  case 'member':
27935
28580
  return {
27936
28581
  type: 'memberExpression',
@@ -27941,6 +28586,12 @@ function toCoreAST(node) {
27941
28586
  computed: node.computed ?? false,
27942
28587
  ...nodePos(node),
27943
28588
  };
28589
+ case 'array':
28590
+ return {
28591
+ type: 'arrayLiteral',
28592
+ elements: node.elements.map(element => toCoreAST(element)),
28593
+ ...nodePos(node),
28594
+ };
27944
28595
  case 'possessive':
27945
28596
  return {
27946
28597
  type: 'possessiveExpression',
@@ -28003,72 +28654,85 @@ function convertCommand(node) {
28003
28654
  }
28004
28655
  return result;
28005
28656
  }
28657
+ function block(nodes) {
28658
+ return { type: 'block', commands: nodes.map(n => toCoreAST(n)), ...POS };
28659
+ }
28006
28660
  function convertIf(node) {
28007
- const result = {
28661
+ const [elseIf, ...laterElseIfs] = node.elseIfBranches ?? [];
28662
+ const elseBranch = elseIf
28663
+ ? [
28664
+ {
28665
+ type: 'if',
28666
+ condition: elseIf.condition,
28667
+ thenBranch: elseIf.body,
28668
+ elseIfBranches: laterElseIfs,
28669
+ ...(node.elseBranch ? { elseBranch: node.elseBranch } : {}),
28670
+ },
28671
+ ]
28672
+ : node.elseBranch;
28673
+ return {
28008
28674
  type: 'command',
28009
28675
  name: 'if',
28010
28676
  isBlocking: true,
28011
- condition: toCoreAST(node.condition),
28012
- thenBranch: node.thenBranch.map(n => toCoreAST(n)),
28013
- args: [],
28677
+ args: [
28678
+ toCoreAST(node.condition),
28679
+ block(node.thenBranch),
28680
+ ...(elseBranch ? [block(elseBranch)] : []),
28681
+ ],
28014
28682
  ...nodePos(node),
28015
28683
  };
28016
- if (node.elseBranch) {
28017
- result.elseBranch = node.elseBranch.map(n => toCoreAST(n));
28018
- }
28019
- return result;
28020
28684
  }
28021
- function convertRepeat(node) {
28022
- const result = {
28685
+ function loopCommand(node, form, operands) {
28686
+ const modifiers = { loopType: slotString(form) };
28687
+ for (const [slot, value] of Object.entries(operands)) {
28688
+ if (value)
28689
+ modifiers[slot] = value;
28690
+ }
28691
+ if (node.indexName)
28692
+ modifiers.index = slotString(node.indexName);
28693
+ return {
28023
28694
  type: 'command',
28024
28695
  name: 'repeat',
28025
28696
  isBlocking: true,
28026
- body: node.body.map(n => toCoreAST(n)),
28027
- args: [],
28697
+ args: [block(node.body), ...(node.elseBody ? [block(node.elseBody)] : [])],
28698
+ modifiers,
28028
28699
  ...nodePos(node),
28029
28700
  };
28701
+ }
28702
+ function slotString(value) {
28703
+ return { type: 'string', value, ...POS };
28704
+ }
28705
+ function convertRepeat(node) {
28030
28706
  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';
28707
+ const times = typeof node.count === 'number'
28708
+ ? { type: 'literal', value: node.count, raw: String(node.count), ...POS }
28709
+ : toCoreAST(node.count);
28710
+ return loopCommand(node, 'times', { times });
28711
+ }
28712
+ if (node.whileCondition !== undefined) {
28713
+ return loopCommand(node, 'while', { while: toCoreAST(node.whileCondition) });
28714
+ }
28715
+ if (node.untilEvent !== undefined) {
28716
+ return loopCommand(node, 'until-event', {
28717
+ event: slotString(node.untilEvent),
28718
+ from: node.untilEventTarget ? toCoreAST(node.untilEventTarget) : undefined,
28719
+ });
28044
28720
  }
28045
- return result;
28721
+ return loopCommand(node, 'forever', {});
28046
28722
  }
28047
28723
  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
- };
28724
+ return loopCommand(node, 'for', {
28725
+ for: slotString(node.itemName),
28726
+ in: toCoreAST(node.collection),
28727
+ });
28060
28728
  }
28061
28729
  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
- };
28730
+ return loopCommand(node, 'while', {
28731
+ while: toCoreAST(node.condition),
28732
+ bottomTested: node.bottomTested
28733
+ ? { type: 'literal', value: true, raw: 'true', ...POS }
28734
+ : undefined,
28735
+ });
28072
28736
  }
28073
28737
  function flattenEventModifiers(modifiers) {
28074
28738
  if (!modifiers)