text-guitar-chart 0.1.2 → 0.2.0

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.
@@ -4,11 +4,13 @@ import fingeringToString from './fingeringToString.js';
4
4
  import { SVGuitarChord } from 'svguitar';
5
5
 
6
6
  /**
7
- * Available colors for dots: red and black only
7
+ * Available colors for dots
8
8
  */
9
9
  export const DOT_COLORS = {
10
10
  RED: '#e74c3c',
11
- BLACK: '#000000'
11
+ BLACK: '#000000',
12
+ GREY: '#9B9B9B',
13
+ BLUE: '#4A90E2'
12
14
  };
13
15
 
14
16
  /**
@@ -110,7 +112,7 @@ export class EditableSVGuitarChord {
110
112
  padding: 20px;
111
113
  box-shadow: 0 4px 20px rgba(0,0,0,0.3);
112
114
  z-index: 1000;
113
- min-width: 280px;
115
+ min-width: 220px;
114
116
  `;
115
117
 
116
118
  const title = document.createElement('h3');
@@ -122,35 +124,39 @@ export class EditableSVGuitarChord {
122
124
  titleSection.style.cssText = 'margin-bottom: 15px;';
123
125
 
124
126
  const titleLabel = document.createElement('label');
125
- titleLabel.textContent = 'Title (optional): ';
126
- titleLabel.style.cssText = 'display: block; margin-bottom: 5px; font-weight: bold;';
127
+ titleLabel.textContent = 'Title: ';
128
+ titleLabel.htmlFor = 'editable-svguitar-title-input';
129
+ titleLabel.style.cssText = 'display: inline-block; margin-bottom: 5px; font-weight: bold; width: 80px;';
127
130
 
128
131
  this.titleInput = document.createElement('input');
132
+ this.titleInput.id = 'editable-svguitar-title-input';
129
133
  this.titleInput.type = 'text';
130
134
  this.titleInput.placeholder = 'e.g. A min';
131
135
  this.titleInput.maxLength = 10;
132
136
  this.titleInput.style.cssText = 'width: 10em; padding: 6px; border: 1px solid #ccc; border-radius: 3px; box-sizing: border-box;';
133
137
 
134
- titleLabel.appendChild(this.titleInput);
135
138
  titleSection.appendChild(titleLabel);
139
+ titleSection.appendChild(this.titleInput);
136
140
 
137
141
  // Position input
138
142
  const positionSection = document.createElement('div');
139
143
  positionSection.style.cssText = 'margin-bottom: 15px;';
140
144
 
141
145
  const positionLabel = document.createElement('label');
142
- positionLabel.textContent = 'Position (optional): ';
143
- positionLabel.style.cssText = 'display: block; margin-bottom: 5px; font-weight: bold;';
146
+ positionLabel.textContent = 'Position: ';
147
+ positionLabel.htmlFor = 'editable-svguitar-position-input';
148
+ positionLabel.style.cssText = 'display: inline-block; margin-bottom: 5px; font-weight: bold; width: 80px;';
144
149
 
145
150
  this.positionInput = document.createElement('input');
151
+ this.positionInput.id = 'editable-svguitar-position-input';
146
152
  this.positionInput.type = 'number';
147
153
  this.positionInput.min = '1';
148
154
  this.positionInput.max = '30';
149
155
  this.positionInput.placeholder = '1-30';
150
156
  this.positionInput.style.cssText = 'width: 5em; padding: 6px; border: 1px solid #ccc; border-radius: 3px; box-sizing: border-box;';
151
157
 
152
- positionLabel.appendChild(this.positionInput);
153
158
  positionSection.appendChild(positionLabel);
159
+ positionSection.appendChild(this.positionInput);
154
160
 
155
161
  // Buttons
156
162
  const buttonDiv = document.createElement('div');
@@ -208,7 +214,7 @@ export class EditableSVGuitarChord {
208
214
  padding: 20px;
209
215
  box-shadow: 0 4px 20px rgba(0,0,0,0.3);
210
216
  z-index: 1000;
211
- min-width: 250px;
217
+ min-width: 200px;
212
218
  `;
213
219
 
214
220
  const title = document.createElement('h3');
@@ -219,72 +225,112 @@ export class EditableSVGuitarChord {
219
225
  const colorSection = document.createElement('div');
220
226
  colorSection.style.cssText = 'margin-bottom: 15px;';
221
227
 
222
- const colorLabel = document.createElement('div');
223
- colorLabel.textContent = 'Color:';
224
- colorLabel.style.cssText = 'font-weight: bold; margin-bottom: 8px;';
225
- colorSection.appendChild(colorLabel);
226
-
227
228
  const colorOptions = document.createElement('div');
228
- colorOptions.style.cssText = 'display: flex; gap: 15px;';
229
+ colorOptions.style.cssText = 'display: flex; gap: 8px; align-items: center;';
230
+
231
+ const hiddenRadioStyle =
232
+ 'position:absolute;opacity:0;width:0;height:0;';
233
+ const swatchStyle = (color) =>
234
+ 'width:28px;height:28px;display:block;' +
235
+ `background:${color};` +
236
+ 'border-radius:3px;cursor:pointer;box-sizing:border-box;';
229
237
 
230
238
  // Red option
231
239
  const redOption = document.createElement('label');
232
- redOption.style.cssText = 'display: flex; align-items: center; cursor: pointer;';
233
-
240
+ redOption.style.cssText =
241
+ 'display:inline-block;position:relative;cursor:pointer;';
242
+
234
243
  this.redRadio = document.createElement('input');
235
244
  this.redRadio.type = 'radio';
236
245
  this.redRadio.name = 'dotColor';
237
246
  this.redRadio.value = DOT_COLORS.RED;
247
+ this.redRadio.style.cssText = hiddenRadioStyle;
238
248
  this.redRadio.addEventListener('change', () => this.updateDotColor());
239
-
240
- const redLabel = document.createElement('span');
241
- redLabel.textContent = 'Red';
242
- redLabel.style.cssText = 'margin-left: 5px; color: #e74c3c; font-weight: bold;';
243
-
244
- redOption.appendChild(this.redRadio);
245
- redOption.appendChild(redLabel);
246
249
 
247
- // Black option
250
+ const redSwatch = document.createElement('span');
251
+ redSwatch.className = 'color-swatch';
252
+ redSwatch.style.cssText = swatchStyle(DOT_COLORS.RED);
253
+
254
+ redOption.appendChild(this.redRadio);
255
+ redOption.appendChild(redSwatch);
256
+
257
+ // Grey option
258
+ const greyOption = document.createElement('label');
259
+ greyOption.style.cssText =
260
+ 'display:inline-block;position:relative;cursor:pointer;';
261
+
262
+ this.greyRadio = document.createElement('input');
263
+ this.greyRadio.type = 'radio';
264
+ this.greyRadio.name = 'dotColor';
265
+ this.greyRadio.value = DOT_COLORS.GREY;
266
+ this.greyRadio.style.cssText = hiddenRadioStyle;
267
+ this.greyRadio.addEventListener('change', () => this.updateDotColor());
268
+
269
+ const greySwatch = document.createElement('span');
270
+ greySwatch.className = 'color-swatch';
271
+ greySwatch.style.cssText = swatchStyle(DOT_COLORS.GREY);
272
+
273
+ greyOption.appendChild(this.greyRadio);
274
+ greyOption.appendChild(greySwatch);
275
+
276
+ // Blue option
277
+ const blueOption = document.createElement('label');
278
+ blueOption.style.cssText =
279
+ 'display:inline-block;position:relative;cursor:pointer;';
280
+
281
+ this.blueRadio = document.createElement('input');
282
+ this.blueRadio.type = 'radio';
283
+ this.blueRadio.name = 'dotColor';
284
+ this.blueRadio.value = DOT_COLORS.BLUE;
285
+ this.blueRadio.style.cssText = hiddenRadioStyle;
286
+ this.blueRadio.addEventListener('change', () => this.updateDotColor());
287
+
288
+ const blueSwatch = document.createElement('span');
289
+ blueSwatch.className = 'color-swatch';
290
+ blueSwatch.style.cssText = swatchStyle(DOT_COLORS.BLUE);
291
+
292
+ blueOption.appendChild(this.blueRadio);
293
+ blueOption.appendChild(blueSwatch);
294
+
295
+ // Black option
248
296
  const blackOption = document.createElement('label');
249
- blackOption.style.cssText = 'display: flex; align-items: center; cursor: pointer;';
250
-
297
+ blackOption.style.cssText =
298
+ 'display:inline-block;position:relative;cursor:pointer;';
299
+
251
300
  this.blackRadio = document.createElement('input');
252
301
  this.blackRadio.type = 'radio';
253
302
  this.blackRadio.name = 'dotColor';
254
303
  this.blackRadio.value = DOT_COLORS.BLACK;
255
304
  this.blackRadio.checked = true; // Default to black
305
+ this.blackRadio.style.cssText = hiddenRadioStyle;
256
306
  this.blackRadio.addEventListener('change', () => this.updateDotColor());
257
-
258
- const blackLabel = document.createElement('span');
259
- blackLabel.textContent = 'Black';
260
- blackLabel.style.cssText = 'margin-left: 5px; color: #000000; font-weight: bold;';
261
-
307
+
308
+ const blackSwatch = document.createElement('span');
309
+ blackSwatch.className = 'color-swatch';
310
+ blackSwatch.style.cssText = swatchStyle(DOT_COLORS.BLACK);
311
+
262
312
  blackOption.appendChild(this.blackRadio);
263
- blackOption.appendChild(blackLabel);
264
-
313
+ blackOption.appendChild(blackSwatch);
314
+
265
315
  colorOptions.appendChild(redOption);
316
+ colorOptions.appendChild(greyOption);
317
+ colorOptions.appendChild(blueOption);
266
318
  colorOptions.appendChild(blackOption);
267
- colorSection.appendChild(colorOptions);
268
319
 
269
- // Text input (conditional on black color)
270
- this.textSection = document.createElement('div');
271
- this.textSection.style.cssText = 'margin-bottom: 15px;';
272
-
273
- const textLabel = document.createElement('label');
274
- textLabel.textContent = 'Text (optional): ';
275
- textLabel.style.cssText = 'display: block; margin-bottom: 5px; font-weight: bold;';
276
-
320
+ // Text input inline with color swatches (visible only for black)
277
321
  this.textInput = document.createElement('input');
278
322
  this.textInput.type = 'text';
279
- this.textInput.maxLength = 2; // Reduced from 3 to 2
280
- this.textInput.placeholder = '1-2 chars';
281
- this.textInput.style.cssText = 'width: 60px; padding: 4px; border: 1px solid #ccc; border-radius: 3px;';
282
-
323
+ this.textInput.maxLength = 1;
324
+ this.textInput.placeholder = 'Aa';
325
+ this.textInput.style.cssText =
326
+ 'width:32px;height:28px;padding:0 4px;margin-left:4px;' +
327
+ 'border:1px solid #ccc;border-radius:3px;box-sizing:border-box;';
328
+
283
329
  // Add real-time text change listener
284
330
  this.textInput.addEventListener('input', () => this.updateDotText());
285
-
286
- textLabel.appendChild(this.textInput);
287
- this.textSection.appendChild(textLabel);
331
+
332
+ colorOptions.appendChild(this.textInput);
333
+ colorSection.appendChild(colorOptions);
288
334
 
289
335
  const buttonDiv = document.createElement('div');
290
336
  buttonDiv.style.cssText = 'display: flex; gap: 10px; justify-content: flex-end;';
@@ -304,7 +350,6 @@ export class EditableSVGuitarChord {
304
350
 
305
351
  this.dialog.appendChild(title);
306
352
  this.dialog.appendChild(colorSection);
307
- this.dialog.appendChild(this.textSection);
308
353
  this.dialog.appendChild(buttonDiv);
309
354
 
310
355
  document.body.appendChild(this.dialog);
@@ -344,83 +389,85 @@ export class EditableSVGuitarChord {
344
389
  padding: 20px;
345
390
  box-shadow: 0 4px 20px rgba(0,0,0,0.3);
346
391
  z-index: 1000;
347
- min-width: 250px;
392
+ min-width: 165px;
348
393
  `;
349
394
 
350
395
  const title = document.createElement('h3');
351
396
  title.textContent = 'Edit Open String';
352
397
  title.style.cssText = 'margin: 0 0 15px 0; font-size: 16px;';
353
398
 
354
- // Type selection with radio buttons (Open vs Muted)
399
+ // Type selection with swatch squares (Muted × vs Open ○)
355
400
  const typeSection = document.createElement('div');
356
401
  typeSection.style.cssText = 'margin-bottom: 15px;';
357
-
358
- const typeLabel = document.createElement('div');
359
- typeLabel.textContent = 'Type:';
360
- typeLabel.style.cssText = 'font-weight: bold; margin-bottom: 8px;';
361
- typeSection.appendChild(typeLabel);
362
402
 
363
403
  const typeOptions = document.createElement('div');
364
- typeOptions.style.cssText = 'display: flex; gap: 15px;';
404
+ typeOptions.style.cssText = 'display: flex; gap: 8px; align-items: center;';
405
+
406
+ const hiddenRadioStyle =
407
+ 'position:absolute;opacity:0;width:0;height:0;';
408
+ const openStringSwatchStyle =
409
+ 'width:28px;height:28px;display:flex;align-items:center;' +
410
+ 'justify-content:center;background:#fff;border:1px solid #ccc;' +
411
+ 'border-radius:3px;cursor:pointer;box-sizing:border-box;' +
412
+ 'font-size:16px;font-weight:bold;user-select:none;';
413
+
414
+ // Muted option (×)
415
+ const mutedOption = document.createElement('label');
416
+ mutedOption.style.cssText =
417
+ 'display:inline-block;position:relative;cursor:pointer;';
418
+
419
+ this.mutedRadio = document.createElement('input');
420
+ this.mutedRadio.type = 'radio';
421
+ this.mutedRadio.name = 'openStringType';
422
+ this.mutedRadio.value = 'x';
423
+ this.mutedRadio.style.cssText = hiddenRadioStyle;
424
+ this.mutedRadio.addEventListener('change', () => this.updateOpenStringType());
425
+
426
+ const mutedSwatch = document.createElement('span');
427
+ mutedSwatch.className = 'open-string-swatch';
428
+ mutedSwatch.textContent = '\u00d7';
429
+ mutedSwatch.style.cssText = openStringSwatchStyle;
430
+
431
+ mutedOption.appendChild(this.mutedRadio);
432
+ mutedOption.appendChild(mutedSwatch);
365
433
 
366
- // Open option
434
+ // Open option (○)
367
435
  const openOption = document.createElement('label');
368
- openOption.style.cssText = 'display: flex; align-items: center; cursor: pointer;';
369
-
436
+ openOption.style.cssText =
437
+ 'display:inline-block;position:relative;cursor:pointer;';
438
+
370
439
  this.openRadio = document.createElement('input');
371
440
  this.openRadio.type = 'radio';
372
441
  this.openRadio.name = 'openStringType';
373
442
  this.openRadio.value = '0';
374
443
  this.openRadio.checked = true; // Default to open
444
+ this.openRadio.style.cssText = hiddenRadioStyle;
375
445
  this.openRadio.addEventListener('change', () => this.updateOpenStringType());
376
-
377
- const openLabel = document.createElement('span');
378
- openLabel.textContent = 'Open';
379
- openLabel.style.cssText = 'margin-left: 5px; font-weight: bold;';
380
-
446
+
447
+ const openSwatch = document.createElement('span');
448
+ openSwatch.className = 'open-string-swatch';
449
+ openSwatch.textContent = '\u25cb';
450
+ openSwatch.style.cssText = openStringSwatchStyle;
451
+
381
452
  openOption.appendChild(this.openRadio);
382
- openOption.appendChild(openLabel);
453
+ openOption.appendChild(openSwatch);
383
454
 
384
- // Muted option
385
- const mutedOption = document.createElement('label');
386
- mutedOption.style.cssText = 'display: flex; align-items: center; cursor: pointer;';
387
-
388
- this.mutedRadio = document.createElement('input');
389
- this.mutedRadio.type = 'radio';
390
- this.mutedRadio.name = 'openStringType';
391
- this.mutedRadio.value = 'x';
392
- this.mutedRadio.addEventListener('change', () => this.updateOpenStringType());
393
-
394
- const mutedLabel = document.createElement('span');
395
- mutedLabel.textContent = 'Muted';
396
- mutedLabel.style.cssText = 'margin-left: 5px; font-weight: bold;';
397
-
398
- mutedOption.appendChild(this.mutedRadio);
399
- mutedOption.appendChild(mutedLabel);
400
-
401
- typeOptions.appendChild(openOption);
402
455
  typeOptions.appendChild(mutedOption);
403
- typeSection.appendChild(typeOptions);
456
+ typeOptions.appendChild(openOption);
404
457
 
405
- // Text input (only for open strings)
406
- this.openStringTextSection = document.createElement('div');
407
- this.openStringTextSection.style.cssText = 'margin-bottom: 15px;';
408
-
409
- const textLabel = document.createElement('label');
410
- textLabel.textContent = 'Text (optional): ';
411
- textLabel.style.cssText = 'display: block; margin-bottom: 5px; font-weight: bold;';
412
-
458
+ // Text input inline (visible only for open strings)
413
459
  this.openStringTextInput = document.createElement('input');
414
460
  this.openStringTextInput.type = 'text';
415
- this.openStringTextInput.maxLength = 2;
416
- this.openStringTextInput.placeholder = '1-2 chars';
417
- this.openStringTextInput.style.cssText = 'width: 60px; padding: 4px; border: 1px solid #ccc; border-radius: 3px;';
418
-
419
- // Add real-time text change listener
420
- this.openStringTextInput.addEventListener('input', () => this.updateOpenStringText());
421
-
422
- textLabel.appendChild(this.openStringTextInput);
423
- this.openStringTextSection.appendChild(textLabel);
461
+ this.openStringTextInput.maxLength = 1;
462
+ this.openStringTextInput.placeholder = 'Aa';
463
+ this.openStringTextInput.style.cssText =
464
+ 'width:32px;height:28px;padding:0 4px;margin-left:4px;' +
465
+ 'border:1px solid #ccc;border-radius:3px;box-sizing:border-box;';
466
+ this.openStringTextInput.addEventListener('input',
467
+ () => this.updateOpenStringText());
468
+
469
+ typeOptions.appendChild(this.openStringTextInput);
470
+ typeSection.appendChild(typeOptions);
424
471
 
425
472
  const buttonDiv = document.createElement('div');
426
473
  buttonDiv.style.cssText = 'display: flex; gap: 10px; justify-content: flex-end;';
@@ -440,7 +487,6 @@ export class EditableSVGuitarChord {
440
487
 
441
488
  this.openStringDialog.appendChild(title);
442
489
  this.openStringDialog.appendChild(typeSection);
443
- this.openStringDialog.appendChild(this.openStringTextSection);
444
490
  this.openStringDialog.appendChild(buttonDiv);
445
491
 
446
492
  document.body.appendChild(this.openStringDialog);
@@ -766,12 +812,17 @@ export class EditableSVGuitarChord {
766
812
  const currentColor = typeof finger[2] === 'object' && finger[2]?.color || DOT_COLORS.BLACK;
767
813
  const currentText = typeof finger[2] === 'object' && finger[2]?.text || '';
768
814
 
769
- // Normalize color to red or black (handle legacy colors)
770
- const normalizedColor = currentColor === DOT_COLORS.RED ? DOT_COLORS.RED : DOT_COLORS.BLACK;
815
+ // Normalize color to one of the 4 known colors (handle legacy colors)
816
+ let normalizedColor = DOT_COLORS.BLACK;
817
+ if (currentColor === DOT_COLORS.RED) normalizedColor = DOT_COLORS.RED;
818
+ else if (currentColor === DOT_COLORS.GREY) normalizedColor = DOT_COLORS.GREY;
819
+ else if (currentColor === DOT_COLORS.BLUE) normalizedColor = DOT_COLORS.BLUE;
771
820
 
772
821
  // Set radio buttons
773
822
  this.redRadio.checked = normalizedColor === DOT_COLORS.RED;
774
823
  this.blackRadio.checked = normalizedColor === DOT_COLORS.BLACK;
824
+ this.greyRadio.checked = normalizedColor === DOT_COLORS.GREY;
825
+ this.blueRadio.checked = normalizedColor === DOT_COLORS.BLUE;
775
826
 
776
827
  // Set text and update visibility
777
828
  this.textInput.value = currentText;
@@ -1055,6 +1106,14 @@ export class EditableSVGuitarChord {
1055
1106
  .editable-svguitar-settings-btn:hover {
1056
1107
  background: #f0f0f0;
1057
1108
  }
1109
+
1110
+ .editable-svguitar-dialog input[name="dotColor"]:checked + .color-swatch {
1111
+ box-shadow: 0 0 0 2px white, 0 0 0 4px grey;
1112
+ }
1113
+
1114
+ .editable-svguitar-open-string-dialog input[name="openStringType"]:checked + .open-string-swatch {
1115
+ box-shadow: 0 0 0 2px white, 0 0 0 4px grey;
1116
+ }
1058
1117
  `;
1059
1118
  document.head.appendChild(style);
1060
1119
  }
@@ -1095,30 +1154,22 @@ export class EditableSVGuitarChord {
1095
1154
  * Update text section visibility based on color selection
1096
1155
  */
