@crowdstrike/glide-core 0.8.0 → 0.9.1

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 (181) hide show
  1. package/dist/accordion.d.ts +7 -3
  2. package/dist/button-group.button.d.ts +12 -16
  3. package/dist/button-group.button.js +1 -1
  4. package/dist/button-group.button.styles.js +76 -52
  5. package/dist/button-group.button.test.basics.d.ts +1 -1
  6. package/dist/button-group.button.test.basics.js +83 -147
  7. package/dist/button-group.button.test.events.js +8 -67
  8. package/dist/button-group.button.test.focus.js +13 -0
  9. package/dist/button-group.button.test.interactions.d.ts +1 -0
  10. package/dist/button-group.button.test.interactions.js +42 -0
  11. package/dist/button-group.d.ts +10 -10
  12. package/dist/button-group.js +1 -1
  13. package/dist/button-group.stories.d.ts +1 -5
  14. package/dist/button-group.styles.js +18 -6
  15. package/dist/button-group.test.basics.js +113 -234
  16. package/dist/button-group.test.events.js +210 -263
  17. package/dist/button-group.test.focus.d.ts +1 -0
  18. package/dist/button-group.test.focus.js +39 -0
  19. package/dist/button-group.test.interactions.d.ts +1 -0
  20. package/dist/button-group.test.interactions.js +91 -0
  21. package/dist/button.d.ts +3 -0
  22. package/dist/button.test.basics.js +1 -1
  23. package/dist/checkbox-group.d.ts +6 -2
  24. package/dist/checkbox-group.js +1 -1
  25. package/dist/checkbox-group.stories.d.ts +1 -1
  26. package/dist/checkbox-group.styles.js +1 -1
  27. package/dist/checkbox-group.test.basics.js +1 -1
  28. package/dist/checkbox-group.test.events.js +4 -4
  29. package/dist/checkbox-group.test.focus.js +4 -3
  30. package/dist/checkbox.d.ts +12 -5
  31. package/dist/checkbox.js +1 -1
  32. package/dist/checkbox.stories.d.ts +1 -1
  33. package/dist/checkbox.styles.js +10 -0
  34. package/dist/checkbox.test.basics.js +15 -6
  35. package/dist/checkbox.test.events.js +16 -8
  36. package/dist/checkbox.test.focus.js +3 -3
  37. package/dist/checkbox.test.form.js +1 -0
  38. package/dist/checkbox.test.interactions.js +123 -0
  39. package/dist/drawer.d.ts +5 -5
  40. package/dist/drawer.js +1 -1
  41. package/dist/drawer.stories.d.ts +0 -1
  42. package/dist/dropdown.d.ts +9 -6
  43. package/dist/dropdown.js +1 -1
  44. package/dist/dropdown.option.d.ts +6 -2
  45. package/dist/dropdown.option.js +1 -1
  46. package/dist/dropdown.option.styles.js +13 -0
  47. package/dist/dropdown.option.test.basics.js +6 -3
  48. package/dist/dropdown.option.test.events.js +1 -1
  49. package/dist/dropdown.option.test.focus.js +1 -1
  50. package/dist/dropdown.option.test.interactions.multiple.js +1 -54
  51. package/dist/dropdown.option.test.interactions.single.js +51 -9
  52. package/dist/dropdown.styles.js +20 -19
  53. package/dist/dropdown.test.basics.js +143 -2
  54. package/dist/dropdown.test.basics.multiple.js +5 -2
  55. package/dist/dropdown.test.events.filterable.js +74 -0
  56. package/dist/dropdown.test.events.js +49 -160
  57. package/dist/dropdown.test.events.multiple.js +265 -8
  58. package/dist/dropdown.test.events.single.js +199 -2
  59. package/dist/dropdown.test.focus.filterable.js +9 -5
  60. package/dist/dropdown.test.focus.js +1 -1
  61. package/dist/dropdown.test.focus.multiple.js +1 -1
  62. package/dist/dropdown.test.focus.single.js +1 -1
  63. package/dist/dropdown.test.interactions.filterable.js +68 -11
  64. package/dist/dropdown.test.interactions.js +94 -5
  65. package/dist/dropdown.test.interactions.multiple.js +202 -5
  66. package/dist/dropdown.test.interactions.single.js +68 -6
  67. package/dist/form-controls-layout.test.basics.js +1 -1
  68. package/dist/icon-button.d.ts +2 -0
  69. package/dist/icon-button.test.basics.js +1 -1
  70. package/dist/icons/checked.d.ts +1 -1
  71. package/dist/icons/checked.js +1 -1
  72. package/dist/icons/magnifying-glass.js +1 -1
  73. package/dist/input.d.ts +4 -9
  74. package/dist/input.js +1 -1
  75. package/dist/input.styles.js +7 -2
  76. package/dist/input.test.basics.js +19 -5
  77. package/dist/input.test.events.js +4 -4
  78. package/dist/input.test.focus.js +4 -4
  79. package/dist/input.test.translations.d.ts +1 -0
  80. package/dist/input.test.translations.js +38 -0
  81. package/dist/input.test.validity.js +133 -4
  82. package/dist/label.d.ts +1 -1
  83. package/dist/label.js +1 -1
  84. package/dist/label.styles.js +25 -13
  85. package/dist/label.test.basics.js +26 -24
  86. package/dist/library/expect-argument-error.js +1 -1
  87. package/dist/library/localize.d.ts +4 -1
  88. package/dist/menu.d.ts +3 -5
  89. package/dist/menu.js +1 -1
  90. package/dist/menu.options.test.basics.js +2 -2
  91. package/dist/menu.styles.js +1 -15
  92. package/dist/menu.test.basics.d.ts +1 -2
  93. package/dist/menu.test.basics.js +22 -6
  94. package/dist/menu.test.events.js +197 -7
  95. package/dist/menu.test.focus.d.ts +1 -0
  96. package/dist/menu.test.focus.js +13 -6
  97. package/dist/menu.test.interactions.js +214 -58
  98. package/dist/modal.icon-button.test.basics.js +1 -1
  99. package/dist/modal.js +1 -1
  100. package/dist/modal.stories.d.ts +1 -0
  101. package/dist/modal.styles.js +18 -13
  102. package/dist/modal.tertiary-icon.d.ts +0 -1
  103. package/dist/modal.tertiary-icon.js +1 -1
  104. package/dist/modal.tertiary-icon.test.basics.js +3 -3
  105. package/dist/modal.test.basics.js +1 -1
  106. package/dist/modal.test.events.js +10 -10
  107. package/dist/radio-group.d.ts +4 -3
  108. package/dist/radio-group.js +1 -1
  109. package/dist/radio-group.stories.d.ts +1 -1
  110. package/dist/radio-group.styles.js +1 -1
  111. package/dist/radio-group.test.focus.js +3 -3
  112. package/dist/radio.d.ts +2 -2
  113. package/dist/radio.js +1 -1
  114. package/dist/radio.styles.js +33 -0
  115. package/dist/split-container.d.ts +1 -1
  116. package/dist/split-container.test.basics.js +4 -0
  117. package/dist/split-link.test.interactions.js +1 -1
  118. package/dist/status-indicator.d.ts +1 -1
  119. package/dist/styles/variables.css +1 -1
  120. package/dist/tab.d.ts +1 -1
  121. package/dist/tab.group.js +1 -1
  122. package/dist/tab.group.test.basics.js +1 -1
  123. package/dist/tab.group.test.interactions.js +198 -2
  124. package/dist/tab.js +1 -1
  125. package/dist/tab.panel.d.ts +1 -0
  126. package/dist/tab.panel.js +1 -1
  127. package/dist/tab.panel.styles.js +11 -1
  128. package/dist/tabs.stories.d.ts +1 -0
  129. package/dist/tag.d.ts +3 -6
  130. package/dist/tag.test.basics.js +2 -2
  131. package/dist/textarea.d.ts +4 -4
  132. package/dist/textarea.js +2 -2
  133. package/dist/textarea.stories.d.ts +3 -4
  134. package/dist/textarea.styles.js +14 -3
  135. package/dist/textarea.test.basics.js +80 -44
  136. package/dist/textarea.test.events.js +56 -41
  137. package/dist/textarea.test.translations.d.ts +1 -0
  138. package/dist/textarea.test.translations.js +34 -0
  139. package/dist/textarea.test.validity.js +104 -20
  140. package/dist/toasts.js +1 -1
  141. package/dist/toasts.styles.js +8 -1
  142. package/dist/toasts.test.basics.js +20 -0
  143. package/dist/toggle.d.ts +3 -3
  144. package/dist/toggle.js +1 -1
  145. package/dist/toggle.stories.d.ts +1 -1
  146. package/dist/toggle.test.focus.js +1 -1
  147. package/dist/toggle.test.interactions.d.ts +1 -0
  148. package/dist/{toggle.test.states.js → toggle.test.interactions.js} +26 -0
  149. package/dist/tooltip.d.ts +9 -7
  150. package/dist/tooltip.js +1 -1
  151. package/dist/tooltip.styles.js +90 -25
  152. package/dist/tooltip.test.basics.js +38 -3
  153. package/dist/tooltip.test.interactions.js +136 -34
  154. package/dist/translations/en.js +1 -1
  155. package/dist/translations/fr.js +1 -1
  156. package/dist/translations/ja.js +1 -1
  157. package/dist/tree.d.ts +1 -2
  158. package/dist/tree.item.d.ts +1 -5
  159. package/dist/tree.item.icon-button.d.ts +1 -0
  160. package/dist/tree.item.icon-button.js +1 -1
  161. package/dist/tree.item.icon-button.test.basics.js +9 -0
  162. package/dist/tree.item.js +1 -1
  163. package/dist/tree.item.menu.d.ts +2 -1
  164. package/dist/tree.item.menu.js +1 -1
  165. package/dist/tree.item.menu.test.basics.js +15 -0
  166. package/dist/tree.item.styles.js +2 -0
  167. package/dist/tree.item.test.basics.d.ts +2 -1
  168. package/dist/tree.item.test.basics.js +46 -4
  169. package/dist/tree.js +1 -1
  170. package/dist/tree.test.basics.js +1 -1
  171. package/dist/tree.test.focus.js +91 -4
  172. package/package.json +3 -4
  173. package/dist/checkbox.test.states.js +0 -63
  174. package/dist/drawer.test.floating-components.d.ts +0 -1
  175. package/dist/drawer.test.floating-components.js +0 -52
  176. package/dist/library/set-containing-block.d.ts +0 -15
  177. package/dist/library/set-containing-block.js +0 -1
  178. package/dist/modal.test.floating-components.js +0 -63
  179. /package/dist/{checkbox.test.states.d.ts → button-group.button.test.focus.d.ts} +0 -0
  180. /package/dist/{modal.test.floating-components.d.ts → checkbox.test.interactions.d.ts} +0 -0
  181. /package/dist/{toggle.test.states.d.ts → dropdown.test.events.filterable.d.ts} +0 -0
