@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.js CHANGED
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  var react = require('react');
4
- var jsxRuntime = require('react/jsx-runtime');
5
4
  var reactDom = require('react-dom');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
6
 
7
7
  // src/editor/core/DocumentModel.ts
8
8
  function isTextNode(node) {
@@ -3321,13 +3321,15 @@ var OPTIONS = [
3321
3321
  function AlignmentDropdown({ engine, activeAlignment }) {
3322
3322
  var _a;
3323
3323
  const [open, setOpen] = react.useState(false);
3324
- const containerRef = react.useRef(null);
3324
+ const [popupPos, setPopupPos] = react.useState({ top: 0, left: 0 });
3325
+ const buttonRef = react.useRef(null);
3326
+ const popupRef = react.useRef(null);
3325
3327
  react.useEffect(() => {
3326
3328
  if (!open) return;
3327
3329
  const handler = (e) => {
3328
- if (containerRef.current && !containerRef.current.contains(e.target)) {
3329
- setOpen(false);
3330
- }
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);
3331
3333
  };
3332
3334
  document.addEventListener("mousedown", handler);
3333
3335
  return () => document.removeEventListener("mousedown", handler);
@@ -3341,22 +3343,31 @@ function AlignmentDropdown({ engine, activeAlignment }) {
3341
3343
  return () => document.removeEventListener("keydown", handler);
3342
3344
  }, [open]);
3343
3345
  const activeOption = (_a = OPTIONS.find((o) => o.value === activeAlignment)) != null ? _a : OPTIONS[0];
3344
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative", children: [
3346
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3345
3347
  /* @__PURE__ */ jsxRuntime.jsxs(
3346
3348
  "button",
3347
3349
  {
3350
+ ref: buttonRef,
3348
3351
  type: "button",
3349
3352
  title: `Text alignment (${activeOption.label})`,
3350
3353
  "aria-haspopup": "listbox",
3351
3354
  "aria-expanded": open,
3352
3355
  onMouseDown: (e) => {
3353
3356
  e.preventDefault();
3354
- setOpen((prev) => !prev);
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);
3355
3366
  },
3356
3367
  className: [
3357
3368
  "flex items-center gap-0.5 px-1.5 h-8 rounded text-sm font-medium transition-colors",
3358
3369
  "focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500",
3359
- open ? "bg-gray-100 text-gray-900 " : "text-gray-700 hover:bg-gray-100 "
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"
3360
3371
  ].join(" "),
3361
3372
  children: [
3362
3373
  activeOption.icon,
@@ -3364,40 +3375,45 @@ function AlignmentDropdown({ engine, activeAlignment }) {
3364
3375
  ]
3365
3376
  }
3366
3377
  ),
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);
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
+ ]
3386
3410
  },
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
- }
3411
+ option.value
3412
+ );
3413
+ })
3414
+ }
3415
+ ),
3416
+ document.body
3401
3417
  )
3402
3418
  ] });
3403
3419
  }
@@ -3442,11 +3458,15 @@ function CheckIcon() {
3442
3458
  var SIZES = ["8", "10", "12", "14", "16", "18", "24", "32", "48"];
3443
3459
  function FontSizeDropdown({ engine, activeFontSize }) {
3444
3460
  const [open, setOpen] = react.useState(false);
3445
- const containerRef = react.useRef(null);
3461
+ const [popupPos, setPopupPos] = react.useState({ top: 0, left: 0 });
3462
+ const buttonRef = react.useRef(null);
3463
+ const popupRef = react.useRef(null);
3446
3464
  react.useEffect(() => {
3447
3465
  if (!open) return;
3448
3466
  const handler = (e) => {
3449
- if (containerRef.current && !containerRef.current.contains(e.target)) {
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))) {
3450
3470
  setOpen(false);
3451
3471
  }
3452
3472
  };
@@ -3461,24 +3481,36 @@ function FontSizeDropdown({ engine, activeFontSize }) {
3461
3481
  document.addEventListener("keydown", handler);
3462
3482
  return () => document.removeEventListener("keydown", handler);
3463
3483
  }, [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
+ }
3464
3491
  const activeNum = activeFontSize ? activeFontSize.replace("px", "") : null;
3465
3492
  const label = activeNum != null ? activeNum : "Size";
3466
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative", children: [
3493
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3467
3494
  /* @__PURE__ */ jsxRuntime.jsxs(
3468
3495
  "button",
3469
3496
  {
3497
+ ref: buttonRef,
3470
3498
  type: "button",
3471
3499
  title: "Font size",
3472
3500
  "aria-haspopup": "listbox",
3473
3501
  "aria-expanded": open,
3474
3502
  onMouseDown: (e) => {
3475
3503
  e.preventDefault();
3476
- setOpen((prev) => !prev);
3504
+ if (open) {
3505
+ setOpen(false);
3506
+ } else {
3507
+ openDropdown();
3508
+ }
3477
3509
  },
3478
3510
  className: [
3479
3511
  "flex items-center gap-0.5 px-1.5 h-8 rounded text-sm font-medium transition-colors min-w-[52px]",
3480
3512
  "focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500",
3481
- open ? "bg-gray-100 text-gray-900 " : "text-gray-700 hover:bg-gray-100 "
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"
3482
3514
  ].join(" "),
3483
3515
  children: [
3484
3516
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 text-left", children: label }),
@@ -3486,61 +3518,66 @@ function FontSizeDropdown({ engine, activeFontSize }) {
3486
3518
  ]
3487
3519
  }
3488
3520
  ),
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(
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(
3520
3532
  "button",
3521
3533
  {
3522
3534
  type: "button",
3523
3535
  role: "option",
3524
- "aria-selected": isActive,
3536
+ "aria-selected": activeFontSize === null,
3525
3537
  onMouseDown: (e) => {
3526
3538
  e.preventDefault();
3527
- setFontSize(`${size}px`)(engine);
3539
+ setFontSize(null)(engine);
3528
3540
  setOpen(false);
3529
3541
  },
3530
3542
  className: [
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 "
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"
3533
3545
  ].join(" "),
3534
3546
  children: [
3535
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: `${size}px`, lineHeight: 1.3 }, children: size }),
3536
- isActive && /* @__PURE__ */ jsxRuntime.jsx(CheckIcon2, {})
3547
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Default" }),
3548
+ activeFontSize === null && /* @__PURE__ */ jsxRuntime.jsx(CheckIcon2, {})
3537
3549
  ]
3538
- },
3539
- size
3540
- );
3541
- })
3542
- ]
3543
- }
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
3544
3581
  )
3545
3582
  ] });
3546
3583
  }
@@ -3566,12 +3603,16 @@ function FontFamilyDropdown({ engine, activeFontFamily }) {
3566
3603
  const [open, setOpen] = react.useState(false);
3567
3604
  const [showCustomInput, setShowCustomInput] = react.useState(false);
3568
3605
  const [customValue, setCustomValue] = react.useState("");
3569
- const containerRef = react.useRef(null);
3606
+ const [popupPos, setPopupPos] = react.useState({ top: 0, left: 0 });
3607
+ const buttonRef = react.useRef(null);
3608
+ const popupRef = react.useRef(null);
3570
3609
  const customInputRef = react.useRef(null);
3571
3610
  react.useEffect(() => {
3572
3611
  if (!open) return;
3573
3612
  const handler = (e) => {
3574
- if (containerRef.current && !containerRef.current.contains(e.target)) {
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))) {
3575
3616
  closeDropdown();
3576
3617
  }
3577
3618
  };
@@ -3590,6 +3631,17 @@ function FontFamilyDropdown({ engine, activeFontFamily }) {
3590
3631
  var _a2;
3591
3632
  if (showCustomInput) (_a2 = customInputRef.current) == null ? void 0 : _a2.focus();
3592
3633
  }, [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
+ }
3593
3645
  function closeDropdown() {
3594
3646
  setOpen(false);
3595
3647
  setShowCustomInput(false);
@@ -3601,10 +3653,11 @@ function FontFamilyDropdown({ engine, activeFontFamily }) {
3601
3653
  }
3602
3654
  const activeOption = activeFontFamily ? (_a = FONTS.find((f) => f.value === activeFontFamily)) != null ? _a : null : null;
3603
3655
  const triggerLabel = activeOption ? activeOption.label : activeFontFamily ? activeFontFamily.split(",")[0].replace(/['"]/g, "").trim().slice(0, 10) : "Font";
3604
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative", children: [
3656
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3605
3657
  /* @__PURE__ */ jsxRuntime.jsxs(
3606
3658
  "button",
3607
3659
  {
3660
+ ref: buttonRef,
3608
3661
  type: "button",
3609
3662
  title: "Font family",
3610
3663
  "aria-haspopup": "listbox",
@@ -3613,16 +3666,14 @@ function FontFamilyDropdown({ engine, activeFontFamily }) {
3613
3666
  e.preventDefault();
3614
3667
  if (open) {
3615
3668
  closeDropdown();
3616
- return;
3669
+ } else {
3670
+ openDropdown();
3617
3671
  }
3618
- setShowCustomInput(false);
3619
- setCustomValue("");
3620
- setOpen(true);
3621
3672
  },
3622
3673
  className: [
3623
3674
  "flex items-center gap-0.5 px-1.5 h-8 rounded text-sm font-medium transition-colors min-w-[60px] max-w-[120px]",
3624
3675
  "focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500",
3625
- open ? "bg-gray-100 text-gray-900 " : "text-gray-700 hover:bg-gray-100 "
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"
3626
3677
  ].join(" "),
3627
3678
  children: [
3628
3679
  /* @__PURE__ */ jsxRuntime.jsx(FontIcon, {}),
@@ -3631,60 +3682,65 @@ function FontFamilyDropdown({ engine, activeFontFamily }) {
3631
3682
  ]
3632
3683
  }
3633
3684
  ),
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(
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(
3665
3696
  "button",
3666
3697
  {
3667
3698
  type: "button",
3668
3699
  role: "option",
3669
- "aria-selected": isActive,
3700
+ "aria-selected": activeFontFamily === null,
3670
3701
  onMouseDown: (e) => {
3671
3702
  e.preventDefault();
3672
- applyFont(font.value);
3703
+ applyFont(null);
3673
3704
  },
3674
3705
  className: [
3675
3706
  "w-full flex items-center justify-between px-3 py-1.5 text-sm transition-colors",
3676
- isActive ? "bg-blue-50 text-blue-700 font-medium" : "text-gray-700 hover:bg-gray-100 "
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"
3677
3708
  ].join(" "),
3678
3709
  children: [
3679
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontFamily: font.value }, children: font.label }),
3680
- isActive && /* @__PURE__ */ jsxRuntime.jsx(CheckIcon3, {})
3710
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Default" }),
3711
+ activeFontFamily === null && /* @__PURE__ */ jsxRuntime.jsx(CheckIcon3, {})
3681
3712
  ]
3682
- },
3683
- font.value
3684
- );
3685
- })
3686
- ]
3687
- }
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
3688
3744
  )
3689
3745
  ] });
3690
3746
  }
@@ -3730,11 +3786,15 @@ function BackgroundColorDropdown({
3730
3786
  const [lastColor, setLastColor] = react.useState(PRESET_COLORS[6].value);
3731
3787
  const [hexInput, setHexInput] = react.useState("");
3732
3788
  const [hexError, setHexError] = react.useState(false);
3733
- const containerRef = react.useRef(null);
3789
+ const [popupPos, setPopupPos] = react.useState({ top: 0, left: 0 });
3790
+ const chevronRef = react.useRef(null);
3791
+ const popupRef = react.useRef(null);
3734
3792
  react.useEffect(() => {
3735
3793
  if (!open) return;
3736
3794
  const handler = (e) => {
3737
- if (containerRef.current && !containerRef.current.contains(e.target))
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)))
3738
3798
  setOpen(false);
3739
3799
  };
3740
3800
  document.addEventListener("mousedown", handler);
@@ -3777,7 +3837,7 @@ function BackgroundColorDropdown({
3777
3837
  (c) => !PRESET_COLORS.some((p) => normalizeHex(p.value) === normalizeHex(c))
3778
3838
  );
3779
3839
  const normActive = activeColor ? normalizeHex(activeColor) : null;
3780
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative flex items-center", children: [
3840
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center", children: [
3781
3841
  /* @__PURE__ */ jsxRuntime.jsxs(
3782
3842
  "button",
3783
3843
  {
@@ -3787,7 +3847,7 @@ function BackgroundColorDropdown({
3787
3847
  e.preventDefault();
3788
3848
  handleQuickApply();
3789
3849
  },
3790
- 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",
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",
3791
3851
  children: [
3792
3852
  /* @__PURE__ */ jsxRuntime.jsx(BgColorIcon, {}),
3793
3853
  /* @__PURE__ */ jsxRuntime.jsx(ColorBar, { color: normActive != null ? normActive : lastColor })
@@ -3797,140 +3857,150 @@ function BackgroundColorDropdown({
3797
3857
  /* @__PURE__ */ jsxRuntime.jsx(
3798
3858
  "button",
3799
3859
  {
3860
+ ref: chevronRef,
3800
3861
  type: "button",
3801
3862
  title: "More background colors",
3802
3863
  "aria-haspopup": "listbox",
3803
3864
  "aria-expanded": open,
3804
3865
  onMouseDown: (e) => {
3805
3866
  e.preventDefault();
3806
- setOpen((p) => {
3807
- if (!p) {
3808
- setHexInput("");
3809
- setHexError(false);
3810
- }
3811
- return !p;
3812
- });
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);
3813
3878
  },
3814
3879
  className: [
3815
- "flex items-center justify-center w-4 h-8 rounded-r border-l border-gray-200 ",
3880
+ "flex items-center justify-center w-4 h-8 rounded-r border-l border-gray-200 dark:border-gray-600",
3816
3881
  "focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-500",
3817
3882
  "transition-colors",
3818
- open ? "bg-gray-100 text-gray-700 " : "text-gray-400 hover:bg-gray-100 "
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"
3819
3884
  ].join(" "),
3820
3885
  children: /* @__PURE__ */ jsxRuntime.jsx(ChevronDownIcon4, {})
3821
3886
  }
3822
3887
  ),
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\n bg-white \n border border-gray-200 \n rounded-lg shadow-xl\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\n text-gray-600 \n rounded hover:bg-gray-100 \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
- ),
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: [
3878
3898
  /* @__PURE__ */ jsxRuntime.jsxs(
3879
- "div",
3880
- {
3881
- className: "flex flex-1 items-center border rounded overflow-hidden\n border-gray-300 \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(
3914
3899
  "button",
3915
3900
  {
3916
3901
  type: "button",
3917
3902
  onMouseDown: (e) => {
3918
3903
  e.preventDefault();
3919
- commitHexInput();
3904
+ applyColor(null);
3920
3905
  },
3921
- className: "shrink-0 px-2 py-1.5 text-xs bg-blue-600 text-white rounded\n hover:bg-blue-700 transition-colors",
3922
- children: "\u2713"
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
+ ]
3923
3911
  }
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
- }
3929
- )
3930
- ] });
3931
- }
3932
- function Swatch({
3933
- color,
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
3999
+ )
4000
+ ] });
4001
+ }
4002
+ function Swatch({
4003
+ color,
3934
4004
  isActive,
3935
4005
  onSelect
3936
4006
  }) {
@@ -3950,7 +4020,7 @@ function Swatch({
3950
4020
  "hover:scale-110 focus:outline-none",
3951
4021
  "focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-blue-500",
3952
4022
  isActive ? "ring-2 ring-offset-1 ring-blue-500 scale-105" : "",
3953
- color.hasBorder ? "border border-gray-300 " : ""
4023
+ color.hasBorder ? "border border-gray-300 dark:border-gray-500" : ""
3954
4024
  ].join(" "),
3955
4025
  style: { backgroundColor: color.value },
3956
4026
  children: isActive && /* @__PURE__ */ jsxRuntime.jsx(CheckIcon4, { light: isLight(color.value) })
@@ -4073,11 +4143,15 @@ function TextColorDropdown({
4073
4143
  const [lastColor, setLastColor] = react.useState(PRESET_COLORS2[0].value);
4074
4144
  const [hexInput, setHexInput] = react.useState("");
4075
4145
  const [hexError, setHexError] = react.useState(false);
4076
- const containerRef = react.useRef(null);
4146
+ const [popupPos, setPopupPos] = react.useState({ top: 0, left: 0 });
4147
+ const chevronRef = react.useRef(null);
4148
+ const popupRef = react.useRef(null);
4077
4149
  react.useEffect(() => {
4078
4150
  if (!open) return;
4079
4151
  const handler = (e) => {
4080
- if (containerRef.current && !containerRef.current.contains(e.target))
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)))
4081
4155
  setOpen(false);
4082
4156
  };
4083
4157
  document.addEventListener("mousedown", handler);
@@ -4117,7 +4191,7 @@ function TextColorDropdown({
4117
4191
  (c) => !PRESET_COLORS2.some((p) => normalizeHex2(p.value) === normalizeHex2(c))
4118
4192
  );
4119
4193
  const normActive = activeColor ? normalizeHex2(activeColor) : null;
4120
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative flex items-center", children: [
4194
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center", children: [
4121
4195
  /* @__PURE__ */ jsxRuntime.jsxs(
4122
4196
  "button",
4123
4197
  {
@@ -4127,7 +4201,7 @@ function TextColorDropdown({
4127
4201
  e.preventDefault();
4128
4202
  handleQuickApply();
4129
4203
  },
4130
- 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",
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",
4131
4205
  children: [
4132
4206
  /* @__PURE__ */ jsxRuntime.jsx(TextColorIcon, {}),
4133
4207
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -4143,129 +4217,139 @@ function TextColorDropdown({
4143
4217
  /* @__PURE__ */ jsxRuntime.jsx(
4144
4218
  "button",
4145
4219
  {
4220
+ ref: chevronRef,
4146
4221
  type: "button",
4147
4222
  title: "More text colors",
4148
4223
  "aria-haspopup": "listbox",
4149
4224
  "aria-expanded": open,
4150
4225
  onMouseDown: (e) => {
4151
4226
  e.preventDefault();
4152
- setOpen((p) => {
4153
- if (!p) {
4154
- setHexInput("");
4155
- setHexError(false);
4156
- }
4157
- return !p;
4158
- });
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);
4159
4238
  },
4160
4239
  className: [
4161
- "flex items-center justify-center w-4 h-8 rounded-r border-l border-gray-200 ",
4240
+ "flex items-center justify-center w-4 h-8 rounded-r border-l border-gray-200 dark:border-gray-600",
4162
4241
  "focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-500",
4163
4242
  "transition-colors",
4164
- open ? "bg-gray-100 text-gray-700 " : "text-gray-400 hover:bg-gray-100 "
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"
4165
4244
  ].join(" "),
4166
4245
  children: /* @__PURE__ */ jsxRuntime.jsx(ChevronDownIcon5, {})
4167
4246
  }
4168
4247
  ),
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\n bg-white \n border border-gray-200 \n rounded-lg shadow-xl\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\n text-gray-600 \n rounded hover:bg-gray-100 \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(
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(
4195
4288
  Swatch2,
4196
4289
  {
4197
- color: { label: c, value: c },
4198
- isActive: normActive === normalizeHex2(c),
4290
+ color: c,
4291
+ isActive: normActive === normalizeHex2(c.value),
4199
4292
  onSelect: applyColor
4200
4293
  },
4201
- c
4294
+ c.value
4202
4295
  )) }),
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\n border-gray-300 \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: "#" }),
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: [
4226
4299
  /* @__PURE__ */ jsxRuntime.jsx(
4227
- "input",
4300
+ "div",
4228
4301
  {
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);
4235
- },
4236
- onKeyDown: (e) => {
4237
- if (e.key === "Enter") {
4238
- e.preventDefault();
4239
- commitHexInput();
4240
- }
4241
- if (e.key === "Escape") setOpen(false);
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
+ ] }),
4335
+ /* @__PURE__ */ jsxRuntime.jsx(
4336
+ "button",
4337
+ {
4338
+ type: "button",
4339
+ onMouseDown: (e) => {
4340
+ e.preventDefault();
4341
+ commitHexInput();
4242
4342
  },
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"
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"
4250
4345
  }
4251
4346
  )
4252
4347
  ] }),
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\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
- }
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
4269
4353
  )
