@hyperfixi/core 3.1.0 → 3.2.0

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