@trafica/editor 1.0.21 → 1.0.23

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