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} ); }, });