vue#toRef TypeScript Examples

The following examples show how to use vue#toRef. 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: tools.ts    From trois with MIT License 6 votes vote down vote up
export function bindObjectProp(
  src: any,
  prop: string,
  dst: any,
  apply = true,
  setter?: OptionSetter
): WatchStopHandle {
  if (apply) applyObjectProps(dst, src[prop], setter)
  const r = toRef(src, prop)
  return watch(r, (value) => { applyObjectProps(dst, value, setter) })
}
Example #2
Source File: tools.ts    From trois with MIT License 6 votes vote down vote up
export function bindProp(src: any, srcProp: string, dst: any, dstProp?: string): void {
  const _dstProp = dstProp || srcProp
  const ref = toRef(src, srcProp)
  if (ref.value instanceof Object) {
    setFromProp(dst[_dstProp], ref.value)
    watch(ref, (value) => { setFromProp(dst[_dstProp], value) }, { deep: true })
  } else {
    if (ref.value !== undefined) dst[_dstProp] = src[srcProp]
    watch(ref, (value) => { dst[_dstProp] = value })
  }
}
Example #3
Source File: watchVerbose.spec.ts    From formkit with MIT License 4 votes vote down vote up
describe('watchVerbose', () => {
  it('can detect single level depth mutations', async () => {
    const values = ref({ a: 'a', b: 'b' })
    const callback = jest.fn()
    watchVerbose(values, callback)
    values.value.b = 'c'
    await nextTick()
    expect(callback).toHaveBeenCalledWith(['b'], 'c', values)
    values.value.b = 'd'
    await nextTick()
    expect(callback).toHaveBeenCalledWith(['b'], 'd', values)
  })

  it('can detect double level depth mutations', async () => {
    const values = ref({
      a: {
        b: 'c',
      },
      z: 'e',
    })
    const callback = jest.fn()
    watchVerbose(values, callback)
    values.value.a.b = 'foobar'
    await nextTick()
    expect(callback).toHaveBeenCalledTimes(1)
    expect(callback).toHaveBeenCalledWith(['a', 'b'], 'foobar', values)
  })

  it('can detect root changes when there is depth', async () => {
    const values = ref<any>({
      a: {
        b: 'c',
      },
      z: 'e',
    })
    const callback = jest.fn()
    watchVerbose(values, callback)
    values.value.z = 'foobar'
    await nextTick()
    expect(callback).toHaveBeenCalledTimes(1)
    expect(callback).toHaveBeenCalledWith(['z'], 'foobar', values)
  })

  it('can detect property additions', async () => {
    const values = ref<any>({
      a: {
        b: 'c',
      },
      z: 'e',
    })
    const callback = jest.fn()
    watchVerbose(values, callback)
    values.value.a.c = 'foobar'
    await nextTick()
    expect(callback).toHaveBeenCalledTimes(1)
    expect(callback).toHaveBeenCalledWith(
      ['a'],
      { b: 'c', c: 'foobar' },
      values
    )
  })

  it('can detect property changes at several layers of depth', async () => {
    const values = ref<any>({
      a: {
        b: {
          c: {
            z: 'f',
          },
        },
      },
      z: 'e',
    })
    const callback = jest.fn()
    watchVerbose(values, callback)
    values.value.a.b.c = { z: 'h' }
    await nextTick()
    expect(callback).toHaveBeenCalledTimes(1)
    expect(callback).toHaveBeenNthCalledWith(
      1,
      ['a', 'b', 'c'],
      { z: 'h' },
      values
    )
  })

  it('can detect changes to an array', async () => {
    const values = ref<any>({
      a: {
        b: [{ x: 123 }, { x: 456 }, { x: 789 }],
      },
      z: 'e',
    })
    const callback = jest.fn()
    watchVerbose(values, callback)
    values.value.a.b.push({ x: 10 })
    await nextTick()
    expect(callback).toHaveBeenCalledTimes(1)
    expect(callback).toHaveBeenCalledWith(
      ['a', 'b'],
      [{ x: 123 }, { x: 456 }, { x: 789 }, { x: 10 }],
      values
    )
  })

  it('can detect changes within an array', async () => {
    const values = ref<any>({
      a: {
        b: [{ x: 123 }, { x: 456 }, { x: 789 }],
      },
      z: 'e',
    })
    const callback = jest.fn()
    watchVerbose(values, callback)
    values.value.a.b[1].x = 567
    await nextTick()
    expect(callback).toHaveBeenCalledTimes(1)
    expect(callback).toHaveBeenCalledWith(['a', 'b', '1', 'x'], 567, values)
  })

  it('can detect changes inside a new object', async () => {
    const values = ref<any>({
      a: {
        b: [{ x: 123 }, { x: 456 }, { x: 789 }],
      },
      z: 'e',
    })
    const callback = jest.fn()
    watchVerbose(values, callback)
    values.value.a.b.push({ a: 567 })
    await nextTick()
    expect(callback).toHaveBeenCalledTimes(1)
    expect(callback).toHaveBeenCalledWith(
      ['a', 'b'],
      [{ x: 123 }, { x: 456 }, { x: 789 }, { a: 567 }],
      values
    )
    values.value.a.b[3].a = 8910
    await nextTick()
    expect(callback).toHaveBeenCalledTimes(2)
    expect(callback).toHaveBeenNthCalledWith(
      2,
      ['a', 'b', '3', 'a'],
      8910,
      values
    )
  })

  it('can change the type at a given location and observe its changes', async () => {
    const values = ref<any>({
      a: {
        b: [{ x: 123 }, { x: 456 }, { x: 789 }],
      },
      z: 'e',
    })
    const callback = jest.fn()
    watchVerbose(values, callback)
    values.value.a.b[0] = 'foobar'
    await nextTick()
    values.value.a.b[0] = 'barfoo'
    await nextTick()
    expect(callback).toHaveBeenCalledTimes(2)
    expect(callback).toHaveBeenNthCalledWith(
      2,
      ['a', 'b', '0'],
      'barfoo',
      values
    )
  })

  it('can detect changes at the root of the ref', async () => {
    const value = ref('abc')
    const callback = jest.fn()
    watchVerbose(value, callback)
    value.value = 'def'
    await nextTick()
    expect(callback).toHaveBeenCalledTimes(1)
    expect(callback).toHaveBeenCalledWith([], 'def', value)
  })

  it('can remove and replace old properties without getting watch sequence out of order', async () => {
    const value = ref<Record<string, any> | string>({
      a: {
        b: '123',
      },
    })
    const callback = jest.fn()
    watchVerbose(value, callback)
    value.value = 'foobar'
    await nextTick()
    expect(callback).toHaveBeenCalledTimes(1)
    value.value = { a: { b: '456' } }
    await nextTick()
    expect(callback).toHaveBeenCalledTimes(2)
    expect(callback).toHaveBeenNthCalledWith(2, [], { a: { b: '456' } }, value)
  })

  it('can set values that start with the same string', async () => {
    const values = ref({
      price: 7,
      prices: [5],
      cart: {
        price: 4,
      },
    })
    const callback = jest.fn()
    watchVerbose(values, callback)
    values.value.price = 0
    values.value.prices[0] = 0
    values.value.cart.price = 0
    await nextTick()
    expect(callback).toHaveBeenCalledTimes(3)
    expect(callback).toHaveBeenNthCalledWith(1, ['price'], 0, values)
    expect(callback).toHaveBeenNthCalledWith(2, ['prices', '0'], 0, values)
    expect(callback).toHaveBeenNthCalledWith(3, ['cart', 'price'], 0, values)
  })

  it('can change the same property twice synchronously', async () => {
    const value = ref({ a: '123' })
    const callback = jest.fn()
    watchVerbose(value, callback)
    value.value.a = '456'
    value.value.a = '567'
    await nextTick()
    expect(callback).toHaveBeenCalledTimes(1)
    expect(callback).toHaveBeenNthCalledWith(1, ['a'], '567', value)
  })

  it('works with reactive objects', async () => {
    const value = reactive({
      a: {
        b: {
          c: 123,
        },
      },
    })
    const callback = jest.fn()
    watchVerbose(value, callback)
    value.a.b.c = 456
    await nextTick()
    expect(callback).toHaveBeenCalledTimes(1)
    expect(callback).toHaveBeenNthCalledWith(1, ['a', 'b', 'c'], 456, value)
  })

  it('unwatches objects that are detached from the original ref', async () => {
    const values = ref<any>({
      a: {
        b: {
          c: 123,
        },
      },
    })
    const callback = jest.fn()
    watchVerbose(values, callback)
    const detached = toRef(values.value.a, 'b')
    values.value.a = 'foobar'
    await nextTick()
    expect(callback).toHaveBeenCalledTimes(1)
    detached.value.c = 'bar'
    await nextTick()
    expect(callback).toHaveBeenCalledTimes(1)
  })

  it('responds to new additions on vue reactive objects', async () => {
    const values = reactive<{ a?: string }>({})
    const callback = jest.fn()
    watchVerbose(values, callback)
    values.a = '123'
    await nextTick()
    expect(callback).toHaveBeenCalledTimes(1)
    expect(callback).toHaveBeenCalledWith([], { a: '123' }, values)
  })

  it('responds to additions on vue reactive objects at depth', async () => {
    const values = reactive<{ form: { a?: string } }>({
      form: {},
    })
    const callback = jest.fn()
    watchVerbose(values, callback)
    values.form.a = '123'
    await nextTick()
    expect(callback).toHaveBeenCalledTimes(1)
    expect(callback).toHaveBeenCalledWith(['form'], { a: '123' }, values)
    values.form.a = 'bar'
    await nextTick()
    expect(callback).toHaveBeenCalledWith(['form', 'a'], 'bar', values)
  })

  it('responds to additions to an array via splice', async () => {
    const values = ref({
      disregard: ['A', 'B'],
      users: [{ name: 'A' }, { name: 'B' }],
    })
    const callback = jest.fn()
    watchVerbose(values, callback)
    values.value.users.shift()
    await nextTick()
    values.value.users.splice(
      1,
      2,
      { name: 'splice' },
      { name: 'double spliced' }
    )
    await nextTick()
    expect(callback).toHaveBeenCalledTimes(4)
    expect(callback).toHaveBeenNthCalledWith(
      4,
      ['users'],
      [{ name: 'B' }, { name: 'splice' }, { name: 'double spliced' }],
      values
    )
  })

  it('detects changes to arrays that are v-modeled', async () => {
    const usersId = token()
    const wrapper = mount(
      {
        setup(_props, context) {
          const values = ref<{ users: any[] }>({
            users: [{ name: 'foo' }, { name: 'bar' }],
          })
          context.expose({ values })
          return { values }
        },
        template: `
        <FormKit type="group" v-model="values">
          <FormKit type="list" name="users" id="${usersId}" v-slot="{ value }">
            <FormKit type="group" v-if="value && value.length > 0">
              <FormKit name="name"/>
            </FormKit>
            <FormKit type="group" v-if="value && value.length > 1">
              <FormKit name="name" />
            </FormKit>
          </FormKit>
        </FormKit>`,
      },
      {
        global: { plugins: [[plugin, defaultConfig]] },
      }
    )
    const usersNode = getNode(usersId)!.use((node) => {
      if (node.type === 'group') {
        node.hook.input((value, next) => {
          if (value === undefined) node.destroy()
          return next(value || {})
        })
      }
    })
    await nextTick()
    expect(usersNode.value).toStrictEqual([{ name: 'foo' }, { name: 'bar' }])
    wrapper.vm.values.users.shift()
    await nextTick()
    expect(usersNode.value).toStrictEqual([{ name: 'bar' }])
    wrapper.vm.values.users[1] = { name: 'foo' }
    await nextTick()
    expect(usersNode.value).toStrictEqual([{ name: 'bar' }, { name: 'foo' }])
  })
})
Example #4
Source File: useInput.ts    From formkit with MIT License 4 votes vote down vote up
/**
 * A composable for creating a new FormKit node.
 * @param type - The type of node (input, group, list)
 * @param attrs - The FormKit "props" — which is really the attrs list.
 * @returns
 * @public
 */
