@gblp/chord-finder 0.1.0 → 0.3.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/README.md CHANGED
@@ -2,15 +2,40 @@
2
2
 
3
3
  Standalone Angular 22 component for finding guitar chords and rendering SVG chord diagrams.
4
4
 
5
+ [View the package on npm](https://www.npmjs.com/package/@gblp/chord-finder)
6
+
7
+ ```bash
8
+ npm install @gblp/chord-finder
9
+ ```
10
+
5
11
  ```ts
6
- import { Component } from '@angular/core';
7
- import { ChordFinderComponent } from '@gblp/chord-finder';
12
+ import { Component } from "@angular/core";
13
+ import { ChordFinderComponent } from "@gblp/chord-finder";
8
14
 
9
15
  @Component({
10
16
  imports: [ChordFinderComponent],
11
- template: '<the-chords-chord-finder />'
17
+ template: `<the-chords-chord-finder [language]="'es'" />`,
12
18
  })
13
19
  export class App {}
14
20
  ```
15
21
 
22
+ `language` accepts `en` or `es` and defaults to `en`.
23
+
24
+ Override the component theme from any ancestor with these inherited CSS variables:
25
+
26
+ ```css
27
+ --chords-background;
28
+ --chords-background-alt;
29
+ --chords-surface;
30
+ --chords-surface-elevated;
31
+ --chords-text;
32
+ --chords-muted;
33
+ --chords-primary;
34
+ --chords-secondary;
35
+ --chords-highlight;
36
+ --chords-danger;
37
+ --chords-border;
38
+ --chords-on-primary;
39
+ ```
40
+
16
41
  Build the package with `npm run build:lib`. The publishable Angular package is written to `dist/chord-finder`.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, computed, ChangeDetectionStrategy, Component, Injectable, signal, viewChild } from '@angular/core';
2
+ import { input, computed, ChangeDetectionStrategy, Component, Injectable, signal, viewChild, effect } from '@angular/core';
3
3
  import * as i2 from '@angular/forms';
4
4
  import { FormsModule } from '@angular/forms';
5
5
  import guitarDbJson from '@tombatossals/chords-db/lib/guitar.json';
@@ -98,13 +98,25 @@ class ChordDiagram {
98
98
  return this.position().frets.map((fret) => (fret === -1 ? 'x' : String(fret))).join('');
99
99
  }
100
100
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: ChordDiagram, deps: [], target: i0.ɵɵFactoryTarget.Component });
101
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.4", type: ChordDiagram, isStandalone: true, selector: "app-chord-diagram", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: true, transformFunction: null }, positionIndex: { classPropertyName: "positionIndex", publicName: "positionIndex", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<svg class=\"chord-svg\" [attr.viewBox]=\"viewBox\" role=\"img\" [attr.aria-label]=\"title() + ' guitar chord diagram'\">\n <title>{{ title() }} - posici\u00F3n {{ positionIndex() + 1 }} - {{ positionCode() }}</title>\n\n <rect class=\"card-bg\" x=\"0\" y=\"0\" [attr.width]=\"width\" [attr.height]=\"height\" rx=\"24\" fill=\"#ffffff\" />\n\n <text x=\"120\" y=\"50\" text-anchor=\"middle\" class=\"title\">{{ title() }}</text>\n\n <g class=\"markers\">\n @for (fret of position().frets; track $index) {\n <text [attr.x]=\"stringX($index)\" y=\"92\" text-anchor=\"middle\">{{ markerFor(fret) }}</text>\n }\n </g>\n\n <text x=\"26\" [attr.y]=\"fretY(0) + 28\" text-anchor=\"middle\" class=\"fret-number\">{{ fretLabel() }}</text>\n\n <g class=\"grid\">\n @for (line of fretLines(); track line) {\n <line\n [attr.x1]=\"stringX(0)\"\n [attr.x2]=\"stringX(5)\"\n [attr.y1]=\"fretY(line)\"\n [attr.y2]=\"fretY(line)\"\n [class.nut]=\"line === 0 && baseFret() === 1\"\n />\n }\n\n @for (line of stringLines(); track line) {\n <line\n [attr.x1]=\"stringX(line)\"\n [attr.x2]=\"stringX(line)\"\n [attr.y1]=\"fretY(0)\"\n [attr.y2]=\"fretY(fretCount())\"\n />\n }\n </g>\n\n <g class=\"barres\">\n @for (barre of barreSpans(); track barre.fret) {\n <rect\n [attr.x]=\"barre.x\"\n [attr.y]=\"barre.y\"\n [attr.width]=\"barre.width\"\n [attr.height]=\"dotRadius * 2\"\n rx=\"13\"\n />\n @if (barre.finger) {\n <text [attr.x]=\"barre.labelX\" [attr.y]=\"barre.labelY\" text-anchor=\"middle\">{{ barre.finger }}</text>\n }\n }\n </g>\n\n <g class=\"dots\">\n @for (dot of visibleDots(); track dot.stringIndex) {\n <circle [attr.cx]=\"stringX(dot.stringIndex)\" [attr.cy]=\"dotY(dot.fret)\" [attr.r]=\"dotRadius\" />\n @if (dot.finger) {\n <text [attr.x]=\"stringX(dot.stringIndex)\" [attr.y]=\"dotY(dot.fret)\" text-anchor=\"middle\">{{ dot.finger }}</text>\n }\n }\n </g>\n\n <g class=\"string-labels\">\n @for (label of stringLabels; track label + $index) {\n <text [attr.x]=\"stringX($index)\" [attr.y]=\"fretY(fretCount()) + 32\" text-anchor=\"middle\">{{ label }}</text>\n }\n </g>\n</svg>\n", styles: [":host{display:block}.chord-svg{width:240px;height:330px;display:block;font-family:Roboto,Arial,sans-serif;font-weight:400;border-radius:24px;background:#fff;box-shadow:0 18px 36px #00000059}.card-bg{fill:#fff}.title{font-size:42px;font-weight:400;letter-spacing:-.03em;fill:#000}.grid line{stroke:#000;stroke-width:2.6;stroke-linecap:square;vector-effect:non-scaling-stroke}.grid line.nut{stroke-width:7}.barres rect,.dots circle{fill:#000}.barres text,.dots text{fill:#fff;font-size:16px;font-weight:400;dominant-baseline:central;alignment-baseline:middle;pointer-events:none}.markers text{fill:#000;font-size:30px;font-weight:400}.fret-number{fill:#000;font-size:42px;font-weight:400}.string-labels text{fill:#000;font-size:18px;font-weight:400}\n"], changeDetection: i0.ChangeDetectionStrategy.Eager });
101
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.4", type: ChordDiagram, isStandalone: true, selector: "app-chord-diagram", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: true, transformFunction: null }, positionIndex: { classPropertyName: "positionIndex", publicName: "positionIndex", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<svg class=\"chord-svg\" [attr.viewBox]=\"viewBox\" role=\"img\" [attr.aria-label]=\"title() + ' guitar chord diagram'\">\r\n <title>{{ title() }} - posici\u00F3n {{ positionIndex() + 1 }} - {{ positionCode() }}</title>\r\n\r\n <rect class=\"card-bg\" x=\"0\" y=\"0\" [attr.width]=\"width\" [attr.height]=\"height\" rx=\"24\" fill=\"#ffffff\" />\r\n\r\n <text x=\"120\" y=\"50\" text-anchor=\"middle\" class=\"title\">{{ title() }}</text>\r\n\r\n <g class=\"markers\">\r\n @for (fret of position().frets; track $index) {\r\n <text [attr.x]=\"stringX($index)\" y=\"92\" text-anchor=\"middle\">{{ markerFor(fret) }}</text>\r\n }\r\n </g>\r\n\r\n <text x=\"26\" [attr.y]=\"fretY(0) + 28\" text-anchor=\"middle\" class=\"fret-number\">{{ fretLabel() }}</text>\r\n\r\n <g class=\"grid\">\r\n @for (line of fretLines(); track line) {\r\n <line\r\n [attr.x1]=\"stringX(0)\"\r\n [attr.x2]=\"stringX(5)\"\r\n [attr.y1]=\"fretY(line)\"\r\n [attr.y2]=\"fretY(line)\"\r\n [class.nut]=\"line === 0 && baseFret() === 1\"\r\n />\r\n }\r\n\r\n @for (line of stringLines(); track line) {\r\n <line\r\n [attr.x1]=\"stringX(line)\"\r\n [attr.x2]=\"stringX(line)\"\r\n [attr.y1]=\"fretY(0)\"\r\n [attr.y2]=\"fretY(fretCount())\"\r\n />\r\n }\r\n </g>\r\n\r\n <g class=\"barres\">\r\n @for (barre of barreSpans(); track barre.fret) {\r\n <rect\r\n [attr.x]=\"barre.x\"\r\n [attr.y]=\"barre.y\"\r\n [attr.width]=\"barre.width\"\r\n [attr.height]=\"dotRadius * 2\"\r\n rx=\"13\"\r\n />\r\n @if (barre.finger) {\r\n <text [attr.x]=\"barre.labelX\" [attr.y]=\"barre.labelY\" text-anchor=\"middle\">{{ barre.finger }}</text>\r\n }\r\n }\r\n </g>\r\n\r\n <g class=\"dots\">\r\n @for (dot of visibleDots(); track dot.stringIndex) {\r\n <circle [attr.cx]=\"stringX(dot.stringIndex)\" [attr.cy]=\"dotY(dot.fret)\" [attr.r]=\"dotRadius\" />\r\n @if (dot.finger) {\r\n <text [attr.x]=\"stringX(dot.stringIndex)\" [attr.y]=\"dotY(dot.fret)\" text-anchor=\"middle\">{{ dot.finger }}</text>\r\n }\r\n }\r\n </g>\r\n\r\n <g class=\"string-labels\">\r\n @for (label of stringLabels; track label + $index) {\r\n <text [attr.x]=\"stringX($index)\" [attr.y]=\"fretY(fretCount()) + 32\" text-anchor=\"middle\">{{ label }}</text>\r\n }\r\n </g>\r\n</svg>\r\n", styles: [":host{display:block}.chord-svg{width:240px;height:330px;display:block;font-family:Roboto,Arial,sans-serif;font-weight:400;border-radius:24px;background:#fff;box-shadow:0 18px 36px #00000059}.card-bg{fill:#fff}.title{font-size:42px;font-weight:400;letter-spacing:-.03em;fill:#000}.grid line{stroke:#000;stroke-width:2.6;stroke-linecap:square;vector-effect:non-scaling-stroke}.grid line.nut{stroke-width:7}.barres rect,.dots circle{fill:#000}.barres text,.dots text{fill:#fff;font-size:16px;font-weight:400;dominant-baseline:central;alignment-baseline:middle;pointer-events:none}.markers text{fill:#000;font-size:30px;font-weight:400}.fret-number{fill:#000;font-size:42px;font-weight:400}.string-labels text{fill:#000;font-size:18px;font-weight:400}\n"], changeDetection: i0.ChangeDetectionStrategy.Eager });
102
102
  }
