Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui5-rating-indicator): initial implementation #1729

Merged
merged 18 commits into from
Jun 8, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/base/src/UI5Element.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import getConstructableStyle from "./theming/getConstructableStyle.js";
import createComponentStyleTag from "./theming/createComponentStyleTag.js";
import getEffectiveStyle from "./theming/getEffectiveStyle.js";
import Integer from "./types/Integer.js";
import Float from "./types/Float.js";
import { kebabToCamelCase, camelToKebabCase } from "./util/StringHelper.js";
import isValidPropertyName from "./util/isValidPropertyName.js";
import isSlot from "./util/isSlot.js";
Expand Down Expand Up @@ -303,6 +304,9 @@ class UI5Element extends HTMLElement {
if (propertyTypeClass === Integer) {
newValue = parseInt(newValue);
}
if (propertyTypeClass === Float) {
newValue = parseFloat(newValue);
}
this[nameInCamelCase] = newValue;
}
}
Expand Down
10 changes: 10 additions & 0 deletions packages/base/src/types/Float.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import DataType from "./DataType.js";

class Float extends DataType {
static isValid(value) {
// Assuming that integers are floats as well!
return Number(value) === value;
}
}

export default Float;
1 change: 1 addition & 0 deletions packages/main/bundle.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import Select from "./dist/Select.js";
import Switch from "./dist/Switch.js";
import MessageStrip from "./dist/MessageStrip.js";
import MultiComboBox from "./dist/MultiComboBox.js";
import RatingIndicator from "./dist/RatingIndicator.js";
import TabContainer from "./dist/TabContainer.js";
import Tab from "./dist/Tab.js";
import TabSeparator from "./dist/TabSeparator.js";
Expand Down
9 changes: 6 additions & 3 deletions packages/main/src/BusyIndicator.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
import BusyIndicatorSize from "./types/BusyIndicatorSize.js";
import Size from "./types/Size.js";
import Label from "./Label.js";

