vue#createTextVNode TypeScript Examples
The following examples show how to use
vue#createTextVNode.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: FormKitSchema.ts From formkit with MIT License | 4 votes |
/**
*
* @param library - A library of concrete components to use
* @param schema -
* @returns
*/
function parseSchema(
library: FormKitComponentLibrary,
schema: FormKitSchemaNode | FormKitSchemaNode[]
): SchemaProvider {
/**
* Given an if/then/else schema node, pre-compile the node and return the
* artifacts for the render function.
* @param data - The schema context object
* @param library - The available components
* @param node - The node to parse
*/
function parseCondition(
library: FormKitComponentLibrary,
node: FormKitSchemaCondition
): [RenderContent[0], RenderContent[3], RenderContent[4]] {
const condition = provider(compile(node.if), { if: true })
const children = createElements(library, node.then)
const alternate = node.else ? createElements(library, node.else) : null
return [condition, children, alternate]
}
/**
* Parses a conditional if/then/else attribute statement.
* @param data - The data object
* @param attr - The attribute
* @param _default - The default value
* @returns
*/
function parseConditionAttr(
attr: FormKitSchemaAttributesCondition,
_default: FormKitAttributeValue
): () => FormKitAttributeValue | FormKitSchemaAttributes {
const condition = provider(compile(attr.if))
let b: () => FormKitAttributeValue = () => _default
let a: () => FormKitAttributeValue = () => _default
if (typeof attr.then === 'object') {
a = parseAttrs(attr.then, undefined)
} else if (typeof attr.then === 'string' && attr.then?.startsWith('$')) {
a = provider(compile(attr.then))
} else {
a = () => attr.then
}
if (has(attr, 'else')) {
if (typeof attr.else === 'object') {
b = parseAttrs(attr.else)
} else if (typeof attr.else === 'string' && attr.else?.startsWith('$')) {
b = provider(compile(attr.else))
} else {
b = () => attr.else
}
}
return () => (condition() ? a() : b())
}
/**
* Parse attributes for dynamic content.
* @param attrs - Object of attributes
* @returns
*/
function parseAttrs(
unparsedAttrs?: FormKitSchemaAttributes | FormKitSchemaAttributesCondition,
bindExp?: string,
_default = {}
): () => FormKitSchemaAttributes {
const explicitAttrs = new Set(Object.keys(unparsedAttrs || {}))
const boundAttrs = bindExp ? provider(compile(bindExp)) : () => ({})
const staticAttrs: FormKitSchemaAttributes = {}
const setters: Array<(obj: Record<string, any>) => void> = [
(attrs) => {
const bound: Record<string, any> = boundAttrs()
for (const attr in bound) {
if (!explicitAttrs.has(attr)) {
attrs[attr] = bound[attr]
}
}
},
]
if (unparsedAttrs) {
if (isConditional(unparsedAttrs)) {
// This is a root conditional object that must produce an object of
// attributes.
const condition = parseConditionAttr(
unparsedAttrs,
_default
) as () => FormKitSchemaAttributes
return condition
}
// Some attributes are explicitly bound, we need to parse those ones
// using the compiler and create a dynamic "setter".
for (let attr in unparsedAttrs) {
const value = unparsedAttrs[attr]
let getValue: () => any
const isStr = typeof value === 'string'
if (attr.startsWith(raw)) {
// attributes prefixed with __raw__ should not be parsed
attr = attr.substring(7)
getValue = () => value
} else if (
isStr &&
value.startsWith('$') &&
value.length > 1 &&
!(value.startsWith('$reset') && isClassProp.test(attr))
) {
// Most attribute values starting with $ should be compiled
// -class attributes starting with `$reset` should not be compiled
getValue = provider(compile(value))
} else if (typeof value === 'object' && isConditional(value)) {
// Conditional attrs require further processing
getValue = parseConditionAttr(value, undefined)
} else if (typeof value === 'object' && isPojo(value)) {
// Sub-parse pojos
getValue = parseAttrs(value)
} else {
// In all other cases, the value is static
getValue = () => value
staticAttrs[attr] = value
}
setters.push((attrs) => {
attrs[attr] = getValue()
})
}
}
return () => {
const attrs = {}
setters.forEach((setter) => setter(attrs))
return attrs
}
}
/**
* Given a single schema node, parse it and extract the value.
* @param data - A state object provided to each node
* @param node - The schema node being parsed
* @returns
*/
function parseNode(
library: FormKitComponentLibrary,
_node: FormKitSchemaNode
): RenderContent {
let element: RenderContent[1] = null
let attrs: () => FormKitSchemaAttributes = () => null
let condition: false | (() => boolean | number | string) = false
let children: RenderContent[3] = null
let alternate: RenderContent[4] = null
let iterator: RenderContent[5] = null
let resolve = false
const node = sugar(_node)
if (isDOM(node)) {
// This is an actual HTML DOM element
element = node.$el
attrs =
node.$el !== 'text' ? parseAttrs(node.attrs, node.bind) : () => null
} else if (isComponent(node)) {
// This is a Vue Component
if (typeof node.$cmp === 'string') {
if (has(library, node.$cmp)) {
element = library[node.$cmp]
} else {
element = node.$cmp
resolve = true
}
} else {
// in this case it must be an actual component
element = node.$cmp
}
attrs = parseAttrs(node.props, node.bind)
} else if (isConditional(node)) {
// This is an if/then schema statement
;[condition, children, alternate] = parseCondition(library, node)
}
// This is the same as a "v-if" statement — not an if/else statement
if (!isConditional(node) && 'if' in node) {
condition = provider(compile(node.if as string))
} else if (!isConditional(node) && element === null) {
// In this odd case our element is actually a partial and
// we only want to render the children.
condition = () => true
}
// Compile children down to a function
if ('children' in node && node.children) {
if (typeof node.children === 'string') {
// We are dealing with a raw string value
if (node.children.startsWith('$slots.')) {
// this is a lone text node, turn it into a slot
element = element === 'text' ? 'slot' : element
children = provider(compile(node.children))
} else if (node.children.startsWith('$') && node.children.length > 1) {
const value = provider(compile(node.children))
children = () => String(value())
} else {
children = () => String(node.children)
}
} else if (Array.isArray(node.children)) {
// We are dealing with node sub-children
children = createElements(library, node.children)
} else {
// This is a conditional if/else clause
const [childCondition, c, a] = parseCondition(library, node.children)
children = (iterationData?: Record<string, unknown>) =>
childCondition && childCondition()
? c && c(iterationData)
: a && a(iterationData)
}
}
if (isComponent(node)) {
if (children) {
// Children of components need to be provided as an object of slots
// so we provide an object with the default slot provided as children.
// We also create a new scope for this default slot, and then on each
// render pass the scoped slot props to the scope.
const produceChildren = children
children = (iterationData?: Record<string, unknown>) => {
return {
default(
slotData?: Record<string, any>,
key?: symbol
): RenderableList {
// We need to switch the current instance key back to the one that
// originally called this component's render function.
const currentKey = instanceKey
if (key) instanceKey = key
if (slotData) instanceScopes.get(instanceKey)?.unshift(slotData)
if (iterationData)
instanceScopes.get(instanceKey)?.unshift(iterationData)
const c = produceChildren(iterationData)
// Ensure our instance key never changed during runtime
if (slotData) instanceScopes.get(instanceKey)?.shift()
if (iterationData) instanceScopes.get(instanceKey)?.shift()
instanceKey = currentKey
return c as RenderableList
},
}
}
children.slot = true
} else {
// If we dont have any children, we still need to provide an object
// instead of an empty array (which raises a warning in vue)
children = () => ({})
}
}
// Compile the for loop down
if ('for' in node && node.for) {
const values = node.for.length === 3 ? node.for[2] : node.for[1]
const getValues =
typeof values === 'string' && values.startsWith('$')
? provider(compile(values))
: () => values
iterator = [
getValues,
node.for[0],
node.for.length === 3 ? String(node.for[1]) : null,
]
}
return [condition, element, attrs, children, alternate, iterator, resolve]
}
/**
* Given a particular function that produces children, ensure that the second
* argument of all these slots is the original instance key being used to
* render the slots.
* @param children - The children() function that will produce slots
*/
function createSlots(
children: RenderChildren,
iterationData?: Record<string, unknown>
): RenderableSlots | null {
const slots = children(iterationData) as RenderableSlots
const currentKey = instanceKey
return Object.keys(slots).reduce((allSlots, slotName) => {
const slotFn = slots && slots[slotName]
allSlots[slotName] = (data?: Record<string, any>) => {
return (slotFn && slotFn(data, currentKey)) || null
}
return allSlots
}, {} as RenderableSlots)
}
/**
* Creates an element
* @param data - The context data available to the node
* @param node - The schema node to render
* @returns
*/
function createElement(
library: FormKitComponentLibrary,
node: FormKitSchemaNode
): RenderNodes {
// Parses the schema node into pertinent parts
const [condition, element, attrs, children, alternate, iterator, resolve] =
parseNode(library, node)
// This is a sub-render function (called within a render function). It must
// only use pre-compiled features, and be organized in the most efficient
// manner possible.
let createNodes: RenderNodes = ((
iterationData?: Record<string, unknown>
) => {
if (condition && element === null && children) {
// Handle conditional if/then statements
return condition()
? children(iterationData)
: alternate && alternate(iterationData)
}
if (element && (!condition || condition())) {
// handle text nodes
if (element === 'text' && children) {
return createTextVNode(String(children()))
}
// Handle lone slots
if (element === 'slot' && children) return children(iterationData)
// Handle resolving components
const el = resolve ? resolveComponent(element as string) : element
// If we are rendering slots as children, ensure their instanceKey is properly added
const slots: RenderableSlots | null = children?.slot
? createSlots(children, iterationData)
: null
// Handle dom elements and components
return h(
el,
attrs(),
(slots || (children ? children(iterationData) : [])) as Renderable[]
)
}
return typeof alternate === 'function'
? alternate(iterationData)
: alternate
}) as RenderNodes
if (iterator) {
const repeatedNode = createNodes
const [getValues, valueName, keyName] = iterator
createNodes = (() => {
const _v = getValues()
const values = !isNaN(_v as number)
? Array(Number(_v))
.fill(0)
.map((_, i) => i)
: _v
const fragment = []
if (typeof values !== 'object') return null
const instanceScope = instanceScopes.get(instanceKey) || []
for (const key in values) {
const iterationData: Record<string, unknown> = Object.defineProperty(
{
...instanceScope.reduce(
(
previousIterationData: Record<string, undefined>,
scopedData: Record<string, undefined>
) => {
if (previousIterationData.__idata) {
return { ...previousIterationData, ...scopedData }
}
return scopedData
},
{} as Record<string, undefined>
),
[valueName]: values[key],
...(keyName !== null ? { [keyName]: key } : {}),
},
'__idata',
{ enumerable: false, value: true }
)
instanceScope.unshift(iterationData)
fragment.push(repeatedNode.bind(null, iterationData)())
instanceScope.shift()
}
return fragment
}) as RenderNodes
}
return createNodes as RenderNodes
}
/**
* Given a schema, parse it and return the resulting renderable nodes.
* @param data - The schema context object
* @param library - The available components
* @param node - The node to parse
* @returns
*/
function createElements(
library: FormKitComponentLibrary,
schema: FormKitSchemaNode | FormKitSchemaNode[]
): RenderChildren {
if (Array.isArray(schema)) {
const els = schema.map(createElement.bind(null, library))
return (iterationData?: Record<string, unknown>) =>
els.map((element) => element(iterationData))
}
// Single node to render
const element = createElement(library, schema)
return (iterationData?: Record<string, unknown>) => element(iterationData)
}
/**
* Data providers produced as a result of the compiler.
*/
const providers: ProviderRegistry = []
/**
* Append the requisite compiler provider and return the compiled function.
* @param compiled - A compiled function
* @returns
*/
function provider(
compiled: FormKitCompilerOutput,
hints: Record<string, boolean> = {}
) {
const compiledFns: Record<symbol, FormKitCompilerOutput> = {}
providers.push((callback: SchemaProviderCallback, key: symbol) => {
compiledFns[key] = compiled.provide((tokens) => callback(tokens, hints))
})
return () => compiledFns[instanceKey]()
}
/**
* Creates a new instance of a given schema — this either comes from a
* memoized copy of the parsed schema or a freshly parsed version. An symbol
* instance key, and dataProvider functions are passed in.
* @param providerCallback - A function that is called for each required provider
* @param key - a symbol representing the current instance
*/
return function createInstance(
providerCallback: SchemaProviderCallback,
key
) {
const memoKey = JSON.stringify(schema)
const [render, compiledProviders] = has(memo, memoKey)
? memo[memoKey]
: [createElements(library, schema), providers]
memo[memoKey] = [render, compiledProviders]
compiledProviders.forEach((compiledProvider) => {
compiledProvider(providerCallback, key)
})
return () => {
instanceKey = key
return render()
}
}
}