import { defineComponent } from "vue";
import type { CSSProperties, InputHTMLAttributes, PropType, VNode } from "vue";
import { v4 as uuid } from "uuid";
import codegenConfig from "@darwin-studio/ui-codegen/config.json";
import { COLOR_SCHEME } from "@darwin-studio/ui-codegen/dist/constants/color-scheme";
import { EVENT_NAME } from "@darwin-studio/vue-ui/src/constants/event-name";
import { EVENT_KEY } from "@darwin-studio/vue-ui/src/constants/event-key";
import generateProp from "@darwin-studio/vue-ui/src/utils/generate-prop";
import generateClass from "@darwin-studio/vue-ui/src/utils/generate-class";
import useCaption from "@darwin-studio/vue-ui/src/compositions/caption";
import type { DCaptionProps } from "@darwin-studio/vue-ui/src/components/atoms/d-caption/types";
import type { InputTypes } from "./types";
import { INPUT_TYPE } from "./constants";
import config from "./config";
import styles from "./index.css?module";
// TODO: mask ???
// TODO: props.colorScheme
/**
* Renders input element with label, error and icons slots.
*/
export default defineComponent({
name: config.name,
props: {
/**
* Defines id attr of the input element
*/
id: generateProp.text(() => uuid()),
/**
* Defines initial value attr of the input element
*/
value: generateProp.text(),
/**
* Defines initial placeholder attr of the input element
*/
placeholder: String,
/**
* Defines padding type of the input element
*/
padding: generateProp.padding(),
/**
* Defines corner rounding of the input element
*/
rounding: generateProp.rounding(),
/**
* Defines size of the component
*/
// TODO: fontSize and size separately ???
size: generateProp.size(),
/**
* Defines transition type of the component
*/
transition: generateProp.transition(),
/**
* Defines type attr of the input element
*/
// TODO: move to inputOptions
inputType: generateProp.string(INPUT_TYPE.TEXT),
// TODO: doesnt work properly (((
/**
* Defines size attr of the input element.
*/
// TODO: move to inputOptions
inputSize: generateProp.number(1),
/**
* You can pass own class name to the input element.
*/
// TODO: move to inputOptions
inputClass: String,
/**
* Defines font size of the input element. By default depends on props.size
*/
// TODO: move to inputOptions
inputFont: generateProp.font(undefined, true),
/**
* You can pass any attributes to the input element.
*/
// TODO: move to inputOptions
inputAttrs: generateProp.options(),
/**
* Defines content of the label element.
*/
label: generateProp.content(),
/**
* You can pass own class name to the label element.
*/
// TODO: move to labelOptions
labelClass: String,
/**
* Defines font size of the label element. By default depends on props.size
*/
// TODO: move to labelOptions
labelFont: generateProp.font(),
/**
* If not empty renders DCaption below the input element.
*/
caption: generateProp.content(),
/**
* Pass any DCaption.props to customize it, f.e. { type: "error" }
*/
captionOptions: generateProp.options(config.captionOptions),
/**
* Defines offset of DCaption
*/
captionOffset: generateProp.text(config.captionOffset), // TODO: move to captionOptions
/**
* Pass true to disable input element.
*/
disabled: Boolean,
/**
* Defines container element type of the component
*/
tag: generateProp.tag(),
/**
* Alternative way to catch change event
*/
whenChange: {
type: Function as PropType<(value: string) => void | Promise>,
},
/**
* Alternative way to catch input event
*/
whenInput: {
type: Function as PropType<(value: string) => void | Promise>,
},
/**
* Alternative way to catch submit event
*/
whenSubmit: {
type: Function as PropType<(value: string) => void | Promise>,
},
},
setup(props, { slots }) {
return useCaption(props, slots, config.captionOptions);
},
emits: [
EVENT_NAME.CHANGE,
EVENT_NAME.INPUT,
EVENT_NAME.SUBMIT,
EVENT_NAME.UPDATE_VALUE,
],
computed: {
renderLabel(): VNode | null {
if (this.label || this.$slots.label) {
const bindings = {
for: String(this.id),
class: [
config.labelClass,
this.labelClass,
generateClass.font(this.labelFont || this.size),
],
};
return (
);
}
return null;
},
inputClasses(): (string | undefined)[] {
return [
config.inputClass,
this.inputClass,
generateClass.border(COLOR_SCHEME.SECONDARY, this.size), // TODO: config or props
generateClass.font(this.inputFont || this.size),
generateClass.outline(COLOR_SCHEME.SECONDARY, this.size), // TODO: config or props
...generateClass.padding(this.padding, this.size),
generateClass.rounding(this.rounding),
generateClass.size(this.size),
generateClass.transition(this.transition),
];
},
// TODO: how to do it in a more elegant way
// TODO: test cases
inputStyles(): CSSProperties {
const styles: CSSProperties = {};
if (this.$slots.before) {
styles[
"padding-left"
] = `var(--${codegenConfig.TOKENS.SIZE.NAME}-${this.size})`;
}
if (this.$slots.after) {
styles[
"padding-right"
] = `var(--${codegenConfig.TOKENS.SIZE.NAME}-${this.size})`;
}
return styles;
},
hasSlot(): boolean {
return Boolean(this.$slots.before || this.$slots.after);
},
renderInput(): VNode {
const inputVNode = (
);
if (this.hasSlot) {
return (
{this.$slots.before && (
{this.$slots.before?.()}
)}
{inputVNode}
{this.$slots.after && (
{this.$slots.after?.()}
)}
);
}
return inputVNode;
},
},
methods: {
changeHandler(event: Event) {
const value = (event.target as HTMLInputElement).value;
/**
* Emits on change with value payload
* @event change
* @type {value: Text}
*/
this.$emit(EVENT_NAME.CHANGE, value);
/**
* Emits on change with value payload
* @event update:value
* @type {value: Text}
*/
this.$emit(EVENT_NAME.UPDATE_VALUE, value);
this.whenChange?.(value);
},
inputHandler(event: Event) {
const value = (event.target as HTMLInputElement).value;
/**
* Emits on input with value payload
* @event input
* @type {value: Text}
*/
this.$emit(EVENT_NAME.INPUT, value);
this.whenInput?.(value);
},
keyupHandler(event: KeyboardEvent) {
if (event.key === EVENT_KEY.Enter) {
const value = (event.target as HTMLInputElement).value;
/**
* Emits on Enter keyup with value payload
* @event submit
* @type {value: Text}
*/
this.$emit(EVENT_NAME.SUBMIT, value);
this.whenSubmit?.(value);
}
},
},
/*TODO: why vue-docgen cant' detect not default slots ???*/
/**
* @slot $slots.before
* Any content before the input element
* */
/**
* @slot $slots.after
* Any content after the input element
* */
/**
* @slot $slots.label
* Use instead of props.label to fully customize label content
* */
/**
* @slot $slots.caption
* Use instead of props.caption to fully customize caption content
* */
render(): VNode {
const Tag = this.tag;
return (
{this.renderLabel}
{this.renderInput}
{this.renderCaption}
);
},
});