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

@mind-wired/core

v0.2.0

Published

Javascript based mindmap library. It provides dragging, aligment, styling(node, edge) and editing functionality.

Downloads

68

Readme

MindWired

mind-wired is javascript library to build mindmap.

1. installing

npm install @mind-wired/[email protected]

2. Client type

2.1. Javascript modules(Typescript)

The example code in this document was generated using Vite(Vanilla + TS).

[PROJECT]
  +- assets
  +- src
      +- api.ts
      +- main.ts
  +- index.html

index.html

The library needs a placeholder for mindmap

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>MindWired Demo</title>
  </head>
  <body>
    <div id="mmap-root"><!-- viewport generated here--></div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>
  • #mmap-root - placeholder for mindmap(You can name it freely)

main.ts

It is a minimal initialization code for an instance of mind-wired

/* src/main.ts */
import type { MindWired, NodeSpec, UISetting } from "@mind-wired/core";
import { initMindWired } from "@mind-wired/core";
import "@mind-wired/core/mind-wired.css";
import "@mind-wired/core/mind-wired-form.scss";
import { loadFromServer } from "./api";

window.onload = async () => {
  const mapData: { node: NodeSpec } = await loadFromServer();
  const el = document.querySelector<HTMLDivElement>("#mmap-root")!;
  const mwd: MindWired = await initMindWired({
    el,
    ui: {
      width: "100%",
      height: 500,
    } as UISetting,
  });
  mwd.nodes(mapData.node);
};
  • initMindWired({el, ui}) - called to initialize mindmap.
  • el - placeholder for mindmap.
  • ui - size, scale, class namings and snap layout etc.
  • @mind-wired/core/mind-wired.css - minimal css style for mindmap. You can add or modify style for your own css(scss). See section 3. Style
  • @mind-wired/core/mind-wired-form.scss - style for default editing form.
  • loadFromServer - fetch mindmap data(nodes, ui, and schema) from your server

You might fetch mindmap data from server like this.

/* src/api.ts */
export const loadFromServer = (): Promise<{
  node: NodeSpec;
}> => {
  // using axis(...) or fetch(...) in production code
  return Promise.resolve({
    node: {
      model: { text: "Countries\nand\nCities" },
      view: {
        x: 0,
        y: 0,
      },
      subs: [
        {
          model: { text: "Canada" },
          view: { x: -100, y: 0 },
          subs: [
            { model: { text: "Toronto" }, view: { x: -90, y: 0 } },
            { model: { text: "Quebec City" }, view: { x: -10, y: -40 } },
          ],
        },
        {
          model: { text: "Spain" },
          view: { x: 100, y: 0 },
          subs: [
            { model: { text: "Madrid" }, view: { x: 90, y: 90 } },
            { model: { text: "Barcelona" }, view: { x: 100, y: 0 } },
            { model: { text: "Valencia" }, view: { x: 90, y: 45 } },
          ],
        },
      ],
    },
  });
};
  • root node is positioned at the center of viewport view: {x:0, y:0}

NodeSpec has three key properties

  • model - data of node(plain text, icon badge, or thumbnail)
  • view - relative offset (x, y) from it's direct parent node
  • subs - direct child nodes, which are also type of NodeSpec[].

For examples,

  • Node Spain(100, 0) is positioned to the right of the root node.
  • Three cities of Madrid, Barcelona, Valencia are also positioned to the right of the parent node Spain

2.2. Svelte

2.3. Vue

2.4. UMD

3. Style

mind-wired generates base structure.

<div id="mmap-root">
  <!-- generated automatically by mind-wired -->
  <div data-mind-wired-viewport>
    <canvas></canvas>
    <div class="mwd-selection-area"></div>
    <div class="mwd-nodes"></div>
  </div>
</div>
  • [data-mind-wired-viewport] - reserved data attribute meaning root element of mindmap
  • <canvas></canvas> - placeholer for edges
  • .mwd-selection-area - used to highlight selected nodes
  • .mwd-nodes - placeholder for node structure

3.1 Style file

To define your node styles, create a css(scss) file

