primer_view_components 0.38.0 → 0.39.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.
@@ -1,4 +1,9 @@
1
1
  <%= render Primer::BaseComponent.new(**@system_arguments) do %>
2
+ <% if required_form_arguments_given? %>
3
+ <span data-select-panel-inputs="true">
4
+ <%= @form_builder.hidden_field(@input_name, multiple: multi_select?, skip_default_ids: true, value: @value) %>
5
+ </span>
6
+ <% end %>
2
7
  <dialog-helper>
3
8
  <%= show_button %>
4
9
  <%= render(@dialog) do %>
@@ -152,27 +152,6 @@ module Primer
152
152
  # end
153
153
  # end
154
154
  # ```
155
- #
156
- # If items are provided dynamically, things become a bit more complicated. The `form_for` or `form_with` method call
157
- # happens in the view that renders the `SelectPanel`, which means the form builder object but isn't available in the
158
- # view that renders the list items. In such a case, it can be useful to create an instance of the form builder maually:
159
- #
160
- # ```erb
161
- # <% builder = ActionView::Helpers::FormBuilder.new(
162
- # "address", # the name of the model, used to wrap input names, eg 'address[country]'
163
- # nil, # object (eg. the Address instance, which we can omit)
164
- # self, # template
165
- # {} # options
166
- # ) %>
167
- # <%= render(Primer::Alpha::SelectPanel::ItemList.new(
168
- # form_arguments: { builder: builder, name: "country" }
169
- # )) do |list| %>
170
- # <% countries.each do |country| %>
171
- # <% menu.with_item(label: country.name, content_arguments: { data: { value: country.code } }) %>
172
- # <% end %>
173
- # <% end %>
174
- # ```
175
- #
176
155
  # ### JavaScript API
177
156
  #
178
157
  # `SelectPanel`s render a `<select-panel>` custom element that exposes behavior to the client.
@@ -375,7 +354,9 @@ module Primer
375
354
  # @param dynamic_aria_label_prefix [String] If provided, the prefix is prepended to the dynamic label and set as the value of the `aria-label` attribute on the show button.
376
355
  # @param body_id [String] The unique ID of the panel body. If not provided, the body ID will be set to the panel ID with a "-body" suffix.
377
356
  # @param list_arguments [Hash] Arguments to pass to the underlying <%= link_to_component(Primer::Alpha::ActionList) %> component. Only has an effect for the local fetch strategy.
378
- # @param form_arguments [Hash] Form arguments to pass to the underlying <%= link_to_component(Primer::Alpha::ActionList) %> component. Only has an effect for the local fetch strategy.
357
+ # @param form_arguments [Hash] Form arguments
358
+
359
+ # @param use_experimental_non_local_form [Boolean] A feature flag used to slowly roll out moving the input field (generated from form arguments) to the top of the SelectPanel HTML thus allowing remote fetching to have default form values.
379
360
  # @param show_filter [Boolean] Whether or not to show the filter input.
380
361
  # @param open_on_load [Boolean] Open the panel when the page loads.
381
362
  # @param anchor_align [Symbol] The anchor alignment of the Overlay. <%= one_of(Primer::Alpha::Overlay::ANCHOR_ALIGN_OPTIONS) %>
@@ -397,6 +378,7 @@ module Primer
397
378
  dynamic_label_prefix: nil,
398
379
  dynamic_aria_label_prefix: nil,
399
380
  body_id: nil,
381
+ use_experimental_non_local_form: false,
400
382
  list_arguments: {},
401
383
  form_arguments: {},
402
384
  show_filter: true,
@@ -429,6 +411,15 @@ module Primer
429
411
  @dynamic_aria_label_prefix = dynamic_aria_label_prefix
430
412
  @loading_label = loading_label
431
413
  @loading_description_id = nil
414
+
415
+ if use_experimental_non_local_form
416
+ @form_builder = form_arguments[:builder]
417
+ @value = form_arguments[:value]
418
+ @input_name = form_arguments[:name]
419
+ end
420
+
421
+ @list_form_arguments = use_experimental_non_local_form ? {} : form_arguments
422
+
432
423
  if loading_description.present?
433
424
  @loading_description_id = "#{@panel_id}-loading-description"
434
425
  end
