jq79 0.4.18 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/jq79.cjs +13 -13
- package/dist/jq79.cjs.map +1 -1
- package/dist/jq79.d.ts +1 -0
- package/dist/jq79.global.js +13 -13
- package/dist/jq79.global.js.map +1 -1
- package/dist/jq79.js +13 -13
- package/dist/jq79.js.map +1 -1
- package/package.json +1 -1
- package/src/jq79.ts +174 -39
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jq79",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Mini reactive component library: single-file components, Svelte-style setup scripts, fine-grained proxy reactivity. Single-file build, zero dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"reactive",
|
package/src/jq79.ts
CHANGED
|
@@ -550,6 +550,11 @@ const renderNestedComponent = (key: string, node: TemplateNode, scope: Record<st
|
|
|
550
550
|
// the assignment would vanish into the comment and compile as a bare read,
|
|
551
551
|
// dropping every update without a word
|
|
552
552
|
const assignment = (expr: string) => `${expr}\n= $value`
|
|
553
|
+
// the models whose expression will never take an update, decided here rather
|
|
554
|
+
// than at update time: an assignment that landed and one that was dropped
|
|
555
|
+
// both evaluate to the value assigned, so the result can't tell them apart -
|
|
556
|
+
// which is what $updateModel's return has to report
|
|
557
|
+
const unassignable = new Set<string>()
|
|
553
558
|
Object.entries(models).forEach(([name, expr]) => {
|
|
554
559
|
const prop = modelProp(name)
|
|
555
560
|
if (props[prop] !== undefined) {
|
|
@@ -559,6 +564,7 @@ const renderNestedComponent = (key: string, node: TemplateNode, scope: Record<st
|
|
|
559
564
|
// an expression that can't be an assignment target is a wiring mistake -
|
|
560
565
|
// say so now, not on the first update that silently goes nowhere
|
|
561
566
|
if (compileExpr(assignment(expr), ["$value"]) === null) {
|
|
567
|
+
unassignable.add(name)
|
|
562
568
|
console.warn(`jq79: ${modelAttr(name)}="${expr}" is not assignable - updates from <${node.tag}> will be dropped`)
|
|
563
569
|
}
|
|
564
570
|
})
|
|
@@ -640,39 +646,37 @@ const renderNestedComponent = (key: string, node: TemplateNode, scope: Record<st
|
|
|
640
646
|
// the content this site wrote inside the tag, before the first render: a
|
|
641
647
|
// <slot> is resolved while rendering, so the map has to be there by then
|
|
642
648
|
if (slots) instance.slots = slots
|
|
643
|
-
// the writeback half of :model -
|
|
644
|
-
//
|
|
645
|
-
//
|
|
646
|
-
//
|
|
647
|
-
//
|
|
649
|
+
// the writeback half of :model - the function the child's $updateModel
|
|
650
|
+
// calls, handed over before the first render. Not an event: nothing about
|
|
651
|
+
// a parent-child assignment wants a CustomEvent bubbling through the page
|
|
652
|
+
// on every keystroke, and a direct call has no payload shape to get wrong.
|
|
653
|
+
// The name is normalized like the attribute was (kebab->camel; absent
|
|
654
|
+
// means the default model), and a name nothing binds warns: a typo must
|
|
655
|
+
// not be an input that types into the void
|
|
648
656
|
if (Object.keys(models).length) {
|
|
649
657
|
// each mistake is warned once per instance, not once per keystroke: an
|
|
650
|
-
// input
|
|
658
|
+
// input updating a typo'd name would otherwise flood the console on
|
|
651
659
|
// every character typed into it
|
|
652
660
|
const warned = new Set<string>()
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
warned.add(key)
|
|
656
|
-
console.warn(message)
|
|
657
|
-
}
|
|
658
|
-
instance.on("model:update", (_event, payload) => {
|
|
659
|
-
if (payload === null || typeof payload !== "object") {
|
|
660
|
-
warnOnce("payload", `jq79: model:update expects a { name?, value } payload, got ${payload === null ? "null" : typeof payload}`)
|
|
661
|
-
return
|
|
662
|
-
}
|
|
663
|
-
const name = payload.name == null ? "default" : kebabToCamel(String(payload.name))
|
|
661
|
+
instance.modelWriteback = (rawName, value) => {
|
|
662
|
+
const name = rawName == null ? "default" : kebabToCamel(String(rawName))
|
|
664
663
|
const expr = models[name]
|
|
665
664
|
if (expr === undefined) {
|
|
666
|
-
|
|
667
|
-
|
|
665
|
+
if (!warned.has(name)) {
|
|
666
|
+
warned.add(name)
|
|
667
|
+
console.warn(`jq79: <${node.tag}> has no ${modelAttr(name)} - bound: ${Object.keys(models).map(modelAttr).join(", ")}`)
|
|
668
|
+
}
|
|
669
|
+
return false
|
|
668
670
|
}
|
|
669
|
-
|
|
671
|
+
if (unassignable.has(name)) return false // already warned, at wiring time
|
|
672
|
+
// untracked, like the tag handlers: a child updating from its setup
|
|
670
673
|
// script runs inside the parent's *creation* effect, and the reads a
|
|
671
674
|
// path assignment makes (`user` in `user.name = $value`) would land
|
|
672
675
|
// in its deps - donating that effect one wasted (guard-stopped) wake
|
|
673
676
|
// per later write. An imperative writeback is nobody's dependency
|
|
674
|
-
untracked(() => evalExpr(assignment(expr), scope, { $value:
|
|
675
|
-
|
|
677
|
+
untracked(() => evalExpr(assignment(expr), scope, { $value: value }))
|
|
678
|
+
return true
|
|
679
|
+
}
|
|
676
680
|
}
|
|
677
681
|
|
|
678
682
|
// @event on the tag listens to this instance's $emit channel (and only
|
|
@@ -685,6 +689,7 @@ const renderNestedComponent = (key: string, node: TemplateNode, scope: Record<st
|
|
|
685
689
|
// or an undeclared name would be filtered on the first render and reappear
|
|
686
690
|
// on the next update
|
|
687
691
|
const declared = declaredPropSet(instance.scripts)
|
|
692
|
+
warnUndeclared(node, key, Object.keys(props), declared)
|
|
688
693
|
const seed = pickDeclared(untracked(resolveProps), declared)
|
|
689
694
|
// mounting into a fragment attaches no shadow root of its own: a
|
|
690
695
|
// shadow-rendered child keeps its <style> elements inline, next to the DOM
|
|
@@ -815,6 +820,41 @@ const normalizeAllowUrl = (policy: any): AllowUrl => {
|
|
|
815
820
|
return () => false
|
|
816
821
|
}
|
|
817
822
|
|
|
823
|
+
// HTML's boolean attributes, verbatim from the spec's list. Presence is the
|
|
824
|
+
// whole message for these: `disabled="false"` and `disabled="0"` both disable,
|
|
825
|
+
// so the value they carry is noise. This is a table of a fact, not of a jq79
|
|
826
|
+
// convention - nobody in this repo decides what belongs in it, which is what
|
|
827
|
+
// earns it a place in a codebase that otherwise has no name tables
|
|
828
|
+
const BOOLEAN_ATTRS = new Set([
|
|
829
|
+
"allowfullscreen", "async", "autofocus", "autoplay", "checked", "controls",
|
|
830
|
+
"default", "defer", "disabled", "formnovalidate", "inert", "ismap",
|
|
831
|
+
"itemscope", "loop", "multiple", "muted", "nomodule", "novalidate", "open",
|
|
832
|
+
"playsinline", "readonly", "required", "reversed", "selected",
|
|
833
|
+
])
|
|
834
|
+
|
|
835
|
+
// the one value rule, shared by `:attr="expr"` and `:attrs` so the two forms
|
|
836
|
+
// can never disagree:
|
|
837
|
+
//
|
|
838
|
+
// - a boolean attribute is removed by ANY falsy value and set to "" when
|
|
839
|
+
// truthy, so `:disabled="items.length"` enables the button on an empty list
|
|
840
|
+
// (with `value !== false` as the only test, 0 set the attribute and disabled
|
|
841
|
+
// it - the trap renderComponent.test.ts used to pin);
|
|
842
|
+
// - every other attribute is removed only by null/undefined, so `false`, `0`
|
|
843
|
+
// and `""` are written. `aria-expanded="false"` and a `data-` flag mean
|
|
844
|
+
// something that absent cannot say.
|
|
845
|
+
//
|
|
846
|
+
// Asking the DOM which family a name belongs to (`typeof el[name] ===
|
|
847
|
+
// "boolean"`) is deliberately not what this does: jsdom and Chrome disagree on
|
|
848
|
+
// `autofocus` and every `aria-*`, so the tests would pin a semantics the
|
|
849
|
+
// browser doesn't have - and `readonly`/`novalidate`/`ismap` reflect under
|
|
850
|
+
// camelCase property names no kebab->camel pass can produce, failing toward
|
|
851
|
+
// `readonly="false"`, which is read-only
|
|
852
|
+
const applyAttr = (el: Element, name: string, value: any) => {
|
|
853
|
+
const boolean = BOOLEAN_ATTRS.has(name)
|
|
854
|
+
if (boolean ? !value : value == null) el.removeAttribute(name)
|
|
855
|
+
else el.setAttribute(name, boolean ? "" : String(value))
|
|
856
|
+
}
|
|
857
|
+
|
|
818
858
|
// renders a single element node: static attrs, @event listeners, a reactive
|
|
819
859
|
// :attrs object, and its content - :text/:html override the element's own
|
|
820
860
|
// children with a reactive textContent/innerHTML, otherwise children render
|
|
@@ -844,7 +884,11 @@ const renderNode = (node: TemplateNode, outerScope: Record<string, any>, fx: Eff
|
|
|
844
884
|
// imported component after `await`). Watch for the key: the effect tracks
|
|
845
885
|
// no deps, so it only re-runs on the store's new-key sweep, and swaps the
|
|
846
886
|
// placeholder element for the component exactly once
|
|
847
|
-
|
|
887
|
+
// dashes included, because findComponentKey matches them case-insensitively
|
|
888
|
+
// with dashes stripped: <drop-area> resolves DropArea, so a dashed tag is a
|
|
889
|
+
// possible component too, not only a custom element
|
|
890
|
+
const mayUpgrade = el instanceof HTMLUnknownElement || node.tag.includes("-")
|
|
891
|
+
if (mayUpgrade) {
|
|
848
892
|
let upgraded = false
|
|
849
893
|
fx.effect(() => {
|
|
850
894
|
if (upgraded) return
|
|
@@ -867,10 +911,30 @@ const renderNode = (node: TemplateNode, outerScope: Record<string, any>, fx: Eff
|
|
|
867
911
|
// the native-element form is parked there). Warn on a real element, but
|
|
868
912
|
// not on a tag that may still upgrade into a component - the upgrade
|
|
869
913
|
// re-renders through renderNestedComponent, models and all
|
|
870
|
-
if (!
|
|
914
|
+
if (!mayUpgrade) {
|
|
871
915
|
console.warn(`jq79: ${key} on <${node.tag}> does nothing - :model binds component tags only (for now)`)
|
|
872
916
|
}
|
|
873
|
-
} else if (
|
|
917
|
+
} else if (isControlAttr(key)) {
|
|
918
|
+
// a directive of its own, bound further down (or by renderNodes)
|
|
919
|
+
} else if (key.startsWith(":")) {
|
|
920
|
+
// :name="expr" binds that one attribute, reactively - the single-key
|
|
921
|
+
// case :attrs="{ name: expr }" was carrying. `:name` alone is shorthand
|
|
922
|
+
// for `:name="name"`, like props and :model.<name>, and the shorthand
|
|
923
|
+
// reads the camelCase variable while the attribute keeps its written
|
|
924
|
+
// (kebab) name: `:aria-expanded` binds `ariaExpanded`, because
|
|
925
|
+
// `aria-expanded` as an expression is a subtraction.
|
|
926
|
+
//
|
|
927
|
+
// On a tag that may still upgrade this is a *parameter*, not an
|
|
928
|
+
// attribute: leave it written verbatim, as before, so the upgrade's
|
|
929
|
+
// renderNestedComponent still finds it. A component tag has no single
|
|
930
|
+
// root for an attribute to land on anyway (TODOS/2026-07-15.class-directive.md)
|
|
931
|
+
if (mayUpgrade) el.setAttribute(key, value)
|
|
932
|
+
else {
|
|
933
|
+
const name = key.slice(1)
|
|
934
|
+
const expr = value || kebabToCamel(name)
|
|
935
|
+
fx.effect(() => applyAttr(el, name, evalExpr(expr, scope)))
|
|
936
|
+
}
|
|
937
|
+
} else el.setAttribute(key, value)
|
|
874
938
|
})
|
|
875
939
|
|
|
876
940
|
const bindExpr = node.attrs[":attrs"]
|
|
@@ -881,10 +945,7 @@ const renderNode = (node: TemplateNode, outerScope: Record<string, any>, fx: Eff
|
|
|
881
945
|
boundKeys.forEach(key => el.removeAttribute(key))
|
|
882
946
|
const bound = evalExpr(bindExpr, scope)
|
|
883
947
|
boundKeys = bound && typeof bound === "object" ? Object.keys(bound) : []
|
|
884
|
-
boundKeys.forEach(key =>
|
|
885
|
-
const value = bound[key]
|
|
886
|
-
if (value != null && value !== false) el.setAttribute(key, String(value))
|
|
887
|
-
})
|
|
948
|
+
boundKeys.forEach(key => applyAttr(el, key, bound[key]))
|
|
888
949
|
})
|
|
889
950
|
}
|
|
890
951
|
|
|
@@ -1642,6 +1703,22 @@ const declareProps = (store: Record<string, any>, props: PropDecl[] | null) => {
|
|
|
1642
1703
|
})
|
|
1643
1704
|
}
|
|
1644
1705
|
|
|
1706
|
+
// a setup script's signature. A bare `<script :setup>` is a CLOSED signature -
|
|
1707
|
+
// the same as `<script :setup="{}">`, declaring zero props and taking none -
|
|
1708
|
+
// because the difference between "takes nothing" and "takes anything" should
|
|
1709
|
+
// not be a pair of braces somebody didn't type. Permissive is still reachable,
|
|
1710
|
+
// it just has to be asked for: `<script :setup="_">`, the same `_` convention
|
|
1711
|
+
// factory scripts already use, which parsePropsPattern reads as no signature.
|
|
1712
|
+
//
|
|
1713
|
+
// Only the empty *value* is closed. An absent attribute (a factory <script>
|
|
1714
|
+
// with no :setup at all) stays `null`, so its signature is still read from the
|
|
1715
|
+
// factory's first parameter
|
|
1716
|
+
const setupSignature = (script: TagBlock): PropDecl[] | null => {
|
|
1717
|
+
const pattern = script.attrs[":setup"]
|
|
1718
|
+
if (pattern === undefined) return null
|
|
1719
|
+
return pattern.trim() === "" ? [] : parsePropsPattern(pattern)
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1645
1722
|
// every prop name a component's scripts declare, across both script modes.
|
|
1646
1723
|
// Read before the store exists, because what a component declares decides
|
|
1647
1724
|
// which of its file's sibling components it can still see: declaring a name
|
|
@@ -1650,7 +1727,7 @@ const declareProps = (store: Record<string, any>, props: PropDecl[] | null) => {
|
|
|
1650
1727
|
const declaredPropNames = (scripts: TagBlock[]): Set<string> => {
|
|
1651
1728
|
const names = new Set<string>()
|
|
1652
1729
|
scripts.forEach(script => {
|
|
1653
|
-
const declarations = parseFactoryProps(script.content) ??
|
|
1730
|
+
const declarations = parseFactoryProps(script.content) ?? setupSignature(script)
|
|
1654
1731
|
declarations?.forEach(({ name }) => names.add(name))
|
|
1655
1732
|
})
|
|
1656
1733
|
return names
|
|
@@ -1658,13 +1735,13 @@ const declaredPropNames = (scripts: TagBlock[]): Set<string> => {
|
|
|
1658
1735
|
|
|
1659
1736
|
// the same names, but null when NO script declared a signature at all - the
|
|
1660
1737
|
// distinction declareProps already keeps, and the only one that can decide
|
|
1661
|
-
// whether to filter what a parent passes
|
|
1662
|
-
//
|
|
1663
|
-
//
|
|
1738
|
+
// whether to filter what a parent passes. `<script :setup>` and
|
|
1739
|
+
// `<script :setup="{}">` are both closed signatures that take nothing (see
|
|
1740
|
+
// setupSignature); `<script :setup="_">` is the permissive one
|
|
1664
1741
|
const declaredPropSet = (scripts: TagBlock[]): Set<string> | null => {
|
|
1665
1742
|
let names: Set<string> | null = null
|
|
1666
1743
|
scripts.forEach(script => {
|
|
1667
|
-
const declarations = parseFactoryProps(script.content) ??
|
|
1744
|
+
const declarations = parseFactoryProps(script.content) ?? setupSignature(script)
|
|
1668
1745
|
if (!declarations) return
|
|
1669
1746
|
const into = (names ??= new Set())
|
|
1670
1747
|
declarations.forEach(({ name }) => into.add(name))
|
|
@@ -1686,6 +1763,30 @@ const pickDeclared = (props: Record<string, any>, declared: Set<string> | null):
|
|
|
1686
1763
|
return out
|
|
1687
1764
|
}
|
|
1688
1765
|
|
|
1766
|
+
// names already reported by warnUndeclared, keyed by the template node - which
|
|
1767
|
+
// is the usage site itself, built once and shared by every instance it ever
|
|
1768
|
+
// renders. So a :each over 200 rows says it once, not once per row, and a
|
|
1769
|
+
// definition swap doesn't repeat what the last one already said
|
|
1770
|
+
const undeclaredWarned = new WeakMap<TemplateNode, Set<string>>()
|
|
1771
|
+
|
|
1772
|
+
// a parameter the child's signature doesn't declare is dropped by pickDeclared
|
|
1773
|
+
// and never reaches its store - `{{ bar }}` renders empty at the other end of
|
|
1774
|
+
// the file. Written parameters only: this is handed the named ones (`:bar`,
|
|
1775
|
+
// and the prop each :model binds), never a `:props` spread's keys, because a
|
|
1776
|
+
// spread of an object wider than the component is the documented, intended use
|
|
1777
|
+
// and taking only the declared few is its point - see pickDeclared. A
|
|
1778
|
+
// component with no signature at all declares nothing to compare against
|
|
1779
|
+
const warnUndeclared = (node: TemplateNode, name: string, written: string[], declared: Set<string> | null) => {
|
|
1780
|
+
if (declared === null) return
|
|
1781
|
+
const said = undeclaredWarned.get(node) ?? new Set<string>()
|
|
1782
|
+
undeclaredWarned.set(node, said)
|
|
1783
|
+
written.forEach(prop => {
|
|
1784
|
+
if (declared.has(prop) || said.has(prop)) return
|
|
1785
|
+
said.add(prop)
|
|
1786
|
+
console.warn(`jq79: :${prop} is not declared by <${name}> - add it to the :setup signature, or drop it`)
|
|
1787
|
+
})
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1689
1790
|
// the sibling components this one resolves by name, or null when there are
|
|
1690
1791
|
// none left to resolve. They go on the store's *prototype* rather than in it:
|
|
1691
1792
|
// the component-key scan walks the chain, so <Row> resolves; they stay out of
|
|
@@ -1902,6 +2003,13 @@ export class Component79 {
|
|
|
1902
2003
|
// and every render reads it from here: a hot reload re-renders from a data
|
|
1903
2004
|
// snapshot, which a symbol on the store would not survive
|
|
1904
2005
|
slots?: SlotMap
|
|
2006
|
+
// the writeback half of :model, same story: the function that assigns into
|
|
2007
|
+
// the parent, set by renderNestedComponent before the first render and
|
|
2008
|
+
// called by this instance's $updateModel. Kept outside the render generation
|
|
2009
|
+
// so it survives re-render and hot reload. Absent means no :model on the tag
|
|
2010
|
+
// (or no tag at all - a root mount), which makes every $updateModel a no-op.
|
|
2011
|
+
// Internal: set by the usage site, not part of the public API
|
|
2012
|
+
modelWriteback?: (name: string | undefined, value: any) => boolean
|
|
1905
2013
|
|
|
1906
2014
|
data: ReactiveDeepData<Record<string, any>> | null = null
|
|
1907
2015
|
|
|
@@ -2094,7 +2202,16 @@ export class Component79 {
|
|
|
2094
2202
|
// event is cancelable so preventDefault() - from either channel - flips
|
|
2095
2203
|
// the return to false, telling the emitting child "the parent vetoed"
|
|
2096
2204
|
const marker = this.startMarker
|
|
2205
|
+
// model:update used to be the writeback's event name; it is a direct call
|
|
2206
|
+
// now ($updateModel), so an emit under that name reaches nothing. Said
|
|
2207
|
+
// once per generation rather than per keystroke, and said at all because
|
|
2208
|
+
// the alternative is a child whose edits silently stop arriving
|
|
2209
|
+
let warnedModelUpdate = false
|
|
2097
2210
|
const $emit = (eventName: string, payload?: any): boolean => {
|
|
2211
|
+
if (eventName === "model:update" && !warnedModelUpdate) {
|
|
2212
|
+
warnedModelUpdate = true
|
|
2213
|
+
console.warn("jq79: $emit('model:update', …) no longer feeds :model - call $updateModel(value) or $updateModel(name, value) instead")
|
|
2214
|
+
}
|
|
2098
2215
|
const event = new CustomEvent(eventName, { detail: payload, bubbles: true, composed: true, cancelable: true })
|
|
2099
2216
|
if (marker === this.startMarker) {
|
|
2100
2217
|
this.emitListeners.get(eventName)?.forEach(listener => listener(event, payload))
|
|
@@ -2141,17 +2258,35 @@ export class Component79 {
|
|
|
2141
2258
|
const $import = (url: string): Promise<any> =>
|
|
2142
2259
|
modules && url in modules ? Promise.resolve(modules[url]) : importResource(url)
|
|
2143
2260
|
|
|
2261
|
+
// the writeback half of :model, from the child's side: one argument is the
|
|
2262
|
+
// value for the default model (the bare :model), two are a name and a
|
|
2263
|
+
// value. Arity is what tells them apart, so the value is never inspected -
|
|
2264
|
+
// an object with `name`/`value` keys is just a value, which is exactly
|
|
2265
|
+
// what a payload-shaped contract could not promise. Returns whether a
|
|
2266
|
+
// bound model took it; no :model at the usage site is a silent no-op,
|
|
2267
|
+
// since a child may be designed to work bound or unbound
|
|
2268
|
+
const $updateModel = (...args: [value?: any] | [name: string, value: any]): boolean => {
|
|
2269
|
+
const [name, value] = args.length > 1 ? args as [string, any] : [undefined, args[0]]
|
|
2270
|
+
// the same stale-generation guard $emit has: destroy() nulls the marker,
|
|
2271
|
+
// so a closure the old child leaked (a timer, a registered callback)
|
|
2272
|
+
// cannot keep writing a parent that replaced it
|
|
2273
|
+
if (marker !== this.startMarker) return false
|
|
2274
|
+
return this.modelWriteback?.(name, value) ?? false
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2144
2277
|
// the names a component answers on top of its store: $emit, so an inline
|
|
2145
2278
|
// handler can emit without routing through a setup function
|
|
2146
|
-
// (@input="$emit('update', $event.target.value)"),
|
|
2147
|
-
//
|
|
2148
|
-
//
|
|
2279
|
+
// (@input="$emit('update', $event.target.value)"), $updateModel, the
|
|
2280
|
+
// writeback a :model binding listens for, and $slots, the static map of
|
|
2281
|
+
// the names the usage site filled, so a wrapper can be dropped when
|
|
2282
|
+
// nothing filled it (<footer :if="$slots.footer">). All reach the
|
|
2149
2283
|
// template (through templateScope, below) and both script modes (as
|
|
2150
|
-
// instance helpers), and a same-named store key shadows
|
|
2284
|
+
// instance helpers), and a same-named store key shadows any of them.
|
|
2151
2285
|
// Null-prototype, for the same reason storeApi is: `key in injected` must
|
|
2152
2286
|
// not start answering true for toString, constructor and the rest
|
|
2153
2287
|
const injected: Record<string, any> = Object.assign(Object.create(null), {
|
|
2154
2288
|
$emit,
|
|
2289
|
+
$updateModel,
|
|
2155
2290
|
$slots: Object.fromEntries(Object.keys(this.slots ?? {}).map(name => [name, true])),
|
|
2156
2291
|
})
|
|
2157
2292
|
|
|
@@ -2179,7 +2314,7 @@ export class Component79 {
|
|
|
2179
2314
|
return
|
|
2180
2315
|
}
|
|
2181
2316
|
const { vars, code } = transformSetupScript(script.content)
|
|
2182
|
-
declareProps(store,
|
|
2317
|
+
declareProps(store, setupSignature(script))
|
|
2183
2318
|
// pre-declare script vars on the store so `with` resolves assignments
|
|
2184
2319
|
// to them (and reads of them) through the reactive proxy
|
|
2185
2320
|
vars.forEach(name => { if (!(name in store)) (store as any)[name] = undefined })
|