zero-query 1.0.5 → 1.1.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.
- package/README.md +3 -3
- package/cli/commands/build-api.js +442 -0
- package/cli/commands/build.js +33 -2
- package/cli/commands/bundle.js +174 -8
- package/cli/commands/dev/server.js +57 -3
- package/cli/scaffold/default/app/components/contacts/contacts.css +9 -9
- package/cli/scaffold/default/app/components/playground/playground.css +1 -1
- package/cli/scaffold/default/app/components/playground/playground.html +5 -5
- package/cli/scaffold/default/app/components/playground/playground.js +1 -1
- package/cli/scaffold/default/app/components/toolkit/toolkit.css +1 -1
- package/cli/scaffold/default/app/components/toolkit/toolkit.html +3 -3
- package/cli/scaffold/default/app/components/toolkit/toolkit.js +4 -4
- package/cli/utils.js +16 -7
- package/dist/API.md +6603 -0
- package/dist/zquery.dist.zip +0 -0
- package/dist/zquery.js +387 -25
- package/dist/zquery.min.js +631 -2
- package/index.d.ts +9 -3
- package/index.js +10 -2
- package/package.json +3 -2
- package/src/component.js +243 -6
- package/src/reactive.js +4 -3
- package/src/router.js +79 -9
- package/src/store.js +49 -3
- package/tests/cli.test.js +343 -0
- package/tests/compare.test.js +486 -0
- package/tests/dev-server.test.js +489 -0
- package/tests/docs.test.js +1650 -0
- package/tests/electron-features.test.js +864 -0
- package/types/misc.d.ts +7 -7
- package/types/reactive.d.ts +1 -1
- package/types/store.d.ts +2 -1
package/cli/commands/bundle.js
CHANGED
|
@@ -87,14 +87,137 @@ function walkImportGraph(entry) {
|
|
|
87
87
|
return order;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
/**
|
|
90
|
+
/**
|
|
91
|
+
* Strip ES module import/export syntax, keeping declarations.
|
|
92
|
+
* Exported const/let are converted to var so they hoist past the per-module
|
|
93
|
+
* block scope and remain accessible to downstream modules.
|
|
94
|
+
* Exported function/class are converted to var assignments for the same reason.
|
|
95
|
+
* Non-exported declarations stay as-is and remain block-scoped (private).
|
|
96
|
+
*
|
|
97
|
+
* Template literals are temporarily hidden via a character-level scanner
|
|
98
|
+
* (handles nested backtick expressions) so that code examples inside
|
|
99
|
+
* backtick strings aren't accidentally rewritten.
|
|
100
|
+
*/
|
|
91
101
|
function stripModuleSyntax(code) {
|
|
102
|
+
// -- Hide template literals (supports nesting) -------------------------
|
|
103
|
+
const templates = [];
|
|
104
|
+
|
|
105
|
+
function scanTemplateLiteral(str, start) {
|
|
106
|
+
let i = start + 1; // skip opening backtick
|
|
107
|
+
while (i < str.length) {
|
|
108
|
+
const ch = str[i];
|
|
109
|
+
if (ch === '\\') { i += 2; continue; }
|
|
110
|
+
if (ch === '`') { return i + 1; } // end of template
|
|
111
|
+
if (ch === '$' && str[i + 1] === '{') {
|
|
112
|
+
i += 2; // skip ${
|
|
113
|
+
let depth = 1;
|
|
114
|
+
while (i < str.length && depth > 0) {
|
|
115
|
+
const c = str[i];
|
|
116
|
+
if (c === '{') { depth++; i++; }
|
|
117
|
+
else if (c === '}') { depth--; i++; }
|
|
118
|
+
else if (c === '`') { i = scanTemplateLiteral(str, i); }
|
|
119
|
+
else if (c === "'" || c === '"') { i = skipString(str, i); }
|
|
120
|
+
else if (c === '/' && str[i + 1] === '/') { while (i < str.length && str[i] !== '\n') i++; }
|
|
121
|
+
else if (c === '/' && str[i + 1] === '*') { i += 2; while (i < str.length - 1 && !(str[i] === '*' && str[i + 1] === '/')) i++; i += 2; }
|
|
122
|
+
else { i++; }
|
|
123
|
+
}
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
i++;
|
|
127
|
+
}
|
|
128
|
+
return i;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function skipString(str, start) {
|
|
132
|
+
const q = str[start];
|
|
133
|
+
let i = start + 1;
|
|
134
|
+
while (i < str.length) {
|
|
135
|
+
if (str[i] === '\\') { i += 2; continue; }
|
|
136
|
+
if (str[i] === q) { return i + 1; }
|
|
137
|
+
i++;
|
|
138
|
+
}
|
|
139
|
+
return i;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let hidden = '';
|
|
143
|
+
let pos = 0;
|
|
144
|
+
while (pos < code.length) {
|
|
145
|
+
const ch = code[pos];
|
|
146
|
+
if (ch === "'" || ch === '"') {
|
|
147
|
+
const end = skipString(code, pos);
|
|
148
|
+
hidden += code.substring(pos, end);
|
|
149
|
+
pos = end;
|
|
150
|
+
} else if (ch === '/' && code[pos + 1] === '/') {
|
|
151
|
+
let end = pos;
|
|
152
|
+
while (end < code.length && code[end] !== '\n') end++;
|
|
153
|
+
hidden += code.substring(pos, end);
|
|
154
|
+
pos = end;
|
|
155
|
+
} else if (ch === '/' && code[pos + 1] === '*') {
|
|
156
|
+
let end = pos + 2;
|
|
157
|
+
while (end < code.length - 1 && !(code[end] === '*' && code[end + 1] === '/')) end++;
|
|
158
|
+
end += 2;
|
|
159
|
+
hidden += code.substring(pos, end);
|
|
160
|
+
pos = end;
|
|
161
|
+
} else if (ch === '`') {
|
|
162
|
+
const end = scanTemplateLiteral(code, pos);
|
|
163
|
+
templates.push(code.substring(pos, end));
|
|
164
|
+
hidden += `__TPL_${templates.length - 1}__`;
|
|
165
|
+
pos = end;
|
|
166
|
+
} else {
|
|
167
|
+
hidden += ch;
|
|
168
|
+
pos++;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// -- Apply import / export transforms -----------------------------------
|
|
173
|
+
code = hidden;
|
|
92
174
|
code = code.replace(/^\s*import\s+[\s\S]*?from\s+['"].*?['"];?\s*$/gm, '');
|
|
93
175
|
code = code.replace(/^\s*import\s+['"].*?['"];?\s*$/gm, '');
|
|
94
176
|
code = code.replace(/^(\s*)export\s+default\s+/gm, '$1');
|
|
95
|
-
|
|
96
|
-
code = code.replace(
|
|
97
|
-
|
|
177
|
+
// Convert exported const/let/var to var (hoists past block scope)
|
|
178
|
+
code = code.replace(/^(\s*)export\s+(const|let|var)\s/gm, '$1var ');
|
|
179
|
+
// Convert exported function/async function to var assignment (hoists past block scope)
|
|
180
|
+
code = code.replace(/^(\s*)export\s+async\s+function\s+(\w+)/gm, '$1var $2 = async function $2');
|
|
181
|
+
code = code.replace(/^(\s*)export\s+function\s+(\w+)/gm, '$1var $2 = function $2');
|
|
182
|
+
// Convert exported class to var assignment
|
|
183
|
+
code = code.replace(/^(\s*)export\s+class\s+(\w+)/gm, '$1var $2 = class $2');
|
|
184
|
+
// Collect names from bare export blocks: export { a, b, c };
|
|
185
|
+
// These names are declared elsewhere (function/const/let) and need to be
|
|
186
|
+
// hoisted past the per-module block scope. We collect them here and
|
|
187
|
+
// the caller converts their declarations to var-based forms.
|
|
188
|
+
const bareExportNames = [];
|
|
189
|
+
code = code.replace(/^\s*export\s*\{([^}]+)\};?\s*$/gm, (_, names) => {
|
|
190
|
+
for (const n of names.split(',')) {
|
|
191
|
+
const parts = n.trim().split(/\s+as\s+/);
|
|
192
|
+
if (parts[0]) bareExportNames.push({ local: parts[0].trim(), exported: (parts[1] || parts[0]).trim() });
|
|
193
|
+
}
|
|
194
|
+
return '';
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// For every bare-exported name, convert its block-scoped declaration to a
|
|
198
|
+
// var-based form so it hoists past the per-module { } wrapper:
|
|
199
|
+
// function foo(…) { … } → var foo = function foo(…) { … }
|
|
200
|
+
// async function foo(…) → var foo = async function foo(…)
|
|
201
|
+
// const/let foo = … → var foo = …
|
|
202
|
+
for (const { local } of bareExportNames) {
|
|
203
|
+
const fnRe = new RegExp(`^(\\s*)function\\s+${local}\\s*\\(`, 'gm');
|
|
204
|
+
code = code.replace(fnRe, `$1var ${local} = function ${local}(`);
|
|
205
|
+
const asyncFnRe = new RegExp(`^(\\s*)async\\s+function\\s+${local}\\s*\\(`, 'gm');
|
|
206
|
+
code = code.replace(asyncFnRe, `$1var ${local} = async function ${local}(`);
|
|
207
|
+
const constLetRe = new RegExp(`^(\\s*)(const|let)\\s+${local}\\b`, 'gm');
|
|
208
|
+
code = code.replace(constLetRe, `$1var ${local}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Create aliases for "export { local as exported }" where the names differ
|
|
212
|
+
for (const { local, exported } of bareExportNames) {
|
|
213
|
+
if (exported !== local) {
|
|
214
|
+
code += `\nvar ${exported} = ${local};`;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// -- Restore template literals ------------------------------------------
|
|
219
|
+
code = code.replace(/__TPL_(\d+)__/g, (_, i) => templates[i]);
|
|
220
|
+
return { code, bareExportNames };
|
|
98
221
|
}
|
|
99
222
|
|
|
100
223
|
/** Replace import.meta.url with a runtime equivalent. */
|
|
@@ -181,6 +304,27 @@ function minifyCSS(css) {
|
|
|
181
304
|
return css.trim();
|
|
182
305
|
}
|
|
183
306
|
|
|
307
|
+
/**
|
|
308
|
+
* Heuristic: is the next '/' the start of a regex literal (vs division)?
|
|
309
|
+
* Used by minifyTemplateLiterals to avoid misinterpreting backticks
|
|
310
|
+
* inside regex patterns as template literal delimiters.
|
|
311
|
+
*/
|
|
312
|
+
function _isRegexCtxTpl(out) {
|
|
313
|
+
let end = out.length - 1;
|
|
314
|
+
while (end >= 0 && (out[end] === ' ' || out[end] === '\t' || out[end] === '\n' || out[end] === '\r')) end--;
|
|
315
|
+
if (end < 0) return true;
|
|
316
|
+
const last = out[end];
|
|
317
|
+
if ('=({[,;:!&|?~+-*/%^>'.includes(last)) return true;
|
|
318
|
+
const tail = out.substring(Math.max(0, end - 7), end + 1);
|
|
319
|
+
for (const kw of ['return', 'typeof', 'case', 'in', 'delete', 'void', 'throw', 'new']) {
|
|
320
|
+
if (tail.endsWith(kw)) {
|
|
321
|
+
const pos = end - kw.length;
|
|
322
|
+
if (pos < 0 || !/[a-zA-Z0-9_$]/.test(out[pos])) return true;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
|
|
184
328
|
/**
|
|
185
329
|
* Walk JS source and minify the HTML/CSS inside template literals.
|
|
186
330
|
* Handles ${…} interpolations (with nesting) and preserves <pre> blocks.
|
|
@@ -217,6 +361,25 @@ function minifyTemplateLiterals(code) {
|
|
|
217
361
|
continue;
|
|
218
362
|
}
|
|
219
363
|
|
|
364
|
+
// Regex literal: copy verbatim (prevents backticks inside regex from
|
|
365
|
+
// being mistaken for template literals, e.g. /`([^`]+)`/g)
|
|
366
|
+
if (ch === '/' && _isRegexCtxTpl(out)) {
|
|
367
|
+
out += ch; i++;
|
|
368
|
+
let inCharClass = false;
|
|
369
|
+
while (i < code.length) {
|
|
370
|
+
const rc = code[i];
|
|
371
|
+
if (rc === '\\') { out += rc + (code[i + 1] || ''); i += 2; continue; }
|
|
372
|
+
if (rc === '[') inCharClass = true;
|
|
373
|
+
if (rc === ']') inCharClass = false;
|
|
374
|
+
out += rc; i++;
|
|
375
|
+
if (rc === '/' && !inCharClass) {
|
|
376
|
+
while (i < code.length && /[gimsuy]/.test(code[i])) { out += code[i]; i++; }
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
|
|
220
383
|
// Template literal: extract, minify HTML, and emit
|
|
221
384
|
if (ch === '`') {
|
|
222
385
|
out += _minifyTemplate(code, i);
|
|
@@ -381,8 +544,8 @@ function collectInlineResources(files, projectRoot) {
|
|
|
381
544
|
|
|
382
545
|
// styleUrl:
|
|
383
546
|
const styleUrlRe = /styleUrl\s*:\s*['"]([^'"]+)['"]/g;
|
|
384
|
-
|
|
385
|
-
|
|
547
|
+
let styleMatch;
|
|
548
|
+
while ((styleMatch = styleUrlRe.exec(code)) !== null) {
|
|
386
549
|
const stylePath = path.join(fileDir, styleMatch[1]);
|
|
387
550
|
if (fs.existsSync(stylePath)) {
|
|
388
551
|
const relKey = path.relative(projectRoot, stylePath).replace(/\\/g, '/');
|
|
@@ -877,12 +1040,13 @@ function bundleApp() {
|
|
|
877
1040
|
|
|
878
1041
|
const sections = files.map(file => {
|
|
879
1042
|
let code = fs.readFileSync(file, 'utf-8');
|
|
880
|
-
|
|
1043
|
+
const stripped = stripModuleSyntax(code);
|
|
1044
|
+
code = stripped.code;
|
|
881
1045
|
code = replaceImportMeta(code, file, projectRoot);
|
|
882
1046
|
code = rewriteResourceUrls(code, file, projectRoot);
|
|
883
1047
|
code = minifyTemplateLiterals(code);
|
|
884
1048
|
const rel = path.relative(projectRoot, file);
|
|
885
|
-
return `// --- ${rel} ${'-'.repeat(Math.max(1, 60 - rel.length))}\n${code.trim()}`;
|
|
1049
|
+
return `// --- ${rel} ${'-'.repeat(Math.max(1, 60 - rel.length))}\n{\n${code.trim()}\n}`;
|
|
886
1050
|
});
|
|
887
1051
|
|
|
888
1052
|
// Embed zquery.min.js
|
|
@@ -1056,3 +1220,5 @@ function bundleApp() {
|
|
|
1056
1220
|
}
|
|
1057
1221
|
|
|
1058
1222
|
module.exports = bundleApp;
|
|
1223
|
+
module.exports.stripModuleSyntax = stripModuleSyntax;
|
|
1224
|
+
module.exports.minifyTemplateLiterals = minifyTemplateLiterals;
|
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
* Creates the zero-http app, serves static files, injects the
|
|
5
5
|
* error-overlay snippet into HTML responses, and manages the
|
|
6
6
|
* SSE connection pool for live-reload events.
|
|
7
|
+
*
|
|
8
|
+
* Uses zero-http middleware:
|
|
9
|
+
* - helmet() → security headers (relaxed CSP for dev inline scripts)
|
|
10
|
+
* - compress() → brotli/gzip/deflate response compression
|
|
11
|
+
* - cors() → allow cross-origin requests in development
|
|
12
|
+
* - serveStatic() → static file serving with ETag & Cache-Control
|
|
13
|
+
* - SSE with keepAlive, retry, pad for proxy compatibility
|
|
7
14
|
*/
|
|
8
15
|
|
|
9
16
|
'use strict';
|
|
@@ -22,6 +29,7 @@ class SSEPool {
|
|
|
22
29
|
this._clients = new Set();
|
|
23
30
|
}
|
|
24
31
|
|
|
32
|
+
/** @param {import('zero-http').SSEStream} sse */
|
|
25
33
|
add(sse) {
|
|
26
34
|
this._clients.add(sse);
|
|
27
35
|
sse.on('close', () => this._clients.delete(sse));
|
|
@@ -33,6 +41,9 @@ class SSEPool {
|
|
|
33
41
|
}
|
|
34
42
|
}
|
|
35
43
|
|
|
44
|
+
/** Number of connected SSE clients. */
|
|
45
|
+
get size() { return this._clients.size; }
|
|
46
|
+
|
|
36
47
|
closeAll() {
|
|
37
48
|
for (const sse of this._clients) {
|
|
38
49
|
try { sse.close(); } catch { /* ignore */ }
|
|
@@ -92,14 +103,54 @@ async function createServer({ root, htmlEntry, port, noIntercept }) {
|
|
|
92
103
|
zeroHttp = require('zero-http');
|
|
93
104
|
}
|
|
94
105
|
|
|
95
|
-
const {
|
|
106
|
+
const {
|
|
107
|
+
createApp,
|
|
108
|
+
static: serveStatic,
|
|
109
|
+
helmet,
|
|
110
|
+
compress,
|
|
111
|
+
cors,
|
|
112
|
+
debug,
|
|
113
|
+
} = zeroHttp;
|
|
114
|
+
|
|
115
|
+
debug.level('silent');
|
|
96
116
|
|
|
97
117
|
const app = createApp();
|
|
98
118
|
const pool = new SSEPool();
|
|
99
119
|
|
|
120
|
+
// ---- Security headers (dev-friendly CSP) ----
|
|
121
|
+
app.use(helmet({
|
|
122
|
+
contentSecurityPolicy: {
|
|
123
|
+
directives: {
|
|
124
|
+
defaultSrc: ["'self'"],
|
|
125
|
+
scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
|
|
126
|
+
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
127
|
+
imgSrc: ["'self'", 'data:', 'blob:'],
|
|
128
|
+
connectSrc: ["'self'", 'ws:', 'wss:'],
|
|
129
|
+
fontSrc: ["'self'", 'data:'],
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
// SPA dev server runs over plain HTTP
|
|
133
|
+
hsts: false,
|
|
134
|
+
// Allow framing for devtools panel
|
|
135
|
+
frameguard: false,
|
|
136
|
+
}));
|
|
137
|
+
|
|
138
|
+
// ---- CORS (allow cross-origin during development) ----
|
|
139
|
+
app.use(cors());
|
|
140
|
+
|
|
141
|
+
// ---- Compression (brotli > gzip > deflate) ----
|
|
142
|
+
app.use(compress({
|
|
143
|
+
threshold: 1024,
|
|
144
|
+
}));
|
|
145
|
+
|
|
100
146
|
// ---- SSE endpoint ----
|
|
101
147
|
app.get('/__zq_reload', (req, res) => {
|
|
102
|
-
const sse = res.sse({
|
|
148
|
+
const sse = res.sse({
|
|
149
|
+
keepAlive: 30000,
|
|
150
|
+
keepAliveComment: 'ping',
|
|
151
|
+
retry: 3000,
|
|
152
|
+
pad: 2048,
|
|
153
|
+
});
|
|
103
154
|
pool.add(sse);
|
|
104
155
|
});
|
|
105
156
|
|
|
@@ -157,7 +208,10 @@ async function createServer({ root, htmlEntry, port, noIntercept }) {
|
|
|
157
208
|
});
|
|
158
209
|
|
|
159
210
|
function listen(cb) {
|
|
160
|
-
app.listen(port, cb);
|
|
211
|
+
const server = app.listen(port, cb);
|
|
212
|
+
server.keepAliveTimeout = 65000;
|
|
213
|
+
server.headersTimeout = 66000;
|
|
214
|
+
return server;
|
|
161
215
|
}
|
|
162
216
|
|
|
163
217
|
return { app, pool, listen };
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* to the contacts-page component by zQuery.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
/*
|
|
7
|
+
/* -- Toolbar -- */
|
|
8
8
|
.ct-bar {
|
|
9
9
|
display: flex;
|
|
10
10
|
align-items: center;
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
box-shadow: 0 0 0 2px rgba(88, 166, 255, 0.1);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
/*
|
|
78
|
+
/* -- Filter chips -- */
|
|
79
79
|
.ct-chips {
|
|
80
80
|
display: flex;
|
|
81
81
|
gap: 0.35rem;
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
font-weight: 600;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
/*
|
|
111
|
+
/* -- Buttons -- */
|
|
112
112
|
.ct-btn {
|
|
113
113
|
display: inline-flex;
|
|
114
114
|
align-items: center;
|
|
@@ -166,7 +166,7 @@
|
|
|
166
166
|
font-size: 0.78rem;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
/*
|
|
169
|
+
/* -- Contact Grid -- */
|
|
170
170
|
.ct-grid {
|
|
171
171
|
display: grid;
|
|
172
172
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
@@ -314,7 +314,7 @@
|
|
|
314
314
|
opacity: 1;
|
|
315
315
|
}
|
|
316
316
|
|
|
317
|
-
/*
|
|
317
|
+
/* -- Empty -- */
|
|
318
318
|
.ct-empty-card {
|
|
319
319
|
text-align: center;
|
|
320
320
|
padding: 3rem 1rem;
|
|
@@ -405,7 +405,7 @@
|
|
|
405
405
|
color: #fff;
|
|
406
406
|
}
|
|
407
407
|
|
|
408
|
-
/*
|
|
408
|
+
/* -- Add-contact modal heading -- */
|
|
409
409
|
.ct-modal-heading {
|
|
410
410
|
padding: 1.25rem 1.5rem 0;
|
|
411
411
|
}
|
|
@@ -421,7 +421,7 @@
|
|
|
421
421
|
color: var(--text-muted);
|
|
422
422
|
}
|
|
423
423
|
|
|
424
|
-
/*
|
|
424
|
+
/* -- Form inside modal -- */
|
|
425
425
|
.ct-form {
|
|
426
426
|
display: flex;
|
|
427
427
|
flex-direction: column;
|
|
@@ -488,7 +488,7 @@
|
|
|
488
488
|
padding-top: 0.35rem;
|
|
489
489
|
}
|
|
490
490
|
|
|
491
|
-
/*
|
|
491
|
+
/* -- Detail modal: profile -- */
|
|
492
492
|
.ct-modal-profile {
|
|
493
493
|
display: flex;
|
|
494
494
|
flex-direction: column;
|
|
@@ -608,7 +608,7 @@
|
|
|
608
608
|
font-weight: 500;
|
|
609
609
|
}
|
|
610
610
|
|
|
611
|
-
/*
|
|
611
|
+
/* -- Responsive -- */
|
|
612
612
|
@media (max-width: 768px) {
|
|
613
613
|
.ct-bar {
|
|
614
614
|
flex-direction: column;
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
.pg-vpill:hover { border-color: var(--accent); color: var(--text); }
|
|
103
103
|
.pg-vpill.on { background: var(--accent); color: #fff; border-color: var(--accent); }
|
|
104
104
|
|
|
105
|
-
/*
|
|
105
|
+
/* -- Responsive -- */
|
|
106
106
|
@media (max-width: 768px) {
|
|
107
107
|
.pg-variant-box { min-width: 0; flex: 1 1 100%; }
|
|
108
108
|
.pg-variant-row { flex-direction: column; }
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<p class="subtitle">Interactive UI patterns - event modifiers, animations, reactive bindings, and plugins.</p>
|
|
10
10
|
</div>
|
|
11
11
|
|
|
12
|
-
<!--
|
|
12
|
+
<!-- -- Interaction Patterns ---------------------------- -->
|
|
13
13
|
<div class="pg-section">Interaction Patterns</div>
|
|
14
14
|
|
|
15
15
|
<div class="card" style="position:relative;z-index:2;">
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
</div>
|
|
45
45
|
</div>
|
|
46
46
|
|
|
47
|
-
<!--
|
|
47
|
+
<!-- -- Animations ------------------------------------ -->
|
|
48
48
|
<div class="pg-section">Animations</div>
|
|
49
49
|
|
|
50
50
|
<div class="card">
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
</div>
|
|
82
82
|
</div>
|
|
83
83
|
|
|
84
|
-
<!--
|
|
84
|
+
<!-- -- Reactive Bindings ----------------------------- -->
|
|
85
85
|
<div class="pg-section">Reactive Bindings</div>
|
|
86
86
|
|
|
87
87
|
<div class="card-grid">
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
</div>
|
|
124
124
|
</div>
|
|
125
125
|
|
|
126
|
-
<!--
|
|
126
|
+
<!-- -- Keyboard ------------------------------------ -->
|
|
127
127
|
<div class="pg-section">Keyboard</div>
|
|
128
128
|
|
|
129
129
|
<div class="card">
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
</div>
|
|
141
141
|
</div>
|
|
142
142
|
|
|
143
|
-
<!--
|
|
143
|
+
<!-- -- Plugin System --------------------------------- -->
|
|
144
144
|
<div class="pg-section">Plugin System</div>
|
|
145
145
|
|
|
146
146
|
<div class="card">
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// z-skip - morph opt-out
|
|
11
11
|
// templateUrl / styleUrl - external files
|
|
12
12
|
|
|
13
|
-
//
|
|
13
|
+
// -- $.fn Plugins -------------------------------------------------
|
|
14
14
|
$.fn.highlight = function (color = 'var(--accent)') {
|
|
15
15
|
this.css({ boxShadow: `0 0 0 3px ${color}`, transition: 'box-shadow .3s ease' });
|
|
16
16
|
const self = this;
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
border: 1px solid rgba(88,166,255,.1); }
|
|
88
88
|
.tk-stat-val { font-weight: 700; color: var(--accent); }
|
|
89
89
|
|
|
90
|
-
/*
|
|
90
|
+
/* -- Responsive: tabs -- */
|
|
91
91
|
@media (max-width: 768px) {
|
|
92
92
|
.tk-tabs { flex-wrap: wrap; }
|
|
93
93
|
.tk-tab { flex: 1 1 auto; min-width: 0; padding: .5rem .6rem; font-size: .8rem; }
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
<button class="tk-tab {{activeTab === 'store' ? 'active' : ''}}" @click="setTab('store')">◈ Store</button>
|
|
17
17
|
</div>
|
|
18
18
|
|
|
19
|
-
<!--
|
|
19
|
+
<!-- -- HTTP Tab ------------------------------------- -->
|
|
20
20
|
<div z-if="activeTab === 'http'">
|
|
21
21
|
<div class="card">
|
|
22
22
|
<h3>CRUD Operations</h3>
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
</div>
|
|
62
62
|
</div>
|
|
63
63
|
|
|
64
|
-
<!--
|
|
64
|
+
<!-- -- Utilities Tab -------------------------------- -->
|
|
65
65
|
<div z-else-if="activeTab === 'utils'">
|
|
66
66
|
<div class="card">
|
|
67
67
|
<h3>Utility Belt</h3>
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
</div>
|
|
99
99
|
</div>
|
|
100
100
|
|
|
101
|
-
<!--
|
|
101
|
+
<!-- -- Store Tab ------------------------------------ -->
|
|
102
102
|
<div z-else>
|
|
103
103
|
<div class="card" style="margin-bottom:.75rem;">
|
|
104
104
|
<h3>Store Inspector</h3>
|
|
@@ -105,7 +105,7 @@ $.component('toolkit-page', {
|
|
|
105
105
|
this.state.storeHistory = null;
|
|
106
106
|
},
|
|
107
107
|
|
|
108
|
-
/*
|
|
108
|
+
/* -- HTTP demos --------------------------------------------- */
|
|
109
109
|
|
|
110
110
|
async doGet() {
|
|
111
111
|
this._updateHttpMeta('GET', '/posts/1');
|
|
@@ -165,7 +165,7 @@ $.component('toolkit-page', {
|
|
|
165
165
|
} finally { this.state.searchLoading = false; }
|
|
166
166
|
},
|
|
167
167
|
|
|
168
|
-
/*
|
|
168
|
+
/* -- Utility demos ------------------------------------------- */
|
|
169
169
|
|
|
170
170
|
runUtil(name) {
|
|
171
171
|
this.state.activeUtil = name;
|
|
@@ -197,7 +197,7 @@ $.component('toolkit-page', {
|
|
|
197
197
|
`$.memoize(fibonacci)`,
|
|
198
198
|
``,
|
|
199
199
|
`fib(35) = ${result}`,
|
|
200
|
-
|
|
200
|
+
`----------------------------`,
|
|
201
201
|
`No memoize: ${slowTime.toFixed(1)} ms`,
|
|
202
202
|
`Memoized: ${fastTime.toFixed(3)} ms`,
|
|
203
203
|
`Cached: ${cachedTime.toFixed(4)} ms ⚡`,
|
|
@@ -264,7 +264,7 @@ $.component('toolkit-page', {
|
|
|
264
264
|
this.state.utilOutput = result;
|
|
265
265
|
},
|
|
266
266
|
|
|
267
|
-
/*
|
|
267
|
+
/* -- Store demos -------------------------------------------- */
|
|
268
268
|
|
|
269
269
|
takeSnapshot() {
|
|
270
270
|
this.state.storeSnap = JSON.stringify($.getStore('main').snapshot(), null, 2);
|
package/cli/utils.js
CHANGED
|
@@ -149,7 +149,7 @@ function _minifyBody(code) {
|
|
|
149
149
|
const ch = code[i];
|
|
150
150
|
const nx = code[i + 1];
|
|
151
151
|
|
|
152
|
-
//
|
|
152
|
+
// -- Regular string literal: copy verbatim -----------------
|
|
153
153
|
if (ch === '"' || ch === "'") {
|
|
154
154
|
const q = ch;
|
|
155
155
|
out += ch; i++;
|
|
@@ -162,14 +162,14 @@ function _minifyBody(code) {
|
|
|
162
162
|
continue;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
//
|
|
165
|
+
// -- Template literal: copy verbatim with ${…} nesting -------
|
|
166
166
|
if (ch === '`') {
|
|
167
167
|
const tpl = _copyTemplateLiteral(code, i);
|
|
168
168
|
out += tpl.text; i = tpl.end;
|
|
169
169
|
continue;
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
//
|
|
172
|
+
// -- Block comment: skip -------------------------------------
|
|
173
173
|
if (ch === '/' && nx === '*') {
|
|
174
174
|
i += 2;
|
|
175
175
|
while (i < code.length && !(code[i] === '*' && code[i + 1] === '/')) i++;
|
|
@@ -177,14 +177,14 @@ function _minifyBody(code) {
|
|
|
177
177
|
continue;
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
-
//
|
|
180
|
+
// -- Line comment: skip --------------------------------------
|
|
181
181
|
if (ch === '/' && nx === '/') {
|
|
182
182
|
i += 2;
|
|
183
183
|
while (i < code.length && code[i] !== '\n') i++;
|
|
184
184
|
continue;
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
//
|
|
187
|
+
// -- Regex literal: copy verbatim ----------------------------
|
|
188
188
|
if (ch === '/') {
|
|
189
189
|
if (_isRegexCtx(out)) {
|
|
190
190
|
out += ch; i++;
|
|
@@ -204,11 +204,20 @@ function _minifyBody(code) {
|
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
//
|
|
207
|
+
// -- Whitespace: collapse ------------------------------------
|
|
208
208
|
if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') {
|
|
209
|
-
|
|
209
|
+
let hasNewline = ch === '\n' || ch === '\r';
|
|
210
|
+
while (i < code.length && (code[i] === ' ' || code[i] === '\t' || code[i] === '\n' || code[i] === '\r')) {
|
|
211
|
+
if (code[i] === '\n' || code[i] === '\r') hasNewline = true;
|
|
212
|
+
i++;
|
|
213
|
+
}
|
|
210
214
|
const before = out[out.length - 1];
|
|
211
215
|
const after = code[i];
|
|
216
|
+
// After '}', a newline may be needed for ASI (e.g. var x=function(){}⏎var y).
|
|
217
|
+
// A space alone doesn't trigger ASI, so preserve ';\n' when '}' precedes
|
|
218
|
+
// an identifier-start character and the original whitespace had a newline.
|
|
219
|
+
const afterIsId = after && ((after >= 'a' && after <= 'z') || (after >= 'A' && after <= 'Z') || after === '_' || after === '$');
|
|
220
|
+
if (before === '}' && afterIsId && hasNewline) { out += '\n'; continue; }
|
|
212
221
|
if (_needsSpace(before, after)) out += ' ';
|
|
213
222
|
continue;
|
|
214
223
|
}
|