@lokascript/core 1.1.4 → 1.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/dist/bundle-generator/index.d.ts +2 -1
- package/dist/bundle-generator/index.d.ts.map +1 -1
- package/dist/bundle-generator/index.js +1335 -9
- package/dist/bundle-generator/index.js.map +1 -1
- package/dist/bundle-generator/index.mjs +1330 -10
- package/dist/bundle-generator/index.mjs.map +1 -1
- package/dist/bundle-generator/parser-templates.d.ts +6 -0
- package/dist/bundle-generator/parser-templates.d.ts.map +1 -0
- package/dist/bundle-generator/template-capabilities.d.ts +2 -2
- package/dist/bundle-generator/template-capabilities.d.ts.map +1 -1
- package/dist/bundle-generator/templates.d.ts +1 -0
- package/dist/bundle-generator/templates.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/bundle-generator/index.ts +10 -0
- package/src/bundle-generator/parser-templates.ts +1158 -0
- package/src/bundle-generator/template-capabilities.ts +17 -14
- package/src/bundle-generator/templates.ts +228 -0
- package/src/bundle-generator/validation.test.ts +25 -3
|
@@ -34,8 +34,14 @@ export const AVAILABLE_COMMANDS = [
|
|
|
34
34
|
// Utility
|
|
35
35
|
'log',
|
|
36
36
|
'call',
|
|
37
|
+
'copy',
|
|
38
|
+
'beep',
|
|
37
39
|
// Navigation
|
|
38
40
|
'go',
|
|
41
|
+
'push',
|
|
42
|
+
'push-url',
|
|
43
|
+
'replace',
|
|
44
|
+
'replace-url',
|
|
39
45
|
// Focus
|
|
40
46
|
'focus',
|
|
41
47
|
'blur',
|
|
@@ -43,6 +49,13 @@ export const AVAILABLE_COMMANDS = [
|
|
|
43
49
|
'return',
|
|
44
50
|
'break',
|
|
45
51
|
'continue',
|
|
52
|
+
'halt',
|
|
53
|
+
'exit',
|
|
54
|
+
'throw',
|
|
55
|
+
// Advanced execution
|
|
56
|
+
'js',
|
|
57
|
+
// DOM morphing (requires morphlex import)
|
|
58
|
+
'morph',
|
|
46
59
|
] as const;
|
|
47
60
|
|
|
48
61
|
/**
|
|
@@ -58,33 +71,23 @@ export const AVAILABLE_BLOCKS = ['if', 'repeat', 'for', 'while', 'fetch'] as con
|
|
|
58
71
|
export const FULL_RUNTIME_ONLY_COMMANDS = [
|
|
59
72
|
// Advanced execution
|
|
60
73
|
'async',
|
|
61
|
-
'js',
|
|
62
74
|
// DOM operations (complex)
|
|
63
75
|
'make',
|
|
64
76
|
'swap',
|
|
65
|
-
'morph',
|
|
66
77
|
'process-partials',
|
|
67
78
|
// Data binding
|
|
68
79
|
'bind',
|
|
69
80
|
'persist',
|
|
70
81
|
'default',
|
|
71
|
-
// Utility (complex)
|
|
72
|
-
'beep',
|
|
82
|
+
// Utility (complex - need runtime integration)
|
|
73
83
|
'tell',
|
|
74
|
-
'copy',
|
|
75
84
|
'pick',
|
|
76
|
-
//
|
|
77
|
-
'push-url',
|
|
78
|
-
'replace-url',
|
|
79
|
-
// Control flow (advanced)
|
|
80
|
-
'halt',
|
|
81
|
-
'exit',
|
|
82
|
-
'throw',
|
|
85
|
+
// Conditional (already have 'if' block)
|
|
83
86
|
'unless',
|
|
84
|
-
// Animation (advanced)
|
|
87
|
+
// Animation (advanced - needs helpers)
|
|
85
88
|
'settle',
|
|
86
89
|
'measure',
|
|
87
|
-
// Behaviors
|
|
90
|
+
// Behaviors (requires registry)
|
|
88
91
|
'install',
|
|
89
92
|
] as const;
|
|
90
93
|
|
|
@@ -259,6 +259,34 @@ const COMMAND_IMPLEMENTATIONS_TS: Record<string, string> = {
|
|
|
259
259
|
return content;
|
|
260
260
|
}`,
|
|
261
261
|
|
|
262
|
+
morph: `
|
|
263
|
+
case 'morph': {
|
|
264
|
+
const targets = await getTarget();
|
|
265
|
+
const content = await evaluate(cmd.args[0], ctx);
|
|
266
|
+
const contentStr = String(content);
|
|
267
|
+
const isOuter = cmd.modifier === 'over';
|
|
268
|
+
|
|
269
|
+
for (const target of targets) {
|
|
270
|
+
try {
|
|
271
|
+
if (isOuter) {
|
|
272
|
+
morphlexMorph(target, contentStr);
|
|
273
|
+
} else {
|
|
274
|
+
morphlexMorphInner(target, contentStr);
|
|
275
|
+
}
|
|
276
|
+
} catch (error) {
|
|
277
|
+
// Fallback to innerHTML/outerHTML if morph fails
|
|
278
|
+
console.warn('[LokaScript] Morph failed, falling back:', error);
|
|
279
|
+
if (isOuter) {
|
|
280
|
+
target.outerHTML = contentStr;
|
|
281
|
+
} else {
|
|
282
|
+
target.innerHTML = contentStr;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
ctx.it = targets.length === 1 ? targets[0] : targets;
|
|
287
|
+
return ctx.it;
|
|
288
|
+
}`,
|
|
289
|
+
|
|
262
290
|
take: `
|
|
263
291
|
case 'take': {
|
|
264
292
|
const className = getClassName(await evaluate(cmd.args[0], ctx));
|
|
@@ -350,6 +378,201 @@ const COMMAND_IMPLEMENTATIONS_TS: Record<string, string> = {
|
|
|
350
378
|
case 'continue': {
|
|
351
379
|
throw { type: 'continue' };
|
|
352
380
|
}`,
|
|
381
|
+
|
|
382
|
+
// === Control Flow Commands ===
|
|
383
|
+
halt: `
|
|
384
|
+
case 'halt': {
|
|
385
|
+
// Check for "halt the event" pattern
|
|
386
|
+
const firstArg = cmd.args[0];
|
|
387
|
+
let targetEvent = ctx.event;
|
|
388
|
+
if (firstArg?.type === 'identifier' && firstArg.name === 'the' && cmd.args[1]?.name === 'event') {
|
|
389
|
+
targetEvent = ctx.event;
|
|
390
|
+
} else if (firstArg) {
|
|
391
|
+
const evaluated = await evaluate(firstArg, ctx);
|
|
392
|
+
if (evaluated?.preventDefault) targetEvent = evaluated;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (targetEvent && typeof targetEvent.preventDefault === 'function') {
|
|
396
|
+
targetEvent.preventDefault();
|
|
397
|
+
targetEvent.stopPropagation();
|
|
398
|
+
return { halted: true, eventHalted: true };
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Regular halt - stop execution
|
|
402
|
+
const haltError = new Error('HALT_EXECUTION');
|
|
403
|
+
(haltError as any).isHalt = true;
|
|
404
|
+
throw haltError;
|
|
405
|
+
}`,
|
|
406
|
+
|
|
407
|
+
exit: `
|
|
408
|
+
case 'exit': {
|
|
409
|
+
const exitError = new Error('EXIT_COMMAND');
|
|
410
|
+
(exitError as any).isExit = true;
|
|
411
|
+
throw exitError;
|
|
412
|
+
}`,
|
|
413
|
+
|
|
414
|
+
throw: `
|
|
415
|
+
case 'throw': {
|
|
416
|
+
const message = cmd.args[0] ? await evaluate(cmd.args[0], ctx) : 'Error';
|
|
417
|
+
const errorToThrow = message instanceof Error ? message : new Error(String(message));
|
|
418
|
+
throw errorToThrow;
|
|
419
|
+
}`,
|
|
420
|
+
|
|
421
|
+
// === Debug Commands ===
|
|
422
|
+
beep: `
|
|
423
|
+
case 'beep': {
|
|
424
|
+
const values = await Promise.all(cmd.args.map((a: any) => evaluate(a, ctx)));
|
|
425
|
+
const displayValues = values.length > 0 ? values : [ctx.it];
|
|
426
|
+
|
|
427
|
+
for (const val of displayValues) {
|
|
428
|
+
const typeInfo = val === null ? 'null' :
|
|
429
|
+
val === undefined ? 'undefined' :
|
|
430
|
+
Array.isArray(val) ? \`Array[\${val.length}]\` :
|
|
431
|
+
val instanceof Element ? \`Element<\${val.tagName.toLowerCase()}>\` :
|
|
432
|
+
typeof val;
|
|
433
|
+
console.log('[beep]', typeInfo + ':', val);
|
|
434
|
+
}
|
|
435
|
+
return displayValues[0];
|
|
436
|
+
}`,
|
|
437
|
+
|
|
438
|
+
// === JavaScript Execution ===
|
|
439
|
+
js: `
|
|
440
|
+
case 'js': {
|
|
441
|
+
const codeArg = cmd.args[0];
|
|
442
|
+
let jsCode = '';
|
|
443
|
+
|
|
444
|
+
if (codeArg.type === 'string') {
|
|
445
|
+
jsCode = codeArg.value;
|
|
446
|
+
} else if (codeArg.type === 'template') {
|
|
447
|
+
jsCode = await evaluate(codeArg, ctx);
|
|
448
|
+
} else {
|
|
449
|
+
jsCode = String(await evaluate(codeArg, ctx));
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Build context object for the Function
|
|
453
|
+
const jsContext = {
|
|
454
|
+
me: ctx.me,
|
|
455
|
+
it: ctx.it,
|
|
456
|
+
event: ctx.event,
|
|
457
|
+
target: ctx.target || ctx.me,
|
|
458
|
+
locals: Object.fromEntries(ctx.locals),
|
|
459
|
+
globals: Object.fromEntries(globalVars),
|
|
460
|
+
document: typeof document !== 'undefined' ? document : undefined,
|
|
461
|
+
window: typeof window !== 'undefined' ? window : undefined,
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
try {
|
|
465
|
+
const fn = new Function('ctx', \`with(ctx) { return (async () => { \${jsCode} })(); }\`);
|
|
466
|
+
const result = await fn(jsContext);
|
|
467
|
+
ctx.it = result;
|
|
468
|
+
return result;
|
|
469
|
+
} catch (error) {
|
|
470
|
+
console.error('[js] Execution error:', error);
|
|
471
|
+
throw error;
|
|
472
|
+
}
|
|
473
|
+
}`,
|
|
474
|
+
|
|
475
|
+
// === Clipboard Commands ===
|
|
476
|
+
copy: `
|
|
477
|
+
case 'copy': {
|
|
478
|
+
const source = await evaluate(cmd.args[0], ctx);
|
|
479
|
+
let textToCopy = '';
|
|
480
|
+
|
|
481
|
+
if (typeof source === 'string') {
|
|
482
|
+
textToCopy = source;
|
|
483
|
+
} else if (source instanceof Element) {
|
|
484
|
+
textToCopy = source.textContent || '';
|
|
485
|
+
} else {
|
|
486
|
+
textToCopy = String(source);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
try {
|
|
490
|
+
if (navigator.clipboard) {
|
|
491
|
+
await navigator.clipboard.writeText(textToCopy);
|
|
492
|
+
} else {
|
|
493
|
+
// Fallback for older browsers
|
|
494
|
+
const textarea = document.createElement('textarea');
|
|
495
|
+
textarea.value = textToCopy;
|
|
496
|
+
textarea.style.cssText = 'position:fixed;top:0;left:-9999px';
|
|
497
|
+
document.body.appendChild(textarea);
|
|
498
|
+
textarea.select();
|
|
499
|
+
document.execCommand('copy');
|
|
500
|
+
document.body.removeChild(textarea);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (ctx.me instanceof Element) {
|
|
504
|
+
ctx.me.dispatchEvent(new CustomEvent('copy:success', {
|
|
505
|
+
bubbles: true,
|
|
506
|
+
detail: { text: textToCopy }
|
|
507
|
+
}));
|
|
508
|
+
}
|
|
509
|
+
ctx.it = textToCopy;
|
|
510
|
+
return textToCopy;
|
|
511
|
+
} catch (error) {
|
|
512
|
+
if (ctx.me instanceof Element) {
|
|
513
|
+
ctx.me.dispatchEvent(new CustomEvent('copy:error', {
|
|
514
|
+
bubbles: true,
|
|
515
|
+
detail: { error }
|
|
516
|
+
}));
|
|
517
|
+
}
|
|
518
|
+
throw error;
|
|
519
|
+
}
|
|
520
|
+
}`,
|
|
521
|
+
|
|
522
|
+
// === URL Navigation Commands ===
|
|
523
|
+
push: `
|
|
524
|
+
case 'push':
|
|
525
|
+
case 'push-url': {
|
|
526
|
+
// Handle "push url '/path'" pattern
|
|
527
|
+
let urlArg = cmd.args[0];
|
|
528
|
+
if (urlArg?.type === 'identifier' && urlArg.name === 'url') {
|
|
529
|
+
urlArg = cmd.args[1];
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const url = String(await evaluate(urlArg, ctx));
|
|
533
|
+
let title = '';
|
|
534
|
+
|
|
535
|
+
// Check for "with title" modifier
|
|
536
|
+
if (cmd.modifiers?.title) {
|
|
537
|
+
title = String(await evaluate(cmd.modifiers.title, ctx));
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
window.history.pushState(null, '', url);
|
|
541
|
+
if (title) document.title = title;
|
|
542
|
+
|
|
543
|
+
window.dispatchEvent(new CustomEvent('lokascript:pushurl', {
|
|
544
|
+
detail: { url, title }
|
|
545
|
+
}));
|
|
546
|
+
|
|
547
|
+
return { url, title, mode: 'push' };
|
|
548
|
+
}`,
|
|
549
|
+
|
|
550
|
+
replace: `
|
|
551
|
+
case 'replace':
|
|
552
|
+
case 'replace-url': {
|
|
553
|
+
// Handle "replace url '/path'" pattern
|
|
554
|
+
let urlArg = cmd.args[0];
|
|
555
|
+
if (urlArg?.type === 'identifier' && urlArg.name === 'url') {
|
|
556
|
+
urlArg = cmd.args[1];
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const url = String(await evaluate(urlArg, ctx));
|
|
560
|
+
let title = '';
|
|
561
|
+
|
|
562
|
+
// Check for "with title" modifier
|
|
563
|
+
if (cmd.modifiers?.title) {
|
|
564
|
+
title = String(await evaluate(cmd.modifiers.title, ctx));
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
window.history.replaceState(null, '', url);
|
|
568
|
+
if (title) document.title = title;
|
|
569
|
+
|
|
570
|
+
window.dispatchEvent(new CustomEvent('lokascript:replaceurl', {
|
|
571
|
+
detail: { url, title }
|
|
572
|
+
}));
|
|
573
|
+
|
|
574
|
+
return { url, title, mode: 'replace' };
|
|
575
|
+
}`,
|
|
353
576
|
};
|
|
354
577
|
|
|
355
578
|
/**
|
|
@@ -466,6 +689,11 @@ export const STYLE_COMMANDS = ['set', 'put', 'increment', 'decrement'];
|
|
|
466
689
|
*/
|
|
467
690
|
export const ELEMENT_ARRAY_COMMANDS = ['put', 'increment', 'decrement'];
|
|
468
691
|
|
|
692
|
+
/**
|
|
693
|
+
* Commands that require morphlex import for DOM morphing
|
|
694
|
+
*/
|
|
695
|
+
export const MORPH_COMMANDS = ['morph'];
|
|
696
|
+
|
|
469
697
|
/**
|
|
470
698
|
* Get command implementations for the specified format.
|
|
471
699
|
* @param format 'ts' for TypeScript, 'js' for JavaScript
|
|
@@ -105,7 +105,19 @@ describe('Bundle Generator Validation', () => {
|
|
|
105
105
|
expect(AVAILABLE_COMMANDS).toContain('remove');
|
|
106
106
|
expect(AVAILABLE_COMMANDS).toContain('break');
|
|
107
107
|
expect(AVAILABLE_COMMANDS).toContain('continue');
|
|
108
|
-
|
|
108
|
+
// New commands added for hybrid-plus
|
|
109
|
+
expect(AVAILABLE_COMMANDS).toContain('js');
|
|
110
|
+
expect(AVAILABLE_COMMANDS).toContain('copy');
|
|
111
|
+
expect(AVAILABLE_COMMANDS).toContain('beep');
|
|
112
|
+
expect(AVAILABLE_COMMANDS).toContain('halt');
|
|
113
|
+
expect(AVAILABLE_COMMANDS).toContain('exit');
|
|
114
|
+
expect(AVAILABLE_COMMANDS).toContain('throw');
|
|
115
|
+
expect(AVAILABLE_COMMANDS).toContain('push');
|
|
116
|
+
expect(AVAILABLE_COMMANDS).toContain('push-url');
|
|
117
|
+
expect(AVAILABLE_COMMANDS).toContain('replace');
|
|
118
|
+
expect(AVAILABLE_COMMANDS).toContain('replace-url');
|
|
119
|
+
expect(AVAILABLE_COMMANDS).toContain('morph');
|
|
120
|
+
expect(AVAILABLE_COMMANDS.length).toBeGreaterThan(30);
|
|
109
121
|
});
|
|
110
122
|
|
|
111
123
|
it('should list all available blocks', () => {
|
|
@@ -115,10 +127,20 @@ describe('Bundle Generator Validation', () => {
|
|
|
115
127
|
it('should list full runtime only commands', () => {
|
|
116
128
|
expect(FULL_RUNTIME_ONLY_COMMANDS).toContain('async');
|
|
117
129
|
expect(FULL_RUNTIME_ONLY_COMMANDS).toContain('swap');
|
|
118
|
-
expect(FULL_RUNTIME_ONLY_COMMANDS).toContain('
|
|
119
|
-
|
|
130
|
+
expect(FULL_RUNTIME_ONLY_COMMANDS).toContain('tell');
|
|
131
|
+
expect(FULL_RUNTIME_ONLY_COMMANDS).toContain('install');
|
|
132
|
+
// These are now available in lite bundles (with morphlex/native support)
|
|
133
|
+
expect(FULL_RUNTIME_ONLY_COMMANDS).not.toContain('morph');
|
|
120
134
|
expect(FULL_RUNTIME_ONLY_COMMANDS).not.toContain('break');
|
|
121
135
|
expect(FULL_RUNTIME_ONLY_COMMANDS).not.toContain('continue');
|
|
136
|
+
expect(FULL_RUNTIME_ONLY_COMMANDS).not.toContain('js');
|
|
137
|
+
expect(FULL_RUNTIME_ONLY_COMMANDS).not.toContain('copy');
|
|
138
|
+
expect(FULL_RUNTIME_ONLY_COMMANDS).not.toContain('beep');
|
|
139
|
+
expect(FULL_RUNTIME_ONLY_COMMANDS).not.toContain('halt');
|
|
140
|
+
expect(FULL_RUNTIME_ONLY_COMMANDS).not.toContain('exit');
|
|
141
|
+
expect(FULL_RUNTIME_ONLY_COMMANDS).not.toContain('throw');
|
|
142
|
+
expect(FULL_RUNTIME_ONLY_COMMANDS).not.toContain('push');
|
|
143
|
+
expect(FULL_RUNTIME_ONLY_COMMANDS).not.toContain('replace');
|
|
122
144
|
});
|
|
123
145
|
|
|
124
146
|
it('isAvailableCommand should return true for valid commands', () => {
|