@brightspace-ui/core 3.227.8 → 3.227.9

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.
@@ -5,6 +5,14 @@ import '../dropdown/dropdown.js';
5
5
  import '../dropdown/dropdown-content.js';
6
6
  import { css, html, LitElement } from 'lit';
7
7
 
8
+ function setIndent(text, indent = 0, skipFirstLine = false) {
9
+ const lines = text.split('\n');
10
+ const indentedLines = lines.filter((l, i) => l.trim() !== '' && !(skipFirstLine && i === 0));
11
+ const minIndent = Math.min(...indentedLines.map(l => l.match(/^\s*/)[0].length));
12
+
13
+ return lines.map((l, i) => `${' '.repeat(indent)}${skipFirstLine && i === 0 ? l : l.substring(minIndent)}`).join('\n');
14
+ }
15
+
8
16
  class DemoSnippet extends LitElement {
9
17
 
10
18
  static get properties() {
@@ -27,7 +35,7 @@ class DemoSnippet extends LitElement {
27
35
  background-color: var(--d2l-theme-background-color-base);
28
36
  border: 1px solid var(--d2l-theme-border-color-standard);
29
37
  border-radius: 6px;
30
- box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
38
+ box-shadow: var(--d2l-theme-shadow-floating);
31
39
  display: block;
32
40
  max-width: 900px;
33
41
  }
@@ -100,13 +108,14 @@ class DemoSnippet extends LitElement {
100
108
  border: none;
101
109
  border-top-left-radius: 0;
102
110
  border-top-right-radius: 0;
111
+ box-shadow: none;
103
112
  margin: 0;
104
113
  max-width: 100%;
105
114
  }
106
115
  :host([code-view-hidden]) d2l-code-view {
107
116
  display: none;
108
117
  }
109
- `;
118
+ `;
110
119
  }
111
120
 
112
121
  constructor() {
@@ -152,73 +161,39 @@ class DemoSnippet extends LitElement {
152
161
  });
153
162
  }
154
163
 
155
- _applyAttr(name, value, applyToShadowRoot) {
156
- const query = this._isTemplate ? 'slot[name="_demo"]' : 'slot:not([name="_demo"])';
157
- if (!this.shadowRoot) return;
158
- const nodes = this.shadowRoot.querySelector(query).assignedNodes();
159
- if (nodes.length === 0) return;
160
- const doApply = (nodes, isRoot) => {
161
- for (let i = 0; i < nodes.length; i++) {
162
- if (nodes[i].nodeType === Node.ELEMENT_NODE) {
163
- if (isRoot || nodes[i].tagName.indexOf('-') !== -1) {
164
- if (typeof(value) === 'boolean') {
165
- if (value) {
166
- nodes[i].setAttribute(name, name);
167
- } else {
168
- nodes[i].removeAttribute(name);
169
- }
170
- } else {
171
- nodes[i].setAttribute(name, value);
172
- }
173
- }
174
- if (applyToShadowRoot && nodes[i].shadowRoot) {
175
- doApply(nodes[i].shadowRoot.children, false);
176
- }
177
- doApply(nodes[i].children, false);
178
- }
179
- }
180
- };
181
- doApply(nodes, true);
182
- }
183
-
184
164
  _formatCode(text) {
185
165
 
186
166
  if (!text) return text;
187
167
 
188
168
  // remove the leading and trailing template tags
189
- text = text.replace(/^[\t]*\n/, '').replace(/\n[\t]*$/, '');
190
- const templateMatch = text.match(/^[\t]*<template>[\n]*/);
191
- this._isTemplate = templateMatch && templateMatch.length > 0;
169
+ text = text.replace(/^(\t*\n)*/, '').replace(/(\n\t*)*$/, '');
170
+ this._isTemplate = /^\s*<template>/.test(text);
192
171
  text = text.replace(/^[\t]*<template>[\n]*/, '').replace(/[\n]*[\t]*<\/template>$/, '');
193
172
 
194
- // fix script whitespace (for some reason brower keeps <script> indent but not the rest)
195
- let lines = text
196
- .replace(/\t/g, ' ')
197
- .replace(/<\/script>/g, '\n</script>')
198
- .replace(/<script>/g, '<script>\n')
199
- .replace(/<script type="module">/g, '<script type="module">\n')
200
- .replace(/<script data-demo-hide(.+?)<\/script>/gis, '')
201
- .split('\n');
202
- let scriptIndent = 0;
203
- lines = lines.map((l) => {
204
- if (l.indexOf('<script') > -1) {
205
- scriptIndent = l.match(/^(\s*)/)[0].length;
206
- return l;
207
- } else if (l.indexOf('</script>') > -1) {
208
- const nl = this._repeat(' ', scriptIndent) + l ;
209
- scriptIndent = 0;
210
- return nl;
211
- } else if (scriptIndent && !this._isTemplate) {
212
- return this._repeat(' ', scriptIndent + 2) + l;
213
- } else {
214
- return l;
215
- }
216
- });
173
+ // fix script whitespace
174
+ text = setIndent(text.replace(/\t/g, ' '))
175
+ .replace(/( *)<script( type="module")?>([^\n]+?)<\/script>/g, '$1<script$2>\n$1 $3\n$1</script>') // convert single line scripts to multi-line
176
+ .replace(/( *)<\/script>/g, '\n$1</script>')
177
+ .replace(/<script( type="module")?>/g, '<script$1>\n')
178
+ .replace(/(\n *)?<script data-demo-hide(.+?)<\/script>/gis, '');
179
+
180
+ const startTags = new Set([...text.matchAll(/<[^/](.*?)>/g)].map(m => m[0]));
181
+ for (const tag of startTags) {
182
+ const formattedTag = tag
183
+ .replace(/ class=""/g, '') // replace empty class attributes (class="")
184
+ .replace(/\s+_[^\s/>"'=]*(=(?<q>['"]).*?(?<!\\)\k<q>)?/g, '') // replace private reflected properties (_attr, _attr="value", but not target="_blank")
185
+ .replace(/=""/g, ''); // replace empty strings for boolean attributes (="")
186
+ text = text.replace(tag, formattedTag);
187
+ }
188
+
189
+ return text;
217
190
 
