ascii-tabs 0.0.0-stage → 0.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +657 -2
- package/ascii-tabs.js +2046 -0
- package/package.json +51 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 libasoles
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,658 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ascii-tabs
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A zero-dependency web component for editing and displaying guitar tablature, with plain ASCII import and export.
|
|
4
|
+
|
|
5
|
+
**▶ [Try the live demo](https://libasoles.github.io/ascii-tabs/)**: every feature, editable, in your browser.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
`ascii-tabs` is published on npm as a single ES module with no dependencies. It defines the `<ascii-tabs>` custom element when imported, so it works in any framework or in plain HTML.
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install ascii-tabs
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import 'ascii-tabs';
|
|
17
|
+
// or, to also use the helpers:
|
|
18
|
+
import { parse, format } from 'ascii-tabs';
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```html
|
|
22
|
+
<ascii-tabs></ascii-tabs>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
No bundler? Load it from a CDN instead, see [Usage](#usage). Full documentation and a live, editable demo: **https://libasoles.github.io/ascii-tabs/**
|
|
26
|
+
|
|
27
|
+
## See it in action
|
|
28
|
+
|
|
29
|
+
### Edit and organize tabs
|
|
30
|
+
|
|
31
|
+
Click a string and type a fret. Navigate with the keyboard, select and move groups of notes, and copy tabs as ASCII. Each Sheet has copy and delete controls, with an add button below.
|
|
32
|
+
|
|
33
|
+
<a href="https://libasoles.github.io/ascii-tabs/">
|
|
34
|
+
<picture>
|
|
35
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/img/example-dark.png">
|
|
36
|
+
<img alt="An ascii-tabs editor showing the opening of Ode to Joy on strings 1 and 2, with copy, delete and add buttons" src="docs/img/example-light.png" width="760">
|
|
37
|
+
</picture>
|
|
38
|
+
</a>
|
|
39
|
+
|
|
40
|
+
### Switch notation and spacing
|
|
41
|
+
|
|
42
|
+
Show frets, letter names or solfege, and adjust spacing with optional built-in controls. Interface language and string labels are configurable independently.
|
|
43
|
+
|
|
44
|
+
<img alt="Two rendered tabs: letter note names with notation and spacing controls, and solfege with Spanish controls" src="docs/img/feature-notation.png" width="760">
|
|
45
|
+
|
|
46
|
+
### Style individual notes
|
|
47
|
+
|
|
48
|
+
Highlight a melody with per-note colors and bold text, and put notes in parentheses. The screenshot below uses structured note data in the same editor.
|
|
49
|
+
|
|
50
|
+
<img alt="Guitar tab with red and blue melody notes, bold and normal weight frets, and parenthesized notes" src="docs/img/feature-note-styles.png" width="760">
|
|
51
|
+
|
|
52
|
+
Design notes: [spec](docs/spec-v0.1.md), [glossary](GLOSSARY.md), [ADRs](docs/adr). `tablatura.html` is the original standalone editor this library grew from.
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
```html
|
|
57
|
+
<script type="module" src="https://cdn.jsdelivr.net/gh/libasoles/ascii-tabs@0.10.3/ascii-tabs.js"></script>
|
|
58
|
+
|
|
59
|
+
<ascii-tabs></ascii-tabs>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
A bare `<ascii-tabs>` is editable: a hint, one Sheet per Tab with copy and delete buttons, and an add button. Click a string and type the fret number (0–24); move with the arrow keys, Enter and Tab. Press Cmd+Right Arrow or Alt+Right Arrow to copy all six strings of the current column into the next column, replacing its contents, and move the cursor there. Press Cmd+0 or Alt+0 to fill only the empty cells of the current column with zeros, keeping existing frets and the cursor in place.
|
|
63
|
+
|
|
64
|
+
Drag from an empty cell or a blank area of a Sheet to select notes inside a rectangle. Drag any selected note to move the group, keeping its relative columns and strings; occupied destination cells are replaced. Cmd+Right Arrow or Alt+Right Arrow duplicates the selected group immediately after its last column and selects the duplicate. Cmd/Ctrl+C or the Sheet's copy button copies the selection as ASCII; Cmd/Ctrl+V pastes ASCII at the current column, preserving string positions. Delete or Backspace removes the selected notes, and Escape clears the selection.
|
|
65
|
+
|
|
66
|
+
## Initial value
|
|
67
|
+
|
|
68
|
+
Pass the content as data through the `value` property. This is the preferred way to fill the element: no parsing, nothing ambiguous, and it is the same shape `value` and `change` give back.
|
|
69
|
+
|
|
70
|
+
`value` is `(number|{ fret: number|null, color?: string, bold?: boolean, parenthesized?: boolean }|null)[][][]`: an array of Tabs, each an array of Columns, each Column 6 entries from String 1 to String 6, a Fret (0–24) or `null` for an empty String. A note with `parenthesized: true` is displayed and copied in parentheses; `fret: null` is allowed for an empty pair.
|
|
71
|
+
|
|
72
|
+
```html
|
|
73
|
+
<ascii-tabs id="song"></ascii-tabs>
|
|
74
|
+
<script>
|
|
75
|
+
song.value = [
|
|
76
|
+
[ // one Tab
|
|
77
|
+
[null, null, 2, null, 0, null],
|
|
78
|
+
[null, null, null, 2, null, null],
|
|
79
|
+
[null, 3, null, null, null, null],
|
|
80
|
+
[null, null, 2, null, 0, null],
|
|
81
|
+
],
|
|
82
|
+
];
|
|
83
|
+
</script>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The component renders that data as:
|
|
87
|
+
|
|
88
|
+
<img alt="Rendered editor for the initial value example, showing frets on strings 2 through 5 with copy, delete and add controls" src="docs/img/initial-value.png" width="760">
|
|
89
|
+
|
|
90
|
+
`value` can be set before or after the element upgrades. Read it back the same way; every edit fires `change`:
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
song.addEventListener('change', e => console.log(e.detail.value));
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`change` fires immediately when a cell's value changes, including each accepted digit
|
|
97
|
+
(desktop and mobile), Backspace, Delete, Escape, parentheses and bold toggles.
|
|
98
|
+
For a single-cell edit, `detail.note` identifies the edited cell and its new value:
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
song.addEventListener('change', ({ detail: { value, note } }) => {
|
|
102
|
+
save(value); // All Tabs, in the same format as song.value.
|
|
103
|
+
if (note) {
|
|
104
|
+
const { tabIndex, columnIndex, stringIndex, value: editedNote } = note;
|
|
105
|
+
preview(editedNote, { tabIndex, columnIndex, stringIndex });
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
song.addEventListener('cellblur', ({ detail: { value, note } }) => {
|
|
110
|
+
// Same payload when leaving a cell, including an untouched cell.
|
|
111
|
+
commit(value, note);
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Indices are zero-based; `stringIndex: 0` is String 1. `note.value` is a number,
|
|
116
|
+
a note object (including its styles), or `null` after clearing the cell. Both
|
|
117
|
+
`detail.value` and `note.value` are independent copies. Multi-cell operations
|
|
118
|
+
(paste, moves, duplication, selection edits) and Tab additions/removals emit
|
|
119
|
+
`change` with `note: null`. `cellblur` bubbles when keyboard or pointer navigation
|
|
120
|
+
moves to another cell, or when the editor loses focus; clicking the same cell
|
|
121
|
+
does not emit it. These are DOM events: register callbacks with `addEventListener`.
|
|
122
|
+
|
|
123
|
+
Setting `value` does not fire `change`. The component is uncontrolled by default; for the controlled pattern, keep the Tabs in your own state, update it on `change` and write `value` back when the state changes elsewhere:
|
|
124
|
+
|
|
125
|
+
```js
|
|
126
|
+
let state = [[]];
|
|
127
|
+
song.value = state;
|
|
128
|
+
song.addEventListener('change', e => { state = e.detail.value; save(state); });
|
|
129
|
+
function load(next) { state = next; song.value = state; }
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Column annotations
|
|
133
|
+
|
|
134
|
+
Set `annotations` to an array per Tab of `{ column, text }` or `{ column, html, text? }` entries. `column` is
|
|
135
|
+
the zero-based logical Column index in `value`, independent of Spacing and Staff
|
|
136
|
+
breaks. No spaces are needed to align text:
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
song.labels = 'notes';
|
|
140
|
+
song.setDocument({
|
|
141
|
+
value: [[
|
|
142
|
+
[null, null, null, null, null, null],
|
|
143
|
+
[1, 3, 2, 0, null, null],
|
|
144
|
+
[1, 3, 2, 3, null, null],
|
|
145
|
+
[1, 3, 2, null, 0, null],
|
|
146
|
+
]],
|
|
147
|
+
annotations: [[
|
|
148
|
+
{ column: 0, text: 'RE MENOR' },
|
|
149
|
+
{ column: 1, text: 'Dm*' },
|
|
150
|
+
{ column: 2, text: 'Dm/F*' },
|
|
151
|
+
{ column: 3, text: 'Dm/A' },
|
|
152
|
+
]],
|
|
153
|
+
});
|
|
154
|
+
// Update text independently, keeping the editing cursor:
|
|
155
|
+
song.annotations = [[{ column: 1, text: 'Dm' }]];
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Changing the Spacing slider, notation, labels, font or available width keeps each
|
|
159
|
+
Annotation aligned with its Column, including when it moves to another Staff.
|
|
160
|
+
In ASCII layout the text starts at the Column's Fret position; with `columnLayout`
|
|
161
|
+
it starts at the supplied center. Long text wraps within the remaining Staff
|
|
162
|
+
width. Overlapping annotations occupy separate lanes above the Strings, and each
|
|
163
|
+
Staff reserves their full height. Explicit newlines are supported, duplicate
|
|
164
|
+
anchors stack. `text` displays HTML characters literally; `html` renders markup:
|
|
165
|
+
|
|
166
|
+
```js
|
|
167
|
+
song.annotations = [[
|
|
168
|
+
{ column: 1, html: '<strong>Dm</strong><sup>*</sup><br><small>Intro</small>' },
|
|
169
|
+
{ column: 2, html: '<em>Dm/F</em>', text: 'Dm/F' },
|
|
170
|
+
]];
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
When both fields are supplied, `html` controls the display and `text` is the ASCII
|
|
174
|
+
fallback. Without a fallback, browser copy/export extracts text from HTML,
|
|
175
|
+
decoding entities and preserving `<br>` and common block breaks. `format()` outside
|
|
176
|
+
a browser requires `text` for HTML annotations and throws a `TypeError` if it is
|
|
177
|
+
missing. HTML is inserted as supplied; pass trusted or sanitized markup. Normal
|
|
178
|
+
flow HTML participates in collision measurement, including later size changes
|
|
179
|
+
such as loaded images. Arbitrary positioned content can escape that flow.
|
|
180
|
+
|
|
181
|
+
Column indices must be nonnegative integers within the corresponding Tab,
|
|
182
|
+
including explicitly supplied empty Columns. Invalid annotations throw a
|
|
183
|
+
`TypeError` before changing state. Empty display content is hidden; `null` or `[]` clears all
|
|
184
|
+
annotations. Assignment and retrieval copy every entry. Entries are sorted by
|
|
185
|
+
Column, preserving the order of duplicate anchors. Annotations on trailing empty
|
|
186
|
+
Columns keep those Columns in `value` and ASCII output.
|
|
187
|
+
|
|
188
|
+
Setting `value` preserves annotations and requires their anchors to remain valid.
|
|
189
|
+
Use `setDocument({ value, annotations, columnLayout })` to update them atomically;
|
|
190
|
+
omitting `annotations` from `setDocument` clears them. Properties can be assigned
|
|
191
|
+
before upgrade, with `value` supplied first. Annotation updates do not emit
|
|
192
|
+
`change`. Annotations are separate from note data and built-in note storage;
|
|
193
|
+
persist them in consumer state. Deleting a Tab removes its annotations. Editing,
|
|
194
|
+
dragging and duplicating Frets leave annotations attached to their Columns.
|
|
195
|
+
|
|
196
|
+
#### Annotation width
|
|
197
|
+
|
|
198
|
+
`annotation-fit` (property `annotationFit`) decides what an Annotation does to
|
|
199
|
+
the Columns around it, in editable and `readonly` Tabs alike:
|
|
200
|
+
|
|
201
|
+
| Value | Behavior |
|
|
202
|
+
| --- | --- |
|
|
203
|
+
| `widen` (default) | The Annotation's Column grows to hold its text plus one space, so the next Column starts after it, even when that Column has notes and no Annotation. |
|
|
204
|
+
| `overflow` | Columns keep the width of their Frets; long text crosses into the next Column. Overlapping Annotations still stack in separate lanes. |
|
|
205
|
+
| `stagger` | Annotations alternate between two rows, so each can span about two Columns. Only when two Annotations on the same row would touch does the gap between them grow. |
|
|
206
|
+
|
|
207
|
+
```html
|
|
208
|
+
<ascii-tabs annotations-editable annotation-fit="stagger"></ascii-tabs>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Widths use the text (or `text` fallback of an `html` Annotation), measured in
|
|
212
|
+
characters, and are recalculated whenever Annotations, Spacing, notation or the
|
|
213
|
+
available width change. Text longer than a Staff wraps inside it. The setting does
|
|
214
|
+
nothing with `columnLayout`, which keeps its explicit geometry. Copy
|
|
215
|
+
uses the same widths and rows, and a Column grows when an edit is committed. `format(tab, { annotations, annotationFit })` takes
|
|
216
|
+
the same option and defaults to `'overflow'`, so existing output does not change.
|
|
217
|
+
|
|
218
|
+
Whole-Tab copy includes annotations, calculating ASCII offsets from the selected
|
|
219
|
+
Spacing and notation. `format(tab, { annotations: [{ column: 1, text: 'Dm' }] })`
|
|
220
|
+
uses the same anchors; colliding text occupies separate lines. Copying selected
|
|
221
|
+
notes keeps the usual six-String ASCII. ASCII import/paste still accepts numeric
|
|
222
|
+
tablature; remove annotation lines before parsing copied text.
|
|
223
|
+
|
|
224
|
+
### Editing annotations and titles
|
|
225
|
+
|
|
226
|
+
Enable either interaction independently (both default to off):
|
|
227
|
+
|
|
228
|
+
```html
|
|
229
|
+
<ascii-tabs annotations-editable titles-editable></ascii-tabs>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
The matching properties are `annotationsEditable` and `titlesEditable` (booleans).
|
|
233
|
+
Hover a Column to reveal a short horizontal bar above its Strings; click in that
|
|
234
|
+
upper strip to write an annotation. Clicking the Strings still edits Frets. Click
|
|
235
|
+
an existing annotation to edit it. The first annotation at an anchor is edited
|
|
236
|
+
from its strip; stacked annotations can each be clicked separately.
|
|
237
|
+
|
|
238
|
+
Hover the empty space immediately above a Sheet's upper-left corner to reveal
|
|
239
|
+
the same indicator for its title. Titles sit outside the card. Both targets also
|
|
240
|
+
work by touch or keyboard: Tab to the target and press Enter or Space. The editor
|
|
241
|
+
has no surrounding border, just a thin bottom border. Enter or blur saves,
|
|
242
|
+
Escape cancels, and Shift+Enter inserts a newline. Saving empty text removes the
|
|
243
|
+
annotation or clears the title. Typed text is literal; editing an HTML annotation
|
|
244
|
+
replaces that entry with plain text only when its text changes. Cancelling or
|
|
245
|
+
saving unchanged text preserves the HTML.
|
|
246
|
+
|
|
247
|
+
`tabTitles` is an array of strings, one per Tab; missing entries are empty titles.
|
|
248
|
+
Assign `null` or `[]` to clear titles. Titles display even when editing is disabled
|
|
249
|
+
or `readonly` is set. `readonly` disables both interactions. `value` assignments
|
|
250
|
+
preserve titles; `setDocument({ value, annotations, columnLayout, tabTitles })`
|
|
251
|
+
updates the whole document atomically and clears titles when omitted. Removing a
|
|
252
|
+
Tab removes its title; adding a Tab starts with an empty title. Titles are display
|
|
253
|
+
metadata and are not included in ASCII copy/export.
|
|
254
|
+
|
|
255
|
+
```js
|
|
256
|
+
song.tabTitles = ['Estudio en re menor'];
|
|
257
|
+
song.addEventListener('annotationchange', ({ detail }) => {
|
|
258
|
+
// { index: Tab index, column: Column index, annotations: all Tabs' annotations }
|
|
259
|
+
saveAnnotations(detail.annotations);
|
|
260
|
+
});
|
|
261
|
+
song.addEventListener('titlechange', ({ detail }) => {
|
|
262
|
+
// { index: Tab index, title: updated text, tabTitles: all Tabs' titles }
|
|
263
|
+
saveTitles(detail.tabTitles);
|
|
264
|
+
});
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
These events bubble and fire only for user edits that change content. Programmatic
|
|
268
|
+
assignments are silent. They do not emit the component's note `change` event.
|
|
269
|
+
Persist annotations and titles in consumer state; built-in storage contains notes.
|
|
270
|
+
Annotating an empty visual Column makes it a logical Column in `value` so its
|
|
271
|
+
anchor stays valid. Programmatic updates to the document or metadata, disabling
|
|
272
|
+
editing, removing a Tab, and disconnecting cancel any open text editor.
|
|
273
|
+
|
|
274
|
+
### Aligning with a score
|
|
275
|
+
|
|
276
|
+
Use external geometry when a score renderer owns horizontal spacing. Update the
|
|
277
|
+
content and geometry together, especially when the number of Columns changes:
|
|
278
|
+
|
|
279
|
+
```js
|
|
280
|
+
song.setDocument({
|
|
281
|
+
value: [[
|
|
282
|
+
[5, 7, null, null, null, null],
|
|
283
|
+
[null, null, null, null, 0, null],
|
|
284
|
+
[12, null, null, null, null, null],
|
|
285
|
+
[null, null, null, null, null, null],
|
|
286
|
+
]],
|
|
287
|
+
columnLayout: [{ width: 640, positions: [84.5, 132, 201.25, 290] }],
|
|
288
|
+
});
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
`columnLayout` has one `{ width, positions }` entry per Tab. Each `positions[c]`
|
|
292
|
+
is the center of Column `c` in CSS pixels from the left edge of the String area
|
|
293
|
+
(`.ascii-tabs-area`). `width` excludes labels, Sheet padding and tools. Align that
|
|
294
|
+
area's origin with the score and translate score coordinates into CSS pixels.
|
|
295
|
+
Labels occupy their own gutter. Frets, note names and parentheses are centered
|
|
296
|
+
independently of their text width. Set `--ascii-tabs-fret-background` to match the
|
|
297
|
+
surface behind the Frets; it defaults to `--ascii-tabs-sheet`.
|
|
298
|
+
|
|
299
|
+
Widths must be finite and positive; positions must be finite, strictly increasing,
|
|
300
|
+
within `[0, width]`, and match the logical Column count, including trailing empty
|
|
301
|
+
Columns. A Tab with zero Columns uses `positions: []` and still shows six Strings.
|
|
302
|
+
Invalid geometry or mismatched content throws a descriptive `TypeError` before
|
|
303
|
+
changing state. Geometry is copied on assignment and retrieval.
|
|
304
|
+
|
|
305
|
+
Each Tab stays in one Staff, with horizontal scrolling when needed and no automatic
|
|
306
|
+
padding or wrapping. `value` and `change.detail.value` preserve all Columns.
|
|
307
|
+
Editing, mobile input, selection and dragging retain their logical indices.
|
|
308
|
+
Clicks and drop targets choose the nearest center within the String area; exact
|
|
309
|
+
ties choose the earlier Column. Moving or duplicating Frets within existing
|
|
310
|
+
Columns is allowed. Paste, duplication and group moves that exceed the available
|
|
311
|
+
Columns are rejected in full. Navigation stops at the first/last Column; deleting
|
|
312
|
+
Frets leaves their Columns in place. Add/delete Tab buttons are disabled, and
|
|
313
|
+
`addTab()`/`removeTab()` do nothing: structure belongs to the consumer.
|
|
314
|
+
|
|
315
|
+
Assign `song.columnLayout` to reposition existing Columns without resetting the
|
|
316
|
+
cursor or typing. Assigning `song.value` while geometry is active requires the same
|
|
317
|
+
Tab/Column counts; use `setDocument({ value, columnLayout })` to change both
|
|
318
|
+
atomically. Programmatic assignments do not emit `change`. Properties may also be
|
|
319
|
+
assigned before custom-element upgrade (supply both `value` and `columnLayout`).
|
|
320
|
+
Built-in storage is suspended while external geometry is active: it neither loads
|
|
321
|
+
nor overwrites the ASCII store. The consumer owns persistence.
|
|
322
|
+
|
|
323
|
+
```js
|
|
324
|
+
song.addEventListener('layoutoverflow', ({ detail }) => {
|
|
325
|
+
// { index, width, collisions }
|
|
326
|
+
// Recalculate score spacing, then provide new positions and width.
|
|
327
|
+
console.log(detail);
|
|
328
|
+
});
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
`layoutoverflow` bubbles after rendering when displayed Frets overlap on a String
|
|
332
|
+
or extend past the area's edges. Each collision has a zero-based `string`,
|
|
333
|
+
`columns` indices and an `overlap` in CSS pixels; edge collisions also have
|
|
334
|
+
`edge: 'left' | 'right'`. Identical reports are suppressed until geometry or
|
|
335
|
+
collisions change. Centers never move automatically. Increasing only `width`
|
|
336
|
+
does not separate existing centers: recalculate positions too.
|
|
337
|
+
|
|
338
|
+
Set `song.columnLayout = null` to restore ASCII layout, wrapping, storage and
|
|
339
|
+
editing. `setDocument({ value })` also selects ASCII layout. Copy still uses
|
|
340
|
+
`format()` and exports ordinary ASCII with the chosen Spacing and notation;
|
|
341
|
+
pixel positions and trailing empty Columns are not preserved in copied text.
|
|
342
|
+
Geometry is never stored inside `Tab[]`.
|
|
343
|
+
|
|
344
|
+
### Per-note colors and bold
|
|
345
|
+
|
|
346
|
+
Use an object instead of a number to give a note its own CSS color:
|
|
347
|
+
|
|
348
|
+
```js
|
|
349
|
+
song.value = [[
|
|
350
|
+
[{ fret: 3, color: '#e63946' }, null, null, null, null, null],
|
|
351
|
+
[5, { fret: 2, color: 'royalblue' }, null, null, null, null],
|
|
352
|
+
]];
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Numbers still use the default color. Objects require a valid `fret` (0–24); omitted or invalid colors use the default. Colors are preserved in `value`, `change`, storage, edits, moves and duplicates. Returned objects are independent copies. Color applies only to the note, including while focused; string dashes keep their theme color. CSS variables such as `var(--melody-color)` work too.
|
|
356
|
+
|
|
357
|
+
Set `bold: true` for bold text or `bold: false` for normal weight. Omit `bold` to keep the existing default (bold). You can combine it with `color`:
|
|
358
|
+
|
|
359
|
+
```js
|
|
360
|
+
song.value = [[
|
|
361
|
+
[{ fret: 3, bold: true, color: '#b91c1c' }, null, null, null, null, null],
|
|
362
|
+
[{ fret: 5, bold: false }, null, null, null, null, null],
|
|
363
|
+
]];
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Press `b` to toggle each selected note's bold state, or the current cell's note when there is no selection. Empty cells stay empty. Notes without an explicit `bold` value start bold, so the first press makes them normal weight.
|
|
367
|
+
|
|
368
|
+
`bold` accepts booleans only and follows the note through edits, moves, duplication, `value`, `change` and storage. Copying as ASCII discards font weight.
|
|
369
|
+
|
|
370
|
+
Colors are available through structured data only. ASCII input has no color information, and copying or calling `format` exports plain text without colors.
|
|
371
|
+
|
|
372
|
+
Type `(` (Shift plus the opening-parenthesis key for your keyboard layout) in a cell to wrap its note in parentheses. An empty cell gets an empty pair with the caret inside, ready for a fret number. Repeating the shortcut keeps a single pair. Parentheses survive storage, ASCII copy/paste, and notation changes.
|
|
373
|
+
|
|
374
|
+
To erase a parenthesized note, press Delete/Supr: this clears the entire cell, including the parentheses. Backspace removes digits inside the parentheses and leaves `()` when the last digit is erased. Escape restores the cell's value before the current edit, so it also undoes parentheses just added. There is currently no shortcut to remove only the parentheses while keeping the note.
|
|
375
|
+
|
|
376
|
+
If all you have is ASCII text, put it in a `<pre>` instead (see below).
|
|
377
|
+
|
|
378
|
+
## ASCII in and out
|
|
379
|
+
|
|
380
|
+
As an alternative to `value`, a `<pre>` per Tab becomes the initial value; it is ignored when `value` is set. Parsing is lenient: number labels (`1 `–`6 `) and note labels (`e|B|G|D|A|E|`), any number of dashes. Frets at the same horizontal position share a Column, and several Staves are joined into one Tab.
|
|
381
|
+
|
|
382
|
+
```html
|
|
383
|
+
<ascii-tabs>
|
|
384
|
+
<pre>
|
|
385
|
+
1 ---------
|
|
386
|
+
2 ---------
|
|
387
|
+
3 -2---2---
|
|
388
|
+
4 ---------
|
|
389
|
+
5 -0---0---
|
|
390
|
+
6 ---------</pre>
|
|
391
|
+
<pre>
|
|
392
|
+
e|-0--3--|
|
|
393
|
+
B|-1--0--|
|
|
394
|
+
G|-0--0--|
|
|
395
|
+
D|-2--0--|
|
|
396
|
+
A|-3--2--|
|
|
397
|
+
E|-----3-|</pre>
|
|
398
|
+
</ascii-tabs>
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
The copy button copies the same plain ASCII. `parse` and `format` are exported as pure functions:
|
|
402
|
+
|
|
403
|
+
```js
|
|
404
|
+
import { parse, format } from 'https://cdn.jsdelivr.net/gh/libasoles/ascii-tabs@0.10.3/ascii-tabs.js';
|
|
405
|
+
|
|
406
|
+
const tab = parse('1 -3--5-\n2 ------\n3 ------\n4 ------\n5 ------\n6 ------'); // one Tab: Column[]
|
|
407
|
+
format(tab, { spacing: 2 }); // "1 -3--5-\n2 ------\n..."
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
`format` writes number labels, Spacing 2 by default, widens a Column that holds a two-digit Fret on every String, and cuts each Staff right after its last Column. Malformed ASCII logs a console error and parses to an empty Tab.
|
|
411
|
+
|
|
412
|
+
## Composition
|
|
413
|
+
|
|
414
|
+
Features are turned on by composing parts, not by boolean attributes ([ADR 0001](docs/adr/0001-composition-over-boolean-props.md)). With no part children you get the default composition: hint, a Sheet with copy and delete, and add (read-only: just a Sheet with copy). With any part child, only the parts you declare appear. Content inside a part replaces its default icon or text.
|
|
415
|
+
|
|
416
|
+
```html
|
|
417
|
+
<ascii-tabs>
|
|
418
|
+
<ascii-tabs-hint></ascii-tabs-hint>
|
|
419
|
+
<ascii-tabs-sheet>
|
|
420
|
+
<ascii-tabs-copy></ascii-tabs-copy>
|
|
421
|
+
<ascii-tabs-delete></ascii-tabs-delete>
|
|
422
|
+
</ascii-tabs-sheet>
|
|
423
|
+
<ascii-tabs-add></ascii-tabs-add>
|
|
424
|
+
</ascii-tabs>
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### Read-only
|
|
428
|
+
|
|
429
|
+
```html
|
|
430
|
+
<ascii-tabs id="song" readonly></ascii-tabs>
|
|
431
|
+
<script>
|
|
432
|
+
song.value = [[[3, null, null, null, null, null]]];
|
|
433
|
+
</script>
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
`readonly` disables editing. Hint, delete and add hide themselves even when declared; copy still works. Like the editable element, every Staff runs the full width of its Sheet.
|
|
437
|
+
|
|
438
|
+
Elements with no width (including those inside `hidden` or `display: none` panels) defer
|
|
439
|
+
Staffs and annotations until `ResizeObserver` detects a visible size. Properties and
|
|
440
|
+
getters remain available while hidden. Showing an unchanged panel at the same width
|
|
441
|
+
reuses its rendered content; changes made while hidden appear on reveal.
|
|
442
|
+
|
|
443
|
+
### Copy, hint, add and delete
|
|
444
|
+
|
|
445
|
+
```html
|
|
446
|
+
<!-- No copy button -->
|
|
447
|
+
<ascii-tabs><ascii-tabs-sheet></ascii-tabs-sheet></ascii-tabs>
|
|
448
|
+
|
|
449
|
+
<!-- Custom copy text, custom hint -->
|
|
450
|
+
<ascii-tabs>
|
|
451
|
+
<ascii-tabs-hint>Pick a string, then type a fret.</ascii-tabs-hint>
|
|
452
|
+
<ascii-tabs-sheet><ascii-tabs-copy>Copy ASCII</ascii-tabs-copy></ascii-tabs-sheet>
|
|
453
|
+
<ascii-tabs-add>+ New tab</ascii-tabs-add>
|
|
454
|
+
</ascii-tabs>
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Delete never asks for confirmation. With several Tabs it removes its Tab; with one Tab it clears it, and is disabled while that Tab is empty.
|
|
458
|
+
|
|
459
|
+
The delete button emits a cancelable `delete` event on `<ascii-tabs>` before it acts. This gives consumers three choices: do nothing to keep the default, add a listener for an extra effect, or cancel the event and take over the action. `event.detail.index` is the Tab targeted by the button.
|
|
460
|
+
|
|
461
|
+
```js
|
|
462
|
+
// Keep the default and complement it (for example, analytics).
|
|
463
|
+
tabs.addEventListener('delete', event => {
|
|
464
|
+
console.log('Deleting tab', event.detail.index);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
// Override the default—for an asynchronous confirmation, cancel first and
|
|
468
|
+
// invoke the public method only after confirmation succeeds.
|
|
469
|
+
tabs.addEventListener('delete', async event => {
|
|
470
|
+
event.preventDefault();
|
|
471
|
+
if (await confirmDeletion()) tabs.removeTab(event.detail.index);
|
|
472
|
+
});
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
When one empty Tab remains, the default button is disabled because there is no Tab for it to remove. Add `enabled` to the delete part when the host needs to handle that click—for example, to remove a surrounding songbook block. The default behavior still does nothing in that state; cancel the event to replace it.
|
|
476
|
+
|
|
477
|
+
```html
|
|
478
|
+
<ascii-tabs>
|
|
479
|
+
<ascii-tabs-sheet><ascii-tabs-delete enabled></ascii-tabs-delete></ascii-tabs-sheet>
|
|
480
|
+
</ascii-tabs>
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Custom controls
|
|
484
|
+
|
|
485
|
+
Skip the parts and call the root's methods:
|
|
486
|
+
|
|
487
|
+
```html
|
|
488
|
+
<ascii-tabs id="tabs"></ascii-tabs>
|
|
489
|
+
<button onclick="tabs.copy(0)">Copy</button>
|
|
490
|
+
<button onclick="tabs.addTab()">Add</button>
|
|
491
|
+
<button onclick="tabs.removeTab(0)">Delete</button>
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
`copy(index)`, `addTab()` and `removeTab(index)` behave like the built-in buttons, including the delete rules.
|
|
495
|
+
|
|
496
|
+
## Spacing
|
|
497
|
+
|
|
498
|
+
Spacing is the number of dashes between two Columns' Frets: `spacing="N"` (1–6, clamped, default 2), also available as the `spacing` property. Add the optional slider part to let people change it; it is not in the default composition and also works in `readonly`. Spacing changes the rendered Tab and the copied text, and a Column with a two-digit Fret is still one character wider on every String.
|
|
499
|
+
|
|
500
|
+
```html
|
|
501
|
+
<!-- Initial spacing, no slider -->
|
|
502
|
+
<ascii-tabs spacing="3"></ascii-tabs>
|
|
503
|
+
|
|
504
|
+
<!-- Slider from 1 to 6 (put text inside to replace its label) -->
|
|
505
|
+
<ascii-tabs>
|
|
506
|
+
<ascii-tabs-spacing></ascii-tabs-spacing>
|
|
507
|
+
<ascii-tabs-sheet><ascii-tabs-copy></ascii-tabs-copy></ascii-tabs-sheet>
|
|
508
|
+
</ascii-tabs>
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
## Note notation
|
|
512
|
+
|
|
513
|
+
`notation` changes the displayed and copied cell text: `frets` (default), `letters`
|
|
514
|
+
(`C D E F G A B`), or `solfege` (lowercase `do re mi fa sol la si`). Notes default to
|
|
515
|
+
standard guitar tuning, String 1 to 6: E B G D A E. For example, Fret 0 on
|
|
516
|
+
String 1 is `E` or `mi`, and Fret 3 is `G` or `sol`. Accidentals use sharps
|
|
517
|
+
(`F#`, `fa#`); octaves share the same name.
|
|
518
|
+
|
|
519
|
+
```html
|
|
520
|
+
<ascii-tabs notation="letters"></ascii-tabs>
|
|
521
|
+
<ascii-tabs notation="solfege" lang="es"></ascii-tabs>
|
|
522
|
+
|
|
523
|
+
<!-- Optional selector, also available in read-only mode -->
|
|
524
|
+
<ascii-tabs notation="solfege" lang="es">
|
|
525
|
+
<ascii-tabs-notation></ascii-tabs-notation>
|
|
526
|
+
<ascii-tabs-spacing></ascii-tabs-spacing>
|
|
527
|
+
<ascii-tabs-hint></ascii-tabs-hint>
|
|
528
|
+
<ascii-tabs-sheet><ascii-tabs-copy></ascii-tabs-copy></ascii-tabs-sheet>
|
|
529
|
+
</ascii-tabs>
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
Also available as the `notation` property and the `format(tab, { notation: 'solfege' })`
|
|
533
|
+
option. Every Column uses its longest displayed name (including `sol#`) on all six
|
|
534
|
+
Strings, keeping chords aligned and wrapping by the actual text width. Spacing
|
|
535
|
+
still adds the configured dashes around each Column.
|
|
536
|
+
|
|
537
|
+
Editing, `value`, storage and `change` keep numeric Frets (0–24). Type fret
|
|
538
|
+
numbers in every notation. `lang` controls interface text independently of note
|
|
539
|
+
notation, and `labels` still controls the String labels. The copy button exports
|
|
540
|
+
the selected notation. Import and paste accept numeric tablature only: note names
|
|
541
|
+
without octaves cannot identify the original Fret (for example, 0, 12 and 24).
|
|
542
|
+
Switch to `frets` before copying when you need to paste the result back.
|
|
543
|
+
|
|
544
|
+
## Tuning
|
|
545
|
+
|
|
546
|
+
Set `tuning` to six open-string note names, in order from String 1 (highest)
|
|
547
|
+
to String 6 (lowest). It affects displayed and copied note names and
|
|
548
|
+
`labels="notes"`; Frets in `value` and storage stay unchanged.
|
|
549
|
+
|
|
550
|
+
```html
|
|
551
|
+
<!-- Drop D: lower only String 6 from E to D -->
|
|
552
|
+
<ascii-tabs tuning="E B G D A D" notation="letters" labels="notes"></ascii-tabs>
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
```js
|
|
556
|
+
tabs.tuning = ['E', 'B', 'G', 'D', 'A', 'D'];
|
|
557
|
+
format(tab, { tuning: ['E', 'B', 'G', 'D', 'A', 'D'], notation: 'solfege' });
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
The property and `format` option accept an array or a space-separated string.
|
|
561
|
+
The getter returns a fresh array. Names accept A–G (case insensitive), optionally
|
|
562
|
+
followed by `#` or `b`, without octave numbers. Flats are normalized to sharps.
|
|
563
|
+
Missing, invalid or incomplete tunings use standard tuning (`E B G D A E`).
|
|
564
|
+
Changing or removing the attribute updates the view immediately.
|
|
565
|
+
|
|
566
|
+
## String labels
|
|
567
|
+
|
|
568
|
+
Strings are labelled `1`–`6` by default, on screen and in copied text. `labels="notes"` names them using `tuning` (standard tuning by default: `e B G D A E`, String 1 to 6). `labels="none"` hides the labels and uses their space for Columns. This also applies to copied text. The `labels` property accepts `"numbers"`, `"notes"`, or `"none"`; other values fall back to numbers. Parsing accepts all three styles whatever the attribute says.
|
|
569
|
+
|
|
570
|
+
```html
|
|
571
|
+
<ascii-tabs></ascii-tabs> <!-- 1 -3--5- -->
|
|
572
|
+
<ascii-tabs labels="notes"></ascii-tabs> <!-- e|-3--5- -->
|
|
573
|
+
<ascii-tabs labels="none"></ascii-tabs> <!-- -3--5- -->
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
## Tool position
|
|
577
|
+
|
|
578
|
+
A Sheet's tools (copy, delete) stand in a column to the right of the Staves by default, and the Staves leave room for them. `tools="top"` lines them up in a row above the first String instead. Also available as the `tools` property.
|
|
579
|
+
|
|
580
|
+
```html
|
|
581
|
+
<ascii-tabs></ascii-tabs> <!-- column beside the Staves -->
|
|
582
|
+
<ascii-tabs tools="top"></ascii-tabs> <!-- row above them -->
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
## Sheet variant
|
|
586
|
+
|
|
587
|
+
Sheets look like cards by default. `variant="flat"` removes their background, border, and padding so the Tab can blend into its surrounding layout. It is also available as the `variant` property.
|
|
588
|
+
|
|
589
|
+
In the flat variant, each Staff has a transparent background in both light and
|
|
590
|
+
dark themes. Override `--ascii-tabs-flat-background` to give it a custom surface.
|
|
591
|
+
|
|
592
|
+
```html
|
|
593
|
+
<ascii-tabs variant="flat"></ascii-tabs>
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
## Themes
|
|
597
|
+
|
|
598
|
+
The element renders in the light DOM ([ADR 0002](docs/adr/0002-light-dom.md)) and paints only its Sheets, never the page background. Without `theme` it follows `prefers-color-scheme`; `theme="light"` or `theme="dark"` forces a palette. Every colour is a custom property you can override on the element or any ancestor:
|
|
599
|
+
|
|
600
|
+
| Property | Light | Dark |
|
|
601
|
+
|---|---|---|
|
|
602
|
+
| `--ascii-tabs-paper` | `#fdfaf5` | `#1b1a18` |
|
|
603
|
+
| `--ascii-tabs-sheet` | `#fff` | `#232220` |
|
|
604
|
+
| `--ascii-tabs-flat-background` | `transparent` | `transparent` |
|
|
605
|
+
| `--ascii-tabs-ink` | `#1a1a1a` | `#f1ede4` |
|
|
606
|
+
| `--ascii-tabs-dash` | `#6f6c66` | `#959189` |
|
|
607
|
+
| `--ascii-tabs-string` | `#b3ada1` | `#b3ada1` |
|
|
608
|
+
| `--ascii-tabs-line` | `#e7e2d6` | `#36342f` |
|
|
609
|
+
| `--ascii-tabs-hover` | `#f4efe4` | `#2c2a27` |
|
|
610
|
+
| `--ascii-tabs-focus` | `#f6e4df` | `#3a2a1d` |
|
|
611
|
+
| `--ascii-tabs-accent` | `#8b0000` | `#fb923c` |
|
|
612
|
+
|
|
613
|
+
`--ascii-tabs-font` and `--ascii-tabs-font-size` set the typography. The font
|
|
614
|
+
size defaults to `22px`, but its custom property is not set on the component,
|
|
615
|
+
so it can be inherited from an ancestor:
|
|
616
|
+
|
|
617
|
+
```css
|
|
618
|
+
.compact-tabs { --ascii-tabs-font-size: 1em; }
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
`--ascii-tabs-string` colours the visual guitar strings (the dashes). They are hidden from assistive technology because they are diagrammatic decoration; labels, Frets, hints, and controls continue to use the AA-contrast text colours.
|
|
622
|
+
|
|
623
|
+
```html
|
|
624
|
+
<ascii-tabs></ascii-tabs> <!-- auto -->
|
|
625
|
+
<ascii-tabs theme="light"></ascii-tabs>
|
|
626
|
+
<ascii-tabs theme="dark"></ascii-tabs>
|
|
627
|
+
<ascii-tabs style="--ascii-tabs-accent: teal; --ascii-tabs-sheet: #f0fafa"></ascii-tabs>
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
## Text
|
|
631
|
+
|
|
632
|
+
Built-in text is English. `lang="es"` switches every built-in string to Spanish. The `messages` property takes a partial object that is merged over the active language; content you put inside a part still wins over `messages`.
|
|
633
|
+
|
|
634
|
+
```html
|
|
635
|
+
<ascii-tabs></ascii-tabs> <!-- "Click a string and type the fret number." -->
|
|
636
|
+
<ascii-tabs lang="es"></ascii-tabs> <!-- "Hacé clic en una cuerda y escribí el número de traste." -->
|
|
637
|
+
|
|
638
|
+
<ascii-tabs id="tabs"></ascii-tabs>
|
|
639
|
+
<script>
|
|
640
|
+
tabs.messages = { hint: 'Tap a string, then type a fret.', copy: 'Copy as ASCII' };
|
|
641
|
+
</script>
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
Keys: `hint`, `copy`, `copied`, `delete`, `add`, `fret`, `spacing`, `notation`, `frets`, `letters`, `solfege`.
|
|
645
|
+
|
|
646
|
+
## Storage
|
|
647
|
+
|
|
648
|
+
Nothing is stored unless you add the storage part. `<ascii-tabs-storage key="…">` loads the Tabs from `localStorage` under that key on start and saves them on every change. Stored Tabs take precedence over an initial `value` and `<pre>` content; an empty or unreadable store falls back to them. Storage failures (private mode, quota) are ignored. Two instances with different keys don't interfere.
|
|
649
|
+
|
|
650
|
+
```html
|
|
651
|
+
<ascii-tabs>
|
|
652
|
+
<ascii-tabs-storage key="my-tabs"></ascii-tabs-storage>
|
|
653
|
+
</ascii-tabs>
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
A root whose only part child is the storage part still gets the default composition. The stored format is Tabs → Columns → 6 cells, each `""` or the Fret as text (as in the original editor); colored notes are stored as objects. Reading also accepts numbers and a single Tab.
|
|
657
|
+
|
|
658
|
+
MIT licensed.
|