@hosanna/chordpro 1.1.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.
@@ -0,0 +1,789 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/editor/index.ts
31
+ var editor_exports = {};
32
+ __export(editor_exports, {
33
+ ChordFinder: () => ChordFinder,
34
+ Editor: () => Editor,
35
+ default: () => Editor_default,
36
+ preloadEditor: () => preloadEditor,
37
+ registerChordproMode: () => registerChordproMode,
38
+ registerChordproSnippets: () => registerChordproSnippets
39
+ });
40
+ module.exports = __toCommonJS(editor_exports);
41
+
42
+ // src/editor/Editor.tsx
43
+ var import_react = __toESM(require("react"));
44
+
45
+ // src/editor/ChordFinder.ts
46
+ var ChordFinder = class {
47
+ static {
48
+ this.CHORD_REGEX = /\[(.*?)\]/gi;
49
+ }
50
+ /**
51
+ * Extracts chords from text and returns them formatted for Ace Autocomplete
52
+ */
53
+ static getChords(text) {
54
+ const matches = String(text).match(this.CHORD_REGEX);
55
+ if (!matches || matches.length === 0) {
56
+ return [];
57
+ }
58
+ const chordCounts = matches.reduce(
59
+ (acc, chord) => {
60
+ acc[chord] = (acc[chord] || 0) + 1;
61
+ return acc;
62
+ },
63
+ {}
64
+ );
65
+ return Object.entries(chordCounts).map(([chord, count]) => ({
66
+ value: chord,
67
+ meta: `${count} occurrence${count > 1 ? "s" : ""}`,
68
+ score: count
69
+ // Ace uses score to rank autocomplete suggestions
70
+ })).sort((a, b) => b.score - a.score);
71
+ }
72
+ };
73
+
74
+ // src/editor/mode-chordpro.ts
75
+ async function registerChordproMode(aceInstance) {
76
+ let ace = aceInstance;
77
+ if (!ace) {
78
+ try {
79
+ const mod = await import("ace-builds");
80
+ ace = mod.default || mod;
81
+ } catch {
82
+ return;
83
+ }
84
+ }
85
+ if (!ace || typeof ace.define !== "function" || ace._chordproModeRegistered) {
86
+ return;
87
+ }
88
+ ace.define(
89
+ "ace/mode/chordpro_highlight_rules",
90
+ [
91
+ "require",
92
+ "exports",
93
+ "module",
94
+ "ace/lib/oop",
95
+ "ace/mode/text_highlight_rules"
96
+ ],
97
+ (require2, exports2) => {
98
+ const oop = require2("ace/lib/oop");
99
+ const TextHighlightRules = require2("ace/mode/text_highlight_rules").TextHighlightRules;
100
+ const ChordproHighlightRules = function() {
101
+ const rStart = "(\\{\\s*)";
102
+ const rEnd = "(\\s*\\})";
103
+ const rSep = "(\\s*:\\s*)";
104
+ this.$rules = {
105
+ start: [
106
+ // Comments
107
+ { token: "comment.line.number-sign", regex: "^#.*$" },
108
+ // Tab Block Start -> moves to tabBlock state
109
+ {
110
+ token: ["punctuation.tag", "markup.bold", "punctuation.tag"],
111
+ regex: rStart + "(start_of_tab|sot)" + rEnd,
112
+ caseInsensitive: true,
113
+ next: "tabBlock"
114
+ },
115
+ // Grid Block Start -> moves to gridBlock state
116
+ {
117
+ token: ["punctuation.tag", "markup.bold", "punctuation.tag"],
118
+ regex: rStart + "(start_of_grid|sog)" + rEnd,
119
+ caseInsensitive: true,
120
+ next: "gridBlock"
121
+ },
122
+ // Generic Block Starts (Chorus, Verse, Bridge, Custom) with optional labels
123
+ {
124
+ token: [
125
+ "punctuation.tag",
126
+ "markup.bold",
127
+ "punctuation.separator",
128
+ "string",
129
+ "punctuation.tag"
130
+ ],
131
+ regex: rStart + "(start_of_[a-z_]+|so[a-z])" + rSep + "(.*?)" + rEnd,
132
+ caseInsensitive: true
133
+ },
134
+ {
135
+ token: ["punctuation.tag", "markup.bold", "punctuation.tag"],
136
+ regex: rStart + "(start_of_[a-z_]+|so[a-z])" + rEnd,
137
+ caseInsensitive: true
138
+ },
139
+ // Generic Block Ends
140
+ {
141
+ token: ["punctuation.tag", "markup.bold", "punctuation.tag"],
142
+ regex: rStart + "(end_of_[a-z_]+|eo[a-z])" + rEnd,
143
+ caseInsensitive: true
144
+ },
145
+ // Formatting & Styling Directives (e.g. textfont, chordsize)
146
+ {
147
+ token: [
148
+ "punctuation.tag",
149
+ "support.type",
150
+ "punctuation.separator",
151
+ "constant.numeric",
152
+ "punctuation.tag"
153
+ ],
154
+ regex: rStart + "(textfont|textsize|chordfont|chordsize|tabfont|tabsize|gridfont|gridsize)" + rSep + "(.*?)" + rEnd,
155
+ caseInsensitive: true
156
+ },
157
+ // Meta Directives (e.g. title, key, tempo)
158
+ {
159
+ token: [
160
+ "punctuation.tag",
161
+ "keyword.control",
162
+ "punctuation.separator",
163
+ "string",
164
+ "punctuation.tag"
165
+ ],
166
+ regex: rStart + "(title|t|subtitle|st|artist|a|composer|lyricist|ccli|translator|youtube|chorus|copyright|album|year|key|k|time|tempo|duration|capo|meta|c|comment|chord|define|song_number|x_[a-zA-Z0-9_]+)" + rSep + "(.*?)" + rEnd,
167
+ caseInsensitive: true
168
+ },
169
+ // Standalone Directives (e.g. column_break, new_page)
170
+ {
171
+ token: ["punctuation.tag", "keyword.operator", "punctuation.tag"],
172
+ regex: rStart + "(column_break|cb|new_page|np|new_song|ns|chorus)" + rEnd,
173
+ caseInsensitive: true
174
+ },
175
+ // Chord Section barlines (||, |:, :|, |)
176
+ {
177
+ token: "constant.character.barline",
178
+ regex: "\\|\\||:\\||\\|:|\\|"
179
+ },
180
+ // Inline Annotations e.g. [* Bass fill]
181
+ {
182
+ token: ["punctuation.tag", "comment.line", "punctuation.tag"],
183
+ regex: "(\\[\\s*\\*)(.*?)(\\])"
184
+ },
185
+ // Chord with timing annotation e.g. [Em@2x] or [C@0.5x]
186
+ {
187
+ token: [
188
+ "punctuation.tag",
189
+ "constant.language.bold",
190
+ "keyword.operator.timing",
191
+ "punctuation.tag"
192
+ ],
193
+ regex: "(\\[)([^\\]@]+)(@[0-9]*\\.?[0-9]+x)(\\])"
194
+ },
195
+ // Chords e.g. [C#maj7/F]
196
+ {
197
+ token: [
198
+ "punctuation.tag",
199
+ "constant.language.bold",
200
+ "punctuation.tag"
201
+ ],
202
+ regex: "(\\[)([^\\]]+)(\\])"
203
+ },
204
+ // Invalid/Unknown braces catch-all
205
+ {
206
+ token: ["punctuation.tag", "invalid", "punctuation.tag"],
207
+ regex: rStart + "(.+?)" + rEnd
208
+ }
209
+ ],
210
+ // Advanced State for Tablature blocks
211
+ tabBlock: [
212
+ {
213
+ token: ["punctuation.tag", "markup.bold", "punctuation.tag"],
214
+ regex: rStart + "(end_of_tab|eot)" + rEnd,
215
+ caseInsensitive: true,
216
+ next: "start"
217
+ },
218
+ { token: "comment.line", regex: "-+" },
219
+ // Tab lines
220
+ { token: "constant.character", regex: "\\|+" },
221
+ // Measure bars
222
+ { token: "string.regexp", regex: "^[a-gA-G][b#]?\\s*\\|" },
223
+ // Tuning strings at start of line
224
+ { token: "constant.numeric", regex: "\\b[0-9]+\\b" },
225
+ // Fret numbers
226
+ { token: "support.function", regex: "[hpsbrv~t]" },
227
+ // Articulations (hammer-on, pull-off, bend)
228
+ { defaultToken: "comment" }
229
+ // Fallback to comment color for tab lines
230
+ ],
231
+ // Advanced State for Grid blocks (Jazz grids / Chord progression boxes)
232
+ gridBlock: [
233
+ {
234
+ token: ["punctuation.tag", "markup.bold", "punctuation.tag"],
235
+ regex: rStart + "(end_of_grid|eog)" + rEnd,
236
+ caseInsensitive: true,
237
+ next: "start"
238
+ },
239
+ { token: "constant.character", regex: "\\|\\||\\|\\.|\\.\\||\\|" },
240
+ // Grid barlines
241
+ { token: "keyword.operator", regex: "%" },
242
+ // Grid repeat markers
243
+ {
244
+ token: "constant.language.bold",
245
+ regex: "[A-G][b#]?(?:m|maj|dim|aug|sus|[0-9])*(?:\\/[A-G][b#]?)?"
246
+ },
247
+ // Grid Chords (raw text)
248
+ { defaultToken: "text" }
249
+ ]
250
+ };
251
+ };
252
+ oop.inherits(ChordproHighlightRules, TextHighlightRules);
253
+ exports2.ChordproHighlightRules = ChordproHighlightRules;
254
+ }
255
+ );
256
+ ace.define(
257
+ "ace/mode/folding/chordpro",
258
+ [
259
+ "require",
260
+ "exports",
261
+ "module",
262
+ "ace/lib/oop",
263
+ "ace/range",
264
+ "ace/mode/folding/fold_mode"
265
+ ],
266
+ (require2, exports2) => {
267
+ const oop = require2("ace/lib/oop");
268
+ const Range = require2("ace/range").Range;
269
+ const BaseFoldMode = require2("ace/mode/folding/fold_mode").FoldMode;
270
+ const FoldMode = exports2.FoldMode = function() {
271
+ };
272
+ oop.inherits(FoldMode, BaseFoldMode);
273
+ (function() {
274
+ this.foldingStartMarker = /\{\s*(so(?<short>[a-z_]+)|start_of_(?<long>[a-z_]+))(:.*?)?\s*\}/i;
275
+ this.foldingStopMarker = /\{\s*(eo(?<short>[a-z_]+)|end_of_(?<long>[a-z_]+))\s*\}/i;
276
+ const mapBlockName = (name) => {
277
+ const map = {
278
+ c: "chorus",
279
+ v: "verse",
280
+ b: "bridge",
281
+ t: "tab",
282
+ g: "grid"
283
+ };
284
+ return map[name.toLowerCase()] || name.toLowerCase();
285
+ };
286
+ this.getFoldWidgetRange = function(session, _, row) {
287
+ const line = session.getLine(row);
288
+ const match = line.match(this.foldingStartMarker);
289
+ if (match && match.groups) {
290
+ const rawName = match.groups["short"] || match.groups["long"];
291
+ if (!rawName) return;
292
+ return this.getRegionBlock(
293
+ session,
294
+ line,
295
+ row,
296
+ mapBlockName(rawName)
297
+ );
298
+ }
299
+ };
300
+ this.getRegionBlock = function(session, line, row, normalizedStartName) {
301
+ const startColumn = line.search(/\s*$/);
302
+ const maxRow = session.getLength();
303
+ const startRow = row;
304
+ let hasMatch = false;
305
+ while (++row < maxRow) {
306
+ line = session.getLine(row);
307
+ const m = this.foldingStopMarker.exec(line);
308
+ if (m && m.groups) {
309
+ const rawEndName = m.groups["short"] || m.groups["long"];
310
+ if (rawEndName && mapBlockName(rawEndName) === normalizedStartName) {
311
+ hasMatch = true;
312
+ break;
313
+ }
314
+ }
315
+ }
316
+ if (hasMatch)
317
+ return new Range(startRow, startColumn, row, line.length);
318
+ };
319
+ }).call(FoldMode.prototype);
320
+ }
321
+ );
322
+ ace.define(
323
+ "ace/mode/chordpro",
324
+ [
325
+ "require",
326
+ "exports",
327
+ "module",
328
+ "ace/lib/oop",
329
+ "ace/mode/text",
330
+ "ace/mode/chordpro_highlight_rules",
331
+ "ace/mode/folding/chordpro"
332
+ ],
333
+ (require2, exports2) => {
334
+ const oop = require2("ace/lib/oop");
335
+ const TextMode = require2("ace/mode/text").Mode;
336
+ const ChordproHighlightRules = require2("ace/mode/chordpro_highlight_rules").ChordproHighlightRules;
337
+ const FoldMode = require2("ace/mode/folding/chordpro").FoldMode;
338
+ const Mode = function() {
339
+ this.HighlightRules = ChordproHighlightRules;
340
+ this.foldingRules = new FoldMode();
341
+ };
342
+ oop.inherits(Mode, TextMode);
343
+ (function() {
344
+ this.$id = "ace/mode/chordpro";
345
+ this.snippetFileId = "ace/snippets/chordpro";
346
+ }).call(Mode.prototype);
347
+ exports2.Mode = Mode;
348
+ }
349
+ );
350
+ ace._chordproModeRegistered = true;
351
+ }
352
+
353
+ // src/editor/snippets-chordpro.ts
354
+ async function registerChordproSnippets(aceInstance) {
355
+ let ace = aceInstance;
356
+ if (!ace) {
357
+ try {
358
+ const mod = await import("ace-builds");
359
+ ace = mod.default || mod;
360
+ } catch {
361
+ return;
362
+ }
363
+ }
364
+ if (!ace || typeof ace.define !== "function" || ace._chordproSnippetsRegistered) {
365
+ return;
366
+ }
367
+ ace.define(
368
+ "ace/snippets/chordpro",
369
+ ["require", "exports", "module"],
370
+ (_, exports2) => {
371
+ exports2.snippetText = [
372
+ // album tag
373
+ "snippet album",
374
+ " {album: ${1:value}}",
375
+ "snippet youtube",
376
+ " {youtube: ${1:url}}",
377
+ "snippet number",
378
+ " {song_number: ${1:number}}",
379
+ "snippet cc",
380
+ " {chorus}",
381
+ // arranger tag
382
+ "snippet arranger",
383
+ " {arranger: ${1:value}}",
384
+ // artist tag
385
+ "snippet a",
386
+ " {artist: ${1:value}}",
387
+ "snippet artist",
388
+ " {artist: ${1:value}}",
389
+ // capo tag
390
+ "snippet capo",
391
+ " {capo: ${1:5}}",
392
+ // composer tag
393
+ "snippet composer",
394
+ " {composer: ${1:value}}",
395
+ // copyright tag
396
+ "snippet copyright",
397
+ " {copyright: ${1:value}}",
398
+ // duration tag
399
+ "snippet duration",
400
+ " {duration: ${1:4}:${2:00}}",
401
+ // key tag
402
+ "snippet k",
403
+ " {key: ${1:Am}}",
404
+ "snippet key",
405
+ " {key: ${1:Am}}",
406
+ // lyricist tag
407
+ "snippet lyricist",
408
+ " {lyricist: ${1:value}}",
409
+ // tempo tag
410
+ "snippet tempo",
411
+ " {tempo: ${1:120}}",
412
+ // time tag
413
+ "snippet time",
414
+ " {time: ${1:4}/${2:4}}",
415
+ // title tag
416
+ "snippet t",
417
+ " {title: ${1:value}}",
418
+ "snippet title",
419
+ " {title: ${1:value}}",
420
+ // subtitle tag
421
+ "snippet st",
422
+ " {subtitle: ${1:value}}",
423
+ "snippet subtitle",
424
+ " {subtitle: ${1:value}}",
425
+ // year tag
426
+ "snippet year",
427
+ " {year: ${1:2020}}",
428
+ // meta tag
429
+ "snippet meta",
430
+ " {meta: ${1:label} ${2:value}}",
431
+ // comment tag
432
+ "snippet c",
433
+ " {comment: ${1:value}}",
434
+ "snippet comment",
435
+ " {comment: ${1:value}}",
436
+ // chorus block
437
+ "snippet soc",
438
+ " {start_of_chorus}",
439
+ "snippet eoc",
440
+ " {end_of_chorus}",
441
+ "snippet chorus",
442
+ " {start_of_chorus: ${1:Refr\xE3o}}",
443
+ " ${2:lyrics}",
444
+ " {end_of_chorus}",
445
+ // verse block
446
+ "snippet sov",
447
+ " {start_of_verse}",
448
+ "snippet eov",
449
+ " {end_of_verse}",
450
+ "snippet verse",
451
+ " {start_of_verse: ${1:Verso} ${2:1}}",
452
+ " ${3:lyrics}",
453
+ " {end_of_verse}",
454
+ // bridge block
455
+ "snippet sob",
456
+ " {start_of_bridge}",
457
+ "snippet eob",
458
+ " {end_of_bridge}",
459
+ "snippet bridge",
460
+ " {start_of_bridge: ${1:Ponte}}",
461
+ " ${2:lyrics}",
462
+ " {end_of_bridge}",
463
+ // tabs block
464
+ "snippet sot",
465
+ " {start_of_tab}",
466
+ "snippet eot",
467
+ " {end_of_tab}",
468
+ "snippet tab",
469
+ " {start_of_tab}",
470
+ " e|-${1:-}--------------------------------|",
471
+ " B|----------------------------------|",
472
+ " G|----------------------------------|",
473
+ " D|----------------------------------|",
474
+ " A|----------------------------------|",
475
+ " E|----------------------------------|",
476
+ " {end_of_tab}",
477
+ // define tag
478
+ "snippet d",
479
+ " {define: ${1:Am} base-fret ${2:1} frets ${3:0 0 0 0 0 0} fingers ${4:0 0 0 0 0 0}}",
480
+ "snippet define",
481
+ " {define: ${1:Am} base-fret ${2:1} frets ${3:0 0 0 0 0 0} fingers ${4:0 0 0 0 0 0}}",
482
+ // single-liners
483
+ "snippet cb",
484
+ " {column_break}",
485
+ "snippet column",
486
+ " {column_break}",
487
+ // that's all folks!
488
+ // chord usage
489
+ "snippet [",
490
+ " [${1:Am}]",
491
+ // chord section / grid notation
492
+ "snippet ||",
493
+ " ||[${1:Am}]|[${2:C}]|[${3:G}]|[${4:F}]||",
494
+ "snippet grid",
495
+ " {start_of_grid}",
496
+ " ||[${1:Em}]|[${2:C}]|[${3:D}]||",
497
+ " {end_of_grid}",
498
+ "snippet !",
499
+ " {title: ${1:value}}",
500
+ " {artist: ${2:value}}",
501
+ " {duration: ${3:4:00}}",
502
+ " {key: ${4:C}}",
503
+ " ",
504
+ " ${5:lyrics}",
505
+ " {start_of_chorus}",
506
+ " ${6:lyrics}",
507
+ " {end_of_chorus}"
508
+ ].join("\n");
509
+ exports2.scope = "chordpro";
510
+ }
511
+ );
512
+ ace._chordproSnippetsRegistered = true;
513
+ }
514
+
515
+ // src/editor/Editor.tsx
516
+ var import_jsx_runtime = require("react/jsx-runtime");
517
+ var aceLoaderPromise = null;
518
+ function preloadEditor() {
519
+ if (!aceLoaderPromise) {
520
+ aceLoaderPromise = (async () => {
521
+ try {
522
+ const aceModule = await import("ace-builds");
523
+ const ace = aceModule?.default || aceModule;
524
+ if (typeof window !== "undefined" && ace) {
525
+ window.ace = ace;
526
+ }
527
+ const [reactAceModule] = await Promise.all([
528
+ import("react-ace"),
529
+ import("ace-builds/src-noconflict/ext-language_tools"),
530
+ import("ace-builds/src-noconflict/theme-dracula"),
531
+ import("ace-builds/src-noconflict/theme-github"),
532
+ import("ace-builds/src-noconflict/theme-monokai"),
533
+ import("ace-builds/src-noconflict/theme-solarized_dark"),
534
+ import("ace-builds/src-noconflict/theme-solarized_light"),
535
+ import("ace-builds/src-noconflict/theme-textmate"),
536
+ import("ace-builds/src-noconflict/theme-tomorrow"),
537
+ import("ace-builds/src-noconflict/theme-tomorrow_night")
538
+ ]);
539
+ const AceEditor = reactAceModule?.default || reactAceModule;
540
+ if (ace) {
541
+ await registerChordproMode(ace);
542
+ await registerChordproSnippets(ace);
543
+ if (typeof ace.require === "function") {
544
+ try {
545
+ const langTools = ace.require("ace/ext/language_tools");
546
+ if (langTools && typeof langTools.addCompleter === "function" && typeof window !== "undefined" && !window._chordproCompleterRegistered) {
547
+ const chordCompleter = {
548
+ getCompletions: (editor, _session, _pos, _prefix, callback) => {
549
+ if (!editor || typeof editor.getValue !== "function") {
550
+ callback(null, []);
551
+ return;
552
+ }
553
+ const text = editor.getValue();
554
+ const chords = ChordFinder.getChords(text);
555
+ callback(null, chords);
556
+ }
557
+ };
558
+ langTools.addCompleter(chordCompleter);
559
+ window._chordproCompleterRegistered = true;
560
+ }
561
+ } catch {
562
+ }
563
+ }
564
+ }
565
+ if (!AceEditor) {
566
+ throw new Error("Failed to resolve AceEditor component");
567
+ }
568
+ return AceEditor;
569
+ } catch (error) {
570
+ console.error("Failed to load Ace editor:", error);
571
+ aceLoaderPromise = null;
572
+ const ErrorFallback = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-full h-full flex items-center justify-center p-4 text-center text-sm text-red-500 bg-red-50/50 dark:bg-red-950/20 rounded-lg border border-red-200 dark:border-red-900/30", children: "Failed to load code editor. Please ensure 'ace-builds' and 'react-ace' are installed." });
573
+ return ErrorFallback;
574
+ }
575
+ })();
576
+ }
577
+ return aceLoaderPromise;
578
+ }
579
+ var LazyAce = import_react.default.lazy(async () => {
580
+ const Component = await preloadEditor();
581
+ return { default: Component };
582
+ });
583
+ var SECTION_LABELS = {
584
+ verse: {
585
+ start: "start_of_verse",
586
+ end: "end_of_verse",
587
+ defaultLabel: "Verso"
588
+ },
589
+ chorus: {
590
+ start: "start_of_chorus",
591
+ end: "end_of_chorus",
592
+ defaultLabel: "Refr\xE3o"
593
+ },
594
+ bridge: {
595
+ start: "start_of_bridge",
596
+ end: "end_of_bridge",
597
+ defaultLabel: "Ponte"
598
+ }
599
+ };
600
+ function wrapSelectionInSection(editor, sectionType) {
601
+ if (!editor || !editor.getSelection || !editor.session) return;
602
+ if (typeof editor.session.getTextRange !== "function" || typeof editor.session.replace !== "function") {
603
+ return;
604
+ }
605
+ const selection = editor.getSelection();
606
+ if (!selection || typeof selection.getRange !== "function") return;
607
+ const range = selection.getRange();
608
+ if (!range) return;
609
+ const selectedText = editor.session.getTextRange(range);
610
+ if (!selectedText || !selectedText.trim()) return;
611
+ const info = SECTION_LABELS[sectionType];
612
+ const wrapped = `{${info.start}: ${info.defaultLabel}}
613
+ ${selectedText}
614
+ {${info.end}}`;
615
+ editor.session.replace(range, wrapped);
616
+ }
617
+ var MENU_ITEMS = [
618
+ { type: "verse", label: "Envolver em Verso", shortcut: "Alt+V" },
619
+ { type: "chorus", label: "Envolver em Refr\xE3o", shortcut: "Alt+R" },
620
+ { type: "bridge", label: "Envolver em Ponte", shortcut: "Alt+B" }
621
+ ];
622
+ function EditorContextMenu({
623
+ state,
624
+ onAction,
625
+ onClose
626
+ }) {
627
+ const menuRef = (0, import_react.useRef)(null);
628
+ (0, import_react.useEffect)(() => {
629
+ const handleClickOutside = (e) => {
630
+ if (menuRef.current && !menuRef.current.contains(e.target)) {
631
+ onClose();
632
+ }
633
+ };
634
+ const handleEscape = (e) => {
635
+ if (e.key === "Escape") onClose();
636
+ };
637
+ if (state.visible) {
638
+ document.addEventListener("mousedown", handleClickOutside);
639
+ document.addEventListener("keydown", handleEscape);
640
+ }
641
+ return () => {
642
+ document.removeEventListener("mousedown", handleClickOutside);
643
+ document.removeEventListener("keydown", handleEscape);
644
+ };
645
+ }, [state.visible, onClose]);
646
+ if (!state.visible) return null;
647
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
648
+ "div",
649
+ {
650
+ ref: menuRef,
651
+ className: "fixed z-[9999] min-w-[240px] bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-700 rounded-xl shadow-2xl py-1.5 animate-in fade-in zoom-in-95 duration-100",
652
+ style: { left: state.x, top: state.y },
653
+ children: [
654
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "px-3 py-1.5 text-[10px] font-bold text-zinc-400 dark:text-zinc-500 uppercase tracking-wider select-none border-b border-zinc-100 dark:border-zinc-800 mb-1", children: "Envolver sele\xE7\xE3o em" }),
655
+ MENU_ITEMS.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
656
+ "button",
657
+ {
658
+ className: "w-full flex items-center gap-2.5 px-3 py-2 text-left text-sm text-zinc-700 dark:text-zinc-200 hover:bg-indigo-50 dark:hover:bg-indigo-950/40 hover:text-indigo-700 dark:hover:text-indigo-300 transition-colors",
659
+ onClick: () => {
660
+ onAction(item.type);
661
+ onClose();
662
+ },
663
+ children: [
664
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "flex-1 font-medium", children: item.label }),
665
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-[10px] font-mono text-zinc-400 dark:text-zinc-500 bg-zinc-100 dark:bg-zinc-800 px-1.5 py-0.5 rounded", children: item.shortcut })
666
+ ]
667
+ },
668
+ item.type
669
+ ))
670
+ ]
671
+ }
672
+ );
673
+ }
674
+ var DEFAULT_EDITOR_SETTINGS = {
675
+ theme: "textmate",
676
+ fontSize: 14,
677
+ wordWrap: true,
678
+ showLineNumbers: true
679
+ };
680
+ function Editor({
681
+ value,
682
+ onChange,
683
+ onSave,
684
+ settings,
685
+ mode = "chordpro",
686
+ readOnly = false,
687
+ fallback = null
688
+ }) {
689
+ const activeSettings = {
690
+ ...DEFAULT_EDITOR_SETTINGS,
691
+ ...settings
692
+ };
693
+ const editorRef = (0, import_react.useRef)(null);
694
+ const [contextMenu, setContextMenu] = (0, import_react.useState)({
695
+ x: 0,
696
+ y: 0,
697
+ visible: false
698
+ });
699
+ const handleContextMenuAction = (0, import_react.useCallback)((type) => {
700
+ if (editorRef.current) {
701
+ wrapSelectionInSection(editorRef.current, type);
702
+ }
703
+ }, []);
704
+ const handleLoad = (0, import_react.useCallback)((editor) => {
705
+ if (!editor) return;
706
+ editorRef.current = editor;
707
+ if (editor.commands && typeof editor.commands.addCommand === "function") {
708
+ editor.commands.addCommand({
709
+ name: "save",
710
+ bindKey: { win: "Ctrl-S", mac: "Cmd-S" },
711
+ exec: (ed) => {
712
+ if (ed && typeof ed.getValue === "function") {
713
+ onSave?.(ed.getValue());
714
+ }
715
+ }
716
+ });
717
+ editor.commands.addCommand({
718
+ name: "wrapInVerse",
719
+ bindKey: { win: "Alt-V", mac: "Alt-V" },
720
+ exec: (ed) => wrapSelectionInSection(ed, "verse")
721
+ });
722
+ editor.commands.addCommand({
723
+ name: "wrapInChorus",
724
+ bindKey: { win: "Alt-R", mac: "Alt-R" },
725
+ exec: (ed) => wrapSelectionInSection(ed, "chorus")
726
+ });
727
+ editor.commands.addCommand({
728
+ name: "wrapInBridge",
729
+ bindKey: { win: "Alt-B", mac: "Alt-B" },
730
+ exec: (ed) => wrapSelectionInSection(ed, "bridge")
731
+ });
732
+ }
733
+ if (editor.container && typeof editor.container.addEventListener === "function") {
734
+ const handleContextMenu = (e) => {
735
+ if (!editor || typeof editor.getSelectedText !== "function") return;
736
+ const selectedText = editor.getSelectedText();
737
+ if (selectedText && selectedText.trim()) {
738
+ e.preventDefault();
739
+ e.stopPropagation();
740
+ setContextMenu({ x: e.clientX, y: e.clientY, visible: true });
741
+ }
742
+ };
743
+ editor.container.addEventListener("contextmenu", handleContextMenu);
744
+ }
745
+ }, [onSave]);
746
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
747
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.Suspense, { fallback, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
748
+ LazyAce,
749
+ {
750
+ mode,
751
+ theme: activeSettings.theme,
752
+ width: "100%",
753
+ height: "100%",
754
+ value,
755
+ onChange,
756
+ onLoad: handleLoad,
757
+ readOnly,
758
+ fontSize: activeSettings.fontSize,
759
+ wrapEnabled: activeSettings.wordWrap,
760
+ showGutter: activeSettings.showLineNumbers,
761
+ setOptions: {
762
+ enableLiveAutocompletion: true,
763
+ enableBasicAutocompletion: true,
764
+ enableSnippets: true,
765
+ showLineNumbers: activeSettings.showLineNumbers,
766
+ tabSize: 2,
767
+ useWorker: false
768
+ }
769
+ }
770
+ ) }),
771
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
772
+ EditorContextMenu,
773
+ {
774
+ state: contextMenu,
775
+ onAction: handleContextMenuAction,
776
+ onClose: () => setContextMenu((s) => ({ ...s, visible: false }))
777
+ }
778
+ )
779
+ ] });
780
+ }
781
+ var Editor_default = Editor;
782
+ // Annotate the CommonJS export names for ESM import in node:
783
+ 0 && (module.exports = {
784
+ ChordFinder,
785
+ Editor,
786
+ preloadEditor,
787
+ registerChordproMode,
788
+ registerChordproSnippets
789
+ });