primer_view_components 0.10.0 → 0.12.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -0
  3. data/app/assets/javascripts/app/components/primer/alpha/tool_tip.d.ts +1 -0
  4. data/app/assets/javascripts/primer_view_components.js +1 -1
  5. data/app/assets/javascripts/primer_view_components.js.map +1 -1
  6. data/app/assets/styles/primer_view_components.css +1 -1
  7. data/app/assets/styles/primer_view_components.css.map +1 -1
  8. data/app/components/primer/alpha/action_bar/item.rb +7 -4
  9. data/app/components/primer/alpha/action_bar.rb +2 -2
  10. data/app/components/primer/alpha/action_bar_element.js +2 -2
  11. data/app/components/primer/alpha/action_bar_element.ts +2 -2
  12. data/app/components/primer/alpha/action_menu/action_menu_element.js +168 -84
  13. data/app/components/primer/alpha/action_menu/action_menu_element.ts +204 -79
  14. data/app/components/primer/alpha/action_menu/list.rb +0 -2
  15. data/app/components/primer/alpha/segmented_control/item.html.erb +1 -8
  16. data/app/components/primer/alpha/segmented_control/item.rb +38 -4
  17. data/app/components/primer/alpha/segmented_control.css +1 -1
  18. data/app/components/primer/alpha/segmented_control.css.map +1 -1
  19. data/app/components/primer/alpha/segmented_control.pcss +3 -1
  20. data/app/components/primer/alpha/tool_tip.d.ts +1 -0
  21. data/app/components/primer/alpha/tool_tip.js +17 -2
  22. data/app/components/primer/alpha/tool_tip.ts +16 -0
  23. data/app/components/primer/beta/base_button.rb +4 -0
  24. data/app/components/primer/beta/button.css +1 -1
  25. data/app/components/primer/beta/button.css.map +1 -1
  26. data/app/components/primer/beta/button.pcss +6 -2
  27. data/app/components/primer/focus_group.js +28 -3
  28. data/app/components/primer/focus_group.ts +27 -1
  29. data/lib/primer/view_components/version.rb +1 -1
  30. data/previews/primer/alpha/action_menu_preview/with_actions.html.erb +5 -4
  31. data/previews/primer/alpha/action_menu_preview.rb +4 -1
  32. data/previews/primer/alpha/segmented_control_preview.rb +35 -0
  33. data/previews/primer/alpha/tooltip_preview/tooltip_with_dialog_moving_focus_to_input.html.erb +23 -0
  34. data/previews/primer/alpha/tooltip_preview.rb +5 -0
  35. data/previews/primer/beta/button_preview.rb +20 -2
  36. data/previews/primer/beta/icon_button_preview.rb +3 -0
  37. data/static/arguments.json +18 -1
  38. data/static/info_arch.json +107 -3
  39. data/static/previews.json +65 -0
  40. metadata +3 -2
