ark-meta-tags
v0.0.1
Published
very simple customizable meta tags with appropriate value to capture
Maintainers
Readme
🏷 ArkMetaTags
A self-contained, zero-dependency multi-instance metadata tag-capture widget.
Built for News Trace — ARK: Immanuel project.
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 libraryInstallation
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-fontCSS 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 definitionsEvents
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 agoData 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 cycleIntegration 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 hasNoteandhasDateflags for simple typesfields[]multi-field mode per tagcollectData()/loadData()round-trip- DOM events:
ark-meta:add,ark-meta:remove,ark-meta:change onChangecallback optionaddTag(),reset(),destroy()public methodsArkMetaTags.defaultTagDefsstatic accessor- UMD wrapper (script tag / require / import)
- Self-injecting CSS with
amt-namespace prefix
License
MIT © ARK: Immanuel — https://immanuel.co
