- 3.6 version
This commit is contained in:
Emmanuel ROY 2021-06-04 11:45:04 +02:00
parent d7ab71841e
commit 23878bfe8e
42 changed files with 25107 additions and 16 deletions

View file

@ -24,11 +24,13 @@ class Bdd
public function faireSQLRequete($sql)
{
$sql = \MVC\Classe\Caracter::avoid_sql_injection($sql);
$req = $this->bdd->query($sql);
// Print Pdo::ERRORs
if (!$req && (ENV == 'TEST' || ENV == 'DEV')) {
if (!$req && (ENV == 'TEST' || ENV == 'DEVEL')) {
echo "\nPDO::errorInfo():\n";
print_r($this->bdd->errorInfo());
print_r($sql);
}
return $req;
}
@ -55,17 +57,44 @@ class Bdd
*/
public function faireBindRequete($sql, array $params = null)
{
$req = $this->bdd->prepare($sql);
if ($params) {
foreach ($params as $value) {
$req->bindParam($value[0], Caracter::normalise_ChaineDeCaracteres($value[1]), $value[2]);
try{
$req = $this->bdd->prepare($sql);
// cas de tests des variables qui lance une exception
//if(!$Allow) throw new Exception("Le format de l'isbn ne correspond pas au format attendu");
if ($params) {
foreach ($params as $value) {
$value[1] = Caracter::normalise_ChaineDeCaracteres($value[1]);
if($value[2] !== 'VALUE') {
$req->bindParam(':'.$value[0], $value[1], $value[2]);
}else{
$req->bindValue(':'.$value[0], $value[1]);
}
}
}
$req->execute();
}
$req->execute();
// Print Pdo::ERRORs
if (!$req && (ENV == 'TEST' || ENV == 'DEV')) {
echo "\nPDO::errorInfo():\n";
print_r($this->bdd->errorInfo());
catch(PDOException $pdo_e){
if (ENV == 'TEST' || ENV == 'DEVEL')
{
// Print Pdo::ERRORs
//Faire quelque choses en cas d'erreur PDO
echo "\nPDOException():\n";
print_r($this->bdd->errorInfo());
print_r($pdo_e);
print_r($sql);
}
}
catch(Exception $e){
if (ENV == 'TEST' || ENV == 'DEVEL') {
//Pour les autres erreurs faire autre chose
echo "\nException():\n";
print_r($this->bdd->errorInfo());
print_r($e);
print_r($sql);
}
}
//$req->closeCursor();
return $req;

View file

@ -74,4 +74,17 @@ class Caracter
$chaine = str_replace('"', '"', $chaine);
return $chaine;
}
public static function avoid_sql_injection($chaine){
$chaine = preg_replace("/`;--/", "", $chaine);
$chaine = preg_replace("/';--/", "", $chaine);
$chaine = preg_replace('/";--/', "", $chaine);
$chaine = preg_replace("/;--/", "", $chaine);
return $chaine;
}
public static function avoid_guillemets($chaine)
{
$chaine = str_replace("'", "", $chaine);
$chaine = str_replace('"', '', $chaine);
return $chaine;
}
}

View file

@ -47,6 +47,11 @@ class Controlleur
$_GET[$key] = $value;
$url_params[$key] = $value;
}
//FIXME : Comportement anormal sur les traitements
foreach($application->url->page['params'] as $value => $key){
$_GET[$key] = $value;
$url_params[$key] = $value;
}
require TRAITEMENT_PATH . DIRECTORY_SEPARATOR . $application->url->page['name'] . '.php';
//sinon c'est une page MVC normale
} else {

View file

@ -10,6 +10,12 @@ class Action
return $this->renderBlade($view,$data);
}
public function renderJSON($json)
{
//header('Content-Type: application/json; charset=utf-8');
return json_encode($json, JSON_HEX_APOS);
}
public function renderTwig($view, $data)
{
$paths = new \SplPriorityQueue;

View file

@ -3,8 +3,6 @@
namespace MVC\Classe;
use Symfony\Component\Validator\Constraints\Date;
class Logger
{
public static function addLog($type = 'default', $what = "")
@ -28,14 +26,14 @@ class Logger
*/
public static function logCommandErrors(array $errors)
{
// log connection errors to the web service
// log connection errors
ob_start();
foreach ($errors as $key => $value) {
echo "\n\n$key : \n";
print_r($value);
}
$write_string = ob_get_clean();
file_put_contents(dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . "logs" . DIRECTORY_SEPARATOR . "errors_command.log", $write_string);
file_put_contents(OUTPUT_PATH . DIRECTORY_SEPARATOR . "logs" . DIRECTORY_SEPARATOR . "errors_command.log", $write_string);
return;
}

View file

@ -244,12 +244,20 @@ class Url
} else {
$scheme = 'http';
}
$base_url = $scheme . "://" . $url . "/";
if(BASE_SERVER_DIRECTORY == "") {
$base_url = $scheme . "://" . $url . "/";
}else{
$base_url = $scheme . "://" . $url;
}
$url = $base_url;
}else{
$url = PATH_URL;
}
return $url . BASE_SERVER_DIRECTORY;
if( substr($url . BASE_SERVER_DIRECTORY, -1) == "/"){
return substr($url . BASE_SERVER_DIRECTORY, 0, -1);
}else {
return $url . BASE_SERVER_DIRECTORY;
}
}
/**
* Obtiens le fragment depuis une variable serveur,

View file

@ -67,6 +67,10 @@ class Vue
$renderer = new \Windwalker\Renderer\BladeRenderer($paths, array('cache_path' => VIEW_PATH . DIRECTORY_SEPARATOR . "cache"));
}
foreach ($page_params as $key => $value) {
$templateData[$key] = $value;
}
//WINWALKER TEMPLATING ENGINE RENDER
echo $renderer->render($name, $templateData);
} else {

View file

@ -0,0 +1,5 @@
<?php
/**
* Controlleur permettant d'afficher le recevoir un feedback
*/

View file

@ -0,0 +1,7 @@
<?php
use MVC\Classe\Logger;
$templateData = array("templating_a"=>'blade',"templating_b"=>'twig',"templating_c"=>'edge');
Logger::addLog('ok', 'Hello world');

View file

@ -0,0 +1,7 @@
<?php
use MVC\Classe\Logger;
$templateData = array("templating_a"=>'blade',"templating_b"=>'twig',"templating_c"=>'edge');
Logger::addLog('ok', 'Hello world');

