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