primer_view_components 0.1.5 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +28 -0
  3. data/app/assets/javascripts/primer_view_components.js +1 -1
  4. data/app/assets/javascripts/primer_view_components.js.map +1 -1
  5. data/app/assets/styles/primer_view_components.css +3 -3
  6. data/app/assets/styles/primer_view_components.css.map +1 -1
  7. data/app/components/primer/alpha/action_list/form_wrapper.html.erb +10 -0
  8. data/app/components/primer/alpha/action_list/form_wrapper.rb +61 -0
  9. data/app/components/primer/alpha/action_list/item.html.erb +41 -36
  10. data/app/components/primer/alpha/action_list/item.rb +18 -4
  11. data/app/components/primer/alpha/action_list.css +1 -1
  12. data/app/components/primer/alpha/action_list.css.map +1 -1
  13. data/app/components/primer/alpha/action_list.html.erb +5 -0
  14. data/app/components/primer/alpha/action_list.pcss +37 -37
  15. data/app/components/primer/alpha/action_list.rb +18 -1
  16. data/app/components/primer/alpha/action_menu/action_menu_element.d.ts +8 -1
  17. data/app/components/primer/alpha/action_menu/action_menu_element.js +100 -22
  18. data/app/components/primer/alpha/action_menu/action_menu_element.ts +119 -20
  19. data/app/components/primer/alpha/action_menu/list.rb +27 -19
  20. data/app/components/primer/alpha/action_menu.rb +106 -29
  21. data/app/components/primer/alpha/auto_complete.css +1 -1
  22. data/app/components/primer/alpha/auto_complete.css.map +1 -1
  23. data/app/components/primer/alpha/auto_complete.pcss +2 -2
  24. data/app/components/primer/alpha/banner.css +1 -1
  25. data/app/components/primer/alpha/banner.css.map +1 -1
  26. data/app/components/primer/alpha/banner.pcss +7 -7
  27. data/app/components/primer/alpha/dialog.css +1 -1
  28. data/app/components/primer/alpha/dialog.css.json +0 -2
  29. data/app/components/primer/alpha/dialog.css.map +1 -1
  30. data/app/components/primer/alpha/dialog.pcss +33 -36
  31. data/app/components/primer/alpha/dropdown/menu.rb +1 -1
  32. data/app/components/primer/alpha/dropdown.css +1 -1
  33. data/app/components/primer/alpha/dropdown.css.map +1 -1
  34. data/app/components/primer/alpha/dropdown.pcss +12 -11
  35. data/app/components/primer/alpha/layout.css +1 -1
  36. data/app/components/primer/alpha/layout.css.map +1 -1
  37. data/app/components/primer/alpha/layout.pcss +4 -4
  38. data/app/components/primer/alpha/menu.css +1 -1
  39. data/app/components/primer/alpha/menu.css.map +1 -1
  40. data/app/components/primer/alpha/menu.pcss +20 -20
  41. data/app/components/primer/alpha/modal_dialog.js +12 -0
  42. data/app/components/primer/alpha/modal_dialog.ts +17 -0
  43. data/app/components/primer/alpha/overlay.css +1 -1
  44. data/app/components/primer/alpha/overlay.css.map +1 -1
  45. data/app/components/primer/alpha/overlay.pcss +1 -1
  46. data/app/components/primer/alpha/overlay.rb +9 -5
  47. data/app/components/primer/alpha/segmented_control.css +1 -1
  48. data/app/components/primer/alpha/segmented_control.css.map +1 -1
  49. data/app/components/primer/alpha/segmented_control.pcss +27 -38
  50. data/app/components/primer/alpha/tab_nav.css +1 -1
  51. data/app/components/primer/alpha/tab_nav.css.map +1 -1
  52. data/app/components/primer/alpha/tab_nav.pcss +12 -12
  53. data/app/components/primer/alpha/text_field.css +3 -3
  54. data/app/components/primer/alpha/text_field.css.map +1 -1
  55. data/app/components/primer/alpha/text_field.pcss +74 -88
  56. data/app/components/primer/alpha/toggle_switch.css +1 -1
  57. data/app/components/primer/alpha/toggle_switch.css.map +1 -1
  58. data/app/components/primer/alpha/toggle_switch.pcss +9 -9
  59. data/app/components/primer/alpha/underline_nav.css +1 -1
  60. data/app/components/primer/alpha/underline_nav.css.map +1 -1
  61. data/app/components/primer/alpha/underline_nav.pcss +7 -7
  62. data/app/components/primer/beta/auto_complete/auto_complete.html.erb +1 -1
  63. data/app/components/primer/beta/auto_complete/item.html.erb +9 -9
  64. data/app/components/primer/beta/auto_complete/item.rb +17 -13
  65. data/app/components/primer/beta/auto_complete.rb +20 -2
  66. data/app/components/primer/beta/avatar.css +1 -1
  67. data/app/components/primer/beta/avatar.css.map +1 -1
  68. data/app/components/primer/beta/avatar.pcss +2 -2
  69. data/app/components/primer/beta/avatar_stack.css +1 -1
  70. data/app/components/primer/beta/avatar_stack.css.map +1 -1
  71. data/app/components/primer/beta/avatar_stack.pcss +5 -5
  72. data/app/components/primer/beta/blankslate.css +1 -1
  73. data/app/components/primer/beta/blankslate.css.map +1 -1
  74. data/app/components/primer/beta/blankslate.pcss +13 -13
  75. data/app/components/primer/beta/border_box.css +1 -1
  76. data/app/components/primer/beta/border_box.css.json +1 -1
  77. data/app/components/primer/beta/border_box.css.map +1 -1
  78. data/app/components/primer/beta/border_box.pcss +41 -39
  79. data/app/components/primer/beta/button.css +1 -1
  80. data/app/components/primer/beta/button.css.json +0 -2
  81. data/app/components/primer/beta/button.css.map +1 -1
  82. data/app/components/primer/beta/button.pcss +27 -29
  83. data/app/components/primer/beta/counter.css +1 -1
  84. data/app/components/primer/beta/counter.css.map +1 -1
  85. data/app/components/primer/beta/counter.pcss +3 -3
  86. data/app/components/primer/beta/flash.css +1 -1
  87. data/app/components/primer/beta/flash.css.map +1 -1
  88. data/app/components/primer/beta/flash.pcss +10 -11
  89. data/app/components/primer/beta/label.css +1 -1
  90. data/app/components/primer/beta/label.css.map +1 -1
  91. data/app/components/primer/beta/label.pcss +2 -2
  92. data/app/components/primer/beta/popover.css +1 -1
  93. data/app/components/primer/beta/popover.css.map +1 -1
  94. data/app/components/primer/beta/popover.pcss +4 -4
  95. data/app/components/primer/beta/state.css +1 -1
  96. data/app/components/primer/beta/state.css.map +1 -1
  97. data/app/components/primer/beta/state.pcss +5 -5
  98. data/app/components/primer/beta/subhead.css +1 -1
  99. data/app/components/primer/beta/subhead.css.map +1 -1
  100. data/app/components/primer/beta/subhead.pcss +4 -4
  101. data/app/components/primer/beta/timeline_item.css +1 -1
  102. data/app/components/primer/beta/timeline_item.css.map +1 -1
  103. data/app/components/primer/beta/timeline_item.pcss +13 -13
  104. data/app/components/primer/beta/truncate.css +1 -1
  105. data/app/components/primer/beta/truncate.css.map +1 -1
  106. data/app/components/primer/beta/truncate.pcss +1 -1
  107. data/lib/postcss_mixins/activeIndicatorLine.pcss +1 -1
  108. data/lib/primer/view_components/linters/disallow_component_css_counter.rb +1 -1
  109. data/lib/primer/view_components/version.rb +1 -1
  110. data/lib/primer/yard/component_manifest.rb +1 -0
  111. data/previews/primer/alpha/action_menu_preview/content_labels.html.erb +9 -0
  112. data/previews/primer/alpha/action_menu_preview/multiple_select_form.html.erb +13 -0
  113. data/previews/primer/alpha/action_menu_preview/single_select_form.html.erb +13 -0
  114. data/previews/primer/alpha/action_menu_preview/submitting_forms.html.erb +15 -0
  115. data/previews/primer/alpha/action_menu_preview.rb +89 -38
  116. data/previews/primer/beta/auto_complete_preview.rb +36 -23
  117. data/static/arguments.json +43 -2
  118. data/static/audited_at.json +1 -0
  119. data/static/constants.json +37 -9
  120. data/static/info_arch.json +137 -23
  121. data/static/previews.json +25 -0
  122. data/static/statuses.json +1 -0
  123. metadata +12 -6