4270
4354
  ] });
4271
4355
  }
@@ -4290,7 +4374,7 @@ function Swatch2({
4290
4374
  "hover:scale-110 focus:outline-none",
4291
4375
  "focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-blue-500",
4292
4376
  isActive ? "ring-2 ring-offset-1 ring-blue-500 scale-105" : "",
4293
- color.hasBorder ? "border border-gray-300 " : ""
4377
+ color.hasBorder ? "border border-gray-300 dark:border-gray-500" : ""
4294
4378
  ].join(" "),
4295
4379
  style: { backgroundColor: color.value },
4296
4380
  children: isActive && /* @__PURE__ */ jsxRuntime.jsx(CheckIcon5, { light: isLight2(color.value) })
@@ -5822,7 +5906,9 @@ function CodeBlockButton({ engine }) {
5822
5906
  const activeBlock = getActiveBlockType(state.doc, state.selection);
5823
5907
  const isActive = activeBlock === "code_block";
5824
5908
  const [open, setOpen] = react.useState(false);
5825
- const containerRef = react.useRef(null);
5909
+ const [popupPos, setPopupPos] = react.useState({ top: 0, left: 0 });
5910
+ const chevronRef = react.useRef(null);
5911
+ const popupRef = react.useRef(null);
5826
5912
  const currentLang = (() => {
5827
5913
  var _a2;
5828
5914
  if (!isActive || !state.selection) return "plaintext";
@@ -5835,9 +5921,9 @@ function CodeBlockButton({ engine }) {
5835
5921
  react.useEffect(() => {
5836
5922
  if (!open) return;
5837
5923
  const handler = (e) => {
5838
- if (containerRef.current && !containerRef.current.contains(e.target)) {
5839
- closeDropdown();
5840
- }
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();
5841
5927
  };
5842
5928
  document.addEventListener("mousedown", handler);
5843
5929
  return () => document.removeEventListener("mousedown", handler);
@@ -5864,7 +5950,7 @@ function CodeBlockButton({ engine }) {
5864
5950
  setOpen(false);
5865
5951
  };
5866
5952
  const activeLang = LANGUAGES.find((l) => l.value === currentLang);
5867
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative flex items-center", children: [
5953
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center", children: [
5868
5954
  /* @__PURE__ */ jsxRuntime.jsx(
5869
5955
  "button",
5870
5956
  {
@@ -5876,7 +5962,7 @@ function CodeBlockButton({ engine }) {
5876
5962
  },
5877
5963
  className: [
5878
5964
  "flex items-center justify-center w-8 h-8 rounded-l text-sm transition-colors",
5879
- isActive ? "bg-blue-100 text-blue-700 " : "text-gray-700 hover:bg-gray-100 "
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"
5880
5966
  ].join(" "),
5881
5967
  children: /* @__PURE__ */ jsxRuntime.jsx(CodeBlockIcon, {})
5882
5968
  }
@@ -5884,55 +5970,69 @@ function CodeBlockButton({ engine }) {
5884
5970
  /* @__PURE__ */ jsxRuntime.jsx(
5885
5971
  "button",
5886
5972
  {
5973
+ ref: chevronRef,
5887
5974
  type: "button",
5888
5975
  title: isActive ? `Language: ${(_a = activeLang == null ? void 0 : activeLang.label) != null ? _a : "Plain text"}` : "Select language",
5889
5976
  "aria-haspopup": "listbox",
5890
5977
  "aria-expanded": open,
5891
5978
  onMouseDown: (e) => {
5892
5979
  e.preventDefault();
5893
- setOpen((p) => !p);
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);
5894
5989
  },
5895
5990
  className: [
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 "
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"
5898
5993
  ].join(" "),
5899
5994
  children: /* @__PURE__ */ jsxRuntime.jsx(ChevronIcon, {})
5900
5995
  }
5901
5996
  ),
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);
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
+ ]
5921
6028
  },
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
- }
6029
+ lang.value
6030
+ );
6031
+ })
6032
+ ]
6033
+ }
6034
+ ),
6035
+ document.body
5936
6036
  )
