├── .gitignore ├── .npmignore ├── .babelrc.js ├── scripts └── post_install.js ├── rollup.config.js ├── LICENSE ├── package.json ├── src └── index.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /node_modules 3 | yarn-error.log 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .babelrc 2 | .DS_Store 3 | .gitignore 4 | .yarn.lock 5 | /.git 6 | /node_modules 7 | rollup.config.js 8 | .babelrc.js 9 | .prettierrc.json 10 | /.vscode -------------------------------------------------------------------------------- /.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | test: { 4 | plugins: [ 5 | [ 6 | 'istanbul', 7 | { 8 | exclude: ['spec/**/*.js'] 9 | } 10 | ] 11 | ] 12 | } 13 | }, 14 | presets: [ 15 | [ 16 | '@babel/preset-env', 17 | { 18 | targets: { 19 | node: 'current' 20 | } 21 | } 22 | ] 23 | ], 24 | plugins: [ 25 | '@babel/plugin-proposal-class-properties', 26 | '@babel/plugin-transform-classes', 27 | '@babel/plugin-proposal-object-rest-spread' 28 | ] 29 | } -------------------------------------------------------------------------------- /scripts/post_install.js: -------------------------------------------------------------------------------- 1 | console.log( 2 | 'Registering a Stimulus controller for use is easy!\n\n' + 3 | '1. Open index.js in your controllers folder.\n' + 4 | '2. Add the following import:\n' + 5 | " import Radiolabel from 'radiolabel'\n" + 6 | '3. Register the radiolabel controller at the bottom:\n' + 7 | " application.register('radiolabel', Radiolabel)\n\n" + 8 | 'You can also set up Radiolabel for conditional import:\n\n' + 9 | "if (process.env.RAILS_ENV === 'development') {\n" + 10 | " import('radiolabel').then(Radiolabel =>\n" + 11 | " application.register('radiolabel', Radiolabel.default)\n" + 12 | ' )\n' + 13 | '}\n\n' 14 | ) 15 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import filesize from 'rollup-plugin-filesize' 2 | import resolve from 'rollup-plugin-node-resolve' 3 | import babel from 'rollup-plugin-babel' 4 | 5 | const pkg = require('./package.json') 6 | 7 | const name = pkg.name 8 | 9 | export default { 10 | input: 'src/index.js', 11 | external: ['stimulus'], 12 | output: [ 13 | { 14 | file: 'dist/index.js', 15 | format: 'cjs', 16 | sourcemap: true 17 | }, 18 | { 19 | file: 'dist/index.m.js', 20 | format: 'es', 21 | sourcemap: true 22 | }, 23 | { 24 | file: 'dist/index.umd.js', 25 | format: 'umd', 26 | name, 27 | sourcemap: true, 28 | globals: { 29 | stimulus: 'Stimulus' 30 | } 31 | } 32 | ], 33 | plugins: [resolve(), babel(), filesize()] 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 leastbad 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "radiolabel", 3 | "version": "1.1.1", 4 | "description": "Mutation indicator overlays for CableReady operations", 5 | "keywords": ["stimulusjs", "cableready", "rails", "development", "overlay"], 6 | "main": "dist/index.js", 7 | "umd:main": "dist/index.umd.js", 8 | "module": "dist/index.m.js", 9 | "source": "src/index.js", 10 | "author": "@leastbad", 11 | "license": "MIT", 12 | "external": "stimulus", 13 | "scripts": { 14 | "postinstall": "node scripts/post_install.js", 15 | "prettier-standard:check": "yarn run prettier-standard --check *.js **/*.js", 16 | "prettier-standard:format": "yarn run prettier-standard *.js **/*.js", 17 | "build": "rollup -c", 18 | "dev": "rollup -wc", 19 | "release": "np" 20 | }, 21 | "homepage": "https://leastbad.com/", 22 | "bugs": { 23 | "url": "https://github.com/leastbad/radiolabel/issues" 24 | }, 25 | "dependencies": { 26 | "cable_ready": ">=4.5.0", 27 | "gsap": "^3.8.0", 28 | "stimulus": ">=2.0.0" 29 | }, 30 | "devDependencies": { 31 | "@babel/core": "^7.6.2", 32 | "@babel/plugin-proposal-class-properties": "^7.3.4", 33 | "@babel/plugin-proposal-object-rest-spread": "^7.5.5", 34 | "@babel/plugin-transform-classes": "^7.3.4", 35 | "@babel/plugin-transform-spread": "^7.2.2", 36 | "@babel/preset-env": "^7.6.2", 37 | "np": "^5.1.3", 38 | "prettier-standard": "^16.1.0", 39 | "rollup": "^1.20.3", 40 | "rollup-plugin-babel": "^4.3.2", 41 | "rollup-plugin-filesize": "^6.0.1", 42 | "rollup-plugin-node-resolve": "^5.2.0" 43 | }, 44 | "repository": { 45 | "type": "git", 46 | "url": "https://github.com/leastbad/radiolabel.git" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { Controller } from 'stimulus' 2 | import CableReady from 'cable_ready' 3 | import { gsap } from 'gsap' 4 | 5 | const dasherize = string => { 6 | return string.replace(/[A-Z]/g, function (char, index) { 7 | return (index !== 0 ? '-' : '') + char.toLowerCase() 8 | }) 9 | } 10 | 11 | export default class extends Controller { 12 | static values = { duration: Number } 13 | 14 | initialize () { 15 | this.operations = Object.keys(CableReady.DOMOperations).map(key => 16 | dasherize(key) 17 | ) 18 | this.duration = 7 19 | } 20 | 21 | connect () { 22 | this.operations.forEach(operation => 23 | document.addEventListener( 24 | `cable-ready:after-${operation}`, 25 | this.intercept 26 | ) 27 | ) 28 | } 29 | 30 | disconnect () { 31 | this.operations.forEach(operation => 32 | document.removeEventListener( 33 | `cable-ready:after-${operation}`, 34 | this.intercept 35 | ) 36 | ) 37 | } 38 | 39 | durationValueChanged () { 40 | this.duration = this.durationValue 41 | } 42 | 43 | intercept = ({ detail, target, type }) => { 44 | if (target !== document) { 45 | const body = target === document.body 46 | const style = getComputedStyle(target) 47 | const border = style.getPropertyValue('border') 48 | const title = document.createElement('div') 49 | const overlay = document.createElement('div') 50 | const eventType = type.split('after-')[1] 51 | const color = eventType === 'morph' ? '#FF9800' : '#0F0' 52 | const d = detail.stimulusReflex 53 | const titleTarget = (d && d.target) || '' 54 | const reflexId = (d && d.reflexId) || '' 55 | 56 | setTimeout(() => { 57 | const t_rect = target.getBoundingClientRect() 58 | const rect = body ? { top: 56, left: 0 } : t_rect 59 | const titleTop = rect.top - 56 + Math.round(scrollY) 60 | const oTop = body ? 0 : t_rect.top + Math.round(scrollY) 61 | 62 | title.style.cssText = `position:absolute;z-index:5001;top:${titleTop}px;left:${rect.left}px;background-color:#fff;padding: 3px 8px 3px 8px;border: 1px solid #000;pointer-events: none;` 63 | title.innerHTML = `${eventType} ${titleTarget} \u2192 ${detail.selector}
${reflexId}` 64 | 65 | overlay.style.cssText = `position:absolute;z-index:5000;top:${oTop}px;left:${t_rect.left}px;width:${t_rect.width}px;height:${t_rect.height}px;background-color: ${color};pointer-events: none;` 66 | overlay.style.border = border 67 | 68 | document.body.appendChild(title) 69 | document.body.appendChild(overlay) 70 | 71 | gsap.fromTo( 72 | overlay, 73 | { 74 | opacity: 1.0 75 | }, 76 | { 77 | opacity: 0, 78 | duration: this.duration, 79 | ease: 'expo', 80 | onComplete: () => { 81 | title.remove() 82 | overlay.remove() 83 | } 84 | } 85 | ) 86 | }) 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Radiolabel

2 |

3 | 4 | npm version 5 | 6 |

7 | 8 |

9 | Mutation indicator overlays for CableReady operations
10 | Tiny at <100 LOC 11 |

12 | 13 |
14 | 15 | - **Simple**: this is a drop-in, code-free solution 16 | - **Styled**: zero CSS, use any design framework 17 | - **Backend Agnostic**: works with or without [StimulusReflex](https://docs.stimulusreflex.com) 18 | - **Turbolinks**: compatible with Turbolinks by design 19 | - **MIT Licensed**: free for personal and commercial use 20 | 21 | ## Built for CableReady 22 | 23 | This [Stimulus](https://stimulusjs.org/) controller intercepts CableReady `after-` DOM events. When it detects an operation that mutates an element, it will create a titled overlay which briefly announces when an element is modified. 24 | 25 | Morph operations will be orange, while all others are green. 26 | 27 | If an operation was initiated by [StimulusReflex](https://docs.stimulusreflex.com), additional information will be presented about the Reflex action in the title. 28 | 29 | ## Setup 30 | 31 | First, add Radiolabel to your `package.json`: 32 | 33 | `yarn add radiolabel` 34 | 35 | Then, just add Radiolabel to your main JS entry point or Stimulus controllers root folder: 36 | 37 | ```js 38 | import { Application } from 'stimulus' 39 | import Radiolabel from 'radiolabel' 40 | 41 | import { definitionsFromContext } from 'stimulus/webpack-helpers' 42 | const application = Application.start() 43 | const context = require.context('../controllers', true, /\.js$/) 44 | application.load(definitionsFromContext(context)) 45 | 46 | // Manually register Radiolabel as a Stimulus controller 47 | application.register('radiolabel', Radiolabel) 48 | ``` 49 | 50 | Optionally, you can restrict the import to your `development` environment: 51 | 52 | ```js 53 | import { Application } from 'stimulus' 54 | 55 | import { definitionsFromContext } from 'stimulus/webpack-helpers' 56 | const application = Application.start() 57 | const context = require.context('../controllers', true, /\.js$/) 58 | application.load(definitionsFromContext(context)) 59 | 60 | if (process.env.RAILS_ENV === 'development') { 61 | import('radiolabel').then(Radiolabel => 62 | application.register('radiolabel', Radiolabel.default) 63 | ) 64 | } 65 | ``` 66 | 67 | If Stimulus can't locate a controller at runtime, the `data-controller` attribute is ignored, meaning your template can reference `radiolabel` in the `production` environment and nothing will happen. 68 | 69 | ## HTML Markup 70 | 71 | ```html 72 | 73 | ``` 74 | 75 | If you'd like to change the default 7 second visual effect duration, just set a new value on the same element that the 76 | controller is defined on. 77 | 78 | ```html 79 | 80 | ``` 81 | 82 | Yes, that's really it. 83 | 84 | ## Contributing 85 | 86 | Bug reports and pull requests are welcome. 87 | 88 | ## License 89 | 90 | This package is available as open source under the terms of the MIT License. 91 | --------------------------------------------------------------------------------