@brightspace-ui/core 2.102.0 → 2.104.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.
Files changed (121) hide show
  1. package/README.md +7 -7
  2. package/components/alert/alert.js +1 -1
  3. package/components/breadcrumbs/breadcrumb.js +2 -2
  4. package/components/breadcrumbs/breadcrumbs.js +1 -1
  5. package/components/button/button-icon.js +3 -4
  6. package/components/button/button-mixin.js +1 -1
  7. package/components/button/button-move.js +245 -0
  8. package/components/button/button-subtle.js +1 -1
  9. package/components/button/demo/button-move.html +46 -0
  10. package/components/button/floating-buttons.js +1 -1
  11. package/components/calendar/calendar.js +1 -1
  12. package/components/card/card-footer-link.js +2 -2
  13. package/components/card/card.js +2 -2
  14. package/components/collapsible-panel/collapsible-panel.js +2 -2
  15. package/components/count-badge/count-badge-icon.js +1 -1
  16. package/components/count-badge/count-badge-mixin.js +1 -1
  17. package/components/count-badge/count-badge.js +1 -1
  18. package/components/dialog/dialog-mixin.js +1 -1
  19. package/components/dropdown/dropdown-button.js +1 -1
  20. package/components/dropdown/dropdown-content-mixin.js +1 -1
  21. package/components/dropdown/dropdown-context-menu.js +1 -1
  22. package/components/dropdown/dropdown-menu.js +1 -1
  23. package/components/dropdown/dropdown-more.js +1 -1
  24. package/components/empty-state/empty-state-simple.js +1 -1
  25. package/components/filter/filter-overflow-group.js +1 -1
  26. package/components/filter/filter.js +2 -2
  27. package/components/focus-trap/focus-trap.js +1 -1
  28. package/components/form/form-errory-summary.js +1 -1
  29. package/components/html-block/demo/html-block.html +1 -1
  30. package/components/html-block/html-block.js +1 -1
  31. package/components/icons/icon-custom.js +1 -1
  32. package/components/icons/icon.js +1 -1
  33. package/components/inputs/README.md +1 -1
  34. package/components/inputs/demo/input-radio-label-simple-test.js +1 -1
  35. package/components/inputs/demo/input-radio-label-test.js +1 -1
  36. package/components/inputs/demo/input-select-test.js +1 -1
  37. package/components/inputs/docs/input-radio.md +1 -1
  38. package/components/inputs/docs/input-select-styles.md +1 -1
  39. package/components/inputs/input-checkbox-spacer.js +1 -1
  40. package/components/inputs/input-checkbox.js +2 -2
  41. package/components/inputs/input-color.js +1 -1
  42. package/components/inputs/input-date-range.js +2 -2
  43. package/components/inputs/input-date-time-range.js +2 -2
  44. package/components/inputs/input-date-time.js +3 -3
  45. package/components/inputs/input-date.js +2 -2
  46. package/components/inputs/input-fieldset.js +1 -1
  47. package/components/inputs/input-number.js +2 -2
  48. package/components/inputs/input-percent.js +3 -3
  49. package/components/inputs/input-radio-spacer.js +1 -1
  50. package/components/inputs/input-search.js +2 -2
  51. package/components/inputs/input-text.js +3 -3
  52. package/components/inputs/input-textarea.js +3 -3
  53. package/components/inputs/input-time-range.js +2 -2
  54. package/components/inputs/input-time.js +2 -2
  55. package/components/link/link.js +1 -1
  56. package/components/list/list-item-drag-handle.js +94 -126
  57. package/components/list/list-item-drag-image.js +1 -1
  58. package/components/list/list-item-generic-layout.js +2 -3
  59. package/components/list/list-item-mixin.js +2 -2
  60. package/components/list/list-item-placement-marker.js +1 -1
  61. package/components/menu/menu-item-checkbox.js +1 -1
  62. package/components/menu/menu-item-radio.js +1 -1
  63. package/components/menu/menu-item-return.js +1 -1
  64. package/components/menu/menu.js +1 -1
  65. package/components/meter/meter-circle.js +1 -1
  66. package/components/meter/meter-linear.js +1 -1
  67. package/components/meter/meter-radial.js +1 -1
  68. package/components/object-property-list/object-property-list-item-link.js +1 -1
  69. package/components/offscreen/offscreen.js +1 -1
  70. package/components/overflow-group/overflow-group.js +1 -1
  71. package/components/paging/pager-load-more.js +1 -1
  72. package/components/scroll-wrapper/demo/scroll-wrapper-test.js +1 -1
  73. package/components/scroll-wrapper/scroll-wrapper.js +1 -1
  74. package/components/selection/selection-action-dropdown.js +1 -1
  75. package/components/selection/selection-action.js +1 -1
  76. package/components/selection/selection-controls.js +1 -1
  77. package/components/selection/selection-input.js +1 -1
  78. package/components/selection/selection-mixin.js +1 -1
  79. package/components/selection/selection-select-all-pages.js +1 -1
  80. package/components/selection/selection-select-all.js +1 -1
  81. package/components/skeleton/skeleton-mixin.js +1 -1
  82. package/components/switch/switch-mixin.js +2 -2
  83. package/components/table/demo/table-test.js +1 -1
  84. package/components/table/table-col-sort-button.js +1 -1
  85. package/components/table/table-wrapper.js +1 -1
  86. package/components/tabs/tab-internal.js +1 -1
  87. package/components/tabs/tabs.js +2 -2
  88. package/components/tag-list/tag-list-item-mixin.js +1 -1
  89. package/components/tag-list/tag-list.js +2 -2
  90. package/components/tooltip/tooltip-help.js +1 -1
  91. package/components/tooltip/tooltip.js +1 -1
  92. package/custom-elements.json +60 -0
  93. package/helpers/demo/prism.html +3 -3
  94. package/mixins/{arrow-keys-mixin.md → arrow-keys/README.md} +2 -2
  95. package/mixins/arrow-keys/arrow-keys-mixin.js +125 -0
  96. package/mixins/{demo → arrow-keys/demo}/arrow-keys-mixin.html +2 -2
  97. package/mixins/{demo → arrow-keys/demo}/arrow-keys-test.js +1 -1
  98. package/mixins/arrow-keys-mixin.js +1 -125
  99. package/mixins/{focus-mixin.md → focus/README.md} +1 -1
  100. package/mixins/focus/focus-mixin.js +43 -0
  101. package/mixins/focus-mixin.js +1 -43
  102. package/mixins/{interactive-mixin.md → interactive/README.md} +1 -1
  103. package/mixins/{interactive-mixin.js → interactive/interactive-mixin.js} +4 -4
  104. package/mixins/{labelled-mixin.md → labelled/README.md} +2 -2
  105. package/mixins/{demo → labelled/demo}/labelled-mixin.html +3 -3
  106. package/mixins/labelled/labelled-mixin.js +215 -0
  107. package/mixins/labelled-mixin.js +1 -215
  108. package/mixins/{provider-mixin.md → provider/README.md} +3 -3
  109. package/mixins/provider/provider-mixin.js +35 -0
  110. package/mixins/provider-mixin.js +1 -35
  111. package/mixins/{rtl-mixin.md → rtl/README.md} +1 -1
  112. package/mixins/rtl/rtl-mixin.js +40 -0
  113. package/mixins/rtl-mixin.js +1 -40
  114. package/mixins/theme/theme-mixin.js +19 -0
  115. package/mixins/theme-mixin.js +1 -19
  116. package/mixins/{visible-on-ancestor-mixin.md → visible-on-ancestor/README.md} +1 -1
  117. package/mixins/visible-on-ancestor/visible-on-ancestor-mixin.js +160 -0
  118. package/mixins/visible-on-ancestor-mixin.js +1 -160
  119. package/package.json +1 -1
  120. package/templates/primary-secondary/primary-secondary.js +1 -1
  121. package/tools/dom-test-helpers.js +4 -3
