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 🙏

© 2026 – Pkg Stats / Ryan Hefner

ark-meta-tags

v0.0.1

Published

very simple customizable meta tags with appropriate value to capture

Readme

🏷 ArkMetaTags

A self-contained, zero-dependency multi-instance metadata tag-capture widget.
Built for News Trace — ARK: Immanuel project.

Version License Zero Dependencies UMD


What it does

ArkMetaTags renders a tag-chip strip. Tapping any tag adds an entry below; tapping again adds another instance of the same tag. This makes it ideal for recording sequential events against a single record — multiple court hearings, multiple follow-up dates, multiple witnesses — each with its own note.

Demo Link

You may be using Demo Link.

Key features

| Feature | Detail | |---|---| | 16 built-in tags | Contact, Phone, FIR, IPC Section, Officer, Victim, Witness, Accused, Case Status, Court/Hearing, Hospital, Source, Reference, Follow-up Date, Note, Custom Tag | | 6 field types | text, textarea, select, chiplist, keyvalue, dateNote | | hasNote flag | Adds an action / note sub-row to any simple type | | hasDate flag | Adds a date picker sub-row to any simple type | | fields[] array | Full multi-field control per instance | | Custom tagDefs | Extend or fully replace the 16 built-in tags | | collectData() | Returns structured JSON — one array per tag key | | loadData(meta) | Restores widget from a previous collectData() output | | DOM events | ark-meta:add, ark-meta:remove, ark-meta:change | | Self-contained CSS | Injects <style> into <head> once, no external sheet needed | | UMD build | Works as <script>, require(), or import |


File structure

ark-meta-tags.js       ← Main library
ark-test-data.js       ← Companion test-data generator (optional)
ark-meta-tags-docs.html← Interactive documentation page
cases-list.html        ← Case review list (approve / reject / invalid)
news-trace-demo.html   ← News Trace entry form using the library

Installation

Script tag

<script src="ark-meta-tags.js"></script>

ES Module

import ArkMetaTags from './ark-meta-tags.js';

CommonJS

const ArkMetaTags = require('./ark-meta-tags');

No npm, no bundler required.


Quick Start

<!-- Host element -->
<div id="meta-host"></div>

<script src="ark-meta-tags.js"></script>
<script>
  const widget = new ArkMetaTags('#meta-host', {
    onChange(data) {
      console.log(data); // fires on every add / remove
    },
  });

  // Collect structured JSON
  const meta = widget.collectData();

  // Restore from saved data
  widget.loadData(savedMeta);
</script>

No CSS needed on the host element. The library writes its own <style> once. Override with --amt-font CSS variable.


Tag Types

Every tag definition requires a type that controls the rendered input and the shape of each collectData() entry.

text

Single-line text input.

{ key: 'witness', label: 'Witness', icon: `…`,
  type: 'text', ph: 'Witness name and account…' }
// collectData entry: { value: "…" }

textarea

Resizable multiline text area.

{ key: 'note', label: 'Internal Note', icon: `…`,
  type: 'textarea', ph: 'Off-record notes…' }
// collectData entry: { value: "…" }

select

Dropdown from a fixed options list. The first option is treated as a blank placeholder.

{ key: 'status', label: 'Case Status', icon: `…`,
  type: 'select',
  opts: ['Select status…', 'Under investigation', 'FIR registered', 'In court'] }
// collectData entry: { value: "Under investigation" }

chiplist

Multiple values entered as chips. Press Enter or , to commit each chip. Backspace removes the last chip.

{ key: 'phone', label: 'Phone', icon: `…`,
  type: 'chiplist', ph: 'Type number, press Enter…' }
// collectData entry: { values: ["+91 9876543210", "+91 9898989898"] }

keyvalue

Side-by-side label : value pair. Left field is the key/label, right field is the value.

{ key: 'custom', label: 'Custom Tag', icon: `…`,
  type: 'keyvalue', phKey: 'Field name…', phVal: 'Value…' }
// collectData entry: { key: "RTI Ref", value: "RTI/221/2025" }

dateNote

Date picker as the primary input with a mandatory note textarea always rendered below it.

{ key: 'followup', label: 'Follow-up Date', icon: `…`,
  type: 'dateNote', ph: 'What to follow up on…' }
// collectData entry: { date: "2025-06-01", note: "Check FIR status" }

hasNote and hasDate Flags

Add optional sub-rows to any simple type without switching to multi-field mode.

// hasNote — appends "Action / note" textarea below the main input
{ key: 'fir', label: 'FIR No.', icon: `…`,
  type: 'text', ph: 'FIR No. 000/2025…',
  hasNote: true }
// → { value: "FIR No. 047/2025", note: "IPC 302, 307" }

