Developer Documentation
ArkMetaTags v0.0.1
A self-contained multi-instance metadata tag-capture widget. Tap a tag to add an entry — tap again to add another instance with a per-instance note. Zero dependencies. UMD build.
Zero dependencies UMD · ESM · CJS 16 built-in tags 6 field types 3 DOM events MIT
01 · Getting Started
Installation
Place both ark-meta-tags.js and your host HTML in the same directory. No npm, no bundler needed.
HTML
<!-- Script tag (simplest) -->
<script src="ark-meta-tags.js"></script>

<!-- Or as ES module -->
<script type="module">
  import ArkMetaTags from './ark-meta-tags.js';
</script>

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

CSS is self-injected. The library writes its own <style> tag to <head> once per page. You don't need any external stylesheet. Override with .amt-widget { --amt-font: … }.

02 · Getting Started
Quick Start
Mount on any block element, collect structured JSON, restore from saved data.
Live demo — full widget with default tags Interactive
collectData() output
Click "collectData()" after adding some entries…
JavaScript
// 1. Mount
const widget = new ArkMetaTags('#my-div', {
  onChange: data => console.log(data),
});

// 2. Collect structured JSON
const meta = widget.collectData();
// → { court: [{value:'…',note:'…'}], phone: [{values:[…]}] }

// 3. Restore from saved data
widget.loadData(savedMeta);