1097
1156
  updateTextSectionVisibility() {
1098
- if (!this.textSection) return;
1157
+ if (!this.textInput) return;
1099
1158
 
1100
1159
  const isBlack = this.blackRadio && this.blackRadio.checked;
1101
- this.textSection.style.display = isBlack ? 'block' : 'none';
1102
-
1103
- // Disable text input for red dots
1104
- if (this.textInput) {
1105
- this.textInput.disabled = !isBlack;
1106
- }
1160
+ this.textInput.style.display = isBlack ? 'inline-block' : 'none';
1161
+ this.textInput.disabled = !isBlack;
1107
1162
  }
1108
1163
 
1109
1164
  /**
1110
1165
  * Update text section visibility for open string dialog based on type selection
1111
1166
  */
1112
1167
  updateOpenStringTextSectionVisibility() {
1113
- if (!this.openStringTextSection) return;
1114
-
1168
+ if (!this.openStringTextInput) return;
1169
+
1115
1170
  const isOpen = this.openRadio && this.openRadio.checked;
1116
- this.openStringTextSection.style.display = isOpen ? 'block' : 'none';
1117
-
1118
- // Disable text input for muted strings
1119
- if (this.openStringTextInput) {
1120
- this.openStringTextInput.disabled = !isOpen;
1121
- }
1171
+ this.openStringTextInput.style.display = isOpen ? 'inline-block' : 'none';
1172
+ this.openStringTextInput.disabled = !isOpen;
1122
1173
  }
