UNPKG

@v4fire/client

Version:

V4Fire client core library

290 lines (232 loc) • 6.37 kB
/*! * V4Fire Client Core * https://github.com/V4Fire/Client * * Released under the MIT license * https://github.com/V4Fire/Client/blob/master/LICENSE */ /** * [[include:core/component/vnode/README.md]] * @packageDocumentation */ import { components } from 'core/component/const'; import type { RenderContext, VNode, VNodeData, NormalizedScopedSlot } from 'core/component/engines'; import type { ComponentInterface, ComponentMeta } from 'core/component/interface'; import type { ComponentVNodeData, ComponentModelVNodeData, PatchComponentVDataOptions } from 'core/component/vnode/interface'; export * from 'core/component/vnode/interface'; /** * Returns a component render context object from the specified vnode * * @param component - component name or meta object * @param vnode * @param [parent] - parent component instance */ export function getComponentRenderCtxFromVNode( component: string | ComponentMeta, vnode: VNode, parent?: ComponentInterface ): RenderContext { const data = getComponentDataFromVNode(component, vnode); return { parent: Object.cast(parent), children: vnode.children ?? [], props: data.props, listeners: <Record<string, CanArray<Function>>>data.on, slots: () => data.slots, scopedSlots: <Record<string, NormalizedScopedSlot>>data.scopedSlots, injections: undefined, data: { ref: data.ref, refInFor: data.refInFor, on: <Record<string, CanArray<Function>>>data.on, nativeOn: <Record<string, CanArray<Function>>>data.nativeOn, attrs: data.attrs, class: data.class, staticClass: data.staticClass, style: data.style, directives: data.directives } }; } /** * Returns a component data object from the specified vnode * * @param component - component name or a meta object * @param vnode */ export function getComponentDataFromVNode(component: string | ComponentMeta, vnode: VNode): ComponentVNodeData { const vData = vnode.data ?? {}, {slots, model} = (<Dictionary>vData); const res = <ComponentVNodeData>{ ref: vData.ref, refInFor: vData.refInFor, attrs: {}, props: {}, model: (<Dictionary>vData).model, directives: Array.concat([], vData.directives), slots: {...<Dictionary>slots}, scopedSlots: {...vData.scopedSlots}, on: {...vData.on}, nativeOn: {...vData.nativeOn}, class: [].concat(vData.class ?? []), staticClass: vData.staticClass, style: vData.style }; const meta = Object.isString(component) ? components.get(component) : component; if (!meta) { res.attrs = vData.attrs ?? res.attrs; return res; } const componentModel = meta.params.model; if (model != null && componentModel) { const // eslint-disable-next-line @typescript-eslint/unbound-method {value, callback} = <ComponentModelVNodeData>model, {prop, event} = componentModel; if (prop != null && event != null) { res.props[prop] = value; res.on[event] = callback; } } const vAttrs = vData.attrs, propsObj = meta.component.props; for (let keys = Object.keys(propsObj), i = 0; i < keys.length; i++) { res.props[keys[i]] = undefined; } if (vAttrs) { for (let keys = Object.keys(vAttrs), i = 0; i < keys.length; i++) { const key = keys[i], prop = key.camelize(false), val = vAttrs[key]; if (propsObj[prop]) { res.props[prop] = val; } else { res.attrs[key] = val; } } } if (slots == null && vnode.children) { const {children} = vnode; let hasSlots = false; for (let i = 0; i < children.length; i++) { const node = children[i], // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition data = node?.data ?? {}; const {attrs} = data; if (attrs?.slot != null) { hasSlots = true; res.slots[attrs.slot] = node; } } if (!hasSlots) { res.slots.default = children; } } return res; } /** * Patches the specified component VNode data object by using another VNode data object * * @param data - VNode data object * @param anotherData - another VNode data object * @param [opts] - additional options */ export function patchComponentVData( data: CanUndef<VNodeData | ComponentVNodeData>, anotherData: CanUndef<VNodeData | ComponentVNodeData>, opts?: PatchComponentVDataOptions ): CanUndef<VNodeData | ComponentVNodeData> { if (anotherData == null || data == null) { return data; } data.staticClass = data.staticClass ?? ''; if (Object.isTruly(anotherData.staticClass)) { data.staticClass += ` ${anotherData.staticClass}`; } if (Object.isTruly(anotherData.class)) { data.class = Array.concat([], data.class, anotherData.class); } if (Object.isTruly(anotherData.style)) { data.style = parseStyle(data.style, parseStyle(anotherData.style)); } if (Object.isTruly(anotherData.attrs) && opts?.patchAttrs) { data.attrs = Object.assign(data.attrs ?? {}, anotherData.attrs); } data.ref = anotherData.ref; data.refInFor = anotherData.refInFor; data.directives = Array.concat([], data.directives, anotherData.directives); data.on = data.on ?? {}; if (anotherData.nativeOn) { const {on} = data; for (let o = anotherData.nativeOn, keys = Object.keys(o), i = 0; i < keys.length; i++) { const key = keys[i]; on[key] = Array.concat([], on[key], o[key]); } } return data; } /** * Parses the specified style value and returns a dictionary with styles * * @param style - original style * @param [acc] - accumulator * * @example * ```js * // {color: 'red', background: 'blue'} * parseStyle(['color: red', {background: 'blue'}]) * ``` */ export function parseStyle( style: CanUndef<CanArray<string | object>>, acc: Dictionary<string> = {} ): Dictionary<string> { if (!Object.isTruly(style)) { return acc; } if (Object.isDictionary(style)) { Object.assign(acc, style); return acc; } if (Object.isString(style)) { const styles = style.split(';'); for (let i = 0; i < styles.length; i++) { const rule = styles[i]; if (rule.trim().length === 0) { continue; } const chunks = rule.split(':'); acc[chunks[0].trim()] = chunks[1].trim(); } return acc; } if (Object.isArray(style)) { for (let i = 0; i < style.length; i++) { const el = style[i]; if (Object.isDictionary(el)) { Object.assign(acc, el); } else { parseStyle(<CanArray<string>>el, acc); } } } return acc; }