@pubwave/editor 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -600,32 +600,15 @@ const customCommands: SlashCommand[] = [
600
600
 
601
601
  ---
602
602
 
603
- ## ⚡ SSR Integration (Next.js)
604
603
 
605
- The library is SSR-safe and can be imported server-side without errors. The editor component itself renders client-side only.
606
604
 
607
- ```tsx
608
- 'use client';
609
-
610
- import { PubwaveEditor } from '@pubwave/editor';
611
- import '@pubwave/editor/style.css';
612
-
613
- export default function EditorComponent() {
614
- return <PubwaveEditor />;
615
- }
616
- ```
617
605
 
618
- ---
619
606
 
620
607
 
621
608
  ## 🐛 Troubleshooting
622
609
 
623
610
  ### Common Issues
624
611
 
625
- **Q: The editor doesn't render in Next.js SSR**
626
- - Make sure you're using `'use client'` directive
627
- - Ensure the component is marked as a client component
628
-
629
612
  **Q: Styles are not applied**
630
613
  - Ensure you've imported the CSS: `import '@pubwave/editor/style.css'`
631
614
  - Check that CSS custom properties are defined
@@ -650,11 +633,6 @@ export default function EditorComponent() {
650
633
 
651
634
  ---
652
635
 
653
- ## 🤝 Contributing
654
-
655
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup and guidelines.
656
-
657
- ---
658
636
 
659
637
  ## 📄 License
660
638
 
package/dist/index.cjs CHANGED
@@ -463,6 +463,13 @@ function safeRequestAnimationFrame(callback) {
463
463
  }
464
464
  return 0;
465
465
  }
466
+ function isMobileDevice() {
467
+ if (!canUseDOM) return false;
468
+ const hasTouchSupport = "ontouchstart" in window || navigator.maxTouchPoints > 0;
469
+ const hasCoarsePointer = window.matchMedia("(pointer: coarse)").matches;
470
+ const hasHover = window.matchMedia("(hover: hover)").matches;
471
+ return hasTouchSupport && hasCoarsePointer || hasTouchSupport && !hasHover;
472
+ }
466
473
  function TextIcon() {
467
474
  return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 7V4h16v3M9 20h6M12 4v16" }) });
468
475
  }
@@ -4847,6 +4854,89 @@ function calculateVerticalPositionFromRect(triggerRect, dropdownHeight, margin =
4847
4854
  margin
4848
4855
  );
4849
4856
  }