@@ -1,215 +1 @@
1
-
2
- import { cssEscape } from '../helpers/dom.js';
3
-
4
- const getCommonAncestor = (elem1, elem2) => {
5
-
6
- const labelledPath = new WeakMap();
7
- let elem = elem1;
8
- while (elem) {
9
- labelledPath.set(elem, elem);
10
- elem = elem.parentNode;
11
- }
12
-
13
- let ancestorElem = elem2.parentNode;
14
- while (ancestorElem) {
15
- if (labelledPath.has(ancestorElem)) return ancestorElem;
16
- ancestorElem = ancestorElem.parentNode;
17
- }
18
-
19
- };
20
-
21
- const isCustomElement = elem => {
22
- return elem.tagName.includes('-');
23
- };
24
-
25
- const getLabel = labelElem => {
26
- if (isCustomElement(labelElem)) return labelElem._label;
27
- else return labelElem.textContent;
28
- };
29
-
30
- const waitForElement = async(contextElement, selector, timeout) => {
31
- let elem = contextElement.querySelector(selector);
32
- if (elem) return elem;
33
-
34
- return new Promise(resolve => {
35
- let elapsedTime = 0;
36
- const intervalId = setInterval(() => {
37
- elem = contextElement.querySelector(selector);
38
- if (!elem) elapsedTime += 100;
39
- if (elem || elapsedTime > timeout) {
40
- clearInterval(intervalId);
41
- resolve(elem);
42
- return;
43
- }
44
- }, 100);
45
- });
46
- };
47
-
48
- export const LabelMixin = superclass => class extends superclass {
49
-
50
- static get properties() {
51
- return {
52
- _label: { type: String, reflect: true }
53
- };
54
- }
55
-
56
- connectedCallback() {
57
- super.connectedCallback();
58
- this.addEventListener('d2l-label-change', this._handleLabelChange);
59
- }
60
-
61
- disconnectedCallback() {
62
- super.disconnectedCallback();
63
- this.removeEventListener('d2l-label-change', this._handleLabelChange);
64
- }
65
-
66
- updateLabel(text) {
67
- this._label = text;
68
- }
69
-
70
- _handleLabelChange(e) {
71
- e.stopPropagation();
72
- this.updateLabel(e.detail);
73
- }
74
-
75
- };
76
-
77
- export const LabelledMixin = superclass => class extends superclass {
78
-
79
- static get properties() {
80
- return {
81
- /**
82
- * The id of element that provides the label for this element
83
- * @type {string}
84
- */
85
- labelledBy: { type: String, reflect: true, attribute: 'labelled-by' },
86
- /**
87
- * REQUIRED: Explicitly defined label for the element
88
- * @type {string}
89
- */
90
- label: { type: String }
91
- };
92
- }
93
-
94
- constructor() {
95
- super();
96
- this.labelRequired = true;
97
- this._labelElem = null;
98
- this._missingLabelErrorHasBeenThrown = false;
99
- this._validatingLabelTimeout = null;
100
- }
101
-
102
- firstUpdated(changedProperties) {
103
- super.firstUpdated(changedProperties);
104
- this._validateLabel(); // need to check this even if "label" isn't updated in case it's never set
105
- }
106
-
107
- async updated(changedProperties) {
108
-
109
- super.updated(changedProperties);
110
-
111
- if (changedProperties.has('label')) this._validateLabel();
112
-
113
- if (!changedProperties.has('labelledBy')) return;
114
-
115
- if (!this.labelledBy) {
116
- this._updateLabelElem(null);
117
- } else {
118
- const labelElem = await waitForElement(this.getRootNode(), `#${cssEscape(this.labelledBy)}`, 3000);
119
- if (!labelElem) {
120
- this._throwError(
121
- new Error(`LabelledMixin: "${this.tagName.toLowerCase()}" is labelled-by="${this.labelledBy}", but no such element exists`)
122
- );
123
- }
124
- this._updateLabelElem(labelElem);
125
- }
126
-
127
- }
128
-
129
- _throwError(err) {
130
- if (!this.labelRequired || this._missingLabelErrorHasBeenThrown) return;
131
- this._missingLabelErrorHasBeenThrown = true;
132
- setTimeout(() => { throw err; }); // we don't want to prevent rendering
133
- }
134
-
135
- _updateLabelElem(labelElem) {
136
-
137
- // setting textContent doesn't change labelElem but we do need to refetch the label
138
- if (labelElem === this._labelElem && this._labelElem) {
139
- this.label = getLabel(this._labelElem);
140
- return;
141
- }
142
-
143
- this._labelElem = labelElem;
144
-
145
- if (this._labelObserver) this._labelObserver.disconnect();
146
- if (!this._labelElem) {
147
- this.label = undefined;
148
- return;
149
- }
150
-
151
- this._labelObserver = new MutationObserver(() => {
152
- const newElem = this.getRootNode().querySelector(`#${cssEscape(this.labelledBy)}`);
153
- if (isCustomElement(newElem)) {
154
- requestAnimationFrame(() => {
155
- // element often sets its label in its own updated(), so we need to wait
156
- this._updateLabelElem(newElem);
157
- });
158
- } else {
159
- this._updateLabelElem(newElem);
160
- }
161
- });
162
-
163
- const ancestor = getCommonAncestor(this, this._labelElem);
164
-
165
- // assumption: the labelling element will not change from a native to a custom element
166
- // or vice versa, which allows the use of a more optimal observer configuration
167
- if (isCustomElement(this._labelElem)) {
168
- this._labelObserver.observe(ancestor, {
169
- attributes: true, // required for legacy-Edge, otherwise attributeFilter throws a syntax error
170
- attributeFilter: ['_label'],
171
- childList: true,
172
- subtree: true
173
- });
174
- } else {
175
- this._labelObserver.observe(ancestor, {
176
- characterData: true,
177
- childList: true,
178
- subtree: true
179
- });
180
- }
181
-
182
- this.label = getLabel(this._labelElem);
183
- /** @ignore */
184
- this.dispatchEvent(new CustomEvent(
185
- 'd2l-labelled-mixin-label-elem-change', {
186
- bubbles: false,
187
- composed: false
188
- }
189
- ));
190
-
191
- }
192
-
193
- _validateLabel() {
194
- clearTimeout(this._validatingLabelTimeout);
195
- // don't error immediately in case it doesn't get set immediately
196
- this._validatingLabelTimeout = setTimeout(() => {
197
- this._validatingLabelTimeout = null;
198
- const hasLabel = (typeof this.label === 'string') && this.label.length > 0;
199
- if (!hasLabel) {
200
- if (this.labelledBy) {
201
- if (this._labelElem) {
202
- this._throwError(
203
- new Error(`LabelledMixin: "${this.tagName.toLowerCase()}" is labelled-by="${this.labelledBy}", but its label is empty`)
204
- );
205
- }
206
- } else {
207
- this._throwError(
208
- new Error(`LabelledMixin: "${this.tagName.toLowerCase()}" is missing a required "label" attribute`)
209
- );
210
- }
211
- }
212
- }, 3000);
213
- }
214
-
215
- };
1
+ export { LabelMixin, LabelledMixin } from './labelled/labelled-mixin.js';
@@ -7,7 +7,7 @@ The `ProviderMixin` and `RequesterMixin` can be used to create a DI-like system
7
7
  Apply the `ProviderMixin` to the component that will be responsible for providing some data to components that request it:
