@brightspace-ui/core 3.300.0 → 3.302.0
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/components/dropdown/README.md +21 -13
- package/components/dropdown/dropdown-content.js +0 -2
- package/components/dropdown/dropdown-menu.js +0 -2
- package/components/dropdown/dropdown-opener-mixin.js +13 -2
- package/components/dropdown/dropdown-popover-mixin.js +35 -22
- package/components/dropdown/dropdown-tabs.js +0 -2
- package/components/inputs/docs/form-layout-validation.md +4 -0
- package/components/popover/popover-mixin.js +49 -29
- package/components/tooltip/tooltip.js +0 -1
- package/components/validation/README.md +14 -16
- package/components/validation/validation-custom-mixin.js +10 -0
- package/components/validation/validation-custom.js +4 -0
- package/custom-elements.json +63 -34
- package/package.json +1 -1
|
@@ -215,6 +215,7 @@ To make your usage of `d2l-dropdown-more` accessible, use the following property
|
|
|
215
215
|
| Property | Type | Description |
|
|
216
216
|
|---|---|---|
|
|
217
217
|
| `align` | String | Optionally align dropdown to either `start` or `end`. If not set, the dropdown will attempt to be centred. |
|
|
218
|
+
| `async` | Boolean, default: `false` | Enable when dropdown contains async content. When set, the dropdown will dispatch a `d2l-dropdown-async-load` event before opening for the first time, giving an opportunity to load async content. |
|
|
218
219
|
| `max-height` | Number | Override default `max-height`. _Note that the default behaviour is to be as tall as necessary within the viewport, so this property is usually not needed_ |
|
|
219
220
|
| `min-height` | Number | Override default height used for required space when `no-auto-fit` is true. Specify a number that would be the px value. _Note that the default behaviour is to be as tall as necessary within the viewport, so this property is usually not needed._ |
|
|
220
221
|
| `max-width` | Number | Override default `max-width` |
|
|
@@ -236,7 +237,7 @@ To make your usage of `d2l-dropdown-more` accessible, use the following property
|
|
|
236
237
|
| Name | Description |
|
|
237
238
|
|---|---|
|
|
238
239
|
| `d2l-dropdown-open` | Dispatched when the dropdown is opened |
|
|
239
|
-
| `d2l-dropdown-
|
|
240
|
+
| `d2l-dropdown-async-load` | Dispatched before an "async" dropdown is opened for the first time, giving an opportunity to load async content |
|
|
240
241
|
| `d2l-dropdown-close` | dispatched when the dropdown is closed |
|
|
241
242
|
| `d2l-dropdown-position` | Dispatched when the dropdown position finishes adjusting |
|
|
242
243
|
| `d2l-dropdown-focus-enter` | dispatched when the 'trap-focus' attribute is applied and the focus-trap is entered (trap-focus option only) |
|
|
@@ -254,28 +255,35 @@ To make your usage of `d2l-dropdown-more` accessible, use the following property
|
|
|
254
255
|
|
|
255
256
|
### Asynchronous Content
|
|
256
257
|
|
|
257
|
-
For dropdown content that's fetched asynchronously, the `d2l-dropdown-
|
|
258
|
+
For dropdown content that's fetched asynchronously, the `async` attribute and the `d2l-dropdown-async-load` event can be leveraged to let `<d2l-dropdown-content>` know when things are ready.
|
|
258
259
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
-
|
|
262
|
-
-
|
|
260
|
+
Automatic focus and sizing logic will be delayed until content has loaded.
|
|
261
|
+
|
|
262
|
+
- Set the `async` attribute
|
|
263
|
+
- Add a listener for the `d2l-dropdown-async-load` event that fetches the content
|
|
264
|
+
- Render `nothing` inside `<d2l-dropdown-content>`, allowing dropdown to render a loading spinner while content loads
|
|
265
|
+
- When content is ready, call `complete()` on the event detail
|
|
266
|
+
- To avoid a shift in size between the "loading" and "loaded" states, consider setting a `min-width` matching the typical width of the loaded content
|
|
267
|
+
|
|
268
|
+
The `d2l-dropdown-async-load` event will dispatch pre-emptively when the user hovers or focuses on the dropdown opener, anticipating that the dropdown will be opened.
|
|
269
|
+
|
|
270
|
+
It is also only be dispatched once, so there's no need to ignore subsequent calls
|
|
263
271
|
|
|
264
272
|
```javascript
|
|
265
273
|
class MyElem extends LitElement {
|
|
274
|
+
properties: {
|
|
275
|
+
_content: { state: true }
|
|
276
|
+
}
|
|
266
277
|
render() {
|
|
267
|
-
const content = this._loading ? 'Loading...' : 'Loaded!';
|
|
268
278
|
return html`
|
|
269
279
|
<d2l-dropdown-content
|
|
270
|
-
@d2l-dropdown-
|
|
271
|
-
${
|
|
280
|
+
@d2l-dropdown-async-load="${this.#handleDropdownAsyncLoad}">
|
|
281
|
+
${this._content}
|
|
272
282
|
</d2l-dropdown-content>
|
|
273
283
|
`;
|
|
274
284
|
}
|
|
275
|
-
async #
|
|
276
|
-
|
|
277
|
-
await this.#fetchData();
|
|
278
|
-
this._loaded = true;
|
|
285
|
+
async #handleDropdownAsyncLoad(e) {
|
|
286
|
+
this._content = await this.#fetchData();
|
|
279
287
|
e.detail.complete();
|
|
280
288
|
}
|
|
281
289
|
}
|
|
@@ -7,8 +7,6 @@ import { LitElement } from 'lit';
|
|
|
7
7
|
* @slot header - Sticky container at the top of the dropdown
|
|
8
8
|
* @slot footer - Sticky container at the bottom of the dropdown
|
|
9
9
|
* @fires d2l-dropdown-open - Dispatched when the dropdown is opened
|
|
10
|
-
* @fires d2l-dropdown-open-async - Dispatched before the dropdown is opened for the first time, giving an opportunity to load async content
|
|
11
|
-
* @fires d2l-popover-open-async - Ignore
|
|
12
10
|
*/
|
|
13
11
|
class DropdownContent extends DropdownPopoverMixin(LitElement) { }
|
|
14
12
|
customElements.define('d2l-dropdown-content', DropdownContent);
|
|
@@ -11,8 +11,6 @@ const dropdownDelay = 300;
|
|
|
11
11
|
* @slot header - Sticky container at the top of the dropdown
|
|
12
12
|
* @slot footer - Sticky container at the bottom of the dropdown
|
|
13
13
|
* @fires d2l-dropdown-open - Dispatched when the dropdown is opened
|
|
14
|
-
* @fires d2l-dropdown-open-async - Dispatched before the dropdown is opened for the first time, giving an opportunity to load async content
|
|
15
|
-
* @fires d2l-popover-open-async - Ignore
|
|
16
14
|
*/
|
|
17
15
|
class DropdownMenu extends ThemeMixin(DropdownPopoverMixin(LitElement)) {
|
|
18
16
|
|
|
@@ -71,6 +71,7 @@ export const DropdownOpenerMixin = superclass => class extends superclass {
|
|
|
71
71
|
|
|
72
72
|
// listeners
|
|
73
73
|
this.addEventListener('keypress', this.__onKeypress);
|
|
74
|
+
this.addEventListener('focusin', this.#handleFocusIn);
|
|
74
75
|
this.addEventListener('mouseup', this.__onMouseUp);
|
|
75
76
|
this.addEventListener('mouseenter', this.__onMouseEnter);
|
|
76
77
|
this.addEventListener('mouseleave', this.__onMouseLeave);
|
|
@@ -226,12 +227,15 @@ export const DropdownOpenerMixin = superclass => class extends superclass {
|
|
|
226
227
|
}
|
|
227
228
|
|
|
228
229
|
async __onMouseEnter() {
|
|
229
|
-
if (!this.openOnHover) return;
|
|
230
|
-
// do not respond to hover events on mobile screens
|
|
231
230
|
const dropdownContent = this.__getContentElement();
|
|
232
231
|
|
|
232
|
+
// do not respond to hover events on mobile screens
|
|
233
233
|
if (dropdownContent._mobile) return;
|
|
234
234
|
|
|
235
|
+
dropdownContent?.startAsyncLoad();
|
|
236
|
+
|
|
237
|
+
if (!this.openOnHover) return;
|
|
238
|
+
|
|
235
239
|
clearTimeout(this._dismissTimerId);
|
|
236
240
|
if (!this.dropdownOpened) await this.openDropdown(false);
|
|
237
241
|
this._closeTimerStop();
|
|
@@ -316,4 +320,11 @@ export const DropdownOpenerMixin = superclass => class extends superclass {
|
|
|
316
320
|
}
|
|
317
321
|
return true;
|
|
318
322
|
}
|
|
323
|
+
|
|
324
|
+
#handleFocusIn(e) {
|
|
325
|
+
const opener = this.getOpenerElement();
|
|
326
|
+
if (e.target === this || isComposedAncestor(opener, e.target)) {
|
|
327
|
+
this.__getContentElement()?.startAsyncLoad();
|
|
328
|
+
}
|
|
329
|
+
}
|
|
319
330
|
};
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import '../button/button.js';
|
|
2
|
+
import '../loading-spinner/loading-spinner.js';
|
|
2
3
|
import '../../helpers/viewport-size.js';
|
|
3
4
|
import '../../helpers/visualReady.js';
|
|
4
|
-
import {
|
|
5
|
+
import { asyncStates, PopoverMixin } from '../popover/popover-mixin.js';
|
|
6
|
+
import { css, html, nothing } from 'lit';
|
|
5
7
|
import { classMap } from 'lit/directives/class-map.js';
|
|
6
8
|
import { findComposedAncestor } from '../../helpers/dom.js';
|
|
7
9
|
import { LocalizeCoreElement } from '../../helpers/localize-core-element.js';
|
|
8
|
-
import { PopoverMixin } from '../popover/popover-mixin.js';
|
|
9
10
|
import { styleMap } from 'lit/directives/style-map.js';
|
|
10
11
|
|
|
11
12
|
export const DropdownPopoverMixin = superclass => class extends LocalizeCoreElement(PopoverMixin(superclass)) {
|
|
12
13
|
|
|
13
14
|
static properties = {
|
|
15
|
+
/**
|
|
16
|
+
* Enable when dropdown contains async content. When set, the dropdown will dispatch a `d2l-dropdown-async-load` event before opening for the first time, giving an opportunity to load async content.
|
|
17
|
+
*/
|
|
18
|
+
async: { type: Boolean },
|
|
14
19
|
/**
|
|
15
20
|
* Optionally align dropdown to either start or end. If not set, the dropdown will attempt to be centred.
|
|
16
21
|
* @type {'start'|'end'}
|
|
@@ -121,6 +126,11 @@ export const DropdownPopoverMixin = superclass => class extends LocalizeCoreElem
|
|
|
121
126
|
overflow-y: auto;
|
|
122
127
|
padding: 1rem;
|
|
123
128
|
}
|
|
129
|
+
.dropdown-content-loading {
|
|
130
|
+
display: flex;
|
|
131
|
+
justify-content: center;
|
|
132
|
+
padding: 20px;
|
|
133
|
+
}
|
|
124
134
|
.dropdown-header,
|
|
125
135
|
.dropdown-footer {
|
|
126
136
|
box-sizing: border-box;
|
|
@@ -169,6 +179,7 @@ export const DropdownPopoverMixin = superclass => class extends LocalizeCoreElem
|
|
|
169
179
|
|
|
170
180
|
constructor() {
|
|
171
181
|
super();
|
|
182
|
+
this.async = false;
|
|
172
183
|
this.opened = false;
|
|
173
184
|
this.noAutoClose = false;
|
|
174
185
|
this.noAutoFit = false;
|
|
@@ -197,8 +208,8 @@ export const DropdownPopoverMixin = superclass => class extends LocalizeCoreElem
|
|
|
197
208
|
if (this.#contentElement) {
|
|
198
209
|
this.#resizeObserver.observe(this.#contentElement);
|
|
199
210
|
}
|
|
211
|
+
this.addEventListener('d2l-popover-async-load', this.#handlePopoverAsyncLoad);
|
|
200
212
|
this.addEventListener('d2l-popover-open', this.#handlePopoverOpen);
|
|
201
|
-
this.addEventListener('d2l-popover-open-async', this.#handlePopoverOpenAsync);
|
|
202
213
|
this.addEventListener('d2l-popover-close', this.#handlePopoverClose);
|
|
203
214
|
this.addEventListener('d2l-popover-position', this.#handlePopoverPosition);
|
|
204
215
|
this.addEventListener('d2l-popover-focus-enter', this.#handlePopoverFocusEnter);
|
|
@@ -232,13 +243,16 @@ export const DropdownPopoverMixin = superclass => class extends LocalizeCoreElem
|
|
|
232
243
|
|
|
233
244
|
const closeButtonStyles = this.#getMobileCloseButtonStyles();
|
|
234
245
|
|
|
246
|
+
const isLoading = (this._async && this._asyncState === asyncStates.loading);
|
|
247
|
+
const loading = isLoading ? html`<div class="dropdown-content-loading"><d2l-loading-spinner></d2l-loading-spinner></div>` : nothing;
|
|
248
|
+
|
|
235
249
|
const content = html`
|
|
236
250
|
<div class="dropdown-content-layout" style="${styleMap(contentLayoutStyles)}">
|
|
237
251
|
<div class="${classMap(headerClasses)}">
|
|
238
252
|
<slot name="header" @slotchange="${this.#handleHeaderSlotChange}"></slot>
|
|
239
253
|
</div>
|
|
240
254
|
<div class="${classMap(contentClasses)}" @scroll="${this.#toggleScrollStyles}">
|
|
241
|
-
<slot
|
|
255
|
+
<slot>${loading}</slot>
|
|
242
256
|
</div>
|
|
243
257
|
<div class="${classMap(footerClasses)}">
|
|
244
258
|
<slot name="footer" @slotchange="${this.#handleFooterSlotChange}"></slot>
|
|
@@ -253,12 +267,13 @@ export const DropdownPopoverMixin = superclass => class extends LocalizeCoreElem
|
|
|
253
267
|
}
|
|
254
268
|
|
|
255
269
|
willUpdate(changedProperties) {
|
|
256
|
-
if (changedProperties.has('align') || changedProperties.has('maxHeight') || changedProperties.has('maxWidth') || changedProperties.has('minHeight') || changedProperties.has('minWidth') || changedProperties.has('mobileBreakpointOverride') || changedProperties.has('mobileTray') || changedProperties.has('noAutoClose') || changedProperties.has('noAutoFit') || changedProperties.has('noAutoFocus') || changedProperties.has('noPointer') || changedProperties.has('trapFocus') || changedProperties.has('verticalOffset')) {
|
|
270
|
+
if (changedProperties.has('align') || changedProperties.has('async') || changedProperties.has('maxHeight') || changedProperties.has('maxWidth') || changedProperties.has('minHeight') || changedProperties.has('minWidth') || changedProperties.has('mobileBreakpointOverride') || changedProperties.has('mobileTray') || changedProperties.has('noAutoClose') || changedProperties.has('noAutoFit') || changedProperties.has('noAutoFocus') || changedProperties.has('noPointer') || changedProperties.has('trapFocus') || changedProperties.has('verticalOffset')) {
|
|
257
271
|
super.configure({
|
|
272
|
+
async: this.async,
|
|
258
273
|
maxHeight: this.maxHeight,
|
|
259
274
|
maxWidth: this.maxWidth,
|
|
260
275
|
minHeight: this.minHeight,
|
|
261
|
-
minWidth: this.minWidth,
|
|
276
|
+
minWidth: (this.minWidth === undefined && this.async) ? 88 : this.minWidth, /* loading-spinner + padding */
|
|
262
277
|
mobileBreakpoint: this.mobileBreakpointOverride,
|
|
263
278
|
mobileTrayLocation: this.#adaptMobileTrayLocation(this.mobileTray),
|
|
264
279
|
noAutoClose: this.noAutoClose,
|
|
@@ -356,6 +371,20 @@ export const DropdownPopoverMixin = superclass => class extends LocalizeCoreElem
|
|
|
356
371
|
this._hasHeaderSlotContent = e.target.assignedNodes().length !== 0;
|
|
357
372
|
}
|
|
358
373
|
|
|
374
|
+
#handlePopoverAsyncLoad(e) {
|
|
375
|
+
e.preventDefault();
|
|
376
|
+
e.stopPropagation();
|
|
377
|
+
/** Dispatched before an "async" dropdown is opened for the first time, giving an opportunity to load async content */
|
|
378
|
+
this.dispatchEvent(new CustomEvent(
|
|
379
|
+
'd2l-dropdown-async-load', {
|
|
380
|
+
bubbles: false,
|
|
381
|
+
cancelable: false,
|
|
382
|
+
composed: false,
|
|
383
|
+
detail: { complete: e.detail.complete, reset: e.detail.reset }
|
|
384
|
+
}
|
|
385
|
+
));
|
|
386
|
+
}
|
|
387
|
+
|
|
359
388
|
#handlePopoverClose(e) {
|
|
360
389
|
// ignore popover close events from nested popovers
|
|
361
390
|
if (e.target !== this) return;
|
|
@@ -387,22 +416,6 @@ export const DropdownPopoverMixin = superclass => class extends LocalizeCoreElem
|
|
|
387
416
|
});
|
|
388
417
|
}
|
|
389
418
|
|
|
390
|
-
#handlePopoverOpenAsync(e) {
|
|
391
|
-
const openAsyncEvent = new CustomEvent(
|
|
392
|
-
'd2l-dropdown-open-async', {
|
|
393
|
-
bubbles: false,
|
|
394
|
-
cancelable: true,
|
|
395
|
-
composed: false,
|
|
396
|
-
detail: { complete: e.detail.complete }
|
|
397
|
-
}
|
|
398
|
-
);
|
|
399
|
-
/** @ignore */
|
|
400
|
-
this.dispatchEvent(openAsyncEvent);
|
|
401
|
-
if (openAsyncEvent.defaultPrevented) {
|
|
402
|
-
e.preventDefault();
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
419
|
#handlePopoverPosition() {
|
|
407
420
|
this.#toggleScrollStyles();
|
|
408
421
|
|
|
@@ -7,8 +7,6 @@ import { DropdownPopoverMixin } from './dropdown-popover-mixin.js';
|
|
|
7
7
|
* @slot header - Sticky container at the top of the dropdown
|
|
8
8
|
* @slot footer - Sticky container at the bottom of the dropdown
|
|
9
9
|
* @fires d2l-dropdown-open - Dispatched when the dropdown is opened
|
|
10
|
-
* @fires d2l-dropdown-open-async - Dispatched before the dropdown is opened for the first time, giving an opportunity to load async content
|
|
11
|
-
* @fires d2l-popover-open-async - Ignore
|
|
12
10
|
*/
|
|
13
11
|
class DropdownTabs extends DropdownPopoverMixin(LitElement) {
|
|
14
12
|
|
|
@@ -144,3 +144,7 @@ Within the fieldset, `<d2l-input-group>` can still be used to provide a consiste
|
|
|
144
144
|
* `Default`: Related input components
|
|
145
145
|
* `inline-help`: Help text that will appear below the input. Use this only when other helpful cues are not sufficient, such as a carefully-worded label.
|
|
146
146
|
<!-- docs: end hidden content -->
|
|
147
|
+
|
|
148
|
+
## Custom Validation [validation-custom]
|
|
149
|
+
|
|
150
|
+
To attach custom validation logic to an existing component (native or custom), use [d2l-validation-custom](../../../components/validation).
|
|
@@ -12,6 +12,11 @@ import { styleMap } from 'lit/directives/style-map.js';
|
|
|
12
12
|
import { tryGetIfrauBackdropService } from '../../helpers/ifrauBackdropService.js';
|
|
13
13
|
import { waitForElem } from '../../helpers/internal/waitForElem.js';
|
|
14
14
|
|
|
15
|
+
export const asyncStates = Object.freeze({
|
|
16
|
+
unloaded: 'unloaded',
|
|
17
|
+
loading: 'loading',
|
|
18
|
+
loaded: 'loaded'
|
|
19
|
+
});
|
|
15
20
|
export const positionLocations = Object.freeze({
|
|
16
21
|
blockEnd: 'block-end',
|
|
17
22
|
blockStart: 'block-start',
|
|
@@ -53,6 +58,8 @@ const SCROLLBAR_WIDTH = (() => {
|
|
|
53
58
|
export const PopoverMixin = superclass => class extends superclass {
|
|
54
59
|
|
|
55
60
|
static properties = {
|
|
61
|
+
_async: { type: Boolean },
|
|
62
|
+
_asyncState: { state: true },
|
|
56
63
|
_contentHeight: { state: true },
|
|
57
64
|
_location: { type: String, reflect: true, attribute: '_location' },
|
|
58
65
|
_margin: { state: true },
|
|
@@ -261,6 +268,7 @@ export const PopoverMixin = superclass => class extends superclass {
|
|
|
261
268
|
constructor() {
|
|
262
269
|
super();
|
|
263
270
|
this.configure();
|
|
271
|
+
this._asyncState = asyncStates.unloaded;
|
|
264
272
|
this._mobile = false;
|
|
265
273
|
this._showBackdrop = false;
|
|
266
274
|
this._useNativePopover = isPopoverSupported ? 'manual' : undefined;
|
|
@@ -316,6 +324,7 @@ export const PopoverMixin = superclass => class extends superclass {
|
|
|
316
324
|
else if (properties?.position?.location === positionLocations.inlineStart
|
|
317
325
|
|| properties?.position?.location === positionLocations.inlineEnd) this._margin = 0;
|
|
318
326
|
else this._margin = 18;
|
|
327
|
+
this._async = properties?.async ?? false;
|
|
319
328
|
this._maxHeight = properties?.maxHeight;
|
|
320
329
|
this._maxWidth = properties?.maxWidth;
|
|
321
330
|
this._minHeight = properties?.minHeight;
|
|
@@ -372,7 +381,15 @@ export const PopoverMixin = superclass => class extends superclass {
|
|
|
372
381
|
|
|
373
382
|
this.#addRepositionHandlers();
|
|
374
383
|
|
|
375
|
-
|
|
384
|
+
if (this._async) {
|
|
385
|
+
await this.startAsyncLoad();
|
|
386
|
+
if (!this._opened) return; // could have closed while async loading
|
|
387
|
+
if (this.#firstOpen) {
|
|
388
|
+
this.#firstOpen = false;
|
|
389
|
+
await waitForElem(this.#getContentContainer());
|
|
390
|
+
await this.position();
|
|
391
|
+
}
|
|
392
|
+
}
|
|
376
393
|
|
|
377
394
|
this.#focusContent(this);
|
|
378
395
|
|
|
@@ -579,12 +596,43 @@ export const PopoverMixin = superclass => class extends superclass {
|
|
|
579
596
|
await this.position();
|
|
580
597
|
}
|
|
581
598
|
|
|
599
|
+
async startAsyncLoad() {
|
|
600
|
+
if (!this._async || this._asyncState === asyncStates.loaded) return;
|
|
601
|
+
if (this._asyncState === asyncStates.loading) {
|
|
602
|
+
return this.#asyncLoadPromise;
|
|
603
|
+
}
|
|
604
|
+
this._asyncState = asyncStates.loading;
|
|
605
|
+
|
|
606
|
+
this.#asyncLoadPromise = new Promise(resolve => {
|
|
607
|
+
/** @ignore */
|
|
608
|
+
this.dispatchEvent(new CustomEvent(
|
|
609
|
+
'd2l-popover-async-load', {
|
|
610
|
+
bubbles: false,
|
|
611
|
+
cancelable: true,
|
|
612
|
+
composed: false,
|
|
613
|
+
detail: {
|
|
614
|
+
complete: resolve,
|
|
615
|
+
reset: () => {
|
|
616
|
+
this._asyncState = asyncStates.unloaded;
|
|
617
|
+
this.#firstOpen = true;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
));
|
|
622
|
+
});
|
|
623
|
+
await this.#asyncLoadPromise;
|
|
624
|
+
|
|
625
|
+
this._asyncState = asyncStates.loaded;
|
|
626
|
+
await this.updateComplete;
|
|
627
|
+
}
|
|
628
|
+
|
|
582
629
|
toggleOpen(opener, applyFocus = true) {
|
|
583
630
|
if (this._opened) return this.close();
|
|
584
631
|
else return this.open(opener, (!this._noAutoFocus && applyFocus));
|
|
585
632
|
}
|
|
586
633
|
|
|
587
634
|
#ancestorMutations;
|
|
635
|
+
#asyncLoadPromise;
|
|
588
636
|
#firstOpen = true;
|
|
589
637
|
#ifrauContextInfo;
|
|
590
638
|
#mediaQueryList;
|
|
@@ -1198,32 +1246,4 @@ export const PopoverMixin = superclass => class extends superclass {
|
|
|
1198
1246
|
removeResizeNoopEventListener(this.#handleResize);
|
|
1199
1247
|
}
|
|
1200
1248
|
|
|
1201
|
-
async #waitForOpenAsync() {
|
|
1202
|
-
if (!this.#firstOpen) return;
|
|
1203
|
-
this.#firstOpen = false;
|
|
1204
|
-
|
|
1205
|
-
let doWait = false;
|
|
1206
|
-
await new Promise(resolve => {
|
|
1207
|
-
const openAsyncEvent = new CustomEvent(
|
|
1208
|
-
'd2l-popover-open-async', {
|
|
1209
|
-
bubbles: false,
|
|
1210
|
-
cancelable: true,
|
|
1211
|
-
composed: false,
|
|
1212
|
-
detail: { complete: resolve }
|
|
1213
|
-
}
|
|
1214
|
-
);
|
|
1215
|
-
/** @ignore */
|
|
1216
|
-
this.dispatchEvent(openAsyncEvent);
|
|
1217
|
-
if (!openAsyncEvent.defaultPrevented) {
|
|
1218
|
-
resolve();
|
|
1219
|
-
} else {
|
|
1220
|
-
doWait = true;
|
|
1221
|
-
}
|
|
1222
|
-
});
|
|
1223
|
-
if (doWait) {
|
|
1224
|
-
await waitForElem(this.#getContentContainer());
|
|
1225
|
-
await this.position();
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
1249
|
};
|
|
@@ -55,7 +55,6 @@ let activeTooltip = null;
|
|
|
55
55
|
* @slot - Default content placed inside of the tooltip
|
|
56
56
|
* @fires d2l-tooltip-show - Dispatched when the tooltip is opened
|
|
57
57
|
* @fires d2l-tooltip-hide - Dispatched when the tooltip is closed
|
|
58
|
-
* @fires d2l-popover-open-async - Ignore
|
|
59
58
|
*/
|
|
60
59
|
class Tooltip extends PopoverMixin(LitElement) {
|
|
61
60
|
|
|
@@ -1,25 +1,21 @@
|
|
|
1
1
|
# Validation
|
|
2
|
-
## d2l-validation-custom
|
|
2
|
+
## Validation Custom [d2l-validation-custom]
|
|
3
3
|
|
|
4
|
-
The `d2l-validation-custom` component is used to add custom validation logic to native form elements like `input`, `select` and `textarea` or custom form elements created with the [`FormElementMixin`](
|
|
4
|
+
The `d2l-validation-custom` component is used to add custom validation logic to native form elements like `input`, `select` and `textarea` or custom form elements created with the [`FormElementMixin`](https://github.com/BrightspaceUI/core/blob/main/components/form/docs/form-element-mixin.md).
|
|
5
5
|
|
|
6
6
|
**Native Form Elements:**
|
|
7
|
-
- When attached to native form elements like `input`, `select` and `textarea`, both the `d2l-validation-custom` and native form element **must** be within a [`d2l-form`](
|
|
7
|
+
- When attached to native form elements like `input`, `select` and `textarea`, both the `d2l-validation-custom` and native form element **must** be within a [`d2l-form`](../../components/form-layout-validation) for the validation custom to function.
|
|
8
8
|
|
|
9
9
|
**Custom Form Elements:**
|
|
10
|
-
- When attached to custom form elements created with the [`FormElementMixin`](
|
|
10
|
+
- When attached to custom form elements created with the [`FormElementMixin`](https://github.com/BrightspaceUI/core/blob/main/components/form/docs/form-element-mixin.md), the `d2l-validation-custom` will function even if no [`d2l-form`](../../components/form-layout-validation) is present.
|
|
11
11
|
|
|
12
12
|
**Usage:**
|
|
13
|
+
<!-- docs: demo code properties autoSize:false size:small name:d2l-validation-custom sandboxTitle:'Custom Validation' -->
|
|
13
14
|
```html
|
|
14
15
|
<script type="module">
|
|
16
|
+
import '@brightspace-ui/core/components/inputs/input-text.js';
|
|
15
17
|
import '@brightspace-ui/core/components/validation/validation-custom.js';
|
|
16
|
-
</script>
|
|
17
|
-
|
|
18
|
-
<d2l-input-text id="my-text-input"></d2l-input-text>
|
|
19
|
-
<d2l-validation-custom for="my-text-input" failure-text="My custom error message">
|
|
20
|
-
</d2l-validation-custom>
|
|
21
18
|
|
|
22
|
-
<script>
|
|
23
19
|
const validator = document.querySelector('d2l-validation-custom');
|
|
24
20
|
validator.addEventListener('d2l-validation-custom-validate', e => {
|
|
25
21
|
// Implement your custom validation logic
|
|
@@ -28,8 +24,12 @@ The `d2l-validation-custom` component is used to add custom validation logic to
|
|
|
28
24
|
e.detail.resolve(isValid);
|
|
29
25
|
});
|
|
30
26
|
</script>
|
|
31
|
-
```
|
|
32
27
|
|
|
28
|
+
<d2l-input-text id="my-text-input" label="name" style="max-width: 200px;"></d2l-input-text>
|
|
29
|
+
<d2l-validation-custom for="my-text-input" failure-text="My custom error message">
|
|
30
|
+
</d2l-validation-custom>
|
|
31
|
+
```
|
|
32
|
+
<!-- docs: start hidden content -->
|
|
33
33
|
**Properties:**
|
|
34
34
|
|
|
35
35
|
| Property | Type | Description |
|
|
@@ -42,7 +42,9 @@ The `d2l-validation-custom` component is used to add custom validation logic to
|
|
|
42
42
|
* The form element being validated can be accessed from the `detail`'s `forElement` property.
|
|
43
43
|
* When validation is finished, the `detail`'s `resolve` function **must** be called with a `boolean` value to indicate valid or invalid. If it is not called then validation will never complete and the component will be stuck in a validating state.
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
<!-- docs: end hidden content -->
|
|
46
|
+
|
|
47
|
+
## Validation Custom Mixin [ValidationCustomMixin]
|
|
46
48
|
|
|
47
49
|
If you find yourself duplicating `d2l-validation-custom` validation logic in many places you may want to create your own custom validator using the `ValidationCustomMixin`.
|
|
48
50
|
|
|
@@ -64,7 +66,3 @@ customElements.define('my-validation-custom', MyValidationCustom);
|
|
|
64
66
|
```
|
|
65
67
|
|
|
66
68
|
Once a component has been created using the `ValidationCustomMixin`, it may be used exactly like a `d2l-validation-custom`. However, the `d2l-validation-custom-validate` event isn't needed because the validation logic is included in the component itself.
|
|
67
|
-
|
|
68
|
-
## Future Enhancements
|
|
69
|
-
|
|
70
|
-
Looking for an enhancement not listed here? Create a GitHub issue!
|
|
@@ -3,7 +3,15 @@ import { isCustomFormElement } from '../form/form-helper.js';
|
|
|
3
3
|
export const ValidationCustomMixin = superclass => class extends superclass {
|
|
4
4
|
|
|
5
5
|
static properties = {
|
|
6
|
+
/**
|
|
7
|
+
* REQUIRED: The text to display when validation fails.
|
|
8
|
+
* @type {string}
|
|
9
|
+
*/
|
|
6
10
|
failureText: { type: String, attribute: 'failure-text' },
|
|
11
|
+
/**
|
|
12
|
+
* REQUIRED: The id of the form element to validate.
|
|
13
|
+
* @type {string}
|
|
14
|
+
*/
|
|
7
15
|
for: { type: String }
|
|
8
16
|
};
|
|
9
17
|
|
|
@@ -19,6 +27,7 @@ export const ValidationCustomMixin = superclass => class extends superclass {
|
|
|
19
27
|
connectedCallback() {
|
|
20
28
|
super.connectedCallback();
|
|
21
29
|
this._updateForElement();
|
|
30
|
+
/**@ignore */
|
|
22
31
|
this.dispatchEvent(new CustomEvent('d2l-validation-custom-connected', { bubbles: true }));
|
|
23
32
|
}
|
|
24
33
|
|
|
@@ -28,6 +37,7 @@ export const ValidationCustomMixin = superclass => class extends superclass {
|
|
|
28
37
|
this._forElement.validationCustomDisconnected(this);
|
|
29
38
|
}
|
|
30
39
|
this._forElement = null;
|
|
40
|
+
/**@ignore */
|
|
31
41
|
this.dispatchEvent(new CustomEvent('d2l-validation-custom-disconnected'));
|
|
32
42
|
}
|
|
33
43
|
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
import { css, LitElement } from 'lit';
|
|
3
3
|
import { ValidationCustomMixin } from './validation-custom-mixin.js';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* A component that can be used to provide custom validation for a form element. The component is hidden and does not render any visible content.
|
|
7
|
+
* @fires d2l-validation-custom-validate - Dispatched when the component is validated.
|
|
8
|
+
*/
|
|
5
9
|
class ValidationCustom extends ValidationCustomMixin(LitElement) {
|
|
6
10
|
|
|
7
11
|
static styles = css`
|
package/custom-elements.json
CHANGED
|
@@ -3329,6 +3329,12 @@
|
|
|
3329
3329
|
"description": "Provide custom offset, positive or negative",
|
|
3330
3330
|
"type": "string"
|
|
3331
3331
|
},
|
|
3332
|
+
{
|
|
3333
|
+
"name": "async",
|
|
3334
|
+
"description": "Enable when dropdown contains async content. When set, the dropdown will dispatch a `d2l-dropdown-async-load` event before opening for the first time, giving an opportunity to load async content.",
|
|
3335
|
+
"type": "boolean",
|
|
3336
|
+
"default": "false"
|
|
3337
|
+
},
|
|
3332
3338
|
{
|
|
3333
3339
|
"name": "opened",
|
|
3334
3340
|
"description": "Whether the dropdown is open or not",
|
|
@@ -3439,6 +3445,13 @@
|
|
|
3439
3445
|
"description": "Provide custom offset, positive or negative",
|
|
3440
3446
|
"type": "string"
|
|
3441
3447
|
},
|
|
3448
|
+
{
|
|
3449
|
+
"name": "async",
|
|
3450
|
+
"attribute": "async",
|
|
3451
|
+
"description": "Enable when dropdown contains async content. When set, the dropdown will dispatch a `d2l-dropdown-async-load` event before opening for the first time, giving an opportunity to load async content.",
|
|
3452
|
+
"type": "boolean",
|
|
3453
|
+
"default": "false"
|
|
3454
|
+
},
|
|
3442
3455
|
{
|
|
3443
3456
|
"name": "opened",
|
|
3444
3457
|
"attribute": "opened",
|
|
@@ -3514,12 +3527,8 @@
|
|
|
3514
3527
|
"description": "Dispatched when the dropdown is opened"
|
|
3515
3528
|
},
|
|
3516
3529
|
{
|
|
3517
|
-
"name": "d2l-dropdown-
|
|
3518
|
-
"description": "Dispatched before
|
|
3519
|
-
},
|
|
3520
|
-
{
|
|
3521
|
-
"name": "d2l-popover-open-async",
|
|
3522
|
-
"description": "Ignore"
|
|
3530
|
+
"name": "d2l-dropdown-async-load",
|
|
3531
|
+
"description": "Dispatched before an \"async\" dropdown is opened for the first time, giving an opportunity to load async content"
|
|
3523
3532
|
},
|
|
3524
3533
|
{
|
|
3525
3534
|
"name": "d2l-dropdown-close",
|
|
@@ -3708,6 +3717,12 @@
|
|
|
3708
3717
|
"description": "Provide custom offset, positive or negative",
|
|
3709
3718
|
"type": "string"
|
|
3710
3719
|
},
|
|
3720
|
+
{
|
|
3721
|
+
"name": "async",
|
|
3722
|
+
"description": "Enable when dropdown contains async content. When set, the dropdown will dispatch a `d2l-dropdown-async-load` event before opening for the first time, giving an opportunity to load async content.",
|
|
3723
|
+
"type": "boolean",
|
|
3724
|
+
"default": "false"
|
|
3725
|
+
},
|
|
3711
3726
|
{
|
|
3712
3727
|
"name": "opened",
|
|
3713
3728
|
"description": "Whether the dropdown is open or not",
|
|
@@ -3818,6 +3833,13 @@
|
|
|
3818
3833
|
"description": "Provide custom offset, positive or negative",
|
|
3819
3834
|
"type": "string"
|
|
3820
3835
|
},
|
|
3836
|
+
{
|
|
3837
|
+
"name": "async",
|
|
3838
|
+
"attribute": "async",
|
|
3839
|
+
"description": "Enable when dropdown contains async content. When set, the dropdown will dispatch a `d2l-dropdown-async-load` event before opening for the first time, giving an opportunity to load async content.",
|
|
3840
|
+
"type": "boolean",
|
|
3841
|
+
"default": "false"
|
|
3842
|
+
},
|
|
3821
3843
|
{
|
|
3822
3844
|
"name": "opened",
|
|
3823
3845
|
"attribute": "opened",
|
|
@@ -3893,12 +3915,8 @@
|
|
|
3893
3915
|
"description": "Dispatched when the dropdown is opened"
|
|
3894
3916
|
},
|
|
3895
3917
|
{
|
|
3896
|
-
"name": "d2l-dropdown-
|
|
3897
|
-
"description": "Dispatched before
|
|
3898
|
-
},
|
|
3899
|
-
{
|
|
3900
|
-
"name": "d2l-popover-open-async",
|
|
3901
|
-
"description": "Ignore"
|
|
3918
|
+
"name": "d2l-dropdown-async-load",
|
|
3919
|
+
"description": "Dispatched before an \"async\" dropdown is opened for the first time, giving an opportunity to load async content"
|
|
3902
3920
|
},
|
|
3903
3921
|
{
|
|
3904
3922
|
"name": "d2l-dropdown-close",
|
|
@@ -4087,6 +4105,12 @@
|
|
|
4087
4105
|
"description": "Provide custom offset, positive or negative",
|
|
4088
4106
|
"type": "string"
|
|
4089
4107
|
},
|
|
4108
|
+
{
|
|
4109
|
+
"name": "async",
|
|
4110
|
+
"description": "Enable when dropdown contains async content. When set, the dropdown will dispatch a `d2l-dropdown-async-load` event before opening for the first time, giving an opportunity to load async content.",
|
|
4111
|
+
"type": "boolean",
|
|
4112
|
+
"default": "false"
|
|
4113
|
+
},
|
|
4090
4114
|
{
|
|
4091
4115
|
"name": "opened",
|
|
4092
4116
|
"description": "Whether the dropdown is open or not",
|
|
@@ -4197,6 +4221,13 @@
|
|
|
4197
4221
|
"description": "Provide custom offset, positive or negative",
|
|
4198
4222
|
"type": "string"
|
|
4199
4223
|
},
|
|
4224
|
+
{
|
|
4225
|
+
"name": "async",
|
|
4226
|
+
"attribute": "async",
|
|
4227
|
+
"description": "Enable when dropdown contains async content. When set, the dropdown will dispatch a `d2l-dropdown-async-load` event before opening for the first time, giving an opportunity to load async content.",
|
|
4228
|
+
"type": "boolean",
|
|
4229
|
+
"default": "false"
|
|
4230
|
+
},
|
|
4200
4231
|
{
|
|
4201
4232
|
"name": "opened",
|
|
4202
4233
|
"attribute": "opened",
|
|
@@ -4272,12 +4303,8 @@
|
|
|
4272
4303
|
"description": "Dispatched when the dropdown is opened"
|
|
4273
4304
|
},
|
|
4274
4305
|
{
|
|
4275
|
-
"name": "d2l-dropdown-
|
|
4276
|
-
"description": "Dispatched before
|
|
4277
|
-
},
|
|
4278
|
-
{
|
|
4279
|
-
"name": "d2l-popover-open-async",
|
|
4280
|
-
"description": "Ignore"
|
|
4306
|
+
"name": "d2l-dropdown-async-load",
|
|
4307
|
+
"description": "Dispatched before an \"async\" dropdown is opened for the first time, giving an opportunity to load async content"
|
|
4281
4308
|
},
|
|
4282
4309
|
{
|
|
4283
4310
|
"name": "d2l-dropdown-close",
|
|
@@ -13803,6 +13830,11 @@
|
|
|
13803
13830
|
"description": "Position the popover to span from the opener edge to this grid line. Default is \"all\" (centered).",
|
|
13804
13831
|
"type": "'start'|'end'|'all'"
|
|
13805
13832
|
},
|
|
13833
|
+
{
|
|
13834
|
+
"name": "async",
|
|
13835
|
+
"type": "boolean",
|
|
13836
|
+
"default": "false"
|
|
13837
|
+
},
|
|
13806
13838
|
{
|
|
13807
13839
|
"name": "no-auto-close",
|
|
13808
13840
|
"description": "Whether to disable auto-close/light-dismiss",
|
|
@@ -13881,6 +13913,12 @@
|
|
|
13881
13913
|
"name": "opened",
|
|
13882
13914
|
"type": "boolean | undefined"
|
|
13883
13915
|
},
|
|
13916
|
+
{
|
|
13917
|
+
"name": "async",
|
|
13918
|
+
"attribute": "async",
|
|
13919
|
+
"type": "boolean",
|
|
13920
|
+
"default": "false"
|
|
13921
|
+
},
|
|
13884
13922
|
{
|
|
13885
13923
|
"name": "noAutoClose",
|
|
13886
13924
|
"attribute": "no-auto-close",
|
|
@@ -13914,11 +13952,6 @@
|
|
|
13914
13952
|
"type": "array",
|
|
13915
13953
|
"default": "[\"styles\",null]"
|
|
13916
13954
|
}
|
|
13917
|
-
],
|
|
13918
|
-
"events": [
|
|
13919
|
-
{
|
|
13920
|
-
"name": "d2l-popover-open-async"
|
|
13921
|
-
}
|
|
13922
13955
|
]
|
|
13923
13956
|
},
|
|
13924
13957
|
{
|
|
@@ -17342,10 +17375,6 @@
|
|
|
17342
17375
|
{
|
|
17343
17376
|
"name": "d2l-tooltip-hide",
|
|
17344
17377
|
"description": "Dispatched when the tooltip is closed"
|
|
17345
|
-
},
|
|
17346
|
-
{
|
|
17347
|
-
"name": "d2l-popover-open-async",
|
|
17348
|
-
"description": "Ignore"
|
|
17349
17378
|
}
|
|
17350
17379
|
],
|
|
17351
17380
|
"slots": [
|
|
@@ -17358,13 +17387,16 @@
|
|
|
17358
17387
|
{
|
|
17359
17388
|
"name": "d2l-validation-custom",
|
|
17360
17389
|
"path": "./components/validation/validation-custom.js",
|
|
17390
|
+
"description": "A component that can be used to provide custom validation for a form element. The component is hidden and does not render any visible content.",
|
|
17361
17391
|
"attributes": [
|
|
17362
17392
|
{
|
|
17363
17393
|
"name": "failure-text",
|
|
17394
|
+
"description": "REQUIRED: The text to display when validation fails.",
|
|
17364
17395
|
"type": "string"
|
|
17365
17396
|
},
|
|
17366
17397
|
{
|
|
17367
17398
|
"name": "for",
|
|
17399
|
+
"description": "REQUIRED: The id of the form element to validate.",
|
|
17368
17400
|
"type": "string"
|
|
17369
17401
|
}
|
|
17370
17402
|
],
|
|
@@ -17376,11 +17408,13 @@
|
|
|
17376
17408
|
{
|
|
17377
17409
|
"name": "failureText",
|
|
17378
17410
|
"attribute": "failure-text",
|
|
17411
|
+
"description": "REQUIRED: The text to display when validation fails.",
|
|
17379
17412
|
"type": "string"
|
|
17380
17413
|
},
|
|
17381
17414
|
{
|
|
17382
17415
|
"name": "for",
|
|
17383
17416
|
"attribute": "for",
|
|
17417
|
+
"description": "REQUIRED: The id of the form element to validate.",
|
|
17384
17418
|
"type": "string"
|
|
17385
17419
|
},
|
|
17386
17420
|
{
|
|
@@ -17389,13 +17423,8 @@
|
|
|
17389
17423
|
],
|
|
17390
17424
|
"events": [
|
|
17391
17425
|
{
|
|
17392
|
-
"name": "d2l-validation-custom-validate"
|
|
17393
|
-
|
|
17394
|
-
{
|
|
17395
|
-
"name": "d2l-validation-custom-connected"
|
|
17396
|
-
},
|
|
17397
|
-
{
|
|
17398
|
-
"name": "d2l-validation-custom-disconnected"
|
|
17426
|
+
"name": "d2l-validation-custom-validate",
|
|
17427
|
+
"description": "Dispatched when the component is validated."
|
|
17399
17428
|
}
|
|
17400
17429
|
]
|
|
17401
17430
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brightspace-ui/core",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.302.0",
|
|
4
4
|
"description": "A collection of accessible, free, open-source web components for building Brightspace applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": "https://github.com/BrightspaceUI/core.git",
|