Skip to content

Conversation

veryboringhwl
Copy link
Contributor

@veryboringhwl veryboringhwl commented Sep 13, 2025

Used to be hardcoded to 1 causing the nested submenu to disappear when hovered.

Allows this to work

Screenshot 2025-09-13 213055

To use add the depth and then 1+ the previous

const test1 = new Spicetify.ContextMenuV2.Item({
  children: "test1",
  onClick: async () => {
    console.log("test1");
  },
  shouldAdd: () => true,
});

const NestedSubMenu = new Spicetify.ContextMenuV2.ItemSubMenu({
  text: "NestedSubMenu",
  depth: 2,
  items: [test1],
  shouldAdd: () => true,
});

const SubMenu = new Spicetify.ContextMenuV2.ItemSubMenu({
  text: "SubMenu",
  // depth: 1, This is the default
  items: [
    NestedSubMenu,
  ],
  shouldAdd: () => true,
}).register();

Summary by CodeRabbit

  • New Features
    • Added support for multi-level nested submenus in context menus.
    • Submenus now display their correct nesting level dynamically.
    • Enables deeper, more organized navigation for complex actions.
    • Improves clarity and discoverability within context menus.

Copy link

coderabbitai bot commented Sep 13, 2025

Walkthrough

Introduces a configurable depth parameter in ItemSubMenu to support multi-level nested context menu submenus. Stores depth in component state and passes it through to MenuSubMenuItem during render, replacing a previously hard-coded depth value.

Changes

Cohort / File(s) Summary
Context menu submenu nesting
jsHelper/spicetifyWrapper.js
Added depth parameter (default 1) to ItemSubMenu constructor; stored as _depth with setter _setDepth. Render path now passes depth: _depth to MenuSubMenuItem instead of a fixed value. Public signature updated accordingly.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Caller as Caller
  participant CM as ContextMenuV2
  participant ISM as ItemSubMenu (React)
  participant MSI as MenuSubMenuItem

  Caller->>CM: new ItemSubMenu({ text, items, depth })
  CM->>ISM: construct with depth (default 1)
  note right of ISM: Store depth in state: _depth
  ISM->>MSI: render({ text, items, depth: _depth })
  MSI-->>Caller: submenu rendered with correct nesting level
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

I burrow through menus, hop by hop,
Each nested nibble, a carrot on top. 🥕
Depths unfold like tunnels I know,
Click by click, the submenus grow—
A warren of options, ready to go!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "feat(wrapper): allow nesting submenus" is a concise, single-sentence summary that directly reflects the primary change—making submenu depth configurable so nested context-menu submenus remain visible—and maps to the ItemSubMenu depth parameter and render updates described in the PR. It follows conventional commit style, avoids irrelevant detail, and lets a reviewer scanning history understand the main intent.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
jsHelper/spicetifyWrapper.js (2)

1819-1823: Bug: incorrect setter used for leadingIcon (no reactive updates).

this._setIcon is undefined in this class; should call this._setLeadingIcon.

 set leadingIcon(name) {
   this._leadingIcon = name;
-  this._setIcon?.(this._leadingIcon);
+  this._setLeadingIcon?.(this._leadingIcon);
 }

1748-1750: Support Sets when rendering submenu items.

items is manipulated with .add/.delete elsewhere, implying a Set. .filter on a Set throws. Convert to array first.

- static itemsToComponents = (items, props, trigger, target) => {
-   return items.filter((item) => (item.shouldAdd || (() => true))?.(props, trigger, target)).map((item) => item._element);
- };
+ static itemsToComponents = (items, props, trigger, target) => {
+   const list = Array.isArray(items) ? items : Array.from(items ?? []);
+   return list
+     .filter((item) => (item?.shouldAdd || (() => true))?.(props, trigger, target))
+     .map((item) => item._element);
+ };
🧹 Nitpick comments (4)
jsHelper/spicetifyWrapper.js (4)

1752-1761: Clamp and sanitize the new depth input.

Guard against 0, negatives, floats, or non-numbers to avoid undefined UI behavior.

- constructor({ text, disabled = false, leadingIcon, divider, items, depth = 1, shouldAdd = () => true }) {
+ constructor({ text, disabled = false, leadingIcon, divider, items, depth = 1, shouldAdd = () => true }) {
   this.shouldAdd = shouldAdd;
   this._text = text;
   this._disabled = disabled;
   this._leadingIcon = leadingIcon;
   this._divider = divider;
   this._items = items;
-  this._depth = depth;
+  // Sanitize depth: integer >= 1
+  const d = Number.isFinite(depth) ? Math.floor(depth) : 1;
+  this._depth = Math.max(1, d);

1767-1768: Depth in React state: either expose a setter or keep it as a plain prop.

If depth won’t change post‑construction, you can drop state and pass this._depth directly; otherwise, expose a setter to reactively update it. Pick one to reduce complexity.

Proposed setter (outside changed hunk, add near other setters):

set depth(value) {
  const d = Number.isFinite(value) ? Math.floor(value) : 1;
  this._depth = Math.max(1, d);
  this._setDepth?.(this._depth);
}
get depth() {
  return this._depth;
}

Also applies to: 1775-1776, 1782-1783


1959-1969: Expose depth via wrapper constructors for convenience.

ContextMenu.SubMenu can’t set depth currently; consumers must drop to ItemSubMenu. Consider adding an optional depth arg and forwarding it.

- constructor(name, items, shouldAdd, disabled = false, icon = undefined) {
+ constructor(name, items, shouldAdd, disabled = false, icon = undefined, depth = 1) {
   super({
     text: name,
     disabled,
     leadingIcon: icon,
     items,
+    depth,
     shouldAdd: (props) => {
       const parsedProps = Spicetify.ContextMenuV2.parseProps(props);
       return parsedProps && shouldAdd(...parsedProps);
     },
   });
 }

1895-1899: Mirror the depth option in Menu.SubMenu as well.

Parity between wrappers avoids API surprises.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 40c2df3 and dc2738b.

📒 Files selected for processing (1)
  • jsHelper/spicetifyWrapper.js (2 hunks)
🔇 Additional comments (1)
jsHelper/spicetifyWrapper.js (1)

1792-1793: Verify MenuSubMenuItem actually consumes depth.

Sanity‑check in-app that hover/focus behavior stays correct for depth >= 2 and that positioning (placement right-start) doesn’t regress.

Copy link
Member

@rxri rxri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't the depth be automatically calculated tho?

@rxri rxri closed this in 6f82fa9 Sep 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants