@brightspace-ui/core 3.281.0 → 3.283.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/page/page.js +65 -5
- package/custom-elements.json +11 -0
- package/helpers/README.md +10 -6
- package/helpers/dom.js +41 -16
- package/package.json +1 -1
package/components/page/page.js
CHANGED
|
@@ -7,6 +7,8 @@ import { LocalizeCoreElement } from '../../helpers/localize-core-element.js';
|
|
|
7
7
|
import { ProviderMixin } from '../../mixins/provider/provider-mixin.js';
|
|
8
8
|
import { styleMap } from 'lit/directives/style-map.js';
|
|
9
9
|
|
|
10
|
+
export const panelStateStorageKey = key => `d2l-page-panel-state-${key}`;
|
|
11
|
+
|
|
10
12
|
const DRAWER_MIN_HEIGHT = 200; // TO DO: Confirm
|
|
11
13
|
const MAIN_MIN_WIDTH = 600; // TO DO: Confirm
|
|
12
14
|
const PANEL_MIN_WIDTH = 320;
|
|
@@ -41,12 +43,28 @@ class PanelStateController {
|
|
|
41
43
|
return panel.size;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
initialize(defaults) {
|
|
47
|
+
const storedState = this.#getStoredState();
|
|
48
|
+
|
|
49
|
+
for (const [key, panel] of Object.entries(this.#panels)) {
|
|
50
|
+
const stored = storedState?.[key];
|
|
51
|
+
if (stored) {
|
|
52
|
+
const storedSize = parseInt(stored.size);
|
|
53
|
+
this.resize(key, isFinite(storedSize) ? storedSize : defaults[key]);
|
|
54
|
+
if (stored.collapsed) panel.collapsed = true;
|
|
55
|
+
} else {
|
|
56
|
+
this.resize(key, defaults[key]);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
resize(key, requestedSize, { animate = false, storeState = false } = {}) {
|
|
45
62
|
const panel = this.#panels[key];
|
|
46
63
|
// Clamp requested size to min and max bounds
|
|
47
64
|
panel.size = Math.max(panel.minSize, Math.min(requestedSize, panel.maxSize));
|
|
48
65
|
panel.animate = animate;
|
|
49
66
|
this.#host.requestUpdate();
|
|
67
|
+
if (storeState) this.#storePanelState(key);
|
|
50
68
|
}
|
|
51
69
|
|
|
52
70
|
setCollapsed(key, collapsed) {
|
|
@@ -54,6 +72,7 @@ class PanelStateController {
|
|
|
54
72
|
panel.collapsed = collapsed;
|
|
55
73
|
panel.animate = true;
|
|
56
74
|
this.#host.requestUpdate();
|
|
75
|
+
this.#storePanelState(key);
|
|
57
76
|
}
|
|
58
77
|
|
|
59
78
|
setDragSize(key) {
|
|
@@ -74,6 +93,39 @@ class PanelStateController {
|
|
|
74
93
|
|
|
75
94
|
#host;
|
|
76
95
|
#panels;
|
|
96
|
+
|
|
97
|
+
#getPanelStateStorageKey() {
|
|
98
|
+
return this.#host.stateStorageKey ? panelStateStorageKey(this.#host.stateStorageKey) : null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
#getStoredState() {
|
|
102
|
+
const fullKey = this.#getPanelStateStorageKey();
|
|
103
|
+
if (!fullKey) return;
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
return JSON.parse(localStorage.getItem(fullKey));
|
|
107
|
+
} catch {
|
|
108
|
+
// Return nothing if local storage isn't available or has bad data
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
#storePanelState(panelKey) {
|
|
114
|
+
const fullKey = this.#getPanelStateStorageKey();
|
|
115
|
+
if (!fullKey) return;
|
|
116
|
+
const state = this.#getStoredState() || {};
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
state[panelKey] = {
|
|
120
|
+
size: this.#panels[panelKey].size,
|
|
121
|
+
collapsed: this.#panels[panelKey].collapsed
|
|
122
|
+
};
|
|
123
|
+
localStorage.setItem(fullKey, JSON.stringify(state));
|
|
124
|
+
} catch {
|
|
125
|
+
// Do nothing if storage quota exceeded or using private browsing mode of an old Safari version
|
|
126
|
+
// TO DO: If QuotaExceededError, log this to our logging framework
|
|
127
|
+
}
|
|
128
|
+
}
|
|
77
129
|
}
|
|
78
130
|
|
|
79
131
|
/**
|
|
@@ -87,6 +139,12 @@ class PanelStateController {
|
|
|
87
139
|
class Page extends ProviderMixin(LocalizeCoreElement(LitElement)) {
|
|
88
140
|
|
|
89
141
|
static properties = {
|
|
142
|
+
/**
|
|
143
|
+
* Key used to optionally store page state across reloads (using localStorage). Different pages should use different keys.
|
|
144
|
+
* Currently, this includes panel state info.
|
|
145
|
+
* @type {string}
|
|
146
|
+
*/
|
|
147
|
+
stateStorageKey: { type: String, attribute: 'state-storage-key' },
|
|
90
148
|
/**
|
|
91
149
|
* Width type of the page and its underlying pieces
|
|
92
150
|
* @type {'normal'|'wide'|'fullscreen'}
|
|
@@ -354,7 +412,7 @@ class Page extends ProviderMixin(LocalizeCoreElement(LitElement)) {
|
|
|
354
412
|
|
|
355
413
|
#handleDividerResize(e) {
|
|
356
414
|
const panelKey = e.target.dataset.panelKey;
|
|
357
|
-
this._panelState.resize(panelKey, e.detail.requestedSize, true);
|
|
415
|
+
this._panelState.resize(panelKey, e.detail.requestedSize, { animate: true, storeState: true });
|
|
358
416
|
};
|
|
359
417
|
|
|
360
418
|
#handleDividerToggle(e) {
|
|
@@ -373,9 +431,11 @@ class Page extends ProviderMixin(LocalizeCoreElement(LitElement)) {
|
|
|
373
431
|
const defaultWidth = Math.floor(this._contentWidth / 3);
|
|
374
432
|
const defaultHeight = Math.floor(window.innerHeight / 2);
|
|
375
433
|
|
|
376
|
-
this._panelState.
|
|
377
|
-
|
|
378
|
-
|
|
434
|
+
this._panelState.initialize({
|
|
435
|
+
'side-nav': defaultWidth,
|
|
436
|
+
'supporting': defaultWidth,
|
|
437
|
+
'supporting-mobile': defaultHeight
|
|
438
|
+
});
|
|
379
439
|
}
|
|
380
440
|
|
|
381
441
|
#renderDivider(panelKey, label, panelPosition) {
|
package/custom-elements.json
CHANGED
|
@@ -13574,6 +13574,11 @@
|
|
|
13574
13574
|
"path": "./components/page/page.js",
|
|
13575
13575
|
"description": "Component for laying out a page, with header, optional footer and optional navigation or supporting panels",
|
|
13576
13576
|
"attributes": [
|
|
13577
|
+
{
|
|
13578
|
+
"name": "state-storage-key",
|
|
13579
|
+
"description": "Key used to optionally store page state across reloads (using localStorage). Different pages should use different keys.\nCurrently, this includes panel state info.",
|
|
13580
|
+
"type": "string"
|
|
13581
|
+
},
|
|
13577
13582
|
{
|
|
13578
13583
|
"name": "width-type",
|
|
13579
13584
|
"description": "Width type of the page and its underlying pieces",
|
|
@@ -13582,6 +13587,12 @@
|
|
|
13582
13587
|
}
|
|
13583
13588
|
],
|
|
13584
13589
|
"properties": [
|
|
13590
|
+
{
|
|
13591
|
+
"name": "stateStorageKey",
|
|
13592
|
+
"attribute": "state-storage-key",
|
|
13593
|
+
"description": "Key used to optionally store page state across reloads (using localStorage). Different pages should use different keys.\nCurrently, this includes panel state info.",
|
|
13594
|
+
"type": "string"
|
|
13595
|
+
},
|
|
13585
13596
|
{
|
|
13586
13597
|
"name": "styles",
|
|
13587
13598
|
"default": "\"css`\\n\\t\\t:host {\\n\\t\\t\\t--d2l-page-header-max-width: 1230px;\\n\\t\\t\\t--d2l-page-content-max-width: 1230px;\\n\\t\\t\\t--d2l-page-footer-max-width: 1230px;\\n\\t\\t\\t--d2l-page-margin-inline: auto;\\n\\t\\t\\t--d2l-page-padding: 30px;\\n\\t\\t}\\n\\n\\t\\t:host([width-type=\\\"wide\\\"]) {\\n\\t\\t\\t--d2l-page-header-max-width: 1440px;\\n\\t\\t\\t--d2l-page-content-max-width: 1440px;\\n\\t\\t\\t--d2l-page-footer-max-width: 1440px;\\n\\t\\t}\\n\\n\\t\\t:host([width-type=\\\"fullscreen\\\"]) {\\n\\t\\t\\t--d2l-page-header-max-width: 100%;\\n\\t\\t\\t--d2l-page-content-max-width: 100%;\\n\\t\\t\\t--d2l-page-footer-max-width: 100%;\\n\\t\\t}\\n\\n\\t\\t@media (max-width: 929px) {\\n\\t\\t\\t:host {\\n\\t\\t\\t\\t--d2l-page-padding: 24px;\\n\\t\\t\\t}\\n\\t\\t}\\n\\t\\t@media (max-width: 767px) {\\n\\t\\t\\t:host {\\n\\t\\t\\t\\t--d2l-page-padding: 18px;\\n\\t\\t\\t}\\n\\t\\t}\\n\\n\\t\\t.header {\\n\\t\\t\\tposition: relative;\\n\\t\\t\\tz-index: 16; /* To be over sticky content of our core components and the divider */\\n\\t\\t}\\n\\n\\t\\t.page.header-sticky .header {\\n\\t\\t\\tposition: sticky;\\n\\t\\t\\ttop: 0;\\n\\t\\t}\\n\\n\\t\\t.content {\\n\\t\\t\\tbox-sizing: border-box;\\n\\t\\t\\tdisplay: flex;\\n\\t\\t\\tmargin-inline: var(--d2l-page-margin-inline, 0);\\n\\t\\t\\tmax-width: var(--d2l-page-content-max-width, 100%);\\n\\t\\t\\tpadding-bottom: var(--d2l-page-footer-height, 0); /* Reserve space for fixed footer */\\n\\t\\t}\\n\\t\\t.content.has-panels {\\n\\t\\t\\tmin-height: calc(100vh - var(--d2l-page-header-height-measured, 0px));\\n\\t\\t}\\n\\n\\t\\tmain {\\n\\t\\t\\tflex: 1;\\n\\t\\t\\tmin-width: min(${MAIN_MIN_WIDTH}px, 100%);\\n\\t\\t}\\n\\n\\t\\t.side-nav,\\n\\t\\t.supporting {\\n\\t\\t\\tdisplay: contents;\\n\\t\\t}\\n\\n\\t\\t.side-nav-panel,\\n\\t\\t.supporting-panel {\\n\\t\\t\\toverflow: hidden;\\n\\t\\t}\\n\\n\\t\\t.side-nav-panel,\\n\\t\\t.supporting-panel,\\n\\t\\t.divider {\\n\\t\\t\\tposition: sticky;\\n\\t\\t\\ttop: var(--d2l-page-header-height, 0);\\n\\t\\t}\\n\\n\\t\\t.side-nav-panel,\\n\\t\\t.supporting-panel,\\n\\t\\t.side-nav-panel-content,\\n\\t\\t.supporting-panel-content,\\n\\t\\t.divider {\\n\\t\\t\\tmax-height: calc(100vh - var(--d2l-page-header-height, 0) - var(--d2l-page-footer-height, 0));\\n\\t\\t}\\n\\n\\t\\t.side-nav-panel-content,\\n\\t\\t.supporting-panel-content {\\n\\t\\t\\tbox-sizing: border-box;\\n\\t\\t\\toverflow: clip auto;\\n\\t\\t}\\n\\t\\t.side-nav-panel-content {\\n\\t\\t\\tfloat: inline-end;\\n\\t\\t}\\n\\t\\t.supporting-panel-content {\\n\\t\\t\\tfloat: inline-start;\\n\\t\\t}\\n\\n\\t\\t.divider {\\n\\t\\t\\tz-index: 15; /* To be over d2l-page-* panel headers */\\n\\t\\t}\\n\\n\\t\\t.side-nav .divider[collapsed] {\\n\\t\\t\\tmargin-inline-start: 18px;\\n\\t\\t}\\n\\t\\t.supporting .divider[collapsed] {\\n\\t\\t\\tmargin-inline-end: 18px;\\n\\t\\t}\\n\\t\\t.side-nav-panel.collapsed,\\n\\t\\t.supporting-panel.collapsed {\\n\\t\\t\\tvisibility: hidden;\\n\\t\\t}\\n\\t\\t@media (prefers-reduced-motion: no-preference) {\\n\\t\\t\\t.side-nav-panel.animate,\\n\\t\\t\\t.supporting-panel.animate,\\n\\t\\t\\t.side-nav-panel.animate .side-nav-panel-content,\\n\\t\\t\\t.supporting-panel.animate .supporting-panel-content {\\n\\t\\t\\t\\ttransition: width 400ms cubic-bezier(0, 0.7, 0.5, 1);\\n\\t\\t\\t}\\n\\t\\t\\t.divider.animate {\\n\\t\\t\\t\\ttransition: margin 400ms cubic-bezier(0, 0.7, 0.5, 1);\\n\\t\\t\\t}\\n\\t\\t\\t.side-nav-panel.animate.collapsed,\\n\\t\\t\\t.supporting-panel.animate.collapsed {\\n\\t\\t\\t\\ttransition: width 400ms cubic-bezier(0, 0.7, 0.5, 1), visibility 0s 400ms;\\n\\t\\t\\t}\\n\\t\\t}\\n\\n\\t\\t.footer:not([hidden]),\\n\\t\\t.floating-buttons-container {\\n\\t\\t\\tdisplay: inline;\\n\\t\\t}\\n\\t\\t.fixed-footer {\\n\\t\\t\\tbackground-color: white;\\n\\t\\t\\tbox-shadow: 0 -2px 4px rgba(32, 33, 34, 0.2); /* ferrite */\\n\\t\\t\\tinset: auto 0 0;\\n\\t\\t\\tpadding-block-start: 0.75rem;\\n\\t\\t\\tposition: fixed;\\n\\t\\t\\tz-index: 15; /* To be over sticky content of our core components and divider */\\n\\t\\t}\\n\\t\\t.footer-contents {\\n\\t\\t\\tmargin-inline: var(--d2l-page-margin-inline, 0);\\n\\t\\t\\tmax-width: var(--d2l-page-footer-max-width, 100%);\\n\\t\\t}\\n\\t`\""
|
package/helpers/README.md
CHANGED
|
@@ -86,18 +86,25 @@ elemIdListRemoves(node, attrName, value);
|
|
|
86
86
|
findComposedAncestor(node, predicate);
|
|
87
87
|
|
|
88
88
|
// gets the composed children (including shadow children & distributed children)
|
|
89
|
-
// includes
|
|
89
|
+
// includes optional predicate for filtering child elements
|
|
90
90
|
getComposedChildren(element, predicate = () => true);
|
|
91
91
|
|
|
92
|
+
// gets the composed next element sibling considing how children are distributed into slots
|
|
93
|
+
// includes optional predicate for filtering elements
|
|
94
|
+
getComposedNextElementSibling(node, { predicate } = {}) {
|
|
95
|
+
|
|
92
96
|
// gets the composed parent (including shadow host & insertion points)
|
|
93
97
|
getComposedParent(node);
|
|
94
98
|
|
|
99
|
+
// returns the first visible ancestor of the given node
|
|
100
|
+
getFirstVisibleAncestor(node)
|
|
101
|
+
|
|
95
102
|
// returns the next composed sibling at least one dom level up
|
|
96
|
-
// includes
|
|
103
|
+
// includes optional predicate for filtering elements
|
|
97
104
|
getNextAncestorSibling(node, predicate = () => true);
|
|
98
105
|
|
|
99
106
|
// returns the previous composed sibling at least one dom level up
|
|
100
|
-
// includes
|
|
107
|
+
// includes optional predicate for filtering elements
|
|
101
108
|
getPreviousAncestorSibling(node, predicate = () => true);
|
|
102
109
|
|
|
103
110
|
// browser consistent implementation of HTMLElement.offsetParent
|
|
@@ -109,9 +116,6 @@ isComposedAncestor(ancestorNode, node);
|
|
|
109
116
|
// returns true/false whether the element is visible regardless of positioning
|
|
110
117
|
isVisible(node, { checkAncestors: true });
|
|
111
118
|
|
|
112
|
-
// returns the first visible ancestor of the given node
|
|
113
|
-
getFirstVisibleAncestor(node)
|
|
114
|
-
|
|
115
119
|
// similar to document.querySelector or element.querySelector,
|
|
116
120
|
// except it queries not just the light DOM but also shadow DOM
|
|
117
121
|
querySelectorComposed(node, selector)
|
package/helpers/dom.js
CHANGED
|
@@ -79,40 +79,65 @@ export function getBoundingAncestor(node) {
|
|
|
79
79
|
});
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
function getComposedChildNodes(node, { elementsOnly = false, predicate } = {}) {
|
|
83
83
|
|
|
84
|
-
if (!node)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
if (!node) return null;
|
|
85
|
+
|
|
86
|
+
if (node.nodeType !== Node.ELEMENT_NODE
|
|
87
|
+
&& node.nodeType !== Node.DOCUMENT_NODE
|
|
88
|
+
&& node.nodeType !== Node.DOCUMENT_FRAGMENT_NODE) {
|
|
88
89
|
return null;
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
let nodes;
|
|
92
|
-
const
|
|
93
|
+
const filteredNodes = [];
|
|
93
94
|
|
|
94
95
|
if (node.tagName === 'CONTENT') {
|
|
95
96
|
nodes = node.getDistributedNodes();
|
|
96
97
|
} else if (node.tagName === 'SLOT') {
|
|
97
98
|
// note: this is not handling default slot content
|
|
98
|
-
nodes = node.assignedNodes({ flatten: true });
|
|
99
|
+
nodes = elementsOnly ? node.assignedElements({ flatten: true }) : node.assignedNodes({ flatten: true });
|
|
99
100
|
} else {
|
|
100
|
-
if (node.shadowRoot)
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
nodes = node.children || node.childNodes;
|
|
101
|
+
if (node.shadowRoot) node = node.shadowRoot;
|
|
102
|
+
nodes = elementsOnly ? node.children : node.childNodes;
|
|
104
103
|
}
|
|
105
104
|
|
|
106
105
|
for (let i = 0; i < nodes.length; i++) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
106
|
+
// need to check nodeType since old Polymer getDistributedNodes could return non-elements
|
|
107
|
+
if (!elementsOnly || nodes[i].nodeType === Node.ELEMENT_NODE) {
|
|
108
|
+
if (!predicate || predicate(nodes[i])) filteredNodes.push(nodes[i]);
|
|
111
109
|
}
|
|
112
110
|
}
|
|
113
111
|
|
|
114
|
-
return
|
|
112
|
+
return filteredNodes;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function getComposedChildren(node, predicate) {
|
|
116
|
+
return getComposedChildNodes(node, { elementsOnly: true, predicate });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function getComposedNextElementSibling(node, { predicate } = {}) {
|
|
115
120
|
|
|
121
|
+
if (!node || node.nodeType === undefined) return null;
|
|
122
|
+
|
|
123
|
+
const parentNode = getComposedParent(node);
|
|
124
|
+
if (!parentNode) return null;
|
|
125
|
+
|
|
126
|
+
// for regular parents this is child elements; for SLOT this is distributed elements
|
|
127
|
+
const composedChildNodes = getComposedChildNodes(parentNode, { elementsOnly: (node.nodeType === Node.ELEMENT_NODE) });
|
|
128
|
+
|
|
129
|
+
if (!composedChildNodes || composedChildNodes.length === 0) return null;
|
|
130
|
+
|
|
131
|
+
const index = composedChildNodes.indexOf(node);
|
|
132
|
+
if (index === -1) return null;
|
|
133
|
+
|
|
134
|
+
for (let i = index + 1; i < composedChildNodes.length; i++) {
|
|
135
|
+
if (composedChildNodes[i].nodeType === Node.ELEMENT_NODE && (!predicate || predicate(composedChildNodes[i]))) {
|
|
136
|
+
return composedChildNodes[i];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return null;
|
|
116
141
|
}
|
|
117
142
|
|
|
118
143
|
export function getComposedParent(node) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brightspace-ui/core",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.283.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",
|