jq79 0.4.17 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/jq79.cjs +12 -12
- package/dist/jq79.cjs.map +1 -1
- package/dist/jq79.d.ts +1 -0
- package/dist/jq79.global.js +12 -12
- package/dist/jq79.global.js.map +1 -1
- package/dist/jq79.js +12 -12
- package/dist/jq79.js.map +1 -1
- package/package.json +1 -1
- package/src/jq79.ts +169 -38
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jq79",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
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
|
@@ -182,6 +182,12 @@ const wireTagEvent = (instance: Component79, attr: string, expr: string, scope:
|
|
|
182
182
|
|
|
183
183
|
const kebabToCamel = (name: string) => name.replace(/-(\w)/g, (_, c: string) => c.toUpperCase())
|
|
184
184
|
|
|
185
|
+
// the inverse, used only by the pre-parse name rewrite (see expandNameCase):
|
|
186
|
+
// uppercase ASCII letters only, never digits - `:props.0` is a generated
|
|
187
|
+
// attribute name and splitting on digits would mangle it. Round-trips through
|
|
188
|
+
// kebabToCamel, acronyms included: userID -> user-i-d -> userID
|
|
189
|
+
const camelToKebab = (name: string) => name.replace(/[A-Z]/g, c => `-${c.toLowerCase()}`)
|
|
190
|
+
|
|
185
191
|
// the stable boundaries of a rendered chunk. An element is its own handle, but
|
|
186
192
|
// a fragment (a nested component: two anchors with the instance's DOM between
|
|
187
193
|
// them) empties itself into the parent on insertion - after that its identity
|
|
@@ -289,10 +295,11 @@ type SlotMap = Record<string, SlotRenderer>
|
|
|
289
295
|
// nested component is handed
|
|
290
296
|
const SLOTS = Symbol("jq79.slots")
|
|
291
297
|
|
|
292
|
-
// <slot>, <slot.header-bar>: the hole and its name. Names
|
|
293
|
-
//
|
|
294
|
-
//
|
|
295
|
-
//
|
|
298
|
+
// <slot>, <slot.header-bar>: the hole and its name. Names arrive kebab-case
|
|
299
|
+
// whichever way they were authored (the HTML parser lowercases tag names and
|
|
300
|
+
// attribute modifiers alike, so expandNameCase normalizes camelCase to kebab
|
|
301
|
+
// before parsing) and are camelCase where read - <slot.header-bar> and
|
|
302
|
+
// <slot.headerBar> are :slot.header-bar is $slots.headerBar
|
|
296
303
|
const isSlotTag = (tag: string): boolean => tag === "slot" || tag.startsWith("slot.")
|
|
297
304
|
|
|
298
305
|
const slotName = (suffix: string): string => (suffix ? kebabToCamel(suffix) : "default")
|
|
@@ -515,8 +522,8 @@ const renderNestedComponent = (key: string, node: TemplateNode, scope: Record<st
|
|
|
515
522
|
} else if (attr === ":model" || attr.startsWith(":model.")) {
|
|
516
523
|
// :model[.name]="expr" - two-way: a prop down plus a writeback listener
|
|
517
524
|
// (wired below, once the instance exists). The modifier arrives
|
|
518
|
-
//
|
|
519
|
-
// the bare :model binds the name "default"
|
|
525
|
+
// kebab-case whichever way it was authored (expandNameCase rewrote any
|
|
526
|
+
// camelCase before parsing); the bare :model binds the name "default"
|
|
520
527
|
const name = attr === ":model" ? "default" : kebabToCamel(attr.slice(":model.".length))
|
|
521
528
|
models[name] = value || (attr === ":model" ? "model" : name)
|
|
522
529
|
} else if (attr.startsWith(":")) {
|
|
@@ -543,6 +550,11 @@ const renderNestedComponent = (key: string, node: TemplateNode, scope: Record<st
|
|
|
543
550
|
// the assignment would vanish into the comment and compile as a bare read,
|
|
544
551
|
// dropping every update without a word
|
|
545
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>()
|
|
546
558
|
Object.entries(models).forEach(([name, expr]) => {
|
|
547
559
|
const prop = modelProp(name)
|
|
548
560
|
if (props[prop] !== undefined) {
|
|
@@ -552,6 +564,7 @@ const renderNestedComponent = (key: string, node: TemplateNode, scope: Record<st
|
|
|
552
564
|
// an expression that can't be an assignment target is a wiring mistake -
|
|
553
565
|
// say so now, not on the first update that silently goes nowhere
|
|
554
566
|
if (compileExpr(assignment(expr), ["$value"]) === null) {
|
|
567
|
+
unassignable.add(name)
|
|
555
568
|
console.warn(`jq79: ${modelAttr(name)}="${expr}" is not assignable - updates from <${node.tag}> will be dropped`)
|
|
556
569
|
}
|
|
557
570
|
})
|
|
@@ -633,39 +646,37 @@ const renderNestedComponent = (key: string, node: TemplateNode, scope: Record<st
|
|
|
633
646
|
// the content this site wrote inside the tag, before the first render: a
|
|
634
647
|
// <slot> is resolved while rendering, so the map has to be there by then
|
|
635
648
|
if (slots) instance.slots = slots
|
|
636
|
-
// the writeback half of :model -
|
|
637
|
-
//
|
|
638
|
-
//
|
|
639
|
-
//
|
|
640
|
-
//
|
|
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
|
|
641
656
|
if (Object.keys(models).length) {
|
|
642
657
|
// each mistake is warned once per instance, not once per keystroke: an
|
|
643
|
-
// input
|
|
658
|
+
// input updating a typo'd name would otherwise flood the console on
|
|
644
659
|
// every character typed into it
|
|
645
660
|
const warned = new Set<string>()
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
warned.add(key)
|
|
649
|
-
console.warn(message)
|
|
650
|
-
}
|
|
651
|
-
instance.on("model:update", (_event, payload) => {
|
|
652
|
-
if (payload === null || typeof payload !== "object") {
|
|
653
|
-
warnOnce("payload", `jq79: model:update expects a { name?, value } payload, got ${payload === null ? "null" : typeof payload}`)
|
|
654
|
-
return
|
|
655
|
-
}
|
|
656
|
-
const name = payload.name == null ? "default" : kebabToCamel(String(payload.name))
|
|
661
|
+
instance.modelWriteback = (rawName, value) => {
|
|
662
|
+
const name = rawName == null ? "default" : kebabToCamel(String(rawName))
|
|
657
663
|
const expr = models[name]
|
|
658
664
|
if (expr === undefined) {
|
|
659
|
-
|
|
660
|
-
|
|
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
|
|
661
670
|
}
|
|
662
|
-
|
|
671
|
+
if (unassignable.has(name)) return false // already warned, at wiring time
|
|
672
|
+
// untracked, like the tag handlers: a child updating from its setup
|
|
663
673
|
// script runs inside the parent's *creation* effect, and the reads a
|
|
664
674
|
// path assignment makes (`user` in `user.name = $value`) would land
|
|
665
675
|
// in its deps - donating that effect one wasted (guard-stopped) wake
|
|
666
676
|
// per later write. An imperative writeback is nobody's dependency
|
|
667
|
-
untracked(() => evalExpr(assignment(expr), scope, { $value:
|
|
668
|
-
|
|
677
|
+
untracked(() => evalExpr(assignment(expr), scope, { $value: value }))
|
|
678
|
+
return true
|
|
679
|
+
}
|
|
669
680
|
}
|
|
670
681
|
|
|
671
682
|
// @event on the tag listens to this instance's $emit channel (and only
|
|
@@ -673,7 +684,12 @@ const renderNestedComponent = (key: string, node: TemplateNode, scope: Record<st
|
|
|
673
684
|
// explicit re-emit)
|
|
674
685
|
events.forEach(([attr, expr]) => wireTagEvent(instance, attr, expr, scope))
|
|
675
686
|
|
|
676
|
-
|
|
687
|
+
// what this component actually takes, decided by its signature. Applied to
|
|
688
|
+
// every path that writes a prop - the seed here and both sync paths below -
|
|
689
|
+
// or an undeclared name would be filtered on the first render and reappear
|
|
690
|
+
// on the next update
|
|
691
|
+
const declared = declaredPropSet(instance.scripts)
|
|
692
|
+
const seed = pickDeclared(untracked(resolveProps), declared)
|
|
677
693
|
// mounting into a fragment attaches no shadow root of its own: a
|
|
678
694
|
// shadow-rendered child keeps its <style> elements inline, next to the DOM
|
|
679
695
|
// they style, and the parent's shadow root is what scopes both
|
|
@@ -709,7 +725,7 @@ const renderNestedComponent = (key: string, node: TemplateNode, scope: Record<st
|
|
|
709
725
|
if (hasSpread) {
|
|
710
726
|
let written: string[] = []
|
|
711
727
|
syncFx.effect(() => {
|
|
712
|
-
const next = resolveProps()
|
|
728
|
+
const next = pickDeclared(resolveProps(), declared)
|
|
713
729
|
const nextKeys = Object.keys(next)
|
|
714
730
|
written.forEach(key => { if (!(key in next)) (instance.data as Record<string, any>)[key] = undefined })
|
|
715
731
|
nextKeys.forEach(key => { (instance.data as Record<string, any>)[key] = next[key] })
|
|
@@ -717,6 +733,7 @@ const renderNestedComponent = (key: string, node: TemplateNode, scope: Record<st
|
|
|
717
733
|
})
|
|
718
734
|
} else {
|
|
719
735
|
Object.entries(props).forEach(([name, expr]) => {
|
|
736
|
+
if (declared !== null && !declared.has(name)) return
|
|
720
737
|
syncFx.effect(() => { (instance.data as Record<string, any>)[name] = evalExpr(expr, scope) })
|
|
721
738
|
})
|
|
722
739
|
}
|
|
@@ -1277,6 +1294,53 @@ const expandPropsSpread = (src: string): string =>
|
|
|
1277
1294
|
)
|
|
1278
1295
|
.join("")
|
|
1279
1296
|
|
|
1297
|
+
// a `:`-prefixed attribute name in name position, and a </slot.name> closing
|
|
1298
|
+
// tag. Both quote-aware for the same reason ATTR_SPREAD_RE is: a colon inside
|
|
1299
|
+
// a value (@click="a ? b : c", style="color: red") is not an attribute name
|
|
1300
|
+
const ATTR_NAME_RE = /"[^"]*"|'[^']*'|(^|\s)(:[\w.$-]+)/g
|
|
1301
|
+
const CLOSE_SLOT_RE = /<\/slot\.([\w.$-]+)(\s*)>/gi
|
|
1302
|
+
const SLOT_TAG_RE = /^slot\./i
|
|
1303
|
+
|
|
1304
|
+
// camelCase -> kebab-case for every name the HTML parser would lowercase,
|
|
1305
|
+
// BEFORE it gets the chance: `:firstName` would arrive as `:firstname` and
|
|
1306
|
+
// kebabToCamel (which is what reads these names back out) would have nothing
|
|
1307
|
+
// to un-kebab, so the prop, model or slot would silently land under the wrong
|
|
1308
|
+
// key. Rewriting to `:first-name` here means both spellings converge on the
|
|
1309
|
+
// same camelCase name downstream - the author picks, the runtime doesn't care.
|
|
1310
|
+
//
|
|
1311
|
+
// Runs FIRST among the pre-parse passes, which is what keeps it simple: it
|
|
1312
|
+
// never sees the `:props.<n>` that expandPropsSpread generates, and a
|
|
1313
|
+
// <slot.firstName /> is still one occurrence rather than the open+close pair
|
|
1314
|
+
// expandSelfClosingTags turns it into. Same defenses as the passes after it -
|
|
1315
|
+
// <script>/<style> bodies split out, only start-tag interiors scanned, quoted
|
|
1316
|
+
// values consumed whole.
|
|
1317
|
+
//
|
|
1318
|
+
// Two name positions, not one: attribute names (`:model.firstName`) and the
|
|
1319
|
+
// dotted tag names (`<slot.firstName>`), whose closing halves are rewritten
|
|
1320
|
+
// too or the parser sees a mismatched pair. Component tags are deliberately
|
|
1321
|
+
// left alone - findComponentKey already matches them case-insensitively with
|
|
1322
|
+
// dashes stripped, so <UserCard> needs no help and rewriting it would only
|
|
1323
|
+
// obscure what the author wrote
|
|
1324
|
+
const kebabTagName = (tag: string): string =>
|
|
1325
|
+
SLOT_TAG_RE.test(tag) ? `slot.${camelToKebab(tag.slice("slot.".length))}` : tag
|
|
1326
|
+
|
|
1327
|
+
const expandNameCase = (src: string): string =>
|
|
1328
|
+
src
|
|
1329
|
+
.split(RAW_BLOCK_RE)
|
|
1330
|
+
.map((chunk, i) =>
|
|
1331
|
+
i % 2 === 1
|
|
1332
|
+
? chunk
|
|
1333
|
+
: chunk
|
|
1334
|
+
.replace(OPEN_TAG_RE, (_match, tag: string, attrs: string) => {
|
|
1335
|
+
const rewritten = attrs.replace(ATTR_NAME_RE, (whole, space: string | undefined, name: string | undefined) =>
|
|
1336
|
+
name === undefined ? whole : `${space}${camelToKebab(name)}`
|
|
1337
|
+
)
|
|
1338
|
+
return `<${kebabTagName(tag)}${rewritten}>`
|
|
1339
|
+
})
|
|
1340
|
+
.replace(CLOSE_SLOT_RE, (_match, suffix: string, space: string) => `</slot.${camelToKebab(suffix)}${space}>`)
|
|
1341
|
+
)
|
|
1342
|
+
.join("")
|
|
1343
|
+
|
|
1280
1344
|
// <style scoped> support. Every element of the component's own template is
|
|
1281
1345
|
// stamped with data-jq79="<hash>" and the style's selectors are rewritten to
|
|
1282
1346
|
// require that attribute, so its rules can't reach anything the component
|
|
@@ -1367,10 +1431,13 @@ const parseComponentString = (component: string): ComponentParts => {
|
|
|
1367
1431
|
// </style>
|
|
1368
1432
|
|
|
1369
1433
|
// parsed as the content of a <template> so leading <script>/<style> tags
|
|
1370
|
-
// aren't reparented into <head> by the HTML parser.
|
|
1371
|
-
// rewrites run here
|
|
1372
|
-
//
|
|
1373
|
-
|
|
1434
|
+
// aren't reparented into <head> by the HTML parser. All three pre-DOM string
|
|
1435
|
+
// rewrites run here, and the order is load-bearing: camelCase names ->
|
|
1436
|
+
// kebab-case first (before `:props.<n>` exists to be mangled and while a
|
|
1437
|
+
// self-closing tag is still one occurrence), then `...expr` -> :props.<n>
|
|
1438
|
+
// (which reads the raw camelCase before the parser can lowercase names),
|
|
1439
|
+
// then self-closing tags
|
|
1440
|
+
const prepared = expandSelfClosingTags(expandPropsSpread(expandNameCase(component)))
|
|
1374
1441
|
const parsedDOM = new DOMParser().parseFromString(`<template>${prepared}</template>`, "text/html")
|
|
1375
1442
|
const root = parsedDOM.querySelector("template") as HTMLTemplateElement
|
|
1376
1443
|
|
|
@@ -1593,6 +1660,36 @@ const declaredPropNames = (scripts: TagBlock[]): Set<string> => {
|
|
|
1593
1660
|
return names
|
|
1594
1661
|
}
|
|
1595
1662
|
|
|
1663
|
+
// the same names, but null when NO script declared a signature at all - the
|
|
1664
|
+
// distinction declareProps already keeps, and the only one that can decide
|
|
1665
|
+
// whether to filter what a parent passes: `<script :setup>` declares nothing
|
|
1666
|
+
// and stays permissive, `<script :setup="{}">` is a closed signature that
|
|
1667
|
+
// declares zero props and takes none
|
|
1668
|
+
const declaredPropSet = (scripts: TagBlock[]): Set<string> | null => {
|
|
1669
|
+
let names: Set<string> | null = null
|
|
1670
|
+
scripts.forEach(script => {
|
|
1671
|
+
const declarations = parseFactoryProps(script.content) ?? parsePropsPattern(script.attrs[":setup"])
|
|
1672
|
+
if (!declarations) return
|
|
1673
|
+
const into = (names ??= new Set())
|
|
1674
|
+
declarations.forEach(({ name }) => into.add(name))
|
|
1675
|
+
})
|
|
1676
|
+
return names
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
// drops the props a component didn't declare, so an undeclared name is simply
|
|
1680
|
+
// absent from its store rather than quietly present: `{{ label }}` renders
|
|
1681
|
+
// empty and `{{ user.name }}` throws on the member access, both at the usage
|
|
1682
|
+
// site that got the name wrong. A null signature keeps everything - see
|
|
1683
|
+
// declaredPropSet. Silent by design: the main source of extra keys is a
|
|
1684
|
+
// `:props` spread of an object wider than the component (`...sdk`), where
|
|
1685
|
+
// taking only the declared few is the point, not a mistake to report
|
|
1686
|
+
const pickDeclared = (props: Record<string, any>, declared: Set<string> | null): Record<string, any> => {
|
|
1687
|
+
if (declared === null) return props
|
|
1688
|
+
const out: Record<string, any> = {}
|
|
1689
|
+
Object.keys(props).forEach(key => { if (declared.has(key)) out[key] = props[key] })
|
|
1690
|
+
return out
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1596
1693
|
// the sibling components this one resolves by name, or null when there are
|
|
1597
1694
|
// none left to resolve. They go on the store's *prototype* rather than in it:
|
|
1598
1695
|
// the component-key scan walks the chain, so <Row> resolves; they stay out of
|
|
@@ -1809,6 +1906,13 @@ export class Component79 {
|
|
|
1809
1906
|
// and every render reads it from here: a hot reload re-renders from a data
|
|
1810
1907
|
// snapshot, which a symbol on the store would not survive
|
|
1811
1908
|
slots?: SlotMap
|
|
1909
|
+
// the writeback half of :model, same story: the function that assigns into
|
|
1910
|
+
// the parent, set by renderNestedComponent before the first render and
|
|
1911
|
+
// called by this instance's $updateModel. Kept outside the render generation
|
|
1912
|
+
// so it survives re-render and hot reload. Absent means no :model on the tag
|
|
1913
|
+
// (or no tag at all - a root mount), which makes every $updateModel a no-op.
|
|
1914
|
+
// Internal: set by the usage site, not part of the public API
|
|
1915
|
+
modelWriteback?: (name: string | undefined, value: any) => boolean
|
|
1812
1916
|
|
|
1813
1917
|
data: ReactiveDeepData<Record<string, any>> | null = null
|
|
1814
1918
|
|
|
@@ -2001,7 +2105,16 @@ export class Component79 {
|
|
|
2001
2105
|
// event is cancelable so preventDefault() - from either channel - flips
|
|
2002
2106
|
// the return to false, telling the emitting child "the parent vetoed"
|
|
2003
2107
|
const marker = this.startMarker
|
|
2108
|
+
// model:update used to be the writeback's event name; it is a direct call
|
|
2109
|
+
// now ($updateModel), so an emit under that name reaches nothing. Said
|
|
2110
|
+
// once per generation rather than per keystroke, and said at all because
|
|
2111
|
+
// the alternative is a child whose edits silently stop arriving
|
|
2112
|
+
let warnedModelUpdate = false
|
|
2004
2113
|
const $emit = (eventName: string, payload?: any): boolean => {
|
|
2114
|
+
if (eventName === "model:update" && !warnedModelUpdate) {
|
|
2115
|
+
warnedModelUpdate = true
|
|
2116
|
+
console.warn("jq79: $emit('model:update', …) no longer feeds :model - call $updateModel(value) or $updateModel(name, value) instead")
|
|
2117
|
+
}
|
|
2005
2118
|
const event = new CustomEvent(eventName, { detail: payload, bubbles: true, composed: true, cancelable: true })
|
|
2006
2119
|
if (marker === this.startMarker) {
|
|
2007
2120
|
this.emitListeners.get(eventName)?.forEach(listener => listener(event, payload))
|
|
@@ -2048,17 +2161,35 @@ export class Component79 {
|
|
|
2048
2161
|
const $import = (url: string): Promise<any> =>
|
|
2049
2162
|
modules && url in modules ? Promise.resolve(modules[url]) : importResource(url)
|
|
2050
2163
|
|
|
2164
|
+
// the writeback half of :model, from the child's side: one argument is the
|
|
2165
|
+
// value for the default model (the bare :model), two are a name and a
|
|
2166
|
+
// value. Arity is what tells them apart, so the value is never inspected -
|
|
2167
|
+
// an object with `name`/`value` keys is just a value, which is exactly
|
|
2168
|
+
// what a payload-shaped contract could not promise. Returns whether a
|
|
2169
|
+
// bound model took it; no :model at the usage site is a silent no-op,
|
|
2170
|
+
// since a child may be designed to work bound or unbound
|
|
2171
|
+
const $updateModel = (...args: [value?: any] | [name: string, value: any]): boolean => {
|
|
2172
|
+
const [name, value] = args.length > 1 ? args as [string, any] : [undefined, args[0]]
|
|
2173
|
+
// the same stale-generation guard $emit has: destroy() nulls the marker,
|
|
2174
|
+
// so a closure the old child leaked (a timer, a registered callback)
|
|
2175
|
+
// cannot keep writing a parent that replaced it
|
|
2176
|
+
if (marker !== this.startMarker) return false
|
|
2177
|
+
return this.modelWriteback?.(name, value) ?? false
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2051
2180
|
// the names a component answers on top of its store: $emit, so an inline
|
|
2052
2181
|
// handler can emit without routing through a setup function
|
|
2053
|
-
// (@input="$emit('update', $event.target.value)"),
|
|
2054
|
-
//
|
|
2055
|
-
//
|
|
2182
|
+
// (@input="$emit('update', $event.target.value)"), $updateModel, the
|
|
2183
|
+
// writeback a :model binding listens for, and $slots, the static map of
|
|
2184
|
+
// the names the usage site filled, so a wrapper can be dropped when
|
|
2185
|
+
// nothing filled it (<footer :if="$slots.footer">). All reach the
|
|
2056
2186
|
// template (through templateScope, below) and both script modes (as
|
|
2057
|
-
// instance helpers), and a same-named store key shadows
|
|
2187
|
+
// instance helpers), and a same-named store key shadows any of them.
|
|
2058
2188
|
// Null-prototype, for the same reason storeApi is: `key in injected` must
|
|
2059
2189
|
// not start answering true for toString, constructor and the rest
|
|
2060
2190
|
const injected: Record<string, any> = Object.assign(Object.create(null), {
|
|
2061
2191
|
$emit,
|
|
2192
|
+
$updateModel,
|
|
2062
2193
|
$slots: Object.fromEntries(Object.keys(this.slots ?? {}).map(name => [name, true])),
|
|
2063
2194
|
})
|
|
2064
2195
|
|