@@ -20,6 +20,7 @@ export class ActionMenuElement extends HTMLElement {
20
20
  #abortController: AbortController
21
21
  #originalLabel = ''
22
22
  #inputName = ''
23
+ #invokerBeingClicked = false
23
24
 
24
25
  get selectVariant(): SelectVariant {
25
26
  return this.getAttribute('data-select-variant') as SelectVariant
@@ -52,7 +53,7 @@ export class ActionMenuElement extends HTMLElement {
52
53
  }
53
54
 
54
55
  get popoverElement(): HTMLElement | null {
55
- return this.invokerElement?.popoverTargetElement || null
56
+ return (this.invokerElement?.popoverTargetElement as HTMLElement) || null
56
57
  }
57
58
 
58
59
  get invokerElement(): HTMLButtonElement | null {
@@ -94,8 +95,10 @@ export class ActionMenuElement extends HTMLElement {
94
95
  this.addEventListener('click', this, {signal})
95
96
  this.addEventListener('mouseover', this, {signal})
96
97
  this.addEventListener('focusout', this, {signal})
98
+ this.addEventListener('mousedown', this, {signal})
97
99
  this.#setDynamicLabel()
98
100
  this.#updateInput()
101
+ this.#softDisableItems()
99
102
 
100
103
  if (this.includeFragment) {
101
104
  this.includeFragment.addEventListener('include-fragment-replaced', this, {
@@ -104,19 +107,69 @@ export class ActionMenuElement extends HTMLElement {
104
107
  }
105
108
  }
106
109
 
110
+ #softDisableItems() {
111
+ const {signal} = this.#abortController
112
+
113
+ for (const item of this.#items) {
114
+ item.addEventListener('click', this.#potentiallyDisallowActivation.bind(this), {signal})
115
+ item.addEventListener('keydown', this.#potentiallyDisallowActivation.bind(this), {signal})
116
+ }
117
+ }
118
+
119
+ #potentiallyDisallowActivation(event: Event) {
120
+ if (!this.#isActivation(event)) return
121
+
122
+ const item = (event.target as HTMLElement).closest(menuItemSelectors.join(','))
123
+ if (!item) return
124
+
125
+ if (item.getAttribute('aria-disabled')) {
126
+ event.preventDefault()
127
+ event.stopPropagation()
128
+ event.stopImmediatePropagation()
129
+ }
130
+ }
131
+
107
132
  disconnectedCallback() {
108
133
  this.#abortController.abort()
109
134
  }
110
135
 
136
+ #isKeyboardActivation(event: Event): boolean {
137
+ return (
138
+ event instanceof KeyboardEvent &&
139
+ event.type === 'keydown' &&
140
+ !(event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) &&
141
+ (event.key === 'Enter' || event.key === ' ')
142
+ )
143
+ }
144
+
145
+ #isMouseActivation(event: Event): boolean {
146
+ return event instanceof MouseEvent && event.type === 'click'
147
+ }
148
+
149
+ #isActivation(event: Event): boolean {
150
+ return this.#isMouseActivation(event) || this.#isKeyboardActivation(event)
151
+ }
152
+
111
153
  handleEvent(event: Event) {
112
- const activation = this.#isActivationKeydown(event)
113
- if (event.target === this.invokerElement && activation) {
114
- if (this.#firstItem) {
115
- event.preventDefault()
116
- this.popoverElement?.showPopover()
117
- this.#firstItem.focus()
118
- return
119
- }
154
+ const targetIsInvoker = this.invokerElement?.contains(event.target as HTMLElement)
155
+ const eventIsActivation = this.#isActivation(event)
156
+
157
+ if (targetIsInvoker && event.type === 'mousedown') {
158
+ this.#invokerBeingClicked = true
159
+ return
160
+ }
161
+
162
+ // Prevent safari bug that dismisses menu on mousedown instead of allowing
163
+ // the click event to propagate to the button
164
+ if (event.type === 'mousedown') {
165
+ event.preventDefault()
166
+ return
167
+ }
168
+
169
+ if (targetIsInvoker && eventIsActivation) {
170
+ this.#handleInvokerActivated(event)
171
+ this.#invokerBeingClicked = false
172
+ return
120
173
  }
121
174
 
122
175
  // Ignore events within dialogs within menus
@@ -124,84 +177,161 @@ export class ActionMenuElement extends HTMLElement {
124
177
  return
125
178
  }
126
179
 
127
- // If a dialog has been rendered within the menu, we do not want to hide
128
- // the entire menu, as that will also hide the Dialog. Instead we want to
129
- // show the Dialog while hiding just the visible part of the menu.
130
- if ((activation || event.type === 'click') && (event.target as HTMLElement)?.closest('[data-show-dialog-id]')) {
131
- const dialogInvoker = (event.target as HTMLElement)!.closest('[data-show-dialog-id]')
132
- const dialog = this.ownerDocument.getElementById(dialogInvoker?.getAttribute('data-show-dialog-id') || '')
133
- if (dialogInvoker && dialog && this.contains(dialogInvoker) && this.contains(dialog)) {
134
- this.querySelector<HTMLElement>('.ActionListWrap')!.style.display = 'none'
135
- const dialog_controller = new AbortController()
136
- const {signal} = dialog_controller
137
- const handleDialogClose = () => {
138
- dialog_controller.abort()
139
- this.querySelector<HTMLElement>('.ActionListWrap')!.style.display = ''
140
- if (this.popoverElement?.matches(':popover-open')) {
141
- this.popoverElement?.hidePopover()
142
- }
180
+ if (event.type === 'focusout') {
181
+ if (this.#invokerBeingClicked) return
182
+
183
+ // Give the browser time to focus the next element
184
+ requestAnimationFrame(() => {
185
+ if (!this.contains(document.activeElement) || document.activeElement === this.invokerElement) {
186
+ this.#handleFocusOut()
143
187
  }
144
- dialog.addEventListener('close', handleDialogClose, {signal})
145
- dialog.addEventListener('cancel', handleDialogClose, {signal})
146
- return
147
- }
188
+ })
189
+
190
+ return
148
191
  }
149
192
 
150
- if (!this.popoverElement?.matches(':popover-open')) return
193
+ const item = (event.target as Element).closest(menuItemSelectors.join(','))
194
+ const targetIsItem = item !== null
151
195
 
152
- if (event.type === 'include-fragment-replaced') {
153
- if (this.#firstItem) this.#firstItem.focus()
154
- } else if (activation || (event instanceof MouseEvent && event.type === 'click')) {
155
- // Hide popover after current event loop to prevent changes in focus from
156
- // altering the target of the event. Not doing this specifically affects
157
- // <a> tags. It causes the event to be sent to the currently focused element
158
- // instead of the anchor, which effectively prevents navigation, i.e. it
159
- // appears as if hitting enter does nothing. Curiously, clicking instead
160
- // works fine.
161
- if (this.selectVariant !== 'multiple') {
162
- setTimeout(() => {
163
- if (this.popoverElement?.matches(':popover-open')) {
164
- this.popoverElement?.hidePopover()
165
- }
166
- })
196
+ if (targetIsItem && eventIsActivation) {
197
+ const dialogInvoker = item.closest('[data-show-dialog-id]')
198
+
199
+ if (dialogInvoker) {
200
+ const dialog = this.ownerDocument.getElementById(dialogInvoker.getAttribute('data-show-dialog-id') || '')
201
+
202
+ if (dialog && this.contains(dialogInvoker) && this.contains(dialog)) {
203
+ this.#handleDialogItemActivated(event, dialog)
204
+ return
205
+ }
167
206
  }
168
207
 
169
- // The rest of the code below deals with single/multiple selection behavior, and should not
170
- // interfere with events fired by menu items whose behavior is specified outside the library.
171
- if (this.selectVariant !== 'multiple' && this.selectVariant !== 'single') return
208
+ this.#activateItem(event, item)
209
+ this.#handleItemActivated(event, item)
210
+ return
211
+ }
172
212
 
173
- const item = (event.target as Element).closest(menuItemSelectors.join(','))
174
- if (!item) return
175
- const ariaChecked = item.getAttribute('aria-checked')
176
- const checked = ariaChecked !== 'true'
213
+ if (event.type === 'include-fragment-replaced') {
214
+ this.#handleIncludeFragmentReplaced()
215
+ }
216
+ }
177
217
 
178
- if (this.selectVariant === 'single') {
179
- // Only check, never uncheck here. Single-select mode does not allow unchecking a checked item.
180
- if (checked) {
181
- item.setAttribute('aria-checked', 'true')
182
- }
218
+ #handleInvokerActivated(event: Event) {
219
+ event.preventDefault()
220
+ event.stopPropagation()
183
221
 
184
- for (const checkedItem of this.querySelectorAll('[aria-checked]')) {
185
- if (checkedItem !== item) {
186
- checkedItem.setAttribute('aria-checked', 'false')
187
- }
188
- }
222
+ if (this.#isOpen()) {
223
+ this.#hide()
224
+ } else {
225
+ this.#show()
226
+ this.#firstItem?.focus()
227
+ }
228
+ }
189
229
 
190
- this.#setDynamicLabel()
191
- } else {
192
- // multi-select mode allows unchecking a checked item
193
- item.setAttribute('aria-checked', `${checked}`)
230
+ #handleDialogItemActivated(event: Event, dialog: HTMLElement) {
231
+ this.querySelector<HTMLElement>('.ActionListWrap')!.style.display = 'none'
232
+ const dialog_controller = new AbortController()
233
+ const {signal} = dialog_controller
234
+ const handleDialogClose = () => {
235
+ dialog_controller.abort()
236
+ this.querySelector<HTMLElement>('.ActionListWrap')!.style.display = ''
237
+ if (this.#isOpen()) {
238
+ this.#hide()
194
239
  }
240
+ }
241
+ dialog.addEventListener('close', handleDialogClose, {signal})
242
+ dialog.addEventListener('cancel', handleDialogClose, {signal})
243
+ }
244
+
245
+ #handleItemActivated(event: Event, item: Element) {
246
+ // Hide popover after current event loop to prevent changes in focus from
247
+ // altering the target of the event. Not doing this specifically affects
248
+ // <a> tags. It causes the event to be sent to the currently focused element
249
+ // instead of the anchor, which effectively prevents navigation, i.e. it
250
+ // appears as if hitting enter does nothing. Curiously, clicking instead
251
+ // works fine.
252
+ if (this.selectVariant !== 'multiple') {
253
+ setTimeout(() => {
254
+ if (this.#isOpen()) {
255
+ this.#hide()
256
+ }
257
+ })
258
+ }
259
+
260
+ // The rest of the code below deals with single/multiple selection behavior, and should not
261
+ // interfere with events fired by menu items whose behavior is specified outside the library.
262
+ if (this.selectVariant !== 'multiple' && this.selectVariant !== 'single') return
195
263
 
196
- this.#updateInput()
264
+ const ariaChecked = item.getAttribute('aria-checked')
265
+ const checked = ariaChecked !== 'true'
197
266
 
198
- if (event instanceof KeyboardEvent && event.target instanceof HTMLButtonElement) {
199
- // prevent buttons from being clicked twice
200
- event.preventDefault()
267
+ if (this.selectVariant === 'single') {
268
+ // Only check, never uncheck here. Single-select mode does not allow unchecking a checked item.
269
+ if (checked) {
270
+ item.setAttribute('aria-checked', 'true')
271
+ }
272
+
273
+ for (const checkedItem of this.querySelectorAll('[aria-checked]')) {
274
+ if (checkedItem !== item) {
275
+ checkedItem.setAttribute('aria-checked', 'false')
276
+ }
201
277
  }
278
+
279
+ this.#setDynamicLabel()
280
+ } else {
281
+ // multi-select mode allows unchecking a checked item
282
+ item.setAttribute('aria-checked', `${checked}`)
283
+ }
284
+
285
+ this.#updateInput()
286
+
287
+ if (event instanceof KeyboardEvent && event.target instanceof HTMLButtonElement) {
288
+ // prevent buttons from being clicked twice
289
+ event.preventDefault()
290
+ return
202
291
  }
203
292
  }
204
293
 
294
+ #activateItem(event: Event, item: Element) {
295
+ const eventWillActivateByDefault =
296
+ (event instanceof MouseEvent && event.type === 'click') ||
297
+ (event instanceof KeyboardEvent &&
298
+ event.type === 'keydown' &&
299
+ !(event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) &&
300
+ event.key === 'Enter')
301
+
302
+ // if the event will result in activating the current item by default, i.e. is a
303
+ // mouse click or keyboard enter, bail out
304
+ if (eventWillActivateByDefault) return
305
+
306
+ // otherwise, event will not result in activation by default, so we stop it and
307
+ // simulate a click
308
+ event.stopPropagation()
309
+ const elem = item as HTMLElement
310
+ elem.click()
311
+ }
312
+
313
+ #handleIncludeFragmentReplaced() {
314
+ if (this.#firstItem) this.#firstItem.focus()
315
+ this.#softDisableItems()
316
+ }
317
+
318
+ // Close when focus leaves menu
319
+ #handleFocusOut() {
320
+ this.#hide()
321
+ }
322
+
323
+ #show() {
324
+ this.popoverElement?.showPopover()
325
+ }
326
+
327
+ #hide() {
328
+ this.popoverElement?.hidePopover()
329
+ }
330
+
331
+ #isOpen() {
332
+ return this.popoverElement?.matches(':popover-open')
333
+ }
334
+
205
335
  #setDynamicLabel() {
206
336
  if (!this.dynamicLabel) return
207
337
  const invokerLabel = this.invokerLabel
@@ -261,18 +391,13 @@ export class ActionMenuElement extends HTMLElement {
261
391
  }
262
392
  }
263
393
 
264
- #isActivationKeydown(event: Event): boolean {
265
- return (
266
- event instanceof KeyboardEvent &&
267
- event.type === 'keydown' &&
268
- !(event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) &&
269
- (event.key === 'Enter' || event.key === ' ')
270
- )
271
- }
272
-
273
394
  get #firstItem(): HTMLElement | null {
274
395
  return this.querySelector(menuItemSelectors.join(','))
275
396
  }
397
+
398
+ get #items(): HTMLElement[] {
399
+ return Array.from(this.querySelectorAll(menuItemSelectors.join(',')))
400
+ }
276
401
  }