// hasDate — appends a date picker below the main input
{ key: 'officer', label: 'Officer / Official', icon: `…`,
  type: 'text', ph: 'Name, rank, department…',
  hasDate: true }
// → { value: "SP Rajesh Kumar", date: "2025-05-13" }

// Both together
{ key: 'accused', label: 'Accused', icon: `…`,
  type: 'text', ph: 'Name(s) of accused…',
  hasDate: true, hasNote: true }
// → { value: "…", date: "2025-03-10", note: "Arrested; currently in judicial custody" }

Multi-field (fields[])

For tags that need several named sub-fields per instance, replace type with a fields array.

| Field type | Renders as | |---|---| | text | Single-line input (primary row or labelled secondary row) | | date | Date picker in a date-row style | | note | Textarea in a note-row style (italic, subdued background) | | chiplist | Chip input | | select | Dropdown | | textarea | Multiline input |

{
  key: 'court', label: 'Court / Hearing', icon: `…`,
  fields: [
    { name: 'court',   type: 'text',     ph: 'Court name…'           },
    { name: 'date',    type: 'date',     label: 'Hearing date'       },
    { name: 'judge',   type: 'text',     ph: 'Presiding judge…',
                                          label: 'Judge'              },
    { name: 'outcome', type: 'note',     ph: 'What happened…'         },
    { name: 'links',   type: 'chiplist', ph: 'Paste URL, Enter…'      },
    { name: 'verdict', type: 'select',
      opts: ['Select…', 'Pending', 'Convicted', 'Acquitted'] },
  ],
}

// collectData() entry per instance:
// {
//   court:   "Madras High Court",
//   date:    "2025-05-13",
//   judge:   "Hon. Justice S. Ramakrishnan",
//   outcome: "Witness did not appear; hearing adjourned",
//   links:   ["https://ecourts.gov.in/…"],
//   verdict: "Pending"
// }

Multiple instances example — three separate court hearings on one case:

widget.collectData();
// {
//   court: [
//     { court: "Chennai HC", date: "2025-03-10", outcome: "Bail rejected" },
//     { court: "Chennai HC", date: "2025-05-13", outcome: "Witness absent" },
//     { court: "Chennai HC", date: "2025-07-22", outcome: "Judge reserved" },
//   ]
// }

Custom tagDefs

Merge mode (default — replaceTagDefs: false)

User-supplied entries override built-ins by matching key. Unknown keys are appended to the end of the strip.

const widget = new ArkMetaTags('#host', {
  tagDefs: [
    // New tag — appended to the strip
    {
      key: 'journalist', label: 'Journalist',
      icon: `<path d="M12 20h9"/><path d="M16.5 3.5a2.12 2.12 0 013 3L7 19l-4 1 1-4z"/>`,
      type: 'text', ph: 'Reporter name & outlet…',
    },
    // Override built-in 'court' with multi-field version
    {
      key: 'court',
      fields: [
        { name: 'court',  type: 'text', ph: 'Court name…'    },
        { name: 'date',   type: 'date', label: 'Hearing date' },
        { name: 'note',   type: 'note', ph: 'What happened…'  },
      ],
    },
  ],
});

Replace mode (replaceTagDefs: true)

Skips all 16 built-ins entirely — use only your definitions.

const widget = new ArkMetaTags('#host', {
  replaceTagDefs: true,
  tagDefs: [
    { key: 'sku',    label: 'SKU',    icon: `…`, type: 'text'     },
    { key: 'colour', label: 'Colour', icon: `…`, type: 'chiplist' },
    { key: 'expiry', label: 'Expiry', icon: `…`, type: 'dateNote' },
  ],
});

Options Reference

new ArkMetaTags(container, {
  tagDefs       : [],             // custom tag definitions (see above)
  replaceTagDefs: false,          // true = use tagDefs only, ignore built-ins
  onChange      : meta => {},     // called on add/remove with full collectData() object
  sectionTitle  : 'Details — tap to add',  // text in the ornamental divider
  hint          : 'Tap a tag to add…',     // italic hint below divider
  showRule      : true,           // show/hide the ornamental divider
  injectStyles  : true,           // auto-inject library CSS into <head>
});

| Option | Type | Default | Description | |---|---|---|---| | tagDefs | Array | null | Custom tag definitions | | replaceTagDefs | boolean | false | Skip built-in tags entirely | | onChange | function | null | Structural-change callback | | sectionTitle | string | 'Details — tap to add' | Section-rule label | | hint | string | 'Tap a tag to add…' | Italic hint text | | showRule | boolean | true | Show ornamental divider | | injectStyles | boolean | true | Auto-inject CSS |


Methods

const w = new ArkMetaTags('#host', { /* options */ });

