@trafica/editor 1.0.23 → 1.0.24

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.js CHANGED
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  var react = require('react');
4
- var reactDom = require('react-dom');
5
4
  var jsxRuntime = require('react/jsx-runtime');
5
+ var reactDom = require('react-dom');
6
6
 
7
7
  // src/editor/core/DocumentModel.ts
8
8
  function isTextNode(node) {
@@ -3321,15 +3321,13 @@ var OPTIONS = [
3321
3321
  function AlignmentDropdown({ engine, activeAlignment }) {
3322
3322
  var _a;
3323
3323
  const [open, setOpen] = react.useState(false);
3324
- const [popupPos, setPopupPos] = react.useState({ top: 0, left: 0 });
3325
- const buttonRef = react.useRef(null);
3326
- const popupRef = react.useRef(null);
3324
+ const containerRef = react.useRef(null);
3327
3325
  react.useEffect(() => {
3328
3326
  if (!open) return;
3329
3327
  const handler = (e) => {
3330
- var _a2, _b;
3331
- const target = e.target;
3332
- if (!((_a2 = popupRef.current) == null ? void 0 : _a2.contains(target)) && !((_b = buttonRef.current) == null ? void 0 : _b.contains(target))) setOpen(false);
3328
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
3329
+ setOpen(false);
3330
+ }
3333
3331
  };
3334
3332
  document.addEventListener("mousedown", handler);
3335
3333
  return () => document.removeEventListener("mousedown", handler);
@@ -3343,31 +3341,22 @@ function AlignmentDropdown({ engine, activeAlignment }) {
3343
3341
  return () => document.removeEventListener("keydown", handler);
3344
3342
  }, [open]);
3345
3343
  const activeOption = (_a = OPTIONS.find((o) => o.value === activeAlignment)) != null ? _a : OPTIONS[0];
3346
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3344
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative", children: [
3347
3345
  /* @__PURE__ */ jsxRuntime.jsxs(
3348
3346
  "button",
3349
3347
  {
3350
- ref: buttonRef,
3351
3348
  type: "button",
3352
3349
  title: `Text alignment (${activeOption.label})`,
3353
3350
  "aria-haspopup": "listbox",
3354
3351
  "aria-expanded": open,
3355
3352
  onMouseDown: (e) => {
3356
3353
  e.preventDefault();
3357
- if (open) {
3358
- setOpen(false);
3359
- return;
3360
- }
3361
- if (buttonRef.current) {
3362
- const rect = buttonRef.current.getBoundingClientRect();
3363
- setPopupPos({ top: rect.bottom + 4, left: rect.left });
3364
- }
3365
- setOpen(true);
3354
+ setOpen((prev) => !prev);
3366
3355
  },
3367
3356
  className: [
3368
3357
  "flex items-center gap-0.5 px-1.5 h-8 rounded text-sm font-medium transition-colors",
3369
3358
  "focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500",
3370
- open ? "bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-gray-100" : "text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
3359
+ open ? "bg-gray-100 text-gray-900 " : "text-gray-700 hover:bg-gray-100 "
3371
3360
  ].join(" "),
3372
3361
  children: [
3373
3362
  activeOption.icon,
@@ -3375,45 +3364,40 @@ function AlignmentDropdown({ engine, activeAlignment }) {
3375
3364
  ]
3376
3365
  }
3377
3366
  ),
3378
- open && reactDom.createPortal(
3379
- /* @__PURE__ */ jsxRuntime.jsx(
3380
- "div",
3381
- {
3382
- ref: popupRef,
3383
- role: "listbox",
3384
- "aria-label": "Text alignment",
3385
- style: { position: "fixed", top: popupPos.top, left: popupPos.left, zIndex: 9999, minWidth: 168 },
3386
- className: "bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg shadow-lg py-1",
3387
- children: OPTIONS.map((option) => {
3388
- const isActive = activeAlignment === option.value;
3389
- return /* @__PURE__ */ jsxRuntime.jsxs(
3390
- "button",
3391
- {
3392
- type: "button",
3393
- role: "option",
3394
- "aria-selected": isActive,
3395
- title: `${option.label} (${option.shortcut})`,
3396
- onMouseDown: (e) => {
3397
- e.preventDefault();
3398
- setAlignment(option.value)(engine);
3399
- setOpen(false);
3400
- },
3401
- className: [
3402
- "w-full flex items-center gap-3 px-3 py-1.5 text-sm transition-colors",
3403
- isActive ? "bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 font-medium" : "text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
3404
- ].join(" "),
3405
- children: [
3406
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", children: option.icon }),
3407
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 text-left", children: option.label }),
3408
- isActive && /* @__PURE__ */ jsxRuntime.jsx(CheckIcon, {})
3409
- ]
3367
+ open && /* @__PURE__ */ jsxRuntime.jsx(
3368
+ "div",
3369
+ {
3370
+ role: "listbox",
3371
+ "aria-label": "Text alignment",
3372
+ className: "absolute top-full left-0 mt-1 z-50 bg-white border border-gray-200 rounded-lg shadow-lg py-1 min-w-[168px]",
3373
+ children: OPTIONS.map((option) => {
3374
+ const isActive = activeAlignment === option.value;
3375
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3376
+ "button",
3377
+ {
3378
+ type: "button",
3379
+ role: "option",
3380
+ "aria-selected": isActive,
3381
+ title: `${option.label} (${option.shortcut})`,
3382
+ onMouseDown: (e) => {
3383
+ e.preventDefault();
3384
+ setAlignment(option.value)(engine);
3385
+ setOpen(false);
3410
3386
  },
3411
- option.value
3412
- );
3413
- })
3414
- }
3415
- ),
3416
- document.body
3387
+ className: [
3388
+ "w-full flex items-center gap-3 px-3 py-1.5 text-sm transition-colors",
3389
+ isActive ? "bg-blue-50 text-blue-700 font-medium" : "text-gray-700 hover:bg-gray-100 "
3390
+ ].join(" "),
3391
+ children: [
3392
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", children: option.icon }),
3393
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 text-left", children: option.label }),
3394
+ isActive && /* @__PURE__ */ jsxRuntime.jsx(CheckIcon, {})
3395
+ ]
3396
+ },
3397
+ option.value
3398
+ );
3399
+ })
3400
+ }
3417
3401
  )
3418
3402
  ] });
3419
3403
  }
@@ -3458,15 +3442,11 @@ function CheckIcon() {
3458
3442
  var SIZES = ["8", "10", "12", "14", "16", "18", "24", "32", "48"];
3459
3443
  function FontSizeDropdown({ engine, activeFontSize }) {
3460
3444
  const [open, setOpen] = react.useState(false);
3461
- const [popupPos, setPopupPos] = react.useState({ top: 0, left: 0 });
3462
- const buttonRef = react.useRef(null);
3463
- const popupRef = react.useRef(null);
3445
+ const containerRef = react.useRef(null);
3464
3446
  react.useEffect(() => {
3465
3447
  if (!open) return;
3466
3448
  const handler = (e) => {
3467
- var _a, _b;
3468
- const target = e.target;
3469
- if (!((_a = popupRef.current) == null ? void 0 : _a.contains(target)) && !((_b = buttonRef.current) == null ? void 0 : _b.contains(target))) {
3449
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
3470
3450
  setOpen(false);
3471
3451
  }
3472
3452
  };
@@ -3481,36 +3461,24 @@ function FontSizeDropdown({ engine, activeFontSize }) {
3481
3461
  document.addEventListener("keydown", handler);
3482
3462
  return () => document.removeEventListener("keydown", handler);
3483
3463
  }, [open]);
3484
- function openDropdown() {
3485
- if (buttonRef.current) {
3486
- const rect = buttonRef.current.getBoundingClientRect();
3487
- setPopupPos({ top: rect.bottom + 4, left: rect.left });
3488
- }
3489
- setOpen(true);
3490
- }
3491
3464
  const activeNum = activeFontSize ? activeFontSize.replace("px", "") : null;
3492
3465
  const label = activeNum != null ? activeNum : "Size";
3493
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3466
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative", children: [
3494
3467
  /* @__PURE__ */ jsxRuntime.jsxs(
3495
3468
  "button",
3496
3469
  {
3497
- ref: buttonRef,
3498
3470
  type: "button",
3499
3471
  title: "Font size",
3500
3472
  "aria-haspopup": "listbox",
3501
3473
  "aria-expanded": open,
3502
3474
  onMouseDown: (e) => {
3503
3475
  e.preventDefault();
3504
- if (open) {
3505
- setOpen(false);
3506
- } else {
3507
- openDropdown();
3508
- }
3476
+ setOpen((prev) => !prev);
3509
3477
  },
3510
3478
  className: [
3511
3479
  "flex items-center gap-0.5 px-1.5 h-8 rounded text-sm font-medium transition-colors min-w-[52px]",
3512
3480
  "focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500",
3513
- open ? "bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-gray-100" : "text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
3481
+ open ? "bg-gray-100 text-gray-900 " : "text-gray-700 hover:bg-gray-100 "
3514
3482
  ].join(" "),
3515
3483
  children: [
3516
3484
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 text-left", children: label }),
@@ -3518,66 +3486,61 @@ function FontSizeDropdown({ engine, activeFontSize }) {
3518
3486
  ]
3519
3487
  }
3520
3488
  ),
3521
- open && reactDom.createPortal(
3522
- /* @__PURE__ */ jsxRuntime.jsxs(
3523
- "div",
3524
- {
3525
- ref: popupRef,
3526
- role: "listbox",
3527
- "aria-label": "Font size",
3528
- style: { position: "fixed", top: popupPos.top, left: popupPos.left, zIndex: 9999, minWidth: 88, maxHeight: 256, overflowY: "auto" },
3529
- className: "bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg shadow-lg py-1",
3530
- children: [
3531
- /* @__PURE__ */ jsxRuntime.jsxs(
3489
+ open && /* @__PURE__ */ jsxRuntime.jsxs(
3490
+ "div",
3491
+ {
3492
+ role: "listbox",
3493
+ "aria-label": "Font size",
3494
+ className: "absolute top-full left-0 mt-1 z-50 bg-white border border-gray-200 rounded-lg shadow-lg py-1 min-w-[88px] max-h-64 overflow-y-auto",
3495
+ children: [
3496
+ /* @__PURE__ */ jsxRuntime.jsxs(
3497
+ "button",
3498
+ {
3499
+ type: "button",
3500
+ role: "option",
3501
+ "aria-selected": activeFontSize === null,
3502
+ onMouseDown: (e) => {
3503
+ e.preventDefault();
3504
+ setFontSize(null)(engine);
3505
+ setOpen(false);
3506
+ },
3507
+ className: [
3508
+ "w-full flex items-center justify-between px-3 py-1.5 text-sm transition-colors",
3509
+ activeFontSize === null ? "bg-blue-50 text-blue-700 font-medium" : "text-gray-700 hover:bg-gray-100 "
3510
+ ].join(" "),
3511
+ children: [
3512
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Default" }),
3513
+ activeFontSize === null && /* @__PURE__ */ jsxRuntime.jsx(CheckIcon2, {})
3514
+ ]
3515
+ }
3516
+ ),
3517
+ SIZES.map((size) => {
3518
+ const isActive = activeNum === size;
3519
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3532
3520
  "button",
3533
3521
  {
3534
3522
  type: "button",
3535
3523
  role: "option",
3536
- "aria-selected": activeFontSize === null,
3524
+ "aria-selected": isActive,
3537
3525
  onMouseDown: (e) => {
3538
3526
  e.preventDefault();
3539
- setFontSize(null)(engine);
3527
+ setFontSize(`${size}px`)(engine);
3540
3528
  setOpen(false);
3541
3529
  },
3542
3530
  className: [
3543
- "w-full flex items-center justify-between px-3 py-1.5 text-sm transition-colors",
3544
- activeFontSize === null ? "bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 font-medium" : "text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
3531
+ "w-full flex items-center justify-between px-3 py-1.5 transition-colors",
3532
+ isActive ? "bg-blue-50 text-blue-700 font-medium" : "text-gray-700 hover:bg-gray-100 "
3545
3533
  ].join(" "),
3546
3534
  children: [
3547
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Default" }),
3548
- activeFontSize === null && /* @__PURE__ */ jsxRuntime.jsx(CheckIcon2, {})
3535
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: `${size}px`, lineHeight: 1.3 }, children: size }),
3536
+ isActive && /* @__PURE__ */ jsxRuntime.jsx(CheckIcon2, {})
3549
3537
  ]
3550
- }
3551
- ),
3552
- SIZES.map((size) => {
3553
- const isActive = activeNum === size;
3554
- return /* @__PURE__ */ jsxRuntime.jsxs(
3555
- "button",
3556
- {
3557
- type: "button",
3558
- role: "option",
3559
- "aria-selected": isActive,
3560
- onMouseDown: (e) => {
3561
- e.preventDefault();
3562
- setFontSize(`${size}px`)(engine);
3563
- setOpen(false);
3564
- },
3565
- className: [
3566
- "w-full flex items-center justify-between px-3 py-1.5 transition-colors",
3567
- isActive ? "bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 font-medium" : "text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
3568
- ].join(" "),
3569
- children: [
3570
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: `${size}px`, lineHeight: 1.3 }, children: size }),
3571
- isActive && /* @__PURE__ */ jsxRuntime.jsx(CheckIcon2, {})
3572
- ]
3573
- },
3574
- size
3575
- );
3576
- })
3577
- ]
3578
- }
3579
- ),
3580
- document.body
3538
+ },
3539
+ size
3540
+ );
3541
+ })
3542
+ ]
3543
+ }
3581
3544
  )
