pulse-js-framework 1.10.3 → 1.11.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 +11 -0
- package/cli/build.js +13 -3
- package/compiler/directives.js +356 -0
- package/compiler/lexer.js +18 -3
- package/compiler/parser/core.js +6 -0
- package/compiler/parser/view.js +2 -6
- package/compiler/preprocessor.js +43 -23
- package/compiler/sourcemap.js +3 -1
- package/compiler/transformer/actions.js +329 -0
- package/compiler/transformer/export.js +7 -0
- package/compiler/transformer/expressions.js +85 -33
- package/compiler/transformer/imports.js +3 -0
- package/compiler/transformer/index.js +2 -0
- package/compiler/transformer/store.js +1 -1
- package/compiler/transformer/style.js +45 -16
- package/compiler/transformer/view.js +23 -2
- package/loader/rollup-plugin-server-components.js +391 -0
- package/loader/vite-plugin-server-components.js +420 -0
- package/loader/webpack-loader-server-components.js +356 -0
- package/package.json +127 -74
- package/runtime/a11y/widgets.js +14 -1
- package/runtime/async.js +4 -0
- package/runtime/context.js +16 -3
- package/runtime/dom-adapter.js +5 -3
- package/runtime/dom-virtual-list.js +2 -1
- package/runtime/form.js +8 -3
- package/runtime/graphql/cache.js +1 -1
- package/runtime/graphql/client.js +22 -0
- package/runtime/graphql/hooks.js +12 -6
- package/runtime/graphql/subscriptions.js +4 -0
- package/runtime/hmr.js +6 -3
- package/runtime/http.js +1 -0
- package/runtime/i18n.js +2 -0
- package/runtime/lru-cache.js +3 -1
- package/runtime/native.js +46 -20
- package/runtime/pulse.js +3 -0
- package/runtime/router/core.js +5 -1
- package/runtime/router/index.js +17 -1
- package/runtime/router/psc-integration.js +301 -0
- package/runtime/security.js +58 -29
- package/runtime/server-components/actions-server.js +798 -0
- package/runtime/server-components/actions.js +389 -0
- package/runtime/server-components/client.js +447 -0
- package/runtime/server-components/error-sanitizer.js +438 -0
- package/runtime/server-components/index.js +275 -0
- package/runtime/server-components/security-csrf.js +593 -0
- package/runtime/server-components/security-errors.js +227 -0
- package/runtime/server-components/security-ratelimit.js +733 -0
- package/runtime/server-components/security-validation.js +467 -0
- package/runtime/server-components/security.js +598 -0
- package/runtime/server-components/serializer.js +617 -0
- package/runtime/server-components/server.js +382 -0
- package/runtime/server-components/types.js +383 -0
- package/runtime/server-components/utils/mutex.js +60 -0
- package/runtime/server-components/utils/path-sanitizer.js +109 -0
- package/runtime/ssr.js +2 -1
- package/runtime/store.js +19 -10
- package/runtime/utils.js +12 -128
- package/types/animation.d.ts +300 -0
- package/types/i18n.d.ts +283 -0
- package/types/persistence.d.ts +267 -0
- package/types/sse.d.ts +248 -0
- package/types/sw.d.ts +150 -0
- package/runtime/a11y.js.original +0 -1844
- package/runtime/graphql.js.original +0 -1326
- package/runtime/router.js.original +0 -1605
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse Server Components - Webpack Loader Extension
|
|
3
|
+
*
|
|
4
|
+
* Extends webpack-loader.js with Server Components support:
|
|
5
|
+
* - Detects Client Components via __directive metadata
|
|
6
|
+
* - Generates client manifest (component ID → chunk URL mapping)
|
|
7
|
+
* - Writes manifest to filesystem
|
|
8
|
+
*
|
|
9
|
+
* Usage in webpack.config.js:
|
|
10
|
+
* ```js
|
|
11
|
+
* const { addServerComponentsSupport } = require('pulse-js-framework/loader/webpack-loader-server-components');
|
|
12
|
+
*
|
|
13
|
+
* module.exports = {
|
|
14
|
+
* module: {
|
|
15
|
+
* rules: [
|
|
16
|
+
* {
|
|
17
|
+
* test: /\.pulse$/,
|
|
18
|
+
* use: [
|
|
19
|
+
* 'style-loader',
|
|
20
|
+
* 'css-loader',
|
|
21
|
+
* 'pulse-js-framework/loader/webpack-loader'
|
|
22
|
+
* ]
|
|
23
|
+
* }
|
|
24
|
+
* ]
|
|
25
|
+
* },
|
|
26
|
+
* plugins: [
|
|
27
|
+
* addServerComponentsSupport({
|
|
28
|
+
* manifestPath: 'dist/.pulse-manifest.json'
|
|
29
|
+
* })
|
|
30
|
+
* ]
|
|
31
|
+
* };
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @module pulse-js-framework/loader/webpack-loader-server-components
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import { writeFileSync, mkdirSync } from 'fs';
|
|
38
|
+
import { dirname, posix, relative } from 'path';
|
|
39
|
+
import { getComponentTypeFromSource } from '../compiler/directives.js';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Default options for Server Components Webpack plugin
|
|
43
|
+
*/
|
|
44
|
+
const DEFAULT_OPTIONS = {
|
|
45
|
+
// Output directory for client manifest
|
|
46
|
+
manifestPath: 'dist/.pulse-manifest.json',
|
|
47
|
+
|
|
48
|
+
// Public base path for chunk URLs (empty for relative paths)
|
|
49
|
+
base: '',
|
|
50
|
+
|
|
51
|
+
// Custom manifest filename
|
|
52
|
+
manifestFilename: '.pulse-manifest.json'
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Storage for Client Components per compilation
|
|
57
|
+
* WeakMap<Compilation, Map<componentId, { file, chunk }>>
|
|
58
|
+
*/
|
|
59
|
+
const compilationClientComponents = new WeakMap();
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get or create Client Components map for a compilation
|
|
63
|
+
*/
|
|
64
|
+
function getClientComponentsMap(compilation) {
|
|
65
|
+
if (!compilationClientComponents.has(compilation)) {
|
|
66
|
+
compilationClientComponents.set(compilation, new Map());
|
|
67
|
+
}
|
|
68
|
+
return compilationClientComponents.get(compilation);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Webpack plugin for Pulse Server Components support
|
|
73
|
+
*/
|
|
74
|
+
class PulseServerComponentsPlugin {
|
|
75
|
+
constructor(options = {}) {
|
|
76
|
+
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
apply(compiler) {
|
|
80
|
+
const pluginName = 'PulseServerComponentsPlugin';
|
|
81
|
+
|
|
82
|
+
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
|
|
83
|
+
const clientComponents = getClientComponentsMap(compilation);
|
|
84
|
+
const componentTypes = new Map(); // module.resource → 'client' | 'server' | 'shared'
|
|
85
|
+
|
|
86
|
+
// Hook into module processing to detect Client Components and track types
|
|
87
|
+
compilation.hooks.succeedModule.tap(pluginName, (module) => {
|
|
88
|
+
if (!module.resource) return;
|
|
89
|
+
|
|
90
|
+
// Get the compiled source
|
|
91
|
+
const source = module._source?.source?.();
|
|
92
|
+
if (!source || typeof source !== 'string') {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Determine component type for all JS/TS files
|
|
97
|
+
if (/\.(js|ts|jsx|tsx|pulse)$/.test(module.resource)) {
|
|
98
|
+
const componentType = getComponentTypeFromSource(source, module.resource);
|
|
99
|
+
componentTypes.set(module.resource, componentType);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Only process .pulse files for Client Component detection
|
|
103
|
+
if (!module.resource.endsWith('.pulse')) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Check if this module exports a Client Component
|
|
108
|
+
// Look for: __directive: "use client"
|
|
109
|
+
const directiveMatch = source.match(/__directive:\s*["']use client["']/);
|
|
110
|
+
|
|
111
|
+
if (directiveMatch) {
|
|
112
|
+
// Extract component ID
|
|
113
|
+
const componentIdMatch = source.match(/__componentId:\s*["'](\w+)["']/);
|
|
114
|
+
const exportMatch = source.match(/export const (\w+) = \{/);
|
|
115
|
+
|
|
116
|
+
const componentId = componentIdMatch ? componentIdMatch[1] : (exportMatch ? exportMatch[1] : null);
|
|
117
|
+
|
|
118
|
+
if (componentId) {
|
|
119
|
+
// Store this as a Client Component
|
|
120
|
+
clientComponents.set(componentId, {
|
|
121
|
+
file: module.resource,
|
|
122
|
+
chunk: null, // Will be filled later
|
|
123
|
+
moduleId: module.id
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const relativePath = relative(process.cwd(), module.resource);
|
|
127
|
+
console.log(`[Pulse Server Components] Detected Client Component: ${componentId} (${relativePath})`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Validate imports after module graph is built
|
|
133
|
+
compilation.hooks.finishModules.tap(pluginName, (modules) => {
|
|
134
|
+
for (const module of modules) {
|
|
135
|
+
if (!module.resource) continue;
|
|
136
|
+
|
|
137
|
+
const moduleType = componentTypes.get(module.resource);
|
|
138
|
+
if (moduleType !== 'client') continue;
|
|
139
|
+
|
|
140
|
+
// Check all dependencies
|
|
141
|
+
for (const dependency of module.dependencies || []) {
|
|
142
|
+
const depModule = compilation.moduleGraph?.getModule?.(dependency);
|
|
143
|
+
if (!depModule || !depModule.resource) continue;
|
|
144
|
+
|
|
145
|
+
const depType = componentTypes.get(depModule.resource);
|
|
146
|
+
|
|
147
|
+
// Client → Server violation
|
|
148
|
+
if (depType === 'server') {
|
|
149
|
+
const error = new Error(createImportViolationError(
|
|
150
|
+
module.resource,
|
|
151
|
+
depModule.resource,
|
|
152
|
+
dependency.userRequest || depModule.resource
|
|
153
|
+
));
|
|
154
|
+
compilation.errors.push(error);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Generate manifest after all assets are processed
|
|
161
|
+
compilation.hooks.processAssets.tap(
|
|
162
|
+
{
|
|
163
|
+
name: pluginName,
|
|
164
|
+
stage: compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE
|
|
165
|
+
},
|
|
166
|
+
(assets) => {
|
|
167
|
+
// Skip if no Client Components detected
|
|
168
|
+
if (clientComponents.size === 0) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Map modules to their chunks
|
|
173
|
+
for (const [componentId, info] of clientComponents.entries()) {
|
|
174
|
+
// Find chunk(s) containing this module
|
|
175
|
+
for (const chunk of compilation.chunks) {
|
|
176
|
+
for (const module of compilation.chunkGraph.getChunkModulesIterable(chunk)) {
|
|
177
|
+
if (module.resource === info.file) {
|
|
178
|
+
// Get chunk filename
|
|
179
|
+
const chunkFiles = Array.from(chunk.files);
|
|
180
|
+
const jsFile = chunkFiles.find(f => f.endsWith('.js'));
|
|
181
|
+
|
|
182
|
+
if (jsFile) {
|
|
183
|
+
info.chunk = jsFile;
|
|
184
|
+
console.log(`[Pulse Server Components] Mapped ${componentId} → ${jsFile}`);
|
|
185
|
+
}
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Build client manifest
|
|
193
|
+
const manifest = {
|
|
194
|
+
version: '1.0',
|
|
195
|
+
components: {}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
for (const [componentId, info] of clientComponents.entries()) {
|
|
199
|
+
if (info.chunk) {
|
|
200
|
+
const base = this.options.base || '';
|
|
201
|
+
const chunkUrl = posix.join(base, info.chunk);
|
|
202
|
+
|
|
203
|
+
manifest.components[componentId] = {
|
|
204
|
+
id: componentId,
|
|
205
|
+
chunk: chunkUrl,
|
|
206
|
+
exports: ['default', componentId]
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Emit manifest as asset
|
|
212
|
+
const manifestJson = JSON.stringify(manifest, null, 2);
|
|
213
|
+
const { RawSource } = compilation.compiler.webpack.sources;
|
|
214
|
+
|
|
215
|
+
compilation.emitAsset(
|
|
216
|
+
this.options.manifestFilename,
|
|
217
|
+
new RawSource(manifestJson)
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
console.log(`[Pulse Server Components] Generated client manifest with ${clientComponents.size} components`);
|
|
221
|
+
}
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
// Write manifest to filesystem after emit
|
|
225
|
+
compilation.hooks.afterProcessAssets.tap(pluginName, () => {
|
|
226
|
+
if (clientComponents.size === 0) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Build manifest object
|
|
231
|
+
const manifest = {
|
|
232
|
+
version: '1.0',
|
|
233
|
+
components: {}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
for (const [componentId, info] of clientComponents.entries()) {
|
|
237
|
+
if (info.chunk) {
|
|
238
|
+
const base = this.options.base || '';
|
|
239
|
+
const chunkUrl = posix.join(base, info.chunk);
|
|
240
|
+
|
|
241
|
+
manifest.components[componentId] = {
|
|
242
|
+
id: componentId,
|
|
243
|
+
chunk: chunkUrl,
|
|
244
|
+
exports: ['default', componentId]
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Write to file system
|
|
250
|
+
if (this.options.manifestPath) {
|
|
251
|
+
try {
|
|
252
|
+
const manifestDir = dirname(this.options.manifestPath);
|
|
253
|
+
mkdirSync(manifestDir, { recursive: true });
|
|
254
|
+
writeFileSync(this.options.manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');
|
|
255
|
+
console.log(`[Pulse Server Components] Manifest written to ${this.options.manifestPath}`);
|
|
256
|
+
} catch (error) {
|
|
257
|
+
console.warn(`[Pulse Server Components] Failed to write manifest: ${error.message}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ============================================================================
|
|
266
|
+
// Helper Functions
|
|
267
|
+
// ============================================================================
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Create import violation error message
|
|
271
|
+
* @param {string} clientPath - Client Component path
|
|
272
|
+
* @param {string} serverPath - Server Component path
|
|
273
|
+
* @param {string} importSource - Import statement source
|
|
274
|
+
* @returns {string} Error message
|
|
275
|
+
*/
|
|
276
|
+
function createImportViolationError(clientPath, serverPath, importSource) {
|
|
277
|
+
const clientRelative = relative(process.cwd(), clientPath);
|
|
278
|
+
const serverRelative = relative(process.cwd(), serverPath);
|
|
279
|
+
|
|
280
|
+
return `
|
|
281
|
+
[Pulse] Import Violation: Client Component cannot import Server Component
|
|
282
|
+
at ${clientRelative}
|
|
283
|
+
importing ${importSource}
|
|
284
|
+
resolved to ${serverRelative}
|
|
285
|
+
|
|
286
|
+
Client Components can only import:
|
|
287
|
+
• Other Client Components ('use client')
|
|
288
|
+
• Shared utilities (no directive)
|
|
289
|
+
• Third-party packages
|
|
290
|
+
|
|
291
|
+
→ Move shared logic to a Client Component
|
|
292
|
+
→ Use Server Actions for server-side operations
|
|
293
|
+
→ Create a wrapper Client Component that calls Server Actions
|
|
294
|
+
|
|
295
|
+
See: https://pulse-js.fr/server-components#import-rules
|
|
296
|
+
`.trim();
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Helper function to add Server Components support to Webpack config
|
|
301
|
+
*
|
|
302
|
+
* @param {Object} options - Plugin options
|
|
303
|
+
* @returns {PulseServerComponentsPlugin} Webpack plugin instance
|
|
304
|
+
*
|
|
305
|
+
* @example
|
|
306
|
+
* const { addServerComponentsSupport } = require('pulse-js-framework/loader/webpack-loader-server-components');
|
|
307
|
+
*
|
|
308
|
+
* module.exports = {
|
|
309
|
+
* plugins: [
|
|
310
|
+
* addServerComponentsSupport({ manifestPath: 'dist/.pulse-manifest.json' })
|
|
311
|
+
* ]
|
|
312
|
+
* };
|
|
313
|
+
*/
|
|
314
|
+
export function addServerComponentsSupport(options = {}) {
|
|
315
|
+
return new PulseServerComponentsPlugin(options);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Helper function to load client manifest (for SSR)
|
|
320
|
+
*
|
|
321
|
+
* @param {string} manifestPath - Path to manifest file
|
|
322
|
+
* @returns {Object} Client manifest
|
|
323
|
+
*/
|
|
324
|
+
export function loadClientManifest(manifestPath) {
|
|
325
|
+
try {
|
|
326
|
+
const { readFileSync } = require('fs');
|
|
327
|
+
const content = readFileSync(manifestPath, 'utf-8');
|
|
328
|
+
return JSON.parse(content);
|
|
329
|
+
} catch (error) {
|
|
330
|
+
console.warn(`Failed to load client manifest from ${manifestPath}:`, error.message);
|
|
331
|
+
return { version: '1.0', components: {} };
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Helper function to get client component chunk URL
|
|
337
|
+
*
|
|
338
|
+
* @param {Object} manifest - Client manifest
|
|
339
|
+
* @param {string} componentId - Component ID
|
|
340
|
+
* @returns {string|null} Chunk URL or null if not found
|
|
341
|
+
*/
|
|
342
|
+
export function getComponentChunk(manifest, componentId) {
|
|
343
|
+
return manifest.components[componentId]?.chunk || null;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Helper function to get all client component IDs
|
|
348
|
+
*
|
|
349
|
+
* @param {Object} manifest - Client manifest
|
|
350
|
+
* @returns {Set<string>} Set of component IDs
|
|
351
|
+
*/
|
|
352
|
+
export function getClientComponentIds(manifest) {
|
|
353
|
+
return new Set(Object.keys(manifest.components));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export default PulseServerComponentsPlugin;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pulse-js-framework",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "A declarative DOM framework with CSS selector-based structure and reactive pulsations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -90,13 +90,24 @@
|
|
|
90
90
|
"./compiler/parser": "./compiler/parser.js",
|
|
91
91
|
"./compiler/transformer": "./compiler/transformer.js",
|
|
92
92
|
"./compiler/preprocessor": "./compiler/preprocessor.js",
|
|
93
|
+
"./runtime/context": {
|
|
94
|
+
"types": "./types/context.d.ts",
|
|
95
|
+
"default": "./runtime/context.js"
|
|
96
|
+
},
|
|
97
|
+
"./runtime/errors": {
|
|
98
|
+
"types": "./types/errors.d.ts",
|
|
99
|
+
"default": "./runtime/errors.js"
|
|
100
|
+
},
|
|
93
101
|
"./core/errors": "./runtime/errors.js",
|
|
94
102
|
"./vite": {
|
|
95
103
|
"types": "./types/index.d.ts",
|
|
96
104
|
"default": "./loader/vite-plugin.js"
|
|
97
105
|
},
|
|
106
|
+
"./vite/server-components": "./loader/vite-plugin-server-components.js",
|
|
98
107
|
"./webpack": "./loader/webpack-loader.js",
|
|
108
|
+
"./webpack/server-components": "./loader/webpack-loader-server-components.js",
|
|
99
109
|
"./rollup": "./loader/rollup-plugin.js",
|
|
110
|
+
"./rollup/server-components": "./loader/rollup-plugin-server-components.js",
|
|
100
111
|
"./esbuild": "./loader/esbuild-plugin.js",
|
|
101
112
|
"./parcel": "./loader/parcel-plugin.js",
|
|
102
113
|
"./swc": "./loader/swc-plugin.js",
|
|
@@ -105,6 +116,7 @@
|
|
|
105
116
|
"./runtime/ssr-stream": "./runtime/ssr-stream.js",
|
|
106
117
|
"./runtime/ssr-mismatch": "./runtime/ssr-mismatch.js",
|
|
107
118
|
"./runtime/ssr-preload": "./runtime/ssr-preload.js",
|
|
119
|
+
"./runtime/server-components": "./runtime/server-components/index.js",
|
|
108
120
|
"./server": "./server/index.js",
|
|
109
121
|
"./server/express": "./server/express.js",
|
|
110
122
|
"./server/hono": "./server/hono.js",
|
|
@@ -135,7 +147,6 @@
|
|
|
135
147
|
"files": [
|
|
136
148
|
"index.js",
|
|
137
149
|
"cli/",
|
|
138
|
-
"core/",
|
|
139
150
|
"runtime/",
|
|
140
151
|
"compiler/",
|
|
141
152
|
"loader/",
|
|
@@ -147,97 +158,139 @@
|
|
|
147
158
|
"LICENSE"
|
|
148
159
|
],
|
|
149
160
|
"scripts": {
|
|
150
|
-
"
|
|
161
|
+
"bench": "node benchmarks/index.js",
|
|
162
|
+
"bench:json": "node benchmarks/index.js --json",
|
|
163
|
+
"build:netlify": "node scripts/build-netlify.js",
|
|
164
|
+
"docs": "node cli/index.js dev docs",
|
|
165
|
+
"test": "node scripts/sync-tests.js --fix && node scripts/run-all-tests.js",
|
|
166
|
+
"test:a11y": "node test/a11y.test.js",
|
|
167
|
+
"test:a11y-enhanced": "node test/a11y-enhanced.test.js",
|
|
168
|
+
"test:a11y-focus-coverage-boost": "node --test test/a11y-focus-coverage-boost.test.js",
|
|
169
|
+
"test:a11y-widgets-coverage-boost": "node --test test/a11y-widgets-coverage-boost.test.js",
|
|
170
|
+
"test:analyze": "node test/analyze.test.js",
|
|
171
|
+
"test:animation": "node --test test/animation.test.js",
|
|
172
|
+
"test:async": "node test/async.test.js",
|
|
173
|
+
"test:async-coverage": "node test/async-coverage.test.js",
|
|
174
|
+
"test:benchmarks": "node test/benchmarks.test.js",
|
|
175
|
+
"test:build": "node test/build.test.js",
|
|
176
|
+
"test:build-extended": "node --test test/build-extended.test.js",
|
|
177
|
+
"test:cli": "node test/cli.test.js",
|
|
178
|
+
"test:cli-create": "node test/cli-create.test.js",
|
|
179
|
+
"test:cli-help": "node --test test/cli-help.test.js",
|
|
180
|
+
"test:cli-logger": "node --test test/cli-logger.test.js",
|
|
181
|
+
"test:cli-mobile": "node --test test/cli-mobile.test.js",
|
|
182
|
+
"test:cli-release": "node --test test/cli-release.test.js",
|
|
183
|
+
"test:cli-ui": "node test/cli-ui.test.js",
|
|
151
184
|
"test:compiler": "node test/compiler.test.js",
|
|
152
|
-
"test:
|
|
153
|
-
"test:
|
|
185
|
+
"test:context": "node test/context.test.js",
|
|
186
|
+
"test:context-stress": "node test/context-stress.test.js",
|
|
154
187
|
"test:css-parsing": "node test/css-parsing.test.js",
|
|
155
|
-
"test:
|
|
156
|
-
"test:
|
|
188
|
+
"test:dev-server": "node --test test/dev-server.test.js",
|
|
189
|
+
"test:devtools": "node test/devtools.test.js",
|
|
190
|
+
"test:docs": "node test/docs.test.js",
|
|
191
|
+
"test:docs-nav": "node test/docs-navigation.test.js",
|
|
192
|
+
"test:docs-navigation": "node test/docs-navigation.test.js",
|
|
193
|
+
"test:doctor": "node test/doctor.test.js",
|
|
157
194
|
"test:dom": "node test/dom.test.js",
|
|
158
|
-
"test:dom-
|
|
159
|
-
"test:dom-
|
|
195
|
+
"test:dom-adapter": "node test/dom-adapter.test.js",
|
|
196
|
+
"test:dom-advanced": "node test/dom-advanced.test.js",
|
|
197
|
+
"test:dom-binding": "node test/dom-binding.test.js",
|
|
160
198
|
"test:dom-conditional": "node test/dom-conditional.test.js",
|
|
199
|
+
"test:dom-element": "node test/dom-element.test.js",
|
|
200
|
+
"test:dom-element-coverage-boost": "node --test test/dom-element-coverage-boost.test.js",
|
|
201
|
+
"test:dom-event-delegate": "node test/dom-event-delegate.test.js",
|
|
161
202
|
"test:dom-lifecycle": "node test/dom-lifecycle.test.js",
|
|
203
|
+
"test:dom-list": "node test/dom-list.test.js",
|
|
204
|
+
"test:dom-recycle": "node test/dom-recycle.test.js",
|
|
162
205
|
"test:dom-selector": "node test/dom-selector.test.js",
|
|
163
|
-
"test:dom-
|
|
164
|
-
"test:dom-advanced": "node test/dom-advanced.test.js",
|
|
206
|
+
"test:dom-virtual-list": "node test/dom-virtual-list.test.js",
|
|
165
207
|
"test:enhanced-mock-adapter": "node test/enhanced-mock-adapter.test.js",
|
|
166
|
-
"test:
|
|
167
|
-
"test:
|
|
168
|
-
"test:
|
|
169
|
-
"test:hmr": "node test/hmr.test.js",
|
|
170
|
-
"test:lint": "node test/lint.test.js",
|
|
171
|
-
"test:format": "node test/format.test.js",
|
|
172
|
-
"test:analyze": "node test/analyze.test.js",
|
|
173
|
-
"test:cli": "node test/cli.test.js",
|
|
174
|
-
"test:cli-ui": "node test/cli-ui.test.js",
|
|
175
|
-
"test:cli-create": "node test/cli-create.test.js",
|
|
176
|
-
"test:lru-cache": "node test/lru-cache.test.js",
|
|
177
|
-
"test:utils": "node test/utils.test.js",
|
|
178
|
-
"test:utils-coverage": "node test/utils-coverage.test.js",
|
|
179
|
-
"test:docs": "node test/docs.test.js",
|
|
180
|
-
"test:docs-nav": "node test/docs-navigation.test.js",
|
|
181
|
-
"test:async": "node test/async.test.js",
|
|
182
|
-
"test:async-coverage": "node test/async-coverage.test.js",
|
|
208
|
+
"test:error-sanitizer": "node --test test/error-sanitizer.test.js",
|
|
209
|
+
"test:errors": "node test/errors.test.js",
|
|
210
|
+
"test:esbuild-plugin": "node test/esbuild-plugin.test.js",
|
|
183
211
|
"test:form": "node test/form.test.js",
|
|
212
|
+
"test:form-coverage": "node test/form-coverage.test.js",
|
|
213
|
+
"test:form-edge-cases": "node test/form-edge-cases.test.js",
|
|
184
214
|
"test:form-v2": "node test/form-v2.test.js",
|
|
185
|
-
"test:
|
|
186
|
-
"test:devtools": "node test/devtools.test.js",
|
|
187
|
-
"test:native": "node test/native.test.js",
|
|
188
|
-
"test:a11y": "node test/a11y.test.js",
|
|
189
|
-
"test:a11y-enhanced": "node test/a11y-enhanced.test.js",
|
|
190
|
-
"test:logger": "node test/logger.test.js",
|
|
191
|
-
"test:logger-prod": "node test/logger-prod.test.js",
|
|
192
|
-
"test:errors": "node test/errors.test.js",
|
|
193
|
-
"test:security": "node test/security.test.js",
|
|
194
|
-
"test:websocket": "node test/websocket.test.js",
|
|
215
|
+
"test:format": "node test/format.test.js",
|
|
195
216
|
"test:graphql": "node test/graphql.test.js",
|
|
196
217
|
"test:graphql-coverage": "node test/graphql-coverage.test.js",
|
|
197
|
-
"test:doctor": "node test/doctor.test.js",
|
|
198
|
-
"test:scaffold": "node test/scaffold.test.js",
|
|
199
|
-
"test:test-runner": "node test/test-runner.test.js",
|
|
200
|
-
"test:build": "node test/build.test.js",
|
|
201
|
-
"test:integration": "node test/integration.test.js",
|
|
202
|
-
"test:context-stress": "node test/context-stress.test.js",
|
|
203
|
-
"test:form-edge-cases": "node test/form-edge-cases.test.js",
|
|
204
|
-
"test:form-coverage": "node test/form-coverage.test.js",
|
|
205
218
|
"test:graphql-subscriptions": "node test/graphql-subscriptions.test.js",
|
|
219
|
+
"test:graphql-subscriptions-coverage-boost": "node --test test/graphql-subscriptions-coverage-boost.test.js",
|
|
220
|
+
"test:hmr": "node test/hmr.test.js",
|
|
221
|
+
"test:hmr-coverage-boost": "node --test test/hmr-coverage-boost.test.js",
|
|
222
|
+
"test:http": "node test/http.test.js",
|
|
206
223
|
"test:http-edge-cases": "node test/http-edge-cases.test.js",
|
|
224
|
+
"test:i18n": "node --test test/i18n.test.js",
|
|
225
|
+
"test:integration": "node test/integration.test.js",
|
|
207
226
|
"test:integration-advanced": "node test/integration-advanced.test.js",
|
|
208
|
-
"test:websocket-stress": "node test/websocket-stress.test.js",
|
|
209
|
-
"test:ssr": "node test/ssr.test.js",
|
|
210
|
-
"test:ssr-hydrator": "node test/ssr-hydrator.test.js",
|
|
211
|
-
"test:webpack-loader": "node test/webpack-loader.test.js",
|
|
212
|
-
"test:rollup-plugin": "node test/rollup-plugin.test.js",
|
|
213
|
-
"test:esbuild-plugin": "node test/esbuild-plugin.test.js",
|
|
214
|
-
"test:parcel-plugin": "node test/parcel-plugin.test.js",
|
|
215
|
-
"test:swc-plugin": "node test/swc-plugin.test.js",
|
|
216
|
-
"test:dom-recycle": "node test/dom-recycle.test.js",
|
|
217
|
-
"test:dom-virtual-list": "node test/dom-virtual-list.test.js",
|
|
218
|
-
"test:dom-event-delegate": "node test/dom-event-delegate.test.js",
|
|
219
|
-
"test:dom-binding": "node test/dom-binding.test.js",
|
|
220
227
|
"test:interceptor-manager": "node test/interceptor-manager.test.js",
|
|
221
|
-
"test:
|
|
228
|
+
"test:lint": "node test/lint.test.js",
|
|
229
|
+
"test:lite": "node test/lite.test.js",
|
|
230
|
+
"test:logger": "node test/logger.test.js",
|
|
231
|
+
"test:logger-coverage-boost": "node --test test/logger-coverage-boost.test.js",
|
|
232
|
+
"test:logger-prod": "node test/logger-prod.test.js",
|
|
233
|
+
"test:lru-cache": "node test/lru-cache.test.js",
|
|
222
234
|
"test:memory-cleanup": "node --test test/memory-cleanup.test.js",
|
|
223
|
-
"test:
|
|
235
|
+
"test:mutex": "node --test test/mutex.test.js",
|
|
236
|
+
"test:native": "node test/native.test.js",
|
|
237
|
+
"test:native-coverage-boost": "node --test test/native-coverage-boost.test.js",
|
|
238
|
+
"test:parcel-plugin": "node test/parcel-plugin.test.js",
|
|
239
|
+
"test:path-sanitizer": "node --test test/path-sanitizer.test.js",
|
|
240
|
+
"test:parser-coverage": "node test/parser-coverage.test.js",
|
|
224
241
|
"test:persistence": "node --test test/persistence.test.js",
|
|
225
|
-
"test:
|
|
242
|
+
"test:persistence-coverage-boost": "node --test test/persistence-coverage-boost.test.js",
|
|
226
243
|
"test:portal": "node --test test/portal.test.js",
|
|
227
|
-
"test:
|
|
228
|
-
"test:
|
|
229
|
-
"test:
|
|
230
|
-
"test:
|
|
244
|
+
"test:preprocessor": "node test/preprocessor.test.js",
|
|
245
|
+
"test:pulse": "node test/pulse.test.js",
|
|
246
|
+
"test:rollup-plugin": "node test/rollup-plugin.test.js",
|
|
247
|
+
"test:router": "node test/router.test.js",
|
|
248
|
+
"test:router-psc": "node --test test/router-psc-integration.test.js",
|
|
249
|
+
"test:router-psc-integration": "node test/router-psc-integration.test.js",
|
|
250
|
+
"test:scaffold": "node test/scaffold.test.js",
|
|
251
|
+
"test:security": "node test/security.test.js",
|
|
252
|
+
"test:security-coverage-boost": "node --test test/security-coverage-boost.test.js",
|
|
253
|
+
"test:security-regression": "node test/security-regression.test.js",
|
|
254
|
+
"test:server-utils": "node --test test/server-utils.test.js",
|
|
255
|
+
"test:server-actions": "node --test test/server-actions.test.js",
|
|
256
|
+
"test:server-actions-client": "node --test test/server-actions-client.test.js",
|
|
257
|
+
"test:server-actions-server-extended": "node --test test/server-actions-server-extended.test.js",
|
|
258
|
+
"test:server-adapters": "node --test test/server-adapters.test.js",
|
|
259
|
+
"test:server-components-build-tools": "node --test test/server-components-build-tools.test.js",
|
|
260
|
+
"test:server-components-client": "node --test test/server-components-client.test.js",
|
|
261
|
+
"test:server-components-compiler": "node --test test/server-components-compiler.test.js",
|
|
262
|
+
"test:server-components-core": "node --test test/server-components-core.test.js",
|
|
263
|
+
"test:server-components-csrf": "node --test test/server-components-csrf.test.js",
|
|
264
|
+
"test:server-components-ratelimit": "node --test test/server-components-ratelimit.test.js",
|
|
265
|
+
"test:server-components-ratelimit-extended": "node --test test/server-components-ratelimit-extended.test.js",
|
|
266
|
+
"test:server-components-security": "node --test test/server-components-security.test.js",
|
|
267
|
+
"test:server-components-security-comprehensive": "node --test test/server-components-security-comprehensive.test.js",
|
|
268
|
+
"test:server-components-serializer": "node --test test/server-components-serializer.test.js",
|
|
269
|
+
"test:server-components-validation": "node --test test/server-components-validation.test.js",
|
|
270
|
+
"test:sourcemap": "node test/sourcemap.test.js",
|
|
271
|
+
"test:sourcemap-coverage-boost": "node --test test/sourcemap-coverage-boost.test.js",
|
|
272
|
+
"test:sse": "node --test test/sse.test.js",
|
|
273
|
+
"test:ssg": "node --test test/ssg.test.js",
|
|
274
|
+
"test:ssr": "node test/ssr.test.js",
|
|
275
|
+
"test:ssr-directives": "node --test test/ssr-directives.test.js",
|
|
276
|
+
"test:ssr-hydrator": "node test/ssr-hydrator.test.js",
|
|
231
277
|
"test:ssr-mismatch": "node --test test/ssr-mismatch.test.js",
|
|
232
278
|
"test:ssr-preload": "node --test test/ssr-preload.test.js",
|
|
233
|
-
"test:ssr-
|
|
234
|
-
"test:
|
|
235
|
-
"test:
|
|
236
|
-
"
|
|
237
|
-
"
|
|
238
|
-
"
|
|
239
|
-
"
|
|
240
|
-
"
|
|
279
|
+
"test:ssr-stream": "node --test test/ssr-stream.test.js",
|
|
280
|
+
"test:store": "node test/store.test.js",
|
|
281
|
+
"test:sw": "node --test test/sw.test.js",
|
|
282
|
+
"test:swc-plugin": "node test/swc-plugin.test.js",
|
|
283
|
+
"test:sync": "node scripts/sync-tests.js",
|
|
284
|
+
"test:sync:fix": "node scripts/sync-tests.js --fix",
|
|
285
|
+
"test:test-runner": "node test/test-runner.test.js",
|
|
286
|
+
"test:utils": "node test/utils.test.js",
|
|
287
|
+
"test:utils-coverage": "node test/utils-coverage.test.js",
|
|
288
|
+
"test:vite-plugin": "node --test test/vite-plugin.test.js",
|
|
289
|
+
"test:webpack-loader": "node test/webpack-loader.test.js",
|
|
290
|
+
"test:websocket": "node test/websocket.test.js",
|
|
291
|
+
"test:websocket-coverage-boost": "node --test test/websocket-coverage-boost.test.js",
|
|
292
|
+
"test:websocket-stress": "node test/websocket-stress.test.js",
|
|
293
|
+
"version": "node scripts/sync-version.js"
|
|
241
294
|
},
|
|
242
295
|
"keywords": [
|
|
243
296
|
"framework",
|
|
@@ -264,7 +317,7 @@
|
|
|
264
317
|
"url": "https://github.com/vincenthirtz/pulse-js-framework/issues"
|
|
265
318
|
},
|
|
266
319
|
"engines": {
|
|
267
|
-
"node": ">=
|
|
320
|
+
"node": ">=20.0.0"
|
|
268
321
|
},
|
|
269
322
|
"dependencies": {},
|
|
270
323
|
"devDependencies": {}
|
package/runtime/a11y/widgets.js
CHANGED
|
@@ -445,6 +445,7 @@ export function createMenu(button, menu, options = {}) {
|
|
|
445
445
|
const menuId = menu.id || generateId('menu');
|
|
446
446
|
let rovingCleanup = null;
|
|
447
447
|
let documentClickHandler = null;
|
|
448
|
+
let documentClickTimeout = null;
|
|
448
449
|
|
|
449
450
|
// Set ARIA attributes
|
|
450
451
|
menu.id = menuId;
|
|
@@ -476,7 +477,8 @@ export function createMenu(button, menu, options = {}) {
|
|
|
476
477
|
if (firstItem) firstItem.focus();
|
|
477
478
|
|
|
478
479
|
// Close on click outside (delay to avoid immediate close)
|
|
479
|
-
setTimeout(() => {
|
|
480
|
+
documentClickTimeout = setTimeout(() => {
|
|
481
|
+
documentClickTimeout = null;
|
|
480
482
|
documentClickHandler = (e) => {
|
|
481
483
|
if (!button.contains(e.target) && !menu.contains(e.target)) {
|
|
482
484
|
close();
|
|
@@ -498,6 +500,11 @@ export function createMenu(button, menu, options = {}) {
|
|
|
498
500
|
rovingCleanup = null;
|
|
499
501
|
}
|
|
500
502
|
|
|
503
|
+
if (documentClickTimeout) {
|
|
504
|
+
clearTimeout(documentClickTimeout);
|
|
505
|
+
documentClickTimeout = null;
|
|
506
|
+
}
|
|
507
|
+
|
|
501
508
|
if (documentClickHandler) {
|
|
502
509
|
document.removeEventListener('click', documentClickHandler);
|
|
503
510
|
documentClickHandler = null;
|
|
@@ -533,11 +540,17 @@ export function createMenu(button, menu, options = {}) {
|
|
|
533
540
|
button.removeEventListener('click', toggle);
|
|
534
541
|
button.removeEventListener('keydown', handleButtonKeyDown);
|
|
535
542
|
menu.removeEventListener('keydown', handleMenuKeyDown);
|
|
543
|
+
if (documentClickTimeout) {
|
|
544
|
+
clearTimeout(documentClickTimeout);
|
|
545
|
+
documentClickTimeout = null;
|
|
546
|
+
}
|
|
536
547
|
if (documentClickHandler) {
|
|
537
548
|
document.removeEventListener('click', documentClickHandler);
|
|
549
|
+
documentClickHandler = null;
|
|
538
550
|
}
|
|
539
551
|
if (rovingCleanup) {
|
|
540
552
|
rovingCleanup();
|
|
553
|
+
rovingCleanup = null;
|
|
541
554
|
}
|
|
542
555
|
};
|
|
543
556
|
|