// Read all entries as structured JSON
const data = w.collectData();
// → { court: [{value,note}], phone: [{values:[…]}], followup: [{date,note}], … }

// Restore from a previous collectData() result (clears first)
w.loadData(data);

// Programmatically add one instance of a tag
w.addTag('court');    // same as clicking the "Court / Hearing" chip

// Clear all entries and groups
w.reset();

// Unmount — empties the container element
w.destroy();

// Static properties
ArkMetaTags.version;         // '1.0.0'
ArkMetaTags.defaultTagDefs;  // array of 16 built-in definitions

Events

All events are dispatched on the host container element with bubbles: true.

| Event | e.detail | Fired when | |---|---|---| | ark-meta:add | { key, iid, instance } | An instance is added | | ark-meta:remove | { key, iid, instance } | An instance is removed | | ark-meta:change | { data, instance } | Any structural change (add/remove/reset) |

e.detail.instance is the live ArkMetaTags object — call any method from inside a listener.

const host = document.getElementById('meta-host');
const w    = new ArkMetaTags(host, {
  onChange(data) {
    // Fires on every add / remove / reset
    saveToServer(data);
  },
});

// Or listen via DOM:
host.addEventListener('ark-meta:add', e => {
  console.log('Added tag:', e.detail.key, 'instance id:', e.detail.iid);
});

host.addEventListener('ark-meta:remove', e => {
  console.log('Removed:', e.detail.key);
});

host.addEventListener('ark-meta:change', e => {
  // e.detail.data = full collectData() object
  console.log(e.detail.data);
});

collectData() Format Reference

{
  // text / select / textarea → { value, [note], [date] }
  witness  : [{ value: "Arjun Kumar — Eyewitness" }],
  fir      : [{ value: "FIR No. 047/2025", note: "IPC 302, 307" }],
  officer  : [{ value: "SP Rajesh Kumar", date: "2025-05-13", note: "Present at scene" }],
  status   : [{ value: "In court / Trial", note: "Fast-track court hearing scheduled" }],

  // chiplist → { values: string[] }
  phone    : [{ values: ["+91 9876543210", "+91 9898989898"] }],
  ref      : [{ values: ["https://thehindu.com/…", "https://ecourts.gov.in/…"] }],

  // keyvalue → { key, value }
  custom   : [{ key: "RTI Ref No.", value: "RTI/221/2025" },
              { key: "Editor Note", value: "Hold for legal clearance" }],

  // dateNote → { date, note }
  followup : [{ date: "2025-06-01", note: "Chase FIR status from PS" },
              { date: "2025-07-22", note: "Attend court hearing" }],

  // fields[] → flat object keyed by field name
  court    : [
    { court: "Madras HC", date: "2025-03-10", judge: "Hon. Justice S.R.",
      outcome: "Bail rejected", links: ["https://…"], verdict: "Pending" },
    { court: "Madras HC", date: "2025-05-13", judge: "Hon. Justice S.R.",
      outcome: "Case reserved for judgment", links: [], verdict: "Reserved" },
  ],
}

Styling & CSS Variables

/* Override font */
.amt-widget { --amt-font: 'Your Font', sans-serif; }

/* Key classes for targeted overrides */
.amt-strip      { /* tag chip row */ }
.amt-tag        { /* individual chip */ }
.amt-tag-count  { /* instance count badge */ }
.amt-group      { /* all instances of one tag */ }
.amt-group-head { /* group header (label + "Add another" button) */ }
.amt-inst       { /* one instance row */ }
.amt-inst-num   { /* instance number badge */ }
.amt-note-row   { /* note sub-row */ }
.amt-date-row   { /* date sub-row */ }
.amt-chip       { /* chip in a chiplist */ }
.amt-kv-key     { /* key field of keyvalue */ }
.amt-kv-val     { /* value field of keyvalue */ }

All library classes are prefixed amt- to avoid collision with host page styles.


ArkTestData — Companion Test Library

ark-test-data.js generates realistic Indian news-reporter data to seed and test the widget.

Quick usage

// Generate and load into widget
widget.loadData(ArkTestData.generateMeta());

// Generate a full case object
const c = ArkTestData.generateCase();
// → { id, title, priority, category, location, meta, body, reporter, … }

// Generate N cases with unique IDs
const cases = ArkTestData.generateCases(10);

// Seed localStorage (used by cases-list.html)
ArkTestData.seedLocalStorage(20);       // append 20 cases
ArkTestData.seedLocalStorage(5, true);  // replace with 5
ArkTestData.clearLocalStorage();

// Summary table in console
ArkTestData.consoleReport(cases);

