Chandra

 view release on metacpan or  search on metacpan

xs/contextmenu.xs  view on Meta::CPAN

SV *
js_code(self)
    SV *self
CODE:
{
    HV *hv = (HV *)SvRV(self);
    SV **att_svp = hv_fetchs(hv, "_attachments", 0);
    SV **items_svp = hv_fetchs(hv, "_items", 0);
    SV **global_svp = hv_fetchs(hv, "_global", 0);
    HV *att_hv = (HV *)SvRV(*att_svp);
    AV *items_av = (AV *)SvRV(*items_svp);
    int is_global = (global_svp && SvTRUE(*global_svp));
    SV *js;

    js = newSVpvs(
        "(function(){\n"
        "if(window.__chandraCtxMenu)return;\n"
        "window.__chandraCtxMenu=1;\n"
        "var sels=["
    );

    /* Emit selectors */
    {
        HE *entry;
        int first = 1;
        hv_iterinit(att_hv);
        while ((entry = hv_iternext(att_hv)) != NULL) {
            I32 klen;
            const char *key = hv_iterkey(entry, &klen);
            if (!first) sv_catpvs(js, ",");
            sv_catpvs(js, "'");
            sv_catpvn(js, key, klen);
            sv_catpvs(js, "'");
            first = 0;
        }
    }
    sv_catpvs(js, "];\n");

    /* Items JSON */
    sv_catpvs(js, "var items=");
    _cm_items_to_js(aTHX_ js, items_av);
    sv_catpvs(js, ";\n");

    /* Global flag */
    sv_catpvf(js, "var isGlobal=%d;\n", is_global);

    /* CSS for menu */
    sv_catpvs(js,
        "var style=document.createElement('style');\n"
        "style.textContent='"
        ".chandra-ctx-menu{"
            "position:fixed;z-index:999999;min-width:160px;"
            "background:#fff;border:1px solid #ccc;border-radius:6px;"
            "box-shadow:0 4px 16px rgba(0,0,0,.18);padding:4px 0;"
            "font:13px/1.4 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;"
            "color:#222;user-select:none;opacity:0;transform:scale(.96);"
            "transition:opacity .12s,transform .12s;"
        "}"
        ".chandra-ctx-menu.show{opacity:1;transform:scale(1);}"
        ".chandra-ctx-item{"
            "padding:6px 32px 6px 28px;cursor:pointer;white-space:nowrap;"
            "display:flex;align-items:center;position:relative;"
        "}"
        ".chandra-ctx-item:hover{background:#e8f0fe;}"
        ".chandra-ctx-item.disabled{color:#999;pointer-events:none;}"
        ".chandra-ctx-sep{height:1px;background:#e0e0e0;margin:4px 8px;}"
        ".chandra-ctx-icon{width:20px;margin-right:6px;text-align:center;}"
        ".chandra-ctx-sc{margin-left:auto;padding-left:24px;color:#888;font-size:12px;}"
        ".chandra-ctx-check{position:absolute;left:8px;}"
        ".chandra-ctx-sub-arrow{margin-left:auto;padding-left:16px;color:#888;}"
        ".chandra-ctx-sub{position:fixed;}"
        "@media(prefers-color-scheme:dark){"
            ".chandra-ctx-menu{background:#2d2d2d;border-color:#555;color:#e0e0e0;}"
            ".chandra-ctx-item:hover{background:#404040;}"
            ".chandra-ctx-sep{background:#555;}"
            ".chandra-ctx-sc,.chandra-ctx-sub-arrow{color:#888;}"
            ".chandra-ctx-item.disabled{color:#666;}"
        "}"
        "';\n"
        "document.head.appendChild(style);\n"
    );

    /* Menu rendering and event handling JS */
    sv_catpvs(js,
        "var openMenus=[];\n"

        "function closeAll(){"
            "openMenus.forEach(function(m){m.remove()});"
            "openMenus=[];"
        "}\n"

        "function buildMenu(items,x,y,depth){"
            "var m=document.createElement('div');"
            "m.className='chandra-ctx-menu';"

            "items.forEach(function(it){"
                "if(it.sep){"
                    "var s=document.createElement('div');"
                    "s.className='chandra-ctx-sep';"
                    "m.appendChild(s);return;"
                "}"
                "var row=document.createElement('div');"
                "row.className='chandra-ctx-item'+(it.dis?' disabled':'');"

                /* Check mark */
                "if(it.chk){"
                    "var ck=document.createElement('span');"
                    "ck.className='chandra-ctx-check';"
                    "ck.textContent=it.ckd?'\\u2713':'';"
                    "row.appendChild(ck);"
                "}"

                /* Icon */
                "if(it.ico){"
                    "var ic=document.createElement('span');"
                    "ic.className='chandra-ctx-icon';"
                    "ic.textContent=it.ico;"
                    "row.appendChild(ic);"
                "}"

                /* Label */



( run in 0.586 second using v1.01-cache-2.11-cpan-140bd7fdf52 )