rich-html-editor 0.2.1 → 0.2.2

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/dist/index.mjs CHANGED
@@ -317,218 +317,73 @@ function injectStyles(doc) {
317
317
  styleEl.textContent = css;
318
318
  }
319
319
 
320
- // src/toolbar/toolbar.ts
321
- function injectToolbar(doc, options) {
322
- const existing = doc.getElementById(TOOLBAR_ID);
323
- if (existing) existing.remove();
324
- const toolbar = doc.createElement("div");
325
- toolbar.id = TOOLBAR_ID;
326
- toolbar.setAttribute("role", "toolbar");
327
- toolbar.setAttribute("aria-label", "Rich text editor toolbar");
328
- function makeButton(label, title, command, value, isActive, disabled) {
329
- const btn = doc.createElement("button");
330
- btn.type = "button";
331
- if (label && label.trim().startsWith("<")) {
332
- btn.innerHTML = label;
333
- } else {
334
- btn.textContent = label;
320
+ // src/toolbar/color.ts
321
+ function makeColorInput(doc, options, title, command, initialColor) {
322
+ const input = doc.createElement("input");
323
+ input.type = "color";
324
+ input.className = "toolbar-color-input";
325
+ const wrapper = doc.createElement("label");
326
+ wrapper.className = "color-label";
327
+ wrapper.appendChild(doc.createTextNode(title + " "));
328
+ wrapper.appendChild(input);
329
+ let savedRange = null;
330
+ input.addEventListener("pointerdown", () => {
331
+ const s = doc.getSelection();
332
+ if (s && s.rangeCount) savedRange = s.getRangeAt(0).cloneRange();
333
+ });
334
+ input.onchange = (e) => {
335
+ try {
336
+ const s = doc.getSelection();
337
+ if (savedRange && s) {
338
+ s.removeAllRanges();
339
+ s.addRange(savedRange);
340
+ }
341
+ } catch (err) {
335
342
  }
336
- btn.title = title;
337
- btn.setAttribute("aria-label", title);
338
- if (typeof isActive !== "undefined")
339
- btn.setAttribute("aria-pressed", String(!!isActive));
340
- btn.tabIndex = 0;
341
- if (disabled) btn.disabled = true;
342
- btn.onclick = () => options.onCommand(command, value);
343
- btn.addEventListener("keydown", (ev) => {
344
- if (ev.key === "Enter" || ev.key === " ") {
345
- ev.preventDefault();
346
- btn.click();
343
+ options.onCommand(command, e.target.value);
344
+ savedRange = null;
345
+ };
346
+ function rgbToHex(input2) {
347
+ if (!input2) return null;
348
+ const v = input2.trim();
349
+ if (v.startsWith("#")) {
350
+ if (v.length === 4) {
351
+ return ("#" + v[1] + v[1] + v[2] + v[2] + v[3] + v[3]).toLowerCase();
347
352
  }
348
- });
349
- return btn;
350
- }
351
- function makeSelect(title, command, optionsList, initialValue) {
352
- const select = doc.createElement("select");
353
- select.title = title;
354
- select.setAttribute("aria-label", title);
355
- select.appendChild(new Option(title, "", true, true));
356
- for (const opt of optionsList) {
357
- select.appendChild(new Option(opt.label, opt.value));
353
+ return v.toLowerCase();
358
354
  }
359
- try {
360
- if (initialValue) select.value = initialValue;
361
- } catch (e) {
355
+ const rgbMatch = v.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/i);
356
+ if (rgbMatch) {
357
+ const r = Number(rgbMatch[1]);
358
+ const g = Number(rgbMatch[2]);
359
+ const b = Number(rgbMatch[3]);
360
+ const hex = "#" + [r, g, b].map((n) => n.toString(16).padStart(2, "0")).join("").toLowerCase();
361
+ return hex;
362
362
  }
363
- select.onchange = (e) => {
364
- const val = e.target.value;
365
- options.onCommand(command, val);
366
- select.selectedIndex = 0;
367
- };
368
- return select;
363
+ return null;
369
364
  }
370
- function makeColorInput(title, command, initialColor) {
371
- const input = doc.createElement("input");
372
- input.type = "color";
373
- input.className = "toolbar-color-input";
374
- const wrapper = doc.createElement("label");
375
- wrapper.className = "color-label";
376
- wrapper.appendChild(doc.createTextNode(title + " "));
377
- wrapper.appendChild(input);
378
- let savedRange = null;
379
- input.addEventListener("pointerdown", () => {
380
- const s = doc.getSelection();
381
- if (s && s.rangeCount) savedRange = s.getRangeAt(0).cloneRange();
382
- });
383
- input.onchange = (e) => {
384
- try {
385
- const s = doc.getSelection();
386
- if (savedRange && s) {
387
- s.removeAllRanges();
388
- s.addRange(savedRange);
389
- }
390
- } catch (err) {
391
- }
392
- options.onCommand(command, e.target.value);
393
- savedRange = null;
394
- };
395
- function rgbToHex(input2) {
396
- if (!input2) return null;
397
- const v = input2.trim();
398
- if (v.startsWith("#")) {
399
- if (v.length === 4) {
400
- return ("#" + v[1] + v[1] + v[2] + v[2] + v[3] + v[3]).toLowerCase();
401
- }
402
- return v.toLowerCase();
403
- }
404
- const rgbMatch = v.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/i);
405
- if (rgbMatch) {
406
- const r = Number(rgbMatch[1]);
407
- const g = Number(rgbMatch[2]);
408
- const b = Number(rgbMatch[3]);
409
- const hex = "#" + [r, g, b].map((n) => n.toString(16).padStart(2, "0")).join("").toLowerCase();
410
- return hex;
365
+ const setColor = (val) => {
366
+ if (!val) return;
367
+ const hex = rgbToHex(val) || val;
368
+ try {
369
+ if (hex && hex.startsWith("#") && input.value !== hex) {
370
+ input.value = hex;
411
371
  }
412
- return null;
372
+ } catch (e) {
413
373
  }
414
- const setColor = (val) => {
415
- if (!val) return;
416
- const hex = rgbToHex(val) || val;
417
- try {
418
- if (hex && hex.startsWith("#") && input.value !== hex) {
419
- input.value = hex;
420
- }
421
- } catch (e) {
422
- }
423
- };
424
- if (initialColor) setColor(initialColor);
425
- input.addEventListener("input", (e) => {
426
- const val = e.target.value;
427
- setColor(val);
428
- });
429
- input.title = title;
430
- input.setAttribute("aria-label", title);
431
- return wrapper;
432
- }
433
- const format = options.getFormatState();
434
- function makeGroup() {
435
- const g = doc.createElement("div");
436
- g.className = "toolbar-group";
437
- return g;
438
- }
439
- function makeSep() {
440
- const s = doc.createElement("div");
441
- s.className = "toolbar-sep";
442
- return s;
443
- }
444
- const undoBtn = makeButton(
445
- LABEL_UNDO,
446
- "Undo",
447
- "undo",
448
- void 0,
449
- false,
450
- !options.canUndo()
451
- );
452
- undoBtn.onclick = () => options.onUndo();
453
- const redoBtn = makeButton(
454
- LABEL_REDO,
455
- "Redo",
456
- "redo",
457
- void 0,
458
- false,
459
- !options.canRedo()
460
- );
461
- redoBtn.onclick = () => options.onRedo();
462
- const grp1 = makeGroup();
463
- grp1.appendChild(undoBtn);
464
- grp1.appendChild(redoBtn);
465
- toolbar.appendChild(grp1);
466
- toolbar.appendChild(makeSep());
467
- const grp2 = makeGroup();
468
- grp2.className = "toolbar-group collapse-on-small";
469
- grp2.appendChild(
470
- makeSelect(
471
- "Format",
472
- "formatBlock",
473
- FORMAT_OPTIONS,
474
- format.formatBlock
475
- )
476
- );
477
- grp2.appendChild(
478
- makeSelect("Font", "fontName", FONT_OPTIONS, format.fontName)
479
- );
480
- grp2.appendChild(
481
- makeSelect("Size", "fontSize", SIZE_OPTIONS, format.fontSize)
482
- );
483
- toolbar.appendChild(grp2);
484
- toolbar.appendChild(makeSep());
485
- const grp3 = makeGroup();
486
- grp3.appendChild(
487
- makeButton(LABEL_BOLD, "Bold", "bold", void 0, format.bold)
488
- );
489
- grp3.appendChild(
490
- makeButton(LABEL_ITALIC, "Italic", "italic", void 0, format.italic)
491
- );
492
- grp3.appendChild(
493
- makeButton(
494
- LABEL_UNDERLINE,
495
- "Underline",
496
- "underline",
497
- void 0,
498
- format.underline
499
- )
500
- );
501
- grp3.appendChild(makeButton(LABEL_STRIKETHROUGH, "Strikethrough", "strike"));
502
- toolbar.appendChild(grp3);
503
- toolbar.appendChild(makeSep());
504
- const grp4 = makeGroup();
505
- grp4.appendChild(makeButton(LABEL_ALIGN_LEFT, "Align left", "align", "left"));
506
- grp4.appendChild(
507
- makeButton(LABEL_ALIGN_CENTER, "Align center", "align", "center")
508
- );
509
- grp4.appendChild(
510
- makeButton(LABEL_ALIGN_RIGHT, "Align right", "align", "right")
511
- );
512
- toolbar.appendChild(grp4);
513
- toolbar.appendChild(makeSep());
514
- const grp5 = makeGroup();
515
- grp5.className = "toolbar-group collapse-on-small";
516
- grp5.appendChild(
517
- makeColorInput("Text color", "foreColor", format.foreColor)
518
- );
519
- grp5.appendChild(
520
- makeColorInput(
521
- "Highlight color",
522
- "hiliteColor",
523
- format.hiliteColor
524
- )
525
- );
526
- toolbar.appendChild(grp5);
527
- toolbar.appendChild(makeSep());
528
- const grp6 = makeGroup();
529
- grp6.className = "toolbar-group collapse-on-small";
530
- grp6.appendChild(makeButton(LABEL_LINK, "Insert link", "link"));
531
- toolbar.appendChild(grp6);
374
+ };
375
+ if (initialColor) setColor(initialColor);
376
+ input.addEventListener("input", (e) => {
377
+ const val = e.target.value;
378
+ setColor(val);
379
+ });
380
+ input.title = title;
381
+ input.setAttribute("aria-label", title);
382
+ return wrapper;
383
+ }
384
+
385
+ // src/toolbar/overflow.ts
386
+ function setupOverflow(doc, toolbar, options, format, helpers) {
532
387
  const overflowBtn = doc.createElement("button");
533
388
  overflowBtn.type = "button";
534
389
  overflowBtn.className = "toolbar-overflow-btn";
@@ -555,7 +410,7 @@ function injectToolbar(doc, options) {
555
410
  overflowBtn.setAttribute("aria-expanded", "false");
556
411
  overflowBtn.focus();
557
412
  }
558
- overflowBtn.addEventListener("click", (e) => {
413
+ overflowBtn.addEventListener("click", () => {
559
414
  if (overflowMenu.hidden) openOverflow();
560
415
  else closeOverflow();
561
416
  });
@@ -582,35 +437,105 @@ function injectToolbar(doc, options) {
582
437
  }
583
438
  });
584
439
  overflowMenu.appendChild(
585
- makeSelect(
440
+ helpers.makeSelect(
586
441
  "Format",
587
442
  "formatBlock",
588
- FORMAT_OPTIONS,
443
+ window.RHE_FORMAT_OPTIONS || [],
589
444
  format.formatBlock
590
445
  )
591
446
  );
592
447
  overflowMenu.appendChild(
593
- makeSelect("Font", "fontName", FONT_OPTIONS, format.fontName)
448
+ helpers.makeSelect(
449
+ "Font",
450
+ "fontName",
451
+ window.RHE_FONT_OPTIONS || [],
452
+ format.fontName
453
+ )
594
454
  );
595
455
  overflowMenu.appendChild(
596
- makeSelect("Size", "fontSize", SIZE_OPTIONS, format.fontSize)
456
+ helpers.makeSelect(
457
+ "Size",
458
+ "fontSize",
459
+ window.RHE_SIZE_OPTIONS || [],
460
+ format.fontSize
461
+ )
597
462
  );
598
463
  overflowMenu.appendChild(
599
- makeColorInput("Text color", "foreColor", format.foreColor)
464
+ helpers.makeColorInput("Text color", "foreColor", format.foreColor)
600
465
  );
601
466
  overflowMenu.appendChild(
602
- makeColorInput(
467
+ helpers.makeColorInput(
603
468
  "Highlight color",
604
469
  "hiliteColor",
605
470
  format.hiliteColor
606
471
  )
607
472
  );
608
- overflowMenu.appendChild(makeButton(LABEL_LINK, "Insert link", "link"));
609
- const overflowWrap = makeGroup();
473
+ overflowMenu.appendChild(helpers.makeButton("Link", "Insert link", "link"));
474
+ const overflowWrap = helpers.makeGroup();
610
475
  overflowWrap.className = "toolbar-group toolbar-overflow-wrap";
611
476
  overflowWrap.appendChild(overflowBtn);
612
477
  overflowWrap.appendChild(overflowMenu);
613
478
  toolbar.appendChild(overflowWrap);
479
+ }
480
+
481
+ // src/toolbar/buttons.ts
482
+ function makeButton(doc, options, label, title, command, value, isActive, disabled) {
483
+ const btn = doc.createElement("button");
484
+ btn.type = "button";
485
+ if (label && label.trim().startsWith("<")) {
486
+ btn.innerHTML = label;
487
+ } else {
488
+ btn.textContent = label;
489
+ }
490
+ btn.title = title;
491
+ btn.setAttribute("aria-label", title);
492
+ if (typeof isActive !== "undefined")
493
+ btn.setAttribute("aria-pressed", String(!!isActive));
494
+ btn.tabIndex = 0;
495
+ if (disabled) btn.disabled = true;
496
+ btn.onclick = () => options.onCommand(command, value);
497
+ btn.addEventListener("keydown", (ev) => {
498
+ if (ev.key === "Enter" || ev.key === " ") {
499
+ ev.preventDefault();
500
+ btn.click();
501
+ }
502
+ });
503
+ return btn;
504
+ }
505
+ function makeGroup(doc) {
506
+ const g = doc.createElement("div");
507
+ g.className = "toolbar-group";
508
+ return g;
509
+ }
510
+ function makeSep(doc) {
511
+ const s = doc.createElement("div");
512
+ s.className = "toolbar-sep";
513
+ return s;
514
+ }
515
+
516
+ // src/toolbar/selects.ts
517
+ function makeSelect(doc, options, title, command, optionsList, initialValue) {
518
+ const select = doc.createElement("select");
519
+ select.title = title;
520
+ select.setAttribute("aria-label", title);
521
+ select.appendChild(new Option(title, "", true, true));
522
+ for (const opt of optionsList) {
523
+ select.appendChild(new Option(opt.label, opt.value));
524
+ }
525
+ try {
526
+ if (initialValue) select.value = initialValue;
527
+ } catch (e) {
528
+ }
529
+ select.onchange = (e) => {
530
+ const val = e.target.value;
531
+ options.onCommand(command, val);
532
+ select.selectedIndex = 0;
533
+ };
534
+ return select;
535
+ }
536
+
537
+ // src/toolbar/navigation.ts
538
+ function setupNavigation(toolbar) {
614
539
  toolbar.addEventListener("keydown", (e) => {
615
540
  const focusable = Array.from(
616
541
  toolbar.querySelectorAll("button, select, input, [tabindex]")
@@ -633,6 +558,140 @@ function injectToolbar(doc, options) {
633
558
  focusable[focusable.length - 1].focus();
634
559
  }
635
560
  });
561
+ }
562
+
563
+ // src/toolbar/render.ts
564
+ function injectToolbar(doc, options) {
565
+ const existing = doc.getElementById(TOOLBAR_ID);
566
+ if (existing) existing.remove();
567
+ const toolbar = doc.createElement("div");
568
+ toolbar.id = TOOLBAR_ID;
569
+ toolbar.setAttribute("role", "toolbar");
570
+ toolbar.setAttribute("aria-label", "Rich text editor toolbar");
571
+ const makeButton2 = (label, title, command, value, isActive, disabled) => makeButton(
572
+ doc,
573
+ { onCommand: options.onCommand },
574
+ label,
575
+ title,
576
+ command,
577
+ value,
578
+ isActive,
579
+ disabled
580
+ );
581
+ const makeSelect2 = (title, command, optionsList, initialValue) => makeSelect(
582
+ doc,
583
+ { onCommand: options.onCommand },
584
+ title,
585
+ command,
586
+ optionsList,
587
+ initialValue
588
+ );
589
+ const format = options.getFormatState();
590
+ const makeGroup2 = () => makeGroup(doc);
591
+ const makeSep2 = () => makeSep(doc);
592
+ const undoBtn = makeButton2(
593
+ LABEL_UNDO,
594
+ "Undo",
595
+ "undo",
596
+ void 0,
597
+ false,
598
+ !options.canUndo()
599
+ );
600
+ undoBtn.onclick = () => options.onUndo();
601
+ const redoBtn = makeButton2(
602
+ LABEL_REDO,
603
+ "Redo",
604
+ "redo",
605
+ void 0,
606
+ false,
607
+ !options.canRedo()
608
+ );
609
+ redoBtn.onclick = () => options.onRedo();
610
+ const grp1 = makeGroup2();
611
+ grp1.appendChild(undoBtn);
612
+ grp1.appendChild(redoBtn);
613
+ toolbar.appendChild(grp1);
614
+ toolbar.appendChild(makeSep2());
615
+ const grp2 = makeGroup2();
616
+ grp2.className = "toolbar-group collapse-on-small";
617
+ grp2.appendChild(
618
+ makeSelect2(
619
+ "Format",
620
+ "formatBlock",
621
+ FORMAT_OPTIONS,
622
+ format.formatBlock
623
+ )
624
+ );
625
+ grp2.appendChild(
626
+ makeSelect2("Font", "fontName", FONT_OPTIONS, format.fontName)
627
+ );
628
+ grp2.appendChild(
629
+ makeSelect2("Size", "fontSize", SIZE_OPTIONS, format.fontSize)
630
+ );
631
+ toolbar.appendChild(grp2);
632
+ toolbar.appendChild(makeSep2());
633
+ const grp3 = makeGroup2();
634
+ grp3.appendChild(
635
+ makeButton2(LABEL_BOLD, "Bold", "bold", void 0, format.bold)
636
+ );
637
+ grp3.appendChild(
638
+ makeButton2(LABEL_ITALIC, "Italic", "italic", void 0, format.italic)
639
+ );
640
+ grp3.appendChild(
641
+ makeButton2(
642
+ LABEL_UNDERLINE,
643
+ "Underline",
644
+ "underline",
645
+ void 0,
646
+ format.underline
647
+ )
648
+ );
649
+ grp3.appendChild(makeButton2(LABEL_STRIKETHROUGH, "Strikethrough", "strike"));
650
+ toolbar.appendChild(grp3);
651
+ toolbar.appendChild(makeSep2());
652
+ const grp4 = makeGroup2();
653
+ grp4.appendChild(makeButton2(LABEL_ALIGN_LEFT, "Align left", "align", "left"));
654
+ grp4.appendChild(
655
+ makeButton2(LABEL_ALIGN_CENTER, "Align center", "align", "center")
656
+ );
657
+ grp4.appendChild(
658
+ makeButton2(LABEL_ALIGN_RIGHT, "Align right", "align", "right")
659
+ );
660
+ toolbar.appendChild(grp4);
661
+ toolbar.appendChild(makeSep2());
662
+ const grp5 = makeGroup2();
663
+ grp5.className = "toolbar-group collapse-on-small";
664
+ grp5.appendChild(
665
+ makeColorInput(
666
+ doc,
667
+ options,
668
+ "Text color",
669
+ "foreColor",
670
+ format.foreColor
671
+ )
672
+ );
673
+ grp5.appendChild(
674
+ makeColorInput(
675
+ doc,
676
+ options,
677
+ "Highlight color",
678
+ "hiliteColor",
679
+ format.hiliteColor
680
+ )
681
+ );
682
+ toolbar.appendChild(grp5);
683
+ toolbar.appendChild(makeSep2());
684
+ const grp6 = makeGroup2();
685
+ grp6.className = "toolbar-group collapse-on-small";
686
+ grp6.appendChild(makeButton2(LABEL_LINK, "Insert link", "link"));
687
+ toolbar.appendChild(grp6);
688
+ setupOverflow(doc, toolbar, options, format, {
689
+ makeSelect: makeSelect2,
690
+ makeColorInput: (title, command, initial) => makeColorInput(doc, options, title, command, initial),
691
+ makeButton: makeButton2,
692
+ makeGroup: makeGroup2
693
+ });
694
+ setupNavigation(toolbar);
636
695
  doc.body.insertBefore(toolbar, doc.body.firstChild);
637
696
  }
638
697