vue#getCurrentInstance TypeScript Examples

The following examples show how to use vue#getCurrentInstance. 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: util.ts    From vhook with MIT License 7 votes vote down vote up
export function tryOnMounted(cb: () => any): void {
  const instance = getCurrentInstance()
  if (instance) {
    if (instance?.isMounted) {
      cb()
    } else {
      onMounted(cb)
    }
  }
}
Example #2
Source File: useParent.ts    From elenext with MIT License 6 votes vote down vote up
useParent = <T>(key: InjectionKey<ParentProvide<T>>) => {
  const instance = getCurrentInstance() as ComponentInternalInstance
  const parent = inject(key, null)
  const index = computed(() => parent?.children.indexOf(instance))

  parent?.insert(instance)
  onUnmounted(() => {
    parent?.remove(instance)
  })

  return { parent, index }
}
Example #3
Source File: use-current-instance.ts    From gitmars with GNU General Public License v3.0 6 votes vote down vote up
export default function useCurrentInstance(props?: string | string[]) {
    if (typeof props === 'string') props = [props]
    const { appContext, proxy } =
        getCurrentInstance() as ComponentInternalInstance
    const globalProperties = appContext.config.globalProperties
    let style = {}
    props && props.includes('style') && (style = useCssModule() as any)
    return {
        globalProperties,
        proxy,
        style
    }
}
Example #4
Source File: util.ts    From vhook with MIT License 6 votes vote down vote up
export function tryOnUnmounted(cb: () => any): void {
  if (getCurrentInstance()) {
    onUnmounted(cb)
  }
}
Example #5
Source File: useDebounce.ts    From vhook with MIT License 6 votes vote down vote up
export function useDebounce<T>(value: T, delay = 200) {
  let timer: any
  const clear = () => {
    if (timer) {
      clearTimeout(timer)
    }
  }
  if (getCurrentInstance()) {
    onUnmounted(() => {
      clear()
    })
  }
  return customRef((tracker, trigger) => ({
    get() {
      tracker()
      return value
    },
    set(val: T) {
      clear()
      timer = setTimeout(() => {
        value = val
        timer = null
        trigger()
      }, delay)
    }
  }))
}
Example #6
Source File: composer.ts    From vue-i18n-next with MIT License 6 votes vote down vote up
function defineCoreMissingHandler(missing: MissingHandler): CoreMissingHandler {
  return ((
    ctx: CoreContext,
    locale: Locale,
    key: Path,
    type: string
  ): string | void => {
    return missing(locale, key, getCurrentInstance() || undefined, type)
  }) as CoreMissingHandler
}
Example #7
Source File: utils.ts    From vooks with MIT License 6 votes vote down vote up
export function hasInstance (): boolean {
  return getCurrentInstance() !== null
}
Example #8
Source File: inject.ts    From fect with MIT License 6 votes vote down vote up
useProvider = <T>(key: InjectionKey<Provider<T>>) => {
  const context = inject(key, null)
  if (context) {
    const instance = getCurrentInstance()!
    const { link, unlink, internalChildren, ...rest } = context
    link(instance)
    onUnmounted(() => unlink(instance))
    const idx = computed(() => internalChildren.indexOf(instance))
    return {
      context: rest,
      idx: idx.value
    }
  }
  return {
    context: null,
    idx: -1
  }
}
Example #9
Source File: utils.ts    From elements with MIT License 5 votes vote down vote up
defineContainer = <Props>(
  name: string,
  defineCustomElement: any,
  componentProps: string[] = [],
  modelProp?: string,
  modelUpdateEvent?: string,
  externalModelUpdateEvent?: string
) => {
  /**
  * Create a Vue component wrapper around a Web Component.
  * Note: The `props` here are not all properties on a component.
  * They refer to whatever properties are set on an instance of a component.
  */

  if (defineCustomElement !== undefined) {
    defineCustomElement();
  }

  const Container = defineComponent<Props & InputProps>((props: any, { attrs, slots, emit }) => {
    let modelPropValue = props[modelProp];
    const containerRef = ref<HTMLElement>();
    const classes = new Set(getComponentClasses(attrs.class));
    const onVnodeBeforeMount = (vnode: VNode) => {
      // Add a listener to tell Vue to update the v-model
      if (vnode.el) {
        const eventsNames = Array.isArray(modelUpdateEvent) ? modelUpdateEvent : [modelUpdateEvent];
        eventsNames.forEach((eventName: string) => {
          vnode.el.addEventListener(eventName.toLowerCase(), (e: Event) => {
            modelPropValue = (e as CustomEvent).detail;
            emit(UPDATE_VALUE_EVENT, modelPropValue);

            /**
             * We need to emit the change event here
             * rather than on the web component to ensure
             * that any v-model bindings have been updated.
             * Otherwise, the developer will listen on the
             * native web component, but the v-model will
             * not have been updated yet.
             */
            if (externalModelUpdateEvent) {
              emit(externalModelUpdateEvent, e);
            }
          });
        });
      }
    };

    const currentInstance = getCurrentInstance();
    const hasRouter = currentInstance?.appContext?.provides[NAV_MANAGER];
    const navManager: NavManager | undefined = hasRouter ? inject(NAV_MANAGER) : undefined;
    const handleRouterLink = (ev: Event) => {
      const { routerLink } = props;
      if (routerLink === EMPTY_PROP) return;

      if (navManager !== undefined) {
        let navigationPayload: any = { event: ev };
        for (const key in props) {
          const value = props[key];
          if (props.hasOwnProperty(key) && key.startsWith(ROUTER_PROP_PREFIX) && value !== EMPTY_PROP) {
            navigationPayload[key] = value;
          }
        }

        navManager.navigate(navigationPayload);
      } else {
        console.warn('Tried to navigate, but no router was found. Make sure you have mounted Vue Router.');
      }
    }

    return () => {
      modelPropValue = props[modelProp];

      getComponentClasses(attrs.class).forEach(value => {
        classes.add(value);
      });

      const oldClick = props.onClick;
      const handleClick = (ev: Event) => {
        if (oldClick !== undefined) {
          oldClick(ev);
        }
        if (!ev.defaultPrevented) {
          handleRouterLink(ev);
        }
      }

      let propsToAdd: any = {
        ref: containerRef,
        class: getElementClasses(containerRef, classes),
        onClick: handleClick,
        onVnodeBeforeMount: (modelUpdateEvent) ? onVnodeBeforeMount : undefined
      };

      /**
       * We can use Object.entries here
       * to avoid the hasOwnProperty check,
       * but that would require 2 iterations
       * where as this only requires 1.
       */
      for (const key in props) {
        const value = props[key];
        if (props.hasOwnProperty(key) && value !== EMPTY_PROP) {
          propsToAdd[key] = value;
        }
      }

      if (modelProp) {
        /**
         * If form value property was set using v-model
         * then we should use that value.
         * Otherwise, check to see if form value property
         * was set as a static value (i.e. no v-model).
         */
        if (props[MODEL_VALUE] !== EMPTY_PROP) {
          propsToAdd = {
            ...propsToAdd,
            [modelProp]: props[MODEL_VALUE]
          }
        } else if (modelPropValue !== EMPTY_PROP) {
          propsToAdd = {
            ...propsToAdd,
            [modelProp]: modelPropValue
          }
        }
      }

      return h(name, propsToAdd, slots.default && slots.default());
    }
  });

  Container.displayName = name;

  Container.props = {
    [ROUTER_LINK_VALUE]: DEFAULT_EMPTY_PROP
  };

  componentProps.forEach(componentProp => {
    Container.props[componentProp] = DEFAULT_EMPTY_PROP;
  });

  if (modelProp) {
    Container.props[MODEL_VALUE] = DEFAULT_EMPTY_PROP;
    Container.emits = [UPDATE_VALUE_EVENT, externalModelUpdateEvent];
  }

  return Container;
}
Example #10
Source File: use-expose.ts    From fect with MIT License 5 votes vote down vote up
useExpose = (merge: Record<string, any>) => {
  const instance = getCurrentInstance()
  if (instance) return Object.assign(instance.proxy, merge)
}
Example #11
Source File: composer.ts    From vue-i18n-next with MIT License 5 votes vote down vote up
getMetaInfo = /* #__PURE__*/ (): MetaInfo | null => {
  const instance = getCurrentInstance()
  let meta: any = null // eslint-disable-line @typescript-eslint/no-explicit-any
  return instance && (meta = getComponentOptions(instance)[DEVTOOLS_META])
    ? { [DEVTOOLS_META]: meta } // eslint-disable-line @typescript-eslint/no-explicit-any
    : null
}
Example #12
Source File: utils.ts    From vue3-datagrid with MIT License 5 votes vote down vote up
defineContainer = <Props>(name: string, [...componentProps]: string[] = [], componentOptions: ComponentOptions = {}) => {
  const { modelProp, modelUpdateEvent } = componentOptions;

  /**
  * Create a Vue component wrapper around a Web Component.
  * Note: The `props` here are not all properties on a component.
  * They refer to whatever properties are set on an instance of a component.
  */
  const Container = defineComponent<Props & InputProps>((props, { attrs, slots, emit }) => {
    let modelPropValue = (props as any)[modelProp];
    const containerRef = ref<HTMLElement>();
    const classes = new Set(getComponentClasses(attrs.class));
    const onVnodeBeforeMount = (vnode: VNode) => {
      // Add a listener to tell Vue to update the v-model
      if (vnode.el) {
        vnode.el.addEventListener(modelUpdateEvent.toLowerCase(), (e: Event) => {
          modelPropValue = (e?.target as any)[modelProp];
          emit(UPDATE_VALUE_EVENT, modelPropValue);

          /**
           * We need to emit the change event here
           * rather than on the web component to ensure
           * that any v-model bindings have been updated.
           * Otherwise, the developer will listen on the
           * native web component, but the v-model will
           * not have been updated yet.
           */
          emit(modelUpdateEvent, e);
          e.stopImmediatePropagation();
        });
      }
    };

    const currentInstance = getCurrentInstance();
    const hasRouter = currentInstance?.appContext?.provides[NAV_MANAGER];
    const navManager: NavManager | undefined = hasRouter ? inject(NAV_MANAGER) : undefined;
    const handleRouterLink = (ev: Event) => {
      const { routerLink } = props as any;
      if (!routerLink) return;

      const routerProps = Object.keys(props).filter(p => p.startsWith(ROUTER_PROP_REFIX));

      if (navManager !== undefined) {
        let navigationPayload: any = { event: ev };
        routerProps.forEach(prop => {
          navigationPayload[prop] = (props as any)[prop];
        });
        navManager.navigate(navigationPayload);
      } else {
        console.warn('Tried to navigate, but no router was found. Make sure you have mounted Vue Router.');
      }
    }

    return () => {
      getComponentClasses(attrs.class).forEach(value => {
        classes.add(value);
      });

      const oldClick = (props as any).onClick;
      const handleClick = (ev: Event) => {
        if (oldClick !== undefined) {
          oldClick(ev);
        }
        if (!ev.defaultPrevented) {
          handleRouterLink(ev);
        }
      }

      let propsToAdd = {
        ref: containerRef,
        class: getElementClasses(containerRef, classes),
        onClick: handleClick,
        onVnodeBeforeMount: (modelUpdateEvent) ? onVnodeBeforeMount : undefined
      };

      for (const [prop, value] of Object.entries(props)) {
        if (value !== EMPTY_SENTINEL) {
          propsToAdd[prop] = value;
        }
      }

      if (modelProp) {
        propsToAdd = {
          ...propsToAdd,
          [modelProp]: props.modelValue !== EMPTY_SENTINEL ? props.modelValue : modelPropValue
        }
      }

      return h(name, propsToAdd, slots.default && slots.default());
    }
  });

  Container.displayName = name;
  componentProps.push(ROUTER_LINK_VALUE);
  if (modelProp) {
    componentProps.push(MODEL_VALUE);
    Container.emits = [UPDATE_VALUE_EVENT, modelUpdateEvent];
  }

  Container.props = {};
  for (const componentProp of componentProps) {
	Container.props[componentProp] = { default: EMPTY_SENTINEL };
  }

  return Container;
}
Example #13
Source File: use-route.ts    From fect with MIT License 5 votes vote down vote up
useRoute = () => {
  const vm = getCurrentInstance()!.proxy as ComponentPublicInstance<RouteProps>
  return () => route(vm)
}
Example #14
Source File: FormKitSchema.ts    From formkit with MIT License 5 votes vote down vote up
FormKitSchema = defineComponent({
  name: 'FormKitSchema',
  props: {
    schema: {
      type: [Array, Object] as PropType<
        FormKitSchemaNode[] | FormKitSchemaCondition
      >,
      required: true,
    },
    data: {
      type: Object as PropType<Record<string, any>>,
      default: () => ({}),
    },
    library: {
      type: Object as PropType<FormKitComponentLibrary>,
      default: () => ({}),
    },
  },
  setup(props, context) {
    const instance = getCurrentInstance()
    let instanceKey = Symbol(String(i++))
    instanceScopes.set(instanceKey, [])
    let provider = parseSchema(props.library, props.schema)
    let render: RenderChildren
    let data: Record<string, any>
    // Re-parse the schema if it changes:
    watch(
      () => props.schema,
      (newSchema, oldSchema) => {
        instanceKey = Symbol(String(i++))
        provider = parseSchema(props.library, props.schema)
        render = createRenderFn(provider, data, instanceKey)
        if (newSchema === oldSchema) {
          // In this edge case, someone pushed/modified something in the schema
          // and we've successfully re-parsed, but since the schema is not
          // referenced in the render function it technically isnt a dependency
          // and we need to force a re-render since we swapped out the render
          // function completely.
          ;(instance?.proxy?.$forceUpdate as unknown as CallableFunction)()
        }
      },
      { deep: true }
    )

    // Watch the data object explicitly
    watchEffect(() => {
      data = Object.assign(reactive(props.data), {
        slots: context.slots,
      })
      render = createRenderFn(provider, data, instanceKey)
    })
    return () => render()
  },
})
Example #15
Source File: provider.ts    From fect with MIT License 5 votes vote down vote up
createProvider = <
  // eslint-disable-next-line
  T extends ComponentPublicInstance = ComponentPublicInstance<{}, any>,
  ProvideValue = never
