publish(@omiu/color-picker)

This commit is contained in:
dntzhang 2020-04-26 19:43:51 +08:00
parent e9702b5a81
commit 6b32489e95
30 changed files with 7640 additions and 0 deletions

View File

@ -0,0 +1,90 @@
## ColorPicker
Color Picker
* [→ CodePen](https://codepen.io/omijs/pen/gOaWmZE)
## Import
```js
import '@omiu/color-picker'
```
Or use script tag to ref it.
```html
<script src="https://unpkg.com/@omiu/color-picker"></script>
```
## Usage
```html
<o-color-picker></o-color-picker>
```
## API
### Props
```tsx
{
button?: boolean,
preview?: boolean,
opacity?: boolean,
hue?: boolean,
hex?: boolean,
rgba?: boolean,
hsla?: boolean,
hsva?: boolean,
input?: boolean,
clear?: boolean,
save?: boolean,
cmyk?: boolean,
default?: string,
swatches?: string[],
inline?: boolean,
strings?: {
save: string, // Default for save button
clear: string // Default for clear button
}
```
### 默认属性
```tsx
{
button: true,
preview: true,
opacity: true,
hue: true,
hex: true,
rgba: true,
hsla: true,
hsva: false,
input: true,
clear: true,
save: true,
cmyk: false,
default: '#07c160',
swatches: [],
inline: true,
strings: {
save: 'Save', // Default for save button
clear: 'Clear' // Default for clear button
)
```
### Events
* init
* save
* change
* swatchselect

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
<meta charset="UTF-8" />
<title>Omiu ActionSheet</title>
</head>
<body>
<a href="https://github.com/Tencent/omi" target="_blank" style="position: fixed; right: 0; top: 0; z-index: 3;">
<img src="//alloyteam.github.io/github.png" alt="">
</a>
<script src="https://tencent.github.io/omi/packages/omi/dist/omi.js"></script>
<script src="https://unpkg.com/@omiu/button@0.0.7/src/index.js"></script>
<script src="../../src/index.js"></script>
<div>
<o-color-picker id='picker' save="0" preview="0" button='0' clear='0' width="300px">
</o-color-picker>
</div>
<script>
var picker = document.querySelector('#picker')
picker.addEventListener('change', function (evt) {
console.log(evt.detail.color)
})
</script>
</body>
</html>

View File

@ -0,0 +1,101 @@
{
"name": "@omiu/color-picker",
"version": "0.0.1",
"description": "Color Picker",
"docsExtend": {
"cnName": "颜色选择器",
"cnDescription": "Color Picker",
"codepen": "gOaWmZE",
"codepenHeight": 351,
"codepenDefaultTab": "html,result"
},
"main": "src/index.js",
"module": "src/index.esm.js",
"types": "src/index.d.ts",
"scripts": {
"docs": "node ./scripts/docs-gen.js",
"start": "node ./scripts/webpack.build.js -- demo",
"build": "node ./scripts/webpack.build.js -- build && rollup -c scripts/rollup.config.js && node ./scripts/rollup.end.js"
},
"typings": "./dist/index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/Tencent/omi.git"
},
"files": [
"src",
"dist",
"typings.json"
],
"keywords": [
"omiu",
"omi",
"omio",
"preact",
"react",
"virtual dom",
"vdom",
"components",
"virtual",
"dom"
],
"author": "dntzhang <dntzhang@qq.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/Tencent/omi/issues"
},
"homepage": "http://omijs.org",
"devDependencies": {
"@rollup/plugin-commonjs": "^11.1.0",
"css": "^2.2.4",
"css-loader": "^1.0.1",
"file": "^0.2.2",
"file-loader": "^2.0.0",
"html-webpack-plugin": "^3.2.0",
"less": "^3.9.0",
"less-loader": "^4.1.0",
"mini-css-extract-plugin": "^0.4.5",
"node-sass": "^4.12.0",
"omi": "latest",
"omio": "latest",
"optimize-css-assets-webpack-plugin": "^5.0.1",
"progress-bar-webpack-plugin": "^2.1.0",
"resolve-url-loader": "^3.1.0",
"rollup": "^2.7.1",
"rollup-plugin-license": "^2.0.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-scss": "^2.4.0",
"rollup-plugin-typescript": "^1.0.1",
"sass-loader": "^7.1.0",
"style-loader": "^0.23.1",
"to-string-loader": "^1.1.5",
"ts-loader": "^5.4.4",
"typescript": "^3.2.1",
"url": "^0.11.0",
"url-loader": "^1.1.2",
"webpack": "^4.42.1",
"webpack-cli": "^3.3.1",
"webpack-dev-server": "^3.1.10",
"webpack-merge": "^4.1.4"
},
"greenkeeper": {
"ignore": [
"babel-cli",
"babel-core",
"babel-eslint",
"babel-loader",
"jscodeshift",
"rollup-plugin-babel"
]
},
"prettier": {
"singleQuote": true,
"semi": false,
"tabWidth": 2,
"useTabs": false
},
"dependencies": {
"@omiu/common": "latest",
"omi": "latest"
}
}

View File

@ -0,0 +1,134 @@
//自动扫描 index.tsx 生成 readme
const fs = require('fs')
const content = fs.readFileSync('./src/index.tsx', 'utf-8')
const props = content.match(new RegExp('interface Props \\{[\\s\\S]*?}'))[0].replace('interface Props ', '')
const defaultPropsContext = content.match(new RegExp('static defaultProps = \\{[\\s\\S]*?}'))
let defaultProps
if (defaultPropsContext) {
defaultProps = defaultPropsContext[0].replace('static defaultProps = ', '').replace(/ /g, ' ').replace(/ }/g, ')')
}
const eventContexts = content.match(new RegExp('this.fire\\([\\s\\S]*?[,|)]', 'g'))
const package = require('../package.json')
const packageName = package.name
const name = packageName.split('/')[1]
const upperCaseName = name.split('-').map(item => {
return item.charAt(0).toUpperCase() + item.slice(1)
}).join('')
const tagName = 'o-' + name
//fire 附近打标标记 event.detail 类型?
let events, eventMap
if (eventContexts) {
events = eventContexts.map(event => {
return event.replace('this.fire(\'', '').replace('\',', '').replace('\')', '')
})
eventMap = {}
events.forEach(event => {
eventMap[event] = 1
})
}
const cnContent = `## ${upperCaseName} ${package.docsExtend.cnName}
${package.docsExtend.cnDescription}
<iframe height="${package.docsExtend.codepenHeight}" style="width: 100%;" scrolling="no" title="OMIU ${upperCaseName}" src="https://codepen.io/omijs/embed/${package.docsExtend.codepen}?height=${package.docsExtend.codepenHeight}&theme-id=default&default-tab=${package.docsExtend.codepenDefaultTab}" frameborder="no" allowtransparency="true" allowfullscreen="true" loading="lazy">
See the Pen <a href='https://codepen.io/omijs/pen/${package.docsExtend.codepen}'>OMIU Checkbox</a> by OMI
(<a href='https://codepen.io/omijs'>@omijs</a>) on <a href='https://codepen.io'>CodePen</a>.
</iframe>
## 导入
\`\`\`js
import '${packageName}'
\`\`\`
或者直接 script 标签引入
\`\`\`html
<script src="https://unpkg.com/${packageName}"></script>
\`\`\`
## 使用
\`\`\`html
<${tagName}> </${tagName}>
\`\`\`
## API
### 属性
\`\`\`tsx
${props}
\`\`\`
${defaultProps ? '### 默认属性\n' : ''}${defaultProps ? '\`\`\`tsx\n' : ''}${defaultProps ? defaultProps : ''}
${defaultProps ? '\`\`\`\n' : ''}${eventMap ? '### 事件\n' : ''}${eventMap ? Object.keys(eventMap).map(event => {
return `* ${event}\n`
}).join('') : ''}`
fs.writeFileSync(`../docs-src/src/docs/zh-cn/${name}.md`, cnContent)
const enContent = `## ${upperCaseName}
${package.description}
<iframe height="${package.docsExtend.codepenHeight}" style="width: 100%;" scrolling="no" title="OMIU ${upperCaseName}" src="https://codepen.io/omijs/embed/${package.docsExtend.codepen}?height=${package.docsExtend.codepenHeight}&theme-id=default&default-tab=${package.docsExtend.codepenDefaultTab}" frameborder="no" allowtransparency="true" allowfullscreen="true" loading="lazy">
See the Pen <a href='https://codepen.io/omijs/pen/${package.docsExtend.codepen}'>OMIU Checkbox</a> by OMI
(<a href='https://codepen.io/omijs'>@omijs</a>) on <a href='https://codepen.io'>CodePen</a>.
</iframe>
## Import
\`\`\`js
import '${packageName}'
\`\`\`
Or use script tag to ref it.
\`\`\`html
<script src="https://unpkg.com/${packageName}"></script>
\`\`\`
## Usage
\`\`\`html
<${tagName}></${tagName}>
\`\`\`
## API
### Props
\`\`\`tsx
${props}
\`\`\`
${defaultProps ? '### 默认属性\n\n' : ''}${defaultProps ? '\`\`\`tsx\n' : ''}${defaultProps ? defaultProps : ''}
${defaultProps ? '\`\`\`\n' : ''}${eventMap ? '### Events\n\n' : ''}${eventMap ? Object.keys(eventMap).map(event => {
return `* ${event}\n`
}).join('') : ''}`
fs.writeFileSync(`../docs-src/src/docs/en/${name}.md`, enContent)
fs.writeFileSync(`../${name}/README.md`, enContent.replace(/<iframe[\s\S]*?<\/iframe>/, `* [→ CodePen](https://codepen.io/omijs/pen/${package.docsExtend.codepen})`))
// console.log(props)
// console.log(defaultProps)
// console.log(Object.keys(eventMap))

View File

@ -0,0 +1,38 @@
import nodeResolve from "rollup-plugin-node-resolve";
import typescript from 'rollup-plugin-typescript';
import scss from 'rollup-plugin-scss'
import commonjs from '@rollup/plugin-commonjs';
const fs = require('fs')
const license = require("rollup-plugin-license");
const pkg = require("../package.json");
const licensePlugin = license({
banner: `${pkg.name} v${pkg.version} http://omijs.org\r\nFront End Cross-Frameworks Framework.\r\nBy dntzhang https://github.com/dntzhang \r\n Github: https://github.com/Tencent/omi\r\n MIT Licensed.`
});
export default {
input: "src/index.tsx",
output: {
format: "es",
file: "./src/index.esm.js",
name: pkg.name,
sourcemap: true,
strict: true
},
plugins: [
nodeResolve({
main: true
}),
scss({
//output: false,
output: function (styles, styleNodes) {
fs.writeFileSync('./src/index.css', styles)
},
}),
typescript(),
commonjs(),
licensePlugin
],
external: ['omi']
};

View File

@ -0,0 +1,16 @@
const fs = require('fs')
const css = fs.readFileSync('./src/index.css')
const js = fs.readFileSync('./src/index.esm.js', 'utf-8')
fs.writeFileSync('./src/index.esm.js',
js.replace(`var css = /*#__PURE__*/Object.freeze({
__proto__: null
});`, `
var css = \`${css}\`
`)
)