3582
3545
  ] });
3583
3546
  }
@@ -3603,16 +3566,12 @@ function FontFamilyDropdown({ engine, activeFontFamily }) {
3603
3566
  const [open, setOpen] = react.useState(false);
3604
3567
  const [showCustomInput, setShowCustomInput] = react.useState(false);
3605
3568
  const [customValue, setCustomValue] = react.useState("");
3606
- const [popupPos, setPopupPos] = react.useState({ top: 0, left: 0 });
3607
- const buttonRef = react.useRef(null);
3608
- const popupRef = react.useRef(null);
3569
+ const containerRef = react.useRef(null);
3609
3570
  const customInputRef = react.useRef(null);
3610
3571
  react.useEffect(() => {
3611
3572
  if (!open) return;
3612
3573
  const handler = (e) => {
3613
- var _a2, _b;
3614
- const target = e.target;
3615
- if (!((_a2 = popupRef.current) == null ? void 0 : _a2.contains(target)) && !((_b = buttonRef.current) == null ? void 0 : _b.contains(target))) {
3574
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
3616
3575
  closeDropdown();
3617
3576
  }
3618
3577
  };
@@ -3631,17 +3590,6 @@ function FontFamilyDropdown({ engine, activeFontFamily }) {
3631
3590
  var _a2;
3632
3591
  if (showCustomInput) (_a2 = customInputRef.current) == null ? void 0 : _a2.focus();
3633
3592
  }, [showCustomInput]);
3634
- function openDropdown() {
3635
- if (buttonRef.current) {
3636
- const rect = buttonRef.current.getBoundingClientRect();
3637
- const popupWidth = 200;
3638
- const left = Math.min(rect.left, window.innerWidth - popupWidth - 8);
3639
- setPopupPos({ top: rect.bottom + 4, left: Math.max(8, left) });
3640
- }
3641
- setShowCustomInput(false);
3642
- setCustomValue("");
3643
- setOpen(true);
3644
- }
3645
3593
  function closeDropdown() {
3646
3594
  setOpen(false);
3647
3595
  setShowCustomInput(false);
@@ -3653,11 +3601,10 @@ function FontFamilyDropdown({ engine, activeFontFamily }) {
3653
3601
  }
3654
3602
  const activeOption = activeFontFamily ? (_a = FONTS.find((f) => f.value === activeFontFamily)) != null ? _a : null : null;
3655
3603
  const triggerLabel = activeOption ? activeOption.label : activeFontFamily ? activeFontFamily.split(",")[0].replace(/['"]/g, "").trim().slice(0, 10) : "Font";
3656
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3604
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative", children: [
3657
3605
  /* @__PURE__ */ jsxRuntime.jsxs(
3658
3606
  "button",
3659
3607
  {
3660
- ref: buttonRef,
3661
3608
  type: "button",
3662
3609
  title: "Font family",
3663
3610
  "aria-haspopup": "listbox",
@@ -3666,14 +3613,16 @@ function FontFamilyDropdown({ engine, activeFontFamily }) {
3666
3613
  e.preventDefault();
3667
3614
  if (open) {
3668
3615
  closeDropdown();
3669
- } else {
3670
- openDropdown();
3616
+ return;
3671
3617
  }
3618
+ setShowCustomInput(false);
3619
+ setCustomValue("");
3620
+ setOpen(true);
3672
3621
  },
3673
3622
  className: [
3674
3623
  "flex items-center gap-0.5 px-1.5 h-8 rounded text-sm font-medium transition-colors min-w-[60px] max-w-[120px]",
3675
3624
  "focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500",
3676
- open ? "bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-gray-100" : "text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
3625
+ open ? "bg-gray-100 text-gray-900 " : "text-gray-700 hover:bg-gray-100 "
3677
3626
  ].join(" "),
3678
3627
  children: [
3679
3628
  /* @__PURE__ */ jsxRuntime.jsx(FontIcon, {}),
@@ -3682,65 +3631,60 @@ function FontFamilyDropdown({ engine, activeFontFamily }) {
3682
3631
  ]
3683
3632
  }
3684
3633
  ),
3685
- open && reactDom.createPortal(
3686
- /* @__PURE__ */ jsxRuntime.jsxs(
3687
- "div",
3688
- {
3689
- ref: popupRef,
3690
- role: "listbox",
3691
- "aria-label": "Font family",
3692
- style: { position: "fixed", top: popupPos.top, left: popupPos.left, zIndex: 9999, minWidth: 200 },
3693
- className: "bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg shadow-lg py-1",
3694
- children: [
3695
- /* @__PURE__ */ jsxRuntime.jsxs(
3634
+ open && /* @__PURE__ */ jsxRuntime.jsxs(
3635
+ "div",
3636
+ {
3637
+ role: "listbox",
3638
+ "aria-label": "Font family",
3639
+ className: "absolute top-full left-0 mt-1 z-50 bg-white border border-gray-200 rounded-lg shadow-lg py-1 min-w-[200px]",
3640
+ children: [
3641
+ /* @__PURE__ */ jsxRuntime.jsxs(
3642
+ "button",
3643
+ {
3644
+ type: "button",
3645
+ role: "option",
3646
+ "aria-selected": activeFontFamily === null,
3647
+ onMouseDown: (e) => {
3648
+ e.preventDefault();
3649
+ applyFont(null);
3650
+ },
3651
+ className: [
3652
+ "w-full flex items-center justify-between px-3 py-1.5 text-sm transition-colors",
3653
+ activeFontFamily === null ? "bg-blue-50 text-blue-700 font-medium" : "text-gray-700 hover:bg-gray-100 "
3654
+ ].join(" "),
3655
+ children: [
3656
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Default" }),
3657
+ activeFontFamily === null && /* @__PURE__ */ jsxRuntime.jsx(CheckIcon3, {})
3658
+ ]
3659
+ }
3660
+ ),
3661
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "my-1 border-t border-gray-100 " }),
3662
+ FONTS.map((font) => {
3663
+ const isActive = activeFontFamily === font.value;
3664
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3696
3665
  "button",
3697
3666
  {
3698
3667
  type: "button",
3699
3668
  role: "option",
3700
- "aria-selected": activeFontFamily === null,
3669
+ "aria-selected": isActive,
3701
3670
  onMouseDown: (e) => {
3702
3671
  e.preventDefault();
3703
- applyFont(null);
3672
+ applyFont(font.value);
3704
3673
  },
3705
3674
  className: [
3706
3675
  "w-full flex items-center justify-between px-3 py-1.5 text-sm transition-colors",
3707
- activeFontFamily === null ? "bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 font-medium" : "text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
3676
+ isActive ? "bg-blue-50 text-blue-700 font-medium" : "text-gray-700 hover:bg-gray-100 "
3708
3677
  ].join(" "),
3709
3678
  children: [
3710
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Default" }),
3711
- activeFontFamily === null && /* @__PURE__ */ jsxRuntime.jsx(CheckIcon3, {})
3679
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontFamily: font.value }, children: font.label }),
3680
+ isActive && /* @__PURE__ */ jsxRuntime.jsx(CheckIcon3, {})
3712
3681
  ]
3713
- }
3714
- ),
3715
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "my-1 border-t border-gray-100 dark:border-gray-700" }),
3716
- FONTS.map((font) => {
3717
- const isActive = activeFontFamily === font.value;
3718
- return /* @__PURE__ */ jsxRuntime.jsxs(
3719
- "button",
3720
- {
3721
- type: "button",
3722
- role: "option",
3723
- "aria-selected": isActive,
3724
- onMouseDown: (e) => {
3725
- e.preventDefault();
3726
- applyFont(font.value);
3727
- },
3728
- className: [
3729
- "w-full flex items-center justify-between px-3 py-1.5 text-sm transition-colors",
3730
- isActive ? "bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 font-medium" : "text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
3731
- ].join(" "),
3732
- children: [
3733
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontFamily: font.value }, children: font.label }),
3734
- isActive && /* @__PURE__ */ jsxRuntime.jsx(CheckIcon3, {})
3735
- ]
3736
- },
3737
- font.value
3738
- );
3739
- })
3740
- ]
3741
- }
3742
- ),
3743
- document.body
3682
+ },
3683
+ font.value
3684
+ );
3685
+ })
3686
+ ]
3687
+ }
3744
3688
  )
