@nextcloud/vue
Version:
Nextcloud vue components
1 lines • 13.2 kB
Source Map (JSON)
{"version":3,"file":"NcTextArea-CdtxX_QJ.mjs","sources":["../../src/components/NcTextArea/NcTextArea.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors\n - SPDX-License-Identifier: AGPL-3.0-or-later\n-->\n\n<docs>\n### Description\n\nThis component is a textarea field.\nIt extends and styles an HTMLTextAreaElement.\n\n```\n<template>\n\t<div class=\"wrapper\">\n\t\t<NcTextArea v-model=\"text\"\n\t\t\tlabel=\"Text area\"\n\t\t\tplaceholder=\"Placeholders are possible\"\n\t\t\thelper-text=\"This is a regular helper text.\" />\n\t\t<NcTextArea label=\"Success state\"\n\t\t\t:success=\"true\" />\n\t\t<NcTextArea label=\"Error state\"\n\t\t\tplaceholder=\"Enter something valid\"\n\t\t\thelper-text=\"Helper texts will be styled accordingly.\"\n\t\t\t:error=\"true\" />\n\t\t<NcTextArea label=\"Disabled\"\n\t\t\t:disabled=\"true\" />\n\t\t<NcTextArea label=\"Disabled + Success\"\n\t\t\t:success=\"true\"\n\t\t\t:disabled=\"true\" />\n\t\t<div class=\"external-label\">\n\t\t\t<label for=\"textField\">External label</label>\n\t\t\t<NcTextArea id=\"textField\"\n\t\t\t\t:label-outside=\"true\"\n\t\t\t\tplaceholder=\"Text area with external label\" />\n\t\t</div>\n\t\t<NcTextArea label=\"Custom size and no resize\"\n\t\t\tresize=\"none\"\n\t\t\trows=\"5\" />\n\t</div>\n</template>\n\n<script>\nexport default {\n\tdata() {\n\t\treturn {\n\t\t\ttext: 'This is text.',\n\t\t}\n\t},\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.wrapper {\n\tdisplay: flex;\n\tgap: 4px;\n\talign-items: flex-end;\n\tflex-wrap: wrap;\n}\n\n.external-label {\n\tdisplay: flex;\n\twidth: 100%;\n\talign-items: center;\n\tgap: 14px;\n\n\t> label {\n\t\tflex-shrink: 0;\n\t}\n}\n</style>\n```\n</docs>\n\n<script setup lang=\"ts\">\nimport type { VueClassType } from '../../utils/VueTypes.ts'\n\nimport { mdiAlertCircleOutline, mdiCheck } from '@mdi/js'\nimport { computed, useAttrs, useTemplateRef, watch } from 'vue'\nimport NcIconSvgWrapper from '../NcIconSvgWrapper/NcIconSvgWrapper.vue'\nimport { createElementId } from '../../utils/createElementId.ts'\nimport { isLegacy } from '../../utils/legacy.ts'\nimport { logger } from '../../utils/logger.ts'\n\ndefineOptions({ inheritAttrs: false })\n\n/**\n * The value of the text area\n */\nconst modelValue = defineModel<string>({ required: true })\n\nconst props = withDefaults(defineProps<{\n\t/**\n\t * Disable the text area\n\t */\n\tdisabled?: boolean\n\n\t/**\n\t * Toggles the error state of the component.\n\t * Adds an error icon.\n\t */\n\terror?: boolean\n\n\t/**\n\t * Additional helper text message\n\t *\n\t * This will be displayed beneath the input field. In case the field is\n\t * also marked as having an error, the text will be displayed in red.\n\t */\n\thelperText?: string\n\n\t/**\n\t * The id of the textarea element\n\t */\n\tid?: string\n\n\t/**\n\t * Class to add to the `<textarea>` element.\n\t */\n\tinputClass?: VueClassType\n\n\t/**\n\t * The input label, always provide one for accessibility purposes.\n\t * On Nextcloud before version 32 this will also be used as a placeholder\n\t * unless the placeholder prop is populated with a different string.\n\t */\n\tlabel?: string\n\n\t/**\n\t * Pass in true if you want to use an external label. This is useful\n\t * if you need a label that looks different from the one provided by\n\t * this component\n\t */\n\tlabelOutside?: boolean\n\n\t/**\n\t * The placeholder of the input.\n\t * On Nextcloud before 32 this defaults to the value of the `label` prop.\n\t */\n\tplaceholder?: string\n\n\t/**\n\t * The resize CSS property sets whether an element is resizable, and if\n\t * so, in which directions.\n\t */\n\tresize?: 'both' | 'vertical' | 'horizontal' | 'none'\n\n\t/**\n\t * Toggles the success state of the component.\n\t * Adds a checkmark icon.\n\t */\n\tsuccess?: boolean\n}>(), {\n\thelperText: undefined,\n\tid: () => createElementId(),\n\tinputClass: '',\n\tlabel: undefined,\n\tplaceholder: undefined,\n\tresize: 'both',\n})\n\ndefineExpose({\n\tfocus,\n\tselect,\n})\n\nconst attrs = useAttrs()\n\n/**\n * The native text area component instance\n */\nconst textAreaElement = useTemplateRef('input')\n\nconst internalPlaceholder = computed(() => props.placeholder || (isLegacy ? props.label : undefined))\n\n// warn about invalid labels (missing label and no label outside)\nwatch(() => props.labelOutside, () => {\n\tif (!props.labelOutside && !props.label) {\n\t\tlogger.warn('[NcTextArea] You need to add a label to the NcInputField component. Either use the prop label or use an external one, as per the example in the documentation.')\n\t}\n})\n\nconst ariaDescribedby = computed(() => {\n\tconst ariaDescribedby: string[] = []\n\tif (props.helperText) {\n\t\tariaDescribedby.push(`${props.id}-helper-text`)\n\t}\n\tif (typeof attrs['aria-describedby'] === 'string') {\n\t\tariaDescribedby.push(attrs['aria-describedby'])\n\t}\n\treturn ariaDescribedby.join(' ') || undefined\n})\n\n/**\n * Update the model value to the text area value.\n *\n * @param event - The input event\n */\nfunction handleInput(event: Event) {\n\tconst { value } = event.target as HTMLTextAreaElement\n\tmodelValue.value = value\n}\n\n/**\n * Focus the input element\n *\n * @param options - Focus options\n * @public\n */\nfunction focus(options?: FocusOptions) {\n\ttextAreaElement.value!.focus(options)\n}\n\n/**\n * Select all the text in the input\n *\n * @public\n */\nfunction select() {\n\ttextAreaElement.value!.select()\n}\n</script>\n\n<template>\n\t<div\n\t\tclass=\"textarea\"\n\t\t:class=\"[\n\t\t\t$attrs.class,\n\t\t\t{\n\t\t\t\t'textarea--disabled': disabled,\n\t\t\t\t'textarea--legacy': isLegacy,\n\t\t\t},\n\t\t]\">\n\t\t<div class=\"textarea__main-wrapper\">\n\t\t\t<textarea\n\t\t\t\tv-bind=\"{ ...$attrs, class: undefined }\"\n\t\t\t\t:id\n\t\t\t\tref=\"input\"\n\t\t\t\t:aria-describedby\n\t\t\t\taria-live=\"polite\"\n\t\t\t\tclass=\"textarea__input\"\n\t\t\t\t:class=\"[\n\t\t\t\t\tinputClass,\n\t\t\t\t\t{\n\t\t\t\t\t\t'textarea__input--label-outside': labelOutside,\n\t\t\t\t\t\t'textarea__input--legacy': isLegacy,\n\t\t\t\t\t\t'textarea__input--success': success,\n\t\t\t\t\t\t'textarea__input--error': error,\n\t\t\t\t\t},\n\t\t\t\t]\"\n\t\t\t\t:disabled\n\t\t\t\t:placeholder=\"internalPlaceholder\"\n\t\t\t\t:style=\"{ resize }\"\n\t\t\t\t:value=\"modelValue\"\n\t\t\t\t@input=\"handleInput\" />\n\t\t\t<!-- Label -->\n\t\t\t<label\n\t\t\t\tv-if=\"!labelOutside\"\n\t\t\t\tclass=\"textarea__label\"\n\t\t\t\t:for=\"id\">\n\t\t\t\t{{ label }}\n\t\t\t</label>\n\t\t</div>\n\t\t<p\n\t\t\tv-if=\"helperText\"\n\t\t\t:id=\"`${id}-helper-text`\"\n\t\t\tclass=\"textarea__helper-text-message\"\n\t\t\t:class=\"{\n\t\t\t\t'textarea__helper-text-message--error': error,\n\t\t\t\t'textarea__helper-text-message--success': success,\n\t\t\t}\">\n\t\t\t<NcIconSvgWrapper\n\t\t\t\tv-if=\"success\"\n\t\t\t\tclass=\"textarea__helper-text-message__icon\"\n\t\t\t\t:path=\"mdiCheck\"\n\t\t\t\tinline />\n\t\t\t<NcIconSvgWrapper\n\t\t\t\tv-else-if=\"error\"\n\t\t\t\tclass=\"textarea__helper-text-message__icon\"\n\t\t\t\t:path=\"mdiAlertCircleOutline\"\n\t\t\t\tinline />\n\t\t\t{{ helperText }}\n\t\t</p>\n\t</div>\n</template>\n\n<style lang=\"scss\" scoped>\n@use '../../assets/input-border.scss' as border;\n\n.textarea {\n\t--input-border-color: var(--color-border-maxcontrast);\n\t--input-border-width-offset: calc(var(--border-width-input-focused, 2px) - var(--border-width-input, 2px));\n\tposition: relative;\n\twidth: 100%;\n\tborder-radius: var(--border-radius-element);\n\tmargin-block-start: 6px; // for the label in active state\n\tresize: vertical;\n\n\t&--disabled {\n\t\topacity: 0.7;\n\t\tfilter: saturate(0.7);\n\t}\n\n\t&__main-wrapper {\n\t\theight: calc(var(--default-clickable-area) * 2);\n\t\tpadding: var(--border-width-input-focused, 2px);\n\t\tposition: relative;\n\t}\n\n\t&__input {\n\t\tmargin: 0;\n\t\tpadding-block: var(--border-radius-element);\n\t\tpadding-inline: 10px; // align with label 8px margin label + 4px padding label - 2px border input\n\t\twidth: 100%;\n\t\tfont-size: var(--default-font-size);\n\t\ttext-overflow: ellipsis;\n\t\tcursor: pointer;\n\n\t\tbackground-color: var(--color-main-background);\n\t\tcolor: var(--color-main-text);\n\t\t@include border.inputBorder('.textarea--legacy', var(--input-border-color));\n\n\t\t&:active:not([disabled]),\n\t\t&:focus:not([disabled]) {\n\t\t\t--input-border-width-offset: 0px;\n\t\t\t--input-border-color: var(--color-main-text);\n\t\t}\n\n\t\t// Hide placeholder while not focussed -> show label instead (only if internal label is used)\n\t\t&:not(:focus,&--label-outside)::placeholder {\n\t\t\topacity: 0;\n\t\t}\n\n\t\t&:focus {\n\t\t\tcursor: text;\n\t\t}\n\n\t\t&:disabled {\n\t\t\tcursor: default;\n\t\t}\n\n\t\t&:focus-visible {\n\t\t\tbox-shadow: unset !important; // Override server rules\n\t\t}\n\n\t\t&--success {\n\t\t\t--input-border-color: var(--color-border-success, var(--color-success)) !important; //Override hover border color\n\t\t\t&:focus-visible {\n\t\t\t\tbox-shadow: rgb(248, 250, 252) 0px 0px 0px 2px, var(--color-primary-element) 0px 0px 0px 4px, rgba(0, 0, 0, 0.05) 0px 1px 2px 0px\n\t\t\t}\n\t\t}\n\n\t\t&--error {\n\t\t\t--input-border-color: var(--color-border-error, var(--color-error)) !important; //Override hover border color\n\t\t\t&:focus-visible {\n\t\t\t\tbox-shadow: rgb(248, 250, 252) 0px 0px 0px 2px, var(--color-primary-element) 0px 0px 0px 4px, rgba(0, 0, 0, 0.05) 0px 1px 2px 0px\n\t\t\t}\n\t\t}\n\t}\n\n\t&__label {\n\t\tposition: absolute;\n\t\tmargin-inline: 12px 0;\n\t\tmax-width: fit-content;\n\t\tinset-block-start: 11px;\n\t\tinset-inline: 0;\n\t\t// Fix color so that users do not think the input already has content\n\t\tcolor: var(--color-text-maxcontrast);\n\t\t// only one line labels are allowed\n\t\twhite-space: nowrap;\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\t\t// forward events to input\n\t\tpointer-events: none;\n\t\t// Position transition\n\t\ttransition: height var(--animation-quick), inset-block-start var(--animation-quick), font-size var(--animation-quick), color var(--animation-quick), background-color var(--animation-quick) var(--animation-slow);\n\t}\n\n\t&__input:focus + &__label,\n\t&__input:not(:placeholder-shown) + &__label {\n\t\tinset-block-start: -10px;\n\t\tline-height: 1.5; // minimum allowed line height for accessibility\n\t\tfont-size: 13px; // minimum allowed font size for accessibility\n\t\tfont-weight: 500;\n\t\tcolor: var(--color-main-text);\n\t\tbackground-color: var(--color-main-background);\n\t\tpadding-inline: 4px;\n\t\tmargin-inline-start: 8px;\n\n\t\ttransition: height var(--animation-quick), inset-block-start var(--animation-quick), font-size var(--animation-quick), color var(--animation-quick);\n\t}\n\n\t&__helper-text-message {\n\t\tpadding-block: 4px;\n\t\tdisplay: flex;\n\t\talign-items: center;\n\n\t\t&__icon {\n\t\t\tmargin-inline-end: 8px;\n\t\t}\n\n\t\t&--error {\n\t\t\tcolor: var(--color-error-text);\n\t\t}\n\n\t\t&--success {\n\t\t\tcolor: var(--color-success-text);\n\t\t}\n\t}\n}\n</style>\n"],"names":["_useModel","ariaDescribedby","_createElementBlock","$attrs","disabled","_unref","_createElementVNode","_mergeProps","id","inputClass","labelOutside","success","error","resize","label","helperText","_createBlock","_createTextVNode"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwFA,UAAM,aAAaA,SAAmB,SAAA,YAAmB;AAEzD,UAAM,QAAQ;AAsEd,aAAa;AAAA,MACZ;AAAA,MACA;AAAA,IAAA,CACA;AAED,UAAM,QAAQ,SAAA;AAKd,UAAM,kBAAkB,eAAe,OAAO;AAE9C,UAAM,sBAAsB,SAAS,MAAM,MAAM,gBAAgB,WAAW,MAAM,QAAQ,OAAU;AAGpG,UAAM,MAAM,MAAM,cAAc,MAAM;AACrC,UAAI,CAAC,MAAM,gBAAgB,CAAC,MAAM,OAAO;AACxC,eAAO,KAAK,gKAAgK;AAAA,MAC7K;AAAA,IACD,CAAC;AAED,UAAM,kBAAkB,SAAS,MAAM;AACtC,YAAMC,mBAA4B,CAAA;AAClC,UAAI,MAAM,YAAY;AACrBA,yBAAgB,KAAK,GAAG,MAAM,EAAE,cAAc;AAAA,MAC/C;AACA,UAAI,OAAO,MAAM,kBAAkB,MAAM,UAAU;AAClDA,yBAAgB,KAAK,MAAM,kBAAkB,CAAC;AAAA,MAC/C;AACA,aAAOA,iBAAgB,KAAK,GAAG,KAAK;AAAA,IACrC,CAAC;AAOD,aAAS,YAAY,OAAc;AAClC,YAAM,EAAE,UAAU,MAAM;AACxB,iBAAW,QAAQ;AAAA,IACpB;AAQA,aAAS,MAAM,SAAwB;AACtC,sBAAgB,MAAO,MAAM,OAAO;AAAA,IACrC;AAOA,aAAS,SAAS;AACjB,sBAAgB,MAAO,OAAA;AAAA,IACxB;;0BAICC,mBA2DM,OAAA;AAAA,QA1DL,uBAAM,YAAU;AAAA,UACHC,KAAAA,OAAO;AAAA;kCAAsCC,KAAAA;AAAAA,gCAAkCC,MAAA,QAAA;AAAA,UAAA;AAAA;;QAO5FC,mBA6BM,OA7BN,YA6BM;AAAA,UA5BLA,mBAoBwB,YApBxBC,WAoBwB,EAAA,GAnBVJ,KAAAA,eAAe,UAAS;AAAA,YACpC,IAAAK,KAAAA;AAAAA,YACD,KAAI;AAAA,YACH,oBAAA,gBAAA;AAAA,YACD,aAAU;AAAA,YACV,QAAM,mBAAiB;AAAA,cACRC,KAAAA;AAAAA;kDAA2DC,KAAAA;AAAAA,2CAA+CL,MAAA,QAAA;AAAA,4CAA4CM,KAAAA;AAAAA,0CAAyCC,KAAAA;AAAAA,cAAAA;AAAAA;YAS7M,UAAAR,KAAAA;AAAAA,YACA,aAAa,oBAAA;AAAA,YACb,iBAASS,KAAAA,OAAAA;AAAAA,YACT,OAAO,WAAA;AAAA,YACP,SAAO;AAAA,UAAA;WAGDH,KAAAA,6BADRR,mBAKQ,SAAA;AAAA;YAHP,OAAM;AAAA,YACL,KAAKM,KAAAA;AAAAA,UAAAA,mBACHM,KAAAA,KAAK,GAAA,GAAA,UAAA;;QAIHC,KAAAA,2BADPb,mBAmBI,KAAA;AAAA;UAjBF,OAAOM,KAAAA,EAAE;AAAA,UACV,uBAAM,iCAA+B;AAAA,oDACiBI,KAAAA;AAAAA,sDAAqDD,KAAAA;AAAAA,UAAAA;;UAKpGA,KAAAA,wBADPK,YAIU,kBAAA;AAAA;YAFT,OAAM;AAAA,YACL,MAAMX,MAAA,QAAA;AAAA,YACP,QAAA;AAAA,UAAA,yBAEWO,KAAAA,sBADZI,YAIU,kBAAA;AAAA;YAFT,OAAM;AAAA,YACL,MAAMX,MAAA,qBAAA;AAAA,YACP,QAAA;AAAA,UAAA;UAASY,gBAAA,sBACPF,KAAAA,UAAU,GAAA,CAAA;AAAA,QAAA;;;;;;"}