View file

@ -0,0 +1,8 @@
name : feedback
page_title : Page de retour d'expérience utilisateur de l'application
description : UFC - Page de retour d'expérience utilisateur de l'application
engine : blade
authentification : no
ariane : {acceuil, retour d'expérience utilisateur}
arianelink : {index, feedback}

View file

@ -0,0 +1,8 @@
name : react
page_title : module_title
description : module_description
engine : blade
authentification : yes
ariane : {acceuil, react}
arianelink : {index, react}

View file

@ -0,0 +1,8 @@
name : vuejs
page_title : module_title
description : module_description
engine : blade
authentification : yes
ariane : {acceuil, vuejs}
arianelink : {index, vuejs}

View file

@ -14,6 +14,7 @@
<li @if($name == 'donate') class="actual" @endif ><a href="{{ \MVC\Classe\Url::link_rewrite( false, 'Donate', []) }}">Donate</a></li>
<li @if($name == 'cgu') class="actual" @endif ><a href="{{ \MVC\Classe\Url::link_rewrite( false, 'CGU', []) }}"> CGU Terms</a></li>
<li @if($name == 'policy') class="actual" @endif ><a href="{{ \MVC\Classe\Url::link_rewrite( false, 'Policy', []) }}">Policy</a></li>
<li @if($name == 'feedback') class="actual" @endif ><a href="{{ \MVC\Classe\Url::link_rewrite( false, 'Feedback', []) }}">Feedback</a></li>
</ul>
</div>
</div>
@ -52,12 +53,14 @@
</header>
<!-- end: Header -->
<!-- Subbar -->
@if($authentification == 'yes')
<div id="subbar" class="fullwidth">
<div class="container">
<span style="float:left;">Vous êtes connecté en tant que {{$_SESSION['user_login']}}</span>
<span style="float:right;"><a href="{{ \MVC\Classe\Url::link_rewrite( false, 'Logout', []) }}">Se Deconnecter</a></span>
</div>
</div>
@endif
<!-- end: Subbar -->
<!-- Breadcrumbs -->

View file

@ -0,0 +1,51 @@
@extends('body')
@section('content')
<div class="flex-center-container">
De 1 à 10, combien d'étoiles donneriez-vous pour le framework SAND?
<br/><br/><br/>
<div class="form-field box">
<form method="POST" action="{{\MVC\Classe\Url::link_rewrite(true,"send-feedback")}}">
<select name="glsr-custom-options" id="glsr-custom-options" class="star-rating" data-options='{"clearable":false, "tooltip":true}'>
<option value="">Select a rating</option>
<option value="10">10</option>
<option value="9">9</option>
<option value="8" selected>8</option>
<option value="7">7</option>
<option value="6">6</option>
<option value="5">5</option>
<option value="4">4</option>
<option value="3">3</option>
<option value="2">2</option>
<option value="1">1</option>
</select>
<br/>
<input type="submit" class="btn btn-outline-primary" value="Noter">
</form>
</div>
</div>
<center>
<br/><br/>
<br/><br/>
<br/><br/>
Un bug, une question ? envoyez-moi un courriel !
</center>
@endsection
@section('top-css')
@parent
<link rel="stylesheet" href="{{\MVC\Classe\Url::asset_rewrite('assets/star-rating.js-master/dist/star-rating.css')}}">
@endsection
@section('top-javascript')
@parent
<script src="{{\MVC\Classe\Url::asset_rewrite('assets/star-rating.js-master/dist/star-rating.js')}}"></script>
@endsection
@section('bottom-javascript')
@parent
<script>
var stars = new StarRating('.star-rating');
</script>
@endsection

View file

@ -0,0 +1,69 @@
@extends('body')
@section('top-javascript')
@parent
<script src="https://unpkg.com/react@^16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.13.0/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.js"></script>
@endsection
@section('content')
<h1>%PAGE% - REACT.js Controlleur</h1>
<br/><br/><br/>
<div id="root"></div>
@endsection
@section('bottom-javascript')
@parent
<script type="text/babel">
class App extends React.Component {
state = {
data: [],
}
// Code is invoked after the component is mounted/inserted into the DOM tree.
componentDidMount() {
const url =
'https://ghibliapi.herokuapp.com/films'
fetch(url)
.then((result) => result.json())
.then((result) => {
this.setState({
data: result,
})
})
}
render() {
const {data} = this.state
const result = data.map(obj => {
return (
<a href="#" key="{obj.id}">
<div>
<div>
<h2>
{obj.title}
</h2>
</div>
</div>
<div>
<p>
{obj.description.slice(0, 300) + "..." }
</p>
</div>
<div>
<span>Year : {obj.release_date }</span>
<span>Director : {obj.director }</span>
<span>Producer : {obj.producer }</span>
</div>
</a>);
})
return <div>{result}</div>
}
}
ReactDOM.render(<App />, document.getElementById('root'))
</script>
@endsection

View file

@ -0,0 +1,82 @@
@extends('body')
@section('top-javascript')
@parent
<script src="https://unpkg.com/vue@2.6.12/dist/vue.js"></script>
<script src="https://unpkg.com/axios@0.21.1/dist/axios.min.js"></script>
@endsection
@section('content')
<h1>%PAGE% - VUE.js Controlleur</h1>
<br/><br/><br/>
<div id="app">
<div>
<input v-model="searchText" placeholder="Search...">
</div>
<div v-if="is_loading">
<div class="lds-ellipsis"><div></div><div></div><div></div><div></div></div>
</div>
<div v-if="items" >
<a href="#" v-for="item in itemsSearched" :key="item.id">
<div>
<div>
<h2>
@{{ item.title }}
</h2>
</div>
</div>
<div>
<p>
@{{ item.description.slice(0, 300) + "..." }}
</p>
</div>
<div>
<span>Year : @{{ item.release_date }}</span>
<span>Director : @{{ item.director }}</span>
<span>Producer : @{{ item.producer }}</span>
</div>
</a>
</div>
</div>
@endsection
@section('bottom-javascript')
@parent
<script>
const vue = new Vue({
el: '#app',
data: {
items: [],
searchText: '',
is_loading: true,
},
mounted() {
axios
.get('https://ghibliapi.herokuapp.com/films')
.then(response => {
this.items = response.data;
this.is_loading = false
})
.catch(error => console.log(error))
},
computed : {
itemsSearched : function(){
var self = this;
if( this.searchText == ''){
return this.items;
}
return this.items.filter(function(item){
// https://www.reddit.com/r/vuejs/comments/62kfae/how_do_i_create_very_simple_instant_search_filter/
// Must be of string type
return item.title.toLowerCase().indexOf(self.searchText) >= 0 ||
item.producer.toLowerCase().indexOf(self.searchText) >= 0 ||
item.director.toLowerCase().indexOf(self.searchText) >= 0 ||
item.release_date.toString().indexOf(self.searchText) >= 0;
});
}
}
});
</script>
@endsection

View file

@ -0,0 +1,12 @@
<?php
/**
* Controlleur de traitement permettant de créér un individu sa période d'hébergement associé
*/
\MVC\Object\Session::createAndTestSession();
$note = $_POST['glsr-custom-options'];
header('location:'.\MVC\Classe\Url::link_rewrite(false, "donate", []));

View file

@ -0,0 +1,4 @@
.DS_Store
bower_components
node_modules
npm-debug.log

View file

@ -0,0 +1,110 @@
# language: javascript
filter:
excluded_paths:
- demo/*
- dist/*
- node_modules/
- gulpfile.js
checks:
javascript:
check_for_loops_test: true
check_loop_no_body: true
check_switch_ambiguous_test: true
check_switch_default_not_last: true
check_switch_default_only: true
check_switch_no_default: true
check_too_many_arguments: true
check_try_statement: true
check_undeclared_vars: true
check_unnecessary_continue: true
check_unnecessary_return: true
check_unused_member_calls: true
check_unused_object_creation: true
check_unused_parameters: true
code_rating: true
consistent_return: true
curly: false
duplicate_code: true
eqeqeq: true
guard_for_in: true
jsdoc_no_duplicate_params: true
jsdoc_non_existent_params: true
new_cap: true
no_alert: true
no_alias_builtins: true
no_array_constructor: true
no_bitwise: true
no_caller: true
no_comma_dangle: true
no_console: true
no_constant_condition: true
no_debugger: true
no_delete_var: true
no_dupe_keys: true
no_else_return: true
no_empty: true
no_empty_class: true
no_empty_label: true
no_eval: true
no_ex_assign: true
no_extend_native: true
no_extra_bind: true
no_func_assign: true
no_implicit_undefined_return: true
no_implied_eval: true
no_inner_declarations: true
no_invalid_regexp: true
no_label_var: true
no_loop_var_assign: true
no_native_reassign: true
no_negated_in_lhs: true
no_new_func: true
no_new_require: true
no_new_wrappers: true
no_param_assign: true
no_path_concat: true
no_process_exit: true
no_redeclare: true
no_redeclared_const: true
no_return_assign: true
no_sequences: true
no_shadow_builtins: true
no_sparse_arrays: true
no_undef: true
no_undef_init: true
no_unreachable: true
no_unused_assignment: true
no_unused_const: true
no_unused_expressions: true
no_unused_function: true
no_unused_vars: true
no_use_before_define: true
no_var: true
no_void: true
no_with: true
nsp_vulnerabilities: true
unsafe_mutable_variable_usage: true
unsafe_undefined: true
use_isnan: true
valid_typeof: true
var_never_initialized: true
var_sometimes_initialized: true
build:
nodes:
analysis:
environment:
elasticsearch: false
memcached: false
mongodb: false
neo4j: false
node: '9.3.0'
php: '7.2.0'
postgresql: false
rabbitmq: false
redis: false
tests:
override:
- js-scrutinizer-run

View file

@ -0,0 +1,8 @@
{
"language": "node_js",
"node_js": "stable",
"script": [
"npm run lint"
],
"sudo": false
}

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,368 @@
# 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-border-radius: 4px;
--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;
--gl-tooltip-size: 6px;
}
```
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.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,628 @@
body {
margin: 0 auto;
font-family: "Open Sans", sans-serif;
color: #212121;
}
h1, h2, h3, h4, h5, h6 {
font-family: "Open Sans", sans-serif;
letter-spacing: -0.03125em;
margin: 0 0 1.5rem;
color: #212121;
}
h1 {
font-size: 1.77687rem;
line-height: 1.5rem;
font-weight: 700;
}
h2 {
font-size: 1.60181rem;
line-height: 1.5rem;
font-weight: 700;
}
h3 {
font-size: 1.125rem;
line-height: 1.5rem;
font-weight: 700;
}
h4 {
font-size: 1rem;
line-height: 1.5rem;
font-weight: 700;
}
h5 {
font-size: 1.333rem;
line-height: 1.5rem;
font-weight: 700;
}
h6 {
font-size: 1.125rem;
line-height: 1.5rem;
font-weight: 700;
text-transform: uppercase;
}
p, ul, ol, pre, table, blockquote {
margin: 0 0 1.5rem;
}
p {
font-size: 1rem;
line-height: 1.5rem;
}
blockquote {
position: relative;
line-height: 2.25rem;
border-left: 4px solid #eeeeee;
padding: 0 1rem;
}
blockquote p {
margin: 0;
font-size: 1rem;
}
blockquote p:not(:last-child) {
margin-bottom: 0.75rem;
}
blockquote pre {
margin-bottom: 0;
}
blockquote code {
background-color: #eeeeee;
border-radius: 3px;
line-height: 1;
padding: 2px 4px;
text-shadow: 1px 1px 0 rgba(255, 255, 255, 0.75);
}
cite {
display: block;
}
small {
font-size: 0.88887rem;
line-height: 1;
}
ul ul,
ul ol,
ol ul,
ol ol {
margin: 0;
}
dd {
margin: 0 0 0.75rem;
}
dt {
font-weight: 700;
}
sub,
sup {
position: relative;
line-height: 0;
vertical-align: baseline;
font-size: 0.88887rem;
}
sub {
bottom: -0.1875rem;
}
sup {
top: -0.375rem;
}
abbr[title] {
border-bottom: .1em dotted currentColor;
cursor: help;
}
.button {
font-family: "Open Sans", sans-serif;
font-size: 1rem;
line-height: 1.375rem;
padding: 0.375rem 1.125rem;
margin-bottom: 1.5rem;
position: relative;
display: inline-block;
text-align: center;
text-decoration: none;
outline: none;
cursor: pointer;
border: 1px solid transparent;
border-radius: 3px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
vertical-align: top;
color: #fff;
background-color: #212121;
border-color: #212121;
}
.button:hover {
outline: none;
text-decoration: none;
}
.button[disabled], .button.disabled {
opacity: 0.7;
cursor: default;
}
.button i {
margin-right: 0.1875rem;
}
form {
font-family: "Open Sans", sans-serif;
letter-spacing: -0.03125em;
margin: 0 0 1.5rem;
}
fieldset {
position: relative;
display: block;
border: 0 solid currentColor;
padding: 0;
margin: 0;
}
legend {
position: absolute;
top: -24px;
left: -1px;
font-weight: 400;
padding: 0;
font-size: 0.88887rem;
}
label {
display: inline-block;
vertical-align: top;
cursor: pointer;
font-size: 0.88887rem;
line-height: 1.5rem;
padding: 0;
}
input,
select,
textarea {
display: block;
width: 100%;
outline: none;
background-color: transparent;
border: solid currentColor;
border-radius: 3px;
font-size: 0.88887rem;
line-height: 1.5rem;
padding: 0.375rem 0.75rem;
border-width: 1px;
margin-top: -0.0625rem;
margin-bottom: 1.4375rem;
}
input:-moz-placeholder,
select:-moz-placeholder,
textarea:-moz-placeholder {
color: rgba(0, 0, 0, 0.5);
}
input::-moz-placeholder,
select::-moz-placeholder,
textarea::-moz-placeholder {
color: rgba(0, 0, 0, 0.5);
}
input:-ms-input-placeholder,
select:-ms-input-placeholder,
textarea:-ms-input-placeholder {
color: rgba(0, 0, 0, 0.5);
}
input::-webkit-input-placeholder,
select::-webkit-input-placeholder,
textarea::-webkit-input-placeholder {
color: rgba(0, 0, 0, 0.5);
}
input:active, input:focus,
select:active,
select:focus,
textarea:active,
textarea:focus {
border-color: #212121;
}
select {
height: 2.375rem;
}
textarea {
height: 6.875rem;
}
input[type=date],
input[type=search],
input[type=number] {
box-sizing: border-box;
-webkit-appearance: textfield;
-moz-appearance: textfield;
appearance: textfield;
}
input[type=radio],
input[type=checkbox] {
display: inline-block;
position: relative;
top: -1px;
width: auto;
padding: 0;
margin: 0;
}
input[type=radio] + label,
input[type=checkbox] + label {
display: inline-block;
width: auto;
padding: 0;
line-height: 1.5rem;
margin: 0 1.5rem 1.5rem 0.1875rem;
}
/*************************************************/
a {
color: #e91e63;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.page-header {
padding: 3rem 1.5rem;
background-color: #fdd835;
text-align: center;
}
@media screen and (min-width: 42em) {
.page-header {
padding: 3rem 4.5rem;
}
}
@media screen and (min-width: 50em) {
.page-header {
padding: 6rem 4.5rem;
}
}
.page-header .button {
font-size: 1rem;
padding: 0.75rem 1.5rem;
margin: 1.125rem 0 0;
}
@media screen and (min-width: 35em) {
.page-header .button {
margin: 1.125rem 0.375rem 0;
}
}
.page-footer {
max-width: 52em;
margin: 0 auto;
padding: 1.5rem;
}
.page-footer p:first-child {
position: relative;
top: 1px;
font-weight: bold;
border-top: 1px solid #eeeeee;
margin: -2px 0 0;
padding-top: 1.5rem;
}
.main-content {
max-width: 52em;
margin: 0 auto;
padding: 2.25rem 1.5rem 0;
}
.section ul {
line-height: 1.875rem;
}
.section:not(:last-child) {
margin-bottom: 3rem;
}
.project-name {
color: #212121;
max-width: 52rem;
margin-left: auto;
margin-right: auto;
margin-bottom: 3rem;
}
@media screen and (min-width: 42em) {
.project-name {
font-size: 2.36856rem;
line-height: 3rem;
}
}
@media screen and (min-width: 50em) {
.project-name {
font-size: 3.15731rem;
}
}
.project-tagline {
max-width: 52rem;
margin-left: auto;
margin-right: auto;
color: #212121;
font-weight: 400;
font-size: 1rem;
margin-bottom: 1.5rem;
}
@media screen and (min-width: 42em) {
.project-tagline {
font-size: 1.333rem;
line-height: 3rem;
line-height: 2.25rem;
}
}
.badge {
display: inline-block;
line-height: 2.25rem;
margin-bottom: 1.5rem;
}
.badge img {
height: 1.5rem;
}
.button {
font-family: "Open Sans", sans-serif;
font-size: 1rem;
line-height: 1.375rem;
padding: 0.375rem 1.125rem;
margin-bottom: 1.5rem;
position: relative;
display: inline-block;
text-align: center;
text-decoration: none;
outline: none;
cursor: pointer;
border: 1px solid transparent;
border-radius: 3px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
vertical-align: top;
color: #fff;
background-color: #e91e63;
border-color: #e91e63;
-webkit-transition: color 0.125s ease-in-out, background-color 0.125s ease-in-out, border-color 0.125s ease-in-out;
transition: color 0.125s ease-in-out, background-color 0.125s ease-in-out, border-color 0.125s ease-in-out;
text-shadow: 0 -1px 0 rgba(173, 20, 87, 0.5);
}
.button:hover {
outline: none;
text-decoration: none;
}
.button[disabled], .button.disabled {
opacity: 0.7;
cursor: default;
}
.button-group {
display: inline-block;
vertical-align: bottom;
}
.button-group .button {
float: left;
z-index: 1;
}
.button-group .button:hover, .button-group .button:focus {
z-index: 2;
}
.button-group .button:active, .button-group .button.active {
z-index: 3;
}
.button-group .button:first-child {
border-radius: 3px 0 0 3px;
}
.button-group .button:last-child {
border-radius: 0 3px 3px 0;
}
.button-group .button:not(:first-child):not(:last-child) {
border-radius: 0;
}
.button-group .button:not(:first-child) {
margin-left: -1px;
}
.button:hover {
background-color: #c1134e;
border-color: #c1134e;
}
.button:active, .button.active {
background-color: #930e3b;
border-color: #930e3b;
}
.button[disabled], .button.disabled {
color: #fff;
background-color: #e91e63;
border-color: #e91e63;
}
.button:not([disabled]):not(.disabled):active {
-webkit-transform: translate3d(0, 1px, 0);
transform: translate3d(0, 1px, 0);
}
.button.secondary {
color: #fff;
background-color: #424242;
border-color: #424242;
text-shadow: 0 -1px 0 rgba(33, 33, 33, 0.5);
}
.button.secondary:hover {
background-color: #292929;
border-color: #292929;
}
.button.secondary:active, .button.secondary.active {
background-color: #0f0f0f;
border-color: #0f0f0f;
}
.button.secondary[disabled], .button.secondary.disabled {
color: #fff;
background-color: #424242;
border-color: #424242;
}
.button-group {
display: block;
margin-bottom: 1.5rem;
}
.button-group:after {
content: '';
display: table;
clear: both;
}
:not(.button-group) > .button {
width: 100%;
}
@media screen and (min-width: 35em) {
:not(.button-group) > .button {
width: auto;
}
}
form .button {
display: block;
margin: 0;
}
label {
font-size: 1rem;
font-weight: 600;
}
.form-field {
margin-bottom: 1.5rem;
border-radius: 3px;
background-color: #fafafa;
box-shadow: inset 0 0 8px #eeeeee;
padding: 0.75rem;
}
@media screen and (min-width: 42em) {
.form-field {
padding: 1.125rem;
}
}
select {
background-color: #fff;
border-color: #e0e0e0;
height: 34px;
margin: 0;
}
.button-group .button:not(:first-child) {
margin-left: 0;
}
/*!
* Star Rating
* @version: 4.0.0
* @author: Paul Ryley (http://geminilabs.io)
* @url: https://github.com/pryley/star-rating.js
* @license: MIT
*/
:root {
--gl-star-color: #fdd835;
--gl-star-size: 24px;
--gl-tooltip-color: #fff;
}
@media screen and (min-width: 35em) {
.gl-star-rating .gl-star-rating--stars > span {
--gl-star-size: 30px;
}
.gl-star-rating .gl-star-rating--stars[aria-label]::after {
--gl-tooltip-padding: .75em 1em;
}
}
@media screen and (min-width: 42em) {
.gl-star-rating .gl-star-rating--stars > span {
--gl-star-size: 36px;
}
}
@media screen and (min-width: 50em) {
.gl-star-rating .gl-star-rating--stars > span {
--gl-star-size: 42px;
}
}
.gl-star-rating .gl-emote-bg {
transition: fill 0.15s ease-in-out;
}
.gl-star-rating [data-value]:not(.gl-active) .gl-emote-bg {
fill: #DCDCE6;
}
.gl-star-rating .gl-emote {
transform: scale(.9);
transition: transform 0.25s ease-in-out;
}
.gl-star-rating .gl-selected .gl-emote {
transform: scale(1.1);
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,18 @@
var args = require('yargs').argv;
var bump = require('gulp-bump');
var gulp = require('gulp');
var pump = require('pump');
gulp.task('bump', function(cb) {
var type = 'patch';
['prerelease','patch','minor','major'].some(function(arg) {
if(!args[arg])return;
type = arg;
return true;
});
pump([
gulp.src(['index.html', 'package.json', 'src/index.js', 'src/index.css'], {base: '.'}),
bump({type: type, keys: ['version','ver=']}),
gulp.dest('.'),
], cb);
});

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1800 1800"><title>star-empty</title><path fill="#FFB900" d="M900 0l300 600 600 75-413 462 113 663-600-300-600 300 112-663L0 675l600-75L900 0zm0 224L666 693l-465 58 318 356-87 515 468-234 468 234-87-515 318-356-465-58-234-469z"/></svg>

After

Width:  |  Height:  |  Size: 287 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1800 1800"><title>star-full</title><path fill="#FFB900" d="M900 0L600 600 0 675l412 462-112 663 600-300 600 300-113-663 413-462-600-75z"/></svg>

After

Width:  |  Height:  |  Size: 198 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1800 1800"><title>star-half</title><path fill="#FFB900" d="M900 0L600 600 0 675l413 462-113 663 600-300 600 300-112-663 412-462-600-75L900 0zm0 224l234 469 465 58-318 356 87 515-468-234V224z"/></svg>

After

Width:  |  Height:  |  Size: 252 B

View file

@ -0,0 +1,292 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Star Rating by pryley</title>
<meta name="keywords" content="starrating star-rating star rating">
<meta name="description" content="A zero-dependency ES6 module that transforms a SELECT with numerical-range values (i.e. 1-5) into a dynamic star rating element.">
<meta name="author" content="Paul Ryley">
<link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Open+Sans:400,700' type='text/css'>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/modern-normalize/1.0.0/modern-normalize.min.css" integrity="sha512-ISS7cAi1PEhQ8jnbJpJZMd29NlhNj4AWYyLOSp2CE/CsHxTCu+r+t0D2yoJudVrd0/8fTVPUVDzY5Tvli75u/g==" crossorigin="anonymous" />
<link rel="stylesheet" href="dist/star-rating.css">
<link rel="stylesheet" href="demo/styles.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.23.0/themes/prism.min.css" integrity="sha512-tN7Ec6zAFaVSG3TpNAKtk4DOHNpSwKHxxrsiw4GHKESGPs5njn/0sMCUMl2svV4wo4BK/rCP7juYz+zx+l6oeQ==" crossorigin="anonymous" />
</head>
<body>
<header class="page-header">
<h1 class="project-name">star-rating.js</h1>
<h2 class="project-tagline">A zero-dependency ES6 module that transforms a SELECT with numerical-range values (i.e. 1-5) into a dynamic star rating element.</h2>
<a class="button medium" href="https://github.com/pryley/star-rating.js">View on GitHub</a>
<a class="button medium" href="https://github.com/pryley/star-rating.js/zipball/master">Download .zip</a>
<a class="button medium" href="https://github.com/pryley/star-rating.js/tarball/master">Download .tar.gz</a>
</header>
<main class="main-content">
<section class="section">
<a class="badge" href="https://badge.fury.io/js/star-rating.js"><img src="https://badge.fury.io/js/star-rating.js.svg" alt="npm version" height="18"></a>
<a class="badge" href="https://github.com/pryley/star-rating.js/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License" height="18"></a>
</section>
<section class="section" id="section-1">
<h2>Demo</h2>
<form class="form-1">
<label for="glsr-ltr" style="pointer-events: none;">Prebuilt</label>
<div class="form-field">
<span class="gl-star-rating">
<select id="glsr-prebuilt" class="star-rating-prebuilt">
<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" class=""><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="gl-emote" style="pointer-events: none;"><circle class="gl-emote-bg" fill="#FFA98D" cx="12" cy="12" r="10"></circle><path fill="#393939" d="M12 14.6c1.48 0 2.9.38 4.15 1.1a.8.8 0 01-.79 1.39 6.76 6.76 0 00-6.72 0 .8.8 0 11-.8-1.4A8.36 8.36 0 0112 14.6zm4.6-6.25a1.62 1.62 0 01.58 1.51 1.6 1.6 0 11-2.92-1.13c.2-.04.25-.05.45-.08.21-.04.76-.11 1.12-.18.37-.07.46-.08.77-.12zm-9.2 0c.31.04.4.05.77.12.36.07.9.14 1.12.18.2.03.24.04.45.08a1.6 1.6 0 11-2.34-.38z"></path></svg></span>
<span data-value="2" class=""><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="gl-emote" style="pointer-events: none;"><circle class="gl-emote-bg" fill="#FFC385" cx="12" cy="12" r="10"></circle><path fill="#393939" d="M12 14.8c1.48 0 3.08.28 3.97.75a.8.8 0 11-.74 1.41A8.28 8.28 0 0012 16.4a9.7 9.7 0 00-3.33.61.8.8 0 11-.54-1.5c1.35-.48 2.56-.71 3.87-.71zM15.7 8c.25.31.28.34.51.64.24.3.25.3.43.52.18.23.27.33.56.7A1.6 1.6 0 1115.7 8zM8.32 8a1.6 1.6 0 011.21 2.73 1.6 1.6 0 01-2.7-.87c.28-.37.37-.47.55-.7.18-.22.2-.23.43-.52.23-.3.26-.33.51-.64z"></path></svg></span>
<span data-value="3" class=""><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="gl-emote" style="pointer-events: none;"><circle class="gl-emote-bg" fill="#FFD885" cx="12" cy="12" r="10"></circle><path fill="#393939" d="M15.33 15.2a.8.8 0 01.7.66.85.85 0 01-.68.94h-6.2c-.24 0-.67-.26-.76-.7-.1-.38.17-.81.6-.9zm.35-7.2a1.6 1.6 0 011.5 1.86A1.6 1.6 0 1115.68 8zM8.32 8a1.6 1.6 0 011.21 2.73A1.6 1.6 0 118.33 8z"></path></svg></span>
<span data-value="4" class=""><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="gl-emote" style="pointer-events: none;"><circle class="gl-emote-bg" fill="#FFD885" cx="12" cy="12" r="10"></circle><path fill="#393939" d="M15.45 15.06a.8.8 0 11.8 1.38 8.36 8.36 0 01-8.5 0 .8.8 0 11.8-1.38 6.76 6.76 0 006.9 0zM15.68 8a1.6 1.6 0 011.5 1.86A1.6 1.6 0 1115.68 8zM8.32 8a1.6 1.6 0 011.21 2.73A1.6 1.6 0 118.33 8z"></path></svg></span>
<span data-value="5" class=""><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="gl-emote" style="pointer-events: none;"><circle class="gl-emote-bg" fill="#FFD885" cx="12" cy="12" r="10"></circle><path fill="#393939" d="M16.8 14.4c.32 0 .59.2.72.45.12.25.11.56-.08.82a6.78 6.78 0 01-10.88 0 .78.78 0 01-.05-.87c.14-.23.37-.4.7-.4zM15.67 8a1.6 1.6 0 011.5 1.86A1.6 1.6 0 1115.68 8zM8.32 8a1.6 1.6 0 011.21 2.73A1.6 1.6 0 118.33 8z"></path></svg></span>
</span>
</span>
</div>
<label for="glsr-ltr" style="pointer-events: none;">Left to Right</label>
<div class="form-field">
<select id="glsr-ltr" class="star-rating">
<option value="">Select a rating</option>
<option value="5">Fantastic</option>
<option value="4">Great</option>
<option value="3">Good</option>
<option value="2">Poor</option>
<option value="1">Terrible</option>
</select>
</div>
<label for="glsr-rtl" style="pointer-events: none;">Right to Left</label>
<div class="form-field" style="direction: rtl;">
<select id="glsr-rtl" class="star-rating-old">
<option value="">Select a rating</option>
<option value="5">Fantastic</option>
<option value="4">Great</option>
<option value="3">Good</option>
<option value="2">Poor</option>
<option value="1">Terrible</option>
</select>
</div>
<label for="glsr-custom-options"s tyle="pointer-events: none;">Using the "data-options" attribute to hide the tooltip and prevent the control from being cleared</label>
<div class="form-field">
<select id="glsr-custom-options" class="star-rating" data-options='{"clearable":false, "tooltip":false}'>
<option value="">Select a rating</option>
<option value="10">10</option>
<option value="9">9</option>
<option value="8" selected>8</option>
<option value="7">7</option>
<option value="6">6</option>
<option value="5">5</option>
<option value="4">4</option>
<option value="3">3</option>
<option value="2">2</option>
<option value="1">1</option>
</select>
</div>
<div class="button-group">
<button type="button" class="button large toggle-star-rating">Toggle Star Rating</button>
<button type="reset" class="button large secondary">Reset form</button>
</div>
</form>
</section>
<section class="section">
<h2>Installation</h2>
<p><pre><code class="language-bash">npm i star-rating.js</code></pre></p>
</section>
<section class="section">
<h2>Usage</h2>
<p>Your SELECT option fields must have numerical values.</p>
<p><pre><code class="language-html">&lt;link href="css/star-rating.css" rel="stylesheet"&gt;
&lt;select class="star-rating"&gt;
&lt;option value=""&gt;Select a rating&lt;/option&gt;
&lt;option value="5"&gt;Excellent&lt;/option&gt;
&lt;option value="4"&gt;Very Good&lt;/option&gt;
&lt;option value="3"&gt;Average&lt;/option&gt;
&lt;option value="2"&gt;Poor&lt;/option&gt;
&lt;option value="1"&gt;Terrible&lt;/option&gt;
&lt;/select&gt;
&lt;script src="js/star-rating.js"&gt;&lt;/script&gt;
&lt;script&gt;
var stars = new StarRating('.star-rating');
&lt;/script&gt;</code></pre></p>
<p>To rebuild all star rating controls (e.g. after form fields have changed with ajax):</p>
<p><pre><code class="language-js">stars.rebuild();</code></pre></p>
<p>To fully remove all star rating controls, including all attached Event Listeners:</p>
<p><pre><code class="language-js">stars.destroy();</code></pre></p>
</section>
<section class="section">
<h2>Options</h2>
<p>These are the default options:</p>
<p><pre><code class="language-js">{
classNames: {
active: "gl-active",
base: "gl-star-rating",
selected: "gl-selected",
},
clearable: true,
maxStars: 10,
stars: null,
tooltip: 'Select a Rating',
}</code></pre></p>
<h4>className.active</h4>
<blockquote>
<p>Type: <code>String</code></p>
<p>The classname to use for the active (hovered or value <= of the selected value) state of a star.</p>
</blockquote>
<h4>className.base</h4>
<blockquote>
<p>Type: <code>String</code></p>
<p>The classname to use for the base element that wraps the star rating.</p>
</blockquote>
<h4>className.selected</h4>
<blockquote>
<p>Type: <code>String</code></p>
<p>The classname to use for the selected state of a star.</p>
</blockquote>
<h4>clearable</h4>
<blockquote>
<p>Type: <code>Boolean</code></p>
<p>Whether or not the star rating can be cleared by clicking on an already selected star.</p>
</blockquote>
<h4>maxStars</h4>
<blockquote>
<p>Type: <code>Integer</code></p>
<p>The maximum number of stars allowed in a star rating.</p>
</blockquote>
<h4>prebuilt</h4>
<blockquote>
<p>Type: <code>Boolean</code></p>
<p>If this option is <code>true</code>, 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:</p>
<p><pre><code class="language-html">&lt;span class="gl-star-rating"&gt;
&lt;select&gt;
&lt;option value=""&gt;Select a rating&lt;/option&gt;
&lt;option value="5"&gt;5 Stars&lt;/option&gt;
&lt;option value="4"&gt;4 Stars&lt;/option&gt;
&lt;option value="3"&gt;3 Stars&lt;/option&gt;
&lt;option value="2"&gt;2 Stars&lt;/option&gt;
&lt;option value="1"&gt;1 Star&lt;/option&gt;
&lt;/select&gt;
&lt;span class="gl-star-rating--stars"&gt;
&lt;span data-value="1"&gt;&lt;/span&gt;
&lt;span data-value="2"&gt;&lt;/span&gt;
&lt;span data-value="3"&gt;&lt;/span&gt;
&lt;span data-value="4"&gt;&lt;/span&gt;
&lt;span data-value="5"&gt;&lt;/span&gt;
&lt;/span&gt;
&lt;/span&gt;
</code></pre></p>
</blockquote>
<h4>stars</h4>
<blockquote>
<p>Type: <code>Function</code></p>
<p>This can be used to add a SVG image to each star value instead of using the background images in the CSS.</p>
</blockquote>
<h4>tooltip</h4>
<blockquote>
<p>Type: <code>String|False</code></p>
<p>The placeholder text for the rating tooltip, or <code>false</code> to disable the tooltip.</p>
</blockquote>
</section>
<section class="section">
<h2>Build</h2>
<p><pre><code class="language-bash">npm run build</code></pre></p>
<p>The compiled files will be saved in the <code>dist/</code> folder.</p>
<p><strong>Note:</strong> If importing this into your project, you will need to add <a href="https://www.npmjs.com/package/@babel/plugin-proposal-optional-chaining">@babel/plugin-proposal-optional-chaining</a> to your babel config.</p>
<h3>Style Customization</h3>
<p>Following are the default CSS variable values for Star Rating:</p>
<pre><code class="language-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-border-radius: 4px;
--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;
--gl-tooltip-size: 6px;
}</code></pre>
<p>To override any values with your own, simply import the CSS file into your project, then enter new CSS variable values after the import.</p>
<p><pre><code class="language-css">@import 'star-rating';
:root {
--gl-star-size: 32px;
}</code></pre></p>
<h3>How to change CSS style priority</h3>
<p>Sometimes an existing stylesheet rules will override the default CSS styles for Star Ratings. To solve this problem, you can use the <a href="https://github.com/topaxi/postcss-selector-namespace">postcss-selector-namespace</a> 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.</p>
</section>
<section class="section">
<h2>Tips</h2>
<ol>
<li>If your star rating has a label field, add the <code class="language-css">pointer-events: none;</code> style to it to prevent the focus event from triggering on touch devices.</li>
</ol>
</section>
<section class="section">
<h2>Compatibility</h2>
<ul>
<li>All modern browsers</li>
</ul>
<p>If you need to use the Star Rating library in a unsupported browser (i.e. Internet Explorer), use the <a href="https://polyfill.io">Polyfill service</a>.
</section>
<section class="section">
<h2>License</h2>
<p><a href="https://github.com/pryley/star-rating.js/blob/master/LICENSE">MIT</a></p>
</section>
</main>
<footer class="page-footer">
<p>This project is maintained by <a href="https://github.com/pryley">Paul Ryley</a></p>
</footer>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.23.0/prism.min.js" integrity="sha512-YBk7HhgDZvBxmtOfUdvX0z8IH2d10Hp3aEygaMNhtF8fSOvBZ16D/1bXZTJV6ndk/L/DlXxYStP8jrF77v2MIg==" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.23.0/plugins/autoloader/prism-autoloader.min.js" integrity="sha512-zc7WDnCM3aom2EziyDIRAtQg1mVXLdILE09Bo+aE1xk0AM2c2cVLfSW9NrxE5tKTX44WBY0Z2HClZ05ur9vB6A==" crossorigin="anonymous"></script>
<script src="dist/star-rating.js?ver=4.1.4"></script>
<script>
var destroyed = false;
var starratingPrebuilt = new StarRating('.star-rating-prebuilt', {
prebuilt: true,
maxStars: 5,
});
var starrating = new StarRating('.star-rating', {
stars: function (el, item, index) {
el.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><rect class="gl-star-full" width="19" height="19" x="2.5" y="2.5"/><polygon fill="#FFF" points="12 5.375 13.646 10.417 19 10.417 14.665 13.556 16.313 18.625 11.995 15.476 7.688 18.583 9.333 13.542 5 10.417 10.354 10.417"/></svg>';
},
});
var starratingOld = new StarRating('.star-rating-old');
document.querySelector('.toggle-star-rating').addEventListener('click', function () {
if (!destroyed) {
starrating.destroy();
starratingOld.destroy();
starratingPrebuilt.destroy()
destroyed = true;
} else {
starrating.rebuild();
starratingOld.rebuild();
starratingPrebuilt.rebuild()
destroyed = false;
}
});
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,49 @@
{
"name": "star-rating.js",
"description": "This zero-dependency ES6 module transforms a SELECT into a dynamic star rating element.",
"version": "4.1.4",
"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-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 rollup -c",
"watch": "rollup -cw"
},
"main": "index.js",
"style": "src/index.css",
"dependencies": {
"detect-it": "^4.0.0"
}
}

