diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 88ec062..00c61b5 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,17 +1,17 @@ name: publish on: - workflow_dispatch: - push: - branches: - - publish + workflow_dispatch: + push: + branches: + - publish jobs: - publish: - runs-on: ubuntu-latest - environment: publish - steps: - - uses: actions/checkout@v2 - - run: yarn - - run: yarn build - - run: yarn publish - env: - NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} + publish: + runs-on: ubuntu-latest + environment: publish + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: yarn build + - run: yarn publish + env: + NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..d23a980 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,20 @@ +{ + "singleQuote": true, + "jsxSingleQuote": false, + "printWidth": 120, + "proseWrap": "preserve", + "trailingComma": "es5", + "endOfLine": "lf", + "tabWidth": 4, + "useTabs": false, + "semi": true, + "overrides": [ + { + "files": ["*.yml", "*.yaml"], + "options": { + "singleQuote": false, + "tabWidth": 2 + } + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index edf18ae..51d8e6d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,12 +9,7 @@ }, "prettier.enable": true, "prettier.tabWidth": 4, - "eslint.validate": [ - "javascript", - "javascriptreact", - "typescript", - "typescriptreact" - ], + "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"], "eslint.packageManager": "yarn", "css.validate": true, "scss.validate": true, diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..e43b07d --- /dev/null +++ b/src/index.css @@ -0,0 +1,8 @@ +.unstyled-textarea-autosize { + cursor: text; +} + +.unstyled-textarea-autosize:empty::before { + content: attr(data-placeholder); + opacity: 0.75; +} diff --git a/src/index.tsx b/src/index.tsx index 917cf1e..b5c4f3f 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,17 +1,12 @@ -import React, { - ComponentProps, - FormEvent, - forwardRef, - KeyboardEvent, - useCallback, - useEffect, -} from "react"; -import { useRefWithForwarding } from "use-ref-with-forwarding"; - -export type UnstyledTextareaAutosizeProps = ComponentProps<"div"> & { +import React, { ComponentProps, FormEvent, forwardRef, KeyboardEvent, useCallback, useEffect } from 'react'; +import { useRefWithForwarding } from 'use-ref-with-forwarding'; +import './index.css'; + +export type UnstyledTextareaAutosizeProps = ComponentProps<'div'> & { readOnly?: boolean; value?: string; initialValue?: string; + placeholder?: string; onValueChange?: (value: string) => void; }; @@ -19,11 +14,11 @@ export type UnstyledTextareaAutosizeElement = HTMLDivElement & { value: string; }; -const INVALID_CTRL_KEYS = ["b", "i", "u"]; +const INVALID_CTRL_KEYS = ['b', 'i', 'u']; function initElement(element: UnstyledTextareaAutosizeElement | null) { if (element) { - Object.defineProperty(element, "value", { + Object.defineProperty(element, 'value', { get: function () { return element.innerText; }, @@ -38,13 +33,11 @@ function isElementInitialized( element: UnstyledTextareaAutosizeElement | null ): element is UnstyledTextareaAutosizeElement { if (!element) { - console.error("unstyled-textarea-autosize: unexpected null ref"); + console.error('unstyled-textarea-autosize: unexpected null ref'); return false; } - if (Object.getOwnPropertyDescriptor(element, "value") === undefined) { - console.error( - "unstyled-textarea-autosize: value property not configured" - ); + if (Object.getOwnPropertyDescriptor(element, 'value') === undefined) { + console.error('unstyled-textarea-autosize: value property not configured'); return false; } return true; @@ -54,24 +47,13 @@ export const UnstyledTextareaAutosize = forwardRef< UnstyledTextareaAutosizeElement | null, UnstyledTextareaAutosizeProps >(function UnstyledTextareaAutosize( - { - readOnly, - value, - initialValue, - onValueChange, - onInput, - onKeyDown, - ...props - }, + { readOnly, value, initialValue, placeholder, className, onValueChange, onInput, onKeyDown, ...props }, outerRef ) { // initialize dom element with the value property // reference to the root element - const ref = useRefWithForwarding( - null, - [initElement, outerRef] - ); + const ref = useRefWithForwarding(null, [initElement, outerRef]); // update the text when the value changes (controlled mode) useEffect(() => { @@ -122,14 +104,12 @@ export const UnstyledTextareaAutosize = forwardRef< // propagate the value change const newValue = ref.current.value; - if (newValue !== value && onValueChange) { - onValueChange(newValue); + if (newValue !== value) { + onValueChange?.(newValue); } // call outer onInput handler for full transparency - if (onInput) { - onInput(event); - } + onInput?.(event); }, [ref, value, onValueChange, onInput] ); @@ -143,22 +123,18 @@ export const UnstyledTextareaAutosize = forwardRef< } // block formatting shortcuts - if ( - (event.ctrlKey || event.metaKey) && - INVALID_CTRL_KEYS.includes(event.key) - ) { + if ((event.ctrlKey || event.metaKey) && INVALID_CTRL_KEYS.includes(event.key)) { event.preventDefault(); } - // blur on escape - if (event.key === "Escape") { + // blur on escape and unselect all text + if (event.key === 'Escape') { ref.current.blur(); + getSelection()?.removeAllRanges(); } // call outer onKeyDown handler for full transparency - if (onKeyDown) { - onKeyDown(event); - } + onKeyDown?.(event); }, [ref, onKeyDown] ); @@ -168,6 +144,8 @@ export const UnstyledTextareaAutosize = forwardRef<