@brightspace-ui/labs 2.30.0 → 2.31.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.
@@ -0,0 +1,128 @@
1
+ import { action, decorate, observable } from 'mobx';
2
+ import { COURSE_OFFERING, includesSearch, Tree } from '../../../src/components/ou-filter/tree-filter.js';
3
+ import { OuFilterDataManager } from '../../../src/components/ou-filter/ou-filter.js';
4
+
5
+ const orgUnitChildrenCache = new Map();
6
+ function fetchCachedChildren() {
7
+ return orgUnitChildrenCache;
8
+ }
9
+
10
+ const OU_TYPES = {
11
+ ORG: 1,
12
+ DEPT: 2,
13
+ COURSE: 3,
14
+ FAC: 7,
15
+ SEM: 25
16
+ };
17
+
18
+ /* eslint-disable no-console */
19
+ export class DemoDataManager extends OuFilterDataManager {
20
+
21
+ constructor(searchFn) {
22
+ super();
23
+ this._orgUnitTree = new Tree({});
24
+ this._searchFn = searchFn;
25
+ }
26
+
27
+ get orgUnitTree() {
28
+ return this._orgUnitTree;
29
+ }
30
+
31
+ // the method called only when Tree.isDynamic === true
32
+ async fetchRelevantChildren(id, bookmark) {
33
+ console.log(`fetchRelevantChildren ${id}, ${bookmark}`);
34
+
35
+ let results;
36
+ // one case for each child in "Faculty 2 - children loaded on open" (id 9)
37
+ switch (id) {
38
+ case 9:
39
+ if (bookmark !== 'orgUnit-9') {
40
+ results = { PagingInfo: { HasMoreItems: true, Bookmark: 'orgUnit-9' }, Items: [
41
+ { Id: 20, Name: 'Department 3', Type: OU_TYPES.DEPT, Parents: [9], IsActive: false }
42
+ ] };
43
+ } else {
44
+ results = { PagingInfo: { HasMoreItems: false, Bookmark: null }, Items: [
45
+ { Id: 30, Name: 'Department 4', Type: OU_TYPES.DEPT, Parents: [9], IsActive: false }
46
+ ] };
47
+ }
48
+ break;
49
+ case 20:
50
+ results = { PagingInfo: { HasMoreItems: false, Bookmark: null }, Items: [
51
+ { Id: 21, Name: 'Department 3 Course 1', Type: OU_TYPES.COURSE, Parents: [20], IsActive: false },
52
+ { Id: 22, Name: 'Department 3 Course 2', Type: OU_TYPES.COURSE, Parents: [20], IsActive: false }
53
+ ] };
54
+ break;
55
+ case 30:
56
+ results = { PagingInfo: { HasMoreItems: false, Bookmark: null }, Items: [
57
+ { Id: 31, Name: 'Department 3 Course 1', Type: OU_TYPES.COURSE, Parents: [20], IsActive: false }
58
+ ] };
59
+ break;
60
+ default:
61
+ // no-op: return object with no children
62
+ return await super.fetchRelevantChildren(id, bookmark);
63
+ }
64
+
65
+ // example of handling cache
66
+ if (!orgUnitChildrenCache.has(id)) {
67
+ orgUnitChildrenCache.set(id, results);
68
+ } else {
69
+ const cached = orgUnitChildrenCache.get(id);
70
+ cached.PagingInfo = results.PagingInfo;
71
+ cached.Items.push(...results.Items);
72
+ }
73
+
74
+ // return result in 2 sec to show loading spinner
75
+ return await new Promise(resolve => setTimeout(() => resolve(results), 2000));
76
+ }
77
+
78
+ loadData() {
79
+ const lastSearchResults = null;
80
+ const orgUnits = [
81
+ { Id: 1, Name: 'Course 1', Type: OU_TYPES.COURSE, Parents: [3, 4], IsActive: false },
82
+ { Id: 2, Name: 'Course 2', Type: OU_TYPES.COURSE, Parents: [3, 10], IsActive: true },
83
+ { Id: 6, Name: 'Course 3 has a surprisingly long name, but nonetheless this kind of thing is bound to happen sometimes and we do need to design for it. Is that not so?', Type: OU_TYPES.COURSE, Parents: [7, 4], IsActive: true },
84
+ { Id: 8, Name: 'ZCourse 4', Type: OU_TYPES.COURSE, Parents: [5], IsActive: false },
85
+ { Id: 3, Name: 'Department 1', Type: OU_TYPES.DEPT, Parents: [5], IsActive: false },
86
+ { Id: 7, Name: 'Department 2 has a longer name', Type: OU_TYPES.DEPT, Parents: [5], IsActive: false },
87
+ { Id: 4, Name: 'Semester 1', Type: OU_TYPES.SEM, Parents: [6606], IsActive: false },
88
+ { Id: 10, Name: 'Semester 2', Type: OU_TYPES.SEM, Parents: [6606], IsActive: false },
89
+ { Id: 5, Name: 'Faculty 1 - children loaded on first load', Type: OU_TYPES.FAC, Parents: [6606], IsActive: false },
90
+ { Id: 9, Name: 'Faculty 2 - children loaded on open', Type: OU_TYPES.FAC, Parents: [6606, 10], IsActive: false },
91
+ { Id: 6606, Name: 'Dev', Type: OU_TYPES.ORG, Parents: [0], IsActive: false }
92
+ ];
93
+ const isOrgUnitsTruncated = true;
94
+
95
+ this._orgUnitTree = new Tree({
96
+ // add in any nodes from the most recent search; otherwise
97
+ // the search will blink out and come back, and also drop any "load more" results
98
+ nodes: lastSearchResults ? [...orgUnits, ...lastSearchResults] : orgUnits,
99
+ leafTypes: [COURSE_OFFERING],
100
+ invisibleTypes: [OU_TYPES.SEM],
101
+ selectedIds: [1, 21],
102
+ ancestorIds: [],
103
+ oldTree: this.orgUnitTree,
104
+ isDynamic: isOrgUnitsTruncated,
105
+ // preload the tree with any children queries we've already run: otherwise parts of the
106
+ // tree blink out and then come back as they are loaded again
107
+ extraChildren: isOrgUnitsTruncated ?
108
+ fetchCachedChildren() || new Map() :
109
+ null,
110
+ searchFn: this._searchFn ? this._searchFn : includesSearch
111
+ });
112
+
113
+ // for perf testing
114
+ // this._orgUnitTree = createNaryTree(5, 5000);
115
+ }
116
+
117
+ // the method called only when Tree.isDynamic === true
118
+ async orgUnitSearch(searchString, bookmark) {
119
+ console.log(`orgUnitSearch ${searchString}, ${bookmark}`);
120
+
121
+ return await super.orgUnitSearch(searchString, bookmark);
122
+ }
123
+ }
124
+
125
+ decorate(DemoDataManager, {
126
+ _orgUnitTree: observable,
127
+ loadData: action
128
+ });
@@ -0,0 +1,103 @@
1
+ import '@brightspace-ui/core/components/inputs/input-search.js';
2
+
3
+ import { css, html } from 'lit';
4
+ import { DemoDataManager } from './demoDataManager.js';
5
+ import { MobxLitElement } from '@adobe/lit-mobx';
6
+ import { startsWithSearch } from '../../../src/components/ou-filter/tree-filter.js';
7
+
8
+ function parseHash(hash) {
9
+ return hash.substring(1).split(';').reduce((acc, curr) => {
10
+ const [key, val] = curr.split('=');
11
+ acc.set(key, val);
12
+ return acc;
13
+ }, new Map());
14
+ }
15
+
16
+ /* eslint-disable no-console */
17
+ class OuFilterDemoPage extends MobxLitElement {
18
+
19
+ static get styles() {
20
+ return [
21
+ css`
22
+ :host {
23
+ display: inline-block;
24
+ }
25
+ div {
26
+ padding: 30px;
27
+ }
28
+ label {
29
+ display: block;
30
+ margin-top: 25px;
31
+ }
32
+ `
33
+ ];
34
+ }
35
+
36
+ constructor() {
37
+ super();
38
+ this.dataManager = new DemoDataManager();
39
+ }
40
+
41
+ connectedCallback() {
42
+ super.connectedCallback();
43
+
44
+ const hashMap = parseHash(window.location.hash);
45
+
46
+ if (hashMap.has('dir')) {
47
+ document.documentElement.setAttribute('dir', hashMap.get('dir'));
48
+ }
49
+
50
+ if (hashMap.has('search') && hashMap.get('search') === 'startswith') {
51
+ this.dataManager = new DemoDataManager(startsWithSearch);
52
+ }
53
+ }
54
+
55
+ firstUpdated() {
56
+ this.dataManager.loadData();
57
+ }
58
+
59
+ render() {
60
+ return html`
61
+ <div>
62
+ <d2l-labs-ou-filter
63
+ .dataManager=${this.dataManager}
64
+ select-all-ui
65
+ @d2l-labs-ou-filter-change="${this._handleOrgUnitFilterChange}"
66
+ ></d2l-labs-ou-filter>
67
+ <label for="org-unit-id-search">Test visibility modifiers: show only branches containing org unit ids</label>
68
+ <d2l-input-search
69
+ id="org-unit-id-search"
70
+ label="Demo search input"
71
+ @d2l-input-search-searched="${this._handleInputSearchChange}">
72
+ </d2l-input-search>
73
+ </div>
74
+ `;
75
+ }
76
+
77
+ _handleInputSearchChange(event) {
78
+ const searchInput = event.detail.value;
79
+ const visibilityModifierKey = 'searchInputFilter';
80
+ if (!searchInput) {
81
+ this.dataManager.orgUnitTree.removeVisibilityModifier(visibilityModifierKey);
82
+ return;
83
+ }
84
+
85
+ // expect CSV of org unit ids
86
+ const searchedOrgUnitIds = searchInput.split(',').map(orgUnitIdStr => Number(orgUnitIdStr));
87
+ const tree = this.dataManager.orgUnitTree;
88
+
89
+ // example: only load branches that contain any of the searched orgUnitIds
90
+ tree.setVisibilityModifier(
91
+ visibilityModifierKey,
92
+ (id) => tree.hasDescendantsInList(id, searchedOrgUnitIds)
93
+ );
94
+ }
95
+
96
+ _handleOrgUnitFilterChange(event) {
97
+ event.stopPropagation();
98
+ console.log(event.target.selected);
99
+ }
100
+
101
+ }
102
+
103
+ customElements.define('d2l-labs-oufilter-demo-page', OuFilterDemoPage);
@@ -0,0 +1,38 @@
1
+ import { Tree } from '../../../src/components/ou-filter/tree-filter.js';
2
+
3
+ let idCounter = 1;
4
+
5
+ function createNode(id, parents, type) {
6
+ return { Id: id, Name: `Node ${id}`, Type: type, Parents: parents, IsActive: true };
7
+ }
8
+
9
+ function spawnChildren(node, n) {
10
+ const newNodes = [];
11
+ for (let i = 1; i <= n; i++) {
12
+ newNodes.push(createNode(++idCounter, [node.Id], node.Type + 1));
13
+ }
14
+ return newNodes;
15
+ }
16
+
17
+ export function createNaryTree(n, numNodes) {
18
+
19
+ idCounter = 1;
20
+
21
+ const nodes = [createNode(1, [0], 0)];
22
+ let newNodes = [...nodes];
23
+
24
+ // create each layer of the tree until we have the requisite total number of nodes; drop any excess nodes
25
+ while (nodes.length < numNodes) {
26
+
27
+ let children = newNodes.flatMap(node => spawnChildren(node, n));
28
+ if (children.length + nodes.length > numNodes) {
29
+ children = children.slice(0, numNodes - nodes.length);
30
+ }
31
+
32
+ nodes.push(...children);
33
+ newNodes = children;
34
+ }
35
+
36
+ const numLevels = Math.ceil(Math.log(numNodes - 1) / Math.log(n)); // including level 0 (root node)
37
+ return new Tree({ nodes, leafTypes: [numLevels] });
38
+ }
package/package.json CHANGED
@@ -51,6 +51,7 @@
51
51
  "./controllers/computed-value.js": "./src/controllers/computed-values/computed-value.js",
