npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@dotglitch/ngx-ctx-menu

v0.2.36

Published

Angular context menu that works with templates

Downloads

20

Readme

npm npm npm downloads GitHub stars

Quickstart

Demo

Install

$ npm install @dotglitch/ngx-ctx-menu

Import the Module

import { NgModule } from '@angular/core';
import { NgxAppMenuDirective, NgxContextMenuDirective } from '@dotglitch/ngx-ctx-menu';
import { AppComponent } from './app.component';

@NgModule({
    declarations: [
        AppComponent,
    ],
    imports: [
        NgxAppMenuDirective,
        NgxContextMenuDirective,
        ...
    ]
    bootstrap: [AppComponent]
})
export class AppModule { }

Basic Usage

Context menus show up when a user right-clicks a specific element in a given application. App menus are menus that show up on buttons when you click them. The support provided by this package is for convenience of feature support, allowing you to make context menus that also appear when buttons are pressed, which is very useful for mobile development or in cases where complex button menus are required.

component.html

<ul>
    <!-- Bind the context menu to the list item -->
    <li *ngFor="let item of items" [ngx-ctx-menu]="actionMenu" [ngx-ctx-menu-context]="item">
        
        <!-- Bind the same menu to a button that can easily be clicked on mobile -->
        <button mat-icon-button [ngx-app-menu]="actionMenu" [ngx-app-menu-context]="item">
            <mat-icon>more_vert</mat-icon>
        </button>

        <div>
            Item: {{item}}
        </div>
    </li>
</ul>

component.ts

import { Component } from '@angular/core';
import { NgForOf } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { ContextMenuItem, NgxAppMenuDirective, NgxContextMenuDirective } from '@dotglitch/ngx-ctx-menu';

@Component({
    selector: 'app-tag-picker',
    templateUrl: './tag-picker.component.html',
    styleUrls: ['./tag-picker.component.scss'],
    imports: [
        NgForOf,
        MatButtonModule,
        NgxAppMenuDirective,
        NgxContextMenuDirective
    ],
    standalone: true
})
export class SimpleComponent {

    // For the sake of this example, this has no action handlers.
    actionMenu: ContextMenuItem<MyDataObject>[] = [
        {
            label: "Duplicate",
            icon: "add"
        },
        {
            label: "Edit",
            icon: "edit"
        },
        "separator",
        {
            label: "Delete",
            icon: "delete_outline"
        },
    ];

    items = [
        "item1",
        "item2",
        "item3",
        "item4",
        "item5"
    ]
}

Advanced usage

component.html

<img src="/foo/bar.png" [ngx-ctx-menu]="actionMenu" [ngx-ctx-menu-context]="someArbitraryValue">

<ng-template #iconSelectTemplate let-data="data">
    <!-- 
        We need to pass data.data and data.dialog through so that the child component
        has all of the references it needs.
     -->
    <app-icon-picker [commonMatIcons]="commonMatIcons" [data]="data.data" [dialog]="data.dialog"/>
</ng-template>

component.ts

import { Component, Input, OnInit, EventEmitter, Output } from '@angular/core';
import { ContextMenuItem, NgxAppMenuDirective, NgxContextMenuDirective } from '@dotglitch/ngx-ctx-menu';
import { MatIconModule } from '@angular/material/icon';
import { CommonModule } from '@angular/common';
import { MatDialogRef } from '@angular/material/dialog';
import { MatButtonModule } from '@angular/material/button';
import { MatInputModule } from '@angular/material/input';

type MyDataObject = {
    name: string,
    value: number,
    color: string
}

@Component({
    selector: 'app-raci',
    templateUrl: 'component.html',
    imports: [
        IconPickerComponent,
        NgxContextMenuDirective
    ],
    // This library works with both standalone components and components declared on modules.
    // Just import the Directive where you need to use it.
    standalone: true
})
export class MyComponent {
    @ViewChild('iconSelectTemplate', { read: TemplateRef }) iconSelectTemplate: TemplateRef<any>;

    readonly commonMatIcons = [
        "warning",       "new_releases",      "priority_high",   "flag",            "bug_report",        "star_rate",
        "sync",          "architecture",      "design_services", "polyline",        "view_quilt",        "emoji_events",
        "bookmark",      "workspace_premium", "person",          "groups",          "menu_book",         "library_books",
        "sticky_note_2", "receipt_long",      "settings",        "tune",            "settings_ethernet", "security",
        "privacy_tip",   "lock",              "fact_check",      "event_available", "newspaper",         "schema",
    ];
    
    data: MyDataObject[] = [{
        name: "foobar",
        value: 800,
        color: "red"
    },{
        name: "lemons",
        value: 9000,
        color: "yellow"
    }]

    cellCtxMenu: ContextMenuItem<MyDataObject>[];

