@hyperfixi/core 3.1.1 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +28 -36
  2. package/dist/ast/command-slots.d.ts +1 -1
  3. package/dist/ast/nodes.d.ts +2 -0
  4. package/dist/ast-utils/index.cjs +193 -48
  5. package/dist/ast-utils/index.mjs +193 -48
  6. package/dist/ast-utils/interchange/types.d.ts +19 -1
  7. package/dist/behaviors/index.cjs +19 -0
  8. package/dist/behaviors/index.mjs +19 -0
  9. package/dist/bundle-generator/index.cjs +36 -17
  10. package/dist/bundle-generator/index.mjs +36 -17
  11. package/dist/bundle-generator/parser-templates.d.ts +1 -1
  12. package/dist/chunks/bridge-CI9SOl8u.js +2 -0
  13. package/dist/chunks/browser-modular-7CpI5VF2.js +2 -0
  14. package/dist/chunks/index-BwXDEK0m.js +2 -0
  15. package/dist/chunks/index-DQi2o8Lk.js +2 -0
  16. package/dist/chunks/index-uCOQ8Qn0.js +2 -0
  17. package/dist/commands/animation/transition.d.ts +12 -6
  18. package/dist/commands/helpers/loop-executor.d.ts +2 -2
  19. package/dist/commands/helpers/numeric-target-parser.d.ts +1 -1
  20. package/dist/commands/helpers/variable-access.d.ts +5 -2
  21. package/dist/commands/helpers/write-target.d.ts +5 -0
  22. package/dist/commands/index.cjs +938 -425
  23. package/dist/commands/index.mjs +938 -425
  24. package/dist/commands/templates/render.d.ts +4 -4
  25. package/dist/commands/utility/beep.d.ts +0 -3
  26. package/dist/expressions/index.cjs +7 -13
  27. package/dist/expressions/index.mjs +7 -13
  28. package/dist/htmx/i18n-hooks.d.ts +4 -1
  29. package/dist/hyperfixi-browser-classic-i18n.js +1 -1
  30. package/dist/hyperfixi-browser.js +1 -1
  31. package/dist/hyperfixi-classic-i18n.js +1 -1
  32. package/dist/hyperfixi-hx-v4.js +1 -1
  33. package/dist/hyperfixi-hx.js +1 -1
  34. package/dist/hyperfixi-hybrid-complete.js +1 -1
  35. package/dist/hyperfixi-hybrid-hx.js +1 -1
  36. package/dist/hyperfixi-multilingual.js +1 -1
  37. package/dist/hyperfixi.js +1 -1
  38. package/dist/hyperfixi.mjs +1 -1
  39. package/dist/index.cjs +1149 -485
  40. package/dist/index.min.js +1 -1
  41. package/dist/index.mjs +1149 -485
  42. package/dist/lib/swap-executor.d.ts +1 -1
  43. package/dist/lokascript-browser-classic-i18n.js +1 -1
  44. package/dist/lokascript-browser.js +1 -1
  45. package/dist/lokascript-hybrid-complete.js +1 -1
  46. package/dist/lokascript-hybrid-hx.js +1 -1
  47. package/dist/lokascript-multilingual.js +1 -1
  48. package/dist/metadata.cjs +9 -9
  49. package/dist/metadata.d.ts +1 -1
  50. package/dist/metadata.mjs +9 -9
  51. package/dist/parser/command-grammar.d.ts +1 -0
  52. package/dist/parser/full-parser.cjs +396 -153
  53. package/dist/parser/full-parser.mjs +396 -153
  54. package/dist/parser/helpers/parsing-helpers.d.ts +3 -0
  55. package/dist/parser/hybrid/index.cjs +3 -0
  56. package/dist/parser/hybrid/index.mjs +3 -0
  57. package/dist/parser/hybrid/parser-core.cjs +3 -0
  58. package/dist/parser/hybrid/parser-core.mjs +3 -0
  59. package/dist/parser/hybrid-parser.cjs +3 -0
  60. package/dist/parser/hybrid-parser.mjs +3 -0
  61. package/dist/parser/parser-types.d.ts +2 -0
  62. package/dist/parser/parser.d.ts +9 -0
  63. package/dist/parser/pratt-parser.d.ts +2 -0
  64. package/dist/registry/index.cjs +535 -210
  65. package/dist/registry/index.mjs +535 -210
  66. package/dist/utils/beep.d.ts +8 -0
  67. package/dist/version.d.ts +1 -1
  68. package/package.json +21 -18
  69. package/vocab/htmx/ar.js +22 -20
  70. package/vocab/htmx/bn.js +21 -5
  71. package/vocab/htmx/de.js +33 -10
  72. package/vocab/htmx/en.js +4 -1
  73. package/vocab/htmx/es.js +28 -9
  74. package/vocab/htmx/fr.js +28 -8
  75. package/vocab/htmx/he.js +22 -1
  76. package/vocab/htmx/hi.js +28 -7
  77. package/vocab/htmx/id.js +29 -10
  78. package/vocab/htmx/it.js +26 -5
  79. package/vocab/htmx/ja.js +36 -11
  80. package/vocab/htmx/ko.js +27 -6
  81. package/vocab/htmx/ms.js +24 -1
  82. package/vocab/htmx/pl.js +27 -7
  83. package/vocab/htmx/pt.js +30 -9
  84. package/vocab/htmx/qu.js +32 -9
  85. package/vocab/htmx/ru.js +28 -6
  86. package/vocab/htmx/sw.js +28 -8
  87. package/vocab/htmx/th.js +19 -1
  88. package/vocab/htmx/tl.js +24 -1
  89. package/vocab/htmx/tr.js +34 -8
  90. package/vocab/htmx/uk.js +28 -6
  91. package/vocab/htmx/vi.js +27 -19
  92. package/vocab/htmx/zh.js +28 -9
  93. package/dist/chunks/bridge-D7qrHhV0.js +0 -2
  94. package/dist/chunks/browser-modular-C4LFxfoS.js +0 -2
  95. package/dist/chunks/index-C7lqD4mo.js +0 -2
  96. package/dist/chunks/index-CAPhqA6k.js +0 -2
  97. package/dist/chunks/index-CLdRyZaP.js +0 -2
package/dist/index.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')) {
@@ -3802,6 +3947,15 @@ function parseToggleCommand(ctx, identifierNode) {
3802
3947
  args.push(classArg.expression);
3803
3948
  modifiers['as'] = literalModifier(mode);
3804
3949
  }
3950
+ else if (classArg?.type === 'binaryExpression' &&
3951
+ classArg.operator === 'of' &&
3952
+ classArg.left?.type === 'selector' &&
3953
+ typeof classArg.left.value === 'string' &&
3954
+ classArg.left.value.startsWith('*') &&
3955
+ classArg.right) {
3956
+ args.push(classArg.left);
3957
+ modifiers['on'] = classArg.right;
3958
+ }
3805
3959
  else if (classArg) {
3806
3960
  args.push(classArg);
3807
3961
  }