103
103
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: ChordDiagram, decorators: [{
104
104
  type: Component,
105
- args: [{ selector: 'app-chord-diagram', imports: [], changeDetection: ChangeDetectionStrategy.Eager, template: "<svg class=\"chord-svg\" [attr.viewBox]=\"viewBox\" role=\"img\" [attr.aria-label]=\"title() + ' guitar chord diagram'\">\n <title>{{ title() }} - posici\u00F3n {{ positionIndex() + 1 }} - {{ positionCode() }}</title>\n\n <rect class=\"card-bg\" x=\"0\" y=\"0\" [attr.width]=\"width\" [attr.height]=\"height\" rx=\"24\" fill=\"#ffffff\" />\n\n <text x=\"120\" y=\"50\" text-anchor=\"middle\" class=\"title\">{{ title() }}</text>\n\n <g class=\"markers\">\n @for (fret of position().frets; track $index) {\n <text [attr.x]=\"stringX($index)\" y=\"92\" text-anchor=\"middle\">{{ markerFor(fret) }}</text>\n }\n </g>\n\n <text x=\"26\" [attr.y]=\"fretY(0) + 28\" text-anchor=\"middle\" class=\"fret-number\">{{ fretLabel() }}</text>\n\n <g class=\"grid\">\n @for (line of fretLines(); track line) {\n <line\n [attr.x1]=\"stringX(0)\"\n [attr.x2]=\"stringX(5)\"\n [attr.y1]=\"fretY(line)\"\n [attr.y2]=\"fretY(line)\"\n [class.nut]=\"line === 0 && baseFret() === 1\"\n />\n }\n\n @for (line of stringLines(); track line) {\n <line\n [attr.x1]=\"stringX(line)\"\n [attr.x2]=\"stringX(line)\"\n [attr.y1]=\"fretY(0)\"\n [attr.y2]=\"fretY(fretCount())\"\n />\n }\n </g>\n\n <g class=\"barres\">\n @for (barre of barreSpans(); track barre.fret) {\n <rect\n [attr.x]=\"barre.x\"\n [attr.y]=\"barre.y\"\n [attr.width]=\"barre.width\"\n [attr.height]=\"dotRadius * 2\"\n rx=\"13\"\n />\n @if (barre.finger) {\n <text [attr.x]=\"barre.labelX\" [attr.y]=\"barre.labelY\" text-anchor=\"middle\">{{ barre.finger }}</text>\n }\n }\n </g>\n\n <g class=\"dots\">\n @for (dot of visibleDots(); track dot.stringIndex) {\n <circle [attr.cx]=\"stringX(dot.stringIndex)\" [attr.cy]=\"dotY(dot.fret)\" [attr.r]=\"dotRadius\" />\n @if (dot.finger) {\n <text [attr.x]=\"stringX(dot.stringIndex)\" [attr.y]=\"dotY(dot.fret)\" text-anchor=\"middle\">{{ dot.finger }}</text>\n }\n }\n </g>\n\n <g class=\"string-labels\">\n @for (label of stringLabels; track label + $index) {\n <text [attr.x]=\"stringX($index)\" [attr.y]=\"fretY(fretCount()) + 32\" text-anchor=\"middle\">{{ label }}</text>\n }\n </g>\n</svg>\n", styles: [":host{display:block}.chord-svg{width:240px;height:330px;display:block;font-family:Roboto,Arial,sans-serif;font-weight:400;border-radius:24px;background:#fff;box-shadow:0 18px 36px #00000059}.card-bg{fill:#fff}.title{font-size:42px;font-weight:400;letter-spacing:-.03em;fill:#000}.grid line{stroke:#000;stroke-width:2.6;stroke-linecap:square;vector-effect:non-scaling-stroke}.grid line.nut{stroke-width:7}.barres rect,.dots circle{fill:#000}.barres text,.dots text{fill:#fff;font-size:16px;font-weight:400;dominant-baseline:central;alignment-baseline:middle;pointer-events:none}.markers text{fill:#000;font-size:30px;font-weight:400}.fret-number{fill:#000;font-size:42px;font-weight:400}.string-labels text{fill:#000;font-size:18px;font-weight:400}\n"] }]
105
+ args: [{ selector: 'app-chord-diagram', imports: [], changeDetection: ChangeDetectionStrategy.Eager, template: "<svg class=\"chord-svg\" [attr.viewBox]=\"viewBox\" role=\"img\" [attr.aria-label]=\"title() + ' guitar chord diagram'\">\r\n <title>{{ title() }} - posici\u00F3n {{ positionIndex() + 1 }} - {{ positionCode() }}</title>\r\n\r\n <rect class=\"card-bg\" x=\"0\" y=\"0\" [attr.width]=\"width\" [attr.height]=\"height\" rx=\"24\" fill=\"#ffffff\" />\r\n\r\n <text x=\"120\" y=\"50\" text-anchor=\"middle\" class=\"title\">{{ title() }}</text>\r\n\r\n <g class=\"markers\">\r\n @for (fret of position().frets; track $index) {\r\n <text [attr.x]=\"stringX($index)\" y=\"92\" text-anchor=\"middle\">{{ markerFor(fret) }}</text>\r\n }\r\n </g>\r\n\r\n <text x=\"26\" [attr.y]=\"fretY(0) + 28\" text-anchor=\"middle\" class=\"fret-number\">{{ fretLabel() }}</text>\r\n\r\n <g class=\"grid\">\r\n @for (line of fretLines(); track line) {\r\n <line\r\n [attr.x1]=\"stringX(0)\"\r\n [attr.x2]=\"stringX(5)\"\r\n [attr.y1]=\"fretY(line)\"\r\n [attr.y2]=\"fretY(line)\"\r\n [class.nut]=\"line === 0 && baseFret() === 1\"\r\n />\r\n }\r\n\r\n @for (line of stringLines(); track line) {\r\n <line\r\n [attr.x1]=\"stringX(line)\"\r\n [attr.x2]=\"stringX(line)\"\r\n [attr.y1]=\"fretY(0)\"\r\n [attr.y2]=\"fretY(fretCount())\"\r\n />\r\n }\r\n </g>\r\n\r\n <g class=\"barres\">\r\n @for (barre of barreSpans(); track barre.fret) {\r\n <rect\r\n [attr.x]=\"barre.x\"\r\n [attr.y]=\"barre.y\"\r\n [attr.width]=\"barre.width\"\r\n [attr.height]=\"dotRadius * 2\"\r\n rx=\"13\"\r\n />\r\n @if (barre.finger) {\r\n <text [attr.x]=\"barre.labelX\" [attr.y]=\"barre.labelY\" text-anchor=\"middle\">{{ barre.finger }}</text>\r\n }\r\n }\r\n </g>\r\n\r\n <g class=\"dots\">\r\n @for (dot of visibleDots(); track dot.stringIndex) {\r\n <circle [attr.cx]=\"stringX(dot.stringIndex)\" [attr.cy]=\"dotY(dot.fret)\" [attr.r]=\"dotRadius\" />\r\n @if (dot.finger) {\r\n <text [attr.x]=\"stringX(dot.stringIndex)\" [attr.y]=\"dotY(dot.fret)\" text-anchor=\"middle\">{{ dot.finger }}</text>\r\n }\r\n }\r\n </g>\r\n\r\n <g class=\"string-labels\">\r\n @for (label of stringLabels; track label + $index) {\r\n <text [attr.x]=\"stringX($index)\" [attr.y]=\"fretY(fretCount()) + 32\" text-anchor=\"middle\">{{ label }}</text>\r\n }\r\n </g>\r\n</svg>\r\n", styles: [":host{display:block}.chord-svg{width:240px;height:330px;display:block;font-family:Roboto,Arial,sans-serif;font-weight:400;border-radius:24px;background:#fff;box-shadow:0 18px 36px #00000059}.card-bg{fill:#fff}.title{font-size:42px;font-weight:400;letter-spacing:-.03em;fill:#000}.grid line{stroke:#000;stroke-width:2.6;stroke-linecap:square;vector-effect:non-scaling-stroke}.grid line.nut{stroke-width:7}.barres rect,.dots circle{fill:#000}.barres text,.dots text{fill:#fff;font-size:16px;font-weight:400;dominant-baseline:central;alignment-baseline:middle;pointer-events:none}.markers text{fill:#000;font-size:30px;font-weight:400}.fret-number{fill:#000;font-size:42px;font-weight:400}.string-labels text{fill:#000;font-size:18px;font-weight:400}\n"] }]
106
106
  }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: true }] }], positionIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "positionIndex", required: false }] }] } });
107
107
 
108
+ const ERROR_COPY = {
109
+ en: {
110
+ invalid: 'Invalid chord name. Try C, F#, C#m, Bb, Am7, or Dsus4.',
111
+ missingRoot: 'Chord root not found in chords-db.',
112
+ missingType: (suffix) => `The type "${suffix}" is not available for this chord.`,
113
+ },
114
+ es: {
115
+ invalid: 'Nombre inválido. Prueba C, F#, C#m, Bb, Am7 o Dsus4.',
116
+ missingRoot: 'Raíz no encontrada en chords-db.',
117
+ missingType: (suffix) => `El tipo "${suffix}" no existe para este acorde en la base actual.`,
118
+ },
119
+ };
108
120
  class ChordService {
109
121
  maxBatchSize = 5;
110
122
  guitarDb = guitarDbJson;
@@ -129,7 +141,7 @@ class ChordService {
129
141
  Bb: 'Bb',
130
142
  B: 'B',
131
143
  Cb: 'B',
132
- 'B#': 'C'
144
+ 'B#': 'C',
133
145
  };
134
146
  suffixAliases = {
135
147
  '': 'major',
@@ -153,31 +165,32 @@ class ChordService {
153
165
  sus2: 'sus2',
154
166
  sus4: 'sus4',
155
167
  add9: 'add9',
156
- 4: 'sus4'
168
+ 4: 'sus4',
157
169
  };
158
- search(input) {
170
+ search(input, language = 'en') {
159
171
  const tokens = input
160
172
  .split(',')
161
173
  .map((token) => token.trim())
162
174
  .filter(Boolean);
163
175
  const limitedTokens = tokens.slice(0, this.maxBatchSize);
164
176
  return {
165
- results: limitedTokens.map((token, index) => this.searchSingle(token, index)),
166
- wasLimited: tokens.length > this.maxBatchSize
177
+ results: limitedTokens.map((token, index) => this.searchSingle(token, index, language)),
178
+ wasLimited: tokens.length > this.maxBatchSize,
167
179
  };
168
180
  }
169
181
  suffixes() {
170
182
  return this.guitarDb.suffixes;
171
183
  }
172
- searchSingle(token, index) {
184
+ searchSingle(token, index, language) {
173
185
  const parsed = this.parseChordName(token);
186
+ const copy = ERROR_COPY[language];
174
187
  if (!parsed) {
175
188
  return {
176
189
  id: `${index}-${token}`,
177
190
  raw: token,
178
191
  displayName: token,
179
192
  positions: [],
180
- error: 'Nombre inválido. Prueba C, F#, C#m, Bb, Am7 o Dsus4.'
193
+ error: copy.invalid,
181
194
  };
182
195
  }
183
196
  const chordFamily = this.guitarDb.chords[parsed.dbRoot];
@@ -187,7 +200,7 @@ class ChordService {
187
200
  raw: token,
188
201
  displayName: parsed.displayName,
189
202
  positions: [],
190
- error: 'Raíz no encontrada en chords-db.'
203
+ error: copy.missingRoot,
191
204
  };
192
205
  }
193
206
  const chord = chordFamily.find((item) => item.suffix === parsed.suffix);
@@ -198,7 +211,7 @@ class ChordService {
198
211
  displayName: parsed.displayName,
199
212
  dbName: `${parsed.dbRoot} ${parsed.suffix}`,
200
213
  positions: [],
201
- error: `El tipo "${parsed.suffix}" no existe para este acorde en la base actual.`
214
+ error: copy.missingType(parsed.suffix),
202
215
  };
203
216
  }
204
217
  return {
@@ -233,7 +246,7 @@ class ChordService {
233
246
  root,
234
247
  dbRoot,
235
248
  displayName: `${root}${this.displaySuffix(suffix)}`,
236
- suffix
249
+ suffix,
237
250
  };
238
251
  }
239
252
  normalizeSuffix(rawSuffix) {
@@ -268,8 +281,52 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImpor
268
281
  args: [{ providedIn: 'root' }]
269
282
  }] });
270
283
 
