diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 31822eeb..00000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Lint - -on: [push] - -jobs: - vue-cli: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - name: Install - run: | - npm ci - - name: Lint - run: | - npm run lint \ No newline at end of file diff --git a/.github/workflows/npm-deploy.yml b/.github/workflows/npm-deploy.yml index 93e1e837..a006afab 100644 --- a/.github/workflows/npm-deploy.yml +++ b/.github/workflows/npm-deploy.yml @@ -9,16 +9,16 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - name: Install - run: | - npm ci - - name: Build - run: | - npm run build - - name: Publish - run: | - echo "//registry.npmjs.org/:_authToken=$NPM_AUTH_TOKEN" > .npmrc - npm publish || true - env: - NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} \ No newline at end of file + - uses: actions/checkout@v1 + - name: Install + run: | + npm ci + - name: Build + run: | + npm run build + - name: Publish + run: | + echo "//registry.npmjs.org/:_authToken=$NPM_AUTH_TOKEN" > .npmrc + npm publish || true + env: + NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} diff --git a/.github/workflows/push-process-todo-comments.yml b/.github/workflows/push-process-todo-comments.yml index a5bb81e4..190b2575 100644 --- a/.github/workflows/push-process-todo-comments.yml +++ b/.github/workflows/push-process-todo-comments.yml @@ -13,4 +13,4 @@ jobs: uses: dtinth/todo-actions@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TODO_ACTIONS_MONGO_URL: ${{ secrets.TODO_ACTIONS_MONGO_URL }} \ No newline at end of file + TODO_ACTIONS_MONGO_URL: ${{ secrets.TODO_ACTIONS_MONGO_URL }} diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml new file mode 100644 index 00000000..b3ed9b36 --- /dev/null +++ b/.github/workflows/push.yml @@ -0,0 +1,25 @@ +name: Push + +on: [push] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Install + run: | + npm ci + - name: Lint + run: | + npm run lint + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Install + run: | + npm ci + - name: Build + run: | + npm run build diff --git a/.mergify.yml b/.mergify.yml index 75f4a442..717506e0 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -3,7 +3,7 @@ pull_request_rules: conditions: - author=greenkeeper[bot] - label~=greenkeeper - - status-success~=Lint + - status-success~=Push - status-success~=WIP actions: merge: diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..65300ef2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,19 @@ +# 0.3.0 + +**Additions** + +- add native WebComponents support including attribute type conversion. + +**Changes** + +- added `options` attribute where all options (`colorLeft`, `colorRight`, ...) now must be placed in. The old Attributes were removed. + +# 0.2.0 + +**Fixes** + +- channel value `[1,1]` was not reachable. The top center value is therefore now `[1,1]` + +# 0.1.0 + +- Initial Release, may be buggy, unstable and not ready for production until version 1.0.0 is reached. diff --git a/README.md b/README.md index 0097b1e2..da7089bd 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,25 @@ > A simple two channel Color Picker -![two channel picker screenshot](docs/img/screenshot.png) +## About + +I needed an intuitive color picker to control my warm-white-cold-white (WWCW) LED Strips with the [SmartLight Project](https://github.com/adrianjost/SmartLight-Web-Client). + +

+ two channel picker screenshot +

+ +The picker value is therefore indepent from the displayed color. The given value is a number between 0 and 1 for each of the two channels. The brightness can be determined by dragging the slider along the Y-axis. Dragging along the X-axis changes the relationship between the two channels. + +

+ two channel picker screenshot +