export function useInput(
  props: FormKitComponentProps,
  context: SetupContext<any>,
  options: FormKitOptions = {}
): FormKitNode {
  /**
   * The configuration options, these are provided by either the plugin or by
   * explicit props.
   */
  const config = Object.assign({}, inject(optionsSymbol) || {}, options)

  /**
   * The current instance.
   */
  const instance = getCurrentInstance()

  /**
   * Extracts the listeners.
   */
  const listeners = onlyListeners(instance?.vnode.props)

  /**
   * Determines if the prop is v-modeled.
   */
  const isVModeled = props.modelValue !== undefined

  /**
   * Determines if the object being passed as a v-model is reactive.
   */
  // const isReactiveVModel = isVModeled && isReactive(props.modelValue)

  /**
   * Define the initial component
   */
  const value: any =
    props.modelValue !== undefined
      ? props.modelValue
      : cloneAny(context.attrs.value)

  /**
   * Creates the node's initial props from the context, props, and definition
   * @returns
   */
  function createInitialProps(): Record<string, any> {
    const initialProps: Record<string, any> = {
      ...nodeProps(props),
      ...listeners,
    }
    const attrs = except(nodeProps(context.attrs), pseudoProps)
    initialProps.attrs = attrs
    const propValues = only(nodeProps(context.attrs), pseudoProps)
    for (const propName in propValues) {
      initialProps[camel(propName)] = propValues[propName]
    }
    const classesProps = { props: {} }
    classesToNodeProps(classesProps as FormKitNode, props)
    Object.assign(initialProps, classesProps.props)
    if (typeof initialProps.type !== 'string') {
      initialProps.definition = initialProps.type
      delete initialProps.type
    }
    return initialProps
  }

  /**
   * Create the FormKitNode.
   */
  const initialProps = createInitialProps()

  /**
   * The parent node.
   */
  const parent = initialProps.ignore
    ? null
    : props.parent || inject(parentSymbol, null)

  const node = createNode(
    extend(
      config || {},
      {
        name: props.name || undefined,
        value,
        parent,
        plugins: (config.plugins || []).concat(props.plugins),
        config: props.config,
        props: initialProps,
        index: props.index,
      },
      false,
      true
    ) as Partial<FormKitOptions>
  ) as FormKitNode

  /**
   * If no definition has been assigned at this point — we're out!
   */
  if (!node.props.definition) error(600, node)

  /**
   * All props that are bound "late" (after node creation) — are added to a set
   * which is used to watch the context.attrs object.
   */
  const lateBoundProps = ref<Set<string | RegExp>>(
    new Set(node.props.definition.props || [])
  )

  /**
   * Any additional props added at a "later" time should also be part of the
   * late bound props.
   */
  node.on('added-props', ({ payload: lateProps }) => {
    if (Array.isArray(lateProps))
      lateProps.forEach((newProp) => lateBoundProps.value.add(newProp))
  })

  /**
   * These prop names must be assigned.
   */
  const pseudoPropNames = computed(() =>
    pseudoProps.concat([...lateBoundProps.value]).reduce((names, prop) => {
      if (typeof prop === 'string') {
        names.push(camel(prop))
        names.push(kebab(prop))
      } else {
        names.push(prop)
      }
      return names
    }, [] as Array<string | RegExp>)
  )

  /* Splits Classes object into discrete props for each key */
  watchEffect(() => classesToNodeProps(node, props))

  /**
   * The props object already has properties even if they start as "undefined"
   * so we can loop over them and individual watchEffect to prevent responding
   * inappropriately.
   */
  const passThrough = nodeProps(props)
  for (const prop in passThrough) {
    watch(
      () => props[prop as keyof FormKitComponentProps],
      () => {
        if (props[prop as keyof FormKitComponentProps] !== undefined) {
          node.props[prop] = props[prop as keyof FormKitComponentProps]
        }
      }
    )
  }

  /**
   * Watch "pseudoProp" attributes explicitly.
   */
  const attributeWatchers = new Set<WatchStopHandle>()
  const possibleProps = nodeProps(context.attrs)
  watchEffect(() => {
    watchAttributes(only(possibleProps, pseudoPropNames.value))
  })

  /**
   * Defines attributes that should be used as props.
   * @param attrProps - Attributes that should be used as props instead
   */
  function watchAttributes(attrProps: Record<string, any>) {
    attributeWatchers.forEach((stop) => {
      stop()
      attributeWatchers.delete(stop)
    })
    for (const prop in attrProps) {
      const camelName = camel(prop)
      attributeWatchers.add(
        watch(
          () => context.attrs[prop],
          () => {
            node.props[camelName] = context.attrs[prop]
          }
        )
      )
    }
  }

  /**
   * Watch and dynamically set attribute values, those values that are not
   * props and are not pseudoProps
   */
  watchEffect(() => {
    const attrs = except(nodeProps(context.attrs), pseudoPropNames.value)
    node.props.attrs = Object.assign({}, node.props.attrs || {}, attrs)
  })

  /**
   * Add any/all "prop" errors to the store.
   */
  watchEffect(() => {
    const messages = props.errors.map((error) =>
      createMessage({
        key: slugify(error),
        type: 'error',
        value: error,
        meta: { source: 'prop' },
      })
    )
    node.store.apply(
      messages,
      (message) => message.type === 'error' && message.meta.source === 'prop'
    )
  })

  /**
   * Add input errors.
   */
  if (node.type !== 'input') {
    const sourceKey = `${node.name}-prop`
    watchEffect(() => {
      const keys = Object.keys(props.inputErrors)
      const messages = keys.reduce((messages, key) => {
        let value = props.inputErrors[key]
        if (typeof value === 'string') value = [value]
        if (Array.isArray(value)) {
          messages[key] = value.map((error) =>
            createMessage({
              key: error,
              type: 'error',
              value: error,
              meta: { source: sourceKey },
            })
          )
        }
        return messages
      }, {} as Record<string, FormKitMessage[]>)
      node.store.apply(
        messages,
        (message) =>
          message.type === 'error' && message.meta.source === sourceKey
      )
    })
  }

  /**
   * Watch the config prop for any changes.
   */
  watchEffect(() => Object.assign(node.config, props.config))

  /**
   * Produce another parent object.
   */
  if (node.type !== 'input') {
    provide(parentSymbol, node)
  }

  let inputTimeout: number | undefined

  // eslint-disable-next-line @typescript-eslint/ban-types
  const mutex = new WeakSet<object>()

  /**
   * Explicitly watch the input value, and emit changes (lazy)
   */
  node.on('modelUpdated', () => {
    // Emit the values after commit
    context.emit('inputRaw', node.context?.value, node)
    clearTimeout(inputTimeout)
    inputTimeout = setTimeout(
      context.emit,
      20,
      'input',
      node.context?.value,
      node
    ) as unknown as number

    if (isVModeled && node.context) {
      const newValue = useRaw(node.context.value)
      if (isObject(newValue) && useRaw(props.modelValue) !== newValue) {
        // If this is an object that has been mutated inside FormKit core then
        // we know when it is emitted it will "return" in the watchVerbose so
        // we pro-actively add it to the mutex.
        mutex.add(newValue)
      }
      context.emit('update:modelValue', newValue)
    }
  })

  /**
   * Enabled support for v-model, using this for groups/lists is not recommended
   */
  if (isVModeled) {
    watchVerbose(toRef(props, 'modelValue'), (path, value): void | boolean => {
      const rawValue = useRaw(value)
      if (isObject(rawValue) && mutex.has(rawValue)) {
        return mutex.delete(rawValue)
      }
      if (!path.length) node.input(value, false)
      else node.at(path)?.input(value, false)
    })
  }

  /**
   * When this input shuts down, we need to "delete" the node too.
   */
  onUnmounted(() => node.destroy())

  return node
}