jq79 0.7.3 → 0.7.5
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 +2 -2
- package/dist/jq79-sw.js +1 -1
- package/dist/jq79.cjs +16 -16
- package/dist/jq79.cjs.map +1 -1
- package/dist/jq79.d.ts +12 -1
- package/dist/jq79.global.js +16 -16
- package/dist/jq79.global.js.map +1 -1
- package/dist/jq79.js +16 -16
- package/dist/jq79.js.map +1 -1
- package/dist/precompile.cjs +1 -1
- package/dist/precompile.cjs.map +1 -1
- package/dist/precompile.js +1 -1
- package/dist/precompile.js.map +1 -1
- package/dist/reactive.d.ts +4 -0
- package/package.json +1 -1
- package/src/jq79.ts +211 -6
- package/src/precompile.ts +4 -3
- package/src/reactive.ts +29 -0
- package/src/source.ts +4 -3
- package/src/transform.ts +1 -1
package/dist/reactive.d.ts
CHANGED
|
@@ -53,4 +53,8 @@ export type EffectScope = {
|
|
|
53
53
|
dispose: () => void;
|
|
54
54
|
};
|
|
55
55
|
export declare const createEffectScope: (scope: Record<string, any>, deep?: boolean) => EffectScope;
|
|
56
|
+
export type Computed<T> = ReactiveDeepData<{
|
|
57
|
+
readonly value: T;
|
|
58
|
+
}>;
|
|
59
|
+
export declare const $computed: <T>(get: () => T) => Computed<T>;
|
|
56
60
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jq79",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
4
4
|
"description": "Mini reactive component library: single-file .html components, Svelte-style setup scripts, fine-grained proxy reactivity. No compiler, no bundler, no dependencies — works from any static host.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"reactive",
|
package/src/jq79.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import { $, $$, $create, sanitizeHTML, allowedHosts } from "./dom"
|
|
3
3
|
import type { AllowUrl, QueryAll, QueryOne } from "./dom"
|
|
4
|
-
import { $reactive, $toRaw, untracked, createEffectScope, ALSO_WAKEN_BY, OWNER } from "./reactive"
|
|
4
|
+
import { $reactive, $toRaw, $computed, untracked, createEffectScope, ALSO_WAKEN_BY, OWNER } from "./reactive"
|
|
5
5
|
import type { ReactiveDeepData, EffectScope } from "./reactive"
|
|
6
6
|
import { transformSetupScript, transformFactoryScript, parsePropsPattern, parseFactoryProps, type PropDecl } from "./transform"
|
|
7
7
|
import {
|
|
@@ -14,7 +14,8 @@ import {
|
|
|
14
14
|
|
|
15
15
|
export { $, $$, $create } from "./dom"
|
|
16
16
|
export type { QueryOne, QueryAll } from "./dom"
|
|
17
|
-
export { $reactive, $toRaw } from "./reactive"
|
|
17
|
+
export { $reactive, $toRaw, $computed } from "./reactive"
|
|
18
|
+
export type { Computed } from "./reactive"
|
|
18
19
|
|
|
19
20
|
// the package version, substituted at build time (tsup/vitest `define`, read
|
|
20
21
|
// from package.json - releases bump it there and nowhere else). The typeof
|
|
@@ -3350,8 +3351,10 @@ const importResource = (url: string): Promise<any> =>
|
|
|
3350
3351
|
// For the same reason a *root-absolute* specifier is resolved too, not passed
|
|
3351
3352
|
// through: `/x.js` means the page's root to whoever wrote it, and the page is
|
|
3352
3353
|
// the only base under which the two branches agree. Bare specifiers ("lodash")
|
|
3353
|
-
// are the exception that stays untouched - they belong to the import map
|
|
3354
|
-
// the bundler, and resolving one would quietly turn it into
|
|
3354
|
+
// are the exception that stays untouched here - they belong to the import map
|
|
3355
|
+
// or the bundler, and resolving one against a base would quietly turn it into
|
|
3356
|
+
// a path. A bare *.html* one still reaches the map: fetchComponent asks it,
|
|
3357
|
+
// since fetch() cannot (see resolveBare)
|
|
3355
3358
|
//
|
|
3356
3359
|
// The base is absolutized first, the way hotKey does and for the same reason:
|
|
3357
3360
|
// the filename may itself be relative ("./card.html", from an import() in a
|
|
@@ -3901,7 +3904,32 @@ const warnIfStuck = (component: Component79, gates: Promise<void>[]) => {
|
|
|
3901
3904
|
Promise.all(gates).then(() => clearTimeout(timer))
|
|
3902
3905
|
}
|
|
3903
3906
|
|
|
3904
|
-
|
|
3907
|
+
// a bare specifier belongs to the import map, and native import() reads one -
|
|
3908
|
+
// fetch() never does, so `fetch("@app/card.html")` asks the page's directory
|
|
3909
|
+
// for a folder named "@app". A bare URL is resolved here through the same map,
|
|
3910
|
+
// with the function import() itself would use, so the component gets the URL
|
|
3911
|
+
// its own relative imports, its precompiled script and its HMR key resolve
|
|
3912
|
+
// against. That includes a relative path written without "./" ("views/x.html"):
|
|
3913
|
+
// it reaches the map too, and changes only if the map names it. One the map
|
|
3914
|
+
// does not name throws, and goes on unchanged, as it did before.
|
|
3915
|
+
//
|
|
3916
|
+
// The map is read as this module sees it: a `scopes` entry applies when it
|
|
3917
|
+
// covers jq79.js, not the component. Guarded because the CJS and classic-script
|
|
3918
|
+
// builds (jq79.cjs, jq79.global.js) have no import.meta, and there bare URLs go
|
|
3919
|
+
// to fetch() as they are
|
|
3920
|
+
const resolveBare = (url: string): string => {
|
|
3921
|
+
if (RESOLVABLE_SPECIFIER_RE.test(url)) return url
|
|
3922
|
+
const resolve = (import.meta as { resolve?: (spec: string) => string }).resolve
|
|
3923
|
+
if (typeof resolve !== "function") return url
|
|
3924
|
+
try {
|
|
3925
|
+
return resolve(url)
|
|
3926
|
+
} catch {
|
|
3927
|
+
return url
|
|
3928
|
+
}
|
|
3929
|
+
}
|
|
3930
|
+
|
|
3931
|
+
const fetchComponent = async (rawUrl: string): Promise<Component79> => {
|
|
3932
|
+
const url = resolveBare(rawUrl)
|
|
3905
3933
|
// under safeEval()'s worker, the component's functions arrive beside it,
|
|
3906
3934
|
// and both have to be in before anyone can render it
|
|
3907
3935
|
const [text] = await Promise.all([
|
|
@@ -4054,6 +4082,17 @@ export class Component79 {
|
|
|
4054
4082
|
// must not resolve on attach alone - a script awaiting it would wake to an
|
|
4055
4083
|
// empty component and find nothing to query
|
|
4056
4084
|
private renderDone = false
|
|
4085
|
+
// what this render generation's scripts asked to run when it is torn down
|
|
4086
|
+
// ($destroyed). Created on the first call, and taken by destroy() before
|
|
4087
|
+
// anything else goes, so a hook still sees the DOM and the store
|
|
4088
|
+
private destroyHooks: (() => void)[] | null = null
|
|
4089
|
+
// this generation's $attached / $detached hooks, and whether it has been
|
|
4090
|
+
// told it is on the page. Only an instance that registered one of them is
|
|
4091
|
+
// in pageWatchers, which is what keeps a page that uses neither from paying
|
|
4092
|
+
// for a walk on every mount (see settleAttached)
|
|
4093
|
+
private attachHooks: (() => void)[] | null = null
|
|
4094
|
+
private detachHooks: (() => void)[] | null = null
|
|
4095
|
+
private onPage = false
|
|
4057
4096
|
// instance-level listeners for $emit events, registered with on(). Kept
|
|
4058
4097
|
// outside the render generation so they survive re-render and destroy()
|
|
4059
4098
|
private emitListeners = new Map<string, Set<EmitListener>>()
|
|
@@ -4351,6 +4390,55 @@ export class Component79 {
|
|
|
4351
4390
|
this.resolveMounted = resolveMounted
|
|
4352
4391
|
this.renderDone = false
|
|
4353
4392
|
|
|
4393
|
+
// $destroyed(fn) runs fn when this generation is torn down: destroy(), a
|
|
4394
|
+
// re-render, a hot reload, or a parent's :if/:each removing it - never
|
|
4395
|
+
// detach(), which keeps state for a mount() to resume. Registered after
|
|
4396
|
+
// the generation is gone (a script that awaited past its own destroy), it
|
|
4397
|
+
// runs at once: holding it for a destroy that already happened would leak
|
|
4398
|
+
// exactly what it was written to stop. Returns the unregister
|
|
4399
|
+
const $destroyed = (fn: () => void): (() => void) => {
|
|
4400
|
+
if (typeof fn !== "function") throw new TypeError("jq79: $destroyed expects a function")
|
|
4401
|
+
if (marker !== this.startMarker) {
|
|
4402
|
+
runHook(fn, "$destroyed")
|
|
4403
|
+
return () => {}
|
|
4404
|
+
}
|
|
4405
|
+
return addHook((this.destroyHooks ??= []), fn)
|
|
4406
|
+
}
|
|
4407
|
+
// $attached(fn) runs fn every time this generation goes onto the page -
|
|
4408
|
+
// in the document, rendered - the first mount included; $detached(fn)
|
|
4409
|
+
// every time it leaves, and only after an $attached it balances. A nested
|
|
4410
|
+
// component hears its root's mount and detach (see settleAttached and
|
|
4411
|
+
// notifyDetached). `immediate`, on by default, also runs fn now when the
|
|
4412
|
+
// component is on the page already, so "while I'm on the page" holds
|
|
4413
|
+
// wherever the line sits - above `await $mounted()` or below it. A late
|
|
4414
|
+
// $detached runs at once, as a late $destroyed does; a late $attached has
|
|
4415
|
+
// nothing left to attach
|
|
4416
|
+
const $attached = (fn: () => void, { immediate = true }: { immediate?: boolean } = {}): (() => void) => {
|
|
4417
|
+
if (typeof fn !== "function") throw new TypeError("jq79: $attached expects a function")
|
|
4418
|
+
if (marker !== this.startMarker) return () => {}
|
|
4419
|
+
this.watchPage()
|
|
4420
|
+
const off = addHook((this.attachHooks ??= []), fn)
|
|
4421
|
+
if (immediate && this.onPage) runHook(fn, "$attached")
|
|
4422
|
+
return off
|
|
4423
|
+
}
|
|
4424
|
+
const $detached = (fn: () => void): (() => void) => {
|
|
4425
|
+
if (typeof fn !== "function") throw new TypeError("jq79: $detached expects a function")
|
|
4426
|
+
if (marker !== this.startMarker) {
|
|
4427
|
+
runHook(fn, "$detached")
|
|
4428
|
+
return () => {}
|
|
4429
|
+
}
|
|
4430
|
+
this.watchPage()
|
|
4431
|
+
return addHook((this.detachHooks ??= []), fn)
|
|
4432
|
+
}
|
|
4433
|
+
// the library's $computed, disposed with this generation: one made over a
|
|
4434
|
+
// shared store is otherwise held by that store, and kept recomputing, for
|
|
4435
|
+
// as long as the store lives
|
|
4436
|
+
const $instanceComputed = <T>(get: () => T) => {
|
|
4437
|
+
const computed = $computed(get)
|
|
4438
|
+
$destroyed(() => computed.$dispose())
|
|
4439
|
+
return computed
|
|
4440
|
+
}
|
|
4441
|
+
|
|
4354
4442
|
// $self / $$self mirror $ / $$ but only search this instance's own
|
|
4355
4443
|
// output: the sibling nodes between its markers. They work detached too
|
|
4356
4444
|
// (the holding fragment keeps markers and rendered nodes as siblings),
|
|
@@ -4451,7 +4539,7 @@ export class Component79 {
|
|
|
4451
4539
|
// resolve to nothing at all. In setup mode this composes with `with` -
|
|
4452
4540
|
// scriptScope's `has` declines any name that is a helper, so the
|
|
4453
4541
|
// parameter is what the name resolves to
|
|
4454
|
-
const instanceHelpers = { $mounted, $self, $$self, ...injected, ...siblingScope }
|
|
4542
|
+
const instanceHelpers = { $mounted, $destroyed, $attached, $detached, $computed: $instanceComputed, $self, $$self, ...injected, ...siblingScope }
|
|
4455
4543
|
const at: ScriptLocation = { filename: this.filename, index }
|
|
4456
4544
|
const deferred = ":mounted" in script.attrs
|
|
4457
4545
|
const factoryCode = transformFactoryScript(script.content)
|
|
@@ -4541,6 +4629,8 @@ export class Component79 {
|
|
|
4541
4629
|
this.endMarker!.parentNode!.insertBefore(renderNodes(this.template, templateScope, fx, shadow), this.endMarker!)
|
|
4542
4630
|
this.renderDone = true
|
|
4543
4631
|
this.settleMounted()
|
|
4632
|
+
// a held render that paints on the page is this component's attach
|
|
4633
|
+
Component79.settleAttached()
|
|
4544
4634
|
})
|
|
4545
4635
|
warnIfStuck(this, gates)
|
|
4546
4636
|
}
|
|
@@ -4603,9 +4693,87 @@ export class Component79 {
|
|
|
4603
4693
|
root.appendChild(this.content!)
|
|
4604
4694
|
this.mountRoot = root
|
|
4605
4695
|
this.settleMounted()
|
|
4696
|
+
Component79.settleAttached()
|
|
4606
4697
|
return this
|
|
4607
4698
|
}
|
|
4608
4699
|
|
|
4700
|
+
// whether this generation's DOM is in the document, template built - the
|
|
4701
|
+
// one meaning "on the page" has for $attached. Not "attach() was called": a
|
|
4702
|
+
// nested component is mounted into a fragment its parent inserts later, and
|
|
4703
|
+
// a root can be mounted into an element that isn't in the document
|
|
4704
|
+
private isOnPage(): boolean {
|
|
4705
|
+
return this.renderDone && this.startMarker?.isConnected === true
|
|
4706
|
+
}
|
|
4707
|
+
|
|
4708
|
+
// joins pageWatchers on the first $attached/$detached of a generation, with
|
|
4709
|
+
// the state as it is now: a hook registered while on the page must not fire
|
|
4710
|
+
// for an attach that already happened. Off the page, a settle is queued: a
|
|
4711
|
+
// component inserted by its parent's :if, :each or tag is built in a
|
|
4712
|
+
// fragment and put on the page later on this same stack, with no attach()
|
|
4713
|
+
// of its own to say so - one microtask late, never early
|
|
4714
|
+
private watchPage() {
|
|
4715
|
+
if (pageWatchers.has(this)) return
|
|
4716
|
+
pageWatchers.add(this)
|
|
4717
|
+
this.onPage = this.isOnPage()
|
|
4718
|
+
if (!this.onPage) Component79.queueSettle()
|
|
4719
|
+
}
|
|
4720
|
+
|
|
4721
|
+
private static queueSettle() {
|
|
4722
|
+
if (settleQueued) return
|
|
4723
|
+
settleQueued = true
|
|
4724
|
+
queueMicrotask(() => {
|
|
4725
|
+
settleQueued = false
|
|
4726
|
+
Component79.settleAttached()
|
|
4727
|
+
})
|
|
4728
|
+
}
|
|
4729
|
+
|
|
4730
|
+
// tells every watcher that is on the page and hasn't been told, in document
|
|
4731
|
+
// order - a parent before what it rendered, slot content included, because
|
|
4732
|
+
// the DOM is what says where a component is
|
|
4733
|
+
private static settleAttached() {
|
|
4734
|
+
if (pageWatchers.size === 0) return
|
|
4735
|
+
const due = [...pageWatchers].filter(component => !component.onPage && component.isOnPage())
|
|
4736
|
+
Component79.inDocumentOrder(due).forEach(component => {
|
|
4737
|
+
// a hook that ran before this one may have destroyed it
|
|
4738
|
+
if (component.onPage || !pageWatchers.has(component)) return
|
|
4739
|
+
component.onPage = true
|
|
4740
|
+
component.attachHooks?.slice().forEach(fn => runHook(fn, "$attached"))
|
|
4741
|
+
})
|
|
4742
|
+
}
|
|
4743
|
+
|
|
4744
|
+
// tells this component and every watcher whose DOM is under its markers
|
|
4745
|
+
// that they are leaving the page - before anything moves, so the hooks see
|
|
4746
|
+
// the DOM where it was. One whose DOM already left without it (an :if
|
|
4747
|
+
// removes its branch, then destroys what was in it) has only itself to tell
|
|
4748
|
+
private notifyDetached() {
|
|
4749
|
+
if (pageWatchers.size === 0) return
|
|
4750
|
+
let due: Component79[] = []
|
|
4751
|
+
if (this.startMarker?.isConnected) {
|
|
4752
|
+
const top = new Set<Node>()
|
|
4753
|
+
for (let node: Node | null = this.startMarker; node; node = node.nextSibling) {
|
|
4754
|
+
top.add(node)
|
|
4755
|
+
if (node === this.endMarker) break
|
|
4756
|
+
}
|
|
4757
|
+
const under = (node: Node | null): boolean => {
|
|
4758
|
+
for (; node; node = node.parentNode) if (top.has(node)) return true
|
|
4759
|
+
return false
|
|
4760
|
+
}
|
|
4761
|
+
due = Component79.inDocumentOrder([...pageWatchers].filter(component => component.onPage && under(component.startMarker)))
|
|
4762
|
+
} else if (this.onPage) {
|
|
4763
|
+
due = [this]
|
|
4764
|
+
}
|
|
4765
|
+
due.forEach(component => {
|
|
4766
|
+
if (!component.onPage) return
|
|
4767
|
+
component.onPage = false
|
|
4768
|
+
component.detachHooks?.slice().forEach(fn => runHook(fn, "$detached"))
|
|
4769
|
+
})
|
|
4770
|
+
}
|
|
4771
|
+
|
|
4772
|
+
private static inDocumentOrder(components: Component79[]): Component79[] {
|
|
4773
|
+
return components.sort((a, b) =>
|
|
4774
|
+
a.startMarker!.compareDocumentPosition(b.startMarker!) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1)
|
|
4775
|
+
}
|
|
4776
|
+
|
|
4609
4777
|
// where a shadow-rendered component's styles go: <style> elements ahead of
|
|
4610
4778
|
// its content, as always - or, under safe mode, sheets adopted by the shadow
|
|
4611
4779
|
// root. Built on first placement, since only then is there a root; one that
|
|
@@ -4640,6 +4808,7 @@ export class Component79 {
|
|
|
4640
4808
|
// with any updates that happened while detached already applied
|
|
4641
4809
|
detach(): this {
|
|
4642
4810
|
if (!this.mountRoot || !this.content || !this.startMarker || !this.endMarker) return this
|
|
4811
|
+
this.notifyDetached()
|
|
4643
4812
|
|
|
4644
4813
|
// move everything between the markers (inclusive) back into the holding
|
|
4645
4814
|
// fragment - including nodes :if/:each inserted after mounting
|
|
@@ -4656,6 +4825,18 @@ export class Component79 {
|
|
|
4656
4825
|
}
|
|
4657
4826
|
|
|
4658
4827
|
destroy(): this {
|
|
4828
|
+
// first, while everything they might read is still there: leaving the
|
|
4829
|
+
// page ($detached, for this component and what it rendered), then going
|
|
4830
|
+
// ($destroyed). Taken before running, so a hook that destroys again (or
|
|
4831
|
+
// re-renders) finds none
|
|
4832
|
+
this.notifyDetached()
|
|
4833
|
+
pageWatchers.delete(this)
|
|
4834
|
+
this.attachHooks = null
|
|
4835
|
+
this.detachHooks = null
|
|
4836
|
+
this.onPage = false
|
|
4837
|
+
const hooks = this.destroyHooks
|
|
4838
|
+
this.destroyHooks = null
|
|
4839
|
+
hooks?.forEach(fn => runHook(fn, "$destroyed"))
|
|
4659
4840
|
this.detach()
|
|
4660
4841
|
this.fx?.dispose()
|
|
4661
4842
|
this.fx = null
|
|
@@ -4789,6 +4970,30 @@ export type Component<P = any, E extends string = never, S extends string = neve
|
|
|
4789
4970
|
|
|
4790
4971
|
export const parseComponent = (component: string): Component79 => new Component79(component)
|
|
4791
4972
|
|
|
4973
|
+
// one broken hook must not leave the others' timers running, or the
|
|
4974
|
+
// teardown half done: reported, and the rest still run
|
|
4975
|
+
const runHook = (fn: () => void, name: string) => {
|
|
4976
|
+
try {
|
|
4977
|
+
fn()
|
|
4978
|
+
} catch (error) {
|
|
4979
|
+
console.error(`jq79: error in a ${name} hook`, error)
|
|
4980
|
+
}
|
|
4981
|
+
}
|
|
4982
|
+
|
|
4983
|
+
// registers fn on a generation's hook list; the returned function takes it off
|
|
4984
|
+
const addHook = (hooks: (() => void)[], fn: () => void): (() => void) => {
|
|
4985
|
+
hooks.push(fn)
|
|
4986
|
+
return () => {
|
|
4987
|
+
const at = hooks.indexOf(fn)
|
|
4988
|
+
if (at !== -1) hooks.splice(at, 1)
|
|
4989
|
+
}
|
|
4990
|
+
}
|
|
4991
|
+
|
|
4992
|
+
// the instances with an $attached or $detached hook, in this generation -
|
|
4993
|
+
// the only ones a mount or a detach has to look at (see settleAttached)
|
|
4994
|
+
const pageWatchers = new Set<Component79>()
|
|
4995
|
+
let settleQueued = false
|
|
4996
|
+
|
|
4792
4997
|
// library helpers injected into setup scripts. They behave like extra
|
|
4793
4998
|
// globals: a same-named scope property (render data or a top-level
|
|
4794
4999
|
// declaration) shadows them. Their names, in this order, are also
|
package/src/precompile.ts
CHANGED
|
@@ -175,9 +175,10 @@ const precompileComponent = (
|
|
|
175
175
|
|
|
176
176
|
// the helper names a script is compiled with: renderWith's
|
|
177
177
|
// { ...SETUP_HELPERS, ...instanceHelpers }, where instanceHelpers is
|
|
178
|
-
// { $mounted, $
|
|
179
|
-
//
|
|
180
|
-
//
|
|
178
|
+
// { $mounted, $destroyed, $attached, $detached, $computed, $self, $$self,
|
|
179
|
+
// ...injected, ...siblingScope } - key order included, because the
|
|
180
|
+
// parameters are positional (built as objects, so a sibling named like a
|
|
181
|
+
// helper keeps the helper's place, as it does there)
|
|
181
182
|
//
|
|
182
183
|
// Reading the signatures is also the first thing renderWith does, and where
|
|
183
184
|
// it refuses a component - a factory destructuring a ctx name out of props
|
package/src/reactive.ts
CHANGED
|
@@ -1200,3 +1200,32 @@ class Scope implements EffectScope {
|
|
|
1200
1200
|
}
|
|
1201
1201
|
|
|
1202
1202
|
export const createEffectScope = (scope: Record<string, any>, deep = false): EffectScope => new Scope(scope, deep)
|
|
1203
|
+
|
|
1204
|
+
// a derived value: `get`'s result on `.value`, recomputed whenever anything it
|
|
1205
|
+
// read changes - in any store, the way any effect is woken. What comes back is
|
|
1206
|
+
// a store, so it does everything one does (passed as a prop, held in another
|
|
1207
|
+
// store and bridged there, $on("value")), seen through a read-only view: a
|
|
1208
|
+
// write would be overwritten by the next recompute, so it is refused aloud
|
|
1209
|
+
// instead (see RECORD/2026-10-01.computed.md)
|
|
1210
|
+
export type Computed<T> = ReactiveDeepData<{ readonly value: T }>
|
|
1211
|
+
|
|
1212
|
+
export const $computed = <T>(get: () => T): Computed<T> => {
|
|
1213
|
+
const box = $reactive({ value: undefined as T })
|
|
1214
|
+
box.$effect(() => {
|
|
1215
|
+
let value: T
|
|
1216
|
+
try {
|
|
1217
|
+
value = get()
|
|
1218
|
+
} catch (error) {
|
|
1219
|
+
// left to propagate, it would throw out of whatever write woke it -
|
|
1220
|
+
// `cart.items.push(x)` failing for code that has nothing to do with it
|
|
1221
|
+
console.error("jq79: error in $computed, keeping its last value", error)
|
|
1222
|
+
return
|
|
1223
|
+
}
|
|
1224
|
+
box.value = value
|
|
1225
|
+
})
|
|
1226
|
+
const refuse = (_target: object, key: string | symbol): boolean => {
|
|
1227
|
+
console.warn(`jq79: a $computed is read-only - the write to ${String(key)} was ignored`)
|
|
1228
|
+
return true
|
|
1229
|
+
}
|
|
1230
|
+
return new Proxy(box, { set: refuse, deleteProperty: refuse, defineProperty: refuse }) as Computed<T>
|
|
1231
|
+
}
|
package/src/source.ts
CHANGED
|
@@ -364,12 +364,13 @@ export const declaredPropNames = (scripts: TagBlock[], signature: (script: TagBl
|
|
|
364
364
|
// the names every setup and factory script is compiled with, in the order
|
|
365
365
|
// renderWith passes them: SETUP_HELPERS (whose values live in jq79.ts, beside
|
|
366
366
|
// the functions they are) and then the per-instance ones it builds -
|
|
367
|
-
// $mounted, $
|
|
368
|
-
//
|
|
367
|
+
// $mounted, $destroyed, $attached, $detached, $computed, $self and $$self,
|
|
368
|
+
// then the injected
|
|
369
|
+
// $emit, $updateModel and $slots. They are positional parameters, so the order is part of what
|
|
369
370
|
// precompile has to reproduce; change one side and precompiled scripts stop
|
|
370
371
|
// matching, which tests/precompile.test.ts and `npm run check:precompile` catch
|
|
371
372
|
export const SETUP_HELPER_NAMES = ["$", "$$", "$create", "$reactive", "$toRaw", "Component79"]
|
|
372
|
-
export const INSTANCE_HELPER_NAMES = ["$mounted", "$self", "$$self", "$emit", "$updateModel", "$slots"]
|
|
373
|
+
export const INSTANCE_HELPER_NAMES = ["$mounted", "$destroyed", "$attached", "$detached", "$computed", "$self", "$$self", "$emit", "$updateModel", "$slots"]
|
|
373
374
|
|
|
374
375
|
// the global a precompiled script leaves its functions on, for the runtime to
|
|
375
376
|
// drain: (self.__jq79precompiled = self.__jq79precompiled || []).push([params,
|
package/src/transform.ts
CHANGED
|
@@ -401,7 +401,7 @@ export const transformSetupScript = (src: string): SetupTransform => {
|
|
|
401
401
|
// plain lexical module instead of a `with`-scoped setup script: no implicit
|
|
402
402
|
// reactivity, no `$:` labels - standard JS that editors and type-checkers
|
|
403
403
|
// understand. The default export is called with the instance context
|
|
404
|
-
// ({ $data, $effect, $emit, $mounted, $self, $$self }) and a returned object
|
|
404
|
+
// ({ $data, $effect, $emit, $mounted, $destroyed, $attached, $detached, $computed, $self, $$self }) and a returned object
|
|
405
405
|
// is merged into the reactive store for the template to use.
|
|
406
406
|
// Detection is backwards-safe: `export default` is a SyntaxError inside a
|
|
407
407
|
// setup script, so no previously-working component can change behavior.
|