// Template
Expand Down Expand Up @@ -48,11 +48,14 @@ const metadata = {
* <br><br>
* <b>Note:</b> Available options are "Small", "Medium", and "Large".
*
* @type {BusyIndicatorSize}
* @type {Size}
* @defaultvalue "Medium"
* @public
*/
size: { type: BusyIndicatorSize, defaultValue: BusyIndicatorSize.Medium },
size: {
type: Size,
defaultValue: Size.Medium,
},

/**
* Defines if the busy indicator is visible on the screen. By default it is not.
Expand Down
29 changes: 29 additions & 0 deletions packages/main/src/RatingIndicator.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<div class="ui5-rating-indicator-root"
role="slider"
aria-roledescription="{{_ariaRoleDescription}}"
aria-valuemin="0"
aria-valuenow="{{this.value}}"
aria-valuemax="{{this.maxValue}}"
aria-orientation="horizontal"
?aria-disabled="{{this.disabled}}"
?aria-readonly="{{this.readOnly}}"
tabindex="{{tabIndex}}"
@focusin="{{_onfocusin}}"
@focusout="{{_onfocusout}}"
@click="{{_onclick}}"
@keydown="{{_onkeydown}}"
>
<div
class="ui5-rating-indicator-stars-wrapper"
>
{{#each _stars}}
{{#if this.selected}}
<div class="ui5-rating-indicator-icon ui5-rating-indicator-active-icon" data-value="{{this.index}}">&#9733;</div>
{{else if this.halfStar}}
<div class="ui5-rating-indicator-icon ui5-rating-indicator-half-icon" data-value="{{this.index}}">&#9734;</div>
{{else}}
<div class="ui5-rating-indicator-icon" data-value="{{this.index}}">&#9734;</div>
{{/if}}
{{/each}}
</div>
</div>
270 changes: 270 additions & 0 deletions packages/main/src/RatingIndicator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
import {
isDown,
isUp,
isLeft,
isRight,
isSpace,
isEnter,
} from "@ui5/webcomponents-base/dist/Keys.js";
import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
import Integer from "@ui5/webcomponents-base/dist/types/Integer.js";
import Float from "@ui5/webcomponents-base/dist/types/Float.js";
import {
RATING_INDICATOR_TEXT,
} from "./generated/i18n/i18n-defaults.js";
import Size from "./types/Size.js";
import RatingIndicatorTemplate from "./generated/templates/RatingIndicatorTemplate.lit.js";

// Styles
import RatingIndicatorCss from "./generated/themes/RatingIndicator.css.js";

/**
* @public
*/
const metadata = {
tag: "ui5-rating-indicator",
properties: /** @lends sap.ui.webcomponents.main.RatingIndicator.prototype */ {

/**
* The indicated value of the rating
* <br><br>
* <b>Note:</b> If you set a number which is not round, it would be shown as follows:
* <ul>
* <li>1.0 - 1.2 -> 1</li>
* <li>1.3 - 1.7 -> 1.5</li>
* <li>1.8 - 1.9 -> 2</li>
* <ul>
* @type {Float}
* @defaultvalue 0
* @public
*/
value: {
type: Float,
defaultValue: 0,
},

/**
* The number of displayed rating symbols
* @type {Integer}
* @defaultvalue 5
* @public
*/
maxValue: {
type: Integer,
defaultValue: 5,
},

/**
* Defines the size of the <code>ui5-rating-indicator</code>.
* <br><br>
* <b>Note:</b> Available options are "Small", "Medium", and "Large".
*
* @type {Size}
* @defaultvalue "Medium"
* @public
*/
size: {
type: Size,
defaultValue: Size.Medium,
},

/**
* Defines whether the <code>ui5-rating-indicator</code> is disabled.
*/
/**
* @type {Boolean}
* @defaultvalue false
* @public
*/
disabled: {
type: Boolean,
},

/**
* @type {Boolean}
* @defaultvalue falase
* @public
*/
readOnly: {
type: Boolean,
},

/**
* @private
*/
_stars: {
type: Integer,
multiple: true,
},

_focused: {
type: Boolean,
},
},
slots: /** @lends sap.ui.webcomponents.main.RatingIndicator.prototype */ {
//
},
events: /** @lends sap.ui.webcomponents.main.RatingIndicator.prototype */ {

/**
* This event is triggered each time the rating value changes.
*
* @event
* @public
*/
input: {},

/**
* The event is fired on focuseout if the value has changed.
*
* @event
* @public
*/
change: {},
},
};

/**
* @class
*
* <h3 class="comment-api-title">Overview</h3>
*
*
* <h3>Usage</h3>
*
* For the <code>ui5-rating-indicator</code>
* <h3>ES6 Module Import</h3>
*
* <code>import @ui5/webcomponents/dist/RatingIndicator.js";</code>
*
* @constructor
* @author SAP SE
* @alias sap.ui.webcomponents.main.RatingIndicator
* @extends UI5Element
* @tagname ui5-rating-indicator
* @public
*/
class RatingIndicator extends UI5Element {
static get metadata() {
return metadata;
}

static get render() {
return litRender;
}

static get styles() {
return RatingIndicatorCss;
}

static get template() {
return RatingIndicatorTemplate;
}

static async onDefine() {
await Promise.all([
fetchI18nBundle("@ui5/webcomponents"),
]);
}

constructor() {
super();
this._prevValue = undefined;

this.i18nBundle = getI18nBundle("@ui5/webcomponents");
}

onBeforeRendering() {
this.calcState();
}

calcState() {
this._stars = [];

for (let i = 1; i < this.maxValue + 1; i++) {
const remainder = Math.round((this.value - Math.floor(this.value)) * 10);
let halfStar = false,
tempValue = this.value;

if (Math.floor(this.value) + 1 === i && remainder > 2 && remainder < 8) {
halfStar = true;
} else if (remainder <= 2) {
tempValue = Math.floor(this.value);
} else if (remainder >= 8) {
tempValue = Math.ceil(this.value);
}

this._stars.push({
selected: i <= tempValue,
index: i,
halfStar,
});
}
}

_onclick(event) {
if (this.disabled || this.readOnly) {
return;
}

this._prevValue = this.value;
this.value = parseInt(event.target.getAttribute("data-value"));
if (this.value === 1 && this._prevValue === 1) {
this.value = 0;
}

if (this._prevValue !== this.value) {
this.fireEvent("input");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure about the name of the event. Indeed for Input, Combo, etc we decided to use "input" instead of "liveChange", but for the RatingIndicator, the user does not type anything. Maybe here live-change is a little better.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we fire value-changed for Angular here and in the rest of the places you fire input,change?

}
}

_onkeydown(event) {
if (this.disabled || this.readOnly) {
return;
}

const down = isDown(event) || isLeft(event);
const up = isRight(event) || isUp(event) || isSpace(event) || isEnter(event);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With Space and Enter, after the values reaches the maxValue (all starts are active) it starts over, it is cycling behaviour basically. This is how in openui5 works at least.

With Up/Left and Down/Right it is like currently works.


if (down || up) {
event.preventDefault();

if (down && this.value > 0) {
this.value = Math.round(this.value - 1);
this.fireEvent("input");
} else if (up && this.value < this.maxValue) {
this.value = Math.round(this.value + 1);
this.fireEvent("input");
}
}
}

_onfocusin(event) {
if (!(this.disabled)) {
this._focused = true;
this._prevValue = this.value;
}
}

_onfocusout(event) {
if (this._focused && !this.disabled && !this.readOnly && this._prevValue !== this.value) {
this.fireEvent("change");
}

this._focused = false;
}

get tabIndex() {
return this.disabled ? "-1" : "0";
}

get _ariaRoleDescription() {
return this.i18nBundle.getText(RATING_INDICATOR_TEXT);
}
}

RatingIndicator.define();

export default RatingIndicator;
3 changes: 3 additions & 0 deletions packages/main/src/i18n/messagebundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,9 @@ MULTIINPUT_SHOW_MORE_TOKENS={0} More
#XTOL: Tooltip for panel expand title
PANEL_ICON=Expand/Collapse

#XBUT: Rating indicator aria-roledescription text
RATING_INDICATOR_TEXT=Rating Indicator

#XACT: ARIA description for the segmented button
SEGMENTEDBUTTON_ARIA_DESCRIPTION=Segmented button

Expand Down
Loading