5937
6037
  ] });
5938
6038
  }
@@ -6262,12 +6362,16 @@ function HeadingDropdown({
6262
6362
  }) {
6263
6363
  var _a;
6264
6364
  const [open, setOpen] = react.useState(false);
6265
- const containerRef = react.useRef(null);
6365
+ const [popupPos, setPopupPos] = react.useState({ top: 0, left: 0 });
6366
+ const buttonRef = react.useRef(null);
6367
+ const popupRef = react.useRef(null);
6266
6368
  const run = (command) => command(engine);
6267
6369
  react.useEffect(() => {
6268
6370
  if (!open) return;
6269
6371
  const handler = (e) => {
6270
- if (containerRef.current && !containerRef.current.contains(e.target))
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)))
6271
6375
  setOpen(false);
6272
6376
  };
6273
6377
  document.addEventListener("mousedown", handler);
@@ -6285,14 +6389,23 @@ function HeadingDropdown({
6285
6389
  code_block: "Code"
6286
6390
  };
6287
6391
  const label = (_a = BLOCK_LABELS[activeBlock]) != null ? _a : "Paragraph";
6288
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative", children: [
6392
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6289
6393
  /* @__PURE__ */ jsxRuntime.jsxs(
6290
6394
  "button",
6291
6395
  {
6396
+ ref: buttonRef,
6292
6397
  type: "button",
6293
6398
  onMouseDown: (e) => {
6294
6399
  e.preventDefault();
6295
- setOpen((v) => !v);
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);
6296
6409
  },
6297
6410
  className: "flex items-center gap-1 px-2 h-8 rounded text-sm font-medium text-gray-700 hover:bg-gray-100 cursor-pointer",
6298
6411
  children: [
@@ -6301,7 +6414,7 @@ function HeadingDropdown({
6301
6414
  ]
6302
6415
  }
6303
6416
  ),
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: [
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: [
6305
6418
  {
6306
6419
  label: "Paragraph",
6307
6420
  cmd: setParagraph,
@@ -6353,7 +6466,7 @@ function HeadingDropdown({
6353
6466
  children: label2
6354
6467
  },
6355
6468
  label2
6356
- )) })
6469
+ )) }), document.body)
6357
6470
  ] });
