├── .editorconfig ├── .eslintignore ├── .eslintrc.cjs ├── .github └── workflows │ └── release-package.yml ├── .gitignore ├── .npmrc ├── .prettierrc.cjs ├── LICENSE.txt ├── README.md ├── multiline2.gif ├── package.json ├── rollup.config.js ├── scripts ├── assets-append-version.js └── dist-name.js ├── src ├── controller.ts ├── index.ts ├── plugin.ts ├── sass │ └── plugin.scss ├── tsconfig-dts.json ├── tsconfig.json └── view.ts └── test └── browser.html /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{html}] 4 | indent_size = 2 5 | indent_style = tab 6 | 7 | [*.{js,json,ts}] 8 | indent_size = 2 9 | indent_style = tab 10 | 11 | [*.md] 12 | indent_size = 2 13 | indent_style = space 14 | 15 | [*.scss] 16 | indent_size = 2 17 | indent_style = tab 18 | 19 | [*.yml] 20 | indent_size = 2 21 | indent_style = space 22 | 23 | [package.json] 24 | indent_size = 2 25 | indent_style = space 26 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /dist/**/* 2 | /docs/**/* 3 | /ts*/**/* 4 | /types/**/* 5 | .eslintrc.cjs 6 | rollup.config.js 7 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'eslint:recommended', 4 | 'plugin:@typescript-eslint/eslint-recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:prettier/recommended', 7 | ], 8 | parser: '@typescript-eslint/parser', 9 | plugins: ['@typescript-eslint', 'simple-import-sort'], 10 | root: true, 11 | rules: { 12 | camelcase: 'off', 13 | 'no-unused-vars': 'off', 14 | 'sort-imports': 'off', 15 | 16 | 'prettier/prettier': 'error', 17 | 'simple-import-sort/imports': 'error', 18 | '@typescript-eslint/naming-convention': [ 19 | 'error', 20 | { 21 | selector: 'variable', 22 | format: ['camelCase', 'PascalCase', 'UPPER_CASE'], 23 | custom: { 24 | regex: '^opt_', 25 | match: false, 26 | }, 27 | }, 28 | ], 29 | '@typescript-eslint/explicit-function-return-type': 'off', 30 | '@typescript-eslint/no-empty-function': 'off', 31 | '@typescript-eslint/no-explicit-any': 'off', 32 | '@typescript-eslint/no-unused-vars': [ 33 | 'error', 34 | { 35 | argsIgnorePattern: '^_', 36 | }, 37 | ], 38 | 39 | // TODO: Resolve latest lint warnings 40 | '@typescript-eslint/explicit-module-boundary-types': 'off', 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /.github/workflows/release-package.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package to npmjs 2 | on: 3 | release: 4 | types: [created] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | # Setup .npmrc file to publish to npm 11 | - uses: actions/setup-node@v4 12 | with: 13 | node-version: '20' 14 | registry-url: 'https://registry.npmjs.org' 15 | - run: npm install 16 | - run: npm publish --access public 17 | env: 18 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode/ 2 | /dist/ 3 | /*.zip 4 | 5 | ### https://raw.github.com/github/gitignore//Node.gitignore 6 | 7 | # Logs 8 | logs 9 | *.log 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # Diagnostic reports (https://nodejs.org/api/report.html) 16 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 17 | 18 | # Runtime data 19 | pids 20 | *.pid 21 | *.seed 22 | *.pid.lock 23 | 24 | # Directory for instrumented libs generated by jscoverage/JSCover 25 | lib-cov 26 | 27 | # Coverage directory used by tools like istanbul 28 | coverage 29 | *.lcov 30 | 31 | # nyc test coverage 32 | .nyc_output 33 | 34 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 35 | .grunt 36 | 37 | # Bower dependency directory (https://bower.io/) 38 | bower_components 39 | 40 | # node-waf configuration 41 | .lock-wscript 42 | 43 | # Compiled binary addons (https://nodejs.org/api/addons.html) 44 | build/Release 45 | 46 | # Dependency directories 47 | node_modules/ 48 | jspm_packages/ 49 | 50 | # Snowpack dependency directory (https://snowpack.dev/) 51 | web_modules/ 52 | 53 | # TypeScript cache 54 | *.tsbuildinfo 55 | 56 | # Optional npm cache directory 57 | .npm 58 | 59 | # Optional eslint cache 60 | .eslintcache 61 | 62 | # Microbundle cache 63 | .rpt2_cache/ 64 | .rts2_cache_cjs/ 65 | .rts2_cache_es/ 66 | .rts2_cache_umd/ 67 | 68 | # Optional REPL history 69 | .node_repl_history 70 | 71 | # Output of 'npm pack' 72 | *.tgz 73 | 74 | # Yarn Integrity file 75 | .yarn-integrity 76 | 77 | # dotenv environment variables file 78 | .env 79 | .env.test 80 | 81 | # parcel-bundler cache (https://parceljs.org/) 82 | .cache 83 | .parcel-cache 84 | 85 | # Next.js build output 86 | .next 87 | out 88 | 89 | # Nuxt.js build / generate output 90 | .nuxt 91 | dist 92 | 93 | # Gatsby files 94 | .cache/ 95 | # Comment in the public line in if your project uses Gatsby and not Next.js 96 | # https://nextjs.org/blog/next-9-1#public-directory-support 97 | # public 98 | 99 | # vuepress build output 100 | .vuepress/dist 101 | 102 | # Serverless directories 103 | .serverless/ 104 | 105 | # FuseBox cache 106 | .fusebox/ 107 | 108 | # DynamoDB Local files 109 | .dynamodb/ 110 | 111 | # TernJS port file 112 | .tern-port 113 | 114 | # Stores VSCode versions used for testing VSCode extensions 115 | .vscode-test 116 | 117 | # yarn v2 118 | .yarn/cache 119 | .yarn/unplugged 120 | .yarn/build-state.yml 121 | .yarn/install-state.gz 122 | .pnp.* 123 | 124 | .DS_Store -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'always', 3 | bracketSpacing: false, 4 | singleQuote: true, 5 | trailingComma: 'all', 6 | useTabs: true, 7 | }; 8 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 cocopon 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Textarea plugin for Tweakpane 2 | [![Version](http://img.shields.io/npm/v/@pangenerator/tweakpane-textarea-plugin.svg)](https://www.npmjs.org/package/@pangenerator/tweakpane-textarea-plugin) 3 | 4 | Simple textarea (multiline text input) plugin for [Tweakpane][tweakpane].\ 5 | ![image](multiline2.gif) 6 | 7 | ## Installation 8 | 9 | ### NPM 10 | 11 | `npm i @pangenerator/tweakpane-textarea-plugin` 12 | 13 | ### Browser 14 | 15 | ```html 16 | 17 | 18 | 22 | ``` 23 | 24 | 25 | ### Package 26 | 27 | ```js 28 | import {Pane} from 'tweakpane'; 29 | import * as TextareaPlugin from '@pangenerator/tweakpane-textarea-plugin'; 30 | 31 | const pane = new Pane(); 32 | pane.registerPlugin(TextareaPlugin); 33 | ``` 34 | 35 | ## Usage 36 | 37 | ```js 38 | const params = { 39 | prop: 'Put your\nmultiline\ntext here!' 40 | }; 41 | 42 | pane.addBinding(params, 'prop', { 43 | view: 'textarea', 44 | rows: 6, 45 | placeholder: 'Type here...' 46 | }).on('change', (ev) => { 47 | console.log(ev.value); 48 | }); 49 | ``` 50 | 51 | [tweakpane]: https://github.com/cocopon/tweakpane/ 52 | -------------------------------------------------------------------------------- /multiline2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/panGenerator/tweakpane-textarea-plugin/02d8ca73a82cafb8fb3f8166ca56ed50ac028d23/multiline2.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pangenerator/tweakpane-textarea-plugin", 3 | "version": "2.0.0", 4 | "description": "Textarea plugin for Tweakpane", 5 | "main": "dist/tweakpane-textarea-plugin.js", 6 | "type": "module", 7 | "types": "dist/types/index.d.ts", 8 | "author": "panGenerator ", 9 | "license": "MIT", 10 | "files": [ 11 | "dist" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/panGenerator/tweakpane-textarea-plugin.git" 16 | }, 17 | "keywords": [ 18 | "tweakpane", 19 | "tweakpane-plugin", 20 | "plugin", 21 | "textarea", 22 | "multiline" 23 | ], 24 | "scripts": { 25 | "assets": "run-s clean build assets:version assets:zip", 26 | "assets:version": "node scripts/assets-append-version.js", 27 | "assets:zip": "zip -x '*types*' -j -r $(node scripts/dist-name.js)-$(cat package.json | npx json version).zip dist", 28 | "build": "run-p build:*", 29 | "build:dev": "rollup --config rollup.config.js", 30 | "build:dts": "tsc --project src/tsconfig-dts.json", 31 | "build:prod": "rollup --config rollup.config.js --environment BUILD:production", 32 | "clean": "rimraf dist *.tgz *.zip", 33 | "format": "run-p format:*", 34 | "format:scss": "prettier --parser scss --write \"src/sass/**/*.scss\"", 35 | "format:ts": "eslint --ext .ts --fix \"src/**/*.ts\"", 36 | "postversion": "git push --tags", 37 | "prepare": "run-s clean build", 38 | "prepublishOnly": "npm test", 39 | "server": "http-server -c-1 -o /test/browser.html", 40 | "start": "run-p watch server", 41 | "test": "eslint --ext .ts \"src/**/*.ts\"", 42 | "watch": "run-p watch:*", 43 | "watch:sass": "onchange --initial --kill \"src/sass/**/*.scss\" -- npm run build:dev", 44 | "watch:ts": "onchange --initial --kill \"src/**/*.ts\" -- rollup --config rollup.config.js" 45 | }, 46 | "devDependencies": { 47 | "@rollup/plugin-alias": "^3.1.2", 48 | "@rollup/plugin-node-resolve": "^13.0.0", 49 | "@rollup/plugin-replace": "^2.4.1", 50 | "@rollup/plugin-typescript": "^8.2.0", 51 | "@tweakpane/core": "^2.0.0", 52 | "@typescript-eslint/eslint-plugin": "^5.62.0", 53 | "@typescript-eslint/parser": "^5.62.0", 54 | "autoprefixer": "^10.2.4", 55 | "eslint": "^8.46.0", 56 | "eslint-config-prettier": "^8.1.0", 57 | "eslint-plugin-prettier": "^3.3.1", 58 | "eslint-plugin-simple-import-sort": "^7.0.0", 59 | "http-server": "^14.1.1", 60 | "npm-run-all": "^4.1.5", 61 | "onchange": "^7.1.0", 62 | "postcss": "^8.2.6", 63 | "prettier": "^2.2.1", 64 | "rimraf": "^3.0.2", 65 | "rollup": "^2.39.1", 66 | "rollup-plugin-cleanup": "^3.2.1", 67 | "rollup-plugin-terser": "^7.0.2", 68 | "sass": "^1.49.9", 69 | "tweakpane": "^4.0.0", 70 | "typescript": "^4.9.5" 71 | }, 72 | "peerDependencies": { 73 | "tweakpane": "^4.0.0" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | import Alias from '@rollup/plugin-alias'; 4 | import {nodeResolve} from '@rollup/plugin-node-resolve'; 5 | import Replace from '@rollup/plugin-replace'; 6 | import Typescript from '@rollup/plugin-typescript'; 7 | import Autoprefixer from 'autoprefixer'; 8 | import Postcss from 'postcss'; 9 | import Cleanup from 'rollup-plugin-cleanup'; 10 | import {terser as Terser} from 'rollup-plugin-terser'; 11 | import Sass from 'sass'; 12 | 13 | import Package from './package.json'; 14 | 15 | async function compileCss() { 16 | const css = Sass.renderSync({ 17 | file: 'src/sass/plugin.scss', 18 | outputStyle: 'compressed', 19 | }).css.toString(); 20 | 21 | const result = await Postcss([Autoprefixer]).process(css, { 22 | from: undefined, 23 | }); 24 | return result.css.replace(/'/g, "\\'").trim(); 25 | } 26 | 27 | function getPlugins(css, shouldMinify) { 28 | const plugins = [ 29 | Alias({ 30 | entries: [ 31 | { 32 | find: '@tweakpane/core', 33 | replacement: './node_modules/@tweakpane/core/dist/index.js', 34 | }, 35 | ], 36 | }), 37 | Typescript({ 38 | tsconfig: 'src/tsconfig.json', 39 | }), 40 | nodeResolve(), 41 | Replace({ 42 | __css__: css, 43 | preventAssignment: false, 44 | }), 45 | ]; 46 | if (shouldMinify) { 47 | plugins.push(Terser()); 48 | } 49 | return [ 50 | ...plugins, 51 | // https://github.com/microsoft/tslib/issues/47 52 | Cleanup({ 53 | comments: 'none', 54 | }), 55 | ]; 56 | } 57 | 58 | function getDistName(packageName) { 59 | return packageName.replace(/^@[^\/]+\//, '') 60 | } 61 | 62 | export default async () => { 63 | const production = process.env.BUILD === 'production'; 64 | const postfix = production ? '.min' : ''; 65 | 66 | const distName = getDistName(Package.name); 67 | const css = await compileCss(); 68 | return { 69 | input: 'src/index.ts', 70 | external: ['tweakpane'], 71 | output: { 72 | file: `dist/${distName}${postfix}.js`, 73 | format: 'esm', 74 | globals: { 75 | tweakpane: 'Tweakpane', 76 | }, 77 | }, 78 | plugins: getPlugins(css, production), 79 | 80 | // Suppress `Circular dependency` warning 81 | onwarn(warning, rollupWarn) { 82 | if (warning.code === 'CIRCULAR_DEPENDENCY') { 83 | return; 84 | } 85 | rollupWarn(warning); 86 | }, 87 | }; 88 | }; 89 | -------------------------------------------------------------------------------- /scripts/assets-append-version.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | /* eslint-env node */ 3 | 4 | import Fs from 'fs'; 5 | import Glob from 'glob'; 6 | import Path from 'path'; 7 | 8 | const Package = JSON.parse( 9 | Fs.readFileSync(new URL('../package.json', import.meta.url)), 10 | ); 11 | 12 | const PATTERN = 'dist/*'; 13 | 14 | const paths = Glob.sync(PATTERN); 15 | paths.forEach((path) => { 16 | const fileName = Path.basename(path); 17 | if (Fs.statSync(path).isDirectory()) { 18 | return; 19 | } 20 | 21 | const ext = fileName.match(/(\..+)$/)[1]; 22 | const base = Path.basename(fileName, ext); 23 | const versionedPath = Path.join( 24 | Path.dirname(path), 25 | `${base}-${Package.version}${ext}`, 26 | ); 27 | Fs.renameSync(path, versionedPath); 28 | }); 29 | -------------------------------------------------------------------------------- /scripts/dist-name.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | /* eslint-env node */ 3 | 4 | import Fs from 'fs'; 5 | 6 | const Package = JSON.parse( 7 | Fs.readFileSync(new URL('../package.json', import.meta.url)), 8 | ); 9 | 10 | // `@tweakpane/plugin-foobar` -> `tweakpane-plugin-foobar` 11 | // `tweakpane-plugin-foobar` -> `tweakpane-plugin-foobar` 12 | const name = Package.name 13 | .split(/[@/-]/) 14 | .reduce((comps, comp) => (comp !== '' ? [...comps, comp] : comps), []) 15 | .join('-'); 16 | console.log(name); 17 | -------------------------------------------------------------------------------- /src/controller.ts: -------------------------------------------------------------------------------- 1 | import {Controller, forceCast, Value, ViewProps} from '@tweakpane/core'; 2 | 3 | import {TextAreaView} from './view.js'; 4 | /** 5 | * @hidden 6 | */ 7 | export interface Config { 8 | value: Value; 9 | viewProps: ViewProps; 10 | rows: number; 11 | placeholder: string; 12 | } 13 | 14 | /** 15 | * @hidden 16 | */ 17 | export class TextAreaController implements Controller { 18 | public readonly value: Value; 19 | public readonly view: TextAreaView; 20 | public readonly viewProps: ViewProps; 21 | public readonly rows: number; 22 | public readonly placeholder: string; 23 | 24 | constructor(doc: Document, config: Config) { 25 | this.onInputChange_ = this.onInputChange_.bind(this); 26 | this.value = config.value; 27 | this.viewProps = config.viewProps; 28 | this.rows = config.rows; 29 | this.placeholder = config.placeholder; 30 | 31 | // console.log( this.rows ) 32 | 33 | this.view = new TextAreaView(doc, { 34 | value: this.value, 35 | viewProps: this.viewProps, 36 | rows: this.rows, 37 | placeholder: this.placeholder, 38 | }); 39 | this.view.inputElement.addEventListener('keyup', this.onInputChange_); 40 | } 41 | 42 | private onInputChange_(e: KeyboardEvent): void { 43 | //console.log( e.key ) 44 | 45 | //if( e.key === 'Enter' ){ 46 | const inputElem: HTMLInputElement = forceCast(e.currentTarget); 47 | const value = inputElem.value; 48 | this.value.rawValue = value; 49 | this.view.refresh(); 50 | //} 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import type {TpPlugin} from 'tweakpane'; 2 | 3 | import {TweakpaneTextareaPlugin} from './plugin.js'; 4 | 5 | export const id = 'textarea'; 6 | 7 | export const css = '__css__'; 8 | 9 | // Export your plugin(s) as constant `plugins` 10 | export const plugins: TpPlugin[] = [TweakpaneTextareaPlugin]; 11 | -------------------------------------------------------------------------------- /src/plugin.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BaseInputParams, 3 | BindingTarget, 4 | createPlugin, 5 | InputBindingPlugin, 6 | parseRecord, 7 | } from '@tweakpane/core'; 8 | 9 | import {TextAreaController} from './controller.js'; 10 | 11 | export interface TextareaPluginInputParams extends BaseInputParams { 12 | view: 'textarea'; 13 | rows?: number; 14 | placeholder?: string; 15 | } 16 | 17 | // NOTE: You can see JSDoc comments of `InputBindingPlugin` for details about each property 18 | // 19 | // `InputBindingPlugin` means... 20 | // - The plugin receives the bound value as `Ex`, 21 | // - converts `Ex` into `In` and holds it 22 | // - P is the type of the parsed parameters 23 | // 24 | export const TweakpaneTextareaPlugin: InputBindingPlugin< 25 | string, 26 | string, 27 | TextareaPluginInputParams 28 | > = createPlugin({ 29 | id: 'input-template', 30 | type: 'input', 31 | 32 | accept(exValue: unknown, params: Record) { 33 | if (typeof exValue !== 'string') { 34 | // Return null to deny the user input 35 | return null; 36 | } 37 | 38 | // Parse parameters object 39 | // console.log(params) 40 | const result = parseRecord(params, (p) => ({ 41 | // `view` option may be useful to provide a custom control for primitive values 42 | view: p.required.constant('textarea'), 43 | rows: p.optional.number, 44 | placeholder: p.optional.string, 45 | })); 46 | if (!result) { 47 | return null; 48 | } 49 | 50 | // Return a typed value and params to accept the user input 51 | return { 52 | initialValue: exValue, 53 | params: result, 54 | }; 55 | }, 56 | 57 | binding: { 58 | reader(_args) { 59 | return (exValue: unknown): string => { 60 | // Convert an external unknown value into the internal value 61 | return typeof exValue === 'string' ? exValue : ''; 62 | }; 63 | }, 64 | 65 | writer(_args) { 66 | return (target: BindingTarget, inValue) => { 67 | // Use `target.write()` to write the primitive value to the target, 68 | // or `target.writeProperty()` to write a property of the target 69 | target.write(inValue); 70 | }; 71 | }, 72 | }, 73 | 74 | controller(args) { 75 | // Create a controller for the plugin 76 | return new TextAreaController(args.document, { 77 | value: args.value, 78 | rows: args.params.rows ?? 3, 79 | placeholder: args.params.placeholder ?? 'Enter text here', 80 | viewProps: args.viewProps, 81 | }); 82 | }, 83 | }); 84 | -------------------------------------------------------------------------------- /src/sass/plugin.scss: -------------------------------------------------------------------------------- 1 | // Import core styles 2 | @use '../../node_modules/@tweakpane/core/lib/sass/tp'; 3 | 4 | // Additional style for the plugin 5 | .#{tp.$prefix}-txtrv { 6 | // Extend a general input view style 7 | @extend %tp-input; 8 | display: block; 9 | height: auto; //calc(#{tp.cssVar('container-unit-size')} * 4) 10 | //margin-bottom: -5px; 11 | padding-bottom: 0; 12 | overflow: hidden; 13 | position: relative; 14 | 15 | &.#{tp.$prefix}-v-disabled { 16 | opacity: 0.5; 17 | } 18 | 19 | .tp-txtrv_i { 20 | font-family: var(--font-family); 21 | background-color: tp.cssVar('input-bg'); 22 | border-radius: tp.cssVar('blade-border-radius'); 23 | box-sizing: border-box; 24 | color: tp.cssVar('input-fg'); 25 | font-size: 11px; 26 | padding: 4px; 27 | line-height: 16px; 28 | min-width: 0; 29 | width: 100%; 30 | border: none; 31 | height: 100%; 32 | resize: none; 33 | margin-bottom: -8px; 34 | } 35 | .tp-txtrv_i:focus { 36 | outline: none; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/tsconfig-dts.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "emitDeclarationOnly": true, 6 | "outDir": "../dist/types" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["DOM", "ES2015"], 4 | "moduleResolution": "Node16", 5 | "strict": true, 6 | "target": "ES6" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/view.ts: -------------------------------------------------------------------------------- 1 | import {ClassName, Value, View, ViewProps} from '@tweakpane/core'; 2 | 3 | interface Config { 4 | value: Value; 5 | viewProps: ViewProps; 6 | rows: number; 7 | placeholder: string; 8 | } 9 | 10 | const className = ClassName('txtr'); 11 | 12 | export class TextAreaView implements View { 13 | public readonly inputElement: HTMLTextAreaElement; 14 | public readonly element: HTMLElement; 15 | private readonly value_: Value; 16 | 17 | constructor(doc: Document, config: Config) { 18 | this.onChange_ = this.onChange_.bind(this); 19 | 20 | this.element = doc.createElement('div'); 21 | this.element.classList.add(className()); 22 | config.viewProps.bindClassModifiers(this.element); 23 | 24 | //this.onChange_ = this.onChange_.bind(this); 25 | 26 | const inputElem = doc.createElement('textarea'); 27 | inputElem.rows = config.rows; 28 | inputElem.cols = 22; 29 | inputElem.placeholder = config.placeholder; 30 | inputElem.classList.add(className('i')); 31 | 32 | config.viewProps.bindDisabled(inputElem); 33 | this.element.appendChild(inputElem); 34 | this.inputElement = inputElem; 35 | 36 | config.value.emitter.on('change', this.onChange_); 37 | this.value_ = config.value; 38 | 39 | this.refresh(); 40 | } 41 | 42 | public refresh(): void { 43 | this.inputElement.value = this.value_.rawValue; 44 | } 45 | 46 | private onChange_(): void { 47 | this.refresh(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/browser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Text Area Plugin Test 6 | 7 | 8 |
Type in the text area to refresh content live...
9 | 47 | 48 | 49 | --------------------------------------------------------------------------------