View File

@ -0,0 +1,102 @@
const path = require('path')
const glob = require('glob')
const webpack = require('webpack')
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
const pkgName = require('../package.json')
const componentName = pkgName.name.split('/')[1]
const name = 'o-' + componentName
const library = 'O' + componentName.split('-').map(name => name.charAt(0).toUpperCase() + name.slice(1)).join('')
const config = {
devtool: 'source-map',
plugins: [
new ProgressBarPlugin()
],
entry: {
[name]: './src/index.tsx'
},
output: {
path: path.resolve(__dirname, '../src/'),
filename: 'index.js',
libraryTarget: 'umd',
library: library,
libraryExport: "default",
globalObject: 'this'
},
mode: 'development',
module: {
rules: [{
test: /\.scss$/,
use: [
'to-string-loader',
'css-loader',
{
loader: 'resolve-url-loader'
},
{
loader: 'sass-loader',
options: {
sourceMap: true,
// mdc-web doesn't use sass-loader's normal syntax for imports
// across modules, so we add all module directories containing
// mdc-web components to the Sass include path
// https://github.com/material-components/material-components-web/issues/351
includePaths: glob.sync(path.join(__dirname, '../node_modules/@material')).map((dir) => path.dirname(dir))
}
}
]
},
{
test: /\.css$/,
use: [
'to-string-loader',
'css-loader',
{
loader: 'resolve-url-loader'
}
]
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
{
loader: 'resolve-url-loader'
},
'less-loader'
]
},
{
test: /\.(jpe?g|png|gif|svg)$/i,
loader: "url-loader"
},
{
test: /\.[t|j]sx?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
watch: process.argv[3] === 'demo',
externals: {
'omi': {
commonjs: "omi",
commonjs2: "omi",
amd: "omi",
root: "Omi"
}
}
}
webpack(config, (err, stats) => { // Stats Object
if (err || stats.hasErrors()) {
// Handle errors here
}
// Done processing
})

View File

@ -0,0 +1,563 @@
.pickr {
position: relative;
overflow: visible;
transform: translateY(0); }
.pickr * {
box-sizing: border-box; }
.pickr .pcr-button {
position: relative;
height: 2em;
width: 2em;
padding: 0.5em;
cursor: pointer;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica Neue", Arial, sans-serif;
border-radius: 0.15em;
background: url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" stroke="%2342445A" stroke-width="5px" stroke-linecap="round"><path d="M45,45L5,5"></path><path d="M45,5L5,45"></path></svg>') no-repeat center;
background-size: 0;
transition: all 0.3s; }
.pickr .pcr-button::before {
position: absolute;
content: '';
top: 0;
left: 0;
width: 100%;
height: 100%;
background: url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2"><path fill="white" d="M1,0H2V1H1V0ZM0,1H1V2H0V1Z"/><path fill="gray" d="M0,0H1V1H0V0ZM1,1H2V2H1V1Z"/></svg>');
background-size: 0.5em;
border-radius: 0.15em;
z-index: -1; }
.pickr .pcr-button::before {
z-index: initial; }
.pickr .pcr-button::after {
position: absolute;
content: '';
top: 0;
left: 0;
height: 100%;
width: 100%;
transition: background 0.3s;
background: currentColor;
border-radius: 0.15em; }
.pickr .pcr-button.clear {
background-size: 70%; }
.pickr .pcr-button.clear::before {
opacity: 0; }
.pickr .pcr-button.clear:focus {
box-shadow: 0 0 0 1px #f1f3f4, 0 0 0 3px currentColor; }
.pickr .pcr-button.disabled {
cursor: not-allowed; }
.pickr input,
.pickr button,
.pcr-app input,
.pcr-app button {
outline: none;
border: none;
-webkit-appearance: none; }
.pickr input:focus,
.pickr button:focus,
.pcr-app input:focus,
.pcr-app button:focus {
box-shadow: 0 0 0 1px #f1f3f4, 0 0 0 3px currentColor; }
.pcr-app {
position: fixed;
display: flex;
flex-direction: column;
z-index: 10000;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica Neue", Arial, sans-serif;
box-shadow: 0 0.15em 1.5em 0 rgba(0, 0, 0, 0.1), 0 0 1em 0 rgba(0, 0, 0, 0.03);
width: 22.5em;
max-width: 95vw;
padding: 0.8em;
border-radius: 0.1em;
background: #fff;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s;
left: 0;
top: 0; }
.pcr-app.visible {
visibility: visible;
opacity: 1; }
.pcr-app .pcr-swatches {
display: flex;
flex-wrap: wrap;
margin-top: 0.75em; }
.pcr-app .pcr-swatches.pcr-last {
margin: 0; }
@supports (display: grid) {
.pcr-app .pcr-swatches {
display: grid;
align-items: center;
justify-content: space-around;
grid-template-columns: repeat(auto-fit, 1.75em); } }
.pcr-app .pcr-swatches > button {
position: relative;
width: 1.75em;
height: 1.75em;
border-radius: 0.15em;
cursor: pointer;
margin: 2.5px;
flex-shrink: 0;
justify-self: center;
transition: all 0.15s;
overflow: hidden;
background: transparent;
z-index: 1; }
.pcr-app .pcr-swatches > button::before {
position: absolute;
content: '';
top: 0;
left: 0;
width: 100%;
height: 100%;
background: url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2"><path fill="white" d="M1,0H2V1H1V0ZM0,1H1V2H0V1Z"/><path fill="gray" d="M0,0H1V1H0V0ZM1,1H2V2H1V1Z"/></svg>');
background-size: 6px;
border-radius: 0.15em;
z-index: -1; }
.pcr-app .pcr-swatches > button::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: currentColor;
border: 1px solid rgba(0, 0, 0, 0.05);
border-radius: 0.15em;
box-sizing: border-box; }
.pcr-app .pcr-swatches > button:hover {
filter: brightness(1.05); }
.pcr-app .pcr-interaction {
display: flex;
flex-wrap: wrap;
align-items: center;
margin: 0 -0.2em 0 -0.2em; }
.pcr-app .pcr-interaction > * {
margin: 0 0.2em; }
.pcr-app .pcr-interaction input {
letter-spacing: 0.07em;
font-size: 0.75em;
text-align: center;
cursor: pointer;
color: #75797e;
background: #f1f3f4;
border-radius: 0.15em;
transition: all 0.15s;
padding: 0.45em 0.5em;
margin-top: 0.75em; }
.pcr-app .pcr-interaction input:hover {
filter: brightness(0.975); }
.pcr-app .pcr-interaction input:focus {
box-shadow: 0 0 0 1px #f1f3f4, 0 0 0 3px rgba(66, 133, 244, 0.75); }
.pcr-app .pcr-interaction .pcr-result {
color: #75797e;
text-align: left;
flex: 1 1 8em;
min-width: 8em;
transition: all 0.2s;
border-radius: 0.15em;
background: #f1f3f4;
cursor: text; }
.pcr-app .pcr-interaction .pcr-result::selection {
background: #4285f4;
color: #fff; }
.pcr-app .pcr-interaction .pcr-type.active {
color: #fff;
background: #4285f4; }
.pcr-app .pcr-interaction .pcr-clear,
.pcr-app .pcr-interaction .pcr-save {
color: #fff;
width: auto; }
.pcr-app .pcr-interaction .pcr-save,
.pcr-app .pcr-interaction .pcr-clear {
color: #fff; }
.pcr-app .pcr-interaction .pcr-save:hover,
.pcr-app .pcr-interaction .pcr-clear:hover {
filter: brightness(0.925); }
.pcr-app .pcr-interaction .pcr-save {
background: #4285f4; }
.pcr-app .pcr-interaction .pcr-clear {
background: #f44250; }
.pcr-app .pcr-interaction .pcr-clear:focus {
box-shadow: 0 0 0 1px #f1f3f4, 0 0 0 3px rgba(244, 66, 80, 0.75); }
.pcr-app .pcr-selection {
display: flex;
justify-content: space-between;
flex-grow: 1; }
.pcr-app .pcr-selection .pcr-picker {
position: absolute;
height: 18px;
width: 18px;
border: 2px solid #fff;
border-radius: 100%;
user-select: none; }
.pcr-app .pcr-selection .pcr-color-preview {
position: relative;
z-index: 1;
width: 2em;
display: flex;
flex-direction: column;
justify-content: space-between;
margin-right: 0.75em; }
.pcr-app .pcr-selection .pcr-color-preview::before {
position: absolute;
content: '';
top: 0;
left: 0;
width: 100%;
height: 100%;
background: url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2"><path fill="white" d="M1,0H2V1H1V0ZM0,1H1V2H0V1Z"/><path fill="gray" d="M0,0H1V1H0V0ZM1,1H2V2H1V1Z"/></svg>');
background-size: 0.5em;
border-radius: 0.15em;
z-index: -1; }
.pcr-app .pcr-selection .pcr-color-preview .pcr-last-color {
cursor: pointer;
transition: background-color 0.3s, box-shadow 0.3s;
border-radius: 0.15em 0.15em 0 0;
z-index: 2; }
.pcr-app .pcr-selection .pcr-color-preview .pcr-current-color {
border-radius: 0 0 0.15em 0.15em; }
.pcr-app .pcr-selection .pcr-color-preview .pcr-last-color,
.pcr-app .pcr-selection .pcr-color-preview .pcr-current-color {
background: currentColor;
width: 100%;
height: 50%; }
.pcr-app .pcr-selection .pcr-color-palette,
.pcr-app .pcr-selection .pcr-color-chooser,
.pcr-app .pcr-selection .pcr-color-opacity {
position: relative;
user-select: none;
display: flex;
flex-direction: column;
cursor: grab;
cursor: -moz-grab;
cursor: -webkit-grab; }
.pcr-app .pcr-selection .pcr-color-palette:active,
.pcr-app .pcr-selection .pcr-color-chooser:active,
.pcr-app .pcr-selection .pcr-color-opacity:active {
cursor: grabbing;
cursor: -moz-grabbing;
cursor: -webkit-grabbing; }
.pcr-app .pcr-selection .pcr-color-palette {
width: 100%;
height: 8em;
z-index: 1; }
.pcr-app .pcr-selection .pcr-color-palette .pcr-palette {
flex-grow: 1;
border-radius: 0.15em; }
.pcr-app .pcr-selection .pcr-color-palette .pcr-palette::before {
position: absolute;
content: '';
top: 0;
left: 0;
width: 100%;
height: 100%;
background: url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2"><path fill="white" d="M1,0H2V1H1V0ZM0,1H1V2H0V1Z"/><path fill="gray" d="M0,0H1V1H0V0ZM1,1H2V2H1V1Z"/></svg>');
background-size: 0.5em;
border-radius: 0.15em;
z-index: -1; }
.pcr-app .pcr-selection .pcr-color-chooser,
.pcr-app .pcr-selection .pcr-color-opacity {
margin-left: 0.75em; }
.pcr-app .pcr-selection .pcr-color-chooser .pcr-picker,
.pcr-app .pcr-selection .pcr-color-opacity .pcr-picker {
left: 50%;
transform: translateX(-50%); }
.pcr-app .pcr-selection .pcr-color-chooser .pcr-slider,
.pcr-app .pcr-selection .pcr-color-opacity .pcr-slider {
width: 8px;
flex-grow: 1;
border-radius: 50em; }
.pcr-app .pcr-selection .pcr-color-chooser .pcr-slider {
background: linear-gradient(to bottom, red, yellow, lime, cyan, blue, magenta, red); }
.pcr-app .pcr-selection .pcr-color-opacity .pcr-slider {
background: linear-gradient(to bottom, transparent, black), url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2"><path fill="white" d="M1,0H2V1H1V0ZM0,1H1V2H0V1Z"/><path fill="gray" d="M0,0H1V1H0V0ZM1,1H2V2H1V1Z"/></svg>');
background-size: 100%, 50%; }
.pickr {
position: relative;
overflow: visible;
transform: translateY(0); }
.pickr * {
box-sizing: border-box; }
.pickr .pcr-button {
position: relative;
height: 2em;
width: 2em;
padding: 0.5em;
cursor: pointer;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica Neue", Arial, sans-serif;
border-radius: 0.15em;
background: url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" stroke="%2342445A" stroke-width="5px" stroke-linecap="round"><path d="M45,45L5,5"></path><path d="M45,5L5,45"></path></svg>') no-repeat center;
background-size: 0;
transition: all 0.3s; }
.pickr .pcr-button::before {
position: absolute;
content: '';
top: 0;
left: 0;
width: 100%;
height: 100%;
background: url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2"><path fill="white" d="M1,0H2V1H1V0ZM0,1H1V2H0V1Z"/><path fill="gray" d="M0,0H1V1H0V0ZM1,1H2V2H1V1Z"/></svg>');
background-size: 0.5em;
border-radius: 0.15em;
z-index: -1; }
.pickr .pcr-button::before {
z-index: initial; }
.pickr .pcr-button::after {
position: absolute;
content: '';
top: 0;
left: 0;
height: 100%;
width: 100%;
transition: background 0.3s;
background: currentColor;
border-radius: 0.15em; }
.pickr .pcr-button.clear {
background-size: 70%; }
.pickr .pcr-button.clear::before {
opacity: 0; }
.pickr .pcr-button.clear:focus {
box-shadow: 0 0 0 1px #f1f3f4, 0 0 0 3px currentColor; }
.pickr .pcr-button.disabled {
cursor: not-allowed; }
.pickr input,
.pickr button,
.pcr-app input,
.pcr-app button {
outline: none;
border: none;
-webkit-appearance: none; }
.pickr input:focus,
.pickr button:focus,
.pcr-app input:focus,
.pcr-app button:focus {
box-shadow: 0 0 0 1px #f1f3f4, 0 0 0 3px currentColor; }
.pcr-app {
position: fixed;
display: flex;
flex-direction: column;
z-index: 10000;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica Neue", Arial, sans-serif;
box-shadow: 0 0.15em 1.5em 0 rgba(0, 0, 0, 0.1), 0 0 1em 0 rgba(0, 0, 0, 0.03);
width: 22.5em;
max-width: 95vw;
padding: 0.8em;
border-radius: 0.1em;
background: #fff;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s;
left: 0;
top: 0; }
.pcr-app.visible {
visibility: visible;
opacity: 1; }
.pcr-app .pcr-swatches {
display: flex;
flex-wrap: wrap;
margin-top: 0.75em; }
.pcr-app .pcr-swatches.pcr-last {
margin: 0; }
@supports (display: grid) {
.pcr-app .pcr-swatches {
display: grid;
align-items: center;
justify-content: space-around;
grid-template-columns: repeat(auto-fit, 1.75em); } }
.pcr-app .pcr-swatches > button {
position: relative;
width: 1.75em;
height: 1.75em;
border-radius: 0.15em;
cursor: pointer;
margin: 2.5px;
flex-shrink: 0;
justify-self: center;
transition: all 0.15s;
overflow: hidden;
background: transparent;
z-index: 1; }
.pcr-app .pcr-swatches > button::before {
position: absolute;
content: '';
top: 0;
left: 0;
width: 100%;
height: 100%;
background: url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2"><path fill="white" d="M1,0H2V1H1V0ZM0,1H1V2H0V1Z"/><path fill="gray" d="M0,0H1V1H0V0ZM1,1H2V2H1V1Z"/></svg>');
background-size: 6px;
border-radius: 0.15em;
z-index: -1; }
.pcr-app .pcr-swatches > button::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: currentColor;
border: 1px solid rgba(0, 0, 0, 0.05);
border-radius: 0.15em;
box-sizing: border-box; }
.pcr-app .pcr-swatches > button:hover {
filter: brightness(1.05); }
.pcr-app .pcr-interaction {
display: flex;
flex-wrap: wrap;
align-items: center;
margin: 0 -0.2em 0 -0.2em; }
.pcr-app .pcr-interaction > * {
margin: 0 0.2em; }
.pcr-app .pcr-interaction input {
letter-spacing: 0.07em;
font-size: 0.75em;
text-align: center;
cursor: pointer;
color: #75797e;
background: #f1f3f4;
border-radius: 0.15em;
transition: all 0.15s;
padding: 0.45em 0.5em;
margin-top: 0.75em; }
.pcr-app .pcr-interaction input:hover {
filter: brightness(0.975); }
.pcr-app .pcr-interaction input:focus {
box-shadow: 0 0 0 1px #f1f3f4, 0 0 0 3px rgba(66, 133, 244, 0.75); }
.pcr-app .pcr-interaction .pcr-result {
color: #75797e;
text-align: left;
flex: 1 1 8em;
min-width: 8em;
transition: all 0.2s;
border-radius: 0.15em;
background: #f1f3f4;
cursor: text; }
.pcr-app .pcr-interaction .pcr-result::selection {
background: #4285f4;
color: #fff; }
.pcr-app .pcr-interaction .pcr-type.active {
color: #fff;
background: #4285f4; }
.pcr-app .pcr-interaction .pcr-clear,
.pcr-app .pcr-interaction .pcr-save {
color: #fff;
width: auto; }
.pcr-app .pcr-interaction .pcr-save,
.pcr-app .pcr-interaction .pcr-clear {
color: #fff; }
.pcr-app .pcr-interaction .pcr-save:hover,
.pcr-app .pcr-interaction .pcr-clear:hover {
filter: brightness(0.925); }
.pcr-app .pcr-interaction .pcr-save {
background: #4285f4; }
.pcr-app .pcr-interaction .pcr-clear {
background: #f44250; }
.pcr-app .pcr-interaction .pcr-clear:focus {
box-shadow: 0 0 0 1px #f1f3f4, 0 0 0 3px rgba(244, 66, 80, 0.75); }
.pcr-app .pcr-selection {
display: flex;
justify-content: space-between;
flex-grow: 1; }
.pcr-app .pcr-selection .pcr-picker {
position: absolute;
height: 18px;
width: 18px;
border: 2px solid #fff;
border-radius: 100%;
user-select: none; }
.pcr-app .pcr-selection .pcr-color-preview {
position: relative;
z-index: 1;
width: 2em;
display: flex;
flex-direction: column;
justify-content: space-between;
margin-right: 0.75em; }
.pcr-app .pcr-selection .pcr-color-preview::before {
position: absolute;
content: '';
top: 0;
left: 0;
width: 100%;
height: 100%;
background: url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2"><path fill="white" d="M1,0H2V1H1V0ZM0,1H1V2H0V1Z"/><path fill="gray" d="M0,0H1V1H0V0ZM1,1H2V2H1V1Z"/></svg>');
background-size: 0.5em;
border-radius: 0.15em;
z-index: -1; }
.pcr-app .pcr-selection .pcr-color-preview .pcr-last-color {
cursor: pointer;
transition: background-color 0.3s, box-shadow 0.3s;
border-radius: 0.15em 0.15em 0 0;
z-index: 2; }
.pcr-app .pcr-selection .pcr-color-preview .pcr-current-color {
border-radius: 0 0 0.15em 0.15em; }
.pcr-app .pcr-selection .pcr-color-preview .pcr-last-color,
.pcr-app .pcr-selection .pcr-color-preview .pcr-current-color {
background: currentColor;
width: 100%;
height: 50%; }
.pcr-app .pcr-selection .pcr-color-palette,
.pcr-app .pcr-selection .pcr-color-chooser,
.pcr-app .pcr-selection .pcr-color-opacity {
position: relative;
user-select: none;
display: flex;
flex-direction: column;
cursor: grab;
cursor: -moz-grab;
cursor: -webkit-grab; }
.pcr-app .pcr-selection .pcr-color-palette:active,
.pcr-app .pcr-selection .pcr-color-chooser:active,
.pcr-app .pcr-selection .pcr-color-opacity:active {
cursor: grabbing;
cursor: -moz-grabbing;
cursor: -webkit-grabbing; }
.pcr-app .pcr-selection .pcr-color-palette {
width: 100%;
height: 8em;
z-index: 1; }
.pcr-app .pcr-selection .pcr-color-palette .pcr-palette {
flex-grow: 1;
border-radius: 0.15em; }
.pcr-app .pcr-selection .pcr-color-palette .pcr-palette::before {
position: absolute;
content: '';
top: 0;
left: 0;
width: 100%;
height: 100%;
background: url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2"><path fill="white" d="M1,0H2V1H1V0ZM0,1H1V2H0V1Z"/><path fill="gray" d="M0,0H1V1H0V0ZM1,1H2V2H1V1Z"/></svg>');
background-size: 0.5em;
border-radius: 0.15em;
z-index: -1; }
.pcr-app .pcr-selection .pcr-color-chooser,
.pcr-app .pcr-selection .pcr-color-opacity {
margin-left: 0.75em; }
.pcr-app .pcr-selection .pcr-color-chooser .pcr-picker,
.pcr-app .pcr-selection .pcr-color-opacity .pcr-picker {
left: 50%;
transform: translateX(-50%); }
.pcr-app .pcr-selection .pcr-color-chooser .pcr-slider,
.pcr-app .pcr-selection .pcr-color-opacity .pcr-slider {
width: 8px;
flex-grow: 1;
border-radius: 50em; }
.pcr-app .pcr-selection .pcr-color-chooser .pcr-slider {
background: linear-gradient(to bottom, red, yellow, lime, cyan, blue, magenta, red); }
.pcr-app .pcr-selection .pcr-color-opacity .pcr-slider {
background: linear-gradient(to bottom, transparent, black), url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2"><path fill="white" d="M1,0H2V1H1V0ZM0,1H1V2H0V1Z"/><path fill="gray" d="M0,0H1V1H0V0ZM1,1H2V2H1V1Z"/></svg>');
background-size: 100%, 50%; }

72
components/color-picker/src/index.d.ts vendored Normal file
View File

@ -0,0 +1,72 @@
import { WeElement } from 'omi';
import Pickr from './js/pickr.js';
interface Props {
button?: boolean;
preview?: boolean;
opacity?: boolean;
hue?: boolean;
hex?: boolean;
rgba?: boolean;
hsla?: boolean;
hsva?: boolean;
input?: boolean;
clear?: boolean;
save?: boolean;
cmyk?: boolean;
default?: string;
swatches?: string[];
inline?: boolean;
strings?: {
save: string;
clear: string;
};
width?: string;
}
export default class ColorPicker extends WeElement<Props> {
static css: any;
picker: Pickr;
static defaultProps: {
button: boolean;
preview: boolean;
opacity: boolean;
hue: boolean;
hex: boolean;
rgba: boolean;
hsla: boolean;
hsva: boolean;
input: boolean;
clear: boolean;
save: boolean;
cmyk: boolean;
default: string;
swatches: any[];
inline: boolean;
strings: {
save: string;
clear: string;
};
};
static propTypes: {
button: BooleanConstructor;
preview: BooleanConstructor;
opacity: BooleanConstructor;
hue: BooleanConstructor;
hex: BooleanConstructor;
rgba: BooleanConstructor;
hsla: BooleanConstructor;
hsva: BooleanConstructor;
input: BooleanConstructor;
clear: BooleanConstructor;
save: BooleanConstructor;
cmyk: BooleanConstructor;
default: StringConstructor;
swatches: ArrayConstructor;
inline: BooleanConstructor;
strings: ObjectConstructor;
width: StringConstructor;
};
installed(): void;
setColor(color: any): any;
render(props: any): JSX.Element;
}
export {};

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
@import "@omiu/common/theme";
@import "./scss/pickr";

View File

@ -0,0 +1,158 @@
import { tag, WeElement, h, extractClass } from 'omi'
import * as css from './index.scss'
import Pickr from './js/pickr.js'
interface Props {
button?: boolean,
preview?: boolean,
opacity?: boolean,
hue?: boolean,
hex?: boolean,
rgba?: boolean,
hsla?: boolean,
hsva?: boolean,
input?: boolean,
clear?: boolean,
save?: boolean,
cmyk?: boolean,
default?: string,
swatches?: string[],
inline?: boolean,
strings?: {
save: string, // Default for save button
clear: string // Default for clear button
},
width?: string
}
@tag('o-color-picker')
export default class ColorPicker extends WeElement<Props>{
static css = css
picker: Pickr
static defaultProps = {
button: true,
preview: true,
opacity: true,
hue: true,
hex: true,
rgba: true,
hsla: true,
hsva: false,
input: true,
clear: true,
save: true,
cmyk: false,
default: '#07c160',
swatches: [],
inline: true,
strings: {
save: 'Save', // Default for save button
clear: 'Clear' // Default for clear button
}
}
static propTypes = {
button: Boolean,
preview: Boolean,
opacity: Boolean,
hue: Boolean,
hex: Boolean,
rgba: Boolean,
hsla: Boolean,
hsva: Boolean,
input: Boolean,
clear: Boolean,
save: Boolean,
cmyk: Boolean,
default: String,
swatches: Array,
inline: Boolean,
strings: Object,
width: String
}
installed() {
const picker = Pickr.create({
el: this.shadowRoot.querySelector('.picker'),
inline: this.props.inline,
default: this.props.default,
useAsButton: !this.props.button,
swatches: this.props.swatches,
width: this.props.width,
components: {
// Main components
preview: this.props.preview,
opacity: this.props.opacity,
hue: this.props.hue,
// Input / output Options
interaction: {
hex: this.props.hex,
rgba: this.props.rgba,
hsla: this.props.hsla,
hsva: this.props.hsva,
cmyk: this.props.cmyk,
input: this.props.input,
clear: this.props.clear,
save: this.props.save
}
},
strings: this.props.strings
})
picker.on('init', (...args) => {
this.picker.show()
this.fire('init', args[0])
}).on('save', (...args) => {
this.fire('save', {
color: args[0].toHEXA().toString(),
colorObject: args[0]
})
}).on('change', (...args) => {
this.fire('change', {
color: args[0].toHEXA().toString(),
colorObject: args[0]
})
}).on('swatchselect', (...args) => {
this.fire('swatchselect', {
color: args[0].toHEXA().toString(),
colorObject: args[0]
})
})
this.picker = picker
}
setColor(color) {
return this.picker.setColor(color)
}
render(props) {
return (
<div {...extractClass(props, 'o-color-picker')}>
<div class='picker'>
</div>
</div>
)
}
}

View File

@ -0,0 +1,96 @@
import * as _ from '../utils/utils';
export default function Moveable(opt) {
const that = {
// Assign default values
options: Object.assign({
lockX: false,
lockY: false,
onchange: () => 0
}, opt),
_tapstart(evt) {
_.on(document, ['mouseup', 'touchend', 'touchcancel'], that._tapstop);
_.on(document, ['mousemove', 'touchmove'], that._tapmove);
// Prevent default touch event
evt.preventDefault();
// Trigger
that._tapmove(evt);
},
_tapmove(evt) {
const {options, cache} = that;
const {element} = options;
const b = that.options.wrapper.getBoundingClientRect();
let x = 0, y = 0;
if (evt) {
const touch = evt && evt.touches && evt.touches[0];
x = evt ? (touch || evt).clientX : 0;
y = evt ? (touch || evt).clientY : 0;
// Reset to bounds
if (x < b.left) x = b.left;
else if (x > b.left + b.width) x = b.left + b.width;
if (y < b.top) y = b.top;
else if (y > b.top + b.height) y = b.top + b.height;
// Normalize
x -= b.left;
y -= b.top;
} else if (cache) {
x = cache.x * b.width;
y = cache.y * b.height;
}
if (!options.lockX) {
element.style.left = `calc(${x / b.width * 100}% - ${element.offsetWidth / 2}px)`;
}
if (!options.lockY) {
element.style.top = `calc(${y / b.height * 100}% - ${element.offsetWidth / 2}px)`;
}
that.cache = {x: x / b.width, y: y / b.height};
options.onchange(x, y);
},
_tapstop() {
_.off(document, ['mouseup', 'touchend', 'touchcancel'], that._tapstop);
_.off(document, ['mousemove', 'touchmove'], that._tapmove);
},
trigger() {
that._tapmove();
},
update(x = 0, y = 0) {
const wrapperRect = that.options.wrapper.getBoundingClientRect();
that._tapmove({
clientX: wrapperRect.left + x,
clientY: wrapperRect.top + y
});
},
destroy() {
const {options, _tapstart} = that;
_.off([options.wrapper, options.element], 'mousedown', _tapstart);
_.off([options.wrapper, options.element], 'touchstart', _tapstart, {
passive: false
});
}
};
// Initilize
const {options, _tapstart} = that;
_.on([options.wrapper, options.element], 'mousedown', _tapstart);
_.on([options.wrapper, options.element], 'touchstart', _tapstart, {
passive: false
});
return that;
}

View File

@ -0,0 +1,57 @@
/**
* Micro positioning-engine
* @param el
* @param reference
* @param pos
* @param padding
* @returns {{update(): void}}
* @constructor
*/
export default function Nanopop({el, reference, pos, padding = 8}) {
const vBehaviour = {start: 'sme', middle: 'mse', end: 'ems'};
const hBehaviour = {top: 'tb', right: 'rl', bottom: 'bt', left: 'lr'};
const [position, variant = 'middle'] = pos.split('-');
const isVertical = (position === 'top' || position === 'bottom');
return {
update() {
const rb = reference.getBoundingClientRect();
const eb = el.getBoundingClientRect();
const positions = isVertical ? {
t: rb.top - eb.height - padding,
b: rb.bottom + padding
} : {
r: rb.right + padding,
l: rb.left - eb.width - padding
};
const variants = isVertical ? {
s: rb.left + rb.width - eb.width,
m: (-eb.width / 2) + (rb.left + rb.width / 2),
e: rb.left
} : {
s: rb.bottom - eb.height,
m: rb.bottom - rb.height / 2 - eb.height / 2,
e: rb.bottom - rb.height
};
function apply(bevs, vars, styleprop) {
const vertical = styleprop === 'top';
const adder = vertical ? eb.height : eb.width;
const win = window[vertical ? 'innerHeight' : 'innerWidth'];
for (const ch of bevs) {
const v = vars[ch];
if (v > 0 && (v + adder) < win) {
el.style[styleprop] = `${v}px`;
break;
}
}
}
apply(vBehaviour[variant], variants, isVertical ? 'left' : 'top');
apply(hBehaviour[position], positions, isVertical ? 'top' : 'left');
}
};
}

View File

@ -0,0 +1,21 @@
import * as _ from '../utils/utils';
export default function Selectable(opt = {}) {
opt = Object.assign({
onchange: () => 0,
className: '',
elements: []
}, opt);
const onTap = _.on(opt.elements, 'click', evt => {
opt.elements.forEach(e =>
e.classList[evt.target === e ? 'add' : 'remove'](opt.className)
);
opt.onchange(evt);
});
return {
destroy: () => _.off(...onTap)
};
}

View File

@ -0,0 +1,763 @@
// Import styles
import '../scss/pickr.scss';
// Import utils
import * as _ from './utils/utils';
import * as Color from './utils/color';
// Import classes
import {HSVaColor} from './utils/hsvacolor';
import Moveable from './libs/moveable';
import Selectable from './libs/selectable';
import Nanopop from './libs/nanopop';
import buildPickr from './template';
class Pickr {
// Will be used to prevent specific actions during initilization
_initializingActive = true;
// Replace element with color picker
_recalc = true;
// Current and last color for comparison
_color = HSVaColor();
_lastColor = HSVaColor();
_swatchColors = [];
// Evenlistener name: [callbacks]
_eventListener = {
'swatchselect': [],
'change': [],
'save': [],
'init': []
};
constructor(opt) {
// Assign default values
this.options = opt = Object.assign({
appClass: null,
useAsButton: false,
disabled: false,
comparison: true,
components: {
interaction: {}
},
strings: {},
swatches: null,
inline: false,
default: '#42445A',
defaultRepresentation: null,
position: 'bottom-middle',
adjustableNumbers: true,
showAlways: false,
closeWithKey: 'Escape'
}, opt);
const {swatches, inline, components, position} = opt;
// Check interaction section
if (!components.interaction) {
components.interaction = {};
}
// Overwrite palette if preview, opacity or hue are true
const {preview, opacity, hue, palette} = components;
components.palette = palette || preview || opacity || hue;
// Per default enabled if inline
if (inline) {
opt.showAlways = true;
}
// Initialize picker
this._preBuild();
this._buildComponents();
this._bindEvents();
// Finalize build
this._finalBuild();
// Append pre-defined swatch colors
if (swatches && swatches.length) {
swatches.forEach(color => this.addSwatch(color));
}
// Initialize positioning engine
this._nanopop = Nanopop({
reference: this._root.button,
el: this._root.app,
pos: position
});
// Initilization is finish, pickr is visible and ready for usage
const {button} = this._root;
const that = this;
requestAnimationFrame((function cb() {
// offsetParent of body is always 0. So check if it is the body
if (button.offsetParent === null && button !== document.body) {
return requestAnimationFrame(cb);
}
// Apply default color
that.setColor(opt.default);
that._rePositioningPicker();
// Initialize color representation
if (opt.defaultRepresentation) {
that._representation = opt.defaultRepresentation;
that.setColorRepresentation(that._representation);
}
// Show pickr if locked
if (opt.showAlways) {
that.show();
}
// Initialization is done - pickr is usable, fire init event
that._initializingActive = false;
that._emit('init');
}));
}
// Does only the absolutly basic thing to initialize the components
_preBuild() {
const opt = this.options;
// Check if element is selector
if (typeof opt.el === 'string') {
// Resolve possible shadow dom access
opt.el = opt.el.split(/>>/g).reduce((pv, cv, ci, a) => {
pv = pv.querySelector(cv);
return ci < a.length - 1 ? pv.shadowRoot : pv;
}, document);
}
// Create element and append it to body to
// prevent initialization errors
this._root = buildPickr(opt);
// Check if a custom button is used
if (opt.useAsButton) {
this._root.button = opt.el; // Replace button with customized button
}
document.body.appendChild(this._root.root);
}
_finalBuild() {
const opt = this.options;
const root = this._root;
// Remove from body
document.body.removeChild(root.root);
//if (opt.inline) {
const {parentElement} = opt.el;
if (parentElement.lastChild === opt.el) {
parentElement.appendChild(root.app);
} else {
parentElement.insertBefore(root.app, opt.el.nextSibling);
}
// } else {
// document.body.appendChild(root.app);
// }
// Don't replace the the element if a custom button is used
if (!opt.useAsButton) {
// Replace element with actual color-picker
opt.el.parentNode.replaceChild(root.root, opt.el);
}
// Call disable to also add the disabled class
if (opt.disabled) {
this.disable();
}
// Check if color comparison is disabled, if yes - remove transitions so everything keeps smoothly
if (!opt.comparison) {
root.button.style.transition = 'none';
if (!opt.useAsButton) {
root.preview.lastColor.style.transition = 'none';
}
}
this.hide();
}
_buildComponents() {
// Instance reference
const inst = this;
const comp = this.options.components;
const components = {
palette: Moveable({
element: inst._root.palette.picker,
wrapper: inst._root.palette.palette,
onchange(x, y) {
if (!comp.palette) return;
const {_color, _root, options} = inst;
// Calculate saturation based on the position
_color.s = (x / this.wrapper.offsetWidth) * 100;
// Calculate the value
_color.v = 100 - (y / this.wrapper.offsetHeight) * 100;
// Prevent falling under zero
_color.v < 0 ? _color.v = 0 : 0;
// Set picker and gradient color
const cssRGBaString = _color.toRGBA().toString();
this.element.style.background = cssRGBaString;
this.wrapper.style.background = `
linear-gradient(to top, rgba(0, 0, 0, ${_color.a}), transparent),
linear-gradient(to left, hsla(${_color.h}, 100%, 50%, ${_color.a}), rgba(255, 255, 255, ${_color.a}))
`;
// Check if color is locked
if (!options.comparison) {
_root.button.style.color = cssRGBaString;
if (!options.useAsButton) {
_root.preview.lastColor.style.color = cssRGBaString;
}
}
// Change current color
_root.preview.currentColor.style.color = cssRGBaString;
// Update the input field only if the user is currently not typing
if (inst._recalc) {
inst._updateOutput();
}
// If the user changes the color, remove the cleared icon
_root.button.classList.remove('clear');
}
}),
hue: Moveable({
lockX: true,
element: inst._root.hue.picker,
wrapper: inst._root.hue.slider,
onchange(x, y) {
if (!comp.hue || !comp.palette) return;
// Calculate hue
inst._color.h = (y / this.wrapper.offsetHeight) * 360;
// Update color
this.element.style.backgroundColor = `hsl(${inst._color.h}, 100%, 50%)`;
components.palette.trigger();
}
}),
opacity: Moveable({
lockX: true,
element: inst._root.opacity.picker,
wrapper: inst._root.opacity.slider,
onchange(x, y) {
if (!comp.opacity || !comp.palette) return;
// Calculate opacity
inst._color.a = Math.round(((y / this.wrapper.offsetHeight)) * 1e2) / 100;
// Update color
this.element.style.background = `rgba(0, 0, 0, ${inst._color.a})`;
inst.components.palette.trigger();
}
}),
selectable: Selectable({
elements: inst._root.interaction.options,
className: 'active',
onchange(e) {
inst._representation = e.target.getAttribute('data-type').toUpperCase();
inst._updateOutput();
}
})
};
this.components = components;
}
_bindEvents() {
const {_root, options} = this;
const eventBindings = [
// Clear color
_.on(_root.interaction.clear, 'click', () => this._clearColor()),
// Select last color on click
_.on(_root.preview.lastColor, 'click', () => this.setHSVA(...this._lastColor.toHSVA())),
// Save color
_.on(_root.interaction.save, 'click', () => {
!this.applyColor() && !options.showAlways && this.hide();
}),
// Detect user input and disable auto-recalculation
_.on(_root.interaction.result, ['keyup', 'input'], e => {
this._recalc = false;
// Fire listener if initialization is finish and changed color was valid
if (this.setColor(e.target.value, true) && !this._initializingActive) {
this._emit('change', this._color);
}
e.stopImmediatePropagation();
}),
// Cancel input detection on color change
_.on([
_root.palette.palette,
_root.palette.picker,
_root.hue.slider,
_root.hue.picker,
_root.opacity.slider,
_root.opacity.picker
], ['mousedown', 'touchstart'], () => this._recalc = true)
];
// Provide hiding / showing abilities only if showAlways is false
if (!options.showAlways) {
const ck = options.closeWithKey;
eventBindings.push(
// Save and hide / show picker
_.on(_root.button, 'click', () => this.isOpen() ? this.hide() : this.show()),
// Close with escape key
_.on(document, 'keyup', e => this.isOpen() && (e.key === ck || e.code === ck) && this.hide()),
// Cancel selecting if the user taps behind the color picker
_.on(document, ['touchstart', 'mousedown'], e => {
if (this.isOpen() && !_.eventPath(e).some(el => el === _root.app || el === _root.button)) {
this.hide();
}
}, {capture: true})
);
}
// Make input adjustable if enabled
if (options.adjustableNumbers) {
_.adjustableInputNumbers(_root.interaction.result, false);
}
if (!options.inline) {
let timeout = null;
const that = this;
// Re-calc position on window resize, scroll and wheel
eventBindings.push(
_.on(window, ['scroll', 'resize'], () => {
if (that.isOpen()) {
if (timeout === null) {
timeout = setTimeout(() => timeout = null, 100);
// Update position on every frame
requestAnimationFrame(function rs() {
that._rePositioningPicker();
(timeout !== null) && requestAnimationFrame(rs);
});
} else {
clearTimeout(timeout);
timeout = setTimeout(() => timeout = null, 100);
}
}
})
);
}
// Save bindings
this._eventBindings = eventBindings;
}
_rePositioningPicker() {
// No repositioning needed if inline
if (!this.options.inline) {
this._nanopop.update();
}
}
_updateOutput() {
// Check if component is present
if (this._root.interaction.type()) {
// Construct function name and call if present
const method = `to${this._root.interaction.type().getAttribute('data-type')}`;
this._root.interaction.result.value = typeof this._color[method] === 'function' ? this._color[method]().toString() : '';
}
// Fire listener if initialization is finish
if (!this._initializingActive) {
this._emit('change', this._color);
}
}
_clearColor() {
const {_root, options} = this;
// Change only the button color if it isn't customized
if (!options.useAsButton) {
_root.button.style.color = 'rgba(0, 0, 0, 0.15)';
}
_root.button.classList.add('clear');
if (!options.showAlways) {
this.hide();
}
if (!this._initializingActive) {
// Fire listener
this._emit('save', null);
}
}
_emit(event, ...args) {
this._eventListener[event].forEach(cb => cb(...args, this));
}
on(event, cb) {
// Validate
if (typeof cb === 'function' && typeof event === 'string' && event in this._eventListener) {
this._eventListener[event].push(cb);
}
return this;
}
off(event, cb) {
const callBacks = this._eventListener[event];
if (callBacks) {
const index = callBacks.indexOf(cb);
if (~index) {
callBacks.splice(index, 1);
}
}
return this;
}
/**
* Appends a color to the swatch palette
* @param color
* @returns {boolean}
*/
addSwatch(color) {
const {values} = Color.parseToHSV(color);
if (values) {
const {_swatchColors, _root} = this;
const hsvaColorObject = HSVaColor(...values);
// Create new swatch HTMLElement
const element = _.createElementFromString(
`<button type="button" style="color: ${hsvaColorObject.toRGBA()}"></button>`
);
// Append element and save swatch data
_root.swatches.appendChild(element);
_swatchColors.push({element, hsvaColorObject});
// Bind event
this._eventBindings.push(
_.on(element, 'click', () => {
this.setHSVA(...hsvaColorObject.toHSVA(), true);
this._emit('swatchselect', hsvaColorObject);
})
);
return true;
}
return false;
}
/**
* Removes a swatch color by it's index
* @param index
* @returns {boolean}
*/
removeSwatch(index) {
// Validate index
if (typeof index === 'number') {
const swatchColor = this._swatchColors[index];
// Check swatch data
if (swatchColor) {
const {element} = swatchColor;
// Remove HTML child and swatch data
this._root.swatches.removeChild(element);
this._swatchColors.splice(index, 1);
return true;
}
}
return false;
}
applyColor(silent = false) {
const {preview, button} = this._root;
// Change preview and current color
const cssRGBaString = this._color.toRGBA().toString();
preview.lastColor.style.color = cssRGBaString;
// Change only the button color if it isn't customized
if (!this.options.useAsButton) {
button.style.color = cssRGBaString;
}
// User changed the color so remove the clear clas
button.classList.remove('clear');
// Save last color
this._lastColor = this._color.clone();
// Fire listener
if (!this._initializingActive && !silent) {
this._emit('save', this._color);
}
}
/**
* Destroy's all functionalitys
*/
destroy() {
this._eventBindings.forEach(args => _.off(...args));
Object.keys(this.components).forEach(key => this.components[key].destroy());
}
/**
* Destroy's all functionalitys and removes
* the pickr element.
*/
destroyAndRemove() {
this.destroy();
// Remove element
const root = this._root.root;
root.parentElement.removeChild(root);
// remove .pcr-app
const app = this._root.app;
app.parentElement.removeChild(app);
// There are references to various DOM elements stored in the pickr instance
// This cleans all of them to avoid detached DOMs
const pickr = this;
Object.keys(pickr).forEach(key => pickr[key] = null);
}
/**
* Hides the color-picker ui.
*/
hide() {
this._root.app.classList.remove('visible');
return this;
}
/**
* Shows the color-picker ui.
*/
show() {
if (this.options.disabled) return;
this._root.app.classList.add('visible');
this._rePositioningPicker();
return this;
}
/**
* @return {boolean} If the color picker is currently open
*/
isOpen() {
return this._root.app.classList.contains('visible');
}
/**
* Set a specific color.
* @param h Hue
* @param s Saturation
* @param v Value
* @param a Alpha channel (0 - 1)
* @param silent If the button should not change the color
* @return boolean if the color has been accepted
*/
setHSVA(h = 360, s = 0, v = 0, a = 1, silent = false) {
// Deactivate color calculation
const recalc = this._recalc; // Save state
this._recalc = false;
// Validate input
if (h < 0 || h > 360 || s < 0 || s > 100 || v < 0 || v > 100 || a < 0 || a > 1) {
return false;
}
// Override current color and re-active color calculation
this._color = HSVaColor(h, s, v, a);
// Short names
const {hue, opacity, palette} = this.components;
// Calculate y position of hue slider
const hueWrapper = hue.options.wrapper;
const hueY = hueWrapper.offsetHeight * (h / 360);
hue.update(0, hueY);
// Calculate y position of opacity slider
const opacityWrapper = opacity.options.wrapper;
const opacityY = opacityWrapper.offsetHeight * a;
opacity.update(0, opacityY);
// Calculate y and x position of color palette
const pickerWrapper = palette.options.wrapper;
const pickerX = pickerWrapper.offsetWidth * (s / 100);
const pickerY = pickerWrapper.offsetHeight * (1 - (v / 100));
palette.update(pickerX, pickerY);
// Restore old state
this._recalc = recalc;
// Update output if recalculation is enabled
if (this._recalc) {
this._updateOutput();
}
// Check if call is silent
if (!silent) {
this.applyColor();
}
return true;
}
/**
* Tries to parse a string which represents a color.
* Examples: #fff
* rgb 10 10 200
* hsva 10 20 5 0.5
* @param string
* @param silent
*/
setColor(string, silent = false) {
// Check if null
if (string === null) {
this._clearColor();
return true;
}
const {values, type} = Color.parseToHSV(string);
// Check if color is ok
if (values) {
// Change selected color format
const utype = type.toUpperCase();
const {options} = this._root.interaction;
const target = options.find(el => el.getAttribute('data-type').startsWith(utype));
// Auto select only if not hidden
if (target && !target.hidden) {
for (const el of options) {
el.classList[el === target ? 'add' : 'remove']('active');
}
}
return this.setHSVA(...values, silent);
}
}
/**
* Changes the color _representation.
* Allowed values are HEX, RGB, HSV, HSL and CMYK
* @param type
* @returns {boolean} if the selected type was valid.
*/
setColorRepresentation(type) {
// Force uppercase to allow a case-sensitiv comparison
type = type.toUpperCase();
// Find button with given type and trigger click event
return !!this._root.interaction.options.find(v => v.getAttribute('data-type').startsWith(type) && !v.click());
}
/**
* Returns the current color representaion. See setColorRepresentation
* @returns {*}
*/
getColorRepresentation() {
return this._representation;
}
/**
* @returns HSVaColor Current HSVaColor object.
*/
getColor() {
return this._color;
}
/**
* @returns The root HTMLElement with all his components.
*/
getRoot() {
return this._root;
}
/**
* Disable pickr
*/
disable() {
this.hide();
this.options.disabled = true;
this._root.button.classList.add('disabled');
return this;
}
/**
* Enable pickr
*/
enable() {
this.options.disabled = false;
this._root.button.classList.remove('disabled');
return this;
}
}
// Expose pickr utils
Pickr.utils = _;
// Create instance via method
Pickr.create = options => new Pickr(options);
// Assign version and export
Pickr.version = '0.6.2';
export default Pickr;

View File

@ -0,0 +1,60 @@
import * as _ from './utils/utils';
export default ({components: c, strings: s, useAsButton, inline, appClass, width}) => {
const hidden = con => con ? '' : 'style="display:none" hidden';
const root = _.createFromTemplate(`
<div data-key="root" class="pickr">
${useAsButton ? '' : '<button type="button" data-key="button" class="pcr-button"></button>'}
<div data-key="app" class="pcr-app ${appClass || ''}" ${`style="${inline ? 'position: unset;':''}${width?`width:${width};`:''}"`}>
<div class="pcr-selection" ${hidden(c.palette)}>
<div data-con="preview" class="pcr-color-preview" ${hidden(c.preview)}>
<button type="button" data-key="lastColor" class="pcr-last-color"></button>
<div data-key="currentColor" class="pcr-current-color"></div>
</div>
<div data-con="palette" class="pcr-color-palette">
<div data-key="picker" class="pcr-picker"></div>
<div data-key="palette" class="pcr-palette"></div>
</div>
<div data-con="hue" class="pcr-color-chooser" ${hidden(c.hue)}>
<div data-key="picker" class="pcr-picker"></div>
<div data-key="slider" class="pcr-hue pcr-slider"></div>
</div>
<div data-con="opacity" class="pcr-color-opacity" ${hidden(c.opacity)}>
<div data-key="picker" class="pcr-picker"></div>
<div data-key="slider" class="pcr-opacity pcr-slider"></div>
</div>
</div>
<div class="pcr-swatches ${c.palette ? '' : ' pcr-last'}" data-key="swatches"></div>
<div data-con="interaction" class="pcr-interaction" ${hidden(Object.keys(c.interaction).length)}>
<input data-key="result" class="pcr-result" type="text" spellcheck="false" ${hidden(c.interaction.input)}>
<input data-arr="options" class="pcr-type" data-type="HEXA" value="HEXA" type="button" ${hidden(c.interaction.hex)}>
<input data-arr="options" class="pcr-type" data-type="RGBA" value="RGBA" type="button" ${hidden(c.interaction.rgba)}>
<input data-arr="options" class="pcr-type" data-type="HSLA" value="HSLA" type="button" ${hidden(c.interaction.hsla)}>
<input data-arr="options" class="pcr-type" data-type="HSVA" value="HSVA" type="button" ${hidden(c.interaction.hsva)}>
<input data-arr="options" class="pcr-type" data-type="CMYK" value="CMYK" type="button" ${hidden(c.interaction.cmyk)}>
<input data-key="save" class="pcr-save" value="${s.save || 'Save'}" type="button" ${hidden(c.interaction.save)}>
<input data-key="clear" class="pcr-clear" value="${s.clear || 'Clear'}" type="button" ${hidden(c.interaction.clear)}>
</div>
</div>
</div>
`);
const int = root.interaction;
// Select option which is not hidden
int.options.find(o => !o.hidden && !o.classList.add('active'));
// Create method to find currenlty active option
int.type = () => int.options.find(e => e.classList.contains('active'));
return root;
}

View File

@ -0,0 +1,301 @@
// Shorthands
const {min, max, floor, round} = Math;
/**
* Tries to convert a color name to rgb/a hex representation
* @param name
* @returns {string | CanvasGradient | CanvasPattern}
*/
function standardizeColor(name) {
const ctx = document.createElement('canvas').getContext('2d');
ctx.fillStyle = name;
return ctx.fillStyle;
}
/**
* Convert HSV spectrum to RGB.
* @param h Hue
* @param s Saturation
* @param v Value
* @returns {number[]} Array with rgb values.
*/
export function hsvToRgb(h, s, v) {
h = (h / 360) * 6;
s /= 100;
v /= 100;
let i = floor(h);
let f = h - i;
let p = v * (1 - s);
let q = v * (1 - f * s);
let t = v * (1 - (1 - f) * s);
let mod = i % 6;
let r = [v, q, p, p, t, v][mod];
let g = [t, v, v, q, p, p][mod];
let b = [p, p, t, v, v, q][mod];
return [
r * 255,
g * 255,
b * 255
];
}
/**
* Convert HSV spectrum to Hex.
* @param h Hue
* @param s Saturation
* @param v Value
* @returns {string[]} Hex values
*/
export function hsvToHex(h, s, v) {
return hsvToRgb(h, s, v).map(v =>
round(v).toString(16).padStart(2, '0')
);
}
/**
* Convert HSV spectrum to CMYK.
* @param h Hue
* @param s Saturation
* @param v Value
* @returns {number[]} CMYK values
*/
export function hsvToCmyk(h, s, v) {
const rgb = hsvToRgb(h, s, v);
const r = rgb[0] / 255;
const g = rgb[1] / 255;
const b = rgb[2] / 255;
let k, c, m, y;
k = min(1 - r, 1 - g, 1 - b);
c = k === 1 ? 0 : (1 - r - k) / (1 - k);
m = k === 1 ? 0 : (1 - g - k) / (1 - k);
y = k === 1 ? 0 : (1 - b - k) / (1 - k);
return [
c * 100,
m * 100,
y * 100,
k * 100
];
}
/**
* Convert HSV spectrum to HSL.
* @param h Hue
* @param s Saturation
* @param v Value
* @returns {number[]} HSL values
*/
export function hsvToHsl(h, s, v) {
s /= 100, v /= 100;
let l = (2 - s) * v / 2;
if (l !== 0) {
if (l === 1) {
s = 0;
} else if (l < 0.5) {
s = s * v / (l * 2);
} else {
s = s * v / (2 - l * 2);
}
}
return [
h,
s * 100,
l * 100
];
}
/**
* Convert RGB to HSV.
* @param r Red
* @param g Green
* @param b Blue
* @return {number[]} HSV values.
*/
function rgbToHsv(r, g, b) {
r /= 255, g /= 255, b /= 255;
let h, s, v;
const minVal = min(r, g, b);
const maxVal = max(r, g, b);
const delta = maxVal - minVal;
v = maxVal;
if (delta === 0) {
h = s = 0;
} else {
s = delta / maxVal;
let dr = (((maxVal - r) / 6) + (delta / 2)) / delta;
let dg = (((maxVal - g) / 6) + (delta / 2)) / delta;
let db = (((maxVal - b) / 6) + (delta / 2)) / delta;
if (r === maxVal) {
h = db - dg;
} else if (g === maxVal) {
h = (1 / 3) + dr - db;
} else if (b === maxVal) {
h = (2 / 3) + dg - dr;
}
if (h < 0) {
h += 1;
} else if (h > 1) {
h -= 1;
}
}
return [
h * 360,
s * 100,
v * 100
];
}
/**
* Convert CMYK to HSV.
* @param c Cyan
* @param m Magenta
* @param y Yellow
* @param k Key (Black)
* @return {number[]} HSV values.
*/
function cmykToHsv(c, m, y, k) {
c /= 100, m /= 100, y /= 100, k /= 100;
const r = (1 - min(1, c * (1 - k) + k)) * 255;
const g = (1 - min(1, m * (1 - k) + k)) * 255;
const b = (1 - min(1, y * (1 - k) + k)) * 255;
return [...rgbToHsv(r, g, b)];
}
/**
* Convert HSL to HSV.
* @param h Hue
* @param s Saturation
* @param l Lightness
* @return {number[]} HSV values.
*/
function hslToHsv(h, s, l) {
s /= 100, l /= 100;
s *= l < 0.5 ? l : 1 - l;
let ns = (2 * s / (l + s)) * 100;
let v = (l + s) * 100;
return [h, ns, v];
}
/**
* Convert HEX to HSV.
* @param hex Hexadecimal string of rgb colors, can have length 3 or 6.
* @return {number[]} HSV values.
*/
function hexToHsv(hex) {
return rgbToHsv(...hex.match(/.{2}/g).map(v => parseInt(v, 16)));
}
/**
* Try's to parse a string which represents a color to a HSV array.
* Current supported types are cmyk, rgba, hsla and hexadecimal.
* @param str
* @return {*}
*/
export function parseToHSV(str) {
// Check if string is a color-name
str = str.match(/^[a-zA-Z]+$/) ? standardizeColor(str) : str;
// Regular expressions to match different types of color represention
const regex = {
cmyk: /^cmyk[\D]+(\d+)[\D]+(\d+)[\D]+(\d+)[\D]+(\d+)/i,
rgba: /^(rgb|rgba)[\D]+(\d+)[\D]+(\d+)[\D]+(\d+)[\D]*?([\d.]+|$)/i,
hsla: /^(hsl|hsla)[\D]+(\d+)[\D]+(\d+)[\D]+(\d+)[\D]*?([\d.]+|$)/i,
hsva: /^(hsv|hsva)[\D]+(\d+)[\D]+(\d+)[\D]+(\d+)[\D]*?([\d.]+|$)/i,
hex: /^#?(([\dA-Fa-f]{3,4})|([\dA-Fa-f]{6})|([\dA-Fa-f]{8}))$/i
};
/**
* Takes an Array of any type, convert strings which represents
* a number to a number an anything else to undefined.
* @param array
* @return {*}
*/
const numarize = array => array.map(v => /^(|\d+)\.\d+|\d+$/.test(v) ? Number(v) : undefined);
let match;
for (const type in regex) {
// Check if current scheme passed
if (!(match = regex[type].exec(str)))
continue;
// Try to convert
switch (type) {
case 'cmyk': {
let [, c, m, y, k] = numarize(match);
if (c > 100 || m > 100 || y > 100 || k > 100)
break;
return {values: [...cmykToHsv(c, m, y, k), 1], type};
}
case 'rgba': {
let [, , r, g, b, a = 1] = numarize(match);
if (r > 255 || g > 255 || b > 255 || a < 0 || a > 1)
break;
return {values: [...rgbToHsv(r, g, b), a], type};
}
case 'hex': {
const splitAt = (s, i) => [s.substring(0, i), s.substring(i, s.length)];
let [, hex] = match;
// Fill up opacity if not declared
if (hex.length === 3) {
hex += 'F';
} else if (hex.length === 6) {
hex += 'FF';
}
let alpha;
if (hex.length === 4) {
[hex, alpha] = splitAt(hex, 3).map(v => v + v);
} else if (hex.length === 8) {
[hex, alpha] = splitAt(hex, 6);
}
// Convert 0 - 255 to 0 - 1 for opacity
alpha = parseInt(alpha, 16) / 255;
return {values: [...hexToHsv(hex), alpha], type};
}
case 'hsla': {
let [, , h, s, l, a = 1] = numarize(match);
if (h > 360 || s > 100 || l > 100 || a < 0 || a > 1)
break;
return {values: [...hslToHsv(h, s, l), a], type};
}
case 'hsva': {
let [, , h, s, v, a = 1] = numarize(match);
if (h > 360 || s > 100 || v > 100 || a < 0 || a > 1)
break;
return {values: [h, s, v, a], type};
}
}
}
return {values: null, type: null};
}

View File

@ -0,0 +1,71 @@
import {hsvToHsl, hsvToRgb, hsvToCmyk, hsvToHex} from './color';
/**
* Simple class which holds the properties
* of the color represention model hsla (hue saturation lightness alpha)
*/
export function HSVaColor(h = 0, s = 0, v = 0, a = 1) {
const {ceil} = Math;
const that = {
h, s, v, a,
toHSVA() {
const hsva = [that.h, that.s, that.v];
const rhsva = hsva.map(ceil);
hsva.push(that.a);
hsva.toString = () => `hsva(${rhsva[0]}, ${rhsva[1]}%, ${rhsva[2]}%, ${that.a.toFixed(1)})`;
return hsva;
},
toHSLA() {
const hsla = hsvToHsl(that.h, that.s, that.v);
const rhsla = hsla.map(ceil);
hsla.push(that.a);
hsla.toString = () => `hsla(${rhsla[0]}, ${rhsla[1]}%, ${rhsla[2]}%, ${that.a.toFixed(1)})`;
return hsla;
},
toRGBA() {
const rgba = hsvToRgb(that.h, that.s, that.v);
const rrgba = rgba.map(ceil);
rgba.push(that.a);
rgba.toString = () => `rgba(${rrgba[0]}, ${rrgba[1]}, ${rrgba[2]}, ${that.a.toFixed(1)})`;
return rgba;
},
toCMYK() {
const cmyk = hsvToCmyk(that.h, that.s, that.v);
const rcmyk = cmyk.map(ceil);
cmyk.toString = () => `cmyk(${rcmyk[0]}%, ${rcmyk[1]}%, ${rcmyk[2]}%, ${rcmyk[3]}%)`;
return cmyk;
},
toHEXA() {
const hex = hsvToHex(that.h, that.s, that.v);
hex.toString = () => {
// Check if alpha channel make sense, convert it to 255 number space, convert
// to hex and pad it with zeros if needet.
const alpha = that.a >= 1 ? '' : Number((that.a * 255).toFixed(0))
.toString(16)
.toUpperCase().padStart(2, '0');
return `#${hex.join('').toUpperCase() + alpha}`;
};
return hex;
},
clone() {
return HSVaColor(that.h, that.s, that.v, that.a);
}
};
return that;
}

View File

@ -0,0 +1,189 @@
/**
* Add event(s) to element(s).
* @param elements DOM-Elements
* @param events Event names
* @param fn Callback
* @param options Optional options
* @return Array passed arguments
*/
export const on = eventListener.bind(null, 'addEventListener');
/**
* Remove event(s) from element(s).
* @param elements DOM-Elements
* @param events Event names
* @param fn Callback
* @param options Optional options
* @return Array passed arguments
*/
export const off = eventListener.bind(null, 'removeEventListener');
function eventListener(method, elements, events, fn, options = {}) {
// Normalize array
if (elements instanceof HTMLCollection || elements instanceof NodeList) {
elements = Array.from(elements);
} else if (!Array.isArray(elements)) {
elements = [elements];
}
if (!Array.isArray(events)) {
events = [events];
}
for (const el of elements) {
for (const ev of events) {
el[method](ev, fn, {capture: false, ...options});
}
}
return Array.prototype.slice.call(arguments, 1);
}
/**
* Creates an DOM-Element out of a string (Single element).
* @param html HTML representing a single element
* @returns {Element | null} The element.
*/
export function createElementFromString(html) {
const div = document.createElement('div');
div.innerHTML = html.trim();
return div.firstElementChild;
}
/**
* Removes an attribute from a HTMLElement and returns the value.
* @param el
* @param name
* @return {string}
*/
export function removeAttribute(el, name) {
const value = el.getAttribute(name);
el.removeAttribute(name);
return value;
}
/**
* Creates a new html element, every element which has
* a 'data-key' attribute will be saved in a object (which will be returned)
* where the value of 'data-key' ist the object-key and the value the HTMLElement.
*
* It's possible to create a hierarchy if you add a 'data-con' attribute. Every
* sibling will be added to the object which will get the name from the 'data-con' attribute.
*
* If you want to create an Array out of multiple elements, you can use the 'data-arr' attribute,
* the value defines the key and all elements, which has the same parent and the same 'data-arr' attribute,
* would be added to it.
*
* @param str - The HTML String.
*/
export function createFromTemplate(str) {
// Recursive function to resolve template
function resolve(element, base = {}) {
// Check key and container attribute
const con = removeAttribute(element, 'data-con');
const key = removeAttribute(element, 'data-key');
// Check and save element
if (key) {
base[key] = element;
}
// Check all children
const subtree = con ? (base[con] = {}) : base;
for (let child of Array.from(element.children)) {
// Check if element should be saved as array
const arr = removeAttribute(child, 'data-arr');
if (arr) {
// Check if there is already an array and add element
(subtree[arr] || (subtree[arr] = [])).push(child);
} else {
resolve(child, subtree);
}
}
return base;
}
return resolve(createElementFromString(str));
}
/**
* Polyfill for safari & firefox for the eventPath event property.
* @param evt The event object.
* @return [String] event path.
*/
export function eventPath(evt) {
let path = evt.path || (evt.composedPath && evt.composedPath());
if (path) return path;
let el = evt.target.parentElement;
path = [evt.target, el];
while (el = el.parentElement) path.push(el);
path.push(document, window);
return path;
}
/**
* Creates the ability to change numbers in an input field with the scroll-wheel.
* @param el
* @param negative
*/
export function adjustableInputNumbers(el, negative = true) {
// Check if a char represents a number
const isNumChar = c => (c >= '0' && c <= '9') || c === '-' || c === '.';
function handleScroll(e) {
const val = el.value;
const off = el.selectionStart;
let numStart = off;
let num = ''; // Will be the number as string
// Look back
for (let i = off - 1; i > 0 && isNumChar(val[i]); i--) {
num = val[i] + num;
numStart--; // Find start of number
}
// Look forward
for (let i = off, n = val.length; i < n && isNumChar(val[i]); i++) {
num += val[i];
}
// Check if number is valid
if (num.length > 0 && !isNaN(num) && isFinite(num)) {
const mul = e.deltaY < 0 ? 1 : -1;
const inc = ([1, 10, 100])[Number(e.shiftKey || e.ctrlKey * 2)] * mul;
let newNum = Number(num) + inc;
if (!negative && newNum < 0) {
newNum = 0;
}
const newStr = val.substr(0, numStart) + newNum + val.substring(numStart + num.length, val.length);
const curPos = numStart + String(newNum).length;
// Update value and set cursor
el.value = newStr;
el.focus();
el.setSelectionRange(curPos, curPos);
}
// Prevent default
e.preventDefault();
// Trigger input event
el.dispatchEvent(new Event('input'));
}
// Bind events
on(el, 'focus', () => on(window, 'wheel', handleScroll, {passive: false}));
on(el, 'blur', () => off(window, 'wheel', handleScroll));
}

View File

@ -0,0 +1,21 @@
@import 'variables';
// Pseudo style reset
@mixin pseudo-reset {
position: absolute;
content: '';
top: 0;
left: 0;
}
@mixin transparency-background($size: 0.5em) {
&::before {
@include pseudo-reset;
width: 100%;
height: 100%;
background: $icon-transparency;
background-size: $size;
border-radius: $border-radius-mid;
z-index: -1;
}
}

View File

@ -0,0 +1,38 @@
// Font family
$font-family: -apple-system,
BlinkMacSystemFont,
"Segoe UI",
"Roboto",
"Helvetica Neue", Arial, sans-serif, !default;
// Colors
$palette-cloud-blue: #4285f4 !default;
$palette-soft-red: #f44250 !default;
$palette-snowwhite: #f1f3f4 !default;
$palette-lightgray: #c4c4c4 !default;
$palette-darkgray: #75797e !default;
// Constants
$box-shadow-app: 0 0.15em 1.5em 0 rgba(0, 0, 0, 0.1), 0 0 1em 0 rgba(0, 0, 0, 0.03) !default;
@mixin focus($color: rgba($palette-cloud-blue, 0.75)) {
box-shadow: 0 0 0 1px $palette-snowwhite, 0 0 0 3px $color;
}
$color-rainbow: linear-gradient(to bottom,
hsl(0, 100%, 50%),
hsl(60, 100%, 50%),
hsl(120, 100%, 50%),
hsl(180, 100%, 50%),
hsl(240, 100%, 50%),
hsl(300, 100%, 50%),
hsl(360, 100%, 50%)) !default;
// Box shadows
$box-shadow-small: 0 1px 2px 0 rgba(0, 0, 0, 0.2) !default;
// Border radius
$border-radius-mid: 0.15em !default;
// Inline SVG muster
$icon-transparency: url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2"><path fill="white" d="M1,0H2V1H1V0ZM0,1H1V2H0V1Z"/><path fill="gray" d="M0,0H1V1H0V0ZM1,1H2V2H1V1Z"/></svg>') !default;
$icon-x: url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" stroke="%2342445A" stroke-width="5px" stroke-linecap="round"><path d="M45,45L5,5"></path><path d="M45,5L5,45"></path></svg>') !default;

View File

@ -0,0 +1,325 @@
@import 'lib/variables';
@import 'lib/mixins';
.pickr {
position: relative;
overflow: visible;
transform: translateY(0); // Create local transform space
* {
box-sizing: border-box;
}
}
.pickr .pcr-button {
@include transparency-background;
position: relative;
height: 2em;
width: 2em;
padding: 0.5em;
cursor: pointer;
font-family: $font-family;
border-radius: $border-radius-mid;
background: $icon-x no-repeat center;
background-size: 0;
transition: all 0.3s;
&::before {
z-index: initial;
}
&::after {
@include pseudo-reset;
height: 100%;
width: 100%;
transition: background 0.3s;
background: currentColor;
border-radius: $border-radius-mid;
}
&.clear {
background-size: 70%;
&::before {
opacity: 0;
}
&:focus {
@include focus(currentColor);
}
}
&.disabled {
cursor: not-allowed;
}
}
.pickr,
.pcr-app {
input,
button {
outline: none;
border: none;
-webkit-appearance: none;
&:focus {
@include focus(currentColor);
}
}
}
.pcr-app {
position: fixed;
display: flex;
flex-direction: column;
z-index: 10000;
font-family: $font-family;
box-shadow: $box-shadow-app;
width: 22.5em;
max-width: 95vw;
padding: 0.8em;
border-radius: 0.1em;
background: #fff;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s;
left: 0;
top: 0;
&.visible {
visibility: visible;
opacity: 1;
}
}
.pcr-app .pcr-swatches {
// Flex fallback
display: flex;
flex-wrap: wrap;
margin-top: 0.75em;
&.pcr-last {
margin: 0;
}
@supports (display: grid) {
display: grid;
align-items: center;
justify-content: space-around;
grid-template-columns: repeat(auto-fit, 1.75em);
}
> button {
@include transparency-background(6px);
position: relative;
width: 1.75em;
height: 1.75em;
border-radius: 0.15em;
cursor: pointer;
margin: 2.5px;
flex-shrink: 0;
justify-self: center;
transition: all 0.15s;
overflow: hidden;
background: transparent;
z-index: 1;
&::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: currentColor;
border: 1px solid rgba(black, 0.05);
border-radius: 0.15em;
box-sizing: border-box;
}
&:hover {
filter: brightness(1.05);
}
}
}
.pcr-app .pcr-interaction {
display: flex;
flex-wrap: wrap;
align-items: center;
margin: 0 -0.2em 0 -0.2em;
> * {
margin: 0 0.2em;
}
input {
letter-spacing: 0.07em;
font-size: 0.75em;
text-align: center;
cursor: pointer;
color: $palette-darkgray;
background: $palette-snowwhite;
border-radius: $border-radius-mid;
transition: all 0.15s;
padding: 0.45em 0.5em;
margin-top: 0.75em;
&:hover {
filter: brightness(0.975);
}
&:focus {
@include focus();
}
}
.pcr-result {
color: $palette-darkgray;
text-align: left;
flex: 1 1 8em;
min-width: 8em;
transition: all 0.2s;
border-radius: $border-radius-mid;
background: $palette-snowwhite;
cursor: text;
&::selection {
background: $palette-cloud-blue;
color: #fff;
}
}
.pcr-type.active {
color: #fff;
background: $palette-cloud-blue;
}
.pcr-clear,
.pcr-save {
color: #fff;
width: auto;
}
.pcr-save,
.pcr-clear {
color: #fff;
&:hover {
filter: brightness(0.925);
}
}
.pcr-save {
background: $palette-cloud-blue;
}
.pcr-clear {
background: $palette-soft-red;
&:focus {
@include focus(rgba($palette-soft-red, 0.75));
}
}
}
.pcr-app .pcr-selection {
display: flex;
justify-content: space-between;
flex-grow: 1;
.pcr-picker {
position: absolute;
height: 18px;
width: 18px;
border: 2px solid #fff;
border-radius: 100%;
user-select: none;
}
.pcr-color-preview {
@include transparency-background;
position: relative;
z-index: 1;
width: 2em;
display: flex;
flex-direction: column;
justify-content: space-between;
margin-right: 0.75em;
.pcr-last-color {
cursor: pointer;
transition: background-color 0.3s, box-shadow 0.3s;
border-radius: 0.15em 0.15em 0 0;
z-index: 2;
}
.pcr-current-color {
border-radius: 0 0 0.15em 0.15em;
}
.pcr-last-color,
.pcr-current-color {
background: currentColor;
width: 100%;
height: 50%;
}
}
.pcr-color-palette,
.pcr-color-chooser,
.pcr-color-opacity {
position: relative;
user-select: none;
display: flex;
flex-direction: column;
cursor: grab;
cursor: -moz-grab;
cursor: -webkit-grab;
&:active {
cursor: grabbing;
cursor: -moz-grabbing;
cursor: -webkit-grabbing;
}
}
.pcr-color-palette {
width: 100%;
height: 8em;
z-index: 1;
.pcr-palette {
flex-grow: 1;
border-radius: $border-radius-mid;
@include transparency-background;
}
}
.pcr-color-chooser,
.pcr-color-opacity {
margin-left: 0.75em;
.pcr-picker {
left: 50%;
transform: translateX(-50%);
}
.pcr-slider {
width: 8px;
flex-grow: 1;
border-radius: 50em;
}
}
.pcr-color-chooser .pcr-slider {
background: $color-rainbow;
}
.pcr-color-opacity .pcr-slider {
background: linear-gradient(to bottom, transparent, black), $icon-transparency;
background-size: 100%, 50%;
}
}

View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"baseUrl": ".",
"experimentalDecorators": true,
"jsx": "react",
"jsxFactory": "h",
"target": "es5",
"outDir": "dist",
"allowJs": true
},
"include": [
"src/**/*"
]
}

View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"baseUrl": ".",
"experimentalDecorators": true,
"jsx": "react",
"jsxFactory": "h",
"target": "es5",
"allowJs": true,
"declaration": true
},
"include": [
"src/**/*"
]
}

View File

@ -0,0 +1,93 @@
## ColorPicker
Color Picker
<iframe height="351" style="width: 100%;" scrolling="no" title="OMIU ColorPicker" src="https://codepen.io/omijs/embed/gOaWmZE?height=351&theme-id=default&default-tab=html,result" frameborder="no" allowtransparency="true" allowfullscreen="true" loading="lazy">
See the Pen <a href='https://codepen.io/omijs/pen/gOaWmZE'>OMIU Checkbox</a> by OMI
(<a href='https://codepen.io/omijs'>@omijs</a>) on <a href='https://codepen.io'>CodePen</a>.
</iframe>
## Import
```js
import '@omiu/color-picker'
```
Or use script tag to ref it.
```html
<script src="https://unpkg.com/@omiu/color-picker"></script>
```
## Usage
```html
<o-color-picker></o-color-picker>
```
## API
### Props
```tsx
{
button?: boolean,
preview?: boolean,
opacity?: boolean,
hue?: boolean,
hex?: boolean,
rgba?: boolean,
hsla?: boolean,
hsva?: boolean,
input?: boolean,
clear?: boolean,
save?: boolean,
cmyk?: boolean,
default?: string,
swatches?: string[],
inline?: boolean,
strings?: {
save: string, // Default for save button
clear: string // Default for clear button
}
```
### 默认属性
```tsx
{
button: true,
preview: true,
opacity: true,
hue: true,
hex: true,
rgba: true,
hsla: true,
hsva: false,
input: true,
clear: true,
save: true,
cmyk: false,
default: '#07c160',
swatches: [],
inline: true,
strings: {
save: 'Save', // Default for save button
clear: 'Clear' // Default for clear button
)
```
### Events
* init
* save
* change
* swatchselect

View File

@ -0,0 +1,92 @@
## ColorPicker 颜色选择器
Color Picker
<iframe height="351" style="width: 100%;" scrolling="no" title="OMIU ColorPicker" src="https://codepen.io/omijs/embed/gOaWmZE?height=351&theme-id=default&default-tab=html,result" frameborder="no" allowtransparency="true" allowfullscreen="true" loading="lazy">
See the Pen <a href='https://codepen.io/omijs/pen/gOaWmZE'>OMIU Checkbox</a> by OMI
(<a href='https://codepen.io/omijs'>@omijs</a>) on <a href='https://codepen.io'>CodePen</a>.
</iframe>
## 导入
```js
import '@omiu/color-picker'
```
或者直接 script 标签引入。
```html
<script src="https://unpkg.com/@omiu/color-picker"></script>
```
## 使用
```html
<o-color-picker> </o-color-picker>
```
## API
### 属性
```tsx
{
button?: boolean,
preview?: boolean,
opacity?: boolean,
hue?: boolean,
hex?: boolean,
rgba?: boolean,
hsla?: boolean,
hsva?: boolean,
input?: boolean,
clear?: boolean,
save?: boolean,
cmyk?: boolean,
default?: string,
swatches?: string[],
inline?: boolean,
strings?: {
save: string, // Default for save button
clear: string // Default for clear button
}
```
### 默认属性
```tsx
{
button: true,
preview: true,
opacity: true,
hue: true,
hex: true,
rgba: true,
hsla: true,
hsva: false,
input: true,
clear: true,
save: true,
cmyk: false,
default: '#07c160',
swatches: [],
inline: true,
strings: {
save: 'Save', // Default for save button
clear: 'Clear' // Default for clear button
)
```
### 事件
* init
* save
* change
* swatchselect