@hyperfixi/core 2.6.0 → 2.7.1

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 (54) hide show
  1. package/dist/api/hyperscript-api.d.ts +6 -0
  2. package/dist/ast-utils/index.js +8 -8
  3. package/dist/ast-utils/index.mjs +8 -8
  4. package/dist/bundle-generator/index.js +12 -4
  5. package/dist/bundle-generator/index.mjs +12 -4
  6. package/dist/chunks/{bridge-fFLEvCQs.js → bridge-lZbOVRDD.js} +2 -2
  7. package/dist/chunks/browser-modular-DegkWQ8d.js +2 -0
  8. package/dist/chunks/{index-BRgVy8nY.js → index-i_j9Z-1e.js} +2 -2
  9. package/dist/commands/data/set.d.ts +1 -0
  10. package/dist/commands/index.js +156 -47
  11. package/dist/commands/index.mjs +156 -47
  12. package/dist/core/context.d.ts +4 -0
  13. package/dist/dom/attribute-processor.d.ts +1 -0
  14. package/dist/hyperfixi-browser-classic-i18n.js +1 -1
  15. package/dist/hyperfixi-browser-minimal.js +1 -1
  16. package/dist/hyperfixi-browser-standard.js +1 -1
  17. package/dist/hyperfixi-browser.js +1 -1
  18. package/dist/hyperfixi-classic-i18n.js +1 -1
  19. package/dist/hyperfixi-hx-v4.js +1 -1
  20. package/dist/hyperfixi-hx.js +1 -1
  21. package/dist/hyperfixi-hybrid-complete.js +1 -1
  22. package/dist/hyperfixi-hybrid-hx.js +1 -1
  23. package/dist/hyperfixi-lite-plus.js +1 -1
  24. package/dist/hyperfixi-lite.js +1 -1
  25. package/dist/hyperfixi-minimal.js +1 -1
  26. package/dist/hyperfixi-multilingual.js +1 -1
  27. package/dist/hyperfixi-standard.js +1 -1
  28. package/dist/hyperfixi.js +1 -1
  29. package/dist/hyperfixi.mjs +1 -1
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.js +694 -459
  32. package/dist/index.min.js +1 -1
  33. package/dist/index.mjs +691 -460
  34. package/dist/lokascript-browser-classic-i18n.js +1 -1
  35. package/dist/lokascript-browser-minimal.js +1 -1
  36. package/dist/lokascript-browser-standard.js +1 -1
  37. package/dist/lokascript-browser.js +1 -1
  38. package/dist/lokascript-hybrid-complete.js +1 -1
  39. package/dist/lokascript-hybrid-hx.js +1 -1
  40. package/dist/lokascript-lite-plus.js +1 -1
  41. package/dist/lokascript-lite.js +1 -1
  42. package/dist/lokascript-multilingual.js +1 -1
  43. package/dist/parser/full-parser.js +70 -35
  44. package/dist/parser/full-parser.mjs +70 -35
  45. package/dist/parser/parser-types.d.ts +1 -1
  46. package/dist/parser/parser.d.ts +2 -0
  47. package/dist/parser/regex-parser.js +23 -14
  48. package/dist/parser/regex-parser.mjs +23 -14
  49. package/dist/registry/index.js +178 -45
  50. package/dist/registry/index.mjs +178 -45
  51. package/dist/types/core-context.d.ts +1 -0
  52. package/package.json +6 -6
  53. package/dist/chunks/browser-modular-BK3IOo93.js +0 -2
  54. package/dist/compatibility/browser-bundle.d.ts +0 -164
@@ -1220,6 +1220,7 @@ const FEATURE_REGISTRY = new Map();
1220
1220
  const NODE_EVALUATORS = new Map();
1221
1221
  const GLOBAL_WRITE_HOOKS = new Set();
1222
1222
  const GLOBAL_READ_HOOKS = new Set();
1223
+ const LOCAL_WRITE_HOOKS = new Set();
1223
1224
  const LOCAL_READ_HOOKS = new Set();
1224
1225
  function getRegisteredFeature(name) {
1225
1226
  return FEATURE_REGISTRY.get(name.toLowerCase());
@@ -1241,6 +1242,20 @@ function notifyGlobalRead(name, context) {
1241
1242
  }
1242
1243
  }
1243
1244
  }