>(
  key: InjectionKey<ProvideValue>
) => {
  const publicChildren: T[] = reactive([])
  const internalChildren: ComponentInternalInstance[] = reactive([])
  const parent = getCurrentInstance()!
  const provider = (value?: ProvideValue) => {
    const link = (child: ComponentInternalInstance) => {
      if (child.proxy) {
        internalChildren.push(child)
        publicChildren.push(child.proxy as T)
        sortChildren(parent, publicChildren, internalChildren)
      }
    }

    const unlink = (child: ComponentInternalInstance) => {
      const idx = internalChildren.indexOf(child)
      publicChildren.splice(idx, 1)
      internalChildren.splice(idx, 1)
    }
    provide(
      key,
      Object.assign(
        {
          link,
          unlink,
          children: publicChildren,
          internalChildren
        },
        value
      )
    )
  }
  return {
    children: publicChildren,
    provider
  }
}
Example #16
Source File: wc.test.ts    From vue-i18n-next with MIT License 5 votes vote down vote up
test('custom blocks', async () => {
  const i18n = createI18n<false>({
    legacy: false,
    locale: 'en',
    messages: {
      en: {
        hello: 'hello web components!'
      },
      ja: {
        hello: 'こんにちは Web コンポーネント!'
      }
    }
  })

  const Provider = defineCustomElement({
    setup() {
      provide(I18nInjectionKey, i18n)
      return () => h('my-child-block')
    }
  })
  customElements.define('my-provider-block', Provider)
  const Child = defineCustomElement({
    setup() {
      const instance = getCurrentInstance()
      if (instance == null) {
        throw new Error()
      }
      const options = instance.type as ComponentOptions
      options.__i18n = [
        {
          locale: 'en',
          resource: { foo: 'foo!' }
        },
        {
          locale: 'ja',
          resource: { foo: 'ふー!' }
        }
      ]
      const { t } = useI18n({
        inheritLocale: true,
        useScope: 'local'
      })
      return () => h('div', t('foo'))
    }
  })
  customElements.define('my-child-block', Child)

  container.innerHTML = `<my-provider-block></my-provider-block>`
  await nextTick()
  const provider = container.childNodes[0] as VueElement
  const child = provider.shadowRoot!.childNodes[0] as VueElement
  expect(child.shadowRoot!.innerHTML).toBe(`<div>foo!</div>`)

  i18n.global.locale.value = 'ja'
  await nextTick()
  expect(child.shadowRoot!.innerHTML).toBe(`<div>ふー!</div>`)
})
Example #17
Source File: component.ts    From vue3-gettext with MIT License 5 votes vote down vote up
Component = defineComponent({
  // eslint-disable-next-line vue/multi-word-component-names, vue/component-definition-name-casing
  name: "translate",
  props: {
    tag: {
      type: String,
      default: "span",
    },
    // Always use v-bind for dynamically binding the `translateN` prop to data on the parent,
    // i.e.: `:translate-n`.
    translateN: {
      type: Number,
      default: null,
    },
    translatePlural: {
      type: String,
      default: null,
    },
    translateContext: {
      type: String,
      default: null,
    },
    translateParams: {
      type: Object,
      default: null,
    },
    translateComment: {
      type: String,
      default: null,
    },
  },

  setup(props, context) {
    const isPlural = props.translateN !== undefined && props.translatePlural !== undefined;
    if (!isPlural && (props.translateN || props.translatePlural)) {
      throw new Error(
        `\`translate-n\` and \`translate-plural\` attributes must be used together: ${
          context.slots.default?.()[0]?.children
        }.`,
      );
    }

    const root = ref<HTMLElement>();

    const plugin = useGettext();
    const msgid = ref<string | null>(null);

    onMounted(() => {
      if (!msgid.value && root.value) {
        msgid.value = root.value.innerHTML.trim();
      }
    });

    const translation = computed(() => {
      const translatedMsg = translate(plugin).getTranslation(
        msgid.value!,
        props.translateN,
        props.translateContext,
        isPlural ? props.translatePlural : null,
        plugin.current,
      );

      return interpolate(plugin)(translatedMsg, props.translateParams, undefined, getCurrentInstance()?.parent);
    });

    // The text must be wraped inside a root HTML element, so we use a <span> by default.
    return () => {
      if (!msgid.value) {
        return h(props.tag, { ref: root }, context.slots.default ? context.slots.default() : "");
      }
      return h(props.tag, { ref: root, innerHTML: translation.value });
    };
  },
})
Example #18
Source File: i18n.test.ts    From vue-i18n-next with MIT License 4 votes vote down vote up
describe('merge i18n custom blocks to global scope', () => {
  test('composition mode', async () => {
    const i18n = createI18n({
      legacy: false,
      locale: 'ja',
      messages: {
        en: {
          hi: { hello: 'hello!' }
        }
      }
    })

    const App = defineComponent({
      setup() {
        const instance = getCurrentInstance()
        if (instance == null) {
          throw new Error()
        }
        const options = instance.type as ComponentOptions
        options.__i18nGlobal = [
          {
            locale: 'en',
            resource: {
              hi: { hi: 'hi!' },
              foo: 'foo!'
            }
          },
          {
            locale: 'ja',
            resource: { foo: 'ふー!' }
          }
        ]
        useI18n({
          useScope: 'global',
          messages: {
            ja: {
              hello: 'こんにちは!'
            }
          }
        })
        return {}
      },
      template: `<p>foo</p>`
    })
    await mount(App, i18n)

    expect(i18n.global.getLocaleMessage('en')).toEqual({
      hi: {
        hi: 'hi!',
        hello: 'hello!'
      },
      foo: 'foo!'
    })
    expect(i18n.global.getLocaleMessage('ja')).toEqual({
      hello: 'こんにちは!',
      foo: 'ふー!'
    })
  })

  test('legacy mode', async () => {
    const i18n = createI18n({
      legacy: true,
      locale: 'ja',
      messages: {
        en: {
          hi: { hello: 'hello!' }
        },
        ja: {
          hello: 'こんにちは!'
        }
      }
    })

    const App = defineComponent({
      __i18nGlobal: [
        {
          locale: 'en',
          resource: {
            hi: { hi: 'hi!' },
            foo: 'foo!'
          }
        },
        {
          locale: 'ja',
          resource: { foo: 'ふー!' }
        }
      ],
      template: `<p>foo</p>`
    })
    await mount(App, i18n)

    expect(i18n.global.getLocaleMessage('en')).toEqual({
      hi: {
        hi: 'hi!',
        hello: 'hello!'
      },
      foo: 'foo!'
    })
    expect(i18n.global.getLocaleMessage('ja')).toEqual({
      hello: 'こんにちは!',
      foo: 'ふー!'
    })
  })
})
Example #19
Source File: i18n.test.ts    From vue-i18n-next with MIT License 4 votes vote down vote up
describe('custom pluralization', () => {
  test('legacy', async () => {
    const i18n = createI18n({
      locale: 'ru',
      pluralizationRules: _pluralRules,
      messages: {
        ru: {
          car: '0 машин | {n} машина | {n} машины | {n} машин'
        }
      }
    })

    const App = defineComponent({
      template: `
        <p>{{ $tc('car', 1) }}</p>
        <p>{{ $tc('car', 2) }}</p>
        <p>{{ $tc('car', 4) }}</p>
        <p>{{ $tc('car', 12) }}</p>
        <p>{{ $tc('car', 21) }}</p>
      `
    })
    const { find } = await mount(App, i18n)
    await nextTick()
    expect(find('p:nth-child(1)')!.innerHTML).toEqual('1 машина')
    expect(find('p:nth-child(2)')!.innerHTML).toEqual('2 машины')
    expect(find('p:nth-child(3)')!.innerHTML).toEqual('4 машины')
    expect(find('p:nth-child(4)')!.innerHTML).toEqual('12 машин')
    expect(find('p:nth-child(5)')!.innerHTML).toEqual('21 машина')
  })

  test('legacy + custom block', async () => {
    const i18n = createI18n({
      locale: 'ru',
      pluralizationRules: _pluralRules
    })

    const App = defineComponent({
      __i18n: [
        {
          locale: 'ru',
          resource: {
            car: '0 машин | {n} машина | {n} машины | {n} машин'
          }
        }
      ],
      template: `
        <p>{{ $tc('car', 1) }}</p>
        <p>{{ $tc('car', 2) }}</p>
        <p>{{ $tc('car', 4) }}</p>
        <p>{{ $tc('car', 12) }}</p>
        <p>{{ $tc('car', 21) }}</p>
      `
    })
    const { find } = await mount(App, i18n)
    await nextTick()
    expect(find('p:nth-child(1)')!.innerHTML).toEqual('1 машина')
    expect(find('p:nth-child(2)')!.innerHTML).toEqual('2 машины')
    expect(find('p:nth-child(3)')!.innerHTML).toEqual('4 машины')
    expect(find('p:nth-child(4)')!.innerHTML).toEqual('12 машин')
    expect(find('p:nth-child(5)')!.innerHTML).toEqual('21 машина')
  })

  test('composition', async () => {
    const i18n = createI18n({
      legacy: false,
      locale: 'ru',
      pluralRules: _pluralRules,
      messages: {
        ru: {
          car: '0 машин | {n} машина | {n} машины | {n} машин'
        }
      }
    })

    const App = defineComponent({
      setup() {
        const { t } = useI18n()
        return { t }
      },
      template: `
        <p>{{ t('car', 1) }}</p>
        <p>{{ t('car', 2) }}</p>
        <p>{{ t('car', 4) }}</p>
        <p>{{ t('car', 12) }}</p>
        <p>{{ t('car', 21) }}</p>
      `
    })
    const { find } = await mount(App, i18n)
    await nextTick()
    expect(find('p:nth-child(1)')!.innerHTML).toEqual('1 машина')
    expect(find('p:nth-child(2)')!.innerHTML).toEqual('2 машины')
    expect(find('p:nth-child(3)')!.innerHTML).toEqual('4 машины')
    expect(find('p:nth-child(4)')!.innerHTML).toEqual('12 машин')
    expect(find('p:nth-child(5)')!.innerHTML).toEqual('21 машина')
  })

  test('composition + custom block', async () => {
    const i18n = createI18n({
      legacy: false,
      locale: 'ru'
    })

    const App = defineComponent({
      setup() {
        const instance = getCurrentInstance()
        if (instance == null) {
          throw new Error()
        }
        const options = instance.type as ComponentOptions
        options.__i18n = [
          {
            locale: 'ru',
            resource: {
              car: '0 машин | {n} машина | {n} машины | {n} машин'
            }
          }
        ]
        const { t } = useI18n({
          inheritLocale: true,
          useScope: 'local',
          pluralRules: _pluralRules
        })
        return { t }
      },
      template: `
        <p>{{ t('car', 1) }}</p>
        <p>{{ t('car', 2) }}</p>
        <p>{{ t('car', 4) }}</p>
        <p>{{ t('car', 12) }}</p>
        <p>{{ t('car', 21) }}</p>
      `
    })
    const { find } = await mount(App, i18n)
    await nextTick()
    expect(find('p:nth-child(1)')!.innerHTML).toEqual('1 машина')
    expect(find('p:nth-child(2)')!.innerHTML).toEqual('2 машины')
    expect(find('p:nth-child(3)')!.innerHTML).toEqual('4 машины')
    expect(find('p:nth-child(4)')!.innerHTML).toEqual('12 машин')
    expect(find('p:nth-child(5)')!.innerHTML).toEqual('21 машина')
  })
})
Example #20
Source File: i18n.test.ts    From vue-i18n-next with MIT License 4 votes vote down vote up
describe('useI18n', () => {
  let org: any // eslint-disable-line @typescript-eslint/no-explicit-any
  let spy: any // eslint-disable-line @typescript-eslint/no-explicit-any
  beforeEach(() => {
    org = console.warn
    spy = jest.fn()
    console.warn = spy
  })
  afterEach(() => {
    console.warn = org
  })

  test('basic', async () => {
    const i18n = createI18n({
      legacy: false,
      locale: 'ja',
      messages: {
        en: {
          hello: 'hello!'
        }
      }
    })

    let composer: unknown
    const App = defineComponent({
      setup() {
        composer = useI18n({
          inheritLocale: false,
          locale: 'en',
          messages: {
            en: {
              hello: 'hello!'
            }
          }
        })
        return {}
      },
      template: `<p>foo</p>`
    })
    await mount(App, i18n)

    expect(i18n.global).not.toEqual(composer)
    expect((composer as Composer).locale.value).toEqual('en')
  })

  test('global scope', async () => {
    const i18n = createI18n({
      legacy: false,
      locale: 'ja',
      messages: {
        en: {
          hello: 'hello!'
        }
      }
    })

    let composer: unknown
    const App = defineComponent({
      setup() {
        composer = useI18n({ useScope: 'global' })
        return {}
      },
      template: `<p>foo</p>`
    })
    await mount(App, i18n)

    expect(i18n.global).toEqual(composer)
    expect((composer as Composer).locale.value).toEqual('ja')
  })

  test('parent scope', async () => {
    const i18n = createI18n({
      legacy: false,
      locale: 'ja',
      messages: {
        en: {
          hello: 'hello!'
        }
      }
    })

    let leaf: unknown
    let parent: unknown
    const App = defineComponent({
      components: {
        Leaf: {
          template: '<p>local</p>',
          setup() {
            leaf = useI18n({ useScope: 'parent' })
            return {}
          }
        }
      },
      setup() {
        parent = useI18n({
          inheritLocale: false,
          locale: 'en',
          messages: {
            en: {
              hello: 'hello!'
            }
          }
        })
        return {}
      },
      template: `<div>parent</div><Leaf />`
    })
    await mount(App, i18n)

    expect(i18n.global).not.toEqual(leaf)
    expect(i18n.global).not.toEqual(parent)
    expect(parent).toEqual(leaf)
    expect((leaf as Composer).locale.value).toEqual('en')
  })

  test('not found parent composer with parent scope', async () => {
    const i18n = createI18n({
      legacy: false,
      locale: 'ja',
      messages: {
        en: {
          hello: 'hello!'
        }
      }
    })

    let composer: unknown
    const App = defineComponent({
      components: {
        Leaf: {
          template: '<p>local</p>',
          setup() {
            composer = useI18n({ useScope: 'parent' })
            return {}
          }
        }
      },
      setup() {
        return {}
      },
      template: `<div>parent</div><Leaf />`
    })
    await mount(App, i18n)

    expect(i18n.global).toEqual(composer)
    expect((composer as Composer).locale.value).toEqual('ja')
  })

  test('empty options', async () => {
    const i18n = createI18n({
      legacy: false,
      locale: 'ja',
      messages: {
        en: {
          hello: 'hello!'
        }
      }
    })

    let composer: unknown
    const App = defineComponent({
      setup() {
        composer = useI18n()
        return {}
      },
      template: `<p>foo</p>`
    })
    await mount(App, i18n)

    expect(i18n.global).toEqual(composer)
    expect((composer as Composer).locale.value).toEqual('ja')
  })

  test('empty options, when have i18n custom blocks', async () => {
    const i18n = createI18n({
      legacy: false,
      locale: 'ja',
      messages: {
        en: {
          hello: 'hello!'
        }
      }
    })

    let composer: unknown
    const App = defineComponent({
      setup() {
        const instance = getCurrentInstance()
        if (instance == null) {
          throw new Error()
        }
        const options = instance.type as ComponentOptions
        options.__i18n = [
          {
            locale: '',
            resource: { en: { hello: 'Hello,world!' } }
          },
          {
            locale: '',
            resource: { ja: { hello: 'こんにちは、世界!' } }
          }
        ] as any // eslint-disable-line @typescript-eslint/no-explicit-any
        composer = useI18n()
        return { t: (composer as Composer).t }
      },
      template: `<p>{{ t('hello', ['foo'], { locale: 'ja' }) }}</p>`
    })
    const { html } = await mount(App, i18n)

    expect(html()).toEqual('<p>こんにちは、世界!</p>')
    expect(i18n.global).not.toEqual(composer)
    expect((composer as Composer).locale.value).toEqual('ja')
  })

  test(errorMessages[I18nErrorCodes.MUST_BE_CALL_SETUP_TOP], async () => {
    let error = ''
    try {
      useI18n({})
    } catch (e) {
      error = e.message
    }
    expect(error).toEqual(errorMessages[I18nErrorCodes.MUST_BE_CALL_SETUP_TOP])
  })

  test(errorMessages[I18nErrorCodes.NOT_INSLALLED], async () => {
    const i18n = createI18n({
      legacy: false,
      locale: 'ja',
      messages: {
        en: {
          hello: 'hello!'
        }
      }
    })

    let error = ''
    const App = defineComponent({
      setup() {
        try {
          useI18n({})
        } catch (e) {
          error = e.message
        }
        return {}
      }
    })
    await mount(App, i18n, { installI18n: false })
    expect(error).toEqual(errorMessages[I18nErrorCodes.NOT_INSLALLED])
  })

  describe('On legacy', () => {
    describe('default', () => {
      test(
        errorMessages[I18nErrorCodes.NOT_AVAILABLE_IN_LEGACY_MODE],
        async () => {
          const i18n = createI18n({
            legacy: true,
            locale: 'ja',
            messages: {
              en: {
                hello: 'hello!'
              }
            }
          })

          let error = ''
          const App = defineComponent({
            setup() {
              try {
                useI18n({
                  locale: 'en',
                  messages: {
                    en: {
                      hello: 'hello!'
                    }
                  }
                })
              } catch (e) {
                error = e.message
              }
              return {}
            },
            template: `<p>foo</p>`
          })
          await mount(App, i18n)
          expect(error).toEqual(
            errorMessages[I18nErrorCodes.NOT_AVAILABLE_IN_LEGACY_MODE]
          )
        }
      )
    })

    describe('enable', () => {
      describe('t', () => {
        test('translation & locale changing', async () => {
          const i18n = createI18n({
            allowComposition: true,
            locale: 'ja',
            messages: {
              en: {
                hello: 'hello!'
              },
              ja: {
                hello: 'こんにちは!'
              }
            }
          })

          const App = defineComponent({
            setup() {
              const { locale, t } = useI18n()
              return { locale, t }
            },
            template: `<p>{{ t('hello') }}</p>`
          })
          const { html } = await mount(App, i18n)
          expect(html()).toEqual('<p>こんにちは!</p>')

          i18n.global.locale = 'en'
          await nextTick()
          expect(html()).toEqual('<p>hello!</p>')
        })

        test('local scope', async () => {
          const i18n = createI18n({
            allowComposition: true,
            locale: 'en',
            messages: {
              en: {
                hello: 'hello!'
              },
              ja: {}
            }
          })

          const App = defineComponent({
            setup() {
              const { locale, t } = useI18n({
                useScope: 'local',
                messages: {
                  en: {
                    world: 'world!'
                  },
                  ja: {
                    world: '世界!'
                  }
                }
              })
              return { locale, t }
            },
            i18n: {},
            template: `<p>{{ locale }}:{{ t('world') }}</p>`
          })
          const { html } = await mount(App, i18n)
          expect(html()).toEqual('<p>en:world!</p>')

          i18n.global.locale = 'ja'
          await nextTick()
          expect(html()).toEqual('<p>ja:世界!</p>')
        })

        test('use i18n option', async () => {
          const i18n = createI18n({
            allowComposition: true,
            locale: 'en',
            messages: {
              en: {
                hello: 'hello!'
              },
              ja: {}
            }
          })

          const App = defineComponent({
            setup() {
              const { locale, t } = useI18n({
                useScope: 'local'
              })
              return { locale, t }
            },
            i18n: {
              messages: {
                en: {
                  world: 'world!'
                },
                ja: {
                  world: '世界!'
                }
              }
            },
            template: `<p>{{ locale }}:{{ t('world') }}</p>`
          })
          const { html } = await mount(App, i18n)
          expect(html()).toEqual('<p>en:world!</p>')
        })

        test('use custom block', async () => {
          const i18n = createI18n({
            allowComposition: true,
            locale: 'ja',
            messages: {
              en: {
                hello: 'hello!'
              },
              ja: {}
            }
          })

          const App = defineComponent({
            setup() {
              const instance = getCurrentInstance()
              if (instance == null) {
                throw new Error()
              }
              const options = instance.type as ComponentOptions
              options.__i18n = [
                {
                  locale: 'ja',
                  resource: {
                    hello: 'こんにちは!'
                  }
                }
              ]
              const { locale, t } = useI18n({
                inheritLocale: true,
                useScope: 'local'
              })
              return { locale, t }
            },
            template: `<p>{{ locale }}:{{ t('hello') }}</p>`
          })
          const { html } = await mount(App, i18n)
          expect(html()).toEqual('<p>ja:こんにちは!</p>')
        })

        test('not defined i18n option in local scope', async () => {
          const i18n = createI18n({
            allowComposition: true,
            locale: 'en',
            messages: {
              en: {
                hello: 'hello!'
              }
            }
          })

          let error = ''
          const App = defineComponent({
            setup() {
              try {
                useI18n({ useScope: 'local' })
              } catch (e) {
                error = e.message
              }
              return {}
            }
          })
          await mount(App, i18n)
          expect(error).toEqual(
            errorMessages[
              I18nErrorCodes.MUST_DEFINE_I18N_OPTION_IN_ALLOW_COMPOSITION
            ]
          )
        })
      })
    })

    describe('d', () => {
      test('datetime formatting', async () => {
        const i18n = createI18n({
          allowComposition: true,
          locale: 'en-US',
          fallbackLocale: ['ja-JP'],
          datetimeFormats: {
            'en-US': {
              short: {
                year: 'numeric',
                month: '2-digit',
                day: '2-digit',
                hour: '2-digit',
                minute: '2-digit',
                timeZone: 'America/New_York'
              }
            },
            'ja-JP': {
              long: {
                year: 'numeric',
                month: '2-digit',
                day: '2-digit',
                hour: '2-digit',
                minute: '2-digit',
                second: '2-digit',
                timeZone: 'Asia/Tokyo'
              },
              short: {
                year: 'numeric',
                month: '2-digit',
                day: '2-digit',
                hour: '2-digit',
                minute: '2-digit',
                timeZone: 'Asia/Tokyo'
              }
            }
          }
        })

        const App = defineComponent({
          setup() {
            const { d } = useI18n()
            const dt = new Date(Date.UTC(2012, 11, 20, 3, 0, 0))
            return { d, dt }
          },
          template: `<p>{{ d(dt, 'long') }}</p>`
        })
        const { html } = await mount(App, i18n)
        expect(html()).toEqual('<p>2012/12/20 12:00:00</p>')
      })
    })

    describe('n', () => {
      test('number formatting', async () => {
        const i18n = createI18n({
          allowComposition: true,
          locale: 'en-US',
          fallbackLocale: ['ja-JP'],
          numberFormats: {
            'en-US': {
              currency: {
                style: 'currency',
                currency: 'USD',
                currencyDisplay: 'symbol'
              },
              decimal: {
                style: 'decimal',
                useGrouping: false
              }
            },
            'ja-JP': {
              currency: {
                style: 'currency',
                currency: 'JPY' /*, currencyDisplay: 'symbol'*/
              },
              numeric: {
                style: 'decimal',
                useGrouping: false
              },
              percent: {
                style: 'percent',
                useGrouping: false
              }
            }
          }
        })

        const App = defineComponent({
          setup() {
            const { n } = useI18n()
            const value = 0.99
            return { n, value }
          },
          template: `<p>{{ n(value, { key: 'percent' }) }}</p>`
        })
        const { html } = await mount(App, i18n)
        expect(html()).toEqual('<p>99%</p>')
      })
    })
  })

  test(errorMessages[I18nErrorCodes.NOT_INSLALLED_WITH_PROVIDE], async () => {
    const Provider = defineCustomElement({
      setup() {
        createI18n<false>({ legacy: false })
        return () => h('my-consumer')
      }
    })
    customElements.define('my-provider', Provider)

    let error = ''
    const Consumer = defineCustomElement({
      setup() {
        try {
          useI18n()
        } catch (e) {
          error = e.message
        }
        return () => h('div')
      }
    })
    customElements.define('my-consumer', Consumer)

    container.innerHTML = `<my-provider><my-provider>`
    await nextTick()

    expect(error).toEqual(
      errorMessages[I18nErrorCodes.NOT_INSLALLED_WITH_PROVIDE]
    )
  })
})
Example #21
Source File: next.ts    From vue-i18n-next with MIT License 4 votes vote down vote up
/**
 * Supports compatibility for legacy vue-i18n APIs
 * This mixin is used when we use [email protected] or later
 */