@@ -1,20 +1,20 @@
1
1
  /* eslint-disable @typescript-eslint/no-unused-expressions */
2
- import { expect, fixture, html } from '@open-wc/testing';
2
+ import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
3
3
  import { sendKeys } from '@web/test-runner-commands';
4
4
  import GlideCoreTextarea from './textarea.js';
5
5
  GlideCoreTextarea.shadowRootOptions.mode = 'open';
6
6
  it('is valid by default', async () => {
7
- const template = '<glide-core-textarea></glide-core-textarea>';
8
- const textarea = await fixture(template);
7
+ const textarea = await fixture(html `<glide-core-textarea></glide-core-textarea>`);
9
8
  expect(textarea.validity?.valid).to.be.true;
10
9
  expect(textarea.validity?.valueMissing).to.be.false;
11
10
  expect(textarea.validity?.tooLong).to.be.false;
12
11
  expect(textarea.checkValidity()).to.be.true;
13
12
  expect(textarea.reportValidity()).to.be.true;
13
+ await elementUpdated(textarea);
14
+ expect(textarea.shadowRoot?.querySelector('textarea')).to.have.attribute('aria-invalid', 'false');
14
15
  });
15
16
  it('is valid after being filled in and required', async () => {
16
- const template = '<glide-core-textarea required></glide-core-textarea>';
17
- const textarea = await fixture(template);
17
+ const textarea = await fixture(html `<glide-core-textarea required></glide-core-textarea>`);
18
18
  textarea.focus();
19
19
  await sendKeys({ type: 'value' });
20
20
  expect(textarea.validity?.valid).to.be.true;
@@ -22,29 +22,32 @@ it('is valid after being filled in and required', async () => {
22
22
  expect(textarea.validity?.tooLong).to.be.false;
23
23
  expect(textarea.checkValidity()).to.be.true;
24
24
  expect(textarea.reportValidity()).to.be.true;
25
+ await elementUpdated(textarea);
26
+ expect(textarea.shadowRoot?.querySelector('textarea')).to.have.attribute('aria-invalid', 'false');
25
27
  });
26
28
  it('is invalid if no value and required', async () => {
27
- const template = '<glide-core-textarea required></glide-core-textarea>';
28
- const textarea = await fixture(template);
29
+ const textarea = await fixture(html `<glide-core-textarea required></glide-core-textarea>`);
29
30
  expect(textarea.validity?.valid).to.be.false;
30
31
  expect(textarea.validity?.valueMissing).to.be.true;
31
32
  expect(textarea.validity?.tooLong).to.be.false;
32
33
  expect(textarea.willValidate).to.be.true;
33
34
  expect(textarea.checkValidity()).to.be.false;
34
35
  expect(textarea.reportValidity()).to.be.false;
36
+ await elementUpdated(textarea);
37
+ expect(textarea.shadowRoot?.querySelector('textarea')).to.have.attribute('aria-invalid', 'true');
35
38
  });
36
39
  it('is valid when empty and does not exceed `maxlength`', async () => {
37
- const template = '<glide-core-textarea maxlength="3"></glide-core-textarea>';
38
- const textarea = await fixture(template);
40
+ const textarea = await fixture(html `<glide-core-textarea maxlength="3"></glide-core-textarea>`);
39
41
  expect(textarea.validity?.valid).to.be.true;
40
42
  expect(textarea.validity?.valueMissing).to.be.false;
41
43
  expect(textarea.validity?.tooLong).to.be.false;
42
44
  expect(textarea.checkValidity()).to.be.true;
43
45
  expect(textarea.reportValidity()).to.be.true;
46
+ await elementUpdated(textarea);
47
+ expect(textarea.shadowRoot?.querySelector('textarea')).to.have.attribute('aria-invalid', 'false');
44
48
  });
45
49
  it('is valid when filled in and does not exceed `maxlength`', async () => {
46
- const template = '<glide-core-textarea maxlength="3"></glide-core-textarea>';
47
- const textarea = await fixture(template);
50
+ const textarea = await fixture(html `<glide-core-textarea maxlength="3"></glide-core-textarea>`);
48
51
  textarea.focus();
49
52
  await sendKeys({ type: 'abc' });
50
53
  expect(textarea.validity?.valid).to.be.true;
@@ -52,10 +55,11 @@ it('is valid when filled in and does not exceed `maxlength`', async () => {
52
55
  expect(textarea.validity?.tooLong).to.be.false;
53
56
  expect(textarea.checkValidity()).to.be.true;
54
57
  expect(textarea.reportValidity()).to.be.true;
58
+ await elementUpdated(textarea);
59
+ expect(textarea.shadowRoot?.querySelector('textarea')).to.have.attribute('aria-invalid', 'false');
55
60
  });
56
61
  it('is invalid when filled in and exceeds `maxlength`', async () => {
57
- const template = '<glide-core-textarea maxlength="3"></glide-core-textarea>';
58
- const textarea = await fixture(template);
62
+ const textarea = await fixture(html `<glide-core-textarea maxlength="3"></glide-core-textarea>`);
59
63
  textarea.focus();
60
64
  await sendKeys({ type: 'value' });
61
65
  expect(textarea.validity?.valid).to.be.false;
@@ -63,24 +67,104 @@ it('is invalid when filled in and exceeds `maxlength`', async () => {
63
67
  expect(textarea.validity?.tooLong).to.be.true;
64
68
  expect(textarea.checkValidity()).to.be.false;
65
69
  expect(textarea.reportValidity()).to.be.false;
70
+ await elementUpdated(textarea);
71
+ expect(textarea.shadowRoot?.querySelector('textarea')).to.have.attribute('aria-invalid', 'true');
66
72
  });
67
73
  it('is valid if no value but required and disabled', async () => {
68
- const template = '<glide-core-textarea required disabled></glide-core-textarea>';
69
- const textarea = await fixture(template);
74
+ const textarea = await fixture(html `<glide-core-textarea required disabled></glide-core-textarea>`);
75
+ expect(textarea.validity?.valid).to.be.true;
76
+ expect(textarea.validity?.valueMissing).to.be.false;
77
+ expect(textarea.validity?.tooLong).to.be.false;
78
+ expect(textarea.checkValidity()).to.be.true;
79
+ expect(textarea.reportValidity()).to.be.true;
80
+ await elementUpdated(textarea);
81
+ expect(textarea.shadowRoot?.querySelector('textarea')).to.have.attribute('aria-invalid', 'false');
82
+ });
83
+ it('updates validity when `required` and `value` is changed programmatically', async () => {
84
+ const textarea = await fixture(html `<glide-core-textarea label="Label" required></glide-core-textarea>`);
85
+ expect(textarea.validity?.valid).to.be.false;
86
+ expect(textarea.validity?.valueMissing).to.be.true;
87
+ expect(textarea.checkValidity()).to.be.false;
88
+ expect(textarea.reportValidity()).to.be.false;
89
+ await elementUpdated(textarea);
90
+ expect(textarea.shadowRoot?.querySelector('textarea')).to.have.attribute('aria-invalid', 'true');
91
+ textarea.value = 'text';
92
+ await elementUpdated(textarea);
93
+ expect(textarea.validity?.valid).to.be.true;
94
+ expect(textarea.validity?.valueMissing).to.be.false;
95
+ expect(textarea.checkValidity()).to.be.true;
96
+ expect(textarea.reportValidity()).to.be.true;
97
+ await elementUpdated(textarea);
98
+ expect(textarea.shadowRoot?.querySelector('textarea')).to.have.attribute('aria-invalid', 'false');
99
+ // Resetting the value to empty to ensure it goes
100
+ // back to an invalid state
101
+ textarea.value = '';
102
+ await elementUpdated(textarea);
103
+ expect(textarea.validity?.valid).to.be.false;
104
+ expect(textarea.validity?.valueMissing).to.be.true;
105
+ expect(textarea.checkValidity()).to.be.false;
106
+ expect(textarea.reportValidity()).to.be.false;
107
+ expect(textarea.shadowRoot?.querySelector('textarea')).to.have.attribute('aria-invalid', 'true');
108
+ });
109
+ it('is invalid when `value` is empty and `required` is set to `true` programmatically', async () => {
110
+ const textarea = await fixture(html `<glide-core-textarea label="Label"></glide-core-textarea>`);
111
+ expect(textarea.validity?.valid).to.be.true;
112
+ expect(textarea.validity?.valueMissing).to.be.false;
113
+ expect(textarea.checkValidity()).to.be.true;
114
+ expect(textarea.reportValidity()).to.be.true;
115
+ await elementUpdated(textarea);
116
+ expect(textarea.shadowRoot?.querySelector('textarea')).to.have.attribute('aria-invalid', 'false');
117
+ textarea.required = true;
118
+ await elementUpdated(textarea);
119
+ expect(textarea.validity?.valid).to.be.false;
120
+ expect(textarea.validity?.valueMissing).to.be.true;
121
+ expect(textarea.checkValidity()).to.be.false;
122
+ expect(textarea.reportValidity()).to.be.false;
123
+ expect(textarea.shadowRoot?.querySelector('textarea')).to.have.attribute('aria-invalid', 'true');
124
+ });
125
+ it('is valid when `value` is empty and `required` is set to `false` programmatically', async () => {
126
+ const textarea = await fixture(html `<glide-core-textarea label="Label" required></glide-core-textarea>`);
127
+ expect(textarea.validity?.valid).to.be.false;
128
+ expect(textarea.validity?.valueMissing).to.be.true;
129
+ expect(textarea.checkValidity()).to.be.false;
130
+ expect(textarea.reportValidity()).to.be.false;
131
+ await elementUpdated(textarea);
132
+ expect(textarea.shadowRoot?.querySelector('textarea')).to.have.attribute('aria-invalid', 'true');
133
+ textarea.required = false;
134
+ await elementUpdated(textarea);
135
+ expect(textarea.validity?.valid).to.be.true;
136
+ expect(textarea.validity?.valueMissing).to.be.false;
137
+ expect(textarea.checkValidity()).to.be.true;
138
+ expect(textarea.reportValidity()).to.be.true;
139
+ expect(textarea.shadowRoot?.querySelector('textarea')).to.have.attribute('aria-invalid', 'false');
140
+ });
141
+ it('is valid when filled in, disabled, and value exceeds `maxlength`', async () => {
142
+ const textarea = await fixture(html `<glide-core-textarea
143
+ value="value"
144
+ disabled
145
+ maxlength="3"
146
+ ></glide-core-textarea>`);
70
147
  expect(textarea.validity?.valid).to.be.true;
71
148
  expect(textarea.validity?.valueMissing).to.be.false;
72
149
  expect(textarea.validity?.tooLong).to.be.false;
73
150
  expect(textarea.checkValidity()).to.be.true;
74
151
  expect(textarea.reportValidity()).to.be.true;
152
+ await elementUpdated(textarea);
153
+ expect(textarea.shadowRoot?.querySelector('textarea')).to.have.attribute('aria-invalid', 'false');
75
154
  });
76
- it('is valid when filled in, disabled, and exceeds `maxlength`', async () => {
77
- const template = '<glide-core-textarea value="value" disabled maxlength="3"></glide-core-textarea>';
78
- const textarea = await fixture(template);
155
+ it('is valid when filled in, readonly, and value exceeds `maxlength`', async () => {
156
+ const textarea = await fixture(html `<glide-core-textarea
157
+ value="value"
158
+ readonly
159
+ maxlength="3"
160
+ ></glide-core-textarea>`);
79
161
  expect(textarea.validity?.valid).to.be.true;
80
162
  expect(textarea.validity?.valueMissing).to.be.false;
81
163
  expect(textarea.validity?.tooLong).to.be.false;
82
164
  expect(textarea.checkValidity()).to.be.true;
83
165
  expect(textarea.reportValidity()).to.be.true;
166
+ await elementUpdated(textarea);
167
+ expect(textarea.shadowRoot?.querySelector('textarea')).to.have.attribute('aria-invalid', 'false');
84
168
  });
85
169
  it('blurs the textarea and reports validity if `blur` is called', async () => {
86
170
  const textarea = await fixture(html `<glide-core-textarea required></glide-core-textarea>`);
@@ -91,6 +175,6 @@ it('blurs the textarea and reports validity if `blur` is called', async () => {
91
175
  await textarea.updateComplete;
92
176
  expect(textarea.shadowRoot?.activeElement === null).to.be.true;
93
177
  expect(textarea.validity.valid).to.equal(false);
94
- expect(textarea.shadowRoot?.querySelector('glide-core-label')?.error).to.be
95
- .true;
178
+ expect(textarea.shadowRoot?.querySelector('glide-core-private-label')?.error)
179
+ .to.be.true;
96
180
  });
package/dist/toasts.js CHANGED
@@ -1 +1 @@
1
- var __decorate=this&&this.__decorate||function(e,t,o,r){var s,i=arguments.length,l=i<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,o):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)l=Reflect.decorate(e,t,o,r);else for(var n=e.length-1;n>=0;n--)(s=e[n])&&(l=(i<3?s(l):i>3?s(t,o,l):s(t,o))||l);return i>3&&l&&Object.defineProperty(t,o,l),l};import"./toasts.toast.js";import{LitElement,html}from"lit";import{LocalizeController}from"./library/localize.js";import{createRef,ref}from"lit/directives/ref.js";import{customElement}from"lit/decorators.js";import ow from"./library/ow.js";import styles from"./toasts.styles.js";let GlideCoreToasts=class GlideCoreToasts extends LitElement{static{this.shadowRootOptions={...LitElement.shadowRootOptions,mode:"closed"}}static{this.styles=styles}add(e){const{variant:t,label:o,description:r,duration:s}=e,i=Object.assign(document.createElement("glide-core-toast"),{variant:t,label:o,description:r,duration:s});return ow(this.#e.value,ow.object.instanceOf(Element)),this.#e.value.append(i),i.addEventListener("close",(()=>{i.remove()}),{once:!0}),i}render(){return html`<div class="component" role="region" tabindex="-1" aria-label="${this.#t.term("notifications")}" ${ref(this.#e)}></div>`}#e=createRef();#t=new LocalizeController(this)};GlideCoreToasts=__decorate([customElement("glide-core-toasts")],GlideCoreToasts);export default GlideCoreToasts;
1
+ var __decorate=this&&this.__decorate||function(e,t,o,r){var l,s=arguments.length,n=s<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,o):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,o,r);else for(var i=e.length-1;i>=0;i--)(l=e[i])&&(n=(s<3?l(n):s>3?l(t,o,n):l(t,o))||n);return s>3&&n&&Object.defineProperty(t,o,n),n};import"./toasts.toast.js";import{LitElement,html}from"lit";import{LocalizeController}from"./library/localize.js";import{createRef,ref}from"lit/directives/ref.js";import{customElement}from"lit/decorators.js";import ow from"./library/ow.js";import styles from"./toasts.styles.js";let GlideCoreToasts=class GlideCoreToasts extends LitElement{static{this.shadowRootOptions={...LitElement.shadowRootOptions,mode:"closed"}}static{this.styles=styles}add(e){ow(this.#e.value,ow.object.instanceOf(Element)),this.#e.value.popover="manual",this.#e.value.showPopover();const{variant:t,label:o,description:r,duration:l}=e,s=Object.assign(document.createElement("glide-core-toast"),{variant:t,label:o,description:r,duration:l});return this.#e.value.append(s),s.addEventListener("close",(()=>{s.remove(),0===this.#e.value?.querySelectorAll("glide-core-toast").length&&this.#e.value?.hidePopover()}),{once:!0}),s}render(){return html`<div class="component" role="region" tabindex="-1" aria-label="${this.#t.term("notifications")}" ${ref(this.#e)}></div>`}#e=createRef();#t=new LocalizeController(this)};GlideCoreToasts=__decorate([customElement("glide-core-toasts")],GlideCoreToasts);export default GlideCoreToasts;
@@ -1,15 +1,22 @@
1
1
  import{css}from"lit";export default[css`
2
2
  .component {
3
- display: flex;
3
+ background-color: transparent;
4
+ border: none;
5
+ display: none;
4
6
  flex-direction: column;
5
7
  gap: var(--glide-core-spacing-md);
6
8
  inline-size: 24.25rem;
7
9
  inset-block-start: 0;
8
10
  inset-inline-end: 0;
11
+ margin: 0 0 auto auto;
9
12
  max-block-size: 100%;
10
13
  max-inline-size: 100%;
11
14
  overflow: hidden;
12
15
  padding: var(--glide-core-spacing-sm);
13
16
  position: fixed;
17
+
18
+ &:popover-open {
19
+ display: flex;
20
+ }
14
21
  }
15
22
  `];
@@ -92,3 +92,23 @@ it('removes a closed toast from the DOM', async () => {
92
92
  toasts = component.shadowRoot?.querySelectorAll('glide-core-toast');
93
93
  expect(toasts?.length).to.equal(0);
94
94
  });
95
+ it('is hidden unless there are toasts displayed', async () => {
96
+ const component = await fixture(html `<glide-core-toasts></glide-core-toasts>`);
97
+ const shadowComponent = component.shadowRoot?.querySelector('.component');
98
+ assert(shadowComponent);
99
+ expect(shadowComponent.hasAttribute('popover')).to.equal(false);
100
+ expect(getComputedStyle(shadowComponent).display).to.equal('none');
101
+ component.add({
102
+ label: 'Test toast',
103
+ description: 'Test toast description',
104
+ variant: 'informational',
105
+ });
106
+ expect(shadowComponent.getAttribute('popover')).to.equal('manual');
107
+ expect(getComputedStyle(shadowComponent).display).to.equal('flex');
108
+ const toasts = component.shadowRoot?.querySelectorAll('glide-core-toast');
109
+ assert(toasts);
110
+ const toast = toasts[0];
111
+ toast.close();
112
+ toast.dispatchEvent(new Event('close', { bubbles: true }));
113
+ expect(getComputedStyle(shadowComponent).display).to.equal('none');
114
+ });
package/dist/toggle.d.ts CHANGED
@@ -8,9 +8,9 @@ declare global {
8
8
  /**
9
9
  * @description A toggle with a label and optional tooltip, summary, and description.
10
10
  *
11
- * @event change - Dispatched when checked or unchecked.
12
- * @event input - Dispatched when checked or unchecked.
13
-
11
+ * @event change - `(event: Event) => void`
12
+ * @event input - `(event: Event) => void`
13
+ *
14
14
  * @slot description - Additional information or context.
15
15
  * @slot tooltip - Content for the tooltip.
16
16
  */
package/dist/toggle.js CHANGED
@@ -1 +1 @@
1
- var __decorate=this&&this.__decorate||function(e,t,o,i){var l,r=arguments.length,s=r<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,o):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)s=Reflect.decorate(e,t,o,i);else for(var d=e.length-1;d>=0;d--)(l=e[d])&&(s=(r<3?l(s):r>3?l(t,o,s):l(t,o))||s);return r>3&&s&&Object.defineProperty(t,o,s),s};import"./label.js";import{LitElement,html}from"lit";import{createRef,ref}from"lit/directives/ref.js";import{customElement,property}from"lit/decorators.js";import{ifDefined}from"lit/directives/if-defined.js";import styles from"./toggle.styles.js";let GlideCoreToggle=class GlideCoreToggle extends LitElement{constructor(){super(...arguments),this.checked=!1,this.disabled=!1,this.hideLabel=!1,this.orientation="horizontal",this.#e=createRef()}static{this.shadowRootOptions={...LitElement.shadowRootOptions,mode:"closed"}}static{this.styles=styles}click(){this.#e.value?.click()}focus(e){this.#e.value?.focus(e)}render(){return html`<div data-test="component"><glide-core-label orientation="${this.orientation}" split="${ifDefined(this.privateSplit??void 0)}" ?disabled="${this.disabled}" ?hide="${this.hideLabel}"><slot name="tooltip" slot="tooltip"></slot><label for="input">${this.label}</label><div class="toggle-and-input" slot="control"><input aria-checked="${this.checked}" aria-describedby="summary description" data-test="input" id="input" role="switch" type="checkbox" ?checked="${this.checked}" ?disabled="${this.disabled}" @change="${this.#t}" ${ref(this.#e)}></div><div slot="summary" id="summary">${this.summary}</div><slot class="description" id="description" name="description" slot="description"></slot></glide-core-label></div>`}#e;#t(e){e.target instanceof HTMLInputElement&&(this.checked=e.target.checked),this.dispatchEvent(new Event(e.type,e))}};__decorate([property({type:Boolean})],GlideCoreToggle.prototype,"checked",void 0),__decorate([property({reflect:!0,type:Boolean})],GlideCoreToggle.prototype,"disabled",void 0),__decorate([property({attribute:"hide-label",type:Boolean})],GlideCoreToggle.prototype,"hideLabel",void 0),__decorate([property({reflect:!0})],GlideCoreToggle.prototype,"label",void 0),__decorate([property({reflect:!0})],GlideCoreToggle.prototype,"orientation",void 0),__decorate([property({reflect:!0})],GlideCoreToggle.prototype,"name",void 0),__decorate([property()],GlideCoreToggle.prototype,"privateSplit",void 0),__decorate([property({reflect:!0})],GlideCoreToggle.prototype,"summary",void 0),GlideCoreToggle=__decorate([customElement("glide-core-toggle")],GlideCoreToggle);export default GlideCoreToggle;
1
+ var __decorate=this&&this.__decorate||function(e,t,o,i){var r,l=arguments.length,s=l<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,o):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)s=Reflect.decorate(e,t,o,i);else for(var d=e.length-1;d>=0;d--)(r=e[d])&&(s=(l<3?r(s):l>3?r(t,o,s):r(t,o))||s);return l>3&&s&&Object.defineProperty(t,o,s),s};import"./label.js";import{LitElement,html}from"lit";import{createRef,ref}from"lit/directives/ref.js";import{customElement,property}from"lit/decorators.js";import{ifDefined}from"lit/directives/if-defined.js";import styles from"./toggle.styles.js";let GlideCoreToggle=class GlideCoreToggle extends LitElement{constructor(){super(...arguments),this.checked=!1,this.disabled=!1,this.hideLabel=!1,this.orientation="horizontal",this.#e=createRef()}static{this.shadowRootOptions={...LitElement.shadowRootOptions,mode:"closed"}}static{this.styles=styles}click(){this.#e.value?.click()}focus(e){this.#e.value?.focus(e)}render(){return html`<div data-test="component"><glide-core-private-label orientation="${this.orientation}" split="${ifDefined(this.privateSplit??void 0)}" ?disabled="${this.disabled}" ?hide="${this.hideLabel}"><slot name="tooltip" slot="tooltip"></slot><label for="input">${this.label}</label><div class="toggle-and-input" slot="control"><input aria-checked="${this.checked}" aria-describedby="summary description" data-test="input" id="input" role="switch" type="checkbox" .checked="${this.checked}" ?disabled="${this.disabled}" @change="${this.#t}" @input="${this.#t}" ${ref(this.#e)}></div><div slot="summary" id="summary">${this.summary}</div><slot class="description" id="description" name="description" slot="description"></slot></glide-core-private-label></div>`}#e;#t(e){e.target instanceof HTMLInputElement&&(this.checked=e.target.checked),"change"===e.type&&this.dispatchEvent(new Event(e.type,e))}};__decorate([property({type:Boolean})],GlideCoreToggle.prototype,"checked",void 0),__decorate([property({reflect:!0,type:Boolean})],GlideCoreToggle.prototype,"disabled",void 0),__decorate([property({attribute:"hide-label",type:Boolean})],GlideCoreToggle.prototype,"hideLabel",void 0),__decorate([property({reflect:!0})],GlideCoreToggle.prototype,"label",void 0),__decorate([property({reflect:!0})],GlideCoreToggle.prototype,"orientation",void 0),__decorate([property({reflect:!0})],GlideCoreToggle.prototype,"name",void 0),__decorate([property()],GlideCoreToggle.prototype,"privateSplit",void 0),__decorate([property({reflect:!0})],GlideCoreToggle.prototype,"summary",void 0),GlideCoreToggle=__decorate([customElement("glide-core-toggle")],GlideCoreToggle);export default GlideCoreToggle;
@@ -4,4 +4,4 @@ export default meta;
4
4
  export declare const Horizontal: StoryObj;
5
5
  export declare const HorizontalWithTooltip: StoryObj;
6
6
  export declare const Vertical: StoryObj;
7
- export declare const VerticalWithToolip: StoryObj;
7
+ export declare const VerticalWithTooltip: StoryObj;
@@ -1,7 +1,7 @@
1
1
  import { expect, fixture, html } from '@open-wc/testing';
2
2
  import GlideCoreToggle from './toggle.js';
3
3
  GlideCoreToggle.shadowRootOptions.mode = 'open';
4
- it('focuses the input when `focus` is called', async () => {
4
+ it('focuses the input when `focus()` is called', async () => {
5
5
  const component = await fixture(html `<glide-core-toggle label="Label"></glide-core-toggle>`);
6
6
  component.focus();
7
7
  const input = component.shadowRoot?.querySelector('[data-test="input"]');
@@ -0,0 +1 @@
1
+ export {};
@@ -42,3 +42,29 @@ it('is still unchecked after being clicked when unchecked and disabled', async (
42
42
  const input = component.shadowRoot?.querySelector('[data-test="input"]');
43
43
  expect(input?.getAttribute('aria-checked')).to.equal('false');
44
44
  });
45
+ it('is unchecked after being clicked then forcibly unchecked via a "change" listener', async () => {
46
+ const component = await fixture(html `<glide-core-toggle label="Label"></glide-core-toggle>`);
47
+ component.addEventListener('change', async () => {
48
+ await component.updateComplete;
49
+ component.checked = false;
50
+ });
51
+ component.click();
52
+ await elementUpdated(component);
53
+ expect(component.hasAttribute('checked')).to.be.false;
54
+ expect(component.checked).to.equal(false);
55
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
56
+ expect(input?.getAttribute('aria-checked')).to.equal('false');
57
+ });
58
+ it('is unchecked after being clicked then forcibly unchecked via an "input" listener', async () => {
59
+ const component = await fixture(html `<glide-core-toggle label="Label"></glide-core-toggle>`);
60
+ component.addEventListener('input', async () => {
61
+ await component.updateComplete;
62
+ component.checked = false;
63
+ });
64
+ component.click();
65
+ await elementUpdated(component);
66
+ expect(component.hasAttribute('checked')).to.be.false;
67
+ expect(component.checked).to.equal(false);
68
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
69
+ expect(input?.getAttribute('aria-checked')).to.equal('false');
70
+ });
package/dist/tooltip.d.ts CHANGED
@@ -5,19 +5,21 @@ declare global {
5
5
  }
6
6
  }
7
7
  /**
8
- * @slot - The contents of the tooltip.
9
- * @slot target - The element to which the tooltip should attach.
8
+ * @slot - The content of the tooltip.
9
+ * @slot target - The element to which the tooltip should anchor.
10
10
  */
11
11
  export default class GlideCoreTooltip extends LitElement {
12
12
  #private;
13
13
  static shadowRootOptions: ShadowRootInit;
14
14
  static styles: import("lit").CSSResult[];
15
- disabled: boolean;
15
+ get disabled(): boolean;
16
+ set disabled(isDisabled: boolean);
17
+ offset: number;
18
+ get open(): boolean;
19
+ set open(isOpen: boolean);
16
20
  placement?: 'bottom' | 'left' | 'right' | 'top';
17
- containingBlock?: Element;
21
+ disconnectedCallback(): void;
18
22
  firstUpdated(): void;
19
- private get isVisible();
20
- private set isVisible(value);
21
23
  render(): import("lit").TemplateResult<1>;
22
- setContainingBlock(containingBlock: Element): void;
24
+ private effectivePlacement;
23
25
  }
package/dist/tooltip.js CHANGED
@@ -1 +1 @@
1
- var __decorate=this&&this.__decorate||function(e,t,o,i){var l,s=arguments.length,a=s<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,o):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)a=Reflect.decorate(e,t,o,i);else for(var r=e.length-1;r>=0;r--)(l=e[r])&&(a=(s<3?l(a):s>3?l(t,o,a):l(t,o))||a);return s>3&&a&&Object.defineProperty(t,o,a),a};import{LitElement,html}from"lit";import{arrow,autoUpdate,computePosition,flip,offset,platform,shift}from"@floating-ui/dom";import{classMap}from"lit/directives/class-map.js";import{createRef,ref}from"lit/directives/ref.js";import{customElement,property,state}from"lit/decorators.js";import{ifDefined}from"lit/directives/if-defined.js";import{offsetParent}from"composed-offset-position";import{owSlot}from"./library/ow.js";import styles from"./tooltip.styles.js";let GlideCoreTooltip=class GlideCoreTooltip extends LitElement{constructor(){super(...arguments),this.disabled=!1,this.#e=createRef(),this.#t=createRef(),this.#o=!1,this.#i=createRef(),this.#l=createRef(),this.#s=createRef()}static{this.shadowRootOptions={...LitElement.shadowRootOptions,delegatesFocus:!0,mode:"closed"}}static{this.styles=styles}firstUpdated(){owSlot(this.#t.value),owSlot(this.#l.value),this.#a()}get isVisible(){return this.#o}set isVisible(e){this.#o=e,this.isVisible?this.#a():this.#r?.()}render(){return html`<div class="component" @mouseover="${this.#n}" @mouseout="${this.#f}"><div aria-labelledby="${ifDefined(this.disabled?void 0:"tooltip")}" class="target" slot="target" @focusin="${this.#p}" @focusout="${this.#c}" @keydown="${this.#d}" ${ref(this.#i)}><slot @slotchange="${this.#m}" ${ref(this.#l)} name="target"></slot></div><div class="${classMap({tooltip:!0,visible:this.isVisible&&!this.disabled})}" id="tooltip" role="${ifDefined(this.disabled?void 0:"tooltip")}" ${ref(this.#s)}><span aria-label="${ifDefined(this.disabled?void 0:"Tooltip: ")}"></span><slot @slotchange="${this.#h}" ${ref(this.#t)}></slot><div class="arrow" ${ref(this.#e)}></div></div></div>`}setContainingBlock(e){this.containingBlock=e}#e;#r;#u;#t;#o;#i;#l;#s;#g(){clearTimeout(this.#u)}#h(){owSlot(this.#t.value)}#p(){this.isVisible=!0}#c(){this.isVisible=!1}#d(e){"Escape"===e.key&&(this.isVisible=!1)}#f(){this.#R()}#n(){this.#g(),this.isVisible=!0}#m(){owSlot(this.#l.value)}#R(){this.#u=setTimeout((()=>{this.isVisible=!1}),200)}#a(){this.#i.value&&this.#s.value&&(this.#r=autoUpdate(this.#i.value,this.#s.value,(()=>{(async()=>{if(this.#i.value&&this.#s.value){const e=this.#e.value,{placement:t,x:o,y:i,middlewareData:l}=await computePosition(this.#i.value,this.#s.value,{platform:{...platform,getOffsetParent:e=>this.containingBlock&&e!==this.#e.value?this.containingBlock:platform.getOffsetParent(e,offsetParent)},placement:this.placement,strategy:"fixed",middleware:[offset(6),flip({fallbackStrategy:"initialPlacement"}),shift(),arrow({element:e})]});Object.assign(this.#s.value.style,{left:`${o}px`,top:`${i}px`});const s=l.arrow?.x??null,a=l.arrow?.y??null,r={top:"bottom",right:"left",bottom:"top",left:"right"}[t.split("-")[0]];Object.assign(e.style,{left:null===s?"":`${s}px`,top:null===a?"":`${a}px`,right:"",bottom:"",[r]:"-3px"})}})()})))}};__decorate([property({reflect:!0,type:Boolean})],GlideCoreTooltip.prototype,"disabled",void 0),__decorate([property()],GlideCoreTooltip.prototype,"placement",void 0),__decorate([state()],GlideCoreTooltip.prototype,"containingBlock",void 0),__decorate([state()],GlideCoreTooltip.prototype,"isVisible",null),GlideCoreTooltip=__decorate([customElement("glide-core-tooltip")],GlideCoreTooltip);export default GlideCoreTooltip;
1
+ var __decorate=this&&this.__decorate||function(e,t,o,l){var i,s=arguments.length,a=s<3?t:null===l?l=Object.getOwnPropertyDescriptor(t,o):l;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)a=Reflect.decorate(e,t,o,l);else for(var n=e.length-1;n>=0;n--)(i=e[n])&&(a=(s<3?i(a):s>3?i(t,o,a):i(t,o))||a);return s>3&&a&&Object.defineProperty(t,o,a),a};import{LitElement,html}from"lit";import{arrow,autoUpdate,computePosition,flip,limitShift,offset,shift}from"@floating-ui/dom";import{choose}from"lit/directives/choose.js";import{classMap}from"lit/directives/class-map.js";import{createRef,ref}from"lit/directives/ref.js";import{customElement,property,state}from"lit/decorators.js";import{ifDefined}from"lit/directives/if-defined.js";import ow,{owSlot}from"./library/ow.js";import styles from"./tooltip.styles.js";let GlideCoreTooltip=class GlideCoreTooltip extends LitElement{constructor(){super(...arguments),this.offset=4,this.effectivePlacement=this.placement??"bottom",this.#e=createRef(),this.#t=createRef(),this.#o=!1,this.#l=!1,this.#i=createRef(),this.#s=createRef(),this.#a=createRef()}static{this.shadowRootOptions={...LitElement.shadowRootOptions,delegatesFocus:!0,mode:"closed"}}static{this.styles=styles}get disabled(){return this.#o}set disabled(e){this.#o=e,this.open&&!e?this.#n():this.#r()}get open(){return this.#l}set open(e){this.#l=e,e&&!this.disabled?this.#n():this.#r()}disconnectedCallback(){super.disconnectedCallback(),clearTimeout(this.#f),clearTimeout(this.#p)}firstUpdated(){owSlot(this.#t.value),owSlot(this.#s.value),ow(this.#a.value,ow.object.instanceOf(HTMLElement)),this.#a.value.popover="manual",this.open&&!this.disabled&&this.#n()}render(){return html`<div class="component" @mouseover="${this.#h}" @mouseout="${this.#d}"><div aria-labelledby="${ifDefined(this.disabled?void 0:"tooltip")}" class="target" data-test="target" slot="target" @focusin="${this.#c}" @focusout="${this.#m}" @keydown="${this.#u}" ${ref(this.#i)}><slot @slotchange="${this.#v}" ${ref(this.#s)} name="target"></slot></div><div class="${classMap({tooltip:!0,[this.effectivePlacement]:!0})}" id="tooltip" data-test="tooltip" data-open-delay="300" data-close-delay="200" role="${ifDefined(this.disabled?void 0:"tooltip")}" ${ref(this.#a)}><div class="${classMap({arrow:!0,[this.effectivePlacement]:!0})}" data-test="arrow" ${ref(this.#e)}>${choose(this.effectivePlacement,[["top",()=>html`<svg viewBox="0 0 10 6" fill="none"><path d="M4.23178 5.07814C4.63157 5.55789 5.36843 5.55789 5.76822 5.07813L10 -7.9486e-08L-2.62268e-07 3.57628e-07L4.23178 5.07814Z" fill="#212121"/></svg>`],["right",()=>html`<svg viewBox="0 0 6 10" fill="none"><path d="M0.921865 4.23178C0.442111 4.63157 0.442112 5.36843 0.921866 5.76822L6 10L6 -2.62268e-07L0.921865 4.23178Z" fill="#212121"/></svg>`],["bottom",()=>html`<svg viewBox="0 0 10 6" fill="none"><path d="M4.23178 0.921865C4.63157 0.442111 5.36843 0.442112 5.76822 0.921866L10 6L-2.62268e-07 6L4.23178 0.921865Z" fill="#212121"/></svg>`],["left",()=>html`<svg viewBox="0 0 6 10" fill="none"><path d="M5.07814 4.23178C5.55789 4.63157 5.55789 5.36843 5.07813 5.76822L-4.37114e-07 10L0 -2.62268e-07L5.07814 4.23178Z" fill="#212121"/></svg>`]])}</div><span aria-label="${ifDefined(this.disabled?void 0:"Tooltip: ")}"></span><slot class="default-slot" @slotchange="${this.#R}" ${ref(this.#t)}></slot></div></div>`}#e;#g;#f;#t;#o;#l;#p;#i;#s;#a;#E(){clearTimeout(this.#f)}#r(){this.#a.value?.hidePopover(),this.#g&&this.#g()}#R(){owSlot(this.#t.value)}#c(){this.open=!0}#m(){this.open=!1}#u(e){"Escape"===e.key&&(this.open=!1)}#d(){this.#w(),clearTimeout(this.#p)}#h(){ow(this.#a.value,ow.object.instanceOf(HTMLElement)),this.#E(),this.#p=setTimeout((()=>{this.open=!0}),Number(this.#a.value.dataset.openDelay))}#v(){owSlot(this.#s.value)}#w(){ow(this.#a.value,ow.object.instanceOf(HTMLElement)),this.#f=setTimeout((()=>{this.open=!1}),Number(this.#a.value.dataset.closeDelay))}#n(){this.disabled||(this.#g?.(),this.#i.value&&this.#a.value&&(this.#g=autoUpdate(this.#i.value,this.#a.value,(()=>{(async()=>{if(this.#i.value&&this.#a.value&&this.#e.value){const{x:e,y:t,placement:o,middlewareData:l}=await computePosition(this.#i.value,this.#a.value,{placement:this.placement,middleware:[offset(this.offset),flip({fallbackStrategy:"initialPlacement"}),shift({limiter:limitShift({offset:20})}),arrow({element:this.#e.value})]});Object.assign(this.#a.value.style,{left:`${e}px`,top:`${t}px`}),Object.assign(this.#e.value.style,{left:l.arrow?.x?`${l.arrow.x}px`:null,top:l.arrow?.y?`${l.arrow.y}px`:null}),this.effectivePlacement=o,this.#a.value.showPopover()}})()}))))}};__decorate([property({reflect:!0,type:Boolean})],GlideCoreTooltip.prototype,"disabled",null),__decorate([property({reflect:!0,type:Number})],GlideCoreTooltip.prototype,"offset",void 0),__decorate([property({reflect:!0,type:Boolean})],GlideCoreTooltip.prototype,"open",null),__decorate([property()],GlideCoreTooltip.prototype,"placement",void 0),__decorate([state()],GlideCoreTooltip.prototype,"effectivePlacement",void 0),GlideCoreTooltip=__decorate([customElement("glide-core-tooltip")],GlideCoreTooltip);export default GlideCoreTooltip;
@@ -1,7 +1,26 @@
1
1
  import{css}from"lit";import focusOutline from"./styles/focus-outline.js";export default[css`
2
+ @keyframes tooltip {
3
+ from {
4
+ opacity: 0;
5
+ transform: scale(0.95);
6
+ }
7
+
8
+ to {
9
+ opacity: 1;
10
+ transform: scale(1);
11
+ }
12
+ }
13
+
14
+ :host {
15
+ display: inline-block;
16
+ }
17
+
2
18
  .component {
3
19
  /* https://github.com/CrowdStrike/glide-core/pull/119#issuecomment-2113314591 */
4
20
  display: flex;
21
+
22
+ /* https://css-tricks.com/flexbox-truncated-text/#aa-the-solution-is-min-width-0-on-the-flex-child */
23
+ min-inline-size: 0;
5
24
  position: relative;
6
25
  }
7
26
 
@@ -9,10 +28,13 @@ import{css}from"lit";import focusOutline from"./styles/focus-outline.js";export
9
28
  background-color: transparent;
10
29
  border-width: 0;
11
30
 
12
- /* Additional whitespace from line height and the tooltip won't be vertically centered. */
31
+ /*
32
+ Additional whitespace from line height and the tooltip won't be vertically
33
+ centered against its target.
34
+ */
13
35
  display: flex;
14
36
 
15
- /* Allows the consumer to style the target using "text-overflow: ellipsis". */
37
+ /* Allows the consumer to style the target with an ellipsis when its text is truncated. */
16
38
  inline-size: 100%;
17
39
  padding: 0;
18
40
  position: relative;
@@ -31,22 +53,11 @@ import{css}from"lit";import focusOutline from"./styles/focus-outline.js";export
31
53
  }
32
54
 
33
55
  .tooltip {
34
- background-color: var(--glide-core-surface-base-dark);
35
- border-radius: var(--glide-core-spacing-xs);
36
- color: var(--glide-core-text-selected);
37
- display: none;
38
- font-family: var(--glide-core-body-md-font-family);
39
- font-style: var(--glide-core-body-md-font-style);
40
- font-weight: var(--glide-core-body-md-font-weight);
41
- inline-size: max-content;
42
- inset-block-start: 50%;
43
- inset-block-start: 0;
44
- inset-inline-start: 0;
45
- max-inline-size: 11.25rem;
46
- overflow-wrap: break-word;
47
- padding: var(--glide-core-spacing-xs) var(--glide-core-spacing-sm);
48
- position: fixed;
49
- z-index: 1;
56
+ background-color: transparent;
57
+ border: none;
58
+ inset: unset;
59
+ padding: 0;
60
+ position: absolute;
50
61
 
51
62
  ::slotted(kbd) {
52
63
  color: var(--glide-core-text-header-2);
@@ -56,16 +67,70 @@ import{css}from"lit";import focusOutline from"./styles/focus-outline.js";export
56
67
  white-space: nowrap;
57
68
  }
58
69
 
59
- &.visible {
60
- display: unset;
70
+ &:popover-open {
71
+ animation: tooltip 250ms cubic-bezier(0.25, 0, 0.3, 1);
72
+ display: flex;
73
+
74
+ /*
75
+ Elements with a "popover" attribute don't allow overflow. So the arrow can't
76
+ be positioned with "position: absolute". Relative positioning is used instead.
77
+ Flex is used to get the arrow on the correct side of the tooltip. Floating UI
78
+ handles the rest.
79
+
80
+ A simple "transform" could replace Floating UI for the arrow if not for a Chrome
81
+ bug with "popover" when "scale()" is animated on the popover or a container within
82
+ it. With "transform" on the arrow, the arrow is initially offset by a couple pixels
83
+ before landing in the correct position when the animation finishes. It only happens
84
+ when the tooltip is left or right of its target.
85
+ */
86
+
87
+ &.top {
88
+ flex-direction: column-reverse;
89
+ }
90
+
91
+ &.right {
92
+ flex-direction: row-reverse;
93
+ }
94
+
95
+ &.bottom {
96
+ flex-direction: column;
97
+ }
61
98
  }
62
99
  }
63
100
 
64
101
  .arrow {
65
- background: var(--glide-core-surface-base-dark);
66
- block-size: 0.375rem;
67
- inline-size: 0.375rem;
68
- position: absolute;
69
- transform: rotate(45deg);
102
+ --arrow-height: 0.375rem;
103
+ --arrow-width: 0.625rem;
104
+
105
+ display: flex;
106
+ position: relative;
107
+
108
+ &.top,
109
+ &.bottom {
110
+ block-size: var(--arrow-height);
111
+ inline-size: var(--arrow-width);
112
+ }
113
+
114
+ &.right,
115
+ &.left {
116
+ block-size: var(--arrow-width);
117
+ inline-size: var(--arrow-height);
118
+ order: 2;
119
+ }
120
+ }
121
+
122
+ .default-slot {
123
+ background-color: var(--glide-core-surface-base-dark);
124
+ border-radius: var(--glide-core-spacing-xs);
125
+ color: var(--glide-core-text-selected);
126
+ display: block;
127
+ font-family: var(--glide-core-body-md-font-family);
128
+ font-style: var(--glide-core-body-md-font-style);
129
+ font-weight: var(--glide-core-body-md-font-weight);
130
+ inline-size: max-content;
131
+ inset-block-start: 50%;
132
+ max-inline-size: 11.25rem;
133
+ overflow-wrap: break-word;
134
+ padding: var(--glide-core-spacing-xs) var(--glide-core-spacing-sm);
70
135
  }
71
136
  `];
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable @typescript-eslint/no-unused-expressions */
2
2
  import './tooltip.js';
3
3
  import { ArgumentError } from 'ow';
4
- import { expect, fixture, html } from '@open-wc/testing';
4
+ import { aTimeout, expect, fixture, html } from '@open-wc/testing';
5
5
  import GlideCoreTooltip from './tooltip.js';
6
6
  import sinon from 'sinon';
7
7
  GlideCoreTooltip.shadowRootOptions.mode = 'open';
@@ -18,6 +18,31 @@ it('is accessible', async () => {
18
18
  ignoredRules: ['aria-tooltip-name'],
19
19
  });
20
20
  });
21
+ it('has defaults', async () => {
22
+ const component = await fixture(html `<glide-core-tooltip>
23
+ Tooltip
24
+ <span slot="target" tabindex="0">Target</span>
25
+ </glide-core-tooltip>`);
26
+ const tooltip = component.shadowRoot?.querySelector('[data-test="tooltip"]');
27
+ // Wait for Floating UI.
28
+ await aTimeout(0);
29
+ expect(component.offset).to.equal(4);
30
+ expect(component.open).to.be.false;
31
+ expect(component.placement).to.be.be.undefined;
32
+ expect(component.disabled).to.be.false;
33
+ expect(component.hasAttribute('disabled')).to.be.false;
34
+ expect(tooltip?.checkVisibility()).to.be.false;
35
+ });
36
+ it('can be open', async () => {
37
+ const component = await fixture(html `<glide-core-tooltip aria-label="Label" open>
38
+ Tooltip
39
+ <span slot="target" tabindex="0">Target</span>
40
+ </glide-core-tooltip>`);
41
+ const tooltip = component.shadowRoot?.querySelector('[data-test="tooltip"]');
42
+ // Wait for Floating UI.
43
+ await aTimeout(0);
44
+ expect(tooltip?.checkVisibility()).to.be.true;
45
+ });
21
46
  it('can have a tooltip', async () => {
22
47
  const component = await fixture(html `<glide-core-tooltip aria-label="Label">
23
48
  Tooltip
@@ -39,6 +64,16 @@ it('can have a target', async () => {
39
64
  ?.assignedElements();
40
65
  expect(assignedElements?.at(0)?.textContent).to.equal('Target');
41
66
  });
67
+ it('does not open when disabled', async () => {
68
+ const component = await fixture(html `<glide-core-tooltip aria-label="Label" open disabled>
69
+ Tooltip
70
+ <span slot="target" tabindex="0">Target</span>
71
+ </glide-core-tooltip>`);
72
+ const tooltip = component.shadowRoot?.querySelector('[data-test="tooltip"]');
73
+ // Wait for Floating UI.
74
+ await aTimeout(0);
75
+ expect(tooltip?.checkVisibility()).to.be.false;
76
+ });
42
77
  it('throws if it does not have a default slot', async () => {
43
78
  const spy = sinon.spy();
44
79
  try {
@@ -49,7 +84,7 @@ it('throws if it does not have a default slot', async () => {
49
84
  spy();
50
85
  }
51
86
  }
52
- expect(spy.called).to.be.true;
87
+ expect(spy.callCount).to.equal(1);
53
88
  });
54
89
  it('throws if it does not have a "target" slot', async () => {
55
90
  const spy = sinon.spy();
@@ -61,5 +96,5 @@ it('throws if it does not have a "target" slot', async () => {
61
96
  spy();
62
97
  }
63
98
  }
64
- expect(spy.called).to.be.true;
99
+ expect(spy.callCount).to.equal(1);
65
100
  });