    ngAfterViewInit() {
        // We attach this after the view init hook to ensure the template is bound.
        // If you have a truly simple ctx menu, assigning it directly in the class 
        // works perfectly fine.
        this.cellCtxMenu = [
            // First, a simple entry that is always visible.
            {
                icon: "edit",
                label: "Edit",
                // Action is invoked when the menu item is clicked.
                action: (data) => {
                    this.data.splice(this.data.findIndex(d => d.name == data.name), 1);
                }
            },
            // Now, let's customize the label and when it's shown.
            {
                icon: "trash",
                // Only show the entry if the name isn't blank.
                isVisible: (entry) => entry.name?.length > 1,
                labelTemplate: (entry) => `Delete ${user?.name}`,
                action: (data) => {
                    this.data.splice(this.data.findIndex(d => d.name == data.name), 1);
                }
            },
            // Last, we're attaching a context menu item that opens up an angular template.
            {
                label: "Icon",
                icon: "category",
                childTemplate: this.iconSelectTemplate,
                // Action is invoked when the template dialog closes
                action: (data) => this.updateEventTag(data)
            },
        ];
    }
}


@Component({
    selector: 'app-icon-picker',
    template: `
<div class="icon-container">
    <button mat-icon-button *ngFor="let icon of commonMatIcons" (click)="setIcon(icon)">
        <!-- [class.active]="data.tags." -->
        <mat-icon [fontIcon]="icon" [style.color]="icon == currentIcon ? '#fff' : ''"></mat-icon>
    </button>
</div>

<hr/>

<div>
    <div style="padding: 0 24px">
        Specify a <a href="https://fonts.google.com/icons?icon.set=Material+Icons&icon.style=Filled" target="_blank">material icon</a> id, such as 'schedule_send'
    </div>

    <mat-form-field style="margin: 12px 24px 24px 24px; width: -webkit-fill-available;">
        <mat-label>Custom</mat-label>
        <input #input matInput type="text" />
    </mat-form-field>

    <button mat-icon-button class="custom-btn" (click)="setIcon(input.value?.toLowerCase())"><mat-icon>check</mat-icon></button>
</div>
    `,
    imports: [
        CommonModule,
        MatIconModule,
        MatButtonModule,
        MatInputModule
    ],
    standalone: true
})
export class IconPickerComponent implements OnInit {

    @Input() commonMatIcons: string[] = [];
    @Input() data: MyDataObject;
    @Input() dialog: MatDialogRef<any>;

    currentIcon;

    ngOnInit() {
        this.currentIcon = this.data.tags?.find(t => t.name == "icon").value;
    }

    async setIcon(classes: string) {
        axios.put(`/api/data/${this.data.id}`, {
            name: "icon",
            value: classes
        })

        if (!this.data.tags) this.data.tags = [];

        let old = this.data.tags.findIndex(t => t.name == "icon");
        if (old >= 0)
            this.data.tags.splice(old, 1);

        this.data.tags.push({
            name: "icon",
            value: classes
        });

        this.dialog.close(this.data);
    }
}

Configuration

You can set configuration options on the App menu for where it should pop-up relative to the parent element.

<button mat-button [ngx-app-menu]="createMenu" [ngx-app-menu-config]="{ position: 'bottom', alignment: 'center' }">
    <mat-icon>add</mat-icon>
    New Item
</button>
export type NgxAppMenuTriggers = "click" | "dblclick" | "hover";

export type NgxAppMenuOptions = Partial<{
    /**
     * Position relative to the element the menu pops-up at
     * @default 'right'
     */
    position: "top" | "right" | "bottom" | "left",
    /**
     * How the popup is aligned relative to the element
     * @default 'center'
     */
    alignment: "center" | "beforestart" | "start" | "end" | "afterend",
    /**
     * How much padding to add near the edges of the screen.
     */
    edgePadding: number,
    /**
     * Which event should trigger the app menu
     * @default 'click'
     */
    trigger: NgxAppMenuTriggers | NgxAppMenuTriggers[];
}>;

Styling

Custom styling is still a work-in-progress.

// You can use these CSS variables to configure colors
--ngx-ctx-menu-background-color: #000000;
--ngx-ctx-menu-text-color: #000000;
--ngx-ctx-menu-hover-background-color: #000000;
--ngx-ctx-menu-hover-text-color: #000000;
--ngx-ctx-menu-disabled-text-color: #000000;
--ngx-ctx-menu-separator-color: #000000;
--ngx-ctx-menu-shortcut-text-color: #000000;

Debugging

The menu isn't bound

  • Did you import the module?
  • Does it have any entries? Are you sure?
  • Do you have a global contextmenu handler that's interrupting events?

Roadmap

  • [*] Basic menu template support
  • [ ] Support for better styling
  • [ ] Support for templates without relying on @ViewChild
  • [ ] Add more trigger events for app menu
  • [ ] Better ARIA support and configuration
  • [ ] Support dynamic entries
  • [ ] Enable sharing menus and combining menu chunks