@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
@@ -537,6 +537,75 @@ function parseAttributeWithValue(expression) {
537
537
  return { name: result.name, value: result.value ?? '' };
538
538
  }
539
539
 
540
+ function _parseDurationComponents(value) {
541
+ const trimmed = value.trim();
542
+ const match = trimmed.match(/^(\d+(?:\.\d+)?)\s*(s|ms|sec|seconds?|milliseconds?)?$/i);
543
+ if (!match) {
544
+ return null;
545
+ }
546
+ const number = parseFloat(match[1]);
547
+ const unit = (match[2] || 'ms').toLowerCase();
548
+ if (unit === 'ms' || unit === 'millisecond' || unit === 'milliseconds') {
549
+ return { number, milliseconds: Math.floor(number) };
550
+ }
551
+ else if (unit === 's' || unit === 'sec' || unit === 'second' || unit === 'seconds') {
552
+ return { number, milliseconds: Math.floor(number * 1000) };
553
+ }
554
+ return null;
555
+ }
556
+ function parseDuration(value, defaultMs = 300) {
557
+ if (typeof value === 'number') {
558
+ return Math.max(0, Math.floor(value));
559
+ }
560
+ if (typeof value === 'string') {
561
+ const result = _parseDurationComponents(value);
562
+ return result ? result.milliseconds : defaultMs;
563
+ }
564
+ return defaultMs;
565
+ }
566
+ function parseDurationStrict(value) {
567
+ if (typeof value === 'number') {
568
+ if (value < 0)
569
+ throw new Error('Duration must be >= 0');
570
+ return Math.floor(value);
571
+ }
572
+ if (typeof value === 'string') {
573
+ const result = _parseDurationComponents(value);
574
+ if (!result) {
575
+ throw new Error(`Invalid duration format: "${value}"`);
576
+ }
577
+ return result.milliseconds;
578
+ }
579
+ throw new Error(`Invalid duration type: ${typeof value}`);
580
+ }
581
+ function parseCSSDurations(durationString) {
582
+ if (!durationString || durationString === 'none') {
583
+ return [0];
584
+ }
585
+ return durationString.split(',').map(duration => {
586
+ const value = parseFloat(duration.trim());
587
+ if (isNaN(value))
588
+ return 0;
589
+ if (duration.includes('s') && !duration.includes('ms')) {
590
+ return value * 1000;
591
+ }
592
+ return value;
593
+ });
594
+ }
595
+ function calculateMaxAnimationTime(durations, delays) {
596
+ let maxTime = 0;
597
+ for (let i = 0; i < durations.length; i++) {
598
+ const duration = durations[i] || 0;
599
+ const delay = delays[i] || 0;
600
+ const totalTime = duration + delay;
601
+ maxTime = Math.max(maxTime, totalTime);
602
+ }
603
+ return maxTime;
604
+ }
605
+ function camelToKebab(str) {
606
+ return str.replace(/([A-Z])/g, '-$1').toLowerCase();
607
+ }
608
+
540
609
  function batchApply(targets, operation) {
541
610
  for (const element of targets) {
542
611
  operation(element);
@@ -602,15 +671,18 @@ function toggleAttribute(element, name, value) {
602
671
  }
603
672
  }
604
673
  }
674
+ function toCssPropertyName(property) {
675
+ return property.startsWith('--') ? property : camelToKebab(property);
676
+ }
605
677
  function batchSetStyles(targets, styles) {
606
678
  const entries = Object.entries(styles);
607
679
  return batchApplyItems(targets, entries, (element, [property, value]) => {
608
- element.style.setProperty(property, value);
680
+ element.style.setProperty(toCssPropertyName(property), value);
609
681
  });
610
682
  }
611
683
  function batchRemoveStyles(targets, properties) {
612
684
  return batchApplyItems(targets, properties, (element, property) => {
613
- element.style.removeProperty(property);
685
+ element.style.removeProperty(toCssPropertyName(property));
614
686
  });
615
687
  }
616
688
 
@@ -1345,75 +1417,6 @@ let RemoveCommand = (() => {
1345
1417
  })();
1346
1418
  const createRemoveCommand = createFactory(RemoveCommand);
1347
1419
 