4857
+ function calculateHorizontalPosition(triggerLeft, triggerRight, dropdownWidth, preferredAlign = "left", margin = 8) {
4858
+ const viewportWidth = window.innerWidth;
4859
+ const triggerCenter = (triggerLeft + triggerRight) / 2;
4860
+ const spaceLeft = triggerLeft;
4861
+ const spaceRight = viewportWidth - triggerRight;
4862
+ const requiredSpaceForLeft = dropdownWidth + margin;
4863
+ const requiredSpaceForRight = dropdownWidth + margin;
4864
+ const requiredSpaceForCenter = dropdownWidth / 2 + margin;
4865
+ let align = preferredAlign;
4866
+ let left2;
4867
+ if (preferredAlign === "left") {
4868
+ left2 = triggerLeft;
4869
+ if (left2 < margin || left2 + dropdownWidth > viewportWidth - margin) {
4870
+ if (spaceRight >= requiredSpaceForRight && triggerRight + dropdownWidth <= viewportWidth - margin) {
4871
+ align = "right";
4872
+ left2 = triggerRight - dropdownWidth;
4873
+ } else if (spaceLeft >= requiredSpaceForCenter && triggerCenter - dropdownWidth / 2 >= margin) {
4874
+ align = "center";
4875
+ left2 = triggerCenter - dropdownWidth / 2;
4876
+ } else {
4877
+ if (spaceRight >= spaceLeft) {
4878
+ align = "right";
4879
+ left2 = triggerRight - dropdownWidth;
4880
+ } else {
4881
+ align = "left";
4882
+ left2 = triggerLeft;
4883
+ }
4884
+ left2 = Math.max(margin, Math.min(left2, viewportWidth - dropdownWidth - margin));
4885
+ }
4886
+ }
4887
+ } else if (preferredAlign === "center") {
4888
+ left2 = triggerCenter - dropdownWidth / 2;
4889
+ if (left2 < margin || left2 + dropdownWidth > viewportWidth - margin) {
4890
+ if (spaceRight >= requiredSpaceForRight && triggerRight + dropdownWidth <= viewportWidth - margin) {
4891
+ align = "right";
4892
+ left2 = triggerRight - dropdownWidth;
4893
+ } else if (spaceLeft >= requiredSpaceForLeft && triggerLeft + dropdownWidth <= viewportWidth - margin) {
4894
+ align = "left";
4895
+ left2 = triggerLeft;
4896
+ } else {
4897
+ if (spaceRight >= spaceLeft) {
4898
+ align = "right";
4899
+ left2 = triggerRight - dropdownWidth;
4900
+ } else {
4901
+ align = "left";
4902
+ left2 = triggerLeft;
4903
+ }
4904
+ left2 = Math.max(margin, Math.min(left2, viewportWidth - dropdownWidth - margin));
4905
+ }
4906
+ }
4907
+ } else {
4908
+ left2 = triggerRight - dropdownWidth;
4909
+ if (left2 < margin || left2 + dropdownWidth > viewportWidth - margin) {
4910
+ if (spaceLeft >= requiredSpaceForLeft && triggerLeft + dropdownWidth <= viewportWidth - margin) {
4911
+ align = "left";
4912
+ left2 = triggerLeft;
4913
+ } else if (spaceLeft >= requiredSpaceForCenter && triggerCenter - dropdownWidth / 2 >= margin) {
4914
+ align = "center";
4915
+ left2 = triggerCenter - dropdownWidth / 2;
4916
+ } else {
4917
+ if (spaceLeft >= spaceRight) {
4918
+ align = "left";
4919
+ left2 = triggerLeft;
4920
+ } else {
4921
+ align = "right";
4922
+ left2 = triggerRight - dropdownWidth;
4923
+ }
4924
+ left2 = Math.max(margin, Math.min(left2, viewportWidth - dropdownWidth - margin));
4925
+ }
4926
+ }
4927
+ }
4928
+ left2 = Math.max(margin, Math.min(left2, viewportWidth - dropdownWidth - margin));
4929
+ return { align, left: left2 };
4930
+ }
4931
+ function calculateHorizontalPositionFromRect(triggerRect, dropdownWidth, preferredAlign = "left", margin = 8) {
4932
+ return calculateHorizontalPosition(
4933
+ triggerRect.left,
4934
+ triggerRect.right,
4935
+ dropdownWidth,
4936
+ preferredAlign,
4937
+ margin
4938
+ );
4939
+ }
4850
4940
  function PositionedDropdown({
4851
4941
  isOpen,
4852
4942
  buttonRef,
@@ -4859,7 +4949,8 @@ function PositionedDropdown({
4859
4949
  onClick,
4860
4950
  "data-testid": dataTestId
4861
4951
  }) {
4862
- const [position, setPosition] = React.useState("top");
4952
+ const [verticalPosition, setVerticalPosition] = React.useState("top");
4953
+ const [horizontalPosition, setHorizontalPosition] = React.useState(null);
4863
4954
  const dropdownRef = React.useRef(null);
4864
4955
  React.useEffect(() => {
4865
4956
  if (!isOpen || !buttonRef.current) return;
@@ -4867,15 +4958,37 @@ function PositionedDropdown({
4867
4958
  if (!dropdownRef.current || !buttonRef.current) return;
4868
4959
  const buttonRect = buttonRef.current.getBoundingClientRect();
4869
4960
  const dropdownHeight = dropdownRef.current.offsetHeight || 300;
4870
- const newPosition = calculateVerticalPositionFromRect(buttonRect, dropdownHeight, margin);
4871
- setPosition(newPosition);
4961
+ const dropdownWidth = dropdownRef.current.offsetWidth || 240;
4962
+ const newVerticalPosition = calculateVerticalPositionFromRect(buttonRect, dropdownHeight, margin);
4963
+ setVerticalPosition(newVerticalPosition);
4964
+ let parentContainer = buttonRef.current.parentElement;
4965
+ while (parentContainer) {
4966
+ const style2 = window.getComputedStyle(parentContainer);
4967
+ if (style2.position === "relative" || style2.position === "absolute" || style2.position === "fixed") {
4968
+ break;
4969
+ }
4970
+ parentContainer = parentContainer.parentElement;
4971
+ }
4972
+ const horizontalPos = calculateHorizontalPositionFromRect(
4973
+ buttonRect,
4974
+ dropdownWidth,
4975
+ align,
4976
+ margin
4977
+ );
4978
+ if (parentContainer) {
4979
+ const parentRect = parentContainer.getBoundingClientRect();
4980
+ const relativeLeft = horizontalPos.left - parentRect.left;
4981
+ setHorizontalPosition({ align: horizontalPos.align, left: relativeLeft });
4982
+ } else {
4983
+ setHorizontalPosition(horizontalPos);
4984
+ }
4872
4985
  };
4873
4986
  checkPosition();
4874
4987
  const rafId = requestAnimationFrame(() => {
4875
4988
  checkPosition();
4876
4989
  });
4877
4990
  return () => cancelAnimationFrame(rafId);
4878
- }, [isOpen, buttonRef, margin]);
4991
+ }, [isOpen, buttonRef, margin, align]);
4879
4992
  React.useEffect(() => {
4880
4993
  if (!isOpen || !onClickOutside2) return;
4881
4994
  const handleClickOutside = (event) => {
@@ -4890,6 +5003,11 @@ function PositionedDropdown({
4890
5003
  }, [isOpen, onClickOutside2, buttonRef]);
4891
5004
  if (!isOpen) return null;
4892
5005
  const getHorizontalStyle = () => {
5006
+ if (horizontalPosition) {
5007
+ return {
5008
+ left: `${horizontalPosition.left}px`
5009
+ };
5010
+ }
4893
5011
  switch (align) {
4894
5012
  case "center":
4895
5013
  return {
@@ -4915,7 +5033,7 @@ function PositionedDropdown({
4915
5033
  "data-testid": dataTestId,
4916
5034
  style: {
4917
5035
  position: "absolute",
4918
- ...position === "top" ? {
5036
+ ...verticalPosition === "top" ? {
4919
5037
  bottom: "100%",
4920
5038
  marginBottom: `${margin}px`
4921
5039
  } : {
@@ -5985,8 +6103,6 @@ function calculatePosition(editor, toolbarEl) {
5985
6103
  const toolbarRect = toolbarEl.getBoundingClientRect();
5986
6104
  const toolbarWidth = toolbarRect.width;
5987
6105
  const toolbarHeight = toolbarRect.height;
5988
- const centerX = (selectionLeft + selectionRight) / 2;
5989
- let left2 = centerX - toolbarWidth / 2;
5990
6106
  const selectionBottom = Math.max(start2.bottom, end2.bottom);
5991
6107
  const verticalPosition = calculateVerticalPosition(
5992
6108
  selectionTop,
@@ -6000,20 +6116,21 @@ function calculatePosition(editor, toolbarEl) {
6000
6116
  } else {
6001
6117
  top2 = selectionBottom + TOOLBAR_OFFSET;
6002
6118
  }
6119
+ const horizontalPos = calculateHorizontalPosition(
6120
+ selectionLeft,
6121
+ selectionRight,
6122
+ toolbarWidth,
6123
+ "center",
6124
+ // Default to center alignment
6125
+ TOOLBAR_OFFSET
6126
+ );
6127
+ const left2 = horizontalPos.left;
6003
6128
  const viewportWidth = window.innerWidth;
6004
6129
  const viewportHeight = window.innerHeight;
6005
- const padding = 8;
6006
- if (left2 < padding) {
6007
- left2 = padding;
6008
- } else if (left2 + toolbarWidth > viewportWidth - padding) {
6009
- left2 = viewportWidth - toolbarWidth - padding;
6010
- }
6011
- if (top2 < padding) {
6012
- top2 = padding;
6013
- } else if (top2 + toolbarHeight > viewportHeight - padding) {
6014
- top2 = viewportHeight - toolbarHeight - padding;
6015
- }
6016
- return { top: top2, left: left2, visible: true };
6130
+ const padding = TOOLBAR_OFFSET;
6131
+ const clampedLeft = Math.max(padding, Math.min(left2, viewportWidth - toolbarWidth - padding));
6132
+ const clampedTop = Math.max(padding, Math.min(top2, viewportHeight - toolbarHeight - padding));
6133
+ return { top: clampedTop, left: clampedLeft, visible: true };
6017
6134
  }
6018
6135
  function BubbleToolbar({
6019
6136
  editor,
@@ -6258,6 +6375,7 @@ function BubbleToolbar({
6258
6375
  }
6259
6376
  );
6260
6377
  }
6378
+ const MIN_WINDOW_WIDTH = 768;
6261
6379
  function getClosestBlock(target, proseMirror, editor) {
6262
6380
  if (!target || !(target instanceof Node)) return null;
6263
6381
  try {
@@ -6302,18 +6420,33 @@ function BlockHandle({ editor }) {
6302
6420
  const [visible, setVisible] = React.useState(false);
6303
6421
  const [isDragging, setIsDragging] = React.useState(false);
6304
6422
  const [position, setPosition] = React.useState({ top: 0 });
6423
+ const [isMobile, setIsMobile] = React.useState(false);
6424
+ const [isWindowTooSmall, setIsWindowTooSmall] = React.useState(false);
6425
+ React.useEffect(() => {
6426
+ const checkDeviceAndWindow = () => {
6427
+ setIsMobile(isMobileDevice());
6428
+ setIsWindowTooSmall(window.innerWidth < MIN_WINDOW_WIDTH);
6429
+ };
6430
+ checkDeviceAndWindow();
6431
+ window.addEventListener("resize", checkDeviceAndWindow);
6432
+ return () => {
6433
+ window.removeEventListener("resize", checkDeviceAndWindow);
6434
+ };
6435
+ }, []);
6305
6436
  const updatePosition = React.useCallback(() => {
6306
6437
  const container = containerRef.current;
6307
6438
  const block = currentBlockRef.current;
6308
6439
  if (!container || !block) return;
6309
6440
  const editorContainer = container.closest(".pubwave-editor");
6310
6441
  if (!editorContainer) return;
6442
+ void editorContainer.offsetHeight;
6311
6443
  const editorRect = editorContainer.getBoundingClientRect();
6312
6444
  let offset2 = 14;
6313
6445
  let target = block;
6314
6446
  if (["UL", "OL", "BLOCKQUOTE"].includes(block.tagName) && block.firstElementChild instanceof HTMLElement) {
6315
6447
  target = block.firstElementChild;
6316
6448
  }
6449
+ void target.offsetHeight;
6317
6450
  const targetRect = target.getBoundingClientRect();
6318
6451
  try {
6319
6452
  const style = window.getComputedStyle(target);
@@ -6332,12 +6465,18 @@ function BlockHandle({ editor }) {
6332
6465
  } catch (e) {
6333
6466
  }
6334
6467
  const top2 = targetRect.top - editorRect.top + offset2;
6335
- setPosition({ top: top2 });
6468
+ if (!isNaN(top2) && isFinite(top2) && top2 >= 0) {
6469
+ setPosition({ top: top2 });
6470
+ }
6336
6471
  }, []);
6337
6472
  const showHandle = React.useCallback((block) => {
6338
6473
  currentBlockRef.current = block;
6339
6474
  setVisible(true);
6340
- setTimeout(updatePosition, 0);
6475
+ requestAnimationFrame(() => {
6476
+ requestAnimationFrame(() => {
6477
+ updatePosition();
6478
+ });
6479
+ });
6341
6480
  }, [updatePosition]);
6342
6481
  const hideHandle = React.useCallback(() => {
6343
6482
  if (!isDraggingRef.current) {
@@ -6399,8 +6538,16 @@ function BlockHandle({ editor }) {
6399
6538
  hideHandle();
6400
6539
  }, 150);
6401
6540
  };
6541
+ const onEditorMouseLeave = () => {
6542
+ if (isDraggingRef.current) return;
6543
+ clearHideTimeout();
6544
+ hideTimeoutRef.current = setTimeout(() => {
6545
+ hideHandle();
6546
+ }, 150);
6547
+ };
6402
6548
  proseMirror.addEventListener("mouseover", onMouseOver);
6403
6549
  proseMirror.addEventListener("mouseout", onMouseOut);
6550
+ editorContainer.addEventListener("mouseleave", onEditorMouseLeave);
6404
6551
  const handleTransaction = ({ transaction }) => {
6405
6552
  if (transaction.docChanged && !isDraggingRef.current) {
6406
6553
  hideHandle();
@@ -6410,10 +6557,20 @@ function BlockHandle({ editor }) {
6410
6557
  return () => {
6411
6558
  proseMirror.removeEventListener("mouseover", onMouseOver);
6412
6559
  proseMirror.removeEventListener("mouseout", onMouseOut);
6560
+ editorContainer.removeEventListener("mouseleave", onEditorMouseLeave);
6413
6561
  editor.off("transaction", handleTransaction);
6414
6562
  clearHideTimeout();
6415
6563
  };
6416
6564
  }, [editor, showHandle, hideHandle, clearHideTimeout]);
6565
+ React.useEffect(() => {
6566
+ if (visible && currentBlockRef.current) {
6567
+ requestAnimationFrame(() => {
6568
+ requestAnimationFrame(() => {
6569
+ updatePosition();
6570
+ });
6571
+ });
6572
+ }
6573
+ }, [visible, updatePosition]);
6417
6574
  React.useEffect(() => {
6418
6575
  if (!visible) return;
6419
6576
  const onScroll = () => {
@@ -6508,7 +6665,7 @@ function BlockHandle({ editor }) {
6508
6665
  }
6509
6666
  setVisible(false);
6510
6667
  }, []);
6511
- if (!visible) return null;
6668
+ if (isMobile || isWindowTooSmall || !visible) return null;
6512
6669
  return /* @__PURE__ */ jsxRuntime.jsxs(
6513
6670
  "div",
6514
6671
  {
@@ -7515,6 +7672,7 @@ exports.handleSelectionClear = handleSelectionClear;
7515
7672
  exports.isEditable = isEditable;
7516
7673
  exports.isEditorValid = isEditorValid;
7517
7674
  exports.isMarkActive = isMarkActive;
7675
+ exports.isMobileDevice = isMobileDevice;
7518
7676
  exports.isNodeActive = isNodeActive;
7519
7677
  exports.isReadOnly = isReadOnly;
7520
7678
  exports.isSSR = isSSR;
package/dist/index.css CHANGED
@@ -1,2 +1,2 @@
1
1
  /*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
2
- @layer properties{@supports ((-webkit-hyphens:none) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.isolate{isolation:isolate}.container{width:100%}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.resize{resize:both}.border{border-style:var(--tw-border-style);border-width:1px}.uppercase{text-transform:uppercase}.italic{font-style:italic}.underline{text-decoration-line:underline}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}:root{--pubwave-bg:#fff;--pubwave-surface:#fff;--pubwave-text:#37352f;--pubwave-text-muted:#9b9a97;--pubwave-border:#e3e2e0;--pubwave-border-light:#f3f4f6;--pubwave-hover:#f7f6f3;--pubwave-focus:#0000000d;--pubwave-selection:#3b82f6;--pubwave-selection-bg:#2383e226;--pubwave-primary:#2383e2;--pubwave-primary-hover:#2563eb;--pubwave-handle-color:#9b9a97;--pubwave-primary-faded:#3b82f61a;--pubwave-drop-indicator:#3b82f6;--pubwave-drop-target:#3b82f60d;--pubwave-error:#ef4444;--pubwave-success:#10b981;--pubwave-warning:#f59e0b;--pubwave-text-secondary:#374151;--pubwave-text-tertiary:#4b5563;--pubwave-hover-bg:#f3f4f6;--pubwave-hover-task:#e8f2ff;--pubwave-checked-text:#9ca3af;--pubwave-spacing-xs:.25rem;--pubwave-spacing-sm:.5rem;--pubwave-spacing-md:1rem;--pubwave-spacing-lg:1.5rem;--pubwave-spacing-xl:2rem;--pubwave-spacing-1:4px;--pubwave-spacing-2:8px;--pubwave-spacing-3:12px;--pubwave-spacing-4:16px;--pubwave-spacing-7:28px;--pubwave-font-family:inherit;--pubwave-font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--pubwave-font-size-xs:11px;--pubwave-font-size-sm:13px;--pubwave-font-size-base:14px;--pubwave-font-size-md:1rem;--pubwave-font-size-lg:1.125rem;--pubwave-font-size-xl:1.25rem;--pubwave-font-size-2xl:1.5rem;--pubwave-font-size-3xl:2rem;--pubwave-line-height:1.625;--pubwave-line-height-heading:1.25;--pubwave-button-width:28px;--pubwave-button-height:28px;--pubwave-icon-size:20px;--pubwave-checkbox-size:20px;--pubwave-checkbox-input-size:16px;--pubwave-divider-width:1px;--pubwave-divider-height:20px;--pubwave-drop-indicator-width:2px;--pubwave-drop-indicator-height:2px;--pubwave-task-item-height:30px;--pubwave-radius-xs:3px;--pubwave-radius-sm:4px;--pubwave-radius:6px;--pubwave-radius-md:6px;--pubwave-radius-lg:8px;--pubwave-shadow-sm:0 1px 2px 0 #0000000d;--pubwave-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;--pubwave-shadow-lg:0 10px 15px -3px #0000001a,0 4px 6px -2px #0000000d}@media (prefers-color-scheme:dark){:root{--pubwave-bg:#1a1a1a;--pubwave-surface:#262626;--pubwave-text:#e5e7eb;--pubwave-text-muted:#9ca3af;--pubwave-border:#374151;--pubwave-border-light:#4b5563;--pubwave-hover:#ffffff0d;--pubwave-focus:#ffffff14;--pubwave-selection:#60a5fa;--pubwave-selection-bg:#60a5fa26;--pubwave-primary:#60a5fa;--pubwave-primary-hover:#3b82f6;--pubwave-primary-faded:#60a5fa26;--pubwave-drop-indicator:#60a5fa;--pubwave-drop-target:#60a5fa14;--pubwave-error:#f87171;--pubwave-text-secondary:#d1d5db;--pubwave-text-tertiary:#9ca3af;--pubwave-hover-bg:#374151;--pubwave-hover-task:#3b82f626;--pubwave-checked-text:#6b7280}}.pubwave-editor{width:var(--pubwave-container-width,100%);max-width:var(--pubwave-container-max-width,none);font-family:var(--pubwave-font-family);color:var(--pubwave-text);background:var(--pubwave-bg,#fff);border-radius:var(--pubwave-container-border-radius,16px);min-height:var(--pubwave-container-min-height,700px);padding:var(--pubwave-container-padding-y,96px)var(--pubwave-container-padding-x,120px);padding-left:var(--pubwave-container-padding-left,140px);position:relative;box-shadow:0 0 0 1px #0000000a,0 20px 25px -5px #0000000d,0 10px 10px -5px #00000005}@media (max-width:768px){.pubwave-editor{padding:var(--pubwave-container-padding-y-mobile,20px)var(--pubwave-container-padding-x-mobile,16px);padding-left:var(--pubwave-container-padding-left-mobile,16px);border-radius:var(--pubwave-container-border-radius-mobile,12px);min-height:var(--pubwave-container-min-height-mobile,400px)}}.pubwave-editor,.pubwave-editor *,.pubwave-editor :before,.pubwave-editor :after{box-sizing:border-box}.pubwave-editor__content{outline:none;flex-direction:column;min-height:1em;display:flex}.pubwave-editor__content .ProseMirror{min-height:100%;padding-bottom:30vh;padding-top:var(--pubwave-spacing-md,1rem);outline:none;flex-grow:1}.pubwave-editor__content .ProseMirror p.is-editor-empty:first-child:before,.pubwave-editor__content.ProseMirror-focused p.pubwave-editor__node--empty:before,.ProseMirror.ProseMirror-focused p.pubwave-editor__node--empty:before{content:attr(data-placeholder);float:left;color:var(--pubwave-text-muted);pointer-events:none;height:0}.pubwave-editor__content .ProseMirror>*+*{margin-top:.75em}@media (max-width:768px){.pubwave-editor__content .ProseMirror>*+*{margin-top:.35em}.pubwave-editor__content .ProseMirror h1+*,.pubwave-editor__content .ProseMirror h2+*,.pubwave-editor__content .ProseMirror h3+*{margin-top:.4em}.pubwave-editor__content .ProseMirror h1,.pubwave-editor__content .ProseMirror h2,.pubwave-editor__content .ProseMirror h3{margin-top:.6em}.pubwave-editor__content .ProseMirror{font-size:var(--pubwave-font-size-base-mobile,15px)}.pubwave-editor__content .ProseMirror h1{font-size:var(--pubwave-font-size-3xl-mobile,1.75rem)}.pubwave-editor__content .ProseMirror h2{font-size:var(--pubwave-font-size-2xl-mobile,1.375rem)}.pubwave-editor__content .ProseMirror h3{font-size:var(--pubwave-font-size-xl-mobile,1.125rem)}}.pubwave-editor__content .pubwave-editor__bullet-list,.pubwave-editor__content .pubwave-editor__ordered-list{padding-left:1.3em!important}.pubwave-editor__content .ProseMirror p{line-height:var(--pubwave-line-height);margin:0}.pubwave-editor__content .ProseMirror h1,.pubwave-editor__content .ProseMirror h2,.pubwave-editor__content .ProseMirror h3{line-height:var(--pubwave-line-height-heading);margin:0;font-weight:600}.pubwave-editor__content .ProseMirror h1{font-size:var(--pubwave-font-size-3xl)}.pubwave-editor__content .ProseMirror h2{font-size:var(--pubwave-font-size-2xl)}.pubwave-editor__content .ProseMirror h3{font-size:var(--pubwave-font-size-xl)}.pubwave-editor__content .ProseMirror ul:not([data-type=taskList]),.pubwave-editor__content .ProseMirror ol{margin:0;padding-left:1.5em}.pubwave-editor__content .ProseMirror li:not([data-type=taskItem]):not(.pubwave-editor__task-item){margin:.25em 0}.pubwave-editor__content .ProseMirror li>p{margin:0}.pubwave-editor__content ul[data-type=taskList]{padding:var(--pubwave-spacing-4,16px)0;margin:0!important;padding-left:0!important;list-style:none!important}.pubwave-editor__content .ProseMirror ul[data-type=taskList] li,.pubwave-editor__content .ProseMirror ul[data-type=taskList]>li,.pubwave-editor__content .ProseMirror ul[data-type=taskList] li[data-type=taskItem],.pubwave-editor__content .ProseMirror ul[data-type=taskList] li.pubwave-editor__task-item,.pubwave-editor__content .ProseMirror .pubwave-editor__task-item,.pubwave-editor__content.ProseMirror ul[data-type=taskList] li,.ProseMirror.pubwave-editor__content ul[data-type=taskList] li{border-radius:var(--pubwave-radius-sm,4px)!important;-webkit-appearance:none!important;-moz-appearance:none!important;appearance:none!important;flex-direction:row!important;align-items:center!important;gap:.5em!important;margin:0!important;padding:0!important;list-style:none!important;display:flex!important}.pubwave-editor__task-item{height:var(--pubwave-task-item-height,30px)}.pubwave-editor__content .ProseMirror ul[data-type=taskList] li::marker{content:""!important;width:0!important;font-size:0!important;display:none!important}.pubwave-editor__content .ProseMirror ul[data-type=taskList] li[data-type=taskItem]::marker{content:""!important;width:0!important;font-size:0!important;display:none!important}.pubwave-editor__content .ProseMirror ul[data-type=taskList] li.pubwave-editor__task-item::marker{content:""!important;width:0!important;font-size:0!important;display:none!important}.pubwave-editor__content .ProseMirror ul[data-type=taskList] li:before,.pubwave-editor__content .ProseMirror ul[data-type=taskList] li[data-type=taskItem]:before,.pubwave-editor__content .ProseMirror ul[data-type=taskList] li.pubwave-editor__task-item:before{content:""!important;width:0!important;display:none!important}.pubwave-editor__content .ProseMirror ul[data-type=taskList] li:hover,.pubwave-editor__content .ProseMirror ul[data-type=taskList] li.ProseMirror-selectednode{background-color:var(--pubwave-hover-task,#e8f2ff)}.pubwave-editor__content .ProseMirror ul[data-type=taskList] li>label,.pubwave-editor__content .ProseMirror ul[data-type=taskList] li[data-type=taskItem]>label,.pubwave-editor__content .ProseMirror ul[data-type=taskList] li.pubwave-editor__task-item>label{cursor:pointer!important;width:var(--pubwave-checkbox-size,20px)!important;height:var(--pubwave-checkbox-size,20px)!important;min-height:var(--pubwave-checkbox-size,20px)!important;max-height:var(--pubwave-checkbox-size,20px)!important;flex-shrink:0!important;align-self:center!important;margin:0!important;padding:0!important;line-height:1!important;display:block!important;position:relative!important;overflow:hidden!important}li>label input[type=checkbox]{width:var(--pubwave-checkbox-input-size,16px);height:var(--pubwave-checkbox-input-size,16px);cursor:pointer;margin-top:5px}.pubwave-editor__content .ProseMirror ul[data-type=taskList] li>label:before{content:"";width:var(--pubwave-checkbox-size,20px);height:var(--pubwave-checkbox-size,20px);border:1.5px solid var(--pubwave-text,#1a1a1a);border-radius:var(--pubwave-radius-xs,3px);background-color:var(--pubwave-bg,#fff);box-sizing:border-box;transition:all .15s;display:block;position:absolute;top:0;left:0}.pubwave-editor__content .ProseMirror ul[data-type=taskList] li[data-checked=true]>label:before{background-color:var(--pubwave-primary-hover,#2563eb);border-color:var(--pubwave-primary-hover,#2563eb)}.pubwave-editor__content .ProseMirror ul[data-type=taskList] li[data-checked=true]>label:after{content:"";border:solid var(--pubwave-bg,#fff);box-sizing:border-box;z-index:1;border-width:0 2px 2px 0;width:5px;height:10px;position:absolute;top:3px;left:6px;transform:rotate(45deg)}.pubwave-editor__content ul[data-type=taskList] li>div,.pubwave-editor__content ul[data-type=taskList] li[data-type=taskItem]>div,.pubwave-editor__content ul[data-type=taskList] li.pubwave-editor__task-item>div{min-width:0!important;color:var(--pubwave-text,#1a1a1a)!important;flex:1!important;margin:0!important;padding:.1em 0!important;display:block!important}.pubwave-editor__content ul[data-type=taskList] li>div p,.pubwave-editor__content ul[data-type=taskList] li[data-type=taskItem]>div p,.pubwave-editor__content ul[data-type=taskList] li.pubwave-editor__task-item>div p,.pubwave-editor__content ul[data-type=taskList] li>div>p,.pubwave-editor__content ul[data-type=taskList] li>div p.pubwave-editor__paragraph,.pubwave-editor__content ul[data-type=taskList] li[data-type=taskItem]>div p.pubwave-editor__paragraph{width:100%!important;min-height:1.5em!important;margin:0!important;padding:0!important;line-height:1.5!important;display:block!important}.pubwave-editor__content.ProseMirror ul[data-type=taskList] li[data-checked=true]>div p,.pubwave-editor__content ul[data-type=taskList] li[data-checked=true]>div p{color:var(--pubwave-checked-text,#9ca3af)!important;text-decoration:line-through!important}.pubwave-editor__content blockquote,.pubwave-editor__content.ProseMirror blockquote{margin:.5em 0;padding-left:1em;font-style:italic;border-left:3px solid var(--pubwave-text,#1a1a1a)!important}.pubwave-editor__content .ProseMirror pre,.pubwave-editor__content pre{margin:.5em 0;padding:.5em .75em;line-height:1.5;overflow-x:auto;background:var(--pubwave-border-light,#f3f4f6)!important;border-radius:var(--pubwave-radius-md,6px)!important;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace!important;font-size:var(--pubwave-font-size-sm,13px)!important;color:#1a1a1a!important}.pubwave-editor__content .ProseMirror pre code,.pubwave-editor__content pre code{font-size:inherit!important;color:inherit!important;background:0 0!important;border-radius:0!important;padding:0!important;font-family:inherit!important}.pubwave-block{padding-left:2.5em;position:relative}.pubwave-block-menu{z-index:2;align-items:center;gap:.5em;display:flex;position:absolute;top:50%;left:.5em;transform:translateY(-50%)}.pubwave-editor__content .ProseMirror hr,.pubwave-editor__content hr,.pubwave-editor__content .pubwave-editor__divider{border:none;border-top:1px solid var(--pubwave-border,#e5e7eb)!important;background:0 0!important;height:0!important;margin:1em 0!important}.pubwave-editor__content .ProseMirror img,.pubwave-editor__content img,.pubwave-editor__content .pubwave-editor__image{border-radius:var(--pubwave-radius-md,6px);cursor:pointer;max-width:100%;height:auto;margin:1em auto;transition:opacity .2s;display:block;box-shadow:0 1px 3px #0000001a}.pubwave-editor__content .ProseMirror img:hover,.pubwave-editor__content img:hover,.pubwave-editor__content .pubwave-editor__image:hover{opacity:.9}.pubwave-editor__content .ProseMirror img.ProseMirror-selectednode,.pubwave-editor__content img.ProseMirror-selectednode,.pubwave-editor__content .pubwave-editor__image.ProseMirror-selectednode{outline:2px solid var(--pubwave-primary,#2383e2);outline-offset:2px}.pubwave-editor__content .ProseMirror strong{font-weight:600}.pubwave-editor__content .ProseMirror em{font-style:italic}.pubwave-editor__content .ProseMirror u{text-decoration:underline}.pubwave-editor__content .ProseMirror s{text-decoration:line-through}.pubwave-editor__content .ProseMirror code{background:var(--pubwave-hover);border-radius:var(--pubwave-radius-sm);font-family:var(--pubwave-font-mono);padding:.15em .35em;font-size:.9em}.pubwave-editor__content .ProseMirror a,.pubwave-editor .ProseMirror a{cursor:pointer;text-decoration:underline;color:var(--pubwave-link-color,#2383e2)!important}.pubwave-editor__content .ProseMirror a:hover,.pubwave-editor .ProseMirror a:hover{opacity:.8;color:var(--pubwave-link-hover-color,var(--pubwave-link-color,#2383e2))!important}.pubwave-editor__content .ProseMirror ::-moz-selection{background-color:var(--pubwave-selection-bg)}.pubwave-editor__content .ProseMirror ::selection{background-color:var(--pubwave-selection-bg)}.pubwave-editor--readonly,.pubwave-editor--readonly .pubwave-editor__content{cursor:default}.pubwave-block--dragging{opacity:.4}.pubwave-block--drag-preview{color:var(--pubwave-text-muted,#9b9a97)!important;opacity:.6!important}.pubwave-block--drag-preview div,.pubwave-block--drag-preview p,.pubwave-block--drag-preview label,.pubwave-block--drag-preview .pubwave-editor__task-item>div{color:var(--pubwave-text-muted,#9b9a97)!important}.pubwave-block--drag-preview label input{display:none!important}.pubwave-block--drag-preview label:before{border-color:var(--pubwave-text-muted,#9b9a97)!important;width:16px!important;height:16px!important}.pubwave-block--drag-preview label:after{border-color:var(--pubwave-bg,#fff)!important;top:2px!important;left:4px!important}.ProseMirror .ProseMirror-gapcursor{pointer-events:none;display:none;position:absolute}.ProseMirror .ProseMirror-gapcursor:after{content:"";border-top:1px solid var(--pubwave-text);width:20px;animation:1.1s steps(2,start) infinite pubwave-cursor-blink;display:block;position:absolute;top:-2px}@keyframes pubwave-cursor-blink{to{visibility:hidden}}.ProseMirror-focused .ProseMirror-gapcursor{display:block}.tippy-box{z-index:var(--pubwave-z-dropdown,60)!important;background:0 0!important;border:none!important}.tippy-box:has(.pubwave-slash-menu){background:0 0!important;border:none!important}.pubwave-slash-menu{border:1px solid var(--pubwave-border,#e3e2e0)!important}.ProseMirror-dropcursor{border-left:2px solid var(--pubwave-primary);pointer-events:none}.pubwave-editor .ProseMirror .suggestion{border:1px solid var(--pubwave-border,#e3e2e0)!important;border-radius:var(--pubwave-radius-md,6px)!important;color:#1f2937!important;background-color:#fffffff2!important;padding:2px 4px!important;display:inline-block!important}.pubwave-editor .ProseMirror .suggestion,.pubwave-editor .ProseMirror .suggestion *,.pubwave-editor .ProseMirror .suggestion span,.pubwave-editor .ProseMirror .suggestion strong,.pubwave-editor .ProseMirror .suggestion em,.pubwave-editor .ProseMirror .suggestion code{color:#1f2937!important}.tippy-box,.tippy-box[data-theme]{z-index:60!important}.tippy-box[data-theme~=pubwave]{box-shadow:none;background:0 0;border:none}.tippy-box[data-theme~=pubwave] .tippy-content{padding:0}.tippy-box[data-theme~=light-border]{border-radius:var(--pubwave-radius,6px);box-shadow:var(--pubwave-shadow-md,0 4px 6px -1px #0000001a);padding:4px 8px;font-size:12px;color:#fff!important;background-color:#000!important;border:none!important}.tippy-box[data-theme~=light-border] .tippy-content{color:#fff!important}.pubwave-block-handle__drag,.pubwave-block-handle__add{color:var(--pubwave-text-muted,#9b9a97)!important}.pubwave-toolbar__button:not(:disabled):hover,.pubwave-toolbar__turn-into-option:not(.pubwave-toolbar__turn-into-option--active):hover{background-color:var(--pubwave-hover)!important}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1}
2
+ @layer properties{@supports ((-webkit-hyphens:none) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.isolate{isolation:isolate}.container{width:100%}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.resize{resize:both}.border{border-style:var(--tw-border-style);border-width:1px}.uppercase{text-transform:uppercase}.italic{font-style:italic}.underline{text-decoration-line:underline}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}:root{--pubwave-bg:#fff;--pubwave-surface:#fff;--pubwave-text:#37352f;--pubwave-text-muted:#9b9a97;--pubwave-border:#e3e2e0;--pubwave-border-light:#f3f4f6;--pubwave-hover:#f7f6f3;--pubwave-focus:#0000000d;--pubwave-selection:#3b82f6;--pubwave-selection-bg:#2383e226;--pubwave-primary:#2383e2;--pubwave-primary-hover:#2563eb;--pubwave-handle-color:#9b9a97;--pubwave-primary-faded:#3b82f61a;--pubwave-drop-indicator:#3b82f6;--pubwave-drop-target:#3b82f60d;--pubwave-error:#ef4444;--pubwave-success:#10b981;--pubwave-warning:#f59e0b;--pubwave-text-secondary:#374151;--pubwave-text-tertiary:#4b5563;--pubwave-hover-bg:#f3f4f6;--pubwave-hover-task:#e8f2ff;--pubwave-checked-text:#9ca3af;--pubwave-spacing-xs:.25rem;--pubwave-spacing-sm:.5rem;--pubwave-spacing-md:1rem;--pubwave-spacing-lg:1.5rem;--pubwave-spacing-xl:2rem;--pubwave-spacing-1:4px;--pubwave-spacing-2:8px;--pubwave-spacing-3:12px;--pubwave-spacing-4:16px;--pubwave-spacing-7:28px;--pubwave-font-family:inherit;--pubwave-font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--pubwave-font-size-xs:11px;--pubwave-font-size-sm:13px;--pubwave-font-size-base:14px;--pubwave-font-size-md:1rem;--pubwave-font-size-lg:1.125rem;--pubwave-font-size-xl:1.25rem;--pubwave-font-size-2xl:1.5rem;--pubwave-font-size-3xl:2rem;--pubwave-line-height:1.625;--pubwave-line-height-heading:1.25;--pubwave-button-width:28px;--pubwave-button-height:28px;--pubwave-icon-size:20px;--pubwave-checkbox-size:20px;--pubwave-checkbox-input-size:16px;--pubwave-divider-width:1px;--pubwave-divider-height:20px;--pubwave-drop-indicator-width:2px;--pubwave-drop-indicator-height:2px;--pubwave-task-item-height:30px;--pubwave-radius-xs:3px;--pubwave-radius-sm:4px;--pubwave-radius:6px;--pubwave-radius-md:6px;--pubwave-radius-lg:8px;--pubwave-shadow-sm:0 1px 2px 0 #0000000d;--pubwave-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;--pubwave-shadow-lg:0 10px 15px -3px #0000001a,0 4px 6px -2px #0000000d}@media (prefers-color-scheme:dark){:root{--pubwave-bg:#1a1a1a;--pubwave-surface:#262626;--pubwave-text:#e5e7eb;--pubwave-text-muted:#9ca3af;--pubwave-border:#374151;--pubwave-border-light:#4b5563;--pubwave-hover:#ffffff0d;--pubwave-focus:#ffffff14;--pubwave-selection:#60a5fa;--pubwave-selection-bg:#60a5fa26;--pubwave-primary:#60a5fa;--pubwave-primary-hover:#3b82f6;--pubwave-primary-faded:#60a5fa26;--pubwave-drop-indicator:#60a5fa;--pubwave-drop-target:#60a5fa14;--pubwave-error:#f87171;--pubwave-text-secondary:#d1d5db;--pubwave-text-tertiary:#9ca3af;--pubwave-hover-bg:#374151;--pubwave-hover-task:#3b82f626;--pubwave-checked-text:#6b7280}}.pubwave-editor{width:var(--pubwave-container-width,100%);max-width:var(--pubwave-container-max-width,none);font-family:var(--pubwave-font-family);color:var(--pubwave-text);background:var(--pubwave-bg,#fff);border-radius:var(--pubwave-container-border-radius,16px);min-height:var(--pubwave-container-min-height,700px);padding:var(--pubwave-container-padding-y,96px)var(--pubwave-container-padding-x,120px);padding-left:var(--pubwave-container-padding-left,140px);position:relative;box-shadow:0 0 0 1px #0000000a,0 20px 25px -5px #0000000d,0 10px 10px -5px #00000005}@media (max-width:768px){.pubwave-editor{padding:var(--pubwave-container-padding-y-mobile,20px)var(--pubwave-container-padding-x-mobile,16px);padding-left:var(--pubwave-container-padding-left-mobile,16px);border-radius:var(--pubwave-container-border-radius-mobile,12px);min-height:var(--pubwave-container-min-height-mobile,400px)}}.pubwave-editor,.pubwave-editor *,.pubwave-editor :before,.pubwave-editor :after{box-sizing:border-box}.pubwave-editor__content{outline:none;flex-direction:column;min-height:1em;display:flex}.pubwave-editor__content .ProseMirror{min-height:100%;padding-bottom:30vh;padding-top:var(--pubwave-spacing-md,1rem);outline:none;flex-grow:1}.pubwave-editor__content .ProseMirror p.is-editor-empty:first-child:before,.pubwave-editor__content.ProseMirror-focused p.pubwave-editor__node--empty:before,.ProseMirror.ProseMirror-focused p.pubwave-editor__node--empty:before{content:attr(data-placeholder);float:left;color:var(--pubwave-text-muted);pointer-events:none;height:0}.pubwave-editor__content .ProseMirror>*+*{margin-top:.75em}@media (max-width:768px){.pubwave-editor__content .ProseMirror>*+*{margin-top:.35em}.pubwave-editor__content .ProseMirror h1+*,.pubwave-editor__content .ProseMirror h2+*,.pubwave-editor__content .ProseMirror h3+*{margin-top:.4em}.pubwave-editor__content .ProseMirror h1,.pubwave-editor__content .ProseMirror h2,.pubwave-editor__content .ProseMirror h3{margin-top:.6em}.pubwave-editor__content .ProseMirror{font-size:var(--pubwave-font-size-base-mobile,15px)}.pubwave-editor__content .ProseMirror h1{font-size:var(--pubwave-font-size-3xl-mobile,1.75rem)}.pubwave-editor__content .ProseMirror h2{font-size:var(--pubwave-font-size-2xl-mobile,1.375rem)}.pubwave-editor__content .ProseMirror h3{font-size:var(--pubwave-font-size-xl-mobile,1.125rem)}}.pubwave-editor__content .pubwave-editor__bullet-list,.pubwave-editor__content .pubwave-editor__ordered-list{padding-left:1.3em!important}.pubwave-editor__content .ProseMirror p{line-height:var(--pubwave-line-height);margin:0}.pubwave-editor__content .ProseMirror h1,.pubwave-editor__content .ProseMirror h2,.pubwave-editor__content .ProseMirror h3{line-height:var(--pubwave-line-height-heading);margin:0;font-weight:600}.pubwave-editor__content .ProseMirror h1{font-size:var(--pubwave-font-size-3xl)}.pubwave-editor__content .ProseMirror h2{font-size:var(--pubwave-font-size-2xl)}.pubwave-editor__content .ProseMirror h3{font-size:var(--pubwave-font-size-xl)}.pubwave-editor__content .ProseMirror ul:not([data-type=taskList]),.pubwave-editor__content .ProseMirror ol{margin:0;padding-left:1.5em}.pubwave-editor__content .ProseMirror li:not([data-type=taskItem]):not(.pubwave-editor__task-item){margin:.25em 0}.pubwave-editor__content .ProseMirror li>p{margin:0}.pubwave-editor__content ul[data-type=taskList]{padding:var(--pubwave-spacing-4,16px)0;margin:0!important;padding-left:0!important;list-style:none!important}.pubwave-editor__content .ProseMirror ul[data-type=taskList] li,.pubwave-editor__content .ProseMirror ul[data-type=taskList]>li,.pubwave-editor__content .ProseMirror ul[data-type=taskList] li[data-type=taskItem],.pubwave-editor__content .ProseMirror ul[data-type=taskList] li.pubwave-editor__task-item,.pubwave-editor__content .ProseMirror .pubwave-editor__task-item,.pubwave-editor__content.ProseMirror ul[data-type=taskList] li,.ProseMirror.pubwave-editor__content ul[data-type=taskList] li{border-radius:var(--pubwave-radius-sm,4px)!important;-webkit-appearance:none!important;-moz-appearance:none!important;appearance:none!important;flex-direction:row!important;align-items:center!important;gap:.5em!important;margin:0!important;padding:0!important;list-style:none!important;display:flex!important}.pubwave-editor__task-item{height:var(--pubwave-task-item-height,30px)}.pubwave-editor__content .ProseMirror ul[data-type=taskList] li::marker{content:""!important;width:0!important;font-size:0!important;display:none!important}.pubwave-editor__content .ProseMirror ul[data-type=taskList] li[data-type=taskItem]::marker{content:""!important;width:0!important;font-size:0!important;display:none!important}.pubwave-editor__content .ProseMirror ul[data-type=taskList] li.pubwave-editor__task-item::marker{content:""!important;width:0!important;font-size:0!important;display:none!important}.pubwave-editor__content .ProseMirror ul[data-type=taskList] li:before,.pubwave-editor__content .ProseMirror ul[data-type=taskList] li[data-type=taskItem]:before,.pubwave-editor__content .ProseMirror ul[data-type=taskList] li.pubwave-editor__task-item:before{content:""!important;width:0!important;display:none!important}.pubwave-editor__content .ProseMirror ul[data-type=taskList] li:hover,.pubwave-editor__content .ProseMirror ul[data-type=taskList] li.ProseMirror-selectednode{background-color:var(--pubwave-hover-task,#e8f2ff)}.pubwave-editor__content .ProseMirror ul[data-type=taskList] li>label,.pubwave-editor__content .ProseMirror ul[data-type=taskList] li[data-type=taskItem]>label,.pubwave-editor__content .ProseMirror ul[data-type=taskList] li.pubwave-editor__task-item>label{cursor:pointer!important;width:var(--pubwave-checkbox-size,20px)!important;height:var(--pubwave-checkbox-size,20px)!important;min-height:var(--pubwave-checkbox-size,20px)!important;max-height:var(--pubwave-checkbox-size,20px)!important;flex-shrink:0!important;align-self:center!important;margin:0!important;padding:0!important;line-height:1!important;display:block!important;position:relative!important;overflow:hidden!important}li>label input[type=checkbox]{width:var(--pubwave-checkbox-input-size,16px);height:var(--pubwave-checkbox-input-size,16px);cursor:pointer;margin-top:5px}.pubwave-editor__content .ProseMirror ul[data-type=taskList] li>label:before{content:"";width:var(--pubwave-checkbox-size,20px);height:var(--pubwave-checkbox-size,20px);border:1.5px solid var(--pubwave-text,#1a1a1a);border-radius:var(--pubwave-radius-xs,3px);background-color:var(--pubwave-bg,#fff);box-sizing:border-box;transition:all .15s;display:block;position:absolute;top:0;left:0}.pubwave-editor__content .ProseMirror ul[data-type=taskList] li[data-checked=true]>label:before{background-color:var(--pubwave-primary-hover,#2563eb);border-color:var(--pubwave-primary-hover,#2563eb)}.pubwave-editor__content .ProseMirror ul[data-type=taskList] li[data-checked=true]>label:after{content:"";border:solid var(--pubwave-bg,#fff);box-sizing:border-box;z-index:1;border-width:0 2px 2px 0;width:5px;height:10px;position:absolute;top:3px;left:6px;transform:rotate(45deg)}.pubwave-editor__content ul[data-type=taskList] li>div,.pubwave-editor__content ul[data-type=taskList] li[data-type=taskItem]>div,.pubwave-editor__content ul[data-type=taskList] li.pubwave-editor__task-item>div{min-width:0!important;color:var(--pubwave-text,#1a1a1a)!important;flex:1!important;margin:0!important;padding:.1em 0!important;display:block!important}.pubwave-editor__content ul[data-type=taskList] li>div p,.pubwave-editor__content ul[data-type=taskList] li[data-type=taskItem]>div p,.pubwave-editor__content ul[data-type=taskList] li.pubwave-editor__task-item>div p,.pubwave-editor__content ul[data-type=taskList] li>div>p,.pubwave-editor__content ul[data-type=taskList] li>div p.pubwave-editor__paragraph,.pubwave-editor__content ul[data-type=taskList] li[data-type=taskItem]>div p.pubwave-editor__paragraph{width:100%!important;min-height:1.5em!important;margin:0!important;padding:0!important;line-height:1.5!important;display:block!important}.pubwave-editor__content.ProseMirror ul[data-type=taskList] li[data-checked=true]>div p,.pubwave-editor__content ul[data-type=taskList] li[data-checked=true]>div p{color:var(--pubwave-checked-text,#9ca3af)!important;text-decoration:line-through!important}.pubwave-editor__content blockquote,.pubwave-editor__content.ProseMirror blockquote{margin:.5em 0;padding-left:1em;font-style:italic;border-left:3px solid var(--pubwave-text,#1a1a1a)!important}.pubwave-editor__content .ProseMirror pre,.pubwave-editor__content pre{margin:.5em 0;padding:.5em .75em;line-height:1.5;overflow-x:auto;background:var(--pubwave-border-light,#f3f4f6)!important;border-radius:var(--pubwave-radius-md,6px)!important;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace!important;font-size:var(--pubwave-font-size-sm,13px)!important;color:#1a1a1a!important}.pubwave-editor__content .ProseMirror pre code,.pubwave-editor__content pre code{font-size:inherit!important;color:inherit!important;background:0 0!important;border-radius:0!important;padding:0!important;font-family:inherit!important}.pubwave-block{padding-left:2.5em;position:relative}.pubwave-block-menu{z-index:2;align-items:center;gap:.5em;display:flex;position:absolute;top:50%;left:.5em;transform:translateY(-50%)}.pubwave-editor__content .ProseMirror hr,.pubwave-editor__content hr,.pubwave-editor__content .pubwave-editor__divider{border:none;border-top:1px solid var(--pubwave-border,#e5e7eb)!important;background:0 0!important;height:0!important;margin:1em 0!important}.pubwave-editor__content .ProseMirror img,.pubwave-editor__content img,.pubwave-editor__content .pubwave-editor__image{border-radius:var(--pubwave-radius-md,6px);cursor:pointer;max-width:100%;height:auto;margin:1em auto;transition:opacity .2s;display:block;box-shadow:0 1px 3px #0000001a}.pubwave-editor__content .ProseMirror img:hover,.pubwave-editor__content img:hover,.pubwave-editor__content .pubwave-editor__image:hover{opacity:.9}.pubwave-editor__content .ProseMirror img.ProseMirror-selectednode,.pubwave-editor__content img.ProseMirror-selectednode,.pubwave-editor__content .pubwave-editor__image.ProseMirror-selectednode{outline:2px solid var(--pubwave-primary,#2383e2);outline-offset:2px}.pubwave-editor__content .ProseMirror strong{font-weight:600}.pubwave-editor__content .ProseMirror em{font-style:italic}.pubwave-editor__content .ProseMirror u{text-decoration:underline}.pubwave-editor__content .ProseMirror s{text-decoration:line-through}.pubwave-editor__content .ProseMirror code{background:var(--pubwave-hover);border-radius:var(--pubwave-radius-sm);font-family:var(--pubwave-font-mono);padding:.15em .35em;font-size:.9em}.pubwave-editor__content .ProseMirror a,.pubwave-editor .ProseMirror a{cursor:pointer;text-decoration:underline;color:var(--pubwave-link-color,#2383e2)!important}.pubwave-editor__content .ProseMirror a:hover,.pubwave-editor .ProseMirror a:hover{opacity:.8;color:var(--pubwave-link-hover-color,var(--pubwave-link-color,#2383e2))!important}.pubwave-editor__content .ProseMirror ::-moz-selection{background-color:var(--pubwave-selection-bg)}.pubwave-editor__content .ProseMirror ::selection{background-color:var(--pubwave-selection-bg)}.pubwave-editor--readonly,.pubwave-editor--readonly .pubwave-editor__content{cursor:default}.pubwave-block--dragging{opacity:.4}.pubwave-block--drag-preview{color:var(--pubwave-text-muted,#9b9a97)!important;opacity:.6!important}.pubwave-block--drag-preview div,.pubwave-block--drag-preview p,.pubwave-block--drag-preview label,.pubwave-block--drag-preview .pubwave-editor__task-item>div{color:var(--pubwave-text-muted,#9b9a97)!important}.pubwave-block--drag-preview label input{display:none!important}.pubwave-block--drag-preview label:before{border-color:var(--pubwave-text-muted,#9b9a97)!important;width:16px!important;height:16px!important}.pubwave-block--drag-preview label:after{border-color:var(--pubwave-bg,#fff)!important;top:2px!important;left:4px!important}.ProseMirror .ProseMirror-gapcursor{pointer-events:none;display:none;position:absolute}.ProseMirror .ProseMirror-gapcursor:after{content:"";border-top:1px solid var(--pubwave-text);width:20px;animation:1.1s steps(2,start) infinite pubwave-cursor-blink;display:block;position:absolute;top:-2px}@keyframes pubwave-cursor-blink{to{visibility:hidden}}.ProseMirror-focused .ProseMirror-gapcursor{display:block}.tippy-box{z-index:var(--pubwave-z-dropdown,60)!important;background:0 0!important;border:none!important}.tippy-box:has(.pubwave-slash-menu){background:0 0!important;border:none!important}.pubwave-slash-menu{border:1px solid var(--pubwave-border,#e3e2e0)!important}.ProseMirror-dropcursor{border-left:2px solid var(--pubwave-primary);pointer-events:none}.pubwave-editor .ProseMirror .suggestion{border:1px solid var(--pubwave-border,#e3e2e0)!important;border-radius:var(--pubwave-radius-md,6px)!important;color:#1f2937!important;background-color:#fffffff2!important;padding:2px 4px!important;display:inline-block!important}.pubwave-editor .ProseMirror .suggestion,.pubwave-editor .ProseMirror .suggestion *,.pubwave-editor .ProseMirror .suggestion span,.pubwave-editor .ProseMirror .suggestion strong,.pubwave-editor .ProseMirror .suggestion em,.pubwave-editor .ProseMirror .suggestion code{color:#1f2937!important}.tippy-box,.tippy-box[data-theme]{z-index:60!important}.tippy-box[data-theme~=pubwave]{box-shadow:none;background:0 0;border:none}.tippy-box[data-theme~=pubwave] .tippy-content{padding:0}.tippy-box[data-theme~=light-border]{border-radius:var(--pubwave-radius,6px);box-shadow:var(--pubwave-shadow-md,0 4px 6px -1px #0000001a);padding:4px 8px;font-size:12px;color:#fff!important;background-color:#000!important;border:none!important}.tippy-box[data-theme~=light-border] .tippy-content{color:#fff!important}.pubwave-block-handle__drag,.pubwave-block-handle__add{color:var(--pubwave-text-muted,#9b9a97)!important}.pubwave-toolbar__button:not(:disabled):hover,.pubwave-toolbar__turn-into-option:not(.pubwave-toolbar__turn-into-option--active):hover{background-color:var(--pubwave-hover)!important}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1}
package/dist/index.d.ts CHANGED
@@ -759,6 +759,19 @@ export declare function isEditorValid(editor: Editor_2 | null): editor is Editor
759
759
  */
760
760
  export declare function isMarkActive(editor: Editor_2, markType: string): boolean;
761
761
 
762
+ /**
763
+ * Check if the device is a mobile/touch device
764
+ * Returns true for mobile devices, false for desktop
765
+ *
766
+ * This function safely checks for mobile device characteristics:
767
+ * - Touch support
768
+ * - Coarse pointer type (touch screen)
769
+ * - Hover capability (mobile devices typically don't support hover)
770
+ *
771
+ * @returns true if the device appears to be a mobile/touch device, false otherwise
772
+ */
773
+ export declare function isMobileDevice(): boolean;
774
+
762
775
  /**
763
776
  * Check if a node type is currently active
764
777
  */
package/dist/index.js CHANGED
@@ -461,6 +461,13 @@ function safeRequestAnimationFrame(callback) {
461
461
  }
462
462
  return 0;
463
463
  }
464
+ function isMobileDevice() {
465
+ if (!canUseDOM) return false;
466
+ const hasTouchSupport = "ontouchstart" in window || navigator.maxTouchPoints > 0;
467
+ const hasCoarsePointer = window.matchMedia("(pointer: coarse)").matches;
468
+ const hasHover = window.matchMedia("(hover: hover)").matches;
469
+ return hasTouchSupport && hasCoarsePointer || hasTouchSupport && !hasHover;
470
+ }
464
471
  function TextIcon() {
465
472
  return /* @__PURE__ */ jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx("path", { d: "M4 7V4h16v3M9 20h6M12 4v16" }) });
466
473
  }
@@ -4845,6 +4852,89 @@ function calculateVerticalPositionFromRect(triggerRect, dropdownHeight, margin =
4845
4852
  margin
4846
4853
  );
4847
4854
  }
4855
+ function calculateHorizontalPosition(triggerLeft, triggerRight, dropdownWidth, preferredAlign = "left", margin = 8) {
4856
+ const viewportWidth = window.innerWidth;
4857
+ const triggerCenter = (triggerLeft + triggerRight) / 2;
4858
+ const spaceLeft = triggerLeft;
4859
+ const spaceRight = viewportWidth - triggerRight;
4860
+ const requiredSpaceForLeft = dropdownWidth + margin;
4861
+ const requiredSpaceForRight = dropdownWidth + margin;
4862
+ const requiredSpaceForCenter = dropdownWidth / 2 + margin;
4863
+ let align = preferredAlign;
4864
+ let left2;
4865
+ if (preferredAlign === "left") {
4866
+ left2 = triggerLeft;
4867
+ if (left2 < margin || left2 + dropdownWidth > viewportWidth - margin) {
4868
+ if (spaceRight >= requiredSpaceForRight && triggerRight + dropdownWidth <= viewportWidth - margin) {
4869
+ align = "right";
4870
+ left2 = triggerRight - dropdownWidth;
4871
+ } else if (spaceLeft >= requiredSpaceForCenter && triggerCenter - dropdownWidth / 2 >= margin) {
4872
+ align = "center";
4873
+ left2 = triggerCenter - dropdownWidth / 2;
4874
+ } else {
4875
+ if (spaceRight >= spaceLeft) {
4876
+ align = "right";
4877
+ left2 = triggerRight - dropdownWidth;
4878
+ } else {
4879
+ align = "left";
4880
+ left2 = triggerLeft;
4881
+ }
4882
+ left2 = Math.max(margin, Math.min(left2, viewportWidth - dropdownWidth - margin));
4883
+ }
4884
+ }
4885
+ } else if (preferredAlign === "center") {
4886
+ left2 = triggerCenter - dropdownWidth / 2;
4887
+ if (left2 < margin || left2 + dropdownWidth > viewportWidth - margin) {
4888
+ if (spaceRight >= requiredSpaceForRight && triggerRight + dropdownWidth <= viewportWidth - margin) {
4889
+ align = "right";
4890
+ left2 = triggerRight - dropdownWidth;
4891
+ } else if (spaceLeft >= requiredSpaceForLeft && triggerLeft + dropdownWidth <= viewportWidth - margin) {
4892
+ align = "left";
4893
+ left2 = triggerLeft;
4894
+ } else {
4895
+ if (spaceRight >= spaceLeft) {
4896
+ align = "right";
4897
+ left2 = triggerRight - dropdownWidth;
4898
+ } else {
4899
+ align = "left";
4900
+ left2 = triggerLeft;
4901
+ }
4902
+ left2 = Math.max(margin, Math.min(left2, viewportWidth - dropdownWidth - margin));
4903
+ }
4904
+ }
4905
+ } else {
4906
+ left2 = triggerRight - dropdownWidth;
4907
+ if (left2 < margin || left2 + dropdownWidth > viewportWidth - margin) {
4908
+ if (spaceLeft >= requiredSpaceForLeft && triggerLeft + dropdownWidth <= viewportWidth - margin) {
4909
+ align = "left";
4910
+ left2 = triggerLeft;
4911
+ } else if (spaceLeft >= requiredSpaceForCenter && triggerCenter - dropdownWidth / 2 >= margin) {
4912
+ align = "center";
4913
+ left2 = triggerCenter - dropdownWidth / 2;
4914
+ } else {
4915
+ if (spaceLeft >= spaceRight) {
4916
+ align = "left";
4917
+ left2 = triggerLeft;
4918
+ } else {
4919
+ align = "right";
4920
+ left2 = triggerRight - dropdownWidth;
4921
+ }
4922
+ left2 = Math.max(margin, Math.min(left2, viewportWidth - dropdownWidth - margin));
4923
+ }
4924
+ }
4925
+ }
4926
+ left2 = Math.max(margin, Math.min(left2, viewportWidth - dropdownWidth - margin));
4927
+ return { align, left: left2 };
4928
+ }
4929
+ function calculateHorizontalPositionFromRect(triggerRect, dropdownWidth, preferredAlign = "left", margin = 8) {
4930
+ return calculateHorizontalPosition(
4931
+ triggerRect.left,
4932
+ triggerRect.right,
4933
+ dropdownWidth,
4934
+ preferredAlign,
4935
+ margin
4936
+ );
4937
+ }
4848
4938
  function PositionedDropdown({
4849
4939
  isOpen,
4850
4940
  buttonRef,
@@ -4857,7 +4947,8 @@ function PositionedDropdown({
4857
4947
  onClick,
4858
4948
  "data-testid": dataTestId
4859
4949
  }) {
4860
- const [position, setPosition] = useState("top");
4950
+ const [verticalPosition, setVerticalPosition] = useState("top");
4951
+ const [horizontalPosition, setHorizontalPosition] = useState(null);
4861
4952
  const dropdownRef = useRef(null);
4862
4953
  useEffect(() => {
4863
4954
  if (!isOpen || !buttonRef.current) return;
@@ -4865,15 +4956,37 @@ function PositionedDropdown({
4865
4956
  if (!dropdownRef.current || !buttonRef.current) return;
4866
4957
  const buttonRect = buttonRef.current.getBoundingClientRect();
4867
4958
  const dropdownHeight = dropdownRef.current.offsetHeight || 300;
4868
- const newPosition = calculateVerticalPositionFromRect(buttonRect, dropdownHeight, margin);
4869
- setPosition(newPosition);
4959
+ const dropdownWidth = dropdownRef.current.offsetWidth || 240;
4960
+ const newVerticalPosition = calculateVerticalPositionFromRect(buttonRect, dropdownHeight, margin);
4961
+ setVerticalPosition(newVerticalPosition);
4962
+ let parentContainer = buttonRef.current.parentElement;
4963
+ while (parentContainer) {
4964
+ const style2 = window.getComputedStyle(parentContainer);
4965
+ if (style2.position === "relative" || style2.position === "absolute" || style2.position === "fixed") {
4966
+ break;
4967
+ }
4968
+ parentContainer = parentContainer.parentElement;
4969
+ }
4970
+ const horizontalPos = calculateHorizontalPositionFromRect(
4971
+ buttonRect,
4972
+ dropdownWidth,
4973
+ align,
4974
+ margin
4975
+ );
4976
+ if (parentContainer) {
4977
+ const parentRect = parentContainer.getBoundingClientRect();
4978
+ const relativeLeft = horizontalPos.left - parentRect.left;
4979
+ setHorizontalPosition({ align: horizontalPos.align, left: relativeLeft });
4980
+ } else {
4981
+ setHorizontalPosition(horizontalPos);
4982
+ }
4870
4983
  };
4871
4984
  checkPosition();
4872
4985
  const rafId = requestAnimationFrame(() => {
4873
4986
  checkPosition();
4874
4987
  });
4875
4988
  return () => cancelAnimationFrame(rafId);
4876
- }, [isOpen, buttonRef, margin]);
4989
+ }, [isOpen, buttonRef, margin, align]);
4877
4990
  useEffect(() => {
4878
4991
  if (!isOpen || !onClickOutside2) return;
4879
4992
  const handleClickOutside = (event) => {
@@ -4888,6 +5001,11 @@ function PositionedDropdown({
4888
5001
  }, [isOpen, onClickOutside2, buttonRef]);
4889
5002
  if (!isOpen) return null;
4890
5003
  const getHorizontalStyle = () => {
5004
+ if (horizontalPosition) {
5005
+ return {
5006
+ left: `${horizontalPosition.left}px`
5007
+ };
5008
+ }
4891
5009
  switch (align) {
4892
5010
  case "center":
4893
5011
  return {
@@ -4913,7 +5031,7 @@ function PositionedDropdown({
4913
5031
  "data-testid": dataTestId,
4914
5032
  style: {
4915
5033
  position: "absolute",
4916
- ...position === "top" ? {
5034
+ ...verticalPosition === "top" ? {
4917
5035
  bottom: "100%",
4918
5036
  marginBottom: `${margin}px`
4919
5037
  } : {
@@ -5983,8 +6101,6 @@ function calculatePosition(editor, toolbarEl) {
5983
6101
  const toolbarRect = toolbarEl.getBoundingClientRect();
5984
6102
  const toolbarWidth = toolbarRect.width;
5985
6103
  const toolbarHeight = toolbarRect.height;
5986
- const centerX = (selectionLeft + selectionRight) / 2;
5987
- let left2 = centerX - toolbarWidth / 2;
5988
6104
  const selectionBottom = Math.max(start2.bottom, end2.bottom);
5989
6105
  const verticalPosition = calculateVerticalPosition(
5990
6106
  selectionTop,
@@ -5998,20 +6114,21 @@ function calculatePosition(editor, toolbarEl) {
5998
6114
  } else {
5999
6115
  top2 = selectionBottom + TOOLBAR_OFFSET;
6000
6116
  }
6117
+ const horizontalPos = calculateHorizontalPosition(
6118
+ selectionLeft,
6119
+ selectionRight,
6120
+ toolbarWidth,
6121
+ "center",
6122
+ // Default to center alignment
6123
+ TOOLBAR_OFFSET
6124
+ );
6125
+ const left2 = horizontalPos.left;
6001
6126
  const viewportWidth = window.innerWidth;
6002
6127
  const viewportHeight = window.innerHeight;
6003
- const padding = 8;
6004
- if (left2 < padding) {
6005
- left2 = padding;
6006
- } else if (left2 + toolbarWidth > viewportWidth - padding) {
6007
- left2 = viewportWidth - toolbarWidth - padding;
6008
- }
6009
- if (top2 < padding) {
6010
- top2 = padding;
6011
- } else if (top2 + toolbarHeight > viewportHeight - padding) {
6012
- top2 = viewportHeight - toolbarHeight - padding;
6013
- }
6014
- return { top: top2, left: left2, visible: true };
6128
+ const padding = TOOLBAR_OFFSET;
6129
+ const clampedLeft = Math.max(padding, Math.min(left2, viewportWidth - toolbarWidth - padding));
6130
+ const clampedTop = Math.max(padding, Math.min(top2, viewportHeight - toolbarHeight - padding));
6131
+ return { top: clampedTop, left: clampedLeft, visible: true };
6015
6132
  }
6016
6133
  function BubbleToolbar({
6017
6134
  editor,
@@ -6256,6 +6373,7 @@ function BubbleToolbar({
6256
6373
  }
6257
6374
  );
6258
6375
  }
6376
+ const MIN_WINDOW_WIDTH = 768;
6259
6377
  function getClosestBlock(target, proseMirror, editor) {
6260
6378
  if (!target || !(target instanceof Node)) return null;
6261
6379
  try {
@@ -6300,18 +6418,33 @@ function BlockHandle({ editor }) {
6300
6418
  const [visible, setVisible] = useState(false);
6301
6419
  const [isDragging, setIsDragging] = useState(false);
6302
6420
  const [position, setPosition] = useState({ top: 0 });
6421
+ const [isMobile, setIsMobile] = useState(false);
6422
+ const [isWindowTooSmall, setIsWindowTooSmall] = useState(false);
6423
+ useEffect(() => {
6424
+ const checkDeviceAndWindow = () => {
6425
+ setIsMobile(isMobileDevice());
6426
+ setIsWindowTooSmall(window.innerWidth < MIN_WINDOW_WIDTH);
6427
+ };
6428
+ checkDeviceAndWindow();
6429
+ window.addEventListener("resize", checkDeviceAndWindow);
6430
+ return () => {
6431
+ window.removeEventListener("resize", checkDeviceAndWindow);
6432
+ };
6433
+ }, []);
6303
6434
  const updatePosition = useCallback(() => {
6304
6435
  const container = containerRef.current;
6305
6436
  const block = currentBlockRef.current;
6306
6437
  if (!container || !block) return;
6307
6438
  const editorContainer = container.closest(".pubwave-editor");
6308
6439
  if (!editorContainer) return;
6440
+ void editorContainer.offsetHeight;
6309
6441
  const editorRect = editorContainer.getBoundingClientRect();
6310
6442
  let offset2 = 14;
6311
6443
  let target = block;
6312
6444
  if (["UL", "OL", "BLOCKQUOTE"].includes(block.tagName) && block.firstElementChild instanceof HTMLElement) {
6313
6445
  target = block.firstElementChild;
6314
6446
  }
6447
+ void target.offsetHeight;
6315
6448
  const targetRect = target.getBoundingClientRect();
6316
6449
  try {
6317
6450
  const style = window.getComputedStyle(target);
@@ -6330,12 +6463,18 @@ function BlockHandle({ editor }) {
6330
6463
  } catch (e) {
6331
6464
  }
6332
6465
  const top2 = targetRect.top - editorRect.top + offset2;
6333
- setPosition({ top: top2 });
6466
+ if (!isNaN(top2) && isFinite(top2) && top2 >= 0) {
6467
+ setPosition({ top: top2 });
6468
+ }
6334
6469
  }, []);
6335
6470
  const showHandle = useCallback((block) => {
6336
6471
  currentBlockRef.current = block;
6337
6472
  setVisible(true);
6338
- setTimeout(updatePosition, 0);
6473
+ requestAnimationFrame(() => {
6474
+ requestAnimationFrame(() => {
6475
+ updatePosition();
6476
+ });
6477
+ });
6339
6478
  }, [updatePosition]);
6340
6479
  const hideHandle = useCallback(() => {
6341
6480
  if (!isDraggingRef.current) {
@@ -6397,8 +6536,16 @@ function BlockHandle({ editor }) {
6397
6536
  hideHandle();
6398
6537
  }, 150);
6399
6538
  };
6539
+ const onEditorMouseLeave = () => {
6540
+ if (isDraggingRef.current) return;
6541
+ clearHideTimeout();
6542
+ hideTimeoutRef.current = setTimeout(() => {
6543
+ hideHandle();
6544
+ }, 150);
6545
+ };
6400
6546
  proseMirror.addEventListener("mouseover", onMouseOver);
6401
6547
  proseMirror.addEventListener("mouseout", onMouseOut);
6548
+ editorContainer.addEventListener("mouseleave", onEditorMouseLeave);
6402
6549
  const handleTransaction = ({ transaction }) => {
6403
6550
  if (transaction.docChanged && !isDraggingRef.current) {
6404
6551
  hideHandle();
@@ -6408,10 +6555,20 @@ function BlockHandle({ editor }) {
6408
6555
  return () => {
6409
6556
  proseMirror.removeEventListener("mouseover", onMouseOver);
6410
6557
  proseMirror.removeEventListener("mouseout", onMouseOut);
6558
+ editorContainer.removeEventListener("mouseleave", onEditorMouseLeave);
6411
6559
  editor.off("transaction", handleTransaction);
6412
6560
  clearHideTimeout();
6413
6561
  };
6414
6562
  }, [editor, showHandle, hideHandle, clearHideTimeout]);
6563
+ useEffect(() => {
6564
+ if (visible && currentBlockRef.current) {
6565
+ requestAnimationFrame(() => {
6566
+ requestAnimationFrame(() => {
6567
+ updatePosition();
6568
+ });
6569
+ });
6570
+ }
6571
+ }, [visible, updatePosition]);
6415
6572
  useEffect(() => {
6416
6573
  if (!visible) return;
6417
6574
  const onScroll = () => {
@@ -6506,7 +6663,7 @@ function BlockHandle({ editor }) {
6506
6663
  }
6507
6664
  setVisible(false);
6508
6665
  }, []);
6509
- if (!visible) return null;
6666
+ if (isMobile || isWindowTooSmall || !visible) return null;
6510
6667
  return /* @__PURE__ */ jsxs(
6511
6668
  "div",
6512
6669
  {
@@ -7514,6 +7671,7 @@ export {
7514
7671
  isEditable,
7515
7672
  isEditorValid,
7516
7673
  isMarkActive,
7674
+ isMobileDevice,
7517
7675
  isNodeActive,
7518
7676
  isReadOnly,
7519
7677
  isSSR,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pubwave/editor",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "A Notion-level block editor built with React and Tiptap",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",