@brightspace-ui/core 3.41.1 → 3.43.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -22,7 +22,7 @@
22
22
  <d2l-demo-snippet>
23
23
  <template>
24
24
  <d2l-button id="openConfirm">Show Confirm</d2l-button>
25
- <d2l-dialog-confirm id="confirm" text="Are you sure you want more cookies?">
25
+ <d2l-dialog-confirm id="confirm" title-text="Confirm Title" text="Are you sure you want more cookies?">
26
26
  <d2l-button slot="footer" primary data-dialog-action="ok">Yes</d2l-button>
27
27
  <d2l-button slot="footer" data-dialog-action>No</d2l-button>
28
28
  </d2l-dialog-confirm>
@@ -247,6 +247,32 @@
247
247
  </template>
248
248
  </d2l-demo-snippet>
249
249
 
250
+ <h2>Dialog (overflowing content)</h2>
251
+
252
+ <d2l-demo-snippet>
253
+ <template>
254
+ <d2l-button id="openScrollFocus">Show Dialog</d2l-button>
255
+ <d2l-dialog title-text="My Dialog" id="dialogScrollFocus">
256
+ <p>Deadlights jack lad schooner scallywag dance the hempen jig carouser broadside cable strike colors. Bring a spring upon her cable holystone blow the man down spanker</p>
257
+ <p>Shiver me timbers to go on account lookout wherry doubloon chase. Belay yo-ho-ho keelhaul squiffy black spot yardarm spyglass sheet transom heave to.</p>
258
+ <p>Trysail Sail ho Corsair red ensign hulk smartly boom jib rum gangway. Case shot Shiver me timbers gangplank crack Jennys tea cup ballast Blimey lee snow crow's nest rutters. Fluke jib scourge of the seven seas boatswain schooner gaff booty Jack Tar transom spirits.</p>
259
+ <p>Deadlights jack lad schooner scallywag dance the hempen jig carouser broadside cable strike colors. Bring a spring upon her cable holystone blow the man down spanker</p>
260
+ <p>Shiver me timbers to go on account lookout wherry doubloon chase. Belay yo-ho-ho keelhaul squiffy black spot yardarm spyglass sheet transom heave to.</p>
261
+ <p>Trysail Sail ho Corsair red ensign hulk smartly boom jib rum gangway. Case shot Shiver me timbers gangplank crack Jennys tea cup ballast Blimey lee snow crow's nest rutters. Fluke jib scourge of the seven seas boatswain schooner gaff booty Jack Tar transom spirits.</p>
262
+ <p>Deadlights jack lad schooner scallywag dance the hempen jig carouser broadside cable strike colors. Bring a spring upon her cable holystone blow the man down spanker</p>
263
+ <p>Shiver me timbers to go on account lookout wherry doubloon chase. Belay yo-ho-ho keelhaul squiffy black spot yardarm spyglass sheet transom heave to.</p>
264
+ <p>Trysail Sail ho Corsair red ensign hulk smartly boom jib rum gangway. Case shot Shiver me timbers gangplank crack Jennys tea cup ballast Blimey lee snow crow's nest rutters. Fluke jib scourge of the seven seas boatswain schooner gaff booty Jack Tar transom spirits.</p>
265
+ <d2l-button slot="footer" primary data-dialog-action="ok">Click Me!</d2l-button>
266
+ <d2l-button slot="footer" data-dialog-action>Cancel</d2l-button>
267
+ </d2l-dialog>
268
+ <script>
269
+ document.querySelector('#openScrollFocus').addEventListener('click', () => {
270
+ document.querySelector('#dialogScrollFocus').opened = true;
271
+ });
272
+ </script>
273
+ </template>
274
+ </d2l-demo-snippet>
275
+
250
276
  </d2l-demo-page>
251
277
  </body>
252
278
  </html>
@@ -3,6 +3,7 @@ import { css, html, LitElement, nothing } from 'lit';
3
3
  import { DialogMixin } from './dialog-mixin.js';
4
4
  import { dialogStyles } from './dialog-styles.js';
5
5
  import { getUniqueId } from '../../helpers/uniqueId.js';
6
+ import { ifDefined } from 'lit/directives/if-defined.js';
6
7
  import { LocalizeCoreElement } from '../../helpers/localize-core-element.js';
7
8
 
