@brightspace-ui/labs 2.19.1 → 2.20.1

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.
@@ -0,0 +1,409 @@
1
+ import '@brightspace-ui/core/components/colors/colors.js';
2
+ import { css, html, LitElement } from 'lit';
3
+ import { LocalizeLabsElement } from '../localize-labs-element.js';
4
+ import { RtlMixin } from '@brightspace-ui/core/mixins/rtl-mixin.js';
5
+
6
+ class SliderBar extends LocalizeLabsElement(RtlMixin(LitElement)) {
7
+
8
+ static get properties() {
9
+ return {
10
+ value: { type: Number },
11
+ immediateValue: { type: Number, reflect: true },
12
+ hoverValue: { type: Number, reflect: true },
13
+ dragging: { type: Boolean, reflect: true },
14
+ hovering: { type: Boolean, reflect: true },
15
+ vertical: { type: Boolean },
16
+ fullWidth: { type: Boolean },
17
+ min: { type: Number },
18
+ max: { type: Number }
19
+ };
20
+ }
21
+
22
+ static get styles() {
23
+ return css`
24
+ :host {
25
+ --d2l-color-corundum-65-opacity: rgba(177, 185, 190, 0.65);
26
+ --d2l-color-galena-88-opacity: rgba(110, 116, 119, 0.88);
27
+ --d2l-calculated-seek-bar-height: var(--d2l-seek-bar-height, 6px);
28
+ --d2l-calculated-knob-size: var(--d2l-knob-size, 32px);
29
+ --d2l-half-knob-size: calc(var(--d2l-calculated-knob-size)/2);
30
+ --d2l-half-knob-size-overflow: calc((var(--d2l-calculated-knob-size) - var(--d2l-calculated-seek-bar-height)) / 2 - 1px);
31
+ --d2l-calculated-inner-knob-margin: var(--d2l-inner-knob-margin, 8px);
32
+ --d2l-calculated-knob-box-shadow: var(--d2l-knob-box-shadow, 0 2px 4px 0 rgba(0, 0, 0, 0.52));
33
+ --d2l-calculated-outer-knob-color: var(--d2l-outer-knob-color, var(--d2l-color-regolith));
34
+ --d2l-calculated-outer-knob-border-color: var(--d2l-outer-knob-border-color, var(--d2l-color-pressicus));
35
+ --d2l-inner-knob-color: var(--d2l-inner-knob-color, var(--d2l-color-celestine-plus-1));
36
+ --d2l-calculated-knob-focus-color: var(--d2l-knob-focus-color, var(--d2l-color-celestine));
37
+ --d2l-calculated-knob-focus-size: var(--d2l-knob-focus-size, 2px);
38
+ --d2l-calculated-progress-border-color: var(--d2l-progress-border-color, var(--d2l-color-pressicus));
39
+ --d2l-calculated-progress-border-radius: var(--d2l-progress-border-radius, 6px);
40
+ --d2l-calculated-progress-shadow-color: var(--d2l-progress-shadow-color, var(--d2l-color-galena-88-opacity));
41
+ --d2l-calculated-progress-active-color: var(--d2l-progress-active-color, var(--d2l-color-celestine-plus-1));
42
+ --d2l-calculated-progress-background-color: var(--d2l-progress-background-color, var(--d2l-color-corundum-65-opacity));
43
+ display: block;
44
+ }
45
+
46
+ :host(:focus) {
47
+ outline: none;
48
+ }
49
+
50
+ :host(:focus) .slider-knob::after {
51
+ border-radius: 50%;
52
+ bottom: 0;
53
+ box-shadow: 0 0 0 var(--d2l-calculated-knob-focus-size) var(--d2l-calculated-knob-focus-color);
54
+ content: "";
55
+ left: 0;
56
+ position: absolute;
57
+ right: 0;
58
+ top: 0;
59
+ }
60
+
61
+ :host([solid]) .slider-knob-inner {
62
+ display: none;
63
+ }
64
+
65
+ #sliderContainer {
66
+ height: var(--d2l-calculated-knob-size);
67
+ margin-left: var(--d2l-half-knob-size);
68
+ margin-right: var(--d2l-half-knob-size);
69
+ position: relative;
70
+ }
71
+
72
+ #knobContainer {
73
+ bottom: 0;
74
+ left: 0;
75
+ pointer-events: none;
76
+ position: absolute;
77
+ right: 0;
78
+ top: 0;
79
+ }
80
+
81
+ :host([fullWidth]) #sliderContainer {
82
+ margin-left: 0;
83
+ margin-right: 0;
84
+ }
85
+
86
+ :host([fullWidth]) #knobContainer {
87
+ left: var(--d2l-half-knob-size);
88
+ right: var(--d2l-half-knob-size);
89
+ }
90
+
91
+ .bar-container {
92
+ cursor: pointer;
93
+ overflow: hidden;
94
+ }
95
+
96
+ #sliderBar {
97
+ padding: var(--d2l-half-knob-size-overflow) 0;
98
+ width: 100%;
99
+ }
100
+
101
+ #progressContainer {
102
+ --d2l-progress-container-color: var(--d2l-calculated-progress-background-color);
103
+ background: var(--d2l-progress-container-color, var(--d2l-color-gypsum));
104
+ border-radius: var(--d2l-calculated-progress-border-radius);
105
+ box-shadow: inset 0 1px 0 0 var(--d2l-calculated-progress-shadow-color);
106
+ height: var(--d2l-progress-height, 6px);
107
+ position: relative;
108
+ }
109
+
110
+ #primaryProgress {
111
+ --d2l-progress-active-color: var(--d2l-calculated-progress-active-color);
112
+ background: var(--d2l-progress-active-color, var(--d2l-color-celestine));
113
+ border-radius: var(--d2l-calculated-progress-border-radius);
114
+ box-shadow: inset 0 1px 0 0 rgba(0, 0, 0, 0.07);
115
+ height: 100%;
116
+ }
117
+
118
+ .slider-knob {
119
+ background-color: var(--d2l-calculated-outer-knob-color);
120
+ border-radius: 50%;
121
+ box-shadow: var(--d2l-calculated-knob-box-shadow);
122
+ cursor: pointer;
123
+ height: calc((((var(--d2l-calculated-knob-size) / 2) - var(--d2l-calculated-seek-bar-height) / 2) * 2) + var(--d2l-calculated-seek-bar-height) - 2px);
124
+ left: 0;
125
+ margin-left: calc(-1 * ((var(--d2l-calculated-knob-size) / 2) - var(--d2l-calculated-seek-bar-height) / 2) - var(--d2l-calculated-seek-bar-height) / 2);
126
+ position: absolute;
127
+ top: 0;
128
+ width: calc((((var(--d2l-calculated-knob-size) / 2) - var(--d2l-calculated-seek-bar-height) / 2) * 2) + var(--d2l-calculated-seek-bar-height) - 2px);
129
+ z-index: 1;
130
+ }
131
+
132
+ .slider-knob-inner {
133
+ background-color: var(--d2l-inner-knob-color);
134
+ border-radius: 50%;
135
+ box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.07);
136
+ box-sizing: border-box;
137
+ height: calc(100% - var(--d2l-calculated-inner-knob-margin) *2);
138
+ margin: var(--d2l-calculated-inner-knob-margin);
139
+ width: calc(100% - var(--d2l-calculated-inner-knob-margin) *2);
140
+ }`;
141
+ }
142
+
143
+ constructor() {
144
+ super();
145
+
146
+ this.immediateValue = 0;
147
+ this.hoverValue = 0;
148
+ this.dragging = false;
149
+ this.hovering = false;
150
+ this.vertical = false;
151
+ this.fullWidth = false;
152
+ this.min = 0;
153
+ this.max = 100;
154
+ }
155
+
156
+ connectedCallback() {
157
+ super.connectedCallback();
158
+ window.addEventListener('mouseup', () => { this._barUp(); });
159
+ window.addEventListener('mousemove', (event) => { this._onTrack(event); });
160
+ }
161
+
162
+ disconnectedCallback() {
163
+ super.disconnectedCallback();
164
+ window.removeEventListener('mouseup', () => { this._barUp(); });
165
+ window.removeEventListener('mousemove', (event) => { this._onTrack(event); });
166
+ }
167
+
168
+ render() {
169
+ return html`
170
+ <div id="sliderContainer"
171
+ @mouseover="${this._onHostHover}"
172
+ @mousemove="${this._onHostMove}"
173
+ @mouseout="${this._onHostUnhover}"
174
+ style="width: ${this.fullWidth ? '100%' : 'auto'}"
175
+ >
176
+ <div class="bar-container">
177
+ <div
178
+ id="sliderBar"
179
+ @mousedown="${this._barDown}"
180
+ role="slider"
181
+ aria-label="${this.localize('components:mediaPlayer:sliderBarProgress')}"
182
+ aria-orientation="${this.vertical ? 'vertical' : 'horizontal'}"
183
+ aria-valuemin="${this.min}"
184
+ aria-valuemax="${this.max}"
185
+ aria-valuenow="${this.immediateValue ? this.immediateValue : 0}"
186
+ >
187
+ <div id="progressContainer">
188
+ <div id="primaryProgress"></div>
189
+ </div>
190
+ </div>
191
+ </div>
192
+ <div id="knobContainer">
193
+ <div id="sliderKnob"
194
+ class="slider-knob"
195
+ @keydown="${this._onKeyPress}"
196
+ tabindex="0"
197
+ >
198
+ <div class="slider-knob-inner"></div>
199
+ </div>
200
+ </div>
201
+ </div>
202
+ `;
203
+ }
204
+
205
+ updated(changedProperties) {
206
+ this.ratio = this._calcRatio(this.value);
207
+
208
+ if (changedProperties.has('value') || changedProperties.has('min') || changedProperties.has('max')) {
209
+ this._updateKnob(this.value, this.min, this.max);
210
+ this._progressChanged(this.value);
211
+ }
212
+ if (changedProperties.has('immediateValue')) {
213
+ this._immediateValueChanged(this.immediateValue);
214
+ }
215
+ if (changedProperties.has('dragging')) {
216
+ this._draggingChanged(this.dragging);
217
+ }
218
+ if (changedProperties.has('hoverValue')) {
219
+ this._hoverValueChanged(this.hoverValue);
220
+ }
221
+ if (changedProperties.has('hovering')) {
222
+ this._hoveringChanged(this.hovering);
223
+ }
224
+ }
225
+
226
+ _barDown(event) {
227
+ const knobContainer = this.shadowRoot.getElementById('knobContainer');
228
+ this._w = knobContainer.offsetWidth;
229
+ const rect = knobContainer.getBoundingClientRect();
230
+
231
+ const mousePosition = this.vertical ? rect.bottom - event.clientY : event.clientX - rect.left;
232
+ const ratio = mousePosition / this._w;
233
+
234
+ this._x = mousePosition;
235
+ this._startx = this._x;
236
+ this._knobstartx = this._startx;
237
+
238
+ this.dragging = true;
239
+ this._positionKnob(ratio);
240
+
241
+ event.preventDefault();
242
+ this.shadowRoot.getElementById('sliderKnob').focus();
243
+ }
244
+
245
+ _barUp() {
246
+ this._trackEnd();
247
+ this.removeEventListener('mousemove', this._onTrack);
248
+ }
249
+
250
+ _calcKnobPosition(ratio) {
251
+ return (this.max - this.min) * ratio + this.min;
252
+ }
253
+
254
+ _calcRatio(value) {
255
+ return (value - this.min) / (this.max - this.min);
256
+ }
257
+
258
+ _calcStep(position) {
259
+ return Math.min(Math.max(position, this.min), this.max);
260
+ }
261
+
262
+ _checkKey(event, key, valueChange) {
263
+ if (event.key === key) {
264
+ event.preventDefault();
265
+ this.immediateValue += valueChange;
266
+ this.dispatchEvent(new CustomEvent('position-change', { bubbles: true, composed: true }));
267
+ }
268
+ }
269
+
270
+ _clampValue(value) {
271
+ return Math.max(this.min, Math.min(this.max, value));
272
+ }
273
+
274
+ _draggingChanged() {
275
+ if (this.dragging) {
276
+ this.dispatchEvent(new CustomEvent('drag-start', { bubbles: true, composed: true }));
277
+ } else {
278
+ this.dispatchEvent(new CustomEvent('drag-end', { bubbles: true, composed: true }));
279
+ }
280
+ }
281
+
282
+ _hoveringChanged() {
283
+ if (this.hovering) {
284
+ this.dispatchEvent(new CustomEvent('hovering-start', { bubbles: true, composed: true }));
285
+ } else {
286
+ this.dispatchEvent(new CustomEvent('hovering-end', { bubbles: true, composed: true }));
287
+ }
288
+ }
289
+
290
+ _hoverValueChanged() {
291
+ if (this.hovering) {
292
+ this.dispatchEvent(new CustomEvent('hovering-move', { bubbles: true, composed: true }));
293
+ }
294
+ }
295
+
296
+ _immediateValueChanged() {
297
+ if (!this.dragging) {
298
+ this.value = this.immediateValue;
299
+ }
300
+ }
301
+
302
+ _onHostHover() {
303
+ this.hovering = true;
304
+ }
305
+
306
+ _onHostMove(e) {
307
+ if (this.hovering) {
308
+ const rect = this.shadowRoot.getElementById('knobContainer').getBoundingClientRect();
309
+ const mousePosition = this.vertical ? rect.bottom - e.clientY : e.clientX - rect.left;
310
+ const ratio = mousePosition / this.shadowRoot.getElementById('knobContainer').offsetWidth;
311
+
312
+ const value = this._calcStep(this._calcKnobPosition(ratio));
313
+ if (value >= this.min && value <= this.max)
314
+ this.hoverValue = value;
315
+ }
316
+ }
317
+
318
+ _onHostUnhover() {
319
+ this.hovering = false;
320
+
321
+ this.removeEventListener('mousemove', this._onTrack);
322
+ }
323
+
324
+ _onKeyPress(event) {
325
+ if (this.vertical) {
326
+ this._checkKey(event, 'ArrowUp', 5);
327
+ this._checkKey(event, 'ArrowDown', -5);
328
+ } else {
329
+ this._checkKey(event, 'ArrowRight', 5);
330
+ this._checkKey(event, 'ArrowLeft', -5);
331
+ }
332
+ }
333
+
334
+ _onTrack(event) {
335
+ if (!this.dragging) {
336
+ return;
337
+ }
338
+
339
+ this.dispatchEvent(new CustomEvent('position-change', { bubbles: true, composed: true }));
340
+ event.stopPropagation();
341
+ this._track(event);
342
+ }
343
+
344
+ _positionKnob(ratio) {
345
+ this.immediateValue = this._calcStep(this._calcKnobPosition(ratio));
346
+ this.ratio = this._calcRatio(this.immediateValue);
347
+
348
+ const sliderKnob = this.shadowRoot.getElementById('sliderKnob');
349
+ sliderKnob.style.left = `${this.ratio * 100}%`;
350
+
351
+ if (this.dragging) {
352
+ this._knobstartx = this.ratio * this._w;
353
+ this._translate3d(0, 0, 0, sliderKnob);
354
+ }
355
+ }
356
+
357
+ _progressChanged(value) {
358
+ this.value = this._clampValue(value);
359
+
360
+ const mainRatio = this._calcRatio(this.value) * 100;
361
+ const progress = this.shadowRoot.getElementById('primaryProgress');
362
+
363
+ this._transformProgress(progress, mainRatio);
364
+ }
365
+
366
+ _track(event) {
367
+ if (!this.dragging) {
368
+ this._trackStart(event);
369
+ }
370
+ const rect = this.shadowRoot.getElementById('knobContainer').getBoundingClientRect();
371
+ const mousePosition = this.vertical ? rect.bottom - event.clientY : event.clientX - rect.left;
372
+ this._x = mousePosition;
373
+
374
+ this.immediateValue = this._calcStep(this._calcKnobPosition(this._x / this._w));
375
+
376
+ const translateX = ((this._calcRatio(this.immediateValue) * this._w) - this._knobstartx);
377
+ this._translate3d(`${translateX}px`, '0', '0', this.shadowRoot.getElementById('sliderKnob'));
378
+ }
379
+
380
+ _trackEnd() {
381
+ const sliderKnobStyle = this.shadowRoot.getElementById('sliderKnob').style;
382
+
383
+ this.dragging = false;
384
+ this.value = this.immediateValue;
385
+ sliderKnobStyle.transform = sliderKnobStyle.transformOrigin = '';
386
+ }
387
+
388
+ _trackStart() {
389
+ const knobContainer = this.shadowRoot.getElementById('knobContainer');
390
+ this._w = knobContainer.offsetWidth;
391
+ this._x = this.ratio * this._w;
392
+ this._startx = this._x;
393
+ this._knobstartx = this._startx;
394
+ this.dragging = true;
395
+ }
396
+
397
+ _transformProgress(progress, ratio) {
398
+ progress.style.width = `${ratio}%`;
399
+ }
400
+
401
+ _translate3d(x, y, z, element) {
402
+ element.style.transform = `translate3d(${x}, ${y}, ${z})`;
403
+ }
404
+
405
+ _updateKnob(value) {
406
+ this._positionKnob(this._calcRatio(value));
407
+ }
408
+ }
409
+ customElements.define('d2l-slider-bar', SliderBar);
@@ -1,9 +1,16 @@
1
1
  import { css, html, LitElement } from 'lit';
