spree_admin 5.2.0.rc3 → 5.2.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/app/assets/stylesheets/spree/admin/application.scss +1 -1
- data/app/assets/stylesheets/spree/admin/components/_dropdowns.scss +2 -0
- data/app/assets/stylesheets/spree/admin/components/_main.scss +2 -233
- data/app/assets/stylesheets/spree/admin/components/_sidebar.scss +693 -0
- data/app/assets/stylesheets/spree/admin/global/_variables.scss +1 -0
- data/app/assets/stylesheets/spree/admin/shared/_base.scss +7 -1
- data/app/assets/stylesheets/spree/admin/views/_dashboard.scss +14 -0
- data/app/controllers/spree/admin/integrations_controller.rb +1 -1
- data/app/controllers/spree/admin/metafields_controller.rb +1 -1
- data/app/controllers/spree/admin/page_blocks_controller.rb +1 -1
- data/app/controllers/spree/admin/payment_methods_controller.rb +1 -1
- data/app/controllers/spree/admin/promotion_actions_controller.rb +1 -1
- data/app/controllers/spree/admin/promotion_rules_controller.rb +1 -1
- data/app/controllers/spree/admin/promotions_controller.rb +1 -1
- data/app/controllers/spree/admin/reports_controller.rb +1 -1
- data/app/controllers/spree/admin/taxons_controller.rb +1 -1
- data/app/controllers/spree/admin/translations_controller.rb +1 -1
- data/app/helpers/spree/admin/base_helper.rb +1 -1
- data/app/helpers/spree/admin/drawer_helper.rb +6 -6
- data/app/helpers/spree/admin/dropdown_helper.rb +10 -2
- data/app/helpers/spree/admin/modal_helper.rb +2 -0
- data/app/helpers/spree/admin/navigation_helper.rb +46 -3
- data/app/helpers/spree/admin/orders_filters_helper.rb +1 -0
- data/app/helpers/spree/admin/promotion_actions_helper.rb +1 -1
- data/app/helpers/spree/admin/promotion_rules_helper.rb +1 -1
- data/app/helpers/spree/admin/translations_helper.rb +1 -1
- data/app/javascript/spree/admin/application.js +2 -1
- data/app/javascript/spree/admin/controllers/dropdown_controller.js +85 -14
- data/app/javascript/spree/admin/controllers/sidebar_controller.js +231 -0
- data/app/javascript/spree/admin/controllers/tooltip_controller.js +84 -31
- data/app/models/spree/admin/form_builder.rb +74 -16
- data/app/models/spree/admin/navigation/builder.rb +82 -0
- data/app/models/spree/admin/navigation/item.rb +177 -0
- data/app/models/spree/admin/navigation.rb +193 -0
- data/app/views/layouts/spree/admin.html.erb +1 -1
- data/app/views/spree/admin/assets/edit.html.erb +1 -1
- data/app/views/spree/admin/custom_domains/_form.html.erb +2 -14
- data/app/views/spree/admin/gift_cards/_filters.html.erb +26 -18
- data/app/views/spree/admin/gift_cards/index.html.erb +1 -1
- data/app/views/spree/admin/orders/_customer.html.erb +2 -2
- data/app/views/spree/admin/orders/_filters.html.erb +34 -25
- data/app/views/spree/admin/orders/_table_filter_dropdown.html.erb +1 -1
- data/app/views/spree/admin/page_blocks/forms/_image.html.erb +2 -5
- data/app/views/spree/admin/page_links/_form.html.erb +4 -13
- data/app/views/spree/admin/page_sections/forms/_header.html.erb +0 -2
- data/app/views/spree/admin/payments/_payment.html.erb +7 -0
- data/app/views/spree/admin/posts/_form.html.erb +1 -4
- data/app/views/spree/admin/posts/filters.html.erb +18 -8
- data/app/views/spree/admin/products/_bulk_operations.html.erb +1 -1
- data/app/views/spree/admin/products/_filters.html.erb +17 -6
- data/app/views/spree/admin/products/_table_filter_dropdown.html.erb +1 -1
- data/app/views/spree/admin/profile/edit.html.erb +9 -61
- data/app/views/spree/admin/promotions/_filters.html.erb +23 -13
- data/app/views/spree/admin/promotions/_table_filter_dropdown.html.erb +1 -1
- data/app/views/spree/admin/promotions/form/_settings.html.erb +2 -13
- data/app/views/spree/admin/refunds/_form.html.erb +1 -9
- data/app/views/spree/admin/return_authorizations/filters.html.erb +1 -1
- data/app/views/spree/admin/shared/_audit_nav.html.erb +2 -0
- data/app/views/spree/admin/shared/_calendar_range_picker.html.erb +2 -2
- data/app/views/spree/admin/shared/_content_header.html.erb +1 -1
- data/app/views/spree/admin/shared/_developers_nav.html.erb +2 -4
- data/app/views/spree/admin/shared/_header.html.erb +5 -7
- data/app/views/spree/admin/shared/_navigation.html.erb +5 -0
- data/app/views/spree/admin/shared/_navigation_item.html.erb +64 -0
- data/app/views/spree/admin/shared/_new_item_dropdown.html.erb +1 -1
- data/app/views/spree/admin/shared/_page_section_image.html.erb +2 -5
- data/app/views/spree/admin/shared/_page_section_logo.html.erb +1 -1
- data/app/views/spree/admin/shared/_returns_and_refunds_nav.html.erb +2 -3
- data/app/views/spree/admin/shared/_shipping_nav.html.erb +3 -2
- data/app/views/spree/admin/shared/_sidebar.html.erb +33 -7
- data/app/views/spree/admin/shared/_stock_nav.html.erb +6 -3
- data/app/views/spree/admin/shared/_tax_nav.html.erb +1 -2
- data/app/views/spree/admin/shared/_team_nav.html.erb +2 -3
- data/app/views/spree/admin/shared/_user_dropdown.html.erb +26 -17
- data/app/views/spree/admin/shared/sidebar/_customers_nav.html.erb +7 -0
- data/app/views/spree/admin/shared/sidebar/_orders_nav.html.erb +22 -2
- data/app/views/spree/admin/shared/sidebar/_products_nav.html.erb +21 -0
- data/app/views/spree/admin/shared/sidebar/_promotions_nav.html.erb +8 -0
- data/app/views/spree/admin/shared/sidebar/_returns_nav.html.erb +12 -0
- data/app/views/spree/admin/shared/sidebar/_store_dropdown.html.erb +3 -1
- data/app/views/spree/admin/shared/sidebar/_store_nav.html.erb +15 -1
- data/app/views/spree/admin/shared/sidebar/_storefront_nav.html.erb +25 -3
- data/app/views/spree/admin/shared/sortable_tree/_taxonomy.html.erb +1 -1
- data/app/views/spree/admin/stock_items/_filters.html.erb +18 -8
- data/app/views/spree/admin/stock_locations/_table_row.html.erb +1 -1
- data/app/views/spree/admin/stock_transfers/_filters.html.erb +19 -9
- data/app/views/spree/admin/storefront/edit.html.erb +2 -14
- data/app/views/spree/admin/stores/form/_basic.html.erb +2 -8
- data/app/views/spree/admin/stores/form/_emails.html.erb +1 -1
- data/app/views/spree/admin/tax_rates/_form.html.erb +1 -10
- data/app/views/spree/admin/taxons/_form.html.erb +2 -9
- data/app/views/spree/admin/themes/_theme.html.erb +1 -1
- data/app/views/spree/admin/translations/translation_rows/_permalink_field_row.html.erb +1 -12
- data/app/views/spree/admin/users/_filters.html.erb +23 -13
- data/config/initializers/spree_admin_navigation.rb +510 -0
- data/config/locales/en.yml +4 -0
- data/lib/generators/spree/admin/scaffold/templates/controller.rb.tt +3 -1
- data/lib/generators/spree/admin/scaffold/templates/views/_table_row.html.erb.tt +8 -6
- data/lib/spree/admin/engine.rb +64 -2
- data/lib/spree/admin/runtime_configuration.rb +1 -0
- data/lib/spree/admin.rb +20 -0
- metadata +17 -15
- data/app/assets/stylesheets/spree/admin/components/_offcanvas.scss +0 -26
- data/app/javascript/spree/admin/helpers/canvas.js +0 -29
- data/app/views/spree/admin/shared/_offcanvas_nav.html.erb +0 -12
|
@@ -31,4 +31,18 @@
|
|
|
31
31
|
display: flex;
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#updater-notice {
|
|
37
|
+
background-color: $white;
|
|
38
|
+
width: 100%;
|
|
39
|
+
display: flex;
|
|
40
|
+
flex-direction: row;
|
|
41
|
+
gap: 1rem;
|
|
42
|
+
justify-content: flex-start;
|
|
43
|
+
align-items: center;
|
|
44
|
+
padding: 0.5rem 0.5rem;
|
|
45
|
+
border: 1px solid $border-color;
|
|
46
|
+
border-radius: $border-radius-lg;
|
|
47
|
+
box-shadow: $box-shadow-xs;
|
|
34
48
|
}
|
|
@@ -16,7 +16,7 @@ module Spree
|
|
|
16
16
|
private
|
|
17
17
|
|
|
18
18
|
def allowed_integration_types
|
|
19
|
-
@allowed_integration_types ||=
|
|
19
|
+
@allowed_integration_types ||= Spree.integrations.map { |klass| [klass.to_s, klass] }.to_h
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def require_integration_type
|
|
@@ -33,7 +33,7 @@ module Spree
|
|
|
33
33
|
def allowed_payment_types
|
|
34
34
|
# We need to map to strings, otherwise some weird things happen with STI
|
|
35
35
|
# where Rails can't find the ancestor class when we try to save the payment method.
|
|
36
|
-
|
|
36
|
+
Spree.payment_methods.map(&:to_s)
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def permitted_resource_params
|
|
@@ -28,7 +28,7 @@ module Spree
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def load_form_data
|
|
31
|
-
@promotion_rules =
|
|
31
|
+
@promotion_rules = Spree.promotions.rules
|
|
32
32
|
@rule_types = @promotion_rules.map do |promotion_rule|
|
|
33
33
|
[Spree.t("promotion_rule_types.#{promotion_rule.to_s.demodulize.underscore}.name"), promotion_rule.to_s]
|
|
34
34
|
end
|
|
@@ -97,7 +97,7 @@ module Spree
|
|
|
97
97
|
end
|
|
98
98
|
|
|
99
99
|
def load_form_data
|
|
100
|
-
@taxon_rules =
|
|
100
|
+
@taxon_rules = Spree.taxon_rules
|
|
101
101
|
@rule_types = @taxon_rules.map do |taxon_rule|
|
|
102
102
|
[Spree.t("admin.taxon_rules.#{taxon_rule.to_s.demodulize.underscore}"), taxon_rule.to_s]
|
|
103
103
|
end
|
|
@@ -4,7 +4,7 @@ module Spree
|
|
|
4
4
|
include Spree::ImagesHelper
|
|
5
5
|
|
|
6
6
|
def render_admin_partials(section, options = {})
|
|
7
|
-
|
|
7
|
+
Spree.admin.partials.send(section.to_s.gsub('_partials', '').to_sym).map do |partial|
|
|
8
8
|
render partial, options
|
|
9
9
|
end.join.html_safe
|
|
10
10
|
end
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
module Admin
|
|
3
3
|
module DrawerHelper
|
|
4
|
-
def drawer_header(title)
|
|
4
|
+
def drawer_header(title, controller_name = 'drawer')
|
|
5
5
|
content_tag(:div, class: 'drawer-header') do
|
|
6
|
-
content_tag(:h5, title, class: 'drawer-title') + drawer_close_button
|
|
6
|
+
content_tag(:h5, title, class: 'drawer-title') + drawer_close_button(controller_name)
|
|
7
7
|
end.html_safe
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
def drawer_close_button
|
|
11
|
-
button_tag('', type: 'button', class: 'btn-close', data: { action:
|
|
10
|
+
def drawer_close_button(controller_name = 'drawer')
|
|
11
|
+
button_tag('', type: 'button', class: 'btn-close', data: { action: "#{controller_name}#close", dismiss: controller_name, aria_label: Spree.t(:close) }).html_safe
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
def drawer_discard_button
|
|
15
|
-
button_tag(type: 'button', class: 'btn btn-light', data: { action:
|
|
14
|
+
def drawer_discard_button(controller_name = 'drawer')
|
|
15
|
+
button_tag(type: 'button', class: 'btn btn-light', data: { action: "#{controller_name}#close", dismiss: controller_name }) do
|
|
16
16
|
Spree.t('actions.discard')
|
|
17
17
|
end.html_safe
|
|
18
18
|
end
|
|
@@ -16,10 +16,18 @@ module Spree
|
|
|
16
16
|
options[:placement] || 'bottom-start'
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
# Extract portal option
|
|
20
|
+
portal = options.delete(:portal)
|
|
21
|
+
|
|
22
|
+
data_attrs = {
|
|
20
23
|
controller: 'dropdown',
|
|
21
24
|
dropdown_placement_value: placement
|
|
22
|
-
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# Add portal value if explicitly set
|
|
28
|
+
data_attrs[:dropdown_portal_value] = portal unless portal.nil?
|
|
29
|
+
|
|
30
|
+
options[:data] = data_attrs.merge(options[:data] || {})
|
|
23
31
|
content_tag(:div, options, &block)
|
|
24
32
|
end
|
|
25
33
|
|
|
@@ -5,6 +5,8 @@ module Spree
|
|
|
5
5
|
# @param title [String, Proc] the title of the modal
|
|
6
6
|
# @return [String]
|
|
7
7
|
def modal_header(title)
|
|
8
|
+
Spree::Deprecation.warn('Bootstrap modals are deprecated and will be removed in Spree 6. Please use native dialogs with `dialog_header` helper.')
|
|
9
|
+
|
|
8
10
|
title = capture(&title) if block_given?
|
|
9
11
|
content_tag(:div, class: 'modal-header') do
|
|
10
12
|
content_tag(:h5, title, class: 'modal-title') + modal_close_button
|
|
@@ -7,15 +7,15 @@ module Spree
|
|
|
7
7
|
# @param [String, nil] icon Optional icon name to prepend to the label
|
|
8
8
|
# @param [Boolean, nil] active Whether the link should be marked as active
|
|
9
9
|
# @return [SafeBuffer] The navigation item HTML
|
|
10
|
-
def nav_item(label = nil, url, icon: nil, active: nil, data: {})
|
|
10
|
+
def nav_item(label = nil, url, icon: nil, active: nil, data: {}, **options)
|
|
11
11
|
content_tag :li, class: 'nav-item', role: 'presentation' do
|
|
12
12
|
if block_given?
|
|
13
|
-
active_link_to url, class: 'nav-link', active: active, data: data do
|
|
13
|
+
active_link_to url, class: 'nav-link', active: active, data: data, **options do
|
|
14
14
|
yield
|
|
15
15
|
end
|
|
16
16
|
else
|
|
17
17
|
label = icon(icon) + label if icon.present? && label.present?
|
|
18
|
-
active_link_to label, url, class: 'nav-link', active: active, data: data
|
|
18
|
+
active_link_to label, url, class: 'nav-link', active: active, data: data, **options
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
end
|
|
@@ -312,6 +312,49 @@ module Spree
|
|
|
312
312
|
icon(@breadcrumb_icon)
|
|
313
313
|
end
|
|
314
314
|
end
|
|
315
|
+
|
|
316
|
+
# Renders the navigation for the given context
|
|
317
|
+
# @param context [Symbol] the navigation context (:sidebar, :settings, etc.)
|
|
318
|
+
# @param options [Hash] additional options for rendering
|
|
319
|
+
# @return [String] the rendered navigation HTML
|
|
320
|
+
def render_navigation(context = :sidebar, **options)
|
|
321
|
+
return '' if Spree::Admin::RuntimeConfig.legacy_sidebar_navigation
|
|
322
|
+
|
|
323
|
+
items = navigation_items(context)
|
|
324
|
+
return '' if items.empty?
|
|
325
|
+
|
|
326
|
+
render 'spree/admin/shared/navigation',
|
|
327
|
+
items: items,
|
|
328
|
+
context: context,
|
|
329
|
+
**options
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# Get navigation items for the given context
|
|
333
|
+
# @param context [Symbol] the navigation context
|
|
334
|
+
# @return [Array<Spree::Admin::Navigation::Item>] the visible navigation items
|
|
335
|
+
def navigation_items(context = :sidebar)
|
|
336
|
+
# Pass the view context (self) so that can? and other helpers are available
|
|
337
|
+
Spree.admin.navigation.send(context)&.visible_items(self) || []
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# Renders page tab navigation for the given context
|
|
341
|
+
# @param context [Symbol] the navigation context (:tax_tabs, :shipping_tabs, etc.)
|
|
342
|
+
# @param options [Hash] additional options for rendering
|
|
343
|
+
# @return [String] the rendered tab navigation HTML wrapped in content_for(:page_tabs)
|
|
344
|
+
def render_tab_navigation(context, **options)
|
|
345
|
+
items = navigation_items(context)
|
|
346
|
+
return '' if items.empty?
|
|
347
|
+
|
|
348
|
+
content_for :page_tabs do
|
|
349
|
+
items.map do |item|
|
|
350
|
+
item_url = item.resolve_url(self)
|
|
351
|
+
item_label = item.resolve_label
|
|
352
|
+
is_active = item.active?(request.path, self)
|
|
353
|
+
|
|
354
|
+
nav_item(item_label, item_url, active: is_active)
|
|
355
|
+
end.join.html_safe
|
|
356
|
+
end
|
|
357
|
+
end
|
|
315
358
|
end
|
|
316
359
|
end
|
|
317
360
|
end
|
|
@@ -70,6 +70,7 @@ module Spree
|
|
|
70
70
|
# e.g. SELECT DISTINCT DISTINCT "spree_orders".id, "spree_orders"."created_at" AS alias_0 FROM "spree_orders"
|
|
71
71
|
# see https://github.com/spree/spree/pull/3919
|
|
72
72
|
@orders = @search.result(distinct: true).page(params[:page]).per(params[:per_page] || Spree::Admin::RuntimeConfig.admin_orders_per_page)
|
|
73
|
+
@collection = @orders
|
|
73
74
|
end
|
|
74
75
|
|
|
75
76
|
def load_user
|
|
@@ -3,7 +3,7 @@ module Spree
|
|
|
3
3
|
module PromotionActionsHelper
|
|
4
4
|
def options_for_promotion_action_types(promotion)
|
|
5
5
|
existing = promotion.actions.pluck(:type)
|
|
6
|
-
|
|
6
|
+
Spree.promotions.actions.map(&:name).reject { |r| existing.include? r }
|
|
7
7
|
end
|
|
8
8
|
end
|
|
9
9
|
end
|
|
@@ -3,7 +3,7 @@ module Spree
|
|
|
3
3
|
module PromotionRulesHelper
|
|
4
4
|
def options_for_promotion_rule_types(promotion)
|
|
5
5
|
existing = promotion.rules.pluck(:type)
|
|
6
|
-
|
|
6
|
+
Spree.promotions.rules.map(&:name).reject { |r| existing.include? r }
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def active_options_for_option_value_promotion_rule(promotion_rule)
|
|
@@ -2,7 +2,7 @@ module Spree
|
|
|
2
2
|
module Admin
|
|
3
3
|
module TranslationsHelper
|
|
4
4
|
def link_to_edit_translations(resource, options = {})
|
|
5
|
-
return unless
|
|
5
|
+
return unless Spree.translatable_resources.map(&:name).include?(resource.class.name)
|
|
6
6
|
return unless can?(:update, resource)
|
|
7
7
|
return unless can?(:update, :translations)
|
|
8
8
|
|
|
@@ -15,7 +15,6 @@ import "mapkick/bundle"
|
|
|
15
15
|
|
|
16
16
|
// Helpers
|
|
17
17
|
import 'spree/admin/helpers/tinymce'
|
|
18
|
-
import 'spree/admin/helpers/canvas'
|
|
19
18
|
import 'spree/admin/helpers/trix/video_embed'
|
|
20
19
|
import 'spree/admin/helpers/bootstrap'
|
|
21
20
|
|
|
@@ -76,6 +75,7 @@ import SearchPickerController from 'spree/admin/controllers/search_picker_contro
|
|
|
76
75
|
import SectionFormController from 'spree/admin/controllers/section_form_controller'
|
|
77
76
|
import SelectController from 'spree/admin/controllers/select_controller'
|
|
78
77
|
import SeoFormController from 'spree/admin/controllers/seo_form_controller'
|
|
78
|
+
import SidebarController from 'spree/admin/controllers/sidebar_controller'
|
|
79
79
|
import SlugFormController from 'spree/admin/controllers/slug_form_controller'
|
|
80
80
|
import StickyController from 'spree/admin/controllers/sticky_controller'
|
|
81
81
|
import SortableAutoSubmit from 'spree/admin/controllers/sortable_auto_submit_controller'
|
|
@@ -141,6 +141,7 @@ application.register('search-picker', SearchPickerController)
|
|
|
141
141
|
application.register('section-form', SectionFormController)
|
|
142
142
|
application.register('select', SelectController)
|
|
143
143
|
application.register('seo-form', SeoFormController)
|
|
144
|
+
application.register('sidebar', SidebarController)
|
|
144
145
|
application.register('slug-form', SlugFormController)
|
|
145
146
|
application.register('sticky', StickyController)
|
|
146
147
|
application.register('sortable', Sortable)
|
|
@@ -9,24 +9,44 @@ import {
|
|
|
9
9
|
import { Controller } from "@hotwired/stimulus"
|
|
10
10
|
|
|
11
11
|
export default class extends Controller {
|
|
12
|
-
static targets = ["menu", "toggle"]
|
|
13
12
|
static values = {
|
|
14
13
|
placement: { type: String, default: "bottom-start" },
|
|
15
14
|
offset: { type: Number, default: 4 },
|
|
15
|
+
portal: { type: Boolean, default: true },
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
connect() {
|
|
19
|
+
// Find menu and toggle elements by CSS class instead of Stimulus target
|
|
20
|
+
// Try both .dropdown-menu and .dropdown-container for backward compatibility
|
|
21
|
+
this.menu = this.element.querySelector('.dropdown-menu') ||
|
|
22
|
+
this.element.querySelector('.dropdown-container')
|
|
23
|
+
this.toggleBtn = this.element.querySelector('.dropdown-toggle')
|
|
24
|
+
|
|
25
|
+
// Early return if no menu element exists
|
|
26
|
+
if (!this.menu) {
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
19
30
|
this._cleanup = null
|
|
20
31
|
this.boundUpdate = this.update.bind(this)
|
|
21
32
|
this._isOpen = false
|
|
22
33
|
this._toggleElement = null
|
|
34
|
+
this._originalParent = null
|
|
35
|
+
this._originalNextSibling = null
|
|
36
|
+
this._movedToBody = false
|
|
23
37
|
}
|
|
24
38
|
|
|
25
39
|
disconnect() {
|
|
40
|
+
if (!this.menu) return
|
|
26
41
|
this.stopAutoUpdate()
|
|
42
|
+
this.restoreMenuPosition()
|
|
27
43
|
}
|
|
28
44
|
|
|
29
45
|
toggle(event) {
|
|
46
|
+
if (!this.menu) {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
30
50
|
event.preventDefault()
|
|
31
51
|
event.stopPropagation()
|
|
32
52
|
|
|
@@ -41,9 +61,19 @@ export default class extends Controller {
|
|
|
41
61
|
}
|
|
42
62
|
|
|
43
63
|
open() {
|
|
44
|
-
if (this._isOpen)
|
|
64
|
+
if (!this.menu || this._isOpen) {
|
|
65
|
+
return
|
|
66
|
+
}
|
|
45
67
|
|
|
46
|
-
|
|
68
|
+
// Move menu to body on first open to prevent clipping by sidebar overflow
|
|
69
|
+
// Skip if portal is disabled or if inside bulk panel (to preserve Stimulus controller context)
|
|
70
|
+
if (!this._movedToBody && this.shouldPortalToBody()) {
|
|
71
|
+
this.moveMenuToBody()
|
|
72
|
+
this._movedToBody = true
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
this.menu.classList.remove("hidden")
|
|
76
|
+
this.menu.style.display = "block"
|
|
47
77
|
this._isOpen = true
|
|
48
78
|
|
|
49
79
|
// Start automatic positioning
|
|
@@ -59,9 +89,10 @@ export default class extends Controller {
|
|
|
59
89
|
}
|
|
60
90
|
|
|
61
91
|
close() {
|
|
62
|
-
if (!this._isOpen) return
|
|
92
|
+
if (!this.menu || !this._isOpen) return
|
|
63
93
|
|
|
64
|
-
this.
|
|
94
|
+
this.menu.classList.add("hidden")
|
|
95
|
+
this.menu.style.display = ""
|
|
65
96
|
this._isOpen = false
|
|
66
97
|
|
|
67
98
|
// Stop automatic positioning
|
|
@@ -101,11 +132,11 @@ export default class extends Controller {
|
|
|
101
132
|
}
|
|
102
133
|
|
|
103
134
|
startAutoUpdate() {
|
|
104
|
-
if (!this._cleanup) {
|
|
105
|
-
const referenceElement = this.
|
|
135
|
+
if (!this._cleanup && this.menu) {
|
|
136
|
+
const referenceElement = this.toggleBtn || this._toggleElement || this.element
|
|
106
137
|
this._cleanup = autoUpdate(
|
|
107
138
|
referenceElement,
|
|
108
|
-
this.
|
|
139
|
+
this.menu,
|
|
109
140
|
this.boundUpdate,
|
|
110
141
|
)
|
|
111
142
|
}
|
|
@@ -119,10 +150,12 @@ export default class extends Controller {
|
|
|
119
150
|
}
|
|
120
151
|
|
|
121
152
|
update() {
|
|
122
|
-
|
|
123
|
-
|
|
153
|
+
if (!this.menu) return
|
|
154
|
+
|
|
155
|
+
// Use the toggle button if available, or the stored toggle element, or fall back to the controller element
|
|
156
|
+
const referenceElement = this.toggleBtn || this._toggleElement || this.element
|
|
124
157
|
|
|
125
|
-
computePosition(referenceElement, this.
|
|
158
|
+
computePosition(referenceElement, this.menu, {
|
|
126
159
|
placement: this.placementValue,
|
|
127
160
|
middleware: [
|
|
128
161
|
offset(this.offsetValue),
|
|
@@ -133,9 +166,18 @@ export default class extends Controller {
|
|
|
133
166
|
shift({ padding: 8 }),
|
|
134
167
|
size({
|
|
135
168
|
apply({ availableWidth, availableHeight, elements }) {
|
|
136
|
-
//
|
|
169
|
+
// Get the element's computed max-width
|
|
170
|
+
const computedStyle = window.getComputedStyle(elements.floating)
|
|
171
|
+
const originalMaxWidth = parseFloat(computedStyle.maxWidth)
|
|
172
|
+
|
|
173
|
+
// Use the smaller of availableWidth or original max-width
|
|
174
|
+
const maxWidth = originalMaxWidth && !isNaN(originalMaxWidth)
|
|
175
|
+
? Math.min(availableWidth, originalMaxWidth)
|
|
176
|
+
: availableWidth
|
|
177
|
+
|
|
178
|
+
// Ensure dropdown doesn't exceed viewport or original constraints
|
|
137
179
|
Object.assign(elements.floating.style, {
|
|
138
|
-
maxWidth: `${
|
|
180
|
+
maxWidth: `${maxWidth}px`,
|
|
139
181
|
maxHeight: `${availableHeight}px`,
|
|
140
182
|
overflow: "auto",
|
|
141
183
|
})
|
|
@@ -144,11 +186,40 @@ export default class extends Controller {
|
|
|
144
186
|
}),
|
|
145
187
|
],
|
|
146
188
|
}).then(({ x, y }) => {
|
|
147
|
-
Object.assign(this.
|
|
189
|
+
Object.assign(this.menu.style, {
|
|
148
190
|
left: `${x}px`,
|
|
149
191
|
top: `${y}px`,
|
|
150
192
|
position: "absolute",
|
|
151
193
|
})
|
|
152
194
|
})
|
|
153
195
|
}
|
|
196
|
+
|
|
197
|
+
shouldPortalToBody() {
|
|
198
|
+
// Don't portal if explicitly disabled via data attribute
|
|
199
|
+
return this.portalValue
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
moveMenuToBody() {
|
|
203
|
+
if (this.menu && this.menu.parentNode !== document.body) {
|
|
204
|
+
// Save original position for restoration
|
|
205
|
+
this._originalParent = this.menu.parentNode
|
|
206
|
+
this._originalNextSibling = this.menu.nextSibling
|
|
207
|
+
|
|
208
|
+
// Move menu to body to prevent clipping by sidebar overflow
|
|
209
|
+
document.body.appendChild(this.menu)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
restoreMenuPosition() {
|
|
214
|
+
if (this.menu && this._originalParent) {
|
|
215
|
+
// Restore menu to original position
|
|
216
|
+
if (this._originalNextSibling) {
|
|
217
|
+
this._originalParent.insertBefore(this.menu, this._originalNextSibling)
|
|
218
|
+
} else {
|
|
219
|
+
this._originalParent.appendChild(this.menu)
|
|
220
|
+
}
|
|
221
|
+
this._originalParent = null
|
|
222
|
+
this._originalNextSibling = null
|
|
223
|
+
}
|
|
224
|
+
}
|
|
154
225
|
}
|