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