@@ -1,5 +1,5 @@
1
1
  :root {
2
- --primer-actionListContent-paddingBlock: var(--primer-control-medium-paddingBlock, 6px);
2
+ --actionListContent-paddingBlock: var(--control-medium-paddingBlock, 6px);
3
3
  }
4
4
 
5
5
  /* ActionList */
@@ -18,7 +18,7 @@
18
18
  .ActionListWrap--divided {
19
19
  & .ActionListItem-label::before {
20
20
  position: absolute;
21
- top: calc(-1 * var(--primer-actionListContent-paddingBlock));
21
+ top: calc(-1 * var(--actionListContent-paddingBlock));
22
22
  display: block;
23
23
  width: 100%;
24
24
  height: 1px;
@@ -30,10 +30,10 @@
30
30
  & .ActionListItem-descriptionWrap--inline {
31
31
  &::before {
32
32
  position: absolute;
33
- top: calc(-1 * var(--primer-actionListContent-paddingBlock));
33
+ top: calc(-1 * var(--actionListContent-paddingBlock));
34
34
  display: block;
35
35
  width: 100%;
36
- height: var(--primer-borderWidth-thin, 1px);
36
+ height: var(--borderWidth-thin, 1px);
37
37
  content: '';
38
38
  background: var(--color-action-list-item-inline-divider);
39
39
  }
@@ -71,7 +71,7 @@
71
71
  position: relative;
72
72
  list-style: none;
73
73
  background-color: transparent;
74
- border-radius: var(--primer-borderRadius-medium, 6px);
74
+ border-radius: var(--borderRadius-medium, 6px);
75
75
 
76
76
  /* state */
77
77
 
@@ -129,9 +129,9 @@
129
129
 
130
130
  &:not(.ActionListItem--navActive, :focus-visible) {
131
131
  /* Support for "Windows high contrast mode" */
132
- outline: solid var(--primer-borderWidth-thin, 1px) transparent;
133
- outline-offset: calc(-1 * var(--primer-borderWidth-thin, 1px));
134
- box-shadow: var(--primer-borderInset-thin, 1px) var(--color-action-list-item-default-active-border);
132
+ outline: solid var(--borderWidth-thin, 1px) transparent;
133
+ outline-offset: calc(-1 * var(--borderWidth-thin, 1px));
134
+ box-shadow: var(--borderInset-thin, 1px) var(--color-action-list-item-default-active-border);
135
135
  }
136
136
  }
137
137
  }
@@ -141,9 +141,9 @@
141
141
 
142
142
  &:not(.ActionListItem--navActive) {
143
143
  /* Support for "Windows high contrast mode" https:sarahmhigley.com/writing/whcm-quick-tips/ */
144
- outline: solid var(--primer-borderWidth-thin, 1px) transparent;
145
- outline-offset: calc(-1 * var(--primer-borderWidth-thin, 1px));
146
- box-shadow: var(--primer-borderInset-thin, 1px) var(--color-action-list-item-default-active-border);
144
+ outline: solid var(--borderWidth-thin, 1px) transparent;
145
+ outline-offset: calc(-1 * var(--borderWidth-thin, 1px));
146
+ box-shadow: var(--borderInset-thin, 1px) var(--color-action-list-item-default-active-border);
147
147
  }
148
148
 
149
149
  & .ActionListItem-label::before,
@@ -189,7 +189,7 @@
189
189
  & .ActionListItem-multiSelectIconRect {
190
190
  fill: var(--color-accent-fg);
191
191
  stroke: var(--color-accent-fg);
192
- stroke-width: var(--primer-borderWidth-thin, 1px);
192
+ stroke-width: var(--borderWidth-thin, 1px);
193
193
  }
194
194
 
195
195
  & .ActionListItem-multiSelectCheckmark {
@@ -232,13 +232,13 @@
232
232
  & .ActionListItem-multiSelectIconRect {
233
233
  fill: var(--color-canvas-default);
234
234
  stroke: var(--color-border-default);
235
- stroke-width: var(--primer-borderWidth-thin, 1px);
235
+ stroke-width: var(--borderWidth-thin, 1px);
236
236
  }
237
237
  }
238
238
 
239
239
  & .ActionListItem-multiSelectIconRect {
240
240
  fill: var(--color-canvas-default);
241
- border: var(--primer-borderWidth-thin, 1px) solid var(--color-border-default);
241
+ border: var(--borderWidth-thin, 1px) solid var(--color-border-default);
242
242
  }
243
243
  }
