Class: LcpRuby::Metadata::MenuItem
- Inherits:
-
Object
- Object
- LcpRuby::Metadata::MenuItem
- Defined in:
- lib/lcp_ruby/metadata/menu_item.rb
Constant Summary collapse
- TYPES =
%i[view_group link group separator sidebar_toggle].freeze
- POSITION_BOTTOM =
"bottom"- POSITION_RIGHT =
"right"- POSITION_TOP =
"top"- POSITION_LEFT =
"left"- POSITIONS =
[ POSITION_BOTTOM, POSITION_RIGHT, POSITION_TOP, POSITION_LEFT ].freeze
- MAX_NESTING_DEPTH =
2- BUILTIN_ACTIONS =
Built-in CRUD actions implicit on every presenter. Used by ‘meets_visibility?` to pick `can?(action)` (built-in) vs `can_execute_action?(action)` (custom) for permission gating of `presenter:` items, and by `MenuItemResolver` to pick the correct route helper.
%w[show edit new destroy index].freeze
- DESTINATION_KEYS =
Destination keys — exactly one must appear on every non-widget item. ‘widget:` (PR #2b) accepts `provider:` only; that mutex is layered on top in PR #2b’s MenuItem extensions.
%w[url presenter view_group provider children separator sidebar_toggle].freeze
- SEPARATOR_ALLOWED_KEYS =
Keys allowed on a ‘:separator` item. Anything else is a configurator footgun — a separator with a `label:` is invisible (no element renders it); a separator with `disable_when:` makes no sense (nothing to disable). Validator rejects at boot.
%w[separator position visible_when].freeze
- SIDEBAR_TOGGLE_ALLOWED_KEYS =
Keys allowed on a ‘:sidebar_toggle` item. Platform-owned UI chrome has no destination, no children, and no responsive overflow semantics (it’s implicitly ‘pin: always` at render time). The responsive keys (responsive_priority/pin/hide_below/show_below/ collapse_label_below) would be no-ops here, so reject them at parse time rather than letting them silently slide.
%w[ sidebar_toggle label label_key icon aria_label aria_label_key position visible_when ].freeze
Instance Attribute Summary collapse
-
#action_name ⇒ Object
readonly
Returns the value of attribute action_name.
-
#alias_name ⇒ Object
readonly
Returns the value of attribute alias_name.
-
#aria_label ⇒ Object
readonly
Returns the value of attribute aria_label.
-
#aria_label_key ⇒ Object
readonly
Returns the value of attribute aria_label_key.
-
#aria_label_source_loc ⇒ Object
readonly
Returns the value of attribute aria_label_source_loc.
-
#badge ⇒ Object
readonly
Returns the value of attribute badge.
-
#children ⇒ Object
readonly
Returns the value of attribute children.
-
#collapse_label_below ⇒ Object
readonly
Returns the value of attribute collapse_label_below.
-
#defaults ⇒ Object
readonly
Returns the value of attribute defaults.
-
#disable_when ⇒ Object
readonly
Returns the value of attribute disable_when.
-
#hide_below ⇒ Object
readonly
Returns the value of attribute hide_below.
-
#http_method ⇒ Object
readonly
Returns the value of attribute http_method.
-
#icon ⇒ Object
readonly
Returns the value of attribute icon.
-
#label ⇒ Object
readonly
Returns the value of attribute label.
-
#label_key ⇒ Object
readonly
Returns the value of attribute label_key.
-
#label_source_loc ⇒ Object
readonly
Returns the value of attribute label_source_loc.
-
#panel_provider_name ⇒ Object
readonly
Returns the value of attribute panel_provider_name.
-
#pin ⇒ Object
readonly
Returns the value of attribute pin.
-
#position ⇒ Object
readonly
Returns the value of attribute position.
-
#presenter_slug ⇒ Object
readonly
Returns the value of attribute presenter_slug.
-
#provider_name ⇒ Object
readonly
Returns the value of attribute provider_name.
-
#provider_options ⇒ Object
readonly
Returns the value of attribute provider_options.
-
#render_panel_partial ⇒ Object
readonly
Returns the value of attribute render_panel_partial.
-
#render_partial ⇒ Object
readonly
Returns the value of attribute render_partial.
-
#responsive_priority ⇒ Object
readonly
Returns the value of attribute responsive_priority.
-
#show_below ⇒ Object
readonly
Returns the value of attribute show_below.
-
#type ⇒ Object
readonly
Returns the value of attribute type.
-
#url ⇒ Object
readonly
Returns the value of attribute url.
-
#view_group_name ⇒ Object
readonly
Returns the value of attribute view_group_name.
-
#visible_when ⇒ Object
readonly
Returns the value of attribute visible_when.
-
#widget ⇒ Object
readonly
Returns the value of attribute widget.
Class Method Summary collapse
-
.from_hash(hash) ⇒ Object
Build a MenuItem from a parsed YAML hash.
-
.from_hash_at_depth(hash, depth) ⇒ Object
Variant that threads an explicit depth budget through static ‘:children:` recursion AND provider expansion (PR #2b).
Instance Method Summary collapse
- #badge_form ⇒ Object
- #badge_options ⇒ Object
- #badge_partial ⇒ Object
- #badge_provider ⇒ Object
- #badge_renderer ⇒ Object
- #badge_template ⇒ Object
- #bottom? ⇒ Boolean
-
#contains_slug?(slug, loader) ⇒ Boolean
Recursively check if this item or any descendant contains the given slug.
- #group? ⇒ Boolean
- #has_badge? ⇒ Boolean
-
#initialize(type:, view_group_name: nil, label: nil, label_key: nil, icon: nil, url: nil, children: [], visible_when: {}, disable_when: {}, position: nil, badge: nil, aria_label: nil, aria_label_key: nil, http_method: nil, presenter_slug: nil, alias_name: nil, action_name: nil, defaults: nil, render_partial: nil, render_panel_partial: nil, widget: nil, provider_name: nil, provider_options: nil, panel_provider_name: nil, label_source_loc: nil, aria_label_source_loc: nil, responsive_priority: nil, pin: nil, hide_below: nil, show_below: nil, collapse_label_below: nil) ⇒ MenuItem
constructor
A new instance of MenuItem.
- #left? ⇒ Boolean
- #link? ⇒ Boolean
-
#meets_enabled?(user, _helper = nil) ⇒ Boolean
Mirror of ‘meets_visibility?` but for `disable_when:`.
-
#meets_visibility?(user, helper = nil) ⇒ Boolean
Menu items evaluate visibility from two stacks: the ‘visible_when:` condition tree (record-free; supports service: and compound all/any/not — `field:`/`collection:` are rejected by the validator) AND, for `presenter:` items, the same permission gate the presenter toolbar runs (`can_access_presenter?` plus `can?(action)` for built-in CRUD or `can_execute_action?` for custom actions).
-
#render_panel_partial_path ⇒ Object
‘<namespace>/menu_renderers/<name>` — Rails partial path for the panel renderer.
-
#render_partial_path ⇒ Object
‘<namespace>/menu_renderers/<name>` — Rails partial path for the trigger renderer.
-
#resolved_aria_label(_loader = nil) ⇒ Object
Resolve the accessible name for the link/button.
-
#resolved_icon(loader) ⇒ Object
Resolve icon from view group’s primary presenter when not explicitly set.
-
#resolved_label(loader) ⇒ Object
Resolve label with i18n support.
-
#resolved_slug(loader) ⇒ Object
Resolve slug from view group’s primary page.
- #right? ⇒ Boolean
- #separator? ⇒ Boolean
- #sidebar_toggle? ⇒ Boolean
-
#to_kwargs ⇒ Object
Snapshot of the keyword arguments needed to reconstruct this item via ‘MenuItem.new(**kwargs)`.
-
#top? ⇒ Boolean
Forward-compat predicates for ‘position: top` / `position: left`.
-
#validate_resolved_content!(loader) ⇒ Object
Loader-aware boot-time validation.
- #view_group? ⇒ Boolean
-
#widget? ⇒ Boolean
Predicate sugar — clearer at call sites than ‘widget == true`.
-
#with(**overrides) ⇒ Object
Returns a copy of this item with selected attributes overridden.
Constructor Details
#initialize(type:, view_group_name: nil, label: nil, label_key: nil, icon: nil, url: nil, children: [], visible_when: {}, disable_when: {}, position: nil, badge: nil, aria_label: nil, aria_label_key: nil, http_method: nil, presenter_slug: nil, alias_name: nil, action_name: nil, defaults: nil, render_partial: nil, render_panel_partial: nil, widget: nil, provider_name: nil, provider_options: nil, panel_provider_name: nil, label_source_loc: nil, aria_label_source_loc: nil, responsive_priority: nil, pin: nil, hide_below: nil, show_below: nil, collapse_label_below: nil) ⇒ MenuItem
Returns a new instance of MenuItem.
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 32 def initialize(type:, view_group_name: nil, label: nil, label_key: nil, icon: nil, url: nil, children: [], visible_when: {}, disable_when: {}, position: nil, badge: nil, aria_label: nil, aria_label_key: nil, http_method: nil, presenter_slug: nil, alias_name: nil, action_name: nil, defaults: nil, render_partial: nil, render_panel_partial: nil, widget: nil, provider_name: nil, provider_options: nil, panel_provider_name: nil, label_source_loc: nil, aria_label_source_loc: nil, responsive_priority: nil, pin: nil, hide_below: nil, show_below: nil, collapse_label_below: nil) @type = type @view_group_name = view_group_name # `label` may be a String, `false` (explicit opt-out from any # resolved fallback — relevant for view_group items where the # presenter would otherwise supply a label), or nil/omitted. @label = label @label_key = label_key @icon = icon @url = url @children = children @visible_when = HashUtils.stringify_deep(visible_when || {}) @disable_when = HashUtils.stringify_deep(disable_when || {}) @position = position @badge = badge.is_a?(Hash) ? HashUtils.stringify_deep(badge) : nil @aria_label = aria_label @aria_label_key = aria_label_key # `method:` (YAML key) is stored as `http_method` to avoid # shadowing `Object#method(name)`. Normalized to a lowercase # symbol (`:get`, `:post`, `:put`, `:patch`, `:delete`); `nil` # means "unspecified" (renderer picks a default — `:get` for # url:/view_group:/presenter+show, etc.). @http_method = http_method&.to_s&.downcase&.to_sym @presenter_slug = presenter_slug&.to_s @alias_name = alias_name&.to_s @action_name = action_name&.to_s @defaults = defaults.is_a?(Hash) ? HashUtils.stringify_deep(defaults) : nil # PR #2b modifiers: `render:` partial path, `render_panel:` # partial path, `widget:` flag, `provider:` and `panel_provider:` # service names, and the shared `options:` hash both providers # consume (mutex with each other so no key collision). @render_partial = render_partial&.to_s @render_panel_partial = render_panel_partial&.to_s @widget = == true @provider_name = provider_name&.to_s @provider_options = .is_a?(Hash) ? HashUtils.stringify_deep() : {} @panel_provider_name = panel_provider_name&.to_s # i18n_check Phase 3a — populated when a menu DSL builder is # added (none exists today; menu is YAML-only). YAML-loaded # menus carry nil and Pass 3 covers them. @label_source_loc = label_source_loc @aria_label_source_loc = aria_label_source_loc # Responsive per-item keys (all optional; nil = no effect at # runtime). Type coercion only — depth/layout-aware warnings # live in ConfigurationValidator. @responsive_priority = responsive_priority&.to_i @pin = pin&.to_s @hide_below = hide_below&.to_i @show_below = show_below&.to_i @collapse_label_below = collapse_label_below&.to_i validate! end |
Instance Attribute Details
#action_name ⇒ Object (readonly)
Returns the value of attribute action_name.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def action_name @action_name end |
#alias_name ⇒ Object (readonly)
Returns the value of attribute alias_name.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def alias_name @alias_name end |
#aria_label ⇒ Object (readonly)
Returns the value of attribute aria_label.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def aria_label @aria_label end |
#aria_label_key ⇒ Object (readonly)
Returns the value of attribute aria_label_key.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def aria_label_key @aria_label_key end |
#aria_label_source_loc ⇒ Object (readonly)
Returns the value of attribute aria_label_source_loc.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def aria_label_source_loc @aria_label_source_loc end |
#badge ⇒ Object (readonly)
Returns the value of attribute badge.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def badge @badge end |
#children ⇒ Object (readonly)
Returns the value of attribute children.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def children @children end |
#collapse_label_below ⇒ Object (readonly)
Returns the value of attribute collapse_label_below.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def collapse_label_below @collapse_label_below end |
#defaults ⇒ Object (readonly)
Returns the value of attribute defaults.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def defaults @defaults end |
#disable_when ⇒ Object (readonly)
Returns the value of attribute disable_when.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def disable_when @disable_when end |
#hide_below ⇒ Object (readonly)
Returns the value of attribute hide_below.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def hide_below @hide_below end |
#http_method ⇒ Object (readonly)
Returns the value of attribute http_method.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def http_method @http_method end |
#icon ⇒ Object (readonly)
Returns the value of attribute icon.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def icon @icon end |
#label ⇒ Object (readonly)
Returns the value of attribute label.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def label @label end |
#label_key ⇒ Object (readonly)
Returns the value of attribute label_key.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def label_key @label_key end |
#label_source_loc ⇒ Object (readonly)
Returns the value of attribute label_source_loc.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def label_source_loc @label_source_loc end |
#panel_provider_name ⇒ Object (readonly)
Returns the value of attribute panel_provider_name.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def panel_provider_name @panel_provider_name end |
#pin ⇒ Object (readonly)
Returns the value of attribute pin.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def pin @pin end |
#position ⇒ Object (readonly)
Returns the value of attribute position.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def position @position end |
#presenter_slug ⇒ Object (readonly)
Returns the value of attribute presenter_slug.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def presenter_slug @presenter_slug end |
#provider_name ⇒ Object (readonly)
Returns the value of attribute provider_name.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def provider_name @provider_name end |
#provider_options ⇒ Object (readonly)
Returns the value of attribute provider_options.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def @provider_options end |
#render_panel_partial ⇒ Object (readonly)
Returns the value of attribute render_panel_partial.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def render_panel_partial @render_panel_partial end |
#render_partial ⇒ Object (readonly)
Returns the value of attribute render_partial.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def render_partial @render_partial end |
#responsive_priority ⇒ Object (readonly)
Returns the value of attribute responsive_priority.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def responsive_priority @responsive_priority end |
#show_below ⇒ Object (readonly)
Returns the value of attribute show_below.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def show_below @show_below end |
#type ⇒ Object (readonly)
Returns the value of attribute type.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def type @type end |
#url ⇒ Object (readonly)
Returns the value of attribute url.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def url @url end |
#view_group_name ⇒ Object (readonly)
Returns the value of attribute view_group_name.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def view_group_name @view_group_name end |
#visible_when ⇒ Object (readonly)
Returns the value of attribute visible_when.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def visible_when @visible_when end |
#widget ⇒ Object (readonly)
Returns the value of attribute widget.
21 22 23 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 21 def @widget end |
Class Method Details
.from_hash(hash) ⇒ Object
Build a MenuItem from a parsed YAML hash. Detection priority: separator > view_group > children > url|presenter Existing call style preserved: ‘from_hash(“view_group” => “x”, …)` works because the method takes a single positional Hash. Any incoming `:type` / `“type”` key is silently ignored — type is always re-derived from the destination keys present (so merging `to_kwargs` with provider returns works without colliding on type).
111 112 113 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 111 def self.from_hash(hash) build_from_hash(hash, 0) end |
.from_hash_at_depth(hash, depth) ⇒ Object
Variant that threads an explicit depth budget through static ‘:children:` recursion AND provider expansion (PR #2b). Public so `LayoutHelper#expand_provider` can pass the running depth without re-implementing the construction logic. The leading positional `hash` keeps Ruby 3 from misinterpreting string-key hash literals as kwargs.
121 122 123 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 121 def self.from_hash_at_depth(hash, depth) build_from_hash(hash, depth) end |
Instance Method Details
#badge_form ⇒ Object
529 530 531 532 533 534 535 536 537 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 529 def badge_form if badge&.dig("renderer") :renderer elsif badge&.dig("partial") :partial elsif badge&.dig("template") :template end end |
#badge_options ⇒ Object
551 552 553 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 551 def badge&.dig("options") || {} end |
#badge_partial ⇒ Object
543 544 545 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 543 def badge_partial badge&.dig("partial") end |
#badge_provider ⇒ Object
521 522 523 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 521 def badge_provider badge&.dig("provider") end |
#badge_renderer ⇒ Object
539 540 541 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 539 def badge_renderer badge&.dig("renderer") end |
#badge_template ⇒ Object
547 548 549 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 547 def badge_template badge&.dig("template") end |
#bottom? ⇒ Boolean
555 556 557 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 555 def bottom? position.to_s == POSITION_BOTTOM end |
#contains_slug?(slug, loader) ⇒ Boolean
Recursively check if this item or any descendant contains the given slug
414 415 416 417 418 419 420 421 422 423 424 425 426 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 414 def contains_slug?(slug, loader) if view_group? vg = loader.view_group_definitions[view_group_name] return false unless vg all_slugs = vg.page_names.filter_map do |name| loader.page_definitions[name]&.slug end return true if all_slugs.include?(slug) end children.any? { |child| child.contains_slug?(slug, loader) } end |
#group? ⇒ Boolean
582 583 584 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 582 def group? type == :group end |
#has_badge? ⇒ Boolean
525 526 527 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 525 def has_badge? badge_provider.present? end |
#left? ⇒ Boolean
570 571 572 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 570 def left? position.to_s == POSITION_LEFT end |
#link? ⇒ Boolean
578 579 580 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 578 def link? type == :link end |
#meets_enabled?(user, _helper = nil) ⇒ Boolean
Mirror of ‘meets_visibility?` but for `disable_when:`. Returns true when the item should render in its enabled (clickable) state. Returns false when `disable_when:` evaluates true →rendering applies `aria-disabled` / `<button disabled>` plus the `lcp-menu-item–disabled` CSS class.
On condition errors: dev/test re-raises, production records and returns true (item enabled). The “enabled by default on error” bias mirrors today’s failure-modes table — disable_when is a UI affordance, not a security boundary, so its failure should not silently block users from a working endpoint.
459 460 461 462 463 464 465 466 467 468 469 470 471 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 459 def meets_enabled?(user, _helper = nil) return true if disable_when.blank? !LcpRuby::ConditionEvaluator.evaluate_any( nil, disable_when, context: { current_user: user } ) rescue LcpRuby::ConditionError => e raise unless defined?(Rails) && Rails.respond_to?(:env) && Rails.env.production? LcpRuby.record_error(e, subsystem: "menu_disable", item: label || view_group_name || url) true end |
#meets_visibility?(user, helper = nil) ⇒ Boolean
Menu items evaluate visibility from two stacks: the ‘visible_when:` condition tree (record-free; supports service: and compound all/any/not — `field:`/`collection:` are rejected by the validator) AND, for `presenter:` items, the same permission gate the presenter toolbar runs (`can_access_presenter?` plus `can?(action)` for built-in CRUD or `can_execute_action?` for custom actions).
The optional ‘helper` arg lets the layout helper share its per-render `@permission_cache` so N siblings with the same presenter pay the evaluator construction cost once. When `helper` is nil (specs and any non-helper context) the method falls back to constructing a fresh evaluator per call —functionally identical, just slower.
442 443 444 445 446 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 442 def meets_visibility?(user, helper = nil) return false unless meets_condition_visibility?(user) return true unless presenter_slug.present? (user, helper) end |
#render_panel_partial_path ⇒ Object
‘<namespace>/menu_renderers/<name>` — Rails partial path for the panel renderer. Same naming convention as `render_partial_path`; the role distinguishing `render:` from `render_panel:` lives at the YAML key level.
689 690 691 692 693 694 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 689 def render_panel_partial_path return nil if render_panel_partial.blank? ns, name = render_panel_partial.split("/", 2) return nil if name.blank? "#{ns}/menu_renderers/#{name}" end |
#render_partial_path ⇒ Object
‘<namespace>/menu_renderers/<name>` — Rails partial path for the trigger renderer. Returns nil when `render:` is unset so callers can guard with `if render_partial.present?`.
678 679 680 681 682 683 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 678 def render_partial_path return nil if render_partial.blank? ns, name = render_partial.split("/", 2) return nil if name.blank? "#{ns}/menu_renderers/#{name}" end |
#resolved_aria_label(_loader = nil) ⇒ Object
Resolve the accessible name for the link/button. Required when no visible label is rendered (icon-only items). Mirrors ‘resolved_label` priority: explicit aria_label_key > literal aria_label with i18n auto-lookup. Namespace `lcp_ruby.menu.aria.<slug>` keeps it separate from labels so the same literal used in both roles does not collide.
382 383 384 385 386 387 388 389 390 391 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 382 def resolved_aria_label(_loader = nil) if @aria_label_key.present? fallback = @aria_label.is_a?(String) && @aria_label.present? ? @aria_label : @aria_label_key.split(".").last.humanize return I18n.t(@aria_label_key, default: fallback) end return nil unless @aria_label.is_a?(String) && @aria_label.present? I18n.t("lcp_ruby.menu.aria.#{@aria_label.parameterize(separator: '_')}", default: nil) || @aria_label end |
#resolved_icon(loader) ⇒ Object
Resolve icon from view group’s primary presenter when not explicitly set
394 395 396 397 398 399 400 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 394 def resolved_icon(loader) return @icon if @icon.present? return nil unless view_group? presenter = primary_presenter(loader) presenter&.icon end |
#resolved_label(loader) ⇒ Object
Resolve label with i18n support. Priority: explicit ‘label: false` short-circuit > label_key > explicit label > view_group’s primary presenter resolved_label > ‘presenter:` referenced presenter resolved_label. Returns nil when the item should render no visible label —icon-only items, separator items, or items whose underlying presenter exposes no resolvable label.
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 348 def resolved_label(loader) return nil if @label == false if @label_key.present? fallback = @label.is_a?(String) && @label.present? ? @label : @label_key.split(".").last.humanize return I18n.t(@label_key, default: fallback) end if @label.is_a?(String) && @label.present? result = I18n.t("lcp_ruby.menu.#{@label.parameterize(separator: '_')}", default: nil) return result || @label end # No `view_group_name.humanize` fallback — a view_group whose # presenter has no resolvable label is a misconfiguration that # the validator surfaces, not something we silently humanize. return primary_presenter(loader)&.resolved_label if view_group? # `presenter:` items inherit the referenced presenter's resolved # label as the fallback — keeps `validate_resolved_content!` # happy without forcing the configurator to repeat the label. if presenter_slug.present? return loader.presenter_definitions[presenter_slug]&.resolved_label end nil end |
#resolved_slug(loader) ⇒ Object
Resolve slug from view group’s primary page
403 404 405 406 407 408 409 410 411 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 403 def resolved_slug(loader) return nil unless view_group? vg = loader.view_group_definitions[view_group_name] return nil unless vg page = loader.page_definitions[vg.primary_page] page&.slug end |
#right? ⇒ Boolean
559 560 561 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 559 def right? position.to_s == POSITION_RIGHT end |
#separator? ⇒ Boolean
586 587 588 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 586 def separator? type == :separator end |
#sidebar_toggle? ⇒ Boolean
590 591 592 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 590 def type == :sidebar_toggle end |
#to_kwargs ⇒ Object
Snapshot of the keyword arguments needed to reconstruct this item via ‘MenuItem.new(**kwargs)`. Powers `#with` and keeps the rebuild path in `LayoutHelper#visible_menu_items` from drifting away from `initialize` whenever a new optional key is added. Public so provider expansion (PR #2b) can reconstruct items via `to_kwargs.merge(provider_returned_keys)`.
639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 639 def to_kwargs { type: type, view_group_name: view_group_name, label: label, label_key: label_key, icon: icon, url: url, children: children, visible_when: visible_when, disable_when: disable_when, position: position, badge: badge, aria_label: aria_label, aria_label_key: aria_label_key, http_method: http_method, presenter_slug: presenter_slug, alias_name: alias_name, action_name: action_name, defaults: defaults, render_partial: render_partial, render_panel_partial: render_panel_partial, widget: , provider_name: provider_name, provider_options: , panel_provider_name: panel_provider_name, label_source_loc: label_source_loc, aria_label_source_loc: aria_label_source_loc, responsive_priority: responsive_priority, pin: pin, hide_below: hide_below, show_below: show_below, collapse_label_below: collapse_label_below } end |
#top? ⇒ Boolean
Forward-compat predicates for ‘position: top` / `position: left`. Reserved for future sidebar alignments — no consumer paths exist today, so these always return false unless the YAML opts in.
566 567 568 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 566 def top? position.to_s == POSITION_TOP end |
#validate_resolved_content!(loader) ⇒ Object
Loader-aware boot-time validation. Called by ‘ConfigurationValidator#validate_menu_items` after the loader is fully populated so view_group fallbacks resolve. Raises with a descriptor that points at the offending entry.
598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 598 def validate_resolved_content!(loader) return if separator? # Platform-owned UI chrome — no destination, no privilege # escalation surface, stable aria_label. return if # widget: items drop the wrapper entirely — the render: partial # owns all visible content + accessibility semantics. The # validator can't introspect the partial's HTML. return if # provider: items are computed at render time — the provider # supplies label/icon/url in its return hash. Static YAML # content is optional (and usually empty); the validator # cannot introspect provider returns at boot. return if provider_name.present? content = visible_content(loader) # render: items provide visible content via the renderer # partial; label/icon are not required. aria_label/aria_label_key # is still strongly recommended (the renderer's HTML may not # include accessible text); enforce when neither label nor # aria_label is set. if content.empty? && render_partial.blank? raise MetadataError, "Menu item #{descriptor} has no visible content (label or icon)" end # Items with no visible label (icon-only OR render: producing # opaque HTML) need an explicit aria_label/aria_label_key. We # cannot introspect a render: partial's accessible text, so we # require the aria attribute regardless when no YAML label resolves. if !content.include?(:label) && @aria_label.blank? && @aria_label_key.blank? raise MetadataError, "Icon-only menu item #{descriptor} requires `aria_label` or `aria_label_key`" end end |
#view_group? ⇒ Boolean
574 575 576 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 574 def view_group? type == :view_group end |
#widget? ⇒ Boolean
Predicate sugar — clearer at call sites than ‘widget == true`.
697 698 699 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 697 def @widget == true end |
#with(**overrides) ⇒ Object
Returns a copy of this item with selected attributes overridden. Used by ‘LayoutHelper#visible_menu_items` to rebuild a `:group` item with filtered children without re-enumerating every attribute by hand.
100 101 102 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 100 def with(**overrides) self.class.new(**to_kwargs.merge(overrides)) end |