3745
3689
  ] });
3746
3690
  }
@@ -3786,15 +3730,11 @@ function BackgroundColorDropdown({
3786
3730
  const [lastColor, setLastColor] = react.useState(PRESET_COLORS[6].value);
3787
3731
  const [hexInput, setHexInput] = react.useState("");
3788
3732
  const [hexError, setHexError] = react.useState(false);
3789
- const [popupPos, setPopupPos] = react.useState({ top: 0, left: 0 });
3790
- const chevronRef = react.useRef(null);
3791
- const popupRef = react.useRef(null);
3733
+ const containerRef = react.useRef(null);
3792
3734
  react.useEffect(() => {
3793
3735
  if (!open) return;
3794
3736
  const handler = (e) => {
3795
- var _a, _b;
3796
- const target = e.target;
3797
- if (!((_a = popupRef.current) == null ? void 0 : _a.contains(target)) && !((_b = chevronRef.current) == null ? void 0 : _b.contains(target)))
3737
+ if (containerRef.current && !containerRef.current.contains(e.target))
3798
3738
  setOpen(false);
3799
3739
  };
3800
3740
  document.addEventListener("mousedown", handler);
@@ -3837,7 +3777,7 @@ function BackgroundColorDropdown({
3837
3777
  (c) => !PRESET_COLORS.some((p) => normalizeHex(p.value) === normalizeHex(c))
3838
3778
  );
3839
3779
  const normActive = activeColor ? normalizeHex(activeColor) : null;
3840
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center", children: [
3780
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative flex items-center", children: [
3841
3781
  /* @__PURE__ */ jsxRuntime.jsxs(
3842
3782
  "button",
3843
3783
  {
@@ -3847,7 +3787,7 @@ function BackgroundColorDropdown({
3847
3787
  e.preventDefault();
3848
3788
  handleQuickApply();
3849
3789
  },
3850
- className: "flex flex-col items-center justify-center gap-0.5 w-8 h-8 rounded-l\n text-gray-700 dark:text-gray-300\n hover:bg-gray-100 dark:hover:bg-gray-700\n focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-500\n transition-colors",
3790
+ className: "flex flex-col items-center justify-center gap-0.5 w-8 h-8 rounded-l\r\n text-gray-700 \r\n hover:bg-gray-100 \r\n focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-500\r\n transition-colors",
3851
3791
  children: [
3852
3792
  /* @__PURE__ */ jsxRuntime.jsx(BgColorIcon, {}),
3853
3793
  /* @__PURE__ */ jsxRuntime.jsx(ColorBar, { color: normActive != null ? normActive : lastColor })
@@ -3857,145 +3797,135 @@ function BackgroundColorDropdown({
3857
3797
  /* @__PURE__ */ jsxRuntime.jsx(
3858
3798
  "button",
3859
3799
  {
3860
- ref: chevronRef,
3861
3800
  type: "button",
3862
3801
  title: "More background colors",
3863
3802
  "aria-haspopup": "listbox",
3864
3803
  "aria-expanded": open,
3865
3804
  onMouseDown: (e) => {
3866
3805
  e.preventDefault();
3867
- if (open) {
3868
- setOpen(false);
3869
- return;
3870
- }
3871
- if (chevronRef.current) {
3872
- const rect = chevronRef.current.getBoundingClientRect();
3873
- setPopupPos({ top: rect.bottom + 4, left: rect.left - 190 });
3874
- }
3875
- setHexInput("");
3876
- setHexError(false);
3877
- setOpen(true);
3806
+ setOpen((p) => {
3807
+ if (!p) {
3808
+ setHexInput("");
3809
+ setHexError(false);
3810
+ }
3811
+ return !p;
3812
+ });
3878
3813
  },
3879
3814
  className: [
3880
- "flex items-center justify-center w-4 h-8 rounded-r border-l border-gray-200 dark:border-gray-600",
3815
+ "flex items-center justify-center w-4 h-8 rounded-r border-l border-gray-200 ",
3881
3816
  "focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-500",
3882
3817
  "transition-colors",
3883
- open ? "bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-200" : "text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700"
3818
+ open ? "bg-gray-100 text-gray-700 " : "text-gray-400 hover:bg-gray-100 "
3884
3819
  ].join(" "),
3885
3820
  children: /* @__PURE__ */ jsxRuntime.jsx(ChevronDownIcon4, {})
3886
3821
  }
3887
3822
  ),
3888
- open && reactDom.createPortal(
3889
- /* @__PURE__ */ jsxRuntime.jsxs(
3890
- "div",
3891
- {
3892
- ref: popupRef,
3893
- role: "dialog",
3894
- "aria-label": "Background color",
3895
- style: { position: "fixed", top: popupPos.top, left: popupPos.left, zIndex: 9999, width: 208 },
3896
- className: "bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg shadow-xl p-3",
3897
- children: [
3823
+ open && /* @__PURE__ */ jsxRuntime.jsxs(
3824
+ "div",
3825
+ {
3826
+ role: "dialog",
3827
+ "aria-label": "Background color",
3828
+ className: "absolute top-full left-0 mt-1 z-50\r\n bg-white \r\n border border-gray-200 \r\n rounded-lg shadow-xl\r\n p-3 w-[208px]",
3829
+ children: [
3830
+ /* @__PURE__ */ jsxRuntime.jsxs(
3831
+ "button",
3832
+ {
3833
+ type: "button",
3834
+ onMouseDown: (e) => {
3835
+ e.preventDefault();
3836
+ applyColor(null);
3837
+ },
3838
+ className: "flex items-center gap-2 w-full px-2 py-1.5 mb-2.5 text-xs font-medium\r\n text-gray-600 \r\n rounded hover:bg-gray-100 \r\n transition-colors",
3839
+ children: [
3840
+ /* @__PURE__ */ jsxRuntime.jsx(RemoveColorIcon, {}),
3841
+ "Remove background"
3842
+ ]
3843
+ }
3844
+ ),
3845
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px bg-gray-100 mb-2.5" }),
3846
+ extraDocColors.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3847
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] font-semibold uppercase tracking-widest text-gray-400 mb-1.5 px-0.5", children: "Document colors" }),
3848
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-5 gap-1 mb-2.5", children: extraDocColors.slice(0, 10).map((c) => /* @__PURE__ */ jsxRuntime.jsx(
3849
+ Swatch,
3850
+ {
3851
+ color: { label: c, value: c },
3852
+ isActive: normActive === normalizeHex(c),
3853
+ onSelect: applyColor
3854
+ },
3855
+ c
3856
+ )) }),
3857
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px bg-gray-100 mb-2.5" })
3858
+ ] }),
3859
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-5 gap-1 mb-3", children: PRESET_COLORS.map((c) => /* @__PURE__ */ jsxRuntime.jsx(
3860
+ Swatch,
3861
+ {
3862
+ color: c,
3863
+ isActive: normActive === normalizeHex(c.value),
3864
+ onSelect: applyColor
3865
+ },
3866
+ c.value
3867
+ )) }),
3868
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px bg-gray-100 mb-2.5" }),
3869
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] font-semibold uppercase tracking-widest text-gray-400 mb-1.5 px-0.5", children: "Custom color" }),
3870
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
3871
+ /* @__PURE__ */ jsxRuntime.jsx(
3872
+ "div",
3873
+ {
3874
+ className: "shrink-0 w-7 h-7 rounded border border-gray-300 ",
3875
+ style: { backgroundColor: hexInputToPreview(hexInput) }
3876
+ }
3877
+ ),
3898
3878
  /* @__PURE__ */ jsxRuntime.jsxs(
3879
+ "div",
3880
+ {
3881
+ className: "flex flex-1 items-center border rounded overflow-hidden\r\n border-gray-300 \r\n focus-within:ring-2 focus-within:ring-blue-500",
3882
+ children: [
3883
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pl-2 text-xs text-gray-400 select-none", children: "#" }),
3884
+ /* @__PURE__ */ jsxRuntime.jsx(
3885
+ "input",
3886
+ {
3887
+ type: "text",
3888
+ maxLength: 6,
3889
+ value: hexInput,
3890
+ onChange: (e) => {
3891
+ setHexInput(e.target.value.replace(/[^0-9a-fA-F]/g, ""));
3892
+ setHexError(false);
3893
+ },
3894
+ onKeyDown: (e) => {
3895
+ if (e.key === "Enter") {
3896
+ e.preventDefault();
3897
+ commitHexInput();
3898
+ }
3899
+ if (e.key === "Escape") setOpen(false);
3900
+ },
3901
+ placeholder: "e.g. ffff00",
3902
+ className: [
3903
+ "flex-1 px-1 py-1.5 text-xs bg-transparent outline-none font-mono",
3904
+ "text-gray-900 ",
3905
+ hexError ? "text-red-500" : ""
3906
+ ].join(" "),
3907
+ "aria-label": "Hex colour value"
3908
+ }
3909
+ )
3910
+ ]
3911
+ }
3912
+ ),
3913
+ /* @__PURE__ */ jsxRuntime.jsx(
3899
3914
  "button",
3900
3915
  {
3901
3916
  type: "button",
3902
3917
  onMouseDown: (e) => {
3903
3918
  e.preventDefault();
3904
- applyColor(null);
3919
+ commitHexInput();
3905
3920
  },
3906
- className: "flex items-center gap-2 w-full px-2 py-1.5 mb-2.5 text-xs font-medium\n text-gray-600 dark:text-gray-300\n rounded hover:bg-gray-100 dark:hover:bg-gray-700\n transition-colors",
3907
- children: [
3908
- /* @__PURE__ */ jsxRuntime.jsx(RemoveColorIcon, {}),
3909
- "Remove background"
3910
- ]
3921
+ className: "shrink-0 px-2 py-1.5 text-xs bg-blue-600 text-white rounded\r\n hover:bg-blue-700 transition-colors",
3922
+ children: "\u2713"
3911
3923
  }
3912
- ),
3913
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px bg-gray-100 dark:bg-gray-700 mb-2.5" }),
3914
- extraDocColors.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3915
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] font-semibold uppercase tracking-widest text-gray-400 dark:text-gray-500 mb-1.5 px-0.5", children: "Document colors" }),
3916
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-5 gap-1 mb-2.5", children: extraDocColors.slice(0, 10).map((c) => /* @__PURE__ */ jsxRuntime.jsx(
3917
- Swatch,
3918
- {
3919
- color: { label: c, value: c },
3920
- isActive: normActive === normalizeHex(c),
3921
- onSelect: applyColor
3922
- },
3923
- c
3924
- )) }),
3925
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px bg-gray-100 dark:bg-gray-700 mb-2.5" })
3926
- ] }),
3927
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-5 gap-1 mb-3", children: PRESET_COLORS.map((c) => /* @__PURE__ */ jsxRuntime.jsx(
3928
- Swatch,
3929
- {
3930
- color: c,
3931
- isActive: normActive === normalizeHex(c.value),
3932
- onSelect: applyColor
3933
- },
3934
- c.value
3935
- )) }),
3936
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px bg-gray-100 dark:bg-gray-700 mb-2.5" }),
3937
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] font-semibold uppercase tracking-widest text-gray-400 dark:text-gray-500 mb-1.5 px-0.5", children: "Custom color" }),
3938
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
3939
- /* @__PURE__ */ jsxRuntime.jsx(
3940
- "div",
3941
- {
3942
- className: "shrink-0 w-7 h-7 rounded border border-gray-300 dark:border-gray-600",
3943
- style: { backgroundColor: hexInputToPreview(hexInput) }
3944
- }
3945
- ),
3946
- /* @__PURE__ */ jsxRuntime.jsxs(
3947
- "div",
3948
- {
3949
- className: "flex flex-1 items-center border rounded overflow-hidden\n border-gray-300 dark:border-gray-600\n focus-within:ring-2 focus-within:ring-blue-500",
3950
- children: [
3951
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pl-2 text-xs text-gray-400 select-none", children: "#" }),
3952
- /* @__PURE__ */ jsxRuntime.jsx(
3953
- "input",
3954
- {
3955
- type: "text",
3956
- maxLength: 6,
3957
- value: hexInput,
3958
- onChange: (e) => {
3959
- setHexInput(e.target.value.replace(/[^0-9a-fA-F]/g, ""));
3960
- setHexError(false);
3961
- },
3962
- onKeyDown: (e) => {
3963
- if (e.key === "Enter") {
3964
- e.preventDefault();
3965
- commitHexInput();
3966
- }
3967
- if (e.key === "Escape") setOpen(false);
3968
- },
3969
- placeholder: "e.g. ffff00",
3970
- className: [
3971
- "flex-1 px-1 py-1.5 text-xs bg-transparent outline-none font-mono",
3972
- "text-gray-900 dark:text-gray-100",
3973
- hexError ? "text-red-500" : ""
3974
- ].join(" "),
3975
- "aria-label": "Hex colour value"
3976
- }
3977
- )
3978
- ]
3979
- }
3980
- ),
3981
- /* @__PURE__ */ jsxRuntime.jsx(
3982
- "button",
3983
- {
3984
- type: "button",
3985
- onMouseDown: (e) => {
3986
- e.preventDefault();
3987
- commitHexInput();
3988
- },
3989
- className: "shrink-0 px-2 py-1.5 text-xs bg-blue-600 text-white rounded\n hover:bg-blue-700 transition-colors",
3990
- children: "\u2713"
3991
- }
3992
- )
3993
- ] }),
3994
- hexError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] text-red-500 mt-1 px-0.5", children: "Invalid hex colour" })
3995
- ]
3996
- }
3997
- ),
3998
- document.body
3924
+ )
3925
+ ] }),
3926
+ hexError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] text-red-500 mt-1 px-0.5", children: "Invalid hex colour" })
3927
+ ]
3928
+ }
3999
3929
  )