View file

@ -0,0 +1,8 @@
module.exports = {
plugins: [
require('postcss-hexrgba'),
require('postcss-custom-properties'),
require('postcss-selector-namespace')({namespace: ''}), // add a custom namespace here
require('autoprefixer'),
],
};

View file

@ -0,0 +1,67 @@
import babel from '@rollup/plugin-babel';
import filesize from 'rollup-plugin-filesize';
import postcss from 'rollup-plugin-postcss'
import resolve from '@rollup/plugin-node-resolve';
import { terser } from "rollup-plugin-terser";
const production = process.env.NODE_ENV === 'production';
export default [
{
input: 'src/index.js',
output: {
file: 'index.js',
format: 'cjs',
},
plugins: [
resolve(),
filesize(),
babel({
babelHelpers: 'bundled',
presets: [
['@babel/preset-env', {
include: ['@babel/plugin-proposal-optional-chaining'],
}],
],
}),
terser(),
]
},
{
input: 'src/index.js',
output: {
name: 'StarRating',
file: 'dist/star-rating.js',
format: 'iife',
},
plugins: [
resolve(),
filesize(),
babel({
babelHelpers: 'bundled',
presets: [
['@babel/preset-env', {
include: ['@babel/plugin-proposal-optional-chaining'],
}],
],
}),
production && terser(),
]
},
{
input: 'src/index.css',
output: {
file: 'dist/star-rating.css',
},
plugins: [
filesize(),
postcss({
extract: true,
minimize: production,
plugins: [
// require('postcss-custom-properties')({preserve: false}),
],
}),
]
},
]

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,167 @@
/**
* Star Rating
* @version: 4.1.4
* @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-border-radius: 4px;
--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;
--gl-tooltip-size: 6px;
}
[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%;
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-size: 100% auto !important;
background: url(data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2212px%22%20height%3D%2236px%22%3E%3Cpath%20fill%3D%22rgba%2817,%2017,%2017,%200.9%29%22%20transform%3D%22rotate%2890%206%206%29%22%20d%3D%22M2.658,0.000%20C-13.615,0.000%2050.938,0.000%2034.662,0.000%20C28.662,0.000%2023.035,12.002%2018.660,12.002%20C14.285,12.002%208.594,0.000%202.658,0.000%20Z%22/%3E%3C/svg%3E) no-repeat;
content: '';
height: 18px;
margin-bottom: 0;
margin-left: var(--gl-tooltip-size);
width: var(--gl-tooltip-size);
}
[data-star-rating] .gl-star-rating--stars[aria-label]::after {
background: rgba(17,17,17,.9);
border-radius: var(--gl-tooltip-border-radius);
color: #fff;
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 {
background: url(data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2212px%22%20height%3D%2236px%22%3E%3Cpath%20fill%3D%22rgba%2817,%2017,%2017,%200.9%29%22%20transform%3D%22rotate%28-90%2018%2018%29%22%20d%3D%22M2.658,0.000%20C-13.615,0.000%2050.938,0.000%2034.662,0.000%20C28.662,0.000%2023.035,12.002%2018.660,12.002%20C14.285,12.002%208.594,0.000%202.658,0.000%20Z%22/%3E%3C/svg%3E) no-repeat;
margin-left: 0;
margin-right: var(--gl-tooltip-size);
}
[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 */
[data-star-rating] .gl-star-rating--stars[class*=" s"] > span {
background-image: var(--gl-star-empty);
background-position: center;
background-repeat: no-repeat;
background-size: 90%;
}
[data-star-rating] .gl-star-rating--stars.s10 > span:nth-child(1),
[data-star-rating] .gl-star-rating--stars.s20 > span:nth-child(-1n+2),
[data-star-rating] .gl-star-rating--stars.s30 > span:nth-child(-1n+3),
[data-star-rating] .gl-star-rating--stars.s40 > span:nth-child(-1n+4),
[data-star-rating] .gl-star-rating--stars.s50 > span:nth-child(-1n+5),
[data-star-rating] .gl-star-rating--stars.s60 > span:nth-child(-1n+6),
[data-star-rating] .gl-star-rating--stars.s70 > span:nth-child(-1n+7),
[data-star-rating] .gl-star-rating--stars.s80 > span:nth-child(-1n+8),
[data-star-rating] .gl-star-rating--stars.s90 > span:nth-child(-1n+9),
[data-star-rating] .gl-star-rating--stars.s100 > span {
background-image: var(--gl-star-full);
}

View file

@ -0,0 +1,62 @@
/**!
* Star Rating
* @version: 4.1.4
* @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.rebuild = this.rebuild.bind(this);
this.widgets = [];
this.buildWidgets(selector, props);
}
buildWidgets(selector, props) { // (HTMLSelectElement|NodeList|string, object):void
this.queryElements(selector).forEach(el => {
const options = merge(defaults, 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());
}
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.widgets.forEach(widget => widget.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,191 @@
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, widgetEl
if (this.props.prebuilt) {
parentEl = this.el.parentNode
widgetEl = parentEl.querySelector('.' + this.props.classNames.base + '--stars')
} else {
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);
});
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);
return [].slice.call(el.parentNode.children).indexOf(el);
}
onChange () { // ():void
this.changeIndexTo(this.selected(), true);
}
onKeyDown (ev) { // (KeyboardEvent):void
const key = ev.key.slice(5);
if (!~['Left', 'Right'].indexOf(key)) return;
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 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);
}
}