6358
6471
  }
6359
6472
  function LinkButton({
@@ -6368,10 +6481,16 @@ function LinkButton({
6368
6481
  const [editMode, setEditMode] = react.useState(false);
6369
6482
  const [url, setUrl] = react.useState("");
6370
6483
  const [displayText, setDisplayText] = react.useState("");
6484
+ const [popupPos, setPopupPos] = react.useState({ top: 0, left: 0 });
6371
6485
  const containerRef = react.useRef(null);
6486
+ const popupRef = react.useRef(null);
6372
6487
  const urlInputRef = react.useRef(null);
6373
6488
  const textInputRef = react.useRef(null);
6374
6489
  const openPopup = () => {
6490
+ if (containerRef.current) {
6491
+ const rect = containerRef.current.getBoundingClientRect();
6492
+ setPopupPos({ top: rect.bottom + 4, left: rect.left });
6493
+ }
6375
6494
  if (isActive) {
6376
6495
  setUrl(activeHref != null ? activeHref : "");
6377
6496
  setDisplayText("");
@@ -6403,7 +6522,9 @@ function LinkButton({
6403
6522
  react.useEffect(() => {
6404
6523
  if (!open) return;
6405
6524
  const handler = (e) => {
6406
- if (containerRef.current && !containerRef.current.contains(e.target)) {
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))) {
6407
6528
  closePopup();
6408
6529
  }
6409
6530
  };
@@ -6443,7 +6564,7 @@ function LinkButton({
6443
6564
  setEditMode(true);
6444
6565
  };
6445
6566
  const showTextInput = !hasTextSelected && !isActive;
6446
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative", children: [
6567
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, children: [
6447
6568
  /* @__PURE__ */ jsxRuntime.jsx(
6448
6569
  ToolbarButton,
6449
6570
  {
@@ -6454,115 +6575,43 @@ function LinkButton({
6454
6575
  icon: /* @__PURE__ */ jsxRuntime.jsx(LinkIcon, {})
6455
6576
  }
6456
6577
  ),
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" }),
6510
- /* @__PURE__ */ jsxRuntime.jsx(
6511
- "input",
6512
- {
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"
6527
- }
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" }),
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: [
6532
6590
  /* @__PURE__ */ jsxRuntime.jsx(
6533
- "input",
6591
+ "a",
6534
6592
  {
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"
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
6548
6599
  }
6549
- )
6550
- ] }),
6551
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
6600
+ ),
6552
6601
  /* @__PURE__ */ jsxRuntime.jsx(
6553
6602
  "button",
6554
6603
  {
6555
6604
  type: "button",
6556
6605
  onMouseDown: (e) => {
6557
6606
  e.preventDefault();
6558
- handleApply();
6607
+ handleStartEdit();
6559
6608
  },
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"
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"
6563
6612
  }
6564
6613
  ),
6565
- isActive && /* @__PURE__ */ jsxRuntime.jsx(
6614
+ /* @__PURE__ */ jsxRuntime.jsx(
6566
6615
  "button",
6567
6616
  {
6568
6617
  type: "button",
@@ -6570,27 +6619,104 @@ function LinkButton({
6570
6619
  e.preventDefault();
6571
6620
  handleRemove();
6572
6621
  },
6573
- className: "px-3 py-1.5 text-sm text-red-600 border border-red-200 rounded hover:bg-red-50 ",
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",
6574
6624
  children: "Remove"
6575
6625
  }
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
- }
6589
6626
  )
6590
6627
  ] })
6591
- ] })
6592
- )
6593
- }
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
6594
6720
  )
6595
6721
  ] });