// 4. Other methods
widget.addTag('court');   // programmatic add
widget.reset();           // clear all
widget.destroy();         // unmount
03 · Tag Types
All Field Types
Every tag has a type property that controls what input is rendered and what shape the collectData() entry produces.
text
Single-line text input. The most common type.
→ { value: "…" }
textarea
Resizable multiline text area.
→ { value: "…" }
select
Dropdown from a fixed opts[] array.
→ { value: "…" }
chiplist
Multiple values as chips. Press Enter or comma to add.
→ { values: ["a","b"] }
keyvalue
Side-by-side label : value pair. Good for custom fields.
→ { key:"…", value:"…" }
dateNote
Date picker with a mandatory note row always shown below.
→ { date:"YYYY-MM-DD",
note:"…" }
Try each type — one widget per type Interactive
text & textarea
select
chiplist
keyvalue
dateNote
All widgets' collectData()
Click "collectData() all"…
JavaScript — type definitions
[
  // text
  { key: 'witness', label: 'Witness', icon: `…`,
    type: 'text', ph: 'Witness name…' },

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

  // select (opts required)
  { key: 'status', label: 'Case Status', icon: `…`,
    type: 'select',
    opts: ['Select…', 'Under investigation', 'FIR registered'] },

  // chiplist — multiple values per instance
  { key: 'phone', label: 'Phone', icon: `…`,
    type: 'chiplist', ph: 'Type number, press Enter…' },

  // keyvalue — field name : value
  { key: 'custom', label: 'Custom Tag', icon: `…`,
    type: 'keyvalue', phKey: 'Field name…', phVal: 'Value…' },

  // dateNote — date picker + always-visible note row
  { key: 'followup', label: 'Follow-up Date', icon: `…`,
    type: 'dateNote', ph: 'What to follow up on…' },
]
04 · Flags
hasNote & hasDate Flags
Add an action/note sub-row or a date sub-row to any simple type without switching to multi-field mode. Both can be combined.
FIR tag — type:'text' + hasNote:true · Officer tag — text + hasDate + hasNote Interactive
output
JavaScript
// hasNote — appends an "Action / note" textarea row
{ 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 sub-row
{ key: 'officer', label: 'Officer', icon: `…`,
  type: 'text', ph: 'Name, rank…',
  hasDate: true,
  hasNote: true }
// → { value: "SP Rajesh Kumar", date: "2025-05-13", note: "Present at scene" }
05 · Advanced
Multi-field (fields[])
For tags that need several named sub-fields per instance, replace type with a fields array. Each field has a name, type, and optional label / ph. The collectData entry is a flat object keyed by field name.
Court hearing — each instance records: court, date, judge, outcome, links Interactive
output — flat object per instance
JavaScript — fields[] definition
{
  key: 'court', label: 'Court / Hearing',
  icon: `<line x1="12" y1="1" x2="12" y2="23"/>…`,
  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 shape:
// { court:'…', date:'2025-05-13', judge:'…', outcome:'…',
//   links:['https://…'], verdict:'Pending' }
⚠️

Field name note and date are reserved. When type:'note' or type:'date' is used inside fields[], the row renders in the note-row or date-row style (subdued italic background) regardless of its position in the array.

06 · Configuration
Custom tagDefs
Pass tagDefs to the constructor to extend the built-in 16 tags. By default they merge by key — any tag with a matching key overrides the default; new keys are appended. Set replaceTagDefs: true to use only your tags.
Merge — adds "Journalist" tag + overrides built-in "court" with multi-field Interactive
output
JavaScript — merge mode (default)
const widget = new ArkMetaTags('#host', {
  tagDefs: [
    // Entirely new tag (appended to 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 definition
    {
      key: 'court',
      fields: [
        { name: 'court',  type: 'text', ph: 'Court name…'     },
        { name: 'date',   type: 'date', label: 'Hearing date'  },
        { name: 'note',   type: 'note', ph: 'What happened…'  },
      ],
    },
  ],
  // replaceTagDefs: false,  ← default; set true to skip built-ins
});
JavaScript — replace mode
const widget = new ArkMetaTags('#host', {
  replaceTagDefs: true,   // ignore all 16 built-in tags
  tagDefs: [
    { key: 'sku',      label: 'SKU',       icon: `…`, type: 'text'     },
    { key: 'colour',   label: 'Colour',    icon: `…`, type: 'chiplist' },
    { key: 'expiry',   label: 'Expiry',    icon: `…`, type: 'dateNote' },
  ],
});
07 · Configuration
Options Reference
All options are passed as the second argument to the constructor.
OptionTypeDefaultDescription
tagDefs Array null Custom tag definitions. Merged with built-ins by key unless replaceTagDefs is true.
replaceTagDefs boolean false When true, tagDefs completely replaces built-in defaults. No merging.
onChange function null Called with the full collectData() object whenever an instance is added or removed.
sectionTitle string 'Details — tap to add' Text inside the ornamental section-rule divider above the tag strip.
hint string 'Tap a tag to add…' Small italic hint line below the section title.
showRule boolean true Show or hide the ornamental section-rule line above the tag strip.
injectStyles boolean true Auto-inject the library's CSS into <head>. Disable if you manage styles yourself.
JavaScript — all options
new ArkMetaTags('#host', {
  tagDefs      : [],             // extend / replace built-in tags
  replaceTagDefs: false,         // true = use tagDefs only
  onChange     : meta => {},     // structural-change callback
  sectionTitle : 'Add details', // section-rule label
  hint         : 'Tap to add',  // italic hint below title
  showRule     : true,           // show/hide ornamental divider
  injectStyles : true,           // auto-inject library CSS
});
08 · API
Methods
All methods are on the instance returned by new ArkMetaTags(…).
MethodReturnsDescription
collectData() object Returns a plain object keyed by tag key, each value an array of instance objects. See formats per type above.
loadData(meta) void Clears all entries, then re-renders from a collectData()-shaped object. Inverse of collectData.
addTag(key) void Programmatically add one instance of the tag with key key. Same as tapping the tag chip.
reset() void Remove all instances and groups. Fires ark-meta:change with empty data.
destroy() void Unmount: empties the container element. Call before removing the container from the DOM.
ArkMetaTags.version string Static property — current library version string.
ArkMetaTags.defaultTagDefs Array Static property — the full array of 16 built-in tag definitions. Useful for inspection or partial cloning.
Methods playground — call any method and observe the widget Interactive
result
JavaScript — all methods
const w = new ArkMetaTags('#host', { /* options */ });

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

// Restore (clear first, then fill)
w.loadData(data);

// Programmatic add
w.addTag('court');    // adds one instance, same as clicking tag chip

// Clear everything
w.reset();

// Unmount (empties container)
w.destroy();

// Static
console.log(ArkMetaTags.version);          // '1.0.0'
console.log(ArkMetaTags.defaultTagDefs);   // [ {key,label,…}, … ]
09 · API
Events
All events are dispatched on the host container element with bubbles: true — so you can also listen on a parent. Every event's e.detail includes instance — the live ArkMetaTags object.
Evente.detailFired when
ark-meta:add { key, iid, instance } A new instance is added (tag chip tapped or addTag() called).
ark-meta:remove { key, iid, instance } An instance row is deleted (× button clicked).
ark-meta:change { data, instance } Any structural change (add, remove, or reset). data is the full collectData() object.
💡

onChange vs ark-meta:change — They fire at the same time. Use the onChange constructor option when you want a clean closure. Use DOM events when you need to listen from outside the constructor, or bubble up to a parent.

Live event log — interact with the widget and watch events fire in real time Events
Event log
Waiting for events — add or remove a tag above…
JavaScript — all events
const host = document.getElementById('meta-host');
const w    = new ArkMetaTags(host, {
  onChange(data) {
    // Fires on every add / remove / reset
    console.log('onChange:', data);
  },
});

// Or listen via DOM events:

host.addEventListener('ark-meta:add', e => {
  console.log('Added:', e.detail.key, 'iid:', e.detail.iid);
  // e.detail.instance → the ArkMetaTags object
});

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

host.addEventListener('ark-meta:change', e => {
  // e.detail.data = full collectData() output
  saveToServer(e.detail.data);
});
10 · Testing
ArkTestData — Companion Test Library
ark-test-data.js generates realistic Indian news-reporter data. It covers all 16 tag keys with correctly-shaped random values so you can seed, test, and reload the widget without touching real data.
MethodReturnsDescription
generateCase()objectOne full case object: title, location, meta (all tag keys), body HTML, reporter, priority…
generateCases(n)ArrayArray of N cases with unique IDs and staggered dates.
generateMeta()objectOnly the meta portion — compatible with widget.loadData().
generateLocation()objectRandom Indian location with city, state, GPS, pincode, police station.
seedLocalStorage(n)ArrayGenerates N cases and writes them to localStorage['nt_drafts']. Appends by default.
clearLocalStorage()voidRemoves localStorage['nt_drafts'].
consoleReport(cases)voidPrints a console.table summary of a cases array.
smokeTest(){pass,fail}Runs ~20 assertions. Logs ✓/✗ per assertion. Returns pass/fail counts.
corpusobjectExposes raw data arrays: CITIES, STATES, HEADLINES, IPC_SECTIONS, etc.
rngobjectExposes random helpers: rng.pick(arr), rng.int(min,max), rng.name(), rng.phone()
Test data generators — live output ArkTestData
output
Click any button above…
Widget loaded from generateMeta()
JavaScript — ArkTestData usage
// Generate and inspect
const singleCase = ArkTestData.generateCase();
const metaOnly   = ArkTestData.generateMeta();
const tenCases   = ArkTestData.generateCases(10);

// Load random data into any ArkMetaTags widget
widget.loadData(ArkTestData.generateMeta());

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

// Console report
ArkTestData.consoleReport(tenCases);

// Use corpus for custom generators
const city  = ArkTestData.rng.pick(ArkTestData.corpus.CITIES);
const phone = ArkTestData.rng.phone();
const name  = ArkTestData.rng.name();
11 · Testing
Smoke Test
Run ArkTestData.smokeTest() to verify the data generator and localStorage integration. The test runner below runs in the page — no console required.
ArkTestData.smokeTest() — click to run all assertions Test runner
JavaScript — integration test pattern
// Integration test: generate → loadData → collectData → verify shapes
function integrationTest(widget) {
  const meta = ArkTestData.generateMeta();
  widget.loadData(meta);                    // fill the widget
  const out  = widget.collectData();         // read it back

  // court entries must have value and note
  if (out.court) {
    out.court.forEach(e => {
      assert('court has value', 'value' in e);
      assert('court has note',  'note'  in e);
    });
  }
  // phone must have values array
  if (out.phone) {
    assert('phone values is array', Array.isArray(out.phone[0].values));
  }
  // custom must have key + value
  if (out.custom) {
    out.custom.forEach(e => {
      assert('custom has key',   'key'   in e);
      assert('custom has value', 'value' in e);
    });
  }
  // followup must have date + note
  if (out.followup) {
    out.followup.forEach(e => {
      assert('followup has date', typeof e.date === 'string');
      assert('followup has note', typeof e.note === 'string');
    });
  }
  // round-trip: loadData then reset should give empty
  widget.reset();
  const empty = widget.collectData();
  assert('reset gives empty object', Object.keys(empty).length === 0);
}