277
402
 
278
403
  if (!window.customElements.get('action-menu')) {
@@ -111,8 +111,6 @@ module Primer
111
111
  system_arguments,
112
112
  { aria: { disabled: true } }
113
113
  )
114
-
115
- content_arguments[:disabled] = "" if content_arguments[:tag] == :button
116
114
  end
117
115
 
118
116
  { data: data, **system_arguments, content_arguments: content_arguments }
@@ -2,12 +2,5 @@
2
2
  "SegmentedControl-item",
3
3
  "SegmentedControl-item--selected": @selected
4
4
  ) %>" role="listitem" data-targets="segmented-control.items">
5
- <% if @hide_labels %>
6
- <%= render Primer::Beta::IconButton.new(icon: @icon, "aria-label": @label, **@system_arguments) %>
7
- <% else %>
8
- <%= render Primer::Beta::Button.new(**@system_arguments) do |button| %>
9
- <% button.with_leading_visual_icon(icon: @icon) unless @icon.nil? %>
10
- <%= @label %>
11
- <% end %>
12
- <% end %>
5
+ <%= render @button %>
13
6
  </li>
@@ -13,16 +13,50 @@ module Primer
13
13
  # @param selected [Boolean] Whether the item is selected
14
14
  # @param icon [Symbol] The icon to use
15
15
  # @param hide_labels [Symbol] Whether to only show the icon
16
- def initialize(label:, selected: false, icon: nil, hide_labels: false, **system_arguments)
17
- @icon = icon
18
- @hide_labels = hide_labels
19
- @label = label
16
+ def initialize(
17
+ label:,
18
+ selected: false,
19
+ icon: nil,
20
+ hide_labels: false,
21
+ **system_arguments
22
+ )
20
23
  @selected = selected
21
24
 
22
25
  @system_arguments = system_arguments
23
26
  @system_arguments[:"data-action"] = "click:segmented-control#select" if system_arguments[:href].nil?
24
27
  @system_arguments[:"aria-current"] = selected
25
28
  @system_arguments[:scheme] = :invisible
29
+
30
+ if hide_labels
31
+ @button = Primer::Beta::IconButton.new(
32
+ icon: icon,
33
+ "aria-label": label,
34
+ **@system_arguments
35
+ )
36
+ else
37
+ @button = Primer::Beta::Button.new(**@system_arguments)
38
+ @button.with_leading_visual_icon(icon: icon) if icon
39
+ @button.with_content(label)
40
+ end
41
+ end
42
+
43
+ # @!parse
44
+ # # Optional trailing Label
45
+ # #
46
+ # # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Beta::Button) %>'s `with_trailing_visual_label` slot.
47
+ # renders_one(:trailing_visual_label)
48
+
49
+ # Optional trailing label.
50
+ #
51
+ # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Beta::Button) %>'s `with_trailing_visual_label` slot.
52
+ def with_trailing_visual_label(**system_arguments, &block)
53
+ @button.with_trailing_visual_label(**system_arguments, &block)
54
+ end
55
+
56
+ private
57
+
58
+ def before_render
59
+ content
26
60
  end
27
61
  end
28
62
  end