52
52
  "./controllers/computed-values.js": "./src/controllers/computed-values/computed-values.js",
53
53
  "./controllers/language-listener.js": "./src/controllers/language-listener/language-listener.js",
54
+ "./demo/components/ou-filter/*.js": "./demo/components/ou-filter/*.js",
54
55
  "./utilities/pub-sub.js": "./src/utilities/pub-sub/pub-sub.js",
55
56
  "./utilities/reactive-store.js": "./src/utilities/reactive-store/reactive-store.js",
56
57
  "./utilities/lit-router": "./src/utilities/router/index.js"
@@ -93,7 +94,8 @@
93
94
  "files": [
94
95
  "labs.serge.json",
95
96
  "/src",
96
- "/demo/components/media-player/static"
97
+ "/demo/components/media-player/static",
98
+ "/demo/components/ou-filter/*.js"
97
99
  ],
98
100
  "publishConfig": {
99
101
  "access": "public"
@@ -112,5 +114,5 @@
112
114
  "resize-observer-polyfill": "^1",
113
115
  "webvtt-parser": "^2.1.2"
114
116
  },
115
- "version": "2.30.0"
117
+ "version": "2.31.0"
116
118
  }
@@ -0,0 +1,74 @@
1
+ # Org Unit Filter
2
+
3
+ A Lit component that renders an org unit structure tree. It supports load more and searching functionality.
4
+
5
+ ## Org Unit Filter [d2l-labs-ou-filter]
6
+
7
+ <!-- docs: demo align:flex-start autoSize:false size:xlarge -->
8
+ ```html
9
+ <script type="module">
10
+ import '@brightspace-ui/labs/demo/components/ou-filter/ouFilterDemoPage.js';
11
+ </script>
12
+ <d2l-labs-oufilter-demo-page></d2l-labs-oufilter-demo-page>
13
+ ```
14
+
15
+ ### General Usage
16
+
17
+ ```js
18
+ import { action, decorate, observable } from 'mobx';
19
+ import { MobxLitElement } from '@adobe/lit-mobx';
20
+ import { OuFilterDataManager } from '@brightspace-ui-labs/ou-filter/ou-filter.js';
21
+
22
+ class FooDataManager extends OuFilterDataManager {
23
+
24
+ constructor() {
25
+ super();
26
+ this._orgUnitTree = new Tree({});
27
+ }
28
+
29
+ async loadData() {
30
+ this._orgUnitTree = new Tree({ nodes: ..., ... });
31
+ }
32
+ }
33
+
34
+ decorate(FooDataManager, {
35
+ _orgUnitTree: observable,
36
+ loadData: action
37
+ });
38
+
39
+ class FooPage extends MobxLitElement {
40
+ constructor() {
41
+ this.dataManager = new FooDataManager();
42
+ }
43
+
44
+ firstUpdated() {
45
+ this.dataManager.loadData();
46
+ }
47
+
48
+ render () {
49
+ return html`
50
+ <d2l-labs-ou-filter
51
+ .dataManager=${this.dataManager}
52
+ select-all-ui
53
+ @d2l-labs-ou-filter-change="${this._orgUnitFilterChange}"
54
+ ></d2l-labs-ou-filter>`;
55
+ }
56
+
57
+ _orgUnitFilterChange() {
58
+ console.log(event.target.selected);
59
+ }
60
+ }
61
+ ```
62
+
63
+ <!-- docs: start hidden content -->
64
+
65
+ **Properties:**
66
+
67
+ | Property | Type | Default | Description |
68
+ |----------|------|---------|-------------|
69
+ | dataManager | Object | {empty} | Object that extends OuFilterDataManager. It provides and manages data for d2l-labs-ou-filter |
70
+ | select-all-ui | Boolean | {empty} | Shows Select all button |
71
+ | d2l-labs-ou-filter-change | Function | {empty} | Event handler that is fired when selection is changed |
72
+ | disabled | Boolean | {empty} | Render the filter in a disabled state |
73
+
74
+ <!-- docs: end hidden content -->
@@ -1,34 +1,52 @@
1
1
  # Wizard
