openproject-primer_view_components 0.67.0 → 0.69.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/app/assets/javascripts/components/primer/alpha/action_menu/action_menu_element.d.ts +5 -0
- data/app/assets/javascripts/components/primer/alpha/action_menu/action_menu_focus_zone_stack.d.ts +17 -0
- data/app/assets/javascripts/components/primer/open_project/tree_view/tree_view.d.ts +1 -0
- data/app/assets/javascripts/components/primer/open_project/tree_view/tree_view_sub_tree_node_element.d.ts +1 -0
- data/app/assets/javascripts/primer_view_components.js +1 -1
- data/app/assets/javascripts/primer_view_components.js.map +1 -1
- data/app/components/primer/alpha/action_menu/action_menu_element.d.ts +5 -0
- data/app/components/primer/alpha/action_menu/action_menu_element.js +111 -16
- data/app/components/primer/alpha/action_menu/action_menu_element.ts +136 -23
- data/app/components/primer/alpha/action_menu/action_menu_focus_zone_stack.d.ts +17 -0
- data/app/components/primer/alpha/action_menu/action_menu_focus_zone_stack.js +62 -0
- data/app/components/primer/alpha/action_menu/action_menu_focus_zone_stack.ts +67 -0
- data/app/components/primer/alpha/action_menu/list.rb +3 -1
- data/app/components/primer/alpha/action_menu/list_wrapper.rb +31 -0
- data/app/components/primer/alpha/action_menu/menu.html.erb +24 -0
- data/app/components/primer/alpha/action_menu/menu.rb +136 -0
- data/app/components/primer/alpha/action_menu/primary_menu.rb +86 -0
- data/app/components/primer/alpha/action_menu/sub_menu.rb +74 -0
- data/app/components/primer/alpha/action_menu/sub_menu_item.html.erb +5 -0
- data/app/components/primer/alpha/action_menu/sub_menu_item.rb +54 -0
- data/app/components/primer/alpha/action_menu.html.erb +1 -26
- data/app/components/primer/alpha/action_menu.rb +44 -118
- data/app/components/primer/alpha/select_panel.rb +3 -3
- data/app/components/primer/open_project/border_box/collapsible_header.rb +0 -3
- data/app/components/primer/open_project/collapsible_section.rb +1 -7
- data/app/components/primer/open_project/page_header/title.rb +1 -1
- data/app/components/primer/open_project/tree_view/tree_view.d.ts +1 -0
- data/app/components/primer/open_project/tree_view/tree_view.js +34 -1
- data/app/components/primer/open_project/tree_view/tree_view.ts +37 -0
- data/app/components/primer/open_project/tree_view/tree_view_sub_tree_node_element.d.ts +1 -0
- data/app/components/primer/open_project/tree_view/tree_view_sub_tree_node_element.js +14 -0
- data/app/components/primer/open_project/tree_view/tree_view_sub_tree_node_element.ts +18 -0
- data/lib/primer/view_components/version.rb +1 -1
- data/previews/primer/alpha/action_menu_preview/multiple_select_form.html.erb +13 -4
- data/previews/primer/alpha/action_menu_preview/opens_dialog.html.erb +20 -11
- data/previews/primer/alpha/action_menu_preview/single_select_form_items.html.erb +13 -2
- data/previews/primer/alpha/action_menu_preview/sub_menus.html.erb +19 -0
- data/previews/primer/alpha/action_menu_preview/with_actions.html.erb +20 -11
- data/previews/primer/alpha/action_menu_preview/with_deferred_content.html.erb +24 -0
- data/previews/primer/alpha/action_menu_preview.rb +93 -29
- data/previews/primer/open_project/border_box/collapsible_header_preview/playground.html.erb +1 -1
- data/previews/primer/open_project/tree_view_preview/async_alpha.html.erb +12 -0
- data/previews/primer/open_project/tree_view_preview.rb +24 -0
- data/static/arguments.json +169 -68
- data/static/audited_at.json +4 -0
- data/static/constants.json +28 -8
- data/static/info_arch.json +794 -170
- data/static/previews.json +39 -0
- data/static/statuses.json +4 -0
- metadata +15 -2
@@ -171,143 +171,69 @@ module Primer
|
|
171
171
|
class ActionMenu < Primer::Component
|
172
172
|
status :alpha
|
173
173
|
|
174
|
-
|
174
|
+
delegate :preload, :preload?, :list, to: :@primary_menu
|
175
|
+
delegate :with_show_button, :with_item, :items, :with_divider, :with_avatar_item, :with_group, :with_sub_menu_item, to: :@primary_menu
|
175
176
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
#
|
188
|
-
#
|
189
|
-
#
|
190
|
-
#
|
191
|
-
# @param
|
192
|
-
#
|
193
|
-
#
|
194
|
-
#
|
195
|
-
#
|
196
|
-
#
|
197
|
-
# @param
|
198
|
-
#
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
dynamic_label: false,
|
207
|
-
dynamic_label_prefix: nil,
|
208
|
-
select_variant: DEFAULT_SELECT_VARIANT,
|
209
|
-
form_arguments: {},
|
210
|
-
overlay_arguments: {},
|
211
|
-
**system_arguments
|
212
|
-
)
|
213
|
-
@menu_id = menu_id
|
214
|
-
@src = src
|
215
|
-
@preload = fetch_or_fallback_boolean(preload, DEFAULT_PRELOAD)
|
216
|
-
@system_arguments = deny_tag_argument(**system_arguments)
|
217
|
-
|
218
|
-
@system_arguments[:preload] = true if @src.present? && preload?
|
177
|
+
# @!parse
|
178
|
+
# # Adds an item to the menu.
|
179
|
+
# #
|
180
|
+
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionList) %>'s `item` slot.
|
181
|
+
# def with_item(**system_arguments)
|
182
|
+
# end
|
183
|
+
#
|
184
|
+
# # Adds an avatar item to the menu.
|
185
|
+
# #
|
186
|
+
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionList) %>'s `item` slot.
|
187
|
+
# def with_avatar_item(**system_arguments)
|
188
|
+
# end
|
189
|
+
#
|
190
|
+
# # Adds a divider to the list. Dividers visually separate items.
|
191
|
+
# #
|
192
|
+
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionList::Divider) %>.
|
193
|
+
# def with_divider(**system_arguments)
|
194
|
+
# end
|
195
|
+
#
|
196
|
+
# # Adds a group to the menu. Groups are a logical set of items with a header.
|
197
|
+
# #
|
198
|
+
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionMenu::Group) %>.
|
199
|
+
# def with_group(**system_arguments)
|
200
|
+
# end
|
201
|
+
#
|
202
|
+
# # Gets the list of configured menu items, which includes regular items, avatar items, groups, and dividers.
|
203
|
+
# #
|
204
|
+
# # @return [Array<ViewComponent::Slot>]
|
205
|
+
# def items
|
206
|
+
# end
|
219
207
|
|
220
|
-
|
208
|
+
# @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionMenu::PrimaryMenu) %>.
|
209
|
+
def initialize(**system_arguments)
|
210
|
+
@primary_menu = PrimaryMenu.allocate
|
211
|
+
@system_arguments = @primary_menu.send(:initialize, **system_arguments)
|
221
212
|
|
222
213
|
@system_arguments[:tag] = :"action-menu"
|
223
|
-
@system_arguments[:
|
224
|
-
@system_arguments[:"data-dynamic-label"] = "" if dynamic_label
|
225
|
-
@system_arguments[:"data-dynamic-label-prefix"] = dynamic_label_prefix if dynamic_label_prefix.present?
|
214
|
+
@system_arguments[:preload] = true if @primary_menu.preload?
|
226
215
|
|
227
|
-
|
228
|
-
|
229
|
-
|
216
|
+
@system_arguments[:data] = merge_data(
|
217
|
+
@system_arguments, {
|
218
|
+
data: { "select-variant": @primary_menu.select_variant }
|
230
219
|
}
|
231
220
|
)
|
232
221
|
|
233
|
-
@
|
234
|
-
id: "#{@menu_id}-overlay",
|
235
|
-
title: "Menu",
|
236
|
-
visually_hide_title: true,
|
237
|
-
anchor_align: anchor_align,
|
238
|
-
anchor_side: anchor_side,
|
239
|
-
size: size,
|
240
|
-
**overlay_arguments
|
241
|
-
)
|
222
|
+
@system_arguments[:"data-dynamic-label"] = "" if @primary_menu.dynamic_label
|
242
223
|
|
243
|
-
@
|
244
|
-
|
245
|
-
select_variant: select_variant,
|
246
|
-
form_arguments: form_arguments
|
247
|
-
)
|
248
|
-
end
|
249
|
-
|
250
|
-
# @!parse
|
251
|
-
# # Button to activate the menu.
|
252
|
-
# #
|
253
|
-
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::Overlay) %>'s `show_button` slot.
|
254
|
-
# renders_one(:show_button)
|
255
|
-
|
256
|
-
# Button to activate the menu.
|
257
|
-
#
|
258
|
-
# @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::Overlay) %>'s `show_button` slot.
|
259
|
-
def with_show_button(**system_arguments, &block)
|
260
|
-
@overlay.with_show_button(**system_arguments, id: "#{@menu_id}-button", controls: "#{@menu_id}-list") do |button|
|
261
|
-
evaluate_block(button, &block)
|
224
|
+
if @primary_menu.dynamic_label_prefix.present?
|
225
|
+
@system_arguments[:"data-dynamic-label-prefix"] = @primary_menu.dynamic_label_prefix
|
262
226
|
end
|
263
227
|
end
|
264
228
|
|
265
|
-
# @!parse
|
266
|
-
# # Adds a new item to the list.
|
267
|
-
# #
|
268
|
-
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionList::Item) %>.
|
269
|
-
# renders_many(:items)
|
270
|
-
|
271
|
-
# Adds a new item to the list.
|
272
|
-
#
|
273
|
-
# @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionList::Item) %>.
|
274
|
-
def with_item(**system_arguments, &block)
|
275
|
-
@list.with_item(**system_arguments, &block)
|
276
|
-
end
|
277
|
-
|
278
|
-
# Adds a divider to the list.
|
279
|
-
#
|
280
|
-
# @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionList) %>'s `divider` slot.
|
281
|
-
def with_divider(**system_arguments, &block)
|
282
|
-
@list.with_divider(**system_arguments, &block)
|
283
|
-
end
|
284
|
-
|
285
|
-
# Adds an avatar item to the list. Avatar items are a convenient way to accessibly add an item with a leading avatar image.
|
286
|
-
#
|
287
|
-
# @param src [String] The source url of the avatar image.
|
288
|
-
# @param username [String] The username associated with the avatar.
|
289
|
-
# @param full_name [String] Optional. The user's full name.
|
290
|
-
# @param full_name_scheme [Symbol] Optional. How to display the user's full name.
|
291
|
-
# @param avatar_arguments [Hash] Optional. The arguments accepted by <%= link_to_component(Primer::Beta::Avatar) %>.
|
292
|
-
# @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionList::Item) %>.
|
293
|
-
def with_avatar_item(**system_arguments, &block)
|
294
|
-
@list.with_avatar_item(**system_arguments, &block)
|
295
|
-
end
|
296
|
-
|
297
|
-
def with_group(**system_arguments, &block)
|
298
|
-
@list.with_group(**system_arguments, &block)
|
299
|
-
end
|
300
|
-
|
301
229
|
private
|
302
230
|
|
303
231
|
def before_render
|
304
232
|
content
|
305
|
-
|
306
|
-
raise ArgumentError, "`items` cannot be set when `src` is specified" if @src.present? && @list.items.any?
|
307
233
|
end
|
308
234
|
|
309
235
|
def render?
|
310
|
-
@
|
236
|
+
@primary_menu.items.any? || @primary_menu.src.present?
|
311
237
|
end
|
312
238
|
end
|
313
239
|
end
|
@@ -312,7 +312,7 @@ module Primer
|
|
312
312
|
# @return [String]
|
313
313
|
attr_reader :body_id
|
314
314
|
|
315
|
-
# <%= one_of(Primer::Alpha::ActionMenu::SELECT_VARIANT_OPTIONS) %>
|
315
|
+
# <%= one_of(Primer::Alpha::ActionMenu::Menu::SELECT_VARIANT_OPTIONS) %>
|
316
316
|
#
|
317
317
|
# @return [Symbol]
|
318
318
|
attr_reader :select_variant
|
@@ -461,7 +461,7 @@ module Primer
|
|
461
461
|
|
462
462
|
@list = Primer::Alpha::SelectPanel::ItemList.new(
|
463
463
|
**list_arguments,
|
464
|
-
form_arguments: @list_form_arguments,
|
464
|
+
form_arguments: @list_form_arguments,
|
465
465
|
id: "#{@panel_id}-list",
|
466
466
|
select_variant: @select_variant,
|
467
467
|
aria: {
|
@@ -546,4 +546,4 @@ module Primer
|
|
546
546
|
end
|
547
547
|
end
|
548
548
|
end
|
549
|
-
end
|
549
|
+
end
|
@@ -12,8 +12,6 @@ module Primer
|
|
12
12
|
#
|
13
13
|
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
|
14
14
|
renders_one :title, lambda { |**system_arguments, &block|
|
15
|
-
raise ArgumentError, "Title must be a string" unless block.call.is_a?(String)
|
16
|
-
|
17
15
|
system_arguments[:mr] ||= 2
|
18
16
|
|
19
17
|
Primer::Beta::Text.new(**system_arguments, &block)
|
@@ -33,7 +31,6 @@ module Primer
|
|
33
31
|
#
|
34
32
|
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
|
35
33
|
renders_one :description, lambda { |**system_arguments, &block|
|
36
|
-
raise ArgumentError, "Description must be a string" unless block.call.is_a?(String)
|
37
34
|
system_arguments[:color] ||= :subtle
|
38
35
|
system_arguments[:hidden] = @collapsed
|
39
36
|
|
@@ -15,8 +15,6 @@ module Primer
|
|
15
15
|
# @param tag [Symbol] Customize the element type of the rendered title container.
|
16
16
|
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
|
17
17
|
renders_one :title, lambda { |tag: TITLE_TAG_FALLBACK, **system_arguments, &block|
|
18
|
-
raise ArgumentError, "Title must be a string" unless block.call.is_a?(String)
|
19
|
-
|
20
18
|
system_arguments[:tag] = fetch_or_fallback(TITLE_TAG_OPTIONS, tag, TITLE_TAG_FALLBACK)
|
21
19
|
system_arguments[:font_size] ||= 3
|
22
20
|
system_arguments[:mr] ||= 2
|
@@ -28,8 +26,6 @@ module Primer
|
|
28
26
|
#
|
29
27
|
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
|
30
28
|
renders_one :caption, lambda { |**system_arguments, &block|
|
31
|
-
raise ArgumentError, "Caption must be a string" unless block.call.is_a?(String)
|
32
|
-
|
33
29
|
system_arguments[:color] ||= :subtle
|
34
30
|
system_arguments[:mr] ||= 2
|
35
31
|
system_arguments[:display] ||= [:none, :block]
|
@@ -40,9 +36,7 @@ module Primer
|
|
40
36
|
# Optional right-side content
|
41
37
|
#
|
42
38
|
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
|
43
|
-
renders_one :additional_information, lambda { |**system_arguments
|
44
|
-
raise ArgumentError, "The additional information must be a string" unless block.call.is_a?(String)
|
45
|
-
|
39
|
+
renders_one :additional_information, lambda { |**system_arguments|
|
46
40
|
Primer::BaseComponent.new(tag: :div, **system_arguments)
|
47
41
|
}
|
48
42
|
|
@@ -9,7 +9,7 @@ module Primer
|
|
9
9
|
status :open_project
|
10
10
|
|
11
11
|
HEADING_TAG_OPTIONS = [:h1, :h2, :h3, :h4, :h5, :h6].freeze
|
12
|
-
HEADING_TAG_FALLBACK = :
|
12
|
+
HEADING_TAG_FALLBACK = :h2
|
13
13
|
|
14
14
|
renders_one :editable_form, lambda { |model: false, update_path:, cancel_path:, input_name: :title, method: :put, label: I18n.t(:label_title), placeholder: I18n.t(:label_title), **system_arguments|
|
15
15
|
primer_form_with(
|
@@ -22,6 +22,7 @@ export declare class TreeViewElement extends HTMLElement {
|
|
22
22
|
getNodeCheckedValue(node: Element): TreeViewCheckedValue;
|
23
23
|
nodeHasCheckBox(node: Element): boolean;
|
24
24
|
nodeHasNativeAction(node: Element): boolean;
|
25
|
+
expandAncestorsForNode(node: HTMLElement): void;
|
25
26
|
infoFromNode(node: Element, newCheckedValue?: TreeViewCheckedValue): TreeViewNodeInfo | null;
|
26
27
|
}
|
27
28
|
declare global {
|
@@ -15,7 +15,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
15
15
|
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");
|
16
16
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
17
17
|
};
|
18
|
-
var _TreeViewElement_instances, _TreeViewElement_abortController, _TreeViewElement_eventIsActivation, _TreeViewElement_nodeForEvent, _TreeViewElement_handleNodeEvent, _TreeViewElement_eventIsCheckboxToggle, _TreeViewElement_handleCheckboxToggle, _TreeViewElement_handleNodeActivated, _TreeViewElement_handleNodeFocused, _TreeViewElement_handleNodeKeyboardEvent, _TreeViewElement_setNodeCheckedValue;
|
18
|
+
var _TreeViewElement_instances, _TreeViewElement_abortController, _TreeViewElement_autoExpandFrom, _TreeViewElement_eventIsActivation, _TreeViewElement_nodeForEvent, _TreeViewElement_handleNodeEvent, _TreeViewElement_eventIsCheckboxToggle, _TreeViewElement_handleCheckboxToggle, _TreeViewElement_handleNodeActivated, _TreeViewElement_handleNodeFocused, _TreeViewElement_handleNodeKeyboardEvent, _TreeViewElement_setNodeCheckedValue;
|
19
19
|
import { controller } from '@github/catalyst';
|
20
20
|
import { useRovingTabIndex } from './tree_view_roving_tab_index';
|
21
21
|
let TreeViewElement = class TreeViewElement extends HTMLElement {
|
@@ -30,6 +30,24 @@ let TreeViewElement = class TreeViewElement extends HTMLElement {
|
|
30
30
|
this.addEventListener('focusin', this, { signal });
|
31
31
|
this.addEventListener('keydown', this, { signal });
|
32
32
|
useRovingTabIndex(this);
|
33
|
+
// catch-all for any straggler nodes that aren't available when connectedCallback runs
|
34
|
+
new MutationObserver(mutations => {
|
35
|
+
for (const mutation of mutations) {
|
36
|
+
for (const addedNode of mutation.addedNodes) {
|
37
|
+
if (!(addedNode instanceof HTMLElement))
|
38
|
+
continue;
|
39
|
+
// eslint-disable-next-line custom-elements/no-dom-traversal-in-connectedcallback
|
40
|
+
if (addedNode.querySelector('[aria-expanded=true]')) {
|
41
|
+
__classPrivateFieldGet(this, _TreeViewElement_instances, "m", _TreeViewElement_autoExpandFrom).call(this, addedNode);
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}).observe(this, { childList: true, subtree: true });
|
46
|
+
// eslint-disable-next-line github/no-then -- We don't want to wait for this to resolve, just get on with it
|
47
|
+
customElements.whenDefined('tree-view-sub-tree-node').then(() => {
|
48
|
+
// depends on TreeViewSubTreeNodeElement#eachAncestorSubTreeNode, which may not be defined yet
|
49
|
+
__classPrivateFieldGet(this, _TreeViewElement_instances, "m", _TreeViewElement_autoExpandFrom).call(this, this);
|
50
|
+
});
|
33
51
|
}
|
34
52
|
disconnectedCallback() {
|
35
53
|
__classPrivateFieldGet(this, _TreeViewElement_abortController, "f").abort();
|
@@ -132,6 +150,16 @@ let TreeViewElement = class TreeViewElement extends HTMLElement {
|
|
132
150
|
nodeHasNativeAction(node) {
|
133
151
|
return node instanceof HTMLAnchorElement || node instanceof HTMLButtonElement;
|
134
152
|
}
|
153
|
+
expandAncestorsForNode(node) {
|
154
|
+
const subTreeNode = node.closest('tree-view-sub-tree-node');
|
155
|
+
if (!subTreeNode)
|
156
|
+
return;
|
157
|
+
for (const ancestor of subTreeNode.eachAncestorSubTreeNode()) {
|
158
|
+
if (!ancestor.expanded) {
|
159
|
+
ancestor.expand();
|
160
|
+
}
|
161
|
+
}
|
162
|
+
}
|
135
163
|
// PRIVATE API METHOD
|
136
164
|
//
|
137
165
|
// This would normally be marked private, but it's called by TreeViewSubTreeNodes
|
@@ -152,6 +180,11 @@ let TreeViewElement = class TreeViewElement extends HTMLElement {
|
|
152
180
|
};
|
153
181
|
_TreeViewElement_abortController = new WeakMap();
|
154
182
|
_TreeViewElement_instances = new WeakSet();
|
183
|
+
_TreeViewElement_autoExpandFrom = function _TreeViewElement_autoExpandFrom(root) {
|
184
|
+
for (const element of root.querySelectorAll('[aria-expanded=true]')) {
|
185
|
+
this.expandAncestorsForNode(element);
|
186
|
+
}
|
187
|
+
};
|
155
188
|
_TreeViewElement_eventIsActivation = function _TreeViewElement_eventIsActivation(event) {
|
156
189
|
return event.type === 'click';
|
157
190
|
};
|
@@ -14,6 +14,32 @@ export class TreeViewElement extends HTMLElement {
|
|
14
14
|
this.addEventListener('keydown', this, {signal})
|
15
15
|
|
16
16
|
useRovingTabIndex(this)
|
17
|
+
|
18
|
+
// catch-all for any straggler nodes that aren't available when connectedCallback runs
|
19
|
+
new MutationObserver(mutations => {
|
20
|
+
for (const mutation of mutations) {
|
21
|
+
for (const addedNode of mutation.addedNodes) {
|
22
|
+
if (!(addedNode instanceof HTMLElement)) continue
|
23
|
+
|
24
|
+
// eslint-disable-next-line custom-elements/no-dom-traversal-in-connectedcallback
|
25
|
+
if (addedNode.querySelector('[aria-expanded=true]')) {
|
26
|
+
this.#autoExpandFrom(addedNode)
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}).observe(this, {childList: true, subtree: true})
|
31
|
+
|
32
|
+
// eslint-disable-next-line github/no-then -- We don't want to wait for this to resolve, just get on with it
|
33
|
+
customElements.whenDefined('tree-view-sub-tree-node').then(() => {
|
34
|
+
// depends on TreeViewSubTreeNodeElement#eachAncestorSubTreeNode, which may not be defined yet
|
35
|
+
this.#autoExpandFrom(this)
|
36
|
+
})
|
37
|
+
}
|
38
|
+
|
39
|
+
#autoExpandFrom(root: HTMLElement) {
|
40
|
+
for (const element of root.querySelectorAll('[aria-expanded=true]')) {
|
41
|
+
this.expandAncestorsForNode(element as HTMLElement)
|
42
|
+
}
|
17
43
|
}
|
18
44
|
|
19
45
|
disconnectedCallback() {
|
@@ -246,6 +272,17 @@ export class TreeViewElement extends HTMLElement {
|
|
246
272
|
return node instanceof HTMLAnchorElement || node instanceof HTMLButtonElement
|
247
273
|
}
|
248
274
|
|
275
|
+
expandAncestorsForNode(node: HTMLElement) {
|
276
|
+
const subTreeNode = node.closest('tree-view-sub-tree-node') as TreeViewSubTreeNodeElement
|
277
|
+
if (!subTreeNode) return
|
278
|
+
|
279
|
+
for (const ancestor of subTreeNode.eachAncestorSubTreeNode()) {
|
280
|
+
if (!ancestor.expanded) {
|
281
|
+
ancestor.expand()
|
282
|
+
}
|
283
|
+
}
|
284
|
+
}
|
285
|
+
|
249
286
|
// PRIVATE API METHOD
|
250
287
|
//
|
251
288
|
// This would normally be marked private, but it's called by TreeViewSubTreeNodes
|
@@ -28,6 +28,7 @@ export declare class TreeViewSubTreeNodeElement extends HTMLElement {
|
|
28
28
|
get nodes(): NodeListOf<Element>;
|
29
29
|
eachDirectDescendantNode(): Generator<Element>;
|
30
30
|
eachDescendantNode(): Generator<Element>;
|
31
|
+
eachAncestorSubTreeNode(): Generator<TreeViewSubTreeNodeElement>;
|
31
32
|
get isEmpty(): boolean;
|
32
33
|
get treeView(): TreeViewElement | null;
|
33
34
|
toggleChecked(): void;
|
@@ -165,6 +165,16 @@ let TreeViewSubTreeNodeElement = class TreeViewSubTreeNodeElement extends HTMLEl
|
|
165
165
|
yield node;
|
166
166
|
}
|
167
167
|
}
|
168
|
+
*eachAncestorSubTreeNode() {
|
169
|
+
if (!this.treeView)
|
170
|
+
return;
|
171
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
172
|
+
let current = this;
|
173
|
+
while (current && this.treeView.contains(current)) {
|
174
|
+
yield current;
|
175
|
+
current = current.parentElement?.closest('tree-view-sub-tree-node');
|
176
|
+
}
|
177
|
+
}
|
168
178
|
get isEmpty() {
|
169
179
|
return this.nodes.length === 0;
|
170
180
|
}
|
@@ -230,6 +240,9 @@ _TreeViewSubTreeNodeElement_handleIncludeFragmentEvent = function _TreeViewSubTr
|
|
230
240
|
this.loadingState = 'success';
|
231
241
|
break;
|
232
242
|
case 'include-fragment-replaced':
|
243
|
+
// Make sure to expand the new sub-tree, otherwise it looks like nothing happened. This prevents
|
244
|
+
// having to remember to pass `SubTree.new(expanded: true)` in the controller.
|
245
|
+
this.expanded = true;
|
233
246
|
if (__classPrivateFieldGet(this, _TreeViewSubTreeNodeElement_activeElementIsLoader, "f")) {
|
234
247
|
const firstItem = this.querySelector('[role=group] > :first-child');
|
235
248
|
if (!firstItem)
|
@@ -308,6 +321,7 @@ _TreeViewSubTreeNodeElement_update = function _TreeViewSubTreeNodeElement_update
|
|
308
321
|
if (this.subTree)
|
309
322
|
this.subTree.hidden = false;
|
310
323
|
this.node.setAttribute('aria-expanded', 'true');
|
324
|
+
this.treeView?.expandAncestorsForNode(this);
|
311
325
|
if (this.iconPair) {
|
312
326
|
this.iconPair.showExpanded();
|
313
327
|
}
|
@@ -217,6 +217,19 @@ export class TreeViewSubTreeNodeElement extends HTMLElement {
|
|
217
217
|
}
|
218
218
|
}
|
219
219
|
|
220
|
+
*eachAncestorSubTreeNode(): Generator<TreeViewSubTreeNodeElement> {
|
221
|
+
if (!this.treeView) return
|
222
|
+
|
223
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
224
|
+
let current: TreeViewSubTreeNodeElement | null = this
|
225
|
+
|
226
|
+
while (current && this.treeView.contains(current)) {
|
227
|
+
yield current
|
228
|
+
|
229
|
+
current = current.parentElement?.closest('tree-view-sub-tree-node') as TreeViewSubTreeNodeElement | null
|
230
|
+
}
|
231
|
+
}
|
232
|
+
|
220
233
|
get isEmpty(): boolean {
|
221
234
|
return this.nodes.length === 0
|
222
235
|
}
|
@@ -252,6 +265,10 @@ export class TreeViewSubTreeNodeElement extends HTMLElement {
|
|
252
265
|
break
|
253
266
|
|
254
267
|
case 'include-fragment-replaced':
|
268
|
+
// Make sure to expand the new sub-tree, otherwise it looks like nothing happened. This prevents
|
269
|
+
// having to remember to pass `SubTree.new(expanded: true)` in the controller.
|
270
|
+
this.expanded = true
|
271
|
+
|
255
272
|
if (this.#activeElementIsLoader) {
|
256
273
|
const firstItem = this.querySelector('[role=group] > :first-child') as HTMLElement | null
|
257
274
|
if (!firstItem) return
|
@@ -377,6 +394,7 @@ export class TreeViewSubTreeNodeElement extends HTMLElement {
|
|
377
394
|
if (this.expanded) {
|
378
395
|
if (this.subTree) this.subTree.hidden = false
|
379
396
|
this.node.setAttribute('aria-expanded', 'true')
|
397
|
+
this.treeView?.expandAncestorsForNode(this)
|
380
398
|
|
381
399
|
if (this.iconPair) {
|
382
400
|
this.iconPair.showExpanded()
|
@@ -1,10 +1,19 @@
|
|
1
1
|
<%= form_with(url: action_menu_form_action_path(format: route_format)) do |f| %>
|
2
|
+
<% content = -> (base) do %>
|
3
|
+
<% base.with_item(label: "Fast forward", data: { value: "fast_forward" }) %>
|
4
|
+
<% base.with_item(label: "Recursive", data: { value: "recursive" }) %>
|
5
|
+
<% base.with_item(label: "Ours", data: { value: "ours" }, active: true) %>
|
6
|
+
<% base.with_item(label: "Resolve") %>
|
7
|
+
<% end %>
|
2
8
|
<%= render(Primer::Alpha::ActionMenu.new(select_variant: :multiple, dynamic_label: true, dynamic_label_prefix: "Strategy", form_arguments: { builder: f, name: "foo" })) do |menu| %>
|
3
9
|
<% menu.with_show_button { "Strategy" } %>
|
4
|
-
<%
|
5
|
-
|
6
|
-
|
7
|
-
|
10
|
+
<% if nest_in_sub_menu %>
|
11
|
+
<% menu.with_sub_menu_item(label: "Sub-menu") do |sub_menu| %>
|
12
|
+
<% content.call(sub_menu) %>
|
13
|
+
<% end %>
|
14
|
+
<% else %>
|
15
|
+
<% content.call(menu) %>
|
16
|
+
<% end %>
|
8
17
|
<% end %>
|
9
18
|
<hr>
|
10
19
|
<div>
|
@@ -1,20 +1,29 @@
|
|
1
1
|
<%= render(Primer::Alpha::ActionMenu.new) do |component| %>
|
2
2
|
<% component.with_show_button { "Menu" } %>
|
3
|
-
<%
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
3
|
+
<% contents = -> (base) do %>
|
4
|
+
<% base.with_item(label: "Item", tag: :button, value: "") %>
|
5
|
+
<% base.with_item(
|
6
|
+
label: "Show dialog",
|
7
|
+
tag: :button,
|
8
|
+
content_arguments: { "data-show-dialog-id": "my-dialog" },
|
9
|
+
value: "",
|
10
|
+
scheme: :danger
|
11
|
+
) %>
|
12
|
+
<% end %>
|
13
|
+
<% if nest_in_sub_menu %>
|
14
|
+
<% component.with_sub_menu_item(label: "Sub-menu") do |sub_menu_item| %>
|
15
|
+
<% contents.call(sub_menu_item) %>
|
16
|
+
<% end %>
|
17
|
+
<% else %>
|
18
|
+
<% contents.call(component) %>
|
19
|
+
<% end %>
|
11
20
|
<% end %>
|
12
21
|
|
13
|
-
<%= render(Primer::Alpha::Dialog.new(id: "my-dialog", title: "Confirm deletion")) do |
|
14
|
-
|
22
|
+
<%= render(Primer::Alpha::Dialog.new(id: "my-dialog", title: "Confirm deletion")) do |dialog| %>
|
23
|
+
<% dialog.with_body do %>
|
15
24
|
Are you sure you want to delete this?
|
16
25
|
<% end %>
|
17
|
-
|
26
|
+
<% dialog.with_footer do %>
|
18
27
|
<%= render(Primer::Beta::Button.new(data: { "close-dialog-id": "my-dialog" })) { "Cancel" } %>
|
19
28
|
<%= render(Primer::Beta::Button.new(scheme: :danger)) { "Delete" } %>
|
20
29
|
<% end %>
|
@@ -1,5 +1,4 @@
|
|
1
|
-
|
2
|
-
<% menu.with_show_button { "Group By" } %>
|
1
|
+
<% contents = -> (menu) do %>
|
3
2
|
<% menu.with_item(
|
4
3
|
label: "Repository",
|
5
4
|
href: action_menu_form_action_path(format: route_format),
|
@@ -29,3 +28,15 @@
|
|
29
28
|
}
|
30
29
|
) %>
|
31
30
|
<% end %>
|
31
|
+
|
32
|
+
<%= render(Primer::Alpha::ActionMenu.new(select_variant: :single)) do |menu| %>
|
33
|
+
<% menu.with_show_button { "Group By" } %>
|
34
|
+
|
35
|
+
<% if nest_in_sub_menu %>
|
36
|
+
<% menu.with_sub_menu_item(label: "Sub-menu") do |sub_menu_item| %>
|
37
|
+
<% contents.call(sub_menu_item) %>
|
38
|
+
<% end %>
|
39
|
+
<% else %>
|
40
|
+
<% contents.call(menu) %>
|
41
|
+
<% end %>
|
42
|
+
<% end %>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<%# Center the invoker button to give left-appearing sub-menus enough space %>
|
2
|
+
<div style="<%= sub_menu_anchor_side.to_s.include?("left") ? "text-align: center" : "" %>">
|
3
|
+
<%= render(Primer::Alpha::ActionMenu.new(anchor_align: anchor_align, anchor_side: anchor_side)) do |menu| %>
|
4
|
+
<% menu.with_show_button { "Edit" } %>
|
5
|
+
<% menu.with_item(label: "Cut") %>
|
6
|
+
<% menu.with_item(label: "Copy") %>
|
7
|
+
<% menu.with_sub_menu_item(label: "Paste special", anchor_align: sub_menu_anchor_align, anchor_side: sub_menu_anchor_side) do |sub_menu| %>
|
8
|
+
<% sub_menu.with_leading_visual_icon(icon: :"sparkle-fill") %>
|
9
|
+
<% sub_menu.with_item(label: "Paste plain text") %>
|
10
|
+
<% sub_menu.with_item(label: "Paste formulas") %>
|
11
|
+
<% sub_menu.with_item(label: "Paste with formatting") %>
|
12
|
+
<% sub_menu.with_sub_menu_item(label: "Paste from") do |sub_menu| %>
|
13
|
+
<% sub_menu.with_item(label: "Current clipboard") %>
|
14
|
+
<% sub_menu.with_item(label: "History") %>
|
15
|
+
<% sub_menu.with_item(label: "Another device") %>
|
16
|
+
<% end %>
|
17
|
+
<% end %>
|
18
|
+
<% end %>
|
19
|
+
</div>
|
@@ -8,15 +8,24 @@
|
|
8
8
|
|
9
9
|
<%= render(Primer::Alpha::ActionMenu.new) do |component| %>
|
10
10
|
<% component.with_show_button { "Trigger" } %>
|
11
|
-
<%
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
11
|
+
<% contents = -> (base) do %>
|
12
|
+
<% base.with_item(label: "Alert", tag: :button, id: "alert-item", disabled: disable_items) %>
|
13
|
+
<% base.with_item(label: "Navigate", tag: :a, content_arguments: { href: action_menu_landing_path }, disabled: disable_items) %>
|
14
|
+
<% base.with_item(label: "Copy text", tag: :"clipboard-copy", content_arguments: { value: "Text to copy" }, disabled: disable_items) %>
|
15
|
+
<% base.with_item(
|
16
|
+
label: "Submit form",
|
17
|
+
href: action_menu_form_action_path(format: route_format),
|
18
|
+
form_arguments: {
|
19
|
+
name: "foo", value: "bar", method: :post
|
20
|
+
},
|
21
|
+
disabled: disable_items
|
22
|
+
) %>
|
23
|
+
<% end %>
|
24
|
+
<% if nest_in_sub_menu %>
|
25
|
+
<% component.with_sub_menu_item(label: "Sub-menu") do |sub_menu_item| %>
|
26
|
+
<% contents.call(sub_menu_item) %>
|
27
|
+
<% end %>
|
28
|
+
<% else %>
|
29
|
+
<% contents.call(component) %>
|
30
|
+
<% end %>
|
22
31
|
<% end %>
|