vue#inject TypeScript Examples

The following examples show how to use vue#inject. 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: index.ts    From css-render with MIT License 6 votes vote down vote up
export function useSsrAdapter ():
| {
  adapter: typeof ssrAdapter
  context: CssrSsrContext
}
| undefined {
  if (isBrowser) return undefined
  const context = inject(ssrContextKey, null)
  if (context === null) return undefined
  return {
    adapter: ssrAdapter,
    context
  }
}
Example #2
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 #3
Source File: lib.ts    From hexon with GNU General Public License v3.0 6 votes vote down vote up
export function useTheme<N extends Exclude<keyof IGlobalTheme, "common">>(
  id: N
) {
  const { globalThemeRef } = inject(key)!
  const themeRef = computed(() => {
    const { common } = globalThemeRef.value
    return merge(
      {},
      common,
      globalThemeRef.value[id]?.self(common) as ReturnType<
        IGlobalTheme[N]["self"]
      >
    )
  })
  return themeRef
}
Example #4
Source File: useIcon.ts    From vue3-treeview with MIT License 6 votes vote down vote up
export default function useIcon(props: Record<string, unknown>): {} {
    const { isLeaf } = toRefs(props); 
    
    const state = inject<IState>("state")

    const config = state.config;

    const openedIcon = computed(() => {
        return config.value.openedIcon || defaultConfig.openedIcon;
    });

    const closedIcon = computed(() => {
        return config.value.closedIcon || defaultConfig.closedIcon;
    });

    const hasIcons = computed(() => {
        return !isNil(closedIcon.value) && !isNil(openedIcon.value);
    });

    const useIcons = computed(() => {
        return !isLeaf.value && hasIcons.value;
    });

    const fakeNodeStyle = computed(() => {
        return {
            width: `${defaultSize}px`,
            height: `${defaultSize}px`
        };
    });

    return {
        hasIcons,
        openedIcon,
        closedIcon,
        useIcons,
        fakeNodeStyle
    };
}
Example #5
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 #6
Source File: i18n.ts    From vue-i18n-next with MIT License 6 votes vote down vote up
function getI18nInstance(instance: ComponentInternalInstance): I18n {
  if (!__BRIDGE__) {
    const i18n = inject(
      !instance.isCE
        ? instance.appContext.app.__VUE_I18N_SYMBOL__!
        : I18nInjectionKey
    )
    /* istanbul ignore if */
    if (!i18n) {
      throw createI18nError(
        !instance.isCE
          ? I18nErrorCodes.UNEXPECTED_ERROR
          : I18nErrorCodes.NOT_INSLALLED_WITH_PROVIDE
      )
    }
    return i18n
  } else {
    const vm = instance.proxy
    /* istanbul ignore if */
    if (vm == null) {
      throw createI18nError(I18nErrorCodes.UNEXPECTED_ERROR)
    }
    const i18n = (vm as any)._i18nBridgeRoot // eslint-disable-line @typescript-eslint/no-explicit-any
    /* istanbul ignore if */
    if (!i18n) {
      throw createI18nError(I18nErrorCodes.NOT_INSLALLED)
    }
    return i18n as I18n
  }
}
Example #7
Source File: index.ts    From jz-gantt with MIT License 6 votes vote down vote up
useStore = () => {
  return {
    GtData: inject(Variables.provider.gtData) as GanttData,
    GtParam: inject(Variables.provider.gtParam) as ParamData,
    rootEmit: inject(Variables.provider.gtRootEmit) as Ref<any>,
    rootRef: inject(Variables.provider.gtRootRef) as Ref<HTMLDivElement>,
    ganttRef: inject(Variables.provider.gtGanttRef) as Ref<HTMLDivElement>,
    tableRef: inject(Variables.provider.gtTableRef) as Ref<HTMLDivElement>,
    isShowMask: inject(Variables.provider.gtIsShowMask) as Ref<boolean>,
    showDateList: inject(Variables.provider.gtShowDateList) as Date[],
    successBarList: inject(Variables.provider.gtSuccessBarList) as Row[],
    initGanttWidth: inject(Variables.provider.gtInitGanttWidth) as Ref<number>,
    columnSliderLineVisible: inject(
      Variables.provider.gtColumnSliderLineVisible
    ) as Ref<boolean>,
    columnSliderLineLeft: inject(
      Variables.provider.gtColumnSliderLineLeft
    ) as Ref<number>,
    columnDefaultLeft: inject(
      Variables.provider.gtColumnDefaultLeft
    ) as Ref<number>,
    successBarTimeout: inject(Variables.provider.gtSuccessBarTimeout) as number,
    isShowToast: inject(Variables.provider.gtIsShowToast) as Ref<boolean>,
    toastMessage: inject(Variables.provider.gtToastMessage) as Ref<string>,
    toastQueue: inject(Variables.provider.gtToastQueue) as any[],
    scrollTop: inject(Variables.provider.gtScrollTop) as Ref<number>,
    rootHeight: inject(Variables.provider.gtRootHeight) as Ref<number>,
    scrollBarHeight: inject(
      Variables.provider.gtScrollBarHeight
    ) as Ref<number>,
    stopClickEvent: inject(Variables.provider.gtStopClickEvent) as Ref<boolean>
  };
}
Example #8
Source File: utilities.ts    From vue3-gettext with MIT License 6 votes vote down vote up
useGettext = (): Language => {
  const gettext = inject(GetTextSymbol, null) as Language | null;
  if (!gettext) {
    throw new Error("Failed to inject gettext. Make sure vue3-gettext is set up properly.");
  }
  return gettext;
}
Example #9
Source File: index.ts    From css-render with MIT License 6 votes vote down vote up
function ssrAdapter (id: string, style: string): void {
  const ssrContext = inject(ssrContextKey, null)
  if (ssrContext === null) {
    console.error('[css-render/vue3-ssr]: no ssr context found.')
    return
  }
  const { styles, ids } = ssrContext
  // we need to impl other options to make it behaves the same as the client side
  if (ids.has(id)) return
  if (styles !== null) {
    ids.add(id)
    styles.push(createStyleString(id, style))
  }
}
Example #10
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 #11
Source File: lib.ts    From hexon with GNU General Public License v3.0 5 votes vote down vote up
useNotification = () => {
  const notification = inject(notificationProviderInjectionKey)
  if (!notification) {
    console.warn(`Did you forget to load notification plugin?`)
  }
  return notification!
}
Example #12
Source File: index.ts    From hexon with GNU General Public License v3.0 5 votes vote down vote up
export function useModal() {
  return inject(key)!
}
Example #13
Source File: index.ts    From hexon with GNU General Public License v3.0 5 votes vote down vote up
export function useLoading() {
  return inject(key)!
}
Example #14
Source File: lib.ts    From hexon with GNU General Public License v3.0 5 votes vote down vote up
export function useDialog() {
  return inject(key)!
}
Example #15
Source File: lib.ts    From hexon with GNU General Public License v3.0 5 votes vote down vote up
export function useThemeVars() {
  const { globalThemeRef } = inject(key)!
  const themeVarsRef = computed(() => {
    const { common } = globalThemeRef.value
    return { ...common }
  })
  return themeVarsRef
}
Example #16
Source File: lib.ts    From hexon with GNU General Public License v3.0 5 votes vote down vote up
export function useThemeController() {
  const { setTheme, setGlobal } = inject(key)!
  return { setTheme, setGlobal }
}
Example #17
Source File: components.ts    From vite-ssr with MIT License 5 votes vote down vote up
export function useContext() {
  return inject(CONTEXT_SYMBOL) as Context
}
Example #18
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 #19
Source File: form-context.ts    From fect with MIT License 5 votes vote down vote up
useFormStateContext = (): Pick<FormItemCotnext, 'behavior' | 'validate'> | undefined => {
  const parentState = inject<FormItemCotnext | null>(READONLY_FORM_ITEM_KEY, null)
  if (!parentState) return
  const { behavior, validate } = parentState
  return { behavior, validate }
}
Example #20
Source File: usePageContext.ts    From vite-plugin-ssr with MIT License 5 votes vote down vote up
function usePageContext() {
  const pageContext = inject(key)
  return pageContext
}
Example #21
Source File: usePageContext.ts    From vite-plugin-ssr with MIT License 5 votes vote down vote up
function usePageContext() {
  const pageContext = inject(key)
  if (!pageContext) throw new Error("setPageContext() not called in parent")
  return pageContext
}
Example #22
Source File: useApi.ts    From vue-cookie-next with MIT License 5 votes vote down vote up
export function useCookie(): IVueCookieNext {
  return inject<IVueCookieNext>('cookie')!
}
Example #23
Source File: useLevel.ts    From vue3-treeview with MIT License 5 votes vote down vote up
export default function useLevel(props: {parentId: string; depth: number}): {} {
    const state = inject<IState>("state")
    const config = state.config;
    const nodes = state.nodes;
    const depth = ref(props.depth);
    const parent = ref(props.parentId);

    const level = computed(() => {
      const res: INode[] = [];

      if (isNil(parent.value) && config.value.roots && depth.value === 0) {
          for (const id of config.value.roots) {
            addNode(id, res);
          }
    
          return res;
        }
    
      if (!isNil(parent.value)) {
        const node = nodes.value[parent.value];
  
        if (node && node.children && node.children.length > 0) {
          for (const id of node.children) {
            addNode(id, res);
          }
        }
  
        return res;
      }
      
        return [];
    });

    const addNode = ((id: string, a: INode[]) => {
      if (nodes.value[id]) {
        nodes.value[id].id = id;
        nodes.value[id].parent = parent.value;
        a.push(nodes.value[id]);
      }
    });

    const id = computed(() => {
        return new Date().valueOf();
    });

    const padding = computed(() => {
        if (depth.value === 0) {
            return 0;
        }
      
        if (isNil(config.value.padding)) {
            return defaultConfig.padding;
        }

        const p = toInteger(config.value.padding);

        return p >=0 ? p : 0;      
    });

    const style = computed(() => {
        return {
            "padding-left": `${padding.value}px`,
            "list-style": "none"
        };
    });

    return {
        id,
        level,
        padding,
        style
    };
}
Example #24
Source File: useCommon.ts    From vue3-treeview with MIT License 5 votes vote down vote up
export default function useCommon(props: INodeProps): IUseCommon {
    const { node } = toRefs(props);
    const state = inject<IState>("state");
    const config = state.config;
    const wrapper = ref<HTMLElement>(null);
    const focused = ref(false);

    const root = {
        emit: inject<(event: string, ...args: any[]) => void>("emitter")
    };

    ensureState(node.value);

    const hasNode = computed(() => {
        return !isNil(node);
    });

    const hasConfig = computed(() => {
        return !isNil(config.value);
    });

    const hasState = computed(() => {
        return hasNode.value && !isNil(node.value.state);
    });

    const disabled = computed(() => {
        return config.value.disabled || node.value.state.disabled;
    });

    const editable = computed(() => {
        return config.value.editable && 
        (!isNil(node.value.state.editable) ? node.value.state.editable : true) || defaultConfig.editable;
    });

    const editing = computed(() => {
        return editable.value && (config.value.editing === node.value.id);
    });

    const blur = ((e: MouseEvent) => {
        if (e.type === "blur") {
            const current = e.currentTarget as HTMLElement;
            const related = e.relatedTarget as HTMLElement;
    
            if (!current.contains(related)) {
                config.value.editing = null;
                focused.value = false;
                root.emit(nodeEvents.blur, e, node.value);
            }
        }
    });

    return {
        state,
        node, 
        config,
        hasNode,
        hasState,
        hasConfig,
        disabled,
        wrapper,
        editable,
        editing,
        focused,
        blur,
        root,
    };
}
Example #25
Source File: theme.ts    From elenext with MIT License 5 votes vote down vote up
export function useTheme() {
  const config: any = inject('EConfigProvider')
  const theme = computed<ThemeOption>(() => {
    const configTheme = config?.theme || {}
    return { ...configTheme, ...TEMEMS }
  })
  return { theme: theme.value }
}
Example #26
Source File: index.ts    From elenext with MIT License 5 votes vote down vote up
export function useGlobal() {
  return inject(ElGlobalConfigSymbol, null)
}
Example #27
Source File: provides.ts    From elenext with MIT License 5 votes vote down vote up
export function useElForm() {
  const from = inject(EL_FORM_INJECTKEY)
  return { from }
}
Example #28
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
}
Example #29
Source File: useCommon.spec.ts    From vue3-treeview with MIT License 4 votes vote down vote up
describe("test useCommon", () => {
    let props = null;

    let useTest = null;

    const config = {
        roots: ["id1"],
        disabled: false,
        editing: null
    };

    const storeProps = reactive({
        nodes: {},
        config
    });

    const v = require("vue");

    let state = null;

    v.inject = jest.fn((s) => {
        return s === "emitter" ? jest.fn() : {
            config: ref(config)
        }
    });

    beforeEach(() => {
        props = reactive({
            node: ref({
                id: "test"
            })
        });
        const id = createState(storeProps);
        state = states.get(id);
        useTest = useCommon(props);
    });

    it("Expect to have state", () => {
        expect(props.node.state).toBeDefined();
    });
    
    it("Expect to have node", () => {
        expect(useTest.hasNode.value).toBeTruthy();
    });

    it("Expect to have config", () => {
        expect(useTest.hasConfig.value).toBeTruthy();
    });

    it("Expect to have state", () => {
        expect(useTest.hasState.value).toBeTruthy();
    });

    it("Expect not to be disabled", () => {
        expect(useTest.disabled.value).toBeFalsy();
    });

    it("Expect not to be editable", () => {
        expect(useTest.editable.value).toBeFalsy();
    });

    it("Expect not to be edited", () => {
        expect(useTest.editing.value).toBeFalsy();
    });

    it("Expect to be editable", () => {
        state.config.value.editable = true;
        props.node.state.editable = true;
        expect(useTest.editable.value).toBeTruthy();
    });

    it("Expect to be editing", () => {
        state.config.value.editable = true;
        state.config.value.editing = "test";
        props.node.state.editable = true;
        expect(useTest.editable.value).toBeTruthy();
    });

    it("Expect to blur", () => {
        state.config.value.editing = "tata";
        const e = {
            currentTarget: document.createElement("div"),
            relatedTarget: document.createElement("div"),
            type: "blur"
        };
        useTest.blur(e);
        expect(state.config.value.editing).toBeNull();
    });
});