juxscript 1.1.271 → 1.1.275
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.
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
declare class PageState {
|
|
2
2
|
private _registry;
|
|
3
3
|
private _proxy;
|
|
4
|
-
static readonly WIRE_EVENTS: readonly ["blur", "focus", "click", "dblclick", "change", "input", "keydown", "keyup", "keypress", "mouseenter", "mouseleave", "submit"];
|
|
4
|
+
static readonly WIRE_EVENTS: readonly ["blur", "focus", "click", "dblclick", "change", "input", "keydown", "keyup", "keypress", "mouseenter", "mouseleave", "mousedown", "mouseup", "submit"];
|
|
5
5
|
constructor();
|
|
6
6
|
private _createComponentProxy;
|
|
7
7
|
private _register;
|
|
8
8
|
private _wireEvent;
|
|
9
9
|
private _findElement;
|
|
10
|
+
private _wireStateFlag;
|
|
10
11
|
private _unregister;
|
|
11
12
|
private _notify;
|
|
12
13
|
private _watch;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pageState.d.ts","sourceRoot":"","sources":["../../../lib/state/pageState.ts"],"names":[],"mappings":"AAeA,cAAM,SAAS;IACX,OAAO,CAAC,SAAS,CAA0C;IAC3D,OAAO,CAAC,MAAM,CAAsB;IAEpC,MAAM,CAAC,QAAQ,CAAC,WAAW,
|
|
1
|
+
{"version":3,"file":"pageState.d.ts","sourceRoot":"","sources":["../../../lib/state/pageState.ts"],"names":[],"mappings":"AAeA,cAAM,SAAS;IACX,OAAO,CAAC,SAAS,CAA0C;IAC3D,OAAO,CAAC,MAAM,CAAsB;IAEpC,MAAM,CAAC,QAAQ,CAAC,WAAW,mKAQhB;;IA2BX,OAAO,CAAC,qBAAqB;IAoF7B,OAAO,CAAC,SAAS;IA+DjB,OAAO,CAAC,UAAU;IAwBlB,OAAO,CAAC,YAAY;IAkBpB,OAAO,CAAC,cAAc;IAuBtB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,OAAO;IAqBf,OAAO,CAAC,MAAM;IAcd,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;CAGlC;AAID,eAAO,MAAM,SAAS,qBAAuB,CAAC;AAE9C,OAAO,EAAE,SAAS,EAAE,CAAC"}
|
|
@@ -165,6 +165,10 @@ class PageState {
|
|
|
165
165
|
for (const eventName of PageState.WIRE_EVENTS) {
|
|
166
166
|
this._wireEvent(id, entry, eventName);
|
|
167
167
|
}
|
|
168
|
+
// Wire composite state flags: hover, active, focused
|
|
169
|
+
this._wireStateFlag(id, entry, 'hover', 'mouseenter', 'mouseleave');
|
|
170
|
+
this._wireStateFlag(id, entry, 'active', 'mousedown', 'mouseup');
|
|
171
|
+
this._wireStateFlag(id, entry, 'focused', 'focus', 'blur');
|
|
168
172
|
}
|
|
169
173
|
_wireEvent(id, entry, eventName) {
|
|
170
174
|
entry.events[eventName] = false;
|
|
@@ -206,6 +210,20 @@ class PageState {
|
|
|
206
210
|
}
|
|
207
211
|
return null;
|
|
208
212
|
}
|
|
213
|
+
_wireStateFlag(id, entry, flagName, onEvent, offEvent) {
|
|
214
|
+
entry.events[flagName] = false;
|
|
215
|
+
const el = this._findElement(entry.component);
|
|
216
|
+
if (!el)
|
|
217
|
+
return;
|
|
218
|
+
el.addEventListener(onEvent, () => {
|
|
219
|
+
entry.events[flagName] = true;
|
|
220
|
+
this._notify(`${id}.${flagName}`);
|
|
221
|
+
});
|
|
222
|
+
el.addEventListener(offEvent, () => {
|
|
223
|
+
entry.events[flagName] = false;
|
|
224
|
+
this._notify(`${id}.${flagName}`);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
209
227
|
_unregister(id) {
|
|
210
228
|
this._registry.delete(id);
|
|
211
229
|
}
|
|
@@ -252,6 +270,7 @@ PageState.WIRE_EVENTS = [
|
|
|
252
270
|
'change', 'input',
|
|
253
271
|
'keydown', 'keyup', 'keypress',
|
|
254
272
|
'mouseenter', 'mouseleave',
|
|
273
|
+
'mousedown', 'mouseup',
|
|
255
274
|
'submit'
|
|
256
275
|
];
|
|
257
276
|
// Singleton
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-wraps unwatched pageState references with __watch wrappers.
|
|
3
|
+
*
|
|
4
|
+
* Takes raw .jux source code and returns the same code with
|
|
5
|
+
* reactive statements wrapped in pageState.__watch(() => { ... });
|
|
6
|
+
*
|
|
7
|
+
* Pure function: source in → { code, wrappedCount, details } out
|
|
8
|
+
*/
|
|
9
|
+
import * as acorn from 'acorn';
|
|
10
|
+
|
|
11
|
+
// We use acorn's simple walker inline to avoid import issues
|
|
12
|
+
function walkSimple(node, visitors) {
|
|
13
|
+
if (!node || typeof node !== 'object') return;
|
|
14
|
+
if (Array.isArray(node)) { node.forEach(n => walkSimple(n, visitors)); return; }
|
|
15
|
+
const visitor = visitors[node.type];
|
|
16
|
+
if (visitor) visitor(node);
|
|
17
|
+
for (const key of Object.keys(node)) {
|
|
18
|
+
if (key === 'type' || key === 'start' || key === 'end' || key === 'loc' || key === 'raw') continue;
|
|
19
|
+
const child = node[key];
|
|
20
|
+
if (child && typeof child === 'object') walkSimple(child, visitors);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isPageStateAccess(node) {
|
|
25
|
+
if (node.type !== 'MemberExpression') return false;
|
|
26
|
+
const obj = node.object;
|
|
27
|
+
if (obj.type === 'MemberExpression' &&
|
|
28
|
+
obj.object.type === 'Identifier' &&
|
|
29
|
+
obj.object.name === 'pageState') return true;
|
|
30
|
+
if (node.object.type === 'Identifier' && node.object.name === 'pageState') return true;
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function containsPageStateRef(node) {
|
|
35
|
+
let found = false;
|
|
36
|
+
walkSimple(node, {
|
|
37
|
+
MemberExpression(n) { if (isPageStateAccess(n)) found = true; }
|
|
38
|
+
});
|
|
39
|
+
return found;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function containsJuxCall(node) {
|
|
43
|
+
let found = false;
|
|
44
|
+
walkSimple(node, {
|
|
45
|
+
CallExpression(n) { if (n.callee?.object?.name === 'jux') found = true; }
|
|
46
|
+
});
|
|
47
|
+
return found;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getDeclaredVarNames(stmt) {
|
|
51
|
+
if (stmt.type !== 'VariableDeclaration') return [];
|
|
52
|
+
return stmt.declarations
|
|
53
|
+
.filter(d => d.id.type === 'Identifier')
|
|
54
|
+
.map(d => d.id.name);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function usesIdentifier(node, name) {
|
|
58
|
+
let found = false;
|
|
59
|
+
walkSimple(node, {
|
|
60
|
+
Identifier(n) { if (n.name === name) found = true; }
|
|
61
|
+
});
|
|
62
|
+
return found;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isAlreadyWrapped(stmt) {
|
|
66
|
+
return stmt.type === 'ExpressionStatement' &&
|
|
67
|
+
stmt.expression?.type === 'CallExpression' &&
|
|
68
|
+
stmt.expression?.callee?.object?.name === 'pageState' &&
|
|
69
|
+
stmt.expression?.callee?.property?.name === '__watch';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isSetupStatement(stmt) {
|
|
73
|
+
if (stmt.type === 'ImportDeclaration') return true;
|
|
74
|
+
if (stmt.type === 'FunctionDeclaration') return true;
|
|
75
|
+
if (stmt.type === 'ExpressionStatement') {
|
|
76
|
+
const expr = stmt.expression;
|
|
77
|
+
if (expr.type === 'CallExpression' &&
|
|
78
|
+
expr.callee?.object?.name === 'jux' &&
|
|
79
|
+
!containsPageStateRef(stmt)) return true;
|
|
80
|
+
if (expr.type === 'AwaitExpression' &&
|
|
81
|
+
expr.argument?.callee?.object?.name === 'jux' &&
|
|
82
|
+
!containsPageStateRef(stmt)) return true;
|
|
83
|
+
}
|
|
84
|
+
if (stmt.type === 'VariableDeclaration' && !containsPageStateRef(stmt)) return true;
|
|
85
|
+
if (stmt.type === 'VariableDeclaration' && containsJuxCall(stmt)) return true;
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getLineNumber(source, pos) {
|
|
90
|
+
let line = 1;
|
|
91
|
+
for (let i = 0; i < pos; i++) {
|
|
92
|
+
if (source[i] === '\n') line++;
|
|
93
|
+
}
|
|
94
|
+
return line;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @param {string} source - Raw .jux source code
|
|
99
|
+
* @param {string} [filename] - For logging
|
|
100
|
+
* @returns {{ code: string, wrappedCount: number, details: string[] }}
|
|
101
|
+
*/
|
|
102
|
+
export function autowrap(source, filename = '') {
|
|
103
|
+
let ast;
|
|
104
|
+
try {
|
|
105
|
+
ast = acorn.parse(source, {
|
|
106
|
+
ecmaVersion: 2022,
|
|
107
|
+
sourceType: 'module',
|
|
108
|
+
allowAwaitOutsideFunction: true,
|
|
109
|
+
});
|
|
110
|
+
} catch (err) {
|
|
111
|
+
// Can't parse — return as-is
|
|
112
|
+
return { code: source, wrappedCount: 0, details: [`parse error: ${err.message}`] };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const needsWatch = [];
|
|
116
|
+
let i = 0;
|
|
117
|
+
|
|
118
|
+
while (i < ast.body.length) {
|
|
119
|
+
const stmt = ast.body[i];
|
|
120
|
+
|
|
121
|
+
if (isSetupStatement(stmt)) { i++; continue; }
|
|
122
|
+
if (isAlreadyWrapped(stmt)) { i++; continue; }
|
|
123
|
+
|
|
124
|
+
// VariableDeclaration reading pageState — group with next if uses declared var
|
|
125
|
+
if (stmt.type === 'VariableDeclaration' && containsPageStateRef(stmt)) {
|
|
126
|
+
const varNames = getDeclaredVarNames(stmt);
|
|
127
|
+
const groupStmts = [stmt];
|
|
128
|
+
const next = ast.body[i + 1];
|
|
129
|
+
if (next && !isAlreadyWrapped(next) && !isSetupStatement(next)) {
|
|
130
|
+
if (varNames.some(v => usesIdentifier(next, v))) {
|
|
131
|
+
groupStmts.push(next);
|
|
132
|
+
i += 2;
|
|
133
|
+
} else {
|
|
134
|
+
i++;
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
i++;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
needsWatch.push({
|
|
141
|
+
line: getLineNumber(source, groupStmts[0].start),
|
|
142
|
+
endLine: getLineNumber(source, groupStmts[groupStmts.length - 1].end),
|
|
143
|
+
});
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Any other statement referencing pageState
|
|
148
|
+
if (containsPageStateRef(stmt)) {
|
|
149
|
+
needsWatch.push({
|
|
150
|
+
line: getLineNumber(source, stmt.start),
|
|
151
|
+
endLine: getLineNumber(source, stmt.end),
|
|
152
|
+
});
|
|
153
|
+
i++;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
i++;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (needsWatch.length === 0) {
|
|
161
|
+
return { code: source, wrappedCount: 0, details: [] };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Apply wraps bottom-up to preserve line numbers
|
|
165
|
+
const lines = source.split('\n');
|
|
166
|
+
const sorted = [...needsWatch].sort((a, b) => b.line - a.line);
|
|
167
|
+
const details = [];
|
|
168
|
+
|
|
169
|
+
for (const item of sorted) {
|
|
170
|
+
const startIdx = item.line - 1;
|
|
171
|
+
const endIdx = item.endLine - 1;
|
|
172
|
+
const blockLines = lines.slice(startIdx, endIdx + 1);
|
|
173
|
+
const indent = blockLines[0].match(/^(\s*)/)[1];
|
|
174
|
+
|
|
175
|
+
const wrapped = [
|
|
176
|
+
`${indent}pageState.__watch(() => {`,
|
|
177
|
+
...blockLines.map(l => `${indent} ${l.trim()}`),
|
|
178
|
+
`${indent}});`,
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
lines.splice(startIdx, endIdx - startIdx + 1, ...wrapped);
|
|
182
|
+
details.push(`L${item.line}-${item.endLine}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
code: lines.join('\n'),
|
|
187
|
+
wrappedCount: needsWatch.length,
|
|
188
|
+
details
|
|
189
|
+
};
|
|
190
|
+
}
|
package/machinery/compiler4.js
CHANGED
|
@@ -5,6 +5,7 @@ import fs from 'fs';
|
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
import { generateErrorCollector } from './errors.js';
|
|
8
|
+
import { autowrap } from './autowrap.js';
|
|
8
9
|
|
|
9
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
11
|
const __dirname = path.dirname(__filename);
|
|
@@ -77,9 +78,19 @@ export class JuxCompiler {
|
|
|
77
78
|
} else if (hasExports) {
|
|
78
79
|
sharedModules.push({ name, file: relativePath, content, originalContent: content });
|
|
79
80
|
} else {
|
|
81
|
+
// ✅ Auto-wrap pageState references before wrapping in async function
|
|
82
|
+
let processedContent = content;
|
|
83
|
+
if (file.endsWith('.jux')) {
|
|
84
|
+
const result = autowrap(content, relativePath);
|
|
85
|
+
if (result.wrappedCount > 0) {
|
|
86
|
+
console.log(`🔄 Auto-wrapped ${result.wrappedCount} reactive block(s) in ${relativePath} [${result.details.join(', ')}]`);
|
|
87
|
+
processedContent = result.code;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
80
91
|
let wrappedContent;
|
|
81
92
|
try {
|
|
82
|
-
const ast = acorn.parse(
|
|
93
|
+
const ast = acorn.parse(processedContent, {
|
|
83
94
|
ecmaVersion: 'latest',
|
|
84
95
|
sourceType: 'module',
|
|
85
96
|
locations: true
|
|
@@ -90,12 +101,12 @@ export class JuxCompiler {
|
|
|
90
101
|
|
|
91
102
|
for (const node of ast.body) {
|
|
92
103
|
if (node.type === 'ImportDeclaration') {
|
|
93
|
-
imports.push(
|
|
104
|
+
imports.push(processedContent.substring(node.start, node.end));
|
|
94
105
|
lastImportEnd = node.end;
|
|
95
106
|
}
|
|
96
107
|
}
|
|
97
108
|
|
|
98
|
-
const restOfCode =
|
|
109
|
+
const restOfCode = processedContent.substring(lastImportEnd).trim();
|
|
99
110
|
|
|
100
111
|
wrappedContent = [
|
|
101
112
|
...imports,
|
|
@@ -107,7 +118,7 @@ export class JuxCompiler {
|
|
|
107
118
|
|
|
108
119
|
} catch (parseError) {
|
|
109
120
|
console.warn(`⚠️ Could not parse ${relativePath}, using basic wrapping`);
|
|
110
|
-
wrappedContent = `export default async function() {\n${
|
|
121
|
+
wrappedContent = `export default async function() {\n${processedContent}\n}`;
|
|
111
122
|
}
|
|
112
123
|
|
|
113
124
|
views.push({ name, file: relativePath, content: wrappedContent, originalContent: content });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "juxscript",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.275",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A JavaScript UX authorship platform",
|
|
6
6
|
"main": "./dist/lib/index.js",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"import": "./dist/lib/index.js",
|
|
16
16
|
"types": "./dist/lib/index.d.ts"
|
|
17
17
|
},
|
|
18
|
+
"./machinery/*": "./machinery/*",
|
|
18
19
|
"./package.json": "./package.json"
|
|
19
20
|
},
|
|
20
21
|
"files": [
|