## Usage -### 1. Install +### Vue + +Install the package: ```bash npm i @adrianjost/two-channel-picker @@ -18,29 +32,74 @@ or yarn add @adrianjost/two-channel-picker ``` -### 2. Usage in Component (Vue) +In your App: ```vue +``` + +### As a native Web Component + +```html + + +
+ +
+ + -``` \ No newline at end of file +``` + +> please note, that you may need to adjust the version number in the url. + +## API + +### Props + +You can customize the picker with the following props/attributes. + +> When using the lib as a web component you must provide all attributes `JSON.stringify()`-ed. + +| attribute | type | default value | description | +| --- | --- | --- | --- | +| `value` / `v-model` | Array, String | `[1,0]` | the current channel values, if provided as a String, this must be `JSON.parse()`-able | +| `options` | Object | `{}` | all your config goes in here | + +#### Options + +Available Attributes in the `options` prop: + +| attribute | type | default value | description | +| --- | --- | --- | --- | +| `readOnly` | Boolean | `false` | should the user be able to move the marker? | +| `colorLeft` | String | `#fd9` | the color in the top left corner. Must be in the HEX Format with 3 or 6 digits. | +| `colorRight` | String | `#fd9` | the color in the top right corner. Must be in the HEX Format with 3 or 6 digits. | +| `marker.borderWidth` | Number | `2` | The border width of the active marker in px. | +| `marker.radius` | Number | `16` | The border radius including the borderWidth in px. | + +You need more options? Please open an issue and I will do my best to implement it. Pull Requests are also welcome! diff --git a/docs/img/screenshot.png b/docs/img/screenshot.png index 49a99d21..bc694728 100644 Binary files a/docs/img/screenshot.png and b/docs/img/screenshot.png differ diff --git a/docs/img/values.png b/docs/img/values.png new file mode 100644 index 00000000..aa5c0311 Binary files /dev/null and b/docs/img/values.png differ diff --git a/package-lock.json b/package-lock.json index 4e3a0ea2..679fd18f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@adrianjost/two-channel-picker", - "version": "0.1.0", + "version": "0.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -13888,7 +13888,8 @@ "vue": { "version": "2.6.10", "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.10.tgz", - "integrity": "sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==" + "integrity": "sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==", + "dev": true }, "vue-eslint-parser": { "version": "2.0.3", diff --git a/package.json b/package.json index c6fddb85..80ba3072 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,19 @@ { "name": "@adrianjost/two-channel-picker", - "version": "0.2.0", + "version": "0.3.0", "author": "Adrian Jost (https://adrianjost.dev)", "scripts": { "lint": "vue-cli-service lint", - "build": "vue-cli-service build --target lib --name TwoChannelPicker ./src/TwoChannelPicker.vue --mode production --dest dist/lib", + "build:vue": "vue-cli-service build --target lib --name TwoChannelPicker ./src/TwoChannelPicker.vue --mode production --dest dist/lib", + "build:wc": "vue-cli-service build --target wc --inline-vue --name two-channel-picker ./src/TwoChannelPicker.vue --dest dist/wc", + "build": "run-p build:*", "dev": "vue-cli-service serve" }, - "dependencies": { + "peerDependencies": { "vue": "^2.6.10" }, "devDependencies": { + "vue": "^2.6.10", "@vue/cli-plugin-babel": "^3.7.0", "@vue/cli-plugin-eslint": "^3.8.0", "@vue/cli-service": "^4.0.0", @@ -39,7 +42,10 @@ "last 2 versions" ], "files": [ - "dist/lib/*" + "dist/*", + "docs/*", + "LICENSE", + "README.md" ], "gitHooks": { "pre-commit": "lint-staged" diff --git a/src/Dev.vue b/src/Dev.vue index 9084269a..658ce719 100644 --- a/src/Dev.vue +++ b/src/Dev.vue @@ -1,7 +1,7 @@ diff --git a/src/TwoChannelPicker.vue b/src/TwoChannelPicker.vue index 09e4020e..c6f965c8 100644 --- a/src/TwoChannelPicker.vue +++ b/src/TwoChannelPicker.vue @@ -22,6 +22,7 @@ :style="{ background: currentColor, transform: translate, + ...getMarkerStyles(_options), }" /> @@ -39,20 +40,13 @@ export default { mixins: [colorConversion], props: { value: { - type: Array, + type: [Array, String], default: () => [1, 0], validator: (v) => v[0] >= 0 && v[0] <= 1 && v[1] >= 0 && v[1] <= 1, }, - colorLeft: { - type: String, - default: "#fd9", - }, - colorRight: { - type: String, - default: "#9df", - }, - readOnly: { - type: Boolean, + options: { + type: Object, + default: () => ({}), }, }, data() { @@ -70,12 +64,22 @@ export default { }, }; }, - animation: { - animationFrame: undefined, - changeTimeout: undefined, - hasChangedLately: false, - }, computed: { + _options() { + return { + colorLeft: "#fd9", + colorRight: "#9df", + readOnly: false, + marker: { + radius: 16, + borderWidth: 2, + }, + ...this.getTypesafeAttr(this.options), + }; + }, + typeSafeValue() { + return this.getTypesafeAttr(this.value); + }, currentColor() { return this.getColorForPosition({ x: this.marker.x, @@ -86,8 +90,8 @@ export default { return this.getTranslate(this.marker.x, this.marker.y); }, colorMid() { - const cA = this.hex2rgb(this.colorLeft); - const cB = this.hex2rgb(this.colorRight); + const cA = this.hex2rgb(this._options.colorLeft); + const cB = this.hex2rgb(this._options.colorRight); const maxColor = (v1, v2) => { const s = v1 + v2; return s > 255 ? 255 : s; @@ -99,7 +103,7 @@ export default { }); }, backgroundGradient() { - return `linear-gradient(to right, ${this.colorLeft} 0%, ${this.colorMid} 50%, ${this.colorRight} 100%)`; + return `linear-gradient(to right, ${this._options.colorLeft} 0%, ${this.colorMid} 50%, ${this._options.colorRight} 100%)`; }, }, watch: { @@ -107,7 +111,7 @@ export default { const channels = this.getChannelsForPosition(this.marker); this.$emit("input", channels); }, - value: { + typeSafeValue: { immediate: true, handler(values) { if (this.$options.hasChangedLately) { @@ -126,30 +130,49 @@ export default { }, }, }, + animation: { + animationFrame: undefined, + changeTimeout: undefined, + hasChangedLately: false, + }, mounted() { window.addEventListener("resize", this.resize); - this.resize(); + setTimeout(this.resize, 0); }, methods: { + getTypesafeAttr(value) { + try { + if (typeof value === "string") { + return JSON.parse(value); + } else { + return value; + } + } catch (error) { + console.error(error); + } + console.error("unable to type convert attribute"); + return value; + }, /* START MARKER POSITIONING */ start(event) { - if (this.readOnly) { + if (this._options.readOnly) { return; } + this.resize(); this.dragActive = true; this.move(event); }, end(event) { - if (this.readOnly) { + if (this._options.readOnly) { return; } this.move(event); this.dragActive = false; }, move(event) { - if (this.readOnly) { + if (this._options.readOnly) { return; } // event.preventDefault(); @@ -174,6 +197,7 @@ export default { this.frame.minY = boundingBox.top; this.frame.maxX = boundingBox.right; this.frame.maxY = boundingBox.bottom; + this.$forceUpdate(); }, _getEventPosition(event) { // mouse @@ -205,7 +229,7 @@ export default { END MARKER POSITIONING */ /* - START MARKER COLOR + START MARKER STYLES */ getColorBetweenColors(colorA, colorB, position) { const cA = this.hex2rgb(colorA); @@ -221,14 +245,14 @@ export default { let newColor; if (x < 0.5) { newColor = this.getColorBetweenColors( - this.colorLeft, + this._options.colorLeft, this.colorMid, x * 2 ); } else { newColor = this.getColorBetweenColors( this.colorMid, - this.colorRight, + this._options.colorRight, (x - 0.5) * 2 ); } @@ -237,8 +261,17 @@ export default { newColor.b = Math.round(newColor.b * y); return this.rgb2hex(newColor); }, + getMarkerStyles(options) { + const { radius, borderWidth } = options.marker; + return { + padding: `${radius - borderWidth}px`, + "margin-bottom": `${-radius}px`, + "margin-left": `${-radius}px`, + "border-width": `${borderWidth}px`, + }; + }, /* - END MARKER COLOR + END MARKER STYLES */ /* START V-MODEL CALCULATIONS */ getChannelsForPosition({ x, y }) { @@ -286,9 +319,6 @@ $borderWidth: 2px; left: 0; z-index: 2; display: inline-block; - padding: ($height / 2) - $borderWidth; - margin-bottom: -($height / 2); - margin-left: -($height / 2); border: $borderWidth solid var(--color-border-i, #fff); border-radius: 50%;