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.
- package/FORMAT.md +10 -4
- package/docs/bundle.js +157 -91
- package/docs/bundle.js.map +2 -2
- package/lib/editableSVGuitar.js +184 -127
- package/lib/fingeringToString.js +25 -2
- package/lib/stringToFingering.js +23 -2
- package/package.json +1 -1
- package/test/editableSVGuitar.test.js +15 -6
- package/test/fingeringToString.test.js +221 -0
- package/test/stringToFingering.test.js +176 -0
- package/types/editableSVGuitar.d.ts +22 -1
- package/types/stringToFingering.d.ts +3 -1
package/lib/editableSVGuitar.js
CHANGED
|
@@ -4,11 +4,13 @@ import fingeringToString from './fingeringToString.js';
|
|
|
4
4
|
import { SVGuitarChord } from 'svguitar';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Available colors for dots
|
|
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:
|
|
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
|
|
126
|
-
titleLabel.
|
|
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
|
|
143
|
-
positionLabel.
|
|
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:
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
259
|
-
|
|
260
|
-
|
|
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(
|
|
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 (
|
|
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 =
|
|
280
|
-
this.textInput.placeholder = '
|
|
281
|
-
this.textInput.style.cssText =
|
|
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
|
-
|
|
287
|
-
|
|
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:
|
|
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
|
|
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:
|
|
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 =
|
|
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
|
|
378
|
-
|
|
379
|
-
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
416
|
-
this.openStringTextInput.placeholder = '
|
|
417
|
-
this.openStringTextInput.style.cssText =
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
this.openStringTextInput.addEventListener('input',
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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
|
|
770
|
-
|
|
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.
|
|
1157
|
+
if (!this.textInput) return;
|
|
1099
1158
|
|
|
1100
1159
|
const isBlack = this.blackRadio && this.blackRadio.checked;
|
|
1101
|
-
this.
|
|
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.
|
|
1114
|
-
|
|
1168
|
+
if (!this.openStringTextInput) return;
|
|
1169
|
+
|
|
1115
1170
|
const isOpen = this.openRadio && this.openRadio.checked;
|
|
1116
|
-
this.
|
|
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
|
-
|
|
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
|
|
1202
|
-
if (selectedColor
|
|
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
|
-
|
|
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();
|
package/lib/fingeringToString.js
CHANGED
|
@@ -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 {
|
package/lib/stringToFingering.js
CHANGED
|
@@ -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 {
|
|
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
|
@@ -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
|
|