2
2
  import { highlightBorderStyles, highlightButtonStyles } from './navigation-styles.js';
3
3
  import { DropdownOpenerMixin } from '@brightspace-ui/core/components/dropdown/dropdown-opener-mixin.js';
4
+ import { ifDefined } from 'lit/directives/if-defined.js';
4
5
 
5
6
  class NavigationDropdownButtonCustom extends DropdownOpenerMixin(LitElement) {
6
7
 
8
+ static get properties() {
9
+ return {
10
+ openerLabel: { type: String, attribute: 'opener-label' }
11
+ };
12
+ }
13
+
7
14
  static get styles() {
8
15
  return [highlightBorderStyles, highlightButtonStyles, css`
9
16
  :host {
@@ -19,7 +26,10 @@ class NavigationDropdownButtonCustom extends DropdownOpenerMixin(LitElement) {
19
26
 
20
27
  render() {
21
28
  return html`
22
- <button type="button">
29
+ <button
30
+ type="button"
31
+ aria-haspopup="menu"
32
+ aria-label="${ifDefined(this.openerLabel)}">
23
33
  <span class="d2l-labs-navigation-highlight-border"></span>
24
34
  <slot name="opener"></slot>
25
35
  </button>
@@ -44,14 +44,9 @@ class NavigationSkip extends FocusMixin(PropertyRequiredMixin(LitElement)) {
44
44
  }
45
45
 
46
46
  render() {
47
- return html`<a tabindex="0" @keydown="${this._handleKeyDown}" class="vdiff-target">${this.text}</a>`;
48
- }
49
-
50
- _handleKeyDown(e) {
51
- if (e.keyCode === 13) {
52
- e.preventDefault();
53
- this.dispatchEvent(new CustomEvent('click', { bubbles: true, composed: true }));
54
- }
47
+ // Href attribute is needed for a11y tools to recognize anchor as a link
48
+ // and for click events to be dispatched using key presses
49
+ return html`<a href="javascript:void(0);" class="vdiff-target">${this.text}</a>`;
55
50
  }
56
51
 
57
52
  }