@kubex/zinc 1.1.76 → 1.1.78
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 +1049 -955
- package/dist/vscode.html-custom-data.json +53 -43
- package/dist/web-types.json +156 -131
- package/dist/zn.d.ts +21 -1
- package/dist/zn.min.js +129 -104
- package/docs/pages/components/preview-frame.md +14 -6
- package/docs/pages/components/rating.md +59 -0
- package/docs/pages/components/theme-editor.md +4 -0
- package/package.json +1 -1
- package/src/components/preview-frame/preview-frame.component.ts +12 -2
- package/src/components/preview-frame/preview-frame.test.ts +38 -0
- package/src/components/rating/rating.component.ts +112 -55
- package/src/components/rating/rating.scss +24 -0
- package/src/components/theme-editor/theme-editor.component.ts +7 -4
- package/src/components/theme-editor/theme-editor.scss +4 -2
- package/src/components/theme-editor/theme-editor.test.ts +16 -0
|
@@ -56,7 +56,13 @@ handlers can't be cancelled from out here — blocking pointer input is the only
|
|
|
56
56
|
way to stop them, and hover goes with it. Scrolling doesn't: an overflowing page
|
|
57
57
|
is scrolled by the panel instead, as below.
|
|
58
58
|
|
|
59
|
-
Set `interactive` when the embed is meant to be used rather than looked at.
|
|
59
|
+
Set `interactive` when the embed is meant to be used rather than looked at. The
|
|
60
|
+
frame then behaves as a viewport: it stays the panel's own height and the embed
|
|
61
|
+
scrolls itself, so there's a single scrollbar and the embed's `100vh`,
|
|
62
|
+
`position: fixed` and sticky content size to what's actually on screen. The
|
|
63
|
+
[reported content height](#overflowing-content) is ignored while `interactive`
|
|
64
|
+
is set — it exists to make an *inert* frame's overflow reachable, which an
|
|
65
|
+
interactive one does for itself.
|
|
60
66
|
|
|
61
67
|
```html:preview
|
|
62
68
|
<zn-preview-frame
|
|
@@ -73,11 +79,13 @@ Set `interactive` when the embed is meant to be used rather than looked at.
|
|
|
73
79
|
|
|
74
80
|
## Overflowing Content
|
|
75
81
|
|
|
76
|
-
|
|
77
|
-
frame can't do it itself: a cross-origin document can't be scrolled
|
|
78
|
-
(`contentWindow.scrollTo` is blocked), and with pointer input off
|
|
79
|
-
reaches it anyway. So the frame is instead laid out at its full
|
|
80
|
-
nothing scrolls inside it — and the panel scrolls that.
|
|
82
|
+
An inert page taller than the panel is scrolled by the panel, not inside the
|
|
83
|
+
frame. The frame can't do it itself: a cross-origin document can't be scrolled
|
|
84
|
+
from the host (`contentWindow.scrollTo` is blocked), and with pointer input off
|
|
85
|
+
the wheel never reaches it anyway. So the frame is instead laid out at its full
|
|
86
|
+
content height — nothing scrolls inside it — and the panel scrolls that. An
|
|
87
|
+
[`interactive`](#interactivity) frame doesn't need any of this and opts out of
|
|
88
|
+
it: it keeps the panel's height and the embed scrolls itself.
|
|
81
89
|
|
|
82
90
|
For the frame to be sized that way, the embed reports its height alongside
|
|
83
91
|
`hp-preview:rendered`:
|
|
@@ -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.
|
|
@@ -272,6 +329,8 @@ Available CSS custom properties:
|
|
|
272
329
|
- `--symbol-color-active` - Color of filled/active symbols
|
|
273
330
|
- `--symbol-size` - Size of the symbols
|
|
274
331
|
- `--symbol-spacing` - Spacing between symbols
|
|
332
|
+
- `--preview-color` - Color of the preview value (see [Preview](#preview))
|
|
333
|
+
- `--preview-size` - Font size of the preview value
|
|
275
334
|
|
|
276
335
|
### Complete Example
|
|
277
336
|
|
|
@@ -24,6 +24,10 @@ Set `controls-caption` and `preview-caption` to label each column's header
|
|
|
24
24
|
row — both are empty by default, rendering no text (the controls column's
|
|
25
25
|
header row still renders either way, so the two columns stay aligned).
|
|
26
26
|
|
|
27
|
+
The preview is [interactive](/components/preview-frame/#interactivity): the
|
|
28
|
+
embedded page can be clicked and hovered, and it scrolls itself, so the preview
|
|
29
|
+
column never adds a second scrollbar beside the embed's own.
|
|
30
|
+
|
|
27
31
|
The preview always fills its column, leaving no dead space beneath it.
|
|
28
32
|
`min-height` (default `480`) is a floor for that column, not a fixed height —
|
|
29
33
|
it's still forwarded to the [preview frame](/components/preview-frame/), which
|
package/package.json
CHANGED
|
@@ -111,6 +111,12 @@ export default class ZnPreviewFrame extends ZincElement {
|
|
|
111
111
|
* be reached from here to cancel its own handlers, so this blocks pointer
|
|
112
112
|
* input entirely — hover goes with it. Scrolling doesn't: an overflowing
|
|
113
113
|
* page is scrolled by the panel rather than by the frame (see _contentHeight).
|
|
114
|
+
*
|
|
115
|
+
* Set, the frame becomes a real viewport instead: it stays the panel's own
|
|
116
|
+
* height and the embed scrolls itself, so there is one scrollbar rather than
|
|
117
|
+
* a panel scrolling an oversized frame, and the embed's viewport-relative
|
|
118
|
+
* layout (`100vh`, `position: fixed`, sticky headers) sizes to what's on
|
|
119
|
+
* screen. _contentHeight is ignored while this is set.
|
|
114
120
|
*/
|
|
115
121
|
@property({type: Boolean, reflect: true}) interactive = false;
|
|
116
122
|
|
|
@@ -125,7 +131,8 @@ export default class ZnPreviewFrame extends ZincElement {
|
|
|
125
131
|
* height rather than the panel's, so the page never scrolls inside the frame
|
|
126
132
|
* — the panel scrolls instead, which is what makes an overflowing preview
|
|
127
133
|
* reachable while pointer input to the frame is blocked. 0 = unknown, and the
|
|
128
|
-
* frame falls back to filling the panel.
|
|
134
|
+
* frame falls back to filling the panel. Kept up to date either way, but only
|
|
135
|
+
* laid out when the frame is inert: an `interactive` frame scrolls itself.
|
|
129
136
|
*/
|
|
130
137
|
@state() private _contentHeight = 0;
|
|
131
138
|
|
|
@@ -396,7 +403,10 @@ export default class ZnPreviewFrame extends ZincElement {
|
|
|
396
403
|
// transformed back down, so the frame fills the panel while the content
|
|
397
404
|
// renders smaller and more of the page is visible. Percentage width means
|
|
398
405
|
// nothing is measured — no layout feedback loop.
|
|
399
|
-
|
|
406
|
+
// Growing the frame past the panel is the workaround for an inert frame not
|
|
407
|
+
// being scrollable; interactive, that trades one scrollbar for two and hands
|
|
408
|
+
// the embed a viewport taller than the panel it's shown in.
|
|
409
|
+
const content = this.error || this.interactive ? 0 : this._contentHeight;
|
|
400
410
|
const iframeStyles = this.fill
|
|
401
411
|
? {width: '100%', height: content ? `max(${content}px, 100%)` : '100%'}
|
|
402
412
|
: {
|
|
@@ -683,6 +683,44 @@ describe('<zn-preview-frame>', () => {
|
|
|
683
683
|
const stage = el.shadowRoot!.querySelector<HTMLDivElement>('.preview__stage')!;
|
|
684
684
|
expect(stage.style.height).to.equal('max(1500px, 100%)');
|
|
685
685
|
});
|
|
686
|
+
|
|
687
|
+
it('leaves an interactive frame at the panel height, so the embed scrolls itself', async () => {
|
|
688
|
+
const el = await fixture(html`
|
|
689
|
+
<zn-preview-frame
|
|
690
|
+
src="about:blank"
|
|
691
|
+
frame-origin="https://site.example"
|
|
692
|
+
data-uri="/payload"
|
|
693
|
+
fill
|
|
694
|
+
interactive></zn-preview-frame>`);
|
|
695
|
+
|
|
696
|
+
const iframe = el.shadowRoot!.querySelector('iframe')!;
|
|
697
|
+
const panel = el.shadowRoot!.querySelector<HTMLDivElement>('.preview')!;
|
|
698
|
+
const stage = el.shadowRoot!.querySelector<HTMLDivElement>('.preview__stage')!;
|
|
699
|
+
|
|
700
|
+
reportHeight(el, 1500);
|
|
701
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
702
|
+
|
|
703
|
+
expect(iframe.style.height).to.equal('100%');
|
|
704
|
+
expect(stage.style.height).to.equal('');
|
|
705
|
+
expect(panel.scrollHeight).to.equal(panel.clientHeight);
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
it('inflates the frame again when interactive is turned off', async () => {
|
|
709
|
+
const el = await fixture(html`
|
|
710
|
+
<zn-preview-frame
|
|
711
|
+
src="about:blank"
|
|
712
|
+
frame-origin="https://site.example"
|
|
713
|
+
data-uri="/payload"
|
|
714
|
+
interactive></zn-preview-frame>`) as HTMLElement & {interactive: boolean};
|
|
715
|
+
const iframe = el.shadowRoot!.querySelector('iframe')!;
|
|
716
|
+
|
|
717
|
+
reportHeight(el, 1400);
|
|
718
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
719
|
+
expect(iframe.style.height).to.equal('480px');
|
|
720
|
+
|
|
721
|
+
el.interactive = false;
|
|
722
|
+
await waitUntil(() => iframe.style.height === '1400px', 'kept the reported height unused');
|
|
723
|
+
});
|
|
686
724
|
});
|
|
687
725
|
|
|
688
726
|
it('skips the config fetch when data-uri is empty', async () => {
|
|
@@ -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,17 @@ export default class ZnRating extends ZincElement implements ZincFormControl {
|
|
|
94
111
|
}
|
|
95
112
|
|
|
96
113
|
private _getValueFromXCoordinate(coordinate: number): number {
|
|
97
|
-
const {left, width} = this.
|
|
114
|
+
const {left, width} = this.symbols.getBoundingClientRect();
|
|
98
115
|
const value = this._roundToPrecision(((coordinate - left) / width) * this.max, this.precision);
|
|
99
116
|
|
|
100
117
|
return Math.min(Math.max(value, 0), this.max);
|
|
101
118
|
}
|
|
102
119
|
|
|
120
|
+
private _formatValue(value: number): string {
|
|
121
|
+
const decimals = String(this.precision).split('.')[1]?.length ?? 0;
|
|
122
|
+
return value.toFixed(decimals);
|
|
123
|
+
}
|
|
124
|
+
|
|
103
125
|
private _getValueFromMousePosition(event: MouseEvent): number {
|
|
104
126
|
return this._getValueFromXCoordinate(event.clientX);
|
|
105
127
|
}
|
|
@@ -150,13 +172,15 @@ export default class ZnRating extends ZincElement implements ZincFormControl {
|
|
|
150
172
|
|
|
151
173
|
private _handleTouchEnd(event: TouchEvent) {
|
|
152
174
|
this.isHovering = false;
|
|
153
|
-
|
|
175
|
+
// `touches` is empty once the finger lifts, so the final position lives in `changedTouches`.
|
|
176
|
+
this._setValue(this._getValueFromXCoordinate(event.changedTouches[0].clientX));
|
|
154
177
|
|
|
155
178
|
event.preventDefault();
|
|
156
179
|
}
|
|
157
180
|
|
|
158
181
|
render() {
|
|
159
182
|
const counter = Array.from(Array(this.max).keys());
|
|
183
|
+
const hasHelpText = this.helpText ? true : this.hasSlotController.test('help-text');
|
|
160
184
|
let displayValue = 0;
|
|
161
185
|
|
|
162
186
|
if (this.disabled || this.readonly) {
|
|
@@ -167,69 +191,102 @@ export default class ZnRating extends ZincElement implements ZincFormControl {
|
|
|
167
191
|
|
|
168
192
|
return html`
|
|
169
193
|
<div
|
|
170
|
-
part="
|
|
194
|
+
part="form-control"
|
|
171
195
|
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
|
-
|
|
196
|
+
'form-control': true,
|
|
197
|
+
'form-control--small': this.size === 'small',
|
|
198
|
+
'form-control--medium': this.size === 'medium',
|
|
199
|
+
'form-control--large': this.size === 'large',
|
|
200
|
+
'form-control--has-help-text': hasHelpText
|
|
201
|
+
})}">
|
|
202
|
+
<div
|
|
203
|
+
part="base"
|
|
204
|
+
class="${classMap({
|
|
205
|
+
rating: true,
|
|
206
|
+
'rating--readonly': this.readonly,
|
|
207
|
+
'rating--disabled': this.disabled,
|
|
208
|
+
'rating--small': this.size === 'small',
|
|
209
|
+
'rating--medium': this.size === 'medium',
|
|
210
|
+
'rating--large': this.size === 'large'
|
|
211
|
+
})}"
|
|
212
|
+
role="slider"
|
|
213
|
+
aria-label="${this.label}"
|
|
214
|
+
aria-describedby="help-text"
|
|
215
|
+
aria-disabled="${this.disabled ? 'true' : 'false'}"
|
|
216
|
+
aria-readonly="${this.readonly ? 'true' : 'false'}"
|
|
217
|
+
aria-valuenow="${this.value}"
|
|
218
|
+
aria-valuemin="0"
|
|
219
|
+
aria-valuemax="${this.max}"
|
|
220
|
+
>
|
|
221
|
+
<span
|
|
222
|
+
class="rating__symbols"
|
|
223
|
+
@click="${this._handleClick}"
|
|
224
|
+
@mouseenter="${this._handleMouseEnter}"
|
|
225
|
+
@mousemove="${this._handleMouseMove}"
|
|
226
|
+
@mouseleave="${this._handleMouseLeave}"
|
|
227
|
+
@touchstart="${this._handleTouchStart}"
|
|
228
|
+
@touchmove="${this._handleTouchMove}"
|
|
229
|
+
@touchend="${this._handleTouchEnd}"
|
|
230
|
+
>
|
|
231
|
+
${counter.map(index => {
|
|
232
|
+
if (displayValue > index && displayValue < index + 1) {
|
|
233
|
+
return html`
|
|
234
|
+
<span
|
|
235
|
+
class=${classMap({
|
|
236
|
+
rating__symbol: true,
|
|
237
|
+
'rating__partial-symbol-container': true,
|
|
238
|
+
'rating__symbol--hover': this.isHovering && Math.ceil(displayValue) === index + 1
|
|
239
|
+
})}
|
|
240
|
+
role="presentation">
|
|
241
|
+
<div
|
|
242
|
+
style=${styleMap({
|
|
243
|
+
clipPath: `inset(0 0 0 ${(displayValue - index) * 100}%)`
|
|
244
|
+
})}>
|
|
245
|
+
${unsafeHTML(this.getSymbol(index + 1))}
|
|
246
|
+
</div>
|
|
247
|
+
<div
|
|
248
|
+
class="rating__partial--filled"
|
|
249
|
+
style=${styleMap({
|
|
250
|
+
clipPath: `inset(0 ${100 - (displayValue - index) * 100}% 0 0)`
|
|
251
|
+
})}>
|
|
252
|
+
${unsafeHTML(this.getSymbol(index + 1))}
|
|
253
|
+
</div>
|
|
254
|
+
</span>
|
|
255
|
+
`;
|
|
256
|
+
}
|
|
197
257
|
return html`
|
|
198
258
|
<span
|
|
199
259
|
class=${classMap({
|
|
200
260
|
rating__symbol: true,
|
|
201
|
-
'
|
|
202
|
-
'rating__symbol--
|
|
261
|
+
'rating__symbol--hover': this.isHovering && Math.ceil(displayValue) === index + 1,
|
|
262
|
+
'rating__symbol--active': displayValue >= index + 1
|
|
203
263
|
})}
|
|
204
264
|
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`
|
|
265
|
+
${unsafeHTML(this.getSymbol(index + 1))}
|
|
266
|
+
</span>`;
|
|
267
|
+
})}
|
|
268
|
+
</span>
|
|
269
|
+
${this.preview
|
|
270
|
+
? html`
|
|
222
271
|
<span
|
|
272
|
+
part="preview"
|
|
223
273
|
class=${classMap({
|
|
224
|
-
|
|
225
|
-
'
|
|
226
|
-
'rating__symbol--active': displayValue >= index + 1
|
|
274
|
+
rating__preview: true,
|
|
275
|
+
'rating__preview--hover': this.isHovering
|
|
227
276
|
})}
|
|
228
|
-
|
|
229
|
-
${
|
|
230
|
-
</span
|
|
231
|
-
|
|
232
|
-
</
|
|
277
|
+
aria-hidden="true">
|
|
278
|
+
${this._formatValue(displayValue)}
|
|
279
|
+
</span>`
|
|
280
|
+
: ''}
|
|
281
|
+
</div>
|
|
282
|
+
|
|
283
|
+
<div
|
|
284
|
+
part="form-control-help-text"
|
|
285
|
+
id="help-text"
|
|
286
|
+
class="form-control__help-text"
|
|
287
|
+
aria-hidden=${hasHelpText ? 'false' : 'true'}>
|
|
288
|
+
<slot name="help-text">${this.helpText}</slot>
|
|
289
|
+
</div>
|
|
233
290
|
</div>
|
|
234
291
|
`;
|
|
235
292
|
}
|
|
@@ -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
8
|
--symbol-spacing: 4px;
|
|
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
|
|
|
@@ -62,6 +66,21 @@
|
|
|
62
66
|
}
|
|
63
67
|
}
|
|
64
68
|
|
|
69
|
+
&__preview {
|
|
70
|
+
margin-left: var(--symbol-spacing);
|
|
71
|
+
font-size: var(--preview-size);
|
|
72
|
+
font-weight: var(--zn-font-weight-semibold);
|
|
73
|
+
line-height: 1;
|
|
74
|
+
color: var(--preview-color);
|
|
75
|
+
font-variant-numeric: tabular-nums;
|
|
76
|
+
pointer-events: none;
|
|
77
|
+
user-select: none;
|
|
78
|
+
|
|
79
|
+
&--hover {
|
|
80
|
+
color: var(--symbol-color-active);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
65
84
|
&--disabled &__symbols,
|
|
66
85
|
&--readonly &__symbols {
|
|
67
86
|
cursor: default;
|
|
@@ -72,6 +91,11 @@
|
|
|
72
91
|
scale: none;
|
|
73
92
|
}
|
|
74
93
|
|
|
94
|
+
&--disabled &__preview--hover,
|
|
95
|
+
&--readonly &__preview--hover {
|
|
96
|
+
color: var(--preview-color);
|
|
97
|
+
}
|
|
98
|
+
|
|
75
99
|
&--disabled {
|
|
76
100
|
opacity: 0.5;
|
|
77
101
|
}
|
|
@@ -840,12 +840,14 @@ export default class ZnThemeEditor extends ZincElement {
|
|
|
840
840
|
<slot @slotchange="${this._onSlotChange}"></slot>
|
|
841
841
|
${this._hasNestedGroups()
|
|
842
842
|
? this._renderTabs(section => html`
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
843
|
+
${this._hasAssignedControls(section.name) ? html`
|
|
844
|
+
<slot name="${section.name}" class="editor__section-slot"
|
|
845
|
+
@slotchange="${this._onSlotChange}"></slot>` : nothing}
|
|
846
|
+
${this._renderGroups(section)}`)
|
|
846
847
|
: this.sectionLayout === 'tabs'
|
|
847
848
|
? this._renderTabs(section => html`
|
|
848
|
-
|
|
849
|
+
<slot name="${section.name}" class="editor__section-slot"
|
|
850
|
+
@slotchange="${this._onSlotChange}"></slot>`)
|
|
849
851
|
: this._renderSections()}
|
|
850
852
|
</div>
|
|
851
853
|
${this.hasSlotController.test('footer') ? html`
|
|
@@ -910,6 +912,7 @@ export default class ZnThemeEditor extends ZincElement {
|
|
|
910
912
|
device="${this.device}"
|
|
911
913
|
min-height="${this.minHeight}"
|
|
912
914
|
fill
|
|
915
|
+
interactive
|
|
913
916
|
backdrop="${this.standalone ? 'panel' : 'dots'}"
|
|
914
917
|
exportparts="base:preview__base,stage:preview__stage,iframe:preview__iframe,error:preview__error"
|
|
915
918
|
@zn-error="${this._onFrameError}"></zn-preview-frame>
|
|
@@ -180,9 +180,11 @@
|
|
|
180
180
|
margin-top: auto;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
//
|
|
183
|
+
// Holds the preview below the toolbar, and never scrolls: the frame is its own
|
|
184
|
+
// scroll region (interactive, the embed is), so a scroller here would only ever
|
|
185
|
+
// be a second scrollbar beside that one.
|
|
184
186
|
.editor__preview {
|
|
185
|
-
overflow:
|
|
187
|
+
overflow: hidden;
|
|
186
188
|
flex: 1 1 auto;
|
|
187
189
|
min-width: 0;
|
|
188
190
|
min-height: 0;
|
|
@@ -729,6 +729,22 @@ describe('<zn-theme-editor>', () => {
|
|
|
729
729
|
expect(controlsHeader).to.exist;
|
|
730
730
|
expect(controlsHeader!.textContent?.trim()).to.equal('');
|
|
731
731
|
});
|
|
732
|
+
|
|
733
|
+
it('gives the preview an interactive frame that scrolls itself, so the column never adds a second scrollbar', async () => {
|
|
734
|
+
const el = await fixture(FIXTURE);
|
|
735
|
+
await (el as HTMLElement & {updateComplete: Promise<unknown>}).updateComplete;
|
|
736
|
+
|
|
737
|
+
const preview = el.shadowRoot!.querySelector<HTMLElement>('[part="preview"]')!;
|
|
738
|
+
const frame = el.shadowRoot!.querySelector<HTMLElement>('zn-preview-frame')!;
|
|
739
|
+
await (frame as HTMLElement & {updateComplete: Promise<unknown>}).updateComplete;
|
|
740
|
+
|
|
741
|
+
expect(frame.hasAttribute('interactive')).to.equal(true);
|
|
742
|
+
expect(getComputedStyle(preview).overflowY).to.equal('hidden');
|
|
743
|
+
expect(preview.scrollHeight).to.equal(preview.clientHeight);
|
|
744
|
+
|
|
745
|
+
const iframe = frame.shadowRoot!.querySelector('iframe')!;
|
|
746
|
+
expect(getComputedStyle(iframe).pointerEvents).to.equal('auto');
|
|
747
|
+
});
|
|
732
748
|
});
|
|
733
749
|
|
|
734
750
|
describe('tabbed sections (section-layout="tabs")', () => {
|