@kubex/zinc 1.1.77 → 1.1.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/custom-elements.json +96 -2
- package/dist/vscode.html-custom-data.json +11 -1
- package/dist/web-types.json +28 -3
- package/dist/zn.d.ts +13 -0
- package/dist/zn.min.js +122 -100
- package/docs/pages/components/rating.md +61 -1
- package/package.json +1 -1
- package/src/components/rating/rating.component.ts +122 -56
- package/src/components/rating/rating.scss +30 -9
|
@@ -74,6 +74,29 @@ Use the `disabled` attribute to disable the rating component.
|
|
|
74
74
|
<zn-rating value="3" disabled label="Rating disabled with value"></zn-rating>
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
### Help Text
|
|
78
|
+
|
|
79
|
+
Add descriptive help text to a rating with the `help-text` attribute. For help text that contains HTML, use the
|
|
80
|
+
`help-text` slot instead.
|
|
81
|
+
|
|
82
|
+
```html:preview
|
|
83
|
+
<zn-rating label="Rate this product" help-text="Select a star to rate this product."></zn-rating>
|
|
84
|
+
<br />
|
|
85
|
+
<zn-rating value="3" precision="0.5" label="Overall rating">
|
|
86
|
+
<div slot="help-text">Ratings are <strong>public</strong> and can be changed at any time.</div>
|
|
87
|
+
</zn-rating>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Help text is styled to match [Input](/components/input) and follows the `size` attribute.
|
|
91
|
+
|
|
92
|
+
```html:preview
|
|
93
|
+
<zn-rating size="small" value="3" help-text="Small help text"></zn-rating>
|
|
94
|
+
<br />
|
|
95
|
+
<zn-rating size="medium" value="3" help-text="Medium help text"></zn-rating>
|
|
96
|
+
<br />
|
|
97
|
+
<zn-rating size="large" value="3" help-text="Large help text"></zn-rating>
|
|
98
|
+
```
|
|
99
|
+
|
|
77
100
|
### Sizes
|
|
78
101
|
|
|
79
102
|
Use the `size` attribute to change the rating size. Available sizes are `small`, `medium` (default), and `large`.
|
|
@@ -116,6 +139,40 @@ The rating component displays hover effects by default, scaling the hovered star
|
|
|
116
139
|
**Usage:** Hover effects provide visual feedback to help users understand which rating they will select. The hover effect is automatically disabled for `readonly` and `disabled` ratings.
|
|
117
140
|
:::
|
|
118
141
|
|
|
142
|
+
### Preview
|
|
143
|
+
|
|
144
|
+
Use the `preview` attribute to show the value next to the symbols. While the pointer moves across the symbols the
|
|
145
|
+
preview follows it, so the value being selected is visible before it is committed. Once the pointer leaves, the preview
|
|
146
|
+
falls back to the selected value.
|
|
147
|
+
|
|
148
|
+
```html:preview
|
|
149
|
+
<zn-rating preview label="Rate this product"></zn-rating>
|
|
150
|
+
<br />
|
|
151
|
+
<zn-rating preview value="2.5" precision="0.5" label="Half star precision"></zn-rating>
|
|
152
|
+
<br />
|
|
153
|
+
<zn-rating preview value="3.75" precision="0.25" max="10" size="large" label="Quarter star precision"></zn-rating>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The preview is formatted to match `precision`, so a rating with `precision="0.5"` previews `4.5` rather than `4`.
|
|
157
|
+
It also works with `readonly` and `disabled` ratings, where it simply displays the current value.
|
|
158
|
+
|
|
159
|
+
```html:preview
|
|
160
|
+
<zn-rating preview value="4.5" precision="0.5" readonly label="Average rating"></zn-rating>
|
|
161
|
+
<br />
|
|
162
|
+
<zn-rating preview value="3" disabled label="Rating disabled"></zn-rating>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Style the preview with the `--preview-color` and `--preview-size` custom properties, or the `preview` part.
|
|
166
|
+
|
|
167
|
+
```html:preview
|
|
168
|
+
<zn-rating
|
|
169
|
+
preview
|
|
170
|
+
value="4"
|
|
171
|
+
label="Custom preview styling"
|
|
172
|
+
style="--preview-color: #3B82F6; --preview-size: 1.25rem;"
|
|
173
|
+
></zn-rating>
|
|
174
|
+
```
|
|
175
|
+
|
|
119
176
|
### Form Integration
|
|
120
177
|
|
|
121
178
|
Ratings work seamlessly with forms and will be submitted with form data using the `name` attribute.
|
|
@@ -271,7 +328,10 @@ Available CSS custom properties:
|
|
|
271
328
|
- `--symbol-color` - Color of unfilled symbols
|
|
272
329
|
- `--symbol-color-active` - Color of filled/active symbols
|
|
273
330
|
- `--symbol-size` - Size of the symbols
|
|
274
|
-
- `--symbol-spacing` -
|
|
331
|
+
- `--symbol-spacing` - Gap between symbols. The rating adds no padding of its own, so the first symbol sits flush with
|
|
332
|
+
the component's edge and lines up with other form controls.
|
|
333
|
+
- `--preview-color` - Color of the preview value (see [Preview](#preview))
|
|
334
|
+
- `--preview-size` - Font size of the preview value
|
|
275
335
|
|
|
276
336
|
### Complete Example
|
|
277
337
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {classMap} from "lit/directives/class-map.js";
|
|
2
2
|
import {type CSSResultGroup, html, unsafeCSS} from 'lit';
|
|
3
3
|
import {FormControlController, validValidityState} from "../../internal/form";
|
|
4
|
+
import {HasSlotController} from "../../internal/slot";
|
|
4
5
|
import {property, query, state} from 'lit/decorators.js';
|
|
5
6
|
import {styleMap} from 'lit/directives/style-map.js';
|
|
6
7
|
import {unsafeHTML} from "lit/directives/unsafe-html.js";
|
|
@@ -22,10 +23,16 @@ import styles from './rating.scss';
|
|
|
22
23
|
*
|
|
23
24
|
* @slot - The default slot.
|
|
24
25
|
* @slot example - An example slot.
|
|
26
|
+
* @slot help-text - Text that describes how to use the rating. Alternatively, you can use the `help-text` attribute.
|
|
25
27
|
*
|
|
28
|
+
* @csspart form-control - The form control that wraps the symbols and help text.
|
|
29
|
+
* @csspart form-control-help-text - The help text's wrapper.
|
|
26
30
|
* @csspart base - The component's base wrapper.
|
|
31
|
+
* @csspart preview - The value shown next to the symbols when `preview` is enabled.
|
|
27
32
|
*
|
|
28
33
|
* @cssproperty --example - An example CSS custom property.
|
|
34
|
+
* @cssproperty --preview-color - The color of the preview value.
|
|
35
|
+
* @cssproperty --preview-size - The font size of the preview value.
|
|
29
36
|
*/
|
|
30
37
|
export default class ZnRating extends ZincElement implements ZincFormControl {
|
|
31
38
|
static styles: CSSResultGroup = unsafeCSS(styles);
|
|
@@ -34,14 +41,21 @@ export default class ZnRating extends ZincElement implements ZincFormControl {
|
|
|
34
41
|
assumeInteractionOn: ['zn-blur', 'zn-input']
|
|
35
42
|
});
|
|
36
43
|
|
|
44
|
+
private readonly hasSlotController = new HasSlotController(this, 'help-text');
|
|
45
|
+
|
|
37
46
|
@query('.rating') rating: HTMLElement;
|
|
38
47
|
|
|
48
|
+
@query('.rating__symbols') symbols: HTMLElement;
|
|
49
|
+
|
|
39
50
|
@state() private hoverValue: number = 0;
|
|
40
51
|
|
|
41
52
|
@state() private isHovering: boolean = false;
|
|
42
53
|
|
|
43
54
|
@property() label: string;
|
|
44
55
|
|
|
56
|
+
/** The rating's help text. If you need to display HTML, use the `help-text` slot instead. */
|
|
57
|
+
@property({attribute: 'help-text'}) helpText: string = '';
|
|
58
|
+
|
|
45
59
|
@property() name: string;
|
|
46
60
|
|
|
47
61
|
@property({type: Number}) value: number = 0;
|
|
@@ -54,6 +68,9 @@ export default class ZnRating extends ZincElement implements ZincFormControl {
|
|
|
54
68
|
|
|
55
69
|
@property({type: Boolean}) disabled: boolean = false;
|
|
56
70
|
|
|
71
|
+
/** Displays the value alongside the symbols, updating live as the pointer moves across them. */
|
|
72
|
+
@property({type: Boolean}) preview: boolean = false;
|
|
73
|
+
|
|
57
74
|
@property({}) size: 'small' | 'medium' | 'large' = 'medium';
|
|
58
75
|
|
|
59
76
|
@property() getSymbol: (value: number) => string = () => '<zn-icon src="star" library="material"></zn-icon>';
|
|
@@ -94,12 +111,26 @@ export default class ZnRating extends ZincElement implements ZincFormControl {
|
|
|
94
111
|
}
|
|
95
112
|
|
|
96
113
|
private _getValueFromXCoordinate(coordinate: number): number {
|
|
97
|
-
const {left, width} = this.
|
|
98
|
-
|
|
114
|
+
const {left, width} = this.symbols.getBoundingClientRect();
|
|
115
|
+
// Symbols are spaced with a gap rather than padding, so the symbols only cover part of the
|
|
116
|
+
// container and a plain width-to-value ratio would skew fractions within each symbol.
|
|
117
|
+
const gap = parseFloat(getComputedStyle(this.symbols).columnGap) || 0;
|
|
118
|
+
const symbolWidth = (width - gap * (this.max - 1)) / this.max;
|
|
119
|
+
const stride = symbolWidth + gap;
|
|
120
|
+
const position = Math.min(Math.max(coordinate - left, 0), width);
|
|
121
|
+
const index = Math.min(Math.floor(position / stride), this.max - 1);
|
|
122
|
+
// Landing in a gap reads as the preceding symbol being complete.
|
|
123
|
+
const fraction = Math.min((position - index * stride) / symbolWidth, 1);
|
|
124
|
+
const value = this._roundToPrecision(index + fraction, this.precision);
|
|
99
125
|
|
|
100
126
|
return Math.min(Math.max(value, 0), this.max);
|
|
101
127
|
}
|
|
102
128
|
|
|
129
|
+
private _formatValue(value: number): string {
|
|
130
|
+
const decimals = String(this.precision).split('.')[1]?.length ?? 0;
|
|
131
|
+
return value.toFixed(decimals);
|
|
132
|
+
}
|
|
133
|
+
|
|
103
134
|
private _getValueFromMousePosition(event: MouseEvent): number {
|
|
104
135
|
return this._getValueFromXCoordinate(event.clientX);
|
|
105
136
|
}
|
|
@@ -150,13 +181,15 @@ export default class ZnRating extends ZincElement implements ZincFormControl {
|
|
|
150
181
|
|
|
151
182
|
private _handleTouchEnd(event: TouchEvent) {
|
|
152
183
|
this.isHovering = false;
|
|
153
|
-
|
|
184
|
+
// `touches` is empty once the finger lifts, so the final position lives in `changedTouches`.
|
|
185
|
+
this._setValue(this._getValueFromXCoordinate(event.changedTouches[0].clientX));
|
|
154
186
|
|
|
155
187
|
event.preventDefault();
|
|
156
188
|
}
|
|
157
189
|
|
|
158
190
|
render() {
|
|
159
191
|
const counter = Array.from(Array(this.max).keys());
|
|
192
|
+
const hasHelpText = this.helpText ? true : this.hasSlotController.test('help-text');
|
|
160
193
|
let displayValue = 0;
|
|
161
194
|
|
|
162
195
|
if (this.disabled || this.readonly) {
|
|
@@ -167,69 +200,102 @@ export default class ZnRating extends ZincElement implements ZincFormControl {
|
|
|
167
200
|
|
|
168
201
|
return html`
|
|
169
202
|
<div
|
|
170
|
-
part="
|
|
203
|
+
part="form-control"
|
|
171
204
|
class="${classMap({
|
|
172
|
-
|
|
173
|
-
'
|
|
174
|
-
'
|
|
175
|
-
'
|
|
176
|
-
'
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
${
|
|
196
|
-
|
|
205
|
+
'form-control': true,
|
|
206
|
+
'form-control--small': this.size === 'small',
|
|
207
|
+
'form-control--medium': this.size === 'medium',
|
|
208
|
+
'form-control--large': this.size === 'large',
|
|
209
|
+
'form-control--has-help-text': hasHelpText
|
|
210
|
+
})}">
|
|
211
|
+
<div
|
|
212
|
+
part="base"
|
|
213
|
+
class="${classMap({
|
|
214
|
+
rating: true,
|
|
215
|
+
'rating--readonly': this.readonly,
|
|
216
|
+
'rating--disabled': this.disabled,
|
|
217
|
+
'rating--small': this.size === 'small',
|
|
218
|
+
'rating--medium': this.size === 'medium',
|
|
219
|
+
'rating--large': this.size === 'large'
|
|
220
|
+
})}"
|
|
221
|
+
role="slider"
|
|
222
|
+
aria-label="${this.label}"
|
|
223
|
+
aria-describedby="help-text"
|
|
224
|
+
aria-disabled="${this.disabled ? 'true' : 'false'}"
|
|
225
|
+
aria-readonly="${this.readonly ? 'true' : 'false'}"
|
|
226
|
+
aria-valuenow="${this.value}"
|
|
227
|
+
aria-valuemin="0"
|
|
228
|
+
aria-valuemax="${this.max}"
|
|
229
|
+
>
|
|
230
|
+
<span
|
|
231
|
+
class="rating__symbols"
|
|
232
|
+
@click="${this._handleClick}"
|
|
233
|
+
@mouseenter="${this._handleMouseEnter}"
|
|
234
|
+
@mousemove="${this._handleMouseMove}"
|
|
235
|
+
@mouseleave="${this._handleMouseLeave}"
|
|
236
|
+
@touchstart="${this._handleTouchStart}"
|
|
237
|
+
@touchmove="${this._handleTouchMove}"
|
|
238
|
+
@touchend="${this._handleTouchEnd}"
|
|
239
|
+
>
|
|
240
|
+
${counter.map(index => {
|
|
241
|
+
if (displayValue > index && displayValue < index + 1) {
|
|
242
|
+
return html`
|
|
243
|
+
<span
|
|
244
|
+
class=${classMap({
|
|
245
|
+
rating__symbol: true,
|
|
246
|
+
'rating__partial-symbol-container': true,
|
|
247
|
+
'rating__symbol--hover': this.isHovering && Math.ceil(displayValue) === index + 1
|
|
248
|
+
})}
|
|
249
|
+
role="presentation">
|
|
250
|
+
<div
|
|
251
|
+
style=${styleMap({
|
|
252
|
+
clipPath: `inset(0 0 0 ${(displayValue - index) * 100}%)`
|
|
253
|
+
})}>
|
|
254
|
+
${unsafeHTML(this.getSymbol(index + 1))}
|
|
255
|
+
</div>
|
|
256
|
+
<div
|
|
257
|
+
class="rating__partial--filled"
|
|
258
|
+
style=${styleMap({
|
|
259
|
+
clipPath: `inset(0 ${100 - (displayValue - index) * 100}% 0 0)`
|
|
260
|
+
})}>
|
|
261
|
+
${unsafeHTML(this.getSymbol(index + 1))}
|
|
262
|
+
</div>
|
|
263
|
+
</span>
|
|
264
|
+
`;
|
|
265
|
+
}
|
|
197
266
|
return html`
|
|
198
267
|
<span
|
|
199
268
|
class=${classMap({
|
|
200
269
|
rating__symbol: true,
|
|
201
|
-
'
|
|
202
|
-
'rating__symbol--
|
|
270
|
+
'rating__symbol--hover': this.isHovering && Math.ceil(displayValue) === index + 1,
|
|
271
|
+
'rating__symbol--active': displayValue >= index + 1
|
|
203
272
|
})}
|
|
204
273
|
role="presentation">
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
<div
|
|
212
|
-
class="rating__partial--filled"
|
|
213
|
-
style=${styleMap({
|
|
214
|
-
clipPath: `inset(0 ${100 - (displayValue - index) * 100}% 0 0)`
|
|
215
|
-
})}>
|
|
216
|
-
${unsafeHTML(this.getSymbol(index + 1))}
|
|
217
|
-
</div>
|
|
218
|
-
</span>
|
|
219
|
-
`;
|
|
220
|
-
}
|
|
221
|
-
return html`
|
|
274
|
+
${unsafeHTML(this.getSymbol(index + 1))}
|
|
275
|
+
</span>`;
|
|
276
|
+
})}
|
|
277
|
+
</span>
|
|
278
|
+
${this.preview
|
|
279
|
+
? html`
|
|
222
280
|
<span
|
|
281
|
+
part="preview"
|
|
223
282
|
class=${classMap({
|
|
224
|
-
|
|
225
|
-
'
|
|
226
|
-
'rating__symbol--active': displayValue >= index + 1
|
|
283
|
+
rating__preview: true,
|
|
284
|
+
'rating__preview--hover': this.isHovering
|
|
227
285
|
})}
|
|
228
|
-
|
|
229
|
-
${
|
|
230
|
-
</span
|
|
231
|
-
|
|
232
|
-
</
|
|
286
|
+
aria-hidden="true">
|
|
287
|
+
${this._formatValue(displayValue)}
|
|
288
|
+
</span>`
|
|
289
|
+
: ''}
|
|
290
|
+
</div>
|
|
291
|
+
|
|
292
|
+
<div
|
|
293
|
+
part="form-control-help-text"
|
|
294
|
+
id="help-text"
|
|
295
|
+
class="form-control__help-text"
|
|
296
|
+
aria-hidden=${hasHelpText ? 'false' : 'true'}>
|
|
297
|
+
<slot name="help-text">${this.helpText}</slot>
|
|
298
|
+
</div>
|
|
233
299
|
</div>
|
|
234
300
|
`;
|
|
235
301
|
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
@use "../../wc";
|
|
2
|
+
@use "../../form-control";
|
|
2
3
|
|
|
3
4
|
:host {
|
|
4
5
|
--symbol-color: rgb(var(--zn-border-color));
|
|
5
6
|
--symbol-color-active: var(--zn-color-amber-400);
|
|
6
7
|
--symbol-size: 1.2rem;
|
|
7
|
-
--symbol-spacing:
|
|
8
|
+
--symbol-spacing: var(--zn-spacing-x-small);
|
|
9
|
+
--preview-color: rgb(var(--zn-text));
|
|
10
|
+
--preview-size: 0.875rem;
|
|
8
11
|
|
|
9
12
|
display: block;
|
|
10
13
|
}
|
|
@@ -12,6 +15,7 @@
|
|
|
12
15
|
.rating {
|
|
13
16
|
position: relative;
|
|
14
17
|
display: inline-flex;
|
|
18
|
+
align-items: center;
|
|
15
19
|
border-radius: var(--zn-border-radius);
|
|
16
20
|
vertical-align: middle;
|
|
17
21
|
|
|
@@ -27,15 +31,12 @@
|
|
|
27
31
|
&__symbols {
|
|
28
32
|
display: inline-flex;
|
|
29
33
|
position: relative;
|
|
34
|
+
gap: var(--symbol-spacing);
|
|
30
35
|
font-size: var(--symbol-size);
|
|
31
36
|
line-height: 0;
|
|
32
37
|
color: var(--symbol-color);
|
|
33
38
|
white-space: nowrap;
|
|
34
39
|
cursor: pointer;
|
|
35
|
-
|
|
36
|
-
> * {
|
|
37
|
-
padding: var(--symbol-spacing);
|
|
38
|
-
}
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
&__symbol--active,
|
|
@@ -49,8 +50,8 @@
|
|
|
49
50
|
|
|
50
51
|
&__partial--filled {
|
|
51
52
|
position: absolute;
|
|
52
|
-
top:
|
|
53
|
-
left:
|
|
53
|
+
top: 0;
|
|
54
|
+
left: 0;
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
&__symbol {
|
|
@@ -62,6 +63,21 @@
|
|
|
62
63
|
}
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
&__preview {
|
|
67
|
+
margin-left: var(--symbol-spacing);
|
|
68
|
+
font-size: var(--preview-size);
|
|
69
|
+
font-weight: var(--zn-font-weight-semibold);
|
|
70
|
+
line-height: 1;
|
|
71
|
+
color: var(--preview-color);
|
|
72
|
+
font-variant-numeric: tabular-nums;
|
|
73
|
+
pointer-events: none;
|
|
74
|
+
user-select: none;
|
|
75
|
+
|
|
76
|
+
&--hover {
|
|
77
|
+
color: var(--symbol-color-active);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
65
81
|
&--disabled &__symbols,
|
|
66
82
|
&--readonly &__symbols {
|
|
67
83
|
cursor: default;
|
|
@@ -72,6 +88,11 @@
|
|
|
72
88
|
scale: none;
|
|
73
89
|
}
|
|
74
90
|
|
|
91
|
+
&--disabled &__preview--hover,
|
|
92
|
+
&--readonly &__preview--hover {
|
|
93
|
+
color: var(--preview-color);
|
|
94
|
+
}
|
|
95
|
+
|
|
75
96
|
&--disabled {
|
|
76
97
|
opacity: 0.5;
|
|
77
98
|
}
|
|
@@ -82,7 +103,7 @@
|
|
|
82
103
|
|
|
83
104
|
&--small {
|
|
84
105
|
--symbol-size: 1rem;
|
|
85
|
-
--symbol-spacing:
|
|
106
|
+
--symbol-spacing: var(--zn-spacing-2x-small);
|
|
86
107
|
}
|
|
87
108
|
|
|
88
109
|
&--medium {
|
|
@@ -91,6 +112,6 @@
|
|
|
91
112
|
|
|
92
113
|
&--large {
|
|
93
114
|
--symbol-size: 1.5rem;
|
|
94
|
-
--symbol-spacing:
|
|
115
|
+
--symbol-spacing: 12px;
|
|
95
116
|
}
|
|
96
117
|
}
|