244
244
 
@@ -360,14 +360,14 @@
360
360
  position: relative;
361
361
  display: grid;
362
362
  width: 100%;
363
- padding-block: var(--primer-actionListContent-paddingBlock);
364
- padding-inline: var(--primer-control-medium-paddingInline-condensed, 8px);
363
+ padding-block: var(--actionListContent-paddingBlock);
364
+ padding-inline: var(--control-medium-paddingInline-condensed, 8px);
365
365
  color: var(--color-fg-default);
366
366
  text-align: left;
367
367
  user-select: none;
368
368
  background-color: transparent;
369
369
  border: none;
370
- border-radius: var(--primer-borderRadius-medium, 6px);
370
+ border-radius: var(--borderRadius-medium, 6px);
371
371
  transition: background 33.333ms linear;
372
372
  touch-action: manipulation;
373
373
  -webkit-tap-highlight-color: transparent;
@@ -378,7 +378,7 @@
378
378
 
379
379
  /* column-gap persists with empty grid-areas, margin applies only when children exist */
380
380
  & > :not(:last-child) {
381
- margin-right: var(--primer-control-medium-gap, 8px);
381
+ margin-right: var(--control-medium-gap, 8px);
382
382
  }
383
383
 
384
384
  /* state */
@@ -501,16 +501,16 @@
501
501
  /* sizes */
502
502
 
503
503
  &.ActionListContent--sizeLarge {
504
- --primer-actionListContent-paddingBlock: var(--primer-control-large-paddingBlock, calc((2.5rem - 1.25rem) / 2));
504
+ --actionListContent-paddingBlock: var(--control-large-paddingBlock, calc((2.5rem - 1.25rem) / 2));
505
505
  }
506
506
 
507
507
  &.ActionListContent--sizeXLarge {
508
- --primer-actionListContent-paddingBlock: var(--primer-control-xlarge-paddingBlock, calc((3rem - 1.25rem) / 2));
508
+ --actionListContent-paddingBlock: var(--control-xlarge-paddingBlock, calc((3rem - 1.25rem) / 2));
509
509
  }
510
510
 
511
511
  /* On pointer:coarse (mobile), all list items are large */
512
512
  @media (pointer: coarse) {
513
- --primer-actionListContent-paddingBlock: var(--primer-control-large-paddingBlock, calc((2.5rem - 1.25rem) / 2));
513
+ --actionListContent-paddingBlock: var(--control-large-paddingBlock, calc((2.5rem - 1.25rem) / 2));
514
514
  }
515
515
 