1123
1174
 
1124
1175
  /**
@@ -1193,13 +1244,16 @@ export class EditableSVGuitarChord {
1193
1244
  }
1194
1245
 
1195
1246
  // Get selected color from radio buttons
1196
- const selectedColor = this.redRadio.checked ? DOT_COLORS.RED : DOT_COLORS.BLACK;
1247
+ let selectedColor = DOT_COLORS.BLACK;
1248
+ if (this.redRadio.checked) selectedColor = DOT_COLORS.RED;
1249
+ else if (this.greyRadio.checked) selectedColor = DOT_COLORS.GREY;
1250
+ else if (this.blueRadio.checked) selectedColor = DOT_COLORS.BLUE;
1197
1251
 
1198
1252
  const fingerOptions = typeof this.currentEditFinger[2] === 'object' ? this.currentEditFinger[2] : {};
1199
1253
  this.currentEditFinger[2] = { ...fingerOptions, color: selectedColor };
1200
1254
 
1201
- // Clear text if red is selected
1202
- if (selectedColor === DOT_COLORS.RED) {
1255
+ // Clear text if non-black color is selected
1256
+ if (selectedColor !== DOT_COLORS.BLACK) {
1203
1257
  this.currentEditFinger[2].text = '';
1204
1258
  this.textInput.value = '';
1205
1259
  }
@@ -1221,7 +1275,10 @@ export class EditableSVGuitarChord {
1221
1275
  }
1222
1276
 
1223
1277
  // Get selected color from radio buttons
1224
- const selectedColor = this.redRadio.checked ? DOT_COLORS.RED : DOT_COLORS.BLACK;
1278
+ let selectedColor = DOT_COLORS.BLACK;
1279
+ if (this.redRadio.checked) selectedColor = DOT_COLORS.RED;
1280
+ else if (this.greyRadio.checked) selectedColor = DOT_COLORS.GREY;
1281
+ else if (this.blueRadio.checked) selectedColor = DOT_COLORS.BLUE;
1225
1282
  this.currentEditFinger[2] = { text: this.textInput.value, color: selectedColor };
1226
1283
 
1227
1284
  this.closeDialog();
@@ -1,5 +1,28 @@
1
1
  //@ts-check
2
2
 
3
+ // Known color constants
4
+ const COLOR_BLACK = '#000000';
5
+ const COLOR_RED = '#e74c3c';
6
+ const COLOR_GREY = '#9B9B9B';
7
+ const COLOR_BLUE = '#4A90E2';
8
+
9
+ /**
10
+ * Get the marker character for a non-black color
11
+ * @param {string} color - The color hex value
12
+ * @param {boolean} isUnicode - Whether to use Unicode characters
13
+ * @returns {string} The marker character
14
+ */
15
+ function getMarkerChar(color, isUnicode) {
16
+ if (color === COLOR_GREY) {
17
+ return isUnicode ? '□' : 'O';
18
+ }
19
+ if (color === COLOR_BLUE) {
20
+ return isUnicode ? '■' : '+';
21
+ }
22
+ // Red or any unrecognized color falls back to root marker
23
+ return isUnicode ? '●' : '*';
24
+ }
25
+
3
26
  /**
4
27
  * Parse a string representation of a guitar fingering into internal format
5
28
  * @param {import("svguitar").Chord} chord
@@ -140,7 +163,7 @@ function buildAsciiOutput(
140
163
 
141
164
  if (fingerInfo) {
142
165
  if (fingerInfo.color !== "#000000") {
143
- line += "*";
166
+ line += getMarkerChar(fingerInfo.color, false);
144
167
  } else if (fingerInfo.text) {
145
168
  line += fingerInfo.text[0];
146
169
  } else {
@@ -237,7 +260,7 @@ function buildUnicodeOutput(
237
260
 
238
261
  if (fingerInfo) {
239
262
  if (fingerInfo.color !== "#000000") {
240
- line += "●";
263
+ line += getMarkerChar(fingerInfo.color, true);
241
264
  } else if (fingerInfo.text) {
242
265
  line += fingerInfo.text[0];
243
266
  } else {
@@ -7,12 +7,16 @@ const ASCII_EQUALS = "=";
7
7
  const ASCII_OPEN = "o";
8
8
  const ASCII_MUTED = "x";
9
9
  const ASCII_ROOT = "*";
10
+ const ASCII_GREY = "O";
11
+ const ASCII_BLUE = "+";
10
12
 
11
13
  // Unicode format characters
12
14
  const UNICODE_VERTICAL = "│";
13
15
  const UNICODE_OPEN = "○";
14
16
  const UNICODE_MUTED = "×";
15
17
  const UNICODE_ROOT = "●";
18
+ const UNICODE_GREY = "□";
19
+ const UNICODE_BLUE = "■";
16
20
 
17
21
  // Unicode box drawing characters for grid detection
18
22
  // Includes both double-line (╒═╤╕) and light (┌─┬┐) box-drawing sets
@@ -29,6 +33,8 @@ function isUnicodeFormat(str) {
29
33
  str.includes(UNICODE_OPEN) ||
30
34
  str.includes(UNICODE_ROOT) ||
31
35
  str.includes(UNICODE_MUTED) ||
36
+ str.includes(UNICODE_GREY) ||
37
+ str.includes(UNICODE_BLUE) ||
32
38
  [...UNICODE_BOX_CHARS].some(c => str.includes(c))
33
39
  );
34
40
  }
@@ -183,11 +189,16 @@ function isGridRow(line, isUnicode) {
183
189
  /**
184
190
  * Parse a string representation of a guitar fingering into internal format
185
191
  * @param {string} fingeringStr - The string representation of the fingering
186
- * @param {{ redColor?: string, blackColor?: string }} [options]
192
+ * @param {{ redColor?: string, blackColor?: string, greyColor?: string, blueColor?: string }} [options]
187
193
  * @returns {import("svguitar").Chord | null} The parsed fingering object
188
194
  */
