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,2IAOhB;;IA2BX,OAAO,CAAC,qBAAqB;IAoF7B,OAAO,CAAC,SAAS;IA0DjB,OAAO,CAAC,UAAU;IAwBlB,OAAO,CAAC,YAAY;IAkBpB,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"}
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
+ }
@@ -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(content, {
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(content.substring(node.start, node.end));
104
+ imports.push(processedContent.substring(node.start, node.end));
94
105
  lastImportEnd = node.end;
95
106
  }
96
107
  }
97
108
 
98
- const restOfCode = content.substring(lastImportEnd).trim();
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${content}\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.271",
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": [