2
2
 
3
- The `<d2l-labs-wizard>` can be used to be display a stepped workflow.
3
+ The wizard component can be used to display a stepped workflow.
4
4
 
5
- ## Usage
5
+ ## Wizard [d2l-labs-wizard]
6
6
 
7
+ <!-- docs: demo code -->
7
8
  ```html
8
9
  <script type="module">
9
- import '@brightspace-ui/labs/components/wizard.js';
10
- import '@brightspace-ui/labs/components/wizard-step.js';
10
+ import '@brightspace-ui/labs/components/wizard.js';
11
+ import '@brightspace-ui/labs/components/wizard-step.js';
12
+ // <!-- docs: start hidden content -->
13
+ requestAnimationFrame(() => {
14
+ const wizard = document.getElementById('wizard');
15
+ wizard.addEventListener('stepper-next', function() {
16
+ wizard.next();
17
+ });
18
+ wizard.addEventListener('stepper-restart', function() {
19
+ wizard.restart();
20
+ });
21
+ })
22
+ // <!-- docs: end hidden content -->
11
23
  </script>
12
- <d2l-labs-wizard id="wizard">
13
- <d2l-labs-wizard-step step-title="Step 1">
14
- <p> First step </p>
15
- </d2l-labs-wizard-step>
16
-
17
- <d2l-labs-wizard-step step-title="Step 2">
18
- <p> Second step </p>
19
- </d2l-labs-wizard-step>
24
+ <d2l-labs-wizard
25
+ id="wizard"
26
+ selected-step="1">
27
+ <d2l-labs-wizard-step
28
+ next-button-aria-label="Go to step 2"
29
+ step-title="Get Started"
30
+ hide-restart-button="true">
31
+ <p>First Step</p>
32
+ </d2l-labs-wizard-step>
33
+
34
+ <d2l-labs-wizard-step
35
+ aria-title="This is the second step"
36
+ restart-button-tooltip="Restart this wizard">
37
+ <p>Second Step</p>
38
+ </d2l-labs-wizard-step>
39
+
40
+ <d2l-labs-wizard-step
41
+ step-title="Almost Done"
42
+ next-button-title="Done"
43
+ next-button-tooltip="Save this wizard">
44
+ <p>Last Step</p>
45
+ </d2l-labs-wizard-step>
20
46
  </d2l-labs-wizard>
21
- <script>
22
- var wizard = document.getElementById('wizard');
23
- wizard.addEventListener('stepper-next', function() {
24
- wizard.next();
25
- });
26
- wizard.addEventListener('stepper-restart', function() {
27
- wizard.restart();
28
- });
29
- </script>
30
47
  ```
31
48
 
49
+ <!-- docs: start hidden content -->
32
50
 
33
51
  ### Properties:
34
52
 
@@ -47,3 +65,4 @@ The `<d2l-labs-wizard>` can be used to be display a stepped workflow.
47
65
  - `stepper-next`: dispatched when the Next button is clicked
48
66
  - `stepper-restart`: dispatched when the Restart button is clicked
49
67
 
68
+ <!-- docs: end hidden content -->