8
8
 
9
9
  ```js
10
- import { ProviderMixin } from '@brightspace-ui/core/mixins/provider-mixin.js';
10
+ import { ProviderMixin } from '@brightspace-ui/core/mixins/provider/provider-mixin.js';
11
11
 
12
12
  class InterestingFactProvider extends ProviderMixin(LitElement) {
13
13
  constructor() {
@@ -24,7 +24,7 @@ Once this has been set up, child components can request your provider's data via
24
24
  NB: due to its reliance on DOM events, `requestInstance()` needs to be called after the element has been attached to the DOM, such as in `connectedCallback()`.
25
25
 
26
26
  ```js
27
- import { RequesterMixin } from '@brightspace-ui/core/mixins/provider-mixin.js'
27
+ import { RequesterMixin } from '@brightspace-ui/core/mixins/provider/provider-mixin.js'
28
28
 
29
29
  class InterestingFactUI extends RequesterMixin(LitElement) {
30
30
  static get properties() {
@@ -55,7 +55,7 @@ class InterestingFactUI extends RequesterMixin(LitElement) {
55
55
  In the absence of a component context, the `requestInstance` helper may be used by providing the `node` context and the `key` for the instance.
56
56
 
57
57
  ```js
58
- import { requestInstance } from '@brightspace-ui/core/mixins/provider-mixin.js'
58
+ import { requestInstance } from '@brightspace-ui/core/mixins/provider/provider-mixin.js'
59
59
 
60
60
  const factString = requestInstance(node, 'd2l-interesting-fact-string');
61
61
  const factObjectString = requestInstance(node, 'd2l-interesting-fact-object').fact;
@@ -0,0 +1,35 @@
1
+ export function provideInstance(node, key, obj) {
2
+ if (!node._providerInstances) {
3
+ node._providerInstances = new Map();
4
+ node.addEventListener('d2l-request-instance', e => {
5
+ if (node._providerInstances.has(e.detail.key)) {
6
+ e.detail.instance = node._providerInstances.get(e.detail.key);
7
+ e.stopPropagation();
8
+ }
9
+ });
10
+ }
11
+ node._providerInstances.set(key, obj);
12
+ }
13
+
14
+ export const ProviderMixin = superclass => class extends superclass {
15
+ provideInstance(key, obj) {
16
+ provideInstance(this, key, obj);
17
+ }
18
+ };
19
+
20
+ export function requestInstance(node, key) {
21
+ const event = new CustomEvent('d2l-request-instance', {
22
+ detail: { key },
23
+ bubbles: true,
24
+ composed: true,
25
+ cancelable: true
26
+ });
27
+ node.dispatchEvent(event);
28
+ return event.detail.instance;
29
+ }
30
+
31
+ export const RequesterMixin = superclass => class extends superclass {
32
+ requestInstance(key) {
33
+ return requestInstance(this, key);
34
+ }
35
+ };
@@ -1,35 +1 @@
1
- export function provideInstance(node, key, obj) {
2
- if (!node._providerInstances) {
3
- node._providerInstances = new Map();
4
- node.addEventListener('d2l-request-instance', e => {
5
- if (node._providerInstances.has(e.detail.key)) {
6
- e.detail.instance = node._providerInstances.get(e.detail.key);
7
- e.stopPropagation();
8
- }
9
- });
10
- }
11
- node._providerInstances.set(key, obj);
12
- }
13
-
14
- export const ProviderMixin = superclass => class extends superclass {
15
- provideInstance(key, obj) {
16
- provideInstance(this, key, obj);
17
- }
18
- };
19
-
20
- export function requestInstance(node, key) {
21
- const event = new CustomEvent('d2l-request-instance', {
22
- detail: { key },
23
- bubbles: true,
24
- composed: true,
25
- cancelable: true
26
- });
27
- node.dispatchEvent(event);
28
- return event.detail.instance;
29
- }
30
-
31
- export const RequesterMixin = superclass => class extends superclass {
32
- requestInstance(key) {
33
- return requestInstance(this, key);
34
- }
35
- };
1
+ export { provideInstance, ProviderMixin, requestInstance, RequesterMixin } from './provider/provider-mixin.js';
@@ -7,7 +7,7 @@ The `RtlMixin` creates `dir` attributes on host elements based on the document's
7
7
  Apply the mixin and define RTL styles.
8
8
 
9
9
  ```js
10
- import { RtlMixin } from '@brightspace-ui/core/mixins/rtl-mixin.js';
10
+ import { RtlMixin } from '@brightspace-ui/core/mixins/rtl/rtl-mixin.js';
11
11
  class MyComponent extends RtlMixin(LitElement) {
12
12
  static get styles() {
13
13
  return css`
@@ -0,0 +1,40 @@
1
+ import { dedupeMixin } from '@open-wc/dedupe-mixin';
2
+ import { getDocumentLocaleSettings } from '@brightspace-ui/intl/lib/common.js';
3
+
4
+ export const RtlMixin = dedupeMixin(superclass => class extends superclass {
5
+
6
+ static get properties() {
7
+ return {
8
+ /**
9
+ * @ignore
10
+ */
11
+ dir: { type: String, reflect: true }
12
+ };
13
+ }
14
+
15
+ constructor() {
16
+ super();
17
+ this._localeSettings = getDocumentLocaleSettings();
18
+ this._handleLanguageChange = this._handleLanguageChange.bind(this);
19
+ this._handleLanguageChange();
20
+ }
21
+
22
+ connectedCallback() {
23
+ super.connectedCallback();
24
+ this._localeSettings.addChangeListener(this._handleLanguageChange);
25
+ }
26
+
27
+ disconnectedCallback() {
28
+ super.disconnectedCallback();
29
+ this._localeSettings.removeChangeListener(this._handleLanguageChange);
30
+ }
31
+
32
+ _handleLanguageChange() {
33
+ const dir = document.documentElement.getAttribute('dir');
34
+ // avoid reflecting "ltr" for better performance
35
+ if (dir && (dir !== 'ltr' || this.dir === 'rtl')) {
36
+ this.dir = dir;
37
+ }
38
+ }
39
+
40
+ });
@@ -1,40 +1 @@
1
- import { dedupeMixin } from '@open-wc/dedupe-mixin';
2
- import { getDocumentLocaleSettings } from '@brightspace-ui/intl/lib/common.js';
3
-
4
- export const RtlMixin = dedupeMixin(superclass => class extends superclass {
5
-
6
- static get properties() {
7
- return {
8
- /**
9
- * @ignore
10
- */
11
- dir: { type: String, reflect: true }
12
- };
13
- }
14
-
15
- constructor() {
16
- super();
17
- this._localeSettings = getDocumentLocaleSettings();
18
- this._handleLanguageChange = this._handleLanguageChange.bind(this);
19
- this._handleLanguageChange();
20
- }
21
-
22
- connectedCallback() {
23
- super.connectedCallback();
24
- this._localeSettings.addChangeListener(this._handleLanguageChange);
25
- }
26
-
27
- disconnectedCallback() {
28
- super.disconnectedCallback();
29
- this._localeSettings.removeChangeListener(this._handleLanguageChange);
30
- }
31
-
32
- _handleLanguageChange() {
33
- const dir = document.documentElement.getAttribute('dir');
34
- // avoid reflecting "ltr" for better performance
35
- if (dir && (dir !== 'ltr' || this.dir === 'rtl')) {
36
- this.dir = dir;
37
- }
38
- }
39
-
40
- });
1
+ export { RtlMixin } from './rtl/rtl-mixin.js';
@@ -0,0 +1,19 @@
1
+ /**
2
+ * This is a draft mixin that may eventually be extended to support
3
+ * themed components, including "dark mode". At that point, the
4
+ * "theme" attribute could resolve automatically based on the user's
5
+ * OS preference. For now, it's only used in menus/dropdowns by
6
+ * the media player.
7
+ */
8
+ export const ThemeMixin = superclass => class extends superclass {
9
+
10
+ static get properties() {
11
+ return {
12
+ /**
13
+ * @ignore
14
+ */
15
+ theme: { reflect: true, type: String }
16
+ };
17
+ }
18
+
19
+ };
@@ -1,19 +1 @@
1
- /**
2
- * This is a draft mixin that may eventually be extended to support
3
- * themed components, including "dark mode". At that point, the
4
- * "theme" attribute could resolve automatically based on the user's
5
- * OS preference. For now, it's only used in menus/dropdowns by
6
- * the media player.
7
- */
8
- export const ThemeMixin = superclass => class extends superclass {
9
-
10
- static get properties() {
11
- return {
12
- /**
13
- * @ignore
14
- */
15
- theme: { reflect: true, type: String }
16
- };
17
- }
18
-
19
- };
1
+ export { ThemeMixin } from './theme/theme-mixin.js';
@@ -7,7 +7,7 @@ The `VisibleOnAncestorMixin` adds a behavior to a component so that it is initia
7
7
  Apply the mixin and include the required `visibleOnAncestorStyles`.
8
8
 
9
9
  ```js
10
- import { VisibleOnAncestorMixin, visibleOnAncestorStyles } from '@brightspace-ui/core/mixins/visible-on-ancestor-mixin.js';
10
+ import { VisibleOnAncestorMixin, visibleOnAncestorStyles } from '@brightspace-ui/core/mixins/visible-on-ancestor/visible-on-ancestor-mixin.js';
11
11
  class MyComponent extends VisibleOnAncestorMixin(LitElement) {
12
12
  static get styles() {
13
13
  return [ visibleOnAncestorStyles, css`/* MyComponent styles */` ];
@@ -0,0 +1,160 @@
1
+ import { findComposedAncestor, isComposedAncestor } from '../../helpers/dom.js';
2
+ import { css } from 'lit';
3
+
4
+ const reduceMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;
5
+
6
+ export const visibleOnAncestorStyles = css`
7
+
8
+ :host([__voa-state="hidden"]),
9
+ :host([__voa-state="hiding"]) {
10
+ opacity: 0 !important;
11
+ transform: translateY(-10px) !important;
12
+ }
13
+ :host([__voa-state="showing"]),
14
+ :host([__voa-state="hiding"]) {
15
+ transition: transform 200ms ease-out, opacity 200ms ease-out !important;
16
+ }
17
+
18
+ @media only screen and (hover: none), only screen and (-moz-touch-enabled: 1) {
19
+ :host([__voa-state="hidden"]),
20
+ :host([__voa-state="hiding"]) {
21
+ opacity: 1 !important;
22
+ transform: translateY(0) !important;
23
+ }
24
+ :host([__voa-state="hidden"][d2l-visible-on-ancestor-no-hover-hide]),
25
+ :host([__voa-state="hiding"][d2l-visible-on-ancestor-no-hover-hide]) {
26
+ opacity: 0 !important;
27
+ transform: translateY(-10px) !important;
28
+ }
29
+ }
30
+
31
+ `;
32
+
33
+ export const VisibleOnAncestorMixin = superclass => class extends superclass {
34
+
35
+ static get properties() {
36
+ return {
37
+ /**
38
+ * @ignore
39
+ */
40
+ visibleOnAncestor: { type: Boolean, reflect: true, attribute: 'visible-on-ancestor' },
41
+ __voaState: { type: String, reflect: true, attribute: '__voa-state' }
42
+ };
43
+ }
44
+
45
+ constructor() {
46
+ super();
47
+
48
+ this.visibleOnAncestor = false;
49
+ }
50
+
51
+ attributeChangedCallback(name, oldval, newval) {
52
+ if (name === 'visible-on-ancestor' && this.__voaAttached) {
53
+ if (newval) this.__voaInit();
54
+ else this.__voaUninit();
55
+ }
56
+ super.attributeChangedCallback(name, oldval, newval);
57
+ }
58
+
59
+ connectedCallback() {
60
+ super.connectedCallback();
61
+ this.__voaAttached = true;
62
+ if (this.visibleOnAncestor) {
63
+ requestAnimationFrame(() => this.__voaInit());
64
+ } else this.__voaState = null;
65
+ }
66
+
67
+ disconnectedCallback() {
68
+ this.__voaAttached = false;
69
+ this.__voaUninit();
70
+ super.disconnectedCallback();
71
+ }
72
+
73
+ __voaHandleBlur(e) {
74
+ if (isComposedAncestor(this.__voaTarget, e.relatedTarget)) return;
75
+ this.__voaFocusIn = false;
76
+ this.__voaHide();
77
+ }
78
+
79
+ __voaHandleFocus() {
80
+ this.__voaFocusIn = true;
81
+ this.__voaShow();
82
+ }
83
+
84
+ __voaHandleMouseEnter() {
85
+ this.__voaMouseOver = true;
86
+ this.__voaShow();
87
+ }
88
+
89
+ __voaHandleMouseLeave() {
90
+ this.__voaMouseOver = false;
91
+ this.__voaHide();
92
+ }
93
+
94
+ __voaHide() {
95
+ if (this.__voaFocusIn || this.__voaMouseOver) return;
96
+ if (reduceMotion) {
97
+ this.__voaState = 'hidden';
98
+ } else {
99
+ const handleTransitionEnd = (e) => {
100
+ if (e.propertyName !== 'transform') return;
101
+ this.removeEventListener('transitionend', handleTransitionEnd);
102
+ this.__voaState = 'hidden';
103
+ };
104
+ this.addEventListener('transitionend', handleTransitionEnd);
105
+ this.__voaState = 'hiding';
106
+ }
107
+ }
108
+
109
+ __voaInit() {
110
+
111
+ if (!this.visibleOnAncestor) return;
112
+
113
+ this.__voaTarget = findComposedAncestor(this, (node) => {
114
+ if (!node || node.nodeType !== 1) return false;
115
+ return (node.classList.contains('d2l-visible-on-ancestor-target'));
116
+ });
117
+ if (!this.__voaTarget) {
118
+ this.__voaState = null;
119
+ return;
120
+ }
121
+
122
+ this.__voaHandleBlur = this.__voaHandleBlur.bind(this);
123
+ this.__voaHandleFocus = this.__voaHandleFocus.bind(this);
124
+ this.__voaHandleMouseEnter = this.__voaHandleMouseEnter.bind(this);
125
+ this.__voaHandleMouseLeave = this.__voaHandleMouseLeave.bind(this);
126
+
127
+ this.__voaTarget.addEventListener('focus', this.__voaHandleFocus, true);
128
+ this.__voaTarget.addEventListener('blur', this.__voaHandleBlur, true);
129
+ this.__voaTarget.addEventListener('mouseenter', this.__voaHandleMouseEnter);
130
+ this.__voaTarget.addEventListener('mouseleave', this.__voaHandleMouseLeave);
131
+
132
+ this.__voaState = 'hidden';
133
+
134
+ }
135
+
136
+ __voaShow() {
137
+ if (reduceMotion) {
138
+ this.__voaState = 'shown';
139
+ } else {
140
+ const handleTransitionEnd = (e) => {
141
+ if (e.propertyName !== 'transform') return;
142
+ this.removeEventListener('transitionend', handleTransitionEnd);
143
+ this.__voaState = 'shown';
144
+ };
145
+ this.addEventListener('transitionend', handleTransitionEnd);
146
+ this.__voaState = 'showing';
147
+ }
148
+ }
149
+
150
+ __voaUninit() {
151
+ this.__voaState = null;
152
+ if (!this.__voaTarget) return;
153
+ this.__voaTarget.removeEventListener('focus', this.__voaHandleFocus, true);
154
+ this.__voaTarget.removeEventListener('blur', this.__voaHandleBlur, true);
155
+ this.__voaTarget.removeEventListener('mouseenter', this.__voaHandleMouseEnter);
156
+ this.__voaTarget.removeEventListener('mouseleave', this.__voaHandleMouseLeave);
157
+ this.__voaTarget = null;
158
+ }
159
+
160
+ };