516
516
  &.ActionListContent--blockDescription {
@@ -562,9 +562,9 @@
562
562
 
563
563
  /* description */
564
564
  .ActionListItem-description {
565
- font-size: var(--primer-text-body-size-small, 12px);
565
+ font-size: var(--text-body-size-small, 12px);
566
566
  font-weight: var(--base-text-weight-normal, 400);
567
- line-height: var(--primer-text-body-lineHeight-small, calc(20 / 12));
567
+ line-height: var(--text-body-lineHeight-small, calc(20 / 12));
568
568
  color: var(--color-fg-muted);
569
569
  }
570
570
 
@@ -573,7 +573,7 @@
573
573
  .ActionListItem-visual,
574
574
  .ActionListItem-action {
575
575
  display: flex;
576
- min-height: var(--primer-control-medium-lineBoxHeight, 20px);
576
+ min-height: var(--control-medium-lineBoxHeight, 20px);
577
577
  color: var(--color-fg-muted);
578
578
  pointer-events: none;
579
579
  fill: var(--color-fg-muted);
@@ -583,9 +583,9 @@
583
583
  /* text */
584
584
  .ActionListItem-label {
585
585
  position: relative;
586
- font-size: var(--primer-text-body-size-medium, 14px);
586
+ font-size: var(--text-body-size-medium, 14px);
587
587
  font-weight: var(--base-text-weight-normal, 400);
588
- line-height: var(--primer-text-body-lineHeight-medium, calc(20 / 14));
588
+ line-height: var(--text-body-lineHeight-medium, calc(20 / 14));
589
589
  color: var(--color-fg-default);
590
590
  grid-area: label;
591
591
  }
@@ -600,8 +600,8 @@
600
600
  target ActionListItem--subItem for padding-left to maintain :active :after state */
601
601
 
602
602
  .ActionListItem--subItem > .ActionListContent > .ActionListItem-label {
603
- font-size: var(--primer-text-body-size-small, 12px);
604
- line-height: var(--primer-text-body-lineHeight-small, calc(20 / 12));
603
+ font-size: var(--text-body-size-small, 12px);
604
+ line-height: var(--text-body-lineHeight-small, calc(20 / 12));
605
605
  }
606
606
 
607
607
  /* trailing action icon button */
@@ -638,10 +638,10 @@
638
638
  /* with children */
639
639
  &:not(:empty) {
640
640
  display: flex;
641
- padding-inline: var(--primer-actionListContent-paddingBlock);
641
+ padding-inline: var(--actionListContent-paddingBlock);
642
642
  padding-block: var(--base-size-8, 8px);
643
- font-size: var(--primer-text-body-size-small, 12px);
644
- line-height: var(--primer-text-body-lineHeight-small, calc(20 / 12));
643
+ font-size: var(--text-body-size-small, 12px);
644
+ line-height: var(--text-body-lineHeight-small, calc(20 / 12));
645
645
  font-weight: var(--base-text-weight-semibold, 600);
646
646
  color: var(--color-fg-muted);
647
647
  flex-direction: column;
@@ -650,9 +650,9 @@
650
650
  /* no children */
651
651
  &:empty {
652
652
  display: block;
653
- height: var(--primer-borderWidth-thin, 1px);
653
+ height: var(--borderWidth-thin, 1px);
654
654
  padding: 0;
655
- margin-block-start: calc(var(--base-size-8, 8px) - var(--primer-borderWidth-thin, 1px));
655
+ margin-block-start: calc(var(--base-size-8, 8px) - var(--borderWidth-thin, 1px));
656
656
  margin-block-end: var(--base-size-8, 8px);
657
657
  margin-inline: calc(-1 * var(--base-size-8, 8px));
658
658
  list-style: none;
@@ -661,19 +661,19 @@
661
661
  }
662
662
 
663
663
  & .ActionList-sectionDivider-title {
664
- font-size: var(--primer-text-body-size-small, 12px);
664
+ font-size: var(--text-body-size-small, 12px);
665
665
  font-weight: var(--base-text-weight-semibold, 600);
666
666
  color: var(--color-fg-muted);
667
667
  }
668
668
  }
669
669
 
670
670
  .ActionList-sectionDivider--filled {
671
- margin-block-start: calc(var(--base-size-8, 8px) - var(--primer-borderWidth-thin, 1px));
671
+ margin-block-start: calc(var(--base-size-8, 8px) - var(--borderWidth-thin, 1px));
672
672
  margin-block-end: var(--base-size-8, 8px);
673
673
  margin-inline: calc(-1 * var(--base-size-8, 8px));
674
674
  background: var(--color-canvas-subtle);
675
- border-top: solid var(--primer-borderWidth-thin, 1px) var(--color-action-list-item-inline-divider);
676
- border-bottom: solid var(--primer-borderWidth-thin, 1px) var(--color-action-list-item-inline-divider);
675
+ border-top: solid var(--borderWidth-thin, 1px) var(--color-action-list-item-inline-divider);
676
+ border-bottom: solid var(--borderWidth-thin, 1px) var(--color-action-list-item-inline-divider);
677
677
 
678
678
  /* if no children, treat as divider */
679
679
  &:empty {
@@ -82,6 +82,7 @@ module Primer
82
82
  # @param scheme [Symbol] <%= one_of(Primer::Alpha::ActionList::SCHEME_OPTIONS) %> `inset` children are offset (vertically and horizontally) from list edges. `full` (default) children are flush (vertically and horizontally) with list edges.
83
83
  # @param show_dividers [Boolean] Display a divider above each item in the list when it does not follow a header or divider.
84
84
  # @param select_variant [Symbol] How items may be selected in the list. <%= one_of(Primer::Alpha::ActionList::SELECT_VARIANT_OPTIONS) %>
85
+ # @param form_arguments [Hash] Allows an `ActionList` to act as a select list in multi- and single-select modes. Pass the `builder:` and `name:` options to this hash. `builder:` should be an instance of `ActionView::Helpers::FormBuilder`, which are created by the standard Rails `#form_with` and `#form_for` helpers. The `name:` option is the desired name of the field that will be included in the params sent to the server on form submission. *NOTE*: Consider using an <%= link_to_component(Primer::Alpha::ActionMenu) %> instead of using this feature directly.
85
86
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
86
87
  def initialize(
87
88
  id: self.class.generate_id,
@@ -90,6 +91,7 @@ module Primer
90
91
  scheme: DEFAULT_SCHEME,
91
92
  show_dividers: false,
92
93
  select_variant: DEFAULT_SELECT_VARIANT,
94
+ form_arguments: {},
93
95
  **system_arguments
94
96
  )
95
97
  @system_arguments = system_arguments
@@ -107,10 +109,17 @@ module Primer
107
109
  "ActionListWrap--divided" => @show_dividers
108
110
  )
109
111
 
110
- @role = role || allows_selection? ? MENU_ROLE : DEFAULT_ROLE
112
+ @role = role || (allows_selection? ? MENU_ROLE : DEFAULT_ROLE)
111
113
  @system_arguments[:role] = @role
112
114
 
113
115
  @list_wrapper_arguments = {}
116
+
117
+ @form_builder = form_arguments[:builder]
118
+ @input_name = form_arguments[:name]
119
+
120
+ return unless required_form_arguments_given? && !allows_selection?
121
+
122
+ raise ArgumentError, "lists/menus that act as form inputs must also allow item selection (please pass the `select_variant:` option)"
114
123
  end
115
124
 
116
125
  # @private
@@ -158,6 +167,14 @@ module Primer
158
167
  @system_arguments[:role] == :menu
159
168
  end
160
169
 
170
+ def required_form_arguments_given?
171
+ @form_builder && @input_name
172
+ end
173
+
174
+ def acts_as_form_input?
175
+ required_form_arguments_given? && allows_selection?
176
+ end
177
+
161
178
  # @private
162
179
  def will_add_item(_item); end
163
180
  end
@@ -1,5 +1,10 @@
1
1
  import '@github/include-fragment-element';
2
- declare type SelectVariant = 'single' | 'multiple' | null;
2
+ declare type SelectVariant = 'none' | 'single' | 'multiple' | null;
3
+ declare type SelectedItem = {
4
+ label: string | null | undefined;
5
+ value: string | null | undefined;
6
+ element: Element;
7
+ };
3
8
  export declare class ActionMenuElement extends HTMLElement {
4
9
  #private;
5
10
  get selectVariant(): SelectVariant;
@@ -10,6 +15,8 @@ export declare class ActionMenuElement extends HTMLElement {
10
15
  set dynamicLabel(value: boolean);
11
16
  get popoverElement(): HTMLElement | null;
12
17
  get invokerElement(): HTMLElement | null;
18
+ get invokerLabel(): HTMLElement | null;
19
+ get selectedItems(): SelectedItem[];
13
20
  connectedCallback(): void;
14
21
  disconnectedCallback(): void;
15
22
  handleEvent(event: Event): void;
@@ -9,7 +9,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- var _ActionMenuElement_instances, _ActionMenuElement_abortController, _ActionMenuElement_originalLabel, _ActionMenuElement_setDynamicLabel;
12
+ var _ActionMenuElement_instances, _ActionMenuElement_abortController, _ActionMenuElement_originalLabel, _ActionMenuElement_inputName, _ActionMenuElement_setDynamicLabel, _ActionMenuElement_updateInput, _ActionMenuElement_isEnterKeydown, _ActionMenuElement_firstItem_get;
13
13
  import '@github/include-fragment-element';
14
14
  const popoverSelector = (() => {
15
15
  try {
@@ -20,13 +20,14 @@ const popoverSelector = (() => {
20
20
  return '.\\:open';
21
21
  }
22
22
  })();
23
- const menuItemSelectors = ['[role="menuitem"]', '[role="menuitemcheckbox"]', '[role="menuitemradio"]', '[role="none"]'];
23
+ const menuItemSelectors = ['[role="menuitem"]', '[role="menuitemcheckbox"]', '[role="menuitemradio"]'];
24
24
  export class ActionMenuElement extends HTMLElement {
25
25
  constructor() {
26
26
  super(...arguments);
27
27
  _ActionMenuElement_instances.add(this);
28
28
  _ActionMenuElement_abortController.set(this, void 0);
29
29
  _ActionMenuElement_originalLabel.set(this, '');
30
+ _ActionMenuElement_inputName.set(this, '');
30
31
  }
31
32
  get selectVariant() {
32
33
  return this.getAttribute('data-select-variant');
@@ -68,6 +69,24 @@ export class ActionMenuElement extends HTMLElement {
68
69
  }
69
70
  return null;
70
71
  }
72
+ get invokerLabel() {
73
+ if (!this.invokerElement)
74
+ return null;
75
+ return this.invokerElement.querySelector('.Button-label');
76
+ }
77
+ get selectedItems() {
78
+ const selectedItems = this.querySelectorAll('[aria-checked=true]');
79
+ const results = [];
80
+ for (const selectedItem of selectedItems) {
81
+ const labelEl = selectedItem.querySelector('.ActionListItem-label');
82
+ results.push({
83
+ label: labelEl === null || labelEl === void 0 ? void 0 : labelEl.textContent,
84
+ value: selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.getAttribute('data-value'),
85
+ element: selectedItem
86
+ });
87
+ }
88
+ return results;
89
+ }
71
90
  connectedCallback() {
72
91
  const { signal } = (__classPrivateFieldSet(this, _ActionMenuElement_abortController, new AbortController(), "f"));
73
92
  this.addEventListener('keydown', this, { signal });
@@ -75,23 +94,28 @@ export class ActionMenuElement extends HTMLElement {
75
94
  this.addEventListener('mouseover', this, { signal });
76
95
  this.addEventListener('focusout', this, { signal });
77
96
  __classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_setDynamicLabel).call(this);
97
+ __classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_updateInput).call(this);
78
98
  }
79
99
  disconnectedCallback() {
80
100
  __classPrivateFieldGet(this, _ActionMenuElement_abortController, "f").abort();
81
101
  }
82
102
  handleEvent(event) {
83
103
  var _a, _b, _c, _d;
84
- if (!((_a = this.popoverElement) === null || _a === void 0 ? void 0 : _a.matches(popoverSelector)))
104
+ if (event.target === this.invokerElement && __classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_isEnterKeydown).call(this, event)) {
105
+ if (__classPrivateFieldGet(this, _ActionMenuElement_instances, "a", _ActionMenuElement_firstItem_get)) {
106
+ event.preventDefault();
107
+ (_a = this.popoverElement) === null || _a === void 0 ? void 0 : _a.showPopover();
108
+ __classPrivateFieldGet(this, _ActionMenuElement_instances, "a", _ActionMenuElement_firstItem_get).focus();
109
+ return;
110
+ }
111
+ }
112
+ if (!((_b = this.popoverElement) === null || _b === void 0 ? void 0 : _b.matches(popoverSelector)))
85
113
  return;
86
114
  if (event.type === 'focusout' && !this.contains(event.relatedTarget)) {
87
- (_b = this.popoverElement) === null || _b === void 0 ? void 0 : _b.hidePopover();
115
+ (_c = this.popoverElement) === null || _c === void 0 ? void 0 : _c.hidePopover();
88
116
  }
89
- else if ((event instanceof KeyboardEvent &&
90
- event.type === 'keydown' &&
91
- !(event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) &&
92
- event.key === 'Enter') ||
93
- (event instanceof MouseEvent && event.type === 'click')) {
94
- const item = (_c = event.target.closest(menuItemSelectors.join(','))) === null || _c === void 0 ? void 0 : _c.closest('li');
117
+ else if (__classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_isEnterKeydown).call(this, event) || (event instanceof MouseEvent && event.type === 'click')) {
118
+ const item = (_d = event.target.closest(menuItemSelectors.join(','))) === null || _d === void 0 ? void 0 : _d.closest('li');
95
119
  if (!item)
96
120
  return;
97
121
  const ariaChecked = item.getAttribute('aria-checked');
@@ -107,31 +131,85 @@ export class ActionMenuElement extends HTMLElement {
107
131
  }
108
132
  __classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_setDynamicLabel).call(this);
109
133
  }
110
- event.preventDefault();
111
- (_d = this.popoverElement) === null || _d === void 0 ? void 0 : _d.hidePopover();
134
+ __classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_updateInput).call(this);
135
+ if (event instanceof KeyboardEvent && event.target instanceof HTMLButtonElement) {
136
+ // prevent buttons from being clicked twice
137
+ event.preventDefault();
138
+ }
139
+ // Hide popover after current event loop to prevent changes in focus from
140
+ // altering the target of the event. Not doing this specifically affects
141
+ // <a> tags. It causes the event to be sent to the currently focused element
142
+ // instead of the anchor, which effectively prevents navigation, i.e. it
143
+ // appears as if hitting enter does nothing. Curiously, clicking instead
144
+ // works fine.
145
+ if (this.selectVariant !== 'multiple') {
146
+ setTimeout(() => { var _a; return (_a = this.popoverElement) === null || _a === void 0 ? void 0 : _a.hidePopover(); });
147
+ }
112
148
  }
113
149
  }
114
150
  }
115
- _ActionMenuElement_abortController = new WeakMap(), _ActionMenuElement_originalLabel = new WeakMap(), _ActionMenuElement_instances = new WeakSet(), _ActionMenuElement_setDynamicLabel = function _ActionMenuElement_setDynamicLabel() {
151
+ _ActionMenuElement_abortController = new WeakMap(), _ActionMenuElement_originalLabel = new WeakMap(), _ActionMenuElement_inputName = new WeakMap(), _ActionMenuElement_instances = new WeakSet(), _ActionMenuElement_setDynamicLabel = function _ActionMenuElement_setDynamicLabel() {
116
152
  if (!this.dynamicLabel)
117
153
  return;
118
- const invoker = this.invokerElement;
119
- if (!invoker)
154
+ const invokerLabel = this.invokerLabel;
155
+ if (!invokerLabel)
120
156
  return;
121
- const selector = menuItemSelectors.map(s => `${s}[aria-checked=true]`).join(',');
122
- const item = this.querySelector(selector);
123
- if (item && this.dynamicLabel) {
124
- __classPrivateFieldSet(this, _ActionMenuElement_originalLabel, __classPrivateFieldGet(this, _ActionMenuElement_originalLabel, "f") || (invoker.textContent || ''), "f");
157
+ __classPrivateFieldSet(this, _ActionMenuElement_originalLabel, __classPrivateFieldGet(this, _ActionMenuElement_originalLabel, "f") || (invokerLabel.textContent || ''), "f");
158
+ const itemLabel = this.querySelector('[aria-checked=true] .ActionListItem-label');
159
+ if (itemLabel && this.dynamicLabel) {
125
160
  const prefixSpan = document.createElement('span');
126
161
  prefixSpan.classList.add('color-fg-muted');
127
162
  const contentSpan = document.createElement('span');
128
163
  prefixSpan.textContent = this.dynamicLabelPrefix;
129
- contentSpan.textContent = item.textContent || '';
130
- invoker.replaceChildren(prefixSpan, contentSpan);
164
+ contentSpan.textContent = itemLabel.textContent || '';
165
+ invokerLabel.replaceChildren(prefixSpan, contentSpan);
131
166
  }
132
167
  else {
133
- invoker.textContent = __classPrivateFieldGet(this, _ActionMenuElement_originalLabel, "f");
168
+ invokerLabel.textContent = __classPrivateFieldGet(this, _ActionMenuElement_originalLabel, "f");
169
+ }
170
+ }, _ActionMenuElement_updateInput = function _ActionMenuElement_updateInput() {
171
+ if (this.selectVariant === 'single') {
172
+ const input = this.querySelector(`[data-list-inputs=true] input`);
173
+ if (!input)
174
+ return;
175
+ const selectedItem = this.selectedItems[0];
176
+ if (selectedItem) {
177
+ input.value = (selectedItem.value || selectedItem.label || '').trim();
178
+ input.removeAttribute('disabled');
179
+ }
180
+ else {
181
+ input.setAttribute('disabled', 'disabled');
182
+ }
183
+ }
184
+ else if (this.selectVariant !== 'none') {
185
+ // multiple select variant
186
+ const inputList = this.querySelector('[data-list-inputs=true]');
187
+ if (!inputList)
188
+ return;
189
+ const inputs = inputList.querySelectorAll('input');
190
+ if (inputs.length > 0) {
191
+ __classPrivateFieldSet(this, _ActionMenuElement_inputName, __classPrivateFieldGet(this, _ActionMenuElement_inputName, "f") || inputs[0].name, "f");
192
+ }
193
+ for (const selectedItem of this.selectedItems) {
194
+ const newInput = document.createElement('input');
195
+ newInput.setAttribute('data-list-input', 'true');
196
+ newInput.type = 'hidden';
197
+ newInput.autocomplete = 'off';
198
+ newInput.name = __classPrivateFieldGet(this, _ActionMenuElement_inputName, "f");
199
+ newInput.value = (selectedItem.value || selectedItem.label || '').trim();
200
+ inputList.append(newInput);
201
+ }
202
+ for (const input of inputs) {
203
+ input.remove();
204
+ }
134
205
  }
206
+ }, _ActionMenuElement_isEnterKeydown = function _ActionMenuElement_isEnterKeydown(event) {
207
+ return (event instanceof KeyboardEvent &&
208
+ event.type === 'keydown' &&
209
+ !(event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) &&
210
+ event.key === 'Enter');
211
+ }, _ActionMenuElement_firstItem_get = function _ActionMenuElement_firstItem_get() {
212
+ return this.querySelector(menuItemSelectors.join(','));
135
213
  };
136
214
  if (!window.customElements.get('action-menu')) {
137
215
  window.ActionMenuElement = ActionMenuElement;
@@ -9,13 +9,19 @@ const popoverSelector = (() => {
9
9
  }
10
10
  })()
11
11
 
12
- type SelectVariant = 'single' | 'multiple' | null
12
+ type SelectVariant = 'none' | 'single' | 'multiple' | null
13
+ type SelectedItem = {
14
+ label: string | null | undefined
15
+ value: string | null | undefined
16
+ element: Element
17
+ }
13
18
 
14
- const menuItemSelectors = ['[role="menuitem"]', '[role="menuitemcheckbox"]', '[role="menuitemradio"]', '[role="none"]']
19
+ const menuItemSelectors = ['[role="menuitem"]', '[role="menuitemcheckbox"]', '[role="menuitemradio"]']
15
20
 
16
21
  export class ActionMenuElement extends HTMLElement {
17
22
  #abortController: AbortController
18
23
  #originalLabel = ''
24
+ #inputName = ''
19
25
 
20
26
  get selectVariant(): SelectVariant {
21
27
  return this.getAttribute('data-select-variant') as SelectVariant
@@ -60,6 +66,28 @@ export class ActionMenuElement extends HTMLElement {
60
66
  return null
61
67
  }
62
68
 
69
+ get invokerLabel(): HTMLElement | null {
70
+ if (!this.invokerElement) return null
71
+ return this.invokerElement.querySelector('.Button-label')
72
+ }
73
+
74
+ get selectedItems(): SelectedItem[] {
75
+ const selectedItems = this.querySelectorAll('[aria-checked=true]')
76
+ const results: SelectedItem[] = []
77
+
78
+ for (const selectedItem of selectedItems) {
79
+ const labelEl = selectedItem.querySelector('.ActionListItem-label')
80
+
81
+ results.push({
82
+ label: labelEl?.textContent,
83
+ value: selectedItem?.getAttribute('data-value'),
84
+ element: selectedItem
85
+ })
86
+ }
87
+
88
+ return results
89
+ }
90
+
63
91
  connectedCallback() {
64
92
  const {signal} = (this.#abortController = new AbortController())
65
93
  this.addEventListener('keydown', this, {signal})
@@ -67,6 +95,7 @@ export class ActionMenuElement extends HTMLElement {
67
95
  this.addEventListener('mouseover', this, {signal})
68
96
  this.addEventListener('focusout', this, {signal})
69
97
  this.#setDynamicLabel()
98
+ this.#updateInput()
70
99
  }
71
100
 
72
101
  disconnectedCallback() {
@@ -74,17 +103,20 @@ export class ActionMenuElement extends HTMLElement {
74
103
  }
75
104
 
76
105
  handleEvent(event: Event) {
106
+ if (event.target === this.invokerElement && this.#isEnterKeydown(event)) {
107
+ if (this.#firstItem) {
108
+ event.preventDefault()
109
+ this.popoverElement?.showPopover()
110
+ this.#firstItem.focus()
111
+ return
112
+ }
113
+ }
114
+
77
115
  if (!this.popoverElement?.matches(popoverSelector)) return
78
116
 
79
117
  if (event.type === 'focusout' && !this.contains((event as FocusEvent).relatedTarget as Node)) {
80
118
  this.popoverElement?.hidePopover()
81
- } else if (
82
- (event instanceof KeyboardEvent &&
83
- event.type === 'keydown' &&
84
- !(event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) &&
85
- event.key === 'Enter') ||
86
- (event instanceof MouseEvent && event.type === 'click')
87
- ) {
119
+ } else if (this.#isEnterKeydown(event) || (event instanceof MouseEvent && event.type === 'click')) {
88
120
  const item = (event.target as Element).closest(menuItemSelectors.join(','))?.closest('li')
89
121
  if (!item) return
90
122
  const ariaChecked = item.getAttribute('aria-checked')
@@ -100,29 +132,96 @@ export class ActionMenuElement extends HTMLElement {
100
132
  }
101
133
  this.#setDynamicLabel()
102
134
  }
103
- event.preventDefault()
104
- this.popoverElement?.hidePopover()
135
+
136
+ this.#updateInput()
137
+
138
+ if (event instanceof KeyboardEvent && event.target instanceof HTMLButtonElement) {
139
+ // prevent buttons from being clicked twice
140
+ event.preventDefault()
141
+ }
142
+ // Hide popover after current event loop to prevent changes in focus from
143
+ // altering the target of the event. Not doing this specifically affects
144
+ // <a> tags. It causes the event to be sent to the currently focused element
145
+ // instead of the anchor, which effectively prevents navigation, i.e. it
146
+ // appears as if hitting enter does nothing. Curiously, clicking instead
147
+ // works fine.
148
+ if (this.selectVariant !== 'multiple') {
149
+ setTimeout(() => this.popoverElement?.hidePopover())
150
+ }
105
151
  }
106
152
  }
107
153
 
108
154
  #setDynamicLabel() {
109
155
  if (!this.dynamicLabel) return
110
- const invoker = this.invokerElement
111
- if (!invoker) return
112
- const selector = menuItemSelectors.map(s => `${s}[aria-checked=true]`).join(',')
113
- const item = this.querySelector(selector)
114
- if (item && this.dynamicLabel) {
115
- this.#originalLabel ||= invoker.textContent || ''
156
+ const invokerLabel = this.invokerLabel
157
+ if (!invokerLabel) return
158
+ this.#originalLabel ||= invokerLabel.textContent || ''
159
+ const itemLabel = this.querySelector('[aria-checked=true] .ActionListItem-label')
160
+ if (itemLabel && this.dynamicLabel) {
116
161
  const prefixSpan = document.createElement('span')
117
162
  prefixSpan.classList.add('color-fg-muted')
118
163
  const contentSpan = document.createElement('span')
119
164
  prefixSpan.textContent = this.dynamicLabelPrefix
120
- contentSpan.textContent = item.textContent || ''
121
- invoker.replaceChildren(prefixSpan, contentSpan)
165
+ contentSpan.textContent = itemLabel.textContent || ''
166
+ invokerLabel.replaceChildren(prefixSpan, contentSpan)
122
167
  } else {
123
- invoker.textContent = this.#originalLabel
168
+ invokerLabel.textContent = this.#originalLabel
124
169
  }
125
170
  }
171
+
172
+ #updateInput() {
173
+ if (this.selectVariant === 'single') {
174
+ const input = this.querySelector(`[data-list-inputs=true] input`) as HTMLInputElement | null
175
+ if (!input) return
176
+
177
+ const selectedItem = this.selectedItems[0]
178
+
179
+ if (selectedItem) {
180
+ input.value = (selectedItem.value || selectedItem.label || '').trim()
181
+ input.removeAttribute('disabled')
182
+ } else {
183
+ input.setAttribute('disabled', 'disabled')
184
+ }
185
+ } else if (this.selectVariant !== 'none') {
186
+ // multiple select variant
187
+ const inputList = this.querySelector('[data-list-inputs=true]')
188
+ if (!inputList) return
189
+
190
+ const inputs = inputList.querySelectorAll('input')
191
+
192
+ if (inputs.length > 0) {
193
+ this.#inputName ||= (inputs[0] as HTMLInputElement).name
194
+ }
195
+
196
+ for (const selectedItem of this.selectedItems) {
197
+ const newInput = document.createElement('input')
198
+ newInput.setAttribute('data-list-input', 'true')
199
+ newInput.type = 'hidden'
200
+ newInput.autocomplete = 'off'
201
+ newInput.name = this.#inputName
202
+ newInput.value = (selectedItem.value || selectedItem.label || '').trim()
203
+
204
+ inputList.append(newInput)
205
+ }
206
+
207
+ for (const input of inputs) {
208
+ input.remove()
209
+ }
210
+ }
211
+ }
212
+
213
+ #isEnterKeydown(event: Event): boolean {
214
+ return (
215
+ event instanceof KeyboardEvent &&
216
+ event.type === 'keydown' &&
217
+ !(event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) &&
218
+ event.key === 'Enter'
219
+ )
220
+ }
221
+
222
+ get #firstItem(): HTMLElement | null {
223
+ return this.querySelector(menuItemSelectors.join(','))
224
+ }
126
225
  }
127
226
 
128
227
  if (!window.customElements.get('action-menu')) {