@crowdstrike/glide-core 0.31.2 → 0.32.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/dist/accordion.js CHANGED
@@ -140,7 +140,10 @@ let Accordion = class Accordion extends LitElement {
140
140
 
141
141
  <div class="label-container">
142
142
  <slot
143
- class="prefix-icon-slot"
143
+ class=${classMap({
144
+ 'prefix-icon-slot': true,
145
+ 'slotted-content': this.hasPrefixIcon,
146
+ })}
144
147
  name="prefix-icon"
145
148
  @slotchange=${this.#onPrefixIconSlotChange}
146
149
  ${ref(this.#prefixIconSlotElementRef)}
@@ -94,6 +94,13 @@ export default [
94
94
  }
95
95
  }
96
96
 
97
+ .prefix-icon-slot {
98
+ &.slotted-content {
99
+ align-items: center;
100
+ display: flex;
101
+ }
102
+ }
103
+
97
104
  .suffix-icons-slot {
98
105
  align-items: center;
99
106
  color: var(--glide-core-color-interactive-icon-link);
@@ -0,0 +1 @@
1
+ export default function (): Promise<unknown>;
@@ -0,0 +1,5 @@
1
+ export default function () {
2
+ return new Promise((resolve) => {
3
+ requestIdleCallback(resolve);
4
+ });
5
+ }
package/dist/link.js CHANGED
@@ -41,22 +41,40 @@ let Link = class Link extends LitElement {
41
41
  this.#componentElementRef.value?.click();
42
42
  }
43
43
  render() {
44
- return html `<a
45
- aria-disabled=${this.disabled}
46
- class=${classMap({
47
- component: true,
48
- disabled: this.disabled,
49
- href: Boolean(this.href),
50
- })}
51
- data-test="component"
52
- download=${ifDefined(this.download)}
53
- href=${ifDefined(this.href)}
54
- target=${ifDefined(this.target)}
55
- @click=${this.#onClick}
56
- ${ref(this.#componentElementRef)}
57
- >
58
- ${this.label}
59
- </a>`;
44
+ // Lit-a11y also wants a keyboard listener on anything with a "click" listener and
45
+ // doesn't account for `role="link"`.
46
+ //
47
+ /* eslint-disable lit-a11y/click-events-have-key-events */
48
+ return this.disabled
49
+ ? html `<span
50
+ aria-disabled="true"
51
+ class=${classMap({
52
+ component: true,
53
+ disabled: this.disabled,
54
+ })}
55
+ data-test="component"
56
+ role="link"
57
+ tabindex="0"
58
+ @click=${this.#onClick}
59
+ ${ref(this.#componentElementRef)}
60
+ >
61
+ ${this.label}
62
+ </span>`
63
+ : html `<a
64
+ class=${classMap({
65
+ component: true,
66
+ disabled: this.disabled,
67
+ href: Boolean(this.href),
68
+ })}
69
+ data-test="component"
70
+ download=${ifDefined(this.download)}
71
+ href=${ifDefined(this.href)}
72
+ target=${ifDefined(this.target)}
73
+ @click=${this.#onClick}
74
+ ${ref(this.#componentElementRef)}
75
+ >
76
+ ${this.label}
77
+ </a>`;
60
78
  }
61
79
  #componentElementRef;
62
80
  #onClick(event) {
package/dist/menu.d.ts CHANGED
@@ -8,7 +8,7 @@ declare global {
8
8
  * @attr {boolean} [loading=false]
9
9
  * @attr {number} [offset=4]
10
10
  * @attr {boolean} [open=false]
11
- * @attr {'bottom'|'left'|'right'|'top'|'bottom-start'|'bottom-end'|'left-start'|'left-end'|'right-start'|'right-end'|'top-start'|'top-end'} [placement='bottom-start']
11
+ * @attr {'bottom'|'left'|'right'|'top'|'bottom-start'|'bottom-end'|'left-start'|'left-end'|'right-start'|'right-end'|'top-start'|'top-end'} [placement='bottom-start'] - Menu will try to move itself to the opposite of this value if not doing so would result in overflow. For example, if "bottom" results in overflow Menu will try "top" but not "right" or "left".
12
12
  *
13
13
  * @readonly
14
14
  * @attr {string} [version]
@@ -37,6 +37,10 @@ export default class Menu extends LitElement {
37
37
  */
38
38
  get open(): boolean;
39
39
  set open(isOpen: boolean);
40
+ /**
41
+ * Menu will try to move itself to the opposite of this value if not doing so would result in overflow.
42
+ * For example, if "bottom" results in overflow Menu will try "top" but not "right" or "left".
43
+ */
40
44
  placement: 'bottom' | 'left' | 'right' | 'top' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end' | 'right-start' | 'right-end' | 'top-start' | 'top-end';
41
45
  privateOpenedViaKeyboard: boolean;
42
46
  readonly version: string;
package/dist/menu.js CHANGED
@@ -25,7 +25,7 @@ import uniqueId from './library/unique-id.js';
25
25
  * @attr {boolean} [loading=false]
26
26
  * @attr {number} [offset=4]
27
27
  * @attr {boolean} [open=false]
28
- * @attr {'bottom'|'left'|'right'|'top'|'bottom-start'|'bottom-end'|'left-start'|'left-end'|'right-start'|'right-end'|'top-start'|'top-end'} [placement='bottom-start']
28
+ * @attr {'bottom'|'left'|'right'|'top'|'bottom-start'|'bottom-end'|'left-start'|'left-end'|'right-start'|'right-end'|'top-start'|'top-end'} [placement='bottom-start'] - Menu will try to move itself to the opposite of this value if not doing so would result in overflow. For example, if "bottom" results in overflow Menu will try "top" but not "right" or "left".
29
29
  *
30
30
  * @readonly
31
31
  * @attr {string} [version]
@@ -38,6 +38,10 @@ import uniqueId from './library/unique-id.js';
38
38
  let Menu = class Menu extends LitElement {
39
39
  constructor() {
40
40
  super(...arguments);
41
+ /**
42
+ * Menu will try to move itself to the opposite of this value if not doing so would result in overflow.
43
+ * For example, if "bottom" results in overflow Menu will try "top" but not "right" or "left".
44
+ */
41
45
  this.placement = 'bottom-start';
42
46
  // Used in `#show()` to open the active Option's tooltip when Menu is opened via
43
47
  // keyboard. Unlike mouse users, keyboard users can't hover an Option to reveal
@@ -55,7 +59,7 @@ let Menu = class Menu extends LitElement {
55
59
  // `#onComponentFocusOut()` to decide if Menu should close. Also used in
56
60
  // `#onTargetAndDefaultSlotKeyDown()` to decide if we need to move focus.
57
61
  this.#hasVoiceOverMovedFocusToOptionsOrAnOption = false;
58
- // Set in `#onDefaultSlotMouseUp()`. Used in `#onDocumentClick()` to guard against
62
+ // Set in `#onDefaultSlotClick()`. Used in `#onDocumentClick()` to guard against
59
63
  // Menu closing when any number of things that are not an Option are clicked. Those
60
64
  // "click" events will be retargeted to Menu's host the moment they bubble out of
61
65
  // Menu. So checking in `#onDocumentClick()` if the click's `event.target` came
@@ -224,7 +228,7 @@ let Menu = class Menu extends LitElement {
224
228
  }
225
229
  }
226
230
  if (this.#defaultSlotElementRef.value) {
227
- // `popover` so Options can break out of Modal or another container that has
231
+ // `popover` so Options can break out of Modal or another element that has
228
232
  // `overflow: hidden`. Elements with `popover` are positioned relative to the
229
233
  // viewport. Thus Floating UI in addition to `popover` until anchor positioning is
230
234
  // well supported.
@@ -294,7 +298,6 @@ let Menu = class Menu extends LitElement {
294
298
  @keydown=${this.#onTargetAndDefaultSlotKeyDown}
295
299
  @mousedown=${this.#onDefaultSlotMouseDown}
296
300
  @mouseover=${this.#onDefaultSlotMouseOver}
297
- @mouseup=${this.#onDefaultSlotMouseUp}
298
301
  @private-disabled-change=${this.#onDefaultSlotDisabledChange}
299
302
  @private-slot-change=${this.#onDefaultSlotSlotChange}
300
303
  @toggle=${this.#onDefaultSlotToggle}
@@ -316,7 +319,7 @@ let Menu = class Menu extends LitElement {
316
319
  // `#onComponentFocusOut()` to decide if Menu should close. Also used in
317
320
  // `#onTargetAndDefaultSlotKeyDown()` to decide if we need to move focus.
318
321
  #hasVoiceOverMovedFocusToOptionsOrAnOption;
319
- // Set in `#onDefaultSlotMouseUp()`. Used in `#onDocumentClick()` to guard against
322
+ // Set in `#onDefaultSlotClick()`. Used in `#onDocumentClick()` to guard against
320
323
  // Menu closing when any number of things that are not an Option are clicked. Those
321
324
  // "click" events will be retargeted to Menu's host the moment they bubble out of
322
325
  // Menu. So checking in `#onDocumentClick()` if the click's `event.target` came
@@ -494,6 +497,7 @@ let Menu = class Menu extends LitElement {
494
497
  }
495
498
  }
496
499
  #onDefaultSlotClick(event) {
500
+ this.#isDefaultSlotClick = true;
497
501
  // When the padding or border on the default slot of a sub-Menu is clicked, the
498
502
  // event will be retargeted by the browser to the sub-Menu's parent Option.
499
503
  //
@@ -607,9 +611,6 @@ let Menu = class Menu extends LitElement {
607
611
  event.preventDefault();
608
612
  }
609
613
  }
610
- #onDefaultSlotMouseUp() {
611
- this.#isDefaultSlotClick = true;
612
- }
613
614
  #onDefaultSlotSlotChange() {
614
615
  const wasActiveOptionRemoved = this.#optionElements?.every((option) => option !== this.#activeOption);
615
616
  if (wasActiveOptionRemoved &&
@@ -1146,6 +1147,60 @@ let Menu = class Menu extends LitElement {
1146
1147
  }
1147
1148
  #show() {
1148
1149
  this.#cleanUpFloatingUi?.();
1150
+ // Ideally, we wouldn't show the popover until after Floating UI has calculated its
1151
+ // position. But calling `showPopover()` changes the nature of how `top` and `left`
1152
+ // affect an element's position when the element's containing block is something
1153
+ // other than the document.
1154
+ //
1155
+ // Unlike non-popovers, popovers break out of containing blocks¹. So `top` and
1156
+ // `left` are always relative to the document. For non-popovers, however, `top`
1157
+ // and `left` are relative to the element's containing block.
1158
+ //
1159
+ // Imagine that Menu has a parent with `transform: scale(1)` and we let Floating UI
1160
+ // calculate the default slot's position before calling `showPopover()`. Floating
1161
+ // UI will correctly position the default slot relative to that parent. But, after
1162
+ // `showPopover()` is called, Floating UI will recalculate the default slot's
1163
+ // position and position it relative to the document.
1164
+ //
1165
+ // The problem is, during the time between when `showPopover()` is called and
1166
+ // Floating UI does its recalculation, the default slot will have a `top` and
1167
+ // `left` that were correct when it was positioned relative to its containing
1168
+ // block, but are now incorrect. So the default slot will, for a moment, be
1169
+ // positioned above or below where it should be.
1170
+ //
1171
+ // The recalculation and subsequent repositioning will happen so quickly that the
1172
+ // user won't see it. But, if the user's mouse happens to be on top of the default
1173
+ // slot while it's positioned incorrectly, then `#onDefaultSlotMouseOver()` will
1174
+ // be called and whatever Option the user's mouse is on will be activated. Floating
1175
+ // UI will finish its work, then default slot will become visible. But the wrong
1176
+ // Option will be active when Menu finally appears open.
1177
+ //
1178
+ // To reproduce the issue:
1179
+ //
1180
+ // 1. Move the `showPopover()` call below inside `computePosition().then()`.
1181
+ // 2. Go to Menu's Overview page in Storybook.
1182
+ // 3. Place your mouse just above Menu's target.
1183
+ // 4. Tab to Menu's target.
1184
+ // 5. Press Space to open Menu.
1185
+ //
1186
+ // Note how the second or third Option is active instead of the first.
1187
+ //
1188
+ // I've omitted a test for this because calling `sendKey({ press: 'Tab' })`
1189
+ // followed by `sendKey({ press: ' ' })`, after moving the mouse above Menu's
1190
+ // target, introduces just enough of a delay for Floating UI to complete its
1191
+ // recalculation, making the issue impossible to reproduce in a test.
1192
+ //
1193
+ // 1. https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_display/Containing_block#identifying_the_containing_block
1194
+ this.#defaultSlotElementRef.value?.showPopover();
1195
+ if (this.#isSubMenu && this.#parentOption) {
1196
+ this.#parentOption.ariaExpanded = 'true';
1197
+ }
1198
+ else if (!this.#isSubMenu && this.#targetElement) {
1199
+ this.#targetElement.ariaExpanded = 'true';
1200
+ }
1201
+ if (this.#optionsElement && this.#activeOption?.id) {
1202
+ this.#optionsElement.ariaActivedescendant = this.#activeOption.id;
1203
+ }
1149
1204
  if (this.#previouslyActiveOption &&
1150
1205
  !this.#previouslyActiveOption.disabled &&
1151
1206
  this.#optionsElement) {
@@ -1178,17 +1233,6 @@ let Menu = class Menu extends LitElement {
1178
1233
  left: `${x}px`,
1179
1234
  top: `${y}px`,
1180
1235
  });
1181
- if (this.#isSubMenu && this.#parentOption) {
1182
- this.#parentOption.ariaExpanded = 'true';
1183
- }
1184
- else if (!this.#isSubMenu && this.#targetElement) {
1185
- this.#targetElement.ariaExpanded = 'true';
1186
- }
1187
- this.#defaultSlotElementRef.value.showPopover();
1188
- }
1189
- if (this.#optionsElement && this.#activeOption?.id) {
1190
- this.#optionsElement.ariaActivedescendant =
1191
- this.#activeOption.id;
1192
1236
  }
1193
1237
  });
1194
1238
  }
@@ -35,7 +35,6 @@ export default [
35
35
  padding-block-end: 0;
36
36
  padding-block-start: var(--glide-core-spacing-base-xxxs);
37
37
  padding-inline: var(--glide-core-spacing-base-xxxs);
38
- position: absolute;
39
38
 
40
39
  /*
41
40
  This little hack replaces "padding-block-end", which the last option overlaps
package/dist/popover.d.ts CHANGED
@@ -8,13 +8,13 @@ declare global {
8
8
  * @attr {boolean} [disabled=false]
9
9
  * @attr {number} [offset=4]
10
10
  * @attr {boolean} [open=false]
11
- * @attr {'bottom'|'left'|'right'|'top'} [placement]
11
+ * @attr {'bottom'|'left'|'right'|'top'} [placement] - Popover will try to move itself to the opposite of this value if not doing so would result in overflow. For example, if "bottom" results in overflow Popover will try "top" but not "right" or "left".
12
12
  *
13
13
  * @readonly
14
14
  * @attr {string} [version]
15
15
  *
16
16
  * @slot {Element | string} - The content of the popover
17
- * @slot {Element} [target] - The element to which the popover will anchor. Can be any focusable element.
17
+ * @slot {Element} [target] - The element to which Popover will anchor. Can be any focusable element.
18
18
  *
19
19
  * @fires {Event} toggle
20
20
  */
@@ -37,6 +37,10 @@ export default class Popover extends LitElement {
37
37
  */
38
38
  get open(): boolean;
39
39
  set open(isOpen: boolean);
40
+ /**
41
+ * Popover will try to move itself to the opposite of this value if not doing so would result in overflow.
42
+ * For example, if "bottom" results in overflow Popover will try "top" but not "right" or "left".
43
+ */
40
44
  placement?: 'bottom' | 'left' | 'right' | 'top';
41
45
  readonly version: string;
42
46
  connectedCallback(): void;
package/dist/popover.js CHANGED
@@ -19,13 +19,13 @@ import final from './library/final.js';
19
19
  * @attr {boolean} [disabled=false]
20
20
  * @attr {number} [offset=4]
21
21
  * @attr {boolean} [open=false]
22
- * @attr {'bottom'|'left'|'right'|'top'} [placement]
22
+ * @attr {'bottom'|'left'|'right'|'top'} [placement] - Popover will try to move itself to the opposite of this value if not doing so would result in overflow. For example, if "bottom" results in overflow Popover will try "top" but not "right" or "left".
23
23
  *
24
24
  * @readonly
25
25
  * @attr {string} [version]
26
26
  *
27
27
  * @slot {Element | string} - The content of the popover
28
- * @slot {Element} [target] - The element to which the popover will anchor. Can be any focusable element.
28
+ * @slot {Element} [target] - The element to which Popover will anchor. Can be any focusable element.
29
29
  *
30
30
  * @fires {Event} toggle
31
31
  */
@@ -36,19 +36,26 @@ let Popover = class Popover extends LitElement {
36
36
  this.effectivePlacement = this.placement ?? 'bottom';
37
37
  this.#arrowElementRef = createRef();
38
38
  this.#defaultSlotElementRef = createRef();
39
+ // Set in `#onArrowClick()`. Used in `#onDocumentClick()` to guard against closing
40
+ // Popover when the user accidentally clicks the arrow.
39
41
  this.#isArrowClick = false;
42
+ // Set in `#onDefaultSlotClick()`. Used in `#onDocumentClick()` to guard against
43
+ // closing Popover when the user interacts with its default slot.
40
44
  this.#isDefaultSlotClick = false;
41
45
  this.#isDisabled = false;
42
46
  this.#isOpen = false;
47
+ // Set in `#onTargetSlotClick()`. Used in `#onDocumentClick()` to guard against
48
+ // immediately closing Popover when it's opened via `onTargetSlotClick()`.
43
49
  this.#isTargetSlotClick = false;
44
50
  this.#popoverElementRef = createRef();
45
51
  this.#targetSlotElementRef = createRef();
46
52
  // An arrow function field instead of a method so `this` is closed over and
47
53
  // set to the component instead of `document`.
48
54
  this.#onDocumentClick = () => {
49
- // Checking that the click's `event.target` is equal to
50
- // `#defaultSlotElementRef.value` would be a lot simpler. But, when the target is
51
- // inside of another web component, `event.target` will be that component instead.
55
+ // Checking that `event.target` is equal to `this.#defaultSlotElementRef.value`
56
+ // would be simpler. But, when the default slot is inside of another web component,
57
+ // `event.target` will be that component instead.
58
+ //
52
59
  // Same for `this.#isTargetSlotClick` and `this.#isArrowClick`.
53
60
  if (this.#isDefaultSlotClick ||
54
61
  this.#isTargetSlotClick ||
@@ -122,65 +129,34 @@ let Popover = class Popover extends LitElement {
122
129
  }
123
130
  connectedCallback() {
124
131
  super.connectedCallback();
125
- // 1. The consumer has a click handler on a button.
126
- // 2. The user clicks the button.
127
- // 3. The button's click handler is called and it sets `this.open` to `true`.
128
- // 4. The "click" event bubbles up and is handled by `#onDocumentClick`.
129
- // 5. That handler sets `open` to `false` because the click came from outside
130
- // Popover.
131
- // 6. Popover is opened then closed in the same frame and so never opens.
132
- //
133
- // `capture` ensures `#onDocumentClick` is called before #3, so the button click
134
- // handler setting `open` to `true` isn't overwritten by this handler setting
135
- // `open` to `false`.
136
- document.addEventListener('click', this.#onDocumentClick, {
137
- capture: true,
138
- });
132
+ document.addEventListener('click', this.#onDocumentClick);
139
133
  }
140
134
  firstUpdated() {
141
135
  if (this.#popoverElementRef.value) {
142
- // `popover` is used so the popover can break out of Modal or another container
143
- // that has `overflow: hidden`. And elements with `popover` are positioned
144
- // relative to the viewport. Thus Floating UI in addition to `popover`.
136
+ // `popover` so Popover can break out of Modal or another element that has
137
+ // `overflow: hidden`. Elements with `popover` are positioned relative to the
138
+ // viewport. Thus Floating UI in addition to `popover` until anchor positioning is
139
+ // well supported.
145
140
  //
146
- // Set here instead of in the template to escape Lit Analyzer, which isn't aware
147
- // of `popover` and doesn't have a way to disable its "no-unknown-attribute" rule.
141
+ // "manual" is set here instead of in the template to circumvent Lit Analyzer,
142
+ // which isn't aware of `popover` and doesn't provide a way to disable its
143
+ // "no-unknown-attribute" rule.
148
144
  //
149
- // "auto" means only one popover can be open at a time. Consumers, however, may
150
- // have popovers in own components that need to be open while this one is open.
151
- //
152
- // "auto" also automatically opens the popover when its target is clicked. We want
153
- // it to remain closed when clicked when there are no menu options.
145
+ // "manual" instead of "auto" because the latter only allows one popover to be open
146
+ // at a time. And consumers may have other popovers that need to remain open while
147
+ // this popover is open.
154
148
  this.#popoverElementRef.value.popover = 'manual';
155
149
  }
156
150
  if (this.open && !this.disabled) {
157
151
  this.#show();
158
152
  }
159
- // Popover's "click" handler on `document` listens for clicks in the capture
160
- // phase. There's a comment explaining why. `#isDefaultSlotclick` must be
161
- // set before that handler is called so it has the information it needs
162
- // to determine whether or not to close Popover. Same for `#isTargetSlotClick`
163
- // and `#isArrowClick`.
164
- this.#defaultSlotElementRef.value?.addEventListener('mouseup', () => {
165
- this.#isDefaultSlotClick = true;
166
- });
167
- this.#targetSlotElementRef.value?.addEventListener('mouseup', () => {
168
- this.#isTargetSlotClick = true;
169
- });
170
- this.#arrowElementRef.value?.addEventListener('mouseup', () => {
171
- this.#isArrowClick = true;
172
- });
173
- this.#targetSlotElementRef.value?.addEventListener('keydown', (event) => {
174
- if (event.key === 'Enter' || event.key === ' ') {
175
- this.#isTargetSlotClick = true;
176
- }
177
- });
178
153
  }
179
154
  render() {
180
- // Lit-a11y calls for "blur" and "focus" handlers but doesn't account for "focusin"
181
- // and "focusout". It also calls for popovers to have an `aria-label`, but then
182
- // VoiceOver, at least, won't read the popover's content. So an element with an
183
- // `aria-label` is placed inside the popover.
155
+ // Lit-a11y also wants a keyboard listener on anything with a "click" listener. The
156
+ // "click" listeners on the arrow and default slot, however, are for a specific
157
+ // purpose that Lit-a11y isn't aware of.
158
+ //
159
+ /* eslint-disable lit-a11y/click-events-have-key-events */
184
160
  return html `
185
161
  <div class="component">
186
162
  <slot
@@ -193,7 +169,7 @@ let Popover = class Popover extends LitElement {
193
169
  ${ref(this.#targetSlotElementRef)}
194
170
  >
195
171
  <!--
196
- The element to which the popover will anchor. Can be any focusable element.
172
+ The element to which Popover will anchor. Can be any focusable element.
197
173
  @type {Element}
198
174
  -->
199
175
  </slot>
@@ -213,6 +189,7 @@ let Popover = class Popover extends LitElement {
213
189
  [this.effectivePlacement]: true,
214
190
  })}
215
191
  data-test="arrow"
192
+ @click=${this.#onArrowClick}
216
193
  ${ref(this.#arrowElementRef)}
217
194
  >
218
195
  ${choose(this.effectivePlacement, [
@@ -225,6 +202,7 @@ let Popover = class Popover extends LitElement {
225
202
 
226
203
  <slot
227
204
  class="default-slot"
205
+ @click=${this.#onDefaultSlotClick}
228
206
  ${assertSlot()}
229
207
  ${ref(this.#defaultSlotElementRef)}
230
208
  >
@@ -240,10 +218,16 @@ let Popover = class Popover extends LitElement {
240
218
  #arrowElementRef;
241
219
  #cleanUpFloatingUi;
242
220
  #defaultSlotElementRef;
221
+ // Set in `#onArrowClick()`. Used in `#onDocumentClick()` to guard against closing
222
+ // Popover when the user accidentally clicks the arrow.
243
223
  #isArrowClick;
224
+ // Set in `#onDefaultSlotClick()`. Used in `#onDocumentClick()` to guard against
225
+ // closing Popover when the user interacts with its default slot.
244
226
  #isDefaultSlotClick;
245
227
  #isDisabled;
246
228
  #isOpen;
229
+ // Set in `#onTargetSlotClick()`. Used in `#onDocumentClick()` to guard against
230
+ // immediately closing Popover when it's opened via `onTargetSlotClick()`.
247
231
  #isTargetSlotClick;
248
232
  #offset;
249
233
  #popoverElementRef;
@@ -258,14 +242,31 @@ let Popover = class Popover extends LitElement {
258
242
  }
259
243
  this.#cleanUpFloatingUi?.();
260
244
  }
261
- #onTargetSlotClick() {
262
- this.open = !this.open;
245
+ #onArrowClick() {
246
+ this.#isArrowClick = true;
247
+ }
248
+ #onDefaultSlotClick() {
249
+ this.#isDefaultSlotClick = true;
250
+ }
251
+ #onTargetSlotClick(event) {
252
+ this.#isTargetSlotClick = true;
253
+ // The timeout gives consumers a chance to cancel the event to prevent Popover
254
+ // from opening or closing.
255
+ setTimeout(() => {
256
+ if (!event.defaultPrevented) {
257
+ this.open = !this.open;
258
+ }
259
+ });
263
260
  }
264
261
  #onTargetSlotKeydown(event) {
262
+ if (event.key === 'Enter' || event.key === ' ') {
263
+ this.#isTargetSlotClick = true;
264
+ return;
265
+ }
265
266
  if (event.key === 'Escape') {
266
- // Prevent Safari from leaving full screen.
267
- event.preventDefault();
267
+ event.preventDefault(); // Prevent Safari from leaving full screen.
268
268
  this.open = false;
269
+ return;
269
270
  }
270
271
  }
271
272
  get #targetElement() {
package/dist/slider.js CHANGED
@@ -345,6 +345,7 @@ let Slider = class Slider extends LitElement {
345
345
  // input elements directly or via the handles. Exposing the
346
346
  // track via keyboard wouldn't bring any real value in this
347
347
  // instance.
348
+ //
348
349
  /* eslint-disable lit-a11y/click-events-have-key-events */
349
350
  return html `
350
351
  <glide-core-private-label
package/dist/tooltip.d.ts CHANGED
@@ -11,7 +11,7 @@ declare global {
11
11
  * @attr {boolean} [disabled=false]
12
12
  * @attr {number} [offset=4]
13
13
  * @attr {boolean} [open=false]
14
- * @attr {'bottom'|'left'|'right'|'top'} [placement] - The placement of the tooltip relative to its target. Automatic placement will take over if the tooltip is cut off by the viewport.
14
+ * @attr {'bottom'|'left'|'right'|'top'} [placement] - Tooltip will try to move itself to the opposite of this value if not doing so would result in overflow. For example, if "bottom" results in overflow Tooltip will try "top" but not "right" or "left".
15
15
  * @attr {boolean} [screenreader-hidden=false]
16
16
  * @attr {string[]} [shortcut=[]]
17
17
  *
@@ -19,7 +19,7 @@ declare global {
19
19
  * @attr {string} [version]
20
20
  *
21
21
  * @slot {TooltipContainer} [private]
22
- * @slot {Element} target - The element to which the tooltip will anchor. Can be any element with an implicit or explicit ARIA role.
22
+ * @slot {Element} target - The element to which Tooltip will anchor. Can be any interactive element with an implicit or explicit ARIA role.
23
23
  *
24
24
  * @fires {Event} toggle
25
25
  */
@@ -56,8 +56,8 @@ export default class Tooltip extends LitElement {
56
56
  */
57
57
  set open(isOpen: boolean);
58
58
  /**
59
- * The placement of the tooltip relative to its target. Automatic placement will
60
- * take over if the tooltip is cut off by the viewport.
59
+ * Tooltip will try to move itself to the opposite of this value if not doing so would result in overflow.
60
+ * For example, if "bottom" results in overflow Tooltip will try "top" but not "right" or "left".
61
61
  */
62
62
  placement?: 'bottom' | 'left' | 'right' | 'top';
63
63
  /**
package/dist/tooltip.js CHANGED
@@ -23,7 +23,7 @@ import required from './library/required.js';
23
23
  * @attr {boolean} [disabled=false]
24
24
  * @attr {number} [offset=4]
25
25
  * @attr {boolean} [open=false]
26
- * @attr {'bottom'|'left'|'right'|'top'} [placement] - The placement of the tooltip relative to its target. Automatic placement will take over if the tooltip is cut off by the viewport.
26
+ * @attr {'bottom'|'left'|'right'|'top'} [placement] - Tooltip will try to move itself to the opposite of this value if not doing so would result in overflow. For example, if "bottom" results in overflow Tooltip will try "top" but not "right" or "left".
27
27
  * @attr {boolean} [screenreader-hidden=false]
28
28
  * @attr {string[]} [shortcut=[]]
29
29
  *
@@ -31,7 +31,7 @@ import required from './library/required.js';
31
31
  * @attr {string} [version]
32
32
  *
33
33
  * @slot {TooltipContainer} [private]
34
- * @slot {Element} target - The element to which the tooltip will anchor. Can be any element with an implicit or explicit ARIA role.
34
+ * @slot {Element} target - The element to which Tooltip will anchor. Can be any interactive element with an implicit or explicit ARIA role.
35
35
  *
36
36
  * @fires {Event} toggle
37
37
  */
@@ -179,18 +179,18 @@ let Tooltip = class Tooltip extends LitElement {
179
179
  }
180
180
  firstUpdated() {
181
181
  if (this.#tooltipElementRef.value) {
182
- // `popover` is used so the tooltip can break out of Modal or another container
183
- // that has `overflow: hidden`. And elements with `popover` are positioned
184
- // relative to the viewport. Thus Floating UI in addition to `popover`.
182
+ // `popover` so Tooltip can break out of Modal or another element that has
183
+ // `overflow: hidden`. Elements with `popover` are positioned relative to the
184
+ // viewport. Thus Floating UI in addition to `popover` until anchor positioning is
185
+ // well supported.
185
186
  //
186
- // Set here instead of in the template to escape Lit Analyzer, which isn't aware
187
- // of `popover` and doesn't have a way to disable its "no-unknown-attribute" rule.
187
+ // "manual" is set here instead of in the template to circumvent Lit Analyzer,
188
+ // which isn't aware of `popover` and doesn't provide a way to disable its
189
+ // "no-unknown-attribute" rule.
188
190
  //
189
- // "auto" means only one popover can be open at a time. Consumers, however, may
190
- // have popovers in own components that need to be open while this one is open.
191
- //
192
- // "auto" also automatically opens the popover when its target is clicked. We
193
- // only want it to open on hover or focus.
191
+ // "manual" instead of "auto" because the latter only allows one popover to be open
192
+ // at a time. And consumers may have other popovers that need to remain open while
193
+ // this popover is open.
194
194
  this.#tooltipElementRef.value.popover = 'manual';
195
195
  }
196
196
  if (this.open && !this.disabled) {
@@ -229,8 +229,8 @@ let Tooltip = class Tooltip extends LitElement {
229
229
  ${ref(this.#targetSlotElementRef)}
230
230
  >
231
231
  <!--
232
- The element to which the tooltip will anchor.
233
- Can be any element with an implicit or explicit ARIA role.
232
+ The element to which Tooltip will anchor.
233
+ Can be any interactive element with an implicit or explicit ARIA role.
234
234
 
235
235
  @required
236
236
  @type {Element}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crowdstrike/glide-core",
3
- "version": "0.31.2",
3
+ "version": "0.32.0",
4
4
  "description": "A Web Component design system",
5
5
  "author": "CrowdStrike UX Team",
6
6
  "license": "Apache-2.0",