_push(\`</optgroup></select></div>\`)
}"
`)
- _push(\`<!--]--></optgroup></select></div>\`)
+
+ expect(
+ compileWithWrapper(`
+ <select multiple v-model="model">
+ <optgroup>
+ <option v-for="item in items" :value="item">{{item}}</option>
+ </optgroup>
+ </select>`).code,
+ ).toMatchInlineSnapshot(`
+ "const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
+
+ return function ssrRender(_ctx, _push, _parent, _attrs) {
+ _push(\`<div\${_ssrRenderAttrs(_attrs)}><select multiple><optgroup><!--[-->\`)
+ _ssrRenderList(_ctx.items, (item) => {
+ _push(\`<option\${
+ _ssrRenderAttr("value", item)
+ }\${
+ (_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
+ ? _ssrLooseContain(_ctx.model, item)
+ : _ssrLooseEqual(_ctx.model, item))) ? " selected" : ""
+ }>\${
+ _ssrInterpolate(item)
+ }</option>\`)
+ })
- _push(\`<!--]-->\`)
++ _push(\`<!--]--><!--for--></optgroup></select></div>\`)
+ }"
+ `)
+
+ expect(
+ compileWithWrapper(`
+ <select multiple v-model="model">
+ <optgroup>
+ <option v-if="true" :value="item">{{item}}</option>
+ </optgroup>
+ </select>`).code,
+ ).toMatchInlineSnapshot(`
+ "const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require("vue/server-renderer")
+
+ return function ssrRender(_ctx, _push, _parent, _attrs) {
+ _push(\`<div\${_ssrRenderAttrs(_attrs)}><select multiple><optgroup>\`)
+ if (true) {
+ _push(\`<option\${
+ _ssrRenderAttr("value", _ctx.item)
+ }\${
+ (_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
+ ? _ssrLooseContain(_ctx.model, _ctx.item)
+ : _ssrLooseEqual(_ctx.model, _ctx.item))) ? " selected" : ""
+ }>\${
+ _ssrInterpolate(_ctx.item)
+ }</option>\`)
++ _push(\`<!--if-->\`)
+ } else {
+ _push(\`<!---->\`)
+ }
+ _push(\`</optgroup></select></div>\`)
+ }"
+ `)
+
+ expect(
+ compileWithWrapper(`
+ <select multiple v-model="model">
+ <optgroup>
+ <template v-if="ok">
+ <option v-for="item in items" :value="item">{{item}}</option>
+ </template>
+ </optgroup>
+ </select>`).code,
+ ).toMatchInlineSnapshot(`
+ "const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
+
+ return function ssrRender(_ctx, _push, _parent, _attrs) {
+ _push(\`<div\${_ssrRenderAttrs(_attrs)}><select multiple><optgroup>\`)
+ if (_ctx.ok) {
+ _push(\`<!--[-->\`)
+ _ssrRenderList(_ctx.items, (item) => {
+ _push(\`<option\${
+ _ssrRenderAttr("value", item)
+ }\${
+ (_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
+ ? _ssrLooseContain(_ctx.model, item)
+ : _ssrLooseEqual(_ctx.model, item))) ? " selected" : ""
+ }>\${
+ _ssrInterpolate(item)
+ }</option>\`)
+ })
- _push(\`<!--]--></optgroup></select></div>\`)
++ _push(\`<!--]--><!--for-->\`)
++ _push(\`<!--if-->\`)
+ } else {
+ _push(\`<!---->\`)
+ }
+ _push(\`</optgroup></select></div>\`)
+ }"
+ `)
+
+ expect(
+ compileWithWrapper(`
+ <select multiple v-model="model">
+ <optgroup>
+ <template v-for="item in items" :value="item">
+ <option v-if="item===1" :value="item">{{item}}</option>
+ </template>
+ </optgroup>
+ </select>`).code,
+ ).toMatchInlineSnapshot(`
+ "const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
+
+ return function ssrRender(_ctx, _push, _parent, _attrs) {
+ _push(\`<div\${_ssrRenderAttrs(_attrs)}><select multiple><optgroup><!--[-->\`)
+ _ssrRenderList(_ctx.items, (item) => {
+ _push(\`<!--[-->\`)
+ if (item===1) {
+ _push(\`<option\${
+ _ssrRenderAttr("value", item)
+ }\${
+ (_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
+ ? _ssrLooseContain(_ctx.model, item)
+ : _ssrLooseEqual(_ctx.model, item))) ? " selected" : ""
+ }>\${
+ _ssrInterpolate(item)
+ }</option>\`)
++ _push(\`<!--if-->\`)
+ } else {
+ _push(\`<!---->\`)
+ }
+ _push(\`<!--]-->\`)
+ })
++ _push(\`<!--]--><!--for--></optgroup></select></div>\`)
+ }"
+ `)
})
test('<input type="radio">', () => {
shallowRef,
toReactive,
toReadonly,
+ watch,
} from '@vue/reactivity'
- import {
- FOR_ANCHOR_LABEL,
- getSequence,
- isArray,
- isObject,
- isString,
- } from '@vue/shared'
-import { isArray, isObject, isString } from '@vue/shared'
++import { FOR_ANCHOR_LABEL, isArray, isObject, isString } from '@vue/shared'
import { createComment, createTextNode } from './dom/node'
import {
type Block,
let oldBlocks: ForBlock[] = []
let newBlocks: ForBlock[]
let parent: ParentNode | undefined | null
- // TODO handle this in hydration
- const parentAnchor = __DEV__ ? createComment('for') : createTextNode()
+ // useSelector only
+ let currentKey: any
+ let parentAnchor: Node
+ if (isHydrating) {
+ parentAnchor = locateVaporFragmentAnchor(
+ currentHydrationNode!,
+ FOR_ANCHOR_LABEL,
+ )!
+ if (__DEV__ && !parentAnchor) {
+ // this should not happen
+ throw new Error(`v-for fragment anchor node was not found.`)
+ }
+ } else {
+ parentAnchor = __DEV__ ? createComment('for') : createTextNode()
+ }
+
const frag = new VaporFragment(oldBlocks)
const instance = currentInstance!
- const canUseFastRemove = flags & VaporVForFlags.FAST_REMOVE
- const isComponent = flags & VaporVForFlags.IS_COMPONENT
+ const canUseFastRemove = !!(flags & VaporVForFlags.FAST_REMOVE)
+ const isComponent = !!(flags & VaporVForFlags.IS_COMPONENT)
+ const selectors: {
+ deregister: (key: any) => void
+ cleanup: () => void
+ }[] = []
if (__DEV__ && !instance) {
warn('createFor() can only be used inside setup()')
unmountComponent,
} from './component'
import { createComment, createTextNode } from './dom/node'
- import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity'
+ import { EffectScope, setActiveSub } from '@vue/reactivity'
-import { isHydrating } from './dom/hydration'
+import {
+ currentHydrationNode,
+ isComment,
+ isHydrating,
+ locateHydrationNode,
+ locateVaporFragmentAnchor,
+} from './dom/hydration'
export type Block =
| Node
parent && insert(this.nodes, parent, this.anchor)
}
- resetTracking()
+ setActiveSub(prevSub)
}
+
+ hydrate(label: string): void {
+ // for `v-if="false"` the node will be an empty comment, use it as the anchor.
+ // otherwise, find next sibling vapor fragment anchor
+ if (isComment(currentHydrationNode!, '')) {
+ this.anchor = currentHydrationNode
+ } else {
+ const anchor = locateVaporFragmentAnchor(currentHydrationNode!, label)!
+ if (anchor) {
+ this.anchor = anchor
+ } else if (__DEV__) {
+ // this should not happen
+ throw new Error(`${label} fragment anchor node was not found.`)
+ }
+ }
+ }
}
export function isFragment(val: NonNullable<unknown>): val is VaporFragment {