4000
3930
  ] });
4001
3931
  }
@@ -4020,7 +3950,7 @@ function Swatch({
4020
3950
  "hover:scale-110 focus:outline-none",
4021
3951
  "focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-blue-500",
4022
3952
  isActive ? "ring-2 ring-offset-1 ring-blue-500 scale-105" : "",
4023
- color.hasBorder ? "border border-gray-300 dark:border-gray-500" : ""
3953
+ color.hasBorder ? "border border-gray-300 " : ""
4024
3954
  ].join(" "),
4025
3955
  style: { backgroundColor: color.value },
4026
3956
  children: isActive && /* @__PURE__ */ jsxRuntime.jsx(CheckIcon4, { light: isLight(color.value) })
@@ -4143,15 +4073,11 @@ function TextColorDropdown({
4143
4073
  const [lastColor, setLastColor] = react.useState(PRESET_COLORS2[0].value);
4144
4074
  const [hexInput, setHexInput] = react.useState("");
4145
4075
  const [hexError, setHexError] = react.useState(false);
4146
- const [popupPos, setPopupPos] = react.useState({ top: 0, left: 0 });
4147
- const chevronRef = react.useRef(null);
4148
- const popupRef = react.useRef(null);
4076
+ const containerRef = react.useRef(null);
4149
4077
  react.useEffect(() => {
4150
4078
  if (!open) return;
4151
4079
  const handler = (e) => {
4152
- var _a, _b;
4153
- const target = e.target;
4154
- if (!((_a = popupRef.current) == null ? void 0 : _a.contains(target)) && !((_b = chevronRef.current) == null ? void 0 : _b.contains(target)))
4080
+ if (containerRef.current && !containerRef.current.contains(e.target))
4155
4081
  setOpen(false);
4156
4082
  };
4157
4083
  document.addEventListener("mousedown", handler);
@@ -4191,7 +4117,7 @@ function TextColorDropdown({
4191
4117
  (c) => !PRESET_COLORS2.some((p) => normalizeHex2(p.value) === normalizeHex2(c))
4192
4118
  );
4193
4119
  const normActive = activeColor ? normalizeHex2(activeColor) : null;
4194
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center", children: [
4120
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative flex items-center", children: [
4195
4121
  /* @__PURE__ */ jsxRuntime.jsxs(
4196
4122
  "button",
4197
4123
  {
@@ -4201,7 +4127,7 @@ function TextColorDropdown({
4201
4127
  e.preventDefault();
4202
4128
  handleQuickApply();
4203
4129
  },
4204
- className: "flex flex-col items-center justify-center gap-0.5 w-8 h-8 rounded-l\n text-gray-700 dark:text-gray-300\n hover:bg-gray-100 dark:hover:bg-gray-700\n focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-500\n transition-colors",
4130
+ className: "flex flex-col items-center justify-center gap-0.5 w-8 h-8 rounded-l\r\n text-gray-700 \r\n hover:bg-gray-100 \r\n focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-500\r\n transition-colors",
4205
4131
  children: [
4206
4132
  /* @__PURE__ */ jsxRuntime.jsx(TextColorIcon, {}),
4207
4133
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -4217,139 +4143,129 @@ function TextColorDropdown({
4217
4143
  /* @__PURE__ */ jsxRuntime.jsx(
4218
4144
  "button",
4219
4145
  {
4220
- ref: chevronRef,
4221
4146
  type: "button",
4222
4147
  title: "More text colors",
4223
4148
  "aria-haspopup": "listbox",
4224
4149
  "aria-expanded": open,
4225
4150
  onMouseDown: (e) => {
4226
4151
  e.preventDefault();
4227
- if (open) {
4228
- setOpen(false);
4229
- return;
4230
- }
4231
- if (chevronRef.current) {
4232
- const rect = chevronRef.current.getBoundingClientRect();
4233
- setPopupPos({ top: rect.bottom + 4, left: rect.left - 190 });
4234
- }
4235
- setHexInput("");
4236
- setHexError(false);
4237
- setOpen(true);
4152
+ setOpen((p) => {
4153
+ if (!p) {
4154
+ setHexInput("");
4155
+ setHexError(false);
4156
+ }
4157
+ return !p;
4158
+ });
4238
4159
  },
4239
4160
  className: [
4240
- "flex items-center justify-center w-4 h-8 rounded-r border-l border-gray-200 dark:border-gray-600",
4161
+ "flex items-center justify-center w-4 h-8 rounded-r border-l border-gray-200 ",
4241
4162
  "focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-500",
4242
4163
  "transition-colors",
4243
- open ? "bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-200" : "text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700"
4164
+ open ? "bg-gray-100 text-gray-700 " : "text-gray-400 hover:bg-gray-100 "
4244
4165
  ].join(" "),
4245
4166
  children: /* @__PURE__ */ jsxRuntime.jsx(ChevronDownIcon5, {})
4246
4167
  }
4247
4168
  ),
4248
- open && reactDom.createPortal(
4249
- /* @__PURE__ */ jsxRuntime.jsxs(
4250
- "div",
4251
- {
4252
- ref: popupRef,
4253
- role: "dialog",
4254
- "aria-label": "Text color",
4255
- style: { position: "fixed", top: popupPos.top, left: popupPos.left, zIndex: 9999, width: 208 },
4256
- className: "bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg shadow-xl p-3",
4257
- children: [
4258
- /* @__PURE__ */ jsxRuntime.jsxs(
4259
- "button",
4260
- {
4261
- type: "button",
4262
- onMouseDown: (e) => {
4263
- e.preventDefault();
4264
- applyColor(null);
4265
- },
4266
- className: "flex items-center gap-2 w-full px-2 py-1.5 mb-2.5 text-xs font-medium\n text-gray-600 dark:text-gray-300\n rounded hover:bg-gray-100 dark:hover:bg-gray-700\n transition-colors",
4267
- children: [
4268
- /* @__PURE__ */ jsxRuntime.jsx(RemoveColorIcon2, {}),
4269
- "Remove color"
4270
- ]
4271
- }
4272
- ),
4273
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px bg-gray-100 dark:bg-gray-700 mb-2.5" }),
4274
- extraDocColors.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4275
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] font-semibold uppercase tracking-widest text-gray-400 dark:text-gray-500 mb-1.5 px-0.5", children: "Document colors" }),
4276
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-5 gap-1 mb-2.5", children: extraDocColors.slice(0, 10).map((c) => /* @__PURE__ */ jsxRuntime.jsx(
4277
- Swatch2,
4278
- {
4279
- color: { label: c, value: c },
4280
- isActive: normActive === normalizeHex2(c),
4281
- onSelect: applyColor
4282
- },
4283
- c
4284
- )) }),
4285
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px bg-gray-100 dark:bg-gray-700 mb-2.5" })
4286
- ] }),
4287
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-5 gap-1 mb-3", children: PRESET_COLORS2.map((c) => /* @__PURE__ */ jsxRuntime.jsx(
4169
+ open && /* @__PURE__ */ jsxRuntime.jsxs(
4170
+ "div",
4171
+ {
4172
+ role: "dialog",
4173
+ "aria-label": "Text color",
4174
+ className: "absolute top-full left-0 mt-1 z-50\r\n bg-white \r\n border border-gray-200 \r\n rounded-lg shadow-xl\r\n p-3 w-[208px]",
4175
+ children: [
4176
+ /* @__PURE__ */ jsxRuntime.jsxs(
4177
+ "button",
4178
+ {
4179
+ type: "button",
4180
+ onMouseDown: (e) => {
4181
+ e.preventDefault();
4182
+ applyColor(null);
4183
+ },
4184
+ className: "flex items-center gap-2 w-full px-2 py-1.5 mb-2.5 text-xs font-medium\r\n text-gray-600 \r\n rounded hover:bg-gray-100 \r\n transition-colors",
4185
+ children: [
4186
+ /* @__PURE__ */ jsxRuntime.jsx(RemoveColorIcon2, {}),
4187
+ "Remove color"
4188
+ ]
4189
+ }
4190
+ ),
4191
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px bg-gray-100 mb-2.5" }),
4192
+ extraDocColors.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4193
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] font-semibold uppercase tracking-widest text-gray-400 mb-1.5 px-0.5", children: "Document colors" }),
4194
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-5 gap-1 mb-2.5", children: extraDocColors.slice(0, 10).map((c) => /* @__PURE__ */ jsxRuntime.jsx(
4288
4195
  Swatch2,
4289
4196
  {
4290
- color: c,
4291
- isActive: normActive === normalizeHex2(c.value),
4197
+ color: { label: c, value: c },
4198
+ isActive: normActive === normalizeHex2(c),
4292
4199
  onSelect: applyColor
4293
4200
  },
4294
- c.value
4201
+ c
4295
4202
  )) }),
4296
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px bg-gray-100 dark:bg-gray-700 mb-2.5" }),
4297
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] font-semibold uppercase tracking-widest text-gray-400 dark:text-gray-500 mb-1.5 px-0.5", children: "Custom color" }),
4298
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
4299
- /* @__PURE__ */ jsxRuntime.jsx(
4300
- "div",
4301
- {
4302
- className: "shrink-0 w-7 h-7 rounded border border-gray-300 dark:border-gray-600",
4303
- style: { backgroundColor: hexInputToPreview2(hexInput) }
4304
- }
4305
- ),
4306
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 items-center border rounded overflow-hidden\n border-gray-300 dark:border-gray-600\n focus-within:ring-2 focus-within:ring-blue-500", children: [
4307
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pl-2 text-xs text-gray-400 select-none", children: "#" }),
4308
- /* @__PURE__ */ jsxRuntime.jsx(
4309
- "input",
4310
- {
4311
- type: "text",
4312
- maxLength: 6,
4313
- value: hexInput,
4314
- onChange: (e) => {
4315
- setHexInput(e.target.value.replace(/[^0-9a-fA-F]/g, ""));
4316
- setHexError(false);
4317
- },
4318
- onKeyDown: (e) => {
4319
- if (e.key === "Enter") {
4320
- e.preventDefault();
4321
- commitHexInput();
4322
- }
4323
- if (e.key === "Escape") setOpen(false);
4324
- },
4325
- placeholder: "e.g. 000000",
4326
- className: [
4327
- "flex-1 px-1 py-1.5 text-xs bg-transparent outline-none font-mono",
4328
- "text-gray-900 dark:text-gray-100",
4329
- hexError ? "text-red-500" : ""
4330
- ].join(" "),
4331
- "aria-label": "Hex colour value"
4332
- }
4333
- )
4334
- ] }),
4203
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px bg-gray-100 mb-2.5" })
4204
+ ] }),
4205
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-5 gap-1 mb-3", children: PRESET_COLORS2.map((c) => /* @__PURE__ */ jsxRuntime.jsx(
4206
+ Swatch2,
4207
+ {
4208
+ color: c,
4209
+ isActive: normActive === normalizeHex2(c.value),
4210
+ onSelect: applyColor
4211
+ },
4212
+ c.value
4213
+ )) }),
4214
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px bg-gray-100 mb-2.5" }),
4215
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] font-semibold uppercase tracking-widest text-gray-400 mb-1.5 px-0.5", children: "Custom color" }),
4216
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
4217
+ /* @__PURE__ */ jsxRuntime.jsx(
4218
+ "div",
4219
+ {
4220
+ className: "shrink-0 w-7 h-7 rounded border border-gray-300 ",
4221
+ style: { backgroundColor: hexInputToPreview2(hexInput) }
4222
+ }
4223
+ ),
4224
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 items-center border rounded overflow-hidden\r\n border-gray-300 \r\n focus-within:ring-2 focus-within:ring-blue-500", children: [
4225
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pl-2 text-xs text-gray-400 select-none", children: "#" }),
4335
4226
  /* @__PURE__ */ jsxRuntime.jsx(
4336
- "button",
4227
+ "input",
4337
4228
  {
4338
- type: "button",
4339
- onMouseDown: (e) => {
4340
- e.preventDefault();
4341
- commitHexInput();
4229
+ type: "text",
4230
+ maxLength: 6,
4231
+ value: hexInput,
4232
+ onChange: (e) => {
4233
+ setHexInput(e.target.value.replace(/[^0-9a-fA-F]/g, ""));
4234
+ setHexError(false);
4342
4235
  },
4343
- className: "shrink-0 px-2 py-1.5 text-xs bg-blue-600 text-white rounded\n hover:bg-blue-700 transition-colors",
4344
- children: "\u2713"
4236
+ onKeyDown: (e) => {
4237
+ if (e.key === "Enter") {
4238
+ e.preventDefault();
4239
+ commitHexInput();
4240
+ }
4241
+ if (e.key === "Escape") setOpen(false);
4242
+ },
4243
+ placeholder: "e.g. 000000",
4244
+ className: [
4245
+ "flex-1 px-1 py-1.5 text-xs bg-transparent outline-none font-mono",
4246
+ "text-gray-900 ",
4247
+ hexError ? "text-red-500" : ""
4248
+ ].join(" "),
4249
+ "aria-label": "Hex colour value"
4345
4250
  }
4346
4251
  )
4347
4252
  ] }),