8
9
  /**
@@ -81,6 +82,7 @@ class DialogConfirm extends LocalizeCoreElement(DialogMixin(LitElement)) {
81
82
  }
82
83
 
83
84
  render() {
85
+ const contentTabIndex = !this.focusableContentElemPresent ? '0' : undefined;
84
86
  const inner = html`
85
87
  ${this.critical ? html`<div id="${this._criticalLabelId}" hidden>${this.localize('components.dialog.critical')}</div>` : nothing}
86
88
  <div class="d2l-dialog-inner">
@@ -88,7 +90,7 @@ class DialogConfirm extends LocalizeCoreElement(DialogMixin(LitElement)) {
88
90
  <div class="d2l-dialog-header">
89
91
  <div><h2 id="${this._titleId}" class="d2l-heading-3">${this.titleText}</h2></div>
90
92
  </div>` : null}
91
- <div id="${this._textId}" class="d2l-dialog-content">
93
+ <div id="${this._textId}" class="d2l-dialog-content" tabindex="${ifDefined(contentTabIndex)}">
92
94
  <div>${this.text ? this.text.split('\n').map(line => html`<p>${line}</p>`) : null}</div>
93
95
  </div>
94
96
  <div class="d2l-dialog-footer">
@@ -7,6 +7,7 @@ import { classMap } from 'lit/directives/class-map.js';
7
7
  import { DialogMixin } from './dialog-mixin.js';
8
8
  import { dialogStyles } from './dialog-styles.js';
9
9
  import { getUniqueId } from '../../helpers/uniqueId.js';
10
+ import { ifDefined } from 'lit/directives/if-defined.js';
10
11
  import { LocalizeCoreElement } from '../../helpers/localize-core-element.js';
11
12
  import { PropertyRequiredMixin } from '../../mixins/property-required/property-required-mixin.js';
12
13
  import { styleMap } from 'lit/directives/style-map.js';
@@ -256,6 +257,8 @@ class DialogFullscreen extends PropertyRequiredMixin(LocalizeCoreElement(AsyncCo
256
257
  <div style=${styleMap(slotStyles)}><slot></slot></div>
257
258
  `;
258
259
 
260
+ const contentTabIndex = !this.focusableContentElemPresent ? '0' : undefined;
261
+
259
262
  const inner = html`
260
263
  <div class="d2l-dialog-inner" style=${styleMap(heightOverride)}>
261
264
  <div class="d2l-dialog-header">
@@ -264,7 +267,7 @@ class DialogFullscreen extends PropertyRequiredMixin(LocalizeCoreElement(AsyncCo
264
267
  <d2l-button-icon icon="${this._icon}" text="${this.localize('components.dialog.close')}" @click="${this._abort}"></d2l-button-icon>
265
268
  </div>
266
269
  </div>
267
- <div class="d2l-dialog-content" @pending-state="${this._handleAsyncItemState}">${content}</div>
270
+ <div class="d2l-dialog-content" @pending-state="${this._handleAsyncItemState}" tabindex="${ifDefined(contentTabIndex)}">${content}</div>
268
271
  <div class="${classMap(footerClasses)}">
269
272
  <slot name="footer" @slotchange="${this._handleFooterSlotChange}"></slot>
270
273
  </div>
@@ -33,6 +33,10 @@ export const DialogMixin = superclass => class extends RtlMixin(superclass) {
33
33
 
34
34
  static get properties() {
35
35
  return {
36
+ /**
37
+ * @ignore
38
+ */
39
+ focusableContentElemPresent: { state: true },
36
40
  /**
37
41
  * Whether or not the dialog is open
38
42
  */