6596
6722
  }
@@ -8629,6 +8755,53 @@ function prettyPrintHTML(html) {
8629
8755
  }
8630
8756
 
8631
8757
  // src/editor/table/TablePlugin.ts
8758
+ function getCellDOMElement(cellPathJson) {
8759
+ return document.querySelector(`[data-block-path='${cellPathJson}']`);
8760
+ }
8761
+ function isCaretAtContainerStart(container) {
8762
+ const sel = window.getSelection();
8763
+ if (!sel || sel.rangeCount === 0 || !sel.isCollapsed) return false;
8764
+ const range = sel.getRangeAt(0);
8765
+ if (range.startOffset !== 0) return false;
8766
+ let node = range.startContainer;
8767
+ while (node && node !== container) {
8768
+ if (node.previousSibling) return false;
8769
+ node = node.parentNode;
8770
+ }
8771
+ return node === container;
8772
+ }
8773
+ function isCaretAtContainerEnd(container) {
8774
+ var _a, _b;
8775
+ const sel = window.getSelection();
8776
+ if (!sel || sel.rangeCount === 0 || !sel.isCollapsed) return false;
8777
+ const range = sel.getRangeAt(0);
8778
+ const endNode = range.endContainer;
8779
+ const endOffset = range.endOffset;
8780
+ const len = endNode.nodeType === Node.TEXT_NODE ? (_b = (_a = endNode.textContent) == null ? void 0 : _a.length) != null ? _b : 0 : endNode.childNodes.length;
8781
+ if (endOffset !== len) return false;
8782
+ let node = endNode;
8783
+ while (node && node !== container) {
8784
+ if (node.nextSibling) return false;
8785
+ node = node.parentNode;
8786
+ }
8787
+ return node === container;
8788
+ }
8789
+ function isCaretOnEdgeLine(container, direction) {
8790
+ const sel = window.getSelection();
8791
+ if (!sel || sel.rangeCount === 0 || !sel.isCollapsed) return false;
8792
+ const caretRange = sel.getRangeAt(0).cloneRange();
8793
+ caretRange.collapse(true);
8794
+ const caretRects = caretRange.getClientRects();
8795
+ if (!caretRects.length) return true;
8796
+ const caretRect = caretRects[0];
8797
+ const containerRect = container.getBoundingClientRect();
8798
+ const lineHeight = parseFloat(getComputedStyle(container).lineHeight) || 20;
8799
+ if (direction === "up") {
8800
+ return caretRect.top < containerRect.top + lineHeight;
8801
+ } else {
8802
+ return caretRect.bottom > containerRect.bottom - lineHeight;
8803
+ }
8804
+ }
8632
8805
  var TablePlugin = {
8633
8806
  name: "table",
8634
8807
  keyBindings: {
@@ -8679,6 +8852,125 @@ var TablePlugin = {
8679
8852
  engine.dispatch(tr);
8680
8853
  return true;
8681
8854
  },
8855
+ "ArrowRight": (engine) => {
8856
+ var _a;
8857
+ const state = engine.getState();
8858
+ const sel = state.selection;
8859
+ if (!sel) return false;
8860
+ const cellPos = findCellPosition(state.doc, sel.anchor.path);
8861
+ if (!cellPos) return false;
8862
+ const { tablePath, row, col } = cellPos;
8863
+ const cellPath = [...tablePath, row, col];
8864
+ const cellEl = getCellDOMElement(JSON.stringify(cellPath));
8865
+ if (!cellEl || !isCaretAtContainerEnd(cellEl)) return false;
8866
+ const table = getNodeAtPath(state.doc, tablePath);
8867
+ const { rows, cols } = getTableDimensions(table);
8868
+ let nextRow = row;
8869
+ let nextCol = col + 1;
8870
+ while (nextRow < rows) {
8871
+ if (nextCol >= cols) {
8872
+ nextCol = 0;
8873
+ nextRow++;
8874
+ continue;
8875
+ }
8876
+ const c = getNodeAtPath(state.doc, [...tablePath, nextRow, nextCol]);
8877
+ if (!((_a = c == null ? void 0 : c.attrs) == null ? void 0 : _a.covered)) break;
8878
+ nextCol++;
8879
+ }
8880
+ if (nextRow >= rows) return true;
8881
+ const pos = getCellFirstPosition(state.doc, tablePath, nextRow, nextCol);
8882
+ if (!pos) return false;
8883
+ const tr = createTransaction();
8884
+ tr.steps.push(tr_setSelection(makeCollapsedSelection(pos)));
8885
+ engine.dispatch(tr);
8886
+ return true;
8887
+ },
8888
+ "ArrowLeft": (engine) => {
8889
+ var _a;
8890
+ const state = engine.getState();
8891
+ const sel = state.selection;
8892
+ if (!sel) return false;
8893
+ const cellPos = findCellPosition(state.doc, sel.anchor.path);
8894
+ if (!cellPos) return false;
8895
+ const { tablePath, row, col } = cellPos;
8896
+ const cellPath = [...tablePath, row, col];
8897
+ const cellEl = getCellDOMElement(JSON.stringify(cellPath));
8898
+ if (!cellEl || !isCaretAtContainerStart(cellEl)) return false;
8899
+ const table = getNodeAtPath(state.doc, tablePath);
8900
+ const { cols } = getTableDimensions(table);
8901
+ let prevRow = row;
8902
+ let prevCol = col - 1;
8903
+ while (prevRow >= 0) {
8904
+ if (prevCol < 0) {
8905
+ prevCol = cols - 1;
8906
+ prevRow--;
8907
+ continue;
8908
+ }
8909
+ const c = getNodeAtPath(state.doc, [...tablePath, prevRow, prevCol]);
8910
+ if (!((_a = c == null ? void 0 : c.attrs) == null ? void 0 : _a.covered)) break;
8911
+ prevCol--;
8912
+ }
8913
+ if (prevRow < 0) return true;
8914
+ const pos = getCellLastPosition(state.doc, tablePath, prevRow, prevCol);
8915
+ if (!pos) return false;
8916
+ const tr = createTransaction();
8917
+ tr.steps.push(tr_setSelection(makeCollapsedSelection(pos)));
8918
+ engine.dispatch(tr);
8919
+ return true;
8920
+ },
8921
+ "ArrowDown": (engine) => {
8922
+ var _a;
8923
+ const state = engine.getState();
8924
+ const sel = state.selection;
8925
+ if (!sel) return false;
8926
+ const cellPos = findCellPosition(state.doc, sel.anchor.path);
8927
+ if (!cellPos) return false;
8928
+ const { tablePath, row, col } = cellPos;
8929
+ const cellPath = [...tablePath, row, col];
8930
+ const cellEl = getCellDOMElement(JSON.stringify(cellPath));
8931
+ if (!cellEl || !isCaretOnEdgeLine(cellEl, "down")) return false;
8932
+ const table = getNodeAtPath(state.doc, tablePath);
8933
+ const { rows, cols } = getTableDimensions(table);
8934
+ let nextRow = row + 1;
8935
+ while (nextRow < rows) {
8936
+ const c = getNodeAtPath(state.doc, [...tablePath, nextRow, col]);
8937
+ if (c && !((_a = c.attrs) == null ? void 0 : _a.covered)) break;
8938
+ nextRow++;
8939
+ }
8940
+ if (nextRow >= rows) return false;
8941
+ const pos = getCellFirstPosition(state.doc, tablePath, nextRow, col < cols ? col : 0);
8942
+ if (!pos) return false;
8943
+ const tr = createTransaction();
8944
+ tr.steps.push(tr_setSelection(makeCollapsedSelection(pos)));
8945
+ engine.dispatch(tr);
8946
+ return true;
8947
+ },
8948
+ "ArrowUp": (engine) => {
8949
+ var _a;
8950
+ const state = engine.getState();
8951
+ const sel = state.selection;
8952
+ if (!sel) return false;
8953
+ const cellPos = findCellPosition(state.doc, sel.anchor.path);
8954
+ if (!cellPos) return false;
8955
+ const { tablePath, row, col } = cellPos;
8956
+ const cellPath = [...tablePath, row, col];
8957
+ const cellEl = getCellDOMElement(JSON.stringify(cellPath));
8958
+ if (!cellEl || !isCaretOnEdgeLine(cellEl, "up")) return false;
8959
+ let prevRow = row - 1;
8960
+ while (prevRow >= 0) {
8961
+ const c = getNodeAtPath(state.doc, [...tablePath, prevRow, col]);
8962
+ if (c && !((_a = c.attrs) == null ? void 0 : _a.covered)) break;
8963
+ prevRow--;
8964
+ }
8965
+ if (prevRow < 0) return false;
8966
+ const { cols } = getTableDimensions(getNodeAtPath(state.doc, tablePath));
8967
+ const pos = getCellLastPosition(state.doc, tablePath, prevRow, col < cols ? col : 0);
8968
+ if (!pos) return false;
8969
+ const tr = createTransaction();
8970
+ tr.steps.push(tr_setSelection(makeCollapsedSelection(pos)));
8971
+ engine.dispatch(tr);
8972
+ return true;
8973
+ },
8682
8974
  "Shift+Tab": (engine) => {
8683
8975
  var _a;
8684
8976
  const state = engine.getState();