4348
- hexError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] text-red-500 mt-1 px-0.5", children: "Invalid hex colour" })
4349
- ]
4350
- }
4351
- ),
4352
- document.body
4253
+ /* @__PURE__ */ jsxRuntime.jsx(
4254
+ "button",
4255
+ {
4256
+ type: "button",
4257
+ onMouseDown: (e) => {
4258
+ e.preventDefault();
4259
+ commitHexInput();
4260
+ },
4261
+ className: "shrink-0 px-2 py-1.5 text-xs bg-blue-600 text-white rounded\r\n hover:bg-blue-700 transition-colors",
4262
+ children: "\u2713"
4263
+ }
4264
+ )
4265
+ ] }),
4266
+ hexError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] text-red-500 mt-1 px-0.5", children: "Invalid hex colour" })
4267
+ ]
4268
+ }
4353
4269
  )
4354
4270
  ] });
4355
4271
  }
@@ -4374,7 +4290,7 @@ function Swatch2({
4374
4290
  "hover:scale-110 focus:outline-none",
4375
4291
  "focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-blue-500",
4376
4292
  isActive ? "ring-2 ring-offset-1 ring-blue-500 scale-105" : "",
4377
- color.hasBorder ? "border border-gray-300 dark:border-gray-500" : ""
4293
+ color.hasBorder ? "border border-gray-300 " : ""
4378
4294
  ].join(" "),
4379
4295
  style: { backgroundColor: color.value },
4380
4296
  children: isActive && /* @__PURE__ */ jsxRuntime.jsx(CheckIcon5, { light: isLight2(color.value) })
@@ -5906,9 +5822,7 @@ function CodeBlockButton({ engine }) {
5906
5822
  const activeBlock = getActiveBlockType(state.doc, state.selection);
5907
5823
  const isActive = activeBlock === "code_block";
5908
5824
  const [open, setOpen] = react.useState(false);
5909
- const [popupPos, setPopupPos] = react.useState({ top: 0, left: 0 });
5910
- const chevronRef = react.useRef(null);
5911
- const popupRef = react.useRef(null);
5825
+ const containerRef = react.useRef(null);
5912
5826
  const currentLang = (() => {
5913
5827
  var _a2;
5914
5828
  if (!isActive || !state.selection) return "plaintext";
@@ -5921,9 +5835,9 @@ function CodeBlockButton({ engine }) {
5921
5835
  react.useEffect(() => {
5922
5836
  if (!open) return;
5923
5837
  const handler = (e) => {
5924
- var _a2, _b;
5925
- const target = e.target;
5926
- if (!((_a2 = popupRef.current) == null ? void 0 : _a2.contains(target)) && !((_b = chevronRef.current) == null ? void 0 : _b.contains(target))) closeDropdown();
5838
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
5839
+ closeDropdown();
5840
+ }
5927
5841
  };
5928
5842
  document.addEventListener("mousedown", handler);
5929
5843
  return () => document.removeEventListener("mousedown", handler);
@@ -5950,7 +5864,7 @@ function CodeBlockButton({ engine }) {
5950
5864
  setOpen(false);
5951
5865
  };
5952
5866
  const activeLang = LANGUAGES.find((l) => l.value === currentLang);
5953
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center", children: [
5867
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative flex items-center", children: [
5954
5868
  /* @__PURE__ */ jsxRuntime.jsx(
5955
5869
  "button",
5956
5870
  {
@@ -5962,7 +5876,7 @@ function CodeBlockButton({ engine }) {
5962
5876
  },
5963
5877
  className: [
5964
5878
  "flex items-center justify-center w-8 h-8 rounded-l text-sm transition-colors",
5965
- isActive ? "bg-blue-100 dark:bg-blue-900/40 text-blue-700 dark:text-blue-300" : "text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
5879
+ isActive ? "bg-blue-100 text-blue-700 " : "text-gray-700 hover:bg-gray-100 "
5966
5880
  ].join(" "),
5967
5881
  children: /* @__PURE__ */ jsxRuntime.jsx(CodeBlockIcon, {})
5968
5882
  }
@@ -5970,69 +5884,55 @@ function CodeBlockButton({ engine }) {
5970
5884
  /* @__PURE__ */ jsxRuntime.jsx(
5971
5885
  "button",
5972
5886
  {
5973
- ref: chevronRef,
5974
5887
  type: "button",
5975
5888
  title: isActive ? `Language: ${(_a = activeLang == null ? void 0 : activeLang.label) != null ? _a : "Plain text"}` : "Select language",
5976
5889
  "aria-haspopup": "listbox",
5977
5890
  "aria-expanded": open,
5978
5891
  onMouseDown: (e) => {
5979
5892
  e.preventDefault();
5980
- if (open) {
5981
- closeDropdown();
5982
- return;
5983
- }
5984
- if (chevronRef.current) {
5985
- const rect = chevronRef.current.getBoundingClientRect();
5986
- setPopupPos({ top: rect.bottom + 4, left: rect.left - 160 });
5987
- }
5988
- setOpen(true);
5893
+ setOpen((p) => !p);
5989
5894
  },
5990
5895
  className: [
5991
- "flex items-center justify-center w-4 h-8 rounded-r border-l border-gray-200 dark:border-gray-600 transition-colors",
5992
- open ? "bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-200" : "text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700"
5896
+ "flex items-center justify-center w-4 h-8 rounded-r border-l border-gray-200 transition-colors",
5897
+ open ? "bg-gray-100 text-gray-700 " : "text-gray-400 hover:bg-gray-100 "
5993
5898
  ].join(" "),
5994
5899
  children: /* @__PURE__ */ jsxRuntime.jsx(ChevronIcon, {})
5995
5900
  }
5996
5901
  ),
5997
- open && reactDom.createPortal(
5998
- /* @__PURE__ */ jsxRuntime.jsxs(
5999
- "div",
6000
- {
6001
- ref: popupRef,
6002
- role: "listbox",
6003
- "aria-label": "Code block language",
6004
- style: { position: "fixed", top: popupPos.top, left: popupPos.left, zIndex: 9999, width: 176, maxHeight: 288, overflowY: "auto" },
6005
- className: "bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg shadow-xl py-1",
6006
- children: [
6007
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-3 py-1.5 text-[10px] font-semibold uppercase tracking-widest text-gray-400 dark:text-gray-500", children: "Language" }),
6008
- LANGUAGES.map((lang) => {
6009
- const isSelected = isActive && currentLang === lang.value;
6010
- return /* @__PURE__ */ jsxRuntime.jsxs(
6011
- "button",
6012
- {
6013
- type: "button",
6014
- role: "option",
6015
- "aria-selected": isSelected,
6016
- onMouseDown: (e) => {
6017
- e.preventDefault();
6018
- handleSelectLanguage(lang.value);
6019
- },
6020
- className: [
6021
- "w-full text-left px-3 py-1.5 text-sm flex items-center gap-2 transition-colors",
6022
- isSelected ? "bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 font-medium" : "text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
6023
- ].join(" "),
6024
- children: [
6025
- isSelected && /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-3 h-3 shrink-0", viewBox: "0 0 12 12", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "1 6 4.5 9.5 11 2.5", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) }),
6026
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: isSelected ? "" : "ml-5", children: lang.label })
6027
- ]
5902
+ open && /* @__PURE__ */ jsxRuntime.jsxs(
5903
+ "div",
5904
+ {
5905
+ role: "listbox",
5906
+ "aria-label": "Code block language",
5907
+ className: "absolute top-full left-0 mt-1 z-50 bg-white border border-gray-200 rounded-lg shadow-xl py-1 w-44 max-h-72 overflow-y-auto",
5908
+ children: [
5909
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-3 py-1.5 text-[10px] font-semibold uppercase tracking-widest text-gray-400 ", children: "Language" }),
5910
+ LANGUAGES.map((lang) => {
5911
+ const isSelected = isActive && currentLang === lang.value;
5912
+ return /* @__PURE__ */ jsxRuntime.jsxs(
5913
+ "button",
5914
+ {
5915
+ type: "button",
5916
+ role: "option",
5917
+ "aria-selected": isSelected,
5918
+ onMouseDown: (e) => {
5919
+ e.preventDefault();
5920
+ handleSelectLanguage(lang.value);
6028
5921
  },
6029
- lang.value
6030
- );
6031
- })
6032
- ]
6033
- }
6034
- ),
6035
- document.body
5922
+ className: [
5923
+ "w-full text-left px-3 py-1.5 text-sm flex items-center gap-2 transition-colors",
5924
+ isSelected ? "bg-blue-50 text-blue-700 font-medium" : "text-gray-700 hover:bg-gray-100 "
5925
+ ].join(" "),
5926
+ children: [
5927
+ isSelected && /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-3 h-3 shrink-0", viewBox: "0 0 12 12", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "1 6 4.5 9.5 11 2.5", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) }),
5928
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: isSelected ? "" : "ml-5", children: lang.label })
5929
+ ]
5930
+ },
5931
+ lang.value
5932
+ );
5933
+ })
5934
+ ]
5935
+ }
6036
5936
  )
6037
5937
  ] });
6038
5938
  }
@@ -6362,16 +6262,12 @@ function HeadingDropdown({
6362
6262
  }) {
6363
6263
  var _a;
6364
6264
  const [open, setOpen] = react.useState(false);
6365
- const [popupPos, setPopupPos] = react.useState({ top: 0, left: 0 });
6366
- const buttonRef = react.useRef(null);
6367
- const popupRef = react.useRef(null);
6265
+ const containerRef = react.useRef(null);
6368
6266
  const run = (command) => command(engine);
6369
6267
  react.useEffect(() => {
6370
6268
  if (!open) return;
6371
6269
  const handler = (e) => {
6372
- var _a2, _b;
6373
- const target = e.target;
6374
- if (!((_a2 = popupRef.current) == null ? void 0 : _a2.contains(target)) && !((_b = buttonRef.current) == null ? void 0 : _b.contains(target)))
6270
+ if (containerRef.current && !containerRef.current.contains(e.target))
6375
6271
  setOpen(false);
6376
6272
  };
6377
6273
  document.addEventListener("mousedown", handler);
@@ -6389,23 +6285,14 @@ function HeadingDropdown({
6389
6285
  code_block: "Code"
6390
6286
  };
6391
6287
  const label = (_a = BLOCK_LABELS[activeBlock]) != null ? _a : "Paragraph";
6392
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6288
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative", children: [
6393
6289
  /* @__PURE__ */ jsxRuntime.jsxs(
6394
6290
  "button",
6395
6291
  {
6396
- ref: buttonRef,
6397
6292
  type: "button",
6398
6293
  onMouseDown: (e) => {
6399
6294
  e.preventDefault();
6400
- if (open) {
6401
- setOpen(false);
6402
- return;
6403
- }
6404
- if (buttonRef.current) {
6405
- const rect = buttonRef.current.getBoundingClientRect();
6406
- setPopupPos({ top: rect.bottom + 2, left: rect.left });
6407
- }
6408
- setOpen(true);
6295
+ setOpen((v) => !v);
6409
6296
  },
6410
6297
  className: "flex items-center gap-1 px-2 h-8 rounded text-sm font-medium text-gray-700 hover:bg-gray-100 cursor-pointer",
6411
6298
  children: [
@@ -6414,7 +6301,7 @@ function HeadingDropdown({
6414
6301
  ]
6415
6302
  }
6416
6303
  ),
6417
- open && reactDom.createPortal(/* @__PURE__ */ jsxRuntime.jsx("div", { ref: popupRef, style: { position: "fixed", top: popupPos.top, left: popupPos.left, zIndex: 9999, width: 160 }, className: "bg-white border border-gray-200 rounded shadow-lg", children: [
6304
+ open && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-full left-0 mt-0.5 w-40 bg-white border border-gray-200 rounded shadow-lg z-50", children: [
6418
6305
  {
6419
6306
  label: "Paragraph",
6420
6307
  cmd: setParagraph,
@@ -6466,7 +6353,7 @@ function HeadingDropdown({
6466
6353
  children: label2
6467
6354
  },
6468
6355
  label2
6469
- )) }), document.body)
6356
+ )) })
6470
6357
  ] });
6471
6358
  }
6472
6359
  function LinkButton({
@@ -6481,16 +6368,10 @@ function LinkButton({
6481
6368
  const [editMode, setEditMode] = react.useState(false);
6482
6369
  const [url, setUrl] = react.useState("");
6483
6370
  const [displayText, setDisplayText] = react.useState("");
6484
- const [popupPos, setPopupPos] = react.useState({ top: 0, left: 0 });
6485
6371
  const containerRef = react.useRef(null);
6486
- const popupRef = react.useRef(null);
6487
6372
  const urlInputRef = react.useRef(null);
6488
6373
  const textInputRef = react.useRef(null);
6489
6374
  const openPopup = () => {
6490
- if (containerRef.current) {
6491
- const rect = containerRef.current.getBoundingClientRect();
6492
- setPopupPos({ top: rect.bottom + 4, left: rect.left });
6493
- }
6494
6375
  if (isActive) {
6495
6376
  setUrl(activeHref != null ? activeHref : "");
6496
6377
  setDisplayText("");
@@ -6522,9 +6403,7 @@ function LinkButton({
6522
6403
  react.useEffect(() => {
6523
6404
  if (!open) return;
6524
6405
  const handler = (e) => {
6525
- var _a, _b;
6526
- const target = e.target;
6527
- if (!((_a = popupRef.current) == null ? void 0 : _a.contains(target)) && !((_b = containerRef.current) == null ? void 0 : _b.contains(target))) {
6406
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
6528
6407
  closePopup();
6529
6408
  }
6530
6409
  };
@@ -6564,7 +6443,7 @@ function LinkButton({
6564
6443
  setEditMode(true);
6565
6444
  };
6566
6445
  const showTextInput = !hasTextSelected && !isActive;
6567
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, children: [
6446
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative", children: [
6568
6447
  /* @__PURE__ */ jsxRuntime.jsx(
6569
6448
  ToolbarButton,
6570
6449
  {
@@ -6575,43 +6454,115 @@ function LinkButton({
6575
6454
  icon: /* @__PURE__ */ jsxRuntime.jsx(LinkIcon, {})
6576
6455
  }
6577
6456
  ),
6578
- open && reactDom.createPortal(
6579
- /* @__PURE__ */ jsxRuntime.jsx(
6580
- "div",
6581
- {
6582
- ref: popupRef,
6583
- role: "dialog",
6584
- "aria-label": "Link editor",
6585
- style: { position: "fixed", top: popupPos.top, left: popupPos.left, zIndex: 9999, width: 320 },
6586
- className: "bg-white border border-gray-200 rounded-lg shadow-xl p-3",
6587
- children: isActive && !editMode ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6588
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-medium text-gray-400 mb-2 uppercase tracking-wide", children: "Link" }),
6589
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
6457
+ open && /* @__PURE__ */ jsxRuntime.jsx(
6458
+ "div",
6459
+ {
6460
+ role: "dialog",
6461
+ "aria-label": "Link editor",
6462
+ className: "absolute top-full left-0 mt-1 z-50 bg-white border border-gray-200 rounded-lg shadow-xl p-3 w-80",
6463
+ children: isActive && !editMode ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6464
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-medium text-gray-400 mb-2 uppercase tracking-wide", children: "Link" }),
6465
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
6466
+ /* @__PURE__ */ jsxRuntime.jsx(
6467
+ "a",
6468
+ {
6469
+ href: activeHref != null ? activeHref : "#",
6470
+ target: "_blank",
6471
+ rel: "noopener noreferrer",
6472
+ className: "flex-1 text-sm text-blue-600 hover:underline truncate",
6473
+ title: activeHref != null ? activeHref : "",
6474
+ children: activeHref
6475
+ }
6476
+ ),
6477
+ /* @__PURE__ */ jsxRuntime.jsx(
6478
+ "button",
6479
+ {
6480
+ type: "button",
6481
+ onMouseDown: (e) => {
6482
+ e.preventDefault();
6483
+ handleStartEdit();
6484
+ },
6485
+ className: "shrink-0 text-xs text-gray-500 hover:text-gray-700 px-2 py-1 rounded hover:bg-gray-100 ",
6486
+ "aria-label": "Edit link",
6487
+ children: "Edit"
6488
+ }
6489
+ ),
6490
+ /* @__PURE__ */ jsxRuntime.jsx(
6491
+ "button",
6492
+ {
6493
+ type: "button",
6494
+ onMouseDown: (e) => {
6495
+ e.preventDefault();
6496
+ handleRemove();
6497
+ },
6498
+ className: "shrink-0 text-xs text-red-500 hover:text-red-700 px-2 py-1 rounded hover:bg-red-50 ",
6499
+ "aria-label": "Remove link",
6500
+ children: "Remove"
6501
+ }
6502
+ )
6503
+ ] })
6504
+ ] }) : (
6505
+ /* ── Edit / Insert mode ── */
6506
+ /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6507
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-medium text-gray-400 mb-3 uppercase tracking-wide", children: isActive ? "Edit link" : "Insert link" }),
6508
+ showTextInput && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-2", children: [
6509
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-xs text-gray-600 mb-1", children: "Display text" }),
6590
6510
  /* @__PURE__ */ jsxRuntime.jsx(
6591
- "a",
6511
+ "input",
6592
6512
  {
6593
- href: activeHref != null ? activeHref : "#",
6594
- target: "_blank",
6595
- rel: "noopener noreferrer",
6596
- className: "flex-1 text-sm text-blue-600 hover:underline truncate",
6597
- title: activeHref != null ? activeHref : "",
6598
- children: activeHref
6513
+ ref: textInputRef,
6514
+ type: "text",
6515
+ value: displayText,
6516
+ onChange: (e) => setDisplayText(e.target.value),
6517
+ onKeyDown: (e) => {
6518
+ var _a;
6519
+ if (e.key === "Enter") {
6520
+ e.preventDefault();
6521
+ (_a = urlInputRef.current) == null ? void 0 : _a.focus();
6522
+ }
6523
+ if (e.key === "Escape") closePopup();
6524
+ },
6525
+ placeholder: "Link text",
6526
+ className: "w-full border border-gray-300 rounded px-2 py-1.5 text-sm bg-white text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"
6599
6527
  }
6600
- ),
6528
+ )
6529
+ ] }),
6530
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-3", children: [
6531
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-xs text-gray-600 mb-1", children: "URL" }),
6532
+ /* @__PURE__ */ jsxRuntime.jsx(
6533
+ "input",
6534
+ {
6535
+ ref: urlInputRef,
6536
+ type: "url",
6537
+ value: url,
6538
+ onChange: (e) => setUrl(e.target.value),
6539
+ onKeyDown: (e) => {
6540
+ if (e.key === "Enter") {
6541
+ e.preventDefault();
6542
+ handleApply();
6543
+ }
6544
+ if (e.key === "Escape") closePopup();
6545
+ },
6546
+ placeholder: "https://example.com",
6547
+ className: "w-full border border-gray-300 rounded px-2 py-1.5 text-sm bg-white text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"
6548
+ }
6549
+ )
6550
+ ] }),
6551
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
6601
6552
  /* @__PURE__ */ jsxRuntime.jsx(
6602
6553
  "button",
6603
6554
  {
6604
6555
  type: "button",
6605
6556
  onMouseDown: (e) => {
6606
6557
  e.preventDefault();
6607
- handleStartEdit();
6558
+ handleApply();
6608
6559
  },
6609
- className: "shrink-0 text-xs text-gray-500 hover:text-gray-700 px-2 py-1 rounded hover:bg-gray-100 ",
6610
- "aria-label": "Edit link",
6611
- children: "Edit"
6560
+ disabled: !url.trim() || showTextInput && !displayText.trim() && !hasTextSelected && !isActive,
6561
+ className: "flex-1 px-3 py-1.5 bg-blue-600 text-white text-sm rounded hover:bg-blue-700 disabled:opacity-40 disabled:cursor-not-allowed",
6562
+ children: isActive ? "Update" : "Insert"
6612
6563
  }
6613
6564
  ),
6614
- /* @__PURE__ */ jsxRuntime.jsx(
6565
+ isActive && /* @__PURE__ */ jsxRuntime.jsx(
6615
6566
  "button",
6616
6567
  {
6617
6568
  type: "button",
@@ -6619,104 +6570,27 @@ function LinkButton({
6619
6570
  e.preventDefault();
6620
6571
  handleRemove();
6621
6572
  },
6622
- className: "shrink-0 text-xs text-red-500 hover:text-red-700 px-2 py-1 rounded hover:bg-red-50 ",
6623
- "aria-label": "Remove link",
6573
+ className: "px-3 py-1.5 text-sm text-red-600 border border-red-200 rounded hover:bg-red-50 ",
6624
6574
  children: "Remove"
6625
6575
  }
6576
+ ),
6577
+ /* @__PURE__ */ jsxRuntime.jsx(
6578
+ "button",
6579
+ {
6580
+ type: "button",
6581
+ onMouseDown: (e) => {
6582
+ e.preventDefault();
6583
+ closePopup();
6584
+ },
6585
+ className: "px-3 py-1.5 text-sm text-gray-500 hover:text-gray-700 ",
6586
+ "aria-label": "Cancel",
6587
+ children: "\u2715"
6588
+ }
6626
6589
  )
6627
6590
  ] })
6628
- ] }) : (
6629
- /* ── Edit / Insert mode ── */
6630
- /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6631
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-medium text-gray-400 mb-3 uppercase tracking-wide", children: isActive ? "Edit link" : "Insert link" }),
6632
- showTextInput && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-2", children: [
6633
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-xs text-gray-600 mb-1", children: "Display text" }),
6634
- /* @__PURE__ */ jsxRuntime.jsx(
6635
- "input",
6636
- {
6637
- ref: textInputRef,
6638
- type: "text",
6639
- value: displayText,
6640
- onChange: (e) => setDisplayText(e.target.value),
6641
- onKeyDown: (e) => {
6642
- var _a;
6643
- if (e.key === "Enter") {
6644
- e.preventDefault();
6645
- (_a = urlInputRef.current) == null ? void 0 : _a.focus();
6646
- }
6647
- if (e.key === "Escape") closePopup();
6648
- },
6649
- placeholder: "Link text",
6650
- className: "w-full border border-gray-300 rounded px-2 py-1.5 text-sm bg-white text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"
6651
- }
6652
- )
6653
- ] }),
6654
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-3", children: [
6655
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-xs text-gray-600 mb-1", children: "URL" }),
6656
- /* @__PURE__ */ jsxRuntime.jsx(
6657
- "input",
6658
- {
6659
- ref: urlInputRef,
6660
- type: "url",
6661
- value: url,
6662
- onChange: (e) => setUrl(e.target.value),
6663
- onKeyDown: (e) => {
6664
- if (e.key === "Enter") {
6665
- e.preventDefault();
6666
- handleApply();
6667
- }
6668
- if (e.key === "Escape") closePopup();
6669
- },
6670
- placeholder: "https://example.com",
6671
- className: "w-full border border-gray-300 rounded px-2 py-1.5 text-sm bg-white text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"
6672
- }
6673
- )
6674
- ] }),
6675
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
6676
- /* @__PURE__ */ jsxRuntime.jsx(
6677
- "button",
6678
- {
6679
- type: "button",
6680
- onMouseDown: (e) => {
6681
- e.preventDefault();
6682
- handleApply();
6683
- },
6684
- disabled: !url.trim() || showTextInput && !displayText.trim() && !hasTextSelected && !isActive,
6685
- className: "flex-1 px-3 py-1.5 bg-blue-600 text-white text-sm rounded hover:bg-blue-700 disabled:opacity-40 disabled:cursor-not-allowed",
6686
- children: isActive ? "Update" : "Insert"
6687
- }
6688
- ),
6689
- isActive && /* @__PURE__ */ jsxRuntime.jsx(
6690
- "button",
6691
- {
6692
- type: "button",
6693
- onMouseDown: (e) => {
6694
- e.preventDefault();
6695
- handleRemove();
6696
- },
6697
- className: "px-3 py-1.5 text-sm text-red-600 border border-red-200 rounded hover:bg-red-50 ",
6698
- children: "Remove"
6699
- }
6700
- ),
6701
- /* @__PURE__ */ jsxRuntime.jsx(
6702
- "button",
6703
- {
6704
- type: "button",
6705
- onMouseDown: (e) => {
6706
- e.preventDefault();
6707
- closePopup();
6708
- },
6709
- className: "px-3 py-1.5 text-sm text-gray-500 hover:text-gray-700 ",
6710
- "aria-label": "Cancel",
6711
- children: "\u2715"
6712
- }
6713
- )
6714
- ] })
6715
- ] })
6716
- )
6717
- }
6718
- ),
6719
- document.body
6591
+ ] })
6592
+ )
6593
+ }
6720
6594
  )
6721
6595
  ] });
6722
6596
  }