1245
+ function notifyLocalWrite(name, value, context) {
1246
+ if (LOCAL_WRITE_HOOKS.size === 0)
1247
+ return;
1248
+ for (const hook of LOCAL_WRITE_HOOKS) {
1249
+ try {
1250
+ hook(name, value, context);
1251
+ }
1252
+ catch (err) {
1253
+ if (typeof console !== 'undefined') {
1254
+ console.error('[hyperfixi] localWriteHook threw:', err);
1255
+ }
1256
+ }
1257
+ }
1258
+ }
1244
1259
  function notifyLocalRead(name, context) {
1245
1260
  if (LOCAL_READ_HOOKS.size === 0)
1246
1261
  return;
@@ -1271,6 +1286,68 @@ function setGlobal(context, name, value) {
1271
1286
  }
1272
1287
  }
1273
1288
 
1289
+ const elementScopes = new WeakMap();
1290
+ function getElementScopeMap(element) {
1291
+ let map = elementScopes.get(element);
1292
+ if (!map) {
1293
+ map = new Map();
1294
+ elementScopes.set(element, map);
1295
+ }
1296
+ return map;
1297
+ }
1298
+ function resolveScopeOwner(context) {
1299
+ const owner = context.owner;
1300
+ if (owner)
1301
+ return owner;
1302
+ return context.me instanceof Element ? context.me : null;
1303
+ }
1304
+ function getElementVar(context, name) {
1305
+ const owner = resolveScopeOwner(context);
1306
+ if (!owner)
1307
+ return undefined;
1308
+ notifyLocalRead(name, context);
1309
+ return getElementScopeMap(owner).get(name);
1310
+ }
1311
+ function setElementVar(context, name, value) {
1312
+ const owner = resolveScopeOwner(context);
1313
+ if (!owner)
1314
+ return;
1315
+ getElementScopeMap(owner).set(name, value);
1316
+ notifyLocalWrite(name, value, context);
1317
+ }
1318
+
1319
+ function convertToNumber(value) {
1320
+ if (value === null || value === undefined) {
1321
+ return 0;
1322
+ }
1323
+ if (typeof value === 'number') {
1324
+ return isFinite(value) ? value : 0;
1325
+ }
1326
+ if (typeof value === 'string') {
1327
+ const parsed = parseFloat(value);
1328
+ return parsed;
1329
+ }
1330
+ if (typeof value === 'boolean') {
1331
+ return value ? 1 : 0;
1332
+ }
1333
+ if (Array.isArray(value)) {
1334
+ return value.length;
1335
+ }
1336
+ if (typeof value === 'object') {
1337
+ if ('length' in value && typeof value.length === 'number') {
1338
+ return value.length;
1339
+ }
1340
+ if (typeof value.valueOf === 'function') {
1341
+ const result = value.valueOf();
1342
+ if (typeof result === 'number') {
1343
+ return result;
1344
+ }
1345
+ }
1346
+ return NaN;
1347
+ }
1348
+ return 0;
1349
+ }
1350
+
1274
1351
  class ExpressionTypeRegistry {
1275
1352
  constructor(config = {}) {
1276
1353
  this.types = new Map();
@@ -6974,7 +7051,7 @@ function parseColonVariable(ctx) {
6974
7051
  return {
6975
7052
  type: 'identifier',
6976
7053
  name: varToken.value,
6977
- scope: 'local',
7054
+ scope: 'element',
6978
7055
  start: varToken.start - 1,
6979
7056
  end: varToken.end,
6980
7057
  };
@@ -7026,7 +7103,7 @@ function parseTargetFallback(ctx) {
7026
7103
  expression: {
7027
7104
  type: 'identifier',
7028
7105
  name: varToken.value,
7029
- scope: 'local',
7106
+ scope: 'element',
7030
7107
  start: varToken.start,
7031
7108
  end: varToken.end,
7032
7109
  },
@@ -7107,6 +7184,11 @@ function parseSetCommand(ctx, identifierNode) {
7107
7184
  }
7108
7185
  ctx.advance();
7109
7186
  const value = ctx.parseExpression() ?? ctx.parsePrimary();
7187
+ let scopeTarget;
7188
+ if (ctx.check(KEYWORDS.ON) && tokenLooksLikeElementTarget(ctx.peekAt(1))) {
7189
+ ctx.advance();
7190
+ scopeTarget = parseOneArgument(ctx);
7191
+ }
7110
7192
  const finalArgs = [];
7111
7193
  if (targetExpression) {
7112
7194
  finalArgs.push(targetExpression);
@@ -7118,11 +7200,22 @@ function parseSetCommand(ctx, identifierNode) {
7118
7200
  if (value) {
7119
7201
  finalArgs.push(value);
7120
7202
  }
7121
- return CommandNodeBuilder.fromIdentifier(identifierNode)
7122
- .withArgs(...finalArgs)
7123
- .endingAt(ctx.getPosition())
7124
- .build();
7203
+ const builder = CommandNodeBuilder.fromIdentifier(identifierNode).withArgs(...finalArgs);
7204
+ if (scopeTarget) {
7205
+ builder.withModifier('on', scopeTarget);
7206
+ }
7207
+ return builder.endingAt(ctx.getPosition()).build();
7208
+ }
7209
+ function tokenLooksLikeElementTarget(token) {
7210
+ if (!token)
7211
+ return false;
7212
+ if (token.kind === 'selector')
7213
+ return true;
7214
+ if (token.value.startsWith('<'))
7215
+ return true;
7216
+ return ELEMENT_PRONOUNS.has(token.value);
7125
7217
  }
7218
+ const ELEMENT_PRONOUNS = new Set(['me', 'my', 'it', 'its', 'you', 'your', 'yourself']);
7126
7219
  function parseIncrementDecrementCommand(ctx, commandToken) {
7127
7220
  const commandName = commandToken.value;
7128
7221
  const isIncrement = commandName === 'increment';
@@ -7168,6 +7261,7 @@ function parseIncrementDecrementCommand(ctx, commandToken) {
7168
7261
  column: commandToken.column,
7169
7262
  };
7170
7263
  const binaryExpr = createBinaryExpression(operator, target, amount, pos);
7264
+ binaryExpr.coerceNumeric = true;
7171
7265
  const toIdentifier = createIdentifier(KEYWORDS.TO, pos);
7172
7266
  const args = [targetWithScope, toIdentifier, binaryExpr];
7173
7267
  return CommandNodeBuilder.from(commandToken)
@@ -7786,6 +7880,18 @@ class Parser {
7786
7880
  this.advance();
7787
7881
  continue;
7788
7882
  }
7883
+ if (this.check('then')) {
7884
+ const next = this.peekAt(1);
7885
+ const startsFeature = next !== null &&
7886
+ (next.value === 'init' ||
7887
+ next.value === 'on' ||
7888
+ next.value === 'def' ||
7889
+ getRegisteredFeature(next.value) !== undefined);
7890
+ if (startsFeature) {
7891
+ this.advance();
7892
+ continue;
7893
+ }
7894
+ }
7789
7895
  if (this.check('init')) {
7790
7896
  this.advance();
7791
7897
  const initBlock = this.parseTopLevelInitBlock();
@@ -8586,7 +8692,7 @@ class Parser {
8586
8692
  return {
8587
8693
  type: 'identifier',
8588
8694
  name: varToken.value,
8589
- scope: 'local',
8695
+ scope: 'element',
8590
8696
  start: varToken.start - 1,
8591
8697
  end: varToken.end,
8592
8698
  line: varToken.line,
@@ -9213,6 +9319,33 @@ class Parser {
9213
9319
  return null;
9214
9320
  }
9215
9321
  }
9322
+ parseTemporalKeywordModifiers(modifiers) {
9323
+ if (!this.check('debounced') && !this.check('throttled')) {
9324
+ return;
9325
+ }
9326
+ const modToken = this.advance();
9327
+ const modName = modToken.value.toLowerCase();
9328
+ const modifierKey = modName === 'debounced' ? 'debounce' : 'throttle';
9329
+ if (this.match('at')) {
9330
+ if (this.checkTimeExpression()) {
9331
+ const timeToken = this.advance();
9332
+ const delayMs = parseTimeToMs(timeToken.value);
9333
+ modifiers[modifierKey] = delayMs;
9334
+ debug.parse(`🔧 parseEventHandler: Parsed '${modName} at ${timeToken.value}' (${delayMs}ms)`);
9335
+ }
9336
+ else if (this.matchNumber()) {
9337
+ const delayMs = parseInt(this.previous().value, 10);
9338
+ modifiers[modifierKey] = delayMs;
9339
+ debug.parse(`🔧 parseEventHandler: Parsed '${modName} at ${delayMs}'`);
9340
+ }
9341
+ else {
9342
+ this.addError(`Expected time expression after '${modName} at', got: ${this.peek().value}`);
9343
+ }
9344
+ }
9345
+ else {
9346
+ this.addError(`Expected 'at' after '${modName}'`);
9347
+ }
9348
+ }
9216
9349
  parseEventHandler() {
9217
9350
  debug.parse(`🔧 parseEventHandler: ENTRY - parsing event handler`);
9218
9351
  const eventNames = [];
@@ -9300,30 +9433,7 @@ class Parser {
9300
9433
  debug.parse(`🔧 parseEventHandler: Warning - unknown modifier '.${modName}'`);
9301
9434
  }
9302
9435
  }
9303
- if (this.check('debounced') || this.check('throttled')) {
9304
- const modToken = this.advance();
9305
- const modName = modToken.value.toLowerCase();
9306
- const modifierKey = modName === 'debounced' ? 'debounce' : 'throttle';
9307
- if (this.match('at')) {
9308
- if (this.checkTimeExpression()) {
9309
- const timeToken = this.advance();
9310
- const delayMs = parseTimeToMs(timeToken.value);
9311
- modifiers[modifierKey] = delayMs;
9312
- debug.parse(`🔧 parseEventHandler: Parsed '${modName} at ${timeToken.value}' (${delayMs}ms)`);
9313
- }
9314
- else if (this.matchNumber()) {
9315
- const delayMs = parseInt(this.previous().value, 10);
9316
- modifiers[modifierKey] = delayMs;
9317
- debug.parse(`🔧 parseEventHandler: Parsed '${modName} at ${delayMs}'`);
9318
- }
9319
- else {
9320
- this.addError(`Expected time expression after '${modName} at', got: ${this.peek().value}`);
9321
- }
9322
- }
9323
- else {
9324
- this.addError(`Expected 'at' after '${modName}'`);
9325
- }
9326
- }
9436
+ this.parseTemporalKeywordModifiers(modifiers);
9327
9437
  if (firstOnceAlias) {
9328
9438
  modifiers.once = true;
9329
9439
  }
@@ -9340,6 +9450,7 @@ class Parser {
9340
9450
  const targetToken = this.advance();
9341
9451
  target = targetToken.value;
9342
9452
  debug.parse(`🔧 parseEventHandler: Parsed 'from' target: ${target} (kind: ${targetToken.kind})`);
9453
+ this.parseTemporalKeywordModifiers(modifiers);
9343
9454
  }
9344
9455
  let attributeName;
9345
9456
  if (this.match('of')) {
@@ -10310,6 +10421,10 @@ class Parser {
10310
10421
  }
10311
10422
  return this.tokens[this.current];
10312
10423
  }
10424
+ peekAt(offset) {
10425
+ const index = this.current + offset;
10426
+ return index >= 0 && index < this.tokens.length ? this.tokens[index] : null;
10427
+ }
10313
10428
  previous() {
10314
10429
  return this.tokens[this.current - 1];
10315
10430
  }
@@ -10514,10 +10629,7 @@ class Parser {
10514
10629
  restorePosition: (pos) => {
10515
10630
  parser.current = pos;
10516
10631
  },
10517
- peekAt: (offset) => {
10518
- const index = parser.current + offset;
10519
- return index >= 0 && index < parser.tokens.length ? parser.tokens[index] : null;
10520
- },
10632
+ peekAt: (offset) => parser.peekAt(offset),
10521
10633
  getInputSlice: (start, end) => {
10522
10634
  if (!this.originalInput)
10523
10635
  return '';
@@ -10661,7 +10773,7 @@ function evaluateExpressionSync(node, context) {
10661
10773
  case 'selector':
10662
10774
  return evaluateSelectorSync(n, context);
10663
10775
  case 'identifier':
10664
- return resolveIdentifierSync(n.name, context);
10776
+ return resolveIdentifierSync(n.name, context, n.scope);
10665
10777
  case 'contextReference':
10666
10778
  return resolveContextReferenceSync(n.contextType, context);
10667
10779
  case 'arrayLiteral':
@@ -10697,7 +10809,7 @@ function evaluateExpressionSync(node, context) {
10697
10809
  throw new NotSyncEvaluable(n?.type ?? 'unknown');
10698
10810
  }
10699
10811
  }
10700
- function resolveIdentifierSync(name, context) {
10812
+ function resolveIdentifierSync(name, context, scope) {
10701
10813
  if (name === 'me' || name === 'my' || name === 'I')
10702
10814
  return context.me;
10703
10815
  if (name === 'you' || name === 'your' || name === 'yourself')
@@ -10708,6 +10820,8 @@ function resolveIdentifierSync(name, context) {
10708
10820
  return typeof window !== 'undefined' ? window : globalThis;
10709
10821
  if (name === 'document')
10710
10822
  return typeof document !== 'undefined' ? document : undefined;
10823
+ if (scope === 'element')
10824
+ return getElementVar(context, name);
10711
10825
  if (context.locals?.has(name))
10712
10826
  return context.locals.get(name);
10713
10827
  if (context.globals?.has(name))
@@ -10837,6 +10951,9 @@ async function evaluateIdentifier(node, context) {
10837
10951
  notifyGlobalRead(name, context);
10838
10952
  return context.globals.get(name);
10839
10953
  }
10954
+ if (node.scope === 'element') {
10955
+ return getElementVar(context, name);
10956
+ }
10840
10957
  if (context.locals && context.locals.has(name)) {
10841
10958
  notifyLocalRead(name, context);
10842
10959
  return context.locals.get(name);
@@ -10872,6 +10989,9 @@ async function evaluateBinaryExpression(node, context) {
10872
10989
  else if (leftNode.scope === 'global') {
10873
10990
  setGlobal(context, name, value);
10874
10991
  }
10992
+ else if (leftNode.scope === 'element') {
10993
+ setElementVar(context, name, value);
10994
+ }
10875
10995
  else {
10876
10996
  if (!context.locals)
10877
10997
  context.locals = new Map();
@@ -10953,14 +11073,26 @@ async function evaluateBinaryExpression(node, context) {
10953
11073
  const L = node.ignoringCase ? applyCI(left) : left;
10954
11074
  const R = node.ignoringCase ? applyCI(right) : right;
10955
11075
  switch (operator) {
10956
- case '+':
10957
- if (typeof left === 'string' || typeof right === 'string') {
10958
- return String(left ?? '') + String(right ?? '');
10959
- }
10960
- if (Array.isArray(left)) {
10961
- return Array.isArray(right) ? [...left, ...right] : [...left, right];
10962
- }
10963
- return unwrapTypedResult(await getExpr(context, 'addition').evaluate(context, { left, right }));
11076
+ case '+': {
11077
+ let addLeft = left;
11078
+ let addRight = right;
11079
+ if (node.coerceNumeric) {
11080
+ if (typeof addLeft === 'string')
11081
+ addLeft = convertToNumber(addLeft);
11082
+ if (typeof addRight === 'string')
11083
+ addRight = convertToNumber(addRight);
11084
+ }
11085
+ if (typeof addLeft === 'string' || typeof addRight === 'string') {
11086
+ return String(addLeft ?? '') + String(addRight ?? '');
11087
+ }
11088
+ if (Array.isArray(addLeft)) {
11089
+ return Array.isArray(addRight) ? [...addLeft, ...addRight] : [...addLeft, addRight];
11090
+ }
11091
+ return unwrapTypedResult(await getExpr(context, 'addition').evaluate(context, {
11092
+ left: addLeft,
11093
+ right: addRight,
11094
+ }));
11095
+ }
10964
11096
  case '-':
10965
11097
  return unwrapTypedResult(await getExpr(context, 'subtraction').evaluate(context, { left, right }));
10966
11098
  case '*':
@@ -11742,6 +11874,7 @@ class ContextBridge {
11742
11874
  static toTyped(context) {
11743
11875
  return {
11744
11876
  me: context.me,
11877
+ ...(context.owner !== undefined && { owner: context.owner }),
11745
11878
  it: context.it,
11746
11879
  you: context.you,
11747
11880
  result: context.result,
@@ -1,5 +1,6 @@
1
1
  export interface CoreExecutionContext {
2
2
  me: Element | null;
3
+ owner?: Element | null;
3
4
  you: Element | null;
4
5
  it: unknown;
5
6
  readonly event?: Event | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperfixi/core",
3
- "version": "2.6.0",
3
+ "version": "2.7.1",
4
4
  "description": "Multilingual, tree-shakeable hyperscript",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -278,21 +278,21 @@
278
278
  },
279
279
  "homepage": "https://github.com/codetalcott/hyperfixi/tree/main/packages/core#readme",
280
280
  "engines": {
281
- "node": ">=18.0.0"
281
+ "node": ">=24"
282
282
  },
283
283
  "sideEffects": false,
284
284
  "publishConfig": {
285
285
  "access": "public"
286
286
  },
287
287
  "dependencies": {
288
- "@lokascript/intent": "^2.6.0",
289
- "@lokascript/semantic": "^2.6.0",
288
+ "@lokascript/intent": "^2.7.1",
289
+ "@lokascript/semantic": "^2.7.1",
290
290
  "morphlex": "^1.0.5",
291
291
  "tslib": "^2.8.1"
292
292
  },
293
293
  "peerDependencies": {
294
- "@hyperfixi/patterns-reference": "^2.6.0",
295
- "@lokascript/framework": "^2.6.0"
294
+ "@hyperfixi/patterns-reference": "^2.7.1",
295
+ "@lokascript/framework": "^2.7.1"
296
296
  },
297
297
  "peerDependenciesMeta": {
298
298
  "@hyperfixi/patterns-reference": {