export function defineMixin(
  vuei18n: VueI18n,
  composer: Composer,
  i18n: I18nInternal
): ComponentOptions {
  return {
    beforeCreate(): void {
      const instance = getCurrentInstance()
      /* istanbul ignore if */
      if (!instance) {
        throw createI18nError(I18nErrorCodes.UNEXPECTED_ERROR)
      }

      const options = this.$options
      if (options.i18n) {
        const optionsI18n = options.i18n as VueI18nOptions &
          ComposerInternalOptions

        if (options.__i18n) {
          optionsI18n.__i18n = options.__i18n
        }
        optionsI18n.__root = composer
        if (this === this.$root) {
          this.$i18n = mergeToRoot(vuei18n, optionsI18n)
        } else {
          optionsI18n.__injectWithOption = true
          this.$i18n = createVueI18n(optionsI18n)
        }
      } else if (options.__i18n) {
        if (this === this.$root) {
          this.$i18n = mergeToRoot(vuei18n, options)
        } else {
          this.$i18n = createVueI18n({
            __i18n: (options as ComposerInternalOptions).__i18n,
            __injectWithOption: true,
            __root: composer
          } as VueI18nOptions)
        }
      } else {
        // set global
        this.$i18n = vuei18n
      }

      if (options.__i18nGlobal) {
        adjustI18nResources(composer, options as ComposerOptions, options)
      }

      ;(vuei18n as unknown as VueI18nInternal).__onComponentInstanceCreated(
        this.$i18n
      )
      i18n.__setInstance(instance, this.$i18n as VueI18n)

      // defines vue-i18n legacy APIs
      this.$t = (...args: unknown[]): TranslateResult => this.$i18n.t(...args)
      this.$rt = (...args: unknown[]): TranslateResult => this.$i18n.rt(...args)
      this.$tc = (...args: unknown[]): TranslateResult => this.$i18n.tc(...args)
      this.$te = (key: Path, locale?: Locale): boolean =>
        this.$i18n.te(key, locale)
      this.$d = (...args: unknown[]): DateTimeFormatResult =>
        this.$i18n.d(...args)
      this.$n = (...args: unknown[]): NumberFormatResult =>
        this.$i18n.n(...args)
      this.$tm = (key: Path): LocaleMessageValue<VueMessageType> | {} =>
        this.$i18n.tm(key)
    },

    mounted(): void {
      /* istanbul ignore if */
      if (
        (__DEV__ || __FEATURE_PROD_VUE_DEVTOOLS__) &&
        !__NODE_JS__ &&
        this.$el &&
        this.$i18n
      ) {
        this.$el.__VUE_I18N__ = this.$i18n.__composer
        const emitter: VueDevToolsEmitter = (this.__v_emitter =
          createEmitter<VueDevToolsEmitterEvents>())
        const _vueI18n = this.$i18n as unknown as VueI18nInternal
        _vueI18n.__enableEmitter && _vueI18n.__enableEmitter(emitter)
        emitter.on('*', addTimelineEvent)
      }
    },

    unmounted(): void {
      const instance = getCurrentInstance()
      /* istanbul ignore if */
      if (!instance) {
        throw createI18nError(I18nErrorCodes.UNEXPECTED_ERROR)
      }

      /* istanbul ignore if */
      if (
        (__DEV__ || __FEATURE_PROD_VUE_DEVTOOLS__) &&
        !__NODE_JS__ &&
        this.$el &&
        this.$el.__VUE_I18N__
      ) {
        if (this.__v_emitter) {
          this.__v_emitter.off('*', addTimelineEvent)
          delete this.__v_emitter
        }
        if (this.$i18n) {
          const _vueI18n = this.$i18n as unknown as VueI18nInternal
          _vueI18n.__disableEmitter && _vueI18n.__disableEmitter()
          delete this.$el.__VUE_I18N__
        }
      }

      delete this.$t
      delete this.$rt
      delete this.$tc
      delete this.$te
      delete this.$d
      delete this.$n
      delete this.$tm

      i18n.__deleteInstance(instance)
      delete this.$i18n
    }
  }
}
Example #22
Source File: i18n.ts    From vue-i18n-next with MIT License 4 votes vote down vote up
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function useI18n<
  Options extends UseI18nOptions = UseI18nOptions,
  Messages = NonNullable<Options['messages']>,
  DateTimeFormats = NonNullable<Options['datetimeFormats']>,
  NumberFormats = NonNullable<Options['numberFormats']>,
  OptionLocale = NonNullable<Options['locale']>