@@ -1 +1 @@
1
- .SegmentedControl{background-color:var(--controlTrack-bgColor-rest,var(--color-segmented-control-bg));border-radius:var(--borderRadius-medium,.375rem);display:inline-flex;list-style:none}.SegmentedControl-item{border:var(--borderWidth-thin,max(1px,.0625rem)) solid #0000;border-radius:var(--borderRadius-medium,.375rem);display:inline-flex;padding:var(--control-xsmall-paddingInline-condensed,.25rem);position:relative}.SegmentedControl-item .Button--invisible:hover:not(:disabled){background-color:var(--controlTrack-bgColor-hover,var(--color-action-list-item-default-hover-bg))}.SegmentedControl-item .Button--invisible:active:not(:disabled){background-color:var(--controlTrack-bgColor-active,var(--color-action-list-item-default-active-bg))}.SegmentedControl-item.SegmentedControl-item--selected{background-color:var(--controlKnob-bgColor-rest,var(--color-segmented-control-button-bg));border-color:var(--controlKnob-borderColor-rest,var(--color-segmented-control-button-selected-border))}.SegmentedControl-item.SegmentedControl-item--selected .Button{font-weight:var(--base-text-weight-semibold,600)}.SegmentedControl-item.SegmentedControl-item--selected .Button:hover{background-color:initial}.SegmentedControl-item.SegmentedControl-item--selected:before{border-color:#0000!important}.SegmentedControl-item.SegmentedControl-item--selected+.SegmentedControl-item:before{border-color:#0000}.SegmentedControl-item .Button-label[data-content]:before{content:attr(data-content);display:block;font-weight:var(--base-text-weight-semibold,600);height:0;visibility:hidden}.SegmentedControl-item:not(:first-child):before{border-left:var(--borderWidth-thin,max(1px,.0625rem)) solid var(--borderColor-default,var(--color-border-default));content:"";inset:0 0 0 -1px;margin-bottom:var(--control-medium-paddingBlock,.375rem);margin-top:var(--control-medium-paddingBlock,.375rem);position:absolute}.SegmentedControl-item .Button{border:0;color:var(--button-default-fgColor-rest,var(--color-btn-text));font-weight:var(--base-text-weight-normal,400);transition:none}.SegmentedControl-item .Button:focus-visible{border-radius:calc(var(--borderRadius-medium,.375rem) - 5px);outline-offset:calc(var(--control-xsmall-paddingInline-condensed,.25rem) - var(--borderWidth-thin,max(1px, .0625rem)))}.SegmentedControl-item .Button--small{height:calc(var(--control-small-size,1.75rem) - var(--control-xsmall-paddingInline-condensed,.25rem)*2 - var(--borderWidth-thin,max(1px, .0625rem))*2);padding:0 calc(var(--control-small-paddingInline-condensed,.5rem) - var(--control-xsmall-paddingInline-condensed,.25rem))}.SegmentedControl-item .Button--small.Button--iconOnly{width:calc(var(--control-medium-size,2rem) - var(--control-xsmall-paddingInline-condensed,.25rem)*2 - var(--borderWidth-thin,max(1px, .0625rem))*2)}.SegmentedControl-item .Button--small.Button--iconOnly:before{content:"";height:100%;left:50%;min-height:var(--control-medium-size,2rem);min-width:var(--control-medium-size,2rem);position:absolute;top:50%;transform:translateX(-50%) translateY(-50%);width:100%}.SegmentedControl-item .Button--medium{height:calc(var(--control-medium-size,2rem) - var(--control-xsmall-paddingInline-condensed,.25rem)*2 - var(--borderWidth-thin,max(1px, .0625rem))*2);padding:0 calc(var(--control-medium-paddingInline-normal,.75rem) - var(--control-xsmall-paddingInline-condensed,.25rem))}.SegmentedControl-item .Button--medium.Button--iconOnly{width:calc(var(--control-medium-size,2rem) - var(--control-xsmall-paddingInline-condensed,.25rem)*2 - var(--borderWidth-thin,max(1px, .0625rem))*2)}.SegmentedControl-item .Button--medium.Button--iconOnly:before{content:"";height:100%;left:50%;min-height:var(--control-medium-size,2rem);min-width:var(--control-medium-size,2rem);position:absolute;top:50%;transform:translateX(-50%) translateY(-50%);width:100%}.SegmentedControl-item .Button--large{height:calc(var(--control-large-size,2.5rem) - var(--control-xsmall-paddingInline-condensed,.25rem)*2 - var(--borderWidth-thin,max(1px, .0625rem))*2);padding:0 calc(var(--control-large-paddingInline-spacious,1rem) - var(--control-xsmall-paddingInline-condensed,.25rem))}.SegmentedControl-item .Button--large.Button--iconOnly{width:calc(var(--control-large-size,2.5rem) - var(--control-xsmall-paddingInline-condensed,.25rem)*2 - var(--borderWidth-thin,max(1px, .0625rem))*2)}.SegmentedControl-item .Button--large.Button--iconOnly:before{content:"";height:100%;left:50%;min-height:var(--control-large-size,2.5rem);min-width:var(--control-large-size,2.5rem);position:absolute;top:50%;transform:translateX(-50%) translateY(-50%);width:100%}.SegmentedControl-item .Button--iconOnly{padding:initial}.SegmentedControl-item .Button--invisible.Button--invisible-noVisuals .Button-label{color:var(--button-default-fgColor-rest,var(--color-btn-text))}.SegmentedControl--fullWidth{display:flex}.SegmentedControl--fullWidth .SegmentedControl-item{flex:1;justify-content:center}.SegmentedControl--fullWidth .Button--iconOnly,.SegmentedControl--fullWidth .Button-withTooltip{width:100%}
1
+ .SegmentedControl{background-color:var(--controlTrack-bgColor-rest,var(--color-segmented-control-bg));border-radius:var(--borderRadius-medium,.375rem);display:inline-flex;list-style:none}.SegmentedControl-item{border:var(--borderWidth-thin,max(1px,.0625rem)) solid #0000;border-radius:var(--borderRadius-medium,.375rem);display:inline-flex;padding:var(--control-xsmall-paddingInline-condensed,.25rem);position:relative}.SegmentedControl-item .Button--invisible:hover:not(:disabled){background-color:var(--controlTrack-bgColor-hover,var(--color-action-list-item-default-hover-bg))}.SegmentedControl-item .Button--invisible:active:not(:disabled){background-color:var(--controlTrack-bgColor-active,var(--color-action-list-item-default-active-bg))}.SegmentedControl-item.SegmentedControl-item--selected{background-color:var(--controlKnob-bgColor-rest,var(--color-segmented-control-button-bg));border-color:var(--controlKnob-borderColor-rest,var(--color-segmented-control-button-selected-border))}.SegmentedControl-item.SegmentedControl-item--selected .Button{font-weight:var(--base-text-weight-semibold,600)}.SegmentedControl-item.SegmentedControl-item--selected .Button:hover{background-color:initial}.SegmentedControl-item.SegmentedControl-item--selected:before{border-color:#0000!important}.SegmentedControl-item.SegmentedControl-item--selected+.SegmentedControl-item:before{border-color:#0000}.SegmentedControl-item .Button-label[data-content]:before{content:attr(data-content);display:block;font-weight:var(--base-text-weight-semibold,600);height:0;visibility:hidden}.SegmentedControl-item:not(:first-child):before{border-left:var(--borderWidth-thin,max(1px,.0625rem)) solid var(--borderColor-default,var(--color-border-default));content:"";inset:0 0 0 -1px;margin-bottom:var(--control-medium-paddingBlock,.375rem);margin-top:var(--control-medium-paddingBlock,.375rem);position:absolute}.SegmentedControl-item .Button{border:0;color:var(--button-default-fgColor-rest,var(--color-btn-text));font-weight:var(--base-text-weight-normal,400);transition:none}.SegmentedControl-item .Button:focus-visible{border-radius:calc(var(--borderRadius-medium,.375rem) - 5px);outline-offset:calc(var(--control-xsmall-paddingInline-condensed,.25rem) - var(--borderWidth-thin,max(1px, .0625rem)))}.SegmentedControl-item .Button--small{height:calc(var(--control-small-size,1.75rem) - var(--control-xsmall-paddingInline-condensed,.25rem)*2 - var(--borderWidth-thin,max(1px, .0625rem))*2);padding:0 calc(var(--control-small-paddingInline-condensed,.5rem) - var(--control-xsmall-paddingInline-condensed,.25rem))}.SegmentedControl-item .Button--small.Button--iconOnly{width:calc(var(--control-medium-size,2rem) - var(--control-xsmall-paddingInline-condensed,.25rem)*2 - var(--borderWidth-thin,max(1px, .0625rem))*2)}.SegmentedControl-item .Button--small.Button--iconOnly:before{content:"";height:100%;left:50%;min-height:var(--control-medium-size,2rem);min-width:var(--control-medium-size,2rem);position:absolute;top:50%;transform:translateX(-50%) translateY(-50%);width:100%}.SegmentedControl-item .Button--medium{height:calc(var(--control-medium-size,2rem) - var(--control-xsmall-paddingInline-condensed,.25rem)*2 - var(--borderWidth-thin,max(1px, .0625rem))*2);padding:0 calc(var(--control-medium-paddingInline-normal,.75rem) - var(--control-xsmall-paddingInline-condensed,.25rem))}.SegmentedControl-item .Button--medium.Button--iconOnly{width:calc(var(--control-medium-size,2rem) - var(--control-xsmall-paddingInline-condensed,.25rem)*2 - var(--borderWidth-thin,max(1px, .0625rem))*2)}.SegmentedControl-item .Button--medium.Button--iconOnly:before{content:"";height:100%;left:50%;min-height:var(--control-medium-size,2rem);min-width:var(--control-medium-size,2rem);position:absolute;top:50%;transform:translateX(-50%) translateY(-50%);width:100%}.SegmentedControl-item .Button--large{height:calc(var(--control-large-size,2.5rem) - var(--control-xsmall-paddingInline-condensed,.25rem)*2 - var(--borderWidth-thin,max(1px, .0625rem))*2);padding:0 calc(var(--control-large-paddingInline-spacious,1rem) - var(--control-xsmall-paddingInline-condensed,.25rem))}.SegmentedControl-item .Button--large.Button--iconOnly{width:calc(var(--control-large-size,2.5rem) - var(--control-xsmall-paddingInline-condensed,.25rem)*2 - var(--borderWidth-thin,max(1px, .0625rem))*2)}.SegmentedControl-item .Button--large.Button--iconOnly:before{content:"";height:100%;left:50%;min-height:var(--control-large-size,2.5rem);min-width:var(--control-large-size,2.5rem);position:absolute;top:50%;transform:translateX(-50%) translateY(-50%);width:100%}.SegmentedControl-item .Button--iconOnly{padding:initial}.SegmentedControl-item .Button--invisible.Button--invisible-noVisuals .Button-label{color:var(--button-default-fgColor-rest,var(--color-btn-text))}.SegmentedControl--fullWidth{display:flex}.SegmentedControl--fullWidth .SegmentedControl-item{flex:1;justify-content:center}.SegmentedControl--fullWidth .Button--iconOnly,.SegmentedControl--fullWidth .Button-withTooltip{display:flex;justify-content:center;width:100%!important}
@@ -1 +1 @@
1
- {"version":3,"sources":["segmented_control.pcss","<no source>"],"names":[],"mappings":"AAEA,kBAGE,mFAAqF,CACrF,gDAAyC,CAHzC,mBAAoB,CACpB,eAGF,CAEA,uBAGE,4DAAiD,CACjD,gDAAyC,CAFzC,mBAAoB,CAGpB,4DAAsD,CAJtD,iBAkIF,CA3HI,+DACE,iGACF,CAEA,gEACE,mGACF,CAIF,uDACE,yFAA2F,CAC3F,sGAiBF,CAfE,+DACE,gDAKF,CAHE,qEACE,wBACF,CAGF,8DACE,4BACF,CAEA,qFACE,kBACF,CAIF,0DAKE,0BAA2B,CAJ3B,aAAc,CAEd,gDAA6C,CAD7C,QAAS,CAET,iBAEF,CAIE,gDAME,kHAAqE,CADrE,UAAW,CAHX,gBAAiB,CAEjB,wDAAiD,CADjD,qDAA8C,CAF9C,iBAMF,CAIF,+BACE,QAAS,CAGT,8DAAyC,CAFzC,8CAA2C,CAC3C,eAOF,CAJE,6CAEE,4DAAqD,CADrD,sHAEF,CAGF,sCACE,sJAEC,CACD,yHAWF,CATE,uDACE,mJAOF,CAFI,8DC9FR,WAAA,YAAA,SAAA,2CAAA,0CAAA,kBAAA,QAAA,4CAAA,UD8FoF,CAKlF,uCACE,oJAEC,CACD,wHAWF,CATE,wDACE,mJAOF,CAFI,+DC/GR,WAAA,YAAA,SAAA,2CAAA,0CAAA,kBAAA,QAAA,4CAAA,UD+GoF,CAKlF,sCACE,qJAEC,CACD,uHAWF,CATE,uDACE,oJAOF,CAFI,8DChIR,WAAA,YAAA,SAAA,4CAAA,2CAAA,kBAAA,QAAA,4CAAA,UDgIkF,CAKhF,yCACE,eACF,CAEA,oFACE,8DACF,CAIF,6BACE,YAYF,CAVE,oDACE,MAAO,CACP,sBACF,CAGA,gGAEE,UACF","file":"segmented_control.css","sourcesContent":["/* SegmentedControl */\n\n.SegmentedControl {\n display: inline-flex;\n list-style: none;\n background-color: var(--controlTrack-bgColor-rest, var(--color-segmented-control-bg));\n border-radius: var(--borderRadius-medium);\n}\n\n.SegmentedControl-item {\n position: relative;\n display: inline-flex;\n border: var(--borderWidth-thin) solid transparent;\n border-radius: var(--borderRadius-medium);\n padding: var(--control-xsmall-paddingInline-condensed);\n\n & .Button--invisible {\n &:hover:not(:disabled) {\n background-color: var(--controlTrack-bgColor-hover, var(--color-action-list-item-default-hover-bg));\n }\n\n &:active:not(:disabled) {\n background-color: var(--controlTrack-bgColor-active, var(--color-action-list-item-default-active-bg));\n }\n }\n\n /* Selected ---------------------------------------- */\n &.SegmentedControl-item--selected {\n background-color: var(--controlKnob-bgColor-rest, var(--color-segmented-control-button-bg));\n border-color: var(--controlKnob-borderColor-rest, var(--color-segmented-control-button-selected-border));\n\n & .Button {\n font-weight: var(--base-text-weight-semibold);\n\n &:hover {\n background-color: transparent;\n }\n }\n\n &::before {\n border-color: transparent !important;\n }\n\n & + .SegmentedControl-item::before {\n border-color: transparent;\n }\n }\n\n /* renders a visibly hidden \"copy\" of the text in bold, reserving box space for when text becomes bold on selected */\n & .Button-label[data-content]::before {\n display: block;\n height: 0;\n font-weight: var(--base-text-weight-semibold);\n visibility: hidden;\n content: attr(data-content);\n }\n\n /* Separator lines */\n &:not(:first-child) {\n &::before {\n position: absolute;\n inset: 0 0 0 -1px;\n margin-top: var(--control-medium-paddingBlock);\n margin-bottom: var(--control-medium-paddingBlock);\n content: '';\n border-left: var(--borderWidth-thin) solid var(--borderColor-default);\n }\n }\n\n /* Button ----------------------------------------- */\n & .Button {\n border: 0;\n font-weight: var(--base-text-weight-normal);\n transition: none;\n color: var(--button-default-fgColor-rest);\n\n &:focus-visible {\n outline-offset: calc(var(--control-xsmall-paddingInline-condensed) - var(--borderWidth-thin));\n border-radius: calc(var(--borderRadius-medium) - 5px);\n }\n }\n\n & .Button--small {\n height: calc(\n var(--control-small-size) - var(--control-xsmall-paddingInline-condensed) * 2 - var(--borderWidth-thin) * 2\n );\n padding: 0 calc(var(--control-small-paddingInline-condensed) - var(--control-xsmall-paddingInline-condensed));\n\n &.Button--iconOnly {\n width: calc(\n var(--control-medium-size) - var(--control-xsmall-paddingInline-condensed) * 2 - var(--borderWidth-thin) * 2\n );\n\n &::before {\n @mixin minTouchTarget var(--control-medium-size), var(--control-medium-size);\n }\n }\n }\n\n & .Button--medium {\n height: calc(\n var(--control-medium-size) - var(--control-xsmall-paddingInline-condensed) * 2 - var(--borderWidth-thin) * 2\n );\n padding: 0 calc(var(--control-medium-paddingInline-normal) - var(--control-xsmall-paddingInline-condensed));\n\n &.Button--iconOnly {\n width: calc(\n var(--control-medium-size) - var(--control-xsmall-paddingInline-condensed) * 2 - var(--borderWidth-thin) * 2\n );\n\n &::before {\n @mixin minTouchTarget var(--control-medium-size), var(--control-medium-size);\n }\n }\n }\n\n & .Button--large {\n height: calc(\n var(--control-large-size) - var(--control-xsmall-paddingInline-condensed) * 2 - var(--borderWidth-thin) * 2\n );\n padding: 0 calc(var(--control-large-paddingInline-spacious) - var(--control-xsmall-paddingInline-condensed));\n\n &.Button--iconOnly {\n width: calc(\n var(--control-large-size) - var(--control-xsmall-paddingInline-condensed) * 2 - var(--borderWidth-thin) * 2\n );\n\n &::before {\n @mixin minTouchTarget var(--control-large-size), var(--control-large-size);\n }\n }\n }\n\n & .Button--iconOnly {\n padding: initial;\n }\n\n & .Button--invisible.Button--invisible-noVisuals .Button-label {\n color: var(--button-default-fgColor-rest);\n }\n}\n\n/* fullWidth */\n.SegmentedControl--fullWidth {\n display: flex;\n\n & .SegmentedControl-item {\n flex: 1;\n justify-content: center;\n }\n\n /* is .Button-withTooltip used anywhere? can't find use of it */\n & .Button--iconOnly,\n & .Button-withTooltip {\n width: 100%;\n }\n}\n",null]}
1
+ {"version":3,"sources":["segmented_control.pcss","<no source>"],"names":[],"mappings":"AAEA,kBAGE,mFAAqF,CACrF,gDAAyC,CAHzC,mBAAoB,CACpB,eAGF,CAEA,uBAGE,4DAAiD,CACjD,gDAAyC,CAFzC,mBAAoB,CAGpB,4DAAsD,CAJtD,iBAkIF,CA3HI,+DACE,iGACF,CAEA,gEACE,mGACF,CAIF,uDACE,yFAA2F,CAC3F,sGAiBF,CAfE,+DACE,gDAKF,CAHE,qEACE,wBACF,CAGF,8DACE,4BACF,CAEA,qFACE,kBACF,CAIF,0DAKE,0BAA2B,CAJ3B,aAAc,CAEd,gDAA6C,CAD7C,QAAS,CAET,iBAEF,CAIE,gDAME,kHAAqE,CADrE,UAAW,CAHX,gBAAiB,CAEjB,wDAAiD,CADjD,qDAA8C,CAF9C,iBAMF,CAIF,+BACE,QAAS,CAGT,8DAAyC,CAFzC,8CAA2C,CAC3C,eAOF,CAJE,6CAEE,4DAAqD,CADrD,sHAEF,CAGF,sCACE,sJAEC,CACD,yHAWF,CATE,uDACE,mJAOF,CAFI,8DC9FR,WAAA,YAAA,SAAA,2CAAA,0CAAA,kBAAA,QAAA,4CAAA,UD8FoF,CAKlF,uCACE,oJAEC,CACD,wHAWF,CATE,wDACE,mJAOF,CAFI,+DC/GR,WAAA,YAAA,SAAA,2CAAA,0CAAA,kBAAA,QAAA,4CAAA,UD+GoF,CAKlF,sCACE,qJAEC,CACD,uHAWF,CATE,uDACE,oJAOF,CAFI,8DChIR,WAAA,YAAA,SAAA,4CAAA,2CAAA,kBAAA,QAAA,4CAAA,UDgIkF,CAKhF,yCACE,eACF,CAEA,oFACE,8DACF,CAIF,6BACE,YAcF,CAZE,oDACE,MAAO,CACP,sBACF,CAGA,gGAGE,YAAa,CACb,sBAAuB,CAFvB,oBAGF","file":"segmented_control.css","sourcesContent":["/* SegmentedControl */\n\n.SegmentedControl {\n display: inline-flex;\n list-style: none;\n background-color: var(--controlTrack-bgColor-rest, var(--color-segmented-control-bg));\n border-radius: var(--borderRadius-medium);\n}\n\n.SegmentedControl-item {\n position: relative;\n display: inline-flex;\n border: var(--borderWidth-thin) solid transparent;\n border-radius: var(--borderRadius-medium);\n padding: var(--control-xsmall-paddingInline-condensed);\n\n & .Button--invisible {\n &:hover:not(:disabled) {\n background-color: var(--controlTrack-bgColor-hover, var(--color-action-list-item-default-hover-bg));\n }\n\n &:active:not(:disabled) {\n background-color: var(--controlTrack-bgColor-active, var(--color-action-list-item-default-active-bg));\n }\n }\n\n /* Selected ---------------------------------------- */\n &.SegmentedControl-item--selected {\n background-color: var(--controlKnob-bgColor-rest, var(--color-segmented-control-button-bg));\n border-color: var(--controlKnob-borderColor-rest, var(--color-segmented-control-button-selected-border));\n\n & .Button {\n font-weight: var(--base-text-weight-semibold);\n\n &:hover {\n background-color: transparent;\n }\n }\n\n &::before {\n border-color: transparent !important;\n }\n\n & + .SegmentedControl-item::before {\n border-color: transparent;\n }\n }\n\n /* renders a visibly hidden \"copy\" of the text in bold, reserving box space for when text becomes bold on selected */\n & .Button-label[data-content]::before {\n display: block;\n height: 0;\n font-weight: var(--base-text-weight-semibold);\n visibility: hidden;\n content: attr(data-content);\n }\n\n /* Separator lines */\n &:not(:first-child) {\n &::before {\n position: absolute;\n inset: 0 0 0 -1px;\n margin-top: var(--control-medium-paddingBlock);\n margin-bottom: var(--control-medium-paddingBlock);\n content: '';\n border-left: var(--borderWidth-thin) solid var(--borderColor-default);\n }\n }\n\n /* Button ----------------------------------------- */\n & .Button {\n border: 0;\n font-weight: var(--base-text-weight-normal);\n transition: none;\n color: var(--button-default-fgColor-rest);\n\n &:focus-visible {\n outline-offset: calc(var(--control-xsmall-paddingInline-condensed) - var(--borderWidth-thin));\n border-radius: calc(var(--borderRadius-medium) - 5px);\n }\n }\n\n & .Button--small {\n height: calc(\n var(--control-small-size) - var(--control-xsmall-paddingInline-condensed) * 2 - var(--borderWidth-thin) * 2\n );\n padding: 0 calc(var(--control-small-paddingInline-condensed) - var(--control-xsmall-paddingInline-condensed));\n\n &.Button--iconOnly {\n width: calc(\n var(--control-medium-size) - var(--control-xsmall-paddingInline-condensed) * 2 - var(--borderWidth-thin) * 2\n );\n\n &::before {\n @mixin minTouchTarget var(--control-medium-size), var(--control-medium-size);\n }\n }\n }\n\n & .Button--medium {\n height: calc(\n var(--control-medium-size) - var(--control-xsmall-paddingInline-condensed) * 2 - var(--borderWidth-thin) * 2\n );\n padding: 0 calc(var(--control-medium-paddingInline-normal) - var(--control-xsmall-paddingInline-condensed));\n\n &.Button--iconOnly {\n width: calc(\n var(--control-medium-size) - var(--control-xsmall-paddingInline-condensed) * 2 - var(--borderWidth-thin) * 2\n );\n\n &::before {\n @mixin minTouchTarget var(--control-medium-size), var(--control-medium-size);\n }\n }\n }\n\n & .Button--large {\n height: calc(\n var(--control-large-size) - var(--control-xsmall-paddingInline-condensed) * 2 - var(--borderWidth-thin) * 2\n );\n padding: 0 calc(var(--control-large-paddingInline-spacious) - var(--control-xsmall-paddingInline-condensed));\n\n &.Button--iconOnly {\n width: calc(\n var(--control-large-size) - var(--control-xsmall-paddingInline-condensed) * 2 - var(--borderWidth-thin) * 2\n );\n\n &::before {\n @mixin minTouchTarget var(--control-large-size), var(--control-large-size);\n }\n }\n }\n\n & .Button--iconOnly {\n padding: initial;\n }\n\n & .Button--invisible.Button--invisible-noVisuals .Button-label {\n color: var(--button-default-fgColor-rest);\n }\n}\n\n/* fullWidth */\n.SegmentedControl--fullWidth {\n display: flex;\n\n & .SegmentedControl-item {\n flex: 1;\n justify-content: center;\n }\n\n /* is .Button-withTooltip used anywhere? can't find use of it */\n & .Button--iconOnly,\n & .Button-withTooltip {\n width: 100% !important;\n display: flex;\n justify-content: center;\n }\n}\n",null]}
@@ -152,6 +152,8 @@
152
152
  /* is .Button-withTooltip used anywhere? can't find use of it */
153
153
  & .Button--iconOnly,
154
154
  & .Button-withTooltip {
155
- width: 100%;
155
+ width: 100% !important;
156
+ display: flex;
157
+ justify-content: center;
156
158
  }
157
159
  }
@@ -3,6 +3,7 @@ type Direction = 'n' | 's' | 'e' | 'w' | 'ne' | 'se' | 'nw' | 'sw';
3
3
  declare class ToolTipElement extends HTMLElement {
4
4
  #private;
5
5
  styles(): string;
6
+ get showReason(): "focus" | "mouse";
6
7
  get htmlFor(): string;
7
8
  set htmlFor(value: string);
8
9
  get type(): 'description' | 'label';
@@ -9,7 +9,7 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
10
10
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
11
  };
12
- var _ToolTipElement_instances, _ToolTipElement_abortController, _ToolTipElement_align, _ToolTipElement_side, _ToolTipElement_allowUpdatePosition, _ToolTipElement_update, _ToolTipElement_updateControlReference, _ToolTipElement_updateDirection, _ToolTipElement_updatePosition;
12
+ var _ToolTipElement_instances, _ToolTipElement_abortController, _ToolTipElement_align, _ToolTipElement_side, _ToolTipElement_allowUpdatePosition, _ToolTipElement_showReason, _ToolTipElement_update, _ToolTipElement_updateControlReference, _ToolTipElement_updateDirection, _ToolTipElement_updatePosition;
13
13
  import '@oddbird/popover-polyfill';
14
14
  import { getAnchoredPosition } from '@primer/behaviors';
15
15
  const isPopoverOpen = (() => {
@@ -60,6 +60,15 @@ function closeOpenTooltips(except) {
60
60
  function focusOutListener() {
61
61
  closeOpenTooltips();
62
62
  }
63
+ function focusInListener(event) {
64
+ setTimeout(() => {
65
+ for (const tooltip of openTooltips) {
66
+ if (isPopoverOpen(tooltip) && tooltip.showReason === 'focus' && tooltip.control !== event.target) {
67
+ tooltip.hidePopover();
68
+ }
69
+ }
70
+ }, 0);
71
+ }
63
72
  const tooltips = new Set();
64
73
  const openTooltips = new Set();
65
74
  class ToolTipElement extends HTMLElement {
@@ -70,6 +79,7 @@ class ToolTipElement extends HTMLElement {
70
79
  _ToolTipElement_align.set(this, 'center');
71
80
  _ToolTipElement_side.set(this, 'outside-bottom');
72
81
  _ToolTipElement_allowUpdatePosition.set(this, false);
82
+ _ToolTipElement_showReason.set(this, 'mouse');
73
83
  }
74
84
  styles() {
75
85
  return `
@@ -203,6 +213,9 @@ class ToolTipElement extends HTMLElement {
203
213
  }
204
214
  `;
205
215
  }
216
+ get showReason() {
217
+ return __classPrivateFieldGet(this, _ToolTipElement_showReason, "f");
218
+ }
206
219
  get htmlFor() {
207
220
  return this.getAttribute('for') || '';
208
221
  }
@@ -269,6 +282,7 @@ class ToolTipElement extends HTMLElement {
269
282
  signal
270
283
  });
271
284
  this.ownerDocument.addEventListener('focusout', focusOutListener);
285
+ this.ownerDocument.addEventListener('focusin', focusInListener);
272
286
  this.ownerDocument.addEventListener('keydown', this, { signal });
273
287
  }
274
288
  disconnectedCallback() {
@@ -293,6 +307,7 @@ class ToolTipElement extends HTMLElement {
293
307
  const shouldHide = isMouseLeaveFromButton || isEscapeKeydown || isMouseDownOnButton || isOpeningOtherPopover;
294
308
  await Promise.resolve();
295
309
  if (!showing && shouldShow && !isPopoverOpen(this)) {
310
+ __classPrivateFieldSet(this, _ToolTipElement_showReason, event.type === 'mouseenter' ? 'mouse' : 'focus', "f");
296
311
  this.showPopover();
297
312
  }
298
313
  else if (showing && shouldHide && isPopoverOpen(this)) {
@@ -313,7 +328,7 @@ class ToolTipElement extends HTMLElement {
313
328
  }
314
329
  }
315
330
  }
316
- _ToolTipElement_abortController = new WeakMap(), _ToolTipElement_align = new WeakMap(), _ToolTipElement_side = new WeakMap(), _ToolTipElement_allowUpdatePosition = new WeakMap(), _ToolTipElement_instances = new WeakSet(), _ToolTipElement_update = function _ToolTipElement_update(isOpen) {
331
+ _ToolTipElement_abortController = new WeakMap(), _ToolTipElement_align = new WeakMap(), _ToolTipElement_side = new WeakMap(), _ToolTipElement_allowUpdatePosition = new WeakMap(), _ToolTipElement_showReason = new WeakMap(), _ToolTipElement_instances = new WeakSet(), _ToolTipElement_update = function _ToolTipElement_update(isOpen) {
317
332
  if (isOpen) {
318
333
  openTooltips.add(this);
319
334
  this.classList.remove(TOOLTIP_SR_ONLY_CLASS);
@@ -53,6 +53,16 @@ function focusOutListener() {
53
53
  closeOpenTooltips()
54
54
  }
55
55
 
56
+ function focusInListener(event: Event) {
57
+ setTimeout(() => {
58
+ for (const tooltip of openTooltips) {
59
+ if (isPopoverOpen(tooltip) && tooltip.showReason === 'focus' && tooltip.control !== event.target) {
60
+ tooltip.hidePopover()
61
+ }
62
+ }
63
+ }, 0)
64
+ }
65
+
56
66
  const tooltips = new Set<ToolTipElement>()
57
67
  const openTooltips = new Set<ToolTipElement>()
58
68
  class ToolTipElement extends HTMLElement {
@@ -193,6 +203,10 @@ class ToolTipElement extends HTMLElement {
193
203
  #align: AnchorAlignment = 'center'
194
204
  #side: AnchorSide = 'outside-bottom'
195
205
  #allowUpdatePosition = false
206
+ #showReason: 'focus' | 'mouse' = 'mouse'
207
+ get showReason() {
208
+ return this.#showReason
209
+ }
196
210
 
197
211
  get htmlFor(): string {
198
212
  return this.getAttribute('for') || ''
@@ -270,6 +284,7 @@ class ToolTipElement extends HTMLElement {
270
284
  signal
271
285
  })
272
286
  this.ownerDocument.addEventListener('focusout', focusOutListener)
287
+ this.ownerDocument.addEventListener('focusin', focusInListener)
273
288
  this.ownerDocument.addEventListener('keydown', this, {signal})
274
289
  }
275
290
 
@@ -297,6 +312,7 @@ class ToolTipElement extends HTMLElement {
297
312
 
298
313
  await Promise.resolve()
299
314
  if (!showing && shouldShow && !isPopoverOpen(this)) {
315
+ this.#showReason = event.type === 'mouseenter' ? 'mouse' : 'focus'
300
316
  this.showPopover()
301
317
  } else if (showing && shouldHide && isPopoverOpen(this)) {
302
318
  this.hidePopover()
@@ -19,12 +19,14 @@ module Primer
19
19
  # @param type [Symbol] <%= one_of(Primer::Beta::BaseButton::TYPE_OPTIONS) %>
20
20
  # @param block [Boolean] Whether button is full-width with `display: block`.
21
21
  # @param disabled [Boolean] Whether or not the button is disabled. If true, this option forces `tag:` to `:button`.
22
+ # @param inactive [Boolean] Whether the button looks visually disabled, but can still accept all the same interactions as an enabled button.
22
23
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
23
24
  def initialize(
24
25
  tag: DEFAULT_TAG,
25
26
  type: DEFAULT_TYPE,
26
27
  block: false,
27
28
  disabled: false,
29
+ inactive: false,
28
30
  **system_arguments
29
31
  )
30
32
  @system_arguments = system_arguments
@@ -37,6 +39,8 @@ module Primer
37
39
  "btn-block" => block
38
40
  )
39
41
 
42
+ @system_arguments[:"aria-disabled"] = true if inactive
43
+
40
44
  @disabled = disabled
41
45
  return unless @disabled
42
46