@hyperfixi/core 3.1.1 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -36
- package/dist/ast/command-slots.d.ts +1 -1
- package/dist/ast/nodes.d.ts +2 -0
- package/dist/ast-utils/index.cjs +193 -48
- package/dist/ast-utils/index.mjs +193 -48
- package/dist/ast-utils/interchange/types.d.ts +19 -1
- package/dist/behaviors/index.cjs +19 -0
- package/dist/behaviors/index.mjs +19 -0
- package/dist/bundle-generator/index.cjs +24 -9
- package/dist/bundle-generator/index.mjs +24 -9
- package/dist/bundle-generator/parser-templates.d.ts +1 -1
- package/dist/chunks/bridge-Cnj8dQFV.js +2 -0
- package/dist/chunks/browser-modular-BNDYF6Dq.js +2 -0
- package/dist/chunks/index-DQi2o8Lk.js +2 -0
- package/dist/chunks/index-LLuwEQp0.js +2 -0
- package/dist/chunks/index-uCOQ8Qn0.js +2 -0
- package/dist/commands/animation/transition.d.ts +12 -6
- package/dist/commands/helpers/loop-executor.d.ts +2 -2
- package/dist/commands/helpers/numeric-target-parser.d.ts +1 -1
- package/dist/commands/helpers/variable-access.d.ts +5 -2
- package/dist/commands/helpers/write-target.d.ts +5 -0
- package/dist/commands/index.cjs +929 -425
- package/dist/commands/index.mjs +929 -425
- package/dist/commands/templates/render.d.ts +4 -4
- package/dist/commands/utility/beep.d.ts +0 -3
- package/dist/expressions/index.cjs +7 -13
- package/dist/expressions/index.mjs +7 -13
- package/dist/htmx/i18n-hooks.d.ts +4 -1
- package/dist/hyperfixi-browser-classic-i18n.js +1 -1
- package/dist/hyperfixi-browser.js +1 -1
- package/dist/hyperfixi-classic-i18n.js +1 -1
- package/dist/hyperfixi-hx-v4.js +1 -1
- package/dist/hyperfixi-hx.js +1 -1
- package/dist/hyperfixi-hybrid-complete.js +1 -1
- package/dist/hyperfixi-hybrid-hx.js +1 -1
- package/dist/hyperfixi-multilingual.js +1 -1
- package/dist/hyperfixi.js +1 -1
- package/dist/hyperfixi.mjs +1 -1
- package/dist/index.cjs +1140 -485
- package/dist/index.min.js +1 -1
- package/dist/index.mjs +1140 -485
- package/dist/lib/swap-executor.d.ts +1 -1
- package/dist/lokascript-browser-classic-i18n.js +1 -1
- package/dist/lokascript-browser.js +1 -1
- package/dist/lokascript-hybrid-complete.js +1 -1
- package/dist/lokascript-hybrid-hx.js +1 -1
- package/dist/lokascript-multilingual.js +1 -1
- package/dist/metadata.cjs +9 -9
- package/dist/metadata.d.ts +1 -1
- package/dist/metadata.mjs +9 -9
- package/dist/parser/command-grammar.d.ts +1 -0
- package/dist/parser/full-parser.cjs +387 -153
- package/dist/parser/full-parser.mjs +387 -153
- package/dist/parser/helpers/parsing-helpers.d.ts +3 -0
- package/dist/parser/hybrid/index.cjs +3 -0
- package/dist/parser/hybrid/index.mjs +3 -0
- package/dist/parser/hybrid/parser-core.cjs +3 -0
- package/dist/parser/hybrid/parser-core.mjs +3 -0
- package/dist/parser/hybrid-parser.cjs +3 -0
- package/dist/parser/hybrid-parser.mjs +3 -0
- package/dist/parser/parser-types.d.ts +2 -0
- package/dist/parser/parser.d.ts +9 -0
- package/dist/parser/pratt-parser.d.ts +2 -0
- package/dist/registry/index.cjs +526 -210
- package/dist/registry/index.mjs +526 -210
- package/dist/utils/beep.d.ts +8 -0
- package/dist/version.d.ts +1 -1
- package/package.json +21 -18
- package/vocab/htmx/ar.js +22 -20
- package/vocab/htmx/bn.js +21 -5
- package/vocab/htmx/de.js +33 -10
- package/vocab/htmx/en.js +4 -1
- package/vocab/htmx/es.js +28 -9
- package/vocab/htmx/fr.js +28 -8
- package/vocab/htmx/he.js +22 -1
- package/vocab/htmx/hi.js +28 -7
- package/vocab/htmx/id.js +29 -10
- package/vocab/htmx/it.js +26 -5
- package/vocab/htmx/ja.js +36 -11
- package/vocab/htmx/ko.js +27 -6
- package/vocab/htmx/ms.js +24 -1
- package/vocab/htmx/pl.js +27 -7
- package/vocab/htmx/pt.js +30 -9
- package/vocab/htmx/qu.js +32 -9
- package/vocab/htmx/ru.js +28 -6
- package/vocab/htmx/sw.js +28 -8
- package/vocab/htmx/th.js +19 -1
- package/vocab/htmx/tl.js +24 -1
- package/vocab/htmx/tr.js +34 -8
- package/vocab/htmx/uk.js +28 -6
- package/vocab/htmx/vi.js +27 -19
- package/vocab/htmx/zh.js +28 -9
- package/dist/chunks/bridge-D7qrHhV0.js +0 -2
- package/dist/chunks/browser-modular-C4LFxfoS.js +0 -2
- package/dist/chunks/index-C7lqD4mo.js +0 -2
- package/dist/chunks/index-CAPhqA6k.js +0 -2
- package/dist/chunks/index-CLdRyZaP.js +0 -2
package/dist/ast-utils/index.mjs
CHANGED
|
@@ -2252,6 +2252,14 @@ function convertNode(node, infer) {
|
|
|
2252
2252
|
...pos(node),
|
|
2253
2253
|
};
|
|
2254
2254
|
case 'identifier':
|
|
2255
|
+
if (node.scope === 'element' || node.scope === 'local' || node.scope === 'global') {
|
|
2256
|
+
return {
|
|
2257
|
+
type: 'variable',
|
|
2258
|
+
name: (node.name ?? ''),
|
|
2259
|
+
scope: node.scope,
|
|
2260
|
+
...pos(node),
|
|
2261
|
+
};
|
|
2262
|
+
}
|
|
2255
2263
|
return {
|
|
2256
2264
|
type: 'identifier',
|
|
2257
2265
|
value: (node.name ?? node.value ?? ''),
|
|
@@ -2274,6 +2282,15 @@ function convertNode(node, infer) {
|
|
|
2274
2282
|
operand: convertNode(node.argument ?? node.operand, infer),
|
|
2275
2283
|
...pos(node),
|
|
2276
2284
|
};
|
|
2285
|
+
case 'typeCheckExpression':
|
|
2286
|
+
return {
|
|
2287
|
+
type: 'typeCheck',
|
|
2288
|
+
value: convertNode(node.value, infer),
|
|
2289
|
+
typeName: String(node.typeName),
|
|
2290
|
+
nullOk: node.nullOk !== false,
|
|
2291
|
+
negated: node.negated === true,
|
|
2292
|
+
...pos(node),
|
|
2293
|
+
};
|
|
2277
2294
|
case 'timeExpression':
|
|
2278
2295
|
return { type: 'literal', value: node.value, ...pos(node) };
|
|
2279
2296
|
case 'templateLiteral':
|
|
@@ -2285,6 +2302,28 @@ function convertNode(node, infer) {
|
|
|
2285
2302
|
scope: (node.scope ?? 'local'),
|
|
2286
2303
|
...pos(node),
|
|
2287
2304
|
};
|
|
2305
|
+
case 'arrayLiteral':
|
|
2306
|
+
return {
|
|
2307
|
+
type: 'array',
|
|
2308
|
+
elements: (node.elements ?? []).map(element => convertNode(element, infer)),
|
|
2309
|
+
...pos(node),
|
|
2310
|
+
};
|
|
2311
|
+
case 'attributeAccess':
|
|
2312
|
+
return {
|
|
2313
|
+
type: 'possessive',
|
|
2314
|
+
object: { type: 'identifier', value: 'me' },
|
|
2315
|
+
property: `@${node.attributeName}`,
|
|
2316
|
+
...pos(node),
|
|
2317
|
+
};
|
|
2318
|
+
case 'propertyOfExpression': {
|
|
2319
|
+
const property = node.property;
|
|
2320
|
+
return {
|
|
2321
|
+
type: 'possessive',
|
|
2322
|
+
object: convertNode(node.target, infer),
|
|
2323
|
+
property: (property?.name ?? property?.value ?? ''),
|
|
2324
|
+
...pos(node),
|
|
2325
|
+
};
|
|
2326
|
+
}
|
|
2288
2327
|
case 'htmlSelector':
|
|
2289
2328
|
return {
|
|
2290
2329
|
type: 'selector',
|
|
@@ -2336,6 +2375,21 @@ function convertCommand$1(node, infer) {
|
|
|
2336
2375
|
if (name === 'repeat') {
|
|
2337
2376
|
return convertRepeatCommand(node, infer);
|
|
2338
2377
|
}
|
|
2378
|
+
const original = node.originalCommand;
|
|
2379
|
+
const sum = node.modifiers?.to;
|
|
2380
|
+
const counted = node.args?.[0];
|
|
2381
|
+
if (name === 'set' &&
|
|
2382
|
+
(original === 'increment' || original === 'decrement') &&
|
|
2383
|
+
sum?.type === 'binaryExpression' &&
|
|
2384
|
+
counted) {
|
|
2385
|
+
return convertCommand$1({
|
|
2386
|
+
...node,
|
|
2387
|
+
name: original,
|
|
2388
|
+
args: [counted],
|
|
2389
|
+
modifiers: { by: sum.right },
|
|
2390
|
+
originalCommand: undefined,
|
|
2391
|
+
}, infer);
|
|
2392
|
+
}
|
|
2339
2393
|
const args = (node.args ?? []).map(arg => convertNode(arg, infer));
|
|
2340
2394
|
const target = node.target ? convertNode(node.target, infer) : undefined;
|
|
2341
2395
|
const modifiers = node.modifiers
|
|
@@ -2384,6 +2438,64 @@ function convertIfCommand(node, infer) {
|
|
|
2384
2438
|
};
|
|
2385
2439
|
}
|
|
2386
2440
|
function convertRepeatCommand(node, infer) {
|
|
2441
|
+
const slots = (node.modifiers ?? {});
|
|
2442
|
+
const form = slotText(slots.loopType);
|
|
2443
|
+
if (form === undefined)
|
|
2444
|
+
return convertPositionalRepeat(node, infer);
|
|
2445
|
+
const [bodyBlock, elseBlock] = (node.args ?? []);
|
|
2446
|
+
const body = extractBlockCommands(bodyBlock, infer);
|
|
2447
|
+
const indexName = slotText(slots.index);
|
|
2448
|
+
const shared = {
|
|
2449
|
+
...(indexName ? { indexName } : {}),
|
|
2450
|
+
...(elseBlock ? { elseBody: extractBlockCommands(elseBlock, infer) } : {}),
|
|
2451
|
+
...pos(node),
|
|
2452
|
+
};
|
|
2453
|
+
const operand = (slot) => (slot ? convertNode(slot, infer) : undefined);
|
|
2454
|
+
switch (form) {
|
|
2455
|
+
case 'for':
|
|
2456
|
+
return {
|
|
2457
|
+
type: 'foreach',
|
|
2458
|
+
itemName: slotText(slots.for) ?? 'it',
|
|
2459
|
+
collection: operand(slots.in) ?? { type: 'literal', value: null },
|
|
2460
|
+
body,
|
|
2461
|
+
...shared,
|
|
2462
|
+
};
|
|
2463
|
+
case 'times':
|
|
2464
|
+
return { type: 'repeat', count: operand(slots.times), body, ...shared };
|
|
2465
|
+
case 'while':
|
|
2466
|
+
case 'until': {
|
|
2467
|
+
const test = operand(slots[form]) ?? { type: 'literal', value: form === 'until' };
|
|
2468
|
+
return {
|
|
2469
|
+
type: 'while',
|
|
2470
|
+
condition: form === 'until' ? { type: 'unary', operator: 'not', operand: test } : test,
|
|
2471
|
+
...(slots.bottomTested?.value === true ? { bottomTested: true } : {}),
|
|
2472
|
+
body,
|
|
2473
|
+
...shared,
|
|
2474
|
+
};
|
|
2475
|
+
}
|
|
2476
|
+
case 'until-event': {
|
|
2477
|
+
const event = slotText(slots.event);
|
|
2478
|
+
if (!event) {
|
|
2479
|
+
return { type: 'error', message: "'repeat until event' has no event name", ...pos(node) };
|
|
2480
|
+
}
|
|
2481
|
+
const target = operand(slots.from);
|
|
2482
|
+
return {
|
|
2483
|
+
type: 'repeat',
|
|
2484
|
+
untilEvent: event,
|
|
2485
|
+
...(target ? { untilEventTarget: target } : {}),
|
|
2486
|
+
body,
|
|
2487
|
+
...shared,
|
|
2488
|
+
};
|
|
2489
|
+
}
|
|
2490
|
+
default:
|
|
2491
|
+
return { type: 'repeat', body, ...shared };
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
function slotText(slot) {
|
|
2495
|
+
const text = slot?.value ?? slot?.name;
|
|
2496
|
+
return typeof text === 'string' ? text : undefined;
|
|
2497
|
+
}
|
|
2498
|
+
function convertPositionalRepeat(node, infer) {
|
|
2387
2499
|
const args = (node.args ?? []);
|
|
2388
2500
|
if (args.length === 0) {
|
|
2389
2501
|
return { type: 'repeat', body: [], ...pos(node) };
|
|
@@ -2700,6 +2812,15 @@ function toCoreAST(node) {
|
|
|
2700
2812
|
prefix: true,
|
|
2701
2813
|
...nodePos(node),
|
|
2702
2814
|
};
|
|
2815
|
+
case 'typeCheck':
|
|
2816
|
+
return {
|
|
2817
|
+
type: 'typeCheckExpression',
|
|
2818
|
+
value: toCoreAST(node.value),
|
|
2819
|
+
typeName: node.typeName,
|
|
2820
|
+
nullOk: node.nullOk,
|
|
2821
|
+
negated: node.negated,
|
|
2822
|
+
...nodePos(node),
|
|
2823
|
+
};
|
|
2703
2824
|
case 'member':
|
|
2704
2825
|
return {
|
|
2705
2826
|
type: 'memberExpression',
|
|
@@ -2710,6 +2831,12 @@ function toCoreAST(node) {
|
|
|
2710
2831
|
computed: node.computed ?? false,
|
|
2711
2832
|
...nodePos(node),
|
|
2712
2833
|
};
|
|
2834
|
+
case 'array':
|
|
2835
|
+
return {
|
|
2836
|
+
type: 'arrayLiteral',
|
|
2837
|
+
elements: node.elements.map(element => toCoreAST(element)),
|
|
2838
|
+
...nodePos(node),
|
|
2839
|
+
};
|
|
2713
2840
|
case 'possessive':
|
|
2714
2841
|
return {
|
|
2715
2842
|
type: 'possessiveExpression',
|
|
@@ -2772,72 +2899,85 @@ function convertCommand(node) {
|
|
|
2772
2899
|
}
|
|
2773
2900
|
return result;
|
|
2774
2901
|
}
|
|
2902
|
+
function block(nodes) {
|
|
2903
|
+
return { type: 'block', commands: nodes.map(n => toCoreAST(n)), ...POS };
|
|
2904
|
+
}
|
|
2775
2905
|
function convertIf(node) {
|
|
2776
|
-
const
|
|
2906
|
+
const [elseIf, ...laterElseIfs] = node.elseIfBranches ?? [];
|
|
2907
|
+
const elseBranch = elseIf
|
|
2908
|
+
? [
|
|
2909
|
+
{
|
|
2910
|
+
type: 'if',
|
|
2911
|
+
condition: elseIf.condition,
|
|
2912
|
+
thenBranch: elseIf.body,
|
|
2913
|
+
elseIfBranches: laterElseIfs,
|
|
2914
|
+
...(node.elseBranch ? { elseBranch: node.elseBranch } : {}),
|
|
2915
|
+
},
|
|
2916
|
+
]
|
|
2917
|
+
: node.elseBranch;
|
|
2918
|
+
return {
|
|
2777
2919
|
type: 'command',
|
|
2778
2920
|
name: 'if',
|
|
2779
2921
|
isBlocking: true,
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2922
|
+
args: [
|
|
2923
|
+
toCoreAST(node.condition),
|
|
2924
|
+
block(node.thenBranch),
|
|
2925
|
+
...(elseBranch ? [block(elseBranch)] : []),
|
|
2926
|
+
],
|
|
2783
2927
|
...nodePos(node),
|
|
2784
2928
|
};
|
|
2785
|
-
if (node.elseBranch) {
|
|
2786
|
-
result.elseBranch = node.elseBranch.map(n => toCoreAST(n));
|
|
2787
|
-
}
|
|
2788
|
-
return result;
|
|
2789
2929
|
}
|
|
2790
|
-
function
|
|
2791
|
-
const
|
|
2930
|
+
function loopCommand(node, form, operands) {
|
|
2931
|
+
const modifiers = { loopType: slotString(form) };
|
|
2932
|
+
for (const [slot, value] of Object.entries(operands)) {
|
|
2933
|
+
if (value)
|
|
2934
|
+
modifiers[slot] = value;
|
|
2935
|
+
}
|
|
2936
|
+
if (node.indexName)
|
|
2937
|
+
modifiers.index = slotString(node.indexName);
|
|
2938
|
+
return {
|
|
2792
2939
|
type: 'command',
|
|
2793
2940
|
name: 'repeat',
|
|
2794
2941
|
isBlocking: true,
|
|
2795
|
-
|
|
2796
|
-
|
|
2942
|
+
args: [block(node.body), ...(node.elseBody ? [block(node.elseBody)] : [])],
|
|
2943
|
+
modifiers,
|
|
2797
2944
|
...nodePos(node),
|
|
2798
2945
|
};
|
|
2946
|
+
}
|
|
2947
|
+
function slotString(value) {
|
|
2948
|
+
return { type: 'string', value, ...POS };
|
|
2949
|
+
}
|
|
2950
|
+
function convertRepeat(node) {
|
|
2799
2951
|
if (node.count !== undefined) {
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2952
|
+
const times = typeof node.count === 'number'
|
|
2953
|
+
? { type: 'literal', value: node.count, raw: String(node.count), ...POS }
|
|
2954
|
+
: toCoreAST(node.count);
|
|
2955
|
+
return loopCommand(node, 'times', { times });
|
|
2956
|
+
}
|
|
2957
|
+
if (node.whileCondition !== undefined) {
|
|
2958
|
+
return loopCommand(node, 'while', { while: toCoreAST(node.whileCondition) });
|
|
2959
|
+
}
|
|
2960
|
+
if (node.untilEvent !== undefined) {
|
|
2961
|
+
return loopCommand(node, 'until-event', {
|
|
2962
|
+
event: slotString(node.untilEvent),
|
|
2963
|
+
from: node.untilEventTarget ? toCoreAST(node.untilEventTarget) : undefined,
|
|
2964
|
+
});
|
|
2813
2965
|
}
|
|
2814
|
-
return
|
|
2966
|
+
return loopCommand(node, 'forever', {});
|
|
2815
2967
|
}
|
|
2816
2968
|
function convertForEach(node) {
|
|
2817
|
-
return {
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
loopVariant: 'for',
|
|
2822
|
-
itemName: node.itemName,
|
|
2823
|
-
...(node.indexName ? { indexName: node.indexName } : {}),
|
|
2824
|
-
collection: toCoreAST(node.collection),
|
|
2825
|
-
body: node.body.map(n => toCoreAST(n)),
|
|
2826
|
-
args: [],
|
|
2827
|
-
...nodePos(node),
|
|
2828
|
-
};
|
|
2969
|
+
return loopCommand(node, 'for', {
|
|
2970
|
+
for: slotString(node.itemName),
|
|
2971
|
+
in: toCoreAST(node.collection),
|
|
2972
|
+
});
|
|
2829
2973
|
}
|
|
2830
2974
|
function convertWhile(node) {
|
|
2831
|
-
return {
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
body: node.body.map(n => toCoreAST(n)),
|
|
2838
|
-
args: [],
|
|
2839
|
-
...nodePos(node),
|
|
2840
|
-
};
|
|
2975
|
+
return loopCommand(node, 'while', {
|
|
2976
|
+
while: toCoreAST(node.condition),
|
|
2977
|
+
bottomTested: node.bottomTested
|
|
2978
|
+
? { type: 'literal', value: true, raw: 'true', ...POS }
|
|
2979
|
+
: undefined,
|
|
2980
|
+
});
|
|
2841
2981
|
}
|
|
2842
2982
|
function flattenEventModifiers(modifiers) {
|
|
2843
2983
|
if (!modifiers)
|
|
@@ -3241,6 +3381,9 @@ function getChildren(node) {
|
|
|
3241
3381
|
case 'unary':
|
|
3242
3382
|
children.push(node.operand);
|
|
3243
3383
|
break;
|
|
3384
|
+
case 'typeCheck':
|
|
3385
|
+
children.push(node.value);
|
|
3386
|
+
break;
|
|
3244
3387
|
case 'member': {
|
|
3245
3388
|
const m = node;
|
|
3246
3389
|
children.push(m.object);
|
|
@@ -3378,6 +3521,8 @@ function friendlyTypeName(node) {
|
|
|
3378
3521
|
return `Binary Expression (${node.operator})`;
|
|
3379
3522
|
case 'unary':
|
|
3380
3523
|
return `Unary Expression (${node.operator})`;
|
|
3524
|
+
case 'typeCheck':
|
|
3525
|
+
return `Type Check (${node.typeName})`;
|
|
3381
3526
|
case 'literal':
|
|
3382
3527
|
return 'Literal';
|
|
3383
3528
|
case 'identifier':
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type InterchangeNode = EventNode | CommandNode | LiteralNode | IdentifierNode | SelectorNode | VariableNode | BinaryNode | UnaryNode | MemberNode | PossessiveNode | CallNode | IfNode | RepeatNode | ForEachNode | WhileNode | PositionalNode | ErrorNode;
|
|
1
|
+
export type InterchangeNode = EventNode | CommandNode | LiteralNode | IdentifierNode | SelectorNode | VariableNode | BinaryNode | UnaryNode | TypeCheckNode | MemberNode | PossessiveNode | ArrayNode | CallNode | IfNode | RepeatNode | ForEachNode | WhileNode | PositionalNode | ErrorNode;
|
|
2
2
|
export interface BaseNode {
|
|
3
3
|
readonly type: string;
|
|
4
4
|
readonly start?: number;
|
|
@@ -35,6 +35,13 @@ export interface UnaryNode extends BaseNode {
|
|
|
35
35
|
readonly operator: string;
|
|
36
36
|
readonly operand: InterchangeNode;
|
|
37
37
|
}
|
|
38
|
+
export interface TypeCheckNode extends BaseNode {
|
|
39
|
+
readonly type: 'typeCheck';
|
|
40
|
+
readonly value: InterchangeNode;
|
|
41
|
+
readonly typeName: string;
|
|
42
|
+
readonly nullOk: boolean;
|
|
43
|
+
readonly negated: boolean;
|
|
44
|
+
}
|
|
38
45
|
export interface MemberNode extends BaseNode {
|
|
39
46
|
readonly type: 'member';
|
|
40
47
|
readonly object: InterchangeNode;
|
|
@@ -46,6 +53,10 @@ export interface PossessiveNode extends BaseNode {
|
|
|
46
53
|
readonly object: InterchangeNode;
|
|
47
54
|
readonly property: string;
|
|
48
55
|
}
|
|
56
|
+
export interface ArrayNode extends BaseNode {
|
|
57
|
+
readonly type: 'array';
|
|
58
|
+
readonly elements: readonly InterchangeNode[];
|
|
59
|
+
}
|
|
49
60
|
export interface CallNode extends BaseNode {
|
|
50
61
|
readonly type: 'call';
|
|
51
62
|
readonly callee: InterchangeNode;
|
|
@@ -92,7 +103,10 @@ export interface RepeatNode extends BaseNode {
|
|
|
92
103
|
readonly count?: number | InterchangeNode;
|
|
93
104
|
readonly whileCondition?: InterchangeNode;
|
|
94
105
|
readonly untilEvent?: string;
|
|
106
|
+
readonly untilEventTarget?: InterchangeNode;
|
|
107
|
+
readonly indexName?: string;
|
|
95
108
|
readonly body: InterchangeNode[];
|
|
109
|
+
readonly elseBody?: InterchangeNode[];
|
|
96
110
|
}
|
|
97
111
|
export interface ForEachNode extends BaseNode {
|
|
98
112
|
readonly type: 'foreach';
|
|
@@ -100,11 +114,15 @@ export interface ForEachNode extends BaseNode {
|
|
|
100
114
|
readonly indexName?: string;
|
|
101
115
|
readonly collection: InterchangeNode;
|
|
102
116
|
readonly body: InterchangeNode[];
|
|
117
|
+
readonly elseBody?: InterchangeNode[];
|
|
103
118
|
}
|
|
104
119
|
export interface WhileNode extends BaseNode {
|
|
105
120
|
readonly type: 'while';
|
|
106
121
|
readonly condition: InterchangeNode;
|
|
122
|
+
readonly bottomTested?: boolean;
|
|
123
|
+
readonly indexName?: string;
|
|
107
124
|
readonly body: InterchangeNode[];
|
|
125
|
+
readonly elseBody?: InterchangeNode[];
|
|
108
126
|
}
|
|
109
127
|
export interface EventModifiers {
|
|
110
128
|
readonly prevent?: boolean;
|
package/dist/behaviors/index.cjs
CHANGED
|
@@ -655,10 +655,29 @@ function isHTMLElement(value) {
|
|
|
655
655
|
'classList' in value);
|
|
656
656
|
}
|
|
657
657
|
|
|
658
|
+
function loneRootWithTag(content, tagName) {
|
|
659
|
+
let root;
|
|
660
|
+
if (typeof content === 'string') {
|
|
661
|
+
const template = document.createElement('template');
|
|
662
|
+
template.innerHTML = content;
|
|
663
|
+
root = template.content.firstElementChild;
|
|
664
|
+
if (!root || root.nextElementSibling)
|
|
665
|
+
return null;
|
|
666
|
+
}
|
|
667
|
+
else {
|
|
668
|
+
root = content.cloneNode(true);
|
|
669
|
+
}
|
|
670
|
+
return root.tagName === tagName && isHTMLElement(root) ? root : null;
|
|
671
|
+
}
|
|
658
672
|
function executeSwap(target, content, strategy, morphOptions) {
|
|
659
673
|
const contentStr = content !== null && !isHTMLElement(content) ? content : '';
|
|
660
674
|
const contentEl = isHTMLElement(content) ? content : null;
|
|
661
675
|
switch (strategy) {
|
|
676
|
+
case 'morphMerge': {
|
|
677
|
+
const root = content !== null ? loneRootWithTag(content, target.tagName) : null;
|
|
678
|
+
executeSwap(target, root ?? content, root ? 'morphOuter' : 'morph', morphOptions);
|
|
679
|
+
break;
|
|
680
|
+
}
|
|
662
681
|
case 'morph':
|
|
663
682
|
if (content !== null) {
|
|
664
683
|
try {
|
package/dist/behaviors/index.mjs
CHANGED
|
@@ -653,10 +653,29 @@ function isHTMLElement(value) {
|
|
|
653
653
|
'classList' in value);
|
|
654
654
|
}
|
|
655
655
|
|
|
656
|
+
function loneRootWithTag(content, tagName) {
|
|
657
|
+
let root;
|
|
658
|
+
if (typeof content === 'string') {
|
|
659
|
+
const template = document.createElement('template');
|
|
660
|
+
template.innerHTML = content;
|
|
661
|
+
root = template.content.firstElementChild;
|
|
662
|
+
if (!root || root.nextElementSibling)
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
else {
|
|
666
|
+
root = content.cloneNode(true);
|
|
667
|
+
}
|
|
668
|
+
return root.tagName === tagName && isHTMLElement(root) ? root : null;
|
|
669
|
+
}
|
|
656
670
|
function executeSwap(target, content, strategy, morphOptions) {
|
|
657
671
|
const contentStr = content !== null && !isHTMLElement(content) ? content : '';
|
|
658
672
|
const contentEl = isHTMLElement(content) ? content : null;
|
|
659
673
|
switch (strategy) {
|
|
674
|
+
case 'morphMerge': {
|
|
675
|
+
const root = content !== null ? loneRootWithTag(content, target.tagName) : null;
|
|
676
|
+
executeSwap(target, root ?? content, root ? 'morphOuter' : 'morph', morphOptions);
|
|
677
|
+
break;
|
|
678
|
+
}
|
|
660
679
|
case 'morph':
|
|
661
680
|
if (content !== null) {
|
|
662
681
|
try {
|
|
@@ -262,13 +262,23 @@ const COMMAND_IMPLEMENTATIONS_TS = {
|
|
|
262
262
|
if (isOuter) {
|
|
263
263
|
morphlexMorph(target, contentStr);
|
|
264
264
|
} else {
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
265
|
+
// Upstream's morph MERGES a lone root element that has the
|
|
266
|
+
// target's tag (attributes and children); anything else morphs the
|
|
267
|
+
// children \u2014 the full runtime's 'morphMerge' (lib/swap-executor.ts).
|
|
268
|
+
const template = document.createElement('template');
|
|
269
|
+
template.innerHTML = contentStr;
|
|
270
|
+
const root = template.content.firstElementChild;
|
|
271
|
+
if (root && !root.nextElementSibling && root.tagName === target.tagName) {
|
|
272
|
+
morphlexMorph(target, root);
|
|
273
|
+
} else {
|
|
274
|
+
// morphlex's morphInner takes an ELEMENT, not a string \u2014 the same
|
|
275
|
+
// wrapping src/lib/morph-adapter.ts does. Passing the string threw
|
|
276
|
+
// straight into the fallback below, so inner morph never actually
|
|
277
|
+
// morphed (it silently degraded to innerHTML on every call).
|
|
278
|
+
const wrapper = document.createElement(target.tagName);
|
|
279
|
+
wrapper.innerHTML = contentStr;
|
|
280
|
+
morphlexMorphInner(target, wrapper);
|
|
281
|
+
}
|
|
272
282
|
}
|
|
273
283
|
} catch (error) {
|
|
274
284
|
// Fallback to innerHTML/outerHTML if morph fails
|
|
@@ -592,7 +602,9 @@ const BLOCK_IMPLEMENTATIONS_TS = {
|
|
|
592
602
|
repeat: `
|
|
593
603
|
case 'repeat': {
|
|
594
604
|
const count = await evaluate(block.condition!, ctx);
|
|
595
|
-
|
|
605
|
+
// A count reads as upstream's \`index < times\` does, and core's loopCount:
|
|
606
|
+
// "6.5" loops 7 times, "6abc" none (parseInt read 6 for both), true once.
|
|
607
|
+
const n = Number(count) || 0;
|
|
596
608
|
for (let i = 0; i < n && i < MAX_LOOP_ITERATIONS; i++) {
|
|
597
609
|
ctx.locals.set('__loop_index__', i);
|
|
598
610
|
ctx.locals.set('__loop_count__', i + 1);
|
|
@@ -2342,7 +2354,7 @@ class HybridParser {
|
|
|
2342
2354
|
}
|
|
2343
2355
|
return { type: "command", name, args: [url], modifiers };
|
|
2344
2356
|
}
|
|
2345
|
-
/** \`morph [over] <target> with|
|
|
2357
|
+
/** \`morph [over] <target> to|with|into <content>\` \u2014 upstream spells it \`to\`; the full parser also takes \`with\`; \`into\` is this parser's own. */
|
|
2346
2358
|
parseMorph() {
|
|
2347
2359
|
this.expect("morph");
|
|
2348
2360
|
let modifier;
|
|
@@ -2540,6 +2552,9 @@ class HybridParser {
|
|
|
2540
2552
|
return left;
|
|
2541
2553
|
}
|
|
2542
2554
|
parseUnary() {
|
|
2555
|
+
if (this.match("beep") && this.peek(1).value === "!") {
|
|
2556
|
+
throw new Error(\`'beep!' needs the full parser (use hyperfixi.js)\`);
|
|
2557
|
+
}
|
|
2543
2558
|
if (this.match("not", "!")) {
|
|
2544
2559
|
this.advance();
|
|
2545
2560
|
return { type: "unary", operator: "not", operand: this.parseUnary() };
|
|
@@ -260,13 +260,23 @@ const COMMAND_IMPLEMENTATIONS_TS = {
|
|
|
260
260
|
if (isOuter) {
|
|
261
261
|
morphlexMorph(target, contentStr);
|
|
262
262
|
} else {
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
263
|
+
// Upstream's morph MERGES a lone root element that has the
|
|
264
|
+
// target's tag (attributes and children); anything else morphs the
|
|
265
|
+
// children \u2014 the full runtime's 'morphMerge' (lib/swap-executor.ts).
|
|
266
|
+
const template = document.createElement('template');
|
|
267
|
+
template.innerHTML = contentStr;
|
|
268
|
+
const root = template.content.firstElementChild;
|
|
269
|
+
if (root && !root.nextElementSibling && root.tagName === target.tagName) {
|
|
270
|
+
morphlexMorph(target, root);
|
|
271
|
+
} else {
|
|
272
|
+
// morphlex's morphInner takes an ELEMENT, not a string \u2014 the same
|
|
273
|
+
// wrapping src/lib/morph-adapter.ts does. Passing the string threw
|
|
274
|
+
// straight into the fallback below, so inner morph never actually
|
|
275
|
+
// morphed (it silently degraded to innerHTML on every call).
|
|
276
|
+
const wrapper = document.createElement(target.tagName);
|
|
277
|
+
wrapper.innerHTML = contentStr;
|
|
278
|
+
morphlexMorphInner(target, wrapper);
|
|
279
|
+
}
|
|
270
280
|
}
|
|
271
281
|
} catch (error) {
|
|
272
282
|
// Fallback to innerHTML/outerHTML if morph fails
|
|
@@ -590,7 +600,9 @@ const BLOCK_IMPLEMENTATIONS_TS = {
|
|
|
590
600
|
repeat: `
|
|
591
601
|
case 'repeat': {
|
|
592
602
|
const count = await evaluate(block.condition!, ctx);
|
|
593
|
-
|
|
603
|
+
// A count reads as upstream's \`index < times\` does, and core's loopCount:
|
|
604
|
+
// "6.5" loops 7 times, "6abc" none (parseInt read 6 for both), true once.
|
|
605
|
+
const n = Number(count) || 0;
|
|
594
606
|
for (let i = 0; i < n && i < MAX_LOOP_ITERATIONS; i++) {
|
|
595
607
|
ctx.locals.set('__loop_index__', i);
|
|
596
608
|
ctx.locals.set('__loop_count__', i + 1);
|
|
@@ -2340,7 +2352,7 @@ class HybridParser {
|
|
|
2340
2352
|
}
|
|
2341
2353
|
return { type: "command", name, args: [url], modifiers };
|
|
2342
2354
|
}
|
|
2343
|
-
/** \`morph [over] <target> with|
|
|
2355
|
+
/** \`morph [over] <target> to|with|into <content>\` \u2014 upstream spells it \`to\`; the full parser also takes \`with\`; \`into\` is this parser's own. */
|
|
2344
2356
|
parseMorph() {
|
|
2345
2357
|
this.expect("morph");
|
|
2346
2358
|
let modifier;
|
|
@@ -2538,6 +2550,9 @@ class HybridParser {
|
|
|
2538
2550
|
return left;
|
|
2539
2551
|
}
|
|
2540
2552
|
parseUnary() {
|
|
2553
|
+
if (this.match("beep") && this.peek(1).value === "!") {
|
|
2554
|
+
throw new Error(\`'beep!' needs the full parser (use hyperfixi.js)\`);
|
|
2555
|
+
}
|
|
2541
2556
|
if (this.match("not", "!")) {
|
|
2542
2557
|
this.advance();
|
|
2543
2558
|
return { type: "unary", operator: "not", operand: this.parseUnary() };
|