284
+ const COPY = {
285
+ en: {
286
+ eyebrow: 'Angular · SVG · chords-db',
287
+ title: 'Chord Finder',
288
+ descriptionStart: 'Enter up to',
289
+ descriptionLimit: '5 chords',
290
+ descriptionEnd: 'separated by commas. Sharps and flats are supported:',
291
+ inputLabel: 'Chords',
292
+ exportPng: 'Export PNG',
293
+ limited: 'Only the first 5 chords are rendered.',
294
+ availableTypes: 'Available chord types:',
295
+ resultsLabel: 'Chord results',
296
+ chord: 'Chord',
297
+ normalizedAs: 'Normalized as',
298
+ position: 'Position',
299
+ of: 'of',
300
+ openSource: 'Open source project:',
301
+ contributions: 'Contributions are welcome.',
302
+ assistance: 'Built with AI assistance under supervision from the repository owner.',
303
+ },
304
+ es: {
305
+ eyebrow: 'Angular · SVG · chords-db',
306
+ title: 'Buscador de acordes',
307
+ descriptionStart: 'Escribe hasta',
308
+ descriptionLimit: '5 acordes',
309
+ descriptionEnd: 'separados por coma. Soporta sostenidos y bemoles:',
310
+ inputLabel: 'Acordes',
311
+ exportPng: 'Exportar PNG',
312
+ limited: 'Solo se renderizan los primeros 5 acordes.',
313
+ availableTypes: 'Tipos de acorde disponibles:',
314
+ resultsLabel: 'Resultados de acordes',
315
+ chord: 'Acorde',
316
+ normalizedAs: 'Normalizado como',
317
+ position: 'Posición',
318
+ of: 'de',
319
+ openSource: 'Proyecto de código abierto:',
320
+ contributions: 'Las contribuciones son bienvenidas.',
321
+ assistance: 'Creado con asistencia de IA bajo la supervisión del propietario del repositorio.',
322
+ },
323
+ };
271
324
  class ChordFinderComponent {
272
325
  chordService;
326
+ language = input('en', /* @ts-ignore */
327
+ ...(ngDevMode ? [{ debugName: "language" }] : /* istanbul ignore next */ []));
328
+ text = computed(() => COPY[this.language()], /* @ts-ignore */
329
+ ...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
273
330
  query = 'C';
274
331
  results = signal([], /* @ts-ignore */
275
332
  ...(ngDevMode ? [{ debugName: "results" }] : /* istanbul ignore next */ []));
@@ -281,7 +338,7 @@ class ChordFinderComponent {
281
338
  resultsRow = viewChild.required('resultsRow');
282
339
  constructor(chordService) {
283
340
  this.chordService = chordService;
284
- this.runSearch();
341
+ effect(() => this.runSearch(this.language()));
285
342
  }
286
343
  async exportPng() {
287
344
  const svgs = Array.from(this.resultsRow().nativeElement.querySelectorAll('svg.chord-svg'));
@@ -293,7 +350,8 @@ class ChordFinderComponent {
293
350
  const height = 330;
294
351
  const scale = 2;
295
352
  const canvas = document.createElement('canvas');
296
- canvas.width = (padding * 2 + svgs.length * width + (svgs.length - 1) * gap) * scale;
353
+ canvas.width =
354
+ (padding * 2 + svgs.length * width + (svgs.length - 1) * gap) * scale;
297
355
  canvas.height = (padding * 2 + height) * scale;
298
356
  const ctx = canvas.getContext('2d');
299
357
  if (!ctx)
@@ -313,7 +371,9 @@ class ChordFinderComponent {
313
371
  .fret-number{fill:#000;font-size:42px;font-weight:400}
314
372
  .string-labels text{fill:#000;font-size:18px;font-weight:400}
315
373
  </style>`);
316
- const url = URL.createObjectURL(new Blob([new XMLSerializer().serializeToString(clone)], { type: 'image/svg+xml' }));
374
+ const url = URL.createObjectURL(new Blob([new XMLSerializer().serializeToString(clone)], {
375
+ type: 'image/svg+xml',
376
+ }));
317
377
  const image = new Image();
318
378
  image.src = url;
319
379
  await image.decode();
@@ -325,8 +385,8 @@ class ChordFinderComponent {
325
385
  link.href = canvas.toDataURL('image/png');
326
386
  link.click();
327
387
  }
328
- runSearch() {
329
- const { results, wasLimited } = this.chordService.search(this.query);
388
+ runSearch(language = this.language()) {
389
+ const { results, wasLimited } = this.chordService.search(this.query, language);
330
390
  this.results.set(results);
331
391
  this.wasLimited.set(wasLimited);
332
392
  for (const result of results) {
@@ -344,18 +404,19 @@ class ChordFinderComponent {
344
404
  selectedPosition(result) {
345
405
  if (!result.positions.length)
346
406
  return null;
347
- return result.positions[this.selectedPositionIndex[result.id] ?? 0] ?? result.positions[0];
407
+ return (result.positions[this.selectedPositionIndex[result.id] ?? 0] ??
408
+ result.positions[0]);
348
409
  }
349
410
  trackByResultId(_, result) {
350
411
  return result.id;
351
412
  }
352
413
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: ChordFinderComponent, deps: [{ token: ChordService }], target: i0.ɵɵFactoryTarget.Component });
353
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.4", type: ChordFinderComponent, isStandalone: true, selector: "the-chords-chord-finder", viewQueries: [{ propertyName: "resultsRow", first: true, predicate: ["resultsRow"], descendants: true, isSignal: true }], ngImport: i0, template: "<main class=\"page-shell\">\n <section class=\"hero-card\">\n <p class=\"eyebrow\">Angular \u00B7 SVG \u00B7 chords-db</p>\n <h1>Chord Finder</h1>\n\n <p class=\"description\">\n Escribe hasta <strong>5 acordes</strong> separados por coma. Soporta sostenidos y bemoles:\n <strong>C, F#, C#m, Bb</strong>.\n </p>\n\n <div class=\"search-row\">\n <label for=\"chordInput\">Input de acordes</label>\n <input id=\"chordInput\" type=\"text\" [(ngModel)]=\"query\" (input)=\"runSearch()\" placeholder=\"C, F#, C#m, Bb\"\n autocomplete=\"off\" />\n <button type=\"button\" (click)=\"exportPng()\" [disabled]=\"!results().length\">\n Exportar PNG\n </button>\n </div>\n\n @if (wasLimited()) {\n <p class=\"warning\">Solo se renderizan los primeros 5 acordes por ahora.</p>\n }\n\n <p class=\"hint\">\n Tipos disponibles desde la base: {{ supportedSuffixes() }}...\n </p>\n </section>\n\n <section #resultsRow class=\"results-row\" aria-label=\"Chord results\">\n @for (result of results(); track trackByResultId($index, result)) {\n <article class=\"result-card\" [class.has-error]=\"result.error\">\n <header class=\"result-header\">\n <div>\n <p class=\"result-label\">Acorde</p>\n <h2>{{ result.displayName }}</h2>\n @if (result.dbName && result.raw !== result.displayName) {\n <p class=\"db-name\">Normalizado como {{ result.dbName }}</p>\n }\n </div>\n\n @if (result.positions.length > 1) {\n <label class=\"select-label\">\n Posici\u00F3n\n <select [ngModel]=\"selectedPositionIndex[result.id]\" (ngModelChange)=\"selectPosition(result.id, $event)\">\n @for (position of result.positions; track $index) {\n <option [value]=\"$index\">{{ $index + 1 }} de {{ result.positions.length }}</option>\n }\n </select>\n </label>\n }\n </header>\n\n @if (result.error) {\n <p class=\"error-text\">{{ result.error }}</p>\n } @else if (selectedPosition(result); as position) {\n <div class=\"svg-wrap\">\n <app-chord-diagram [title]=\"result.displayName\" [position]=\"position\"\n [positionIndex]=\"selectedIndex(result.id)\" />\n </div>\n }\n </article>\n }\n </section>\n\n <footer class=\"app-footer\">\n <p>\n Open source project:\n <a href=\"https://github.com/elparaquecosadeque/chord-generator\" target=\"_blank\" rel=\"noreferrer\">\n elparaquecosadeque/chord-generator\n </a>.\n Contributions are welcome.\n </p>\n <p>\n Built with AI assistance under supervision from the repository owner.\n </p>\n </footer>\n</main>\n", styles: [":host{box-sizing:border-box;display:block;min-height:100%;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}:host *,:host *:before,:host *:after{box-sizing:inherit}.page-shell{min-height:100%;padding:clamp(24px,5vw,56px);color:#f8f8ff;background:radial-gradient(circle at 10% 12%,rgba(0,255,255,.25),transparent 28%),radial-gradient(circle at 92% 4%,rgba(255,0,230,.25),transparent 24%),radial-gradient(circle at 50% 110%,rgba(255,242,0,.16),transparent 30%),linear-gradient(135deg,#020004,#050012 48%,#000)}.hero-card{max-width:1020px;padding:clamp(24px,4vw,38px);border:1px solid rgba(0,255,255,.8);border-radius:30px;background:#000000c7;box-shadow:0 0 30px #00ffff52,inset 0 0 26px #ff00e621}.eyebrow{margin:0 0 14px;color:#00fff0;font-size:13px;font-weight:900;letter-spacing:.22em;text-transform:uppercase}h1{margin:0;max-width:820px;font-size:clamp(44px,8vw,92px);line-height:.88;letter-spacing:-.06em;text-shadow:0 0 18px rgba(0,255,240,.8),0 0 32px rgba(255,0,230,.5)}.description,.hint{max-width:760px;margin:22px 0 0;color:#dedbff;font-size:18px;line-height:1.55}.description strong{color:#fe0}.search-row{display:grid;gap:10px;margin-top:28px}.search-row label,.select-label,.result-label{color:#ff2bd6;font-size:12px;font-weight:900;letter-spacing:.14em;text-transform:uppercase}.search-row input{width:min(760px,100%);padding:18px 20px;color:#fff;background:#000;border:2px solid #ff2bd6;border-radius:18px;font-size:clamp(22px,4vw,32px);font-weight:900;outline:none;box-shadow:0 0 18px #ff2bd694,inset 0 0 18px #00ffff1f}.search-row input:focus{border-color:#00fff0;box-shadow:0 0 24px #00fff0c7,inset 0 0 18px #ff2bd633}.search-row button{width:fit-content;padding:12px 18px;color:#000;background:#00fff0;border:0;border-radius:12px;font-weight:900;cursor:pointer}.search-row button:disabled{opacity:.45;cursor:not-allowed}.warning{margin:16px 0 0;color:#fe0;font-weight:900}.results-row{display:flex;flex-wrap:wrap;gap:32px;margin-top:34px;align-items:flex-start}.result-card{width:292px;padding:18px;border:1px solid rgba(0,255,240,.76);border-radius:30px;background:#000c;box-shadow:0 0 22px #00fff040,inset 0 0 18px #ff2bd61f}.result-card.has-error{border-color:#ff315a;box-shadow:0 0 22px #ff315a59}.result-header{display:grid;gap:12px;margin-bottom:16px}.result-label,.db-name{margin:0}.result-header h2{margin:4px 0 0;color:#fff;font-size:30px}.db-name{margin-top:6px;color:#00fff0;font-size:12px;font-weight:800}.select-label{display:grid;gap:8px}select{width:100%;padding:11px 12px;color:#fff;background:#050008;border:1px solid #ff2bd6;border-radius:12px;font-weight:800;outline:none}select:focus{border-color:#00fff0}.svg-wrap{display:grid;place-items:center;padding:0;background:transparent;border-radius:28px}.error-text{color:#ff7b95;font-weight:800;line-height:1.45}.app-footer{max-width:1020px;margin-top:40px;padding-top:20px;border-top:1px solid rgba(0,255,240,.42);color:#dedbff;font-size:14px;line-height:1.6}.app-footer p{margin:0}.app-footer p+p{margin-top:6px}.app-footer a{color:#00fff0;font-weight:900}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ChordDiagram, selector: "app-chord-diagram", inputs: ["title", "position", "positionIndex"] }], changeDetection: i0.ChangeDetectionStrategy.Eager });
414
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.4", type: ChordFinderComponent, isStandalone: true, selector: "the-chords-chord-finder", inputs: { language: { classPropertyName: "language", publicName: "language", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.lang": "language()" } }, viewQueries: [{ propertyName: "resultsRow", first: true, predicate: ["resultsRow"], descendants: true, isSignal: true }], ngImport: i0, template: "<main class=\"page-shell\">\n <section class=\"hero-card\">\n <p class=\"eyebrow\">{{ text().eyebrow }}</p>\n <h1>{{ text().title }}</h1>\n\n <p class=\"description\">\n {{ text().descriptionStart }}\n <strong>{{ text().descriptionLimit }}</strong>\n {{ text().descriptionEnd }}\n <strong>C, F#, C#m, Bb</strong>.\n </p>\n\n <div class=\"search-row\">\n <label for=\"chordInput\">{{ text().inputLabel }}</label>\n <input\n id=\"chordInput\"\n type=\"text\"\n [(ngModel)]=\"query\"\n (input)=\"runSearch()\"\n placeholder=\"C, F#, C#m, Bb\"\n autocomplete=\"off\"\n />\n <button\n type=\"button\"\n (click)=\"exportPng()\"\n [disabled]=\"!results().length\"\n >\n {{ text().exportPng }}\n </button>\n </div>\n\n @if (wasLimited()) {\n <p class=\"warning\">{{ text().limited }}</p>\n }\n\n <p class=\"hint\">{{ text().availableTypes }} {{ supportedSuffixes() }}...</p>\n </section>\n\n <section\n #resultsRow\n class=\"results-row\"\n [attr.aria-label]=\"text().resultsLabel\"\n >\n @for (result of results(); track trackByResultId($index, result)) {\n <article class=\"result-card\" [class.has-error]=\"result.error\">\n <header class=\"result-header\">\n <div>\n <p class=\"result-label\">{{ text().chord }}</p>\n <h2>{{ result.displayName }}</h2>\n @if (result.dbName && result.raw !== result.displayName) {\n <p class=\"db-name\">{{ text().normalizedAs }} {{ result.dbName }}</p>\n }\n </div>\n\n @if (result.positions.length > 1) {\n <label class=\"select-label\">\n {{ text().position }}\n <select\n [ngModel]=\"selectedPositionIndex[result.id]\"\n (ngModelChange)=\"selectPosition(result.id, $event)\"\n >\n @for (position of result.positions; track $index) {\n <option [value]=\"$index\">\n {{ $index + 1 }} {{ text().of }} {{ result.positions.length }}\n </option>\n }\n </select>\n </label>\n }\n </header>\n\n @if (result.error) {\n <p class=\"error-text\">{{ result.error }}</p>\n } @else if (selectedPosition(result); as position) {\n <div class=\"svg-wrap\">\n <app-chord-diagram\n [title]=\"result.displayName\"\n [position]=\"position\"\n [positionIndex]=\"selectedIndex(result.id)\"\n />\n </div>\n }\n </article>\n }\n </section>\n\n <footer class=\"app-footer\">\n <p>\n {{ text().openSource }}\n <a\n href=\"https://github.com/elparaquecosadeque/chord-generator\"\n target=\"_blank\"\n rel=\"noreferrer\"\n >\n elparaquecosadeque/chord-generator </a\n >. {{ text().contributions }}\n </p>\n <p>{{ text().assistance }}</p>\n </footer>\n</main>\n", styles: [":host{--_chords-background: var(--chords-background, #020004);--_chords-background-alt: var(--chords-background-alt, #050012);--_chords-surface: var(--chords-surface, #000000);--_chords-surface-elevated: var( --chords-surface-elevated, rgba(0, 0, 0, .8) );--_chords-text: var(--chords-text, #f8f8ff);--_chords-muted: var(--chords-muted, #dedbff);--_chords-primary: var(--chords-primary, #00fff0);--_chords-secondary: var(--chords-secondary, #ff2bd6);--_chords-highlight: var(--chords-highlight, #ffee00);--_chords-danger: var(--chords-danger, #ff315a);--_chords-border: var(--chords-border, rgba(0, 255, 240, .76));--_chords-on-primary: var(--chords-on-primary, #000000);box-sizing:border-box;display:block;min-height:100%;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}:host *,:host *:before,:host *:after{box-sizing:inherit}.page-shell{min-height:100%;padding:clamp(24px,5vw,56px);color:var(--_chords-text);background:radial-gradient(circle at 10% 12%,color-mix(in srgb,var(--_chords-primary) 25%,transparent),transparent 28%),radial-gradient(circle at 92% 4%,color-mix(in srgb,var(--_chords-secondary) 25%,transparent),transparent 24%),radial-gradient(circle at 50% 110%,color-mix(in srgb,var(--_chords-highlight) 16%,transparent),transparent 30%),linear-gradient(135deg,var(--_chords-background) 0%,var(--_chords-background-alt) 48%,var(--_chords-surface) 100%)}.hero-card{max-width:1020px;padding:clamp(24px,4vw,38px);border:1px solid var(--_chords-border);border-radius:30px;background:var(--_chords-surface-elevated);box-shadow:0 0 30px color-mix(in srgb,var(--_chords-primary) 32%,transparent),inset 0 0 26px color-mix(in srgb,var(--_chords-secondary) 13%,transparent)}.eyebrow{margin:0 0 14px;color:var(--_chords-primary);font-size:13px;font-weight:900;letter-spacing:.22em;text-transform:uppercase}h1{margin:0;max-width:820px;font-size:clamp(44px,8vw,92px);line-height:.88;letter-spacing:-.06em;text-shadow:0 0 18px color-mix(in srgb,var(--_chords-primary) 80%,transparent),0 0 32px color-mix(in srgb,var(--_chords-secondary) 50%,transparent)}.description,.hint{max-width:760px;margin:22px 0 0;color:var(--_chords-muted);font-size:18px;line-height:1.55}.description strong{color:var(--_chords-highlight)}.search-row{display:grid;gap:10px;margin-top:28px}.search-row label,.select-label,.result-label{color:var(--_chords-secondary);font-size:12px;font-weight:900;letter-spacing:.14em;text-transform:uppercase}.search-row input{width:min(760px,100%);padding:18px 20px;color:var(--_chords-text);background:var(--_chords-surface);border:2px solid var(--_chords-secondary);border-radius:18px;font-size:clamp(22px,4vw,32px);font-weight:900;outline:none;box-shadow:0 0 18px color-mix(in srgb,var(--_chords-secondary) 58%,transparent),inset 0 0 18px color-mix(in srgb,var(--_chords-primary) 12%,transparent)}.search-row input:focus{border-color:var(--_chords-primary);box-shadow:0 0 24px color-mix(in srgb,var(--_chords-primary) 78%,transparent),inset 0 0 18px color-mix(in srgb,var(--_chords-secondary) 20%,transparent)}.search-row button{width:fit-content;padding:12px 18px;color:var(--_chords-on-primary);background:var(--_chords-primary);border:0;border-radius:12px;font-weight:900;cursor:pointer}.search-row button:disabled{opacity:.45;cursor:not-allowed}.warning{margin:16px 0 0;color:var(--_chords-highlight);font-weight:900}.results-row{display:flex;flex-wrap:wrap;gap:32px;margin-top:34px;align-items:flex-start}.result-card{width:292px;padding:18px;border:1px solid var(--_chords-border);border-radius:30px;background:var(--_chords-surface-elevated);box-shadow:0 0 22px color-mix(in srgb,var(--_chords-primary) 25%,transparent),inset 0 0 18px color-mix(in srgb,var(--_chords-secondary) 12%,transparent)}.result-card.has-error{border-color:var(--_chords-danger);box-shadow:0 0 22px color-mix(in srgb,var(--_chords-danger) 35%,transparent)}.result-header{display:grid;gap:12px;margin-bottom:16px}.result-label,.db-name{margin:0}.result-header h2{margin:4px 0 0;color:var(--_chords-text);font-size:30px}.db-name{margin-top:6px;color:var(--_chords-primary);font-size:12px;font-weight:800}.select-label{display:grid;gap:8px}select{width:100%;padding:11px 12px;color:var(--_chords-text);background:var(--_chords-background-alt);border:1px solid var(--_chords-secondary);border-radius:12px;font-weight:800;outline:none}select:focus{border-color:var(--_chords-primary)}.svg-wrap{display:grid;place-items:center;padding:0;background:transparent;border-radius:28px}.error-text{color:var(--_chords-danger);font-weight:800;line-height:1.45}.app-footer{max-width:1020px;margin-top:40px;padding-top:20px;border-top:1px solid color-mix(in srgb,var(--_chords-primary) 42%,transparent);color:var(--_chords-muted);font-size:14px;line-height:1.6}.app-footer p{margin:0}.app-footer p+p{margin-top:6px}.app-footer a{color:var(--_chords-primary);font-weight:900}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ChordDiagram, selector: "app-chord-diagram", inputs: ["title", "position", "positionIndex"] }], changeDetection: i0.ChangeDetectionStrategy.Eager });
354
415
  }
355
416
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: ChordFinderComponent, decorators: [{
356
417
  type: Component,
357
- args: [{ selector: 'the-chords-chord-finder', standalone: true, imports: [FormsModule, ChordDiagram], changeDetection: ChangeDetectionStrategy.Eager, template: "<main class=\"page-shell\">\n <section class=\"hero-card\">\n <p class=\"eyebrow\">Angular \u00B7 SVG \u00B7 chords-db</p>\n <h1>Chord Finder</h1>\n\n <p class=\"description\">\n Escribe hasta <strong>5 acordes</strong> separados por coma. Soporta sostenidos y bemoles:\n <strong>C, F#, C#m, Bb</strong>.\n </p>\n\n <div class=\"search-row\">\n <label for=\"chordInput\">Input de acordes</label>\n <input id=\"chordInput\" type=\"text\" [(ngModel)]=\"query\" (input)=\"runSearch()\" placeholder=\"C, F#, C#m, Bb\"\n autocomplete=\"off\" />\n <button type=\"button\" (click)=\"exportPng()\" [disabled]=\"!results().length\">\n Exportar PNG\n </button>\n </div>\n\n @if (wasLimited()) {\n <p class=\"warning\">Solo se renderizan los primeros 5 acordes por ahora.</p>\n }\n\n <p class=\"hint\">\n Tipos disponibles desde la base: {{ supportedSuffixes() }}...\n </p>\n </section>\n\n <section #resultsRow class=\"results-row\" aria-label=\"Chord results\">\n @for (result of results(); track trackByResultId($index, result)) {\n <article class=\"result-card\" [class.has-error]=\"result.error\">\n <header class=\"result-header\">\n <div>\n <p class=\"result-label\">Acorde</p>\n <h2>{{ result.displayName }}</h2>\n @if (result.dbName && result.raw !== result.displayName) {\n <p class=\"db-name\">Normalizado como {{ result.dbName }}</p>\n }\n </div>\n\n @if (result.positions.length > 1) {\n <label class=\"select-label\">\n Posici\u00F3n\n <select [ngModel]=\"selectedPositionIndex[result.id]\" (ngModelChange)=\"selectPosition(result.id, $event)\">\n @for (position of result.positions; track $index) {\n <option [value]=\"$index\">{{ $index + 1 }} de {{ result.positions.length }}</option>\n }\n </select>\n </label>\n }\n </header>\n\n @if (result.error) {\n <p class=\"error-text\">{{ result.error }}</p>\n } @else if (selectedPosition(result); as position) {\n <div class=\"svg-wrap\">\n <app-chord-diagram [title]=\"result.displayName\" [position]=\"position\"\n [positionIndex]=\"selectedIndex(result.id)\" />\n </div>\n }\n </article>\n }\n </section>\n\n <footer class=\"app-footer\">\n <p>\n Open source project:\n <a href=\"https://github.com/elparaquecosadeque/chord-generator\" target=\"_blank\" rel=\"noreferrer\">\n elparaquecosadeque/chord-generator\n </a>.\n Contributions are welcome.\n </p>\n <p>\n Built with AI assistance under supervision from the repository owner.\n </p>\n </footer>\n</main>\n", styles: [":host{box-sizing:border-box;display:block;min-height:100%;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}:host *,:host *:before,:host *:after{box-sizing:inherit}.page-shell{min-height:100%;padding:clamp(24px,5vw,56px);color:#f8f8ff;background:radial-gradient(circle at 10% 12%,rgba(0,255,255,.25),transparent 28%),radial-gradient(circle at 92% 4%,rgba(255,0,230,.25),transparent 24%),radial-gradient(circle at 50% 110%,rgba(255,242,0,.16),transparent 30%),linear-gradient(135deg,#020004,#050012 48%,#000)}.hero-card{max-width:1020px;padding:clamp(24px,4vw,38px);border:1px solid rgba(0,255,255,.8);border-radius:30px;background:#000000c7;box-shadow:0 0 30px #00ffff52,inset 0 0 26px #ff00e621}.eyebrow{margin:0 0 14px;color:#00fff0;font-size:13px;font-weight:900;letter-spacing:.22em;text-transform:uppercase}h1{margin:0;max-width:820px;font-size:clamp(44px,8vw,92px);line-height:.88;letter-spacing:-.06em;text-shadow:0 0 18px rgba(0,255,240,.8),0 0 32px rgba(255,0,230,.5)}.description,.hint{max-width:760px;margin:22px 0 0;color:#dedbff;font-size:18px;line-height:1.55}.description strong{color:#fe0}.search-row{display:grid;gap:10px;margin-top:28px}.search-row label,.select-label,.result-label{color:#ff2bd6;font-size:12px;font-weight:900;letter-spacing:.14em;text-transform:uppercase}.search-row input{width:min(760px,100%);padding:18px 20px;color:#fff;background:#000;border:2px solid #ff2bd6;border-radius:18px;font-size:clamp(22px,4vw,32px);font-weight:900;outline:none;box-shadow:0 0 18px #ff2bd694,inset 0 0 18px #00ffff1f}.search-row input:focus{border-color:#00fff0;box-shadow:0 0 24px #00fff0c7,inset 0 0 18px #ff2bd633}.search-row button{width:fit-content;padding:12px 18px;color:#000;background:#00fff0;border:0;border-radius:12px;font-weight:900;cursor:pointer}.search-row button:disabled{opacity:.45;cursor:not-allowed}.warning{margin:16px 0 0;color:#fe0;font-weight:900}.results-row{display:flex;flex-wrap:wrap;gap:32px;margin-top:34px;align-items:flex-start}.result-card{width:292px;padding:18px;border:1px solid rgba(0,255,240,.76);border-radius:30px;background:#000c;box-shadow:0 0 22px #00fff040,inset 0 0 18px #ff2bd61f}.result-card.has-error{border-color:#ff315a;box-shadow:0 0 22px #ff315a59}.result-header{display:grid;gap:12px;margin-bottom:16px}.result-label,.db-name{margin:0}.result-header h2{margin:4px 0 0;color:#fff;font-size:30px}.db-name{margin-top:6px;color:#00fff0;font-size:12px;font-weight:800}.select-label{display:grid;gap:8px}select{width:100%;padding:11px 12px;color:#fff;background:#050008;border:1px solid #ff2bd6;border-radius:12px;font-weight:800;outline:none}select:focus{border-color:#00fff0}.svg-wrap{display:grid;place-items:center;padding:0;background:transparent;border-radius:28px}.error-text{color:#ff7b95;font-weight:800;line-height:1.45}.app-footer{max-width:1020px;margin-top:40px;padding-top:20px;border-top:1px solid rgba(0,255,240,.42);color:#dedbff;font-size:14px;line-height:1.6}.app-footer p{margin:0}.app-footer p+p{margin-top:6px}.app-footer a{color:#00fff0;font-weight:900}\n"] }]
358
- }], ctorParameters: () => [{ type: ChordService }], propDecorators: { resultsRow: [{ type: i0.ViewChild, args: ['resultsRow', { isSignal: true }] }] } });
418
+ args: [{ selector: 'the-chords-chord-finder', standalone: true, imports: [FormsModule, ChordDiagram], changeDetection: ChangeDetectionStrategy.Eager, host: { '[attr.lang]': 'language()' }, template: "<main class=\"page-shell\">\n <section class=\"hero-card\">\n <p class=\"eyebrow\">{{ text().eyebrow }}</p>\n <h1>{{ text().title }}</h1>\n\n <p class=\"description\">\n {{ text().descriptionStart }}\n <strong>{{ text().descriptionLimit }}</strong>\n {{ text().descriptionEnd }}\n <strong>C, F#, C#m, Bb</strong>.\n </p>\n\n <div class=\"search-row\">\n <label for=\"chordInput\">{{ text().inputLabel }}</label>\n <input\n id=\"chordInput\"\n type=\"text\"\n [(ngModel)]=\"query\"\n (input)=\"runSearch()\"\n placeholder=\"C, F#, C#m, Bb\"\n autocomplete=\"off\"\n />\n <button\n type=\"button\"\n (click)=\"exportPng()\"\n [disabled]=\"!results().length\"\n >\n {{ text().exportPng }}\n </button>\n </div>\n\n @if (wasLimited()) {\n <p class=\"warning\">{{ text().limited }}</p>\n }\n\n <p class=\"hint\">{{ text().availableTypes }} {{ supportedSuffixes() }}...</p>\n </section>\n\n <section\n #resultsRow\n class=\"results-row\"\n [attr.aria-label]=\"text().resultsLabel\"\n >\n @for (result of results(); track trackByResultId($index, result)) {\n <article class=\"result-card\" [class.has-error]=\"result.error\">\n <header class=\"result-header\">\n <div>\n <p class=\"result-label\">{{ text().chord }}</p>\n <h2>{{ result.displayName }}</h2>\n @if (result.dbName && result.raw !== result.displayName) {\n <p class=\"db-name\">{{ text().normalizedAs }} {{ result.dbName }}</p>\n }\n </div>\n\n @if (result.positions.length > 1) {\n <label class=\"select-label\">\n {{ text().position }}\n <select\n [ngModel]=\"selectedPositionIndex[result.id]\"\n (ngModelChange)=\"selectPosition(result.id, $event)\"\n >\n @for (position of result.positions; track $index) {\n <option [value]=\"$index\">\n {{ $index + 1 }} {{ text().of }} {{ result.positions.length }}\n </option>\n }\n </select>\n </label>\n }\n </header>\n\n @if (result.error) {\n <p class=\"error-text\">{{ result.error }}</p>\n } @else if (selectedPosition(result); as position) {\n <div class=\"svg-wrap\">\n <app-chord-diagram\n [title]=\"result.displayName\"\n [position]=\"position\"\n [positionIndex]=\"selectedIndex(result.id)\"\n />\n </div>\n }\n </article>\n }\n </section>\n\n <footer class=\"app-footer\">\n <p>\n {{ text().openSource }}\n <a\n href=\"https://github.com/elparaquecosadeque/chord-generator\"\n target=\"_blank\"\n rel=\"noreferrer\"\n >\n elparaquecosadeque/chord-generator </a\n >. {{ text().contributions }}\n </p>\n <p>{{ text().assistance }}</p>\n </footer>\n</main>\n", styles: [":host{--_chords-background: var(--chords-background, #020004);--_chords-background-alt: var(--chords-background-alt, #050012);--_chords-surface: var(--chords-surface, #000000);--_chords-surface-elevated: var( --chords-surface-elevated, rgba(0, 0, 0, .8) );--_chords-text: var(--chords-text, #f8f8ff);--_chords-muted: var(--chords-muted, #dedbff);--_chords-primary: var(--chords-primary, #00fff0);--_chords-secondary: var(--chords-secondary, #ff2bd6);--_chords-highlight: var(--chords-highlight, #ffee00);--_chords-danger: var(--chords-danger, #ff315a);--_chords-border: var(--chords-border, rgba(0, 255, 240, .76));--_chords-on-primary: var(--chords-on-primary, #000000);box-sizing:border-box;display:block;min-height:100%;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}:host *,:host *:before,:host *:after{box-sizing:inherit}.page-shell{min-height:100%;padding:clamp(24px,5vw,56px);color:var(--_chords-text);background:radial-gradient(circle at 10% 12%,color-mix(in srgb,var(--_chords-primary) 25%,transparent),transparent 28%),radial-gradient(circle at 92% 4%,color-mix(in srgb,var(--_chords-secondary) 25%,transparent),transparent 24%),radial-gradient(circle at 50% 110%,color-mix(in srgb,var(--_chords-highlight) 16%,transparent),transparent 30%),linear-gradient(135deg,var(--_chords-background) 0%,var(--_chords-background-alt) 48%,var(--_chords-surface) 100%)}.hero-card{max-width:1020px;padding:clamp(24px,4vw,38px);border:1px solid var(--_chords-border);border-radius:30px;background:var(--_chords-surface-elevated);box-shadow:0 0 30px color-mix(in srgb,var(--_chords-primary) 32%,transparent),inset 0 0 26px color-mix(in srgb,var(--_chords-secondary) 13%,transparent)}.eyebrow{margin:0 0 14px;color:var(--_chords-primary);font-size:13px;font-weight:900;letter-spacing:.22em;text-transform:uppercase}h1{margin:0;max-width:820px;font-size:clamp(44px,8vw,92px);line-height:.88;letter-spacing:-.06em;text-shadow:0 0 18px color-mix(in srgb,var(--_chords-primary) 80%,transparent),0 0 32px color-mix(in srgb,var(--_chords-secondary) 50%,transparent)}.description,.hint{max-width:760px;margin:22px 0 0;color:var(--_chords-muted);font-size:18px;line-height:1.55}.description strong{color:var(--_chords-highlight)}.search-row{display:grid;gap:10px;margin-top:28px}.search-row label,.select-label,.result-label{color:var(--_chords-secondary);font-size:12px;font-weight:900;letter-spacing:.14em;text-transform:uppercase}.search-row input{width:min(760px,100%);padding:18px 20px;color:var(--_chords-text);background:var(--_chords-surface);border:2px solid var(--_chords-secondary);border-radius:18px;font-size:clamp(22px,4vw,32px);font-weight:900;outline:none;box-shadow:0 0 18px color-mix(in srgb,var(--_chords-secondary) 58%,transparent),inset 0 0 18px color-mix(in srgb,var(--_chords-primary) 12%,transparent)}.search-row input:focus{border-color:var(--_chords-primary);box-shadow:0 0 24px color-mix(in srgb,var(--_chords-primary) 78%,transparent),inset 0 0 18px color-mix(in srgb,var(--_chords-secondary) 20%,transparent)}.search-row button{width:fit-content;padding:12px 18px;color:var(--_chords-on-primary);background:var(--_chords-primary);border:0;border-radius:12px;font-weight:900;cursor:pointer}.search-row button:disabled{opacity:.45;cursor:not-allowed}.warning{margin:16px 0 0;color:var(--_chords-highlight);font-weight:900}.results-row{display:flex;flex-wrap:wrap;gap:32px;margin-top:34px;align-items:flex-start}.result-card{width:292px;padding:18px;border:1px solid var(--_chords-border);border-radius:30px;background:var(--_chords-surface-elevated);box-shadow:0 0 22px color-mix(in srgb,var(--_chords-primary) 25%,transparent),inset 0 0 18px color-mix(in srgb,var(--_chords-secondary) 12%,transparent)}.result-card.has-error{border-color:var(--_chords-danger);box-shadow:0 0 22px color-mix(in srgb,var(--_chords-danger) 35%,transparent)}.result-header{display:grid;gap:12px;margin-bottom:16px}.result-label,.db-name{margin:0}.result-header h2{margin:4px 0 0;color:var(--_chords-text);font-size:30px}.db-name{margin-top:6px;color:var(--_chords-primary);font-size:12px;font-weight:800}.select-label{display:grid;gap:8px}select{width:100%;padding:11px 12px;color:var(--_chords-text);background:var(--_chords-background-alt);border:1px solid var(--_chords-secondary);border-radius:12px;font-weight:800;outline:none}select:focus{border-color:var(--_chords-primary)}.svg-wrap{display:grid;place-items:center;padding:0;background:transparent;border-radius:28px}.error-text{color:var(--_chords-danger);font-weight:800;line-height:1.45}.app-footer{max-width:1020px;margin-top:40px;padding-top:20px;border-top:1px solid color-mix(in srgb,var(--_chords-primary) 42%,transparent);color:var(--_chords-muted);font-size:14px;line-height:1.6}.app-footer p{margin:0}.app-footer p+p{margin-top:6px}.app-footer a{color:var(--_chords-primary);font-weight:900}\n"] }]
419
+ }], ctorParameters: () => [{ type: ChordService }], propDecorators: { language: [{ type: i0.Input, args: [{ isSignal: true, alias: "language", required: false }] }], resultsRow: [{ type: i0.ViewChild, args: ['resultsRow', { isSignal: true }] }] } });
359
420
 
360
421
  /*
361
422
  * Public API Surface of chord-finder
@@ -1 +1 @@
1
- {"version":3,"file":"gblp-chord-finder.mjs","sources":["../../../projects/chord-finder/src/lib/components/chord-diagram/chord-diagram.ts","../../../projects/chord-finder/src/lib/components/chord-diagram/chord-diagram.html","../../../projects/chord-finder/src/lib/services/chord.service.ts","../../../projects/chord-finder/src/lib/chord-finder.ts","../../../projects/chord-finder/src/lib/chord-finder.html","../../../projects/chord-finder/src/public-api.ts","../../../projects/chord-finder/src/gblp-chord-finder.ts"],"sourcesContent":["import { Component, computed, input, ChangeDetectionStrategy } from '@angular/core';\nimport { ChordsDbPosition } from '../../models/chord.model';\n\ninterface BarreSpan {\n fret: number;\n x: number;\n y: number;\n width: number;\n labelX: number;\n labelY: number;\n finger: number | undefined;\n}\n\ninterface DotMarker {\n fret: number;\n stringIndex: number;\n finger?: number;\n}\n\n@Component({\n selector: 'app-chord-diagram',\n imports: [],\n templateUrl: './chord-diagram.html',\n changeDetection: ChangeDetectionStrategy.Eager,\n styleUrl: './chord-diagram.scss'\n})\nexport class ChordDiagram {\n title = input.required<string>();\n position = input.required<ChordsDbPosition>();\n positionIndex = input<number>(0);\n\n readonly width = 240;\n readonly height = 330;\n readonly left = 54;\n readonly top = 112;\n readonly stringGap = 28;\n readonly fretGap = 34;\n readonly dotRadius = 13;\n readonly stringLabels = ['E', 'A', 'D', 'G', 'B', 'E'];\n\n fretCount = computed(() => Math.max(5, ...this.position().frets.filter((fret) => fret > 0)));\n\n fretLines = computed(() => Array.from({ length: this.fretCount() + 1 }, (_, index) => index));\n\n stringLines = computed(() => Array.from({ length: 6 }, (_, index) => index));\n\n visibleDots = computed<DotMarker[]>(() => {\n const barres = new Set(this.position().barres ?? []);\n const fingers = this.position().fingers ?? [];\n\n return this.position().frets\n .map((fret, stringIndex) => ({\n fret,\n stringIndex,\n finger: fingers[stringIndex] > 0 ? fingers[stringIndex] : undefined\n }))\n .filter(({ fret }) => fret > 0)\n .filter(({ fret }) => !barres.has(fret));\n });\n\n barreSpans = computed<BarreSpan[]>(() => {\n const barres = this.position().barres ?? [];\n const fingers = this.position().fingers ?? [];\n\n return barres\n .map((barreFret) => {\n const playableStrings = this.position().frets\n .map((fret, index) => ({ fret, index }))\n .filter(({ fret }) => fret >= barreFret);\n\n if (playableStrings.length < 2) return null;\n\n const firstString = playableStrings[0].index;\n const lastString = playableStrings[playableStrings.length - 1].index;\n const x = this.stringX(firstString) - this.dotRadius;\n const y = this.dotY(barreFret) - this.dotRadius;\n const width = this.stringX(lastString) - this.stringX(firstString) + this.dotRadius * 2;\n const labelX = x + width / 2;\n const labelY = y + this.dotRadius;\n const finger = fingers[firstString] > 0 ? fingers[firstString] : undefined;\n\n return {\n fret: barreFret,\n x,\n y,\n width,\n labelX,\n labelY,\n finger\n } satisfies BarreSpan;\n })\n .filter((span): span is BarreSpan => span !== null);\n });\n\n get viewBox(): string {\n return `0 0 ${this.width} ${this.height}`;\n }\n\n baseFret(): number {\n return this.position().baseFret ?? 1;\n }\n\n stringX(index: number): number {\n return this.left + index * this.stringGap;\n }\n\n fretY(index: number): number {\n return this.top + index * this.fretGap;\n }\n\n dotY(fret: number): number {\n return this.top + (fret - 0.5) * this.fretGap;\n }\n\n markerFor(fret: number): string {\n if (fret === -1) return '×';\n if (fret === 0) return '○';\n return '';\n }\n\n fretLabel(): string {\n return String(this.baseFret());\n }\n\n positionCode(): string {\n return this.position().frets.map((fret) => (fret === -1 ? 'x' : String(fret))).join('');\n }\n}\n","<svg class=\"chord-svg\" [attr.viewBox]=\"viewBox\" role=\"img\" [attr.aria-label]=\"title() + ' guitar chord diagram'\">\n <title>{{ title() }} - posición {{ positionIndex() + 1 }} - {{ positionCode() }}</title>\n\n <rect class=\"card-bg\" x=\"0\" y=\"0\" [attr.width]=\"width\" [attr.height]=\"height\" rx=\"24\" fill=\"#ffffff\" />\n\n <text x=\"120\" y=\"50\" text-anchor=\"middle\" class=\"title\">{{ title() }}</text>\n\n <g class=\"markers\">\n @for (fret of position().frets; track $index) {\n <text [attr.x]=\"stringX($index)\" y=\"92\" text-anchor=\"middle\">{{ markerFor(fret) }}</text>\n }\n </g>\n\n <text x=\"26\" [attr.y]=\"fretY(0) + 28\" text-anchor=\"middle\" class=\"fret-number\">{{ fretLabel() }}</text>\n\n <g class=\"grid\">\n @for (line of fretLines(); track line) {\n <line\n [attr.x1]=\"stringX(0)\"\n [attr.x2]=\"stringX(5)\"\n [attr.y1]=\"fretY(line)\"\n [attr.y2]=\"fretY(line)\"\n [class.nut]=\"line === 0 && baseFret() === 1\"\n />\n }\n\n @for (line of stringLines(); track line) {\n <line\n [attr.x1]=\"stringX(line)\"\n [attr.x2]=\"stringX(line)\"\n [attr.y1]=\"fretY(0)\"\n [attr.y2]=\"fretY(fretCount())\"\n />\n }\n </g>\n\n <g class=\"barres\">\n @for (barre of barreSpans(); track barre.fret) {\n <rect\n [attr.x]=\"barre.x\"\n [attr.y]=\"barre.y\"\n [attr.width]=\"barre.width\"\n [attr.height]=\"dotRadius * 2\"\n rx=\"13\"\n />\n @if (barre.finger) {\n <text [attr.x]=\"barre.labelX\" [attr.y]=\"barre.labelY\" text-anchor=\"middle\">{{ barre.finger }}</text>\n }\n }\n </g>\n\n <g class=\"dots\">\n @for (dot of visibleDots(); track dot.stringIndex) {\n <circle [attr.cx]=\"stringX(dot.stringIndex)\" [attr.cy]=\"dotY(dot.fret)\" [attr.r]=\"dotRadius\" />\n @if (dot.finger) {\n <text [attr.x]=\"stringX(dot.stringIndex)\" [attr.y]=\"dotY(dot.fret)\" text-anchor=\"middle\">{{ dot.finger }}</text>\n }\n }\n </g>\n\n <g class=\"string-labels\">\n @for (label of stringLabels; track label + $index) {\n <text [attr.x]=\"stringX($index)\" [attr.y]=\"fretY(fretCount()) + 32\" text-anchor=\"middle\">{{ label }}</text>\n }\n </g>\n</svg>\n","import { Injectable } from '@angular/core';\nimport guitarDbJson from '@tombatossals/chords-db/lib/guitar.json';\nimport { ChordSearchResult, ChordsDbInstrument, ParsedChord } from '../models/chord.model';\n\n@Injectable({ providedIn: 'root' })\nexport class ChordService {\n readonly maxBatchSize = 5;\n\n private readonly guitarDb = guitarDbJson as ChordsDbInstrument;\n\n private readonly dbRootMap: Record<string, string> = {\n C: 'C',\n 'C#': 'Csharp',\n Db: 'Csharp',\n D: 'D',\n 'D#': 'Eb',\n Eb: 'Eb',\n E: 'E',\n Fb: 'E',\n 'E#': 'F',\n F: 'F',\n 'F#': 'Fsharp',\n Gb: 'Fsharp',\n G: 'G',\n 'G#': 'Ab',\n Ab: 'Ab',\n A: 'A',\n 'A#': 'Bb',\n Bb: 'Bb',\n B: 'B',\n Cb: 'B',\n 'B#': 'C'\n };\n\n private readonly suffixAliases: Record<string, string> = {\n '': 'major',\n M: 'major',\n maj: 'major',\n major: 'major',\n m: 'minor',\n min: 'minor',\n '-': 'minor',\n minor: 'minor',\n Δ: 'maj7',\n maj7: 'maj7',\n M7: 'maj7',\n m7: 'm7',\n min7: 'm7',\n dim: 'dim',\n diminished: 'dim',\n aug: 'aug',\n augmented: 'aug',\n sus: 'sus4',\n sus2: 'sus2',\n sus4: 'sus4',\n add9: 'add9',\n 4: 'sus4'\n };\n\n search(input: string): { results: ChordSearchResult[]; wasLimited: boolean } {\n const tokens = input\n .split(',')\n .map((token) => token.trim())\n .filter(Boolean);\n\n const limitedTokens = tokens.slice(0, this.maxBatchSize);\n\n return {\n results: limitedTokens.map((token, index) => this.searchSingle(token, index)),\n wasLimited: tokens.length > this.maxBatchSize\n };\n }\n\n suffixes(): string[] {\n return this.guitarDb.suffixes;\n }\n\n private searchSingle(token: string, index: number): ChordSearchResult {\n const parsed = this.parseChordName(token);\n\n if (!parsed) {\n return {\n id: `${index}-${token}`,\n raw: token,\n displayName: token,\n positions: [],\n error: 'Nombre inválido. Prueba C, F#, C#m, Bb, Am7 o Dsus4.'\n };\n }\n const chordFamily = this.guitarDb.chords[parsed.dbRoot];\n\n if (!chordFamily) {\n return {\n id: `${index}-${parsed.displayName}`,\n raw: token,\n displayName: parsed.displayName,\n positions: [],\n error: 'Raíz no encontrada en chords-db.'\n };\n }\n\n const chord = chordFamily.find((item) => item.suffix === parsed.suffix);\n\n if (!chord) {\n return {\n id: `${index}-${parsed.displayName}`,\n raw: token,\n displayName: parsed.displayName,\n dbName: `${parsed.dbRoot} ${parsed.suffix}`,\n positions: [],\n error: `El tipo \"${parsed.suffix}\" no existe para este acorde en la base actual.`\n };\n }\n\n return {\n id: `${index}-${parsed.dbRoot}-${parsed.suffix}`,\n raw: token,\n displayName: parsed.displayName,\n dbName: `${chord.key} ${chord.suffix}`,\n chord,\n positions: chord.positions,\n };\n }\n\n private parseChordName(raw: string): ParsedChord | null {\n const cleaned = raw\n .replaceAll('♯', '#')\n .replaceAll('♭', 'b')\n .replace(/\\s+/g, '');\n\n const match = cleaned.match(/^([A-Ga-g])([#b]?)(.*)$/);\n if (!match) return null;\n\n const letter = match[1].toUpperCase();\n const accidental = match[2] ?? '';\n const suffixRaw = match[3] ?? '';\n const root = `${letter}${accidental}`;\n const dbRoot = this.dbRootMap[root];\n\n if (!dbRoot) return null;\n\n const suffix = this.normalizeSuffix(suffixRaw);\n if (!suffix) return null;\n\n return {\n raw,\n root,\n dbRoot,\n displayName: `${root}${this.displaySuffix(suffix)}`,\n suffix\n };\n }\n\n private normalizeSuffix(rawSuffix: string): string | null {\n const trimmed = rawSuffix.trim();\n\n if (this.suffixAliases[trimmed] !== undefined) {\n return this.suffixAliases[trimmed];\n }\n\n const lower = trimmed.toLowerCase();\n if (this.suffixAliases[lower] !== undefined) {\n return this.suffixAliases[lower];\n }\n\n if (this.guitarDb.suffixes.includes(trimmed)) {\n return trimmed;\n }\n\n if (this.guitarDb.suffixes.includes(lower)) {\n return lower;\n }\n\n return null;\n }\n\n private displaySuffix(suffix: string): string {\n if (suffix === 'major') return '';\n if (suffix === 'minor') return 'm';\n return suffix;\n }\n}\n","import { Component, ElementRef, computed, signal, viewChild, ChangeDetectionStrategy } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { ChordDiagram } from './components/chord-diagram/chord-diagram';\nimport { ChordSearchResult, ChordsDbPosition } from './models/chord.model';\nimport { ChordService } from './services/chord.service';\n\n@Component({\n selector: 'the-chords-chord-finder',\n standalone: true,\n imports: [FormsModule, ChordDiagram],\n templateUrl: './chord-finder.html',\n changeDetection: ChangeDetectionStrategy.Eager,\n styleUrl: './chord-finder.scss'\n})\nexport class ChordFinderComponent {\n query = 'C';\n results = signal<ChordSearchResult[]>([]);\n wasLimited = signal(false);\n selectedPositionIndex: Record<string, number> = {};\n supportedSuffixes = computed(() => this.chordService.suffixes().slice(0, 18).join(', '));\n\n resultsRow = viewChild.required<ElementRef<HTMLElement>>('resultsRow');\n\n constructor(private readonly chordService: ChordService) {\n this.runSearch();\n }\n\n async exportPng(): Promise<void> {\n const svgs = Array.from(this.resultsRow().nativeElement.querySelectorAll<SVGSVGElement>('svg.chord-svg'));\n if (!svgs.length) return;\n\n const padding = 32;\n const gap = 24;\n const width = 240;\n const height = 330;\n const scale = 2;\n\n const canvas = document.createElement('canvas');\n canvas.width = (padding * 2 + svgs.length * width + (svgs.length - 1) * gap) * scale;\n canvas.height = (padding * 2 + height) * scale;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n ctx.scale(scale, scale);\n ctx.fillStyle = '#ffffff';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n\n for (const [index, svg] of svgs.entries()) {\n const clone = svg.cloneNode(true) as SVGSVGElement;\n\n clone.insertAdjacentHTML('afterbegin', `<style>\n .chord-svg{font-family:Roboto,Arial,sans-serif;font-weight:400}\n .card-bg{fill:#fff}.title{font-size:42px;font-weight:400;fill:#000}\n .grid line{stroke:#000;stroke-width:2.6;stroke-linecap:square}\n .grid line.nut{stroke-width:7}.barres rect,.dots circle{fill:#000}\n .barres text,.dots text{fill:#fff;font-size:16px;font-weight:400;dominant-baseline:central;alignment-baseline:middle}\n .markers text{fill:#000;font-size:30px;font-weight:400}\n .fret-number{fill:#000;font-size:42px;font-weight:400}\n .string-labels text{fill:#000;font-size:18px;font-weight:400}\n </style>`);\n\n const url = URL.createObjectURL(new Blob([new XMLSerializer().serializeToString(clone)], { type: 'image/svg+xml' }));\n const image = new Image();\n image.src = url;\n await image.decode();\n\n ctx.drawImage(image, padding + index * (width + gap), padding, width, height);\n URL.revokeObjectURL(url);\n }\n\n const link = document.createElement('a');\n link.download = 'chords.png';\n link.href = canvas.toDataURL('image/png');\n link.click();\n }\n\n\n runSearch(): void {\n const { results, wasLimited } = this.chordService.search(this.query);\n this.results.set(results);\n this.wasLimited.set(wasLimited);\n\n for (const result of results) {\n if (this.selectedPositionIndex[result.id] === undefined) {\n this.selectedPositionIndex[result.id] = 0;\n }\n }\n }\n\n selectPosition(resultId: string, value: string | number): void {\n this.selectedPositionIndex[resultId] = Number(value);\n }\n\n selectedIndex(resultId: string): number {\n return this.selectedPositionIndex[resultId] ?? 0;\n }\n\n selectedPosition(result: ChordSearchResult): ChordsDbPosition | null {\n if (!result.positions.length) return null;\n return result.positions[this.selectedPositionIndex[result.id] ?? 0] ?? result.positions[0];\n }\n\n trackByResultId(_: number, result: ChordSearchResult): string {\n return result.id;\n }\n}\n","<main class=\"page-shell\">\n <section class=\"hero-card\">\n <p class=\"eyebrow\">Angular · SVG · chords-db</p>\n <h1>Chord Finder</h1>\n\n <p class=\"description\">\n Escribe hasta <strong>5 acordes</strong> separados por coma. Soporta sostenidos y bemoles:\n <strong>C, F#, C#m, Bb</strong>.\n </p>\n\n <div class=\"search-row\">\n <label for=\"chordInput\">Input de acordes</label>\n <input id=\"chordInput\" type=\"text\" [(ngModel)]=\"query\" (input)=\"runSearch()\" placeholder=\"C, F#, C#m, Bb\"\n autocomplete=\"off\" />\n <button type=\"button\" (click)=\"exportPng()\" [disabled]=\"!results().length\">\n Exportar PNG\n </button>\n </div>\n\n @if (wasLimited()) {\n <p class=\"warning\">Solo se renderizan los primeros 5 acordes por ahora.</p>\n }\n\n <p class=\"hint\">\n Tipos disponibles desde la base: {{ supportedSuffixes() }}...\n </p>\n </section>\n\n <section #resultsRow class=\"results-row\" aria-label=\"Chord results\">\n @for (result of results(); track trackByResultId($index, result)) {\n <article class=\"result-card\" [class.has-error]=\"result.error\">\n <header class=\"result-header\">\n <div>\n <p class=\"result-label\">Acorde</p>\n <h2>{{ result.displayName }}</h2>\n @if (result.dbName && result.raw !== result.displayName) {\n <p class=\"db-name\">Normalizado como {{ result.dbName }}</p>\n }\n </div>\n\n @if (result.positions.length > 1) {\n <label class=\"select-label\">\n Posición\n <select [ngModel]=\"selectedPositionIndex[result.id]\" (ngModelChange)=\"selectPosition(result.id, $event)\">\n @for (position of result.positions; track $index) {\n <option [value]=\"$index\">{{ $index + 1 }} de {{ result.positions.length }}</option>\n }\n </select>\n </label>\n }\n </header>\n\n @if (result.error) {\n <p class=\"error-text\">{{ result.error }}</p>\n } @else if (selectedPosition(result); as position) {\n <div class=\"svg-wrap\">\n <app-chord-diagram [title]=\"result.displayName\" [position]=\"position\"\n [positionIndex]=\"selectedIndex(result.id)\" />\n </div>\n }\n </article>\n }\n </section>\n\n <footer class=\"app-footer\">\n <p>\n Open source project:\n <a href=\"https://github.com/elparaquecosadeque/chord-generator\" target=\"_blank\" rel=\"noreferrer\">\n elparaquecosadeque/chord-generator\n </a>.\n Contributions are welcome.\n </p>\n <p>\n Built with AI assistance under supervision from the repository owner.\n </p>\n </footer>\n</main>\n","/*\r\n * Public API Surface of chord-finder\r\n */\r\n\r\nexport * from './lib/chord-finder';\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["i1.ChordService"],"mappings":"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"}
1
+ {"version":3,"file":"gblp-chord-finder.mjs","sources":["../../../projects/chord-finder/src/lib/components/chord-diagram/chord-diagram.ts","../../../projects/chord-finder/src/lib/components/chord-diagram/chord-diagram.html","../../../projects/chord-finder/src/lib/services/chord.service.ts","../../../projects/chord-finder/src/lib/chord-finder.ts","../../../projects/chord-finder/src/lib/chord-finder.html","../../../projects/chord-finder/src/public-api.ts","../../../projects/chord-finder/src/gblp-chord-finder.ts"],"sourcesContent":["import { Component, computed, input, ChangeDetectionStrategy } from '@angular/core';\r\nimport { ChordsDbPosition } from '../../models/chord.model';\r\n\r\ninterface BarreSpan {\r\n fret: number;\r\n x: number;\r\n y: number;\r\n width: number;\r\n labelX: number;\r\n labelY: number;\r\n finger: number | undefined;\r\n}\r\n\r\ninterface DotMarker {\r\n fret: number;\r\n stringIndex: number;\r\n finger?: number;\r\n}\r\n\r\n@Component({\r\n selector: 'app-chord-diagram',\r\n imports: [],\r\n templateUrl: './chord-diagram.html',\r\n changeDetection: ChangeDetectionStrategy.Eager,\r\n styleUrl: './chord-diagram.scss'\r\n})\r\nexport class ChordDiagram {\r\n title = input.required<string>();\r\n position = input.required<ChordsDbPosition>();\r\n positionIndex = input<number>(0);\r\n\r\n readonly width = 240;\r\n readonly height = 330;\r\n readonly left = 54;\r\n readonly top = 112;\r\n readonly stringGap = 28;\r\n readonly fretGap = 34;\r\n readonly dotRadius = 13;\r\n readonly stringLabels = ['E', 'A', 'D', 'G', 'B', 'E'];\r\n\r\n fretCount = computed(() => Math.max(5, ...this.position().frets.filter((fret) => fret > 0)));\r\n\r\n fretLines = computed(() => Array.from({ length: this.fretCount() + 1 }, (_, index) => index));\r\n\r\n stringLines = computed(() => Array.from({ length: 6 }, (_, index) => index));\r\n\r\n visibleDots = computed<DotMarker[]>(() => {\r\n const barres = new Set(this.position().barres ?? []);\r\n const fingers = this.position().fingers ?? [];\r\n\r\n return this.position().frets\r\n .map((fret, stringIndex) => ({\r\n fret,\r\n stringIndex,\r\n finger: fingers[stringIndex] > 0 ? fingers[stringIndex] : undefined\r\n }))\r\n .filter(({ fret }) => fret > 0)\r\n .filter(({ fret }) => !barres.has(fret));\r\n });\r\n\r\n barreSpans = computed<BarreSpan[]>(() => {\r\n const barres = this.position().barres ?? [];\r\n const fingers = this.position().fingers ?? [];\r\n\r\n return barres\r\n .map((barreFret) => {\r\n const playableStrings = this.position().frets\r\n .map((fret, index) => ({ fret, index }))\r\n .filter(({ fret }) => fret >= barreFret);\r\n\r\n if (playableStrings.length < 2) return null;\r\n\r\n const firstString = playableStrings[0].index;\r\n const lastString = playableStrings[playableStrings.length - 1].index;\r\n const x = this.stringX(firstString) - this.dotRadius;\r\n const y = this.dotY(barreFret) - this.dotRadius;\r\n const width = this.stringX(lastString) - this.stringX(firstString) + this.dotRadius * 2;\r\n const labelX = x + width / 2;\r\n const labelY = y + this.dotRadius;\r\n const finger = fingers[firstString] > 0 ? fingers[firstString] : undefined;\r\n\r\n return {\r\n fret: barreFret,\r\n x,\r\n y,\r\n width,\r\n labelX,\r\n labelY,\r\n finger\r\n } satisfies BarreSpan;\r\n })\r\n .filter((span): span is BarreSpan => span !== null);\r\n });\r\n\r\n get viewBox(): string {\r\n return `0 0 ${this.width} ${this.height}`;\r\n }\r\n\r\n baseFret(): number {\r\n return this.position().baseFret ?? 1;\r\n }\r\n\r\n stringX(index: number): number {\r\n return this.left + index * this.stringGap;\r\n }\r\n\r\n fretY(index: number): number {\r\n return this.top + index * this.fretGap;\r\n }\r\n\r\n dotY(fret: number): number {\r\n return this.top + (fret - 0.5) * this.fretGap;\r\n }\r\n\r\n markerFor(fret: number): string {\r\n if (fret === -1) return '×';\r\n if (fret === 0) return '○';\r\n return '';\r\n }\r\n\r\n fretLabel(): string {\r\n return String(this.baseFret());\r\n }\r\n\r\n positionCode(): string {\r\n return this.position().frets.map((fret) => (fret === -1 ? 'x' : String(fret))).join('');\r\n }\r\n}\r\n","<svg class=\"chord-svg\" [attr.viewBox]=\"viewBox\" role=\"img\" [attr.aria-label]=\"title() + ' guitar chord diagram'\">\r\n <title>{{ title() }} - posición {{ positionIndex() + 1 }} - {{ positionCode() }}</title>\r\n\r\n <rect class=\"card-bg\" x=\"0\" y=\"0\" [attr.width]=\"width\" [attr.height]=\"height\" rx=\"24\" fill=\"#ffffff\" />\r\n\r\n <text x=\"120\" y=\"50\" text-anchor=\"middle\" class=\"title\">{{ title() }}</text>\r\n\r\n <g class=\"markers\">\r\n @for (fret of position().frets; track $index) {\r\n <text [attr.x]=\"stringX($index)\" y=\"92\" text-anchor=\"middle\">{{ markerFor(fret) }}</text>\r\n }\r\n </g>\r\n\r\n <text x=\"26\" [attr.y]=\"fretY(0) + 28\" text-anchor=\"middle\" class=\"fret-number\">{{ fretLabel() }}</text>\r\n\r\n <g class=\"grid\">\r\n @for (line of fretLines(); track line) {\r\n <line\r\n [attr.x1]=\"stringX(0)\"\r\n [attr.x2]=\"stringX(5)\"\r\n [attr.y1]=\"fretY(line)\"\r\n [attr.y2]=\"fretY(line)\"\r\n [class.nut]=\"line === 0 && baseFret() === 1\"\r\n />\r\n }\r\n\r\n @for (line of stringLines(); track line) {\r\n <line\r\n [attr.x1]=\"stringX(line)\"\r\n [attr.x2]=\"stringX(line)\"\r\n [attr.y1]=\"fretY(0)\"\r\n [attr.y2]=\"fretY(fretCount())\"\r\n />\r\n }\r\n </g>\r\n\r\n <g class=\"barres\">\r\n @for (barre of barreSpans(); track barre.fret) {\r\n <rect\r\n [attr.x]=\"barre.x\"\r\n [attr.y]=\"barre.y\"\r\n [attr.width]=\"barre.width\"\r\n [attr.height]=\"dotRadius * 2\"\r\n rx=\"13\"\r\n />\r\n @if (barre.finger) {\r\n <text [attr.x]=\"barre.labelX\" [attr.y]=\"barre.labelY\" text-anchor=\"middle\">{{ barre.finger }}</text>\r\n }\r\n }\r\n </g>\r\n\r\n <g class=\"dots\">\r\n @for (dot of visibleDots(); track dot.stringIndex) {\r\n <circle [attr.cx]=\"stringX(dot.stringIndex)\" [attr.cy]=\"dotY(dot.fret)\" [attr.r]=\"dotRadius\" />\r\n @if (dot.finger) {\r\n <text [attr.x]=\"stringX(dot.stringIndex)\" [attr.y]=\"dotY(dot.fret)\" text-anchor=\"middle\">{{ dot.finger }}</text>\r\n }\r\n }\r\n </g>\r\n\r\n <g class=\"string-labels\">\r\n @for (label of stringLabels; track label + $index) {\r\n <text [attr.x]=\"stringX($index)\" [attr.y]=\"fretY(fretCount()) + 32\" text-anchor=\"middle\">{{ label }}</text>\r\n }\r\n </g>\r\n</svg>\r\n","import { Injectable } from '@angular/core';\nimport guitarDbJson from '@tombatossals/chords-db/lib/guitar.json';\nimport {\n ChordSearchResult,\n ChordsDbInstrument,\n Language,\n ParsedChord,\n} from '../models/chord.model';\n\nconst ERROR_COPY = {\n en: {\n invalid: 'Invalid chord name. Try C, F#, C#m, Bb, Am7, or Dsus4.',\n missingRoot: 'Chord root not found in chords-db.',\n missingType: (suffix: string) =>\n `The type \"${suffix}\" is not available for this chord.`,\n },\n es: {\n invalid: 'Nombre inválido. Prueba C, F#, C#m, Bb, Am7 o Dsus4.',\n missingRoot: 'Raíz no encontrada en chords-db.',\n missingType: (suffix: string) =>\n `El tipo \"${suffix}\" no existe para este acorde en la base actual.`,\n },\n} satisfies Record<\n Language,\n {\n invalid: string;\n missingRoot: string;\n missingType: (suffix: string) => string;\n }\n>;\n\n@Injectable({ providedIn: 'root' })\nexport class ChordService {\n readonly maxBatchSize = 5;\n\n private readonly guitarDb = guitarDbJson as ChordsDbInstrument;\n\n private readonly dbRootMap: Record<string, string> = {\n C: 'C',\n 'C#': 'Csharp',\n Db: 'Csharp',\n D: 'D',\n 'D#': 'Eb',\n Eb: 'Eb',\n E: 'E',\n Fb: 'E',\n 'E#': 'F',\n F: 'F',\n 'F#': 'Fsharp',\n Gb: 'Fsharp',\n G: 'G',\n 'G#': 'Ab',\n Ab: 'Ab',\n A: 'A',\n 'A#': 'Bb',\n Bb: 'Bb',\n B: 'B',\n Cb: 'B',\n 'B#': 'C',\n };\n\n private readonly suffixAliases: Record<string, string> = {\n '': 'major',\n M: 'major',\n maj: 'major',\n major: 'major',\n m: 'minor',\n min: 'minor',\n '-': 'minor',\n minor: 'minor',\n Δ: 'maj7',\n maj7: 'maj7',\n M7: 'maj7',\n m7: 'm7',\n min7: 'm7',\n dim: 'dim',\n diminished: 'dim',\n aug: 'aug',\n augmented: 'aug',\n sus: 'sus4',\n sus2: 'sus2',\n sus4: 'sus4',\n add9: 'add9',\n 4: 'sus4',\n };\n\n search(\n input: string,\n language: Language = 'en',\n ): { results: ChordSearchResult[]; wasLimited: boolean } {\n const tokens = input\n .split(',')\n .map((token) => token.trim())\n .filter(Boolean);\n\n const limitedTokens = tokens.slice(0, this.maxBatchSize);\n\n return {\n results: limitedTokens.map((token, index) =>\n this.searchSingle(token, index, language),\n ),\n wasLimited: tokens.length > this.maxBatchSize,\n };\n }\n\n suffixes(): string[] {\n return this.guitarDb.suffixes;\n }\n\n private searchSingle(\n token: string,\n index: number,\n language: Language,\n ): ChordSearchResult {\n const parsed = this.parseChordName(token);\n const copy = ERROR_COPY[language];\n\n if (!parsed) {\n return {\n id: `${index}-${token}`,\n raw: token,\n displayName: token,\n positions: [],\n error: copy.invalid,\n };\n }\n const chordFamily = this.guitarDb.chords[parsed.dbRoot];\n\n if (!chordFamily) {\n return {\n id: `${index}-${parsed.displayName}`,\n raw: token,\n displayName: parsed.displayName,\n positions: [],\n error: copy.missingRoot,\n };\n }\n\n const chord = chordFamily.find((item) => item.suffix === parsed.suffix);\n\n if (!chord) {\n return {\n id: `${index}-${parsed.displayName}`,\n raw: token,\n displayName: parsed.displayName,\n dbName: `${parsed.dbRoot} ${parsed.suffix}`,\n positions: [],\n error: copy.missingType(parsed.suffix),\n };\n }\n\n return {\n id: `${index}-${parsed.dbRoot}-${parsed.suffix}`,\n raw: token,\n displayName: parsed.displayName,\n dbName: `${chord.key} ${chord.suffix}`,\n chord,\n positions: chord.positions,\n };\n }\n\n private parseChordName(raw: string): ParsedChord | null {\n const cleaned = raw\n .replaceAll('♯', '#')\n .replaceAll('♭', 'b')\n .replace(/\\s+/g, '');\n\n const match = cleaned.match(/^([A-Ga-g])([#b]?)(.*)$/);\n if (!match) return null;\n\n const letter = match[1].toUpperCase();\n const accidental = match[2] ?? '';\n const suffixRaw = match[3] ?? '';\n const root = `${letter}${accidental}`;\n const dbRoot = this.dbRootMap[root];\n\n if (!dbRoot) return null;\n\n const suffix = this.normalizeSuffix(suffixRaw);\n if (!suffix) return null;\n\n return {\n raw,\n root,\n dbRoot,\n displayName: `${root}${this.displaySuffix(suffix)}`,\n suffix,\n };\n }\n\n private normalizeSuffix(rawSuffix: string): string | null {\n const trimmed = rawSuffix.trim();\n\n if (this.suffixAliases[trimmed] !== undefined) {\n return this.suffixAliases[trimmed];\n }\n\n const lower = trimmed.toLowerCase();\n if (this.suffixAliases[lower] !== undefined) {\n return this.suffixAliases[lower];\n }\n\n if (this.guitarDb.suffixes.includes(trimmed)) {\n return trimmed;\n }\n\n if (this.guitarDb.suffixes.includes(lower)) {\n return lower;\n }\n\n return null;\n }\n\n private displaySuffix(suffix: string): string {\n if (suffix === 'major') return '';\n if (suffix === 'minor') return 'm';\n return suffix;\n }\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n computed,\n effect,\n input,\n signal,\n viewChild,\n} from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { ChordDiagram } from './components/chord-diagram/chord-diagram';\nimport {\n ChordSearchResult,\n ChordsDbPosition,\n Language,\n} from './models/chord.model';\nimport { ChordService } from './services/chord.service';\n\nconst COPY = {\n en: {\n eyebrow: 'Angular · SVG · chords-db',\n title: 'Chord Finder',\n descriptionStart: 'Enter up to',\n descriptionLimit: '5 chords',\n descriptionEnd: 'separated by commas. Sharps and flats are supported:',\n inputLabel: 'Chords',\n exportPng: 'Export PNG',\n limited: 'Only the first 5 chords are rendered.',\n availableTypes: 'Available chord types:',\n resultsLabel: 'Chord results',\n chord: 'Chord',\n normalizedAs: 'Normalized as',\n position: 'Position',\n of: 'of',\n openSource: 'Open source project:',\n contributions: 'Contributions are welcome.',\n assistance:\n 'Built with AI assistance under supervision from the repository owner.',\n },\n es: {\n eyebrow: 'Angular · SVG · chords-db',\n title: 'Buscador de acordes',\n descriptionStart: 'Escribe hasta',\n descriptionLimit: '5 acordes',\n descriptionEnd: 'separados por coma. Soporta sostenidos y bemoles:',\n inputLabel: 'Acordes',\n exportPng: 'Exportar PNG',\n limited: 'Solo se renderizan los primeros 5 acordes.',\n availableTypes: 'Tipos de acorde disponibles:',\n resultsLabel: 'Resultados de acordes',\n chord: 'Acorde',\n normalizedAs: 'Normalizado como',\n position: 'Posición',\n of: 'de',\n openSource: 'Proyecto de código abierto:',\n contributions: 'Las contribuciones son bienvenidas.',\n assistance:\n 'Creado con asistencia de IA bajo la supervisión del propietario del repositorio.',\n },\n} as const;\n\n@Component({\n selector: 'the-chords-chord-finder',\n standalone: true,\n imports: [FormsModule, ChordDiagram],\n templateUrl: './chord-finder.html',\n changeDetection: ChangeDetectionStrategy.Eager,\n styleUrl: './chord-finder.scss',\n host: { '[attr.lang]': 'language()' },\n})\nexport class ChordFinderComponent {\n readonly language = input<Language>('en');\n readonly text = computed(() => COPY[this.language()]);\n query = 'C';\n results = signal<ChordSearchResult[]>([]);\n wasLimited = signal(false);\n selectedPositionIndex: Record<string, number> = {};\n supportedSuffixes = computed(() =>\n this.chordService.suffixes().slice(0, 18).join(', '),\n );\n\n resultsRow = viewChild.required<ElementRef<HTMLElement>>('resultsRow');\n\n constructor(private readonly chordService: ChordService) {\n effect(() => this.runSearch(this.language()));\n }\n\n async exportPng(): Promise<void> {\n const svgs = Array.from(\n this.resultsRow().nativeElement.querySelectorAll<SVGSVGElement>(\n 'svg.chord-svg',\n ),\n );\n if (!svgs.length) return;\n\n const padding = 32;\n const gap = 24;\n const width = 240;\n const height = 330;\n const scale = 2;\n\n const canvas = document.createElement('canvas');\n canvas.width =\n (padding * 2 + svgs.length * width + (svgs.length - 1) * gap) * scale;\n canvas.height = (padding * 2 + height) * scale;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n ctx.scale(scale, scale);\n ctx.fillStyle = '#ffffff';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n\n for (const [index, svg] of svgs.entries()) {\n const clone = svg.cloneNode(true) as SVGSVGElement;\n\n clone.insertAdjacentHTML(\n 'afterbegin',\n `<style>\n .chord-svg{font-family:Roboto,Arial,sans-serif;font-weight:400}\n .card-bg{fill:#fff}.title{font-size:42px;font-weight:400;fill:#000}\n .grid line{stroke:#000;stroke-width:2.6;stroke-linecap:square}\n .grid line.nut{stroke-width:7}.barres rect,.dots circle{fill:#000}\n .barres text,.dots text{fill:#fff;font-size:16px;font-weight:400;dominant-baseline:central;alignment-baseline:middle}\n .markers text{fill:#000;font-size:30px;font-weight:400}\n .fret-number{fill:#000;font-size:42px;font-weight:400}\n .string-labels text{fill:#000;font-size:18px;font-weight:400}\n </style>`,\n );\n\n const url = URL.createObjectURL(\n new Blob([new XMLSerializer().serializeToString(clone)], {\n type: 'image/svg+xml',\n }),\n );\n const image = new Image();\n image.src = url;\n await image.decode();\n\n ctx.drawImage(\n image,\n padding + index * (width + gap),\n padding,\n width,\n height,\n );\n URL.revokeObjectURL(url);\n }\n\n const link = document.createElement('a');\n link.download = 'chords.png';\n link.href = canvas.toDataURL('image/png');\n link.click();\n }\n\n runSearch(language: Language = this.language()): void {\n const { results, wasLimited } = this.chordService.search(\n this.query,\n language,\n );\n this.results.set(results);\n this.wasLimited.set(wasLimited);\n\n for (const result of results) {\n if (this.selectedPositionIndex[result.id] === undefined) {\n this.selectedPositionIndex[result.id] = 0;\n }\n }\n }\n\n selectPosition(resultId: string, value: string | number): void {\n this.selectedPositionIndex[resultId] = Number(value);\n }\n\n selectedIndex(resultId: string): number {\n return this.selectedPositionIndex[resultId] ?? 0;\n }\n\n selectedPosition(result: ChordSearchResult): ChordsDbPosition | null {\n if (!result.positions.length) return null;\n return (\n result.positions[this.selectedPositionIndex[result.id] ?? 0] ??\n result.positions[0]\n );\n }\n\n trackByResultId(_: number, result: ChordSearchResult): string {\n return result.id;\n }\n}\n","<main class=\"page-shell\">\n <section class=\"hero-card\">\n <p class=\"eyebrow\">{{ text().eyebrow }}</p>\n <h1>{{ text().title }}</h1>\n\n <p class=\"description\">\n {{ text().descriptionStart }}\n <strong>{{ text().descriptionLimit }}</strong>\n {{ text().descriptionEnd }}\n <strong>C, F#, C#m, Bb</strong>.\n </p>\n\n <div class=\"search-row\">\n <label for=\"chordInput\">{{ text().inputLabel }}</label>\n <input\n id=\"chordInput\"\n type=\"text\"\n [(ngModel)]=\"query\"\n (input)=\"runSearch()\"\n placeholder=\"C, F#, C#m, Bb\"\n autocomplete=\"off\"\n />\n <button\n type=\"button\"\n (click)=\"exportPng()\"\n [disabled]=\"!results().length\"\n >\n {{ text().exportPng }}\n </button>\n </div>\n\n @if (wasLimited()) {\n <p class=\"warning\">{{ text().limited }}</p>\n }\n\n <p class=\"hint\">{{ text().availableTypes }} {{ supportedSuffixes() }}...</p>\n </section>\n\n <section\n #resultsRow\n class=\"results-row\"\n [attr.aria-label]=\"text().resultsLabel\"\n >\n @for (result of results(); track trackByResultId($index, result)) {\n <article class=\"result-card\" [class.has-error]=\"result.error\">\n <header class=\"result-header\">\n <div>\n <p class=\"result-label\">{{ text().chord }}</p>\n <h2>{{ result.displayName }}</h2>\n @if (result.dbName && result.raw !== result.displayName) {\n <p class=\"db-name\">{{ text().normalizedAs }} {{ result.dbName }}</p>\n }\n </div>\n\n @if (result.positions.length > 1) {\n <label class=\"select-label\">\n {{ text().position }}\n <select\n [ngModel]=\"selectedPositionIndex[result.id]\"\n (ngModelChange)=\"selectPosition(result.id, $event)\"\n >\n @for (position of result.positions; track $index) {\n <option [value]=\"$index\">\n {{ $index + 1 }} {{ text().of }} {{ result.positions.length }}\n </option>\n }\n </select>\n </label>\n }\n </header>\n\n @if (result.error) {\n <p class=\"error-text\">{{ result.error }}</p>\n } @else if (selectedPosition(result); as position) {\n <div class=\"svg-wrap\">\n <app-chord-diagram\n [title]=\"result.displayName\"\n [position]=\"position\"\n [positionIndex]=\"selectedIndex(result.id)\"\n />\n </div>\n }\n </article>\n }\n </section>\n\n <footer class=\"app-footer\">\n <p>\n {{ text().openSource }}\n <a\n href=\"https://github.com/elparaquecosadeque/chord-generator\"\n target=\"_blank\"\n rel=\"noreferrer\"\n >\n elparaquecosadeque/chord-generator </a\n >. {{ text().contributions }}\n </p>\n <p>{{ text().assistance }}</p>\n </footer>\n</main>\n","/*\r\n * Public API Surface of chord-finder\r\n */\r\n\r\nexport * from './lib/chord-finder';\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["i1.ChordService"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gblp/chord-finder",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -1,6 +1,7 @@
1
1
  import * as _angular_core from '@angular/core';
2
2
  import { ElementRef } from '@angular/core';
3
3
 
4
+ type Language = 'en' | 'es';
4
5
  interface ChordsDbPosition {
5
6
  frets: number[];
6
7
  fingers?: number[];
@@ -29,7 +30,7 @@ declare class ChordService {
29
30
  private readonly guitarDb;
30
31
  private readonly dbRootMap;
31
32
  private readonly suffixAliases;
32
- search(input: string): {
33
+ search(input: string, language?: Language): {
33
34
  results: ChordSearchResult[];
34
35
  wasLimited: boolean;
35
36
  };
@@ -44,6 +45,44 @@ declare class ChordService {
44
45
 
45
46
  declare class ChordFinderComponent {
46
47
  private readonly chordService;
48
+ readonly language: _angular_core.InputSignal<Language>;
49
+ readonly text: _angular_core.Signal<{
50
+ readonly eyebrow: "Angular · SVG · chords-db";
51
+ readonly title: "Chord Finder";
52
+ readonly descriptionStart: "Enter up to";
53
+ readonly descriptionLimit: "5 chords";
54
+ readonly descriptionEnd: "separated by commas. Sharps and flats are supported:";
55
+ readonly inputLabel: "Chords";
56
+ readonly exportPng: "Export PNG";
57
+ readonly limited: "Only the first 5 chords are rendered.";
58
+ readonly availableTypes: "Available chord types:";
59
+ readonly resultsLabel: "Chord results";
60
+ readonly chord: "Chord";
61
+ readonly normalizedAs: "Normalized as";
62
+ readonly position: "Position";
63
+ readonly of: "of";
64
+ readonly openSource: "Open source project:";
65
+ readonly contributions: "Contributions are welcome.";
66
+ readonly assistance: "Built with AI assistance under supervision from the repository owner.";
67
+ } | {
68
+ readonly eyebrow: "Angular · SVG · chords-db";
69
+ readonly title: "Buscador de acordes";
70
+ readonly descriptionStart: "Escribe hasta";
71
+ readonly descriptionLimit: "5 acordes";
72
+ readonly descriptionEnd: "separados por coma. Soporta sostenidos y bemoles:";
73
+ readonly inputLabel: "Acordes";
74
+ readonly exportPng: "Exportar PNG";
75
+ readonly limited: "Solo se renderizan los primeros 5 acordes.";
76
+ readonly availableTypes: "Tipos de acorde disponibles:";
77
+ readonly resultsLabel: "Resultados de acordes";
78
+ readonly chord: "Acorde";
79
+ readonly normalizedAs: "Normalizado como";
80
+ readonly position: "Posición";
81
+ readonly of: "de";
82
+ readonly openSource: "Proyecto de código abierto:";
83
+ readonly contributions: "Las contribuciones son bienvenidas.";
84
+ readonly assistance: "Creado con asistencia de IA bajo la supervisión del propietario del repositorio.";
85
+ }>;
47
86
  query: string;
48
87
  results: _angular_core.WritableSignal<ChordSearchResult[]>;
49
88
  wasLimited: _angular_core.WritableSignal<boolean>;
@@ -52,13 +91,13 @@ declare class ChordFinderComponent {
52
91
  resultsRow: _angular_core.Signal<ElementRef<HTMLElement>>;
53
92
  constructor(chordService: ChordService);
54
93
  exportPng(): Promise<void>;
55
- runSearch(): void;
94
+ runSearch(language?: Language): void;
56
95
  selectPosition(resultId: string, value: string | number): void;
57
96
  selectedIndex(resultId: string): number;
58
97
  selectedPosition(result: ChordSearchResult): ChordsDbPosition | null;
59
98
  trackByResultId(_: number, result: ChordSearchResult): string;
60
99
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<ChordFinderComponent, never>;
61
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<ChordFinderComponent, "the-chords-chord-finder", never, {}, {}, never, never, true, never>;
100
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<ChordFinderComponent, "the-chords-chord-finder", never, { "language": { "alias": "language"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
62
101
  }
63
102
 
64
103
  export { ChordFinderComponent };