189
195
  export default function stringToFingering(fingeringStr, options = {}) {
190
- const { redColor = "#e74c3c", blackColor = "#000000" } = options;
196
+ const {
197
+ redColor = "#e74c3c",
198
+ blackColor = "#000000",
199
+ greyColor = "#9B9B9B",
200
+ blueColor = "#4A90E2",
201
+ } = options;
191
202
 
192
203
  if (!fingeringStr || fingeringStr.trim() === "") {
193
204
  return null;
@@ -199,6 +210,8 @@ export default function stringToFingering(fingeringStr, options = {}) {
199
210
  const openChar = isUnicode ? UNICODE_OPEN : ASCII_OPEN;
200
211
  const mutedChar = isUnicode ? UNICODE_MUTED : ASCII_MUTED;
201
212
  const rootChar = isUnicode ? UNICODE_ROOT : ASCII_ROOT;
213
+ const greyChar = isUnicode ? UNICODE_GREY : ASCII_GREY;
214
+ const blueChar = isUnicode ? UNICODE_BLUE : ASCII_BLUE;
202
215
 
203
216
  /** @type {import("svguitar").Finger[]} */
204
217
  const fingers = [];
@@ -368,6 +381,14 @@ export default function stringToFingering(fingeringStr, options = {}) {
368
381
  if (char === rootChar) {
369
382
  fingers.push([stringNum, fretNumber, { text: "", color: redColor }]);
370
383
  }
384
+ // Check for grey marker
385
+ else if (char === greyChar) {
386
+ fingers.push([stringNum, fretNumber, { text: "", color: greyColor }]);
387
+ }
388
+ // Check for blue marker
389
+ else if (char === blueChar) {
390
+ fingers.push([stringNum, fretNumber, { text: "", color: blueColor }]);
391
+ }
371
392
  // Check for regular note (○ in Unicode, o in ASCII within grid)
372
393
  else if (char === UNICODE_OPEN || char === ASCII_OPEN) {
373
394
  fingers.push([stringNum, fretNumber, { text: "", color: blackColor }]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "text-guitar-chart",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "A JavaScript library to write text based guitar chord charts and convert them to SVG.",
5
5
  "main": "index.js",
6
6
  "types": "types/index.d.ts",
@@ -210,6 +210,18 @@ describe('DOT_COLORS', () => {
210
210
  assert.equal(DOT_COLORS.RED, '#e74c3c', 'RED should be correct hex value');
211
211
  assert.equal(DOT_COLORS.BLACK, '#000000', 'BLACK should be correct hex value');
212
212
  });
213
+
214
+ test('DOT_COLORS has expected grey and blue values', () => {
215
+ assert.equal(DOT_COLORS.GREY, '#9B9B9B', 'GREY should be correct hex value');
216
+ assert.equal(DOT_COLORS.BLUE, '#4A90E2', 'BLUE should be correct hex value');
217
+ });
218
+
219
+ test('DOT_COLORS has all 4 color properties', () => {
220
+ assert.ok(DOT_COLORS.RED, 'DOT_COLORS should have RED property');
221
+ assert.ok(DOT_COLORS.BLACK, 'DOT_COLORS should have BLACK property');
222
+ assert.ok(DOT_COLORS.GREY, 'DOT_COLORS should have GREY property');
223
+ assert.ok(DOT_COLORS.BLUE, 'DOT_COLORS should have BLUE property');
224
+ });
213
225
  });
214
226
  describe('EditableSVGuitarChord (Title and Position)', () => {
215
227
  test('sets title and position via chord() method', () => {
@@ -517,8 +529,7 @@ describe('EditableSVGuitarChord (Open String Dialog)', () => {
517
529
  // Mock radio buttons
518
530
  editableChord.openRadio = { checked: false };
519
531
  editableChord.mutedRadio = { checked: true };
520
- editableChord.openStringTextInput = { value: '' };
521
- editableChord.openStringTextSection = { style: { display: 'block' } };
532
+ editableChord.openStringTextInput = { value: '', style: { display: '' }, disabled: false };
522
533
 
523
534
  // Switch to muted
524
535
  editableChord.updateOpenStringType();
@@ -572,8 +583,7 @@ describe('EditableSVGuitarChord (Open String Dialog)', () => {
572
583
  editableChord.currentEditFinger = editableChord.chordConfig.fingers[0];
573
584
  editableChord.openRadio = { checked: false };
574
585
  editableChord.mutedRadio = { checked: true };
575
- editableChord.openStringTextInput = { value: 'R' };
576
- editableChord.openStringTextSection = { style: { display: 'block' } };
586
+ editableChord.openStringTextInput = { value: 'R', style: { display: '' }, disabled: false };
577
587
 
578
588
  editableChord.updateOpenStringType();
579
589
 
@@ -634,8 +644,7 @@ describe('EditableSVGuitarChord (Open String Dialog)', () => {
634
644
  editableChord.currentEditFinger = editableChord.chordConfig.fingers[0];
635
645
  editableChord.openRadio = { checked: false };
636
646
  editableChord.mutedRadio = { checked: true };
637
- editableChord.openStringTextInput = { value: '' };
638
- editableChord.openStringTextSection = { style: { display: 'block' } };
647
+ editableChord.openStringTextInput = { value: '', style: { display: '' }, disabled: false };
639
648
 
640
649
  editableChord.updateOpenStringType();
641
650