1348
- function _parseDurationComponents(value) {
1349
- const trimmed = value.trim();
1350
- const match = trimmed.match(/^(\d+(?:\.\d+)?)\s*(s|ms|sec|seconds?|milliseconds?)?$/i);
1351
- if (!match) {
1352
- return null;
1353
- }
1354
- const number = parseFloat(match[1]);
1355
- const unit = (match[2] || 'ms').toLowerCase();
1356
- if (unit === 'ms' || unit === 'millisecond' || unit === 'milliseconds') {
1357
- return { number, milliseconds: Math.floor(number) };
1358
- }
1359
- else if (unit === 's' || unit === 'sec' || unit === 'second' || unit === 'seconds') {
1360
- return { number, milliseconds: Math.floor(number * 1000) };
1361
- }
1362
- return null;
1363
- }
1364
- function parseDuration(value, defaultMs = 300) {
1365
- if (typeof value === 'number') {
1366
- return Math.max(0, Math.floor(value));
1367
- }
1368
- if (typeof value === 'string') {
1369
- const result = _parseDurationComponents(value);
1370
- return result ? result.milliseconds : defaultMs;
1371
- }
1372
- return defaultMs;
1373
- }
1374
- function parseDurationStrict(value) {
1375
- if (typeof value === 'number') {
1376
- if (value < 0)
1377
- throw new Error('Duration must be >= 0');
1378
- return Math.floor(value);
1379
- }
1380
- if (typeof value === 'string') {
1381
- const result = _parseDurationComponents(value);
1382
- if (!result) {
1383
- throw new Error(`Invalid duration format: "${value}"`);
1384
- }
1385
- return result.milliseconds;
1386
- }
1387
- throw new Error(`Invalid duration type: ${typeof value}`);
1388
- }
1389
- function parseCSSDurations(durationString) {
1390
- if (!durationString || durationString === 'none') {
1391
- return [0];
1392
- }
1393
- return durationString.split(',').map(duration => {
1394
- const value = parseFloat(duration.trim());
1395
- if (isNaN(value))
1396
- return 0;
1397
- if (duration.includes('s') && !duration.includes('ms')) {
1398
- return value * 1000;
1399
- }
1400
- return value;
1401
- });
1402
- }
1403
- function calculateMaxAnimationTime(durations, delays) {
1404
- let maxTime = 0;
1405
- for (let i = 0; i < durations.length; i++) {
1406
- const duration = durations[i] || 0;
1407
- const delay = delays[i] || 0;
1408
- const totalTime = duration + delay;
1409
- maxTime = Math.max(maxTime, totalTime);
1410
- }
1411
- return maxTime;
1412
- }
1413
- function camelToKebab(str) {
1414
- return str.replace(/([A-Z])/g, '-$1').toLowerCase();
1415
- }
1416
-
1417
1420
  function detectSmartElementType(elements) {
1418
1421
  if (elements.length === 0)
1419
1422
  return null;
@@ -1879,13 +1882,6 @@ function isFunction(value) {
1879
1882
  function isInputElement$1(el) {
1880
1883
  return (el !== null && typeof el === 'object' && 'tagName' in el && el.tagName === 'INPUT');
1881
1884
  }
1882
- function isFormElement$1(el) {
1883
- if (el === null || typeof el !== 'object' || !('tagName' in el)) {
1884
- return false;
1885
- }
1886
- const tag = el.tagName;
1887
- return tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA';
1888
- }
1889
1885
  function isHTMLElement$1(el) {
1890
1886
  return (el !== null &&
1891
1887
  typeof el === 'object' &&
@@ -1945,7 +1941,7 @@ const SPECIAL_DOM_PROPERTIES = {
1945
1941
  innertext: el => el.textContent?.trim(),
1946
1942
  innerHTML: el => el.innerHTML,
1947
1943
  outerhtml: el => el.outerHTML,
1948
- value: el => (isFormElement$1(el) ? el.value : undefined),
1944
+ value: el => ('value' in el ? el.value : undefined),
1949
1945
  checked: el => (isInputElement$1(el) ? el.checked : undefined),
1950
1946
  disabled: el => ('disabled' in el ? el.disabled : undefined),
1951
1947
  selected: el => (isOptionElement$1(el) ? el.selected : undefined),
@@ -2815,7 +2811,11 @@ let PutCommand = (() => {
2815
2811
  }
2816
2812
  if (isInsertableNode(v))
2817
2813
  return v;
2818
- return v == null ? '' : String(v);
2814
+ if (v == null)
2815
+ return 'null';
2816
+ if (Array.isArray(v))
2817
+ return arrayFragment(v);
2818
+ return String(v);
2819
2819
  }
2820
2820
  looksLikeCss(s) {
2821
2821
  if (!s)
@@ -2900,6 +2900,19 @@ let PutCommand = (() => {
2900
2900
  })();
2901
2901
  return PutCommand = _classThis;
2902
2902
  })();
2903
+ function arrayFragment(items) {
2904
+ const fragment = document.createDocumentFragment();
2905
+ for (const item of items) {
2906
+ if (isInsertableNode(item)) {
2907
+ fragment.append(item);
2908
+ continue;
2909
+ }
2910
+ const template = document.createElement('template');
2911
+ template.innerHTML = item === null ? '' : String(item);
2912
+ fragment.append(template.content);
2913
+ }
2914
+ return fragment;
2915
+ }
2903
2916
  const createPutCommand = createFactory(PutCommand);
2904
2917
 
2905
2918
  const GLOBAL_WRITE_HOOKS = new Set();
@@ -3022,6 +3035,16 @@ function convertToNumber(value) {
3022
3035
  }
3023
3036
  return 0;
3024
3037
  }
3038
+ function counterAmount(value) {
3039
+ return typeof value === 'number' ? value : parseFloat(String(value));
3040
+ }
3041
+ function counterValue(value) {
3042
+ if (isHTMLElement$2(value)) {
3043
+ const element = value;
3044
+ return counterValue(element.value || element.textContent);
3045
+ }
3046
+ return value ? counterAmount(value) : 0;
3047
+ }
3025
3048
  function getVariableValue(name, context, preferredScope) {
3026
3049
  if (preferredScope === 'element') {
3027
3050
  return getElementVar(context, name);
@@ -3126,35 +3149,39 @@ function setElementPropertyValue(propertyPath, value, context) {
3126
3149
  element[property] = value;
3127
3150
  }
3128
3151
  }
3152
+ function isObjectOwner(value) {
3153
+ return (typeof value === 'object' && value !== null && !Array.isArray(value) && !isHTMLElement$2(value));
3154
+ }
3129
3155
  function getCurrentNumericValue(target, property, scope, context) {
3130
3156
  if (typeof target === 'number') {
3131
3157
  return target;
3132
3158
  }
3133
3159
  if (isHTMLElement$2(target)) {
3134
3160
  const element = target;
3161
+ if (property?.startsWith('@'))
3162
+ return counterValue(element.getAttribute(property.slice(1)));
3135
3163
  if (property) {
3136
3164
  if (property.startsWith('data-') ||
3137
3165
  ['id', 'class', 'title', 'alt', 'src', 'href'].includes(property)) {
3138
- const value = element.getAttribute(property);
3139
- return convertToNumber(value);
3166
+ return counterValue(element.getAttribute(property));
3140
3167
  }
3141
3168
  else {
3142
- const value = element[property];
3143
- return convertToNumber(value);
3169
+ return counterValue(element[property]);
3144
3170
  }
3145
3171
  }
3146
3172
  else {
3147
- const value = element.value || element.textContent;
3148
- return convertToNumber(value);
3173
+ return counterValue(element);
3149
3174
  }
3150
3175
  }
3176
+ if (property && isObjectOwner(target)) {
3177
+ return counterValue(target[property]);
3178
+ }
3151
3179
  if (typeof target === 'string') {
3152
3180
  if (scope === 'element') {
3153
- return convertToNumber(getVariableValue(target, context, 'element'));
3181
+ return counterValue(getVariableValue(target, context, 'element'));
3154
3182
  }
3155
3183
  if (scope === 'global') {
3156
- const value = getVariableValue(target, context, 'global');
3157
- return convertToNumber(value);
3184
+ return counterValue(getVariableValue(target, context, 'global'));
3158
3185
  }
3159
3186
  if (target.includes('.')) {
3160
3187
  return getElementPropertyValue(target, context);
@@ -3168,14 +3195,17 @@ function getCurrentNumericValue(target, property, scope, context) {
3168
3195
  else if (target === 'you' && context.you) {
3169
3196
  return convertToNumber(context.you.value || 0);
3170
3197
  }
3171
- const value = getVariableValue(target, context);
3172
- return convertToNumber(value);
3198
+ return counterValue(getVariableValue(target, context));
3173
3199
  }
3174
- return convertToNumber(target);
3200
+ return counterValue(target);
3175
3201
  }
3176
3202
  function setTargetValue(target, property, scope, newValue, context) {
3177
3203
  if (isHTMLElement$2(target)) {
3178
3204
  const element = target;
3205
+ if (property?.startsWith('@')) {
3206
+ element.setAttribute(property.slice(1), String(newValue));
3207
+ return;
3208
+ }
3179
3209
  if (property) {
3180
3210
  if (property.startsWith('data-') ||
3181
3211
  ['id', 'class', 'title', 'alt', 'src', 'href'].includes(property)) {
@@ -3195,6 +3225,10 @@ function setTargetValue(target, property, scope, newValue, context) {
3195
3225
  }
3196
3226
  return;
3197
3227
  }
3228
+ if (property && isObjectOwner(target)) {
3229
+ target[property] = newValue;
3230
+ return;
3231
+ }
3198
3232
  if (typeof target === 'string') {
3199
3233
  if (scope === 'element') {
3200
3234
  setVariableValue(target, newValue, context, 'element');
@@ -3969,6 +4003,39 @@ function makeTypeCheckHandler(negated) {
3969
4003
  };
3970
4004
  };
3971
4005
  }
4006
+ const OF_OPERAND_BP = 70;
4007
+ function parseOfOperand(ctx, minBp = OF_OPERAND_BP) {
4008
+ let right = ctx.parseExpr(minBp);
4009
+ if (ctx.peek()?.value === 'in') {
4010
+ const inToken = ctx.advance();
4011
+ const scope = ctx.parseExpr(minBp);
4012
+ right = {
4013
+ type: 'binaryExpression',
4014
+ operator: 'in',
4015
+ left: right,
4016
+ right: scope,
4017
+ start: right.start,
4018
+ end: scope.end ?? inToken.end,
4019
+ line: right.line ?? inToken.line,
4020
+ column: right.column ?? inToken.column,
4021
+ };
4022
+ }
4023
+ return right;
4024
+ }
4025
+ const THE_OF_TARGET_BP = OF_OPERAND_BP + 1;
4026
+ function ofHandler(left, token, ctx) {
4027
+ const right = parseOfOperand(ctx);
4028
+ return {
4029
+ type: 'binaryExpression',
4030
+ operator: token.value,
4031
+ left,
4032
+ right,
4033
+ start: left.start,
4034
+ end: right.end ?? token.end,
4035
+ line: left.line ?? token.line,
4036
+ column: left.column ?? token.column,
4037
+ };
4038
+ }
3972
4039
  const PARSER_COMPARISON_FRAGMENT = new Map([
3973
4040
  ['===', leftAssoc(30)],
3974
4041
  ['!==', leftAssoc(30)],
@@ -4013,7 +4080,7 @@ const PARSER_COMPARISON_FRAGMENT = new Map([
4013
4080
  ['is greater than or equal to', leftAssoc(30)],
4014
4081
  ['is less than or equal to', leftAssoc(30)],
4015
4082
  ['in', leftAssoc(30)],
4016
- ['of', rightAssoc(30)],
4083
+ ['of', { infix: { bp: [90, OF_OPERAND_BP], handler: ofHandler } }],
4017
4084
  ['really', leftAssoc(30)],
4018
4085
  [
4019
4086
  'exists',
@@ -4104,6 +4171,89 @@ function getRegisteredNodeWriter(type) {
4104
4171
  return NODE_WRITERS.get(type);
4105
4172
  }
4106
4173
 
4174
+ function beepValues(me, values) {
4175
+ const outputs = values.map(describeBeepValue);
4176
+ const shown = outputs.filter(output => announce(me, output.value));
4177
+ if (shown.length === 0)
4178
+ return outputs;
4179
+ console.group('\ud83d\udd14 beep! Debug Output');
4180
+ for (const output of shown) {
4181
+ console.log(`Value:`, output.value);
4182
+ console.log(`Type:`, output.type);
4183
+ console.log(`Representation:`, output.representation);
4184
+ console.log('---');
4185
+ }
4186
+ console.groupEnd();
4187
+ return outputs;
4188
+ }
4189
+ function announce(me, value) {
4190
+ const target = me;
4191
+ if (typeof CustomEvent !== 'function' || typeof target?.dispatchEvent !== 'function')
4192
+ return true;
4193
+ return target.dispatchEvent(new CustomEvent('hyperscript:beep', {
4194
+ bubbles: true,
4195
+ cancelable: true,
4196
+ detail: { element: me, value },
4197
+ }));
4198
+ }
4199
+ function describeBeepValue(value) {
4200
+ return { value, type: typeOf(value), representation: representationOf(value) };
4201
+ }
4202
+ function typeOf(value) {
4203
+ if (value === null)
4204
+ return 'null';
4205
+ if (value === undefined)
4206
+ return 'undefined';
4207
+ if (Array.isArray(value))
4208
+ return 'array';
4209
+ if (isHTMLElement$2(value))
4210
+ return 'HTMLElement';
4211
+ if (typeof Element !== 'undefined' && value instanceof Element)
4212
+ return 'Element';
4213
+ if (typeof Node !== 'undefined' && value instanceof Node)
4214
+ return 'Node';
4215
+ if (value instanceof Error)
4216
+ return 'Error';
4217
+ if (value instanceof Date)
4218
+ return 'Date';
4219
+ if (value instanceof RegExp)
4220
+ return 'RegExp';
4221
+ return typeof value;
4222
+ }
4223
+ function representationOf(value) {
4224
+ if (value === null)
4225
+ return 'null';
4226
+ if (value === undefined)
4227
+ return 'undefined';
4228
+ if (Array.isArray(value)) {
4229
+ return `Array(${value.length}) [${value
4230
+ .slice(0, 3)
4231
+ .map(v => representationOf(v))
4232
+ .join(', ')}${value.length > 3 ? '...' : ''}]`;
4233
+ }
4234
+ if (isHTMLElement$2(value)) {
4235
+ const el = value;
4236
+ const tag = el.tagName.toLowerCase();
4237
+ const id = el.id ? `#${el.id}` : '';
4238
+ const classes = el.className ? `.${el.className.split(' ').join('.')}` : '';
4239
+ return `<${tag}${id}${classes}/>`;
4240
+ }
4241
+ if (value instanceof Error)
4242
+ return `Error: ${value.message}`;
4243
+ if (typeof value === 'string')
4244
+ return value.length > 50 ? `"${value.substring(0, 47)}..."` : `"${value}"`;
4245
+ if (typeof value === 'object') {
4246
+ try {
4247
+ const keys = Object.keys(value);
4248
+ return `Object {${keys.slice(0, 3).join(', ')}${keys.length > 3 ? '...' : ''}}`;
4249
+ }
4250
+ catch {
4251
+ return '[Object]';
4252
+ }
4253
+ }
4254
+ return String(value);
4255
+ }
4256
+
4107
4257
  const NUMBER_WORDS = {
4108
4258
  1: 'one',
4109
4259
  2: 'two',
@@ -4705,14 +4855,15 @@ const closestExpression = {
4705
4855
  if (typeof selector !== 'string') {
4706
4856
  throw new Error('closest requires a string selector');
4707
4857
  }
4708
- if (!context.me) {
4858
+ const start = args.length > 1 ? args[1] : context.me;
4859
+ if (!(start instanceof Element)) {
4709
4860
  return null;
4710
4861
  }
4711
- return context.me.closest(selector);
4862
+ return start.closest(selector);
4712
4863
  },
4713
4864
  validate(args) {
4714
- if (args.length !== 1) {
4715
- return 'closest requires exactly 1 argument (selector)';
4865
+ if (args.length !== 1 && args.length !== 2) {
4866
+ return 'closest requires a selector and, optionally, the element to start from';
4716
4867
  }
4717
4868
  const arg = args[0];
4718
4869
  if (typeof arg === 'string')
@@ -7098,7 +7249,7 @@ const isEmptyExpression = {
7098
7249
  else if (value instanceof Node || value instanceof Element)
7099
7250
  result = false;
7100
7251
  else if (isObject(value))
7101
- result = Object.keys(value).length === 0;
7252
+ result = value.length === 0;
7102
7253
  else
7103
7254
  result = false;
7104
7255
  return result;
@@ -8175,6 +8326,48 @@ function parseMaybeNamedArgument(ctx) {
8175
8326
  const value = ctx.parseExpression();
8176
8327
  return name !== undefined ? { name, value } : { value };
8177
8328
  }
8329
+ function isNakedNamedArgStart(ctx) {
8330
+ if (ctx.check('{'))
8331
+ return false;
8332
+ if (!ctx.checkIdentifierLike())
8333
+ return false;
8334
+ const next = ctx.peekAt(1);
8335
+ return next !== null && next.value === ':';
8336
+ }
8337
+ function parseNakedNamedArgs(ctx, parseValue, command) {
8338
+ const properties = [];
8339
+ const startPos = ctx.getPosition();
8340
+ do {
8341
+ if (!ctx.checkIdentifierLike())
8342
+ break;
8343
+ const keyToken = ctx.advance();
8344
+ const key = {
8345
+ type: 'identifier',
8346
+ name: keyToken.value,
8347
+ start: keyToken.start,
8348
+ end: keyToken.end,
8349
+ line: keyToken.line,
8350
+ column: keyToken.column,
8351
+ };
8352
+ ctx.consume(':', `Expected ':' after property name in ${command} named arguments`);
8353
+ const value = parseValue();
8354
+ if (value) {
8355
+ properties.push({ key, value });
8356
+ }
8357
+ } while (ctx.match(',') && !ctx.isAtEnd());
8358
+ const endPos = ctx.getPosition();
8359
+ return {
8360
+ type: 'objectLiteral',
8361
+ properties,
8362
+ start: startPos.start,
8363
+ end: endPos.end,
8364
+ line: startPos.line,
8365
+ column: startPos.column,
8366
+ };
8367
+ }
8368
+ function isGluedBeepBang(beep, bang) {
8369
+ return beep?.value === 'beep' && bang?.value === '!' && bang.start === beep.end;
8370
+ }
8178
8371
 
8179
8372
  class CommandNodeBuilder {
8180
8373
  constructor(name) {
@@ -8376,6 +8569,8 @@ function parseTriggerCommand(ctx, identifierNode) {
8376
8569
  const modifiers = {};
8377
8570
  while (!isCommandBoundary(ctx)) {
8378
8571
  if (ctx.check(KEYWORDS.ON) || ctx.check(KEYWORDS.TO)) {
8572
+ if (modifiers['on'])
8573
+ break;
8379
8574
  ctx.advance();
8380
8575
  const target = parseOneArgument(ctx, [KEYWORDS.WITH]);
8381
8576
  if (target)
@@ -8516,6 +8711,9 @@ function parseRepeatCommand(ctx, commandToken) {
8516
8711
  ctx.advance();
8517
8712
  loopType = KEYWORDS.FOREVER;
8518
8713
  }
8714
+ else if (ctx.checkIsCommand() || ctx.isCommand(ctx.peek().value)) {
8715
+ loopType = KEYWORDS.FOREVER;
8716
+ }
8519
8717
  else {
8520
8718
  times = ctx.parseExpression();
8521
8719
  if (ctx.check(KEYWORDS.TIMES)) {
@@ -8558,10 +8756,12 @@ function parseRepeatCommand(ctx, commandToken) {
8558
8756
  loopType = result.terminator;
8559
8757
  ctx.advance();
8560
8758
  condition = ctx.parseExpression();
8561
- if (!ctx.check('end')) {
8759
+ if (ctx.check('end')) {
8760
+ ctx.advance();
8761
+ }
8762
+ else if (!ctx.atEndOfInput()) {
8562
8763
  throw new Error('Expected "end" to close repeat block');
8563
8764
  }
8564
- ctx.advance();
8565
8765
  }
8566
8766
  }
8567
8767
  else {
@@ -8807,7 +9007,7 @@ function parseIfCommand(ctx, commandToken) {
8807
9007
  }));
8808
9008
  }
8809
9009
  }
8810
- if (!consumedElseIf && !ctx.isAtEnd()) {
9010
+ if (!consumedElseIf && !ctx.atEndOfInput()) {
8811
9011
  ctx.consume(KEYWORDS.END, "Expected 'end' after if block");
8812
9012
  }
8813
9013
  }
@@ -8832,7 +9032,6 @@ function parseIfCommand(ctx, commandToken) {
8832
9032
  .build();
8833
9033
  }
8834
9034
  function parseForCommand(ctx, commandToken) {
8835
- const args = [];
8836
9035
  let variable = null;
8837
9036
  let collection = null;
8838
9037
  if (ctx.check(KEYWORDS.EACH)) {
@@ -8875,31 +9074,22 @@ function parseForCommand(ctx, commandToken) {
8875
9074
  }
8876
9075
  }
8877
9076
  const commands = ctx.parseCommandListUntilEnd('for');
8878
- args.push({
8879
- type: 'identifier',
8880
- name: 'for',
8881
- start: commandToken.start,
8882
- end: commandToken.end,
8883
- line: commandToken.line,
8884
- column: commandToken.column,
8885
- });
8886
- const forPos = {
9077
+ const pos = {
8887
9078
  start: commandToken.start,
8888
9079
  end: commandToken.end,
8889
9080
  line: commandToken.line,
8890
9081
  column: commandToken.column,
8891
9082
  };
8892
- args.push(createStringLiteral(variable, forPos));
8893
- args.push(collection);
8894
- if (indexVariable) {
8895
- args.push(createStringLiteral(indexVariable, forPos));
8896
- }
8897
- args.push(createBlock(commands, { ...forPos, end: forPos.end || 0 }));
8898
- return CommandNodeBuilder.from({
8899
- ...commandToken,
8900
- value: 'repeat',
8901
- })
8902
- .withArgs(...args)
9083
+ const modifiers = {
9084
+ loopType: toLegacyExpression(createStringLiteral(KEYWORDS.FOR, pos)),
9085
+ for: toLegacyExpression(createStringLiteral(variable, pos)),
9086
+ in: collection,
9087
+ };
9088
+ if (indexVariable)
9089
+ modifiers['index'] = toLegacyExpression(createStringLiteral(indexVariable, pos));
9090
+ return CommandNodeBuilder.from({ ...commandToken, value: 'repeat' })
9091
+ .withArgs(createBlock(commands, { ...pos, end: pos.end || 0 }))
9092
+ .withModifiers(modifiers)
8903
9093
  .endingAt(ctx.getPosition())
8904
9094
  .build();
8905
9095
  }
@@ -8955,86 +9145,159 @@ function parseMeasureCommand(ctx, identifierNode) {
8955
9145
  }
8956
9146
  return builder.build();
8957
9147
  }
8958
- const CONTEXT_POSSESSIVES = new Set(['my', 'its', 'your']);
9148
+ const CONTEXT_REFS = new Set(['me', 'it', 'you', 'result']);
8959
9149
  function parseTransitionCommand(ctx, commandToken) {
8960
- const args = [];
8961
- const modifiers = {};
8962
- let property = null;
8963
- let target = null;
8964
- const leadToken = ctx.peek();
8965
- const startsTarget = ctx.checkAnySelector() ||
8966
- ctx.checkContextVar() ||
8967
- ctx.check('<') ||
8968
- (isIdentifierLike(leadToken) && CONTEXT_POSSESSIVES.has(leadToken.value));
8969
- if (startsTarget) {
8970
- const parsed = ctx.parsePrimary();
8971
- if ((parsed.type === 'possessiveExpression' ||
8972
- (parsed.type === 'memberExpression' && !parsed.computed)) &&
8973
- parsed.object &&
8974
- parsed.property?.name) {
8975
- target = parsed.object;
8976
- property = {
8977
- type: 'string',
8978
- value: parsed.property.name,
8979
- start: leadToken.start || 0,
8980
- end: ctx.getPosition().end,
8981
- line: leadToken.line,
8982
- column: leadToken.column,
8983
- };
9150
+ const pairs = [];
9151
+ do {
9152
+ pairs.push(parseTransitionPair(ctx));
9153
+ } while (startsAnotherPair(ctx));
9154
+ const [first, ...rest] = pairs;
9155
+ const args = first.owner ? [first.owner, first.property] : [first.property];
9156
+ const modifiers = { to: first.to };
9157
+ if (first.from)
9158
+ modifiers['from'] = first.from;
9159
+ if (rest.length > 0)
9160
+ modifiers['pairs'] = pairsNode(rest);
9161
+ for (let i = 0; i < 3 && !ctx.isAtEnd(); i++) {
9162
+ if (ctx.check('over') && !modifiers['over']) {
9163
+ ctx.advance();
9164
+ modifiers['over'] = ctx.parseExpression();
8984
9165
  }
8985
- else {
8986
- target = parsed;
8987
- if (ctx.check("'s"))
8988
- ctx.advance();
9166
+ else if (ctx.check('using') && !modifiers['using']) {
9167
+ ctx.advance();
9168
+ modifiers['using'] = ctx.parseExpression();
8989
9169
  }
8990
- }
8991
- const firstToken = ctx.peek();
8992
- if (!property && (isIdentifierLike(firstToken) || firstToken.value === '*')) {
8993
- let propertyValue = '';
8994
- if (ctx.check('*')) {
8995
- propertyValue = '*';
9170
+ else if (ctx.check(KEYWORDS.WITH) && !modifiers['with']) {
8996
9171
  ctx.advance();
9172
+ modifiers['with'] = ctx.parsePrimary();
8997
9173
  }
8998
- const hyphenatedName = parseHyphenatedName(ctx);
8999
- if (hyphenatedName) {
9000
- propertyValue += hyphenatedName;
9001
- property = {
9002
- type: 'string',
9003
- value: propertyValue,
9004
- start: firstToken.start || 0,
9005
- end: ctx.getPosition().end,
9006
- line: firstToken.line,
9007
- column: firstToken.column,
9008
- };
9174
+ else {
9175
+ break;
9009
9176
  }
9010
9177
  }
9011
- if (!property) {
9012
- throw new Error('Transition command requires a CSS property');
9178
+ return CommandNodeBuilder.from(commandToken)
9179
+ .withArgs(...args)
9180
+ .withModifiers(modifiers)
9181
+ .endingAt(ctx.getPosition())
9182
+ .build();
9183
+ }
9184
+ function parseTransitionPair(ctx) {
9185
+ const { owner, property } = parseTransitionProperty(ctx);
9186
+ let from = null;
9187
+ if (ctx.check('from')) {
9188
+ ctx.advance();
9189
+ from = ctx.parseExpression();
9013
9190
  }
9014
- if (target)
9015
- args.push(target);
9016
- args.push(property);
9017
9191
  if (!ctx.check(KEYWORDS.TO)) {
9018
9192
  throw new Error('Expected "to" keyword after property in transition command');
9019
9193
  }
9020
9194
  ctx.advance();
9021
- const value = ctx.parseExpression();
9022
- modifiers['to'] = value;
9023
- if (ctx.check('over')) {
9024
- ctx.advance();
9025
- const duration = ctx.parseExpression();
9026
- modifiers['over'] = duration;
9195
+ const to = ctx.parseExpression();
9196
+ return { owner, property, from, to };
9197
+ }
9198
+ function parseTransitionProperty(ctx) {
9199
+ const lead = ctx.peek();
9200
+ if (!lead || isKeyword(lead, [KEYWORDS.TO, 'from'])) {
9201
+ throw new Error('Transition command requires a CSS property');
9027
9202
  }
9028
- if (ctx.check(KEYWORDS.WITH)) {
9203
+ const expr = ctx.parseExpression();
9204
+ const split = splitStyleExpression(expr);
9205
+ if (split)
9206
+ return { owner: split.owner, property: propertyNode(split.name, lead, ctx) };
9207
+ let name = '';
9208
+ if (ctx.check('*')) {
9209
+ name = '*';
9029
9210
  ctx.advance();
9030
- const timingFunction = ctx.parsePrimary();
9031
- modifiers['with'] = timingFunction;
9032
9211
  }
9033
- return CommandNodeBuilder.from(commandToken)
9034
- .withArgs(...args)
9035
- .withModifiers(modifiers)
9036
- .endingAt(ctx.getPosition())
9037
- .build();
9212
+ const hyphenated = parseHyphenatedName(ctx);
9213
+ if (!hyphenated)
9214
+ throw new Error('Transition command requires a CSS property');
9215
+ return { owner: expr, property: propertyNode(name + hyphenated, lead, ctx) };
9216
+ }
9217
+ function splitStyleExpression(node) {
9218
+ const n = node;
9219
+ switch (n.type) {
9220
+ case 'selector':
9221
+ return typeof n.value === 'string' && n.value.startsWith('*')
9222
+ ? { owner: null, name: n.value }
9223
+ : null;
9224
+ case 'identifier':
9225
+ return typeof n.name === 'string' && !CONTEXT_REFS.has(n.name)
9226
+ ? { owner: null, name: n.name }
9227
+ : null;
9228
+ case 'possessiveExpression':
9229
+ case 'memberExpression':
9230
+ return !n.computed && n.object && typeof n.property?.name === 'string'
9231
+ ? { owner: n.object, name: n.property.name }
9232
+ : null;
9233
+ case 'binaryExpression': {
9234
+ if (n.operator !== 'of' || !n.left || !n.right)
9235
+ return null;
9236
+ const inner = splitStyleExpression(n.left);
9237
+ return inner && !inner.owner ? { owner: n.right, name: inner.name } : null;
9238
+ }
9239
+ case 'propertyOfExpression':
9240
+ return n.target && typeof n.property?.name === 'string'
9241
+ ? { owner: n.target, name: n.property.name }
9242
+ : null;
9243
+ default:
9244
+ return null;
9245
+ }
9246
+ }
9247
+ function propertyNode(name, lead, ctx) {
9248
+ return {
9249
+ type: 'string',
9250
+ value: name,
9251
+ start: lead.start || 0,
9252
+ end: ctx.getPosition().end,
9253
+ line: lead.line,
9254
+ column: lead.column,
9255
+ };
9256
+ }
9257
+ function startsAnotherPair(ctx) {
9258
+ if (ctx.isAtEnd() || isComment(ctx.peek()))
9259
+ return false;
9260
+ if (isKeyword(ctx.peek(), ['over', 'using', KEYWORDS.WITH]))
9261
+ return false;
9262
+ return !isCommandBoundary(ctx, ['catch', 'finally', 'on']);
9263
+ }
9264
+ function pairsNode(pairs) {
9265
+ const field = (name, value) => ({
9266
+ key: {
9267
+ type: 'identifier',
9268
+ name,
9269
+ start: value.start,
9270
+ end: value.end,
9271
+ line: value.line,
9272
+ column: value.column,
9273
+ },
9274
+ value,
9275
+ });
9276
+ const elements = pairs.map(pair => {
9277
+ const properties = [field('property', pair.property), field('to', pair.to)];
9278
+ if (pair.owner)
9279
+ properties.push(field('owner', pair.owner));
9280
+ if (pair.from)
9281
+ properties.push(field('from', pair.from));
9282
+ return {
9283
+ type: 'objectLiteral',
9284
+ properties,
9285
+ start: (pair.owner ?? pair.property).start,
9286
+ end: pair.to.end,
9287
+ line: (pair.owner ?? pair.property).line,
9288
+ column: (pair.owner ?? pair.property).column,
9289
+ };
9290
+ });
9291
+ const firstEl = elements[0];
9292
+ const lastEl = elements[elements.length - 1];
9293
+ return {
9294
+ type: 'arrayLiteral',
9295
+ elements,
9296
+ start: firstEl.start,
9297
+ end: lastEl.end,
9298
+ line: firstEl.line,
9299
+ column: firstEl.column,
9300
+ };
9038
9301
  }
9039
9302
  function parseStartCommand(ctx, identifierNode) {
9040
9303
  if (!ctx.match('view')) {
@@ -9154,6 +9417,15 @@ function parseToggleCommand(ctx, identifierNode) {
9154
9417
  args.push(classArg.expression);
9155
9418
  modifiers['as'] = literalModifier(mode);
9156
9419
  }
9420
+ else if (classArg?.type === 'binaryExpression' &&
9421
+ classArg.operator === 'of' &&
9422
+ classArg.left?.type === 'selector' &&
9423
+ typeof classArg.left.value === 'string' &&
9424
+ classArg.left.value.startsWith('*') &&
9425
+ classArg.right) {
9426
+ args.push(classArg.left);
9427
+ modifiers['on'] = classArg.right;
9428
+ }
9157
9429
  else if (classArg) {
9158
9430
  args.push(classArg);
9159
9431
  }
@@ -9331,11 +9603,17 @@ function parseSwapCommand(ctx, identifierNode) {
9331
9603
  const targetExpr = isDelete ? ctx.parseExpression() : parseOneArgument(ctx, [KEYWORDS.WITH]);
9332
9604
  if (targetExpr)
9333
9605
  args.push(targetExpr);
9334
- if (!isDelete && consumeOptionalKeyword(ctx, KEYWORDS.WITH)) {
9606
+ const isMorph = identifierNode.name === 'morph';
9607
+ if (!isDelete &&
9608
+ (consumeOptionalKeyword(ctx, KEYWORDS.WITH) ||
9609
+ (isMorph && consumeOptionalKeyword(ctx, KEYWORDS.TO)))) {
9335
9610
  const contentExpr = parseOneArgument(ctx, ['using']);
9336
9611
  if (contentExpr)
9337
9612
  modifiers['with'] = contentExpr;
9338
9613
  }
9614
+ if (isMorph && modifiers['with'] === undefined) {
9615
+ ctx.addError("Expected 'to' and the content to morph into");
9616
+ }
9339
9617
  return CommandNodeBuilder.fromIdentifier(identifierNode)
9340
9618
  .withArgs(...args)
9341
9619
  .withModifiers({ ...modifiers, ...parseViewTransitionTail(ctx) })
@@ -10096,6 +10374,8 @@ function atArgumentBoundary(ctx, grammar) {
10096
10374
  const next = ctx.peek();
10097
10375
  if (ctx.checkIsCommand() && ctx.peekAt(1)?.value === '(')
10098
10376
  return false;
10377
+ if (isGluedBeepBang(next, ctx.peekAt(1)))
10378
+ return false;
10099
10379
  if (isCommandBoundary(ctx, ['catch', 'finally', 'on']))
10100
10380
  return true;
10101
10381
  return isKeyword(next, [...grammar.markers]);
@@ -10127,10 +10407,13 @@ function parseDeclaredCommand(ctx, commandToken, name, grammar) {
10127
10407
  }
10128
10408
  }
10129
10409
  const commaList = new Set(grammar.commaList ?? []);
10410
+ const namedArgs = new Set(grammar.namedArgs ?? []);
10130
10411
  while (!ctx.isAtEnd() && isKeyword(ctx.peek(), [...grammar.markers])) {
10131
10412
  const keyword = ctx.advance().value;
10132
10413
  const key = ctx.resolveKeyword(keyword).toLowerCase();
10133
- let value = ctx.parseExpression();
10414
+ let value = namedArgs.has(key) && isNakedNamedArgStart(ctx)
10415
+ ? parseNakedNamedArgs(ctx, () => ctx.parseExpression(), name)
10416
+ : ctx.parseExpression();
10134
10417
  if (value && commaList.has(key) && ctx.check(',')) {
10135
10418
  const elements = [value];
10136
10419
  while (ctx.match(',')) {
@@ -10295,8 +10578,8 @@ function parseFetchCommand(ctx, commandToken) {
10295
10578
  }
10296
10579
  if (ctx.check('with') && !modifiers['with']) {
10297
10580
  ctx.advance();
10298
- if (isFetchNakedNamedArgStart(ctx)) {
10299
- modifiers['with'] = parseFetchNakedNamedArgs(ctx);
10581
+ if (isNakedNamedArgStart(ctx)) {
10582
+ modifiers['with'] = parseNakedNamedArgs(ctx, () => ctx.parsePrimary(), 'fetch');
10300
10583
  }
10301
10584
  else {
10302
10585
  modifiers['with'] = ctx.parsePrimary();
@@ -10331,45 +10614,6 @@ function parseFetchCommand(ctx, commandToken) {
10331
10614
  }
10332
10615
  return builder.build();
10333
10616
  }
10334
- function isFetchNakedNamedArgStart(ctx) {
10335
- if (ctx.check('{'))
10336
- return false;
10337
- if (!ctx.checkIdentifierLike())
10338
- return false;
10339
- const next = ctx.peekAt(1);
10340
- return next !== null && next.value === ':';
10341
- }
10342
- function parseFetchNakedNamedArgs(ctx) {
10343
- const properties = [];
10344
- const startPos = ctx.getPosition();
10345
- do {
10346
- if (!ctx.checkIdentifierLike())
10347
- break;
10348
- const keyToken = ctx.advance();
10349
- const key = {
10350
- type: 'identifier',
10351
- name: keyToken.value,
10352
- start: keyToken.start,
10353
- end: keyToken.end,
10354
- line: keyToken.line,
10355
- column: keyToken.column,
10356
- };
10357
- ctx.consume(':', "Expected ':' after property name in fetch named arguments");
10358
- const value = ctx.parsePrimary();
10359
- if (value) {
10360
- properties.push({ key, value });
10361
- }
10362
- } while (ctx.match(',') && !ctx.isAtEnd());
10363
- const endPos = ctx.getPosition();
10364
- return {
10365
- type: 'objectLiteral',
10366
- properties,
10367
- start: startPos.start,
10368
- end: endPos.end,
10369
- line: startPos.line,
10370
- column: startPos.column,
10371
- };
10372
- }
10373
10617
  function findJsEndBoundary(ctx, startPos) {
10374
10618
  const input = ctx.getInputSlice(startPos);
10375
10619
  if (!input) {
@@ -10489,6 +10733,7 @@ function parseTellCommand(ctx, identifierNode) {
10489
10733
  const commands = [];
10490
10734
  while (!ctx.isAtEnd()) {
10491
10735
  if (ctx.checkIsCommand()) {
10736
+ const word = ctx.peek().value;
10492
10737
  try {
10493
10738
  ctx.advance();
10494
10739
  const cmd = ctx.parseCommand();
@@ -10499,7 +10744,8 @@ function parseTellCommand(ctx, identifierNode) {
10499
10744
  break;
10500
10745
  }
10501
10746
  }
10502
- catch {
10747
+ catch (error) {
10748
+ ctx.addError(`'${word}' in a tell body failed to parse: ${error instanceof Error ? error.message : String(error)}`);
10503
10749
  break;
10504
10750
  }
10505
10751
  if (ctx.match(KEYWORDS.AND) || ctx.match(KEYWORDS.THEN)) {
@@ -10648,7 +10894,6 @@ const ONE_EXPR = (syntax) => ({
10648
10894
  markers: [],
10649
10895
  syntax,
10650
10896
  });
10651
- const BEEP = ONE_EXPR('beep! [<expression>, \u2026]');
10652
10897
  const COMMAND_GRAMMAR = {
10653
10898
  append: { positional: 'expression', markers: ['to'], syntax: 'append <value> [to <target>]' },
10654
10899
  prepend: { positional: 'expression', markers: ['to'], syntax: 'prepend <value> [to <target>]' },
@@ -10659,8 +10904,7 @@ const COMMAND_GRAMMAR = {
10659
10904
  syntax: 'make (a|an) <type> [from <args>] [called <name>]',
10660
10905
  },
10661
10906
  throw: { positional: 'primary', markers: [], syntax: 'throw <error>' },
10662
- 'beep!': BEEP,
10663
- beep: BEEP,
10907
+ beep: ONE_EXPR('beep! [<expression>, \u2026]'),
10664
10908
  blur: ONE_EXPR('blur [<target>]'),
10665
10909
  break: NONE,
10666
10910
  breakpoint: NONE,
@@ -10684,7 +10928,8 @@ const COMMAND_GRAMMAR = {
10684
10928
  render: {
10685
10929
  positional: 'expression',
10686
10930
  markers: ['with'],
10687
- syntax: 'render <template> [with <variables>]',
10931
+ namedArgs: ['with'],
10932
+ syntax: 'render <template> [with <name>: <value>, \u2026]',
10688
10933
  },
10689
10934
  reset: ONE_EXPR('reset [<form>]'),
10690
10935
  return: ONE_EXPR('return [<value>]'),
@@ -11039,7 +11284,10 @@ class Parser {
11039
11284
  const firstToken = this.peek();
11040
11285
  const prefixEntry = Parser.PRATT_TABLE.get(firstToken.value);
11041
11286
  let left;
11042
- if (prefixEntry?.prefix) {
11287
+ if (isGluedBeepBang(firstToken, this.tokens[this.current + 1])) {
11288
+ left = this.parseBeepExpression();
11289
+ }
11290
+ else if (prefixEntry?.prefix) {
11043
11291
  const token = this.advance();
11044
11292
  if (this.isAtEnd()) {
11045
11293
  this.addError(`Expected expression after '${token.value}' operator`);
@@ -11059,6 +11307,9 @@ class Parser {
11059
11307
  continue;
11060
11308
  }
11061
11309
  }
11310
+ if (nextToken.value === '*' && this.isSpacedStyleRef(this.current)) {
11311
+ break;
11312
+ }
11062
11313
  const infixEntry = Parser.PRATT_TABLE.get(nextToken.value);
11063
11314
  if (!infixEntry?.infix) {
11064
11315
  if (STOP_TOKENS.has(nextToken.value) || STOP_DELIMITERS.has(nextToken.value)) {
@@ -11120,6 +11371,35 @@ class Parser {
11120
11371
  this.prattCache.set(cacheKey, { node: left, endPos: this.current });
11121
11372
  return left;
11122
11373
  }
11374
+ parseBeepExpression() {
11375
+ const beepToken = this.advance();
11376
+ this.advance();
11377
+ if (this.isAtEnd()) {
11378
+ this.addError("Expected expression after 'beep!'");
11379
+ return this.createErrorNode();
11380
+ }
11381
+ const operand = this.parseExpressionPratt(Parser.BEEP_OPERAND_BP);
11382
+ return {
11383
+ type: 'unaryExpression',
11384
+ operator: 'beep!',
11385
+ operand,
11386
+ argument: operand,
11387
+ prefix: true,
11388
+ start: beepToken.start,
11389
+ end: this.previous().end,
11390
+ line: beepToken.line,
11391
+ column: beepToken.column,
11392
+ };
11393
+ }
11394
+ isStyleRefStar(i) {
11395
+ const star = this.tokens[i];
11396
+ const name = this.tokens[i + 1];
11397
+ return star?.value === '*' && !!name && name.start === star.end && isIdentifierLike(name);
11398
+ }
11399
+ isSpacedStyleRef(i) {
11400
+ const prev = this.tokens[i - 1];
11401
+ return this.isStyleRefStar(i) && !!prev && prev.end < this.tokens[i].start;
11402
+ }
11123
11403
  tryParseStringPostfix(left, nextToken) {
11124
11404
  const v = nextToken.value;
11125
11405
  let unit = null;
@@ -11322,6 +11602,10 @@ class Parser {
11322
11602
  const isStop = () => this.check('end') || extraStops.some(s => this.check(s));
11323
11603
  debug.parse('\ud83d\udd04 parseCommandListUntilTerminator: Starting (extraStops:', extraStops.join(','), ')');
11324
11604
  while (!this.isAtEnd() && !isStop()) {
11605
+ if (this.checkComment()) {
11606
+ this.advance();
11607
+ continue;
11608
+ }
11325
11609
  debug.parse('\ud83d\udccd Loop iteration, current token:', this.peek().value, 'kind:', this.peek().kind);
11326
11610
  let parsedCommand = false;
11327
11611
  const isCommandToken = this.checkIsCommand();
@@ -11405,7 +11689,7 @@ class Parser {
11405
11689
  debug.parse('\u2705 Found "end", consuming it');
11406
11690
  this.advance();
11407
11691
  }
11408
- else {
11692
+ else if (!this.atEndOfInput()) {
11409
11693
  debug.parse('\u274c ERROR: Expected "end" but got:', this.peek().value, 'at position:', this.peek().start);
11410
11694
  throw new Error(`Expected "end" to close ${construct} block`);
11411
11695
  }
@@ -11418,7 +11702,7 @@ class Parser {
11418
11702
  if (this.check('end')) {
11419
11703
  this.advance();
11420
11704
  }
11421
- else {
11705
+ else if (!this.atEndOfInput()) {
11422
11706
  throw new Error('Expected "end" to close repeat block');
11423
11707
  }
11424
11708
  }
@@ -11434,7 +11718,10 @@ class Parser {
11434
11718
  this.advance();
11435
11719
  }
11436
11720
  else if (terminator === '') {
11437
- throw new Error('Expected "end", "else", "until", or "while" to close repeat block');
11721
+ if (!this.atEndOfInput()) {
11722
+ throw new Error('Expected "end", "else", "until", or "while" to close repeat block');
11723
+ }
11724
+ return { commands, terminator: 'end' };
11438
11725
  }
11439
11726
  return { commands, terminator };
11440
11727
  }
@@ -12018,7 +12305,9 @@ class Parser {
12018
12305
  parseTopLevelInitBlock() {
12019
12306
  const pos = this.getPosition();
12020
12307
  const initCommands = this.parseCommandBlock(['end']);
12021
- this.consume('end', "Expected 'end' after init block");
12308
+ if (!this.match('end') && !this.atEndOfInput() && !this.checkFeatureStart()) {
12309
+ this.consume('end', "Expected 'end' after init block");
12310
+ }
12022
12311
  return {
12023
12312
  type: 'initBlock',
12024
12313
  commands: initCommands,
@@ -12194,10 +12483,29 @@ class Parser {
12194
12483
  ...(sourceExpr.column !== undefined && { column: sourceExpr.column }),
12195
12484
  };
12196
12485
  }
12486
+ isArgumentListOnly(open) {
12487
+ let depth = 0;
12488
+ for (let i = open; i < this.tokens.length; i++) {
12489
+ const token = this.tokens[i];
12490
+ if (token.kind !== 'operator')
12491
+ continue;
12492
+ if (token.value === '(' || token.value === '[' || token.value === '{')
12493
+ depth++;
12494
+ else if (token.value === ')' || token.value === ']' || token.value === '}') {
12495
+ if (--depth === 0)
12496
+ return i === open + 1;
12497
+ }
12498
+ else if (token.value === ',' && depth === 1)
12499
+ return true;
12500
+ }
12501
+ return false;
12502
+ }
12197
12503
  tryParsePseudoCommand(callExpr, sourceExpr) {
12198
12504
  const methodName = callExpr.callee.name;
12199
12505
  const nextToken = this.peek();
12200
- const hasPseudoCommandPattern = Parser.PSEUDO_COMMAND_PREPOSITIONS.includes(nextToken.value.toLowerCase()) ||
12506
+ const nextIsArticle = nextToken.value.toLowerCase() === 'the';
12507
+ const hasPseudoCommandPattern = nextIsArticle ||
12508
+ Parser.PSEUDO_COMMAND_PREPOSITIONS.includes(nextToken.value.toLowerCase()) ||
12201
12509
  (isIdentifier(nextToken) && !this.isCommand(nextToken.value)) ||
12202
12510
  isContextVar(nextToken);
12203
12511
  if (!hasPseudoCommandPattern) {
@@ -12223,7 +12531,10 @@ class Parser {
12223
12531
  });
12224
12532
  }
12225
12533
  let preposition;
12226
- if (Parser.PSEUDO_COMMAND_PREPOSITIONS.includes(this.peek().value.toLowerCase())) {
12534
+ if (nextIsArticle) {
12535
+ this.advance();
12536
+ }
12537
+ else if (Parser.PSEUDO_COMMAND_PREPOSITIONS.includes(this.peek().value.toLowerCase())) {
12227
12538
  preposition = this.advance().value.toLowerCase();
12228
12539
  }
12229
12540
  let targetExpr;
@@ -12387,10 +12698,22 @@ class Parser {
12387
12698
  this.consume(']', "Expected ']' after event condition");
12388
12699
  }
12389
12700
  let target;
12701
+ let targetExpression;
12390
12702
  if (this.match('from')) {
12391
- const targetToken = this.advance();
12392
- target = targetToken.value;
12393
- debug.parse(`\ud83d\udd27 parseEventHandler: Parsed 'from' target: ${target} (kind: ${targetToken.kind})`);
12703
+ if (this.check('the'))
12704
+ this.advance();
12705
+ if (Parser.POSITIONAL_SOURCE_HEADS.has(this.peek().value.toLowerCase())) {
12706
+ const startIndex = this.current;
12707
+ targetExpression = this.parseExpression();
12708
+ target = this.tokens
12709
+ .slice(startIndex, this.current)
12710
+ .map(t => t.value)
12711
+ .join(' ');
12712
+ }
12713
+ else {
12714
+ target = this.advance().value;
12715
+ }
12716
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed 'from' target: ${target}`);
12394
12717
  this.parseTemporalKeywordModifiers(modifiers);
12395
12718
  }
12396
12719
  let attributeName;
@@ -12436,7 +12759,7 @@ class Parser {
12436
12759
  const nextIsOpenParen = this.tokens[this.current + 1]?.value === '(';
12437
12760
  const commandName = this.peek().value.toLowerCase();
12438
12761
  const isSpecialBodyCommand = commandName === 'js' || commandName === 'tell';
12439
- if (nextIsOpenParen && !isSpecialBodyCommand) {
12762
+ if (nextIsOpenParen && !isSpecialBodyCommand && this.isArgumentListOnly(this.current + 1)) {
12440
12763
  let expr;
12441
12764
  const savedError = this.error;
12442
12765
  try {
@@ -12468,7 +12791,7 @@ class Parser {
12468
12791
  }
12469
12792
  }
12470
12793
  }
12471
- else if (this.checkIdentifier()) {
12794
+ else if (this.checkIdentifier() || this.checkEventNamedCall()) {
12472
12795
  const token = this.peek();
12473
12796
  if (this.isCommand(token.value)) {
12474
12797
  const cmd = this.parseCommandWithErrorRecovery();
@@ -12577,6 +12900,7 @@ class Parser {
12577
12900
  ...(eventParams.length > 0 && { args: eventParams }),
12578
12901
  ...(condition && { condition: fromLegacyExpression(condition) }),
12579
12902
  ...(target && { target }),
12903
+ ...(targetExpression && { targetExpression: fromLegacyExpression(targetExpression) }),
12580
12904
  ...(attributeName && { attributeName }),
12581
12905
  ...(watchTarget && { watchTarget: fromLegacyExpression(watchTarget) }),
12582
12906
  ...(customEventSource && { customEventSource }),
@@ -12832,10 +13156,9 @@ class Parser {
12832
13156
  }
12833
13157
  parseCommandCore() {
12834
13158
  const commandToken = this.previous();
12835
- let commandName = commandToken.value;
13159
+ const commandName = commandToken.value;
12836
13160
  if (commandName === 'beep' && this.check('!')) {
12837
13161
  this.advance();
12838
- commandName = 'beep!';
12839
13162
  }
12840
13163
  if (commandName === 'fetch') {
12841
13164
  return parseFetchCommand(this.getContext(), commandToken);
@@ -12934,6 +13257,9 @@ class Parser {
12934
13257
  args.push(this.parsePrimary());
12935
13258
  }
12936
13259
  const callNode = this.createCallExpression(createIdentifier(funcName, namePos), args);
13260
+ if (funcName === 'closest' && args.length > 0 && this.match('to')) {
13261
+ callNode.closestTo = this.parseExpression();
13262
+ }
12937
13263
  if (funcName === 'next' || funcName === 'previous') {
12938
13264
  const relativePositional = this.parseRelativePositionalModifiers();
12939
13265
  if (relativePositional) {
@@ -12973,6 +13299,9 @@ class Parser {
12973
13299
  }
12974
13300
  parseTheXofY() {
12975
13301
  const positionalKeywords = ['first', 'last', 'next', 'previous', 'random', 'closest'];
13302
+ if (this.isStyleRefStar(this.current)) {
13303
+ return this.parsePrimary();
13304
+ }
12976
13305
  if (!this.checkIdentifier()) {
12977
13306
  return this.createIdentifier('the');
12978
13307
  }
@@ -12988,7 +13317,7 @@ class Parser {
12988
13317
  return this.createIdentifier(propertyName);
12989
13318
  }
12990
13319
  this.advance();
12991
- const target = this.parseCall();
13320
+ const target = parseOfOperand(this.makePrattContext(), THE_OF_TARGET_BP);
12992
13321
  return {
12993
13322
  type: 'propertyOfExpression',
12994
13323
  property: { type: 'identifier', name: propertyName },
@@ -13186,6 +13515,13 @@ class Parser {
13186
13515
  checkLiteral() {
13187
13516
  return this.checkPredicate(isLiteral);
13188
13517
  }
13518
+ checkEventNamedCall() {
13519
+ if (this.isAtEnd() || !this.checkIdentifierLike() || this.checkIsCommand())
13520
+ return false;
13521
+ const token = this.peek();
13522
+ const next = this.tokens[this.current + 1];
13523
+ return next?.value === '(' && next.start === token.end;
13524
+ }
13189
13525
  checkIdentifierLike() {
13190
13526
  return this.checkPredicate(isIdentifierLike);
13191
13527
  }
@@ -13294,6 +13630,22 @@ class Parser {
13294
13630
  isAtEnd() {
13295
13631
  return this.current >= this.tokens.length;
13296
13632
  }
13633
+ atEndOfInput() {
13634
+ for (let i = this.current; i < this.tokens.length; i++) {
13635
+ if (!isComment(this.tokens[i]))
13636
+ return false;
13637
+ }
13638
+ return true;
13639
+ }
13640
+ checkFeatureStart() {
13641
+ if (this.isAtEnd())
13642
+ return false;
13643
+ const value = this.peek().value;
13644
+ return (value === 'init' ||
13645
+ value === 'on' ||
13646
+ value === 'def' ||
13647
+ getRegisteredFeature(value) !== undefined);
13648
+ }
13297
13649
  peek() {
13298
13650
  if (this.isAtEnd()) {
13299
13651
  return { kind: 'unknown', value: '', start: 0, end: 0, line: 1, column: 1 };
@@ -13505,6 +13857,7 @@ class Parser {
13505
13857
  match: this.match.bind(this),
13506
13858
  matchOperator: this.matchOperator.bind(this),
13507
13859
  isAtEnd: this.isAtEnd.bind(this),
13860
+ atEndOfInput: this.atEndOfInput.bind(this),
13508
13861
  createIdentifier: this.createIdentifier.bind(this),
13509
13862
  parseExpression: this.parseExpression.bind(this),
13510
13863
  parsePrimary: this.parsePrimary.bind(this),
@@ -13555,7 +13908,15 @@ Parser.POSTFIX_UNARY_OPERATORS = new Set([
13555
13908
  'ignoring case',
13556
13909
  ]);
13557
13910
  Parser.PRATT_TABLE = PARSER_TABLE;
13911
+ Parser.BEEP_OPERAND_BP = 80;
13558
13912
  Parser.PSEUDO_COMMAND_PREPOSITIONS = ['from', 'on', 'with', 'into', 'at', 'to'];
13913
+ Parser.POSITIONAL_SOURCE_HEADS = new Set([
13914
+ 'closest',
13915
+ 'next',
13916
+ 'previous',
13917
+ 'first',
13918
+ 'last',
13919
+ ]);
13559
13920
  function parse(input, options) {
13560
13921
  const tokens = tokenize(input);
13561
13922
  const parser = new Parser(tokens, options, input);
@@ -13997,11 +14358,16 @@ async function evaluateBinaryExpression(node, context) {
13997
14358
  }
13998
14359
  }
13999
14360
  }
14000
- if (leftNode?.type === 'selector' && leftNode.fromQuery && typeof leftNode.value === 'string') {
14361
+ if (leftNode?.type === 'selector' &&
14362
+ typeof leftNode.value === 'string' &&
14363
+ (leftNode.fromQuery || (operator === 'in' && leftNode.value.startsWith('.')))) {
14001
14364
  const root = await evaluateAST(node.right, context);
14002
14365
  const scope = root && typeof root.querySelectorAll === 'function' ? root : null;
14003
14366
  if (scope) {
14004
- return Array.from(scope.querySelectorAll(leftNode.value));
14367
+ const query = leftNode.fromQuery
14368
+ ? leftNode.value
14369
+ : escapeSelectorForQuery(leftNode, leftNode.value);
14370
+ return Array.from(scope.querySelectorAll(query));
14005
14371
  }
14006
14372
  }
14007
14373
  }
@@ -14013,6 +14379,12 @@ async function evaluateBinaryExpression(node, context) {
14013
14379
  return getExpr(context, 'styleRef').evaluate(context, leftNode.value.slice(1), el);
14014
14380
  }
14015
14381
  if (operator === 'of') {
14382
+ if (leftNode?.type === 'attributeAccess') {
14383
+ const target = await evaluateAST(node.right, context);
14384
+ if (target == null)
14385
+ return undefined;
14386
+ return getExpr(context, 'possessive').evaluate(context, target, `@${leftNode.attributeName}`);
14387
+ }
14016
14388
  const path = propertyPathOf(node.left);
14017
14389
  if (path) {
14018
14390
  const target = await evaluateAST(node.right, context);
@@ -14020,6 +14392,10 @@ async function evaluateBinaryExpression(node, context) {
14020
14392
  for (const seg of path) {
14021
14393
  if (cur == null)
14022
14394
  return undefined;
14395
+ if (isMappableCollection(cur) && !(seg in cur)) {
14396
+ cur = await getExpr(context, 'possessive').evaluate(context, cur, seg);
14397
+ continue;
14398
+ }
14023
14399
  cur = isElement(cur) ? getElementProperty$1(cur, seg) : cur[seg];
14024
14400
  }
14025
14401
  return typeof cur === 'function' ? cur.bind(target) : cur;
@@ -14044,26 +14420,21 @@ async function evaluateBinaryExpression(node, context) {
14044
14420
  const R = node.ignoringCase ? applyCI(right) : right;
14045
14421
  switch (operator) {
14046
14422
  case '+': {
14047
- let addLeft = left;
14048
- let addRight = right;
14049
14423
  if (node.coerceNumeric) {
14050
- if (typeof addLeft === 'string')
14051
- addLeft = convertToNumber(addLeft);
14052
- if (typeof addRight === 'string')
14053
- addRight = convertToNumber(addRight);
14424
+ return counterValue(left) + counterAmount(right);
14054
14425
  }
14055
- if (typeof addLeft === 'string' || typeof addRight === 'string') {
14056
- return String(addLeft ?? '') + String(addRight ?? '');
14426
+ if (typeof left === 'string' || typeof right === 'string') {
14427
+ return String(left ?? '') + String(right ?? '');
14057
14428
  }
14058
- if (Array.isArray(addLeft)) {
14059
- return Array.isArray(addRight) ? [...addLeft, ...addRight] : [...addLeft, addRight];
14429
+ if (Array.isArray(left)) {
14430
+ return Array.isArray(right) ? [...left, ...right] : [...left, right];
14060
14431
  }
14061
- return unwrapTypedResult(await getExpr(context, 'addition').evaluate(context, {
14062
- left: addLeft,
14063
- right: addRight,
14064
- }));
14432
+ return unwrapTypedResult(await getExpr(context, 'addition').evaluate(context, { left, right }));
14065
14433
  }
14066
14434
  case '-':
14435
+ if (node.coerceNumeric) {
14436
+ return counterValue(left) - counterAmount(right);
14437
+ }
14067
14438
  return unwrapTypedResult(await getExpr(context, 'subtraction').evaluate(context, { left, right }));
14068
14439
  case '*':
14069
14440
  return unwrapTypedResult(await getExpr(context, 'multiplication').evaluate(context, { left, right }));
@@ -14321,9 +14692,8 @@ async function evaluatePropertyOfExpressionNode(node, context) {
14321
14692
  }
14322
14693
  const propertyName = propertyNode.name;
14323
14694
  const target = await evaluateAST(node.target, context);
14324
- if (target == null) {
14325
- throw new Error(`Cannot access property "${propertyName}" of ${target}`);
14326
- }
14695
+ if (target == null)
14696
+ return undefined;
14327
14697
  return getExpr(context, 'possessive').evaluate(context, target, propertyName);
14328
14698
  }
14329
14699
  async function evaluateTemplateLiteralNode(node, context) {
@@ -14441,6 +14811,9 @@ async function evaluateUnaryExpression(node, context) {
14441
14811
  case 'not':
14442
14812
  case '!':
14443
14813
  return logicalExpressions.not.evaluate(context, value);
14814
+ case 'beep!':
14815
+ beepValues(context.me, [value]);
14816
+ return value;
14444
14817
  case 'no':
14445
14818
  return logicalExpressions.no.evaluate(context, value);
14446
14819
  case '-':
@@ -14530,6 +14903,10 @@ async function evaluateCallExpression(node, context) {
14530
14903
  const args = await resolveCallArgs(node.arguments, funcName, context);
14531
14904
  switch (funcName) {
14532
14905
  case 'closest':
14906
+ if (node.closestTo) {
14907
+ const start = unwrapElement(await evaluateAST(node.closestTo, context)) ?? null;
14908
+ return referencesExpressions.closest.evaluate(context, args[0], start);
14909
+ }
14533
14910
  return referencesExpressions.closest.evaluate(context, ...args);
14534
14911
  case 'previous':
14535
14912
  case 'next': {
@@ -15954,10 +16331,29 @@ const STRATEGY_KEYWORDS = {
15954
16331
  innermorph: 'morph',
15955
16332
  outermorph: 'morphOuter',
15956
16333
  };
16334
+ function loneRootWithTag(content, tagName) {
16335
+ let root;
16336
+ if (typeof content === 'string') {
16337
+ const template = document.createElement('template');
16338
+ template.innerHTML = content;
16339
+ root = template.content.firstElementChild;
16340
+ if (!root || root.nextElementSibling)
16341
+ return null;
16342
+ }
16343
+ else {
16344
+ root = content.cloneNode(true);
16345
+ }
16346
+ return root.tagName === tagName && isHTMLElement$2(root) ? root : null;
16347
+ }
15957
16348
  function executeSwap(target, content, strategy, morphOptions) {
15958
16349
  const contentStr = content !== null && !isHTMLElement$2(content) ? content : '';
15959
16350
  const contentEl = isHTMLElement$2(content) ? content : null;
15960
16351
  switch (strategy) {
16352
+ case 'morphMerge': {
16353
+ const root = content !== null ? loneRootWithTag(content, target.tagName) : null;
16354
+ executeSwap(target, root ?? content, root ? 'morphOuter' : 'morph', morphOptions);
16355
+ break;
16356
+ }
15961
16357
  case 'morph':
15962
16358
  if (content !== null) {
15963
16359
  try {
@@ -16247,7 +16643,7 @@ let MorphCommand = (() => {
16247
16643
  if (args.length === 0 && modifiers.with === undefined) {
16248
16644
  throw new Error('[HyperFixi] morph: command requires arguments');
16249
16645
  }
16250
- const strategy = literalText(modifiers.strategy)?.toLowerCase() === 'over' ? 'morphOuter' : 'morph';
16646
+ const strategy = literalText(modifiers.strategy)?.toLowerCase() === 'over' ? 'morphOuter' : 'morphMerge';
16251
16647
  const useViewTransition = modifiers.viewTransition !== undefined;
16252
16648
  const targetNode = modifiers.on !== undefined ? modifiers.on : args[0];
16253
16649
  const contentNode = modifiers.with !== undefined
@@ -17537,6 +17933,19 @@ async function resolveWriteTarget(node, evaluator, context, options) {
17537
17933
  }
17538
17934
  return { kind: 'property', target: property };
17539
17935
  }
17936
+ if (options.ofAndObjectProperty) {
17937
+ const access = ofOrPossessiveAccess(n);
17938
+ if (access) {
17939
+ const owner = await evaluator.evaluate(access.owner, context);
17940
+ const element = Array.isArray(owner) ? owner[0] : owner;
17941
+ if (access.isOf && isHTMLElement$2(element)) {
17942
+ return { kind: 'property', target: { element, property: access.name } };
17943
+ }
17944
+ if (isObjectOwner(owner)) {
17945
+ return { kind: 'member', container: owner, property: access.name };
17946
+ }
17947
+ }
17948
+ }
17540
17949
  if (options.bareReference &&
17541
17950
  (nodeType === 'identifier' || nodeType === 'variable' || nodeType === 'symbol') &&
17542
17951
  typeof n.name === 'string') {
@@ -17546,6 +17955,19 @@ async function resolveWriteTarget(node, evaluator, context, options) {
17546
17955
  }
17547
17956
  return null;
17548
17957
  }
17958
+ function ofOrPossessiveAccess(n) {
17959
+ const [owner, property, isOf] = n.type === 'possessiveExpression'
17960
+ ? [n.object, n.property, false]
17961
+ : n.type === 'propertyOfExpression'
17962
+ ? [n.target, n.property, false]
17963
+ : n.type === 'binaryExpression' && n.operator === 'of'
17964
+ ? [n.right, n.left, true]
17965
+ : [undefined, undefined, false];
17966
+ const name = property?.name;
17967
+ if (!owner || typeof name !== 'string' || !/^[A-Za-z_$][\w$]*$/.test(name))
17968
+ return null;
17969
+ return { owner, name, isOf };
17970
+ }
17549
17971
 
17550
17972
  function toSetInput(target, value) {
17551
17973
  switch (target.kind) {
@@ -17567,10 +17989,18 @@ function toSetInput(target, value) {
17567
17989
  property: target.target.property,
17568
17990
  value,
17569
17991
  };
17992
+ case 'member':
17993
+ return {
17994
+ type: 'member-assignment',
17995
+ container: target.container,
17996
+ property: target.property,
17997
+ value,
17998
+ };
17570
17999
  default:
17571
18000
  throw new Error(`set: unrequested write-target rung '${target.kind}'`);
17572
18001
  }
17573
18002
  }
18003
+ const SET_RUNGS = { nodeWriters: true, styleSplit: true, ofAndObjectProperty: true };
17574
18004
  let SetCommand = (() => {
17575
18005
  let _classDecorators = [command({ name: 'set' })];
17576
18006
  let _classDescriptor;
@@ -17589,8 +18019,7 @@ let SetCommand = (() => {
17589
18019
  const argScope = firstArg?.scope;
17590
18020
  const writeTarget = await resolveWriteTarget(firstArg, evaluator, context, {
17591
18021
  scopeElements: () => this.resolveTargets(raw.modifiers.on, evaluator, context),
17592
- nodeWriters: true,
17593
- styleSplit: true,
18022
+ ...SET_RUNGS,
17594
18023
  });
17595
18024
  if (writeTarget) {
17596
18025
  return toSetInput(writeTarget, await this.extractValue(raw, evaluator, context));
@@ -17916,12 +18345,30 @@ function getNodeType(node) {
17916
18345
  return 'unknown';
17917
18346
  return node.type || 'unknown';
17918
18347
  }
18348
+ function propertyTarget(node) {
18349
+ const n = node;
18350
+ const owner = n.type === 'propertyOfExpression'
18351
+ ? n.target
18352
+ : n.type === 'possessiveExpression' ||
18353
+ n.type === 'memberExpression' ||
18354
+ n.type === 'propertyAccess'
18355
+ ? n.object
18356
+ : undefined;
18357
+ if (!owner || n.computed)
18358
+ return null;
18359
+ const p = n.property;
18360
+ const name = typeof p === 'string' ? p : typeof p?.name === 'string' ? p.name : undefined;
18361
+ if (!name || !/^[A-Za-z_$][\w$-]*$/.test(name))
18362
+ return null;
18363
+ return { owner, name };
18364
+ }
17919
18365
  async function parseNumericTargetInput(raw, evaluator, context, commandName) {
17920
18366
  if (!raw.args || raw.args.length === 0) {
17921
18367
  throw new Error(`${commandName} command requires a target`);
17922
18368
  }
17923
18369
  const targetArg = raw.args[0];
17924
18370
  let target;
18371
+ let property;
17925
18372
  let extractedScope;
17926
18373
  let customWrite;
17927
18374
  const nodeType = getNodeType(targetArg);
@@ -17936,7 +18383,25 @@ async function parseNumericTargetInput(raw, evaluator, context, commandName) {
17936
18383
  }
17937
18384
  else {
17938
18385
  const writer = getRegisteredNodeWriter(nodeType);
17939
- if (writer) {
18386
+ const attribute = writer
18387
+ ? null
18388
+ : await resolveAttributeWriteTarget(targetArg, evaluator, context, async () => context.me ? [context.me] : []);
18389
+ const prop = writer || attribute ? null : propertyTarget(targetArg);
18390
+ const owner = prop ? await evaluator.evaluate(prop.owner, context) : undefined;
18391
+ const ownerElement = Array.isArray(owner) ? owner[0] : owner;
18392
+ if (attribute?.elements[0]) {
18393
+ target = attribute.elements[0];
18394
+ property = `@${attribute.name}`;
18395
+ }
18396
+ else if (prop && isHTMLElement$2(ownerElement)) {
18397
+ target = ownerElement;
18398
+ property = prop.name;
18399
+ }
18400
+ else if (prop && isObjectOwner(owner)) {
18401
+ target = owner;
18402
+ property = prop.name;
18403
+ }
18404
+ else if (writer) {
17940
18405
  const currentRaw = await evaluator.evaluate(targetArg, context);
17941
18406
  const currentValue = typeof currentRaw === 'number'
17942
18407
  ? currentRaw
@@ -17974,24 +18439,22 @@ async function parseNumericTargetInput(raw, evaluator, context, commandName) {
17974
18439
  if (typeof evaluated === 'number') {
17975
18440
  amount = evaluated;
17976
18441
  }
18442
+ else if (evaluated == null) {
18443
+ amount = NaN;
18444
+ }
17977
18445
  }
17978
18446
  }
17979
18447
  const byModifier = raw.modifiers?.by;
17980
18448
  if (byModifier) {
17981
- const byValue = byModifier.value;
17982
- if (getNodeType(byModifier) === 'literal' && typeof byValue === 'number') {
17983
- amount = byValue;
17984
- }
17985
- else if (getNodeType(byModifier) !== 'literal') {
17986
- const evaluated = await evaluator.evaluate(byModifier, context);
17987
- if (typeof evaluated === 'number') {
17988
- amount = evaluated;
17989
- }
17990
- }
18449
+ const value = getNodeType(byModifier) === 'literal'
18450
+ ? byModifier.value
18451
+ : await evaluator.evaluate(byModifier, context);
18452
+ amount = counterAmount(value);
17991
18453
  }
17992
18454
  return {
17993
18455
  target,
17994
18456
  amount,
18457
+ ...(property && { property }),
17995
18458
  ...(scope && { scope }),
17996
18459
  ...(customWrite && { customWrite }),
17997
18460
  };
@@ -18014,7 +18477,7 @@ let NumericModifyCommand = (() => {
18014
18477
  async execute(input, context) {
18015
18478
  const { target, property, scope, amount = 1, operation = 'increment', customWrite } = input;
18016
18479
  if (customWrite) {
18017
- const delta = isFinite(amount) ? amount : 1;
18480
+ const delta = amount;
18018
18481
  const newValue = operation === 'increment'
18019
18482
  ? customWrite.currentValue + delta
18020
18483
  : customWrite.currentValue - delta;
@@ -18028,8 +18491,7 @@ let NumericModifyCommand = (() => {
18028
18491
  newValue = NaN;
18029
18492
  }
18030
18493
  else {
18031
- const delta = isFinite(amount) ? amount : 1;
18032
- newValue = operation === 'increment' ? currentValue + delta : currentValue - delta;
18494
+ newValue = operation === 'increment' ? currentValue + amount : currentValue - amount;
18033
18495
  }
18034
18496
  setTargetValue(target, property, scope, newValue, context);
18035
18497
  Object.assign(context, { it: newValue });
@@ -18845,17 +19307,7 @@ let BeepCommand = (() => {
18845
19307
  this.debugContext(context);
18846
19308
  return { expressionCount: 0, debugged: true, outputs: [] };
18847
19309
  }
18848
- const outputs = [];
18849
- console.group('\ud83d\udd14 beep! Debug Output');
18850
- for (const expression of expressions) {
18851
- const output = this.debugExpression(expression);
18852
- outputs.push(output);
18853
- console.log(`Value:`, expression);
18854
- console.log(`Type:`, output.type);
18855
- console.log(`Representation:`, output.representation);
18856
- console.log('---');
18857
- }
18858
- console.groupEnd();
19310
+ const outputs = beepValues(context.me, expressions);
18859
19311
  return { expressionCount: expressions.length, debugged: true, outputs };
18860
19312
  }
18861
19313
  debugContext(context) {
@@ -18868,67 +19320,6 @@ let BeepCommand = (() => {
18868
19320
  console.log('variables:', context.variables);
18869
19321
  console.groupEnd();
18870
19322
  }
18871
- debugExpression(expression) {
18872
- return {
18873
- value: expression,
18874
- type: this.getType(expression),
18875
- representation: this.getRepresentation(expression),
18876
- };
18877
- }
18878
- getType(value) {
18879
- if (value === null)
18880
- return 'null';
18881
- if (value === undefined)
18882
- return 'undefined';
18883
- if (Array.isArray(value))
18884
- return 'array';
18885
- if (isHTMLElement$2(value))
18886
- return 'HTMLElement';
18887
- if (value instanceof Element)
18888
- return 'Element';
18889
- if (value instanceof Node)
18890
- return 'Node';
18891
- if (value instanceof Error)
18892
- return 'Error';
18893
- if (value instanceof Date)
18894
- return 'Date';
18895
- if (value instanceof RegExp)
18896
- return 'RegExp';
18897
- return typeof value;
18898
- }
18899
- getRepresentation(value) {
18900
- if (value === null)
18901
- return 'null';
18902
- if (value === undefined)
18903
- return 'undefined';
18904
- if (Array.isArray(value)) {
18905
- return `Array(${value.length}) [${value
18906
- .slice(0, 3)
18907
- .map(v => this.getRepresentation(v))
18908
- .join(', ')}${value.length > 3 ? '...' : ''}]`;
18909
- }
18910
- if (isHTMLElement$2(value)) {
18911
- const el = value;
18912
- const tag = el.tagName.toLowerCase();
18913
- const id = el.id ? `#${el.id}` : '';
18914
- const classes = el.className ? `.${el.className.split(' ').join('.')}` : '';
18915
- return `<${tag}${id}${classes}/>`;
18916
- }
18917
- if (value instanceof Error)
18918
- return `Error: ${value.message}`;
18919
- if (typeof value === 'string')
18920
- return value.length > 50 ? `"${value.substring(0, 47)}..."` : `"${value}"`;
18921
- if (typeof value === 'object') {
18922
- try {
18923
- const keys = Object.keys(value);
18924
- return `Object {${keys.slice(0, 3).join(', ')}${keys.length > 3 ? '...' : ''}}`;
18925
- }
18926
- catch {
18927
- return '[Object]';
18928
- }
18929
- }
18930
- return String(value);
18931
- }
18932
19323
  };
18933
19324
  __setFunctionName(_classThis, "BeepCommand");
18934
19325
  (() => {
@@ -19890,7 +20281,10 @@ function createUntilLoopConfig(conditionExpr, evaluateCondition, context, indexV
19890
20281
  };
19891
20282
  const config = {
19892
20283
  type: 'until',
19893
- shouldContinue: () => !evaluateCondition(conditionExpr, context),
20284
+ shouldContinue: () => {
20285
+ const met = evaluateCondition(conditionExpr, context);
20286
+ return met instanceof Promise ? met.then(value => !value) : !met;
20287
+ },
19894
20288
  };
19895
20289
  return { config, iterCtx };
19896
20290
  }
@@ -19939,6 +20333,18 @@ async function slotText(node, evaluator, context) {
19939
20333
  const v = await evaluator.evaluate(node, context);
19940
20334
  return typeof v === 'string' ? v : undefined;
19941
20335
  }
20336
+ function isEventTarget(value) {
20337
+ return typeof value?.addEventListener === 'function';
20338
+ }
20339
+ async function evaluateLoopCondition(condition, context) {
20340
+ if (typeof condition === 'function')
20341
+ return Boolean(await condition());
20342
+ return evaluateCondition(condition, context);
20343
+ }
20344
+ function loopCount(value) {
20345
+ const count = Number(value);
20346
+ return Number.isNaN(count) ? 0 : count;
20347
+ }
19942
20348
  let RepeatCommand = (() => {
19943
20349
  let _classDecorators = [command({ name: 'repeat' })];
19944
20350
  let _classDescriptor;
@@ -19996,17 +20402,24 @@ let RepeatCommand = (() => {
19996
20402
  };
19997
20403
  }
19998
20404
  if (loopType === 'times' || m.times) {
19999
- const countValue = m.times && (await evaluator.evaluate(m.times, context));
20000
- const count = typeof countValue === 'number' ? countValue : parseInt(String(countValue), 10);
20001
- if (isNaN(count))
20405
+ if (!m.times)
20002
20406
  throw new Error('times loops require a count number');
20407
+ const count = loopCount(await evaluator.evaluate(m.times, context));
20003
20408
  return { type: 'times', count, indexVariable, commands, elseCommands };
20004
20409
  }
20005
20410
  if (loopType === 'while' || m.while) {
20006
20411
  const condition = m.while;
20007
20412
  if (!condition)
20008
20413
  throw new Error('while loops require a condition');
20009
- return { type: 'while', condition, indexVariable, commands, elseCommands, bottomTested };
20414
+ const check = () => evaluator.evaluate(condition, context);
20415
+ return {
20416
+ type: 'while',
20417
+ condition: check,
20418
+ indexVariable,
20419
+ commands,
20420
+ elseCommands,
20421
+ bottomTested,
20422
+ };
20010
20423
  }
20011
20424
  if (loopType === 'until-event' || m.event) {
20012
20425
  const eventName = await text(m.event);
@@ -20024,7 +20437,7 @@ let RepeatCommand = (() => {
20024
20437
  }
20025
20438
  else {
20026
20439
  const target = await evaluator.evaluate(m.from, context);
20027
- if (target instanceof EventTarget)
20440
+ if (isEventTarget(target))
20028
20441
  eventTarget = target;
20029
20442
  else if (target === 'document')
20030
20443
  eventTarget = document;
@@ -20045,7 +20458,15 @@ let RepeatCommand = (() => {
20045
20458
  const condition = m.until;
20046
20459
  if (!condition)
20047
20460
  throw new Error('until loops require a condition');
20048
- return { type: 'until', condition, indexVariable, commands, elseCommands, bottomTested };
20461
+ const check = () => evaluator.evaluate(condition, context);
20462
+ return {
20463
+ type: 'until',
20464
+ condition: check,
20465
+ indexVariable,
20466
+ commands,
20467
+ elseCommands,
20468
+ bottomTested,
20469
+ };
20049
20470
  }
20050
20471
  if (loopType === 'forever') {
20051
20472
  return { type: 'forever', indexVariable, commands, elseCommands };
@@ -20063,10 +20484,10 @@ let RepeatCommand = (() => {
20063
20484
  ({ config, iterCtx } = createTimesLoopConfig(count, indexVariable));
20064
20485
  break;
20065
20486
  case 'while':
20066
- ({ config, iterCtx } = createWhileLoopConfig(condition, evaluateCondition, context, indexVariable));
20487
+ ({ config, iterCtx } = createWhileLoopConfig(condition, evaluateLoopCondition, context, indexVariable));
20067
20488
  break;
20068
20489
  case 'until':
20069
- ({ config, iterCtx } = createUntilLoopConfig(condition, evaluateCondition, context, indexVariable));
20490
+ ({ config, iterCtx } = createUntilLoopConfig(condition, evaluateLoopCondition, context, indexVariable));
20070
20491
  break;
20071
20492
  case 'until-event':
20072
20493
  ({ config, iterCtx } = createUntilEventLoopConfig(eventName, eventTarget, indexVariable));
@@ -21097,6 +21518,29 @@ function namedPropertyOf(node) {
21097
21518
  }
21098
21519
  return null;
21099
21520
  }
21521
+ const RESTORE_KEYWORDS = new Set(['initial', 'inherit', 'unset', 'revert']);
21522
+ function elementsOf(value) {
21523
+ if (Array.isArray(value) || isNodeList(value)) {
21524
+ return Array.from(value).filter(isHTMLElement$2);
21525
+ }
21526
+ return isHTMLElement$2(value) ? [value] : [];
21527
+ }
21528
+ async function valueOf(node, evaluator, context) {
21529
+ const value = await evaluator.evaluate(node, context);
21530
+ const name = node.name;
21531
+ if (value === undefined && node.type === 'identifier' && typeof name === 'string')
21532
+ return name;
21533
+ return value;
21534
+ }
21535
+ function longestTransitionMs(css) {
21536
+ let longest;
21537
+ for (const item of css.split(',')) {
21538
+ const times = [...item.matchAll(/(\d*\.?\d+)(ms|s)\b/g)].map(m => parseFloat(m[1]) * (m[2] === 's' ? 1000 : 1));
21539
+ if (times.length > 0)
21540
+ longest = Math.max(longest ?? 0, times[0] + (times[1] ?? 0));
21541
+ }
21542
+ return longest;
21543
+ }
21100
21544
  let TransitionCommand = (() => {
21101
21545
  let _classDecorators = [command({ name: 'transition' })];
21102
21546
  let _classDescriptor;
@@ -21112,11 +21556,10 @@ let TransitionCommand = (() => {
21112
21556
  let property;
21113
21557
  let target;
21114
21558
  const firstArg = await evaluator.evaluate(raw.args[0], context);
21115
- const asElement = (value) => Array.isArray(value) || isNodeList(value) ? value[0] : value;
21116
- const firstAsElement = asElement(firstArg);
21117
- if (isHTMLElement$2(firstAsElement) ||
21559
+ const owners = elementsOf(firstArg);
21560
+ if (owners.length > 0 ||
21118
21561
  (typeof firstArg === 'string' && /^[#.]|^(?:me|it|you)$/.test(firstArg))) {
21119
- target = firstAsElement;
21562
+ target = owners.length === 1 ? owners[0] : owners.length > 1 ? owners : firstArg;
21120
21563
  property = String(await evaluator.evaluate(raw.args[1], context));
21121
21564
  }
21122
21565
  else if (raw.args.length >= 2) {
@@ -21128,9 +21571,9 @@ let TransitionCommand = (() => {
21128
21571
  throw new Error('transition requires a CSS property');
21129
21572
  property = named.property;
21130
21573
  if (named.objectNode !== undefined) {
21131
- const owner = asElement(await evaluator.evaluate(named.objectNode, context));
21132
- if (isHTMLElement$2(owner))
21133
- target = owner;
21574
+ const owner = elementsOf(await evaluator.evaluate(named.objectNode, context));
21575
+ if (owner.length > 0)
21576
+ target = owner.length === 1 ? owner[0] : owner;
21134
21577
  }
21135
21578
  }
21136
21579
  else {
@@ -21141,60 +21584,131 @@ let TransitionCommand = (() => {
21141
21584
  }
21142
21585
  if (!raw.modifiers?.to)
21143
21586
  throw new Error('transition requires "to <value>"');
21144
- let value = await evaluator.evaluate(raw.modifiers.to, context);
21145
- if (value === undefined && raw.modifiers.to.type === 'identifier') {
21146
- value = raw.modifiers.to.name;
21147
- }
21148
- const result = { property, value: value };
21587
+ const result = {
21588
+ property,
21589
+ value: await valueOf(raw.modifiers.to, evaluator, context),
21590
+ };
21149
21591
  if (target !== undefined)
21150
21592
  result.target = target;
21593
+ if (raw.modifiers?.from)
21594
+ result.from = await valueOf(raw.modifiers.from, evaluator, context);
21595
+ if (raw.modifiers?.pairs) {
21596
+ result.pairs = await this.laterPairs(raw.modifiers.pairs, evaluator, context);
21597
+ }
21151
21598
  if (raw.modifiers?.over)
21152
21599
  result.duration = await evaluator.evaluate(raw.modifiers.over, context);
21153
21600
  if (raw.modifiers?.with)
21154
21601
  result.timingFunction = String(await evaluator.evaluate(raw.modifiers.with, context));
21602
+ if (raw.modifiers?.using)
21603
+ result.using = String(await evaluator.evaluate(raw.modifiers.using, context));
21155
21604
  return result;
21156
21605
  }
21606
+ async laterPairs(node, evaluator, context) {
21607
+ const pairs = [];
21608
+ for (const pairNode of node.elements ?? []) {
21609
+ const fields = new Map();
21610
+ const properties = pairNode
21611
+ .properties ?? [];
21612
+ for (const { key, value } of properties) {
21613
+ if (typeof key.name === 'string')
21614
+ fields.set(key.name, value);
21615
+ }
21616
+ const propertyNode = fields.get('property');
21617
+ const toNode = fields.get('to');
21618
+ if (!propertyNode || !toNode)
21619
+ throw new Error('transition requires property and value');
21620
+ const pair = {
21621
+ property: String(await evaluator.evaluate(propertyNode, context)),
21622
+ value: await valueOf(toNode, evaluator, context),
21623
+ };
21624
+ const ownerNode = fields.get('owner');
21625
+ if (ownerNode) {
21626
+ const owners = elementsOf(await evaluator.evaluate(ownerNode, context));
21627
+ if (owners.length === 0)
21628
+ throw new Error('transition: target element not found');
21629
+ pair.target = owners.length === 1 ? owners[0] : owners;
21630
+ }
21631
+ const fromNode = fields.get('from');
21632
+ if (fromNode)
21633
+ pair.from = await valueOf(fromNode, evaluator, context);
21634
+ pairs.push(pair);
21635
+ }
21636
+ return pairs;
21637
+ }
21157
21638
  async execute(input, context) {
21158
- let { property } = input;
21159
- const { target, value, duration: durationInput, timingFunction } = input;
21160
- if (property.startsWith('*'))
21161
- property = property.substring(1);
21162
- property = camelToKebab(property);
21163
- const targetElement = resolveElement(target, context, 'transition');
21164
- const duration = parseDuration(durationInput, 300);
21165
- const fromValue = getComputedStyle(targetElement).getPropertyValue(property);
21166
- const originalTransition = targetElement.style.transition;
21167
- const transitionProp = `${property} ${duration}ms ${timingFunction || 'ease'}`;
21168
- targetElement.style.transition = originalTransition
21169
- ? `${originalTransition}, ${transitionProp}`
21170
- : transitionProp;
21171
- let toValue = String(value);
21172
- let removeInlineAfter = false;
21173
- if (toValue === 'initial' ||
21174
- toValue === 'inherit' ||
21175
- toValue === 'unset' ||
21176
- toValue === 'revert') {
21177
- const currentInline = targetElement.style.getPropertyValue(property);
21178
- targetElement.style.removeProperty(property);
21179
- toValue = getComputedStyle(targetElement).getPropertyValue(property);
21180
- if (currentInline) {
21181
- targetElement.style.setProperty(property, currentInline);
21182
- }
21183
- removeInlineAfter = true;
21184
- }
21185
- targetElement.style.setProperty(property, toValue);
21186
- const result = await waitForTransitionEnd(targetElement, property, duration);
21187
- targetElement.style.transition = originalTransition;
21188
- if (removeInlineAfter) {
21189
- targetElement.style.removeProperty(property);
21639
+ const duration = parseDuration(input.duration, (input.using !== undefined && longestTransitionMs(input.using)) || 300);
21640
+ const timing = input.timingFunction || 'ease';
21641
+ const jobs = [];
21642
+ for (const pair of [input, ...(input.pairs ?? [])]) {
21643
+ let property = pair.property;
21644
+ if (property.startsWith('*'))
21645
+ property = property.substring(1);
21646
+ property = camelToKebab(property);
21647
+ const elements = Array.isArray(pair.target)
21648
+ ? pair.target.filter(isHTMLElement$2)
21649
+ : [resolveElement(pair.target, context, 'transition')];
21650
+ for (const element of elements) {
21651
+ jobs.push({
21652
+ element,
21653
+ property,
21654
+ pair,
21655
+ fromValue: getComputedStyle(element).getPropertyValue(property),
21656
+ toValue: '',
21657
+ removeInlineAfter: false,
21658
+ });
21659
+ }
21190
21660
  }
21661
+ if (jobs.length === 0)
21662
+ throw new Error('transition: target element not found');
21663
+ for (const job of jobs) {
21664
+ if (job.pair.from !== undefined) {
21665
+ job.element.style.setProperty(job.property, String(job.pair.from));
21666
+ }
21667
+ }
21668
+ for (const job of jobs)
21669
+ if (job.pair.from !== undefined)
21670
+ void job.element.offsetWidth;
21671
+ const originals = new Map();
21672
+ for (const job of jobs) {
21673
+ if (!originals.has(job.element))
21674
+ originals.set(job.element, job.element.style.transition);
21675
+ }
21676
+ for (const [element, original] of originals) {
21677
+ const own = jobs
21678
+ .filter(job => job.element === element)
21679
+ .map(job => `${job.property} ${duration}ms ${timing}`);
21680
+ element.style.transition = input.using ?? [original, ...own].filter(Boolean).join(', ');
21681
+ }
21682
+ for (const job of jobs) {
21683
+ const { element, property } = job;
21684
+ let toValue = String(job.pair.value);
21685
+ if (RESTORE_KEYWORDS.has(toValue)) {
21686
+ const currentInline = element.style.getPropertyValue(property);
21687
+ element.style.removeProperty(property);
21688
+ toValue = getComputedStyle(element).getPropertyValue(property);
21689
+ if (currentInline) {
21690
+ element.style.setProperty(property, currentInline);
21691
+ }
21692
+ job.removeInlineAfter = true;
21693
+ }
21694
+ job.toValue = toValue;
21695
+ element.style.setProperty(property, toValue);
21696
+ }
21697
+ const results = await Promise.all(jobs.map(job => waitForTransitionEnd(job.element, job.property, duration)));
21698
+ for (const [element, original] of originals)
21699
+ element.style.transition = original;
21700
+ for (const job of jobs) {
21701
+ if (job.removeInlineAfter)
21702
+ job.element.style.removeProperty(job.property);
21703
+ }
21704
+ const first = jobs[0];
21191
21705
  return {
21192
- element: targetElement,
21193
- property,
21194
- fromValue,
21195
- toValue,
21706
+ element: first.element,
21707
+ property: first.property,
21708
+ fromValue: first.fromValue,
21709
+ toValue: first.toValue,
21196
21710
  duration,
21197
- completed: result.completed,
21711
+ completed: results.every(result => result.completed),
21198
21712
  };
21199
21713
  }
21200
21714
  };
@@ -21207,13 +21721,16 @@ let TransitionCommand = (() => {
21207
21721
  })();
21208
21722
  _classThis.metadata = commandMeta({
21209
21723
  description: 'Animate CSS properties using CSS transitions',
21210
- syntax: 'transition [<target>] <property> to <value> [over <duration>] [with <timing>]',
21724
+ syntax: 'transition <property> [from <value>] to <value> [<property> \u2026 to <value>]\u2026 [over <duration>] [using <css> | with <timing>]',
21211
21725
  examples: [
21212
21726
  'transition opacity to 0.5',
21213
21727
  'transition my *opacity to 0 over 200ms',
21214
21728
  "transition #box's *opacity to 0 over 200ms",
21215
21729
  'transition left to 100px over 500ms',
21216
21730
  'transition background-color to red over 1s with ease-in-out',
21731
+ 'transition *width to 100px *height to 50px over 300ms',
21732
+ 'transition *opacity of #panel from 0 to 1 over 200ms',
21733
+ 'transition my *opacity to 0 using "opacity 1s ease-in"',
21217
21734
  ],
21218
21735
  sideEffects: ['style-change', 'timing'],
21219
21736
  category: 'animation',
@@ -22339,16 +22856,12 @@ class RenderCommand {
22339
22856
  }
22340
22857
  RenderCommand.metadata = commandMeta({
22341
22858
  description: 'Render templates with @if, @else, and @repeat directives',
22342
- syntax: [
22343
- 'render <template>',
22344
- 'render <template> with <variables>',
22345
- 'render <template> with (key: value, ...)',
22346
- ],
22859
+ syntax: ['render <template>', 'render <template> with <name>: <value>, ...'],
22347
22860
  examples: [
22348
22861
  'render myTemplate',
22349
- 'render myTemplate with (name: "Alice")',
22350
- 'render "<template>Hello ${name}!</template>" with (name: "World")',
22351
- 'render template with (items: data)',
22862
+ 'render myTemplate with name: "Alice"',
22863
+ 'render "<template>Hello ${name}!</template>" with name: "World"',
22864
+ 'render template with items: data',
22352
22865
  ],
22353
22866
  category: 'templates',
22354
22867
  compatibility: 'standard',