[PROJECT]
  +- assets
      +- mindmap.css (+)
  +- src
      +- main.ts
  +- index.html
  • assets/mindmap.css - you can name it as you want

Then, import the (s)css file

/* /src/main.ts */
...
import "@mind-wired/core/mind-wired.css";
import "@mind-wired/core/mind-wired-form.scss";
...
import "./assets/mindmap.css"

window.onload = async () => {
  ...
};

3.2. Snap to node

MindWired supports snap to node, which helps node alignment while dragging.

initinitMindWired({
  el,
  ui: {
    ...
    snap: {           # optional
      limit: 4,       # within 4 pixels
      width: 0.4,     # snap line width
      dash: [6, 2],   # dashed line style
      color: "red",   # line color
    },
})
  • Snap guide lines are displayed when a node is whithin 4 pixels to the adjacent nodes.
  • Lines are rendered on <canvas/>

You can disable it by setting false

initinitMindWired({
  el,
  ui: {
    ...
    snap: false,
})
// or
  ui: {
    snap: {           # optional
      limit: 4,       # within 4 pixels
      width: 0.4,     # snap line width
      dash: [6, 2],   # dashed line style
      color: "red",   # line color
      enabled: false  # disable snap
    },
  }

3.3. Node Style

All nodes are placed in the .mwd-nodes with tree structure(recursively)

<div id="mmap-root">
  <div data-mind-wired-viewport>
    ...
    <div class="mwd-nodes">
      <!-- root node -->
      <div class="mwd-node">
        <div class="mwd-body"></div>
        <div class="mwd-subs">
          <!--child nodes -->
          <div class="mwd-node">..Canada..</div>
          <div class="mwd-node">..Spain..</div>
        </div>
      </div>
    </div>
  </div>
</div>

3.3.1. Level

Each node is assigned level number, 0 for root node, 1 for sub nodes of the root.

[TOP]
  +- [Left]
  |
  +- [Right]
        |
        +--[Cat]
  • Root node TOP - class="level-0"
  • Node Left - class="level-1"
  • Node Right - class="level-1"
  • Node Cat - class="level-2"
<div class="mwd-nodes">
  <div class="mwd-node">
    <div class="mwd-body level-0">..TOP..</div>
    <div class="mwd-subs">
      <div class="mwd-node">
        <div class="mwd-body level-1">..LEFT..</div>
      </div>
      <div class="mwd-node">
        <div class="mwd-body level-1">..RIGHT..</div>
        <div class="mwd-subs">
          <div class="mwd-node">
            <div class="mwd-body level-2">..Cat..</div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
  • level classname(level-x) is attached at .mwd-body
  • level number changes whenever depth of node changes(except root node)

For example, here is css to assign rounded border with bigger text to root node,

/* assets/mindmap.css */
[data-mind-wired-viewport] .mwd-body.level-0 {
  border: 1px solid #444;
  border-radius: 8px;
  font-size: 1.5rem;
}
  • be sure to keep .node-body together to override default css style

Style for level-1(Left, Right)

/* assets/mindmap.css */
[data-mind-wired-viewport] .mwd-body.level-1 {
  color: 'red'
  font-size: 1.25rem;
}

3.3.2. Schema

A group of nodes(Canada, Spain) need to have same style(border, background and font style etc) regardless of level.

Schema can be specified in each node

{
  node: {
    model: { text: "Countries\nand\nCities" },
    view: {...},
    subs: [
      {
        model: { text: "Canada", schema: 'country' },
        view: {...},
        subs: [...],
      },
      {
        model: { text: "Spain", schema: 'country' },
        view: {...},
        subs: [...],
      },
    ],
  },
}

// schemas
const schema = [{
  name: 'country',
  style: { // optional
    fontSize: '1rem',
    border: '1px solid #2323FF',
    color: '#2323FF',
    borderRadius: '6px'
  }
}]
  • path - model.schema in each NodeSpec
  • type: string

If you have your schema, pass them to initMindWired

const yourSchemap: [...]
initMindWired({ el: mapEl!, ui, schema: yourSchema })
  .then((mwd: MindWired) => { ... });

It is rendered as class value

<div class="mwd-nodes">
  <div class="mwd-node">
    <div class="mwd-body level-0">..Countries...</div>
    <div class="mwd-subs">
      <div class="mwd-node country">
        <div class="mwd-body country level-1">..Canada..</div>
      </div>
      <div class="mwd-node country">
        <div class="mwd-body country level-1">..Span..</div>
        ...
      </div>
    </div>
  </div>
</div>
  • class city(schema) is assigned at .mwd-node and .mwd-body
  • <style>...</style> for schemas are injected into <head/>

Dynamic Schema Style

If schema has property style defined, <style>...</style> for each schema is created in <head/>

<!-- automatically created from property schema.style -->
<head>
  <style id="...">
    [data-mind-wired-viewport] .mwd-node.country > .mwd-body {
      font-size: 1rem;
      border: 1px dashed #2323ff;
      color: #2323ff;
      border-radius: 6px;
    }
  </style>
</head>

Static Schema Style

You can define style for schema without property style.

const yourSchema = [
  {name: 'coutnry', style: {...}},
  {name: 'city'}
]
  • schema city has no style.

Nodes with schema city can be styled like this

/* assets/mindmap.css */
[data-mind-wired-viewport] .mwd-node.city > .mwd-body.city {
  color: #777;
  box-shadow: 0 0 8px #0000002d, 0 0 2px #00000041;
}

3.3.3. Style per node

You can define separate CSS styles for each node, which add or override styles defined by level and schema.

return Promise.resolve({
  node: {
    model: { text: "Countries\nand\nCities" },
    view: {...},
    subs: [
      {
        model: { text: "Canada", schema: 'country'},
        ...
      },
      {
        model: { text: "Spain", schema: 'country' },
        view: { x: 100, y: 0,
          style: {
            backgroundColor: '#9a7baf',
            color: 'white',
            border:'none'
          }
        },
      },
      {
        model: { text: "South Korea", schema: 'country' },
        ...
      },
    ],
  },
  schema : [{
    name: 'country',
    style: {...}
    }]
});
  • Three countries share schema country.
  • Spain has additional style at view.style, which override background(#9a7baf), font color(white) and border(none)

3.4. EdgeSpec

Edges are rendered on <canvas/>

node: {
  model { ... },
  view: {
    x: ...,
    y: ...,
    layout: ...,
    edge: {
      name: 'line',  # name of edge renderer
      color: 'blue', # edge color
      width: 4       # edge width
    }
  }
}
  • path : view.edge of NodeSpec
  • 4 edge styles(line, natural_curve, mustache_lr and mustache_tb) are available.
  • All nodes inherite edge style from it's ancenstors

For example, mustache_lr edge on the root node

export const loadFromServer = () => {
  return Promise.resolve({
    node: {
      model: { text: "Countries\nand\nCities" },
      view: {
        x: 0,
        y: 0,
        edge: { name: "mustache_lr", color: "#2378ff", width: 2 },
      },
      subs: [...],
    },
  });
};
  • path : view.edge (typeof EdgeSpec)
  • color - keyword defined in css color keywords or web color (ex #acdeff)
  • All descendant nodes inherite EdgeSpec from the root node, if they has no one.

3.4.1. Edge preview

1. line

      // for line edge
      view: {
        x: ..., y: ...,
        edge: {
          name: 'line',
          color: ...,
          width: ...
        }
      }

2. mustache_lr (bottom)

      // for mustach_lr bottom edge
      view: {
        x: ...,
        y: ...,
        edge: {
          name: 'mustache_lr',
          option: {
            valign: "bottom",
          },
          color: ...,
          width: ...
        }
      }

3. mustache_lr(center)

      // for mustach_lr center edge
      view: {
        x: ...,
        y: ...,
        edge: {
          name: 'mustache_lr',
          // option: {
          //   valign: "center",
          // },
          color: ...,
          width: ...
        }
      }
  • center is default

4. mustache_tb

      // for mustach_lr center edge
      view: {
        x: ...,
        y: ...,
        edge: {
          name: 'mustache_tb',
          color: ...,
          width: ...
        }
      }

5. natural_curve

      // for natural_curve center edge
      view: {
        x: ...,
        y: ...,
        edge: {
          name: 'natural_curve',
          color: ...,
          width: ...
        }
      }

4. Layout

When you drag node Right to the left side of the root node, child nodes cat and Dog keep their side, which results in annoying troublesome(have to move all sub nodes to the left of the parent Right).

should move the child nodes

Layout can help moving all descendant nodes to the opposite side when a node moves.

4 layouts are predefined.

  • X-AXIS
  • Y-AXIS
  • XY-AXIS
  • DEFAULT

4.1. X-AXIS

               [A]
                |
         [B]    |    [B`]
  [C] [D]       |       [D`] [C`]
  • If node B moves to the opposite side B', node C, D also moves to D', C'

Let's install X-AXIS on the root node

export const loadFromServer = () => {
  return Promise.resolve({
    node: {
      model: { text: "Countries\nand\nCities" },
      view: {
        x: 0,
        y: 0,
        edge: {...},
        layout: {type: 'X-AXIS'},
      },
      subs: [...],
    },
  });
};
  • path: view.layout of NodeSpec
  • All nodes inherit layout from it's ancenstors if it has no one.
  • Dragging node Right to the opposite side makes Cat and Dog change their sides.

4.2. Y-AXIS

  [C] [D]
         [B]

---------------[A]---------------

         [B']
  [C'][D']

4.3. XY-AXIS

  • X-AXIS + Y-AXIS

4.4. DEFAULT

If root node has no layout, layout DEFAULT is assign, which does nothing.

5. Events

5.1. Node Event

| event name | description | | --------------- | ----------------------------------- | | node.selected | nodes are selected | | node.clicked | a node is clicked | | node.created | nodes are created | | node.updated | nodes are updated(model, pos, path) | | node.deleted | nodes are deleted |

node.selected

triggered when nodes have been selected(activate sate).

import {..., type NodeEventArg} from "@mind-wired/core";

window.onload = async () => {
  ...
  mwd.listen("node.selected", async (e: NodeEventArg) => {
    const {type, nodes} = e;
    console.log(type, nodes);
  })
};
  • node.selected always preoceds node.clicked
  • Clicking viewport also triggers the event with empty nodes.

node.clicked

triggered when a node has been clicked.

window.onload = async () => {
  ...
  mwd.listen("node.clicked", async (e: NodeEventArg) => {
    const {type, nodes} = e;
    console.log(type, nodes);
  })
};

node.created

triggered when nodes have been created(for example Enter, or Shift+Enter)

window.onload = async () => {
  ...
  mwd.listen("node.created", (e: NodeEventArg) => {
    const {type, nodes} = e;
    console.log(type, nodes);
  })
};

node.updated

triggered when nodes have been updated by

  • offset (x, y) changed(type : 'pos')
  • changing parent(type: 'path')
  • content updated(type: 'model')
  • schema (un)bound(type: schema)
  • folding state changed (type: folding)
window.onload = async () => {
  ...
  mwd.listen("node.updated", (e: NodeEventArg) => {
    const {type, nodes} = e; // type: 'pos' | 'path' | 'model' | 'schema' | 'folding'
    console.log(type, nodes);
  })
};
  • nodes - updated nodes
  • type - cause of updates, path, pos, model, schema, folding

type have one of five values.

  1. path - means the nodes have changed parent(by dragging control icon).
  2. pos - means the nodes move by dragging
  3. model - content has updated(text, icon, etc)
  4. schema - a schema has been (un)bounded to node
  5. folding - folding state has been changed of node

node.deleted

triggered when nodes have been deleted(pressing delete key, fn+delete in mac)

window.onload = async () => {
  ...
  mwd.listen("node.deleted", (e: NodeDeletionArg) => {
    const { type, nodes, updated } = e; // type: 'delete'
    console.log(type, nodes, updated);
  })
};
  • Children node[] of deleted node P are attached to parent of node P, keeping their position on viewport. NodeDeletionArg.updated references children node[]

If deleted node has children, they are moved to node.parent, which triggers node.updated event

5.2. Schema Event

| event name | description | | ---------------- | ----------------------------- | | schema.created | new schema(s) is(are) created | | schema.updated | schemas are updated | | schema.deleted | schemas are deleted |

schema.created

triggered when new schemas are created.

window.onload = async () => {
  ...
  mwd.listen("schema.created", (e: SchemaEventArg) => {
    const { type, schemas } = e; // type: 'create'
    console.log(type, schemas);
  })
};

// or
import { EVENT } from '@mind-wired/core'
mwd.listenStrict(EVENT.SCHEMA.CREATED, (e: SchemaEventArg) => {
    const { type, schemas } = e; // type: 'create'
    console.log(type, schemas);
});

schema.updated

triggered when schemas are updated.

window.onload = async () => {
  ...
  mwd.listen("schema.updated", (e: SchemaEventArg) => {
    const { type, schemas } = e; // type: 'create'
    console.log(type, schemas);
  })
};

// or
mwd.listenStrict(EVENT.SCHEMA.UPDATED, (e: SchemaEventArg) => {
    const { type, schemas } = e; // type: 'update'
    console.log(type, schemas);
});

schema.deleted

triggered when schemas are updated.

window.onload = async () => {
  ...
  mwd.listen("schema.deleted", (e: SchemaEventArg) => {
    const { type, schemas } = e; // type: 'delete'
    console.log(type, schemas);
  })
};

// or
mwd.listenStrict(EVENT.SCHEMA.DELETED, (e: SchemaEventArg) => {
    const { type, schemas } = e; // type: 'update'
    console.log(type, schemas);
});

6. Short Key

Node

on idle node

| Ctrl | Alt | Shift | KEY | description | | ---- | --- | ----- | --- | ----------- | | | | | | none |

| Ctrl | Alt | Shift | Click | description | | ---- | --- | ------- | ------- | ------------------------ | | | | | click | make node active | | | | shift | click | add node to active state |

on active state

  • When one or more nodes are selected

| Ctrl | Alt | Shift | KEY | description | | ---- | --- | ------- | -------- | ----------------------------------------- | | | | | Enter | insert sinbling of active node enter | | | | shift | Enter | insert child on active node shift+enter | | | | | Delete | delete active node(s), fn+delete in mac | | | | | Space | start editing state of active node |

on editing state

  • When editor of an active node is open

| Ctrl | Alt | Shift | KEY | description | | ---- | --- | ----- | ------- | --------------------------------- | | | | | Enter | save data and finish editing | | | | | esc | finish editing state without save |

7. Store

Calling MindWired.exportWith() exports current state of mindmap.

7.1. by listeners

/* /src/main.ts */
import type {..., NodeEventArg } from "@mind-wired/core";
...

const sendToBackend = (data: ExportResponse) => {
  console.log(data)
}
window.onload = async () => {
  ...
  const mwd: MindWired = await initMindWired({...});
  mwd.nodes(mapData.node);

  mwd
    .listen("node.updated", async (e: NodeEventArg) => {
      const data = await mwd.exportWith();
      sendToBackend(data)
    }).listen("node.created", async (e: NodeEventArg) => {
      const data = await mwd.exportWith();
      sendToBackend(data)
    }).listen("node.deleted", async (e: NodeDeletionArg) => {
      const data = await mwd.exportWith();
      sendToBackend(data)
    });
};

7.2. by control ui

You could provide, for example, <button/> to export current state of mindmap

<body>
  <nav id="controls">
    <button data-export>EXPORT</button>
  </nav>
  <div id="mmap-root">...</div>
</body>
window.onload = async () => {

  const mwd: MindWired = await initMindWired({...});
  ...
  const btnExport = document.querySelector<HTMLButtonElement>('#controls > [data-export]')
  btnExport!.addEventListener('click', async () => {
    const data = await mwd.exportWith();
    sendToBackend(data)
  }, false)
};