>(options: Options = {} as Options) {
  const instance = getCurrentInstance()
  if (instance == null) {
    throw createI18nError(I18nErrorCodes.MUST_BE_CALL_SETUP_TOP)
  }
  if (
    !__BRIDGE__ &&
    !instance.isCE &&
    instance.appContext.app != null &&
    !instance.appContext.app.__VUE_I18N_SYMBOL__
  ) {
    throw createI18nError(I18nErrorCodes.NOT_INSLALLED)
  }

  if (__BRIDGE__) {
    if (_legacyVueI18n == null) {
      throw createI18nError(I18nErrorCodes.NOT_INSLALLED)
    }
  }

  const i18n = getI18nInstance(instance)
  const global = getGlobalComposer(i18n)
  const componentOptions = getComponentOptions(instance)
  const scope = getScope(options, componentOptions)

  if (!__LITE__ && __FEATURE_LEGACY_API__) {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    if (i18n.mode === 'legacy' && !(options as any).__useComponent) {
      if (!i18n.allowComposition) {
        throw createI18nError(I18nErrorCodes.NOT_AVAILABLE_IN_LEGACY_MODE)
      }
      return useI18nForLegacy(instance, scope, global, options)
    }
  }

  if (scope === 'global') {
    adjustI18nResources(global, options, componentOptions)
    return global as Composer<
      Messages,
      DateTimeFormats,
      NumberFormats,
      OptionLocale
    >
  }

  if (scope === 'parent') {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    let composer = getComposer(i18n, instance, (options as any).__useComponent)
    if (composer == null) {
      if (__DEV__) {
        warn(getWarnMessage(I18nWarnCodes.NOT_FOUND_PARENT_SCOPE))
      }
      composer = global as unknown as Composer
    }
    return composer as Composer<
      Messages,
      DateTimeFormats,
      NumberFormats,
      OptionLocale
    >
  }

  const i18nInternal = i18n as unknown as I18nInternal
  let composer = i18nInternal.__getInstance(instance)
  if (composer == null) {
    const composerOptions = assign({}, options) as ComposerOptions &
      ComposerInternalOptions

    if ('__i18n' in componentOptions) {
      composerOptions.__i18n = componentOptions.__i18n
    }

    if (global) {
      composerOptions.__root = global
    }

    composer = createComposer(composerOptions, _legacyVueI18n) as Composer
    setupLifeCycle(i18nInternal, instance, composer)

    i18nInternal.__setInstance(instance, composer)
  }

  return composer as Composer<
    Messages,
    DateTimeFormats,
    NumberFormats,
    OptionLocale
  >
}
Example #23
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
}