// Use raw corpus for custom generators
const city  = ArkTestData.rng.pick(ArkTestData.corpus.CITIES);
const phone = ArkTestData.rng.phone();
const name  = ArkTestData.rng.name();
const date  = ArkTestData.rng.isoDate(365);  // random past date ≤ 1 year ago

Data generated per tag key

| Tag key | Generated shape | |---|---| | contact | { value: "Priya Krishnan" } | | phone | { values: ["+91 9876543210"] } | | fir | { value: "FIR No. 047/2025 — Central PS", note: "IPC 302, 307" } | | section | { value: "IPC 302, BNS 101" } | | officer | 1–3 instances — { value: "SP Rajesh Kumar — Tamil Nadu Police", note: "…" } | | victim | 1–2 instances — { value: "Arjun Kumar, 34 yrs, Male", note: "Admitted to Govt. General Hospital" } | | witness | { value: "Sunita Rao — Eyewitness", note: "Identity withheld" } | | accused | 1–3 instances — { value: "Mohan Raj", note: "Arrested; in judicial custody" } | | status | { value: "In court / Trial", note: "Last updated: 2025-03-10" } | | court | 1–4 instances, each one hearing — { value: "Chennai HC · 13 May", note: "Witness absent" } | | hospital | { value: "Govt. General Hospital", note: "Condition: Stable" } | | source | { value: "Eyewitness", note: "Contact: +91 …" } | | ref | { values: ["https://thehindu.com/…", "https://scroll.in/…"] } | | followup | 1–3 instances — { date: "2025-07-01", note: "Chase FIR status from PS" } | | note | { value: "Off-record: source requested anonymity" } | | custom | 1–2 instances — { key: "RTI Ref No.", value: "RTI/221/2025" } |

Smoke test

const { pass, fail } = ArkTestData.smokeTest();
// Logs ~20 ✓/✗ assertions covering:
//   - case object shape (id, title, priority, reviewStatus, location, meta, body, created)
//   - meta key shapes (phone.values, court.note, custom.key, followup.date)
//   - generateCases(5) unique IDs
//   - localStorage seed, read, clear cycle

Integration Test Pattern

function integrationTest(widget) {
  // 1. Generate random meta
  const meta = ArkTestData.generateMeta();

  // 2. Load into widget
  widget.loadData(meta);

  // 3. Collect back
  const out = widget.collectData();

  // 4. Assert shapes match what was loaded
  if (meta.court && out.court) {
    console.assert(out.court.length === meta.court.length, 'court entry count matches');
    console.assert('value' in out.court[0], 'court[0] has value');
    console.assert('note'  in out.court[0], 'court[0] has note');
  }
  if (meta.phone) {
    console.assert(Array.isArray(out.phone[0].values), 'phone values is array');
  }
  if (meta.custom) {
    console.assert(out.custom[0].key   === meta.custom[0].key,   'custom key round-trips');
    console.assert(out.custom[0].value === meta.custom[0].value, 'custom value round-trips');
  }
  if (meta.followup) {
    console.assert(out.followup[0].date === meta.followup[0].date, 'followup date round-trips');
  }

  // 5. Reset and verify empty
  widget.reset();
  const empty = widget.collectData();
  console.assert(Object.keys(empty).length === 0, 'reset gives empty collectData()');

  console.log('Integration test: PASS');
}

integrationTest(new ArkMetaTags('#test-host'));

Browser Support

| Chrome | Firefox | Safari | Edge | |:---:|:---:|:---:|:---:| | ✅ 80+ | ✅ 75+ | ✅ 13.1+ | ✅ 80+ |

Requires: CustomEvent, IntersectionObserver (docs page only), navigator.clipboard (copy button only).


File List

| File | Purpose | |---|---| | ark-meta-tags.js | Library — mount with new ArkMetaTags(el, options) | | ark-test-data.js | Test helper — ArkTestData.generateMeta(), seedLocalStorage(), smokeTest() | | ark-meta-tags-docs.html | Interactive documentation with all features and live demos | | news-trace-demo.html | News Trace entry form — real-world usage of the library | | cases-list.html | Responsive case list with approve / reject / invalid review actions |


Changelog

v1.0.0

  • Initial release extracted from News Trace project
  • 16 built-in news-reporter tag definitions
  • 6 field types: text, textarea, select, chiplist, keyvalue, dateNote
  • hasNote and hasDate flags for simple types
  • fields[] multi-field mode per tag
  • collectData() / loadData() round-trip
  • DOM events: ark-meta:add, ark-meta:remove, ark-meta:change
  • onChange callback option
  • addTag(), reset(), destroy() public methods
  • ArkMetaTags.defaultTagDefs static accessor
  • UMD wrapper (script tag / require / import)
  • Self-injecting CSS with amt- namespace prefix

License

MIT © ARK: Immanuel — https://immanuel.co