brustjs 0.1.62-alpha → 0.1.64-alpha
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brustjs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.64-alpha",
|
|
4
4
|
"description": "Bun + Rust SSR framework — React on the server, Rust everywhere else (napi cdylib + per-worker SharedArrayBuffer).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
"typescript": "^6.0.3"
|
|
42
42
|
},
|
|
43
43
|
"optionalDependencies": {
|
|
44
|
-
"brustjs-darwin-x64": "0.1.
|
|
45
|
-
"brustjs-darwin-arm64": "0.1.
|
|
46
|
-
"brustjs-linux-x64-gnu": "0.1.
|
|
47
|
-
"brustjs-linux-arm64-gnu": "0.1.
|
|
48
|
-
"brustjs-linux-x64-musl": "0.1.
|
|
49
|
-
"brustjs-linux-arm64-musl": "0.1.
|
|
44
|
+
"brustjs-darwin-x64": "0.1.64-alpha",
|
|
45
|
+
"brustjs-darwin-arm64": "0.1.64-alpha",
|
|
46
|
+
"brustjs-linux-x64-gnu": "0.1.64-alpha",
|
|
47
|
+
"brustjs-linux-arm64-gnu": "0.1.64-alpha",
|
|
48
|
+
"brustjs-linux-x64-musl": "0.1.64-alpha",
|
|
49
|
+
"brustjs-linux-arm64-musl": "0.1.64-alpha"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"react": "^19.2.6",
|
|
@@ -8,6 +8,22 @@ import { islandChunkBasename } from '../islands/chunk-id.ts'
|
|
|
8
8
|
import { emitBehaviorSsrModule } from '../islands/behavior-ssr-loader.ts'
|
|
9
9
|
import { DIRECTIVES_BOOTSTRAP, ISLANDS_IMPORTMAP_AND_BOOTSTRAP } from '../islands/importmap.ts'
|
|
10
10
|
|
|
11
|
+
const NATIVE_INLINE_FALLBACK_WARNING = /^native component "([^"\r\n]+)" not inlined: ([\s\S]+)$/
|
|
12
|
+
|
|
13
|
+
export function formatCompilerWarning(warning: string): string {
|
|
14
|
+
const match = NATIVE_INLINE_FALLBACK_WARNING.exec(warning)
|
|
15
|
+
if (!match) return `brust: ${warning}`
|
|
16
|
+
|
|
17
|
+
const [, component, reason] = match
|
|
18
|
+
return [
|
|
19
|
+
`brust: warning — native component "${component}" was not inlined`,
|
|
20
|
+
` reason: ${reason}`,
|
|
21
|
+
' impact: rendered through React SSR; React hooks and event handlers are not hydrated automatically on a native route',
|
|
22
|
+
` interactive fix: use <Island component={${component}} props={...} />`,
|
|
23
|
+
` zero-JS fix: rewrite ${component} using native-compatible JSX`,
|
|
24
|
+
].join('\n')
|
|
25
|
+
}
|
|
26
|
+
|
|
11
27
|
/** Gather transitive component sources starting from a page source file.
|
|
12
28
|
*
|
|
13
29
|
* BFS/DFS over local imports reachable from `pageSourcePath`:
|
|
@@ -936,8 +952,12 @@ export async function emitNativeTemplates(opts: NativeRouteEmitOpts): Promise<Na
|
|
|
936
952
|
)
|
|
937
953
|
}
|
|
938
954
|
|
|
939
|
-
// Print non-fatal compiler warnings to stderr.
|
|
940
|
-
|
|
955
|
+
// Print non-fatal compiler warnings to stderr. Native-inline fallbacks get
|
|
956
|
+
// actionable multiline guidance; all other warning strings retain the
|
|
957
|
+
// existing one-line prefix.
|
|
958
|
+
for (const warning of compiled.warnings ?? []) {
|
|
959
|
+
process.stderr.write(`${formatCompilerWarning(warning)}\n`)
|
|
960
|
+
}
|
|
941
961
|
|
|
942
962
|
// SPA navigation extracts the FIRST <main>…</main> block, so a native route
|
|
943
963
|
// template must hold exactly one <main>. More than one (typically a leaf
|
package/runtime/index.js
CHANGED
|
@@ -77,8 +77,8 @@ function requireNative() {
|
|
|
77
77
|
try {
|
|
78
78
|
const binding = require('brustjs-android-arm64')
|
|
79
79
|
const bindingPackageVersion = require('brustjs-android-arm64/package.json').version
|
|
80
|
-
if (bindingPackageVersion !== '0.1.
|
|
81
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
80
|
+
if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
81
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
82
82
|
}
|
|
83
83
|
return binding
|
|
84
84
|
} catch (e) {
|
|
@@ -93,8 +93,8 @@ function requireNative() {
|
|
|
93
93
|
try {
|
|
94
94
|
const binding = require('brustjs-android-arm-eabi')
|
|
95
95
|
const bindingPackageVersion = require('brustjs-android-arm-eabi/package.json').version
|
|
96
|
-
if (bindingPackageVersion !== '0.1.
|
|
97
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
96
|
+
if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
97
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
98
98
|
}
|
|
99
99
|
return binding
|
|
100
100
|
} catch (e) {
|
|
@@ -114,8 +114,8 @@ function requireNative() {
|
|
|
114
114
|
try {
|
|
115
115
|
const binding = require('brustjs-win32-x64-gnu')
|
|
116
116
|
const bindingPackageVersion = require('brustjs-win32-x64-gnu/package.json').version
|
|
117
|
-
if (bindingPackageVersion !== '0.1.
|
|
118
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
117
|
+
if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
118
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
119
119
|
}
|
|
120
120
|
return binding
|
|
121
121
|
} catch (e) {
|
|
@@ -130,8 +130,8 @@ function requireNative() {
|
|
|
130
130
|
try {
|
|
131
131
|
const binding = require('brustjs-win32-x64-msvc')
|
|
132
132
|
const bindingPackageVersion = require('brustjs-win32-x64-msvc/package.json').version
|
|
133
|
-
if (bindingPackageVersion !== '0.1.
|
|
134
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
133
|
+
if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
134
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
135
135
|
}
|
|
136
136
|
return binding
|
|
137
137
|
} catch (e) {
|
|
@@ -147,8 +147,8 @@ function requireNative() {
|
|
|
147
147
|
try {
|
|
148
148
|
const binding = require('brustjs-win32-ia32-msvc')
|
|
149
149
|
const bindingPackageVersion = require('brustjs-win32-ia32-msvc/package.json').version
|
|
150
|
-
if (bindingPackageVersion !== '0.1.
|
|
151
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
150
|
+
if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
151
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
152
152
|
}
|
|
153
153
|
return binding
|
|
154
154
|
} catch (e) {
|
|
@@ -163,8 +163,8 @@ function requireNative() {
|
|
|
163
163
|
try {
|
|
164
164
|
const binding = require('brustjs-win32-arm64-msvc')
|
|
165
165
|
const bindingPackageVersion = require('brustjs-win32-arm64-msvc/package.json').version
|
|
166
|
-
if (bindingPackageVersion !== '0.1.
|
|
167
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
166
|
+
if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
167
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
168
168
|
}
|
|
169
169
|
return binding
|
|
170
170
|
} catch (e) {
|
|
@@ -182,8 +182,8 @@ function requireNative() {
|
|
|
182
182
|
try {
|
|
183
183
|
const binding = require('brustjs-darwin-universal')
|
|
184
184
|
const bindingPackageVersion = require('brustjs-darwin-universal/package.json').version
|
|
185
|
-
if (bindingPackageVersion !== '0.1.
|
|
186
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
185
|
+
if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
186
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
187
187
|
}
|
|
188
188
|
return binding
|
|
189
189
|
} catch (e) {
|
|
@@ -198,8 +198,8 @@ function requireNative() {
|
|
|
198
198
|
try {
|
|
199
199
|
const binding = require('brustjs-darwin-x64')
|
|
200
200
|
const bindingPackageVersion = require('brustjs-darwin-x64/package.json').version
|
|
201
|
-
if (bindingPackageVersion !== '0.1.
|
|
202
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
201
|
+
if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
202
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
203
203
|
}
|
|
204
204
|
return binding
|
|
205
205
|
} catch (e) {
|
|
@@ -214,8 +214,8 @@ function requireNative() {
|
|
|
214
214
|
try {
|
|
215
215
|
const binding = require('brustjs-darwin-arm64')
|
|
216
216
|
const bindingPackageVersion = require('brustjs-darwin-arm64/package.json').version
|
|
217
|
-
if (bindingPackageVersion !== '0.1.
|
|
218
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
217
|
+
if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
218
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
219
219
|
}
|
|
220
220
|
return binding
|
|
221
221
|
} catch (e) {
|
|
@@ -234,8 +234,8 @@ function requireNative() {
|
|
|
234
234
|
try {
|
|
235
235
|
const binding = require('brustjs-freebsd-x64')
|
|
236
236
|
const bindingPackageVersion = require('brustjs-freebsd-x64/package.json').version
|
|
237
|
-
if (bindingPackageVersion !== '0.1.
|
|
238
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
237
|
+
if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
238
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
239
239
|
}
|
|
240
240
|
return binding
|
|
241
241
|
} catch (e) {
|
|
@@ -250,8 +250,8 @@ function requireNative() {
|
|
|
250
250
|
try {
|
|
251
251
|
const binding = require('brustjs-freebsd-arm64')
|
|
252
252
|
const bindingPackageVersion = require('brustjs-freebsd-arm64/package.json').version
|
|
253
|
-
if (bindingPackageVersion !== '0.1.
|
|
254
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
253
|
+
if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
254
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
255
255
|
}
|
|
256
256
|
return binding
|
|
257
257
|
} catch (e) {
|
|
@@ -271,8 +271,8 @@ function requireNative() {
|
|
|
271
271
|
try {
|
|
272
272
|
const binding = require('brustjs-linux-x64-musl')
|
|
273
273
|
const bindingPackageVersion = require('brustjs-linux-x64-musl/package.json').version
|
|
274
|
-
if (bindingPackageVersion !== '0.1.
|
|
275
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
274
|
+
if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
275
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
276
276
|
}
|
|
277
277
|
return binding
|
|
278
278
|
} catch (e) {
|
|
@@ -287,8 +287,8 @@ function requireNative() {
|
|
|
287
287
|
try {
|
|
288
288
|
const binding = require('brustjs-linux-x64-gnu')
|
|
289
289
|
const bindingPackageVersion = require('brustjs-linux-x64-gnu/package.json').version
|
|
290
|
-
if (bindingPackageVersion !== '0.1.
|
|
291
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
290
|
+
if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
291
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
292
292
|
}
|
|
293
293
|
return binding
|
|
294
294
|
} catch (e) {
|
|
@@ -305,8 +305,8 @@ function requireNative() {
|
|
|
305
305
|
try {
|
|
306
306
|
const binding = require('brustjs-linux-arm64-musl')
|
|
307
307
|
const bindingPackageVersion = require('brustjs-linux-arm64-musl/package.json').version
|
|
308
|
-
if (bindingPackageVersion !== '0.1.
|
|
309
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
308
|
+
if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
309
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
310
310
|
}
|
|
311
311
|
return binding
|
|
312
312
|
} catch (e) {
|
|
@@ -321,8 +321,8 @@ function requireNative() {
|
|
|
321
321
|
try {
|
|
322
322
|
const binding = require('brustjs-linux-arm64-gnu')
|
|
323
323
|
const bindingPackageVersion = require('brustjs-linux-arm64-gnu/package.json').version
|
|
324
|
-
if (bindingPackageVersion !== '0.1.
|
|
325
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
324
|
+
if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
325
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
326
326
|
}
|
|
327
327
|
return binding
|
|
328
328
|
} catch (e) {
|
|
@@ -339,8 +339,8 @@ function requireNative() {
|
|
|
339
339
|
try {
|
|
340
340
|
const binding = require('brustjs-linux-arm-musleabihf')
|
|
341
341
|
const bindingPackageVersion = require('brustjs-linux-arm-musleabihf/package.json').version
|
|
342
|
-
if (bindingPackageVersion !== '0.1.
|
|
343
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
342
|
+
if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
343
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
344
344
|
}
|
|
345
345
|
return binding
|
|
346
346
|
} catch (e) {
|
|
@@ -355,8 +355,8 @@ function requireNative() {
|
|
|
355
355
|
try {
|
|
356
356
|
const binding = require('brustjs-linux-arm-gnueabihf')
|
|
357
357
|
const bindingPackageVersion = require('brustjs-linux-arm-gnueabihf/package.json').version
|
|
358
|
-
if (bindingPackageVersion !== '0.1.
|
|
359
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
358
|
+
if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
359
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
360
360
|
}
|
|
361
361
|
return binding
|
|
362
362
|
} catch (e) {
|
|
@@ -373,8 +373,8 @@ function requireNative() {
|
|
|
373
373
|
try {
|
|
374
374
|
const binding = require('brustjs-linux-loong64-musl')
|
|
375
375
|
const bindingPackageVersion = require('brustjs-linux-loong64-musl/package.json').version
|
|
376
|
-
if (bindingPackageVersion !== '0.1.
|
|
377
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
376
|
+
if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
377
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
378
378
|
}
|
|
379
379
|
return binding
|
|
380
380
|
} catch (e) {
|
|
@@ -389,8 +389,8 @@ function requireNative() {
|
|
|
389
389
|
try {
|
|
390
390
|
const binding = require('brustjs-linux-loong64-gnu')
|
|
391
391
|
const bindingPackageVersion = require('brustjs-linux-loong64-gnu/package.json').version
|
|
392
|
-
if (bindingPackageVersion !== '0.1.
|
|
393
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
392
|
+
if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
393
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
394
394
|
}
|
|
395
395
|
return binding
|
|
396
396
|
} catch (e) {
|
|
@@ -407,8 +407,8 @@ function requireNative() {
|
|
|
407
407
|
try {
|
|
408
408
|
const binding = require('brustjs-linux-riscv64-musl')
|
|
409
409
|
const bindingPackageVersion = require('brustjs-linux-riscv64-musl/package.json').version
|
|
410
|
-
if (bindingPackageVersion !== '0.1.
|
|
411
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
410
|
+
if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
411
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
412
412
|
}
|
|
413
413
|
return binding
|
|
414
414
|
} catch (e) {
|
|
@@ -423,8 +423,8 @@ function requireNative() {
|
|
|
423
423
|
try {
|
|
424
424
|
const binding = require('brustjs-linux-riscv64-gnu')
|
|
425
425
|
const bindingPackageVersion = require('brustjs-linux-riscv64-gnu/package.json').version
|
|
426
|
-
if (bindingPackageVersion !== '0.1.
|
|
427
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
426
|
+
if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
427
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
428
428
|
}
|
|
429
429
|
return binding
|
|
430
430
|
} catch (e) {
|
|
@@ -440,8 +440,8 @@ function requireNative() {
|
|
|
440
440
|
try {
|
|
441
441
|
const binding = require('brustjs-linux-ppc64-gnu')
|
|
442
442
|
const bindingPackageVersion = require('brustjs-linux-ppc64-gnu/package.json').version
|
|
443
|
-
if (bindingPackageVersion !== '0.1.
|
|
444
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
443
|
+
if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
444
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
445
445
|
}
|
|
446
446
|
return binding
|
|
447
447
|
} catch (e) {
|
|
@@ -456,8 +456,8 @@ function requireNative() {
|
|
|
456
456
|
try {
|
|
457
457
|
const binding = require('brustjs-linux-s390x-gnu')
|
|
458
458
|
const bindingPackageVersion = require('brustjs-linux-s390x-gnu/package.json').version
|
|
459
|
-
if (bindingPackageVersion !== '0.1.
|
|
460
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
459
|
+
if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
460
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
461
461
|
}
|
|
462
462
|
return binding
|
|
463
463
|
} catch (e) {
|
|
@@ -476,8 +476,8 @@ function requireNative() {
|
|
|
476
476
|
try {
|
|
477
477
|
const binding = require('brustjs-openharmony-arm64')
|
|
478
478
|
const bindingPackageVersion = require('brustjs-openharmony-arm64/package.json').version
|
|
479
|
-
if (bindingPackageVersion !== '0.1.
|
|
480
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
479
|
+
if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
480
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
481
481
|
}
|
|
482
482
|
return binding
|
|
483
483
|
} catch (e) {
|
|
@@ -492,8 +492,8 @@ function requireNative() {
|
|
|
492
492
|
try {
|
|
493
493
|
const binding = require('brustjs-openharmony-x64')
|
|
494
494
|
const bindingPackageVersion = require('brustjs-openharmony-x64/package.json').version
|
|
495
|
-
if (bindingPackageVersion !== '0.1.
|
|
496
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
495
|
+
if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
496
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
497
497
|
}
|
|
498
498
|
return binding
|
|
499
499
|
} catch (e) {
|
|
@@ -508,8 +508,8 @@ function requireNative() {
|
|
|
508
508
|
try {
|
|
509
509
|
const binding = require('brustjs-openharmony-arm')
|
|
510
510
|
const bindingPackageVersion = require('brustjs-openharmony-arm/package.json').version
|
|
511
|
-
if (bindingPackageVersion !== '0.1.
|
|
512
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
511
|
+
if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
512
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
513
513
|
}
|
|
514
514
|
return binding
|
|
515
515
|
} catch (e) {
|
|
@@ -12,22 +12,25 @@ export type Instance = Record<string, unknown>
|
|
|
12
12
|
* for side-effects on signal change (sync localStorage, the DOM
|
|
13
13
|
* outside the component, timers). Returns the disposer too.
|
|
14
14
|
* - `onCleanup(fn)` — register a one-shot teardown for unmount (e.g. removeEventListener). */
|
|
15
|
-
export interface BehaviorCtx {
|
|
16
|
-
el:
|
|
15
|
+
export interface BehaviorCtx<Host extends Element = HTMLElement> {
|
|
16
|
+
el: Host
|
|
17
17
|
props: unknown
|
|
18
18
|
// biome-ignore lint/suspicious/noConfusingVoidType: React useEffect return shape (`void | Destructor`) — see store `effect`.
|
|
19
19
|
effect: (fn: () => void | (() => void)) => () => void
|
|
20
20
|
onCleanup: (fn: () => void) => void
|
|
21
21
|
}
|
|
22
|
-
export type Behavior = (ctx: BehaviorCtx) => Instance
|
|
22
|
+
export type Behavior<Host extends Element = HTMLElement> = (ctx: BehaviorCtx<Host>) => Instance
|
|
23
23
|
|
|
24
24
|
interface Mounted {
|
|
25
25
|
disposers: Array<() => void>
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
type RegisteredBehavior = Behavior<Element>
|
|
29
|
+
|
|
30
|
+
const registry = new Map<string, RegisteredBehavior>()
|
|
31
|
+
const mounted = new WeakMap<Element, Mounted>()
|
|
30
32
|
const loading = new Map<string, Promise<unknown>>()
|
|
33
|
+
const pending = new Map<string, Set<Element>>()
|
|
31
34
|
let started = false
|
|
32
35
|
|
|
33
36
|
/** Per-component behavior chunk URL. Each native interactive component is built to
|
|
@@ -39,8 +42,15 @@ const CHUNK_BASE = '/_brust/islands/'
|
|
|
39
42
|
/** Register a component behavior under `name`. Called by `<name>.directive.js` chunks
|
|
40
43
|
* via the global handle below (they do NOT import this module — keeps each chunk to
|
|
41
44
|
* just its behavior, with the runtime shared as the single `_directives.js` copy). */
|
|
42
|
-
export function register
|
|
43
|
-
|
|
45
|
+
export function register<Host extends Element = HTMLElement>(
|
|
46
|
+
name: string,
|
|
47
|
+
behavior: Behavior<Host>,
|
|
48
|
+
): void {
|
|
49
|
+
registry.set(name, behavior as unknown as RegisteredBehavior)
|
|
50
|
+
const hosts = pending.get(name)
|
|
51
|
+
if (!hosts) return
|
|
52
|
+
pending.delete(name)
|
|
53
|
+
for (const el of hosts) mountElement(el)
|
|
44
54
|
}
|
|
45
55
|
// Expose `register` on a global so dynamically-imported behavior chunks self-register
|
|
46
56
|
// into THIS runtime's registry without importing/duplicating the runtime. Symbol.for
|
|
@@ -72,8 +82,8 @@ export function start(root?: ParentNode): void {
|
|
|
72
82
|
}
|
|
73
83
|
|
|
74
84
|
function scanAndMount(scope: ParentNode): void {
|
|
75
|
-
if (scope instanceof
|
|
76
|
-
for (const el of Array.from(scope.querySelectorAll<
|
|
85
|
+
if (scope instanceof Element && scope.hasAttribute('x-data')) mountElement(scope)
|
|
86
|
+
for (const el of Array.from(scope.querySelectorAll<Element>('[x-data]'))) {
|
|
77
87
|
mountElement(el)
|
|
78
88
|
}
|
|
79
89
|
// R10 — OPEN shadow roots host their own component trees: scan each the same
|
|
@@ -81,8 +91,8 @@ function scanAndMount(scope: ParentNode): void {
|
|
|
81
91
|
// per root, since neither the body observer nor an outer root's observer sees
|
|
82
92
|
// mutations inside an inner shadow tree. Closed roots expose `shadowRoot ===
|
|
83
93
|
// null` and are unreachable by design. The walk-all is per added subtree only.
|
|
84
|
-
if (scope instanceof
|
|
85
|
-
for (const el of Array.from(scope.querySelectorAll<
|
|
94
|
+
if (scope instanceof Element && scope.shadowRoot) scanShadowRoot(scope.shadowRoot)
|
|
95
|
+
for (const el of Array.from(scope.querySelectorAll<Element>('*'))) {
|
|
86
96
|
if (el.shadowRoot) scanShadowRoot(el.shadowRoot)
|
|
87
97
|
}
|
|
88
98
|
}
|
|
@@ -92,7 +102,7 @@ function scanShadowRoot(root: ShadowRoot): void {
|
|
|
92
102
|
observeRoot(root)
|
|
93
103
|
}
|
|
94
104
|
|
|
95
|
-
function mountElement(el:
|
|
105
|
+
function mountElement(el: Element): void {
|
|
96
106
|
if (mounted.has(el)) return
|
|
97
107
|
// Removed mid-scan (e.g. an initial-falsy x-if subtree pruned while scanAndMount's
|
|
98
108
|
// snapshot loop was still iterating): mounting a detached element would leak its
|
|
@@ -101,6 +111,12 @@ function mountElement(el: HTMLElement): void {
|
|
|
101
111
|
const name = el.getAttribute('x-data') ?? ''
|
|
102
112
|
const behavior = registry.get(name)
|
|
103
113
|
if (!behavior) {
|
|
114
|
+
let hosts = pending.get(name)
|
|
115
|
+
if (!hosts) {
|
|
116
|
+
hosts = new Set()
|
|
117
|
+
pending.set(name, hosts)
|
|
118
|
+
}
|
|
119
|
+
hosts.add(el)
|
|
104
120
|
// Behavior chunk not loaded yet → fetch it on demand, then mount this name.
|
|
105
121
|
loadBehavior(name)
|
|
106
122
|
return
|
|
@@ -124,12 +140,12 @@ function mountElement(el: HTMLElement): void {
|
|
|
124
140
|
mounted.set(el, m)
|
|
125
141
|
// ctxEffect/onCleanup typed via BehaviorCtx so the `void | Destructor` shape is
|
|
126
142
|
// declared in one place (the interface) — no inline void-union to suppress here.
|
|
127
|
-
const ctxEffect: BehaviorCtx['effect'] = (fn) => {
|
|
143
|
+
const ctxEffect: BehaviorCtx<Element>['effect'] = (fn) => {
|
|
128
144
|
const dispose = effect(fn)
|
|
129
145
|
m.disposers.push(dispose)
|
|
130
146
|
return dispose
|
|
131
147
|
}
|
|
132
|
-
const onCleanup: BehaviorCtx['onCleanup'] = (fn) => {
|
|
148
|
+
const onCleanup: BehaviorCtx<Element>['onCleanup'] = (fn) => {
|
|
133
149
|
m.disposers.push(fn)
|
|
134
150
|
}
|
|
135
151
|
const instance = behavior({ el, props, effect: ctxEffect, onCleanup })
|
|
@@ -150,6 +166,7 @@ function mountElement(el: HTMLElement): void {
|
|
|
150
166
|
function loadBehavior(name: string): void {
|
|
151
167
|
if (registry.has(name) || loading.has(name)) return
|
|
152
168
|
if (!/^[A-Za-z0-9_-]+$/.test(name)) {
|
|
169
|
+
pending.delete(name)
|
|
153
170
|
console.warn(`[brust] unsafe x-data component name "${name}" — not loaded`)
|
|
154
171
|
return
|
|
155
172
|
}
|
|
@@ -159,17 +176,16 @@ function loadBehavior(name: string): void {
|
|
|
159
176
|
.then(() => import(/* @vite-ignore */ `${CHUNK_BASE}${name}.directive.js`))
|
|
160
177
|
.then(() => {
|
|
161
178
|
if (!registry.has(name)) {
|
|
179
|
+
loading.delete(name)
|
|
180
|
+
pending.delete(name)
|
|
162
181
|
console.warn(`[brust] "${name}.directive.js" loaded but did not register "${name}"`)
|
|
163
|
-
return
|
|
164
|
-
}
|
|
165
|
-
// Mount every element waiting on this name (initial + swapped-in).
|
|
166
|
-
if (typeof document !== 'undefined') {
|
|
167
|
-
for (const el of Array.from(document.querySelectorAll<HTMLElement>(`[x-data="${name}"]`))) {
|
|
168
|
-
mountElement(el)
|
|
169
|
-
}
|
|
170
182
|
}
|
|
171
183
|
})
|
|
172
|
-
.catch((e) =>
|
|
184
|
+
.catch((e) => {
|
|
185
|
+
loading.delete(name)
|
|
186
|
+
pending.delete(name)
|
|
187
|
+
console.error(`[brust] failed to load directive component "${name}":`, e)
|
|
188
|
+
})
|
|
173
189
|
loading.set(name, p)
|
|
174
190
|
}
|
|
175
191
|
|
|
@@ -180,7 +196,7 @@ function loadBehavior(name: string): void {
|
|
|
180
196
|
// independently via scanAndMount's shadow-root scan (R10), never inheriting the
|
|
181
197
|
// enclosing instance's scope. (The `el.children` walk below naturally excludes
|
|
182
198
|
// shadow content; this is by design, not an accident.)
|
|
183
|
-
function bindTree(el:
|
|
199
|
+
function bindTree(el: Element, instance: Instance, disposers: Array<() => void>): void {
|
|
184
200
|
// Coexistence check MUST precede the x-for early-exit, else x-for preempts and the
|
|
185
201
|
// warn never fires. Strip x-if so x-for's template clones don't carry it either.
|
|
186
202
|
if (el.hasAttribute('x-if') && el.hasAttribute('x-for')) {
|
|
@@ -197,7 +213,7 @@ function bindTree(el: HTMLElement, instance: Instance, disposers: Array<() => vo
|
|
|
197
213
|
}
|
|
198
214
|
bindAttrs(el, instance, disposers)
|
|
199
215
|
for (const child of Array.from(el.children)) {
|
|
200
|
-
if (!(child instanceof
|
|
216
|
+
if (!(child instanceof Element)) continue
|
|
201
217
|
if (child.hasAttribute('x-data')) continue
|
|
202
218
|
bindTree(child, instance, disposers)
|
|
203
219
|
}
|
|
@@ -243,7 +259,7 @@ export function parseFor(raw: string): ForExpr | null {
|
|
|
243
259
|
}
|
|
244
260
|
|
|
245
261
|
interface ForEntry {
|
|
246
|
-
node:
|
|
262
|
+
node: Element
|
|
247
263
|
itemSig: Signal<unknown>
|
|
248
264
|
idxSig?: Signal<number>
|
|
249
265
|
disposers: Array<() => void>
|
|
@@ -259,7 +275,7 @@ const forMountGuard = new WeakMap<Node, Set<string>>()
|
|
|
259
275
|
// (legacy v1, now with optional plain index). With `by <keypath>...` it is an opt-in
|
|
260
276
|
// keyed reconcile that reuses DOM nodes (focus/scroll survive) and is reactive
|
|
261
277
|
// per-item via a per-clone `signal(item)` resolved through `read`'s unwrap-each-hop.
|
|
262
|
-
function bindFor(tplEl:
|
|
278
|
+
function bindFor(tplEl: Element, instance: Instance, disposers: Array<() => void>): void {
|
|
263
279
|
const expr = parseFor(tplEl.getAttribute('x-for') ?? '')
|
|
264
280
|
if (!expr) {
|
|
265
281
|
console.warn(`[brust] malformed x-for expression: "${tplEl.getAttribute('x-for')}"`)
|
|
@@ -292,12 +308,12 @@ function bindFor(tplEl: HTMLElement, instance: Instance, disposers: Array<() =>
|
|
|
292
308
|
const anchor = tplEl.ownerDocument.createComment(`x-for:${itemName}`)
|
|
293
309
|
parent.insertBefore(anchor, tplEl)
|
|
294
310
|
tplEl.removeAttribute('x-for')
|
|
295
|
-
const template = tplEl.cloneNode(true) as
|
|
311
|
+
const template = tplEl.cloneNode(true) as Element
|
|
296
312
|
tplEl.remove()
|
|
297
313
|
|
|
298
314
|
// ---- legacy (no `by`) — full re-render, with optional plain index ----
|
|
299
315
|
if (!keyPaths) {
|
|
300
|
-
const rendered:
|
|
316
|
+
const rendered: Element[] = []
|
|
301
317
|
const childDisposers: Array<() => void> = []
|
|
302
318
|
const clear = () => {
|
|
303
319
|
for (const d of childDisposers.splice(0)) {
|
|
@@ -315,7 +331,7 @@ function bindFor(tplEl: HTMLElement, instance: Instance, disposers: Array<() =>
|
|
|
315
331
|
const list = read(instance, listPath)
|
|
316
332
|
if (!Array.isArray(list)) return
|
|
317
333
|
for (let i = 0; i < list.length; i++) {
|
|
318
|
-
const clone = template.cloneNode(true) as
|
|
334
|
+
const clone = template.cloneNode(true) as Element
|
|
319
335
|
const childScope: Instance = Object.create(instance)
|
|
320
336
|
childScope[itemName] = list[i]
|
|
321
337
|
if (indexName) childScope[indexName] = i
|
|
@@ -340,7 +356,7 @@ function installKeyedReconcile(
|
|
|
340
356
|
instance: Instance,
|
|
341
357
|
parent: Node,
|
|
342
358
|
expr: ForExpr,
|
|
343
|
-
template:
|
|
359
|
+
template: Element,
|
|
344
360
|
anchor: Comment,
|
|
345
361
|
initialMap: Map<string, ForEntry>,
|
|
346
362
|
disposers: Array<() => void>,
|
|
@@ -382,7 +398,7 @@ function installKeyedReconcile(
|
|
|
382
398
|
live.delete(key)
|
|
383
399
|
next.set(key, existing)
|
|
384
400
|
} else {
|
|
385
|
-
const clone = template.cloneNode(true) as
|
|
401
|
+
const clone = template.cloneNode(true) as Element
|
|
386
402
|
const itemSig = signal(item)
|
|
387
403
|
const idxSig = indexName ? signal(i) : undefined
|
|
388
404
|
const childScope: Instance = Object.create(instance)
|
|
@@ -409,11 +425,11 @@ function installKeyedReconcile(
|
|
|
409
425
|
* `data-x-key-0`). Only direct children — the key attr lives on the for-item
|
|
410
426
|
* root, never a descendant. The x-for match prevents a sibling x-for list under
|
|
411
427
|
* the same parent from having its seeds consumed by this one. */
|
|
412
|
-
function collectSeeds(parent: Node, keyPaths: string[], xforRaw: string):
|
|
428
|
+
function collectSeeds(parent: Node, keyPaths: string[], xforRaw: string): Element[] {
|
|
413
429
|
const sel = keyPaths.length > 1 ? '[data-x-key-0]' : '[data-x-key]'
|
|
414
|
-
const out:
|
|
430
|
+
const out: Element[] = []
|
|
415
431
|
for (const c of Array.from((parent as Element).children ?? [])) {
|
|
416
|
-
if (c instanceof
|
|
432
|
+
if (c instanceof Element && c.matches(sel) && c.getAttribute('x-for') === xforRaw) {
|
|
417
433
|
out.push(c)
|
|
418
434
|
}
|
|
419
435
|
}
|
|
@@ -422,14 +438,14 @@ function collectSeeds(parent: Node, keyPaths: string[], xforRaw: string): HTMLEl
|
|
|
422
438
|
|
|
423
439
|
/** The seed's key from markup — must match the reconcile's computed key (single
|
|
424
440
|
* `data-x-key`, OR `data-x-key-*` joined with `\x00` IN JS — NUL never in HTML). */
|
|
425
|
-
function seedKey(node:
|
|
441
|
+
function seedKey(node: Element, keyPaths: string[]): string {
|
|
426
442
|
if (keyPaths.length > 1) {
|
|
427
443
|
return keyPaths.map((_, i) => node.getAttribute(`data-x-key-${i}`) ?? '').join('\x00')
|
|
428
444
|
}
|
|
429
445
|
return node.getAttribute('data-x-key') ?? ''
|
|
430
446
|
}
|
|
431
447
|
|
|
432
|
-
function stripKeyAttrs(el:
|
|
448
|
+
function stripKeyAttrs(el: Element, keyPaths: string[]): void {
|
|
433
449
|
if (keyPaths.length > 1) {
|
|
434
450
|
for (let i = 0; i < keyPaths.length; i++) el.removeAttribute(`data-x-key-${i}`)
|
|
435
451
|
} else {
|
|
@@ -440,10 +456,10 @@ function stripKeyAttrs(el: HTMLElement, keyPaths: string[]): void {
|
|
|
440
456
|
/** Bind an adopted seed node as a plain subtree. The node KEEPS its x-for attr so
|
|
441
457
|
* the parent bindTree loop's later re-visit routes back to bindFor → mount guard
|
|
442
458
|
* no-ops; do NOT route the node itself through bindFor again here. */
|
|
443
|
-
function bindAdoptedNode(node:
|
|
459
|
+
function bindAdoptedNode(node: Element, scope: Instance, disposers: Array<() => void>): void {
|
|
444
460
|
bindAttrs(node, scope, disposers)
|
|
445
461
|
for (const child of Array.from(node.children)) {
|
|
446
|
-
if (!(child instanceof
|
|
462
|
+
if (!(child instanceof Element)) continue
|
|
447
463
|
if (child.hasAttribute('x-data')) continue
|
|
448
464
|
bindTree(child, scope, disposers)
|
|
449
465
|
}
|
|
@@ -453,7 +469,7 @@ function bindAdoptedNode(node: HTMLElement, scope: Instance, disposers: Array<()
|
|
|
453
469
|
* each item-signal from the matching client item by key, wire reactivity, then
|
|
454
470
|
* hand the pre-populated map to the shared reconcile (first run = all reused). */
|
|
455
471
|
function bindForAdopt(
|
|
456
|
-
seeds:
|
|
472
|
+
seeds: Element[],
|
|
457
473
|
instance: Instance,
|
|
458
474
|
parent: Node,
|
|
459
475
|
expr: ForExpr,
|
|
@@ -469,7 +485,7 @@ function bindForAdopt(
|
|
|
469
485
|
}
|
|
470
486
|
const keys = keyPaths as string[]
|
|
471
487
|
// template for future creates: stripped clone of the first seed.
|
|
472
|
-
const template = seeds[0].cloneNode(true) as
|
|
488
|
+
const template = seeds[0].cloneNode(true) as Element
|
|
473
489
|
template.removeAttribute('x-for')
|
|
474
490
|
stripKeyAttrs(template, keys)
|
|
475
491
|
// anchor AFTER the last seed so future inserts keep document order.
|
|
@@ -489,7 +505,7 @@ function bindForAdopt(
|
|
|
489
505
|
// adopt each seed in place.
|
|
490
506
|
const map = new Map<string, ForEntry>()
|
|
491
507
|
for (let si = 0; si < seeds.length; si++) {
|
|
492
|
-
const node = seeds[si] as
|
|
508
|
+
const node = seeds[si] as Element
|
|
493
509
|
let key = seedKey(node, keys)
|
|
494
510
|
if (map.has(key)) {
|
|
495
511
|
// mirror the reconcile's dup-key handling: suffix so the entry is tracked
|
|
@@ -518,15 +534,15 @@ function bindForAdopt(
|
|
|
518
534
|
* the node and runs those disposers. The per-clone disposers cover only non-x-data
|
|
519
535
|
* teardown (bindTree skips nested x-data); nested x-data dispose/mount is delegated
|
|
520
536
|
* to the MutationObserver on removal/insert — single-owner discipline. */
|
|
521
|
-
function bindIf(el:
|
|
537
|
+
function bindIf(el: Element, instance: Instance, disposers: Array<() => void>): void {
|
|
522
538
|
const path = el.getAttribute('x-if') ?? ''
|
|
523
539
|
const parent = el.parentNode
|
|
524
540
|
if (!parent) return
|
|
525
541
|
const anchor = el.ownerDocument.createComment('x-if')
|
|
526
542
|
parent.insertBefore(anchor, el)
|
|
527
543
|
el.removeAttribute('x-if')
|
|
528
|
-
const template = el.cloneNode(true) as
|
|
529
|
-
let current:
|
|
544
|
+
const template = el.cloneNode(true) as Element // capture FIRST (before initial effect)
|
|
545
|
+
let current: Element | null = el // the original, adopted if initially truthy
|
|
530
546
|
let bound = false // original starts unbound; clones are bound at creation
|
|
531
547
|
const currentDisposers: Array<() => void> = []
|
|
532
548
|
const teardown = () => {
|
|
@@ -557,7 +573,7 @@ function bindIf(el: HTMLElement, instance: Instance, disposers: Array<() => void
|
|
|
557
573
|
}
|
|
558
574
|
return
|
|
559
575
|
}
|
|
560
|
-
const clone = template.cloneNode(true) as
|
|
576
|
+
const clone = template.cloneNode(true) as Element
|
|
561
577
|
bindTree(clone, instance, currentDisposers) // bind BEFORE insert (observer mounts nested x-data after)
|
|
562
578
|
anchor.parentNode?.insertBefore(clone, anchor.nextSibling)
|
|
563
579
|
current = clone
|
|
@@ -657,13 +673,14 @@ function bindModel(
|
|
|
657
673
|
)
|
|
658
674
|
}
|
|
659
675
|
|
|
660
|
-
function bindAttrs(el:
|
|
676
|
+
function bindAttrs(el: Element, scope: Instance, disposers: Array<() => void>): void {
|
|
661
677
|
for (const attr of Array.from(el.attributes)) {
|
|
662
678
|
const name = attr.name
|
|
663
679
|
const value = attr.value
|
|
664
680
|
if (name === 'x-data' || name === 'x-props') continue
|
|
665
681
|
if (name === 'x-model') {
|
|
666
|
-
bindModel(el, scope, value, disposers)
|
|
682
|
+
if (el instanceof HTMLElement) bindModel(el, scope, value, disposers)
|
|
683
|
+
else console.warn('[brust] x-model is only supported on HTML elements — binding skipped')
|
|
667
684
|
continue
|
|
668
685
|
}
|
|
669
686
|
if (name === 'x-text') {
|
|
@@ -678,7 +695,11 @@ function bindAttrs(el: HTMLElement, scope: Instance, disposers: Array<() => void
|
|
|
678
695
|
if (name === 'x-show') {
|
|
679
696
|
disposers.push(
|
|
680
697
|
effect(() => {
|
|
681
|
-
|
|
698
|
+
if ('style' in el) {
|
|
699
|
+
;(el as Element & { style: CSSStyleDeclaration }).style.display = read(scope, value)
|
|
700
|
+
? ''
|
|
701
|
+
: 'none'
|
|
702
|
+
}
|
|
682
703
|
}),
|
|
683
704
|
)
|
|
684
705
|
continue
|
|
@@ -718,40 +739,40 @@ function observeRoot(root: Node): void {
|
|
|
718
739
|
const obs = new MutationObserver((records) => {
|
|
719
740
|
for (const rec of records) {
|
|
720
741
|
for (const node of Array.from(rec.removedNodes)) {
|
|
721
|
-
if (node instanceof
|
|
742
|
+
if (node instanceof Element) disposeTree(node)
|
|
722
743
|
}
|
|
723
744
|
for (const node of Array.from(rec.addedNodes)) {
|
|
724
|
-
if (node instanceof
|
|
745
|
+
if (node instanceof Element) scanAndMount(node)
|
|
725
746
|
}
|
|
726
747
|
}
|
|
727
748
|
})
|
|
728
749
|
obs.observe(root, { childList: true, subtree: true })
|
|
729
750
|
}
|
|
730
751
|
|
|
731
|
-
function disposeTree(node:
|
|
752
|
+
function disposeTree(node: Element): void {
|
|
732
753
|
if (mounted.has(node)) disposeElement(node)
|
|
733
|
-
for (const el of Array.from(node.querySelectorAll<
|
|
754
|
+
for (const el of Array.from(node.querySelectorAll<Element>('[x-data]'))) {
|
|
734
755
|
disposeElement(el)
|
|
735
756
|
}
|
|
736
757
|
// R10 — a removed HOST's shadow contents never reach any observer: the host's
|
|
737
758
|
// removal fires on the light tree's observer, and the shadow root's own
|
|
738
759
|
// observer only sees mutations INSIDE the root. Walk shadow roots explicitly.
|
|
739
760
|
if (node.shadowRoot) disposeShadowContents(node.shadowRoot)
|
|
740
|
-
for (const el of Array.from(node.querySelectorAll<
|
|
761
|
+
for (const el of Array.from(node.querySelectorAll<Element>('*'))) {
|
|
741
762
|
if (el.shadowRoot) disposeShadowContents(el.shadowRoot)
|
|
742
763
|
}
|
|
743
764
|
}
|
|
744
765
|
|
|
745
766
|
function disposeShadowContents(root: ShadowRoot): void {
|
|
746
|
-
for (const el of Array.from(root.querySelectorAll<
|
|
767
|
+
for (const el of Array.from(root.querySelectorAll<Element>('[x-data]'))) {
|
|
747
768
|
disposeElement(el)
|
|
748
769
|
}
|
|
749
|
-
for (const el of Array.from(root.querySelectorAll<
|
|
770
|
+
for (const el of Array.from(root.querySelectorAll<Element>('*'))) {
|
|
750
771
|
if (el.shadowRoot) disposeShadowContents(el.shadowRoot)
|
|
751
772
|
}
|
|
752
773
|
}
|
|
753
774
|
|
|
754
|
-
function disposeElement(el:
|
|
775
|
+
function disposeElement(el: Element): void {
|
|
755
776
|
const m = mounted.get(el)
|
|
756
777
|
if (!m) return
|
|
757
778
|
for (const d of m.disposers.splice(0)) {
|
|
@@ -766,15 +787,19 @@ function disposeElement(el: HTMLElement): void {
|
|
|
766
787
|
|
|
767
788
|
const BOOL_PROPS = new Set(['disabled', 'checked', 'hidden', 'readonly', 'required', 'selected'])
|
|
768
789
|
|
|
769
|
-
/** Apply a bound value to a DOM attr/property. class → className
|
|
770
|
-
*
|
|
771
|
-
export function setBound(el:
|
|
790
|
+
/** Apply a bound value to a DOM attr/property. class → HTML className or a
|
|
791
|
+
* namespace-safe attribute; boolean/value properties are used only when present. */
|
|
792
|
+
export function setBound(el: Element, attr: string, value: unknown): void {
|
|
772
793
|
if (attr === 'class') {
|
|
773
|
-
el.className = value == null ? '' : String(value)
|
|
794
|
+
if (el instanceof HTMLElement) el.className = value == null ? '' : String(value)
|
|
795
|
+
else if (value == null) el.removeAttribute('class')
|
|
796
|
+
else el.setAttribute('class', String(value))
|
|
774
797
|
return
|
|
775
798
|
}
|
|
776
799
|
if (attr === 'value') {
|
|
777
|
-
|
|
800
|
+
if ('value' in el) (el as unknown as { value: unknown }).value = value == null ? '' : value
|
|
801
|
+
else if (value == null) el.removeAttribute(attr)
|
|
802
|
+
else el.setAttribute(attr, String(value))
|
|
778
803
|
return
|
|
779
804
|
}
|
|
780
805
|
if (BOOL_PROPS.has(attr)) {
|
|
@@ -6,17 +6,17 @@ export type Instance = Record<string, unknown>;
|
|
|
6
6
|
* for side-effects on signal change (sync localStorage, the DOM
|
|
7
7
|
* outside the component, timers). Returns the disposer too.
|
|
8
8
|
* - `onCleanup(fn)` — register a one-shot teardown for unmount (e.g. removeEventListener). */
|
|
9
|
-
export interface BehaviorCtx {
|
|
10
|
-
el:
|
|
9
|
+
export interface BehaviorCtx<Host extends Element = HTMLElement> {
|
|
10
|
+
el: Host;
|
|
11
11
|
props: unknown;
|
|
12
12
|
effect: (fn: () => void | (() => void)) => () => void;
|
|
13
13
|
onCleanup: (fn: () => void) => void;
|
|
14
14
|
}
|
|
15
|
-
export type Behavior = (ctx: BehaviorCtx) => Instance;
|
|
15
|
+
export type Behavior<Host extends Element = HTMLElement> = (ctx: BehaviorCtx<Host>) => Instance;
|
|
16
16
|
/** Register a component behavior under `name`. Called by `<name>.directive.js` chunks
|
|
17
17
|
* via the global handle below (they do NOT import this module — keeps each chunk to
|
|
18
18
|
* just its behavior, with the runtime shared as the single `_directives.js` copy). */
|
|
19
|
-
export declare function register(name: string, behavior: Behavior): void;
|
|
19
|
+
export declare function register<Host extends Element = HTMLElement>(name: string, behavior: Behavior<Host>): void;
|
|
20
20
|
/** Scan `root` (default: document) for [x-data], mount each, and (once) attach a
|
|
21
21
|
* MutationObserver for dynamic mount/dispose. Idempotent. NOTE: `root` scopes the
|
|
22
22
|
* INITIAL scan only; the observer always watches the global `document.body` (one
|
|
@@ -39,9 +39,9 @@ export declare function parseFor(raw: string): ForExpr | null;
|
|
|
39
39
|
* for multi-hop paths). The LEAF is never called: `isSignal(leaf)` → `.set(value)`,
|
|
40
40
|
* else warn once and skip. */
|
|
41
41
|
export declare function writePath(scope: Instance, path: string, value: unknown): void;
|
|
42
|
-
/** Apply a bound value to a DOM attr/property. class → className
|
|
43
|
-
*
|
|
44
|
-
export declare function setBound(el:
|
|
42
|
+
/** Apply a bound value to a DOM attr/property. class → HTML className or a
|
|
43
|
+
* namespace-safe attribute; boolean/value properties are used only when present. */
|
|
44
|
+
export declare function setBound(el: Element, attr: string, value: unknown): void;
|
|
45
45
|
/** Walk a dotted member path against `scope`, unwrapping a signal/computed at EVERY
|
|
46
46
|
* hop (so an intermediate item-signal is tracked by `effect`); at the LEAF also call
|
|
47
47
|
* a plain function to obtain its value (this read is what `effect` tracks). */
|