avances en plantillas

This commit is contained in:
JACS 2026-05-01 18:15:40 -05:00
parent 0f84beacf1
commit da0530d79b
2062 changed files with 598814 additions and 22 deletions

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 pryley
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,400 @@
# star-rating.js
[![npm version](https://badge.fury.io/js/star-rating.js.svg)](https://badge.fury.io/js/star-rating.js)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/pryley/star-rating.js/blob/master/LICENSE)
A zero-dependency library that transforms a select with numerical-range values (i.e. 1-5) into a dynamic star rating element.
For production, use the files from the `dist/` folder.
## Installation
```js
npm i star-rating.js
```
## Usage
Your SELECT option fields must have numerical values.
```html
<link href="css/star-rating.css" rel="stylesheet">
<select class="star-rating">
<option value="">Select a rating</option>
<option value="5">Excellent</option>
<option value="4">Very Good</option>
<option value="3">Average</option>
<option value="2">Poor</option>
<option value="1">Terrible</option>
</select>
<script src="js/star-rating.min.js"></script>
<script>
var stars = new StarRating('.star-rating');
</script>
```
To rebuild all star rating controls (e.g. after form fields have changed with ajax):
```js
stars.rebuild();
```
To fully remove all star rating controls, including all attached Event Listeners:
```js
stars.destroy();
```
## Options
Here are the default options
```js
{
classNames: {
active: 'gl-active',
base: 'gl-star-rating',
selected: 'gl-selected',
},
clearable: true,
maxStars: 10,
prebuilt: false,
stars: null,
tooltip: 'Select a Rating',
}
```
### classNames.active
Type: `String`
The classname to use for the active (hovered or value <= of the selected value) state of a star.
### classNames.base
Type: `String`
The classname to use for the base element that wraps the star rating.
### classNames.selected
Type: `String`
The classname to use for the selected state of a star.
### clearable
Type: `Boolean`
Whether or not the star rating can be cleared by clicking on an already selected star.
### maxStars:
Type: `Integer`
The maximum number of stars allowed in a star rating.
### prebuilt:
Type: `Boolean`
If this option is `true`, only the event listeners will be added and no DOM manipulation will take place. You will need to ensure that the DOM looks something like this:
```html
<span class="gl-star-rating">
<select>
<option value="">Select a rating</option>
<option value="5">5 Stars</option>
<option value="4">4 Stars</option>
<option value="3">3 Stars</option>
<option value="2">2 Stars</option>
<option value="1">1 Star</option>
</select>
<span class="gl-star-rating--stars">
<span data-value="1"></span>
<span data-value="2"></span>
<span data-value="3"></span>
<span data-value="4"></span>
<span data-value="5"></span>
</span>
</span>
```
### stars:
Type: `Function`
This can be used to add a SVG image to each star value instead of using the background images in the CSS.
### tooltip:
Type: `String|False`
The placeholder text for the rating tooltip, or `false` to disable the tooltip.
## Build
```sh
npm install
npm run build
```
The compiled files will be saved in the `dist/` folder.
**Note:** If importing this into your project, you will need to add [@babel/plugin-proposal-optional-chaining](https://www.npmjs.com/package/@babel/plugin-proposal-optional-chaining) to your babel config.
### Style Customization
Following are the default CSS variable values for Star Rating:
```css
:root {
--gl-star-color: #fdd835; /* if using SVG images */
--gl-star-color-inactive: #dcdce6; /* if using SVG images */
--gl-star-empty: url(../img/star-empty.svg); /* if using background images */
--gl-star-full: url(../img/star-full.svg); /* if using background images */
--gl-star-size: 24px;
--gl-tooltip-background: rgba(17,17,17, .9);
--gl-tooltip-border-radius: 4px;
--gl-tooltip-color: #fff;
--gl-tooltip-font-size: 0.875rem;
--gl-tooltip-font-weight: 400;
--gl-tooltip-line-height: 1;
--gl-tooltip-margin: 12px;
--gl-tooltip-padding: .5em 1em;
}
```
To override any values with your own, simply import the CSS file into your project, then enter new CSS variable values after the import.
```css
@import 'star-rating';
:root {
--gl-star-size: 32px;
}
```
### How to change CSS style priority
Sometimes an existing stylesheet rules will override the default CSS styles for Star Ratings. To solve this problem, you can use the [postcss-selector-namespace](https://github.com/topaxi/postcss-selector-namespace) plugin in your PostCSS build on the CSS file before combining with your main stylesheet. This namespace value should be a high priority/specificity property such as an id attribute or similar.
## Compatibility
- All modern browsers
If you need to use the Star Rating library in a unsupported browser (i.e. Internet Explorer), use the [Polyfill service](https://polyfill.io).
## Tips
1. If your star rating has a label field, add the `pointer-events: none;` style to it to prevent the focus event from triggering on touch devices.
## Contributing
All changes should be committed to the files in `src/`.
## Changelog
`v4.3.1 - [2024-04-30]`
- Fixed edge-case bug with prebuilt config option
- Fixed tooltip CSS
`v4.3.0 - [2022-08-05]`
- Added module and exports fields to package.json
- Fixed left/right keydown events
- Optimised CSS
`v4.2.5 - [2022-07-30]`
- Fixed active index when stars have gaps between them
`v4.2.3 - [2022-06-03]`
- Disabled pointer-events on tooltip
`v4.2.2 - [2022-03-30]`
- Fixed rebuild function
`v4.2.0 - [2022-03-24]`
- Perform a complete teardown on destroy allowing a rebuild from the selector in a new DOM
`v4.1.5 - [2021-09-25]`
- Added a data-rating attribute on the widget which holds the transient/selected rating
`v4.1.4 - [2021-05-29]`
- Fixed selected index on reset
`v4.1.3 - [2021-04-09]`
- Fixed focus state with pointer events
`v4.1.2 - [2021-02-24]`
- Fixed error when initialising more than once
`v4.1.1 - [2021-02-14]`
- Removed v3 compatibility mode when using the `prebuilt` option
`v4.1.0 - [2021-02-13]`
- Added the `prebuilt` option
`v4.0.6 - [2021-02-05]`
- Remove the focus from being triggered entirely as it caused to many problems on ios and I don't have time to fix it
`v4.0.5 - [2021-02-03]`
- Fixed an invalid change event from being triggered by the reset event
`v4.0.4 - [2021-02-02]`
- Export a babel-transpiled commonjs module
`v4.0.3 - [2021-01-29]`
- Fixed rollup config to support optional-chaining in babel
`v4.0.2 - [2021-01-23]`
- Fixed compatibility mode (when `'function' !== typeof options.stars`)
- Removed trigger of change event after init as this could trigger unwanted validation
`v4.0.1 - [2021-01-22]`
- Fixed the change event for disabled SELECT elements
`v4.0.0 - [2021-01-22]`
- Code has been rewritten as an ES6 module and optimised
- Added requestAnimationFrame to the pointer move events
- Added the `stars` option which allows you to use custom SVG images for each star
- Replaced the `classname` option with the `classNames` option
- Replaced the `initialText` with the `tooltip` option
- Replaced gulp with rollup for the build
- Replaced SASS with PostCSS
`v3.4.0 - [2020-10-18]`
- Specify passive:false on event listeners to suppress Chrome warnings
`v3.2.0 - [2020-07-13]`
- Cleanup stale DOM if needed before initializing
`v3.1.8 - [2020-06-29]`
- Fixed clearable option
- Fixed events on disabled SELECT
`v3.1.5 - [2019-11-01]`
- Added ability to use a NodeList as a selector
`v3.1.4 - [2019-01-28]`
- Updated package URL
`v3.1.3 - [2019-01-27]`
- Fixed issue when used outside of a FORM
`v3.1.2 - [2019-01-07]`
- Fixed issue that allowed multiple star-rating transformations on the same SELECT element
`v3.1.1 - [2018-07-27]`
- Provided an un-minified CSS file in /dist
- Removed the change event trigger from the reset event
`v3.1.0 - [2018-07-24]`
- Changed the `star-filled` SCSS map option to `star-full`
- Changed the `star-empty`, `star-full`, and `star-half` SCSS map options to `url(...)`. This allows one to use `none` as the value of `background-image`.
`v3.0.0 - [2018-07-24]`
- Dropped support for Internet Explorer (use polyfill.io, specifically: Element.prototype.closest, Element.prototype.dataset, Event)
- Removed the `onClick` option (listen for the `change` event instead)
`v2.3.1 - [2018-07-22]`
- CSS improvements
`v2.3.0 - [2018-07-20]`
- Added a `$star-rating[parent]` SCSS option
`v2.2.2 - [2018-07-16]`
- Fixed IE 11+ compatibility
`v2.2.1 - [2018-07-13]`
- Fixed touch events on Android devices
`v2.2.0 - [2018-07-09]`
- Added a `classname` option
- Added a `$star-rating[base-classname]` SCSS option
- Added touch events
- Fixed detection of an unset option value
- Optimised the minified output
- Removed unused code
`v2.1.1 - [2018-05-25]`
- Fixed jshint warnings
`v2.1.0 - [2018-05-11]`
- Added support for the keyboard
- Fixed accessibility support
- Fixed RTL support
`v2.0.0 - [2018-05-02]`
- Major rewrite of library
- Added support for loading as a module
- Added support for RTL
- Removed jQuery plugin
- Removed IE9 support
`v1.3.3 - [2017-04-11]`
- Fixed race conditions preventing correct element.outerWidth calculation
`v1.3.1 - [2016-12-22]`
- Fixed checking existence of parent form element before attaching an event to it
- Fixed mousemove event not correctly unattaching
`v1.3.0 - [2016-10-10]`
- Changed `clickFn` to `onClick` which now passes the select HTMLElement as the argument
`v1.2.2 - [2016-10-10]`
- Fixed "reset" event when the `clearable` option is false
`v1.2.1 - [2016-10-09]`
- Fixed resetting the star-rating when a form "reset" event is triggered
`v1.2.0 - [2016-10-09]`
- Removed dependencies
- Fixed HTML5 “required” attribute validation
`v1.1.0 - [2016-10-06]`
- Added `showText` option
`v1.0.1 - [2016-10-06]`
- Fixed using the wrong left offset
`v1.0.0 - [2016-10-06]`
- Initial release
## License
[MIT](/LICENSE)

View file

@ -0,0 +1,484 @@
'use strict';
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
var defaults = {
classNames: {
active: 'gl-active',
base: 'gl-star-rating',
selected: 'gl-selected'
},
clearable: true,
maxStars: 10,
prebuilt: false,
stars: null,
tooltip: 'Select a Rating'
};
var addRemoveClass = function addRemoveClass(el, bool, className) {
el.classList[bool ? 'add' : 'remove'](className);
};
var createSpanEl = function createSpanEl(attributes) {
var el = document.createElement('span');
attributes = attributes || {};
for (var key in attributes) {
el.setAttribute(key, attributes[key]);
}
return el;
};
var inRange = function inRange(value, min, max) {
return /^\d+$/.test(value) && min <= value && value <= max;
};
var insertSpanEl = function insertSpanEl(el, after, attributes) {
var newEl = createSpanEl(attributes);
el.parentNode.insertBefore(newEl, after ? el.nextSibling : el);
return newEl;
};
var merge = function merge() {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
// adapted from https://github.com/firstandthird/aug
var results = {};
args.forEach(function (prop) {
Object.keys(prop || {}).forEach(function (propName) {
if (args[0][propName] === undefined) return; // restrict keys to the defaults
var propValue = prop[propName];
if (type(propValue) === 'Object' && type(results[propName]) === 'Object') {
results[propName] = merge(results[propName], propValue);
return;
}
results[propName] = propValue;
});
});
return results;
};
var type = function type(value) {
return {}.toString.call(value).slice(8, -1);
};
var values = function values(selectEl) {
var values = [];
[].forEach.call(selectEl.options, function (el) {
var value = parseInt(el.value, 10) || 0;
if (value > 0) {
values.push({
index: el.index,
text: el.text,
value: value
});
}
});
return values.sort(function (a, b) {
return a.value - b.value;
});
};
var Widget = /*#__PURE__*/function () {
function Widget(el, props) {
_classCallCheck(this, Widget);
// (HTMLElement, object):void
this.direction = window.getComputedStyle(el, null).getPropertyValue('direction');
this.el = el;
this.events = {
change: this.onChange.bind(this),
keydown: this.onKeyDown.bind(this),
mousedown: this.onPointerDown.bind(this),
mouseleave: this.onPointerLeave.bind(this),
mousemove: this.onPointerMove.bind(this),
reset: this.onReset.bind(this),
touchend: this.onPointerDown.bind(this),
touchmove: this.onPointerMove.bind(this)
};
this.indexActive = null; // the active span index
this.indexSelected = null; // the selected span index
this.props = props;
this.tick = null;
this.ticking = false;
this.values = values(el);
this.widgetEl = null;
if (this.el.widget) {
this.el.widget.destroy(); // remove any stale event listeners
}
if (inRange(this.values.length, 1, this.props.maxStars)) {
this.build();
} else {
this.destroy();
}
}
_createClass(Widget, [{
key: "build",
value: function build() {
// ():void
this.destroy();
this.buildWidget();
this.selectValue(this.indexSelected = this.selected(), false); // set the initial value but do not trigger change event
this.handleEvents('add');
this.el.widget = this; // store a reference to this widget on the SELECT so that we can remove stale event listeners
}
}, {
key: "buildWidget",
value: function buildWidget() {
var _this = this;
// ():void
var parentEl = null;
var widgetEl = null;
if (this.props.prebuilt) {
parentEl = this.el.parentNode;
widgetEl = parentEl.querySelector('.' + this.props.classNames.base + '--stars');
}
if (null === widgetEl) {
parentEl = insertSpanEl(this.el, false, {
"class": this.props.classNames.base
});
parentEl.appendChild(this.el);
widgetEl = insertSpanEl(this.el, true, {
"class": this.props.classNames.base + '--stars'
});
this.values.forEach(function (item, index) {
var el = createSpanEl({
'data-index': index,
'data-value': item.value
});
if ('function' === typeof _this.props.stars) {
_this.props.stars.call(_this, el, item, index);
}
[].forEach.call(el.children, function (el) {
return el.style.pointerEvents = 'none';
});
widgetEl.innerHTML += el.outerHTML;
});
}
parentEl.dataset.starRating = '';
parentEl.classList.add(this.props.classNames.base + '--' + this.direction);
if (this.props.tooltip) {
widgetEl.setAttribute('role', 'tooltip');
}
this.widgetEl = widgetEl;
}
}, {
key: "changeIndexTo",
value: function changeIndexTo(index, force) {
var _this2 = this;
// (int):void
if (this.indexActive !== index || force) {
[].forEach.call(this.widgetEl.children, function (el, i) {
// i starts at zero
addRemoveClass(el, i <= index, _this2.props.classNames.active);
addRemoveClass(el, i === _this2.indexSelected, _this2.props.classNames.selected);
});
this.widgetEl.setAttribute('data-rating', index + 1);
if ('function' !== typeof this.props.stars && !this.props.prebuilt) {
// @v3 compat
this.widgetEl.classList.remove('s' + 10 * (this.indexActive + 1));
this.widgetEl.classList.add('s' + 10 * (index + 1));
}
if (this.props.tooltip) {
var _this$values$index;
var label = index < 0 ? this.props.tooltip : (_this$values$index = this.values[index]) === null || _this$values$index === void 0 ? void 0 : _this$values$index.text;
this.widgetEl.setAttribute('aria-label', label);
}
this.indexActive = index;
}
this.ticking = false;
}
}, {
key: "destroy",
value: function destroy() {
// ():void
this.indexActive = null; // the active span index
this.indexSelected = this.selected(); // the selected span index
var parentEl = this.el.parentNode;
if (parentEl.classList.contains(this.props.classNames.base)) {
if (this.props.prebuilt) {
this.widgetEl = parentEl.querySelector('.' + this.props.classNames.base + '--stars');
parentEl.classList.remove(this.props.classNames.base + '--' + this.direction);
delete parentEl.dataset.starRating;
} else {
parentEl.parentNode.replaceChild(this.el, parentEl);
}
this.handleEvents('remove');
}
delete this.el.widget; // remove the widget reference
}
}, {
key: "eventListener",
value: function eventListener(el, action, events, items) {
var _this3 = this;
// (HTMLElement, string, array, object):void
events.forEach(function (ev) {
return el[action + 'EventListener'](ev, _this3.events[ev], items || false);
});
}
}, {
key: "handleEvents",
value: function handleEvents(action) {
// (string):void
var formEl = this.el.closest('form');
if (formEl && formEl.tagName === 'FORM') {
this.eventListener(formEl, action, ['reset']);
}
this.eventListener(this.el, action, ['change']); // always trigger the change event, even when SELECT is disabled
if ('add' === action && this.el.disabled) return;
this.eventListener(this.el, action, ['keydown']);
this.eventListener(this.widgetEl, action, ['mousedown', 'mouseleave', 'mousemove', 'touchend', 'touchmove'], false);
}
}, {
key: "indexFromEvent",
value: function indexFromEvent(ev) {
var _ev$touches, _ev$changedTouches;
// (MouseEvent|TouchEvent):void
var origin = ((_ev$touches = ev.touches) === null || _ev$touches === void 0 ? void 0 : _ev$touches[0]) || ((_ev$changedTouches = ev.changedTouches) === null || _ev$changedTouches === void 0 ? void 0 : _ev$changedTouches[0]) || ev;
var el = document.elementFromPoint(origin.clientX, origin.clientY);
if (el.parentNode === this.widgetEl) {
return [].slice.call(el.parentNode.children).indexOf(el);
}
return this.indexActive;
}
}, {
key: "onChange",
value: function onChange() {
// ():void
this.changeIndexTo(this.selected(), true);
}
}, {
key: "onKeyDown",
value: function onKeyDown(ev) {
// (KeyboardEvent):void
var key = ev.key.slice(5);
if (!~['Left', 'Right'].indexOf(key)) return;
ev.preventDefault();
var increment = key === 'Left' ? -1 : 1;
if (this.direction === 'rtl') {
increment *= -1;
}
var maxIndex = this.values.length - 1;
var minIndex = -1;
var index = Math.min(Math.max(this.selected() + increment, minIndex), maxIndex);
this.selectValue(index, true); // trigger change event
}
}, {
key: "onPointerDown",
value: function onPointerDown(ev) {
// (MouseEvent|TouchEvent):void
ev.preventDefault(); // this.el.focus(); // highlight the rating field
var index = this.indexFromEvent(ev);
if (this.props.clearable && index === this.indexSelected) {
index = -1; // remove the value
}
this.selectValue(index, true); // trigger change event
}
}, {
key: "onPointerLeave",
value: function onPointerLeave(ev) {
var _this4 = this;
// (MouseEvent):void
ev.preventDefault();
cancelAnimationFrame(this.tick);
requestAnimationFrame(function () {
return _this4.changeIndexTo(_this4.indexSelected);
});
}
}, {
key: "onPointerMove",
value: function onPointerMove(ev) {
var _this5 = this;
// (MouseEvent|TouchEvent):void
ev.preventDefault();
if (!this.ticking) {
this.tick = requestAnimationFrame(function () {
return _this5.changeIndexTo(_this5.indexFromEvent(ev));
});
this.ticking = true;
}
}
}, {
key: "onReset",
value: function onReset() {
var _this$el$querySelecto;
// ():void
var index = this.valueIndex((_this$el$querySelecto = this.el.querySelector('[selected]')) === null || _this$el$querySelecto === void 0 ? void 0 : _this$el$querySelecto.value);
this.selectValue(index || -1, false); // do not trigger change event
}
}, {
key: "selected",
value: function selected() {
// ():int
return this.valueIndex(this.el.value); // get the selected span index
}
}, {
key: "selectValue",
value: function selectValue(index, triggerChangeEvent) {
var _this$values$index2;
// (int, bool):void
this.el.value = ((_this$values$index2 = this.values[index]) === null || _this$values$index2 === void 0 ? void 0 : _this$values$index2.value) || ''; // first set the new value
this.indexSelected = this.selected(); // get the actual index from the selected value
if (false === triggerChangeEvent) {
this.changeIndexTo(this.selected(), true);
} else {
this.el.dispatchEvent(new Event('change'));
}
}
}, {
key: "valueIndex",
value: function valueIndex(value) {
return this.values.findIndex(function (val) {
return val.value === +value;
});
}
}]);
return Widget;
}();
var StarRating = /*#__PURE__*/function () {
function StarRating(selector, props) {
_classCallCheck(this, StarRating);
// (HTMLSelectElement|NodeList|string, object):void
this.destroy = this.destroy.bind(this);
this.props = props;
this.rebuild = this.rebuild.bind(this);
this.selector = selector;
this.widgets = [];
this.build();
}
_createClass(StarRating, [{
key: "build",
value: function build() {
var _this = this;
// (HTMLSelectElement|NodeList|string, object):void
this.queryElements(this.selector).forEach(function (el) {
var options = merge(defaults, _this.props, JSON.parse(el.getAttribute('data-options')));
if ('SELECT' === el.tagName && !el.widget) {
// check for an existing Widget reference
if (!options.prebuilt && el.parentNode.classList.contains(options.classNames.base)) {
_this.unwrap(el);
}
_this.widgets.push(new Widget(el, options));
}
});
}
}, {
key: "destroy",
value: function destroy() {
// ():void
this.widgets.forEach(function (widget) {
return widget.destroy();
});
this.widgets = [];
}
}, {
key: "queryElements",
value: function queryElements(selector) {
// (HTMLSelectElement|NodeList|string):array
if ('HTMLSelectElement' === type(selector)) {
return [selector];
}
if ('NodeList' === type(selector)) {
return [].slice.call(selector);
}
if ('String' === type(selector)) {
return [].slice.call(document.querySelectorAll(selector));
}
return [];
}
}, {
key: "rebuild",
value: function rebuild() {
// ():void
this.destroy();
this.build();
}
}, {
key: "unwrap",
value: function unwrap(el) {
var removeEl = el.parentNode;
var parentEl = removeEl.parentNode;
parentEl.insertBefore(el, removeEl);
parentEl.removeChild(removeEl);
}
}]);
return StarRating;
}();
module.exports = StarRating;

View file

@ -0,0 +1,223 @@
/**
* Star Rating
* @version: 4.3.0
* @author: Paul Ryley (http://geminilabs.io)
* @url: https://github.com/pryley/star-rating.js
* @license: MIT
*/
:root {
--gl-star-color: #fdd835;
--gl-star-color-inactive: #dcdce6;
--gl-star-empty: url('../img/star-empty.svg');
--gl-star-full: url('../img/star-full.svg');
--gl-star-size: 24px;
--gl-tooltip-background: rgba(17,17,17, .9);
--gl-tooltip-border-radius: 4px;
--gl-tooltip-color: #fff;
--gl-tooltip-font-size: 0.875rem;
--gl-tooltip-font-weight: 400;
--gl-tooltip-line-height: 1;
--gl-tooltip-margin: 12px;
--gl-tooltip-padding: .5em 1em;
}
[data-star-rating] > select {
-webkit-clip-path: circle(1px at 0 0) !important;
clip-path: circle(1px at 0 0) !important;
clip: rect(1px, 1px, 1px, 1px) !important;
height: 1px !important;
margin: 0 !important;
overflow: hidden !important;
padding: 0 !important;
position: absolute !important;
top: 0 !important;
visibility: visible !important;
white-space: nowrap !important;
width: 1px !important;
}
[data-star-rating] > select {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
pointer-events: none;
}
[data-star-rating] > select::before,
[data-star-rating] > select::after {
display: none !important;
}
[data-star-rating].gl-star-rating--ltr > select {
left: 0 !important;
}
[data-star-rating].gl-star-rating--rtl > select {
right: 0 !important;
}
[data-star-rating] {
align-items: center;
display: flex;
position: relative;
}
.gl-star-rating:not([data-star-rating]) .gl-star-rating--stars {
display: none;
}
[data-star-rating] .gl-star-rating--stars {
align-items: center;
cursor: pointer;
display: flex;
position: relative;
}
[data-star-rating] > select:focus + .gl-star-rating--stars span:first-child::before {
box-shadow: 0 0 0 3px -moz-mac-focusring;
box-shadow: 0 0 0 3px -webkit-focus-ring-color;
box-shadow: 0 0 0 3px Highlight;
content: '';
display: block;
height: 100%;
outline: 1px solid transparent;
pointer-events: none;
position: absolute;
width: 100%;
}
[data-star-rating] select[disabled] + .gl-star-rating--stars {
cursor: default;
}
[data-star-rating] .gl-star-rating--stars > span {
display: flex;
height: 24px;
height: 24px;
height: var(--gl-star-size);
margin: 0;
width: 24px;
width: 24px;
width: var(--gl-star-size);
}
[data-star-rating] .gl-star-rating--stars[aria-label]::before,
[data-star-rating] .gl-star-rating--stars[aria-label]::after {
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
bottom: auto;
box-sizing: border-box;
left: 100%;
pointer-events: none;
position: absolute;
top: 50%;
transform-origin: top;
transform: translate3d(0,-50%,0);
white-space: nowrap;
z-index: 10;
}
[data-star-rating] .gl-star-rating--stars[aria-label]::before {
background: rgba(17,17,17, .9);
background: rgba(17,17,17, .9);
background: var(--gl-tooltip-background);
-webkit-clip-path: path('M6 14.998c0-3-6-5.499-6-7.499S5.999 3 5.999 0L6 14.998z');
clip-path: path('M6 14.998c0-3-6-5.499-6-7.499S5.999 3 5.999 0L6 14.998z');
content: '';
height: 15px;
margin: 0 0 0 6px;
width: 6px;
}
[data-star-rating] .gl-star-rating--stars[aria-label]::after {
background: rgba(17,17,17, .9);
background: rgba(17,17,17, .9);
background: var(--gl-tooltip-background);
border-radius: 4px;
border-radius: 4px;
border-radius: var(--gl-tooltip-border-radius);
color: #fff;
color: #fff;
color: var(--gl-tooltip-color);
content: attr(aria-label);
font-size: 0.875rem;
font-size: 0.875rem;
font-size: var(--gl-tooltip-font-size);
font-weight: normal;
margin-left: 12px;
margin-left: 12px;
margin-left: var(--gl-tooltip-margin);
padding: .5em 1em;
padding: .5em 1em;
padding: var(--gl-tooltip-padding);
text-transform: none;
}
[data-star-rating].gl-star-rating--rtl .gl-star-rating--stars[aria-label]::before,
[data-star-rating].gl-star-rating--rtl .gl-star-rating--stars[aria-label]::after {
left: auto;
right: 100%;
}
[data-star-rating].gl-star-rating--rtl .gl-star-rating--stars[aria-label]::before {
transform: scaleX(-1) translate3d(0,-50%,0);
margin: 0 6px 0 0;
}
[data-star-rating].gl-star-rating--rtl .gl-star-rating--stars[aria-label]::after {
margin-left: 0;
margin-right: 12px;
margin-right: 12px;
margin-right: var(--gl-tooltip-margin);
}
[data-star-rating] svg {
height: 100%;
width: 100%;
}
[data-star-rating] .gl-star-half {
fill: none;
stroke: none;
}
[data-star-rating] .gl-star-full {
fill: #dcdce6;
fill: #dcdce6;
fill: var(--gl-star-color-inactive);
stroke: #dcdce6;
stroke: #dcdce6;
stroke: var(--gl-star-color-inactive);
transition: fill 0.15s ease-in-out, stroke 0.15s ease-in-out;
}
[data-star-rating] .gl-active .gl-star-full {
fill: #fdd835;
fill: #fdd835;
fill: var(--gl-star-color);
stroke: #fdd835;
stroke: #fdd835;
stroke: var(--gl-star-color);
}
/* Compatibilty with v3 */
.gl-star-rating--stars[class*=" s"] > span {
background-image: url('../img/star-empty.svg') !important;
background-image: url('../img/star-empty.svg') !important;
background-image: var(--gl-star-empty) !important;
}
.gl-star-rating--stars[class*=" s"] > span {
background-position: center;
background-repeat: no-repeat;
background-size: 90%;
}
.gl-star-rating--stars[class*=" s"] > span.gl-active,
.gl-star-rating--stars[class*=" s"] > span.gl-active.gl-selected {
background-image: url('../img/star-full.svg') !important;
background-image: url('../img/star-full.svg') !important;
background-image: var(--gl-star-full) !important;
}

View file

@ -0,0 +1,482 @@
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
var defaults = {
classNames: {
active: 'gl-active',
base: 'gl-star-rating',
selected: 'gl-selected'
},
clearable: true,
maxStars: 10,
prebuilt: false,
stars: null,
tooltip: 'Select a Rating'
};
var addRemoveClass = function addRemoveClass(el, bool, className) {
el.classList[bool ? 'add' : 'remove'](className);
};
var createSpanEl = function createSpanEl(attributes) {
var el = document.createElement('span');
attributes = attributes || {};
for (var key in attributes) {
el.setAttribute(key, attributes[key]);
}
return el;
};
var inRange = function inRange(value, min, max) {
return /^\d+$/.test(value) && min <= value && value <= max;
};
var insertSpanEl = function insertSpanEl(el, after, attributes) {
var newEl = createSpanEl(attributes);
el.parentNode.insertBefore(newEl, after ? el.nextSibling : el);
return newEl;
};
var merge = function merge() {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
// adapted from https://github.com/firstandthird/aug
var results = {};
args.forEach(function (prop) {
Object.keys(prop || {}).forEach(function (propName) {
if (args[0][propName] === undefined) return; // restrict keys to the defaults
var propValue = prop[propName];
if (type(propValue) === 'Object' && type(results[propName]) === 'Object') {
results[propName] = merge(results[propName], propValue);
return;
}
results[propName] = propValue;
});
});
return results;
};
var type = function type(value) {
return {}.toString.call(value).slice(8, -1);
};
var values = function values(selectEl) {
var values = [];
[].forEach.call(selectEl.options, function (el) {
var value = parseInt(el.value, 10) || 0;
if (value > 0) {
values.push({
index: el.index,
text: el.text,
value: value
});
}
});
return values.sort(function (a, b) {
return a.value - b.value;
});
};
var Widget = /*#__PURE__*/function () {
function Widget(el, props) {
_classCallCheck(this, Widget);
// (HTMLElement, object):void
this.direction = window.getComputedStyle(el, null).getPropertyValue('direction');
this.el = el;
this.events = {
change: this.onChange.bind(this),
keydown: this.onKeyDown.bind(this),
mousedown: this.onPointerDown.bind(this),
mouseleave: this.onPointerLeave.bind(this),
mousemove: this.onPointerMove.bind(this),
reset: this.onReset.bind(this),
touchend: this.onPointerDown.bind(this),
touchmove: this.onPointerMove.bind(this)
};
this.indexActive = null; // the active span index
this.indexSelected = null; // the selected span index
this.props = props;
this.tick = null;
this.ticking = false;
this.values = values(el);
this.widgetEl = null;
if (this.el.widget) {
this.el.widget.destroy(); // remove any stale event listeners
}
if (inRange(this.values.length, 1, this.props.maxStars)) {
this.build();
} else {
this.destroy();
}
}
_createClass(Widget, [{
key: "build",
value: function build() {
// ():void
this.destroy();
this.buildWidget();
this.selectValue(this.indexSelected = this.selected(), false); // set the initial value but do not trigger change event
this.handleEvents('add');
this.el.widget = this; // store a reference to this widget on the SELECT so that we can remove stale event listeners
}
}, {
key: "buildWidget",
value: function buildWidget() {
var _this = this;
// ():void
var parentEl = null;
var widgetEl = null;
if (this.props.prebuilt) {
parentEl = this.el.parentNode;
widgetEl = parentEl.querySelector('.' + this.props.classNames.base + '--stars');
}
if (null === widgetEl) {
parentEl = insertSpanEl(this.el, false, {
"class": this.props.classNames.base
});
parentEl.appendChild(this.el);
widgetEl = insertSpanEl(this.el, true, {
"class": this.props.classNames.base + '--stars'
});
this.values.forEach(function (item, index) {
var el = createSpanEl({
'data-index': index,
'data-value': item.value
});
if ('function' === typeof _this.props.stars) {
_this.props.stars.call(_this, el, item, index);
}
[].forEach.call(el.children, function (el) {
return el.style.pointerEvents = 'none';
});
widgetEl.innerHTML += el.outerHTML;
});
}
parentEl.dataset.starRating = '';
parentEl.classList.add(this.props.classNames.base + '--' + this.direction);
if (this.props.tooltip) {
widgetEl.setAttribute('role', 'tooltip');
}
this.widgetEl = widgetEl;
}
}, {
key: "changeIndexTo",
value: function changeIndexTo(index, force) {
var _this2 = this;
// (int):void
if (this.indexActive !== index || force) {
[].forEach.call(this.widgetEl.children, function (el, i) {
// i starts at zero
addRemoveClass(el, i <= index, _this2.props.classNames.active);
addRemoveClass(el, i === _this2.indexSelected, _this2.props.classNames.selected);
});
this.widgetEl.setAttribute('data-rating', index + 1);
if ('function' !== typeof this.props.stars && !this.props.prebuilt) {
// @v3 compat
this.widgetEl.classList.remove('s' + 10 * (this.indexActive + 1));
this.widgetEl.classList.add('s' + 10 * (index + 1));
}
if (this.props.tooltip) {
var _this$values$index;
var label = index < 0 ? this.props.tooltip : (_this$values$index = this.values[index]) === null || _this$values$index === void 0 ? void 0 : _this$values$index.text;
this.widgetEl.setAttribute('aria-label', label);
}
this.indexActive = index;
}
this.ticking = false;
}
}, {
key: "destroy",
value: function destroy() {
// ():void
this.indexActive = null; // the active span index
this.indexSelected = this.selected(); // the selected span index
var parentEl = this.el.parentNode;
if (parentEl.classList.contains(this.props.classNames.base)) {
if (this.props.prebuilt) {
this.widgetEl = parentEl.querySelector('.' + this.props.classNames.base + '--stars');
parentEl.classList.remove(this.props.classNames.base + '--' + this.direction);
delete parentEl.dataset.starRating;
} else {
parentEl.parentNode.replaceChild(this.el, parentEl);
}
this.handleEvents('remove');
}
delete this.el.widget; // remove the widget reference
}
}, {
key: "eventListener",
value: function eventListener(el, action, events, items) {
var _this3 = this;
// (HTMLElement, string, array, object):void
events.forEach(function (ev) {
return el[action + 'EventListener'](ev, _this3.events[ev], items || false);
});
}
}, {
key: "handleEvents",
value: function handleEvents(action) {
// (string):void
var formEl = this.el.closest('form');
if (formEl && formEl.tagName === 'FORM') {
this.eventListener(formEl, action, ['reset']);
}
this.eventListener(this.el, action, ['change']); // always trigger the change event, even when SELECT is disabled
if ('add' === action && this.el.disabled) return;
this.eventListener(this.el, action, ['keydown']);
this.eventListener(this.widgetEl, action, ['mousedown', 'mouseleave', 'mousemove', 'touchend', 'touchmove'], false);
}
}, {
key: "indexFromEvent",
value: function indexFromEvent(ev) {
var _ev$touches, _ev$changedTouches;
// (MouseEvent|TouchEvent):void
var origin = ((_ev$touches = ev.touches) === null || _ev$touches === void 0 ? void 0 : _ev$touches[0]) || ((_ev$changedTouches = ev.changedTouches) === null || _ev$changedTouches === void 0 ? void 0 : _ev$changedTouches[0]) || ev;
var el = document.elementFromPoint(origin.clientX, origin.clientY);
if (el.parentNode === this.widgetEl) {
return [].slice.call(el.parentNode.children).indexOf(el);
}
return this.indexActive;
}
}, {
key: "onChange",
value: function onChange() {
// ():void
this.changeIndexTo(this.selected(), true);
}
}, {
key: "onKeyDown",
value: function onKeyDown(ev) {
// (KeyboardEvent):void
var key = ev.key.slice(5);
if (!~['Left', 'Right'].indexOf(key)) return;
ev.preventDefault();
var increment = key === 'Left' ? -1 : 1;
if (this.direction === 'rtl') {
increment *= -1;
}
var maxIndex = this.values.length - 1;
var minIndex = -1;
var index = Math.min(Math.max(this.selected() + increment, minIndex), maxIndex);
this.selectValue(index, true); // trigger change event
}
}, {
key: "onPointerDown",
value: function onPointerDown(ev) {
// (MouseEvent|TouchEvent):void
ev.preventDefault(); // this.el.focus(); // highlight the rating field
var index = this.indexFromEvent(ev);
if (this.props.clearable && index === this.indexSelected) {
index = -1; // remove the value
}
this.selectValue(index, true); // trigger change event
}
}, {
key: "onPointerLeave",
value: function onPointerLeave(ev) {
var _this4 = this;
// (MouseEvent):void
ev.preventDefault();
cancelAnimationFrame(this.tick);
requestAnimationFrame(function () {
return _this4.changeIndexTo(_this4.indexSelected);
});
}
}, {
key: "onPointerMove",
value: function onPointerMove(ev) {
var _this5 = this;
// (MouseEvent|TouchEvent):void
ev.preventDefault();
if (!this.ticking) {
this.tick = requestAnimationFrame(function () {
return _this5.changeIndexTo(_this5.indexFromEvent(ev));
});
this.ticking = true;
}
}
}, {
key: "onReset",
value: function onReset() {
var _this$el$querySelecto;
// ():void
var index = this.valueIndex((_this$el$querySelecto = this.el.querySelector('[selected]')) === null || _this$el$querySelecto === void 0 ? void 0 : _this$el$querySelecto.value);
this.selectValue(index || -1, false); // do not trigger change event
}
}, {
key: "selected",
value: function selected() {
// ():int
return this.valueIndex(this.el.value); // get the selected span index
}
}, {
key: "selectValue",
value: function selectValue(index, triggerChangeEvent) {
var _this$values$index2;
// (int, bool):void
this.el.value = ((_this$values$index2 = this.values[index]) === null || _this$values$index2 === void 0 ? void 0 : _this$values$index2.value) || ''; // first set the new value
this.indexSelected = this.selected(); // get the actual index from the selected value
if (false === triggerChangeEvent) {
this.changeIndexTo(this.selected(), true);
} else {
this.el.dispatchEvent(new Event('change'));
}
}
}, {
key: "valueIndex",
value: function valueIndex(value) {
return this.values.findIndex(function (val) {
return val.value === +value;
});
}
}]);
return Widget;
}();
var StarRating = /*#__PURE__*/function () {
function StarRating(selector, props) {
_classCallCheck(this, StarRating);
// (HTMLSelectElement|NodeList|string, object):void
this.destroy = this.destroy.bind(this);
this.props = props;
this.rebuild = this.rebuild.bind(this);
this.selector = selector;
this.widgets = [];
this.build();
}
_createClass(StarRating, [{
key: "build",
value: function build() {
var _this = this;
// (HTMLSelectElement|NodeList|string, object):void
this.queryElements(this.selector).forEach(function (el) {
var options = merge(defaults, _this.props, JSON.parse(el.getAttribute('data-options')));
if ('SELECT' === el.tagName && !el.widget) {
// check for an existing Widget reference
if (!options.prebuilt && el.parentNode.classList.contains(options.classNames.base)) {
_this.unwrap(el);
}
_this.widgets.push(new Widget(el, options));
}
});
}
}, {
key: "destroy",
value: function destroy() {
// ():void
this.widgets.forEach(function (widget) {
return widget.destroy();
});
this.widgets = [];
}
}, {
key: "queryElements",
value: function queryElements(selector) {
// (HTMLSelectElement|NodeList|string):array
if ('HTMLSelectElement' === type(selector)) {
return [selector];
}
if ('NodeList' === type(selector)) {
return [].slice.call(selector);
}
if ('String' === type(selector)) {
return [].slice.call(document.querySelectorAll(selector));
}
return [];
}
}, {
key: "rebuild",
value: function rebuild() {
// ():void
this.destroy();
this.build();
}
}, {
key: "unwrap",
value: function unwrap(el) {
var removeEl = el.parentNode;
var parentEl = removeEl.parentNode;
parentEl.insertBefore(el, removeEl);
parentEl.removeChild(removeEl);
}
}]);
return StarRating;
}();
export default StarRating;

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,487 @@
var StarRating = (function () {
'use strict';
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
var defaults = {
classNames: {
active: 'gl-active',
base: 'gl-star-rating',
selected: 'gl-selected'
},
clearable: true,
maxStars: 10,
prebuilt: false,
stars: null,
tooltip: 'Select a Rating'
};
var addRemoveClass = function addRemoveClass(el, bool, className) {
el.classList[bool ? 'add' : 'remove'](className);
};
var createSpanEl = function createSpanEl(attributes) {
var el = document.createElement('span');
attributes = attributes || {};
for (var key in attributes) {
el.setAttribute(key, attributes[key]);
}
return el;
};
var inRange = function inRange(value, min, max) {
return /^\d+$/.test(value) && min <= value && value <= max;
};
var insertSpanEl = function insertSpanEl(el, after, attributes) {
var newEl = createSpanEl(attributes);
el.parentNode.insertBefore(newEl, after ? el.nextSibling : el);
return newEl;
};
var merge = function merge() {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
// adapted from https://github.com/firstandthird/aug
var results = {};
args.forEach(function (prop) {
Object.keys(prop || {}).forEach(function (propName) {
if (args[0][propName] === undefined) return; // restrict keys to the defaults
var propValue = prop[propName];
if (type(propValue) === 'Object' && type(results[propName]) === 'Object') {
results[propName] = merge(results[propName], propValue);
return;
}
results[propName] = propValue;
});
});
return results;
};
var type = function type(value) {
return {}.toString.call(value).slice(8, -1);
};
var values = function values(selectEl) {
var values = [];
[].forEach.call(selectEl.options, function (el) {
var value = parseInt(el.value, 10) || 0;
if (value > 0) {
values.push({
index: el.index,
text: el.text,
value: value
});
}
});
return values.sort(function (a, b) {
return a.value - b.value;
});
};
var Widget = /*#__PURE__*/function () {
function Widget(el, props) {
_classCallCheck(this, Widget);
// (HTMLElement, object):void
this.direction = window.getComputedStyle(el, null).getPropertyValue('direction');
this.el = el;
this.events = {
change: this.onChange.bind(this),
keydown: this.onKeyDown.bind(this),
mousedown: this.onPointerDown.bind(this),
mouseleave: this.onPointerLeave.bind(this),
mousemove: this.onPointerMove.bind(this),
reset: this.onReset.bind(this),
touchend: this.onPointerDown.bind(this),
touchmove: this.onPointerMove.bind(this)
};
this.indexActive = null; // the active span index
this.indexSelected = null; // the selected span index
this.props = props;
this.tick = null;
this.ticking = false;
this.values = values(el);
this.widgetEl = null;
if (this.el.widget) {
this.el.widget.destroy(); // remove any stale event listeners
}
if (inRange(this.values.length, 1, this.props.maxStars)) {
this.build();
} else {
this.destroy();
}
}
_createClass(Widget, [{
key: "build",
value: function build() {
// ():void
this.destroy();
this.buildWidget();
this.selectValue(this.indexSelected = this.selected(), false); // set the initial value but do not trigger change event
this.handleEvents('add');
this.el.widget = this; // store a reference to this widget on the SELECT so that we can remove stale event listeners
}
}, {
key: "buildWidget",
value: function buildWidget() {
var _this = this;
// ():void
var parentEl = null;
var widgetEl = null;
if (this.props.prebuilt) {
parentEl = this.el.parentNode;
widgetEl = parentEl.querySelector('.' + this.props.classNames.base + '--stars');
}
if (null === widgetEl) {
parentEl = insertSpanEl(this.el, false, {
"class": this.props.classNames.base
});
parentEl.appendChild(this.el);
widgetEl = insertSpanEl(this.el, true, {
"class": this.props.classNames.base + '--stars'
});
this.values.forEach(function (item, index) {
var el = createSpanEl({
'data-index': index,
'data-value': item.value
});
if ('function' === typeof _this.props.stars) {
_this.props.stars.call(_this, el, item, index);
}
[].forEach.call(el.children, function (el) {
return el.style.pointerEvents = 'none';
});
widgetEl.innerHTML += el.outerHTML;
});
}
parentEl.dataset.starRating = '';
parentEl.classList.add(this.props.classNames.base + '--' + this.direction);
if (this.props.tooltip) {
widgetEl.setAttribute('role', 'tooltip');
}
this.widgetEl = widgetEl;
}
}, {
key: "changeIndexTo",
value: function changeIndexTo(index, force) {
var _this2 = this;
// (int):void
if (this.indexActive !== index || force) {
[].forEach.call(this.widgetEl.children, function (el, i) {
// i starts at zero
addRemoveClass(el, i <= index, _this2.props.classNames.active);
addRemoveClass(el, i === _this2.indexSelected, _this2.props.classNames.selected);
});
this.widgetEl.setAttribute('data-rating', index + 1);
if ('function' !== typeof this.props.stars && !this.props.prebuilt) {
// @v3 compat
this.widgetEl.classList.remove('s' + 10 * (this.indexActive + 1));
this.widgetEl.classList.add('s' + 10 * (index + 1));
}
if (this.props.tooltip) {
var _this$values$index;
var label = index < 0 ? this.props.tooltip : (_this$values$index = this.values[index]) === null || _this$values$index === void 0 ? void 0 : _this$values$index.text;
this.widgetEl.setAttribute('aria-label', label);
}
this.indexActive = index;
}
this.ticking = false;
}
}, {
key: "destroy",
value: function destroy() {
// ():void
this.indexActive = null; // the active span index
this.indexSelected = this.selected(); // the selected span index
var parentEl = this.el.parentNode;
if (parentEl.classList.contains(this.props.classNames.base)) {
if (this.props.prebuilt) {
this.widgetEl = parentEl.querySelector('.' + this.props.classNames.base + '--stars');
parentEl.classList.remove(this.props.classNames.base + '--' + this.direction);
delete parentEl.dataset.starRating;
} else {
parentEl.parentNode.replaceChild(this.el, parentEl);
}
this.handleEvents('remove');
}
delete this.el.widget; // remove the widget reference
}
}, {
key: "eventListener",
value: function eventListener(el, action, events, items) {
var _this3 = this;
// (HTMLElement, string, array, object):void
events.forEach(function (ev) {
return el[action + 'EventListener'](ev, _this3.events[ev], items || false);
});
}
}, {
key: "handleEvents",
value: function handleEvents(action) {
// (string):void
var formEl = this.el.closest('form');
if (formEl && formEl.tagName === 'FORM') {
this.eventListener(formEl, action, ['reset']);
}
this.eventListener(this.el, action, ['change']); // always trigger the change event, even when SELECT is disabled
if ('add' === action && this.el.disabled) return;
this.eventListener(this.el, action, ['keydown']);
this.eventListener(this.widgetEl, action, ['mousedown', 'mouseleave', 'mousemove', 'touchend', 'touchmove'], false);
}
}, {
key: "indexFromEvent",
value: function indexFromEvent(ev) {
var _ev$touches, _ev$changedTouches;
// (MouseEvent|TouchEvent):void
var origin = ((_ev$touches = ev.touches) === null || _ev$touches === void 0 ? void 0 : _ev$touches[0]) || ((_ev$changedTouches = ev.changedTouches) === null || _ev$changedTouches === void 0 ? void 0 : _ev$changedTouches[0]) || ev;
var el = document.elementFromPoint(origin.clientX, origin.clientY);
if (el.parentNode === this.widgetEl) {
return [].slice.call(el.parentNode.children).indexOf(el);
}
return this.indexActive;
}
}, {
key: "onChange",
value: function onChange() {
// ():void
this.changeIndexTo(this.selected(), true);
}
}, {
key: "onKeyDown",
value: function onKeyDown(ev) {
// (KeyboardEvent):void
var key = ev.key.slice(5);
if (!~['Left', 'Right'].indexOf(key)) return;
ev.preventDefault();
var increment = key === 'Left' ? -1 : 1;
if (this.direction === 'rtl') {
increment *= -1;
}
var maxIndex = this.values.length - 1;
var minIndex = -1;
var index = Math.min(Math.max(this.selected() + increment, minIndex), maxIndex);
this.selectValue(index, true); // trigger change event
}
}, {
key: "onPointerDown",
value: function onPointerDown(ev) {
// (MouseEvent|TouchEvent):void
ev.preventDefault(); // this.el.focus(); // highlight the rating field
var index = this.indexFromEvent(ev);
if (this.props.clearable && index === this.indexSelected) {
index = -1; // remove the value
}
this.selectValue(index, true); // trigger change event
}
}, {
key: "onPointerLeave",
value: function onPointerLeave(ev) {
var _this4 = this;
// (MouseEvent):void
ev.preventDefault();
cancelAnimationFrame(this.tick);
requestAnimationFrame(function () {
return _this4.changeIndexTo(_this4.indexSelected);
});
}
}, {
key: "onPointerMove",
value: function onPointerMove(ev) {
var _this5 = this;
// (MouseEvent|TouchEvent):void
ev.preventDefault();
if (!this.ticking) {
this.tick = requestAnimationFrame(function () {
return _this5.changeIndexTo(_this5.indexFromEvent(ev));
});
this.ticking = true;
}
}
}, {
key: "onReset",
value: function onReset() {
var _this$el$querySelecto;
// ():void
var index = this.valueIndex((_this$el$querySelecto = this.el.querySelector('[selected]')) === null || _this$el$querySelecto === void 0 ? void 0 : _this$el$querySelecto.value);
this.selectValue(index || -1, false); // do not trigger change event
}
}, {
key: "selected",
value: function selected() {
// ():int
return this.valueIndex(this.el.value); // get the selected span index
}
}, {
key: "selectValue",
value: function selectValue(index, triggerChangeEvent) {
var _this$values$index2;
// (int, bool):void
this.el.value = ((_this$values$index2 = this.values[index]) === null || _this$values$index2 === void 0 ? void 0 : _this$values$index2.value) || ''; // first set the new value
this.indexSelected = this.selected(); // get the actual index from the selected value
if (false === triggerChangeEvent) {
this.changeIndexTo(this.selected(), true);
} else {
this.el.dispatchEvent(new Event('change'));
}
}
}, {
key: "valueIndex",
value: function valueIndex(value) {
return this.values.findIndex(function (val) {
return val.value === +value;
});
}
}]);
return Widget;
}();
var StarRating = /*#__PURE__*/function () {
function StarRating(selector, props) {
_classCallCheck(this, StarRating);
// (HTMLSelectElement|NodeList|string, object):void
this.destroy = this.destroy.bind(this);
this.props = props;
this.rebuild = this.rebuild.bind(this);
this.selector = selector;
this.widgets = [];
this.build();
}
_createClass(StarRating, [{
key: "build",
value: function build() {
var _this = this;
// (HTMLSelectElement|NodeList|string, object):void
this.queryElements(this.selector).forEach(function (el) {
var options = merge(defaults, _this.props, JSON.parse(el.getAttribute('data-options')));
if ('SELECT' === el.tagName && !el.widget) {
// check for an existing Widget reference
if (!options.prebuilt && el.parentNode.classList.contains(options.classNames.base)) {
_this.unwrap(el);
}
_this.widgets.push(new Widget(el, options));
}
});
}
}, {
key: "destroy",
value: function destroy() {
// ():void
this.widgets.forEach(function (widget) {
return widget.destroy();
});
this.widgets = [];
}
}, {
key: "queryElements",
value: function queryElements(selector) {
// (HTMLSelectElement|NodeList|string):array
if ('HTMLSelectElement' === type(selector)) {
return [selector];
}
if ('NodeList' === type(selector)) {
return [].slice.call(selector);
}
if ('String' === type(selector)) {
return [].slice.call(document.querySelectorAll(selector));
}
return [];
}
}, {
key: "rebuild",
value: function rebuild() {
// ():void
this.destroy();
this.build();
}
}, {
key: "unwrap",
value: function unwrap(el) {
var removeEl = el.parentNode;
var parentEl = removeEl.parentNode;
parentEl.insertBefore(el, removeEl);
parentEl.removeChild(removeEl);
}
}]);
return StarRating;
}();
return StarRating;
}());

View file

@ -0,0 +1 @@
:root{--gl-star-color:#fdd835;--gl-star-color-inactive:#dcdce6;--gl-star-empty:url(../img/star-empty.svg);--gl-star-full:url(../img/star-full.svg);--gl-star-size:24px;--gl-tooltip-background:rgba(17,17,17,0.9);--gl-tooltip-border-radius:4px;--gl-tooltip-color:#fff;--gl-tooltip-font-size:0.875rem;--gl-tooltip-font-weight:400;--gl-tooltip-line-height:1;--gl-tooltip-margin:12px;--gl-tooltip-padding:.5em 1em}[data-star-rating]>select{-webkit-clip-path:circle(1px at 0 0)!important;clip-path:circle(1px at 0 0)!important;clip:rect(1px,1px,1px,1px)!important;height:1px!important;margin:0!important;overflow:hidden!important;padding:0!important;position:absolute!important;top:0!important;visibility:visible!important;white-space:nowrap!important;width:1px!important;-webkit-appearance:none;-moz-appearance:none;appearance:none;pointer-events:none}[data-star-rating]>select:after,[data-star-rating]>select:before{display:none!important}[data-star-rating].gl-star-rating--ltr>select{left:0!important}[data-star-rating].gl-star-rating--rtl>select{right:0!important}[data-star-rating]{align-items:center;display:flex;position:relative}.gl-star-rating:not([data-star-rating]) .gl-star-rating--stars{display:none}[data-star-rating] .gl-star-rating--stars{align-items:center;cursor:pointer;display:flex;position:relative}[data-star-rating]>select:focus+.gl-star-rating--stars span:first-child:before{box-shadow:0 0 0 3px -moz-mac-focusring;box-shadow:0 0 0 3px -webkit-focus-ring-color;box-shadow:0 0 0 3px Highlight;content:"";display:block;height:100%;outline:1px solid transparent;pointer-events:none;position:absolute;width:100%}[data-star-rating] select[disabled]+.gl-star-rating--stars{cursor:default}[data-star-rating] .gl-star-rating--stars>span{display:flex;height:24px;height:var(--gl-star-size);margin:0;width:24px;width:var(--gl-star-size)}[data-star-rating] .gl-star-rating--stars[aria-label]:after,[data-star-rating] .gl-star-rating--stars[aria-label]:before{-webkit-backface-visibility:hidden;backface-visibility:hidden;bottom:auto;box-sizing:border-box;left:100%;pointer-events:none;position:absolute;top:50%;transform-origin:top;transform:translate3d(0,-50%,0);white-space:nowrap;z-index:10}[data-star-rating] .gl-star-rating--stars[aria-label]:before{background:rgba(17,17,17,.9);background:var(--gl-tooltip-background);-webkit-clip-path:path("M6 14.998c0-3-6-5.499-6-7.499S5.999 3 5.999 0L6 14.998z");clip-path:path("M6 14.998c0-3-6-5.499-6-7.499S5.999 3 5.999 0L6 14.998z");content:"";height:15px;margin:0 0 0 6px;width:6px}[data-star-rating] .gl-star-rating--stars[aria-label]:after{background:rgba(17,17,17,.9);background:var(--gl-tooltip-background);border-radius:4px;border-radius:var(--gl-tooltip-border-radius);color:#fff;color:var(--gl-tooltip-color);content:attr(aria-label);font-size:.875rem;font-size:var(--gl-tooltip-font-size);font-weight:400;margin-left:12px;margin-left:var(--gl-tooltip-margin);padding:.5em 1em;padding:var(--gl-tooltip-padding);text-transform:none}[data-star-rating].gl-star-rating--rtl .gl-star-rating--stars[aria-label]:after,[data-star-rating].gl-star-rating--rtl .gl-star-rating--stars[aria-label]:before{left:auto;right:100%}[data-star-rating].gl-star-rating--rtl .gl-star-rating--stars[aria-label]:before{transform:scaleX(-1) translate3d(0,-50%,0);margin:0 6px 0 0}[data-star-rating].gl-star-rating--rtl .gl-star-rating--stars[aria-label]:after{margin-left:0;margin-right:12px;margin-right:var(--gl-tooltip-margin)}[data-star-rating] svg{height:100%;width:100%}[data-star-rating] .gl-star-half{fill:none;stroke:none}[data-star-rating] .gl-star-full{fill:#dcdce6;fill:var(--gl-star-color-inactive);stroke:#dcdce6;stroke:var(--gl-star-color-inactive);transition:fill .15s ease-in-out,stroke .15s ease-in-out}[data-star-rating] .gl-active .gl-star-full{fill:#fdd835;fill:var(--gl-star-color);stroke:#fdd835;stroke:var(--gl-star-color)}.gl-star-rating--stars[class*=" s"]>span{background-image:url(../img/star-empty.svg)!important;background-image:var(--gl-star-empty)!important;background-position:50%;background-repeat:no-repeat;background-size:90%}.gl-star-rating--stars[class*=" s"]>span.gl-active,.gl-star-rating--stars[class*=" s"]>span.gl-active.gl-selected{background-image:url(../img/star-full.svg)!important;background-image:var(--gl-star-full)!important}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><path fill="#ffb900" d="M9 0l3 6 6 .75-4.13 4.62L15 18l-6-3-6 3 1.12-6.63L0 6.75 6 6l3-6zm0 2.24L6.66 6.93l-4.65.58 3.18 3.56-.87 5.15L9 13.88l4.68 2.34-.87-5.15 3.18-3.56-4.65-.58L9 2.24z"/></svg>

After

Width:  |  Height:  |  Size: 258 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><path fill="#ffb900" d="M9 0L6 6l-6 .75 4.12 4.62L3 18l6-3 6 3-1.13-6.63L18 6.75 12 6z"/></svg>

After

Width:  |  Height:  |  Size: 156 B

View file

@ -0,0 +1,69 @@
{
"name": "star-rating.js",
"description": "This zero-dependency ES6 module transforms a SELECT into a dynamic star rating element.",
"version": "4.3.1",
"author": {
"name": "Paul Ryley",
"email": "paul@geminilabs.io",
"url": "http://geminilabs.io"
},
"homepage": "https://github.com/pryley/star-rating.js",
"repository": {
"type": "git",
"url": "https://github.com/pryley/star-rating.js.git"
},
"bugs": {
"url": "https://github.com/pryley/star-rating.js/issues"
},
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.12.10",
"@babel/plugin-proposal-optional-chaining": "^7.12.7",
"@babel/preset-env": "^7.12.11",
"@rollup/plugin-babel": "^5.2.2",
"@rollup/plugin-eslint": "^8.0.1",
"@rollup/plugin-node-resolve": "^11.0.1",
"autoprefixer": "^10.2.3",
"gulp": "^4.0.2",
"gulp-bump": "^3.2.0",
"jshint": "^2.11.1",
"postcss-custom-properties": "^11.0.0",
"postcss-hexrgba": "^2.0.1",
"postcss-import": "^14.1.0",
"postcss-preset-env": "^7.7.2",
"postcss-selector-namespace": "github:pryley/postcss-selector-namespace#v3.0.2-beta",
"pump": "^3.0.0",
"rollup": "^2.36.1",
"rollup-plugin-filesize": "^9.1.0",
"rollup-plugin-postcss": "^4.0.0",
"rollup-plugin-terser": "^7.0.2",
"yargs": "^15.4.1"
},
"scripts": {
"build": "NODE_ENV=production npx rollup -c",
"watch": "npx rollup -cw"
},
"main": "dist/star-rating.cjs.js",
"module": "dist/star-rating.esm.js",
"style": "src/index.css",
"files": [
"dist",
"img",
"src"
],
"exports": {
".": {
"require": "./dist/star-rating.cjs.js",
"import": "./dist/star-rating.esm.js",
"default": "./dist/star-rating.esm.js"
},
"./css": "./dist/star-rating.min.css",
"./dist/": "./dist/",
"./img/": "./img/",
"./src/": "./src/",
"./package.json": "./package.json"
},
"dependencies": {
"detect-it": "^4.0.0"
}
}

View file

@ -0,0 +1,12 @@
export const defaults = {
classNames: {
active: 'gl-active',
base: 'gl-star-rating',
selected: 'gl-selected',
},
clearable: true,
maxStars: 10,
prebuilt: false,
stars: null,
tooltip: 'Select a Rating',
};

View file

@ -0,0 +1,61 @@
export const addRemoveClass = (el, bool, className) => {
el.classList[bool ? 'add' : 'remove'](className);
}
export const createSpanEl = (attributes) => {
const el = document.createElement('span');
attributes = attributes || {};
for (let key in attributes) {
el.setAttribute(key, attributes[key]);
}
return el;
}
export const inRange = (value, min, max) => {
return /^\d+$/.test(value) && min <= value && value <= max;
}
export const insertSpanEl = (el, after, attributes) => {
const newEl = createSpanEl(attributes);
el.parentNode.insertBefore(newEl, after ? el.nextSibling : el);
return newEl;
}
export const isEmpty = (el) => {
return null === el.getAttribute('value') || '' === el.value;
}
export const merge = (...args) => { // adapted from https://github.com/firstandthird/aug
const results = {};
args.forEach(prop => {
Object.keys(prop || {}).forEach(propName => {
if (args[0][propName] === undefined) return; // restrict keys to the defaults
const propValue = prop[propName];
if (type(propValue) === 'Object' && type(results[propName]) === 'Object') {
results[propName] = merge(results[propName], propValue);
return;
}
results[propName] = propValue;
});
});
return results;
}
export const type = (value) => {
return {}.toString.call(value).slice(8, -1);
};
export const values = (selectEl) => {
const values = [];
[].forEach.call(selectEl.options, (el) => {
const value = parseInt(el.value, 10) || 0;
if (value > 0) {
values.push({
index: el.index,
text: el.text,
value: value,
})
}
});
return values.sort((a, b) => a.value - b.value);
}

View file

@ -0,0 +1,159 @@
/**
* Star Rating
* @version: 4.3.1
* @author: Paul Ryley (http://geminilabs.io)
* @url: https://github.com/pryley/star-rating.js
* @license: MIT
*/
:root {
--gl-star-color: #fdd835;
--gl-star-color-inactive: #dcdce6;
--gl-star-empty: url('../img/star-empty.svg');
--gl-star-full: url('../img/star-full.svg');
--gl-star-size: 24px;
--gl-tooltip-background: rgba(17,17,17, .9);
--gl-tooltip-border-radius: 4px;
--gl-tooltip-color: #fff;
--gl-tooltip-font-size: 0.875rem;
--gl-tooltip-font-weight: 400;
--gl-tooltip-line-height: 1;
--gl-tooltip-margin: 12px;
--gl-tooltip-padding: .5em 1em;
}
[data-star-rating] > select {
appearance: none;
clip-path: circle(1px at 0 0) !important;
clip: rect(1px, 1px, 1px, 1px) !important;
height: 1px !important;
margin: 0 !important;
overflow: hidden !important;
padding: 0 !important;
pointer-events: none;
position: absolute !important;
top: 0 !important;
visibility: visible !important;
white-space: nowrap !important;
width: 1px !important;
}
[data-star-rating] > select::before,
[data-star-rating] > select::after {
display: none !important;
}
[data-star-rating].gl-star-rating--ltr > select {
left: 0 !important;
}
[data-star-rating].gl-star-rating--rtl > select {
right: 0 !important;
}
[data-star-rating] {
align-items: center;
display: flex;
position: relative;
}
.gl-star-rating:not([data-star-rating]) .gl-star-rating--stars {
display: none;
}
[data-star-rating] .gl-star-rating--stars {
align-items: center;
cursor: pointer;
display: flex;
position: relative;
}
[data-star-rating] > select:focus + .gl-star-rating--stars span:first-child::before {
box-shadow: 0 0 0 3px -moz-mac-focusring;
box-shadow: 0 0 0 3px -webkit-focus-ring-color;
box-shadow: 0 0 0 3px Highlight;
content: '';
display: block;
height: 100%;
outline: 1px solid transparent;
pointer-events: none;
position: absolute;
width: 100%;
}
[data-star-rating] select[disabled] + .gl-star-rating--stars {
cursor: default;
}
[data-star-rating] .gl-star-rating--stars > span {
display: flex;
height: var(--gl-star-size);
margin: 0;
width: var(--gl-star-size);
}
[data-star-rating] .gl-star-rating--stars[aria-label]::before,
[data-star-rating] .gl-star-rating--stars[aria-label]::after {
backface-visibility: hidden;
bottom: auto;
box-sizing: border-box;
left: 100%;
pointer-events: none;
position: absolute;
top: 50%;
transform-origin: top;
transform: translate3d(0,-50%,0);
white-space: nowrap;
z-index: 10;
}
[data-star-rating] .gl-star-rating--stars[aria-label]::before {
background: var(--gl-tooltip-background);
clip-path: path('M6 14.998c0-3-6-5.499-6-7.499S5.999 3 5.999 0L6 14.998z');
content: '';
height: 15px;
margin: 0 0 0 6px;
width: 6px;
}
[data-star-rating] .gl-star-rating--stars[aria-label]::after {
background: var(--gl-tooltip-background);
border-radius: var(--gl-tooltip-border-radius);
color: var(--gl-tooltip-color);
content: attr(aria-label);
font-size: var(--gl-tooltip-font-size);
font-weight: normal;
margin-left: var(--gl-tooltip-margin);
padding: var(--gl-tooltip-padding);
text-transform: none;
}
[data-star-rating].gl-star-rating--rtl .gl-star-rating--stars[aria-label]::before,
[data-star-rating].gl-star-rating--rtl .gl-star-rating--stars[aria-label]::after {
left: auto;
right: 100%;
}
[data-star-rating].gl-star-rating--rtl .gl-star-rating--stars[aria-label]::before {
transform: scaleX(-1) translate3d(0,-50%,0);
margin: 0 6px 0 0;
}
[data-star-rating].gl-star-rating--rtl .gl-star-rating--stars[aria-label]::after {
margin-left: 0;
margin-right: var(--gl-tooltip-margin);
}
[data-star-rating] svg {
height: 100%;
width: 100%;
}
[data-star-rating] .gl-star-half {
fill: none;
stroke: none;
}
[data-star-rating] .gl-star-full {
fill: var(--gl-star-color-inactive);
stroke: var(--gl-star-color-inactive);
transition: fill 0.15s ease-in-out, stroke 0.15s ease-in-out;
}
[data-star-rating] .gl-active .gl-star-full {
fill: var(--gl-star-color);
stroke: var(--gl-star-color);
}
/* Compatibilty with v3 */
.gl-star-rating--stars[class*=" s"] > span {
background-image: var(--gl-star-empty) !important;
background-position: center;
background-repeat: no-repeat;
background-size: 90%;
}
.gl-star-rating--stars[class*=" s"] > span.gl-active,
.gl-star-rating--stars[class*=" s"] > span.gl-active.gl-selected {
background-image: var(--gl-star-full) !important;
}

View file

@ -0,0 +1,66 @@
/**!
* Star Rating
* @version: 4.3.1
* @author: Paul Ryley (http://geminilabs.io)
* @url: https://github.com/pryley/star-rating.js
* @license: MIT
*/
import { defaults } from './defaults'
import { merge, type } from './helpers'
import { Widget } from './widget'
class StarRating {
constructor (selector, props) { // (HTMLSelectElement|NodeList|string, object):void
this.destroy = this.destroy.bind(this);
this.props = props;
this.rebuild = this.rebuild.bind(this);
this.selector = selector;
this.widgets = [];
this.build();
}
build() { // (HTMLSelectElement|NodeList|string, object):void
this.queryElements(this.selector).forEach(el => {
const options = merge(defaults, this.props, JSON.parse(el.getAttribute('data-options')));
if ('SELECT' === el.tagName && !el.widget) { // check for an existing Widget reference
if (!options.prebuilt && el.parentNode.classList.contains(options.classNames.base)) {
this.unwrap(el);
}
this.widgets.push(new Widget(el, options));
}
});
}
destroy () { // ():void
this.widgets.forEach(widget => widget.destroy());
this.widgets = [];
}
queryElements (selector) { // (HTMLSelectElement|NodeList|string):array
if ('HTMLSelectElement' === type(selector)) {
return [selector];
}
if ('NodeList' === type(selector)) {
return [].slice.call(selector);
}
if ('String' === type(selector)) {
return [].slice.call(document.querySelectorAll(selector))
}
return []
}
rebuild () { // ():void
this.destroy();
this.build();
}
unwrap (el) {
const removeEl = el.parentNode;
const parentEl = removeEl.parentNode;
parentEl.insertBefore(el, removeEl);
parentEl.removeChild(removeEl);
}
}
export default StarRating

View file

@ -0,0 +1,198 @@
import { addRemoveClass, createSpanEl, inRange, insertSpanEl, isEmpty, values } from './helpers'
import { supportsPassiveEvents } from 'detect-it'
export class Widget {
constructor (el, props) { // (HTMLElement, object):void
this.direction = window.getComputedStyle(el, null).getPropertyValue('direction');
this.el = el;
this.events = {
change: this.onChange.bind(this),
keydown: this.onKeyDown.bind(this),
mousedown: this.onPointerDown.bind(this),
mouseleave: this.onPointerLeave.bind(this),
mousemove: this.onPointerMove.bind(this),
reset: this.onReset.bind(this),
touchend: this.onPointerDown.bind(this),
touchmove: this.onPointerMove.bind(this),
};
this.indexActive = null; // the active span index
this.indexSelected = null; // the selected span index
this.props = props;
this.tick = null;
this.ticking = false;
this.values = values(el);
this.widgetEl = null;
if (this.el.widget) {
this.el.widget.destroy(); // remove any stale event listeners
}
if (inRange(this.values.length, 1, this.props.maxStars)) {
this.build();
} else {
this.destroy();
}
}
build () { // ():void
this.destroy();
this.buildWidget();
this.selectValue((this.indexSelected = this.selected()), false); // set the initial value but do not trigger change event
this.handleEvents('add');
this.el.widget = this; // store a reference to this widget on the SELECT so that we can remove stale event listeners
}
buildWidget () { // ():void
let parentEl = null;
let widgetEl = null;
if (this.props.prebuilt) {
parentEl = this.el.parentNode
widgetEl = parentEl.querySelector('.' + this.props.classNames.base + '--stars')
}
if (null === widgetEl) {
parentEl = insertSpanEl(this.el, false, { class: this.props.classNames.base });
parentEl.appendChild(this.el);
widgetEl = insertSpanEl(this.el, true, { class: this.props.classNames.base + '--stars' });
this.values.forEach((item, index) => {
const el = createSpanEl({ 'data-index': index, 'data-value': item.value });
if ('function' === typeof this.props.stars) {
this.props.stars.call(this, el, item, index);
}
[].forEach.call(el.children, el => el.style.pointerEvents = 'none');
widgetEl.innerHTML += el.outerHTML;
})
}
parentEl.dataset.starRating = '';
parentEl.classList.add(this.props.classNames.base + '--' + this.direction);
if (this.props.tooltip) {
widgetEl.setAttribute('role', 'tooltip');
}
this.widgetEl = widgetEl
}
changeIndexTo (index, force) { // (int):void
if (this.indexActive !== index || force) {
[].forEach.call(this.widgetEl.children, (el, i) => { // i starts at zero
addRemoveClass(el, i <= index, this.props.classNames.active);
addRemoveClass(el, i === this.indexSelected, this.props.classNames.selected);
});
this.widgetEl.setAttribute('data-rating', index + 1);
if ('function' !== typeof this.props.stars && !this.props.prebuilt) { // @v3 compat
this.widgetEl.classList.remove('s' + (10 * (this.indexActive + 1)));
this.widgetEl.classList.add('s' + (10 * (index + 1)));
}
if (this.props.tooltip) {
const label = index < 0 ? this.props.tooltip : this.values[index]?.text;
this.widgetEl.setAttribute('aria-label', label);
}
this.indexActive = index;
}
this.ticking = false;
}
destroy () { // ():void
this.indexActive = null; // the active span index
this.indexSelected = this.selected(); // the selected span index
const parentEl = this.el.parentNode;
if (parentEl.classList.contains(this.props.classNames.base)) {
if (this.props.prebuilt) {
this.widgetEl = parentEl.querySelector('.' + this.props.classNames.base + '--stars')
parentEl.classList.remove(this.props.classNames.base + '--' + this.direction);
delete parentEl.dataset.starRating
} else {
parentEl.parentNode.replaceChild(this.el, parentEl);
}
this.handleEvents('remove');
}
delete this.el.widget // remove the widget reference
}
eventListener (el, action, events, items) { // (HTMLElement, string, array, object):void
events.forEach(ev => el[action + 'EventListener'](ev, this.events[ev], items || false));
}
handleEvents (action) { // (string):void
const formEl = this.el.closest('form');
if (formEl && formEl.tagName === 'FORM') {
this.eventListener(formEl, action, ['reset']);
}
this.eventListener(this.el, action, ['change']); // always trigger the change event, even when SELECT is disabled
if ('add' === action && this.el.disabled) return;
this.eventListener(this.el, action, ['keydown']);
this.eventListener(this.widgetEl, action, ['mousedown', 'mouseleave', 'mousemove', 'touchend', 'touchmove'],
supportsPassiveEvents ? { passive: false } : false
);
}
indexFromEvent (ev) { // (MouseEvent|TouchEvent):void
const origin = ev.touches?.[0] || ev.changedTouches?.[0] || ev;
const el = document.elementFromPoint(origin.clientX, origin.clientY);
if (el.parentNode === this.widgetEl) {
return [].slice.call(el.parentNode.children).indexOf(el);
}
return this.indexActive;
}
onChange () { // ():void
this.changeIndexTo(this.selected(), true);
}
onKeyDown (ev) { // (KeyboardEvent):void
const key = ev.key.slice(5);
if (!~['Left', 'Right'].indexOf(key)) return;
ev.preventDefault();
let increment = key === 'Left' ? -1 : 1;
if (this.direction === 'rtl') {
increment *= -1;
}
const maxIndex = this.values.length - 1;
const minIndex = -1;
const index = Math.min(Math.max(this.selected() + increment, minIndex), maxIndex);
this.selectValue(index, true); // trigger change event
}
onPointerDown (ev) { // (MouseEvent|TouchEvent):void
ev.preventDefault();
// this.el.focus(); // highlight the rating field
let index = this.indexFromEvent(ev);
if (this.props.clearable && index === this.indexSelected) {
index = -1; // remove the value
}
this.selectValue(index, true); // trigger change event
}
onPointerLeave (ev) { // (MouseEvent):void
ev.preventDefault();
cancelAnimationFrame(this.tick);
requestAnimationFrame(() => this.changeIndexTo(this.indexSelected));
}
onPointerMove (ev) { // (MouseEvent|TouchEvent):void
ev.preventDefault();
if (!this.ticking) {
this.tick = requestAnimationFrame(() => this.changeIndexTo(this.indexFromEvent(ev)));
this.ticking = true;
}
}
onReset () { // ():void
const index = this.valueIndex(this.el.querySelector('[selected]')?.value)
this.selectValue(index || -1, false); // do not trigger change event
}
selected () { // ():int
return this.valueIndex(this.el.value); // get the selected span index
}
selectValue (index, triggerChangeEvent) { // (int, bool):void
this.el.value = this.values[index]?.value || ''; // first set the new value
this.indexSelected = this.selected(); // get the actual index from the selected value
if (false === triggerChangeEvent) {
this.changeIndexTo(this.selected(), true);
} else {
this.el.dispatchEvent(new Event('change'));
}
}
valueIndex (value) {
return this.values.findIndex(val => val.value === +value);
}
}