;
export type TRadioOptions = WithVariantProps<{
modelValue?: TRadioValue
} & InputHTMLAttributes & {
type?: 'radio'
} & Data>;
================================================
FILE: src/types/components/t-rich-select.ts
================================================
import {
Data, InputOptions, Measure, NormalizedOption, NormalizedOptions, TRichSelectClassesValidKeys, WithVariantPropsAndClassesList,
} from '@variantjs/core';
import { HTMLAttributes } from 'vue';
import { Placement, Options } from '@popperjs/core';
import { TSelectValue } from './t-select';
import { FetchOptionsFn, PreFetchOptionsFn } from '../misc';
export type MinimumInputLengthTextProp = ((minimumInputLength: number, query?: string) => string) | string;
export type TRichSelectOptions = WithVariantPropsAndClassesList<{
modelValue?: TSelectValue,
options?: InputOptions | NormalizedOption[] | NormalizedOptions,
multiple?: boolean
name?: string,
tags?: boolean
normalizeOptions?: boolean,
valueAttribute?: string,
textAttribute?: string,
delay?: number,
fetchOptions?: FetchOptionsFn,
prefetchOptions?: boolean | PreFetchOptionsFn,
minimumInputLength?: number,
minimumInputLengthText?: MinimumInputLengthTextProp,
minimumResultsForSearch?: number,
hideSearchBox?: boolean,
toggleOnFocus?: boolean,
toggleOnClick?: boolean,
closeOnSelect?: boolean,
selectOnClose?: boolean,
clearable?: boolean,
disabled?: boolean,
placeholder?: string,
searchBoxPlaceholder?: string,
noResultsText?: string,
searchingText?: string,
loadingClosedPlaceholder?: string,
loadingMoreResultsText?: string,
maxHeight?: Measure | null,
dropdownPlacement?: Placement,
dropdownPopperOptions?: Options,
teleport?: boolean,
teleportTo?: string | HTMLElement,
} & HTMLAttributes & Data, TRichSelectClassesValidKeys>;
================================================
FILE: src/types/components/t-select.ts
================================================
import {
Data, InputOptions, NormalizedOption, NormalizedOptions, WithVariantProps,
} from '@variantjs/core';
import { SelectHTMLAttributes } from 'vue';
import { Truthy } from '../misc';
// eslint-disable-next-line @typescript-eslint/ban-types
export type TSelectValue = string | number | boolean | undefined | null | Date | Function | symbol | TSelectValue[];
export type TSelectOptions = WithVariantProps<{
modelValue?: TSelectValue,
options?: InputOptions | NormalizedOption[] | NormalizedOptions,
multiple?: Truthy,
normalizeOptions?: boolean,
valueAttribute?: string
textAttribute?: string
} & SelectHTMLAttributes & Data>;
================================================
FILE: src/types/components/t-tag.ts
================================================
import { Data, WithVariantProps } from '@variantjs/core';
import { HTMLAttributes } from 'vue';
export type TTagOptions = WithVariantProps<{
tagName?: string
text?: string
} & HTMLAttributes & Data>;
================================================
FILE: src/types/components/t-textarea.ts
================================================
import { Data, WithVariantProps } from '@variantjs/core';
import { InputHTMLAttributes } from 'vue';
export type TTextareaValue = string | number | string[] | undefined;
export type TTextareaOptions = WithVariantProps<{
modelValue?: TTextareaValue,
} & InputHTMLAttributes & Data>;
================================================
FILE: src/types/components/t-toggle.ts
================================================
import { WithVariantPropsAndClassesList, TToggleClassesValidKeys, Data } from '@variantjs/core';
import { HTMLAttributes } from 'vue';
import { TCheckboxValue } from './t-checkbox';
export type TToggleValue = TCheckboxValue;
export type TToggleOptions = WithVariantPropsAndClassesList<{
name?: string,
modelValue?: TToggleValue,
value?: TToggleValue,
uncheckedValue?: TToggleValue,
checked?: boolean,
disabled?: boolean,
checkedPlaceholder?: string,
uncheckedPlaceholder?: string,
} & HTMLAttributes & Data, TToggleClassesValidKeys>;
================================================
FILE: src/types/helpers.ts
================================================
type ObjectWithProperties = Record;
type KeysOfType = { [P in keyof T]: T[P] extends TProp? P : never }[keyof T];
export { ObjectWithProperties, KeysOfType };
================================================
FILE: src/types/index.ts
================================================
export { VariantJSConfiguration, VariantJSProps, VariantJSWithClassesListProps } from './variantCore';
export { TInputValue, TInputOptions } from './components/t-input';
export { TTextareaValue, TTextareaOptions } from './components/t-textarea';
export { TRadioValue, TRadioOptions } from './components/t-radio';
export { TCheckboxValue, TCheckboxOptions } from './components/t-checkbox';
export { TSelectValue, TSelectOptions } from './components/t-select';
export { TButtonOptions } from './components/t-button';
export { TCardOptions } from './components/t-card';
export { TModalOptions } from './components/t-modal';
export { TDialogOptions } from './components/t-dialog';
export { TTagOptions } from './components/t-tag';
export { TAlertOptions } from './components/t-alert';
export { TToggleValue, TToggleOptions } from './components/t-toggle';
export { TDropdownOptions } from './components/t-dropdown';
export { TRichSelectOptions, MinimumInputLengthTextProp } from './components/t-rich-select';
export { TInputGroupOptions, TInputGroupValidChilElementsKeys } from './components/t-input-group';
export {
Truthy, IconProp, FetchOptionsFn, FetchedOptions, PreFetchOptionsFn, PromiseRejectFn,
} from './misc';
export { ObjectWithProperties, KeysOfType } from './helpers';
export { EmitterEvents, EmitterFunction, EmitterInterface } from './utils';
export { VueRouteAriaCurrentValue, VueRouteRouteLocationRaw } from './vueRouter';
================================================
FILE: src/types/misc.ts
================================================
import { Data, InputOptions } from '@variantjs/core';
type Truthy = boolean | string;
// eslint-disable-next-line @typescript-eslint/ban-types
type IconProp = Element | string | (Data & { render?: Function });
type FetchedOptions = Promise<{
results: InputOptions;
hasMorePages?: boolean;
}>;
type FetchOptionsFn = (query?: string, nextPage?: number) => FetchedOptions;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type PreFetchOptionsFn = (currentValue?: any) => Promise;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type PromiseRejectFn = ((reason?: any) => void);
export {
Truthy, IconProp, FetchOptionsFn, FetchedOptions, PromiseRejectFn, PreFetchOptionsFn,
};
================================================
FILE: src/types/utils.ts
================================================
/* eslint-disable @typescript-eslint/no-explicit-any */
type EmitterFunction = (...args : any[]) => void;
type EmitterEvents = {
[key: string]: EmitterFunction[]
};
interface EmitterInterface {
on(name: keyof EmitterEvents, callback: EmitterFunction): void;
once(name: keyof EmitterEvents, callback: EmitterFunction): void;
emit(name: keyof EmitterEvents, ...args: any[]): void;
emit(name: keyof EmitterEvents, ...args: any[]): void;
off(name: keyof EmitterEvents, callback: EmitterFunction): void;
}
export { EmitterEvents, EmitterFunction, EmitterInterface };
================================================
FILE: src/types/variantCore.ts
================================================
import {
CSSRawClassesList,
CSSClass, Variants, VariantsWithClassesList, WithVariantProps, WithVariantPropsAndClassesList, Data,
} from '@variantjs/core';
import { ComponentPropsOptions, PropType } from 'vue';
import { TButtonOptions } from './components/t-button';
import { TCardOptions } from './components/t-card';
import { TCheckboxOptions } from './components/t-checkbox';
import { TInputOptions } from './components/t-input';
import { TInputGroupOptions } from './components/t-input-group';
import { TRadioOptions } from './components/t-radio';
import { TSelectOptions } from './components/t-select';
import { TTagOptions } from './components/t-tag';
import { TTextareaOptions } from './components/t-textarea';
type VariantJSProps = {
classes?: CSSClass;
fixedClasses?: CSSClass;
variants?: Variants;
variant?: string;
class?: string;
}, PropsOptions extends Readonly = {
classes: {
type: PropType;
default: undefined;
},
fixedClasses: {
type: PropType;
default: undefined;
},
variants: {
type: PropType>;
default: undefined;
},
variant: {
type:PropType;
default: undefined;
},
}> = PropsOptions & {
classes: {
type: PropType;
default: undefined;
},
fixedClasses: {
type: PropType;
default: undefined;
},
variants: {
type: PropType>;
default: undefined;
},
variant: {
type:PropType;
default: undefined;
},
};
type VariantJSWithClassesListProps<
ClassesKeys extends string,
ComponentOptions extends WithVariantPropsAndClassesList = WithVariantPropsAndClassesList,
PropsOptions extends Readonly = {
classes: {
type: PropType>;
default: undefined;
},
fixedClasses: {
type: PropType>;
default: undefined;
},
variants: {
type: PropType>;
default: undefined;
},
variant: {
type:PropType;
default: undefined;
},
}> = PropsOptions & {
classes: {
type: PropType>;
default: undefined;
},
fixedClasses: {
type: PropType>;
default: undefined;
},
variants: {
type: PropType>;
default: undefined;
},
variant: {
type:PropType;
default: undefined;
},
};
type VariantJSConfiguration = {
TInput?: TInputOptions
TSelect?: TSelectOptions
TRadio?: TRadioOptions
TCheckbox?: TCheckboxOptions
TButton?: TButtonOptions
TTextarea?: TTextareaOptions
TTag?: TTagOptions
TCard?: TCardOptions
TInputGroup?: TInputGroupOptions,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any
};
export { VariantJSConfiguration, VariantJSProps, VariantJSWithClassesListProps };
================================================
FILE: src/types/vueRouter.ts
================================================
// Copied from the types on vue/node_modules/vue-router/dist/vue-router.d.ts
// so we dont need to add any extra dependency
type RouteParamValue = string;
type RouteParamValueRaw = RouteParamValue | number;
type RouteParamsRaw = Record;
type RouteRecordName = string | symbol;
interface LocationAsRelativeRaw {
name?: RouteRecordName;
params?: RouteParamsRaw;
}
type HistoryStateArray = Array;
type HistoryStateValue = string | number | boolean | null | undefined | HistoryState | HistoryStateArray;
/**
* Allowed HTML history.state
*/
interface HistoryState {
[x: number]: HistoryStateValue;
[x: string]: HistoryStateValue;
}
interface RouteLocationOptions {
/**
* Replace the entry in the history instead of pushing a new entry
*/
replace?: boolean;
/**
* Triggers the navigation even if the location is the same as the current one
*/
force?: boolean;
/**
* State to save using the History API. This cannot contain any reactive
* values and some primitives like Symbols are forbidden. More info at
* https://developer.mozilla.org/en-US/docs/Web/API/History/state
*/
state?: HistoryState;
}
interface LocationAsPath {
path: string;
}
type LocationQueryValue = string | null;
type LocationQueryValueRaw = LocationQueryValue | number | undefined;
type LocationQueryRaw = Record;
interface RouteQueryAndHash {
query?: LocationQueryRaw;
hash?: string;
}
type VueRouteRouteLocationRaw = string | (RouteQueryAndHash & LocationAsPath & RouteLocationOptions) | (RouteQueryAndHash & LocationAsRelativeRaw & RouteLocationOptions);
type VueRouteAriaCurrentValue = 'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false' | undefined;
export { VueRouteAriaCurrentValue, VueRouteRouteLocationRaw };
================================================
FILE: src/use/useActivableOption.ts
================================================
import { isEqual, NormalizedOption, normalizedOptionIsDisabled } from '@variantjs/core';
import {
computed, ComputedRef, Ref, ref, watch,
} from 'vue';
export default function useActivableOption(
options: ComputedRef,
localValue: Ref,
): {
activeOption: Ref,
initActiveOption: () => void,
optionIsActive: (option: NormalizedOption) => boolean,
setActiveOption: (option: NormalizedOption) => void,
setNextOptionActive: () => void,
setPrevOptionActive: () => void,
} {
const getActiveOption = (): NormalizedOption | null => {
const selectedOption = options.value.find((option: NormalizedOption) => isEqual(option.value, localValue.value) && !normalizedOptionIsDisabled(option));
if (selectedOption !== undefined) {
return selectedOption;
}
if (options.value.length > 0) {
return options.value.find((option) => !normalizedOptionIsDisabled(option)) || null;
}
return null;
};
const activeOption = ref(getActiveOption());
const activeOptionIndex = computed((): number => {
if (activeOption.value === null) {
return 0;
}
const index = options.value.findIndex((option) => isEqual(option.value, (activeOption.value as NormalizedOption).value));
return index < 0 ? 0 : index;
});
const optionIsActive = (option: NormalizedOption): boolean => (activeOption.value === null ? false : isEqual(activeOption.value.value, option.value));
const setActiveOption = (option: NormalizedOption): void => {
activeOption.value = option;
};
const setNextOptionActive = (): void => {
let newActiveOption: NormalizedOption | undefined;
let nextIndex = activeOptionIndex.value + 1;
while (nextIndex < options.value.length && newActiveOption === undefined) {
const option = options.value[nextIndex];
const optionIsDisabled = normalizedOptionIsDisabled(option);
if (!optionIsDisabled) {
newActiveOption = option;
}
nextIndex += 1;
}
if (newActiveOption) {
setActiveOption(newActiveOption);
}
};
const setPrevOptionActive = (): void => {
let newActiveOption: NormalizedOption | undefined;
let nextIndex = activeOptionIndex.value - 1;
while (nextIndex >= 0 && newActiveOption === undefined) {
const option = options.value[nextIndex];
const optionIsDisabled = normalizedOptionIsDisabled(option);
if (!optionIsDisabled) {
newActiveOption = option;
}
nextIndex -= 1;
}
if (newActiveOption) {
setActiveOption(newActiveOption);
}
};
const initActiveOption = (): void => {
activeOption.value = getActiveOption();
};
watch(options, (newOptions: NormalizedOption[], oldOptions: NormalizedOption[]) => {
const firstNewOption = newOptions.find((o) => !oldOptions.includes(o) && !normalizedOptionIsDisabled(o));
if (firstNewOption) {
setActiveOption(firstNewOption);
} else {
initActiveOption();
}
});
return {
activeOption,
initActiveOption,
optionIsActive,
setActiveOption,
setNextOptionActive,
setPrevOptionActive,
};
}
================================================
FILE: src/use/useConfiguration.ts
================================================
import {
computed, inject, camelize, getCurrentInstance, ComponentInternalInstance, ComputedRef, watch, reactive,
} from 'vue';
import {
Data, get, isEqual, isPrimitive, parseVariant, pick,
} from '@variantjs/core';
import { VariantJSConfiguration } from '../types';
export const extractDefinedProps = (vm: ComponentInternalInstance): string[] => {
const validProps = Object.keys(vm.props);
const definedProps = Object.keys(vm.vnode.props || {})
.map((propName) => camelize(propName))
.filter((propName) => validProps.includes(propName) && propName !== 'modelValue');
return definedProps;
};
export function useAttributes(configuration: ComponentOptions): Data {
const vm = getCurrentInstance()!;
const computedAttributes: ComputedRef = computed(():Data => {
const availableProps = Object.keys(vm.props);
return pick(configuration, (value, key) => isPrimitive(value) && !availableProps.includes(String(key)));
});
const attributes = reactive(computedAttributes.value);
watch(computedAttributes, (newValue) => {
Object.keys(newValue).forEach((key) => {
if (!isEqual(attributes[key], newValue[key])) {
attributes[key] = newValue[key];
}
});
});
return attributes;
}
export function useConfigurationParts(): {
componentGlobalConfiguration?: ComponentOptions
propsValues: ComputedRef
} {
const vm = getCurrentInstance()!;
const variantGlobalConfiguration = inject('configuration', {});
const componentGlobalConfiguration = get(variantGlobalConfiguration, vm?.type.name as keyof VariantJSConfiguration, {});
const propsValues = computed(() => {
const values: Data = {};
extractDefinedProps(vm).forEach((attributeName) => {
const normalizedAttribute = camelize(attributeName);
values[normalizedAttribute] = vm.props[normalizedAttribute];
});
return values;
});
return {
componentGlobalConfiguration,
propsValues: propsValues as ComputedRef,
};
}
export default function useConfiguration(defaultConfiguration: ComponentOptions): {
configuration: ComponentOptions,
attributes: Data,
} {
const vm = getCurrentInstance()!;
const { propsValues, componentGlobalConfiguration } = useConfigurationParts();
const computedConfiguration = computed(() => {
const props = { ...vm.props };
delete props.modelValue;
return {
...props,
...parseVariant(
propsValues.value,
componentGlobalConfiguration,
defaultConfiguration,
),
};
});
const configuration = reactive(computedConfiguration.value);
watch(computedConfiguration, (newValue) => {
Object.keys(newValue).forEach((key) => {
configuration[key] = newValue[key];
});
});
const attributes = useAttributes(configuration);
return {
configuration: configuration as ComponentOptions,
attributes,
};
}
================================================
FILE: src/use/useConfigurationWithClassesList.ts
================================================
import {
computed, getCurrentInstance, reactive, watch,
} from 'vue';
import { Data, parseVariantWithClassesList } from '@variantjs/core';
import { useAttributes, useConfigurationParts } from './useConfiguration';
export default function useConfigurationWithClassesList(defaultConfiguration: ComponentOptions, classesListKeys: string[]): {
configuration: ComponentOptions,
attributes: Data,
} {
const vm = getCurrentInstance()!;
const { propsValues, componentGlobalConfiguration } = useConfigurationParts();
const computedConfiguration = computed(() => ({
...vm.props,
...parseVariantWithClassesList(
propsValues.value,
classesListKeys,
componentGlobalConfiguration,
defaultConfiguration,
),
}));
const configuration = reactive(computedConfiguration.value);
watch(computedConfiguration, (newValue) => {
Object.keys(newValue).forEach((key) => {
configuration[key] = newValue[key];
});
});
const attributes = useAttributes(configuration);
return {
configuration: configuration as ComponentOptions,
attributes,
};
}
================================================
FILE: src/use/useFetchsOptions.ts
================================================
import {
debounce, filterOptions, flattenOptions, InputOptions, NormalizedOption, NormalizedOptions, normalizeOptions,
} from '@variantjs/core';
import {
computed, Ref, ref, ComputedRef, getCurrentInstance, watch,
} from 'vue';
import {
FetchedOptions, FetchOptionsFn, MinimumInputLengthTextProp, PreFetchOptionsFn,
} from '../types';
export default function useFetchsOptions(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
localValue: Ref,
options: Ref,
textAttribute: Ref,
valueAttribute: Ref,
normalize: Ref,
searchQuery: Ref,
fetchFn: Ref,
prefetchFn: Ref,
fetchDelay: Ref,
fetchMinimumInputLength: Ref,
fetchMinimumInputLengthText: Ref,
): {
normalizedOptions: ComputedRef
flattenedOptions: ComputedRef
fetchsOptions: ComputedRef,
needsMoreCharsToFetch: ComputedRef,
needsMoreCharsMessage: ComputedRef,
fetchingOptions: Ref,
fetchingMoreOptions: Ref,
fetchedOptionsHaveMorePages: Ref,
optionsWereFetched: Ref,
fetchOptions: () => void,
prefetchOptions: () => void,
fetchMoreOptions: () => void,
fetchOptionsCancel: () => void,
} {
const { emit } = getCurrentInstance()!;
const getNormalizedOptions = (rawOptions: InputOptions): NormalizedOptions => (normalize.value
? normalizeOptions(rawOptions, textAttribute.value, valueAttribute.value)
: rawOptions as NormalizedOptions);
const fetchedOptions = ref(getNormalizedOptions(options.value || []));
watch(options, () => {
fetchedOptions.value = getNormalizedOptions(options.value || []);
});
const optionsWereFetched = ref(false);
const normalizedOptions = computed(() => {
if (typeof fetchFn.value !== 'function' && typeof prefetchFn.value !== 'function') {
const normalized = getNormalizedOptions(options.value || []);
if (searchQuery.value) {
return filterOptions(normalized, searchQuery.value);
}
return normalized;
}
return fetchedOptions.value;
});
const flattenedOptions = computed(() => flattenOptions(normalizedOptions.value));
const fetchsOptions = computed(() => fetchFn.value !== undefined);
const needsMoreCharsToFetch = computed(() => {
if (!fetchsOptions.value) {
return false;
}
if (!fetchMinimumInputLength.value) {
return false;
}
return !searchQuery.value || searchQuery.value.length < fetchMinimumInputLength.value;
});
const fetchNextPage = ref(undefined);
const fetchingOptions = ref(false);
const fetchingMoreOptions = ref(false);
const fetchedOptionsHaveMorePages = computed(() => fetchNextPage.value !== undefined);
const fetchOptionsFn = ([nextPage]: [number | undefined]): void => {
fetchFn.value!(searchQuery.value, nextPage)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.then((response: FetchedOptions | any) => {
if (typeof response === 'object' && Object.prototype.hasOwnProperty.call(response, 'results')) {
const {
results,
hasMorePages,
} = response;
if (!Array.isArray(results) && results !== undefined && typeof results !== 'object') {
throw new Error(`Response.results must be an array or object, got ${typeof results}`);
}
if (nextPage !== undefined && nextPage >= 2) {
fetchedOptions.value = fetchedOptions.value.concat(getNormalizedOptions(results));
} else {
fetchedOptions.value = getNormalizedOptions(results);
}
if (hasMorePages) {
fetchNextPage.value = fetchNextPage.value === undefined ? 2 : fetchNextPage.value + 1;
} else {
fetchNextPage.value = undefined;
}
} else {
throw new Error('Options response must be an object with `results` property.');
}
optionsWereFetched.value = true;
emit('fetch-options-success', response);
}).catch((error) => {
emit('fetch-options-error', error);
}).then(() => {
fetchingOptions.value = false;
fetchingMoreOptions.value = false;
});
};
const debouncedFetchOptions = debounce(fetchOptionsFn, fetchDelay.value);
const fetchOptionsCancel = () => {
debouncedFetchOptions.cancel();
fetchingOptions.value = false;
fetchingMoreOptions.value = false;
};
const fetchOptions = (nextPage?: number) => {
if (!fetchsOptions.value) {
fetchOptionsCancel();
return;
}
if (nextPage !== undefined && nextPage >= 2) {
fetchingMoreOptions.value = true;
} else {
fetchingOptions.value = true;
}
debouncedFetchOptions(nextPage);
};
const fetchMoreOptions = () => {
fetchOptions(fetchNextPage.value);
};
watch(searchQuery, () => {
if (!fetchsOptions.value || needsMoreCharsToFetch.value) {
return;
}
optionsWereFetched.value = false;
fetchOptions();
});
const needsMoreCharsMessage = computed((): string => {
if (typeof fetchMinimumInputLengthText.value === 'string') {
return fetchMinimumInputLengthText.value;
}
return fetchMinimumInputLengthText.value(fetchMinimumInputLength.value!, searchQuery.value);
});
const prefetchOptions = (): void => {
if (typeof prefetchFn.value !== 'function') {
fetchOptions();
return;
}
fetchingOptions.value = true;
prefetchFn.value!(localValue.value)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.then((results: InputOptions | any) => {
if (!Array.isArray(results) && results !== undefined && typeof results !== 'object') {
throw new Error(`Response must be an array or object, got ${typeof results}`);
}
fetchedOptions.value = getNormalizedOptions(results);
optionsWereFetched.value = true;
emit('fetch-options-success', results);
}).catch((error) => {
emit('fetch-options-error', error);
}).then(() => {
fetchingOptions.value = false;
});
};
return {
normalizedOptions,
flattenedOptions,
fetchsOptions,
needsMoreCharsToFetch,
needsMoreCharsMessage,
fetchingOptions,
fetchingMoreOptions,
fetchedOptionsHaveMorePages,
optionsWereFetched,
fetchOptions,
prefetchOptions,
fetchMoreOptions,
fetchOptionsCancel,
};
}
================================================
FILE: src/use/useInjectsClassesList.ts
================================================
import { CSSClassesList } from '@variantjs/core';
import { ComputedRef, computed } from 'vue';
import useInjectsConfiguration from './useInjectsConfiguration';
export default function useInjectsClassesList(): ComputedRef {
const configuration = useInjectsConfiguration();
return computed((): CSSClassesList => configuration.classesList || {});
}
================================================
FILE: src/use/useInjectsClassesListClass.ts
================================================
import { CSSClass, get } from '@variantjs/core';
import { ComputedRef, computed } from 'vue';
import useInjectsConfiguration from './useInjectsConfiguration';
export default function useInjectsClassesListClass(property: string): ComputedRef {
const configuration = useInjectsConfiguration();
return computed((): CSSClass => get(configuration.classesList, property, ''));
}
================================================
FILE: src/use/useInjectsConfiguration.ts
================================================
import { Data, WithVariantPropsAndClassesList } from '@variantjs/core';
import { inject } from 'vue';
export default function useInjectsConfiguration>(): P {
return inject
('configuration', {} as P);
}
================================================
FILE: src/use/useMulipleableVModel.ts
================================================
import { Data } from '@variantjs/core';
import {
computed, ref, getCurrentInstance, Ref, watch,
} from 'vue';
export default function useMulipleableVModel
(
props: P,
key: K,
configuration?: C,
): {
localValue: Ref
;
clearValue: () => void
} {
const vm = getCurrentInstance();
const isMultiple = computed((): boolean => (configuration === undefined ? false : configuration.multiple !== null && configuration.multiple !== undefined && configuration.multiple !== false));
const getDefaultValue = (): P[K] => {
if (isMultiple.value) {
return [] as P[K];
}
return undefined as P[K];
};
const initialValue = props[key];
const localValue = ref(initialValue === undefined ? getDefaultValue() : initialValue) as Ref;
watch(localValue, (value) => {
vm?.emit(`update:${key}`, value);
});
watch(() => props[key], (value) => {
localValue.value = value;
});
const clearValue = () : void => {
localValue.value = getDefaultValue();
};
watch(isMultiple, () => {
clearValue();
});
return {
localValue,
clearValue,
};
}
================================================
FILE: src/use/useMultioptions.ts
================================================
import {
flattenOptions,
InputOptions,
NormalizedOption,
NormalizedOptions,
normalizeOptions,
} from '@variantjs/core';
import { computed, ComputedRef, Ref } from 'vue';
export default function useMultioptions(
options: Ref,
textAttribute: Ref,
valueAttribute: Ref,
normalize: Ref,
): {
normalizedOptions: ComputedRef
flattenedOptions: ComputedRef
} {
const normalizedOptions = computed(() => {
if (!normalize.value) {
return options.value as NormalizedOptions;
}
return normalizeOptions(
options.value,
textAttribute.value,
valueAttribute.value,
);
});
// Flattened array with all posible options
const flattenedOptions = computed((): NormalizedOption[] => flattenOptions(normalizedOptions.value));
return {
normalizedOptions,
flattenedOptions,
};
}
================================================
FILE: src/use/useSelectableOption.ts
================================================
/* eslint-disable no-param-reassign */
import {
addToArray, isEqual, NormalizedOption, substractFromArray,
} from '@variantjs/core';
import {
computed, ComputedRef, Ref, ref, watch,
} from 'vue';
type SelectedOption = NormalizedOption | NormalizedOption[] | undefined;
export default function useSelectableOption(
options: Ref,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
localValue: Ref,
multiple: Ref,
): {
selectedOption: Ref,
hasSelectedOption: ComputedRef,
selectOption: (option: NormalizedOption) => void,
toggleOption: (option: NormalizedOption) => void,
optionIsSelected: (option: NormalizedOption) => boolean,
} {
const optionIsSelected = (option: NormalizedOption): boolean => {
if (multiple.value === true) {
return Array.isArray(localValue.value)
&& localValue.value.some((value) => isEqual(value, option.value));
}
return isEqual(localValue.value, option.value);
};
const getSelectedOption = (currentSelectedOption?: SelectedOption): SelectedOption => {
let allOptions: NormalizedOption[] = options.value;
// If the option is part of the current selected option is desired to use
// those option since its possible that the options is not in the list
// For example: an option that was selected from an ajax list but was removed
if (Array.isArray(currentSelectedOption)) {
allOptions = allOptions
// Remove the options that are also on the current selected option list
.filter((option) => !currentSelectedOption.some((selectedOption) => isEqual(selectedOption.value, option.value)))
// Concat the current selected option list
.concat(currentSelectedOption);
} else if (currentSelectedOption !== undefined) {
allOptions = allOptions
// Remove the selected option if already exists in the list so it
// can be replaced with the selected option
.filter((option) => !isEqual(currentSelectedOption.value, option.value))
.concat([currentSelectedOption]);
}
if (multiple.value === true) {
if (Array.isArray(localValue.value)) {
return allOptions.filter((option) => optionIsSelected(option));
}
return [];
}
return allOptions.find((option) => optionIsSelected(option));
};
const selectedOption = ref(getSelectedOption());
const selectOption = (option: NormalizedOption): void => {
if (optionIsSelected(option)) {
return;
}
if (multiple.value === true) {
if (Array.isArray(localValue.value)) {
localValue.value = addToArray(localValue.value, option.value);
selectedOption.value = addToArray(selectedOption.value, option);
} else {
localValue.value = [option.value];
selectedOption.value = [option];
}
} else {
localValue.value = option.value;
selectedOption.value = option;
}
};
const toggleOption = (option: NormalizedOption): void => {
if (optionIsSelected(option)) {
if (multiple.value === true) {
localValue.value = substractFromArray(localValue.value, option.value);
} else {
localValue.value = undefined;
}
} else if (multiple.value === true) {
if (Array.isArray(localValue.value)) {
localValue.value = addToArray(localValue.value, option.value);
} else {
localValue.value = [option.value];
}
} else {
localValue.value = option.value;
}
};
watch([options, localValue], () => {
selectedOption.value = getSelectedOption(selectedOption.value);
});
const hasSelectedOption = computed((): boolean => {
if (multiple.value === true) {
return (selectedOption.value as NormalizedOption[]).length > 0;
}
return selectedOption.value !== undefined;
});
return {
selectedOption,
hasSelectedOption,
selectOption,
toggleOption,
optionIsSelected,
};
}
================================================
FILE: src/use/useVModel.ts
================================================
import { Data } from '@variantjs/core';
import {
getCurrentInstance, Ref, watch, ref,
} from 'vue';
export default function useVModel(
props: P,
key: K,
): Ref
{
const vm = getCurrentInstance();
const localValue = ref(props[key]) as Ref
;
watch(localValue, (value) => {
vm?.emit(`update:${key}`, value);
});
watch(() => props[key], (value) => {
localValue.value = value;
});
return localValue;
}
================================================
FILE: src/utils/createDialogProgramatically.ts
================================================
import { DialogResponse, DialogType } from '@variantjs/core';
import { createApp } from 'vue';
import { TDialogOptions } from '../types/components/t-dialog';
import { VariantJSConfiguration } from '../types/variantCore';
import TDialog from '../components/TDialog.vue';
const createDialogProgramatically = (configuration: VariantJSConfiguration, type: DialogType, titleOrDialogOptions: TDialogOptions | string, text?: string, icon?: string) : Promise => {
const { props } = TDialog;
if (typeof titleOrDialogOptions === 'string') {
props.title.default = titleOrDialogOptions;
} else {
Object.keys(titleOrDialogOptions).forEach((key) => {
props[key].default = titleOrDialogOptions[key];
});
}
if (typeof text === 'string') {
props.text.default = text;
}
if (typeof icon === 'string') {
props.icon.default = icon;
}
props.type.default = type;
const instance = createApp(TDialog);
instance.provide('configuration', configuration);
const dialogInstance = instance.mount(document.createElement('div'));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const promise = (dialogInstance as any).show() as Promise;
return promise
.then((response) => {
instance.unmount();
return Promise.resolve(response);
})
.catch((error) => {
instance.unmount();
return Promise.reject(error);
});
};
export default createDialogProgramatically;
================================================
FILE: src/utils/emitter.ts
================================================
/* eslint-disable @typescript-eslint/no-explicit-any */
import { EmitterEvents, EmitterFunction, EmitterInterface } from '../types';
export class Emitter implements EmitterInterface {
/* eslint-disable tree-shaking/no-side-effects-in-initialization */
private events: EmitterEvents = {};
on(name: keyof EmitterEvents, callback: EmitterFunction): void {
if (this.events[name] === undefined) {
this.events[name] = [callback];
} else {
this.events[name].push(callback);
}
}
once(name: keyof EmitterEvents, callback: EmitterFunction): void {
const listener = (...args: any[]) => {
callback(...args);
this.off(name, listener);
};
return this.on(name, listener);
}
emit(name: keyof EmitterEvents, ...args: any[]): void {
const events = this.events[name];
if (events === undefined) {
return;
}
events.forEach((callback) => {
callback(...args);
});
}
off(name: keyof EmitterEvents, callback: EmitterFunction): void {
const events = this.events[name];
if (events === undefined) {
return;
}
const index = events.findIndex((c) => c === callback);
if (index < 0) {
return;
}
events.splice(index, 1);
this.events[name] = events;
}
}
================================================
FILE: src/utils/getVariantProps.ts
================================================
import {
CSSClass, CSSRawClassesList, Data, Variants, VariantsWithClassesList,
} from '@variantjs/core';
import { PropType } from 'vue';
import { VariantJSProps, VariantJSWithClassesListProps } from '../types';
const getVariantProps = () : VariantJSProps => ({
classes: {
type: [String, Array, Object] as PropType,
default: undefined,
},
fixedClasses: {
type: [String, Array, Object] as PropType,
default: undefined,
},
variants: {
type: Object as PropType>,
default: undefined,
},
variant: {
type: String as PropType,
default: undefined,
},
});
const getVariantPropsWithClassesList = () : VariantJSWithClassesListProps => ({
classes: {
type: [String, Array, Object] as PropType>,
default: undefined,
},
fixedClasses: {
type: [String, Array, Object] as PropType>,
default: undefined,
},
variants: {
type: Object as PropType>,
default: undefined,
},
variant: {
type: String as PropType,
default: undefined,
},
});
export { getVariantProps, getVariantPropsWithClassesList };
================================================
FILE: src/utils/popper.ts
================================================
import { Modifier, ModifierArguments } from '@popperjs/core';
import { Data } from '@variantjs/core';
const sameWidthModifier: Modifier<'sameWidth', Data> = {
name: 'sameWidth',
enabled: true,
phase: 'beforeWrite',
requires: ['computeStyles'],
fn: (options: ModifierArguments): void => {
const { state } = options;
state.styles.popper.width = `${state.rects.reference.width}px`;
},
effect: (options: ModifierArguments): void => {
const { state } = options;
const reference = state.elements.reference as HTMLElement;
state.elements.popper.style.width = `${reference.offsetWidth}px`;
},
};
export { sameWidthModifier };
================================================
FILE: src/utils/svgToVueComponent.ts
================================================
import { Data } from '@variantjs/core';
import { h, VNode, VNodeProps } from 'vue';
const icons: {
[key: string]: VNode
} = {};
export const svgToVueComponent = (el: Element | string, deep = 0): VNode => {
let iconAsString: string | null = null;
if (deep === 0) {
iconAsString = typeof el === 'string' ? el : el.outerHTML;
if (icons[iconAsString]) {
return icons[iconAsString];
}
}
let elToConvert: Element | null | string = el;
if (typeof elToConvert === 'string') {
const div = document.createElement('div');
div.innerHTML = elToConvert;
elToConvert = div.firstElementChild;
}
if (elToConvert === null) {
return h('span');
}
const attributes = Array.from(elToConvert.attributes);
const children = Array.from(elToConvert.children);
const attrs: VNodeProps & Data = {};
attributes
.filter((attribute: Attr) => !attribute.name.startsWith('on'))
.forEach((attribute: Attr) => {
attrs[attribute.name] = attribute.value;
});
const component = h(elToConvert.tagName, attrs, children.map((child) => svgToVueComponent(child, deep + 1)));
if (deep === 0 && iconAsString !== null) {
icons[iconAsString] = component;
}
return component;
};
================================================
FILE: tailwind.config.js
================================================
module.exports = {
content: [
'./src/development/**/*.{html,js,vue,ts}',
'./node_modules/@variantjs/core/src/config/**/*.ts'
],
plugins: [
require('@tailwindcss/forms'),
],
}
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"declaration": true,
"outDir": "./dist",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"],
"types": ["vite/client", "jest"],
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"exclude": ["node_modules"]
}
================================================
FILE: vite.config.ts
================================================
import path from 'path'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import typescript from '@rollup/plugin-typescript'
export default defineConfig({
plugins: [vue()],
build: {
minify: false,
sourcemap: true,
lib: {
entry: path.resolve(__dirname, 'src/index.ts'),
name: 'VariantJS',
fileName: (format) => `index.${format}.js`
},
rollupOptions: {
plugins: [
typescript({
"exclude": ["node_modules", 'src/__tests/**/*']
}),
],
external: ['vue', '@popperjs/core', '@variantjs/core'],
output: {
globals: {
vue: 'Vue'
},
}
}
}
})
================================================
FILE: vite.demo.config.ts
================================================
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
})