Chord ChartATM
One chord diagram of a fretted instrument, written as a figure with the chord's name and one attribute: data-shape="0003", a fret per string. The runtime draws the grid from it - a dot where a finger goes, nothing on an open string, × on a muted one, a barre's dashed line, a capo's bold bar with the strings going on behind it, the first fret's number when the shape sits higher up the neck. Across as the player sees the fretboard (the default), or upright like a classic chord chart, for the left hand or mirrored for the right, in one colour or a chart colour per string. Its State API sets the chord and marks it active - the Fretboard writes a chart per chord into its chords outlet and marks the one sounding; the whole neck is the Fretboard Neck.
On this page (14)
§Chord Chart
C on a ukulele: one finger on the 1st string at the 3rd fret, the other three strings open - nothing is drawn on them. Across, as you see the fretboard looking down at the instrument: the nut on the left (the fretting hand's side), the 1st string on top. Open the State tab to write another name, shape, barre or capo, or to mark it active.
§Upright
data-orientation="vertical": the classic chord chart - the nut on top, the strings from the 4th (G) on the left to the 1st on the right. G7 on a ukulele.
§Right hand
data-hand="right" mirrors the grid for a left-handed player: across, the nut on the right.
§String colours
data-colors="strings": string n takes chart colour n, so a dot reads as its string at a glance.
§Six strings, a muted one
A guitar's C: six frets in the shape, x on the 6th string - an × before the nut.
§Barre
F on a guitar with data-barre="1": one finger lies across all six strings at the 1st fret - a dashed line, and no dots of their own on the barred strings. A barre is never guessed from the frets.
§Higher up the neck
D as an A-shape barre at the 5th fret: a shape above the 4th fret is drawn from its lowest fret, its number named beside the grid (5fr). Frets past 9 make the shape a spaced list.
§Capo
data-capo="2": the shape counts from the capo, as tabs for a capo are written - the G shape sounds as A. The capo is the bold bar, the strings go on behind it.
§Active
The active state (data-active): the chord sounding - its frame, name and dots in the primary colour.
§A chord library
df$.shadcn.chordChart.chord() shows another chord in the chart: pick one from the select. The chart keeps its state - mark it active with the switch, and the next chord stays active.
§States
Named states via the shared State API, bound on each .chord-chart:
default- the chart at restactive- the chord sounding (data-active): its frame, name and dots in the primary colour
Every state takes { name, shape, barre, capo }, merged into the current config: the caption, data-shape, data-barre and data-capo. getState() reports what the chart shows, also what was written into its markup - the Fretboard marks the chart of the chord sounding through data-active.
The first example carries data-state-demo; bun run screenshots drives it via el.api.setState(name) and captures screenshots/{mode}/chord-chart-{state}.png.
| State | Type | Values | Default | Description |
|---|---|---|---|---|
active | boolean | true, false | false | The chord sounding (setState('active')): frame, name and dots in the primary colour. |
name | string | — | — | The chord's name, the caption; observed from getState().config.name. |
shape | string | — | — | A fret per string, x muted; observed from getState().config.shape. |
barre | number | — | — | The fret a barre lies on (empty: none); observed from getState().config.barre. |
capo | number | — | 0 | The capo's fret (0: none); observed from getState().config.capo. |
§API
Generated from chord-chart.ts and the shared State API - the descriptions are their JSDoc, the types are checked by the compiler.
States
type ChordChartState = 'default' | 'active' - setState(name, config) takes the config of the state it names.
| State | Description | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
default | The chart at rest.
| |||||||||||||||
active | The chord sounding now (data-active): its frame, caption and dots in the primary colour.
|
Every element
| Member | Description | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|
el.api.setState<S extends ChordChartState>(name: S, config?: ChordChartStateConfigs[S]): unknown | Enter a state: the DOM work runs (also when it is the current state), the store records it.
Returns | |||||||||
el.api.getState(): { name: ChordChartState; config: ChordChartStateConfigs[ChordChartState]; model?: ElementModel } | The state the element shows now - read back from the DOM, so it includes what the user changed. Returns | |||||||||
el.api.render(state?: { name: ChordChartState; config: ChordChartStateConfigs[ChordChartState]; model?: ElementModel }): string | The element's markup in a state - the authored markup with that state applied; a pure function of the state.
Returns | |||||||||
el.api.settled(): Promise<void> | Wait for the last state's DOM work (async states: a diagram rendering, a chart mounting). Returns | |||||||||
el.store: Store<{ name: ChordChartState; config: ChordChartStateConfigs[ChordChartState] }> | A defuss-store store of the element's state - subscribe to follow every change (also the user's), set it to drive the component. |
Registry
| Member | Description | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
df$.shadcn.chordChartApi.setState<S extends ChordChartState>(el: HTMLElement, name: S, config?: ChordChartStateConfigs[S]): unknown | Enter a state: the DOM work runs (also when it is the current state), the store records it.
Returns | ||||||||||||
df$.shadcn.chordChartApi.getState(el: HTMLElement): { name: ChordChartState; config: ChordChartStateConfigs[ChordChartState]; model?: ElementModel } | The state the element shows now - read back from the DOM, so it includes what the user changed.
Returns | ||||||||||||
df$.shadcn.chordChartApi.render(state: { name: ChordChartState; config: ChordChartStateConfigs[ChordChartState]; model?: ElementModel }): string | The element's markup in a state - the authored markup with that state applied; a pure function of the state.
Returns | ||||||||||||
df$.shadcn.chordChartApi.store(el: HTMLElement): Store<{ name: ChordChartState; config: ChordChartStateConfigs[ChordChartState] }> | The element's store (bindComponent made it).
Returns | ||||||||||||
df$.shadcn.chordChartApi.commit<S extends ChordChartState>(el: HTMLElement, name: S, config?: ChordChartStateConfigs[S]): void | Record a state the element reached on its own (no DOM work) - for a component's own handlers.
| ||||||||||||
df$.shadcn.chordChartStates: ChordChartState[] | The declared states, 'default' first: default, active. |
df$.shadcn.chordChart
| Member | Description | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|
chord(target: string | HTMLElement, chord?: ChordChartConfig): Required<ChordChartConfig> | null | The chord a chart shows, or show another (setState in its current state - the rest of the chord stays).
Returns | |||||||||
parse(shape: string): ChordChartShape | null | Read a shape's text.
Returns |
Types
| Type | Description | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
ChordChartConfig | The config every state takes.
| |||||||||||||||
ChordChartShape | A shape read: each string's fret (x for a muted one), from the string the tuning names first to the 1st. = |
§CSS view file
The figure and its caption, the active frame, the grid (strings and fret wires as two gradients, the nut, a capo's bar with the strings behind it, the first fret's number), the dots, the × and the barre, the upright and mirrored grid, the string colours and the accessibility media queries.
/* -- Chord Chart ------------------------------------------------ One chord diagram: the figure (its caption, the frame of the active state) and the grid the runtime draws from data-shape - strings and fret wires as two gradients, the dots, the × of a muted string, a barre's dashed line, a capo's bold bar with the strings going on behind it. Across by default, as the player sees the fretboard (the nut on the fretting hand's side, the 1st string on top); upright with data-orientation="vertical". */@layer components { .chord-chart { display: inline-grid; justify-items: center; gap: 0.25rem; margin: 0; padding: 0.375rem 0.625rem 0.5rem; border: 1px solid transparent; border-radius: var(--radius-md); color: var(--foreground); font-family: var(--font-sans); transition: background-color 120ms ease, border-color 120ms ease; & figcaption { font-size: 0.8125rem; font-weight: 700; } /* the chord sounding */ &[data-active] { border-color: var(--primary); background-color: color-mix(in oklab, var(--primary) 10%, transparent); & figcaption { color: var(--primary); } &:not([data-colors]) .chord-chart-dot { background-color: var(--primary); } & .chord-chart-barre { border-color: var(--primary); } } } /* across (default): the nut on the fretting hand's side, the frets away from it, the strings from the 1st at the top; × before the nut on a muted string (nothing on an open one), a shape higher up names its first fret below */ .chord-chart-grid { --chord-chart-strings: 4; --chord-chart-frets: 4; --chord-chart-tx: -50%; position: relative; inline-size: calc(var(--chord-chart-frets) * 1rem); block-size: calc((var(--chord-chart-strings) - 1) * 0.75rem); margin: 0.375rem 0.75rem 0.25rem; border-inline-start: 3px solid var(--foreground); background-image: linear-gradient(to right, var(--muted-foreground) 1px, transparent 1px), linear-gradient(to bottom, var(--muted-foreground) 1px, transparent 1px); background-size: calc((100% - 1px) / var(--chord-chart-frets)) 100%, 100% calc((100% - 1px) / (var(--chord-chart-strings) - 1)); /* a capo: the bold bar is the capo, the strings go on behind it to the fret wire before it */ &[data-capo] { --chord-chart-behind: 1rem; margin-inline-start: calc(0.75rem + var(--chord-chart-behind)); border-inline-start-width: 4px; &::before { position: absolute; inset-block: 0; inset-inline-end: calc(100% + 4px); inline-size: var(--chord-chart-behind); border-inline-start: 1px solid var(--muted-foreground); background-image: linear-gradient(to bottom, var(--muted-foreground) 1px, transparent 1px); background-size: 100% calc((100% - 1px) / (var(--chord-chart-strings) - 1)); content: ""; } } &[data-base] { margin-block-end: 1rem; border-inline-start-width: 1px; &::after { position: absolute; inset-block-start: calc(100% + 0.25rem); inset-inline-start: calc((100% - 1px) * 0.5 / var(--chord-chart-frets)); color: var(--muted-foreground); content: attr(data-base) "fr"; font-size: 0.625rem; line-height: 1; translate: var(--chord-chart-tx) 0; } } } .chord-chart-dot, .chord-chart-mark { position: absolute; inset-block-start: calc( (100% - 1px) * (var(--chord-chart-strings) - 1 - var(--chord-chart-i, 0)) / (var(--chord-chart-strings) - 1) + 0.5px ); translate: var(--chord-chart-tx) -50%; } .chord-chart-dot { inset-inline-start: calc((100% - 1px) * (var(--chord-chart-r, 0) + 0.5) / var(--chord-chart-frets)); inline-size: 0.625rem; block-size: 0.625rem; border-radius: 50%; background-color: var(--chord-chart-color, var(--foreground)); } .chord-chart-mark { inset-inline-start: calc(-0.625rem - var(--chord-chart-behind, 0rem)); color: var(--chord-chart-color, var(--muted-foreground)); font-size: 0.5625rem; line-height: 1; } /* a barre: one finger across its strings, a dashed line through them */ .chord-chart-barre { position: absolute; inset-block-start: calc( (100% - 1px) * (var(--chord-chart-strings) - 1 - var(--chord-chart-j, 0)) / (var(--chord-chart-strings) - 1) + 0.5px - 0.3125rem ); inset-inline-start: calc((100% - 1px) * (var(--chord-chart-r, 0) + 0.5) / var(--chord-chart-frets)); inline-size: 0; block-size: calc( (100% - 1px) * (var(--chord-chart-j, 0) - var(--chord-chart-i, 0)) / (var(--chord-chart-strings) - 1) + 0.625rem ); border-inline-start: 3px dashed var(--foreground); translate: var(--chord-chart-tx) 0; } /* upright (data-orientation="vertical"), the classic chord chart: the nut on top, the strings from the highest-numbered on the left; × above the nut, a shape higher up names its first fret beside it */ .chord-chart[data-orientation="vertical"] .chord-chart-grid { inline-size: calc((var(--chord-chart-strings) - 1) * 1rem); block-size: calc(var(--chord-chart-frets) * 0.875rem); margin: 0.75rem 0 0; border-inline-start: 0; border-block-start: 3px solid var(--foreground); background-size: calc((100% - 1px) / (var(--chord-chart-strings) - 1)) 100%, 100% calc((100% - 1px) / var(--chord-chart-frets)); &[data-capo] { margin: calc(0.75rem + var(--chord-chart-behind)) 0 0; border-block-start-width: 4px; &::before { inset-block: auto calc(100% + 4px); inset-inline: 0; inline-size: auto; block-size: var(--chord-chart-behind); border-inline-start: 0; border-block-start: 1px solid var(--muted-foreground); background-image: linear-gradient(to right, var(--muted-foreground) 1px, transparent 1px); background-size: calc((100% - 1px) / (var(--chord-chart-strings) - 1)) 100%; } } &[data-base] { border-block-start-width: 1px; &::after { inset-block-start: 0.4375rem; inset-inline-start: calc(100% + 0.25rem); translate: 0 -50%; } } .chord-chart-dot, .chord-chart-mark { inset-inline-start: calc((100% - 1px) * var(--chord-chart-i, 0) / (var(--chord-chart-strings) - 1) + 0.5px); } .chord-chart-dot { inset-block-start: calc((100% - 1px) * (var(--chord-chart-r, 0) + 0.5) / var(--chord-chart-frets)); } .chord-chart-mark { inset-block-start: calc(-0.5rem - var(--chord-chart-behind, 0rem)); } .chord-chart-barre { inset-block-start: calc((100% - 1px) * (var(--chord-chart-r, 0) + 0.5) / var(--chord-chart-frets)); inset-inline-start: calc( (100% - 1px) * var(--chord-chart-i, 0) / (var(--chord-chart-strings) - 1) + 0.5px - 0.3125rem ); inline-size: calc( (100% - 1px) * (var(--chord-chart-j, 0) - var(--chord-chart-i, 0)) / (var(--chord-chart-strings) - 1) + 0.625rem ); block-size: 0; border-inline-start: 0; border-block-start: 3px dashed var(--foreground); translate: 0 -50%; } } /* for the right hand, mirrored: across the nut on the right, upright the strings 1st to highest from the left */ .chord-chart[data-hand="right"] .chord-chart-grid { --chord-chart-tx: 50%; direction: rtl; } /* string colours (data-colors="strings"): string n takes chart colour n, from the 6th on they start over */ .chord-chart[data-colors="strings"] { & :is([data-string="1"], [data-string="6"], [data-string="11"]) { --chord-chart-color: var(--chart-1); } & :is([data-string="2"], [data-string="7"], [data-string="12"]) { --chord-chart-color: var(--chart-2); } & :is([data-string="3"], [data-string="8"]) { --chord-chart-color: var(--chart-3); } & :is([data-string="4"], [data-string="9"]) { --chord-chart-color: var(--chart-4); } & :is([data-string="5"], [data-string="10"]) { --chord-chart-color: var(--chart-5); } /* a × in the string's colour mixed toward the text colour - legible in either mode */ & .chord-chart-mark { color: color-mix(in oklab, var(--chord-chart-color, var(--foreground)) 55%, var(--foreground)); font-weight: 700; } } @media (prefers-reduced-motion: reduce) { .chord-chart { transition: none; } } @media (prefers-contrast: more) { .chord-chart-grid { background-image: linear-gradient(to right, var(--foreground) 1px, transparent 1px), linear-gradient(to bottom, var(--foreground) 1px, transparent 1px); } } @media (forced-colors: active) { .chord-chart[data-active] { border-color: Highlight; & .chord-chart-dot { background-color: Highlight; forced-color-adjust: none; } } .chord-chart-dot { background-color: CanvasText; forced-color-adjust: none; } }}§JavaScript view file
The shape's parser, the barre a shape can hold, the grid drawn from the markup, the State API and df$.shadcn.chordChart.
// -- Chord Chart -------------------------------------------------// One chord diagram of a fretted instrument from its markup: the shape (a fret per string), the barre the data names,// a capo. Drawn across as the player sees the fretboard (the nut on the fretting hand's side, the 1st string on top)// or upright (the classic chart: the nut on top), for either hand: dots where fingers go, nothing on an open string,// × on a muted one, a barre as a dashed line, the first fret's number when the shape sits higher up the neck, a capo// as the bold bar with the strings going on behind it. The Fretboard writes one per chord into its chords outlet and// marks the one sounding (the active state).// Shared preamble (AGENTS.md "State API"); the implementation lives in core.js —// build.ts rewrites this import into a df$.shadcn.shared binding in dist/.import { defussGlobals, defussQuery, componentState, bindComponent } from '../../../shared/state-api.js';const df$ = defussGlobals();const dfDollar = defussQuery();const chordChartStates = ['default', 'active'];/** The config every state takes. */export interface ChordChartConfig { /** the chord's name - the caption */ name?: string; /** the shape: a fret per string, from the string the tuning names first (a ukulele's G) to the 1st - "0003", "x32010", "x 10 12 12 12 10" for frets past 9; x mutes a string */ shape?: string; /** the fret a barre lies on (one finger across the strings pressed there), null for none */ barre?: number | null; /** the capo's fret, 0 for none */ capo?: number;}// VERIFIED: (verify's API docs gate) the states below are exactly the declared ones, each// described, and every config field typed, described and named in the code./** setState() configs per state. */export interface ChordChartStateConfigs { /** The chart at rest. */ default: ChordChartConfig; /** The chord sounding now (data-active): its frame, caption and dots in the primary colour. */ active: ChordChartConfig;}/** A shape read: each string's fret (x for a muted one), from the string the tuning names first to the 1st. */export type ChordChartShape = (number | 'x')[];type Fret = number | 'x';type Barre = { fret: number; from: number; to: number };const clamp = (v: number, lo: number, hi: number): number => Math.min(hi, Math.max(lo, v));const esc = (s: unknown): string => String(s).replace( /[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c] ?? c, );/** a shape from its text: one character per string, or a space- / comma-separated list once a fret has two digits */function parseShape(text: string | null | undefined): Fret[] | null { const s = String(text ?? '').trim(); if (!s) return null; const tokens = /[\s,]/.test(s) ? s.split(/[\s,]+/).filter(Boolean) : [...s]; const out: Fret[] = []; for (const t of tokens) { if (t === 'x' || t === 'X') out.push('x'); else if (/^\d{1,2}$/.test(t) && Number(t) <= 24) out.push(Number(t)); else return null; } return out.length >= 1 && out.length <= 12 ? out : null;}/** a shape as text: characters while every fret has one digit, else a spaced list */const shapeText = (shape: Fret[]): string => shape.some((f) => typeof f === 'number' && f > 9) ? shape.join(' ') : shape.join('');/** a barre on a fret: the strings from the first to the last pressed there, every string between pressed there or * higher - a barre the shape cannot hold is none */function barreOf(shape: Fret[], fret: number): Barre | null { if (!(fret > 0)) return null; const at = shape.map((f, i) => (f === fret ? i : -1)).filter((i) => i >= 0); if (at.length < 2) return null; const from = at[0]; const to = at[at.length - 1]; for (let i = from; i <= to; i++) if (typeof shape[i] !== 'number' || (shape[i] as number) < fret) return null; return { fret, from, to };}const numAttr = (el: Element, name: string): number | null => { const v = el.getAttribute(name); return v === null || v === '' || !Number.isFinite(Number(v)) ? null : Number(v);};const captionOf = (el: Element): string => dfDollar(el).children('figcaption').text().trim();/** the chart's grid: four frets or more, the first fret's number when the shape sits higher up (above the 4th) */function gridMarkup(el: Element): string { const name = captionOf(el); const shape = parseShape(el.getAttribute('data-shape')); const n = shape?.length ?? Math.max(1, numAttr(el, 'data-strings') ?? 4); const played = (shape ?? []).filter((f): f is number => typeof f === 'number' && f > 0); const top = Math.max(0, ...played); const base = top > 4 ? Math.min(...played) : 1; const rows = clamp(top - base + 1, 4, 6); const barre = shape ? barreOf(shape, numAttr(el, 'data-barre') ?? 0) : null; const barred = (f: Fret, i: number) => !!barre && f === barre.fret && i >= barre.from && i <= barre.to; const marks = (shape ?? []) .map((f, i) => f === 'x' ? `<span class="chord-chart-mark" data-string="${n - i}" style="--chord-chart-i: ${i}">×</span>` : f === 0 || barred(f, i) ? '' : `<span class="chord-chart-dot" data-string="${n - i}" style="--chord-chart-i: ${i}; --chord-chart-r: ${f - base}"></span>`, ) .join(''); const bar = barre ? `<span class="chord-chart-barre" style="--chord-chart-i: ${barre.from}; --chord-chart-j: ${barre.to}; --chord-chart-r: ${barre.fret - base}"></span>` : ''; const capo = clamp(Math.round(numAttr(el, 'data-capo') ?? 0), 0, 12); const label = shape ? `${name ? `${name}: ` : ''}${shape.join(' ')}${barre ? `, barre at fret ${barre.fret}` : ''}${capo ? `, capo ${capo}` : ''}` : name; return `<div class="chord-chart-grid" role="img" aria-label="${esc(label)}" style="--chord-chart-strings: ${n}; --chord-chart-frets: ${rows}"${base > 1 ? ` data-base="${base}"` : ''}${capo && base === 1 ? ` data-capo="${capo}"` : ''}>${bar}${marks}</div>`;}/** Draw the grid again (another shape, barre, capo or name) - after the caption. */function draw(el: HTMLElement): void { const markup = gridMarkup(el); // a morph of the markup around it (the Fretboard writing its chords outlet again) keeps the chart, not its grid const old = dfDollar(el).children('.chord-chart-grid'); if (el._chordChart === markup && old.length) return; el._chordChart = markup; const grid = dfDollar(markup).get(0) as HTMLElement; if (old.length) old.replaceWith(grid); else { const caption = dfDollar(el).children('figcaption'); if (caption.length) caption.after(grid); else dfDollar(el).append(grid); }}/** the markup of a state: active is data-active; the config's name, shape, barre and capo are the caption and the * data attributes */function applyMarkup(el: Element, stateName: string, config: ChordChartConfig): void { const active = stateName === 'active' ? '' : null; if (el.getAttribute('data-active') !== active) dfDollar(el).attr('data-active', active); if (typeof config.name === 'string') { const caption = dfDollar(el).children('figcaption'); if (!caption.length) dfDollar(el).prepend(dfDollar(`<figcaption>${esc(config.name)}</figcaption>`)); else if (caption.text() !== config.name) caption.text(config.name); } if (typeof config.shape === 'string') { const parsed = parseShape(config.shape); const text = parsed ? shapeText(parsed) : null; // the authored text stays while it means the same shape ("x 5 7 7 7 5" is x57775) const now = parseShape(el.getAttribute('data-shape')); if ((now ? shapeText(now) : null) !== text) dfDollar(el).attr('data-shape', text); } if (config.barre !== undefined) { const v = config.barre === null || !(Number(config.barre) > 0) ? null : String(Math.round(Number(config.barre))); if (el.getAttribute('data-barre') !== v) dfDollar(el).attr('data-barre', v); } if (config.capo !== undefined) { const c = clamp(Math.round(Number(config.capo) || 0), 0, 12); const v = c ? String(c) : null; if (el.getAttribute('data-capo') !== v) dfDollar(el).attr('data-capo', v); }}/** what the chart shows, read from its markup */const configOf = (el: Element): Required<ChordChartConfig> => ({ name: captionOf(el), shape: el.getAttribute('data-shape') ?? '', barre: numAttr(el, 'data-barre'), capo: numAttr(el, 'data-capo') ?? 0,});/** UI side of setState: the state's markup, the grid drawn again. */function triggerStateChange(el: HTMLElement, stateName: string, incoming: ChordChartConfig): undefined { applyMarkup(el, stateName, incoming); if (el._chordChart !== undefined) draw(el); return undefined;}/** Registry-level API; pass the .chord-chart element explicitly. Unknown names throw. */export const chordChartApi = componentState({ component: 'chord-chart', states: chordChartStates, // the config is the chord: a state change keeps it unless it names another mergeConfig: true, apply: (el, state, _previous, incoming) => triggerStateChange(el, state.name, incoming as ChordChartConfig), // data-active is the active state - the Fretboard writes it on the chart of the chord sounding read: (el, state) => { const name = el.hasAttribute('data-active') ? 'active' : 'default'; const now = configOf(el); const c = state.config as ChordChartConfig; if ( name === state.name && c.name === now.name && c.shape === now.shape && c.barre === now.barre && c.capo === now.capo ) return state; return { name, config: now as Record<string, unknown> }; }, markup: (el, state) => applyMarkup(el, state.name, state.config as ChordChartConfig),});df$.chordChartApi = chordChartApi;df$.chordChartStates = chordChartStates;function init() { dfDollar('.chord-chart:not([data-init])') .toArray() .forEach((el: HTMLElement) => { el.dataset.init = ''; draw(el); // el.store + el.api (AGENTS.md "State through stores"): $('#c').api.setState('active') bindComponent(el, chordChartApi, { name: el.hasAttribute('data-active') ? 'active' : 'default', config: configOf(el), }); // another shape, barre, capo or name written into the markup: drawn again new MutationObserver(() => draw(el)).observe(el, { attributes: true, attributeFilter: ['data-shape', 'data-barre', 'data-capo', 'data-strings'], childList: true, characterData: true, subtree: true, }); });}/** the chart of a target: the .chord-chart element or a selector of it */const resolve = (target: string | HTMLElement): HTMLElement | undefined => { const el = typeof target === 'string' ? dfDollar(target).get(0) : target; const chart = el?.closest?.<HTMLElement>('.chord-chart'); return chart?.api ? chart : undefined;};df$.chordChart = { /** * The chord a chart shows, or show another (setState in its current state - the rest of the chord stays). * @param target - the .chord-chart element or a selector of it * @param chord - the name, shape, barre and capo to show; leave it out to read * @returns the chord now; null for an unknown element */ chord: (target: string | HTMLElement, chord?: ChordChartConfig): Required<ChordChartConfig> | null => { const el = resolve(target); if (!el) return null; if (chord) el.api?.setState(el.hasAttribute('data-active') ? 'active' : 'default', chord as Record<string, unknown>); return configOf(el); }, /** * Read a shape's text. * @param shape - "0003", "x32010" or "x 10 12 12 12 10" * @returns each string's fret (x muted), from the string the tuning names first to the 1st; null for a shape that is none */ parse: (shape: string): ChordChartShape | null => parseShape(shape),};init();new MutationObserver(init).observe(document, { childList: true, subtree: true });Comments, ideas or improvements? Edit this page's source on GitHub