@@ -64,6 +68,7 @@ export const DialogMixin = superclass => class extends RtlMixin(superclass) {
64
68
 
65
69
  constructor() {
66
70
  super();
71
+ this.focusableContentElemPresent = false;
67
72
  this.opened = false;
68
73
  this._autoSize = true;
69
74
  this._dialogId = getUniqueId();
@@ -71,6 +76,7 @@ export const DialogMixin = superclass => class extends RtlMixin(superclass) {
71
76
  this._handleDropdownOpenClose = this._handleDropdownOpenClose.bind(this);
72
77
  this._handleMvcDialogOpen = this._handleMvcDialogOpen.bind(this);
73
78
  this._inIframe = false;
79
+ this._isDialogMixin = true;
74
80
  this._isFullHeight = false;
75
81
  this._height = 0;
76
82
  this._left = 0;
@@ -219,6 +225,9 @@ export const DialogMixin = superclass => class extends RtlMixin(superclass) {
219
225
  const content = this.shadowRoot.querySelector('.d2l-dialog-content');
220
226
  if (content) {
221
227
  const elementToFocus = this._findAutofocusElement(content) ?? getNextFocusable(content);
228
+ if (isComposedAncestor(this.shadowRoot.querySelector('.d2l-dialog-content'), elementToFocus)) {
229
+ this.focusableContentElemPresent = true;
230
+ }
222
231
  if (isComposedAncestor(this.shadowRoot.querySelector('.d2l-dialog-inner'), elementToFocus)) {
223
232
  this._focusElemOrDescendant(elementToFocus);
224
233
  return;
@@ -132,6 +132,12 @@ export const dialogStyles = css`
132
132
  padding: 0 30px;
133
133
  }
134
134
 
135
+ .d2l-dialog-content:focus-visible {
136
+ border-radius: 6px;
137
+ outline: 2px solid var(--d2l-color-celestine);
138
+ outline-offset: -2px;
139
+ }
140
+
135
141
  .d2l-dialog-content > div {
136
142
  position: relative; /* make this the positioned parent for absolute positioned elements like d2l-template-primary-secondary */
137
143
  }
@@ -170,6 +170,7 @@ class Dialog extends PropertyRequiredMixin(LocalizeCoreElement(AsyncContainerMix
170
170
  <div id="${ifDefined(this._textId)}" style=${styleMap(slotStyles)}><slot></slot></div>
171
171
  `;
172
172
 
173
+ const contentTabIndex = !this.focusableContentElemPresent ? '0' : undefined;
173
174
  const labelId = this.critical ? `${this._criticalLabelId} ${this._titleId}` : this._titleId;
174
175
  const inner = html`
175
176
  ${this.critical ? html`<div id="${this._criticalLabelId}" hidden>${this.localize('components.dialog.critical')}</div>` : nothing}
@@ -180,7 +181,7 @@ class Dialog extends PropertyRequiredMixin(LocalizeCoreElement(AsyncContainerMix
180
181
  <d2l-button-icon icon="tier1:close-small" text="${this.localize('components.dialog.close')}" @click="${this._abort}"></d2l-button-icon>
181
182
  </div>
182
183
  </div>
183
- <div class="d2l-dialog-content" @pending-state="${this._handleAsyncItemState}">${content}</div>
184
+ <div class="d2l-dialog-content" @pending-state="${this._handleAsyncItemState}" tabindex="${ifDefined(contentTabIndex)}">${content}</div>
184
185
  <div class="${classMap(footerClasses)}">
185
186
  <slot name="footer" @slotchange="${this._handleFooterSlotChange}"></slot>
186
187
  </div>
@@ -71,6 +71,7 @@ class FormNestedDemo extends LitElement {
71
71
  </div>
72
72
  <d2l-floating-buttons always-float>
73
73
  <d2l-button primary @click=${this._submit}>Save</d2l-button>
74
+ <d2l-button @click=${this._reset}>Reset</d2l-button>
74
75
  </d2l-floating-buttons>
75
76
  </d2l-form>
76
77
  `;
@@ -85,6 +86,10 @@ class FormNestedDemo extends LitElement {
85
86
  e.preventDefault();
86
87
  }
87
88
 
89
+ _reset() {
90
+ if (this.shadowRoot) this.shadowRoot.querySelector('#root').resetValidation();
91
+ }
92
+
88
93
  _submit() {
89
94
  if (this.shadowRoot) this.shadowRoot.querySelector('#root').submit();
90
95
  }
@@ -18,6 +18,7 @@ invalid state.
18
18
  | `name` | String | The name of the form control. Submitted with the form as part of a name/value pair. |
19
19
 
20
20
  **Methods:**
21
+ - `resetValidation()`: Resets any validation errors on the form element. Note that this does not reset any form element value.
21
22
  - `setFormValue(value)`: Sets the current value of the form control. Submitted with the form as part of a name/value pair. `value` may be a:
22
23
  1. An `Object`: `{ 'key1': val, 'key2': val }`
23
24
  - When an `Object` is provided, all keys-value pairs will be submitted. To avoid collision it is recommended that you prefix each key with the component's `name` property value.
@@ -114,6 +114,7 @@ If you're looking to emulate native form element submission, `d2l-form-native` m
114
114
  ### Methods
115
115
  - `submit()`: Submits the form. This will first perform validation on all elements within the form including nested `d2l-form` elements.
116
116
  - **Note:** If validation succeeds, the form data will be aggregated and passed back to the caller via the `d2l-form-submit` event. It will not be submitted by the form itself.
117
+ - `resetValidation()`: Resets the validation errors. Note that this does not reset any form values. Daylight dialog components (e.g., `d2l-dialog`) will automatically run this method on close.
117
118
  - `async validate()`: Validates the form and any nested `d2l-form` elements without submitting even if validation succeeds for all elements. Returns a `Map` mapping from an element to the list of error messages associated with it.
118
119
  - **Note:** The return value will include elements and errors from both the root form and any nested descendant forms.
119
120
 
@@ -227,6 +227,18 @@ export const FormElementMixin = superclass => class extends LocalizeCoreElement(
227
227
  await this.updatedComplete;
228
228
  }
229
229
 
230
+ resetValidation() {
231
+ this.invalid = false;
232
+ this.validationError = null;
233
+ this._errors = [];
234
+
235
+ this.childErrors.forEach((_, ele) => {
236
+ if (!isCustomFormElement(ele)) return;
237
+ ele.resetValidation();
238
+ });
239
+ this.childErrors = new Map();
240
+ }
241
+
230
242
  setFormValue(formValue) {
231
243
  this.formValue = formValue;
232
244
  }
@@ -1,5 +1,6 @@
1
1
  import { css, html, LitElement } from 'lit';
2
2
  import { findFormElements, flattenMap, getFormElementData, isCustomFormElement, isNativeFormElement } from './form-helper.js';
3
+ import { findComposedAncestor } from '../../helpers/dom.js';
3
4
  import { FormMixin } from './form-mixin.js';
4
5
 
5
6
  /**
@@ -55,6 +56,12 @@ class Form extends FormMixin(LitElement) {
55
56
  this._isSubForm = false;
56
57
  }
57
58
 
59
+ firstUpdated(changedProperties) {
60
+ super.firstUpdated(changedProperties);
61
+
62
+ this._setupDialogValidationReset();
63
+ }
64
+
58
65
  render() {
59
66
  let errorSummary = null;
60
67
  if (this._isRootForm()) {
@@ -77,6 +84,26 @@ class Form extends FormMixin(LitElement) {
77
84
  this._submitData(submitter);
78
85
  }
79
86
 
87
+ resetValidation() {
88
+ const formElements = this._findFormElements();
89
+ for (const ele of formElements) {
90
+ if (this._hasSubForms(ele)) {
91
+ const forms = this._getSubForms(ele);
92
+ for (const form of forms) {
93
+ form.resetValidation();
94
+ }
95
+ } else {
96
+ if (isCustomFormElement(ele)) {
97
+ ele.resetValidation();
98
+ } else if (isNativeFormElement(ele)) {
99
+ this._displayValid(ele);
100
+ }
101
+ }
102
+ }
103
+ this._errors = new Map();
104
+ this._tooltips = new Map();
105
+ }
106
+
80
107
  async submit() {
81
108
  return this.requestSubmit(null);
82
109
  }
@@ -165,6 +192,21 @@ class Form extends FormMixin(LitElement) {
165
192
 
166
193
  }
167
194
 
195
+ _setupDialogValidationReset() {
196
+ const flag = window.D2L?.LP?.Web?.UI?.Flags.Flag('GAUD-6979-dialog-close-reset-validation', true) ?? true;
197
+ if (!flag) return;
198
+
199
+ const dialogAncestor = findComposedAncestor(
200
+ this,
201
+ node => node?._isDialogMixin
202
+ );
203
+ if (!dialogAncestor) return;
204
+
205
+ dialogAncestor.addEventListener('d2l-dialog-close', () => {
206
+ this.resetValidation();
207
+ });
208
+ }
209
+
168
210
  async _submitData(submitter) {
169
211
  this._dirty = false;
170
212
 
@@ -2008,6 +2008,11 @@
2008
2008
  "description": "The optional title for the dialog",
2009
2009
  "type": "string"
2010
2010
  },
2011
+ {
2012
+ "name": "focusableContentElemPresent",
2013
+ "type": "boolean",
2014
+ "default": "false"
2015
+ },
2011
2016
  {
2012
2017
  "name": "opened",
2013
2018
  "attribute": "opened",
@@ -2109,6 +2114,11 @@
2109
2114
  "description": "REQUIRED: the title for the dialog",
2110
2115
  "type": "string"
2111
2116
  },
2117
+ {
2118
+ "name": "focusableContentElemPresent",
2119
+ "type": "boolean",
2120
+ "default": "false"
2121
+ },
2112
2122
  {
2113
2123
  "name": "opened",
2114
2124
  "attribute": "opened",
@@ -2240,6 +2250,11 @@
2240
2250
  "description": "REQUIRED: the title for the dialog",
2241
2251
  "type": "string"
2242
2252
  },
2253
+ {
2254
+ "name": "focusableContentElemPresent",
2255
+ "type": "boolean",
2256
+ "default": "false"
2257
+ },
2243
2258
  {
2244
2259
  "name": "opened",
2245
2260
  "attribute": "opened",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightspace-ui/core",
3
- "version": "3.41.1",
3
+ "version": "3.43.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",