@@ -471,7 +462,7 @@ module Primer
471
462
 
472
463
  @list = Primer::Alpha::SelectPanel::ItemList.new(
473
464
  **list_arguments,
474
- form_arguments: form_arguments,
465
+ form_arguments: @list_form_arguments,
475
466
  id: "#{@panel_id}-list",
476
467
  select_variant: @select_variant,
477
468
  aria: {
@@ -546,6 +537,14 @@ module Primer
546
537
  def before_render
547
538
  content
548
539
  end
540
+
541
+ def required_form_arguments_given?
542
+ @input_name && @form_builder
543
+ end
544
+
545
+ def multi_select?
546
+ select_variant == :multiple
547
+ end
549
548
  end
550
549
  end
551
- end
550
+ end
@@ -893,7 +893,8 @@ _SelectPanelElement_setDynamicLabel = function _SelectPanelElement_setDynamicLab
893
893
  };
894
894
  _SelectPanelElement_updateInput = function _SelectPanelElement_updateInput() {
895
895
  if (this.selectVariant === 'single') {
896
- const input = this.querySelector(`[data-list-inputs=true] input`);
896
+ const input = this.querySelector(`[data-select-panel-inputs=true] input`) ??
897
+ this.querySelector(`[data-list-inputs=true] input`);
897
898
  if (!input)
898
899
  return;
899
900
  const selectedItem = this.selectedItems[0];
@@ -903,13 +904,14 @@ _SelectPanelElement_updateInput = function _SelectPanelElement_updateInput() {
903
904
  input.name = selectedItem.inputName;
904
905
  input.removeAttribute('disabled');
905
906
  }
906
- else {
907
+ else if (__classPrivateFieldGet(this, _SelectPanelElement_hasLoadedData, "f")) {
907
908
  input.setAttribute('disabled', 'disabled');
908
909
  }
909
910
  }
910
911
  else if (this.selectVariant !== 'none') {
911
912
  // multiple select variant
912
- const inputList = this.querySelector('[data-list-inputs=true]');
913
+ const isRemoteInput = !!this.querySelector('[data-select-panel-inputs=true]');
914
+ const inputList = this.querySelector('[data-select-panel-inputs=true]') ?? this.querySelector('[data-list-inputs=true]');
913
915
  if (!inputList)
914
916
  return;
915
917
  const inputs = inputList.querySelectorAll('input');
@@ -918,7 +920,7 @@ _SelectPanelElement_updateInput = function _SelectPanelElement_updateInput() {
918
920
  }
919
921
  for (const selectedItem of this.selectedItems) {
920
922
  const newInput = document.createElement('input');
921
- newInput.setAttribute('data-list-input', 'true');
923
+ newInput.setAttribute(`${isRemoteInput ? 'data-select-panel-input' : 'data-list-input'}`, 'true');
922
924
  newInput.type = 'hidden';
923
925
  newInput.autocomplete = 'off';
924
926
  newInput.name = selectedItem.inputName || __classPrivateFieldGet(this, _SelectPanelElement_inputName, "f");
@@ -959,7 +959,9 @@ export class SelectPanelElement extends HTMLElement {
959
959
 
960
960
  #updateInput() {
961
961
  if (this.selectVariant === 'single') {
962
- const input = this.querySelector(`[data-list-inputs=true] input`) as HTMLInputElement
962
+ const input =
963
+ (this.querySelector(`[data-select-panel-inputs=true] input`) as HTMLInputElement) ??
964
+ (this.querySelector(`[data-list-inputs=true] input`) as HTMLInputElement)
963
965
  if (!input) return
964
966
 
965
967
  const selectedItem = this.selectedItems[0]
@@ -968,12 +970,14 @@ export class SelectPanelElement extends HTMLElement {
968
970
  input.value = (selectedItem.value || selectedItem.label || '').trim()
969
971
  if (selectedItem.inputName) input.name = selectedItem.inputName
970
972
  input.removeAttribute('disabled')
971
- } else {
973
+ } else if (this.#hasLoadedData) {
972
974
  input.setAttribute('disabled', 'disabled')
973
975
  }
974
976
  } else if (this.selectVariant !== 'none') {
975
977
  // multiple select variant
976
- const inputList = this.querySelector('[data-list-inputs=true]')
978
+ const isRemoteInput = !!this.querySelector('[data-select-panel-inputs=true]')
979
+ const inputList =
980
+ this.querySelector('[data-select-panel-inputs=true]') ?? this.querySelector('[data-list-inputs=true]')
977
981
  if (!inputList) return
978
982
 
979
983
  const inputs = inputList.querySelectorAll('input')
@@ -984,7 +988,7 @@ export class SelectPanelElement extends HTMLElement {
984
988
 
985
989
  for (const selectedItem of this.selectedItems) {
986
990
  const newInput = document.createElement('input')
987
- newInput.setAttribute('data-list-input', 'true')
991
+ newInput.setAttribute(`${isRemoteInput ? 'data-select-panel-input' : 'data-list-input'}`, 'true')
988
992
  newInput.type = 'hidden'
989
993
  newInput.autocomplete = 'off'
990
994
  newInput.name = selectedItem.inputName || this.#inputName
@@ -5,7 +5,7 @@ module Primer
5
5
  module ViewComponents
6
6
  module VERSION
7
7
  MAJOR = 0
8
- MINOR = 38
8
+ MINOR = 39
9
9
  PATCH = 0
10
10
 
11
11
  STRING = [MAJOR, MINOR, PATCH].join(".")
@@ -0,0 +1,34 @@
1
+ <% subject_id = SecureRandom.hex %>
2
+
3
+ <%= form_with(url: generic_form_submission_path(format: route_format)) do |builder| %>
4
+ <%= render(Primer::Alpha::SelectPanel.new(
5
+ data: { interaction_subject: subject_id },
6
+ select_variant: :single,
7
+ src: select_panel_items_path(
8
+ select_variant: :single,
9
+ selected_items: selected_items,
10
+ ),
11
+ open_on_load: open_on_load,
12
+ dynamic_label: true,
13
+ dynamic_label_prefix: "Item",
14
+ use_experimental_non_local_form: true,
15
+ form_arguments: {
16
+ name: :item,
17
+ builder: builder,
18
+ value: 3,
19
+ }
20
+ )) do |panel| %>
21
+ <% panel.with_show_button { "Sci-fi equipment" } %>
22
+ <% panel.with_footer(show_divider: true) do %>
23
+ I'm a footer!
24
+ <% end %>
25
+ <% end %>
26
+
27
+ <hr>
28
+
29
+ <%= render(Primer::Beta::Button.new(type: :submit, scheme: :primary)) do %>
30
+ Submit
31
+ <% end %>
32
+ <% end %>
33
+
34
+ <%= render partial: "primer/alpha/select_panel_preview/interaction_subject_js", locals: { subject_id: subject_id } %>
@@ -259,6 +259,15 @@ module Primer
259
259
  render_with_template(locals: { open_on_load: open_on_load, route_format: route_format })
260
260
  end
261
261
 
262
+ # @label Remote fetch form
263
+ #
264
+ # @snapshot interactive
265
+ # @param open_on_load toggle
266
+ # @param selected_items text
267
+ def remote_fetch_form(open_on_load: false, selected_items: "Phaser", route_format: :html)
268
+ render_with_template(locals: { open_on_load: open_on_load, selected_items: selected_items, route_format: route_format })
269
+ end
270
+
262
271
  # @label Multi-select form
263
272
  #
264
273
  # @snapshot interactive
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: primer_view_components
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.38.0
4
+ version: 0.39.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub Open Source
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-03-13 00:00:00.000000000 Z
11
+ date: 2025-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionview
@@ -821,6 +821,7 @@ files:
821
821
  - previews/primer/alpha/select_panel_preview/playground.html.erb
822
822
  - previews/primer/alpha/select_panel_preview/remote_fetch.html.erb
823
823
  - previews/primer/alpha/select_panel_preview/remote_fetch_filter_failure.html.erb
824
+ - previews/primer/alpha/select_panel_preview/remote_fetch_form.html.erb
824
825
  - previews/primer/alpha/select_panel_preview/remote_fetch_initial_failure.html.erb
825
826
  - previews/primer/alpha/select_panel_preview/remote_fetch_no_results.html.erb
826
827
  - previews/primer/alpha/select_panel_preview/scroll_container.html.erb