218
- return lines.join('\n')
219
- .replace(/ class=""/g, '') // replace empty class attributes (class="")
220
- .replace(/\s+_[^\s/>"'=]*(=(?<q>['"]).*?(?<!\\)\k<q>)?/g, '') // replace private reflected properties (_attr, _attr="value", but not target="_blank")
221
- .replace(/=""/g, ''); // replace empty strings for boolean attributes (="")
191
+ }
192
+
193
+ _getDemoNodes() {
194
+ const query = this._isTemplate ? '[slot="_demo"], [slot="_demo"] *' : '*';
195
+ const elements = Array.from(this.querySelectorAll(query));
196
+ return elements;
222
197
  }
223
198
 
224
199
  async _handleFullscreenChange(e) {
@@ -233,27 +208,31 @@ class DemoSnippet extends LitElement {
233
208
 
234
209
  _handleSkeletonChange(e) {
235
210
  this._skeletonOn = e.target.on;
236
- this._applyAttr('skeleton', this._skeletonOn, false);
211
+ const nodes = this._getDemoNodes();
212
+ for (const node of nodes) {
213
+ if (node.nodeType !== Node.ELEMENT_NODE) continue;
214
+ if (node.tagName.indexOf('-') === -1) continue;
215
+ if (this._skeletonOn) {
216
+ node.setAttribute('skeleton', '');
217
+ } else {
218
+ node.removeAttribute('skeleton');
219
+ }
220
+ }
237
221
  }
238
222
 
239
223
  _handleSlotChange(e) {
240
224
  this._updateCode(e.target);
225
+ this._updateHasSkeleton();
241
226
  }
242
227
 
243
228
  _removeImportedDemo() {
244
229
  if (!this.shadowRoot) return;
245
230
  const nodes = this.shadowRoot.querySelector('slot[name="_demo"]').assignedNodes();
246
- for (let i = nodes.length - 1; i === 0; i--) {
231
+ for (let i = nodes.length - 1; i >= 0; i--) {
247
232
  nodes[i].parentNode.removeChild(nodes[i]);
248
233
  }
249
234
  }
250
235
 
251
- _repeat(value, times) {
252
- if (!value || !times) return '';
253
- if (!''.repeat) return Array(times).join(value); // for IE11
254
- return value.repeat(times);
255
- }
256
-
257
236
  _updateCode(slot) {
258
237
  this._removeImportedDemo();
259
238
  const nodes = slot.assignedNodes();
@@ -277,27 +256,12 @@ class DemoSnippet extends LitElement {
277
256
  }
278
257
  const textNode = document.createTextNode(this._formatCode(tempContainer.innerHTML));
279
258
  this._code = textNode.textContent;
280
-
281
- this._updateHasSkeleton();
282
259
  }
283
260
 
284
261
  _updateHasSkeleton() {
262
+ const nodes = this._getDemoNodes();
285
263
 
286
- const query = this._isTemplate ? 'slot[name="_demo"]' : 'slot:not([name="_demo"])';
287
- if (!this.shadowRoot) return;
288
- const nodes = this.shadowRoot.querySelector(query).assignedNodes();
289
-
290
- const doApply = (nodes) => {
291
- for (let i = 0; i < nodes.length; i++) {
292
- if (nodes[i].nodeType === Node.ELEMENT_NODE) {
293
- if (nodes[i].skeleton !== undefined) {
294
- this._hasSkeleton = true;
295
- }
296
- doApply(nodes[i].children);
297
- }
298
- }
299
- };
300
- doApply(nodes);
264
+ this._hasSkeleton = nodes.some(n => n.nodeType === Node.ELEMENT_NODE && n.tagName.indexOf('-') !== -1 && n.skeleton !== undefined);
301
265
 
302
266
  }
303
267
 
@@ -0,0 +1,116 @@
1
+ import '../demo-snippet.js';
2
+ import { defineCE, expect, fixture, html, nextFrame, runConstructor } from '@brightspace-ui/testing';
3
+ import { LitElement } from 'lit';
4
+ import { SkeletonMixin } from '../../skeleton/skeleton-mixin.js';
5
+
6
+ const skeletonTag = defineCE(class extends SkeletonMixin(LitElement) {
7
+ render() {
8
+ return html`<div>Skeleton element</div>`;
9
+ }
10
+ });
11
+ const scriptTestExpected = `<div>
12
+ <script>
13
+
14
+ console.log('hi');
15
+
16
+ </script>
17
+ <script type="module">
18
+
19
+ import { test } from './test.js';
20
+ if (window.test) {
21
+ console.log('test');
22
+ }
23
+
24
+ </script>
25
+ </div>`;
26
+ const tagTestExpected = `<div foo data-keep="ok"></div>
27
+ <another-tag bar target="_blank"></another-tag>
28
+ <script>
29
+
30
+ function _privateFunction() {console.log('Private function name is not removed');}
31
+
32
+ </script>`;
33
+
34
+ function addTemplate(inner) {
35
+ return `<template>${inner}</template>`;
36
+ }
37
+
38
+ describe('d2l-demo-snippet', () => {
39
+
40
+ describe('constructor', () => {
41
+ it('should construct', () => {
42
+ runConstructor('d2l-demo-snippet');
43
+ });
44
+ });
45
+
46
+ describe('code formatting', () => {
47
+ let elem;
48
+
49
+ beforeEach(async() => {
50
+ elem = await fixture(html`<d2l-demo-snippet></d2l-demo-snippet>`);
51
+ });
52
+
53
+ [true, false].forEach(useTemplate => {
54
+ it(`sets template flag to ${useTemplate ? 'true' : 'false'}`, async() => {
55
+ const inner = '<div>demo</div>';
56
+ const formatted = elem._formatCode(useTemplate ? addTemplate(inner) : inner);
57
+ expect(formatted).to.equal('<div>demo</div>');
58
+ expect(elem._isTemplate).to.equal(useTemplate);
59
+ });
60
+ it('parses scripts and removes hidden ones', async() => {
61
+ const inner = `
62
+ <div>
63
+ <script>console.log('hi');</script>
64
+ <script type="module">
65
+ import { test } from './test.js';
66
+ if (window.test) {
67
+ console.log('test');
68
+ }
69
+ </script>
70
+ <script data-demo-hide>hidden</script>
71
+ </div>`;
72
+ const formatted = elem._formatCode(useTemplate ? addTemplate(inner) : inner);
73
+ expect(formatted).to.equal(scriptTestExpected);
74
+ });
75
+ });
76
+
77
+ it('removes empty and private attributes but keeps normal ones', async() => {
78
+ const elem = await fixture(html`<d2l-demo-snippet></d2l-demo-snippet>`);
79
+ const formatted = elem._formatCode(`
80
+ <div class="" _private foo="" data-keep="ok"></div>
81
+ <another-tag _private="value" bar="" target="_blank"></another-tag>
82
+ <script>
83
+ function _privateFunction() {console.log('Private function name is not removed');}
84
+ </script>
85
+ `);
86
+ expect(formatted).to.equal(tagTestExpected);
87
+ });
88
+
89
+ });
90
+
91
+ describe('skeleton detection', () => {
92
+ [true, false].forEach(useTemplate => {
93
+
94
+ function snippetFixture(inner) {
95
+ return fixture(`<d2l-demo-snippet>${useTemplate ? addTemplate(inner) : inner}</d2l-demo-snippet>`, { awaitLoadingComplete: false });
96
+ }
97
+
98
+ it(`sets _hasSkeleton when a slotted element exposes skeleton property${useTemplate ? ' - template' : ''}`, async() => {
99
+ await nextFrame();
100
+ const elem = await snippetFixture(`<${skeletonTag}></${skeletonTag}>`);
101
+ expect(elem._hasSkeleton).to.be.true;
102
+ });
103
+
104
+ it(`sets _hasSkeleton when a nested slotted element exposes skeleton property${useTemplate ? ' - template' : ''}`, async() => {
105
+ const elem = await snippetFixture(`<div><${skeletonTag}></${skeletonTag}></div>`);
106
+ expect(elem._hasSkeleton).to.be.true;
107
+ });
108
+
109
+ it(`does not set _hasSkeleton when a slotted element does not expose skeleton property${useTemplate ? ' - template' : ''}`, async() => {
110
+ const elem = await snippetFixture('<div></div>');
111
+ expect(elem._hasSkeleton).to.be.false;
112
+ });
113
+ });
114
+ });
115
+
116
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightspace-ui/core",
3
- "version": "3.227.8",
3
+ "version": "3.227.9",
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",