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
- KNOWN_KEYS =
Every key the MenuItem parser knows about. Anything outside this set is rejected at boot — the schema also has ‘additionalProperties: false` on menu_item but that runs only via `lcp_ruby:validate`, not at `rails s` time. Parse-time rejection turns silent typos (`section: “Marketplace”` instead of `label:`) into immediate boot errors rather than icon-only menu items the configurator hunts for an hour. Keys starting with `_` are reserved for internal DSL annotations (`_label_source_loc`, `_aria_label_source_loc`) and allowed without enumeration. NOTE: `type` is intentionally NOT here. It’s an internal kwarg (derived from the destination key, round-tripped via ‘to_kwargs` →`new` at the Ruby level, never read from author YAML). Listing it would let `type:` pass this parse-time check while the JSON schema (`additionalProperties: false`, no `type` property) rejects it under `lcp_ruby:validate` — two different verdicts for the same file.
%w[ url presenter view_group provider children separator sidebar_toggle widget method alias action defaults render render_panel panel_provider options label label_key icon aria_label aria_label_key visible_when disable_when position badge responsive_priority pin hide_below show_below collapse_label_below _label_source_loc _aria_label_source_loc ].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. `type` is NOT an accepted key here — reject_unknown_keys! (in the shared build_from_hash) rejects it, matching the JSON schema, which has no `type` property. Type is always re-derived from the destination keys present. Provider returns that echo a `type:` reach build_from_hash via `from_hash_at_depth` (not this method), but LayoutHelper#filter_provider_keys strips it upstream (PROVIDER_STRIP_KEYS) so the round-trip still works.
114 115 116 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 114 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.
124 125 126 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 124 def self.from_hash_at_depth(hash, depth) build_from_hash(hash, depth) end |
Instance Method Details
#badge_form ⇒ Object
578 579 580 581 582 583 584 585 586 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 578 def badge_form if badge&.dig("renderer") :renderer elsif badge&.dig("partial") :partial elsif badge&.dig("template") :template end end |
#badge_options ⇒ Object
600 601 602 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 600 def badge&.dig("options") || {} end |
#badge_partial ⇒ Object
592 593 594 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 592 def badge_partial badge&.dig("partial") end |
#badge_provider ⇒ Object
570 571 572 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 570 def badge_provider badge&.dig("provider") end |
#badge_renderer ⇒ Object
588 589 590 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 588 def badge_renderer badge&.dig("renderer") end |
#badge_template ⇒ Object
596 597 598 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 596 def badge_template badge&.dig("template") end |
#bottom? ⇒ Boolean
604 605 606 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 604 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
463 464 465 466 467 468 469 470 471 472 473 474 475 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 463 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
631 632 633 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 631 def group? type == :group end |
#has_badge? ⇒ Boolean
574 575 576 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 574 def has_badge? badge_provider.present? end |
#left? ⇒ Boolean
619 620 621 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 619 def left? position.to_s == POSITION_LEFT end |
#link? ⇒ Boolean
627 628 629 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 627 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.
508 509 510 511 512 513 514 515 516 517 518 519 520 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 508 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.
491 492 493 494 495 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 491 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.
738 739 740 741 742 743 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 738 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?`.
727 728 729 730 731 732 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 727 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.
431 432 433 434 435 436 437 438 439 440 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 431 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
443 444 445 446 447 448 449 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 443 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.
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 397 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
452 453 454 455 456 457 458 459 460 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 452 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
608 609 610 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 608 def right? position.to_s == POSITION_RIGHT end |
#separator? ⇒ Boolean
635 636 637 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 635 def separator? type == :separator end |
#sidebar_toggle? ⇒ Boolean
639 640 641 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 639 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)`.
688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 688 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.
615 616 617 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 615 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.
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 674 675 676 677 678 679 680 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 647 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
623 624 625 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 623 def view_group? type == :view_group end |
#widget? ⇒ Boolean
Predicate sugar — clearer at call sites than ‘widget == true`.
746 747 748 |
# File 'lib/lcp_ruby/metadata/menu_item.rb', line 746 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 |