├── README.md ├── exp ├── README.md ├── index.js └── index.html ├── .gitignore ├── favicon.ico ├── dist ├── favicon.ico ├── assets │ └── font │ │ ├── comic.woff2 │ │ ├── FZMWFont.woff2 │ │ └── font.css ├── indexzh.html └── index.html ├── public ├── favicon.ico ├── index.js └── index.html ├── src ├── assets │ └── font │ │ ├── comic.woff2 │ │ └── FZMWFont.woff2 ├── util │ └── font.ts └── component │ ├── hand-drawn-radio-group.ts │ ├── hand-drawn-anchor.ts │ ├── hand-drawn-button.ts │ ├── hand-drawn-pad.ts │ ├── hand-drawn-checkbox-group.ts │ ├── hand-drawn-input.ts │ ├── hand-drawn-progress.ts │ ├── hand-drawn-dialog.ts │ ├── hand-drawn-item.ts │ ├── hand-drawn-checkbox.ts │ ├── hand-drawn-textarea.ts │ ├── hand-drawn-radio.ts │ ├── hand-drawn-icon.ts │ ├── hand-drawn-switch.ts │ ├── hand-drawn-selector.ts │ ├── hand-drawn-slider.ts │ └── base │ └── hand-drawn-base.ts ├── web.config ├── index.html ├── .eslintrc.json ├── LICENSE.md ├── package.json ├── rollup.config.js └── tsconfig.json /README.md: -------------------------------------------------------------------------------- 1 | # hand-drawn-component 2 | 3 | 🚧working🚧 -------------------------------------------------------------------------------- /exp/README.md: -------------------------------------------------------------------------------- 1 | 使用`tsc watch & server` 2 | 查看引用编译后的组件效果`/exp/index.html` 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directories 2 | node_modules/ 3 | 4 | .idea/ 5 | 6 | lib/ 7 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MissThee/hand-drawn-component/HEAD/favicon.ico -------------------------------------------------------------------------------- /dist/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MissThee/hand-drawn-component/HEAD/dist/favicon.ico -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MissThee/hand-drawn-component/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /dist/assets/font/comic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MissThee/hand-drawn-component/HEAD/dist/assets/font/comic.woff2 -------------------------------------------------------------------------------- /src/assets/font/comic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MissThee/hand-drawn-component/HEAD/src/assets/font/comic.woff2 -------------------------------------------------------------------------------- /dist/assets/font/FZMWFont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MissThee/hand-drawn-component/HEAD/dist/assets/font/FZMWFont.woff2 -------------------------------------------------------------------------------- /src/assets/font/FZMWFont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MissThee/hand-drawn-component/HEAD/src/assets/font/FZMWFont.woff2 -------------------------------------------------------------------------------- /web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /exp/index.js: -------------------------------------------------------------------------------- 1 | import "../lib/component/hand-drawn-button.js" 2 | import "../lib/component/hand-drawn-icon.js" 3 | import "../lib/component/hand-drawn-pad.js" 4 | import "../lib/component/hand-drawn-checkbox.js" 5 | import "../lib/component/hand-drawn-radio.js" 6 | -------------------------------------------------------------------------------- /dist/assets/font/font.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'FZMWFont'; 3 | font-display: swap; 4 | src: url('FZMWFont.woff2'); 5 | } 6 | 7 | @font-face { 8 | font-family: 'comic'; 9 | font-display: swap; 10 | src: url('comic.woff2'); 11 | } 12 | 13 | * { 14 | font-family: 'comic', 'FZMWFont', sans-serif; 15 | } -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "parser": "@typescript-eslint/parser", 7 | "parserOptions": { 8 | "ecmaVersion": 12, 9 | "sourceType": "module" 10 | }, 11 | "plugins": [ 12 | "@typescript-eslint" 13 | ], 14 | "rules": { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2021 MissThee 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hand-drawn-component", 3 | "version": "1.0.0", 4 | "description": "hand-drawn component", 5 | "main": "index.js", 6 | "scripts": { 7 | "tsc": "tsc", 8 | "tsc watch & server": "concurrently \"tsc -w\" \"npx web-dev-server --node-resolve\"", 9 | "rollup watch server": "cross-env NODE_ENV=development NODE_PACKAGED_PATH=dist rollup -c -w" 10 | }, 11 | "keywords": [ 12 | "hand-drawn", 13 | "js", 14 | "webComponent" 15 | ], 16 | "author": "MissThee", 17 | "license": "ISC", 18 | "dependencies": { 19 | "lit": "^2.2.3", 20 | "roughjs": "^4.5.2" 21 | }, 22 | "devDependencies": { 23 | "@types/html-minifier": "^4.0.2", 24 | "@rollup/plugin-alias": "^3.1.9", 25 | "@rollup/plugin-node-resolve": "^13.2.1", 26 | "@rollup/plugin-replace": "^4.0.0", 27 | "@typescript-eslint/eslint-plugin": "^5.21.0", 28 | "@typescript-eslint/parser": "^5.12.0", 29 | "@web/dev-server": "^0.1.31", 30 | "concurrently": "^7.1.0", 31 | "cross-env": "^7.0.3", 32 | "esbuild": "^0.14.38", 33 | "eslint": "^8.14.0", 34 | "rollup": "^2.70.2", 35 | "rollup-plugin-copy": "^3.4.0", 36 | "rollup-plugin-dev": "^2.0.4", 37 | "rollup-plugin-esbuild": "^4.9.1", 38 | "rollup-plugin-html2": "^3.1.0", 39 | "tslib": "^2.4.0", 40 | "typescript": "^4.6.4" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/util/font.ts: -------------------------------------------------------------------------------- 1 | export interface FontInfo { 2 | fontFamily: string 3 | fontSrc: string 4 | loaded?: Function 5 | } 6 | 7 | const loadFonts = async (fontFamily: string, fontSrc: string) => { 8 | // @ts-ignore 9 | const font = new FontFace(fontFamily, `url(${fontSrc})`); 10 | const loadFontFace: any = await font.load(); 11 | // @ts-ignore 12 | document.fonts.add(loadFontFace); 13 | return loadFontFace 14 | }; 15 | 16 | export const setFont = (fontInfoArray: FontInfo[]) => { 17 | let fontFamilyStr = ''; 18 | for (let fontInfo of fontInfoArray) { 19 | fontFamilyStr += "'" + fontInfo.fontFamily + "',"; 20 | if (fontInfo.fontFamily && fontInfo.fontSrc) { 21 | loadFonts(fontInfo.fontFamily, fontInfo.fontSrc).then(() => { 22 | fontInfo.loaded?.call(undefined) 23 | }); 24 | } 25 | } 26 | if (fontFamilyStr) { 27 | document.body.style.fontFamily = fontFamilyStr.substr(0, fontFamilyStr.length - 1); 28 | } 29 | } 30 | export const setFontSync = async (fontInfoArray: FontInfo[]) => { 31 | let fontFamilyStr = ''; 32 | for (let fontInfo of fontInfoArray) { 33 | fontFamilyStr += "'" + fontInfo.fontFamily + "',"; 34 | if (fontInfo.fontFamily && fontInfo.fontSrc) { 35 | await loadFonts(fontInfo.fontFamily, fontInfo.fontSrc) 36 | fontInfo.loaded?.call(undefined); 37 | } 38 | } 39 | if (fontFamilyStr) { 40 | document.body.style.fontFamily = fontFamilyStr.substr(0, fontFamilyStr.length - 1); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import resolve from '@rollup/plugin-node-resolve'; 3 | import copy from 'rollup-plugin-copy'; 4 | import replace from '@rollup/plugin-replace'; 5 | import dev from 'rollup-plugin-dev' 6 | import esbuild from 'rollup-plugin-esbuild' 7 | import html2 from 'rollup-plugin-html2' 8 | import alias from '@rollup/plugin-alias'; 9 | const isProd = process.env.NODE_ENV === 'production' 10 | 11 | const outputPath = process.env.NODE_PACKAGED_PATH 12 | export default { 13 | input: 'public/index.js', 14 | output: { 15 | dir: outputPath, 16 | format: 'es', 17 | }, 18 | plugins: [ 19 | copy({ 20 | targets: [ 21 | {src: 'src/assets', dest: path.resolve(outputPath)}, 22 | {src: 'public/favicon.ico', dest: path.resolve(outputPath)}, 23 | ] 24 | }), 25 | resolve(), 26 | replace({ 27 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), 28 | 'process.env.NODE_PACKAGED_PATH': JSON.stringify(outputPath), 29 | preventAssignment: true 30 | }), 31 | alias({ 32 | entries: [ 33 | { 34 | find: 'src', 35 | replacement: path.resolve(__dirname, 'src') 36 | } 37 | ], 38 | }), 39 | html2({ 40 | template: 'public/index.html', 41 | minify: { 42 | removeComments: isProd, 43 | collapseWhitespace: isProd, 44 | keepClosingSlash: isProd, 45 | }, 46 | title: 'hand-drawn-component' 47 | }), 48 | esbuild({ // include rollup-plugin-terser & @rollup/plugin-typescript & rollup-plugin-ts 49 | sourceMap: true, 50 | minify: isProd, 51 | }), 52 | dev({ 53 | dirs:['dist'], 54 | }) 55 | ], 56 | preserveEntrySignatures: false, 57 | }; 58 | -------------------------------------------------------------------------------- /src/component/hand-drawn-radio-group.ts: -------------------------------------------------------------------------------- 1 | import {css, html, PropertyValues} from 'lit'; 2 | import {customElement, property, query} from 'lit/decorators.js'; 3 | import {HandDrawnBase} from './base/hand-drawn-base'; 4 | import './hand-drawn-radio'; 5 | 6 | export interface RadioItem { 7 | value: string 8 | name: string 9 | disabled?: boolean 10 | checked?: boolean 11 | } 12 | 13 | export interface RadioEl extends RadioItem, HTMLElement { 14 | } 15 | 16 | @customElement('hand-drawn-radio-group') 17 | export class HandDrawnRadioGroup extends HandDrawnBase { 18 | @property({type: Boolean, reflect: true}) disabled = false; 19 | @query('slot') slotEl: HTMLSlotElement | undefined; 20 | @property({type: String}) checkedValue: string | null = null; 21 | 22 | protected render() { 23 | return html` 24 | 25 | `; 26 | } 27 | 28 | protected firstUpdated(_changedProperties: PropertyValues) { 29 | super.firstUpdated(_changedProperties); 30 | this.setSubRadioState(); 31 | } 32 | 33 | connectedCallback() { 34 | super.connectedCallback(); 35 | this.addEventListener('change', this.change); 36 | } 37 | 38 | disconnectedCallback() { 39 | super.disconnectedCallback(); 40 | this.removeEventListener('change', this.change); 41 | } 42 | 43 | private change(e: Event) { 44 | // console.log('change', e); 45 | if (e instanceof CustomEvent) { 46 | this.checkedValue = e.detail.value; 47 | } 48 | this.setSubRadioState(); 49 | this.requestUpdate(); 50 | } 51 | 52 | private setSubRadioState() { 53 | const els = ((this.slotEl?.assignedNodes() || []) as RadioEl[]).filter(e=>e.tagName === 'HAND-DRAWN-RADIO'); 54 | els.forEach(radioEl => { 55 | radioEl.disabled = this.disabled || radioEl.disabled; 56 | radioEl.checked = this.checkedValue === radioEl.value; 57 | }); 58 | } 59 | 60 | static get styles() { 61 | return [ 62 | super.styles, 63 | //all children of slot, in this html root level 64 | css` 65 | ::slotted(*) { 66 | margin: 0 1em 0 0 67 | } 68 | ` 69 | ]; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/component/hand-drawn-anchor.ts: -------------------------------------------------------------------------------- 1 | import {css, html} from 'lit'; 2 | import {customElement, property} from 'lit/decorators.js'; 3 | import {HandDrawnBase, RoughObjCanvas, RoughObjSvg} from './base/hand-drawn-base'; 4 | 5 | @customElement('hand-drawn-anchor') 6 | export class HandDrawnAnchor extends HandDrawnBase { 7 | @property({type: String}) href = ''; 8 | @property({type: String}) target = ''; 9 | @property({type: String}) type = ''; 10 | @property({type: String}) lineColor = ''; 11 | 12 | protected render() { 13 | return html` 14 | 15 |
16 | 17 |
18 | `; 19 | } 20 | 21 | protected roughDrawOne(roughObj: RoughObjSvg | RoughObjCanvas) { 22 | const size = { 23 | width: roughObj.roughParentEl.clientWidth, 24 | height: roughObj.roughParentEl.clientHeight 25 | }; 26 | if (roughObj.roughEl instanceof HTMLCanvasElement) { 27 | roughObj.roughEl.getContext('2d')?.clearRect(0, 0, this.clientWidth, this.clientHeight); 28 | } 29 | const nodeArray = []; 30 | nodeArray.push(roughObj.roughInstance.line(0, size.height - this.roughPadding, size.width, size.height - this.roughPadding, {...this.roughOps, stroke: this.lineColor || this.roughOps.stroke})); 31 | if (roughObj.roughEl instanceof SVGSVGElement) { 32 | roughObj.roughEl.innerHTML = ''; 33 | for (let node of nodeArray) { 34 | roughObj.roughEl.appendChild(node); 35 | } 36 | } 37 | } 38 | 39 | static get styles() { 40 | return [ 41 | super.styles, 42 | css` 43 | :host { 44 | display: inline; 45 | position: relative; 46 | } 47 | 48 | .rough { 49 | position: absolute; 50 | top: 0; 51 | bottom: 0; 52 | left: 0; 53 | right: 0; 54 | } 55 | 56 | .slot { 57 | font-size: 1em; 58 | } 59 | 60 | a { 61 | text-decoration: none; 62 | color: inherit; 63 | } 64 | ` 65 | ]; 66 | } 67 | 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/component/hand-drawn-button.ts: -------------------------------------------------------------------------------- 1 | import {css, html} from 'lit'; 2 | import {customElement, property} from 'lit/decorators.js'; 3 | import {HandDrawnBase} from './base/hand-drawn-base'; 4 | 5 | @customElement('hand-drawn-button') 6 | export class HandDrawnButton extends HandDrawnBase { 7 | @property({type: Boolean, reflect: true}) disabled = false; 8 | 9 | protected updateAnimationState() { 10 | if (!this.disabled) { 11 | super.updateAnimationState(); 12 | } 13 | } 14 | 15 | protected render() { 16 | return html` 17 |
18 | 22 |
23 | `; 24 | } 25 | 26 | static get styles() { 27 | return [ 28 | super.styles, 29 | css` 30 | .rough-context { 31 | background-color: white; 32 | } 33 | 34 | .button { 35 | opacity: 0; 36 | position: absolute 37 | } 38 | 39 | .button-label { 40 | position: relative; 41 | display: block; 42 | cursor: pointer; 43 | padding: 10px 12px; 44 | } 45 | 46 | .button-label[disabled] { 47 | cursor: not-allowed; 48 | } 49 | 50 | .button-wrapper { 51 | font: inherit; 52 | overflow: hidden; 53 | position: relative; 54 | border: none; 55 | cursor: pointer; 56 | letter-spacing: 1.25px; 57 | text-align: center; 58 | outline: none; 59 | width: 100%; 60 | height: 100%; 61 | } 62 | 63 | .button-wrapper:active { 64 | transform: scale(0.95) 65 | } 66 | 67 | .button-wrapper[disabled] { 68 | opacity: 0.5; 69 | background: rgba(0, 0, 0, 0.08); 70 | cursor: not-allowed; 71 | } 72 | 73 | .button-wrapper[disabled]:active { 74 | transform: scale(1) 75 | } 76 | ` 77 | ]; 78 | } 79 | 80 | 81 | } 82 | -------------------------------------------------------------------------------- /public/index.js: -------------------------------------------------------------------------------- 1 | import "src/component/hand-drawn-button.ts" 2 | import "src/component/hand-drawn-icon.ts" 3 | import "src/component/hand-drawn-pad.ts" 4 | import "src/component/hand-drawn-input.ts" 5 | import "src/component/hand-drawn-textarea.ts" 6 | import "src/component/hand-drawn-checkbox.ts" 7 | import "src/component/hand-drawn-checkbox-group.ts" 8 | import "src/component/hand-drawn-radio.ts" 9 | import "src/component/hand-drawn-radio-group.ts" 10 | import "src/component/hand-drawn-dialog.ts" 11 | import "src/component/hand-drawn-switch.ts" 12 | import "src/component/hand-drawn-anchor.ts" 13 | import "src/component/hand-drawn-progress.ts" 14 | import "src/component/hand-drawn-slider.ts" 15 | import "src/component/hand-drawn-selector.ts" 16 | import "src/component/hand-drawn-item.ts" 17 | 18 | 19 | // import * as FontTool from "src/util/font.ts" 20 | // 21 | // const loading = document.getElementById('loading') 22 | // const content = document.getElementById('content') 23 | // content.style.opacity = '0' 24 | // content.style.transition = 'opacity 1.2s steps(4,start) 0s' 25 | // FontTool.setFont([ 26 | // { 27 | // fontFamily: 'comic', fontSrc: './assets/font/comic.woff2', loaded: () => { 28 | // content.style.opacity = '1' 29 | // loading.style.display = 'none' 30 | // } 31 | // }, 32 | // {fontFamily: 'FZMWFont', fontSrc: './assets/font/FZMWFont.woff2'}, 33 | // {fontFamily: 'monospace'}, 34 | // {fontFamily: 'sans-serif'} 35 | // ]) 36 | 37 | window.onload = function () { 38 | const loading = document.getElementById('loading') 39 | const loadingText = document.getElementById('loadingText') 40 | const content = document.getElementById('content') 41 | content.style.transition = 'opacity 1.2s ease-out' 42 | content.style.opacity = '0' 43 | loadingText.style.transformOrigin = '50% 50%' 44 | loadingText.style.transition = 'all 1s ease-out' 45 | Promise.all([ 46 | new Promise((resolve) => { 47 | setTimeout(() => { 48 | resolve() 49 | }, 500) 50 | }), 51 | document.fonts.ready 52 | ]).then(() => { 53 | loadingText.innerText = '^_^' 54 | loadingText.style.opacity = '0' 55 | loadingText.style.transform = 'scale(2)' 56 | setTimeout(() => { 57 | content.style.opacity = '1' 58 | setTimeout(() => { 59 | loading.style.display = 'none' 60 | }, 1000) 61 | }, 1000) 62 | }) 63 | } 64 | -------------------------------------------------------------------------------- /src/component/hand-drawn-pad.ts: -------------------------------------------------------------------------------- 1 | import {css, html, PropertyValues} from 'lit'; 2 | import {customElement, property} from 'lit/decorators.js'; 3 | import {AnimationType, HandDrawnBase} from './base/hand-drawn-base'; 4 | 5 | @customElement('hand-drawn-pad') 6 | export class HandDrawnPad extends HandDrawnBase { 7 | @property() bodyStyle: string = ''; 8 | // @property({type: String}) style: CSSStyleDeclaration = {}; 9 | @property({type: Boolean}) noBorder = false; 10 | @property({type: Boolean}) realTimeResize = false; 11 | private textareaResizeInterval: NodeJS.Timeout | null = null; 12 | 13 | protected firstUpdated(_changedProperties: PropertyValues) { 14 | super.firstUpdated(_changedProperties); 15 | 16 | } 17 | 18 | protected updated(_changedProperties: PropertyValues) { 19 | super.updated(_changedProperties); 20 | if (this.animationType === AnimationType.ACTIVE) { 21 | this.animationType = AnimationType.NONE; 22 | } 23 | this.updateAnimationState(); 24 | } 25 | 26 | protected render() { 27 | return html` 28 |
29 |
30 | 31 |
32 |
33 | `; 34 | } 35 | 36 | connectedCallback() { 37 | super.connectedCallback(); 38 | if (this.textareaResizeInterval) { 39 | clearInterval(this.textareaResizeInterval); 40 | this.textareaResizeInterval = null; 41 | } 42 | if (this.realTimeResize) { 43 | this.textareaResizeInterval = setInterval(() => { 44 | this.resizeHandler(); 45 | }, this.animationIntervalTime); 46 | } 47 | } 48 | 49 | disconnectedCallback() { 50 | super.disconnectedCallback(); 51 | if (this.textareaResizeInterval) { 52 | clearInterval(this.textareaResizeInterval); 53 | this.textareaResizeInterval = null; 54 | } 55 | } 56 | 57 | static get styles() { 58 | return [ 59 | super.styles, 60 | css` 61 | .pad { 62 | padding: 3px; 63 | background: none; 64 | overflow: hidden; 65 | border: none; 66 | outline: none; 67 | position: inherit; 68 | top: inherit; 69 | bottom: inherit; 70 | left: inherit; 71 | right: inherit; 72 | } 73 | 74 | .pad-content { 75 | position: relative; 76 | overflow: auto; 77 | height: 100%; 78 | z-index: 1000; 79 | } 80 | ` 81 | ]; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/component/hand-drawn-checkbox-group.ts: -------------------------------------------------------------------------------- 1 | import {css, html, PropertyValues} from 'lit'; 2 | import {customElement, property, query} from 'lit/decorators.js'; 3 | import {HandDrawnBase} from './base/hand-drawn-base'; 4 | import './hand-drawn-checkbox'; 5 | 6 | export interface CheckboxItem { 7 | value: string 8 | name: string 9 | disabled?: boolean 10 | checked?: boolean 11 | } 12 | 13 | export interface CheckboxEl extends CheckboxItem, HTMLElement { 14 | } 15 | 16 | @customElement('hand-drawn-checkbox-group') 17 | export class HandDrawnCheckboxGroup extends HandDrawnBase { 18 | @property({type: Boolean, reflect: true}) disabled = false; 19 | @query('slot') slotEl: HTMLSlotElement | undefined; 20 | @property({type: Array}) checkedValues: string[] = []; 21 | 22 | protected render() { 23 | return html` 24 | 25 | `; 26 | } 27 | 28 | protected firstUpdated(_changedProperties: PropertyValues) { 29 | super.firstUpdated(_changedProperties); 30 | const els = ((this.slotEl?.assignedNodes() || []) as CheckboxEl[]).filter(e=>e.tagName === 'HAND-DRAWN-CHECKBOX'); 31 | els.forEach(checkboxEl => { 32 | checkboxEl.disabled = this.disabled || checkboxEl.disabled; 33 | checkboxEl.checked = this.checkedValues.indexOf(checkboxEl.value) >= 0; 34 | }); 35 | } 36 | 37 | connectedCallback() { 38 | super.connectedCallback(); 39 | this.addEventListener('change', this.change); 40 | } 41 | 42 | disconnectedCallback() { 43 | super.disconnectedCallback(); 44 | this.removeEventListener('change', this.change); 45 | } 46 | 47 | private change() { 48 | // two ways to update checked value 49 | // 1. collecting value of checked child 50 | const els = (this.slotEl?.assignedNodes() || []) as CheckboxEl[]; 51 | this.checkedValues = els 52 | .filter(el => (el).tagName === 'HAND-DRAWN-CHECKBOX' && (el).checked) 53 | .map(el => (el).value); 54 | // 2. change collected value. need param: e:Event 55 | // if (e instanceof CustomEvent) { 56 | // if (e.detail.checked) { 57 | // this.checkedValues.push(e.detail.value); 58 | // } else { 59 | // const index = this.checkedValues.indexOf(e.detail.value); 60 | // if (index >= 0) { 61 | // this.checkedValues.splice(index, 1); 62 | // } 63 | // } 64 | // this.checkedValues = [...this.checkedValues]; 65 | // } 66 | } 67 | 68 | static get styles() { 69 | return [ 70 | super.styles, 71 | //all children of slot, in this html root level 72 | css` 73 | ::slotted(*) { 74 | margin: 0 1em 0 0 75 | } 76 | ` 77 | ]; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/component/hand-drawn-input.ts: -------------------------------------------------------------------------------- 1 | import {css, html} from 'lit'; 2 | import {customElement, property, query} from 'lit/decorators.js'; 3 | import {HandDrawnBase} from './base/hand-drawn-base'; 4 | 5 | @customElement('hand-drawn-input') 6 | export class HandDrawnInput extends HandDrawnBase { 7 | @property({type: Boolean, reflect: true}) disabled = false; 8 | @property({type: String}) autocomplete = ''; 9 | @property({type: Boolean}) autofocus = false; 10 | @property({type: Number}) max?: number; 11 | @property({type: Number}) maxlength?: number; 12 | @property({type: Number}) min?: number; 13 | @property({type: String}) name?: string; 14 | @property({type: String}) placeholder = ''; 15 | @property({type: Boolean}) required = false; 16 | @property({type: Boolean}) readonly = false; 17 | @property({type: String}) type: 'text' | 'password' | 'tel' | 'number' = 'text'; 18 | @property({type: String}) value: string | null = ''; 19 | @query('input') private inputEl?: HTMLInputElement; 20 | 21 | protected updateAnimationState() { 22 | if (!this.disabled) { 23 | super.updateAnimationState(); 24 | } 25 | } 26 | 27 | protected render() { 28 | return html` 29 |
30 | 46 |
47 | `; 48 | } 49 | 50 | private inputHandler(e: Event) { 51 | this.value = this.inputEl?.value || null; 52 | this.emitEvent(e); 53 | } 54 | 55 | private changeHandler(e: Event) { 56 | this.value = this.inputEl?.value || null; 57 | this.emitEvent(e); 58 | } 59 | 60 | emitEvent(event: Event) { 61 | this.dispatchEvent(new CustomEvent(event.type, { 62 | composed: true, 63 | bubbles: true, 64 | detail: event 65 | })); 66 | } 67 | 68 | static get styles() { 69 | return [ 70 | super.styles, 71 | css` 72 | .input-wrapper { 73 | overflow: hidden; 74 | padding: 10px; 75 | } 76 | 77 | .input { 78 | font: inherit; 79 | overflow: hidden; 80 | border: none; 81 | background: none; 82 | outline: none; 83 | width: 100%; 84 | } 85 | 86 | .input[disabled] { 87 | opacity: 0.5; 88 | background: rgba(0, 0, 0, 0.08); 89 | cursor: not-allowed; 90 | } 91 | ` 92 | ]; 93 | } 94 | } -------------------------------------------------------------------------------- /src/component/hand-drawn-progress.ts: -------------------------------------------------------------------------------- 1 | import {css, html, PropertyValues} from 'lit'; 2 | import {customElement, property} from 'lit/decorators.js'; 3 | import {HandDrawnBase, RoughObjCanvas, RoughObjSvg} from './base/hand-drawn-base'; 4 | import {Options} from 'roughjs/bin/core'; 5 | 6 | @customElement('hand-drawn-progress') 7 | export class HandDrawnProgress extends HandDrawnBase { 8 | @property({type: Number}) value = 0; 9 | @property({type: Boolean}) isShowPercent = false; 10 | @property({type: String}) completeColor = ''; 11 | private isComplete: boolean; 12 | 13 | constructor() { 14 | super(); 15 | this.isComplete = this.value === 100; 16 | } 17 | 18 | protected render() { 19 | return html` 20 |
21 |
22 |
23 | ${this.isShowPercent ? 24 | html` 25 |
${this.value || 0}%
26 | ` 27 | : 28 | '' 29 | } 30 |
31 | `; 32 | } 33 | 34 | protected roughOpsDefault: Options = { 35 | bowing: 0.5, 36 | roughness: 0.8, 37 | stroke: '#363636', 38 | strokeWidth: 1, 39 | fillStyle: 'zigzag', 40 | fillWeight: 0.3, 41 | hachureGap: 3, 42 | }; 43 | 44 | protected updated(_changedProperties: PropertyValues) { 45 | super.updated(_changedProperties); 46 | if ((this.isComplete && this.value !== 100) || (!this.isComplete && this.value === 100)) { 47 | this.isComplete = this.value === 100; 48 | this.roughRender(true); 49 | } 50 | } 51 | 52 | protected roughDrawOne(roughObj: RoughObjSvg | RoughObjCanvas) { 53 | if (roughObj.roughParentEl.id === 'progressBar') { 54 | const roughOpsTmp = {fill: this.roughOps.stroke, ...this.roughOps, stroke: 'transparent'}; 55 | if (this.value === 100) { 56 | roughOpsTmp.fill = this.completeColor; 57 | } 58 | super.roughDrawOne(roughObj, roughOpsTmp); 59 | } else if (roughObj.roughParentEl.id === 'progressWrapper') { 60 | super.roughDrawOne(roughObj, {...this.roughOps, fill: ""}); 61 | } else { 62 | super.roughDrawOne(roughObj); 63 | } 64 | } 65 | 66 | static get styles() { 67 | return [ 68 | super.styles, 69 | css` 70 | :host { 71 | width: 100%; 72 | height: 1.2em; 73 | } 74 | 75 | .progress-wrapper { 76 | overflow: hidden; 77 | position: relative; 78 | width: 100%; 79 | height: 100%; 80 | } 81 | 82 | .progress-wrapper .rough-context { 83 | //background-color: white; 84 | z-index: 1; 85 | } 86 | 87 | .progress-bar { 88 | position: absolute; 89 | top: 0; 90 | left: 0; 91 | right: 0; 92 | bottom: 0; 93 | z-index: -1; 94 | } 95 | 96 | .progress-bar .rough-context { 97 | background-color: white; 98 | } 99 | 100 | .progress-bar-mask { 101 | transition: width 0.2s ease-out; 102 | position: absolute; 103 | background-color: white; 104 | top: 0; 105 | right: 0; 106 | height: 100%; 107 | z-index: 0; 108 | } 109 | 110 | .progress-percent { 111 | position: absolute; 112 | top: 0; 113 | left: 50%; 114 | transform: translateX(-50%); 115 | z-index: 1; 116 | vertical-align: middle; 117 | line-height: 1.2em; 118 | font-size: 1em; 119 | 120 | } 121 | ` 122 | ]; 123 | } 124 | 125 | 126 | } -------------------------------------------------------------------------------- /src/component/hand-drawn-dialog.ts: -------------------------------------------------------------------------------- 1 | import {css, html, PropertyValues} from 'lit'; 2 | import {customElement, property} from 'lit/decorators.js'; 3 | import {HandDrawnBase} from './base/hand-drawn-base'; 4 | // (Include sibling component dependencies) 5 | import './hand-drawn-pad'; 6 | import './hand-drawn-icon'; 7 | 8 | @customElement('hand-drawn-dialog') 9 | export class HandDrawnDialog extends HandDrawnBase { 10 | @property({type: Boolean}) visible = false; 11 | @property({type: Boolean}) appendToBody = false; 12 | @property({type: Boolean}) closeOnClickMask = false; 13 | private keyDownHandler = this.keyDownHandlerTmp.bind(this); 14 | private isAppendToBodyDone = false; 15 | 16 | protected render() { 17 | if (this.visible) { 18 | return html` 19 |
20 |
21 |
22 | 23 |
24 | 25 | 26 | 27 |
28 | `; 29 | } else { 30 | return html``; 31 | } 32 | } 33 | 34 | protected firstUpdated(_changedProperties: PropertyValues) { 35 | super.firstUpdated(_changedProperties); 36 | if (this.appendToBody) { 37 | document.body.insertBefore(this, document.body.childNodes[0]); 38 | this.isAppendToBodyDone = true; 39 | } 40 | } 41 | 42 | connectedCallback() { 43 | super.connectedCallback(); 44 | window.addEventListener('keydown', this.keyDownHandler); 45 | } 46 | 47 | disconnectedCallback() { 48 | super.disconnectedCallback(); 49 | window.removeEventListener('keydown', this.keyDownHandler); 50 | this.visible = false; 51 | if (this.appendToBody && this.isAppendToBodyDone && this && this.parentNode) { 52 | this.parentNode.removeChild(this); 53 | } 54 | } 55 | 56 | private keyDownHandlerTmp(e: KeyboardEvent) { 57 | if (e.code === 'Escape') { 58 | this.visible = false; 59 | } 60 | } 61 | 62 | private closeClickHandler() { 63 | this.visible = false; 64 | } 65 | 66 | private maskClickHandler() { 67 | if (this.closeOnClickMask) { 68 | this.visible = false; 69 | } 70 | } 71 | 72 | static get styles() { 73 | return [ 74 | super.styles, 75 | css` 76 | :host { 77 | position: absolute; 78 | } 79 | 80 | .dialog-mask { 81 | position: fixed; 82 | background-color: rgba(255, 255, 255, 0.75); 83 | top: 0; 84 | bottom: 0; 85 | left: 0; 86 | right: 0; 87 | z-index: 10000; 88 | } 89 | 90 | .dialog { 91 | position: fixed; 92 | top: 15%; 93 | left: 50%; 94 | width: 65%; 95 | min-width: 300px; 96 | height: 60%; 97 | overflow: visible; 98 | background-color: white; 99 | transform: translate(-50%, 0); 100 | box-shadow: 2px 2px 10px #999999; 101 | z-index: 10001; 102 | } 103 | 104 | .dialog-pad { 105 | position: absolute; 106 | top: 0; 107 | bottom: 0; 108 | left: 0; 109 | right: 0; 110 | overflow: auto; 111 | } 112 | 113 | .dialog-close { 114 | display: inline-block; 115 | position: absolute; 116 | top: 0; 117 | right: -20px; 118 | z-index: 10002; 119 | cursor: pointer; 120 | } 121 | 122 | .dialog-close-icon { 123 | pointer-events: none; 124 | } 125 | ` 126 | ]; 127 | } 128 | 129 | 130 | } 131 | -------------------------------------------------------------------------------- /src/component/hand-drawn-item.ts: -------------------------------------------------------------------------------- 1 | import {css, html} from 'lit'; 2 | import {customElement, property} from 'lit/decorators.js'; 3 | import {HandDrawnBase} from './base/hand-drawn-base'; 4 | 5 | // use with selector 6 | @customElement('hand-drawn-item') 7 | export class HandDrawnItem extends HandDrawnBase { 8 | @property({type: Boolean, reflect: true}) disabled = false; 9 | @property({type: Boolean}) checked = false; 10 | @property({type: String}) value: string | null = null; 11 | @property({type: String}) name: string | null = null; 12 | @property({type: String}) selectedColor: string | null = null; 13 | @property({type: Boolean, state:true}) isHover: boolean = false 14 | 15 | private isMouseDown: boolean = false 16 | private itemMouseUpHandler = this.itemMouseUpHandlerTmp.bind(this) 17 | 18 | constructor() { 19 | super(); 20 | this.attachShadow({mode: 'open'}); 21 | } 22 | 23 | protected render() { 24 | return html` 25 |
${this.name} 31 |
32 | `; 33 | } 34 | 35 | protected createRenderRoot(): ShadowRoot | Element { 36 | return super.createRenderRoot(); 37 | } 38 | 39 | private itemMouseDownHandler(event: CustomEvent) { 40 | if (this.disabled) { 41 | return 42 | } 43 | event.preventDefault(); 44 | event.stopPropagation(); 45 | this.isMouseDown = true 46 | } 47 | 48 | private itemMouseUpHandlerTmp(event: Event) { 49 | if (this.disabled) { 50 | return 51 | } 52 | if (this.isMouseDown) { 53 | event.stopPropagation(); 54 | this.checked = true; 55 | this.dispatchEvent(new CustomEvent('change', { 56 | composed: true, 57 | bubbles: true, 58 | detail: { 59 | value: this.value, 60 | name: this.name, 61 | checked: this.checked 62 | } 63 | })); 64 | } 65 | this.isMouseDown = false 66 | } 67 | 68 | protected updateAnimationState() { 69 | if (!this.disabled) { 70 | super.updateAnimationState(); 71 | } 72 | } 73 | 74 | private hoverHandler() { 75 | this.isHover = true 76 | } 77 | 78 | private leaveHandler() { 79 | this.isHover = false 80 | } 81 | 82 | connectedCallback() { 83 | super.connectedCallback(); 84 | this.addEventListener('mouseover', this.hoverHandler) 85 | this.addEventListener('mouseleave', this.leaveHandler) 86 | window.addEventListener('mouseup', this.itemMouseUpHandler) 87 | } 88 | 89 | disconnectedCallback() { 90 | super.disconnectedCallback(); 91 | this.removeEventListener('mouseover', this.hoverHandler) 92 | this.removeEventListener('mouseleave', this.leaveHandler) 93 | window.removeEventListener('mouseup', this.itemMouseUpHandler) 94 | } 95 | 96 | static get styles() { 97 | return [ 98 | super.styles, 99 | css` 100 | :host { 101 | display: block; 102 | } 103 | 104 | .item { 105 | word-break:keep-all; 106 | white-space:nowrap; 107 | padding: 0.2em 0; 108 | position: relative; 109 | border: none; 110 | background: none; 111 | cursor: pointer; 112 | outline: none; 113 | } 114 | 115 | .item-input:focus { 116 | font-weight: bold; 117 | } 118 | 119 | .item[disabled] { 120 | opacity: 0.5; 121 | cursor: not-allowed; 122 | } 123 | ` 124 | ]; 125 | } 126 | 127 | 128 | } 129 | -------------------------------------------------------------------------------- /src/component/hand-drawn-checkbox.ts: -------------------------------------------------------------------------------- 1 | import {css, html} from 'lit'; 2 | import {customElement, property, query} from 'lit/decorators.js'; 3 | import {HandDrawnBase, RoughObjCanvas, RoughObjSvg} from './base/hand-drawn-base'; 4 | 5 | @customElement('hand-drawn-checkbox') 6 | export class HandDrawnCheckbox extends HandDrawnBase { 7 | @property({type: Boolean, reflect: true}) disabled = false; 8 | @property({type: Boolean}) checked = false; 9 | @property({type: String}) value: string | null = null; 10 | @query('input') private input?: HTMLInputElement; 11 | 12 | protected render() { 13 | return html` 14 | 21 | `; 22 | } 23 | 24 | private checkSwitchHandler() { 25 | this.checked = this.input!.checked; 26 | this.dispatchEvent(new CustomEvent('change', { 27 | composed: true, 28 | bubbles: true, 29 | detail: { 30 | value: this.value, 31 | checked: this.checked 32 | } 33 | })); 34 | } 35 | 36 | protected updateAnimationState() { 37 | if (!this.disabled) { 38 | super.updateAnimationState(); 39 | } 40 | } 41 | 42 | protected roughDrawOne(roughObj: RoughObjSvg | RoughObjCanvas) { 43 | const size = { 44 | width: roughObj.roughParentEl.clientWidth, 45 | height: roughObj.roughParentEl.clientHeight 46 | }; 47 | if (roughObj.roughParentEl.id === 'tick') { 48 | if (roughObj.roughEl instanceof HTMLCanvasElement) { 49 | roughObj.roughEl.getContext('2d')?.clearRect(0, 0, this.clientWidth, this.clientHeight); 50 | } 51 | const nodeArray = []; 52 | nodeArray.push(roughObj.roughInstance.line(size.width / 5, size.height / 3, size.width / 5 * 2, size.height / 5 * 4, this.roughOps)); 53 | nodeArray.push(roughObj.roughInstance.line(size.width / 5 * 2, size.height / 5 * 4, size.width, 0, this.roughOps)); 54 | if (roughObj.roughEl instanceof SVGSVGElement) { 55 | roughObj.roughEl.innerHTML = ''; 56 | for (let node of nodeArray) { 57 | roughObj.roughEl.appendChild(node); 58 | } 59 | } 60 | } else { 61 | super.roughDrawOne(roughObj); 62 | } 63 | } 64 | 65 | static get styles() { 66 | return [ 67 | super.styles, 68 | css` 69 | .slot { 70 | display: inline-block; 71 | vertical-align: middle; 72 | } 73 | 74 | .checkbox { 75 | overflow: hidden; 76 | position: relative; 77 | border: none; 78 | background: none; 79 | cursor: pointer; 80 | outline: none; 81 | height: 100%; 82 | } 83 | 84 | .checkbox-input { 85 | width: 0; 86 | height: 0; 87 | opacity: 0; 88 | position: absolute; 89 | } 90 | 91 | .checkbox-wrapper { 92 | display: inline-block; 93 | overflow: hidden; 94 | position: relative; 95 | border: none; 96 | background: none; 97 | outline: none; 98 | width: 1em; 99 | height: 1em; 100 | line-height: 1em; 101 | vertical-align: middle; 102 | } 103 | 104 | .checkbox-tick { 105 | height: 100%; 106 | width: 100%; 107 | position: relative; 108 | } 109 | 110 | .checkbox[disabled] { 111 | opacity: 0.5; 112 | cursor: not-allowed; 113 | } 114 | ` 115 | ]; 116 | } 117 | 118 | 119 | } -------------------------------------------------------------------------------- /src/component/hand-drawn-textarea.ts: -------------------------------------------------------------------------------- 1 | import {css, html} from 'lit'; 2 | import {customElement, property, query} from 'lit/decorators.js'; 3 | import {HandDrawnBase} from './base/hand-drawn-base'; 4 | 5 | @customElement('hand-drawn-textarea') 6 | export class HandDrawnTextarea extends HandDrawnBase { 7 | @property({type: Boolean, reflect: true}) disabled = false; 8 | @property({type: String}) value: string | null = ''; 9 | @property({type: Boolean}) autofocus = false; 10 | @property({type: Boolean}) autoHeight = false; 11 | @property({type: String}) placeholder = ''; 12 | @property({type: Number}) maxlength?: number; 13 | @property({type: String}) name?: string; 14 | @property({type: Boolean}) required = false; 15 | @property({type: Boolean}) readonly = false; 16 | @property({type: String}) resize: string = 'both'; 17 | @property({type: Number}) row = 3; 18 | 19 | @query('textarea') private textareaEl?: HTMLTextAreaElement; 20 | 21 | private isMouseDowningInThis = false; 22 | private textareaResizeHandler = this.textareaResizeHandlerTmp.bind(this); 23 | 24 | protected updateAnimationState() { 25 | if (!this.disabled) { 26 | super.updateAnimationState(); 27 | } 28 | } 29 | 30 | protected render() { 31 | return html` 32 |
33 |