@@ -3979,11 +4133,17 @@ function parseSwapCommand(ctx, identifierNode) {
3979
4133
  const targetExpr = isDelete ? ctx.parseExpression() : parseOneArgument(ctx, [KEYWORDS.WITH]);
3980
4134
  if (targetExpr)
3981
4135
  args.push(targetExpr);
3982
- if (!isDelete && consumeOptionalKeyword(ctx, KEYWORDS.WITH)) {
4136
+ const isMorph = identifierNode.name === 'morph';
4137
+ if (!isDelete &&
4138
+ (consumeOptionalKeyword(ctx, KEYWORDS.WITH) ||
4139
+ (isMorph && consumeOptionalKeyword(ctx, KEYWORDS.TO)))) {
3983
4140
  const contentExpr = parseOneArgument(ctx, ['using']);
3984
4141
  if (contentExpr)
3985
4142
  modifiers['with'] = contentExpr;
3986
4143
  }
4144
+ if (isMorph && modifiers['with'] === undefined) {
4145
+ ctx.addError("Expected 'to' and the content to morph into");
4146
+ }
3987
4147
  return CommandNodeBuilder.fromIdentifier(identifierNode)
3988
4148
  .withArgs(...args)
3989
4149
  .withModifiers({ ...modifiers, ...parseViewTransitionTail(ctx) })
@@ -4744,6 +4904,8 @@ function atArgumentBoundary(ctx, grammar) {
4744
4904
  const next = ctx.peek();
4745
4905
  if (ctx.checkIsCommand() && ctx.peekAt(1)?.value === '(')
4746
4906
  return false;
4907
+ if (isGluedBeepBang(next, ctx.peekAt(1)))
4908
+ return false;
4747
4909
  if (isCommandBoundary(ctx, ['catch', 'finally', 'on']))
4748
4910
  return true;
4749
4911
  return isKeyword(next, [...grammar.markers]);
@@ -4775,10 +4937,13 @@ function parseDeclaredCommand(ctx, commandToken, name, grammar) {
4775
4937
  }
4776
4938
  }
4777
4939
  const commaList = new Set(grammar.commaList ?? []);
4940
+ const namedArgs = new Set(grammar.namedArgs ?? []);
4778
4941
  while (!ctx.isAtEnd() && isKeyword(ctx.peek(), [...grammar.markers])) {
4779
4942
  const keyword = ctx.advance().value;
4780
4943
  const key = ctx.resolveKeyword(keyword).toLowerCase();
4781
- let value = ctx.parseExpression();
4944
+ let value = namedArgs.has(key) && isNakedNamedArgStart(ctx)
4945
+ ? parseNakedNamedArgs(ctx, () => ctx.parseExpression(), name)
4946
+ : ctx.parseExpression();
4782
4947
  if (value && commaList.has(key) && ctx.check(',')) {
4783
4948
  const elements = [value];
4784
4949
  while (ctx.match(',')) {
@@ -4943,8 +5108,8 @@ function parseFetchCommand(ctx, commandToken) {
4943
5108
  }
4944
5109
  if (ctx.check('with') && !modifiers['with']) {
4945
5110
  ctx.advance();
4946
- if (isFetchNakedNamedArgStart(ctx)) {
4947
- modifiers['with'] = parseFetchNakedNamedArgs(ctx);
5111
+ if (isNakedNamedArgStart(ctx)) {
5112
+ modifiers['with'] = parseNakedNamedArgs(ctx, () => ctx.parsePrimary(), 'fetch');
4948
5113
  }
4949
5114
  else {
4950
5115
  modifiers['with'] = ctx.parsePrimary();
@@ -4979,45 +5144,6 @@ function parseFetchCommand(ctx, commandToken) {
4979
5144
  }
4980
5145
  return builder.build();
4981
5146
  }
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
5147
  function findJsEndBoundary(ctx, startPos) {
5022
5148
  const input = ctx.getInputSlice(startPos);
5023
5149
  if (!input) {
@@ -5137,6 +5263,7 @@ function parseTellCommand(ctx, identifierNode) {
5137
5263
  const commands = [];
5138
5264
  while (!ctx.isAtEnd()) {
5139
5265
  if (ctx.checkIsCommand()) {
5266
+ const word = ctx.peek().value;
5140
5267
  try {
5141
5268
  ctx.advance();
5142
5269
  const cmd = ctx.parseCommand();
@@ -5147,7 +5274,8 @@ function parseTellCommand(ctx, identifierNode) {
5147
5274
  break;
5148
5275
  }
5149
5276
  }
5150
- catch {
5277
+ catch (error) {
5278
+ ctx.addError(`'${word}' in a tell body failed to parse: ${error instanceof Error ? error.message : String(error)}`);
5151
5279
  break;
5152
5280
  }
5153
5281
  if (ctx.match(KEYWORDS.AND) || ctx.match(KEYWORDS.THEN)) {
@@ -5296,7 +5424,6 @@ const ONE_EXPR = (syntax) => ({
5296
5424
  markers: [],
5297
5425
  syntax,
5298
5426
  });
5299
- const BEEP = ONE_EXPR('beep! [<expression>, \u2026]');
5300
5427
  const COMMAND_GRAMMAR = {
5301
5428
  append: { positional: 'expression', markers: ['to'], syntax: 'append <value> [to <target>]' },
5302
5429
  prepend: { positional: 'expression', markers: ['to'], syntax: 'prepend <value> [to <target>]' },
@@ -5307,8 +5434,7 @@ const COMMAND_GRAMMAR = {
5307
5434
  syntax: 'make (a|an) <type> [from <args>] [called <name>]',
5308
5435
  },
5309
5436
  throw: { positional: 'primary', markers: [], syntax: 'throw <error>' },
5310
- 'beep!': BEEP,
5311
- beep: BEEP,
5437
+ beep: ONE_EXPR('beep! [<expression>, \u2026]'),
5312
5438
  blur: ONE_EXPR('blur [<target>]'),
5313
5439
  break: NONE,
5314
5440
  breakpoint: NONE,
@@ -5332,7 +5458,8 @@ const COMMAND_GRAMMAR = {
5332
5458
  render: {
5333
5459
  positional: 'expression',
5334
5460
  markers: ['with'],
5335
- syntax: 'render <template> [with <variables>]',
5461
+ namedArgs: ['with'],
5462
+ syntax: 'render <template> [with <name>: <value>, \u2026]',
5336
5463
  },
5337
5464
  reset: ONE_EXPR('reset [<form>]'),
5338
5465
  return: ONE_EXPR('return [<value>]'),
@@ -5687,7 +5814,10 @@ class Parser {
5687
5814
  const firstToken = this.peek();
5688
5815
  const prefixEntry = Parser.PRATT_TABLE.get(firstToken.value);
5689
5816
  let left;
5690
- if (prefixEntry?.prefix) {
5817
+ if (isGluedBeepBang(firstToken, this.tokens[this.current + 1])) {
5818
+ left = this.parseBeepExpression();
5819
+ }
5820
+ else if (prefixEntry?.prefix) {
5691
5821
  const token = this.advance();
5692
5822
  if (this.isAtEnd()) {
5693
5823
  this.addError(`Expected expression after '${token.value}' operator`);
@@ -5707,6 +5837,9 @@ class Parser {
5707
5837
  continue;
5708
5838
  }
5709
5839
  }
5840
+ if (nextToken.value === '*' && this.isSpacedStyleRef(this.current)) {
5841
+ break;
5842
+ }
5710
5843
  const infixEntry = Parser.PRATT_TABLE.get(nextToken.value);
5711
5844
  if (!infixEntry?.infix) {
5712
5845
  if (STOP_TOKENS.has(nextToken.value) || STOP_DELIMITERS.has(nextToken.value)) {
@@ -5768,6 +5901,35 @@ class Parser {
5768
5901
  this.prattCache.set(cacheKey, { node: left, endPos: this.current });
5769
5902
  return left;
5770
5903
  }
5904
+ parseBeepExpression() {
5905
+ const beepToken = this.advance();
5906
+ this.advance();
5907
+ if (this.isAtEnd()) {
5908
+ this.addError("Expected expression after 'beep!'");
5909
+ return this.createErrorNode();
5910
+ }
5911
+ const operand = this.parseExpressionPratt(Parser.BEEP_OPERAND_BP);
5912
+ return {
5913
+ type: 'unaryExpression',
5914
+ operator: 'beep!',
5915
+ operand,
5916
+ argument: operand,
5917
+ prefix: true,
5918
+ start: beepToken.start,
5919
+ end: this.previous().end,
5920
+ line: beepToken.line,
5921
+ column: beepToken.column,
5922
+ };
5923
+ }
5924
+ isStyleRefStar(i) {
5925
+ const star = this.tokens[i];
5926
+ const name = this.tokens[i + 1];
5927
+ return star?.value === '*' && !!name && name.start === star.end && isIdentifierLike(name);
5928
+ }
5929
+ isSpacedStyleRef(i) {
5930
+ const prev = this.tokens[i - 1];
5931
+ return this.isStyleRefStar(i) && !!prev && prev.end < this.tokens[i].start;
5932
+ }
5771
5933
  tryParseStringPostfix(left, nextToken) {
5772
5934
  const v = nextToken.value;
5773
5935
  let unit = null;
@@ -5970,6 +6132,10 @@ class Parser {
5970
6132
  const isStop = () => this.check('end') || extraStops.some(s => this.check(s));
5971
6133
  debug.parse('\ud83d\udd04 parseCommandListUntilTerminator: Starting (extraStops:', extraStops.join(','), ')');
5972
6134
  while (!this.isAtEnd() && !isStop()) {
6135
+ if (this.checkComment()) {
6136
+ this.advance();
6137
+ continue;
6138
+ }
5973
6139
  debug.parse('\ud83d\udccd Loop iteration, current token:', this.peek().value, 'kind:', this.peek().kind);
5974
6140
  let parsedCommand = false;
5975
6141
  const isCommandToken = this.checkIsCommand();
@@ -6053,7 +6219,7 @@ class Parser {
6053
6219
  debug.parse('\u2705 Found "end", consuming it');
6054
6220
  this.advance();
6055
6221
  }
6056
- else {
6222
+ else if (!this.atEndOfInput()) {
6057
6223
  debug.parse('\u274c ERROR: Expected "end" but got:', this.peek().value, 'at position:', this.peek().start);
6058
6224
  throw new Error(`Expected "end" to close ${construct} block`);
6059
6225
  }
@@ -6066,7 +6232,7 @@ class Parser {
6066
6232
  if (this.check('end')) {
6067
6233
  this.advance();
6068
6234
  }
6069
- else {
6235
+ else if (!this.atEndOfInput()) {
6070
6236
  throw new Error('Expected "end" to close repeat block');
6071
6237
  }
6072
6238
  }
@@ -6082,7 +6248,10 @@ class Parser {
6082
6248
  this.advance();
6083
6249
  }
6084
6250
  else if (terminator === '') {
6085
- throw new Error('Expected "end", "else", "until", or "while" to close repeat block');
6251
+ if (!this.atEndOfInput()) {
6252
+ throw new Error('Expected "end", "else", "until", or "while" to close repeat block');
6253
+ }
6254
+ return { commands, terminator: 'end' };
6086
6255
  }
6087
6256
  return { commands, terminator };
6088
6257
  }
@@ -6666,7 +6835,9 @@ class Parser {
6666
6835
  parseTopLevelInitBlock() {
6667
6836
  const pos = this.getPosition();
6668
6837
  const initCommands = this.parseCommandBlock(['end']);
6669
- this.consume('end', "Expected 'end' after init block");
6838
+ if (!this.match('end') && !this.atEndOfInput() && !this.checkFeatureStart()) {
6839
+ this.consume('end', "Expected 'end' after init block");
6840
+ }
6670
6841
  return {
6671
6842
  type: 'initBlock',
6672
6843
  commands: initCommands,
@@ -6842,10 +7013,29 @@ class Parser {
6842
7013
  ...(sourceExpr.column !== undefined && { column: sourceExpr.column }),
6843
7014
  };
6844
7015
  }
7016
+ isArgumentListOnly(open) {
7017
+ let depth = 0;
7018
+ for (let i = open; i < this.tokens.length; i++) {
7019
+ const token = this.tokens[i];
7020
+ if (token.kind !== 'operator')
7021
+ continue;
7022
+ if (token.value === '(' || token.value === '[' || token.value === '{')
7023
+ depth++;
7024
+ else if (token.value === ')' || token.value === ']' || token.value === '}') {
7025
+ if (--depth === 0)
7026
+ return i === open + 1;
7027
+ }
7028
+ else if (token.value === ',' && depth === 1)
7029
+ return true;
7030
+ }
7031
+ return false;
7032
+ }
6845
7033
  tryParsePseudoCommand(callExpr, sourceExpr) {
6846
7034
  const methodName = callExpr.callee.name;
6847
7035
  const nextToken = this.peek();
6848
- const hasPseudoCommandPattern = Parser.PSEUDO_COMMAND_PREPOSITIONS.includes(nextToken.value.toLowerCase()) ||
7036
+ const nextIsArticle = nextToken.value.toLowerCase() === 'the';
7037
+ const hasPseudoCommandPattern = nextIsArticle ||
7038
+ Parser.PSEUDO_COMMAND_PREPOSITIONS.includes(nextToken.value.toLowerCase()) ||
6849
7039
  (isIdentifier(nextToken) && !this.isCommand(nextToken.value)) ||
6850
7040
  isContextVar(nextToken);
6851
7041
  if (!hasPseudoCommandPattern) {
@@ -6871,7 +7061,10 @@ class Parser {
6871
7061
  });
6872
7062
  }
6873
7063
  let preposition;
6874
- if (Parser.PSEUDO_COMMAND_PREPOSITIONS.includes(this.peek().value.toLowerCase())) {
7064
+ if (nextIsArticle) {
7065
+ this.advance();
7066
+ }
7067
+ else if (Parser.PSEUDO_COMMAND_PREPOSITIONS.includes(this.peek().value.toLowerCase())) {
6875
7068
  preposition = this.advance().value.toLowerCase();
6876
7069
  }
6877
7070
  let targetExpr;
@@ -7035,10 +7228,22 @@ class Parser {
7035
7228
  this.consume(']', "Expected ']' after event condition");
7036
7229
  }
7037
7230
  let target;
7231
+ let targetExpression;
7038
7232
  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})`);
7233
+ if (this.check('the'))
7234
+ this.advance();
7235
+ if (Parser.POSITIONAL_SOURCE_HEADS.has(this.peek().value.toLowerCase())) {
7236
+ const startIndex = this.current;
7237
+ targetExpression = this.parseExpression();
7238
+ target = this.tokens
7239
+ .slice(startIndex, this.current)
7240
+ .map(t => t.value)
7241
+ .join(' ');
7242
+ }
7243
+ else {
7244
+ target = this.advance().value;
7245
+ }
7246
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed 'from' target: ${target}`);
7042
7247
  this.parseTemporalKeywordModifiers(modifiers);
7043
7248
  }
7044
7249
  let attributeName;
@@ -7084,7 +7289,7 @@ class Parser {
7084
7289
  const nextIsOpenParen = this.tokens[this.current + 1]?.value === '(';
7085
7290
  const commandName = this.peek().value.toLowerCase();
7086
7291
  const isSpecialBodyCommand = commandName === 'js' || commandName === 'tell';
7087
- if (nextIsOpenParen && !isSpecialBodyCommand) {
7292
+ if (nextIsOpenParen && !isSpecialBodyCommand && this.isArgumentListOnly(this.current + 1)) {
7088
7293
  let expr;
7089
7294
  const savedError = this.error;
7090
7295
  try {
@@ -7116,7 +7321,7 @@ class Parser {
7116
7321
  }
7117
7322
  }
7118
7323
  }
7119
- else if (this.checkIdentifier()) {
7324
+ else if (this.checkIdentifier() || this.checkEventNamedCall()) {
7120
7325
  const token = this.peek();
7121
7326
  if (this.isCommand(token.value)) {
7122
7327
  const cmd = this.parseCommandWithErrorRecovery();
@@ -7225,6 +7430,7 @@ class Parser {
7225
7430
  ...(eventParams.length > 0 && { args: eventParams }),
7226
7431
  ...(condition && { condition: fromLegacyExpression(condition) }),
7227
7432
  ...(target && { target }),
7433
+ ...(targetExpression && { targetExpression: fromLegacyExpression(targetExpression) }),
7228
7434
  ...(attributeName && { attributeName }),
7229
7435
  ...(watchTarget && { watchTarget: fromLegacyExpression(watchTarget) }),
7230
7436
  ...(customEventSource && { customEventSource }),
@@ -7480,10 +7686,9 @@ class Parser {
7480
7686
  }
7481
7687
  parseCommandCore() {
7482
7688
  const commandToken = this.previous();
7483
- let commandName = commandToken.value;
7689
+ const commandName = commandToken.value;
7484
7690
  if (commandName === 'beep' && this.check('!')) {
7485
7691
  this.advance();
7486
- commandName = 'beep!';
7487
7692
  }
7488
7693
  if (commandName === 'fetch') {
7489
7694
  return parseFetchCommand(this.getContext(), commandToken);
@@ -7582,6 +7787,9 @@ class Parser {
7582
7787
  args.push(this.parsePrimary());
7583
7788
  }
7584
7789
  const callNode = this.createCallExpression(createIdentifier(funcName, namePos), args);
7790
+ if (funcName === 'closest' && args.length > 0 && this.match('to')) {
7791
+ callNode.closestTo = this.parseExpression();
7792
+ }
7585
7793
  if (funcName === 'next' || funcName === 'previous') {
7586
7794
  const relativePositional = this.parseRelativePositionalModifiers();
7587
7795
  if (relativePositional) {
@@ -7621,6 +7829,9 @@ class Parser {
7621
7829
  }
7622
7830
  parseTheXofY() {
7623
7831
  const positionalKeywords = ['first', 'last', 'next', 'previous', 'random', 'closest'];
7832
+ if (this.isStyleRefStar(this.current)) {
7833
+ return this.parsePrimary();
7834
+ }
7624
7835
  if (!this.checkIdentifier()) {
7625
7836
  return this.createIdentifier('the');
7626
7837
  }
@@ -7636,7 +7847,7 @@ class Parser {
7636
7847
  return this.createIdentifier(propertyName);
7637
7848
  }
7638
7849
  this.advance();
7639
- const target = this.parseCall();
7850
+ const target = parseOfOperand(this.makePrattContext(), THE_OF_TARGET_BP);
7640
7851
  return {
7641
7852
  type: 'propertyOfExpression',
7642
7853
  property: { type: 'identifier', name: propertyName },
@@ -7834,6 +8045,13 @@ class Parser {
7834
8045
  checkLiteral() {
7835
8046
  return this.checkPredicate(isLiteral);
7836
8047
  }
8048
+ checkEventNamedCall() {
8049
+ if (this.isAtEnd() || !this.checkIdentifierLike() || this.checkIsCommand())
8050
+ return false;
8051
+ const token = this.peek();
8052
+ const next = this.tokens[this.current + 1];
8053
+ return next?.value === '(' && next.start === token.end;
8054
+ }
7837
8055
  checkIdentifierLike() {
7838
8056
  return this.checkPredicate(isIdentifierLike);
7839
8057
  }
@@ -7942,6 +8160,22 @@ class Parser {
7942
8160
  isAtEnd() {
7943
8161
  return this.current >= this.tokens.length;
7944
8162
  }
8163
+ atEndOfInput() {
8164
+ for (let i = this.current; i < this.tokens.length; i++) {
8165
+ if (!isComment(this.tokens[i]))
8166
+ return false;
8167
+ }
8168
+ return true;
8169
+ }
8170
+ checkFeatureStart() {
8171
+ if (this.isAtEnd())
8172
+ return false;
8173
+ const value = this.peek().value;
8174
+ return (value === 'init' ||
8175
+ value === 'on' ||
8176
+ value === 'def' ||
8177
+ getRegisteredFeature(value) !== undefined);
8178
+ }
7945
8179
  peek() {
7946
8180
  if (this.isAtEnd()) {
7947
8181
  return { kind: 'unknown', value: '', start: 0, end: 0, line: 1, column: 1 };
@@ -8153,6 +8387,7 @@ class Parser {
8153
8387
  match: this.match.bind(this),
8154
8388
  matchOperator: this.matchOperator.bind(this),
8155
8389
  isAtEnd: this.isAtEnd.bind(this),
8390
+ atEndOfInput: this.atEndOfInput.bind(this),
8156
8391
  createIdentifier: this.createIdentifier.bind(this),
8157
8392
  parseExpression: this.parseExpression.bind(this),
8158
8393
  parsePrimary: this.parsePrimary.bind(this),
@@ -8203,7 +8438,15 @@ Parser.POSTFIX_UNARY_OPERATORS = new Set([
8203
8438
  'ignoring case',
8204
8439
  ]);
8205
8440
  Parser.PRATT_TABLE = PARSER_TABLE;
8441
+ Parser.BEEP_OPERAND_BP = 80;
8206
8442
  Parser.PSEUDO_COMMAND_PREPOSITIONS = ['from', 'on', 'with', 'into', 'at', 'to'];
8443
+ Parser.POSITIONAL_SOURCE_HEADS = new Set([
8444
+ 'closest',
8445
+ 'next',
8446
+ 'previous',
8447
+ 'first',
8448
+ 'last',
8449
+ ]);
8207
8450
  function parse(input, options) {
8208
8451
  const tokens = tokenize(input);
8209
8452
  const parser = new Parser(tokens, options, input);
@@ -8408,6 +8651,89 @@ function isDocumentFragment(value) {
8408
8651
  'childNodes' in value);
8409
8652
  }
8410
8653
 
8654
+ function beepValues(me, values) {
8655
+ const outputs = values.map(describeBeepValue);
8656
+ const shown = outputs.filter(output => announce(me, output.value));
8657
+ if (shown.length === 0)
8658
+ return outputs;
8659
+ console.group('\ud83d\udd14 beep! Debug Output');
8660
+ for (const output of shown) {
8661
+ console.log(`Value:`, output.value);
8662
+ console.log(`Type:`, output.type);
8663
+ console.log(`Representation:`, output.representation);
8664
+ console.log('---');
8665
+ }
8666
+ console.groupEnd();
8667
+ return outputs;
8668
+ }
8669
+ function announce(me, value) {
8670
+ const target = me;
8671
+ if (typeof CustomEvent !== 'function' || typeof target?.dispatchEvent !== 'function')
8672
+ return true;
8673
+ return target.dispatchEvent(new CustomEvent('hyperscript:beep', {
8674
+ bubbles: true,
8675
+ cancelable: true,
8676
+ detail: { element: me, value },
8677
+ }));
8678
+ }
8679
+ function describeBeepValue(value) {
8680
+ return { value, type: typeOf(value), representation: representationOf(value) };
8681
+ }
8682
+ function typeOf(value) {
8683
+ if (value === null)
8684
+ return 'null';
8685
+ if (value === undefined)
8686
+ return 'undefined';
8687
+ if (Array.isArray(value))
8688
+ return 'array';
8689
+ if (isHTMLElement$2(value))
8690
+ return 'HTMLElement';
8691
+ if (typeof Element !== 'undefined' && value instanceof Element)
8692
+ return 'Element';
8693
+ if (typeof Node !== 'undefined' && value instanceof Node)
8694
+ return 'Node';
8695
+ if (value instanceof Error)
8696
+ return 'Error';
8697
+ if (value instanceof Date)
8698
+ return 'Date';
8699
+ if (value instanceof RegExp)
8700
+ return 'RegExp';
8701
+ return typeof value;
8702
+ }
8703
+ function representationOf(value) {
8704
+ if (value === null)
8705
+ return 'null';
8706
+ if (value === undefined)
8707
+ return 'undefined';
8708
+ if (Array.isArray(value)) {
8709
+ return `Array(${value.length}) [${value
8710
+ .slice(0, 3)
8711
+ .map(v => representationOf(v))
8712
+ .join(', ')}${value.length > 3 ? '...' : ''}]`;
8713
+ }
8714
+ if (isHTMLElement$2(value)) {
8715
+ const el = value;
8716
+ const tag = el.tagName.toLowerCase();
8717
+ const id = el.id ? `#${el.id}` : '';
8718
+ const classes = el.className ? `.${el.className.split(' ').join('.')}` : '';
8719
+ return `<${tag}${id}${classes}/>`;
8720
+ }
8721
+ if (value instanceof Error)
8722
+ return `Error: ${value.message}`;
8723
+ if (typeof value === 'string')
8724
+ return value.length > 50 ? `"${value.substring(0, 47)}..."` : `"${value}"`;
8725
+ if (typeof value === 'object') {
8726
+ try {
8727
+ const keys = Object.keys(value);
8728
+ return `Object {${keys.slice(0, 3).join(', ')}${keys.length > 3 ? '...' : ''}}`;
8729
+ }
8730
+ catch {
8731
+ return '[Object]';
8732
+ }
8733
+ }
8734
+ return String(value);
8735
+ }
8736
+
8411
8737
  function convertToNumber(value) {
8412
8738
  if (value === null || value === undefined) {
8413
8739
  return 0;
@@ -8439,6 +8765,16 @@ function convertToNumber(value) {
8439
8765
  }
8440
8766
  return 0;
8441
8767
  }
8768
+ function counterAmount(value) {
8769
+ return typeof value === 'number' ? value : parseFloat(String(value));
8770
+ }
8771
+ function counterValue(value) {
8772
+ if (isHTMLElement$2(value)) {
8773
+ const element = value;
8774
+ return counterValue(element.value || element.textContent);
8775
+ }
8776
+ return value ? counterAmount(value) : 0;
8777
+ }
8442
8778
  function getVariableValue(name, context, preferredScope) {
8443
8779
  if (preferredScope === 'element') {
8444
8780
  return getElementVar(context, name);
@@ -8543,35 +8879,39 @@ function setElementPropertyValue(propertyPath, value, context) {
8543
8879
  element[property] = value;
8544
8880
  }
8545
8881
  }
8882
+ function isObjectOwner(value) {
8883
+ return (typeof value === 'object' && value !== null && !Array.isArray(value) && !isHTMLElement$2(value));
8884
+ }
8546
8885
  function getCurrentNumericValue(target, property, scope, context) {
8547
8886
  if (typeof target === 'number') {
8548
8887
  return target;
8549
8888
  }
8550
8889
  if (isHTMLElement$2(target)) {
8551
8890
  const element = target;
8891
+ if (property?.startsWith('@'))
8892
+ return counterValue(element.getAttribute(property.slice(1)));
8552
8893
  if (property) {
8553
8894
  if (property.startsWith('data-') ||
8554
8895
  ['id', 'class', 'title', 'alt', 'src', 'href'].includes(property)) {
8555
- const value = element.getAttribute(property);
8556
- return convertToNumber(value);
8896
+ return counterValue(element.getAttribute(property));
8557
8897
  }
8558
8898
  else {
8559
- const value = element[property];
8560
- return convertToNumber(value);
8899
+ return counterValue(element[property]);
8561
8900
  }
8562
8901
  }
8563
8902
  else {
8564
- const value = element.value || element.textContent;
8565
- return convertToNumber(value);
8903
+ return counterValue(element);
8566
8904
  }
8567
8905
  }
8906
+ if (property && isObjectOwner(target)) {
8907
+ return counterValue(target[property]);
8908
+ }
8568
8909
  if (typeof target === 'string') {
8569
8910
  if (scope === 'element') {
8570
- return convertToNumber(getVariableValue(target, context, 'element'));
8911
+ return counterValue(getVariableValue(target, context, 'element'));
8571
8912
  }
8572
8913
  if (scope === 'global') {
8573
- const value = getVariableValue(target, context, 'global');
8574
- return convertToNumber(value);
8914
+ return counterValue(getVariableValue(target, context, 'global'));
8575
8915
  }
8576
8916
  if (target.includes('.')) {
8577
8917
  return getElementPropertyValue(target, context);
@@ -8585,14 +8925,17 @@ function getCurrentNumericValue(target, property, scope, context) {
8585
8925
  else if (target === 'you' && context.you) {
8586
8926
  return convertToNumber(context.you.value || 0);
8587
8927
  }
8588
- const value = getVariableValue(target, context);
8589
- return convertToNumber(value);
8928
+ return counterValue(getVariableValue(target, context));
8590
8929
  }
8591
- return convertToNumber(target);
8930
+ return counterValue(target);
8592
8931
  }
8593
8932
  function setTargetValue(target, property, scope, newValue, context) {
8594
8933
  if (isHTMLElement$2(target)) {
8595
8934
  const element = target;
8935
+ if (property?.startsWith('@')) {
8936
+ element.setAttribute(property.slice(1), String(newValue));
8937
+ return;
8938
+ }
8596
8939
  if (property) {
8597
8940
  if (property.startsWith('data-') ||
8598
8941
  ['id', 'class', 'title', 'alt', 'src', 'href'].includes(property)) {
@@ -8612,6 +8955,10 @@ function setTargetValue(target, property, scope, newValue, context) {
8612
8955
  }
8613
8956
  return;
8614
8957
  }
8958
+ if (property && isObjectOwner(target)) {
8959
+ target[property] = newValue;
8960
+ return;
8961
+ }
8615
8962
  if (typeof target === 'string') {
8616
8963
  if (scope === 'element') {
8617
8964
  setVariableValue(target, newValue, context, 'element');
@@ -9040,13 +9387,6 @@ function isFunction(value) {
9040
9387
  function isInputElement$1(el) {
9041
9388
  return (el !== null && typeof el === 'object' && 'tagName' in el && el.tagName === 'INPUT');
9042
9389
  }
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
9390
  function isHTMLElement$1(el) {
9051
9391
  return (el !== null &&
9052
9392
  typeof el === 'object' &&
@@ -9106,7 +9446,7 @@ const SPECIAL_DOM_PROPERTIES = {
9106
9446
  innertext: el => el.textContent?.trim(),
9107
9447
  innerHTML: el => el.innerHTML,
9108
9448
  outerhtml: el => el.outerHTML,
9109
- value: el => (isFormElement$1(el) ? el.value : undefined),
9449
+ value: el => ('value' in el ? el.value : undefined),
9110
9450
  checked: el => (isInputElement$1(el) ? el.checked : undefined),
9111
9451
  disabled: el => ('disabled' in el ? el.disabled : undefined),
9112
9452
  selected: el => (isOptionElement$1(el) ? el.selected : undefined),
@@ -9774,14 +10114,15 @@ const closestExpression = {
9774
10114
  if (typeof selector !== 'string') {
9775
10115
  throw new Error('closest requires a string selector');
9776
10116
  }
9777
- if (!context.me) {
10117
+ const start = args.length > 1 ? args[1] : context.me;
10118
+ if (!(start instanceof Element)) {
9778
10119
  return null;
9779
10120
  }
9780
- return context.me.closest(selector);
10121
+ return start.closest(selector);
9781
10122
  },
9782
10123
  validate(args) {
9783
- if (args.length !== 1) {
9784
- return 'closest requires exactly 1 argument (selector)';
10124
+ if (args.length !== 1 && args.length !== 2) {
10125
+ return 'closest requires a selector and, optionally, the element to start from';
9785
10126
  }
9786
10127
  const arg = args[0];
9787
10128
  if (typeof arg === 'string')
@@ -11281,7 +11622,7 @@ const isEmptyExpression = {
11281
11622
  else if (value instanceof Node || value instanceof Element)
11282
11623
  result = false;
11283
11624
  else if (isObject(value))
11284
- result = Object.keys(value).length === 0;
11625
+ result = value.length === 0;
11285
11626
  else
11286
11627
  result = false;
11287
11628
  return result;
@@ -12405,11 +12746,16 @@ async function evaluateBinaryExpression(node, context) {
12405
12746
  }
12406
12747
  }
12407
12748
  }
12408
- if (leftNode?.type === 'selector' && leftNode.fromQuery && typeof leftNode.value === 'string') {
12749
+ if (leftNode?.type === 'selector' &&
12750
+ typeof leftNode.value === 'string' &&
12751
+ (leftNode.fromQuery || (operator === 'in' && leftNode.value.startsWith('.')))) {
12409
12752
  const root = await evaluateAST(node.right, context);
12410
12753
  const scope = root && typeof root.querySelectorAll === 'function' ? root : null;
12411
12754
  if (scope) {
12412
- return Array.from(scope.querySelectorAll(leftNode.value));
12755
+ const query = leftNode.fromQuery
12756
+ ? leftNode.value
12757
+ : escapeSelectorForQuery(leftNode, leftNode.value);
12758
+ return Array.from(scope.querySelectorAll(query));
12413
12759
  }
12414
12760
  }
12415
12761
  }
@@ -12421,6 +12767,12 @@ async function evaluateBinaryExpression(node, context) {
12421
12767
  return getExpr(context, 'styleRef').evaluate(context, leftNode.value.slice(1), el);
12422
12768
  }
12423
12769
  if (operator === 'of') {
12770
+ if (leftNode?.type === 'attributeAccess') {
12771
+ const target = await evaluateAST(node.right, context);
12772
+ if (target == null)
12773
+ return undefined;
12774
+ return getExpr(context, 'possessive').evaluate(context, target, `@${leftNode.attributeName}`);
12775
+ }
12424
12776
  const path = propertyPathOf(node.left);
12425
12777
  if (path) {
12426
12778
  const target = await evaluateAST(node.right, context);
@@ -12428,6 +12780,10 @@ async function evaluateBinaryExpression(node, context) {
12428
12780
  for (const seg of path) {
12429
12781
  if (cur == null)
12430
12782
  return undefined;
12783
+ if (isMappableCollection(cur) && !(seg in cur)) {
12784
+ cur = await getExpr(context, 'possessive').evaluate(context, cur, seg);
12785
+ continue;
12786
+ }
12431
12787
  cur = isElement(cur) ? getElementProperty$1(cur, seg) : cur[seg];
12432
12788
  }
12433
12789
  return typeof cur === 'function' ? cur.bind(target) : cur;
@@ -12452,26 +12808,21 @@ async function evaluateBinaryExpression(node, context) {
12452
12808
  const R = node.ignoringCase ? applyCI(right) : right;
12453
12809
  switch (operator) {
12454
12810
  case '+': {
12455
- let addLeft = left;
12456
- let addRight = right;
12457
12811
  if (node.coerceNumeric) {
12458
- if (typeof addLeft === 'string')
12459
- addLeft = convertToNumber(addLeft);
12460
- if (typeof addRight === 'string')
12461
- addRight = convertToNumber(addRight);
12812
+ return counterValue(left) + counterAmount(right);
12462
12813
  }
12463
- if (typeof addLeft === 'string' || typeof addRight === 'string') {
12464
- return String(addLeft ?? '') + String(addRight ?? '');
12814
+ if (typeof left === 'string' || typeof right === 'string') {
12815
+ return String(left ?? '') + String(right ?? '');
12465
12816
  }
12466
- if (Array.isArray(addLeft)) {
12467
- return Array.isArray(addRight) ? [...addLeft, ...addRight] : [...addLeft, addRight];
12817
+ if (Array.isArray(left)) {
12818
+ return Array.isArray(right) ? [...left, ...right] : [...left, right];
12468
12819
  }
12469
- return unwrapTypedResult(await getExpr(context, 'addition').evaluate(context, {
12470
- left: addLeft,
12471
- right: addRight,
12472
- }));
12820
+ return unwrapTypedResult(await getExpr(context, 'addition').evaluate(context, { left, right }));
12473
12821
  }
12474
12822
  case '-':
12823
+ if (node.coerceNumeric) {
12824
+ return counterValue(left) - counterAmount(right);
12825
+ }
12475
12826
  return unwrapTypedResult(await getExpr(context, 'subtraction').evaluate(context, { left, right }));
12476
12827
  case '*':
12477
12828
  return unwrapTypedResult(await getExpr(context, 'multiplication').evaluate(context, { left, right }));
@@ -12729,9 +13080,8 @@ async function evaluatePropertyOfExpressionNode(node, context) {
12729
13080
  }
12730
13081
  const propertyName = propertyNode.name;
12731
13082
  const target = await evaluateAST(node.target, context);
12732
- if (target == null) {
12733
- throw new Error(`Cannot access property "${propertyName}" of ${target}`);
12734
- }
13083
+ if (target == null)
13084
+ return undefined;
12735
13085
  return getExpr(context, 'possessive').evaluate(context, target, propertyName);
12736
13086
  }
12737
13087
  async function evaluateTemplateLiteralNode(node, context) {
@@ -12849,6 +13199,9 @@ async function evaluateUnaryExpression(node, context) {
12849
13199
  case 'not':
12850
13200
  case '!':
12851
13201
  return logicalExpressions.not.evaluate(context, value);
13202
+ case 'beep!':
13203
+ beepValues(context.me, [value]);
13204
+ return value;
12852
13205
  case 'no':
12853
13206
  return logicalExpressions.no.evaluate(context, value);
12854
13207
  case '-':
@@ -12938,6 +13291,10 @@ async function evaluateCallExpression(node, context) {
12938
13291
  const args = await resolveCallArgs(node.arguments, funcName, context);
12939
13292
  switch (funcName) {
12940
13293
  case 'closest':
13294
+ if (node.closestTo) {
13295
+ const start = unwrapElement(await evaluateAST(node.closestTo, context)) ?? null;
13296
+ return referencesExpressions.closest.evaluate(context, args[0], start);
13297
+ }
12941
13298
  return referencesExpressions.closest.evaluate(context, ...args);
12942
13299
  case 'previous':
12943
13300
  case 'next': {
@@ -14324,12 +14681,23 @@ class RuntimeBase {
14324
14681
  debug.runtime(`BEHAVIOR: Finished installing ${behaviorName}`);
14325
14682
  }
14326
14683
  async executeEventHandler(node, context) {
14327
- const { event, events, commands, target, args, selector, condition, attributeName, watchTarget, modifiers, errorSymbol, errorHandler, finallyHandler, } = node;
14684
+ const { event, events, commands, target, targetExpression, args, selector, condition, attributeName, watchTarget, modifiers, errorSymbol, errorHandler, finallyHandler, } = node;
14328
14685
  const eventNames = events && events.length > 0 ? events : [event];
14329
14686
  debug.runtime(`BEHAVIOR: executeEventHandler: event='${event}', target='${target}'`);
14330
14687
  let targets = [];
14331
14688
  let globalTarget = null;
14332
- if (target) {
14689
+ if (targetExpression) {
14690
+ const resolved = await this.execute(targetExpression, context);
14691
+ if (resolved === window)
14692
+ globalTarget = window;
14693
+ else if (resolved === document)
14694
+ globalTarget = document;
14695
+ else if (this.isElement(resolved))
14696
+ targets = [resolved];
14697
+ else if (Array.isArray(resolved))
14698
+ targets = resolved.filter(el => this.isElement(el));
14699
+ }
14700
+ else if (target) {
14333
14701
  const targetLower = typeof target === 'string' ? target.toLowerCase() : '';
14334
14702
  if (targetLower === 'window' || targetLower === 'the window') {
14335
14703
  globalTarget = window;
@@ -16350,14 +16718,83 @@ function parseAttribute(expression) {
16350
16718
  if (trimmed.startsWith('@')) {
16351
16719
  return { name: trimmed.substring(1).trim() };
16352
16720
  }
16353
- throw new Error(`Invalid attribute syntax: ${expression}`);
16354
- }
16355
- function parseAttributeName(expression) {
16356
- return parseAttribute(expression).name;
16721
+ throw new Error(`Invalid attribute syntax: ${expression}`);
16722
+ }
16723
+ function parseAttributeName(expression) {
16724
+ return parseAttribute(expression).name;
16725
+ }
16726
+ function parseAttributeWithValue(expression) {
16727
+ const result = parseAttribute(expression);
16728
+ return { name: result.name, value: result.value ?? '' };
16729
+ }
16730
+
16731
+ function _parseDurationComponents(value) {
16732
+ const trimmed = value.trim();
16733
+ const match = trimmed.match(/^(\d+(?:\.\d+)?)\s*(s|ms|sec|seconds?|milliseconds?)?$/i);
16734
+ if (!match) {
16735
+ return null;
16736
+ }
16737
+ const number = parseFloat(match[1]);
16738
+ const unit = (match[2] || 'ms').toLowerCase();
16739
+ if (unit === 'ms' || unit === 'millisecond' || unit === 'milliseconds') {
16740
+ return { number, milliseconds: Math.floor(number) };
16741
+ }
16742
+ else if (unit === 's' || unit === 'sec' || unit === 'second' || unit === 'seconds') {
16743
+ return { number, milliseconds: Math.floor(number * 1000) };
16744
+ }
16745
+ return null;
16746
+ }
16747
+ function parseDuration(value, defaultMs = 300) {
16748
+ if (typeof value === 'number') {
16749
+ return Math.max(0, Math.floor(value));
16750
+ }
16751
+ if (typeof value === 'string') {
16752
+ const result = _parseDurationComponents(value);
16753
+ return result ? result.milliseconds : defaultMs;
16754
+ }
16755
+ return defaultMs;
16756
+ }
16757
+ function parseDurationStrict(value) {
16758
+ if (typeof value === 'number') {
16759
+ if (value < 0)
16760
+ throw new Error('Duration must be >= 0');
16761
+ return Math.floor(value);
16762
+ }
16763
+ if (typeof value === 'string') {
16764
+ const result = _parseDurationComponents(value);
16765
+ if (!result) {
16766
+ throw new Error(`Invalid duration format: "${value}"`);
16767
+ }
16768
+ return result.milliseconds;
16769
+ }
16770
+ throw new Error(`Invalid duration type: ${typeof value}`);
16771
+ }
16772
+ function parseCSSDurations(durationString) {
16773
+ if (!durationString || durationString === 'none') {
16774
+ return [0];
16775
+ }
16776
+ return durationString.split(',').map(duration => {
16777
+ const value = parseFloat(duration.trim());
16778
+ if (isNaN(value))
16779
+ return 0;
16780
+ if (duration.includes('s') && !duration.includes('ms')) {
16781
+ return value * 1000;
16782
+ }
16783
+ return value;
16784
+ });
16785
+ }
16786
+ function calculateMaxAnimationTime(durations, delays) {
16787
+ let maxTime = 0;
16788
+ for (let i = 0; i < durations.length; i++) {
16789
+ const duration = durations[i] || 0;
16790
+ const delay = delays[i] || 0;
16791
+ const totalTime = duration + delay;
16792
+ maxTime = Math.max(maxTime, totalTime);
16793
+ }
16794
+ return maxTime;
16357
16795
  }
16358
- function parseAttributeWithValue(expression) {
16359
- const result = parseAttribute(expression);
16360
- return { name: result.name, value: result.value ?? '' };
16796
+ function camelToKebab(str) {
16797
+ return str.replace(/([A-Z])/g, '-$1').toLowerCase();
16361
16798
  }
16362
16799
 
16363
16800
  function batchApply(targets, operation) {
@@ -16425,15 +16862,18 @@ function toggleAttribute(element, name, value) {
16425
16862
  }
16426
16863
  }
16427
16864
  }
16865
+ function toCssPropertyName(property) {
16866
+ return property.startsWith('--') ? property : camelToKebab(property);
16867
+ }
16428
16868
  function batchSetStyles(targets, styles) {
16429
16869
  const entries = Object.entries(styles);
16430
16870
  return batchApplyItems(targets, entries, (element, [property, value]) => {
16431
- element.style.setProperty(property, value);
16871
+ element.style.setProperty(toCssPropertyName(property), value);
16432
16872
  });
16433
16873
  }
16434
16874
  function batchRemoveStyles(targets, properties) {
16435
16875
  return batchApplyItems(targets, properties, (element, property) => {
16436
- element.style.removeProperty(property);
16876
+ element.style.removeProperty(toCssPropertyName(property));
16437
16877
  });
16438
16878
  }
16439
16879
 
@@ -17099,75 +17539,6 @@ let RemoveCommand = (() => {
17099
17539
  })();
17100
17540
  const createRemoveCommand = createFactory(RemoveCommand);
17101
17541
 
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
17542
  function detectSmartElementType(elements) {
17172
17543
  if (elements.length === 0)
17173
17544
  return null;
@@ -18037,7 +18408,11 @@ let PutCommand = (() => {
18037
18408
  }
18038
18409
  if (isInsertableNode(v))
18039
18410
  return v;
18040
- return v == null ? '' : String(v);
18411
+ if (v == null)
18412
+ return 'null';
18413
+ if (Array.isArray(v))
18414
+ return arrayFragment(v);
18415
+ return String(v);
18041
18416
  }
18042
18417
  looksLikeCss(s) {
18043
18418
  if (!s)
@@ -18122,6 +18497,19 @@ let PutCommand = (() => {
18122
18497
  })();
18123
18498
  return PutCommand = _classThis;
18124
18499
  })();
18500
+ function arrayFragment(items) {
18501
+ const fragment = document.createDocumentFragment();
18502
+ for (const item of items) {
18503
+ if (isInsertableNode(item)) {
18504
+ fragment.append(item);
18505
+ continue;
18506
+ }
18507
+ const template = document.createElement('template');
18508
+ template.innerHTML = item === null ? '' : String(item);
18509
+ fragment.append(template.content);
18510
+ }
18511
+ return fragment;
18512
+ }
18125
18513
  const createPutCommand = createFactory(PutCommand);
18126
18514
 
18127
18515
  const HTML_ESCAPE = {
@@ -19339,10 +19727,29 @@ const STRATEGY_KEYWORDS = {
19339
19727
  innermorph: 'morph',
19340
19728
  outermorph: 'morphOuter',
19341
19729
  };
19730
+ function loneRootWithTag(content, tagName) {
19731
+ let root;
19732
+ if (typeof content === 'string') {
19733
+ const template = document.createElement('template');
19734
+ template.innerHTML = content;
19735
+ root = template.content.firstElementChild;
19736
+ if (!root || root.nextElementSibling)
19737
+ return null;
19738
+ }
19739
+ else {
19740
+ root = content.cloneNode(true);
19741
+ }
19742
+ return root.tagName === tagName && isHTMLElement$2(root) ? root : null;
19743
+ }
19342
19744
  function executeSwap(target, content, strategy, morphOptions) {
19343
19745
  const contentStr = content !== null && !isHTMLElement$2(content) ? content : '';
19344
19746
  const contentEl = isHTMLElement$2(content) ? content : null;
19345
19747
  switch (strategy) {
19748
+ case 'morphMerge': {
19749
+ const root = content !== null ? loneRootWithTag(content, target.tagName) : null;
19750
+ executeSwap(target, root ?? content, root ? 'morphOuter' : 'morph', morphOptions);
19751
+ break;
19752
+ }
19346
19753
  case 'morph':
19347
19754
  if (content !== null) {
19348
19755
  try {
@@ -19632,7 +20039,7 @@ let MorphCommand = (() => {
19632
20039
  if (args.length === 0 && modifiers.with === undefined) {
19633
20040
  throw new Error('[HyperFixi] morph: command requires arguments');
19634
20041
  }
19635
- const strategy = literalText(modifiers.strategy)?.toLowerCase() === 'over' ? 'morphOuter' : 'morph';
20042
+ const strategy = literalText(modifiers.strategy)?.toLowerCase() === 'over' ? 'morphOuter' : 'morphMerge';
19636
20043
  const useViewTransition = modifiers.viewTransition !== undefined;
19637
20044
  const targetNode = modifiers.on !== undefined ? modifiers.on : args[0];
19638
20045
  const contentNode = modifiers.with !== undefined
@@ -20938,6 +21345,19 @@ async function resolveWriteTarget(node, evaluator, context, options) {
20938
21345
  }
20939
21346
  return { kind: 'property', target: property };
20940
21347
  }
21348
+ if (options.ofAndObjectProperty) {
21349
+ const access = ofOrPossessiveAccess(n);
21350
+ if (access) {
21351
+ const owner = await evaluator.evaluate(access.owner, context);
21352
+ const element = Array.isArray(owner) ? owner[0] : owner;
21353
+ if (access.isOf && isHTMLElement$2(element)) {
21354
+ return { kind: 'property', target: { element, property: access.name } };
21355
+ }
21356
+ if (isObjectOwner(owner)) {
21357
+ return { kind: 'member', container: owner, property: access.name };
21358
+ }
21359
+ }
21360
+ }
20941
21361
  if (options.bareReference &&
20942
21362
  (nodeType === 'identifier' || nodeType === 'variable' || nodeType === 'symbol') &&
20943
21363
  typeof n.name === 'string') {
@@ -20947,6 +21367,19 @@ async function resolveWriteTarget(node, evaluator, context, options) {
20947
21367
  }
20948
21368
  return null;
20949
21369
  }
21370
+ function ofOrPossessiveAccess(n) {
21371
+ const [owner, property, isOf] = n.type === 'possessiveExpression'
21372
+ ? [n.object, n.property, false]
21373
+ : n.type === 'propertyOfExpression'
21374
+ ? [n.target, n.property, false]
21375
+ : n.type === 'binaryExpression' && n.operator === 'of'
21376
+ ? [n.right, n.left, true]
21377
+ : [undefined, undefined, false];
21378
+ const name = property?.name;
21379
+ if (!owner || typeof name !== 'string' || !/^[A-Za-z_$][\w$]*$/.test(name))
21380
+ return null;
21381
+ return { owner, name, isOf };
21382
+ }
20950
21383
 
20951
21384
  function toSetInput(target, value) {
20952
21385
  switch (target.kind) {
@@ -20968,10 +21401,18 @@ function toSetInput(target, value) {
20968
21401
  property: target.target.property,
20969
21402
  value,
20970
21403
  };
21404
+ case 'member':
21405
+ return {
21406
+ type: 'member-assignment',
21407
+ container: target.container,
21408
+ property: target.property,
21409
+ value,
21410
+ };
20971
21411
  default:
20972
21412
  throw new Error(`set: unrequested write-target rung '${target.kind}'`);
20973
21413
  }
20974
21414
  }
21415
+ const SET_RUNGS = { nodeWriters: true, styleSplit: true, ofAndObjectProperty: true };
20975
21416
  let SetCommand = (() => {
20976
21417
  let _classDecorators = [command({ name: 'set' })];
20977
21418
  let _classDescriptor;
@@ -20990,8 +21431,7 @@ let SetCommand = (() => {
20990
21431
  const argScope = firstArg?.scope;
20991
21432
  const writeTarget = await resolveWriteTarget(firstArg, evaluator, context, {
20992
21433
  scopeElements: () => this.resolveTargets(raw.modifiers.on, evaluator, context),
20993
- nodeWriters: true,
20994
- styleSplit: true,
21434
+ ...SET_RUNGS,
20995
21435
  });
20996
21436
  if (writeTarget) {
20997
21437
  return toSetInput(writeTarget, await this.extractValue(raw, evaluator, context));
@@ -21317,12 +21757,30 @@ function getNodeType(node) {
21317
21757
  return 'unknown';
21318
21758
  return node.type || 'unknown';
21319
21759
  }
21760
+ function propertyTarget(node) {
21761
+ const n = node;
21762
+ const owner = n.type === 'propertyOfExpression'
21763
+ ? n.target
21764
+ : n.type === 'possessiveExpression' ||
21765
+ n.type === 'memberExpression' ||
21766
+ n.type === 'propertyAccess'
21767
+ ? n.object
21768
+ : undefined;
21769
+ if (!owner || n.computed)
21770
+ return null;
21771
+ const p = n.property;
21772
+ const name = typeof p === 'string' ? p : typeof p?.name === 'string' ? p.name : undefined;
21773
+ if (!name || !/^[A-Za-z_$][\w$-]*$/.test(name))
21774
+ return null;
21775
+ return { owner, name };
21776
+ }
21320
21777
  async function parseNumericTargetInput(raw, evaluator, context, commandName) {
21321
21778
  if (!raw.args || raw.args.length === 0) {
21322
21779
  throw new Error(`${commandName} command requires a target`);
21323
21780
  }
21324
21781
  const targetArg = raw.args[0];
21325
21782
  let target;
21783
+ let property;
21326
21784
  let extractedScope;
21327
21785
  let customWrite;
21328
21786
  const nodeType = getNodeType(targetArg);
@@ -21337,7 +21795,25 @@ async function parseNumericTargetInput(raw, evaluator, context, commandName) {
21337
21795
  }
21338
21796
  else {
21339
21797
  const writer = getRegisteredNodeWriter(nodeType);
21340
- if (writer) {
21798
+ const attribute = writer
21799
+ ? null
21800
+ : await resolveAttributeWriteTarget(targetArg, evaluator, context, async () => context.me ? [context.me] : []);
21801
+ const prop = writer || attribute ? null : propertyTarget(targetArg);
21802
+ const owner = prop ? await evaluator.evaluate(prop.owner, context) : undefined;
21803
+ const ownerElement = Array.isArray(owner) ? owner[0] : owner;
21804
+ if (attribute?.elements[0]) {
21805
+ target = attribute.elements[0];
21806
+ property = `@${attribute.name}`;
21807
+ }
21808
+ else if (prop && isHTMLElement$2(ownerElement)) {
21809
+ target = ownerElement;
21810
+ property = prop.name;
21811
+ }
21812
+ else if (prop && isObjectOwner(owner)) {
21813
+ target = owner;
21814
+ property = prop.name;
21815
+ }
21816
+ else if (writer) {
21341
21817
  const currentRaw = await evaluator.evaluate(targetArg, context);
21342
21818
  const currentValue = typeof currentRaw === 'number'
21343
21819
  ? currentRaw
@@ -21375,24 +21851,22 @@ async function parseNumericTargetInput(raw, evaluator, context, commandName) {
21375
21851
  if (typeof evaluated === 'number') {
21376
21852
  amount = evaluated;
21377
21853
  }
21854
+ else if (evaluated == null) {
21855
+ amount = NaN;
21856
+ }
21378
21857
  }
21379
21858
  }
21380
21859
  const byModifier = raw.modifiers?.by;
21381
21860
  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
- }
21861
+ const value = getNodeType(byModifier) === 'literal'
21862
+ ? byModifier.value
21863
+ : await evaluator.evaluate(byModifier, context);
21864
+ amount = counterAmount(value);
21392
21865
  }
21393
21866
  return {
21394
21867
  target,
21395
21868
  amount,
21869
+ ...(property && { property }),
21396
21870
  ...(scope && { scope }),
21397
21871
  ...(customWrite && { customWrite }),
21398
21872
  };
@@ -21415,7 +21889,7 @@ let NumericModifyCommand = (() => {
21415
21889
  async execute(input, context) {
21416
21890
  const { target, property, scope, amount = 1, operation = 'increment', customWrite } = input;
21417
21891
  if (customWrite) {
21418
- const delta = isFinite(amount) ? amount : 1;
21892
+ const delta = amount;
21419
21893
  const newValue = operation === 'increment'
21420
21894
  ? customWrite.currentValue + delta
21421
21895
  : customWrite.currentValue - delta;
@@ -21429,8 +21903,7 @@ let NumericModifyCommand = (() => {
21429
21903
  newValue = NaN;
21430
21904
  }
21431
21905
  else {
21432
- const delta = isFinite(amount) ? amount : 1;
21433
- newValue = operation === 'increment' ? currentValue + delta : currentValue - delta;
21906
+ newValue = operation === 'increment' ? currentValue + amount : currentValue - amount;
21434
21907
  }
21435
21908
  setTargetValue(target, property, scope, newValue, context);
21436
21909
  Object.assign(context, { it: newValue });
@@ -22538,7 +23011,10 @@ function createUntilLoopConfig(conditionExpr, evaluateCondition, context, indexV
22538
23011
  };
22539
23012
  const config = {
22540
23013
  type: 'until',
22541
- shouldContinue: () => !evaluateCondition(conditionExpr, context),
23014
+ shouldContinue: () => {
23015
+ const met = evaluateCondition(conditionExpr, context);
23016
+ return met instanceof Promise ? met.then(value => !value) : !met;
23017
+ },
22542
23018
  };
22543
23019
  return { config, iterCtx };
22544
23020
  }
@@ -22574,7 +23050,7 @@ function createForeverLoopConfig(indexVariable, maxIterations = 10000) {
22574
23050
  return { config, iterCtx };
22575
23051
  }
22576
23052
 
22577
- async function slotText(node, evaluator, context) {
23053
+ async function slotText$1(node, evaluator, context) {
22578
23054
  if (!node)
22579
23055
  return undefined;
22580
23056
  const n = node;
@@ -22587,6 +23063,18 @@ async function slotText(node, evaluator, context) {
22587
23063
  const v = await evaluator.evaluate(node, context);
22588
23064
  return typeof v === 'string' ? v : undefined;
22589
23065
  }
23066
+ function isEventTarget(value) {
23067
+ return typeof value?.addEventListener === 'function';
23068
+ }
23069
+ async function evaluateLoopCondition(condition, context) {
23070
+ if (typeof condition === 'function')
23071
+ return Boolean(await condition());
23072
+ return evaluateCondition(condition, context);
23073
+ }
23074
+ function loopCount(value) {
23075
+ const count = Number(value);
23076
+ return Number.isNaN(count) ? 0 : count;
23077
+ }
22590
23078
  let RepeatCommand = (() => {
22591
23079
  let _classDecorators = [command({ name: 'repeat' })];
22592
23080
  let _classDescriptor;
@@ -22624,7 +23112,7 @@ let RepeatCommand = (() => {
22624
23112
  }
22625
23113
  }
22626
23114
  const m = raw.modifiers ?? {};
22627
- const text = (node) => slotText(node, evaluator, context);
23115
+ const text = (node) => slotText$1(node, evaluator, context);
22628
23116
  const loopType = (await text(m.loopType)) ?? null;
22629
23117
  const bottomTested = m.bottomTested
22630
23118
  ? (await evaluator.evaluate(m.bottomTested, context))
@@ -22644,17 +23132,24 @@ let RepeatCommand = (() => {
22644
23132
  };
22645
23133
  }
22646
23134
  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))
23135
+ if (!m.times)
22650
23136
  throw new Error('times loops require a count number');
23137
+ const count = loopCount(await evaluator.evaluate(m.times, context));
22651
23138
  return { type: 'times', count, indexVariable, commands, elseCommands };
22652
23139
  }
22653
23140
  if (loopType === 'while' || m.while) {
22654
23141
  const condition = m.while;
22655
23142
  if (!condition)
22656
23143
  throw new Error('while loops require a condition');
22657
- return { type: 'while', condition, indexVariable, commands, elseCommands, bottomTested };
23144
+ const check = () => evaluator.evaluate(condition, context);
23145
+ return {
23146
+ type: 'while',
23147
+ condition: check,
23148
+ indexVariable,
23149
+ commands,
23150
+ elseCommands,
23151
+ bottomTested,
23152
+ };
22658
23153
  }
22659
23154
  if (loopType === 'until-event' || m.event) {
22660
23155
  const eventName = await text(m.event);
@@ -22672,7 +23167,7 @@ let RepeatCommand = (() => {
22672
23167
  }
22673
23168
  else {
22674
23169
  const target = await evaluator.evaluate(m.from, context);
22675
- if (target instanceof EventTarget)
23170
+ if (isEventTarget(target))
22676
23171
  eventTarget = target;
22677
23172
  else if (target === 'document')
22678
23173
  eventTarget = document;
@@ -22693,7 +23188,15 @@ let RepeatCommand = (() => {
22693
23188
  const condition = m.until;
22694
23189
  if (!condition)
22695
23190
  throw new Error('until loops require a condition');
22696
- return { type: 'until', condition, indexVariable, commands, elseCommands, bottomTested };
23191
+ const check = () => evaluator.evaluate(condition, context);
23192
+ return {
23193
+ type: 'until',
23194
+ condition: check,
23195
+ indexVariable,
23196
+ commands,
23197
+ elseCommands,
23198
+ bottomTested,
23199
+ };
22697
23200
  }
22698
23201
  if (loopType === 'forever') {
22699
23202
  return { type: 'forever', indexVariable, commands, elseCommands };
@@ -22711,10 +23214,10 @@ let RepeatCommand = (() => {
22711
23214
  ({ config, iterCtx } = createTimesLoopConfig(count, indexVariable));
22712
23215
  break;
22713
23216
  case 'while':
22714
- ({ config, iterCtx } = createWhileLoopConfig(condition, evaluateCondition, context, indexVariable));
23217
+ ({ config, iterCtx } = createWhileLoopConfig(condition, evaluateLoopCondition, context, indexVariable));
22715
23218
  break;
22716
23219
  case 'until':
22717
- ({ config, iterCtx } = createUntilLoopConfig(condition, evaluateCondition, context, indexVariable));
23220
+ ({ config, iterCtx } = createUntilLoopConfig(condition, evaluateLoopCondition, context, indexVariable));
22718
23221
  break;
22719
23222
  case 'until-event':
22720
23223
  ({ config, iterCtx } = createUntilEventLoopConfig(eventName, eventTarget, indexVariable));
@@ -23745,6 +24248,29 @@ function namedPropertyOf(node) {
23745
24248
  }
23746
24249
  return null;
23747
24250
  }
24251
+ const RESTORE_KEYWORDS = new Set(['initial', 'inherit', 'unset', 'revert']);
24252
+ function elementsOf(value) {
24253
+ if (Array.isArray(value) || isNodeList(value)) {
24254
+ return Array.from(value).filter(isHTMLElement$2);
24255
+ }
24256
+ return isHTMLElement$2(value) ? [value] : [];
24257
+ }
24258
+ async function valueOf(node, evaluator, context) {
24259
+ const value = await evaluator.evaluate(node, context);
24260
+ const name = node.name;
24261
+ if (value === undefined && node.type === 'identifier' && typeof name === 'string')
24262
+ return name;
24263
+ return value;
24264
+ }
24265
+ function longestTransitionMs(css) {
24266
+ let longest;
24267
+ for (const item of css.split(',')) {
24268
+ const times = [...item.matchAll(/(\d*\.?\d+)(ms|s)\b/g)].map(m => parseFloat(m[1]) * (m[2] === 's' ? 1000 : 1));
24269
+ if (times.length > 0)
24270
+ longest = Math.max(longest ?? 0, times[0] + (times[1] ?? 0));
24271
+ }
24272
+ return longest;
24273
+ }
23748
24274
  let TransitionCommand = (() => {
23749
24275
  let _classDecorators = [command({ name: 'transition' })];
23750
24276
  let _classDescriptor;
@@ -23760,11 +24286,10 @@ let TransitionCommand = (() => {
23760
24286
  let property;
23761
24287
  let target;
23762
24288
  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) ||
24289
+ const owners = elementsOf(firstArg);
24290
+ if (owners.length > 0 ||
23766
24291
  (typeof firstArg === 'string' && /^[#.]|^(?:me|it|you)$/.test(firstArg))) {
23767
- target = firstAsElement;
24292
+ target = owners.length === 1 ? owners[0] : owners.length > 1 ? owners : firstArg;
23768
24293
  property = String(await evaluator.evaluate(raw.args[1], context));
23769
24294
  }
23770
24295
  else if (raw.args.length >= 2) {
@@ -23776,9 +24301,9 @@ let TransitionCommand = (() => {
23776
24301
  throw new Error('transition requires a CSS property');
23777
24302
  property = named.property;
23778
24303
  if (named.objectNode !== undefined) {
23779
- const owner = asElement(await evaluator.evaluate(named.objectNode, context));
23780
- if (isHTMLElement$2(owner))
23781
- target = owner;
24304
+ const owner = elementsOf(await evaluator.evaluate(named.objectNode, context));
24305
+ if (owner.length > 0)
24306
+ target = owner.length === 1 ? owner[0] : owner;
23782
24307
  }
23783
24308
  }
23784
24309
  else {
@@ -23789,60 +24314,131 @@ let TransitionCommand = (() => {
23789
24314
  }
23790
24315
  if (!raw.modifiers?.to)
23791
24316
  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 };
24317
+ const result = {
24318
+ property,
24319
+ value: await valueOf(raw.modifiers.to, evaluator, context),
24320
+ };
23797
24321
  if (target !== undefined)
23798
24322
  result.target = target;
24323
+ if (raw.modifiers?.from)
24324
+ result.from = await valueOf(raw.modifiers.from, evaluator, context);
24325
+ if (raw.modifiers?.pairs) {
24326
+ result.pairs = await this.laterPairs(raw.modifiers.pairs, evaluator, context);
24327
+ }
23799
24328
  if (raw.modifiers?.over)
23800
24329
  result.duration = await evaluator.evaluate(raw.modifiers.over, context);
23801
24330
  if (raw.modifiers?.with)
23802
24331
  result.timingFunction = String(await evaluator.evaluate(raw.modifiers.with, context));
24332
+ if (raw.modifiers?.using)
24333
+ result.using = String(await evaluator.evaluate(raw.modifiers.using, context));
23803
24334
  return result;
23804
24335
  }
24336
+ async laterPairs(node, evaluator, context) {
24337
+ const pairs = [];
24338
+ for (const pairNode of node.elements ?? []) {
24339
+ const fields = new Map();
24340
+ const properties = pairNode
24341
+ .properties ?? [];
24342
+ for (const { key, value } of properties) {
24343
+ if (typeof key.name === 'string')
24344
+ fields.set(key.name, value);
24345
+ }
24346
+ const propertyNode = fields.get('property');
24347
+ const toNode = fields.get('to');
24348
+ if (!propertyNode || !toNode)
24349
+ throw new Error('transition requires property and value');
24350
+ const pair = {
24351
+ property: String(await evaluator.evaluate(propertyNode, context)),
24352
+ value: await valueOf(toNode, evaluator, context),
24353
+ };
24354
+ const ownerNode = fields.get('owner');
24355
+ if (ownerNode) {
24356
+ const owners = elementsOf(await evaluator.evaluate(ownerNode, context));
24357
+ if (owners.length === 0)
24358
+ throw new Error('transition: target element not found');
24359
+ pair.target = owners.length === 1 ? owners[0] : owners;
24360
+ }
24361
+ const fromNode = fields.get('from');
24362
+ if (fromNode)
24363
+ pair.from = await valueOf(fromNode, evaluator, context);
24364
+ pairs.push(pair);
24365
+ }
24366
+ return pairs;
24367
+ }
23805
24368
  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);
24369
+ const duration = parseDuration(input.duration, (input.using !== undefined && longestTransitionMs(input.using)) || 300);
24370
+ const timing = input.timingFunction || 'ease';
24371
+ const jobs = [];
24372
+ for (const pair of [input, ...(input.pairs ?? [])]) {
24373
+ let property = pair.property;
24374
+ if (property.startsWith('*'))
24375
+ property = property.substring(1);
24376
+ property = camelToKebab(property);
24377
+ const elements = Array.isArray(pair.target)
24378
+ ? pair.target.filter(isHTMLElement$2)
24379
+ : [resolveElement(pair.target, context, 'transition')];
24380
+ for (const element of elements) {
24381
+ jobs.push({
24382
+ element,
24383
+ property,
24384
+ pair,
24385
+ fromValue: getComputedStyle(element).getPropertyValue(property),
24386
+ toValue: '',
24387
+ removeInlineAfter: false,
24388
+ });
24389
+ }
24390
+ }
24391
+ if (jobs.length === 0)
24392
+ throw new Error('transition: target element not found');
24393
+ for (const job of jobs) {
24394
+ if (job.pair.from !== undefined) {
24395
+ job.element.style.setProperty(job.property, String(job.pair.from));
24396
+ }
24397
+ }
24398
+ for (const job of jobs)
24399
+ if (job.pair.from !== undefined)
24400
+ void job.element.offsetWidth;
24401
+ const originals = new Map();
24402
+ for (const job of jobs) {
24403
+ if (!originals.has(job.element))
24404
+ originals.set(job.element, job.element.style.transition);
24405
+ }
24406
+ for (const [element, original] of originals) {
24407
+ const own = jobs
24408
+ .filter(job => job.element === element)
24409
+ .map(job => `${job.property} ${duration}ms ${timing}`);
24410
+ element.style.transition = input.using ?? [original, ...own].filter(Boolean).join(', ');
24411
+ }
24412
+ for (const job of jobs) {
24413
+ const { element, property } = job;
24414
+ let toValue = String(job.pair.value);
24415
+ if (RESTORE_KEYWORDS.has(toValue)) {
24416
+ const currentInline = element.style.getPropertyValue(property);
24417
+ element.style.removeProperty(property);
24418
+ toValue = getComputedStyle(element).getPropertyValue(property);
24419
+ if (currentInline) {
24420
+ element.style.setProperty(property, currentInline);
24421
+ }
24422
+ job.removeInlineAfter = true;
24423
+ }
24424
+ job.toValue = toValue;
24425
+ element.style.setProperty(property, toValue);
24426
+ }
24427
+ const results = await Promise.all(jobs.map(job => waitForTransitionEnd(job.element, job.property, duration)));
24428
+ for (const [element, original] of originals)
24429
+ element.style.transition = original;
24430
+ for (const job of jobs) {
24431
+ if (job.removeInlineAfter)
24432
+ job.element.style.removeProperty(job.property);
23838
24433
  }
24434
+ const first = jobs[0];
23839
24435
  return {
23840
- element: targetElement,
23841
- property,
23842
- fromValue,
23843
- toValue,
24436
+ element: first.element,
24437
+ property: first.property,
24438
+ fromValue: first.fromValue,
24439
+ toValue: first.toValue,
23844
24440
  duration,
23845
- completed: result.completed,
24441
+ completed: results.every(result => result.completed),
23846
24442
  };
23847
24443
  }
23848
24444
  };
@@ -23855,13 +24451,16 @@ let TransitionCommand = (() => {
23855
24451
  })();
23856
24452
  _classThis.metadata = commandMeta({
23857
24453
  description: 'Animate CSS properties using CSS transitions',
23858
- syntax: 'transition [<target>] <property> to <value> [over <duration>] [with <timing>]',
24454
+ syntax: 'transition <property> [from <value>] to <value> [<property> \u2026 to <value>]\u2026 [over <duration>] [using <css> | with <timing>]',
23859
24455
  examples: [
23860
24456
  'transition opacity to 0.5',
23861
24457
  'transition my *opacity to 0 over 200ms',
23862
24458
  "transition #box's *opacity to 0 over 200ms",
23863
24459
  'transition left to 100px over 500ms',
23864
24460
  'transition background-color to red over 1s with ease-in-out',
24461
+ 'transition *width to 100px *height to 50px over 300ms',
24462
+ 'transition *opacity of #panel from 0 to 1 over 200ms',
24463
+ 'transition my *opacity to 0 using "opacity 1s ease-in"',
23865
24464
  ],
23866
24465
  sideEffects: ['style-change', 'timing'],
23867
24466
  category: 'animation',
@@ -25111,17 +25710,7 @@ let BeepCommand = (() => {
25111
25710
  this.debugContext(context);
25112
25711
  return { expressionCount: 0, debugged: true, outputs: [] };
25113
25712
  }
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();
25713
+ const outputs = beepValues(context.me, expressions);
25125
25714
  return { expressionCount: expressions.length, debugged: true, outputs };
25126
25715
  }
25127
25716
  debugContext(context) {
@@ -25134,67 +25723,6 @@ let BeepCommand = (() => {
25134
25723
  console.log('variables:', context.variables);
25135
25724
  console.groupEnd();
25136
25725
  }
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
25726
  };
25199
25727
  __setFunctionName(_classThis, "BeepCommand");
25200
25728
  (() => {
@@ -25742,16 +26270,12 @@ class RenderCommand {
25742
26270
  }
25743
26271
  RenderCommand.metadata = commandMeta({
25744
26272
  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
- ],
26273
+ syntax: ['render <template>', 'render <template> with <name>: <value>, ...'],
25750
26274
  examples: [
25751
26275
  'render myTemplate',
25752
- 'render myTemplate with (name: "Alice")',
25753
- 'render "<template>Hello ${name}!</template>" with (name: "World")',
25754
- 'render template with (items: data)',
26276
+ 'render myTemplate with name: "Alice"',
26277
+ 'render "<template>Hello ${name}!</template>" with name: "World"',
26278
+ 'render template with items: data',
25755
26279
  ],
25756
26280
  category: 'templates',
25757
26281
  compatibility: 'standard',
@@ -25939,7 +26463,7 @@ function updateDebugStats(event) {
25939
26463
  stats.confidenceHistory.reduce((a, b) => a + b, 0) / stats.confidenceHistory.length;
25940
26464
  }
25941
26465
 
25942
- const VERSION = '3.1.1';
26466
+ const VERSION = '3.3.0';
25943
26467
 
25944
26468
  function createHistorySwap(config) {
25945
26469
  const { target, strategy = 'morph', useViewTransition = false, fetchOptions = {}, transformUrl, onBeforeFetch, onAfterSwap, onError, } = config;
@@ -27485,6 +28009,14 @@ function convertNode(node, infer) {
27485
28009
  ...pos(node),
27486
28010
  };
27487
28011
  case 'identifier':
28012
+ if (node.scope === 'element' || node.scope === 'local' || node.scope === 'global') {
28013
+ return {
28014
+ type: 'variable',
28015
+ name: (node.name ?? ''),
28016
+ scope: node.scope,
28017
+ ...pos(node),
28018
+ };
28019
+ }
27488
28020
  return {
27489
28021
  type: 'identifier',
27490
28022
  value: (node.name ?? node.value ?? ''),
@@ -27507,6 +28039,15 @@ function convertNode(node, infer) {
27507
28039
  operand: convertNode(node.argument ?? node.operand, infer),
27508
28040
  ...pos(node),
27509
28041
  };
28042
+ case 'typeCheckExpression':
28043
+ return {
28044
+ type: 'typeCheck',
28045
+ value: convertNode(node.value, infer),
28046
+ typeName: String(node.typeName),
28047
+ nullOk: node.nullOk !== false,
28048
+ negated: node.negated === true,
28049
+ ...pos(node),
28050
+ };
27510
28051
  case 'timeExpression':
27511
28052
  return { type: 'literal', value: node.value, ...pos(node) };
27512
28053
  case 'templateLiteral':
@@ -27518,6 +28059,28 @@ function convertNode(node, infer) {
27518
28059
  scope: (node.scope ?? 'local'),
27519
28060
  ...pos(node),
27520
28061
  };
28062
+ case 'arrayLiteral':
28063
+ return {
28064
+ type: 'array',
28065
+ elements: (node.elements ?? []).map(element => convertNode(element, infer)),
28066
+ ...pos(node),
28067
+ };
28068
+ case 'attributeAccess':
28069
+ return {
28070
+ type: 'possessive',
28071
+ object: { type: 'identifier', value: 'me' },
28072
+ property: `@${node.attributeName}`,
28073
+ ...pos(node),
28074
+ };
28075
+ case 'propertyOfExpression': {
28076
+ const property = node.property;
28077
+ return {
28078
+ type: 'possessive',
28079
+ object: convertNode(node.target, infer),
28080
+ property: (property?.name ?? property?.value ?? ''),
28081
+ ...pos(node),
28082
+ };
28083
+ }
27521
28084
  case 'htmlSelector':
27522
28085
  return {
27523
28086
  type: 'selector',
@@ -27569,6 +28132,21 @@ function convertCommand$1(node, infer) {
27569
28132
  if (name === 'repeat') {
27570
28133
  return convertRepeatCommand(node, infer);
27571
28134
  }
28135
+ const original = node.originalCommand;
28136
+ const sum = node.modifiers?.to;
28137
+ const counted = node.args?.[0];
28138
+ if (name === 'set' &&
28139
+ (original === 'increment' || original === 'decrement') &&
28140
+ sum?.type === 'binaryExpression' &&
28141
+ counted) {
28142
+ return convertCommand$1({
28143
+ ...node,
28144
+ name: original,
28145
+ args: [counted],
28146
+ modifiers: { by: sum.right },
28147
+ originalCommand: undefined,
28148
+ }, infer);
28149
+ }
27572
28150
  const args = (node.args ?? []).map(arg => convertNode(arg, infer));
27573
28151
  const target = node.target ? convertNode(node.target, infer) : undefined;
27574
28152
  const modifiers = node.modifiers
@@ -27617,6 +28195,64 @@ function convertIfCommand(node, infer) {
27617
28195
  };
27618
28196
  }
27619
28197
  function convertRepeatCommand(node, infer) {
28198
+ const slots = (node.modifiers ?? {});
28199
+ const form = slotText(slots.loopType);
28200
+ if (form === undefined)
28201
+ return convertPositionalRepeat(node, infer);
28202
+ const [bodyBlock, elseBlock] = (node.args ?? []);
28203
+ const body = extractBlockCommands(bodyBlock, infer);
28204
+ const indexName = slotText(slots.index);
28205
+ const shared = {
28206
+ ...(indexName ? { indexName } : {}),
28207
+ ...(elseBlock ? { elseBody: extractBlockCommands(elseBlock, infer) } : {}),
28208
+ ...pos(node),
28209
+ };
28210
+ const operand = (slot) => (slot ? convertNode(slot, infer) : undefined);
28211
+ switch (form) {
28212
+ case 'for':
28213
+ return {
28214
+ type: 'foreach',
28215
+ itemName: slotText(slots.for) ?? 'it',
28216
+ collection: operand(slots.in) ?? { type: 'literal', value: null },
28217
+ body,
28218
+ ...shared,
28219
+ };
28220
+ case 'times':
28221
+ return { type: 'repeat', count: operand(slots.times), body, ...shared };
28222
+ case 'while':
28223
+ case 'until': {
28224
+ const test = operand(slots[form]) ?? { type: 'literal', value: form === 'until' };
28225
+ return {
28226
+ type: 'while',
28227
+ condition: form === 'until' ? { type: 'unary', operator: 'not', operand: test } : test,
28228
+ ...(slots.bottomTested?.value === true ? { bottomTested: true } : {}),
28229
+ body,
28230
+ ...shared,
28231
+ };
28232
+ }
28233
+ case 'until-event': {
28234
+ const event = slotText(slots.event);
28235
+ if (!event) {
28236
+ return { type: 'error', message: "'repeat until event' has no event name", ...pos(node) };
28237
+ }
28238
+ const target = operand(slots.from);
28239
+ return {
28240
+ type: 'repeat',
28241
+ untilEvent: event,
28242
+ ...(target ? { untilEventTarget: target } : {}),
28243
+ body,
28244
+ ...shared,
28245
+ };
28246
+ }
28247
+ default:
28248
+ return { type: 'repeat', body, ...shared };
28249
+ }
28250
+ }
28251
+ function slotText(slot) {
28252
+ const text = slot?.value ?? slot?.name;
28253
+ return typeof text === 'string' ? text : undefined;
28254
+ }
28255
+ function convertPositionalRepeat(node, infer) {
27620
28256
  const args = (node.args ?? []);
27621
28257
  if (args.length === 0) {
27622
28258
  return { type: 'repeat', body: [], ...pos(node) };
@@ -27933,6 +28569,15 @@ function toCoreAST(node) {
27933
28569
  prefix: true,
27934
28570
  ...nodePos(node),
27935
28571
  };
28572
+ case 'typeCheck':
28573
+ return {
28574
+ type: 'typeCheckExpression',
28575
+ value: toCoreAST(node.value),
28576
+ typeName: node.typeName,
28577
+ nullOk: node.nullOk,
28578
+ negated: node.negated,
28579
+ ...nodePos(node),
28580
+ };
27936
28581
  case 'member':
27937
28582
  return {
27938
28583
  type: 'memberExpression',
@@ -27943,6 +28588,12 @@ function toCoreAST(node) {
27943
28588
  computed: node.computed ?? false,
27944
28589
  ...nodePos(node),
27945
28590
  };
28591
+ case 'array':
28592
+ return {
28593
+ type: 'arrayLiteral',
28594
+ elements: node.elements.map(element => toCoreAST(element)),
28595
+ ...nodePos(node),
28596
+ };
27946
28597
  case 'possessive':
27947
28598
  return {
27948
28599
  type: 'possessiveExpression',
@@ -28005,72 +28656,85 @@ function convertCommand(node) {
28005
28656
  }
28006
28657
  return result;
28007
28658
  }
28659
+ function block(nodes) {
28660
+ return { type: 'block', commands: nodes.map(n => toCoreAST(n)), ...POS };
28661
+ }
28008
28662
  function convertIf(node) {
28009
- const result = {
28663
+ const [elseIf, ...laterElseIfs] = node.elseIfBranches ?? [];
28664
+ const elseBranch = elseIf
28665
+ ? [
28666
+ {
28667
+ type: 'if',
28668
+ condition: elseIf.condition,
28669
+ thenBranch: elseIf.body,
28670
+ elseIfBranches: laterElseIfs,
28671
+ ...(node.elseBranch ? { elseBranch: node.elseBranch } : {}),
28672
+ },
28673
+ ]
28674
+ : node.elseBranch;
28675
+ return {
28010
28676
  type: 'command',
28011
28677
  name: 'if',
28012
28678
  isBlocking: true,
28013
- condition: toCoreAST(node.condition),
28014
- thenBranch: node.thenBranch.map(n => toCoreAST(n)),
28015
- args: [],
28679
+ args: [
28680
+ toCoreAST(node.condition),
28681
+ block(node.thenBranch),
28682
+ ...(elseBranch ? [block(elseBranch)] : []),
28683
+ ],
28016
28684
  ...nodePos(node),
28017
28685
  };
28018
- if (node.elseBranch) {
28019
- result.elseBranch = node.elseBranch.map(n => toCoreAST(n));
28020
- }
28021
- return result;
28022
28686
  }
28023
- function convertRepeat(node) {
28024
- const result = {
28687
+ function loopCommand(node, form, operands) {
28688
+ const modifiers = { loopType: slotString(form) };
28689
+ for (const [slot, value] of Object.entries(operands)) {
28690
+ if (value)
28691
+ modifiers[slot] = value;
28692
+ }
28693
+ if (node.indexName)
28694
+ modifiers.index = slotString(node.indexName);
28695
+ return {
28025
28696
  type: 'command',
28026
28697
  name: 'repeat',
28027
28698
  isBlocking: true,
28028
- body: node.body.map(n => toCoreAST(n)),
28029
- args: [],
28699
+ args: [block(node.body), ...(node.elseBody ? [block(node.elseBody)] : [])],
28700
+ modifiers,
28030
28701
  ...nodePos(node),
28031
28702
  };
28703
+ }
28704
+ function slotString(value) {
28705
+ return { type: 'string', value, ...POS };
28706
+ }
28707
+ function convertRepeat(node) {
28032
28708
  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';
28709
+ const times = typeof node.count === 'number'
28710
+ ? { type: 'literal', value: node.count, raw: String(node.count), ...POS }
28711
+ : toCoreAST(node.count);
28712
+ return loopCommand(node, 'times', { times });
28713
+ }
28714
+ if (node.whileCondition !== undefined) {
28715
+ return loopCommand(node, 'while', { while: toCoreAST(node.whileCondition) });
28716
+ }
28717
+ if (node.untilEvent !== undefined) {
28718
+ return loopCommand(node, 'until-event', {
28719
+ event: slotString(node.untilEvent),
28720
+ from: node.untilEventTarget ? toCoreAST(node.untilEventTarget) : undefined,
28721
+ });
28046
28722
  }
28047
- return result;
28723
+ return loopCommand(node, 'forever', {});
28048
28724
  }
28049
28725
  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
- };
28726
+ return loopCommand(node, 'for', {
28727
+ for: slotString(node.itemName),
28728
+ in: toCoreAST(node.collection),
28729
+ });
28062
28730
  }
28063
28731
  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
- };
28732
+ return loopCommand(node, 'while', {
28733
+ while: toCoreAST(node.condition),
28734
+ bottomTested: node.bottomTested
28735
+ ? { type: 'literal', value: true, raw: 'true', ...POS }
28736
+ : undefined,
28737
+ });
28074
28738
  }
28075
28739
  function flattenEventModifiers(modifiers) {
28076
28740
  if (!modifiers)